diff --git a/lib/bin-context.ts b/lib/bin-context.ts index faa1c65a2..d7edaa5a9 100644 --- a/lib/bin-context.ts +++ b/lib/bin-context.ts @@ -6,10 +6,37 @@ */ import { spawnSync } from "child_process"; +import type { SpawnSyncOptionsWithStringEncoding, SpawnSyncReturns } from "child_process"; + +/** + * Spawn one of gstack's own `#!/usr/bin/env bash` bins. + * + * Windows has no shebang support, so `spawnSync(bin, ...)` on an extensionless + * bash script fails with ENOENT. `shell: true` does not help either: that routes + * through cmd.exe, which reports "is not recognized as an internal or external + * command". Only spawning `bash` with the script as argv[0] actually starts it. + * + * (`shell: true` IS correct for `gbrain` — see NEEDS_SHELL_ON_WINDOWS in + * lib/gbrain-exec.ts — but only because gbrain ships a `.cmd` shim on Windows, + * which cmd.exe can run. gstack's own bins have no such shim.) + * + * Tries the direct spawn first so POSIX keeps its existing behaviour untouched, + * and only falls back to bash when the direct spawn produced nothing. + */ +export function spawnBashBin( + binPath: string, + args: string[] = [], + opts: Partial = {}, +): SpawnSyncReturns { + const options = { encoding: "utf-8" as const, ...opts }; + const direct = spawnSync(binPath, args, options); + if (!direct.error && direct.stdout != null) return direct; + return spawnSync("bash", [binPath, ...args], options); +} /** Resolve the project slug via the `gstack-slug` helper (parses `SLUG=...`). */ export function resolveSlug(slugBinPath: string): string { - const r = spawnSync(slugBinPath, { encoding: "utf-8" }); + const r = spawnBashBin(slugBinPath); const m = (r.stdout || "").match(/^SLUG=(.+)$/m); return m ? m[1].trim() : "unknown"; }