Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
141 lines
6.1 KiB
JavaScript
141 lines
6.1 KiB
JavaScript
'use strict';
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// #3799 — legacy cleanup must honor the install's resolved destination.
|
|
//
|
|
// `install()` called `cleanupLegacyMsdCc({ dryRun: false })` with no
|
|
// arguments, so the scan always ran against `os.homedir()` +
|
|
// `_LEGACY_SCAN_SUBDIR_NAMES` — an install redirected with `--config-dir
|
|
// ~/.sandbox` planned removals of a LIVE legacy install under the DEFAULT
|
|
// home (the reporter's dry-run listed 60 default-home paths; a real install
|
|
// would have deleted 59 files). The cleanup now accepts an explicit
|
|
// `configDirs` scope (install() passes `[targetDir]` when --config-dir
|
|
// redirected the destination), and `--no-legacy-cleanup` skips the scan.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
process.env.MSD_TEST_MODE = '1';
|
|
|
|
const REPO_ROOT = path.join(__dirname, '..');
|
|
const INSTALL_BIN = path.join(REPO_ROOT, 'bin', 'install.js');
|
|
const { cleanupLegacyMsdCc } = require(INSTALL_BIN);
|
|
const { createTempDir, cleanup } = require('./helpers.cjs');
|
|
const { runNode } = require('./helpers/process-seam.cjs');
|
|
|
|
const LEGACY_PKG_SIGNAL = 'get-shit-done-cc';
|
|
|
|
function seedLegacySkill(configDir) {
|
|
// configDir is a CONFIG DIR root (like ~/.claude): artifacts live at
|
|
// <configDir>/skills/msd-*/SKILL.md. The scan's content signal is a stale
|
|
// '/get-shit-done/' PATH reference (legacy-cleanup.cjs // msd-allow-legacy-name
|
|
// LEGACY_SKILL_PATH_SIGNAL), not the bare package name.
|
|
const skillDir = path.join(configDir, 'skills', 'msd-add-tests');
|
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(skillDir, 'SKILL.md'),
|
|
`<!-- installed via ${LEGACY_PKG_SIGNAL} -->\nSee ~/.claude/get-shit-done/skills/msd-add-tests/SKILL.md\n`, // msd-allow-legacy-name
|
|
);
|
|
return path.join(skillDir, 'SKILL.md');
|
|
}
|
|
|
|
describe('#3799: cleanupLegacyMsdCc honors an explicit configDirs scope', () => {
|
|
let tmpRoot;
|
|
let defaultHome;
|
|
let sandboxDir;
|
|
|
|
beforeEach(() => {
|
|
tmpRoot = createTempDir('msd-3799-cleanup-');
|
|
defaultHome = path.join(tmpRoot, 'home');
|
|
sandboxDir = path.join(tmpRoot, 'pilot-sandbox');
|
|
fs.mkdirSync(defaultHome, { recursive: true });
|
|
fs.mkdirSync(sandboxDir, { recursive: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup(tmpRoot);
|
|
});
|
|
|
|
test('#3799: an explicit configDirs scope never plans removals outside it', () => {
|
|
// A LIVE legacy install under the default home (at <home>/.claude, one
|
|
// of _LEGACY_SCAN_SUBDIR_NAMES)…
|
|
const liveArtifact = seedLegacySkill(path.join(defaultHome, '.claude'));
|
|
// …and one stale artifact inside the redirected sandbox.
|
|
const sandboxArtifact = seedLegacySkill(sandboxDir);
|
|
|
|
const { plan } = cleanupLegacyMsdCc({
|
|
homeDir: defaultHome,
|
|
configDirs: [sandboxDir],
|
|
dryRun: true,
|
|
logger: { log: () => {} },
|
|
});
|
|
|
|
const paths = plan.map((p) => p.path);
|
|
assert.ok(
|
|
paths.every((p) => p.startsWith(sandboxDir)),
|
|
`#3799: every plan entry must live inside the explicit scope; got ${JSON.stringify(paths)}`,
|
|
);
|
|
assert.ok(paths.includes(sandboxArtifact), 'the sandbox artifact is in scope and planned');
|
|
assert.ok(
|
|
!paths.includes(liveArtifact),
|
|
'#3799: the default home\'s live legacy install must not be planned for removal',
|
|
);
|
|
assert.ok(
|
|
fs.existsSync(liveArtifact),
|
|
'dry-run leaves everything in place',
|
|
);
|
|
});
|
|
|
|
test('#3799 control: without the override, the home scan is unchanged', () => {
|
|
const liveArtifact = seedLegacySkill(path.join(defaultHome, '.claude'));
|
|
const { plan } = cleanupLegacyMsdCc({
|
|
homeDir: defaultHome,
|
|
dryRun: true,
|
|
logger: { log: () => {} },
|
|
});
|
|
assert.ok(
|
|
plan.map((p) => p.path).includes(liveArtifact),
|
|
'default behavior (no scope) still scans homeDir subdirs as before',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('#3799: --no-legacy-cleanup and --config-dir CLI flags', () => {
|
|
test('the --no-legacy-cleanup flag exists and is documented in --help', () => {
|
|
const result = runNode([INSTALL_BIN, '--help']);
|
|
assert.equal(result.outcome, 'exited', `install.js --help did not exit cleanly: ${result.stderr}`);
|
|
assert.ok(
|
|
result.stdout.includes('--no-legacy-cleanup'),
|
|
'the escape hatch must be documented in the installer --help output',
|
|
);
|
|
});
|
|
|
|
test('the install() call site threads the config-dir scope and the skip flag', () => {
|
|
// allow-test-rule: structural-implementation-guard (#3545) — structural assertion
|
|
// on install()'s internal wiring (that its cleanupLegacyMsdCc call site threads configDirs/
|
|
// skipNoLegacyCleanup) — install() is a ~2500-line, side-effect-heavy
|
|
// top-level installer routine, so exercising this specific plumbing
|
|
// behaviorally would require a full install() run; the source-slice
|
|
// check is the minimum-cost regression guard for the exact #3799 defect
|
|
// shape
|
|
const src = fs.readFileSync(INSTALL_BIN, 'utf-8');
|
|
// Slice the install() body up to its cleanup call so the conditional
|
|
// spread's braces cannot defeat a single-regex match.
|
|
const fnStart = src.indexOf('function install(isGlobal, runtime = DEFAULT_RUNTIME');
|
|
const callIdx = src.indexOf('cleanupLegacyMsdCc({ dryRun: false', fnStart);
|
|
assert.ok(fnStart > 0 && callIdx > fnStart, 'install() still calls cleanupLegacyMsdCc');
|
|
const callSite = src.slice(callIdx, callIdx + 240);
|
|
assert.ok(
|
|
/configDirs/.test(callSite),
|
|
'#3799: the call site must pass a configDirs scope',
|
|
);
|
|
assert.ok(
|
|
/skipNoLegacyCleanup/.test(src.slice(fnStart, callIdx + 400)),
|
|
'#3799: the call site must honor the --no-legacy-cleanup skip',
|
|
);
|
|
});
|
|
});
|