nose-pluto/app/src/utils.tsx
2025-09-27 18:10:46 -07:00

93 lines
2.6 KiB
TypeScript

////
// Shell utilities and helper functions.
import { statSync } from "fs"
import { basename } from "path"
import { stat } from "node:fs/promises"
import { NOSE_ICON } from "./config"
// End the process with an instructive error if a directory doesn't exist.
export function expectDir(path: string) {
if (!isDir(path)) {
console.error(NOSE_ICON)
console.error(`No ${basename(path)} directory detected. Please run:`)
console.error("\tmkdir -p", path)
process.exit(1)
}
}
// 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
}
}
// is the given file binary?
export async function isBinaryFile(path: string): Promise<boolean> {
// 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()
}
}
// 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}`), "~")
}
const transpiler = new Bun.Transpiler({ loader: 'tsx' })
const transpileCache: Record<string, string> = {}
// Transpile the frontend *.ts file at `path` to JavaScript.
export async function transpile(path: string): Promise<string> {
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
}