gstack/hosts/claude/hooks/spawn-bin.ts

44 lines
1.7 KiB
TypeScript

/**
* Windows-safe resolution + spawn for gstack's bash bins. Two Windows-only
* bugs made every hook subprocess a silent no-op; both are fixed here so all
* call sites are covered at once.
*
* 1. `new URL(import.meta.url).pathname` yields `/C:/Users/...`; path.resolve
* then rebases it onto the drive root as `C:\C:\Users\...`. fileURLToPath
* is the correct conversion. (ENOENT before the bin ever ran.)
* 2. `bin/gstack-*` are extensionless bash scripts. Windows has no shebang
* support, so they must be handed to bash explicitly.
*/
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { spawnSync, type SpawnSyncOptions } from 'child_process';
// Forward slashes on purpose: Bun's spawnSync on Windows returns ENOENT for a
// backslash exe path containing spaces.
const GIT_BASH = 'C:/Program Files/Git/bin/bash.exe';
/** bash Windows itself can execute — env override, Git Bash, then PATH. */
function bashExe(): string {
return process.env.GSTACK_BASH || (fs.existsSync(GIT_BASH) ? GIT_BASH : 'bash');
}
/** gstack install root. This file lives at hosts/claude/hooks/. */
export function repoRoot(): string {
const here = path.dirname(fileURLToPath(import.meta.url));
return path.resolve(here, '..', '..', '..');
}
/** Absolute path to a `bin/` script. */
export function binPath(name: string): string {
return path.join(repoRoot(), 'bin', name);
}
/** Resolve `name` under bin/ and run it, via bash on Windows. */
export function runBin(name: string, args: string[], opts: SpawnSyncOptions) {
const bin = binPath(name);
return process.platform === 'win32'
? spawnSync(bashExe(), [bin, ...args], opts)
: spawnSync(bin, args, opts);
}