Files
documentation/scripts/build-diagrams.mjs
jarvis-at-skic 1a2d16bc91
Build and publish Eldorado documentation / build (pull_request) Successful in 1m30s
docs: [verified] answer the six-month AI Architect assignment
2026-10-08 16:59:36 +00:00

88 lines
6.5 KiB
JavaScript

import {readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, renameSync, rmSync} from 'node:fs';
import {join} from 'node:path';
import {execFileSync} from 'node:child_process';
import {skillsViewer, isSkillsStory} from './skills-viewer.mjs';
import {roadmapViewer} from './roadmap-viewer.mjs';
import {targetRouting} from './target-routing.mjs';
import {targetCaption} from './target-caption.mjs';
import {createHash} from 'node:crypto';
const cli = 'vendor/archify/bin/archify.mjs';
const types = new Set(['architecture','workflow','sequence','dataflow','lifecycle']);
const inlineStyles = readFileSync(new URL('./inline-viewer.css', import.meta.url), 'utf8');
const cycleStyles = readFileSync(new URL('./cycle-viewer.css', import.meta.url), 'utf8');
const themeBridge = readFileSync(new URL('./viewer-theme-bridge.js', import.meta.url), 'utf8');
// This directory is generated-only and ignored by Git. Remove stale artifacts.
rmSync('static/diagrams', {recursive: true, force: true});
mkdirSync('static/diagrams', {recursive: true});
mkdirSync('.cache/archify', {recursive: true});
const names = new Set();
for (const file of readdirSync('diagrams').sort()) {
const match = /^([a-z0-9]+(?:-[a-z0-9]+)*)\.([a-z]+)\.json$/.exec(file);
if (!match || !types.has(match[2])) throw new Error(`Unsupported diagram source: ${file}`);
const [,name,type] = match;
if (names.has(name)) throw new Error(`Duplicate diagram output: ${name}`);
names.add(name);
const input = join('diagrams',file);
const spec = JSON.parse(readFileSync(input,'utf8'));
const output = `static/diagrams/${name}/index.html`;
mkdirSync(`static/diagrams/${name}`, {recursive: true});
if (spec.diagram_type !== type || spec.meta.output !== output) throw new Error(`Diagram type/output mismatch: ${file}`);
const run = args => execFileSync(process.execPath,[cli,...args],{stdio:'inherit',env:{...process.env,ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
const roadmap = /^strategy-roadmap(?:-(?:adoption|loop))?(?:-mobile)?$/.test(name);
const skills = isSkillsStory(name);
if (roadmap || skills) {
// Native renderer validates schema and compiles layout. Check readability
// after multiline typography, not upstream's single-line preview.
execFileSync(process.execPath, [`vendor/archify/renderers/${type}/render-${type}.mjs`, input, output], {stdio:'inherit', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}});
writeFileSync(output, (skills ? skillsViewer : roadmapViewer)(readFileSync(output, 'utf8'), spec));
run(['check',output,'--json']);
} else {
run(['validate',type,input,'--quality','showcase','--json']);
run(['deliver',type,input,output,'--quality','showcase','--json']);
run(['check',output,'--json','--require-provenance']);
}
// Verify the upstream artifact first, then add the scoped portal integration.
let viewer = readFileSync(output, 'utf8');
if (name === 'strategy-target') viewer = targetRouting(viewer);
if (/^strategy-target(-mobile)?$/.test(name)) viewer = targetCaption(viewer);
// Upstream intentionally disables preset selection in gallery embeds. Enable
// only those two guards; fail closed if a future vendor update changes them.
const start = viewer.indexOf('Archify.preset = (function () {');
const end = viewer.indexOf('Archify.theme = (function () {', start);
const preset = viewer.slice(start, end);
const guard = "if (html.getAttribute('data-embed') === 'true') return false;";
if (start < 0 || end < start || preset.split(guard).length !== 3) throw new Error('Archify preset integration drift');
viewer = viewer.slice(0, start) + preset.replaceAll(guard, '') + viewer.slice(end);
if (!viewer.includes('</head>') || !viewer.includes('</body>')) throw new Error(`Invalid viewer: ${output}`);
const storyStyles = ['strategy-target', 'strategy-target-mobile'].includes(name)
? `${name === 'strategy-target' ? ':root {--cycle-label: 27px; --cycle-detail: 21px;}' : ':root {--cycle-label: 38px; --cycle-detail: 28px;}'}${cycleStyles}` : '';
if (storyStyles) {
// Keep typography and relationship styles inside exported standalone SVGs.
const diagramRoot = /<svg\b[^>]*role="img"[^>]*>/g;
if ([...viewer.matchAll(diagramRoot)].length !== 1) throw new Error('Cycle SVG integration drift');
const exportStyles = storyStyles.replaceAll(':root', 'svg[role="img"]').replaceAll('.diagram-container > svg', 'svg[role="img"]');
viewer = viewer.replace(diagramRoot, root => `${root}<style id="eldorado-cycle-export">${exportStyles}</style>`);
}
writeFileSync(output, viewer.replace('</head>', `<style id="eldorado-inline-controls">${inlineStyles}</style><style id="eldorado-cycle-labels">${storyStyles}</style></head>`)
.replace('</body>', `<script>${themeBridge}</script></body>`));
if (roadmap || skills) {
// This is a portal receipt, not upstream deliver provenance: formatting
// happens between the native schema/layout compiler and artifact checker.
const hash = path => createHash('sha256').update(readFileSync(path)).digest('hex');
const checked = JSON.parse(execFileSync(process.execPath, [cli,'check',output,'--json'], {encoding:'utf8', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}));
if (!checked.ok || checked.composition?.status !== 'pass') throw new Error('Story final artifact failed');
writeFileSync(`.cache/archify/${name}.portal.json`, JSON.stringify({source:input, sourceSha256:hash(input), adapterSha256:hash(skills ? 'scripts/skills-viewer.mjs' : 'scripts/roadmap-viewer.mjs'), artifactSha256:hash(output), checker:checked}, null, 2));
} else {
renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`);
if (name === 'strategy-target') {
// The native receipt describes the PRE-integration artifact only.
const hash = path => createHash('sha256').update(readFileSync(path)).digest('hex');
const checked = JSON.parse(execFileSync(process.execPath, [cli,'check',output,'--json'], {encoding:'utf8', env:{...process.env, ARCHIFY_UPDATE_CHECK_DISABLED:'1'}}));
if (!checked.ok || checked.composition?.status !== 'pass') throw new Error('Target final artifact failed');
writeFileSync(`.cache/archify/${name}.portal.json`, JSON.stringify({source:input, sourceSha256:hash(input), adapterSha256:hash('scripts/target-routing.mjs'), artifactSha256:hash(output), nativeReceiptScope:'before portal integration', checker:checked}, null, 2));
}
}
}
copyFileSync('vendor/archify/LICENSE','static/diagrams/ARCHIFY-LICENSE.txt');
copyFileSync('vendor/archify/THIRD_PARTY_NOTICES.md','static/diagrams/THIRD-PARTY-NOTICES.md');