Adds get-shit-done/bin/lib/package-identity.cjs as the single source of truth for PACKAGE_NAME, derived from package.json `name` via require. Refactors all runtime code-line occurrences in bin/install.js, get-shit-done/bin/check-latest-version.cjs, get-shit-done/bin/lib/shell-command-projection.cjs, get-shit-done/bin/lib/verify.cjs, scripts/changeset/cli.cjs, scripts/changeset/github-release-notes.cjs, and scripts/release-tarball-smoke.cjs to import PACKAGE_NAME from the identity module instead of hardcoding the literal. The package name is unchanged (@opengsd/get-shit-done-redux). Behaviour is byte-identical: all --help, hint, and release-notes strings render exactly as before. Golden-literal tests (bug-2992, bug-378) keep their hardcoded expected values and remain GREEN. Adds tests/package-name-single-source.test.cjs lint guard: fails CI if @opengsd/get-shit-done-redux appears as a code-line literal in runtime .cjs/.js outside the identity module, enforcing a one-file rename path. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
115
tests/package-name-single-source.test.cjs
Normal file
115
tests/package-name-single-source.test.cjs
Normal file
@@ -0,0 +1,115 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Lint guard: the package name must be single-sourced from package-identity.cjs.
|
||||
*
|
||||
* Scans runtime files (bin/install.js, get-shit-done/bin/**, scripts/*.cjs)
|
||||
* and FAILS if the literal `@opengsd/get-shit-done-redux` appears in a
|
||||
* non-comment, non-identity-module line. This enforces that a future rename
|
||||
* is a one-file change in package.json (#516).
|
||||
*
|
||||
* Comment lines are detected by checking if the trimmed line starts with
|
||||
* `//`, `*`, or `/*` (best-effort; covers all common JS comment forms).
|
||||
*
|
||||
* Tests:
|
||||
* 1. No code-literal occurrences of the package name in runtime files
|
||||
* (outside the identity module itself).
|
||||
* 2. PACKAGE_NAME exported from the identity module equals package.json name.
|
||||
*/
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { execSync } = require('node:child_process');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const LITERAL = '@opengsd/get-shit-done-redux';
|
||||
const IDENTITY_MODULE = path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'package-identity.cjs');
|
||||
|
||||
// Files to scan: bin/install.js + everything under get-shit-done/bin/ + touched scripts
|
||||
function getRuntimeFiles() {
|
||||
const files = [];
|
||||
|
||||
// bin/install.js
|
||||
const installJs = path.join(ROOT, 'bin', 'install.js');
|
||||
if (fs.existsSync(installJs)) files.push(installJs);
|
||||
|
||||
// get-shit-done/bin/**/*.cjs and *.js (recursive)
|
||||
function collectDir(dir) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
collectDir(fullPath);
|
||||
} else if (entry.isFile() && /\.(cjs|js)$/.test(entry.name)) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
collectDir(path.join(ROOT, 'get-shit-done', 'bin'));
|
||||
|
||||
// scripts/*.cjs
|
||||
for (const entry of fs.readdirSync(path.join(ROOT, 'scripts'), { withFileTypes: true })) {
|
||||
if (entry.isFile() && entry.name.endsWith('.cjs')) {
|
||||
files.push(path.join(ROOT, 'scripts', entry.name));
|
||||
}
|
||||
}
|
||||
// scripts/changeset/*.cjs
|
||||
const changesetDir = path.join(ROOT, 'scripts', 'changeset');
|
||||
if (fs.existsSync(changesetDir)) {
|
||||
for (const entry of fs.readdirSync(changesetDir, { withFileTypes: true })) {
|
||||
if (entry.isFile() && entry.name.endsWith('.cjs')) {
|
||||
files.push(path.join(changesetDir, entry.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function isCommentLine(trimmed) {
|
||||
return (
|
||||
trimmed.startsWith('//') ||
|
||||
trimmed.startsWith('*') ||
|
||||
trimmed.startsWith('/*')
|
||||
);
|
||||
}
|
||||
|
||||
test('no hardcoded package-name literals in runtime non-comment code lines (#516)', () => {
|
||||
const files = getRuntimeFiles();
|
||||
const violations = [];
|
||||
|
||||
for (const file of files) {
|
||||
// Skip the identity module itself — it legitimately contains the literal
|
||||
if (path.resolve(file) === path.resolve(IDENTITY_MODULE)) continue;
|
||||
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (!line.includes(LITERAL)) continue;
|
||||
const trimmed = line.trimStart();
|
||||
if (isCommentLine(trimmed)) continue;
|
||||
violations.push(`${path.relative(ROOT, file)}:${i + 1}: ${line.trim()}`);
|
||||
}
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
violations,
|
||||
[],
|
||||
`Found ${violations.length} hardcoded package-name literal(s) in non-comment code lines:\n` +
|
||||
violations.map(v => ` ${v}`).join('\n') +
|
||||
'\n\nReplace each with the PACKAGE_NAME imported from get-shit-done/bin/lib/package-identity.cjs'
|
||||
);
|
||||
});
|
||||
|
||||
test('PACKAGE_NAME from identity module matches package.json name (#516)', () => {
|
||||
const { PACKAGE_NAME } = require('../get-shit-done/bin/lib/package-identity.cjs');
|
||||
const pkgJson = require('../package.json');
|
||||
assert.equal(
|
||||
PACKAGE_NAME,
|
||||
pkgJson.name,
|
||||
`Identity module PACKAGE_NAME (${PACKAGE_NAME}) must equal package.json name (${pkgJson.name})`
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user