import { describe, test, expect } from "bun:test"; import * as fs from "fs"; import * as path from "path"; const ROOT = path.resolve(import.meta.dir, ".."); const read = (rel: string) => fs.readFileSync(path.join(ROOT, rel), "utf-8"); // #1731 tripwire. Windows can't spawn the `gbrain` shim (gbrain.cmd) or the bash // shebang script gstack-brain-sync without a shell; the fix gates `shell: true` // behind NEEDS_SHELL_ON_WINDOWS. These static checks fail CI if a refactor adds // a gbrain/brain-sync child spawn without the Windows shell flag, since macOS/ // Linux CI can't exercise the Windows path at runtime. describe("#1731 gbrain spawns carry the Windows shell flag", () => { test("NEEDS_SHELL_ON_WINDOWS is platform-gated in gbrain-exec.ts", () => { const src = read("lib/gbrain-exec.ts"); expect(src).toMatch(/export const NEEDS_SHELL_ON_WINDOWS\s*=\s*process\.platform === "win32"/); }); // Every direct `gbrain` child spawn in these files must be matched by a // shell:NEEDS_SHELL_ON_WINDOWS flag. Count openers vs flags as a cheap, // refactor-resistant invariant. const gbrainSpawnFiles = [ "lib/gbrain-exec.ts", "lib/gbrain-sources.ts", "lib/gbrain-local-status.ts", ]; for (const rel of gbrainSpawnFiles) { test(`${rel}: every gbrain spawn has shell:NEEDS_SHELL_ON_WINDOWS`, () => { const src = read(rel); const spawnOpeners = src.match(/(spawnSync|spawn|execFileSync)\("gbrain"/g)?.length ?? 0; const shellFlags = src.match(/shell:\s*NEEDS_SHELL_ON_WINDOWS/g)?.length ?? 0; expect(spawnOpeners).toBeGreaterThan(0); expect(shellFlags).toBeGreaterThanOrEqual(spawnOpeners); }); } test("orchestrator brain-sync spawns route through bash on Windows", () => { const src = read("bin/gstack-gbrain-sync.ts"); // cmd.exe (shell: true) cannot execute an extensionless bash shebang // script, so the brain-sync invocations must exec bash explicitly on // Windows instead of relying on the shell flag. expect(src).toMatch(/NEEDS_SHELL_ON_WINDOWS \? "bash" : brainSyncPath/); expect(src).toMatch(/NEEDS_SHELL_ON_WINDOWS \? \[brainSyncPath, flag\] : \[flag\]/); // And no brain-sync spawn may still route through cmd via shell:true. expect(src).not.toMatch(/spawnSync\(brainSyncPath,[\s\S]{0,300}?shell:\s*NEEDS_SHELL_ON_WINDOWS/); }); });