119 lines
3.6 KiB
JavaScript
119 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { chromium } from "playwright";
|
|
|
|
const sourcePath = path.resolve(
|
|
process.argv[2] || "docs/architecture/hmm-ai-data-access-architecture.drawio",
|
|
);
|
|
const outputDir = path.resolve(process.argv[3] || path.dirname(sourcePath));
|
|
const source = fs.readFileSync(sourcePath, "utf8");
|
|
const diagrams = [...source.matchAll(
|
|
/<diagram\b[^>]*name="([^"]+)"[^>]*>[\s\S]*?<\/diagram>/g,
|
|
)];
|
|
|
|
if (diagrams.length !== 2) {
|
|
throw new Error(`Expected 2 Draw.io pages, found ${diagrams.length}`);
|
|
}
|
|
|
|
const outputs = [
|
|
"hmm-ai-data-access-architecture-overview",
|
|
"hmm-ai-data-access-architecture-security-flow",
|
|
];
|
|
const embedUrl = "https://embed.diagrams.net/?embed=1&ui=min&spin=1&proto=json";
|
|
|
|
function singlePageMxfile(diagram) {
|
|
return [
|
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
'<mxfile host="app.diagrams.net" compressed="false">',
|
|
diagram,
|
|
"</mxfile>",
|
|
].join("");
|
|
}
|
|
|
|
function decodeDataUrl(data, mimeType) {
|
|
const base64Prefix = `data:${mimeType};base64,`;
|
|
if (data.startsWith(base64Prefix)) {
|
|
return Buffer.from(data.slice(base64Prefix.length), "base64");
|
|
}
|
|
const utf8Prefix = `data:${mimeType},`;
|
|
if (data.startsWith(utf8Prefix)) {
|
|
return Buffer.from(decodeURIComponent(data.slice(utf8Prefix.length)), "utf8");
|
|
}
|
|
throw new Error(`Unexpected diagrams.net export prefix: ${data.slice(0, 80)}`);
|
|
}
|
|
|
|
async function exportPage(browser, xml, format) {
|
|
const page = await browser.newPage({ viewport: { width: 1600, height: 1000 } });
|
|
const html = `<!doctype html>
|
|
<meta charset="utf-8">
|
|
<script>
|
|
window.inputXml = ${JSON.stringify(xml)};
|
|
window.exportResult = null;
|
|
window.exportError = null;
|
|
window.addEventListener("message", (event) => {
|
|
let message = event.data;
|
|
try {
|
|
if (typeof message === "string") message = JSON.parse(message);
|
|
} catch (_error) {
|
|
return;
|
|
}
|
|
if (!message || !message.event) return;
|
|
if (message.event === "init") {
|
|
event.source.postMessage(JSON.stringify({
|
|
action: "load",
|
|
xml: window.inputXml,
|
|
autosave: 0
|
|
}), "*");
|
|
} else if (message.event === "load") {
|
|
event.source.postMessage(JSON.stringify({
|
|
action: "export",
|
|
format: ${JSON.stringify(format)},
|
|
xml: window.inputXml,
|
|
border: 20,
|
|
scale: 1
|
|
}), "*");
|
|
} else if (message.event === "export") {
|
|
window.exportResult = message.data;
|
|
} else if (message.event === "error") {
|
|
window.exportError = JSON.stringify(message);
|
|
}
|
|
});
|
|
</script>
|
|
<iframe title="diagrams.net exporter" style="width:100vw;height:100vh;border:0"
|
|
src="${embedUrl}"></iframe>`;
|
|
await page.setContent(html, { waitUntil: "domcontentloaded" });
|
|
await page.waitForFunction(
|
|
() => window.exportResult || window.exportError,
|
|
null,
|
|
{ timeout: 120000 },
|
|
);
|
|
const result = await page.evaluate(() => ({
|
|
data: window.exportResult,
|
|
error: window.exportError,
|
|
}));
|
|
await page.close();
|
|
if (result.error) {
|
|
throw new Error(result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
const browser = await chromium.launch({ headless: true });
|
|
try {
|
|
for (let index = 0; index < diagrams.length; index += 1) {
|
|
const xml = singlePageMxfile(diagrams[index][0]);
|
|
for (const format of ["svg", "png"]) {
|
|
const data = await exportPage(browser, xml, format);
|
|
const mimeType = format === "svg" ? "image/svg+xml" : "image/png";
|
|
const output = path.join(outputDir, `${outputs[index]}.${format}`);
|
|
fs.writeFileSync(output, decodeDataUrl(data, mimeType));
|
|
console.log(output);
|
|
}
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|