mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-17 23:30:09 +02:00
fb3103237a
On Windows, bun/npm install gbrain as a gbrain.cmd/.ps1 shim and gstack-brain-sync is a bash shebang script. spawnSync/spawn/execFileSync resolve neither without a shell, so the child spawn failed ENOENT — on the sync orchestrator this surfaced as 'brain-sync exited undefined' (#1731). Add NEEDS_SHELL_ON_WINDOWS (process.platform === 'win32') in gbrain-exec and pass it as shell: to every gbrain/brain-sync child spawn: spawnGbrain, spawnGbrainAsync, execGbrainText (gbrain-exec), the two sources-list/remove/add spawns (gbrain-sources), the version + probe spawns (gbrain-local-status), and the two brain-sync spawns in the orchestrator. POSIX keeps the cheaper no-shell path. macOS/Linux CI can't exercise the Windows path, so test/gbrain-spawn-windows-shell.ts is a static-grep tripwire: it fails CI if a gbrain/brain-sync spawn is added without the shell flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
2.1 KiB
TypeScript
46 lines
2.1 KiB
TypeScript
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 carry the Windows shell flag", () => {
|
|
const src = read("bin/gstack-gbrain-sync.ts");
|
|
const brainSyncSpawns = src.match(/spawnSync\(brainSyncPath,/g)?.length ?? 0;
|
|
expect(brainSyncSpawns).toBe(2);
|
|
// Both spawnSync(brainSyncPath, ...) blocks must include the shell flag.
|
|
const withShell = src.match(/spawnSync\(brainSyncPath,[\s\S]*?shell:\s*NEEDS_SHELL_ON_WINDOWS/g)?.length ?? 0;
|
|
expect(withShell).toBe(2);
|
|
});
|
|
});
|