Node v24 forbids --experimental-strip-types for files under node_modules/.
When GSD is globally installed, all src/ files live under node_modules/gsd-pi/,
causing every subprocess worker to crash with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.
Bug 1: Extract resolveTypeStrippingFlag() into src/web/ts-subprocess-flags.ts.
When the package root is under node_modules/ and Node >= 22.7, the function
returns --experimental-transform-types (which handles node_modules paths).
All 15 service files and cli-entry.ts now call this function instead of
hardcoding --experimental-strip-types.
Bug 2: waitForBootReady() now tracks consecutive 5xx responses and aborts
after 3 in a row, including the response body in the error message.
Connection-level errors (transient during cold start) reset the counter.
Bug 3: The /api/boot route handler now wraps collectBootPayload() in
try/catch and returns { error: message } with status 500, matching the
error response pattern used by other API routes.
Fixes #1849
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
import { existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import { resolveTypeStrippingFlag } from "./ts-subprocess-flags.ts";
|
|
|
|
export interface GsdCliEntry {
|
|
command: string;
|
|
args: string[];
|
|
cwd: string;
|
|
}
|
|
|
|
export interface ResolveGsdCliEntryOptions {
|
|
packageRoot: string;
|
|
cwd: string;
|
|
execPath?: string;
|
|
hostKind?: string;
|
|
mode?: "interactive" | "rpc";
|
|
sessionDir?: string;
|
|
messages?: string[];
|
|
existsSync?: (path: string) => boolean;
|
|
}
|
|
|
|
function buildExtraArgs(options: ResolveGsdCliEntryOptions): string[] {
|
|
if (options.mode !== "rpc") return [];
|
|
|
|
if (!options.sessionDir) {
|
|
throw new Error("RPC CLI entry requires sessionDir");
|
|
}
|
|
|
|
return ["--mode", "rpc", "--continue", "--session-dir", options.sessionDir];
|
|
}
|
|
|
|
export function resolveGsdCliEntry(options: ResolveGsdCliEntryOptions): GsdCliEntry {
|
|
const checkExists = options.existsSync ?? existsSync;
|
|
const execPath = options.execPath ?? process.execPath;
|
|
const extraArgs = buildExtraArgs(options);
|
|
const messageArgs = options.mode === "interactive" ? options.messages ?? [] : [];
|
|
|
|
const sourceEntry = join(options.packageRoot, "src", "loader.ts");
|
|
const resolveTsLoader = join(options.packageRoot, "src", "resources", "extensions", "gsd", "tests", "resolve-ts.mjs");
|
|
const builtEntry = join(options.packageRoot, "dist", "loader.js");
|
|
|
|
const sourceCliEntry =
|
|
checkExists(sourceEntry) && checkExists(resolveTsLoader)
|
|
? {
|
|
command: execPath,
|
|
args: [
|
|
"--import",
|
|
pathToFileURL(resolveTsLoader).href,
|
|
resolveTypeStrippingFlag(options.packageRoot),
|
|
sourceEntry,
|
|
...extraArgs,
|
|
...messageArgs,
|
|
],
|
|
cwd: options.cwd,
|
|
} satisfies GsdCliEntry
|
|
: null;
|
|
|
|
const builtCliEntry = checkExists(builtEntry)
|
|
? {
|
|
command: execPath,
|
|
args: [builtEntry, ...extraArgs, ...messageArgs],
|
|
cwd: options.cwd,
|
|
} satisfies GsdCliEntry
|
|
: null;
|
|
|
|
if (options.hostKind === "packaged-standalone") {
|
|
if (builtCliEntry) return builtCliEntry;
|
|
if (sourceCliEntry) return sourceCliEntry;
|
|
} else {
|
|
if (sourceCliEntry) return sourceCliEntry;
|
|
if (builtCliEntry) return builtCliEntry;
|
|
}
|
|
|
|
throw new Error(`GSD CLI entry not found; checked=${sourceEntry},${builtEntry}`);
|
|
}
|