@miragon/design-iq-validator 5.10.0 → 5.11.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-BjTdVfz8.js → content-2Udu_aBL.js} +27 -3
- package/dist/{content-CAFoHROH.js → content-aBn6QjJK.js} +1 -1
- package/dist/{validate-BWa99fy6.js → validate-HHrcxggb.js} +92 -11
- package/dist/validate.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ the generic cross-model reference rule (every required reference — `callActivi
|
|
|
7
7
|
notations, and a per-mediaKind parse baseline for every other registered notation (a file that
|
|
8
8
|
is no JSON or XML is an ERROR; DSL notations are lenient by design; a `mermaid` fence in a
|
|
9
9
|
Markdown document that does not start with a diagram type such as `flowchart` or
|
|
10
|
-
`sequenceDiagram` is a WARN, `mermaid/diagram-type`, naming the document's line
|
|
10
|
+
`sequenceDiagram` is a WARN, `mermaid/diagram-type`, naming the document's line; another casing
|
|
11
|
+
passes only where mermaid draws it, so `Ishikawa` passes and `Flowchart` warns). Every finding
|
|
11
12
|
carries a stable `ruleId` (e.g. `bpmn/flow`, `dmn/di`, `refs/dangling`). It treats the target
|
|
12
13
|
repo as pure data — it never executes content-repo code. Exit code 0 = no errors (warnings
|
|
13
14
|
allowed), 1 = errors.
|
package/dist/cli.js
CHANGED
|
@@ -545,6 +545,8 @@ const TT_EDGE_INTERACTIONS_VERSION = 1;
|
|
|
545
545
|
function extractTeamTopology(raw) {
|
|
546
546
|
const data = JSON.parse(raw);
|
|
547
547
|
const list = (v) => Array.isArray(v) ? v : [];
|
|
548
|
+
const objects = (v) => list(v).filter((i) => i !== null && typeof i === "object");
|
|
549
|
+
const text = (v) => typeof v === "string" && v !== "" ? v : void 0;
|
|
548
550
|
const teams = list(data.nodes).map((n) => ({
|
|
549
551
|
id: n.id,
|
|
550
552
|
type: n.type ?? "team",
|
|
@@ -552,16 +554,38 @@ function extractTeamTopology(raw) {
|
|
|
552
554
|
extra: n.description ? { description: n.description } : void 0
|
|
553
555
|
}));
|
|
554
556
|
const placed = (i) => "position" in i && "size" in i;
|
|
555
|
-
const interactions =
|
|
557
|
+
const interactions = objects(data.interactions).filter((i) => data.version !== TT_EDGE_INTERACTIONS_VERSION || placed(i)).map((i) => ({
|
|
556
558
|
id: i.id,
|
|
557
559
|
type: "interaction",
|
|
558
560
|
name: i.label,
|
|
559
561
|
extra: { mode: i.mode }
|
|
560
562
|
}));
|
|
563
|
+
const flows = objects(data.flows).map((f) => ({
|
|
564
|
+
id: f.id,
|
|
565
|
+
type: "flow",
|
|
566
|
+
name: text(f.label)
|
|
567
|
+
}));
|
|
568
|
+
const attachable = new Set([
|
|
569
|
+
...teams,
|
|
570
|
+
...interactions,
|
|
571
|
+
...flows
|
|
572
|
+
].map((n) => n.id));
|
|
573
|
+
const annotations = objects(data.annotations).map((a) => ({
|
|
574
|
+
id: a.id,
|
|
575
|
+
type: "annotation",
|
|
576
|
+
name: text(a.text),
|
|
577
|
+
extra: typeof a.attachedTo === "string" && attachable.has(a.attachedTo) ? { attachedTo: a.attachedTo } : void 0
|
|
578
|
+
}));
|
|
561
579
|
return {
|
|
562
580
|
notation: "team-topology",
|
|
563
|
-
nodes: [
|
|
564
|
-
|
|
581
|
+
nodes: [
|
|
582
|
+
...teams,
|
|
583
|
+
...interactions,
|
|
584
|
+
...flows,
|
|
585
|
+
...annotations
|
|
586
|
+
],
|
|
587
|
+
edges: [],
|
|
588
|
+
meta: { title: text(data.title) ?? null }
|
|
565
589
|
};
|
|
566
590
|
}
|
|
567
591
|
const STORM_ID_PREFIX = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as CONTENT_CONFIG_FILE, i as contentConfigConflict, a as discoverModels, o as legacyContentConfigFile, s as loadContentConfig } from "./content-
|
|
1
|
+
export { t as CONTENT_CONFIG_FILE, i as contentConfigConflict, a as discoverModels, o as legacyContentConfigFile, s as loadContentConfig } from "./content-2Udu_aBL.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-2Udu_aBL.js";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { relative, resolve } from "node:path";
|
|
4
4
|
import { parseDocumentText, serializeDocumentText, validateDocument } from "@miragon/context-maps-schema-model";
|
|
@@ -98,9 +98,14 @@ const SETEXT_UNDERLINE = /^(?:=+|-+)[ \t]*$/;
|
|
|
98
98
|
const BULLET = /^[-+*](?=[ \t]|$)/;
|
|
99
99
|
const ORDERED = /^(\d{1,9})[.)](?=[ \t]|$)/;
|
|
100
100
|
const BLANK = /^[ \t]*$/;
|
|
101
|
+
/** a footnote definition's marker: a label without blanks */
|
|
102
|
+
const FOOTNOTE_DEFINITION = /^\[\^[^\] ]+\]:/;
|
|
103
|
+
/** the columns a footnote definition's content is indented by past the definition */
|
|
104
|
+
const FOOTNOTE_INDENT = 4;
|
|
101
105
|
/** a link reference definition on one line: [label]: destination "title" (a
|
|
102
|
-
* "[^" label is a footnote,
|
|
103
|
-
* destination ends at a space or a control character,
|
|
106
|
+
* "[^" label is a footnote definition, or with a blank in it paragraph text
|
|
107
|
+
* to render.ts; a bare destination ends at a space or a control character,
|
|
108
|
+
* as in markdown-it) */
|
|
104
109
|
const LINK_DEFINITION = /^\[(?!\^)((?:[^[\]\\]|\\.)*)\]:[ \t]*(<(?:[^<>\\]|\\.)*>|[^ <\x00-\x1f\x7f][^ \x00-\x1f\x7f]*)(?:[ \t]+(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\((?:[^()\\]|\\.)*\)))?[ \t]*$/;
|
|
105
110
|
/** the destinations markdown-it's validateLink refuses: no definition then */
|
|
106
111
|
const BAD_LINK = /^(?:vbscript|javascript|file|data):/i;
|
|
@@ -216,6 +221,11 @@ function continues(container, rest, lead) {
|
|
|
216
221
|
const at = dropIndent(rest, lead.indent);
|
|
217
222
|
return at.text.startsWith(">") ? afterQuoteMarker(at) : void 0;
|
|
218
223
|
}
|
|
224
|
+
if (container.kind === "footnote") {
|
|
225
|
+
if (!lead.blank && lead.indent < FOOTNOTE_INDENT) return void 0;
|
|
226
|
+
lead.indent = Math.max(0, lead.indent - FOOTNOTE_INDENT);
|
|
227
|
+
return dropIndent(rest, FOOTNOTE_INDENT);
|
|
228
|
+
}
|
|
219
229
|
const fresh = container.fresh;
|
|
220
230
|
container.fresh = false;
|
|
221
231
|
if (lead.blank ? fresh : lead.indent < container.indent) return void 0;
|
|
@@ -250,9 +260,25 @@ function listItem(at, indent, level, interruptsParagraph) {
|
|
|
250
260
|
function htmlBlock(text) {
|
|
251
261
|
return text.startsWith("<") ? HTML_BLOCKS.find(([start]) => start.test(text)) : void 0;
|
|
252
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* The content of a footnote definition's first line, `at` the line after its
|
|
265
|
+
* indent: the text after the colon. markdown-it-footnote indents it by the
|
|
266
|
+
* blanks after the colon, less `indent` (the columns the definition's content
|
|
267
|
+
* is indented by in the innermost quote) once there are that many.
|
|
268
|
+
*/
|
|
269
|
+
function footnoteContent(at, marker, indent) {
|
|
270
|
+
const after = {
|
|
271
|
+
text: at.text.slice(marker.length),
|
|
272
|
+
col: at.col + marker.length
|
|
273
|
+
};
|
|
274
|
+
return indentOf(after) < indent ? after : dropIndent(after, indent);
|
|
275
|
+
}
|
|
253
276
|
/** whether a line that does not continue a paragraph's containers starts a
|
|
254
|
-
* block of its own (else it is a lazy continuation line of the paragraph)
|
|
255
|
-
|
|
277
|
+
* block of its own (else it is a lazy continuation line of the paragraph).
|
|
278
|
+
* A footnote definition does, whatever its indent, but not in a quote it
|
|
279
|
+
* does not continue: a quote reads it as a lazy line */
|
|
280
|
+
function startsBlock(rest, quoted) {
|
|
281
|
+
if (!quoted && FOOTNOTE_DEFINITION.test(dropIndent(rest, indentOf(rest)).text)) return true;
|
|
256
282
|
const at = blockStart(rest);
|
|
257
283
|
if (!at) return false;
|
|
258
284
|
const text = at.text;
|
|
@@ -308,6 +334,16 @@ function markdownFences(source) {
|
|
|
308
334
|
const fences = [];
|
|
309
335
|
const stack = [];
|
|
310
336
|
const level = () => stack.length > 0 ? stack[stack.length - 1].level : 0;
|
|
337
|
+
/** the columns the innermost container's content is indented by, counted
|
|
338
|
+
* from the innermost quote (inside a quote markdown-it counts from its ">") */
|
|
339
|
+
const contentIndent = () => {
|
|
340
|
+
let indent = 0;
|
|
341
|
+
for (let i = stack.length - 1; i >= 0 && stack[i].kind !== "quote"; i--) {
|
|
342
|
+
const container = stack[i];
|
|
343
|
+
indent += container.kind === "item" ? container.indent : FOOTNOTE_INDENT;
|
|
344
|
+
}
|
|
345
|
+
return indent;
|
|
346
|
+
};
|
|
311
347
|
let leaf = NONE;
|
|
312
348
|
let dropping;
|
|
313
349
|
const finish = (fence, closed) => {
|
|
@@ -341,7 +377,7 @@ function markdownFences(source) {
|
|
|
341
377
|
dropping.lastBlank = BLANK.test(rest.text);
|
|
342
378
|
continue;
|
|
343
379
|
}
|
|
344
|
-
if (!dropping.lastBlank && !BLANK.test(rest.text) && !startsBlock(rest)) continue;
|
|
380
|
+
if (!dropping.lastBlank && !BLANK.test(rest.text) && !startsBlock(rest, true)) continue;
|
|
345
381
|
dropping = void 0;
|
|
346
382
|
}
|
|
347
383
|
if (leaf.kind === "fence" && all) {
|
|
@@ -356,7 +392,8 @@ function markdownFences(source) {
|
|
|
356
392
|
continue;
|
|
357
393
|
}
|
|
358
394
|
if (!all) {
|
|
359
|
-
|
|
395
|
+
const quoted = stack.slice(matched).some((container) => container.kind === "quote");
|
|
396
|
+
if (leaf.kind === "paragraph" && !BLANK.test(rest.text) && !startsBlock(rest, quoted)) continue;
|
|
360
397
|
if (leaf.kind === "fence") finish(leaf, false);
|
|
361
398
|
stack.length = matched;
|
|
362
399
|
leaf = NONE;
|
|
@@ -364,12 +401,19 @@ function markdownFences(source) {
|
|
|
364
401
|
while (level() < MAX_NESTING && !BLANK.test(rest.text)) {
|
|
365
402
|
const at = blockStart(rest);
|
|
366
403
|
if (!at) break;
|
|
404
|
+
const footnote = FOOTNOTE_DEFINITION.exec(at.text)?.[0];
|
|
367
405
|
if (at.text.startsWith(">")) {
|
|
368
406
|
stack.push({
|
|
369
407
|
kind: "quote",
|
|
370
408
|
level: level() + 1
|
|
371
409
|
});
|
|
372
410
|
rest = afterQuoteMarker(at);
|
|
411
|
+
} else if (footnote !== void 0) {
|
|
412
|
+
stack.push({
|
|
413
|
+
kind: "footnote",
|
|
414
|
+
level: level() + 1
|
|
415
|
+
});
|
|
416
|
+
rest = footnoteContent(at, footnote, contentIndent());
|
|
373
417
|
} else {
|
|
374
418
|
if (THEMATIC_BREAK.test(at.text)) break;
|
|
375
419
|
const opening = listItem(at, indentOf(rest), level(), leaf.kind === "paragraph");
|
|
@@ -496,8 +540,44 @@ const KEEPS_COMMENTS = /^(?:eventmodeling|agentflow-beta\b)/;
|
|
|
496
540
|
/** mermaid's default maxTextSize, which apps/web keeps (a secure key): past
|
|
497
541
|
* it, mermaid parses even those types without their comment lines */
|
|
498
542
|
const MAX_TEXT_SIZE = 5e4;
|
|
499
|
-
/**
|
|
500
|
-
|
|
543
|
+
/**
|
|
544
|
+
* The header keywords MERMAID_VERSION also draws in another casing, each with
|
|
545
|
+
* the part its detector matches as written. Past that part, the detector
|
|
546
|
+
* ignores case (src/diagrams/ishikawa/ishikawaDetector.ts) or matches no
|
|
547
|
+
* further (src/diagrams/requirement/requirementDetector.ts stops at
|
|
548
|
+
* `requirement`), and the grammar's lexer ignores case. Every other keyword
|
|
549
|
+
* draws as listed only: its detector or its grammar reads it as written
|
|
550
|
+
* (wardley-beta's and the railroad types' detectors ignore case, but their
|
|
551
|
+
* grammars refuse `Wardley-beta`).
|
|
552
|
+
*/
|
|
553
|
+
const ANY_CASE_AFTER = /* @__PURE__ */ new Map([
|
|
554
|
+
["ishikawa", ""],
|
|
555
|
+
["ishikawa-beta", ""],
|
|
556
|
+
["requirementDiagram", "requirement"],
|
|
557
|
+
["stateDiagram-v2", "stateDiagram"],
|
|
558
|
+
["xychart-beta", "xychart"],
|
|
559
|
+
["sankey-beta", "sankey"]
|
|
560
|
+
]);
|
|
561
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
|
|
562
|
+
/** `text` in any casing, a character class per letter: the `i` flag would
|
|
563
|
+
* cover every keyword, and not every browser reads an inline (?i:…) group */
|
|
564
|
+
const anyCase = (text) => [...text].map((character) => character.toLowerCase() === character.toUpperCase() ? escapeRegExp(character) : `[${character.toLowerCase()}${character.toUpperCase()}]`).join("");
|
|
565
|
+
/**
|
|
566
|
+
* The header keyword `type` (a MERMAID_DIAGRAM_TYPES entry) in every casing
|
|
567
|
+
* MERMAID_VERSION draws it in, as the source of a regular expression without
|
|
568
|
+
* flags or anchors. mermaidHeader reads a header with it, and so does the web
|
|
569
|
+
* app's source editor (apps/web/src/lib/mermaid-monarch.ts): the two never
|
|
570
|
+
* disagree about a header.
|
|
571
|
+
*/
|
|
572
|
+
function mermaidKeywordPattern(type) {
|
|
573
|
+
const asWritten = ANY_CASE_AFTER.get(type) ?? type;
|
|
574
|
+
return escapeRegExp(asWritten) + anyCase(type.slice(asWritten.length));
|
|
575
|
+
}
|
|
576
|
+
/** a header keyword in a casing mermaid draws, followed by the end of the
|
|
577
|
+
* line, a space, ":" or ";" */
|
|
578
|
+
const HEADER = new RegExp(`^(?:${MERMAID_DIAGRAM_TYPES.map(mermaidKeywordPattern).join("|")})(?=$|[\\s:;])`);
|
|
579
|
+
/** the listed keyword of a header keyword HEADER read in another casing */
|
|
580
|
+
const LISTED_BY_LOWER_CASE = new Map(MERMAID_DIAGRAM_TYPES.map((type) => [type.toLowerCase(), type]));
|
|
501
581
|
/** mermaid's cleanupText: line breaks to \n, tag attributes single-quoted;
|
|
502
582
|
* the line structure stays, so the source lines can be matched against it */
|
|
503
583
|
function cleanup(source) {
|
|
@@ -580,10 +660,11 @@ function mermaidHeader(source) {
|
|
|
580
660
|
const index = headerIndex(lines);
|
|
581
661
|
if (index < 0) return void 0;
|
|
582
662
|
const text = lines[index].trim();
|
|
663
|
+
const keyword = HEADER.exec(text)?.[0];
|
|
583
664
|
return {
|
|
584
665
|
line: mermaidSourceLine(source, index + 1),
|
|
585
666
|
text,
|
|
586
|
-
diagramType:
|
|
667
|
+
diagramType: keyword === void 0 ? void 0 : LISTED_BY_LOWER_CASE.get(keyword.toLowerCase())
|
|
587
668
|
};
|
|
588
669
|
}
|
|
589
670
|
//#endregion
|
|
@@ -1053,7 +1134,7 @@ async function runCli() {
|
|
|
1053
1134
|
/** optional single-model filter (a model file stem) */
|
|
1054
1135
|
const only = argv[0];
|
|
1055
1136
|
const rel = (p) => p.startsWith("/") ? relative(ROOT, p) : p;
|
|
1056
|
-
const { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig } = await import("./content-
|
|
1137
|
+
const { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig } = await import("./content-aBn6QjJK.js");
|
|
1057
1138
|
const cfg = loadContentConfig(ROOT);
|
|
1058
1139
|
if (!cfg) {
|
|
1059
1140
|
console.error(notContentRepoError(ROOT));
|
package/dist/validate.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as checkBpmnXml, n as checkDmnXml, r as checkModel, i as checkModelBaseline, a as checkSchemaModel, o as runCli } from "./validate-
|
|
1
|
+
export { t as checkBpmnXml, n as checkDmnXml, r as checkModel, i as checkModelBaseline, a as checkSchemaModel, o as runCli } from "./validate-HHrcxggb.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miragon/design-iq-validator",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.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, the schema check of Team Topologies, Context Maps and Value Chains, and a parse check for every other notation. Runs against any checkout via --root; never executes content-repo code.",
|
|
6
6
|
"license": "MIT",
|