github-router 0.3.72 → 0.3.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import "./paths-CutqqG7k.js";
2
- import { a as sweepRegistry, i as registerExitHandlers, n as getInstanceUuid, o as sweepStaleWorktreesAtBoot, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-CSzT74Yn.js";
1
+ import "./paths-yJ97KlKp.js";
2
+ import { a as sweepRegistry, i as registerExitHandlers, n as getInstanceUuid, o as sweepStaleWorktreesAtBoot, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-CMPthagV.js";
3
3
 
4
4
  export { WorktreeRegistry, getInstanceUuid, recordWorkerRepo, registerExitHandlers, sweepRegistry, sweepStaleWorktreesAtBoot };
@@ -0,0 +1,452 @@
1
+ import { t as PATHS } from "./paths-yJ97KlKp.js";
2
+ import { randomUUID } from "node:crypto";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { spawn } from "node:child_process";
7
+ import { existsSync } from "node:fs";
8
+
9
+ //#region src/lib/exec.ts
10
+ /**
11
+ * Parse a boolean-ish env value. Returns `undefined` when unset or
12
+ * unrecognized so callers can apply their own default. Accepts
13
+ * `1|true|yes|on` (true) and `0|false|no|off|<empty>` (false),
14
+ * case-insensitive. The single shared parser for all new `GH_ROUTER_*`
15
+ * flags so on/off semantics don't drift per call site.
16
+ */
17
+ function parseBoolEnv(value) {
18
+ if (value === void 0) return void 0;
19
+ const v = value.trim().toLowerCase();
20
+ if (v === "1" || v === "true" || v === "yes" || v === "on") return true;
21
+ if (v === "0" || v === "false" || v === "no" || v === "off" || v === "") return false;
22
+ }
23
+ /** Read the PATH value from an env object, case-insensitively. */
24
+ function pathValueOf(env) {
25
+ for (const key of Object.keys(env)) if (key.toLowerCase() === "path") return env[key] ?? "";
26
+ return "";
27
+ }
28
+ /**
29
+ * Resolve an executable name to an absolute path against PATH, honoring
30
+ * `PATHEXT` on Windows and **excluding the current working directory**.
31
+ *
32
+ * Returns `null` when unresolved — callers treat that as "tool absent"
33
+ * and skip (best-effort). Spawning the returned absolute path means
34
+ * `cmd.exe`'s implicit cwd-first lookup never applies, closing the
35
+ * planted-`npm.cmd` vector.
36
+ */
37
+ function resolveExecutable(name, opts = {}) {
38
+ const platform = opts.platform ?? process.platform;
39
+ const env = opts.env ?? process.env;
40
+ const cwdRaw = opts.cwd ?? (typeof process.cwd === "function" ? process.cwd() : void 0);
41
+ const resolvedCwd = cwdRaw ? path.resolve(cwdRaw) : null;
42
+ const dirs = pathValueOf(env).split(path.delimiter).filter((d) => d.length > 0 && d !== ".");
43
+ const isWin = platform === "win32";
44
+ if (!isWin && name.includes("/")) return existsSync(name) ? path.resolve(name) : null;
45
+ if (isWin && (name.includes("\\") || name.includes("/"))) return existsSync(name) ? path.resolve(name) : null;
46
+ const exts = isWin && path.extname(name) === "" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean) : [""];
47
+ for (const dir of dirs) {
48
+ if (resolvedCwd && path.resolve(dir) === resolvedCwd) continue;
49
+ for (const ext of exts) {
50
+ const candidate = path.join(dir, name + ext);
51
+ if (existsSync(candidate)) return candidate;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+ /**
57
+ * Quote one argument for a `cmd.exe /c "<line>"` command line so the
58
+ * target program receives it verbatim and no `cmd.exe` metacharacter
59
+ * retains shell meaning.
60
+ *
61
+ * Two phases (the canonical Windows approach — Colascione / Rust std):
62
+ * 1. **argv quoting** so `CommandLineToArgvW` in the target parses
63
+ * the token as one argument (double-quote, backslash-escape).
64
+ * 2. **caret-escaping** every `cmd.exe` metacharacter — including the
65
+ * quotes from phase 1 — so `cmd.exe` is never in quote-mode, strips
66
+ * the carets, and hands the argv-quoted string to the program.
67
+ *
68
+ * `%` is special: it cannot be reliably escaped on the `cmd.exe`
69
+ * *command line* (caret does not stop `%VAR%` expansion there). Rather
70
+ * than mis-escape, we **throw** — our callers never pass `%`, so this
71
+ * fails closed on the one unescapable injection vector.
72
+ */
73
+ function quoteWinArg(arg) {
74
+ if (arg.includes("%")) throw new Error("buildExecInvocation: argument contains '%', which cannot be safely escaped on the Windows command line; refusing to build the command.");
75
+ let quoted;
76
+ if (arg.length > 0 && !/[ \t\n\v"&|<>()^!]/.test(arg)) quoted = arg;
77
+ else {
78
+ let s = "\"";
79
+ let backslashes = 0;
80
+ for (const ch of arg) if (ch === "\\") backslashes++;
81
+ else if (ch === "\"") {
82
+ s += "\\".repeat(backslashes * 2 + 1) + "\"";
83
+ backslashes = 0;
84
+ } else {
85
+ s += "\\".repeat(backslashes) + ch;
86
+ backslashes = 0;
87
+ }
88
+ s += "\\".repeat(backslashes * 2) + "\"";
89
+ quoted = s;
90
+ }
91
+ return quoted.replace(/[()!^"<>&|]/g, "^$&");
92
+ }
93
+ /**
94
+ * Build the platform-correct `spawn` invocation for a command given as
95
+ * an argv array. Pure / unit-testable (no spawn).
96
+ *
97
+ * - win32 → a single caret/argv-quoted command string + `shell:true`
98
+ * + empty args array (the empty array avoids the DEP0190 warning
99
+ * that fires when args and `shell:true` are combined). `cmd[0]`
100
+ * should already be an absolute path from `resolveExecutable`.
101
+ * - posix → `(cmd[0], cmd.slice(1))` with `shell:false` — no shell,
102
+ * no injection surface.
103
+ */
104
+ function buildExecInvocation(cmd, platform = process.platform) {
105
+ if (cmd.length === 0) throw new Error("buildExecInvocation: empty command");
106
+ if (platform === "win32") return {
107
+ command: cmd.map(quoteWinArg).join(" "),
108
+ args: [],
109
+ shell: true
110
+ };
111
+ return {
112
+ command: cmd[0],
113
+ args: cmd.slice(1),
114
+ shell: false
115
+ };
116
+ }
117
+ function runInternal(cmd, stdoutMode, opts) {
118
+ const { command, args, shell } = buildExecInvocation(cmd);
119
+ return new Promise((resolve, reject) => {
120
+ let child;
121
+ try {
122
+ child = spawn(command, args, {
123
+ cwd: opts.cwd,
124
+ env: opts.env ?? process.env,
125
+ shell,
126
+ windowsHide: true,
127
+ stdio: [
128
+ "ignore",
129
+ stdoutMode,
130
+ stdoutMode === "inherit" ? "inherit" : "pipe"
131
+ ]
132
+ });
133
+ } catch (err) {
134
+ reject(err instanceof Error ? err : new Error(String(err)));
135
+ return;
136
+ }
137
+ let stdout = "";
138
+ let stderr = "";
139
+ let timedOut = false;
140
+ let settled = false;
141
+ const timer = opts.timeoutMs ? setTimeout(() => {
142
+ timedOut = true;
143
+ killTree(child.pid);
144
+ }, opts.timeoutMs) : void 0;
145
+ timer?.unref?.();
146
+ child.stdout?.on("data", (c) => {
147
+ stdout += c.toString("utf8");
148
+ });
149
+ child.stderr?.on("data", (c) => {
150
+ stderr += c.toString("utf8");
151
+ });
152
+ child.stdout?.on("error", () => {});
153
+ child.stderr?.on("error", () => {});
154
+ const finish = (code) => {
155
+ if (settled) return;
156
+ settled = true;
157
+ if (timer) clearTimeout(timer);
158
+ resolve({
159
+ stdout,
160
+ stderr,
161
+ code,
162
+ timedOut
163
+ });
164
+ };
165
+ child.on("error", (err) => {
166
+ if (settled) return;
167
+ settled = true;
168
+ if (timer) clearTimeout(timer);
169
+ reject(err);
170
+ });
171
+ child.on("close", (code) => finish(code));
172
+ });
173
+ }
174
+ /** Kill a process tree best-effort (taskkill /T on Windows). */
175
+ function killTree(pid) {
176
+ if (!pid) return;
177
+ try {
178
+ if (process.platform === "win32") spawn("taskkill", [
179
+ "/T",
180
+ "/F",
181
+ "/PID",
182
+ String(pid)
183
+ ], {
184
+ stdio: "ignore",
185
+ windowsHide: true
186
+ });
187
+ else process.kill(pid, "SIGTERM");
188
+ } catch {}
189
+ }
190
+ /** Run a command and capture stdout/stderr. Rejects on spawn error. */
191
+ function runCommandCapture(cmd, opts = {}) {
192
+ return runInternal(cmd, "pipe", opts);
193
+ }
194
+ /** Run a command discarding output (still captures stderr for errors). */
195
+ function runCommandVoid(cmd, opts = {}) {
196
+ return runInternal(cmd, "pipe", opts);
197
+ }
198
+ /**
199
+ * Run a **native executable** (a real `.exe`/Mach-O/ELF, NOT a `.cmd`
200
+ * shim) capturing stdout/stderr, with `shell:false` on EVERY platform.
201
+ *
202
+ * Why a separate runner from `runCommandCapture`: that path routes
203
+ * through `buildExecInvocation`, which on Windows builds a
204
+ * `cmd.exe`-quoted command string and **throws on `%`** (`quoteWinArg`).
205
+ * A workspace path can legally contain `%` (and `&`, `(`, `)`, `!`, …),
206
+ * so the managed colgrep binary — which IS a native `.exe`, not a shim —
207
+ * must bypass cmd.exe entirely. `spawn(absExe, args, {shell:false})`
208
+ * resolves the `.exe` via CreateProcess directly: no cmd.exe, no
209
+ * metacharacter hazard, no `%` refusal. POSIX was already `shell:false`.
210
+ * This is what makes "ANY absolute workspace" hold on Windows.
211
+ *
212
+ * Lifecycle:
213
+ * - `timeoutMs` → tree-kill on expiry (`taskkill /T /F` on Windows,
214
+ * POSIX process-group `kill(-pgid)` so colgrep's rayon worker
215
+ * children die too). `timedOut: true` in the result.
216
+ * - `maxStdoutBytes` → tree-kill + `stdoutTruncated: true` once the
217
+ * captured stdout exceeds the cap.
218
+ * - `onSpawn(child)` → register the child with the caller's ledger.
219
+ *
220
+ * `command` MUST be an absolute path to the executable (the caller
221
+ * resolves it; we never search PATH here — there is nothing to inject).
222
+ */
223
+ function runManagedExeCapture(command, args, opts = {}) {
224
+ const isWin = process.platform === "win32";
225
+ return new Promise((resolve, reject) => {
226
+ let child;
227
+ try {
228
+ child = spawn(command, [...args], {
229
+ cwd: opts.cwd,
230
+ env: opts.env ?? process.env,
231
+ shell: false,
232
+ windowsHide: true,
233
+ detached: !isWin,
234
+ stdio: [
235
+ "ignore",
236
+ "pipe",
237
+ "pipe"
238
+ ]
239
+ });
240
+ } catch (err) {
241
+ reject(err instanceof Error ? err : new Error(String(err)));
242
+ return;
243
+ }
244
+ try {
245
+ opts.onSpawn?.(child);
246
+ } catch {}
247
+ const chunks = [];
248
+ let stdoutBytes = 0;
249
+ const stderrChunks = [];
250
+ let stderrBytes = 0;
251
+ const STDERR_CAP = 64 * 1024;
252
+ let timedOut = false;
253
+ let stdoutTruncated = false;
254
+ let settled = false;
255
+ const killNow = () => killManagedTree(child, isWin);
256
+ const timer = opts.timeoutMs ? setTimeout(() => {
257
+ timedOut = true;
258
+ killNow();
259
+ }, opts.timeoutMs) : void 0;
260
+ timer?.unref?.();
261
+ child.stdout?.on("data", (c) => {
262
+ if (stdoutTruncated) return;
263
+ stdoutBytes += c.length;
264
+ if (opts.maxStdoutBytes !== void 0 && stdoutBytes > opts.maxStdoutBytes) {
265
+ stdoutTruncated = true;
266
+ killNow();
267
+ return;
268
+ }
269
+ chunks.push(c);
270
+ });
271
+ child.stderr?.on("data", (c) => {
272
+ if (stderrBytes >= STDERR_CAP) return;
273
+ const remaining = STDERR_CAP - stderrBytes;
274
+ const slice = c.length > remaining ? c.subarray(0, remaining) : c;
275
+ stderrChunks.push(slice);
276
+ stderrBytes += slice.length;
277
+ });
278
+ child.stdout?.on("error", () => {});
279
+ child.stderr?.on("error", () => {});
280
+ const finish = (code) => {
281
+ if (settled) return;
282
+ settled = true;
283
+ if (timer) clearTimeout(timer);
284
+ resolve({
285
+ stdout: Buffer.concat(chunks).toString("utf8"),
286
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
287
+ code,
288
+ timedOut,
289
+ stdoutTruncated
290
+ });
291
+ };
292
+ child.on("error", (err) => {
293
+ if (settled) return;
294
+ settled = true;
295
+ if (timer) clearTimeout(timer);
296
+ reject(err);
297
+ });
298
+ child.on("close", (code) => finish(code));
299
+ });
300
+ }
301
+ /**
302
+ * Tree-kill a managed child. Windows: `taskkill /T /F /PID` (whole
303
+ * tree). POSIX: kill the process GROUP (`-pgid`) so colgrep's rayon
304
+ * worker children die with the parent.
305
+ *
306
+ * `runManagedExeCapture` always spawns POSIX children `detached:true`
307
+ * (their own process group), so the group kill is the correct and
308
+ * sufficient primitive. We deliberately do NOT fall back to a positive-
309
+ * pid `process.kill(pid)` when the group kill fails: by the time a kill
310
+ * fires (timeout / byte-cap race), the child may have already exited and
311
+ * its PID been recycled by an unrelated process — a positive-pid kill
312
+ * would then target the wrong process. `ESRCH` (group already gone) is
313
+ * the success case for our purposes; any other error is swallowed.
314
+ */
315
+ function killManagedTree(child, isWin = process.platform === "win32") {
316
+ const pid = child.pid;
317
+ if (!pid) return;
318
+ try {
319
+ if (isWin) spawn("taskkill", [
320
+ "/T",
321
+ "/F",
322
+ "/PID",
323
+ String(pid)
324
+ ], {
325
+ stdio: "ignore",
326
+ windowsHide: true
327
+ });
328
+ else process.kill(-pid, "SIGKILL");
329
+ } catch {}
330
+ }
331
+
332
+ //#endregion
333
+ //#region src/lib/colbert/lifecycle.ts
334
+ let _instanceUuid = null;
335
+ /**
336
+ * Stable UUID4 generated once per proxy process. Written into the
337
+ * sidecar metadata `ownerInstanceId` so the boot sweep can tell "this
338
+ * proxy's still-live build" from "a stranded `building` entry from a
339
+ * prior proxy whose PID got recycled" (Docker PID-1 across restarts).
340
+ */
341
+ function getColbertInstanceUuid() {
342
+ if (_instanceUuid === null) _instanceUuid = randomUUID();
343
+ return _instanceUuid;
344
+ }
345
+ const _liveChildren = /* @__PURE__ */ new Set();
346
+ /**
347
+ * Register a freshly-spawned colgrep child so the exit sweep can reap
348
+ * it. The runner also removes it on natural close via `untrackChild`.
349
+ */
350
+ function trackChild(child) {
351
+ _liveChildren.add(child);
352
+ child.once("close", () => _liveChildren.delete(child));
353
+ child.once("error", () => _liveChildren.delete(child));
354
+ }
355
+ /**
356
+ * Synchronous best-effort tree-kill of every tracked child. Called from
357
+ * the signal/exit handlers. After killing, the set is cleared so a
358
+ * second call is a no-op.
359
+ */
360
+ function sweepLiveChildren() {
361
+ const isWin = process.platform === "win32";
362
+ for (const child of _liveChildren) try {
363
+ killManagedTree(child, isWin);
364
+ } catch {}
365
+ _liveChildren.clear();
366
+ }
367
+ let _registered = false;
368
+ let _exitHandler = null;
369
+ let _sigintHandler = null;
370
+ let _sigtermHandler = null;
371
+ /**
372
+ * Wire SIGINT/SIGTERM/exit handlers that tree-kill every tracked
373
+ * colgrep child. Idempotent — subsequent calls are a no-op (we never
374
+ * leak listeners). The signal handlers re-raise after sweeping so Node's
375
+ * default terminate-on-signal behavior is restored (otherwise attaching
376
+ * a listener cancels the default and Ctrl-C would clean but not exit).
377
+ */
378
+ function registerColbertExitHandlers() {
379
+ if (_registered) return;
380
+ _registered = true;
381
+ _exitHandler = () => sweepLiveChildren();
382
+ _sigintHandler = () => {
383
+ sweepLiveChildren();
384
+ if (_sigintHandler) process.off("SIGINT", _sigintHandler);
385
+ process.kill(process.pid, "SIGINT");
386
+ };
387
+ _sigtermHandler = () => {
388
+ sweepLiveChildren();
389
+ if (_sigtermHandler) process.off("SIGTERM", _sigtermHandler);
390
+ process.kill(process.pid, "SIGTERM");
391
+ };
392
+ process.on("SIGINT", _sigintHandler);
393
+ process.on("SIGTERM", _sigtermHandler);
394
+ process.on("exit", _exitHandler);
395
+ }
396
+ function isPidAlive(pid) {
397
+ if (!Number.isInteger(pid) || pid <= 0) return false;
398
+ try {
399
+ process.kill(pid, 0);
400
+ return true;
401
+ } catch (err) {
402
+ if (err.code === "EPERM") return true;
403
+ return false;
404
+ }
405
+ }
406
+ /**
407
+ * Boot-time sweep. Walks `.gh-router-meta/*.json`; any entry stuck in
408
+ * `status:"building"` whose `buildPid` is DEAD is a crashed-build
409
+ * escapee → reset to `status:"failed"` so the next search re-kicks a
410
+ * build instead of routing to a never-finishing one.
411
+ *
412
+ * It NEVER kills anything: a live PID matching a stale `buildPid` from a
413
+ * prior boot may be a recycled PID belonging to an unrelated process, so
414
+ * the boot sweep only RECLASSIFIES metadata. The in-memory ledger (this
415
+ * run's spawns) is the only thing the SIGINT/SIGTERM handler ever kills.
416
+ *
417
+ * Best-effort; never throws (wrapped by the caller in `ensurePaths`).
418
+ */
419
+ async function sweepStaleColbertMetaAtBoot() {
420
+ const metaDir = PATHS.COLBERT_META_DIR;
421
+ let names;
422
+ try {
423
+ names = await fs.readdir(metaDir);
424
+ } catch {
425
+ return;
426
+ }
427
+ for (const name of names) {
428
+ if (!name.endsWith(".json")) continue;
429
+ const file = path.join(metaDir, name);
430
+ let meta;
431
+ try {
432
+ meta = JSON.parse(await fs.readFile(file, "utf8"));
433
+ } catch {
434
+ continue;
435
+ }
436
+ if (meta.status !== "building") continue;
437
+ const buildPid = typeof meta.buildPid === "number" ? meta.buildPid : 0;
438
+ if (buildPid > 0 && isPidAlive(buildPid)) continue;
439
+ meta.status = "failed";
440
+ const tmp = `${file}.${process.pid}.tmp`;
441
+ try {
442
+ await fs.writeFile(tmp, JSON.stringify(meta, null, 2));
443
+ await fs.rename(tmp, file);
444
+ } catch {
445
+ await fs.rm(tmp, { force: true }).catch(() => {});
446
+ }
447
+ }
448
+ }
449
+
450
+ //#endregion
451
+ export { trackChild as a, runCommandCapture as c, sweepStaleColbertMetaAtBoot as i, runCommandVoid as l, registerColbertExitHandlers as n, parseBoolEnv as o, sweepLiveChildren as r, resolveExecutable as s, getColbertInstanceUuid as t, runManagedExeCapture as u };
452
+ //# sourceMappingURL=lifecycle-yaqqtsV1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-yaqqtsV1.js","names":["quoted: string","child: ReturnType<typeof spawn>","chunks: Array<Buffer>","stderrChunks: Array<Buffer>","_instanceUuid: string | null","_exitHandler: (() => void) | null","_sigintHandler: (() => void) | null","_sigtermHandler: (() => void) | null","names: Array<string>","meta: Record<string, unknown>"],"sources":["../src/lib/exec.ts","../src/lib/colbert/lifecycle.ts"],"sourcesContent":["/**\n * Windows-safe, injection-safe command execution helpers.\n *\n * Why this module exists: on Windows, npm-installed CLIs (`claude`,\n * `npm`, `codex`) are `.cmd`/`.bat` shims. Node's `execFile`/`spawn`\n * with `shell:false` cannot launch them (CreateProcess only resolves\n * `.exe`), so callers must go through `cmd.exe` (`shell:true`). That in\n * turn opens two hazards this module closes:\n *\n * 1. **Metacharacter injection** (`& | < > ^ ( ) ! %`). A naive\n * \"quote only tokens with spaces\" scheme lets `pkg@latest&calc`\n * run `calc` as a second command. `buildExecInvocation` applies\n * real cmd.exe quoting (argv-quote + caret-escape) and fails\n * closed on `%` (which cannot be reliably escaped on the cmd\n * command line).\n *\n * 2. **CWD shadowing.** `cmd.exe` resolves a bare `npm` from the\n * current directory before PATH, so an untrusted repo can plant\n * `npm.cmd`. `resolveExecutable` resolves to an absolute path\n * against PATH only (honoring PATHEXT), never the cwd, so callers\n * spawn the real binary by absolute path.\n *\n * All runners are best-effort and timeout-bounded; callers wrap in\n * try/catch and never let an update/probe failure block launch.\n */\n\nimport { spawn } from \"node:child_process\"\nimport { existsSync } from \"node:fs\"\nimport path from \"node:path\"\nimport process from \"node:process\"\n\n/**\n * Parse a boolean-ish env value. Returns `undefined` when unset or\n * unrecognized so callers can apply their own default. Accepts\n * `1|true|yes|on` (true) and `0|false|no|off|<empty>` (false),\n * case-insensitive. The single shared parser for all new `GH_ROUTER_*`\n * flags so on/off semantics don't drift per call site.\n */\nexport function parseBoolEnv(value: string | undefined): boolean | undefined {\n if (value === undefined) return undefined\n const v = value.trim().toLowerCase()\n if (v === \"1\" || v === \"true\" || v === \"yes\" || v === \"on\") return true\n if (v === \"0\" || v === \"false\" || v === \"no\" || v === \"off\" || v === \"\") {\n return false\n }\n return undefined\n}\n\n/** Read the PATH value from an env object, case-insensitively. */\nfunction pathValueOf(env: NodeJS.ProcessEnv): string {\n for (const key of Object.keys(env)) {\n if (key.toLowerCase() === \"path\") return env[key] ?? \"\"\n }\n return \"\"\n}\n\nexport interface ResolveExecutableOpts {\n env?: NodeJS.ProcessEnv\n platform?: NodeJS.Platform\n /** Directory to treat as \"current dir\" and exclude from resolution. */\n cwd?: string\n}\n\n/**\n * Resolve an executable name to an absolute path against PATH, honoring\n * `PATHEXT` on Windows and **excluding the current working directory**.\n *\n * Returns `null` when unresolved — callers treat that as \"tool absent\"\n * and skip (best-effort). Spawning the returned absolute path means\n * `cmd.exe`'s implicit cwd-first lookup never applies, closing the\n * planted-`npm.cmd` vector.\n */\nexport function resolveExecutable(\n name: string,\n opts: ResolveExecutableOpts = {},\n): string | null {\n const platform = opts.platform ?? process.platform\n const env = opts.env ?? process.env\n // Defensive: some test harnesses mock `node:process` without `cwd`.\n // Absent a usable cwd we simply skip the cwd-exclusion guard (the\n // returned absolute path already bypasses cmd.exe's cwd-first lookup).\n const cwdRaw =\n opts.cwd ?? (typeof process.cwd === \"function\" ? process.cwd() : undefined)\n const resolvedCwd = cwdRaw ? path.resolve(cwdRaw) : null\n\n const dirs = pathValueOf(env)\n .split(path.delimiter)\n // Drop empty entries and a literal \".\" — both denote the cwd on\n // Windows, which we explicitly refuse to resolve against.\n .filter((d) => d.length > 0 && d !== \".\")\n\n const isWin = platform === \"win32\"\n\n // POSIX: an explicit path component → resolve directly.\n if (!isWin && name.includes(\"/\")) {\n return existsSync(name) ? path.resolve(name) : null\n }\n // Windows: an explicit path component (with a separator) → direct.\n if (isWin && (name.includes(\"\\\\\") || name.includes(\"/\"))) {\n return existsSync(name) ? path.resolve(name) : null\n }\n\n const exts =\n isWin && path.extname(name) === \"\"\n ? (env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\")\n .split(\";\")\n .map((e) => e.trim())\n .filter(Boolean)\n : [\"\"]\n\n for (const dir of dirs) {\n // Belt-and-suspenders: never resolve against the cwd even if it is\n // listed explicitly in PATH.\n if (resolvedCwd && path.resolve(dir) === resolvedCwd) continue\n for (const ext of exts) {\n const candidate = path.join(dir, name + ext)\n if (existsSync(candidate)) return candidate\n }\n }\n return null\n}\n\n/**\n * Quote one argument for a `cmd.exe /c \"<line>\"` command line so the\n * target program receives it verbatim and no `cmd.exe` metacharacter\n * retains shell meaning.\n *\n * Two phases (the canonical Windows approach — Colascione / Rust std):\n * 1. **argv quoting** so `CommandLineToArgvW` in the target parses\n * the token as one argument (double-quote, backslash-escape).\n * 2. **caret-escaping** every `cmd.exe` metacharacter — including the\n * quotes from phase 1 — so `cmd.exe` is never in quote-mode, strips\n * the carets, and hands the argv-quoted string to the program.\n *\n * `%` is special: it cannot be reliably escaped on the `cmd.exe`\n * *command line* (caret does not stop `%VAR%` expansion there). Rather\n * than mis-escape, we **throw** — our callers never pass `%`, so this\n * fails closed on the one unescapable injection vector.\n */\nexport function quoteWinArg(arg: string): string {\n if (arg.includes(\"%\")) {\n throw new Error(\n \"buildExecInvocation: argument contains '%', which cannot be safely \" +\n \"escaped on the Windows command line; refusing to build the command.\",\n )\n }\n\n // Phase 1 — argv quoting.\n let quoted: string\n if (arg.length > 0 && !/[ \\t\\n\\v\"&|<>()^!]/.test(arg)) {\n quoted = arg\n } else {\n let s = '\"'\n let backslashes = 0\n for (const ch of arg) {\n if (ch === \"\\\\\") {\n backslashes++\n } else if (ch === '\"') {\n s += \"\\\\\".repeat(backslashes * 2 + 1) + '\"'\n backslashes = 0\n } else {\n s += \"\\\\\".repeat(backslashes) + ch\n backslashes = 0\n }\n }\n s += \"\\\\\".repeat(backslashes * 2) + '\"'\n quoted = s\n }\n\n // Phase 2 — caret-escape all cmd.exe metacharacters (and the carets\n // themselves). cmd strips these; the program sees the phase-1 string.\n return quoted.replace(/[()!^\"<>&|]/g, \"^$&\")\n}\n\nexport interface ExecInvocation {\n command: string\n args: string[]\n shell: boolean\n}\n\n/**\n * Build the platform-correct `spawn` invocation for a command given as\n * an argv array. Pure / unit-testable (no spawn).\n *\n * - win32 → a single caret/argv-quoted command string + `shell:true`\n * + empty args array (the empty array avoids the DEP0190 warning\n * that fires when args and `shell:true` are combined). `cmd[0]`\n * should already be an absolute path from `resolveExecutable`.\n * - posix → `(cmd[0], cmd.slice(1))` with `shell:false` — no shell,\n * no injection surface.\n */\nexport function buildExecInvocation(\n cmd: string[],\n platform: NodeJS.Platform = process.platform,\n): ExecInvocation {\n if (cmd.length === 0) throw new Error(\"buildExecInvocation: empty command\")\n if (platform === \"win32\") {\n return { command: cmd.map(quoteWinArg).join(\" \"), args: [], shell: true }\n }\n return { command: cmd[0], args: cmd.slice(1), shell: false }\n}\n\nexport interface RunOpts {\n cwd?: string\n /** Hard timeout in ms; the process tree is killed on expiry. */\n timeoutMs?: number\n /** Extra env to merge over the parent env for the child. */\n env?: NodeJS.ProcessEnv\n}\n\nexport interface RunResult {\n stdout: string\n stderr: string\n /** Exit code; `null` when killed by signal/timeout. */\n code: number | null\n timedOut: boolean\n}\n\nfunction runInternal(\n cmd: string[],\n stdoutMode: \"pipe\" | \"inherit\" | \"ignore\",\n opts: RunOpts,\n): Promise<RunResult> {\n const { command, args, shell } = buildExecInvocation(cmd)\n return new Promise<RunResult>((resolve, reject) => {\n let child: ReturnType<typeof spawn>\n try {\n child = spawn(command, args, {\n cwd: opts.cwd,\n env: opts.env ?? process.env,\n shell,\n windowsHide: true,\n stdio: [\n \"ignore\",\n stdoutMode,\n stdoutMode === \"inherit\" ? \"inherit\" : \"pipe\",\n ],\n })\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)))\n return\n }\n\n let stdout = \"\"\n let stderr = \"\"\n let timedOut = false\n let settled = false\n\n const timer = opts.timeoutMs\n ? setTimeout(() => {\n timedOut = true\n killTree(child.pid)\n }, opts.timeoutMs)\n : undefined\n timer?.unref?.()\n\n child.stdout?.on(\"data\", (c: Buffer) => {\n stdout += c.toString(\"utf8\")\n })\n child.stderr?.on(\"data\", (c: Buffer) => {\n stderr += c.toString(\"utf8\")\n })\n child.stdout?.on(\"error\", () => {})\n child.stderr?.on(\"error\", () => {})\n\n const finish = (code: number | null): void => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n resolve({ stdout, stderr, code, timedOut })\n }\n\n child.on(\"error\", (err) => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n reject(err)\n })\n child.on(\"close\", (code) => finish(code))\n })\n}\n\n/** Kill a process tree best-effort (taskkill /T on Windows). */\nfunction killTree(pid: number | undefined): void {\n if (!pid) return\n try {\n if (process.platform === \"win32\") {\n spawn(\"taskkill\", [\"/T\", \"/F\", \"/PID\", String(pid)], {\n stdio: \"ignore\",\n windowsHide: true,\n })\n } else {\n process.kill(pid, \"SIGTERM\")\n }\n } catch {\n // already gone\n }\n}\n\n/** Run a command and capture stdout/stderr. Rejects on spawn error. */\nexport function runCommandCapture(\n cmd: string[],\n opts: RunOpts = {},\n): Promise<RunResult> {\n return runInternal(cmd, \"pipe\", opts)\n}\n\n/** Run a command discarding output (still captures stderr for errors). */\nexport function runCommandVoid(\n cmd: string[],\n opts: RunOpts = {},\n): Promise<RunResult> {\n return runInternal(cmd, \"pipe\", opts)\n}\n\n/** Run a command with the child's stdout/stderr inherited to the user. */\nexport function runCommandInherit(\n cmd: string[],\n opts: RunOpts = {},\n): Promise<RunResult> {\n return runInternal(cmd, \"inherit\", opts)\n}\n\nexport interface ManagedExeOpts extends RunOpts {\n /**\n * Hard cap on captured stdout bytes. On overflow the child is\n * tree-killed and the result carries `stdoutTruncated: true`. Defends\n * against a full-`CodeUnit` colgrep `--json` payload (source + 5\n * analysis layers per hit) bloating memory.\n */\n maxStdoutBytes?: number\n /**\n * Called synchronously with the spawned child right after spawn\n * succeeds, BEFORE any output arrives. The colbert lifecycle ledger\n * uses this to register the child so a session-exit sweep can\n * tree-kill an orphan. Never throws into the runner.\n */\n onSpawn?: (child: ReturnType<typeof spawn>) => void\n}\n\nexport interface ManagedExeResult extends RunResult {\n /** True iff stdout was truncated at `maxStdoutBytes` (child was killed). */\n stdoutTruncated: boolean\n}\n\n/**\n * Run a **native executable** (a real `.exe`/Mach-O/ELF, NOT a `.cmd`\n * shim) capturing stdout/stderr, with `shell:false` on EVERY platform.\n *\n * Why a separate runner from `runCommandCapture`: that path routes\n * through `buildExecInvocation`, which on Windows builds a\n * `cmd.exe`-quoted command string and **throws on `%`** (`quoteWinArg`).\n * A workspace path can legally contain `%` (and `&`, `(`, `)`, `!`, …),\n * so the managed colgrep binary — which IS a native `.exe`, not a shim —\n * must bypass cmd.exe entirely. `spawn(absExe, args, {shell:false})`\n * resolves the `.exe` via CreateProcess directly: no cmd.exe, no\n * metacharacter hazard, no `%` refusal. POSIX was already `shell:false`.\n * This is what makes \"ANY absolute workspace\" hold on Windows.\n *\n * Lifecycle:\n * - `timeoutMs` → tree-kill on expiry (`taskkill /T /F` on Windows,\n * POSIX process-group `kill(-pgid)` so colgrep's rayon worker\n * children die too). `timedOut: true` in the result.\n * - `maxStdoutBytes` → tree-kill + `stdoutTruncated: true` once the\n * captured stdout exceeds the cap.\n * - `onSpawn(child)` → register the child with the caller's ledger.\n *\n * `command` MUST be an absolute path to the executable (the caller\n * resolves it; we never search PATH here — there is nothing to inject).\n */\nexport function runManagedExeCapture(\n command: string,\n args: ReadonlyArray<string>,\n opts: ManagedExeOpts = {},\n): Promise<ManagedExeResult> {\n const isWin = process.platform === \"win32\"\n return new Promise<ManagedExeResult>((resolve, reject) => {\n let child: ReturnType<typeof spawn>\n try {\n child = spawn(command, [...args], {\n cwd: opts.cwd,\n env: opts.env ?? process.env,\n shell: false,\n windowsHide: true,\n // POSIX: new process group so we can kill the whole tree\n // (colgrep + rayon workers) with kill(-pgid). Windows uses\n // taskkill /T instead; `detached` there has no group semantics.\n detached: !isWin,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n })\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)))\n return\n }\n\n try {\n opts.onSpawn?.(child)\n } catch {\n // ledger registration must never break the spawn\n }\n\n const chunks: Array<Buffer> = []\n let stdoutBytes = 0\n const stderrChunks: Array<Buffer> = []\n let stderrBytes = 0\n const STDERR_CAP = 64 * 1024\n let timedOut = false\n let stdoutTruncated = false\n let settled = false\n\n const killNow = (): void => killManagedTree(child, isWin)\n\n const timer = opts.timeoutMs\n ? setTimeout(() => {\n timedOut = true\n killNow()\n }, opts.timeoutMs)\n : undefined\n timer?.unref?.()\n\n child.stdout?.on(\"data\", (c: Buffer) => {\n if (stdoutTruncated) return\n stdoutBytes += c.length\n if (\n opts.maxStdoutBytes !== undefined &&\n stdoutBytes > opts.maxStdoutBytes\n ) {\n stdoutTruncated = true\n killNow()\n return\n }\n chunks.push(c)\n })\n child.stderr?.on(\"data\", (c: Buffer) => {\n // Hard byte cap on stderr — append only the slice that fits so a\n // single huge chunk can't overshoot. Never logged raw (it can\n // embed source code from colgrep).\n if (stderrBytes >= STDERR_CAP) return\n const remaining = STDERR_CAP - stderrBytes\n const slice = c.length > remaining ? c.subarray(0, remaining) : c\n stderrChunks.push(slice)\n stderrBytes += slice.length\n })\n child.stdout?.on(\"error\", () => {})\n child.stderr?.on(\"error\", () => {})\n\n const finish = (code: number | null): void => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n resolve({\n stdout: Buffer.concat(chunks).toString(\"utf8\"),\n stderr: Buffer.concat(stderrChunks).toString(\"utf8\"),\n code,\n timedOut,\n stdoutTruncated,\n })\n }\n\n child.on(\"error\", (err) => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n reject(err)\n })\n child.on(\"close\", (code) => finish(code))\n })\n}\n\n/**\n * Tree-kill a managed child. Windows: `taskkill /T /F /PID` (whole\n * tree). POSIX: kill the process GROUP (`-pgid`) so colgrep's rayon\n * worker children die with the parent.\n *\n * `runManagedExeCapture` always spawns POSIX children `detached:true`\n * (their own process group), so the group kill is the correct and\n * sufficient primitive. We deliberately do NOT fall back to a positive-\n * pid `process.kill(pid)` when the group kill fails: by the time a kill\n * fires (timeout / byte-cap race), the child may have already exited and\n * its PID been recycled by an unrelated process — a positive-pid kill\n * would then target the wrong process. `ESRCH` (group already gone) is\n * the success case for our purposes; any other error is swallowed.\n */\nexport function killManagedTree(\n child: ReturnType<typeof spawn>,\n isWin: boolean = process.platform === \"win32\",\n): void {\n const pid = child.pid\n if (!pid) return\n try {\n if (isWin) {\n spawn(\"taskkill\", [\"/T\", \"/F\", \"/PID\", String(pid)], {\n stdio: \"ignore\",\n windowsHide: true,\n })\n } else {\n // Negative pid → the process group (we spawned detached). No\n // positive-pid fallback (PID-reuse hazard — see doc comment).\n process.kill(-pid, \"SIGKILL\")\n }\n } catch {\n // ESRCH (already gone) / EPERM — best-effort, nothing more to do.\n }\n}\n","/**\n * ColBERT sidecar lifecycle: in-memory PID ledger for the short-lived\n * `colgrep` children this proxy spawns, signal-handler tree-kill on\n * exit, and a boot-time metadata reclassification sweep.\n *\n * Because colgrep is CLI-per-invocation (no daemon), the lifecycle\n * problem is **process tracking + cancellation + boot/exit sweep**, NOT\n * keep-alive. Modeled on `worker-agent/lifecycle.ts` (PID ledger + boot\n * sweep + per-proxy-run instance UUID) and `exec.ts`'s tree-kill.\n *\n * Three cooperating layers (none sufficient alone):\n * 1. Per-call cleanup — the runner's `finally` force-kills the child\n * it spawned (handled in runner.ts).\n * 2. Session-end signal sweep (this file) — SIGINT/SIGTERM/exit kill\n * every still-tracked child of THIS run.\n * 3. Boot-time sweep (`sweepStaleColbertMetaAtBoot`) — reclassifies\n * `.gh-router-meta/*.json` entries whose `buildPid` is dead from\n * `building` → `failed`. It NEVER issues a kill to a PID from a\n * prior boot (a reused PID may belong to an unrelated process);\n * only the in-memory ledger (this run's spawns) is ever killed.\n */\n\nimport { spawn } from \"node:child_process\"\nimport { randomUUID } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\nimport path from \"node:path\"\nimport process from \"node:process\"\n\nimport { killManagedTree } from \"../exec\"\nimport { PATHS } from \"../paths\"\n\n// ---------------------------------------------------------------------\n// Per-launch instance UUID (mirrors worker-agent/lifecycle.ts)\n// ---------------------------------------------------------------------\n\nlet _instanceUuid: string | null = null\n\n/**\n * Stable UUID4 generated once per proxy process. Written into the\n * sidecar metadata `ownerInstanceId` so the boot sweep can tell \"this\n * proxy's still-live build\" from \"a stranded `building` entry from a\n * prior proxy whose PID got recycled\" (Docker PID-1 across restarts).\n */\nexport function getColbertInstanceUuid(): string {\n if (_instanceUuid === null) _instanceUuid = randomUUID()\n return _instanceUuid\n}\n\n/** Test-only: reset the cached UUID. */\nexport function __resetColbertInstanceUuidForTests(): void {\n _instanceUuid = null\n}\n\n// ---------------------------------------------------------------------\n// In-memory PID ledger of THIS run's live colgrep children\n// ---------------------------------------------------------------------\n\ntype TrackedChild = ReturnType<typeof spawn>\n\nconst _liveChildren = new Set<TrackedChild>()\n\n/**\n * Register a freshly-spawned colgrep child so the exit sweep can reap\n * it. The runner also removes it on natural close via `untrackChild`.\n */\nexport function trackChild(child: TrackedChild): void {\n _liveChildren.add(child)\n child.once(\"close\", () => _liveChildren.delete(child))\n child.once(\"error\", () => _liveChildren.delete(child))\n}\n\n/** Remove a child from the ledger (e.g. after a clean per-call kill). */\nexport function untrackChild(child: TrackedChild): void {\n _liveChildren.delete(child)\n}\n\n/** Count of live tracked children (test/diagnostic). */\nexport function liveChildCount(): number {\n return _liveChildren.size\n}\n\n/**\n * Synchronous best-effort tree-kill of every tracked child. Called from\n * the signal/exit handlers. After killing, the set is cleared so a\n * second call is a no-op.\n */\nexport function sweepLiveChildren(): void {\n const isWin = process.platform === \"win32\"\n for (const child of _liveChildren) {\n try {\n killManagedTree(child, isWin)\n } catch {\n // already gone\n }\n }\n _liveChildren.clear()\n}\n\n// ---------------------------------------------------------------------\n// Signal handlers (mirror worker-agent/lifecycle.ts re-raise pattern)\n// ---------------------------------------------------------------------\n\nlet _registered = false\nlet _exitHandler: (() => void) | null = null\nlet _sigintHandler: (() => void) | null = null\nlet _sigtermHandler: (() => void) | null = null\n\n/**\n * Wire SIGINT/SIGTERM/exit handlers that tree-kill every tracked\n * colgrep child. Idempotent — subsequent calls are a no-op (we never\n * leak listeners). The signal handlers re-raise after sweeping so Node's\n * default terminate-on-signal behavior is restored (otherwise attaching\n * a listener cancels the default and Ctrl-C would clean but not exit).\n */\nexport function registerColbertExitHandlers(): void {\n if (_registered) return\n _registered = true\n _exitHandler = () => sweepLiveChildren()\n _sigintHandler = () => {\n sweepLiveChildren()\n if (_sigintHandler) process.off(\"SIGINT\", _sigintHandler)\n process.kill(process.pid, \"SIGINT\")\n }\n _sigtermHandler = () => {\n sweepLiveChildren()\n if (_sigtermHandler) process.off(\"SIGTERM\", _sigtermHandler)\n process.kill(process.pid, \"SIGTERM\")\n }\n process.on(\"SIGINT\", _sigintHandler)\n process.on(\"SIGTERM\", _sigtermHandler)\n process.on(\"exit\", _exitHandler)\n}\n\n/** Test-only: unregister handlers + reset module state. */\nexport function __unregisterColbertExitHandlersForTests(): void {\n if (_sigintHandler) {\n process.off(\"SIGINT\", _sigintHandler)\n _sigintHandler = null\n }\n if (_sigtermHandler) {\n process.off(\"SIGTERM\", _sigtermHandler)\n _sigtermHandler = null\n }\n if (_exitHandler) {\n process.off(\"exit\", _exitHandler)\n _exitHandler = null\n }\n _registered = false\n _liveChildren.clear()\n}\n\n// ---------------------------------------------------------------------\n// Boot-time metadata reclassification sweep\n// ---------------------------------------------------------------------\n\nfunction isPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n process.kill(pid, 0)\n return true\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EPERM\") return true\n return false\n }\n}\n\n/**\n * Boot-time sweep. Walks `.gh-router-meta/*.json`; any entry stuck in\n * `status:\"building\"` whose `buildPid` is DEAD is a crashed-build\n * escapee → reset to `status:\"failed\"` so the next search re-kicks a\n * build instead of routing to a never-finishing one.\n *\n * It NEVER kills anything: a live PID matching a stale `buildPid` from a\n * prior boot may be a recycled PID belonging to an unrelated process, so\n * the boot sweep only RECLASSIFIES metadata. The in-memory ledger (this\n * run's spawns) is the only thing the SIGINT/SIGTERM handler ever kills.\n *\n * Best-effort; never throws (wrapped by the caller in `ensurePaths`).\n */\nexport async function sweepStaleColbertMetaAtBoot(): Promise<void> {\n const metaDir = PATHS.COLBERT_META_DIR\n let names: Array<string>\n try {\n names = await fs.readdir(metaDir)\n } catch {\n return // no meta dir yet — nothing to sweep\n }\n for (const name of names) {\n if (!name.endsWith(\".json\")) continue\n const file = path.join(metaDir, name)\n let meta: Record<string, unknown>\n try {\n meta = JSON.parse(await fs.readFile(file, \"utf8\")) as Record<string, unknown>\n } catch {\n continue // corrupt — leave it; index-store re-derives on next access\n }\n if (meta.status !== \"building\") continue\n const buildPid = typeof meta.buildPid === \"number\" ? meta.buildPid : 0\n if (buildPid > 0 && isPidAlive(buildPid)) {\n // A live PID — could be ours (this run re-kicked) or a recycled\n // unrelated PID. Either way: never kill from the boot sweep. Leave\n // the entry; the runner's own ownership check governs.\n continue\n }\n // Dead build PID → reclassify to failed (atomic temp+rename).\n meta.status = \"failed\"\n const tmp = `${file}.${process.pid}.tmp`\n try {\n await fs.writeFile(tmp, JSON.stringify(meta, null, 2))\n await fs.rename(tmp, file)\n } catch {\n await fs.rm(tmp, { force: true }).catch(() => {})\n }\n }\n}\n"],"mappings":"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"}