From d9160c3975b61b100727c9545a4ca8d808ec4161 Mon Sep 17 00:00:00 2001 From: Mathias Bynens Date: Fri, 17 Apr 2026 09:55:28 +0200 Subject: [PATCH] fix: avoid showing update notification for local builds Previously, we would show the notification if the local version string was *different* from the latest version string published to npm. With this patch, we actually check if the npm-published version is newer and avoid showing the notification otherwise. --- package-lock.json | 9 ++++ package.json | 2 + src/third_party/index.ts | 1 + src/utils/check-for-updates.ts | 3 +- tests/check-for-updates.test.ts | 20 +++++++++ tests/third_party_notices.test.js.snapshot | 48 +++++++++++----------- 6 files changed, 58 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 68de61514..092035087 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@types/debug": "^4.1.12", "@types/filesystem": "^0.0.36", "@types/node": "^25.0.0", + "@types/semver": "^7.7.1", "@types/sinon": "^21.0.0", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.43.0", @@ -40,6 +41,7 @@ "rollup": "4.60.1", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-license": "^3.6.0", + "semver": "^7.7.4", "sinon": "^21.0.0", "tiktoken": "^1.0.22", "typescript": "^6.0.2", @@ -2194,6 +2196,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/shimmer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", diff --git a/package.json b/package.json index 3ea6bfc5d..cae696ec6 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@types/debug": "^4.1.12", "@types/filesystem": "^0.0.36", "@types/node": "^25.0.0", + "@types/semver": "^7.7.1", "@types/sinon": "^21.0.0", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.43.0", @@ -73,6 +74,7 @@ "rollup": "4.60.1", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-license": "^3.6.0", + "semver": "^7.7.4", "sinon": "^21.0.0", "tiktoken": "^1.0.22", "typescript": "^6.0.2", diff --git a/src/third_party/index.ts b/src/third_party/index.ts index 09b0d02ce..d3e00ee6c 100644 --- a/src/third_party/index.ts +++ b/src/third_party/index.ts @@ -16,6 +16,7 @@ export type {Flags, Result, RunnerResult, OutputMode}; export type {Options as YargsOptions} from 'yargs'; export {default as yargs} from 'yargs'; export {hideBin} from 'yargs/helpers'; +export {default as semver} from 'semver'; export {default as debug} from 'debug'; export type {Debugger} from 'debug'; export {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; diff --git a/src/utils/check-for-updates.ts b/src/utils/check-for-updates.ts index 36c5b13b8..c1e59e0bb 100644 --- a/src/utils/check-for-updates.ts +++ b/src/utils/check-for-updates.ts @@ -10,6 +10,7 @@ import os from 'node:os'; import path from 'node:path'; import process from 'node:process'; +import {semver} from '../third_party/index.js'; import {VERSION} from '../version.js'; /** @@ -46,7 +47,7 @@ export async function checkForUpdates(message: string) { // Ignore errors reading cache. } - if (cachedVersion && cachedVersion !== VERSION) { + if (cachedVersion && semver.lt(VERSION, cachedVersion)) { console.warn( `\nUpdate available: ${VERSION} -> ${cachedVersion}\n${message}\n`, ); diff --git a/tests/check-for-updates.test.ts b/tests/check-for-updates.test.ts index 82413c493..13a8fc039 100644 --- a/tests/check-for-updates.test.ts +++ b/tests/check-for-updates.test.ts @@ -73,6 +73,26 @@ describe('checkForUpdates', () => { assert.ok(spawnStub.notCalled); }); + it('does not notify if incoming version is older than current version', async () => { + sinon.stub(os, 'homedir').returns('/home/user'); + sinon.stub(fs, 'stat').resolves({mtimeMs: Date.now()} as unknown as Stats); + sinon.stub(fs, 'readFile').callsFake(async filePath => { + if (filePath.toString().includes('latest.json')) { + return JSON.stringify({ + version: '0.0.1', + }); + } + throw new Error(`File not found: ${filePath}`); + }); + const warnStub = sinon.stub(console, 'warn'); + const spawnStub = sinon.stub(child_process, 'spawn'); + + await checkForUpdates('Run `npm update` to update.'); + + assert.ok(warnStub.notCalled); + assert.ok(spawnStub.notCalled); + }); + it('does not spawn fetch process if cache is fresh', async () => { sinon.stub(os, 'homedir').returns('/home/user'); sinon.stub(fs, 'stat').resolves({mtimeMs: Date.now()} as unknown as Stats); diff --git a/tests/third_party_notices.test.js.snapshot b/tests/third_party_notices.test.js.snapshot index ea1ca84bf..bbc79a248 100644 --- a/tests/third_party_notices.test.js.snapshot +++ b/tests/third_party_notices.test.js.snapshot @@ -294,6 +294,30 @@ Permission to use, copy, modify, and/or distribute this software for any purpose THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-------------------- DEPENDENCY DIVIDER -------------------- + +Name: semver +URL: git+https://github.com/npm/node-semver.git +Version: +License: ISC + +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + -------------------- DEPENDENCY DIVIDER -------------------- Name: debug @@ -829,30 +853,6 @@ URL: https://github.com/puppeteer/puppeteer/tree/main/packages/browsers Version: License: Apache-2.0 --------------------- DEPENDENCY DIVIDER -------------------- - -Name: semver -URL: git+https://github.com/npm/node-semver.git -Version: -License: ISC - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - -------------------- DEPENDENCY DIVIDER -------------------- Name: proxy-agent