From 76fad85ee6fdedc7f7ffcdb1971e54073c7ad693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97SO?= <142557582+Linxiushen@users.noreply.github.com> Date: Wed, 5 Aug 2026 03:36:37 +0800 Subject: [PATCH 1/2] fix(updater): discard stale version hints --- bin/ocr.js | 10 +++++++++- package.json | 4 +++- scripts/update.js | 19 ++----------------- scripts/version.js | 33 +++++++++++++++++++++++++++++++++ scripts/version.test.js | 28 ++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 scripts/version.js create mode 100644 scripts/version.test.js diff --git a/bin/ocr.js b/bin/ocr.js index a1e66437..828eb4cb 100755 --- a/bin/ocr.js +++ b/bin/ocr.js @@ -7,6 +7,7 @@ const fs = require("fs"); const os = require("os"); const { resolveNativeBinary } = require("../scripts/platform"); +const { parseVersionOutput, shouldShowUpdateHint } = require("../scripts/version"); const resolved = resolveNativeBinary(); if (!resolved) { @@ -20,11 +21,18 @@ const binaryPath = resolved.path; const hintFile = path.join(os.homedir(), ".opencodereview", "update-available"); try { const hint = JSON.parse(fs.readFileSync(hintFile, "utf8")); - if (hint.version && hint.pkg) { + const versionResult = spawnSync(binaryPath, ["version"], { + encoding: "utf8", + timeout: 3000, + }); + const installedVersion = parseVersionOutput(versionResult.stdout); + if (hint.pkg && shouldShowUpdateHint(hint.version, installedVersion)) { console.error( `\x1b[33m[ocr] A new version (v${hint.version}) is available. Run to update:\x1b[0m\n` + `\x1b[33m npm i -g ${hint.pkg}@${hint.version}\x1b[0m\n` ); + } else { + fs.unlinkSync(hintFile); } } catch (_) {} diff --git a/package.json b/package.json index 0e568efb..5571e7e1 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,14 @@ "bin/ocr.js", "scripts/install.js", "scripts/update.js", + "scripts/version.js", "scripts/platform.js", "imgs/" ], "scripts": { "postinstall": "node scripts/install.js", - "test:github-actions": "node scripts/github-actions/post-review-comments.test.js && node scripts/github-actions/check-translation-sync.test.js" + "test:github-actions": "node scripts/github-actions/post-review-comments.test.js && node scripts/github-actions/check-translation-sync.test.js", + "test:update": "node scripts/version.test.js" }, "repository": { "type": "git", diff --git a/scripts/update.js b/scripts/update.js index ba6fc249..9851cc90 100644 --- a/scripts/update.js +++ b/scripts/update.js @@ -9,6 +9,7 @@ const { spawnSync } = require("child_process"); const { resolveNativeBinary } = require("./platform"); const { loadPackageJson } = require("./install.js"); +const { SEMVER_RE, parseVersionOutput, semverGt } = require("./version"); const stateDir = path.join(os.homedir(), ".opencodereview"); const tsFile = path.join(stateDir, "last-update-check"); @@ -62,8 +63,7 @@ function getInstalledVersion(binPath) { encoding: "utf8", timeout: 3000, }); - const match = (result.stdout || "").match(/v(\d+\.\d+(?:\.\d+)?)/); - return match ? match[1] : null; + return parseVersionOutput(result.stdout); } catch (_) { return null; } @@ -109,21 +109,6 @@ function fetchLatestVersion(pkg) { }); } -const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/; - -function semverGt(a, b) { - const pa = a.replace(/-.*$/, "").split(".").map(Number); - const pb = b.replace(/-.*$/, "").split(".").map(Number); - for (let i = 0; i < 3; i++) { - if ((pa[i] || 0) > (pb[i] || 0)) return true; - if ((pa[i] || 0) < (pb[i] || 0)) return false; - } - const aPre = a.includes("-"); - const bPre = b.includes("-"); - if (bPre && !aPre) return true; - return false; -} - function writeHint(latestVersion, pkgName) { try { fs.writeFileSync(hintFile, JSON.stringify({ version: latestVersion, pkg: pkgName })); diff --git a/scripts/version.js b/scripts/version.js new file mode 100644 index 00000000..a2411615 --- /dev/null +++ b/scripts/version.js @@ -0,0 +1,33 @@ +"use strict"; + +const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/; + +function parseVersionOutput(output) { + const match = String(output || "").match(/v(\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?)/); + return match ? match[1] : null; +} + +function semverGt(a, b) { + const pa = a.replace(/-.*$/, "").split(".").map(Number); + const pb = b.replace(/-.*$/, "").split(".").map(Number); + for (let i = 0; i < 3; i++) { + if ((pa[i] || 0) > (pb[i] || 0)) return true; + if ((pa[i] || 0) < (pb[i] || 0)) return false; + } + const aPre = a.includes("-"); + const bPre = b.includes("-"); + if (bPre && !aPre) return true; + return false; +} + +function shouldShowUpdateHint(hintVersion, installedVersion) { + if (!SEMVER_RE.test(hintVersion)) return false; + return !installedVersion || semverGt(hintVersion, installedVersion); +} + +module.exports = { + SEMVER_RE, + parseVersionOutput, + semverGt, + shouldShowUpdateHint, +}; diff --git a/scripts/version.test.js b/scripts/version.test.js new file mode 100644 index 00000000..40e857d4 --- /dev/null +++ b/scripts/version.test.js @@ -0,0 +1,28 @@ +"use strict"; + +const assert = require("assert"); + +const { + parseVersionOutput, + semverGt, + shouldShowUpdateHint, +} = require("./version"); + +assert.strictEqual( + parseVersionOutput("open-code-review v1.8.6 (1b193db35) darwin/arm64"), + "1.8.6" +); +assert.strictEqual(parseVersionOutput("version unavailable"), null); + +assert.strictEqual(semverGt("1.8.7", "1.8.6"), true); +assert.strictEqual(semverGt("1.8.6", "1.8.6"), false); +assert.strictEqual(semverGt("1.8.5", "1.8.6"), false); +assert.strictEqual(semverGt("1.8.6", "1.8.6-beta.1"), true); + +assert.strictEqual(shouldShowUpdateHint("1.8.7", "1.8.6"), true); +assert.strictEqual(shouldShowUpdateHint("1.8.6", "1.8.6"), false); +assert.strictEqual(shouldShowUpdateHint("1.8.5", "1.8.6"), false); +assert.strictEqual(shouldShowUpdateHint("not-a-version", "1.8.6"), false); +assert.strictEqual(shouldShowUpdateHint("1.8.7", null), true); + +console.log("version tests passed"); From 227e05043d01d0a041ee538c7165ef32b8aba90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97SO?= <142557582+Linxiushen@users.noreply.github.com> Date: Wed, 5 Aug 2026 04:33:50 +0800 Subject: [PATCH 2/2] fix(updater): address version hint review feedback --- bin/ocr.js | 10 +++------- scripts/version.js | 29 +++++++++++++++++++---------- scripts/version.test.js | 11 ++++++++++- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/bin/ocr.js b/bin/ocr.js index 828eb4cb..e27c6364 100755 --- a/bin/ocr.js +++ b/bin/ocr.js @@ -7,7 +7,8 @@ const fs = require("fs"); const os = require("os"); const { resolveNativeBinary } = require("../scripts/platform"); -const { parseVersionOutput, shouldShowUpdateHint } = require("../scripts/version"); +const { version: packageVersion } = require("../package.json"); +const { shouldShowUpdateHint } = require("../scripts/version"); const resolved = resolveNativeBinary(); if (!resolved) { @@ -21,12 +22,7 @@ const binaryPath = resolved.path; const hintFile = path.join(os.homedir(), ".opencodereview", "update-available"); try { const hint = JSON.parse(fs.readFileSync(hintFile, "utf8")); - const versionResult = spawnSync(binaryPath, ["version"], { - encoding: "utf8", - timeout: 3000, - }); - const installedVersion = parseVersionOutput(versionResult.stdout); - if (hint.pkg && shouldShowUpdateHint(hint.version, installedVersion)) { + if (hint.pkg && shouldShowUpdateHint(hint.version, packageVersion)) { console.error( `\x1b[33m[ocr] A new version (v${hint.version}) is available. Run to update:\x1b[0m\n` + `\x1b[33m npm i -g ${hint.pkg}@${hint.version}\x1b[0m\n` diff --git a/scripts/version.js b/scripts/version.js index a2411615..33c87d03 100644 --- a/scripts/version.js +++ b/scripts/version.js @@ -1,28 +1,37 @@ "use strict"; -const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/; +const SEMVER_RE = + /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/; function parseVersionOutput(output) { - const match = String(output || "").match(/v(\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?)/); + const match = String(output || "").match( + /v(\d+\.\d+(?:\.\d+)?(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)/ + ); return match ? match[1] : null; } function semverGt(a, b) { - const pa = a.replace(/-.*$/, "").split(".").map(Number); - const pb = b.replace(/-.*$/, "").split(".").map(Number); + if (!SEMVER_RE.test(a) || !SEMVER_RE.test(b)) return false; + + const aWithoutBuild = a.replace(/\+.*$/, ""); + const bWithoutBuild = b.replace(/\+.*$/, ""); + const pa = aWithoutBuild.replace(/-.*$/, "").split(".").map(Number); + const pb = bWithoutBuild.replace(/-.*$/, "").split(".").map(Number); for (let i = 0; i < 3; i++) { - if ((pa[i] || 0) > (pb[i] || 0)) return true; - if ((pa[i] || 0) < (pb[i] || 0)) return false; + if (pa[i] > pb[i]) return true; + if (pa[i] < pb[i]) return false; } - const aPre = a.includes("-"); - const bPre = b.includes("-"); + const aPre = aWithoutBuild.includes("-"); + const bPre = bWithoutBuild.includes("-"); if (bPre && !aPre) return true; return false; } function shouldShowUpdateHint(hintVersion, installedVersion) { - if (!SEMVER_RE.test(hintVersion)) return false; - return !installedVersion || semverGt(hintVersion, installedVersion); + if (!SEMVER_RE.test(hintVersion) || !SEMVER_RE.test(installedVersion)) { + return false; + } + return semverGt(hintVersion, installedVersion); } module.exports = { diff --git a/scripts/version.test.js b/scripts/version.test.js index 40e857d4..24f76db0 100644 --- a/scripts/version.test.js +++ b/scripts/version.test.js @@ -13,16 +13,25 @@ assert.strictEqual( "1.8.6" ); assert.strictEqual(parseVersionOutput("version unavailable"), null); +assert.strictEqual( + parseVersionOutput("open-code-review v1.8.6-beta.1+darwin.arm64"), + "1.8.6-beta.1+darwin.arm64" +); assert.strictEqual(semverGt("1.8.7", "1.8.6"), true); assert.strictEqual(semverGt("1.8.6", "1.8.6"), false); assert.strictEqual(semverGt("1.8.5", "1.8.6"), false); assert.strictEqual(semverGt("1.8.6", "1.8.6-beta.1"), true); +assert.strictEqual(semverGt("1.8.6+build.2", "1.8.6+build.1"), false); +assert.strictEqual(semverGt("1.8.6", "1.8.6-beta.1+build.1"), true); +assert.strictEqual(semverGt("not-a-version", "1.8.6"), false); assert.strictEqual(shouldShowUpdateHint("1.8.7", "1.8.6"), true); assert.strictEqual(shouldShowUpdateHint("1.8.6", "1.8.6"), false); assert.strictEqual(shouldShowUpdateHint("1.8.5", "1.8.6"), false); assert.strictEqual(shouldShowUpdateHint("not-a-version", "1.8.6"), false); -assert.strictEqual(shouldShowUpdateHint("1.8.7", null), true); +assert.strictEqual(shouldShowUpdateHint("1.8.7", null), false); +assert.strictEqual(shouldShowUpdateHint("1.8.7", "not-a-version"), false); +assert.strictEqual(shouldShowUpdateHint("1.8.6+new", "1.8.6+old"), false); console.log("version tests passed");