Files
vpd-permission-poc/tools/architecture/export-drawio.mjs
2026-08-04 14:37:38 +09:00

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();
}