@noir-ai/cli 1.7.3-beta.1 → 1.7.4-beta.1
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/bin.js +4 -4
- package/dist/{chunk-VW6KKJV4.js → chunk-JUVY6L3F.js} +4 -2
- package/dist/chunk-JUVY6L3F.js.map +1 -0
- package/dist/{chunk-6NRDGRCF.js → chunk-L7XR7EA3.js} +3 -1
- package/dist/chunk-L7XR7EA3.js.map +1 -0
- package/dist/{install-M54KESYS.js → install-4XEGT4GR.js} +2 -2
- package/dist/tui/{chunk-KQXGWFXJ.js → chunk-FQCYGUCG.js} +4 -2
- package/dist/tui/chunk-FQCYGUCG.js.map +1 -0
- package/dist/tui/{chunk-A2YMK3HL.js → chunk-X2CROVY6.js} +3 -1
- package/dist/tui/chunk-X2CROVY6.js.map +1 -0
- package/dist/tui/index.js +4 -4
- package/dist/tui/{install-BHXP6LG5.js → install-LZQUUIFK.js} +2 -2
- package/dist/tui/{update-FEYBGMVO.js → update-CHPJMWOV.js} +3 -3
- package/dist/{update-M74XT6CS.js → update-7JNJ7JF3.js} +3 -3
- package/package.json +9 -9
- package/dist/chunk-6NRDGRCF.js.map +0 -1
- package/dist/chunk-VW6KKJV4.js.map +0 -1
- package/dist/tui/chunk-A2YMK3HL.js.map +0 -1
- package/dist/tui/chunk-KQXGWFXJ.js.map +0 -1
- /package/dist/{install-M54KESYS.js.map → install-4XEGT4GR.js.map} +0 -0
- /package/dist/tui/{install-BHXP6LG5.js.map → install-LZQUUIFK.js.map} +0 -0
- /package/dist/tui/{update-FEYBGMVO.js.map → update-CHPJMWOV.js.map} +0 -0
- /package/dist/{update-M74XT6CS.js.map → update-7JNJ7JF3.js.map} +0 -0
package/dist/bin.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_UPDATE_CONFIG,
|
|
7
7
|
runAsyncUpdateCheck
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-JUVY6L3F.js";
|
|
9
|
+
import "./chunk-L7XR7EA3.js";
|
|
10
10
|
import {
|
|
11
11
|
init
|
|
12
12
|
} from "./chunk-KQYVVGOH.js";
|
|
@@ -1869,7 +1869,7 @@ function createProgram() {
|
|
|
1869
1869
|
"dismiss the migration banner for the current version (persists in install.json)"
|
|
1870
1870
|
).argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(
|
|
1871
1871
|
async (spec, opts, cmd) => {
|
|
1872
|
-
const { install } = await import("./install-
|
|
1872
|
+
const { install } = await import("./install-4XEGT4GR.js");
|
|
1873
1873
|
const cli = cmd.optsWithGlobals();
|
|
1874
1874
|
await install({
|
|
1875
1875
|
...cli,
|
|
@@ -1882,7 +1882,7 @@ function createProgram() {
|
|
|
1882
1882
|
);
|
|
1883
1883
|
installCmd.alias("migrate");
|
|
1884
1884
|
program2.command("update").description("update Noir to the latest version via the active install method").option("--check", "check for a new version without changing anything").argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(async (spec, opts, cmd) => {
|
|
1885
|
-
const { update } = await import("./update-
|
|
1885
|
+
const { update } = await import("./update-7JNJ7JF3.js");
|
|
1886
1886
|
const cli = cmd.optsWithGlobals();
|
|
1887
1887
|
await update({ ...cli, spec, check: opts.check === true });
|
|
1888
1888
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
installManagedNode
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-L7XR7EA3.js";
|
|
5
5
|
import {
|
|
6
6
|
EXIT,
|
|
7
7
|
fail,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
// src/commands/update.ts
|
|
15
15
|
import {
|
|
16
16
|
detectActiveMethod,
|
|
17
|
+
ensureShimExecutable,
|
|
17
18
|
fetchLatestVersion,
|
|
18
19
|
loadProjectInfo,
|
|
19
20
|
readInstallRecord,
|
|
@@ -132,6 +133,7 @@ async function update(opts = {}) {
|
|
|
132
133
|
updateSpin.fail("Update failed");
|
|
133
134
|
fail(EXIT.ERROR, res.error ?? "update failed", opts);
|
|
134
135
|
}
|
|
136
|
+
ensureShimExecutable();
|
|
135
137
|
updateSpin.succeed(`Updated to noir ${res.version}`);
|
|
136
138
|
success(`Updated to ${res.version}.`);
|
|
137
139
|
return;
|
|
@@ -191,4 +193,4 @@ export {
|
|
|
191
193
|
update,
|
|
192
194
|
runAsyncUpdateCheck
|
|
193
195
|
};
|
|
194
|
-
//# sourceMappingURL=chunk-
|
|
196
|
+
//# sourceMappingURL=chunk-JUVY6L3F.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
atomicWriteFile,
|
|
16
16
|
detectActiveMethod,
|
|
17
17
|
detectInstallMethods,
|
|
18
|
+
ensureShimExecutable,
|
|
18
19
|
loadProjectInfo,
|
|
19
20
|
MANAGED_NODE_VERSION,
|
|
20
21
|
NOIR_VERSION,
|
|
@@ -93,6 +94,7 @@ async function installManagedNode(opts) {
|
|
|
93
94
|
`;
|
|
94
95
|
atomicWriteFile(shim, shimBody);
|
|
95
96
|
chmodSync(shim, 493);
|
|
97
|
+
ensureShimExecutable();
|
|
96
98
|
const ver = await runManagerCmd(
|
|
97
99
|
[
|
|
98
100
|
nodeBin,
|
|
@@ -205,4 +207,4 @@ export {
|
|
|
205
207
|
installManagedNode,
|
|
206
208
|
install
|
|
207
209
|
};
|
|
208
|
-
//# sourceMappingURL=chunk-
|
|
210
|
+
//# sourceMappingURL=chunk-L7XR7EA3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/install.ts"],"sourcesContent":["import { chmodSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n atomicWriteFile,\n type DetectResult,\n detectActiveMethod,\n detectInstallMethods,\n ensureShimExecutable,\n loadProjectInfo,\n MANAGED_NODE_VERSION,\n NOIR_VERSION,\n noirHome,\n type ProvisionedNode,\n type ProvisionOptions,\n provisionManagedNode,\n readInstallRecord,\n runManagerCmd,\n writeInstallRecord,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\n\nexport interface InstallOptions extends CliOptions {\n list?: boolean;\n uninstallPrev?: boolean;\n spec?: string;\n /** Dismiss the migration banner for the current CLI version (persists in\n * install.json's `dismissedVersions`). Typically passed with `--list`. */\n dismiss?: boolean;\n}\n\nexport interface MigrationPlan {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: string | null;\n shouldMigrate: boolean;\n isDowngrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the resolved\n * target is a concrete version below the configured floor. Channel targets\n * (`latest`/`beta`) resolve to the newest at install time and never trip it.\n * Enforced in {@link install} with the same warn/refuse pattern as the\n * downgrade guard.\n */\n belowMinVersion: boolean;\n nativeVersion: string;\n prevUninstallCmd: string | null;\n autoUninstall: boolean; // always false unless --uninstall-prev\n}\n\n/** Per-segment numeric semver comparison. Returns true when `a < b`. */\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\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/** Pure: build the migration plan from detection + target. No I/O. */\nexport function buildMigrationPlan(opts: {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: 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}): MigrationPlan {\n const detected = opts.detected;\n const shouldMigrate = detected.length > 0 && opts.currentMethod !== 'native';\n const nativeVersion = opts.targetSpec;\n let isDowngrade = false;\n if (opts.installedVersion && nativeVersion !== 'latest' && nativeVersion !== 'beta') {\n isDowngrade = semverLt(nativeVersion, opts.installedVersion);\n }\n // I2 — minVersion floor: only a concrete version below the floor trips it;\n // 'latest'/'beta' resolve to the newest at install time (always >= floor).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion =\n nativeVersion !== 'latest' && nativeVersion !== 'beta' && semverLt(nativeVersion, floor);\n return {\n detected,\n currentMethod: opts.currentMethod,\n targetSpec: opts.targetSpec,\n installedVersion: opts.installedVersion,\n shouldMigrate,\n isDowngrade,\n belowMinVersion,\n nativeVersion,\n prevUninstallCmd: detected[0]?.uninstallCmd ?? null,\n autoUninstall: false,\n };\n}\n\nexport interface InstallManagedNodeOptions {\n channel?: string;\n version?: string;\n env: NodeJS.ProcessEnv;\n /**\n * Forwarded into {@link provisionManagedNode} (P1). Carries the offline mock\n * seams (`fetch`, `exec`, `target`, `signal`) so the install pipeline is\n * unit-testable without network — same pattern as `provisionManagedNode`'s\n * own suite in `packages/core/test/node-provision.test.ts`.\n */\n provision?: Pick<ProvisionOptions, 'fetch' | 'exec' | 'target' | 'signal'>;\n}\n\n/** Normalized record of which runtime backed an install (P2). */\nexport interface InstallManagedNodeResult {\n ok: boolean;\n version: string | null;\n /** What runtime backed this install: `'managed'` (downloaded into ~/.noir)\n * or `'system'` (used the system-Node fallback). */\n runtimeSource?: ProvisionedNode['source'];\n error?: string;\n}\n\n/**\n * Provision the managed Node runtime (P1) and install `@noir-ai/cli` into an\n * isolated `~/.noir/cli` prefix, shimmed from `~/.noir/bin/noir`.\n *\n * Replaces the prior \"not provisioned\" fail branch: instead of requiring the\n * user to pre-run `install.sh`/`install.ps1`, this now delegates to\n * {@link provisionManagedNode} (download + verify + extract, fail-closed), then\n * installs the CLI with the provisioned npm. On a system-Node fallback the\n * fallback node/npm paths are used and a warning is emitted; the install\n * record's `managedRuntimeVersion` reflects which path was taken so `noir\n * doctor` can report it accurately.\n */\nexport async function installManagedNode(\n opts: InstallManagedNodeOptions,\n): Promise<InstallManagedNodeResult> {\n const spec = opts.version ?? opts.channel ?? 'latest';\n const home = noirHome();\n const cliDir = join(home, 'cli');\n const binDir = join(home, 'bin');\n mkdirSync(cliDir, { recursive: true });\n mkdirSync(binDir, { recursive: true });\n\n // Provision the managed Node runtime (P1). Idempotent — re-runs are no-ops.\n // On any failure, provisionManagedNode either falls back to a system Node\n // >=22 (returns { source: 'system' }) or throws — we surface the throw as an\n // {ok:false} envelope rather than letting it kill the CLI.\n let runtime: ProvisionedNode;\n try {\n runtime = await provisionManagedNode({\n ...(opts.provision ?? {}),\n env: opts.env,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return { ok: false, version: null, error: `managed-Node provision failed: ${reason}` };\n }\n if (runtime.source === 'system') {\n warn(\n `managed-Node download failed; using system Node ${runtime.version} at ${runtime.nodeBin}.`,\n );\n }\n\n const { nodeBin, npmBin } = runtime;\n\n // Install @noir-ai/cli into the isolated prefix using the provisioned npm.\n const { code, stderr } = await runManagerCmd(\n [npmBin, 'install', '-g', `@noir-ai/cli@${spec}`, `--prefix=${cliDir}`],\n { env: opts.env, timeoutMs: 120_000 },\n );\n if (code !== 0) {\n return { ok: false, version: null, error: `npm install failed: ${stderr.slice(0, 300)}` };\n }\n\n // Shim: ~/.noir/bin/noir -> provisioned node + isolated prefix.\n const shim = join(binDir, 'noir');\n const shimBody = `#!/usr/bin/env bash\\n\"${nodeBin}\" \"${join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js')}\" \"$@\"\\n`;\n atomicWriteFile(shim, shimBody);\n chmodSync(shim, 0o755); // must be executable — atomicWriteFile sets 0o644 by default\n ensureShimExecutable(); // defense-in-depth: re-assert 0o755 (idempotent, never throws)\n // (POSIX shim; Windows uses a .cmd wrapper -- install.sh/install.ps1, P3.)\n\n // Resolve installed version via the provisioned node.\n const ver = await runManagerCmd(\n [\n nodeBin,\n join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js'),\n '--version',\n ],\n { env: opts.env, timeoutMs: 30_000 },\n );\n const version = ver.code === 0 ? ver.stdout.trim() : null;\n writeInstallRecord({\n method: 'native',\n version: version ?? '0.0.0',\n channel: opts.channel ?? 'latest',\n installedAt: new Date().toISOString(),\n // Reflect which runtime backs this install: the pinned managed version\n // (e.g. '22.23.2') for managed, or 'system-<v>' for the fallback so doctor\n // can distinguish a fallback install from a real managed one.\n managedRuntimeVersion:\n runtime.source === 'managed' ? MANAGED_NODE_VERSION : `system-${runtime.version}`,\n });\n return { ok: true, version, runtimeSource: runtime.source };\n}\n\nexport async function install(opts: InstallOptions = {}): Promise<void> {\n // --dismiss: append the current CLI version to install.json's\n // `dismissedVersions` so the migration banner stops showing for this\n // version. Idempotent (no duplicate entries). Typically combined with\n // `--list`; works standalone too. Requires an existing install record —\n // if there's none, there's nothing to dismiss (no banner was shown).\n if (opts.dismiss === true) {\n const rec = readInstallRecord();\n if (!rec) {\n info('No install record found; nothing to dismiss.');\n return;\n }\n const dismissed = new Set(rec.dismissedVersions ?? []);\n if (!dismissed.has(NOIR_VERSION)) dismissed.add(NOIR_VERSION);\n writeInstallRecord({ ...rec, dismissedVersions: [...dismissed] });\n success(`Migration banner dismissed for ${NOIR_VERSION}.`);\n if (opts.list !== true) return; // --dismiss alone is done; --list continues below.\n }\n\n if (opts.list === true) {\n const detected = await detectInstallMethods(process.env);\n const jsonOutput = { ok: true, data: { detected } };\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify(jsonOutput)}\\n`);\n return;\n }\n info('Detected installs:');\n for (const d of detected) info(` ${d.method} (${d.version ?? 'unknown'})`);\n return;\n }\n\n const currentMethod = detectActiveMethod();\n const detected = await detectInstallMethods(process.env);\n const installedRecord = readInstallRecord();\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 = '1.6.0';\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const plan = buildMigrationPlan({\n detected,\n currentMethod,\n targetSpec: opts.spec ?? 'latest',\n installedVersion: installedRecord?.version ?? null,\n minVersion,\n });\n\n // I2 — refuse to install a version below the configured minVersion floor\n // (same warn/refuse pattern as the installed-version downgrade guard).\n if (plan.belowMinVersion) {\n warn(`Target ${plan.nativeVersion} is below the minVersion floor (${minVersion}).`);\n if (opts.noInput === true || opts.input === false) {\n fail(\n EXIT.USAGE,\n `refusing install of ${plan.nativeVersion} (below minVersion ${minVersion})`,\n opts,\n );\n }\n }\n\n if (plan.isDowngrade) {\n warn(`Target ${plan.nativeVersion} is OLDER than installed ${plan.installedVersion}.`);\n // (Interactive confirm is skipped under --no-input; we hard-stop to be safe.)\n if (opts.noInput === true || opts.input === false) {\n fail(\n EXIT.USAGE,\n `refusing downgrade to ${plan.nativeVersion} (installed ${plan.installedVersion})`,\n opts,\n );\n }\n // @clack confirm gate (lazy import) -- same pattern as home.ts.\n }\n\n const installSpin = spinner('Installing Noir via native installer...', opts).start();\n const result = await installManagedNode({\n channel: plan.nativeVersion === 'beta' ? 'beta' : undefined,\n version: plan.nativeVersion.startsWith('v')\n ? plan.nativeVersion\n : plan.nativeVersion === 'latest' || plan.nativeVersion === 'beta'\n ? undefined\n : plan.nativeVersion,\n env: process.env,\n });\n if (!result.ok) {\n installSpin.fail('Native install failed');\n fail(EXIT.ERROR, result.error ?? 'native install failed', opts);\n }\n installSpin.succeed(`Installed noir ${result.version}`);\n\n info(`Installed native: ${result.version}.`);\n if (plan.prevUninstallCmd && opts.uninstallPrev !== true) {\n warn(`To finish the migration, uninstall the previous install: ${plan.prevUninstallCmd}`);\n warn(\n ' Or re-run with --uninstall-prev to do it now. Rollback: reinstall via the previous manager.',\n );\n } else if (plan.prevUninstallCmd && opts.uninstallPrev === true) {\n const { code } = await runManagerCmd(plan.prevUninstallCmd.split(' '), { env: process.env });\n if (code === 0) success('Previous install removed.');\n else warn('Previous install NOT removed (non-zero exit). You can remove it manually.');\n }\n // Claude Code #41806/#27910 mitigation: the old npm bin may still resolve\n // first in $PATH after migration (the shell hashes command paths, and a\n // stale global install can shadow ~/.noir/bin). Hint the user to refresh.\n info('If `noir` still points at the old install, run: hash -r && which -a noir');\n success(`noir ${result.version} installed via native. Run \\`noir doctor\\` to verify.`);\n}\n"],"mappings":"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","names":["detected"]}
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
buildMigrationPlan,
|
|
4
4
|
install,
|
|
5
5
|
installManagedNode
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-L7XR7EA3.js";
|
|
7
7
|
import "./chunk-3NT7RW6K.js";
|
|
8
8
|
export {
|
|
9
9
|
buildMigrationPlan,
|
|
10
10
|
install,
|
|
11
11
|
installManagedNode
|
|
12
12
|
};
|
|
13
|
-
//# sourceMappingURL=install-
|
|
13
|
+
//# sourceMappingURL=install-4XEGT4GR.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
installManagedNode
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-X2CROVY6.js";
|
|
4
4
|
import {
|
|
5
5
|
EXIT,
|
|
6
6
|
fail,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
// src/commands/update.ts
|
|
14
14
|
import {
|
|
15
15
|
detectActiveMethod,
|
|
16
|
+
ensureShimExecutable,
|
|
16
17
|
fetchLatestVersion,
|
|
17
18
|
loadProjectInfo,
|
|
18
19
|
readInstallRecord,
|
|
@@ -131,6 +132,7 @@ async function update(opts = {}) {
|
|
|
131
132
|
updateSpin.fail("Update failed");
|
|
132
133
|
fail(EXIT.ERROR, res.error ?? "update failed", opts);
|
|
133
134
|
}
|
|
135
|
+
ensureShimExecutable();
|
|
134
136
|
updateSpin.succeed(`Updated to noir ${res.version}`);
|
|
135
137
|
success(`Updated to ${res.version}.`);
|
|
136
138
|
return;
|
|
@@ -190,4 +192,4 @@ export {
|
|
|
190
192
|
update,
|
|
191
193
|
runAsyncUpdateCheck
|
|
192
194
|
};
|
|
193
|
-
//# sourceMappingURL=chunk-
|
|
195
|
+
//# sourceMappingURL=chunk-FQCYGUCG.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
atomicWriteFile,
|
|
15
15
|
detectActiveMethod,
|
|
16
16
|
detectInstallMethods,
|
|
17
|
+
ensureShimExecutable,
|
|
17
18
|
loadProjectInfo,
|
|
18
19
|
MANAGED_NODE_VERSION,
|
|
19
20
|
NOIR_VERSION,
|
|
@@ -92,6 +93,7 @@ async function installManagedNode(opts) {
|
|
|
92
93
|
`;
|
|
93
94
|
atomicWriteFile(shim, shimBody);
|
|
94
95
|
chmodSync(shim, 493);
|
|
96
|
+
ensureShimExecutable();
|
|
95
97
|
const ver = await runManagerCmd(
|
|
96
98
|
[
|
|
97
99
|
nodeBin,
|
|
@@ -204,4 +206,4 @@ export {
|
|
|
204
206
|
installManagedNode,
|
|
205
207
|
install
|
|
206
208
|
};
|
|
207
|
-
//# sourceMappingURL=chunk-
|
|
209
|
+
//# sourceMappingURL=chunk-X2CROVY6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/install.ts"],"sourcesContent":["import { chmodSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n atomicWriteFile,\n type DetectResult,\n detectActiveMethod,\n detectInstallMethods,\n ensureShimExecutable,\n loadProjectInfo,\n MANAGED_NODE_VERSION,\n NOIR_VERSION,\n noirHome,\n type ProvisionedNode,\n type ProvisionOptions,\n provisionManagedNode,\n readInstallRecord,\n runManagerCmd,\n writeInstallRecord,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\n\nexport interface InstallOptions extends CliOptions {\n list?: boolean;\n uninstallPrev?: boolean;\n spec?: string;\n /** Dismiss the migration banner for the current CLI version (persists in\n * install.json's `dismissedVersions`). Typically passed with `--list`. */\n dismiss?: boolean;\n}\n\nexport interface MigrationPlan {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: string | null;\n shouldMigrate: boolean;\n isDowngrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the resolved\n * target is a concrete version below the configured floor. Channel targets\n * (`latest`/`beta`) resolve to the newest at install time and never trip it.\n * Enforced in {@link install} with the same warn/refuse pattern as the\n * downgrade guard.\n */\n belowMinVersion: boolean;\n nativeVersion: string;\n prevUninstallCmd: string | null;\n autoUninstall: boolean; // always false unless --uninstall-prev\n}\n\n/** Per-segment numeric semver comparison. Returns true when `a < b`. */\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\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/** Pure: build the migration plan from detection + target. No I/O. */\nexport function buildMigrationPlan(opts: {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: 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}): MigrationPlan {\n const detected = opts.detected;\n const shouldMigrate = detected.length > 0 && opts.currentMethod !== 'native';\n const nativeVersion = opts.targetSpec;\n let isDowngrade = false;\n if (opts.installedVersion && nativeVersion !== 'latest' && nativeVersion !== 'beta') {\n isDowngrade = semverLt(nativeVersion, opts.installedVersion);\n }\n // I2 — minVersion floor: only a concrete version below the floor trips it;\n // 'latest'/'beta' resolve to the newest at install time (always >= floor).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion =\n nativeVersion !== 'latest' && nativeVersion !== 'beta' && semverLt(nativeVersion, floor);\n return {\n detected,\n currentMethod: opts.currentMethod,\n targetSpec: opts.targetSpec,\n installedVersion: opts.installedVersion,\n shouldMigrate,\n isDowngrade,\n belowMinVersion,\n nativeVersion,\n prevUninstallCmd: detected[0]?.uninstallCmd ?? null,\n autoUninstall: false,\n };\n}\n\nexport interface InstallManagedNodeOptions {\n channel?: string;\n version?: string;\n env: NodeJS.ProcessEnv;\n /**\n * Forwarded into {@link provisionManagedNode} (P1). Carries the offline mock\n * seams (`fetch`, `exec`, `target`, `signal`) so the install pipeline is\n * unit-testable without network — same pattern as `provisionManagedNode`'s\n * own suite in `packages/core/test/node-provision.test.ts`.\n */\n provision?: Pick<ProvisionOptions, 'fetch' | 'exec' | 'target' | 'signal'>;\n}\n\n/** Normalized record of which runtime backed an install (P2). */\nexport interface InstallManagedNodeResult {\n ok: boolean;\n version: string | null;\n /** What runtime backed this install: `'managed'` (downloaded into ~/.noir)\n * or `'system'` (used the system-Node fallback). */\n runtimeSource?: ProvisionedNode['source'];\n error?: string;\n}\n\n/**\n * Provision the managed Node runtime (P1) and install `@noir-ai/cli` into an\n * isolated `~/.noir/cli` prefix, shimmed from `~/.noir/bin/noir`.\n *\n * Replaces the prior \"not provisioned\" fail branch: instead of requiring the\n * user to pre-run `install.sh`/`install.ps1`, this now delegates to\n * {@link provisionManagedNode} (download + verify + extract, fail-closed), then\n * installs the CLI with the provisioned npm. On a system-Node fallback the\n * fallback node/npm paths are used and a warning is emitted; the install\n * record's `managedRuntimeVersion` reflects which path was taken so `noir\n * doctor` can report it accurately.\n */\nexport async function installManagedNode(\n opts: InstallManagedNodeOptions,\n): Promise<InstallManagedNodeResult> {\n const spec = opts.version ?? opts.channel ?? 'latest';\n const home = noirHome();\n const cliDir = join(home, 'cli');\n const binDir = join(home, 'bin');\n mkdirSync(cliDir, { recursive: true });\n mkdirSync(binDir, { recursive: true });\n\n // Provision the managed Node runtime (P1). Idempotent — re-runs are no-ops.\n // On any failure, provisionManagedNode either falls back to a system Node\n // >=22 (returns { source: 'system' }) or throws — we surface the throw as an\n // {ok:false} envelope rather than letting it kill the CLI.\n let runtime: ProvisionedNode;\n try {\n runtime = await provisionManagedNode({\n ...(opts.provision ?? {}),\n env: opts.env,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return { ok: false, version: null, error: `managed-Node provision failed: ${reason}` };\n }\n if (runtime.source === 'system') {\n warn(\n `managed-Node download failed; using system Node ${runtime.version} at ${runtime.nodeBin}.`,\n );\n }\n\n const { nodeBin, npmBin } = runtime;\n\n // Install @noir-ai/cli into the isolated prefix using the provisioned npm.\n const { code, stderr } = await runManagerCmd(\n [npmBin, 'install', '-g', `@noir-ai/cli@${spec}`, `--prefix=${cliDir}`],\n { env: opts.env, timeoutMs: 120_000 },\n );\n if (code !== 0) {\n return { ok: false, version: null, error: `npm install failed: ${stderr.slice(0, 300)}` };\n }\n\n // Shim: ~/.noir/bin/noir -> provisioned node + isolated prefix.\n const shim = join(binDir, 'noir');\n const shimBody = `#!/usr/bin/env bash\\n\"${nodeBin}\" \"${join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js')}\" \"$@\"\\n`;\n atomicWriteFile(shim, shimBody);\n chmodSync(shim, 0o755); // must be executable — atomicWriteFile sets 0o644 by default\n ensureShimExecutable(); // defense-in-depth: re-assert 0o755 (idempotent, never throws)\n // (POSIX shim; Windows uses a .cmd wrapper -- install.sh/install.ps1, P3.)\n\n // Resolve installed version via the provisioned node.\n const ver = await runManagerCmd(\n [\n nodeBin,\n join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js'),\n '--version',\n ],\n { env: opts.env, timeoutMs: 30_000 },\n );\n const version = ver.code === 0 ? ver.stdout.trim() : null;\n writeInstallRecord({\n method: 'native',\n version: version ?? '0.0.0',\n channel: opts.channel ?? 'latest',\n installedAt: new Date().toISOString(),\n // Reflect which runtime backs this install: the pinned managed version\n // (e.g. '22.23.2') for managed, or 'system-<v>' for the fallback so doctor\n // can distinguish a fallback install from a real managed one.\n managedRuntimeVersion:\n runtime.source === 'managed' ? MANAGED_NODE_VERSION : `system-${runtime.version}`,\n });\n return { ok: true, version, runtimeSource: runtime.source };\n}\n\nexport async function install(opts: InstallOptions = {}): Promise<void> {\n // --dismiss: append the current CLI version to install.json's\n // `dismissedVersions` so the migration banner stops showing for this\n // version. Idempotent (no duplicate entries). Typically combined with\n // `--list`; works standalone too. Requires an existing install record —\n // if there's none, there's nothing to dismiss (no banner was shown).\n if (opts.dismiss === true) {\n const rec = readInstallRecord();\n if (!rec) {\n info('No install record found; nothing to dismiss.');\n return;\n }\n const dismissed = new Set(rec.dismissedVersions ?? []);\n if (!dismissed.has(NOIR_VERSION)) dismissed.add(NOIR_VERSION);\n writeInstallRecord({ ...rec, dismissedVersions: [...dismissed] });\n success(`Migration banner dismissed for ${NOIR_VERSION}.`);\n if (opts.list !== true) return; // --dismiss alone is done; --list continues below.\n }\n\n if (opts.list === true) {\n const detected = await detectInstallMethods(process.env);\n const jsonOutput = { ok: true, data: { detected } };\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify(jsonOutput)}\\n`);\n return;\n }\n info('Detected installs:');\n for (const d of detected) info(` ${d.method} (${d.version ?? 'unknown'})`);\n return;\n }\n\n const currentMethod = detectActiveMethod();\n const detected = await detectInstallMethods(process.env);\n const installedRecord = readInstallRecord();\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 = '1.6.0';\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const plan = buildMigrationPlan({\n detected,\n currentMethod,\n targetSpec: opts.spec ?? 'latest',\n installedVersion: installedRecord?.version ?? null,\n minVersion,\n });\n\n // I2 — refuse to install a version below the configured minVersion floor\n // (same warn/refuse pattern as the installed-version downgrade guard).\n if (plan.belowMinVersion) {\n warn(`Target ${plan.nativeVersion} is below the minVersion floor (${minVersion}).`);\n if (opts.noInput === true || opts.input === false) {\n fail(\n EXIT.USAGE,\n `refusing install of ${plan.nativeVersion} (below minVersion ${minVersion})`,\n opts,\n );\n }\n }\n\n if (plan.isDowngrade) {\n warn(`Target ${plan.nativeVersion} is OLDER than installed ${plan.installedVersion}.`);\n // (Interactive confirm is skipped under --no-input; we hard-stop to be safe.)\n if (opts.noInput === true || opts.input === false) {\n fail(\n EXIT.USAGE,\n `refusing downgrade to ${plan.nativeVersion} (installed ${plan.installedVersion})`,\n opts,\n );\n }\n // @clack confirm gate (lazy import) -- same pattern as home.ts.\n }\n\n const installSpin = spinner('Installing Noir via native installer...', opts).start();\n const result = await installManagedNode({\n channel: plan.nativeVersion === 'beta' ? 'beta' : undefined,\n version: plan.nativeVersion.startsWith('v')\n ? plan.nativeVersion\n : plan.nativeVersion === 'latest' || plan.nativeVersion === 'beta'\n ? undefined\n : plan.nativeVersion,\n env: process.env,\n });\n if (!result.ok) {\n installSpin.fail('Native install failed');\n fail(EXIT.ERROR, result.error ?? 'native install failed', opts);\n }\n installSpin.succeed(`Installed noir ${result.version}`);\n\n info(`Installed native: ${result.version}.`);\n if (plan.prevUninstallCmd && opts.uninstallPrev !== true) {\n warn(`To finish the migration, uninstall the previous install: ${plan.prevUninstallCmd}`);\n warn(\n ' Or re-run with --uninstall-prev to do it now. Rollback: reinstall via the previous manager.',\n );\n } else if (plan.prevUninstallCmd && opts.uninstallPrev === true) {\n const { code } = await runManagerCmd(plan.prevUninstallCmd.split(' '), { env: process.env });\n if (code === 0) success('Previous install removed.');\n else warn('Previous install NOT removed (non-zero exit). You can remove it manually.');\n }\n // Claude Code #41806/#27910 mitigation: the old npm bin may still resolve\n // first in $PATH after migration (the shell hashes command paths, and a\n // stale global install can shadow ~/.noir/bin). Hint the user to refresh.\n info('If `noir` still points at the old install, run: hash -r && which -a noir');\n success(`noir ${result.version} installed via native. Run \\`noir doctor\\` to verify.`);\n}\n"],"mappings":"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","names":["detected"]}
|
package/dist/tui/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_UPDATE_CONFIG,
|
|
3
3
|
runAsyncUpdateCheck
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-FQCYGUCG.js";
|
|
5
|
+
import "./chunk-X2CROVY6.js";
|
|
6
6
|
import {
|
|
7
7
|
init
|
|
8
8
|
} from "./chunk-KBM2UXAC.js";
|
|
@@ -1594,7 +1594,7 @@ function createProgram() {
|
|
|
1594
1594
|
"dismiss the migration banner for the current version (persists in install.json)"
|
|
1595
1595
|
).argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(
|
|
1596
1596
|
async (spec, opts, cmd) => {
|
|
1597
|
-
const { install } = await import("./install-
|
|
1597
|
+
const { install } = await import("./install-LZQUUIFK.js");
|
|
1598
1598
|
const cli = cmd.optsWithGlobals();
|
|
1599
1599
|
await install({
|
|
1600
1600
|
...cli,
|
|
@@ -1607,7 +1607,7 @@ function createProgram() {
|
|
|
1607
1607
|
);
|
|
1608
1608
|
installCmd.alias("migrate");
|
|
1609
1609
|
program2.command("update").description("update Noir to the latest version via the active install method").option("--check", "check for a new version without changing anything").argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(async (spec, opts, cmd) => {
|
|
1610
|
-
const { update } = await import("./update-
|
|
1610
|
+
const { update } = await import("./update-CHPJMWOV.js");
|
|
1611
1611
|
const cli = cmd.optsWithGlobals();
|
|
1612
1612
|
await update({ ...cli, spec, check: opts.check === true });
|
|
1613
1613
|
});
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
buildMigrationPlan,
|
|
3
3
|
install,
|
|
4
4
|
installManagedNode
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-X2CROVY6.js";
|
|
6
6
|
import "./chunk-A2Q74DOV.js";
|
|
7
7
|
export {
|
|
8
8
|
buildMigrationPlan,
|
|
9
9
|
install,
|
|
10
10
|
installManagedNode
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=install-
|
|
12
|
+
//# sourceMappingURL=install-LZQUUIFK.js.map
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
buildUpdateTarget,
|
|
4
4
|
runAsyncUpdateCheck,
|
|
5
5
|
update
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-FQCYGUCG.js";
|
|
7
|
+
import "./chunk-X2CROVY6.js";
|
|
8
8
|
import "./chunk-A2Q74DOV.js";
|
|
9
9
|
export {
|
|
10
10
|
DEFAULT_UPDATE_CONFIG,
|
|
@@ -12,4 +12,4 @@ export {
|
|
|
12
12
|
runAsyncUpdateCheck,
|
|
13
13
|
update
|
|
14
14
|
};
|
|
15
|
-
//# sourceMappingURL=update-
|
|
15
|
+
//# sourceMappingURL=update-CHPJMWOV.js.map
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
buildUpdateTarget,
|
|
5
5
|
runAsyncUpdateCheck,
|
|
6
6
|
update
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-JUVY6L3F.js";
|
|
8
|
+
import "./chunk-L7XR7EA3.js";
|
|
9
9
|
import "./chunk-3NT7RW6K.js";
|
|
10
10
|
export {
|
|
11
11
|
DEFAULT_UPDATE_CONFIG,
|
|
@@ -13,4 +13,4 @@ export {
|
|
|
13
13
|
runAsyncUpdateCheck,
|
|
14
14
|
update
|
|
15
15
|
};
|
|
16
|
-
//# sourceMappingURL=update-
|
|
16
|
+
//# sourceMappingURL=update-7JNJ7JF3.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noir-ai/cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4-beta.1",
|
|
4
4
|
"description": "Noir CLI — the `noir` command tree (commander + @clack/prompts), the shell entry point to the Noir AI toolkit.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "agaaaptr",
|
|
@@ -55,14 +55,14 @@
|
|
|
55
55
|
"ora": "^8.1.0",
|
|
56
56
|
"picocolors": "^1.1.1",
|
|
57
57
|
"react": "^19.2.0",
|
|
58
|
-
"@noir-ai/adapters": "1.7.
|
|
59
|
-
"@noir-ai/
|
|
60
|
-
"@noir-ai/
|
|
61
|
-
"@noir-ai/
|
|
62
|
-
"@noir-ai/
|
|
63
|
-
"@noir-ai/model": "1.7.
|
|
64
|
-
"@noir-ai/
|
|
65
|
-
"@noir-ai/
|
|
58
|
+
"@noir-ai/adapters": "1.7.4-beta.1",
|
|
59
|
+
"@noir-ai/core": "1.7.4-beta.1",
|
|
60
|
+
"@noir-ai/create": "1.7.4-beta.1",
|
|
61
|
+
"@noir-ai/daemon": "1.7.4-beta.1",
|
|
62
|
+
"@noir-ai/context": "1.7.4-beta.1",
|
|
63
|
+
"@noir-ai/model": "1.7.4-beta.1",
|
|
64
|
+
"@noir-ai/store": "1.7.4-beta.1",
|
|
65
|
+
"@noir-ai/skills": "1.7.4-beta.1"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^26.1.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/install.ts"],"sourcesContent":["import { chmodSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n atomicWriteFile,\n type DetectResult,\n detectActiveMethod,\n detectInstallMethods,\n loadProjectInfo,\n MANAGED_NODE_VERSION,\n NOIR_VERSION,\n noirHome,\n type ProvisionedNode,\n type ProvisionOptions,\n provisionManagedNode,\n readInstallRecord,\n runManagerCmd,\n writeInstallRecord,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\n\nexport interface InstallOptions extends CliOptions {\n list?: boolean;\n uninstallPrev?: boolean;\n spec?: string;\n /** Dismiss the migration banner for the current CLI version (persists in\n * install.json's `dismissedVersions`). Typically passed with `--list`. */\n dismiss?: boolean;\n}\n\nexport interface MigrationPlan {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: string | null;\n shouldMigrate: boolean;\n isDowngrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the resolved\n * target is a concrete version below the configured floor. Channel targets\n * (`latest`/`beta`) resolve to the newest at install time and never trip it.\n * Enforced in {@link install} with the same warn/refuse pattern as the\n * downgrade guard.\n */\n belowMinVersion: boolean;\n nativeVersion: string;\n prevUninstallCmd: string | null;\n autoUninstall: boolean; // always false unless --uninstall-prev\n}\n\n/** Per-segment numeric semver comparison. Returns true when `a < b`. */\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\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/** Pure: build the migration plan from detection + target. No I/O. */\nexport function buildMigrationPlan(opts: {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: 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}): MigrationPlan {\n const detected = opts.detected;\n const shouldMigrate = detected.length > 0 && opts.currentMethod !== 'native';\n const nativeVersion = opts.targetSpec;\n let isDowngrade = false;\n if (opts.installedVersion && nativeVersion !== 'latest' && nativeVersion !== 'beta') {\n isDowngrade = semverLt(nativeVersion, opts.installedVersion);\n }\n // I2 — minVersion floor: only a concrete version below the floor trips it;\n // 'latest'/'beta' resolve to the newest at install time (always >= floor).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion =\n nativeVersion !== 'latest' && nativeVersion !== 'beta' && semverLt(nativeVersion, floor);\n return {\n detected,\n currentMethod: opts.currentMethod,\n targetSpec: opts.targetSpec,\n installedVersion: opts.installedVersion,\n shouldMigrate,\n isDowngrade,\n belowMinVersion,\n nativeVersion,\n prevUninstallCmd: detected[0]?.uninstallCmd ?? null,\n autoUninstall: false,\n };\n}\n\nexport interface InstallManagedNodeOptions {\n channel?: string;\n version?: string;\n env: NodeJS.ProcessEnv;\n /**\n * Forwarded into {@link provisionManagedNode} (P1). Carries the offline mock\n * seams (`fetch`, `exec`, `target`, `signal`) so the install pipeline is\n * unit-testable without network — same pattern as `provisionManagedNode`'s\n * own suite in `packages/core/test/node-provision.test.ts`.\n */\n provision?: Pick<ProvisionOptions, 'fetch' | 'exec' | 'target' | 'signal'>;\n}\n\n/** Normalized record of which runtime backed an install (P2). */\nexport interface InstallManagedNodeResult {\n ok: boolean;\n version: string | null;\n /** What runtime backed this install: `'managed'` (downloaded into ~/.noir)\n * or `'system'` (used the system-Node fallback). */\n runtimeSource?: ProvisionedNode['source'];\n error?: string;\n}\n\n/**\n * Provision the managed Node runtime (P1) and install `@noir-ai/cli` into an\n * isolated `~/.noir/cli` prefix, shimmed from `~/.noir/bin/noir`.\n *\n * Replaces the prior \"not provisioned\" fail branch: instead of requiring the\n * user to pre-run `install.sh`/`install.ps1`, this now delegates to\n * {@link provisionManagedNode} (download + verify + extract, fail-closed), then\n * installs the CLI with the provisioned npm. On a system-Node fallback the\n * fallback node/npm paths are used and a warning is emitted; the install\n * record's `managedRuntimeVersion` reflects which path was taken so `noir\n * doctor` can report it accurately.\n */\nexport async function installManagedNode(\n opts: InstallManagedNodeOptions,\n): Promise<InstallManagedNodeResult> {\n const spec = opts.version ?? opts.channel ?? 'latest';\n const home = noirHome();\n const cliDir = join(home, 'cli');\n const binDir = join(home, 'bin');\n mkdirSync(cliDir, { recursive: true });\n mkdirSync(binDir, { recursive: true });\n\n // Provision the managed Node runtime (P1). Idempotent — re-runs are no-ops.\n // On any failure, provisionManagedNode either falls back to a system Node\n // >=22 (returns { source: 'system' }) or throws — we surface the throw as an\n // {ok:false} envelope rather than letting it kill the CLI.\n let runtime: ProvisionedNode;\n try {\n runtime = await provisionManagedNode({\n ...(opts.provision ?? {}),\n env: opts.env,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return { ok: false, version: null, error: `managed-Node provision failed: ${reason}` };\n }\n if (runtime.source === 'system') {\n warn(\n `managed-Node download failed; using system Node ${runtime.version} at ${runtime.nodeBin}.`,\n );\n }\n\n const { nodeBin, npmBin } = runtime;\n\n // Install @noir-ai/cli into the isolated prefix using the provisioned npm.\n const { code, stderr } = await runManagerCmd(\n [npmBin, 'install', '-g', `@noir-ai/cli@${spec}`, `--prefix=${cliDir}`],\n { env: opts.env, timeoutMs: 120_000 },\n );\n if (code !== 0) {\n return { ok: false, version: null, error: `npm install failed: ${stderr.slice(0, 300)}` };\n }\n\n // Shim: ~/.noir/bin/noir -> provisioned node + isolated prefix.\n const shim = join(binDir, 'noir');\n const shimBody = `#!/usr/bin/env bash\\n\"${nodeBin}\" \"${join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js')}\" \"$@\"\\n`;\n atomicWriteFile(shim, shimBody);\n chmodSync(shim, 0o755); // must be executable — atomicWriteFile sets 0o644 by default\n // (POSIX shim; Windows uses a .cmd wrapper -- install.sh/install.ps1, P3.)\n\n // Resolve installed version via the provisioned node.\n const ver = await runManagerCmd(\n [\n nodeBin,\n join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js'),\n '--version',\n ],\n { env: opts.env, timeoutMs: 30_000 },\n );\n const version = ver.code === 0 ? ver.stdout.trim() : null;\n writeInstallRecord({\n method: 'native',\n version: version ?? '0.0.0',\n channel: opts.channel ?? 'latest',\n installedAt: new Date().toISOString(),\n // Reflect which runtime backs this install: the pinned managed version\n // (e.g. '22.23.2') for managed, or 'system-<v>' for the fallback so doctor\n // can distinguish a fallback install from a real managed one.\n managedRuntimeVersion:\n runtime.source === 'managed' ? MANAGED_NODE_VERSION : `system-${runtime.version}`,\n });\n return { ok: true, version, runtimeSource: runtime.source };\n}\n\nexport async function install(opts: InstallOptions = {}): Promise<void> {\n // --dismiss: append the current CLI version to install.json's\n // `dismissedVersions` so the migration banner stops showing for this\n // version. Idempotent (no duplicate entries). Typically combined with\n // `--list`; works standalone too. Requires an existing install record —\n // if there's none, there's nothing to dismiss (no banner was shown).\n if (opts.dismiss === true) {\n const rec = readInstallRecord();\n if (!rec) {\n info('No install record found; nothing to dismiss.');\n return;\n }\n const dismissed = new Set(rec.dismissedVersions ?? []);\n if (!dismissed.has(NOIR_VERSION)) dismissed.add(NOIR_VERSION);\n writeInstallRecord({ ...rec, dismissedVersions: [...dismissed] });\n success(`Migration banner dismissed for ${NOIR_VERSION}.`);\n if (opts.list !== true) return; // --dismiss alone is done; --list continues below.\n }\n\n if (opts.list === true) {\n const detected = await detectInstallMethods(process.env);\n const jsonOutput = { ok: true, data: { detected } };\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify(jsonOutput)}\\n`);\n return;\n }\n info('Detected installs:');\n for (const d of detected) info(` ${d.method} (${d.version ?? 'unknown'})`);\n return;\n }\n\n const currentMethod = detectActiveMethod();\n const detected = await detectInstallMethods(process.env);\n const installedRecord = readInstallRecord();\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 = '1.6.0';\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const plan = buildMigrationPlan({\n detected,\n currentMethod,\n targetSpec: opts.spec ?? 'latest',\n installedVersion: installedRecord?.version ?? null,\n minVersion,\n });\n\n // I2 — refuse to install a version below the configured minVersion floor\n // (same warn/refuse pattern as the installed-version downgrade guard).\n if (plan.belowMinVersion) {\n warn(`Target ${plan.nativeVersion} is below the minVersion floor (${minVersion}).`);\n if (opts.noInput === true || opts.input === false) {\n fail(\n EXIT.USAGE,\n `refusing install of ${plan.nativeVersion} (below minVersion ${minVersion})`,\n opts,\n );\n }\n }\n\n if (plan.isDowngrade) {\n warn(`Target ${plan.nativeVersion} is OLDER than installed ${plan.installedVersion}.`);\n // (Interactive confirm is skipped under --no-input; we hard-stop to be safe.)\n if (opts.noInput === true || opts.input === false) {\n fail(\n EXIT.USAGE,\n `refusing downgrade to ${plan.nativeVersion} (installed ${plan.installedVersion})`,\n opts,\n );\n }\n // @clack confirm gate (lazy import) -- same pattern as home.ts.\n }\n\n const installSpin = spinner('Installing Noir via native installer...', opts).start();\n const result = await installManagedNode({\n channel: plan.nativeVersion === 'beta' ? 'beta' : undefined,\n version: plan.nativeVersion.startsWith('v')\n ? plan.nativeVersion\n : plan.nativeVersion === 'latest' || plan.nativeVersion === 'beta'\n ? undefined\n : plan.nativeVersion,\n env: process.env,\n });\n if (!result.ok) {\n installSpin.fail('Native install failed');\n fail(EXIT.ERROR, result.error ?? 'native install failed', opts);\n }\n installSpin.succeed(`Installed noir ${result.version}`);\n\n info(`Installed native: ${result.version}.`);\n if (plan.prevUninstallCmd && opts.uninstallPrev !== true) {\n warn(`To finish the migration, uninstall the previous install: ${plan.prevUninstallCmd}`);\n warn(\n ' Or re-run with --uninstall-prev to do it now. Rollback: reinstall via the previous manager.',\n );\n } else if (plan.prevUninstallCmd && opts.uninstallPrev === true) {\n const { code } = await runManagerCmd(plan.prevUninstallCmd.split(' '), { env: process.env });\n if (code === 0) success('Previous install removed.');\n else warn('Previous install NOT removed (non-zero exit). You can remove it manually.');\n }\n // Claude Code #41806/#27910 mitigation: the old npm bin may still resolve\n // first in $PATH after migration (the shell hashes command paths, and a\n // stale global install can shadow ~/.noir/bin). Hint the user to refresh.\n info('If `noir` still points at the old install, run: hash -r && which -a noir');\n success(`noir ${result.version} installed via native. Run \\`noir doctor\\` to verify.`);\n}\n"],"mappings":"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","names":["detected"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import {\n detectActiveMethod,\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 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 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/install.ts"],"sourcesContent":["import { chmodSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n atomicWriteFile,\n type DetectResult,\n detectActiveMethod,\n detectInstallMethods,\n loadProjectInfo,\n MANAGED_NODE_VERSION,\n NOIR_VERSION,\n noirHome,\n type ProvisionedNode,\n type ProvisionOptions,\n provisionManagedNode,\n readInstallRecord,\n runManagerCmd,\n writeInstallRecord,\n} from '@noir-ai/core';\nimport { type CliOptions, EXIT, fail, info, spinner, success, warn } from '../output.js';\n\nexport interface InstallOptions extends CliOptions {\n list?: boolean;\n uninstallPrev?: boolean;\n spec?: string;\n /** Dismiss the migration banner for the current CLI version (persists in\n * install.json's `dismissedVersions`). Typically passed with `--list`. */\n dismiss?: boolean;\n}\n\nexport interface MigrationPlan {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: string | null;\n shouldMigrate: boolean;\n isDowngrade: boolean;\n /**\n * I2 — `update.minVersion` floor (config.ts:238). True when the resolved\n * target is a concrete version below the configured floor. Channel targets\n * (`latest`/`beta`) resolve to the newest at install time and never trip it.\n * Enforced in {@link install} with the same warn/refuse pattern as the\n * downgrade guard.\n */\n belowMinVersion: boolean;\n nativeVersion: string;\n prevUninstallCmd: string | null;\n autoUninstall: boolean; // always false unless --uninstall-prev\n}\n\n/** Per-segment numeric semver comparison. Returns true when `a < b`. */\nfunction semverLt(a: string, b: string): boolean {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\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/** Pure: build the migration plan from detection + target. No I/O. */\nexport function buildMigrationPlan(opts: {\n detected: DetectResult[];\n currentMethod: string;\n targetSpec: string;\n installedVersion: 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}): MigrationPlan {\n const detected = opts.detected;\n const shouldMigrate = detected.length > 0 && opts.currentMethod !== 'native';\n const nativeVersion = opts.targetSpec;\n let isDowngrade = false;\n if (opts.installedVersion && nativeVersion !== 'latest' && nativeVersion !== 'beta') {\n isDowngrade = semverLt(nativeVersion, opts.installedVersion);\n }\n // I2 — minVersion floor: only a concrete version below the floor trips it;\n // 'latest'/'beta' resolve to the newest at install time (always >= floor).\n const floor = opts.minVersion ?? '1.6.0';\n const belowMinVersion =\n nativeVersion !== 'latest' && nativeVersion !== 'beta' && semverLt(nativeVersion, floor);\n return {\n detected,\n currentMethod: opts.currentMethod,\n targetSpec: opts.targetSpec,\n installedVersion: opts.installedVersion,\n shouldMigrate,\n isDowngrade,\n belowMinVersion,\n nativeVersion,\n prevUninstallCmd: detected[0]?.uninstallCmd ?? null,\n autoUninstall: false,\n };\n}\n\nexport interface InstallManagedNodeOptions {\n channel?: string;\n version?: string;\n env: NodeJS.ProcessEnv;\n /**\n * Forwarded into {@link provisionManagedNode} (P1). Carries the offline mock\n * seams (`fetch`, `exec`, `target`, `signal`) so the install pipeline is\n * unit-testable without network — same pattern as `provisionManagedNode`'s\n * own suite in `packages/core/test/node-provision.test.ts`.\n */\n provision?: Pick<ProvisionOptions, 'fetch' | 'exec' | 'target' | 'signal'>;\n}\n\n/** Normalized record of which runtime backed an install (P2). */\nexport interface InstallManagedNodeResult {\n ok: boolean;\n version: string | null;\n /** What runtime backed this install: `'managed'` (downloaded into ~/.noir)\n * or `'system'` (used the system-Node fallback). */\n runtimeSource?: ProvisionedNode['source'];\n error?: string;\n}\n\n/**\n * Provision the managed Node runtime (P1) and install `@noir-ai/cli` into an\n * isolated `~/.noir/cli` prefix, shimmed from `~/.noir/bin/noir`.\n *\n * Replaces the prior \"not provisioned\" fail branch: instead of requiring the\n * user to pre-run `install.sh`/`install.ps1`, this now delegates to\n * {@link provisionManagedNode} (download + verify + extract, fail-closed), then\n * installs the CLI with the provisioned npm. On a system-Node fallback the\n * fallback node/npm paths are used and a warning is emitted; the install\n * record's `managedRuntimeVersion` reflects which path was taken so `noir\n * doctor` can report it accurately.\n */\nexport async function installManagedNode(\n opts: InstallManagedNodeOptions,\n): Promise<InstallManagedNodeResult> {\n const spec = opts.version ?? opts.channel ?? 'latest';\n const home = noirHome();\n const cliDir = join(home, 'cli');\n const binDir = join(home, 'bin');\n mkdirSync(cliDir, { recursive: true });\n mkdirSync(binDir, { recursive: true });\n\n // Provision the managed Node runtime (P1). Idempotent — re-runs are no-ops.\n // On any failure, provisionManagedNode either falls back to a system Node\n // >=22 (returns { source: 'system' }) or throws — we surface the throw as an\n // {ok:false} envelope rather than letting it kill the CLI.\n let runtime: ProvisionedNode;\n try {\n runtime = await provisionManagedNode({\n ...(opts.provision ?? {}),\n env: opts.env,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return { ok: false, version: null, error: `managed-Node provision failed: ${reason}` };\n }\n if (runtime.source === 'system') {\n warn(\n `managed-Node download failed; using system Node ${runtime.version} at ${runtime.nodeBin}.`,\n );\n }\n\n const { nodeBin, npmBin } = runtime;\n\n // Install @noir-ai/cli into the isolated prefix using the provisioned npm.\n const { code, stderr } = await runManagerCmd(\n [npmBin, 'install', '-g', `@noir-ai/cli@${spec}`, `--prefix=${cliDir}`],\n { env: opts.env, timeoutMs: 120_000 },\n );\n if (code !== 0) {\n return { ok: false, version: null, error: `npm install failed: ${stderr.slice(0, 300)}` };\n }\n\n // Shim: ~/.noir/bin/noir -> provisioned node + isolated prefix.\n const shim = join(binDir, 'noir');\n const shimBody = `#!/usr/bin/env bash\\n\"${nodeBin}\" \"${join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js')}\" \"$@\"\\n`;\n atomicWriteFile(shim, shimBody);\n chmodSync(shim, 0o755); // must be executable — atomicWriteFile sets 0o644 by default\n // (POSIX shim; Windows uses a .cmd wrapper -- install.sh/install.ps1, P3.)\n\n // Resolve installed version via the provisioned node.\n const ver = await runManagerCmd(\n [\n nodeBin,\n join(cliDir, 'lib', 'node_modules', '@noir-ai', 'cli', 'dist', 'bin.js'),\n '--version',\n ],\n { env: opts.env, timeoutMs: 30_000 },\n );\n const version = ver.code === 0 ? ver.stdout.trim() : null;\n writeInstallRecord({\n method: 'native',\n version: version ?? '0.0.0',\n channel: opts.channel ?? 'latest',\n installedAt: new Date().toISOString(),\n // Reflect which runtime backs this install: the pinned managed version\n // (e.g. '22.23.2') for managed, or 'system-<v>' for the fallback so doctor\n // can distinguish a fallback install from a real managed one.\n managedRuntimeVersion:\n runtime.source === 'managed' ? MANAGED_NODE_VERSION : `system-${runtime.version}`,\n });\n return { ok: true, version, runtimeSource: runtime.source };\n}\n\nexport async function install(opts: InstallOptions = {}): Promise<void> {\n // --dismiss: append the current CLI version to install.json's\n // `dismissedVersions` so the migration banner stops showing for this\n // version. Idempotent (no duplicate entries). Typically combined with\n // `--list`; works standalone too. Requires an existing install record —\n // if there's none, there's nothing to dismiss (no banner was shown).\n if (opts.dismiss === true) {\n const rec = readInstallRecord();\n if (!rec) {\n info('No install record found; nothing to dismiss.');\n return;\n }\n const dismissed = new Set(rec.dismissedVersions ?? []);\n if (!dismissed.has(NOIR_VERSION)) dismissed.add(NOIR_VERSION);\n writeInstallRecord({ ...rec, dismissedVersions: [...dismissed] });\n success(`Migration banner dismissed for ${NOIR_VERSION}.`);\n if (opts.list !== true) return; // --dismiss alone is done; --list continues below.\n }\n\n if (opts.list === true) {\n const detected = await detectInstallMethods(process.env);\n const jsonOutput = { ok: true, data: { detected } };\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify(jsonOutput)}\\n`);\n return;\n }\n info('Detected installs:');\n for (const d of detected) info(` ${d.method} (${d.version ?? 'unknown'})`);\n return;\n }\n\n const currentMethod = detectActiveMethod();\n const detected = await detectInstallMethods(process.env);\n const installedRecord = readInstallRecord();\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 = '1.6.0';\n try {\n minVersion = loadProjectInfo(process.cwd()).config.update.minVersion;\n } catch {\n // Not initialized yet — the config default applies.\n }\n const plan = buildMigrationPlan({\n detected,\n currentMethod,\n targetSpec: opts.spec ?? 'latest',\n installedVersion: installedRecord?.version ?? null,\n minVersion,\n });\n\n // I2 — refuse to install a version below the configured minVersion floor\n // (same warn/refuse pattern as the installed-version downgrade guard).\n if (plan.belowMinVersion) {\n warn(`Target ${plan.nativeVersion} is below the minVersion floor (${minVersion}).`);\n if (opts.noInput === true || opts.input === false) {\n fail(\n EXIT.USAGE,\n `refusing install of ${plan.nativeVersion} (below minVersion ${minVersion})`,\n opts,\n );\n }\n }\n\n if (plan.isDowngrade) {\n warn(`Target ${plan.nativeVersion} is OLDER than installed ${plan.installedVersion}.`);\n // (Interactive confirm is skipped under --no-input; we hard-stop to be safe.)\n if (opts.noInput === true || opts.input === false) {\n fail(\n EXIT.USAGE,\n `refusing downgrade to ${plan.nativeVersion} (installed ${plan.installedVersion})`,\n opts,\n );\n }\n // @clack confirm gate (lazy import) -- same pattern as home.ts.\n }\n\n const installSpin = spinner('Installing Noir via native installer...', opts).start();\n const result = await installManagedNode({\n channel: plan.nativeVersion === 'beta' ? 'beta' : undefined,\n version: plan.nativeVersion.startsWith('v')\n ? plan.nativeVersion\n : plan.nativeVersion === 'latest' || plan.nativeVersion === 'beta'\n ? undefined\n : plan.nativeVersion,\n env: process.env,\n });\n if (!result.ok) {\n installSpin.fail('Native install failed');\n fail(EXIT.ERROR, result.error ?? 'native install failed', opts);\n }\n installSpin.succeed(`Installed noir ${result.version}`);\n\n info(`Installed native: ${result.version}.`);\n if (plan.prevUninstallCmd && opts.uninstallPrev !== true) {\n warn(`To finish the migration, uninstall the previous install: ${plan.prevUninstallCmd}`);\n warn(\n ' Or re-run with --uninstall-prev to do it now. Rollback: reinstall via the previous manager.',\n );\n } else if (plan.prevUninstallCmd && opts.uninstallPrev === true) {\n const { code } = await runManagerCmd(plan.prevUninstallCmd.split(' '), { env: process.env });\n if (code === 0) success('Previous install removed.');\n else warn('Previous install NOT removed (non-zero exit). You can remove it manually.');\n }\n // Claude Code #41806/#27910 mitigation: the old npm bin may still resolve\n // first in $PATH after migration (the shell hashes command paths, and a\n // stale global install can shadow ~/.noir/bin). Hint the user to refresh.\n info('If `noir` still points at the old install, run: hash -r && which -a noir');\n success(`noir ${result.version} installed via native. Run \\`noir doctor\\` to verify.`);\n}\n"],"mappings":"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","names":["detected"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/update.ts"],"sourcesContent":["import {\n detectActiveMethod,\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 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":";;;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AAIA,IAAM,wBAA0C;AAAA,EACrD,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,SAAS;AACX;AAsBO,SAAS,kBAAkB,MAWjB;AACf,QAAM,aAAa,KAAK,QAAQ,KAAK;AAOrC,QAAM,YACJ,KAAK,eAAe,QACpB,KAAK,kBAAkB,QACvB,SAAS,KAAK,aAAa,KAAK,cAAc;AAGhD,QAAM,QAAQ,KAAK,cAAc;AACjC,QAAM,kBAAkB,KAAK,eAAe,QAAQ,SAAS,KAAK,aAAa,KAAK;AACpF,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb;AAAA,IACA,gBAAgB,KAAK;AAAA,IACrB,aAAa,KAAK;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AACF;AAUA,SAAS,SAAS,GAAW,GAAoB;AAC/C,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC;AACjD,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC;AACjD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,IAAI,GAAG,CAAC,KAAK;AACnB,UAAM,IAAI,GAAG,CAAC,KAAK;AACnB,QAAI,MAAM,EAAG,QAAO,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;AAOA,SAAS,SAAS,GAAW,GAAoB;AAC/C,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC;AACjD,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC;AACjD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,IAAI,GAAG,CAAC,KAAK;AACnB,UAAM,IAAI,GAAG,CAAC,KAAK;AACnB,QAAI,MAAM,EAAG,QAAO,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;AAEA,eAAsB,OAAO,OAAsB,CAAC,GAAkB;AAOpE,MAAI,QAAQ,IAAI,yBAAyB,QAAW;AAClD;AAAA,MACE,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AACA;AAAA,EACF;AAEA,QAAM,SAAS,mBAAmB;AAClC,QAAM,MAAM,kBAAkB;AAC9B,QAAM,iBAAiB,KAAK,WAAW;AAEvC,MAAI,KAAK,UAAU,MAAM;AACvB,UAAM,YAAY,QAAQ,2BAA2B,IAAI,EAAE,MAAM;AACjE,UAAMA,UAAS,MAAM,mBAAmB,QAAQ;AAChD,QAAIA,SAAQ;AACV,gBAAU,QAAQ,WAAWA,OAAM,cAAc,kBAAkB,SAAS,GAAG;AAAA,IACjF,OAAO;AACL,gBAAU,KAAK,+BAA+B;AAAA,IAChD;AACA;AAAA,EACF;AAGA,QAAM,YAAY,QAAQ,2BAA2B,IAAI,EAAE,MAAM;AACjE,QAAM,SAAS,MAAM,mBAAmB,QAAQ;AAChD,MAAI,WAAW,MAAM;AACnB,cAAU,KAAK,+BAA+B;AAC9C,SAAK,iCAAiC,IAAI;AAC1C;AAAA,EACF;AACA,YAAU,QAAQ,WAAW,MAAM,EAAE;AAIrC,MAAI,aAAa,sBAAsB;AACvC,MAAI;AACF,iBAAa,gBAAgB,QAAQ,IAAI,CAAC,EAAE,OAAO,OAAO;AAAA,EAC5D,QAAQ;AAAA,EAER;AACA,QAAM,SAAS,kBAAkB;AAAA,IAC/B;AAAA,IACA,SAAS;AAAA,IACT,MAAM,KAAK;AAAA,IACX;AAAA,IACA,aAAa;AAAA,IACb;AAAA,EACF,CAAC;AAMD,MAAI,OAAO,iBAAiB;AAC1B,SAAK,gBAAgB,OAAO,WAAW,mCAAmC,UAAU,IAAI;AACxF;AAAA,MACE,KAAK;AAAA,MACL,sBAAsB,OAAO,WAAW,sBAAsB,UAAU;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,WAAW;AACrB,cAAU,QAAQ,QAAQ,cAAc,gBAAgB;AACxD,SAAK,QAAQ,cAAc,mBAAmB,IAAI;AAClD;AAAA,EACF;AAEA,MAAI,WAAW,UAAU;AACvB,UAAM,aAAa;AAAA,MACjB,eAAe,OAAO,WAAW;AAAA,MACjC;AAAA,IACF,EAAE,MAAM;AACR,UAAM,MAAM,MAAM,mBAAmB;AAAA,MACnC,SAAS,OAAO,eAAe,WAAW,SAAY,OAAO;AAAA,MAC7D,KAAK,QAAQ;AAAA,IACf,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,iBAAW,KAAK,eAAe;AAC/B,WAAK,KAAK,OAAO,IAAI,SAAS,iBAAiB,IAAI;AAAA,IACrD;AACA,eAAW,QAAQ,mBAAmB,IAAI,OAAO,EAAE;AACnD,YAAQ,cAAc,IAAI,OAAO,GAAG;AACpC;AAAA,EACF;AAGA,QAAM,MAAM,aAAa,QAAQ,OAAO,UAAU;AAClD,MAAI,CAAC;AACH,SAAK,KAAK,OAAO,wBAAwB,MAAM,sCAAsC,IAAI;AAC3F,QAAM,UAAU,QAAQ,gBAAgB,MAAM,OAAO,IAAI,EAAE,MAAM;AACjE,QAAM,EAAE,MAAM,OAAO,IAAI,MAAM,cAAc,KAAK,EAAE,KAAK,QAAQ,IAAI,CAAC;AACtE,MAAI,SAAS,GAAG;AACd,YAAQ,KAAK,eAAe;AAC5B,SAAK,KAAK,OAAO,kBAAkB,OAAO,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI;AAAA,EACjE;AACA,UAAQ,QAAQ,SAAS;AACzB,UAAQ,UAAU;AACpB;AAEA,SAAS,aAAa,QAAgB,MAA+B;AACnE,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,CAAC,OAAO,WAAW,MAAM,gBAAgB,IAAI,EAAE;AAAA,IACxD,KAAK;AACH,aAAO,CAAC,QAAQ,OAAO,MAAM,gBAAgB,IAAI,EAAE;AAAA,IACrD,KAAK;AACH,aAAO,CAAC,QAAQ,UAAU,OAAO,gBAAgB,IAAI,EAAE;AAAA,IACzD,KAAK;AACH,aAAO,CAAC,OAAO,OAAO,MAAM,gBAAgB,IAAI,EAAE;AAAA,IACpD,KAAK;AACH,aAAO,CAAC,QAAQ,WAAW,MAAM;AAAA,IACnC,KAAK;AACH,aAAO,CAAC,SAAS,UAAU,MAAM;AAAA,IACnC;AACE,aAAO;AAAA,EACX;AACF;AAGA,eAAsB,oBAAoB,MAIxB;AAChB,MAAI;AACF,UAAM,QAAQ,gBAAgB;AAC9B,QAAI,CAAC,qBAAqB,EAAE,KAAK,KAAK,KAAK,cAAc,KAAK,cAAc,MAAM,CAAC,EAAG;AACtF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,GAAI;AACvD,UAAM,SAAS,MAAM,mBAAmB,KAAK,aAAa,SAAS,WAAW,MAAM;AACpF,iBAAa,KAAK;AAClB,QAAI,UAAU,MAAM;AAClB,uBAAiB;AAAA,QACf,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,eAAe;AAAA,QACf,SAAS,KAAK,aAAa;AAAA,MAC7B,CAAC;AAAA,IACH;AAAA,EAEF,QAAQ;AAAA,EAER;AACF;","names":["latest"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|