From a8a4a842cdae54fb644d98b15c4b6181063bf12f Mon Sep 17 00:00:00 2001 From: Damian Tometzki <26849652+dtometzki@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:55:16 +0200 Subject: [PATCH 1/5] fix(cli): use npm view for update check instead of update-notifier (#7515) --- packages/cli/src/ui/utils/updateCheck.ts | 54 +++++++++++------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/cli/src/ui/utils/updateCheck.ts b/packages/cli/src/ui/utils/updateCheck.ts index a5e042dd51b..87400a4b7f4 100644 --- a/packages/cli/src/ui/utils/updateCheck.ts +++ b/packages/cli/src/ui/utils/updateCheck.ts @@ -5,7 +5,6 @@ */ import type { UpdateInfo } from 'update-notifier'; -import updateNotifier from 'update-notifier'; import semver from 'semver'; import { execFile } from 'node:child_process'; import { realpath } from 'node:fs/promises'; @@ -25,22 +24,22 @@ export const FETCH_TIMEOUT_MS = 5000; /** * Sentinel error thrown when `fetchInfo()` does not resolve within - * `FETCH_TIMEOUT_MS`. `update-notifier`'s `fetchInfo()` does not accept a - * timeout option, so slow / unreachable registries (corporate proxies, offline - * networks, DNS failures) would otherwise hang the check indefinitely or fall - * through to a stale configstore cache. Race the call against a bounded timer - * and surface a real error so `/update` can report "check failed" instead of - * silently returning "up to date". The `distTag` is carried on the message so - * an oncall reading logs can tell which registry endpoint stalled — the - * nightly path fires two concurrent fetches, and only one of them may be - * blocked (e.g. a corporate proxy that lets `nightly` through but not - * `latest`). Related: #6857. + * `FETCH_TIMEOUT_MS`. `npm view` is bounded by the `timeout` option passed to + * `execFile` (see `runGlobalNpm`), but we still race it here as a second, + * independent bound so a slow / unreachable registry (corporate proxy, + * offline network, DNS failure) can never hang the check indefinitely. Race + * the call against a bounded timer and surface a real error so `/update` can + * report "check failed" instead of silently returning "up to date". The + * `distTag` is carried on the message so an oncall reading logs can tell + * which registry endpoint stalled — the nightly path fires two concurrent + * fetches, and only one of them may be blocked (e.g. a corporate proxy that + * lets `nightly` through but not `latest`). Related: #6857. */ export class UpdateCheckTimeoutError extends Error { readonly distTag?: string; constructor(timeoutMs: number, distTag?: string) { const suffix = distTag ? ` for ${distTag}` : ''; - super(`update-notifier fetchInfo timed out after ${timeoutMs}ms${suffix}`); + super(`update check timed out after ${timeoutMs}ms${suffix}`); this.name = 'UpdateCheckTimeoutError'; this.distTag = distTag; } @@ -267,7 +266,6 @@ function getBestAvailableUpdate( } export async function checkForUpdatesDetailed( - detectGlobalNpm = isGlobalNpmInstallation, fetchGlobalNpm = fetchGlobalNpmUpdateInfo, ): Promise { let currentVersion: string | undefined; @@ -282,23 +280,17 @@ export async function checkForUpdatesDetailed( } const { name, version } = packageJson; - const isGlobalNpm = await detectGlobalNpm(); currentVersion = version; const isNightly = version.includes('nightly'); - const createNotifier = (distTag: 'latest' | 'nightly') => - isGlobalNpm - ? { - fetchInfo: () => fetchGlobalNpm(name, version, distTag), - } - : updateNotifier({ - pkg: { - name, - version, - }, - updateCheckInterval: 0, - shouldNotifyInNpmScript: true, - distTag, - }); + // Always resolve via `npm view` (see fetchGlobalNpmUpdateInfo), regardless + // of installation type. update-notifier's fetchInfo() requests the + // abbreviated metadata format (Accept: application/vnd.npm.install-v1+json), + // which registry.npmjs.org now answers with an empty HTTP 406 response, + // breaking the check for every non-global install. `npm view` doesn't send + // that header and is unaffected. Related: #7515. + const createNotifier = (distTag: 'latest' | 'nightly') => ({ + fetchInfo: () => fetchGlobalNpm(name, version, distTag), + }); if (isNightly) { const [nightlyUpdateInfo, latestUpdateInfo] = await Promise.all([ @@ -368,7 +360,9 @@ export async function checkForUpdatesDetailed( } } -export async function checkForUpdates(): Promise { - const result = await checkForUpdatesDetailed(); +export async function checkForUpdates( + fetchGlobalNpm = fetchGlobalNpmUpdateInfo, +): Promise { + const result = await checkForUpdatesDetailed(fetchGlobalNpm); return result.status === 'update' ? result.info : null; } From 57331d16de759cfff644341d1585986312e36310 Mon Sep 17 00:00:00 2001 From: Damian Tometzki <26849652+dtometzki@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:57:19 +0200 Subject: [PATCH 2/5] fix(cli): use npm view for update check instead of update-notifier (#7515) --- packages/cli/src/ui/utils/updateCheck.test.ts | 235 +++++++++--------- 1 file changed, 118 insertions(+), 117 deletions(-) diff --git a/packages/cli/src/ui/utils/updateCheck.test.ts b/packages/cli/src/ui/utils/updateCheck.test.ts index 3898dfd44cb..77b29df3108 100644 --- a/packages/cli/src/ui/utils/updateCheck.test.ts +++ b/packages/cli/src/ui/utils/updateCheck.test.ts @@ -21,11 +21,6 @@ vi.mock('../../utils/package.js', () => ({ getPackageJson, })); -const updateNotifier = vi.hoisted(() => vi.fn()); -vi.mock('update-notifier', () => ({ - default: updateNotifier, -})); - describe('checkForUpdates', () => { beforeEach(() => { vi.useFakeTimers(); @@ -46,15 +41,11 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi - .fn() - .mockResolvedValue({ current: '1.0.0', latest: '1.1.0' }), - }); - const result = await checkForUpdates(); + const fetchGlobalNpm = vi.fn(); + const result = await checkForUpdates(fetchGlobalNpm); expect(result).toBeNull(); expect(getPackageJson).not.toHaveBeenCalled(); - expect(updateNotifier).not.toHaveBeenCalled(); + expect(fetchGlobalNpm).not.toHaveBeenCalled(); }); it('should return null if package.json is missing', async () => { @@ -68,10 +59,13 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi.fn().mockResolvedValue(null), + const fetchGlobalNpm = vi.fn().mockResolvedValue({ + current: '1.0.0', + latest: '1.0.0', + type: 'latest', + name: 'test-package', }); - const result = await checkForUpdates(); + const result = await checkForUpdates(fetchGlobalNpm); expect(result).toBeNull(); }); @@ -80,15 +74,21 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi - .fn() - .mockResolvedValue({ current: '1.0.0', latest: '1.1.0' }), + const fetchGlobalNpm = vi.fn().mockResolvedValue({ + current: '1.0.0', + latest: '1.1.0', + type: 'latest', + name: 'test-package', }); - const result = await checkForUpdates(); + const result = await checkForUpdates(fetchGlobalNpm); expect(result?.message).toContain('1.0.0 → 1.1.0'); - expect(result?.update).toEqual({ current: '1.0.0', latest: '1.1.0' }); + expect(result?.update).toEqual({ + current: '1.0.0', + latest: '1.1.0', + type: 'latest', + name: 'test-package', + }); }); it('should return null if the latest version is the same as the current version', async () => { @@ -96,12 +96,13 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi - .fn() - .mockResolvedValue({ current: '1.0.0', latest: '1.0.0' }), + const fetchGlobalNpm = vi.fn().mockResolvedValue({ + current: '1.0.0', + latest: '1.0.0', + type: 'latest', + name: 'test-package', }); - const result = await checkForUpdates(); + const result = await checkForUpdates(fetchGlobalNpm); expect(result).toBeNull(); }); @@ -110,12 +111,13 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.1.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi - .fn() - .mockResolvedValue({ current: '1.1.0', latest: '1.0.0' }), + const fetchGlobalNpm = vi.fn().mockResolvedValue({ + current: '1.1.0', + latest: '1.0.0', + type: 'latest', + name: 'test-package', }); - const result = await checkForUpdates(); + const result = await checkForUpdates(fetchGlobalNpm); expect(result).toBeNull(); }); @@ -124,11 +126,9 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi.fn().mockRejectedValue(new Error('Timeout')), - }); + const fetchGlobalNpm = vi.fn().mockRejectedValue(new Error('Timeout')); - const result = await checkForUpdates(); + const result = await checkForUpdates(fetchGlobalNpm); expect(result).toBeNull(); }); @@ -139,7 +139,6 @@ describe('checkForUpdates', () => { expect(result).toEqual({ status: 'skipped', reason: 'development mode' }); expect(getPackageJson).not.toHaveBeenCalled(); - expect(updateNotifier).not.toHaveBeenCalled(); }); it('should return a detailed skipped result if package metadata is missing', async () => { @@ -158,11 +157,14 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi.fn().mockResolvedValue(null), + const fetchGlobalNpm = vi.fn().mockResolvedValue({ + current: '1.0.0', + latest: '1.0.0', + type: 'latest', + name: 'test-package', }); - const result = await checkForUpdatesDetailed(); + const result = await checkForUpdatesDetailed(fetchGlobalNpm); expect(result).toEqual({ status: 'up-to-date', currentVersion: '1.0.0' }); }); @@ -173,11 +175,9 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi.fn().mockRejectedValue(error), - }); + const fetchGlobalNpm = vi.fn().mockRejectedValue(error); - const result = await checkForUpdatesDetailed(); + const result = await checkForUpdatesDetailed(fetchGlobalNpm); expect(result).toEqual({ status: 'error', @@ -191,19 +191,25 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi - .fn() - .mockResolvedValue({ current: '1.0.0', latest: '1.1.0' }), + const fetchGlobalNpm = vi.fn().mockResolvedValue({ + current: '1.0.0', + latest: '1.1.0', + type: 'latest', + name: 'test-package', }); - const result = await checkForUpdatesDetailed(); + const result = await checkForUpdatesDetailed(fetchGlobalNpm); expect(result).toEqual({ status: 'update', info: { message: 'Qwen Code update available! 1.0.0 → 1.1.0', - update: { current: '1.0.0', latest: '1.1.0' }, + update: { + current: '1.0.0', + latest: '1.1.0', + type: 'latest', + name: 'test-package', + }, }, }); }); @@ -240,31 +246,34 @@ describe('checkForUpdates', () => { ); }); - it('selects the global npm registry for global npm installs', async () => { + it('always resolves the update check via npm view, regardless of installation type (#7515)', async () => { + // update-notifier's fetchInfo() requests the abbreviated npm metadata + // format, which registry.npmjs.org now rejects with an empty HTTP 406 + // for every install type, not just global ones. The check must always + // go through fetchGlobalNpm (npm view), never fall back to update-notifier. getPackageJson.mockResolvedValue({ name: '@qwen-code/qwen-code', version: '1.0.0', }); - const detectGlobalNpm = vi.fn().mockResolvedValue(true); const fetchGlobalNpm = vi.fn().mockResolvedValue({ current: '1.0.0', latest: '1.1.0', + type: 'latest', + name: '@qwen-code/qwen-code', }); await expect( - checkForUpdatesDetailed(detectGlobalNpm, fetchGlobalNpm), + checkForUpdatesDetailed(fetchGlobalNpm), ).resolves.toMatchObject({ status: 'update', info: { update: { current: '1.0.0', latest: '1.1.0' } }, }); - expect(detectGlobalNpm).toHaveBeenCalledOnce(); expect(fetchGlobalNpm).toHaveBeenCalledWith( '@qwen-code/qwen-code', '1.0.0', 'latest', ); - expect(updateNotifier).not.toHaveBeenCalled(); }); it('does not treat pnpm installs as global npm installs', async () => { @@ -464,21 +473,24 @@ describe('checkForUpdates', () => { ).resolves.toMatchObject({ current: '1.0.0', latest: '1.0.0' }); }); - it('should pass a non-optional package version to update-notifier', async () => { + it('should pass the exact package name and version to fetchGlobalNpm', async () => { getPackageJson.mockResolvedValue({ name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi.fn().mockResolvedValue(null), + const fetchGlobalNpm = vi.fn().mockResolvedValue({ + current: '1.0.0', + latest: '1.0.0', + type: 'latest', + name: 'test-package', }); - await checkForUpdatesDetailed(); + await checkForUpdatesDetailed(fetchGlobalNpm); - expect(updateNotifier).toHaveBeenCalledWith( - expect.objectContaining({ - pkg: { name: 'test-package', version: '1.0.0' }, - }), + expect(fetchGlobalNpm).toHaveBeenCalledWith( + 'test-package', + '1.0.0', + 'latest', ); }); @@ -495,27 +507,22 @@ describe('checkForUpdates', () => { version: '1.2.3-nightly.1', }); - const fetchInfoMock = vi.fn().mockImplementation(({ distTag }) => { - if (distTag === 'nightly') { - return Promise.resolve({ - latest: '1.2.3-nightly.2', - current: '1.2.3-nightly.1', - }); - } - if (distTag === 'latest') { - return Promise.resolve({ - latest: '1.2.3', - current: '1.2.3-nightly.1', - }); - } - return Promise.resolve(null); - }); - - updateNotifier.mockImplementation(({ pkg, distTag }) => ({ - fetchInfo: () => fetchInfoMock({ pkg, distTag }), - })); - - const result = await checkForUpdates(); + const fetchGlobalNpm = vi + .fn() + .mockImplementation( + async ( + name: string, + current: string, + distTag: 'latest' | 'nightly', + ) => ({ + latest: distTag === 'nightly' ? '1.2.3-nightly.2' : '1.2.3', + current, + type: 'latest' as const, + name, + }), + ); + + const result = await checkForUpdates(fetchGlobalNpm); expect(result?.message).toContain('1.2.3-nightly.1 → 1.2.3-nightly.2'); expect(result?.update.latest).toBe('1.2.3-nightly.2'); }); @@ -523,25 +530,17 @@ describe('checkForUpdates', () => { describe('fetchInfo timeout (#6857)', () => { it('returns a detailed error when fetchInfo does not resolve within FETCH_TIMEOUT_MS', async () => { - // update-notifier's fetchInfo() takes no timeout option, so an - // unreachable registry (proxy, offline, corporate mirror without - // scoped .npmrc auth) would hang the check. We race it against a - // bounded timer instead — this asserts the timer actually fires and - // surfaces a real error rather than silently reporting "up to date". + // npm view is bounded by its own execFile timeout (see runGlobalNpm), + // but we still race it here as a second, independent bound — this + // asserts the timer actually fires and surfaces a real error rather + // than silently reporting "up to date". getPackageJson.mockResolvedValue({ name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - // never resolves - fetchInfo: vi.fn().mockReturnValue(new Promise(() => {})), - }); + const fetchGlobalNpm = vi.fn().mockReturnValue(new Promise(() => {})); // never resolves - // Stub the global-npm probe: the real isGlobalNpmInstallation runs a - // real realpath() I/O before the timeout is armed, which races with the - // fake-timer advance below and makes this test hang non-deterministically - // on slow/loaded runners. - const resultPromise = checkForUpdatesDetailed(async () => false); + const resultPromise = checkForUpdatesDetailed(fetchGlobalNpm); await vi.advanceTimersByTimeAsync(FETCH_TIMEOUT_MS + 1); const result = await resultPromise; @@ -563,13 +562,14 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0', }); - updateNotifier.mockReturnValue({ - fetchInfo: vi - .fn() - .mockResolvedValue({ current: '1.0.0', latest: '1.1.0' }), + const fetchGlobalNpm = vi.fn().mockResolvedValue({ + current: '1.0.0', + latest: '1.1.0', + type: 'latest', + name: 'test-package', }); - const result = await checkForUpdatesDetailed(async () => false); + const result = await checkForUpdatesDetailed(fetchGlobalNpm); expect(result.status).toBe('update'); if (result.status === 'update') { @@ -588,17 +588,20 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0-nightly.1', }); - updateNotifier.mockImplementation(({ distTag }) => ({ - fetchInfo: () => - distTag === 'nightly' - ? new Promise(() => {}) // never resolves - : Promise.resolve({ - current: '1.0.0-nightly.1', - latest: '1.0.0', - }), - })); - - const resultPromise = checkForUpdatesDetailed(async () => false); + const fetchGlobalNpm = vi + .fn() + .mockImplementation( + async ( + name: string, + current: string, + distTag: 'latest' | 'nightly', + ) => + distTag === 'nightly' + ? new Promise(() => {}) // never resolves + : { current, latest: '1.0.0', type: 'latest' as const, name }, + ); + + const resultPromise = checkForUpdatesDetailed(fetchGlobalNpm); await vi.advanceTimersByTimeAsync(FETCH_TIMEOUT_MS + 1); const result = await resultPromise; @@ -619,11 +622,9 @@ describe('checkForUpdates', () => { name: 'test-package', version: '1.0.0-nightly.1', }); - updateNotifier.mockImplementation(() => ({ - fetchInfo: () => new Promise(() => {}), - })); + const fetchGlobalNpm = vi.fn().mockReturnValue(new Promise(() => {})); - const resultPromise = checkForUpdatesDetailed(async () => false); + const resultPromise = checkForUpdatesDetailed(fetchGlobalNpm); await vi.advanceTimersByTimeAsync(FETCH_TIMEOUT_MS + 1); const result = await resultPromise; From 9fd141663f9c70f487064633488e444352eba1c5 Mon Sep 17 00:00:00 2001 From: Damian Tometzki <26849652+dtometzki@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:11:58 +0200 Subject: [PATCH 3/5] fix(cli): accept array-wrapped npm view output in update check (#7515) npm 11+ prints `npm view dist-tags. --json` as ["0.20.1"] instead of "0.20.1", so the strict string check re-broke the update check with "Invalid npm latest version response". Accept both shapes. Co-Authored-By: Claude Fable 5 --- packages/cli/src/ui/utils/updateCheck.test.ts | 41 +++++++++++++++++++ packages/cli/src/ui/utils/updateCheck.ts | 14 ++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/ui/utils/updateCheck.test.ts b/packages/cli/src/ui/utils/updateCheck.test.ts index 77b29df3108..51544ad972f 100644 --- a/packages/cli/src/ui/utils/updateCheck.test.ts +++ b/packages/cli/src/ui/utils/updateCheck.test.ts @@ -473,6 +473,47 @@ describe('checkForUpdates', () => { ).resolves.toMatchObject({ current: '1.0.0', latest: '1.0.0' }); }); + it('accepts array-wrapped dist-tag output from npm 11+ (#7515)', async () => { + // npm 11+ prints `npm view dist-tags. --json` as ["0.20.1"] + // instead of "0.20.1"; rejecting it re-broke the update check with + // "Invalid npm latest version response". + const run = vi + .fn() + .mockResolvedValue({ stdout: '[\n"1.1.0"\n]\n', stderr: '' }); + + await expect( + fetchGlobalNpmUpdateInfo( + '@qwen-code/qwen-code', + '1.0.0', + 'latest', + run as unknown as NonNullable< + Parameters[3] + >, + ), + ).resolves.toMatchObject({ current: '1.0.0', latest: '1.1.0' }); + }); + + it.each([ + ['a multi-element array', '["1.1.0","1.2.0"]'], + ['a non-string value', '42'], + ['an array of non-strings', '[42]'], + ])('rejects %s as an invalid dist-tag response', async (_desc, stdout) => { + const run = vi + .fn() + .mockResolvedValue({ stdout: `${stdout}\n`, stderr: '' }); + + await expect( + fetchGlobalNpmUpdateInfo( + '@qwen-code/qwen-code', + '1.0.0', + 'latest', + run as unknown as NonNullable< + Parameters[3] + >, + ), + ).rejects.toThrow('Invalid npm latest version response'); + }); + it('should pass the exact package name and version to fetchGlobalNpm', async () => { getPackageJson.mockResolvedValue({ name: 'test-package', diff --git a/packages/cli/src/ui/utils/updateCheck.ts b/packages/cli/src/ui/utils/updateCheck.ts index 87400a4b7f4..d059ef2a430 100644 --- a/packages/cli/src/ui/utils/updateCheck.ts +++ b/packages/cli/src/ui/utils/updateCheck.ts @@ -219,8 +219,18 @@ export async function fetchGlobalNpmUpdateInfo( name: packageName, }; } - const latest: unknown = JSON.parse(output); - if (typeof latest !== 'string') { + // npm ≤10 prints the field as a bare JSON string ("0.20.1"); npm 11+ wraps + // single `view` field results in an array (["0.20.1"]). Accept both. + const parsed: unknown = JSON.parse(output); + const latest = + typeof parsed === 'string' + ? parsed + : Array.isArray(parsed) && + parsed.length === 1 && + typeof parsed[0] === 'string' + ? parsed[0] + : undefined; + if (latest === undefined) { throw new Error(`Invalid npm ${distTag} version response`); } return { From 2e2c11296b0ad438bfeefbe176267ed344d09941 Mon Sep 17 00:00:00 2001 From: Damian Tometzki <26849652+dtometzki@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:46:19 +0200 Subject: [PATCH 4/5] chore(cli): drop update-notifier dependency and dead install-detection code (#7515) Version checking now goes through npm view for every install type, so: - replace the update-notifier UpdateInfo type import with a local interface and remove update-notifier / @types/update-notifier from dependencies - remove isGlobalNpmInstallation and looksLikeNpmPackagePath, which no longer have any production callers, along with their tests Co-Authored-By: Claude Fable 5 --- package-lock.json | 620 +----------------- packages/cli/package.json | 2 - packages/cli/src/ui/utils/updateCheck.test.ts | 138 ---- packages/cli/src/ui/utils/updateCheck.ts | 60 +- 4 files changed, 26 insertions(+), 794 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87b9ae88235..da1ff9840a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4353,47 +4353,6 @@ "node": ">=18" } }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "license": "MIT", - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "license": "ISC" - }, - "node_modules/@pnpm/npm-conf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", - "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -7786,12 +7745,6 @@ "commander": "*" } }, - "node_modules/@types/configstore": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/configstore/-/configstore-6.0.2.tgz", - "integrity": "sha512-OS//b51j9uyR3zvwD04Kfs5kHpve2qalQ18JhY/ho3voGYUTPLEG90/ocfKPI48hyHH8T04f7KEEbK6Ue60oZQ==", - "license": "MIT" - }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -8578,16 +8531,6 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/@types/update-notifier": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/@types/update-notifier/-/update-notifier-6.0.8.tgz", - "integrity": "sha512-IlDFnfSVfYQD+cKIg63DEXn3RFmd7W1iYtKQsJodcHK9R1yr8aKbKaPKfBxzPpcHCq2DU8zUq4PIPmy19Thjfg==", - "license": "MIT", - "dependencies": { - "@types/configstore": "*", - "boxen": "^7.1.1" - } - }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -9751,47 +9694,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -10315,15 +10217,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/atomically": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.0.3.tgz", - "integrity": "sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==", - "dependencies": { - "stubborn-fs": "^1.2.5", - "when-exit": "^2.1.1" - } - }, "node_modules/auto-bind": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", @@ -10594,40 +10487,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -10877,18 +10736,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -11190,18 +11037,6 @@ "url": "https://polar.sh/cva" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -11707,40 +11542,6 @@ "dev": true, "license": "MIT" }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/configstore": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.0.0.tgz", - "integrity": "sha512-yk7/5PN5im4qwz0WFZW3PXnzHgPu9mX29Y8uZ3aefe2lBPC1FYttWZRcaW9fKkT0pBCJyuQ2HfbmPVaODi9jcQ==", - "license": "BSD-2-Clause", - "dependencies": { - "atomically": "^2.0.3", - "dot-prop": "^9.0.0", - "graceful-fs": "^4.2.11", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" - } - }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -12770,7 +12571,9 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=4.0.0" } @@ -13091,33 +12894,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/dot-prop": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^4.18.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dotenv": { "version": "17.4.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", @@ -13618,18 +13394,6 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -15402,21 +15166,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", - "license": "MIT", - "dependencies": { - "ini": "4.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globals": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", @@ -16232,15 +15981,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/ink": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/ink/-/ink-7.0.3.tgz", @@ -16928,21 +16668,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-in-ci": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", - "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", - "license": "MIT", - "bin": { - "is-in-ci": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-in-ssh": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", @@ -16975,22 +16700,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-installed-globally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", - "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", - "license": "MIT", - "dependencies": { - "global-directory": "^4.0.1", - "is-path-inside": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -17037,18 +16746,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-npm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", - "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -17089,18 +16786,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -17842,33 +17527,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ky": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.8.1.tgz", - "integrity": "sha512-7Bp3TpsE+L+TARSnnDpk3xg8Idi8RwSLdj6CMbNWoOARIrGrbuLGusV0dYwbZOm4bB3jHNxSw8Wk/ByDqJEnDw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, - "node_modules/latest-version": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", - "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", - "license": "MIT", - "dependencies": { - "package-json": "^10.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/layout-base": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", @@ -17995,7 +17653,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18017,7 +17674,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18039,7 +17695,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18061,7 +17716,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18083,7 +17737,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18105,7 +17758,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18127,7 +17779,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18149,7 +17800,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18171,7 +17821,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18193,7 +17842,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -18215,7 +17863,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19956,6 +19603,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -21327,24 +20975,6 @@ "node": ">=6" } }, - "node_modules/package-json": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", - "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", - "license": "MIT", - "dependencies": { - "ky": "^1.2.0", - "registry-auth-token": "^5.0.2", - "registry-url": "^6.0.1", - "semver": "^7.6.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -22408,12 +22038,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "license": "ISC" - }, "node_modules/protobufjs": { "version": "7.6.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", @@ -22499,21 +22123,6 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", - "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", - "license": "MIT", - "dependencies": { - "escape-goat": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/qrcode-terminal": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", @@ -22707,7 +22316,9 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -22748,13 +22359,17 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "dev": true, + "license": "ISC", + "optional": true }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -23264,33 +22879,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/registry-auth-token": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", - "license": "MIT", - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "license": "MIT", - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/rehype-katex": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", @@ -25119,11 +24707,6 @@ "boundary": "^2.0.0" } }, - "node_modules/stubborn-fs": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz", - "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==" - }, "node_modules/style-mod": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", @@ -26148,18 +25731,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", @@ -26605,126 +26176,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", - "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==", - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^8.0.1", - "chalk": "^5.3.0", - "configstore": "^7.0.0", - "is-in-ci": "^1.0.0", - "is-installed-globally": "^1.0.0", - "is-npm": "^6.0.0", - "latest-version": "^9.0.0", - "pupa": "^3.1.0", - "semver": "^7.6.3", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "license": "MIT" - }, - "node_modules/update-notifier/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -27255,12 +26706,6 @@ "node": ">=18" } }, - "node_modules/when-exit": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.4.tgz", - "integrity": "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==", - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -27382,21 +26827,6 @@ "node": ">=8" } }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "license": "MIT", - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -27411,6 +26841,7 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", @@ -27478,6 +26909,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -27490,12 +26922,14 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -27552,18 +26986,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -28035,7 +27457,6 @@ "@qwen-code/qwen-code-core": "file:../core", "@qwen-code/sdk": "file:../sdk-typescript", "@qwen-code/web-templates": "file:../web-templates", - "@types/update-notifier": "^6.0.8", "ansi-regex": "^6.2.2", "chokidar": "^4.0.3", "command-exists": "^1.2.9", @@ -28064,7 +27485,6 @@ "strip-json-comments": "^3.1.1", "tar": "^7.5.19", "undici": "^7.28.0", - "update-notifier": "^7.3.1", "wrap-ansi": "^10.0.0", "ws": "^8.18.0", "yargs": "^17.7.2", @@ -31131,18 +30551,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/test-utils": { - "name": "@qwen-code/qwen-code-test-utils", - "version": "0.14.4", - "extraneous": true, - "license": "Apache-2.0", - "devDependencies": { - "typescript": "^5.3.3" - }, - "engines": { - "node": ">=20" - } - }, "packages/vscode-ide-companion": { "name": "qwen-code-vscode-ide-companion", "version": "0.20.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index f1a9022a62b..15ba7068973 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,7 +56,6 @@ "@qwen-code/qwen-code-core": "file:../core", "@qwen-code/sdk": "file:../sdk-typescript", "@qwen-code/web-templates": "file:../web-templates", - "@types/update-notifier": "^6.0.8", "ansi-regex": "^6.2.2", "chokidar": "^4.0.3", "command-exists": "^1.2.9", @@ -85,7 +84,6 @@ "strip-json-comments": "^3.1.1", "tar": "^7.5.19", "undici": "^7.28.0", - "update-notifier": "^7.3.1", "wrap-ansi": "^10.0.0", "ws": "^8.18.0", "yauzl": "^2.10.0", diff --git a/packages/cli/src/ui/utils/updateCheck.test.ts b/packages/cli/src/ui/utils/updateCheck.test.ts index 51544ad972f..04fa02cdd10 100644 --- a/packages/cli/src/ui/utils/updateCheck.test.ts +++ b/packages/cli/src/ui/utils/updateCheck.test.ts @@ -11,7 +11,6 @@ import { classifyUpdateCheckError, fetchGlobalNpmUpdateInfo, FETCH_TIMEOUT_MS, - isGlobalNpmInstallation, runGlobalNpm, UpdateCheckTimeoutError, } from './updateCheck.js'; @@ -27,7 +26,6 @@ describe('checkForUpdates', () => { vi.resetAllMocks(); // Clear DEV environment variable before each test delete process.env['DEV']; - delete process.env['QWEN_CODE_MANAGED_NPM_UPDATE']; }); afterEach(() => { @@ -276,64 +274,6 @@ describe('checkForUpdates', () => { ); }); - it('does not treat pnpm installs as global npm installs', async () => { - const run = vi.fn(); - - await expect( - isGlobalNpmInstallation( - '/home/user/.pnpm/@qwen-code+qwen-code/node_modules/@qwen-code/qwen-code/dist/index.js', - run as unknown as NonNullable< - Parameters[1] - >, - ), - ).resolves.toBe(false); - expect(run).not.toHaveBeenCalled(); - }); - - it('uses the global npm registry for managed background updates', async () => { - process.env['QWEN_CODE_MANAGED_NPM_UPDATE'] = 'true'; - const run = vi.fn(); - const canonicalize = vi.fn(); - - await expect( - isGlobalNpmInstallation( - '/home/user/.qwen/updates/npm/versions/2.0.0/dist/cli.js', - run as unknown as NonNullable< - Parameters[1] - >, - canonicalize as unknown as NonNullable< - Parameters[2] - >, - ), - ).resolves.toBe(true); - expect(run).not.toHaveBeenCalled(); - expect(canonicalize).not.toHaveBeenCalled(); - }); - - it('resolves a bin symlink before matching the npm package path', async () => { - const run = vi.fn().mockResolvedValue({ - stdout: '/usr/local/lib/node_modules\n', - stderr: '', - }); - const canonicalize = vi.fn(async (candidate: string) => - candidate === '/usr/local/bin/qwen' - ? '/usr/local/lib/node_modules/@qwen-code/qwen-code/dist/cli.js' - : '/usr/local/lib/node_modules', - ); - - await expect( - isGlobalNpmInstallation( - '/usr/local/bin/qwen', - run as unknown as NonNullable< - Parameters[1] - >, - canonicalize as unknown as NonNullable< - Parameters[2] - >, - ), - ).resolves.toBe(true); - }); - it('runs the Windows npm CLI through Node without a shell', async () => { const run = vi.fn().mockResolvedValue({ stdout: '"1.1.0"', stderr: '' }); const resolveNpmCliPath = vi @@ -365,84 +305,6 @@ describe('checkForUpdates', () => { ); }); - it('canonicalizes the global npm root before comparing paths', async () => { - const run = vi.fn().mockResolvedValue({ - stdout: '/linked/node_modules\n', - stderr: '', - }); - const canonicalize = vi.fn(async (candidate: string) => - candidate === '/linked/node_modules' - ? '/real/node_modules' - : '/real/node_modules/@qwen-code/qwen-code/cli.js', - ); - - await expect( - isGlobalNpmInstallation( - '/linked/node_modules/@qwen-code/qwen-code/cli.js', - run as unknown as NonNullable< - Parameters[1] - >, - canonicalize as unknown as NonNullable< - Parameters[2] - >, - ), - ).resolves.toBe(true); - expect(run).toHaveBeenCalledWith( - process.execPath, - [expect.stringMatching(/npm-cli\.js$/), 'root', '--global'], - expect.objectContaining({ timeout: FETCH_TIMEOUT_MS }), - ); - }); - - it('does not treat a missing global npm root as a global install', async () => { - const run = vi.fn().mockResolvedValue({ - stdout: '/missing/node_modules\n', - stderr: '', - }); - const canonicalize = vi.fn(async (candidate: string) => { - if (candidate === '/missing/node_modules') { - throw Object.assign(new Error('not found'), { code: 'ENOENT' }); - } - return '/local/node_modules/@qwen-code/qwen-code/cli.js'; - }); - - await expect( - isGlobalNpmInstallation( - '/local/node_modules/@qwen-code/qwen-code/cli.js', - run as unknown as NonNullable< - Parameters[1] - >, - canonicalize as unknown as NonNullable< - Parameters[2] - >, - ), - ).resolves.toBe(false); - }); - - it('does not treat local npm installs as global npm installs', async () => { - const run = vi.fn().mockResolvedValue({ - stdout: '/global/node_modules\n', - stderr: '', - }); - const canonicalize = vi.fn(async (candidate: string) => - candidate === '/global/node_modules' - ? '/global/node_modules' - : '/repo/node_modules/@qwen-code/qwen-code/cli.js', - ); - - await expect( - isGlobalNpmInstallation( - '/repo/node_modules/@qwen-code/qwen-code/cli.js', - run as unknown as NonNullable< - Parameters[1] - >, - canonicalize as unknown as NonNullable< - Parameters[2] - >, - ), - ).resolves.toBe(false); - }); - it('does not fall back when the global npm query fails', async () => { const run = vi.fn().mockRejectedValue(new Error('npm view failed')); diff --git a/packages/cli/src/ui/utils/updateCheck.ts b/packages/cli/src/ui/utils/updateCheck.ts index d059ef2a430..d77adb6e552 100644 --- a/packages/cli/src/ui/utils/updateCheck.ts +++ b/packages/cli/src/ui/utils/updateCheck.ts @@ -4,11 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { UpdateInfo } from 'update-notifier'; import semver from 'semver'; import { execFile } from 'node:child_process'; -import { realpath } from 'node:fs/promises'; -import path from 'node:path'; import { promisify } from 'node:util'; import { getPackageJson } from '../../utils/package.js'; import { getNpmCliPath } from '../../utils/installationInfo.js'; @@ -17,6 +14,18 @@ import { t } from '../../i18n/index.js'; const debugLogger = createDebugLogger('UPDATE_CHECK'); +/** + * Result of an update lookup. Mirrors the subset of update-notifier's + * UpdateInfo that the CLI consumes — kept local so version checking no longer + * depends on update-notifier at all (#7515). + */ +export interface UpdateInfo { + latest: string; + current: string; + type: string; + name: string; +} + // 5s matches comparable CLIs (e.g. Claude Code's autoUpdater uses // AbortSignal.timeout(5000)) and gives slow mirrors and corporate proxies a // realistic budget. Related: #7049. @@ -150,51 +159,6 @@ export async function runGlobalNpm( return String(stdout).trim(); } -function looksLikeNpmPackagePath(cliPath: string): boolean { - const normalized = cliPath.replace(/\\/g, '/'); - return ( - normalized.includes('/node_modules/@qwen-code/qwen-code/') && - !normalized.includes('/.pnpm/') - ); -} - -export async function isGlobalNpmInstallation( - cliPath = process.argv[1], - run: typeof execFileAsync = execFileAsync, - canonicalize: typeof realpath = realpath, -): Promise { - if (process.env['QWEN_CODE_MANAGED_NPM_UPDATE'] === 'true') return true; - if (!cliPath) return false; - // Canonicalize before matching. The CLI can be launched through its global - // bin symlink (e.g. `.../bin/qwen`), whose path carries no `node_modules` - // segment, and Node does not resolve `process.argv[1]` symlinks. Matching the - // raw path would silently skip the global-npm path here, unlike - // getInstallationInfo which realpath-resolves first. - let resolvedCliPath: string; - try { - resolvedCliPath = await canonicalize(cliPath); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false; - throw error; - } - if (!looksLikeNpmPackagePath(resolvedCliPath)) return false; - const unresolvedGlobalRoot = await runGlobalNpm(['root', '--global'], run); - let globalRoot: string; - try { - globalRoot = await canonicalize(unresolvedGlobalRoot); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false; - throw error; - } - const relative = path.relative(globalRoot, resolvedCliPath); - return ( - relative !== '' && - relative !== '..' && - !relative.startsWith(`..${path.sep}`) && - !path.isAbsolute(relative) - ); -} - export async function fetchGlobalNpmUpdateInfo( packageName: string, currentVersion: string, From 9471f7adf932df9be40959f6be119e6bf937a216 Mon Sep 17 00:00:00 2001 From: Damian Tometzki <26849652+dtometzki@users.noreply.github.com> Date: Thu, 23 Jul 2026 08:37:18 +0200 Subject: [PATCH 5/5] docs(cli): fix npm version in array-output comment (npm 12+, not 11+) --- packages/cli/src/ui/utils/updateCheck.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/ui/utils/updateCheck.ts b/packages/cli/src/ui/utils/updateCheck.ts index d77adb6e552..669a25201f2 100644 --- a/packages/cli/src/ui/utils/updateCheck.ts +++ b/packages/cli/src/ui/utils/updateCheck.ts @@ -183,7 +183,7 @@ export async function fetchGlobalNpmUpdateInfo( name: packageName, }; } - // npm ≤10 prints the field as a bare JSON string ("0.20.1"); npm 11+ wraps + // npm ≤11 prints the field as a bare JSON string ("0.20.1"); npm 12+ wraps // single `view` field results in an array (["0.20.1"]). Accept both. const parsed: unknown = JSON.parse(output); const latest =