@miragon/design-iq-validator 5.4.0 → 5.5.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 +3 -2
- package/dist/cli.js +1 -1
- package/dist/{content-DJ1x-ykR.js → content-Bsv9ZLFd.js} +0 -12
- package/dist/{content-Dld6ZUbs.js → content-wjC_epKd.js} +1 -1
- package/dist/{validate-Dy2lNoBR.js → validate-DjtZJTGk.js} +355 -25
- package/dist/validate.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@ 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; a
|
|
9
|
-
`flowchart` or `sequenceDiagram` is a WARN, `mermaid/diagram-type
|
|
8
|
+
lenient by design; a `mermaid` fence in a Markdown document that does not start with a diagram
|
|
9
|
+
type such as `flowchart` or `sequenceDiagram` is a WARN, `mermaid/diagram-type`, naming the
|
|
10
|
+
document's line). Every finding carries a stable `ruleId` (e.g. `bpmn/flow`, `dmn/di`,
|
|
10
11
|
`refs/dangling`). It treats the target repo as pure data — it never executes content-repo code.
|
|
11
12
|
Exit code 0 = no errors (warnings allowed), 1 = errors.
|
|
12
13
|
|
package/dist/cli.js
CHANGED
|
@@ -126,18 +126,6 @@ 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"
|
|
141
129
|
}
|
|
142
130
|
];
|
|
143
131
|
function byId(id) {
|
|
@@ -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-Bsv9ZLFd.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-Bsv9ZLFd.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,343 @@ 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/markdown.ts
|
|
88
|
+
const NONE = { kind: "none" };
|
|
89
|
+
const PARAGRAPH = { kind: "paragraph" };
|
|
90
|
+
const CODE = { kind: "code" };
|
|
91
|
+
const FENCE_OPEN = /^(`{3,}|~{3,})(.*)$/;
|
|
92
|
+
const THEMATIC_BREAK = /^(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/;
|
|
93
|
+
const ATX_HEADING = /^#{1,6}(?:[ \t]|$)/;
|
|
94
|
+
const SETEXT_UNDERLINE = /^(?:=+|-+)[ \t]*$/;
|
|
95
|
+
const BULLET = /^[-+*](?=[ \t]|$)/;
|
|
96
|
+
const ORDERED = /^(\d{1,9})[.)](?=[ \t]|$)/;
|
|
97
|
+
const BLANK = /^[ \t]*$/;
|
|
98
|
+
/** a link reference definition on one line: [label]: destination "title" (a
|
|
99
|
+
* "[^" label is a footnote, which render.ts keeps as paragraph text; a bare
|
|
100
|
+
* destination ends at a space or a control character, as in markdown-it) */
|
|
101
|
+
const LINK_DEFINITION = /^\[(?!\^)((?:[^[\]\\]|\\.)*)\]:[ \t]*(<(?:[^<>\\]|\\.)*>|[^ <\x00-\x1f\x7f][^ \x00-\x1f\x7f]*)(?:[ \t]+(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\((?:[^()\\]|\\.)*\)))?[ \t]*$/;
|
|
102
|
+
/** the destinations markdown-it's validateLink refuses: no definition then */
|
|
103
|
+
const BAD_LINK = /^(?:vbscript|javascript|file|data):/i;
|
|
104
|
+
const GOOD_DATA_LINK = /^data:image\/(?:gif|png|jpeg|webp);/i;
|
|
105
|
+
/** markdown-it's maxNesting: content nested this deep (a Container's `level`)
|
|
106
|
+
* it does not parse, and drops the rest of the innermost quote around it,
|
|
107
|
+
* or of the document */
|
|
108
|
+
const MAX_NESTING = 100;
|
|
109
|
+
/** start, end and whether it may interrupt a paragraph; an end of /^$/ is the next blank line */
|
|
110
|
+
const HTML_BLOCKS = [
|
|
111
|
+
[
|
|
112
|
+
/^<(?:script|pre|style|textarea)(?=\s|>|$)/i,
|
|
113
|
+
/<\/(?:script|pre|style|textarea)>/i,
|
|
114
|
+
true
|
|
115
|
+
],
|
|
116
|
+
[
|
|
117
|
+
/^<!--/,
|
|
118
|
+
/-->/,
|
|
119
|
+
true
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
/^<\?/,
|
|
123
|
+
/\?>/,
|
|
124
|
+
true
|
|
125
|
+
],
|
|
126
|
+
[
|
|
127
|
+
/^<![A-Za-z]/,
|
|
128
|
+
/>/,
|
|
129
|
+
true
|
|
130
|
+
],
|
|
131
|
+
[
|
|
132
|
+
/^<!\[CDATA\[/,
|
|
133
|
+
/\]\]>/,
|
|
134
|
+
true
|
|
135
|
+
],
|
|
136
|
+
[
|
|
137
|
+
new RegExp(`^</?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?=\\s|/?>|$)`, "i"),
|
|
138
|
+
/^$/,
|
|
139
|
+
true
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
new RegExp(`^(?:<[A-Za-z][A-Za-z0-9\\-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\\s*=\\s*(?:[^"'=<>\`\\x00-\\x20]+|'[^']*'|"[^"]*"))?)*\\s*\\/?>|<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>)\\s*$`),
|
|
143
|
+
/^$/,
|
|
144
|
+
false
|
|
145
|
+
]
|
|
146
|
+
];
|
|
147
|
+
/** the leading blanks of a rest */
|
|
148
|
+
function leading(rest) {
|
|
149
|
+
let col = rest.col;
|
|
150
|
+
let i = 0;
|
|
151
|
+
for (; i < rest.text.length; i++) {
|
|
152
|
+
const ch = rest.text[i];
|
|
153
|
+
if (ch === " ") col++;
|
|
154
|
+
else if (ch === " ") col += 4 - col % 4;
|
|
155
|
+
else break;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
indent: col - rest.col,
|
|
159
|
+
blank: i === rest.text.length
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** the columns of leading blanks */
|
|
163
|
+
const indentOf = (rest) => leading(rest).indent;
|
|
164
|
+
/** drop up to `n` columns of leading blanks; a tab cut in two leaves its other columns as spaces */
|
|
165
|
+
function dropIndent(rest, n) {
|
|
166
|
+
const target = rest.col + n;
|
|
167
|
+
let col = rest.col;
|
|
168
|
+
let i = 0;
|
|
169
|
+
while (i < rest.text.length && col < target) {
|
|
170
|
+
const ch = rest.text[i];
|
|
171
|
+
if (ch === " ") col++;
|
|
172
|
+
else if (ch === " ") {
|
|
173
|
+
const next = col + 4 - col % 4;
|
|
174
|
+
if (next > target) return {
|
|
175
|
+
text: " ".repeat(next - target) + rest.text.slice(i + 1),
|
|
176
|
+
col: target
|
|
177
|
+
};
|
|
178
|
+
col = next;
|
|
179
|
+
} else break;
|
|
180
|
+
i++;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
text: rest.text.slice(i),
|
|
184
|
+
col
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/** the line after its indent, when that is at most three columns (more is indented code) */
|
|
188
|
+
function blockStart(rest) {
|
|
189
|
+
const indent = indentOf(rest);
|
|
190
|
+
return indent < 4 ? dropIndent(rest, indent) : void 0;
|
|
191
|
+
}
|
|
192
|
+
/** a quote's ">" and the one column of blank after it: a space, or the first
|
|
193
|
+
* column of a tab (which stays, a column narrower, as in markdown-it) */
|
|
194
|
+
function afterQuoteMarker(at) {
|
|
195
|
+
const rest = {
|
|
196
|
+
text: at.text.slice(1),
|
|
197
|
+
col: at.col + 1
|
|
198
|
+
};
|
|
199
|
+
if (rest.text.startsWith(" ") || rest.text.startsWith(" ") && rest.col % 4 === 3) return {
|
|
200
|
+
text: rest.text.slice(1),
|
|
201
|
+
col: rest.col + 1
|
|
202
|
+
};
|
|
203
|
+
return rest.text.startsWith(" ") ? {
|
|
204
|
+
text: rest.text,
|
|
205
|
+
col: rest.col + 1
|
|
206
|
+
} : rest;
|
|
207
|
+
}
|
|
208
|
+
/** the rest of the line when it continues the container, undefined when the
|
|
209
|
+
* container ends. `lead` is the rest's leading blanks, kept up to date across
|
|
210
|
+
* the columns a list item drops, so a line is not measured once per container */
|
|
211
|
+
function continues(container, rest, lead) {
|
|
212
|
+
if (container.kind === "quote") {
|
|
213
|
+
const at = dropIndent(rest, lead.indent);
|
|
214
|
+
return at.text.startsWith(">") ? afterQuoteMarker(at) : void 0;
|
|
215
|
+
}
|
|
216
|
+
const fresh = container.fresh;
|
|
217
|
+
container.fresh = false;
|
|
218
|
+
if (lead.blank ? fresh : lead.indent < container.indent) return void 0;
|
|
219
|
+
lead.indent = Math.max(0, lead.indent - container.indent);
|
|
220
|
+
return dropIndent(rest, container.indent);
|
|
221
|
+
}
|
|
222
|
+
/** a list item opening at `at` (the line after its indent of `indent` columns),
|
|
223
|
+
* inside containers nested `level` deep */
|
|
224
|
+
function listItem(at, indent, level, interruptsParagraph) {
|
|
225
|
+
const ordered = ORDERED.exec(at.text);
|
|
226
|
+
const marker = ordered ?? BULLET.exec(at.text);
|
|
227
|
+
if (!marker) return void 0;
|
|
228
|
+
const after = {
|
|
229
|
+
text: at.text.slice(marker[0].length),
|
|
230
|
+
col: at.col + marker[0].length
|
|
231
|
+
};
|
|
232
|
+
const empty = BLANK.test(after.text);
|
|
233
|
+
if (interruptsParagraph && (empty || ordered && Number(ordered[1]) !== 1)) return void 0;
|
|
234
|
+
const blanks = indentOf(after);
|
|
235
|
+
const gap = empty || blanks > 4 ? 1 : blanks;
|
|
236
|
+
return {
|
|
237
|
+
item: {
|
|
238
|
+
kind: "item",
|
|
239
|
+
level: level + 2,
|
|
240
|
+
indent: indent + marker[0].length + gap,
|
|
241
|
+
fresh: empty
|
|
242
|
+
},
|
|
243
|
+
rest: dropIndent(after, gap)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** the HTML block starting at `text` (a line after its indent), if any */
|
|
247
|
+
function htmlBlock(text) {
|
|
248
|
+
return text.startsWith("<") ? HTML_BLOCKS.find(([start]) => start.test(text)) : void 0;
|
|
249
|
+
}
|
|
250
|
+
/** whether a line that does not continue a paragraph's containers starts a
|
|
251
|
+
* block of its own (else it is a lazy continuation line of the paragraph) */
|
|
252
|
+
function startsBlock(rest) {
|
|
253
|
+
const at = blockStart(rest);
|
|
254
|
+
if (!at) return false;
|
|
255
|
+
const text = at.text;
|
|
256
|
+
return text.startsWith(">") || fenceOpen(text) !== void 0 || THEMATIC_BREAK.test(text) || BULLET.test(text) || ORDERED.test(text) || ATX_HEADING.test(text) || htmlBlock(text)?.[2] === true;
|
|
257
|
+
}
|
|
258
|
+
/** whether `text` (a line after its indent) is a link reference definition that
|
|
259
|
+
* fits on it and that markdown-it takes: a label with more than blanks, a
|
|
260
|
+
* destination with balanced parentheses and a scheme its validateLink allows */
|
|
261
|
+
function linkDefinition(text) {
|
|
262
|
+
const definition = LINK_DEFINITION.exec(text);
|
|
263
|
+
if (!definition || definition[1].trim() === "") return false;
|
|
264
|
+
let destination = definition[2];
|
|
265
|
+
if (destination.startsWith("<")) destination = destination.slice(1, -1);
|
|
266
|
+
else {
|
|
267
|
+
let depth = 0;
|
|
268
|
+
for (let i = 0; i < destination.length && depth >= 0; i++) {
|
|
269
|
+
const ch = destination[i];
|
|
270
|
+
if (ch === "\\") i++;
|
|
271
|
+
else if (ch === "(") depth = depth < 32 ? depth + 1 : -1;
|
|
272
|
+
else if (ch === ")") depth--;
|
|
273
|
+
}
|
|
274
|
+
if (depth !== 0) return false;
|
|
275
|
+
}
|
|
276
|
+
destination = destination.trim();
|
|
277
|
+
return !BAD_LINK.test(destination) || GOOD_DATA_LINK.test(destination);
|
|
278
|
+
}
|
|
279
|
+
/** the opening run of a fence at `text` (a line after its indent), if any */
|
|
280
|
+
function fenceOpen(text) {
|
|
281
|
+
const open = FENCE_OPEN.exec(text);
|
|
282
|
+
if (!open) return void 0;
|
|
283
|
+
const run = open[1];
|
|
284
|
+
const info = open[2];
|
|
285
|
+
if (run.startsWith("`") && info.includes("`")) return void 0;
|
|
286
|
+
return {
|
|
287
|
+
char: run[0],
|
|
288
|
+
size: run.length,
|
|
289
|
+
info: info.trim()
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/** whether the line is the closing run of `fence` */
|
|
293
|
+
function closes(rest, fence) {
|
|
294
|
+
const at = blockStart(rest);
|
|
295
|
+
if (!at) return false;
|
|
296
|
+
let n = 0;
|
|
297
|
+
while (at.text[n] === fence.char) n++;
|
|
298
|
+
return n >= fence.size && BLANK.test(at.text.slice(n));
|
|
299
|
+
}
|
|
300
|
+
/** every fenced code block of a Markdown document, in document order */
|
|
301
|
+
function markdownFences(source) {
|
|
302
|
+
const lines = source.split(/\r\n|\r|\n/);
|
|
303
|
+
const brokenLast = BLANK.test(lines[lines.length - 1]);
|
|
304
|
+
if (brokenLast) lines.pop();
|
|
305
|
+
const fences = [];
|
|
306
|
+
const stack = [];
|
|
307
|
+
const level = () => stack.length > 0 ? stack[stack.length - 1].level : 0;
|
|
308
|
+
let leaf = NONE;
|
|
309
|
+
let dropping;
|
|
310
|
+
const finish = (fence, closed) => {
|
|
311
|
+
fences.push({
|
|
312
|
+
line: fence.line,
|
|
313
|
+
endLine: fence.line + fence.content.length + (closed ? 1 : 0),
|
|
314
|
+
info: fence.info,
|
|
315
|
+
lang: (fence.info.split(/\s+/)[0] ?? "").toLowerCase(),
|
|
316
|
+
content: fence.content.join(""),
|
|
317
|
+
closed
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
for (let i = 0; i < lines.length; i++) {
|
|
321
|
+
let rest = {
|
|
322
|
+
text: lines[i],
|
|
323
|
+
col: 0
|
|
324
|
+
};
|
|
325
|
+
const lineBreak = i < lines.length - 1 || brokenLast ? "\n" : "";
|
|
326
|
+
let matched = 0;
|
|
327
|
+
let lead;
|
|
328
|
+
for (const container of stack) {
|
|
329
|
+
const next = continues(container, rest, lead ??= leading(rest));
|
|
330
|
+
if (!next) break;
|
|
331
|
+
rest = next;
|
|
332
|
+
if (container.kind === "quote") lead = void 0;
|
|
333
|
+
matched++;
|
|
334
|
+
}
|
|
335
|
+
const all = matched === stack.length;
|
|
336
|
+
if (dropping) {
|
|
337
|
+
if (all) {
|
|
338
|
+
dropping.lastBlank = BLANK.test(rest.text);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (!dropping.lastBlank && !BLANK.test(rest.text) && !startsBlock(rest)) continue;
|
|
342
|
+
dropping = void 0;
|
|
343
|
+
}
|
|
344
|
+
if (leaf.kind === "fence" && all) {
|
|
345
|
+
if (closes(rest, leaf)) {
|
|
346
|
+
finish(leaf, true);
|
|
347
|
+
leaf = NONE;
|
|
348
|
+
} else leaf.content.push(dropIndent(rest, leaf.indent).text + lineBreak);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (leaf.kind === "html" && all) {
|
|
352
|
+
if (leaf.end.test(dropIndent(rest, indentOf(rest)).text)) leaf = NONE;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (!all) {
|
|
356
|
+
if (leaf.kind === "paragraph" && !BLANK.test(rest.text) && !startsBlock(rest)) continue;
|
|
357
|
+
if (leaf.kind === "fence") finish(leaf, false);
|
|
358
|
+
stack.length = matched;
|
|
359
|
+
leaf = NONE;
|
|
360
|
+
}
|
|
361
|
+
while (level() < MAX_NESTING && !BLANK.test(rest.text)) {
|
|
362
|
+
const at = blockStart(rest);
|
|
363
|
+
if (!at) break;
|
|
364
|
+
if (at.text.startsWith(">")) {
|
|
365
|
+
stack.push({
|
|
366
|
+
kind: "quote",
|
|
367
|
+
level: level() + 1
|
|
368
|
+
});
|
|
369
|
+
rest = afterQuoteMarker(at);
|
|
370
|
+
} else {
|
|
371
|
+
if (THEMATIC_BREAK.test(at.text)) break;
|
|
372
|
+
const opening = listItem(at, indentOf(rest), level(), leaf.kind === "paragraph");
|
|
373
|
+
if (!opening) break;
|
|
374
|
+
stack.push(opening.item);
|
|
375
|
+
rest = opening.rest;
|
|
376
|
+
}
|
|
377
|
+
leaf = NONE;
|
|
378
|
+
}
|
|
379
|
+
if (BLANK.test(rest.text)) {
|
|
380
|
+
if (leaf.kind === "paragraph") leaf = NONE;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (level() >= MAX_NESTING) {
|
|
384
|
+
const quote = stack.findLastIndex((container) => container.kind === "quote");
|
|
385
|
+
if (quote < 0) break;
|
|
386
|
+
stack.length = quote + 1;
|
|
387
|
+
leaf = NONE;
|
|
388
|
+
dropping = { lastBlank: false };
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const at = blockStart(rest);
|
|
392
|
+
if (!at) {
|
|
393
|
+
if (leaf.kind !== "paragraph") leaf = CODE;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const open = fenceOpen(at.text);
|
|
397
|
+
if (open) {
|
|
398
|
+
leaf = {
|
|
399
|
+
kind: "fence",
|
|
400
|
+
...open,
|
|
401
|
+
indent: indentOf(rest),
|
|
402
|
+
line: i + 1,
|
|
403
|
+
content: []
|
|
404
|
+
};
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const html = htmlBlock(at.text);
|
|
408
|
+
if (html && (html[2] || leaf.kind !== "paragraph")) {
|
|
409
|
+
leaf = html[1].test(at.text) ? NONE : {
|
|
410
|
+
kind: "html",
|
|
411
|
+
end: html[1]
|
|
412
|
+
};
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (leaf.kind !== "paragraph" && linkDefinition(at.text)) leaf = NONE;
|
|
416
|
+
else if (ATX_HEADING.test(at.text) || THEMATIC_BREAK.test(at.text)) leaf = NONE;
|
|
417
|
+
else if (leaf.kind === "paragraph" && SETEXT_UNDERLINE.test(at.text)) leaf = NONE;
|
|
418
|
+
else leaf = PARAGRAPH;
|
|
419
|
+
}
|
|
420
|
+
if (leaf.kind === "fence") finish(leaf, false);
|
|
421
|
+
return fences;
|
|
422
|
+
}
|
|
423
|
+
//#endregion
|
|
87
424
|
//#region ../notations/mermaid.ts
|
|
88
425
|
/**
|
|
89
426
|
* Every diagram type MERMAID_VERSION renders, spelled as its header keyword
|
|
@@ -453,27 +790,20 @@ function checkDmnXml(raw, opts = {}) {
|
|
|
453
790
|
const COMMON_MERMAID_TYPES = "flowchart, sequenceDiagram, classDiagram, stateDiagram-v2, erDiagram, gantt, pie, mindmap";
|
|
454
791
|
/**
|
|
455
792
|
* Mermaid's own first step (its type detection, @designiq/notations/mermaid
|
|
456
|
-
* mirrors it)
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* save.
|
|
793
|
+
* mirrors it) for a ```mermaid fence opening on document line `line`: after
|
|
794
|
+
* the front matter, directives and comments, the diagram's first line must
|
|
795
|
+
* start with a diagram type. Light on purpose: the grammar itself needs
|
|
796
|
+
* mermaid and a DOM, which the validator never loads; the Markdown view
|
|
797
|
+
* shows those errors live. The message names the document's line (a Finding
|
|
798
|
+
* has no line of its own).
|
|
463
799
|
*/
|
|
464
|
-
function
|
|
465
|
-
const header = mermaidHeader(
|
|
466
|
-
if (!header) return `empty Mermaid diagram: the first line must name a diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
800
|
+
function mermaidFenceProblem(content, line) {
|
|
801
|
+
const header = mermaidHeader(content);
|
|
802
|
+
if (!header) return `line ${line}: empty Mermaid diagram: the first line must name a diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
467
803
|
if (header.diagramType) return void 0;
|
|
468
804
|
const word = header.text.split(/[\s:;]/, 1)[0];
|
|
469
|
-
return `line ${header.line}: '${word}' is not a Mermaid diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
805
|
+
return `line ${line + header.line}: '${word}' is not a Mermaid diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
470
806
|
}
|
|
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
|
-
} };
|
|
477
807
|
/**
|
|
478
808
|
* Baseline parse gate for models WITHOUT a dedicated checker (any registered
|
|
479
809
|
* notation that is not .bpmn/.dmn): a discovered model must at minimum parse
|
|
@@ -507,21 +837,21 @@ function checkModelBaseline(raw, opts) {
|
|
|
507
837
|
if (wf !== true) err(`not well-formed XML: ${wf.err.msg}`);
|
|
508
838
|
break;
|
|
509
839
|
}
|
|
510
|
-
case "dsl":
|
|
840
|
+
case "dsl":
|
|
511
841
|
try {
|
|
512
842
|
extractModelGraph(opts.notation, raw);
|
|
513
843
|
} catch (e) {
|
|
514
844
|
err(`not parseable: ${e.message}`);
|
|
515
845
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
846
|
+
break;
|
|
847
|
+
case "markdown": for (const fence of markdownFences(raw)) {
|
|
848
|
+
const problem = fence.lang === "mermaid" ? mermaidFenceProblem(fence.content, fence.line) : void 0;
|
|
849
|
+
if (problem) findings.push({
|
|
519
850
|
severity: "WARN",
|
|
520
|
-
ruleId:
|
|
851
|
+
ruleId: "mermaid/diagram-type",
|
|
521
852
|
file,
|
|
522
853
|
message: problem
|
|
523
854
|
});
|
|
524
|
-
break;
|
|
525
855
|
}
|
|
526
856
|
}
|
|
527
857
|
return findings;
|
|
@@ -586,7 +916,7 @@ async function runCli() {
|
|
|
586
916
|
/** optional single-model filter (a model file stem) */
|
|
587
917
|
const only = argv[0];
|
|
588
918
|
const rel = (p) => p.startsWith("/") ? relative(ROOT, p) : p;
|
|
589
|
-
const { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig } = await import("./content-
|
|
919
|
+
const { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig } = await import("./content-wjC_epKd.js");
|
|
590
920
|
const cfg = loadContentConfig(ROOT);
|
|
591
921
|
if (!cfg) {
|
|
592
922
|
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 runCli } from "./validate-
|
|
1
|
+
export { t as checkBpmnXml, n as checkDmnXml, r as checkModel, i as checkModelBaseline, a as runCli } from "./validate-DjtZJTGk.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miragon/design-iq-validator",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.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",
|