infra-kit 0.1.129 → 0.1.131

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.
Files changed (58) hide show
  1. package/dist/boot-5GFZBDKL.js +2 -0
  2. package/dist/boot-5GFZBDKL.js.map +7 -0
  3. package/dist/chunk-4UUVKJYG.js +2 -0
  4. package/dist/chunk-4UUVKJYG.js.map +7 -0
  5. package/dist/chunk-52WL2IQX.js +4 -0
  6. package/dist/chunk-52WL2IQX.js.map +7 -0
  7. package/dist/chunk-6FU2TRU5.js +2 -0
  8. package/dist/chunk-6FU2TRU5.js.map +7 -0
  9. package/dist/chunk-6YAKVQZN.js +4 -0
  10. package/dist/chunk-6YAKVQZN.js.map +7 -0
  11. package/dist/chunk-C2HRONYA.js +6 -0
  12. package/dist/chunk-C2HRONYA.js.map +7 -0
  13. package/dist/chunk-CHETBZ6M.js +3 -0
  14. package/dist/chunk-CHETBZ6M.js.map +7 -0
  15. package/dist/chunk-F6VCGS3D.js +2 -0
  16. package/dist/chunk-F6VCGS3D.js.map +7 -0
  17. package/dist/chunk-LENH44GY.js +215 -0
  18. package/dist/chunk-LENH44GY.js.map +7 -0
  19. package/dist/chunk-LTKR3F4N.js +2 -0
  20. package/dist/chunk-LTKR3F4N.js.map +7 -0
  21. package/dist/chunk-O3YJM6IR.js +2 -0
  22. package/dist/chunk-O3YJM6IR.js.map +7 -0
  23. package/dist/chunk-SR2GFEY7.js +2 -0
  24. package/dist/chunk-SR2GFEY7.js.map +7 -0
  25. package/dist/chunk-TKZINCST.js +2 -0
  26. package/dist/chunk-TKZINCST.js.map +7 -0
  27. package/dist/chunk-VJPLVBRA.js +5 -0
  28. package/dist/chunk-VJPLVBRA.js.map +7 -0
  29. package/dist/chunk-X2L4F2VM.js +3 -0
  30. package/dist/chunk-X2L4F2VM.js.map +7 -0
  31. package/dist/cli.js +4 -19
  32. package/dist/cli.js.map +4 -4
  33. package/dist/dev-server.js +20 -16
  34. package/dist/dev-server.js.map +4 -4
  35. package/dist/dev-wizard-run-7HEKSAS2.js +2 -0
  36. package/dist/dev-wizard-run-7HEKSAS2.js.map +7 -0
  37. package/dist/lib/release-slug/release-slug.d.ts +26 -2
  38. package/dist/lib/vite/vite.d.ts +18 -4
  39. package/dist/mcp.js +1 -1
  40. package/dist/mcp.js.map +3 -3
  41. package/dist/persistent-ink-dev-ui-4U4B2B7C.js +8 -0
  42. package/dist/persistent-ink-dev-ui-4U4B2B7C.js.map +7 -0
  43. package/dist/program.js +2 -0
  44. package/dist/program.js.map +7 -0
  45. package/dist/update-check.js +2 -0
  46. package/dist/update-check.js.map +7 -0
  47. package/dist/vite.js +1 -1
  48. package/package.json +6 -3
  49. package/dist/boot-SGM5RVLJ.js +0 -2
  50. package/dist/boot-SGM5RVLJ.js.map +0 -7
  51. package/dist/chunk-2PZRQHWF.js +0 -2
  52. package/dist/chunk-2PZRQHWF.js.map +0 -7
  53. package/dist/chunk-6RRAK2QO.js +0 -5
  54. package/dist/chunk-6RRAK2QO.js.map +0 -7
  55. package/dist/chunk-IX2A34UU.js +0 -2
  56. package/dist/chunk-IX2A34UU.js.map +0 -7
  57. package/dist/chunk-V5IDN7ZM.js +0 -162
  58. package/dist/chunk-V5IDN7ZM.js.map +0 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/entry/dev-server.ts", "../src/dev/cmux-dev.ts", "../src/dev/cmux-layout.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/managed-child.ts", "../src/dev/ui-dev.ts"],
4
- "sourcesContent": ["/**\n * Long-running CLI entry for the local dev-server. Owns flag parsing, OS signal\n * handling, and process exit \u2014 the orchestrator (`src/dev/dev-server`) is signal-\n * 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 { run } from 'src/dev/dev-server'\nimport type { DevServerOptions } from 'src/dev/dev-server'\nimport { resolveSelfAppName } from 'src/dev/discovery'\nimport { isCmuxAvailable } from 'src/integrations/cmux'\n\n/** Raw option object as produced by Commander (comma-joined strings). */\nexport interface DevCliOptions {\n watch?: boolean\n app?: string\n /** Named preset positional (`infra-kit dev <preset>`); selects launch targets from `devPresets`. */\n preset?: string\n cmux?: boolean\n self?: boolean\n verbose?: boolean\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 cmux: raw.cmux ?? false,\n self: raw.self ?? false,\n verbose: raw.verbose ?? 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. This is the ONLY place SIGINT/SIGTERM handlers and\n * `process.exit` live for the dev-server feature.\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 let shuttingDown = false\n\n const shutdown = (signal: NodeJS.Signals): void => {\n if (shuttingDown) return\n shuttingDown = true\n\n void (async () => {\n try {\n process.stdout.write(`\\nReceived ${signal}, shutting down dev-server...\\n`)\n await runner.shutdown()\n } finally {\n process.exit(0)\n }\n })()\n }\n\n process.on('SIGINT', () => {\n return shutdown('SIGINT')\n })\n process.on('SIGTERM', () => {\n return shutdown('SIGTERM')\n })\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 devPresets preset (or all apps)')\n .argument('[preset]', 'Named preset from devPresets (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(\n '-V, --verbose',\n 'Print full boot narration (default: quiet; full detail always in .infra-kit/dev-server.log)',\n )\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 console.error(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'\n\n/** Build the per-pane primitive command for each app (append `--watch` when requested). */\nexport const buildPaneCommands = (appNames: string[], watch: boolean): string[] => {\n return appNames.map((name) => {\n return `pnpm exec infra-kit dev --app=${name}${watch ? ' --watch' : ''}`\n })\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` (once, guarded\n * against a double-fire) and then exit \u2014 this is the supervisor's whole teardown.\n */\nconst registerShutdown = (ref: string): void => {\n let shuttingDown = false\n\n const shutdown = (signal: NodeJS.Signals): void => {\n if (shuttingDown) return\n shuttingDown = true\n\n void (async () => {\n try {\n logger.info(`\\nReceived ${signal}, closing cmux dev workspace ${ref}...`)\n await closeCmuxDevWorkspace(ref)\n } finally {\n process.exit(0)\n }\n })()\n }\n\n process.on('SIGINT', () => {\n return shutdown('SIGINT')\n })\n process.on('SIGTERM', () => {\n return shutdown('SIGTERM')\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 const commands = buildPaneCommands(\n apps.map((app) => {\n return 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 * 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 `<cwd>/.infra-kit/dev-server.log`. 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 * as path from 'node:path'\nimport process from 'node:process'\nimport util from 'node:util'\n\nimport type { DevConfig, DevPreset, DevPresets } from 'src/lib/infra-kit-config'\nimport { getInfraKitConfig } from 'src/lib/infra-kit-config'\nimport { slugifyRelease } from 'src/lib/release-slug'\n\nimport { buildClosureMap, packageDebounceKey, selectPackageRestartTargets } from './dep-closure.js'\nimport type { ClosureMap, DryRunner } from './dep-closure.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 { resolvePreset } from './presets.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 consumer repo's `.infra-kit/` dir at startup. */\nlet LOG_FILE_PATH = path.join(process.cwd(), '.infra-kit', 'dev-server.log')\n\n/** When false (default), boot-narration steps are suppressed from the terminal but still written to the log file. Set from `--verbose`. */\nlet VERBOSE = false\n\n/** Resolve `<cwd>/.infra-kit/dev-server.log`, ensure the dir exists, and clear the file. */\nfunction initLogFile(): void {\n LOG_FILE_PATH = path.join(process.cwd(), '.infra-kit', 'dev-server.log')\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\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). */\nfunction appendLogFile(text: string): void {\n fs.appendFileSync(LOG_FILE_PATH, text)\n}\n\n/** Write a message to the console (routed by level) and tee it to the log file. */\nfunction log(message: string, level: 'info' | 'warn' | 'error' | 'debug' = 'info'): void {\n if (level === 'error') {\n console.error(message)\n } else if (level === 'warn') {\n console.warn(message)\n } else if (level === 'debug') {\n // Debug chatter (dist-change ticks on every save, Doppler banner, build stderr) reaches the\n // terminal only in --verbose \u2014 otherwise it would defeat the quiet default. Always tee'd below.\n if (VERBOSE) process.stdout.write(`${message}\\n`)\n } else {\n process.stdout.write(`${message}\\n`)\n }\n\n appendLogFile(`[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}\\n`)\n}\n\n/**\n * A boot-narration step: ALWAYS tee'd to the log file, but printed to the terminal ONLY when\n * `--verbose` is set. Keeps the default terminal calm while the log file stays complete.\n */\nfunction logStep(message: string): void {\n if (VERBOSE) process.stdout.write(`${message}\\n`)\n appendLogFile(`[${new Date().toISOString()}] [INFO] ${message}\\n`)\n}\n\n/** Write pre-formatted table lines to stdout and tee them to the log file. */\nfunction logTable(lines: string[]): void {\n const output = `${lines.join('\\n')}\\n`\n\n process.stdout.write(`${output}\\n`)\n appendLogFile(`${output}\\n`)\n}\n\n/** Center `s` within `width` columns (clamped, so a too-long string is never truncated). */\nfunction center(s: string, width: number): string {\n const pad = Math.max(0, width - s.length)\n const left = Math.floor(pad / 2)\n\n return `${' '.repeat(left)}${s}${' '.repeat(pad - left)}`\n}\n\n/**\n * Render a box-drawn table whose column widths are computed from the longest cell in\n * each column, so every emitted line shares one width regardless of app-name length\n * (the old fixed `padEnd` misaligned on long names). Borders are generated from the\n * same widths, so they always match the rows.\n */\nfunction renderTable(title: string, headers: string[], rows: string[][]): string[] {\n const widths = headers.map((header, i) => {\n return Math.max(\n header.length,\n ...rows.map((row) => {\n return (row[i] ?? '').length\n }),\n )\n })\n // Grow the last column if the title is wider than the columns, so the title band never clips.\n const columnsInner =\n widths.reduce((sum, w) => {\n return sum + w + 2\n }, 0) +\n (widths.length - 1)\n\n const lastIdx = widths.length - 1\n\n if (lastIdx >= 0 && title.length + 2 > columnsInner) {\n widths[lastIdx] = (widths[lastIdx] ?? 0) + (title.length + 2 - columnsInner)\n }\n\n const inner =\n widths.reduce((sum, w) => {\n return sum + w + 2\n }, 0) +\n (widths.length - 1)\n const blocks = widths.map((w) => {\n return '\u2500'.repeat(w + 2)\n })\n const rowLine = (cells: string[]): string => {\n return `\u2502 ${cells\n .map((c, i) => {\n return (c ?? '').padEnd(widths[i]!)\n })\n .join(' \u2502 ')} \u2502`\n }\n\n return [\n '',\n `\u250C${'\u2500'.repeat(inner)}\u2510`,\n `\u2502${center(title, inner)}\u2502`,\n `\u251C${blocks.join('\u252C')}\u2524`,\n rowLine(headers),\n `\u251C${blocks.join('\u253C')}\u2524`,\n ...rows.map(rowLine),\n `\u2514${blocks.join('\u2534')}\u2518`,\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 `devPresets` 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 * 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 `.infra-kit/dev-server.log` regardless of this flag.\n */\n verbose?: boolean\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}\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 */\ninterface DevContextFragment {\n package: string\n port: number\n pid: number\n writtenAt: number\n release?: string\n}\n\n/**\n * Slugified `<release>` for the app's git branch (resolved from the app's own dir), or\n * `undefined` outside a git repo / on an empty slug. Never throws \u2014 a missing repo just\n * omits the recorded slug, and the vite helper falls back to its own git derivation.\n */\nconst readAppRelease = (cwd: string): string | undefined => {\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 === '' ? undefined : slug\n } catch {\n return undefined\n }\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 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 /** `turbo --dry` closure-source seam \u2014 real turbo (via {@link buildClosureMap}'s default) unless injected for tests. */\n private readonly dryRunner: DryRunner | undefined\n\n constructor(\n options: DevServerOptions = {},\n runBuild: BuildRunner = launchScript,\n turboWatchFactory: TurboWatchFactory = defaultTurboWatchFactory,\n uiDevFactory: UiDevFactory = defaultUiDevFactory,\n dryRunner?: DryRunner,\n ) {\n this.options = options\n this.runBuild = runBuild\n this.turboWatchFactory = turboWatchFactory\n this.uiDevFactory = uiDevFactory\n this.dryRunner = dryRunner\n initLogFile()\n VERBOSE = this.options.verbose ?? false\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 logStep(`Monorepo root: ${this.monorepoRoot}`)\n\n if (process.env.DOPPLER_PROJECT != null || process.env.DOPPLER_ENVIRONMENT != null) {\n 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 `devPresets` map from the resolved infra-kit config (defensive: `{}` on failure). */\n private async loadDevPresets(): Promise<DevPresets> {\n try {\n return (await getInfraKitConfig()).devPresets ?? {}\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 a default `{ apps: { '*': {} } }` (every discovered app +\n * part) when no preset was given.\n */\n private resolvePresetDef(devPresets: DevPresets): DevPreset {\n const name = this.options.preset\n\n if (name == null) {\n return { apps: { '*': {} } }\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 devPresets)'}`,\n )\n }\n\n return def\n }\n\n public async start(): Promise<void> {\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 logStep('\uD83D\uDE80 Starting Development Server Runner')\n\n if (watch) {\n logStep('\uD83D\uDC40 Watch mode: will rebuild and restart on file save')\n }\n logStep(`\uD83D\uDCC2 Monorepo root: ${this.monorepoRoot}`)\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 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 if (apps.length === 0 && uiApps.length === 0) {\n log('\u26A0\uFE0F No API or UI apps to run for this preset', 'warn')\n\n return\n }\n\n // Build the API boot closure (dist must exist before servers import handlers).\n if (apps.length > 0) {\n logStep(\n `\uD83D\uDCE6 Discovered ${apps.length} API app(s): ${apps\n .map((a) => {\n return a.name\n })\n .join(', ')}`,\n )\n this.assertNoPortConflicts(apps)\n await this.buildApps(apps, watch)\n }\n\n // Warm the UI dependency closure BEFORE spawning any persistent child (turbo watch / turbo run\n // dev), so both see a warm cache and the cold-cache double-build race can't corrupt shared dist.\n if (uiApps.length > 0) {\n await this.buildUiApps(uiApps)\n }\n\n if (apps.length > 0) {\n await this.startAllApps(apps)\n logStep('\uD83C\uDF89 All servers started!')\n this.printServerTable()\n this.printRouteDump()\n logStep(\n `\uD83D\uDCDD Handler logs (AWS Powertools, logger.info/debug, etc.) \u2192 this terminal. Runner-only file: ${LOG_FILE_PATH}`,\n )\n }\n\n // Start the watch engine when there's anything to rebuild \u2014 backends OR frontends. A UI-only\n // session (no API app) still needs the engine so a shared-lib edit rebuilds its dist and vite reloads.\n if (watch && (this.appServers.length > 0 || uiApps.length > 0)) {\n const closureMap = await this.buildClosureMapSafe(apps)\n\n this.setupWatch(apps, uiApps, closureMap)\n }\n\n // Frontends last: their delegated `turbo run dev` streams raw to the terminal below the BE table.\n if (uiApps.length > 0) {\n this.startUiDev(uiApps)\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. Non-fatal: if it\n * fails, `turbo run dev`'s own `^build` retries and surfaces the error in its streamed output.\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 logStep('\uD83D\uDD28 Warming UI dependency build cache (turbo)...')\n try {\n await this.runBuild(`pnpm exec turbo run build ${filters} --env-mode=loose`, log)\n logStep('\u2705 UI deps built')\n } catch (error) {\n log(`\u26A0\uFE0F UI dep warm build failed (continuing; turbo run dev will build): ${String(error)}`, 'warn')\n }\n }\n\n /**\n * Start the frontends via ONE delegated `turbo run dev` child (streamed to the terminal). Reaped\n * on {@link shutdown}. Concurrency \u2265 the persistent 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 logStep(`\uD83C\uDFA8 Starting ${uiApps.length} UI dev server(s) via \\`turbo run dev\\`: ${names}`)\n logStep(' (framework dev output streams below; each prints its own local URL)')\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 })\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 log(`\u26A0\uFE0F Port conflict detected! ${duplicatePorts.join(', ')}`, 'error')\n log(`Conflicting apps: ${this.formatAppList(conflictingApps)}`, 'error')\n log('\\n\uD83D\uDCA1 Tip: give each app a distinct port via `{APP}_PORT` env (e.g. `CLIENT_PORT=`,', 'error')\n log(' `SEARCH_ENGINE_PORT=`) or `dev.<app>.port` in infra-kit.json; or run a subset with', 'error')\n 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${watch ? ' --force' : ''}`\n\n logStep('\uD83D\uDD28 Building API apps (turbo)...')\n try {\n await this.runBuild(buildCmd, log)\n logStep('\u2705 Build complete')\n } catch (buildError) {\n log(`\u274C Build failed: ${String(buildError)}`, 'error')\n if (buildError instanceof Error && buildError.message) {\n log(` ${buildError.message}`, 'error')\n }\n const err = buildError as { stdout?: string; stderr?: string }\n\n if (err.stdout) log(` stdout: ${err.stdout.trim()}`, 'error')\n if (err.stderr) 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 log(`\u274C Failed to start ${app.name}: ${String(error)}`, 'error')\n }\n }),\n )\n }\n\n private async startOneApp(app: IApiAppConfig): Promise<{ server: ServerlessLocalRun; boundPort: number } | null> {\n logStep(`\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 })\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 // Record the ACTUAL bound port in this runner's own dev-context fragment. Inside startOneApp\n // (the shared start+restart path) so a watch-restart refreshes the port instead of orphaning\n // a stale fragment (M2). Non-fatal: a fragment-write failure must not down the server.\n try {\n this.writeDevContextFragment(app, boundPort)\n } catch (error) {\n log(`\u26A0\uFE0F Failed to write dev-context fragment for ${app.name}: ${String(error)}`, 'warn')\n }\n\n logStep(`\u2705 ${app.name} started on port ${boundPort}`)\n\n return { server, boundPort }\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): void {\n const release = readAppRelease(app.path)\n const fragment: DevContextFragment = {\n package: app.packageName,\n port: boundPort,\n pid: process.pid,\n writtenAt: Date.now(),\n ...(release ? { release } : {}),\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 return this.scheduleRestartWork(() => {\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 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 log(` Close warning: ${String(err)}`, 'debug')\n }\n }),\n )\n\n await this.delayPortRelease()\n\n await Promise.all(\n targets.map(async ({ idx, app }) => {\n try {\n const restarted = await this.startOneApp(app)\n\n if (restarted) {\n this.appServers[idx] = { app, ...restarted }\n }\n } catch (error) {\n log(`\u274C Failed to restart ${app.name}: ${String(error)}`, 'error')\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 const restartedSummary = targets\n .map((t) => {\n const entry = this.appServers[t.idx]\n\n return entry ? `${t.app.name}:${entry.boundPort}` : t.app.name\n })\n .join(', ')\n\n log(`\u2705 Restarted ${restartedSummary}`)\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 log(`\u26A0\uFE0F Dependency-closure map unavailable (${String(err)}); package changes restart all apps`, 'warn')\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 private setupWatch(apps: IApiAppConfig[], uiApps: DiscoveredUiApp[], closureMap: ClosureMap | null): 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 })\n logStep('\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 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 log('\uD83D\uDC40 chokidar: usePolling enabled (DEV_SERVER_CHOKIDAR_POLL=1)', 'debug')\n }\n\n watcher.on('change', (filePath: string) => {\n log(`\uD83D\uDC40 dist change detected: ${filePath}`, 'debug')\n\n const change = classifyDistChange(filePath, appDistDirs, packageDistDirs)\n\n if (change.kind === 'package') {\n const targets = selectPackageRestartTargets(apps, 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 logStep(`\uD83D\uDC40 Watching ${appDistDirs.length} app dist + ${packageDistDirs.length} package dist dir(s) for changes...`)\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 const existing = this.watchDebounceTimers.get(key)\n\n if (existing) clearTimeout(existing)\n\n const timer = setTimeout(() => {\n this.watchDebounceTimers.delete(key)\n work().catch((err) => {\n log(`Restart error (${key}): ${String(err)}`, 'error')\n })\n }, DevServerRunner.WATCH_DEBOUNCE_MS)\n\n this.watchDebounceTimers.set(key, timer)\n }\n\n /**\n * Print the running-server table. The `Base URL` column carries each app's\n * `prefixUrl` (e.g. `/api/v1`) so it is copy-pasteable \u2014 the old table showed a bare\n * `http://localhost:<port>` that 404s against prefixed handler routes. A `Health`\n * column surfaces the unprefixed `/__health` liveness URL.\n */\n private printServerTable(): void {\n const rows = this.appServers.map(({ app, boundPort }) => {\n return [\n app.name,\n String(boundPort),\n `http://localhost:${boundPort}${app.prefixUrl}`,\n `http://localhost:${boundPort}/__health`,\n ]\n })\n\n logTable(renderTable('\uD83D\uDDA5\uFE0F Running Servers', ['App', 'Port', 'Base URL', 'Health'], rows))\n }\n\n /**\n * Dump each running app's registered `METHOD /path` routes at startup so the\n * emulator is self-describing (no need to open `serverless.yml` to learn the routes).\n * Reads the live route set via {@link ServerlessLocalRun.getRegisteredRoutes}.\n */\n private printRouteDump(): void {\n if (this.appServers.length === 0) return\n\n logStep('\uD83D\uDDFA\uFE0F Registered routes:')\n for (const { app, server } of this.appServers) {\n const routes = server.getRegisteredRoutes()\n\n logStep(` ${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 log('\uD83D\uDED1 Shutting down all servers...')\n\n // Reap the long-lived engines FIRST (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 for (const timer of this.watchDebounceTimers.values()) {\n clearTimeout(timer)\n }\n this.watchDebounceTimers.clear()\n\n if (this.watcher) {\n await this.watcher.close()\n this.watcher = 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 logStep(`\uD83D\uDCDD Logs saved to: ${LOG_FILE_PATH}`)\n }\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 */\nexport async function run(options: DevServerOptions = {}): Promise<DevServerRunner> {\n const runner = new DevServerRunner(options)\n\n await runner.start()\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\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 // Opt-in per-request log line (raw stdout, not the Powertools JSON logger) so live\n // traffic is visible in the dev terminal. Off unless DEV_SERVER_REQUEST_LOG=1.\n if (isRequestLogEnabled()) {\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\n process.stdout.write(`${request.method} ${request.url} \u2192 ${reply.statusCode} ${ms}ms\\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 [funcName, funcDef] of Object.entries(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 this.logger.debug(`Registering route: ${http.method} /${http.path} -> ${funcName}`)\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.logger.debug(`Adding fastify route: ${method} ${urlAction}`)\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 this.logger.debug(`Route added successfully: ${method} ${urlAction}`)\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 } 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\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 = ({ depInclusive, depClosure, cwd, logFile }) => {\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)\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 * Escalation: SIGTERM first, then \u2014 if the group is still alive after a grace window \u2014\n * SIGKILL. FE dev servers (vite/vike/astro) run async teardown and can straggle on a\n * lone SIGTERM (orphaned port \u2192 next run's `EADDRINUSE`), so the SIGKILL fallback matters.\n */\nimport type { ChildProcess } from 'node:child_process'\nimport process from 'node:process'\n\n/** Handle to a supervised child; `kill()` reaps its whole process group and resolves when gone. */\nexport interface ManagedChild {\n kill: () => Promise<void>\n}\n\n/** Default grace before escalating SIGTERM \u2192 SIGKILL on a stubborn process group. */\nconst DEFAULT_GRACE_MS = 2000\n\n/** Is the process group led by `pid` still alive? (`kill(-pid, 0)` throws ESRCH when gone.) */\nfunction groupAlive(pid: number): boolean {\n try {\n process.kill(-pid, 0)\n\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Wrap a detached child: `unref()` it (so it never keeps the loop alive) and return a\n * handle whose `kill()` sends a group SIGTERM, polls up to `graceMs` for the group to\n * exit, and SIGKILLs any survivor. Resolves once the group is gone (or was never spawned).\n */\nexport function superviseChild(child: ChildProcess, graceMs: number = DEFAULT_GRACE_MS): ManagedChild {\n // Don't keep the parent event loop alive on the child; the runner owns lifecycle via kill().\n child.unref()\n\n return {\n kill: async (): Promise<void> => {\n const pid = child.pid\n\n if (pid == null) return\n\n try {\n // Negative pid \u2192 the whole group (detached made the child a group leader).\n process.kill(-pid, 'SIGTERM')\n } catch {\n // Already exited (ESRCH) \u2014 nothing to reap.\n return\n }\n\n const deadline = Date.now() + graceMs\n\n while (Date.now() < deadline) {\n await new Promise((r) => {\n return setTimeout(r, 100)\n })\n if (!groupAlive(pid)) return\n }\n\n // Still alive after the grace window \u2014 force it.\n try {\n process.kill(-pid, 'SIGKILL')\n } catch {\n // Raced to exit between the last poll and now \u2014 fine.\n }\n },\n }\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, `^build` ordering\n * and concurrency) rather than infra-kit spawning each framework itself \u2014 the same delegation\n * choice already made for builds via `turbo watch`. infra-kit treats UIs opaquely: it runs their\n * `dev` script (vite/vike/astro/\u2026) and never encodes per-framework knowledge.\n *\n * Unlike the `turbo watch` child, this one's stdio is INHERITED \u2014 the framework dev servers own\n * their own TTY output (HMR overlays, URLs, spinners), so it streams raw to the terminal below the\n * BE server table rather than being teed into the runner log (which would corrupt it with ANSI).\n *\n * Detached \u2192 its own process group; reaped as a group (SIGTERM\u2192SIGKILL) via {@link superviseChild}.\n */\nimport { spawn } from 'node:child_process'\n\nimport { superviseChild } from './managed-child.js'\nimport type { ManagedChild } 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\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\n/**\n * Default factory: spawn `pnpm exec turbo run dev --filter=<pkg> \u2026 --concurrency=N --env-mode=loose`\n * detached, streaming to the parent terminal, 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 = ({ packageNames, cwd, concurrency }) => {\n const filters = packageNames.map((name) => {\n return `--filter=${name}`\n })\n const child = spawn(\n 'pnpm',\n ['exec', 'turbo', 'run', 'dev', ...filters, `--concurrency=${concurrency}`, '--env-mode=loose'],\n { cwd, detached: true, stdio: ['ignore', 'inherit', 'inherit'] },\n )\n\n return superviseChild(child)\n}\n"],
5
- "mappings": "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",
6
- "names": ["Command", "process", "pathToFileURL", "path", "process", "makeLeaf", "command", "buildNode", "commands", "depth", "leftCount", "left", "right", "buildCmuxLayout", "buildPaneCommands", "appNames", "watch", "name", "selectApiApps", "root", "include", "apps", "discoverApiApps", "app", "logDevWorkspace", "ref", "logger", "registerShutdown", "shuttingDown", "shutdown", "signal", "closeCmuxDevWorkspace", "process", "runCmuxDevServer", "options", "findMonorepoRoot", "normalizeAppInclude", "commands", "layout", "buildCmuxLayout", "title", "openCmuxDevWorkspace", "heartbeat", "chokidar", "exec", "execFileSync", "fs", "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", "ms", "boundPort", "preferred", "address", "_request", "serverlessYmlPath", "fileContents", "data", "p", "funcName", "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", "process", "DEFAULT_GRACE_MS", "groupAlive", "pid", "superviseChild", "child", "graceMs", "deadline", "r", "buildTurboWatchFilters", "depInclusive", "depClosure", "name", "defaultTurboWatchFactory", "cwd", "logFile", "filters", "out", "child", "spawn", "superviseChild", "spawn", "defaultUiDevFactory", "packageNames", "cwd", "concurrency", "filters", "name", "child", "spawn", "superviseChild", "LOG_FILE_PATH", "process", "VERBOSE", "initLogFile", "execFn", "util", "exec", "launchScript", "script", "logFn", "stderr", "error", "err", "appendLogFile", "text", "log", "message", "level", "logStep", "logTable", "lines", "output", "center", "s", "width", "pad", "left", "renderTable", "title", "headers", "rows", "widths", "header", "i", "row", "columnsInner", "sum", "w", "lastIdx", "inner", "blocks", "rowLine", "cells", "c", "readAppRelease", "cwd", "branch", "execFileSync", "slug", "slugifyRelease", "DevServerRunner", "_DevServerRunner", "options", "runBuild", "turboWatchFactory", "defaultTurboWatchFactory", "uiDevFactory", "defaultUiDevFactory", "dryRunner", "findMonorepoRoot", "devConfig", "discoverApiApps", "app", "getInfraKitConfig", "normalizeAppInclude", "appName", "resolvePreferredPort", "resolvePrefixUrl", "devPresets", "name", "def", "available", "include", "watch", "apiAppsAll", "uiAppsAll", "discoverUiApps", "resolved", "resolvePreset", "apiNames", "t", "uiNames", "passesInclude", "watchDepsByApp", "apps", "uiApps", "closureMap", "filters", "a", "names", "explicitApps", "duplicatePorts", "conflictingApps", "findPortConflicts", "buildCmd", "buildError", "started", "server", "ServerlessLocalRun", "boundPort", "release", "fragment", "target", "tmp", "work", "run", "r", "e", "targets", "label", "idx", "restarted", "restartedSummary", "entry", "buildClosureMap", "appDistDirs", "getAppDistDirs", "packageDistDirs", "getPackageDistDirs", "allDistDirs", "usePoll", "watcher", "chokidar", "p", "filePath", "change", "classifyDistChange", "selectPackageRestartTargets", "packageDebounceKey", "key", "existing", "timer", "routes", "runner", "splitList", "value", "parts", "s", "toDevServerOptions", "raw", "resolveSelfOptions", "options", "resolveSelfAppName", "process", "runDevServer", "rawOptions", "isCmuxAvailable", "runCmuxDevServer", "runner", "run", "shuttingDown", "shutdown", "signal", "parseAndRun", "argv", "program", "Command", "pathToFileURL", "error"]
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/proxy/portless-driver.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 /** Portless proxy listen port for the Layer-B hero URLs (Commander gives a string). */\n proxyPort?: string\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/** Parse a `--proxy-port` string to a positive integer, or `undefined` (fall back to the default). */\nconst parsePort = (value: string | undefined): number | undefined => {\n if (value == null) return undefined\n const n = Number.parseInt(value, 10)\n\n return Number.isInteger(n) && n > 0 ? n : undefined\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 proxyPort: parsePort(raw.proxyPort),\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 =\n !raw.preset && !raw.app && !raw.self && !raw.cmux && !raw.watch && !raw.verbose && !raw.routes && !raw.proxyPort\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 .option('--proxy-port <port>', 'Portless proxy listen port for <release>.<package>.localhost URLs (default: 80)')\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/** The signals a resident dev process tears down on. */\nconst HANDLED_SIGNALS: readonly NodeJS.Signals[] = ['SIGINT', 'SIGTERM']\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 {\n DEFAULT_DEV_PROXY_FALLBACK_PORT,\n DEFAULT_DEV_PROXY_PORT,\n PRIVILEGED_PORT_THRESHOLD,\n getInfraKitConfig,\n} 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 } 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 * Portless proxy listen port for the Layer-B `<release>.<package>.localhost` hero URLs. Default\n * {@link DEFAULT_DEV_PROXY_PORT} (`80`), which is elided from the URL. A privileged port is probed,\n * never started \u2014 an unprivileged override buys a zero-sudo daemon at the cost of a `:<port>`\n * suffix on every URL. Only used when portless is available.\n */\n proxyPort?: number\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 `proxyPort` the port it resolves on. Both are always\n * present: the runner refuses to start an app it cannot alias, so a fragment on disk always describes an\n * app reachable by name, and the vite helper can target the template unconditionally.\n */\ninterface DevContextFragment {\n package: string\n port: number\n pid: number\n writtenAt: number\n release: string\n alias: string\n proxyPort: number\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 /** Resolved proxy listen port for the hero URLs (`\u2026localhost:<proxyPort>`). Finalized in {@link start}. */\n private proxyPort: number\n /** Every `<release>.<package>` alias this runner registered; removed one-by-one in {@link shutdown}. */\n private readonly registeredAliases = new Set<string>()\n /** `{ \"<ui-package>\": <port> }` handed to the turbo child via `INFRA_KIT_UI_PORTS` (computed in printReady). */\n private uiPortMap: Record<string, number> = {}\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 this.proxyPort = options.proxyPort ?? DEFAULT_DEV_PROXY_PORT\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: finalize the proxy port (CLI `--proxy-port` > `devProxy.port` config > default 80), then\n * bring the portless proxy up BEFORE any alias registration \u2014 backends register in startOneApp, UIs in\n * printReady, and both need a live daemon. Runs ahead of startAllApps AND startUiDev so a UI-only\n * session ensures it too; a proxy that will not come up throws here, before anything is spawned.\n */\n private async bringUpProxy(apps: IApiAppConfig[]): Promise<void> {\n const resolvedProxyPort = await this.resolveProxyPort()\n\n this.proxyPort = resolvedProxyPort.port\n await this.ensureProxy(resolvedProxyPort.explicit)\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 // 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,\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 * Resolve the proxy listen port with precedence: CLI `--proxy-port` > `devProxy.port` in the project\n * config > {@link DEFAULT_DEV_PROXY_PORT}. A config-read failure degrades to the default (never fatal).\n *\n * `explicit` records WHERE the port came from, which {@link ensureProxy} needs: only the implicit\n * default may be rewritten to an unprivileged fallback. A port the user typed is an instruction.\n */\n private async resolveProxyPort(): Promise<{ port: number; explicit: boolean }> {\n if (this.options.proxyPort != null) return { port: this.options.proxyPort, explicit: true }\n\n try {\n const configured = (await getInfraKitConfig()).devProxy?.port\n\n if (configured != null) return { port: configured, explicit: true }\n } catch {\n // Unreadable config \u2192 the default, exactly as if `devProxy` were absent. Never fatal.\n }\n\n return { port: DEFAULT_DEV_PROXY_PORT, explicit: false }\n }\n\n /**\n * Bring the portless proxy up before any alias registration. Every dev URL is a hostname served by this\n * daemon, so a proxy that will not come up is a FATAL start error, not a degraded mode: there is no\n * second way to reach an app, and a half-started dev loop that silently routes nowhere is worse than a\n * refusal that names the fix.\n *\n * The default `:80` cannot be auto-started (it needs root; see `PortlessDriver.ensureDaemon`), so on a\n * machine without the one-time service install we retry on {@link DEFAULT_DEV_PROXY_FALLBACK_PORT},\n * which portless binds unprivileged. That keeps the `<release>.<package>.localhost:<port>` hero form \u2014\n * `resolveEndpointUrl` appends the port, and the runner records it in each dev-context fragment so\n * `infra-kit/vite` grafts the SAME port onto its proxy target. Only the port-free URL needs sudo.\n *\n * @param explicit Whether {@link proxyPort} was named by a CLI flag or config. Only an implicit port\n * may be rewritten to the fallback \u2014 see {@link resolveProxyPort}.\n * @throws When portless is missing, or no daemon could be brought up on a usable port.\n */\n private async ensureProxy(explicit: boolean): 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.ensureDaemon(this.proxyPort)) return\n\n // An explicit port is honoured or nothing: silently listening somewhere the user did not ask for\n // would desync every consumer that pinned the same number.\n if (explicit) {\n throw new Error(\n `infra-kit dev: no portless proxy on :${this.proxyPort}, and it was requested explicitly (--proxy-port / devProxy.port), so it will not fall back. ${\n this.proxyPort < PRIVILEGED_PORT_THRESHOLD\n ? 'That port is privileged \u2014 install the daemon once: `sudo portless service install --no-tls`.'\n : 'Free that port, or point at one that is already serving portless.'\n }`,\n )\n }\n\n if (await this.proxy.ensureDaemon(DEFAULT_DEV_PROXY_FALLBACK_PORT)) {\n this.proxyPort = DEFAULT_DEV_PROXY_FALLBACK_PORT\n this.renderer.log(\n `\u2139\uFE0F portless proxy on :${DEFAULT_DEV_PROXY_FALLBACK_PORT} (no sudo) \u2014 dev URLs keep their hostname`,\n 'warn',\n )\n this.renderer.log(' want port-free URLs? sudo portless service install --no-tls', 'warn')\n\n return\n }\n\n throw new Error(\n `infra-kit dev: could not bring up a portless proxy on :${DEFAULT_DEV_PROXY_PORT} or :${DEFAULT_DEV_PROXY_FALLBACK_PORT}. ` +\n `Something else may be holding :${DEFAULT_DEV_PROXY_FALLBACK_PORT} \u2014 check with \\`lsof -nP -iTCP:${DEFAULT_DEV_PROXY_FALLBACK_PORT} -sTCP:LISTEN\\`, or set \\`devProxy.port\\` to a free one.`,\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 proxyPort: this.proxyPort,\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 proxyPort = this.proxyPort\n const endpoints: EndpointRow[] = this.appServers.map(({ app, alias }, i) => {\n return {\n tag: `${app.name}/api`,\n url: resolveEndpointUrl({ prefixUrl: app.prefixUrl, alias, proxyPort }),\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, proxyPort }),\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\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", "/**\n * Thin, injectable driver for the `portless` daemon (Layer B \u2014 see `.omc/plans/portless-layer-b.md`).\n *\n * `infra-kit dev` uses it to register `<release>.<package>.localhost \u2192 127.0.0.1:<port>` routes so the\n * hero URLs resolve. Every call here is **time-bounded and never throws**: a missing binary, a non-zero\n * exit, or a wedged process resolves to `false`/no-op. That is a reporting contract, not a tolerance one \u2014\n * portless IS a hard dependency of the dev loop, and `DevServerRunner.ensureProxy` turns a `false` from\n * this driver into a fatal, actionable start error.\n *\n * The binary is NOT resolved from `PATH`: `portless` is a normal npm dependency living in\n * `node_modules/.bin`, which is on `PATH` only when the process was launched via pnpm/npm. Since\n * `infra-kit dev` is often launched otherwise (a global bin, a cmux runner, a foreign cwd), we resolve\n * portless's own `dist/cli.js` by walking `node_modules` from this file (see {@link resolvePortlessBin})\n * and run it with the current `node` (`process.execPath`) \u2014 so it works regardless of how `dev` was\n * invoked. Args are fixed literals plus discovered release/package names + a numeric port, never\n * shell-interpolated.\n *\n * All process I/O is injected (`run` for awaited commands, `spawnDaemon` for the detached daemon) so\n * tests never shell out and assert the exact portless argv.\n */\nimport { execFile, spawn } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport net from 'node:net'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport { promisify } from 'node:util'\n\nimport { PRIVILEGED_PORT_THRESHOLD } from 'src/lib/infra-kit-config'\nimport { withoutPackageManagerEnv } from 'src/lib/pm-env'\n\nconst execFileAsync = promisify(execFile)\n\n/** Read `bin.portless` (the `dist/cli.js` relative path) from a portless `package.json` on disk. */\nconst readBinRel = (pkgJsonPath: string): string | null => {\n const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as { bin?: string | Record<string, string> }\n const rel = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.portless\n\n return rel == null || rel === '' ? null : rel\n}\n\n/**\n * Resolve the absolute path to portless's CLI entry (`portless/dist/cli.js`) from node_modules,\n * independent of `PATH`. Returns `null` when portless is not installed, degrading the whole driver to\n * a no-op.\n *\n * portless is ESM-only (its `.` export exposes only `import`/`types`, no `require`), so\n * `createRequire().resolve` can't see it. We instead walk `node_modules` upward from this file \u2014 the\n * standard resolution path \u2014 and read the package's `package.json` straight off disk, which bypasses the\n * exports map that would otherwise hide both `package.json` and the main entry.\n */\nexport const resolvePortlessBin = (): string | null => {\n try {\n let dir = dirname(fileURLToPath(import.meta.url))\n\n for (;;) {\n const pkgJsonPath = join(dir, 'node_modules', 'portless', 'package.json')\n\n if (existsSync(pkgJsonPath)) {\n const rel = readBinRel(pkgJsonPath)\n\n return rel == null ? null : join(dirname(pkgJsonPath), rel)\n }\n const parent = dirname(dir)\n\n if (parent === dir) return null\n dir = parent\n }\n } catch {\n return null\n }\n}\n\n/** Resolve portless's CLI once per process \u2014 the on-disk location never changes within a run. */\nlet cachedBin: string | null | undefined\nconst portlessBin = (): string | null => {\n if (cachedBin === undefined) cachedBin = resolvePortlessBin()\n\n return cachedBin\n}\n\n/** Awaited portless invocation. Rejects on non-zero exit / timeout; the driver swallows that into a no-op. */\nexport type PortlessRun = (args: string[], opts: { timeoutMs: number }) => Promise<void>\n\n/** Fire-and-forget launch of the long-lived proxy daemon (detached; never awaited). */\nexport type SpawnDaemon = (args: string[]) => void\n\n/**\n * Ground-truth liveness: is something accepting TCP on `127.0.0.1:<port>`? This is the ONLY reliable\n * \"is the proxy up?\" signal \u2014 `portless list`/`doctor` both exit 0 even with the daemon stopped, so a\n * subcommand exit code cannot gate daemon startup.\n */\nexport type IsListening = (port: number) => Promise<boolean>\n\nconst DEFAULT_TIMEOUT_MS = 1500\nconst READY_POLL_ATTEMPTS = 10\nconst READY_POLL_INTERVAL_MS = 200\nconst PROBE_TIMEOUT_MS = 500\n\nexport const defaultIsListening: IsListening = (port) => {\n return new Promise((resolve) => {\n const socket = net.connect({ host: '127.0.0.1', port })\n const finish = (result: boolean): void => {\n socket.destroy()\n resolve(result)\n }\n\n socket.setTimeout(PROBE_TIMEOUT_MS)\n socket.once('connect', () => {\n finish(true)\n })\n socket.once('timeout', () => {\n finish(false)\n })\n socket.once('error', () => {\n finish(false)\n })\n })\n}\n\nconst defaultRun: PortlessRun = async (args, { timeoutMs }) => {\n const bin = portlessBin()\n\n if (bin == null) throw new Error('portless is not installed (not resolvable from node_modules)')\n await execFileAsync(process.execPath, [bin, ...args], {\n signal: AbortSignal.timeout(timeoutMs),\n encoding: 'utf-8',\n env: withoutPackageManagerEnv(process.env),\n })\n}\n\nconst defaultSpawnDaemon: SpawnDaemon = (args) => {\n const bin = portlessBin()\n\n if (bin == null) return\n const child = spawn(process.execPath, [bin, ...args], {\n detached: true,\n stdio: 'ignore',\n env: withoutPackageManagerEnv(process.env),\n })\n\n child.unref()\n}\n\nconst delay = (ms: number): Promise<void> => {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\n/**\n * portless's state directory. `resolveStateDir()` in portless ignores the bound port, so a daemon on\n * ANY port records itself here \u2014 which is what makes {@link defaultIsPortlessOwned} a valid identity check.\n */\nconst portlessStateDir = (): string => {\n return process.env.PORTLESS_STATE_DIR ?? join(homedir(), '.portless')\n}\n\n/**\n * Is the process listening on `port` actually portless, and not an unrelated squatter?\n *\n * A TCP probe alone cannot answer this: `isListening` only proves *something* accepts connections. If\n * that something is not portless, we would register aliases into a route table nobody serves and print\n * hero URLs that never resolve. So cross-check portless's own state files \u2014 `proxy.port` must name this\n * port AND `proxy.pid` must still be alive. Both are stale-prone on their own (a killed daemon leaves\n * both behind), which is exactly why neither is trusted alone.\n */\nexport type IsPortlessOwned = (port: number) => boolean\n\nexport const defaultIsPortlessOwned: IsPortlessOwned = (port) => {\n try {\n const dir = portlessStateDir()\n\n if (Number(readFileSync(join(dir, 'proxy.port'), 'utf-8').trim()) !== port) return false\n\n const pid = Number(readFileSync(join(dir, 'proxy.pid'), 'utf-8').trim())\n\n if (!Number.isInteger(pid) || pid <= 0) return false\n\n // Signal 0 checks liveness without delivering anything. EPERM means the pid exists but belongs to\n // another user \u2014 still alive, so still portless's daemon.\n try {\n process.kill(pid, 0)\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM'\n }\n\n return true\n } catch {\n return false\n }\n}\n\nexport interface PortlessDriver {\n /** Resolve (and memoize) whether the `portless` binary is usable. Absent \u2192 every other call no-ops. */\n isAvailable: () => Promise<boolean>\n /**\n * Ensure the proxy daemon is listening on `port` (probe first; start detached + bounded readiness\n * poll). Returns `true` only when the daemon is confirmed listening \u2014 the caller gates hero URLs on it.\n *\n * A privileged port (< 1024, the default `80`) is PROBED ONLY, never started: binding it needs root,\n * and portless's sudo re-exec cannot prompt from a detached child. Bring it up once out-of-band with\n * `sudo portless service install --no-tls`.\n */\n ensureDaemon: (port: number) => Promise<boolean>\n /**\n * Register `<name> \u2192 127.0.0.1:<port>` (`name` = `<release>.<package>`). Returns `true` on success so\n * the caller shows the hero URL only for an alias that actually resolves (best-effort otherwise).\n */\n registerAlias: (name: string, port: number) => Promise<boolean>\n /** Deregister `<name>`. Best-effort. */\n removeAlias: (name: string) => Promise<void>\n}\n\nexport interface PortlessDriverDeps {\n run?: PortlessRun\n spawnDaemon?: SpawnDaemon\n /** TCP liveness probe (default: real `net` connect). Injected in tests to simulate a daemon coming up. */\n isListening?: IsListening\n /** Identity check: is the listener on this port portless itself? Injected in tests. */\n isPortlessOwned?: IsPortlessOwned\n timeoutMs?: number\n}\n\n/**\n * Build a {@link PortlessDriver}. Inject `run`/`spawnDaemon` in tests to assert argv without shelling\n * out. `isAvailable` memoizes so the binary is probed at most once per runner.\n */\nexport const createPortlessDriver = (deps: PortlessDriverDeps = {}): PortlessDriver => {\n const run = deps.run ?? defaultRun\n const spawnDaemon = deps.spawnDaemon ?? defaultSpawnDaemon\n const isListening = deps.isListening ?? defaultIsListening\n const isPortlessOwned = deps.isPortlessOwned ?? defaultIsPortlessOwned\n const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS\n let availability: boolean | null = null\n\n /** Run a portless subcommand, swallowing any failure into `false` (best-effort contract). */\n const tryRun = async (args: string[]): Promise<boolean> => {\n try {\n await run(args, { timeoutMs })\n\n return true\n } catch {\n return false\n }\n }\n\n const isAvailable = async (): Promise<boolean> => {\n if (availability === null) {\n availability = await tryRun(['--version'])\n }\n\n return availability\n }\n\n const ensureDaemon = async (port: number): Promise<boolean> => {\n if (!(await isAvailable())) return false\n // Already accepting TCP on the proxy port? Nothing to start. (Ground truth \u2014 NOT `portless list`,\n // which exits 0 even when the daemon is stopped and would falsely skip the start below.) A listener\n // we cannot identify as portless is a squatter, not our daemon: claiming it would publish hero URLs\n // that route nowhere, so report it as unusable and let the caller fail loudly.\n if (await isListening(port)) return isPortlessOwned(port)\n\n // A privileged port can only be BOUND by root. portless handles that by re-execing itself through\n // `sudo` with `stdio:'inherit'` \u2014 which a detached `stdio:'ignore'` child can never satisfy: the\n // password prompt has nowhere to go, so the start would hang until the timeout and then report a\n // daemon that never came up. Probe only; the caller tells the user how to install the daemon once.\n if (port < PRIVILEGED_PORT_THRESHOLD) return false\n\n // Start detached (`--no-tls -p <port>` \u2192 plain HTTP, zero-sudo) so a foreground daemon can't block\n // us, then poll TCP readiness within a bounded window.\n spawnDaemon(['proxy', 'start', '--no-tls', '-p', String(port)])\n for (let attempt = 0; attempt < READY_POLL_ATTEMPTS; attempt += 1) {\n await delay(READY_POLL_INTERVAL_MS)\n // BOTH conditions, and keep polling until both hold: the daemon can accept TCP a beat before it\n // writes `proxy.port`/`proxy.pid`, so bailing on the identity check alone would abort a start that\n // was merely a few milliseconds from ready.\n if ((await isListening(port)) && isPortlessOwned(port)) return true\n }\n\n return false\n }\n\n const registerAlias = async (name: string, port: number): Promise<boolean> => {\n if (!(await isAvailable())) return false\n\n return tryRun(['alias', name, String(port)])\n }\n\n const removeAlias = async (name: string): Promise<void> => {\n if (!(await isAvailable())) return\n await tryRun(['alias', '--remove', name])\n }\n\n return { isAvailable, ensureDaemon, registerAlias, removeAlias }\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", "execFile", "spawn", "existsSync", "readFileSync", "net", "homedir", "dirname", "join", "process", "fileURLToPath", "promisify", "execFileAsync", "promisify", "execFile", "readBinRel", "pkgJsonPath", "pkg", "readFileSync", "rel", "resolvePortlessBin", "dir", "dirname", "fileURLToPath", "join", "existsSync", "parent", "cachedBin", "portlessBin", "DEFAULT_TIMEOUT_MS", "READY_POLL_ATTEMPTS", "READY_POLL_INTERVAL_MS", "PROBE_TIMEOUT_MS", "defaultIsListening", "port", "resolve", "socket", "net", "finish", "result", "defaultRun", "args", "timeoutMs", "bin", "process", "withoutPackageManagerEnv", "defaultSpawnDaemon", "spawn", "delay", "ms", "portlessStateDir", "homedir", "defaultIsPortlessOwned", "pid", "error", "createPortlessDriver", "deps", "run", "spawnDaemon", "isListening", "isPortlessOwned", "availability", "tryRun", "isAvailable", "PRIVILEGED_PORT_THRESHOLD", "attempt", "name", "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", "readAppRelease", "cwd", "branch", "execFileSync", "slug", "slugifyRelease", "DEFAULT_RELEASE_SLUG", "errorReason", "firstLine", "DevServerRunner", "_DevServerRunner", "options", "runBuild", "turboWatchFactory", "defaultTurboWatchFactory", "uiDevFactory", "defaultUiDevFactory", "dryRunner", "renderer", "healthProbe", "proxy", "createPortlessDriver", "DEFAULT_DEV_PROXY_PORT", "DevRenderer", "findMonorepoRoot", "devConfig", "discoverApiApps", "app", "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", "resolvedProxyPort", "bootLabel", "map", "filters", "names", "uiPortEnv", "tagByPackage", "pkg", "detail", "explicitApps", "duplicatePorts", "conflictingApps", "findPortConflicts", "buildCmd", "buildError", "started", "configured", "explicit", "PRIVILEGED_PORT_THRESHOLD", "DEFAULT_DEV_PROXY_FALLBACK_PORT", "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", "proxyPort", "endpoints", "i", "resolveEndpointUrl", "uiEndpoints", "uiRefs", "ui", "assigned", "appCount", "pkgCount", "reason", "routes", "selectDevUi", "PersistentInkDevUi", "runner", "toPresetDef", "targets", "key", "explainTargetKey", "parsePort", "value", "n", "splitList", "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
  }