File size: 2,634 Bytes
0ade7b1 47b8a5f 0ade7b1 47b8a5f 0ade7b1 47b8a5f 0ade7b1 47b8a5f 0ade7b1 47b8a5f 0ade7b1 47b8a5f 0ade7b1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | #!/usr/bin/env node
// Compiles every ```dssl fenced block in GUIDE.md (strict: 0 errors allowed)
// and reports README.md blocks (the README may carry teaching examples that
// intentionally error; those are tagged with <!-- dssl:has-errors -->).
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { parse } from '../public/dssl/parser.js';
import { resolve, DEFAULT_TOKENS } from '../public/dssl/resolver.js';
// paths are relative to the repo, so this works in any checkout
const repo = (f) => fileURLToPath(new URL('../' + f, import.meta.url));
const files = [
{ path: repo('GUIDE.md'), strict: true },
{ path: repo('README.md'), strict: false },
];
let pass = 0, fail = 0;
for (const { path, strict } of files) {
let text;
// a missing file used to be skipped quietly, which let the whole check
// report "0 passed, 0 failed" and look green. A missing file is a failure.
try { text = readFileSync(path, 'utf8'); } catch { console.error('FAIL cannot read ' + path); fail++; continue; }
const blocks = [...text.matchAll(/```dssl\n([\s\S]*?)```/g)].map(m => m[1]);
// whole-file workspace fold: seed with the engine's default tokens, collect
// every token defined anywhere in the document, then compile each block
// against the union. Guides are read non-linearly; examples may reference
// tokens from any example on the page.
let registry = { ...DEFAULT_TOKENS };
for (const src of blocks) {
const { ast, errors: pe } = parse(src, 'tokens-pass');
const doc = resolve(ast, 'tokens-pass', registry);
registry = { ...registry, ...doc.tokens };
}
blocks.forEach((src, i) => {
const name = `${path.split('/').pop()} block ${i + 1}`;
const { ast, errors: parseErrors } = parse(src, name);
const doc = resolve(ast, name, registry);
const errors = [...parseErrors, ...doc.errors];
// raw { } needs real CSS, so a missing semicolon in a documented example is a docs bug
if (strict) for (const w of doc.warnings || []) if (/needs a ";"/.test(w.message)) errors.push({ line: w.line, message: w.message });
if (errors.length === 0) { pass++; return; }
if (!strict && /dssl:has-errors/.test(src)) { pass++; return; }
fail++;
console.error(`FAIL ${name}:`);
for (const e of errors.slice(0, 4)) console.error(' line ' + (e.line ?? '?') + ': ' + e.message);
});
console.log(`${path.split('/').pop()}: ${blocks.length} blocks checked`);
if (blocks.length === 0) { console.error('FAIL no dssl blocks found in ' + path); fail++; }
}
console.log(`${pass} passed, ${fail} failed`);
process.exitCode = fail ? 1 : 0;
|