infra-kit 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot-OA6HGGO6.js +2 -0
- package/dist/boot-OA6HGGO6.js.map +7 -0
- package/dist/{chunk-KAWDTA5H.js → chunk-2QJAIOPI.js} +2 -2
- package/dist/chunk-ADCSGQAU.js +2 -0
- package/dist/chunk-ADCSGQAU.js.map +7 -0
- package/dist/chunk-DWZSB6O6.js +4 -0
- package/dist/chunk-DWZSB6O6.js.map +7 -0
- package/dist/chunk-LJVPQQ75.js +2 -0
- package/dist/{chunk-UYHP4BUC.js.map → chunk-LJVPQQ75.js.map} +2 -2
- package/dist/chunk-M5FC2MOJ.js +220 -0
- package/dist/chunk-M5FC2MOJ.js.map +7 -0
- package/dist/chunk-VISEADOF.js +7 -0
- package/dist/{chunk-7DL27USH.js.map → chunk-VISEADOF.js.map} +4 -4
- package/dist/cli.js +10 -10
- package/dist/cli.js.map +3 -3
- package/dist/dev-server.js +32 -28
- package/dist/dev-server.js.map +3 -3
- package/dist/{dev-wizard-run-KIOSJM4D.js → dev-wizard-run-4SVW4W3I.js} +2 -2
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +3 -3
- package/dist/update-check.js +1 -1
- package/package.json +7 -6
- package/readme.md +106 -1
- package/dist/boot-HP5ORQEX.js +0 -2
- package/dist/boot-HP5ORQEX.js.map +0 -7
- package/dist/chunk-7DL27USH.js +0 -7
- package/dist/chunk-PZU6BLQR.js +0 -4
- package/dist/chunk-PZU6BLQR.js.map +0 -7
- package/dist/chunk-UYHP4BUC.js +0 -2
- package/dist/chunk-XJS5R2JU.js +0 -231
- package/dist/chunk-XJS5R2JU.js.map +0 -7
- package/dist/chunk-XTRPE4SX.js +0 -2
- package/dist/chunk-XTRPE4SX.js.map +0 -7
- /package/dist/{chunk-KAWDTA5H.js.map → chunk-2QJAIOPI.js.map} +0 -0
- /package/dist/{dev-wizard-run-KIOSJM4D.js.map → dev-wizard-run-4SVW4W3I.js.map} +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{B as f,W as E,d as x}from"./chunk-DWZSB6O6.js";import{q as w}from"./chunk-CHETBZ6M.js";import{a as k,b as S}from"./chunk-X46DP23W.js";import p from"node:fs/promises";import c from"node:path";import{z as s}from"zod";var i=class extends Error{env;constructor(e,o=null){super(e),this.name="EnvAuthError",this.env=o}},K=t=>t instanceof i;import b from"node:fs/promises";import $ from"node:os";var C=async t=>{try{return await b.access(t),!0}catch{return!1}},l=t=>{let e=$.homedir();return t.startsWith(e)?`~${t.slice(e.length)}`:t};var I="tokens.json",d=1,y=384,T=448,N=s.object({version:s.literal(d).default(d),envs:s.record(s.string().min(1),s.string().min(1))}).strict(),u=async()=>{let t=await E();return c.join(c.dirname(t.userProject),I)},g=async()=>{let t=await u(),e;try{e=await p.readFile(t,"utf-8")}catch{return null}let o;try{o=JSON.parse(e)}catch(n){throw new i(`Invalid JSON in the token store at ${l(t)}: ${n.message}
|
|
2
|
+
Fix or delete the file, then re-add the token with \`infra-kit env-token-set <env>\`.`)}let r=N.safeParse(o);if(!r.success)throw new i(`Invalid token store at ${l(t)}: ${s.prettifyError(r.error)}
|
|
3
|
+
Fix or delete the file, then re-add the token with \`infra-kit env-token-set <env>\`.`);return r.data},h=async t=>{let e=await u(),o=c.dirname(e),r=c.dirname(o),n=c.dirname(r);for(let a of[n,r,o])await p.mkdir(a,{recursive:!0,mode:T}),await p.chmod(a,T);w(e,`${JSON.stringify(t,null,2)}
|
|
4
|
+
`,y),await p.chmod(e,y)},_=async(t,e)=>{let o=await g();await h({version:d,envs:{...o?.envs,[t]:e}})},j=async t=>{let e=await g();if(!e||!(t in e.envs))return;let o={...e.envs};delete o[t],await h({version:d,envs:o})};var D=()=>{let t="",e=[],o=!1;return{start(r){t=r,e=[],o=!1},setInteractive(){o=!0},addOption(r,n){e.push({flag:r,value:n})},formatOptions(){return e.map(r=>typeof r.value=="boolean"?r.value?r.flag:"":Array.isArray(r.value)?`${r.flag} "${r.value.join(", ")}"`:`${r.flag} "${r.value}"`).filter(Boolean).join(" ")},snapshot(){return e.length===0?null:{formattedOptions:this.formatOptions()}},print(){!o||e.length===0||!t||f.info(`\u{1F4DF} Equivalent command:
|
|
5
|
+
pnpm exec infra-kit ${t} ${this.formatOptions()}
|
|
6
|
+
`)},reset(){t="",e=[],o=!1}}},v=D();import O from"node:process";var A=27,P=async(t,e)=>{let o=new AbortController,r={...e,signal:o.signal};if(x()||!O.stdin.isTTY)return t(r);let n=O.stdin,a=m=>{m.length===1&&m[0]===A&&o.abort()};k();try{let m=t(r);return n.isPaused()&&n.resume(),n.on("data",a),await m}finally{n.removeListener("data",a),S()}};import F from"@inquirer/confirm";import R from"node:process";var J=async(t,e)=>{let o=t?!0:await P(r=>F({message:e},r));t||v.setInteractive(),o||(f.info("Operation cancelled. Exiting..."),R.exit(0))};export{i as a,K as b,C as c,l as d,u as e,g as f,_ as g,j as h,v as i,P as j,J as k};
|
|
7
|
+
//# sourceMappingURL=chunk-VISEADOF.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/lib/env-tokens/env-tokens.ts", "../src/lib/errors/env-auth-error.ts", "../src/lib/path-display/path-display.ts", "../src/lib/command-echo/command-echo.ts", "../src/lib/prompts/escapable-context.ts", "../src/lib/
|
|
4
|
-
"sourcesContent": ["// DEFAULT import, deliberately \u2014 the same reason config-bootstrap.ts documents: the module is used as\n// `fs.readFile(...)` / `fs.mkdir(...)` so every call is a property lookup on the live module object,\n// which is what `vi.spyOn(fs, \u2026)` patches. A named import (`import { readFile }`) is bound at import\n// time and the spy silently does NOT intercept it \u2014 the assertion goes green while the real call\n// still hits disk. Do not \"modernize\" this import.\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { z } from 'zod'\n\nimport { atomicWriteFileSync } from 'src/lib/constants'\nimport { EnvAuthError } from 'src/lib/errors/env-auth-error'\nimport { getInfraKitConfigPaths } from 'src/lib/infra-kit-config'\nimport { tildify } from 'src/lib/path-display'\n\n/**\n * Basename of the token store. A SIBLING of the layer-3 `infra-kit.json`, never a key inside it:\n * `getInfraKitConfig` only ever globs the three hardcoded `infra-kit.json` paths and its schema is\n * `.strict()`, so an unknown key throws at the LAYER parse and bricks every command on every pinned\n * version. A sibling file is structurally invisible to that loader \u2014 `config-bootstrap.ts` already\n * relies on exactly this for its `.example.jsonc`. That invisibility is the blast-radius guarantee:\n * a corrupt `tokens.json` can only break the readers below, never `worktrees-list`.\n */\nconst TOKEN_STORE_FILE = 'tokens.json'\n\n/** Schema version of the on-disk store. Bump only alongside a migration. */\nconst TOKEN_STORE_VERSION = 1\n\n/** Mode of `tokens.json` itself \u2014 owner read/write only. It holds live credentials. */\nconst TOKEN_FILE_MODE = 0o600\n\n/** Mode of every directory on the way to the store \u2014 owner-only traversal. */\nconst TOKEN_DIR_MODE = 0o700\n\n/**\n * The store's OWN schema: `{ version?: 1, envs: { <env>: <token> } }`. Deliberately NOT derived from\n * `infraKitConfigObject`: this file is not a merge layer, so `.strict()` here is free (a typo is a\n * loud error in three readers, not a bricked CLI).\n *\n * HAND-AUTHORABILITY IS THE CONSTRAINT THIS SHAPE IS CHOSEN FOR, and two earlier fields were dropped\n * for it \u2014 do not re-add either without re-deciding that:\n *\n * - `version` is OPTIONAL (defaulted to 1) because a store typed by a human carries no version key,\n * and a store the CLI refuses to read is a store the human cannot write.\n * - `repoRoot` is GONE, and with it the refusal that compared it against the current checkout. That\n * guard was real: the store directory is keyed on `path.basename(mainRepoRoot)`, so `~/work/api`\n * and `~/oss/api` share ONE token directory and repo B could load (and then overwrite) repo A's\n * credentials. It was removed anyway, knowingly: it also made the CLI refuse every hand-written\n * store, which carries no such field. Hand-authorability wins; the basename-collision risk is\n * ACCEPTED. A weakened version of the guard (warn-and-continue, or \"only check when present\") is\n * NOT the compromise here \u2014 it would re-introduce the same refusal for anyone who did write the\n * field. If you want the guard back, key the store directory on something collision-free instead.\n */\nconst tokenStoreSchema = z\n .object({\n version: z.literal(TOKEN_STORE_VERSION).default(TOKEN_STORE_VERSION),\n envs: z.record(z.string().min(1), z.string().min(1)),\n })\n .strict()\n\n/** The parsed `tokens.json` shape: `{ version, envs }` \u2014 `version` defaulted to 1 when absent on disk. */\nexport type TokenStore = z.infer<typeof tokenStoreSchema>\n\n/**\n * Absolute path to the per-project token store: `~/.infra-kit/projects/<repo>/tokens.json`.\n *\n * Derived from {@link getInfraKitConfigPaths} rather than re-deriving the home/projects/repo chain \u2014\n * that resolver already keys the directory off the MAIN repo root (so every worktree of a repo shares\n * one store) and memoizes the two `git rev-parse` spawns.\n *\n * @example\n * await getTokenStorePath() // => '/Users/arthur/.infra-kit/projects/infra-kit/tokens.json'\n */\nexport const getTokenStorePath = async (): Promise<string> => {\n const paths = await getInfraKitConfigPaths()\n\n return path.join(path.dirname(paths.userProject), TOKEN_STORE_FILE)\n}\n\n/**\n * Read the token store, or `null` when it does not exist.\n *\n * ABSENCE IS NOT AN ERROR \u2014 a fresh machine has no store, and every caller must be able to say \"no\n * tokens yet\" without a try/catch. The two things that DO throw are malformed JSON and a schema\n * mismatch: both are states only a human can fix, so both are loud.\n *\n * @example\n * const store = await readTokenStore()\n * store?.envs.dev // => 'dp.st.dev.xxxx' | undefined\n * // no file on disk => null\n * // garbage on disk => throws 'Invalid token store at ~/.infra-kit/projects/api/tokens.json: \u2026'\n */\nexport const readTokenStore = async (): Promise<TokenStore | null> => {\n const storePath = await getTokenStorePath()\n\n let raw: string\n\n try {\n raw = await fs.readFile(storePath, 'utf-8')\n } catch {\n return null\n }\n\n // Every refusal below is an EnvAuthError, not a plain Error. A broken store is the MOST durable\n // env-auth failure there is \u2014 it cannot heal on a 30s retry, only a human can fix it \u2014 and env\n // auto-load's sticky marker is keyed on that CLASS. Thrown as a plain Error these were classified\n // TRANSIENT, so a corrupt store went silent on the backgrounded shell-startup spawn and the user's\n // env simply stopped loading with nothing ever said. `env` is null: no ONE environment is at fault.\n let parsedRaw: unknown\n\n try {\n parsedRaw = JSON.parse(raw)\n } catch (err) {\n throw new EnvAuthError(\n `Invalid JSON in the token store at ${tildify(storePath)}: ${(err as Error).message}\\n` +\n `Fix or delete the file, then re-add the token with \\`infra-kit env-token-set <env>\\`.`,\n )\n }\n\n const result = tokenStoreSchema.safeParse(parsedRaw)\n\n if (!result.success) {\n throw new EnvAuthError(\n `Invalid token store at ${tildify(storePath)}: ${z.prettifyError(result.error)}\\n` +\n `Fix or delete the file, then re-add the token with \\`infra-kit env-token-set <env>\\`.`,\n )\n }\n\n return result.data\n}\n\n/**\n * Persist the store atomically at 0600, tightening every directory on the way to it to 0700.\n *\n * The chmods are not belt-and-braces: the config bootstrap already seeds\n * `~/.infra-kit/projects/<repo>/` at the default umask (0755), so an EXISTING directory is\n * world-readable and `mkdir`'s mode argument is a no-op for it. The same holds for the file \u2014 a\n * hand-written `tokens.json` is 0644 \u2014 so the modes of the whole credential path are (re)asserted on\n * EVERY write, not only on the writes that created it. Self-healing by construction: a plain\n * `env-token-set` is enough to fix a store a human typed.\n *\n * @example\n * await writeTokenStore({ version: 1, envs: { dev: 'dp.st.dev.x' } })\n * // ~/.infra-kit/projects/api/tokens.json -rw-------\n * // ~/.infra-kit/projects/api/ drwx------\n */\nexport const writeTokenStore = async (store: TokenStore): Promise<void> => {\n const storePath = await getTokenStorePath()\n\n const projectDir = path.dirname(storePath)\n const projectsDir = path.dirname(projectDir)\n const userConfigDir = path.dirname(projectsDir)\n\n for (const dir of [userConfigDir, projectsDir, projectDir]) {\n await fs.mkdir(dir, { recursive: true, mode: TOKEN_DIR_MODE })\n await fs.chmod(dir, TOKEN_DIR_MODE)\n }\n\n atomicWriteFileSync(storePath, `${JSON.stringify(store, null, 2)}\\n`, TOKEN_FILE_MODE)\n\n // The rename above already lands a fresh 0600 inode, but the mode of a credential file is not a\n // thing to leave to the umask that happened to be in effect \u2014 assert it outright.\n await fs.chmod(storePath, TOKEN_FILE_MODE)\n}\n\n/**\n * Add or replace one environment's token, read-modify-write, stamping the current `version`. Inherits\n * {@link readTokenStore}'s refusals: a corrupt store is never silently overwritten.\n *\n * @example\n * await setToken('dev', 'dp.st.dev.xxxxxxxx')\n * // tokens.json => { version: 1, envs: { dev: 'dp.st.dev.xxxxxxxx' } }\n */\nexport const setToken = async (env: string, token: string): Promise<void> => {\n const existing = await readTokenStore()\n\n await writeTokenStore({\n version: TOKEN_STORE_VERSION,\n envs: { ...existing?.envs, [env]: token },\n })\n}\n\n/**\n * Drop one environment's token. A no-op when the store (or the key) is absent \u2014 removing something\n * that is not there is the state the caller asked for.\n *\n * Removing a token here does NOT revoke it in Doppler; callers say so.\n *\n * @example\n * await removeToken('dev')\n * // tokens.json => { version: 1, envs: {} }\n */\nexport const removeToken = async (env: string): Promise<void> => {\n const existing = await readTokenStore()\n\n if (!existing || !(env in existing.envs)) return\n\n const envs = { ...existing.envs }\n\n delete envs[env]\n\n await writeTokenStore({\n version: TOKEN_STORE_VERSION,\n envs,\n })\n}\n", "/**\n * The DURABLE, USER-FIXABLE env-auth failure \u2014 as a TYPE rather than a string.\n *\n * WHY THIS EXISTS (and why it is deliberately NOT a Doppler type): env auto-load must tell a user\n * apart from a TRANSIENT failure (network, timeout, Doppler down \u2014 heals itself, 30s backoff, stay\n * quiet) and a DURABLE one (no token, a corrupt token store, a foreign token store \u2014 never heals, and\n * only a human can fix it). Only the durable class earns the STICKY marker that survives the\n * backgrounded shell-startup spawn and is replayed onto the next interactive command; a durable\n * failure misclassified as transient is TOTAL SILENCE, forever, on the one path the whole feature\n * exists to survive.\n *\n * That classification therefore has to travel WITH the error, not be re-derived by grepping a message\n * some other module owns \u2014 and it has to be throwable from EVERY module that can produce the class.\n * `lib/env-tokens` (the store) is a layer BELOW `integrations/doppler`, so a Doppler-owned base type\n * could not be thrown there without a layering inversion and an import cycle. Hence a neutral base\n * here in `lib/errors`, with `DopplerAuthError extends EnvAuthError` for the subset of failures that\n * are specifically \"Doppler refused the token we sent\".\n *\n * The defect class this closes is \"a durable failure that is not type-detectable\". Adding a new\n * durable failure and forgetting to type it is exactly how this shipped silent twice \u2014 so throw this\n * (or a subclass), never a plain `Error`, for anything a user must fix before env-load can work.\n *\n * @example\n * // no token minted for this env (integrations/doppler/token-resolver.ts)\n * throw new EnvAuthError(buildMissingTokenMessage('dev', storePath), 'dev')\n *\n * @example\n * // the token store itself is unusable \u2014 no single env is at fault, so `env` stays null\n * throw new EnvAuthError(`Invalid JSON in the token store at ${storePath} \u2026`)\n */\nexport class EnvAuthError extends Error {\n /**\n * The env (= Doppler config) whose token is at fault, or `null` when the failure is not attributable\n * to ONE env (a corrupt or foreign token store breaks every env at once). `null` is honest: do NOT\n * invent an env to fill it \u2014 the consumer names the env it was TRYING to load, which it already\n * knows.\n */\n readonly env: string | null\n\n constructor(message: string, env: string | null = null) {\n super(message)\n this.name = 'EnvAuthError'\n this.env = env\n }\n}\n\n/**\n * The TYPE GUARD every consumer of the auth channel classifies on \u2014 the single question \"is this a\n * durable, user-fixable env-auth failure?\", answered by the error's CLASS and never by its text.\n *\n * This is the default `isAuthFailure` of `lib/env-autoload`'s `runEnvAutoLoad`. It is deliberately\n * WIDER than `isDopplerAuthError`: that one means \"Doppler refused us\", which is only one of the\n * durable failures \u2014 a missing token never reaches Doppler at all, and a corrupt store never reaches\n * the resolver.\n *\n * @example\n * isEnvAuthFailure(new EnvAuthError('No Doppler service token for env \"dev\".', 'dev')) // => true\n * isEnvAuthFailure(new DopplerAuthError('dev', 'revoked')) // => true (subclass)\n * isEnvAuthFailure(new Error('connect ETIMEDOUT')) // => false (transient)\n */\nexport const isEnvAuthFailure = (error: unknown): error is EnvAuthError => {\n return error instanceof EnvAuthError\n}\n", "import fs from 'node:fs/promises'\nimport os from 'node:os'\n\n/**\n * Shared path-display helpers for CLI output. Extracted from `commands/config`\n * and `commands/init/migrate-config` (which each had private copies) so config\n * surfaces print paths consistently. Keep this dependency-light (node builtins\n * only) \u2014 it is imported by several commands.\n */\n\n/**\n * Resolve whether a file is reachable, suppressing ENOENT (and any access error)\n * into a boolean. Used to render `[\u2713]`/`[ ]` existence markers.\n *\n * @example\n * await fileExists('/etc/hosts') // => true\n * await fileExists('/nope.txt') // => false\n */\nexport const fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath)\n\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Replace the user's home prefix with `~` so logged paths stay short and portable\n * across machines. Leaves non-home paths untouched.\n *\n * @example\n * // os.homedir() === '/Users/arthur'\n * tildify('/Users/arthur/.infra-kit/infra-kit.json') // => '~/.infra-kit/infra-kit.json'\n * tildify('/etc/hosts') // => '/etc/hosts'\n */\nexport const tildify = (filePath: string): string => {\n const home = os.homedir()\n\n return filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath\n}\n", "import { logger } from 'src/lib/logger'\n\ninterface CommandOption {\n flag: string\n value: string | string[] | boolean\n}\n\nconst createCommandEcho = () => {\n let cliPath = ''\n let options: CommandOption[] = []\n let isInteractive = false\n\n return {\n /**\n * Bind the echo to the command about to run, and clear the previous one's recording.\n *\n * `cliPath` must be the argv Commander actually parsed (`release create`), which is why the ONLY\n * caller is program.ts's `preAction` hook, feeding it `commandPath(actionCommand)`. Commands used to\n * name themselves here, and that is precisely how the printed line rotted: they passed the flat\n * `release-create`, which stopped being a command the day the flat aliases were dropped, so the\n * \"equivalent command\" we told the user to retype no longer parsed. Commander is the only thing that\n * knows the real path, so it is the only thing allowed to say it.\n */\n start(path: string): void {\n cliPath = path\n options = []\n isInteractive = false\n },\n\n /**\n * Mark that the command had interactive input (prompts)\n * Call this once when ANY prompt happens\n */\n setInteractive(): void {\n isInteractive = true\n },\n\n /**\n * Track an option selection\n * @param flag The CLI flag (e.g., \"--versions\")\n * @param value The selected value\n */\n addOption(flag: string, value: string | string[] | boolean): void {\n options.push({ flag, value })\n },\n\n /**\n * Format the tracked options into a replayable flag string (empty when none).\n */\n formatOptions(): string {\n return options\n .map((opt) => {\n if (typeof opt.value === 'boolean') {\n return opt.value ? opt.flag : ''\n }\n\n if (Array.isArray(opt.value)) {\n return `${opt.flag} \"${opt.value.join(', ')}\"`\n }\n\n return `${opt.flag} \"${opt.value}\"`\n })\n .filter(Boolean)\n .join(' ')\n },\n\n /**\n * The recorded interactive flags for the session shell's report side channel, so the transcript's\n * equivalent line carries the resolved options (`--versions \"1.2.5\"`), not just the bare command.\n * `null` when nothing was recorded \u2014 the caller then falls back to the bare spawned argv.\n */\n snapshot(): { formattedOptions: string } | null {\n if (options.length === 0) {\n return null\n }\n\n return { formattedOptions: this.formatOptions() }\n },\n\n /**\n * Print the equivalent CLI command if there was interactive input.\n *\n * Silent without a bound `cliPath`: the only caller that binds one is Commander's `preAction`, so an\n * unbound echo means the command ran off the CLI (an MCP tool), where a `pnpm exec` line would be\n * nonsense. Printing a path-less `pnpm exec infra-kit --yes` would be worse than printing nothing.\n */\n print(): void {\n if (!isInteractive || options.length === 0 || !cliPath) {\n return\n }\n\n logger.info(`\uD83D\uDCDF Equivalent command: \\npnpm exec infra-kit ${cliPath} ${this.formatOptions()}\\n`)\n },\n\n /**\n * Reset state (useful for testing)\n */\n reset(): void {\n cliPath = ''\n options = []\n isInteractive = false\n },\n }\n}\n\n// Singleton instance (same pattern as logger)\nexport const commandEcho = createCommandEcho()\n", "import type { Buffer } from 'node:buffer'\nimport process from 'node:process'\n\nimport { isMcpMode } from 'src/lib/mcp-mode'\n\nimport { acquireStdin, releaseStdin } from './stdin-ref'\n\n/**\n * Structural mirror of `@inquirer/type`'s `Context` \u2014 the second argument every\n * `@inquirer/*` prompt takes. Declared here rather than imported because\n * `@inquirer/core` and `@inquirer/type` are transitive dependencies of this\n * package, not direct ones (only the four prompt packages are).\n */\nexport interface PromptContext {\n input?: NodeJS.ReadableStream\n output?: NodeJS.WritableStream\n clearPromptOnDone?: boolean\n signal?: AbortSignal\n}\n\nconst ESC_BYTE = 0x1b\n\n/**\n * Run an `@inquirer/*` prompt with Esc bound to cancellation.\n *\n * The cancel path is entirely pre-existing plumbing: an `AbortController`'s signal\n * goes into the prompt context, `@inquirer/core` rejects with an `AbortPromptError`\n * when it fires, that name is already in `CANCELLATION_ERROR_NAMES`\n * (lib/errors/is-prompt-cancellation), and `entry/cli.ts` catches it at the top\n * level \u2192 \"Operation cancelled.\" \u2192 exit 0. All this adds is the thing that calls\n * `abort()`.\n *\n * DETECTION \u2014 a raw `data` listener that aborts iff the chunk is exactly one `0x1b`\n * byte. Deliberately NOT readline `keypress`: that delivers a lone Esc only after its\n * escape-sequence timeout (~523ms measured), and the `escapeCodeTimeout` dial that\n * would shorten it is process-global and first-armer-wins \u2014 a second\n * `emitKeypressEvents` call on `process.stdin` is SILENTLY IGNORED, so the dial\n * degrades to a no-op with no error and no failing test. The raw listener costs ~24ms\n * and touches no global state. Arrow keys (`\\x1b[B`, 3 bytes) and Alt combos\n * (`\\x1bb`, 2 bytes) are ignored because they are LONGER \u2014 no parsed key field is\n * involved. Node `data` listeners broadcast, so this one does not steal bytes from\n * readline: arrows and Enter keep working while it is attached.\n *\n * FALSE POSITIVES are a non-issue: there is no routine source of a lone-`0x1b` chunk.\n * Focus events (`\\x1b[I`/`\\x1b[O`), bracketed paste (`\\x1b[200~`) and mouse reports\n * (`\\x1b[M\u2026`) are all multi-byte, and inquirer enables none of them.\n *\n * FALSE NEGATIVE \u2014 measured and accepted: a COALESCED Esc (`\"\\x1bx\"` arriving in ONE\n * chunk, i.e. Esc immediately followed by another key) is silently dropped and the\n * prompt stays open. The fix would be Ink's 20ms deferred abort \u2014 hold the lone Esc,\n * cancel it if a follow-up chunk lands. That is a logged NON-GOAL; do not pre-build it.\n *\n * The listener attaches only on a real interactive terminal. `isTTY` alone is NOT a\n * sufficient guard \u2014 see lib/mcp-mode for why `stdio: 'inherit'` defeats it.\n */\nexport const withEscape = async <T>(run: (context: PromptContext) => Promise<T>, base?: PromptContext): Promise<T> => {\n const controller = new AbortController()\n const context: PromptContext = { ...base, signal: controller.signal }\n\n if (isMcpMode() || !process.stdin.isTTY) return run(context)\n\n const input = process.stdin\n\n const onData = (chunk: Buffer) => {\n if (chunk.length === 1 && chunk[0] === ESC_BYTE) controller.abort()\n }\n\n // STDIN OWNERSHIP \u2014 this is the seam that keeps stdin alive for inquirer, which refs\n // nothing itself. It must sit BELOW the early return above: a non-TTY run never reads\n // stdin, so acquiring there would ref a handle nobody reads and hang the process. See\n // lib/prompts/stdin-ref for why an unref'd stdin kills a prompt with exit 13.\n acquireStdin()\n\n try {\n // Attach after the prompt has built its readline interface. Cheap ordering defence\n // only \u2014 there is no race today: readline attaches its own consumer synchronously\n // and `data` events are async.\n //\n // INSIDE the `try`, both of them: `run` is caller-supplied and `@inquirer/core` runs\n // synchronously up to readline construction, so a SYNCHRONOUS throw here would skip a\n // `finally` placed any lower \u2014 leaking the ref and hanging the CLI, which is the exact\n // failure this module exists to prevent.\n const pending = run(context)\n\n input.on('data', onData)\n\n return await pending\n } finally {\n // Non-negotiable: the dev wizard runs ~5 prompts back to back, so a leaked\n // listener would abort the next one.\n input.removeListener('data', onData)\n // Same `finally` as the listener teardown, and for the same reason: every exit path\n // (answer, Esc abort, throw) must give the ref back, or node never exits.\n releaseStdin()\n }\n}\n", "/**\n * MCP-mode flag for the CLI. `process.stdin` is the JSON-RPC transport when the\n * binary runs as an MCP server, so anything that would read keystrokes from it\n * (raw `data` listeners, Ink screens, `@inquirer` prompts) must be gated off.\n *\n * This exists because `process.stdin.isTTY` CANNOT answer that question:\n * `commands/mcp/mcp.ts` spawns the server with `stdio: 'inherit'`, so a\n * terminal-launched `infra-kit mcp` hands the child a real TTY stdin and an\n * isTTY-keyed guard does not fire. Piped stdio (how Claude Code spawns it) only\n * makes isTTY look sufficient by coincidence.\n *\n * Set in `createMcpServer()` \u2014 the importable, transport-free entry that every\n * MCP launch goes through \u2014 not in `entry/mcp.ts`, which starts a server at\n * module scope and so can never assert the flag was actually set.\n */\n\n/** Mutable holder (object so `prefer-const` holds while the flag toggles per run). */\nexport const mcpMode = { enabled: false }\n\n/** True when this process is serving MCP, i.e. `process.stdin` carries JSON-RPC. */\nexport const isMcpMode = (): boolean => {\n return mcpMode.enabled\n}\n", "import confirm from '@inquirer/confirm'\nimport process from 'node:process'\n\nimport { logger } from 'src/lib/logger'\nimport { withEscape } from 'src/lib/prompts/escapable-context'\n\nimport { commandEcho } from './command-echo'\n\n/**\n * Shared interactive-confirmation gate for mutating commands.\n *\n * When `confirmedCommand` is truthy (CLI `--yes` or an MCP call, which always\n * injects `confirmedCommand: true`) the prompt is skipped and execution\n * proceeds. Otherwise it prompts the user, marks the echo as interactive, and\n * exits cleanly if the user declines.\n *\n * Esc aborts the prompt (via `withEscape`) rather than answering it \u2014 the caller\n * sees an `AbortPromptError`, which every command's boundary already treats as a\n * clean back-out through `isPromptCancellation`. That is deliberately NOT the same\n * as declining: a decline is an answer of \"no\" and takes the `process.exit(0)` path\n * below, whereas Esc means \"I never answered\".\n *\n * Unlike every other prompt in the CLI this one renders to STDOUT, not stderr \u2014\n * left as-is, since changing it would move confirmation text out of the stream\n * callers have always read it from.\n *\n * The `process.exit(0)` only runs on the interactive-decline path, which is\n * unreachable under MCP \u2014 so this stays safe for the long-lived MCP server.\n *\n * Callers remain responsible for their own `commandEcho.addOption('--yes', \u2026)`\n * bookkeeping, which varies between commands.\n */\nexport const confirmOrExit = async (confirmedCommand: boolean | undefined, message: string): Promise<void> => {\n const answer = confirmedCommand\n ? true\n : await withEscape((context) => {\n return confirm({ message }, context)\n })\n\n if (!confirmedCommand) {\n commandEcho.setInteractive()\n }\n\n if (!answer) {\n logger.info('Operation cancelled. Exiting...')\n process.exit(0)\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["fs", "path", "z", "EnvAuthError", "message", "env", "isEnvAuthFailure", "error", "fs", "os", "fileExists", "filePath", "tildify", "home", "TOKEN_STORE_FILE", "TOKEN_STORE_VERSION", "TOKEN_FILE_MODE", "TOKEN_DIR_MODE", "tokenStoreSchema", "z", "getTokenStorePath", "paths", "getInfraKitConfigPaths", "path", "readTokenStore", "storePath", "raw", "fs", "parsedRaw", "err", "EnvAuthError", "tildify", "result", "writeTokenStore", "store", "projectDir", "projectsDir", "userConfigDir", "dir", "atomicWriteFileSync", "setToken", "env", "token", "existing", "removeToken", "envs", "createCommandEcho", "cliPath", "options", "isInteractive", "path", "flag", "value", "opt", "logger", "commandEcho", "process", "
|
|
3
|
+
"sources": ["../src/lib/env-tokens/env-tokens.ts", "../src/lib/errors/env-auth-error.ts", "../src/lib/path-display/path-display.ts", "../src/lib/command-echo/command-echo.ts", "../src/lib/prompts/escapable-context.ts", "../src/lib/command-echo/confirm-or-exit.ts"],
|
|
4
|
+
"sourcesContent": ["// DEFAULT import, deliberately \u2014 the same reason config-bootstrap.ts documents: the module is used as\n// `fs.readFile(...)` / `fs.mkdir(...)` so every call is a property lookup on the live module object,\n// which is what `vi.spyOn(fs, \u2026)` patches. A named import (`import { readFile }`) is bound at import\n// time and the spy silently does NOT intercept it \u2014 the assertion goes green while the real call\n// still hits disk. Do not \"modernize\" this import.\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { z } from 'zod'\n\nimport { atomicWriteFileSync } from 'src/lib/constants'\nimport { EnvAuthError } from 'src/lib/errors/env-auth-error'\nimport { getInfraKitConfigPaths } from 'src/lib/infra-kit-config'\nimport { tildify } from 'src/lib/path-display'\n\n/**\n * Basename of the token store. A SIBLING of the layer-3 `infra-kit.json`, never a key inside it:\n * `getInfraKitConfig` only ever globs the three hardcoded `infra-kit.json` paths and its schema is\n * `.strict()`, so an unknown key throws at the LAYER parse and bricks every command on every pinned\n * version. A sibling file is structurally invisible to that loader \u2014 `config-bootstrap.ts` already\n * relies on exactly this for its `.example.jsonc`. That invisibility is the blast-radius guarantee:\n * a corrupt `tokens.json` can only break the readers below, never `worktrees-list`.\n */\nconst TOKEN_STORE_FILE = 'tokens.json'\n\n/** Schema version of the on-disk store. Bump only alongside a migration. */\nconst TOKEN_STORE_VERSION = 1\n\n/** Mode of `tokens.json` itself \u2014 owner read/write only. It holds live credentials. */\nconst TOKEN_FILE_MODE = 0o600\n\n/** Mode of every directory on the way to the store \u2014 owner-only traversal. */\nconst TOKEN_DIR_MODE = 0o700\n\n/**\n * The store's OWN schema: `{ version?: 1, envs: { <env>: <token> } }`. Deliberately NOT derived from\n * `infraKitConfigObject`: this file is not a merge layer, so `.strict()` here is free (a typo is a\n * loud error in three readers, not a bricked CLI).\n *\n * HAND-AUTHORABILITY IS THE CONSTRAINT THIS SHAPE IS CHOSEN FOR, and two earlier fields were dropped\n * for it \u2014 do not re-add either without re-deciding that:\n *\n * - `version` is OPTIONAL (defaulted to 1) because a store typed by a human carries no version key,\n * and a store the CLI refuses to read is a store the human cannot write.\n * - `repoRoot` is GONE, and with it the refusal that compared it against the current checkout. That\n * guard was real: the store directory is keyed on `path.basename(mainRepoRoot)`, so `~/work/api`\n * and `~/oss/api` share ONE token directory and repo B could load (and then overwrite) repo A's\n * credentials. It was removed anyway, knowingly: it also made the CLI refuse every hand-written\n * store, which carries no such field. Hand-authorability wins; the basename-collision risk is\n * ACCEPTED. A weakened version of the guard (warn-and-continue, or \"only check when present\") is\n * NOT the compromise here \u2014 it would re-introduce the same refusal for anyone who did write the\n * field. If you want the guard back, key the store directory on something collision-free instead.\n */\nconst tokenStoreSchema = z\n .object({\n version: z.literal(TOKEN_STORE_VERSION).default(TOKEN_STORE_VERSION),\n envs: z.record(z.string().min(1), z.string().min(1)),\n })\n .strict()\n\n/** The parsed `tokens.json` shape: `{ version, envs }` \u2014 `version` defaulted to 1 when absent on disk. */\nexport type TokenStore = z.infer<typeof tokenStoreSchema>\n\n/**\n * Absolute path to the per-project token store: `~/.infra-kit/projects/<repo>/tokens.json`.\n *\n * Derived from {@link getInfraKitConfigPaths} rather than re-deriving the home/projects/repo chain \u2014\n * that resolver already keys the directory off the MAIN repo root (so every worktree of a repo shares\n * one store) and memoizes the two `git rev-parse` spawns.\n *\n * @example\n * await getTokenStorePath() // => '/Users/arthur/.infra-kit/projects/infra-kit/tokens.json'\n */\nexport const getTokenStorePath = async (): Promise<string> => {\n const paths = await getInfraKitConfigPaths()\n\n return path.join(path.dirname(paths.userProject), TOKEN_STORE_FILE)\n}\n\n/**\n * Read the token store, or `null` when it does not exist.\n *\n * ABSENCE IS NOT AN ERROR \u2014 a fresh machine has no store, and every caller must be able to say \"no\n * tokens yet\" without a try/catch. The two things that DO throw are malformed JSON and a schema\n * mismatch: both are states only a human can fix, so both are loud.\n *\n * @example\n * const store = await readTokenStore()\n * store?.envs.dev // => 'dp.st.dev.xxxx' | undefined\n * // no file on disk => null\n * // garbage on disk => throws 'Invalid token store at ~/.infra-kit/projects/api/tokens.json: \u2026'\n */\nexport const readTokenStore = async (): Promise<TokenStore | null> => {\n const storePath = await getTokenStorePath()\n\n let raw: string\n\n try {\n raw = await fs.readFile(storePath, 'utf-8')\n } catch {\n return null\n }\n\n // Every refusal below is an EnvAuthError, not a plain Error. A broken store is the MOST durable\n // env-auth failure there is \u2014 it cannot heal on a 30s retry, only a human can fix it \u2014 and env\n // auto-load's sticky marker is keyed on that CLASS. Thrown as a plain Error these were classified\n // TRANSIENT, so a corrupt store went silent on the backgrounded shell-startup spawn and the user's\n // env simply stopped loading with nothing ever said. `env` is null: no ONE environment is at fault.\n let parsedRaw: unknown\n\n try {\n parsedRaw = JSON.parse(raw)\n } catch (err) {\n throw new EnvAuthError(\n `Invalid JSON in the token store at ${tildify(storePath)}: ${(err as Error).message}\\n` +\n `Fix or delete the file, then re-add the token with \\`infra-kit env-token-set <env>\\`.`,\n )\n }\n\n const result = tokenStoreSchema.safeParse(parsedRaw)\n\n if (!result.success) {\n throw new EnvAuthError(\n `Invalid token store at ${tildify(storePath)}: ${z.prettifyError(result.error)}\\n` +\n `Fix or delete the file, then re-add the token with \\`infra-kit env-token-set <env>\\`.`,\n )\n }\n\n return result.data\n}\n\n/**\n * Persist the store atomically at 0600, tightening every directory on the way to it to 0700.\n *\n * The chmods are not belt-and-braces: the config bootstrap already seeds\n * `~/.infra-kit/projects/<repo>/` at the default umask (0755), so an EXISTING directory is\n * world-readable and `mkdir`'s mode argument is a no-op for it. The same holds for the file \u2014 a\n * hand-written `tokens.json` is 0644 \u2014 so the modes of the whole credential path are (re)asserted on\n * EVERY write, not only on the writes that created it. Self-healing by construction: a plain\n * `env-token-set` is enough to fix a store a human typed.\n *\n * @example\n * await writeTokenStore({ version: 1, envs: { dev: 'dp.st.dev.x' } })\n * // ~/.infra-kit/projects/api/tokens.json -rw-------\n * // ~/.infra-kit/projects/api/ drwx------\n */\nexport const writeTokenStore = async (store: TokenStore): Promise<void> => {\n const storePath = await getTokenStorePath()\n\n const projectDir = path.dirname(storePath)\n const projectsDir = path.dirname(projectDir)\n const userConfigDir = path.dirname(projectsDir)\n\n for (const dir of [userConfigDir, projectsDir, projectDir]) {\n await fs.mkdir(dir, { recursive: true, mode: TOKEN_DIR_MODE })\n await fs.chmod(dir, TOKEN_DIR_MODE)\n }\n\n atomicWriteFileSync(storePath, `${JSON.stringify(store, null, 2)}\\n`, TOKEN_FILE_MODE)\n\n // The rename above already lands a fresh 0600 inode, but the mode of a credential file is not a\n // thing to leave to the umask that happened to be in effect \u2014 assert it outright.\n await fs.chmod(storePath, TOKEN_FILE_MODE)\n}\n\n/**\n * Add or replace one environment's token, read-modify-write, stamping the current `version`. Inherits\n * {@link readTokenStore}'s refusals: a corrupt store is never silently overwritten.\n *\n * @example\n * await setToken('dev', 'dp.st.dev.xxxxxxxx')\n * // tokens.json => { version: 1, envs: { dev: 'dp.st.dev.xxxxxxxx' } }\n */\nexport const setToken = async (env: string, token: string): Promise<void> => {\n const existing = await readTokenStore()\n\n await writeTokenStore({\n version: TOKEN_STORE_VERSION,\n envs: { ...existing?.envs, [env]: token },\n })\n}\n\n/**\n * Drop one environment's token. A no-op when the store (or the key) is absent \u2014 removing something\n * that is not there is the state the caller asked for.\n *\n * Removing a token here does NOT revoke it in Doppler; callers say so.\n *\n * @example\n * await removeToken('dev')\n * // tokens.json => { version: 1, envs: {} }\n */\nexport const removeToken = async (env: string): Promise<void> => {\n const existing = await readTokenStore()\n\n if (!existing || !(env in existing.envs)) return\n\n const envs = { ...existing.envs }\n\n delete envs[env]\n\n await writeTokenStore({\n version: TOKEN_STORE_VERSION,\n envs,\n })\n}\n", "/**\n * The DURABLE, USER-FIXABLE env-auth failure \u2014 as a TYPE rather than a string.\n *\n * WHY THIS EXISTS (and why it is deliberately NOT a Doppler type): env auto-load must tell a user\n * apart from a TRANSIENT failure (network, timeout, Doppler down \u2014 heals itself, 30s backoff, stay\n * quiet) and a DURABLE one (no token, a corrupt token store, a foreign token store \u2014 never heals, and\n * only a human can fix it). Only the durable class earns the STICKY marker that survives the\n * backgrounded shell-startup spawn and is replayed onto the next interactive command; a durable\n * failure misclassified as transient is TOTAL SILENCE, forever, on the one path the whole feature\n * exists to survive.\n *\n * That classification therefore has to travel WITH the error, not be re-derived by grepping a message\n * some other module owns \u2014 and it has to be throwable from EVERY module that can produce the class.\n * `lib/env-tokens` (the store) is a layer BELOW `integrations/doppler`, so a Doppler-owned base type\n * could not be thrown there without a layering inversion and an import cycle. Hence a neutral base\n * here in `lib/errors`, with `DopplerAuthError extends EnvAuthError` for the subset of failures that\n * are specifically \"Doppler refused the token we sent\".\n *\n * The defect class this closes is \"a durable failure that is not type-detectable\". Adding a new\n * durable failure and forgetting to type it is exactly how this shipped silent twice \u2014 so throw this\n * (or a subclass), never a plain `Error`, for anything a user must fix before env-load can work.\n *\n * @example\n * // no token minted for this env (integrations/doppler/token-resolver.ts)\n * throw new EnvAuthError(buildMissingTokenMessage('dev', storePath), 'dev')\n *\n * @example\n * // the token store itself is unusable \u2014 no single env is at fault, so `env` stays null\n * throw new EnvAuthError(`Invalid JSON in the token store at ${storePath} \u2026`)\n */\nexport class EnvAuthError extends Error {\n /**\n * The env (= Doppler config) whose token is at fault, or `null` when the failure is not attributable\n * to ONE env (a corrupt or foreign token store breaks every env at once). `null` is honest: do NOT\n * invent an env to fill it \u2014 the consumer names the env it was TRYING to load, which it already\n * knows.\n */\n readonly env: string | null\n\n constructor(message: string, env: string | null = null) {\n super(message)\n this.name = 'EnvAuthError'\n this.env = env\n }\n}\n\n/**\n * The TYPE GUARD every consumer of the auth channel classifies on \u2014 the single question \"is this a\n * durable, user-fixable env-auth failure?\", answered by the error's CLASS and never by its text.\n *\n * This is the default `isAuthFailure` of `lib/env-autoload`'s `runEnvAutoLoad`. It is deliberately\n * WIDER than `isDopplerAuthError`: that one means \"Doppler refused us\", which is only one of the\n * durable failures \u2014 a missing token never reaches Doppler at all, and a corrupt store never reaches\n * the resolver.\n *\n * @example\n * isEnvAuthFailure(new EnvAuthError('No Doppler service token for env \"dev\".', 'dev')) // => true\n * isEnvAuthFailure(new DopplerAuthError('dev', 'revoked')) // => true (subclass)\n * isEnvAuthFailure(new Error('connect ETIMEDOUT')) // => false (transient)\n */\nexport const isEnvAuthFailure = (error: unknown): error is EnvAuthError => {\n return error instanceof EnvAuthError\n}\n", "import fs from 'node:fs/promises'\nimport os from 'node:os'\n\n/**\n * Shared path-display helpers for CLI output. Extracted from `commands/config`\n * and `commands/init/migrate-config` (which each had private copies) so config\n * surfaces print paths consistently. Keep this dependency-light (node builtins\n * only) \u2014 it is imported by several commands.\n */\n\n/**\n * Resolve whether a file is reachable, suppressing ENOENT (and any access error)\n * into a boolean. Used to render `[\u2713]`/`[ ]` existence markers.\n *\n * @example\n * await fileExists('/etc/hosts') // => true\n * await fileExists('/nope.txt') // => false\n */\nexport const fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath)\n\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Replace the user's home prefix with `~` so logged paths stay short and portable\n * across machines. Leaves non-home paths untouched.\n *\n * @example\n * // os.homedir() === '/Users/arthur'\n * tildify('/Users/arthur/.infra-kit/infra-kit.json') // => '~/.infra-kit/infra-kit.json'\n * tildify('/etc/hosts') // => '/etc/hosts'\n */\nexport const tildify = (filePath: string): string => {\n const home = os.homedir()\n\n return filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath\n}\n", "import { logger } from 'src/lib/logger'\n\ninterface CommandOption {\n flag: string\n value: string | string[] | boolean\n}\n\nconst createCommandEcho = () => {\n let cliPath = ''\n let options: CommandOption[] = []\n let isInteractive = false\n\n return {\n /**\n * Bind the echo to the command about to run, and clear the previous one's recording.\n *\n * `cliPath` must be the argv Commander actually parsed (`release create`), which is why the ONLY\n * caller is program.ts's `preAction` hook, feeding it `commandPath(actionCommand)`. Commands used to\n * name themselves here, and that is precisely how the printed line rotted: they passed the flat\n * `release-create`, which stopped being a command the day the flat aliases were dropped, so the\n * \"equivalent command\" we told the user to retype no longer parsed. Commander is the only thing that\n * knows the real path, so it is the only thing allowed to say it.\n */\n start(path: string): void {\n cliPath = path\n options = []\n isInteractive = false\n },\n\n /**\n * Mark that the command had interactive input (prompts)\n * Call this once when ANY prompt happens\n */\n setInteractive(): void {\n isInteractive = true\n },\n\n /**\n * Track an option selection\n * @param flag The CLI flag (e.g., \"--versions\")\n * @param value The selected value\n */\n addOption(flag: string, value: string | string[] | boolean): void {\n options.push({ flag, value })\n },\n\n /**\n * Format the tracked options into a replayable flag string (empty when none).\n */\n formatOptions(): string {\n return options\n .map((opt) => {\n if (typeof opt.value === 'boolean') {\n return opt.value ? opt.flag : ''\n }\n\n if (Array.isArray(opt.value)) {\n return `${opt.flag} \"${opt.value.join(', ')}\"`\n }\n\n return `${opt.flag} \"${opt.value}\"`\n })\n .filter(Boolean)\n .join(' ')\n },\n\n /**\n * The recorded interactive flags for the session shell's report side channel, so the transcript's\n * equivalent line carries the resolved options (`--versions \"1.2.5\"`), not just the bare command.\n * `null` when nothing was recorded \u2014 the caller then falls back to the bare spawned argv.\n */\n snapshot(): { formattedOptions: string } | null {\n if (options.length === 0) {\n return null\n }\n\n return { formattedOptions: this.formatOptions() }\n },\n\n /**\n * Print the equivalent CLI command if there was interactive input.\n *\n * Silent without a bound `cliPath`: the only caller that binds one is Commander's `preAction`, so an\n * unbound echo means the command ran off the CLI (an MCP tool), where a `pnpm exec` line would be\n * nonsense. Printing a path-less `pnpm exec infra-kit --yes` would be worse than printing nothing.\n */\n print(): void {\n if (!isInteractive || options.length === 0 || !cliPath) {\n return\n }\n\n logger.info(`\uD83D\uDCDF Equivalent command: \\npnpm exec infra-kit ${cliPath} ${this.formatOptions()}\\n`)\n },\n\n /**\n * Reset state (useful for testing)\n */\n reset(): void {\n cliPath = ''\n options = []\n isInteractive = false\n },\n }\n}\n\n// Singleton instance (same pattern as logger)\nexport const commandEcho = createCommandEcho()\n", "import type { Buffer } from 'node:buffer'\nimport process from 'node:process'\n\nimport { isMcpMode } from 'src/lib/mcp-mode'\n\nimport { acquireStdin, releaseStdin } from './stdin-ref'\n\n/**\n * Structural mirror of `@inquirer/type`'s `Context` \u2014 the second argument every\n * `@inquirer/*` prompt takes. Declared here rather than imported because\n * `@inquirer/core` and `@inquirer/type` are transitive dependencies of this\n * package, not direct ones (only the four prompt packages are).\n */\nexport interface PromptContext {\n input?: NodeJS.ReadableStream\n output?: NodeJS.WritableStream\n clearPromptOnDone?: boolean\n signal?: AbortSignal\n}\n\nconst ESC_BYTE = 0x1b\n\n/**\n * Run an `@inquirer/*` prompt with Esc bound to cancellation.\n *\n * The cancel path is entirely pre-existing plumbing: an `AbortController`'s signal\n * goes into the prompt context, `@inquirer/core` rejects with an `AbortPromptError`\n * when it fires, that name is already in `CANCELLATION_ERROR_NAMES`\n * (lib/errors/is-prompt-cancellation), and `entry/cli.ts` catches it at the top\n * level \u2192 \"Operation cancelled.\" \u2192 exit 0. All this adds is the thing that calls\n * `abort()`.\n *\n * DETECTION \u2014 a raw `data` listener that aborts iff the chunk is exactly one `0x1b`\n * byte. Deliberately NOT readline `keypress`: that delivers a lone Esc only after its\n * escape-sequence timeout (~523ms measured), and the `escapeCodeTimeout` dial that\n * would shorten it is process-global and first-armer-wins \u2014 a second\n * `emitKeypressEvents` call on `process.stdin` is SILENTLY IGNORED, so the dial\n * degrades to a no-op with no error and no failing test. The raw listener costs ~24ms\n * and touches no global state. Arrow keys (`\\x1b[B`, 3 bytes) and Alt combos\n * (`\\x1bb`, 2 bytes) are ignored because they are LONGER \u2014 no parsed key field is\n * involved. Node `data` listeners broadcast, so this one does not steal bytes from\n * readline: arrows and Enter keep working while it is attached.\n *\n * FALSE POSITIVES are a non-issue: there is no routine source of a lone-`0x1b` chunk.\n * Focus events (`\\x1b[I`/`\\x1b[O`), bracketed paste (`\\x1b[200~`) and mouse reports\n * (`\\x1b[M\u2026`) are all multi-byte, and inquirer enables none of them.\n *\n * FALSE NEGATIVE \u2014 measured and accepted: a COALESCED Esc (`\"\\x1bx\"` arriving in ONE\n * chunk, i.e. Esc immediately followed by another key) is silently dropped and the\n * prompt stays open. The fix would be Ink's 20ms deferred abort \u2014 hold the lone Esc,\n * cancel it if a follow-up chunk lands. That is a logged NON-GOAL; do not pre-build it.\n *\n * The listener attaches only on a real interactive terminal. `isTTY` alone is NOT a\n * sufficient guard \u2014 see lib/mcp-mode for why `stdio: 'inherit'` defeats it.\n */\nexport const withEscape = async <T>(run: (context: PromptContext) => Promise<T>, base?: PromptContext): Promise<T> => {\n const controller = new AbortController()\n const context: PromptContext = { ...base, signal: controller.signal }\n\n if (isMcpMode() || !process.stdin.isTTY) return run(context)\n\n const input = process.stdin\n\n const onData = (chunk: Buffer) => {\n if (chunk.length === 1 && chunk[0] === ESC_BYTE) controller.abort()\n }\n\n // STDIN OWNERSHIP \u2014 this is the seam that keeps stdin alive for inquirer, which refs\n // nothing itself. It must sit BELOW the early return above: a non-TTY run never reads\n // stdin, so acquiring there would ref a handle nobody reads and hang the process. See\n // lib/prompts/stdin-ref for why an unref'd stdin kills a prompt with exit 13.\n acquireStdin()\n\n try {\n // Attach after the prompt has built its readline interface. Cheap ordering defence\n // only \u2014 there is no race today: readline attaches its own consumer synchronously\n // and `data` events are async.\n //\n // INSIDE the `try`, both of them: `run` is caller-supplied and `@inquirer/core` runs\n // synchronously up to readline construction, so a SYNCHRONOUS throw here would skip a\n // `finally` placed any lower \u2014 leaking the ref and hanging the CLI, which is the exact\n // failure this module exists to prevent.\n const pending = run(context)\n\n // Explicit, because `on('data')` alone is NOT enough to start the flow: `Readable` resumes on a\n // new `data` listener only when `flowing !== false`, and the palette's teardown now pauses stdin\n // (tui/boot.tsx) so a prompt opened after an Ink screen inherits `flowing === false`. Esc would be\n // silently dead. It happens to work anyway today \u2014 `run()` above builds inquirer's readline, whose\n // constructor resumes the stream \u2014 but that is an undocumented side effect of a TRANSITIVE\n // dependency, and the ordering it relies on is the very thing the comment below reserves the right\n // to change. One idempotent call is cheaper than that coupling.\n if (input.isPaused()) input.resume()\n\n input.on('data', onData)\n\n return await pending\n } finally {\n // Non-negotiable: the dev wizard runs ~5 prompts back to back, so a leaked\n // listener would abort the next one.\n input.removeListener('data', onData)\n // Same `finally` as the listener teardown, and for the same reason: every exit path\n // (answer, Esc abort, throw) must give the ref back, or node never exits.\n releaseStdin()\n }\n}\n", "import confirm from '@inquirer/confirm'\nimport process from 'node:process'\n\nimport { logger } from 'src/lib/logger'\nimport { withEscape } from 'src/lib/prompts/escapable-context'\n\nimport { commandEcho } from './command-echo'\n\n/**\n * Shared interactive-confirmation gate for mutating commands.\n *\n * When `confirmedCommand` is truthy (CLI `--yes` or an MCP call, which always\n * injects `confirmedCommand: true`) the prompt is skipped and execution\n * proceeds. Otherwise it prompts the user, marks the echo as interactive, and\n * exits cleanly if the user declines.\n *\n * Esc aborts the prompt (via `withEscape`) rather than answering it \u2014 the caller\n * sees an `AbortPromptError`, which every command's boundary already treats as a\n * clean back-out through `isPromptCancellation`. That is deliberately NOT the same\n * as declining: a decline is an answer of \"no\" and takes the `process.exit(0)` path\n * below, whereas Esc means \"I never answered\".\n *\n * Unlike every other prompt in the CLI this one renders to STDOUT, not stderr \u2014\n * left as-is, since changing it would move confirmation text out of the stream\n * callers have always read it from.\n *\n * The `process.exit(0)` only runs on the interactive-decline path, which is\n * unreachable under MCP \u2014 so this stays safe for the long-lived MCP server.\n *\n * Callers remain responsible for their own `commandEcho.addOption('--yes', \u2026)`\n * bookkeeping, which varies between commands.\n */\nexport const confirmOrExit = async (confirmedCommand: boolean | undefined, message: string): Promise<void> => {\n const answer = confirmedCommand\n ? true\n : await withEscape((context) => {\n return confirm({ message }, context)\n })\n\n if (!confirmedCommand) {\n commandEcho.setInteractive()\n }\n\n if (!answer) {\n logger.info('Operation cancelled. Exiting...')\n process.exit(0)\n }\n}\n"],
|
|
5
|
+
"mappings": "6IAKA,OAAOA,MAAQ,mBACf,OAAOC,MAAU,YACjB,OAAS,KAAAC,MAAS,MCuBX,IAAMC,EAAN,cAA2B,KAAM,CAO7B,IAET,YAAYC,EAAiBC,EAAqB,KAAM,CACtD,MAAMD,CAAO,EACb,KAAK,KAAO,eACZ,KAAK,IAAMC,CACb,CACF,EAgBaC,EAAoBC,GACxBA,aAAiBJ,EC7D1B,OAAOK,MAAQ,mBACf,OAAOC,MAAQ,UAiBR,IAAMC,EAAa,MAAOC,GAAuC,CACtE,GAAI,CACF,aAAMH,EAAG,OAAOG,CAAQ,EAEjB,EACT,MAAQ,CACN,MAAO,EACT,CACF,EAWaC,EAAWD,GAA6B,CACnD,IAAME,EAAOJ,EAAG,QAAQ,EAExB,OAAOE,EAAS,WAAWE,CAAI,EAAI,IAAIF,EAAS,MAAME,EAAK,MAAM,CAAC,GAAKF,CACzE,EFnBA,IAAMG,EAAmB,cAGnBC,EAAsB,EAGtBC,EAAkB,IAGlBC,EAAiB,IAqBjBC,EAAmBC,EACtB,OAAO,CACN,QAASA,EAAE,QAAQJ,CAAmB,EAAE,QAAQA,CAAmB,EACnE,KAAMI,EAAE,OAAOA,EAAE,OAAO,EAAE,IAAI,CAAC,EAAGA,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CACrD,CAAC,EACA,OAAO,EAeGC,EAAoB,SAA6B,CAC5D,IAAMC,EAAQ,MAAMC,EAAuB,EAE3C,OAAOC,EAAK,KAAKA,EAAK,QAAQF,EAAM,WAAW,EAAGP,CAAgB,CACpE,EAeaU,EAAiB,SAAwC,CACpE,IAAMC,EAAY,MAAML,EAAkB,EAEtCM,EAEJ,GAAI,CACFA,EAAM,MAAMC,EAAG,SAASF,EAAW,OAAO,CAC5C,MAAQ,CACN,OAAO,IACT,CAOA,IAAIG,EAEJ,GAAI,CACFA,EAAY,KAAK,MAAMF,CAAG,CAC5B,OAASG,EAAK,CACZ,MAAM,IAAIC,EACR,sCAAsCC,EAAQN,CAAS,CAAC,KAAMI,EAAc,OAAO;AAAA,sFAErF,CACF,CAEA,IAAMG,EAASd,EAAiB,UAAUU,CAAS,EAEnD,GAAI,CAACI,EAAO,QACV,MAAM,IAAIF,EACR,0BAA0BC,EAAQN,CAAS,CAAC,KAAKN,EAAE,cAAca,EAAO,KAAK,CAAC;AAAA,sFAEhF,EAGF,OAAOA,EAAO,IAChB,EAiBaC,EAAkB,MAAOC,GAAqC,CACzE,IAAMT,EAAY,MAAML,EAAkB,EAEpCe,EAAaZ,EAAK,QAAQE,CAAS,EACnCW,EAAcb,EAAK,QAAQY,CAAU,EACrCE,EAAgBd,EAAK,QAAQa,CAAW,EAE9C,QAAWE,IAAO,CAACD,EAAeD,EAAaD,CAAU,EACvD,MAAMR,EAAG,MAAMW,EAAK,CAAE,UAAW,GAAM,KAAMrB,CAAe,CAAC,EAC7D,MAAMU,EAAG,MAAMW,EAAKrB,CAAc,EAGpCsB,EAAoBd,EAAW,GAAG,KAAK,UAAUS,EAAO,KAAM,CAAC,CAAC;AAAA,EAAMlB,CAAe,EAIrF,MAAMW,EAAG,MAAMF,EAAWT,CAAe,CAC3C,EAUawB,EAAW,MAAOC,EAAaC,IAAiC,CAC3E,IAAMC,EAAW,MAAMnB,EAAe,EAEtC,MAAMS,EAAgB,CACpB,QAASlB,EACT,KAAM,CAAE,GAAG4B,GAAU,KAAM,CAACF,CAAG,EAAGC,CAAM,CAC1C,CAAC,CACH,EAYaE,EAAc,MAAOH,GAA+B,CAC/D,IAAME,EAAW,MAAMnB,EAAe,EAEtC,GAAI,CAACmB,GAAY,EAAEF,KAAOE,EAAS,MAAO,OAE1C,IAAME,EAAO,CAAE,GAAGF,EAAS,IAAK,EAEhC,OAAOE,EAAKJ,CAAG,EAEf,MAAMR,EAAgB,CACpB,QAASlB,EACT,KAAA8B,CACF,CAAC,CACH,EGrMA,IAAMC,EAAoB,IAAM,CAC9B,IAAIC,EAAU,GACVC,EAA2B,CAAC,EAC5BC,EAAgB,GAEpB,MAAO,CAWL,MAAMC,EAAoB,CACxBH,EAAUG,EACVF,EAAU,CAAC,EACXC,EAAgB,EAClB,EAMA,gBAAuB,CACrBA,EAAgB,EAClB,EAOA,UAAUE,EAAcC,EAA0C,CAChEJ,EAAQ,KAAK,CAAE,KAAAG,EAAM,MAAAC,CAAM,CAAC,CAC9B,EAKA,eAAwB,CACtB,OAAOJ,EACJ,IAAKK,GACA,OAAOA,EAAI,OAAU,UAChBA,EAAI,MAAQA,EAAI,KAAO,GAG5B,MAAM,QAAQA,EAAI,KAAK,EAClB,GAAGA,EAAI,IAAI,KAAKA,EAAI,MAAM,KAAK,IAAI,CAAC,IAGtC,GAAGA,EAAI,IAAI,KAAKA,EAAI,KAAK,GACjC,EACA,OAAO,OAAO,EACd,KAAK,GAAG,CACb,EAOA,UAAgD,CAC9C,OAAIL,EAAQ,SAAW,EACd,KAGF,CAAE,iBAAkB,KAAK,cAAc,CAAE,CAClD,EASA,OAAc,CACR,CAACC,GAAiBD,EAAQ,SAAW,GAAK,CAACD,GAI/CO,EAAO,KAAK;AAAA,sBAAgDP,CAAO,IAAI,KAAK,cAAc,CAAC;AAAA,CAAI,CACjG,EAKA,OAAc,CACZA,EAAU,GACVC,EAAU,CAAC,EACXC,EAAgB,EAClB,CACF,CACF,EAGaM,EAAcT,EAAkB,ECzG7C,OAAOU,MAAa,eAmBpB,IAAMC,EAAW,GAmCJC,EAAa,MAAUC,EAA6CC,IAAqC,CACpH,IAAMC,EAAa,IAAI,gBACjBC,EAAyB,CAAE,GAAGF,EAAM,OAAQC,EAAW,MAAO,EAEpE,GAAIE,EAAU,GAAK,CAACC,EAAQ,MAAM,MAAO,OAAOL,EAAIG,CAAO,EAE3D,IAAMG,EAAQD,EAAQ,MAEhBE,EAAUC,GAAkB,CAC5BA,EAAM,SAAW,GAAKA,EAAM,CAAC,IAAMV,GAAUI,EAAW,MAAM,CACpE,EAMAO,EAAa,EAEb,GAAI,CASF,IAAMC,EAAUV,EAAIG,CAAO,EAS3B,OAAIG,EAAM,SAAS,GAAGA,EAAM,OAAO,EAEnCA,EAAM,GAAG,OAAQC,CAAM,EAEhB,MAAMG,CACf,QAAE,CAGAJ,EAAM,eAAe,OAAQC,CAAM,EAGnCI,EAAa,CACf,CACF,ECxGA,OAAOC,MAAa,oBACpB,OAAOC,MAAa,eA+Bb,IAAMC,EAAgB,MAAOC,EAAuCC,IAAmC,CAC5G,IAAMC,EAASF,EACX,GACA,MAAMG,EAAYC,GACTC,EAAQ,CAAE,QAAAJ,CAAQ,EAAGG,CAAO,CACpC,EAEAJ,GACHM,EAAY,eAAe,EAGxBJ,IACHK,EAAO,KAAK,iCAAiC,EAC7CC,EAAQ,KAAK,CAAC,EAElB",
|
|
6
|
+
"names": ["fs", "path", "z", "EnvAuthError", "message", "env", "isEnvAuthFailure", "error", "fs", "os", "fileExists", "filePath", "tildify", "home", "TOKEN_STORE_FILE", "TOKEN_STORE_VERSION", "TOKEN_FILE_MODE", "TOKEN_DIR_MODE", "tokenStoreSchema", "z", "getTokenStorePath", "paths", "getInfraKitConfigPaths", "path", "readTokenStore", "storePath", "raw", "fs", "parsedRaw", "err", "EnvAuthError", "tildify", "result", "writeTokenStore", "store", "projectDir", "projectsDir", "userConfigDir", "dir", "atomicWriteFileSync", "setToken", "env", "token", "existing", "removeToken", "envs", "createCommandEcho", "cliPath", "options", "isInteractive", "path", "flag", "value", "opt", "logger", "commandEcho", "process", "ESC_BYTE", "withEscape", "run", "base", "controller", "context", "isMcpMode", "process", "input", "onData", "chunk", "acquireStdin", "pending", "releaseStdin", "confirm", "process", "confirmOrExit", "confirmedCommand", "message", "answer", "withEscape", "context", "confirm", "commandEcho", "logger", "process"]
|
|
7
7
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{A as
|
|
2
|
+
import{$ as $t,A as pt,B as ft,C as gt,D as te,E as ht,F as I,G as d,H as vt,I as yt,J as wt,K as St,L as kt,M as Rt,N as Et,O as xt,P as Tt,Q as bt,R as At,S as oe,T as Pt,U as Ct,V as It,W as Nt,X as Lt,Y as _t,Z as Dt,_ as Ot,a as D,b as De,ba as Ft,c as Oe,ca as Mt,da as Gt,e as Me,f as Ue,g as Ge,h as O,i as Xe,j,k as Qe,l as Ze,m as et,n as tt,o as ot,p as nt,q as it,r as at,s as Z,t as st,u as ct,v as ee,w as lt,x as mt,y as dt,z as ut}from"./chunk-M5FC2MOJ.js";import{u as re}from"./chunk-ADCSGQAU.js";import{a as jt,b as U,c as Ut,d as A,e as ne,f as ie,g as ae}from"./chunk-2QJAIOPI.js";import{a as rt}from"./chunk-LJVPQQ75.js";import{a as $e,b as Fe}from"./chunk-LKKKDB6V.js";import{b as je,c as T,d as h,e as F,f as He,g as Ye,h as ze,i as b,j as M}from"./chunk-VISEADOF.js";import{B as l,W as X,X as Le,Z as _e,a as Ie,r as Ne}from"./chunk-DWZSB6O6.js";import{a as $,b as qe,d as Ve,f as We,g as Je,j as Q,k as Ke,n as v,q as Be}from"./chunk-CHETBZ6M.js";import"./chunk-X46DP23W.js";import tn,{Separator as xo}from"@inquirer/select";import{realpathSync as on}from"node:fs";import g from"node:process";import{fileURLToPath as bo}from"node:url";var Po=(e,t)=>{let o=e,r;for(let n of t){if(r=o.find(a=>a.name()===n),!r)return;o=r.commands}return r},qt=e=>Ot.flatMap(({key:t,label:o})=>Ft(t).flatMap(r=>{let n=Po(e,r.groupPath);return n?[{name:r.groupPath.join(" "),description:n.description(),group:o}]:[]}));import{Command as mr}from"commander";import Y from"node:process";import Vt from"node:process";import{$ as Co}from"zx";var se=async()=>{let e=await X(),t=await Promise.all([{label:"project (committed)",path:e.main},{label:"user global",path:e.userGlobal},{label:"user project",path:e.userProject}].map(async s=>({...s,exists:await T(s.path)}))),o=t.at(-1)?.exists===!0,r=await lt(e.userProject,o),n=o?mt(r):"";l.info(`Project name: ${e.projectName}
|
|
3
3
|
`),l.info(`Config merge chain (later overrides earlier):
|
|
4
|
-
`);for(let
|
|
5
|
-
`))},
|
|
6
|
-
`),he=async({env:e,stdin:t,fromEnv:o,force:r})=>{let
|
|
7
|
-
`;var K=(e,t=!0)=>({line:e,reproducible:t});import H from"node:fs";import cr from"node:os";import lr from"node:path";import oo from"node:process";var Y="INFRA_KIT_SESSION_REPORT",Ee=null,Zt=!1,eo=[],ro=(e=oo.env)=>{Zt||(Ee=e[Y]??null,Zt=!0,delete e[Y])};var no=(e,t)=>{if(!Ee)return;let o=e.summary??(eo.length>0?[...eo]:void 0),r={...e,...o?{summary:o}:{}},s=t?.write??((n,i)=>{H.writeFileSync(n,i)});try{s(Ee,JSON.stringify(r))}catch{}},to=0,io=e=>{let t=e?.tmpdir?.()??cr.tmpdir(),o=e?.pid??oo.pid;return to+=1,lr.join(t,`infra-kit-session-${o}-${to}.json`)},so=(e,t)=>{if(!(t?.exists??(i=>H.existsSync(i)))(e))return null;let r=t?.read??(i=>H.readFileSync(i,"utf-8")),s=t?.unlink??(i=>H.unlinkSync(i)),n;try{n=r(e)}catch{return null}finally{try{s(e)}catch{}}try{return JSON.parse(n)}catch{return null}};var dr=(e,t)=>[...t,e],xe=e=>typeof e=="string"?e.split(",").filter(Boolean):void 0,ao=(e,t)=>{if(!(typeof e>"u")){if(e===!0)return"workspace";if(e===!1)return"none";if(typeof e=="string"&&oe.includes(e))return e;throw new Error(`Invalid ${t} value "${String(e)}". Expected one of: ${oe.join(", ")}.`)}},ur=e=>e.description("Merge dev branch into every release branch").option("-a, --all","Select all active release branches").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await vt({all:t.all,confirmedCommand:t.yes}))}),pr=e=>e.description("List all release branches").action(async()=>{d(await kt())}),fr=e=>e.description("Create one or more release branches (each entry can mix regular/hotfix and its own description)").option("-r, --release <spec>",'Release spec "<version|next|name>[:type[:description]]" (repeatable). The token is a semver ("1.2.5"), the literal "next", or a kebab-case name ("checkout-redesign"). Type is regular|hotfix (default regular). Examples: "1.2.5", "1.2.5:hotfix", "next:regular:Holiday backend", "checkout-redesign:regular:Q3 redesign".',dr,[]).option("-y, --yes","Skip confirmation prompt").action(async t=>{let r=t.release.map(Rt),s=r.length>0?r:void 0;d(await Et({releases:s,confirmedCommand:t.yes}))}),gr=e=>e.description("Edit a release's description in Jira and in the matching GitHub PR body").option("-v, --version <version>","Release version (e.g. 1.2.5) or release name (e.g. checkout-redesign)").option("-d, --description <description>",'New description (use "" to clear)').option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await xt({version:t.version,description:t.description,confirmedCommand:t.yes}))}),hr=e=>e.description("Deploy any release branch to any environment").option("-v, --version <version>",'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; "dev" deploys from the dev branch').option("-e, --env <env>","Specify the environment to deploy to, e.g. dev").option("--skip-terraform","Skip terraform deployment step").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await wt({version:t.version,env:t.env,skipTerraform:t.skipTerraform,confirmedCommand:t.yes}))}),vr=e=>e.description("Deploy selected services from release branch to any environment").option("-v, --version <version>",'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; "dev" deploys from the dev branch').option("-e, --env <env>","Specify the environment to deploy to, e.g. dev").option("-s, --services <services...>","Specify services to deploy, e.g. client-be client-fe").option("--skip-terraform","Skip terraform deployment step").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await St({version:t.version,env:t.env,services:t.services,skipTerraform:t.skipTerraform,confirmedCommand:t.yes}))}),yr=e=>e.description("Release a new version to production").option("-v, --version <version>","Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deliver").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await yt({version:t.version,confirmedCommand:t.yes}))}),wr=e=>e.description("Remove release worktrees whose PRs are no longer open").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await _t({confirmedCommand:t.yes}))}),Sr=e=>e.description("Add git worktrees for release branches").option("-y, --yes","Skip confirmation prompt").option("-a, --all","Select all active release branches").option("-v, --versions <versions>","Specify versions by comma, e.g. 1.2.5, 1.2.6").option("-i, --ide [mode]","Editor mode for created worktrees: workspace (default) | none").option("--no-ide","Skip the editor (alias for --ide none)").option("-c, --cursor [mode]","Deprecated alias for --ide").option("--no-cursor","Deprecated alias for --no-ide").option("-g, --github-desktop","Open created worktrees in GitHub Desktop").option("--no-github-desktop","Skip GitHub Desktop prompt").option("-m, --cmux","Open created worktrees in cmux (3-pane layout)").option("--no-cmux","Skip cmux prompt").action(async t=>{let o=ao(t.ide,"--ide")??ao(t.cursor,"--cursor");d(await Nt({confirmedCommand:t.yes,all:t.all,versions:t.versions,ide:o,githubDesktop:t.githubDesktop,cmux:t.cmux}))}),kr=e=>e.description("List all git worktrees with detailed information").action(async()=>{d(await Lt())}),Rr=e=>e.description("Remove git worktrees for release branches").option("-y, --yes","Skip confirmation prompt").option("-a, --all","Select all active release branches").option("-v, --versions <versions>","Specify versions by comma, e.g. 1.2.5, 1.2.6").action(async t=>{d(await Dt({confirmedCommand:t.yes,all:t.all,versions:t.versions}))}),Er=e=>e.description("Reopen editor + cmux windows for every active worktree in the current project (additive, idempotent)").option("--all","Reopen across every discovered infra-kit project (Stage 3 \u2014 not yet implemented)").option("--project <names...>","Restrict --all to these project names").option("--root <paths...>","Discovery roots for --all (repeatable)").option("--release-only","Restrict to release worktrees (reproduces the legacy worktrees-reload scope)").option("--force","Close each cmux workspace first, then reopen (the legacy worktrees-reload behaviour)").option("--dry-run","Print the plan (paths + cmux titles) and spawn nothing").action(async t=>{d(await Tt({all:t.all,project:t.project,root:t.root,releaseOnly:t.releaseOnly,force:t.force,dryRun:t.dryRun}))}),xr=e=>e.description("Show the machine-local factory config (~/.infra-kit/vendor.json) or scaffold it with --init").option("--init","Scaffold ~/.infra-kit/vendor.json (skips if it already exists)").action(async t=>{d(await Re({init:t.init}))}),Tr=e=>e.description("Verify vendor/ matches vendor/.sync-manifest.json (self-contained; for any consumer repo)").action(async()=>{let t=await At();d(t),t.structuredContent.ok||(_.exitCode=1)}),Ar=e=>e.description("Source-aware drift check (rsync dry-run) of each target vendored subtree vs the source").option("-r, --repos <repos>","Restrict to comma-separated target repo names").action(async t=>{let o=await Ct({repos:xe(t.repos)});d(o),o.structuredContent.ok||(_.exitCode=1)}),Cr=e=>e.description("Show the resolved config merge chain and file paths").action(async()=>{d(await ae())}),br=e=>e.description("Open the user-scope per-project override file in $EDITOR").action(async()=>{d(await ce())}),Pr=new Set(["init","doctor","version","dev","self-update","mcp"]),Ir=e=>e.startsWith("env-")||Pr.has(e),Nr=new Set(["env-autoload","mcp","version","self-update"]),z=e=>{let t=[];for(let o=e;o&&o.parent;o=o.parent)t.unshift(o.name());return t.join(" ")},Te=()=>{let e=new mr,t=e.command("release").description("Release management commands");ur(t.command("merge-dev")),pr(t.command("list")),fr(t.command("create")),gr(t.command("desc-edit")),hr(t.command("deploy-all")),vr(t.command("deploy-selected")),yr(t.command("deliver"));let o=e.command("worktrees").description("Git worktree management commands");Sr(o.command("add")),kr(o.command("list")),Rr(o.command("remove")),wr(o.command("sync")),Er(e.command("reopen"));let r=e.command("config").description("Manage infra-kit configuration files");Cr(r.command("path")),br(r.command("edit")),e.command("audit").description("Audit against infra-kit.config.ts rules (--all for every package, --root for the monorepo root)").option("-a, --all","Audit every non-vendor workspace package").option("-r, --root","Audit the monorepo root (turbo pipeline + root commands)").action(async n=>{let i=await _e({all:n.all,root:n.root});d(i),i.structuredContent.allPassed||(_.exitCode=1)});let s=e.command("vendor").description("Verify and sync the mirrored vendor/ tree");return Tr(s.command("check")),s.command("sync").description("Copy vendored files from the source repo into each target and regenerate manifests").option("-y, --yes","Skip confirmation prompt").option("-r, --repos <repos>","Restrict to comma-separated target repo names").action(async n=>{d(await Pt({confirmedCommand:n.yes,repos:xe(n.repos)}))}),s.command("manifest").description("Regenerate each target vendor/.sync-manifest.json + README from current content (no copy)").option("-r, --repos <repos>","Restrict to comma-separated target repo names").action(async n=>{d(await bt({confirmedCommand:!0,repos:xe(n.repos)}))}),Ar(s.command("diff")),xr(s.command("config")),e.command("doctor").description("Check installation and authentication status of gh and doppler CLIs").option("--fix","Remove portless routes left behind by a dev-server that was killed (kill -9, OOM, force-quit). Refuses while a dev session is running, when a booting UI is indistinguishable from a dead route.").option("--ascii","Render the report with ASCII markers instead of unicode glyphs (check messages are unchanged)").action(async n=>{let i=await mt({fix:!!n.fix});I.enabled||dt(i.structuredContent.checks,{ascii:!!n.ascii}),d(i)}),e.command("self-update").description("Update this CLI using the package manager that installed it").option("--dry-run","Print the detected manager and the command that would run; install nothing").action(n=>{we({dryRun:!!n.dryRun})}),e.command("mcp").description("Run the infra-kit MCP server (stdio transport)").action(()=>{ve()}),e.command("dev").description("Run local dev servers for a named devServersPresets preset (or all apps); api + ui").argument("[preset]","Named preset from devServersPresets (omit to run every app)").option("-w, --watch","Rebuild and restart on file save").option("--app <names>","Further narrow to these app folder names (comma-separated)").option("--target <keys>","Run exactly these <app>/api|<app>/ui packages (comma-separated); part-level, unlike --app").option("--cmux","Run each app in its own cmux pane (one workspace, N panes; falls back to single terminal if cmux is unavailable)").option("--self","Run only the app of the current directory (infer from cwd; use inside apps/<app>/\u2026)").option("-V, --verbose","Print full boot narration (default: quiet; full detail always in the session log)").option("--routes","Print each app\u2019s registered METHOD /path routes at startup (default: off)").option("--no-ui-health","Do not probe the frontends\u2019 liveness (vite\u2019s HMR ping); their rows carry no health dot (also: INFRA_KIT_NO_UI_HEALTH=1)").action(async(n,i)=>{let{runDevServerCli:c}=await import("./dev-server.js"),a=!!(_.stdout.isTTY&&_.stdin.isTTY);await c({...i,preset:n},a,I.enabled)}),e.command("version").description("Print the installed infra-kit CLI version").action(async()=>{d(await It())}),e.command("env-status").description("Show which env is loaded in this session (local introspection; no Doppler call)").action(async()=>{d(await ft())}),e.command("env-list").description("List available Doppler configs for the detected project, and whether a service token resolves for each").action(async()=>{d(await pt())}),e.command("init").description("Inject shell integration into .zshrc and sync repo agent-instruction files").action(async()=>{d(await ee())}),e.command("env-load").description("Load Doppler env vars for a config. Source the returned file path to apply.").option("-c, --config <config>","Environment config name to load (e.g. dev, arthur)").action(async n=>{d(await ze({config:n.config}))}),e.command("env-clear").description("Clear loaded env vars. Source the returned file path to apply.").option("--purge","Also delete this project's warm cache outright (durable disable)").action(async n=>{d(await ut({purge:!!n.purge}))}),e.command("env-token-set").description("Store the Doppler service token for an env (masked prompt; validated against Doppler before writing)").argument("<env>","Environment / Doppler config the token is scoped to (e.g. dev)").option("--stdin","Read the token from stdin instead of prompting (e.g. from a password manager)").option("--from-env <var>","Read the token from the named environment variable (the NAME, never the value)").option("--force","Store even when the token\u2019s scope could not be verified. Never overrides a real mismatch.").action(async(n,i)=>{d(await he({env:n,stdin:i.stdin,fromEnv:i.fromEnv,force:i.force}))}),e.command("env-token-list").description("Show which envs have a Doppler service token (redacted), and where it came from").option("--check","Also ask Doppler whether each token is valid and correctly scoped").action(async n=>{d(await gt({check:!!n.check}))}),e.command("env-token-remove").description("Delete an env\u2019s Doppler service token from the local store (does NOT revoke it in Doppler)").argument("<env>","Environment / Doppler config whose token to remove").action(async n=>{d(await fe({env:n}))}),e.command("env-autoload",{hidden:!0}).description("Internal: prime env for the shell-startup auto-load trigger").option("--project-dir <dir>","Canonical project dir for the warm-cache key (shell-startup only)").action(async n=>{await pe({projectDir:n.projectDir})}),e.commands.forEach(ht),e.hook("preAction",async(n,i)=>{A.start(z(i)),I.enabled=!!i.optsWithGlobals().json,I.enabled&&(l.level="warn"),Nr.has(z(i))||await rt(),V(),Ir(i.name())||await D({expectedTrigger:"cli-invocation"})}),e.hook("postAction",(n,i)=>{let c=z(i);if(Mt(c))return;let m=A.snapshot()?.formattedOptions??"",u=m?` ${m}`:"",f=`infra-kit ${c}${u}`;no({equivalent:K(f,!0)})}),e};var Ae=(e,t=" ")=>{let o=e.reduce((r,[s])=>Math.max(r,s.length),0);return e.map(([r,s])=>`${r.padEnd(o)}${t}${s}`)};import{chalkStderr as Ur}from"chalk";import{spawn as Gr}from"node:child_process";import S from"node:process";import{Chalk as Lr}from"chalk";var b=new Lr({level:1}),w={ok:"ok",findingsPlain:"completed with findings",failed:"failed",cancelled:"cancelled",findingsSuffix:"findings",sep:" \xB7 ",reproPrefix:"$ ",nonReproPrefix:"\u2248 ",envNotice:"Applies to your shell after you exit this session."},co={ok:{unicode:"\u2713",ascii:"[ok]"},findings:{unicode:"\u26A0",ascii:"[!]"},failed:{unicode:"\u2717",ascii:"[x]"},cancelled:{unicode:"\u2298",ascii:"[-]"}},lo={unicode:"\u2500",ascii:"-"},Dr=3,Ce=e=>e,_r={ok:e=>b.green(e),findings:e=>b.yellow(e),failed:e=>b.red(e),cancelled:e=>b.gray(e)},uo=e=>e?{dim:t=>b.dim(t),bold:t=>b.bold(t)}:{dim:Ce,bold:Ce},Or=(e,t)=>e?_r[t]:Ce,mo=e=>[...e].length,po=(e,t={})=>{let o=uo(t.color===!0);return`${o.dim(w.reproPrefix.trim())} ${o.bold(e)}`},$r=e=>e<1e3?`${Math.round(e)}ms`:`${(e/1e3).toFixed(1)}s`,Fr=(e,t)=>e==="findings"?t!=null?`${t} ${w.findingsSuffix}`:w.findingsPlain:{ok:w.ok,failed:w.failed,cancelled:w.cancelled}[e],Mr=(e,t,o,r)=>{if(t==null)return"";let s=t-2-e;return s<Dr?"":` ${r(o.repeat(s))}`},fo=e=>{let t=e.color===!0,o=uo(t),r=Or(t,e.outcome),s=e.ascii===!0,n=e.equivalent.reproducible?w.reproPrefix:w.nonReproPrefix,i=s?co[e.outcome].ascii:co[e.outcome].unicode,c=Fr(e.outcome,e.findingsCount),a=[$r(e.durationMs)],m=e.summary?.[0]?.split(`
|
|
8
|
-
`)[0];m!=null&&m.length>0&&
|
|
9
|
-
`)};var
|
|
4
|
+
`);for(let s of t){let i=s.exists?" [\u2713]":" [ ]",c=s.path===e.userProject&&n!==""?` ${n}`:"";l.info(`${i} ${s.label.padEnd(22)} ${h(s.path)}${c}`)}let a={projectName:e.projectName,layers:t.map(s=>({label:s.label,path:s.path,exists:s.exists})),hasOverrides:r.hasOverrides,overrideKeys:r.overrideKeys};return{content:[{type:"text",text:JSON.stringify(a,null,2)}],structuredContent:a}},ce=async()=>{let e=await X(),t=Vt.env.EDITOR||Vt.env.VISUAL||"vi",o=await nt(e);o.createdConfig&&l.info(it(o)),l.info(`Opening ${h(e.userProject)} in ${t}`),await Co({stdio:"inherit"})`${t} ${e.userProject}`,_e();let r={path:e.userProject,editor:t};return{content:[{type:"text",text:JSON.stringify(r,null,2)}],structuredContent:r}};var le=e=>{if(typeof e!="object"||e===null)return null;let{config:t,reason:o,at:r}=e;return typeof t!="string"||t.length===0||typeof o!="string"||typeof r!="number"||!Number.isFinite(r)?null:{config:t,reason:o,at:r}},N=e=>`infra-kit: Doppler token for env "${e}" is missing, invalid, or unreadable \u2014 env auto-load is not running. Fix: run \`infra-kit env-token-set ${e}\` (mint one at https://dashboard.doppler.com under this config's Access tab).`;import f from"node:fs";import y from"node:path";import L from"node:process";var Io="autoload-warn-fail.flag",me="autoload-warn-auth-fail.flag",de="autoload-fail.flag",ue="autoload-auth-fail.json",No=3e4,Jt=async(e=!0)=>{let t;try{t=await Le()}catch{return null}let o=t.envAutoLoad;if(!o)return null;let r={trigger:o.trigger,config:o.config,project:t.envManagement.config.name};try{if(await Xe(o.config))return r}catch{return r}return Bt({config:o.config,reason:`No Doppler service token for env "${o.config}"`,at:Date.now()}),e&&G(N(o.config),me),null},Kt=e=>{let{trigger:t,expectedTrigger:o,targetConfig:r,targetProject:n,env:a,force:s}=e;return t!==o||!a.session||a.cleared||a.currentConfig&&!a.autoLoadedMarker||!s&&a.autoLoadedMarker&&a.currentConfig===r&&a.currentProject===n?"skip":"load"},_=async({expectedTrigger:e,projectDir:t,force:o,isAuthFailure:r=je})=>{let n=e==="cli-invocation";n&&q();let a=null;try{if(a=await Jt(n),!a||Kt({trigger:a.trigger,expectedTrigger:e,targetConfig:a.config,targetProject:a.project,env:Do(),force:o})==="skip"||Wt()||Fo())return null;let i=Oo(),c=await Qe({config:a.config,autoLoaded:!0,projectDir:t,beforeWrite:()=>!Wt()&&!$o(i)});return c?(jo(),_o(),c.filePath):null}catch(s){let i=s.message,c=r(s);return Mo(),c&&a&&Bt({config:a.config,reason:i,at:Date.now()}),n&&c&&a?(G(N(a.config),me),l.debug(`env auto-load auth failure: ${i}`)):n?G(`infra-kit: env auto-load failed \u2014 ${i} (will retry later)`,Io):l.debug(`env auto-load skipped: ${i}`),null}},q=()=>{let e=Lo();e&&(G(N(e.config),me),l.debug(`env auto-load auth failure (recorded ${new Date(e.at).toISOString()}): ${e.reason}`))},Lo=()=>{try{let e=f.readFileSync(y.join(v(),ue),"utf-8");return le(JSON.parse(e))}catch{return null}},Bt=e=>{try{let t=v();f.mkdirSync(t,{recursive:!0,mode:448}),Be(y.join(t,ue),JSON.stringify(e),384)}catch{}},_o=()=>{try{f.rmSync(y.join(v(),ue),{force:!0})}catch{}},Do=()=>({session:L.env[Ve],cleared:L.env[Ke],currentConfig:L.env[We],currentProject:L.env[Je],autoLoadedMarker:L.env[Q]}),Oo=()=>{try{return f.statSync(y.join(v(),$)).mtimeMs}catch{return null}},$o=e=>{try{let t=y.join(v(),$);if(!f.existsSync(t))return!1;let o=f.statSync(t).mtimeMs;return e!==null&&o<=e?!1:new RegExp(`^unset ${Q}$`,"m").test(f.readFileSync(t,"utf-8"))}catch{return!1}},Wt=()=>{try{let e=v(),t=y.join(e,qe);if(!f.existsSync(t))return!1;let o=y.join(e,$);return f.existsSync(o)?f.statSync(t).mtimeMs>=f.statSync(o).mtimeMs:!0}catch{return!1}},Fo=()=>{try{let e=y.join(v(),de);return f.existsSync(e)?Date.now()-f.statSync(e).mtimeMs<No:!1}catch{return!1}},Mo=()=>{try{let e=v();f.mkdirSync(e,{recursive:!0,mode:448}),f.writeFileSync(y.join(e,de),"",{mode:384})}catch{}},jo=()=>{try{f.rmSync(y.join(v(),de),{force:!0})}catch{}},G=(e,t)=>{try{let o=v(),r=y.join(o,t);if(f.existsSync(r))return;f.mkdirSync(o,{recursive:!0,mode:448}),f.writeFileSync(r,"",{mode:384})}catch{}l.warn(e)};var pe=async({projectDir:e}={})=>{await _({expectedTrigger:"shell-startup",projectDir:e,force:!0})};var Ht="https://dashboard.doppler.com/workplace/projects",Uo=(e,t)=>["Removing it locally does NOT revoke it \u2014 the token still works anywhere else it is stored.",`Revoke it in Doppler (project "${e}", config "${t}"): ${Ht}/${e}`],fe=async({env:e})=>{let o=!!(await He())?.envs[e];await ze(e);let r=await j(),n=await O(),a=await F();o?l.info(`Removed the "${e}" service token from ${h(a)}.`):l.info(`No "${e}" service token was stored in ${h(a)} \u2014 nothing to remove.`),r.length>0&&l.info(`Purged ${r.length} warm cache(s) across this repo's worktrees.`);for(let i of Uo(n,e))l.warn(i);let s={env:e,removed:o,storePath:a,warmCachesPurged:r.length,revokeUrl:`${Ht}/${n}`};return{content:D(JSON.stringify(s,null,2)),structuredContent:s}};import Go from"@inquirer/password";import W from"node:process";import{$ as V}from"zx";var qo=async({stdin:e,fromEnv:t})=>{if(e)return{token:await Vo(),source:"stdin"};if(t){let r=W.env[t];if(!r)throw new Error(`${t} is not set (or is empty) \u2014 nothing to store.`);return{token:r,source:"env"}}return b.setInteractive(),{token:(await M(r=>Go({message:"Paste the Doppler service token (input is hidden)",mask:!0},r),{output:W.stderr})).trim(),source:"prompt"}},Vo=async()=>{let e=[];W.stdin.setEncoding("utf8");for await(let t of W.stdin)e.push(t);return e.join("").trim()},ge="DOPPLER_CONFIG",Wo=3e4,Jo=async(e,t,o)=>{let r=V.quiet;V.quiet=!0;let n;try{n=(await V({env:et(e)})`doppler secrets download --no-file --format json --project ${t} --config ${o}`.timeout(Wo)).stdout}catch(a){throw Ko(a,o)}finally{V.quiet=r}return ot(n)},Ko=(e,t)=>{let o=Ie(e)??(e instanceof Error?e.message:String(e));if(Ue(o)!=="auth")return e instanceof Error?e:new Error(String(e));let r=Ge(o)==="mis-scoped"?"it is scoped to a DIFFERENT config (pasting another environment's token here is the mistake this check exists to catch).":"it is invalid or has been revoked.";return new Error([`Doppler refused this token for config "${t}" \u2014 ${r}`,"Nothing was written. Issue a service token scoped to that config and try again."].join(`
|
|
5
|
+
`))},Bo=e=>[`Could not verify this token's scope: the Doppler payload for "${e}" carries no ${ge}.`,"Refusing to store a credential whose scope is unknown (a token for the wrong environment would load","the wrong secrets into every shell).","Re-run with --force if you are certain the token is scoped to this config."].join(`
|
|
6
|
+
`),he=async({env:e,stdin:t,fromEnv:o,force:r})=>{let n=await O(),{token:a,source:s}=await qo({stdin:t,fromEnv:o});if(!a)throw new Error("No token provided \u2014 nothing was written.");let i=await Jo(a,n,e);tt(i,e);let c=i.some(([k])=>k===ge);if(!c&&!r)throw new Error(Bo(e));await Ye(e,a);let m=await j(),u=await F();l.info(`Stored the "${e}" service token (${te(a)}) in ${h(u)} (mode 0600).`),c||l.warn(`Scope was NOT verified (no ${ge} in the payload) \u2014 written because --force was given.`),m.length>0&&l.info(`Purged ${m.length} warm cache(s) so the next shell cannot serve secrets fetched with an old token.`);let p={env:e,source:s,redactedToken:te(a),storePath:u,scopeVerified:c,warmCachesPurged:m.length};return b.print(),{content:D(JSON.stringify(p,null,2)),structuredContent:p}};import{spawn as Ho}from"node:child_process";import J from"node:process";import{fileURLToPath as Yo}from"node:url";var zo=["SIGINT","SIGTERM"],Yt=()=>Yo(new URL("./mcp.js",import.meta.url)),ve=(e={})=>{let t=e.spawn??Ho,o=e.exit??(s=>J.exit(s)),r=e.env??J.env,n=e.onError??(s=>l.error(s)),a=t(J.execPath,[Yt()],{stdio:"inherit",env:$e(r)});a.on("error",s=>{n(`failed to launch the MCP server: ${s.message}`),o(1)}),zo.forEach(s=>{J.on(s,()=>{a.kill(s)})}),a.on("exit",(s,i)=>{o(i?1:s??1)})};import{spawnSync as Xo}from"node:child_process";import{realpathSync as Qo}from"node:fs";import{homedir as Zo}from"node:os";import ye from"node:process";import{fileURLToPath as er}from"node:url";var tr=()=>Qo(er(import.meta.url)),or=(e,t,o,r)=>e.error?(o(`${t} not found on PATH: ${e.error.message}`),r(1)):e.signal?(o(`update terminated by signal ${e.signal}`),r(1)):r(e.status??1),we=({dryRun:e},t={})=>{let o=t.spawnSync??Xo,r=t.print??(R=>l.info(R)),n=t.exit??(R=>ye.exit(R)),a=t.env??ye.env,s=t.selfRealPath??tr(),i=t.lazyNpmRoot??Ut,{manager:c,updateCommand:m,canSelfSpawn:u}=jt({selfRealPath:s,env:a,realpath:A,lazyNpmRoot:i}),p=m.join(" ");if(e){r(`Detected install manager: ${c}`),r(`Would run: ${p}`);return}if(!u){r(`Detected install manager: ${c}`),r(`Run this yourself: ${p}`),r(c==="homebrew"?"Not run for you: Homebrew manages this install; running a package manager would create a split-brain install.":"Not run for you: the install location is unrecognized, so the command above is a guess \u2014 a guessed global install is worse than a printed one.");return}let k=o(m[0],m.slice(1),{stdio:"inherit",shell:ye.platform==="win32",cwd:Zo(),env:Fe(a)});or(k,c,r,n)};import{VENDOR_CONFIG_FILE as Xt}from"@slip-stream-kit/config/internal";import Se from"node:fs/promises";import ke from"node:path";import zt from"node:process";import{pathToFileURL as rr}from"node:url";var Qt="~/projects",Re=async(e={})=>{if(e.init){await ir(e.cwd);return}await nr()},nr=async()=>{let e=Z(),t=await T(e);if(l.info(`Factory config: ${h(e)} ${t?"[\u2713]":"[ ]"}`),!t){l.info("\nNot found \u2014 run `infra-kit vendor config --init` to scaffold it."),zt.exitCode=1;return}let{workspaceDir:o,targets:r}=await ct(),n=st(o),a=await T(n);l.info(`workspaceDir: ${o} (resolved: ${n}) ${a?"[\u2713 exists]":"[ ] not found"}`),l.info("Targets:");let s=a;for(let i of r){let c=ke.join(n,i),m=await T(c);m||(s=!1);let u=m?"[\u2713]":"[ ]",p=m?"":" (not found \u2014 clone or remove)";l.info(` ${u} ${i} ${h(c)}${p}`)}s||(zt.exitCode=1)},ir=async e=>{let t=Z();if(await T(t)){l.info(`Factory config already exists at ${h(t)} \u2014 leaving it untouched.`);return}let o=e??await Ne(),r=await ar(o);await Se.mkdir(ke.dirname(t),{recursive:!0}),await Se.writeFile(t,sr(r),"utf-8"),l.info(`\u2713 Created ${h(t)}`),r.length>0&&l.info(` Seeded ${r.length} target(s) from the source ${Xt}.`),l.info(` Edit \`workspaceDir\` (placeholder: ${Qt}) to point at where your repos live.`),r.length===0&&l.info(" Add at least one repo name to `targets` before running vendor sync/manifest/diff.")},ar=async e=>{try{let t=ke.join(e,Xt),o=await Se.stat(t),a=(await import(`${rr(t).href}?mtime=${Number(o.mtimeMs)}`)).default,s=typeof a=="function"?await a():a;if(s&&typeof s=="object"&&"targets"in s){let i=s.targets;if(Array.isArray(i)&&i.every(c=>typeof c=="string"))return i}}catch{}return[]},sr=e=>`${JSON.stringify({workspaceDir:Qt,targets:e},null,2)}
|
|
7
|
+
`;var K=(e,t=!0)=>({line:e,reproducible:t});import B from"node:fs";import cr from"node:os";import lr from"node:path";import oo from"node:process";var H="INFRA_KIT_SESSION_REPORT",Ee=null,Zt=!1,eo=[],ro=(e=oo.env)=>{Zt||(Ee=e[H]??null,Zt=!0,delete e[H])};var no=(e,t)=>{if(!Ee)return;let o=e.summary??(eo.length>0?[...eo]:void 0),r={...e,...o?{summary:o}:{}},n=t?.write??((a,s)=>{B.writeFileSync(a,s)});try{n(Ee,JSON.stringify(r))}catch{}},to=0,io=e=>{let t=e?.tmpdir?.()??cr.tmpdir(),o=e?.pid??oo.pid;return to+=1,lr.join(t,`infra-kit-session-${o}-${to}.json`)},ao=(e,t)=>{if(!(t?.exists??(s=>B.existsSync(s)))(e))return null;let r=t?.read??(s=>B.readFileSync(s,"utf-8")),n=t?.unlink??(s=>B.unlinkSync(s)),a;try{a=r(e)}catch{return null}finally{try{n(e)}catch{}}try{return JSON.parse(a)}catch{return null}};var dr=(e,t)=>[...t,e],so=(e,t)=>{if(!(typeof e>"u")){if(e===!0)return"workspace";if(e===!1)return"none";if(typeof e=="string"&&oe.includes(e))return e;throw new Error(`Invalid ${t} value "${String(e)}". Expected one of: ${oe.join(", ")}.`)}},ur=e=>e.description("Merge dev branch into every release branch").option("-a, --all","Select all active release branches").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await yt({all:t.all,confirmedCommand:t.yes}))}),pr=e=>e.description("List all release branches").action(async()=>{d(await Rt())}),fr=e=>e.description("Create one or more release branches (each entry can mix regular/hotfix and its own description)").option("-r, --release <spec>",'Release spec "<version|next|name>[:type[:description]]" (repeatable). The token is a semver ("1.2.5"), the literal "next", or a kebab-case name ("checkout-redesign"). Type is regular|hotfix (default regular). Examples: "1.2.5", "1.2.5:hotfix", "next:regular:Holiday backend", "checkout-redesign:regular:Q3 redesign".',dr,[]).option("-y, --yes","Skip confirmation prompt").action(async t=>{let r=t.release.map(Tt),n=r.length>0?r:void 0;d(await bt({releases:n,confirmedCommand:t.yes}))}),gr=e=>e.description("Edit a release's description in Jira and in the matching GitHub PR body").option("-v, --version <version>","Release version (e.g. 1.2.5) or release name (e.g. checkout-redesign)").option("-d, --description <description>",'New description (use "" to clear)').option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await At({version:t.version,description:t.description,confirmedCommand:t.yes}))}),hr=e=>e.description("Deploy any release branch to any environment").option("-v, --version <version>",'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; "dev" deploys from the dev branch').option("-e, --env <env>","Specify the environment to deploy to, e.g. dev").option("--skip-terraform","Skip terraform deployment step").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await St({version:t.version,env:t.env,skipTerraform:t.skipTerraform,confirmedCommand:t.yes}))}),vr=e=>e.description("Deploy selected services from release branch to any environment").option("-v, --version <version>",'Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deploy; "dev" deploys from the dev branch').option("-e, --env <env>","Specify the environment to deploy to, e.g. dev").option("-s, --services <services...>","Specify services to deploy, e.g. client-be client-fe").option("--skip-terraform","Skip terraform deployment step").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await kt({version:t.version,env:t.env,services:t.services,skipTerraform:t.skipTerraform,confirmedCommand:t.yes}))}),yr=e=>e.description("Release a new version to production").option("-v, --version <version>","Version (e.g. 1.2.5) or release name (e.g. checkout-redesign) to deliver").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await wt({version:t.version,confirmedCommand:t.yes}))}),wr=e=>e.description("Remove release worktrees whose PRs are no longer open").option("-y, --yes","Skip confirmation prompt").action(async t=>{d(await Dt({confirmedCommand:t.yes}))}),Sr=e=>e.description("Add git worktrees for release branches").option("-y, --yes","Skip confirmation prompt").option("-a, --all","Select all active release branches").option("-v, --versions <versions>","Specify versions by comma, e.g. 1.2.5, 1.2.6").option("-i, --ide [mode]","Editor mode for created worktrees: workspace (default) | none").option("--no-ide","Skip the editor (alias for --ide none)").option("-c, --cursor [mode]","Deprecated alias for --ide").option("--no-cursor","Deprecated alias for --no-ide").option("-g, --github-desktop","Open created worktrees in GitHub Desktop").option("--no-github-desktop","Skip GitHub Desktop prompt").option("-m, --cmux","Open created worktrees in cmux (3-pane layout)").option("--no-cmux","Skip cmux prompt").action(async t=>{let o=so(t.ide,"--ide")??so(t.cursor,"--cursor");d(await Nt({confirmedCommand:t.yes,all:t.all,versions:t.versions,ide:o,githubDesktop:t.githubDesktop,cmux:t.cmux}))}),kr=e=>e.description("List all git worktrees with detailed information").action(async()=>{d(await Lt())}),Rr=e=>e.description("Remove git worktrees for release branches").option("-y, --yes","Skip confirmation prompt").option("-a, --all","Select all active release branches").option("-v, --versions <versions>","Specify versions by comma, e.g. 1.2.5, 1.2.6").action(async t=>{d(await _t({confirmedCommand:t.yes,all:t.all,versions:t.versions}))}),Er=e=>e.description("Reopen editor + cmux windows for every active worktree in the current project (additive, idempotent)").option("--all","Reopen across every discovered infra-kit project (Stage 3 \u2014 not yet implemented)").option("--project <names...>","Restrict --all to these project names").option("--root <paths...>","Discovery roots for --all (repeatable)").option("--release-only","Restrict to release worktrees (reproduces the legacy worktrees-reload scope)").option("--force","Close each cmux workspace first, then reopen (the legacy worktrees-reload behaviour)").option("--dry-run","Print the plan (paths + cmux titles) and spawn nothing").action(async t=>{d(await Pt({all:t.all,project:t.project,root:t.root,releaseOnly:t.releaseOnly,force:t.force,dryRun:t.dryRun}))}),xr=e=>e.description("Show the machine-local factory config (~/.infra-kit/vendor.json) or scaffold it with --init").option("--init","Scaffold ~/.infra-kit/vendor.json (skips if it already exists)").action(async t=>{d(await Re({init:t.init}))}),Tr=e=>e.description("Verify vendor/ matches vendor/.sync-manifest.json (self-contained; for any consumer repo)").action(async()=>{let t=await Ct();d(t),t.structuredContent.ok||(Y.exitCode=1)}),br=e=>e.description("Show the resolved config merge chain and file paths").action(async()=>{d(await se())}),Ar=e=>e.description("Open the user-scope per-project override file in $EDITOR").action(async()=>{d(await ce())}),Pr=new Set(["init","doctor","version","dev","self-update","mcp"]),Cr=e=>e.startsWith("env-")||Pr.has(e),Ir=new Set(["env-autoload","mcp","version","self-update"]),z=e=>{let t=[];for(let o=e;o&&o.parent;o=o.parent)t.unshift(o.name());return t.join(" ")},xe=()=>{let e=new mr,t=e.command("release").description("Release management commands");ur(t.command("merge-dev")),pr(t.command("list")),fr(t.command("create")),gr(t.command("desc-edit")),hr(t.command("deploy-all")),vr(t.command("deploy-selected")),yr(t.command("deliver"));let o=e.command("worktrees").description("Git worktree management commands");Sr(o.command("add")),kr(o.command("list")),Rr(o.command("remove")),wr(o.command("sync")),Er(e.command("reopen"));let r=e.command("config").description("Manage infra-kit configuration files");br(r.command("path")),Ar(r.command("edit")),e.command("config-get").description("Print the fully merged infra-kit config (project + user-global + per-project override layers)").action(async()=>{d(await Oe())});let n=e.command("local").description("Run deploys on this machine instead of dispatching CI"),a=i=>i.option("-e, --env <name>","Target environment (prompts when omitted)").option("-y, --yes","Skip the confirmation prompt").option("--dry-run","Resolve target, contract and commands without deploying").option("--print-env","Print the resolved build contract").option("--skip-preflight <check...>","Waive a named check (clean-tree, toolchain) \u2014 never env/account or prod");a(n.command("deploy-all")).description("Deploy every service enabled for the environment, from this machine").action(async i=>{d(await Et({env:i.env,yes:i.yes,dryRun:i.dryRun,printEnv:i.printEnv,skipPreflight:i.skipPreflight}))}),a(n.command("deploy-selected")).description("Deploy chosen services from this machine (prompts when --service is omitted)").option("-s, --service <name...>","Service name(s), as in deploy-<name>.sh").action(async i=>{d(await xt({env:i.env,service:i.service,yes:i.yes,dryRun:i.dryRun,printEnv:i.printEnv,skipPreflight:i.skipPreflight}))}),e.command("audit").description("Audit against infra-kit.config.ts rules (--all for every package, --root for the monorepo root)").option("-a, --all","Audit every non-vendor workspace package").option("-r, --root","Audit the monorepo root (turbo pipeline + root commands)").action(async i=>{let c=await De({all:i.all,root:i.root});d(c),c.structuredContent.allPassed||(Y.exitCode=1)});let s=e.command("vendor").description("Verify the mirrored vendor/ tree");return Tr(s.command("check")),xr(s.command("config")),e.command("doctor").description("Check installation and authentication status of gh and doppler CLIs").option("--fix","Remove portless routes left behind by a dev-server that was killed (kill -9, OOM, force-quit). Refuses while a dev session is running, when a booting UI is indistinguishable from a dead route.").option("--ascii","Render the report with ASCII markers instead of unicode glyphs (check messages are unchanged)").action(async i=>{let c=await dt({fix:!!i.fix});I.enabled||ut(c.structuredContent.checks,{ascii:!!i.ascii}),d(c)}),e.command("self-update").description("Update this CLI using the package manager that installed it").option("--dry-run","Print the detected manager and the command that would run; install nothing").action(i=>{we({dryRun:!!i.dryRun})}),e.command("mcp").description("Run the infra-kit MCP server (stdio transport)").action(()=>{ve()}),e.command("dev").description("Run local dev servers for a named devServersPresets preset (or all apps); api + ui").argument("[preset]","Named preset from devServersPresets (omit to run every app)").option("-w, --watch","Rebuild and restart on file save").option("--app <names>","Further narrow to these app folder names (comma-separated)").option("--target <keys>","Run exactly these <app>/api|<app>/ui packages (comma-separated); part-level, unlike --app").option("--cmux","Run each app in its own cmux pane (one workspace, N panes; falls back to single terminal if cmux is unavailable)").option("--self","Run only the app of the current directory (infer from cwd; use inside apps/<app>/\u2026)").option("-V, --verbose","Print full boot narration (default: quiet; full detail always in the session log)").option("--routes","Print each app\u2019s registered METHOD /path routes at startup (default: off)").option("--no-ui-health","Do not probe the frontends\u2019 liveness (vite\u2019s HMR ping); their rows carry no health dot (also: INFRA_KIT_NO_UI_HEALTH=1)").action(async(i,c)=>{let{runDevServerCli:m}=await import("./dev-server.js"),u=!!(Y.stdout.isTTY&&Y.stdin.isTTY);await m({...c,preset:i},u,I.enabled)}),e.command("dev-status").description("Show what `infra-kit dev` currently has running (reads dev-context fragments; starts nothing)").action(async()=>{d(await Me())}),e.command("version").description("Print the installed infra-kit CLI version").action(async()=>{d(await It())}),e.command("env-status").description("Show which env is loaded in this session (local introspection; no Doppler call)").action(async()=>{d(await gt())}),e.command("env-list").description("List available Doppler configs for the detected project, and whether a service token resolves for each").action(async()=>{d(await ft())}),e.command("init").description("Inject shell integration into .zshrc and sync repo agent-instruction files").action(async()=>{d(await ee())}),e.command("env-load").description("Load Doppler env vars for a config. Source the returned file path to apply.").option("-c, --config <config>","Environment config name to load (e.g. dev, arthur)").action(async i=>{d(await Ze({config:i.config}))}),e.command("env-clear").description("Clear loaded env vars. Source the returned file path to apply.").option("--purge","Also delete this project's warm cache outright (durable disable)").action(async i=>{d(await pt({purge:!!i.purge}))}),e.command("env-token-set").description("Store the Doppler service token for an env (masked prompt; validated against Doppler before writing)").argument("<env>","Environment / Doppler config the token is scoped to (e.g. dev)").option("--stdin","Read the token from stdin instead of prompting (e.g. from a password manager)").option("--from-env <var>","Read the token from the named environment variable (the NAME, never the value)").option("--force","Store even when the token\u2019s scope could not be verified. Never overrides a real mismatch.").action(async(i,c)=>{d(await he({env:i,stdin:c.stdin,fromEnv:c.fromEnv,force:c.force}))}),e.command("env-token-list").description("Show which envs have a Doppler service token (redacted), and where it came from").option("--check","Also ask Doppler whether each token is valid and correctly scoped").action(async i=>{d(await ht({check:!!i.check}))}),e.command("env-token-remove").description("Delete an env\u2019s Doppler service token from the local store (does NOT revoke it in Doppler)").argument("<env>","Environment / Doppler config whose token to remove").action(async i=>{d(await fe({env:i}))}),e.command("env-autoload",{hidden:!0}).description("Internal: prime env for the shell-startup auto-load trigger").option("--project-dir <dir>","Canonical project dir for the warm-cache key (shell-startup only)").action(async i=>{await pe({projectDir:i.projectDir})}),e.commands.forEach(vt),e.hook("preAction",async(i,c)=>{b.start(z(c)),I.enabled=!!c.optsWithGlobals().json,I.enabled&&(l.level="warn"),Ir.has(z(c))||await at(),q(),Cr(c.name())||await _({expectedTrigger:"cli-invocation"})}),e.hook("postAction",(i,c)=>{let m=z(c);if(Mt(m))return;let p=b.snapshot()?.formattedOptions??"",k=p?` ${p}`:"",R=`infra-kit ${m}${k}`;no({equivalent:K(R,!0)})}),e};var Te=(e,t=" ")=>{let o=e.reduce((r,[n])=>Math.max(r,n.length),0);return e.map(([r,n])=>`${r.padEnd(o)}${t}${n}`)};import{chalkStderr as jr}from"chalk";import{spawn as Ur}from"node:child_process";import S from"node:process";import{Chalk as Nr}from"chalk";var P=new Nr({level:1}),w={ok:"ok",findingsPlain:"completed with findings",failed:"failed",cancelled:"cancelled",findingsSuffix:"findings",sep:" \xB7 ",reproPrefix:"$ ",nonReproPrefix:"\u2248 ",envNotice:"Applies to your shell after you exit this session."},co={ok:{unicode:"\u2713",ascii:"[ok]"},findings:{unicode:"\u26A0",ascii:"[!]"},failed:{unicode:"\u2717",ascii:"[x]"},cancelled:{unicode:"\u2298",ascii:"[-]"}},lo={unicode:"\u2500",ascii:"-"},Lr=3,be=e=>e,_r={ok:e=>P.green(e),findings:e=>P.yellow(e),failed:e=>P.red(e),cancelled:e=>P.gray(e)},uo=e=>e?{dim:t=>P.dim(t),bold:t=>P.bold(t)}:{dim:be,bold:be},Dr=(e,t)=>e?_r[t]:be,mo=e=>[...e].length,po=(e,t={})=>{let o=uo(t.color===!0);return`${o.dim(w.reproPrefix.trim())} ${o.bold(e)}`},Or=e=>e<1e3?`${Math.round(e)}ms`:`${(e/1e3).toFixed(1)}s`,$r=(e,t)=>e==="findings"?t!=null?`${t} ${w.findingsSuffix}`:w.findingsPlain:{ok:w.ok,failed:w.failed,cancelled:w.cancelled}[e],Fr=(e,t,o,r)=>{if(t==null)return"";let n=t-2-e;return n<Lr?"":` ${r(o.repeat(n))}`},fo=e=>{let t=e.color===!0,o=uo(t),r=Dr(t,e.outcome),n=e.ascii===!0,a=e.equivalent.reproducible?w.reproPrefix:w.nonReproPrefix,s=n?co[e.outcome].ascii:co[e.outcome].unicode,i=$r(e.outcome,e.findingsCount),c=[Or(e.durationMs)],m=e.summary?.[0]?.split(`
|
|
8
|
+
`)[0];m!=null&&m.length>0&&c.push(m);let u=`${s} ${i}`,p=`${w.sep}${c.join(w.sep)}`,k=mo(u)+mo(p),R=Fr(k,e.width,n?lo.ascii:lo.unicode,o.dim),x=[`${r(u)}${o.dim(p)}${R}`];return e.showEquivalent!==!1&&x.unshift(`${o.dim(a.trim())} ${o.bold(e.equivalent.line)}`),e.envNotice===!0&&x.push(o.dim(w.envNotice)),x.join(`
|
|
9
|
+
`)};var Mr=new Set([130,143]),go=(e,t,o,r=!1)=>o?t!=null||e===0?"ok":"findings":r&&e!=null&&Mr.has(e)||t==="SIGINT"||e===0?"cancelled":"failed";import ho from"node:process";var E={column:"\r",sgr:"\x1B[0m",cursor:"\x1B[?25h",wrap:"\x1B[?7h",saveCursor:"\x1B7",restoreCursor:"\x1B8",scrollRegion:"\x1B[r",primaryBuffer:"\x1B[?1049l"},vo=(e,t)=>{let o=t?.write??(a=>{ho.stderr.write(a)}),r=t?.stdin??ho.stdin,n=[E.saveCursor,E.scrollRegion,E.restoreCursor,E.column,E.sgr,E.cursor,E.wrap];e.entersAltScreen===!0&&n.push(E.primaryBuffer),o(n.join("")),r.isTTY===!0&&r.isRaw===!0&&r.setRawMode?.(!1)};var yo=e=>`infra-kit ${e.groupPath.join(" ")}`,Gr=()=>{let e=S.stderr.columns;return e!=null&&e>0?e:void 0},qr=e=>new Promise(t=>{e.on("exit",(o,r)=>{t({code:o,signal:r})}),e.on("error",()=>{t({code:1,signal:null})})}),Vr=async(e,t,o)=>{let r=io(),n={...t.env,[H]:r,INFRA_KIT_NO_AUTO_UPDATE:"1"},a=t.now(),s={code:1,signal:null};o();try{let p=t.spawn(S.execPath,[t.cliPath,...e.groupPath],{stdio:"inherit",env:n});s=await qr(p)}catch{s={code:1,signal:null}}finally{t.resetTerminal({entersAltScreen:e.entersAltScreen})}let i=ao(r),c=go(s.code,s.signal,i!=null,e.longRunning),m=yo(e),u=i?.equivalent??K(m,!0);return fo({equivalent:u,outcome:c,durationMs:t.now()-a,summary:i?.summary,envNotice:e.sessionEnvNotice,ascii:t.ascii,color:t.color,width:t.columns(),showEquivalent:!(u.reproducible&&u.line===m)})},Wr=1e3,Jr=(e,t)=>{let o=null,r=!1,n=()=>{if(e()){o=t.now();return}t.resetTerminal(),t.exit(0)},a=()=>{if(!e()){t.resetTerminal(),t.exit(0);return}o!=null&&t.now()-o<=Wr||(r=!0)},s=()=>{t.exit(129)},i=()=>{e()&&t.raise("SIGSTOP")};return t.register("SIGINT",n),t.register("SIGTERM",a),t.register("SIGHUP",s),t.register("SIGTSTP",i),{dispose:()=>{t.unregister("SIGINT",n),t.unregister("SIGTERM",a),t.unregister("SIGHUP",s),t.unregister("SIGTSTP",i)},childStarted:()=>{o=null},quitRequested:()=>r}},wo=(e,t)=>!!(t.stdoutIsTTY&&t.stdinIsTTY&&t.stderrIsTTY&&e.TERM!=="dumb"&&!e.INFRA_KIT_NO_SESSION&&!e.INFRA_KIT_SESSION_REPORT),So=async(e,t)=>{let o={spawn:t.spawn??Ur,now:t.now??(()=>Date.now()),env:t.env??S.env,cliPath:t.cliPath,ascii:t.ascii??!(S.stdout.isTTY&&S.env.TERM!=="dumb"),color:t.color??jr.level>0,columns:t.columns??Gr,resetTerminal:t.resetTerminal??(c=>{vo(c)})},r=t.write??(c=>S.stderr.write(c)),n=t.signals??{register:(c,m)=>{S.on(c,m)},unregister:(c,m)=>{S.off(c,m)},exit:c=>{S.exit(c)},raise:c=>{S.kill(S.pid,c)},now:()=>Date.now(),resetTerminal:()=>{o.resetTerminal({entersAltScreen:!1})}},a=!1,s={dispose:()=>{},childStarted:()=>{},quitRequested:()=>!1},i=t.installSignals===!1?s:Jr(()=>a,n);try{for(;;){if(i.quitRequested())return;a=!1;let c=await t.renderPalette(e);if(c==null)return;let m=t.resolveCommand(c);if(!m)continue;let u=po(yo(m),{color:o.color});r(`
|
|
10
10
|
${u}
|
|
11
|
-
`),
|
|
12
|
-
${
|
|
13
|
-
`)}}finally{
|
|
11
|
+
`),i.childStarted();let p=await Vr(m,o,()=>{a=!0});r(`
|
|
12
|
+
${p}
|
|
13
|
+
`)}}finally{i.dispose()}};import{spawn as Hr}from"node:child_process";import Ro from"node:fs";import C from"node:process";import{fileURLToPath as Eo}from"node:url";var ko=["INFRA_KIT_NO_AUTO_UPDATE","NO_UPDATE_NOTIFIER","CI"],Kr=new Set(["mcp","self-update"]),Br=e=>e[2]!=null&&Kr.has(e[2]),Ae=e=>{let{argv:t,env:o,isTty:r,selfRealPath:n,cwd:a,realpath:s}=e;return ko.some(c=>{let m=o[c];return m!=null&&m!==""})?"opt-out":t.includes("--json")?"json":Br(t)?"own-command":r?U(n,a,s)?"local-install":null:"not-a-tty"};var Yr=e=>{Hr(C.execPath,[e,"--parent-pid",String(C.pid)],{detached:!0,stdio:"ignore",windowsHide:!0}).unref()},zr=()=>Eo(new URL("./update-check.js",import.meta.url)),Xr=()=>Ro.realpathSync(Eo(new URL("./cli.js",import.meta.url))),Qr=/^[\w@./+-]+$/i,Zr=e=>Qr.test(e),en=(e,t,o)=>{!e?.latestVersion||!e.updateCommand||ne(e.latestVersion,t)&&e.updateCommand.every(Zr)&&o(`infra-kit ${e.latestVersion} is available (you have ${t}). Run: ${e.updateCommand.join(" ")}`)},Pe=(e,t={})=>{try{let o=t.argv??C.argv,r=t.env??C.env,n=t.isTty??!!C.stdout.isTTY,a=t.cwd??C.cwd(),s=t.nowMs??Date.now(),i=t.selfRealPath??Xr(),c=t.readCache??ie,m=t.spawnChild??Yr,u=t.fileExists??(x=>Ro.existsSync(x)),p=t.notify??(x=>{l.info(x)});if(Ae({argv:o,env:r,isTty:n,selfRealPath:i,cwd:a,realpath:A})!==null)return;let k=c();if(en(k,e,p),!ae(k,s))return;let R=t.childPath??zr();if(!u(R))return;m(R)}catch{}};Gt();ro();var Ce=xe(),To=async e=>{try{e?await Ce.parseAsync(e):await Ce.parseAsync()}catch(t){re(t)&&(l.info("Operation cancelled."),g.exit(0));let o=t instanceof Error?t.message:String(t);l.error(o),g.exit(1)}},rn=()=>{try{if(g.env.INFRA_KIT_NO_LOCATION_WARN||g.argv.includes("--json")||g.argv[2]==="mcp")return;U(on(bo(import.meta.url)),g.cwd(),A)&&l.info("Running from a project-local node_modules. Install globally for faster startup: npm i -g infra-kit")}catch{}};rn();Pe(rt.version);var Ao=()=>qt(Ce.commands),nn=async()=>{let e=Ao(),t=null;try{if(g.stdout.isTTY&&g.stdin.isTTY){let{runCommandPalette:o}=await import("./boot-OA6HGGO6.js");t=await o(e)}else{let o=Te(e.map(n=>[n.name,n.description])),r=e.flatMap((n,a)=>[...e[a-1]?.group===n.group?[]:[new xo(" "),new xo(`\u2014 ${n.group} \u2014`)],{name:o[a]??n.name,value:n.name}]);t=await M(n=>tn({message:"Select a command to run",choices:r},n),{output:g.stderr})}}catch(o){if(!re(o))throw o}return t},an=async()=>{let{runCommandPalette:e}=await import("./boot-OA6HGGO6.js"),t=bo(import.meta.url),o=new Map($t.map(r=>[r.groupPath.join(" "),r]));await So(Ao(),{renderPalette:e,resolveCommand:r=>{let n=o.get(r);return n?{groupPath:n.groupPath,entersAltScreen:n.entersAltScreen,sessionEnvNotice:n.sessionEnvNotice,longRunning:n.longRunning}:void 0},cliPath:t})};if(g.argv.length<=2){let e={stdoutIsTTY:!!g.stdout.isTTY,stdinIsTTY:!!g.stdin.isTTY,stderrIsTTY:!!g.stderr.isTTY};if(wo(g.env,e))await an();else{let t=await nn();t&&await To(["node","infra-kit",...t.split(" ")])}}else await To();
|
|
14
14
|
//# sourceMappingURL=cli.js.map
|