The script that produced the existing vendored copies was deleted with the monorepo layout in 2026-06 and recovered from catalog's history (e84dffd^:scripts/sync-design-system.mjs). It could not be restored unchanged: it walks its source directory and copies everything, and this repo's root is no longer a directory holding only the design system. Run verbatim it vendored 109 files instead of 27, STATE.md and the whole of .git/ among them - and STATE.md is gitignored precisely because this repo's remote is public. The boundary is an explicit allowlist (DELIVERED_FILES) rather than a dist/ directory: one definition, read directly by the tests, with no copy step to go stale. The allowlist's own failure mode - a new design-system file forgotten from the list ships nothing, and --check stays green because it hashes a consumer's tree against that consumer's own MANIFEST - is closed by scanning the source for unlisted root-level *.css, fonts/* and schemas/* and refusing to run. tests/sync-source-boundary.test.mjs pins both directions. It was red on the verbatim script for the right reason (STATE.md present in the target, with tokens.css as the known-positive control) before the boundary was written. --target is the consumer's repo root; the script appends playground/vendor/playground-design-system/ itself. --check verified read-only against both consumers: MANIFEST OK (27 files) for ms-ai-architect and llm-security alike. No re-sync was run, so both MANIFEST.json files are untouched and the stale source_commit is still theirs to inherit at the first real re-sync. README and docs/vendoring-and-re-sync.md now publish the command, and warn that a plain diff/cmp against this repo reports all nine stylesheets as drifted because the sync injects a generated-header line the source does not have - 17/27 raw, 26/27 with line 1 stripped. Found by ms-ai-architect, re-measured here.
136 lines
4.8 KiB
JavaScript
136 lines
4.8 KiB
JavaScript
/**
|
|
* Source-boundary tests for scripts/sync-design-system.mjs.
|
|
*
|
|
* The design system used to live in its own directory inside the marketplace
|
|
* monorepo, so the sync script could copy its whole source tree. After the
|
|
* polyrepo split the source tree is this repo's root, which also holds
|
|
* STATE.md (gitignored, must never reach a public mirror), .git/, docs/ and
|
|
* playground-examples/. These tests pin the boundary: only the delivered
|
|
* files may be vendored.
|
|
*
|
|
* Run with: node --test tests/
|
|
*/
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { promises as fs } from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { execFile } from 'node:child_process';
|
|
import { promisify } from 'node:util';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
const REPO_ROOT = path.resolve(import.meta.dirname, '..');
|
|
const SCRIPT = path.join(REPO_ROOT, 'scripts', 'sync-design-system.mjs');
|
|
const VENDOR_SUBPATH = path.join('playground', 'vendor', 'playground-design-system');
|
|
|
|
const DELIVERED_COUNT = 27;
|
|
|
|
// --target is the PLUGIN root; the script appends playground/vendor/... itself,
|
|
// and refuses to run if the plugin root does not already exist.
|
|
async function makePluginDir() {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'pds-sync-test-'));
|
|
return dir;
|
|
}
|
|
|
|
async function listFiles(dir) {
|
|
const out = [];
|
|
async function walk(current) {
|
|
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
for (const e of entries) {
|
|
const full = path.join(current, e.name);
|
|
if (e.isDirectory()) await walk(full);
|
|
else out.push(path.relative(dir, full));
|
|
}
|
|
}
|
|
await walk(dir);
|
|
return out.sort();
|
|
}
|
|
|
|
async function exists(p) {
|
|
try {
|
|
await fs.stat(p);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
test('sync copies only the delivered files, never the repo apparatus', async (t) => {
|
|
const pluginDir = await makePluginDir();
|
|
t.after(() => fs.rm(pluginDir, { recursive: true, force: true }));
|
|
|
|
await execFileAsync('node', [
|
|
SCRIPT, 'test-plugin',
|
|
'--source', REPO_ROOT,
|
|
'--target', pluginDir,
|
|
]);
|
|
|
|
const vendorDir = path.join(pluginDir, VENDOR_SUBPATH);
|
|
|
|
// Known-positive control: the assertions below are worthless unless this
|
|
// proves the sync actually wrote to the path being inspected.
|
|
assert.ok(
|
|
await exists(path.join(vendorDir, 'tokens.css')),
|
|
'tokens.css must be vendored — without it the leak assertions prove nothing',
|
|
);
|
|
|
|
// The leak this test exists for. STATE.md is gitignored precisely because
|
|
// this repo's remote is public; vendoring it into a consumer publishes it.
|
|
for (const leak of ['STATE.md', '.git', 'playground-examples', 'docs', 'LICENSE', 'SECURITY.md', '.gitignore']) {
|
|
assert.equal(
|
|
await exists(path.join(vendorDir, leak)),
|
|
false,
|
|
`${leak} must not be vendored`,
|
|
);
|
|
}
|
|
|
|
const files = await listFiles(vendorDir);
|
|
assert.equal(
|
|
files.length,
|
|
DELIVERED_COUNT + 1,
|
|
`expected ${DELIVERED_COUNT} delivered files + MANIFEST.json, got ${files.length}`,
|
|
);
|
|
|
|
const manifest = JSON.parse(await fs.readFile(path.join(vendorDir, 'MANIFEST.json'), 'utf8'));
|
|
assert.equal(manifest.file_count, DELIVERED_COUNT);
|
|
});
|
|
|
|
test('sync refuses to run when the source holds a design-system file the allowlist does not name', async (t) => {
|
|
const pluginDir = await makePluginDir();
|
|
const sourceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pds-sync-src-'));
|
|
t.after(() => Promise.all([
|
|
fs.rm(pluginDir, { recursive: true, force: true }),
|
|
fs.rm(sourceDir, { recursive: true, force: true }),
|
|
]));
|
|
|
|
// A minimal but complete source: every delivered file present, empty.
|
|
const { DELIVERED_FILES } = await import(pathToFileURL(SCRIPT).href);
|
|
for (const rel of DELIVERED_FILES) {
|
|
const p = path.join(sourceDir, rel);
|
|
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
await fs.writeFile(p, '', 'utf8');
|
|
}
|
|
|
|
// Baseline: the complete source syncs cleanly. Known-positive control for
|
|
// the failure asserted below.
|
|
await execFileAsync('node', [SCRIPT, 'test-plugin', '--source', sourceDir, '--target', pluginDir]);
|
|
|
|
// A new stylesheet added to the design system but not to the allowlist is
|
|
// the failure mode this repo already lived through once
|
|
// (components-tier4-project-view.css, added v0.6.0, missing from docs until
|
|
// 2026-08-18). It must be loud, not silent: --check hashes target against
|
|
// MANIFEST and would stay green forever.
|
|
await fs.writeFile(path.join(sourceDir, 'components-tier5.css'), '', 'utf8');
|
|
|
|
await assert.rejects(
|
|
execFileAsync('node', [SCRIPT, 'test-plugin', '--source', sourceDir, '--target', pluginDir]),
|
|
(err) => {
|
|
assert.match(err.stderr, /components-tier5\.css/);
|
|
return true;
|
|
},
|
|
'an unlisted stylesheet in the source must fail the sync',
|
|
);
|
|
});
|