zombie-mermaid 5.0.1 → 5.1.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 CHANGED
@@ -73,7 +73,7 @@ the SVG renderer at all (see [ASCII Output](#ascii-output)).
73
73
 
74
74
  ## Features
75
75
 
76
- - **6 diagram types** — Flowcharts, State, Sequence, Class, ER, and XY Charts (bar, line, combined)
76
+ - **8 diagram types** — Flowcharts (including State), Sequence, Class, ER, XY Charts (bar, line, combined), Architecture, C4, and Pie
77
77
  - **Dual output** — SVG for rich UIs, ASCII/Unicode for terminals
78
78
  - **Synchronous rendering** — No async, no flash. Works with React `useMemo()`
79
79
  - **15 built-in themes** — And dead simple to add your own
@@ -86,6 +86,8 @@ the SVG renderer at all (see [ASCII Output](#ascii-output)).
86
86
 
87
87
  ## Installation
88
88
 
89
+ Requires Node.js 24 or newer (`engines` in `package.json`).
90
+
89
91
  ```bash
90
92
  npm install zombie-mermaid
91
93
  # or
@@ -94,9 +96,16 @@ bun add zombie-mermaid
94
96
  pnpm add zombie-mermaid
95
97
  ```
96
98
 
97
- > **Upgrading from 4.x?** `elkjs` is now an optional peer dependency: browser and
98
- > bundler apps must call `registerElk(ELK)` once. See the
99
- > [migration guide](docs/guides/elkjs-optional-peer.md).
99
+ > Not sure this is the right package? See [Which package do I install?](docs/guides/choosing-a-package.md).
100
+
101
+ > **Upgrading from 4.x?** Node.js 24 or newer is now required. `elkjs` is now an
102
+ > optional peer dependency: browser and bundler apps must call `registerElk(ELK)`
103
+ > once (see the [migration guide](docs/guides/elkjs-optional-peer.md)). The
104
+ > package no longer ships `src/` (see [#1538](https://github.com/dfadler/zombie-mermaid/issues/1538)).
105
+
106
+ > **`elkjs` in the umbrella package:** the sub-packages (`@zombie-mermaid/svg-renderer`) treat `elkjs` as an
107
+ > optional peer, but `zombie-mermaid` itself lists it as a regular dependency so the CLI and MCP server work
108
+ > out of the box. See [what happens if you remove it](docs/guides/elkjs-optional-peer.md#removing-elkjs-from-zombie-mermaid).
100
109
 
101
110
  Only need one output format? `@zombie-mermaid/ascii-renderer` and `@zombie-mermaid/svg-renderer` are published standalone — see [Packages](#packages) below.
102
111
 
@@ -129,6 +138,7 @@ import { registerElk } from 'zombie-mermaid'
129
138
  registerElk(ELK)
130
139
  ```
131
140
 
141
+ Architecture diagrams (`architecture-beta`, see [Supported Diagrams](docs/diagrams.md#architecture-diagrams)) are laid out by `elkjs` too.
132
142
  Sequence, pie, xychart and C4 diagrams, and all ASCII output, need no `elkjs`
133
143
  and no registration. Rendering a graph diagram with no `elkjs` available throws an
134
144
  `ElkNotRegisteredError` that says how to fix it.
@@ -201,7 +211,7 @@ hyperlinks — off by default, since terminal and pager support varies (see
201
211
 
202
212
  > 🧪 **Experimental — shipped to gauge interest, not a finished or best-effort implementation.** This is a first cut covering the common case; the tool surface may change based on feedback. Try it and [open an issue](https://github.com/dfadler/zombie-mermaid/issues/new) with what you'd want from it.
203
213
 
204
- `zombie-mermaid mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io/) server on stdio, exposing the library's rendering as six tools: `render_mermaid_svg`, `render_mermaid_ascii`, `check_mermaid_sequence_activations`, `fix_mermaid_sequence_activations`, `list_themes`, and `list_diagram_types`. `render_mermaid_svg` also takes optional `bg`/`fg` hex color overrides (applied on top of `theme`) and an optional `outputPath` that writes the SVG to disk and returns `{ saved, size }` instead of the SVG text. `outputPath` must end in `.svg` and resolve inside the server's working directory (the directory your MCP client launched it from); its parent directory must already exist, symlinks and non-regular files are refused, and an existing `.svg` file at that path is overwritten. Point an MCP client (Claude Desktop, Claude Code, or anything else that speaks MCP) at it to render Mermaid diagrams directly in a conversation, without shelling out to the CLI or importing the library.
214
+ `zombie-mermaid mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io/) server on stdio, exposing the library's rendering as seven tools: `render_mermaid_svg`, `render_mermaid_ascii`, `validate_mermaid`, `check_mermaid_sequence_activations`, `fix_mermaid_sequence_activations`, `list_themes`, and `list_diagram_types`. `render_mermaid_svg` also takes optional `bg`/`fg` hex color overrides (applied on top of `theme`) and an optional `outputPath` that writes the SVG to disk and returns `{ saved, size }` instead of the SVG text. `outputPath` must end in `.svg` and resolve inside the server's working directory (the directory your MCP client launched it from); its parent directory must already exist, symlinks and non-regular files are refused, and an existing `.svg` file at that path is overwritten. Per-tool arguments and return shapes are in the [MCP tool reference](docs/mcp-tools.md). Point an MCP client (Claude Desktop, Claude Code, or anything else that speaks MCP) at it to render Mermaid diagrams directly in a conversation, without shelling out to the CLI or importing the library.
205
215
 
206
216
  Example Claude Desktop / Claude Code MCP server config:
207
217
 
@@ -216,7 +226,7 @@ Example Claude Desktop / Claude Code MCP server config:
216
226
  }
217
227
  ```
218
228
 
219
- All three tools accept a `diagram` string. The two render tools also take a handful of rendering options (`theme`, `transparent`, `font` for SVG; `useAscii`, `paddingX`/`paddingY`/`boxBorderPadding` for ASCII) — see each tool's `inputSchema` for the full, current list. Invalid Mermaid syntax comes back as a normal tool error (`isError: true`) rather than crashing the connection.
229
+ The five diagram tools (including `validate_mermaid`, parse-only) accept a `diagram` string (`list_themes` and `list_diagram_types` take no arguments). The two render tools also take a handful of rendering options (`theme`, `transparent`, `font` for SVG; `useAscii`, `paddingX`/`paddingY`/`boxBorderPadding` for ASCII) — see each tool's `inputSchema` for the full, current list. Invalid Mermaid syntax comes back as a normal tool error (`isError: true`) rather than crashing the connection; `validate_mermaid` is the exception and reports it as a normal `{ valid: false, diagnostics }` result.
220
230
 
221
231
  `check_mermaid_sequence_activations` is a mechanical, deterministic check — no LLM judgment involved — for a specific gap in existing Mermaid validators: every `activate X` (or `+` arrow shorthand) in a `sequenceDiagram` must be closed by a matching `deactivate X` (`-` shorthand) before the diagram ends. It returns a JSON report (`{ ok, issues }`) rather than rendering anything, and errors (`isError: true`) if given a non-sequence diagram.
222
232
 
@@ -266,12 +276,13 @@ See [docs/decisions/monorepo-conversion.md](docs/decisions/monorepo-conversion.m
266
276
 
267
277
  ## Docs
268
278
 
269
- - [Guides](docs/guides/) — task-oriented walkthroughs: [browsing the samples](docs/guides/samples.md), [choosing a theme](docs/guides/theming.md), [using the HTML viewer](docs/guides/html-viewer.md)
279
+ - [Guides](docs/guides/) — task-oriented walkthroughs: [browsing the samples](docs/guides/samples.md), [choosing a theme](docs/guides/theming.md), [using the HTML viewer](docs/guides/html-viewer.md), [upgrading from 2.x to 5.x](docs/guides/upgrading-2x-to-5x.md)
270
280
  - [Accessibility](docs/accessibility.md) — conformance statement: what's guaranteed (and CI-enforced), what's implemented but unverified by automation, and what isn't covered
271
281
  - [Theming](docs/theming.md) — the two-color foundation, enriched mode, built-in themes, custom themes, Shiki compatibility
272
282
  - [Supported Diagrams](docs/diagrams.md) — syntax for every diagram type, XY chart styling, and ASCII rendering options
273
283
  - [React Integration](docs/react-integration.md) — zero-flash rendering with `useMemo()`
274
284
  - [API Reference](docs/api-reference.md) — full function and options reference
285
+ - [Versioning and deprecation policy](docs/decisions/versioning-and-deprecation-policy-1568.md) — what counts as a breaking change, and how old majors are supported and deprecated
275
286
  - [Migrating from `beautiful-mermaid`](docs/migrating-from-beautiful-mermaid.md) — what's drop-in, which fixes change rendered output, and how to audit your own diagrams before upgrading
276
287
 
277
288
  More design write-ups and reference material live in [docs/](docs/).
@@ -0,0 +1,14 @@
1
+ # Third-party notices
2
+
3
+ This repository includes material from the projects below. Each published
4
+ package ships only the notices that apply to it (see its own
5
+ `THIRD_PARTY_NOTICES.md`).
6
+
7
+ - ASCII renderer: based on [mermaid-ascii](https://github.com/AlexanderGrooff/mermaid-ascii), MIT,
8
+ Copyright (c) 2023 Alexander Grooff
9
+ (`packages/ascii-renderer/THIRD_PARTY_NOTICES.md`).
10
+ - JetBrains Mono NL font subset: SIL OFL 1.1, Copyright 2020 The JetBrains Mono
11
+ Project Authors (`packages/svg-renderer/THIRD_PARTY_NOTICES.md`,
12
+ `third_party/fonts/`).
13
+ - Forked from [beautiful-mermaid](https://github.com/lukilabs/beautiful-mermaid),
14
+ MIT, Copyright (c) 2026 Craft Docs (see `LICENSE`).
package/dist/cli.js CHANGED
@@ -1,28 +1,28 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire as U } from "node:module";
3
- import { extname as V, format as H, parse as G } from "node:path";
4
- import { THEMES as B, escapeXml as q, isDirection as j, isWideChar as K } from "@zombie-mermaid/core";
5
- import { access as J, readFile as Z, writeFile as Q } from "node:fs/promises";
2
+ import { createRequire as V } from "node:module";
3
+ import { extname as H, format as q, parse as D } from "node:path";
4
+ import { THEMES as B, escapeXml as j, isDirection as K, isWideChar as J } from "@zombie-mermaid/core";
5
+ import { access as Z, readFile as F, writeFile as Q } from "node:fs/promises";
6
6
  import { diagramColorsToAsciiTheme as ee, renderMermaidASCII as O } from "@zombie-mermaid/ascii-renderer";
7
7
  import "@zombie-mermaid/mermaid-parser";
8
- import { registerElk as te, renderMermaidSVG as z } from "@zombie-mermaid/svg-renderer";
9
- import { createServer as ne } from "node:http";
10
- import { StdioServerTransport as re } from "@modelcontextprotocol/sdk/server/stdio.js";
11
- import { createMcpServer as oe } from "@zombie-mermaid/mcp";
12
- import ie from "elkjs/lib/elk.bundled.js";
13
- var ae = 3e3;
14
- var W = {
8
+ import { registerElk as te, renderMermaidSVG as M, validateMermaid as ne } from "@zombie-mermaid/svg-renderer";
9
+ import { createServer as re } from "node:http";
10
+ import { StdioServerTransport as oe } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import { createMcpServer as ie } from "@zombie-mermaid/mcp";
12
+ import ae from "elkjs/lib/elk.bundled.js";
13
+ var se = 3e3;
14
+ var G = {
15
15
  ".svg": "svg",
16
16
  ".txt": "ascii",
17
17
  ".html": "html",
18
18
  ".htm": "html",
19
19
  ".png": "png"
20
20
  };
21
- function se(e) {
21
+ function de(e) {
22
22
  if (!(e === void 0 || e === "-"))
23
- return W[V(e).toLowerCase()];
23
+ return G[H(e).toLowerCase()];
24
24
  }
25
- var v = {
25
+ var w = {
26
26
  svg: {
27
27
  flag: "--svg",
28
28
  ext: ".svg",
@@ -44,49 +44,57 @@ var v = {
44
44
  display: "PNG",
45
45
  label: "the PNG rasterization"
46
46
  }
47
- }, A = Object.keys(v);
48
- function de(e, n) {
49
- const t = G(e);
50
- return H({
47
+ }, T = Object.keys(w);
48
+ function ce(e, n) {
49
+ const t = D(e);
50
+ return q({
51
51
  dir: t.dir,
52
52
  name: t.name,
53
53
  ext: n
54
54
  });
55
55
  }
56
- function ce(e) {
56
+ function le(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 ue(t);
63
- if (n === "web") return pe(t);
62
+ if (n === "render") return pe(t);
63
+ if (n === "validate") {
64
+ const r = t.find((a, c) => c > 0 || a.startsWith("-"));
65
+ if (r !== void 0) throw new Error(`Unexpected argument: ${r}`);
66
+ return {
67
+ command: "validate",
68
+ input: t[0]
69
+ };
70
+ }
71
+ if (n === "web") return fe(t);
64
72
  if (n === "mcp") return { command: "mcp" };
65
73
  throw new Error(`Unknown command: ${n}`);
66
74
  }
67
- function $(e, n, t) {
75
+ function z(e, n, t) {
68
76
  const r = e[n + 1];
69
77
  if (n + 1 >= e.length || r === void 0) throw new Error(`${t} requires a numeric value`);
70
78
  if (!/^\d+$/.test(r)) throw new Error(`${t} requires a non-negative integer, got: "${r}"`);
71
79
  return Number(r);
72
80
  }
73
- function le(e, n, t) {
81
+ function me(e, n, t) {
74
82
  const r = e[n + 1];
75
83
  if (n + 1 >= e.length || r === void 0) throw new Error(`${t} requires a value ("auto" or a positive integer)`);
76
84
  if (r === "auto") return "auto";
77
- const s = Number(r);
78
- if (!/^\d+$/.test(r) || s === 0 || !Number.isSafeInteger(s)) throw new Error(`${t} requires "auto" or a positive integer, got: "${r}"`);
79
- return s;
85
+ const a = Number(r);
86
+ if (!/^\d+$/.test(r) || a === 0 || !Number.isSafeInteger(a)) throw new Error(`${t} requires "auto" or a positive integer, got: "${r}"`);
87
+ return a;
80
88
  }
81
- function me(e, n, t) {
89
+ function ue(e, n, t) {
82
90
  const r = e[n + 1];
83
91
  if (n + 1 >= e.length || r === void 0) throw new Error(`${t} requires a value (one of TD, TB, BT, LR, RL)`);
84
- const s = r.toUpperCase();
85
- if (!j(s)) throw new Error(`${t} requires one of TD, TB, BT, LR, RL, got: "${r}"`);
86
- return s;
92
+ const a = r.toUpperCase();
93
+ if (!K(a)) throw new Error(`${t} requires one of TD, TB, BT, LR, RL, got: "${r}"`);
94
+ return a;
87
95
  }
88
- function ue(e) {
89
- let n, t = !1, r = !1, s = !1, m = !1, c = !1, d, E = !1, S, k, g, C, T = !1, b = !1, a, u, i = 0;
96
+ function pe(e) {
97
+ let n, t = !1, r = !1, a = !1, c = !1, l = !1, m, g = !1, E, S, k, s, u = !1, p = !1, h, b, i = 0;
90
98
  for (; i < e.length; ) {
91
99
  const o = e[i];
92
100
  if (o === void 0) throw new Error(`parseArgs: index ${i} out of range while parsing arguments`);
@@ -95,115 +103,115 @@ function ue(e) {
95
103
  else if (o === "--svg")
96
104
  r = !0, i++;
97
105
  else if (o === "--resolve-colors")
98
- s = !0, i++;
106
+ a = !0, i++;
99
107
  else if (o === "--html")
100
- m = !0, i++;
101
- else if (o === "--png")
102
108
  c = !0, i++;
109
+ else if (o === "--png")
110
+ l = !0, i++;
103
111
  else if (o === "-o" || o === "--output") {
104
112
  if (i + 1 >= e.length) throw new Error("-o requires a file path (or - for stdout)");
105
- d = e[i + 1], i += 2;
113
+ m = e[i + 1], i += 2;
106
114
  } else if (o === "-f" || o === "--force")
107
- E = !0, i++;
115
+ g = !0, i++;
108
116
  else if (o === "--theme") {
109
117
  if (i + 1 >= e.length) throw new Error("--theme requires a theme name");
110
- S = e[i + 1], i += 2;
118
+ E = e[i + 1], i += 2;
111
119
  } else if (o === "-x" || o === "--paddingX")
112
- k = $(e, i, o), i += 2;
120
+ S = z(e, i, o), i += 2;
113
121
  else if (o === "-y" || o === "--paddingY")
114
- g = $(e, i, o), i += 2;
122
+ k = z(e, i, o), i += 2;
115
123
  else if (o === "-p" || o === "--borderPadding")
116
- C = $(e, i, o), i += 2;
124
+ s = z(e, i, o), i += 2;
117
125
  else if (o === "--coords")
118
- T = !0, i++;
126
+ u = !0, i++;
119
127
  else if (o === "--hyperlinks")
120
- b = !0, i++;
128
+ p = !0, i++;
121
129
  else if (o === "-w" || o === "--max-width")
122
- a = le(e, i, o), i += 2;
130
+ h = me(e, i, o), i += 2;
123
131
  else if (o === "--direction")
124
- u = me(e, i, o), i += 2;
132
+ b = ue(e, i, o), i += 2;
125
133
  else if (!o.startsWith("-") || o === "-") {
126
134
  if (o === "-") throw new Error("Unexpected argument: - (use -o - to write output to stdout; omit the input file to read stdin)");
127
135
  if (n !== void 0) throw new Error(`Unexpected argument: ${o} (input file already set to "${n}")`);
128
136
  n = o, i++;
129
137
  } else throw new Error(`Unknown flag: ${o}`);
130
138
  }
131
- const h = {
139
+ const v = {
132
140
  svg: r,
133
- html: m,
134
- png: c
135
- }, w = A.filter((o) => h[o]);
136
- if (w.length > 1) {
137
- const [o, l] = w;
138
- throw new Error(`${v[o].flag} and ${v[l].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.`);
141
+ html: c,
142
+ png: l
143
+ }, y = T.filter((o) => v[o]);
144
+ if (y.length > 1) {
145
+ const [o, d] = y;
146
+ throw new Error(`${w[o].flag} and ${w[d].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.`);
139
147
  }
140
- const p = se(d);
141
- if (p !== void 0 && p !== "ascii") {
142
- const o = A.find((l) => l !== p && h[l]);
148
+ const f = de(m);
149
+ if (f !== void 0 && f !== "ascii") {
150
+ const o = T.find((d) => d !== f && v[d]);
143
151
  if (o !== void 0) {
144
- const l = v[p], x = v[o];
145
- throw new Error(`-o ${d} has ${l.article} ${l.ext} extension, but ${x.flag} output would be written to it. Use a ${x.ext} path (or -o - for stdout) for ${x.label}.`);
152
+ const d = w[f], $ = w[o];
153
+ throw new Error(`-o ${m} has ${d.article} ${d.ext} extension, but ${$.flag} output would be written to it. Use a ${$.ext} path (or -o - for stdout) for ${$.label}.`);
146
154
  }
147
- p === "svg" ? r = !0 : p === "html" ? m = !0 : c = !0;
148
- } else if (p === "ascii") {
149
- const o = A.find((l) => h[l]);
155
+ f === "svg" ? r = !0 : f === "html" ? c = !0 : l = !0;
156
+ } else if (f === "ascii") {
157
+ const o = T.find((d) => v[d]);
150
158
  if (o !== void 0) {
151
- const l = v[o];
152
- throw new Error(`-o ${d} has a .txt extension, but ${l.flag} output would be written to it. Use a ${l.ext} path (or -o - for stdout) instead.`);
159
+ const d = w[o];
160
+ throw new Error(`-o ${m} has a .txt extension, but ${d.flag} output would be written to it. Use a ${d.ext} path (or -o - for stdout) instead.`);
153
161
  }
154
162
  t = !0;
155
163
  }
156
- if (!t && !r && !m && !c) {
157
- if (d !== void 0 && d !== "-") {
158
- const o = Object.keys(W).join(", ");
159
- throw new Error(`Cannot infer an output format from "${d}" — pass --ascii, --svg, --html, or --png, or use a recognised extension (${o})`);
164
+ if (!t && !r && !c && !l) {
165
+ if (m !== void 0 && m !== "-") {
166
+ const o = Object.keys(G).join(", ");
167
+ throw new Error(`Cannot infer an output format from "${m}" — pass --ascii, --svg, --html, or --png, or use a recognised extension (${o})`);
160
168
  }
161
169
  throw new Error("Specify --ascii, --svg, --html, and/or --png (or -o <path> with a .svg/.txt/.html/.png extension)");
162
170
  }
163
- const y = {
171
+ const C = {
164
172
  svg: r,
165
- html: m,
166
- png: c
167
- }, f = A.find((o) => y[o]);
168
- if (d === "-" && t && f !== void 0) {
169
- const o = v[f].display;
173
+ html: c,
174
+ png: l
175
+ }, x = T.find((o) => C[o]);
176
+ if (m === "-" && t && x !== void 0) {
177
+ const o = w[x].display;
170
178
  throw new Error(`-o - would send both ASCII and ${o} to stdout; drop --ascii, or write the ${o} to a file path`);
171
179
  }
172
- if (f !== void 0 && d === void 0) {
173
- const { flag: o, ext: l } = v[f];
180
+ if (x !== void 0 && m === void 0) {
181
+ const { flag: o, ext: d } = w[x];
174
182
  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`);
175
- d = de(n, l);
183
+ m = ce(n, d);
176
184
  }
177
- if (a !== void 0 && !t) throw new Error("-w/--max-width requires --ascii");
178
- if (b && !t) throw new Error("--hyperlinks requires --ascii");
179
- if (s && !r) throw new Error("--resolve-colors requires --svg");
185
+ if (h !== void 0 && !t) throw new Error("-w/--max-width requires --ascii");
186
+ if (p && !t) throw new Error("--hyperlinks requires --ascii");
187
+ if (a && !r) throw new Error("--resolve-colors requires --svg");
180
188
  return {
181
189
  command: "render",
182
190
  input: n,
183
191
  ascii: t,
184
192
  svg: r,
185
- resolveColors: s,
186
- html: m,
187
- png: c,
188
- output: d,
189
- force: E,
190
- theme: S,
191
- paddingX: k,
192
- paddingY: g,
193
- borderPadding: C,
194
- coords: T,
195
- hyperlinks: b,
196
- maxWidth: a,
197
- direction: u
193
+ resolveColors: a,
194
+ html: c,
195
+ png: l,
196
+ output: m,
197
+ force: g,
198
+ theme: E,
199
+ paddingX: S,
200
+ paddingY: k,
201
+ borderPadding: s,
202
+ coords: u,
203
+ hyperlinks: p,
204
+ maxWidth: h,
205
+ direction: b
198
206
  };
199
207
  }
200
- function pe(e) {
201
- let n = ae, t = 0;
208
+ function fe(e) {
209
+ let n = se, t = 0;
202
210
  for (; t < e.length; ) {
203
211
  const r = e[t];
204
212
  if (r === void 0) throw new Error(`parseArgs: index ${t} out of range while parsing arguments`);
205
213
  if (r === "--port")
206
- n = $(e, t, r), t += 2;
214
+ n = z(e, t, r), t += 2;
207
215
  else throw new Error(`Unknown flag: ${r}`);
208
216
  }
209
217
  return {
@@ -212,33 +220,51 @@ function pe(e) {
212
220
  };
213
221
  }
214
222
  var he = /\p{Mn}|\p{Me}/u;
215
- function fe(e) {
223
+ function ge(e) {
216
224
  return he.test(e);
217
225
  }
218
- var ge = new Intl.Segmenter(void 0, { granularity: "grapheme" }), ve = "️";
219
- function we(e) {
226
+ var ve = new Intl.Segmenter(void 0, { granularity: "grapheme" }), we = "️";
227
+ function be(e) {
220
228
  if (/^[\x20-\x7e]*$/.test(e)) return e.split("");
221
229
  const n = [];
222
- for (const { segment: t } of ge.segment(e)) n.push(t);
230
+ for (const { segment: t } of ve.segment(e)) n.push(t);
223
231
  return n;
224
232
  }
225
- function be(e) {
226
- if (e.includes(ve)) return 2;
233
+ function ye(e) {
234
+ if (e.includes(we)) return 2;
227
235
  let n = !1, t = !1;
228
236
  for (const r of e)
229
- K(r) && (n = !0), fe(r) || (t = !0);
237
+ J(r) && (n = !0), ge(r) || (t = !0);
230
238
  return n ? 2 : t ? 1 : 0;
231
239
  }
232
- function ye(e) {
240
+ function xe(e) {
233
241
  let n = 0;
234
- for (const t of we(e)) n += be(t);
242
+ for (const t of be(e)) n += ye(t);
235
243
  return n;
236
244
  }
237
- var xe = /\x1b\]8;[^\x07\x1b]*(?:\x1b\\|\x07)/g;
238
- function Ee(e) {
239
- return e.replace(xe, "");
245
+ var Ee = /\x1b\]8;[^\x07\x1b]*(?:\x1b\\|\x07)/g;
246
+ function Se(e) {
247
+ return e.replace(Ee, "");
240
248
  }
241
- var Se = `// @ts-check
249
+ var ke = /^Line (\d+): ([\s\S]*)$/;
250
+ function Ce(e, n) {
251
+ const t = ke.exec(e);
252
+ if (t === null) return;
253
+ const r = Number(t[1]), a = n.split(/\r?\n/)[r - 1];
254
+ if (!(!Number.isSafeInteger(r) || r < 1 || a === void 0))
255
+ return {
256
+ line: r,
257
+ sourceLine: a,
258
+ message: t[2] ?? ""
259
+ };
260
+ }
261
+ function W(e, n) {
262
+ if (!(e instanceof Error)) return e;
263
+ const t = Ce(e.message, n);
264
+ return t === void 0 ? e : /* @__PURE__ */ new Error(`${e.message}
265
+ ${t.line} | ${t.sourceLine}`);
266
+ }
267
+ var Te = `// @ts-check
242
268
  /// <reference lib="dom" />
243
269
  // ============================================================================
244
270
  // Client script for the CLI's self-contained HTML viewer (\`render --html\`).
@@ -530,7 +556,7 @@ var Se = `// @ts-check
530
556
  fit()
531
557
  stage.focus({ preventScroll: true })
532
558
  })()
533
- `, ke = `
559
+ `, Ae = `
534
560
  :root {
535
561
  color-scheme: light dark;
536
562
  --bg: #f4f4f5;
@@ -637,16 +663,16 @@ var Se = `// @ts-check
637
663
  #diagram { position: static; transform: none !important; }
638
664
  }
639
665
  `.trim();
640
- function Ce(e) {
666
+ function Ie(e) {
641
667
  const n = e.title?.trim() || "Mermaid diagram";
642
668
  return `<!doctype html>
643
669
  <html lang="en">
644
670
  <head>
645
671
  <meta charset="utf-8" />
646
672
  <meta name="viewport" content="width=device-width, initial-scale=1" />
647
- <title>${q(n)}</title>
673
+ <title>${j(n)}</title>
648
674
  <style>
649
- ${ke}
675
+ ${Ae}
650
676
  </style>
651
677
  </head>
652
678
  <body>
@@ -664,13 +690,13 @@ ${e.svg}
664
690
  <button id="theme" type="button" aria-pressed="false" aria-label="Switch to dark mode (T)">Dark</button>
665
691
  </div>
666
692
  <script>
667
- ${Se}
693
+ ${Te}
668
694
  <\/script>
669
695
  </body>
670
696
  </html>
671
697
  `;
672
698
  }
673
- async function Te(e) {
699
+ async function Me(e) {
674
700
  let n;
675
701
  try {
676
702
  n = await import("@resvg/resvg-js");
@@ -681,123 +707,130 @@ ${r}`);
681
707
  }
682
708
  return (await n.renderAsync(e)).asPng();
683
709
  }
684
- var Ae = 80, P = 1, L = 1, R = 0, Ie = /\x1b\[[0-9;]*m/g;
685
- function D(e) {
710
+ var ze = 80, P = 1, L = 1, R = 0, $e = /\x1b\[[0-9;]*m/g;
711
+ function _(e) {
686
712
  return e.split(`
687
- `).reduce((n, t) => Math.max(n, ye(Ee(t.replace(Ie, "")))), 0);
713
+ `).reduce((n, t) => Math.max(n, xe(Se(t.replace($e, "")))), 0);
688
714
  }
689
- function Me(e) {
690
- return e !== "auto" ? e : process.stdout.columns ?? Ae;
715
+ function Pe(e) {
716
+ return e !== "auto" ? e : process.stdout.columns ?? ze;
691
717
  }
692
- async function ze(e, n, t, r) {
693
- const s = n ?? process.stdout, m = r ?? process.stderr;
694
- let c;
695
- if (e.input !== void 0) c = await Z(e.input, "utf-8");
696
- else if (t !== void 0) c = t;
718
+ async function Le(e, n, t, r) {
719
+ const a = n ?? process.stdout, c = r ?? process.stderr;
720
+ let l;
721
+ if (e.input !== void 0) l = await F(e.input, "utf-8");
722
+ else if (t !== void 0) l = t;
697
723
  else {
698
724
  if (process.stdin.isTTY) throw new Error("No input file specified and stdin is a terminal. Pipe a diagram or pass a file path.");
699
- c = await Le();
725
+ l = await Y();
700
726
  }
701
- if (c = c.trim(), c.length === 0) throw new Error("Empty input — provide a Mermaid diagram via file or stdin");
702
- let d;
703
- if (e.theme !== void 0 && (d = B[e.theme], d === void 0)) {
704
- const a = Object.keys(B).join(", ");
705
- throw new Error(`Unknown theme: "${e.theme}". Available themes: ${a}`);
727
+ if (l.trim().length === 0) throw new Error("Empty input — provide a Mermaid diagram via file or stdin");
728
+ try {
729
+ await Re(e, l, a, c);
730
+ } catch (m) {
731
+ throw W(m, l);
706
732
  }
707
- const E = e.svg && e.output === "-", S = e.html && e.output === "-", k = e.png && e.output === "-", g = e.ascii && !e.svg && !e.html && !e.png && e.output !== void 0 && e.output !== "-" ? e.output : void 0, C = e.svg && !E ? e.output : void 0, T = e.html && !S ? e.output : void 0, b = e.png && !k ? e.output : void 0;
708
- for (const a of [
733
+ }
734
+ async function Re(e, n, t, r) {
735
+ let a;
736
+ if (e.theme !== void 0 && (a = B[e.theme], a === void 0)) {
737
+ const s = Object.keys(B).join(", ");
738
+ throw new Error(`Unknown theme: "${e.theme}". Available themes: ${s}`);
739
+ }
740
+ const c = 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 && !c ? e.output : void 0, S = e.html && !l ? e.output : void 0, k = e.png && !m ? e.output : void 0;
741
+ for (const s of [
709
742
  g,
710
- C,
711
- T,
712
- b
713
- ]) a !== void 0 && await $e(a, e.force);
743
+ E,
744
+ S,
745
+ k
746
+ ]) s !== void 0 && await Be(s, e.force);
714
747
  if (e.ascii) {
715
- const a = d && g === void 0 ? {
748
+ const s = a && g === void 0 ? {
716
749
  colorMode: "auto",
717
- theme: ee(d)
750
+ theme: ee(a)
718
751
  } : { colorMode: "none" };
719
- e.paddingX !== void 0 && (a.paddingX = e.paddingX), e.paddingY !== void 0 && (a.paddingY = e.paddingY), e.borderPadding !== void 0 && (a.boxBorderPadding = e.borderPadding), e.coords && (a.showCoords = !0), e.direction !== void 0 && (a.direction = e.direction), e.hyperlinks && g === void 0 && (a.hyperlinks = !0);
720
- let u = O(c, a);
752
+ 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);
753
+ let u = O(n, s);
721
754
  if (e.maxWidth !== void 0) {
722
- const i = Me(e.maxWidth);
723
- let h = D(u), w = !1;
724
- if (h > i) {
725
- const p = a.paddingX ?? 5, y = a.paddingY ?? 5, f = a.boxBorderPadding ?? 1, o = Math.min(p, P), l = Math.min(y, L), x = Math.min(f, R);
726
- if (o < p || l < y || x < f) {
727
- const _ = O(c, {
728
- ...a,
729
- paddingX: o,
730
- paddingY: l,
755
+ const p = Pe(e.maxWidth);
756
+ let h = _(u), b = !1;
757
+ if (h > p) {
758
+ 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);
759
+ if (f < i || C < v || x < y) {
760
+ const o = O(n, {
761
+ ...s,
762
+ paddingX: f,
763
+ paddingY: C,
731
764
  boxBorderPadding: x
732
- }), N = D(_);
733
- N < h && (u = _, h = N, w = !0);
765
+ }), d = _(o);
766
+ d < h && (u = o, h = d, b = !0);
734
767
  }
735
768
  }
736
- if (h > i) {
737
- const p = e.maxWidth === "auto" ? "detected terminal width" : "--max-width", y = w ? `Compact spacing (-x ${P} -y ${L} -p ${R}) was already applied automatically. ` : "", f = w ? "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.";
738
- m.write(`Warning: ASCII output is ${h} columns wide, exceeding ${p} of ${i}. ${y}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). ${f}
769
+ if (h > p) {
770
+ 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.";
771
+ 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}
739
772
  `);
740
- } else w && m.write(`Note: ASCII output exceeded ${i} columns at the requested spacing; applied compact spacing automatically (-x ${P} -y ${L} -p ${R}) to fit.
773
+ } 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.
741
774
  `);
742
775
  }
743
- await M(I(g === void 0, g), u + `
744
- `, s, e.force);
776
+ await I(A(g === void 0, g), u + `
777
+ `, t, e.force);
745
778
  }
746
779
  if (e.svg) {
747
- const a = {
748
- ...d,
780
+ const s = {
781
+ ...a,
749
782
  resolveColors: e.resolveColors
750
783
  };
751
- e.direction !== void 0 && (a.direction = e.direction);
752
- const u = z(c, a);
753
- await M(I(E, C), u, s, e.force);
784
+ e.direction !== void 0 && (s.direction = e.direction);
785
+ const u = M(n, s);
786
+ await I(A(c, E), u, t, e.force);
754
787
  }
755
788
  if (e.html) {
756
- const a = z(c, d ?? {}), u = e.input ? G(e.input).name : void 0, i = Ce({
757
- svg: a,
789
+ const s = M(n, a ?? {}), u = e.input ? D(e.input).name : void 0, p = Ie({
790
+ svg: s,
758
791
  title: u
759
792
  });
760
- await M(I(S, T), i, s, e.force);
793
+ await I(A(l, S), p, t, e.force);
761
794
  }
762
795
  if (e.png) {
763
- const a = {
764
- ...d,
796
+ const s = {
797
+ ...a,
765
798
  resolveColors: !0
766
799
  };
767
- e.direction !== void 0 && (a.direction = e.direction);
768
- const u = z(c, a), i = await Te(u);
769
- await M(I(k, b), i, s, e.force);
800
+ e.direction !== void 0 && (s.direction = e.direction);
801
+ const u = M(n, s), p = await Me(u);
802
+ await I(A(m, k), p, t, e.force);
770
803
  }
771
804
  }
772
- function I(e, n) {
805
+ function A(e, n) {
773
806
  return e ? { kind: "stdout" } : n !== void 0 ? {
774
807
  kind: "file",
775
808
  path: n
776
809
  } : { kind: "skip" };
777
810
  }
778
- async function M(e, n, t, r) {
811
+ async function I(e, n, t, r) {
779
812
  switch (e.kind) {
780
813
  case "stdout":
781
814
  t.write(n);
782
815
  break;
783
816
  case "file":
784
- await Pe(e.path, n, r);
817
+ await Oe(e.path, n, r);
785
818
  }
786
819
  }
787
820
  function X(e) {
788
821
  return /* @__PURE__ */ new Error(`Refusing to overwrite existing file "${e}" — pass --force to replace it`);
789
822
  }
790
- async function $e(e, n) {
823
+ async function Be(e, n) {
791
824
  if (n) return;
792
825
  let t = !0;
793
826
  try {
794
- await J(e);
827
+ await Z(e);
795
828
  } catch {
796
829
  t = !1;
797
830
  }
798
831
  if (t) throw X(e);
799
832
  }
800
- async function Pe(e, n, t) {
833
+ async function Oe(e, n, t) {
801
834
  try {
802
835
  await Q(e, n, {
803
836
  encoding: "utf-8",
@@ -807,37 +840,54 @@ async function Pe(e, n, t) {
807
840
  throw !t && r instanceof Error && "code" in r && r.code === "EEXIST" ? X(e) : r;
808
841
  }
809
842
  }
810
- async function Le() {
843
+ async function Y() {
811
844
  const e = [], n = process.stdin;
812
845
  return n.setEncoding("utf-8"), new Promise((t, r) => {
813
- n.on("data", (s) => e.push(s)), n.on("end", () => t(e.join(""))), n.on("error", r);
846
+ n.on("data", (a) => e.push(a)), n.on("end", () => t(e.join(""))), n.on("error", r);
814
847
  });
815
848
  }
816
- var Re = 1e6;
817
- function Be(e) {
849
+ async function _e(e, n) {
850
+ let t;
851
+ if (e.input !== void 0) t = await F(e.input, "utf-8");
852
+ else if (n !== void 0) t = n;
853
+ else {
854
+ if (process.stdin.isTTY) throw new Error("No input file specified and stdin is a terminal. Pipe a diagram or pass a file path.");
855
+ t = await Y();
856
+ }
857
+ const r = t;
858
+ if (r.trim().length === 0) throw new Error("Empty input — provide a Mermaid diagram via file or stdin");
859
+ try {
860
+ ne(r);
861
+ } catch (a) {
862
+ throw W(a, r);
863
+ }
864
+ console.log("valid");
865
+ }
866
+ var Ne = 1e6;
867
+ function De(e) {
818
868
  return new Promise((n, t) => {
819
869
  const r = [];
820
- let s = 0;
821
- e.on("data", (m) => {
822
- if (s += m.length, s > Re) {
870
+ let a = 0;
871
+ e.on("data", (c) => {
872
+ if (a += c.length, a > Ne) {
823
873
  t(/* @__PURE__ */ new Error("Request body too large")), e.destroy();
824
874
  return;
825
875
  }
826
- r.push(m);
876
+ r.push(c);
827
877
  }), e.on("end", () => n(Buffer.concat(r).toString("utf-8"))), e.on("error", t);
828
878
  });
829
879
  }
830
- function Y(e) {
880
+ function U(e) {
831
881
  return typeof e == "object" && e !== null;
832
882
  }
833
- function Oe(e) {
883
+ function Fe(e) {
834
884
  let n;
835
885
  try {
836
886
  n = JSON.parse(e);
837
887
  } catch {
838
888
  throw new Error("Invalid JSON in request body");
839
889
  }
840
- if (!Y(n)) throw new Error("Request body must be a JSON object");
890
+ if (!U(n)) throw new Error("Request body must be a JSON object");
841
891
  const t = n.source;
842
892
  if (typeof t != "string" || t.trim().length === 0) throw new Error('Provide Mermaid source as a non-empty "source" string');
843
893
  return {
@@ -845,25 +895,25 @@ function Oe(e) {
845
895
  format: n.format === "svg" ? "svg" : "ascii"
846
896
  };
847
897
  }
848
- function F(e, n, t) {
898
+ function N(e, n, t) {
849
899
  const r = JSON.stringify(t);
850
900
  e.writeHead(n, {
851
901
  "Content-Type": "application/json; charset=utf-8",
852
902
  "Content-Length": Buffer.byteLength(r)
853
903
  }), e.end(r);
854
904
  }
855
- async function _e(e, n) {
905
+ async function Ge(e, n) {
856
906
  try {
857
- const { source: t, format: r } = Oe(await Be(e));
858
- F(n, 200, {
859
- output: r === "svg" ? z(t) : O(t),
907
+ const { source: t, format: r } = Fe(await De(e));
908
+ N(n, 200, {
909
+ output: r === "svg" ? M(t) : O(t),
860
910
  format: r
861
911
  });
862
912
  } catch (t) {
863
- F(n, 400, { error: t instanceof Error ? t.message : String(t) });
913
+ N(n, 400, { error: t instanceof Error ? t.message : String(t) });
864
914
  }
865
915
  }
866
- var Ne = `<!doctype html>
916
+ var We = `<!doctype html>
867
917
  <html lang="en">
868
918
  <head>
869
919
  <meta charset="utf-8" />
@@ -929,56 +979,56 @@ var Ne = `<!doctype html>
929
979
  </body>
930
980
  </html>
931
981
  `;
932
- function De() {
933
- return ne((e, n) => {
982
+ function Xe() {
983
+ return re((e, n) => {
934
984
  const t = (e.url ?? "/").split("?")[0];
935
985
  if (e.method === "GET" && (t === "/" || t === "")) {
936
- n.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), n.end(Ne);
986
+ n.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), n.end(We);
937
987
  return;
938
988
  }
939
989
  if (e.method === "POST" && t === "/render") {
940
- _e(e, n);
990
+ Ge(e, n);
941
991
  return;
942
992
  }
943
993
  n.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }), n.end("Not found");
944
994
  });
945
995
  }
946
- function Fe(e) {
996
+ function Ye(e) {
947
997
  return new Promise((n, t) => {
948
- const r = De();
998
+ const r = Xe();
949
999
  r.once("error", t), r.listen(e, "127.0.0.1", () => {
950
1000
  r.removeListener("error", t), n(r);
951
1001
  });
952
1002
  });
953
1003
  }
954
- async function Ge(e, n = console.log) {
955
- const t = await Fe(e.port), r = t.address();
956
- return n(`zombie-mermaid web running at http://localhost:${Y(r) && typeof r.port == "number" ? r.port : e.port}`), t;
1004
+ async function Ue(e, n = console.log) {
1005
+ const t = await Ye(e.port), r = t.address();
1006
+ return n(`zombie-mermaid web running at http://localhost:${U(r) && typeof r.port == "number" ? r.port : e.port}`), t;
957
1007
  }
958
- async function We(e = (n) => console.error(n)) {
959
- const n = oe(), t = new re();
1008
+ async function Ve(e = (n) => console.error(n)) {
1009
+ const n = ie(), t = new oe();
960
1010
  return await n.connect(t), e("zombie-mermaid MCP server running on stdio (experimental — feedback welcome: https://github.com/dfadler/zombie-mermaid/issues)"), n;
961
1011
  }
962
- function Xe() {
963
- const e = U(import.meta.url)("../package.json");
1012
+ function He() {
1013
+ const e = V(import.meta.url)("../package.json");
964
1014
  return typeof e == "object" && e !== null && "version" in e && typeof e.version == "string" ? e.version : "unknown";
965
1015
  }
966
- te(ie);
967
- var Ye = Xe();
968
- async function Ue() {
1016
+ te(ae);
1017
+ var qe = He();
1018
+ async function je() {
969
1019
  const e = process.argv.slice(2);
970
1020
  let n;
971
1021
  try {
972
- n = ce(e);
1022
+ n = le(e);
973
1023
  } catch (t) {
974
1024
  console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
975
1025
  }
976
1026
  switch (n.command) {
977
1027
  case "help":
978
- Ve();
1028
+ Ke();
979
1029
  break;
980
1030
  case "version":
981
- console.log(`zombie-mermaid ${Ye}`);
1031
+ console.log(`zombie-mermaid ${qe}`);
982
1032
  break;
983
1033
  case "themes":
984
1034
  console.log(`Available themes:
@@ -987,27 +1037,34 @@ async function Ue() {
987
1037
  break;
988
1038
  case "render":
989
1039
  try {
990
- await ze(n);
1040
+ await Le(n);
1041
+ } catch (t) {
1042
+ console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
1043
+ }
1044
+ break;
1045
+ case "validate":
1046
+ try {
1047
+ await _e(n);
991
1048
  } catch (t) {
992
1049
  console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
993
1050
  }
994
1051
  break;
995
1052
  case "web":
996
1053
  try {
997
- await Ge(n);
1054
+ await Ue(n);
998
1055
  } catch (t) {
999
1056
  console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
1000
1057
  }
1001
1058
  break;
1002
1059
  case "mcp":
1003
1060
  try {
1004
- await We();
1061
+ await Ve();
1005
1062
  } catch (t) {
1006
1063
  console.error(`Error: ${t instanceof Error ? t.message : String(t)}`), process.exit(1);
1007
1064
  }
1008
1065
  }
1009
1066
  }
1010
- function Ve() {
1067
+ function Ke() {
1011
1068
  console.log(`
1012
1069
  zombie-mermaid — render Mermaid diagrams from the command line
1013
1070
 
@@ -1021,6 +1078,7 @@ Usage:
1021
1078
  zombie-mermaid render <file> --html Self-contained pan/zoom HTML viewer
1022
1079
  zombie-mermaid render <file> --png Rasterize to <file stem>.png
1023
1080
  cat file.mmd | zombie-mermaid render --ascii Read from stdin
1081
+ zombie-mermaid validate [file] Parse only; exit 1 with line diagnostics if invalid
1024
1082
  zombie-mermaid themes List available themes
1025
1083
  zombie-mermaid web [--port <n>] Start a local web UI (default port: 3000)
1026
1084
  zombie-mermaid mcp Start an MCP server on stdio (render tools)
@@ -1081,7 +1139,7 @@ Options:
1081
1139
  -v, --version Show version
1082
1140
  `.trim());
1083
1141
  }
1084
- Ue();
1142
+ je();
1085
1143
  export {
1086
- Ue as main
1144
+ je as main
1087
1145
  };
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@zombie-mermaid/core"),t=require("@zombie-mermaid/mermaid-parser"),n=require("@zombie-mermaid/ascii-renderer"),r=require("@zombie-mermaid/svg-renderer");Object.defineProperty(exports,"DEFAULTS",{enumerable:!0,get:function(){return e.DEFAULTS}}),Object.defineProperty(exports,"ElkNotRegisteredError",{enumerable:!0,get:function(){return r.ElkNotRegisteredError}}),Object.defineProperty(exports,"THEMES",{enumerable:!0,get:function(){return e.THEMES}}),Object.defineProperty(exports,"createLayoutCache",{enumerable:!0,get:function(){return r.createLayoutCache}}),Object.defineProperty(exports,"fromShikiTheme",{enumerable:!0,get:function(){return e.fromShikiTheme}}),Object.defineProperty(exports,"parseMermaid",{enumerable:!0,get:function(){return t.parseMermaid}}),Object.defineProperty(exports,"registerElk",{enumerable:!0,get:function(){return r.registerElk}}),Object.defineProperty(exports,"renderMermaid",{enumerable:!0,get:function(){return r.renderMermaid}}),Object.defineProperty(exports,"renderMermaidASCII",{enumerable:!0,get:function(){return n.renderMermaidASCII}}),Object.defineProperty(exports,"renderMermaidAscii",{enumerable:!0,get:function(){return n.renderMermaidAscii}}),Object.defineProperty(exports,"renderMermaidSVG",{enumerable:!0,get:function(){return r.renderMermaidSVG}}),Object.defineProperty(exports,"renderMermaidSVGAsync",{enumerable:!0,get:function(){return r.renderMermaidSVGAsync}}),Object.defineProperty(exports,"renderMermaidSync",{enumerable:!0,get:function(){return r.renderMermaidSync}}),Object.defineProperty(exports,"themeCssVariables",{enumerable:!0,get:function(){return r.themeCssVariables}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@zombie-mermaid/core"),t=require("@zombie-mermaid/mermaid-parser"),n=require("@zombie-mermaid/ascii-renderer"),r=require("@zombie-mermaid/svg-renderer");Object.defineProperty(exports,"DEFAULTS",{enumerable:!0,get:function(){return e.DEFAULTS}}),Object.defineProperty(exports,"ElkNotRegisteredError",{enumerable:!0,get:function(){return r.ElkNotRegisteredError}}),Object.defineProperty(exports,"THEMES",{enumerable:!0,get:function(){return e.THEMES}}),Object.defineProperty(exports,"createLayoutCache",{enumerable:!0,get:function(){return r.createLayoutCache}}),Object.defineProperty(exports,"fromShikiTheme",{enumerable:!0,get:function(){return e.fromShikiTheme}}),Object.defineProperty(exports,"parseMermaid",{enumerable:!0,get:function(){return t.parseMermaid}}),Object.defineProperty(exports,"registerElk",{enumerable:!0,get:function(){return r.registerElk}}),Object.defineProperty(exports,"renderMermaid",{enumerable:!0,get:function(){return r.renderMermaid}}),Object.defineProperty(exports,"renderMermaidASCII",{enumerable:!0,get:function(){return n.renderMermaidASCII}}),Object.defineProperty(exports,"renderMermaidAscii",{enumerable:!0,get:function(){return n.renderMermaidAscii}}),Object.defineProperty(exports,"renderMermaidSVG",{enumerable:!0,get:function(){return r.renderMermaidSVG}}),Object.defineProperty(exports,"renderMermaidSVGAsync",{enumerable:!0,get:function(){return r.renderMermaidSVGAsync}}),Object.defineProperty(exports,"renderMermaidSync",{enumerable:!0,get:function(){return r.renderMermaidSync}}),Object.defineProperty(exports,"themeCssVariables",{enumerable:!0,get:function(){return r.themeCssVariables}}),Object.defineProperty(exports,"validateMermaid",{enumerable:!0,get:function(){return r.validateMermaid}});
package/dist/index.d.cts CHANGED
@@ -27,6 +27,7 @@ import { SequenceRenderOptions } from '@zombie-mermaid/core';
27
27
  import { themeCssVariables } from '@zombie-mermaid/svg-renderer';
28
28
  import { ThemeName } from '@zombie-mermaid/core';
29
29
  import { THEMES } from '@zombie-mermaid/core';
30
+ import { validateMermaid } from '@zombie-mermaid/svg-renderer';
30
31
  import { XyChartRenderOptions } from '@zombie-mermaid/core';
31
32
 
32
33
  export { AsciiRenderOptions }
@@ -87,6 +88,8 @@ export { ThemeName }
87
88
 
88
89
  export { THEMES }
89
90
 
91
+ export { validateMermaid }
92
+
90
93
  export { XyChartRenderOptions }
91
94
 
92
95
  export { }
package/dist/index.d.ts CHANGED
@@ -27,6 +27,7 @@ import { SequenceRenderOptions } from '@zombie-mermaid/core';
27
27
  import { themeCssVariables } from '@zombie-mermaid/svg-renderer';
28
28
  import { ThemeName } from '@zombie-mermaid/core';
29
29
  import { THEMES } from '@zombie-mermaid/core';
30
+ import { validateMermaid } from '@zombie-mermaid/svg-renderer';
30
31
  import { XyChartRenderOptions } from '@zombie-mermaid/core';
31
32
 
32
33
  export { AsciiRenderOptions }
@@ -87,6 +88,8 @@ export { ThemeName }
87
88
 
88
89
  export { THEMES }
89
90
 
91
+ export { validateMermaid }
92
+
90
93
  export { XyChartRenderOptions }
91
94
 
92
95
  export { }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DEFAULTS as e, THEMES as t, fromShikiTheme as n } from "@zombie-mermaid/core";
2
2
  import { parseMermaid as r } from "@zombie-mermaid/mermaid-parser";
3
3
  import { renderMermaidASCII as i, renderMermaidAscii as a } from "@zombie-mermaid/ascii-renderer";
4
- import { ElkNotRegisteredError as o, createLayoutCache as s, registerElk as c, renderMermaid as l, renderMermaidSVG as u, renderMermaidSVGAsync as d, renderMermaidSync as f, themeCssVariables as p } from "@zombie-mermaid/svg-renderer";
5
- export { e as DEFAULTS, o as ElkNotRegisteredError, t as THEMES, s as createLayoutCache, n as fromShikiTheme, r as parseMermaid, c as registerElk, l as renderMermaid, i as renderMermaidASCII, a as renderMermaidAscii, u as renderMermaidSVG, d as renderMermaidSVGAsync, f as renderMermaidSync, p as themeCssVariables };
4
+ import { ElkNotRegisteredError as o, createLayoutCache as s, registerElk as c, renderMermaid as l, renderMermaidSVG as u, renderMermaidSVGAsync as d, renderMermaidSync as f, themeCssVariables as p, validateMermaid as m } from "@zombie-mermaid/svg-renderer";
5
+ export { e as DEFAULTS, o as ElkNotRegisteredError, t as THEMES, s as createLayoutCache, n as fromShikiTheme, r as parseMermaid, c as registerElk, l as renderMermaid, i as renderMermaidASCII, a as renderMermaidAscii, u as renderMermaidSVG, d as renderMermaidSVGAsync, f as renderMermaidSync, p as themeCssVariables, m as validateMermaid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zombie-mermaid",
3
- "version": "5.0.1",
3
+ "version": "5.1.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",
@@ -80,6 +80,7 @@
80
80
  "files": [
81
81
  "dist/",
82
82
  "LICENSE",
83
+ "THIRD_PARTY_NOTICES.md",
83
84
  "README.md"
84
85
  ],
85
86
  "engines": {
@@ -89,13 +90,14 @@
89
90
  "@modelcontextprotocol/sdk": "^1.30.0",
90
91
  "elkjs": "^0.11.0",
91
92
  "entities": "^7.0.1",
92
- "@zombie-mermaid/ascii-renderer": "5.0.1",
93
- "@zombie-mermaid/mcp": "5.0.1",
94
- "@zombie-mermaid/core": "5.0.1",
95
- "@zombie-mermaid/svg-renderer": "5.0.1",
96
- "@zombie-mermaid/mermaid-parser": "5.0.1"
93
+ "@zombie-mermaid/ascii-renderer": "5.1.0",
94
+ "@zombie-mermaid/core": "5.1.0",
95
+ "@zombie-mermaid/mcp": "5.1.0",
96
+ "@zombie-mermaid/mermaid-parser": "5.1.0",
97
+ "@zombie-mermaid/svg-renderer": "5.1.0"
97
98
  },
98
99
  "devDependencies": {
100
+ "@axe-core/playwright": "^4.13.0",
99
101
  "@changesets/changelog-github": "^1.0.0",
100
102
  "@changesets/cli": "^3.0.1",
101
103
  "@microsoft/api-extractor": "^7.59.0",
@@ -116,7 +118,7 @@
116
118
  "jsdom": "^30.0.1",
117
119
  "lightningcss": "^1.33.0",
118
120
  "marked": "^18.0.11",
119
- "mermaid": "11.17.2",
121
+ "mermaid": "12.1.0",
120
122
  "prettier": "^3.9.6",
121
123
  "react": "^19.2.8",
122
124
  "react-dom": "^19.2.8",
@@ -138,7 +140,9 @@
138
140
  "test:watch": "vitest -c config/vitest.config.ts",
139
141
  "test:coverage": "vitest run -c config/vitest.config.ts --coverage",
140
142
  "test:visual": "playwright test",
143
+ "test:a11y": "playwright test -c playwright.a11y.config.ts",
141
144
  "test:visual:watch": "playwright test --ui",
145
+ "test:ascii-text:update": "vitest run -c config/vitest.config.ts __tests__/ascii-text-snapshots.test.ts -u",
142
146
  "test:visual:update": "playwright test --update-snapshots",
143
147
  "coverage:diff": "tsx scripts/check-diff-coverage.ts",
144
148
  "samples": "scripts/tsx-css.sh packages/site/index.ts",
@@ -156,8 +160,11 @@
156
160
  "bench:ascii-renderer": "tsx scripts/bench-ascii-renderer.ts",
157
161
  "bench:package-compare": "tsx scripts/bench-package-compare.ts",
158
162
  "bench:history:append": "tsx scripts/bench-history-append.ts",
163
+ "metrics:history:append": "tsx scripts/metrics-history-append.ts",
159
164
  "bench:trend": "tsx scripts/bench-trend.ts",
160
165
  "bench:mcp": "tsx scripts/bench-mcp.ts",
166
+ "check:docs": "tsx scripts/check-docs.ts",
167
+ "smoke:packed": "node scripts/smoke-test-packed.mjs",
161
168
  "check:bundle-size": "tsx scripts/check-bundle-size.ts",
162
169
  "generate:hero": "tsx scripts/generate-hero.ts",
163
170
  "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",
@@ -170,6 +177,7 @@
170
177
  "form-facts": "tsx scripts/form-facts.ts",
171
178
  "form-judge-prepare": "tsx scripts/form-judge-prepare.ts",
172
179
  "form-judge-finalize-cache": "tsx scripts/form-judge-finalize-cache.ts",
180
+ "form-judge-calibrate": "tsx scripts/form-judge-calibrate.ts",
173
181
  "build": "pnpm run build:packages && vite build --app --config config/vite.config.lib.ts",
174
182
  "build:packages": "vite build --app --config packages/core/vite.config.ts && vite build --app --config packages/mermaid-parser/vite.config.ts && vite build --app --config packages/svg-renderer/vite.config.ts && vite build --app --config packages/ascii-renderer/vite.config.ts && vite build --app --config packages/mcp/vite.config.ts",
175
183
  "build:site": "rm -rf site && mkdir -p site && SITE_OUT_DIR=site scripts/tsx-css.sh packages/site/index.ts && SITE_OUT_DIR=site scripts/tsx-css.sh packages/site/editor.ts && SITE_OUT_DIR=site scripts/tsx-css.sh packages/site/fork-fixes.ts && SITE_OUT_DIR=site scripts/tsx-css.sh packages/site/pages.ts && SITE_OUT_DIR=site scripts/tsx-css.sh packages/site/blog.ts && SITE_OUT_DIR=site scripts/tsx-css.sh packages/site/dashboard.ts && cp -r public/* site/",
@@ -183,6 +191,9 @@
183
191
  "upstream:pr-candidates": "tsx scripts/list-upstream-pr-rescue-candidates.ts",
184
192
  "audit:responsive": "tsx scripts/audit-responsive.ts",
185
193
  "layout:oracle": "tsx scripts/layout-oracle.ts",
186
- "label:oracle": "tsx scripts/label-position-oracle.ts"
194
+ "layout:scorecard": "tsx scripts/layout-oracle.ts --history=layout-fidelity-history.jsonl --scorecard=docs/layout-fidelity-scorecard.md && prettier --write docs/layout-fidelity-scorecard.md",
195
+ "label:oracle": "tsx scripts/label-position-oracle.ts",
196
+ "verify": "scripts/verify.sh",
197
+ "doctor": "scripts/doctor.sh"
187
198
  }
188
199
  }