Files
msd-core/tests/release-tarball-smoke.install.test.cjs
Tom Boucher 79002a00cb chore(#518): rename npm package + bin to @opengsd/gsd-core (#519)
* chore: rename npm package + bin to @opengsd/gsd-core (functional)

- package.json: name @opengsd/get-shit-done-redux → @opengsd/gsd-core,
  bin key get-shit-done-redux → gsd-core, repository/homepage/bugs URLs
- package-lock.json: regenerated (npm install --package-lock-only)
- tests/**, scripts/**, bin/**, .github/**, agents/**, commands/**,
  get-shit-done/bin/**, get-shit-done/workflows/**:
  applied the 4-rule replacement (scoped npm ref, GitHub repo path,
  bin/clone invocations) per #505 single-source refactor

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: sweep live references to @opengsd/gsd-core

Update all live documentation (README.md + translations, docs/**,
CONTRIBUTING.md, VERSIONING.md, SECURITY.md, CONTEXT.md,
docs/CANARY.md) to reflect the renamed package and repository.

Rules applied:
- @opengsd/get-shit-done-redux → @opengsd/gsd-core (scoped npm name)
- open-gsd/get-shit-done-redux → open-gsd/gsd-core (GitHub repo)
- GSD-redux/get-shit-done-redux → open-gsd/gsd-core (stale badge org)
- bare bin/clone refs → gsd-core

CHANGELOG.md, docs/adr/**, docs/RELEASE-*.md, docs/research/**,
and .changeset/** are preserved byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: add negative lookbehind to slash-command regex in bug-2954 test

The extractSlashReferences regex matched /gsd-core inside npm package
URLs (@opengsd/gsd-core), producing a false /gsd:core command reference.
Adding a negative lookbehind (?<![a-z]) excludes matches preceded by a
letter, so only standalone /gsd-<cmd> and /gsd:<cmd> tokens are found.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#518): add changeset for package rename

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#518): update package-identity expectations to the renamed coordinates

The rebase regenerated the seam to @opengsd/gsd-core (bin gsd-core, repo
open-gsd/gsd-core). The #498 seam tests assert deriveIdentity against the REAL
package.json, so their expected literals must follow the rename. The drift-lint
unit test is left as-is — its SEAM is a self-consistent fixture and its
stale-literal detection cases would shift if altered; the live-repo scan in it
already passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 17:25:02 -04:00

181 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// allow-test-rule: integration-test-input
// The script under test (scripts/release-tarball-smoke.cjs) is the system
// under test. We exercise it via its exported pure function, not by reading
// source text. The tarball fixture is produced by npm pack in before().
'use strict';
process.env.GSD_TEST_MODE = '1';
const { describe, test, before, after } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { cleanup, createTempDir, runNpm, isolatedNpmEnv } = require('./helpers.cjs');
const { SMOKE, runSmoke } = require('../scripts/release-tarball-smoke.cjs');
const smokeMsg = (label, result) =>
`${label}: code=${result.code} details=${JSON.stringify(result.details)}`;
const PKG_PATH = path.join(__dirname, '..', 'package.json');
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf-8'));
describe('release-tarball-smoke', () => {
// Shared fixture state: pack the tarball once, install it once, reuse for all tests.
let packDir;
let installPrefix;
let tarballPath;
// fixtureDir for lifecycle / init tests; created once in before(), cleaned in after().
let fixtureDir;
before(async () => {
// Pack once into a temp dir.
packDir = createTempDir('gsd-smoke-pack-');
installPrefix = createTempDir('gsd-smoke-prefix-');
fixtureDir = createTempDir('gsd-smoke-fixture-');
// npm pack + npm install -g on a large tarball (1499 files, ~10 MB) can take
// 3–6 minutes on slow Docker hosts (cold disk, constrained CPU). The runNpm
// default timeout of 180 s is sufficient on fast machines but insufficient on
// cartographer-class hosts. 600 s (10 min) gives a safe ceiling without
// masking genuine hangs.
const SLOW_HOST_TIMEOUT = 600_000;
const packOutput = runNpm(
['pack', '--pack-destination', packDir],
{ cwd: path.join(__dirname, '..'), timeout: SLOW_HOST_TIMEOUT },
);
// npm pack prints the filename as the last line of stdout.
const lines = packOutput.split(/\r?\n/).filter(Boolean);
const tgzName = lines[lines.length - 1];
tarballPath = path.join(packDir, tgzName);
if (!fs.existsSync(tarballPath)) {
const found = fs.readdirSync(packDir).find((f) => f.endsWith('.tgz'));
if (!found) throw new Error(`npm pack produced no .tgz in ${packDir}; output: ${packOutput}`);
tarballPath = path.join(packDir, found);
}
// Install once into installPrefix. All tests share this install.
runNpm(['install', '-g', '--prefix', installPrefix, tarballPath], { timeout: SLOW_HOST_TIMEOUT });
});
after(() => {
cleanup(packDir);
cleanup(installPrefix);
cleanup(fixtureDir);
});
// ── Test A — happy path ────────────────────────────────────────────────────
test('A: happy path — installed version matches package.json', () => {
const result = runSmoke({
tarballPath,
installPrefix,
expectedVersion: pkg.version,
fixtureDir,
npmEnv: isolatedNpmEnv(),
});
assert.equal(result.code, SMOKE.OK, smokeMsg('A', result));
assert.equal(result.details.version, pkg.version, smokeMsg('A', result));
});
// ── Test B — version mismatch detected ────────────────────────────────────
test('B: version mismatch detected — returns VERSION_MISMATCH', () => {
const result = runSmoke({
tarballPath,
installPrefix,
expectedVersion: '99.99.99',
fixtureDir,
npmEnv: isolatedNpmEnv(),
});
assert.equal(result.code, SMOKE.VERSION_MISMATCH, smokeMsg('B', result));
});
// ── Test C — happy lifecycle ───────────────────────────────────────────────
// Verifies that the installed package has all expected command .md files and
// that each command resolves a workflow .md file that also exists.
// Also verifies that `gsd-core --local --claude` (init) succeeds in
// the fixtureDir and creates the expected .claude/ directories.
test('C: happy lifecycle — command + workflow files resolve OK', () => {
const result = runSmoke({
tarballPath,
installPrefix,
expectedVersion: pkg.version,
fixtureDir,
lifecycleCommands: ['init', 'discuss-phase', 'plan-phase'],
npmEnv: isolatedNpmEnv(),
});
assert.equal(result.code, SMOKE.OK, smokeMsg('C', result));
// Each non-init command must be in lifecycleResolved with both paths populated
const resolved = result.details.lifecycleResolved;
assert.ok(Array.isArray(resolved));
for (const entry of resolved) {
assert.ok(
typeof entry.commandPath === 'string' && entry.commandPath.length > 0,
`expected commandPath for ${entry.command}`,
);
assert.ok(
fs.existsSync(entry.commandPath) && fs.statSync(entry.commandPath).isFile(),
`commandPath must be an existing file: ${entry.commandPath}`,
);
assert.ok(
typeof entry.workflowPath === 'string' && entry.workflowPath.length > 0,
`expected workflowPath for ${entry.command}`,
);
assert.ok(
fs.existsSync(entry.workflowPath) && fs.statSync(entry.workflowPath).isFile(),
`workflowPath must be an existing file: ${entry.workflowPath}`,
);
}
});
// ── Test D — missing command detected ─────────────────────────────────────
// Passes a nonexistent command name; expects the smoke to detect the missing
// command .md file and return COMMAND_FILE_MISSING with the right details.
test('D: missing command detected — returns COMMAND_FILE_MISSING', () => {
const result = runSmoke({
tarballPath,
installPrefix,
expectedVersion: pkg.version,
fixtureDir,
lifecycleCommands: ['init', 'nonexistent-phase-xyz'],
npmEnv: isolatedNpmEnv(),
});
assert.equal(result.code, SMOKE.COMMAND_FILE_MISSING, smokeMsg('D', result));
assert.equal(result.details.command, 'nonexistent-phase-xyz', smokeMsg('D', result));
assert.ok(typeof result.details.path === 'string' && result.details.path.length > 0, smokeMsg('D', result));
});
// ── Test E — workflow-body checks run (informational) ─────────────────────
// Asserts that the workflow-body scanning machinery ran (structural assertion).
// Does NOT assert colonLeakCount is zero — when those issues are fixed, this
// test continues to pass unchanged.
test('E: workflow-body checks run — scan counts are present integers', () => {
const result = runSmoke({
tarballPath,
installPrefix,
expectedVersion: pkg.version,
fixtureDir,
lifecycleCommands: [],
npmEnv: isolatedNpmEnv(),
});
// Structural: the scan ran and populated the counters
assert.ok(
Number.isInteger(result.details.workflowsScanned) && result.details.workflowsScanned >= 1,
smokeMsg('E', result),
);
assert.ok(
Number.isInteger(result.details.colonLeakCount),
smokeMsg('E', result),
);
});
});