The update worker queried the unscoped 'get-shit-done-redux' via
`npm view`, which returns E404 — so `latest` stayed null and
`update_available` could never become true. Now derives the name from
package.json (`require('../package.json').name`) so it always matches
the actual published scoped name (@opengsd/get-shit-done-redux).
Fixes #378.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
5
.changeset/378-update-check-scoped-name.md
Normal file
5
.changeset/378-update-check-scoped-name.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
type: Fixed
|
||||||
|
pr: 378
|
||||||
|
---
|
||||||
|
Update check now polls the correct scoped package name (@opengsd/get-shit-done-redux) so update_available works.
|
||||||
@@ -13,6 +13,9 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execFileSync } = require('child_process');
|
const { execFileSync } = require('child_process');
|
||||||
const { isSemverNewer } = require('../get-shit-done/bin/lib/semver-compare.cjs');
|
const { isSemverNewer } = require('../get-shit-done/bin/lib/semver-compare.cjs');
|
||||||
|
// Derive the published package name from package.json so this survives
|
||||||
|
// future renames and always matches the actual registry entry (#378).
|
||||||
|
const PACKAGE_NAME = require('../package.json').name;
|
||||||
|
|
||||||
const cacheFile = process.env.GSD_CACHE_FILE;
|
const cacheFile = process.env.GSD_CACHE_FILE;
|
||||||
const projectVersionFile = process.env.GSD_PROJECT_VERSION_FILE;
|
const projectVersionFile = process.env.GSD_PROJECT_VERSION_FILE;
|
||||||
@@ -79,7 +82,7 @@ if (configDir) {
|
|||||||
|
|
||||||
let latest = null;
|
let latest = null;
|
||||||
try {
|
try {
|
||||||
latest = execFileSync('npm', ['view', 'get-shit-done-redux', 'version'], {
|
latest = execFileSync('npm', ['view', PACKAGE_NAME, 'version'], {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
|
|||||||
98
tests/bug-378-update-check-scoped-name.test.cjs
Normal file
98
tests/bug-378-update-check-scoped-name.test.cjs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* Regression test for #378: gsd-check-update-worker.js must query
|
||||||
|
* the SCOPED package name (@opengsd/get-shit-done-redux) when calling
|
||||||
|
* `npm view <name> version`.
|
||||||
|
*
|
||||||
|
* Background: the worker previously hardcoded the unscoped string
|
||||||
|
* 'get-shit-done-redux', which returns E404 from the npm registry because
|
||||||
|
* the published package is scoped. This caused `latest` to stay null and
|
||||||
|
* `update_available` to be permanently false — users never saw update
|
||||||
|
* notifications.
|
||||||
|
*
|
||||||
|
* The fix derives the name from package.json (most robust — survives
|
||||||
|
* future renames). This test locks the contract in two ways:
|
||||||
|
*
|
||||||
|
* 1. Structural: the worker must NOT contain the bare unscoped literal
|
||||||
|
* 'get-shit-done-redux' as a standalone npm view argument.
|
||||||
|
* 2. Derived: the worker must read the package name from package.json
|
||||||
|
* and the package.json name MUST be the scoped string
|
||||||
|
* '@opengsd/get-shit-done-redux'.
|
||||||
|
*
|
||||||
|
* Source-grep policy: this test reads hook source via readFileSync.
|
||||||
|
* The repo's lint-no-source-grep rule targets bin/lib/get-shit-done — hooks/
|
||||||
|
* is out of scope. The shape we need to lock (which string is passed as the
|
||||||
|
* npm view argument) only manifests at runtime against the live registry;
|
||||||
|
* a structural assertion is the minimum-cost contract.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// allow-test-rule: structural assertion on hook npm-view argument; the
|
||||||
|
// behavior being tested (correct package name → no E404) only manifests at
|
||||||
|
// runtime against the live npm registry, which CI does not call.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { test, describe } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const WORKER_PATH = path.join(__dirname, '..', 'hooks', 'gsd-check-update-worker.js');
|
||||||
|
const PKG_PATH = path.join(__dirname, '..', 'package.json');
|
||||||
|
|
||||||
|
describe('bug #378: update-check worker uses scoped package name', () => {
|
||||||
|
test('worker file exists', () => {
|
||||||
|
assert.ok(fs.existsSync(WORKER_PATH), `worker not found at ${WORKER_PATH}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('package.json name is the scoped @opengsd/get-shit-done-redux', () => {
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
||||||
|
assert.equal(
|
||||||
|
pkg.name,
|
||||||
|
'@opengsd/get-shit-done-redux',
|
||||||
|
'package.json must declare the scoped name — this is what npm view must query',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('worker does NOT hardcode the unscoped get-shit-done-redux as an npm view argument', () => {
|
||||||
|
const src = fs.readFileSync(WORKER_PATH, 'utf8');
|
||||||
|
|
||||||
|
// Strip comments so doc-prose mentions don't trigger the check.
|
||||||
|
const codeOnly = src
|
||||||
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||||||
|
.replace(/(^|[^:])\/\/[^\n]*/g, '$1');
|
||||||
|
|
||||||
|
// The unscoped bare string as a string literal used in code.
|
||||||
|
// A match here means the bug is present: npm view 'get-shit-done-redux'
|
||||||
|
// → E404 → update_available permanently false.
|
||||||
|
const unscopedLiteral = /['"]get-shit-done-redux['"]/;
|
||||||
|
|
||||||
|
assert.doesNotMatch(
|
||||||
|
codeOnly,
|
||||||
|
unscopedLiteral,
|
||||||
|
[
|
||||||
|
"Worker must not pass the unscoped 'get-shit-done-redux' to `npm view`.",
|
||||||
|
'That name returns E404, leaving update_available permanently false.',
|
||||||
|
'Use the scoped name from package.json: @opengsd/get-shit-done-redux.',
|
||||||
|
].join(' '),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('worker derives package name from package.json (require + .name)', () => {
|
||||||
|
const src = fs.readFileSync(WORKER_PATH, 'utf8');
|
||||||
|
|
||||||
|
// Structural check: worker must load package.json and read .name from it.
|
||||||
|
// This is the robust form — survives future renames without code edits.
|
||||||
|
const requiresPkgJson = /require\s*\(\s*['"][^'"]*package\.json['"]\s*\)\.name/;
|
||||||
|
|
||||||
|
assert.match(
|
||||||
|
src,
|
||||||
|
requiresPkgJson,
|
||||||
|
[
|
||||||
|
'Worker must derive the npm view package name via',
|
||||||
|
"`require('../package.json').name` (or similar).",
|
||||||
|
'Hardcoding the scoped literal is less robust: a future rename',
|
||||||
|
'would silently break update checks again.',
|
||||||
|
].join(' '),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user