diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fc2d7798..f36935a4 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -81,6 +81,13 @@ jobs:
- name: Build app
run: npm run build
+ # The MCP package ships a trimmed draw.io; this drives the full copy
+ # (public/drawio, downloaded by the build) in Chromium and fails when
+ # the editor requests a file its list does not have. Once is enough.
+ - name: Check the MCP draw.io file list
+ if: matrix.shard == 1
+ run: npm --prefix packages/mcp-server run check-drawio
+
- name: Run E2E tests
run: npm run test:e2e -- --shard=${{ matrix.shard }}/6
env:
diff --git a/packages/mcp-server/drawio-files.txt b/packages/mcp-server/drawio-files.txt
index 5afded21..12264fd6 100644
--- a/packages/mcp-server/drawio-files.txt
+++ b/packages/mcp-server/drawio-files.txt
@@ -3,15 +3,19 @@
# a path segment, `**` across segments.
#
# Always included: the language files, the image icon libraries (loaded one
-# icon at a time, as diagrams use them), the stylesheets, and the licenses of
-# the icon sets.
+# icon at a time, as diagrams use them), the stylesheets, the licenses of the
+# icon sets, the templates of Insert > Template (the guard only sees the
+# previews it scrolled to) and the PlantUML parser of Insert > Advanced.
+images/osa_drive-harddisk.png
img/LICENSE
img/lib/**
+js/plantuml/drawio-plantuml.min.js
mxgraph/css/**
resources/dia*.txt
shapes/LICENSE
stencils/LICENSE
styles/**
+templates/**
# Requested by the editor; rewritten by scripts/check-drawio-files.mjs --update
images/droptarget.png
diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json
index 7919c195..1c4433bb 100644
--- a/packages/mcp-server/package.json
+++ b/packages/mcp-server/package.json
@@ -9,6 +9,7 @@
},
"scripts": {
"build": "tsc && node scripts/copy-assets.mjs && node scripts/fetch-drawio.mjs",
+ "check-drawio": "node scripts/check-drawio-files.mjs",
"check-package": "node scripts/check-package.mjs",
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js",
diff --git a/packages/mcp-server/scripts/check-drawio-files.mjs b/packages/mcp-server/scripts/check-drawio-files.mjs
index 480692d0..733871ac 100644
--- a/packages/mcp-server/scripts/check-drawio-files.mjs
+++ b/packages/mcp-server/scripts/check-drawio-files.mjs
@@ -23,6 +23,7 @@ import {
import http from "node:http"
import path from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url"
+import { readDrawioVersion } from "../../../scripts/drawio-zip.mjs"
import {
formatFileList,
parseFileList,
@@ -44,6 +45,18 @@ if (!existsSync(path.join(drawioDir, "index.html"))) {
)
process.exit(2)
}
+// The list is for the pinned release; the fetch scripts stamp the copy
+const { version } = readDrawioVersion()
+const stampFile = path.join(drawioDir, ".version")
+const stamp = existsSync(stampFile)
+ ? readFileSync(stampFile, "utf8").trim()
+ : null
+if (stamp !== version) {
+ console.error(
+ `${drawioDir} is draw.io ${stamp ?? "(no .version stamp)"}; the package pins ${version}. Delete it and run "npm run dev" at the repository root again, or pass --drawio
.`,
+ )
+ process.exit(2)
+}
const playwrightEntry = path.join(
PKG,
"../../node_modules/playwright/index.mjs",
@@ -172,9 +185,9 @@ const MATH_DOC = doc(
)
// Dialogs and toggles, by draw.io action name (Actions.js); each is closed
-// again right after it opened. Left out on purpose, to keep the package
-// small: Insert > Template (5.6 MB of templates and previews) and the
-// Insert > Advanced tools that load code when used (Mermaid, PlantUML).
+// again right after it opened. Insert > Template and Insert > Advanced >
+// PlantUML, which load files when used, are driven below. Left out on
+// purpose, to keep the package small: Mermaid (it loads its own code).
const ACTIONS = [
"editData",
"editDiagram",
@@ -408,16 +421,48 @@ async function drive(browser, ui, full) {
report("page tabs")
if (full) {
- for (const format of ["png", "svg", "xmlsvg", "xml"]) {
+ // Insert > Advanced > PlantUML: the parser is loaded on first use
+ await frame.evaluate(() => window.__ui.actions.get("plantUml").funct())
+ await frame
+ .locator(".geDialog textarea")
+ .first()
+ .fill("@startuml\nA -> B\n@enduml")
+ await frame
+ .locator('.geDialog button:text-is("Insert")')
+ .first()
+ .click()
+ await wait(page, 3000)
+ report("plantUml insert")
+ await closeAll()
+
+ // Insert > Template: the index and the previews on screen
+ await frame.evaluate(() =>
+ window.__ui.actions.get("insertTemplate").funct(),
+ )
+ await wait(page, 2000)
+ report("template dialog")
+ await closeAll()
+
+ const formats = ["png", "svg", "xmlsvg", "xml"]
+ for (const [i, format] of formats.entries()) {
await page.evaluate(
(f) => window.send({ action: "export", format: f, scale: 2 }),
format,
)
- await wait(page, 1500)
+ await page
+ .waitForFunction((n) => window.exports >= n, i + 1, {
+ timeout: 10000,
+ })
+ .catch(() => {})
report(`export ${format}`)
}
const exports = await page.evaluate(() => window.exports)
- if (exports < 4) console.log(` only ${exports} of 4 exports answered`)
+ if (exports < formats.length) {
+ console.error(
+ ` only ${exports} of ${formats.length} exports answered`,
+ )
+ process.exitCode = 1
+ }
}
await page.close()
}
@@ -430,10 +475,12 @@ try {
server.close()
}
+// A full copy has every file the editor asks for; 404s mean this is not one
if (notFound.size > 0) {
- console.log(
- `\nNot in the full copy either (ignored): ${[...notFound].join(", ")}`,
+ console.error(
+ `\nThe editor requested ${notFound.size} files that ${drawioDir} does not have (not a full draw.io copy?):\n ${[...notFound].join("\n ")}`,
)
+ process.exit(1)
}
// Compare with the list
diff --git a/packages/mcp-server/scripts/check-package.mjs b/packages/mcp-server/scripts/check-package.mjs
index 992233d5..274b2fda 100644
--- a/packages/mcp-server/scripts/check-package.mjs
+++ b/packages/mcp-server/scripts/check-package.mjs
@@ -13,9 +13,9 @@ const REQUIRED = [
"dist/drawio/js/app.min.js",
"dist/drawio/LICENSE",
]
-// The bundled draw.io is most of the package; a jump means the file list
-// grew more than intended
-const MAX_TARBALL_BYTES = 40 * 1024 * 1024
+// The bundled draw.io is most of the package (15.1 MB packed with draw.io
+// v32.0.2); a jump means the file list grew more than intended
+const MAX_TARBALL_BYTES = 20 * 1024 * 1024
const output = JSON.parse(
execSync("npm pack --dry-run --json", { encoding: "utf8" }),