@noir-ai/cli 1.10.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -168,6 +168,32 @@ function updateCmdFor(method, spec) {
168
168
  return null;
169
169
  }
170
170
  }
171
+ function updateAdvice(method) {
172
+ switch (method) {
173
+ case "npm":
174
+ return "run `npm install -g @noir-ai/cli@latest`";
175
+ case "pnpm":
176
+ return "run `pnpm add -g @noir-ai/cli@latest`";
177
+ case "yarn":
178
+ return "run `yarn global add @noir-ai/cli@latest`";
179
+ case "bun":
180
+ return "run `bun add -g @noir-ai/cli@latest`";
181
+ case "homebrew":
182
+ return "run `brew upgrade noir`";
183
+ case "scoop":
184
+ return "run `scoop update noir`";
185
+ case "native":
186
+ return "run `noir update`";
187
+ default:
188
+ return "run `noir update`";
189
+ }
190
+ }
191
+ function buildUpdateNotice(opts) {
192
+ const { method, currentVersion, latestKnown } = opts;
193
+ if (currentVersion == null || latestKnown == null) return null;
194
+ if (!semverGt(latestKnown, currentVersion)) return null;
195
+ return { latestVersion: latestKnown, currentVersion, method, advice: updateAdvice(method) };
196
+ }
171
197
  async function runAsyncUpdateCheck(opts) {
172
198
  try {
173
199
  const cache = readUpdateCache();
@@ -191,6 +217,8 @@ export {
191
217
  DEFAULT_UPDATE_CONFIG,
192
218
  buildUpdateTarget,
193
219
  update,
220
+ updateAdvice,
221
+ buildUpdateNotice,
194
222
  runAsyncUpdateCheck
195
223
  };
196
- //# sourceMappingURL=chunk-PED37JWV.js.map
224
+ //# sourceMappingURL=chunk-Y4M2YC6U.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import {\n detectActiveMethod,\n ensureShimExecutable,\n fetchLatestVersion,\n loadProjectInfo,\n readInstallRecord,\n readUpdateCache,\n runManagerCmd,\n shouldCheckForUpdate,\n type UpdateConfigLike,\n writeUpdateCache,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\nimport { installManagedNode } from './install.js';\n\nexport const DEFAULT_UPDATE_CONFIG: UpdateConfigLike = {\n checkEnabled: true,\n checkIntervalHours: 24,\n channel: 'latest',\n minVersion: '1.6.0',\n display: 'notice',\n};\n\nexport interface UpdateOptions extends CliOptions {\n check?: boolean;\n spec?: string;\n}\n\nexport interface UpdateTarget {\n method: string;\n targetSpec: string;\n currentVersion: string | null;\n latestKnown: string | null;\n isUpgrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the latest-known\n * version the registry returned is below the configured floor (e.g. a\n * beta/prerelease channel lag, or a yanked dist-tag). Enforced in\n * {@link update} with the same warn/refuse pattern as the downgrade guard.\n */\n belowMinVersion: boolean;\n}\n\nexport function buildUpdateTarget(opts: {\n method: string;\n channel: string;\n spec?: string;\n currentVersion: string | null;\n latestKnown: string | null;\n /**\n * The `update.minVersion` floor from config (default `'1.6.0'`). When\n * omitted, the config default applies. See I2.\n */\n minVersion?: string;\n}): UpdateTarget {\n const targetSpec = opts.spec ?? opts.channel;\n // Semver downgrade guard (T6 hardening): only treat the latest-known version\n // as an upgrade when it is STRICTLY NEWER than the current one. The prior\n // inequality check (`latestKnown !== currentVersion`) would treat a registry\n // that returned an OLDER version (e.g. beta/prerelease channel lag, or a\n // pinned `--spec` older than installed) as an \"upgrade\" and silently\n // downgrade the install.\n const isUpgrade =\n opts.latestKnown != null &&\n opts.currentVersion != null &&\n semverGt(opts.latestKnown, opts.currentVersion);\n // I2 — minVersion floor: refuse a registry-offered version below the floor.\n // null latestKnown is handled separately (registry-unreachable branch).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion = opts.latestKnown != null && semverLt(opts.latestKnown, floor);\n return {\n method: opts.method,\n targetSpec,\n currentVersion: opts.currentVersion,\n latestKnown: opts.latestKnown,\n isUpgrade,\n belowMinVersion,\n };\n}\n\n/**\n * Per-segment numeric semver comparison (major.minor.patch). Returns true when\n * `a` is strictly greater than `b`. Pre-release suffixes are ignored — Noir\n * versioning is `MAJOR.MINOR.PATCH` only, and the registry's dist-tag is the\n * source of truth for \"latest\". Non-numeric segments coerce to 0. Used here\n * instead of a full semver library to keep the CLI's dependency surface\n * unchanged (the same helper lives in install.ts for the downgrade guard).\n */\nfunction semverGt(a: string, b: string): boolean {\n const pa = a.split('.').map((s) => Number(s) || 0);\n const pb = b.split('.').map((s) => Number(s) || 0);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x > y;\n }\n return false;\n}\n\n/**\n * Per-segment numeric semver `<` (I2 — minVersion floor). Same comparison\n * discipline as {@link semverGt} / install.ts's downgrade guard: numeric per\n * segment, non-numeric → 0. Returns true when `a` is strictly less than `b`.\n */\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split('.').map((s) => Number(s) || 0);\n const pb = b.split('.').map((s) => Number(s) || 0);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x < y;\n }\n return false;\n}\n\nexport async function update(opts: UpdateOptions = {}): Promise<void> {\n // NOIR_DISABLE_UPDATES is the hard kill-switch for the SELF-UPDATE surface\n // (distinct from NOIR_DISABLE_UPDATE_CHECK, which only gags the background\n // version check). When set, `noir update` refuses to run at all — enterprises\n // can pin this in the environment to enforce \"updates flow only through the\n // package manager / image rebuild, never from inside the CLI\". `fail` is\n // `: never` (always throws) — the explicit `return` is defensive.\n if (process.env.NOIR_DISABLE_UPDATES !== undefined) {\n fail(\n EXIT.USAGE,\n 'self-update is disabled (NOIR_DISABLE_UPDATES is set); update via your package manager or image rebuild.',\n opts,\n );\n return;\n }\n\n const method = detectActiveMethod();\n const rec = readInstallRecord();\n const currentVersion = rec?.version ?? null;\n\n if (opts.check === true) {\n const checkSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest) {\n checkSpin.succeed(`Latest: ${latest} (you have ${currentVersion ?? 'unknown'})`);\n } else {\n checkSpin.fail('Could not reach the registry.');\n }\n return;\n }\n\n // Fetch latest (network; timeout-bound).\n const fetchSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest === null) {\n fetchSpin.fail('Could not reach the registry.');\n info('Could not reach the registry.', opts);\n return;\n }\n fetchSpin.succeed(`Latest: ${latest}`);\n // I2 — read the `update.minVersion` floor from the project config (falls\n // back to the config default '1.6.0' when the project isn't initialized or\n // the block is absent). Same try/catch pattern as home.ts's update-config read.\n let minVersion = DEFAULT_UPDATE_CONFIG.minVersion;\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const target = buildUpdateTarget({\n method,\n channel: 'latest',\n spec: opts.spec,\n currentVersion,\n latestKnown: latest,\n minVersion,\n });\n\n // I2 — refuse to install a registry-offered version below the configured\n // minVersion floor (same warn/refuse pattern as install.ts's downgrade guard).\n // Checked before the isUpgrade branch: a below-floor offer is unsafe even if\n // it happens to be newer than the (very old) current install.\n if (target.belowMinVersion) {\n warn(`Latest known ${target.latestKnown} is below the minVersion floor (${minVersion}).`);\n fail(\n EXIT.USAGE,\n `refusing update to ${target.latestKnown} (below minVersion ${minVersion})`,\n opts,\n );\n }\n\n if (!target.isUpgrade) {\n fetchSpin.succeed(`noir ${currentVersion} is up to date`);\n info(`noir ${currentVersion} is up to date.`, opts);\n return;\n }\n\n if (method === 'native') {\n const updateSpin = spinner(\n `Updating to ${target.latestKnown} via native installer...`,\n opts,\n ).start();\n const res = await installManagedNode({\n version: target.targetSpec === 'latest' ? undefined : target.targetSpec,\n env: process.env,\n });\n if (!res.ok) {\n updateSpin.fail('Update failed');\n fail(EXIT.ERROR, res.error ?? 'update failed', opts);\n }\n // Chicken-egg safety net: the update was ORCHESTRATED by the OLD binary\n // (which may predate the chmod fix). Re-assert the freshly-written shim is\n // executable so the user's NEXT `noir …` doesn't fail with permission\n // denied. This is the ONLY line that closes the regression once 1.7.4+ is\n // installed — it runs in the NEW binary's process.\n ensureShimExecutable();\n updateSpin.succeed(`Updated to noir ${res.version}`);\n success(`Updated to ${res.version}.`);\n return;\n }\n\n // npm/pnpm/yarn/bun/homebrew/scoop -> reinstall via the same manager.\n const cmd = updateCmdFor(method, target.targetSpec);\n if (!cmd)\n fail(EXIT.USAGE, `cannot auto-update a ${method} install; use the manager directly`, opts);\n const mgrSpin = spinner(`Updating via ${method}...`, opts).start();\n const { code, stderr } = await runManagerCmd(cmd, { env: process.env });\n if (code !== 0) {\n mgrSpin.fail(`Update failed`);\n fail(EXIT.ERROR, `update failed: ${stderr.slice(0, 300)}`, opts);\n }\n mgrSpin.succeed('Updated');\n success('Updated.');\n}\n\nfunction updateCmdFor(method: string, spec: string): string[] | null {\n switch (method) {\n case 'npm':\n return ['npm', 'install', '-g', `@noir-ai/cli@${spec}`];\n case 'pnpm':\n return ['pnpm', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'yarn':\n return ['yarn', 'global', 'add', `@noir-ai/cli@${spec}`];\n case 'bun':\n return ['bun', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'homebrew':\n return ['brew', 'upgrade', 'noir'];\n case 'scoop':\n return ['scoop', 'update', 'noir'];\n default:\n return null;\n }\n}\n\n/** Non-blocking, time-boxed startup check. Writes the cache on success; silent on any failure. */\nexport async function runAsyncUpdateCheck(opts: {\n env: NodeJS.ProcessEnv;\n configUpdate: UpdateConfigLike;\n quiet: boolean;\n}): Promise<void> {\n try {\n const cache = readUpdateCache();\n if (!shouldCheckForUpdate({ env: opts.env, configUpdate: opts.configUpdate, cache })) return;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 2000);\n const latest = await fetchLatestVersion(opts.configUpdate.channel, controller.signal);\n clearTimeout(timer);\n if (latest != null) {\n writeUpdateCache({\n lastCheckAt: new Date().toISOString(),\n latestVersion: latest,\n channel: opts.configUpdate.channel,\n });\n }\n // Non-blocking: never print in quiet/CI/non-TTY; a mismatch only nudges.\n } catch {\n // silent on any failure — this is a non-blocking background check\n }\n}\n"],"mappings":"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","names":["latest"]}
1
+ {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import {\n detectActiveMethod,\n ensureShimExecutable,\n fetchLatestVersion,\n loadProjectInfo,\n readInstallRecord,\n readUpdateCache,\n runManagerCmd,\n shouldCheckForUpdate,\n type UpdateConfigLike,\n writeUpdateCache,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\nimport { installManagedNode } from './install.js';\n\nexport const DEFAULT_UPDATE_CONFIG: UpdateConfigLike = {\n checkEnabled: true,\n checkIntervalHours: 24,\n channel: 'latest',\n minVersion: '1.6.0',\n display: 'notice',\n};\n\nexport interface UpdateOptions extends CliOptions {\n check?: boolean;\n spec?: string;\n}\n\nexport interface UpdateTarget {\n method: string;\n targetSpec: string;\n currentVersion: string | null;\n latestKnown: string | null;\n isUpgrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the latest-known\n * version the registry returned is below the configured floor (e.g. a\n * beta/prerelease channel lag, or a yanked dist-tag). Enforced in\n * {@link update} with the same warn/refuse pattern as the downgrade guard.\n */\n belowMinVersion: boolean;\n}\n\nexport function buildUpdateTarget(opts: {\n method: string;\n channel: string;\n spec?: string;\n currentVersion: string | null;\n latestKnown: string | null;\n /**\n * The `update.minVersion` floor from config (default `'1.6.0'`). When\n * omitted, the config default applies. See I2.\n */\n minVersion?: string;\n}): UpdateTarget {\n const targetSpec = opts.spec ?? opts.channel;\n // Semver downgrade guard (T6 hardening): only treat the latest-known version\n // as an upgrade when it is STRICTLY NEWER than the current one. The prior\n // inequality check (`latestKnown !== currentVersion`) would treat a registry\n // that returned an OLDER version (e.g. beta/prerelease channel lag, or a\n // pinned `--spec` older than installed) as an \"upgrade\" and silently\n // downgrade the install.\n const isUpgrade =\n opts.latestKnown != null &&\n opts.currentVersion != null &&\n semverGt(opts.latestKnown, opts.currentVersion);\n // I2 — minVersion floor: refuse a registry-offered version below the floor.\n // null latestKnown is handled separately (registry-unreachable branch).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion = opts.latestKnown != null && semverLt(opts.latestKnown, floor);\n return {\n method: opts.method,\n targetSpec,\n currentVersion: opts.currentVersion,\n latestKnown: opts.latestKnown,\n isUpgrade,\n belowMinVersion,\n };\n}\n\n/**\n * Per-segment numeric semver comparison (major.minor.patch). Returns true when\n * `a` is strictly greater than `b`. Pre-release suffixes are ignored — Noir\n * versioning is `MAJOR.MINOR.PATCH` only, and the registry's dist-tag is the\n * source of truth for \"latest\". Non-numeric segments coerce to 0. Used here\n * instead of a full semver library to keep the CLI's dependency surface\n * unchanged (the same helper lives in install.ts for the downgrade guard).\n */\nfunction semverGt(a: string, b: string): boolean {\n const pa = a.split('.').map((s) => Number(s) || 0);\n const pb = b.split('.').map((s) => Number(s) || 0);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x > y;\n }\n return false;\n}\n\n/**\n * Per-segment numeric semver `<` (I2 — minVersion floor). Same comparison\n * discipline as {@link semverGt} / install.ts's downgrade guard: numeric per\n * segment, non-numeric → 0. Returns true when `a` is strictly less than `b`.\n */\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split('.').map((s) => Number(s) || 0);\n const pb = b.split('.').map((s) => Number(s) || 0);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x < y;\n }\n return false;\n}\n\nexport async function update(opts: UpdateOptions = {}): Promise<void> {\n // NOIR_DISABLE_UPDATES is the hard kill-switch for the SELF-UPDATE surface\n // (distinct from NOIR_DISABLE_UPDATE_CHECK, which only gags the background\n // version check). When set, `noir update` refuses to run at all — enterprises\n // can pin this in the environment to enforce \"updates flow only through the\n // package manager / image rebuild, never from inside the CLI\". `fail` is\n // `: never` (always throws) — the explicit `return` is defensive.\n if (process.env.NOIR_DISABLE_UPDATES !== undefined) {\n fail(\n EXIT.USAGE,\n 'self-update is disabled (NOIR_DISABLE_UPDATES is set); update via your package manager or image rebuild.',\n opts,\n );\n return;\n }\n\n const method = detectActiveMethod();\n const rec = readInstallRecord();\n const currentVersion = rec?.version ?? null;\n\n if (opts.check === true) {\n const checkSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest) {\n checkSpin.succeed(`Latest: ${latest} (you have ${currentVersion ?? 'unknown'})`);\n } else {\n checkSpin.fail('Could not reach the registry.');\n }\n return;\n }\n\n // Fetch latest (network; timeout-bound).\n const fetchSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest === null) {\n fetchSpin.fail('Could not reach the registry.');\n info('Could not reach the registry.', opts);\n return;\n }\n fetchSpin.succeed(`Latest: ${latest}`);\n // I2 — read the `update.minVersion` floor from the project config (falls\n // back to the config default '1.6.0' when the project isn't initialized or\n // the block is absent). Same try/catch pattern as home.ts's update-config read.\n let minVersion = DEFAULT_UPDATE_CONFIG.minVersion;\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const target = buildUpdateTarget({\n method,\n channel: 'latest',\n spec: opts.spec,\n currentVersion,\n latestKnown: latest,\n minVersion,\n });\n\n // I2 — refuse to install a registry-offered version below the configured\n // minVersion floor (same warn/refuse pattern as install.ts's downgrade guard).\n // Checked before the isUpgrade branch: a below-floor offer is unsafe even if\n // it happens to be newer than the (very old) current install.\n if (target.belowMinVersion) {\n warn(`Latest known ${target.latestKnown} is below the minVersion floor (${minVersion}).`);\n fail(\n EXIT.USAGE,\n `refusing update to ${target.latestKnown} (below minVersion ${minVersion})`,\n opts,\n );\n }\n\n if (!target.isUpgrade) {\n fetchSpin.succeed(`noir ${currentVersion} is up to date`);\n info(`noir ${currentVersion} is up to date.`, opts);\n return;\n }\n\n if (method === 'native') {\n const updateSpin = spinner(\n `Updating to ${target.latestKnown} via native installer...`,\n opts,\n ).start();\n const res = await installManagedNode({\n version: target.targetSpec === 'latest' ? undefined : target.targetSpec,\n env: process.env,\n });\n if (!res.ok) {\n updateSpin.fail('Update failed');\n fail(EXIT.ERROR, res.error ?? 'update failed', opts);\n }\n // Chicken-egg safety net: the update was ORCHESTRATED by the OLD binary\n // (which may predate the chmod fix). Re-assert the freshly-written shim is\n // executable so the user's NEXT `noir …` doesn't fail with permission\n // denied. This is the ONLY line that closes the regression once 1.7.4+ is\n // installed — it runs in the NEW binary's process.\n ensureShimExecutable();\n updateSpin.succeed(`Updated to noir ${res.version}`);\n success(`Updated to ${res.version}.`);\n return;\n }\n\n // npm/pnpm/yarn/bun/homebrew/scoop -> reinstall via the same manager.\n const cmd = updateCmdFor(method, target.targetSpec);\n if (!cmd)\n fail(EXIT.USAGE, `cannot auto-update a ${method} install; use the manager directly`, opts);\n const mgrSpin = spinner(`Updating via ${method}...`, opts).start();\n const { code, stderr } = await runManagerCmd(cmd, { env: process.env });\n if (code !== 0) {\n mgrSpin.fail(`Update failed`);\n fail(EXIT.ERROR, `update failed: ${stderr.slice(0, 300)}`, opts);\n }\n mgrSpin.succeed('Updated');\n success('Updated.');\n}\n\nfunction updateCmdFor(method: string, spec: string): string[] | null {\n switch (method) {\n case 'npm':\n return ['npm', 'install', '-g', `@noir-ai/cli@${spec}`];\n case 'pnpm':\n return ['pnpm', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'yarn':\n return ['yarn', 'global', 'add', `@noir-ai/cli@${spec}`];\n case 'bun':\n return ['bun', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'homebrew':\n return ['brew', 'upgrade', 'noir'];\n case 'scoop':\n return ['scoop', 'update', 'noir'];\n default:\n return null;\n }\n}\n\n/**\n * The human-readable update command for an install method. Mirrors\n * {@link updateCmdFor} (the machine command) with the manager the user should\n * actually run — `noir update` is the universal entry (it re-dispatches to the\n * right manager), but the package-manager spellings are shown for transparency.\n */\nexport function updateAdvice(method: string): string {\n switch (method) {\n case 'npm':\n return 'run `npm install -g @noir-ai/cli@latest`';\n case 'pnpm':\n return 'run `pnpm add -g @noir-ai/cli@latest`';\n case 'yarn':\n return 'run `yarn global add @noir-ai/cli@latest`';\n case 'bun':\n return 'run `bun add -g @noir-ai/cli@latest`';\n case 'homebrew':\n return 'run `brew upgrade noir`';\n case 'scoop':\n return 'run `scoop update noir`';\n case 'native':\n return 'run `noir update`';\n default:\n return 'run `noir update`';\n }\n}\n\n/** A \"new version available\" notice for the home menu (version + advice). */\nexport interface UpdateNotice {\n readonly latestVersion: string;\n readonly currentVersion: string;\n readonly method: string;\n readonly advice: string;\n}\n\n/**\n * Build the home-menu \"update available\" notice from the cached latest version.\n * Returns `null` when there is no cached latest, no current version, or the\n * latest is NOT strictly newer (the semver downgrade guard — a stale/older\n * cache entry is never advertised as an upgrade).\n */\nexport function buildUpdateNotice(opts: {\n method: string;\n currentVersion: string | null;\n latestKnown: string | null;\n}): UpdateNotice | null {\n const { method, currentVersion, latestKnown } = opts;\n if (currentVersion == null || latestKnown == null) return null;\n if (!semverGt(latestKnown, currentVersion)) return null;\n return { latestVersion: latestKnown, currentVersion, method, advice: updateAdvice(method) };\n}\n\n/** Non-blocking, time-boxed startup check. Writes the cache on success; silent on any failure. */\nexport async function runAsyncUpdateCheck(opts: {\n env: NodeJS.ProcessEnv;\n configUpdate: UpdateConfigLike;\n quiet: boolean;\n}): Promise<void> {\n try {\n const cache = readUpdateCache();\n if (!shouldCheckForUpdate({ env: opts.env, configUpdate: opts.configUpdate, cache })) return;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 2000);\n const latest = await fetchLatestVersion(opts.configUpdate.channel, controller.signal);\n clearTimeout(timer);\n if (latest != null) {\n writeUpdateCache({\n lastCheckAt: new Date().toISOString(),\n latestVersion: latest,\n channel: opts.configUpdate.channel,\n });\n }\n // Non-blocking: never print in quiet/CI/non-TTY; a mismatch only nudges.\n } catch {\n // silent on any failure — this is a non-blocking background check\n }\n}\n"],"mappings":"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","names":["latest"]}
@@ -167,6 +167,32 @@ function updateCmdFor(method, spec) {
167
167
  return null;
168
168
  }
169
169
  }
170
+ function updateAdvice(method) {
171
+ switch (method) {
172
+ case "npm":
173
+ return "run `npm install -g @noir-ai/cli@latest`";
174
+ case "pnpm":
175
+ return "run `pnpm add -g @noir-ai/cli@latest`";
176
+ case "yarn":
177
+ return "run `yarn global add @noir-ai/cli@latest`";
178
+ case "bun":
179
+ return "run `bun add -g @noir-ai/cli@latest`";
180
+ case "homebrew":
181
+ return "run `brew upgrade noir`";
182
+ case "scoop":
183
+ return "run `scoop update noir`";
184
+ case "native":
185
+ return "run `noir update`";
186
+ default:
187
+ return "run `noir update`";
188
+ }
189
+ }
190
+ function buildUpdateNotice(opts) {
191
+ const { method, currentVersion, latestKnown } = opts;
192
+ if (currentVersion == null || latestKnown == null) return null;
193
+ if (!semverGt(latestKnown, currentVersion)) return null;
194
+ return { latestVersion: latestKnown, currentVersion, method, advice: updateAdvice(method) };
195
+ }
170
196
  async function runAsyncUpdateCheck(opts) {
171
197
  try {
172
198
  const cache = readUpdateCache();
@@ -190,6 +216,8 @@ export {
190
216
  DEFAULT_UPDATE_CONFIG,
191
217
  buildUpdateTarget,
192
218
  update,
219
+ updateAdvice,
220
+ buildUpdateNotice,
193
221
  runAsyncUpdateCheck
194
222
  };
195
- //# sourceMappingURL=chunk-RCMEIONF.js.map
223
+ //# sourceMappingURL=chunk-4OBUMHF6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/update.ts"],"sourcesContent":["import {\n detectActiveMethod,\n ensureShimExecutable,\n fetchLatestVersion,\n loadProjectInfo,\n readInstallRecord,\n readUpdateCache,\n runManagerCmd,\n shouldCheckForUpdate,\n type UpdateConfigLike,\n writeUpdateCache,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\nimport { installManagedNode } from './install.js';\n\nexport const DEFAULT_UPDATE_CONFIG: UpdateConfigLike = {\n checkEnabled: true,\n checkIntervalHours: 24,\n channel: 'latest',\n minVersion: '1.6.0',\n display: 'notice',\n};\n\nexport interface UpdateOptions extends CliOptions {\n check?: boolean;\n spec?: string;\n}\n\nexport interface UpdateTarget {\n method: string;\n targetSpec: string;\n currentVersion: string | null;\n latestKnown: string | null;\n isUpgrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the latest-known\n * version the registry returned is below the configured floor (e.g. a\n * beta/prerelease channel lag, or a yanked dist-tag). Enforced in\n * {@link update} with the same warn/refuse pattern as the downgrade guard.\n */\n belowMinVersion: boolean;\n}\n\nexport function buildUpdateTarget(opts: {\n method: string;\n channel: string;\n spec?: string;\n currentVersion: string | null;\n latestKnown: string | null;\n /**\n * The `update.minVersion` floor from config (default `'1.6.0'`). When\n * omitted, the config default applies. See I2.\n */\n minVersion?: string;\n}): UpdateTarget {\n const targetSpec = opts.spec ?? opts.channel;\n // Semver downgrade guard (T6 hardening): only treat the latest-known version\n // as an upgrade when it is STRICTLY NEWER than the current one. The prior\n // inequality check (`latestKnown !== currentVersion`) would treat a registry\n // that returned an OLDER version (e.g. beta/prerelease channel lag, or a\n // pinned `--spec` older than installed) as an \"upgrade\" and silently\n // downgrade the install.\n const isUpgrade =\n opts.latestKnown != null &&\n opts.currentVersion != null &&\n semverGt(opts.latestKnown, opts.currentVersion);\n // I2 — minVersion floor: refuse a registry-offered version below the floor.\n // null latestKnown is handled separately (registry-unreachable branch).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion = opts.latestKnown != null && semverLt(opts.latestKnown, floor);\n return {\n method: opts.method,\n targetSpec,\n currentVersion: opts.currentVersion,\n latestKnown: opts.latestKnown,\n isUpgrade,\n belowMinVersion,\n };\n}\n\n/**\n * Per-segment numeric semver comparison (major.minor.patch). Returns true when\n * `a` is strictly greater than `b`. Pre-release suffixes are ignored — Noir\n * versioning is `MAJOR.MINOR.PATCH` only, and the registry's dist-tag is the\n * source of truth for \"latest\". Non-numeric segments coerce to 0. Used here\n * instead of a full semver library to keep the CLI's dependency surface\n * unchanged (the same helper lives in install.ts for the downgrade guard).\n */\nfunction semverGt(a: string, b: string): boolean {\n const pa = a.split('.').map((s) => Number(s) || 0);\n const pb = b.split('.').map((s) => Number(s) || 0);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x > y;\n }\n return false;\n}\n\n/**\n * Per-segment numeric semver `<` (I2 — minVersion floor). Same comparison\n * discipline as {@link semverGt} / install.ts's downgrade guard: numeric per\n * segment, non-numeric → 0. Returns true when `a` is strictly less than `b`.\n */\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split('.').map((s) => Number(s) || 0);\n const pb = b.split('.').map((s) => Number(s) || 0);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x < y;\n }\n return false;\n}\n\nexport async function update(opts: UpdateOptions = {}): Promise<void> {\n // NOIR_DISABLE_UPDATES is the hard kill-switch for the SELF-UPDATE surface\n // (distinct from NOIR_DISABLE_UPDATE_CHECK, which only gags the background\n // version check). When set, `noir update` refuses to run at all — enterprises\n // can pin this in the environment to enforce \"updates flow only through the\n // package manager / image rebuild, never from inside the CLI\". `fail` is\n // `: never` (always throws) — the explicit `return` is defensive.\n if (process.env.NOIR_DISABLE_UPDATES !== undefined) {\n fail(\n EXIT.USAGE,\n 'self-update is disabled (NOIR_DISABLE_UPDATES is set); update via your package manager or image rebuild.',\n opts,\n );\n return;\n }\n\n const method = detectActiveMethod();\n const rec = readInstallRecord();\n const currentVersion = rec?.version ?? null;\n\n if (opts.check === true) {\n const checkSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest) {\n checkSpin.succeed(`Latest: ${latest} (you have ${currentVersion ?? 'unknown'})`);\n } else {\n checkSpin.fail('Could not reach the registry.');\n }\n return;\n }\n\n // Fetch latest (network; timeout-bound).\n const fetchSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest === null) {\n fetchSpin.fail('Could not reach the registry.');\n info('Could not reach the registry.', opts);\n return;\n }\n fetchSpin.succeed(`Latest: ${latest}`);\n // I2 — read the `update.minVersion` floor from the project config (falls\n // back to the config default '1.6.0' when the project isn't initialized or\n // the block is absent). Same try/catch pattern as home.ts's update-config read.\n let minVersion = DEFAULT_UPDATE_CONFIG.minVersion;\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const target = buildUpdateTarget({\n method,\n channel: 'latest',\n spec: opts.spec,\n currentVersion,\n latestKnown: latest,\n minVersion,\n });\n\n // I2 — refuse to install a registry-offered version below the configured\n // minVersion floor (same warn/refuse pattern as install.ts's downgrade guard).\n // Checked before the isUpgrade branch: a below-floor offer is unsafe even if\n // it happens to be newer than the (very old) current install.\n if (target.belowMinVersion) {\n warn(`Latest known ${target.latestKnown} is below the minVersion floor (${minVersion}).`);\n fail(\n EXIT.USAGE,\n `refusing update to ${target.latestKnown} (below minVersion ${minVersion})`,\n opts,\n );\n }\n\n if (!target.isUpgrade) {\n fetchSpin.succeed(`noir ${currentVersion} is up to date`);\n info(`noir ${currentVersion} is up to date.`, opts);\n return;\n }\n\n if (method === 'native') {\n const updateSpin = spinner(\n `Updating to ${target.latestKnown} via native installer...`,\n opts,\n ).start();\n const res = await installManagedNode({\n version: target.targetSpec === 'latest' ? undefined : target.targetSpec,\n env: process.env,\n });\n if (!res.ok) {\n updateSpin.fail('Update failed');\n fail(EXIT.ERROR, res.error ?? 'update failed', opts);\n }\n // Chicken-egg safety net: the update was ORCHESTRATED by the OLD binary\n // (which may predate the chmod fix). Re-assert the freshly-written shim is\n // executable so the user's NEXT `noir …` doesn't fail with permission\n // denied. This is the ONLY line that closes the regression once 1.7.4+ is\n // installed — it runs in the NEW binary's process.\n ensureShimExecutable();\n updateSpin.succeed(`Updated to noir ${res.version}`);\n success(`Updated to ${res.version}.`);\n return;\n }\n\n // npm/pnpm/yarn/bun/homebrew/scoop -> reinstall via the same manager.\n const cmd = updateCmdFor(method, target.targetSpec);\n if (!cmd)\n fail(EXIT.USAGE, `cannot auto-update a ${method} install; use the manager directly`, opts);\n const mgrSpin = spinner(`Updating via ${method}...`, opts).start();\n const { code, stderr } = await runManagerCmd(cmd, { env: process.env });\n if (code !== 0) {\n mgrSpin.fail(`Update failed`);\n fail(EXIT.ERROR, `update failed: ${stderr.slice(0, 300)}`, opts);\n }\n mgrSpin.succeed('Updated');\n success('Updated.');\n}\n\nfunction updateCmdFor(method: string, spec: string): string[] | null {\n switch (method) {\n case 'npm':\n return ['npm', 'install', '-g', `@noir-ai/cli@${spec}`];\n case 'pnpm':\n return ['pnpm', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'yarn':\n return ['yarn', 'global', 'add', `@noir-ai/cli@${spec}`];\n case 'bun':\n return ['bun', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'homebrew':\n return ['brew', 'upgrade', 'noir'];\n case 'scoop':\n return ['scoop', 'update', 'noir'];\n default:\n return null;\n }\n}\n\n/** Non-blocking, time-boxed startup check. Writes the cache on success; silent on any failure. */\nexport async function runAsyncUpdateCheck(opts: {\n env: NodeJS.ProcessEnv;\n configUpdate: UpdateConfigLike;\n quiet: boolean;\n}): Promise<void> {\n try {\n const cache = readUpdateCache();\n if (!shouldCheckForUpdate({ env: opts.env, configUpdate: opts.configUpdate, cache })) return;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 2000);\n const latest = await fetchLatestVersion(opts.configUpdate.channel, controller.signal);\n clearTimeout(timer);\n if (latest != null) {\n writeUpdateCache({\n lastCheckAt: new Date().toISOString(),\n latestVersion: latest,\n channel: opts.configUpdate.channel,\n });\n }\n // Non-blocking: never print in quiet/CI/non-TTY; a mismatch only nudges.\n } catch {\n // silent on any failure — this is a non-blocking background check\n }\n}\n"],"mappings":"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","names":["latest"]}
1
+ {"version":3,"sources":["../../src/commands/update.ts"],"sourcesContent":["import {\n detectActiveMethod,\n ensureShimExecutable,\n fetchLatestVersion,\n loadProjectInfo,\n readInstallRecord,\n readUpdateCache,\n runManagerCmd,\n shouldCheckForUpdate,\n type UpdateConfigLike,\n writeUpdateCache,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\nimport { installManagedNode } from './install.js';\n\nexport const DEFAULT_UPDATE_CONFIG: UpdateConfigLike = {\n checkEnabled: true,\n checkIntervalHours: 24,\n channel: 'latest',\n minVersion: '1.6.0',\n display: 'notice',\n};\n\nexport interface UpdateOptions extends CliOptions {\n check?: boolean;\n spec?: string;\n}\n\nexport interface UpdateTarget {\n method: string;\n targetSpec: string;\n currentVersion: string | null;\n latestKnown: string | null;\n isUpgrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the latest-known\n * version the registry returned is below the configured floor (e.g. a\n * beta/prerelease channel lag, or a yanked dist-tag). Enforced in\n * {@link update} with the same warn/refuse pattern as the downgrade guard.\n */\n belowMinVersion: boolean;\n}\n\nexport function buildUpdateTarget(opts: {\n method: string;\n channel: string;\n spec?: string;\n currentVersion: string | null;\n latestKnown: string | null;\n /**\n * The `update.minVersion` floor from config (default `'1.6.0'`). When\n * omitted, the config default applies. See I2.\n */\n minVersion?: string;\n}): UpdateTarget {\n const targetSpec = opts.spec ?? opts.channel;\n // Semver downgrade guard (T6 hardening): only treat the latest-known version\n // as an upgrade when it is STRICTLY NEWER than the current one. The prior\n // inequality check (`latestKnown !== currentVersion`) would treat a registry\n // that returned an OLDER version (e.g. beta/prerelease channel lag, or a\n // pinned `--spec` older than installed) as an \"upgrade\" and silently\n // downgrade the install.\n const isUpgrade =\n opts.latestKnown != null &&\n opts.currentVersion != null &&\n semverGt(opts.latestKnown, opts.currentVersion);\n // I2 — minVersion floor: refuse a registry-offered version below the floor.\n // null latestKnown is handled separately (registry-unreachable branch).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion = opts.latestKnown != null && semverLt(opts.latestKnown, floor);\n return {\n method: opts.method,\n targetSpec,\n currentVersion: opts.currentVersion,\n latestKnown: opts.latestKnown,\n isUpgrade,\n belowMinVersion,\n };\n}\n\n/**\n * Per-segment numeric semver comparison (major.minor.patch). Returns true when\n * `a` is strictly greater than `b`. Pre-release suffixes are ignored — Noir\n * versioning is `MAJOR.MINOR.PATCH` only, and the registry's dist-tag is the\n * source of truth for \"latest\". Non-numeric segments coerce to 0. Used here\n * instead of a full semver library to keep the CLI's dependency surface\n * unchanged (the same helper lives in install.ts for the downgrade guard).\n */\nfunction semverGt(a: string, b: string): boolean {\n const pa = a.split('.').map((s) => Number(s) || 0);\n const pb = b.split('.').map((s) => Number(s) || 0);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x > y;\n }\n return false;\n}\n\n/**\n * Per-segment numeric semver `<` (I2 — minVersion floor). Same comparison\n * discipline as {@link semverGt} / install.ts's downgrade guard: numeric per\n * segment, non-numeric → 0. Returns true when `a` is strictly less than `b`.\n */\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split('.').map((s) => Number(s) || 0);\n const pb = b.split('.').map((s) => Number(s) || 0);\n for (let i = 0; i < 3; i++) {\n const x = pa[i] ?? 0;\n const y = pb[i] ?? 0;\n if (x !== y) return x < y;\n }\n return false;\n}\n\nexport async function update(opts: UpdateOptions = {}): Promise<void> {\n // NOIR_DISABLE_UPDATES is the hard kill-switch for the SELF-UPDATE surface\n // (distinct from NOIR_DISABLE_UPDATE_CHECK, which only gags the background\n // version check). When set, `noir update` refuses to run at all — enterprises\n // can pin this in the environment to enforce \"updates flow only through the\n // package manager / image rebuild, never from inside the CLI\". `fail` is\n // `: never` (always throws) — the explicit `return` is defensive.\n if (process.env.NOIR_DISABLE_UPDATES !== undefined) {\n fail(\n EXIT.USAGE,\n 'self-update is disabled (NOIR_DISABLE_UPDATES is set); update via your package manager or image rebuild.',\n opts,\n );\n return;\n }\n\n const method = detectActiveMethod();\n const rec = readInstallRecord();\n const currentVersion = rec?.version ?? null;\n\n if (opts.check === true) {\n const checkSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest) {\n checkSpin.succeed(`Latest: ${latest} (you have ${currentVersion ?? 'unknown'})`);\n } else {\n checkSpin.fail('Could not reach the registry.');\n }\n return;\n }\n\n // Fetch latest (network; timeout-bound).\n const fetchSpin = spinner('Checking for updates...', opts).start();\n const latest = await fetchLatestVersion('latest');\n if (latest === null) {\n fetchSpin.fail('Could not reach the registry.');\n info('Could not reach the registry.', opts);\n return;\n }\n fetchSpin.succeed(`Latest: ${latest}`);\n // I2 — read the `update.minVersion` floor from the project config (falls\n // back to the config default '1.6.0' when the project isn't initialized or\n // the block is absent). Same try/catch pattern as home.ts's update-config read.\n let minVersion = DEFAULT_UPDATE_CONFIG.minVersion;\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const target = buildUpdateTarget({\n method,\n channel: 'latest',\n spec: opts.spec,\n currentVersion,\n latestKnown: latest,\n minVersion,\n });\n\n // I2 — refuse to install a registry-offered version below the configured\n // minVersion floor (same warn/refuse pattern as install.ts's downgrade guard).\n // Checked before the isUpgrade branch: a below-floor offer is unsafe even if\n // it happens to be newer than the (very old) current install.\n if (target.belowMinVersion) {\n warn(`Latest known ${target.latestKnown} is below the minVersion floor (${minVersion}).`);\n fail(\n EXIT.USAGE,\n `refusing update to ${target.latestKnown} (below minVersion ${minVersion})`,\n opts,\n );\n }\n\n if (!target.isUpgrade) {\n fetchSpin.succeed(`noir ${currentVersion} is up to date`);\n info(`noir ${currentVersion} is up to date.`, opts);\n return;\n }\n\n if (method === 'native') {\n const updateSpin = spinner(\n `Updating to ${target.latestKnown} via native installer...`,\n opts,\n ).start();\n const res = await installManagedNode({\n version: target.targetSpec === 'latest' ? undefined : target.targetSpec,\n env: process.env,\n });\n if (!res.ok) {\n updateSpin.fail('Update failed');\n fail(EXIT.ERROR, res.error ?? 'update failed', opts);\n }\n // Chicken-egg safety net: the update was ORCHESTRATED by the OLD binary\n // (which may predate the chmod fix). Re-assert the freshly-written shim is\n // executable so the user's NEXT `noir …` doesn't fail with permission\n // denied. This is the ONLY line that closes the regression once 1.7.4+ is\n // installed — it runs in the NEW binary's process.\n ensureShimExecutable();\n updateSpin.succeed(`Updated to noir ${res.version}`);\n success(`Updated to ${res.version}.`);\n return;\n }\n\n // npm/pnpm/yarn/bun/homebrew/scoop -> reinstall via the same manager.\n const cmd = updateCmdFor(method, target.targetSpec);\n if (!cmd)\n fail(EXIT.USAGE, `cannot auto-update a ${method} install; use the manager directly`, opts);\n const mgrSpin = spinner(`Updating via ${method}...`, opts).start();\n const { code, stderr } = await runManagerCmd(cmd, { env: process.env });\n if (code !== 0) {\n mgrSpin.fail(`Update failed`);\n fail(EXIT.ERROR, `update failed: ${stderr.slice(0, 300)}`, opts);\n }\n mgrSpin.succeed('Updated');\n success('Updated.');\n}\n\nfunction updateCmdFor(method: string, spec: string): string[] | null {\n switch (method) {\n case 'npm':\n return ['npm', 'install', '-g', `@noir-ai/cli@${spec}`];\n case 'pnpm':\n return ['pnpm', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'yarn':\n return ['yarn', 'global', 'add', `@noir-ai/cli@${spec}`];\n case 'bun':\n return ['bun', 'add', '-g', `@noir-ai/cli@${spec}`];\n case 'homebrew':\n return ['brew', 'upgrade', 'noir'];\n case 'scoop':\n return ['scoop', 'update', 'noir'];\n default:\n return null;\n }\n}\n\n/**\n * The human-readable update command for an install method. Mirrors\n * {@link updateCmdFor} (the machine command) with the manager the user should\n * actually run — `noir update` is the universal entry (it re-dispatches to the\n * right manager), but the package-manager spellings are shown for transparency.\n */\nexport function updateAdvice(method: string): string {\n switch (method) {\n case 'npm':\n return 'run `npm install -g @noir-ai/cli@latest`';\n case 'pnpm':\n return 'run `pnpm add -g @noir-ai/cli@latest`';\n case 'yarn':\n return 'run `yarn global add @noir-ai/cli@latest`';\n case 'bun':\n return 'run `bun add -g @noir-ai/cli@latest`';\n case 'homebrew':\n return 'run `brew upgrade noir`';\n case 'scoop':\n return 'run `scoop update noir`';\n case 'native':\n return 'run `noir update`';\n default:\n return 'run `noir update`';\n }\n}\n\n/** A \"new version available\" notice for the home menu (version + advice). */\nexport interface UpdateNotice {\n readonly latestVersion: string;\n readonly currentVersion: string;\n readonly method: string;\n readonly advice: string;\n}\n\n/**\n * Build the home-menu \"update available\" notice from the cached latest version.\n * Returns `null` when there is no cached latest, no current version, or the\n * latest is NOT strictly newer (the semver downgrade guard — a stale/older\n * cache entry is never advertised as an upgrade).\n */\nexport function buildUpdateNotice(opts: {\n method: string;\n currentVersion: string | null;\n latestKnown: string | null;\n}): UpdateNotice | null {\n const { method, currentVersion, latestKnown } = opts;\n if (currentVersion == null || latestKnown == null) return null;\n if (!semverGt(latestKnown, currentVersion)) return null;\n return { latestVersion: latestKnown, currentVersion, method, advice: updateAdvice(method) };\n}\n\n/** Non-blocking, time-boxed startup check. Writes the cache on success; silent on any failure. */\nexport async function runAsyncUpdateCheck(opts: {\n env: NodeJS.ProcessEnv;\n configUpdate: UpdateConfigLike;\n quiet: boolean;\n}): Promise<void> {\n try {\n const cache = readUpdateCache();\n if (!shouldCheckForUpdate({ env: opts.env, configUpdate: opts.configUpdate, cache })) return;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 2000);\n const latest = await fetchLatestVersion(opts.configUpdate.channel, controller.signal);\n clearTimeout(timer);\n if (latest != null) {\n writeUpdateCache({\n lastCheckAt: new Date().toISOString(),\n latestVersion: latest,\n channel: opts.configUpdate.channel,\n });\n }\n // Non-blocking: never print in quiet/CI/non-TTY; a mismatch only nudges.\n } catch {\n // silent on any failure — this is a non-blocking background check\n }\n}\n"],"mappings":"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","names":["latest"]}