infra-kit 0.1.129 → 0.1.130

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.
@@ -0,0 +1,2 @@
1
+ import{e as k,r as u}from"./chunk-V375VXOL.js";import{F as x,N as w,v as d,w as h,x as P,y}from"./chunk-4YU4TAGX.js";import{c as v}from"./chunk-YGCVOVNX.js";import"./chunk-2PZRQHWF.js";import R from"@inquirer/checkbox";import N from"@inquirer/confirm";import D from"@inquirer/select";import O from"node:path";import m from"node:process";var b=e=>new Map(e.apps.map(r=>[r.name,r])),C=(e,r,t,s,a)=>{let n=e.localCapable&&t.has(e.packageName);n?e.ownerApp!=null&&s.localOwners.add(e.ownerApp):(s.anyCloud=!0,e.ownerApp===r&&s.demoteOwn.add(r));for(let i of e.routes)a[i]=n?"local":"cloud"},S=(e,r)=>{let t={localOwners:new Set,demoteOwn:new Set,proxyByUiApp:new Map,anyCloud:!1};for(let s of e.apps){let a=r.get(s);if(!a?.hasUi)continue;let n=new Set(e.localBackendsByApp[s]??[]),i={};for(let o of a.backends)C(o,s,n,t,i);Object.keys(i).length>0&&t.proxyByUiApp.set(s,i)}return t},A=(e,r)=>{let t=b(r),s=e.apps.filter(o=>t.get(o)?.hasUi===!0),a=new Set(e.apps.filter(o=>t.get(o)?.hasApi===!0)),n=S(e,t);for(let o of n.localOwners)a.add(o);for(let o of n.demoteOwn)n.localOwners.has(o)||a.delete(o);let i={};for(let o of s){let p=n.proxyByUiApp.get(o);i[`${o}/ui`]=p?{proxy:p}:{}}for(let o of a)i[`${o}/api`]??={};return{presetDef:{apps:i,cmux:e.cmux},anyCloudRoute:n.anyCloud,targetKeys:Object.keys(i).sort()}},B=e=>e.split("/")[0],z=(e,r,t)=>{let s=b(t),a=[...new Set(e.targetKeys.map(B))].sort(),n=new Set(e.targetKeys),i=a.every(p=>{let l=s.get(p);if(!l)return!1;let c=!l.hasUi||n.has(`${p}/ui`),g=!l.hasApi||n.has(`${p}/api`);return c&&g}),o=[`--app=${a.join(",")}`];return r.watch&&o.push("--watch"),r.cmux&&o.push("--cmux"),{flags:o.join(" "),exact:i}};var $={select:e=>D({message:e.message,choices:e.choices,default:e.default},{output:m.stderr}),checkbox:e=>R({message:e.message,choices:e.choices},{output:m.stderr}),confirm:e=>N({message:e.message,default:e.default},{output:m.stderr})},U=(e,r)=>{let t=new Map;for(let[s,a]of Object.entries(e)){let n=t.get(a.packageName),i=a.from.includes("local");n?(n.routes.push(s),n.localCapable=n.localCapable||i):t.set(a.packageName,{packageName:a.packageName,routes:[s],localCapable:i,ownerApp:r.get(a.packageName)})}return[...t.values()]},j=async e=>{let r=P(e),t=y(e),s=await w(),a=new Map(r.map(c=>[c.packageName,c.name])),n=new Map(r.map(c=>[c.name,c.packageName])),i=new Set(t.map(c=>c.name)),o=new Set(r.map(c=>c.name)),p=[...new Set([...o,...i])].sort();return{apps:await Promise.all(p.map(async c=>{let g=i.has(c),f=g?(await v(O.join(e,"apps",c,"ui")))?.proxy?.routes:void 0;return{name:c,hasApi:o.has(c),hasUi:g,apiPackage:n.get(c),backends:f?U(f,a):[]}})),presets:Object.keys(s.devServersPresets??{}),environments:s.environments}},q=e=>{let r=[e.hasUi?"ui":null,e.hasApi?"api":null].filter(Boolean);return`${e.name} (${r.join("+")})`},_=async(e,r,t)=>{let s=new Map(t.apps.map(n=>[n.name,n])),a={};for(let n of r){let i=s.get(n);if(!i?.hasUi||i.backends.length===0)continue;let o=i.backends.map(p=>{let l=`${p.routes.join(",")} \u2192 ${p.packageName}`;return p.localCapable&&p.ownerApp!=null?{name:l,value:p.packageName,checked:!0}:{name:`${l} (cloud only)`,value:p.packageName,disabled:"(cloud only)"}});a[n]=await e.checkbox({message:`\u{1F500} ${n}: which backends run locally?`,choices:o})}return a},K=e=>{let r={api:e.apps.filter(a=>a.hasApi).map(a=>a.name),ui:e.apps.filter(a=>a.hasUi).map(a=>a.name)},t={},s=new Map;for(let a of e.apps){a.apiPackage!=null&&(t[a.name]=a.apiPackage);for(let n of a.backends)for(let i of n.routes)s.set(`${a.name} ${i}`,n.packageName)}return{discovered:r,apiPkgByApp:t,routePkg:(a,n)=>s.get(`${a} ${n}`)}},I=(e,r)=>x({__wizard__:e},K(r)).map(t=>t.message),W=async(e,r)=>{if(r.apps.length===0)return d.warn("No apps discovered to run."),null;let t=await e.checkbox({message:"\u{1F4E6} Which apps?",choices:r.apps.map(c=>({name:q(c),value:c.name,checked:!0}))});if(t.length===0)return d.warn("No apps selected."),null;let s=await _(e,t,r),a=await e.confirm({message:"\u{1F440} Rebuild & restart on save (watch)?",default:!1}),n=await e.confirm({message:"\u{1F9E9} Run each app in its own cmux pane?",default:!1}),i={apps:t,localBackendsByApp:s,watch:a,cmux:n},o=A(i,r);o.anyCloudRoute&&(i.env=await e.select({message:"\u2601\uFE0F Point cloud routes at which environment?",choices:r.environments.map(c=>({name:c,value:c}))}),m.env[k]=i.env);let p=I(o.presetDef,r);if(p.length>0){d.warn("\u26A0\uFE0F Proxy audit found issues with this selection:");for(let c of p)d.warn(` \u2022 ${c}`);return null}E(o,i,r);let l=o.targetKeys.filter(c=>c.endsWith("/api")).map(c=>c.split("/")[0]);return n&&l.length>0?{include:l,watch:a,cmux:!0}:(n&&d.info("\u2139\uFE0F cmux needs at least one local backend (panes are backend-only) \u2014 running in-process instead."),{presetDef:o.presetDef,watch:a,cmux:!1})},E=(e,r,t)=>{u.start("dev"),u.setInteractive();let s=z(e,r,t);u.addOption(s.flags,!0),u.print(),s.exact||d.info("\u2139\uFE0F This part-level selection has no exact single-flag form \u2014 save it as a devPreset to reproduce it.")},L=async(e,r)=>{let t=await e.confirm({message:"\u{1F440} Rebuild & restart on save (watch)?",default:!1});return u.start("dev"),u.setInteractive(),u.addOption(r,!0),t&&u.addOption("--watch",!0),u.print(),{preset:r,watch:t,cmux:!1}},M=" manual",T=async(e,r)=>{if(r.presets.length===0)return W(e,r);let t=await e.select({message:"\u{1F680} Start from a preset, or configure manually?",choices:[...r.presets.map(s=>({name:s,value:s})),{name:"Manual (custom)\u2026",value:M}]});return t===M?W(e,r):L(e,t)},se=async(e=$,r=h(m.cwd()))=>T(e,await j(r));export{M as MANUAL_CHOICE,I as auditManualPlan,$ as defaultPrompts,j as gatherWizardModel,se as runDevWizard,T as runWizardFlow};
2
+ //# sourceMappingURL=dev-wizard-run-LYNLPQMV.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/dev/dev-wizard-run.ts", "../src/dev/dev-wizard.ts"],
4
+ "sourcesContent": ["/**\n * Impure driver for the interactive `infra-kit dev` wizard: filesystem/config discovery, the inquirer\n * prompt flow (step-0 preset-or-manual, then the manual matrix), the pre-flight proxy audit, and the\n * `commandEcho` teaching line. The pure answer\u2192plan mapping lives in {@link file://./dev-wizard.ts}.\n *\n * Prompts are behind the injectable {@link WizardPrompts} seam so the flow is testable without a TTY.\n * This module is reached ONLY from the bare-invocation TTY branch of the entry point; every flagged /\n * non-TTY / `--json` / MCP invocation bypasses it entirely.\n */\nimport inquirerCheckbox from '@inquirer/checkbox'\nimport inquirerConfirm from '@inquirer/confirm'\nimport inquirerSelect from '@inquirer/select'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { discoverApiApps, discoverUiApps, findMonorepoRoot } from 'src/dev/discovery'\nimport { commandEcho } from 'src/lib/command-echo'\nimport { INFRA_KIT_ENV_VAR } from 'src/lib/constants'\nimport { getInfraKitConfig } from 'src/lib/infra-kit-config'\nimport type { DevPreset } from 'src/lib/infra-kit-config'\nimport { logger } from 'src/lib/logger'\nimport { loadDev } from 'src/lib/vite/vite'\n\nimport { deriveManualPlan, equivalentCommand } from './dev-wizard.js'\nimport type { DerivedPlan, ManualSelection, ProxyBackend, WizardApp, WizardModel } from './dev-wizard.js'\nimport { validatePresetProxy } from './presets.js'\nimport type { DiscoveredParts, PresetProxyContext } from './presets.js'\n\n/** A wizard prompt choice \u2014 all values are strings, so the seam needs no generics. */\nexport interface WizardChoice {\n name: string\n value: string\n description?: string\n checked?: boolean\n disabled?: boolean | string\n}\n\n/**\n * Injectable prompt seam (a string-valued subset of `@inquirer/*`). The default implementation\n * ({@link defaultPrompts}) delegates to the real prompts, rendering to stderr so the dev-server's\n * stdout stays clean. Tests pass a scripted object.\n */\nexport interface WizardPrompts {\n select: (cfg: { message: string; choices: WizardChoice[]; default?: string }) => Promise<string>\n checkbox: (cfg: { message: string; choices: WizardChoice[] }) => Promise<string[]>\n confirm: (cfg: { message: string; default?: boolean }) => Promise<boolean>\n}\n\n/** Real `@inquirer/*` prompts, rendered to stderr (mirrors the bare-`infra-kit` command palette). */\nexport const defaultPrompts: WizardPrompts = {\n select: (cfg) => {\n return inquirerSelect(\n { message: cfg.message, choices: cfg.choices, default: cfg.default },\n { output: process.stderr },\n )\n },\n checkbox: (cfg) => {\n return inquirerCheckbox({ message: cfg.message, choices: cfg.choices }, { output: process.stderr })\n },\n confirm: (cfg) => {\n return inquirerConfirm({ message: cfg.message, default: cfg.default }, { output: process.stderr })\n },\n}\n\n/** What the wizard hands back to the entry point (merged into `DevServerOptions`), or null on cancel. */\nexport interface WizardResult {\n /** Named preset (preset branch) \u2014 passed through as `options.preset`. */\n preset?: string\n /** In-memory preset (manual branch, non-cmux) \u2014 passed through as `options.presetDef`. */\n presetDef?: DevPreset\n /**\n * App-name include list for the cmux path only. `runCmuxDevServer` is preset-unaware and runs one\n * pane per discovered API app filtered by `include`, so a cmux manual run degrades to its selected\n * API backends (cmux panes are backend-only by design). Unset for the non-cmux manual path.\n */\n include?: string[]\n watch: boolean\n cmux: boolean\n}\n\n/** Group one frontend's `dev.proxy.routes` into {@link ProxyBackend}s keyed by backend package. */\nconst groupBackends = (\n routes: Record<string, { packageName: string; from: readonly string[] }>,\n ownerByPkg: Map<string, string>,\n): ProxyBackend[] => {\n const byPkg = new Map<string, ProxyBackend>()\n\n for (const [route, def] of Object.entries(routes)) {\n const existing = byPkg.get(def.packageName)\n const localCapable = def.from.includes('local')\n\n if (existing) {\n existing.routes.push(route)\n existing.localCapable = existing.localCapable || localCapable\n } else {\n byPkg.set(def.packageName, {\n packageName: def.packageName,\n routes: [route],\n localCapable,\n ownerApp: ownerByPkg.get(def.packageName),\n })\n }\n }\n\n return [...byPkg.values()]\n}\n\n/** Discover apps + configs and assemble the {@link WizardModel} (backends resolved per frontend). */\nexport const gatherWizardModel = async (root: string): Promise<WizardModel> => {\n const apiApps = discoverApiApps(root)\n const uiApps = discoverUiApps(root)\n const config = await getInfraKitConfig()\n\n const ownerByPkg = new Map(\n apiApps.map((a) => {\n return [a.packageName, a.name] as const\n }),\n )\n const apiPkgByName = new Map(\n apiApps.map((a) => {\n return [a.name, a.packageName] as const\n }),\n )\n const uiNames = new Set(\n uiApps.map((a) => {\n return a.name\n }),\n )\n const apiNames = new Set(\n apiApps.map((a) => {\n return a.name\n }),\n )\n const allNames = [...new Set([...apiNames, ...uiNames])].sort()\n\n const apps: WizardApp[] = await Promise.all(\n allNames.map(async (name): Promise<WizardApp> => {\n const hasUi = uiNames.has(name)\n const routes = hasUi ? (await loadDev(path.join(root, 'apps', name, 'ui')))?.proxy?.routes : undefined\n\n return {\n name,\n hasApi: apiNames.has(name),\n hasUi,\n apiPackage: apiPkgByName.get(name),\n backends: routes ? groupBackends(routes, ownerByPkg) : [],\n }\n }),\n )\n\n return { apps, presets: Object.keys(config.devServersPresets ?? {}), environments: config.environments }\n}\n\n/** A short \"client (ui+api)\" style label for the app checkbox. */\nconst appLabel = (app: WizardApp): string => {\n const parts = [app.hasUi ? 'ui' : null, app.hasApi ? 'api' : null].filter(Boolean)\n\n return `${app.name} (${parts.join('+')})`\n}\n\n/**\n * Ask, per selected frontend that proxies to a backend, which backends run locally. A cloud-only route\n * (no `local` in `from`) is shown disabled. Returns the `localBackendsByApp` map.\n */\nconst promptLocalBackends = async (\n prompts: WizardPrompts,\n selectedApps: string[],\n model: WizardModel,\n): Promise<Record<string, string[]>> => {\n const byName = new Map(\n model.apps.map((a) => {\n return [a.name, a] as const\n }),\n )\n const result: Record<string, string[]> = {}\n\n for (const name of selectedApps) {\n const app = byName.get(name)\n\n if (!app?.hasUi || app.backends.length === 0) continue\n\n const choices: WizardChoice[] = app.backends.map((b) => {\n const label = `${b.routes.join(',')} \u2192 ${b.packageName}`\n\n return b.localCapable && b.ownerApp != null\n ? { name: label, value: b.packageName, checked: true }\n : { name: `${label} (cloud only)`, value: b.packageName, disabled: '(cloud only)' }\n })\n\n result[name] = await prompts.checkbox({ message: `\uD83D\uDD00 ${name}: which backends run locally?`, choices })\n }\n\n return result\n}\n\n/** Build the audit context from the gathered model (no re-reading of configs). */\nconst buildAuditContext = (model: WizardModel): PresetProxyContext => {\n const discovered: DiscoveredParts = {\n api: model.apps\n .filter((a) => {\n return a.hasApi\n })\n .map((a) => {\n return a.name\n }),\n ui: model.apps\n .filter((a) => {\n return a.hasUi\n })\n .map((a) => {\n return a.name\n }),\n }\n const apiPkgByApp: Record<string, string> = {}\n const routeToPkg = new Map<string, string>()\n\n for (const app of model.apps) {\n // Map EVERY discovered api app to its own api package \u2014 including api-only apps a frontend proxies\n // to cross-app \u2014 so `launchedPkgs` in validatePresetProxy is faithful (mirrors preset-proxy-check.ts).\n // Deriving this from proxy backends alone would miss api-only owners and false-positive the audit.\n if (app.apiPackage != null) apiPkgByApp[app.name] = app.apiPackage\n for (const b of app.backends) {\n for (const route of b.routes) {\n routeToPkg.set(`${app.name} ${route}`, b.packageName)\n }\n }\n }\n\n return {\n discovered,\n apiPkgByApp,\n routePkg: (app, route) => {\n return routeToPkg.get(`${app} ${route}`)\n },\n }\n}\n\n/**\n * Run the assembled plan through the SAME proxy-locality rule the root audit uses. Returns issue\n * messages (empty when clean). Catches a `local` override whose backend won't launch \u2014 the wizard's\n * derivation launches owners, so a hit means the backend has no discoverable owning app.\n */\nexport const auditManualPlan = (presetDef: DevPreset, model: WizardModel): string[] => {\n return validatePresetProxy({ __wizard__: presetDef }, buildAuditContext(model)).map((i) => {\n return i.message\n })\n}\n\n/** The manual-branch flow: app + per-app proxy + env + watch + cmux \u2192 audited plan \u2192 echo. */\nconst runManualBranch = async (prompts: WizardPrompts, model: WizardModel): Promise<WizardResult | null> => {\n if (model.apps.length === 0) {\n logger.warn('No apps discovered to run.')\n\n return null\n }\n\n const selectedApps = await prompts.checkbox({\n message: '\uD83D\uDCE6 Which apps?',\n choices: model.apps.map((a) => {\n return { name: appLabel(a), value: a.name, checked: true }\n }),\n })\n\n if (selectedApps.length === 0) {\n logger.warn('No apps selected.')\n\n return null\n }\n\n const localBackendsByApp = await promptLocalBackends(prompts, selectedApps, model)\n const watch = await prompts.confirm({ message: '\uD83D\uDC40 Rebuild & restart on save (watch)?', default: false })\n const cmux = await prompts.confirm({ message: '\uD83E\uDDE9 Run each app in its own cmux pane?', default: false })\n\n const selection: ManualSelection = { apps: selectedApps, localBackendsByApp, watch, cmux }\n const plan = deriveManualPlan(selection, model)\n\n if (plan.anyCloudRoute) {\n selection.env = await prompts.select({\n message: '\u2601\uFE0F Point cloud routes at which environment?',\n choices: model.environments.map((e) => {\n return { name: e, value: e }\n }),\n })\n process.env[INFRA_KIT_ENV_VAR] = selection.env\n }\n\n const issues = auditManualPlan(plan.presetDef, model)\n\n if (issues.length > 0) {\n logger.warn('\u26A0\uFE0F Proxy audit found issues with this selection:')\n for (const issue of issues) {\n logger.warn(` \u2022 ${issue}`)\n }\n\n return null\n }\n\n echoManual(plan, selection, model)\n\n // cmux is backend-pane-only and preset-unaware: hand it the selected API apps as an include list.\n const apiApps = plan.targetKeys\n .filter((k) => {\n return k.endsWith('/api')\n })\n .map((k) => {\n return k.split('/')[0]!\n })\n\n // An empty include would collapse to `null` in `normalizeAppInclude` and make cmux run EVERY api app,\n // so a cmux run with no selected backends (e.g. an all-frontend selection) falls back to in-process.\n if (cmux && apiApps.length > 0) {\n return { include: apiApps, watch, cmux: true }\n }\n\n if (cmux) {\n logger.info('\u2139\uFE0F cmux needs at least one local backend (panes are backend-only) \u2014 running in-process instead.')\n }\n\n return { presetDef: plan.presetDef, watch, cmux: false }\n}\n\n/** Print the equivalent flag command (and, for a part-level selection, the save-as-preset hint). */\nconst echoManual = (plan: DerivedPlan, selection: ManualSelection, model: WizardModel): void => {\n commandEcho.start('dev')\n commandEcho.setInteractive()\n\n const eq = equivalentCommand(plan, selection, model)\n\n commandEcho.addOption(eq.flags, true)\n commandEcho.print()\n\n if (!eq.exact) {\n logger.info('\u2139\uFE0F This part-level selection has no exact single-flag form \u2014 save it as a devPreset to reproduce it.')\n }\n}\n\n/** The preset-branch flow: run a named preset, asking only whether to watch (presets can't encode it). */\nconst runPresetBranch = async (prompts: WizardPrompts, preset: string): Promise<WizardResult> => {\n const watch = await prompts.confirm({ message: '\uD83D\uDC40 Rebuild & restart on save (watch)?', default: false })\n\n commandEcho.start('dev')\n commandEcho.setInteractive()\n commandEcho.addOption(preset, true)\n if (watch) commandEcho.addOption('--watch', true)\n commandEcho.print()\n\n return { preset, watch, cmux: false }\n}\n\n/** Sentinel value for the \"Manual (custom)\" step-0 choice (a preset name can never be empty). */\nexport const MANUAL_CHOICE = ' manual'\n\n/**\n * Drive the wizard's branch flow over an ALREADY-gathered model (no disk/config access). Split from\n * {@link runDevWizard} so the flow is unit-testable with scripted prompts + a fixture model.\n */\nexport const runWizardFlow = async (prompts: WizardPrompts, model: WizardModel): Promise<WizardResult | null> => {\n if (model.presets.length === 0) {\n return runManualBranch(prompts, model)\n }\n\n const choice = await prompts.select({\n message: '\uD83D\uDE80 Start from a preset, or configure manually?',\n choices: [\n ...model.presets.map((p) => {\n return { name: p, value: p }\n }),\n { name: 'Manual (custom)\u2026', value: MANUAL_CHOICE },\n ],\n })\n\n return choice === MANUAL_CHOICE ? runManualBranch(prompts, model) : runPresetBranch(prompts, choice)\n}\n\n/**\n * Gather the model from disk/config, then drive the wizard flow, returning the resolved run options (or\n * null on cancel / empty selection). The entry point calls this on a bare TTY `infra-kit dev`.\n */\nexport const runDevWizard = async (\n prompts: WizardPrompts = defaultPrompts,\n root: string = findMonorepoRoot(process.cwd()),\n): Promise<WizardResult | null> => {\n return runWizardFlow(prompts, await gatherWizardModel(root))\n}\n", "/**\n * Pure core of the interactive `infra-kit dev` wizard (the no-args, TTY path).\n *\n * The wizard is FRONTEND-CENTRIC: you pick apps to run, and for each frontend you choose, per backend\n * it proxies to, whether that backend runs LOCALLY (its `/api` is launched \u2192 the route resolves local)\n * or stays CLOUD (not launched \u2192 the route proxies to the `<env>` cloud target). A backend runs iff a\n * frontend points at it locally, or its app is selected and not demoted to cloud. This module turns\n * those answers into an in-memory {@link DevPreset} the runner consumes verbatim (part-level targets\n * that the app-name-only `--app` include cannot express) \u2014 and is side-effect-free so the mapping is\n * fully unit-testable. The impure I/O (discovery, inquirer prompts, audit, launch) lives in\n * {@link file://./dev-wizard-run.ts}.\n */\nimport type { DevPreset, ProxySource } from 'src/lib/infra-kit-config'\n\n/**\n * One backend a frontend's proxy routes can be pointed at, grouped by the backend package. A frontend\n * that maps `/api` and `/media` to the same package yields ONE {@link ProxyBackend} with both routes \u2014\n * launching the package flips every one of its `local`-capable routes together (see `pickSource`).\n */\nexport interface ProxyBackend {\n /** Backend package name (a route's `packageName`). */\n packageName: string\n /** Route paths that resolve to this package (for display + the audit's per-route overrides). */\n routes: string[]\n /** True when at least one of `routes` lists `local` in its `from` capabilities \u2014 i.e. it is toggleable. A `false` backend is fixed-cloud, shown as info. */\n localCapable: boolean\n /** App folder whose `api` package is {@link packageName}, or undefined when no discovered app owns it (a `local` choice then has nothing to launch \u2014 the audit flags it). */\n ownerApp?: string\n}\n\n/** A discovered app as the wizard sees it: which parts exist + which backends its frontend proxies to. */\nexport interface WizardApp {\n /** App folder name (e.g. `client`). */\n name: string\n /** Has `apps/<app>/api` (a launchable backend). */\n hasApi: boolean\n /** Has `apps/<app>/ui` (a launchable frontend). */\n hasUi: boolean\n /**\n * This app's OWN `api` package name (from `apps/<app>/api`), or undefined when it has no api. Carried\n * so the audit can build a faithful `app \u2192 apiPackage` map for EVERY discovered api app \u2014 including\n * api-only apps a frontend proxies to cross-app \u2014 mirroring the root audit (`preset-proxy-check.ts`).\n */\n apiPackage?: string\n /** Backends this app's frontend proxies to (empty for api-only apps or a frontend with no `dev.proxy`). */\n backends: ProxyBackend[]\n}\n\n/** Everything the wizard needs, gathered impurely by {@link file://./dev-wizard-run.ts}. */\nexport interface WizardModel {\n /** All discovered apps (api and/or ui), sorted by name. */\n apps: WizardApp[]\n /** `devServersPresets` names (drives the preset-or-manual step-0). */\n presets: string[]\n /** Configured `environments` (the cloud-env `select` options). */\n environments: string[]\n}\n\n/** The manual-branch answers collected from the prompts, fed to {@link deriveManualPlan}. */\nexport interface ManualSelection {\n /** Selected app folder names. */\n apps: string[]\n /** app folder \u2192 backend package names the user chose to run LOCALLY (subset of that app's `localCapable` backends). */\n localBackendsByApp: Record<string, string[]>\n /** Chosen cloud env for cloud routes (undefined when nothing resolves cloud). */\n env?: string\n /** Rebuild + restart on save. */\n watch: boolean\n /** One cmux pane per app. */\n cmux: boolean\n}\n\n/** The resolved run plan derived from a {@link ManualSelection}. */\nexport interface DerivedPlan {\n /** In-memory preset handed to the runner (`options.presetDef`). */\n presetDef: DevPreset\n /** At least one route resolves to cloud \u2192 an env must be chosen (pre-flight gate). */\n anyCloudRoute: boolean\n /** Resolved `<app>/<part>` target keys, sorted \u2014 for the echo + audit display. */\n targetKeys: string[]\n}\n\n/** Index a model's apps by folder name for O(1) lookup. */\nconst byName = (model: WizardModel): Map<string, WizardApp> => {\n return new Map(\n model.apps.map((a) => {\n return [a.name, a]\n }),\n )\n}\n\ninterface ProxyPass {\n /** Owner apps whose `/api` must launch because a route was chosen local. */\n localOwners: Set<string>\n /** Selected apps whose OWN backend was chosen cloud \u2192 demote (drop their `/api`) unless still needed local. */\n demoteOwn: Set<string>\n /** frontend app \u2192 per-route source overrides (recorded for the audit; runtime derives from the api targets). */\n proxyByUiApp: Map<string, Record<string, ProxySource>>\n /** At least one route resolved cloud. */\n anyCloud: boolean\n}\n\n/**\n * Fold ONE backend of a frontend into the pass: record its Local/Cloud source per route, add its owner\n * to the launch set (local) or flag its own-api demotion + a cloud route (cloud). Extracted so\n * {@link resolveProxyPass}'s loop stays flat.\n */\nconst applyBackend = (\n backend: ProxyBackend,\n appName: string,\n localPkgs: ReadonlySet<string>,\n pass: ProxyPass,\n overrides: Record<string, ProxySource>,\n): void => {\n const isLocal = backend.localCapable && localPkgs.has(backend.packageName)\n\n if (isLocal) {\n if (backend.ownerApp != null) pass.localOwners.add(backend.ownerApp)\n } else {\n pass.anyCloud = true\n // The selected app's OWN backend, left cloud \u2192 run frontend-only (drop its /api later).\n if (backend.ownerApp === appName) pass.demoteOwn.add(appName)\n }\n\n for (const route of backend.routes) {\n overrides[route] = isLocal ? 'local' : 'cloud'\n }\n}\n\n/**\n * Resolve every selected frontend's per-backend Local/Cloud choice into the owner-api launch set, the\n * own-api demotions, and the per-route override map. Kept separate from {@link deriveManualPlan} so the\n * target-set arithmetic stays readable.\n */\nconst resolveProxyPass = (selection: ManualSelection, apps: Map<string, WizardApp>): ProxyPass => {\n const pass: ProxyPass = { localOwners: new Set(), demoteOwn: new Set(), proxyByUiApp: new Map(), anyCloud: false }\n\n for (const name of selection.apps) {\n const app = apps.get(name)\n\n if (!app?.hasUi) continue\n\n const localPkgs = new Set(selection.localBackendsByApp[name] ?? [])\n const overrides: Record<string, ProxySource> = {}\n\n for (const backend of app.backends) {\n applyBackend(backend, name, localPkgs, pass, overrides)\n }\n\n if (Object.keys(overrides).length > 0) pass.proxyByUiApp.set(name, overrides)\n }\n\n return pass\n}\n\n/**\n * Turn a {@link ManualSelection} into an in-memory {@link DevPreset}. Target rules:\n * - every selected app with a `ui` runs its frontend (`<app>/ui`);\n * - a selected app with an `api` runs it full-stack by default, UNLESS its own frontend demoted that\n * backend to cloud (and no other frontend needs it local);\n * - any backend a selected frontend proxies to LOCALLY launches (`<owner>/api`), even cross-app or\n * when the owner app was not itself selected.\n *\n * @example\n * // client (ui+api) selected, its /api chosen cloud \u2192 frontend-only:\n * deriveManualPlan(\n * { apps: ['client'], localBackendsByApp: {}, env: 'dev', watch: false, cmux: false },\n * { apps: [{ name: 'client', hasApi: true, hasUi: true,\n * backends: [{ packageName: 'client-api', routes: ['/api'], localCapable: true, ownerApp: 'client' }] }],\n * presets: [], environments: ['dev'] },\n * ).targetKeys // => ['client/ui']\n */\nexport const deriveManualPlan = (selection: ManualSelection, model: WizardModel): DerivedPlan => {\n const apps = byName(model)\n const uiApps = selection.apps.filter((n) => {\n return apps.get(n)?.hasUi === true\n })\n const apiTargets = new Set(\n selection.apps.filter((n) => {\n return apps.get(n)?.hasApi === true\n }),\n )\n\n const pass = resolveProxyPass(selection, apps)\n\n for (const owner of pass.localOwners) {\n apiTargets.add(owner)\n }\n for (const name of pass.demoteOwn) {\n if (!pass.localOwners.has(name)) apiTargets.delete(name)\n }\n\n const presetApps: Record<string, { proxy?: Record<string, ProxySource> }> = {}\n\n for (const name of uiApps) {\n const overrides = pass.proxyByUiApp.get(name)\n\n presetApps[`${name}/ui`] = overrides ? { proxy: overrides } : {}\n }\n for (const name of apiTargets) {\n presetApps[`${name}/api`] ??= {}\n }\n\n return {\n presetDef: { apps: presetApps, cmux: selection.cmux },\n anyCloudRoute: pass.anyCloud,\n targetKeys: Object.keys(presetApps).sort(),\n }\n}\n\n/** The equivalent non-interactive command, plus whether it reproduces the selection exactly. */\nexport interface EquivalentCommand {\n /** The `--app=\u2026`-form flag string (no leading `infra-kit dev`). */\n flags: string\n /**\n * True when `--app` reproduces the plan exactly \u2014 i.e. every involved app runs ALL the parts it has.\n * False for a part-level selection (e.g. frontend-only), which `--app` (app-name granularity) cannot\n * express; the caller then hints \"save as a preset\" for exact reproduction.\n */\n exact: boolean\n}\n\n/** App folder name from an `<app>/<part>` target key. */\nconst appOf = (key: string): string => {\n return key.split('/')[0]!\n}\n\n/**\n * Build the equivalent `infra-kit dev --app=\u2026 [--watch] [--cmux]` flag string for a derived plan.\n * `exact` is true only when the target set covers every part each involved app HAS (a whole-app\n * selection), since `--app` filters by app name, not part.\n */\nexport const equivalentCommand = (\n plan: DerivedPlan,\n selection: ManualSelection,\n model: WizardModel,\n): EquivalentCommand => {\n const apps = byName(model)\n const involved = [...new Set(plan.targetKeys.map(appOf))].sort()\n const present = new Set(plan.targetKeys)\n\n const exact = involved.every((name) => {\n const app = apps.get(name)\n\n if (!app) return false\n\n const uiOk = !app.hasUi || present.has(`${name}/ui`)\n const apiOk = !app.hasApi || present.has(`${name}/api`)\n\n return uiOk && apiOk\n })\n\n const parts = [`--app=${involved.join(',')}`]\n\n if (selection.watch) parts.push('--watch')\n if (selection.cmux) parts.push('--cmux')\n\n return { flags: parts.join(' '), exact }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["inquirerCheckbox", "inquirerConfirm", "inquirerSelect", "path", "process", "byName", "model", "a", "applyBackend", "backend", "appName", "localPkgs", "pass", "overrides", "isLocal", "route", "resolveProxyPass", "selection", "apps", "name", "app", "deriveManualPlan", "uiApps", "n", "apiTargets", "owner", "presetApps", "appOf", "key", "equivalentCommand", "plan", "involved", "present", "exact", "uiOk", "apiOk", "parts", "defaultPrompts", "cfg", "inquirerSelect", "process", "inquirerCheckbox", "inquirerConfirm", "groupBackends", "routes", "ownerByPkg", "byPkg", "route", "def", "existing", "localCapable", "gatherWizardModel", "root", "apiApps", "discoverApiApps", "uiApps", "discoverUiApps", "config", "getInfraKitConfig", "a", "apiPkgByName", "uiNames", "apiNames", "allNames", "name", "hasUi", "loadDev", "path", "appLabel", "app", "parts", "promptLocalBackends", "prompts", "selectedApps", "model", "byName", "result", "choices", "b", "label", "buildAuditContext", "discovered", "apiPkgByApp", "routeToPkg", "auditManualPlan", "presetDef", "validatePresetProxy", "i", "runManualBranch", "logger", "localBackendsByApp", "watch", "cmux", "selection", "plan", "deriveManualPlan", "e", "INFRA_KIT_ENV_VAR", "issues", "issue", "echoManual", "k", "commandEcho", "eq", "equivalentCommand", "runPresetBranch", "preset", "MANUAL_CHOICE", "runWizardFlow", "choice", "p", "runDevWizard", "findMonorepoRoot"]
7
+ }
@@ -126,6 +126,7 @@ export declare const readLocalSet: (cwd: string) => ReadonlySet<string>;
126
126
  */
127
127
  export declare const infraKitDev: (options?: InfraKitDevOptions) => Promise<{
128
128
  port?: number;
129
+ strictPort?: boolean;
129
130
  proxy: InfraKitViteProxy;
130
131
  }>;
131
132
  /**
package/dist/mcp.js CHANGED
@@ -1,2 +1,2 @@
1
- import{P as m}from"./chunk-V5IDN7ZM.js";import{t as s,u as p,v as c}from"./chunk-6RRAK2QO.js";import"./chunk-Z6KTUVIC.js";import"./chunk-IX2A34UU.js";import"./chunk-2PZRQHWF.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";import h from"node:process";import o from"node:process";var l=r=>{o.on("SIGINT",()=>{r.info({msg:"Received SIGINT. Shutting down..."}),o.exit(0)}),o.on("SIGTERM",()=>{r.info({msg:"Received SIGTERM. Shutting down..."}),o.exit(0)}),o.on("uncaughtException",e=>{r.fatal({err:e,msg:"Uncaught Exception"}),r.error(`Uncaught Exception! Check ${s}. Shutting down...`),r.flush(),o.exit(1)}),o.on("unhandledRejection",(e,n)=>{r.fatal({reason:e,promise:n,msg:"Unhandled Rejection"}),r.error(`Unhandled Rejection! Check ${s}. Shutting down...`),r.flush(),o.exit(1)})};import{McpServer as x}from"@modelcontextprotocol/sdk/server/mcp.js";var d=async r=>{};var u=async r=>{};var a=r=>async e=>{let{toolName:n,handler:S}=r;c.info({msg:`Tool execution started: ${n}`,params:e});try{let i=await S({...e,confirmedCommand:!0});return c.info({msg:`Tool execution successful: ${n}`}),i}catch(i){throw c.error({err:i,params:e,msg:`Tool execution failed: ${n}`}),i}};var f=async r=>{for(let e of m())r.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema,outputSchema:e.outputSchema},a({toolName:e.name,handler:e.handler}))};async function v(){let r=new x({name:"infra-kit",version:"1.0.0"},{capabilities:{resources:{},tools:{},prompts:{}}});return await d(r),await u(r),await f(r),r}var t=p(),T=async()=>{let r;try{r=await v(),t.info("MCP Server instance created")}catch(e){t.error({err:e,msg:"Failed to create MCP server"}),t.error("Fatal error during server creation."),h.exit(1)}try{let e=new g;await r.connect(e),t.info({msg:"Server connected to transport. Ready."})}catch(e){t.error({err:e,msg:"Failed to initialize server"}),t.error("Fatal error during server transport init."),h.exit(1)}};l(t);T();
1
+ import{G as m}from"./chunk-AP63Y7KE.js";import"./chunk-Q36WX5RP.js";import"./chunk-Z6KTUVIC.js";import"./chunk-V375VXOL.js";import{t as s,u as p,v as c}from"./chunk-4YU4TAGX.js";import"./chunk-YGCVOVNX.js";import"./chunk-2PZRQHWF.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";import h from"node:process";import o from"node:process";var l=r=>{o.on("SIGINT",()=>{r.info({msg:"Received SIGINT. Shutting down..."}),o.exit(0)}),o.on("SIGTERM",()=>{r.info({msg:"Received SIGTERM. Shutting down..."}),o.exit(0)}),o.on("uncaughtException",e=>{r.fatal({err:e,msg:"Uncaught Exception"}),r.error(`Uncaught Exception! Check ${s}. Shutting down...`),r.flush(),o.exit(1)}),o.on("unhandledRejection",(e,n)=>{r.fatal({reason:e,promise:n,msg:"Unhandled Rejection"}),r.error(`Unhandled Rejection! Check ${s}. Shutting down...`),r.flush(),o.exit(1)})};import{McpServer as x}from"@modelcontextprotocol/sdk/server/mcp.js";var d=async r=>{};var u=async r=>{};var a=r=>async e=>{let{toolName:n,handler:S}=r;c.info({msg:`Tool execution started: ${n}`,params:e});try{let i=await S({...e,confirmedCommand:!0});return c.info({msg:`Tool execution successful: ${n}`}),i}catch(i){throw c.error({err:i,params:e,msg:`Tool execution failed: ${n}`}),i}};var f=async r=>{for(let e of m())r.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema,outputSchema:e.outputSchema},a({toolName:e.name,handler:e.handler}))};async function v(){let r=new x({name:"infra-kit",version:"1.0.0"},{capabilities:{resources:{},tools:{},prompts:{}}});return await d(r),await u(r),await f(r),r}var t=p(),T=async()=>{let r;try{r=await v(),t.info("MCP Server instance created")}catch(e){t.error({err:e,msg:"Failed to create MCP server"}),t.error("Fatal error during server creation."),h.exit(1)}try{let e=new g;await r.connect(e),t.info({msg:"Server connected to transport. Ready."})}catch(e){t.error({err:e,msg:"Failed to initialize server"}),t.error("Fatal error during server transport init."),h.exit(1)}};l(t);T();
2
2
  //# sourceMappingURL=mcp.js.map
package/dist/mcp.js.map CHANGED
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/entry/mcp.ts", "../src/lib/error-handlers/index.ts", "../src/mcp/server.ts", "../src/mcp/prompts/index.ts", "../src/mcp/resources/index.ts", "../src/lib/tool-handler/tool-handler.ts", "../src/mcp/tools/index.ts"],
4
4
  "sourcesContent": ["import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport process from 'node:process'\n\nimport { setupErrorHandlers } from 'src/lib/error-handlers'\nimport { initLoggerMcp } from 'src/lib/logger'\n\nimport { createMcpServer } from '../mcp/server'\n\nconst logger = initLoggerMcp()\n\nconst startServer = async () => {\n let server\n\n try {\n server = await createMcpServer()\n\n logger.info('MCP Server instance created')\n } catch (error) {\n logger.error({ err: error, msg: 'Failed to create MCP server' })\n logger.error(`Fatal error during server creation.`)\n\n process.exit(1)\n }\n\n try {\n const transport = new StdioServerTransport()\n\n await server.connect(transport)\n\n logger.info({ msg: 'Server connected to transport. Ready.' })\n } catch (error) {\n logger.error({ err: error, msg: 'Failed to initialize server' })\n logger.error(`Fatal error during server transport init.`)\n\n process.exit(1)\n }\n}\n\n// Setup error handlers\nsetupErrorHandlers(logger)\n\n// Start the server\nstartServer()\n", "import process from 'node:process'\nimport type { Logger } from 'pino'\n\nimport { LOG_FILE_PATH } from '../logger/index'\n\n/**\n * Setup error handlers for the application\n * @param logger - The logger instance\n *\n * ONLY FOR SERVER!\n */\nexport const setupErrorHandlers = (logger: Logger) => {\n process.on('SIGINT', () => {\n logger.info({ msg: 'Received SIGINT. Shutting down...' })\n process.exit(0)\n })\n\n process.on('SIGTERM', () => {\n logger.info({ msg: 'Received SIGTERM. Shutting down...' })\n process.exit(0)\n })\n\n process.on('uncaughtException', (error) => {\n logger.fatal({ err: error, msg: 'Uncaught Exception' })\n logger.error(`Uncaught Exception! Check ${LOG_FILE_PATH}. Shutting down...`)\n logger.flush()\n process.exit(1)\n })\n\n process.on('unhandledRejection', (reason, promise) => {\n logger.fatal({ reason, promise, msg: 'Unhandled Rejection' })\n logger.error(`Unhandled Rejection! Check ${LOG_FILE_PATH}. Shutting down...`)\n logger.flush()\n process.exit(1)\n })\n}\n", "import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { initializePrompts } from './prompts'\nimport { initializeResources } from './resources'\nimport { initializeTools } from './tools'\n\nexport async function createMcpServer() {\n const server = new McpServer(\n {\n name: 'infra-kit',\n version: '1.0.0',\n },\n {\n capabilities: {\n resources: {},\n tools: {},\n prompts: {},\n },\n },\n )\n\n await initializePrompts(server)\n await initializeResources(server)\n await initializeTools(server)\n\n return server\n}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport const initializePrompts = async (_server: McpServer) => {}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport const initializeResources = async (_server: McpServer) => {}\n", "import { logger } from 'src/lib/logger'\nimport type { ToolsExecutionResult } from 'src/types'\n\ninterface ToolHandlerArgs {\n toolName: string\n handler: (params: any) => Promise<ToolsExecutionResult>\n}\n\nexport const createToolHandler = (args: ToolHandlerArgs) => {\n return async (params: unknown) => {\n const { toolName, handler } = args\n\n logger.info({ msg: `Tool execution started: ${toolName}`, params })\n try {\n const payload = await handler({ ...(params as object), confirmedCommand: true })\n\n logger.info({ msg: `Tool execution successful: ${toolName}` })\n\n return payload\n } catch (error) {\n logger.error({\n err: error,\n params,\n msg: `Tool execution failed: ${toolName}`,\n })\n\n throw error\n }\n }\n}\n", "import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { getExposedMcpTools } from 'src/lib/command-catalog'\nimport { createToolHandler } from 'src/lib/tool-handler'\n\nexport const initializeTools = async (server: McpServer) => {\n // The registered tool set is derived from the single command catalog, filtered\n // by its explicit `mcpExposed` allowlist. doctor / vendor-sync / vendor-manifest\n // are intentionally excluded there and must never be registered here.\n for (const tool of getExposedMcpTools()) {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n },\n createToolHandler({ toolName: tool.name, handler: tool.handler }),\n )\n }\n}\n"],
5
- "mappings": "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",
5
+ "mappings": "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",
6
6
  "names": ["StdioServerTransport", "process", "process", "setupErrorHandlers", "logger", "process", "error", "LOG_FILE_PATH", "reason", "promise", "McpServer", "initializePrompts", "_server", "initializeResources", "_server", "createToolHandler", "args", "params", "toolName", "handler", "logger", "payload", "error", "initializeTools", "server", "tool", "getExposedMcpTools", "createToolHandler", "createMcpServer", "server", "McpServer", "initializePrompts", "initializeResources", "initializeTools", "logger", "initLoggerMcp", "startServer", "server", "createMcpServer", "error", "process", "transport", "StdioServerTransport", "setupErrorHandlers"]
7
7
  }
package/dist/vite.js CHANGED
@@ -1,2 +1,2 @@
1
- import{b as r,d as t,e}from"./chunk-IX2A34UU.js";import{a as i}from"./chunk-2PZRQHWF.js";export{t as infraKitDev,e as infraKitProxy,r as resolveProxyConfig,i as slugifyRelease};
1
+ import{b as r,d as t,e}from"./chunk-YGCVOVNX.js";import{a as i}from"./chunk-2PZRQHWF.js";export{t as infraKitDev,e as infraKitProxy,r as resolveProxyConfig,i as slugifyRelease};
2
2
  //# sourceMappingURL=vite.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.129",
4
+ "version": "0.1.130",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/aws-lambda": "^8.10.162",
67
- "@types/react": "catalog:",
67
+ "@types/react": "19.2.17",
68
68
  "@wl/eslint-config": "workspace:*",
69
69
  "@wl/vitest-config": "workspace:*",
70
70
  "esbuild": "^0.28.1",
@@ -1,5 +0,0 @@
1
- import O from"node:process";import $ from"pino";import se from"pino-pretty";var _="/tmp/mcp-infra-kit.log",Dt=()=>{let e=O.argv.includes("--debug")?"debug":"info",t=$({level:e},$.destination({dest:_}));return t.info(`Logger initialized with level: ${e}. Logging to: ${_}`),t},ie=()=>{let e=O.argv.includes("--debug")?"debug":"info",t=["time","pid","hostname"];return e==="debug"&&t.push("level"),$({level:e},se({destination:2,ignore:t.join(","),colorize:!0}))},g=ie();import*as u from"node:fs";import*as c from"node:path";function ae(e){let t=e;for(let r=0;r<10;r++){let n=c.join(t,"pnpm-workspace.yaml");if(u.existsSync(n))return t;t=c.dirname(t)}throw new Error("Could not find monorepo root (pnpm-workspace.yaml)")}function B(e,t){let r=c.join(e,"package.json");if(!u.existsSync(r))return t;try{let n=JSON.parse(u.readFileSync(r,"utf-8"));return typeof n.name=="string"?n.name:t}catch{return t}}function It(e){let t=c.join(e,"apps"),r=[];if(!u.existsSync(t))throw new Error(`Apps directory not found: ${t}`);let n=u.readdirSync(t,{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name);for(let o of n){let i=c.join(t,o,"api"),a=c.join(i,"serverless.yml");u.existsSync(a)&&r.push({name:o,packageName:B(i,o),path:i})}return r}function ce(e){let t=c.join(e,"package.json");if(!u.existsSync(t))return!1;try{let r=JSON.parse(u.readFileSync(t,"utf-8"));return typeof r.scripts?.dev=="string"&&r.scripts.dev.length>0}catch{return!1}}function Et(e){let t=c.join(e,"apps"),r=[];if(!u.existsSync(t))return r;let n=u.readdirSync(t,{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name);for(let o of n){let i=c.join(t,o,"ui");u.existsSync(i)&&ce(i)&&r.push({name:o,packageName:B(i,o),path:i})}return r}function jt(e){let t=e?.filter(Boolean)??[];return t.length>0?t:null}function Nt(e){let t=ae(e),r=c.relative(c.join(t,"apps"),e),n=r.split(c.sep)[0];if(r===""||r.startsWith("..")||c.isAbsolute(r)||!n)throw new Error(`--self: not inside an apps/<app> directory (cwd: ${e}). Run from an app folder or use --app=<name>.`);return n}function Lt(e){let t=c.join(e,"packages");if(!u.existsSync(t))return[];let r=u.readdirSync(t,{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name),n=[];for(let o of r){let i=c.join(t,o,"dist");u.existsSync(i)&&u.statSync(i).isDirectory()&&n.push(i)}return n}function Tt(e){return e.map(t=>c.join(t.path,"dist")).filter(t=>u.existsSync(t))}function Wt(e,t,r){let n=c.normalize(e),o=r.find(a=>n.startsWith(c.normalize(a)));return o?{kind:"package",packageDir:o}:{kind:"app",app:t.find(a=>n.startsWith(c.normalize(a)))}}import pe from"node:process";import{$ as D}from"zx";var ue=async()=>{try{return await D`cmux --version`.quiet(),!0}catch{return!1}},le=async e=>{let{cwd:t,title:r,layout:n}=e,o=JSON.stringify(n),a=(await D({env:{...pe.env,CMUX_QUIET:"1"}})`cmux new-workspace --name ${r} --cwd ${t} --focus false --layout ${o}`).stdout;return fe(a)},me=async e=>{try{await D`cmux close-workspace --workspace ${e}`.quiet()}catch(t){g.debug({error:t,ref:e},"cmux: skipped closing dev workspace")}},fe=e=>{let t=e.match(/workspace:\d+/);if(!t)throw new Error("cmux: could not locate workspace ref in new-workspace output");return t[0]};var de=/\bv(\d+\.\d+\.\d+)\b/g,h=e=>e.trim().replace(/\s+/g," ").replace(de,"$1");import{$ as K}from"zx";var ge=async e=>{try{let t=(await K`cmux list-workspaces`.quiet()).stdout,r=he(t,e);if(!r)return;await K`cmux close-workspace --workspace ${r}`.quiet()}catch(t){g.debug({error:t,title:e},"cmux: skipped closing workspace")}},he=(e,t)=>{let r=h(t);for(let n of e.split(`
2
- `)){let o=n.match(/^[* ]\s*(workspace:\d+)\s+(.+?)(?:\s+\[selected\])?\s*$/);if(!o)continue;let i=o[1],a=o[2]?.trim()??"";if(h(a)===r)return i}};import{$ as xe}from"zx";var we=async()=>{try{let e=(await xe`cmux list-workspaces`.quiet()).stdout,t=new Set;for(let r of e.split(`
3
- `)){let n=r.match(/^[* ]\s*workspace:\d+\s+(.+?)(?:\s+\[selected\])?\s*$/);if(!n)continue;let o=n[1]?.trim();o&&t.add(h(o))}return t}catch(e){return g.debug({error:e},"cmux: skipped listing workspace titles"),new Set}};import{$ as P}from"zx";import L from"node:fs/promises";import _e from"node:os";import b from"node:path";import{z as s}from"zod";import V from"node:path";import{$ as m}from"zx";var ke=/^v?(\d+)\.(\d+)\.(\d+)$/,ve=/^(\d+)\.(\d+)\.(\d+)$/,ye=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,C="release/",A="release/v",M="refs/heads/",be="next";var z=new Set(["dev","main","next","hotfix","regular","release"]),d=class extends Error{constructor(t){super(t),this.name="InvalidReleaseNameError"}},x=class extends Error{constructor(t){super(t),this.name="InvalidReleaseRefError"}},G=e=>e.startsWith(M)?e.slice(M.length):e,U=(e,t,r)=>({kind:"version",semver:{major:e,minor:t,patch:r},raw:`${e}.${t}.${r}`}),I=e=>{if(e.length===0)throw new d('Release name is empty. Provide a kebab-case name like "checkout-redesign".');if(e.length>50)throw new d(`Release name "${e}" is ${e.length} characters; the maximum is 50.`);if(!ye.test(e))throw new d(`Release name "${e}" is not kebab-case. Use lowercase letters, digits, and single hyphens, e.g. "checkout-redesign".`);if(z.has(e))throw new d(`Release name "${e}" is reserved. Reserved names: ${[...z].join(", ")}.`)},v=e=>{let t=G(e.trim());if(!t.startsWith(C))return null;if(t.startsWith(A)){let n=t.slice(A.length),o=ve.exec(n);if(o)return U(Number(o[1]),Number(o[2]),Number(o[3]))}let r=t.slice(C.length);try{I(r)}catch{return null}return{kind:"name",name:r,raw:r}},Pe=e=>{let t=e.trim();if(G(t).startsWith(C)){let o=v(t);if(!o)throw new x(`"${e}" looks like a release branch but is not a valid release/v<semver> or release/<name> ref.`);return o}let n=ke.exec(t);if(n)return U(Number(n[1]),Number(n[2]),Number(n[3]));if(t.toLowerCase()===be)throw new x('The "next" token must be resolved to a concrete version (via computeNextVersion) before parsing a release ref.');try{I(t)}catch(o){let i=o instanceof Error?o.message:String(o);throw new x(`Cannot parse "${e}" as a release ref: ${i}`)}return{kind:"name",name:t,raw:t}},Ce=e=>e.kind==="version"?`${A}${e.raw}`:`${C}${e.name}`,Re=(e,t)=>{let r=t==="hotfix"?"Hotfix":"Release";return e.kind==="version"?`${r} v${e.raw}`:`${r} ${e.name}`},Se=e=>e.kind==="version"?`Release v${e.raw} (RC)`:`Release ${e.name} (RC)`,$e=e=>e.kind==="version"?`v${e.raw}`:e.name,E=e=>e.raw,j=e=>e==null?!1:v(e)!==null,F=e=>{if(e===void 0)return null;let t=e instanceof Date?e.getTime():new Date(e).getTime();return Number.isNaN(t)?null:t},De=(e,t,r)=>{if(e.kind==="version"&&t.kind==="version")return e.semver.major!==t.semver.major?e.semver.major-t.semver.major:e.semver.minor!==t.semver.minor?e.semver.minor-t.semver.minor:e.semver.patch-t.semver.patch;if(e.kind==="version")return-1;if(t.kind==="version")return 1;let n=F(r?.a),o=F(r?.b);return n!==null&&o!==null&&n!==o?n-o:e.name<t.name?-1:e.name>t.name?1:0};var Ae=async e=>{let r=(await m`git worktree list`).stdout.split(`
4
- `).filter(Boolean),n={release:Ie,feature:Ee};return r.map(n[e]).filter(o=>o!==null)},q=e=>{let t=e.trimEnd();if(!t.endsWith("]"))return null;let r=t.lastIndexOf("[");if(r===-1)return null;let n=t.slice(r+1,-1);return n.length>0?n:null},Ie=e=>{let t=q(e);return j(t)?t:null},Ee=e=>{let t=q(e);return t?.startsWith("feature/")?t:null},y=async()=>(await m`git rev-parse --show-toplevel`).stdout.trim(),H=async()=>(await m`git rev-parse --abbrev-ref HEAD`).stdout.trim(),je=async()=>(await m`git status --porcelain`).stdout.trim().length===0,Ne=async()=>{let e=await y(),[t,r]=await Promise.all([m({cwd:e})`git rev-parse --absolute-git-dir`,m({cwd:e})`git rev-parse --git-common-dir`]),n=t.stdout.trim(),o=V.resolve(e,r.stdout.trim());return n!==o},Le=async()=>{let e=await y();return V.basename(e)},Te=async e=>{(await m`git branch --list ${e}`).stdout.trim().length!==0&&await H()!==e&&await m`git branch -D ${e}`},We=async e=>{(await m`git ls-remote --heads origin ${e}`).stdout.trim().length!==0&&await m`git push origin --delete ${e}`};var J="infra-kit.json",Y=".infra-kit",Oe="infra-kit.json",Be="projects",Ke=s.object({provider:s.literal("doppler"),config:s.object({name:s.string().min(1)})}),Me=s.discriminatedUnion("provider",[Ke]),ze=s.object({workspaceConfigPath:s.string().min(1)}),Fe=s.object({provider:s.literal("cursor"),config:ze}),Ge=s.object({}),Ue=s.object({provider:s.literal("zed"),config:Ge}),X=s.discriminatedUnion("provider",[Fe,Ue]),Ve=s.union([X,s.array(X).min(1)]),qe=s.object({provider:s.literal("jira"),config:s.object({baseUrl:s.string().url(),projectId:s.number().int().positive()})}),He=s.discriminatedUnion("provider",[qe]),Je=["two-columns","three-pane"],Xe=s.object({layout:s.enum(Je).optional()}),Ye=s.object({openInGithubDesktop:s.boolean().optional(),openInCmux:s.boolean().optional(),cmux:Xe.optional()}),Qe=s.object({port:s.number().int().positive().optional(),prefixUrl:s.string().min(1).optional()}).strict(),Ze=s.record(s.string().min(1),Qe),et=s.enum(["local","cloud"]),tt=s.object({watchDeps:s.boolean().optional(),proxy:s.record(s.string().min(1),et).optional()}).strict(),rt=s.object({apps:s.record(s.string().min(1),tt).optional(),cmux:s.boolean().optional()}).strict(),nt=s.record(s.string().min(1),rt),ot=s.object({trigger:s.enum(["shell-startup","cli-invocation"]),config:s.string().min(1)}).strict(),Q=s.object({environments:s.array(s.string().min(1)).min(1),envManagement:Me,ide:Ve.optional(),taskManager:He.optional(),worktrees:Ye.optional(),envAutoLoad:ot.optional(),dev:Ze.optional(),devPresets:nt.optional()}),Z=Q.superRefine((e,t)=>{if(!Array.isArray(e.ide))return;let r=new Set;for(let n of e.ide){if(r.has(n.provider)){t.addIssue({code:"custom",message:"each IDE provider may appear at most once",path:["ide"]});return}r.add(n.provider)}}),ee=Q.partial(),st=e=>{let t=e.ide;return t?Array.isArray(t)?t:[t]:[]},it="two-columns",T=e=>e.worktrees?.cmux?.layout??it,w=null,te=async()=>{let e=await y(),t=b.basename(e),r=b.join(_e.homedir(),Y);return{main:b.join(e,J),userGlobal:b.join(r,Oe),userProject:b.join(r,Be,t,J),projectName:t}},W=async()=>{let e=await te(),t;try{t=await L.stat(e.main)}catch{w=null;let l=e.main.replace(/\.json$/,".yml");throw await N(l)?new Error(`infra-kit.json not found at ${e.main}. A legacy infra-kit.yml exists \u2014 run \`infra-kit init\` to convert it.`):new Error(`infra-kit.json not found at ${e.main}`)}let[r,n]=await Promise.all([N(e.userGlobal),N(e.userProject)]),o={main:Number(t.mtimeMs),userGlobal:r?Number(r.mtimeMs):null,userProject:n?Number(n.mtimeMs):null};if(w&&pt(w.mtimes,o))return w.value;let i=[{label:"infra-kit.json",path:e.main,required:!0},{label:"~/.infra-kit/infra-kit.json",path:e.userGlobal,required:!1},{label:`~/.infra-kit/projects/${e.projectName}/infra-kit.json`,path:e.userProject,required:!1}],a={};for(let l of i){let f=await ut(l);f!==null&&(a={...a,...f})}let p=Z.safeParse(a);if(!p.success)throw new Error(`Invalid merged infra-kit config: ${s.prettifyError(p.error)}`);return w={mtimes:o,value:p.data},p.data},at=()=>{w=null},N=async e=>{try{return await L.stat(e)}catch{return null}},ct=async e=>{try{return await L.readFile(e,"utf-8")}catch{return null}},pt=(e,t)=>{let r=Object.keys(e);return r.length!==Object.keys(t).length?!1:r.every(n=>e[n]===t[n])},ut=async e=>{let t=await ct(e.path);if(t===null){if(e.required)throw new Error(`${e.label} not found at ${e.path}`);return null}let r;try{r=t.trim()===""?{}:JSON.parse(t)}catch(o){throw new Error(`Invalid JSON in ${e.label} at ${e.path}: ${o.message}`)}let n=ee.safeParse(r);if(!n.success)throw new Error(`Invalid ${e.label} at ${e.path}: ${s.prettifyError(n.error)}`);return n.data};var lt=async e=>{let{cwd:t,title:r}=e,n=T(await W()),o=(await P`cmux workspace create --cwd ${t}`).stdout,i=ft(o),a=(await P`cmux list-pane-surfaces --workspace ${i}`).stdout,p=mt(a);await P`cmux new-split right --workspace ${i} --surface ${p}`,n==="three-pane"&&await P`cmux new-split down --workspace ${i} --surface ${p}`,r&&await P`cmux workspace rename --workspace ${i} --title ${r}`},mt=e=>{let t=e.match(/surface:\d+/);if(!t)throw new Error("cmux: could not locate initial surface in list-pane-surfaces output");return t[0]},ft=e=>{let t=e.match(/workspace:\d+/);if(!t)throw new Error("cmux: could not locate workspace ref in workspace create output");return t[0]};var dt=e=>{let{repoName:t,branch:r}=e,n=v(r),o=n?E(n):r;return`${t} ${o}`};var gt=["api","ui"],ht=e=>{let t=e.split("/"),[r,n]=t;if(t.length>2||!r)throw new Error(`devPresets: invalid target "${e}" (expected "<app>" or "<app>/api|ui")`);if(n===void 0)return{appGlob:r,parts:gt,explicit:!1};if(n==="api"||n==="ui")return{appGlob:r,parts:[n],explicit:!0};throw new Error(`devPresets: invalid target part "${n}" in "${e}" (expected "api" or "ui")`)},xt=(e,t)=>t==="api"?e.api:e.ui,wt=e=>[...new Set([...e.api,...e.ui])].sort(),re=(e,t)=>`${e}/${t}`,kt=(e,t)=>(e?0:2)+(t?1:0),vt=(e,t)=>t.watchDeps===void 0?e??{watchDeps:!0,rank:t.rank,explicitValue:!1}:!e||!e.explicitValue?{watchDeps:t.watchDeps,rank:t.rank,explicitValue:!0}:t.rank>e.rank?{watchDeps:t.watchDeps,rank:t.rank,explicitValue:!0}:t.rank<e.rank?e:{watchDeps:e.watchDeps&&t.watchDeps,rank:e.rank,explicitValue:!0},yt=(e,t,r)=>{let{app:n,part:o,watchDeps:i,isGlob:a,explicit:p}=r;if(!xt(t,o).includes(n)){p&&!a&&e.unmatched.push(re(n,o));return}let l=re(n,o),f=vt(e.targets.get(l),{watchDeps:i,rank:kt(a,p)});e.targets.set(l,{app:n,part:o,...f})},bt=(e,t)=>{let r=Object.entries(e.apps??{"*":{}}),n={targets:new Map,proxy:{},unmatched:[]};for(let[a,p]of r){let{appGlob:l,parts:f,explicit:R}=ht(a),k=l==="*",ne=k?wt(t):[l];for(let S of ne){for(let oe of f)yt(n,t,{app:S,part:oe,watchDeps:p.watchDeps,isGlob:k,explicit:R});p.proxy&&(n.proxy[S]={...n.proxy[S]??{},...p.proxy})}}let o=[...n.targets.values()].map(({app:a,part:p,watchDeps:l})=>({app:a,part:p,watchDeps:l})),i=[...new Set(o.filter(a=>a.part==="api").map(a=>a.app))];return{targets:o,cmux:e.cmux??!1,proxy:n.proxy,localApps:i,unmatched:n.unmatched}},Pt=(e,t)=>Object.keys(e).find(r=>e[r]===t),Ct=({preset:e,app:t,route:r,launchedPkgs:n,ctx:o})=>{let i=o.routePkg(t,r);if(i===void 0)return{preset:e,app:t,route:r,kind:"unknown-route",message:`preset "${e}": proxy override "${r}" on "${t}" names a route not declared in ${t}'s infra-kit.config.ts dev.proxy.routes`};if(n.has(i))return null;let a=Pt(o.apiPkgByApp,i),p=a?`add "${a}/api" to the preset`:`launch the api whose package is "${i}"`;return{preset:e,app:t,route:r,pkg:i,kind:"backend-not-launched",message:`preset "${e}": proxy override "${r}" \u2192 "local" requires backend "${i}" to run locally, but the preset does not launch it \u2014 ${p}, or set "${r}" to "cloud"`}},Sr=(e,t)=>{let r=[];for(let[n,o]of Object.entries(e)){let i=bt(o,t.discovered),a=new Set(i.localApps.map(p=>t.apiPkgByApp[p]).filter(p=>p!==void 0));for(let[p,l]of Object.entries(i.proxy))for(let[f,R]of Object.entries(l)){if(R!=="local")continue;let k=Ct({preset:n,app:p,route:f,launchedPkgs:a,ctx:t});k&&r.push(k)}}return r};export{d as a,I as b,v as c,Pe as d,Ce as e,Re as f,Se as g,$e as h,E as i,j,De as k,Ae as l,y as m,H as n,je as o,Ne as p,Le as q,Te as r,We as s,_ as t,Dt as u,g as v,ae as w,It as x,Et as y,jt as z,Nt as A,Lt as B,Tt as C,Wt as D,bt as E,Sr as F,Y as G,Z as H,ee as I,st as J,te as K,W as L,at as M,h as N,ge as O,we as P,ue as Q,le as R,me as S,lt as T,dt as U};
5
- //# sourceMappingURL=chunk-6RRAK2QO.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/logger/index.ts", "../src/dev/discovery.ts", "../src/integrations/cmux/open-dev-workspace.ts", "../src/integrations/cmux/canonicalize-cmux-title.ts", "../src/integrations/cmux/close-workspace-by-title.ts", "../src/integrations/cmux/list-workspace-titles.ts", "../src/integrations/cmux/open-workspace-with-layout.ts", "../src/lib/infra-kit-config/infra-kit-config.ts", "../src/lib/git-utils/git-utils.ts", "../src/lib/release-id/release-id.ts", "../src/integrations/cmux/workspace-title.ts", "../src/dev/presets.ts"],
4
- "sourcesContent": ["import process from 'node:process'\nimport pino from 'pino'\nimport pretty from 'pino-pretty'\n\n// eslint-disable-next-line sonarjs/publicly-writable-directories\nexport const LOG_FILE_PATH = '/tmp/mcp-infra-kit.log'\n\nexport const initLoggerMcp = () => {\n const logLevel = process.argv.includes('--debug') ? 'debug' : 'info'\n\n const logger = pino({ level: logLevel }, pino.destination({ dest: LOG_FILE_PATH }))\n\n logger.info(`Logger initialized with level: ${logLevel}. Logging to: ${LOG_FILE_PATH}`)\n\n return logger\n}\n\nexport const initLoggerCLI = () => {\n const logLevel = process.argv.includes('--debug') ? 'debug' : 'info'\n\n const ignoreFields = ['time', 'pid', 'hostname']\n\n if (logLevel === 'debug') {\n ignoreFields.push('level')\n }\n\n const logger = pino(\n { level: logLevel },\n pretty({\n destination: 2,\n ignore: ignoreFields.join(','),\n colorize: true,\n }),\n )\n\n return logger\n}\n\n// Singleton logger instance for CLI usage\nexport const logger = initLoggerCLI()\n", "/**\n * Filesystem discovery helpers for the dev-server.\n *\n * These functions read the filesystem (walking for the monorepo root, scanning\n * `apps/` and `packages/`) but never `chdir` and never resolve ports / prefixes \u2014\n * that stays with the pure {@link file://./ports.ts} layer. The starting directory\n * is passed in so discovery is testable against a fixture root.\n */\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n/** Bare metadata for a discovered API app (no resolved port / prefix). */\nexport interface DiscoveredApiApp {\n /** App folder name (e.g. backoffice, client). */\n name: string\n /** Package name from package.json (e.g. sls-trvl-client), or the folder name as fallback. */\n packageName: string\n path: string\n}\n\n/** Walk up from `startDir` (max 10 levels) to the dir containing `pnpm-workspace.yaml`. */\nexport function findMonorepoRoot(startDir: string): string {\n let currentDir = startDir\n\n for (let i = 0; i < 10; i++) {\n const workspaceFile = path.join(currentDir, 'pnpm-workspace.yaml')\n\n if (fs.existsSync(workspaceFile)) {\n return currentDir\n }\n currentDir = path.dirname(currentDir)\n }\n\n throw new Error('Could not find monorepo root (pnpm-workspace.yaml)')\n}\n\n/** Read the `name` field from `<apiPath>/package.json`, falling back to `appName`. */\nexport function getPackageName(apiPath: string, appName: string): string {\n const pkgPath = path.join(apiPath, 'package.json')\n\n if (!fs.existsSync(pkgPath)) return appName\n\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { name?: string }\n\n return typeof pkg.name === 'string' ? pkg.name : appName\n } catch {\n return appName\n }\n}\n\n/** Discover every `apps/<app>` that has an `api/serverless.yml` (bare metadata only). */\nexport function discoverApiApps(root: string): DiscoveredApiApp[] {\n const appsDir = path.join(root, 'apps')\n const apps: DiscoveredApiApp[] = []\n\n if (!fs.existsSync(appsDir)) {\n throw new Error(`Apps directory not found: ${appsDir}`)\n }\n\n const appDirs = fs\n .readdirSync(appsDir, { withFileTypes: true })\n .filter((dirent) => {\n return dirent.isDirectory()\n })\n .map((dirent) => {\n return dirent.name\n })\n\n for (const appName of appDirs) {\n const apiPath = path.join(appsDir, appName, 'api')\n const serverlessPath = path.join(apiPath, 'serverless.yml')\n\n if (fs.existsSync(serverlessPath)) {\n apps.push({\n name: appName,\n packageName: getPackageName(apiPath, appName),\n path: apiPath,\n })\n }\n }\n\n return apps\n}\n\n/** Bare metadata for a discovered UI app (a frontend with its own framework `dev` script). */\nexport interface DiscoveredUiApp {\n /** App folder name (e.g. backoffice, client). */\n name: string\n /** package.json `name` \u2014 used as the exact `turbo run dev --filter` target. */\n packageName: string\n /** Absolute path to `apps/<app>/ui`. */\n path: string\n}\n\n/** True when `<dir>/package.json` declares a non-empty `scripts.dev`. */\nfunction hasDevScript(dir: string): boolean {\n const pkgPath = path.join(dir, 'package.json')\n\n if (!fs.existsSync(pkgPath)) return false\n\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { scripts?: Record<string, string> }\n\n return typeof pkg.scripts?.dev === 'string' && pkg.scripts.dev.length > 0\n } catch {\n return false\n }\n}\n\n/**\n * Discover every `apps/<app>/ui` whose package.json declares a `dev` script \u2014 the frontends\n * `infra-kit dev --ui` runs (via one delegated `turbo run dev`). Lenient: no `apps/` dir \u2192 `[]`\n * (UIs are optional), unlike {@link discoverApiApps} which requires it.\n */\nexport function discoverUiApps(root: string): DiscoveredUiApp[] {\n const appsDir = path.join(root, 'apps')\n const apps: DiscoveredUiApp[] = []\n\n if (!fs.existsSync(appsDir)) return apps\n\n const appDirs = fs\n .readdirSync(appsDir, { withFileTypes: true })\n .filter((dirent) => {\n return dirent.isDirectory()\n })\n .map((dirent) => {\n return dirent.name\n })\n\n for (const appName of appDirs) {\n const uiPath = path.join(appsDir, appName, 'ui')\n\n if (fs.existsSync(uiPath) && hasDevScript(uiPath)) {\n apps.push({\n name: appName,\n packageName: getPackageName(uiPath, appName),\n path: uiPath,\n })\n }\n }\n\n return apps\n}\n\n/** Normalize the `--app` include list: drop empties, and collapse an empty list to `null`. */\nexport function normalizeAppInclude(include?: string[] | null): string[] | null {\n const filtered = include?.filter(Boolean) ?? []\n\n return filtered.length > 0 ? filtered : null\n}\n\n/**\n * Infer the current app's folder name from `startDir`, so a per-app script can\n * run `infra-kit dev --self` without hardcoding its own app name. Walks up to the\n * monorepo root (via {@link findMonorepoRoot}), then takes the first path segment\n * of `startDir` relative to `<root>/apps` \u2014 e.g. `apps/config-handler/api` and\n * `apps/config-handler` both resolve to `config-handler`. Throws when `startDir`\n * isn't inside `<root>/apps/<app>/...` (repo root, or an unrelated path).\n *\n * @example\n * resolveSelfAppName('/repo/apps/config-handler/api') // => 'config-handler'\n */\nexport function resolveSelfAppName(startDir: string): string {\n const root = findMonorepoRoot(startDir)\n const relative = path.relative(path.join(root, 'apps'), startDir)\n const firstSegment = relative.split(path.sep)[0]\n const isOutsideApps = relative === '' || relative.startsWith('..') || path.isAbsolute(relative)\n\n if (isOutsideApps || !firstSegment) {\n throw new Error(\n `--self: not inside an apps/<app> directory (cwd: ${startDir}). Run from an app folder or use --app=<name>.`,\n )\n }\n\n return firstSegment\n}\n\n/**\n * Existing `packages/<pkg>/dist` directories under the monorepo root \u2014 the compiled\n * outputs `turbo watch` rewrites. Watched (alongside app dist) because editing a shared\n * lib rewrites only the lib's `dist`, never the dependent app's, so a package-dist change\n * is the only signal that a lib was rebuilt.\n */\nexport function getPackageDistDirs(root: string): string[] {\n const packagesDir = path.join(root, 'packages')\n\n if (!fs.existsSync(packagesDir)) return []\n\n const names = fs\n .readdirSync(packagesDir, { withFileTypes: true })\n .filter((d) => {\n return d.isDirectory()\n })\n .map((d) => {\n return d.name\n })\n const dirs: string[] = []\n\n for (const name of names) {\n const distDir = path.join(packagesDir, name, 'dist')\n\n if (fs.existsSync(distDir) && fs.statSync(distDir).isDirectory()) {\n dirs.push(distDir)\n }\n }\n\n return dirs\n}\n\n/** Existing `<app.path>/dist` directories for the given apps (order preserved). */\nexport function getAppDistDirs(apps: Array<{ path: string }>): string[] {\n return apps\n .map((app) => {\n return path.join(app.path, 'dist')\n })\n .filter((dir) => {\n return fs.existsSync(dir)\n })\n}\n\n/** How a watched dist change should be routed. */\nexport interface ChangeClassification {\n kind: 'app' | 'package'\n /** For app changes: the matched app dist dir (undefined when nothing matched). */\n app?: string\n /** For package changes: the matched `packages/<pkg>/dist` dir (the change identity used to scope restarts). */\n packageDir?: string\n}\n\n/**\n * Route a changed compiled-output path: a `packages/<pkg>/dist` change is a shared-package\n * rebuild \u2014 the matched package dist dir is returned so the caller can restart only the apps\n * whose dependency closure includes it. An `<app>/dist` change restarts only that app (the\n * matched app dist dir is returned; `undefined` when the path matches neither). Package matches\n * take precedence.\n */\nexport function classifyDistChange(\n changedPath: string,\n appDistDirs: string[],\n packageDistDirs: string[],\n): ChangeClassification {\n const normalized = path.normalize(changedPath)\n\n const matchedPackageDir = packageDistDirs.find((dir) => {\n return normalized.startsWith(path.normalize(dir))\n })\n\n if (matchedPackageDir) {\n return { kind: 'package', packageDir: matchedPackageDir }\n }\n\n const matchedDir = appDistDirs.find((dir) => {\n return normalized.startsWith(path.normalize(dir))\n })\n\n return { kind: 'app', app: matchedDir }\n}\n", "import process from 'node:process'\nimport { $ } from 'zx'\n\nimport type { CmuxLayoutNode } from 'src/dev/cmux-layout'\nimport { logger } from 'src/lib/logger'\n\n/** Args for {@link openCmuxDevWorkspace}: the workspace root, title, and pane layout tree. */\ninterface OpenCmuxDevWorkspaceArgs {\n cwd: string\n title: string\n layout: CmuxLayoutNode\n}\n\n/**\n * True iff the `cmux` CLI is invokable (i.e. `cmux --version` resolves). Used to\n * gate `--cmux` mode and fall back to single-process dev when cmux is absent.\n */\nexport const isCmuxAvailable = async (): Promise<boolean> => {\n try {\n await $`cmux --version`.quiet()\n\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Open ONE cmux workspace rooted at `cwd`, laid out per `layout` (one pane per\n * command). Runs in a `CMUX_QUIET=1` scoped env to suppress cmux's one-time compat\n * notice, then parses and returns the `workspace:<id>` ref from stdout.\n */\nexport const openCmuxDevWorkspace = async (args: OpenCmuxDevWorkspaceArgs): Promise<string> => {\n const { cwd, title, layout } = args\n const layoutJson = JSON.stringify(layout)\n\n const $cmux = $({ env: { ...process.env, CMUX_QUIET: '1' } })\n const output = (await $cmux`cmux new-workspace --name ${title} --cwd ${cwd} --focus false --layout ${layoutJson}`)\n .stdout\n\n return parseWorkspaceRef(output)\n}\n\n/**\n * Best-effort close of the cmux workspace `ref`, tearing down the workspace and\n * every pane process. Silently no-ops (debug-logged) if cmux isn't running or the\n * close fails, mirroring {@link file://./close-workspace-by-title.ts}.\n */\nexport const closeCmuxDevWorkspace = async (ref: string): Promise<void> => {\n try {\n await $`cmux close-workspace --workspace ${ref}`.quiet()\n } catch (error) {\n logger.debug({ error, ref }, 'cmux: skipped closing dev workspace')\n }\n}\n\n/**\n * Extract the `workspace:<id>` ref from `cmux new-workspace` output (e.g.\n * `OK workspace:5`). Throws a clear error when no ref is present.\n *\n * @example\n * parseWorkspaceRef('OK workspace:5\\n') // => 'workspace:5'\n */\nconst parseWorkspaceRef = (output: string): string => {\n const match = output.match(/workspace:\\d+/)\n\n if (!match) {\n throw new Error('cmux: could not locate workspace ref in new-workspace output')\n }\n\n return match[0]\n}\n", "/** Matches a `v`-prefixed semver token (e.g. `v1.48.0`) anchored on shape. */\nconst V_SEMVER_TOKEN_RE = /\\bv(\\d+\\.\\d+\\.\\d+)\\b/g\n\n/**\n * Canonicalizes a cmux workspace title into a stable dedup/close key.\n *\n * cmux workspace titles are human display strings built by\n * `buildCmuxWorkspaceTitle`, so the value stored when a workspace is created can\n * drift from the value rebuilt later \u2014 across whitespace and across CLI versions\n * (an older build titled version releases `v1.48.0`; the current build titles\n * them `1.48.0`). Keying dedup or close on the raw title silently creates\n * duplicate / unclosable workspaces whenever that drift occurs.\n *\n * Canonicalization collapses the known drift axes so both sides round-trip to an\n * equal key:\n * - trims and collapses internal whitespace to single spaces;\n * - normalizes a `v`-prefixed semver token to its bare form\n * (`v1.48.0` \u2192 `1.48.0`), anchored on semver shape so named releases that\n * merely start with `v` (e.g. `vega-redesign`) are left untouched.\n *\n * Non-release fallback titles (which may contain `/`, e.g. `feature/foo`) are\n * preserved as-is apart from whitespace normalization.\n *\n * @example\n * canonicalizeCmuxTitle('hulyo-monorepo v1.48.0') // => 'hulyo-monorepo 1.48.0'\n * canonicalizeCmuxTitle('hulyo-monorepo 1.48.0') // => 'hulyo-monorepo 1.48.0'\n * canonicalizeCmuxTitle('hulyo-monorepo vega-redesign') // => 'hulyo-monorepo vega-redesign'\n */\nexport const canonicalizeCmuxTitle = (raw: string): string => {\n return raw.trim().replace(/\\s+/g, ' ').replace(V_SEMVER_TOKEN_RE, '$1')\n}\n", "import { $ } from 'zx'\n\nimport { logger } from 'src/lib/logger'\n\nimport { canonicalizeCmuxTitle } from './canonicalize-cmux-title'\n\n/**\n * Best-effort close of the cmux workspace whose title matches `title` (compared\n * via {@link canonicalizeCmuxTitle}, so a drifted stored title still resolves).\n * Silently no-ops if cmux isn't running, the workspace isn't found, or close fails.\n */\nexport const closeCmuxWorkspaceByTitle = async (title: string): Promise<void> => {\n try {\n const listOutput = (await $`cmux list-workspaces`.quiet()).stdout\n\n const ref = findWorkspaceRefByTitle(listOutput, title)\n\n if (!ref) {\n return\n }\n\n await $`cmux close-workspace --workspace ${ref}`.quiet()\n } catch (error) {\n logger.debug({ error, title }, 'cmux: skipped closing workspace')\n }\n}\n\n/**\n * Parses `cmux list-workspaces` output and returns the workspace ref whose\n * title matches `title`, or undefined if no match. Both sides are compared via\n * {@link canonicalizeCmuxTitle} so a workspace stored under a drifted title\n * (whitespace, or an older CLI's `v`-prefixed semver) is still found \u2014 keeping\n * close symmetric with the cmux open dedup in `worktrees-reload`.\n *\n * Each line looks like:\n * \" workspace:8 hulyo-monorepo 1.48.0\"\n * \"* workspace:6 obsidian-workspace [selected]\"\n */\nconst findWorkspaceRefByTitle = (output: string, title: string): string | undefined => {\n const target = canonicalizeCmuxTitle(title)\n\n for (const rawLine of output.split('\\n')) {\n // eslint-disable-next-line sonarjs/super-linear-regex, regexp/no-super-linear-backtracking\n const match = rawLine.match(/^[* ]\\s*(workspace:\\d+)\\s+(.+?)(?:\\s+\\[selected\\])?\\s*$/)\n\n if (!match) {\n continue\n }\n\n const ref = match[1]\n const lineTitle = match[2]?.trim() ?? ''\n\n if (canonicalizeCmuxTitle(lineTitle) === target) {\n return ref\n }\n }\n\n return undefined\n}\n", "import { $ } from 'zx'\n\nimport { logger } from 'src/lib/logger'\n\nimport { canonicalizeCmuxTitle } from './canonicalize-cmux-title'\n\n/**\n * Returns the set of **canonical** titles for all currently-open cmux\n * workspaces (see {@link canonicalizeCmuxTitle}). Keying on the canonical form\n * lets callers match a workspace even when its stored title drifted from the\n * title they rebuild (whitespace, or an older CLI's `v`-prefixed semver).\n * Returns an empty set if cmux isn't running, the call fails, or the output\n * can't be parsed \u2014 callers should treat \"empty\" as \"unknown, proceed as if\n * nothing is open\".\n *\n * Each line of `cmux list-workspaces` looks like:\n * \" workspace:8 hulyo-monorepo 1.48.0\"\n * \"* workspace:6 obsidian-workspace [selected]\"\n */\nexport const listCmuxWorkspaceTitles = async (): Promise<Set<string>> => {\n try {\n const output = (await $`cmux list-workspaces`.quiet()).stdout\n\n const titles = new Set<string>()\n\n for (const rawLine of output.split('\\n')) {\n // eslint-disable-next-line sonarjs/super-linear-regex, regexp/no-super-linear-backtracking\n const match = rawLine.match(/^[* ]\\s*workspace:\\d+\\s+(.+?)(?:\\s+\\[selected\\])?\\s*$/)\n\n if (!match) {\n continue\n }\n\n const title = match[1]?.trim()\n\n if (title) {\n titles.add(canonicalizeCmuxTitle(title))\n }\n }\n\n return titles\n } catch (error) {\n logger.debug({ error }, 'cmux: skipped listing workspace titles')\n\n return new Set()\n }\n}\n", "import { $ } from 'zx'\n\nimport { getInfraKitConfig, resolveCmuxLayout } from 'src/lib/infra-kit-config'\n\ninterface OpenCmuxWorkspaceArgs {\n cwd: string\n title?: string\n}\n\n/**\n * Opens a new cmux workspace rooted at `cwd`, with panes arranged per the\n * configured `worktrees.cmux.layout` (resolved via {@link resolveCmuxLayout},\n * default `two-columns`):\n * two-columns \u2014 left | right, both full-height (two panes)\n * three-pane \u2014 left-top / left-bottom | full-height right (three panes)\n * All panes inherit `cwd` from the workspace.\n */\nexport const openCmuxWorkspaceWithLayout = async (args: OpenCmuxWorkspaceArgs): Promise<void> => {\n const { cwd, title } = args\n\n const layout = resolveCmuxLayout(await getInfraKitConfig())\n\n const newWorkspaceOutput = (await $`cmux workspace create --cwd ${cwd}`).stdout\n\n const workspaceRef = parseWorkspaceRef(newWorkspaceOutput)\n\n const surfacesOutput = (await $`cmux list-pane-surfaces --workspace ${workspaceRef}`).stdout\n\n const leftTopRef = parseFirstSurfaceRef(surfacesOutput)\n\n // Both layouts share the vertical split into left | right columns; only the\n // legacy three-pane layout additionally splits the left column top/bottom.\n await $`cmux new-split right --workspace ${workspaceRef} --surface ${leftTopRef}`\n\n if (layout === 'three-pane') {\n await $`cmux new-split down --workspace ${workspaceRef} --surface ${leftTopRef}`\n }\n\n if (title) {\n await $`cmux workspace rename --workspace ${workspaceRef} --title ${title}`\n }\n}\n\n/**\n * Extracts the first `surface:<id>` reference from the output of\n * `cmux list-pane-surfaces`. Used to locate the initial (primary) pane\n * surface so subsequent splits can be anchored relative to it.\n *\n * @example\n * const output = 'surface:12 (active)\\nsurface:13\\n'\n * parseFirstSurfaceRef(output) // => 'surface:12'\n */\nconst parseFirstSurfaceRef = (output: string): string => {\n const match = output.match(/surface:\\d+/)\n\n if (!match) {\n throw new Error('cmux: could not locate initial surface in list-pane-surfaces output')\n }\n\n return match[0]\n}\n\n/**\n * Extracts the `workspace:<id>` reference from the output of\n * `cmux workspace create`. The returned ref is used to target the newly\n * created workspace in follow-up `cmux` commands (splits, rename, etc.).\n *\n * @example\n * const output = 'created workspace:7\\n'\n * parseWorkspaceRef(output) // => 'workspace:7'\n */\nconst parseWorkspaceRef = (output: string): string => {\n const match = output.match(/workspace:\\d+/)\n\n if (!match) {\n throw new Error('cmux: could not locate workspace ref in workspace create output')\n }\n\n return match[0]\n}\n", "import fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { z } from 'zod'\n\nimport { getProjectRoot } from 'src/lib/git-utils'\n\nconst INFRA_KIT_CONFIG_FILE = 'infra-kit.json'\n\n/**\n * Directory under the user's home that holds all machine-local infra-kit config:\n * the runtime `infra-kit.json` merge layer, per-project overrides, and the factory\n * registry (`vendor.json`). Single source of truth for `.infra-kit`, reused\n * by the vendor factory-config loader (no import cycle \u2014 this module imports no\n * `lib/vendor` code).\n */\nexport const USER_CONFIG_DIR_NAME = '.infra-kit'\nconst USER_GLOBAL_CONFIG_FILE = 'infra-kit.json'\nconst USER_PROJECTS_DIR = 'projects'\n\n// envManagement\nconst dopplerEnvManagementSchema = z.object({\n provider: z.literal('doppler'),\n config: z.object({\n name: z.string().min(1),\n }),\n})\n\nconst envManagementSchema = z.discriminatedUnion('provider', [dopplerEnvManagementSchema])\n\n// ide\n// There is one attach style: each worktree is added to the configured editor's\n// workspace and opened (no per-window mode). Cursor needs a `.code-workspace`\n// path to reconcile its `folders` array against.\nconst cursorIdeConfigSchema = z.object({\n workspaceConfigPath: z.string().min(1),\n})\n\nconst cursorIdeSchema = z.object({\n provider: z.literal('cursor'),\n config: cursorIdeConfigSchema,\n})\n\n// Zed has no portable workspace file (no `.code-workspace`) and no folder-remove\n// CLI: a multi-worktree workspace is realized by a single `zed <root> <wt...>`\n// invocation. So `config` carries no settings \u2014 there's no path to point at.\nconst zedIdeConfigSchema = z.object({})\n\nconst zedIdeSchema = z.object({\n provider: z.literal('zed'),\n config: zedIdeConfigSchema,\n})\n\nconst ideSchema = z.discriminatedUnion('provider', [cursorIdeSchema, zedIdeSchema])\n\n// `ide` accepts a single provider (back-compat) OR an array to drive multiple\n// editors at once (e.g. Cursor + Zed). Normalized to an array everywhere via\n// `resolveConfiguredIdes`. Uniqueness-by-provider is enforced at parse time by a\n// `.superRefine` on the full config schema (see below) \u2014 not here, so the message\n// survives `z.union` error aggregation.\nconst idesSchema = z.union([ideSchema, z.array(ideSchema).min(1)])\n\n// taskManager\nconst jiraTaskManagerSchema = z.object({\n provider: z.literal('jira'),\n config: z.object({\n baseUrl: z.string().url(),\n projectId: z.number().int().positive(),\n }),\n})\n\nconst taskManagerSchema = z.discriminatedUnion('provider', [jiraTaskManagerSchema])\n\n// cmux pane layout for opened worktree workspaces. Named presets keep the config\n// typo-proof (an enum, not a free string) and let the opener switch on a single\n// value; extend the enum + the opener's switch to add a layout.\n// two-columns \u2014 left | right, both full-height (default)\n// three-pane \u2014 left split top/bottom + full-height right (legacy layout)\nconst cmuxLayouts = ['two-columns', 'three-pane'] as const\n\nconst cmuxConfigSchema = z.object({\n layout: z.enum(cmuxLayouts).optional(),\n})\n\n// worktrees prompt defaults\nconst worktreesConfigSchema = z.object({\n openInGithubDesktop: z.boolean().optional(),\n openInCmux: z.boolean().optional(),\n cmux: cmuxConfigSchema.optional(),\n})\n\n// dev-server per-app overrides. Maps an app folder name (e.g. `client`) to its\n// local dev port and/or URL prefix. Both keys optional. An app absent from the\n// map falls back to env (`{APP}_PORT` / `PORT`) then the built-in defaults, so\n// the map is intentionally NOT validated against the discovered apps here \u2014 an\n// unknown app name is simply ignored at resolve time (lib/dev), never a parse\n// error that would brick every command.\nconst devAppConfigSchema = z\n .object({\n port: z.number().int().positive().optional(),\n prefixUrl: z.string().min(1).optional(),\n })\n .strict()\n\nconst devConfigSchema = z.record(z.string().min(1), devAppConfigSchema)\n\n// devPresets: named local-dev sessions, declared in the per-project infra-kit.json\n// (team presets committed; personal ones layered via the user-project override).\n// Each preset names launch targets (apps/<app>/{api,ui}), optional per-backend\n// `watchDeps`, per-route proxy-source overrides, and a `cmux` layout flag; it is\n// consumed by `infra-kit dev <preset>` and resolved by src/dev/presets. The SHAPE\n// is strict (typos in a preset surface as parse errors), but unknown app/route\n// NAMES are intentionally NOT validated here \u2014 they resolve at run time against the\n// discovered apps, so a stale preset never bricks every command.\nconst proxySourceSchema = z.enum(['local', 'cloud'])\n\nconst devPresetAppSchema = z\n .object({\n // api targets only: watch the backend's dist + shared-package subdeps and restart on change.\n watchDeps: z.boolean().optional(),\n // route path (e.g. `/api`) \u2192 source, overriding that route's config.ts default for this session.\n proxy: z.record(z.string().min(1), proxySourceSchema).optional(),\n })\n .strict()\n\nconst devPresetSchema = z\n .object({\n // Launch-target key (`client`, `client/ui`, `client/api`, `*/api`) \u2192 its settings. Omit = all.\n apps: z.record(z.string().min(1), devPresetAppSchema).optional(),\n // Run each launched target in its own cmux pane (one workspace, N panes).\n cmux: z.boolean().optional(),\n })\n .strict()\n\nconst devPresetsSchema = z.record(z.string().min(1), devPresetSchema)\n\n// env auto-load: opt-in convenience that primes Doppler env when you work inside\n// this project / a worktree. Absent => disabled. `trigger` selects the moment\n// (pick one):\n// shell-startup \u2014 when a new shell opens inside the project\n// cli-invocation \u2014 before each `infra-kit` command (primes SUBSEQUENT commands)\n// `config` names which environment to load. It is intentionally NOT validated\n// against `environments` here: an invalid name must DISABLE the feature at\n// resolve time (lib/env-autoload), never throw inside the merged-config parse and\n// brick every command.\nconst envAutoLoadSchema = z\n .object({\n trigger: z.enum(['shell-startup', 'cli-invocation']),\n config: z.string().min(1),\n })\n .strict()\n\n// Base object shape, kept separate so `.partial()` (which only works on a plain\n// ZodObject, not the `.superRefine`-wrapped full schema) can derive the override\n// schema from it.\nconst infraKitConfigObject = z.object({\n environments: z.array(z.string().min(1)).min(1),\n envManagement: envManagementSchema,\n ide: idesSchema.optional(),\n taskManager: taskManagerSchema.optional(),\n worktrees: worktreesConfigSchema.optional(),\n envAutoLoad: envAutoLoadSchema.optional(),\n dev: devConfigSchema.optional(),\n devPresets: devPresetsSchema.optional(),\n})\n\n// Full schema = base object + a parse-time uniqueness check on the `ide` array.\n// This runs inside the *merged* `safeParse` in getInfraKitConfig, so it's the\n// gate for the final config. (The override layers use the `.partial()` form\n// below, which drops this object-level refinement \u2014 acceptable, the merged\n// parse is authoritative.)\nexport const infraKitConfigSchema = infraKitConfigObject.superRefine((cfg, ctx) => {\n if (!Array.isArray(cfg.ide)) return\n\n const seen = new Set<string>()\n\n for (const entry of cfg.ide) {\n if (seen.has(entry.provider)) {\n ctx.addIssue({\n code: 'custom',\n message: 'each IDE provider may appear at most once',\n path: ['ide'],\n })\n\n return\n }\n\n seen.add(entry.provider)\n }\n})\n\nexport const infraKitOverrideConfigSchema = infraKitConfigObject.partial()\n\nexport type InfraKitConfig = z.infer<typeof infraKitConfigSchema>\n\n/** Resolved env auto-load config (`{ trigger, config }`), or `undefined` when off. */\nexport type EnvAutoLoadConfig = z.infer<typeof envAutoLoadSchema>\n\n/** Per-app dev-server overrides (`{ port?, prefixUrl? }`). */\nexport type DevAppConfig = z.infer<typeof devAppConfigSchema>\n\n/** The full `dev` section: a map of app folder name to its {@link DevAppConfig}. */\nexport type DevConfig = z.infer<typeof devConfigSchema>\n\n/** A proxy route's resolved source in a preset override (`'local' | 'cloud'`). */\nexport type ProxySource = z.infer<typeof proxySourceSchema>\n\n/** A single dev preset (`{ apps?, cmux? }`) from the `devPresets` map. */\nexport type DevPreset = z.infer<typeof devPresetSchema>\n\n/** The `devPresets` map: preset name \u2192 {@link DevPreset}. */\nexport type DevPresets = z.infer<typeof devPresetsSchema>\n\n/** A single resolved IDE entry (`{ provider, config }`). */\nexport type ConfiguredIde = z.infer<typeof ideSchema>\n\n/**\n * Normalize the `ide` config (single object, array, or unset) into a flat list.\n * Validation-free: assumes already-parsed input (uniqueness is enforced by the\n * schema). The one source of truth for \"which editors are configured.\"\n *\n * @example\n * resolveConfiguredIdes({ ide: { provider: 'cursor', config: {...} } }) // => [cursor]\n * resolveConfiguredIdes({ ide: [cursor, zed] }) // => [cursor, zed]\n * resolveConfiguredIdes({}) // => []\n */\nexport const resolveConfiguredIdes = (config: InfraKitConfig): ConfiguredIde[] => {\n const ide = config.ide\n\n if (!ide) return []\n\n return Array.isArray(ide) ? ide : [ide]\n}\n\n/** A cmux pane layout preset (see {@link cmuxLayouts}). */\nexport type CmuxLayout = (typeof cmuxLayouts)[number]\n\n/** The layout applied when `worktrees.cmux.layout` is left unset. */\nexport const DEFAULT_CMUX_LAYOUT: CmuxLayout = 'two-columns'\n\n/**\n * Resolve the cmux pane layout for opened worktree workspaces, falling back to\n * {@link DEFAULT_CMUX_LAYOUT} when unconfigured. The one source of truth for\n * \"which layout should the cmux opener build.\"\n *\n * @example\n * resolveCmuxLayout({ worktrees: { cmux: { layout: 'three-pane' } } }) // => 'three-pane'\n * resolveCmuxLayout({}) // => 'two-columns'\n */\nexport const resolveCmuxLayout = (config: InfraKitConfig): CmuxLayout => {\n return config.worktrees?.cmux?.layout ?? DEFAULT_CMUX_LAYOUT\n}\n\nexport interface InfraKitConfigPaths {\n /** Committed project config (required). */\n main: string\n /** User-scope global overrides applied to every project. */\n userGlobal: string\n /** User-scope per-project overrides \u2014 `<userProjectsDir>/<projectName>/infra-kit.json`. */\n userProject: string\n /** Repo basename (`path.basename(projectRoot)`) used to namespace the user-project file. */\n projectName: string\n}\n\ninterface CacheEntry {\n mtimes: Record<keyof Omit<InfraKitConfigPaths, 'projectName'>, number | null>\n value: InfraKitConfig\n}\n\nlet cached: CacheEntry | null = null\n\n/**\n * Resolve every file path that participates in the config merge chain. Always\n * returns paths even for files that don't yet exist, so callers can use them\n * for \"where would my override go?\" prompts.\n *\n * @example\n * const paths = await getInfraKitConfigPaths()\n * // {\n * // main: '/Users/arthur/projects/api/infra-kit.json',\n * // userGlobal: '/Users/arthur/.infra-kit/infra-kit.json',\n * // userProject: '/Users/arthur/.infra-kit/projects/api/infra-kit.json',\n * // projectName: 'api',\n * // }\n */\nexport const getInfraKitConfigPaths = async (): Promise<InfraKitConfigPaths> => {\n const projectRoot = await getProjectRoot()\n // Repo name is the project-root basename (same as getRepoName), derived here to\n // avoid a second `git rev-parse` \u2014 getRepoName would re-resolve the root.\n const projectName = path.basename(projectRoot)\n const userConfigDir = path.join(os.homedir(), USER_CONFIG_DIR_NAME)\n\n return {\n main: path.join(projectRoot, INFRA_KIT_CONFIG_FILE),\n userGlobal: path.join(userConfigDir, USER_GLOBAL_CONFIG_FILE),\n userProject: path.join(userConfigDir, USER_PROJECTS_DIR, projectName, INFRA_KIT_CONFIG_FILE),\n projectName,\n }\n}\n\n/**\n * Read and validate `infra-kit.json`, with optional override layers shallow-merged\n * on top in this order (later wins):\n * 1. project `infra-kit.json` \u2014 committed source of truth\n * 2. `~/.infra-kit/infra-kit.json` \u2014 user-global defaults\n * 3. `~/.infra-kit/projects/<repo-name>/infra-kit.json` \u2014 user-scope per-project overrides\n *\n * Top-level keys (entire capability sections like `ide`, `envManagement`)\n * replace wholesale. Results are cached per file mtimes so the long-running\n * MCP server picks up edits without a restart.\n *\n * @example\n * // infra-kit.json: { \"environments\": [\"dev\"], \"envManagement\": { \"provider\": \"doppler\", \"config\": { \"name\": \"p\" } } }\n * // ~/.infra-kit/infra-kit.json: { \"ide\": { \"provider\": \"cursor\", \"config\": { \"workspaceConfigPath\": \"./ws.code-workspace\" } } }\n * const cfg = await getInfraKitConfig()\n * // => { environments: ['dev'], envManagement: {...}, ide: { provider: 'cursor', config: { workspaceConfigPath: './ws.code-workspace' } } }\n */\nexport const getInfraKitConfig = async (): Promise<InfraKitConfig> => {\n const paths = await getInfraKitConfigPaths()\n\n let mainStat: Awaited<ReturnType<typeof fs.stat>>\n\n try {\n mainStat = await fs.stat(paths.main)\n } catch {\n cached = null\n\n // Bridge the YAML\u2192JSON cutover: if a legacy infra-kit.yml is sitting where\n // the JSON config should be, point the user at the one-shot migration.\n const legacyYmlPath = paths.main.replace(/\\.json$/, '.yml')\n\n if (await statIfExists(legacyYmlPath)) {\n throw new Error(\n `infra-kit.json not found at ${paths.main}. A legacy infra-kit.yml exists \u2014 run \\`infra-kit init\\` to convert it.`,\n )\n }\n\n throw new Error(`infra-kit.json not found at ${paths.main}`)\n }\n\n const [userGlobalStat, userProjectStat] = await Promise.all([\n statIfExists(paths.userGlobal),\n statIfExists(paths.userProject),\n ])\n\n const mtimes = {\n main: Number(mainStat.mtimeMs),\n userGlobal: userGlobalStat ? Number(userGlobalStat.mtimeMs) : null,\n userProject: userProjectStat ? Number(userProjectStat.mtimeMs) : null,\n }\n\n if (cached && shallowEqual(cached.mtimes, mtimes)) {\n return cached.value\n }\n\n const layers: ConfigLayer[] = [\n { label: 'infra-kit.json', path: paths.main, required: true },\n { label: '~/.infra-kit/infra-kit.json', path: paths.userGlobal, required: false },\n {\n label: `~/.infra-kit/projects/${paths.projectName}/infra-kit.json`,\n path: paths.userProject,\n required: false,\n },\n ]\n\n let merged: Record<string, unknown> = {}\n\n for (const layer of layers) {\n const data = await loadLayer(layer)\n\n if (data === null) continue\n\n merged = { ...merged, ...data }\n }\n\n const finalResult = infraKitConfigSchema.safeParse(merged)\n\n if (!finalResult.success) {\n throw new Error(`Invalid merged infra-kit config: ${z.prettifyError(finalResult.error)}`)\n }\n\n cached = { mtimes, value: finalResult.data }\n\n return finalResult.data\n}\n\n/**\n * For tests \u2014 drops the in-memory cache so the next read hits disk.\n *\n * @example\n * resetInfraKitConfigCache()\n * await getInfraKitConfig() // re-reads files even if mtimes look unchanged\n */\nexport const resetInfraKitConfigCache = (): void => {\n cached = null\n}\n\n/**\n * `fs.stat` that returns `null` instead of throwing on ENOENT. Used so the\n * resolver can probe optional files in the merge chain without try/catch noise.\n *\n * @example\n * const stat = await statIfExists('/does/not/exist') // => null\n */\nconst statIfExists = async (filePath: string): Promise<Awaited<ReturnType<typeof fs.stat>> | null> => {\n try {\n return await fs.stat(filePath)\n } catch {\n return null\n }\n}\n\n/**\n * `fs.readFile` that returns `null` instead of throwing on ENOENT.\n *\n * @example\n * const raw = await readIfExists('/missing.json') // => null\n * const raw = await readIfExists('/exists.json') // => '{ \"environments\": [\"dev\"] }\\n'\n */\nconst readIfExists = async (filePath: string): Promise<string | null> => {\n try {\n return await fs.readFile(filePath, 'utf-8')\n } catch {\n return null\n }\n}\n\n/**\n * Reference-equality comparison of every key in two flat records. Used to\n * cheaply detect whether the cached mtime fingerprint still matches.\n *\n * @example\n * shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true\n * shallowEqual({ a: 1 }, { a: 1, b: 2 }) // => false\n * shallowEqual({ a: 1 }, { a: 2 }) // => false\n */\nconst shallowEqual = <T extends Record<string, unknown>>(a: T, b: T): boolean => {\n const keys = Object.keys(a)\n\n if (keys.length !== Object.keys(b).length) return false\n\n return keys.every((k) => {\n return a[k] === b[k]\n })\n}\n\ninterface ConfigLayer {\n label: string\n path: string\n required: boolean\n}\n\n/**\n * Read a single layer of the merge chain: parse the JSON if the file exists\n * and validate it against the override schema. Returns `null` if an optional\n * layer is missing; throws if the layer is required, malformed, or invalid.\n * An empty/whitespace-only file is treated as `{}` (JSON.parse would throw).\n *\n * @example\n * await loadLayer({ label: '~/.infra-kit/infra-kit.json', path: '/missing.json', required: false })\n * // => null\n *\n * @example\n * // /home/me/.infra-kit/infra-kit.json: '{ \"ide\": { \"provider\": \"cursor\", \"config\": { \"workspaceConfigPath\": \"./ws.code-workspace\" } } }'\n * await loadLayer({ label: '~/.infra-kit/infra-kit.json', path: '/home/me/.infra-kit/infra-kit.json', required: false })\n * // => { ide: { provider: 'cursor', config: { workspaceConfigPath: './ws.code-workspace' } } }\n */\nconst loadLayer = async (layer: ConfigLayer): Promise<Record<string, unknown> | null> => {\n const raw = await readIfExists(layer.path)\n\n if (raw === null) {\n if (layer.required) {\n throw new Error(`${layer.label} not found at ${layer.path}`)\n }\n\n return null\n }\n\n let parsedRaw: unknown\n\n try {\n parsedRaw = raw.trim() === '' ? {} : JSON.parse(raw)\n } catch (err) {\n throw new Error(`Invalid JSON in ${layer.label} at ${layer.path}: ${(err as Error).message}`)\n }\n\n const result = infraKitOverrideConfigSchema.safeParse(parsedRaw)\n\n if (!result.success) {\n throw new Error(`Invalid ${layer.label} at ${layer.path}: ${z.prettifyError(result.error)}`)\n }\n\n return result.data as Record<string, unknown>\n}\n", "import path from 'node:path'\nimport { $ } from 'zx'\n\nimport { isReleaseBranch } from 'src/lib/release-id'\n\n/**\n * Get current git worktrees\n *\n * @returns [release/v1.18.22, release/v1.18.23, release/v1.18.24] or [feature/mobile-app, feature/explore-page, feature/login-page]\n */\nexport const getCurrentWorktrees = async (type: 'release' | 'feature'): Promise<string[]> => {\n const worktreesOutput = await $`git worktree list`\n\n const worktreeLines = worktreesOutput.stdout.split('\\n').filter(Boolean)\n\n const worktreePredicateMap = {\n release: releaseWorktreePredicate,\n feature: featureWorktreePredicate,\n }\n\n return worktreeLines.map(worktreePredicateMap[type]).filter((branch) => {\n return branch !== null\n })\n}\n\n/**\n * Extract the branch name from a `git worktree list` output line.\n *\n * `git worktree list` formats each line as:\n * <path> <hash> [<branch>]\n *\n * Reads the branch from the trailing `[branch]` token so it works for the\n * main checkout too (whose path does not encode the branch name).\n */\nconst parseWorktreeBranch = (line: string): string | null => {\n const trimmed = line.trimEnd()\n\n if (!trimmed.endsWith(']')) return null\n\n const open = trimmed.lastIndexOf('[')\n\n if (open === -1) return null\n\n const branch = trimmed.slice(open + 1, -1)\n\n return branch.length > 0 ? branch : null\n}\n\n/**\n * Extract a release branch name from a `git worktree list` output line.\n *\n * Returns `null` for lines that are not release worktrees.\n *\n * @example\n * releaseWorktreePredicate('/path/to/release/v1.18.22 abc1234 [release/v1.18.22]')\n * // => 'release/v1.18.22'\n *\n * @example\n * releaseWorktreePredicate('/path/to/feature/login abc1234 [feature/login]')\n * // => null\n */\nconst releaseWorktreePredicate = (line: string): string | null => {\n const branch = parseWorktreeBranch(line)\n\n return isReleaseBranch(branch) ? branch : null\n}\n\n/**\n * Extract a feature branch name from a `git worktree list` output line.\n *\n * Returns `null` for lines that are not feature worktrees.\n *\n * @example\n * featureWorktreePredicate('/path/to/feature/login-page abc1234 [feature/login-page]')\n * // => 'feature/login-page'\n *\n * @example\n * featureWorktreePredicate('/path/to/release/v1.18.22 abc1234 [release/v1.18.22]')\n * // => null\n */\nconst featureWorktreePredicate = (line: string): string | null => {\n const branch = parseWorktreeBranch(line)\n\n return branch?.startsWith('feature/') ? branch : null\n}\n\n/**\n * Get the current project root directory\n */\nexport const getProjectRoot = async (): Promise<string> => {\n const result = await $`git rev-parse --show-toplevel`\n\n return result.stdout.trim()\n}\n\n/**\n * Get the current git branch name (e.g. `dev`, `main`, `release/v1.2.3`).\n */\nexport const getCurrentBranch = async (): Promise<string> => {\n const result = await $`git rev-parse --abbrev-ref HEAD`\n\n return result.stdout.trim()\n}\n\n/**\n * Whether the working tree has no staged, unstaged, or untracked changes.\n */\nexport const isWorkingTreeClean = async (): Promise<boolean> => {\n const result = await $`git status --porcelain`\n\n return result.stdout.trim().length === 0\n}\n\n/**\n * Whether the current checkout is a linked git worktree rather than the main\n * repository checkout.\n *\n * A linked worktree's git dir lives under `<main>/.git/worktrees/<name>`, so it\n * differs from the shared common dir; in the main checkout the two resolve to\n * the same path. Both are anchored to the toplevel so `--git-common-dir` (which\n * git may report relative to cwd) resolves consistently.\n */\nexport const isInsideLinkedWorktree = async (): Promise<boolean> => {\n const cwd = await getProjectRoot()\n\n const [gitDirResult, commonDirResult] = await Promise.all([\n $({ cwd })`git rev-parse --absolute-git-dir`,\n $({ cwd })`git rev-parse --git-common-dir`,\n ])\n\n const gitDir = gitDirResult.stdout.trim()\n const commonDir = path.resolve(cwd, commonDirResult.stdout.trim())\n\n return gitDir !== commonDir\n}\n\n/**\n * Get the current repository name (basename of the project root)\n */\nexport const getRepoName = async (): Promise<string> => {\n const projectRoot = await getProjectRoot()\n\n return path.basename(projectRoot)\n}\n\n/**\n * Delete a local branch if it exists and is not the current checkout.\n *\n * Idempotent: a no-op when the branch is absent (`git branch --list` prints\n * nothing). Uses force `-D` because a delivered release branch was\n * squash-merged \u2014 its tip is unreachable from the base, so `-d` would refuse\n * with \"not fully merged\". The delete itself still rejects if the branch is\n * checked out in another worktree; callers decide how to handle that.\n */\nexport const deleteLocalBranch = async (branch: string): Promise<void> => {\n const listed = await $`git branch --list ${branch}`\n\n if (listed.stdout.trim().length === 0) return\n\n if ((await getCurrentBranch()) === branch) return\n\n await $`git branch -D ${branch}`\n}\n\n/**\n * Delete a branch on the `origin` remote if it exists.\n *\n * Idempotent: a no-op when the branch is absent on the remote. Existence is\n * probed with `git ls-remote --heads` (empty stdout = absent) rather than\n * `--exit-code`, so a genuine network/auth failure rejects and propagates to\n * the caller instead of being silently misread as \"branch absent\".\n */\nexport const deleteRemoteBranch = async (branch: string): Promise<void> => {\n const refs = await $`git ls-remote --heads origin ${branch}`\n\n if (refs.stdout.trim().length === 0) return\n\n await $`git push origin --delete ${branch}`\n}\n", "/**\n * A release identity is either a semantic version or a free-form kebab-case\n * name. `raw` is the canonical token for the id: the no-`v` semver string for\n * versions (e.g. `1.2.3`) and the name itself for named releases.\n */\nexport type ReleaseId =\n | { kind: 'version'; semver: { major: number; minor: number; patch: number }; raw: string }\n | { kind: 'name'; name: string; raw: string }\n\n/** Matches a bare or `v`-prefixed semver token, e.g. `1.2.3` or `v1.2.3`. */\nconst VERSION_RE = /^v?(\\d+)\\.(\\d+)\\.(\\d+)$/\n\n/** Matches the semver core after the `v` in a `release/v\u2026` branch. */\nconst BRANCH_SEMVER_RE = /^(\\d+)\\.(\\d+)\\.(\\d+)$/\n\n/** Kebab-case: lowercase alphanumeric segments joined by single hyphens. */\nconst KEBAB_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\nconst RELEASE_BRANCH_PREFIX = 'release/'\nconst VERSION_BRANCH_PREFIX = 'release/v'\nconst REFS_HEADS_PREFIX = 'refs/heads/'\n\nconst NEXT_TOKEN = 'next'\nconst MAX_NAME_LENGTH = 50\n\n/**\n * Names that would collide with branch/release semantics or read as a special\n * token. Banned regardless of kebab-case validity.\n */\nconst RESERVED_NAMES: ReadonlySet<string> = new Set(['dev', 'main', 'next', 'hotfix', 'regular', 'release'])\n\n/** Thrown by {@link validateName} when a release name is not acceptable. */\nexport class InvalidReleaseNameError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'InvalidReleaseNameError'\n }\n}\n\n/** Thrown by {@link parseReleaseRef} when a release ref cannot be parsed. */\nexport class InvalidReleaseRefError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'InvalidReleaseRefError'\n }\n}\n\nconst stripRefsHeads = (input: string): string => {\n return input.startsWith(REFS_HEADS_PREFIX) ? input.slice(REFS_HEADS_PREFIX.length) : input\n}\n\nconst makeVersion = (major: number, minor: number, patch: number): ReleaseId => {\n return {\n kind: 'version',\n semver: { major, minor, patch },\n raw: `${major}.${minor}.${patch}`,\n }\n}\n\n/**\n * Validate a release name. Throws {@link InvalidReleaseNameError} with a\n * specific message unless the name is kebab-case, at most 50 characters, and\n * not a reserved word. Semver-looking tokens (e.g. `1.2.3`) are already\n * excluded by the kebab-case rule since they contain dots.\n */\nexport const validateName = (name: string): void => {\n if (name.length === 0) {\n throw new InvalidReleaseNameError('Release name is empty. Provide a kebab-case name like \"checkout-redesign\".')\n }\n\n if (name.length > MAX_NAME_LENGTH) {\n throw new InvalidReleaseNameError(\n `Release name \"${name}\" is ${name.length} characters; the maximum is ${MAX_NAME_LENGTH}.`,\n )\n }\n\n if (!KEBAB_RE.test(name)) {\n throw new InvalidReleaseNameError(\n `Release name \"${name}\" is not kebab-case. Use lowercase letters, digits, and single hyphens, e.g. \"checkout-redesign\".`,\n )\n }\n\n if (RESERVED_NAMES.has(name)) {\n throw new InvalidReleaseNameError(\n `Release name \"${name}\" is reserved. Reserved names: ${[...RESERVED_NAMES].join(', ')}.`,\n )\n }\n}\n\n/**\n * Lenient parse of a git branch name into a {@link ReleaseId}. Tolerates a\n * leading `refs/heads/`. Returns `null` for anything that is not a valid\n * `release/v<semver>` or `release/<name>` branch. Never throws.\n *\n * Precedence is version-first: `release/v<semver>` is a version, but if the\n * segment after `release/v` is not a valid semver (e.g. `release/vnext`), the\n * whole token after `release/` is treated as a candidate name instead.\n */\nexport const parseBranchName = (branch: string): ReleaseId | null => {\n const stripped = stripRefsHeads(branch.trim())\n\n if (!stripped.startsWith(RELEASE_BRANCH_PREFIX)) return null\n\n if (stripped.startsWith(VERSION_BRANCH_PREFIX)) {\n const semverPart = stripped.slice(VERSION_BRANCH_PREFIX.length)\n const match = BRANCH_SEMVER_RE.exec(semverPart)\n\n if (match) {\n return makeVersion(Number(match[1]), Number(match[2]), Number(match[3]))\n }\n }\n\n const namePart = stripped.slice(RELEASE_BRANCH_PREFIX.length)\n\n try {\n validateName(namePart)\n } catch {\n return null\n }\n\n return { kind: 'name', name: namePart, raw: namePart }\n}\n\n/**\n * Strict parse of a release ref into a {@link ReleaseId}. Throws\n * {@link InvalidReleaseRefError} on invalid input. Precedence (order matters):\n * 1. `release/\u2026` (or `refs/heads/release/\u2026`) \u2192 delegate to parseBranchName.\n * 2. semver token (`1.2.3` / `v1.2.3`) \u2192 version.\n * 3. `next` \u2192 throws; callers must resolve `next` to a concrete version\n * via computeNextVersion before calling this.\n * 4. otherwise \u2192 validateName, returning a named release.\n */\nexport const parseReleaseRef = (input: string): ReleaseId => {\n const trimmed = input.trim()\n const branchCandidate = stripRefsHeads(trimmed)\n\n if (branchCandidate.startsWith(RELEASE_BRANCH_PREFIX)) {\n const parsed = parseBranchName(trimmed)\n\n if (!parsed) {\n throw new InvalidReleaseRefError(\n `\"${input}\" looks like a release branch but is not a valid release/v<semver> or release/<name> ref.`,\n )\n }\n\n return parsed\n }\n\n const versionMatch = VERSION_RE.exec(trimmed)\n\n if (versionMatch) {\n return makeVersion(Number(versionMatch[1]), Number(versionMatch[2]), Number(versionMatch[3]))\n }\n\n if (trimmed.toLowerCase() === NEXT_TOKEN) {\n throw new InvalidReleaseRefError(\n 'The \"next\" token must be resolved to a concrete version (via computeNextVersion) before parsing a release ref.',\n )\n }\n\n try {\n validateName(trimmed)\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n\n throw new InvalidReleaseRefError(`Cannot parse \"${input}\" as a release ref: ${reason}`)\n }\n\n return { kind: 'name', name: trimmed, raw: trimmed }\n}\n\n/** Render the branch name for a release id: `release/v1.2.3` | `release/<name>`. */\nexport const formatBranchName = (id: ReleaseId): string => {\n if (id.kind === 'version') return `${VERSION_BRANCH_PREFIX}${id.raw}`\n\n return `${RELEASE_BRANCH_PREFIX}${id.name}`\n}\n\n/**\n * Render a PR title: `Release v1.2.3` / `Hotfix v1.2.3` for versions,\n * `Release <name>` / `Hotfix <name>` for names.\n */\nexport const formatPrTitle = (id: ReleaseId, type: 'regular' | 'hotfix'): string => {\n const prefix = type === 'hotfix' ? 'Hotfix' : 'Release'\n\n if (id.kind === 'version') return `${prefix} v${id.raw}`\n\n return `${prefix} ${id.name}`\n}\n\n/** Render a release-candidate PR title: `Release v1.2.3 (RC)` | `Release <name> (RC)`. */\nexport const formatRcTitle = (id: ReleaseId): string => {\n if (id.kind === 'version') return `Release v${id.raw} (RC)`\n\n return `Release ${id.name} (RC)`\n}\n\n/** Render the Jira fix-version name: `v1.2.3` | `<name>`. */\nexport const formatJiraName = (id: ReleaseId): string => {\n if (id.kind === 'version') return `v${id.raw}`\n\n return id.name\n}\n\n/** Render a short human display label: `1.2.3` | `<name>`. */\nexport const displayLabel = (id: ReleaseId): string => {\n return id.raw\n}\n\n/** True iff `branch` is a valid release branch under either scheme. */\nexport const isReleaseBranch = (branch: string | null | undefined): boolean => {\n if (branch === null || branch === undefined) return false\n\n return parseBranchName(branch) !== null\n}\n\nconst toTime = (value: string | Date | undefined): number | null => {\n if (value === undefined) return null\n\n const time = value instanceof Date ? value.getTime() : new Date(value).getTime()\n\n return Number.isNaN(time) ? null : time\n}\n\n/**\n * Comparator for {@link ReleaseId} values (locked ordering):\n * - All versions sort before all names.\n * - Versions: semver ascending (major, then minor, then patch; numeric).\n * - Names: by date ascending when both dates are provided, otherwise\n * lexicographic by name. The result is stable and deterministic.\n */\nexport const compareReleaseIds = (\n a: ReleaseId,\n b: ReleaseId,\n dates?: { a?: string | Date; b?: string | Date },\n): number => {\n if (a.kind === 'version' && b.kind === 'version') {\n if (a.semver.major !== b.semver.major) return a.semver.major - b.semver.major\n if (a.semver.minor !== b.semver.minor) return a.semver.minor - b.semver.minor\n\n return a.semver.patch - b.semver.patch\n }\n\n if (a.kind === 'version') return -1\n if (b.kind === 'version') return 1\n\n const timeA = toTime(dates?.a)\n const timeB = toTime(dates?.b)\n\n if (timeA !== null && timeB !== null && timeA !== timeB) {\n return timeA - timeB\n }\n\n if (a.name < b.name) return -1\n if (a.name > b.name) return 1\n\n return 0\n}\n", "import { displayLabel, parseBranchName } from 'src/lib/release-id'\n\ninterface BuildCmuxWorkspaceTitleArgs {\n repoName: string\n branch: string\n}\n\n/**\n * Builds the cmux workspace title used by `worktrees-add` and looked up by\n * `worktrees-remove`. Release branches are rendered via their release-id\n * display label so the title reads e.g. `\"hulyo-monorepo 1.48.0\"` for\n * `\"release/v1.48.0\"` and `\"hulyo-monorepo checkout-redesign\"` for\n * `\"release/checkout-redesign\"`. Non-release branches (cmux titles them too)\n * fall back to the raw branch string.\n */\nexport const buildCmuxWorkspaceTitle = (args: BuildCmuxWorkspaceTitleArgs): string => {\n const { repoName, branch } = args\n\n const id = parseBranchName(branch)\n const label = id ? displayLabel(id) : branch\n\n return `${repoName} ${label}`\n}\n", "/**\n * Pure resolution of a named dev preset into a concrete run plan.\n *\n * A preset (declared under `devPresets` in the per-project infra-kit.json) names\n * launch targets (`client`, `client/api`, `client/ui`, or a `*` glob), optional\n * per-target `watchDeps` (api + ui), per-route proxy-source overrides, and a `cmux` layout\n * flag. This module turns that declarative shape into a flat {@link ResolvedPreset}\n * the runner consumes. Side-effect free: the discovered app parts are passed in\n * (never read from disk here), so resolution is fully unit-testable.\n */\nimport type { DevPreset, DevPresets, ProxySource } from 'src/lib/infra-kit-config'\n\n/** The two launchable halves of an `apps/<app>` folder. */\nexport type AppPart = 'api' | 'ui'\n\n/** One concrete thing to launch: a single part of a single app. */\nexport interface ResolvedTarget {\n /** App folder name (e.g. `client`). */\n app: string\n /** Which half of the app to launch. */\n part: AppPart\n /**\n * Participate in dependency-closure watching (uniform for api + ui): react to a change in\n * this target's own workspace-dependency closure. Default `true` (opt-in participation).\n * Resolution is specificity-ordered: an explicit value on a more-specific key (a concrete\n * app, or an explicit `/part`) wins over a `*` glob; at equal specificity an explicit\n * `false` wins over `true` (order-independent opt-out). See {@link resolveWatch}.\n */\n watchDeps: boolean\n}\n\n/** Which apps have an `api` / `ui` part on disk \u2014 the resolver's view of discovery. */\nexport interface DiscoveredParts {\n /** App names that have `apps/<app>/api`. */\n api: string[]\n /** App names that have `apps/<app>/ui`. */\n ui: string[]\n}\n\n/** A fully-resolved preset: the flat run plan derived from its declarative shape. */\nexport interface ResolvedPreset {\n /** Every (app, part) to launch, de-duplicated. */\n targets: ResolvedTarget[]\n /** Run each target in its own cmux pane. */\n cmux: boolean\n /** app \u2192 (route path \u2192 source) overrides, threaded to the proxy resolver. */\n proxy: Record<string, Record<string, ProxySource>>\n /** Apps whose `api` part is launched \u2014 their backends are local (derived). */\n localApps: string[]\n /** Explicitly-named `<app>/<part>` targets that matched no discovered app/part (for a warning). */\n unmatched: string[]\n}\n\n/** The two parts, in stable order, used when a bare `<app>` key means \"both halves\". */\nconst ALL_PARTS: readonly AppPart[] = ['api', 'ui']\n\ninterface ParsedKey {\n /** App name or `*` (all discovered apps). */\n appGlob: string\n /** Parts the key selects (both when no `/part` suffix is given). */\n parts: readonly AppPart[]\n /** True when the key named a specific part (`client/api`) \u2014 drives the unmatched warning. */\n explicit: boolean\n}\n\n/**\n * Parse a preset target key into an app glob + selected parts.\n *\n * @example\n * parseTargetKey('client') // => { appGlob: 'client', parts: ['api','ui'], explicit: false }\n * parseTargetKey('client/api') // => { appGlob: 'client', parts: ['api'], explicit: true }\n * parseTargetKey('*') // => { appGlob: '*', parts: ['api','ui'], explicit: false }\n */\nexport const parseTargetKey = (key: string): ParsedKey => {\n const segments = key.split('/')\n const [appGlob, part] = segments\n\n if (segments.length > 2 || !appGlob) {\n throw new Error(`devPresets: invalid target \"${key}\" (expected \"<app>\" or \"<app>/api|ui\")`)\n }\n\n if (part === undefined) {\n return { appGlob, parts: ALL_PARTS, explicit: false }\n }\n\n if (part === 'api' || part === 'ui') {\n return { appGlob, parts: [part], explicit: true }\n }\n\n throw new Error(`devPresets: invalid target part \"${part}\" in \"${key}\" (expected \"api\" or \"ui\")`)\n}\n\n/** App names that have `part` on disk, per the discovery result. */\nconst appsWithPart = (discovered: DiscoveredParts, part: AppPart): string[] => {\n return part === 'api' ? discovered.api : discovered.ui\n}\n\n/** Every discovered app name (union of api + ui halves), sorted for deterministic output. */\nconst allApps = (discovered: DiscoveredParts): string[] => {\n return [...new Set([...discovered.api, ...discovered.ui])].sort()\n}\n\n/** Stable `app/part` identity key for de-duplicating targets across preset keys. */\nconst targetId = (app: string, part: AppPart): string => {\n return `${app}/${part}`\n}\n\n/** A resolved target plus the provenance the merge needs to order later keys by specificity. */\ninterface ResolvedTargetInternal extends ResolvedTarget {\n /** Specificity rank of the key that set `watchDeps` (see {@link keyRank}) \u2014 a higher rank wins. */\n rank: number\n /** Whether `watchDeps` came from an explicit preset value (vs the participate-default). */\n explicitValue: boolean\n}\n\ninterface ResolveState {\n targets: Map<string, ResolvedTargetInternal>\n proxy: Record<string, Record<string, ProxySource>>\n unmatched: string[]\n}\n\n/**\n * Specificity rank of a preset key, higher = more specific. A concrete app beats a `*` glob\n * (the dominant axis, +2), and within either an explicit `<app>/api` part beats a bare `<app>`\n * (+1). So a bare glob is 0, a glob with an explicit part is 1, a bare concrete app is 2, and a\n * concrete app with an explicit part is 3.\n */\nconst keyRank = (isGlob: boolean, explicit: boolean): number => {\n return (isGlob ? 0 : 2) + (explicit ? 1 : 0)\n}\n\n/**\n * Merge a preset key's `watchDeps` into any prior resolution for the same target. A key that\n * omits `watchDeps` never clobbers a prior explicit value (and, absent any explicit value,\n * seeds the participate-default `true`). A strictly more-specific key ({@link keyRank}) wins in\n * either direction, so a `*` glob cannot override a concrete `client/api` and vice-versa. At\n * equal specificity an explicit `false` wins over `true`, making the result order-independent.\n */\nconst resolveWatch = (\n prior: ResolvedTargetInternal | undefined,\n next: { watchDeps: boolean | undefined; rank: number },\n): { watchDeps: boolean; rank: number; explicitValue: boolean } => {\n // This key configured nothing \u2192 keep the prior resolution, or seed the participate-default.\n if (next.watchDeps === undefined) {\n return prior ?? { watchDeps: true, rank: next.rank, explicitValue: false }\n }\n\n // No prior explicit value (fresh, or only a default) \u2192 this explicit value takes it.\n if (!prior || !prior.explicitValue) {\n return { watchDeps: next.watchDeps, rank: next.rank, explicitValue: true }\n }\n\n // A strictly more-specific key wins over a less-specific one, in either direction.\n if (next.rank > prior.rank) return { watchDeps: next.watchDeps, rank: next.rank, explicitValue: true }\n if (next.rank < prior.rank) return prior\n\n // Equal specificity: false wins (AND), so the outcome is independent of key order. Defensive \u2014\n // with today's key grammar each target id is reachable by one key per rank, so two explicit keys\n // can't tie; this keeps resolution deterministic if the grammar ever grows same-rank forms.\n return { watchDeps: prior.watchDeps && next.watchDeps, rank: prior.rank, explicitValue: true }\n}\n\n/**\n * Fold one (app, part) into the accumulator: record a target when the part exists,\n * merge `watchDeps` with any prior target of the same identity by specificity\n * ({@link resolveWatch}), and flag an explicitly-named-but-missing part as unmatched\n * (glob-expanded misses are silent).\n */\nconst addTarget = (\n state: ResolveState,\n discovered: DiscoveredParts,\n spec: { app: string; part: AppPart; watchDeps: boolean | undefined; isGlob: boolean; explicit: boolean },\n): void => {\n const { app, part, watchDeps, isGlob, explicit } = spec\n\n if (!appsWithPart(discovered, part).includes(app)) {\n // A bare `<app>` (both parts) silently skips the missing half; only an explicit,\n // non-glob `<app>/<part>` that can't be found is a mistake worth surfacing.\n if (explicit && !isGlob) {\n state.unmatched.push(targetId(app, part))\n }\n\n return\n }\n\n const id = targetId(app, part)\n const merged = resolveWatch(state.targets.get(id), { watchDeps, rank: keyRank(isGlob, explicit) })\n\n state.targets.set(id, { app, part, ...merged })\n}\n\n/**\n * Resolve a preset against the discovered app parts. Expands globs (`*`) and bare\n * `<app>` keys (both halves), de-duplicates targets, derives the local-backend set\n * from the launched `api` targets, and collects per-app proxy overrides. Unknown\n * app/route names are tolerated (an explicit missing target lands in `unmatched`,\n * not an exception) so a stale preset degrades gracefully.\n *\n * @example\n * resolvePreset(\n * { apps: { 'client/ui': {}, 'client/api': { watchDeps: false } } },\n * { api: ['client'], ui: ['client'] },\n * )\n * // => {\n * // targets: [{ app: 'client', part: 'ui', watchDeps: true }, // default participate\n * // { app: 'client', part: 'api', watchDeps: false }], // explicit opt-out\n * // cmux: false, proxy: {}, localApps: ['client'], unmatched: [],\n * // }\n */\nexport const resolvePreset = (preset: DevPreset, discovered: DiscoveredParts): ResolvedPreset => {\n const entries = Object.entries(preset.apps ?? { '*': {} })\n const state: ResolveState = { targets: new Map(), proxy: {}, unmatched: [] }\n\n for (const [key, cfg] of entries) {\n const { appGlob, parts, explicit } = parseTargetKey(key)\n const isGlob = appGlob === '*'\n const apps = isGlob ? allApps(discovered) : [appGlob]\n\n for (const app of apps) {\n for (const part of parts) {\n addTarget(state, discovered, { app, part, watchDeps: cfg.watchDeps, isGlob, explicit })\n }\n\n if (cfg.proxy) {\n state.proxy[app] = { ...(state.proxy[app] ?? {}), ...cfg.proxy }\n }\n }\n }\n\n const targets: ResolvedTarget[] = [...state.targets.values()].map(({ app, part, watchDeps }) => {\n return { app, part, watchDeps }\n })\n const localApps = [\n ...new Set(\n targets\n .filter((t) => {\n return t.part === 'api'\n })\n .map((t) => {\n return t.app\n }),\n ),\n ]\n\n return { targets, cmux: preset.cmux ?? false, proxy: state.proxy, localApps, unmatched: state.unmatched }\n}\n\n/** A devPresets proxy-locality violation surfaced by the audit. */\nexport interface PresetProxyIssue {\n /** Preset name the violation is in. */\n preset: string\n /** Frontend app folder the override is under (e.g. `client`). */\n app: string\n /** Route path forced to a source (e.g. `/api`). */\n route: string\n /** Backend pkg the route maps to; undefined when the route is not declared in the frontend config. */\n pkg?: string\n /** `unknown-route` = route absent from the frontend config; `backend-not-launched` = local target not launched. */\n kind: 'unknown-route' | 'backend-not-launched'\n /** Human-readable, actionable explanation. */\n message: string\n}\n\n/**\n * Cross-file lookups the proxy validator needs, kept injectable so the rule stays\n * filesystem-free (and unit-testable). The impure side (audit) builds these from\n * discovery + each frontend's loaded `infra-kit.config.ts`.\n */\nexport interface PresetProxyContext {\n /** Discovered app parts, for resolving each preset's launch set. */\n discovered: DiscoveredParts\n /** App folder name \u2192 its `apps/<app>/api` package name. */\n apiPkgByApp: Record<string, string>\n /** (frontend app folder, route path) \u2192 backend pkg from that frontend's config; undefined when the route is absent. */\n routePkg: (app: string, route: string) => string | undefined\n}\n\ninterface CheckLocalRouteArgs {\n preset: string\n app: string\n route: string\n launchedPkgs: ReadonlySet<string>\n ctx: PresetProxyContext\n}\n\n/** App folder whose `api` package is `pkg` (for the remediation hint), or undefined. */\nconst ownerApp = (apiPkgByApp: Record<string, string>, pkg: string): string | undefined => {\n return Object.keys(apiPkgByApp).find((app) => {\n return apiPkgByApp[app] === pkg\n })\n}\n\n/**\n * Validate one `route \u2192 'local'` override: the route must be declared in the\n * frontend config, and the backend pkg it maps to must be in the preset's launched\n * set. Returns the issue, or null when the override is satisfiable.\n */\nconst checkLocalRoute = ({ preset, app, route, launchedPkgs, ctx }: CheckLocalRouteArgs): PresetProxyIssue | null => {\n const pkg = ctx.routePkg(app, route)\n\n if (pkg === undefined) {\n return {\n preset,\n app,\n route,\n kind: 'unknown-route',\n message: `preset \"${preset}\": proxy override \"${route}\" on \"${app}\" names a route not declared in ${app}'s infra-kit.config.ts dev.proxy.routes`,\n }\n }\n\n if (launchedPkgs.has(pkg)) {\n return null\n }\n\n const owner = ownerApp(ctx.apiPkgByApp, pkg)\n const hint = owner ? `add \"${owner}/api\" to the preset` : `launch the api whose package is \"${pkg}\"`\n\n return {\n preset,\n app,\n route,\n pkg,\n kind: 'backend-not-launched',\n message: `preset \"${preset}\": proxy override \"${route}\" \u2192 \"local\" requires backend \"${pkg}\" to run locally, but the preset does not launch it \u2014 ${hint}, or set \"${route}\" to \"cloud\"`,\n }\n}\n\n/**\n * Audit every preset's per-route proxy overrides: a route pinned to `local` is only\n * valid when the backend package it resolves to (per the frontend's\n * `infra-kit.config.ts`) is actually launched by that same preset. Catches the\n * `{ \"client/ui\": { \"proxy\": { \"/api\": \"local\" } } }` case where no local backend\n * is running to serve `/api`. `cloud` overrides are always satisfiable and skipped.\n *\n * @example\n * validatePresetProxy(\n * { 'client-remote': { apps: { 'client/ui': { proxy: { '/api': 'local' } } } } },\n * { discovered: { api: ['client'], ui: ['client'] }, apiPkgByApp: { client: 'backend-api' },\n * routePkg: () => 'backend-api' },\n * )\n * // => [{ preset: 'client-remote', app: 'client', route: '/api', pkg: 'backend-api',\n * // kind: 'backend-not-launched', message: '\u2026' }]\n */\nexport const validatePresetProxy = (presets: DevPresets, ctx: PresetProxyContext): PresetProxyIssue[] => {\n const issues: PresetProxyIssue[] = []\n\n for (const [preset, def] of Object.entries(presets)) {\n const resolved = resolvePreset(def, ctx.discovered)\n const launchedPkgs = new Set(\n resolved.localApps\n .map((app) => {\n return ctx.apiPkgByApp[app]\n })\n .filter((pkg): pkg is string => {\n return pkg !== undefined\n }),\n )\n\n for (const [app, routes] of Object.entries(resolved.proxy)) {\n for (const [route, source] of Object.entries(routes)) {\n if (source !== 'local') {\n continue\n }\n\n const issue = checkLocalRoute({ preset, app, route, launchedPkgs, ctx })\n\n if (issue) {\n issues.push(issue)\n }\n }\n }\n }\n\n return issues\n}\n"],
5
- "mappings": "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",
6
- "names": ["process", "pino", "pretty", "LOG_FILE_PATH", "initLoggerMcp", "logLevel", "logger", "initLoggerCLI", "ignoreFields", "fs", "path", "findMonorepoRoot", "startDir", "currentDir", "i", "workspaceFile", "getPackageName", "apiPath", "appName", "pkgPath", "pkg", "discoverApiApps", "root", "appsDir", "apps", "appDirs", "dirent", "serverlessPath", "hasDevScript", "dir", "discoverUiApps", "uiPath", "normalizeAppInclude", "include", "filtered", "resolveSelfAppName", "relative", "firstSegment", "getPackageDistDirs", "packagesDir", "names", "d", "dirs", "name", "distDir", "getAppDistDirs", "app", "classifyDistChange", "changedPath", "appDistDirs", "packageDistDirs", "normalized", "matchedPackageDir", "process", "$", "isCmuxAvailable", "$", "openCmuxDevWorkspace", "args", "cwd", "title", "layout", "layoutJson", "output", "process", "parseWorkspaceRef", "closeCmuxDevWorkspace", "ref", "error", "logger", "match", "V_SEMVER_TOKEN_RE", "canonicalizeCmuxTitle", "raw", "$", "closeCmuxWorkspaceByTitle", "title", "listOutput", "$", "ref", "findWorkspaceRefByTitle", "error", "logger", "output", "target", "canonicalizeCmuxTitle", "rawLine", "match", "lineTitle", "$", "listCmuxWorkspaceTitles", "output", "$", "titles", "rawLine", "match", "title", "canonicalizeCmuxTitle", "error", "logger", "$", "fs", "os", "path", "z", "path", "$", "VERSION_RE", "BRANCH_SEMVER_RE", "KEBAB_RE", "RELEASE_BRANCH_PREFIX", "VERSION_BRANCH_PREFIX", "REFS_HEADS_PREFIX", "NEXT_TOKEN", "RESERVED_NAMES", "InvalidReleaseNameError", "message", "InvalidReleaseRefError", "stripRefsHeads", "input", "REFS_HEADS_PREFIX", "makeVersion", "major", "minor", "patch", "validateName", "name", "KEBAB_RE", "parseBranchName", "branch", "stripped", "RELEASE_BRANCH_PREFIX", "VERSION_BRANCH_PREFIX", "semverPart", "match", "BRANCH_SEMVER_RE", "namePart", "parseReleaseRef", "trimmed", "parsed", "versionMatch", "VERSION_RE", "NEXT_TOKEN", "err", "reason", "formatBranchName", "id", "formatPrTitle", "type", "prefix", "formatRcTitle", "formatJiraName", "displayLabel", "isReleaseBranch", "toTime", "value", "time", "compareReleaseIds", "a", "b", "dates", "timeA", "timeB", "getCurrentWorktrees", "type", "worktreeLines", "$", "worktreePredicateMap", "releaseWorktreePredicate", "featureWorktreePredicate", "branch", "parseWorktreeBranch", "line", "trimmed", "open", "isReleaseBranch", "getProjectRoot", "getCurrentBranch", "isWorkingTreeClean", "isInsideLinkedWorktree", "cwd", "gitDirResult", "commonDirResult", "gitDir", "commonDir", "path", "getRepoName", "projectRoot", "deleteLocalBranch", "deleteRemoteBranch", "INFRA_KIT_CONFIG_FILE", "USER_CONFIG_DIR_NAME", "USER_GLOBAL_CONFIG_FILE", "USER_PROJECTS_DIR", "dopplerEnvManagementSchema", "z", "envManagementSchema", "cursorIdeConfigSchema", "cursorIdeSchema", "zedIdeConfigSchema", "zedIdeSchema", "ideSchema", "idesSchema", "jiraTaskManagerSchema", "taskManagerSchema", "cmuxLayouts", "cmuxConfigSchema", "worktreesConfigSchema", "devAppConfigSchema", "devConfigSchema", "proxySourceSchema", "devPresetAppSchema", "devPresetSchema", "devPresetsSchema", "envAutoLoadSchema", "infraKitConfigObject", "infraKitConfigSchema", "cfg", "ctx", "seen", "entry", "infraKitOverrideConfigSchema", "resolveConfiguredIdes", "config", "ide", "DEFAULT_CMUX_LAYOUT", "resolveCmuxLayout", "cached", "getInfraKitConfigPaths", "projectRoot", "getProjectRoot", "projectName", "path", "userConfigDir", "os", "getInfraKitConfig", "paths", "mainStat", "fs", "legacyYmlPath", "statIfExists", "userGlobalStat", "userProjectStat", "mtimes", "shallowEqual", "layers", "merged", "layer", "data", "loadLayer", "finalResult", "resetInfraKitConfigCache", "filePath", "readIfExists", "a", "b", "keys", "k", "raw", "parsedRaw", "err", "result", "openCmuxWorkspaceWithLayout", "args", "cwd", "title", "layout", "resolveCmuxLayout", "getInfraKitConfig", "newWorkspaceOutput", "$", "workspaceRef", "parseWorkspaceRef", "surfacesOutput", "leftTopRef", "parseFirstSurfaceRef", "output", "match", "buildCmuxWorkspaceTitle", "args", "repoName", "branch", "id", "parseBranchName", "label", "displayLabel", "ALL_PARTS", "parseTargetKey", "key", "segments", "appGlob", "part", "appsWithPart", "discovered", "allApps", "targetId", "app", "keyRank", "isGlob", "explicit", "resolveWatch", "prior", "next", "addTarget", "state", "spec", "watchDeps", "id", "merged", "resolvePreset", "preset", "entries", "cfg", "parts", "apps", "targets", "localApps", "t", "ownerApp", "apiPkgByApp", "pkg", "checkLocalRoute", "route", "launchedPkgs", "ctx", "owner", "hint", "validatePresetProxy", "presets", "issues", "def", "resolved", "routes", "source", "issue"]
7
- }
@@ -1,2 +0,0 @@
1
- import{a as x}from"./chunk-2PZRQHWF.js";import{Buffer as S}from"node:buffer";import{execFileSync as R}from"node:child_process";import u from"node:fs";import w from"node:net";import l from"node:path";import m from"node:process";import{pathToFileURL as b}from"node:url";import{z as f}from"zod";import{z as o}from"zod";var v=o.strictObject({requiredScripts:o.array(o.string().min(1)).optional(),requiredFiles:o.array(o.string().min(1)).optional(),turbo:o.strictObject({requiredTasks:o.array(o.string().min(1)).optional()}).optional(),dev:o.strictObject({proxy:o.strictObject({templates:o.strictObject({local:o.string().min(1),cloud:o.string().min(1)}),routes:o.record(o.string().min(1),o.strictObject({packageName:o.string().min(1),from:o.array(o.enum(["local","cloud"])).min(1),default:o.enum(["local","cloud"]).optional()}).refine(e=>e.from.length<=1||e.default!==void 0,{message:"default is required when `from` has more than one source"}).refine(e=>e.default===void 0||e.from.includes(e.default),{message:"default must be listed in `from`"}))}).optional()}).optional()});var h="INFRA_KIT_ENV",k="infra-kit.config.ts",A=l.join(".infra-kit","dev-context"),K=l.join(".infra-kit","dev-context.json"),D=f.object({package:f.string(),port:f.number(),pid:f.number().optional(),writtenAt:f.number().optional(),release:f.string().optional()}),E=(e,t)=>{let r=t?.username??e.E2E__BASIC_AUTH_USERNAME,n=t?.password??e.E2E__BASIC_AUTH_PASSWORD;return!r||!n?void 0:`Basic ${S.from(`${r}:${n}`).toString("base64")}`};var I=(e,t)=>e.replaceAll("<release>",t.release).replaceAll("<packageName>",t.packageName).replaceAll("<env>",t.env),N=(e,t)=>{let r=e.default??e.from[0];return e.from.includes("local")&&t.has(e.packageName)?"local":r},O=({routePath:e,route:t,templates:r,localSet:n,env:a,getRelease:i,localInfo:c})=>{if(N(t,n)==="local"){let d=c?.get(t.packageName)?.release??i();return{target:I(r.local,{release:d,packageName:t.packageName,env:a??""}),changeOrigin:!0}}if(!a)throw new Error(`infra-kit/vite: proxy route "${e}" resolves to a cloud backend but ${h} is not set. Source an environment from Doppler first (e.g. \`infra-kit env-load dev\`).`);return{target:I(r.cloud,{release:"",packageName:t.packageName,env:a}),changeOrigin:!0,secure:!1,cookieDomainRewrite:"localhost"}},_=({proxy:e,localSet:t,env:r,getRelease:n,authHeader:a,localInfo:i})=>{let c={},s=a?{Authorization:a}:void 0;for(let[g,d]of Object.entries(e.routes)){let p=O({routePath:g,route:d,templates:e.templates,localSet:t,env:r,getRelease:n,localInfo:i});c[g]=s?{...p,headers:s}:p}return c},j=e=>{let t;return()=>(t??={value:e()},t.value)},C=async e=>{let t=l.join(e,k);if(!u.existsSync(t))return;let r=u.statSync(t),i=(await import(`${b(t).href}?mtime=${Number(r.mtimeMs)}`)).default;if(i===void 0)return;let c=typeof i=="function"?await i():i,s=v.safeParse(c);if(!s.success)throw new Error(`infra-kit/vite: invalid ${k} at ${t}: ${f.prettifyError(s.error)}`);return s.data.dev},L=e=>{if(Array.isArray(e))return e.filter(t=>typeof t=="string");if(e!==null&&typeof e=="object"){let t=e.packages??e.localPackages;if(Array.isArray(t))return t.filter(r=>typeof r=="string")}return[]},P=(e,t)=>{let r=l.resolve(e);for(;;){let n=l.join(r,t);if(u.existsSync(n))return n;let a=l.dirname(r);if(a===r)return;r=a}},y=()=>({packages:new Set,info:new Map}),T=e=>{let t;try{t=u.readdirSync(e)}catch{return y()}let r=new Set,n=new Map;for(let a of t)if(a.endsWith(".json"))try{let i=D.safeParse(JSON.parse(u.readFileSync(l.join(e,a),"utf-8")));if(!i.success)continue;r.add(i.data.package),n.set(i.data.package,{port:i.data.port,release:i.data.release})}catch{continue}return{packages:r,info:n}},F=e=>{let t=P(e,A);if(t)return T(t);let r=P(e,K);if(!r)return y();try{return{packages:new Set(L(JSON.parse(u.readFileSync(r,"utf-8")))),info:new Map}}catch{return y()}};var V=e=>R("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:e,encoding:"utf-8"}).trim(),$=()=>new Promise((e,t)=>{let r=w.createServer();r.unref(),r.on("error",t),r.listen(0,"127.0.0.1",()=>{let n=r.address(),a=typeof n=="object"&&n!==null?n.port:0;r.close(()=>e(a))})}),B=async(e={})=>{let t=e.cwd??m.cwd();if(e.command==="build")return{proxy:{}};let r=e.port??await $(),n=await C(t);if(!n?.proxy)return{port:r,proxy:{}};let{packages:a,info:i}=F(t),c=m.env[h],s=E(m.env,e.basicAuth),g=j(()=>x(V(t)));return{port:r,proxy:_({proxy:n.proxy,localSet:a,env:c,getRelease:g,authHeader:s,localInfo:i})}},ee=async(e={})=>(await B(e)).proxy;export{v as a,_ as b,C as c,B as d,ee as e};
2
- //# sourceMappingURL=chunk-IX2A34UU.js.map