@diagrammo/dgmo 0.8.3 → 0.8.5
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/.claude/commands/dgmo-diagram-this.md +60 -0
- package/.claude/commands/dgmo-document-project.md +128 -0
- package/.claude/commands/dgmo.md +452 -50
- package/.cursorrules +32 -37
- package/.github/copilot-instructions.md +35 -44
- package/.windsurfrules +32 -37
- package/README.md +4 -4
- package/dist/cli.cjs +188 -185
- package/dist/editor.cjs +338 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +27 -0
- package/dist/editor.d.ts +27 -0
- package/dist/editor.js +307 -0
- package/dist/editor.js.map +1 -0
- package/dist/highlight.cjs +560 -0
- package/dist/highlight.cjs.map +1 -0
- package/dist/highlight.d.cts +32 -0
- package/dist/highlight.d.ts +32 -0
- package/dist/highlight.js +530 -0
- package/dist/highlight.js.map +1 -0
- package/dist/index.cjs +3467 -1078
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +3466 -1078
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +46 -37
- package/gallery/fixtures/arc.dgmo +18 -0
- package/gallery/fixtures/area.dgmo +19 -0
- package/gallery/fixtures/bar-stacked.dgmo +10 -0
- package/gallery/fixtures/bar.dgmo +10 -0
- package/gallery/fixtures/c4-full.dgmo +52 -0
- package/gallery/fixtures/c4.dgmo +17 -0
- package/gallery/fixtures/chord.dgmo +12 -0
- package/gallery/fixtures/class-basic.dgmo +14 -0
- package/gallery/fixtures/class-full.dgmo +43 -0
- package/gallery/fixtures/doughnut.dgmo +8 -0
- package/gallery/fixtures/flowchart-basic.dgmo +3 -0
- package/gallery/fixtures/flowchart-colors.dgmo +5 -0
- package/gallery/fixtures/flowchart-complex.dgmo +17 -0
- package/gallery/fixtures/flowchart-decision.dgmo +5 -0
- package/gallery/fixtures/flowchart-full.dgmo +13 -0
- package/gallery/fixtures/flowchart-groups.dgmo +10 -0
- package/gallery/fixtures/flowchart-loop.dgmo +7 -0
- package/gallery/fixtures/flowchart-nested.dgmo +7 -0
- package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
- package/gallery/fixtures/function.dgmo +8 -0
- package/gallery/fixtures/funnel.dgmo +7 -0
- package/gallery/fixtures/gantt-full.dgmo +49 -0
- package/gallery/fixtures/gantt.dgmo +42 -0
- package/gallery/fixtures/heatmap.dgmo +8 -0
- package/gallery/fixtures/infra-full.dgmo +78 -0
- package/gallery/fixtures/infra-overload.dgmo +25 -0
- package/gallery/fixtures/infra.dgmo +47 -0
- package/gallery/fixtures/initiative-status-full.dgmo +46 -0
- package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
- package/gallery/fixtures/initiative-status.dgmo +9 -0
- package/gallery/fixtures/line.dgmo +19 -0
- package/gallery/fixtures/multi-line.dgmo +11 -0
- package/gallery/fixtures/org-basic.dgmo +16 -0
- package/gallery/fixtures/org-full.dgmo +69 -0
- package/gallery/fixtures/org-teams.dgmo +25 -0
- package/gallery/fixtures/pie.dgmo +9 -0
- package/gallery/fixtures/polar-area.dgmo +8 -0
- package/gallery/fixtures/quadrant.dgmo +18 -0
- package/gallery/fixtures/radar.dgmo +8 -0
- package/gallery/fixtures/sankey.dgmo +31 -0
- package/gallery/fixtures/scatter.dgmo +21 -0
- package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
- package/gallery/fixtures/sequence-tags.dgmo +41 -0
- package/gallery/fixtures/sequence.dgmo +35 -0
- package/gallery/fixtures/sitemap-basic.dgmo +12 -0
- package/gallery/fixtures/sitemap-full.dgmo +156 -0
- package/gallery/fixtures/slope.dgmo +9 -0
- package/gallery/fixtures/spr-eras.dgmo +62 -0
- package/gallery/fixtures/state.dgmo +30 -0
- package/gallery/fixtures/timeline-intraday.dgmo +14 -0
- package/gallery/fixtures/timeline.dgmo +32 -0
- package/gallery/fixtures/venn.dgmo +10 -0
- package/gallery/fixtures/wordcloud.dgmo +24 -0
- package/package.json +71 -2
- package/src/c4/layout.ts +372 -90
- package/src/c4/parser.ts +100 -55
- package/src/chart.ts +91 -28
- package/src/class/parser.ts +41 -12
- package/src/cli.ts +211 -62
- package/src/completion.ts +378 -183
- package/src/d3.ts +1044 -303
- package/src/dgmo-mermaid.ts +16 -13
- package/src/dgmo-router.ts +69 -23
- package/src/echarts.ts +646 -153
- package/src/editor/dgmo.grammar +69 -0
- package/src/editor/dgmo.grammar.d.ts +2 -0
- package/src/editor/dgmo.grammar.js +18 -0
- package/src/editor/dgmo.grammar.terms.d.ts +5 -0
- package/src/editor/dgmo.grammar.terms.js +35 -0
- package/src/editor/highlight-api.ts +444 -0
- package/src/editor/highlight.ts +36 -0
- package/src/editor/index.ts +28 -0
- package/src/editor/keywords.ts +222 -0
- package/src/editor/tokens.ts +30 -0
- package/src/er/parser.ts +48 -14
- package/src/er/renderer.ts +112 -53
- package/src/gantt/calculator.ts +91 -29
- package/src/gantt/parser.ts +197 -71
- package/src/gantt/renderer.ts +1120 -350
- package/src/graph/flowchart-parser.ts +46 -25
- package/src/graph/state-parser.ts +47 -17
- package/src/index.ts +96 -31
- package/src/infra/parser.ts +157 -53
- package/src/infra/renderer.ts +723 -271
- package/src/initiative-status/parser.ts +138 -44
- package/src/kanban/parser.ts +25 -14
- package/src/org/layout.ts +111 -44
- package/src/org/parser.ts +69 -22
- package/src/palettes/index.ts +3 -2
- package/src/sequence/parser.ts +193 -61
- package/src/sitemap/parser.ts +65 -29
- package/src/utils/arrows.ts +2 -22
- package/src/utils/duration.ts +39 -21
- package/src/utils/legend-constants.ts +0 -2
- package/src/utils/parsing.ts +75 -31
package/src/infra/parser.ts
CHANGED
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
// and connections, [Group] containers, tag groups, pipe metadata.
|
|
8
8
|
|
|
9
9
|
import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
measureIndent,
|
|
12
|
+
parseFirstLine,
|
|
13
|
+
OPTION_NOCOLON_RE,
|
|
14
|
+
} from '../utils/parsing';
|
|
11
15
|
import { matchTagBlockHeading } from '../utils/tag-groups';
|
|
12
16
|
import type {
|
|
13
17
|
ParsedInfra,
|
|
@@ -21,21 +25,17 @@ import { INFRA_BEHAVIOR_KEYS, EDGE_ONLY_KEYS } from './types';
|
|
|
21
25
|
// Regex patterns
|
|
22
26
|
// ============================================================
|
|
23
27
|
|
|
24
|
-
// Connection: -label-> Target or -> Target (
|
|
25
|
-
const CONNECTION_RE =
|
|
26
|
-
/^-(?:([^-].*?))?->\s*(.+?)(?:(?:\s*\|\s*|\s+)split\s*:?\s*(\d+)%)?\s*$/;
|
|
28
|
+
// Connection: -label-> Target or -> Target (pipe metadata handled by extractPipeMetadata)
|
|
29
|
+
const CONNECTION_RE = /^-(?:([^-].*?))?->\s*(.+?)\s*$/;
|
|
27
30
|
|
|
28
31
|
// Simple connection shorthand: -> Target (no label, no dash prefix needed for edge)
|
|
29
|
-
const SIMPLE_CONNECTION_RE =
|
|
30
|
-
/^->\s*(.+?)(?:(?:\s*\|\s*|\s+)split\s*:?\s*(\d+)%)?\s*$/;
|
|
32
|
+
const SIMPLE_CONNECTION_RE = /^->\s*(.+?)\s*$/;
|
|
31
33
|
|
|
32
|
-
// Async connection: ~label~> Target or ~> Target
|
|
33
|
-
const ASYNC_CONNECTION_RE =
|
|
34
|
-
/^~(?:([^~].*?))?~>\s*(.+?)(?:(?:\s*\|\s*|\s+)split\s*:?\s*(\d+)%)?\s*$/;
|
|
34
|
+
// Async connection: ~label~> Target or ~> Target
|
|
35
|
+
const ASYNC_CONNECTION_RE = /^~(?:([^~].*?))?~>\s*(.+?)\s*$/;
|
|
35
36
|
|
|
36
37
|
// Async simple connection shorthand: ~> Target
|
|
37
|
-
const ASYNC_SIMPLE_CONNECTION_RE =
|
|
38
|
-
/^~>\s*(.+?)(?:(?:\s*\|\s*|\s+)split\s*:?\s*(\d+)%)?\s*$/;
|
|
38
|
+
const ASYNC_SIMPLE_CONNECTION_RE = /^~>\s*(.+?)\s*$/;
|
|
39
39
|
|
|
40
40
|
// Deprecated xN fanout suffix (e.g. "x5" at end of line)
|
|
41
41
|
const DEPRECATED_FANOUT_RE = /\bx(\d+)\s*$/;
|
|
@@ -68,8 +68,12 @@ const EDGE_NODE_NAMES = new Set(['edge', 'internet']);
|
|
|
68
68
|
|
|
69
69
|
// Known top-level option keys (space-separated, no colon)
|
|
70
70
|
const TOP_LEVEL_OPTIONS = new Set([
|
|
71
|
-
'slo-availability',
|
|
72
|
-
'
|
|
71
|
+
'slo-availability',
|
|
72
|
+
'slo-p90-latency-ms',
|
|
73
|
+
'slo-warning-margin',
|
|
74
|
+
'default-latency-ms',
|
|
75
|
+
'default-uptime',
|
|
76
|
+
'default-rps',
|
|
73
77
|
]);
|
|
74
78
|
|
|
75
79
|
// ============================================================
|
|
@@ -94,9 +98,10 @@ function parsePropertyValue(raw: string): string | number {
|
|
|
94
98
|
return raw.trim();
|
|
95
99
|
}
|
|
96
100
|
|
|
97
|
-
function extractPipeMetadata(
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
function extractPipeMetadata(rest: string): {
|
|
102
|
+
tags: Record<string, string>;
|
|
103
|
+
clean: string;
|
|
104
|
+
} {
|
|
100
105
|
const tags: Record<string, string> = {};
|
|
101
106
|
let clean = rest;
|
|
102
107
|
let match: RegExpExecArray | null;
|
|
@@ -108,6 +113,30 @@ function extractPipeMetadata(
|
|
|
108
113
|
return { tags, clean: clean.trim() };
|
|
109
114
|
}
|
|
110
115
|
|
|
116
|
+
// Detect unparsed pipe metadata left in a target name after extractPipeMetadata.
|
|
117
|
+
// Common case: `split 100%` without a colon isn't picked up by PIPE_META_RE.
|
|
118
|
+
const UNPARSED_SPLIT_RE = /\bsplit\s+(\d+)%/;
|
|
119
|
+
|
|
120
|
+
function warnUnparsedPipeMeta(
|
|
121
|
+
targetName: string,
|
|
122
|
+
lineNumber: number,
|
|
123
|
+
warnFn: (line: number, message: string) => void
|
|
124
|
+
): void {
|
|
125
|
+
if (!targetName.includes('|')) return;
|
|
126
|
+
const splitMatch = targetName.match(UNPARSED_SPLIT_RE);
|
|
127
|
+
if (splitMatch) {
|
|
128
|
+
warnFn(
|
|
129
|
+
lineNumber,
|
|
130
|
+
`'split ${splitMatch[1]}%' needs a colon — use 'split: ${splitMatch[1]}%'`
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
warnFn(
|
|
134
|
+
lineNumber,
|
|
135
|
+
`Unparsed pipe metadata in target — pipe values use 'key: value' syntax`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
111
140
|
// ============================================================
|
|
112
141
|
// Parser
|
|
113
142
|
// ============================================================
|
|
@@ -150,20 +179,26 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
150
179
|
if (currentNode && !nodeMap.has(currentNode.id)) {
|
|
151
180
|
// Validate mutual exclusion: concurrency vs instances/max-rps
|
|
152
181
|
const keys = new Set(currentNode.properties.map((p) => p.key));
|
|
153
|
-
if (
|
|
154
|
-
|
|
182
|
+
if (
|
|
183
|
+
keys.has('concurrency') &&
|
|
184
|
+
(keys.has('instances') || keys.has('max-rps'))
|
|
185
|
+
) {
|
|
186
|
+
const conflicting = [
|
|
187
|
+
keys.has('instances') ? 'instances' : '',
|
|
188
|
+
keys.has('max-rps') ? 'max-rps' : '',
|
|
189
|
+
]
|
|
155
190
|
.filter(Boolean)
|
|
156
191
|
.join(', ');
|
|
157
192
|
warn(
|
|
158
193
|
currentNode.lineNumber,
|
|
159
|
-
`'concurrency' (serverless) is mutually exclusive with ${conflicting}. Serverless nodes scale via concurrency, not instances
|
|
194
|
+
`'concurrency' (serverless) is mutually exclusive with ${conflicting}. Serverless nodes scale via concurrency, not instances.`
|
|
160
195
|
);
|
|
161
196
|
}
|
|
162
197
|
// Validate mutual exclusion: buffer (queue) vs max-rps (service)
|
|
163
198
|
if (keys.has('buffer') && keys.has('max-rps')) {
|
|
164
199
|
warn(
|
|
165
200
|
currentNode.lineNumber,
|
|
166
|
-
`'buffer' (queue) and 'max-rps' (service) represent different capacity models. A queue buffers messages; a service processes them
|
|
201
|
+
`'buffer' (queue) and 'max-rps' (service) represent different capacity models. A queue buffers messages; a service processes them.`
|
|
167
202
|
);
|
|
168
203
|
}
|
|
169
204
|
nodeMap.set(currentNode.id, currentNode);
|
|
@@ -202,7 +237,10 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
202
237
|
const firstLineResult = parseFirstLine(trimmed);
|
|
203
238
|
if (firstLineResult) {
|
|
204
239
|
if (firstLineResult.chartType !== 'infra') {
|
|
205
|
-
setError(
|
|
240
|
+
setError(
|
|
241
|
+
lineNumber,
|
|
242
|
+
`Expected chart type 'infra', got '${firstLineResult.chartType}'`
|
|
243
|
+
);
|
|
206
244
|
}
|
|
207
245
|
if (firstLineResult.title) {
|
|
208
246
|
result.title = firstLineResult.title;
|
|
@@ -255,11 +293,16 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
255
293
|
finishCurrentTagGroup();
|
|
256
294
|
const gLabel = groupMatch[1].trim();
|
|
257
295
|
const gId = groupId(gLabel);
|
|
258
|
-
const groupMeta = groupMatch[2]
|
|
296
|
+
const groupMeta = groupMatch[2]
|
|
297
|
+
? extractPipeMetadata('|' + groupMatch[2]).tags
|
|
298
|
+
: undefined;
|
|
259
299
|
currentGroup = {
|
|
260
300
|
id: gId,
|
|
261
301
|
label: gLabel,
|
|
262
|
-
metadata:
|
|
302
|
+
metadata:
|
|
303
|
+
groupMeta && Object.keys(groupMeta).length > 0
|
|
304
|
+
? groupMeta
|
|
305
|
+
: undefined,
|
|
263
306
|
lineNumber,
|
|
264
307
|
};
|
|
265
308
|
result.groups.push(currentGroup);
|
|
@@ -310,6 +353,11 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
310
353
|
}
|
|
311
354
|
continue;
|
|
312
355
|
}
|
|
356
|
+
warn(
|
|
357
|
+
lineNumber,
|
|
358
|
+
`Invalid tag value '${trimmed}' in tag group '${currentTagGroup.name}'.`
|
|
359
|
+
);
|
|
360
|
+
continue;
|
|
313
361
|
}
|
|
314
362
|
|
|
315
363
|
// Inside a [Group] but no current node — group properties or component declaration
|
|
@@ -333,6 +381,8 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
333
381
|
currentGroup.collapsed = val.toLowerCase() === 'true';
|
|
334
382
|
continue;
|
|
335
383
|
}
|
|
384
|
+
// Fall through to component matching — could be a component name
|
|
385
|
+
// that happens to match PROPERTY_RE (e.g., "MyService v2")
|
|
336
386
|
}
|
|
337
387
|
|
|
338
388
|
const compMatch = trimmed.match(COMPONENT_RE);
|
|
@@ -365,9 +415,17 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
365
415
|
if (currentNode && indent > baseIndent) {
|
|
366
416
|
// Detect deprecated xN fanout syntax
|
|
367
417
|
const deprecatedFanout = trimmed.match(DEPRECATED_FANOUT_RE);
|
|
368
|
-
if (
|
|
418
|
+
if (
|
|
419
|
+
deprecatedFanout &&
|
|
420
|
+
(trimmed.startsWith('->') ||
|
|
421
|
+
trimmed.startsWith('-') ||
|
|
422
|
+
trimmed.startsWith('~'))
|
|
423
|
+
) {
|
|
369
424
|
const n = deprecatedFanout[1];
|
|
370
|
-
setError(
|
|
425
|
+
setError(
|
|
426
|
+
lineNumber,
|
|
427
|
+
`'x${n}' fanout syntax is no longer supported — use '| fanout: ${n}' instead`
|
|
428
|
+
);
|
|
371
429
|
continue;
|
|
372
430
|
}
|
|
373
431
|
|
|
@@ -375,14 +433,20 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
375
433
|
const asyncSimpleConn = trimmed.match(ASYNC_SIMPLE_CONNECTION_RE);
|
|
376
434
|
if (asyncSimpleConn) {
|
|
377
435
|
const targetRaw = asyncSimpleConn[1].trim();
|
|
378
|
-
const splitStr = asyncSimpleConn[2];
|
|
379
436
|
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
380
437
|
const targetName = pipeMeta.clean || targetRaw;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
438
|
+
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
439
|
+
const split = pipeMeta.tags.split
|
|
440
|
+
? parseFloat(pipeMeta.tags.split)
|
|
441
|
+
: null;
|
|
442
|
+
const fanoutRaw = pipeMeta.tags.fanout
|
|
443
|
+
? parseInt(pipeMeta.tags.fanout, 10)
|
|
444
|
+
: null;
|
|
384
445
|
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
385
|
-
warn(
|
|
446
|
+
warn(
|
|
447
|
+
lineNumber,
|
|
448
|
+
`Fan-out multiplier must be at least 1 (got fanout: ${fanoutRaw}). Ignoring.`
|
|
449
|
+
);
|
|
386
450
|
}
|
|
387
451
|
const fanout = fanoutRaw !== null && fanoutRaw >= 1 ? fanoutRaw : null;
|
|
388
452
|
result.edges.push({
|
|
@@ -402,14 +466,20 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
402
466
|
if (asyncConnMatch) {
|
|
403
467
|
const label = asyncConnMatch[1]?.trim() || '';
|
|
404
468
|
const targetRaw = asyncConnMatch[2].trim();
|
|
405
|
-
const splitStr = asyncConnMatch[3];
|
|
406
469
|
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
407
470
|
const targetName = pipeMeta.clean || targetRaw;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
471
|
+
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
472
|
+
const split = pipeMeta.tags.split
|
|
473
|
+
? parseFloat(pipeMeta.tags.split)
|
|
474
|
+
: null;
|
|
475
|
+
const fanoutRaw = pipeMeta.tags.fanout
|
|
476
|
+
? parseInt(pipeMeta.tags.fanout, 10)
|
|
477
|
+
: null;
|
|
411
478
|
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
412
|
-
warn(
|
|
479
|
+
warn(
|
|
480
|
+
lineNumber,
|
|
481
|
+
`Fan-out multiplier must be at least 1 (got fanout: ${fanoutRaw}). Ignoring.`
|
|
482
|
+
);
|
|
413
483
|
}
|
|
414
484
|
const fanout = fanoutRaw !== null && fanoutRaw >= 1 ? fanoutRaw : null;
|
|
415
485
|
|
|
@@ -437,15 +507,20 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
437
507
|
const simpleConn = trimmed.match(SIMPLE_CONNECTION_RE);
|
|
438
508
|
if (simpleConn) {
|
|
439
509
|
const targetRaw = simpleConn[1].trim();
|
|
440
|
-
const splitStr = simpleConn[2];
|
|
441
|
-
// Parse pipe metadata for fanout/split (and clean target name)
|
|
442
510
|
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
443
511
|
const targetName = pipeMeta.clean || targetRaw;
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
512
|
+
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
513
|
+
const split = pipeMeta.tags.split
|
|
514
|
+
? parseFloat(pipeMeta.tags.split)
|
|
515
|
+
: null;
|
|
516
|
+
const fanoutRaw = pipeMeta.tags.fanout
|
|
517
|
+
? parseInt(pipeMeta.tags.fanout, 10)
|
|
518
|
+
: null;
|
|
447
519
|
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
448
|
-
warn(
|
|
520
|
+
warn(
|
|
521
|
+
lineNumber,
|
|
522
|
+
`Fan-out multiplier must be at least 1 (got fanout: ${fanoutRaw}). Ignoring.`
|
|
523
|
+
);
|
|
449
524
|
}
|
|
450
525
|
const fanout = fanoutRaw !== null && fanoutRaw >= 1 ? fanoutRaw : null;
|
|
451
526
|
result.edges.push({
|
|
@@ -465,15 +540,20 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
465
540
|
if (connMatch) {
|
|
466
541
|
const label = connMatch[1]?.trim() || '';
|
|
467
542
|
const targetRaw = connMatch[2].trim();
|
|
468
|
-
const splitStr = connMatch[3];
|
|
469
|
-
// Parse pipe metadata for fanout/split (and clean target name)
|
|
470
543
|
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
471
544
|
const targetName = pipeMeta.clean || targetRaw;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
545
|
+
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
546
|
+
const split = pipeMeta.tags.split
|
|
547
|
+
? parseFloat(pipeMeta.tags.split)
|
|
548
|
+
: null;
|
|
549
|
+
const fanoutRaw = pipeMeta.tags.fanout
|
|
550
|
+
? parseInt(pipeMeta.tags.fanout, 10)
|
|
551
|
+
: null;
|
|
475
552
|
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
476
|
-
warn(
|
|
553
|
+
warn(
|
|
554
|
+
lineNumber,
|
|
555
|
+
`Fan-out multiplier must be at least 1 (got fanout: ${fanoutRaw}). Ignoring.`
|
|
556
|
+
);
|
|
477
557
|
}
|
|
478
558
|
const fanout = fanoutRaw !== null && fanoutRaw >= 1 ? fanoutRaw : null;
|
|
479
559
|
|
|
@@ -525,7 +605,10 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
525
605
|
|
|
526
606
|
// Validate edge-only keys
|
|
527
607
|
if (EDGE_ONLY_KEYS.has(key) && !currentNode.isEdge) {
|
|
528
|
-
warn(
|
|
608
|
+
warn(
|
|
609
|
+
lineNumber,
|
|
610
|
+
`Property '${key}' is only valid on the entry point (Edge/Internet).`
|
|
611
|
+
);
|
|
529
612
|
}
|
|
530
613
|
|
|
531
614
|
const value = parsePropertyValue(rawVal);
|
|
@@ -534,7 +617,10 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
534
617
|
}
|
|
535
618
|
|
|
536
619
|
// Unknown indented line
|
|
537
|
-
warn(
|
|
620
|
+
warn(
|
|
621
|
+
lineNumber,
|
|
622
|
+
`Unexpected line inside component '${currentNode.label}'.`
|
|
623
|
+
);
|
|
538
624
|
continue;
|
|
539
625
|
}
|
|
540
626
|
|
|
@@ -592,6 +678,9 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
592
678
|
continue;
|
|
593
679
|
}
|
|
594
680
|
}
|
|
681
|
+
|
|
682
|
+
// Catch-all: nothing matched this line
|
|
683
|
+
warn(lineNumber, `Unexpected line: '${trimmed}'.`);
|
|
595
684
|
}
|
|
596
685
|
|
|
597
686
|
// Flush last open blocks
|
|
@@ -661,7 +750,8 @@ export function extractSymbols(docText: string): DiagramSymbols {
|
|
|
661
750
|
// Recognize new-style bare options (`key value`) and old-style (`key: value`)
|
|
662
751
|
const firstLine = parseFirstLine(line);
|
|
663
752
|
if (firstLine) continue; // chart type line
|
|
664
|
-
if (/^(?:direction-tb|animate|no-animate|slo-|default-)/i.test(line))
|
|
753
|
+
if (/^(?:direction-tb|animate|no-animate|slo-|default-)/i.test(line))
|
|
754
|
+
continue;
|
|
665
755
|
if (/^[a-z-]+\s*:/i.test(line)) continue; // legacy colon options
|
|
666
756
|
inMetadata = false;
|
|
667
757
|
} else {
|
|
@@ -671,8 +761,14 @@ export function extractSymbols(docText: string): DiagramSymbols {
|
|
|
671
761
|
|
|
672
762
|
if (!indented) {
|
|
673
763
|
// Root-level: tag group declaration, group header, or component
|
|
674
|
-
if (/^tag\s/i.test(line)) {
|
|
675
|
-
|
|
764
|
+
if (/^tag\s/i.test(line)) {
|
|
765
|
+
inTagGroup = true;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (/^tag\s*:/i.test(line)) {
|
|
769
|
+
inTagGroup = true;
|
|
770
|
+
continue;
|
|
771
|
+
} // legacy
|
|
676
772
|
inTagGroup = false;
|
|
677
773
|
if (/^\[/.test(line)) continue; // [Group] header
|
|
678
774
|
const m = COMPONENT_RE.exec(line);
|
|
@@ -687,7 +783,15 @@ export function extractSymbols(docText: string): DiagramSymbols {
|
|
|
687
783
|
if (/^\w[\w-]*\s*:/.test(line)) continue; // property (key: value) legacy
|
|
688
784
|
// New-style property: first token is a known behavior/property key
|
|
689
785
|
const firstToken = line.split(/\s/)[0].toLowerCase();
|
|
690
|
-
if (
|
|
786
|
+
if (
|
|
787
|
+
(INFRA_BEHAVIOR_KEYS.has(firstToken) ||
|
|
788
|
+
EDGE_ONLY_KEYS.has(firstToken) ||
|
|
789
|
+
firstToken === 'description' ||
|
|
790
|
+
firstToken === 'instances' ||
|
|
791
|
+
firstToken === 'collapsed') &&
|
|
792
|
+
/\s/.test(line)
|
|
793
|
+
)
|
|
794
|
+
continue;
|
|
691
795
|
const m = COMPONENT_RE.exec(line);
|
|
692
796
|
if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
|
|
693
797
|
}
|