zombie-mermaid 5.14.3 → 5.15.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 +5 -5
- package/dist/cli.js +254 -141
- package/dist/index.cjs +1 -1
- package/dist/index.js +124 -5
- package/dist/mcp.cjs +1 -1
- package/dist/mcp.js +121 -0
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ the SVG renderer at all (see [ASCII Output](#ascii-output)).
|
|
|
86
86
|
|
|
87
87
|
## Features
|
|
88
88
|
|
|
89
|
-
- **
|
|
89
|
+
- **12 diagram types** — Flowcharts (including State), Sequence, Class, ER, XY Charts (bar, line, combined), Architecture, C4, Pie, Quadrant charts, Gantt charts, Mindmaps, and Timelines
|
|
90
90
|
- **Dual output** — SVG for rich UIs, ASCII/Unicode for terminals
|
|
91
91
|
- **Synchronous rendering** — No async, no flash. Works with React `useMemo()`
|
|
92
92
|
- **15 built-in themes** — And dead simple to add your own
|
|
@@ -151,16 +151,16 @@ import { registerElk } from 'zombie-mermaid'
|
|
|
151
151
|
registerElk(ELK)
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
Architecture diagrams (`architecture-beta`, see [Supported Diagrams](docs/diagrams.md#architecture-diagrams))
|
|
155
|
-
Sequence, pie, quadrant, xychart and C4 diagrams, and all ASCII output, need no `elkjs`
|
|
154
|
+
Architecture diagrams (`architecture-beta`, see [Supported Diagrams](docs/diagrams.md#architecture-diagrams)) use their own grid layout (as Mermaid does), so they need no `elkjs`.
|
|
155
|
+
Sequence, pie, quadrant, gantt, xychart and C4 diagrams, and all ASCII output, need no `elkjs`
|
|
156
156
|
and no registration. Rendering a graph diagram with no `elkjs` available throws an
|
|
157
157
|
`ElkNotRegisteredError` that says how to fix it.
|
|
158
158
|
|
|
159
159
|
**Flowcharts without `elkjs`.** Pass `layout: 'simple'` to lay a flowchart out with a
|
|
160
160
|
small built-in layered layout instead of ELK. It needs no `elkjs`, adds about 3.5 KB
|
|
161
161
|
gzipped, and is opt-in: the default output never changes. It is a rougher match for
|
|
162
|
-
ELK on edge routing, and a subgraph's own `direction` is ignored. State, class
|
|
163
|
-
|
|
162
|
+
ELK on edge routing, and a subgraph's own `direction` is ignored. State, class and ER
|
|
163
|
+
diagrams still need `elkjs`. See
|
|
164
164
|
[the decision record](docs/decisions/simple-layout-1685.md) for the measured quality
|
|
165
165
|
gap.
|
|
166
166
|
|
package/dist/cli.js
CHANGED
|
@@ -3,26 +3,26 @@ import { createRequire as e } from "node:module";
|
|
|
3
3
|
import { extname as t, format as n, parse as r } from "node:path";
|
|
4
4
|
import { MermaidParseError as i, THEMES as a, escapeXml as o, isDirection as s, isWideChar as c } from "@zombie-mermaid/core";
|
|
5
5
|
import { access as l, readFile as u, writeFile as d } from "node:fs/promises";
|
|
6
|
-
import { diagramColorsToAsciiTheme as f,
|
|
6
|
+
import { diagramColorsToAsciiTheme as f, registerAsciiLayout as p, renderMermaidASCII as m } from "@zombie-mermaid/ascii-renderer";
|
|
7
|
+
import { ElkNotRegisteredError as h, layoutErDiagramSync as g, layoutFlowchartSync as _, registerElk as v, renderMermaidSVG as y, validateMermaid as b } from "@zombie-mermaid/svg-renderer";
|
|
7
8
|
import "@zombie-mermaid/mermaid-parser";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import x from "elkjs/lib/elk.bundled.js";
|
|
9
|
+
import { renderMermaidASCIIWithLayout as x } from "./ascii-layout.js";
|
|
10
|
+
import { createServer as S } from "node:http";
|
|
11
|
+
import { StdioServerTransport as C } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { createMcpServer as w } from "@zombie-mermaid/mcp";
|
|
13
|
+
import T from "elkjs/lib/elk.bundled.js";
|
|
14
14
|
//#region src/cli/parse-args.ts
|
|
15
|
-
var
|
|
15
|
+
var E = 3e3, D = {
|
|
16
16
|
".svg": "svg",
|
|
17
17
|
".txt": "ascii",
|
|
18
18
|
".html": "html",
|
|
19
19
|
".htm": "html",
|
|
20
20
|
".png": "png"
|
|
21
21
|
};
|
|
22
|
-
function
|
|
23
|
-
if (e !== void 0 && e !== "-") return
|
|
22
|
+
function ee(e) {
|
|
23
|
+
if (e !== void 0 && e !== "-") return D[t(e).toLowerCase()];
|
|
24
24
|
}
|
|
25
|
-
var
|
|
25
|
+
var O = {
|
|
26
26
|
svg: {
|
|
27
27
|
flag: "--svg",
|
|
28
28
|
ext: ".svg",
|
|
@@ -44,8 +44,8 @@ var T = {
|
|
|
44
44
|
display: "PNG",
|
|
45
45
|
label: "the PNG rasterization"
|
|
46
46
|
}
|
|
47
|
-
},
|
|
48
|
-
function
|
|
47
|
+
}, k = Object.keys(O);
|
|
48
|
+
function A(e, t) {
|
|
49
49
|
let i = r(e);
|
|
50
50
|
return n({
|
|
51
51
|
dir: i.dir,
|
|
@@ -53,7 +53,7 @@ function ee(e, t) {
|
|
|
53
53
|
ext: t
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
-
function
|
|
56
|
+
function te(e) {
|
|
57
57
|
let [t, ...n] = e;
|
|
58
58
|
if (t === void 0 || t === "--help" || t === "-h") return { command: "help" };
|
|
59
59
|
if (t === "--version" || t === "-v") return { command: "version" };
|
|
@@ -67,7 +67,7 @@ function D(e) {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
if (t === "themes") return { command: "themes" };
|
|
70
|
-
if (t === "render") return
|
|
70
|
+
if (t === "render") return ie(n);
|
|
71
71
|
if (t === "validate") {
|
|
72
72
|
let e = n.find((e, t) => t > 0 || e.startsWith("-"));
|
|
73
73
|
if (e !== void 0) throw Error(`Unexpected argument: ${e}`);
|
|
@@ -76,17 +76,17 @@ function D(e) {
|
|
|
76
76
|
input: n[0]
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
if (t === "web") return
|
|
79
|
+
if (t === "web") return ae(n);
|
|
80
80
|
if (t === "mcp") return { command: "mcp" };
|
|
81
81
|
throw Error(`Unknown command: ${t}`);
|
|
82
82
|
}
|
|
83
|
-
function
|
|
83
|
+
function j(e, t, n) {
|
|
84
84
|
let r = e[t + 1];
|
|
85
85
|
if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a numeric value`);
|
|
86
86
|
if (!/^\d+$/.test(r)) throw Error(`${n} requires a non-negative integer, got: "${r}"`);
|
|
87
87
|
return Number(r);
|
|
88
88
|
}
|
|
89
|
-
function
|
|
89
|
+
function ne(e, t, n) {
|
|
90
90
|
let r = e[t + 1];
|
|
91
91
|
if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a value ("auto" or a positive integer)`);
|
|
92
92
|
if (r === "auto") return "auto";
|
|
@@ -94,14 +94,14 @@ function k(e, t, n) {
|
|
|
94
94
|
if (!/^\d+$/.test(r) || i === 0 || !Number.isSafeInteger(i)) throw Error(`${n} requires "auto" or a positive integer, got: "${r}"`);
|
|
95
95
|
return i;
|
|
96
96
|
}
|
|
97
|
-
function
|
|
97
|
+
function re(e, t, n) {
|
|
98
98
|
let r = e[t + 1];
|
|
99
99
|
if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a value (one of TD, TB, BT, LR, RL)`);
|
|
100
100
|
let i = r.toUpperCase();
|
|
101
101
|
if (!s(i)) throw Error(`${n} requires one of TD, TB, BT, LR, RL, got: "${r}"`);
|
|
102
102
|
return i;
|
|
103
103
|
}
|
|
104
|
-
function
|
|
104
|
+
function ie(e) {
|
|
105
105
|
let t, n = !1, r = !1, i = !1, a = !1, o = !1, s, c = !1, l, u, d, f, p = !1, m = !1, h, g, _, v, y = 0;
|
|
106
106
|
for (; y < e.length;) {
|
|
107
107
|
let b = e[y];
|
|
@@ -118,12 +118,12 @@ function j(e) {
|
|
|
118
118
|
else if (b === "--theme") {
|
|
119
119
|
if (y + 1 >= e.length) throw Error("--theme requires a theme name");
|
|
120
120
|
l = e[y + 1], y += 2;
|
|
121
|
-
} else if (b === "-x" || b === "--paddingX") u =
|
|
122
|
-
else if (b === "-y" || b === "--paddingY") d =
|
|
123
|
-
else if (b === "-p" || b === "--borderPadding") f =
|
|
121
|
+
} else if (b === "-x" || b === "--paddingX") u = j(e, y, b), y += 2;
|
|
122
|
+
else if (b === "-y" || b === "--paddingY") d = j(e, y, b), y += 2;
|
|
123
|
+
else if (b === "-p" || b === "--borderPadding") f = j(e, y, b), y += 2;
|
|
124
124
|
else if (b === "--coords") p = !0, y++;
|
|
125
125
|
else if (b === "--hyperlinks") m = !0, y++;
|
|
126
|
-
else if (b === "-w" || b === "--max-width") h =
|
|
126
|
+
else if (b === "-w" || b === "--max-width") h = ne(e, y, b), y += 2;
|
|
127
127
|
else if (b === "--profile") {
|
|
128
128
|
let t = e[y + 1];
|
|
129
129
|
if (t !== "markdown") throw Error(`--profile requires one of: markdown, got: "${t ?? ""}"`);
|
|
@@ -132,7 +132,7 @@ function j(e) {
|
|
|
132
132
|
let t = b.indexOf("="), n = t === -1 ? e[y + 1] : b.slice(t + 1);
|
|
133
133
|
if (n !== "mermaid" && n !== "mermaid-wide") throw Error(`--layout requires one of: mermaid, mermaid-wide, got: "${n ?? ""}"`);
|
|
134
134
|
_ = n, y += t === -1 ? 2 : 1;
|
|
135
|
-
} else if (b === "--direction") v =
|
|
135
|
+
} else if (b === "--direction") v = re(e, y, b), y += 2;
|
|
136
136
|
else if (!b.startsWith("-") || b === "-") {
|
|
137
137
|
if (b === "-") throw Error("Unexpected argument: - (use -o - to write output to stdout; omit the input file to read stdin)");
|
|
138
138
|
if (t !== void 0) throw Error(`Unexpected argument: ${b} (input file already set to "${t}")`);
|
|
@@ -143,47 +143,47 @@ function j(e) {
|
|
|
143
143
|
svg: r,
|
|
144
144
|
html: a,
|
|
145
145
|
png: o
|
|
146
|
-
}, x =
|
|
146
|
+
}, x = k.filter((e) => b[e]);
|
|
147
147
|
if (x.length > 1) {
|
|
148
148
|
let [e, t] = x;
|
|
149
|
-
throw Error(`${
|
|
149
|
+
throw Error(`${O[e].flag} and ${O[t].flag} cannot both be set — they would write different content to the same output path. Run the command twice with different -o paths for each.`);
|
|
150
150
|
}
|
|
151
|
-
let S =
|
|
151
|
+
let S = ee(s);
|
|
152
152
|
if (S !== void 0 && S !== "ascii") {
|
|
153
|
-
let e =
|
|
153
|
+
let e = k.find((e) => e !== S && b[e]);
|
|
154
154
|
if (e !== void 0) {
|
|
155
|
-
let t =
|
|
155
|
+
let t = O[S], n = O[e];
|
|
156
156
|
throw Error(`-o ${s} has ${t.article} ${t.ext} extension, but ${n.flag} output would be written to it. Use a ${n.ext} path (or -o - for stdout) for ${n.label}.`);
|
|
157
157
|
}
|
|
158
158
|
S === "svg" ? r = !0 : S === "html" ? a = !0 : o = !0;
|
|
159
159
|
} else if (S === "ascii") {
|
|
160
|
-
let e =
|
|
160
|
+
let e = k.find((e) => b[e]);
|
|
161
161
|
if (e !== void 0) {
|
|
162
|
-
let t =
|
|
162
|
+
let t = O[e];
|
|
163
163
|
throw Error(`-o ${s} has a .txt extension, but ${t.flag} output would be written to it. Use a ${t.ext} path (or -o - for stdout) instead.`);
|
|
164
164
|
}
|
|
165
165
|
n = !0;
|
|
166
166
|
}
|
|
167
167
|
if (!n && !r && !a && !o) {
|
|
168
168
|
if (s !== void 0 && s !== "-") {
|
|
169
|
-
let e = Object.keys(
|
|
169
|
+
let e = Object.keys(D).join(", ");
|
|
170
170
|
throw Error(`Cannot infer an output format from "${s}" — pass --ascii, --svg, --html, or --png, or use a recognised extension (${e})`);
|
|
171
171
|
}
|
|
172
172
|
throw Error("Specify --ascii, --svg, --html, and/or --png (or -o <path> with a .svg/.txt/.html/.png extension)");
|
|
173
173
|
}
|
|
174
|
-
let
|
|
174
|
+
let C = {
|
|
175
175
|
svg: r,
|
|
176
176
|
html: a,
|
|
177
177
|
png: o
|
|
178
|
-
},
|
|
179
|
-
if (s === "-" && n &&
|
|
180
|
-
let e =
|
|
178
|
+
}, w = k.find((e) => C[e]);
|
|
179
|
+
if (s === "-" && n && w !== void 0) {
|
|
180
|
+
let e = O[w].display;
|
|
181
181
|
throw Error(`-o - would send both ASCII and ${e} to stdout; drop --ascii, or write the ${e} to a file path`);
|
|
182
182
|
}
|
|
183
|
-
if (
|
|
184
|
-
let { flag: e, ext: n } =
|
|
183
|
+
if (w !== void 0 && s === void 0) {
|
|
184
|
+
let { flag: e, ext: n } = O[w];
|
|
185
185
|
if (t === void 0) throw Error(`${e} needs -o <path> (or -o - for stdout) when reading from stdin — there is no input file name to derive an output name from`);
|
|
186
|
-
s =
|
|
186
|
+
s = A(t, n);
|
|
187
187
|
}
|
|
188
188
|
if (h !== void 0 && !n) throw Error("-w/--max-width requires --ascii");
|
|
189
189
|
if (g !== void 0 && !n) throw Error("--profile requires --ascii");
|
|
@@ -212,12 +212,12 @@ function j(e) {
|
|
|
212
212
|
direction: v
|
|
213
213
|
};
|
|
214
214
|
}
|
|
215
|
-
function
|
|
216
|
-
let t =
|
|
215
|
+
function ae(e) {
|
|
216
|
+
let t = E, n = 0;
|
|
217
217
|
for (; n < e.length;) {
|
|
218
218
|
let r = e[n];
|
|
219
219
|
if (r === void 0) throw Error(`parseArgs: index ${n} out of range while parsing arguments`);
|
|
220
|
-
if (r === "--port") t =
|
|
220
|
+
if (r === "--port") t = j(e, n, r), n += 2;
|
|
221
221
|
else throw Error(`Unknown flag: ${r}`);
|
|
222
222
|
}
|
|
223
223
|
return {
|
|
@@ -227,40 +227,155 @@ function M(e) {
|
|
|
227
227
|
}
|
|
228
228
|
//#endregion
|
|
229
229
|
//#region packages/ascii-renderer/src/display-width.ts
|
|
230
|
-
var
|
|
231
|
-
function
|
|
232
|
-
return
|
|
230
|
+
var oe = /\p{Mn}|\p{Me}/u;
|
|
231
|
+
function se(e) {
|
|
232
|
+
return oe.test(e);
|
|
233
233
|
}
|
|
234
|
-
var
|
|
235
|
-
function
|
|
234
|
+
var ce = new Intl.Segmenter(void 0, { granularity: "grapheme" }), le = "️";
|
|
235
|
+
function ue(e) {
|
|
236
236
|
if (/^[\x20-\x7e]*$/.test(e)) return e.split("");
|
|
237
237
|
let t = [];
|
|
238
|
-
for (let { segment: n } of
|
|
238
|
+
for (let { segment: n } of ce.segment(e)) t.push(n);
|
|
239
239
|
return t;
|
|
240
240
|
}
|
|
241
|
-
function
|
|
242
|
-
if (e.includes(
|
|
241
|
+
function de(e) {
|
|
242
|
+
if (e.includes(le)) return 2;
|
|
243
243
|
let t = !1, n = !1;
|
|
244
|
-
for (let r of e) c(r) && (t = !0),
|
|
244
|
+
for (let r of e) c(r) && (t = !0), se(r) || (n = !0);
|
|
245
245
|
return t ? 2 : +!!n;
|
|
246
246
|
}
|
|
247
|
-
function
|
|
247
|
+
function fe(e) {
|
|
248
248
|
let t = 0;
|
|
249
|
-
for (let n of
|
|
249
|
+
for (let n of ue(e)) t += de(n);
|
|
250
250
|
return t;
|
|
251
251
|
}
|
|
252
252
|
//#endregion
|
|
253
253
|
//#region packages/ascii-renderer/src/hyperlinks.ts
|
|
254
|
-
var
|
|
255
|
-
function
|
|
256
|
-
return e.replace(
|
|
254
|
+
var pe = /\x1b\]8;[^\x07\x1b]*(?:\x1b\\|\x07)/g;
|
|
255
|
+
function me(e) {
|
|
256
|
+
return e.replace(pe, "");
|
|
257
|
+
}
|
|
258
|
+
var M = .5, N = (e, t, n) => {
|
|
259
|
+
if (e.diamond) {
|
|
260
|
+
let [r, i, a] = n ? [
|
|
261
|
+
t.x - e.cx,
|
|
262
|
+
e.w / 2,
|
|
263
|
+
t.y - e.cy
|
|
264
|
+
] : [
|
|
265
|
+
t.y - e.cy,
|
|
266
|
+
e.h / 2,
|
|
267
|
+
t.x - e.cx
|
|
268
|
+
];
|
|
269
|
+
return Math.abs(r) / i >= .49 ? n ? r < 0 ? "L" : "R" : r < 0 ? "T" : "B" : n ? a < 0 ? "T" : "B" : a < 0 ? "L" : "R";
|
|
270
|
+
}
|
|
271
|
+
let r = {
|
|
272
|
+
T: Math.abs(t.y - (e.cy - e.h / 2)),
|
|
273
|
+
B: Math.abs(t.y - (e.cy + e.h / 2)),
|
|
274
|
+
L: Math.abs(t.x - (e.cx - e.w / 2)),
|
|
275
|
+
R: Math.abs(t.x - (e.cx + e.w / 2))
|
|
276
|
+
};
|
|
277
|
+
return Object.keys(r).reduce((e, t) => r[t] < r[e] ? t : e);
|
|
278
|
+
};
|
|
279
|
+
function P(e, t, n) {
|
|
280
|
+
let r = n === "LR" || n === "RL", i = (e) => r ? e.cx : e.cy, a = (e) => r ? e.cy : e.cx, o = (e) => (r ? e.w : e.h) / 2, s = [...e].sort((e, t) => i(e) - i(t)), c = /* @__PURE__ */ new Map(), l = -1, u, d = (e, t) => Math.abs(e - t) <= 20;
|
|
281
|
+
for (let e of s) (!u || !(d(i(e), i(u)) || d(i(e) - o(e), i(u) - o(u)) || d(i(e) + o(e), i(u) + o(u)))) && (l++, u = e), c.set(e.id, l);
|
|
282
|
+
let f = l, p = n === "RL" || n === "BT", m = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Map();
|
|
283
|
+
for (let t of [...e].sort((e, t) => a(e) - a(t))) {
|
|
284
|
+
let e = c.get(t.id), n = h.get(e) ?? 0;
|
|
285
|
+
h.set(e, n + 1), m.set(t.id, {
|
|
286
|
+
layer: p ? f - e : e,
|
|
287
|
+
order: n,
|
|
288
|
+
cross: a(t),
|
|
289
|
+
size: r ? t.h : t.w
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
let g = new Map(e.map((e) => [e.id, e])), _ = /* @__PURE__ */ new Map(), v = t.map((e, t) => {
|
|
293
|
+
let n = g.get(e.from), i = g.get(e.to);
|
|
294
|
+
if (!n || !i || e.points.length < 2) return;
|
|
295
|
+
let a = e.points[0], o = e.points[e.points.length - 1], s = (e, n, i) => {
|
|
296
|
+
let a = N(e, n, r), o = {
|
|
297
|
+
edge: t,
|
|
298
|
+
start: i,
|
|
299
|
+
face: a,
|
|
300
|
+
at: a === "T" || a === "B" ? n.x : n.y
|
|
301
|
+
}, s = `${e.id}|${a}`;
|
|
302
|
+
return _.set(s, [..._.get(s) ?? [], o]), o;
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
p: e,
|
|
306
|
+
s: s(n, a, !0),
|
|
307
|
+
e: s(i, o, !1)
|
|
308
|
+
};
|
|
309
|
+
}), y = (e, t) => {
|
|
310
|
+
let n = [..._.get(`${e}|${t.face}`)].map((e) => e.at);
|
|
311
|
+
return new Set(n.filter((e) => e < t.at - M)).size;
|
|
312
|
+
}, b = [];
|
|
313
|
+
return v.forEach((e, t) => {
|
|
314
|
+
e && b.push({
|
|
315
|
+
index: t,
|
|
316
|
+
from: e.p.from,
|
|
317
|
+
to: e.p.to,
|
|
318
|
+
start: {
|
|
319
|
+
face: e.s.face,
|
|
320
|
+
order: y(e.p.from, e.s)
|
|
321
|
+
},
|
|
322
|
+
end: {
|
|
323
|
+
face: e.e.face,
|
|
324
|
+
order: y(e.p.to, e.e)
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}), {
|
|
328
|
+
nodes: m,
|
|
329
|
+
edges: b
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
var F = (e) => {
|
|
333
|
+
try {
|
|
334
|
+
return e();
|
|
335
|
+
} catch (e) {
|
|
336
|
+
if (e instanceof h) return;
|
|
337
|
+
throw e;
|
|
338
|
+
}
|
|
339
|
+
}, I = {
|
|
340
|
+
graph: (e) => F(() => {
|
|
341
|
+
let t = _(e);
|
|
342
|
+
return P(t.nodes.map((e) => ({
|
|
343
|
+
id: e.id,
|
|
344
|
+
cx: e.x + e.width / 2,
|
|
345
|
+
cy: e.y + e.height / 2,
|
|
346
|
+
w: e.width,
|
|
347
|
+
h: e.height,
|
|
348
|
+
diamond: e.shape === "diamond"
|
|
349
|
+
})), t.edges.map((e) => ({
|
|
350
|
+
from: e.source,
|
|
351
|
+
to: e.target,
|
|
352
|
+
points: e.points
|
|
353
|
+
})), e.direction);
|
|
354
|
+
}),
|
|
355
|
+
er: (e) => F(() => {
|
|
356
|
+
let t = g(e);
|
|
357
|
+
return P(t.entities.map((e) => ({
|
|
358
|
+
id: e.id,
|
|
359
|
+
cx: e.x + e.width / 2,
|
|
360
|
+
cy: e.y + e.height / 2,
|
|
361
|
+
w: e.width,
|
|
362
|
+
h: e.height
|
|
363
|
+
})), t.relationships.map((e) => ({
|
|
364
|
+
from: e.entity1,
|
|
365
|
+
to: e.entity2,
|
|
366
|
+
points: e.points
|
|
367
|
+
})), e.direction ?? "TD");
|
|
368
|
+
})
|
|
369
|
+
};
|
|
370
|
+
function L() {
|
|
371
|
+
p(I);
|
|
257
372
|
}
|
|
258
373
|
//#endregion
|
|
259
374
|
//#region packages/mcp/src/tools/parse-diagnostics.ts
|
|
260
|
-
var
|
|
261
|
-
function
|
|
375
|
+
var R = /^Line (\d+): ([\s\S]*)$/;
|
|
376
|
+
function z(e, t) {
|
|
262
377
|
if (!(e instanceof i) || e.line === void 0) return;
|
|
263
|
-
let { line: n, message: r } = e, a =
|
|
378
|
+
let { line: n, message: r } = e, a = R.exec(r), o = t.split(/\r?\n/)[n - 1];
|
|
264
379
|
if (!(n < 1 || o === void 0)) return {
|
|
265
380
|
line: n,
|
|
266
381
|
sourceLine: o,
|
|
@@ -269,15 +384,15 @@ function N(e, t) {
|
|
|
269
384
|
}
|
|
270
385
|
//#endregion
|
|
271
386
|
//#region src/cli/diagnostics.ts
|
|
272
|
-
function
|
|
387
|
+
function B(e, t) {
|
|
273
388
|
if (!(e instanceof Error)) return e;
|
|
274
|
-
let n =
|
|
389
|
+
let n = z(e, t);
|
|
275
390
|
return n === void 0 ? e : /* @__PURE__ */ Error(`${e.message}\n ${n.line} | ${n.sourceLine}`);
|
|
276
391
|
}
|
|
277
392
|
//#endregion
|
|
278
393
|
//#region src/cli/html-viewer-client-source.ts
|
|
279
|
-
var F = "// @ts-check\n/// <reference lib=\"dom\" />\n// ============================================================================\n// Client script for the CLI's self-contained HTML viewer (`render --html`).\n//\n// Inlined verbatim into the viewer page by src/cli/html-viewer.ts (imported\n// with Vite's `?raw`), so this file must stay plain browser JavaScript: no\n// imports, no TypeScript syntax, no build step. It is checked by tsc via\n// `// @ts-check` + JSDoc and linted like any other source file.\n//\n// This script ships only inside the HTML *viewer file* the CLI writes. It is\n// not — and must never become — part of the library's SVG output, which\n// stays script-free by decision (docs/decisions/no-script-interactivity.md).\n// ============================================================================\n\n;(() => {\n const stage = document.getElementById('stage')\n const diagram = document.getElementById('diagram')\n const svg = diagram ? diagram.querySelector('svg') : null\n const zoomLabel = document.getElementById('zoom')\n const themeButton = document.getElementById('theme')\n if (!stage || !diagram || !svg || !zoomLabel || !themeButton) return\n\n const MIN_SCALE = 0.05\n const MAX_SCALE = 40\n const KEY_PAN_STEP = 40\n const KEY_PAN_STEP_FAST = 160\n\n let scale = 1\n let tx = 0\n let ty = 0\n\n // -- Geometry ---------------------------------------------------------\n\n // Assigned as `const ... = () => {}` rather than `function` declarations\n // from here through stageCenter (and release/setMode further below):\n // these all read stage/diagram/svg/zoomLabel/themeButton, which the\n // guard above narrowed from `T | null` to `T` — a narrowing that only\n // survives for a function *expression* defined after the guard, not a\n // hoisted `function` declaration (tsc can't rule out the declaration\n // being invoked, in theory, before the guard runs).\n\n /** Natural (unscaled) size of the diagram, from the root's width/height or viewBox. */\n const naturalSize = () => {\n const w = Number(svg.getAttribute('width'))\n const h = Number(svg.getAttribute('height'))\n if (w > 0 && h > 0) return { w, h }\n const vb = svg.viewBox.baseVal\n if (vb && vb.width > 0 && vb.height > 0)\n return { w: vb.width, h: vb.height }\n const r = diagram.getBoundingClientRect()\n return { w: r.width / scale || 1, h: r.height / scale || 1 }\n }\n\n /** @param {number} s */\n function clampScale(s) {\n return Math.min(MAX_SCALE, Math.max(MIN_SCALE, s))\n }\n\n const apply = () => {\n diagram.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`\n zoomLabel.textContent = `${Math.round(scale * 100)}%`\n }\n\n /**\n * Zoom by `factor` keeping the stage point (cx, cy) fixed under the cursor.\n * @param {number} factor\n * @param {number} cx\n * @param {number} cy\n */\n function zoomAt(factor, cx, cy) {\n const next = clampScale(scale * factor)\n const k = next / scale\n tx = cx - (cx - tx) * k\n ty = cy - (cy - ty) * k\n scale = next\n apply()\n }\n\n const fit = () => {\n const r = stage.getBoundingClientRect()\n const { w, h } = naturalSize()\n scale = clampScale(Math.min(r.width / w, r.height / h) * 0.95)\n tx = (r.width - w * scale) / 2\n ty = (r.height - h * scale) / 2\n apply()\n }\n\n const oneToOne = () => {\n const r = stage.getBoundingClientRect()\n const { w, h } = naturalSize()\n scale = 1\n tx = Math.max(0, (r.width - w) / 2)\n ty = Math.max(0, (r.height - h) / 2)\n apply()\n }\n\n const stageCenter = () => {\n const r = stage.getBoundingClientRect()\n return { x: r.width / 2, y: r.height / 2 }\n }\n\n // -- Wheel: plain scroll pans; ctrl/cmd + wheel (which is also what a\n // trackpad pinch produces) zooms around the cursor. -------------------\n\n stage.addEventListener(\n 'wheel',\n (e) => {\n e.preventDefault()\n const r = stage.getBoundingClientRect()\n if (e.ctrlKey || e.metaKey) {\n zoomAt(\n Math.exp(-e.deltaY * 0.01),\n e.clientX - r.left,\n e.clientY - r.top,\n )\n } else {\n tx -= e.deltaX\n ty -= e.deltaY\n apply()\n }\n },\n { passive: false },\n )\n\n // -- Pointers: one pointer drags, two pinch-zoom around their midpoint. --\n\n /** @type {Map<number, {x: number, y: number}>} */\n const pointers = new Map()\n /** @type {{dist: number, mid: {x: number, y: number}} | null} */\n let lastPinch = null\n\n stage.addEventListener('pointerdown', (e) => {\n if (e.pointerType === 'mouse' && e.button !== 0) return\n stage.setPointerCapture(e.pointerId)\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })\n stage.classList.add('dragging')\n })\n\n stage.addEventListener('pointermove', (e) => {\n const prev = pointers.get(e.pointerId)\n if (!prev) return\n const current = { x: e.clientX, y: e.clientY }\n pointers.set(e.pointerId, current)\n if (pointers.size === 1) {\n tx += current.x - prev.x\n ty += current.y - prev.y\n apply()\n return\n }\n if (pointers.size === 2) {\n const [a, b] = [...pointers.values()]\n if (!a || !b) return\n const dist = Math.hypot(a.x - b.x, a.y - b.y)\n const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }\n if (lastPinch && lastPinch.dist > 0) {\n const r = stage.getBoundingClientRect()\n zoomAt(dist / lastPinch.dist, mid.x - r.left, mid.y - r.top)\n tx += mid.x - lastPinch.mid.x\n ty += mid.y - lastPinch.mid.y\n apply()\n }\n lastPinch = { dist, mid }\n }\n })\n\n /** @param {PointerEvent} e */\n const release = (e) => {\n pointers.delete(e.pointerId)\n if (pointers.size < 2) lastPinch = null\n if (pointers.size === 0) stage.classList.remove('dragging')\n }\n stage.addEventListener('pointerup', release)\n stage.addEventListener('pointercancel', release)\n\n stage.addEventListener('dblclick', (e) => {\n const r = stage.getBoundingClientRect()\n zoomAt(scale < 1 ? 1 / scale : 2, e.clientX - r.left, e.clientY - r.top)\n })\n\n // -- Theme: follows prefers-color-scheme until the user toggles, unless\n // the page was generated with an explicit --theme (data-theme preset). --\n\n const root = document.documentElement\n const darkMedia = matchMedia('(prefers-color-scheme: dark)')\n let userChose = root.dataset.themeSource === 'theme'\n\n function currentMode() {\n const preset = root.dataset.theme\n if (preset === 'dark' || preset === 'light') return preset\n return darkMedia.matches ? 'dark' : 'light'\n }\n\n /** @param {'light' | 'dark'} mode */\n const setMode = (mode) => {\n root.dataset.theme = mode\n themeButton.textContent = mode === 'dark' ? 'Light' : 'Dark'\n themeButton.setAttribute('aria-pressed', String(mode === 'dark'))\n themeButton.setAttribute(\n 'aria-label',\n `Switch to ${mode === 'dark' ? 'light' : 'dark'} mode (T)`,\n )\n }\n\n function toggleMode() {\n userChose = true\n setMode(currentMode() === 'dark' ? 'light' : 'dark')\n }\n\n darkMedia.addEventListener('change', () => {\n if (!userChose) {\n delete root.dataset.theme\n setMode(currentMode())\n delete root.dataset.theme\n }\n })\n // Reflect the initial mode on the button without pinning it: leave\n // data-theme unset for system mode so a later OS change still applies.\n themeButton.textContent = currentMode() === 'dark' ? 'Light' : 'Dark'\n themeButton.setAttribute('aria-pressed', String(currentMode() === 'dark'))\n\n // -- Buttons -------------------------------------------------------------\n\n /**\n * @param {string} id\n * @param {() => void} handler\n */\n function onClick(id, handler) {\n const el = document.getElementById(id)\n if (el) el.addEventListener('click', handler)\n }\n onClick('zoom-in', () => {\n const c = stageCenter()\n zoomAt(1.25, c.x, c.y)\n })\n onClick('zoom-out', () => {\n const c = stageCenter()\n zoomAt(0.8, c.x, c.y)\n })\n onClick('fit', fit)\n onClick('one', oneToOne)\n onClick('theme', toggleMode)\n\n // -- Keyboard (stage must be focused; it has tabindex=0) -----------------\n\n stage.addEventListener('keydown', (e) => {\n const c = stageCenter()\n const step = e.shiftKey ? KEY_PAN_STEP_FAST : KEY_PAN_STEP\n switch (e.key) {\n case '+':\n case '=':\n zoomAt(1.25, c.x, c.y)\n break\n case '-':\n case '_':\n zoomAt(0.8, c.x, c.y)\n break\n case '0':\n fit()\n break\n case '1':\n oneToOne()\n break\n case 'ArrowLeft':\n tx += step\n apply()\n break\n case 'ArrowRight':\n tx -= step\n apply()\n break\n case 'ArrowUp':\n ty += step\n apply()\n break\n case 'ArrowDown':\n ty -= step\n apply()\n break\n case 't':\n case 'T':\n toggleMode()\n break\n default:\n return\n }\n e.preventDefault()\n })\n\n fit()\n stage.focus({ preventScroll: true })\n})()\n", I = ":root {\n color-scheme: light dark;\n --bg: #f4f4f5;\n --fg: #18181b;\n --chrome-bg: #ffffff;\n --chrome-border: #e4e4e7;\n --chrome-fg: #3f3f46;\n --chrome-fg-hover: #18181b;\n --focus-ring: #2563eb;\n }\n :root[data-theme='dark'] {\n --bg: #09090b;\n --fg: #fafafa;\n --chrome-bg: #18181b;\n --chrome-border: #3f3f46;\n --chrome-fg: #d4d4d8;\n --chrome-fg-hover: #fafafa;\n --focus-ring: #60a5fa;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme='light']) {\n --bg: #09090b;\n --fg: #fafafa;\n --chrome-bg: #18181b;\n --chrome-border: #3f3f46;\n --chrome-fg: #d4d4d8;\n --chrome-fg-hover: #fafafa;\n --focus-ring: #60a5fa;\n }\n }\n * { box-sizing: border-box; }\n html, body {\n margin: 0;\n height: 100%;\n background: var(--bg);\n color: var(--fg);\n font: 14px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n }\n #stage {\n position: fixed;\n inset: 0;\n overflow: hidden;\n cursor: grab;\n touch-action: none;\n outline: none;\n }\n #stage.dragging { cursor: grabbing; }\n #diagram {\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n will-change: transform;\n }\n #diagram svg { display: block; }\n #toolbar {\n position: fixed;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 6px;\n border-radius: 10px;\n background: var(--chrome-bg);\n border: 1px solid var(--chrome-border);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\n }\n #toolbar button {\n appearance: none;\n border: none;\n background: transparent;\n color: var(--chrome-fg);\n font: inherit;\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n min-width: 32px;\n }\n #toolbar button:hover { color: var(--chrome-fg-hover); background: var(--chrome-border); }\n #toolbar button:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 1px;\n }\n #zoom {\n min-width: 3.5em;\n text-align: center;\n color: var(--chrome-fg);\n font-variant-numeric: tabular-nums;\n }\n #stage:focus-visible {\n box-shadow: inset 0 0 0 2px var(--focus-ring);\n }\n @media print {\n #toolbar { display: none; }\n #stage { position: static; overflow: visible; }\n /* Genuinely needs !important (zombie-mermaid#1080 audit): the client\n * script's apply() sets #diagram's transform inline on every pan/zoom,\n * so whatever pan/zoom state the viewer was left in is a live inline\n * style this print rule must beat -- there's no static default to\n * hoist it to instead. */\n #diagram { position: static; transform: none !important; }\n }";
|
|
280
|
-
function
|
|
394
|
+
var he = "// @ts-check\n/// <reference lib=\"dom\" />\n// ============================================================================\n// Client script for the CLI's self-contained HTML viewer (`render --html`).\n//\n// Inlined verbatim into the viewer page by src/cli/html-viewer.ts (imported\n// with Vite's `?raw`), so this file must stay plain browser JavaScript: no\n// imports, no TypeScript syntax, no build step. It is checked by tsc via\n// `// @ts-check` + JSDoc and linted like any other source file.\n//\n// This script ships only inside the HTML *viewer file* the CLI writes. It is\n// not — and must never become — part of the library's SVG output, which\n// stays script-free by decision (docs/decisions/no-script-interactivity.md).\n// ============================================================================\n\n;(() => {\n const stage = document.getElementById('stage')\n const diagram = document.getElementById('diagram')\n const svg = diagram ? diagram.querySelector('svg') : null\n const zoomLabel = document.getElementById('zoom')\n const themeButton = document.getElementById('theme')\n if (!stage || !diagram || !svg || !zoomLabel || !themeButton) return\n\n const MIN_SCALE = 0.05\n const MAX_SCALE = 40\n const KEY_PAN_STEP = 40\n const KEY_PAN_STEP_FAST = 160\n\n let scale = 1\n let tx = 0\n let ty = 0\n\n // -- Geometry ---------------------------------------------------------\n\n // Assigned as `const ... = () => {}` rather than `function` declarations\n // from here through stageCenter (and release/setMode further below):\n // these all read stage/diagram/svg/zoomLabel/themeButton, which the\n // guard above narrowed from `T | null` to `T` — a narrowing that only\n // survives for a function *expression* defined after the guard, not a\n // hoisted `function` declaration (tsc can't rule out the declaration\n // being invoked, in theory, before the guard runs).\n\n /** Natural (unscaled) size of the diagram, from the root's width/height or viewBox. */\n const naturalSize = () => {\n const w = Number(svg.getAttribute('width'))\n const h = Number(svg.getAttribute('height'))\n if (w > 0 && h > 0) return { w, h }\n const vb = svg.viewBox.baseVal\n if (vb && vb.width > 0 && vb.height > 0)\n return { w: vb.width, h: vb.height }\n const r = diagram.getBoundingClientRect()\n return { w: r.width / scale || 1, h: r.height / scale || 1 }\n }\n\n /** @param {number} s */\n function clampScale(s) {\n return Math.min(MAX_SCALE, Math.max(MIN_SCALE, s))\n }\n\n const apply = () => {\n diagram.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`\n zoomLabel.textContent = `${Math.round(scale * 100)}%`\n }\n\n /**\n * Zoom by `factor` keeping the stage point (cx, cy) fixed under the cursor.\n * @param {number} factor\n * @param {number} cx\n * @param {number} cy\n */\n function zoomAt(factor, cx, cy) {\n const next = clampScale(scale * factor)\n const k = next / scale\n tx = cx - (cx - tx) * k\n ty = cy - (cy - ty) * k\n scale = next\n apply()\n }\n\n const fit = () => {\n const r = stage.getBoundingClientRect()\n const { w, h } = naturalSize()\n scale = clampScale(Math.min(r.width / w, r.height / h) * 0.95)\n tx = (r.width - w * scale) / 2\n ty = (r.height - h * scale) / 2\n apply()\n }\n\n const oneToOne = () => {\n const r = stage.getBoundingClientRect()\n const { w, h } = naturalSize()\n scale = 1\n tx = Math.max(0, (r.width - w) / 2)\n ty = Math.max(0, (r.height - h) / 2)\n apply()\n }\n\n const stageCenter = () => {\n const r = stage.getBoundingClientRect()\n return { x: r.width / 2, y: r.height / 2 }\n }\n\n // -- Wheel: plain scroll pans; ctrl/cmd + wheel (which is also what a\n // trackpad pinch produces) zooms around the cursor. -------------------\n\n stage.addEventListener(\n 'wheel',\n (e) => {\n e.preventDefault()\n const r = stage.getBoundingClientRect()\n if (e.ctrlKey || e.metaKey) {\n zoomAt(\n Math.exp(-e.deltaY * 0.01),\n e.clientX - r.left,\n e.clientY - r.top,\n )\n } else {\n tx -= e.deltaX\n ty -= e.deltaY\n apply()\n }\n },\n { passive: false },\n )\n\n // -- Pointers: one pointer drags, two pinch-zoom around their midpoint. --\n\n /** @type {Map<number, {x: number, y: number}>} */\n const pointers = new Map()\n /** @type {{dist: number, mid: {x: number, y: number}} | null} */\n let lastPinch = null\n\n stage.addEventListener('pointerdown', (e) => {\n if (e.pointerType === 'mouse' && e.button !== 0) return\n stage.setPointerCapture(e.pointerId)\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })\n stage.classList.add('dragging')\n })\n\n stage.addEventListener('pointermove', (e) => {\n const prev = pointers.get(e.pointerId)\n if (!prev) return\n const current = { x: e.clientX, y: e.clientY }\n pointers.set(e.pointerId, current)\n if (pointers.size === 1) {\n tx += current.x - prev.x\n ty += current.y - prev.y\n apply()\n return\n }\n if (pointers.size === 2) {\n const [a, b] = [...pointers.values()]\n if (!a || !b) return\n const dist = Math.hypot(a.x - b.x, a.y - b.y)\n const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }\n if (lastPinch && lastPinch.dist > 0) {\n const r = stage.getBoundingClientRect()\n zoomAt(dist / lastPinch.dist, mid.x - r.left, mid.y - r.top)\n tx += mid.x - lastPinch.mid.x\n ty += mid.y - lastPinch.mid.y\n apply()\n }\n lastPinch = { dist, mid }\n }\n })\n\n /** @param {PointerEvent} e */\n const release = (e) => {\n pointers.delete(e.pointerId)\n if (pointers.size < 2) lastPinch = null\n if (pointers.size === 0) stage.classList.remove('dragging')\n }\n stage.addEventListener('pointerup', release)\n stage.addEventListener('pointercancel', release)\n\n stage.addEventListener('dblclick', (e) => {\n const r = stage.getBoundingClientRect()\n zoomAt(scale < 1 ? 1 / scale : 2, e.clientX - r.left, e.clientY - r.top)\n })\n\n // -- Theme: follows prefers-color-scheme until the user toggles, unless\n // the page was generated with an explicit --theme (data-theme preset). --\n\n const root = document.documentElement\n const darkMedia = matchMedia('(prefers-color-scheme: dark)')\n let userChose = root.dataset.themeSource === 'theme'\n\n function currentMode() {\n const preset = root.dataset.theme\n if (preset === 'dark' || preset === 'light') return preset\n return darkMedia.matches ? 'dark' : 'light'\n }\n\n /** @param {'light' | 'dark'} mode */\n const setMode = (mode) => {\n root.dataset.theme = mode\n themeButton.textContent = mode === 'dark' ? 'Light' : 'Dark'\n themeButton.setAttribute('aria-pressed', String(mode === 'dark'))\n themeButton.setAttribute(\n 'aria-label',\n `Switch to ${mode === 'dark' ? 'light' : 'dark'} mode (T)`,\n )\n }\n\n function toggleMode() {\n userChose = true\n setMode(currentMode() === 'dark' ? 'light' : 'dark')\n }\n\n darkMedia.addEventListener('change', () => {\n if (!userChose) {\n delete root.dataset.theme\n setMode(currentMode())\n delete root.dataset.theme\n }\n })\n // Reflect the initial mode on the button without pinning it: leave\n // data-theme unset for system mode so a later OS change still applies.\n themeButton.textContent = currentMode() === 'dark' ? 'Light' : 'Dark'\n themeButton.setAttribute('aria-pressed', String(currentMode() === 'dark'))\n\n // -- Buttons -------------------------------------------------------------\n\n /**\n * @param {string} id\n * @param {() => void} handler\n */\n function onClick(id, handler) {\n const el = document.getElementById(id)\n if (el) el.addEventListener('click', handler)\n }\n onClick('zoom-in', () => {\n const c = stageCenter()\n zoomAt(1.25, c.x, c.y)\n })\n onClick('zoom-out', () => {\n const c = stageCenter()\n zoomAt(0.8, c.x, c.y)\n })\n onClick('fit', fit)\n onClick('one', oneToOne)\n onClick('theme', toggleMode)\n\n // -- Keyboard (stage must be focused; it has tabindex=0) -----------------\n\n stage.addEventListener('keydown', (e) => {\n const c = stageCenter()\n const step = e.shiftKey ? KEY_PAN_STEP_FAST : KEY_PAN_STEP\n switch (e.key) {\n case '+':\n case '=':\n zoomAt(1.25, c.x, c.y)\n break\n case '-':\n case '_':\n zoomAt(0.8, c.x, c.y)\n break\n case '0':\n fit()\n break\n case '1':\n oneToOne()\n break\n case 'ArrowLeft':\n tx += step\n apply()\n break\n case 'ArrowRight':\n tx -= step\n apply()\n break\n case 'ArrowUp':\n ty += step\n apply()\n break\n case 'ArrowDown':\n ty -= step\n apply()\n break\n case 't':\n case 'T':\n toggleMode()\n break\n default:\n return\n }\n e.preventDefault()\n })\n\n fit()\n stage.focus({ preventScroll: true })\n})()\n", ge = ":root {\n color-scheme: light dark;\n --bg: #f4f4f5;\n --fg: #18181b;\n --chrome-bg: #ffffff;\n --chrome-border: #e4e4e7;\n --chrome-fg: #3f3f46;\n --chrome-fg-hover: #18181b;\n --focus-ring: #2563eb;\n }\n :root[data-theme='dark'] {\n --bg: #09090b;\n --fg: #fafafa;\n --chrome-bg: #18181b;\n --chrome-border: #3f3f46;\n --chrome-fg: #d4d4d8;\n --chrome-fg-hover: #fafafa;\n --focus-ring: #60a5fa;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme='light']) {\n --bg: #09090b;\n --fg: #fafafa;\n --chrome-bg: #18181b;\n --chrome-border: #3f3f46;\n --chrome-fg: #d4d4d8;\n --chrome-fg-hover: #fafafa;\n --focus-ring: #60a5fa;\n }\n }\n * { box-sizing: border-box; }\n html, body {\n margin: 0;\n height: 100%;\n background: var(--bg);\n color: var(--fg);\n font: 14px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n }\n #stage {\n position: fixed;\n inset: 0;\n overflow: hidden;\n cursor: grab;\n touch-action: none;\n outline: none;\n }\n #stage.dragging { cursor: grabbing; }\n #diagram {\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n will-change: transform;\n }\n #diagram svg { display: block; }\n #toolbar {\n position: fixed;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 6px;\n border-radius: 10px;\n background: var(--chrome-bg);\n border: 1px solid var(--chrome-border);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\n }\n #toolbar button {\n appearance: none;\n border: none;\n background: transparent;\n color: var(--chrome-fg);\n font: inherit;\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n min-width: 32px;\n }\n #toolbar button:hover { color: var(--chrome-fg-hover); background: var(--chrome-border); }\n #toolbar button:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 1px;\n }\n #zoom {\n min-width: 3.5em;\n text-align: center;\n color: var(--chrome-fg);\n font-variant-numeric: tabular-nums;\n }\n #stage:focus-visible {\n box-shadow: inset 0 0 0 2px var(--focus-ring);\n }\n @media print {\n #toolbar { display: none; }\n #stage { position: static; overflow: visible; }\n /* Genuinely needs !important (zombie-mermaid#1080 audit): the client\n * script's apply() sets #diagram's transform inline on every pan/zoom,\n * so whatever pan/zoom state the viewer was left in is a live inline\n * style this print rule must beat -- there's no static default to\n * hoist it to instead. */\n #diagram { position: static; transform: none !important; }\n }";
|
|
395
|
+
function _e(e) {
|
|
281
396
|
let t = e.title?.trim() || "Mermaid diagram";
|
|
282
397
|
return `<!doctype html>
|
|
283
398
|
<html lang="en">
|
|
@@ -286,7 +401,7 @@ function L(e) {
|
|
|
286
401
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
287
402
|
<title>${o(t)}</title>
|
|
288
403
|
<style>
|
|
289
|
-
${
|
|
404
|
+
${ge}
|
|
290
405
|
</style>
|
|
291
406
|
</head>
|
|
292
407
|
<body>
|
|
@@ -304,7 +419,7 @@ ${e.svg}
|
|
|
304
419
|
<button id="theme" type="button" aria-pressed="false" aria-label="Switch to dark mode (T)">Dark</button>
|
|
305
420
|
</div>
|
|
306
421
|
<script>
|
|
307
|
-
${
|
|
422
|
+
${he}
|
|
308
423
|
<\/script>
|
|
309
424
|
</body>
|
|
310
425
|
</html>
|
|
@@ -312,7 +427,7 @@ ${F}
|
|
|
312
427
|
}
|
|
313
428
|
//#endregion
|
|
314
429
|
//#region src/cli/png.ts
|
|
315
|
-
async function
|
|
430
|
+
async function ve(e) {
|
|
316
431
|
let t;
|
|
317
432
|
try {
|
|
318
433
|
t = await import("@resvg/resvg-js");
|
|
@@ -324,14 +439,14 @@ async function R(e) {
|
|
|
324
439
|
}
|
|
325
440
|
//#endregion
|
|
326
441
|
//#region src/cli/render.ts
|
|
327
|
-
var
|
|
442
|
+
var ye = 80, V = 1, H = 1, U = 0, be = /\x1b\[[0-9;]*m/g;
|
|
328
443
|
function W(e) {
|
|
329
|
-
return e.split("\n").reduce((e, t) => Math.max(e,
|
|
444
|
+
return e.split("\n").reduce((e, t) => Math.max(e, fe(me(t.replace(be, "")))), 0);
|
|
330
445
|
}
|
|
331
446
|
function G(e) {
|
|
332
|
-
return e === "auto" ? process.stdout.columns ??
|
|
447
|
+
return e === "auto" ? process.stdout.columns ?? ye : e;
|
|
333
448
|
}
|
|
334
|
-
async function
|
|
449
|
+
async function xe(e, t, n, r) {
|
|
335
450
|
let i = t ?? process.stdout, a = r ?? process.stderr, o;
|
|
336
451
|
if (e.input !== void 0) o = await u(e.input, "utf-8");
|
|
337
452
|
else if (n !== void 0) o = n;
|
|
@@ -341,24 +456,24 @@ async function de(e, t, n, r) {
|
|
|
341
456
|
}
|
|
342
457
|
if (o.trim().length === 0) throw Error("Empty input — provide a Mermaid diagram via file or stdin");
|
|
343
458
|
try {
|
|
344
|
-
await
|
|
459
|
+
await Se(e, o, i, a);
|
|
345
460
|
} catch (e) {
|
|
346
|
-
throw
|
|
461
|
+
throw B(e, o);
|
|
347
462
|
}
|
|
348
463
|
}
|
|
349
|
-
async function
|
|
464
|
+
async function Se(e, t, n, i) {
|
|
350
465
|
let o;
|
|
351
466
|
if (e.theme !== void 0 && (o = a[e.theme], o === void 0)) {
|
|
352
467
|
let t = Object.keys(a).join(", ");
|
|
353
468
|
throw Error(`Unknown theme: "${e.theme}". Available themes: ${t}`);
|
|
354
469
|
}
|
|
355
|
-
let s = e.svg && e.output === "-", c = e.html && e.output === "-", l = e.png && e.output === "-", u = e.ascii && !e.svg && !e.html && !e.png && e.output !== void 0 && e.output !== "-" ? e.output : void 0, d = e.svg && !s ? e.output : void 0,
|
|
470
|
+
let s = e.svg && e.output === "-", c = e.html && e.output === "-", l = e.png && e.output === "-", u = e.ascii && !e.svg && !e.html && !e.png && e.output !== void 0 && e.output !== "-" ? e.output : void 0, d = e.svg && !s ? e.output : void 0, p = e.html && !c ? e.output : void 0, h = e.png && !l ? e.output : void 0;
|
|
356
471
|
for (let t of [
|
|
357
472
|
u,
|
|
358
473
|
d,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
]) t !== void 0 && await
|
|
474
|
+
p,
|
|
475
|
+
h
|
|
476
|
+
]) t !== void 0 && await Ce(t, e.force);
|
|
362
477
|
if (e.ascii) {
|
|
363
478
|
let r = o && u === void 0 ? {
|
|
364
479
|
colorMode: "auto",
|
|
@@ -367,19 +482,19 @@ async function fe(e, t, n, i) {
|
|
|
367
482
|
e.paddingX !== void 0 && (r.paddingX = e.paddingX), e.paddingY !== void 0 && (r.paddingY = e.paddingY), e.borderPadding !== void 0 && (r.boxBorderPadding = e.borderPadding), e.coords && (r.showCoords = !0), e.profile !== void 0 && (r.profile = e.profile), e.direction !== void 0 && (r.direction = e.direction), e.hyperlinks && u === void 0 && (r.hyperlinks = !0);
|
|
368
483
|
let a, s = !1;
|
|
369
484
|
if (e.layout !== void 0) {
|
|
370
|
-
let n = e.layout === "mermaid-wide" ? Infinity : e.maxWidth === void 0 ? void 0 : G(e.maxWidth), o = await
|
|
485
|
+
let n = e.layout === "mermaid-wide" ? Infinity : e.maxWidth === void 0 ? void 0 : G(e.maxWidth), o = await x(t, {
|
|
371
486
|
...r,
|
|
372
487
|
layout: "mermaid",
|
|
373
488
|
maxCols: n
|
|
374
489
|
});
|
|
375
490
|
a = o.text, s = o.path === "layout-faithful", i.write(s ? `layout-faithful: used mermaid's ELK layout (mermaid 12.1.0 + elkjs 0.9.3). ${o.note ?? ""}`.trimEnd() + "\n" : `layout-faithful: ${o.fallbackReason ?? "unavailable"}; used standard renderer\n`);
|
|
376
|
-
} else a =
|
|
491
|
+
} else a = m(t, r);
|
|
377
492
|
if (e.maxWidth !== void 0) {
|
|
378
493
|
let n = G(e.maxWidth), o = W(a), c = !1;
|
|
379
494
|
if (o > n && !s) {
|
|
380
|
-
let e = r.paddingX ?? 5, n = r.paddingY ?? 5, i = r.boxBorderPadding ?? 1, s = Math.min(e,
|
|
495
|
+
let e = r.paddingX ?? 5, n = r.paddingY ?? 5, i = r.boxBorderPadding ?? 1, s = Math.min(e, V), l = Math.min(n, H), u = Math.min(i, U);
|
|
381
496
|
if (s < e || l < n || u < i) {
|
|
382
|
-
let e =
|
|
497
|
+
let e = m(t, {
|
|
383
498
|
...r,
|
|
384
499
|
paddingX: s,
|
|
385
500
|
paddingY: l,
|
|
@@ -389,9 +504,9 @@ async function fe(e, t, n, i) {
|
|
|
389
504
|
}
|
|
390
505
|
}
|
|
391
506
|
if (o > n) {
|
|
392
|
-
let t = e.maxWidth === "auto" ? "detected terminal width" : "--max-width", r = c ? `Compact spacing (-x ${
|
|
507
|
+
let t = e.maxWidth === "auto" ? "detected terminal width" : "--max-width", r = c ? `Compact spacing (-x ${V} -y ${H} -p ${U}) was already applied automatically. ` : "", a = c ? "Try a narrower direction (LR vs TD), shorter labels, or widen your terminal." : "Try -x/-y/-p for tighter spacing, a narrower direction (LR vs TD), or widen your terminal.";
|
|
393
508
|
i.write(`Warning: ASCII output is ${o} columns wide, exceeding ${t} of ${n}. ${r}zombie-mermaid does not reflow diagram structure to fit (no label wrapping or direction-flip cascade — see https://github.com/dfadler/zombie-mermaid/issues/335). ${a}\n`);
|
|
394
|
-
} else c && i.write(`Note: ASCII output exceeded ${n} columns at the requested spacing; applied compact spacing automatically (-x ${
|
|
509
|
+
} else c && i.write(`Note: ASCII output exceeded ${n} columns at the requested spacing; applied compact spacing automatically (-x ${V} -y ${H} -p ${U}) to fit.\n`);
|
|
395
510
|
}
|
|
396
511
|
await q(K(u === void 0, u), a + "\n", n, e.force);
|
|
397
512
|
}
|
|
@@ -401,15 +516,15 @@ async function fe(e, t, n, i) {
|
|
|
401
516
|
resolveColors: e.resolveColors
|
|
402
517
|
};
|
|
403
518
|
e.direction !== void 0 && (r.direction = e.direction);
|
|
404
|
-
let i =
|
|
519
|
+
let i = y(t, r);
|
|
405
520
|
await q(K(s, d), i, n, e.force);
|
|
406
521
|
}
|
|
407
522
|
if (e.html) {
|
|
408
|
-
let i =
|
|
409
|
-
svg:
|
|
523
|
+
let i = _e({
|
|
524
|
+
svg: y(t, o ?? {}),
|
|
410
525
|
title: e.input ? r(e.input).name : void 0
|
|
411
526
|
});
|
|
412
|
-
await q(K(c,
|
|
527
|
+
await q(K(c, p), i, n, e.force);
|
|
413
528
|
}
|
|
414
529
|
if (e.png) {
|
|
415
530
|
let r = {
|
|
@@ -417,8 +532,8 @@ async function fe(e, t, n, i) {
|
|
|
417
532
|
resolveColors: !0
|
|
418
533
|
};
|
|
419
534
|
e.direction !== void 0 && (r.direction = e.direction);
|
|
420
|
-
let i = await
|
|
421
|
-
await q(K(l,
|
|
535
|
+
let i = await ve(y(t, r));
|
|
536
|
+
await q(K(l, h), i, n, e.force);
|
|
422
537
|
}
|
|
423
538
|
}
|
|
424
539
|
function K(e, t) {
|
|
@@ -432,13 +547,13 @@ async function q(e, t, n, r) {
|
|
|
432
547
|
case "stdout":
|
|
433
548
|
n.write(t);
|
|
434
549
|
break;
|
|
435
|
-
case "file": await
|
|
550
|
+
case "file": await we(e.path, t, r);
|
|
436
551
|
}
|
|
437
552
|
}
|
|
438
553
|
function J(e) {
|
|
439
554
|
return /* @__PURE__ */ Error(`Refusing to overwrite existing file "${e}" — pass --force to replace it`);
|
|
440
555
|
}
|
|
441
|
-
async function
|
|
556
|
+
async function Ce(e, t) {
|
|
442
557
|
if (t) return;
|
|
443
558
|
let n = !0;
|
|
444
559
|
try {
|
|
@@ -448,7 +563,7 @@ async function pe(e, t) {
|
|
|
448
563
|
}
|
|
449
564
|
if (n) throw J(e);
|
|
450
565
|
}
|
|
451
|
-
async function
|
|
566
|
+
async function we(e, t, n) {
|
|
452
567
|
try {
|
|
453
568
|
await d(e, t, {
|
|
454
569
|
encoding: "utf-8",
|
|
@@ -466,18 +581,18 @@ async function Y() {
|
|
|
466
581
|
}
|
|
467
582
|
//#endregion
|
|
468
583
|
//#region src/cli/check.ts
|
|
469
|
-
var
|
|
470
|
-
function
|
|
471
|
-
let n =
|
|
584
|
+
var Te = /^ {0,3}(`{3,}|~{3,})\s*mermaid\b[^`]*$/i, Ee = /^ {0,3}(`+|~+)\s*$/;
|
|
585
|
+
function De(e, t) {
|
|
586
|
+
let n = Ee.exec(e)?.[1];
|
|
472
587
|
return !!n && n[0] === t[0] && n.length >= t.length;
|
|
473
588
|
}
|
|
474
|
-
function
|
|
589
|
+
function Oe(e) {
|
|
475
590
|
let t = e.split(/\r?\n/), n = [];
|
|
476
591
|
for (let e = 0; e < t.length; e++) {
|
|
477
|
-
let r =
|
|
592
|
+
let r = Te.exec(t[e]);
|
|
478
593
|
if (!r) continue;
|
|
479
594
|
let i = r[1], a = [], o = e + 1;
|
|
480
|
-
for (; o < t.length && !
|
|
595
|
+
for (; o < t.length && !De(t[o], i);) a.push(t[o++]);
|
|
481
596
|
n.push({
|
|
482
597
|
startLine: e + 2,
|
|
483
598
|
source: a.join("\n")
|
|
@@ -485,17 +600,17 @@ function ve(e) {
|
|
|
485
600
|
}
|
|
486
601
|
return n;
|
|
487
602
|
}
|
|
488
|
-
async function
|
|
603
|
+
async function ke(e) {
|
|
489
604
|
let t = 0, n = 0;
|
|
490
605
|
for (let r of e.files) {
|
|
491
606
|
let e = await u(r, "utf-8");
|
|
492
|
-
for (let i of
|
|
607
|
+
for (let i of Oe(e)) {
|
|
493
608
|
n++;
|
|
494
609
|
try {
|
|
495
|
-
|
|
610
|
+
b(i.source);
|
|
496
611
|
} catch (e) {
|
|
497
612
|
t++;
|
|
498
|
-
let n =
|
|
613
|
+
let n = z(e, i.source), a = i.startLine + (n ? n.line - 1 : 0), o = n ? n.message : e instanceof Error ? e.message : String(e);
|
|
499
614
|
console.error(`${r}:${a}: ${o}`), n && console.error(` ${a} | ${n.sourceLine}`);
|
|
500
615
|
}
|
|
501
616
|
}
|
|
@@ -505,7 +620,7 @@ async function ye(e) {
|
|
|
505
620
|
}
|
|
506
621
|
//#endregion
|
|
507
622
|
//#region src/cli/validate.ts
|
|
508
|
-
async function
|
|
623
|
+
async function Ae(e, t) {
|
|
509
624
|
let n;
|
|
510
625
|
if (e.input !== void 0) n = await u(e.input, "utf-8");
|
|
511
626
|
else if (t !== void 0) n = t;
|
|
@@ -516,20 +631,20 @@ async function be(e, t) {
|
|
|
516
631
|
let r = n;
|
|
517
632
|
if (r.trim().length === 0) throw Error("Empty input — provide a Mermaid diagram via file or stdin");
|
|
518
633
|
try {
|
|
519
|
-
|
|
634
|
+
b(r);
|
|
520
635
|
} catch (e) {
|
|
521
|
-
throw
|
|
636
|
+
throw B(e, r);
|
|
522
637
|
}
|
|
523
638
|
console.log("valid");
|
|
524
639
|
}
|
|
525
640
|
//#endregion
|
|
526
641
|
//#region src/cli/web.ts
|
|
527
|
-
var
|
|
528
|
-
function
|
|
642
|
+
var je = 1e6;
|
|
643
|
+
function Me(e) {
|
|
529
644
|
return new Promise((t, n) => {
|
|
530
645
|
let r = [], i = 0;
|
|
531
646
|
e.on("data", (t) => {
|
|
532
|
-
if (i += t.length, i >
|
|
647
|
+
if (i += t.length, i > je) {
|
|
533
648
|
n(/* @__PURE__ */ Error("Request body too large")), e.destroy();
|
|
534
649
|
return;
|
|
535
650
|
}
|
|
@@ -540,7 +655,7 @@ function Se(e) {
|
|
|
540
655
|
function X(e) {
|
|
541
656
|
return typeof e == "object" && !!e;
|
|
542
657
|
}
|
|
543
|
-
function
|
|
658
|
+
function Ne(e) {
|
|
544
659
|
let t;
|
|
545
660
|
try {
|
|
546
661
|
t = JSON.parse(e);
|
|
@@ -555,80 +670,78 @@ function Z(e) {
|
|
|
555
670
|
format: t.format === "svg" ? "svg" : "ascii"
|
|
556
671
|
};
|
|
557
672
|
}
|
|
558
|
-
function
|
|
673
|
+
function Z(e, t, n) {
|
|
559
674
|
let r = JSON.stringify(n);
|
|
560
675
|
e.writeHead(t, {
|
|
561
676
|
"Content-Type": "application/json; charset=utf-8",
|
|
562
677
|
"Content-Length": Buffer.byteLength(r)
|
|
563
678
|
}), e.end(r);
|
|
564
679
|
}
|
|
565
|
-
async function
|
|
680
|
+
async function Pe(e, t) {
|
|
566
681
|
try {
|
|
567
|
-
let { source: n, format: r } =
|
|
568
|
-
|
|
569
|
-
output: r === "svg" ?
|
|
682
|
+
let { source: n, format: r } = Ne(await Me(e));
|
|
683
|
+
Z(t, 200, {
|
|
684
|
+
output: r === "svg" ? y(n) : m(n),
|
|
570
685
|
format: r
|
|
571
686
|
});
|
|
572
687
|
} catch (e) {
|
|
573
|
-
|
|
688
|
+
Z(t, 400, { error: e instanceof Error ? e.message : String(e) });
|
|
574
689
|
}
|
|
575
690
|
}
|
|
576
|
-
var
|
|
577
|
-
function
|
|
578
|
-
return
|
|
691
|
+
var Fe = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>zombie-mermaid</title>\n<style>\n body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 900px; }\n h1 { font-size: 1.25rem; }\n textarea { width: 100%; height: 200px; font-family: monospace; font-size: 14px; box-sizing: border-box; }\n pre { white-space: pre; overflow: auto; background: #111; color: #eee; padding: 1rem; border-radius: 4px; }\n .row { display: flex; gap: 1rem; align-items: center; margin: 0.75rem 0; }\n #error { color: #c00; white-space: pre-wrap; }\n #output svg { max-width: 100%; }\n</style>\n</head>\n<body>\n<h1>zombie-mermaid</h1>\n<textarea id=\"source\" placeholder=\"graph LR\n A --> B\">graph LR\n A --> B --> C</textarea>\n<div class=\"row\">\n <label><input type=\"radio\" name=\"format\" value=\"ascii\" checked /> ASCII</label>\n <label><input type=\"radio\" name=\"format\" value=\"svg\" /> SVG</label>\n <button id=\"render\" type=\"button\">Render</button>\n</div>\n<div id=\"error\"></div>\n<div id=\"output\"></div>\n<script>\n const sourceEl = document.getElementById('source')\n const outputEl = document.getElementById('output')\n const errorEl = document.getElementById('error')\n\n async function render() {\n errorEl.textContent = ''\n outputEl.innerHTML = ''\n const formatInput = document.querySelector('input[name=\"format\"]:checked')\n const format = formatInput ? formatInput.value : 'ascii'\n try {\n const res = await fetch('/render', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ source: sourceEl.value, format }),\n })\n const data = await res.json()\n if (!res.ok) {\n errorEl.textContent = data.error || 'Render failed'\n return\n }\n if (format === 'svg') {\n outputEl.innerHTML = data.output\n } else {\n const pre = document.createElement('pre')\n pre.textContent = data.output\n outputEl.appendChild(pre)\n }\n } catch (err) {\n errorEl.textContent = String(err)\n }\n }\n\n document.getElementById('render').addEventListener('click', render)\n<\/script>\n</body>\n</html>\n";
|
|
692
|
+
function Q() {
|
|
693
|
+
return S((e, t) => {
|
|
579
694
|
let n = (e.url ?? "/").split("?")[0];
|
|
580
695
|
if (e.method === "GET" && (n === "/" || n === "")) {
|
|
581
|
-
t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(
|
|
696
|
+
t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(Fe);
|
|
582
697
|
return;
|
|
583
698
|
}
|
|
584
699
|
if (e.method === "POST" && n === "/render") {
|
|
585
|
-
|
|
700
|
+
Pe(e, t);
|
|
586
701
|
return;
|
|
587
702
|
}
|
|
588
703
|
t.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }), t.end("Not found");
|
|
589
704
|
});
|
|
590
705
|
}
|
|
591
|
-
function
|
|
706
|
+
function Ie(e) {
|
|
592
707
|
return new Promise((t, n) => {
|
|
593
|
-
let r =
|
|
708
|
+
let r = Q();
|
|
594
709
|
r.once("error", n), r.listen(e, "127.0.0.1", () => {
|
|
595
710
|
r.removeListener("error", n), t(r);
|
|
596
711
|
});
|
|
597
712
|
});
|
|
598
713
|
}
|
|
599
|
-
async function
|
|
600
|
-
let n = await
|
|
714
|
+
async function Le(e, t = console.log) {
|
|
715
|
+
let n = await Ie(e.port), r = n.address();
|
|
601
716
|
return t(`zombie-mermaid web running at http://localhost:${X(r) && typeof r.port == "number" ? r.port : e.port}`), n;
|
|
602
717
|
}
|
|
603
718
|
//#endregion
|
|
604
719
|
//#region src/cli/mcp.ts
|
|
605
|
-
async function
|
|
606
|
-
let t =
|
|
720
|
+
async function Re(e = (e) => console.error(e)) {
|
|
721
|
+
let t = w(), n = new C();
|
|
607
722
|
return await t.connect(n), e("zombie-mermaid MCP server running on stdio (experimental — feedback welcome: https://github.com/dfadler/zombie-mermaid/issues)"), t;
|
|
608
723
|
}
|
|
609
724
|
//#endregion
|
|
610
725
|
//#region src/package-info.ts
|
|
611
|
-
function
|
|
726
|
+
function ze() {
|
|
612
727
|
let t = e(import.meta.url)("../package.json");
|
|
613
728
|
return typeof t == "object" && t && "version" in t && typeof t.version == "string" ? t.version : "unknown";
|
|
614
729
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
m(x);
|
|
618
|
-
var Ae = ke();
|
|
730
|
+
v(T), L();
|
|
731
|
+
var Be = ze();
|
|
619
732
|
async function $() {
|
|
620
733
|
let e = process.argv.slice(2), t;
|
|
621
734
|
try {
|
|
622
|
-
t =
|
|
735
|
+
t = te(e);
|
|
623
736
|
} catch (e) {
|
|
624
737
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
625
738
|
}
|
|
626
739
|
switch (t.command) {
|
|
627
740
|
case "help":
|
|
628
|
-
|
|
741
|
+
Ve();
|
|
629
742
|
break;
|
|
630
743
|
case "version":
|
|
631
|
-
console.log(`zombie-mermaid ${
|
|
744
|
+
console.log(`zombie-mermaid ${Be}`);
|
|
632
745
|
break;
|
|
633
746
|
case "themes":
|
|
634
747
|
console.log("Available themes:\n");
|
|
@@ -636,40 +749,40 @@ async function $() {
|
|
|
636
749
|
break;
|
|
637
750
|
case "render":
|
|
638
751
|
try {
|
|
639
|
-
await
|
|
752
|
+
await xe(t);
|
|
640
753
|
} catch (e) {
|
|
641
754
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
642
755
|
}
|
|
643
756
|
break;
|
|
644
757
|
case "validate":
|
|
645
758
|
try {
|
|
646
|
-
await
|
|
759
|
+
await Ae(t);
|
|
647
760
|
} catch (e) {
|
|
648
761
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
649
762
|
}
|
|
650
763
|
break;
|
|
651
764
|
case "check":
|
|
652
765
|
try {
|
|
653
|
-
await
|
|
766
|
+
await ke(t);
|
|
654
767
|
} catch (e) {
|
|
655
768
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
656
769
|
}
|
|
657
770
|
break;
|
|
658
771
|
case "web":
|
|
659
772
|
try {
|
|
660
|
-
await
|
|
773
|
+
await Le(t);
|
|
661
774
|
} catch (e) {
|
|
662
775
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
663
776
|
}
|
|
664
777
|
break;
|
|
665
778
|
case "mcp": try {
|
|
666
|
-
await
|
|
779
|
+
await Re();
|
|
667
780
|
} catch (e) {
|
|
668
781
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
669
782
|
}
|
|
670
783
|
}
|
|
671
784
|
}
|
|
672
|
-
function
|
|
785
|
+
function Ve() {
|
|
673
786
|
console.log("zombie-mermaid — render Mermaid diagrams from the command line\n\nUsage:\n zombie-mermaid render <file> --ascii Render to ASCII in terminal\n zombie-mermaid render <file> --svg Render to <file stem>.svg\n zombie-mermaid render <file> --svg -o <out.svg> Render to a named SVG file\n zombie-mermaid render <file> -o out.svg Format inferred from the extension\n zombie-mermaid render <file> --svg -o - Write SVG to stdout\n zombie-mermaid render <file> --ascii --svg -o <out.svg> Both (ASCII to terminal)\n zombie-mermaid render <file> --html Self-contained pan/zoom HTML viewer\n zombie-mermaid render <file> --png Rasterize to <file stem>.png\n cat file.mmd | zombie-mermaid render --ascii Read from stdin\n zombie-mermaid validate [file] Parse only; exit 1 with line diagnostics if invalid\n zombie-mermaid --check <file.md>... Validate every mermaid fenced block in Markdown; exit 1 with file:line diagnostics\n zombie-mermaid themes List available themes\n zombie-mermaid web [--port <n>] Start a local web UI (default port: 3000)\n zombie-mermaid mcp Start an MCP server on stdio (render tools)\n zombie-mermaid --help Show this help\n zombie-mermaid --version Show version\n\nOptions:\n --ascii Print ASCII/Unicode diagram to terminal, or to -o <file.txt>\n when it is the only format\n --svg Render SVG to -o <path> (default: <input stem>.svg;\n stdin input must give -o)\n --resolve-colors Replace CSS var()/color-mix() in the SVG with computed\n sRGB values, for rasterizers and other non-browser SVG\n consumers (resvg, librsvg, Inkscape) that don't\n evaluate them. The default output stays a live function\n of its CSS variables. Requires --svg.\n --html Render a self-contained HTML pan/zoom viewer to -o\n <path> (default: <input stem>.html). Embeds the SVG;\n no server, no network, opens from disk. Cannot be\n combined with --svg/--png (different content, same\n slot).\n --png Rasterize to PNG at -o <path> (default: <input\n stem>.png), via the optional @resvg/resvg-js native\n dependency — install it with `pnpm add\n @resvg/resvg-js` if it's missing. Always resolves\n CSS var()/color-mix() first (as --resolve-colors\n does for --svg), since a rasterizer can't evaluate\n them. 1:1 with the SVG's own pixel size, no scaling.\n Cannot be combined with --svg/--html.\n -o, --output <path> Output file for the SVG/HTML/PNG (or for ASCII when\n none of those is given). '-' writes to stdout. A\n .svg, .html, .txt, or .png extension selects the\n format when no --svg/--html/--png/--ascii flag is\n given; one that contradicts the flags is an error.\n Existing files are never overwritten without --force.\n -f, --force Overwrite an existing output file\n --theme <name> Apply a built-in theme (see 'themes' command)\n --direction <dir> Override the diagram's layout direction: TD, TB, BT, LR, or RL\n (flowchart, state, and ER diagrams; nested subgraph\n directions still apply on top of it)\n -x, --paddingX <n> Horizontal spacing between nodes (ASCII, default: 5)\n -y, --paddingY <n> Vertical spacing between nodes (ASCII, default: 5; LR/RL sibling gap is n-1, min 1)\n -p, --borderPadding <n> Padding inside node boxes (ASCII, default: 1)\n --coords Overlay row/column index rulers (ASCII, debug layout)\n --hyperlinks Emit OSC 8 terminal hyperlinks for `click` hrefs\n (ASCII). Off by default: terminal support varies —\n iTerm2, WezTerm, kitty, Windows Terminal, and\n VTE-based terminals render them as clickable links;\n others may show the URL or nothing. Pagers need\n OSC 8 support too (`less -R`, recent versions).\n --profile <name> ASCII preset: 'markdown' = plain ASCII characters, no\n color, for pasting into issues/commits/fenced blocks\n (explicit flags like --coords still win). Requires\n --ascii.\n -w, --max-width <n|auto> Fit ASCII output within this many columns\n ('auto' detects the terminal width): applies compact\n spacing automatically when needed, and warns on\n stderr if it still doesn't fit (no label wrapping or\n direction-flip — see issue #335).\n --layout mermaid Opt in (flowcharts, --ascii): draw mermaid's own ELK layout\n instead of the grid renderer. Subgraphs, some shapes,\n other diagram types, layout failures and drawings that\n cannot fit 120 columns (or -w <n>, used as the cap)\n fall back to the default renderer; stderr says which\n one ran. --layout mermaid-wide drops the cap and always\n draws the (possibly wide) layout-faithful text. Merged\n parallel edges hide edge multiplicity. Depends on\n mermaid 12.1.0 + elkjs 0.9.3: install the optional peer\n with `pnpm add elkjs-0.9.3@npm:elkjs@0.9.3`. -x/-y/-p\n and --coords do not apply to the layout-faithful drawing.\n --port <n> Port for the 'web' command (default: 3000)\n -h, --help Show help\n -v, --version Show version");
|
|
674
787
|
}
|
|
675
788
|
$();
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@zombie-mermaid/
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@zombie-mermaid/ascii-renderer"),t=require("@zombie-mermaid/svg-renderer"),n=require("@zombie-mermaid/core"),r=require("@zombie-mermaid/mermaid-parser");var i=.5,a=(e,t,n)=>{if(e.diamond){let[r,i,a]=n?[t.x-e.cx,e.w/2,t.y-e.cy]:[t.y-e.cy,e.h/2,t.x-e.cx];return Math.abs(r)/i>=.49?n?r<0?`L`:`R`:r<0?`T`:`B`:n?a<0?`T`:`B`:a<0?`L`:`R`}let r={T:Math.abs(t.y-(e.cy-e.h/2)),B:Math.abs(t.y-(e.cy+e.h/2)),L:Math.abs(t.x-(e.cx-e.w/2)),R:Math.abs(t.x-(e.cx+e.w/2))};return Object.keys(r).reduce((e,t)=>r[t]<r[e]?t:e)};function o(e,t,n){let r=n===`LR`||n===`RL`,o=e=>r?e.cx:e.cy,s=e=>r?e.cy:e.cx,c=e=>(r?e.w:e.h)/2,l=[...e].sort((e,t)=>o(e)-o(t)),u=new Map,d=-1,f,p=(e,t)=>Math.abs(e-t)<=20;for(let e of l)(!f||!(p(o(e),o(f))||p(o(e)-c(e),o(f)-c(f))||p(o(e)+c(e),o(f)+c(f))))&&(d++,f=e),u.set(e.id,d);let m=d,h=n===`RL`||n===`BT`,g=new Map,_=new Map;for(let t of[...e].sort((e,t)=>s(e)-s(t))){let e=u.get(t.id),n=_.get(e)??0;_.set(e,n+1),g.set(t.id,{layer:h?m-e:e,order:n,cross:s(t),size:r?t.h:t.w})}let v=new Map(e.map(e=>[e.id,e])),y=new Map,b=t.map((e,t)=>{let n=v.get(e.from),i=v.get(e.to);if(!n||!i||e.points.length<2)return;let o=e.points[0],s=e.points[e.points.length-1],c=(e,n,i)=>{let o=a(e,n,r),s={edge:t,start:i,face:o,at:o===`T`||o===`B`?n.x:n.y},c=`${e.id}|${o}`;return y.set(c,[...y.get(c)??[],s]),s};return{p:e,s:c(n,o,!0),e:c(i,s,!1)}}),x=(e,t)=>{let n=[...y.get(`${e}|${t.face}`)].map(e=>e.at);return new Set(n.filter(e=>e<t.at-i)).size},S=[];return b.forEach((e,t)=>{e&&S.push({index:t,from:e.p.from,to:e.p.to,start:{face:e.s.face,order:x(e.p.from,e.s)},end:{face:e.e.face,order:x(e.p.to,e.e)}})}),{nodes:g,edges:S}}var s=e=>{try{return e()}catch(e){if(e instanceof t.ElkNotRegisteredError)return;throw e}},c={graph:e=>s(()=>{let n=(0,t.layoutFlowchartSync)(e);return o(n.nodes.map(e=>({id:e.id,cx:e.x+e.width/2,cy:e.y+e.height/2,w:e.width,h:e.height,diamond:e.shape===`diamond`})),n.edges.map(e=>({from:e.source,to:e.target,points:e.points})),e.direction)}),er:e=>s(()=>{let n=(0,t.layoutErDiagramSync)(e);return o(n.entities.map(e=>({id:e.id,cx:e.x+e.width/2,cy:e.y+e.height/2,w:e.width,h:e.height})),n.relationships.map(e=>({from:e.entity1,to:e.entity2,points:e.points})),e.direction??`TD`)})};function l(){(0,e.registerAsciiLayout)(c)}l(),Object.defineProperty(exports,"DEFAULTS",{enumerable:!0,get:function(){return n.DEFAULTS}}),Object.defineProperty(exports,"DEFAULT_RENDER_LIMITS",{enumerable:!0,get:function(){return n.DEFAULT_RENDER_LIMITS}}),Object.defineProperty(exports,"ElkNotRegisteredError",{enumerable:!0,get:function(){return t.ElkNotRegisteredError}}),Object.defineProperty(exports,"LimitExceededError",{enumerable:!0,get:function(){return n.LimitExceededError}}),Object.defineProperty(exports,"THEMES",{enumerable:!0,get:function(){return n.THEMES}}),Object.defineProperty(exports,"createLayoutCache",{enumerable:!0,get:function(){return t.createLayoutCache}}),Object.defineProperty(exports,"fromShikiTheme",{enumerable:!0,get:function(){return n.fromShikiTheme}}),Object.defineProperty(exports,"loadEmbeddedFont",{enumerable:!0,get:function(){return t.loadEmbeddedFont}}),Object.defineProperty(exports,"parseMermaid",{enumerable:!0,get:function(){return r.parseMermaid}}),Object.defineProperty(exports,"registerElk",{enumerable:!0,get:function(){return t.registerElk}}),Object.defineProperty(exports,"renderMermaid",{enumerable:!0,get:function(){return t.renderMermaid}}),Object.defineProperty(exports,"renderMermaidASCII",{enumerable:!0,get:function(){return e.renderMermaidASCII}}),Object.defineProperty(exports,"renderMermaidAscii",{enumerable:!0,get:function(){return e.renderMermaidAscii}}),Object.defineProperty(exports,"renderMermaidSVG",{enumerable:!0,get:function(){return t.renderMermaidSVG}}),Object.defineProperty(exports,"renderMermaidSVGAsync",{enumerable:!0,get:function(){return t.renderMermaidSVGAsync}}),Object.defineProperty(exports,"renderMermaidSync",{enumerable:!0,get:function(){return t.renderMermaidSync}}),Object.defineProperty(exports,"themeCssVariables",{enumerable:!0,get:function(){return t.themeCssVariables}}),Object.defineProperty(exports,"validateMermaid",{enumerable:!0,get:function(){return t.validateMermaid}});
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
1
|
+
import { registerAsciiLayout as e, renderMermaidASCII as t, renderMermaidAscii as n } from "@zombie-mermaid/ascii-renderer";
|
|
2
|
+
import { ElkNotRegisteredError as r, ElkNotRegisteredError as i, createLayoutCache as a, layoutErDiagramSync as o, layoutFlowchartSync as s, loadEmbeddedFont as c, registerElk as l, renderMermaid as u, renderMermaidSVG as d, renderMermaidSVGAsync as f, renderMermaidSync as p, themeCssVariables as m, validateMermaid as h } from "@zombie-mermaid/svg-renderer";
|
|
3
|
+
import { DEFAULTS as g, DEFAULT_RENDER_LIMITS as _, LimitExceededError as v, THEMES as y, fromShikiTheme as b } from "@zombie-mermaid/core";
|
|
4
|
+
import { parseMermaid as x } from "@zombie-mermaid/mermaid-parser";
|
|
5
|
+
var S = .5, C = (e, t, n) => {
|
|
6
|
+
if (e.diamond) {
|
|
7
|
+
let [r, i, a] = n ? [
|
|
8
|
+
t.x - e.cx,
|
|
9
|
+
e.w / 2,
|
|
10
|
+
t.y - e.cy
|
|
11
|
+
] : [
|
|
12
|
+
t.y - e.cy,
|
|
13
|
+
e.h / 2,
|
|
14
|
+
t.x - e.cx
|
|
15
|
+
];
|
|
16
|
+
return Math.abs(r) / i >= .49 ? n ? r < 0 ? "L" : "R" : r < 0 ? "T" : "B" : n ? a < 0 ? "T" : "B" : a < 0 ? "L" : "R";
|
|
17
|
+
}
|
|
18
|
+
let r = {
|
|
19
|
+
T: Math.abs(t.y - (e.cy - e.h / 2)),
|
|
20
|
+
B: Math.abs(t.y - (e.cy + e.h / 2)),
|
|
21
|
+
L: Math.abs(t.x - (e.cx - e.w / 2)),
|
|
22
|
+
R: Math.abs(t.x - (e.cx + e.w / 2))
|
|
23
|
+
};
|
|
24
|
+
return Object.keys(r).reduce((e, t) => r[t] < r[e] ? t : e);
|
|
25
|
+
};
|
|
26
|
+
function w(e, t, n) {
|
|
27
|
+
let r = n === "LR" || n === "RL", i = (e) => r ? e.cx : e.cy, a = (e) => r ? e.cy : e.cx, o = (e) => (r ? e.w : e.h) / 2, s = [...e].sort((e, t) => i(e) - i(t)), c = /* @__PURE__ */ new Map(), l = -1, u, d = (e, t) => Math.abs(e - t) <= 20;
|
|
28
|
+
for (let e of s) (!u || !(d(i(e), i(u)) || d(i(e) - o(e), i(u) - o(u)) || d(i(e) + o(e), i(u) + o(u)))) && (l++, u = e), c.set(e.id, l);
|
|
29
|
+
let f = l, p = n === "RL" || n === "BT", m = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Map();
|
|
30
|
+
for (let t of [...e].sort((e, t) => a(e) - a(t))) {
|
|
31
|
+
let e = c.get(t.id), n = h.get(e) ?? 0;
|
|
32
|
+
h.set(e, n + 1), m.set(t.id, {
|
|
33
|
+
layer: p ? f - e : e,
|
|
34
|
+
order: n,
|
|
35
|
+
cross: a(t),
|
|
36
|
+
size: r ? t.h : t.w
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
let g = new Map(e.map((e) => [e.id, e])), _ = /* @__PURE__ */ new Map(), v = t.map((e, t) => {
|
|
40
|
+
let n = g.get(e.from), i = g.get(e.to);
|
|
41
|
+
if (!n || !i || e.points.length < 2) return;
|
|
42
|
+
let a = e.points[0], o = e.points[e.points.length - 1], s = (e, n, i) => {
|
|
43
|
+
let a = C(e, n, r), o = {
|
|
44
|
+
edge: t,
|
|
45
|
+
start: i,
|
|
46
|
+
face: a,
|
|
47
|
+
at: a === "T" || a === "B" ? n.x : n.y
|
|
48
|
+
}, s = `${e.id}|${a}`;
|
|
49
|
+
return _.set(s, [..._.get(s) ?? [], o]), o;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
p: e,
|
|
53
|
+
s: s(n, a, !0),
|
|
54
|
+
e: s(i, o, !1)
|
|
55
|
+
};
|
|
56
|
+
}), y = (e, t) => {
|
|
57
|
+
let n = [..._.get(`${e}|${t.face}`)].map((e) => e.at);
|
|
58
|
+
return new Set(n.filter((e) => e < t.at - S)).size;
|
|
59
|
+
}, b = [];
|
|
60
|
+
return v.forEach((e, t) => {
|
|
61
|
+
e && b.push({
|
|
62
|
+
index: t,
|
|
63
|
+
from: e.p.from,
|
|
64
|
+
to: e.p.to,
|
|
65
|
+
start: {
|
|
66
|
+
face: e.s.face,
|
|
67
|
+
order: y(e.p.from, e.s)
|
|
68
|
+
},
|
|
69
|
+
end: {
|
|
70
|
+
face: e.e.face,
|
|
71
|
+
order: y(e.p.to, e.e)
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}), {
|
|
75
|
+
nodes: m,
|
|
76
|
+
edges: b
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var T = (e) => {
|
|
80
|
+
try {
|
|
81
|
+
return e();
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (e instanceof i) return;
|
|
84
|
+
throw e;
|
|
85
|
+
}
|
|
86
|
+
}, E = {
|
|
87
|
+
graph: (e) => T(() => {
|
|
88
|
+
let t = s(e);
|
|
89
|
+
return w(t.nodes.map((e) => ({
|
|
90
|
+
id: e.id,
|
|
91
|
+
cx: e.x + e.width / 2,
|
|
92
|
+
cy: e.y + e.height / 2,
|
|
93
|
+
w: e.width,
|
|
94
|
+
h: e.height,
|
|
95
|
+
diamond: e.shape === "diamond"
|
|
96
|
+
})), t.edges.map((e) => ({
|
|
97
|
+
from: e.source,
|
|
98
|
+
to: e.target,
|
|
99
|
+
points: e.points
|
|
100
|
+
})), e.direction);
|
|
101
|
+
}),
|
|
102
|
+
er: (e) => T(() => {
|
|
103
|
+
let t = o(e);
|
|
104
|
+
return w(t.entities.map((e) => ({
|
|
105
|
+
id: e.id,
|
|
106
|
+
cx: e.x + e.width / 2,
|
|
107
|
+
cy: e.y + e.height / 2,
|
|
108
|
+
w: e.width,
|
|
109
|
+
h: e.height
|
|
110
|
+
})), t.relationships.map((e) => ({
|
|
111
|
+
from: e.entity1,
|
|
112
|
+
to: e.entity2,
|
|
113
|
+
points: e.points
|
|
114
|
+
})), e.direction ?? "TD");
|
|
115
|
+
})
|
|
116
|
+
};
|
|
117
|
+
function D() {
|
|
118
|
+
e(E);
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/index.ts
|
|
122
|
+
D();
|
|
123
|
+
//#endregion
|
|
124
|
+
export { g as DEFAULTS, _ as DEFAULT_RENDER_LIMITS, r as ElkNotRegisteredError, v as LimitExceededError, y as THEMES, a as createLayoutCache, b as fromShikiTheme, c as loadEmbeddedFont, x as parseMermaid, l as registerElk, u as renderMermaid, t as renderMermaidASCII, n as renderMermaidAscii, d as renderMermaidSVG, f as renderMermaidSVGAsync, p as renderMermaidSync, m as themeCssVariables, h as validateMermaid };
|
package/dist/mcp.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=require("@zombie-mermaid/mcp");Object.keys(
|
|
1
|
+
let e=require("@zombie-mermaid/ascii-renderer"),t=require("@zombie-mermaid/svg-renderer");var n=.5,r=(e,t,n)=>{if(e.diamond){let[r,i,a]=n?[t.x-e.cx,e.w/2,t.y-e.cy]:[t.y-e.cy,e.h/2,t.x-e.cx];return Math.abs(r)/i>=.49?n?r<0?`L`:`R`:r<0?`T`:`B`:n?a<0?`T`:`B`:a<0?`L`:`R`}let r={T:Math.abs(t.y-(e.cy-e.h/2)),B:Math.abs(t.y-(e.cy+e.h/2)),L:Math.abs(t.x-(e.cx-e.w/2)),R:Math.abs(t.x-(e.cx+e.w/2))};return Object.keys(r).reduce((e,t)=>r[t]<r[e]?t:e)};function i(e,t,i){let a=i===`LR`||i===`RL`,o=e=>a?e.cx:e.cy,s=e=>a?e.cy:e.cx,c=e=>(a?e.w:e.h)/2,l=[...e].sort((e,t)=>o(e)-o(t)),u=new Map,d=-1,f,p=(e,t)=>Math.abs(e-t)<=20;for(let e of l)(!f||!(p(o(e),o(f))||p(o(e)-c(e),o(f)-c(f))||p(o(e)+c(e),o(f)+c(f))))&&(d++,f=e),u.set(e.id,d);let m=d,h=i===`RL`||i===`BT`,g=new Map,_=new Map;for(let t of[...e].sort((e,t)=>s(e)-s(t))){let e=u.get(t.id),n=_.get(e)??0;_.set(e,n+1),g.set(t.id,{layer:h?m-e:e,order:n,cross:s(t),size:a?t.h:t.w})}let v=new Map(e.map(e=>[e.id,e])),y=new Map,b=t.map((e,t)=>{let n=v.get(e.from),i=v.get(e.to);if(!n||!i||e.points.length<2)return;let o=e.points[0],s=e.points[e.points.length-1],c=(e,n,i)=>{let o=r(e,n,a),s={edge:t,start:i,face:o,at:o===`T`||o===`B`?n.x:n.y},c=`${e.id}|${o}`;return y.set(c,[...y.get(c)??[],s]),s};return{p:e,s:c(n,o,!0),e:c(i,s,!1)}}),x=(e,t)=>{let r=[...y.get(`${e}|${t.face}`)].map(e=>e.at);return new Set(r.filter(e=>e<t.at-n)).size},S=[];return b.forEach((e,t)=>{e&&S.push({index:t,from:e.p.from,to:e.p.to,start:{face:e.s.face,order:x(e.p.from,e.s)},end:{face:e.e.face,order:x(e.p.to,e.e)}})}),{nodes:g,edges:S}}var a=e=>{try{return e()}catch(e){if(e instanceof t.ElkNotRegisteredError)return;throw e}},o={graph:e=>a(()=>{let n=(0,t.layoutFlowchartSync)(e);return i(n.nodes.map(e=>({id:e.id,cx:e.x+e.width/2,cy:e.y+e.height/2,w:e.width,h:e.height,diamond:e.shape===`diamond`})),n.edges.map(e=>({from:e.source,to:e.target,points:e.points})),e.direction)}),er:e=>a(()=>{let n=(0,t.layoutErDiagramSync)(e);return i(n.entities.map(e=>({id:e.id,cx:e.x+e.width/2,cy:e.y+e.height/2,w:e.width,h:e.height})),n.relationships.map(e=>({from:e.entity1,to:e.entity2,points:e.points})),e.direction??`TD`)})};function s(){(0,e.registerAsciiLayout)(o)}s();var c=require("@zombie-mermaid/mcp");Object.keys(c).forEach(function(e){e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:function(){return c[e]}})});
|
package/dist/mcp.js
CHANGED
|
@@ -1 +1,122 @@
|
|
|
1
|
+
import { registerAsciiLayout as e } from "@zombie-mermaid/ascii-renderer";
|
|
2
|
+
import { ElkNotRegisteredError as t, layoutErDiagramSync as n, layoutFlowchartSync as r } from "@zombie-mermaid/svg-renderer";
|
|
1
3
|
export * from "@zombie-mermaid/mcp";
|
|
4
|
+
var i = .5, a = (e, t, n) => {
|
|
5
|
+
if (e.diamond) {
|
|
6
|
+
let [r, i, a] = n ? [
|
|
7
|
+
t.x - e.cx,
|
|
8
|
+
e.w / 2,
|
|
9
|
+
t.y - e.cy
|
|
10
|
+
] : [
|
|
11
|
+
t.y - e.cy,
|
|
12
|
+
e.h / 2,
|
|
13
|
+
t.x - e.cx
|
|
14
|
+
];
|
|
15
|
+
return Math.abs(r) / i >= .49 ? n ? r < 0 ? "L" : "R" : r < 0 ? "T" : "B" : n ? a < 0 ? "T" : "B" : a < 0 ? "L" : "R";
|
|
16
|
+
}
|
|
17
|
+
let r = {
|
|
18
|
+
T: Math.abs(t.y - (e.cy - e.h / 2)),
|
|
19
|
+
B: Math.abs(t.y - (e.cy + e.h / 2)),
|
|
20
|
+
L: Math.abs(t.x - (e.cx - e.w / 2)),
|
|
21
|
+
R: Math.abs(t.x - (e.cx + e.w / 2))
|
|
22
|
+
};
|
|
23
|
+
return Object.keys(r).reduce((e, t) => r[t] < r[e] ? t : e);
|
|
24
|
+
};
|
|
25
|
+
function o(e, t, n) {
|
|
26
|
+
let r = n === "LR" || n === "RL", o = (e) => r ? e.cx : e.cy, s = (e) => r ? e.cy : e.cx, c = (e) => (r ? e.w : e.h) / 2, l = [...e].sort((e, t) => o(e) - o(t)), u = /* @__PURE__ */ new Map(), d = -1, f, p = (e, t) => Math.abs(e - t) <= 20;
|
|
27
|
+
for (let e of l) (!f || !(p(o(e), o(f)) || p(o(e) - c(e), o(f) - c(f)) || p(o(e) + c(e), o(f) + c(f)))) && (d++, f = e), u.set(e.id, d);
|
|
28
|
+
let m = d, h = n === "RL" || n === "BT", g = /* @__PURE__ */ new Map(), _ = /* @__PURE__ */ new Map();
|
|
29
|
+
for (let t of [...e].sort((e, t) => s(e) - s(t))) {
|
|
30
|
+
let e = u.get(t.id), n = _.get(e) ?? 0;
|
|
31
|
+
_.set(e, n + 1), g.set(t.id, {
|
|
32
|
+
layer: h ? m - e : e,
|
|
33
|
+
order: n,
|
|
34
|
+
cross: s(t),
|
|
35
|
+
size: r ? t.h : t.w
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
let v = new Map(e.map((e) => [e.id, e])), y = /* @__PURE__ */ new Map(), b = t.map((e, t) => {
|
|
39
|
+
let n = v.get(e.from), i = v.get(e.to);
|
|
40
|
+
if (!n || !i || e.points.length < 2) return;
|
|
41
|
+
let o = e.points[0], s = e.points[e.points.length - 1], c = (e, n, i) => {
|
|
42
|
+
let o = a(e, n, r), s = {
|
|
43
|
+
edge: t,
|
|
44
|
+
start: i,
|
|
45
|
+
face: o,
|
|
46
|
+
at: o === "T" || o === "B" ? n.x : n.y
|
|
47
|
+
}, c = `${e.id}|${o}`;
|
|
48
|
+
return y.set(c, [...y.get(c) ?? [], s]), s;
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
p: e,
|
|
52
|
+
s: c(n, o, !0),
|
|
53
|
+
e: c(i, s, !1)
|
|
54
|
+
};
|
|
55
|
+
}), x = (e, t) => {
|
|
56
|
+
let n = [...y.get(`${e}|${t.face}`)].map((e) => e.at);
|
|
57
|
+
return new Set(n.filter((e) => e < t.at - i)).size;
|
|
58
|
+
}, S = [];
|
|
59
|
+
return b.forEach((e, t) => {
|
|
60
|
+
e && S.push({
|
|
61
|
+
index: t,
|
|
62
|
+
from: e.p.from,
|
|
63
|
+
to: e.p.to,
|
|
64
|
+
start: {
|
|
65
|
+
face: e.s.face,
|
|
66
|
+
order: x(e.p.from, e.s)
|
|
67
|
+
},
|
|
68
|
+
end: {
|
|
69
|
+
face: e.e.face,
|
|
70
|
+
order: x(e.p.to, e.e)
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}), {
|
|
74
|
+
nodes: g,
|
|
75
|
+
edges: S
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
var s = (e) => {
|
|
79
|
+
try {
|
|
80
|
+
return e();
|
|
81
|
+
} catch (e) {
|
|
82
|
+
if (e instanceof t) return;
|
|
83
|
+
throw e;
|
|
84
|
+
}
|
|
85
|
+
}, c = {
|
|
86
|
+
graph: (e) => s(() => {
|
|
87
|
+
let t = r(e);
|
|
88
|
+
return o(t.nodes.map((e) => ({
|
|
89
|
+
id: e.id,
|
|
90
|
+
cx: e.x + e.width / 2,
|
|
91
|
+
cy: e.y + e.height / 2,
|
|
92
|
+
w: e.width,
|
|
93
|
+
h: e.height,
|
|
94
|
+
diamond: e.shape === "diamond"
|
|
95
|
+
})), t.edges.map((e) => ({
|
|
96
|
+
from: e.source,
|
|
97
|
+
to: e.target,
|
|
98
|
+
points: e.points
|
|
99
|
+
})), e.direction);
|
|
100
|
+
}),
|
|
101
|
+
er: (e) => s(() => {
|
|
102
|
+
let t = n(e);
|
|
103
|
+
return o(t.entities.map((e) => ({
|
|
104
|
+
id: e.id,
|
|
105
|
+
cx: e.x + e.width / 2,
|
|
106
|
+
cy: e.y + e.height / 2,
|
|
107
|
+
w: e.width,
|
|
108
|
+
h: e.height
|
|
109
|
+
})), t.relationships.map((e) => ({
|
|
110
|
+
from: e.entity1,
|
|
111
|
+
to: e.entity2,
|
|
112
|
+
points: e.points
|
|
113
|
+
})), e.direction ?? "TD");
|
|
114
|
+
})
|
|
115
|
+
};
|
|
116
|
+
function l() {
|
|
117
|
+
e(c);
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/mcp-entry.ts
|
|
121
|
+
l();
|
|
122
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zombie-mermaid",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.15.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"mcpName": "io.github.dfadler/zombie-mermaid",
|
|
6
6
|
"description": "Render Mermaid diagrams as beautiful SVGs or ASCII art. Ultra-fast, fully themeable, zero DOM dependencies. Maintained fork of beautiful-mermaid.",
|
|
@@ -115,11 +115,11 @@
|
|
|
115
115
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
116
116
|
"elkjs": "^0.12.0",
|
|
117
117
|
"entities": "^7.0.1",
|
|
118
|
-
"@zombie-mermaid/
|
|
119
|
-
"@zombie-mermaid/
|
|
120
|
-
"@zombie-mermaid/
|
|
121
|
-
"@zombie-mermaid/
|
|
122
|
-
"@zombie-mermaid/
|
|
118
|
+
"@zombie-mermaid/ascii-renderer": "5.15.0",
|
|
119
|
+
"@zombie-mermaid/core": "5.15.0",
|
|
120
|
+
"@zombie-mermaid/svg-renderer": "5.15.0",
|
|
121
|
+
"@zombie-mermaid/mcp": "5.15.0",
|
|
122
|
+
"@zombie-mermaid/mermaid-parser": "5.15.0"
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|
|
125
125
|
"@arethetypeswrong/cli": "0.18.5",
|
|
@@ -204,6 +204,7 @@
|
|
|
204
204
|
"check:changeset-coverage": "tsx scripts/check-changeset-coverage.ts",
|
|
205
205
|
"check:docs": "tsx scripts/check-docs.ts",
|
|
206
206
|
"check:plugin-skill": "tsx scripts/sync-plugin-skill.ts --check",
|
|
207
|
+
"check:llms-full": "tsx scripts/check-llms-full.ts",
|
|
207
208
|
"check:plugin-lock": "tsx scripts/check-plugin-lock.ts",
|
|
208
209
|
"sync:plugin-skill": "tsx scripts/sync-plugin-skill.ts",
|
|
209
210
|
"mcp:server-json": "tsx scripts/generate-server-json.ts",
|
|
@@ -246,6 +247,9 @@
|
|
|
246
247
|
"label:oracle": "tsx scripts/label-position-oracle.ts",
|
|
247
248
|
"sequence:oracle": "tsx scripts/sequence-oracle.ts",
|
|
248
249
|
"quadrant:oracle": "tsx scripts/quadrant-oracle.ts",
|
|
250
|
+
"mindmap:oracle": "tsx scripts/mindmap-oracle.ts",
|
|
251
|
+
"gantt:oracle": "tsx scripts/gantt-oracle.ts",
|
|
252
|
+
"timeline:oracle": "tsx scripts/timeline-oracle.ts",
|
|
249
253
|
"pie:oracle": "tsx scripts/pie-oracle.ts",
|
|
250
254
|
"c4:oracle": "tsx scripts/c4-oracle.ts",
|
|
251
255
|
"xychart:oracle": "tsx scripts/xychart-oracle.ts",
|