@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 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). Every finding carries a stable `ruleId` (e.g. `bpmn/flow`, `dmn/di`,
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as runCli } from "./validate-D5Lo0E8X.js";
2
+ import { a as runCli } from "./validate-Dy2lNoBR.js";
3
3
  //#region src/cli.ts
4
4
  /**
5
5
  * CLI entry for the platform validator. A dedicated entry file (instead of an
@@ -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-Bsv9ZLFd.js";
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": try {
372
- extractModelGraph(opts.notation, raw);
373
- } catch (e) {
374
- err(`not parseable: ${e.message}`);
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-DgyuLYdD.js");
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
- import { a as runCli, i as checkModelBaseline, n as checkDmnXml, r as checkModel, t as checkBpmnXml } from "./validate-D5Lo0E8X.js";
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.0",
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.0",
41
+ "tsdown": "0.23.1",
42
42
  "@designiq/notations": "0.1.0"
43
43
  },
44
44
  "scripts": {
@@ -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 };