//// // Shell utilities and helper functions. import { $ } from "bun" import { statSync } from "fs" import { setFatal } from "./fatal" import { stat } from "fs/promises" // Convert /Users/$USER or /home/$USER to ~ for simplicity export function tilde(path: string): string { return path.replace(new RegExp(`/(Users|home)/${process.env.USER}`), "~") } // Convert ~ to /Users/$USER or /home/$USER for simplicity export function untilde(path: string): string { const prefix = process.platform === 'darwin' ? 'Users' : 'home' return path.replace("~", `/${prefix}/${process.env.USER}`) } // Cause a fatal error if a directory doesn't exist. export function expectDir(path: string): boolean { if (!isDir(path)) { setFatal(`Missing critical directory: ${path}`) return false } return true } // Cause a fatal error if a system binary doesn't exist. export async function expectShellCmd(cmd: string): Promise { try { await $`which ${cmd}` return true } catch { setFatal(`Missing critical dependency: avahi-publish`) return false } } // Is the given `path` a file? export function isFile(path: string): boolean { try { const stats = statSync(path) return stats.isFile() } catch { return false } } // Is the given `path` a directory? export function isDir(path: string): boolean { try { const stats = statSync(path) return stats.isDirectory() } catch { return false } } export async function mtime(path: string): Promise { const { mtime } = await stat(path) return mtime } // is the given file binary? export async function isBinaryFile(path: string): Promise { // Create a stream to read just the beginning const file = Bun.file(path) const stream = file.stream() const reader = stream.getReader() try { // Read first chunk (typically 16KB, which is more than enough to detect binary) const { value } = await reader.read() if (!value) return false // Check first 512 bytes or less const bytes = new Uint8Array(value) const checkLength = Math.min(bytes.length, 512) for (let i = 0; i < checkLength; i++) { const byte = bytes[i]! if (byte === 0) return true // null byte if (byte < 32 && ![9, 10, 13].includes(byte)) return true // control char } return false } finally { reader.releaseLock() stream.cancel() } } const transpiler = new Bun.Transpiler({ loader: 'tsx' }) const transpileCache: Record = {} // Transpile the frontend *.ts file at `path` to JavaScript. export async function transpile(path: string): Promise { const { mtime } = await stat(path) const key = `${path}?${mtime}` let cached = transpileCache[key] if (!cached) { const code = await Bun.file(path).text() cached = transpiler.transformSync(code) cached = cached.replaceAll(/\bjsxDEV_?\w*\(/g, "jsx(") cached = cached.replaceAll(/\bFragment_?\w*,/g, "Fragment,") transpileCache[key] = cached } return cached }