playground-design-system/tests/sync-source-boundary.test.mjs
Kjell Tore Guttormsen 7448cec304 feat(sync): restore sync-design-system.mjs with an explicit source boundary
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.
2026-08-27 00:45:58 +02:00

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