@miragon/design-iq-validator 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli.js +1 -1
- package/dist/{content-Bsv9ZLFd.js → content-DJ1x-ykR.js} +12 -0
- package/dist/content-Dld6ZUbs.js +1 -0
- package/dist/{validate-D5Lo0E8X.js → validate-Dy2lNoBR.js} +156 -6
- package/dist/validate.js +1 -2
- package/package.json +2 -2
- package/dist/content-DgyuLYdD.js +0 -2
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@ structural checks (flow soundness, complete BPMNDI coverage), DMN structure and
|
|
|
5
5
|
the generic cross-model reference rule (every required reference — `callActivity` calls,
|
|
6
6
|
`businessRuleTask` decides — must resolve in the repo), and a per-mediaKind parse baseline for
|
|
7
7
|
every other registered notation (a broken `.tt`/`.vc.json` is an ERROR; DSL notations are
|
|
8
|
-
lenient by design
|
|
8
|
+
lenient by design; a Mermaid diagram that does not start with a diagram type such as
|
|
9
|
+
`flowchart` or `sequenceDiagram` is a WARN, `mermaid/diagram-type`). Every finding carries a stable `ruleId` (e.g. `bpmn/flow`, `dmn/di`,
|
|
9
10
|
`refs/dangling`). It treats the target repo as pure data — it never executes content-repo code.
|
|
10
11
|
Exit code 0 = no errors (warnings allowed), 1 = errors.
|
|
11
12
|
|
package/dist/cli.js
CHANGED
|
@@ -126,6 +126,18 @@ const NOTATIONS = [
|
|
|
126
126
|
mediaKind: "markdown",
|
|
127
127
|
docShape: "text",
|
|
128
128
|
monacoLanguage: "markdown"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "mermaid",
|
|
132
|
+
label: "Mermaid",
|
|
133
|
+
noun: {
|
|
134
|
+
singular: "diagram",
|
|
135
|
+
plural: "diagrams"
|
|
136
|
+
},
|
|
137
|
+
extensions: [".mmd", ".mermaid"],
|
|
138
|
+
mediaKind: "dsl",
|
|
139
|
+
docShape: "text",
|
|
140
|
+
monacoLanguage: "plaintext"
|
|
129
141
|
}
|
|
130
142
|
];
|
|
131
143
|
function byId(id) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as CONTENT_CONFIG_FILE, i as contentConfigConflict, a as discoverModels, o as legacyContentConfigFile, s as loadContentConfig } from "./content-DJ1x-ykR.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as byId, c as resolveContentConfigFile, d as extractModelGraph, f as parseXml, g as byExtension, h as kindOf, l as XMLValidator, m as requirementHref, p as readBpmn, r as CONTENT_CONFIG_NAMES, u as asArray } from "./content-
|
|
1
|
+
import { _ as byId, c as resolveContentConfigFile, d as extractModelGraph, f as parseXml, g as byExtension, h as kindOf, l as XMLValidator, m as requirementHref, p as readBpmn, r as CONTENT_CONFIG_NAMES, u as asArray } from "./content-DJ1x-ykR.js";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { relative, resolve } from "node:path";
|
|
4
4
|
//#region ../notations/refs.ts
|
|
@@ -84,6 +84,120 @@ const notContentRepoError = (root) => {
|
|
|
84
84
|
return found === void 0 ? `[ERROR] ${root}: no ${CONTENT_CONFIG_NAMES} at the root — not a content repo (or wrong --root)` : `[ERROR] ${root}: ${found} at the root names no models folder — not a content repo (or wrong --root)`;
|
|
85
85
|
};
|
|
86
86
|
//#endregion
|
|
87
|
+
//#region ../notations/mermaid.ts
|
|
88
|
+
/**
|
|
89
|
+
* Every diagram type MERMAID_VERSION renders, spelled as its header keyword
|
|
90
|
+
* (its diagram detectors, minus the spellings the detector matches but the
|
|
91
|
+
* grammar then refuses: a bare `architecture` or `requirement`). zenuml is
|
|
92
|
+
* an external plugin mermaid does not bundle, so it is not here.
|
|
93
|
+
*/
|
|
94
|
+
const MERMAID_DIAGRAM_TYPES = [
|
|
95
|
+
"flowchart",
|
|
96
|
+
"flowchart-elk",
|
|
97
|
+
"graph",
|
|
98
|
+
"sequenceDiagram",
|
|
99
|
+
"classDiagram",
|
|
100
|
+
"classDiagram-v2",
|
|
101
|
+
"stateDiagram",
|
|
102
|
+
"stateDiagram-v2",
|
|
103
|
+
"erDiagram",
|
|
104
|
+
"journey",
|
|
105
|
+
"gantt",
|
|
106
|
+
"pie",
|
|
107
|
+
"quadrantChart",
|
|
108
|
+
"requirementDiagram",
|
|
109
|
+
"gitGraph",
|
|
110
|
+
"C4Context",
|
|
111
|
+
"C4Container",
|
|
112
|
+
"C4Component",
|
|
113
|
+
"C4Dynamic",
|
|
114
|
+
"C4Deployment",
|
|
115
|
+
"mindmap",
|
|
116
|
+
"timeline",
|
|
117
|
+
"sankey",
|
|
118
|
+
"sankey-beta",
|
|
119
|
+
"xychart",
|
|
120
|
+
"xychart-beta",
|
|
121
|
+
"block",
|
|
122
|
+
"block-beta",
|
|
123
|
+
"packet",
|
|
124
|
+
"packet-beta",
|
|
125
|
+
"kanban",
|
|
126
|
+
"architecture-beta",
|
|
127
|
+
"radar-beta",
|
|
128
|
+
"treemap",
|
|
129
|
+
"treemap-beta",
|
|
130
|
+
"info",
|
|
131
|
+
"swimlane-beta",
|
|
132
|
+
"treeView-beta",
|
|
133
|
+
"eventmodeling",
|
|
134
|
+
"ishikawa",
|
|
135
|
+
"ishikawa-beta",
|
|
136
|
+
"venn-beta",
|
|
137
|
+
"wardley-beta",
|
|
138
|
+
"cynefin-beta",
|
|
139
|
+
"railroad-beta",
|
|
140
|
+
"railroad-ebnf-beta",
|
|
141
|
+
"railroad-abnf-beta",
|
|
142
|
+
"railroad-peg-beta"
|
|
143
|
+
];
|
|
144
|
+
const FRONT_MATTER = /^([^\S\n\r]*)-{3}\s*[\n\r](.*?)[\n\r]\1-{3}\s*[\n\r]+/s;
|
|
145
|
+
const DIRECTIVE = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
|
146
|
+
const COMMENT_LINE = /^\s*%%(?!{)[^\n]+\n?/gm;
|
|
147
|
+
/** a header keyword, followed by the end of the line, a space, ":" or ";" */
|
|
148
|
+
const HEADER = new RegExp(`^(?:${MERMAID_DIAGRAM_TYPES.map((t) => t.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&")).join("|")})(?=$|[\\s:;])`);
|
|
149
|
+
/** mermaid's cleanupText: line breaks to \n, tag attributes single-quoted;
|
|
150
|
+
* the line structure stays, so the source lines can be matched against it */
|
|
151
|
+
function cleanup(source) {
|
|
152
|
+
return source.replace(/\r\n?/g, "\n").replace(/<(\w+)([^>]*)>/g, (_tag, name, attrs) => `<${name}${attrs.replace(/="([^"]*)"/g, "='$1'")}>`);
|
|
153
|
+
}
|
|
154
|
+
/** The text mermaid's diagram parser reads: the source after mermaid's
|
|
155
|
+
* preprocess (front matter, directives and comment lines removed, leading
|
|
156
|
+
* whitespace trimmed). */
|
|
157
|
+
function mermaidCode(source) {
|
|
158
|
+
return cleanup(source).replace(FRONT_MATTER, "").replace(DIRECTIVE, "").replace(COMMENT_LINE, "").trimStart();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The 1-based source line of the 1-based line `codeLine` of mermaidCode(source).
|
|
162
|
+
* The preprocess only removes lines (and joins the two ends of a directive
|
|
163
|
+
* that spans lines), so the code is the source's lines in order: each code
|
|
164
|
+
* line is matched to the next source line with the same text. A line the
|
|
165
|
+
* preprocess rewrote matches nothing and takes the next line; a line past the
|
|
166
|
+
* end clamps to the last one.
|
|
167
|
+
*/
|
|
168
|
+
function mermaidSourceLine(source, codeLine) {
|
|
169
|
+
const cleaned = cleanup(source);
|
|
170
|
+
const sourceLines = cleaned.split("\n").map((l) => l.trim());
|
|
171
|
+
const codeLines = mermaidCode(source).split("\n");
|
|
172
|
+
let next = (cleaned.match(FRONT_MATTER)?.[0] ?? "").split("\n").length - 1;
|
|
173
|
+
let at = next;
|
|
174
|
+
for (let i = 0; i < Math.min(codeLine, codeLines.length); i++) {
|
|
175
|
+
const text = codeLines[i].trim();
|
|
176
|
+
let k = next;
|
|
177
|
+
while (k < sourceLines.length && sourceLines[k] !== text) k++;
|
|
178
|
+
at = k < sourceLines.length ? k : next;
|
|
179
|
+
next = at + 1;
|
|
180
|
+
}
|
|
181
|
+
return Math.min(at, sourceLines.length - 1) + 1;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The line that names the diagram type: the first line of mermaidCode that
|
|
185
|
+
* is neither blank nor a bare `%%` comment (mermaid's type detection skips
|
|
186
|
+
* those too). undefined when there is none: the source is empty, or nothing
|
|
187
|
+
* but front matter, directives and comments.
|
|
188
|
+
*/
|
|
189
|
+
function mermaidHeader(source) {
|
|
190
|
+
const lines = mermaidCode(source).split("\n");
|
|
191
|
+
const index = lines.findIndex((l) => l.trim() !== "" && !l.trim().startsWith("%%"));
|
|
192
|
+
if (index < 0) return void 0;
|
|
193
|
+
const text = lines[index].trim();
|
|
194
|
+
return {
|
|
195
|
+
line: mermaidSourceLine(source, index + 1),
|
|
196
|
+
text,
|
|
197
|
+
diagramType: HEADER.exec(text)?.[0]
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
87
201
|
//#region src/validate.ts
|
|
88
202
|
/**
|
|
89
203
|
* Deterministic validation of a content repository (the slim contract).
|
|
@@ -335,6 +449,31 @@ function checkDmnXml(raw, opts = {}) {
|
|
|
335
449
|
else for (const id of diRequired) if (!di.has(id)) err("dmn/di", `${id} has no DMNDI shape (breaks the visual editor)`);
|
|
336
450
|
return { findings };
|
|
337
451
|
}
|
|
452
|
+
/** the Mermaid types the header message names: the common ones, not every one */
|
|
453
|
+
const COMMON_MERMAID_TYPES = "flowchart, sequenceDiagram, classDiagram, stateDiagram-v2, erDiagram, gantt, pie, mindmap";
|
|
454
|
+
/**
|
|
455
|
+
* Mermaid's own first step (its type detection, @designiq/notations/mermaid
|
|
456
|
+
* mirrors it): after the front matter, directives and comments, the first
|
|
457
|
+
* line must start with a diagram type. Light on purpose: the grammar itself
|
|
458
|
+
* needs mermaid and a DOM, which the validator never loads; the web viewer
|
|
459
|
+
* shows those errors live. A WARN, not an ERROR: .mmd files a content repo
|
|
460
|
+
* already keeps (snippets, a zenuml diagram mermaid does not bundle) turn
|
|
461
|
+
* up as models with this notation, and must neither fail its CI nor block a
|
|
462
|
+
* save.
|
|
463
|
+
*/
|
|
464
|
+
function mermaidHeaderProblem(raw) {
|
|
465
|
+
const header = mermaidHeader(raw);
|
|
466
|
+
if (!header) return `empty Mermaid diagram: the first line must name a diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
467
|
+
if (header.diagramType) return void 0;
|
|
468
|
+
const word = header.text.split(/[\s:;]/, 1)[0];
|
|
469
|
+
return `line ${header.line}: '${word}' is not a Mermaid diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
470
|
+
}
|
|
471
|
+
/** notation id → the check of its DSL's header line (a WARN under its own
|
|
472
|
+
* rule id); a DSL without one stays as lenient as its extractor */
|
|
473
|
+
const DSL_HEADER_CHECKS = { mermaid: {
|
|
474
|
+
ruleId: "mermaid/diagram-type",
|
|
475
|
+
problem: mermaidHeaderProblem
|
|
476
|
+
} };
|
|
338
477
|
/**
|
|
339
478
|
* Baseline parse gate for models WITHOUT a dedicated checker (any registered
|
|
340
479
|
* notation that is not .bpmn/.dmn): a discovered model must at minimum parse
|
|
@@ -368,10 +507,21 @@ function checkModelBaseline(raw, opts) {
|
|
|
368
507
|
if (wf !== true) err(`not well-formed XML: ${wf.err.msg}`);
|
|
369
508
|
break;
|
|
370
509
|
}
|
|
371
|
-
case "dsl":
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
510
|
+
case "dsl": {
|
|
511
|
+
try {
|
|
512
|
+
extractModelGraph(opts.notation, raw);
|
|
513
|
+
} catch (e) {
|
|
514
|
+
err(`not parseable: ${e.message}`);
|
|
515
|
+
}
|
|
516
|
+
const header = DSL_HEADER_CHECKS[opts.notation];
|
|
517
|
+
const problem = header?.problem(raw);
|
|
518
|
+
if (header && problem) findings.push({
|
|
519
|
+
severity: "WARN",
|
|
520
|
+
ruleId: header.ruleId,
|
|
521
|
+
file,
|
|
522
|
+
message: problem
|
|
523
|
+
});
|
|
524
|
+
break;
|
|
375
525
|
}
|
|
376
526
|
}
|
|
377
527
|
return findings;
|
|
@@ -436,7 +586,7 @@ async function runCli() {
|
|
|
436
586
|
/** optional single-model filter (a model file stem) */
|
|
437
587
|
const only = argv[0];
|
|
438
588
|
const rel = (p) => p.startsWith("/") ? relative(ROOT, p) : p;
|
|
439
|
-
const { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig } = await import("./content-
|
|
589
|
+
const { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig } = await import("./content-Dld6ZUbs.js");
|
|
440
590
|
const cfg = loadContentConfig(ROOT);
|
|
441
591
|
if (!cfg) {
|
|
442
592
|
console.error(notContentRepoError(ROOT));
|
package/dist/validate.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { checkBpmnXml, checkDmnXml, checkModel, checkModelBaseline, runCli };
|
|
1
|
+
export { t as checkBpmnXml, n as checkDmnXml, r as checkModel, i as checkModelBaseline, a as runCli } from "./validate-Dy2lNoBR.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miragon/design-iq-validator",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Platform-owned validator for designIQ content repos — designiq.yml discovery, BPMN/DMN structure and diagram coverage, cross-model references, and a parse check for every other notation. Runs against any checkout via --root; never executes content-repo code.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"yaml": "2.9.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"tsdown": "0.23.
|
|
41
|
+
"tsdown": "0.23.1",
|
|
42
42
|
"@designiq/notations": "0.1.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
package/dist/content-DgyuLYdD.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { a as discoverModels, i as contentConfigConflict, o as legacyContentConfigFile, s as loadContentConfig, t as CONTENT_CONFIG_FILE } from "./content-Bsv9ZLFd.js";
|
|
2
|
-
export { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig };
|