fix(hooks): use semver comparison for update check instead of inequality (#1617)
* fix(hooks): use semver comparison for update check instead of inequality gsd-check-update.js used `installed !== latest` to determine if an update is available. This incorrectly flags an update when the installed version is NEWER than npm (e.g., installing from git ahead of a release). Replace with proper semver comparison: update_available is true only when the npm version is strictly newer than the installed version. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(hooks): use semver comparison for update check instead of inequality gsd-check-update.js used `installed !== latest` to determine if an update is available. This incorrectly flags an update when the installed version is NEWER than npm (e.g., installing from git ahead of a release). Fix: - Move isNewer() inside the spawned child process (was in parent scope, causing ReferenceError in production) - Strip pre-release suffixes before Number() to avoid NaN - Apply same semver comparison to stale hooks check (line 95) update_available is now true only when npm version is strictly newer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add semver comparison tests for gsd-check-update isNewer() 12 test cases covering: major/minor/patch comparison, equal versions, installed-ahead-of-npm scenario, pre-release suffix stripping, null/empty handling, two-segment versions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: explain why isNewer is duplicated in test file Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,18 @@ const child = spawn(process.execPath, ['-e', `
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Compare semver: true if a > b (a is strictly newer than b)
|
||||
// Strips pre-release suffixes (e.g. '3-beta.1' → '3') to avoid NaN from Number()
|
||||
function isNewer(a, b) {
|
||||
const pa = (a || '').split('.').map(s => Number(s.replace(/-.*/, '')) || 0);
|
||||
const pb = (b || '').split('.').map(s => Number(s.replace(/-.*/, '')) || 0);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (pa[i] > pb[i]) return true;
|
||||
if (pa[i] < pb[i]) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const cacheFile = ${JSON.stringify(cacheFile)};
|
||||
const projectVersionFile = ${JSON.stringify(projectVersionFile)};
|
||||
const globalVersionFile = ${JSON.stringify(globalVersionFile)};
|
||||
@@ -81,7 +93,7 @@ const child = spawn(process.execPath, ['-e', `
|
||||
const versionMatch = content.match(/\\/\\/ gsd-hook-version:\\s*(.+)/);
|
||||
if (versionMatch) {
|
||||
const hookVersion = versionMatch[1].trim();
|
||||
if (hookVersion !== installed && !hookVersion.includes('{{')) {
|
||||
if (isNewer(installed, hookVersion) && !hookVersion.includes('{{')) {
|
||||
staleHooks.push({ file: hookFile, hookVersion, installedVersion: installed });
|
||||
}
|
||||
} else {
|
||||
@@ -100,7 +112,7 @@ const child = spawn(process.execPath, ['-e', `
|
||||
} catch (e) {}
|
||||
|
||||
const result = {
|
||||
update_available: latest && installed !== latest,
|
||||
update_available: latest && isNewer(latest, installed),
|
||||
installed,
|
||||
latest: latest || 'unknown',
|
||||
checked: Math.floor(Date.now() / 1000),
|
||||
|
||||
Reference in New Issue
Block a user