Files
msd-core/tests/legacy-cleanup-config-dir.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
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.
2026-10-06 01:47:40 +02:00

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',
);
});
});