/** * Native helper binaries (src/gate/rust, std only): `frame-gate` (the channel's * clock master) and `source-bus` (per-source raw frame fan-out). * * Resolution order, first hit wins: * 1. `MEDIA_FRAME_GATE_BIN` / `MEDIA_SOURCE_BUS_BIN`: an explicit path per binary; * 2. `MEDIA_HELPER_DIR/`: the packaged directory (the image builds the crate * with `scripts/build-helpers.sh` and sets this; see runs/wp4/s2b-report.md item 5); * 3. the crate's `target/release/` (development checkouts and tests). * * Nothing builds at runtime in production. `ensureHelperBinaries()` is for tests * and the spike/smoke scripts: it builds the crate offline when a binary is * missing or older than its sources. */ import { spawnSync } from "node:child_process"; import { existsSync, readdirSync, statSync } from "node:fs"; import { join, resolve } from "node:path"; export type HelperBinary = "frame-gate" | "source-bus"; const ENV: Record = { "frame-gate": "MEDIA_FRAME_GATE_BIN", "source-bus": "MEDIA_SOURCE_BUS_BIN" }; /** The crate directory, from `src/gate` (tsx) or `dist/gate` (compiled). */ export function helperCrateDir(): string { const local = resolve(__dirname, "rust"); return existsSync(join(local, "Cargo.toml")) ? local : resolve(__dirname, "../../src/gate/rust"); } function candidates(name: HelperBinary): string[] { return [ process.env[ENV[name]], process.env.MEDIA_HELPER_DIR ? join(process.env.MEDIA_HELPER_DIR, name) : undefined, join(helperCrateDir(), "target/release", name), ].filter((path): path is string => typeof path === "string" && path.length > 0); } export function resolveHelperBinary(name: HelperBinary): string { const found = candidates(name).find((path) => existsSync(path)); if (!found) throw new Error(`${name} binary not found: set ${ENV[name]} or MEDIA_HELPER_DIR, or run scripts/build-helpers.sh`); return found; } function newestSource(crate: string): number { const files = ["Cargo.toml", "Cargo.lock"].map((f) => join(crate, f)); const walk = (dir: string): void => { for (const entry of readdirSync(dir, { withFileTypes: true })) { const path = join(dir, entry.name); if (entry.isDirectory()) walk(path); else if (entry.name.endsWith(".rs")) files.push(path); } }; walk(join(crate, "src")); return Math.max(...files.filter((f) => existsSync(f)).map((f) => statSync(f).mtimeMs)); } /** Development binaries that are missing or older than the crate sources. Env/packaged binaries are taken as given. */ export function staleHelpers(): HelperBinary[] { const crate = helperCrateDir(); if (!existsSync(join(crate, "Cargo.toml"))) return []; const newest = newestSource(crate); return (Object.keys(ENV) as HelperBinary[]).filter((name) => { if (process.env[ENV[name]] || process.env.MEDIA_HELPER_DIR) return false; const path = join(crate, "target/release", name); return !existsSync(path) || statSync(path).mtimeMs < newest; }); } /** * Tests/scripts: build the crate if a development binary is missing or stale. * `--offline --locked`: no network, and the (dependency-free) lockfile must not change. * Concurrent callers are serialized by cargo's own build-directory lock. */ export function ensureHelperBinaries(): boolean { if (staleHelpers().length > 0) { const built = spawnSync("cargo", ["build", "--release", "--offline", "--locked", "--manifest-path", join(helperCrateDir(), "Cargo.toml")], { stdio: "ignore" }); if (built.status !== 0) return false; } try { resolveHelperBinary("frame-gate"); resolveHelperBinary("source-bus"); return true; } catch { return false; } }