@miragon/design-iq-validator 5.4.0 → 5.6.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 +41 -6
- package/dist/cli.js +1 -1
- package/dist/{content-DJ1x-ykR.js → content-Bu1bNrez.js} +16 -25
- package/dist/{content-Dld6ZUbs.js → content-CE4SulnP.js} +1 -1
- package/dist/{validate-Dy2lNoBR.js → validate-BqkEd0ma.js} +513 -36
- package/dist/validate.js +1 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -3,12 +3,47 @@
|
|
|
3
3
|
Deterministic validator for designIQ content repositories: `designiq.yml` discovery, BPMN
|
|
4
4
|
structural checks (flow soundness, complete BPMNDI coverage), DMN structure and DMNDI coverage,
|
|
5
5
|
the generic cross-model reference rule (every required reference — `callActivity` calls,
|
|
6
|
-
`businessRuleTask` decides — must resolve in the repo),
|
|
7
|
-
every other registered notation (a
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`
|
|
11
|
-
|
|
6
|
+
`businessRuleTask` decides — must resolve in the repo), the schema check of the Miragon JSON
|
|
7
|
+
notations, and a per-mediaKind parse baseline for every other registered notation (a file that
|
|
8
|
+
is no JSON or XML is an ERROR; DSL notations are lenient by design; a `mermaid` fence in a
|
|
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). Every finding
|
|
11
|
+
carries a stable `ruleId` (e.g. `bpmn/flow`, `dmn/di`, `refs/dangling`). It treats the target
|
|
12
|
+
repo as pure data — it never executes content-repo code. Exit code 0 = no errors (warnings
|
|
13
|
+
allowed), 1 = errors.
|
|
14
|
+
|
|
15
|
+
## Team Topologies, Context Maps and Value Chains
|
|
16
|
+
|
|
17
|
+
A `.tt`/`.ttm.json`, `.cm.json` or `.vc.json` file is parsed with the schema model its canvas
|
|
18
|
+
opens it with (`@miragon/team-topologies-schema-model`, `@miragon/context-maps-schema-model`,
|
|
19
|
+
`@miragon/value-chain-schema-model`). Whatever the schema model refuses, the canvas cannot open,
|
|
20
|
+
so each refusal is an ERROR (`team-topology/schema`, `context-map/schema`,
|
|
21
|
+
`value-chain/schema`) that names the offending value by its JSON path, e.g.
|
|
22
|
+
`nodes.1.id: duplicate id "a"`. Refused among others:
|
|
23
|
+
|
|
24
|
+
- a duplicate id, and the ids the diagram keeps for itself: `tt-root`, `cm-root`, `vc-root`,
|
|
25
|
+
every id starting with `__implicitroot`, and `assoc_<annotation id>` in a Team Topology;
|
|
26
|
+
- in all three notations, an id that names a built-in JavaScript object property such as
|
|
27
|
+
`constructor`, `toString` or `__proto__`: the canvas keeps its elements in a plain object and
|
|
28
|
+
cannot import it. The Team Topology and Context Map schema models still take these ids, so the
|
|
29
|
+
validator refuses them itself, under the same rule;
|
|
30
|
+
- in a Context Map: a JSON list instead of an object, a `version` newer than 1, coordinates or
|
|
31
|
+
sizes beyond 1,000,000;
|
|
32
|
+
- in a Value Chain: keys other than `x`, `y`, `width`, `height` in `bounds` and other than `x`,
|
|
33
|
+
`y` in a waypoint, coordinates beyond ±1e9, and a `__proto__` key anywhere.
|
|
34
|
+
|
|
35
|
+
A Context Map's strategic-DDD rules (`validateDocument`, e.g. `protect-or-conform`,
|
|
36
|
+
`endpoints-on-map`) are WARNs, `context-map/semantics`, with the rule named at the end of the
|
|
37
|
+
message: the canvas opens such a map and marks the finding. A repeated id is the exception: the
|
|
38
|
+
canvas refuses it, so it is an ERROR (`context-map/schema`) at the path of the repeat, e.g.
|
|
39
|
+
`contexts.2.id: Duplicate id "a". (unique-ids)`.
|
|
40
|
+
|
|
41
|
+
Paths count in the document as the schema model reads it. A Team Topology drops a legacy
|
|
42
|
+
interaction without `position` or `size` before the check, so the index of a later interaction
|
|
43
|
+
counts without it. Some rules only run once the document's shape is valid: the id rules of a Team
|
|
44
|
+
Topology and a Value Chain, and in a Context Map the repeated ids and the strategic-DDD rules.
|
|
45
|
+
Fixing the reported errors can therefore bring up further ones, so check again until the file is
|
|
46
|
+
clean.
|
|
12
47
|
|
|
13
48
|
## Usage
|
|
14
49
|
|
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) {
|
|
@@ -549,20 +537,23 @@ function extractWardley(raw) {
|
|
|
549
537
|
}
|
|
550
538
|
function extractTeamTopology(raw) {
|
|
551
539
|
const data = JSON.parse(raw);
|
|
540
|
+
const list = (v) => Array.isArray(v) ? v : [];
|
|
541
|
+
const teams = list(data.nodes).map((n) => ({
|
|
542
|
+
id: n.id,
|
|
543
|
+
type: n.type ?? "team",
|
|
544
|
+
name: n.label ?? n.name,
|
|
545
|
+
extra: n.description ? { description: n.description } : void 0
|
|
546
|
+
}));
|
|
547
|
+
const interactions = list(data.interactions).filter((i) => i !== null && typeof i === "object" && "position" in i && "size" in i).map((i) => ({
|
|
548
|
+
id: i.id,
|
|
549
|
+
type: "interaction",
|
|
550
|
+
name: i.label,
|
|
551
|
+
extra: { mode: i.mode }
|
|
552
|
+
}));
|
|
552
553
|
return {
|
|
553
554
|
notation: "team-topology",
|
|
554
|
-
nodes:
|
|
555
|
-
|
|
556
|
-
type: n.type ?? "team",
|
|
557
|
-
name: n.label ?? n.name,
|
|
558
|
-
extra: n.description ? { description: n.description } : void 0
|
|
559
|
-
})),
|
|
560
|
-
edges: (data.edges ?? []).map((e, i) => ({
|
|
561
|
-
id: e.id ?? `edge-${i}`,
|
|
562
|
-
from: e.source ?? e.from,
|
|
563
|
-
to: e.target ?? e.to,
|
|
564
|
-
kind: e.interaction ?? e.type ?? "interaction"
|
|
565
|
-
}))
|
|
555
|
+
nodes: [...teams, ...interactions],
|
|
556
|
+
edges: []
|
|
566
557
|
};
|
|
567
558
|
}
|
|
568
559
|
const STORM_ID_PREFIX = {
|
|
@@ -813,7 +804,7 @@ function extractValueChain(raw) {
|
|
|
813
804
|
nodes: (data.elements ?? []).map((el) => ({
|
|
814
805
|
id: el.id,
|
|
815
806
|
type: el.elementType ?? "element",
|
|
816
|
-
name: el.
|
|
807
|
+
name: el.name
|
|
817
808
|
})),
|
|
818
809
|
edges: (data.connections ?? []).map((c, i) => ({
|
|
819
810
|
id: c.id ?? `conn-${i}`,
|
|
@@ -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-Bu1bNrez.js";
|
|
@@ -1,6 +1,9 @@
|
|
|
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-Bu1bNrez.js";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { relative, resolve } from "node:path";
|
|
4
|
+
import { parseDocumentText, validateDocument } from "@miragon/context-maps-schema-model";
|
|
5
|
+
import { parseDocument } from "@miragon/team-topologies-schema-model";
|
|
6
|
+
import { documentIssues } from "@miragon/value-chain-schema-model";
|
|
4
7
|
//#region ../notations/refs.ts
|
|
5
8
|
const REF_EMITTERS = { bpmn: (graph) => {
|
|
6
9
|
const refs = [];
|
|
@@ -84,6 +87,343 @@ const notContentRepoError = (root) => {
|
|
|
84
87
|
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
88
|
};
|
|
86
89
|
//#endregion
|
|
90
|
+
//#region ../notations/markdown.ts
|
|
91
|
+
const NONE = { kind: "none" };
|
|
92
|
+
const PARAGRAPH = { kind: "paragraph" };
|
|
93
|
+
const CODE = { kind: "code" };
|
|
94
|
+
const FENCE_OPEN = /^(`{3,}|~{3,})(.*)$/;
|
|
95
|
+
const THEMATIC_BREAK = /^(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/;
|
|
96
|
+
const ATX_HEADING = /^#{1,6}(?:[ \t]|$)/;
|
|
97
|
+
const SETEXT_UNDERLINE = /^(?:=+|-+)[ \t]*$/;
|
|
98
|
+
const BULLET = /^[-+*](?=[ \t]|$)/;
|
|
99
|
+
const ORDERED = /^(\d{1,9})[.)](?=[ \t]|$)/;
|
|
100
|
+
const BLANK = /^[ \t]*$/;
|
|
101
|
+
/** a link reference definition on one line: [label]: destination "title" (a
|
|
102
|
+
* "[^" label is a footnote, which render.ts keeps as paragraph text; a bare
|
|
103
|
+
* destination ends at a space or a control character, as in markdown-it) */
|
|
104
|
+
const LINK_DEFINITION = /^\[(?!\^)((?:[^[\]\\]|\\.)*)\]:[ \t]*(<(?:[^<>\\]|\\.)*>|[^ <\x00-\x1f\x7f][^ \x00-\x1f\x7f]*)(?:[ \t]+(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\((?:[^()\\]|\\.)*\)))?[ \t]*$/;
|
|
105
|
+
/** the destinations markdown-it's validateLink refuses: no definition then */
|
|
106
|
+
const BAD_LINK = /^(?:vbscript|javascript|file|data):/i;
|
|
107
|
+
const GOOD_DATA_LINK = /^data:image\/(?:gif|png|jpeg|webp);/i;
|
|
108
|
+
/** markdown-it's maxNesting: content nested this deep (a Container's `level`)
|
|
109
|
+
* it does not parse, and drops the rest of the innermost quote around it,
|
|
110
|
+
* or of the document */
|
|
111
|
+
const MAX_NESTING = 100;
|
|
112
|
+
/** start, end and whether it may interrupt a paragraph; an end of /^$/ is the next blank line */
|
|
113
|
+
const HTML_BLOCKS = [
|
|
114
|
+
[
|
|
115
|
+
/^<(?:script|pre|style|textarea)(?=\s|>|$)/i,
|
|
116
|
+
/<\/(?:script|pre|style|textarea)>/i,
|
|
117
|
+
true
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
/^<!--/,
|
|
121
|
+
/-->/,
|
|
122
|
+
true
|
|
123
|
+
],
|
|
124
|
+
[
|
|
125
|
+
/^<\?/,
|
|
126
|
+
/\?>/,
|
|
127
|
+
true
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
/^<![A-Za-z]/,
|
|
131
|
+
/>/,
|
|
132
|
+
true
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
/^<!\[CDATA\[/,
|
|
136
|
+
/\]\]>/,
|
|
137
|
+
true
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
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"),
|
|
141
|
+
/^$/,
|
|
142
|
+
true
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
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*$`),
|
|
146
|
+
/^$/,
|
|
147
|
+
false
|
|
148
|
+
]
|
|
149
|
+
];
|
|
150
|
+
/** the leading blanks of a rest */
|
|
151
|
+
function leading(rest) {
|
|
152
|
+
let col = rest.col;
|
|
153
|
+
let i = 0;
|
|
154
|
+
for (; i < rest.text.length; i++) {
|
|
155
|
+
const ch = rest.text[i];
|
|
156
|
+
if (ch === " ") col++;
|
|
157
|
+
else if (ch === " ") col += 4 - col % 4;
|
|
158
|
+
else break;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
indent: col - rest.col,
|
|
162
|
+
blank: i === rest.text.length
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** the columns of leading blanks */
|
|
166
|
+
const indentOf = (rest) => leading(rest).indent;
|
|
167
|
+
/** drop up to `n` columns of leading blanks; a tab cut in two leaves its other columns as spaces */
|
|
168
|
+
function dropIndent(rest, n) {
|
|
169
|
+
const target = rest.col + n;
|
|
170
|
+
let col = rest.col;
|
|
171
|
+
let i = 0;
|
|
172
|
+
while (i < rest.text.length && col < target) {
|
|
173
|
+
const ch = rest.text[i];
|
|
174
|
+
if (ch === " ") col++;
|
|
175
|
+
else if (ch === " ") {
|
|
176
|
+
const next = col + 4 - col % 4;
|
|
177
|
+
if (next > target) return {
|
|
178
|
+
text: " ".repeat(next - target) + rest.text.slice(i + 1),
|
|
179
|
+
col: target
|
|
180
|
+
};
|
|
181
|
+
col = next;
|
|
182
|
+
} else break;
|
|
183
|
+
i++;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
text: rest.text.slice(i),
|
|
187
|
+
col
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** the line after its indent, when that is at most three columns (more is indented code) */
|
|
191
|
+
function blockStart(rest) {
|
|
192
|
+
const indent = indentOf(rest);
|
|
193
|
+
return indent < 4 ? dropIndent(rest, indent) : void 0;
|
|
194
|
+
}
|
|
195
|
+
/** a quote's ">" and the one column of blank after it: a space, or the first
|
|
196
|
+
* column of a tab (which stays, a column narrower, as in markdown-it) */
|
|
197
|
+
function afterQuoteMarker(at) {
|
|
198
|
+
const rest = {
|
|
199
|
+
text: at.text.slice(1),
|
|
200
|
+
col: at.col + 1
|
|
201
|
+
};
|
|
202
|
+
if (rest.text.startsWith(" ") || rest.text.startsWith(" ") && rest.col % 4 === 3) return {
|
|
203
|
+
text: rest.text.slice(1),
|
|
204
|
+
col: rest.col + 1
|
|
205
|
+
};
|
|
206
|
+
return rest.text.startsWith(" ") ? {
|
|
207
|
+
text: rest.text,
|
|
208
|
+
col: rest.col + 1
|
|
209
|
+
} : rest;
|
|
210
|
+
}
|
|
211
|
+
/** the rest of the line when it continues the container, undefined when the
|
|
212
|
+
* container ends. `lead` is the rest's leading blanks, kept up to date across
|
|
213
|
+
* the columns a list item drops, so a line is not measured once per container */
|
|
214
|
+
function continues(container, rest, lead) {
|
|
215
|
+
if (container.kind === "quote") {
|
|
216
|
+
const at = dropIndent(rest, lead.indent);
|
|
217
|
+
return at.text.startsWith(">") ? afterQuoteMarker(at) : void 0;
|
|
218
|
+
}
|
|
219
|
+
const fresh = container.fresh;
|
|
220
|
+
container.fresh = false;
|
|
221
|
+
if (lead.blank ? fresh : lead.indent < container.indent) return void 0;
|
|
222
|
+
lead.indent = Math.max(0, lead.indent - container.indent);
|
|
223
|
+
return dropIndent(rest, container.indent);
|
|
224
|
+
}
|
|
225
|
+
/** a list item opening at `at` (the line after its indent of `indent` columns),
|
|
226
|
+
* inside containers nested `level` deep */
|
|
227
|
+
function listItem(at, indent, level, interruptsParagraph) {
|
|
228
|
+
const ordered = ORDERED.exec(at.text);
|
|
229
|
+
const marker = ordered ?? BULLET.exec(at.text);
|
|
230
|
+
if (!marker) return void 0;
|
|
231
|
+
const after = {
|
|
232
|
+
text: at.text.slice(marker[0].length),
|
|
233
|
+
col: at.col + marker[0].length
|
|
234
|
+
};
|
|
235
|
+
const empty = BLANK.test(after.text);
|
|
236
|
+
if (interruptsParagraph && (empty || ordered && Number(ordered[1]) !== 1)) return void 0;
|
|
237
|
+
const blanks = indentOf(after);
|
|
238
|
+
const gap = empty || blanks > 4 ? 1 : blanks;
|
|
239
|
+
return {
|
|
240
|
+
item: {
|
|
241
|
+
kind: "item",
|
|
242
|
+
level: level + 2,
|
|
243
|
+
indent: indent + marker[0].length + gap,
|
|
244
|
+
fresh: empty
|
|
245
|
+
},
|
|
246
|
+
rest: dropIndent(after, gap)
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/** the HTML block starting at `text` (a line after its indent), if any */
|
|
250
|
+
function htmlBlock(text) {
|
|
251
|
+
return text.startsWith("<") ? HTML_BLOCKS.find(([start]) => start.test(text)) : void 0;
|
|
252
|
+
}
|
|
253
|
+
/** 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
|
+
function startsBlock(rest) {
|
|
256
|
+
const at = blockStart(rest);
|
|
257
|
+
if (!at) return false;
|
|
258
|
+
const text = at.text;
|
|
259
|
+
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;
|
|
260
|
+
}
|
|
261
|
+
/** whether `text` (a line after its indent) is a link reference definition that
|
|
262
|
+
* fits on it and that markdown-it takes: a label with more than blanks, a
|
|
263
|
+
* destination with balanced parentheses and a scheme its validateLink allows */
|
|
264
|
+
function linkDefinition(text) {
|
|
265
|
+
const definition = LINK_DEFINITION.exec(text);
|
|
266
|
+
if (!definition || definition[1].trim() === "") return false;
|
|
267
|
+
let destination = definition[2];
|
|
268
|
+
if (destination.startsWith("<")) destination = destination.slice(1, -1);
|
|
269
|
+
else {
|
|
270
|
+
let depth = 0;
|
|
271
|
+
for (let i = 0; i < destination.length && depth >= 0; i++) {
|
|
272
|
+
const ch = destination[i];
|
|
273
|
+
if (ch === "\\") i++;
|
|
274
|
+
else if (ch === "(") depth = depth < 32 ? depth + 1 : -1;
|
|
275
|
+
else if (ch === ")") depth--;
|
|
276
|
+
}
|
|
277
|
+
if (depth !== 0) return false;
|
|
278
|
+
}
|
|
279
|
+
destination = destination.trim();
|
|
280
|
+
return !BAD_LINK.test(destination) || GOOD_DATA_LINK.test(destination);
|
|
281
|
+
}
|
|
282
|
+
/** the opening run of a fence at `text` (a line after its indent), if any */
|
|
283
|
+
function fenceOpen(text) {
|
|
284
|
+
const open = FENCE_OPEN.exec(text);
|
|
285
|
+
if (!open) return void 0;
|
|
286
|
+
const run = open[1];
|
|
287
|
+
const info = open[2];
|
|
288
|
+
if (run.startsWith("`") && info.includes("`")) return void 0;
|
|
289
|
+
return {
|
|
290
|
+
char: run[0],
|
|
291
|
+
size: run.length,
|
|
292
|
+
info: info.trim()
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/** whether the line is the closing run of `fence` */
|
|
296
|
+
function closes(rest, fence) {
|
|
297
|
+
const at = blockStart(rest);
|
|
298
|
+
if (!at) return false;
|
|
299
|
+
let n = 0;
|
|
300
|
+
while (at.text[n] === fence.char) n++;
|
|
301
|
+
return n >= fence.size && BLANK.test(at.text.slice(n));
|
|
302
|
+
}
|
|
303
|
+
/** every fenced code block of a Markdown document, in document order */
|
|
304
|
+
function markdownFences(source) {
|
|
305
|
+
const lines = source.split(/\r\n|\r|\n/);
|
|
306
|
+
const brokenLast = BLANK.test(lines[lines.length - 1]);
|
|
307
|
+
if (brokenLast) lines.pop();
|
|
308
|
+
const fences = [];
|
|
309
|
+
const stack = [];
|
|
310
|
+
const level = () => stack.length > 0 ? stack[stack.length - 1].level : 0;
|
|
311
|
+
let leaf = NONE;
|
|
312
|
+
let dropping;
|
|
313
|
+
const finish = (fence, closed) => {
|
|
314
|
+
fences.push({
|
|
315
|
+
line: fence.line,
|
|
316
|
+
endLine: fence.line + fence.content.length + (closed ? 1 : 0),
|
|
317
|
+
info: fence.info,
|
|
318
|
+
lang: (fence.info.split(/\s+/)[0] ?? "").toLowerCase(),
|
|
319
|
+
content: fence.content.join(""),
|
|
320
|
+
closed
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
for (let i = 0; i < lines.length; i++) {
|
|
324
|
+
let rest = {
|
|
325
|
+
text: lines[i],
|
|
326
|
+
col: 0
|
|
327
|
+
};
|
|
328
|
+
const lineBreak = i < lines.length - 1 || brokenLast ? "\n" : "";
|
|
329
|
+
let matched = 0;
|
|
330
|
+
let lead;
|
|
331
|
+
for (const container of stack) {
|
|
332
|
+
const next = continues(container, rest, lead ??= leading(rest));
|
|
333
|
+
if (!next) break;
|
|
334
|
+
rest = next;
|
|
335
|
+
if (container.kind === "quote") lead = void 0;
|
|
336
|
+
matched++;
|
|
337
|
+
}
|
|
338
|
+
const all = matched === stack.length;
|
|
339
|
+
if (dropping) {
|
|
340
|
+
if (all) {
|
|
341
|
+
dropping.lastBlank = BLANK.test(rest.text);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (!dropping.lastBlank && !BLANK.test(rest.text) && !startsBlock(rest)) continue;
|
|
345
|
+
dropping = void 0;
|
|
346
|
+
}
|
|
347
|
+
if (leaf.kind === "fence" && all) {
|
|
348
|
+
if (closes(rest, leaf)) {
|
|
349
|
+
finish(leaf, true);
|
|
350
|
+
leaf = NONE;
|
|
351
|
+
} else leaf.content.push(dropIndent(rest, leaf.indent).text + lineBreak);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (leaf.kind === "html" && all) {
|
|
355
|
+
if (leaf.end.test(dropIndent(rest, indentOf(rest)).text)) leaf = NONE;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (!all) {
|
|
359
|
+
if (leaf.kind === "paragraph" && !BLANK.test(rest.text) && !startsBlock(rest)) continue;
|
|
360
|
+
if (leaf.kind === "fence") finish(leaf, false);
|
|
361
|
+
stack.length = matched;
|
|
362
|
+
leaf = NONE;
|
|
363
|
+
}
|
|
364
|
+
while (level() < MAX_NESTING && !BLANK.test(rest.text)) {
|
|
365
|
+
const at = blockStart(rest);
|
|
366
|
+
if (!at) break;
|
|
367
|
+
if (at.text.startsWith(">")) {
|
|
368
|
+
stack.push({
|
|
369
|
+
kind: "quote",
|
|
370
|
+
level: level() + 1
|
|
371
|
+
});
|
|
372
|
+
rest = afterQuoteMarker(at);
|
|
373
|
+
} else {
|
|
374
|
+
if (THEMATIC_BREAK.test(at.text)) break;
|
|
375
|
+
const opening = listItem(at, indentOf(rest), level(), leaf.kind === "paragraph");
|
|
376
|
+
if (!opening) break;
|
|
377
|
+
stack.push(opening.item);
|
|
378
|
+
rest = opening.rest;
|
|
379
|
+
}
|
|
380
|
+
leaf = NONE;
|
|
381
|
+
}
|
|
382
|
+
if (BLANK.test(rest.text)) {
|
|
383
|
+
if (leaf.kind === "paragraph") leaf = NONE;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (level() >= MAX_NESTING) {
|
|
387
|
+
const quote = stack.findLastIndex((container) => container.kind === "quote");
|
|
388
|
+
if (quote < 0) break;
|
|
389
|
+
stack.length = quote + 1;
|
|
390
|
+
leaf = NONE;
|
|
391
|
+
dropping = { lastBlank: false };
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const at = blockStart(rest);
|
|
395
|
+
if (!at) {
|
|
396
|
+
if (leaf.kind !== "paragraph") leaf = CODE;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const open = fenceOpen(at.text);
|
|
400
|
+
if (open) {
|
|
401
|
+
leaf = {
|
|
402
|
+
kind: "fence",
|
|
403
|
+
...open,
|
|
404
|
+
indent: indentOf(rest),
|
|
405
|
+
line: i + 1,
|
|
406
|
+
content: []
|
|
407
|
+
};
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const html = htmlBlock(at.text);
|
|
411
|
+
if (html && (html[2] || leaf.kind !== "paragraph")) {
|
|
412
|
+
leaf = html[1].test(at.text) ? NONE : {
|
|
413
|
+
kind: "html",
|
|
414
|
+
end: html[1]
|
|
415
|
+
};
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (leaf.kind !== "paragraph" && linkDefinition(at.text)) leaf = NONE;
|
|
419
|
+
else if (ATX_HEADING.test(at.text) || THEMATIC_BREAK.test(at.text)) leaf = NONE;
|
|
420
|
+
else if (leaf.kind === "paragraph" && SETEXT_UNDERLINE.test(at.text)) leaf = NONE;
|
|
421
|
+
else leaf = PARAGRAPH;
|
|
422
|
+
}
|
|
423
|
+
if (leaf.kind === "fence") finish(leaf, false);
|
|
424
|
+
return fences;
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
87
427
|
//#region ../notations/mermaid.ts
|
|
88
428
|
/**
|
|
89
429
|
* Every diagram type MERMAID_VERSION renders, spelled as its header keyword
|
|
@@ -381,7 +721,7 @@ function checkBpmnXml(raw, opts = {}) {
|
|
|
381
721
|
*
|
|
382
722
|
* Deliberately NOT here: FEEL semantics, hit-policy reachability, dead wiring.
|
|
383
723
|
* Those need the FEEL engine and live in @designiq/decisions (`analyze_decision`),
|
|
384
|
-
* which would drag a browser-side dependency into this
|
|
724
|
+
* which would drag a browser-side dependency into this CLI.
|
|
385
725
|
*/
|
|
386
726
|
function checkDmnXml(raw, opts = {}) {
|
|
387
727
|
const file = opts.file ?? "<dmn>";
|
|
@@ -453,34 +793,27 @@ function checkDmnXml(raw, opts = {}) {
|
|
|
453
793
|
const COMMON_MERMAID_TYPES = "flowchart, sequenceDiagram, classDiagram, stateDiagram-v2, erDiagram, gantt, pie, mindmap";
|
|
454
794
|
/**
|
|
455
795
|
* Mermaid's own first step (its type detection, @designiq/notations/mermaid
|
|
456
|
-
* mirrors it)
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* save.
|
|
796
|
+
* mirrors it) for a ```mermaid fence opening on document line `line`: after
|
|
797
|
+
* the front matter, directives and comments, the diagram's first line must
|
|
798
|
+
* start with a diagram type. Light on purpose: the grammar itself needs
|
|
799
|
+
* mermaid and a DOM, which the validator never loads; the Markdown view
|
|
800
|
+
* shows those errors live. The message names the document's line (a Finding
|
|
801
|
+
* has no line of its own).
|
|
463
802
|
*/
|
|
464
|
-
function
|
|
465
|
-
const header = mermaidHeader(
|
|
466
|
-
if (!header) return `empty Mermaid diagram: the first line must name a diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
803
|
+
function mermaidFenceProblem(content, line) {
|
|
804
|
+
const header = mermaidHeader(content);
|
|
805
|
+
if (!header) return `line ${line}: empty Mermaid diagram: the first line must name a diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
467
806
|
if (header.diagramType) return void 0;
|
|
468
807
|
const word = header.text.split(/[\s:;]/, 1)[0];
|
|
469
|
-
return `line ${header.line}: '${word}' is not a Mermaid diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
808
|
+
return `line ${line + header.line}: '${word}' is not a Mermaid diagram type (${COMMON_MERMAID_TYPES}, …)`;
|
|
470
809
|
}
|
|
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
810
|
/**
|
|
478
811
|
* Baseline parse gate for models WITHOUT a dedicated checker (any registered
|
|
479
812
|
* notation that is not .bpmn/.dmn): a discovered model must at minimum parse
|
|
480
813
|
* per its notation's mediaKind — a broken .tt/.vc.json used to pass
|
|
481
814
|
* validation untouched. Pure: no filesystem, no process.exit. Deliberately
|
|
482
|
-
* mechanical: notation-specific structure checks
|
|
483
|
-
* checkers (epic #118, step 3).
|
|
815
|
+
* mechanical: notation-specific structure checks are the per-notation
|
|
816
|
+
* checkers' (epic #118, step 3; checkSchemaModel runs this gate first).
|
|
484
817
|
*/
|
|
485
818
|
function checkModelBaseline(raw, opts) {
|
|
486
819
|
const file = opts.file ?? `<${opts.notation}>`;
|
|
@@ -507,37 +840,181 @@ function checkModelBaseline(raw, opts) {
|
|
|
507
840
|
if (wf !== true) err(`not well-formed XML: ${wf.err.msg}`);
|
|
508
841
|
break;
|
|
509
842
|
}
|
|
510
|
-
case "dsl":
|
|
843
|
+
case "dsl":
|
|
511
844
|
try {
|
|
512
845
|
extractModelGraph(opts.notation, raw);
|
|
513
846
|
} catch (e) {
|
|
514
847
|
err(`not parseable: ${e.message}`);
|
|
515
848
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
849
|
+
break;
|
|
850
|
+
case "markdown": for (const fence of markdownFences(raw)) {
|
|
851
|
+
const problem = fence.lang === "mermaid" ? mermaidFenceProblem(fence.content, fence.line) : void 0;
|
|
852
|
+
if (problem) findings.push({
|
|
519
853
|
severity: "WARN",
|
|
520
|
-
ruleId:
|
|
854
|
+
ruleId: "mermaid/diagram-type",
|
|
521
855
|
file,
|
|
522
856
|
message: problem
|
|
523
857
|
});
|
|
524
|
-
break;
|
|
525
858
|
}
|
|
526
859
|
}
|
|
527
860
|
return findings;
|
|
528
861
|
}
|
|
862
|
+
/** a schema model's issue location as the canvases print it ("nodes.1.id"),
|
|
863
|
+
* "(root)" for the document itself */
|
|
864
|
+
const issuePath = (path) => path.join(".") || "(root)";
|
|
865
|
+
const schemaError = (ruleId, file) => (issue) => ({
|
|
866
|
+
severity: "ERROR",
|
|
867
|
+
ruleId,
|
|
868
|
+
file,
|
|
869
|
+
message: `${issuePath(issue.path)}: ${issue.message}`
|
|
870
|
+
});
|
|
871
|
+
/**
|
|
872
|
+
* The ids a Team Topology or Context Map canvas cannot hold although its
|
|
873
|
+
* schema model takes them: diagram-js keeps its elements in a plain object,
|
|
874
|
+
* so an id that names a built-in object property ("constructor",
|
|
875
|
+
* "__proto__") reads as already added and the import fails. The Value Chain
|
|
876
|
+
* schema model refuses these ids itself; the other two do not yet.
|
|
877
|
+
*/
|
|
878
|
+
function builtInPropertyIds(collections) {
|
|
879
|
+
return Object.entries(collections).flatMap(([collection, elements]) => (Array.isArray(elements) ? elements : []).flatMap((element, index) => {
|
|
880
|
+
const id = element?.id;
|
|
881
|
+
return typeof id === "string" && Object.hasOwn(Object.prototype, id) ? [{
|
|
882
|
+
path: [
|
|
883
|
+
collection,
|
|
884
|
+
index,
|
|
885
|
+
"id"
|
|
886
|
+
],
|
|
887
|
+
message: `the id "${id}" is reserved: it names a built-in property of JavaScript objects`
|
|
888
|
+
}] : [];
|
|
889
|
+
}));
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* The context-map rules the canvas enforces itself: its importer refuses a
|
|
893
|
+
* repeated id (CmImporter.assertImportable), so the file does not open. The
|
|
894
|
+
* other strategic-DDD rules are advisory there: the canvas opens the map and
|
|
895
|
+
* marks the finding, so a save must not refuse it either.
|
|
896
|
+
*/
|
|
897
|
+
const CONTEXT_MAP_REFUSED_RULES = /* @__PURE__ */ new Set(["unique-ids"]);
|
|
898
|
+
/** the path of every repeated id, in the order validateDocument's unique-ids
|
|
899
|
+
* rule reports them (contexts, then relationships): its findings name no
|
|
900
|
+
* position, and an agent must know which of two entries to change */
|
|
901
|
+
function repeatedIdPaths({ contexts, relationships }) {
|
|
902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
903
|
+
return Object.entries({
|
|
904
|
+
contexts,
|
|
905
|
+
relationships
|
|
906
|
+
}).flatMap(([collection, elements]) => elements.flatMap(({ id }, index) => {
|
|
907
|
+
const repeated = seen.has(id);
|
|
908
|
+
seen.add(id);
|
|
909
|
+
return repeated ? [issuePath([
|
|
910
|
+
collection,
|
|
911
|
+
index,
|
|
912
|
+
"id"
|
|
913
|
+
])] : [];
|
|
914
|
+
}));
|
|
915
|
+
}
|
|
916
|
+
function checkTeamTopology(raw, file) {
|
|
917
|
+
const parsed = parseDocument(JSON.parse(raw));
|
|
918
|
+
if (!parsed.ok) return parsed.issues.map(schemaError("team-topology/schema", file));
|
|
919
|
+
const { nodes, interactions, flows, annotations } = parsed.document;
|
|
920
|
+
return builtInPropertyIds({
|
|
921
|
+
nodes,
|
|
922
|
+
interactions,
|
|
923
|
+
flows,
|
|
924
|
+
annotations
|
|
925
|
+
}).map(schemaError("team-topology/schema", file));
|
|
926
|
+
}
|
|
927
|
+
function checkContextMap(raw, file) {
|
|
928
|
+
const parsed = parseDocumentText(raw);
|
|
929
|
+
const issues = parsed.ok ? [] : parsed.issues ?? [{
|
|
930
|
+
path: [],
|
|
931
|
+
message: parsed.error
|
|
932
|
+
}];
|
|
933
|
+
if (issues.some(({ path }) => path.length === 0 || path[0] === "version")) return issues.map(schemaError("context-map/schema", file));
|
|
934
|
+
const { contexts, relationships } = JSON.parse(raw);
|
|
935
|
+
const schemaFindings = [...issues, ...builtInPropertyIds({
|
|
936
|
+
contexts,
|
|
937
|
+
relationships
|
|
938
|
+
})].map(schemaError("context-map/schema", file));
|
|
939
|
+
if (!parsed.ok) return schemaFindings;
|
|
940
|
+
const repeatedIds = repeatedIdPaths(parsed.document);
|
|
941
|
+
const subject = (finding) => {
|
|
942
|
+
const path = finding.rule === "unique-ids" ? repeatedIds.shift() : void 0;
|
|
943
|
+
if (path) return `${path}: `;
|
|
944
|
+
return finding.relationshipId === void 0 ? "" : `relationship "${finding.relationshipId}": `;
|
|
945
|
+
};
|
|
946
|
+
const { errors, warnings } = validateDocument(parsed.document);
|
|
947
|
+
return [...schemaFindings, ...[...errors, ...warnings].map((finding) => {
|
|
948
|
+
const refused = CONTEXT_MAP_REFUSED_RULES.has(finding.rule);
|
|
949
|
+
return {
|
|
950
|
+
severity: refused ? "ERROR" : "WARN",
|
|
951
|
+
ruleId: refused ? "context-map/schema" : "context-map/semantics",
|
|
952
|
+
file,
|
|
953
|
+
message: `${subject(finding)}${finding.message} (${finding.rule})`
|
|
954
|
+
};
|
|
955
|
+
})];
|
|
956
|
+
}
|
|
957
|
+
function checkValueChain(raw, file) {
|
|
958
|
+
return documentIssues(JSON.parse(raw)).map(schemaError("value-chain/schema", file));
|
|
959
|
+
}
|
|
960
|
+
/** each JSON notation's schema model, read the way its canvas reads the file
|
|
961
|
+
* (a Map, not an object: the notation id may be a caller's free text such
|
|
962
|
+
* as "constructor") */
|
|
963
|
+
const SCHEMA_CHECKS = /* @__PURE__ */ new Map([
|
|
964
|
+
["team-topology", {
|
|
965
|
+
ruleId: "team-topology/schema",
|
|
966
|
+
check: checkTeamTopology
|
|
967
|
+
}],
|
|
968
|
+
["context-map", {
|
|
969
|
+
ruleId: "context-map/schema",
|
|
970
|
+
check: checkContextMap
|
|
971
|
+
}],
|
|
972
|
+
["value-chain", {
|
|
973
|
+
ruleId: "value-chain/schema",
|
|
974
|
+
check: checkValueChain
|
|
975
|
+
}]
|
|
976
|
+
]);
|
|
977
|
+
/**
|
|
978
|
+
* The schema check of a Miragon JSON notation (Team Topology, Context Map,
|
|
979
|
+
* Value Chain): the file is parsed with the notation's schema model, the
|
|
980
|
+
* same parse its canvas opens it with, so everything the model refuses (a
|
|
981
|
+
* duplicate or reserved id, a coordinate out of range, a newer version) is
|
|
982
|
+
* an ERROR — a save would otherwise land a file the canvas cannot open.
|
|
983
|
+
* Text that is no JSON keeps the baseline's finding, and a notation without
|
|
984
|
+
* a schema model gets the baseline alone. Pure: no filesystem, no
|
|
985
|
+
* process.exit.
|
|
986
|
+
*/
|
|
987
|
+
function checkSchemaModel(raw, opts) {
|
|
988
|
+
const baseline = checkModelBaseline(raw, opts);
|
|
989
|
+
const schema = SCHEMA_CHECKS.get(opts.notation);
|
|
990
|
+
if (!schema || baseline.length > 0 || byId(opts.notation)?.docShape === "structured") return baseline;
|
|
991
|
+
const file = opts.file ?? `<${opts.notation}>`;
|
|
992
|
+
try {
|
|
993
|
+
return schema.check(raw, file);
|
|
994
|
+
} catch (error) {
|
|
995
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
996
|
+
return [{
|
|
997
|
+
severity: "ERROR",
|
|
998
|
+
ruleId: schema.ruleId,
|
|
999
|
+
file,
|
|
1000
|
+
message: `(root): could not be checked: ${reason}`
|
|
1001
|
+
}];
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
529
1004
|
/**
|
|
530
1005
|
* THE one check dispatch: the platform check for whatever notation `ctx.path`
|
|
531
|
-
* is — full checkers for BPMN/DMN, the
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
* (the file is not a model;
|
|
535
|
-
*
|
|
1006
|
+
* is — full checkers for BPMN/DMN, the schema model for the Miragon JSON
|
|
1007
|
+
* notations, the mediaKind baseline for everything else, plus the generic
|
|
1008
|
+
* dangling-reference rule for ANY notation with a refs emitter; `undefined`
|
|
1009
|
+
* when the path matches no registered notation (the file is not a model;
|
|
1010
|
+
* nothing to check) or `ctx.notation` names none (a mistyped id must not
|
|
1011
|
+
* pass a file unchecked). Consumed by the CLI (runCli) AND the Live Host's
|
|
1012
|
+
* save gate (application/content.ts) so both always agree.
|
|
536
1013
|
*/
|
|
537
1014
|
function checkModel(raw, ctx) {
|
|
538
1015
|
const notation = ctx.notation ?? byExtension(ctx.path)?.id;
|
|
539
|
-
if (!notation) return void 0;
|
|
540
|
-
return [...notation === "bpmn" ? checkBpmnXml(raw, { file: ctx.path }).findings : notation === "dmn" ? checkDmnXml(raw, { file: ctx.path }).findings :
|
|
1016
|
+
if (!notation || !byId(notation)) return void 0;
|
|
1017
|
+
return [...notation === "bpmn" ? checkBpmnXml(raw, { file: ctx.path }).findings : notation === "dmn" ? checkDmnXml(raw, { file: ctx.path }).findings : checkSchemaModel(raw, {
|
|
541
1018
|
file: ctx.path,
|
|
542
1019
|
notation
|
|
543
1020
|
}), ...checkRefs(notation, raw, ctx)];
|
|
@@ -586,7 +1063,7 @@ async function runCli() {
|
|
|
586
1063
|
/** optional single-model filter (a model file stem) */
|
|
587
1064
|
const only = argv[0];
|
|
588
1065
|
const rel = (p) => p.startsWith("/") ? relative(ROOT, p) : p;
|
|
589
|
-
const { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig } = await import("./content-
|
|
1066
|
+
const { CONTENT_CONFIG_FILE, contentConfigConflict, discoverModels, legacyContentConfigFile, loadContentConfig } = await import("./content-CE4SulnP.js");
|
|
590
1067
|
const cfg = loadContentConfig(ROOT);
|
|
591
1068
|
if (!cfg) {
|
|
592
1069
|
console.error(notContentRepoError(ROOT));
|
|
@@ -649,4 +1126,4 @@ async function runCli() {
|
|
|
649
1126
|
process.exit(errorCount === 0 ? 0 : 1);
|
|
650
1127
|
}
|
|
651
1128
|
//#endregion
|
|
652
|
-
export {
|
|
1129
|
+
export { checkSchemaModel as a, checkModelBaseline as i, checkDmnXml as n, runCli as o, checkModel as r, checkBpmnXml as t };
|
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 checkSchemaModel, o as runCli } from "./validate-BqkEd0ma.js";
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miragon/design-iq-validator",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"type": "module",
|
|
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.",
|
|
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",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -32,10 +32,14 @@
|
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
-
"//": "fast-xml-parser stays a RUNTIME dependency although src/ no longer imports it directly: tsdown inlines @designiq/notations (whose extract.ts imports it) but keeps npm deps external, so the published dist/ resolves it from here. Removing it breaks the npm artifact at import time while the workspace keeps working.",
|
|
35
|
+
"//": "fast-xml-parser stays a RUNTIME dependency although src/ no longer imports it directly: tsdown inlines @designiq/notations (whose extract.ts imports it) but keeps npm deps external, so the published dist/ resolves it from here. Removing it breaks the npm artifact at import time while the workspace keeps working. zod is a RUNTIME dependency for the same reason: src/ never imports it, but the team-topologies and context-maps schema models take it as a peer, and the published package is what provides it.",
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@miragon/context-maps-schema-model": "0.3.0",
|
|
38
|
+
"@miragon/team-topologies-schema-model": "0.11.2",
|
|
39
|
+
"@miragon/value-chain-schema-model": "0.5.0",
|
|
37
40
|
"fast-xml-parser": "5.11.2",
|
|
38
|
-
"yaml": "2.9.1"
|
|
41
|
+
"yaml": "2.9.1",
|
|
42
|
+
"zod": "4.6.7"
|
|
39
43
|
},
|
|
40
44
|
"devDependencies": {
|
|
41
45
|
"tsdown": "0.23.1",
|