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.
279 lines
12 KiB
JavaScript
279 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* inventory-nested-families.test.cjs — 50-test-matrix.md rows 4, 5, 8, 9, 10, 11, 12, 13
|
|
* (issue #2996, epic #1671 Phase 6.5).
|
|
*
|
|
* `collectNested` is what makes `msd-core/workflows/<wf>/steps/*.md` and
|
|
* `<wf>/modes/*.md` visible to docs/INVENTORY-MANIFEST.json. The real tree
|
|
* currently has no same-named step files in two workflows, so the collision
|
|
* safety that motivated PATH keying is unobservable against the live repo —
|
|
* these rows build fixture trees that DO collide, so the property is proven
|
|
* rather than assumed.
|
|
*/
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
|
|
const { collectNested, collectOneLevelSubdirs } = require('../scripts/gen-inventory-manifest.cjs');
|
|
const { cleanup } = require('./helpers.cjs');
|
|
|
|
const MD_ONLY = (f) => f.endsWith('.md');
|
|
const CJS_ONLY = (f) => f.endsWith('.cjs');
|
|
|
|
/** Build a fixture tree: {parentName: {subdirName: [fileNames]}}. */
|
|
function buildTree(spec) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2996-nested-'));
|
|
for (const [parent, subdirs] of Object.entries(spec)) {
|
|
for (const [subdir, files] of Object.entries(subdirs)) {
|
|
const dir = path.join(root, parent, subdir);
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
for (const f of files) fs.writeFileSync(path.join(dir, f), '# fixture\n');
|
|
}
|
|
}
|
|
return root;
|
|
}
|
|
|
|
// ─── Row 4/5: PATH keying, not basename — collisions must not drop entries ────
|
|
|
|
test('row 4 — two workflows owning a same-named step file both appear', (t) => {
|
|
const root = buildTree({
|
|
alpha: { steps: ['regression-gate.md'] },
|
|
beta: { steps: ['regression-gate.md'] },
|
|
});
|
|
t.after(() => cleanup(root));
|
|
|
|
assert.deepStrictEqual(
|
|
collectNested({ root, subdir: 'steps', filter: MD_ONLY }),
|
|
['alpha/steps/regression-gate.md', 'beta/steps/regression-gate.md'],
|
|
'a basename key would collapse these to one entry and the JSON-equality check would read it as up to date',
|
|
);
|
|
});
|
|
|
|
test('row 5 — a step file named after a top-level workflow keeps its own key', (t) => {
|
|
const root = buildTree({ alpha: { steps: ['quick.md'] } });
|
|
t.after(() => cleanup(root));
|
|
|
|
assert.deepStrictEqual(
|
|
collectNested({ root, subdir: 'steps', filter: MD_ONLY }),
|
|
['alpha/steps/quick.md'],
|
|
'the nested key is a path, so it cannot be confused with the top-level workflows entry "quick.md"',
|
|
);
|
|
});
|
|
|
|
// ─── Rows 8/9/10: emptiness boundaries — limit-1 / limit ─────────────────────
|
|
|
|
test('row 8 — a parent with no such subdirectory contributes nothing', (t) => {
|
|
const root = buildTree({ alpha: { modes: ['default.md'] } });
|
|
t.after(() => cleanup(root));
|
|
|
|
assert.deepStrictEqual(
|
|
collectNested({ root, subdir: 'steps', filter: MD_ONLY }),
|
|
[],
|
|
'a workflow without a steps/ dir must contribute no key at all',
|
|
);
|
|
});
|
|
|
|
test('row 9 — an empty subdirectory contributes no entries (limit-1)', (t) => {
|
|
const root = buildTree({ alpha: { steps: [] } });
|
|
t.after(() => cleanup(root));
|
|
|
|
assert.deepStrictEqual(
|
|
collectNested({ root, subdir: 'steps', filter: MD_ONLY }),
|
|
[],
|
|
'an empty steps/ dir must not materialize an empty-array key — that is a committed diff signalling nothing',
|
|
);
|
|
});
|
|
|
|
test('row 10 — exactly one file is listed (limit)', (t) => {
|
|
const root = buildTree({ alpha: { steps: ['only.md'] } });
|
|
t.after(() => cleanup(root));
|
|
|
|
assert.deepStrictEqual(collectNested({ root, subdir: 'steps', filter: MD_ONLY }), ['alpha/steps/only.md']);
|
|
});
|
|
|
|
// ─── Row 11: recursion is bounded at ONE level (limit+1) ─────────────────────
|
|
|
|
test('row 11 — nesting deeper than one level is not swept', (t) => {
|
|
const root = buildTree({ alpha: { steps: ['top.md'] } });
|
|
const deep = path.join(root, 'alpha', 'steps', 'sub');
|
|
fs.mkdirSync(deep, { recursive: true });
|
|
fs.writeFileSync(path.join(deep, 'deeper.md'), '# fixture\n');
|
|
t.after(() => cleanup(root));
|
|
|
|
assert.deepStrictEqual(
|
|
collectNested({ root, subdir: 'steps', filter: MD_ONLY }),
|
|
['alpha/steps/top.md'],
|
|
'recursion is deliberately bounded at one named subdirectory; this row is what stops that bound from silently becoming a general walk',
|
|
);
|
|
});
|
|
|
|
// ─── Rows 12/13: filtering and hostile shapes ────────────────────────────────
|
|
|
|
test('row 12 — non-markdown files in the subdirectory are ignored', (t) => {
|
|
const root = buildTree({ alpha: { steps: ['keep.md'] } });
|
|
fs.writeFileSync(path.join(root, 'alpha', 'steps', 'notes.txt'), 'x\n');
|
|
fs.writeFileSync(path.join(root, 'alpha', 'steps', 'data.json'), '{}\n');
|
|
t.after(() => cleanup(root));
|
|
|
|
assert.deepStrictEqual(collectNested({ root, subdir: 'steps', filter: MD_ONLY }), ['alpha/steps/keep.md']);
|
|
});
|
|
|
|
test('row 13 — a plain FILE named like the subdirectory does not crash the walk', (t) => {
|
|
const root = buildTree({ alpha: { steps: ['real.md'] } });
|
|
fs.mkdirSync(path.join(root, 'beta'), { recursive: true });
|
|
fs.writeFileSync(path.join(root, 'beta', 'steps'), 'i am a file, not a directory\n');
|
|
t.after(() => cleanup(root));
|
|
|
|
assert.deepStrictEqual(
|
|
collectNested({ root, subdir: 'steps', filter: MD_ONLY }),
|
|
['alpha/steps/real.md'],
|
|
'isDirectory() must be checked before readdirSync, or a file named steps throws ENOTDIR',
|
|
);
|
|
});
|
|
|
|
test('row 13b — a missing root contributes nothing rather than throwing', () => {
|
|
assert.deepStrictEqual(
|
|
collectNested({ root: path.join(os.tmpdir(), 'msd-2996-does-not-exist'), subdir: 'steps', filter: MD_ONLY }),
|
|
[],
|
|
);
|
|
});
|
|
|
|
// ─── Row 13c: an unstattable entry is skipped, not fatal ─────────────────────
|
|
|
|
test('row 13c — a dangling symlink under the subdirectory does not crash the walk', (t) => {
|
|
// fs.symlinkSync throws EPERM on Windows without elevation or Developer Mode.
|
|
// A genuine t.skip(), never a bare `return` — a bare return is a PASS in
|
|
// node:test and would hide the gap rather than report it.
|
|
if (process.platform === 'win32') {
|
|
t.skip('symlink creation requires elevation on Windows; the unstattable-entry path is asserted on macOS + Linux');
|
|
return;
|
|
}
|
|
|
|
const root = buildTree({ alpha: { steps: ['real.md'] } });
|
|
t.after(() => cleanup(root));
|
|
fs.symlinkSync(path.join(root, 'alpha', 'steps', 'nope.md'), path.join(root, 'alpha', 'steps', 'dangling.md'));
|
|
|
|
assert.deepStrictEqual(
|
|
collectNested({ root, subdir: 'steps', filter: MD_ONLY }),
|
|
['alpha/steps/real.md'],
|
|
'one unreadable entry must not take down manifest generation for the whole repo',
|
|
);
|
|
});
|
|
|
|
// ─── Determinism ─────────────────────────────────────────────────────────────
|
|
|
|
test('collectNested is deterministic and sorted', (t) => {
|
|
const root = buildTree({ zeta: { steps: ['b.md', 'a.md'] }, alpha: { steps: ['c.md'] } });
|
|
t.after(() => cleanup(root));
|
|
|
|
const first = collectNested({ root, subdir: 'steps', filter: MD_ONLY });
|
|
const second = collectNested({ root, subdir: 'steps', filter: MD_ONLY });
|
|
assert.deepStrictEqual(first, second);
|
|
assert.deepStrictEqual(first, ['alpha/steps/c.md', 'zeta/steps/a.md', 'zeta/steps/b.md']);
|
|
});
|
|
|
|
// ─── collectOneLevelSubdirs — cli_modules one-level subdir scan (#3309) ───────
|
|
//
|
|
// Unlike collectNested's FIXED subdir name (many parents, one shared child-dir
|
|
// name like `steps`), this helper's `dir` IS the parent, and every one of ITS
|
|
// child directories is the thing being collected — e.g. `bin/lib/health-diagnostic-rules/`.
|
|
|
|
/** Build a fixture tree directly under `dir`: {subdirName: [fileNames]} \| flat file list. */
|
|
function buildSubdirTree(dir, spec) {
|
|
for (const [subdir, files] of Object.entries(spec)) {
|
|
const subdirPath = path.join(dir, subdir);
|
|
fs.mkdirSync(subdirPath, { recursive: true });
|
|
for (const f of files) fs.writeFileSync(path.join(subdirPath, f), '// fixture\n');
|
|
}
|
|
}
|
|
|
|
test('collectOneLevelSubdirs picks up a file inside a subdirectory', (t) => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3309-onelevel-'));
|
|
t.after(() => cleanup(dir));
|
|
buildSubdirTree(dir, { 'health-diagnostic-rules': ['root-existence.cjs'] });
|
|
|
|
assert.deepStrictEqual(
|
|
collectOneLevelSubdirs({ dir, filter: CJS_ONLY }),
|
|
['health-diagnostic-rules/root-existence.cjs'],
|
|
);
|
|
});
|
|
|
|
test('collectOneLevelSubdirs merges multiple subdirectories, sorted', (t) => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3309-onelevel-'));
|
|
t.after(() => cleanup(dir));
|
|
buildSubdirTree(dir, {
|
|
observability: ['redaction.cjs', 'event.cjs'],
|
|
'installer-migrations': ['001-x.cjs'],
|
|
});
|
|
|
|
assert.deepStrictEqual(
|
|
collectOneLevelSubdirs({ dir, filter: CJS_ONLY }),
|
|
['installer-migrations/001-x.cjs', 'observability/event.cjs', 'observability/redaction.cjs'],
|
|
);
|
|
});
|
|
|
|
test('collectOneLevelSubdirs contributes nothing for an empty subdirectory', (t) => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3309-onelevel-'));
|
|
t.after(() => cleanup(dir));
|
|
fs.mkdirSync(path.join(dir, 'empty-subdir'), { recursive: true });
|
|
|
|
assert.deepStrictEqual(collectOneLevelSubdirs({ dir, filter: CJS_ONLY }), []);
|
|
});
|
|
|
|
test('collectOneLevelSubdirs ignores files directly in dir (flat scan owns those)', (t) => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3309-onelevel-'));
|
|
t.after(() => cleanup(dir));
|
|
fs.writeFileSync(path.join(dir, 'top-level.cjs'), '// fixture\n');
|
|
buildSubdirTree(dir, { subdir: ['nested.cjs'] });
|
|
|
|
assert.deepStrictEqual(collectOneLevelSubdirs({ dir, filter: CJS_ONLY }), ['subdir/nested.cjs']);
|
|
});
|
|
|
|
test('collectOneLevelSubdirs applies the filter and ignores non-matching files', (t) => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3309-onelevel-'));
|
|
t.after(() => cleanup(dir));
|
|
buildSubdirTree(dir, { subdir: ['keep.cjs', 'skip.md', 'skip.json'] });
|
|
|
|
assert.deepStrictEqual(collectOneLevelSubdirs({ dir, filter: CJS_ONLY }), ['subdir/keep.cjs']);
|
|
});
|
|
|
|
test('collectOneLevelSubdirs does not recurse past one level', (t) => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3309-onelevel-'));
|
|
t.after(() => cleanup(dir));
|
|
buildSubdirTree(dir, { subdir: ['top.cjs'] });
|
|
const deep = path.join(dir, 'subdir', 'deeper');
|
|
fs.mkdirSync(deep, { recursive: true });
|
|
fs.writeFileSync(path.join(deep, 'too-deep.cjs'), '// fixture\n');
|
|
|
|
assert.deepStrictEqual(collectOneLevelSubdirs({ dir, filter: CJS_ONLY }), ['subdir/top.cjs']);
|
|
});
|
|
|
|
test('collectOneLevelSubdirs on a missing dir contributes nothing rather than throwing', () => {
|
|
assert.deepStrictEqual(
|
|
collectOneLevelSubdirs({ dir: path.join(os.tmpdir(), 'msd-3309-does-not-exist'), filter: CJS_ONLY }),
|
|
[],
|
|
);
|
|
});
|
|
|
|
test('collectOneLevelSubdirs skips a dangling symlink without crashing the walk', (t) => {
|
|
if (process.platform === 'win32') {
|
|
t.skip('symlink creation requires elevation on Windows; the unstattable-entry path is asserted on macOS + Linux');
|
|
return;
|
|
}
|
|
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3309-onelevel-'));
|
|
t.after(() => cleanup(dir));
|
|
buildSubdirTree(dir, { subdir: ['real.cjs'] });
|
|
fs.symlinkSync(path.join(dir, 'subdir', 'nope.cjs'), path.join(dir, 'subdir', 'dangling.cjs'));
|
|
|
|
assert.deepStrictEqual(
|
|
collectOneLevelSubdirs({ dir, filter: CJS_ONLY }),
|
|
['subdir/real.cjs'],
|
|
'one unreadable entry must not take down the whole scan',
|
|
);
|
|
});
|