zombie-mermaid 5.3.0 → 5.4.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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +180 -170
  3. package/package.json +13 -8
package/README.md CHANGED
@@ -30,7 +30,7 @@ Ultra-fast, fully themeable, zero DOM dependencies. A maintained fork of [`beaut
30
30
 
31
31
  `beautiful-mermaid` is a genuinely good library — fast, beautiful, and works everywhere from rich UIs to plain terminals. Checkable facts, as of 2026-10-13: the last push to upstream's `main` was 2026-05-06 and its latest release is v1.1.2 (2026-02-26); it has [43 open pull requests](https://github.com/lukilabs/beautiful-mermaid/pulls) (19 opened before 2026-04-13) and [50 open issues](https://github.com/lukilabs/beautiful-mermaid/issues) (live counts at those links; the figures above are a dated snapshot). I maintain `zombie-mermaid` as the fork that won't stay buried: pulling in upstream fixes, giving PRs stuck in the upstream queue a home, and actually shipping releases. Craft and Craft Agents aren't part of this project's process going forward; this is an independently maintained continuation.
32
32
 
33
- This isn't just a claim — see **[what this fork fixes](https://dfadler.github.io/zombie-mermaid/fork-fixes.html)** for an evidence-based before/after showcase (37 fixes as of 2026-10-13): every bug listed there is rendered by the actual pre-fix and post-fix code, not described from memory.
33
+ This isn't just a claim — see **[what this fork fixes](https://dfadler.github.io/zombie-mermaid/fork-fixes.html)** for an evidence-based before/after showcase (38 fixes): every bug listed there is rendered by the actual pre-fix and post-fix code, not described from memory.
34
34
 
35
35
  It's still MIT-licensed, still built on the same foundation, and still credits the original authors — including the ASCII rendering engine's origins — in [Attribution](#attribution) below.
36
36
 
@@ -223,6 +223,7 @@ zombie-mermaid --help # Show all options
223
223
  `--hyperlinks` (with `--ascii`) turns `click` hrefs into OSC 8 terminal
224
224
  hyperlinks — off by default, since terminal and pager support varies (see
225
225
  [ASCII terminal hyperlinks](docs/diagrams.md#ascii-terminal-hyperlinks)).
226
+ `--profile markdown` (with `--ascii`) emits plain ASCII characters with no color, for pasting into issues, commits and fenced blocks (the `profile` option in the library and the MCP `render_mermaid_ascii` tool).
226
227
 
227
228
  **Output rules.** `-o` is the destination for the run's _file_ output — SVG, HTML, or PNG when `--svg`/`--html`/`--png` is given, otherwise the ASCII rendering — and `-o -` sends it to stdout instead. A recognised extension (`.svg`, `.html`/`.htm`, `.txt`, `.png`) picks the format on its own, so the flag can be dropped; an extension that contradicts an explicit flag (`--svg -o out.txt`) is an error, while unrecognised ones are simply used as given. `--svg`/`--html`/`--png` with no `-o` writes `<input stem>.svg`/`.html`/`.png` beside the input (stdin input has no name to derive from, so it must pass `-o`). Only one of `--svg`/`--html`/`--png` can be set at a time — run the command twice for more than one. ASCII always prints to the terminal when one of them is also requested (`--ascii --svg -o out.svg`), and never carries ANSI colour codes when written to a file. **An existing output file is never overwritten unless you pass `--force`/`-f`.**
228
229
 
package/dist/cli.js CHANGED
@@ -1,28 +1,28 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire as V } from "node:module";
3
- import { extname as H, format as q, parse as D } from "node:path";
4
- import { MermaidParseError as j, THEMES as B, escapeXml as K, isDirection as J, isWideChar as Z } from "@zombie-mermaid/core";
5
- import { access as Q, readFile as F, writeFile as ee } from "node:fs/promises";
6
- import { diagramColorsToAsciiTheme as te, renderMermaidASCII as O } from "@zombie-mermaid/ascii-renderer";
2
+ import { createRequire as q } from "node:module";
3
+ import { extname as H, format as j, parse as F } from "node:path";
4
+ import { MermaidParseError as K, THEMES as O, escapeXml as J, isDirection as Z, isWideChar as Q } from "@zombie-mermaid/core";
5
+ import { access as ee, readFile as G, writeFile as te } from "node:fs/promises";
6
+ import { diagramColorsToAsciiTheme as ne, renderMermaidASCII as _ } from "@zombie-mermaid/ascii-renderer";
7
7
  import "@zombie-mermaid/mermaid-parser";
8
- import { registerElk as ne, renderMermaidSVG as M, validateMermaid as re } from "@zombie-mermaid/svg-renderer";
9
- import { createServer as oe } from "node:http";
10
- import { StdioServerTransport as ie } from "@modelcontextprotocol/sdk/server/stdio.js";
11
- import { createMcpServer as ae } from "@zombie-mermaid/mcp";
12
- import se from "elkjs/lib/elk.bundled.js";
13
- var de = 3e3;
14
- var G = {
8
+ import { registerElk as re, renderMermaidSVG as z, validateMermaid as oe } from "@zombie-mermaid/svg-renderer";
9
+ import { createServer as ie } from "node:http";
10
+ import { StdioServerTransport as ae } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import { createMcpServer as se } from "@zombie-mermaid/mcp";
12
+ import de from "elkjs/lib/elk.bundled.js";
13
+ var ce = 3e3;
14
+ var W = {
15
15
  ".svg": "svg",
16
16
  ".txt": "ascii",
17
17
  ".html": "html",
18
18
  ".htm": "html",
19
19
  ".png": "png"
20
20
  };
21
- function ce(e) {
21
+ function le(e) {
22
22
  if (!(e === void 0 || e === "-"))
23
- return G[H(e).toLowerCase()];
23
+ return W[H(e).toLowerCase()];
24
24
  }
25
- var w = {
25
+ var b = {
26
26
  svg: {
27
27
  flag: "--svg",
28
28
  ext: ".svg",
@@ -44,22 +44,22 @@ var w = {
44
44
  display: "PNG",
45
45
  label: "the PNG rasterization"
46
46
  }
47
- }, T = Object.keys(w);
48
- function le(e, n) {
49
- const t = D(e);
50
- return q({
47
+ }, I = Object.keys(b);
48
+ function me(e, n) {
49
+ const t = F(e);
50
+ return j({
51
51
  dir: t.dir,
52
52
  name: t.name,
53
53
  ext: n
54
54
  });
55
55
  }
56
- function me(e) {
56
+ function ue(e) {
57
57
  const [n, ...t] = e;
58
58
  if (n === void 0) return { command: "help" };
59
59
  if (n === "--help" || n === "-h") return { command: "help" };
60
60
  if (n === "--version" || n === "-v") return { command: "version" };
61
61
  if (n === "themes") return { command: "themes" };
62
- if (n === "render") return fe(t);
62
+ if (n === "render") return he(t);
63
63
  if (n === "validate") {
64
64
  const r = t.find((a, d) => d > 0 || a.startsWith("-"));
65
65
  if (r !== void 0) throw new Error(`Unexpected argument: ${r}`);
@@ -68,17 +68,17 @@ function me(e) {
68
68
  input: t[0]
69
69
  };
70
70
  }
71
- if (n === "web") return he(t);
71
+ if (n === "web") return ge(t);
72
72
  if (n === "mcp") return { command: "mcp" };
73
73
  throw new Error(`Unknown command: ${n}`);
74
74
  }
75
- function z(e, n, t) {
75
+ function $(e, n, t) {
76
76
  const r = e[n + 1];
77
77
  if (n + 1 >= e.length || r === void 0) throw new Error(`${t} requires a numeric value`);
78
78
  if (!/^\d+$/.test(r)) throw new Error(`${t} requires a non-negative integer, got: "${r}"`);
79
79
  return Number(r);
80
80
  }
81
- function ue(e, n, t) {
81
+ function pe(e, n, t) {
82
82
  const r = e[n + 1];
83
83
  if (n + 1 >= e.length || r === void 0) throw new Error(`${t} requires a value ("auto" or a positive integer)`);
84
84
  if (r === "auto") return "auto";
@@ -86,15 +86,15 @@ function ue(e, n, t) {
86
86
  if (!/^\d+$/.test(r) || a === 0 || !Number.isSafeInteger(a)) throw new Error(`${t} requires "auto" or a positive integer, got: "${r}"`);
87
87
  return a;
88
88
  }
89
- function pe(e, n, t) {
89
+ function fe(e, n, t) {
90
90
  const r = e[n + 1];
91
91
  if (n + 1 >= e.length || r === void 0) throw new Error(`${t} requires a value (one of TD, TB, BT, LR, RL)`);
92
92
  const a = r.toUpperCase();
93
- if (!J(a)) throw new Error(`${t} requires one of TD, TB, BT, LR, RL, got: "${r}"`);
93
+ if (!Z(a)) throw new Error(`${t} requires one of TD, TB, BT, LR, RL, got: "${r}"`);
94
94
  return a;
95
95
  }
96
- function fe(e) {
97
- let n, t = !1, r = !1, a = !1, d = !1, l = !1, m, g = !1, E, S, k, s, u = !1, p = !1, h, b, i = 0;
96
+ function he(e) {
97
+ let n, t = !1, r = !1, a = !1, d = !1, l = !1, m, g = !1, E, S, k, s, u = !1, p = !1, h, v, y, i = 0;
98
98
  for (; i < e.length; ) {
99
99
  const o = e[i];
100
100
  if (o === void 0) throw new Error(`parseArgs: index ${i} out of range while parsing arguments`);
@@ -117,72 +117,77 @@ function fe(e) {
117
117
  if (i + 1 >= e.length) throw new Error("--theme requires a theme name");
118
118
  E = e[i + 1], i += 2;
119
119
  } else if (o === "-x" || o === "--paddingX")
120
- S = z(e, i, o), i += 2;
120
+ S = $(e, i, o), i += 2;
121
121
  else if (o === "-y" || o === "--paddingY")
122
- k = z(e, i, o), i += 2;
122
+ k = $(e, i, o), i += 2;
123
123
  else if (o === "-p" || o === "--borderPadding")
124
- s = z(e, i, o), i += 2;
124
+ s = $(e, i, o), i += 2;
125
125
  else if (o === "--coords")
126
126
  u = !0, i++;
127
127
  else if (o === "--hyperlinks")
128
128
  p = !0, i++;
129
129
  else if (o === "-w" || o === "--max-width")
130
- h = ue(e, i, o), i += 2;
131
- else if (o === "--direction")
132
- b = pe(e, i, o), i += 2;
130
+ h = pe(e, i, o), i += 2;
131
+ else if (o === "--profile") {
132
+ const c = e[i + 1];
133
+ if (c !== "markdown") throw new Error(`--profile requires one of: markdown, got: "${c ?? ""}"`);
134
+ v = c, i += 2;
135
+ } else if (o === "--direction")
136
+ y = fe(e, i, o), i += 2;
133
137
  else if (!o.startsWith("-") || o === "-") {
134
138
  if (o === "-") throw new Error("Unexpected argument: - (use -o - to write output to stdout; omit the input file to read stdin)");
135
139
  if (n !== void 0) throw new Error(`Unexpected argument: ${o} (input file already set to "${n}")`);
136
140
  n = o, i++;
137
141
  } else throw new Error(`Unknown flag: ${o}`);
138
142
  }
139
- const v = {
143
+ const w = {
140
144
  svg: r,
141
145
  html: d,
142
146
  png: l
143
- }, y = T.filter((o) => v[o]);
144
- if (y.length > 1) {
145
- const [o, c] = y;
146
- throw new Error(`${w[o].flag} and ${w[c].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.`);
147
+ }, C = I.filter((o) => w[o]);
148
+ if (C.length > 1) {
149
+ const [o, c] = C;
150
+ throw new Error(`${b[o].flag} and ${b[c].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.`);
147
151
  }
148
- const f = ce(m);
152
+ const f = le(m);
149
153
  if (f !== void 0 && f !== "ascii") {
150
- const o = T.find((c) => c !== f && v[c]);
154
+ const o = I.find((c) => c !== f && w[c]);
151
155
  if (o !== void 0) {
152
- const c = w[f], $ = w[o];
153
- throw new Error(`-o ${m} has ${c.article} ${c.ext} extension, but ${$.flag} output would be written to it. Use a ${$.ext} path (or -o - for stdout) for ${$.label}.`);
156
+ const c = b[f], P = b[o];
157
+ throw new Error(`-o ${m} has ${c.article} ${c.ext} extension, but ${P.flag} output would be written to it. Use a ${P.ext} path (or -o - for stdout) for ${P.label}.`);
154
158
  }
155
159
  f === "svg" ? r = !0 : f === "html" ? d = !0 : l = !0;
156
160
  } else if (f === "ascii") {
157
- const o = T.find((c) => v[c]);
161
+ const o = I.find((c) => w[c]);
158
162
  if (o !== void 0) {
159
- const c = w[o];
163
+ const c = b[o];
160
164
  throw new Error(`-o ${m} has a .txt extension, but ${c.flag} output would be written to it. Use a ${c.ext} path (or -o - for stdout) instead.`);
161
165
  }
162
166
  t = !0;
163
167
  }
164
168
  if (!t && !r && !d && !l) {
165
169
  if (m !== void 0 && m !== "-") {
166
- const o = Object.keys(G).join(", ");
170
+ const o = Object.keys(W).join(", ");
167
171
  throw new Error(`Cannot infer an output format from "${m}" — pass --ascii, --svg, --html, or --png, or use a recognised extension (${o})`);
168
172
  }
169
173
  throw new Error("Specify --ascii, --svg, --html, and/or --png (or -o <path> with a .svg/.txt/.html/.png extension)");
170
174
  }
171
- const C = {
175
+ const T = {
172
176
  svg: r,
173
177
  html: d,
174
178
  png: l
175
- }, x = T.find((o) => C[o]);
179
+ }, x = I.find((o) => T[o]);
176
180
  if (m === "-" && t && x !== void 0) {
177
- const o = w[x].display;
181
+ const o = b[x].display;
178
182
  throw new Error(`-o - would send both ASCII and ${o} to stdout; drop --ascii, or write the ${o} to a file path`);
179
183
  }
180
184
  if (x !== void 0 && m === void 0) {
181
- const { flag: o, ext: c } = w[x];
185
+ const { flag: o, ext: c } = b[x];
182
186
  if (n === void 0) throw new Error(`${o} needs -o <path> (or -o - for stdout) when reading from stdin — there is no input file name to derive an output name from`);
183
- m = le(n, c);
187
+ m = me(n, c);
184
188
  }
185
189
  if (h !== void 0 && !t) throw new Error("-w/--max-width requires --ascii");
190
+ if (v !== void 0 && !t) throw new Error("--profile requires --ascii");
186
191
  if (p && !t) throw new Error("--hyperlinks requires --ascii");
187
192
  if (a && !r) throw new Error("--resolve-colors requires --svg");
188
193
  return {
@@ -202,16 +207,17 @@ function fe(e) {
202
207
  coords: u,
203
208
  hyperlinks: p,
204
209
  maxWidth: h,
205
- direction: b
210
+ profile: v,
211
+ direction: y
206
212
  };
207
213
  }
208
- function he(e) {
209
- let n = de, t = 0;
214
+ function ge(e) {
215
+ let n = ce, t = 0;
210
216
  for (; t < e.length; ) {
211
217
  const r = e[t];
212
218
  if (r === void 0) throw new Error(`parseArgs: index ${t} out of range while parsing arguments`);
213
219
  if (r === "--port")
214
- n = z(e, t, r), t += 2;
220
+ n = $(e, t, r), t += 2;
215
221
  else throw new Error(`Unknown flag: ${r}`);
216
222
  }
217
223
  return {
@@ -219,37 +225,37 @@ function he(e) {
219
225
  port: n
220
226
  };
221
227
  }
222
- var ge = /\p{Mn}|\p{Me}/u;
223
- function ve(e) {
224
- return ge.test(e);
228
+ var ve = /\p{Mn}|\p{Me}/u;
229
+ function we(e) {
230
+ return ve.test(e);
225
231
  }
226
- var we = new Intl.Segmenter(void 0, { granularity: "grapheme" }), be = "️";
227
- function ye(e) {
232
+ var be = new Intl.Segmenter(void 0, { granularity: "grapheme" }), ye = "️";
233
+ function xe(e) {
228
234
  if (/^[\x20-\x7e]*$/.test(e)) return e.split("");
229
235
  const n = [];
230
- for (const { segment: t } of we.segment(e)) n.push(t);
236
+ for (const { segment: t } of be.segment(e)) n.push(t);
231
237
  return n;
232
238
  }
233
- function xe(e) {
234
- if (e.includes(be)) return 2;
239
+ function Ee(e) {
240
+ if (e.includes(ye)) return 2;
235
241
  let n = !1, t = !1;
236
242
  for (const r of e)
237
- Z(r) && (n = !0), ve(r) || (t = !0);
243
+ Q(r) && (n = !0), we(r) || (t = !0);
238
244
  return n ? 2 : t ? 1 : 0;
239
245
  }
240
- function Ee(e) {
246
+ function Se(e) {
241
247
  let n = 0;
242
- for (const t of ye(e)) n += xe(t);
248
+ for (const t of xe(e)) n += Ee(t);
243
249
  return n;
244
250
  }
245
- var Se = /\x1b\]8;[^\x07\x1b]*(?:\x1b\\|\x07)/g;
246
- function ke(e) {
247
- return e.replace(Se, "");
251
+ var ke = /\x1b\]8;[^\x07\x1b]*(?:\x1b\\|\x07)/g;
252
+ function Ce(e) {
253
+ return e.replace(ke, "");
248
254
  }
249
- var Ce = /^Line (\d+): ([\s\S]*)$/;
250
- function Te(e, n) {
251
- if (!(e instanceof j) || e.line === void 0) return;
252
- const { line: t, message: r } = e, a = Ce.exec(r), d = n.split(/\r?\n/)[t - 1];
255
+ var Te = /^Line (\d+): ([\s\S]*)$/;
256
+ function Ie(e, n) {
257
+ if (!(e instanceof K) || e.line === void 0) return;
258
+ const { line: t, message: r } = e, a = Te.exec(r), d = n.split(/\r?\n/)[t - 1];
253
259
  if (!(t < 1 || d === void 0))
254
260
  return {
255
261
  line: t,
@@ -257,9 +263,9 @@ function Te(e, n) {
257
263
  message: a?.[2] ?? r
258
264
  };
259
265
  }
260
- function W(e, n) {
266
+ function X(e, n) {
261
267
  if (!(e instanceof Error)) return e;
262
- const t = Te(e, n);
268
+ const t = Ie(e, n);
263
269
  return t === void 0 ? e : /* @__PURE__ */ new Error(`${e.message}
264
270
  ${t.line} | ${t.sourceLine}`);
265
271
  }
@@ -555,7 +561,7 @@ var Ae = `// @ts-check
555
561
  fit()
556
562
  stage.focus({ preventScroll: true })
557
563
  })()
558
- `, Ie = `
564
+ `, Me = `
559
565
  :root {
560
566
  color-scheme: light dark;
561
567
  --bg: #f4f4f5;
@@ -662,16 +668,16 @@ var Ae = `// @ts-check
662
668
  #diagram { position: static; transform: none !important; }
663
669
  }
664
670
  `.trim();
665
- function Me(e) {
671
+ function ze(e) {
666
672
  const n = e.title?.trim() || "Mermaid diagram";
667
673
  return `<!doctype html>
668
674
  <html lang="en">
669
675
  <head>
670
676
  <meta charset="utf-8" />
671
677
  <meta name="viewport" content="width=device-width, initial-scale=1" />
672
- <title>${K(n)}</title>
678
+ <title>${J(n)}</title>
673
679
  <style>
674
- ${Ie}
680
+ ${Me}
675
681
  </style>
676
682
  </head>
677
683
  <body>
@@ -695,7 +701,7 @@ ${Ae}
695
701
  </html>
696
702
  `;
697
703
  }
698
- async function ze(e) {
704
+ async function $e(e) {
699
705
  let n;
700
706
  try {
701
707
  n = await import("@resvg/resvg-js");
@@ -706,34 +712,34 @@ ${r}`);
706
712
  }
707
713
  return (await n.renderAsync(e)).asPng();
708
714
  }
709
- var $e = 80, P = 1, L = 1, R = 0, Pe = /\x1b\[[0-9;]*m/g;
710
- function _(e) {
715
+ var Pe = 80, L = 1, R = 1, B = 0, Le = /\x1b\[[0-9;]*m/g;
716
+ function N(e) {
711
717
  return e.split(`
712
- `).reduce((n, t) => Math.max(n, Ee(ke(t.replace(Pe, "")))), 0);
718
+ `).reduce((n, t) => Math.max(n, Se(Ce(t.replace(Le, "")))), 0);
713
719
  }
714
- function Le(e) {
715
- return e !== "auto" ? e : process.stdout.columns ?? $e;
720
+ function Re(e) {
721
+ return e !== "auto" ? e : process.stdout.columns ?? Pe;
716
722
  }
717
- async function Re(e, n, t, r) {
723
+ async function Be(e, n, t, r) {
718
724
  const a = n ?? process.stdout, d = r ?? process.stderr;
719
725
  let l;
720
- if (e.input !== void 0) l = await F(e.input, "utf-8");
726
+ if (e.input !== void 0) l = await G(e.input, "utf-8");
721
727
  else if (t !== void 0) l = t;
722
728
  else {
723
729
  if (process.stdin.isTTY) throw new Error("No input file specified and stdin is a terminal. Pipe a diagram or pass a file path.");
724
- l = await Y();
730
+ l = await U();
725
731
  }
726
732
  if (l.trim().length === 0) throw new Error("Empty input — provide a Mermaid diagram via file or stdin");
727
733
  try {
728
- await Be(e, l, a, d);
734
+ await Oe(e, l, a, d);
729
735
  } catch (m) {
730
- throw W(m, l);
736
+ throw X(m, l);
731
737
  }
732
738
  }
733
- async function Be(e, n, t, r) {
739
+ async function Oe(e, n, t, r) {
734
740
  let a;
735
- if (e.theme !== void 0 && (a = B[e.theme], a === void 0)) {
736
- const s = Object.keys(B).join(", ");
741
+ if (e.theme !== void 0 && (a = O[e.theme], a === void 0)) {
742
+ const s = Object.keys(O).join(", ");
737
743
  throw new Error(`Unknown theme: "${e.theme}". Available themes: ${s}`);
738
744
  }
739
745
  const d = e.svg && e.output === "-", l = e.html && e.output === "-", m = e.png && e.output === "-", g = e.ascii && !e.svg && !e.html && !e.png && e.output !== void 0 && e.output !== "-" ? e.output : void 0, E = e.svg && !d ? e.output : void 0, S = e.html && !l ? e.output : void 0, k = e.png && !m ? e.output : void 0;
@@ -742,37 +748,37 @@ async function Be(e, n, t, r) {
742
748
  E,
743
749
  S,
744
750
  k
745
- ]) s !== void 0 && await Oe(s, e.force);
751
+ ]) s !== void 0 && await _e(s, e.force);
746
752
  if (e.ascii) {
747
753
  const s = a && g === void 0 ? {
748
754
  colorMode: "auto",
749
- theme: te(a)
755
+ theme: ne(a)
750
756
  } : { colorMode: "none" };
751
- e.paddingX !== void 0 && (s.paddingX = e.paddingX), e.paddingY !== void 0 && (s.paddingY = e.paddingY), e.borderPadding !== void 0 && (s.boxBorderPadding = e.borderPadding), e.coords && (s.showCoords = !0), e.direction !== void 0 && (s.direction = e.direction), e.hyperlinks && g === void 0 && (s.hyperlinks = !0);
752
- let u = O(n, s);
757
+ e.paddingX !== void 0 && (s.paddingX = e.paddingX), e.paddingY !== void 0 && (s.paddingY = e.paddingY), e.borderPadding !== void 0 && (s.boxBorderPadding = e.borderPadding), e.coords && (s.showCoords = !0), e.profile !== void 0 && (s.profile = e.profile), e.direction !== void 0 && (s.direction = e.direction), e.hyperlinks && g === void 0 && (s.hyperlinks = !0);
758
+ let u = _(n, s);
753
759
  if (e.maxWidth !== void 0) {
754
- const p = Le(e.maxWidth);
755
- let h = _(u), b = !1;
760
+ const p = Re(e.maxWidth);
761
+ let h = N(u), v = !1;
756
762
  if (h > p) {
757
- const i = s.paddingX ?? 5, v = s.paddingY ?? 5, y = s.boxBorderPadding ?? 1, f = Math.min(i, P), C = Math.min(v, L), x = Math.min(y, R);
758
- if (f < i || C < v || x < y) {
759
- const o = O(n, {
763
+ const y = s.paddingX ?? 5, i = s.paddingY ?? 5, w = s.boxBorderPadding ?? 1, C = Math.min(y, L), f = Math.min(i, R), T = Math.min(w, B);
764
+ if (C < y || f < i || T < w) {
765
+ const x = _(n, {
760
766
  ...s,
761
- paddingX: f,
762
- paddingY: C,
763
- boxBorderPadding: x
764
- }), c = _(o);
765
- c < h && (u = o, h = c, b = !0);
767
+ paddingX: C,
768
+ paddingY: f,
769
+ boxBorderPadding: T
770
+ }), o = N(x);
771
+ o < h && (u = x, h = o, v = !0);
766
772
  }
767
773
  }
768
774
  if (h > p) {
769
- const i = e.maxWidth === "auto" ? "detected terminal width" : "--max-width", v = b ? `Compact spacing (-x ${P} -y ${L} -p ${R}) was already applied automatically. ` : "", y = b ? "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.";
770
- r.write(`Warning: ASCII output is ${h} columns wide, exceeding ${i} of ${p}. ${v}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). ${y}
775
+ const y = e.maxWidth === "auto" ? "detected terminal width" : "--max-width", i = v ? `Compact spacing (-x ${L} -y ${R} -p ${B}) was already applied automatically. ` : "", w = v ? "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.";
776
+ r.write(`Warning: ASCII output is ${h} columns wide, exceeding ${y} of ${p}. ${i}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). ${w}
771
777
  `);
772
- } else b && r.write(`Note: ASCII output exceeded ${p} columns at the requested spacing; applied compact spacing automatically (-x ${P} -y ${L} -p ${R}) to fit.
778
+ } else v && r.write(`Note: ASCII output exceeded ${p} columns at the requested spacing; applied compact spacing automatically (-x ${L} -y ${R} -p ${B}) to fit.
773
779
  `);
774
780
  }
775
- await I(A(g === void 0, g), u + `
781
+ await M(A(g === void 0, g), u + `
776
782
  `, t, e.force);
777
783
  }
778
784
  if (e.svg) {
@@ -781,15 +787,15 @@ async function Be(e, n, t, r) {
781
787
  resolveColors: e.resolveColors
782
788
  };
783
789
  e.direction !== void 0 && (s.direction = e.direction);
784
- const u = M(n, s);
785
- await I(A(d, E), u, t, e.force);
790
+ const u = z(n, s);
791
+ await M(A(d, E), u, t, e.force);
786
792
  }
787
793
  if (e.html) {
788
- const s = M(n, a ?? {}), u = e.input ? D(e.input).name : void 0, p = Me({
794
+ const s = z(n, a ?? {}), u = e.input ? F(e.input).name : void 0, p = ze({
789
795
  svg: s,
790
796
  title: u
791
797
  });
792
- await I(A(l, S), p, t, e.force);
798
+ await M(A(l, S), p, t, e.force);
793
799
  }
794
800
  if (e.png) {
795
801
  const s = {
@@ -797,8 +803,8 @@ async function Be(e, n, t, r) {
797
803
  resolveColors: !0
798
804
  };
799
805
  e.direction !== void 0 && (s.direction = e.direction);
800
- const u = M(n, s), p = await ze(u);
801
- await I(A(m, k), p, t, e.force);
806
+ const u = z(n, s), p = await $e(u);
807
+ await M(A(m, k), p, t, e.force);
802
808
  }
803
809
  }
804
810
  function A(e, n) {
@@ -807,68 +813,68 @@ function A(e, n) {
807
813
  path: n
808
814
  } : { kind: "skip" };
809
815
  }
810
- async function I(e, n, t, r) {
816
+ async function M(e, n, t, r) {
811
817
  switch (e.kind) {
812
818
  case "stdout":
813
819
  t.write(n);
814
820
  break;
815
821
  case "file":
816
- await _e(e.path, n, r);
822
+ await Ne(e.path, n, r);
817
823
  }
818
824
  }
819
- function X(e) {
825
+ function Y(e) {
820
826
  return /* @__PURE__ */ new Error(`Refusing to overwrite existing file "${e}" — pass --force to replace it`);
821
827
  }
822
- async function Oe(e, n) {
828
+ async function _e(e, n) {
823
829
  if (n) return;
824
830
  let t = !0;
825
831
  try {
826
- await Q(e);
832
+ await ee(e);
827
833
  } catch {
828
834
  t = !1;
829
835
  }
830
- if (t) throw X(e);
836
+ if (t) throw Y(e);
831
837
  }
832
- async function _e(e, n, t) {
838
+ async function Ne(e, n, t) {
833
839
  try {
834
- await ee(e, n, {
840
+ await te(e, n, {
835
841
  encoding: "utf-8",
836
842
  flag: t ? "w" : "wx"
837
843
  });
838
844
  } catch (r) {
839
- throw !t && r instanceof Error && "code" in r && r.code === "EEXIST" ? X(e) : r;
845
+ throw !t && r instanceof Error && "code" in r && r.code === "EEXIST" ? Y(e) : r;
840
846
  }
841
847
  }
842
- async function Y() {
848
+ async function U() {
843
849
  const e = [], n = process.stdin;
844
850
  return n.setEncoding("utf-8"), new Promise((t, r) => {
845
851
  n.on("data", (a) => e.push(a)), n.on("end", () => t(e.join(""))), n.on("error", r);
846
852
  });
847
853
  }
848
- async function Ne(e, n) {
854
+ async function De(e, n) {
849
855
  let t;
850
- if (e.input !== void 0) t = await F(e.input, "utf-8");
856
+ if (e.input !== void 0) t = await G(e.input, "utf-8");
851
857
  else if (n !== void 0) t = n;
852
858
  else {
853
859
  if (process.stdin.isTTY) throw new Error("No input file specified and stdin is a terminal. Pipe a diagram or pass a file path.");
854
- t = await Y();
860
+ t = await U();
855
861
  }
856
862
  const r = t;
857
863
  if (r.trim().length === 0) throw new Error("Empty input — provide a Mermaid diagram via file or stdin");
858
864
  try {
859
- re(r);
865
+ oe(r);
860
866
  } catch (a) {
861
- throw W(a, r);
867
+ throw X(a, r);
862
868
  }
863
869
  console.log("valid");
864
870
  }
865
- var De = 1e6;
866
- function Fe(e) {
871
+ var Fe = 1e6;
872
+ function Ge(e) {
867
873
  return new Promise((n, t) => {
868
874
  const r = [];
869
875
  let a = 0;
870
876
  e.on("data", (d) => {
871
- if (a += d.length, a > De) {
877
+ if (a += d.length, a > Fe) {
872
878
  t(/* @__PURE__ */ new Error("Request body too large")), e.destroy();
873
879
  return;
874
880
  }
@@ -876,17 +882,17 @@ function Fe(e) {
876
882
  }), e.on("end", () => n(Buffer.concat(r).toString("utf-8"))), e.on("error", t);
877
883
  });
878
884
  }
879
- function U(e) {
885
+ function V(e) {
880
886
  return typeof e == "object" && e !== null;
881
887
  }
882
- function Ge(e) {
888
+ function We(e) {
883
889
  let n;
884
890
  try {
885
891
  n = JSON.parse(e);
886
892
  } catch {
887
893
  throw new Error("Invalid JSON in request body");
888
894
  }
889
- if (!U(n)) throw new Error("Request body must be a JSON object");
895
+ if (!V(n)) throw new Error("Request body must be a JSON object");
890
896
  const t = n.source;
891
897
  if (typeof t != "string" || t.trim().length === 0) throw new Error('Provide Mermaid source as a non-empty "source" string');
892
898
  return {
@@ -894,25 +900,25 @@ function Ge(e) {
894
900
  format: n.format === "svg" ? "svg" : "ascii"
895
901
  };
896
902
  }
897
- function N(e, n, t) {
903
+ function D(e, n, t) {
898
904
  const r = JSON.stringify(t);
899
905
  e.writeHead(n, {
900
906
  "Content-Type": "application/json; charset=utf-8",
901
907
  "Content-Length": Buffer.byteLength(r)
902
908
  }), e.end(r);
903
909
  }
904
- async function We(e, n) {
910
+ async function Xe(e, n) {
905
911
  try {
906
- const { source: t, format: r } = Ge(await Fe(e));
907
- N(n, 200, {
908
- output: r === "svg" ? M(t) : O(t),
912
+ const { source: t, format: r } = We(await Ge(e));
913
+ D(n, 200, {
914
+ output: r === "svg" ? z(t) : _(t),
909
915
  format: r
910
916
  });
911
917
  } catch (t) {
912
- N(n, 400, { error: t instanceof Error ? t.message : String(t) });
918
+ D(n, 400, { error: t instanceof Error ? t.message : String(t) });
913
919
  }
914
920
  }
915
- var Xe = `<!doctype html>
921
+ var Ye = `<!doctype html>
916
922
  <html lang="en">
917
923
  <head>
918
924
  <meta charset="utf-8" />
@@ -978,79 +984,79 @@ var Xe = `<!doctype html>
978
984
  </body>
979
985
  </html>
980
986
  `;
981
- function Ye() {
982
- return oe((e, n) => {
987
+ function Ue() {
988
+ return ie((e, n) => {
983
989
  const t = (e.url ?? "/").split("?")[0];
984
990
  if (e.method === "GET" && (t === "/" || t === "")) {
985
- n.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), n.end(Xe);
991
+ n.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), n.end(Ye);
986
992
  return;
987
993
  }
988
994
  if (e.method === "POST" && t === "/render") {
989
- We(e, n);
995
+ Xe(e, n);
990
996
  return;
991
997
  }
992
998
  n.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }), n.end("Not found");
993
999
  });
994
1000
  }
995
- function Ue(e) {
1001
+ function Ve(e) {
996
1002
  return new Promise((n, t) => {
997
- const r = Ye();
1003
+ const r = Ue();
998
1004
  r.once("error", t), r.listen(e, "127.0.0.1", () => {
999
1005
  r.removeListener("error", t), n(r);
1000
1006
  });
1001
1007
  });
1002
1008
  }
1003
- async function Ve(e, n = console.log) {
1004
- const t = await Ue(e.port), r = t.address();
1005
- return n(`zombie-mermaid web running at http://localhost:${U(r) && typeof r.port == "number" ? r.port : e.port}`), t;
1009
+ async function qe(e, n = console.log) {
1010
+ const t = await Ve(e.port), r = t.address();
1011
+ return n(`zombie-mermaid web running at http://localhost:${V(r) && typeof r.port == "number" ? r.port : e.port}`), t;
1006
1012
  }
1007
1013
  async function He(e = (n) => console.error(n)) {
1008
- const n = ae(), t = new ie();
1014
+ const n = se(), t = new ae();
1009
1015
  return await n.connect(t), e("zombie-mermaid MCP server running on stdio (experimental — feedback welcome: https://github.com/dfadler/zombie-mermaid/issues)"), n;
1010
1016
  }
1011
- function qe() {
1012
- const e = V(import.meta.url)("../package.json");
1017
+ function je() {
1018
+ const e = q(import.meta.url)("../package.json");
1013
1019
  return typeof e == "object" && e !== null && "version" in e && typeof e.version == "string" ? e.version : "unknown";
1014
1020
  }
1015
- ne(se);
1016
- var je = qe();
1017
- async function Ke() {
1021
+ re(de);
1022
+ var Ke = je();
1023
+ async function Je() {
1018
1024
  const e = process.argv.slice(2);
1019
1025
  let n;
1020
1026
  try {
1021
- n = me(e);
1027
+ n = ue(e);
1022
1028
  } catch (t) {
1023
1029
  console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
1024
1030
  }
1025
1031
  switch (n.command) {
1026
1032
  case "help":
1027
- Je();
1033
+ Ze();
1028
1034
  break;
1029
1035
  case "version":
1030
- console.log(`zombie-mermaid ${je}`);
1036
+ console.log(`zombie-mermaid ${Ke}`);
1031
1037
  break;
1032
1038
  case "themes":
1033
1039
  console.log(`Available themes:
1034
1040
  `);
1035
- for (const t of Object.keys(B)) console.log(` ${t}`);
1041
+ for (const t of Object.keys(O)) console.log(` ${t}`);
1036
1042
  break;
1037
1043
  case "render":
1038
1044
  try {
1039
- await Re(n);
1045
+ await Be(n);
1040
1046
  } catch (t) {
1041
1047
  console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
1042
1048
  }
1043
1049
  break;
1044
1050
  case "validate":
1045
1051
  try {
1046
- await Ne(n);
1052
+ await De(n);
1047
1053
  } catch (t) {
1048
1054
  console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
1049
1055
  }
1050
1056
  break;
1051
1057
  case "web":
1052
1058
  try {
1053
- await Ve(n);
1059
+ await qe(n);
1054
1060
  } catch (t) {
1055
1061
  console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
1056
1062
  }
@@ -1063,7 +1069,7 @@ async function Ke() {
1063
1069
  }
1064
1070
  }
1065
1071
  }
1066
- function Je() {
1072
+ function Ze() {
1067
1073
  console.log(`
1068
1074
  zombie-mermaid — render Mermaid diagrams from the command line
1069
1075
 
@@ -1128,6 +1134,10 @@ Options:
1128
1134
  VTE-based terminals render them as clickable links;
1129
1135
  others may show the URL or nothing. Pagers need
1130
1136
  OSC 8 support too (\`less -R\`, recent versions).
1137
+ --profile <name> ASCII preset: 'markdown' = plain ASCII characters, no
1138
+ color, for pasting into issues/commits/fenced blocks
1139
+ (explicit flags like --coords still win). Requires
1140
+ --ascii.
1131
1141
  -w, --max-width <n|auto> Fit ASCII output within this many columns
1132
1142
  ('auto' detects the terminal width): applies compact
1133
1143
  spacing automatically when needed, and warns on
@@ -1138,7 +1148,7 @@ Options:
1138
1148
  -v, --version Show version
1139
1149
  `.trim());
1140
1150
  }
1141
- Ke();
1151
+ Je();
1142
1152
  export {
1143
- Ke as main
1153
+ Je as main
1144
1154
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zombie-mermaid",
3
- "version": "5.3.0",
3
+ "version": "5.4.0",
4
4
  "license": "MIT",
5
5
  "description": "Render Mermaid diagrams as beautiful SVGs or ASCII art. Ultra-fast, fully themeable, zero DOM dependencies. Maintained fork of beautiful-mermaid.",
6
6
  "type": "module",
@@ -88,22 +88,24 @@
88
88
  },
89
89
  "dependencies": {
90
90
  "@modelcontextprotocol/sdk": "^1.30.0",
91
- "elkjs": "^0.11.0",
91
+ "elkjs": "^0.12.0",
92
92
  "entities": "^7.0.1",
93
- "@zombie-mermaid/ascii-renderer": "5.3.0",
94
- "@zombie-mermaid/mermaid-parser": "5.3.0",
95
- "@zombie-mermaid/mcp": "5.3.0",
96
- "@zombie-mermaid/core": "5.3.0",
97
- "@zombie-mermaid/svg-renderer": "5.3.0"
93
+ "@zombie-mermaid/ascii-renderer": "5.4.0",
94
+ "@zombie-mermaid/core": "5.4.0",
95
+ "@zombie-mermaid/mcp": "5.4.0",
96
+ "@zombie-mermaid/mermaid-parser": "5.4.0",
97
+ "@zombie-mermaid/svg-renderer": "5.4.0"
98
98
  },
99
99
  "devDependencies": {
100
100
  "@arethetypeswrong/cli": "0.18.5",
101
101
  "@axe-core/playwright": "^4.13.0",
102
- "@fast-check/vitest": "0.5.0",
103
102
  "@changesets/changelog-github": "^1.0.0",
104
103
  "@changesets/cli": "^3.0.1",
104
+ "@fast-check/vitest": "0.5.0",
105
105
  "@microsoft/api-extractor": "^7.59.0",
106
106
  "@playwright/test": "1.62.1",
107
+ "@stryker-mutator/core": "10.0.0",
108
+ "@stryker-mutator/vitest-runner": "10.0.0",
107
109
  "@testing-library/jest-dom": "^7.0.1",
108
110
  "@testing-library/react": "^16.3.3",
109
111
  "@testing-library/user-event": "^14.6.7",
@@ -171,12 +173,15 @@
171
173
  "check:changesets": "tsx scripts/check-changesets.ts",
172
174
  "check:docs": "tsx scripts/check-docs.ts",
173
175
  "smoke:packed": "node scripts/smoke-test-packed.mjs",
176
+ "check:pack-dist": "node scripts/check-pack-dist.mjs",
174
177
  "check:package-types": "scripts/check-package-types.sh",
175
178
  "check:bundle-size": "tsx scripts/check-bundle-size.ts",
176
179
  "generate:hero": "tsx scripts/generate-hero.ts",
177
180
  "build:mono-font": "tsx scripts/build-mono-font-subset.ts && prettier --write demo/components/generated/mono-font-subset.ts demo/styles.css packages/svg-renderer/src/generated/mono-font-subset.ts",
178
181
  "generate:primitives-classes": "tsx scripts/generate-primitives-classes.ts && prettier --write demo/components/generated/primitives-classes.ts",
179
182
  "generate:logo-lockup": "tsx scripts/generate-logo-lockup.ts",
183
+ "api:report": "tsx scripts/api-report.ts",
184
+ "mutation": "stryker run",
180
185
  "docs:scripts": "tsx scripts/generate-development-scripts-doc.ts && prettier --write docs/development-scripts.md",
181
186
  "badge:bundle-size": "tsx scripts/generate-bundle-badge.ts",
182
187
  "visual-diff": "tsx scripts/visual-diff.ts",