From 6a3af70326657f51256957ceb170b52990f8bc56 Mon Sep 17 00:00:00 2001
From: Apoorv <130035517+APOORV7G@users.noreply.github.com>
Date: Wed, 15 Jul 2026 07:44:24 +0530
Subject: [PATCH 1/2] feat(verification): add Mermaid support for Docker-based
verification
- Introduced Mermaid as a new target in the Docker verification setup.
- Updated package.json and Dockerfiles to include Mermaid dependencies and configuration.
- Added a new validation test for Mermaid diagrams to ensure proper parsing.
- Enhanced the verification manifest to include Mermaid as a target.
This commit expands the verification capabilities to include Mermaid, ensuring a consistent environment for testing and validation.
Signed-off-by: Apoorv <130035517+APOORV7G@users.noreply.github.com>
---
.github/workflows/verify-codegen.yml | 1 +
package.json | 3 +
scripts/verification/docker-run.js | 2 +-
test/verification/mermaid.validate.test.js | 86 ++++++++++++++++++++++
verification/docker/mermaid/Dockerfile | 14 ++++
verification/docker/mermaid/entrypoint.sh | 25 +++++++
verification/docker/mermaid/validate.js | 70 ++++++++++++++++++
verification/docker/targets.json | 6 ++
8 files changed, 206 insertions(+), 1 deletion(-)
create mode 100644 test/verification/mermaid.validate.test.js
create mode 100644 verification/docker/mermaid/Dockerfile
create mode 100644 verification/docker/mermaid/entrypoint.sh
create mode 100644 verification/docker/mermaid/validate.js
diff --git a/.github/workflows/verify-codegen.yml b/.github/workflows/verify-codegen.yml
index ebfdabf..2f1d28b 100644
--- a/.github/workflows/verify-codegen.yml
+++ b/.github/workflows/verify-codegen.yml
@@ -30,6 +30,7 @@ jobs:
- rust
- java
- odata
+ - mermaid
steps:
- name: Harden the runner (Audit all outbound calls)
diff --git a/package.json b/package.json
index 595db82..ab2cfc5 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"verify:docker:rust": "node scripts/verification/docker-run.js rust",
"verify:docker:java": "node scripts/verification/docker-run.js java",
"verify:docker:odata": "node scripts/verification/docker-run.js odata",
+ "verify:docker:mermaid": "node scripts/verification/docker-run.js mermaid",
"test:updateSnapshots": "nyc mocha --updateSnapshot --recursive -t 10000",
"test:watch": "nyc mocha --watch --recursive -t 10000",
"mocha": "mocha --recursive -t 10000",
@@ -69,9 +70,11 @@
"expect": "30.2.0",
"graphql": "16.14.2",
"jsdoc": "4.0.3",
+ "jsdom": "25.0.1",
"json-schema-to-ts": "3.1.1",
"license-check-and-add": "2.3.6",
"lockfile-lint": "4.14.1",
+ "mermaid": "11.12.0",
"mocha": "11.3.0",
"mocha-expect-snapshot": "8.0.0",
"moxios": "0.4.0",
diff --git a/scripts/verification/docker-run.js b/scripts/verification/docker-run.js
index 8706089..0ff6ab8 100644
--- a/scripts/verification/docker-run.js
+++ b/scripts/verification/docker-run.js
@@ -20,7 +20,7 @@ const path = require('path');
const ROOT = path.join(__dirname, '../..');
const BASE_IMAGE = 'concerto-verify-base:local';
-const TARGETS = ['typescript', 'jsonschema', 'graphql', 'protobuf', 'csharp', 'rust', 'java', 'odata'];
+const TARGETS = ['typescript', 'jsonschema', 'graphql', 'protobuf', 'csharp', 'rust', 'java', 'odata', 'mermaid'];
/**
* Run a command synchronously with inherited stdio.
diff --git a/test/verification/mermaid.validate.test.js b/test/verification/mermaid.validate.test.js
new file mode 100644
index 0000000..1c7a263
--- /dev/null
+++ b/test/verification/mermaid.validate.test.js
@@ -0,0 +1,86 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const { dir } = require('tmp-promise');
+
+const { FileWriter } = require('@accordproject/concerto-util');
+const MermaidVisitor = require('../../lib/codegen/fromcto/mermaid/mermaidvisitor.js');
+const { parseMermaid } = require('../../verification/docker/mermaid/validate.js');
+
+const {
+ CASES,
+ getSkipReason,
+ createModelManager,
+ applyVerificationEnv,
+} = require('./cases.js');
+
+/**
+ * Generate Mermaid and verify each .mmd parses with mermaid.parse.
+ * @param {ModelManager} modelManager populated model manager
+ * @param {object} [visitorOptions] options passed to MermaidVisitor
+ */
+async function verifyMermaidParses(modelManager, visitorOptions = {}) {
+ const { path: outputDir, cleanup } = await dir({ unsafeCleanup: true });
+
+ try {
+ modelManager.accept(new MermaidVisitor(), {
+ fileWriter: new FileWriter(outputDir),
+ ...visitorOptions,
+ });
+
+ const mmdFiles = fs.readdirSync(outputDir)
+ .filter((name) => name.endsWith('.mmd'))
+ .map((name) => path.join(outputDir, name));
+
+ if (mmdFiles.length === 0) {
+ throw new Error('MermaidVisitor produced no .mmd files');
+ }
+
+ for (const mmd of mmdFiles) {
+ const text = fs.readFileSync(mmd, 'utf-8');
+ try {
+ await parseMermaid(text);
+ } catch (err) {
+ throw new Error(`${path.basename(mmd)}: ${err.message}`);
+ }
+ }
+ } finally {
+ await cleanup();
+ }
+}
+
+describe('verification', function () {
+ this.timeout(60000);
+
+ before(function () {
+ applyVerificationEnv();
+ });
+
+ CASES.forEach(function (testCase) {
+ const skipReason = getSkipReason(testCase, 'mermaid');
+ const title = skipReason
+ ? `generated Mermaid from ${testCase.name} parses (pending: ${skipReason})`
+ : `generated Mermaid from ${testCase.name} parses`;
+ const run = skipReason ? it.skip : it;
+
+ run(title, async function () {
+ const modelManager = createModelManager(testCase);
+ await verifyMermaidParses(modelManager, testCase.visitorOptions || {});
+ });
+ });
+});
diff --git a/verification/docker/mermaid/Dockerfile b/verification/docker/mermaid/Dockerfile
new file mode 100644
index 0000000..3916ce0
--- /dev/null
+++ b/verification/docker/mermaid/Dockerfile
@@ -0,0 +1,14 @@
+ARG BASE_IMAGE=concerto-verify-base:local
+FROM ${BASE_IMAGE}
+
+# Syntax check generated Mermaid (.mmd) via mermaid.parse (+ jsdom for Node).
+# Install next to validate.js so ESM import resolution works (NODE_PATH is CJS-only).
+WORKDIR /opt/mermaid-verify
+RUN npm init -y \
+ && npm install mermaid@11.12.0 jsdom@25.0.1
+
+COPY verification/docker/mermaid/validate.js /opt/mermaid-verify/validate.js
+COPY verification/docker/mermaid/entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/verification/docker/mermaid/entrypoint.sh b/verification/docker/mermaid/entrypoint.sh
new file mode 100644
index 0000000..8780a5e
--- /dev/null
+++ b/verification/docker/mermaid/entrypoint.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Generate Mermaid from the corpus and verify each .mmd parses.
+set -eu
+
+CLI_TARGET=Mermaid
+TARGET_KEY=mermaid
+
+for case_name in $(jq -r '.cases[].name' "${CORPUS_DIR}/manifest.json"); do
+ out="${WORK_DIR}/${case_name}"
+ mkdir -p "$out"
+
+ run-case.sh "$case_name" "$CLI_TARGET" "$TARGET_KEY" "$out"
+
+ mmd_files=$(find "$out" -name '*.mmd' | sort)
+ if [ -z "$mmd_files" ]; then
+ continue
+ fi
+
+ echo "==> VERIFY $case_name with mermaid.parse"
+ # shellcheck disable=SC2086
+ for mmd in $mmd_files; do
+ echo " $mmd"
+ node /opt/mermaid-verify/validate.js "$mmd"
+ done
+done
diff --git a/verification/docker/mermaid/validate.js b/verification/docker/mermaid/validate.js
new file mode 100644
index 0000000..b5db586
--- /dev/null
+++ b/verification/docker/mermaid/validate.js
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const fs = require('fs');
+const { JSDOM } = require('jsdom');
+
+/**
+ * Install a minimal DOM so mermaid.parse can sanitise text under Node.
+ */
+function installDom() {
+ const { window } = new JSDOM('
');
+ global.window = window;
+ global.document = window.document;
+ global.DOMParser = window.DOMParser;
+ global.HTMLElement = window.HTMLElement;
+ global.SVGElement = window.SVGElement;
+ global.Element = window.Element;
+ global.Node = window.Node;
+ global.DocumentFragment = window.DocumentFragment;
+ global.XMLSerializer = window.XMLSerializer;
+ global.getComputedStyle = window.getComputedStyle.bind(window);
+}
+
+/**
+ * Parse a Mermaid diagram definition; throws on invalid syntax.
+ * @param {string} text Mermaid source
+ * @returns {Promise