infra-kit 0.1.133 → 0.1.134

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,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/entry/dev-server.ts", "../src/dev/cmux-dev.ts", "../src/dev/cmux-layout.ts", "../src/dev/signal-shutdown.ts", "../src/dev/managed-child.ts", "../src/dev/crash-barrier.ts", "../src/dev/dev-server.ts", "../src/dev/dep-closure.ts", "../src/dev/ports.ts", "../src/dev/serverless-local-run.ts", "../src/dev/turbo-watch.ts", "../src/dev/ui-dev.ts"],
4
- "sourcesContent": ["/**\n * Long-running CLI entry for the local dev-server. Owns flag parsing and wires signal\n * handling to `src/dev/signal-shutdown` \u2014 the orchestrator (`src/dev/dev-server`) stays\n * signal- and exit-agnostic. Kept off the eager cli.js graph: entry/cli.ts reaches this\n * module (and the fastify/chokidar it pulls in) only via `await import(...)`, so\n * those heavy deps never load on the machine command paths.\n */\nimport { Command } from 'commander'\nimport process from 'node:process'\nimport { pathToFileURL } from 'node:url'\n\nimport { runCmuxDevServer } from 'src/dev/cmux-dev'\nimport { registerCrashBarrier } from 'src/dev/crash-barrier'\nimport { run } from 'src/dev/dev-server'\nimport type { DevServerOptions } from 'src/dev/dev-server'\nimport type { WizardResult } from 'src/dev/dev-wizard-run'\nimport { resolveSelfAppName } from 'src/dev/discovery'\nimport { explainTargetKey } from 'src/dev/presets'\nimport { registerSignalShutdown } from 'src/dev/signal-shutdown'\nimport { isCmuxAvailable } from 'src/integrations/cmux'\nimport { isPromptCancellation } from 'src/lib/errors/is-prompt-cancellation'\nimport type { DevPreset } from 'src/lib/infra-kit-config'\n\n/** Raw option object as produced by Commander (comma-joined strings). */\nexport interface DevCliOptions {\n watch?: boolean\n app?: string\n /**\n * Comma-separated `<app>/<part>` target keys (`--target=client/api,client/ui`). The part-level\n * selector `--app` cannot express: `--app=client` expands to every part `client` has. Same grammar as\n * a `devServersPresets` key, so a wizard selection round-trips into a command you can paste.\n */\n target?: string\n /** Named preset positional (`infra-kit dev <preset>`); selects launch targets from `devServersPresets`. */\n preset?: string\n cmux?: boolean\n self?: boolean\n verbose?: boolean\n /** Print each app's registered routes at startup (opt-in; off keeps the calm default screen). */\n routes?: boolean\n}\n\n/**\n * Turn `--target=<app>/<part>,\u2026` into the in-memory preset the runner already understands, or\n * `undefined` when the flag is absent (leaving `preset`/`*` resolution untouched). Validated against the\n * same grammar as a `devServersPresets` key, but with a message that names the FLAG \u2014 a user typing\n * `--target=client` must not be told about `devServersPresets`.\n */\nconst toPresetDef = (targets: string[] | null): DevPreset | undefined => {\n if (targets == null) return undefined\n\n for (const key of targets) {\n if (explainTargetKey(key) !== null) {\n throw new Error(`infra-kit dev: invalid --target \"${key}\" (expected \"<app>/api\" or \"<app>/ui\").`)\n }\n }\n\n return {\n apps: Object.fromEntries(\n targets.map((key) => {\n return [key, {}]\n }),\n ),\n }\n}\n\n/** Split a comma-separated flag value into a trimmed, non-empty list (`null` when unset/empty). */\nconst splitList = (value: string | undefined): string[] | null => {\n if (value == null) return null\n\n const parts = value\n .split(',')\n .map((s) => {\n return s.trim()\n })\n .filter(Boolean)\n\n return parts.length > 0 ? parts : null\n}\n\n/**\n * Map raw Commander flags to the orchestrator's typed options. Shared by the\n * `infra-kit dev` subcommand and the standalone entry so the two never diverge.\n * `self` is passed through as-is here \u2014 it's resolved to `include` later, in\n * `runDevServer`, where `--app` (if also given) is allowed to win.\n */\nexport const toDevServerOptions = (raw: DevCliOptions): DevServerOptions => {\n return {\n watch: raw.watch ?? false,\n include: splitList(raw.app),\n preset: raw.preset,\n presetDef: toPresetDef(splitList(raw.target)),\n cmux: raw.cmux ?? false,\n self: raw.self ?? false,\n verbose: raw.verbose ?? false,\n routes: raw.routes ?? false,\n }\n}\n\n/**\n * Resolve `--self` into an `include` list, without mutating `options`. `--app` (an\n * explicit `include`) wins over `--self` when both are given \u2014 self is only a\n * convenience default for scripts that don't want to hardcode their app name.\n * Lets `resolveSelfAppName`'s error (not inside `apps/<app>/...`) propagate as-is;\n * the caller's top-level catch turns it into a clean message + non-zero exit.\n */\nconst resolveSelfOptions = (options: DevServerOptions): DevServerOptions => {\n if (!options.self || options.include) {\n return options\n }\n\n return { ...options, include: [resolveSelfAppName(process.cwd())] }\n}\n\n/**\n * Start the dev-server, then wait for an OS signal and shut every server down\n * cleanly before exiting. Signal handling and exit are delegated to\n * {@link registerSignalShutdown}: a second signal force-quits, a rejected teardown\n * is logged, and the process exits `128 + signo` rather than a dishonest `0`.\n */\nexport const runDevServer = async (rawOptions: DevServerOptions): Promise<void> => {\n const options = resolveSelfOptions(rawOptions)\n\n // `--cmux`: one workspace, one pane per app. `runCmuxDevServer` owns its own\n // signal handling and never returns, so return before wiring the in-process\n // handlers below. Fall through to single-process dev when cmux isn't installed.\n if (options.cmux) {\n if (await isCmuxAvailable()) {\n await runCmuxDevServer(options)\n\n return\n }\n\n process.stdout.write('cmux not available; falling back to single-terminal dev\\n')\n }\n\n const runner = await run(options)\n\n // In-process backends share this event loop; a handler's escaped async path would otherwise terminate\n // the whole session. Installed only on the single-process path (the cmux path returned above, each pane\n // being its own process) and only after `run()` succeeds, so a boot failure still exits honestly.\n registerCrashBarrier()\n\n registerSignalShutdown({\n onSignal: async (signal) => {\n process.stdout.write(`\\nReceived ${signal}, shutting down dev-server...\\n`)\n await runner.shutdown()\n },\n })\n}\n\n/**\n * True when `infra-kit dev` was invoked BARE \u2014 no preset and no selection/mode flag \u2014 in an interactive\n * TTY (both stdin and stdout) and not `--json`. This is the ONLY condition that launches the wizard;\n * every flagged, piped, non-TTY, `--json`, or MCP invocation runs directly from the parsed flags, so no\n * existing script path changes behaviour.\n */\nexport const shouldRunWizard = (raw: DevCliOptions, tty: boolean, json: boolean): boolean => {\n const bare = !raw.preset && !raw.app && !raw.self && !raw.cmux && !raw.watch && !raw.verbose && !raw.routes\n\n return bare && tty && !json\n}\n\n/** Map a wizard result to runner options: the cmux path uses `include`; otherwise the in-memory `presetDef`. */\nconst wizardToOptions = (result: WizardResult): DevServerOptions => {\n return {\n watch: result.watch,\n cmux: result.cmux,\n include: result.include ?? null,\n preset: result.preset,\n presetDef: result.presetDef,\n self: false,\n verbose: false,\n routes: false,\n }\n}\n\n/**\n * Entry for the `infra-kit dev` subcommand. On a bare TTY invocation it launches the interactive wizard\n * (lazily imported so its inquirer/config graph stays off every other path), then starts the server with\n * the assembled options; otherwise it runs directly from the parsed flags. A cancelled prompt (Ctrl-C /\n * Esc) or an empty selection exits cleanly without starting a server.\n */\nexport const runDevServerCli = async (raw: DevCliOptions, tty: boolean, json: boolean): Promise<void> => {\n if (shouldRunWizard(raw, tty, json)) {\n const { runDevWizard } = await import('src/dev/dev-wizard-run')\n\n let result: WizardResult | null\n\n try {\n result = await runDevWizard()\n } catch (error) {\n if (isPromptCancellation(error)) return\n\n throw error\n }\n\n if (result == null) return\n\n // The wizard only runs on a bare interactive TTY, so tty=true, json=false \u2014 thread them so `run()`\n // selects the Ink boot UI.\n await runDevServer({ ...wizardToOptions(result), tty, json })\n\n return\n }\n\n // Thread the TTY / json signal so `run()` can pick the Ink boot UI (TTY, non-json) vs the plain renderer.\n await runDevServer({ ...toDevServerOptions(raw), tty, json })\n}\n\n/** Parse `node dist/dev-server.js ...` flags and start the server. */\nconst parseAndRun = async (argv: string[]): Promise<void> => {\n const program = new Command()\n\n program\n .name('infra-kit-dev-server')\n .description('Run local dev servers for the apps in a named devServersPresets preset (or all apps)')\n .argument('[preset]', 'Named preset from devServersPresets (omit to run every app)')\n .option('-w, --watch', 'Rebuild and restart on file save')\n .option('--app <names>', 'Further narrow to these app folder names (comma-separated)')\n .option(\n '--cmux',\n 'Run each app in its own cmux pane (one workspace, N panes; falls back to single terminal if cmux is unavailable)',\n )\n .option('--self', 'Run only the app of the current directory (infer from cwd; use inside apps/<app>/\u2026)')\n .option('-V, --verbose', 'Print full boot narration (default: quiet; full detail always in the session log)')\n .option('--routes', 'Print each app\u2019s registered METHOD /path routes at startup (default: off)')\n\n program.parse(argv)\n\n await runDevServer(toDevServerOptions({ ...program.opts<DevCliOptions>(), preset: program.args[0] }))\n}\n\n// Self-execute only when run directly (`node dist/dev-server.js`), not when the\n// `infra-kit dev` subcommand imports this module for its exported helpers.\nif (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {\n parseAndRun(process.argv).catch((error: unknown) => {\n // Message only, matching the `infra-kit dev` path: a bad preset name or target key is a\n // config mistake, and its stack frames are noise.\n console.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n })\n}\n", "/**\n * Resident `infra-kit dev --cmux` supervisor.\n *\n * Opens ONE cmux workspace with one pane per discovered backend API app, each\n * pane running the primitive `pnpm exec infra-kit dev --app=<name>` (single-app,\n * single-process). Stays resident: on SIGINT/SIGTERM it closes the workspace\n * (reaping every pane) and exits. The pure command/layout construction lives here\n * and in {@link file://./cmux-layout.ts} so it is unit-testable without a shell.\n */\nimport * as path from 'node:path'\nimport process from 'node:process'\n\nimport { closeCmuxDevWorkspace, openCmuxDevWorkspace } from 'src/integrations/cmux'\nimport { logger } from 'src/lib/logger'\n\nimport { buildCmuxLayout } from './cmux-layout.js'\nimport type { DevServerOptions } from './dev-server.js'\nimport { discoverApiApps, findMonorepoRoot, normalizeAppInclude } from './discovery.js'\nimport type { DiscoveredApiApp } from './discovery.js'\nimport { registerSignalShutdown } from './signal-shutdown.js'\n\n/** One cmux pane: the app it runs, and (optionally) the exact `<app>/<part>` targets selected for it. */\nexport interface PaneSpec {\n app: string\n /**\n * Exact target keys for this pane. When absent the pane falls back to `--app=<name>`, which expands to\n * EVERY part the app has (both the api and the ui glob targets) \u2014 right for a plain `--cmux`, wrong for a wizard selection\n * that deliberately unticked a part. `--app` has app-name granularity and cannot express `<app>/api`\n * alone; that is exactly what `--target` exists for.\n */\n targets?: string[]\n}\n\n/** Build the per-pane primitive command for each pane (append `--watch` when requested). */\nexport const buildPaneCommands = (panes: PaneSpec[], watch: boolean): string[] => {\n return panes.map(({ app, targets }) => {\n const selector = targets && targets.length > 0 ? `--target=${targets.join(',')}` : `--app=${app}`\n\n return `pnpm exec infra-kit dev ${selector}${watch ? ' --watch' : ''}`\n })\n}\n\n/** The concrete `<app>/<part>` keys of an in-memory preset, grouped by app. Glob keys (those whose app segment is a bare star) are skipped. */\nexport const paneTargetsByApp = (presetDef: DevServerOptions['presetDef']): Map<string, string[]> => {\n const byApp = new Map<string, string[]>()\n\n for (const key of Object.keys(presetDef?.apps ?? {})) {\n const app = key.split('/')[0]\n\n if (app === undefined || app === '*') continue\n\n byApp.set(app, [...(byApp.get(app) ?? []), key])\n }\n\n return byApp\n}\n\n/** Discover API apps under `root`, applying the optional `--app` include filter. */\nconst selectApiApps = (root: string, include: string[] | null): DiscoveredApiApp[] => {\n const apps = discoverApiApps(root)\n\n if (!include) {\n return apps\n }\n\n return apps.filter((app) => {\n return include.includes(app.name)\n })\n}\n\n/**\n * Log each app pane and the opened workspace ref. Ports are NOT shown here: each\n * pane binds a dynamic (ephemeral) port at runtime and prints its own real port \u2014\n * the supervisor cannot know it at spawn time, so a static resolved port would lie.\n */\nconst logDevWorkspace = (apps: DiscoveredApiApp[], ref: string): void => {\n logger.info(`\uD83E\uDDE9 Opened cmux dev workspace ${ref} with ${apps.length} pane(s):`)\n\n for (const app of apps) {\n logger.info(` \u2022 ${app.name} (infra-kit dev --app=${app.name})`)\n }\n}\n\n/**\n * Register SIGINT/SIGTERM handlers that close the workspace `ref` \u2014 the supervisor's whole\n * teardown. No force-deadline: closing a workspace is a single call, not a child reap, and a\n * second signal is already an unconditional escape (see {@link registerSignalShutdown}).\n */\nconst registerShutdown = (ref: string): void => {\n registerSignalShutdown({\n onSignal: async (signal) => {\n logger.info(`\\nReceived ${signal}, closing cmux dev workspace ${ref}...`)\n await closeCmuxDevWorkspace(ref)\n },\n })\n}\n\n/**\n * Open a cmux workspace with one pane per API app, then stay resident as a\n * supervisor until a signal tears the workspace down. Falls out early (no\n * workspace) when no apps are discovered. Never returns on the happy path \u2014 it\n * owns its own SIGINT/SIGTERM handling and blocks forever otherwise.\n *\n * @example\n * // Runs until Ctrl-C; opens `pnpm exec infra-kit dev --app=<name>` per app.\n * await runCmuxDevServer({ include: null, watch: false })\n */\nexport const runCmuxDevServer = async (options: DevServerOptions): Promise<void> => {\n const root = findMonorepoRoot(process.cwd())\n const apps = selectApiApps(root, normalizeAppInclude(options.include))\n\n if (apps.length === 0) {\n logger.warn('No API apps found to run')\n\n return\n }\n\n // A wizard run hands down its in-memory preset, so each pane reproduces the exact parts that were\n // ticked. A plain `--cmux` has no presetDef and falls back to `--app=<name>` (every part), unchanged.\n const targetsByApp = paneTargetsByApp(options.presetDef)\n const commands = buildPaneCommands(\n apps.map((app) => {\n return { app: app.name, targets: targetsByApp.get(app.name) }\n }),\n options.watch ?? false,\n )\n const layout = buildCmuxLayout(commands)\n const title = `${path.basename(root)} dev`\n const ref = await openCmuxDevWorkspace({ cwd: root, title, layout })\n\n logDevWorkspace(apps, ref)\n registerShutdown(ref)\n\n // Stay resident until a signal fires. A never-resolving promise alone does NOT\n // keep Node's event loop alive (nothing pending \u2192 the process exits with code\n // 13, \"unsettled top-level await\"); a ref'd heartbeat timer holds it open. The\n // SIGINT/SIGTERM handler in registerShutdown owns the actual exit.\n const heartbeat = setInterval(() => {\n heartbeat.refresh()\n }, 2 ** 30)\n\n await new Promise<never>(() => {})\n}\n", "/**\n * Pure cmux layout construction for `infra-kit dev --cmux`.\n *\n * Turns a flat list of per-pane shell commands into the recursive binary-tree\n * layout JSON cmux's `new-workspace --layout` consumes. Side-effect free (no fs,\n * no cwd, no shell) so the tiling math stays unit-testable in isolation.\n */\n\n/** A single terminal surface inside a pane, running one shell command. */\nexport interface CmuxTerminalSurface {\n type: 'terminal'\n command: string\n}\n\n/** A leaf node: one pane holding one or more terminal surfaces. */\nexport interface CmuxPaneLeaf {\n pane: {\n surfaces: CmuxTerminalSurface[]\n }\n}\n\n/** An internal node: a horizontal/vertical split into exactly two children. */\nexport interface CmuxSplitNode {\n direction: 'horizontal' | 'vertical'\n split: number\n children: [CmuxLayoutNode, CmuxLayoutNode]\n}\n\n/** Either a pane leaf or a two-way split \u2014 the recursive layout tree. */\nexport type CmuxLayoutNode = CmuxPaneLeaf | CmuxSplitNode\n\n/** Wrap a single command in a bare pane leaf. */\nconst makeLeaf = (command: string): CmuxPaneLeaf => {\n return { pane: { surfaces: [{ type: 'terminal', command }] } }\n}\n\n/**\n * Recursively split `commands` into a balanced binary tree: the left half takes\n * `Math.ceil(n / 2)` commands, the split ratio is that count over the total\n * (2-decimal), and the direction alternates by depth (even \u2192 horizontal,\n * odd \u2192 vertical) so panes tile into a grid rather than one axis.\n */\nconst buildNode = (commands: string[], depth: number): CmuxLayoutNode => {\n if (commands.length === 1) {\n return makeLeaf(commands[0]!)\n }\n\n const leftCount = Math.ceil(commands.length / 2)\n const left = commands.slice(0, leftCount)\n const right = commands.slice(leftCount)\n\n return {\n direction: depth % 2 === 0 ? 'horizontal' : 'vertical',\n split: Math.round((leftCount / commands.length) * 100) / 100,\n children: [buildNode(left, depth + 1), buildNode(right, depth + 1)],\n }\n}\n\n/**\n * Build a balanced cmux layout tree with one pane leaf per command. A single\n * command yields a bare leaf; N commands tile into a grid via alternating\n * horizontal/vertical splits. Throws when `commands` is empty.\n *\n * @example\n * buildCmuxLayout(['a', 'b'])\n * // => {\n * // direction: 'horizontal',\n * // split: 0.5,\n * // children: [\n * // { pane: { surfaces: [{ type: 'terminal', command: 'a' }] } },\n * // { pane: { surfaces: [{ type: 'terminal', command: 'b' }] } },\n * // ],\n * // }\n */\nexport const buildCmuxLayout = (commands: string[]): CmuxLayoutNode => {\n if (commands.length === 0) {\n throw new Error('buildCmuxLayout: at least one command is required')\n }\n\n return buildNode(commands, 0)\n}\n", "/**\n * Signal-driven graceful shutdown, shared by every long-lived dev entry point.\n *\n * Three invariants:\n *\n * 1. Only a REPEAT OF THE SAME SIGNAL is an escape hatch. It is tempting to treat any second\n * signal as \"the user pressed Ctrl-C again, get out now\" \u2014 that was the old rule, and it\n * silently orphaned every child. A process manager in front of us relays its own signal after\n * the TTY's, so ONE Ctrl-C under `pnpm exec` arrives as SIGINT *then* SIGTERM:\n *\n * $ pnpm exec node -e \"process.on('SIGINT',\u2026); process.on('SIGTERM',\u2026)\"\n * ^C GOT SIGINT\n * GOT SIGTERM <- pnpm relaying, NOT a second keypress\n *\n * Under the old rule that relay force-quit the teardown a few hundred ms in and abandoned the\n * turbo/vite children (which sit in their own process groups). Keying the escape on the FIRST\n * signal's type fixes it without losing the hatch: a real second Ctrl-C re-sends SIGINT, and a\n * supervisor escalating SIGTERM -> SIGTERM still force-quits. `npm`/`yarn` relay the same way,\n * so this must live here, in the library, not in one consumer's `dev` script.\n * 2. Force-quitting must still not orphan. Even a genuine double Ctrl-C leaves children holding\n * ports (the next start then 502s), so the force path SIGKILLs the descendant process groups\n * before exiting. Bounded and synchronous \u2014 no grace, no escalation, no waiting for reaping.\n * 3. A signal-terminated process must not report exit code 0. The old handlers ran\n * `finally { process.exit(0) }`, which also swallowed a rejecting teardown without a word.\n * Here the rejection goes to stderr and the code stays `128 + signo` \u2014 even on a failed\n * teardown, because the process genuinely WAS signal-terminated, no conventional code means\n * \"signal-terminated but cleanup failed\", and inventing one would break the SIGTERM -> 143\n * contract supervisors rely on. The failure is surfaced by logging, not by the code.\n */\nimport { constants } from 'node:os'\nimport process from 'node:process'\n\nimport { killDescendantGroupsNow } from 'src/dev/managed-child'\n\n/**\n * The signals a resident dev process tears down on.\n *\n * SIGHUP is here for invariant 2: the terminal going away (window closed, ssh dropped) is delivered to\n * the foreground process group, so `dev` gets it \u2014 but its turbo/vite children DON'T, because they sit\n * in their own detached process groups. Without a handler, `dev` takes SIGHUP's default kill, never runs\n * `killDescendantGroupsNow`, and leaves the whole tree alive holding the dev ports; the next start then\n * 502s. Closing a terminal is the single most common way to walk away from a dev server, so this is the\n * likeliest orphan path, not an exotic one.\n */\nconst HANDLED_SIGNALS: readonly NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGHUP']\n\n/**\n * `os.constants.signals` typed for lookup by an arbitrary {@link NodeJS.Signals}. The platform\n * table omits signals that do not exist on the host, so the index is genuinely partial.\n */\nconst SIGNAL_NUMBERS: Partial<Record<NodeJS.Signals, number>> = constants.signals\n\n/** Fallback signal number when the host table lacks the signal: SIGINT, yielding the usual 130. */\nconst FALLBACK_SIGNO = 2\n\n/**\n * The POSIX exit code for a process terminated by `signal`: `128 + signo`.\n *\n * A shell reports SIGINT as 130 and SIGTERM as 143. Exiting 0 after a signal tells a supervisor\n * the process stopped voluntarily, which is a lie.\n *\n * @example\n * exitCodeForSignal('SIGINT') // 130\n * exitCodeForSignal('SIGTERM') // 143\n */\nexport const exitCodeForSignal = (signal: NodeJS.Signals): number => {\n return 128 + (SIGNAL_NUMBERS[signal] ?? FALLBACK_SIGNO)\n}\n\n/**\n * Seams for {@link registerSignalShutdown}. Each exists because the real implementation is\n * untestable in-process: `process.exit` kills the test runner, and delivering a real POSIX\n * signal to the runner is unsafe and flaky.\n */\nexport interface SignalShutdownDeps {\n /** The context-specific teardown. May reject; the rejection is logged, never swallowed. */\n onSignal: (signal: NodeJS.Signals) => Promise<void>\n /**\n * Terminate the process. Typed as returning `void`, not `never`, so a test can pass a plain\n * recording fake: a `never` fake would have to throw a sentinel, and that throw would reject the\n * in-flight teardown promise. Nothing meaningful runs after a call, so a returning fake is safe.\n */\n exit?: (code: number) => void\n /** Subscribe `handler` to `signal`. Defaults to `process.on`. */\n register?: (signal: NodeJS.Signals, handler: () => void) => void\n /**\n * Reap surviving descendant process groups on the force-quit path, where the normal teardown is\n * abandoned mid-flight. Synchronous by contract: the process exits on the next line, so anything\n * deferred to a later tick would never run. Defaults to {@link killDescendantGroupsNow}.\n */\n forceReap?: () => void\n}\n\nconst defaultExit = (code: number): void => {\n process.exit(code)\n}\n\nconst defaultRegister = (signal: NodeJS.Signals, handler: () => void): void => {\n process.on(signal, handler)\n}\n\n/**\n * Write to stderr, swallowing a failed write.\n *\n * `stderr.write` can throw (EPIPE, when the parent closed the pipe). Every caller here is on the\n * exit path, and failing to REPORT something must never break that path \u2014 the process still has to\n * die with the right code. A lost message is recoverable; a process that survives its own SIGINT\n * is not.\n */\nconst writeStderr = (message: string): void => {\n try {\n process.stderr.write(message)\n } catch {\n // stderr is gone; there is nowhere left to report to.\n }\n}\n\n/** Report a rejected teardown \u2014 message AND stack, so a wedge is never silent. */\nconst writeTeardownFailure = (signal: NodeJS.Signals, error: unknown): void => {\n const detail = error instanceof Error ? `${error.message}\\n${error.stack ?? '(no stack)'}` : String(error)\n\n writeStderr(`\\n\u2717 Teardown failed while handling ${signal}: ${detail}\\n`)\n}\n\n/**\n * Wire SIGINT/SIGTERM to `onSignal`, then exit `128 + signo`.\n *\n * The first signal runs the teardown. While it is in flight, a signal of a DIFFERENT type is a\n * relay from the process manager above us (`pnpm exec` turns one Ctrl-C into SIGINT + SIGTERM) and\n * is ignored, so the teardown survives to reap its children. A repeat of the FIRST signal is the\n * real escape hatch: it force-quits without touching `onSignal` again, SIGKILLing the descendant\n * groups on the way out so nothing is left holding a port.\n *\n * No force-deadline is armed. Any value safe enough not to preempt a legitimate teardown would\n * fire later than a user's second Ctrl-C, and a supervisor sends its own SIGKILL on its own grace.\n *\n * @example\n * registerSignalShutdown({\n * onSignal: async (signal) => {\n * process.stdout.write(`Received ${signal}, shutting down...`)\n * await runner.shutdown()\n * },\n * })\n */\nexport const registerSignalShutdown = ({\n onSignal,\n exit = defaultExit,\n register = defaultRegister,\n forceReap = killDescendantGroupsNow,\n}: SignalShutdownDeps): void => {\n /** The signal that opened the teardown \u2014 `null` until the first one lands. */\n let firstSignal: NodeJS.Signals | null = null\n\n const handle = (signal: NodeJS.Signals): void => {\n if (firstSignal !== null) {\n // A DIFFERENT signal while tearing down is the process manager in front of us relaying its\n // own (pnpm/npm turn one Ctrl-C into SIGINT + SIGTERM), never a second keypress. Ignoring it\n // is what lets the teardown finish and reap the children. Only a repeat of the signal that\n // STARTED the teardown is the user (or a supervisor) genuinely asking again.\n if (signal !== firstSignal) return\n\n writeStderr(`\\n\u26A0 Received ${signal} again \u2014 force-quitting.\\n`)\n // Abandoning the teardown must still not abandon the children: they hold the dev ports, and\n // the next start would 502 against their stale portless aliases.\n forceReap()\n exit(exitCodeForSignal(signal))\n\n return\n }\n\n firstSignal = signal\n\n void (async (): Promise<void> => {\n try {\n await onSignal(signal)\n } catch (error) {\n writeTeardownFailure(signal, error)\n } finally {\n // `finally`, so the exit survives anything the catch body might later throw: a process must\n // never outlive its own signal. Not the old `finally { process.exit(0) }` bug \u2014 that one\n // exited zero without logging; this logs first, then exits the honest code.\n exit(exitCodeForSignal(signal))\n }\n })()\n }\n\n for (const signal of HANDLED_SIGNALS) {\n register(signal, () => {\n return handle(signal)\n })\n }\n}\n", "/**\n * Shared supervision for the dev-server's long-lived, detached child processes\n * (`turbo watch build`, `turbo run dev`). Both are deep trees\n * (`sh \u2192 pnpm \u2192 node \u2192 turbo \u2192 \u2026`) spawned `detached` so they form their own process\n * group; teardown signals the whole GROUP (`process.kill(-pid, \u2026)`), not just the wrapper.\n *\n * Signalling turbo's group is NOT enough on its own: turbo puts every task it runs\n * (`vite`, `tsc -b`, \u2026) into a process group of its OWN, so `kill(-turboPid, \u2026)` reaches\n * turbo but never its tasks. Turbo normally forwards the signal and reaps them, but when it\n * is slower than the grace window a bare `SIGKILL` on turbo's group kills the only process\n * that knows the task groups \u2014 stranding orphaned vite servers that hold their ports until\n * the next run fails with `EADDRINUSE`.\n *\n * So teardown snapshots the descendant process groups BEFORE signalling (once turbo dies its\n * children reparent to init and can no longer be found by walking `ppid`), waits for the whole\n * set to exit, and only then force-kills whatever is left.\n */\nimport { execFileSync } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport process from 'node:process'\n\n/** Handle to a supervised child; `kill()` reaps its whole process tree and resolves when gone. */\nexport interface ManagedChild {\n kill: () => Promise<void>\n}\n\n/**\n * Grace before escalating SIGTERM \u2192 SIGKILL. Long enough for turbo to stop its tasks itself\n * (typically well under a second) and short enough that Ctrl-C still feels immediate. Escalating\n * is safe now that every task group is killed as a set, so this need not cover the worst case.\n * The happy path never waits the full window \u2014 teardown returns as soon as the groups are gone.\n */\nconst DEFAULT_GRACE_MS = 5000\n\n/** Poll interval while waiting for the doomed process groups to exit. */\nconst POLL_MS = 100\n\n/** How long to wait for the kernel to reap a group after SIGKILL before giving up on confirmation. */\nconst REAP_TIMEOUT_MS = 2000\n\n/** Absolute path to `ps` \u2014 present on both macOS and Linux, and immune to `PATH` substitution. */\nconst PS_BIN = '/bin/ps'\n\n/** One row of `ps -eo pid=,ppid=,pgid=`. */\nexport interface ProcRow {\n pid: number\n ppid: number\n pgid: number\n}\n\n/** Parse `ps -eo pid=,ppid=,pgid=` output, skipping any line that isn't three integers. */\nexport const parseProcRows = (raw: string): ProcRow[] => {\n return raw.split('\\n').flatMap((line) => {\n const [rawPid, rawPpid, rawPgid] = line.trim().split(/\\s+/)\n\n if (rawPid == null || rawPpid == null || rawPgid == null) return []\n\n const pid = Number(rawPid)\n const ppid = Number(rawPpid)\n const pgid = Number(rawPgid)\n\n if (!Number.isInteger(pid) || !Number.isInteger(ppid) || !Number.isInteger(pgid)) return []\n\n return [{ pid, ppid, pgid }]\n })\n}\n\n/**\n * Distinct process groups of every transitive descendant of `rootPid`, plus `rootPid`'s own\n * group. Group `0`/`1` and `excludePgid` (our own group \u2014 a detached child can never share it,\n * but a mis-parsed row must never make us signal ourselves) are filtered out.\n *\n * @example\n * const rows = [{ pid: 10, ppid: 1, pgid: 10 }, { pid: 20, ppid: 10, pgid: 20 }]\n * collectDoomedGroups(10, rows, 5) // => [10, 20]\n */\nexport const collectDoomedGroups = (rootPid: number, rows: ProcRow[], excludePgid?: number): number[] => {\n const childrenOf = new Map<number, ProcRow[]>()\n\n for (const row of rows) {\n const siblings = childrenOf.get(row.ppid)\n\n if (siblings) siblings.push(row)\n else childrenOf.set(row.ppid, [row])\n }\n\n const groups = new Set<number>([rootPid])\n const seen = new Set<number>([rootPid])\n const queue = [rootPid]\n\n for (let i = 0; i < queue.length; i += 1) {\n for (const child of childrenOf.get(queue[i] ?? -1) ?? []) {\n if (seen.has(child.pid)) continue\n seen.add(child.pid)\n groups.add(child.pgid)\n queue.push(child.pid)\n }\n }\n\n return [...groups].filter((pgid) => {\n return pgid > 1 && pgid !== excludePgid\n })\n}\n\n/**\n * Snapshot the live process table; an empty list on failure degrades to group-only teardown.\n * `ps` is invoked by absolute path (not via `PATH`, unlike the `pnpm` the engines shell out to)\n * so a writable `PATH` entry can never substitute the binary we hand a kill list to.\n */\nconst snapshotProcRows = (): ProcRow[] => {\n try {\n return parseProcRows(execFileSync(PS_BIN, ['-eo', 'pid=,ppid=,pgid='], { encoding: 'utf8', maxBuffer: 8 << 20 }))\n } catch {\n return []\n }\n}\n\n/** Does `pid` currently exist and name a direct child of `parentPid`? Guards against pid reuse. */\nexport const isChildOf = (pid: number, parentPid: number, rows: ProcRow[]): boolean => {\n return rows.some((row) => {\n return row.pid === pid && row.ppid === parentPid\n })\n}\n\n/**\n * The groups to signal for `rootPid`'s tree, excluding our own group so a mis-parsed `ps` row can\n * never make the dev-server signal itself.\n */\nconst doomedGroupsOf = (rootPid: number, rows: ProcRow[]): number[] => {\n const ownPgid = rows.find((row) => {\n return row.pid === process.pid\n })?.pgid\n\n return collectDoomedGroups(rootPid, rows, ownPgid)\n}\n\n/** Is the process group led by `pgid` still alive? (`kill(-pgid, 0)` throws ESRCH when gone.) */\nconst groupAlive = (pgid: number): boolean => {\n try {\n process.kill(-pgid, 0)\n\n return true\n } catch {\n return false\n }\n}\n\n/** Send `signal` to every named process group, ignoring groups that already exited. */\nconst signalGroups = (pgids: number[], signal: NodeJS.Signals): void => {\n for (const pgid of pgids) {\n try {\n process.kill(-pgid, signal)\n } catch {\n // Already gone (ESRCH) \u2014 nothing to signal.\n }\n }\n}\n\n/**\n * SIGKILL every process group descended from this process, right now.\n *\n * The force-quit path (a second Ctrl-C) abandons the graceful teardown, but it must not abandon the\n * CHILDREN: they hold the dev ports, and the next start would 502 against their stale portless\n * aliases. So this is the blunt counterpart to {@link superviseChild}'s teardown \u2014 no SIGTERM grace,\n * no escalation, no waiting for the kernel to reap. Straight to SIGKILL.\n *\n * Synchronous by contract: the caller exits the process on the very next line, so anything deferred\n * to a later tick would never run. Cost is one `ps` (tens of ms) plus a `kill` per group; that is the\n * whole latency budget a user who just hit Ctrl-C twice is willing to spend.\n */\nexport const killDescendantGroupsNow = (): void => {\n signalGroups(doomedGroupsOf(process.pid, snapshotProcRows()), 'SIGKILL')\n}\n\n/** Resolve after `ms`, used to pace the teardown polling loop. */\nconst sleep = async (ms: number): Promise<void> => {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\n/** Poll `pgids` until every group has exited or `timeoutMs` elapses; returns the survivors. */\nconst waitForExit = async (pgids: number[], timeoutMs: number): Promise<number[]> => {\n const deadline = Date.now() + timeoutMs\n let alive = pgids\n\n while (alive.length > 0 && Date.now() < deadline) {\n alive = alive.filter(groupAlive)\n if (alive.length === 0) break\n await sleep(POLL_MS)\n }\n\n return alive\n}\n\n/**\n * Called when a supervised child dies on its OWN \u2014 a natural `exit` or a spawn `error` that was\n * NOT initiated by {@link ManagedChild.kill}. `detail` is a short human description of the cause.\n * The runner uses it to surface a silently-dead `turbo watch`/`turbo run dev` engine (which stops\n * rebuilds/HMR with no other signal) instead of leaving the session looking healthy.\n */\nexport type UnexpectedExitHandler = (detail: string) => void\n\n/**\n * Wrap a detached child: `unref()` it (so it never keeps the loop alive) and return a handle\n * whose `kill()` reaps the child AND every process group turbo spawned beneath it.\n *\n * Snapshot first, signal second: after turbo exits its tasks reparent to init, so the `ppid`\n * walk that finds them must run while turbo is still alive. Then SIGTERM every doomed group,\n * poll until all are gone, and SIGKILL the survivors. Resolves once nothing is left.\n *\n * `onUnexpectedExit` (optional) fires when the child dies WITHOUT `kill()` having been called \u2014\n * the \"engine died silently\" case. A `killing` latch, set at the top of `kill()` before any signal\n * goes out, suppresses the callback for the exit our own teardown causes, so it reports only genuine\n * crashes. The `error` listener is load-bearing beyond reporting: a {@link ChildProcess} that emits\n * `error` (e.g. `pnpm` ENOENT) with no listener throws as an uncaught exception and would take the\n * whole dev session down.\n */\nexport function superviseChild(\n child: ChildProcess,\n graceMs: number = DEFAULT_GRACE_MS,\n onUnexpectedExit?: UnexpectedExitHandler,\n): ManagedChild {\n // Don't keep the parent event loop alive on the child; the runner owns lifecycle via kill().\n child.unref()\n\n // Latched by kill() before it signals, so the child's own teardown exit is not misreported as a crash.\n let killing = false\n\n const reportUnexpected = (detail: string): void => {\n if (killing) return\n onUnexpectedExit?.(detail)\n }\n\n child.on('exit', (code, signal) => {\n reportUnexpected(`exited unexpectedly (code=${code ?? 'null'}, signal=${signal ?? 'null'})`)\n })\n child.on('error', (error: Error) => {\n reportUnexpected(`failed to spawn: ${error.message}`)\n })\n\n return {\n kill: async (): Promise<void> => {\n // Latch FIRST: the child WILL emit `exit` during the reap below, and that exit is ours, not a crash.\n killing = true\n\n const pid = child.pid\n\n if (pid == null) return\n\n // Two guards against the same hazard: once the child is reaped the OS may recycle `pid`\n // onto an unrelated process, and `kill(-pid, \u2026)` would then destroy a stranger's group.\n // A turbo that exits on its own reaps its own tasks, so bailing here strands nothing.\n if (child.exitCode !== null || child.signalCode !== null) return\n\n const rows = snapshotProcRows()\n\n // The cheap check above misses a child reaped just now, so confirm `pid` is still ours: a\n // detached child keeps us as its parent, so a foreign `ppid` means the pid was recycled.\n // Skipped when `ps` failed (empty `rows`) \u2014 then only the guard above stands.\n if (rows.length > 0 && !isChildOf(pid, process.pid, rows)) return\n\n const doomed = doomedGroupsOf(pid, rows)\n\n // Negative pid \u2192 the whole group (detached made the child a group leader). Turbo forwards\n // this to its tasks; the extra task groups are signalled directly in case it doesn't.\n signalGroups(doomed, 'SIGTERM')\n\n const stragglers = await waitForExit(doomed, graceMs)\n\n if (stragglers.length === 0) return\n\n // Re-snapshot before forcing: turbo is still alive (that's why we're here), so its tree is\n // still walkable, and `turbo watch build` may have started a task AFTER the first snapshot.\n // Union with the known stragglers, whose groups may already have left turbo's subtree.\n const late = doomedGroupsOf(pid, snapshotProcRows())\n const forced = [...new Set([...stragglers, ...late])].filter(groupAlive)\n\n // Killing turbo's group alone would orphan exactly these, so they are killed as a set.\n signalGroups(forced, 'SIGKILL')\n\n // A SIGKILLed leader lingers as an unreaped zombie for a few ms, and `kill(pgid, 0)` still\n // succeeds against it. Wait for the kernel to finish so callers that exit the process\n // immediately after `kill()` resolves aren't racing the teardown they just awaited.\n await waitForExit(forced, REAP_TIMEOUT_MS)\n },\n }\n}\n", "/**\n * Process-level crash barrier for the resident single-process dev-server.\n *\n * The backends run IN-PROCESS (`ServerlessLocalRun` is an in-process fastify that `import()`s the\n * compiled handler), so they share the runner's event loop. Fastify catches errors thrown INSIDE a\n * route handler and turns them into a 500 \u2014 but a stray async path a handler forgot to await\n * (`unhandledRejection`) or a throw from a timer/emitter (`uncaughtException`) escapes to the process\n * and, by Node's defaults, terminates it. That kills the WHOLE dev session \u2014 every backend, the watch\n * engine, the UI child \u2014 over one bad path in one handler.\n *\n * This barrier keeps the session alive and reports the fault loudly instead, the same resilience\n * `nodemon`/`vite dev`/`next dev` give. The tradeoff is deliberate and dev-only: it is installed by the\n * CLI entry point, never by library code, so it can never mask a fault in a production consumer.\n *\n * Node's caveat stands: after an `uncaughtException` the process may be in an undefined state. For a\n * local dev tool a possibly-degraded session the developer can see and restart beats a hard exit that\n * looks like the tool itself crashed. The fault is surfaced with a full stack so it is never silent.\n */\nimport process from 'node:process'\n\n/** The two process-level fault channels that terminate Node by default. */\ntype FaultEvent = 'uncaughtException' | 'unhandledRejection'\n\n/**\n * Seams for {@link registerCrashBarrier}. Each exists because the real implementation is untestable\n * in-process: attaching real `process.on('uncaughtException')` handlers in the test runner would swallow\n * the runner's own faults, and there is nothing to assert against a handler that only logs to stderr.\n */\nexport interface CrashBarrierDeps {\n /** Report the fault. Defaults to a guarded stderr write. Must not throw. */\n onFault?: (event: FaultEvent, error: unknown) => void\n /** Subscribe `handler` to `event`. Defaults to `process.on`. */\n register?: (event: FaultEvent, handler: (error: unknown) => void) => void\n}\n\n/**\n * Write to stderr, swallowing a failed write. Mirrors `signal-shutdown`'s `writeStderr`: reporting must\n * never itself throw on the fault path (EPIPE when the parent closed the pipe), and a lost message is\n * recoverable where a barrier that throws is not.\n */\nconst writeStderr = (message: string): void => {\n try {\n process.stderr.write(message)\n } catch {\n // stderr is gone; there is nowhere left to report to.\n }\n}\n\n/** Default fault reporter: message AND stack to stderr, plus a note that the session was kept alive. */\nconst defaultOnFault = (event: FaultEvent, error: unknown): void => {\n const detail = error instanceof Error ? `${error.message}\\n${error.stack ?? '(no stack)'}` : String(error)\n\n writeStderr(\n `\\n\u26A0\uFE0F ${event}: ${detail}\\n` +\n ` dev-server kept alive (likely a bug in a handler's async path); restart if it misbehaves.\\n`,\n )\n}\n\nconst defaultRegister = (event: FaultEvent, handler: (error: unknown) => void): void => {\n process.on(event, handler)\n}\n\n/**\n * Install the crash barrier: wire `uncaughtException` and `unhandledRejection` to `onFault` and DO NOT\n * exit, so a single escaped async path in an in-process backend handler no longer tears the whole dev\n * session down. The handler never rethrows \u2014 rethrowing would re-arm the very termination this prevents.\n *\n * @example\n * registerCrashBarrier() // resident dev process survives a handler's stray rejection, logs it loudly\n */\nexport const registerCrashBarrier = ({\n onFault = defaultOnFault,\n register = defaultRegister,\n}: CrashBarrierDeps = {}): void => {\n const handle = (event: FaultEvent) => {\n return (error: unknown): void => {\n onFault(event, error)\n }\n }\n\n register('uncaughtException', handle('uncaughtException'))\n register('unhandledRejection', handle('unhandledRejection'))\n}\n", "/**\n * Unified Development Server Runner\n *\n * Discovers and runs API apps under each `apps/<app>/api` folder that contains `serverless.yml`.\n *\n * Ports: `{APP}_PORT`, then `process.env.PORT`, then `dev.<app>.port` from infra-kit.json,\n * else 3010. URL prefix: `dev.<app>.prefixUrl`, else `/api/v1`.\n * Env vars should be provided via secrets manager (e.g. `doppler run -- pnpm dev-server`) or shell.\n *\n * This module is side-effect free on import: call `run()` (or construct `DevServerRunner`\n * directly) to start. Signal handling and process exit are the entry point's responsibility.\n *\n * Runner messages append to the session log `<cacheRoot>/<INFRA_KIT_SESSION>/logs.txt`. Lambda / Powertools logs from\n * handlers go to stdout.\n */\nimport chokidar from 'chokidar'\nimport type { FSWatcher } from 'chokidar'\nimport { exec, execFileSync } from 'node:child_process'\nimport * as fs from 'node:fs'\nimport net from 'node:net'\nimport os from 'node:os'\nimport * as path from 'node:path'\nimport process from 'node:process'\nimport util from 'node:util'\n\nimport { INFRA_KIT_SESSION_VAR, getCacheRoot } from 'src/lib/constants'\nimport type { DevConfig, DevPreset, DevPresets } from 'src/lib/infra-kit-config'\nimport { DEFAULT_DEV_PROXY_PORT, getInfraKitConfig } from 'src/lib/infra-kit-config'\nimport { DEFAULT_RELEASE_SLUG, slugifyHostLabel, slugifyRelease } from 'src/lib/release-slug'\n\nimport { buildClosureMap, packageDebounceKey, selectPackageRestartTargets } from './dep-closure.js'\nimport type { ClosureMap, DryRunner } from './dep-closure.js'\nimport type { DevUi } from './dev-ui.js'\nimport {\n classifyDistChange,\n discoverApiApps as discoverApiAppsBare,\n discoverUiApps as discoverUiAppsBare,\n findMonorepoRoot,\n getAppDistDirs,\n getPackageDistDirs,\n normalizeAppInclude as normalizeAppIncludePure,\n} from './discovery.js'\nimport type { DiscoveredUiApp } from './discovery.js'\nimport {\n findPortConflicts,\n resolvePreferredPort as resolvePreferredPortPure,\n resolvePrefixUrl as resolvePrefixUrlPure,\n} from './ports.js'\nimport { deriveTargetLabel, resolvePreset } from './presets.js'\nimport { createPortlessDriver, readCaPath } from './proxy/portless-driver.js'\nimport type { PortlessDriver } from './proxy/portless-driver.js'\nimport { DevRenderer, resolveEndpointUrl } from './render.js'\nimport type { EndpointRow, UiRef } from './render.js'\nimport { ServerlessLocalRun } from './serverless-local-run.js'\nimport { defaultTurboWatchFactory } from './turbo-watch.js'\nimport type { TurboWatchFactory, TurboWatchHandle } from './turbo-watch.js'\nimport { defaultUiDevFactory } from './ui-dev.js'\nimport type { UiDevFactory, UiDevHandle } from './ui-dev.js'\n\n/** Runner-only log file, resolved under the session cache dir (`<cacheRoot>/<session>/logs.txt`) at startup. */\nlet LOG_FILE_PATH = resolveLogFilePath()\n\n/**\n * Resolve `<cacheRoot>/<INFRA_KIT_SESSION>/logs.txt`. Reuses infra-kit's own per-terminal session id\n * (the same dir that holds the session's `env-load.sh`), falling back to a literal `no-session` folder\n * when the shell hasn't exported one \u2014 so dev logging never depends on `infra-kit init` having run.\n * Built from {@link getCacheRoot} (never `getSessionCacheDir`, which throws when the session is unset).\n */\nexport function resolveLogFilePath(): string {\n return path.join(getCacheRoot(), process.env[INFRA_KIT_SESSION_VAR] ?? 'no-session', 'logs.txt')\n}\n\n/** Resolve the session log path, ensure the dir exists, and clear the file. */\nfunction initLogFile(): void {\n LOG_FILE_PATH = resolveLogFilePath()\n fs.mkdirSync(path.dirname(LOG_FILE_PATH), { recursive: true })\n fs.writeFileSync(LOG_FILE_PATH, `=== Dev Server Started: ${new Date().toISOString()} ===\\n\\n`)\n}\n\n/** Replace a leading home dir with `~` for a compact, human-readable path label (the on-screen log link). */\nexport function homeShorten(p: string): string {\n const home = os.homedir()\n\n return p === home || p.startsWith(`${home}${path.sep}`) ? `~${p.slice(home.length)}` : p\n}\n\n/**\n * Comma-joined package names \u2014 not folder names, so a boot line names the exact `turbo --filter`\n * targets the build runs on and matches turbo's own output.\n */\nfunction packageList(apps: { packageName: string }[]): string {\n return apps\n .map((a) => {\n return a.packageName\n })\n .join(', ')\n}\n\nconst execFn = util.promisify(exec)\n\ntype LogFn = (msg: string, level?: 'info' | 'warn' | 'error' | 'debug') => void\n\n/**\n * Build seam: shells out to turbo by default ({@link launchScript}); injectable so\n * tests can run the orchestrator without a real build. `logFn` is optional so both\n * the verbose initial build and the terse restart builds keep their current output.\n */\nexport type BuildRunner = (cmd: string, logFn?: LogFn) => Promise<void>\n\nconst launchScript = async (script: string, logFn?: LogFn): Promise<void> => {\n try {\n const { stderr } = await execFn(script)\n\n if (stderr && logFn) logFn(` (build) ${stderr.trim()}`, 'debug')\n if (stderr && !logFn) console.error('stderr:', stderr)\n } catch (error) {\n const err = error as { stdout?: string; stderr?: string; message?: string }\n\n if (logFn && (err.stdout || err.stderr)) {\n if (err.stdout) logFn(` stdout: ${err.stdout.trim()}`, 'error')\n if (err.stderr) logFn(` stderr: ${err.stderr.trim()}`, 'error')\n }\n\n throw error\n }\n}\n\n/** Append raw text to the runner-only log file (the single tee-to-file seam the renderer wraps). */\nfunction appendLogFile(text: string): void {\n fs.appendFileSync(LOG_FILE_PATH, text)\n}\n\n/**\n * An OS-assigned free TCP port on 127.0.0.1 \u2014 used to pre-assign each UI's Vite port so the runner can\n * print its URL (and alias it, when a proxy is up) before Vite binds. Probes then releases (small TOCTOU\n * window; `strictPort` on the Vite side turns a rare lost race into a loud failure rather than a silent drift).\n */\nconst getFreePort = (): Promise<number> => {\n return new Promise((resolve, reject) => {\n const srv = net.createServer()\n\n srv.unref()\n srv.on('error', reject)\n srv.listen(0, '127.0.0.1', () => {\n const address = srv.address()\n const port = typeof address === 'object' && address !== null ? address.port : 0\n\n srv.close(() => {\n return resolve(port)\n })\n })\n })\n}\n\ninterface IApiAppConfig {\n /** App folder name (e.g. backoffice, client) */\n name: string\n /** Package name from package.json (e.g. sls-trvl-client) */\n packageName: string\n path: string\n /**\n * EXPLICITLY-configured preferred port (`{APP}_PORT`/`PORT`/`dev.<app>.port`), or\n * `undefined` when unconfigured. Under dynamic allocation this is only a bind hint \u2014 the\n * ACTUAL port is the ephemeral one bound at start time (see {@link IAppServer.boundPort}).\n */\n preferredPort: number | undefined\n prefixUrl: string\n /**\n * Whether this backend participates in dependency-closure watching (plan Phase 1): in\n * `--watch`, a rebuild of a package in this app's closure restarts it. Default `true`;\n * a preset `watchDeps: false` opts out (sticky). Resolved from the preset in {@link run}.\n */\n watchDeps: boolean\n}\n\n/**\n * Runner options, parsed by the CLI entry point (`--watch`, `--app`) and threaded\n * through `run()`. The entry owns flag parsing; the runner never reads `process.argv`\n * itself. App selection is `--app` only; ports come from env/config (see `resolvePort`).\n */\nexport interface DevServerOptions {\n /**\n * Watch mode: start a long-lived `turbo watch build` engine (incremental rebuilds\n * + dependency fan-out) and restart the affected server(s) when compiled `dist/`\n * changes. Without it, `dev` builds and serves once and exits on signal.\n */\n watch?: boolean\n /** Only run these app folder names (null/empty = all discovered). Filters BOTH api and ui apps. */\n include?: string[] | null\n /**\n * Named dev preset (`infra-kit dev <preset>`) from `devServersPresets` in the project's infra-kit config.\n * It selects the launch targets (`apps/<app>/{api,ui}`); resolved by {@link file://./presets.ts}.\n * Unset \u2192 run everything (`*`). `include` (`--app`/`--self`) further narrows the resolved set.\n */\n preset?: string\n /**\n * In-memory preset definition, produced by the interactive wizard (`infra-kit dev` with no args in a\n * TTY). When set it is used verbatim as the run plan \u2014 it WINS over `preset` (the named lookup) and\n * the default `*`. This is how the wizard expresses part-level selection (`<app>/ui` without\n * `<app>/api`), which `include` (app-name-only) cannot. Unset on every non-wizard invocation.\n */\n presetDef?: DevPreset\n /**\n * Run each discovered API app in its own cmux pane (one workspace, N panes), supervised by a\n * resident process that closes the workspace on signal. Falls back to single-process dev when\n * cmux is absent. Handled by `runCmuxDevServer`, not the in-process `DevServerRunner`.\n */\n cmux?: boolean\n /**\n * Infer the single app to run from the current working directory (equivalent to\n * `--app=<that app>`), so every app can share the identical script\n * `pnpm exec infra-kit dev --self` instead of hardcoding its own folder name.\n * Resolved by the entry point (`resolveSelfAppName`) into `include` before this\n * runner ever sees it; the runner itself does not read `self`.\n */\n self?: boolean\n /**\n * Print the full boot narration (build/discovery/watch steps) to the terminal. Default false:\n * the terminal shows only the server panel, warnings, errors, and restart lines. The FULL detail\n * is written to the session log (`<cacheRoot>/<INFRA_KIT_SESSION>/logs.txt`) regardless of this flag.\n */\n verbose?: boolean\n /**\n * Print each app's registered `METHOD /path` route table at startup. Default false \u2014 the route\n * dump is opt-in so the calm default screen stays glanceable; the routes are always in the log.\n */\n routes?: boolean\n /**\n * Interactive TTY (both stdin+stdout). Set by the entry point; gates the Ink boot UI in {@link run}.\n * When unset, {@link run} falls back to `process.stdout.isTTY`. The runner itself never reads it.\n */\n tty?: boolean\n /**\n * Structured `--json` / MCP mode. When true, {@link run} forces the plain {@link DevRenderer} \u2014 Ink\n * must never seize a machine-readable stream. The runner itself never reads it.\n */\n json?: boolean\n /**\n * Background liveness-probe interval in ms (default {@link DevServerRunner.LIVENESS_INTERVAL_MS}, 5000).\n * A test seam only \u2014 lets a test drive the monitor loop fast; production never sets it.\n */\n livenessIntervalMs?: number\n}\n\n/** Health-probe seam: resolve an endpoint's liveness. Injectable so `ready()` stays deterministic in tests. */\nexport type HealthProbe = (url: string) => Promise<boolean>\n\n/** Default probe: a bounded GET against the `/__health` URL; any non-2xx / network error \u2192 down. */\nconst defaultHealthProbe: HealthProbe = async (url: string): Promise<boolean> => {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(1500) })\n\n return res.ok\n } catch {\n return false\n }\n}\n\ninterface IAppServer {\n app: IApiAppConfig\n server: ServerlessLocalRun\n /** The ACTUAL port bound at start (ephemeral or preferred), reported by `server.start()`. */\n boundPort: number\n /** Layer-B alias host (`<release>.<package>.localhost`) \u2014 the app's only address. */\n alias: string\n}\n\n/**\n * One `.infra-kit/dev-context/<app>.json` fragment. `package` (feeds the vite helper's\n * `readLocalSet`) is the app's package name; `port` is the ACTUAL bound port (the writer\n * IS the binder). `release` lets the helper prefer the runner-recorded slug over its own\n * git derivation; `pid`/`writtenAt` are staleness metadata.\n *\n * `alias` is the registered portless hostname, and `origin` is **the authoritative local target** \u2014 the\n * exact origin the helper must proxy to, published by the runner rather than re-derived by the helper from\n * a template in a separately-versioned repo. The runner is the only party that knows what it actually\n * registered, so it says so; the helper obeys.\n *\n * `proxyPort` is deliberately NOT written any more. The CLI self-updates while `infra-kit/vite` stays\n * pinned per consumer, so a new CLI routinely meets an OLD helper \u2014 and that helper's `withProxyPort`\n * grafts any port other than 80 onto its target. Writing `proxyPort: 443` would therefore have produced\n * `http://<alias>:443`: **plain HTTP into a TLS listener**, silently. Omitting the field leaves the old\n * helper's target ungrafted instead. (That is not by itself a loud failure \u2014 `:80` is bound by portless's\n * redirect server, so it 302s rather than refusing \u2014 which is why the CLI-side version floor, not this\n * omission, is the load-bearing skew guard.)\n */\ninterface DevContextFragment {\n package: string\n port: number\n pid: number\n writtenAt: number\n release: string\n alias: string\n origin: string\n}\n\n/**\n * Lowest published `infra-kit` whose `infra-kit/vite` helper understands the fragment's `origin` field.\n *\n * This is the load-bearing guard against version skew, and skew here is GUARANTEED rather than\n * hypothetical: the CLI is installed globally and **self-updates silently**, while `infra-kit/vite` is\n * PINNED in each consumer's `node_modules`. So a new CLI routinely meets an old helper. An old helper\n * ignores `origin` and rebuilds the target from the consumer's `templates.local` \u2014 which still says\n * `http://` \u2014 and then proxies plain HTTP at a TLS listener. That failure is silent (portless answers :80\n * with a 302 rather than refusing), so nothing downstream would catch it. Refuse at start instead.\n */\nexport const HELPER_VERSION_FLOOR = '0.1.132'\n\n/** `true` when `version` sorts strictly below `floor` (numeric, dot-separated; missing parts are 0). */\nexport const isBelowVersion = (version: string, floor: string): boolean => {\n const parse = (v: string): number[] => {\n return v.split('.').map((part) => {\n return Number.parseInt(part, 10) || 0\n })\n }\n const a = parse(version)\n const b = parse(floor)\n\n for (let i = 0; i < Math.max(a.length, b.length); i += 1) {\n const left = a[i] ?? 0\n const right = b[i] ?? 0\n\n if (left !== right) return left < right\n }\n\n return false\n}\n\n/** Does any package.json in the repo declare `infra-kit` as a dependency? */\nconst declaresInfraKit = (repoRoot: string): boolean => {\n const manifests = [path.join(repoRoot, 'package.json')]\n\n try {\n for (const app of fs.readdirSync(path.join(repoRoot, 'apps'), { withFileTypes: true })) {\n if (!app.isDirectory()) continue\n for (const part of ['api', 'ui']) manifests.push(path.join(repoRoot, 'apps', app.name, part, 'package.json'))\n }\n } catch {\n // No `apps/` dir \u2014 the root manifest alone decides.\n }\n\n return manifests.some((file) => {\n try {\n const pkg = JSON.parse(fs.readFileSync(file, 'utf-8')) as Record<string, Record<string, string> | undefined>\n\n return ['dependencies', 'devDependencies'].some((field) => {\n return pkg[field]?.['infra-kit'] != null\n })\n } catch {\n return false\n }\n })\n}\n\n/**\n * Refuse to start against a consumer-pinned `infra-kit/vite` older than {@link HELPER_VERSION_FLOOR}.\n *\n * Three branches, and the reasoning for each matters:\n * - **Workspace-linked \u2192 SKIP.** In this repo `node_modules/infra-kit` symlinks to `apps/infra-kit/cli`,\n * whose version is the unreleased working tree. Enforcing a floor there would brick `infra-kit dev` on\n * the very repo that develops it.\n * - **Declared but unresolvable \u2192 THROW (fail closed).** The consumer says it uses the helper and we\n * cannot prove which one; guessing is how the silent case ships.\n * - **Not a dependency at all \u2192 SKIP.** No `infra-kit` dependency means no `infra-kit/vite` in play, so\n * there is no helper to be skewed against. (This is also what keeps bare test fixtures runnable.)\n */\nexport const assertHelperVersionFloor = (repoRoot: string): void => {\n const helperDir = path.join(repoRoot, 'node_modules', 'infra-kit')\n\n if (!fs.existsSync(helperDir)) {\n if (!declaresInfraKit(repoRoot)) return\n\n throw new Error(\n `infra-kit dev: this repo depends on infra-kit but node_modules/infra-kit is missing, so the ` +\n `\\`infra-kit/vite\\` helper version cannot be verified. Run \\`pnpm install\\`.`,\n )\n }\n\n // Resolve BOTH sides before comparing: on macOS a temp path realpaths from `/var` to `/private/var`, so\n // an unresolved root would never contain a resolved child and a workspace link would be misread as a\n // published install.\n try {\n const real = fs.realpathSync(helperDir)\n const root = fs.realpathSync(repoRoot)\n\n if (real.startsWith(root + path.sep) && !real.includes(`${path.sep}node_modules${path.sep}`)) return\n } catch {\n // Fall through to the version read.\n }\n\n let version: string\n\n try {\n const pkg = JSON.parse(fs.readFileSync(path.join(helperDir, 'package.json'), 'utf-8')) as { version?: string }\n\n if (typeof pkg.version !== 'string') throw new Error('no version field')\n version = pkg.version\n } catch {\n throw new Error(\n 'infra-kit dev: could not read the version of the pinned `infra-kit/vite` helper ' +\n '(node_modules/infra-kit/package.json). Refusing to start rather than risk proxying plain HTTP at a ' +\n 'TLS listener. Run `pnpm install`.',\n )\n }\n\n if (isBelowVersion(version, HELPER_VERSION_FLOOR)) {\n throw new Error(\n `infra-kit dev: this repo pins infra-kit ${version}, but dev URLs are now HTTPS and the ` +\n `\\`infra-kit/vite\\` helper only understands them from ${HELPER_VERSION_FLOOR}. An older helper would ` +\n `proxy plain HTTP at a TLS listener \u2014 silently. Bump the dependency:\\n` +\n ` pnpm add -D infra-kit@^${HELPER_VERSION_FLOOR}`,\n )\n }\n}\n\n/**\n * Slugified `<release>` for the app's git branch (resolved from the app's own dir), falling back to\n * {@link DEFAULT_RELEASE_SLUG} outside a git repo / on an empty slug. Never throws.\n *\n * A release ALWAYS resolves because it is the first DNS label of every alias, and every app is reached\n * by hostname. The fallback cannot collide the way a branch can: worktrees are what make two checkouts\n * coexist, and a worktree is by definition inside a git repo.\n */\nconst readAppRelease = (cwd: string): string => {\n try {\n // eslint-disable-next-line sonarjs/no-os-command-from-path\n const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, encoding: 'utf-8' }).trim()\n const slug = slugifyRelease(branch)\n\n return slug === '' ? DEFAULT_RELEASE_SLUG : slug\n } catch {\n return DEFAULT_RELEASE_SLUG\n }\n}\n\n/**\n * The one-line `reason` for a `\u25CF failed` row. Endpoint rows are a single terminal line, so a stack\n * trace cannot go there \u2014 it is already in the log tail and the session log. Take the message only,\n * and its first line at that: validation errors like to append their own multi-line dumps.\n *\n * @example\n * errorReason(new Error(\"config is missing field: 'connectionURL'\\n at \u2026\")) // \"config is missing field: 'connectionURL'\"\n */\nconst errorReason = (error: unknown): string => {\n const message = error instanceof Error ? error.message : String(error)\n const firstLine = message.split('\\n')[0]?.trim() ?? ''\n\n return firstLine === '' ? 'unknown error (see the log)' : firstLine\n}\n\nexport class DevServerRunner {\n private readonly monorepoRoot: string\n /**\n * `<cwd>/.infra-kit/dev-context` \u2014 the fragment directory this runner writes its own\n * per-app `<app>.json` into (mirrors {@link LOG_FILE_PATH}'s cwd-relative resolution). The\n * `infra-kit/vite` helper searches up-tree for this dir and merges the fragments.\n */\n private readonly devContextDir: string\n private readonly appServers: IAppServer[] = []\n private watchDebounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n /** Active chokidar watcher in `--watch` mode; closed on {@link shutdown}. */\n private watcher: FSWatcher | null = null\n private static readonly WATCH_DEBOUNCE_MS = 400\n /** Serialized restarts so rapid saves never bind :port while the previous server is still shutting down. */\n private restartWorkChain: Promise<void> = Promise.resolve()\n private static readonly PORT_RELEASE_DELAY_MS = 200\n /** Self-rescheduling backend liveness probe; cleared on {@link shutdown}. Null when unarmed (UI-only). */\n private livenessTimer: ReturnType<typeof setTimeout> | null = null\n /** Consecutive `/__health` failures per app NAME (survives an ephemeral-port rebind on restart). */\n private readonly livenessFailures = new Map<string, number>()\n /** Liveness re-probe cadence. A wedged-but-not-crashed backend is caught within THRESHOLD ticks. */\n private static readonly LIVENESS_INTERVAL_MS = 5000\n /** Consecutive failures before a backend is declared unhealthy \u2014 the anti-flap / restart-window filter. */\n private static readonly LIVENESS_FAILURE_THRESHOLD = 2\n private readonly options: DevServerOptions\n /** Build runner seam \u2014 real turbo shell-out by default, injectable for tests. */\n private readonly runBuild: BuildRunner\n /** `turbo watch` spawn seam \u2014 real detached child by default, injectable for tests. */\n private readonly turboWatchFactory: TurboWatchFactory\n /** Live `turbo watch` engine in `--watch` mode; reaped on {@link shutdown}. */\n private turboWatch: TurboWatchHandle | null = null\n /** `turbo run dev` (frontends) spawn seam \u2014 real detached child by default, injectable for tests. */\n private readonly uiDevFactory: UiDevFactory\n /** Live `turbo run dev` frontend engine in `--ui` mode; reaped on {@link shutdown}. */\n private uiDev: UiDevHandle | null = null\n /**\n * Append stream for the UI child's RAW output tee. Deliberately NOT {@link appendLogFile}: that is an\n * `appendFileSync` (open/write/close syscall per call), fine for the renderer's occasional lines but a\n * blocking write once per chunk during an HMR burst \u2014 on the same event loop that repaints the pinned\n * footer. Opened in {@link startUiDev}, closed in {@link shutdown}.\n */\n private uiLogStream: fs.WriteStream | null = null\n /** `turbo --dry` closure-source seam \u2014 real turbo (via {@link buildClosureMap}'s default) unless injected for tests. */\n private readonly dryRunner: DryRunner | undefined\n /** Apps that threw during {@link startAllApps} \u2014 rendered as `\u25CF failed` rows by {@link printReady}. */\n private readonly failedApps: { app: IApiAppConfig; reason: string }[] = []\n /**\n * The terminal UI \u2014 owns every stdout line + the log-file tee. Either the plain {@link DevRenderer}\n * or the Ink boot UI, selected by {@link run} and injected here; the runner drives it through {@link DevUi}.\n */\n private readonly renderer: DevUi\n /** Health-probe seam (real bounded `/__health` GET by default; injectable for tests). */\n private readonly healthProbe: HealthProbe\n /** Portless driver (Layer B) \u2014 best-effort alias registration; injectable for tests. */\n private readonly proxy: PortlessDriver\n /**\n * The port the portless daemon must be serving TLS on. Fixed at {@link DEFAULT_DEV_PROXY_PORT} (443):\n * it is not configurable, because a port-free `https://` URL can only be served from the implicit HTTPS\n * port. A configurable port would put the port straight back into the URL.\n */\n private readonly proxyPort: number = DEFAULT_DEV_PROXY_PORT\n /** Every `<release>.<package>` alias this runner registered; removed one-by-one in {@link shutdown}. */\n private readonly registeredAliases = new Set<string>()\n /**\n * `{ \"<ui-package>\": { port, alias } }` handed to the turbo child via `INFRA_KIT_UI_PORTS` (computed in\n * printReady).\n *\n * The `alias` is load-bearing, not provenance: the page is served from `https://<alias>` on :443, so\n * vite's HMR client must dial `wss://<alias>` \u2014 left to itself it derives the socket from vite's own\n * bound port, which the browser then blocks as mixed content. Only the runner knows which alias it\n * actually registered, so it publishes it rather than letting the helper re-derive it (a helper that\n * merely *computes* an alias would point HMR at an unregistered host on a bare `vite dev`).\n */\n private uiPortMap: Record<string, { port: number; alias: string }> = {}\n /**\n * Latched by {@link shutdown} before it touches any state. Teardown deregisters aliases and then\n * spends seconds reaping children, so without this latch a watch event \u2014 or a restart already in\n * flight \u2014 re-enters {@link startOneApp} and re-registers an alias that nothing will ever remove.\n */\n private shuttingDown = false\n /**\n * Dependency closure used to scope a shared-package restart, or `null` for \"restart every app\"\n * (the fail-safe {@link selectPackageRestartTargets} already honours). Read at EVENT time, not\n * captured when the watcher is armed: building it shells out to turbo (~1s on a 7-backend repo),\n * and blocking the watcher on it left a window right after `ready` in which a save was silently\n * dropped. The watcher arms immediately on `null` and this is swapped in when turbo answers.\n */\n private closureMap: ClosureMap | null = null\n /** In-flight {@link closureMap} build, awaited by {@link shutdown} so no turbo child outlives the runner. */\n private closureBuild: Promise<void> = Promise.resolve()\n\n constructor(\n options: DevServerOptions = {},\n runBuild: BuildRunner = launchScript,\n turboWatchFactory: TurboWatchFactory = defaultTurboWatchFactory,\n uiDevFactory: UiDevFactory = defaultUiDevFactory,\n dryRunner?: DryRunner,\n renderer?: DevUi,\n healthProbe: HealthProbe = defaultHealthProbe,\n proxy: PortlessDriver = createPortlessDriver(),\n ) {\n this.options = options\n this.runBuild = runBuild\n this.turboWatchFactory = turboWatchFactory\n this.uiDevFactory = uiDevFactory\n this.dryRunner = dryRunner\n this.proxy = proxy\n initLogFile()\n // The renderer owns all terminal output + the log tee; construct it before the first narrate below.\n this.renderer = renderer ?? new DevRenderer({ appendLog: appendLogFile, verbose: this.options.verbose ?? false })\n this.healthProbe = healthProbe\n this.devContextDir = path.join(process.cwd(), '.infra-kit', 'dev-context')\n\n // Walk up from the consumer repo cwd to the monorepo root.\n this.monorepoRoot = findMonorepoRoot(process.cwd())\n\n if (process.env.DOPPLER_PROJECT != null || process.env.DOPPLER_ENVIRONMENT != null) {\n this.renderer.log('\uD83D\uDD10 Doppler env detected (DOPPLER_PROJECT / DOPPLER_ENVIRONMENT)', 'debug')\n }\n }\n\n /**\n * Discover API apps and resolve each app's port + URL prefix. Delegates bare\n * filesystem discovery to {@link discoverApiAppsBare} and per-app resolution to\n * the pure `resolvePort` / `resolvePrefixUrl`, preserving the original behavior.\n */\n private discoverApiApps(devConfig: DevConfig): IApiAppConfig[] {\n return discoverApiAppsBare(this.monorepoRoot).map((app) => {\n return {\n ...app,\n preferredPort: this.resolvePreferredPort(app.name, devConfig),\n prefixUrl: this.resolvePrefixUrl(app.name, devConfig),\n // Default participate; the resolved preset value overrides this in `run()`.\n watchDeps: true,\n }\n })\n }\n\n /**\n * Read the `dev` section from the resolved infra-kit config. Defensive: any\n * failure (not in an infra-kit project, missing/invalid config) resolves to an\n * empty map so the dev-server always falls back to env vars + built-in defaults\n * rather than refusing to start.\n */\n private async loadDevConfig(): Promise<DevConfig> {\n try {\n const config = await getInfraKitConfig()\n\n return config.dev ?? {}\n } catch {\n return {}\n }\n }\n\n /** Thin delegator to the pure {@link normalizeAppIncludePure} over the runner's `--app` list. */\n private normalizeAppInclude(): string[] | null {\n return normalizeAppIncludePure(this.options.include)\n }\n\n /** Thin delegator to the pure {@link resolvePreferredPortPure}, threading env + config. */\n private resolvePreferredPort(appName: string, devConfig: DevConfig): number | undefined {\n return resolvePreferredPortPure(appName, process.env, devConfig)\n }\n\n /** Thin delegator to the pure {@link resolvePrefixUrlPure}. */\n private resolvePrefixUrl(appName: string, devConfig: DevConfig): string {\n return resolvePrefixUrlPure(appName, devConfig)\n }\n\n /** Load the top-level `devServersPresets` map from the resolved infra-kit config (defensive: `{}` on failure). */\n private async loadDevPresets(): Promise<DevPresets> {\n try {\n return (await getInfraKitConfig()).devServersPresets ?? {}\n } catch {\n return {}\n }\n }\n\n /**\n * The preset definition to run: the named preset (`infra-kit dev <preset>`; throws with the\n * available names when unknown), or an `apps`-less preset when no preset was given \u2014 which\n * `resolvePreset` expands to every discovered app + part.\n */\n private resolvePresetDef(devPresets: DevPresets): DevPreset {\n // A wizard-built in-memory preset wins over the named lookup: it already IS the resolved run plan.\n if (this.options.presetDef != null) {\n return this.options.presetDef\n }\n\n const name = this.options.preset\n\n if (name == null) {\n return {}\n }\n\n const def = devPresets[name]\n\n if (!def) {\n const available = Object.keys(devPresets)\n\n throw new Error(\n `Unknown dev preset \"${name}\". Available: ${available.length > 0 ? available.join(', ') : '(none defined in devServersPresets)'}`,\n )\n }\n\n return def\n }\n\n public async start(): Promise<void> {\n // Backend readiness clock (UI is fire-and-forget, so `ready in Xs` is BE-only \u2014 labeled honestly).\n const bootStart = Date.now()\n const include = this.normalizeAppInclude()\n const watch = this.options.watch ?? false\n const devConfig = await this.loadDevConfig()\n\n process.env.POWERTOOLS_DEV ??= 'true'\n process.env.LOG_LEVEL ??= 'DEBUG'\n\n this.renderer.narrate('\uD83D\uDE80 Starting Development Server Runner')\n\n if (watch) {\n this.renderer.narrate('\uD83D\uDC40 Watch mode: will rebuild and restart on file save')\n }\n\n const { apps, uiApps, apiAppsAll, uiAppsAll } = await this.resolveRunPlan(devConfig, include)\n\n if (apps.length === 0 && uiApps.length === 0) {\n this.renderer.log('\u26A0\uFE0F No API or UI apps to run for this preset', 'warn')\n\n return\n }\n\n await this.bringUpProxy(apps)\n await this.buildAll(apps, uiApps, watch)\n\n if (apps.length > 0) {\n this.renderer.bootStep('starting servers')\n await this.startAllApps(apps)\n this.renderer.narrate(\n this.failedApps.length === 0\n ? '\uD83C\uDF89 All servers started!'\n : `\u26A0\uFE0F ${this.appServers.length}/${apps.length} servers started \u2014 ${this.failedApps.length} failed`,\n )\n this.renderer.narrate(\n `\uD83D\uDCDD Handler logs (AWS Powertools, logger.info/debug, etc.) \u2192 this terminal. Runner-only file: ${LOG_FILE_PATH}`,\n )\n }\n\n // Collapse the boot spinner into the calm ready header (BE endpoints + UI reference lines).\n // Runs for a UI-only session too, so it never leaves a blank screen. The route dump is opt-in.\n // The header names the running packages, so it is derived from the post-`--app` sets \u2014 not from\n // `options.preset` (unset for the wizard's in-memory preset) nor from `include` (app names only).\n const target = deriveTargetLabel({\n preset: this.options.preset,\n running: [\n ...apps.map((a) => {\n return `${a.name}/api`\n }),\n ...uiApps.map((a) => {\n return `${a.name}/ui`\n }),\n ],\n discovered: [\n ...apiAppsAll.map((a) => {\n return `${a.name}/api`\n }),\n ...uiAppsAll.map((a) => {\n return `${a.name}/ui`\n }),\n ],\n })\n\n await this.printReady(apps, uiApps, bootStart, target)\n if (this.options.routes) {\n this.printRouteDump()\n }\n\n // Everything the user asked for is dead and there is no UI to fall back on: there is nothing left\n // to serve, watch, or proxy. Resident-but-empty is the worst of both worlds \u2014 it looks like a\n // running dev server and exits 0 when finally interrupted, so a CI step or a script would call it\n // a success. Fail loudly instead. A PARTIAL failure stays resident: the survivors are still useful,\n // and `--watch` can bring the casualties back on the next save.\n if (this.appServers.length === 0 && uiApps.length === 0 && this.failedApps.length > 0) {\n throw new Error(\n `infra-kit dev: no app started (${this.failedApps.length} of ${apps.length} failed). ` +\n `First failure: ${this.failedApps[0]?.reason ?? 'unknown'}`,\n )\n }\n\n this.armWatch(apps, uiApps, watch)\n\n // Frontends last: their delegated `turbo run dev` feeds the live tail below the BE table.\n if (uiApps.length > 0) {\n this.startUiDev(uiApps)\n }\n\n // Watch each running backend's health for the rest of the session. Armed only when a backend is\n // running \u2014 UI liveness is vite's, not ours. A UI-only session has nothing to probe.\n if (this.appServers.length > 0) {\n this.startLivenessMonitor()\n }\n }\n\n /**\n * Resolve WHAT to run: discover the api/ui app parts, resolve the active preset against them, then\n * narrow to the `--app`/`--self` include set. Returns both the filtered run sets (`apps`/`uiApps`)\n * and the full discovered sets (`apiAppsAll`/`uiAppsAll`), which the ready-header target label needs.\n */\n private async resolveRunPlan(\n devConfig: DevConfig,\n include: string[] | null,\n ): Promise<{\n apps: IApiAppConfig[]\n uiApps: DiscoveredUiApp[]\n apiAppsAll: IApiAppConfig[]\n uiAppsAll: DiscoveredUiApp[]\n }> {\n // What to run is a named preset (`infra-kit dev <preset>`), resolved against the discovered app\n // parts (api/ui). No preset \u2192 run everything (`*`). `--app`/`--self` (`include`) further narrow it.\n const apiAppsAll = this.discoverApiApps(devConfig)\n const uiAppsAll = discoverUiAppsBare(this.monorepoRoot)\n const resolved = resolvePreset(this.resolvePresetDef(await this.loadDevPresets()), {\n api: apiAppsAll.map((a) => {\n return a.name\n }),\n ui: uiAppsAll.map((a) => {\n return a.name\n }),\n })\n\n if (resolved.unmatched.length > 0) {\n this.renderer.log(`\u26A0\uFE0F Preset targets not found (skipped): ${resolved.unmatched.join(', ')}`, 'warn')\n }\n\n const apiNames = new Set(\n resolved.targets\n .filter((t) => {\n return t.part === 'api'\n })\n .map((t) => {\n return t.app\n }),\n )\n const uiNames = new Set(\n resolved.targets\n .filter((t) => {\n return t.part === 'ui'\n })\n .map((t) => {\n return t.app\n }),\n )\n const passesInclude = (name: string): boolean => {\n return !include || include.includes(name)\n }\n const watchDepsByApp = new Map(\n resolved.targets\n .filter((t) => {\n return t.part === 'api'\n })\n .map((t) => {\n return [t.app, t.watchDeps] as const\n }),\n )\n const apps = apiAppsAll\n .filter((a) => {\n return apiNames.has(a.name) && passesInclude(a.name)\n })\n .map((a) => {\n return { ...a, watchDeps: watchDepsByApp.get(a.name) ?? a.watchDeps }\n })\n const uiApps = uiAppsAll.filter((a) => {\n return uiNames.has(a.name) && passesInclude(a.name)\n })\n\n return { apps, uiApps, apiAppsAll, uiAppsAll }\n }\n\n /**\n * Layer B: confirm the portless proxy is serving BEFORE any alias registration \u2014 backends register in\n * startOneApp, UIs in printReady, and both need a live daemon. Runs ahead of startAllApps AND startUiDev\n * so a UI-only session checks it too; a proxy that is not up throws here, before anything is spawned.\n *\n * The port is no longer negotiable: every dev URL is `https://<alias>` with no port, which means the one\n * port that can serve them is {@link DEFAULT_DEV_PROXY_PORT} (443, the implicit HTTPS port).\n */\n private async bringUpProxy(apps: IApiAppConfig[]): Promise<void> {\n // Before anything is spawned or aliased: refuse if the consumer's PINNED `infra-kit/vite` predates the\n // HTTPS contract. It would ignore the fragment's `origin` and proxy plain HTTP at a TLS listener.\n assertHelperVersionFloor(findMonorepoRoot(process.cwd()))\n await this.ensureProxy()\n\n if (apps.length > 0) this.assertNoPortConflicts(apps)\n }\n\n /**\n * Build both build phases back to back under one boot line: the API boot closure (dist must exist\n * before servers import handlers) then the UI dependency closure (warmed BEFORE any persistent child\n * so both see a warm cache and the cold-cache double-build race can't corrupt shared dist).\n */\n private async buildAll(apps: IApiAppConfig[], uiApps: DiscoveredUiApp[], watch: boolean): Promise<void> {\n // One boot line covers both build phases: they run back to back, so two boot steps only make\n // the spinner flip between near-identical lines. Never empty \u2014 the no-apps case returned above.\n const bootLabel = [\n apps.length > 0 ? `building ${packageList(apps)}` : '',\n uiApps.length > 0 ? `warming ${packageList(uiApps)}` : '',\n ]\n .filter(Boolean)\n .join(' \u00B7 ')\n\n this.renderer.bootStep(bootLabel)\n\n if (apps.length > 0) await this.buildApps(apps, watch)\n if (uiApps.length > 0) await this.buildUiApps(uiApps)\n }\n\n /**\n * Start the watch engine when there's anything to rebuild \u2014 backends OR frontends. A UI-only session\n * (no API app) still needs the engine so a shared-lib edit rebuilds its dist and vite reloads.\n */\n private armWatch(apps: IApiAppConfig[], uiApps: DiscoveredUiApp[], watch: boolean): void {\n if (!(watch && (this.appServers.length > 0 || uiApps.length > 0))) {\n return\n }\n\n // Arm the watcher FIRST, on the fail-safe `null` map (a shared-package change restarts every app).\n // Awaiting the closure map here instead cost ~1s on a 7-backend repo \u2014 a full second after \"ready\"\n // in which a save was watched by nobody. Scoping is an optimisation; never buy it with a blind window.\n this.setupWatch(apps, uiApps)\n\n this.closureBuild = this.buildClosureMapSafe(apps).then((map) => {\n // A teardown may have overtaken us; assigning is harmless either way (nothing reads it after).\n this.closureMap = map\n })\n }\n\n /**\n * Warm ONLY each UI's dependency closure (`<pkg>^...` \u2014 deps, excluding the UI itself, so no full\n * production `vite build`) with a cache-friendly (non-`--force`) turbo build.\n *\n * This is the ONLY build of that closure: the `turbo run dev` child runs with `--only`, so it no\n * longer re-walks `^build` as a fallback (that walk was pure noise \u2014 see {@link file://./ui-dev.ts}).\n * Still non-fatal, because a failure here is loud rather than silent: vite fails to resolve the\n * missing dep and reports it in the live tail. Warn and continue instead of refusing to start.\n */\n private async buildUiApps(uiApps: DiscoveredUiApp[]): Promise<void> {\n const filters = uiApps\n .map((a) => {\n return `--filter=${a.packageName}^...`\n })\n .join(' ')\n\n try {\n await this.runBuild(\n `pnpm exec turbo run build ${filters} --env-mode=loose --output-logs=errors-only --no-update-notifier`,\n this.renderer.logFn,\n )\n this.renderer.narrate('\u2705 UI deps built')\n } catch (error) {\n this.renderer.log(\n `\u26A0\uFE0F UI dep build failed (continuing; vite will report unresolved deps): ${String(error)}`,\n 'warn',\n )\n }\n }\n\n /**\n * Start the frontends via ONE delegated `turbo run dev` child. Its stdio is PIPED, so the raw output\n * is tee'd verbatim to the runner log while each framework line lands in the renderer's tagged tail \u2014\n * the terminal stays owned by `infra-kit dev`. Reaped on {@link shutdown}. Concurrency \u2265 the persistent\n * UI `dev` task count (turbo hard-errors otherwise).\n */\n private startUiDev(uiApps: DiscoveredUiApp[]): void {\n const names = uiApps\n .map((a) => {\n return a.name\n })\n .join(', ')\n\n this.renderer.narrate(`\uD83C\uDFA8 Starting ${uiApps.length} UI dev server(s) via \\`turbo run dev\\`: ${names}`)\n this.renderer.narrate(' (framework output is routed into the live tail; full detail in the runner log)')\n\n // Hand the runner-assigned UI ports to the vite child so each UI binds exactly the port the ready\n // header already advertised (`strictPort` on the vite side) \u2014 proxy or not, the printed URL and the\n // bound port cannot drift. An empty map (every assignment failed) passes no env, so vite falls back\n // to picking its own free port and printing it.\n const uiPortEnv =\n Object.keys(this.uiPortMap).length > 0 ? { INFRA_KIT_UI_PORTS: JSON.stringify(this.uiPortMap) } : undefined\n\n // Every dev URL is now HTTPS behind portless's PRIVATE CA, which Node's bundled trust store knows\n // nothing about (and Node does not read the macOS keychain, so `portless trust` alone does not help a\n // Node process). Vite's own proxy is covered by the scoped `secure: false`, but anything ELSE the dev\n // loop runs \u2014 a backend calling a sibling's hero URL, a node `fetch`, an e2e runner \u2014 would fail with\n // `SELF_SIGNED_CERT_IN_CHAIN`. Hand the CA down so those clients validate instead of breaking.\n // A pre-set NODE_EXTRA_CA_CERTS wins: it is the user's own trust decision, not ours to overwrite.\n const caEnv =\n process.env.NODE_EXTRA_CA_CERTS == null && fs.existsSync(readCaPath())\n ? { NODE_EXTRA_CA_CERTS: readCaPath() }\n : undefined\n\n // turbo tags each line with the PACKAGE name; the endpoint rows are keyed by APP name (`client/ui`),\n // so map back onto the exact label the ready header already shows. An unrecognized package degrades\n // to its own name rather than being dropped \u2014 a line is never silently lost.\n const tagByPackage = new Map(\n uiApps.map((a) => {\n return [a.packageName, `${a.name}/ui`] as const\n }),\n )\n\n // Non-fatal on a log-write failure: a broken tee must never down the dev session, and the terminal\n // tail still carries every framework line.\n this.uiLogStream = fs.createWriteStream(LOG_FILE_PATH, { flags: 'a' })\n this.uiLogStream.on('error', () => {\n this.uiLogStream = null\n })\n\n this.uiDev = this.uiDevFactory({\n packageNames: uiApps.map((a) => {\n return a.packageName\n }),\n cwd: process.cwd(),\n concurrency: Math.max(uiApps.length + 4, 12),\n env: uiPortEnv || caEnv ? { ...uiPortEnv, ...caEnv } : undefined,\n appendLog: (text) => {\n this.uiLogStream?.write(text)\n },\n onLine: ({ pkg, text }) => {\n this.renderer.event({ tag: tagByPackage.get(pkg) ?? `${pkg}/ui`, text })\n },\n // Surface a silently-dead frontend engine: once `turbo run dev` exits, every UI's live reload\n // stops and no framework line ever reaches the tail again.\n onUnexpectedExit: (detail) => {\n this.reportEngineDeath('UI dev engine (`turbo run dev`)', 'frontends stopped reloading', detail)\n },\n })\n }\n\n /** Render an app list as `name:port, name:port` for log lines. */\n private formatAppList(apps: Array<{ name: string; port: number }>): string {\n return apps\n .map((a) => {\n return `${a.name}:${a.port}`\n })\n .join(', ')\n }\n\n /**\n * Throw (after logging remediation tips) when two apps are EXPLICITLY pinned to the same\n * port. Apps with no explicit port bind an ephemeral `listen(0)` port each (collision-free\n * by construction), so they are excluded from the gate \u2014 otherwise the default multi-app\n * run would false-throw on the shared `DEFAULT_PORT` before dynamic allocation de-conflicts.\n */\n private assertNoPortConflicts(apps: IApiAppConfig[]): void {\n const explicitApps = apps\n .filter((app) => {\n return app.preferredPort != null\n })\n .map((app) => {\n return { name: app.name, port: app.preferredPort! }\n })\n const { duplicatePorts, conflictingApps } = findPortConflicts(explicitApps)\n\n if (duplicatePorts.length === 0) {\n return\n }\n\n this.renderer.log(`\u26A0\uFE0F Port conflict detected! ${duplicatePorts.join(', ')}`, 'error')\n this.renderer.log(`Conflicting apps: ${this.formatAppList(conflictingApps)}`, 'error')\n this.renderer.log('\\n\uD83D\uDCA1 Tip: give each app a distinct port via `{APP}_PORT` env (e.g. `CLIENT_PORT=`,', 'error')\n this.renderer.log(' `SEARCH_ENGINE_PORT=`) or `dev.<app>.port` in infra-kit.json; or run a subset with', 'error')\n this.renderer.log(' `--app=<name>,<name>`.\\n', 'error')\n throw new Error(`Port conflict detected: ${duplicatePorts.join(', ')}`)\n }\n\n /** Build every app via turbo; rethrows the build error after logging stdout/stderr. */\n private async buildApps(apps: IApiAppConfig[], watch: boolean): Promise<void> {\n const filters = apps\n .map((a) => {\n return `--filter=${a.packageName}`\n })\n .join(' ')\n // With `--watch`, always bypass Turbo cache so `tsc` runs and `dist/` matches disk (otherwise watch restarts can be no-ops).\n const buildCmd = `pnpm exec turbo run build ${filters} --env-mode=loose --output-logs=errors-only --no-update-notifier${watch ? ' --force' : ''}`\n\n // No narration here: the `building <pkgs>` boot step already names these exact targets.\n try {\n await this.runBuild(buildCmd, this.renderer.logFn)\n this.renderer.narrate('\u2705 Build complete')\n } catch (buildError) {\n this.renderer.log(`\u274C Build failed: ${String(buildError)}`, 'error')\n if (buildError instanceof Error && buildError.message) {\n this.renderer.log(` ${buildError.message}`, 'error')\n }\n const err = buildError as { stdout?: string; stderr?: string }\n\n if (err.stdout) this.renderer.log(` stdout: ${err.stdout.trim()}`, 'error')\n if (err.stderr) this.renderer.log(` stderr: ${err.stderr.trim()}`, 'error')\n throw buildError\n }\n }\n\n /**\n * Start every app concurrently, collecting the ones that boot; per-app failures are logged,\n * not fatal. Safe to parallelize because each app has a distinct port (guarded up-front) and a\n * distinct `ServerlessLocalRun`, and `startOneApp` no longer mutates cwd. Push order into\n * `appServers` is non-deterministic but nothing depends on it (the table renders from `apps`).\n */\n private async startAllApps(apps: IApiAppConfig[]): Promise<void> {\n await Promise.all(\n apps.map(async (app) => {\n try {\n const started = await this.startOneApp(app)\n\n if (started) {\n this.appServers.push({ app, ...started })\n }\n } catch (error) {\n this.renderer.log(`\u274C Failed to start ${app.name}: ${String(error)}`, 'error')\n // Recorded, not just logged: `printReady` renders the table from `appServers`, so an app\n // that only ever appears in a log line vanishes from the header entirely \u2014 indistinguishable\n // from one that was never requested. `failedApps` is what puts the `\u25CF failed` row back.\n this.failedApps.push({ app, reason: errorReason(error) })\n }\n }),\n )\n }\n\n /**\n * Confirm a portless daemon is serving TLS on {@link DEFAULT_DEV_PROXY_PORT} before any alias is\n * registered. Every dev URL is a hostname served by this daemon, so a proxy that is not up is a FATAL\n * start error, not a degraded mode: there is no second way to reach an app, and a half-started dev loop\n * that silently routes nowhere is worse than a refusal that names the fix.\n *\n * **Probe only \u2014 never start, never elevate.** `:443` is privileged, and portless binds it by re-execing\n * through `sudo` with an inherited stdio, which a detached child cannot answer. The daemon is installed\n * once, out-of-band. There is deliberately no unprivileged fallback: a fallback puts the port back in the\n * URL, which is the whole thing this design removes.\n *\n * Identity is proven **on the wire** (`X-Portless`), never from portless's state files \u2014 those are\n * process-global singletons that any other daemon's start rewrites and any stop deletes, which would make\n * a perfectly healthy `:443` daemon look dead. See {@link defaultIsProxyServing}.\n *\n * @throws When portless is missing, or no portless daemon is serving TLS on the proxy port.\n */\n private async ensureProxy(): Promise<void> {\n if (!(await this.proxy.isAvailable())) {\n throw new Error(\n 'infra-kit dev: portless is not installed, so no dev URL can resolve. It ships as a dependency of infra-kit \u2014 reinstall with `pnpm install`.',\n )\n }\n\n if (await this.proxy.isProxyServing(this.proxyPort, true)) return\n\n throw new Error(\n `infra-kit dev: no portless daemon is serving HTTPS on :${this.proxyPort}, so no dev URL can resolve. ` +\n 'Install it once (this is the only step that needs root):\\n' +\n ' sudo portless service install\\n' +\n 'Then trust its local CA (no sudo needed):\\n' +\n ' portless trust\\n' +\n '`infra-kit doctor` checks both.',\n )\n }\n\n /**\n * Register `<release>.<package>` \u2192 `port` with portless and return the alias HOST\n * (`<release>.<package>.localhost`). The alias IS the app's only address, so a failure here is fatal\n * rather than a silent downgrade \u2014 an app nobody can reach is not a running app.\n *\n * @throws When the package name yields no legal DNS label, or portless rejects the registration.\n */\n private async registerAppAlias(packageName: string, appDir: string, port: number): Promise<string> {\n const release = readAppRelease(appDir)\n // An npm name is not a DNS label \u2014 see {@link slugifyHostLabel}. `infra-kit/vite` slugifies its\n // own `<packageName>` template token identically, so the proxy target and this alias cannot drift.\n const label = slugifyHostLabel(packageName)\n\n if (label === '') {\n throw new Error(`infra-kit dev: package name \"${packageName}\" has no letters or digits to build a hostname from.`)\n }\n const name = `${release}.${label}`\n\n if (!(await this.proxy.registerAlias(name, port))) {\n throw new Error(`infra-kit dev: portless refused the alias \"${name}\" \u2192 127.0.0.1:${port}.`)\n }\n this.registeredAliases.add(name)\n\n return `${name}.localhost`\n }\n\n private async startOneApp(\n app: IApiAppConfig,\n ): Promise<{ server: ServerlessLocalRun; boundPort: number; alias: string } | null> {\n this.renderer.narrate(`\uD83D\uDD04 Starting ${app.name}...`)\n\n // No `process.chdir` here: `ServerlessLocalRun` reads `serverless.yml` and imports the\n // compiled handler from `controllersPath` (absolute), so the runner never mutates cwd \u2014\n // which is what makes concurrent boot/restart safe.\n const server = new ServerlessLocalRun({\n controllersPath: app.path,\n prefixUrl: app.prefixUrl,\n port: app.preferredPort,\n appName: app.name,\n // Route live request traffic into the renderer's tagged, timestamped tail (`<app>/api \u2026`).\n // This is the structured seam \u2014 independent of the legacy `DEV_SERVER_REQUEST_LOG` raw line \u2014\n // so the app name is threaded in-process and never leaked to spawned turbo/vite children.\n onRequestLog: ({ method, path: reqPath, status, ms }) => {\n // Keep the runner's own `/__health` liveness probes out of the live tail \u2014 they are internal\n // noise, not app traffic. Real handler routes still stream.\n if (reqPath === '/__health') return\n this.renderer.event({ tag: `${app.name}/api`, text: `${method} ${reqPath} ${status} ${ms}ms` })\n },\n })\n\n // `start()` binds an ephemeral (or preferred-then-ephemeral) port and RETURNS the actual\n // one \u2014 consume THAT, never the static preferred hint, so the log/table/health agree.\n const boundPort = await server.start()\n\n // Layer B: (re)point the portless alias to the freshly-bound port (also fires on watch-restart,\n // since startOneApp is the shared start+restart path). Throws if the alias cannot be registered.\n //\n // Unwind the bind on failure: the caller only records `server` in `appServers` once this method\n // RETURNS, so a throw here would otherwise leave a listening fastify that `shutdown()` never sees\n // and never closes \u2014 a port held for the rest of the session.\n let alias: string\n\n try {\n alias = await this.registerAppAlias(app.packageName, app.path, boundPort)\n } catch (error) {\n await server.close().catch(() => {})\n throw error\n }\n\n // Record the ACTUAL bound port + the alias that was actually registered in this runner's own\n // dev-context fragment. Ordered AFTER registerAppAlias: a fragment written before it could only\n // ever claim `alias: undefined`, and the vite helper would fall back to a direct target for an\n // app that IS reachable by name. Inside startOneApp (the shared start+restart path) so a\n // watch-restart refreshes both instead of orphaning a stale fragment (M2). Non-fatal: a\n // fragment-write failure must not down the server.\n try {\n this.writeDevContextFragment(app, boundPort, alias)\n } catch (error) {\n this.renderer.log(`\u26A0\uFE0F Failed to write dev-context fragment for ${app.name}: ${String(error)}`, 'warn')\n }\n\n this.renderer.narrate(`\u2705 ${app.name} started on port ${boundPort}`)\n\n return { server, boundPort, alias }\n }\n\n /**\n * Atomically write this runner's `.infra-kit/dev-context/<app>.json` fragment recording the\n * ACTUAL bound port (REV-5: serialize to a same-dir temp file, then `renameSync` into place, so a\n * concurrent reader \u2014 the vite helper's directory merge \u2014 never observes torn JSON). Each runner\n * writes ONLY its own app's fragment, so cmux panes never clobber each other.\n */\n private writeDevContextFragment(app: IApiAppConfig, boundPort: number, alias: string): void {\n const fragment: DevContextFragment = {\n package: app.packageName,\n port: boundPort,\n pid: process.pid,\n writtenAt: Date.now(),\n release: readAppRelease(app.path),\n alias,\n // The ORIGIN of the hero URL the ready screen prints (`resolveEndpointUrl` appends the app's\n // `prefixUrl` on top of this same alias). Prefix-free on purpose: this is a proxy target, and the\n // frontend supplies its own path.\n origin: `https://${alias}`,\n }\n const target = path.join(this.devContextDir, `${app.name}.json`)\n const tmp = path.join(this.devContextDir, `${app.name}.json.${process.pid}.tmp`)\n\n fs.mkdirSync(this.devContextDir, { recursive: true })\n fs.writeFileSync(tmp, JSON.stringify(fragment, null, 2))\n fs.renameSync(tmp, target)\n }\n\n /** Remove THIS runner's own `<app>.json` fragment on shutdown (never another runner's). */\n private removeDevContextFragment(app: IApiAppConfig): void {\n fs.rmSync(path.join(this.devContextDir, `${app.name}.json`), { force: true })\n }\n\n /** Run restart jobs one after another (watch can fire faster than close + listen). */\n private scheduleRestartWork(work: () => Promise<void>): Promise<void> {\n const run = this.restartWorkChain.then(\n () => {\n return work()\n },\n () => {\n return work()\n },\n )\n\n this.restartWorkChain = run.catch(() => {})\n\n return run\n }\n\n private async delayPortRelease(): Promise<void> {\n await new Promise((r) => {\n return setTimeout(r, DevServerRunner.PORT_RELEASE_DELAY_MS)\n })\n }\n\n /**\n * Schedule a restart of the given apps (1 or N), serialized against other restarts via\n * {@link scheduleRestartWork}. A single-app dist change passes `[app]`; a dependency-package\n * dist change passes every running app. The `turbo watch` engine has already rebuilt `dist/`,\n * so the runner only bounces the fastify server(s) \u2014 no build here.\n */\n private restart(apps: IApiAppConfig[]): Promise<void> {\n if (this.shuttingDown) return Promise.resolve()\n\n return this.scheduleRestartWork(() => {\n // Re-checked inside the chain: this job may have queued behind a restart that was still\n // running when shutdown() latched, so the flag can flip between scheduling and execution.\n if (this.shuttingDown) return Promise.resolve()\n\n return this.runRestart(apps)\n })\n }\n\n /** Resolve the requested apps to their live server slots (dropping any not running). */\n private resolveRestartTargets(apps: IApiAppConfig[]): Array<{ idx: number; app: IApiAppConfig }> {\n return apps\n .map((app) => {\n return {\n idx: this.appServers.findIndex((e) => {\n return e.app.name === app.name\n }),\n app,\n }\n })\n .filter((t) => {\n return t.idx >= 0\n })\n }\n\n private async runRestart(apps: IApiAppConfig[]): Promise<void> {\n const targets = this.resolveRestartTargets(apps)\n\n if (targets.length === 0) return\n\n const label = targets.length === 1 ? targets[0]!.app.name : `${targets.length} apps`\n\n this.renderer.log(`\uD83D\uDD04 Restarting ${label}...`)\n await Promise.all(\n targets.map(async ({ idx }) => {\n try {\n await this.appServers[idx]!.server.close()\n } catch (err) {\n this.renderer.log(` Close warning: ${String(err)}`, 'debug')\n }\n }),\n )\n\n await this.delayPortRelease()\n\n // Thread each restart's OUTCOME (the fresh entry, or `null` on failure) straight into the probe below\n // instead of re-reading `appServers[idx]`: a failed `startOneApp` leaves the stale, now-closed entry in\n // that slot, so re-reading it would probe a freed port \u2014 wasting the full probe timeout and, if the old\n // `close()` also failed, reporting the dead-but-still-listening server as `\u25CF up`. The outcome list makes\n // the no-probe \"down\" branch actually reachable on failure.\n const outcomes = await Promise.all(\n targets.map(async ({ idx, app }) => {\n try {\n const restarted = await this.startOneApp(app)\n\n if (restarted) {\n const entry = { app, ...restarted }\n\n this.appServers[idx] = entry\n\n return { app, entry }\n }\n\n return { app, entry: null }\n } catch (error) {\n this.renderer.log(`\u274C Failed to restart ${app.name}: ${String(error)}`, 'error')\n\n return { app, entry: null }\n }\n }),\n )\n // Show each restarted app's CURRENT bound port: an ephemeral-port app rebinds a fresh port on\n // restart, and the server table is printed only once at boot \u2014 so this line is the only place the\n // new port surfaces in the default (quiet) terminal.\n //\n // Re-probe `/__health` so the summary reports HONEST liveness, not a bare \"restarted\" for a server\n // that binds its port but 500s on the first request. Mirrors printReady's probe (127.0.0.1, never\n // `localhost` \u2014 ServerlessLocalRun binds v4 loopback only). Probes run concurrently; a target whose\n // restart failed (`entry === null`) is reported down without a probe. A single down server downgrades\n // the leading \u2705 to \u26A0\uFE0F so the line reads consistently.\n const probed = await Promise.all(\n outcomes.map(async ({ app, entry }) => {\n const healthy = entry ? await this.healthProbe(DevServerRunner.healthUrl(entry.boundPort)) : false\n const label = entry ? `${app.name}:${entry.boundPort}` : app.name\n\n return { label: `${label} ${healthy ? '\u25CF up' : '\u25CF down'}`, healthy }\n }),\n )\n\n const allHealthy = probed.every((p) => {\n return p.healthy\n })\n\n this.renderer.log(\n `${allHealthy ? '\u2705' : '\u26A0\uFE0F '} Restarted ${probed\n .map((p) => {\n return p.label\n })\n .join(', ')}`,\n )\n }\n\n /**\n * Build the dependency-closure map ({@link buildClosureMap}) for scoped restarts, or `null` on\n * failure (a `turbo --dry` spawn/parse error). `null` is the fail-safe signal: {@link setupWatch}\n * then restarts every launched app on a package change, exactly as it did before scoping \u2014 never\n * a silent dropped restart.\n */\n private async buildClosureMapSafe(apps: IApiAppConfig[]): Promise<ClosureMap | null> {\n try {\n return await buildClosureMap(this.monorepoRoot, apps, this.dryRunner)\n } catch (err) {\n this.renderer.log(\n `\u26A0\uFE0F Dependency-closure map unavailable (${String(err)}); package changes restart all apps`,\n 'warn',\n )\n\n return null\n }\n }\n\n /**\n * Start the long-lived `turbo watch build` engine, then watch compiled `dist/` output to\n * trigger restarts. A change under an app's `dist` restarts that app; a change under a\n * `packages/<pkg>/dist` restarts only the participating backends whose dependency closure\n * includes that package ({@link selectPackageRestartTargets}), keyed per package dir so unrelated\n * packages don't collapse into one debounce bucket. When the closure map is unavailable\n * (`null`) it falls back to restarting every launched app (fail-safe superset). Restarts are\n * build-less \u2014 the engine already rebuilt `dist/`.\n */\n /**\n * Report a persistent engine (`turbo watch build` / `turbo run dev`) that died on its OWN as a single\n * warn line, unless teardown is already underway. Centralises the `shuttingDown` guard and the message\n * shape shared by both engine callbacks \u2014 `superviseChild`'s `killing` latch already suppresses the\n * exit our own `kill()` causes, so this only ever fires on a genuine crash.\n */\n private reportEngineDeath(engine: string, consequence: string, detail: string): void {\n if (this.shuttingDown) return\n this.renderer.log(`\u26A0\uFE0F ${engine} ${detail} \u2014 ${consequence}. Restart \\`infra-kit dev\\`.`, 'warn')\n }\n\n private setupWatch(apps: IApiAppConfig[], uiApps: DiscoveredUiApp[]): void {\n this.turboWatch = this.turboWatchFactory({\n // API apps: dep-inclusive (`...<pkg>`) \u2014 rebuild the backend + its shared-lib closure and restart it.\n depInclusive: apps.map((a) => {\n return a.packageName\n }),\n // UI apps: dep-closure-only (`<pkg>^...`) \u2014 rebuild the frontend's shared libs (so vite reloads on a\n // FE-only lib edit) WITHOUT production-building the UI; vite owns the UI's own live reload.\n depClosure: uiApps.map((a) => {\n return a.packageName\n }),\n cwd: process.cwd(),\n logFile: LOG_FILE_PATH,\n // Surface a silently-dead engine: once `turbo watch build` exits, saves no longer rebuild `dist/`,\n // so no restart ever fires and the session looks healthy while being frozen.\n onUnexpectedExit: (detail) => {\n this.reportEngineDeath('Watch engine (`turbo watch build`)', 'file saves no longer rebuild', detail)\n },\n })\n this.renderer.narrate('\uD83D\uDC40 Watch mode: started `turbo watch build` engine; watching dist output')\n\n const appDistDirs = getAppDistDirs(apps)\n const packageDistDirs = getPackageDistDirs(this.monorepoRoot)\n const allDistDirs = [...appDistDirs, ...packageDistDirs]\n\n if (allDistDirs.length === 0) {\n this.renderer.log('\u26A0\uFE0F No app or package dist directories found to watch (were they built?)', 'warn')\n\n return\n }\n\n const usePoll = process.env.DEV_SERVER_CHOKIDAR_POLL === '1'\n\n const watcher = chokidar.watch(allDistDirs, {\n ignoreInitial: true,\n awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 },\n // Ignore tsc's incremental bookkeeping + sourcemaps: they rewrite on every build\n // (even content-identical ones) and would bounce fastify onto no real change.\n ignored: (p: string): boolean => {\n return p.endsWith('.tsbuildinfo') || p.endsWith('.map')\n },\n ...(usePoll ? { usePolling: true, interval: 400 } : {}),\n })\n\n this.watcher = watcher\n\n if (usePoll) {\n this.renderer.log('\uD83D\uDC40 chokidar: usePolling enabled (DEV_SERVER_CHOKIDAR_POLL=1)', 'debug')\n }\n\n watcher.on('change', (filePath: string) => {\n this.handleDistChange(filePath, apps, appDistDirs, packageDistDirs)\n })\n\n this.renderer.narrate(\n `\uD83D\uDC40 Watching ${appDistDirs.length} app dist + ${packageDistDirs.length} package dist dir(s) for changes...`,\n )\n }\n\n /**\n * Dispatch a single dist-file change to the right restart. A package (shared-lib) change restarts the\n * dependent apps selected from the closure map (or, fail-safe, every app when the map is missing); an\n * app's own dist change restarts just that app. All restarts are debounced.\n */\n private handleDistChange(\n filePath: string,\n apps: IApiAppConfig[],\n appDistDirs: string[],\n packageDistDirs: string[],\n ): void {\n this.renderer.log(`\uD83D\uDC40 dist change detected: ${filePath}`, 'debug')\n\n const change = classifyDistChange(filePath, appDistDirs, packageDistDirs)\n\n if (change.kind === 'package') {\n // Read late: the map may still be building (\u2192 `null` \u2192 restart all), which is correct, just unscoped.\n const targets = selectPackageRestartTargets(apps, this.closureMap, change.packageDir)\n\n if (targets === null) {\n // Fail-safe: no closure map / no package identity \u2192 restart every launched app. Note this\n // ignores per-app `watchDeps: false` opt-outs \u2014 opt-out is best-effort and yields to the\n // fail-safe superset, so a `turbo --dry` failure never silently drops a needed restart.\n this.scheduleDebounced('__packages__', () => {\n return this.restart(apps)\n })\n\n return\n }\n\n // Empty target set = the package is UI-only or every dependent opted out \u2192 no restart.\n // The `packageDir !== undefined` check narrows it to `string` for `packageDebounceKey`;\n // it is always defined here (a non-null `targets` implies a matched package identity).\n if (targets.length > 0 && change.packageDir !== undefined) {\n this.scheduleDebounced(packageDebounceKey(change.packageDir), () => {\n return this.restart(targets)\n })\n }\n\n return\n }\n\n const app = apps.find((a) => {\n return path.join(a.path, 'dist') === change.app\n })\n\n if (!app) return\n\n this.scheduleDebounced(app.name, () => {\n return this.restart([app])\n })\n }\n\n /**\n * Debounce a restart under `key`: cancel any pending timer for the same key and start\n * a fresh {@link DevServerRunner.WATCH_DEBOUNCE_MS} timer, so a burst of saves collapses\n * into one restart. Errors from the scheduled work are logged, never thrown.\n */\n private scheduleDebounced(key: string, work: () => Promise<void>): void {\n if (this.shuttingDown) return\n\n const existing = this.watchDebounceTimers.get(key)\n\n if (existing) clearTimeout(existing)\n\n const timer = setTimeout(() => {\n this.watchDebounceTimers.delete(key)\n if (this.shuttingDown) return\n work().catch((err) => {\n this.renderer.log(`Restart error (${key}): ${String(err)}`, 'error')\n })\n }, DevServerRunner.WATCH_DEBOUNCE_MS)\n\n this.watchDebounceTimers.set(key, timer)\n }\n\n /**\n * The `/__health` probe URL for a backend on `port`. Always `127.0.0.1`, never `localhost`:\n * ServerlessLocalRun binds v4 loopback only, while `localhost` resolves `[::1]` first on modern Node \u2014\n * the same trap `infra-kit/vite` pins its own host around. Probing by name renders a healthy backend as\n * `\u25CF down`. One home for the string shared by boot ({@link printReady}), restart ({@link runRestart}),\n * and the liveness monitor ({@link livenessTick}).\n */\n private static healthUrl(port: number): string {\n return `http://127.0.0.1:${port}/__health`\n }\n\n /**\n * Start the always-on backend liveness monitor: a background probe loop that catches a backend which\n * goes unhealthy WITHOUT crashing the process (a blocked event loop, a wedged dependency, a fastify that\n * stopped serving) \u2014 invisible to both the one-shot {@link printReady} probe and the crash barrier, which\n * only sees thrown faults. Edge-triggered and flap-resistant; see {@link livenessTick}.\n */\n private startLivenessMonitor(): void {\n this.scheduleLivenessTick()\n }\n\n /**\n * Schedule the next liveness tick. A self-rescheduling `setTimeout` (never `setInterval`): a slow tick \u2014\n * every backend timing out its probe \u2014 can never pile up on the next one, and teardown clears exactly one\n * timer. `unref()` so the monitor never keeps the process alive on its own; the fastify servers do that,\n * and lifecycle is owned by {@link shutdown}.\n */\n private scheduleLivenessTick(): void {\n const intervalMs = this.options.livenessIntervalMs ?? DevServerRunner.LIVENESS_INTERVAL_MS\n\n this.livenessTimer = setTimeout(() => {\n // `finally` always reschedules; the trailing `catch` swallows a rejecting probe seam so one bad tick\n // is skipped rather than crashing the loop, and leaves no floating promise.\n this.livenessTick()\n .finally(() => {\n if (!this.shuttingDown) this.scheduleLivenessTick()\n })\n .catch(() => {})\n }, intervalMs)\n this.livenessTimer.unref()\n }\n\n /**\n * One liveness sweep over every running backend. Edge-triggered per app, keyed by app NAME (so the\n * counter survives an ephemeral-port rebind on restart) with the CURRENT boundPort read fresh each tick:\n *\n * - a probe FAILURE increments a per-app counter; crossing {@link LIVENESS_FAILURE_THRESHOLD} consecutive\n * failures logs ONE `\u26A0\uFE0F unhealthy` line. The threshold is what makes a normal watch-restart (server down\n * <1s, a single interval) invisible \u2014 only a genuinely wedged backend stays down across two sweeps.\n * - a probe SUCCESS after the app had crossed the threshold logs ONE `\u2705 recovered` line; the counter\n * always resets on success. Both edges fire once, so a steady state (healthy OR wedged) emits nothing.\n *\n * `/__health` probes are already filtered out of the live request tail ({@link startOneApp}'s\n * `onRequestLog`), so this never spams. Bails once teardown has latched so a closing server is not\n * misread as down (the timer is also cleared in {@link shutdown} before the servers close).\n */\n private async livenessTick(): Promise<void> {\n if (this.shuttingDown) return\n\n await Promise.all(\n this.appServers.map(async ({ app, boundPort }) => {\n const ok = await this.healthProbe(DevServerRunner.healthUrl(boundPort))\n\n // Re-check AFTER the await: shutdown() may have latched and begun closing servers while this probe\n // was in flight, resolving it `false` against a closing socket. Without this a tick that passed the\n // top-of-method bail could log a false `unhealthy` during teardown.\n if (this.shuttingDown) return\n\n const fails = this.livenessFailures.get(app.name) ?? 0\n\n if (ok) {\n if (fails >= DevServerRunner.LIVENESS_FAILURE_THRESHOLD) {\n this.renderer.log(`\u2705 ${app.name}/api recovered`)\n }\n this.livenessFailures.set(app.name, 0)\n\n return\n }\n\n const next = fails + 1\n\n this.livenessFailures.set(app.name, next)\n if (next === DevServerRunner.LIVENESS_FAILURE_THRESHOLD) {\n this.renderer.log(`\u26A0\uFE0F ${app.name}/api unhealthy (/__health not responding)`, 'warn')\n }\n }),\n )\n }\n\n /**\n * Collapse the boot spinner into the calm ready header: one endpoint row per running backend\n * (pre-probed health dot + a resolving URL), one reference line per UI app (vite prints its own\n * URL in the stream below), the watch line, the clickable log path, and a separator rule.\n * Handles a UI-only session (no backend rows) so it never leaves a blank screen. `ready()` itself\n * is synchronous \u2014 health is probed here and passed in resolved.\n */\n private async printReady(\n apps: IApiAppConfig[],\n uiApps: DiscoveredUiApp[],\n bootStart: number,\n target: string,\n ): Promise<void> {\n // Snapshot BE readiness BEFORE probing \u2014 a `\u25CF down` server's probe timeout must not inflate\n // `ready in Xs` (nor is the boot time itself the probe latency).\n const elapsedMs = Date.now() - bootStart\n const health = await Promise.all(\n this.appServers.map(({ boundPort }) => {\n return this.healthProbe(DevServerRunner.healthUrl(boundPort))\n }),\n )\n const endpoints: EndpointRow[] = this.appServers.map(({ app, alias }, i) => {\n return {\n tag: `${app.name}/api`,\n url: resolveEndpointUrl({ prefixUrl: app.prefixUrl, alias }),\n healthy: health[i] ?? null,\n }\n })\n\n // Pre-assign each UI a free port + its portless alias and stash the map for startUiDev's env. Owning\n // the port is what makes the UI's URL knowable before vite prints it, so every UI gets a real endpoint\n // row. No health probe: vite owns its own readiness. Only a UI whose port could not be assigned falls\n // back to a reference line (\"vite prints its URL below\").\n this.uiPortMap = {}\n const uiEndpoints: EndpointRow[] = []\n const uiRefs: UiRef[] = []\n\n for (const ui of uiApps) {\n const assigned = await this.assignUiPort(ui)\n\n if (assigned != null) {\n uiEndpoints.push({\n tag: `${ui.name}/ui`,\n url: resolveEndpointUrl({ prefixUrl: '', alias: assigned.alias }),\n healthy: null,\n })\n } else {\n uiRefs.push({ tag: `${ui.name}/ui` })\n }\n }\n const watch = this.options.watch ?? false\n const appCount = apps.length + uiApps.length\n const pkgCount = getPackageDistDirs(this.monorepoRoot).length\n\n this.renderer.ready({\n target,\n watch,\n // A UI child (`turbo run dev`) launches iff any UI app runs. This is the sticky-footer signal:\n // its piped output feeds a busy live tail, which the scroll-region UI pins the header above.\n // (`uiRefs` can't serve as the signal \u2014 it empties once a UI aliases into an endpoint row, Layer B.)\n hasUiChild: uiApps.length > 0,\n release: readAppRelease(process.cwd()),\n elapsedMs,\n endpoints: [...endpoints, ...uiEndpoints],\n uiRefs,\n failed: this.failedApps.map(({ app, reason }) => {\n return { tag: `${app.name}/api`, reason }\n }),\n watchSummary: `${appCount} app${appCount === 1 ? '' : 's'} \u00B7 ${pkgCount} package${pkgCount === 1 ? '' : 's'}`,\n logPath: homeShorten(LOG_FILE_PATH),\n logHref: LOG_FILE_PATH,\n })\n }\n\n /**\n * Pre-assign a UI a free port, alias it, and record the port in {@link uiPortMap} (handed to the vite\n * child via `INFRA_KIT_UI_PORTS`, which it binds with `strictPort`). Owning the port ahead of vite's own\n * announcement is what makes the URL knowable in time to print it.\n *\n * `null` degrades this UI to a reference line: either its vite config does not wire `infraKitDev()`\n * ({@link DiscoveredUiApp.managedPort}), so it would ignore the assignment and bind its own port \u2014\n * printing the assigned one would be a lie and aliasing it would 502 \u2014 or the free-port probe failed\n * (extremely rare). An alias that portless REFUSES is not degraded here: {@link registerAppAlias} throws,\n * because a UI advertised at a hostname nothing serves is worse than a UI with no row.\n */\n private async assignUiPort(ui: DiscoveredUiApp): Promise<{ port: number; alias: string } | null> {\n if (!ui.managedPort) return null\n\n let port: number\n\n try {\n port = await getFreePort()\n } catch {\n return null\n }\n const alias = await this.registerAppAlias(ui.packageName, ui.path, port)\n\n this.uiPortMap[ui.packageName] = { port, alias }\n\n return { port, alias }\n }\n\n /**\n * Dump each running app's registered `METHOD /path` routes (opt-in via `--routes`) so the\n * emulator is self-describing. Prints to the terminal (not verbose-gated) \u2014 invoking it means\n * the user explicitly asked for the routes. Reads the live set via `getRegisteredRoutes`.\n */\n private printRouteDump(): void {\n if (this.appServers.length === 0) return\n\n this.renderer.log('\uD83D\uDDFA\uFE0F Registered routes:')\n for (const { app, server } of this.appServers) {\n const routes = server.getRegisteredRoutes()\n\n this.renderer.log(` ${app.name} (${routes.length}): ${routes.length > 0 ? routes.join(', ') : '(none)'}`)\n }\n }\n\n /**\n * Stop watching, cancel any pending debounced restart, and close all running servers.\n * Does not exit the process \u2014 the entry point owns exit.\n */\n public async shutdown(): Promise<void> {\n // Latch BEFORE anything else. Everything below assumes no new alias can be registered once\n // teardown begins; `scheduleDebounced` and `restart` read this flag to honour that.\n this.shuttingDown = true\n\n // Release the terminal FIRST: if the Ink boot UI is still mounted (e.g. SIGINT mid-boot), unmount it\n // before any plain write below, so the shutdown lines never clobber a live region. No-op for the\n // plain renderer and idempotent when Ink already unmounted at ready().\n this.renderer.dispose()\n this.renderer.log('\uD83D\uDED1 Shutting down all servers...')\n\n // Silence every restart SOURCE before deregistering aliases below. Ordered first because alias\n // removal is not idempotent against a concurrent `startOneApp`: a chokidar event or an armed\n // debounce timer firing after the removal would re-register an alias into a set nothing drains\n // again, stranding it exactly like the force-quit case the removal is there to prevent.\n for (const timer of this.watchDebounceTimers.values()) {\n clearTimeout(timer)\n }\n this.watchDebounceTimers.clear()\n\n // Stop the liveness monitor BEFORE the servers close, so a final tick can't probe a closing backend and\n // log a false `unhealthy`. The `shuttingDown` latch above also makes an in-flight tick bail and never\n // reschedule; clearing the pending timer here closes the window between ticks.\n if (this.livenessTimer) {\n clearTimeout(this.livenessTimer)\n this.livenessTimer = null\n }\n\n if (this.watcher) {\n await this.watcher.close()\n this.watcher = null\n }\n\n // Drain a restart already in flight. The latch makes every QUEUED job a no-op, but a job that\n // began before the latch is mid `close() \u2192 listen() \u2192 registerAlias()` and must finish, or its\n // alias lands after the removal below. Never rejects (the chain self-catches).\n await this.restartWorkChain\n\n // And the background closure build, so its `turbo --dry` child never outlives the runner.\n await this.closureBuild\n\n // Layer B: deregister every portless alias BEFORE the child reap below. The reap can take\n // seconds (grace + SIGKILL escalation, per child), and a force-quit during it would otherwise\n // strand an alias pointing at a dead backend \u2014 the 502 you hit on the next start.\n //\n // Concurrent and best-effort: `removeAlias` never throws (the driver swallows failures) and each\n // call is self-bounded by the driver's own subprocess timeout, so this caps the pre-reap delay at\n // roughly one timeout regardless of alias count, and `Promise.all` cannot reject. That bound holds\n // only because `availability` is already warm here: an alias can exist only after `registerAlias`,\n // which awaits `isAvailable()`, and `ensureProxy` awaits it during `start()`. Register an alias\n // without a prior availability check and this silently becomes two subprocesses per alias.\n await Promise.all(\n [...this.registeredAliases].map((name) => {\n return this.proxy.removeAlias(name)\n }),\n )\n this.registeredAliases.clear()\n\n // Reap the long-lived engines (group SIGTERM\u2192SIGKILL) so neither writes fresh dist nor\n // holds a port mid-teardown. Reaped here (not only in the entry signal handler) because tests\n // and any non-signal caller invoke shutdown() directly. Awaited so the SIGKILL escalation\n // completes before the entry point's `process.exit`.\n if (this.turboWatch) {\n await this.turboWatch.kill()\n this.turboWatch = null\n }\n\n if (this.uiDev) {\n await this.uiDev.kill()\n this.uiDev = null\n }\n\n // Strictly after the child is reaped: closing the tee first would drop its teardown output.\n if (this.uiLogStream) {\n this.uiLogStream.end()\n this.uiLogStream = null\n }\n\n for (const { app, server } of this.appServers) {\n try {\n await server.close()\n } catch {\n // ignore\n }\n // Remove this runner's own dev-context fragment so a stopped app drops out of the\n // helper's localSet (only its own \u2014 the directory model keeps runners independent).\n this.removeDevContextFragment(app)\n }\n\n // Final terminal-visible confirmation, so Ctrl-C never ends on a bare cursor. `log` (not the\n // verbose-only `narrate`) so the last line the user sees is always infra-kit's.\n //\n // Our own detached `turbo run dev` child writes its teardown to the log, not the TTY. Any pnpm\n // `ELIFECYCLE` still visible after Ctrl-C therefore comes from the `pnpm run` wrapper processes\n // ABOVE us in the shell's foreground process group (nested consumer scripts), which we cannot\n // redirect: the terminal signals the whole group, pnpm dies at once, and the shell redraws its\n // prompt while this teardown is still running. Fixing that means `exec`ing into the binary from\n // the consumer's dev script so no wrapper survives to report a failed child.\n this.renderer.log(`\u2713 dev stopped \u00B7 logs \u2192 ${homeShorten(LOG_FILE_PATH)}`)\n }\n}\n\n/**\n * Select the terminal UI for this run: the persistent Ink UI on an interactive TTY (dynamically imported\n * so React never loads on the non-TTY / `--json` / MCP chunks), else the plain {@link DevRenderer}\n * (returned as `undefined` so the runner constructs its own default). `--json`/MCP always forces plain \u2014\n * Ink must never seize a machine-readable stream.\n *\n * {@link PersistentInkDevUi} covers both shapes of session, branching at {@link DevUi.ready} on whether a\n * UI child owns the TTY, so there is nothing left to gate on here.\n */\nconst selectDevUi = async (options: DevServerOptions): Promise<DevUi | undefined> => {\n const isTTY = options.tty ?? Boolean(process.stdout.isTTY)\n\n if (!isTTY || options.json) {\n return undefined\n }\n\n const { PersistentInkDevUi } = await import('src/tui/dev-ui/persistent-ink-dev-ui')\n\n return new PersistentInkDevUi({ appendLog: appendLogFile, verbose: options.verbose ?? false })\n}\n\n/**\n * Construct a {@link DevServerRunner}, start it, and return the instance so the caller\n * (the CLI entry point) can wire signal handlers to `shutdown()` and own process exit.\n * Selects the boot UI (Ink on a TTY, plain otherwise) before constructing the runner.\n */\nexport async function run(options: DevServerOptions = {}): Promise<DevServerRunner> {\n const renderer = await selectDevUi(options)\n // Positions: options, runBuild, turboWatchFactory, uiDevFactory, dryRunner, renderer \u2014 pass `undefined`\n // for the injectable seams so their ctor defaults apply; only the renderer is chosen here.\n const runner = new DevServerRunner(options, undefined, undefined, undefined, undefined, renderer)\n\n // Unwind a partial boot. By the time `start()` can reject (e.g. a UI alias portless refuses), the\n // backends are already listening, aliased in the portless daemon, and recorded as dev-context\n // fragments on disk. The caller wires `shutdown()` to signals only AFTER this resolves, so without\n // this the process exits leaving external daemon state and on-disk fragments behind \u2014 the next\n // `vite dev` then proxies at an alias nothing serves. Teardown failures must not mask the original\n // boot error, so they are swallowed.\n try {\n await runner.start()\n } catch (error) {\n await runner.shutdown().catch(() => {})\n throw error\n }\n\n return runner\n}\n", "/**\n * Dependency-closure map for scoped `infra-kit dev --watch` restarts (plan Phase 1, Option B).\n *\n * `pnpm` is resolved from PATH \u2014 the same trust posture as the rest of the dev-server, which\n * already shells out to `pnpm exec turbo \u2026`; the args are fixed literals plus discovered package\n * names, never shell-interpolated.\n *\n * Rather than re-derive the workspace graph in-process, infra-kit asks turbo what it would\n * rebuild: `turbo run build --dry=json --filter=...<pkg>` lists every package in an app's build\n * closure (`tasks[].package`). That set is authoritative \u2014 it is exactly what the running\n * `turbo watch build` engine rebuilds \u2014 so there is no drift and no glob-parser/BFS/cycle-guard\n * to maintain here. The runner inverts these per-app closures into `dependentsByPackageDir`\n * (which app folders depend on each `packages/<x>/dist`) and, on a package-dist change, restarts\n * only the dependent backends instead of every one.\n */\nimport { execFile } from 'node:child_process'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { promisify } from 'node:util'\n\nimport { getPackageDistDirs } from './discovery.js'\n\nconst execFileAsync = promisify(execFile)\n\n/** The subset of an app's identity the closure map needs. */\nexport interface ClosureApp {\n /** App folder name (e.g. `client`) \u2014 the restart key in the dependents map. */\n name: string\n /** package.json `name` (e.g. `sls-trvl-client`) \u2014 the turbo `--filter` target. */\n packageName: string\n}\n\n/**\n * Resolve one app's build closure: the set of workspace package **names** turbo would rebuild\n * for it. Injectable so tests drive `buildClosureMap` without spawning a real turbo. A rejection\n * propagates through `Promise.all` so the caller can apply its restart-all fail-safe.\n */\nexport type DryRunner = (packageName: string) => Promise<string[]>\n\n/** The inverted closure: `packages/<x>/dist` \u2192 the app folders that depend on it, plus the dir\u2192name bridge. */\nexport interface ClosureMap {\n /** `packages/<x>/dist` absolute dir \u2192 set of app folder names whose closure includes it. */\n dependentsByPackageDir: Map<string, Set<string>>\n /** `packages/<x>/dist` absolute dir \u2192 that package's package.json `name` (dir \u2260 name for scoped pkgs). */\n packageNameByDir: Map<string, string>\n}\n\n/** Read `<dir>/package.json` `name`, or `undefined` when absent/unreadable (never throws). */\nconst readPackageName = (dir: string): string | undefined => {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8')) as { name?: unknown }\n\n return typeof parsed.name === 'string' ? parsed.name : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Default {@link DryRunner}: `turbo run build --dry=json --filter=...<pkg>` in `root`, returning the\n * unique `tasks[].package` set (the packages turbo would rebuild for `<pkg>`). `...` includes the\n * app's own dependency closure, mirroring the `turbo watch build` filter.\n */\nexport const defaultDryRunner = (root: string): DryRunner => {\n return async (packageName: string): Promise<string[]> => {\n const { stdout } = await execFileAsync(\n 'pnpm',\n ['exec', 'turbo', 'run', 'build', '--dry=json', `--filter=...${packageName}`],\n { cwd: root, maxBuffer: 32 * 1024 * 1024 },\n )\n const parsed = JSON.parse(stdout) as { tasks?: Array<{ package?: unknown }> }\n const names = (parsed.tasks ?? [])\n .map((t) => {\n return t.package\n })\n .filter((p): p is string => {\n return typeof p === 'string'\n })\n\n return [...new Set(names)]\n }\n}\n\n/** Build `packageNameByDir` (and its reverse) for every existing `packages/<x>/dist` under `root`. */\nconst readPackageDirs = (root: string): { packageNameByDir: Map<string, string>; dirByName: Map<string, string> } => {\n const packageNameByDir = new Map<string, string>()\n const dirByName = new Map<string, string>()\n\n for (const distDir of getPackageDistDirs(root)) {\n const name = readPackageName(path.dirname(distDir))\n\n if (name !== undefined) {\n packageNameByDir.set(distDir, name)\n dirByName.set(name, distDir)\n }\n }\n\n return { packageNameByDir, dirByName }\n}\n\n/**\n * Ask turbo for each app's build closure (in parallel) and invert it into {@link ClosureMap}:\n * `packages/<x>/dist` \u2192 the app folders whose closure includes that package. Apps' own packages\n * (not under `packages/`) simply don't appear in the map, so they never scope a package restart.\n *\n * A {@link DryRunner} rejection propagates (via `Promise.all`) so the caller falls back to\n * restart-all \u2014 the map is never silently partial.\n *\n * @example\n * // appX deps pkgA+pkgC; appY deps pkgC only \u2192\n * // dependentsByPackageDir: { '\u2026/pkgA/dist': {appX}, '\u2026/pkgC/dist': {appX, appY} }\n */\nexport const buildClosureMap = async (\n root: string,\n apps: ClosureApp[],\n dryRunner: DryRunner = defaultDryRunner(root),\n): Promise<ClosureMap> => {\n const { packageNameByDir, dirByName } = readPackageDirs(root)\n const dependentsByPackageDir = new Map<string, Set<string>>()\n\n const closures = await Promise.all(\n apps.map(async (app) => {\n return { app, closure: await dryRunner(app.packageName) }\n }),\n )\n\n for (const { app, closure } of closures) {\n for (const pkgName of closure) {\n const distDir = dirByName.get(pkgName)\n\n if (distDir === undefined) continue\n\n const dependents = dependentsByPackageDir.get(distDir) ?? new Set<string>()\n\n dependents.add(app.name)\n dependentsByPackageDir.set(distDir, dependents)\n }\n }\n\n return { dependentsByPackageDir, packageNameByDir }\n}\n\n/** Debounce key for a package-dist change \u2014 distinct per package dir so unrelated packages never collapse into one bucket. */\nexport const packageDebounceKey = (packageDir: string): string => {\n return `__pkg__:${packageDir}`\n}\n\n/**\n * Decide which launched apps a `packages/<pkg>/dist` change should restart. `null` means\n * \"fail-safe: restart all\" \u2014 the closure map is missing (a `--dry` failure) or the change carried\n * no package identity. Otherwise the participating (`watchDeps`) apps whose closure includes that\n * package dir \u2014 possibly an empty array (the package is UI-only, or every dependent opted out),\n * which the caller treats as \"restart nothing\".\n *\n * @example\n * // closure: pkgA \u2192 {appX}; pkgC \u2192 {appX, appY}\n * selectPackageRestartTargets([appX, appY], map, '\u2026/pkgA/dist') // => [appX] (not appY)\n * selectPackageRestartTargets([appX, appY], map, '\u2026/uiOnly/dist') // => [] (restart nothing)\n * selectPackageRestartTargets([appX, appY], null, '\u2026/pkgA/dist') // => null (fail-safe: restart all)\n */\nexport const selectPackageRestartTargets = <T extends { name: string; watchDeps: boolean }>(\n apps: T[],\n closureMap: ClosureMap | null,\n packageDir: string | undefined,\n): T[] | null => {\n if (closureMap === null || packageDir === undefined) return null\n\n const dependents = closureMap.dependentsByPackageDir.get(packageDir) ?? new Set<string>()\n\n return apps.filter((a) => {\n return a.watchDeps && dependents.has(a.name)\n })\n}\n", "/**\n * Pure port / URL-prefix resolution for the dev-server.\n *\n * These functions are intentionally side-effect free: the environment is passed\n * in (never read from `process.env` here) and no cwd / fs access happens. That\n * keeps port precedence and conflict detection unit-testable in isolation.\n */\nimport type { DevConfig } from '../lib/infra-kit-config/index.js'\n\n/** Fallback port when no PORT / {APP}_PORT env var and no config port is set. */\nexport const DEFAULT_PORT = 3010\n\n/**\n * URL prefix applied to each app's routes when nothing is configured. Overridable\n * per app via `dev.<app>.prefixUrl` in infra-kit.json \u2014 this is only the fallback.\n */\nexport const DEFAULT_PREFIX_URL = '/api/v1'\n\n/**\n * Parse a raw port string (env var or config), stripping a single pair of\n * surrounding quotes and treating blank / non-numeric input as \"unset\".\n */\nexport function parsePortString(raw: string | undefined): number | undefined {\n if (raw == null || raw === '') {\n return undefined\n }\n\n const n = parseInt(raw.trim().replace(/^[\"']|[\"']$/g, ''), 10)\n\n return Number.isNaN(n) ? undefined : n\n}\n\n/**\n * Resolve the PORT for an API app (highest priority first):\n *\n * 1. **`{APP}_PORT`** \u2014 e.g. `CLIENT_PORT`, `SEARCH_ENGINE_PORT` (secrets manager or shell)\n * 2. **`PORT`** \u2014 shared fallback (multi-app: use distinct `{APP}_PORT` in env)\n * 3. **`dev.<app>.port`** from infra-kit.json\n * 4. Default {@link DEFAULT_PORT}\n *\n * Per-app env keys use the app folder name in **UPPER_SNAKE_CASE** (hyphens \u2192 underscores).\n */\nexport function resolvePort(appName: string, env: NodeJS.ProcessEnv, devConfig: DevConfig): number {\n return resolvePreferredPort(appName, env, devConfig) ?? DEFAULT_PORT\n}\n\n/**\n * Resolve the EXPLICITLY-configured port for an API app \u2014 `{APP}_PORT`, then `PORT`, then\n * `dev.<app>.port` \u2014 or `undefined` when none is set. Unlike {@link resolvePort} this does\n * NOT fall back to {@link DEFAULT_PORT}: it distinguishes an app the developer pinned to a\n * port (a preferred bind target) from an unconfigured app (which binds ephemeral straight\n * away under dynamic allocation). Used by the dev-server to (a) pick the preferred bind\n * port and (b) relax the conflict gate to explicit ports only.\n */\nexport function resolvePreferredPort(\n appName: string,\n env: NodeJS.ProcessEnv,\n devConfig: DevConfig,\n): number | undefined {\n const prefix = appName.replace(/-/g, '_').toUpperCase()\n const prefixedKey = `${prefix}_PORT`\n\n const fromPrefixed = parsePortString(env[prefixedKey])\n\n if (fromPrefixed != null) {\n return fromPrefixed\n }\n\n const fromPort = parsePortString(env.PORT)\n\n if (fromPort != null) {\n return fromPort\n }\n\n return devConfig[appName]?.port ?? undefined\n}\n\n/**\n * Resolve the URL prefix for an API app: `dev.<app>.prefixUrl` from\n * infra-kit.json, falling back to {@link DEFAULT_PREFIX_URL} (`/api/v1`).\n */\nexport function resolvePrefixUrl(appName: string, devConfig: DevConfig): string {\n return devConfig[appName]?.prefixUrl ?? DEFAULT_PREFIX_URL\n}\n\n/** The apps that collide on a port, plus the raw duplicate-port list for messaging. */\nexport interface PortConflicts {\n /** Ports that appear more than once (one entry per extra occurrence, in scan order). */\n duplicatePorts: number[]\n /** Every app whose resolved port is one of the duplicates. */\n conflictingApps: Array<{ name: string; port: number }>\n}\n\n/**\n * Find apps that resolve to the same port. Returns empty `duplicatePorts` when\n * there is no conflict; the caller decides how to surface / throw.\n */\nexport function findPortConflicts(apps: Array<{ name: string; port: number }>): PortConflicts {\n const ports = apps.map((a) => {\n return a.port\n })\n const duplicatePorts = ports.filter((port, index) => {\n return ports.indexOf(port) !== index\n })\n const conflictingApps = apps.filter((a) => {\n return duplicatePorts.includes(a.port)\n })\n\n return { duplicatePorts, conflictingApps }\n}\n", "import { Logger } from '@aws-lambda-powertools/logger'\nimport type { APIGatewayProxyEvent, APIGatewayProxyEventQueryStringParameters, Context } from 'aws-lambda'\nimport fastify from 'fastify'\nimport * as fs from 'node:fs'\nimport type { Server } from 'node:http'\nimport * as path from 'node:path'\nimport process from 'node:process'\nimport { pathToFileURL } from 'node:url'\nimport { parse as parseYaml } from 'yaml'\n\nimport type { ILogger } from './interfaces.js'\n\nexport interface IServerConfig {\n controllersPath: string\n prefixUrl?: string\n /**\n * PREFERRED bind port. A number is tried first and, on `EADDRINUSE`, the runner falls\n * back to an ephemeral `listen(0)` port. `undefined` (no explicit config) binds an\n * ephemeral port straight away. After {@link ServerlessLocalRun.start} the field is\n * mutated in place to the ACTUAL bound port so `/__health` reports the real port.\n */\n port?: number\n /** App folder name, surfaced by the `/__health` endpoint. Optional. */\n appName?: string\n /**\n * Structured per-request sink. When provided, every response emits `{ method, path, status, ms }`\n * (path already trimmed of any query string) so the caller can tag + timestamp it \u2014 the dev-server\n * routes this into its renderer's live tail. Independent of the env-gated raw stdout line below.\n */\n onRequestLog?: (event: { method: string; path: string; status: number; ms: number }) => void\n}\n\n/** True when a listen error is a port-already-in-use (`EADDRINUSE`) failure. */\nconst isAddressInUse = (error: unknown): boolean => {\n return (error as { code?: string } | null)?.code === 'EADDRINUSE'\n}\n\ntype HandlerResult = Promise<{ body: string; headers: Record<string, string>; statusCode: number }>\n\n/** Default simulated Lambda timeout; overridable via `DEV_SERVER_TIMEOUT_MS`. */\nconst DEFAULT_LAMBDA_TIMEOUT_MS = 30_000\n\n/** Resolve the simulated Lambda timeout (ms), honoring `DEV_SERVER_TIMEOUT_MS` when it parses. */\nconst resolveLambdaTimeoutMs = (): number => {\n const raw = Number.parseInt(process.env.DEV_SERVER_TIMEOUT_MS ?? '', 10)\n\n return Number.isNaN(raw) ? DEFAULT_LAMBDA_TIMEOUT_MS : raw\n}\n\n/**\n * Whether to emit a one-line `<method> <url> \u2192 <status> <ms>ms` log per request.\n * Off by default; opt in with `DEV_SERVER_REQUEST_LOG=1` (kept out of the Powertools\n * JSON logger so the line stays terminal-readable). Mirrors the env-reader precedent\n * of {@link resolveLambdaTimeoutMs}.\n */\nconst isRequestLogEnabled = (): boolean => {\n return process.env.DEV_SERVER_REQUEST_LOG === '1'\n}\n\nexport class ServerlessLocalRun {\n /** Busts Node ESM `import()` cache on each new server instance (watch restart). */\n private readonly importCacheBust: string\n private readonly logger: Logger\n private readonly server: ReturnType<typeof fastify>\n private readonly controllers: Record<\n string,\n {\n action: Record<string, (event: APIGatewayProxyEvent, ctx: Context, log: ILogger) => HandlerResult>\n handler: string\n }\n > = {}\n\n /** `method urlAction` keys reserved synchronously, so duplicates are caught before any async import. */\n private readonly registeredRouteKeys = new Set<string>()\n\n constructor(private readonly serverConfig: IServerConfig) {\n this.importCacheBust = `${Date.now()}`\n this.logger = new Logger({ serviceName: 'LocalServer', logLevel: 'DEBUG' })\n this.serverConfig.prefixUrl = this.serverConfig.prefixUrl ?? ''\n this.server = fastify({ logger: false })\n\n // Add CORS support for local development\n this.server.addHook(\n 'onRequest',\n async (\n request: { method: string },\n reply: { header: (k: string, v: string) => unknown; status: (n: number) => { send: () => void } },\n ) => {\n reply.header('Access-Control-Allow-Origin', '*')\n reply.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS')\n reply.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With')\n\n // Handle preflight OPTIONS requests\n if (request.method === 'OPTIONS') {\n reply.status(204).send()\n }\n },\n )\n\n // Per-request visibility for live dev traffic. Two independent sinks: a structured\n // `onRequestLog` callback (the dev-server tags + timestamps it in its tail) and/or the\n // legacy env-gated raw stdout line (standalone use). Registered only when at least one is on.\n const onRequestLog = this.serverConfig.onRequestLog\n\n if (isRequestLogEnabled() || onRequestLog) {\n this.server.addHook(\n 'onResponse',\n async (request: { method: string; url: string }, reply: { statusCode: number; elapsedTime: number }) => {\n const ms = Math.round(reply.elapsedTime)\n // Trim the query string so the tail shows a clean route, not `?a=b` noise.\n const requestPath = request.url.split('?')[0] ?? request.url\n\n onRequestLog?.({ method: request.method, path: requestPath, status: reply.statusCode, ms })\n if (isRequestLogEnabled()) {\n process.stdout.write(`${request.method} ${request.url} \u2192 ${reply.statusCode} ${ms}ms\\n`)\n }\n },\n )\n }\n }\n\n /**\n * The registered `METHOD /path` route keys (sorted). Excludes the internal\n * `/__health` liveness route, which is registered outside {@link defineRoute} and\n * never added to {@link registeredRouteKeys}. Used by the runner's startup route dump.\n */\n public getRegisteredRoutes(): string[] {\n return [...this.registeredRouteKeys].sort()\n }\n\n /**\n * Boot the server and RETURN the actual bound port. Under dynamic allocation the port is\n * a runtime fact known only after `listen`, so the caller must consume this return value\n * (never the static config port). {@link serverConfig.port} is mutated in place to the\n * bound port so `/__health` reports the real value.\n */\n public async start(): Promise<number> {\n this.registerHealthRoute()\n\n await Promise.all(this.loadRoutes())\n\n const boundPort = await this.listenWithFallback()\n\n this.serverConfig.port = boundPort\n\n this.logger.info(`Server listening on http://127.0.0.1:${boundPort}`, {\n address: `http://127.0.0.1:${boundPort}`,\n })\n\n return boundPort\n }\n\n /**\n * Bind the server and return the ACTUAL bound port. With an explicitly-configured\n * preferred port, try it first and fall back to an ephemeral `listen(0)` on `EADDRINUSE`\n * so extra worktrees never collide; with no preferred port (`undefined`), bind ephemeral\n * straight away. Non-`EADDRINUSE` errors propagate.\n */\n private async listenWithFallback(): Promise<number> {\n const preferred = this.serverConfig.port\n\n if (preferred != null) {\n try {\n await this.server.listen({ port: preferred, host: '127.0.0.1' })\n\n return this.readBoundPort()\n } catch (error) {\n if (!isAddressInUse(error)) {\n throw error\n }\n }\n }\n\n await this.server.listen({ port: 0, host: '127.0.0.1' })\n\n return this.readBoundPort()\n }\n\n /** Read the concrete bound port from the underlying HTTP server after `listen`. */\n private readBoundPort(): number {\n const address = (this.server.server as Server).address()\n\n if (address == null || typeof address === 'string') {\n throw new Error('Server address unavailable after listen()')\n }\n\n return address.port\n }\n\n /** Close the server (for watch/restart). */\n public async close(): Promise<void> {\n const raw = this.server.server as Server\n\n if (typeof raw.closeAllConnections === 'function') {\n raw.closeAllConnections()\n }\n await this.server.close()\n }\n\n /**\n * Register a fixed `GET /__health` liveness route returning 200. The path is\n * unprefixed (serverless.yml routes carry the `prefixUrl`, e.g. `/api/v1/...`),\n * so it never collides with a handler route.\n */\n private registerHealthRoute(): void {\n this.server.route({\n method: 'GET',\n url: '/__health',\n handler: (_request: unknown, reply: { code: (n: number) => { send: (body: unknown) => void } }) => {\n return reply.code(200).send({\n status: 'ok',\n app: this.serverConfig.appName ?? null,\n port: this.serverConfig.port,\n })\n },\n })\n }\n\n private loadRoutes(): Promise<void>[] {\n const serverlessYmlPath = path.join(this.serverConfig.controllersPath, 'serverless.yml')\n const fileContents = fs.readFileSync(serverlessYmlPath, 'utf8')\n const data = parseYaml(fileContents) as {\n functions: Record<string, { events?: Array<{ http?: { method: string; path: string } }>; handler?: string }>\n }\n const p: Promise<void>[] = []\n\n if (!data?.functions) return p\n\n for (const funcDef of Object.values(data.functions)) {\n if (!funcDef?.events?.length) continue\n for (const element of funcDef.events) {\n const http = element?.http\n\n if (!http) continue\n p.push(this.defineRoute(http, funcDef))\n }\n }\n\n return p\n }\n\n private async defineRoute(http: { method: string; path: string }, funcDef: { handler?: string }): Promise<void> {\n let url = http.path.toString()\n\n url = url.replaceAll('{', ':').replaceAll('}', '')\n\n let urlAction = path.posix.join(this.serverConfig.prefixUrl ?? '', url)\n\n urlAction = urlAction[0] === '/' ? urlAction : `/${urlAction}`\n\n const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']\n const method = String(http.method).toUpperCase()\n\n if (!validMethods.includes(method)) {\n throw new Error(`Invalid HTTP method: \"${http.method}\" for URL: ${urlAction}`)\n }\n\n // Key on method + path: two events can share a path but differ by method (e.g. GET/POST /users),\n // so keying on the path alone would let one handler overwrite the other. Reserve the key\n // synchronously (before the first `await`) so concurrent route loads detect a true duplicate.\n const routeKey = `${method} ${urlAction}`\n\n if (this.registeredRouteKeys.has(routeKey)) {\n throw new Error(`Duplicate route: ${routeKey}`)\n }\n this.registeredRouteKeys.add(routeKey)\n\n const handlerStr = funcDef.handler ?? ''\n const parts = handlerStr.split('.')\n const filepath = parts[0] ?? ''\n const handler = parts[1] ?? ''\n\n const controllerPath = path.join(this.serverConfig.controllersPath, `${filepath}.js`)\n const fileUrl = pathToFileURL(controllerPath)\n\n // Search params bust Node's ESM import cache so watch rebuilds load new `dist` output.\n fileUrl.searchParams.set('v', this.importCacheBust)\n const action = (await import(fileUrl.href)) as Record<\n string,\n (event: APIGatewayProxyEvent, ctx: Context, log: ILogger) => HandlerResult\n >\n\n this.controllers[routeKey] = { action, handler }\n\n const traceLogger = this.logger.createChild({ serviceName: 'RequestLogger' })\n\n this.server.route({\n method: method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS',\n url: urlAction,\n handler: async (\n request: { body?: unknown; query?: unknown; params?: unknown; headers?: unknown },\n reply: {\n headers: (h: Record<string, string>) => unknown\n code: (n: number) => { send: (body: unknown) => void }\n },\n ) => {\n const controller = this.controllers[routeKey]\n\n if (!controller) throw new Error(`No controller for ${routeKey}`)\n const handlerFn = controller.action[controller.handler]\n\n if (!handlerFn) throw new Error(`No handler ${controller.handler} for ${urlAction}`)\n const retVal = await handlerFn(\n this.getEventObj(request.body, request.query, request.params, request.headers, method, urlAction),\n this.getContext(),\n traceLogger,\n )\n const responseBody = JSON.parse(retVal.body)\n\n reply.headers(retVal.headers ?? {})\n\n return reply.code(retVal?.statusCode ?? 500).send(responseBody)\n },\n })\n }\n\n private getEventObj(\n requestBody?: unknown,\n queryParams?: unknown,\n pathParameters?: unknown,\n headers?: unknown,\n httpMethod = '',\n path = '',\n ): APIGatewayProxyEvent {\n const retVal = {\n body: requestBody ? JSON.stringify(requestBody) : null,\n headers: (headers ?? {}) as APIGatewayProxyEvent['headers'],\n multiValueHeaders: {},\n httpMethod,\n isBase64Encoded: false,\n path,\n pathParameters: pathParameters ?? null,\n queryStringParameters: (queryParams as APIGatewayProxyEventQueryStringParameters) ?? null,\n multiValueQueryStringParameters: null,\n stageVariables: null,\n requestContext: {\n accountId: '',\n apiId: '',\n authorizer: undefined,\n protocol: '',\n httpMethod,\n identity: {\n accessKey: null,\n accountId: null,\n apiKey: null,\n apiKeyId: null,\n caller: null,\n clientCert: null,\n cognitoAuthenticationProvider: null,\n cognitoAuthenticationType: null,\n cognitoIdentityId: null,\n cognitoIdentityPoolId: null,\n principalOrgId: null,\n sourceIp: 'devIp',\n user: null,\n userAgent: null,\n userArn: null,\n },\n path,\n stage: '',\n requestId: '',\n requestTimeEpoch: 0,\n resourceId: '',\n resourcePath: path,\n },\n resource: path,\n }\n\n ;(retVal as APIGatewayProxyEvent & { source: string }).source = 'aws.events'\n\n return retVal as APIGatewayProxyEvent\n }\n\n private getContext(): Context {\n const startTime = Date.now()\n const timeoutMs = resolveLambdaTimeoutMs()\n const datePart = new Date().toISOString().split('T')[0] ?? ''\n\n return {\n callbackWaitsForEmptyEventLoop: false,\n functionName: 'local-dev',\n functionVersion: '1.0.0',\n invokedFunctionArn: 'arn:aws:lambda:local:000000000000:function:local-dev',\n memoryLimitInMB: '1024',\n awsRequestId: `local-${Date.now()}`,\n logGroupName: '/aws/lambda/local-dev',\n logStreamName: `${datePart}/local`,\n getRemainingTimeInMillis: (): number => {\n return Math.max(0, timeoutMs - (Date.now() - startTime))\n },\n done: (_error?: Error, _result?: unknown): void => {},\n fail: (_error: string | Error): void => {},\n succeed: (_messageOrObject: unknown): void => {},\n }\n }\n}\n", "/*\n * `pnpm` is resolved from PATH \u2014 the same trust posture as the rest of the dev-server, which\n * already shells out to `pnpm exec turbo \u2026`; the args are fixed literals plus discovered package\n * names, never shell-interpolated. Matches the file-level disable in `scripts/build.js`.\n */\n/* eslint-disable sonarjs/no-os-command-from-path */\n/**\n * `turbo watch build` engine for `infra-kit dev --watch`.\n *\n * Spawns ONE long-lived `turbo watch build` child that owns incremental rebuilds and\n * dependency-graph fan-out. Its stdout is teed to the runner log but NEVER parsed for\n * control flow: piped `turbo watch` block-buffers stdout and emits no per-task completion\n * marker for `tsc -b` tasks, so the dev-server derives \"a build finished\" from watching\n * `dist/` output instead (see {@link file://./dev-server.ts} `setupWatch`).\n *\n * The child is a 5-deep tree (`sh \u2192 pnpm \u2192 node \u2192 turbo \u2192 native binary`); killing the\n * wrapper PID orphans the rest. So it is spawned `detached` (its own process group) and\n * torn down with a process-GROUP signal \u2014 verified to fully reap the tree.\n */\nimport { spawn } from 'node:child_process'\nimport * as fs from 'node:fs'\n\nimport { superviseChild } from './managed-child.js'\nimport type { ManagedChild, UnexpectedExitHandler } from './managed-child.js'\n\n/** Handle to the running `turbo watch` child; `kill()` reaps the whole process group. */\nexport type TurboWatchHandle = ManagedChild\n\n/** Injectable spawn seam so tests run the orchestrator without a real turbo child. */\nexport type TurboWatchFactory = (opts: TurboWatchOptions) => TurboWatchHandle\n\nexport interface TurboWatchOptions {\n /**\n * API app package names, watched DEPENDENCY-INCLUSIVE (`--filter=...<pkg>`): rebuild the app AND its\n * dependency closure, so editing a shared lib the backend uses triggers a rebuild + restart.\n */\n depInclusive: string[]\n /**\n * UI app package names, watched DEP-CLOSURE-ONLY (`--filter=<pkg>^...`): rebuild the frontend's shared-lib\n * dependencies but NEVER the UI's own `build` (that would run a production `vite build` \u2014 the UI's live\n * dev is owned by the separate `turbo run dev`/vite child). Covers FE-only libs + UI-only sessions.\n */\n depClosure: string[]\n /** Consumer repo cwd the child runs in (turbo resolves the consumer's own pin here). */\n cwd: string\n /** Runner log file; the child's stdout+stderr are appended to it. */\n logFile: string\n /**\n * Called if the engine dies on its own (not via `kill()`): incremental rebuilds silently stop, so\n * the runner surfaces it. Optional so the injected test factory can ignore it.\n */\n onUnexpectedExit?: UnexpectedExitHandler\n}\n\n/**\n * Build the `--filter=` arg vector: `...<pkg>` (dependency-inclusive) for each API package, `<pkg>^...`\n * (dependencies only, excluding the package itself) for each UI package. Pure/order-stable so the exact\n * emitted args are unit-testable; API-only input reproduces the historical `--filter=...<pkg>` vector.\n *\n * @example\n * buildTurboWatchFilters(['api-a'], ['ui-a']) // => ['--filter=...api-a', '--filter=ui-a^...']\n */\nexport const buildTurboWatchFilters = (depInclusive: string[], depClosure: string[]): string[] => {\n return [\n ...depInclusive.map((name) => {\n return `--filter=...${name}`\n }),\n ...depClosure.map((name) => {\n return `--filter=${name}^...`\n }),\n ]\n}\n\n/**\n * Default factory: spawn `pnpm exec turbo watch build <filters> --continue=dependencies-successful\n * --env-mode=loose` detached, tee output to `logFile`, and reap the process group on `kill()`.\n *\n * Filters come from {@link buildTurboWatchFilters}: `...<api>` (dep-inclusive) rebuilds a backend + its\n * closure; `<ui>^...` (dep-closure-only) rebuilds the frontend's shared libs without production-building\n * the UI. `--continue=dependencies-successful` keeps the watcher alive when one package fails to compile,\n * so a shared-lib type error never tears down the whole engine and the last-good `dist/` keeps serving.\n */\nexport const defaultTurboWatchFactory: TurboWatchFactory = ({\n depInclusive,\n depClosure,\n cwd,\n logFile,\n onUnexpectedExit,\n}) => {\n const filters = buildTurboWatchFilters(depInclusive, depClosure)\n const out = fs.openSync(logFile, 'a')\n const child = spawn(\n 'pnpm',\n ['exec', 'turbo', 'watch', 'build', ...filters, '--continue=dependencies-successful', '--env-mode=loose'],\n { cwd, detached: true, stdio: ['ignore', out, out] },\n )\n\n return superviseChild(child, undefined, onUnexpectedExit)\n}\n", "/*\n * `pnpm` is resolved from PATH \u2014 same trust posture as the rest of the dev-server (which already\n * shells out to `pnpm exec turbo \u2026`); args are fixed literals plus discovered package names, never\n * shell-interpolated. Matches the file-level disable in `scripts/build.js`.\n */\n/* eslint-disable sonarjs/no-os-command-from-path */\n/**\n * Frontend dev engine for `infra-kit dev --ui`.\n *\n * Delegates FE to ONE `turbo run dev` child (turbo owns the `dev` fan-out and concurrency) rather than\n * infra-kit spawning each framework itself \u2014 the same delegation choice already made for builds via\n * `turbo watch`. infra-kit treats UIs opaquely: it runs their `dev` script (vite/vike/astro/\u2026) and\n * never encodes per-framework knowledge.\n *\n * This child's stdio is PIPED, never inherited. With `inherit`, turbo and the framework write straight\n * to the TTY, bypassing the renderer entirely: turbo's run chrome interleaves with the pinned footer,\n * and vite's `Local:` URL contradicts the proxy hero URL the ready header already shows. Piping makes\n * `infra-kit dev` the single owner of the terminal \u2014 every line is tee'd verbatim to the runner log,\n * and the framework's own lines are routed through `onLine` into the renderer's tagged tail, so vite's\n * URLs, HMR notices and compile errors surface INSIDE the UI instead of fighting it.\n *\n * `--only` keeps that tail signal-dense. A `dev` task `dependsOn: [\"^build\"]`, so turbo would otherwise\n * re-walk the whole dependency closure and emit one `cache hit` line per dep \u2014 work the runner already\n * did in `buildUiApps` (`turbo run build <pkg>^...`) before spawning this child. `--only` drops those\n * `^build` tasks from the graph, so the redundant walk never happens. `--output-logs=new-only` collapses\n * any stray cache-hit replay, `--no-update-notifier` drops turbo's \"Update available\" banner, and\n * `--ui=stream` pins line-oriented output (turbo picks it anyway off a pipe, but it is cheap to be\n * explicit, and it is what `parseTurboDevLine`'s `<pkg>:dev:` prefix contract depends on).\n *\n * Detached \u2192 its own process group; reaped as a group (SIGTERM\u2192SIGKILL) via {@link superviseChild}.\n */\nimport { spawn } from 'node:child_process'\nimport process from 'node:process'\nimport type { Readable } from 'node:stream'\n\nimport { superviseChild } from './managed-child.js'\nimport type { ManagedChild, UnexpectedExitHandler } from './managed-child.js'\n\n/** Handle to the running `turbo run dev` child; `kill()` reaps the whole process group. */\nexport type UiDevHandle = ManagedChild\n\n/** Injectable spawn seam so tests run the orchestrator without a real turbo child. */\nexport type UiDevFactory = (opts: UiDevOptions) => UiDevHandle\n\n/** One framework output line, already stripped of turbo's `<pkg>:dev:` prefix. */\nexport interface TurboDevLine {\n /** The turbo package name that emitted the line (e.g. `website-ui`). */\n pkg: string\n /** The framework's own text, ANSI-stripped. */\n text: string\n}\n\nexport interface UiDevOptions {\n /** UI app package names; each becomes an exact `--filter=<pkg>` (turbo runs its `dev` task). */\n packageNames: string[]\n /** Consumer repo cwd the child runs in. */\n cwd: string\n /**\n * Concurrency cap. Must be \u2265 the number of selected UI `dev` tasks (they're persistent) \u2014\n * turbo hard-errors when persistent tasks exceed concurrency (default 10).\n */\n concurrency: number\n /**\n * Extra env merged over `process.env` for the turbo child (Layer B passes `INFRA_KIT_UI_PORTS`).\n * `turbo \u2026 --env-mode=loose` passes the full env through to each vite `dev` task. Omit \u2192 inherit only.\n */\n env?: Record<string, string>\n /** Raw child output (turbo chrome included, ANSI intact) appended verbatim to the runner log. */\n appendLog?: (text: string) => void\n /** One call per framework output line; turbo's own chrome is filtered out first. */\n onLine?: (line: TurboDevLine) => void\n /**\n * Called if the child dies on its own (not via `kill()`): every UI's live reload silently stops, so\n * the runner surfaces it. Optional so the injected test factory can ignore it.\n */\n onUnexpectedExit?: UnexpectedExitHandler\n}\n\n/* eslint-disable no-control-regex, sonarjs/no-control-regex -- terminal escapes and control chars are, by definition, control chars. */\n/** An OSC sequence: ESC `]` ... terminated by BEL or ST. Frameworks emit these for terminal hyperlinks. */\nconst OSC_ESCAPE = /\\u001B\\][\\s\\S]*?(?:\\u0007|\\u001B\\\\)/g\n/** A CSI escape: ESC `[`, parameter bytes, intermediate bytes, final byte. Covers every SGR colour. */\nconst CSI_ESCAPE = /\\u001B\\[[0-9;?]*[\\u0020-\\u002F]*[\\u0040-\\u007E]/g\n/**\n * Every remaining C0 control char except TAB. CR is the dangerous one: written straight to a TTY it\n * snaps the cursor to column 0, smearing the row and the pinned footer below it - exactly the corruption\n * that piping the child was meant to end. LF cannot appear here: `pumpLines` already split on it.\n */\nconst CONTROL_CHARS = /[\\u0000-\\u0008\\u000B-\\u001F\\u007F]/g\n/* eslint-enable no-control-regex, sonarjs/no-control-regex */\n\n/**\n * Make a raw child line safe for the renderer's tail: drop terminal escapes (which would fight the\n * scroll region) and stray control chars (which would move the cursor), leaving plain text the renderer\n * styles itself. Order matters - OSC is matched before the bare-control sweep can eat its leading ESC.\n */\nexport const stripAnsi = (text: string): string => {\n return text.replace(OSC_ESCAPE, '').replace(CSI_ESCAPE, '').replace(CONTROL_CHARS, '')\n}\n\n/** Turbo's per-task line prefix under `--ui=stream`: `<pkg>:dev:`. */\nconst TASK_PREFIX = /^([^\\s:]+):dev:[ \\t]?/\n\n/**\n * Per-task bookkeeping turbo emits before the framework speaks: the cache verdict\n * (`cache bypass, force executing <hash>`) and the echoed command (`$ pnpm exec vike dev`).\n * Neither is dev signal \u2014 both are already implied by the app appearing in the ready header.\n */\nconst isTaskChrome = (text: string): boolean => {\n return /^cache (?:bypass|hit|miss)/.test(text) || text.startsWith('$ ')\n}\n\n/**\n * One raw turbo line \u2192 the framework line to surface, or `null` to drop it.\n *\n * Turbo's run chrome (`\u2022 Packages in scope: \u2026`, `\u2022 Running dev in 1 packages`, `\u2022 Remote caching\n * disabled`, the closing task summary, pnpm's `ELIFECYCLE` teardown) carries no `<pkg>:dev:` prefix.\n * Requiring that prefix drops all of it under one rule instead of chasing a brittle denylist, and what\n * survives is exactly the framework's own stdout/stderr \u2014 including its errors.\n *\n * @example\n * parseTurboDevLine('website-ui:dev: ready in 384 ms') // => { pkg: 'website-ui', text: 'ready in 384 ms' }\n * parseTurboDevLine('\u2022 Remote caching disabled') // => null\n */\nexport const parseTurboDevLine = (raw: string): TurboDevLine | null => {\n const line = stripAnsi(raw).trimEnd()\n const match = TASK_PREFIX.exec(line)\n\n if (match == null) {\n return null\n }\n\n const text = line.slice(match[0].length)\n\n if (text.trim() === '' || isTaskChrome(text.trim())) {\n return null\n }\n\n return { pkg: match[1]!, text }\n}\n\n/**\n * Cap for the newline-less carry buffer. A framework that renders progress with bare CR and never a LF\n * would otherwise grow `pending` without bound for the life of the dev session.\n */\nconst MAX_PENDING_CHARS = 64 * 1024\n\n/**\n * Split a piped stream into lines: tee every chunk verbatim to the log, and route each complete\n * framework line to `onLine`. A trailing partial line is flushed on `end`, so a framework that exits\n * without a final newline never swallows its last (often the most interesting) line.\n *\n * The `data` listener is attached unconditionally \u2014 NOT gated on `onLine`/`appendLog` being set. A piped\n * child whose stdout is never read blocks once the OS pipe buffer fills, so draining is the contract\n * here; the sinks are merely optional consumers of what we drain.\n */\nconst pumpLines = (stream: Readable | null, opts: Pick<UiDevOptions, 'appendLog' | 'onLine'>): void => {\n if (stream == null) {\n return\n }\n\n let pending = ''\n\n const emit = (raw: string): void => {\n const parsed = parseTurboDevLine(raw)\n\n if (parsed != null) opts.onLine?.(parsed)\n }\n\n stream.setEncoding('utf-8')\n stream.on('data', (chunk: string) => {\n opts.appendLog?.(chunk)\n pending += chunk\n\n const lines = pending.split('\\n')\n\n pending = lines.pop() ?? ''\n for (const raw of lines) {\n emit(raw)\n }\n\n if (pending.length > MAX_PENDING_CHARS) {\n emit(pending)\n pending = ''\n }\n })\n stream.on('end', () => {\n if (pending === '') return\n emit(pending)\n pending = ''\n })\n // A readable that emits `error` with no listener THROWS, taking the whole dev session down. The child's\n // lifecycle is already owned by `superviseChild`, so a read error (pty EIO, a pipe torn down mid-SIGKILL)\n // only needs recording, never escalation.\n stream.on('error', (err: Error) => {\n opts.appendLog?.(`[infra-kit] ui dev stream error: ${err.message}\\n`)\n })\n}\n\n/**\n * Default factory: spawn `pnpm exec turbo run dev --filter=<pkg> \u2026 --only` detached with piped stdio,\n * fan its output into the runner log + the renderer's tail, and reap the process group on `kill()`.\n *\n * Exact `--filter=<pkg>` (no `...`) selects only the UI packages, so an API app that also defines a\n * `dev` task is never picked up.\n */\nexport const defaultUiDevFactory: UiDevFactory = ({\n packageNames,\n cwd,\n concurrency,\n env,\n appendLog,\n onLine,\n onUnexpectedExit,\n}) => {\n const filters = packageNames.map((name) => {\n return `--filter=${name}`\n })\n const child = spawn(\n 'pnpm',\n [\n 'exec',\n 'turbo',\n 'run',\n 'dev',\n ...filters,\n `--concurrency=${concurrency}`,\n '--env-mode=loose',\n '--only',\n '--output-logs=new-only',\n '--no-update-notifier',\n '--ui=stream',\n ],\n { cwd, detached: true, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...env } },\n )\n\n pumpLines(child.stdout, { appendLog, onLine })\n pumpLines(child.stderr, { appendLog, onLine })\n\n return superviseChild(child, undefined, onUnexpectedExit)\n}\n"],
5
- "mappings": "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",
6
- "names": ["Command", "process", "pathToFileURL", "path", "process", "makeLeaf", "command", "buildNode", "commands", "depth", "leftCount", "left", "right", "buildCmuxLayout", "constants", "process", "execFileSync", "process", "DEFAULT_GRACE_MS", "POLL_MS", "REAP_TIMEOUT_MS", "PS_BIN", "parseProcRows", "raw", "line", "rawPid", "rawPpid", "rawPgid", "pid", "ppid", "pgid", "collectDoomedGroups", "rootPid", "rows", "excludePgid", "childrenOf", "row", "siblings", "groups", "seen", "queue", "i", "child", "snapshotProcRows", "isChildOf", "parentPid", "doomedGroupsOf", "ownPgid", "groupAlive", "signalGroups", "pgids", "signal", "killDescendantGroupsNow", "sleep", "ms", "resolve", "waitForExit", "timeoutMs", "deadline", "alive", "superviseChild", "graceMs", "onUnexpectedExit", "killing", "reportUnexpected", "detail", "code", "error", "doomed", "stragglers", "late", "forced", "HANDLED_SIGNALS", "SIGNAL_NUMBERS", "constants", "FALLBACK_SIGNO", "exitCodeForSignal", "signal", "defaultExit", "code", "process", "defaultRegister", "handler", "writeStderr", "message", "writeTeardownFailure", "error", "detail", "registerSignalShutdown", "onSignal", "exit", "register", "forceReap", "killDescendantGroupsNow", "firstSignal", "handle", "buildPaneCommands", "panes", "watch", "app", "targets", "paneTargetsByApp", "presetDef", "byApp", "key", "selectApiApps", "root", "include", "apps", "discoverApiApps", "logDevWorkspace", "ref", "logger", "registerShutdown", "registerSignalShutdown", "signal", "closeCmuxDevWorkspace", "runCmuxDevServer", "options", "findMonorepoRoot", "process", "normalizeAppInclude", "targetsByApp", "commands", "layout", "buildCmuxLayout", "title", "openCmuxDevWorkspace", "heartbeat", "process", "writeStderr", "message", "defaultOnFault", "event", "error", "detail", "defaultRegister", "handler", "registerCrashBarrier", "onFault", "register", "handle", "chokidar", "exec", "execFileSync", "fs", "net", "os", "path", "process", "util", "execFile", "fs", "path", "promisify", "execFileAsync", "promisify", "execFile", "readPackageName", "dir", "parsed", "defaultDryRunner", "root", "packageName", "stdout", "names", "t", "p", "readPackageDirs", "packageNameByDir", "dirByName", "distDir", "getPackageDistDirs", "name", "buildClosureMap", "apps", "dryRunner", "dependentsByPackageDir", "closures", "app", "closure", "pkgName", "dependents", "packageDebounceKey", "packageDir", "selectPackageRestartTargets", "closureMap", "a", "DEFAULT_PREFIX_URL", "parsePortString", "raw", "n", "resolvePreferredPort", "appName", "env", "devConfig", "prefixedKey", "fromPrefixed", "parsePortString", "fromPort", "resolvePrefixUrl", "DEFAULT_PREFIX_URL", "findPortConflicts", "apps", "ports", "a", "duplicatePorts", "port", "index", "conflictingApps", "Logger", "fastify", "fs", "path", "process", "pathToFileURL", "parseYaml", "isAddressInUse", "error", "DEFAULT_LAMBDA_TIMEOUT_MS", "resolveLambdaTimeoutMs", "raw", "isRequestLogEnabled", "ServerlessLocalRun", "serverConfig", "request", "reply", "onRequestLog", "ms", "requestPath", "boundPort", "preferred", "address", "_request", "serverlessYmlPath", "fileContents", "data", "p", "funcDef", "element", "http", "url", "urlAction", "validMethods", "method", "routeKey", "parts", "filepath", "handler", "controllerPath", "fileUrl", "action", "traceLogger", "controller", "handlerFn", "retVal", "responseBody", "requestBody", "queryParams", "pathParameters", "headers", "httpMethod", "startTime", "timeoutMs", "datePart", "_error", "_result", "_messageOrObject", "spawn", "fs", "buildTurboWatchFilters", "depInclusive", "depClosure", "name", "defaultTurboWatchFactory", "cwd", "logFile", "onUnexpectedExit", "filters", "out", "child", "spawn", "superviseChild", "spawn", "process", "OSC_ESCAPE", "CSI_ESCAPE", "CONTROL_CHARS", "stripAnsi", "text", "TASK_PREFIX", "isTaskChrome", "parseTurboDevLine", "raw", "line", "match", "MAX_PENDING_CHARS", "pumpLines", "stream", "opts", "pending", "emit", "parsed", "chunk", "lines", "err", "defaultUiDevFactory", "packageNames", "cwd", "concurrency", "env", "appendLog", "onLine", "onUnexpectedExit", "filters", "name", "child", "spawn", "process", "superviseChild", "LOG_FILE_PATH", "resolveLogFilePath", "getCacheRoot", "process", "INFRA_KIT_SESSION_VAR", "initLogFile", "homeShorten", "p", "home", "os", "packageList", "apps", "a", "execFn", "util", "exec", "launchScript", "script", "logFn", "stderr", "error", "err", "appendLogFile", "text", "getFreePort", "resolve", "reject", "srv", "net", "address", "port", "defaultHealthProbe", "url", "HELPER_VERSION_FLOOR", "isBelowVersion", "version", "floor", "parse", "v", "part", "b", "i", "left", "right", "declaresInfraKit", "repoRoot", "manifests", "app", "file", "pkg", "field", "assertHelperVersionFloor", "helperDir", "real", "root", "readAppRelease", "cwd", "branch", "execFileSync", "slug", "slugifyRelease", "DEFAULT_RELEASE_SLUG", "errorReason", "firstLine", "DevServerRunner", "_DevServerRunner", "DEFAULT_DEV_PROXY_PORT", "options", "runBuild", "turboWatchFactory", "defaultTurboWatchFactory", "uiDevFactory", "defaultUiDevFactory", "dryRunner", "renderer", "healthProbe", "proxy", "createPortlessDriver", "DevRenderer", "findMonorepoRoot", "devConfig", "discoverApiApps", "getInfraKitConfig", "normalizeAppInclude", "appName", "resolvePreferredPort", "resolvePrefixUrl", "devPresets", "name", "def", "available", "bootStart", "include", "watch", "uiApps", "apiAppsAll", "uiAppsAll", "target", "deriveTargetLabel", "discoverUiApps", "resolved", "resolvePreset", "apiNames", "t", "uiNames", "passesInclude", "watchDepsByApp", "bootLabel", "map", "filters", "names", "uiPortEnv", "caEnv", "readCaPath", "tagByPackage", "detail", "explicitApps", "duplicatePorts", "conflictingApps", "findPortConflicts", "buildCmd", "buildError", "started", "packageName", "appDir", "release", "label", "slugifyHostLabel", "server", "ServerlessLocalRun", "method", "reqPath", "status", "ms", "boundPort", "alias", "fragment", "tmp", "work", "run", "r", "e", "targets", "idx", "outcomes", "restarted", "entry", "probed", "healthy", "allHealthy", "buildClosureMap", "engine", "consequence", "appDistDirs", "getAppDistDirs", "packageDistDirs", "getPackageDistDirs", "allDistDirs", "usePoll", "watcher", "chokidar", "filePath", "change", "classifyDistChange", "selectPackageRestartTargets", "packageDebounceKey", "key", "existing", "timer", "intervalMs", "ok", "fails", "next", "elapsedMs", "health", "endpoints", "resolveEndpointUrl", "uiEndpoints", "uiRefs", "ui", "assigned", "appCount", "pkgCount", "reason", "routes", "selectDevUi", "PersistentInkDevUi", "runner", "toPresetDef", "targets", "key", "explainTargetKey", "splitList", "value", "parts", "s", "toDevServerOptions", "raw", "resolveSelfOptions", "options", "resolveSelfAppName", "process", "runDevServer", "rawOptions", "isCmuxAvailable", "runCmuxDevServer", "runner", "run", "registerCrashBarrier", "registerSignalShutdown", "signal", "shouldRunWizard", "tty", "json", "wizardToOptions", "result", "runDevServerCli", "runDevWizard", "error", "isPromptCancellation", "parseAndRun", "argv", "program", "Command", "pathToFileURL"]
4
+ "sourcesContent": ["/**\n * Long-running CLI entry for the local dev-server. Owns flag parsing and wires signal\n * handling to `src/dev/signal-shutdown` \u2014 the orchestrator (`src/dev/dev-server`) stays\n * signal- and exit-agnostic. Kept off the eager cli.js graph: entry/cli.ts reaches this\n * module (and the fastify/chokidar it pulls in) only via `await import(...)`, so\n * those heavy deps never load on the machine command paths.\n */\nimport { Command } from 'commander'\nimport process from 'node:process'\nimport { pathToFileURL } from 'node:url'\n\nimport { runCmuxDevServer } from 'src/dev/cmux-dev'\nimport { registerCrashBarrier } from 'src/dev/crash-barrier'\nimport { run } from 'src/dev/dev-server'\nimport type { DevServerOptions } from 'src/dev/dev-server'\nimport type { WizardResult } from 'src/dev/dev-wizard-run'\nimport { resolveSelfAppName } from 'src/dev/discovery'\nimport { explainTargetKey } from 'src/dev/presets'\nimport { registerSignalShutdown } from 'src/dev/signal-shutdown'\nimport { isCmuxAvailable } from 'src/integrations/cmux'\nimport { isPromptCancellation } from 'src/lib/errors/is-prompt-cancellation'\nimport type { DevPreset } from 'src/lib/infra-kit-config'\n\n/** Raw option object as produced by Commander (comma-joined strings). */\nexport interface DevCliOptions {\n watch?: boolean\n app?: string\n /**\n * Comma-separated `<app>/<part>` target keys (`--target=client/api,client/ui`). The part-level\n * selector `--app` cannot express: `--app=client` expands to every part `client` has. Same grammar as\n * a `devServersPresets` key, so a wizard selection round-trips into a command you can paste.\n */\n target?: string\n /** Named preset positional (`infra-kit dev <preset>`); selects launch targets from `devServersPresets`. */\n preset?: string\n cmux?: boolean\n self?: boolean\n verbose?: boolean\n /** Print each app's registered routes at startup (opt-in; off keeps the calm default screen). */\n routes?: boolean\n}\n\n/**\n * Turn `--target=<app>/<part>,\u2026` into the in-memory preset the runner already understands, or\n * `undefined` when the flag is absent (leaving `preset`/`*` resolution untouched). Validated against the\n * same grammar as a `devServersPresets` key, but with a message that names the FLAG \u2014 a user typing\n * `--target=client` must not be told about `devServersPresets`.\n */\nconst toPresetDef = (targets: string[] | null): DevPreset | undefined => {\n if (targets == null) return undefined\n\n for (const key of targets) {\n if (explainTargetKey(key) !== null) {\n throw new Error(`infra-kit dev: invalid --target \"${key}\" (expected \"<app>/api\" or \"<app>/ui\").`)\n }\n }\n\n return {\n apps: Object.fromEntries(\n targets.map((key) => {\n return [key, {}]\n }),\n ),\n }\n}\n\n/** Split a comma-separated flag value into a trimmed, non-empty list (`null` when unset/empty). */\nconst splitList = (value: string | undefined): string[] | null => {\n if (value == null) return null\n\n const parts = value\n .split(',')\n .map((s) => {\n return s.trim()\n })\n .filter(Boolean)\n\n return parts.length > 0 ? parts : null\n}\n\n/**\n * Map raw Commander flags to the orchestrator's typed options. Shared by the\n * `infra-kit dev` subcommand and the standalone entry so the two never diverge.\n * `self` is passed through as-is here \u2014 it's resolved to `include` later, in\n * `runDevServer`, where `--app` (if also given) is allowed to win.\n */\nexport const toDevServerOptions = (raw: DevCliOptions): DevServerOptions => {\n return {\n watch: raw.watch ?? false,\n include: splitList(raw.app),\n preset: raw.preset,\n presetDef: toPresetDef(splitList(raw.target)),\n cmux: raw.cmux ?? false,\n self: raw.self ?? false,\n verbose: raw.verbose ?? false,\n routes: raw.routes ?? false,\n }\n}\n\n/**\n * Resolve `--self` into an `include` list, without mutating `options`. `--app` (an\n * explicit `include`) wins over `--self` when both are given \u2014 self is only a\n * convenience default for scripts that don't want to hardcode their app name.\n * Lets `resolveSelfAppName`'s error (not inside `apps/<app>/...`) propagate as-is;\n * the caller's top-level catch turns it into a clean message + non-zero exit.\n */\nconst resolveSelfOptions = (options: DevServerOptions): DevServerOptions => {\n if (!options.self || options.include) {\n return options\n }\n\n return { ...options, include: [resolveSelfAppName(process.cwd())] }\n}\n\n/**\n * Start the dev-server, then wait for an OS signal and shut every server down\n * cleanly before exiting. Signal handling and exit are delegated to\n * {@link registerSignalShutdown}: a second signal force-quits, a rejected teardown\n * is logged, and the process exits `128 + signo` rather than a dishonest `0`.\n */\nexport const runDevServer = async (rawOptions: DevServerOptions): Promise<void> => {\n const options = resolveSelfOptions(rawOptions)\n\n // `--cmux`: one workspace, one pane per app. `runCmuxDevServer` owns its own\n // signal handling and never returns, so return before wiring the in-process\n // handlers below. Fall through to single-process dev when cmux isn't installed.\n if (options.cmux) {\n if (await isCmuxAvailable()) {\n await runCmuxDevServer(options)\n\n return\n }\n\n process.stdout.write('cmux not available; falling back to single-terminal dev\\n')\n }\n\n const runner = await run(options)\n\n // In-process backends share this event loop; a handler's escaped async path would otherwise terminate\n // the whole session. Installed only on the single-process path (the cmux path returned above, each pane\n // being its own process) and only after `run()` succeeds, so a boot failure still exits honestly.\n registerCrashBarrier()\n\n registerSignalShutdown({\n onSignal: async (signal) => {\n process.stdout.write(`\\nReceived ${signal}, shutting down dev-server...\\n`)\n await runner.shutdown()\n },\n })\n}\n\n/**\n * True when `infra-kit dev` was invoked BARE \u2014 no preset and no selection/mode flag \u2014 in an interactive\n * TTY (both stdin and stdout) and not `--json`. This is the ONLY condition that launches the wizard;\n * every flagged, piped, non-TTY, `--json`, or MCP invocation runs directly from the parsed flags, so no\n * existing script path changes behaviour.\n */\nexport const shouldRunWizard = (raw: DevCliOptions, tty: boolean, json: boolean): boolean => {\n const bare = !raw.preset && !raw.app && !raw.self && !raw.cmux && !raw.watch && !raw.verbose && !raw.routes\n\n return bare && tty && !json\n}\n\n/** Map a wizard result to runner options: the cmux path uses `include`; otherwise the in-memory `presetDef`. */\nconst wizardToOptions = (result: WizardResult): DevServerOptions => {\n return {\n watch: result.watch,\n cmux: result.cmux,\n include: result.include ?? null,\n preset: result.preset,\n presetDef: result.presetDef,\n self: false,\n verbose: false,\n routes: false,\n }\n}\n\n/**\n * Entry for the `infra-kit dev` subcommand. On a bare TTY invocation it launches the interactive wizard\n * (lazily imported so its inquirer/config graph stays off every other path), then starts the server with\n * the assembled options; otherwise it runs directly from the parsed flags. A cancelled prompt (Ctrl-C /\n * Esc) or an empty selection exits cleanly without starting a server.\n */\nexport const runDevServerCli = async (raw: DevCliOptions, tty: boolean, json: boolean): Promise<void> => {\n if (shouldRunWizard(raw, tty, json)) {\n const { runDevWizard } = await import('src/dev/dev-wizard-run')\n\n let result: WizardResult | null\n\n try {\n result = await runDevWizard()\n } catch (error) {\n if (isPromptCancellation(error)) return\n\n throw error\n }\n\n if (result == null) return\n\n // The wizard only runs on a bare interactive TTY, so tty=true, json=false \u2014 thread them so `run()`\n // selects the Ink boot UI.\n await runDevServer({ ...wizardToOptions(result), tty, json })\n\n return\n }\n\n // Thread the TTY / json signal so `run()` can pick the Ink boot UI (TTY, non-json) vs the plain renderer.\n await runDevServer({ ...toDevServerOptions(raw), tty, json })\n}\n\n/** Parse `node dist/dev-server.js ...` flags and start the server. */\nconst parseAndRun = async (argv: string[]): Promise<void> => {\n const program = new Command()\n\n program\n .name('infra-kit-dev-server')\n .description('Run local dev servers for the apps in a named devServersPresets preset (or all apps)')\n .argument('[preset]', 'Named preset from devServersPresets (omit to run every app)')\n .option('-w, --watch', 'Rebuild and restart on file save')\n .option('--app <names>', 'Further narrow to these app folder names (comma-separated)')\n .option(\n '--cmux',\n 'Run each app in its own cmux pane (one workspace, N panes; falls back to single terminal if cmux is unavailable)',\n )\n .option('--self', 'Run only the app of the current directory (infer from cwd; use inside apps/<app>/\u2026)')\n .option('-V, --verbose', 'Print full boot narration (default: quiet; full detail always in the session log)')\n .option('--routes', 'Print each app\u2019s registered METHOD /path routes at startup (default: off)')\n\n program.parse(argv)\n\n await runDevServer(toDevServerOptions({ ...program.opts<DevCliOptions>(), preset: program.args[0] }))\n}\n\n// Self-execute only when run directly (`node dist/dev-server.js`), not when the\n// `infra-kit dev` subcommand imports this module for its exported helpers.\nif (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {\n parseAndRun(process.argv).catch((error: unknown) => {\n // Message only, matching the `infra-kit dev` path: a bad preset name or target key is a\n // config mistake, and its stack frames are noise.\n console.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n })\n}\n", "/**\n * Resident `infra-kit dev --cmux` supervisor.\n *\n * Opens ONE cmux workspace with one pane per discovered backend API app, each\n * pane running the primitive `pnpm exec infra-kit dev --app=<name>` (single-app,\n * single-process). Stays resident: on SIGINT/SIGTERM it closes the workspace\n * (reaping every pane) and exits. The pure command/layout construction lives here\n * and in {@link file://./cmux-layout.ts} so it is unit-testable without a shell.\n */\nimport * as path from 'node:path'\nimport process from 'node:process'\n\nimport { closeCmuxDevWorkspace, openCmuxDevWorkspace } from 'src/integrations/cmux'\nimport { logger } from 'src/lib/logger'\n\nimport { buildCmuxLayout } from './cmux-layout.js'\nimport type { DevServerOptions } from './dev-server.js'\nimport { discoverApiApps, findMonorepoRoot, normalizeAppInclude } from './discovery.js'\nimport type { DiscoveredApiApp } from './discovery.js'\nimport { registerSignalShutdown } from './signal-shutdown.js'\n\n/** One cmux pane: the app it runs, and (optionally) the exact `<app>/<part>` targets selected for it. */\nexport interface PaneSpec {\n app: string\n /**\n * Exact target keys for this pane. When absent the pane falls back to `--app=<name>`, which expands to\n * EVERY part the app has (both the api and the ui glob targets) \u2014 right for a plain `--cmux`, wrong for a wizard selection\n * that deliberately unticked a part. `--app` has app-name granularity and cannot express `<app>/api`\n * alone; that is exactly what `--target` exists for.\n */\n targets?: string[]\n}\n\n/** Build the per-pane primitive command for each pane (append `--watch` when requested). */\nexport const buildPaneCommands = (panes: PaneSpec[], watch: boolean): string[] => {\n return panes.map(({ app, targets }) => {\n const selector = targets && targets.length > 0 ? `--target=${targets.join(',')}` : `--app=${app}`\n\n return `pnpm exec infra-kit dev ${selector}${watch ? ' --watch' : ''}`\n })\n}\n\n/** The concrete `<app>/<part>` keys of an in-memory preset, grouped by app. Glob keys (those whose app segment is a bare star) are skipped. */\nexport const paneTargetsByApp = (presetDef: DevServerOptions['presetDef']): Map<string, string[]> => {\n const byApp = new Map<string, string[]>()\n\n for (const key of Object.keys(presetDef?.apps ?? {})) {\n const app = key.split('/')[0]\n\n if (app === undefined || app === '*') continue\n\n byApp.set(app, [...(byApp.get(app) ?? []), key])\n }\n\n return byApp\n}\n\n/** Discover API apps under `root`, applying the optional `--app` include filter. */\nconst selectApiApps = (root: string, include: string[] | null): DiscoveredApiApp[] => {\n const apps = discoverApiApps(root)\n\n if (!include) {\n return apps\n }\n\n return apps.filter((app) => {\n return include.includes(app.name)\n })\n}\n\n/**\n * Log each app pane and the opened workspace ref. Ports are NOT shown here: each\n * pane binds a dynamic (ephemeral) port at runtime and prints its own real port \u2014\n * the supervisor cannot know it at spawn time, so a static resolved port would lie.\n */\nconst logDevWorkspace = (apps: DiscoveredApiApp[], ref: string): void => {\n logger.info(`\uD83E\uDDE9 Opened cmux dev workspace ${ref} with ${apps.length} pane(s):`)\n\n for (const app of apps) {\n logger.info(` \u2022 ${app.name} (infra-kit dev --app=${app.name})`)\n }\n}\n\n/**\n * Register SIGINT/SIGTERM handlers that close the workspace `ref` \u2014 the supervisor's whole\n * teardown. No force-deadline: closing a workspace is a single call, not a child reap, and a\n * second signal is already an unconditional escape (see {@link registerSignalShutdown}).\n */\nconst registerShutdown = (ref: string): void => {\n registerSignalShutdown({\n onSignal: async (signal) => {\n logger.info(`\\nReceived ${signal}, closing cmux dev workspace ${ref}...`)\n await closeCmuxDevWorkspace(ref)\n },\n })\n}\n\n/**\n * Open a cmux workspace with one pane per API app, then stay resident as a\n * supervisor until a signal tears the workspace down. Falls out early (no\n * workspace) when no apps are discovered. Never returns on the happy path \u2014 it\n * owns its own SIGINT/SIGTERM handling and blocks forever otherwise.\n *\n * @example\n * // Runs until Ctrl-C; opens `pnpm exec infra-kit dev --app=<name>` per app.\n * await runCmuxDevServer({ include: null, watch: false })\n */\nexport const runCmuxDevServer = async (options: DevServerOptions): Promise<void> => {\n const root = findMonorepoRoot(process.cwd())\n const apps = selectApiApps(root, normalizeAppInclude(options.include))\n\n if (apps.length === 0) {\n logger.warn('No API apps found to run')\n\n return\n }\n\n // A wizard run hands down its in-memory preset, so each pane reproduces the exact parts that were\n // ticked. A plain `--cmux` has no presetDef and falls back to `--app=<name>` (every part), unchanged.\n const targetsByApp = paneTargetsByApp(options.presetDef)\n const commands = buildPaneCommands(\n apps.map((app) => {\n return { app: app.name, targets: targetsByApp.get(app.name) }\n }),\n options.watch ?? false,\n )\n const layout = buildCmuxLayout(commands)\n const title = `${path.basename(root)} dev`\n const ref = await openCmuxDevWorkspace({ cwd: root, title, layout })\n\n logDevWorkspace(apps, ref)\n registerShutdown(ref)\n\n // Stay resident until a signal fires. A never-resolving promise alone does NOT\n // keep Node's event loop alive (nothing pending \u2192 the process exits with code\n // 13, \"unsettled top-level await\"); a ref'd heartbeat timer holds it open. The\n // SIGINT/SIGTERM handler in registerShutdown owns the actual exit.\n const heartbeat = setInterval(() => {\n heartbeat.refresh()\n }, 2 ** 30)\n\n await new Promise<never>(() => {})\n}\n", "/**\n * Pure cmux layout construction for `infra-kit dev --cmux`.\n *\n * Turns a flat list of per-pane shell commands into the recursive binary-tree\n * layout JSON cmux's `new-workspace --layout` consumes. Side-effect free (no fs,\n * no cwd, no shell) so the tiling math stays unit-testable in isolation.\n */\n\n/** A single terminal surface inside a pane, running one shell command. */\nexport interface CmuxTerminalSurface {\n type: 'terminal'\n command: string\n}\n\n/** A leaf node: one pane holding one or more terminal surfaces. */\nexport interface CmuxPaneLeaf {\n pane: {\n surfaces: CmuxTerminalSurface[]\n }\n}\n\n/** An internal node: a horizontal/vertical split into exactly two children. */\nexport interface CmuxSplitNode {\n direction: 'horizontal' | 'vertical'\n split: number\n children: [CmuxLayoutNode, CmuxLayoutNode]\n}\n\n/** Either a pane leaf or a two-way split \u2014 the recursive layout tree. */\nexport type CmuxLayoutNode = CmuxPaneLeaf | CmuxSplitNode\n\n/** Wrap a single command in a bare pane leaf. */\nconst makeLeaf = (command: string): CmuxPaneLeaf => {\n return { pane: { surfaces: [{ type: 'terminal', command }] } }\n}\n\n/**\n * Recursively split `commands` into a balanced binary tree: the left half takes\n * `Math.ceil(n / 2)` commands, the split ratio is that count over the total\n * (2-decimal), and the direction alternates by depth (even \u2192 horizontal,\n * odd \u2192 vertical) so panes tile into a grid rather than one axis.\n */\nconst buildNode = (commands: string[], depth: number): CmuxLayoutNode => {\n if (commands.length === 1) {\n return makeLeaf(commands[0]!)\n }\n\n const leftCount = Math.ceil(commands.length / 2)\n const left = commands.slice(0, leftCount)\n const right = commands.slice(leftCount)\n\n return {\n direction: depth % 2 === 0 ? 'horizontal' : 'vertical',\n split: Math.round((leftCount / commands.length) * 100) / 100,\n children: [buildNode(left, depth + 1), buildNode(right, depth + 1)],\n }\n}\n\n/**\n * Build a balanced cmux layout tree with one pane leaf per command. A single\n * command yields a bare leaf; N commands tile into a grid via alternating\n * horizontal/vertical splits. Throws when `commands` is empty.\n *\n * @example\n * buildCmuxLayout(['a', 'b'])\n * // => {\n * // direction: 'horizontal',\n * // split: 0.5,\n * // children: [\n * // { pane: { surfaces: [{ type: 'terminal', command: 'a' }] } },\n * // { pane: { surfaces: [{ type: 'terminal', command: 'b' }] } },\n * // ],\n * // }\n */\nexport const buildCmuxLayout = (commands: string[]): CmuxLayoutNode => {\n if (commands.length === 0) {\n throw new Error('buildCmuxLayout: at least one command is required')\n }\n\n return buildNode(commands, 0)\n}\n", "/**\n * Signal-driven graceful shutdown, shared by every long-lived dev entry point.\n *\n * Three invariants:\n *\n * 1. Only a REPEAT OF THE SAME SIGNAL is an escape hatch. It is tempting to treat any second\n * signal as \"the user pressed Ctrl-C again, get out now\" \u2014 that was the old rule, and it\n * silently orphaned every child. A process manager in front of us relays its own signal after\n * the TTY's, so ONE Ctrl-C under `pnpm exec` arrives as SIGINT *then* SIGTERM:\n *\n * $ pnpm exec node -e \"process.on('SIGINT',\u2026); process.on('SIGTERM',\u2026)\"\n * ^C GOT SIGINT\n * GOT SIGTERM <- pnpm relaying, NOT a second keypress\n *\n * Under the old rule that relay force-quit the teardown a few hundred ms in and abandoned the\n * turbo/vite children (which sit in their own process groups). Keying the escape on the FIRST\n * signal's type fixes it without losing the hatch: a real second Ctrl-C re-sends SIGINT, and a\n * supervisor escalating SIGTERM -> SIGTERM still force-quits. `npm`/`yarn` relay the same way,\n * so this must live here, in the library, not in one consumer's `dev` script.\n * 2. Force-quitting must still not orphan. Even a genuine double Ctrl-C leaves children holding\n * ports (the next start then 502s), so the force path SIGKILLs the descendant process groups\n * before exiting. Bounded and synchronous \u2014 no grace, no escalation, no waiting for reaping.\n * 3. A signal-terminated process must not report exit code 0. The old handlers ran\n * `finally { process.exit(0) }`, which also swallowed a rejecting teardown without a word.\n * Here the rejection goes to stderr and the code stays `128 + signo` \u2014 even on a failed\n * teardown, because the process genuinely WAS signal-terminated, no conventional code means\n * \"signal-terminated but cleanup failed\", and inventing one would break the SIGTERM -> 143\n * contract supervisors rely on. The failure is surfaced by logging, not by the code.\n */\nimport { constants } from 'node:os'\nimport process from 'node:process'\n\nimport { killDescendantGroupsNow } from 'src/dev/managed-child'\n\n/**\n * The signals a resident dev process tears down on.\n *\n * SIGHUP is here for invariant 2: the terminal going away (window closed, ssh dropped) is delivered to\n * the foreground process group, so `dev` gets it \u2014 but its turbo/vite children DON'T, because they sit\n * in their own detached process groups. Without a handler, `dev` takes SIGHUP's default kill, never runs\n * `killDescendantGroupsNow`, and leaves the whole tree alive holding the dev ports; the next start then\n * 502s. Closing a terminal is the single most common way to walk away from a dev server, so this is the\n * likeliest orphan path, not an exotic one.\n */\nconst HANDLED_SIGNALS: readonly NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGHUP']\n\n/**\n * `os.constants.signals` typed for lookup by an arbitrary {@link NodeJS.Signals}. The platform\n * table omits signals that do not exist on the host, so the index is genuinely partial.\n */\nconst SIGNAL_NUMBERS: Partial<Record<NodeJS.Signals, number>> = constants.signals\n\n/** Fallback signal number when the host table lacks the signal: SIGINT, yielding the usual 130. */\nconst FALLBACK_SIGNO = 2\n\n/**\n * The POSIX exit code for a process terminated by `signal`: `128 + signo`.\n *\n * A shell reports SIGINT as 130 and SIGTERM as 143. Exiting 0 after a signal tells a supervisor\n * the process stopped voluntarily, which is a lie.\n *\n * @example\n * exitCodeForSignal('SIGINT') // 130\n * exitCodeForSignal('SIGTERM') // 143\n */\nexport const exitCodeForSignal = (signal: NodeJS.Signals): number => {\n return 128 + (SIGNAL_NUMBERS[signal] ?? FALLBACK_SIGNO)\n}\n\n/**\n * Seams for {@link registerSignalShutdown}. Each exists because the real implementation is\n * untestable in-process: `process.exit` kills the test runner, and delivering a real POSIX\n * signal to the runner is unsafe and flaky.\n */\nexport interface SignalShutdownDeps {\n /** The context-specific teardown. May reject; the rejection is logged, never swallowed. */\n onSignal: (signal: NodeJS.Signals) => Promise<void>\n /**\n * Terminate the process. Typed as returning `void`, not `never`, so a test can pass a plain\n * recording fake: a `never` fake would have to throw a sentinel, and that throw would reject the\n * in-flight teardown promise. Nothing meaningful runs after a call, so a returning fake is safe.\n */\n exit?: (code: number) => void\n /** Subscribe `handler` to `signal`. Defaults to `process.on`. */\n register?: (signal: NodeJS.Signals, handler: () => void) => void\n /**\n * Reap surviving descendant process groups on the force-quit path, where the normal teardown is\n * abandoned mid-flight. Synchronous by contract: the process exits on the next line, so anything\n * deferred to a later tick would never run. Defaults to {@link killDescendantGroupsNow}.\n */\n forceReap?: () => void\n}\n\nconst defaultExit = (code: number): void => {\n process.exit(code)\n}\n\nconst defaultRegister = (signal: NodeJS.Signals, handler: () => void): void => {\n process.on(signal, handler)\n}\n\n/**\n * Write to stderr, swallowing a failed write.\n *\n * `stderr.write` can throw (EPIPE, when the parent closed the pipe). Every caller here is on the\n * exit path, and failing to REPORT something must never break that path \u2014 the process still has to\n * die with the right code. A lost message is recoverable; a process that survives its own SIGINT\n * is not.\n */\nconst writeStderr = (message: string): void => {\n try {\n process.stderr.write(message)\n } catch {\n // stderr is gone; there is nowhere left to report to.\n }\n}\n\n/** Report a rejected teardown \u2014 message AND stack, so a wedge is never silent. */\nconst writeTeardownFailure = (signal: NodeJS.Signals, error: unknown): void => {\n const detail = error instanceof Error ? `${error.message}\\n${error.stack ?? '(no stack)'}` : String(error)\n\n writeStderr(`\\n\u2717 Teardown failed while handling ${signal}: ${detail}\\n`)\n}\n\n/**\n * Wire SIGINT/SIGTERM to `onSignal`, then exit `128 + signo`.\n *\n * The first signal runs the teardown. While it is in flight, a signal of a DIFFERENT type is a\n * relay from the process manager above us (`pnpm exec` turns one Ctrl-C into SIGINT + SIGTERM) and\n * is ignored, so the teardown survives to reap its children. A repeat of the FIRST signal is the\n * real escape hatch: it force-quits without touching `onSignal` again, SIGKILLing the descendant\n * groups on the way out so nothing is left holding a port.\n *\n * No force-deadline is armed. Any value safe enough not to preempt a legitimate teardown would\n * fire later than a user's second Ctrl-C, and a supervisor sends its own SIGKILL on its own grace.\n *\n * @example\n * registerSignalShutdown({\n * onSignal: async (signal) => {\n * process.stdout.write(`Received ${signal}, shutting down...`)\n * await runner.shutdown()\n * },\n * })\n */\nexport const registerSignalShutdown = ({\n onSignal,\n exit = defaultExit,\n register = defaultRegister,\n forceReap = killDescendantGroupsNow,\n}: SignalShutdownDeps): void => {\n /** The signal that opened the teardown \u2014 `null` until the first one lands. */\n let firstSignal: NodeJS.Signals | null = null\n\n const handle = (signal: NodeJS.Signals): void => {\n if (firstSignal !== null) {\n // A DIFFERENT signal while tearing down is the process manager in front of us relaying its\n // own (pnpm/npm turn one Ctrl-C into SIGINT + SIGTERM), never a second keypress. Ignoring it\n // is what lets the teardown finish and reap the children. Only a repeat of the signal that\n // STARTED the teardown is the user (or a supervisor) genuinely asking again.\n if (signal !== firstSignal) return\n\n writeStderr(`\\n\u26A0 Received ${signal} again \u2014 force-quitting.\\n`)\n // Abandoning the teardown must still not abandon the children: they hold the dev ports, and\n // the next start would 502 against their stale portless aliases.\n forceReap()\n exit(exitCodeForSignal(signal))\n\n return\n }\n\n firstSignal = signal\n\n void (async (): Promise<void> => {\n try {\n await onSignal(signal)\n } catch (error) {\n writeTeardownFailure(signal, error)\n } finally {\n // `finally`, so the exit survives anything the catch body might later throw: a process must\n // never outlive its own signal. Not the old `finally { process.exit(0) }` bug \u2014 that one\n // exited zero without logging; this logs first, then exits the honest code.\n exit(exitCodeForSignal(signal))\n }\n })()\n }\n\n for (const signal of HANDLED_SIGNALS) {\n register(signal, () => {\n return handle(signal)\n })\n }\n}\n", "/**\n * Shared supervision for the dev-server's long-lived, detached child processes\n * (`turbo watch build`, `turbo run dev`). Both are deep trees\n * (`sh \u2192 pnpm \u2192 node \u2192 turbo \u2192 \u2026`) spawned `detached` so they form their own process\n * group; teardown signals the whole GROUP (`process.kill(-pid, \u2026)`), not just the wrapper.\n *\n * Signalling turbo's group is NOT enough on its own: turbo puts every task it runs\n * (`vite`, `tsc -b`, \u2026) into a process group of its OWN, so `kill(-turboPid, \u2026)` reaches\n * turbo but never its tasks. Turbo normally forwards the signal and reaps them, but when it\n * is slower than the grace window a bare `SIGKILL` on turbo's group kills the only process\n * that knows the task groups \u2014 stranding orphaned vite servers that hold their ports until\n * the next run fails with `EADDRINUSE`.\n *\n * So teardown snapshots the descendant process groups BEFORE signalling (once turbo dies its\n * children reparent to init and can no longer be found by walking `ppid`), waits for the whole\n * set to exit, and only then force-kills whatever is left.\n */\nimport { execFileSync } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport process from 'node:process'\n\n/** Handle to a supervised child; `kill()` reaps its whole process tree and resolves when gone. */\nexport interface ManagedChild {\n kill: () => Promise<void>\n}\n\n/**\n * Grace before escalating SIGTERM \u2192 SIGKILL. Long enough for turbo to stop its tasks itself\n * (typically well under a second) and short enough that Ctrl-C still feels immediate. Escalating\n * is safe now that every task group is killed as a set, so this need not cover the worst case.\n * The happy path never waits the full window \u2014 teardown returns as soon as the groups are gone.\n */\nconst DEFAULT_GRACE_MS = 5000\n\n/** Poll interval while waiting for the doomed process groups to exit. */\nconst POLL_MS = 100\n\n/** How long to wait for the kernel to reap a group after SIGKILL before giving up on confirmation. */\nconst REAP_TIMEOUT_MS = 2000\n\n/** Absolute path to `ps` \u2014 present on both macOS and Linux, and immune to `PATH` substitution. */\nconst PS_BIN = '/bin/ps'\n\n/** One row of `ps -eo pid=,ppid=,pgid=`. */\nexport interface ProcRow {\n pid: number\n ppid: number\n pgid: number\n}\n\n/** Parse `ps -eo pid=,ppid=,pgid=` output, skipping any line that isn't three integers. */\nexport const parseProcRows = (raw: string): ProcRow[] => {\n return raw.split('\\n').flatMap((line) => {\n const [rawPid, rawPpid, rawPgid] = line.trim().split(/\\s+/)\n\n if (rawPid == null || rawPpid == null || rawPgid == null) return []\n\n const pid = Number(rawPid)\n const ppid = Number(rawPpid)\n const pgid = Number(rawPgid)\n\n if (!Number.isInteger(pid) || !Number.isInteger(ppid) || !Number.isInteger(pgid)) return []\n\n return [{ pid, ppid, pgid }]\n })\n}\n\n/**\n * Distinct process groups of every transitive descendant of `rootPid`, plus `rootPid`'s own\n * group. Group `0`/`1` and `excludePgid` (our own group \u2014 a detached child can never share it,\n * but a mis-parsed row must never make us signal ourselves) are filtered out.\n *\n * @example\n * const rows = [{ pid: 10, ppid: 1, pgid: 10 }, { pid: 20, ppid: 10, pgid: 20 }]\n * collectDoomedGroups(10, rows, 5) // => [10, 20]\n */\nexport const collectDoomedGroups = (rootPid: number, rows: ProcRow[], excludePgid?: number): number[] => {\n const childrenOf = new Map<number, ProcRow[]>()\n\n for (const row of rows) {\n const siblings = childrenOf.get(row.ppid)\n\n if (siblings) siblings.push(row)\n else childrenOf.set(row.ppid, [row])\n }\n\n const groups = new Set<number>([rootPid])\n const seen = new Set<number>([rootPid])\n const queue = [rootPid]\n\n for (let i = 0; i < queue.length; i += 1) {\n for (const child of childrenOf.get(queue[i] ?? -1) ?? []) {\n if (seen.has(child.pid)) continue\n seen.add(child.pid)\n groups.add(child.pgid)\n queue.push(child.pid)\n }\n }\n\n return [...groups].filter((pgid) => {\n return pgid > 1 && pgid !== excludePgid\n })\n}\n\n/**\n * Snapshot the live process table; an empty list on failure degrades to group-only teardown.\n * `ps` is invoked by absolute path (not via `PATH`, unlike the `pnpm` the engines shell out to)\n * so a writable `PATH` entry can never substitute the binary we hand a kill list to.\n */\nconst snapshotProcRows = (): ProcRow[] => {\n try {\n return parseProcRows(execFileSync(PS_BIN, ['-eo', 'pid=,ppid=,pgid='], { encoding: 'utf8', maxBuffer: 8 << 20 }))\n } catch {\n return []\n }\n}\n\n/** Does `pid` currently exist and name a direct child of `parentPid`? Guards against pid reuse. */\nexport const isChildOf = (pid: number, parentPid: number, rows: ProcRow[]): boolean => {\n return rows.some((row) => {\n return row.pid === pid && row.ppid === parentPid\n })\n}\n\n/**\n * The groups to signal for `rootPid`'s tree, excluding our own group so a mis-parsed `ps` row can\n * never make the dev-server signal itself.\n */\nconst doomedGroupsOf = (rootPid: number, rows: ProcRow[]): number[] => {\n const ownPgid = rows.find((row) => {\n return row.pid === process.pid\n })?.pgid\n\n return collectDoomedGroups(rootPid, rows, ownPgid)\n}\n\n/** Is the process group led by `pgid` still alive? (`kill(-pgid, 0)` throws ESRCH when gone.) */\nconst groupAlive = (pgid: number): boolean => {\n try {\n process.kill(-pgid, 0)\n\n return true\n } catch {\n return false\n }\n}\n\n/** Send `signal` to every named process group, ignoring groups that already exited. */\nconst signalGroups = (pgids: number[], signal: NodeJS.Signals): void => {\n for (const pgid of pgids) {\n try {\n process.kill(-pgid, signal)\n } catch {\n // Already gone (ESRCH) \u2014 nothing to signal.\n }\n }\n}\n\n/**\n * SIGKILL every process group descended from this process, right now.\n *\n * The force-quit path (a second Ctrl-C) abandons the graceful teardown, but it must not abandon the\n * CHILDREN: they hold the dev ports, and the next start would 502 against their stale portless\n * aliases. So this is the blunt counterpart to {@link superviseChild}'s teardown \u2014 no SIGTERM grace,\n * no escalation, no waiting for the kernel to reap. Straight to SIGKILL.\n *\n * Synchronous by contract: the caller exits the process on the very next line, so anything deferred\n * to a later tick would never run. Cost is one `ps` (tens of ms) plus a `kill` per group; that is the\n * whole latency budget a user who just hit Ctrl-C twice is willing to spend.\n */\nexport const killDescendantGroupsNow = (): void => {\n signalGroups(doomedGroupsOf(process.pid, snapshotProcRows()), 'SIGKILL')\n}\n\n/** Resolve after `ms`, used to pace the teardown polling loop. */\nconst sleep = async (ms: number): Promise<void> => {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\n/** Poll `pgids` until every group has exited or `timeoutMs` elapses; returns the survivors. */\nconst waitForExit = async (pgids: number[], timeoutMs: number): Promise<number[]> => {\n const deadline = Date.now() + timeoutMs\n let alive = pgids\n\n while (alive.length > 0 && Date.now() < deadline) {\n alive = alive.filter(groupAlive)\n if (alive.length === 0) break\n await sleep(POLL_MS)\n }\n\n return alive\n}\n\n/**\n * Called when a supervised child dies on its OWN \u2014 a natural `exit` or a spawn `error` that was\n * NOT initiated by {@link ManagedChild.kill}. `detail` is a short human description of the cause.\n * The runner uses it to surface a silently-dead `turbo watch`/`turbo run dev` engine (which stops\n * rebuilds/HMR with no other signal) instead of leaving the session looking healthy.\n */\nexport type UnexpectedExitHandler = (detail: string) => void\n\n/**\n * Wrap a detached child: `unref()` it (so it never keeps the loop alive) and return a handle\n * whose `kill()` reaps the child AND every process group turbo spawned beneath it.\n *\n * Snapshot first, signal second: after turbo exits its tasks reparent to init, so the `ppid`\n * walk that finds them must run while turbo is still alive. Then SIGTERM every doomed group,\n * poll until all are gone, and SIGKILL the survivors. Resolves once nothing is left.\n *\n * `onUnexpectedExit` (optional) fires when the child dies WITHOUT `kill()` having been called \u2014\n * the \"engine died silently\" case. A `killing` latch, set at the top of `kill()` before any signal\n * goes out, suppresses the callback for the exit our own teardown causes, so it reports only genuine\n * crashes. The `error` listener is load-bearing beyond reporting: a {@link ChildProcess} that emits\n * `error` (e.g. `pnpm` ENOENT) with no listener throws as an uncaught exception and would take the\n * whole dev session down.\n */\nexport function superviseChild(\n child: ChildProcess,\n graceMs: number = DEFAULT_GRACE_MS,\n onUnexpectedExit?: UnexpectedExitHandler,\n): ManagedChild {\n // Don't keep the parent event loop alive on the child; the runner owns lifecycle via kill().\n child.unref()\n\n // Latched by kill() before it signals, so the child's own teardown exit is not misreported as a crash.\n let killing = false\n\n const reportUnexpected = (detail: string): void => {\n if (killing) return\n onUnexpectedExit?.(detail)\n }\n\n child.on('exit', (code, signal) => {\n reportUnexpected(`exited unexpectedly (code=${code ?? 'null'}, signal=${signal ?? 'null'})`)\n })\n child.on('error', (error: Error) => {\n reportUnexpected(`failed to spawn: ${error.message}`)\n })\n\n return {\n kill: async (): Promise<void> => {\n // Latch FIRST: the child WILL emit `exit` during the reap below, and that exit is ours, not a crash.\n killing = true\n\n const pid = child.pid\n\n if (pid == null) return\n\n // Two guards against the same hazard: once the child is reaped the OS may recycle `pid`\n // onto an unrelated process, and `kill(-pid, \u2026)` would then destroy a stranger's group.\n // A turbo that exits on its own reaps its own tasks, so bailing here strands nothing.\n if (child.exitCode !== null || child.signalCode !== null) return\n\n const rows = snapshotProcRows()\n\n // The cheap check above misses a child reaped just now, so confirm `pid` is still ours: a\n // detached child keeps us as its parent, so a foreign `ppid` means the pid was recycled.\n // Skipped when `ps` failed (empty `rows`) \u2014 then only the guard above stands.\n if (rows.length > 0 && !isChildOf(pid, process.pid, rows)) return\n\n const doomed = doomedGroupsOf(pid, rows)\n\n // Negative pid \u2192 the whole group (detached made the child a group leader). Turbo forwards\n // this to its tasks; the extra task groups are signalled directly in case it doesn't.\n signalGroups(doomed, 'SIGTERM')\n\n const stragglers = await waitForExit(doomed, graceMs)\n\n if (stragglers.length === 0) return\n\n // Re-snapshot before forcing: turbo is still alive (that's why we're here), so its tree is\n // still walkable, and `turbo watch build` may have started a task AFTER the first snapshot.\n // Union with the known stragglers, whose groups may already have left turbo's subtree.\n const late = doomedGroupsOf(pid, snapshotProcRows())\n const forced = [...new Set([...stragglers, ...late])].filter(groupAlive)\n\n // Killing turbo's group alone would orphan exactly these, so they are killed as a set.\n signalGroups(forced, 'SIGKILL')\n\n // A SIGKILLed leader lingers as an unreaped zombie for a few ms, and `kill(pgid, 0)` still\n // succeeds against it. Wait for the kernel to finish so callers that exit the process\n // immediately after `kill()` resolves aren't racing the teardown they just awaited.\n await waitForExit(forced, REAP_TIMEOUT_MS)\n },\n }\n}\n", "/**\n * Process-level crash barrier for the resident single-process dev-server.\n *\n * The backends run IN-PROCESS (`ServerlessLocalRun` is an in-process fastify that `import()`s the\n * compiled handler), so they share the runner's event loop. Fastify catches errors thrown INSIDE a\n * route handler and turns them into a 500 \u2014 but a stray async path a handler forgot to await\n * (`unhandledRejection`) or a throw from a timer/emitter (`uncaughtException`) escapes to the process\n * and, by Node's defaults, terminates it. That kills the WHOLE dev session \u2014 every backend, the watch\n * engine, the UI child \u2014 over one bad path in one handler.\n *\n * This barrier keeps the session alive and reports the fault loudly instead, the same resilience\n * `nodemon`/`vite dev`/`next dev` give. The tradeoff is deliberate and dev-only: it is installed by the\n * CLI entry point, never by library code, so it can never mask a fault in a production consumer.\n *\n * Node's caveat stands: after an `uncaughtException` the process may be in an undefined state. For a\n * local dev tool a possibly-degraded session the developer can see and restart beats a hard exit that\n * looks like the tool itself crashed. The fault is surfaced with a full stack so it is never silent.\n */\nimport process from 'node:process'\n\n/** The two process-level fault channels that terminate Node by default. */\ntype FaultEvent = 'uncaughtException' | 'unhandledRejection'\n\n/**\n * Seams for {@link registerCrashBarrier}. Each exists because the real implementation is untestable\n * in-process: attaching real `process.on('uncaughtException')` handlers in the test runner would swallow\n * the runner's own faults, and there is nothing to assert against a handler that only logs to stderr.\n */\nexport interface CrashBarrierDeps {\n /** Report the fault. Defaults to a guarded stderr write. Must not throw. */\n onFault?: (event: FaultEvent, error: unknown) => void\n /** Subscribe `handler` to `event`. Defaults to `process.on`. */\n register?: (event: FaultEvent, handler: (error: unknown) => void) => void\n}\n\n/**\n * Write to stderr, swallowing a failed write. Mirrors `signal-shutdown`'s `writeStderr`: reporting must\n * never itself throw on the fault path (EPIPE when the parent closed the pipe), and a lost message is\n * recoverable where a barrier that throws is not.\n */\nconst writeStderr = (message: string): void => {\n try {\n process.stderr.write(message)\n } catch {\n // stderr is gone; there is nowhere left to report to.\n }\n}\n\n/** Default fault reporter: message AND stack to stderr, plus a note that the session was kept alive. */\nconst defaultOnFault = (event: FaultEvent, error: unknown): void => {\n const detail = error instanceof Error ? `${error.message}\\n${error.stack ?? '(no stack)'}` : String(error)\n\n writeStderr(\n `\\n\u26A0\uFE0F ${event}: ${detail}\\n` +\n ` dev-server kept alive (likely a bug in a handler's async path); restart if it misbehaves.\\n`,\n )\n}\n\nconst defaultRegister = (event: FaultEvent, handler: (error: unknown) => void): void => {\n process.on(event, handler)\n}\n\n/**\n * Install the crash barrier: wire `uncaughtException` and `unhandledRejection` to `onFault` and DO NOT\n * exit, so a single escaped async path in an in-process backend handler no longer tears the whole dev\n * session down. The handler never rethrows \u2014 rethrowing would re-arm the very termination this prevents.\n *\n * @example\n * registerCrashBarrier() // resident dev process survives a handler's stray rejection, logs it loudly\n */\nexport const registerCrashBarrier = ({\n onFault = defaultOnFault,\n register = defaultRegister,\n}: CrashBarrierDeps = {}): void => {\n const handle = (event: FaultEvent) => {\n return (error: unknown): void => {\n onFault(event, error)\n }\n }\n\n register('uncaughtException', handle('uncaughtException'))\n register('unhandledRejection', handle('unhandledRejection'))\n}\n", "/**\n * Unified Development Server Runner\n *\n * Discovers and runs API apps under each `apps/<app>/api` folder that contains `serverless.yml`.\n *\n * Ports: `{APP}_PORT`, then `process.env.PORT`, then `dev.<app>.port` from infra-kit.json,\n * else 3010. URL prefix: `dev.<app>.prefixUrl`, else `/api/v1`.\n * Env vars should be provided via secrets manager (e.g. `doppler run -- pnpm dev-server`) or shell.\n *\n * This module is side-effect free on import: call `run()` (or construct `DevServerRunner`\n * directly) to start. Signal handling and process exit are the entry point's responsibility.\n *\n * Runner messages append to the session log `<cacheRoot>/<INFRA_KIT_SESSION>/logs.txt`. Lambda / Powertools logs from\n * handlers go to stdout.\n */\nimport chokidar from 'chokidar'\nimport type { FSWatcher } from 'chokidar'\nimport { exec, execFileSync } from 'node:child_process'\nimport * as fs from 'node:fs'\nimport net from 'node:net'\nimport os from 'node:os'\nimport * as path from 'node:path'\nimport process from 'node:process'\nimport util from 'node:util'\n\nimport { INFRA_KIT_SESSION_VAR, getCacheRoot } from 'src/lib/constants'\nimport type { DevConfig, DevPreset, DevPresets } from 'src/lib/infra-kit-config'\nimport { DEFAULT_DEV_PROXY_PORT, getInfraKitConfig } from 'src/lib/infra-kit-config'\nimport { DEFAULT_RELEASE_SLUG, slugifyHostLabel, slugifyRelease } from 'src/lib/release-slug'\n\nimport { buildClosureMap, packageDebounceKey, selectPackageRestartTargets } from './dep-closure.js'\nimport type { ClosureMap, DryRunner } from './dep-closure.js'\nimport type { DevUi } from './dev-ui.js'\nimport {\n classifyDistChange,\n discoverApiApps as discoverApiAppsBare,\n discoverUiApps as discoverUiAppsBare,\n findMonorepoRoot,\n getAppDistDirs,\n getPackageDistDirs,\n normalizeAppInclude as normalizeAppIncludePure,\n} from './discovery.js'\nimport type { DiscoveredUiApp } from './discovery.js'\nimport {\n findPortConflicts,\n resolvePreferredPort as resolvePreferredPortPure,\n resolvePrefixUrl as resolvePrefixUrlPure,\n} from './ports.js'\nimport { deriveTargetLabel, resolvePreset } from './presets.js'\nimport { createPortlessDriver, formatPortlessCommand, readCaPath } from './proxy/portless-driver.js'\nimport type { PortlessDriver } from './proxy/portless-driver.js'\nimport { DevRenderer, resolveEndpointUrl } from './render.js'\nimport type { EndpointRow, UiRef } from './render.js'\nimport { ServerlessLocalRun } from './serverless-local-run.js'\nimport { defaultTurboWatchFactory } from './turbo-watch.js'\nimport type { TurboWatchFactory, TurboWatchHandle } from './turbo-watch.js'\nimport { defaultUiDevFactory } from './ui-dev.js'\nimport type { UiDevFactory, UiDevHandle } from './ui-dev.js'\n\n/** Runner-only log file, resolved under the session cache dir (`<cacheRoot>/<session>/logs.txt`) at startup. */\nlet LOG_FILE_PATH = resolveLogFilePath()\n\n/**\n * Resolve `<cacheRoot>/<INFRA_KIT_SESSION>/logs.txt`. Reuses infra-kit's own per-terminal session id\n * (the same dir that holds the session's `env-load.sh`), falling back to a literal `no-session` folder\n * when the shell hasn't exported one \u2014 so dev logging never depends on `infra-kit init` having run.\n * Built from {@link getCacheRoot} (never `getSessionCacheDir`, which throws when the session is unset).\n */\nexport function resolveLogFilePath(): string {\n return path.join(getCacheRoot(), process.env[INFRA_KIT_SESSION_VAR] ?? 'no-session', 'logs.txt')\n}\n\n/** Resolve the session log path, ensure the dir exists, and clear the file. */\nfunction initLogFile(): void {\n LOG_FILE_PATH = resolveLogFilePath()\n fs.mkdirSync(path.dirname(LOG_FILE_PATH), { recursive: true })\n fs.writeFileSync(LOG_FILE_PATH, `=== Dev Server Started: ${new Date().toISOString()} ===\\n\\n`)\n}\n\n/** Replace a leading home dir with `~` for a compact, human-readable path label (the on-screen log link). */\nexport function homeShorten(p: string): string {\n const home = os.homedir()\n\n return p === home || p.startsWith(`${home}${path.sep}`) ? `~${p.slice(home.length)}` : p\n}\n\n/**\n * Comma-joined package names \u2014 not folder names, so a boot line names the exact `turbo --filter`\n * targets the build runs on and matches turbo's own output.\n */\nfunction packageList(apps: { packageName: string }[]): string {\n return apps\n .map((a) => {\n return a.packageName\n })\n .join(', ')\n}\n\nconst execFn = util.promisify(exec)\n\ntype LogFn = (msg: string, level?: 'info' | 'warn' | 'error' | 'debug') => void\n\n/**\n * Build seam: shells out to turbo by default ({@link launchScript}); injectable so\n * tests can run the orchestrator without a real build. `logFn` is optional so both\n * the verbose initial build and the terse restart builds keep their current output.\n */\nexport type BuildRunner = (cmd: string, logFn?: LogFn) => Promise<void>\n\nconst launchScript = async (script: string, logFn?: LogFn): Promise<void> => {\n try {\n const { stderr } = await execFn(script)\n\n if (stderr && logFn) logFn(` (build) ${stderr.trim()}`, 'debug')\n if (stderr && !logFn) console.error('stderr:', stderr)\n } catch (error) {\n const err = error as { stdout?: string; stderr?: string; message?: string }\n\n if (logFn && (err.stdout || err.stderr)) {\n if (err.stdout) logFn(` stdout: ${err.stdout.trim()}`, 'error')\n if (err.stderr) logFn(` stderr: ${err.stderr.trim()}`, 'error')\n }\n\n throw error\n }\n}\n\n/** Append raw text to the runner-only log file (the single tee-to-file seam the renderer wraps). */\nfunction appendLogFile(text: string): void {\n fs.appendFileSync(LOG_FILE_PATH, text)\n}\n\n/**\n * An OS-assigned free TCP port on 127.0.0.1 \u2014 used to pre-assign each UI's Vite port so the runner can\n * print its URL (and alias it, when a proxy is up) before Vite binds. Probes then releases (small TOCTOU\n * window; `strictPort` on the Vite side turns a rare lost race into a loud failure rather than a silent drift).\n */\nconst getFreePort = (): Promise<number> => {\n return new Promise((resolve, reject) => {\n const srv = net.createServer()\n\n srv.unref()\n srv.on('error', reject)\n srv.listen(0, '127.0.0.1', () => {\n const address = srv.address()\n const port = typeof address === 'object' && address !== null ? address.port : 0\n\n srv.close(() => {\n return resolve(port)\n })\n })\n })\n}\n\ninterface IApiAppConfig {\n /** App folder name (e.g. backoffice, client) */\n name: string\n /** Package name from package.json (e.g. sls-trvl-client) */\n packageName: string\n path: string\n /**\n * EXPLICITLY-configured preferred port (`{APP}_PORT`/`PORT`/`dev.<app>.port`), or\n * `undefined` when unconfigured. Under dynamic allocation this is only a bind hint \u2014 the\n * ACTUAL port is the ephemeral one bound at start time (see {@link IAppServer.boundPort}).\n */\n preferredPort: number | undefined\n prefixUrl: string\n /**\n * Whether this backend participates in dependency-closure watching (plan Phase 1): in\n * `--watch`, a rebuild of a package in this app's closure restarts it. Default `true`;\n * a preset `watchDeps: false` opts out (sticky). Resolved from the preset in {@link run}.\n */\n watchDeps: boolean\n}\n\n/**\n * Runner options, parsed by the CLI entry point (`--watch`, `--app`) and threaded\n * through `run()`. The entry owns flag parsing; the runner never reads `process.argv`\n * itself. App selection is `--app` only; ports come from env/config (see `resolvePort`).\n */\nexport interface DevServerOptions {\n /**\n * Watch mode: start a long-lived `turbo watch build` engine (incremental rebuilds\n * + dependency fan-out) and restart the affected server(s) when compiled `dist/`\n * changes. Without it, `dev` builds and serves once and exits on signal.\n */\n watch?: boolean\n /** Only run these app folder names (null/empty = all discovered). Filters BOTH api and ui apps. */\n include?: string[] | null\n /**\n * Named dev preset (`infra-kit dev <preset>`) from `devServersPresets` in the project's infra-kit config.\n * It selects the launch targets (`apps/<app>/{api,ui}`); resolved by {@link file://./presets.ts}.\n * Unset \u2192 run everything (`*`). `include` (`--app`/`--self`) further narrows the resolved set.\n */\n preset?: string\n /**\n * In-memory preset definition, produced by the interactive wizard (`infra-kit dev` with no args in a\n * TTY). When set it is used verbatim as the run plan \u2014 it WINS over `preset` (the named lookup) and\n * the default `*`. This is how the wizard expresses part-level selection (`<app>/ui` without\n * `<app>/api`), which `include` (app-name-only) cannot. Unset on every non-wizard invocation.\n */\n presetDef?: DevPreset\n /**\n * Run each discovered API app in its own cmux pane (one workspace, N panes), supervised by a\n * resident process that closes the workspace on signal. Falls back to single-process dev when\n * cmux is absent. Handled by `runCmuxDevServer`, not the in-process `DevServerRunner`.\n */\n cmux?: boolean\n /**\n * Infer the single app to run from the current working directory (equivalent to\n * `--app=<that app>`), so every app can share the identical script\n * `pnpm exec infra-kit dev --self` instead of hardcoding its own folder name.\n * Resolved by the entry point (`resolveSelfAppName`) into `include` before this\n * runner ever sees it; the runner itself does not read `self`.\n */\n self?: boolean\n /**\n * Print the full boot narration (build/discovery/watch steps) to the terminal. Default false:\n * the terminal shows only the server panel, warnings, errors, and restart lines. The FULL detail\n * is written to the session log (`<cacheRoot>/<INFRA_KIT_SESSION>/logs.txt`) regardless of this flag.\n */\n verbose?: boolean\n /**\n * Print each app's registered `METHOD /path` route table at startup. Default false \u2014 the route\n * dump is opt-in so the calm default screen stays glanceable; the routes are always in the log.\n */\n routes?: boolean\n /**\n * Interactive TTY (both stdin+stdout). Set by the entry point; gates the Ink boot UI in {@link run}.\n * When unset, {@link run} falls back to `process.stdout.isTTY`. The runner itself never reads it.\n */\n tty?: boolean\n /**\n * Structured `--json` / MCP mode. When true, {@link run} forces the plain {@link DevRenderer} \u2014 Ink\n * must never seize a machine-readable stream. The runner itself never reads it.\n */\n json?: boolean\n /**\n * Background liveness-probe interval in ms (default {@link DevServerRunner.LIVENESS_INTERVAL_MS}, 5000).\n * A test seam only \u2014 lets a test drive the monitor loop fast; production never sets it.\n */\n livenessIntervalMs?: number\n}\n\n/** Health-probe seam: resolve an endpoint's liveness. Injectable so `ready()` stays deterministic in tests. */\nexport type HealthProbe = (url: string) => Promise<boolean>\n\n/** Default probe: a bounded GET against the `/__health` URL; any non-2xx / network error \u2192 down. */\nconst defaultHealthProbe: HealthProbe = async (url: string): Promise<boolean> => {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(1500) })\n\n return res.ok\n } catch {\n return false\n }\n}\n\ninterface IAppServer {\n app: IApiAppConfig\n server: ServerlessLocalRun\n /** The ACTUAL port bound at start (ephemeral or preferred), reported by `server.start()`. */\n boundPort: number\n /** Layer-B alias host (`<release>.<package>.localhost`) \u2014 the app's only address. */\n alias: string\n}\n\n/**\n * One `.infra-kit/dev-context/<app>.json` fragment. `package` (feeds the vite helper's\n * `readLocalSet`) is the app's package name; `port` is the ACTUAL bound port (the writer\n * IS the binder). `release` lets the helper prefer the runner-recorded slug over its own\n * git derivation; `pid`/`writtenAt` are staleness metadata.\n *\n * `alias` is the registered portless hostname, and `origin` is **the authoritative local target** \u2014 the\n * exact origin the helper must proxy to, published by the runner rather than re-derived by the helper from\n * a template in a separately-versioned repo. The runner is the only party that knows what it actually\n * registered, so it says so; the helper obeys.\n *\n * `proxyPort` is deliberately NOT written any more. The CLI self-updates while `infra-kit/vite` stays\n * pinned per consumer, so a new CLI routinely meets an OLD helper \u2014 and that helper's `withProxyPort`\n * grafts any port other than 80 onto its target. Writing `proxyPort: 443` would therefore have produced\n * `http://<alias>:443`: **plain HTTP into a TLS listener**, silently. Omitting the field leaves the old\n * helper's target ungrafted instead. (That is not by itself a loud failure \u2014 `:80` is bound by portless's\n * redirect server, so it 302s rather than refusing \u2014 which is why the CLI-side version floor, not this\n * omission, is the load-bearing skew guard.)\n */\ninterface DevContextFragment {\n package: string\n port: number\n pid: number\n writtenAt: number\n release: string\n alias: string\n origin: string\n}\n\n/**\n * Lowest published `infra-kit` whose `infra-kit/vite` helper understands the fragment's `origin` field.\n *\n * This is the load-bearing guard against version skew, and skew here is GUARANTEED rather than\n * hypothetical: the CLI is installed globally and **self-updates silently**, while `infra-kit/vite` is\n * PINNED in each consumer's `node_modules`. So a new CLI routinely meets an old helper. An old helper\n * ignores `origin` and rebuilds the target from the consumer's `templates.local` \u2014 which still says\n * `http://` \u2014 and then proxies plain HTTP at a TLS listener. That failure is silent (portless answers :80\n * with a 302 rather than refusing), so nothing downstream would catch it. Refuse at start instead.\n */\nexport const HELPER_VERSION_FLOOR = '0.1.132'\n\n/** `true` when `version` sorts strictly below `floor` (numeric, dot-separated; missing parts are 0). */\nexport const isBelowVersion = (version: string, floor: string): boolean => {\n const parse = (v: string): number[] => {\n return v.split('.').map((part) => {\n return Number.parseInt(part, 10) || 0\n })\n }\n const a = parse(version)\n const b = parse(floor)\n\n for (let i = 0; i < Math.max(a.length, b.length); i += 1) {\n const left = a[i] ?? 0\n const right = b[i] ?? 0\n\n if (left !== right) return left < right\n }\n\n return false\n}\n\n/** Does any package.json in the repo declare `infra-kit` as a dependency? */\nconst declaresInfraKit = (repoRoot: string): boolean => {\n const manifests = [path.join(repoRoot, 'package.json')]\n\n try {\n for (const app of fs.readdirSync(path.join(repoRoot, 'apps'), { withFileTypes: true })) {\n if (!app.isDirectory()) continue\n for (const part of ['api', 'ui']) manifests.push(path.join(repoRoot, 'apps', app.name, part, 'package.json'))\n }\n } catch {\n // No `apps/` dir \u2014 the root manifest alone decides.\n }\n\n return manifests.some((file) => {\n try {\n const pkg = JSON.parse(fs.readFileSync(file, 'utf-8')) as Record<string, Record<string, string> | undefined>\n\n return ['dependencies', 'devDependencies'].some((field) => {\n return pkg[field]?.['infra-kit'] != null\n })\n } catch {\n return false\n }\n })\n}\n\n/**\n * Refuse to start against a consumer-pinned `infra-kit/vite` older than {@link HELPER_VERSION_FLOOR}.\n *\n * Three branches, and the reasoning for each matters:\n * - **Workspace-linked \u2192 SKIP.** In this repo `node_modules/infra-kit` symlinks to `apps/infra-kit/cli`,\n * whose version is the unreleased working tree. Enforcing a floor there would brick `infra-kit dev` on\n * the very repo that develops it.\n * - **Declared but unresolvable \u2192 THROW (fail closed).** The consumer says it uses the helper and we\n * cannot prove which one; guessing is how the silent case ships.\n * - **Not a dependency at all \u2192 SKIP.** No `infra-kit` dependency means no `infra-kit/vite` in play, so\n * there is no helper to be skewed against. (This is also what keeps bare test fixtures runnable.)\n */\nexport const assertHelperVersionFloor = (repoRoot: string): void => {\n const helperDir = path.join(repoRoot, 'node_modules', 'infra-kit')\n\n if (!fs.existsSync(helperDir)) {\n if (!declaresInfraKit(repoRoot)) return\n\n throw new Error(\n `infra-kit dev: this repo depends on infra-kit but node_modules/infra-kit is missing, so the ` +\n `\\`infra-kit/vite\\` helper version cannot be verified. Run \\`pnpm install\\`.`,\n )\n }\n\n // Resolve BOTH sides before comparing: on macOS a temp path realpaths from `/var` to `/private/var`, so\n // an unresolved root would never contain a resolved child and a workspace link would be misread as a\n // published install.\n try {\n const real = fs.realpathSync(helperDir)\n const root = fs.realpathSync(repoRoot)\n\n if (real.startsWith(root + path.sep) && !real.includes(`${path.sep}node_modules${path.sep}`)) return\n } catch {\n // Fall through to the version read.\n }\n\n let version: string\n\n try {\n const pkg = JSON.parse(fs.readFileSync(path.join(helperDir, 'package.json'), 'utf-8')) as { version?: string }\n\n if (typeof pkg.version !== 'string') throw new Error('no version field')\n version = pkg.version\n } catch {\n throw new Error(\n 'infra-kit dev: could not read the version of the pinned `infra-kit/vite` helper ' +\n '(node_modules/infra-kit/package.json). Refusing to start rather than risk proxying plain HTTP at a ' +\n 'TLS listener. Run `pnpm install`.',\n )\n }\n\n if (isBelowVersion(version, HELPER_VERSION_FLOOR)) {\n throw new Error(\n `infra-kit dev: this repo pins infra-kit ${version}, but dev URLs are now HTTPS and the ` +\n `\\`infra-kit/vite\\` helper only understands them from ${HELPER_VERSION_FLOOR}. An older helper would ` +\n `proxy plain HTTP at a TLS listener \u2014 silently. Bump the dependency:\\n` +\n ` pnpm add -D infra-kit@^${HELPER_VERSION_FLOOR}`,\n )\n }\n}\n\n/**\n * Slugified `<release>` for the app's git branch (resolved from the app's own dir), falling back to\n * {@link DEFAULT_RELEASE_SLUG} outside a git repo / on an empty slug. Never throws.\n *\n * A release ALWAYS resolves because it is the first DNS label of every alias, and every app is reached\n * by hostname. The fallback cannot collide the way a branch can: worktrees are what make two checkouts\n * coexist, and a worktree is by definition inside a git repo.\n */\nconst readAppRelease = (cwd: string): string => {\n try {\n // eslint-disable-next-line sonarjs/no-os-command-from-path\n const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, encoding: 'utf-8' }).trim()\n const slug = slugifyRelease(branch)\n\n return slug === '' ? DEFAULT_RELEASE_SLUG : slug\n } catch {\n return DEFAULT_RELEASE_SLUG\n }\n}\n\n/**\n * The one-line `reason` for a `\u25CF failed` row. Endpoint rows are a single terminal line, so a stack\n * trace cannot go there \u2014 it is already in the log tail and the session log. Take the message only,\n * and its first line at that: validation errors like to append their own multi-line dumps.\n *\n * @example\n * errorReason(new Error(\"config is missing field: 'connectionURL'\\n at \u2026\")) // \"config is missing field: 'connectionURL'\"\n */\nconst errorReason = (error: unknown): string => {\n const message = error instanceof Error ? error.message : String(error)\n const firstLine = message.split('\\n')[0]?.trim() ?? ''\n\n return firstLine === '' ? 'unknown error (see the log)' : firstLine\n}\n\nexport class DevServerRunner {\n private readonly monorepoRoot: string\n /**\n * `<cwd>/.infra-kit/dev-context` \u2014 the fragment directory this runner writes its own\n * per-app `<app>.json` into (mirrors {@link LOG_FILE_PATH}'s cwd-relative resolution). The\n * `infra-kit/vite` helper searches up-tree for this dir and merges the fragments.\n */\n private readonly devContextDir: string\n private readonly appServers: IAppServer[] = []\n private watchDebounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n /** Active chokidar watcher in `--watch` mode; closed on {@link shutdown}. */\n private watcher: FSWatcher | null = null\n private static readonly WATCH_DEBOUNCE_MS = 400\n /** Serialized restarts so rapid saves never bind :port while the previous server is still shutting down. */\n private restartWorkChain: Promise<void> = Promise.resolve()\n private static readonly PORT_RELEASE_DELAY_MS = 200\n /** Self-rescheduling backend liveness probe; cleared on {@link shutdown}. Null when unarmed (UI-only). */\n private livenessTimer: ReturnType<typeof setTimeout> | null = null\n /** Consecutive `/__health` failures per app NAME (survives an ephemeral-port rebind on restart). */\n private readonly livenessFailures = new Map<string, number>()\n /** Liveness re-probe cadence. A wedged-but-not-crashed backend is caught within THRESHOLD ticks. */\n private static readonly LIVENESS_INTERVAL_MS = 5000\n /** Consecutive failures before a backend is declared unhealthy \u2014 the anti-flap / restart-window filter. */\n private static readonly LIVENESS_FAILURE_THRESHOLD = 2\n private readonly options: DevServerOptions\n /** Build runner seam \u2014 real turbo shell-out by default, injectable for tests. */\n private readonly runBuild: BuildRunner\n /** `turbo watch` spawn seam \u2014 real detached child by default, injectable for tests. */\n private readonly turboWatchFactory: TurboWatchFactory\n /** Live `turbo watch` engine in `--watch` mode; reaped on {@link shutdown}. */\n private turboWatch: TurboWatchHandle | null = null\n /** `turbo run dev` (frontends) spawn seam \u2014 real detached child by default, injectable for tests. */\n private readonly uiDevFactory: UiDevFactory\n /** Live `turbo run dev` frontend engine in `--ui` mode; reaped on {@link shutdown}. */\n private uiDev: UiDevHandle | null = null\n /**\n * Append stream for the UI child's RAW output tee. Deliberately NOT {@link appendLogFile}: that is an\n * `appendFileSync` (open/write/close syscall per call), fine for the renderer's occasional lines but a\n * blocking write once per chunk during an HMR burst \u2014 on the same event loop that repaints the pinned\n * footer. Opened in {@link startUiDev}, closed in {@link shutdown}.\n */\n private uiLogStream: fs.WriteStream | null = null\n /** `turbo --dry` closure-source seam \u2014 real turbo (via {@link buildClosureMap}'s default) unless injected for tests. */\n private readonly dryRunner: DryRunner | undefined\n /** Apps that threw during {@link startAllApps} \u2014 rendered as `\u25CF failed` rows by {@link printReady}. */\n private readonly failedApps: { app: IApiAppConfig; reason: string }[] = []\n /**\n * The terminal UI \u2014 owns every stdout line + the log-file tee. Either the plain {@link DevRenderer}\n * or the Ink boot UI, selected by {@link run} and injected here; the runner drives it through {@link DevUi}.\n */\n private readonly renderer: DevUi\n /** Health-probe seam (real bounded `/__health` GET by default; injectable for tests). */\n private readonly healthProbe: HealthProbe\n /** Portless driver (Layer B) \u2014 best-effort alias registration; injectable for tests. */\n private readonly proxy: PortlessDriver\n /**\n * The port the portless daemon must be serving TLS on. Fixed at {@link DEFAULT_DEV_PROXY_PORT} (443):\n * it is not configurable, because a port-free `https://` URL can only be served from the implicit HTTPS\n * port. A configurable port would put the port straight back into the URL.\n */\n private readonly proxyPort: number = DEFAULT_DEV_PROXY_PORT\n /** Every `<release>.<package>` alias this runner registered; removed one-by-one in {@link shutdown}. */\n private readonly registeredAliases = new Set<string>()\n /**\n * `{ \"<ui-package>\": { port, alias } }` handed to the turbo child via `INFRA_KIT_UI_PORTS` (computed in\n * printReady).\n *\n * The `alias` is load-bearing, not provenance: the page is served from `https://<alias>` on :443, so\n * vite's HMR client must dial `wss://<alias>` \u2014 left to itself it derives the socket from vite's own\n * bound port, which the browser then blocks as mixed content. Only the runner knows which alias it\n * actually registered, so it publishes it rather than letting the helper re-derive it (a helper that\n * merely *computes* an alias would point HMR at an unregistered host on a bare `vite dev`).\n */\n private uiPortMap: Record<string, { port: number; alias: string }> = {}\n /**\n * Latched by {@link shutdown} before it touches any state. Teardown deregisters aliases and then\n * spends seconds reaping children, so without this latch a watch event \u2014 or a restart already in\n * flight \u2014 re-enters {@link startOneApp} and re-registers an alias that nothing will ever remove.\n */\n private shuttingDown = false\n /**\n * Dependency closure used to scope a shared-package restart, or `null` for \"restart every app\"\n * (the fail-safe {@link selectPackageRestartTargets} already honours). Read at EVENT time, not\n * captured when the watcher is armed: building it shells out to turbo (~1s on a 7-backend repo),\n * and blocking the watcher on it left a window right after `ready` in which a save was silently\n * dropped. The watcher arms immediately on `null` and this is swapped in when turbo answers.\n */\n private closureMap: ClosureMap | null = null\n /** In-flight {@link closureMap} build, awaited by {@link shutdown} so no turbo child outlives the runner. */\n private closureBuild: Promise<void> = Promise.resolve()\n\n constructor(\n options: DevServerOptions = {},\n runBuild: BuildRunner = launchScript,\n turboWatchFactory: TurboWatchFactory = defaultTurboWatchFactory,\n uiDevFactory: UiDevFactory = defaultUiDevFactory,\n dryRunner?: DryRunner,\n renderer?: DevUi,\n healthProbe: HealthProbe = defaultHealthProbe,\n proxy: PortlessDriver = createPortlessDriver(),\n ) {\n this.options = options\n this.runBuild = runBuild\n this.turboWatchFactory = turboWatchFactory\n this.uiDevFactory = uiDevFactory\n this.dryRunner = dryRunner\n this.proxy = proxy\n initLogFile()\n // The renderer owns all terminal output + the log tee; construct it before the first narrate below.\n this.renderer = renderer ?? new DevRenderer({ appendLog: appendLogFile, verbose: this.options.verbose ?? false })\n this.healthProbe = healthProbe\n this.devContextDir = path.join(process.cwd(), '.infra-kit', 'dev-context')\n\n // Walk up from the consumer repo cwd to the monorepo root.\n this.monorepoRoot = findMonorepoRoot(process.cwd())\n\n if (process.env.DOPPLER_PROJECT != null || process.env.DOPPLER_ENVIRONMENT != null) {\n this.renderer.log('\uD83D\uDD10 Doppler env detected (DOPPLER_PROJECT / DOPPLER_ENVIRONMENT)', 'debug')\n }\n }\n\n /**\n * Discover API apps and resolve each app's port + URL prefix. Delegates bare\n * filesystem discovery to {@link discoverApiAppsBare} and per-app resolution to\n * the pure `resolvePort` / `resolvePrefixUrl`, preserving the original behavior.\n */\n private discoverApiApps(devConfig: DevConfig): IApiAppConfig[] {\n return discoverApiAppsBare(this.monorepoRoot).map((app) => {\n return {\n ...app,\n preferredPort: this.resolvePreferredPort(app.name, devConfig),\n prefixUrl: this.resolvePrefixUrl(app.name, devConfig),\n // Default participate; the resolved preset value overrides this in `run()`.\n watchDeps: true,\n }\n })\n }\n\n /**\n * Read the `dev` section from the resolved infra-kit config. Defensive: any\n * failure (not in an infra-kit project, missing/invalid config) resolves to an\n * empty map so the dev-server always falls back to env vars + built-in defaults\n * rather than refusing to start.\n */\n private async loadDevConfig(): Promise<DevConfig> {\n try {\n const config = await getInfraKitConfig()\n\n return config.dev ?? {}\n } catch {\n return {}\n }\n }\n\n /** Thin delegator to the pure {@link normalizeAppIncludePure} over the runner's `--app` list. */\n private normalizeAppInclude(): string[] | null {\n return normalizeAppIncludePure(this.options.include)\n }\n\n /** Thin delegator to the pure {@link resolvePreferredPortPure}, threading env + config. */\n private resolvePreferredPort(appName: string, devConfig: DevConfig): number | undefined {\n return resolvePreferredPortPure(appName, process.env, devConfig)\n }\n\n /** Thin delegator to the pure {@link resolvePrefixUrlPure}. */\n private resolvePrefixUrl(appName: string, devConfig: DevConfig): string {\n return resolvePrefixUrlPure(appName, devConfig)\n }\n\n /** Load the top-level `devServersPresets` map from the resolved infra-kit config (defensive: `{}` on failure). */\n private async loadDevPresets(): Promise<DevPresets> {\n try {\n return (await getInfraKitConfig()).devServersPresets ?? {}\n } catch {\n return {}\n }\n }\n\n /**\n * The preset definition to run: the named preset (`infra-kit dev <preset>`; throws with the\n * available names when unknown), or an `apps`-less preset when no preset was given \u2014 which\n * `resolvePreset` expands to every discovered app + part.\n */\n private resolvePresetDef(devPresets: DevPresets): DevPreset {\n // A wizard-built in-memory preset wins over the named lookup: it already IS the resolved run plan.\n if (this.options.presetDef != null) {\n return this.options.presetDef\n }\n\n const name = this.options.preset\n\n if (name == null) {\n return {}\n }\n\n const def = devPresets[name]\n\n if (!def) {\n const available = Object.keys(devPresets)\n\n throw new Error(\n `Unknown dev preset \"${name}\". Available: ${available.length > 0 ? available.join(', ') : '(none defined in devServersPresets)'}`,\n )\n }\n\n return def\n }\n\n public async start(): Promise<void> {\n // Backend readiness clock (UI is fire-and-forget, so `ready in Xs` is BE-only \u2014 labeled honestly).\n const bootStart = Date.now()\n const include = this.normalizeAppInclude()\n const watch = this.options.watch ?? false\n const devConfig = await this.loadDevConfig()\n\n process.env.POWERTOOLS_DEV ??= 'true'\n process.env.LOG_LEVEL ??= 'DEBUG'\n\n this.renderer.narrate('\uD83D\uDE80 Starting Development Server Runner')\n\n if (watch) {\n this.renderer.narrate('\uD83D\uDC40 Watch mode: will rebuild and restart on file save')\n }\n\n const { apps, uiApps, apiAppsAll, uiAppsAll } = await this.resolveRunPlan(devConfig, include)\n\n if (apps.length === 0 && uiApps.length === 0) {\n this.renderer.log('\u26A0\uFE0F No API or UI apps to run for this preset', 'warn')\n\n return\n }\n\n await this.bringUpProxy(apps)\n await this.buildAll(apps, uiApps, watch)\n\n if (apps.length > 0) {\n this.renderer.bootStep('starting servers')\n await this.startAllApps(apps)\n this.renderer.narrate(\n this.failedApps.length === 0\n ? '\uD83C\uDF89 All servers started!'\n : `\u26A0\uFE0F ${this.appServers.length}/${apps.length} servers started \u2014 ${this.failedApps.length} failed`,\n )\n this.renderer.narrate(\n `\uD83D\uDCDD Handler logs (AWS Powertools, logger.info/debug, etc.) \u2192 this terminal. Runner-only file: ${LOG_FILE_PATH}`,\n )\n }\n\n // Collapse the boot spinner into the calm ready header (BE endpoints + UI reference lines).\n // Runs for a UI-only session too, so it never leaves a blank screen. The route dump is opt-in.\n // The header names the running packages, so it is derived from the post-`--app` sets \u2014 not from\n // `options.preset` (unset for the wizard's in-memory preset) nor from `include` (app names only).\n const target = deriveTargetLabel({\n preset: this.options.preset,\n running: [\n ...apps.map((a) => {\n return `${a.name}/api`\n }),\n ...uiApps.map((a) => {\n return `${a.name}/ui`\n }),\n ],\n discovered: [\n ...apiAppsAll.map((a) => {\n return `${a.name}/api`\n }),\n ...uiAppsAll.map((a) => {\n return `${a.name}/ui`\n }),\n ],\n })\n\n await this.printReady(apps, uiApps, bootStart, target)\n if (this.options.routes) {\n this.printRouteDump()\n }\n\n // Everything the user asked for is dead and there is no UI to fall back on: there is nothing left\n // to serve, watch, or proxy. Resident-but-empty is the worst of both worlds \u2014 it looks like a\n // running dev server and exits 0 when finally interrupted, so a CI step or a script would call it\n // a success. Fail loudly instead. A PARTIAL failure stays resident: the survivors are still useful,\n // and `--watch` can bring the casualties back on the next save.\n if (this.appServers.length === 0 && uiApps.length === 0 && this.failedApps.length > 0) {\n throw new Error(\n `infra-kit dev: no app started (${this.failedApps.length} of ${apps.length} failed). ` +\n `First failure: ${this.failedApps[0]?.reason ?? 'unknown'}`,\n )\n }\n\n this.armWatch(apps, uiApps, watch)\n\n // Frontends last: their delegated `turbo run dev` feeds the live tail below the BE table.\n if (uiApps.length > 0) {\n this.startUiDev(uiApps)\n }\n\n // Watch each running backend's health for the rest of the session. Armed only when a backend is\n // running \u2014 UI liveness is vite's, not ours. A UI-only session has nothing to probe.\n if (this.appServers.length > 0) {\n this.startLivenessMonitor()\n }\n }\n\n /**\n * Resolve WHAT to run: discover the api/ui app parts, resolve the active preset against them, then\n * narrow to the `--app`/`--self` include set. Returns both the filtered run sets (`apps`/`uiApps`)\n * and the full discovered sets (`apiAppsAll`/`uiAppsAll`), which the ready-header target label needs.\n */\n private async resolveRunPlan(\n devConfig: DevConfig,\n include: string[] | null,\n ): Promise<{\n apps: IApiAppConfig[]\n uiApps: DiscoveredUiApp[]\n apiAppsAll: IApiAppConfig[]\n uiAppsAll: DiscoveredUiApp[]\n }> {\n // What to run is a named preset (`infra-kit dev <preset>`), resolved against the discovered app\n // parts (api/ui). No preset \u2192 run everything (`*`). `--app`/`--self` (`include`) further narrow it.\n const apiAppsAll = this.discoverApiApps(devConfig)\n const uiAppsAll = discoverUiAppsBare(this.monorepoRoot)\n const resolved = resolvePreset(this.resolvePresetDef(await this.loadDevPresets()), {\n api: apiAppsAll.map((a) => {\n return a.name\n }),\n ui: uiAppsAll.map((a) => {\n return a.name\n }),\n })\n\n if (resolved.unmatched.length > 0) {\n this.renderer.log(`\u26A0\uFE0F Preset targets not found (skipped): ${resolved.unmatched.join(', ')}`, 'warn')\n }\n\n const apiNames = new Set(\n resolved.targets\n .filter((t) => {\n return t.part === 'api'\n })\n .map((t) => {\n return t.app\n }),\n )\n const uiNames = new Set(\n resolved.targets\n .filter((t) => {\n return t.part === 'ui'\n })\n .map((t) => {\n return t.app\n }),\n )\n const passesInclude = (name: string): boolean => {\n return !include || include.includes(name)\n }\n const watchDepsByApp = new Map(\n resolved.targets\n .filter((t) => {\n return t.part === 'api'\n })\n .map((t) => {\n return [t.app, t.watchDeps] as const\n }),\n )\n const apps = apiAppsAll\n .filter((a) => {\n return apiNames.has(a.name) && passesInclude(a.name)\n })\n .map((a) => {\n return { ...a, watchDeps: watchDepsByApp.get(a.name) ?? a.watchDeps }\n })\n const uiApps = uiAppsAll.filter((a) => {\n return uiNames.has(a.name) && passesInclude(a.name)\n })\n\n return { apps, uiApps, apiAppsAll, uiAppsAll }\n }\n\n /**\n * Layer B: confirm the portless proxy is serving BEFORE any alias registration \u2014 backends register in\n * startOneApp, UIs in printReady, and both need a live daemon. Runs ahead of startAllApps AND startUiDev\n * so a UI-only session checks it too; a proxy that is not up throws here, before anything is spawned.\n *\n * The port is no longer negotiable: every dev URL is `https://<alias>` with no port, which means the one\n * port that can serve them is {@link DEFAULT_DEV_PROXY_PORT} (443, the implicit HTTPS port).\n */\n private async bringUpProxy(apps: IApiAppConfig[]): Promise<void> {\n // Before anything is spawned or aliased: refuse if the consumer's PINNED `infra-kit/vite` predates the\n // HTTPS contract. It would ignore the fragment's `origin` and proxy plain HTTP at a TLS listener.\n assertHelperVersionFloor(findMonorepoRoot(process.cwd()))\n await this.ensureProxy()\n\n if (apps.length > 0) this.assertNoPortConflicts(apps)\n }\n\n /**\n * Build both build phases back to back under one boot line: the API boot closure (dist must exist\n * before servers import handlers) then the UI dependency closure (warmed BEFORE any persistent child\n * so both see a warm cache and the cold-cache double-build race can't corrupt shared dist).\n */\n private async buildAll(apps: IApiAppConfig[], uiApps: DiscoveredUiApp[], watch: boolean): Promise<void> {\n // One boot line covers both build phases: they run back to back, so two boot steps only make\n // the spinner flip between near-identical lines. Never empty \u2014 the no-apps case returned above.\n const bootLabel = [\n apps.length > 0 ? `building ${packageList(apps)}` : '',\n uiApps.length > 0 ? `warming ${packageList(uiApps)}` : '',\n ]\n .filter(Boolean)\n .join(' \u00B7 ')\n\n this.renderer.bootStep(bootLabel)\n\n if (apps.length > 0) await this.buildApps(apps, watch)\n if (uiApps.length > 0) await this.buildUiApps(uiApps)\n }\n\n /**\n * Start the watch engine when there's anything to rebuild \u2014 backends OR frontends. A UI-only session\n * (no API app) still needs the engine so a shared-lib edit rebuilds its dist and vite reloads.\n */\n private armWatch(apps: IApiAppConfig[], uiApps: DiscoveredUiApp[], watch: boolean): void {\n if (!(watch && (this.appServers.length > 0 || uiApps.length > 0))) {\n return\n }\n\n // Arm the watcher FIRST, on the fail-safe `null` map (a shared-package change restarts every app).\n // Awaiting the closure map here instead cost ~1s on a 7-backend repo \u2014 a full second after \"ready\"\n // in which a save was watched by nobody. Scoping is an optimisation; never buy it with a blind window.\n this.setupWatch(apps, uiApps)\n\n this.closureBuild = this.buildClosureMapSafe(apps).then((map) => {\n // A teardown may have overtaken us; assigning is harmless either way (nothing reads it after).\n this.closureMap = map\n })\n }\n\n /**\n * Warm ONLY each UI's dependency closure (`<pkg>^...` \u2014 deps, excluding the UI itself, so no full\n * production `vite build`) with a cache-friendly (non-`--force`) turbo build.\n *\n * This is the ONLY build of that closure: the `turbo run dev` child runs with `--only`, so it no\n * longer re-walks `^build` as a fallback (that walk was pure noise \u2014 see {@link file://./ui-dev.ts}).\n * Still non-fatal, because a failure here is loud rather than silent: vite fails to resolve the\n * missing dep and reports it in the live tail. Warn and continue instead of refusing to start.\n */\n private async buildUiApps(uiApps: DiscoveredUiApp[]): Promise<void> {\n const filters = uiApps\n .map((a) => {\n return `--filter=${a.packageName}^...`\n })\n .join(' ')\n\n try {\n await this.runBuild(\n `pnpm exec turbo run build ${filters} --env-mode=loose --output-logs=errors-only --no-update-notifier`,\n this.renderer.logFn,\n )\n this.renderer.narrate('\u2705 UI deps built')\n } catch (error) {\n this.renderer.log(\n `\u26A0\uFE0F UI dep build failed (continuing; vite will report unresolved deps): ${String(error)}`,\n 'warn',\n )\n }\n }\n\n /**\n * Start the frontends via ONE delegated `turbo run dev` child. Its stdio is PIPED, so the raw output\n * is tee'd verbatim to the runner log while each framework line lands in the renderer's tagged tail \u2014\n * the terminal stays owned by `infra-kit dev`. Reaped on {@link shutdown}. Concurrency \u2265 the persistent\n * UI `dev` task count (turbo hard-errors otherwise).\n */\n private startUiDev(uiApps: DiscoveredUiApp[]): void {\n const names = uiApps\n .map((a) => {\n return a.name\n })\n .join(', ')\n\n this.renderer.narrate(`\uD83C\uDFA8 Starting ${uiApps.length} UI dev server(s) via \\`turbo run dev\\`: ${names}`)\n this.renderer.narrate(' (framework output is routed into the live tail; full detail in the runner log)')\n\n // Hand the runner-assigned UI ports to the vite child so each UI binds exactly the port the ready\n // header already advertised (`strictPort` on the vite side) \u2014 proxy or not, the printed URL and the\n // bound port cannot drift. An empty map (every assignment failed) passes no env, so vite falls back\n // to picking its own free port and printing it.\n const uiPortEnv =\n Object.keys(this.uiPortMap).length > 0 ? { INFRA_KIT_UI_PORTS: JSON.stringify(this.uiPortMap) } : undefined\n\n // Every dev URL is now HTTPS behind portless's PRIVATE CA, which Node's bundled trust store knows\n // nothing about (and Node does not read the macOS keychain, so `portless trust` alone does not help a\n // Node process). Vite's own proxy is covered by the scoped `secure: false`, but anything ELSE the dev\n // loop runs \u2014 a backend calling a sibling's hero URL, a node `fetch`, an e2e runner \u2014 would fail with\n // `SELF_SIGNED_CERT_IN_CHAIN`. Hand the CA down so those clients validate instead of breaking.\n // A pre-set NODE_EXTRA_CA_CERTS wins: it is the user's own trust decision, not ours to overwrite.\n const caEnv =\n process.env.NODE_EXTRA_CA_CERTS == null && fs.existsSync(readCaPath())\n ? { NODE_EXTRA_CA_CERTS: readCaPath() }\n : undefined\n\n // turbo tags each line with the PACKAGE name; the endpoint rows are keyed by APP name (`client/ui`),\n // so map back onto the exact label the ready header already shows. An unrecognized package degrades\n // to its own name rather than being dropped \u2014 a line is never silently lost.\n const tagByPackage = new Map(\n uiApps.map((a) => {\n return [a.packageName, `${a.name}/ui`] as const\n }),\n )\n\n // Non-fatal on a log-write failure: a broken tee must never down the dev session, and the terminal\n // tail still carries every framework line.\n this.uiLogStream = fs.createWriteStream(LOG_FILE_PATH, { flags: 'a' })\n this.uiLogStream.on('error', () => {\n this.uiLogStream = null\n })\n\n this.uiDev = this.uiDevFactory({\n packageNames: uiApps.map((a) => {\n return a.packageName\n }),\n cwd: process.cwd(),\n concurrency: Math.max(uiApps.length + 4, 12),\n env: uiPortEnv || caEnv ? { ...uiPortEnv, ...caEnv } : undefined,\n appendLog: (text) => {\n this.uiLogStream?.write(text)\n },\n onLine: ({ pkg, text }) => {\n this.renderer.event({ tag: tagByPackage.get(pkg) ?? `${pkg}/ui`, text })\n },\n // Surface a silently-dead frontend engine: once `turbo run dev` exits, every UI's live reload\n // stops and no framework line ever reaches the tail again.\n onUnexpectedExit: (detail) => {\n this.reportEngineDeath('UI dev engine (`turbo run dev`)', 'frontends stopped reloading', detail)\n },\n })\n }\n\n /** Render an app list as `name:port, name:port` for log lines. */\n private formatAppList(apps: Array<{ name: string; port: number }>): string {\n return apps\n .map((a) => {\n return `${a.name}:${a.port}`\n })\n .join(', ')\n }\n\n /**\n * Throw (after logging remediation tips) when two apps are EXPLICITLY pinned to the same\n * port. Apps with no explicit port bind an ephemeral `listen(0)` port each (collision-free\n * by construction), so they are excluded from the gate \u2014 otherwise the default multi-app\n * run would false-throw on the shared `DEFAULT_PORT` before dynamic allocation de-conflicts.\n */\n private assertNoPortConflicts(apps: IApiAppConfig[]): void {\n const explicitApps = apps\n .filter((app) => {\n return app.preferredPort != null\n })\n .map((app) => {\n return { name: app.name, port: app.preferredPort! }\n })\n const { duplicatePorts, conflictingApps } = findPortConflicts(explicitApps)\n\n if (duplicatePorts.length === 0) {\n return\n }\n\n this.renderer.log(`\u26A0\uFE0F Port conflict detected! ${duplicatePorts.join(', ')}`, 'error')\n this.renderer.log(`Conflicting apps: ${this.formatAppList(conflictingApps)}`, 'error')\n this.renderer.log('\\n\uD83D\uDCA1 Tip: give each app a distinct port via `{APP}_PORT` env (e.g. `CLIENT_PORT=`,', 'error')\n this.renderer.log(' `SEARCH_ENGINE_PORT=`) or `dev.<app>.port` in infra-kit.json; or run a subset with', 'error')\n this.renderer.log(' `--app=<name>,<name>`.\\n', 'error')\n throw new Error(`Port conflict detected: ${duplicatePorts.join(', ')}`)\n }\n\n /** Build every app via turbo; rethrows the build error after logging stdout/stderr. */\n private async buildApps(apps: IApiAppConfig[], watch: boolean): Promise<void> {\n const filters = apps\n .map((a) => {\n return `--filter=${a.packageName}`\n })\n .join(' ')\n // With `--watch`, always bypass Turbo cache so `tsc` runs and `dist/` matches disk (otherwise watch restarts can be no-ops).\n const buildCmd = `pnpm exec turbo run build ${filters} --env-mode=loose --output-logs=errors-only --no-update-notifier${watch ? ' --force' : ''}`\n\n // No narration here: the `building <pkgs>` boot step already names these exact targets.\n try {\n await this.runBuild(buildCmd, this.renderer.logFn)\n this.renderer.narrate('\u2705 Build complete')\n } catch (buildError) {\n this.renderer.log(`\u274C Build failed: ${String(buildError)}`, 'error')\n if (buildError instanceof Error && buildError.message) {\n this.renderer.log(` ${buildError.message}`, 'error')\n }\n const err = buildError as { stdout?: string; stderr?: string }\n\n if (err.stdout) this.renderer.log(` stdout: ${err.stdout.trim()}`, 'error')\n if (err.stderr) this.renderer.log(` stderr: ${err.stderr.trim()}`, 'error')\n throw buildError\n }\n }\n\n /**\n * Start every app concurrently, collecting the ones that boot; per-app failures are logged,\n * not fatal. Safe to parallelize because each app has a distinct port (guarded up-front) and a\n * distinct `ServerlessLocalRun`, and `startOneApp` no longer mutates cwd. Push order into\n * `appServers` is non-deterministic but nothing depends on it (the table renders from `apps`).\n */\n private async startAllApps(apps: IApiAppConfig[]): Promise<void> {\n await Promise.all(\n apps.map(async (app) => {\n try {\n const started = await this.startOneApp(app)\n\n if (started) {\n this.appServers.push({ app, ...started })\n }\n } catch (error) {\n this.renderer.log(`\u274C Failed to start ${app.name}: ${String(error)}`, 'error')\n // Recorded, not just logged: `printReady` renders the table from `appServers`, so an app\n // that only ever appears in a log line vanishes from the header entirely \u2014 indistinguishable\n // from one that was never requested. `failedApps` is what puts the `\u25CF failed` row back.\n this.failedApps.push({ app, reason: errorReason(error) })\n }\n }),\n )\n }\n\n /**\n * Confirm a portless daemon is serving TLS on {@link DEFAULT_DEV_PROXY_PORT} before any alias is\n * registered. Every dev URL is a hostname served by this daemon, so a proxy that is not up is a FATAL\n * start error, not a degraded mode: there is no second way to reach an app, and a half-started dev loop\n * that silently routes nowhere is worse than a refusal that names the fix.\n *\n * **Probe only \u2014 never start, never elevate.** `:443` is privileged, and portless binds it by re-execing\n * through `sudo` with an inherited stdio, which a detached child cannot answer. The daemon is installed\n * once, out-of-band. There is deliberately no unprivileged fallback: a fallback puts the port back in the\n * URL, which is the whole thing this design removes.\n *\n * Identity is proven **on the wire** (`X-Portless`), never from portless's state files \u2014 those are\n * process-global singletons that any other daemon's start rewrites and any stop deletes, which would make\n * a perfectly healthy `:443` daemon look dead. See {@link defaultIsProxyServing}.\n *\n * @throws When portless is missing, or no portless daemon is serving TLS on the proxy port.\n */\n private async ensureProxy(): Promise<void> {\n // The bin comes from the driver we were handed, never from a fresh resolution behind its back: the fix we\n // print must name the binary THIS driver would run. `null` is not a command to render \u2014 it is a different\n // report, and it is made here rather than passed downstream.\n const bin = this.proxy.binPath()\n\n if (bin == null || !(await this.proxy.isAvailable())) {\n throw new Error(\n 'infra-kit dev: portless is not installed, so no dev URL can resolve. It ships as a dependency of infra-kit \u2014 reinstall with `pnpm install`.',\n )\n }\n\n if (await this.proxy.isProxyServing(this.proxyPort, true)) return\n\n throw new Error(\n `infra-kit dev: no portless daemon is serving HTTPS on :${this.proxyPort}, so no dev URL can resolve. ` +\n 'Install it once (this is the only step that needs root):\\n' +\n ` ${formatPortlessCommand(['service', 'install'], { sudo: true, bin })}\\n` +\n 'Then trust its local CA (no sudo needed):\\n' +\n ` ${formatPortlessCommand(['trust'], { bin })}\\n` +\n '`infra-kit doctor` checks both.',\n )\n }\n\n /**\n * Register `<release>.<package>` \u2192 `port` with portless and return the alias HOST\n * (`<release>.<package>.localhost`). The alias IS the app's only address, so a failure here is fatal\n * rather than a silent downgrade \u2014 an app nobody can reach is not a running app.\n *\n * @throws When the package name yields no legal DNS label, or portless rejects the registration.\n */\n private async registerAppAlias(packageName: string, appDir: string, port: number): Promise<string> {\n const release = readAppRelease(appDir)\n // An npm name is not a DNS label \u2014 see {@link slugifyHostLabel}. `infra-kit/vite` slugifies its\n // own `<packageName>` template token identically, so the proxy target and this alias cannot drift.\n const label = slugifyHostLabel(packageName)\n\n if (label === '') {\n throw new Error(`infra-kit dev: package name \"${packageName}\" has no letters or digits to build a hostname from.`)\n }\n const name = `${release}.${label}`\n\n if (!(await this.proxy.registerAlias(name, port))) {\n throw new Error(`infra-kit dev: portless refused the alias \"${name}\" \u2192 127.0.0.1:${port}.`)\n }\n this.registeredAliases.add(name)\n\n return `${name}.localhost`\n }\n\n private async startOneApp(\n app: IApiAppConfig,\n ): Promise<{ server: ServerlessLocalRun; boundPort: number; alias: string } | null> {\n this.renderer.narrate(`\uD83D\uDD04 Starting ${app.name}...`)\n\n // No `process.chdir` here: `ServerlessLocalRun` reads `serverless.yml` and imports the\n // compiled handler from `controllersPath` (absolute), so the runner never mutates cwd \u2014\n // which is what makes concurrent boot/restart safe.\n const server = new ServerlessLocalRun({\n controllersPath: app.path,\n prefixUrl: app.prefixUrl,\n port: app.preferredPort,\n appName: app.name,\n // Route live request traffic into the renderer's tagged, timestamped tail (`<app>/api \u2026`).\n // This is the structured seam \u2014 independent of the legacy `DEV_SERVER_REQUEST_LOG` raw line \u2014\n // so the app name is threaded in-process and never leaked to spawned turbo/vite children.\n onRequestLog: ({ method, path: reqPath, status, ms }) => {\n // Keep the runner's own `/__health` liveness probes out of the live tail \u2014 they are internal\n // noise, not app traffic. Real handler routes still stream.\n if (reqPath === '/__health') return\n this.renderer.event({ tag: `${app.name}/api`, text: `${method} ${reqPath} ${status} ${ms}ms` })\n },\n })\n\n // `start()` binds an ephemeral (or preferred-then-ephemeral) port and RETURNS the actual\n // one \u2014 consume THAT, never the static preferred hint, so the log/table/health agree.\n const boundPort = await server.start()\n\n // Layer B: (re)point the portless alias to the freshly-bound port (also fires on watch-restart,\n // since startOneApp is the shared start+restart path). Throws if the alias cannot be registered.\n //\n // Unwind the bind on failure: the caller only records `server` in `appServers` once this method\n // RETURNS, so a throw here would otherwise leave a listening fastify that `shutdown()` never sees\n // and never closes \u2014 a port held for the rest of the session.\n let alias: string\n\n try {\n alias = await this.registerAppAlias(app.packageName, app.path, boundPort)\n } catch (error) {\n await server.close().catch(() => {})\n throw error\n }\n\n // Record the ACTUAL bound port + the alias that was actually registered in this runner's own\n // dev-context fragment. Ordered AFTER registerAppAlias: a fragment written before it could only\n // ever claim `alias: undefined`, and the vite helper would fall back to a direct target for an\n // app that IS reachable by name. Inside startOneApp (the shared start+restart path) so a\n // watch-restart refreshes both instead of orphaning a stale fragment (M2). Non-fatal: a\n // fragment-write failure must not down the server.\n try {\n this.writeDevContextFragment(app, boundPort, alias)\n } catch (error) {\n this.renderer.log(`\u26A0\uFE0F Failed to write dev-context fragment for ${app.name}: ${String(error)}`, 'warn')\n }\n\n this.renderer.narrate(`\u2705 ${app.name} started on port ${boundPort}`)\n\n return { server, boundPort, alias }\n }\n\n /**\n * Atomically write this runner's `.infra-kit/dev-context/<app>.json` fragment recording the\n * ACTUAL bound port (REV-5: serialize to a same-dir temp file, then `renameSync` into place, so a\n * concurrent reader \u2014 the vite helper's directory merge \u2014 never observes torn JSON). Each runner\n * writes ONLY its own app's fragment, so cmux panes never clobber each other.\n */\n private writeDevContextFragment(app: IApiAppConfig, boundPort: number, alias: string): void {\n const fragment: DevContextFragment = {\n package: app.packageName,\n port: boundPort,\n pid: process.pid,\n writtenAt: Date.now(),\n release: readAppRelease(app.path),\n alias,\n // The ORIGIN of the hero URL the ready screen prints (`resolveEndpointUrl` appends the app's\n // `prefixUrl` on top of this same alias). Prefix-free on purpose: this is a proxy target, and the\n // frontend supplies its own path.\n origin: `https://${alias}`,\n }\n const target = path.join(this.devContextDir, `${app.name}.json`)\n const tmp = path.join(this.devContextDir, `${app.name}.json.${process.pid}.tmp`)\n\n fs.mkdirSync(this.devContextDir, { recursive: true })\n fs.writeFileSync(tmp, JSON.stringify(fragment, null, 2))\n fs.renameSync(tmp, target)\n }\n\n /** Remove THIS runner's own `<app>.json` fragment on shutdown (never another runner's). */\n private removeDevContextFragment(app: IApiAppConfig): void {\n fs.rmSync(path.join(this.devContextDir, `${app.name}.json`), { force: true })\n }\n\n /** Run restart jobs one after another (watch can fire faster than close + listen). */\n private scheduleRestartWork(work: () => Promise<void>): Promise<void> {\n const run = this.restartWorkChain.then(\n () => {\n return work()\n },\n () => {\n return work()\n },\n )\n\n this.restartWorkChain = run.catch(() => {})\n\n return run\n }\n\n private async delayPortRelease(): Promise<void> {\n await new Promise((r) => {\n return setTimeout(r, DevServerRunner.PORT_RELEASE_DELAY_MS)\n })\n }\n\n /**\n * Schedule a restart of the given apps (1 or N), serialized against other restarts via\n * {@link scheduleRestartWork}. A single-app dist change passes `[app]`; a dependency-package\n * dist change passes every running app. The `turbo watch` engine has already rebuilt `dist/`,\n * so the runner only bounces the fastify server(s) \u2014 no build here.\n */\n private restart(apps: IApiAppConfig[]): Promise<void> {\n if (this.shuttingDown) return Promise.resolve()\n\n return this.scheduleRestartWork(() => {\n // Re-checked inside the chain: this job may have queued behind a restart that was still\n // running when shutdown() latched, so the flag can flip between scheduling and execution.\n if (this.shuttingDown) return Promise.resolve()\n\n return this.runRestart(apps)\n })\n }\n\n /** Resolve the requested apps to their live server slots (dropping any not running). */\n private resolveRestartTargets(apps: IApiAppConfig[]): Array<{ idx: number; app: IApiAppConfig }> {\n return apps\n .map((app) => {\n return {\n idx: this.appServers.findIndex((e) => {\n return e.app.name === app.name\n }),\n app,\n }\n })\n .filter((t) => {\n return t.idx >= 0\n })\n }\n\n private async runRestart(apps: IApiAppConfig[]): Promise<void> {\n const targets = this.resolveRestartTargets(apps)\n\n if (targets.length === 0) return\n\n const label = targets.length === 1 ? targets[0]!.app.name : `${targets.length} apps`\n\n this.renderer.log(`\uD83D\uDD04 Restarting ${label}...`)\n await Promise.all(\n targets.map(async ({ idx }) => {\n try {\n await this.appServers[idx]!.server.close()\n } catch (err) {\n this.renderer.log(` Close warning: ${String(err)}`, 'debug')\n }\n }),\n )\n\n await this.delayPortRelease()\n\n // Thread each restart's OUTCOME (the fresh entry, or `null` on failure) straight into the probe below\n // instead of re-reading `appServers[idx]`: a failed `startOneApp` leaves the stale, now-closed entry in\n // that slot, so re-reading it would probe a freed port \u2014 wasting the full probe timeout and, if the old\n // `close()` also failed, reporting the dead-but-still-listening server as `\u25CF up`. The outcome list makes\n // the no-probe \"down\" branch actually reachable on failure.\n const outcomes = await Promise.all(\n targets.map(async ({ idx, app }) => {\n try {\n const restarted = await this.startOneApp(app)\n\n if (restarted) {\n const entry = { app, ...restarted }\n\n this.appServers[idx] = entry\n\n return { app, entry }\n }\n\n return { app, entry: null }\n } catch (error) {\n this.renderer.log(`\u274C Failed to restart ${app.name}: ${String(error)}`, 'error')\n\n return { app, entry: null }\n }\n }),\n )\n // Show each restarted app's CURRENT bound port: an ephemeral-port app rebinds a fresh port on\n // restart, and the server table is printed only once at boot \u2014 so this line is the only place the\n // new port surfaces in the default (quiet) terminal.\n //\n // Re-probe `/__health` so the summary reports HONEST liveness, not a bare \"restarted\" for a server\n // that binds its port but 500s on the first request. Mirrors printReady's probe (127.0.0.1, never\n // `localhost` \u2014 ServerlessLocalRun binds v4 loopback only). Probes run concurrently; a target whose\n // restart failed (`entry === null`) is reported down without a probe. A single down server downgrades\n // the leading \u2705 to \u26A0\uFE0F so the line reads consistently.\n const probed = await Promise.all(\n outcomes.map(async ({ app, entry }) => {\n const healthy = entry ? await this.healthProbe(DevServerRunner.healthUrl(entry.boundPort)) : false\n const label = entry ? `${app.name}:${entry.boundPort}` : app.name\n\n return { label: `${label} ${healthy ? '\u25CF up' : '\u25CF down'}`, healthy }\n }),\n )\n\n const allHealthy = probed.every((p) => {\n return p.healthy\n })\n\n this.renderer.log(\n `${allHealthy ? '\u2705' : '\u26A0\uFE0F '} Restarted ${probed\n .map((p) => {\n return p.label\n })\n .join(', ')}`,\n )\n }\n\n /**\n * Build the dependency-closure map ({@link buildClosureMap}) for scoped restarts, or `null` on\n * failure (a `turbo --dry` spawn/parse error). `null` is the fail-safe signal: {@link setupWatch}\n * then restarts every launched app on a package change, exactly as it did before scoping \u2014 never\n * a silent dropped restart.\n */\n private async buildClosureMapSafe(apps: IApiAppConfig[]): Promise<ClosureMap | null> {\n try {\n return await buildClosureMap(this.monorepoRoot, apps, this.dryRunner)\n } catch (err) {\n this.renderer.log(\n `\u26A0\uFE0F Dependency-closure map unavailable (${String(err)}); package changes restart all apps`,\n 'warn',\n )\n\n return null\n }\n }\n\n /**\n * Start the long-lived `turbo watch build` engine, then watch compiled `dist/` output to\n * trigger restarts. A change under an app's `dist` restarts that app; a change under a\n * `packages/<pkg>/dist` restarts only the participating backends whose dependency closure\n * includes that package ({@link selectPackageRestartTargets}), keyed per package dir so unrelated\n * packages don't collapse into one debounce bucket. When the closure map is unavailable\n * (`null`) it falls back to restarting every launched app (fail-safe superset). Restarts are\n * build-less \u2014 the engine already rebuilt `dist/`.\n */\n /**\n * Report a persistent engine (`turbo watch build` / `turbo run dev`) that died on its OWN as a single\n * warn line, unless teardown is already underway. Centralises the `shuttingDown` guard and the message\n * shape shared by both engine callbacks \u2014 `superviseChild`'s `killing` latch already suppresses the\n * exit our own `kill()` causes, so this only ever fires on a genuine crash.\n */\n private reportEngineDeath(engine: string, consequence: string, detail: string): void {\n if (this.shuttingDown) return\n this.renderer.log(`\u26A0\uFE0F ${engine} ${detail} \u2014 ${consequence}. Restart \\`infra-kit dev\\`.`, 'warn')\n }\n\n private setupWatch(apps: IApiAppConfig[], uiApps: DiscoveredUiApp[]): void {\n this.turboWatch = this.turboWatchFactory({\n // API apps: dep-inclusive (`...<pkg>`) \u2014 rebuild the backend + its shared-lib closure and restart it.\n depInclusive: apps.map((a) => {\n return a.packageName\n }),\n // UI apps: dep-closure-only (`<pkg>^...`) \u2014 rebuild the frontend's shared libs (so vite reloads on a\n // FE-only lib edit) WITHOUT production-building the UI; vite owns the UI's own live reload.\n depClosure: uiApps.map((a) => {\n return a.packageName\n }),\n cwd: process.cwd(),\n logFile: LOG_FILE_PATH,\n // Surface a silently-dead engine: once `turbo watch build` exits, saves no longer rebuild `dist/`,\n // so no restart ever fires and the session looks healthy while being frozen.\n onUnexpectedExit: (detail) => {\n this.reportEngineDeath('Watch engine (`turbo watch build`)', 'file saves no longer rebuild', detail)\n },\n })\n this.renderer.narrate('\uD83D\uDC40 Watch mode: started `turbo watch build` engine; watching dist output')\n\n const appDistDirs = getAppDistDirs(apps)\n const packageDistDirs = getPackageDistDirs(this.monorepoRoot)\n const allDistDirs = [...appDistDirs, ...packageDistDirs]\n\n if (allDistDirs.length === 0) {\n this.renderer.log('\u26A0\uFE0F No app or package dist directories found to watch (were they built?)', 'warn')\n\n return\n }\n\n const usePoll = process.env.DEV_SERVER_CHOKIDAR_POLL === '1'\n\n const watcher = chokidar.watch(allDistDirs, {\n ignoreInitial: true,\n awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 },\n // Ignore tsc's incremental bookkeeping + sourcemaps: they rewrite on every build\n // (even content-identical ones) and would bounce fastify onto no real change.\n ignored: (p: string): boolean => {\n return p.endsWith('.tsbuildinfo') || p.endsWith('.map')\n },\n ...(usePoll ? { usePolling: true, interval: 400 } : {}),\n })\n\n this.watcher = watcher\n\n if (usePoll) {\n this.renderer.log('\uD83D\uDC40 chokidar: usePolling enabled (DEV_SERVER_CHOKIDAR_POLL=1)', 'debug')\n }\n\n watcher.on('change', (filePath: string) => {\n this.handleDistChange(filePath, apps, appDistDirs, packageDistDirs)\n })\n\n this.renderer.narrate(\n `\uD83D\uDC40 Watching ${appDistDirs.length} app dist + ${packageDistDirs.length} package dist dir(s) for changes...`,\n )\n }\n\n /**\n * Dispatch a single dist-file change to the right restart. A package (shared-lib) change restarts the\n * dependent apps selected from the closure map (or, fail-safe, every app when the map is missing); an\n * app's own dist change restarts just that app. All restarts are debounced.\n */\n private handleDistChange(\n filePath: string,\n apps: IApiAppConfig[],\n appDistDirs: string[],\n packageDistDirs: string[],\n ): void {\n this.renderer.log(`\uD83D\uDC40 dist change detected: ${filePath}`, 'debug')\n\n const change = classifyDistChange(filePath, appDistDirs, packageDistDirs)\n\n if (change.kind === 'package') {\n // Read late: the map may still be building (\u2192 `null` \u2192 restart all), which is correct, just unscoped.\n const targets = selectPackageRestartTargets(apps, this.closureMap, change.packageDir)\n\n if (targets === null) {\n // Fail-safe: no closure map / no package identity \u2192 restart every launched app. Note this\n // ignores per-app `watchDeps: false` opt-outs \u2014 opt-out is best-effort and yields to the\n // fail-safe superset, so a `turbo --dry` failure never silently drops a needed restart.\n this.scheduleDebounced('__packages__', () => {\n return this.restart(apps)\n })\n\n return\n }\n\n // Empty target set = the package is UI-only or every dependent opted out \u2192 no restart.\n // The `packageDir !== undefined` check narrows it to `string` for `packageDebounceKey`;\n // it is always defined here (a non-null `targets` implies a matched package identity).\n if (targets.length > 0 && change.packageDir !== undefined) {\n this.scheduleDebounced(packageDebounceKey(change.packageDir), () => {\n return this.restart(targets)\n })\n }\n\n return\n }\n\n const app = apps.find((a) => {\n return path.join(a.path, 'dist') === change.app\n })\n\n if (!app) return\n\n this.scheduleDebounced(app.name, () => {\n return this.restart([app])\n })\n }\n\n /**\n * Debounce a restart under `key`: cancel any pending timer for the same key and start\n * a fresh {@link DevServerRunner.WATCH_DEBOUNCE_MS} timer, so a burst of saves collapses\n * into one restart. Errors from the scheduled work are logged, never thrown.\n */\n private scheduleDebounced(key: string, work: () => Promise<void>): void {\n if (this.shuttingDown) return\n\n const existing = this.watchDebounceTimers.get(key)\n\n if (existing) clearTimeout(existing)\n\n const timer = setTimeout(() => {\n this.watchDebounceTimers.delete(key)\n if (this.shuttingDown) return\n work().catch((err) => {\n this.renderer.log(`Restart error (${key}): ${String(err)}`, 'error')\n })\n }, DevServerRunner.WATCH_DEBOUNCE_MS)\n\n this.watchDebounceTimers.set(key, timer)\n }\n\n /**\n * The `/__health` probe URL for a backend on `port`. Always `127.0.0.1`, never `localhost`:\n * ServerlessLocalRun binds v4 loopback only, while `localhost` resolves `[::1]` first on modern Node \u2014\n * the same trap `infra-kit/vite` pins its own host around. Probing by name renders a healthy backend as\n * `\u25CF down`. One home for the string shared by boot ({@link printReady}), restart ({@link runRestart}),\n * and the liveness monitor ({@link livenessTick}).\n */\n private static healthUrl(port: number): string {\n return `http://127.0.0.1:${port}/__health`\n }\n\n /**\n * Start the always-on backend liveness monitor: a background probe loop that catches a backend which\n * goes unhealthy WITHOUT crashing the process (a blocked event loop, a wedged dependency, a fastify that\n * stopped serving) \u2014 invisible to both the one-shot {@link printReady} probe and the crash barrier, which\n * only sees thrown faults. Edge-triggered and flap-resistant; see {@link livenessTick}.\n */\n private startLivenessMonitor(): void {\n this.scheduleLivenessTick()\n }\n\n /**\n * Schedule the next liveness tick. A self-rescheduling `setTimeout` (never `setInterval`): a slow tick \u2014\n * every backend timing out its probe \u2014 can never pile up on the next one, and teardown clears exactly one\n * timer. `unref()` so the monitor never keeps the process alive on its own; the fastify servers do that,\n * and lifecycle is owned by {@link shutdown}.\n */\n private scheduleLivenessTick(): void {\n const intervalMs = this.options.livenessIntervalMs ?? DevServerRunner.LIVENESS_INTERVAL_MS\n\n this.livenessTimer = setTimeout(() => {\n // `finally` always reschedules; the trailing `catch` swallows a rejecting probe seam so one bad tick\n // is skipped rather than crashing the loop, and leaves no floating promise.\n this.livenessTick()\n .finally(() => {\n if (!this.shuttingDown) this.scheduleLivenessTick()\n })\n .catch(() => {})\n }, intervalMs)\n this.livenessTimer.unref()\n }\n\n /**\n * One liveness sweep over every running backend. Edge-triggered per app, keyed by app NAME (so the\n * counter survives an ephemeral-port rebind on restart) with the CURRENT boundPort read fresh each tick:\n *\n * - a probe FAILURE increments a per-app counter; crossing {@link LIVENESS_FAILURE_THRESHOLD} consecutive\n * failures logs ONE `\u26A0\uFE0F unhealthy` line. The threshold is what makes a normal watch-restart (server down\n * <1s, a single interval) invisible \u2014 only a genuinely wedged backend stays down across two sweeps.\n * - a probe SUCCESS after the app had crossed the threshold logs ONE `\u2705 recovered` line; the counter\n * always resets on success. Both edges fire once, so a steady state (healthy OR wedged) emits nothing.\n *\n * `/__health` probes are already filtered out of the live request tail ({@link startOneApp}'s\n * `onRequestLog`), so this never spams. Bails once teardown has latched so a closing server is not\n * misread as down (the timer is also cleared in {@link shutdown} before the servers close).\n */\n private async livenessTick(): Promise<void> {\n if (this.shuttingDown) return\n\n await Promise.all(\n this.appServers.map(async ({ app, boundPort }) => {\n const ok = await this.healthProbe(DevServerRunner.healthUrl(boundPort))\n\n // Re-check AFTER the await: shutdown() may have latched and begun closing servers while this probe\n // was in flight, resolving it `false` against a closing socket. Without this a tick that passed the\n // top-of-method bail could log a false `unhealthy` during teardown.\n if (this.shuttingDown) return\n\n const fails = this.livenessFailures.get(app.name) ?? 0\n\n if (ok) {\n if (fails >= DevServerRunner.LIVENESS_FAILURE_THRESHOLD) {\n this.renderer.log(`\u2705 ${app.name}/api recovered`)\n }\n this.livenessFailures.set(app.name, 0)\n\n return\n }\n\n const next = fails + 1\n\n this.livenessFailures.set(app.name, next)\n if (next === DevServerRunner.LIVENESS_FAILURE_THRESHOLD) {\n this.renderer.log(`\u26A0\uFE0F ${app.name}/api unhealthy (/__health not responding)`, 'warn')\n }\n }),\n )\n }\n\n /**\n * Collapse the boot spinner into the calm ready header: one endpoint row per running backend\n * (pre-probed health dot + a resolving URL), one reference line per UI app (vite prints its own\n * URL in the stream below), the watch line, the clickable log path, and a separator rule.\n * Handles a UI-only session (no backend rows) so it never leaves a blank screen. `ready()` itself\n * is synchronous \u2014 health is probed here and passed in resolved.\n */\n private async printReady(\n apps: IApiAppConfig[],\n uiApps: DiscoveredUiApp[],\n bootStart: number,\n target: string,\n ): Promise<void> {\n // Snapshot BE readiness BEFORE probing \u2014 a `\u25CF down` server's probe timeout must not inflate\n // `ready in Xs` (nor is the boot time itself the probe latency).\n const elapsedMs = Date.now() - bootStart\n const health = await Promise.all(\n this.appServers.map(({ boundPort }) => {\n return this.healthProbe(DevServerRunner.healthUrl(boundPort))\n }),\n )\n const endpoints: EndpointRow[] = this.appServers.map(({ app, alias }, i) => {\n return {\n tag: `${app.name}/api`,\n url: resolveEndpointUrl({ prefixUrl: app.prefixUrl, alias }),\n healthy: health[i] ?? null,\n }\n })\n\n // Pre-assign each UI a free port + its portless alias and stash the map for startUiDev's env. Owning\n // the port is what makes the UI's URL knowable before vite prints it, so every UI gets a real endpoint\n // row. No health probe: vite owns its own readiness. Only a UI whose port could not be assigned falls\n // back to a reference line (\"vite prints its URL below\").\n this.uiPortMap = {}\n const uiEndpoints: EndpointRow[] = []\n const uiRefs: UiRef[] = []\n\n for (const ui of uiApps) {\n const assigned = await this.assignUiPort(ui)\n\n if (assigned != null) {\n uiEndpoints.push({\n tag: `${ui.name}/ui`,\n url: resolveEndpointUrl({ prefixUrl: '', alias: assigned.alias }),\n healthy: null,\n })\n } else {\n uiRefs.push({ tag: `${ui.name}/ui` })\n }\n }\n const watch = this.options.watch ?? false\n const appCount = apps.length + uiApps.length\n const pkgCount = getPackageDistDirs(this.monorepoRoot).length\n\n this.renderer.ready({\n target,\n watch,\n // A UI child (`turbo run dev`) launches iff any UI app runs. This is the sticky-footer signal:\n // its piped output feeds a busy live tail, which the scroll-region UI pins the header above.\n // (`uiRefs` can't serve as the signal \u2014 it empties once a UI aliases into an endpoint row, Layer B.)\n hasUiChild: uiApps.length > 0,\n release: readAppRelease(process.cwd()),\n elapsedMs,\n endpoints: [...endpoints, ...uiEndpoints],\n uiRefs,\n failed: this.failedApps.map(({ app, reason }) => {\n return { tag: `${app.name}/api`, reason }\n }),\n watchSummary: `${appCount} app${appCount === 1 ? '' : 's'} \u00B7 ${pkgCount} package${pkgCount === 1 ? '' : 's'}`,\n logPath: homeShorten(LOG_FILE_PATH),\n logHref: LOG_FILE_PATH,\n })\n }\n\n /**\n * Pre-assign a UI a free port, alias it, and record the port in {@link uiPortMap} (handed to the vite\n * child via `INFRA_KIT_UI_PORTS`, which it binds with `strictPort`). Owning the port ahead of vite's own\n * announcement is what makes the URL knowable in time to print it.\n *\n * `null` degrades this UI to a reference line: either its vite config does not wire `infraKitDev()`\n * ({@link DiscoveredUiApp.managedPort}), so it would ignore the assignment and bind its own port \u2014\n * printing the assigned one would be a lie and aliasing it would 502 \u2014 or the free-port probe failed\n * (extremely rare). An alias that portless REFUSES is not degraded here: {@link registerAppAlias} throws,\n * because a UI advertised at a hostname nothing serves is worse than a UI with no row.\n */\n private async assignUiPort(ui: DiscoveredUiApp): Promise<{ port: number; alias: string } | null> {\n if (!ui.managedPort) return null\n\n let port: number\n\n try {\n port = await getFreePort()\n } catch {\n return null\n }\n const alias = await this.registerAppAlias(ui.packageName, ui.path, port)\n\n this.uiPortMap[ui.packageName] = { port, alias }\n\n return { port, alias }\n }\n\n /**\n * Dump each running app's registered `METHOD /path` routes (opt-in via `--routes`) so the\n * emulator is self-describing. Prints to the terminal (not verbose-gated) \u2014 invoking it means\n * the user explicitly asked for the routes. Reads the live set via `getRegisteredRoutes`.\n */\n private printRouteDump(): void {\n if (this.appServers.length === 0) return\n\n this.renderer.log('\uD83D\uDDFA\uFE0F Registered routes:')\n for (const { app, server } of this.appServers) {\n const routes = server.getRegisteredRoutes()\n\n this.renderer.log(` ${app.name} (${routes.length}): ${routes.length > 0 ? routes.join(', ') : '(none)'}`)\n }\n }\n\n /**\n * Stop watching, cancel any pending debounced restart, and close all running servers.\n * Does not exit the process \u2014 the entry point owns exit.\n */\n public async shutdown(): Promise<void> {\n // Latch BEFORE anything else. Everything below assumes no new alias can be registered once\n // teardown begins; `scheduleDebounced` and `restart` read this flag to honour that.\n this.shuttingDown = true\n\n // Release the terminal FIRST: if the Ink boot UI is still mounted (e.g. SIGINT mid-boot), unmount it\n // before any plain write below, so the shutdown lines never clobber a live region. No-op for the\n // plain renderer and idempotent when Ink already unmounted at ready().\n this.renderer.dispose()\n this.renderer.log('\uD83D\uDED1 Shutting down all servers...')\n\n // Silence every restart SOURCE before deregistering aliases below. Ordered first because alias\n // removal is not idempotent against a concurrent `startOneApp`: a chokidar event or an armed\n // debounce timer firing after the removal would re-register an alias into a set nothing drains\n // again, stranding it exactly like the force-quit case the removal is there to prevent.\n for (const timer of this.watchDebounceTimers.values()) {\n clearTimeout(timer)\n }\n this.watchDebounceTimers.clear()\n\n // Stop the liveness monitor BEFORE the servers close, so a final tick can't probe a closing backend and\n // log a false `unhealthy`. The `shuttingDown` latch above also makes an in-flight tick bail and never\n // reschedule; clearing the pending timer here closes the window between ticks.\n if (this.livenessTimer) {\n clearTimeout(this.livenessTimer)\n this.livenessTimer = null\n }\n\n if (this.watcher) {\n await this.watcher.close()\n this.watcher = null\n }\n\n // Drain a restart already in flight. The latch makes every QUEUED job a no-op, but a job that\n // began before the latch is mid `close() \u2192 listen() \u2192 registerAlias()` and must finish, or its\n // alias lands after the removal below. Never rejects (the chain self-catches).\n await this.restartWorkChain\n\n // And the background closure build, so its `turbo --dry` child never outlives the runner.\n await this.closureBuild\n\n // Layer B: deregister every portless alias BEFORE the child reap below. The reap can take\n // seconds (grace + SIGKILL escalation, per child), and a force-quit during it would otherwise\n // strand an alias pointing at a dead backend \u2014 the 502 you hit on the next start.\n //\n // Concurrent and best-effort: `removeAlias` never throws (the driver swallows failures) and each\n // call is self-bounded by the driver's own subprocess timeout, so this caps the pre-reap delay at\n // roughly one timeout regardless of alias count, and `Promise.all` cannot reject. That bound holds\n // only because `availability` is already warm here: an alias can exist only after `registerAlias`,\n // which awaits `isAvailable()`, and `ensureProxy` awaits it during `start()`. Register an alias\n // without a prior availability check and this silently becomes two subprocesses per alias.\n await Promise.all(\n [...this.registeredAliases].map((name) => {\n return this.proxy.removeAlias(name)\n }),\n )\n this.registeredAliases.clear()\n\n // Reap the long-lived engines (group SIGTERM\u2192SIGKILL) so neither writes fresh dist nor\n // holds a port mid-teardown. Reaped here (not only in the entry signal handler) because tests\n // and any non-signal caller invoke shutdown() directly. Awaited so the SIGKILL escalation\n // completes before the entry point's `process.exit`.\n if (this.turboWatch) {\n await this.turboWatch.kill()\n this.turboWatch = null\n }\n\n if (this.uiDev) {\n await this.uiDev.kill()\n this.uiDev = null\n }\n\n // Strictly after the child is reaped: closing the tee first would drop its teardown output.\n if (this.uiLogStream) {\n this.uiLogStream.end()\n this.uiLogStream = null\n }\n\n for (const { app, server } of this.appServers) {\n try {\n await server.close()\n } catch {\n // ignore\n }\n // Remove this runner's own dev-context fragment so a stopped app drops out of the\n // helper's localSet (only its own \u2014 the directory model keeps runners independent).\n this.removeDevContextFragment(app)\n }\n\n // Final terminal-visible confirmation, so Ctrl-C never ends on a bare cursor. `log` (not the\n // verbose-only `narrate`) so the last line the user sees is always infra-kit's.\n //\n // Our own detached `turbo run dev` child writes its teardown to the log, not the TTY. Any pnpm\n // `ELIFECYCLE` still visible after Ctrl-C therefore comes from the `pnpm run` wrapper processes\n // ABOVE us in the shell's foreground process group (nested consumer scripts), which we cannot\n // redirect: the terminal signals the whole group, pnpm dies at once, and the shell redraws its\n // prompt while this teardown is still running. Fixing that means `exec`ing into the binary from\n // the consumer's dev script so no wrapper survives to report a failed child.\n this.renderer.log(`\u2713 dev stopped \u00B7 logs \u2192 ${homeShorten(LOG_FILE_PATH)}`)\n }\n}\n\n/**\n * Select the terminal UI for this run: the persistent Ink UI on an interactive TTY (dynamically imported\n * so React never loads on the non-TTY / `--json` / MCP chunks), else the plain {@link DevRenderer}\n * (returned as `undefined` so the runner constructs its own default). `--json`/MCP always forces plain \u2014\n * Ink must never seize a machine-readable stream.\n *\n * {@link PersistentInkDevUi} covers both shapes of session, branching at {@link DevUi.ready} on whether a\n * UI child owns the TTY, so there is nothing left to gate on here.\n */\nconst selectDevUi = async (options: DevServerOptions): Promise<DevUi | undefined> => {\n const isTTY = options.tty ?? Boolean(process.stdout.isTTY)\n\n if (!isTTY || options.json) {\n return undefined\n }\n\n const { PersistentInkDevUi } = await import('src/tui/dev-ui/persistent-ink-dev-ui')\n\n return new PersistentInkDevUi({ appendLog: appendLogFile, verbose: options.verbose ?? false })\n}\n\n/**\n * Construct a {@link DevServerRunner}, start it, and return the instance so the caller\n * (the CLI entry point) can wire signal handlers to `shutdown()` and own process exit.\n * Selects the boot UI (Ink on a TTY, plain otherwise) before constructing the runner.\n */\nexport async function run(options: DevServerOptions = {}): Promise<DevServerRunner> {\n const renderer = await selectDevUi(options)\n // Positions: options, runBuild, turboWatchFactory, uiDevFactory, dryRunner, renderer \u2014 pass `undefined`\n // for the injectable seams so their ctor defaults apply; only the renderer is chosen here.\n const runner = new DevServerRunner(options, undefined, undefined, undefined, undefined, renderer)\n\n // Unwind a partial boot. By the time `start()` can reject (e.g. a UI alias portless refuses), the\n // backends are already listening, aliased in the portless daemon, and recorded as dev-context\n // fragments on disk. The caller wires `shutdown()` to signals only AFTER this resolves, so without\n // this the process exits leaving external daemon state and on-disk fragments behind \u2014 the next\n // `vite dev` then proxies at an alias nothing serves. Teardown failures must not mask the original\n // boot error, so they are swallowed.\n try {\n await runner.start()\n } catch (error) {\n await runner.shutdown().catch(() => {})\n throw error\n }\n\n return runner\n}\n", "/**\n * Dependency-closure map for scoped `infra-kit dev --watch` restarts (plan Phase 1, Option B).\n *\n * `pnpm` is resolved from PATH \u2014 the same trust posture as the rest of the dev-server, which\n * already shells out to `pnpm exec turbo \u2026`; the args are fixed literals plus discovered package\n * names, never shell-interpolated.\n *\n * Rather than re-derive the workspace graph in-process, infra-kit asks turbo what it would\n * rebuild: `turbo run build --dry=json --filter=...<pkg>` lists every package in an app's build\n * closure (`tasks[].package`). That set is authoritative \u2014 it is exactly what the running\n * `turbo watch build` engine rebuilds \u2014 so there is no drift and no glob-parser/BFS/cycle-guard\n * to maintain here. The runner inverts these per-app closures into `dependentsByPackageDir`\n * (which app folders depend on each `packages/<x>/dist`) and, on a package-dist change, restarts\n * only the dependent backends instead of every one.\n */\nimport { execFile } from 'node:child_process'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { promisify } from 'node:util'\n\nimport { getPackageDistDirs } from './discovery.js'\n\nconst execFileAsync = promisify(execFile)\n\n/** The subset of an app's identity the closure map needs. */\nexport interface ClosureApp {\n /** App folder name (e.g. `client`) \u2014 the restart key in the dependents map. */\n name: string\n /** package.json `name` (e.g. `sls-trvl-client`) \u2014 the turbo `--filter` target. */\n packageName: string\n}\n\n/**\n * Resolve one app's build closure: the set of workspace package **names** turbo would rebuild\n * for it. Injectable so tests drive `buildClosureMap` without spawning a real turbo. A rejection\n * propagates through `Promise.all` so the caller can apply its restart-all fail-safe.\n */\nexport type DryRunner = (packageName: string) => Promise<string[]>\n\n/** The inverted closure: `packages/<x>/dist` \u2192 the app folders that depend on it, plus the dir\u2192name bridge. */\nexport interface ClosureMap {\n /** `packages/<x>/dist` absolute dir \u2192 set of app folder names whose closure includes it. */\n dependentsByPackageDir: Map<string, Set<string>>\n /** `packages/<x>/dist` absolute dir \u2192 that package's package.json `name` (dir \u2260 name for scoped pkgs). */\n packageNameByDir: Map<string, string>\n}\n\n/** Read `<dir>/package.json` `name`, or `undefined` when absent/unreadable (never throws). */\nconst readPackageName = (dir: string): string | undefined => {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8')) as { name?: unknown }\n\n return typeof parsed.name === 'string' ? parsed.name : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Default {@link DryRunner}: `turbo run build --dry=json --filter=...<pkg>` in `root`, returning the\n * unique `tasks[].package` set (the packages turbo would rebuild for `<pkg>`). `...` includes the\n * app's own dependency closure, mirroring the `turbo watch build` filter.\n */\nexport const defaultDryRunner = (root: string): DryRunner => {\n return async (packageName: string): Promise<string[]> => {\n const { stdout } = await execFileAsync(\n 'pnpm',\n ['exec', 'turbo', 'run', 'build', '--dry=json', `--filter=...${packageName}`],\n { cwd: root, maxBuffer: 32 * 1024 * 1024 },\n )\n const parsed = JSON.parse(stdout) as { tasks?: Array<{ package?: unknown }> }\n const names = (parsed.tasks ?? [])\n .map((t) => {\n return t.package\n })\n .filter((p): p is string => {\n return typeof p === 'string'\n })\n\n return [...new Set(names)]\n }\n}\n\n/** Build `packageNameByDir` (and its reverse) for every existing `packages/<x>/dist` under `root`. */\nconst readPackageDirs = (root: string): { packageNameByDir: Map<string, string>; dirByName: Map<string, string> } => {\n const packageNameByDir = new Map<string, string>()\n const dirByName = new Map<string, string>()\n\n for (const distDir of getPackageDistDirs(root)) {\n const name = readPackageName(path.dirname(distDir))\n\n if (name !== undefined) {\n packageNameByDir.set(distDir, name)\n dirByName.set(name, distDir)\n }\n }\n\n return { packageNameByDir, dirByName }\n}\n\n/**\n * Ask turbo for each app's build closure (in parallel) and invert it into {@link ClosureMap}:\n * `packages/<x>/dist` \u2192 the app folders whose closure includes that package. Apps' own packages\n * (not under `packages/`) simply don't appear in the map, so they never scope a package restart.\n *\n * A {@link DryRunner} rejection propagates (via `Promise.all`) so the caller falls back to\n * restart-all \u2014 the map is never silently partial.\n *\n * @example\n * // appX deps pkgA+pkgC; appY deps pkgC only \u2192\n * // dependentsByPackageDir: { '\u2026/pkgA/dist': {appX}, '\u2026/pkgC/dist': {appX, appY} }\n */\nexport const buildClosureMap = async (\n root: string,\n apps: ClosureApp[],\n dryRunner: DryRunner = defaultDryRunner(root),\n): Promise<ClosureMap> => {\n const { packageNameByDir, dirByName } = readPackageDirs(root)\n const dependentsByPackageDir = new Map<string, Set<string>>()\n\n const closures = await Promise.all(\n apps.map(async (app) => {\n return { app, closure: await dryRunner(app.packageName) }\n }),\n )\n\n for (const { app, closure } of closures) {\n for (const pkgName of closure) {\n const distDir = dirByName.get(pkgName)\n\n if (distDir === undefined) continue\n\n const dependents = dependentsByPackageDir.get(distDir) ?? new Set<string>()\n\n dependents.add(app.name)\n dependentsByPackageDir.set(distDir, dependents)\n }\n }\n\n return { dependentsByPackageDir, packageNameByDir }\n}\n\n/** Debounce key for a package-dist change \u2014 distinct per package dir so unrelated packages never collapse into one bucket. */\nexport const packageDebounceKey = (packageDir: string): string => {\n return `__pkg__:${packageDir}`\n}\n\n/**\n * Decide which launched apps a `packages/<pkg>/dist` change should restart. `null` means\n * \"fail-safe: restart all\" \u2014 the closure map is missing (a `--dry` failure) or the change carried\n * no package identity. Otherwise the participating (`watchDeps`) apps whose closure includes that\n * package dir \u2014 possibly an empty array (the package is UI-only, or every dependent opted out),\n * which the caller treats as \"restart nothing\".\n *\n * @example\n * // closure: pkgA \u2192 {appX}; pkgC \u2192 {appX, appY}\n * selectPackageRestartTargets([appX, appY], map, '\u2026/pkgA/dist') // => [appX] (not appY)\n * selectPackageRestartTargets([appX, appY], map, '\u2026/uiOnly/dist') // => [] (restart nothing)\n * selectPackageRestartTargets([appX, appY], null, '\u2026/pkgA/dist') // => null (fail-safe: restart all)\n */\nexport const selectPackageRestartTargets = <T extends { name: string; watchDeps: boolean }>(\n apps: T[],\n closureMap: ClosureMap | null,\n packageDir: string | undefined,\n): T[] | null => {\n if (closureMap === null || packageDir === undefined) return null\n\n const dependents = closureMap.dependentsByPackageDir.get(packageDir) ?? new Set<string>()\n\n return apps.filter((a) => {\n return a.watchDeps && dependents.has(a.name)\n })\n}\n", "/**\n * Pure port / URL-prefix resolution for the dev-server.\n *\n * These functions are intentionally side-effect free: the environment is passed\n * in (never read from `process.env` here) and no cwd / fs access happens. That\n * keeps port precedence and conflict detection unit-testable in isolation.\n */\nimport type { DevConfig } from '../lib/infra-kit-config/index.js'\n\n/** Fallback port when no PORT / {APP}_PORT env var and no config port is set. */\nexport const DEFAULT_PORT = 3010\n\n/**\n * URL prefix applied to each app's routes when nothing is configured. Overridable\n * per app via `dev.<app>.prefixUrl` in infra-kit.json \u2014 this is only the fallback.\n */\nexport const DEFAULT_PREFIX_URL = '/api/v1'\n\n/**\n * Parse a raw port string (env var or config), stripping a single pair of\n * surrounding quotes and treating blank / non-numeric input as \"unset\".\n */\nexport function parsePortString(raw: string | undefined): number | undefined {\n if (raw == null || raw === '') {\n return undefined\n }\n\n const n = parseInt(raw.trim().replace(/^[\"']|[\"']$/g, ''), 10)\n\n return Number.isNaN(n) ? undefined : n\n}\n\n/**\n * Resolve the PORT for an API app (highest priority first):\n *\n * 1. **`{APP}_PORT`** \u2014 e.g. `CLIENT_PORT`, `SEARCH_ENGINE_PORT` (secrets manager or shell)\n * 2. **`PORT`** \u2014 shared fallback (multi-app: use distinct `{APP}_PORT` in env)\n * 3. **`dev.<app>.port`** from infra-kit.json\n * 4. Default {@link DEFAULT_PORT}\n *\n * Per-app env keys use the app folder name in **UPPER_SNAKE_CASE** (hyphens \u2192 underscores).\n */\nexport function resolvePort(appName: string, env: NodeJS.ProcessEnv, devConfig: DevConfig): number {\n return resolvePreferredPort(appName, env, devConfig) ?? DEFAULT_PORT\n}\n\n/**\n * Resolve the EXPLICITLY-configured port for an API app \u2014 `{APP}_PORT`, then `PORT`, then\n * `dev.<app>.port` \u2014 or `undefined` when none is set. Unlike {@link resolvePort} this does\n * NOT fall back to {@link DEFAULT_PORT}: it distinguishes an app the developer pinned to a\n * port (a preferred bind target) from an unconfigured app (which binds ephemeral straight\n * away under dynamic allocation). Used by the dev-server to (a) pick the preferred bind\n * port and (b) relax the conflict gate to explicit ports only.\n */\nexport function resolvePreferredPort(\n appName: string,\n env: NodeJS.ProcessEnv,\n devConfig: DevConfig,\n): number | undefined {\n const prefix = appName.replace(/-/g, '_').toUpperCase()\n const prefixedKey = `${prefix}_PORT`\n\n const fromPrefixed = parsePortString(env[prefixedKey])\n\n if (fromPrefixed != null) {\n return fromPrefixed\n }\n\n const fromPort = parsePortString(env.PORT)\n\n if (fromPort != null) {\n return fromPort\n }\n\n return devConfig[appName]?.port ?? undefined\n}\n\n/**\n * Resolve the URL prefix for an API app: `dev.<app>.prefixUrl` from\n * infra-kit.json, falling back to {@link DEFAULT_PREFIX_URL} (`/api/v1`).\n */\nexport function resolvePrefixUrl(appName: string, devConfig: DevConfig): string {\n return devConfig[appName]?.prefixUrl ?? DEFAULT_PREFIX_URL\n}\n\n/** The apps that collide on a port, plus the raw duplicate-port list for messaging. */\nexport interface PortConflicts {\n /** Ports that appear more than once (one entry per extra occurrence, in scan order). */\n duplicatePorts: number[]\n /** Every app whose resolved port is one of the duplicates. */\n conflictingApps: Array<{ name: string; port: number }>\n}\n\n/**\n * Find apps that resolve to the same port. Returns empty `duplicatePorts` when\n * there is no conflict; the caller decides how to surface / throw.\n */\nexport function findPortConflicts(apps: Array<{ name: string; port: number }>): PortConflicts {\n const ports = apps.map((a) => {\n return a.port\n })\n const duplicatePorts = ports.filter((port, index) => {\n return ports.indexOf(port) !== index\n })\n const conflictingApps = apps.filter((a) => {\n return duplicatePorts.includes(a.port)\n })\n\n return { duplicatePorts, conflictingApps }\n}\n", "import { Logger } from '@aws-lambda-powertools/logger'\nimport type { APIGatewayProxyEvent, APIGatewayProxyEventQueryStringParameters, Context } from 'aws-lambda'\nimport fastify from 'fastify'\nimport * as fs from 'node:fs'\nimport type { Server } from 'node:http'\nimport * as path from 'node:path'\nimport process from 'node:process'\nimport { pathToFileURL } from 'node:url'\nimport { parse as parseYaml } from 'yaml'\n\nimport type { ILogger } from './interfaces.js'\n\nexport interface IServerConfig {\n controllersPath: string\n prefixUrl?: string\n /**\n * PREFERRED bind port. A number is tried first and, on `EADDRINUSE`, the runner falls\n * back to an ephemeral `listen(0)` port. `undefined` (no explicit config) binds an\n * ephemeral port straight away. After {@link ServerlessLocalRun.start} the field is\n * mutated in place to the ACTUAL bound port so `/__health` reports the real port.\n */\n port?: number\n /** App folder name, surfaced by the `/__health` endpoint. Optional. */\n appName?: string\n /**\n * Structured per-request sink. When provided, every response emits `{ method, path, status, ms }`\n * (path already trimmed of any query string) so the caller can tag + timestamp it \u2014 the dev-server\n * routes this into its renderer's live tail. Independent of the env-gated raw stdout line below.\n */\n onRequestLog?: (event: { method: string; path: string; status: number; ms: number }) => void\n}\n\n/** True when a listen error is a port-already-in-use (`EADDRINUSE`) failure. */\nconst isAddressInUse = (error: unknown): boolean => {\n return (error as { code?: string } | null)?.code === 'EADDRINUSE'\n}\n\ntype HandlerResult = Promise<{ body: string; headers: Record<string, string>; statusCode: number }>\n\n/** Default simulated Lambda timeout; overridable via `DEV_SERVER_TIMEOUT_MS`. */\nconst DEFAULT_LAMBDA_TIMEOUT_MS = 30_000\n\n/** Resolve the simulated Lambda timeout (ms), honoring `DEV_SERVER_TIMEOUT_MS` when it parses. */\nconst resolveLambdaTimeoutMs = (): number => {\n const raw = Number.parseInt(process.env.DEV_SERVER_TIMEOUT_MS ?? '', 10)\n\n return Number.isNaN(raw) ? DEFAULT_LAMBDA_TIMEOUT_MS : raw\n}\n\n/**\n * Whether to emit a one-line `<method> <url> \u2192 <status> <ms>ms` log per request.\n * Off by default; opt in with `DEV_SERVER_REQUEST_LOG=1` (kept out of the Powertools\n * JSON logger so the line stays terminal-readable). Mirrors the env-reader precedent\n * of {@link resolveLambdaTimeoutMs}.\n */\nconst isRequestLogEnabled = (): boolean => {\n return process.env.DEV_SERVER_REQUEST_LOG === '1'\n}\n\nexport class ServerlessLocalRun {\n /** Busts Node ESM `import()` cache on each new server instance (watch restart). */\n private readonly importCacheBust: string\n private readonly logger: Logger\n private readonly server: ReturnType<typeof fastify>\n private readonly controllers: Record<\n string,\n {\n action: Record<string, (event: APIGatewayProxyEvent, ctx: Context, log: ILogger) => HandlerResult>\n handler: string\n }\n > = {}\n\n /** `method urlAction` keys reserved synchronously, so duplicates are caught before any async import. */\n private readonly registeredRouteKeys = new Set<string>()\n\n constructor(private readonly serverConfig: IServerConfig) {\n this.importCacheBust = `${Date.now()}`\n this.logger = new Logger({ serviceName: 'LocalServer', logLevel: 'DEBUG' })\n this.serverConfig.prefixUrl = this.serverConfig.prefixUrl ?? ''\n this.server = fastify({ logger: false })\n\n // Add CORS support for local development\n this.server.addHook(\n 'onRequest',\n async (\n request: { method: string },\n reply: { header: (k: string, v: string) => unknown; status: (n: number) => { send: () => void } },\n ) => {\n reply.header('Access-Control-Allow-Origin', '*')\n reply.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS')\n reply.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With')\n\n // Handle preflight OPTIONS requests\n if (request.method === 'OPTIONS') {\n reply.status(204).send()\n }\n },\n )\n\n // Per-request visibility for live dev traffic. Two independent sinks: a structured\n // `onRequestLog` callback (the dev-server tags + timestamps it in its tail) and/or the\n // legacy env-gated raw stdout line (standalone use). Registered only when at least one is on.\n const onRequestLog = this.serverConfig.onRequestLog\n\n if (isRequestLogEnabled() || onRequestLog) {\n this.server.addHook(\n 'onResponse',\n async (request: { method: string; url: string }, reply: { statusCode: number; elapsedTime: number }) => {\n const ms = Math.round(reply.elapsedTime)\n // Trim the query string so the tail shows a clean route, not `?a=b` noise.\n const requestPath = request.url.split('?')[0] ?? request.url\n\n onRequestLog?.({ method: request.method, path: requestPath, status: reply.statusCode, ms })\n if (isRequestLogEnabled()) {\n process.stdout.write(`${request.method} ${request.url} \u2192 ${reply.statusCode} ${ms}ms\\n`)\n }\n },\n )\n }\n }\n\n /**\n * The registered `METHOD /path` route keys (sorted). Excludes the internal\n * `/__health` liveness route, which is registered outside {@link defineRoute} and\n * never added to {@link registeredRouteKeys}. Used by the runner's startup route dump.\n */\n public getRegisteredRoutes(): string[] {\n return [...this.registeredRouteKeys].sort()\n }\n\n /**\n * Boot the server and RETURN the actual bound port. Under dynamic allocation the port is\n * a runtime fact known only after `listen`, so the caller must consume this return value\n * (never the static config port). {@link serverConfig.port} is mutated in place to the\n * bound port so `/__health` reports the real value.\n */\n public async start(): Promise<number> {\n this.registerHealthRoute()\n\n await Promise.all(this.loadRoutes())\n\n const boundPort = await this.listenWithFallback()\n\n this.serverConfig.port = boundPort\n\n this.logger.info(`Server listening on http://127.0.0.1:${boundPort}`, {\n address: `http://127.0.0.1:${boundPort}`,\n })\n\n return boundPort\n }\n\n /**\n * Bind the server and return the ACTUAL bound port. With an explicitly-configured\n * preferred port, try it first and fall back to an ephemeral `listen(0)` on `EADDRINUSE`\n * so extra worktrees never collide; with no preferred port (`undefined`), bind ephemeral\n * straight away. Non-`EADDRINUSE` errors propagate.\n */\n private async listenWithFallback(): Promise<number> {\n const preferred = this.serverConfig.port\n\n if (preferred != null) {\n try {\n await this.server.listen({ port: preferred, host: '127.0.0.1' })\n\n return this.readBoundPort()\n } catch (error) {\n if (!isAddressInUse(error)) {\n throw error\n }\n }\n }\n\n await this.server.listen({ port: 0, host: '127.0.0.1' })\n\n return this.readBoundPort()\n }\n\n /** Read the concrete bound port from the underlying HTTP server after `listen`. */\n private readBoundPort(): number {\n const address = (this.server.server as Server).address()\n\n if (address == null || typeof address === 'string') {\n throw new Error('Server address unavailable after listen()')\n }\n\n return address.port\n }\n\n /** Close the server (for watch/restart). */\n public async close(): Promise<void> {\n const raw = this.server.server as Server\n\n if (typeof raw.closeAllConnections === 'function') {\n raw.closeAllConnections()\n }\n await this.server.close()\n }\n\n /**\n * Register a fixed `GET /__health` liveness route returning 200. The path is\n * unprefixed (serverless.yml routes carry the `prefixUrl`, e.g. `/api/v1/...`),\n * so it never collides with a handler route.\n */\n private registerHealthRoute(): void {\n this.server.route({\n method: 'GET',\n url: '/__health',\n handler: (_request: unknown, reply: { code: (n: number) => { send: (body: unknown) => void } }) => {\n return reply.code(200).send({\n status: 'ok',\n app: this.serverConfig.appName ?? null,\n port: this.serverConfig.port,\n })\n },\n })\n }\n\n private loadRoutes(): Promise<void>[] {\n const serverlessYmlPath = path.join(this.serverConfig.controllersPath, 'serverless.yml')\n const fileContents = fs.readFileSync(serverlessYmlPath, 'utf8')\n const data = parseYaml(fileContents) as {\n functions: Record<string, { events?: Array<{ http?: { method: string; path: string } }>; handler?: string }>\n }\n const p: Promise<void>[] = []\n\n if (!data?.functions) return p\n\n for (const funcDef of Object.values(data.functions)) {\n if (!funcDef?.events?.length) continue\n for (const element of funcDef.events) {\n const http = element?.http\n\n if (!http) continue\n p.push(this.defineRoute(http, funcDef))\n }\n }\n\n return p\n }\n\n private async defineRoute(http: { method: string; path: string }, funcDef: { handler?: string }): Promise<void> {\n let url = http.path.toString()\n\n url = url.replaceAll('{', ':').replaceAll('}', '')\n\n let urlAction = path.posix.join(this.serverConfig.prefixUrl ?? '', url)\n\n urlAction = urlAction[0] === '/' ? urlAction : `/${urlAction}`\n\n const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']\n const method = String(http.method).toUpperCase()\n\n if (!validMethods.includes(method)) {\n throw new Error(`Invalid HTTP method: \"${http.method}\" for URL: ${urlAction}`)\n }\n\n // Key on method + path: two events can share a path but differ by method (e.g. GET/POST /users),\n // so keying on the path alone would let one handler overwrite the other. Reserve the key\n // synchronously (before the first `await`) so concurrent route loads detect a true duplicate.\n const routeKey = `${method} ${urlAction}`\n\n if (this.registeredRouteKeys.has(routeKey)) {\n throw new Error(`Duplicate route: ${routeKey}`)\n }\n this.registeredRouteKeys.add(routeKey)\n\n const handlerStr = funcDef.handler ?? ''\n const parts = handlerStr.split('.')\n const filepath = parts[0] ?? ''\n const handler = parts[1] ?? ''\n\n const controllerPath = path.join(this.serverConfig.controllersPath, `${filepath}.js`)\n const fileUrl = pathToFileURL(controllerPath)\n\n // Search params bust Node's ESM import cache so watch rebuilds load new `dist` output.\n fileUrl.searchParams.set('v', this.importCacheBust)\n const action = (await import(fileUrl.href)) as Record<\n string,\n (event: APIGatewayProxyEvent, ctx: Context, log: ILogger) => HandlerResult\n >\n\n this.controllers[routeKey] = { action, handler }\n\n const traceLogger = this.logger.createChild({ serviceName: 'RequestLogger' })\n\n this.server.route({\n method: method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS',\n url: urlAction,\n handler: async (\n request: { body?: unknown; query?: unknown; params?: unknown; headers?: unknown },\n reply: {\n headers: (h: Record<string, string>) => unknown\n code: (n: number) => { send: (body: unknown) => void }\n },\n ) => {\n const controller = this.controllers[routeKey]\n\n if (!controller) throw new Error(`No controller for ${routeKey}`)\n const handlerFn = controller.action[controller.handler]\n\n if (!handlerFn) throw new Error(`No handler ${controller.handler} for ${urlAction}`)\n const retVal = await handlerFn(\n this.getEventObj(request.body, request.query, request.params, request.headers, method, urlAction),\n this.getContext(),\n traceLogger,\n )\n const responseBody = JSON.parse(retVal.body)\n\n reply.headers(retVal.headers ?? {})\n\n return reply.code(retVal?.statusCode ?? 500).send(responseBody)\n },\n })\n }\n\n private getEventObj(\n requestBody?: unknown,\n queryParams?: unknown,\n pathParameters?: unknown,\n headers?: unknown,\n httpMethod = '',\n path = '',\n ): APIGatewayProxyEvent {\n const retVal = {\n body: requestBody ? JSON.stringify(requestBody) : null,\n headers: (headers ?? {}) as APIGatewayProxyEvent['headers'],\n multiValueHeaders: {},\n httpMethod,\n isBase64Encoded: false,\n path,\n pathParameters: pathParameters ?? null,\n queryStringParameters: (queryParams as APIGatewayProxyEventQueryStringParameters) ?? null,\n multiValueQueryStringParameters: null,\n stageVariables: null,\n requestContext: {\n accountId: '',\n apiId: '',\n authorizer: undefined,\n protocol: '',\n httpMethod,\n identity: {\n accessKey: null,\n accountId: null,\n apiKey: null,\n apiKeyId: null,\n caller: null,\n clientCert: null,\n cognitoAuthenticationProvider: null,\n cognitoAuthenticationType: null,\n cognitoIdentityId: null,\n cognitoIdentityPoolId: null,\n principalOrgId: null,\n sourceIp: 'devIp',\n user: null,\n userAgent: null,\n userArn: null,\n },\n path,\n stage: '',\n requestId: '',\n requestTimeEpoch: 0,\n resourceId: '',\n resourcePath: path,\n },\n resource: path,\n }\n\n ;(retVal as APIGatewayProxyEvent & { source: string }).source = 'aws.events'\n\n return retVal as APIGatewayProxyEvent\n }\n\n private getContext(): Context {\n const startTime = Date.now()\n const timeoutMs = resolveLambdaTimeoutMs()\n const datePart = new Date().toISOString().split('T')[0] ?? ''\n\n return {\n callbackWaitsForEmptyEventLoop: false,\n functionName: 'local-dev',\n functionVersion: '1.0.0',\n invokedFunctionArn: 'arn:aws:lambda:local:000000000000:function:local-dev',\n memoryLimitInMB: '1024',\n awsRequestId: `local-${Date.now()}`,\n logGroupName: '/aws/lambda/local-dev',\n logStreamName: `${datePart}/local`,\n getRemainingTimeInMillis: (): number => {\n return Math.max(0, timeoutMs - (Date.now() - startTime))\n },\n done: (_error?: Error, _result?: unknown): void => {},\n fail: (_error: string | Error): void => {},\n succeed: (_messageOrObject: unknown): void => {},\n }\n }\n}\n", "/*\n * `pnpm` is resolved from PATH \u2014 the same trust posture as the rest of the dev-server, which\n * already shells out to `pnpm exec turbo \u2026`; the args are fixed literals plus discovered package\n * names, never shell-interpolated. Matches the file-level disable in `scripts/build.js`.\n */\n/* eslint-disable sonarjs/no-os-command-from-path */\n/**\n * `turbo watch build` engine for `infra-kit dev --watch`.\n *\n * Spawns ONE long-lived `turbo watch build` child that owns incremental rebuilds and\n * dependency-graph fan-out. Its stdout is teed to the runner log but NEVER parsed for\n * control flow: piped `turbo watch` block-buffers stdout and emits no per-task completion\n * marker for `tsc -b` tasks, so the dev-server derives \"a build finished\" from watching\n * `dist/` output instead (see {@link file://./dev-server.ts} `setupWatch`).\n *\n * The child is a 5-deep tree (`sh \u2192 pnpm \u2192 node \u2192 turbo \u2192 native binary`); killing the\n * wrapper PID orphans the rest. So it is spawned `detached` (its own process group) and\n * torn down with a process-GROUP signal \u2014 verified to fully reap the tree.\n */\nimport { spawn } from 'node:child_process'\nimport * as fs from 'node:fs'\n\nimport { superviseChild } from './managed-child.js'\nimport type { ManagedChild, UnexpectedExitHandler } from './managed-child.js'\n\n/** Handle to the running `turbo watch` child; `kill()` reaps the whole process group. */\nexport type TurboWatchHandle = ManagedChild\n\n/** Injectable spawn seam so tests run the orchestrator without a real turbo child. */\nexport type TurboWatchFactory = (opts: TurboWatchOptions) => TurboWatchHandle\n\nexport interface TurboWatchOptions {\n /**\n * API app package names, watched DEPENDENCY-INCLUSIVE (`--filter=...<pkg>`): rebuild the app AND its\n * dependency closure, so editing a shared lib the backend uses triggers a rebuild + restart.\n */\n depInclusive: string[]\n /**\n * UI app package names, watched DEP-CLOSURE-ONLY (`--filter=<pkg>^...`): rebuild the frontend's shared-lib\n * dependencies but NEVER the UI's own `build` (that would run a production `vite build` \u2014 the UI's live\n * dev is owned by the separate `turbo run dev`/vite child). Covers FE-only libs + UI-only sessions.\n */\n depClosure: string[]\n /** Consumer repo cwd the child runs in (turbo resolves the consumer's own pin here). */\n cwd: string\n /** Runner log file; the child's stdout+stderr are appended to it. */\n logFile: string\n /**\n * Called if the engine dies on its own (not via `kill()`): incremental rebuilds silently stop, so\n * the runner surfaces it. Optional so the injected test factory can ignore it.\n */\n onUnexpectedExit?: UnexpectedExitHandler\n}\n\n/**\n * Build the `--filter=` arg vector: `...<pkg>` (dependency-inclusive) for each API package, `<pkg>^...`\n * (dependencies only, excluding the package itself) for each UI package. Pure/order-stable so the exact\n * emitted args are unit-testable; API-only input reproduces the historical `--filter=...<pkg>` vector.\n *\n * @example\n * buildTurboWatchFilters(['api-a'], ['ui-a']) // => ['--filter=...api-a', '--filter=ui-a^...']\n */\nexport const buildTurboWatchFilters = (depInclusive: string[], depClosure: string[]): string[] => {\n return [\n ...depInclusive.map((name) => {\n return `--filter=...${name}`\n }),\n ...depClosure.map((name) => {\n return `--filter=${name}^...`\n }),\n ]\n}\n\n/**\n * Default factory: spawn `pnpm exec turbo watch build <filters> --continue=dependencies-successful\n * --env-mode=loose` detached, tee output to `logFile`, and reap the process group on `kill()`.\n *\n * Filters come from {@link buildTurboWatchFilters}: `...<api>` (dep-inclusive) rebuilds a backend + its\n * closure; `<ui>^...` (dep-closure-only) rebuilds the frontend's shared libs without production-building\n * the UI. `--continue=dependencies-successful` keeps the watcher alive when one package fails to compile,\n * so a shared-lib type error never tears down the whole engine and the last-good `dist/` keeps serving.\n */\nexport const defaultTurboWatchFactory: TurboWatchFactory = ({\n depInclusive,\n depClosure,\n cwd,\n logFile,\n onUnexpectedExit,\n}) => {\n const filters = buildTurboWatchFilters(depInclusive, depClosure)\n const out = fs.openSync(logFile, 'a')\n const child = spawn(\n 'pnpm',\n ['exec', 'turbo', 'watch', 'build', ...filters, '--continue=dependencies-successful', '--env-mode=loose'],\n { cwd, detached: true, stdio: ['ignore', out, out] },\n )\n\n return superviseChild(child, undefined, onUnexpectedExit)\n}\n", "/*\n * `pnpm` is resolved from PATH \u2014 same trust posture as the rest of the dev-server (which already\n * shells out to `pnpm exec turbo \u2026`); args are fixed literals plus discovered package names, never\n * shell-interpolated. Matches the file-level disable in `scripts/build.js`.\n */\n/* eslint-disable sonarjs/no-os-command-from-path */\n/**\n * Frontend dev engine for `infra-kit dev --ui`.\n *\n * Delegates FE to ONE `turbo run dev` child (turbo owns the `dev` fan-out and concurrency) rather than\n * infra-kit spawning each framework itself \u2014 the same delegation choice already made for builds via\n * `turbo watch`. infra-kit treats UIs opaquely: it runs their `dev` script (vite/vike/astro/\u2026) and\n * never encodes per-framework knowledge.\n *\n * This child's stdio is PIPED, never inherited. With `inherit`, turbo and the framework write straight\n * to the TTY, bypassing the renderer entirely: turbo's run chrome interleaves with the pinned footer,\n * and vite's `Local:` URL contradicts the proxy hero URL the ready header already shows. Piping makes\n * `infra-kit dev` the single owner of the terminal \u2014 every line is tee'd verbatim to the runner log,\n * and the framework's own lines are routed through `onLine` into the renderer's tagged tail, so vite's\n * URLs, HMR notices and compile errors surface INSIDE the UI instead of fighting it.\n *\n * `--only` keeps that tail signal-dense. A `dev` task `dependsOn: [\"^build\"]`, so turbo would otherwise\n * re-walk the whole dependency closure and emit one `cache hit` line per dep \u2014 work the runner already\n * did in `buildUiApps` (`turbo run build <pkg>^...`) before spawning this child. `--only` drops those\n * `^build` tasks from the graph, so the redundant walk never happens. `--output-logs=new-only` collapses\n * any stray cache-hit replay, `--no-update-notifier` drops turbo's \"Update available\" banner, and\n * `--ui=stream` pins line-oriented output (turbo picks it anyway off a pipe, but it is cheap to be\n * explicit, and it is what `parseTurboDevLine`'s `<pkg>:dev:` prefix contract depends on).\n *\n * Detached \u2192 its own process group; reaped as a group (SIGTERM\u2192SIGKILL) via {@link superviseChild}.\n */\nimport { spawn } from 'node:child_process'\nimport process from 'node:process'\nimport type { Readable } from 'node:stream'\n\nimport { superviseChild } from './managed-child.js'\nimport type { ManagedChild, UnexpectedExitHandler } from './managed-child.js'\n\n/** Handle to the running `turbo run dev` child; `kill()` reaps the whole process group. */\nexport type UiDevHandle = ManagedChild\n\n/** Injectable spawn seam so tests run the orchestrator without a real turbo child. */\nexport type UiDevFactory = (opts: UiDevOptions) => UiDevHandle\n\n/** One framework output line, already stripped of turbo's `<pkg>:dev:` prefix. */\nexport interface TurboDevLine {\n /** The turbo package name that emitted the line (e.g. `website-ui`). */\n pkg: string\n /** The framework's own text, ANSI-stripped. */\n text: string\n}\n\nexport interface UiDevOptions {\n /** UI app package names; each becomes an exact `--filter=<pkg>` (turbo runs its `dev` task). */\n packageNames: string[]\n /** Consumer repo cwd the child runs in. */\n cwd: string\n /**\n * Concurrency cap. Must be \u2265 the number of selected UI `dev` tasks (they're persistent) \u2014\n * turbo hard-errors when persistent tasks exceed concurrency (default 10).\n */\n concurrency: number\n /**\n * Extra env merged over `process.env` for the turbo child (Layer B passes `INFRA_KIT_UI_PORTS`).\n * `turbo \u2026 --env-mode=loose` passes the full env through to each vite `dev` task. Omit \u2192 inherit only.\n */\n env?: Record<string, string>\n /** Raw child output (turbo chrome included, ANSI intact) appended verbatim to the runner log. */\n appendLog?: (text: string) => void\n /** One call per framework output line; turbo's own chrome is filtered out first. */\n onLine?: (line: TurboDevLine) => void\n /**\n * Called if the child dies on its own (not via `kill()`): every UI's live reload silently stops, so\n * the runner surfaces it. Optional so the injected test factory can ignore it.\n */\n onUnexpectedExit?: UnexpectedExitHandler\n}\n\n/* eslint-disable no-control-regex, sonarjs/no-control-regex -- terminal escapes and control chars are, by definition, control chars. */\n/** An OSC sequence: ESC `]` ... terminated by BEL or ST. Frameworks emit these for terminal hyperlinks. */\nconst OSC_ESCAPE = /\\u001B\\][\\s\\S]*?(?:\\u0007|\\u001B\\\\)/g\n/** A CSI escape: ESC `[`, parameter bytes, intermediate bytes, final byte. Covers every SGR colour. */\nconst CSI_ESCAPE = /\\u001B\\[[0-9;?]*[\\u0020-\\u002F]*[\\u0040-\\u007E]/g\n/**\n * Every remaining C0 control char except TAB. CR is the dangerous one: written straight to a TTY it\n * snaps the cursor to column 0, smearing the row and the pinned footer below it - exactly the corruption\n * that piping the child was meant to end. LF cannot appear here: `pumpLines` already split on it.\n */\nconst CONTROL_CHARS = /[\\u0000-\\u0008\\u000B-\\u001F\\u007F]/g\n/* eslint-enable no-control-regex, sonarjs/no-control-regex */\n\n/**\n * Make a raw child line safe for the renderer's tail: drop terminal escapes (which would fight the\n * scroll region) and stray control chars (which would move the cursor), leaving plain text the renderer\n * styles itself. Order matters - OSC is matched before the bare-control sweep can eat its leading ESC.\n */\nexport const stripAnsi = (text: string): string => {\n return text.replace(OSC_ESCAPE, '').replace(CSI_ESCAPE, '').replace(CONTROL_CHARS, '')\n}\n\n/** Turbo's per-task line prefix under `--ui=stream`: `<pkg>:dev:`. */\nconst TASK_PREFIX = /^([^\\s:]+):dev:[ \\t]?/\n\n/**\n * Per-task bookkeeping turbo emits before the framework speaks: the cache verdict\n * (`cache bypass, force executing <hash>`) and the echoed command (`$ pnpm exec vike dev`).\n * Neither is dev signal \u2014 both are already implied by the app appearing in the ready header.\n */\nconst isTaskChrome = (text: string): boolean => {\n return /^cache (?:bypass|hit|miss)/.test(text) || text.startsWith('$ ')\n}\n\n/**\n * One raw turbo line \u2192 the framework line to surface, or `null` to drop it.\n *\n * Turbo's run chrome (`\u2022 Packages in scope: \u2026`, `\u2022 Running dev in 1 packages`, `\u2022 Remote caching\n * disabled`, the closing task summary, pnpm's `ELIFECYCLE` teardown) carries no `<pkg>:dev:` prefix.\n * Requiring that prefix drops all of it under one rule instead of chasing a brittle denylist, and what\n * survives is exactly the framework's own stdout/stderr \u2014 including its errors.\n *\n * @example\n * parseTurboDevLine('website-ui:dev: ready in 384 ms') // => { pkg: 'website-ui', text: 'ready in 384 ms' }\n * parseTurboDevLine('\u2022 Remote caching disabled') // => null\n */\nexport const parseTurboDevLine = (raw: string): TurboDevLine | null => {\n const line = stripAnsi(raw).trimEnd()\n const match = TASK_PREFIX.exec(line)\n\n if (match == null) {\n return null\n }\n\n const text = line.slice(match[0].length)\n\n if (text.trim() === '' || isTaskChrome(text.trim())) {\n return null\n }\n\n return { pkg: match[1]!, text }\n}\n\n/**\n * Cap for the newline-less carry buffer. A framework that renders progress with bare CR and never a LF\n * would otherwise grow `pending` without bound for the life of the dev session.\n */\nconst MAX_PENDING_CHARS = 64 * 1024\n\n/**\n * Split a piped stream into lines: tee every chunk verbatim to the log, and route each complete\n * framework line to `onLine`. A trailing partial line is flushed on `end`, so a framework that exits\n * without a final newline never swallows its last (often the most interesting) line.\n *\n * The `data` listener is attached unconditionally \u2014 NOT gated on `onLine`/`appendLog` being set. A piped\n * child whose stdout is never read blocks once the OS pipe buffer fills, so draining is the contract\n * here; the sinks are merely optional consumers of what we drain.\n */\nconst pumpLines = (stream: Readable | null, opts: Pick<UiDevOptions, 'appendLog' | 'onLine'>): void => {\n if (stream == null) {\n return\n }\n\n let pending = ''\n\n const emit = (raw: string): void => {\n const parsed = parseTurboDevLine(raw)\n\n if (parsed != null) opts.onLine?.(parsed)\n }\n\n stream.setEncoding('utf-8')\n stream.on('data', (chunk: string) => {\n opts.appendLog?.(chunk)\n pending += chunk\n\n const lines = pending.split('\\n')\n\n pending = lines.pop() ?? ''\n for (const raw of lines) {\n emit(raw)\n }\n\n if (pending.length > MAX_PENDING_CHARS) {\n emit(pending)\n pending = ''\n }\n })\n stream.on('end', () => {\n if (pending === '') return\n emit(pending)\n pending = ''\n })\n // A readable that emits `error` with no listener THROWS, taking the whole dev session down. The child's\n // lifecycle is already owned by `superviseChild`, so a read error (pty EIO, a pipe torn down mid-SIGKILL)\n // only needs recording, never escalation.\n stream.on('error', (err: Error) => {\n opts.appendLog?.(`[infra-kit] ui dev stream error: ${err.message}\\n`)\n })\n}\n\n/**\n * Default factory: spawn `pnpm exec turbo run dev --filter=<pkg> \u2026 --only` detached with piped stdio,\n * fan its output into the runner log + the renderer's tail, and reap the process group on `kill()`.\n *\n * Exact `--filter=<pkg>` (no `...`) selects only the UI packages, so an API app that also defines a\n * `dev` task is never picked up.\n */\nexport const defaultUiDevFactory: UiDevFactory = ({\n packageNames,\n cwd,\n concurrency,\n env,\n appendLog,\n onLine,\n onUnexpectedExit,\n}) => {\n const filters = packageNames.map((name) => {\n return `--filter=${name}`\n })\n const child = spawn(\n 'pnpm',\n [\n 'exec',\n 'turbo',\n 'run',\n 'dev',\n ...filters,\n `--concurrency=${concurrency}`,\n '--env-mode=loose',\n '--only',\n '--output-logs=new-only',\n '--no-update-notifier',\n '--ui=stream',\n ],\n { cwd, detached: true, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...env } },\n )\n\n pumpLines(child.stdout, { appendLog, onLine })\n pumpLines(child.stderr, { appendLog, onLine })\n\n return superviseChild(child, undefined, onUnexpectedExit)\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Command", "process", "pathToFileURL", "path", "process", "makeLeaf", "command", "buildNode", "commands", "depth", "leftCount", "left", "right", "buildCmuxLayout", "constants", "process", "execFileSync", "process", "DEFAULT_GRACE_MS", "POLL_MS", "REAP_TIMEOUT_MS", "PS_BIN", "parseProcRows", "raw", "line", "rawPid", "rawPpid", "rawPgid", "pid", "ppid", "pgid", "collectDoomedGroups", "rootPid", "rows", "excludePgid", "childrenOf", "row", "siblings", "groups", "seen", "queue", "i", "child", "snapshotProcRows", "isChildOf", "parentPid", "doomedGroupsOf", "ownPgid", "groupAlive", "signalGroups", "pgids", "signal", "killDescendantGroupsNow", "sleep", "ms", "resolve", "waitForExit", "timeoutMs", "deadline", "alive", "superviseChild", "graceMs", "onUnexpectedExit", "killing", "reportUnexpected", "detail", "code", "error", "doomed", "stragglers", "late", "forced", "HANDLED_SIGNALS", "SIGNAL_NUMBERS", "constants", "FALLBACK_SIGNO", "exitCodeForSignal", "signal", "defaultExit", "code", "process", "defaultRegister", "handler", "writeStderr", "message", "writeTeardownFailure", "error", "detail", "registerSignalShutdown", "onSignal", "exit", "register", "forceReap", "killDescendantGroupsNow", "firstSignal", "handle", "buildPaneCommands", "panes", "watch", "app", "targets", "paneTargetsByApp", "presetDef", "byApp", "key", "selectApiApps", "root", "include", "apps", "discoverApiApps", "logDevWorkspace", "ref", "logger", "registerShutdown", "registerSignalShutdown", "signal", "closeCmuxDevWorkspace", "runCmuxDevServer", "options", "findMonorepoRoot", "process", "normalizeAppInclude", "targetsByApp", "commands", "layout", "buildCmuxLayout", "title", "openCmuxDevWorkspace", "heartbeat", "process", "writeStderr", "message", "defaultOnFault", "event", "error", "detail", "defaultRegister", "handler", "registerCrashBarrier", "onFault", "register", "handle", "chokidar", "exec", "execFileSync", "fs", "net", "os", "path", "process", "util", "execFile", "fs", "path", "promisify", "execFileAsync", "promisify", "execFile", "readPackageName", "dir", "parsed", "defaultDryRunner", "root", "packageName", "stdout", "names", "t", "p", "readPackageDirs", "packageNameByDir", "dirByName", "distDir", "getPackageDistDirs", "name", "buildClosureMap", "apps", "dryRunner", "dependentsByPackageDir", "closures", "app", "closure", "pkgName", "dependents", "packageDebounceKey", "packageDir", "selectPackageRestartTargets", "closureMap", "a", "DEFAULT_PREFIX_URL", "parsePortString", "raw", "n", "resolvePreferredPort", "appName", "env", "devConfig", "prefixedKey", "fromPrefixed", "parsePortString", "fromPort", "resolvePrefixUrl", "DEFAULT_PREFIX_URL", "findPortConflicts", "apps", "ports", "a", "duplicatePorts", "port", "index", "conflictingApps", "Logger", "fastify", "fs", "path", "process", "pathToFileURL", "parseYaml", "isAddressInUse", "error", "DEFAULT_LAMBDA_TIMEOUT_MS", "resolveLambdaTimeoutMs", "raw", "isRequestLogEnabled", "ServerlessLocalRun", "serverConfig", "request", "reply", "onRequestLog", "ms", "requestPath", "boundPort", "preferred", "address", "_request", "serverlessYmlPath", "fileContents", "data", "p", "funcDef", "element", "http", "url", "urlAction", "validMethods", "method", "routeKey", "parts", "filepath", "handler", "controllerPath", "fileUrl", "action", "traceLogger", "controller", "handlerFn", "retVal", "responseBody", "requestBody", "queryParams", "pathParameters", "headers", "httpMethod", "startTime", "timeoutMs", "datePart", "_error", "_result", "_messageOrObject", "spawn", "fs", "buildTurboWatchFilters", "depInclusive", "depClosure", "name", "defaultTurboWatchFactory", "cwd", "logFile", "onUnexpectedExit", "filters", "out", "child", "spawn", "superviseChild", "spawn", "process", "OSC_ESCAPE", "CSI_ESCAPE", "CONTROL_CHARS", "stripAnsi", "text", "TASK_PREFIX", "isTaskChrome", "parseTurboDevLine", "raw", "line", "match", "MAX_PENDING_CHARS", "pumpLines", "stream", "opts", "pending", "emit", "parsed", "chunk", "lines", "err", "defaultUiDevFactory", "packageNames", "cwd", "concurrency", "env", "appendLog", "onLine", "onUnexpectedExit", "filters", "name", "child", "spawn", "process", "superviseChild", "LOG_FILE_PATH", "resolveLogFilePath", "getCacheRoot", "process", "INFRA_KIT_SESSION_VAR", "initLogFile", "homeShorten", "p", "home", "os", "packageList", "apps", "a", "execFn", "util", "exec", "launchScript", "script", "logFn", "stderr", "error", "err", "appendLogFile", "text", "getFreePort", "resolve", "reject", "srv", "net", "address", "port", "defaultHealthProbe", "url", "HELPER_VERSION_FLOOR", "isBelowVersion", "version", "floor", "parse", "v", "part", "b", "i", "left", "right", "declaresInfraKit", "repoRoot", "manifests", "app", "file", "pkg", "field", "assertHelperVersionFloor", "helperDir", "real", "root", "readAppRelease", "cwd", "branch", "execFileSync", "slug", "slugifyRelease", "DEFAULT_RELEASE_SLUG", "errorReason", "firstLine", "DevServerRunner", "_DevServerRunner", "DEFAULT_DEV_PROXY_PORT", "options", "runBuild", "turboWatchFactory", "defaultTurboWatchFactory", "uiDevFactory", "defaultUiDevFactory", "dryRunner", "renderer", "healthProbe", "proxy", "createPortlessDriver", "DevRenderer", "findMonorepoRoot", "devConfig", "discoverApiApps", "getInfraKitConfig", "normalizeAppInclude", "appName", "resolvePreferredPort", "resolvePrefixUrl", "devPresets", "name", "def", "available", "bootStart", "include", "watch", "uiApps", "apiAppsAll", "uiAppsAll", "target", "deriveTargetLabel", "discoverUiApps", "resolved", "resolvePreset", "apiNames", "t", "uiNames", "passesInclude", "watchDepsByApp", "bootLabel", "map", "filters", "names", "uiPortEnv", "caEnv", "readCaPath", "tagByPackage", "detail", "explicitApps", "duplicatePorts", "conflictingApps", "findPortConflicts", "buildCmd", "buildError", "started", "bin", "formatPortlessCommand", "packageName", "appDir", "release", "label", "slugifyHostLabel", "server", "ServerlessLocalRun", "method", "reqPath", "status", "ms", "boundPort", "alias", "fragment", "tmp", "work", "run", "r", "e", "targets", "idx", "outcomes", "restarted", "entry", "probed", "healthy", "allHealthy", "buildClosureMap", "engine", "consequence", "appDistDirs", "getAppDistDirs", "packageDistDirs", "getPackageDistDirs", "allDistDirs", "usePoll", "watcher", "chokidar", "filePath", "change", "classifyDistChange", "selectPackageRestartTargets", "packageDebounceKey", "key", "existing", "timer", "intervalMs", "ok", "fails", "next", "elapsedMs", "health", "endpoints", "resolveEndpointUrl", "uiEndpoints", "uiRefs", "ui", "assigned", "appCount", "pkgCount", "reason", "routes", "selectDevUi", "PersistentInkDevUi", "runner", "toPresetDef", "targets", "key", "explainTargetKey", "splitList", "value", "parts", "s", "toDevServerOptions", "raw", "resolveSelfOptions", "options", "resolveSelfAppName", "process", "runDevServer", "rawOptions", "isCmuxAvailable", "runCmuxDevServer", "runner", "run", "registerCrashBarrier", "registerSignalShutdown", "signal", "shouldRunWizard", "tty", "json", "wizardToOptions", "result", "runDevServerCli", "runDevWizard", "error", "isPromptCancellation", "parseAndRun", "argv", "program", "Command", "pathToFileURL"]
7
7
  }