Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778// Snapshot an app directory (node_modules included unless `modules: false`) into the// shape runtime.mountSnapshot and runtime.mount take: a binary v3 format (`.snap`):// bytes 0..3: ASCII magic "NIBS"// bytes 4..7: u32 little-endian header length H// bytes 8..8+H: UTF-8 JSON header {"version":3,"files":[[path, size, mtimeMs], ...]}// then every file's raw bytes, concatenated in header order.// App-owned files are kept whatever their extension (static assets). Inside node_modules// only the files keepInPackage (src/shared/package-files.ts) keeps, as runtime.install does.//// Runs under Node, and as .ts directly (type stripping): erasable syntax only. Public// types use Uint8Array, not Buffer, so a consumer needs no @types/node.import { readdirSync, readFileSync, statSync } from "node:fs";import { join, relative, resolve, sep } from "node:path";import { keepInPackage } from "../shared/package-files.ts";
export interface SnapshotFile { /** The guest path: `mount` plus the path relative to the app directory. */ path: string; bytes: Uint8Array; mtimeMs: number;}
export interface Snapshot { version: 3; /** In walk order (entries sorted by name, depth first), which is also encoded order. */ files: SnapshotFile[];}
export interface SnapshotOptions { /** Include node_modules. `false` leaves it out: the app's own files and lockfile, for * `runtime.install()` to fill in. Default: true. */ modules?: boolean;}
/** Walk `appDir` and return its snapshot; `mount` is the guest path prefix. */export function snapshot(appDir: string, mount = "/app", { modules = true }: SnapshotOptions = {}): Snapshot { const root = resolve(appDir); const files: SnapshotFile[] = []; const walk = (dir: string, inPackages: boolean): void => { for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) { const abs = join(dir, entry.name); if (entry.isDirectory()) { if (!modules && !inPackages && entry.name === "node_modules") continue; // node_modules/.vite is Vite's dep-optimizer cache from a run under real Node; // the guest builds its own. if (inPackages && entry.name === ".vite") continue; walk(abs, inPackages || entry.name === "node_modules"); continue; } if (!entry.isFile()) continue; const bytes = readFileSync(abs); if (inPackages && !keepInPackage(entry.name, bytes)) continue; const guestPath = `${mount.replace(/\/$/, "")}/${relative(root, abs).split(sep).join("/")}`; files.push({ path: guestPath, bytes, mtimeMs: statSync(abs).mtimeMs }); } }; walk(root, false); return { version: 3, files };}
/** Encode a snapshot (as `snapshot()` returns) into the v3 binary format. */export function encodeSnapshot(snap: Snapshot): Uint8Array { const header = new TextEncoder().encode(JSON.stringify({ version: 3, files: snap.files.map((f) => [f.path, f.bytes.length, f.mtimeMs]), })); const out = new Uint8Array(8 + header.length + snap.files.reduce((n, f) => n + f.bytes.length, 0)); out.set([0x4e, 0x49, 0x42, 0x53]); // "NIBS" new DataView(out.buffer).setUint32(4, header.length, true); out.set(header, 8); let offset = 8 + header.length; for (const f of snap.files) { out.set(f.bytes, offset); offset += f.bytes.length; } return out;}