frontmcp 1.3.0 → 1.4.1
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.
- package/README.md +38 -29
- package/package.json +10 -12
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +1 -0
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
- package/src/commands/build/exec/runner-script.js +16 -4
- package/src/commands/build/exec/runner-script.js.map +1 -1
- package/src/commands/dev/dev.d.ts +16 -0
- package/src/commands/dev/dev.js +35 -1
- package/src/commands/dev/dev.js.map +1 -1
- package/src/commands/scaffold/create.js +8 -8
- package/src/commands/scaffold/create.js.map +1 -1
- package/src/core/tsconfig.d.ts +20 -0
- package/src/core/tsconfig.js +41 -2
- package/src/core/tsconfig.js.map +1 -1
|
@@ -16,4 +16,20 @@ export declare function resolveDevPort(opts: {
|
|
|
16
16
|
exit?: (code: number) => never;
|
|
17
17
|
log?: (msg: string) => void;
|
|
18
18
|
}): Promise<number>;
|
|
19
|
+
/**
|
|
20
|
+
* Build the environment handed to the spawned dev child.
|
|
21
|
+
*
|
|
22
|
+
* The resolved port is exported as `PORT`, and the configured
|
|
23
|
+
* `transport.http.path` (when set) as `FRONTMCP_HTTP_ENTRY_PATH` so the server
|
|
24
|
+
* mounts the MCP endpoint where the generated client URLs point (#446). Both are
|
|
25
|
+
* applied AFTER the inherited env so the dev-resolved values win for this run —
|
|
26
|
+
* the same precedence as `PORT`. A hard-coded `@FrontMcp({ http: { entryPath } })`
|
|
27
|
+
* in metadata still wins over the env (the SDK only reads it as a default).
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildDevChildEnv(params: {
|
|
30
|
+
effectiveEnv: NodeJS.ProcessEnv;
|
|
31
|
+
baseEnv: NodeJS.ProcessEnv;
|
|
32
|
+
port: number;
|
|
33
|
+
configHttpPath?: string;
|
|
34
|
+
}): NodeJS.ProcessEnv;
|
|
19
35
|
export declare function runDev(opts: ParsedArgs): Promise<void>;
|
package/src/commands/dev/dev.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveDevPort = resolveDevPort;
|
|
4
|
+
exports.buildDevChildEnv = buildDevChildEnv;
|
|
4
5
|
exports.runDev = runDev;
|
|
5
6
|
const tslib_1 = require("tslib");
|
|
6
7
|
const child_process_1 = require("child_process");
|
|
@@ -70,6 +71,25 @@ async function resolveDevPort(opts) {
|
|
|
70
71
|
log(line);
|
|
71
72
|
return exit(1);
|
|
72
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Build the environment handed to the spawned dev child.
|
|
76
|
+
*
|
|
77
|
+
* The resolved port is exported as `PORT`, and the configured
|
|
78
|
+
* `transport.http.path` (when set) as `FRONTMCP_HTTP_ENTRY_PATH` so the server
|
|
79
|
+
* mounts the MCP endpoint where the generated client URLs point (#446). Both are
|
|
80
|
+
* applied AFTER the inherited env so the dev-resolved values win for this run —
|
|
81
|
+
* the same precedence as `PORT`. A hard-coded `@FrontMcp({ http: { entryPath } })`
|
|
82
|
+
* in metadata still wins over the env (the SDK only reads it as a default).
|
|
83
|
+
*/
|
|
84
|
+
function buildDevChildEnv(params) {
|
|
85
|
+
const { effectiveEnv, baseEnv, port, configHttpPath } = params;
|
|
86
|
+
return {
|
|
87
|
+
...effectiveEnv,
|
|
88
|
+
...baseEnv,
|
|
89
|
+
PORT: String(port),
|
|
90
|
+
...(configHttpPath !== undefined ? { FRONTMCP_HTTP_ENTRY_PATH: configHttpPath } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
73
93
|
async function runDev(opts) {
|
|
74
94
|
// Issue #399 — `--stdio` runs the first-party watch-aware stdio bridge
|
|
75
95
|
// instead of the legacy `tsx --watch + tsc --noEmit --watch` pair. The
|
|
@@ -120,11 +140,20 @@ async function runDev(opts) {
|
|
|
120
140
|
showConflict: !!opts.showConflict,
|
|
121
141
|
envPort: process.env['PORT'],
|
|
122
142
|
});
|
|
143
|
+
// Issue #446 — honor the configured MCP mount path in dev. `transport.http.path`
|
|
144
|
+
// already drives the generated client URLs (eject); propagate it to the spawned
|
|
145
|
+
// server via FRONTMCP_HTTP_ENTRY_PATH so the endpoint is actually mounted there
|
|
146
|
+
// (the SDK's httpOptionsSchema.entryPath default reads this env). Same precedence
|
|
147
|
+
// caveat as PORT: a hard-coded `@FrontMcp({ http: { entryPath } })` still wins.
|
|
148
|
+
const configHttpPath = typeof cfg?.transport?.http?.path === 'string' ? cfg.transport.http.path : undefined;
|
|
123
149
|
console.log(`${(0, colors_1.c)('cyan', '[dev]')} using entry: ${path.relative(cwd, entry)}`);
|
|
124
150
|
if (resolved.configPath || resolved.configDir) {
|
|
125
151
|
console.log(`${(0, colors_1.c)('gray', '[dev]')} config: ${resolved.configPath ?? resolved.configDir}`);
|
|
126
152
|
}
|
|
127
153
|
console.log(`${(0, colors_1.c)('cyan', '[dev]')} listening on port: ${port}`);
|
|
154
|
+
if (configHttpPath) {
|
|
155
|
+
console.log(`${(0, colors_1.c)('gray', '[dev]')} MCP endpoint path: ${configHttpPath}`);
|
|
156
|
+
}
|
|
128
157
|
console.log(`${(0, colors_1.c)('gray', '[dev]')} starting ${(0, colors_1.c)('bold', 'tsx --watch')} and ${(0, colors_1.c)('bold', 'tsc --noEmit --watch')} (async type-checker)`);
|
|
129
158
|
console.log(`${(0, colors_1.c)('gray', 'hint:')} press Ctrl+C to stop`);
|
|
130
159
|
// Use --conditions node to ensure proper Node.js module resolution.
|
|
@@ -139,7 +168,12 @@ async function runDev(opts) {
|
|
|
139
168
|
// Issue #400 — env overlays from `frontmcp.config.env.{shared,dev}` are
|
|
140
169
|
// included via `resolved.effectiveEnv`. `.env`/`.env.local` already loaded
|
|
141
170
|
// into `process.env` above, so they win (they're closer to deployment).
|
|
142
|
-
const childEnv = {
|
|
171
|
+
const childEnv = buildDevChildEnv({
|
|
172
|
+
effectiveEnv: resolved.effectiveEnv,
|
|
173
|
+
baseEnv: process.env,
|
|
174
|
+
port,
|
|
175
|
+
configHttpPath,
|
|
176
|
+
});
|
|
143
177
|
const app = (0, child_process_1.spawn)(npxCmd, ['-y', 'tsx', '--conditions', 'node', '--watch', entry], {
|
|
144
178
|
stdio: 'inherit',
|
|
145
179
|
env: childEnv,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../../../src/commands/dev/dev.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type ChildProcess } from 'child_process';\nimport * as path from 'path';\n\nimport { resolveConfig } from '../../config';\nimport { type ParsedArgs } from '../../core/args';\nimport { c } from '../../core/colors';\nimport { loadDevEnv } from '../../shared/env';\nimport { resolveEntry } from '../../shared/fs';\nimport { findNextFreePort, isPortFree, lookupPortOwner } from './port';\n\nconst DEFAULT_DEV_PORT = 3000;\n\nfunction killQuiet(proc?: ChildProcess, signal: NodeJS.Signals = 'SIGINT') {\n try {\n if (proc && proc.exitCode === null && proc.signalCode === null) {\n proc.kill(signal);\n }\n } catch {\n // ignore\n }\n}\n\n/**\n * Resolve the port the dev child should bind to and report any conflict\n * clearly. Returns the chosen port — or never returns and exits the process\n * with a clear error when the port is busy and `--auto-port` was not set.\n *\n * Issue #398: previously the child crashed with a raw `EADDRINUSE` stack\n * trace; this helper turns that into a one-line message with a suggested\n * remediation and (optionally) the owning process.\n */\nexport async function resolveDevPort(opts: {\n port?: number;\n autoPort?: boolean;\n showConflict?: boolean;\n envPort?: string | undefined;\n exit?: (code: number) => never;\n log?: (msg: string) => void;\n}): Promise<number> {\n const exit = opts.exit ?? ((code: number) => process.exit(code) as never);\n const log = opts.log ?? ((msg: string) => console.error(msg));\n const explicit = opts.port ?? (opts.envPort !== undefined && opts.envPort !== '' ? Number(opts.envPort) : undefined);\n const port =\n explicit !== undefined && Number.isFinite(explicit) && (explicit as number) > 0\n ? (explicit as number)\n : DEFAULT_DEV_PORT;\n\n if (await isPortFree(port)) return port;\n\n if (opts.autoPort) {\n const alt = await findNextFreePort(port + 1);\n log(`${c('yellow', '[dev]')} port ${port} is in use; auto-picked ${alt}`);\n return alt;\n }\n\n // Build a clear, actionable error message.\n const lines = [\n `${c('red', '[dev]')} Port ${port} is already in use — refusing to start.`,\n `${c('gray', ' ')} Retry with one of:`,\n `${c('gray', ' ')} • ${c('bold', `frontmcp dev --port <other-port>`)}`,\n `${c('gray', ' ')} • ${c('bold', `frontmcp dev --auto-port`)} ${c('gray', '(pick the next free port automatically)')}`,\n `${c('gray', ' ')} • ${c('bold', `PORT=<other-port> frontmcp dev`)}`,\n ];\n if (opts.showConflict) {\n const owner = await lookupPortOwner(port);\n if (owner) {\n lines.push(`${c('gray', ' ')} Holder of ${port}:`);\n for (const row of owner.split('\\n')) lines.push(`${c('gray', ' ')} ${row}`);\n } else {\n lines.push(`${c('gray', ' ')} (could not identify the holder of port ${port})`);\n }\n } else {\n lines.push(`${c('gray', ' ')} (pass --show-conflict to print which process is holding the port)`);\n }\n for (const line of lines) log(line);\n return exit(1);\n}\n\nexport async function runDev(opts: ParsedArgs): Promise<void> {\n // Issue #399 — `--stdio` runs the first-party watch-aware stdio bridge\n // instead of the legacy `tsx --watch + tsc --noEmit --watch` pair. The\n // bridge owns process stdin/stdout (JSON-RPC frames only), holds the\n // upstream MCP session across child restarts, and replaces the\n // third-party `mcp-remote` recipe for the dev loop.\n if (opts.stdio) {\n const { runDevBridge } = await import('./bridge/index.js');\n return runDevBridge(opts);\n }\n\n const cwd = process.cwd();\n\n // Issue #400 — resolve frontmcp.config so `entry`, `transport.http.port`,\n // and `env.shared`/`env.dev` overlays apply. Precedence:\n // CLI flag > FRONTMCP_<NAME> env > frontmcp.config field > built-in default.\n const resolved = await resolveConfig({\n cwd,\n mode: 'dev',\n configPath: typeof opts.config === 'string' ? opts.config : undefined,\n });\n const cfg = resolved.config;\n\n const cliEntry = typeof opts.entry === 'string' ? opts.entry : undefined;\n const configEntry = typeof cfg?.entry === 'string' ? cfg.entry : undefined;\n const entry = await resolveEntry(cwd, cliEntry ?? configEntry);\n\n // Load .env and .env.local files (these win over config env overlays for\n // parity with existing behavior — file-based env is the deployment escape\n // hatch and shouldn't be silently overridden by committed config).\n loadDevEnv(cwd);\n\n // Resolve the port BEFORE spawning tsx so EADDRINUSE produces a clean\n // one-line error instead of a raw node:net stack trace (issue #398).\n //\n // Two caveats worth knowing about this pre-flight check:\n // 1. TOCTOU — between this probe returning and the child actually binding,\n // another process can grab the port. We accept that race: this is a\n // dev-time tool, the worst case reverts to the prior behaviour (the\n // child surfaces a raw EADDRINUSE), and the common case (port already\n // busy at startup) is the one we wanted to fix.\n // 2. The resolved port is exported as `PORT` to the child. It only takes\n // effect when the user's `@FrontMcp({ http: { port } })` reads\n // `process.env.PORT` (the SDK's `httpOptionsSchema` default does).\n // If the user's metadata HARD-CODES `http.port`, the child binds to\n // that hard-coded value and ignores PORT — the probe is then advisory\n // only. Documented in docs/frontmcp/deployment/local-dev-server.mdx.\n const cliPort = typeof opts.port === 'number' ? opts.port : opts.port ? Number(opts.port) : undefined;\n const configPort = cfg?.transport?.http?.port;\n const port = await resolveDevPort({\n port: cliPort ?? configPort,\n autoPort: !!opts.autoPort,\n showConflict: !!opts.showConflict,\n envPort: process.env['PORT'],\n });\n\n console.log(`${c('cyan', '[dev]')} using entry: ${path.relative(cwd, entry)}`);\n if (resolved.configPath || resolved.configDir) {\n console.log(`${c('gray', '[dev]')} config: ${resolved.configPath ?? resolved.configDir}`);\n }\n console.log(`${c('cyan', '[dev]')} listening on port: ${port}`);\n console.log(\n `${c('gray', '[dev]')} starting ${c('bold', 'tsx --watch')} and ${c(\n 'bold',\n 'tsc --noEmit --watch',\n )} (async type-checker)`,\n );\n console.log(`${c('gray', 'hint:')} press Ctrl+C to stop`);\n\n // Use --conditions node to ensure proper Node.js module resolution.\n // This helps with dynamic require() calls in packages like ioredis.\n // On Windows resolve npx.cmd directly — previously we passed shell:true\n // for the .cmd suffix, but that triggers Node DEP0190 (#381) every run.\n // spawn() resolves .cmd via CreateProcessW since Node 16, so no shell is\n // needed; on Unix spawn() works on 'npx' directly. SIGINT still\n // propagates cleanly because no intermediate shell sits between us and\n // the child process.\n const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';\n // Issue #400 — env overlays from `frontmcp.config.env.{shared,dev}` are\n // included via `resolved.effectiveEnv`. `.env`/`.env.local` already loaded\n // into `process.env` above, so they win (they're closer to deployment).\n const childEnv = { ...resolved.effectiveEnv, ...process.env, PORT: String(port) };\n const app = spawn(npxCmd, ['-y', 'tsx', '--conditions', 'node', '--watch', entry], {\n stdio: 'inherit',\n env: childEnv,\n });\n const checker = spawn(npxCmd, ['-y', 'tsc', '--noEmit', '--pretty', '--watch'], {\n stdio: 'inherit',\n env: childEnv,\n });\n\n const cleanup = (clearTimer = true) => {\n if (clearTimer) {\n clearForceKillTimer();\n }\n killQuiet(checker);\n killQuiet(app);\n };\n\n let forceKillTimer: NodeJS.Timeout | undefined;\n let appClosed = false;\n let checkerClosed = false;\n\n const clearForceKillTimer = () => {\n if (forceKillTimer) {\n clearTimeout(forceKillTimer);\n forceKillTimer = undefined;\n }\n };\n\n const markClosed = (child: 'app' | 'checker') => {\n if (child === 'app') {\n appClosed = true;\n } else {\n checkerClosed = true;\n }\n if (appClosed && checkerClosed) {\n clearForceKillTimer();\n }\n };\n\n process.once('SIGINT', () => {\n cleanup(false);\n // Force-kill after 2s if children haven't exited\n clearForceKillTimer();\n forceKillTimer = setTimeout(() => {\n killQuiet(checker, 'SIGKILL');\n killQuiet(app, 'SIGKILL');\n process.exit(0);\n }, 2000);\n forceKillTimer.unref();\n // Exit cleanly once both children have closed\n const tryExit = () => {\n if (appClosed && checkerClosed) {\n clearForceKillTimer();\n process.exit(0);\n }\n };\n app.once('close', () => {\n markClosed('app');\n tryExit();\n });\n checker.once('close', () => {\n markClosed('checker');\n tryExit();\n });\n });\n\n process.once('SIGTERM', () => {\n cleanup();\n process.exit(0);\n });\n\n let appExitCode: number | null = 0;\n await new Promise<void>((resolve, reject) => {\n app.on('close', (code) => {\n // Capture the child's exit code so it can propagate to the parent\n // shell. SIGINT/SIGTERM yield code=null with a signalCode — treat\n // those as 0 so Ctrl+C doesn't appear as a failure.\n appExitCode = typeof code === 'number' ? code : 0;\n markClosed('app');\n cleanup(false);\n resolve();\n });\n app.on('error', (err) => {\n clearForceKillTimer();\n cleanup();\n reject(err);\n });\n checker.on('close', () => {\n markClosed('checker');\n });\n checker.on('error', (err) => {\n clearForceKillTimer();\n cleanup();\n reject(err);\n });\n });\n\n // Propagate the child's exit code so CI / shells see real failures\n // instead of always-success.\n if (appExitCode && appExitCode !== 0) {\n process.exit(appExitCode);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../../../src/commands/dev/dev.ts"],"names":[],"mappings":";;AA+BA,wCA6CC;AAYD,4CAaC;AAED,wBAuMC;;AA9SD,iDAAyD;AACzD,mDAA6B;AAE7B,yCAA6C;AAE7C,8CAAsC;AACtC,0CAA8C;AAC9C,wCAA+C;AAC/C,iCAAuE;AAEvE,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,SAAS,SAAS,CAAC,IAAmB,EAAE,SAAyB,QAAQ;IACvE,IAAI,CAAC;QACH,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC/D,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,cAAc,CAAC,IAOpC;IACC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAU,CAAC,CAAC;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACrH,MAAM,IAAI,GACR,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAK,QAAmB,GAAG,CAAC;QAC7E,CAAC,CAAE,QAAmB;QACtB,CAAC,CAAC,gBAAgB,CAAC;IAEvB,IAAI,MAAM,IAAA,iBAAU,EAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,IAAA,uBAAgB,EAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAC7C,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,IAAI,2BAA2B,GAAG,EAAE,CAAC,CAAC;QAC1E,OAAO,GAAG,CAAC;IACb,CAAC;IAED,2CAA2C;IAC3C,MAAM,KAAK,GAAG;QACZ,GAAG,IAAA,UAAC,EAAC,KAAK,EAAE,OAAO,CAAC,SAAS,IAAI,yCAAyC;QAC1E,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,QAAQ,CAAC,qBAAqB;QAC3C,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,UAAU,CAAC,MAAM,IAAA,UAAC,EAAC,MAAM,EAAE,kCAAkC,CAAC,EAAE;QAC7E,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,UAAU,CAAC,MAAM,IAAA,UAAC,EAAC,MAAM,EAAE,0BAA0B,CAAC,QAAQ,IAAA,UAAC,EAAC,MAAM,EAAE,yCAAyC,CAAC,EAAE;QACjI,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,UAAU,CAAC,MAAM,IAAA,UAAC,EAAC,MAAM,EAAE,gCAAgC,CAAC,EAAE;KAC5E,CAAC;IACF,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,IAAA,sBAAe,EAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,IAAI,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,QAAQ,CAAC,cAAc,IAAI,GAAG,CAAC,CAAC;YACxD,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,UAAU,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QACrF,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,QAAQ,CAAC,2CAA2C,IAAI,GAAG,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,QAAQ,CAAC,oEAAoE,CAAC,CAAC;IACzG,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,gBAAgB,CAAC,MAKhC;IACC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;IAC/D,OAAO;QACL,GAAG,YAAY;QACf,GAAG,OAAO;QACV,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;QAClB,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,wBAAwB,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtF,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,MAAM,CAAC,IAAgB;IAC3C,uEAAuE;IACvE,uEAAuE;IACvE,qEAAqE;IACrE,+DAA+D;IAC/D,oDAAoD;IACpD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAC3D,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,0EAA0E;IAC1E,yDAAyD;IACzD,+EAA+E;IAC/E,MAAM,QAAQ,GAAG,MAAM,IAAA,sBAAa,EAAC;QACnC,GAAG;QACH,IAAI,EAAE,KAAK;QACX,UAAU,EAAE,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;KACtE,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC;IAE5B,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACzE,MAAM,WAAW,GAAG,OAAO,GAAG,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3E,MAAM,KAAK,GAAG,MAAM,IAAA,iBAAY,EAAC,GAAG,EAAE,QAAQ,IAAI,WAAW,CAAC,CAAC;IAE/D,yEAAyE;IACzE,0EAA0E;IAC1E,mEAAmE;IACnE,IAAA,gBAAU,EAAC,GAAG,CAAC,CAAC;IAEhB,sEAAsE;IACtE,qEAAqE;IACrE,EAAE;IACF,yDAAyD;IACzD,6EAA6E;IAC7E,yEAAyE;IACzE,yEAAyE;IACzE,2EAA2E;IAC3E,qDAAqD;IACrD,2EAA2E;IAC3E,oEAAoE;IACpE,wEAAwE;IACxE,yEAAyE;IACzE,2EAA2E;IAC3E,0EAA0E;IAC1E,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtG,MAAM,UAAU,GAAG,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC;IAC9C,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC;QAChC,IAAI,EAAE,OAAO,IAAI,UAAU;QAC3B,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ;QACzB,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY;QACjC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;KAC7B,CAAC,CAAC;IAEH,iFAAiF;IACjF,gFAAgF;IAChF,gFAAgF;IAChF,kFAAkF;IAClF,gFAAgF;IAChF,MAAM,cAAc,GAAG,OAAO,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAE5G,OAAO,CAAC,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/E,IAAI,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,OAAO,CAAC,YAAY,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IAChE,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,cAAc,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,CAAC,GAAG,CACT,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,OAAO,CAAC,aAAa,IAAA,UAAC,EAAC,MAAM,EAAE,aAAa,CAAC,QAAQ,IAAA,UAAC,EACjE,MAAM,EACN,sBAAsB,CACvB,uBAAuB,CACzB,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,GAAG,IAAA,UAAC,EAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAE1D,oEAAoE;IACpE,oEAAoE;IACpE,wEAAwE;IACxE,wEAAwE;IACxE,yEAAyE;IACzE,gEAAgE;IAChE,uEAAuE;IACvE,qBAAqB;IACrB,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;IAChE,wEAAwE;IACxE,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,QAAQ,GAAG,gBAAgB,CAAC;QAChC,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,OAAO,EAAE,OAAO,CAAC,GAAG;QACpB,IAAI;QACJ,cAAc;KACf,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,IAAA,qBAAK,EAAC,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE;QACjF,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,QAAQ;KACd,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAA,qBAAK,EAAC,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE;QAC9E,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,QAAQ;KACd,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,CAAC,UAAU,GAAG,IAAI,EAAE,EAAE;QACpC,IAAI,UAAU,EAAE,CAAC;YACf,mBAAmB,EAAE,CAAC;QACxB,CAAC;QACD,SAAS,CAAC,OAAO,CAAC,CAAC;QACnB,SAAS,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC,CAAC;IAEF,IAAI,cAA0C,CAAC;IAC/C,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,MAAM,mBAAmB,GAAG,GAAG,EAAE;QAC/B,IAAI,cAAc,EAAE,CAAC;YACnB,YAAY,CAAC,cAAc,CAAC,CAAC;YAC7B,cAAc,GAAG,SAAS,CAAC;QAC7B,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,CAAC,KAAwB,EAAE,EAAE;QAC9C,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;YACpB,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,aAAa,GAAG,IAAI,CAAC;QACvB,CAAC;QACD,IAAI,SAAS,IAAI,aAAa,EAAE,CAAC;YAC/B,mBAAmB,EAAE,CAAC;QACxB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;QAC1B,OAAO,CAAC,KAAK,CAAC,CAAC;QACf,iDAAiD;QACjD,mBAAmB,EAAE,CAAC;QACtB,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YAC/B,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YAC9B,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,EAAE,IAAI,CAAC,CAAC;QACT,cAAc,CAAC,KAAK,EAAE,CAAC;QACvB,8CAA8C;QAC9C,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,IAAI,SAAS,IAAI,aAAa,EAAE,CAAC;gBAC/B,mBAAmB,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,UAAU,CAAC,KAAK,CAAC,CAAC;YAClB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACzB,UAAU,CAAC,SAAS,CAAC,CAAC;YACtB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;QAC3B,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,IAAI,WAAW,GAAkB,CAAC,CAAC;IACnC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACvB,kEAAkE;YAClE,kEAAkE;YAClE,oDAAoD;YACpD,WAAW,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAClD,UAAU,CAAC,KAAK,CAAC,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,CAAC;YACf,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACtB,mBAAmB,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACvB,UAAU,CAAC,SAAS,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC1B,mBAAmB,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,mEAAmE;IACnE,6BAA6B;IAC7B,IAAI,WAAW,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC","sourcesContent":["import { spawn, type ChildProcess } from 'child_process';\nimport * as path from 'path';\n\nimport { resolveConfig } from '../../config';\nimport { type ParsedArgs } from '../../core/args';\nimport { c } from '../../core/colors';\nimport { loadDevEnv } from '../../shared/env';\nimport { resolveEntry } from '../../shared/fs';\nimport { findNextFreePort, isPortFree, lookupPortOwner } from './port';\n\nconst DEFAULT_DEV_PORT = 3000;\n\nfunction killQuiet(proc?: ChildProcess, signal: NodeJS.Signals = 'SIGINT') {\n try {\n if (proc && proc.exitCode === null && proc.signalCode === null) {\n proc.kill(signal);\n }\n } catch {\n // ignore\n }\n}\n\n/**\n * Resolve the port the dev child should bind to and report any conflict\n * clearly. Returns the chosen port — or never returns and exits the process\n * with a clear error when the port is busy and `--auto-port` was not set.\n *\n * Issue #398: previously the child crashed with a raw `EADDRINUSE` stack\n * trace; this helper turns that into a one-line message with a suggested\n * remediation and (optionally) the owning process.\n */\nexport async function resolveDevPort(opts: {\n port?: number;\n autoPort?: boolean;\n showConflict?: boolean;\n envPort?: string | undefined;\n exit?: (code: number) => never;\n log?: (msg: string) => void;\n}): Promise<number> {\n const exit = opts.exit ?? ((code: number) => process.exit(code) as never);\n const log = opts.log ?? ((msg: string) => console.error(msg));\n const explicit = opts.port ?? (opts.envPort !== undefined && opts.envPort !== '' ? Number(opts.envPort) : undefined);\n const port =\n explicit !== undefined && Number.isFinite(explicit) && (explicit as number) > 0\n ? (explicit as number)\n : DEFAULT_DEV_PORT;\n\n if (await isPortFree(port)) return port;\n\n if (opts.autoPort) {\n const alt = await findNextFreePort(port + 1);\n log(`${c('yellow', '[dev]')} port ${port} is in use; auto-picked ${alt}`);\n return alt;\n }\n\n // Build a clear, actionable error message.\n const lines = [\n `${c('red', '[dev]')} Port ${port} is already in use — refusing to start.`,\n `${c('gray', ' ')} Retry with one of:`,\n `${c('gray', ' ')} • ${c('bold', `frontmcp dev --port <other-port>`)}`,\n `${c('gray', ' ')} • ${c('bold', `frontmcp dev --auto-port`)} ${c('gray', '(pick the next free port automatically)')}`,\n `${c('gray', ' ')} • ${c('bold', `PORT=<other-port> frontmcp dev`)}`,\n ];\n if (opts.showConflict) {\n const owner = await lookupPortOwner(port);\n if (owner) {\n lines.push(`${c('gray', ' ')} Holder of ${port}:`);\n for (const row of owner.split('\\n')) lines.push(`${c('gray', ' ')} ${row}`);\n } else {\n lines.push(`${c('gray', ' ')} (could not identify the holder of port ${port})`);\n }\n } else {\n lines.push(`${c('gray', ' ')} (pass --show-conflict to print which process is holding the port)`);\n }\n for (const line of lines) log(line);\n return exit(1);\n}\n\n/**\n * Build the environment handed to the spawned dev child.\n *\n * The resolved port is exported as `PORT`, and the configured\n * `transport.http.path` (when set) as `FRONTMCP_HTTP_ENTRY_PATH` so the server\n * mounts the MCP endpoint where the generated client URLs point (#446). Both are\n * applied AFTER the inherited env so the dev-resolved values win for this run —\n * the same precedence as `PORT`. A hard-coded `@FrontMcp({ http: { entryPath } })`\n * in metadata still wins over the env (the SDK only reads it as a default).\n */\nexport function buildDevChildEnv(params: {\n effectiveEnv: NodeJS.ProcessEnv;\n baseEnv: NodeJS.ProcessEnv;\n port: number;\n configHttpPath?: string;\n}): NodeJS.ProcessEnv {\n const { effectiveEnv, baseEnv, port, configHttpPath } = params;\n return {\n ...effectiveEnv,\n ...baseEnv,\n PORT: String(port),\n ...(configHttpPath !== undefined ? { FRONTMCP_HTTP_ENTRY_PATH: configHttpPath } : {}),\n };\n}\n\nexport async function runDev(opts: ParsedArgs): Promise<void> {\n // Issue #399 — `--stdio` runs the first-party watch-aware stdio bridge\n // instead of the legacy `tsx --watch + tsc --noEmit --watch` pair. The\n // bridge owns process stdin/stdout (JSON-RPC frames only), holds the\n // upstream MCP session across child restarts, and replaces the\n // third-party `mcp-remote` recipe for the dev loop.\n if (opts.stdio) {\n const { runDevBridge } = await import('./bridge/index.js');\n return runDevBridge(opts);\n }\n\n const cwd = process.cwd();\n\n // Issue #400 — resolve frontmcp.config so `entry`, `transport.http.port`,\n // and `env.shared`/`env.dev` overlays apply. Precedence:\n // CLI flag > FRONTMCP_<NAME> env > frontmcp.config field > built-in default.\n const resolved = await resolveConfig({\n cwd,\n mode: 'dev',\n configPath: typeof opts.config === 'string' ? opts.config : undefined,\n });\n const cfg = resolved.config;\n\n const cliEntry = typeof opts.entry === 'string' ? opts.entry : undefined;\n const configEntry = typeof cfg?.entry === 'string' ? cfg.entry : undefined;\n const entry = await resolveEntry(cwd, cliEntry ?? configEntry);\n\n // Load .env and .env.local files (these win over config env overlays for\n // parity with existing behavior — file-based env is the deployment escape\n // hatch and shouldn't be silently overridden by committed config).\n loadDevEnv(cwd);\n\n // Resolve the port BEFORE spawning tsx so EADDRINUSE produces a clean\n // one-line error instead of a raw node:net stack trace (issue #398).\n //\n // Two caveats worth knowing about this pre-flight check:\n // 1. TOCTOU — between this probe returning and the child actually binding,\n // another process can grab the port. We accept that race: this is a\n // dev-time tool, the worst case reverts to the prior behaviour (the\n // child surfaces a raw EADDRINUSE), and the common case (port already\n // busy at startup) is the one we wanted to fix.\n // 2. The resolved port is exported as `PORT` to the child. It only takes\n // effect when the user's `@FrontMcp({ http: { port } })` reads\n // `process.env.PORT` (the SDK's `httpOptionsSchema` default does).\n // If the user's metadata HARD-CODES `http.port`, the child binds to\n // that hard-coded value and ignores PORT — the probe is then advisory\n // only. Documented in docs/frontmcp/deployment/local-dev-server.mdx.\n const cliPort = typeof opts.port === 'number' ? opts.port : opts.port ? Number(opts.port) : undefined;\n const configPort = cfg?.transport?.http?.port;\n const port = await resolveDevPort({\n port: cliPort ?? configPort,\n autoPort: !!opts.autoPort,\n showConflict: !!opts.showConflict,\n envPort: process.env['PORT'],\n });\n\n // Issue #446 — honor the configured MCP mount path in dev. `transport.http.path`\n // already drives the generated client URLs (eject); propagate it to the spawned\n // server via FRONTMCP_HTTP_ENTRY_PATH so the endpoint is actually mounted there\n // (the SDK's httpOptionsSchema.entryPath default reads this env). Same precedence\n // caveat as PORT: a hard-coded `@FrontMcp({ http: { entryPath } })` still wins.\n const configHttpPath = typeof cfg?.transport?.http?.path === 'string' ? cfg.transport.http.path : undefined;\n\n console.log(`${c('cyan', '[dev]')} using entry: ${path.relative(cwd, entry)}`);\n if (resolved.configPath || resolved.configDir) {\n console.log(`${c('gray', '[dev]')} config: ${resolved.configPath ?? resolved.configDir}`);\n }\n console.log(`${c('cyan', '[dev]')} listening on port: ${port}`);\n if (configHttpPath) {\n console.log(`${c('gray', '[dev]')} MCP endpoint path: ${configHttpPath}`);\n }\n console.log(\n `${c('gray', '[dev]')} starting ${c('bold', 'tsx --watch')} and ${c(\n 'bold',\n 'tsc --noEmit --watch',\n )} (async type-checker)`,\n );\n console.log(`${c('gray', 'hint:')} press Ctrl+C to stop`);\n\n // Use --conditions node to ensure proper Node.js module resolution.\n // This helps with dynamic require() calls in packages like ioredis.\n // On Windows resolve npx.cmd directly — previously we passed shell:true\n // for the .cmd suffix, but that triggers Node DEP0190 (#381) every run.\n // spawn() resolves .cmd via CreateProcessW since Node 16, so no shell is\n // needed; on Unix spawn() works on 'npx' directly. SIGINT still\n // propagates cleanly because no intermediate shell sits between us and\n // the child process.\n const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';\n // Issue #400 — env overlays from `frontmcp.config.env.{shared,dev}` are\n // included via `resolved.effectiveEnv`. `.env`/`.env.local` already loaded\n // into `process.env` above, so they win (they're closer to deployment).\n const childEnv = buildDevChildEnv({\n effectiveEnv: resolved.effectiveEnv,\n baseEnv: process.env,\n port,\n configHttpPath,\n });\n const app = spawn(npxCmd, ['-y', 'tsx', '--conditions', 'node', '--watch', entry], {\n stdio: 'inherit',\n env: childEnv,\n });\n const checker = spawn(npxCmd, ['-y', 'tsc', '--noEmit', '--pretty', '--watch'], {\n stdio: 'inherit',\n env: childEnv,\n });\n\n const cleanup = (clearTimer = true) => {\n if (clearTimer) {\n clearForceKillTimer();\n }\n killQuiet(checker);\n killQuiet(app);\n };\n\n let forceKillTimer: NodeJS.Timeout | undefined;\n let appClosed = false;\n let checkerClosed = false;\n\n const clearForceKillTimer = () => {\n if (forceKillTimer) {\n clearTimeout(forceKillTimer);\n forceKillTimer = undefined;\n }\n };\n\n const markClosed = (child: 'app' | 'checker') => {\n if (child === 'app') {\n appClosed = true;\n } else {\n checkerClosed = true;\n }\n if (appClosed && checkerClosed) {\n clearForceKillTimer();\n }\n };\n\n process.once('SIGINT', () => {\n cleanup(false);\n // Force-kill after 2s if children haven't exited\n clearForceKillTimer();\n forceKillTimer = setTimeout(() => {\n killQuiet(checker, 'SIGKILL');\n killQuiet(app, 'SIGKILL');\n process.exit(0);\n }, 2000);\n forceKillTimer.unref();\n // Exit cleanly once both children have closed\n const tryExit = () => {\n if (appClosed && checkerClosed) {\n clearForceKillTimer();\n process.exit(0);\n }\n };\n app.once('close', () => {\n markClosed('app');\n tryExit();\n });\n checker.once('close', () => {\n markClosed('checker');\n tryExit();\n });\n });\n\n process.once('SIGTERM', () => {\n cleanup();\n process.exit(0);\n });\n\n let appExitCode: number | null = 0;\n await new Promise<void>((resolve, reject) => {\n app.on('close', (code) => {\n // Capture the child's exit code so it can propagate to the parent\n // shell. SIGINT/SIGTERM yield code=null with a signalCode — treat\n // those as 0 so Ctrl+C doesn't appear as a failure.\n appExitCode = typeof code === 'number' ? code : 0;\n markClosed('app');\n cleanup(false);\n resolve();\n });\n app.on('error', (err) => {\n clearForceKillTimer();\n cleanup();\n reject(err);\n });\n checker.on('close', () => {\n markClosed('checker');\n });\n checker.on('error', (err) => {\n clearForceKillTimer();\n cleanup();\n reject(err);\n });\n });\n\n // Propagate the child's exit code so CI / shells see real failures\n // instead of always-success.\n if (appExitCode && appExitCode !== 0) {\n process.exit(appExitCode);\n }\n}\n"]}
|
|
@@ -565,7 +565,7 @@ function generatePmSetupSteps(pm) {
|
|
|
565
565
|
uses: pnpm/action-setup@v4
|
|
566
566
|
|
|
567
567
|
- name: Setup Node.js
|
|
568
|
-
uses: actions/setup-node@
|
|
568
|
+
uses: actions/setup-node@v6
|
|
569
569
|
with:
|
|
570
570
|
node-version: '24'
|
|
571
571
|
cache: '${cfg.ghCache}'
|
|
@@ -575,7 +575,7 @@ function generatePmSetupSteps(pm) {
|
|
|
575
575
|
}
|
|
576
576
|
return `
|
|
577
577
|
- name: Setup Node.js
|
|
578
|
-
uses: actions/setup-node@
|
|
578
|
+
uses: actions/setup-node@v6
|
|
579
579
|
with:
|
|
580
580
|
node-version: '24'
|
|
581
581
|
cache: '${cfg.ghCache}'
|
|
@@ -599,7 +599,7 @@ jobs:
|
|
|
599
599
|
runs-on: ubuntu-latest
|
|
600
600
|
|
|
601
601
|
steps:
|
|
602
|
-
- uses: actions/checkout@
|
|
602
|
+
- uses: actions/checkout@v6
|
|
603
603
|
${generatePmSetupSteps(pm)}
|
|
604
604
|
|
|
605
605
|
- name: Type check
|
|
@@ -625,7 +625,7 @@ jobs:
|
|
|
625
625
|
runs-on: ubuntu-latest
|
|
626
626
|
|
|
627
627
|
steps:
|
|
628
|
-
- uses: actions/checkout@
|
|
628
|
+
- uses: actions/checkout@v6
|
|
629
629
|
${generatePmSetupSteps(pm)}
|
|
630
630
|
|
|
631
631
|
- name: Build
|
|
@@ -655,7 +655,7 @@ jobs:
|
|
|
655
655
|
packages: write
|
|
656
656
|
|
|
657
657
|
steps:
|
|
658
|
-
- uses: actions/checkout@
|
|
658
|
+
- uses: actions/checkout@v6
|
|
659
659
|
|
|
660
660
|
- name: Log in to Container Registry
|
|
661
661
|
uses: docker/login-action@v3
|
|
@@ -693,7 +693,7 @@ jobs:
|
|
|
693
693
|
runs-on: ubuntu-latest
|
|
694
694
|
|
|
695
695
|
steps:
|
|
696
|
-
- uses: actions/checkout@
|
|
696
|
+
- uses: actions/checkout@v6
|
|
697
697
|
${generatePmSetupSteps(pm)}
|
|
698
698
|
|
|
699
699
|
- name: Build
|
|
@@ -722,7 +722,7 @@ jobs:
|
|
|
722
722
|
runs-on: ubuntu-latest
|
|
723
723
|
|
|
724
724
|
steps:
|
|
725
|
-
- uses: actions/checkout@
|
|
725
|
+
- uses: actions/checkout@v6
|
|
726
726
|
${generatePmSetupSteps(pm)}
|
|
727
727
|
|
|
728
728
|
- name: Build
|
|
@@ -759,7 +759,7 @@ jobs:
|
|
|
759
759
|
runs-on: ubuntu-latest
|
|
760
760
|
|
|
761
761
|
steps:
|
|
762
|
-
- uses: actions/checkout@
|
|
762
|
+
- uses: actions/checkout@v6
|
|
763
763
|
${generatePmSetupSteps(pm)}
|
|
764
764
|
|
|
765
765
|
- name: Build
|