gstack/browse/src/find-security-sidecar.ts

79 lines
2.5 KiB
TypeScript

/**
* find-security-sidecar — resolve the Node entry that runs the L4 ML
* classifier sidecar.
*
* The sidecar can't be bundled into the compiled browse binary because
* onnxruntime-node fails to dlopen from Bun's compile extract dir. It runs
* as a separate Node subprocess instead. This module resolves the right
* path + interpreter on each platform:
*
* 1. Prefer node on PATH + a bundled JS entry at
* browse/dist/security-sidecar.js (built by package.json's
* build:security-sidecar script).
* 2. Dev fallback: node + browse/src/security-sidecar-entry.ts via tsx
* (only available in the source checkout, not the compiled install).
* 3. If Node is missing or no entry resolves, return null. The /pty-inject-scan
* endpoint then responds with l4 { available: false } and the extension
* degrades to WARN+confirm (D7).
*/
import { existsSync } from "fs";
import { join, dirname } from "path";
import { execFileSync } from "child_process";
export interface SidecarLocation {
node: string;
entry: string;
/** "compiled" if running from browse/dist/, "dev" if running from src */
mode: "compiled" | "dev";
}
function nodeOnPath(): string | null {
try {
execFileSync("node", ["--version"], { stdio: "ignore", timeout: 2000 });
return "node";
} catch {
return null;
}
}
function browseRoot(): string {
// When running compiled, __dirname (via import.meta.dir) points at the
// Bun extract temp. Walk up until we find a directory containing
// browse/dist/ or browse/src/.
let candidate = dirname(import.meta.path || "");
for (let i = 0; i < 6; i += 1) {
if (existsSync(join(candidate, "browse", "dist", "security-sidecar.js"))) {
return candidate;
}
if (existsSync(join(candidate, "src", "security-sidecar-entry.ts"))) {
return candidate;
}
const next = dirname(candidate);
if (next === candidate) break;
candidate = next;
}
return process.cwd();
}
export function findSecuritySidecar(): SidecarLocation | null {
const node = nodeOnPath();
if (!node) return null;
const root = browseRoot();
const compiled = join(root, "browse", "dist", "security-sidecar.js");
if (existsSync(compiled)) {
return { node, entry: compiled, mode: "compiled" };
}
// Dev fallback. Compiled installs won't have src/ on disk so this only
// resolves when running from the source checkout.
const devEntry = join(root, "src", "security-sidecar-entry.ts");
if (existsSync(devEntry)) {
return { node, entry: devEntry, mode: "dev" };
}
return null;
}