infra-kit 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/{boot-ST2534PA.js → boot-26CWOALE.js} +2 -2
  2. package/dist/{boot-ST2534PA.js.map → boot-26CWOALE.js.map} +1 -1
  3. package/dist/{chunk-QS7RKE5P.js → chunk-HIGOL4H4.js} +2 -2
  4. package/dist/chunk-HMXEV7ZN.js +16 -0
  5. package/dist/chunk-HMXEV7ZN.js.map +7 -0
  6. package/dist/chunk-K5KNXRTF.js +6 -0
  7. package/dist/chunk-K5KNXRTF.js.map +7 -0
  8. package/dist/chunk-K5YNW3DE.js +4 -0
  9. package/dist/chunk-K5YNW3DE.js.map +7 -0
  10. package/dist/{chunk-GCOYOWGH.js → chunk-RVMKL6T4.js} +1 -1
  11. package/dist/{chunk-GCOYOWGH.js.map → chunk-RVMKL6T4.js.map} +1 -1
  12. package/dist/chunk-S23ECFH4.js +5 -0
  13. package/dist/chunk-S23ECFH4.js.map +7 -0
  14. package/dist/{chunk-PP44RGIU.js → chunk-VZAXKGEY.js} +2 -2
  15. package/dist/{chunk-T5WMUJID.js → chunk-YSR7UND3.js} +2 -2
  16. package/dist/{chunk-T5WMUJID.js.map → chunk-YSR7UND3.js.map} +2 -2
  17. package/dist/{chunk-TNT65LOE.js → chunk-ZY6BBGHH.js} +3 -3
  18. package/dist/cli.js +169 -143
  19. package/dist/cli.js.map +4 -4
  20. package/dist/dev-server.js +19 -19
  21. package/dist/dev-server.js.map +4 -4
  22. package/dist/dev-wizard-run-PF3FTXA7.js +2 -0
  23. package/dist/{dev-wizard-run-AAQGTUAH.js.map → dev-wizard-run-PF3FTXA7.js.map} +2 -2
  24. package/dist/mcp-proxy.js +1 -1
  25. package/dist/{persistent-ink-dev-ui-D6ALK2P3.js → persistent-ink-dev-ui-HHTHZ24S.js} +2 -2
  26. package/dist/secrets-download-DXRM5M6F.js +2 -0
  27. package/dist/update-check.js +1 -1
  28. package/package.json +14 -15
  29. package/readme.md +1 -1
  30. package/dist/chunk-KTWOAA5C.js +0 -12
  31. package/dist/chunk-KTWOAA5C.js.map +0 -7
  32. package/dist/chunk-LSZFL3CV.js +0 -7
  33. package/dist/chunk-LSZFL3CV.js.map +0 -7
  34. package/dist/chunk-SIEUC4SP.js +0 -15
  35. package/dist/chunk-SIEUC4SP.js.map +0 -7
  36. package/dist/dev-wizard-run-AAQGTUAH.js +0 -2
  37. package/dist/env-load-KX34NCPI.js +0 -2
  38. /package/dist/{chunk-QS7RKE5P.js.map → chunk-HIGOL4H4.js.map} +0 -0
  39. /package/dist/{chunk-PP44RGIU.js.map → chunk-VZAXKGEY.js.map} +0 -0
  40. /package/dist/{chunk-TNT65LOE.js.map → chunk-ZY6BBGHH.js.map} +0 -0
  41. /package/dist/{persistent-ink-dev-ui-D6ALK2P3.js.map → persistent-ink-dev-ui-HHTHZ24S.js.map} +0 -0
  42. /package/dist/{env-load-KX34NCPI.js.map → secrets-download-DXRM5M6F.js.map} +0 -0
@@ -0,0 +1,2 @@
1
+ import{a as m,e as h}from"./chunk-K5YNW3DE.js";import"./chunk-QWVLAZ6N.js";import{a as v,b as k,d as w,m as x}from"./chunk-55F372Z7.js";import{d as b}from"./chunk-S23ECFH4.js";import{P as u}from"./chunk-HMXEV7ZN.js";import"./chunk-MFAFQVTW.js";import"./chunk-YSR7UND3.js";import{f as P}from"./chunk-RVMKL6T4.js";import N from"@inquirer/checkbox";import E from"@inquirer/confirm";import O from"@inquirer/select";import{loadDev as B,loadPackageConfig as j}from"@slip-stream-kit/config/internal";import z from"node:path";import g from"node:process";var C=e=>e.split("/")[0],R=e=>new Map(e.apps.map(r=>[r.name,r])),S=e=>e.localCapable&&e.cloudCapable,A=(e,r,a)=>S(r)?a[`${e} ${r.path}`]??(r.cloudCapable?"cloud":"local"):r.localCapable?"local":"cloud",D=(e,r,a)=>{let o=new Set,t=new Map;for(let n of e){let s=r.get(C(n));for(let i of s?.backends??[]){t.set(i.packageName,i.ownerApp);for(let p of i.routes)A(n,p,a)==="local"&&o.add(i.packageName)}}return{localPkgs:o,ownerByPkg:t}},$=(e,r)=>{let a={},o=!1;for(let t of e?.backends??[]){let n=r.has(t.packageName);for(let s of t.routes){let i=s.localCapable&&n?"local":"cloud";a[s.path]=i,i==="cloud"&&(o=!0)}}return{overrides:a,anyCloud:o}},W=(e,r)=>{let a=R(r),o=e.targets.filter(l=>l.endsWith("/ui")),t=e.targets.filter(l=>l.endsWith("/api")),{localPkgs:n,ownerByPkg:s}=D(o,a,e.sources),i={},p=!1;for(let l of o){let{overrides:c,anyCloud:f}=$(a.get(C(l)),n);f&&(p=!0),i[l]=Object.keys(c).length>0?{proxy:c}:{}}for(let l of t)i[l]??={};for(let l of n){let c=s.get(l);c!=null&&(i[`${c}/api`]??={})}return{presetDef:{apps:i},anyCloudRoute:p,targetKeys:Object.keys(i).sort()}},M=(e,r)=>{let a=[`--target=${e.targetKeys.join(",")}`];return r.watch||a.push("--no-watch"),r.orca&&a.push("--orca"),{flags:a.join(" ")}};var y={output:g.stderr,clearPromptOnDone:!0},U={select:e=>h(r=>O({message:e.message,choices:e.choices,default:e.default},r),{...y,whenHeadless:"refuse"}),checkbox:e=>h(r=>N({message:e.message,choices:e.choices},r),{...y,whenHeadless:"refuse"}),confirm:e=>h(r=>E({message:e.message,default:e.default},r),{...y,whenHeadless:"refuse"})},V=(e,r)=>{let a=new Map;for(let[o,t]of Object.entries(e)){let n={path:o,localCapable:t.from.includes("local"),cloudCapable:t.from.includes("cloud"),default:t.default},s=a.get(t.packageName);s?(s.routes.push(n),s.localCapable=s.localCapable||n.localCapable,s.cloudCapable=s.cloudCapable||n.cloudCapable):a.set(t.packageName,{packageName:t.packageName,routes:[n],localCapable:n.localCapable,cloudCapable:n.cloudCapable,ownerApp:r.get(t.packageName)})}return[...a.values()]},K=async(e,r,a)=>{try{let o=(await B(e))?.proxy?.routes;return o?V(o,a):[]}catch(o){return u.warn(`\u26A0\uFE0F Skipping ${r}: its dev config failed to load \u2014 ${o instanceof Error?o.message:String(o)}`),[]}},_=async e=>{let r=k(e),a=w(e),o=new Map(r.map(c=>[c.packageName,c.name])),t=new Map(r.map(c=>[c.name,c.packageName])),n=new Set(a.map(c=>c.name)),s=new Set(r.map(c=>c.name)),i=[...new Set([...s,...n])].sort(),p=await Promise.all(i.map(async c=>{let f=n.has(c);return{name:c,hasApi:s.has(c),hasUi:f,apiPackage:t.get(c),backends:f?await K(z.join(e,"apps",c,"ui"),c,o):[],deployedUrlEnv:f?(await j(z.join(e,"apps",c,"ui")).catch(()=>{}))?.deployedUrlEnv:void 0}})),l=await b();return{apps:p,environments:Object.keys(l?.envs??{}).sort()}},q=e=>{let r=[];for(let a of e.apps){if(a.hasUi){let o=a.backends.flatMap(n=>n.routes.map(s=>s.path)),t=o.length>0?`frontend \u2014 proxies ${[...o].sort().join(", ")}`:"frontend";r.push({name:`${a.name}/ui`,value:`${a.name}/ui`,description:t})}a.hasApi&&!a.hasUi&&r.push({name:`${a.name}/api`,value:`${a.name}/api`,description:"backend"})}return r},I=e=>{let r={api:e.apps.filter(t=>t.hasApi).map(t=>t.name),ui:e.apps.filter(t=>t.hasUi).map(t=>t.name)},a={},o=new Map;for(let t of e.apps){t.apiPackage!=null&&(a[t.name]=t.apiPackage);for(let n of t.backends)for(let s of n.routes)o.set(`${t.name} ${s.path}`,n.packageName)}return{discovered:r,apiPkgByApp:a,routePkg:(t,n)=>o.get(`${t} ${n}`)}},L=(e,r)=>x(e,I(r)).map(a=>a.message),T="INFRA_KIT_DEV_WIZARD_ORCA",F=()=>g.env[T]==="1",H=async(e,r,a)=>{let o=new Map(r.apps.map(n=>[n.name,n])),t={};for(let n of a){let s=o.get(n.split("/")[0]);for(let i of s?.backends??[])for(let p of i.routes){if(!p.localCapable||!p.cloudCapable)continue;let l=await e.select({message:`\u{1F500} ${n} ${p.path} \u2192 local or cloud?`,choices:[{name:"local (this machine)",value:"local"},{name:"cloud",value:"cloud"}],default:p.default??"local"});t[`${n} ${p.path}`]=l}}return t},Z=(e,r,a)=>e.apps.find(o=>o.name===r)?.backends.flatMap(o=>o.routes).find(o=>o.path===a),G=(e,r)=>{let a=[];for(let[o,t]of Object.entries(e.presetDef.apps??{})){if(!o.endsWith("/ui")||!t.proxy)continue;let n=o.split("/")[0];for(let[s,i]of Object.entries(t.proxy)){let p=Z(r,n,s),l=p&&p.localCapable&&p.cloudCapable?"":" (only option)";a.push(` ${o} ${s} \u2192 ${i}${l}`)}}if(a.length!==0){u.info("Resolved routes:");for(let o of[...a].sort())u.info(o)}},J=async(e,r)=>{let{downloadEnvSecrets:a}=await import("./secrets-download-DXRM5M6F.js"),o=await a(e);return new Map(r.flatMap(t=>{let n=o.get(t);return n===void 0?[]:[[t,n]]}))},Q=async(e,r,a,o)=>{let t=r.flatMap(s=>{let i=a.apps.find(p=>`${p.name}/ui`===s);return i?.deployedUrlEnv?[i.deployedUrlEnv]:[]}),n=t.length>0?await o(e,t):new Map;g.env[P]=e;for(let s of t){let i=n.get(s);i?g.env[s]=i:(delete g.env[s],u.warn(`\u2601\uFE0F "${e}" holds no ${s}; routes that go to the deployed app will fail until it does.`))}},X=async(e,r,a)=>{if(r.apps.length===0)return u.warn("No apps discovered to run."),null;let o=await e.checkbox({message:"\u{1F4E6} Which packages?",choices:q(r)});if(o.length===0)return u.warn("No packages selected."),null;let t=o.filter(d=>d.endsWith("/ui")),n=await H(e,r,t),s=await e.confirm({message:"\u{1F440} Rebuild & restart on save (watch)?",default:!0}),i=F()?await e.confirm({message:"\u{1F9E9} Run each app in its own Orca pane?",default:!1}):!1,p={targets:o,sources:n,watch:s,orca:i},l=W(p,r);if(l.anyCloudRoute){if(r.environments.length===0)return u.warn("\u2601\uFE0F This selection routes to a cloud environment, but no environment token is loaded."),u.warn(" Run `infra-kit env-load` to add one, then re-run the wizard."),null;p.env=await e.select({message:"\u2601\uFE0F Point cloud routes at which environment?",choices:r.environments.map(d=>({name:d,value:d}))}),await Q(p.env,t,r,a)}let c=L(l.presetDef,r);if(c.length>0){u.warn("\u26A0\uFE0F Proxy audit found issues with this selection:");for(let d of c)u.warn(` \u2022 ${d}`);return null}G(l,r),Y(l,p);let f=l.targetKeys.filter(d=>d.endsWith("/api")).map(d=>d.split("/")[0]);return i&&f.length>0?{include:f,presetDef:l.presetDef,watch:s,orca:!0}:(i&&u.info("\u2139\uFE0F Orca needs at least one local backend (panes are backend-only) \u2014 running in-process instead."),{presetDef:l.presetDef,watch:s,orca:!1})},Y=(e,r)=>{m.setInteractive();let a=M(e,r);m.addOption(a.flags,!0),m.print()},ee=async(e,r,a=J)=>X(e,r,a),he=async(e=U,r=v(g.cwd()))=>ee(e,await _(r));export{T as WIZARD_ORCA_VAR,L as auditManualPlan,U as defaultPrompts,_ as gatherWizardModel,K as loadBackends,he as runDevWizard,ee as runWizardFlow};
2
+ //# sourceMappingURL=dev-wizard-run-PF3FTXA7.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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 (the package + per-route 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` / `--agent` invocation bypasses it entirely.\n */\nimport inquirerCheckbox from '@inquirer/checkbox'\nimport inquirerConfirm from '@inquirer/confirm'\nimport inquirerSelect from '@inquirer/select'\nimport { loadDev, loadPackageConfig } from '@slip-stream-kit/config/internal'\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 { readTokenStore } from 'src/lib/env-tokens'\nimport { logger } from 'src/lib/logger'\nimport { withEscape } from 'src/lib/prompts/escapable-context'\n\nimport { deriveManualPlan, equivalentCommand } from './dev-wizard.js'\nimport type { DerivedPlan, ManualSelection, ProxyBackend, RouteCap, WizardApp, WizardModel } from './dev-wizard.js'\nimport { validatePresetProxy } from './presets.js'\nimport type { DevPreset, DiscoveredParts, PresetProxyContext, ProxySource } 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/**\n * Shared `@inquirer/*` context. Rendered to stderr (mirrors the bare-`infra-kit` command palette).\n *\n * `clearPromptOnDone` erases each answered prompt instead of leaving a `\u2714 <question> <answer>` line\n * behind, so the wizard collapses to nothing once it finishes and the dev-server's ready header lands\n * at the top of a clean screen. The choices are not lost: {@link echoManual} prints the equivalent flag\n * command, which is the durable record of what was picked.\n */\nconst promptContext = { output: process.stderr, clearPromptOnDone: true }\n\n/**\n * Real `@inquirer/*` prompts, rendered to stderr and erased once answered.\n *\n * Every one goes through {@link withEscape}, which binds Esc to cancellation. The wrap is here rather\n * than around the {@link WizardPrompts} seam so the scripted prompts the tests inject stay untouched:\n * the seam is the test boundary, `defaultPrompts` is the only implementation that talks to a terminal.\n *\n * Esc rejects with an `AbortPromptError`, which `entry/cli.ts` catches as a cancellation \u2192 exit 0. The\n * wizard therefore needs no cancel branch of its own: a cancelled prompt never returns here at all, so\n * no server is started.\n */\nexport const defaultPrompts: WizardPrompts = {\n select: (cfg) => {\n // The wizard is TTY-only by construction (`shouldRunWizard`); the declaration is for the sweep,\n // which roots `entry/dev-server.ts` and reads the policy off the literal.\n return withEscape(\n (context) => {\n return inquirerSelect({ message: cfg.message, choices: cfg.choices, default: cfg.default }, context)\n },\n { ...promptContext, whenHeadless: 'refuse' },\n )\n },\n checkbox: (cfg) => {\n return withEscape(\n (context) => {\n return inquirerCheckbox({ message: cfg.message, choices: cfg.choices }, context)\n },\n { ...promptContext, whenHeadless: 'refuse' },\n )\n },\n confirm: (cfg) => {\n return withEscape(\n (context) => {\n return inquirerConfirm({ message: cfg.message, default: cfg.default }, context)\n },\n { ...promptContext, whenHeadless: 'refuse' },\n )\n },\n}\n\n/** What the wizard hands back to the entry point (merged into `DevServerOptions`), or null on cancel. */\nexport interface WizardResult {\n /** In-memory run plan \u2014 passed through as `options.presetDef`. */\n presetDef?: DevPreset\n /**\n * App-name include list for the Orca path only: which discovered API apps get a pane. Paired with\n * `presetDef`, which tells each pane the exact parts to run. An app selected UI-only gets no pane\n * (Orca panes are opened per API app). Unset for the non-Orca manual path.\n */\n include?: string[]\n watch: boolean\n orca: 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[]; default?: 'local' | 'cloud' }>,\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 cap: RouteCap = {\n path: route,\n localCapable: def.from.includes('local'),\n cloudCapable: def.from.includes('cloud'),\n default: def.default,\n }\n\n const existing = byPkg.get(def.packageName)\n\n if (existing) {\n existing.routes.push(cap)\n existing.localCapable = existing.localCapable || cap.localCapable\n existing.cloudCapable = existing.cloudCapable || cap.cloudCapable\n } else {\n byPkg.set(def.packageName, {\n packageName: def.packageName,\n routes: [cap],\n localCapable: cap.localCapable,\n cloudCapable: cap.cloudCapable,\n ownerApp: ownerByPkg.get(def.packageName),\n })\n }\n }\n\n return [...byPkg.values()]\n}\n\n/**\n * Load one frontend's proxy routes and group them into backends. A config that throws \u2014 schema-invalid\n * or an import-time error \u2014 degrades THAT app to zero backends with a named warning instead of rejecting.\n * Without this, a single broken `apps/<x>/ui/infra-kit.config.ts` fails the whole `Promise.all` in\n * {@link gatherWizardModel} and takes the entire wizard down, so `infra-kit dev` becomes unusable for every\n * other app in the monorepo.\n */\nexport const loadBackends = async (\n uiDir: string,\n name: string,\n ownerByPkg: Map<string, string>,\n): Promise<ProxyBackend[]> => {\n try {\n const routes = (await loadDev(uiDir))?.proxy?.routes\n\n return routes ? groupBackends(routes, ownerByPkg) : []\n } catch (error) {\n logger.warn(\n `\u26A0\uFE0F Skipping ${name}: its dev config failed to load \u2014 ${error instanceof Error ? error.message : String(error)}`,\n )\n\n return []\n }\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\n const ownerByPkg = new Map(\n apiApps.map((a) => {\n return [a.packageName, a.name] as const\n }),\n )\n\n const apiPkgByName = new Map(\n apiApps.map((a) => {\n return [a.name, a.packageName] as const\n }),\n )\n\n const uiNames = new Set(\n uiApps.map((a) => {\n return a.name\n }),\n )\n\n const apiNames = new Set(\n apiApps.map((a) => {\n return a.name\n }),\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\n return {\n name,\n hasApi: apiNames.has(name),\n hasUi,\n apiPackage: apiPkgByName.get(name),\n backends: hasUi ? await loadBackends(path.join(root, 'apps', name, 'ui'), name, ownerByPkg) : [],\n deployedUrlEnv: hasUi\n ? (\n await loadPackageConfig(path.join(root, 'apps', name, 'ui')).catch(() => {\n return undefined\n })\n )?.deployedUrlEnv\n : undefined,\n }\n }),\n )\n\n // The cloud-env choices are the envs we hold a token for \u2014 the same authority as `env-load`, because\n // it is the same question. Picking one loads that env's deployed URLs for the picked frontends, which\n // `@slip-stream-kit/config/vite` sends their cloud routes to \u2014 and the download needs the env's token. Sourcing it from the workflow options instead would put `prod` in the list\n // for everyone \u2014 including a developer holding no prod credential \u2014 and point a local UI at\n // production. No token, no entry.\n const store = await readTokenStore()\n\n return {\n apps,\n environments: Object.keys(store?.envs ?? {}).sort(),\n }\n}\n\n/**\n * Build the checkbox choices: one `<app>/ui` per frontend, plus `<app>/api` ONLY for api-only apps\n * (an api with no ui). A full-stack `<app>/api` is deliberately NOT offered \u2014 a full-stack backend is\n * launched by choosing `local` for one of its frontend's routes (asked per-route afterwards), so the\n * checkbox stays a pick of frontends + standalone backends.\n *\n * Nothing starts checked: the selection is opt-in, so a run only ever launches what was explicitly\n * ticked. Pre-checking every part would make the fast path (accept the defaults) boot the whole monorepo\n * \u2014 the exact opposite of why someone reached for the wizard over a bare `infra-kit dev <app>`.\n */\nconst buildPartChoices = (model: WizardModel): WizardChoice[] => {\n const choices: WizardChoice[] = []\n\n for (const app of model.apps) {\n if (app.hasUi) {\n const paths = app.backends.flatMap((b) => {\n return b.routes.map((r) => {\n return r.path\n })\n })\n\n const description = paths.length > 0 ? `frontend \u2014 proxies ${[...paths].sort().join(', ')}` : 'frontend'\n\n choices.push({ name: `${app.name}/ui`, value: `${app.name}/ui`, description })\n }\n if (app.hasApi && !app.hasUi) {\n choices.push({ name: `${app.name}/api`, value: `${app.name}/api`, description: 'backend' })\n }\n }\n\n return choices\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\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.\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.path}`, 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 proxy-locality rule. Returns issue 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(presetDef, buildAuditContext(model)).map((i) => {\n return i.message\n })\n}\n\n/**\n * Opt back into the Orca question: set to exactly `'1'`. The prompt is temporarily off \u2014 the manual\n * branch always runs in-process \u2014 but the derivation below is kept whole for when it returns. `--orca`\n * on the command line is a separate path and stays live either way.\n */\nexport const WIZARD_ORCA_VAR = 'INFRA_KIT_DEV_WIZARD_ORCA'\n\nconst asksOrca = (): boolean => {\n return process.env[WIZARD_ORCA_VAR] === '1'\n}\n\n/**\n * Ask, per selected frontend, an EXPLICIT local/cloud choice for every TWO-OPTION route (its `from`\n * lists both). Single-option routes are auto-picked in the pure mapping, so they are never prompted.\n * The answers are keyed `${app}/ui ${routePath}` to match {@link ManualSelection.sources}; the select's\n * default seeds from the route's declared `default` when it has one.\n */\nconst promptRouteSources = async (\n prompts: WizardPrompts,\n model: WizardModel,\n uiKeys: string[],\n): Promise<Record<string, ProxySource>> => {\n const apps = new Map(\n model.apps.map((a) => {\n return [a.name, a] as const\n }),\n )\n\n const sources: Record<string, ProxySource> = {}\n\n for (const key of uiKeys) {\n const app = apps.get(key.split('/')[0]!)\n\n for (const backend of app?.backends ?? []) {\n for (const route of backend.routes) {\n if (!route.localCapable || !route.cloudCapable) continue\n\n const answer = await prompts.select({\n message: `\uD83D\uDD00 ${key} ${route.path} \u2192 local or cloud?`,\n choices: [\n { name: 'local (this machine)', value: 'local' },\n { name: 'cloud', value: 'cloud' },\n ],\n default: route.default ?? 'local',\n })\n\n sources[`${key} ${route.path}`] = answer as ProxySource\n }\n }\n }\n\n return sources\n}\n\n/** Look up a frontend route's capability in the model (for the recap's \"only option\" annotation). */\nconst findRouteCap = (model: WizardModel, app: string, routePath: string): RouteCap | undefined => {\n return model.apps\n .find((a) => {\n return a.name === app\n })\n ?.backends.flatMap((b) => {\n return b.routes\n })\n .find((r) => {\n return r.path === routePath\n })\n}\n\n/**\n * Print a one-line-per-route recap of what each frontend route resolved to, so the chosen sources are\n * visible before launch. A single-option route is annotated `(only option)` \u2014 it was auto-picked, not\n * asked. This is in addition to {@link echoManual}'s pasteable `--target` command.\n */\nconst recapSources = (plan: DerivedPlan, model: WizardModel): void => {\n const lines: string[] = []\n\n for (const [key, cfg] of Object.entries(plan.presetDef.apps ?? {})) {\n if (!key.endsWith('/ui') || !cfg.proxy) continue\n\n const app = key.split('/')[0]!\n\n for (const [routePath, source] of Object.entries(cfg.proxy)) {\n const cap = findRouteCap(model, app, routePath)\n const suffix = cap && cap.localCapable && cap.cloudCapable ? '' : ' (only option)'\n\n lines.push(` ${key} ${routePath} \u2192 ${source}${suffix}`)\n }\n }\n\n if (lines.length === 0) return\n logger.info('Resolved routes:')\n for (const line of [...lines].sort()) {\n logger.info(line)\n }\n}\n\n/** The manual-branch flow: frontends + per-route source + env + watch + orca \u2192 audited plan \u2192 recap \u2192 echo. */\n/** Reads named variables of one environment (its Doppler config). A seam, so the flow runs without Doppler in tests. */\nexport type LoadEnvVars = (env: string, names: string[]) => Promise<Map<string, string>>\n\nconst loadEnvVarsFromDoppler: LoadEnvVars = async (env, names) => {\n const { downloadEnvSecrets } = await import('src/commands/env-load/env-load')\n const secrets = await downloadEnvSecrets(env)\n\n return new Map(\n names.flatMap((name) => {\n const value = secrets.get(name)\n\n return value === undefined ? [] : [[name, value] as const]\n }),\n )\n}\n\n/**\n * Point this session's cloud routes at the picked env: its name, and the deployed URL of every picked\n * frontend. The URLs are the picked env's own \u2014 the shell may hold another env's, and `INFRA_KIT_ENV` alone\n * no longer names a host. Backends keep the shell's env; only what the proxy reads changes.\n */\nconst applyCloudEnv = async (\n env: string,\n uiKeys: string[],\n model: WizardModel,\n loadEnvVars: LoadEnvVars,\n): Promise<void> => {\n const names = uiKeys.flatMap((key) => {\n const app = model.apps.find((candidate) => {\n return `${candidate.name}/ui` === key\n })\n\n return app?.deployedUrlEnv ? [app.deployedUrlEnv] : []\n })\n\n const values = names.length > 0 ? await loadEnvVars(env, names) : new Map<string, string>()\n\n process.env[INFRA_KIT_ENV_VAR] = env\n\n for (const name of names) {\n const value = values.get(name)\n\n if (value) {\n process.env[name] = value\n } else {\n // The shell's value names another env's host; keeping it would send this env's cloud routes there.\n delete process.env[name]\n logger.warn(`\u2601\uFE0F \"${env}\" holds no ${name}; routes that go to the deployed app will fail until it does.`)\n }\n }\n}\n\nconst runManualBranch = async (\n prompts: WizardPrompts,\n model: WizardModel,\n loadEnvVars: LoadEnvVars,\n): 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 selectedTargets = await prompts.checkbox({ message: '\uD83D\uDCE6 Which packages?', choices: buildPartChoices(model) })\n\n if (selectedTargets.length === 0) {\n logger.warn('No packages selected.')\n\n return null\n }\n\n const uiKeys = selectedTargets.filter((t) => {\n return t.endsWith('/ui')\n })\n\n const sources = await promptRouteSources(prompts, model, uiKeys)\n\n const watch = await prompts.confirm({ message: '\uD83D\uDC40 Rebuild & restart on save (watch)?', default: true })\n\n const orca = asksOrca()\n ? await prompts.confirm({ message: '\uD83E\uDDE9 Run each app in its own Orca pane?', default: false })\n : false\n\n const selection: ManualSelection = { targets: selectedTargets, sources, watch, orca }\n const plan = deriveManualPlan(selection, model)\n\n if (plan.anyCloudRoute) {\n // A cloud route needs an environment to point at, and the only environments we can offer are those we\n // hold a token for. With none loaded, the `select` below would be handed zero choices and `@inquirer/select`\n // throws `No selectable choices` \u2014 a raw stack on a realistic first-run path (fresh clone, no `env-load`).\n // Back out cleanly with guidance instead, and BEFORE mutating `process.env`, so a refused run leaks nothing.\n if (model.environments.length === 0) {\n logger.warn('\u2601\uFE0F This selection routes to a cloud environment, but no environment token is loaded.')\n logger.warn(' Run `infra-kit env-load` to add one, then re-run the wizard.')\n\n return null\n }\n\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 await applyCloudEnv(selection.env, uiKeys, model, loadEnvVars)\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 recapSources(plan, model)\n echoManual(plan, selection)\n\n // Orca opens one pane per selected API app. `include` picks WHICH apps get a pane; `presetDef` tells\n // each pane exactly which of its parts to run. Without the latter a pane runs `--app=<name>`, which\n // expands to every part the app has \u2014 silently starting a UI the user just unticked.\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 Orca run EVERY api app,\n // so an Orca run with no selected backends (e.g. an all-frontend selection) falls back to in-process.\n if (orca && apiApps.length > 0) {\n return { include: apiApps, presetDef: plan.presetDef, watch, orca: true }\n }\n\n if (orca) {\n logger.info('\u2139\uFE0F Orca needs at least one local backend (panes are backend-only) \u2014 running in-process instead.')\n }\n\n return { presetDef: plan.presetDef, watch, orca: false }\n}\n\n/** Print the equivalent (`--target=\u2026`) flag command. */\nconst echoManual = (plan: DerivedPlan, selection: ManualSelection): void => {\n commandEcho.setInteractive()\n\n const eq = equivalentCommand(plan, selection)\n\n commandEcho.addOption(eq.flags, true)\n commandEcho.print()\n}\n\n/**\n * Drive the wizard 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 (\n prompts: WizardPrompts,\n model: WizardModel,\n loadEnvVars: LoadEnvVars = loadEnvVarsFromDoppler,\n): Promise<WizardResult | null> => {\n return runManualBranch(prompts, model, loadEnvVars)\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 which frontends to run, and for each frontend you choose,\n * every proxy route that has TWO real options (its `from` lists both `local` and `cloud`) is decided\n * EXPLICITLY \u2014 local (its backend is launched \u2192 the route resolves local) or cloud (the route proxies\n * to the `<env>` cloud target). Single-option routes are auto-picked. Because a backend runs local OR\n * cloud as a whole, routes that share one backend package RECONCILE: if any of them resolved local, the\n * backend runs local and all its local-capable routes follow. This module turns those answers into an\n * in-memory {@link DevPreset} the runner consumes verbatim (part-level targets the app-name-only `--app`\n * include cannot express) \u2014 and is side-effect-free so the mapping is fully unit-testable. The impure\n * I/O (discovery, inquirer prompts, audit, launch) lives in {@link file://./dev-wizard-run.ts}.\n */\nimport type { DevPreset, ProxySource } from './presets.js'\n\n/**\n * One proxy route's per-route capability, carried through grouping so the wizard can decide whether it\n * even needs to ask about the route (only a `local`+`cloud` route is a real choice).\n */\nexport interface RouteCap {\n /** Route path (e.g. `/api`). */\n path: string\n /** `from` includes `local` \u2014 the route can resolve to a locally-launched backend. */\n localCapable: boolean\n /** `from` includes `cloud` \u2014 the route can resolve to the cloud target. */\n cloudCapable: boolean\n /** The route's declared `default` source when it has one (present iff `from` lists >1 source) \u2014 seeds the prompt's default. */\n default?: ProxySource\n}\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 (the reconcile in\n * {@link deriveManualPlan}).\n */\nexport interface ProxyBackend {\n /** Backend package name (a route's `packageName`). */\n packageName: string\n /** The routes that resolve to this package, each with its own local/cloud capability. */\n routes: RouteCap[]\n /** True when at least one of `routes` lists `local` in its `from` capabilities \u2014 i.e. it is launchable-local. */\n localCapable: boolean\n /** True when at least one of `routes` lists `cloud` \u2014 i.e. it can stay on the cloud target. */\n cloudCapable: 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.\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 /** The frontend's `deployedUrlEnv` \u2014 the variable its cloud routes are sent to. Absent when it declares none. */\n deployedUrlEnv?: string\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 /** 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 /**\n * The target keys the user ticked directly: every selected `<app>/ui` frontend, plus any api-only\n * `<app>/api` backend (apps with an api but no ui). A full-stack backend is NOT ticked here \u2014 it is\n * launched by choosing `local` for one of its frontend's routes (recorded in {@link sources}).\n */\n targets: string[]\n /**\n * Explicit per-route choice for every TWO-OPTION route, keyed `${app}/ui ${routePath}` (e.g.\n * `client/ui /api`). Single-option routes are absent (auto-picked). Missing two-option answers fall\n * back to the route's cloud/local capability in {@link deriveManualPlan}.\n */\n sources: Record<string, ProxySource>\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 Orca pane per app. */\n orca: boolean\n}\n\n/** The resolved run plan derived from a {@link ManualSelection}. */\nexport interface DerivedPlan {\n /** In-memory run plan 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/** App folder name from an `<app>/<part>` target key. */\nconst appOf = (key: string): string => {\n return key.split('/')[0]!\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\n/** Is this route a real choice (both local and cloud reachable)? */\nconst isTwoOption = (route: RouteCap): boolean => {\n return route.localCapable && route.cloudCapable\n}\n\n/**\n * The source a single route resolves to BEFORE per-backend reconciliation:\n * - two-option: the user's recorded answer, else 'cloud' when cloud-capable else 'local';\n * - single local-only: always 'local';\n * - single cloud-only: always 'cloud'.\n */\nconst rawSource = (uiKey: string, route: RouteCap, sources: Record<string, ProxySource>): ProxySource => {\n if (isTwoOption(route)) {\n return sources[`${uiKey} ${route.path}`] ?? (route.cloudCapable ? 'cloud' : 'local')\n }\n\n return route.localCapable ? 'local' : 'cloud'\n}\n\n/**\n * Pass 1: which backend packages resolve local (any of their routes did), plus each package's owner app.\n * A package's local verdict is shared across every frontend that proxies to it \u2014 that is the reconcile.\n */\nconst collectLocalPackages = (\n uiKeys: string[],\n apps: Map<string, WizardApp>,\n sources: Record<string, ProxySource>,\n): { localPkgs: Set<string>; ownerByPkg: Map<string, string | undefined> } => {\n const localPkgs = new Set<string>()\n const ownerByPkg = new Map<string, string | undefined>()\n\n for (const key of uiKeys) {\n const app = apps.get(appOf(key))\n\n for (const backend of app?.backends ?? []) {\n ownerByPkg.set(backend.packageName, backend.ownerApp)\n for (const route of backend.routes) {\n if (rawSource(key, route, sources) === 'local') localPkgs.add(backend.packageName)\n }\n }\n }\n\n return { localPkgs, ownerByPkg }\n}\n\n/**\n * Pass 2 for ONE frontend: its per-route overrides after the per-package verdict, and whether any route\n * went cloud. A local-capable route on a local package is `local` (reconciled up); everything else `cloud`.\n */\nconst frontendOverrides = (\n app: WizardApp | undefined,\n localPkgs: ReadonlySet<string>,\n): { overrides: Record<string, ProxySource>; anyCloud: boolean } => {\n const overrides: Record<string, ProxySource> = {}\n let anyCloud = false\n\n for (const backend of app?.backends ?? []) {\n const backendLocal = localPkgs.has(backend.packageName)\n\n for (const route of backend.routes) {\n const source: ProxySource = route.localCapable && backendLocal ? 'local' : 'cloud'\n\n overrides[route.path] = source\n if (source === 'cloud') anyCloud = true\n }\n }\n\n return { overrides, anyCloud }\n}\n\n/**\n * Turn the manual {@link ManualSelection} into an in-memory {@link DevPreset}. The frontend checkbox picks\n * which UIs (and standalone api-only backends) to run; the per-route answers in {@link ManualSelection.sources}\n * decide each two-option route. Resolution is two passes so same-backend routes stay coherent:\n *\n * 1. Resolve every route of every selected frontend to its raw source; a package is LOCAL iff any of its\n * routes resolved local.\n * 2. Re-resolve each route against that per-package verdict: a local-capable route on a local package is\n * `local` (reconciled up even if it was individually answered cloud); a cloud-only route stays `cloud`.\n *\n * Launched backends are the ticked api-only parts \u222A every package that resolved local (added as\n * `${ownerApp}/api` when the owner is discoverable \u2014 a local override on an owner-less package is still\n * emitted, and the audit flags it).\n *\n * @example\n * // client frontend, its /api answered local \u2192 launches client/api + client/ui, /api resolves local:\n * deriveManualPlan(\n * { targets: ['client/ui'], sources: { 'client/ui /api': 'local' }, watch: false, orca: false },\n * { apps: [{ name: 'client', hasApi: true, hasUi: true, backends: [{ packageName: 'client-api',\n * cloudCapable: true, localCapable: true, ownerApp: 'client',\n * routes: [{ path: '/api', localCapable: true, cloudCapable: true }] }] }],\n * environments: ['dev'] },\n * ).targetKeys // => ['client/api', 'client/ui']\n */\nexport const deriveManualPlan = (selection: ManualSelection, model: WizardModel): DerivedPlan => {\n const apps = byName(model)\n\n const uiKeys = selection.targets.filter((t) => {\n return t.endsWith('/ui')\n })\n\n const apiOnlyKeys = selection.targets.filter((t) => {\n return t.endsWith('/api')\n })\n\n // Pass 1: a package is local iff any of its routes (across every selected frontend) resolved local.\n const { localPkgs, ownerByPkg } = collectLocalPackages(uiKeys, apps, selection.sources)\n\n // Pass 2: re-resolve each route against the per-package verdict + build per-frontend overrides.\n const presetApps: Record<string, { proxy?: Record<string, ProxySource> }> = {}\n let anyCloud = false\n\n for (const key of uiKeys) {\n const { overrides, anyCloud: cloud } = frontendOverrides(apps.get(appOf(key)), localPkgs)\n\n if (cloud) anyCloud = true\n presetApps[key] = Object.keys(overrides).length > 0 ? { proxy: overrides } : {}\n }\n\n // Launch: the ticked api-only backends, plus every package that resolved local (via its owner app).\n for (const key of apiOnlyKeys) {\n presetApps[key] ??= {}\n }\n for (const pkg of localPkgs) {\n const owner = ownerByPkg.get(pkg)\n\n if (owner != null) presetApps[`${owner}/api`] ??= {}\n }\n\n return {\n presetDef: { apps: presetApps },\n anyCloudRoute: anyCloud,\n targetKeys: Object.keys(presetApps).sort(),\n }\n}\n\n/** The equivalent non-interactive command for a derived plan. */\nexport interface EquivalentCommand {\n /** The `--target=<app>/<part>,\u2026`-form flag string (no leading `infra-kit dev`). */\n flags: string\n}\n\n/**\n * Build the equivalent `infra-kit dev --target=\u2026 [--no-watch] [--orca]` flag string for a derived plan.\n * Always exact: `--target` names packages at `<app>/<part>` granularity \u2014 the same grammar the plan's\n * `targetKeys` already use \u2014 so every wizard selection, whole-app or part-level, round-trips into a\n * pasteable command. (`--app` is deliberately NOT used here: its app-name granularity over-launches a\n * part-level pick \u2014 `--app=client` starts `client/api` even when only `client/ui` was ticked.)\n */\nexport const equivalentCommand = (plan: DerivedPlan, selection: ManualSelection): EquivalentCommand => {\n const parts = [`--target=${plan.targetKeys.join(',')}`]\n\n if (!selection.watch) parts.push('--no-watch')\n if (selection.orca) parts.push('--orca')\n\n return { flags: parts.join(' ') }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Impure driver for the interactive `infra-kit dev` wizard: filesystem/config discovery, the inquirer\n * prompt flow (the package + per-route 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` / `--agent` invocation bypasses it entirely.\n */\nimport inquirerCheckbox from '@inquirer/checkbox'\nimport inquirerConfirm from '@inquirer/confirm'\nimport inquirerSelect from '@inquirer/select'\nimport { loadDev, loadPackageConfig } from '@slip-stream-kit/config/internal'\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 { readTokenStore } from 'src/lib/env-tokens'\nimport { logger } from 'src/lib/logger'\nimport { withEscape } from 'src/lib/prompts/escapable-context'\n\nimport { deriveManualPlan, equivalentCommand } from './dev-wizard.js'\nimport type { DerivedPlan, ManualSelection, ProxyBackend, RouteCap, WizardApp, WizardModel } from './dev-wizard.js'\nimport { validatePresetProxy } from './presets.js'\nimport type { DevPreset, DiscoveredParts, PresetProxyContext, ProxySource } 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/**\n * Shared `@inquirer/*` context. Rendered to stderr (mirrors the bare-`infra-kit` command palette).\n *\n * `clearPromptOnDone` erases each answered prompt instead of leaving a `\u2714 <question> <answer>` line\n * behind, so the wizard collapses to nothing once it finishes and the dev-server's ready header lands\n * at the top of a clean screen. The choices are not lost: {@link echoManual} prints the equivalent flag\n * command, which is the durable record of what was picked.\n */\nconst promptContext = { output: process.stderr, clearPromptOnDone: true }\n\n/**\n * Real `@inquirer/*` prompts, rendered to stderr and erased once answered.\n *\n * Every one goes through {@link withEscape}, which binds Esc to cancellation. The wrap is here rather\n * than around the {@link WizardPrompts} seam so the scripted prompts the tests inject stay untouched:\n * the seam is the test boundary, `defaultPrompts` is the only implementation that talks to a terminal.\n *\n * Esc rejects with an `AbortPromptError`, which `entry/cli.ts` catches as a cancellation \u2192 exit 0. The\n * wizard therefore needs no cancel branch of its own: a cancelled prompt never returns here at all, so\n * no server is started.\n */\nexport const defaultPrompts: WizardPrompts = {\n select: (cfg) => {\n // The wizard is TTY-only by construction (`shouldRunWizard`); the declaration is for the sweep,\n // which roots `entry/dev-server.ts` and reads the policy off the literal.\n return withEscape(\n (context) => {\n return inquirerSelect({ message: cfg.message, choices: cfg.choices, default: cfg.default }, context)\n },\n { ...promptContext, whenHeadless: 'refuse' },\n )\n },\n checkbox: (cfg) => {\n return withEscape(\n (context) => {\n return inquirerCheckbox({ message: cfg.message, choices: cfg.choices }, context)\n },\n { ...promptContext, whenHeadless: 'refuse' },\n )\n },\n confirm: (cfg) => {\n return withEscape(\n (context) => {\n return inquirerConfirm({ message: cfg.message, default: cfg.default }, context)\n },\n { ...promptContext, whenHeadless: 'refuse' },\n )\n },\n}\n\n/** What the wizard hands back to the entry point (merged into `DevServerOptions`), or null on cancel. */\nexport interface WizardResult {\n /** In-memory run plan \u2014 passed through as `options.presetDef`. */\n presetDef?: DevPreset\n /**\n * App-name include list for the Orca path only: which discovered API apps get a pane. Paired with\n * `presetDef`, which tells each pane the exact parts to run. An app selected UI-only gets no pane\n * (Orca panes are opened per API app). Unset for the non-Orca manual path.\n */\n include?: string[]\n watch: boolean\n orca: 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[]; default?: 'local' | 'cloud' }>,\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 cap: RouteCap = {\n path: route,\n localCapable: def.from.includes('local'),\n cloudCapable: def.from.includes('cloud'),\n default: def.default,\n }\n\n const existing = byPkg.get(def.packageName)\n\n if (existing) {\n existing.routes.push(cap)\n existing.localCapable = existing.localCapable || cap.localCapable\n existing.cloudCapable = existing.cloudCapable || cap.cloudCapable\n } else {\n byPkg.set(def.packageName, {\n packageName: def.packageName,\n routes: [cap],\n localCapable: cap.localCapable,\n cloudCapable: cap.cloudCapable,\n ownerApp: ownerByPkg.get(def.packageName),\n })\n }\n }\n\n return [...byPkg.values()]\n}\n\n/**\n * Load one frontend's proxy routes and group them into backends. A config that throws \u2014 schema-invalid\n * or an import-time error \u2014 degrades THAT app to zero backends with a named warning instead of rejecting.\n * Without this, a single broken `apps/<x>/ui/infra-kit.config.ts` fails the whole `Promise.all` in\n * {@link gatherWizardModel} and takes the entire wizard down, so `infra-kit dev` becomes unusable for every\n * other app in the monorepo.\n */\nexport const loadBackends = async (\n uiDir: string,\n name: string,\n ownerByPkg: Map<string, string>,\n): Promise<ProxyBackend[]> => {\n try {\n const routes = (await loadDev(uiDir))?.proxy?.routes\n\n return routes ? groupBackends(routes, ownerByPkg) : []\n } catch (error) {\n logger.warn(\n `\u26A0\uFE0F Skipping ${name}: its dev config failed to load \u2014 ${error instanceof Error ? error.message : String(error)}`,\n )\n\n return []\n }\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\n const ownerByPkg = new Map(\n apiApps.map((a) => {\n return [a.packageName, a.name] as const\n }),\n )\n\n const apiPkgByName = new Map(\n apiApps.map((a) => {\n return [a.name, a.packageName] as const\n }),\n )\n\n const uiNames = new Set(\n uiApps.map((a) => {\n return a.name\n }),\n )\n\n const apiNames = new Set(\n apiApps.map((a) => {\n return a.name\n }),\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\n return {\n name,\n hasApi: apiNames.has(name),\n hasUi,\n apiPackage: apiPkgByName.get(name),\n backends: hasUi ? await loadBackends(path.join(root, 'apps', name, 'ui'), name, ownerByPkg) : [],\n deployedUrlEnv: hasUi\n ? (\n await loadPackageConfig(path.join(root, 'apps', name, 'ui')).catch(() => {\n return undefined\n })\n )?.deployedUrlEnv\n : undefined,\n }\n }),\n )\n\n // The cloud-env choices are the envs we hold a token for \u2014 the same authority as `env-load`, because\n // it is the same question. Picking one loads that env's deployed URLs for the picked frontends, which\n // `@slip-stream-kit/config/vite` sends their cloud routes to \u2014 and the download needs the env's token. Sourcing it from the workflow options instead would put `prod` in the list\n // for everyone \u2014 including a developer holding no prod credential \u2014 and point a local UI at\n // production. No token, no entry.\n const store = await readTokenStore()\n\n return {\n apps,\n environments: Object.keys(store?.envs ?? {}).sort(),\n }\n}\n\n/**\n * Build the checkbox choices: one `<app>/ui` per frontend, plus `<app>/api` ONLY for api-only apps\n * (an api with no ui). A full-stack `<app>/api` is deliberately NOT offered \u2014 a full-stack backend is\n * launched by choosing `local` for one of its frontend's routes (asked per-route afterwards), so the\n * checkbox stays a pick of frontends + standalone backends.\n *\n * Nothing starts checked: the selection is opt-in, so a run only ever launches what was explicitly\n * ticked. Pre-checking every part would make the fast path (accept the defaults) boot the whole monorepo\n * \u2014 the exact opposite of why someone reached for the wizard over a bare `infra-kit dev <app>`.\n */\nconst buildPartChoices = (model: WizardModel): WizardChoice[] => {\n const choices: WizardChoice[] = []\n\n for (const app of model.apps) {\n if (app.hasUi) {\n const paths = app.backends.flatMap((b) => {\n return b.routes.map((r) => {\n return r.path\n })\n })\n\n const description = paths.length > 0 ? `frontend \u2014 proxies ${[...paths].sort().join(', ')}` : 'frontend'\n\n choices.push({ name: `${app.name}/ui`, value: `${app.name}/ui`, description })\n }\n if (app.hasApi && !app.hasUi) {\n choices.push({ name: `${app.name}/api`, value: `${app.name}/api`, description: 'backend' })\n }\n }\n\n return choices\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\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.\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.path}`, 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 proxy-locality rule. Returns issue 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(presetDef, buildAuditContext(model)).map((i) => {\n return i.message\n })\n}\n\n/**\n * Opt back into the Orca question: set to exactly `'1'`. The prompt is temporarily off \u2014 the manual\n * branch always runs in-process \u2014 but the derivation below is kept whole for when it returns. `--orca`\n * on the command line is a separate path and stays live either way.\n */\nexport const WIZARD_ORCA_VAR = 'INFRA_KIT_DEV_WIZARD_ORCA'\n\nconst asksOrca = (): boolean => {\n return process.env[WIZARD_ORCA_VAR] === '1'\n}\n\n/**\n * Ask, per selected frontend, an EXPLICIT local/cloud choice for every TWO-OPTION route (its `from`\n * lists both). Single-option routes are auto-picked in the pure mapping, so they are never prompted.\n * The answers are keyed `${app}/ui ${routePath}` to match {@link ManualSelection.sources}; the select's\n * default seeds from the route's declared `default` when it has one.\n */\nconst promptRouteSources = async (\n prompts: WizardPrompts,\n model: WizardModel,\n uiKeys: string[],\n): Promise<Record<string, ProxySource>> => {\n const apps = new Map(\n model.apps.map((a) => {\n return [a.name, a] as const\n }),\n )\n\n const sources: Record<string, ProxySource> = {}\n\n for (const key of uiKeys) {\n const app = apps.get(key.split('/')[0]!)\n\n for (const backend of app?.backends ?? []) {\n for (const route of backend.routes) {\n if (!route.localCapable || !route.cloudCapable) continue\n\n const answer = await prompts.select({\n message: `\uD83D\uDD00 ${key} ${route.path} \u2192 local or cloud?`,\n choices: [\n { name: 'local (this machine)', value: 'local' },\n { name: 'cloud', value: 'cloud' },\n ],\n default: route.default ?? 'local',\n })\n\n sources[`${key} ${route.path}`] = answer as ProxySource\n }\n }\n }\n\n return sources\n}\n\n/** Look up a frontend route's capability in the model (for the recap's \"only option\" annotation). */\nconst findRouteCap = (model: WizardModel, app: string, routePath: string): RouteCap | undefined => {\n return model.apps\n .find((a) => {\n return a.name === app\n })\n ?.backends.flatMap((b) => {\n return b.routes\n })\n .find((r) => {\n return r.path === routePath\n })\n}\n\n/**\n * Print a one-line-per-route recap of what each frontend route resolved to, so the chosen sources are\n * visible before launch. A single-option route is annotated `(only option)` \u2014 it was auto-picked, not\n * asked. This is in addition to {@link echoManual}'s pasteable `--target` command.\n */\nconst recapSources = (plan: DerivedPlan, model: WizardModel): void => {\n const lines: string[] = []\n\n for (const [key, cfg] of Object.entries(plan.presetDef.apps ?? {})) {\n if (!key.endsWith('/ui') || !cfg.proxy) continue\n\n const app = key.split('/')[0]!\n\n for (const [routePath, source] of Object.entries(cfg.proxy)) {\n const cap = findRouteCap(model, app, routePath)\n const suffix = cap && cap.localCapable && cap.cloudCapable ? '' : ' (only option)'\n\n lines.push(` ${key} ${routePath} \u2192 ${source}${suffix}`)\n }\n }\n\n if (lines.length === 0) return\n logger.info('Resolved routes:')\n for (const line of [...lines].sort()) {\n logger.info(line)\n }\n}\n\n/** The manual-branch flow: frontends + per-route source + env + watch + orca \u2192 audited plan \u2192 recap \u2192 echo. */\n/** Reads named variables of one environment (its Doppler config). A seam, so the flow runs without Doppler in tests. */\nexport type LoadEnvVars = (env: string, names: string[]) => Promise<Map<string, string>>\n\nconst loadEnvVarsFromDoppler: LoadEnvVars = async (env, names) => {\n const { downloadEnvSecrets } = await import('src/integrations/doppler/secrets-download')\n const secrets = await downloadEnvSecrets(env)\n\n return new Map(\n names.flatMap((name) => {\n const value = secrets.get(name)\n\n return value === undefined ? [] : [[name, value] as const]\n }),\n )\n}\n\n/**\n * Point this session's cloud routes at the picked env: its name, and the deployed URL of every picked\n * frontend. The URLs are the picked env's own \u2014 the shell may hold another env's, and `INFRA_KIT_ENV` alone\n * no longer names a host. Backends keep the shell's env; only what the proxy reads changes.\n */\nconst applyCloudEnv = async (\n env: string,\n uiKeys: string[],\n model: WizardModel,\n loadEnvVars: LoadEnvVars,\n): Promise<void> => {\n const names = uiKeys.flatMap((key) => {\n const app = model.apps.find((candidate) => {\n return `${candidate.name}/ui` === key\n })\n\n return app?.deployedUrlEnv ? [app.deployedUrlEnv] : []\n })\n\n const values = names.length > 0 ? await loadEnvVars(env, names) : new Map<string, string>()\n\n process.env[INFRA_KIT_ENV_VAR] = env\n\n for (const name of names) {\n const value = values.get(name)\n\n if (value) {\n process.env[name] = value\n } else {\n // The shell's value names another env's host; keeping it would send this env's cloud routes there.\n delete process.env[name]\n logger.warn(`\u2601\uFE0F \"${env}\" holds no ${name}; routes that go to the deployed app will fail until it does.`)\n }\n }\n}\n\nconst runManualBranch = async (\n prompts: WizardPrompts,\n model: WizardModel,\n loadEnvVars: LoadEnvVars,\n): 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 selectedTargets = await prompts.checkbox({ message: '\uD83D\uDCE6 Which packages?', choices: buildPartChoices(model) })\n\n if (selectedTargets.length === 0) {\n logger.warn('No packages selected.')\n\n return null\n }\n\n const uiKeys = selectedTargets.filter((t) => {\n return t.endsWith('/ui')\n })\n\n const sources = await promptRouteSources(prompts, model, uiKeys)\n\n const watch = await prompts.confirm({ message: '\uD83D\uDC40 Rebuild & restart on save (watch)?', default: true })\n\n const orca = asksOrca()\n ? await prompts.confirm({ message: '\uD83E\uDDE9 Run each app in its own Orca pane?', default: false })\n : false\n\n const selection: ManualSelection = { targets: selectedTargets, sources, watch, orca }\n const plan = deriveManualPlan(selection, model)\n\n if (plan.anyCloudRoute) {\n // A cloud route needs an environment to point at, and the only environments we can offer are those we\n // hold a token for. With none loaded, the `select` below would be handed zero choices and `@inquirer/select`\n // throws `No selectable choices` \u2014 a raw stack on a realistic first-run path (fresh clone, no `env-load`).\n // Back out cleanly with guidance instead, and BEFORE mutating `process.env`, so a refused run leaks nothing.\n if (model.environments.length === 0) {\n logger.warn('\u2601\uFE0F This selection routes to a cloud environment, but no environment token is loaded.')\n logger.warn(' Run `infra-kit env-load` to add one, then re-run the wizard.')\n\n return null\n }\n\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 await applyCloudEnv(selection.env, uiKeys, model, loadEnvVars)\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 recapSources(plan, model)\n echoManual(plan, selection)\n\n // Orca opens one pane per selected API app. `include` picks WHICH apps get a pane; `presetDef` tells\n // each pane exactly which of its parts to run. Without the latter a pane runs `--app=<name>`, which\n // expands to every part the app has \u2014 silently starting a UI the user just unticked.\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 Orca run EVERY api app,\n // so an Orca run with no selected backends (e.g. an all-frontend selection) falls back to in-process.\n if (orca && apiApps.length > 0) {\n return { include: apiApps, presetDef: plan.presetDef, watch, orca: true }\n }\n\n if (orca) {\n logger.info('\u2139\uFE0F Orca needs at least one local backend (panes are backend-only) \u2014 running in-process instead.')\n }\n\n return { presetDef: plan.presetDef, watch, orca: false }\n}\n\n/** Print the equivalent (`--target=\u2026`) flag command. */\nconst echoManual = (plan: DerivedPlan, selection: ManualSelection): void => {\n commandEcho.setInteractive()\n\n const eq = equivalentCommand(plan, selection)\n\n commandEcho.addOption(eq.flags, true)\n commandEcho.print()\n}\n\n/**\n * Drive the wizard 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 (\n prompts: WizardPrompts,\n model: WizardModel,\n loadEnvVars: LoadEnvVars = loadEnvVarsFromDoppler,\n): Promise<WizardResult | null> => {\n return runManualBranch(prompts, model, loadEnvVars)\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 which frontends to run, and for each frontend you choose,\n * every proxy route that has TWO real options (its `from` lists both `local` and `cloud`) is decided\n * EXPLICITLY \u2014 local (its backend is launched \u2192 the route resolves local) or cloud (the route proxies\n * to the `<env>` cloud target). Single-option routes are auto-picked. Because a backend runs local OR\n * cloud as a whole, routes that share one backend package RECONCILE: if any of them resolved local, the\n * backend runs local and all its local-capable routes follow. This module turns those answers into an\n * in-memory {@link DevPreset} the runner consumes verbatim (part-level targets the app-name-only `--app`\n * include cannot express) \u2014 and is side-effect-free so the mapping is fully unit-testable. The impure\n * I/O (discovery, inquirer prompts, audit, launch) lives in {@link file://./dev-wizard-run.ts}.\n */\nimport type { DevPreset, ProxySource } from './presets.js'\n\n/**\n * One proxy route's per-route capability, carried through grouping so the wizard can decide whether it\n * even needs to ask about the route (only a `local`+`cloud` route is a real choice).\n */\nexport interface RouteCap {\n /** Route path (e.g. `/api`). */\n path: string\n /** `from` includes `local` \u2014 the route can resolve to a locally-launched backend. */\n localCapable: boolean\n /** `from` includes `cloud` \u2014 the route can resolve to the cloud target. */\n cloudCapable: boolean\n /** The route's declared `default` source when it has one (present iff `from` lists >1 source) \u2014 seeds the prompt's default. */\n default?: ProxySource\n}\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 (the reconcile in\n * {@link deriveManualPlan}).\n */\nexport interface ProxyBackend {\n /** Backend package name (a route's `packageName`). */\n packageName: string\n /** The routes that resolve to this package, each with its own local/cloud capability. */\n routes: RouteCap[]\n /** True when at least one of `routes` lists `local` in its `from` capabilities \u2014 i.e. it is launchable-local. */\n localCapable: boolean\n /** True when at least one of `routes` lists `cloud` \u2014 i.e. it can stay on the cloud target. */\n cloudCapable: 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.\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 /** The frontend's `deployedUrlEnv` \u2014 the variable its cloud routes are sent to. Absent when it declares none. */\n deployedUrlEnv?: string\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 /** 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 /**\n * The target keys the user ticked directly: every selected `<app>/ui` frontend, plus any api-only\n * `<app>/api` backend (apps with an api but no ui). A full-stack backend is NOT ticked here \u2014 it is\n * launched by choosing `local` for one of its frontend's routes (recorded in {@link sources}).\n */\n targets: string[]\n /**\n * Explicit per-route choice for every TWO-OPTION route, keyed `${app}/ui ${routePath}` (e.g.\n * `client/ui /api`). Single-option routes are absent (auto-picked). Missing two-option answers fall\n * back to the route's cloud/local capability in {@link deriveManualPlan}.\n */\n sources: Record<string, ProxySource>\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 Orca pane per app. */\n orca: boolean\n}\n\n/** The resolved run plan derived from a {@link ManualSelection}. */\nexport interface DerivedPlan {\n /** In-memory run plan 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/** App folder name from an `<app>/<part>` target key. */\nconst appOf = (key: string): string => {\n return key.split('/')[0]!\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\n/** Is this route a real choice (both local and cloud reachable)? */\nconst isTwoOption = (route: RouteCap): boolean => {\n return route.localCapable && route.cloudCapable\n}\n\n/**\n * The source a single route resolves to BEFORE per-backend reconciliation:\n * - two-option: the user's recorded answer, else 'cloud' when cloud-capable else 'local';\n * - single local-only: always 'local';\n * - single cloud-only: always 'cloud'.\n */\nconst rawSource = (uiKey: string, route: RouteCap, sources: Record<string, ProxySource>): ProxySource => {\n if (isTwoOption(route)) {\n return sources[`${uiKey} ${route.path}`] ?? (route.cloudCapable ? 'cloud' : 'local')\n }\n\n return route.localCapable ? 'local' : 'cloud'\n}\n\n/**\n * Pass 1: which backend packages resolve local (any of their routes did), plus each package's owner app.\n * A package's local verdict is shared across every frontend that proxies to it \u2014 that is the reconcile.\n */\nconst collectLocalPackages = (\n uiKeys: string[],\n apps: Map<string, WizardApp>,\n sources: Record<string, ProxySource>,\n): { localPkgs: Set<string>; ownerByPkg: Map<string, string | undefined> } => {\n const localPkgs = new Set<string>()\n const ownerByPkg = new Map<string, string | undefined>()\n\n for (const key of uiKeys) {\n const app = apps.get(appOf(key))\n\n for (const backend of app?.backends ?? []) {\n ownerByPkg.set(backend.packageName, backend.ownerApp)\n for (const route of backend.routes) {\n if (rawSource(key, route, sources) === 'local') localPkgs.add(backend.packageName)\n }\n }\n }\n\n return { localPkgs, ownerByPkg }\n}\n\n/**\n * Pass 2 for ONE frontend: its per-route overrides after the per-package verdict, and whether any route\n * went cloud. A local-capable route on a local package is `local` (reconciled up); everything else `cloud`.\n */\nconst frontendOverrides = (\n app: WizardApp | undefined,\n localPkgs: ReadonlySet<string>,\n): { overrides: Record<string, ProxySource>; anyCloud: boolean } => {\n const overrides: Record<string, ProxySource> = {}\n let anyCloud = false\n\n for (const backend of app?.backends ?? []) {\n const backendLocal = localPkgs.has(backend.packageName)\n\n for (const route of backend.routes) {\n const source: ProxySource = route.localCapable && backendLocal ? 'local' : 'cloud'\n\n overrides[route.path] = source\n if (source === 'cloud') anyCloud = true\n }\n }\n\n return { overrides, anyCloud }\n}\n\n/**\n * Turn the manual {@link ManualSelection} into an in-memory {@link DevPreset}. The frontend checkbox picks\n * which UIs (and standalone api-only backends) to run; the per-route answers in {@link ManualSelection.sources}\n * decide each two-option route. Resolution is two passes so same-backend routes stay coherent:\n *\n * 1. Resolve every route of every selected frontend to its raw source; a package is LOCAL iff any of its\n * routes resolved local.\n * 2. Re-resolve each route against that per-package verdict: a local-capable route on a local package is\n * `local` (reconciled up even if it was individually answered cloud); a cloud-only route stays `cloud`.\n *\n * Launched backends are the ticked api-only parts \u222A every package that resolved local (added as\n * `${ownerApp}/api` when the owner is discoverable \u2014 a local override on an owner-less package is still\n * emitted, and the audit flags it).\n *\n * @example\n * // client frontend, its /api answered local \u2192 launches client/api + client/ui, /api resolves local:\n * deriveManualPlan(\n * { targets: ['client/ui'], sources: { 'client/ui /api': 'local' }, watch: false, orca: false },\n * { apps: [{ name: 'client', hasApi: true, hasUi: true, backends: [{ packageName: 'client-api',\n * cloudCapable: true, localCapable: true, ownerApp: 'client',\n * routes: [{ path: '/api', localCapable: true, cloudCapable: true }] }] }],\n * environments: ['dev'] },\n * ).targetKeys // => ['client/api', 'client/ui']\n */\nexport const deriveManualPlan = (selection: ManualSelection, model: WizardModel): DerivedPlan => {\n const apps = byName(model)\n\n const uiKeys = selection.targets.filter((t) => {\n return t.endsWith('/ui')\n })\n\n const apiOnlyKeys = selection.targets.filter((t) => {\n return t.endsWith('/api')\n })\n\n // Pass 1: a package is local iff any of its routes (across every selected frontend) resolved local.\n const { localPkgs, ownerByPkg } = collectLocalPackages(uiKeys, apps, selection.sources)\n\n // Pass 2: re-resolve each route against the per-package verdict + build per-frontend overrides.\n const presetApps: Record<string, { proxy?: Record<string, ProxySource> }> = {}\n let anyCloud = false\n\n for (const key of uiKeys) {\n const { overrides, anyCloud: cloud } = frontendOverrides(apps.get(appOf(key)), localPkgs)\n\n if (cloud) anyCloud = true\n presetApps[key] = Object.keys(overrides).length > 0 ? { proxy: overrides } : {}\n }\n\n // Launch: the ticked api-only backends, plus every package that resolved local (via its owner app).\n for (const key of apiOnlyKeys) {\n presetApps[key] ??= {}\n }\n for (const pkg of localPkgs) {\n const owner = ownerByPkg.get(pkg)\n\n if (owner != null) presetApps[`${owner}/api`] ??= {}\n }\n\n return {\n presetDef: { apps: presetApps },\n anyCloudRoute: anyCloud,\n targetKeys: Object.keys(presetApps).sort(),\n }\n}\n\n/** The equivalent non-interactive command for a derived plan. */\nexport interface EquivalentCommand {\n /** The `--target=<app>/<part>,\u2026`-form flag string (no leading `infra-kit dev`). */\n flags: string\n}\n\n/**\n * Build the equivalent `infra-kit dev --target=\u2026 [--no-watch] [--orca]` flag string for a derived plan.\n * Always exact: `--target` names packages at `<app>/<part>` granularity \u2014 the same grammar the plan's\n * `targetKeys` already use \u2014 so every wizard selection, whole-app or part-level, round-trips into a\n * pasteable command. (`--app` is deliberately NOT used here: its app-name granularity over-launches a\n * part-level pick \u2014 `--app=client` starts `client/api` even when only `client/ui` was ticked.)\n */\nexport const equivalentCommand = (plan: DerivedPlan, selection: ManualSelection): EquivalentCommand => {\n const parts = [`--target=${plan.targetKeys.join(',')}`]\n\n if (!selection.watch) parts.push('--no-watch')\n if (selection.orca) parts.push('--orca')\n\n return { flags: parts.join(' ') }\n}\n"],
5
+ "mappings": "wTASA,OAAOA,MAAsB,qBAC7B,OAAOC,MAAqB,oBAC5B,OAAOC,MAAoB,mBAC3B,OAAS,WAAAC,EAAS,qBAAAC,MAAyB,mCAC3C,OAAOC,MAAU,YACjB,OAAOC,MAAa,eCgGpB,IAAMC,EAASC,GACNA,EAAI,MAAM,GAAG,EAAE,CAAC,EAInBC,EAAUC,GACP,IAAI,IACTA,EAAM,KAAK,IAAKC,GACP,CAACA,EAAE,KAAMA,CAAC,CAClB,CACH,EAIIC,EAAeC,GACZA,EAAM,cAAgBA,EAAM,aAS/BC,EAAY,CAACC,EAAeF,EAAiBG,IAC7CJ,EAAYC,CAAK,EACZG,EAAQ,GAAGD,CAAK,IAAIF,EAAM,IAAI,EAAE,IAAMA,EAAM,aAAe,QAAU,SAGvEA,EAAM,aAAe,QAAU,QAOlCI,EAAuB,CAC3BC,EACAC,EACAH,IAC4E,CAC5E,IAAMI,EAAY,IAAI,IAChBC,EAAa,IAAI,IAEvB,QAAWb,KAAOU,EAAQ,CACxB,IAAMI,EAAMH,EAAK,IAAIZ,EAAMC,CAAG,CAAC,EAE/B,QAAWe,KAAWD,GAAK,UAAY,CAAC,EAAG,CACzCD,EAAW,IAAIE,EAAQ,YAAaA,EAAQ,QAAQ,EACpD,QAAWV,KAASU,EAAQ,OACtBT,EAAUN,EAAKK,EAAOG,CAAO,IAAM,SAASI,EAAU,IAAIG,EAAQ,WAAW,CAErF,CACF,CAEA,MAAO,CAAE,UAAAH,EAAW,WAAAC,CAAW,CACjC,EAMMG,EAAoB,CACxBF,EACAF,IACkE,CAClE,IAAMK,EAAyC,CAAC,EAC5CC,EAAW,GAEf,QAAWH,KAAWD,GAAK,UAAY,CAAC,EAAG,CACzC,IAAMK,EAAeP,EAAU,IAAIG,EAAQ,WAAW,EAEtD,QAAWV,KAASU,EAAQ,OAAQ,CAClC,IAAMK,EAAsBf,EAAM,cAAgBc,EAAe,QAAU,QAE3EF,EAAUZ,EAAM,IAAI,EAAIe,EACpBA,IAAW,UAASF,EAAW,GACrC,CACF,CAEA,MAAO,CAAE,UAAAD,EAAW,SAAAC,CAAS,CAC/B,EA0BaG,EAAmB,CAACC,EAA4BpB,IAAoC,CAC/F,IAAMS,EAAOV,EAAOC,CAAK,EAEnBQ,EAASY,EAAU,QAAQ,OAAQC,GAChCA,EAAE,SAAS,KAAK,CACxB,EAEKC,EAAcF,EAAU,QAAQ,OAAQC,GACrCA,EAAE,SAAS,MAAM,CACzB,EAGK,CAAE,UAAAX,EAAW,WAAAC,CAAW,EAAIJ,EAAqBC,EAAQC,EAAMW,EAAU,OAAO,EAGhFG,EAAsE,CAAC,EACzEP,EAAW,GAEf,QAAWlB,KAAOU,EAAQ,CACxB,GAAM,CAAE,UAAAO,EAAW,SAAUS,CAAM,EAAIV,EAAkBL,EAAK,IAAIZ,EAAMC,CAAG,CAAC,EAAGY,CAAS,EAEpFc,IAAOR,EAAW,IACtBO,EAAWzB,CAAG,EAAI,OAAO,KAAKiB,CAAS,EAAE,OAAS,EAAI,CAAE,MAAOA,CAAU,EAAI,CAAC,CAChF,CAGA,QAAWjB,KAAOwB,EAChBC,EAAWzB,CAAG,IAAM,CAAC,EAEvB,QAAW2B,KAAOf,EAAW,CAC3B,IAAMgB,EAAQf,EAAW,IAAIc,CAAG,EAE5BC,GAAS,OAAMH,EAAW,GAAGG,CAAK,MAAM,IAAM,CAAC,EACrD,CAEA,MAAO,CACL,UAAW,CAAE,KAAMH,CAAW,EAC9B,cAAeP,EACf,WAAY,OAAO,KAAKO,CAAU,EAAE,KAAK,CAC3C,CACF,EAeaI,EAAoB,CAACC,EAAmBR,IAAkD,CACrG,IAAMS,EAAQ,CAAC,YAAYD,EAAK,WAAW,KAAK,GAAG,CAAC,EAAE,EAEtD,OAAKR,EAAU,OAAOS,EAAM,KAAK,YAAY,EACzCT,EAAU,MAAMS,EAAM,KAAK,QAAQ,EAEhC,CAAE,MAAOA,EAAM,KAAK,GAAG,CAAE,CAClC,ED/NA,IAAMC,EAAgB,CAAE,OAAQC,EAAQ,OAAQ,kBAAmB,EAAK,EAa3DC,EAAgC,CAC3C,OAASC,GAGAC,EACJC,GACQC,EAAe,CAAE,QAASH,EAAI,QAAS,QAASA,EAAI,QAAS,QAASA,EAAI,OAAQ,EAAGE,CAAO,EAErG,CAAE,GAAGL,EAAe,aAAc,QAAS,CAC7C,EAEF,SAAWG,GACFC,EACJC,GACQE,EAAiB,CAAE,QAASJ,EAAI,QAAS,QAASA,EAAI,OAAQ,EAAGE,CAAO,EAEjF,CAAE,GAAGL,EAAe,aAAc,QAAS,CAC7C,EAEF,QAAUG,GACDC,EACJC,GACQG,EAAgB,CAAE,QAASL,EAAI,QAAS,QAASA,EAAI,OAAQ,EAAGE,CAAO,EAEhF,CAAE,GAAGL,EAAe,aAAc,QAAS,CAC7C,CAEJ,EAiBMS,EAAgB,CACpBC,EACAC,IACmB,CACnB,IAAMC,EAAQ,IAAI,IAElB,OAAW,CAACC,EAAOC,CAAG,IAAK,OAAO,QAAQJ,CAAM,EAAG,CACjD,IAAMK,EAAgB,CACpB,KAAMF,EACN,aAAcC,EAAI,KAAK,SAAS,OAAO,EACvC,aAAcA,EAAI,KAAK,SAAS,OAAO,EACvC,QAASA,EAAI,OACf,EAEME,EAAWJ,EAAM,IAAIE,EAAI,WAAW,EAEtCE,GACFA,EAAS,OAAO,KAAKD,CAAG,EACxBC,EAAS,aAAeA,EAAS,cAAgBD,EAAI,aACrDC,EAAS,aAAeA,EAAS,cAAgBD,EAAI,cAErDH,EAAM,IAAIE,EAAI,YAAa,CACzB,YAAaA,EAAI,YACjB,OAAQ,CAACC,CAAG,EACZ,aAAcA,EAAI,aAClB,aAAcA,EAAI,aAClB,SAAUJ,EAAW,IAAIG,EAAI,WAAW,CAC1C,CAAC,CAEL,CAEA,MAAO,CAAC,GAAGF,EAAM,OAAO,CAAC,CAC3B,EASaK,EAAe,MAC1BC,EACAC,EACAR,IAC4B,CAC5B,GAAI,CACF,IAAMD,GAAU,MAAMU,EAAQF,CAAK,IAAI,OAAO,OAE9C,OAAOR,EAASD,EAAcC,EAAQC,CAAU,EAAI,CAAC,CACvD,OAASU,EAAO,CACd,OAAAC,EAAO,KACL,0BAAgBH,CAAI,0CAAqCE,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAAC,EACjH,EAEO,CAAC,CACV,CACF,EAGaE,EAAoB,MAAOC,GAAuC,CAC7E,IAAMC,EAAUC,EAAgBF,CAAI,EAC9BG,EAASC,EAAeJ,CAAI,EAE5Bb,EAAa,IAAI,IACrBc,EAAQ,IAAKI,GACJ,CAACA,EAAE,YAAaA,EAAE,IAAI,CAC9B,CACH,EAEMC,EAAe,IAAI,IACvBL,EAAQ,IAAKI,GACJ,CAACA,EAAE,KAAMA,EAAE,WAAW,CAC9B,CACH,EAEME,EAAU,IAAI,IAClBJ,EAAO,IAAKE,GACHA,EAAE,IACV,CACH,EAEMG,EAAW,IAAI,IACnBP,EAAQ,IAAKI,GACJA,EAAE,IACV,CACH,EAEMI,EAAW,CAAC,GAAG,IAAI,IAAI,CAAC,GAAGD,EAAU,GAAGD,CAAO,CAAC,CAAC,EAAE,KAAK,EAExDG,EAAoB,MAAM,QAAQ,IACtCD,EAAS,IAAI,MAAOd,GAA6B,CAC/C,IAAMgB,EAAQJ,EAAQ,IAAIZ,CAAI,EAE9B,MAAO,CACL,KAAAA,EACA,OAAQa,EAAS,IAAIb,CAAI,EACzB,MAAAgB,EACA,WAAYL,EAAa,IAAIX,CAAI,EACjC,SAAUgB,EAAQ,MAAMlB,EAAamB,EAAK,KAAKZ,EAAM,OAAQL,EAAM,IAAI,EAAGA,EAAMR,CAAU,EAAI,CAAC,EAC/F,eAAgBwB,GAEV,MAAME,EAAkBD,EAAK,KAAKZ,EAAM,OAAQL,EAAM,IAAI,CAAC,EAAE,MAAM,IAAM,CAEzE,CAAC,IACA,eACH,MACN,CACF,CAAC,CACH,EAOMmB,EAAQ,MAAMC,EAAe,EAEnC,MAAO,CACL,KAAAL,EACA,aAAc,OAAO,KAAKI,GAAO,MAAQ,CAAC,CAAC,EAAE,KAAK,CACpD,CACF,EAYME,EAAoBC,GAAuC,CAC/D,IAAMC,EAA0B,CAAC,EAEjC,QAAWC,KAAOF,EAAM,KAAM,CAC5B,GAAIE,EAAI,MAAO,CACb,IAAMC,EAAQD,EAAI,SAAS,QAASE,GAC3BA,EAAE,OAAO,IAAKC,GACZA,EAAE,IACV,CACF,EAEKC,EAAcH,EAAM,OAAS,EAAI,2BAAsB,CAAC,GAAGA,CAAK,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC,GAAK,WAE9FF,EAAQ,KAAK,CAAE,KAAM,GAAGC,EAAI,IAAI,MAAO,MAAO,GAAGA,EAAI,IAAI,MAAO,YAAAI,CAAY,CAAC,CAC/E,CACIJ,EAAI,QAAU,CAACA,EAAI,OACrBD,EAAQ,KAAK,CAAE,KAAM,GAAGC,EAAI,IAAI,OAAQ,MAAO,GAAGA,EAAI,IAAI,OAAQ,YAAa,SAAU,CAAC,CAE9F,CAEA,OAAOD,CACT,EAGMM,EAAqBP,GAA2C,CACpE,IAAMQ,EAA8B,CAClC,IAAKR,EAAM,KACR,OAAQZ,GACAA,EAAE,MACV,EACA,IAAKA,GACGA,EAAE,IACV,EACH,GAAIY,EAAM,KACP,OAAQZ,GACAA,EAAE,KACV,EACA,IAAKA,GACGA,EAAE,IACV,CACL,EAEMqB,EAAsC,CAAC,EACvCC,EAAa,IAAI,IAEvB,QAAWR,KAAOF,EAAM,KAAM,CAIxBE,EAAI,YAAc,OAAMO,EAAYP,EAAI,IAAI,EAAIA,EAAI,YACxD,QAAWE,KAAKF,EAAI,SAClB,QAAW9B,KAASgC,EAAE,OACpBM,EAAW,IAAI,GAAGR,EAAI,IAAI,IAAI9B,EAAM,IAAI,GAAIgC,EAAE,WAAW,CAG/D,CAEA,MAAO,CACL,WAAAI,EACA,YAAAC,EACA,SAAU,CAACP,EAAK9B,IACPsC,EAAW,IAAI,GAAGR,CAAG,IAAI9B,CAAK,EAAE,CAE3C,CACF,EAMauC,EAAkB,CAACC,EAAsBZ,IAC7Ca,EAAoBD,EAAWL,EAAkBP,CAAK,CAAC,EAAE,IAAKc,GAC5DA,EAAE,OACV,EAQUC,EAAkB,4BAEzBC,EAAW,IACRxD,EAAQ,IAAIuD,CAAe,IAAM,IASpCE,EAAqB,MACzBC,EACAlB,EACAmB,IACyC,CACzC,IAAM1B,EAAO,IAAI,IACfO,EAAM,KAAK,IAAKZ,GACP,CAACA,EAAE,KAAMA,CAAC,CAClB,CACH,EAEMgC,EAAuC,CAAC,EAE9C,QAAWC,KAAOF,EAAQ,CACxB,IAAMjB,EAAMT,EAAK,IAAI4B,EAAI,MAAM,GAAG,EAAE,CAAC,CAAE,EAEvC,QAAWC,KAAWpB,GAAK,UAAY,CAAC,EACtC,QAAW9B,KAASkD,EAAQ,OAAQ,CAClC,GAAI,CAAClD,EAAM,cAAgB,CAACA,EAAM,aAAc,SAEhD,IAAMmD,EAAS,MAAML,EAAQ,OAAO,CAClC,QAAS,aAAMG,CAAG,IAAIjD,EAAM,IAAI,0BAChC,QAAS,CACP,CAAE,KAAM,uBAAwB,MAAO,OAAQ,EAC/C,CAAE,KAAM,QAAS,MAAO,OAAQ,CAClC,EACA,QAASA,EAAM,SAAW,OAC5B,CAAC,EAEDgD,EAAQ,GAAGC,CAAG,IAAIjD,EAAM,IAAI,EAAE,EAAImD,CACpC,CAEJ,CAEA,OAAOH,CACT,EAGMI,EAAe,CAACxB,EAAoBE,EAAauB,IAC9CzB,EAAM,KACV,KAAMZ,GACEA,EAAE,OAASc,CACnB,GACC,SAAS,QAASE,GACXA,EAAE,MACV,EACA,KAAMC,GACEA,EAAE,OAASoB,CACnB,EAQCC,EAAe,CAACC,EAAmB3B,IAA6B,CACpE,IAAM4B,EAAkB,CAAC,EAEzB,OAAW,CAACP,EAAK3D,CAAG,IAAK,OAAO,QAAQiE,EAAK,UAAU,MAAQ,CAAC,CAAC,EAAG,CAClE,GAAI,CAACN,EAAI,SAAS,KAAK,GAAK,CAAC3D,EAAI,MAAO,SAExC,IAAMwC,EAAMmB,EAAI,MAAM,GAAG,EAAE,CAAC,EAE5B,OAAW,CAACI,EAAWI,CAAM,IAAK,OAAO,QAAQnE,EAAI,KAAK,EAAG,CAC3D,IAAMY,EAAMkD,EAAaxB,EAAOE,EAAKuB,CAAS,EACxCK,EAASxD,GAAOA,EAAI,cAAgBA,EAAI,aAAe,GAAK,iBAElEsD,EAAM,KAAK,KAAKP,CAAG,IAAII,CAAS,WAAMI,CAAM,GAAGC,CAAM,EAAE,CACzD,CACF,CAEA,GAAIF,EAAM,SAAW,EACrB,CAAA/C,EAAO,KAAK,kBAAkB,EAC9B,QAAWkD,IAAQ,CAAC,GAAGH,CAAK,EAAE,KAAK,EACjC/C,EAAO,KAAKkD,CAAI,EAEpB,EAMMC,EAAsC,MAAOC,EAAKC,IAAU,CAChE,GAAM,CAAE,mBAAAC,CAAmB,EAAI,KAAM,QAAO,gCAA2C,EACjFC,EAAU,MAAMD,EAAmBF,CAAG,EAE5C,OAAO,IAAI,IACTC,EAAM,QAASxD,GAAS,CACtB,IAAM2D,EAAQD,EAAQ,IAAI1D,CAAI,EAE9B,OAAO2D,IAAU,OAAY,CAAC,EAAI,CAAC,CAAC3D,EAAM2D,CAAK,CAAU,CAC3D,CAAC,CACH,CACF,EAOMC,EAAgB,MACpBL,EACAd,EACAnB,EACAuC,IACkB,CAClB,IAAML,EAAQf,EAAO,QAASE,GAAQ,CACpC,IAAMnB,EAAMF,EAAM,KAAK,KAAMwC,GACpB,GAAGA,EAAU,IAAI,QAAUnB,CACnC,EAED,OAAOnB,GAAK,eAAiB,CAACA,EAAI,cAAc,EAAI,CAAC,CACvD,CAAC,EAEKuC,EAASP,EAAM,OAAS,EAAI,MAAMK,EAAYN,EAAKC,CAAK,EAAI,IAAI,IAEtE1E,EAAQ,IAAIkF,CAAiB,EAAIT,EAEjC,QAAWvD,KAAQwD,EAAO,CACxB,IAAMG,EAAQI,EAAO,IAAI/D,CAAI,EAEzB2D,EACF7E,EAAQ,IAAIkB,CAAI,EAAI2D,GAGpB,OAAO7E,EAAQ,IAAIkB,CAAI,EACvBG,EAAO,KAAK,kBAAQoD,CAAG,cAAcvD,CAAI,+DAA+D,EAE5G,CACF,EAEMiE,EAAkB,MACtBzB,EACAlB,EACAuC,IACiC,CACjC,GAAIvC,EAAM,KAAK,SAAW,EACxB,OAAAnB,EAAO,KAAK,4BAA4B,EAEjC,KAGT,IAAM+D,EAAkB,MAAM1B,EAAQ,SAAS,CAAE,QAAS,4BAAsB,QAASnB,EAAiBC,CAAK,CAAE,CAAC,EAElH,GAAI4C,EAAgB,SAAW,EAC7B,OAAA/D,EAAO,KAAK,uBAAuB,EAE5B,KAGT,IAAMsC,EAASyB,EAAgB,OAAQC,GAC9BA,EAAE,SAAS,KAAK,CACxB,EAEKzB,EAAU,MAAMH,EAAmBC,EAASlB,EAAOmB,CAAM,EAEzD2B,EAAQ,MAAM5B,EAAQ,QAAQ,CAAE,QAAS,+CAAyC,QAAS,EAAK,CAAC,EAEjG6B,EAAO/B,EAAS,EAClB,MAAME,EAAQ,QAAQ,CAAE,QAAS,+CAAyC,QAAS,EAAM,CAAC,EAC1F,GAEE8B,EAA6B,CAAE,QAASJ,EAAiB,QAAAxB,EAAS,MAAA0B,EAAO,KAAAC,CAAK,EAC9EpB,EAAOsB,EAAiBD,EAAWhD,CAAK,EAE9C,GAAI2B,EAAK,cAAe,CAKtB,GAAI3B,EAAM,aAAa,SAAW,EAChC,OAAAnB,EAAO,KAAK,iGAAuF,EACnGA,EAAO,KAAK,iEAAiE,EAEtE,KAGTmE,EAAU,IAAM,MAAM9B,EAAQ,OAAO,CACnC,QAAS,yDACT,QAASlB,EAAM,aAAa,IAAKkD,IACxB,CAAE,KAAMA,EAAG,MAAOA,CAAE,EAC5B,CACH,CAAC,EACD,MAAMZ,EAAcU,EAAU,IAAK7B,EAAQnB,EAAOuC,CAAW,CAC/D,CAEA,IAAMY,EAASxC,EAAgBgB,EAAK,UAAW3B,CAAK,EAEpD,GAAImD,EAAO,OAAS,EAAG,CACrBtE,EAAO,KAAK,6DAAmD,EAC/D,QAAWuE,KAASD,EAClBtE,EAAO,KAAK,aAAQuE,CAAK,EAAE,EAG7B,OAAO,IACT,CAEA1B,EAAaC,EAAM3B,CAAK,EACxBqD,EAAW1B,EAAMqB,CAAS,EAK1B,IAAMhE,EAAU2C,EAAK,WAClB,OAAQ2B,GACAA,EAAE,SAAS,MAAM,CACzB,EACA,IAAKA,GACGA,EAAE,MAAM,GAAG,EAAE,CAAC,CACtB,EAIH,OAAIP,GAAQ/D,EAAQ,OAAS,EACpB,CAAE,QAASA,EAAS,UAAW2C,EAAK,UAAW,MAAAmB,EAAO,KAAM,EAAK,GAGtEC,GACFlE,EAAO,KAAK,iHAAkG,EAGzG,CAAE,UAAW8C,EAAK,UAAW,MAAAmB,EAAO,KAAM,EAAM,EACzD,EAGMO,EAAa,CAAC1B,EAAmBqB,IAAqC,CAC1EO,EAAY,eAAe,EAE3B,IAAMC,EAAKC,EAAkB9B,EAAMqB,CAAS,EAE5CO,EAAY,UAAUC,EAAG,MAAO,EAAI,EACpCD,EAAY,MAAM,CACpB,EAMaG,GAAgB,MAC3BxC,EACAlB,EACAuC,EAA2BP,IAEpBW,EAAgBzB,EAASlB,EAAOuC,CAAW,EAOvCoB,GAAe,MAC1BzC,EAAyBzD,EACzBsB,EAAe6E,EAAiBpG,EAAQ,IAAI,CAAC,IAEtCkG,GAAcxC,EAAS,MAAMpC,EAAkBC,CAAI,CAAC",
6
6
  "names": ["inquirerCheckbox", "inquirerConfirm", "inquirerSelect", "loadDev", "loadPackageConfig", "path", "process", "appOf", "key", "byName", "model", "a", "isTwoOption", "route", "rawSource", "uiKey", "sources", "collectLocalPackages", "uiKeys", "apps", "localPkgs", "ownerByPkg", "app", "backend", "frontendOverrides", "overrides", "anyCloud", "backendLocal", "source", "deriveManualPlan", "selection", "t", "apiOnlyKeys", "presetApps", "cloud", "pkg", "owner", "equivalentCommand", "plan", "parts", "promptContext", "process", "defaultPrompts", "cfg", "withEscape", "context", "inquirerSelect", "inquirerCheckbox", "inquirerConfirm", "groupBackends", "routes", "ownerByPkg", "byPkg", "route", "def", "cap", "existing", "loadBackends", "uiDir", "name", "loadDev", "error", "logger", "gatherWizardModel", "root", "apiApps", "discoverApiApps", "uiApps", "discoverUiApps", "a", "apiPkgByName", "uiNames", "apiNames", "allNames", "apps", "hasUi", "path", "loadPackageConfig", "store", "readTokenStore", "buildPartChoices", "model", "choices", "app", "paths", "b", "r", "description", "buildAuditContext", "discovered", "apiPkgByApp", "routeToPkg", "auditManualPlan", "presetDef", "validatePresetProxy", "i", "WIZARD_ORCA_VAR", "asksOrca", "promptRouteSources", "prompts", "uiKeys", "sources", "key", "backend", "answer", "findRouteCap", "routePath", "recapSources", "plan", "lines", "source", "suffix", "line", "loadEnvVarsFromDoppler", "env", "names", "downloadEnvSecrets", "secrets", "value", "applyCloudEnv", "loadEnvVars", "candidate", "values", "INFRA_KIT_ENV_VAR", "runManualBranch", "selectedTargets", "t", "watch", "orca", "selection", "deriveManualPlan", "e", "issues", "issue", "echoManual", "k", "commandEcho", "eq", "equivalentCommand", "runWizardFlow", "runDevWizard", "findMonorepoRoot"]
7
7
  }
package/dist/mcp-proxy.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{y as Z}from"./chunk-PP44RGIU.js";import"./chunk-REDCBY4O.js";import{a as M}from"./chunk-MFAFQVTW.js";import{a as H,c as K,d as G,l as A,n as b,p as Q,r as Y}from"./chunk-GCOYOWGH.js";import p from"node:process";var Se=/^[a-z][a-z0-9-]*$/,we=/^[A-Z_]\w*$/i,xe=(e,r,n)=>r==="--name"?e.name!==null?"--name given twice":Se.test(n)?(e.name=n,null):`--name "${n}" must be lowercase letters, digits and hyphens`:r==="--env"||r==="--unset"?we.test(n)?M.has(n)?`${r} "${n}" is process plumbing, not a credential`:(r==="--env"?e.env.push(n):e.unset.push(n),null):`${r} "${n}" is not an environment-variable name`:`unknown flag ${r}`,Ee=(e,r)=>{let n=0;for(;n<e.length;){let o=e[n];if(o==="--")return{separator:n};if(!o.startsWith("--"))return{problem:`missing "--" before the upstream command (saw "${o}")`};let a=e[n+1];if(a===void 0||a.startsWith("--"))return{problem:`${o} needs a value`};let m=xe(r,o,a);if(m!==null)return{problem:m};n+=2}return{problem:'missing "--" before the upstream command'}},X=e=>{let r={name:null,env:[],unset:[]},n=Ee(e,r);if("problem"in n)return{ok:!1,problem:n.problem};if(r.name===null)return{ok:!1,problem:"missing --name"};if(r.env.length===0)return{ok:!1,problem:"missing --env (at least one variable to read)"};let o=e[n.separator+1];return o===void 0||o.length===0?{ok:!1,problem:'missing upstream command after "--"'}:{ok:!0,spec:{name:r.name,command:o,args:e.slice(n.separator+2),env:r.env,unset:r.unset}}};import{spawnSync as be}from"node:child_process";import C from"node:fs";import w from"node:path";import L from"node:process";var ee=e=>w.join(b(),"mcp-proxy",e),Me=e=>e.replaceAll(/[^\w.-]/g,"_").slice(0,64),ne=(e,r)=>w.join(r,`profile-${Me(e)}.json`),O=(e,r)=>{let n=w.isAbsolute(e)?[e]:(r??L.env.PATH??"").split(w.delimiter).map(o=>w.join(o,e));for(let o of n)try{let a=C.statSync(o);return`stat-${a.size}-${Math.trunc(a.mtimeMs)}`}catch{}return"unknown"},re=(e,r=["--version"],n)=>{try{let o=be(e,[...r],{encoding:"utf8",timeout:5e3,env:n==null?L.env:{...L.env,PATH:n}});if(o.status!==0)return O(e,n);let a=`${o.stdout}`.trim().split(`
2
+ import{y as Z}from"./chunk-VZAXKGEY.js";import"./chunk-REDCBY4O.js";import{a as M}from"./chunk-MFAFQVTW.js";import{a as H,c as K,d as G,l as A,n as b,p as Q,r as Y}from"./chunk-RVMKL6T4.js";import p from"node:process";var Se=/^[a-z][a-z0-9-]*$/,we=/^[A-Z_]\w*$/i,xe=(e,r,n)=>r==="--name"?e.name!==null?"--name given twice":Se.test(n)?(e.name=n,null):`--name "${n}" must be lowercase letters, digits and hyphens`:r==="--env"||r==="--unset"?we.test(n)?M.has(n)?`${r} "${n}" is process plumbing, not a credential`:(r==="--env"?e.env.push(n):e.unset.push(n),null):`${r} "${n}" is not an environment-variable name`:`unknown flag ${r}`,Ee=(e,r)=>{let n=0;for(;n<e.length;){let o=e[n];if(o==="--")return{separator:n};if(!o.startsWith("--"))return{problem:`missing "--" before the upstream command (saw "${o}")`};let a=e[n+1];if(a===void 0||a.startsWith("--"))return{problem:`${o} needs a value`};let m=xe(r,o,a);if(m!==null)return{problem:m};n+=2}return{problem:'missing "--" before the upstream command'}},X=e=>{let r={name:null,env:[],unset:[]},n=Ee(e,r);if("problem"in n)return{ok:!1,problem:n.problem};if(r.name===null)return{ok:!1,problem:"missing --name"};if(r.env.length===0)return{ok:!1,problem:"missing --env (at least one variable to read)"};let o=e[n.separator+1];return o===void 0||o.length===0?{ok:!1,problem:'missing upstream command after "--"'}:{ok:!0,spec:{name:r.name,command:o,args:e.slice(n.separator+2),env:r.env,unset:r.unset}}};import{spawnSync as be}from"node:child_process";import C from"node:fs";import w from"node:path";import L from"node:process";var ee=e=>w.join(b(),"mcp-proxy",e),Me=e=>e.replaceAll(/[^\w.-]/g,"_").slice(0,64),ne=(e,r)=>w.join(r,`profile-${Me(e)}.json`),O=(e,r)=>{let n=w.isAbsolute(e)?[e]:(r??L.env.PATH??"").split(w.delimiter).map(o=>w.join(o,e));for(let o of n)try{let a=C.statSync(o);return`stat-${a.size}-${Math.trunc(a.mtimeMs)}`}catch{}return"unknown"},re=(e,r=["--version"],n)=>{try{let o=be(e,[...r],{encoding:"utf8",timeout:5e3,env:n==null?L.env:{...L.env,PATH:n}});if(o.status!==0)return O(e,n);let a=`${o.stdout}`.trim().split(`
3
3
  `)[0]?.trim()??"";return a.length>0?a:O(e,n)}catch{return O(e,n)}},te=(e,r)=>{try{let n=JSON.parse(C.readFileSync(ne(e,r),"utf8"));return n.version!==e||!Array.isArray(n.tools)?null:{version:e,protocolVersion:typeof n.protocolVersion=="string"?n.protocolVersion:"",capabilities:n.capabilities??{},tools:n.tools}}catch{return null}},oe=(e,r)=>{try{return C.mkdirSync(r,{recursive:!0}),Q(ne(e.version,r),`${JSON.stringify(e)}
4
4
  `,384),!0}catch{return!1}};import F from"node:path";import R from"node:process";var Re="no-session",$e="IK_MCP_ENV_FILE",ke=()=>{let e=R.env[G];return e!=null&&e.length>0?e:Re},$=()=>{let e=R.env[$e];return e!=null&&e.length>0?e:F.join(b(),ke(),H)},_e=(e,r)=>{let n=e[r];if(n!=null&&n.length>0)return n;let o=R.env[r];return o!=null&&o.length>0?o:""},se=(e,r=$())=>{let n=Y(F.join(F.dirname(r),K),R.cwd())?A(r):{},o={};for(let a of e){let m=_e(n,a);if(m.length===0)return null;o[a]=m}return o},ie=(e,r=$())=>{let n=new Set(e);return Object.keys(A(r)).filter(o=>!n.has(o))};var k=(e,r)=>{let n="";e.setEncoding("utf8"),e.on("data",o=>{n+=o;let a=n.indexOf(`
5
5
  `);for(;a!==-1;){let m=n.slice(0,a);n=n.slice(a+1);let f=m.endsWith("\r")?m.slice(0,-1):m;f.trim().length>0&&r(f),a=n.indexOf(`
@@ -1,2 +1,2 @@
1
- import{b as p}from"./chunk-UTQ4KTO6.js";import{a as l}from"./chunk-WIZJQ57Q.js";import{render as z}from"ink";import T from"node:process";import{Box as u,Text as h}from"ink";import{useEffect as x,useState as F}from"react";import{jsx as f,jsxs as m}from"react/jsx-runtime";var v=r=>{let{phase:e,narration:t}=r,[i,n]=F(0);return x(()=>{let a=setInterval(()=>{n(o=>o+1)},S);return()=>{clearInterval(a)}},[]),m(u,{flexDirection:"column",children:[m(u,{children:[f(h,{color:"cyan",children:c[i%c.length]}),f(h,{dimColor:!0,children:` ${e}`})]}),t?f(h,{dimColor:!0,children:t}):null]})},c=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],S=80;import{Box as b,Static as w,Text as L}from"ink";import{Fragment as D,jsx as s,jsxs as B}from"react/jsx-runtime";var g=r=>{let{headerLines:e,logLines:t,footerLines:i}=r,n=[...e,...t],a=P(i,r.maxFooterLines);return B(D,{children:[s(w,{items:n,children:(o,d)=>s(L,{children:o},d)}),s(b,{flexDirection:"column",children:a.map((o,d)=>s(L,{children:o},d))})]})},P=(r,e)=>{if(e==null||e<1||r.length<=e)return r;let t=r.slice(0,e-1);return[...t,` \u2026 +${r.length-t.length} more`]};import{jsx as y}from"react/jsx-runtime";var R=class{renderer;renderFn;stdout;verbose;now;instance=null;finalized=!1;persistent=!1;phase="";narration="";headerLines=[];logLines=[];footerLines=[];constructor(e){this.verbose=e.verbose??!1,this.renderFn=e.render??z,this.stdout=e.stdout??l(T.stdout),this.now=e.now??(()=>new Date),this.renderer=new p({appendLog:e.appendLog,verbose:this.verbose,isTTY:!0,write:t=>{this.stdout.write(t)},now:this.now})}logFn=(e,t="info")=>{this.log(e,t)};renderBoot(){let e=y(v,{phase:this.phase,narration:this.narration});if(this.instance){this.instance.rerender(e);return}this.instance=this.renderFn(e,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}unmount(){this.instance&&(this.instance.unmount(),this.instance=null)}narrate(e){if(this.renderer.teeOnly(e,"info"),this.persistent){this.verbose&&(this.logLines.push(e),this.rerenderPersistent());return}this.finalized||(this.narration=e,this.instance&&this.renderBoot())}log(e,t="info",i={}){let n=t!=="debug"||this.verbose;if(this.persistent){i.tee!==!1&&this.renderer.teeOnly(e,t),n&&(this.logLines.push(e),this.rerenderPersistent());return}n&&this.unmount(),this.renderer.log(e,t,i)}bootStep(e){this.renderer.teeOnly(e,"info"),!this.finalized&&(this.phase=e,this.renderBoot())}ready(e){this.headerLines=this.renderer.formatHeaderLines(e),this.footerLines=this.renderer.formatFooterLines(e),this.logLines=[];for(let t of this.headerLines)this.renderer.teeOnly(t,"info");this.finalized=!0,this.persistent=!0,typeof this.stdout.on=="function"&&this.stdout.on("resize",this.onResize),this.rerenderPersistent()}maxFooterLines(){return Math.max(3,(this.stdout.rows??24)-6)}rerenderPersistent(){let e=y(g,{headerLines:this.headerLines,logLines:this.logLines,footerLines:this.footerLines,maxFooterLines:this.maxFooterLines()});if(this.instance){this.instance.rerender(e);return}this.instance=this.renderFn(e,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}onResize=()=>{this.persistent&&this.rerenderPersistent()};refresh(e){this.persistent&&(this.footerLines=this.renderer.formatFooterLines(e),this.rerenderPersistent())}dispose(){this.persistent&&(this.rerenderPersistent(),typeof this.stdout.off=="function"&&this.stdout.off("resize",this.onResize)),this.finalized=!0,this.persistent=!1,this.unmount()}};export{R as PersistentInkDevUi};
2
- //# sourceMappingURL=persistent-ink-dev-ui-D6ALK2P3.js.map
1
+ import{b as l}from"./chunk-UTQ4KTO6.js";import{a as p}from"./chunk-WIZJQ57Q.js";import{render as z}from"ink";import T from"node:process";import{Box as u,Text as h}from"ink";import{useEffect as x,useState as F}from"react";import{jsx as f,jsxs as m}from"react/jsx-runtime";var v=r=>{let{phase:e,narration:t}=r,[i,n]=F(0);return x(()=>{let a=setInterval(()=>{n(o=>o+1)},S);return()=>{clearInterval(a)}},[]),m(u,{flexDirection:"column",children:[m(u,{children:[f(h,{color:"cyan",children:c[i%c.length]}),f(h,{dimColor:!0,children:` ${e}`})]}),t?f(h,{dimColor:!0,children:t}):null]})},c=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],S=80;import{Box as b,Static as w,Text as L}from"ink";import{Fragment as D,jsx as s,jsxs as B}from"react/jsx-runtime";var g=r=>{let{headerLines:e,logLines:t,footerLines:i}=r,n=[...e,...t],a=P(i,r.maxFooterLines);return B(D,{children:[s(w,{items:n,children:(o,d)=>s(L,{children:o},d)}),s(b,{flexDirection:"column",children:a.map((o,d)=>s(L,{children:o},d))})]})},P=(r,e)=>{if(e==null||e<1||r.length<=e)return r;let t=r.slice(0,e-1);return[...t,` \u2026 +${r.length-t.length} more`]};import{jsx as y}from"react/jsx-runtime";var R=class{renderer;renderFn;stdout;verbose;now;instance=null;finalized=!1;persistent=!1;phase="";narration="";headerLines=[];logLines=[];footerLines=[];constructor(e){this.verbose=e.verbose??!1,this.renderFn=e.render??z,this.stdout=e.stdout??p(T.stdout),this.now=e.now??(()=>new Date),this.renderer=new l({appendLog:e.appendLog,verbose:this.verbose,isTTY:!0,write:t=>{this.stdout.write(t)},now:this.now})}logFn=(e,t="info")=>{this.log(e,t)};renderBoot(){let e=y(v,{phase:this.phase,narration:this.narration});if(this.instance){this.instance.rerender(e);return}this.instance=this.renderFn(e,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}unmount(){this.instance&&(this.instance.unmount(),this.instance=null)}narrate(e){if(this.renderer.teeOnly(e,"info"),this.persistent){this.verbose&&(this.logLines.push(e),this.rerenderPersistent());return}this.finalized||(this.narration=e,this.instance&&this.renderBoot())}log(e,t="info",i={}){let n=t!=="debug"||this.verbose;if(this.persistent){i.tee!==!1&&this.renderer.teeOnly(e,t),n&&(this.logLines.push(e),this.rerenderPersistent());return}n&&this.unmount(),this.renderer.log(e,t,i)}bootStep(e){this.renderer.teeOnly(e,"info"),!this.finalized&&(this.phase=e,this.renderBoot())}ready(e){this.headerLines=this.renderer.formatHeaderLines(e),this.footerLines=this.renderer.formatFooterLines(e),this.logLines=[];for(let t of this.headerLines)this.renderer.teeOnly(t,"info");this.finalized=!0,this.persistent=!0,typeof this.stdout.on=="function"&&this.stdout.on("resize",this.onResize),this.rerenderPersistent()}maxFooterLines(){return Math.max(3,(this.stdout.rows??24)-6)}rerenderPersistent(){let e=y(g,{headerLines:this.headerLines,logLines:this.logLines,footerLines:this.footerLines,maxFooterLines:this.maxFooterLines()});if(this.instance){this.instance.rerender(e);return}this.instance=this.renderFn(e,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}onResize=()=>{this.persistent&&this.rerenderPersistent()};refresh(e){this.persistent&&(this.footerLines=this.renderer.formatFooterLines(e),this.rerenderPersistent())}dispose(){this.persistent&&(this.rerenderPersistent(),typeof this.stdout.off=="function"&&this.stdout.off("resize",this.onResize)),this.finalized=!0,this.persistent=!1,this.unmount()}};export{R as PersistentInkDevUi};
2
+ //# sourceMappingURL=persistent-ink-dev-ui-HHTHZ24S.js.map
@@ -0,0 +1,2 @@
1
+ import{g as a,h as b,i as c,j as d,k as e,l as f,m as g,n as h}from"./chunk-K5KNXRTF.js";import"./chunk-S23ECFH4.js";import"./chunk-HMXEV7ZN.js";import"./chunk-MFAFQVTW.js";import"./chunk-YSR7UND3.js";import"./chunk-RVMKL6T4.js";export{a as DOPPLER_MAX_OUTPUT_BYTES,g as assertDopplerOutputSize,e as assertTokenScope,b as buildDopplerChildEnv,d as downloadDopplerSecrets,c as downloadEnvSecrets,h as parseDopplerSecretsJson,f as translateDopplerDownloadError};
2
+ //# sourceMappingURL=secrets-download-DXRM5M6F.js.map
@@ -1,2 +1,2 @@
1
- import{t as i}from"./chunk-QS7RKE5P.js";import"./chunk-REDCBY4O.js";import{a as t}from"./chunk-T5WMUJID.js";import"./chunk-GCOYOWGH.js";import a from"node:fs";import o from"node:process";import{fileURLToPath as s}from"node:url";var m=r=>{let n=r.indexOf("--parent-pid");if(n===-1)return;let e=Number(r[n+1]);return Number.isInteger(e)&&e>0?e:void 0},p=()=>a.realpathSync(s(new URL("./cli.js",import.meta.url))),d=async()=>{await i(t.version,{selfRealPath:p(),parentPid:m(o.argv)})};d().catch(()=>{o.exitCode=0});
1
+ import{t as i}from"./chunk-HIGOL4H4.js";import"./chunk-REDCBY4O.js";import{a as t}from"./chunk-YSR7UND3.js";import"./chunk-RVMKL6T4.js";import a from"node:fs";import o from"node:process";import{fileURLToPath as s}from"node:url";var m=r=>{let n=r.indexOf("--parent-pid");if(n===-1)return;let e=Number(r[n+1]);return Number.isInteger(e)&&e>0?e:void 0},p=()=>a.realpathSync(s(new URL("./cli.js",import.meta.url))),d=async()=>{await i(t.version,{selfRealPath:p(),parentPid:m(o.argv)})};d().catch(()=>{o.exitCode=0});
2
2
  //# sourceMappingURL=update-check.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.40.0",
4
+ "version": "0.42.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ArthurSaenz/infra-kit.git",
@@ -20,29 +20,28 @@
20
20
  "node": ">=24.x"
21
21
  },
22
22
  "dependencies": {
23
- "@aws-lambda-powertools/logger": "^2.35.0",
24
- "@inquirer/checkbox": "^5.2.6",
25
- "@inquirer/confirm": "^6.3.3",
26
- "@inquirer/input": "^5.1.7",
27
- "@inquirer/password": "^5.2.3",
28
- "@inquirer/select": "^5.2.6",
29
- "@slip-stream-kit/config": "^0.40.0",
23
+ "@inquirer/checkbox": "^5.2.8",
24
+ "@inquirer/confirm": "^6.3.5",
25
+ "@inquirer/input": "^5.1.9",
26
+ "@inquirer/password": "^5.2.5",
27
+ "@inquirer/select": "^5.2.8",
28
+ "@slip-stream-kit/config": "^0.42.0",
30
29
  "chalk": "^6.0.1",
31
- "chokidar": "^5.0.0",
30
+ "chokidar": "^6.0.0",
32
31
  "commander": "^15.0.0",
33
- "fastify": "^5.12.5",
32
+ "fastify": "^5.12.7",
34
33
  "ink": "^8.0.0",
35
34
  "pino": "^10.4.0",
36
35
  "pino-pretty": "^13.2.0",
37
- "portless": "^0.15.7",
38
- "react": "19.3.0",
36
+ "portless": "^0.15.12",
37
+ "react": "19.3.1",
39
38
  "yaml": "^2.9.1",
40
- "zod": "^4.6.5",
39
+ "zod": "^4.6.7",
41
40
  "zx": "^8.8.5"
42
41
  },
43
42
  "devDependencies": {
44
- "@slip-stream-kit/eslint-plugin": "0.40.0",
45
- "@types/aws-lambda": "^8.10.163",
43
+ "@slip-stream-kit/eslint-plugin": "0.42.0",
44
+ "@types/aws-lambda": "^8.10.164",
46
45
  "@types/react": "19.3.0",
47
46
  "@wl/eslint-config": "0.1.0",
48
47
  "@wl/vitest-config": "0.1.0",
package/readme.md CHANGED
@@ -45,7 +45,7 @@ Code loads is self-contained and needs no config reader at session start.
45
45
  The split is by level: one `infra-kit.json` per repo, one `infra-kit.config.ts` per workspace package. The repo root has no `infra-kit.config.ts`.
46
46
 
47
47
  **infra-kit.json** (repo)
48
- Repo-wide settings: environment providers (e.g., Doppler), worktrees, MCP proxies, and the `audit` block `infra-kit audit --root` checks the root against. Layered with personal overrides from `~/.infra-kit/infra-kit.json` and `~/.infra-kit/projects/<repo>/infra-kit.json`; `aliasRepo`, `mcp`, `vendorSource`, `vendorTarget` and `audit` are accepted only in the committed file. A `vendorSource.copy` entry with a `module` name reaches only the targets whose `vendorTarget.modules` lists it (no `vendorTarget` block: every module); dropping a module deletes its files under `vendor/` and leaves a path outside it to the target, and `vendor sync` fails the target while one of its `package.json` files still depends on a package the drop would delete. `aliasRepo` (a DNS label, e.g. `"trvl"`) replaces the main checkout's directory name in the dev host `<release>.<package>.<repo>.localhost`. A leftover root `infra-kit.config.ts` fails `audit --root`; `infra-kit audit --fix --root` moves its rules into `audit` and deletes it.
48
+ Repo-wide settings: environment providers (e.g., Doppler), worktrees, MCP proxies, and the `audit` block `infra-kit audit --root` checks the root against. Layered with personal overrides from `~/.infra-kit/infra-kit.json` and `~/.infra-kit/projects/<repo>/infra-kit.json`; `aliasRepo`, `mcp`, `vendorSource`, `vendorTarget` and `audit` are accepted only in the committed file. A `vendorSource.copy` entry with a `module` name reaches only the targets whose `vendorTarget.modules` lists it (no `vendorTarget` block: every module); dropping a module deletes its files under `vendor/` and leaves a path outside it to the target, and `vendor sync` fails the target while one of its `package.json` files still depends on a package the drop would delete. A mirrored file keeps the target's own lines between `# infra-kit:local-start` and `# infra-kit:local-end` marker lines (any comment leader): `vendor sync` writes the source with that block in place of the source's own block (or appended), and `vendor check` leaves the block out of its checksums. `aliasRepo` (a DNS label, e.g. `"trvl"`) replaces the main checkout's directory name in the dev host `<release>.<package>.<repo>.localhost`. A leftover root `infra-kit.config.ts` fails `audit --root`; `infra-kit audit --fix --root` moves its rules into `audit` and deletes it.
49
49
 
50
50
  **infra-kit.config.ts** (package)
51
51
  Strict schema file that declares the package's audit rules (`requiredScripts`, `requiredFiles`), an optional `type` override (`frontend` | `backend` | `lib` | `e2e` | `mobile`) used by the `agent-guidance` check, and the `dev.proxy` / `e2e` blocks read by the Vite plugin, `infra-kit dev` and the Playwright helper.
@@ -1,12 +0,0 @@
1
- import{a as N,e as ie,h as O,i as ae,j as u}from"./chunk-LSZFL3CV.js";import{P as a,S as se,X as m,d as f,e as b,f as x,fa as g,h as S,i as ne,w as A}from"./chunk-SIEUC4SP.js";import{a as G,c as B,d as P,f as Y,g as Q,h as X,i as ee,j as te,o as re,p as L,q as oe}from"./chunk-GCOYOWGH.js";import Ye from"@inquirer/select";import{readRepoIdentity as Qe}from"@slip-stream-kit/config/internal";import{Buffer as Xe}from"node:buffer";import et from"node:fs";import U from"node:path";import C from"node:process";import{z as d}from"zod";import{$ as K}from"zx";var be="Could not find requested project",xe="Could not find requested config",ce="Invalid Auth token",le="does not have access to requested config",D=e=>e.includes(ce)||e.includes(le)?"auth":e.includes(be)?"project":e.includes(xe)?"config":"unknown",R=e=>e.includes(le)?"mis-scoped":e.includes(ce)?"revoked":null;var pe=e=>[`Doppler rejected the service token for env "${e}" \u2014 it is invalid, revoked, or scoped to a different config.`,`Fix: run \`infra-kit env-token-set ${e}\` with a token scoped to that config.`,"CI / agents: set INFRA_KIT_ENV_TOKEN to a token scoped to that config."].join(`
2
- `),v=class extends O{authKind;constructor(t,r=null,o){super(o??pe(t),t),this.name="DopplerAuthError",this.authKind=r}};var M=({kind:e,project:t,config:r,available:o})=>{let n=e==="project"?[`Doppler project "${t}" not found (set in infra-kit.json \u2192 envManagement.config.name).`]:[`Doppler config "${r}" not found in project "${t}".`];if(o&&o.length>0){let s=e==="project"?"Available projects":`Available configs in "${t}"`;n.push(`${s}: ${o.join(", ")}.`)}return n.push(e==="project"?"Fix: update envManagement.config.name to an existing project, or create it in Doppler.":"Fix: pass an existing config, or create it in Doppler."),n.join(`
3
- `)};import{$ as j}from"zx";var Se=5e3,Ne="DOPPLER_CONFIG",h=async()=>{let{envManagement:e}=await g();return e.config.name},Oe=async({childEnv:e,project:t,config:r})=>{let o=j.quiet;j.quiet=!0;try{let s=(await j({env:e})`doppler secrets get ${Ne} --plain --project ${t} --config ${r}`.timeout(Se)).stdout.trim();return s!==r?{outcome:"mis-scoped",scopedTo:s}:{outcome:"valid",scopedTo:s}}catch(n){return{outcome:je(n)}}finally{j.quiet=o}},je=e=>{let t=S(e)??(e instanceof Error?e.message:String(e));return D(t)!=="auth"?"unreachable":R(t)??"unreachable"};import Fe from"node:process";var l="INFRA_KIT_ENV_TOKEN",V=async e=>{let t=Fe.env[l];if(t)return{token:t,source:"env"};let o=(await u())?.envs[e];if(o)return{token:o,source:"store"};throw new O(Ie(e,await ae()),e)},Ie=(e,t)=>[`No Doppler service token for env "${e}".`,`Mint one at https://dashboard.doppler.com \u2014 this project \u2192 config "${e}" \u2192 Access tab \u2192 "Generate Service Token".`,`Fix: run \`infra-kit env-token-set ${e}\` and paste it.`,`It is stored in ${se(t)} (mode 0600, never in the repo).`,`CI / agents: set ${l} instead \u2014 it takes precedence and needs no home config.`].join(`
4
- `);import ke from"node:process";import{z as Ee}from"zod";import We from"node:process";import{z as p}from"zod";var _=async()=>{try{return(await g()).defaultEnv??null}catch{return null}};var ue=["prod"],E={allowed:!1,reason:"disallow"},w=e=>ue.includes(e),$e=["dev","stage"],Ke=e=>$e.includes(e)||w(e),Ce=(e,t=E)=>t.allowed?e:e.filter(r=>!w(r)),Le=(e,t,r=E)=>{if(!r.allowed&&w(e))throw r.reason==="agent-blocked"?new m({status:"refused",env:e,agentMode:f.source},2,{operation:t,remediation:'a human runs it from their own terminal \u2014 this project sets `protectedEnvs: "cli-only"`',stderrExcerpt:`"${e}" is withheld from agents in this project (protectedEnvs: "cli-only")`}):new ne(void 0,{operation:t,remediation:`use \`infra-kit release deliver\` \u2014 it merges the release to main, opens the RC PR, deploys from main, and releases the Jira version. To allow ad-hoc deploys to "${e}" in this project, set \`protectedEnvs\` to "allow" or "cli-only" in infra-kit.json`,stderrExcerpt:`"${e}" is delivered, not deployed ad-hoc`})};var Me=async()=>{let e;try{e=(await g()).protectedEnvs??"disallow"}catch{return E}return e==="disallow"?E:e==="cli-only"&&b()?{allowed:!1,reason:"agent-blocked"}:{allowed:!0,reason:"allowed"}};var Ve=e=>{let{env:t,branch:r}=e;w(t)&&a.warn([`\u26A0\uFE0F "${t}" is a delivery target \u2014 this project allows dispatching to it directly, but this is NOT the delivery flow.`,`Dispatching from "${r}" ships ${t} from a ref that is not merged to main, and skips:`," \u2022 the squash-merge of the release branch into main"," \u2022 the RC pull request"," \u2022 the Jira fix-version release","Run `infra-kit release deliver` instead if you want those."].join(`
5
- `))};import de from"node:fs/promises";import z from"node:path";import ze from"yaml";var fe=".github/workflows",qe=async e=>{let t=await A();return me(z.resolve(t,fe,e))},q=async()=>{let e=await A(),t=z.resolve(e,fe),r;try{r=await de.readdir(t)}catch{return[]}let o=r.filter(s=>s.endsWith(".yml")||s.endsWith(".yaml")).sort(),n=await Promise.all(o.map(s=>me(z.resolve(t,s))));return[...new Set(n.flat())]},me=async e=>{let t;try{t=ze.parse(await de.readFile(e,"utf-8"))}catch{return[]}let r=k(t,"on")??k(t,"true"),o=k(k(k(k(r,"workflow_dispatch"),"inputs"),"environment"),"options");return Array.isArray(o)?o.filter(n=>typeof n=="string"&&n.length>0):[]},k=(e,t)=>{if(!(typeof e!="object"||e===null||Array.isArray(e)))return e[t]};var y=async()=>{let e=await q(),t=await u(),r=e.map(s=>({env:s,source:"gh-workflow"})),o=new Set(e),n=Object.keys(t?.envs??{}).filter(s=>!o.has(s)).sort().map(s=>({env:s,source:"token-only"}));return[...r,...n]},W=async()=>(await y()).map(t=>t.env);var F=e=>[{type:"text",text:e}],I=e=>e;var $=(e,t,r)=>e.map(({env:o,source:n})=>({env:o,source:n,hasToken:!!(r||t[o])})),ge=["Environment","Source","Token"],Je=e=>{let t=e.map(s=>[s.env,s.source,s.hasToken?"set":"not set"]),r=ge.map((s,i)=>Math.max(s.length,...t.map(c=>(c[i]??"").length))),o=s=>s.map((i,c)=>i.padEnd(r[c]??0)).join(" | ").trimEnd(),n=r.map(s=>"\u2500".repeat(s)).join("\u2500\u253C\u2500");return["Environments:","",o(ge),n,...t.map(o)]},he=async()=>{let e=await h(),t=await y(),r=await u(),o=We.env[l],n=await _(),s=$(t,r?.envs??{},o);if(a.info(`Doppler project: ${e}`),a.info(`Default env: ${n??"none \u2014 set one with `infra-kit config-set defaultEnv <env>`"}
6
- `),s.length===0)return a.info("No environments found."),a.info(" Declare them in a workflow's `workflow_dispatch` `environment.options`,"),a.info(" or add a token for one manually."),ve({project:e,defaultEnv:n,envs:s});for(let i of Je(s))a.info(i);return ve({project:e,defaultEnv:n,envs:s})},ve=({project:e,defaultEnv:t,envs:r})=>{let o={project:e,defaultEnv:t,configs:r.map(n=>n.env),tokens:r};return{content:F(JSON.stringify(o,null,2)),structuredContent:o}},Ze=I({name:"env-list",description:"List the environments this project has. The list is the union of two local sources: every environment declared in a GitHub workflow's `workflow_dispatch` `environment.options` (source `gh-workflow`), and every environment the local token store holds a token for (source `token-only`). Not a live fetch from Doppler \u2014 a Doppler service token is config-scoped and cannot enumerate sibling configs. Also returns the Doppler project name, and reports per environment whether a service token resolves locally (from the token store or INFRA_KIT_ENV_TOKEN) \u2014 presence only, never the token value, and never a live Doppler probe. Read-only.",inputSchema:{},outputSchema:{project:p.string().describe("Detected Doppler project name"),defaultEnv:p.string().nullable().describe('The configured "defaultEnv" \u2014 what env-load takes when no config is named; null when unset'),configs:p.array(p.string()).describe("Every environment name known to this project"),tokens:p.array(p.object({env:p.string().describe("Environment / Doppler config name"),source:p.enum(["gh-workflow","token-only"]).describe("Where we learned this environment exists \u2014 a workflow declaration, or a token we hold"),hasToken:p.boolean().describe("Whether a service token resolves locally for this environment")})).describe("Per-environment token presence \u2014 never the token value")},handler:he});var we=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),Ue=async e=>{try{return await e}catch{return[]}},He=async e=>{let t=await u().catch(()=>null);return $(e,t?.envs??{},ke.env[l]).filter(r=>!r.hasToken).map(r=>r.env)},J=()=>({get message(){return`Choose the environment to load into terminal session ${ke.env[P]??`UNKNOWN \u2014 ${P} is not set, the load will fail`}. Picking one LOADS it; there is no further prompt.`},isFormable:e=>we(e)&&(e.config===void 0||e.config===""),buildRequestedSchema:async()=>{let e=await Ue(y());if(e.length===0)return a.info({msg:"Tool execution form options empty: env-load"}),null;let t=e.map(o=>o.env),r=await He(e);return Ee.object({config:Ee.enum(t).describe(`The environment to load. ${r.length===0?"A service token is stored for every one of them.":`NO stored token for: ${r.join(", ")} \u2014 choosing one of those fails until you run \`infra-kit env-token-set <env>\`.`} The list is what env-list knows: workflow-declared environments first, then token-only ones.`)})},toArgs:(e,t)=>{let{config:r}=e;return typeof r!="string"||r.length===0?null:{...we(t)?t:{},config:r}}});import{z as Ge}from"zod";var Be=async e=>{try{return await e.provider.buildRequestedSchema(e.params)}catch(t){return a.debug({err:t,operation:e.operation},"argument form builder threw; refusing without choices"),null}},ye=async e=>{if(!x()||!e.provider.isFormable(e.params))return;let t=await Be(e),r=typeof e.params=="object"&&e.params!==null?e.params:{},o=(t===null?[]:Object.keys(t.shape)).filter(i=>r[i]===void 0||r[i]===""),n=typeof e.argument=="string"?e.argument:e.argument(o),s=t===null?void 0:Ge.toJSONSchema(t);throw new m({status:"argument_required",argument:n,...s===void 0?{}:{choices:s},agentMode:f.source},2,{operation:e.operation,remediation:s===void 0?`pass --${n} on the re-run \u2014 nothing could be listed to choose from`:`ask the human to pick from "choices", then pass --${n} on the re-run`,stderrExcerpt:`--${n} was not given and there is no human to ask`})};var tt="DOPPLER_CONFIG",Te=new Set(["DOPPLER_TOKEN",l]),Pe=!1,rt=e=>{e.length===0||Pe||(Pe=!0,a.warn(`infra-kit: not exporting ${e.join(", ")} from Doppler into your shell \u2014 a service token must not be sourced into every process.`))},ot=({pairs:e,config:t,project:r,projectRoot:o,loadedAt:n})=>{let s=e.filter(([i])=>!Te.has(i));return rt(e.filter(([i])=>Te.has(i)).map(([i])=>i)),["set -a",...s.map(([i,c])=>`${i}=${T(c)}`),`${Y}=${T(t)}`,`${Q}=${T(t)}`,`${X}=${T(r)}`,`${ee}=${T(o)}`,`${te}=${T(n)}`,"set +a"]},nt=async()=>{try{return await A()}catch{return""}},st=(e,t=C.cwd())=>{if(e==="")return[U.resolve(t)];let r=Qe(e).root??e;return[...new Set([r,`${r}${oe}`,e])]},it=async({config:e})=>{let t=await h(),r=await nt(),o=await Re(t,e),n=new Date().toISOString(),i=`${ot({pairs:o,config:e,project:t,projectRoot:r,loadedAt:n}).join(`
7
- `)}
8
- `,c=re(),H=U.resolve(c,G);et.mkdirSync(c,{recursive:!0,mode:448});let _e=st(r);return L(U.resolve(c,B),`${_e.join(`
9
- `)}
10
- `,384),L(H,i,384),{filePath:H,variableCount:o.length,project:t,config:e}},De=J(),at=async e=>{let{config:t}=e,r=t?null:await _();if(!t&&r&&x())return Z(r,"default");if(await ye({provider:De,params:e,operation:"env-load",argument:"config"}),t)return Z(t,"argument");if(b())throw new m({status:"argument_required",argument:"config",agentMode:f.source},2,{operation:"env-load",remediation:'call env-list, ask the human which environment, and re-call env-load with "config"'});let o=await W();N.setInteractive();let n=await ie(s=>Ye({message:"Select environment config",choices:ct(o,r),...r?{default:r}:{}},s),{output:C.stderr,whenHeadless:"refuse"});return Z(n,"picker")},ct=(e,t)=>(t&&!e.includes(t)?[t,...e]:e).map(o=>({name:o===t?`${o} (default)`:o,value:o})),Z=async(e,t)=>{N.addOption("--config",e);let r=await it({config:e});N.print();let o={filePath:r.filePath,variableCount:r.variableCount,project:r.project,config:r.config,configSource:t,sessionId:C.env[P]??null};return{content:F(JSON.stringify(o,null,2)),structuredContent:o}},Ae=1024*1024,lt=3e4,pt=["DOPPLER_TOKEN","DOPPLER_PROJECT","DOPPLER_CONFIG"],ut=(e,t=C.env)=>{let r={...t};for(let o of pt)delete r[o];return r.DOPPLER_TOKEN=e,r},io=async e=>new Map(await Re(await h(),e)),Re=async(e,t)=>{let{token:r}=await V(t),o=ut(r),n=K.quiet;K.quiet=!0;try{let s;try{s=await K({env:o})`doppler secrets download --no-file --format json --project ${e} --config ${t}`.timeout(lt)}catch(c){throw ft(c,e,t)}mt(s.stdout);let i=vt(s.stdout);return dt(i,t),i}finally{K.quiet=n}},dt=(e,t)=>{let r=e.find(([o])=>o===tt)?.[1];if(!(r===void 0||r===t))throw new v(t,null,`Doppler returned secrets for config "${r}" but "${t}" was requested \u2014 the service token for env "${t}" is scoped to the wrong config. Refusing to load another environment's secrets.
11
- Fix: run \`infra-kit env-token-set ${t}\` with a token scoped to "${t}".`)},ft=(e,t,r)=>{let o=S(e)??(e instanceof Error?e.message:String(e)),n=D(o);return n==="auth"?new v(r,R(o)):n==="unknown"?e instanceof Error?e:new Error(String(e)):new Error(M({kind:n,project:t,config:r,available:null}))},mt=e=>{let t=Xe.byteLength(e,"utf-8");if(t>Ae)throw new Error(`doppler returned unexpectedly large output (${t} bytes > ${Ae}) \u2014 refusing to write to disk`)},T=e=>`'${e.replaceAll("'","'\\''")}'`,gt=/^[A-Z_]\w*$/i,vt=e=>{let t;try{t=JSON.parse(e)}catch{throw new Error(`doppler returned non-JSON output for env-load (got: ${JSON.stringify(e.slice(0,80))})`)}if(t===null||typeof t!="object"||Array.isArray(t))throw new Error("doppler returned unexpected JSON shape for env-load (expected an object of KEY: value pairs)");let r=[];for(let[o,n]of Object.entries(t)){if(!gt.test(o))throw new Error(`doppler returned an invalid env var name for env-load (got: ${JSON.stringify(o.slice(0,80))})`);if(typeof n!="string")throw new TypeError(`doppler returned a non-string value for "${o}" (got ${typeof n})`);r.push([o,n])}if(r.length===0)throw new Error("doppler returned empty output for env-load");return r},ao=I({name:"env-load",description:'Download the env vars for a Doppler config and write them to a temporary shell script. Does NOT mutate the calling process \u2014 returns the path to a script that must be sourced ("source <filePath>") for the vars to take effect. The infra-kit shell wrapper auto-sources; direct MCP callers must handle sourcing themselves or surface filePath to the user. This server picks the file up on its next tool call, so a tool that needs the variables can be called right after. Omit "config" and this server offers the human a form listing every environment env-list knows, token-less ones marked; a client that cannot render one gets a refusal naming the missing field \u2014 call env-list and ask the human, never guess. When the project config sets "defaultEnv", omitting "config" headless loads that environment instead of refusing, and "configSource" reports "default".',inputSchema:{config:d.string().optional().describe('Doppler config / environment name to load (e.g. "dev", "arthur"). Omit it to load the configured "defaultEnv", or, with none set, to have the human choose from a form.')},formProvider:De,outputSchema:{filePath:d.string().describe("Path to the file that must be sourced to apply variables"),variableCount:d.number().describe("Number of variables loaded"),project:d.string().describe("Doppler project name"),config:d.string().describe("Doppler config name"),configSource:d.enum(["argument","default","picker"]).describe('How the config was chosen: passed as "config", the configured "defaultEnv", or the terminal picker'),sessionId:d.string().nullable().describe("The INFRA_KIT_SESSION the file was written for; null when the process had no session")},handler:at});export{F as a,I as b,D as c,R as d,h as e,Oe as f,l as g,V as h,ue as i,w as j,Ke as k,Ce as l,Le as m,Me as n,Ve as o,qe as p,y as q,W as r,he as s,Ze as t,ye as u,ot as v,st as w,it as x,at as y,ct as z,Ae as A,ut as B,io as C,dt as D,ft as E,mt as F,T as G,vt as H,ao as I};
12
- //# sourceMappingURL=chunk-KTWOAA5C.js.map