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.
156 lines
6.4 KiB
JavaScript
156 lines
6.4 KiB
JavaScript
'use strict';
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const crypto = require('node:crypto');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
|
|
const retiredArtifactCleanup = require('../msd-core/bin/lib/retired-artifact-cleanup.cjs');
|
|
const capabilityRegistry = require('../msd-core/bin/lib/capability-registry.cjs');
|
|
|
|
const cursorBehaviors = capabilityRegistry.runtimes.cursor.runtime.hostBehaviors;
|
|
|
|
function createFixture(t) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-retired-cleanup-'));
|
|
const configDir = path.join(root, 'config');
|
|
fs.mkdirSync(configDir, { recursive: true });
|
|
t.after(() => {
|
|
// eslint-disable-next-line local/no-raw-rmsync-in-tests -- isolated temp fixture cleanup
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
return { root, configDir };
|
|
}
|
|
|
|
function setDeclarations(t, declarations) {
|
|
const original = cursorBehaviors.retiredArtifacts;
|
|
cursorBehaviors.retiredArtifacts = declarations;
|
|
t.after(() => {
|
|
cursorBehaviors.retiredArtifacts = original;
|
|
});
|
|
}
|
|
|
|
function writeFile(root, relPath, content) {
|
|
const filePath = path.join(root, relPath);
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
return filePath;
|
|
}
|
|
|
|
function sha256(content) {
|
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
}
|
|
|
|
function writeManifest(configDir, files) {
|
|
fs.writeFileSync(path.join(configDir, 'msd-file-manifest.json'), JSON.stringify({
|
|
version: '1.8.0',
|
|
timestamp: '2026-07-28T00:00:00.000Z',
|
|
mode: 'full',
|
|
files,
|
|
}), 'utf8');
|
|
}
|
|
|
|
test('rejects malformed and escaping retired-artifact declarations', (t) => {
|
|
const { root, configDir } = createFixture(t);
|
|
const outsideFile = writeFile(root, 'escape/msd-help.md', '# outside\n');
|
|
const localFile = writeFile(configDir, 'commands/msd-help.md', '# local\n');
|
|
setDeclarations(t, [
|
|
{ destSubpath: null, prefix: 'msd-', suffix: '.md' },
|
|
{ destSubpath: '../escape', prefix: 'msd-', suffix: '.md' },
|
|
]);
|
|
|
|
const result = retiredArtifactCleanup.pruneRetiredRuntimeArtifacts('cursor', configDir);
|
|
|
|
assert.deepEqual(result, { removed: [], preserved: [] });
|
|
assert.ok(fs.existsSync(outsideFile), 'an escaping destination must never be touched');
|
|
assert.ok(fs.existsSync(localFile), 'a malformed declaration must not affect valid-looking files');
|
|
});
|
|
|
|
test('refuses a symlinked retired destination directory', (t) => {
|
|
const { root, configDir } = createFixture(t);
|
|
const targetDir = path.join(root, 'outside-commands');
|
|
const targetFile = writeFile(targetDir, 'msd-help.md', '# outside\n');
|
|
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
fs.symlinkSync(targetDir, path.join(configDir, 'commands'), linkType);
|
|
setDeclarations(t, [{ destSubpath: 'commands', prefix: 'msd-', suffix: '.md' }]);
|
|
|
|
const result = retiredArtifactCleanup.pruneRetiredRuntimeArtifacts('cursor', configDir);
|
|
|
|
assert.deepEqual(result, { removed: [], preserved: [] });
|
|
assert.ok(fs.existsSync(targetFile), 'cleanup must not follow the destination symlink');
|
|
});
|
|
|
|
test('preserves and reports a user-modified managed file', (t) => {
|
|
const { configDir } = createFixture(t);
|
|
const relPath = 'commands/msd-help.md';
|
|
const originalContent = '# generated\n';
|
|
const modifiedContent = '# user changed this\n';
|
|
const filePath = writeFile(configDir, relPath, modifiedContent);
|
|
writeManifest(configDir, { [relPath]: sha256(originalContent) });
|
|
setDeclarations(t, [{ destSubpath: 'commands', prefix: 'msd-', suffix: '.md' }]);
|
|
|
|
const result = retiredArtifactCleanup.pruneRetiredRuntimeArtifacts('cursor', configDir);
|
|
|
|
assert.deepEqual(result, { removed: [], preserved: [relPath] });
|
|
assert.equal(fs.readFileSync(filePath, 'utf8'), modifiedContent);
|
|
assert.ok(fs.existsSync(path.dirname(filePath)), 'directory must remain while a preserved file exists');
|
|
});
|
|
|
|
test('removes and reports a managed-pristine file and then removes the empty directory', (t) => {
|
|
const { configDir } = createFixture(t);
|
|
const relPath = 'commands/msd-help.md';
|
|
const content = '# generated\n';
|
|
const filePath = writeFile(configDir, relPath, content);
|
|
const commandsDir = path.dirname(filePath);
|
|
writeManifest(configDir, { [relPath]: sha256(content) });
|
|
setDeclarations(t, [{ destSubpath: 'commands', prefix: 'msd-', suffix: '.md' }]);
|
|
|
|
const result = retiredArtifactCleanup.pruneRetiredRuntimeArtifacts('cursor', configDir);
|
|
|
|
assert.deepEqual(result, { removed: [relPath], preserved: [] });
|
|
assert.ok(!fs.existsSync(filePath));
|
|
assert.ok(!fs.existsSync(commandsDir), 'an empty retired destination should be removed');
|
|
});
|
|
|
|
test('leaves non-matching files untouched and keeps their directory', (t) => {
|
|
const { configDir } = createFixture(t);
|
|
const managedRelPath = 'commands/msd-help.md';
|
|
const managedContent = '# generated\n';
|
|
const managedFile = writeFile(configDir, managedRelPath, managedContent);
|
|
const unrelatedFile = writeFile(configDir, 'commands/user-command.md', '# user\n');
|
|
writeManifest(configDir, { [managedRelPath]: sha256(managedContent) });
|
|
setDeclarations(t, [{ destSubpath: 'commands', prefix: 'msd-', suffix: '.md' }]);
|
|
|
|
const result = retiredArtifactCleanup.pruneRetiredRuntimeArtifacts('cursor', configDir);
|
|
|
|
assert.deepEqual(result, { removed: [managedRelPath], preserved: [] });
|
|
assert.ok(!fs.existsSync(managedFile));
|
|
assert.ok(fs.existsSync(unrelatedFile));
|
|
assert.ok(fs.existsSync(path.dirname(unrelatedFile)), 'a non-empty destination must remain');
|
|
});
|
|
|
|
test('reports a managed-pristine file as preserved when unlinkSync fails', (t) => {
|
|
const { configDir } = createFixture(t);
|
|
const relPath = 'commands/msd-help.md';
|
|
const content = '# generated\n';
|
|
const filePath = writeFile(configDir, relPath, content);
|
|
writeManifest(configDir, { [relPath]: sha256(content) });
|
|
setDeclarations(t, [{ destSubpath: 'commands', prefix: 'msd-', suffix: '.md' }]);
|
|
|
|
const originalUnlinkSync = fs.unlinkSync;
|
|
fs.unlinkSync = (candidate) => {
|
|
if (path.resolve(candidate) === path.resolve(filePath)) throw new Error('injected unlink failure');
|
|
return originalUnlinkSync(candidate);
|
|
};
|
|
let result;
|
|
try {
|
|
result = retiredArtifactCleanup.pruneRetiredRuntimeArtifacts('cursor', configDir);
|
|
} finally {
|
|
fs.unlinkSync = originalUnlinkSync;
|
|
}
|
|
|
|
assert.deepEqual(result, { removed: [], preserved: [relPath] });
|
|
assert.ok(fs.existsSync(filePath), 'a failed unlink must leave the file in place');
|
|
});
|