zombie-mermaid 5.4.0 → 5.5.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/dist/cli.js +575 -1122
- package/package.json +6 -7
package/dist/cli.js
CHANGED
|
@@ -1,683 +1,276 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire as
|
|
3
|
-
import { extname as
|
|
4
|
-
import { MermaidParseError as
|
|
5
|
-
import { access as
|
|
6
|
-
import { diagramColorsToAsciiTheme as
|
|
2
|
+
import { createRequire as e } from "node:module";
|
|
3
|
+
import { extname as t, format as n, parse as r } from "node:path";
|
|
4
|
+
import { MermaidParseError as i, THEMES as a, escapeXml as o, isDirection as s, isWideChar as c } from "@zombie-mermaid/core";
|
|
5
|
+
import { access as l, readFile as u, writeFile as d } from "node:fs/promises";
|
|
6
|
+
import { diagramColorsToAsciiTheme as f, renderMermaidASCII as p } from "@zombie-mermaid/ascii-renderer";
|
|
7
7
|
import "@zombie-mermaid/mermaid-parser";
|
|
8
|
-
import { registerElk as
|
|
9
|
-
import { createServer as
|
|
10
|
-
import { StdioServerTransport as
|
|
11
|
-
import { createMcpServer as
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
var
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
import { registerElk as m, renderMermaidSVG as h, validateMermaid as g } from "@zombie-mermaid/svg-renderer";
|
|
9
|
+
import { createServer as _ } from "node:http";
|
|
10
|
+
import { StdioServerTransport as v } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { createMcpServer as y } from "@zombie-mermaid/mcp";
|
|
12
|
+
import b from "elkjs/lib/elk.bundled.js";
|
|
13
|
+
//#region src/cli/parse-args.ts
|
|
14
|
+
var x = 3e3, S = {
|
|
15
|
+
".svg": "svg",
|
|
16
|
+
".txt": "ascii",
|
|
17
|
+
".html": "html",
|
|
18
|
+
".htm": "html",
|
|
19
|
+
".png": "png"
|
|
20
20
|
};
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
function
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
message: a?.[2] ?? r
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (!(e instanceof Error)) return e;
|
|
268
|
-
const t = Ie(e, n);
|
|
269
|
-
return t === void 0 ? e : /* @__PURE__ */ new Error(`${e.message}
|
|
270
|
-
${t.line} | ${t.sourceLine}`);
|
|
271
|
-
}
|
|
272
|
-
var Ae = `// @ts-check
|
|
273
|
-
/// <reference lib="dom" />
|
|
274
|
-
// ============================================================================
|
|
275
|
-
// Client script for the CLI's self-contained HTML viewer (\`render --html\`).
|
|
276
|
-
//
|
|
277
|
-
// Inlined verbatim into the viewer page by src/cli/html-viewer.ts (imported
|
|
278
|
-
// with Vite's \`?raw\`), so this file must stay plain browser JavaScript: no
|
|
279
|
-
// imports, no TypeScript syntax, no build step. It is checked by tsc via
|
|
280
|
-
// \`// @ts-check\` + JSDoc and linted like any other source file.
|
|
281
|
-
//
|
|
282
|
-
// This script ships only inside the HTML *viewer file* the CLI writes. It is
|
|
283
|
-
// not — and must never become — part of the library's SVG output, which
|
|
284
|
-
// stays script-free by decision (docs/decisions/no-script-interactivity.md).
|
|
285
|
-
// ============================================================================
|
|
286
|
-
|
|
287
|
-
;(() => {
|
|
288
|
-
const stage = document.getElementById('stage')
|
|
289
|
-
const diagram = document.getElementById('diagram')
|
|
290
|
-
const svg = diagram ? diagram.querySelector('svg') : null
|
|
291
|
-
const zoomLabel = document.getElementById('zoom')
|
|
292
|
-
const themeButton = document.getElementById('theme')
|
|
293
|
-
if (!stage || !diagram || !svg || !zoomLabel || !themeButton) return
|
|
294
|
-
|
|
295
|
-
const MIN_SCALE = 0.05
|
|
296
|
-
const MAX_SCALE = 40
|
|
297
|
-
const KEY_PAN_STEP = 40
|
|
298
|
-
const KEY_PAN_STEP_FAST = 160
|
|
299
|
-
|
|
300
|
-
let scale = 1
|
|
301
|
-
let tx = 0
|
|
302
|
-
let ty = 0
|
|
303
|
-
|
|
304
|
-
// -- Geometry ---------------------------------------------------------
|
|
305
|
-
|
|
306
|
-
// Assigned as \`const ... = () => {}\` rather than \`function\` declarations
|
|
307
|
-
// from here through stageCenter (and release/setMode further below):
|
|
308
|
-
// these all read stage/diagram/svg/zoomLabel/themeButton, which the
|
|
309
|
-
// guard above narrowed from \`T | null\` to \`T\` — a narrowing that only
|
|
310
|
-
// survives for a function *expression* defined after the guard, not a
|
|
311
|
-
// hoisted \`function\` declaration (tsc can't rule out the declaration
|
|
312
|
-
// being invoked, in theory, before the guard runs).
|
|
313
|
-
|
|
314
|
-
/** Natural (unscaled) size of the diagram, from the root's width/height or viewBox. */
|
|
315
|
-
const naturalSize = () => {
|
|
316
|
-
const w = Number(svg.getAttribute('width'))
|
|
317
|
-
const h = Number(svg.getAttribute('height'))
|
|
318
|
-
if (w > 0 && h > 0) return { w, h }
|
|
319
|
-
const vb = svg.viewBox.baseVal
|
|
320
|
-
if (vb && vb.width > 0 && vb.height > 0)
|
|
321
|
-
return { w: vb.width, h: vb.height }
|
|
322
|
-
const r = diagram.getBoundingClientRect()
|
|
323
|
-
return { w: r.width / scale || 1, h: r.height / scale || 1 }
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/** @param {number} s */
|
|
327
|
-
function clampScale(s) {
|
|
328
|
-
return Math.min(MAX_SCALE, Math.max(MIN_SCALE, s))
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const apply = () => {
|
|
332
|
-
diagram.style.transform = \`translate(\${tx}px, \${ty}px) scale(\${scale})\`
|
|
333
|
-
zoomLabel.textContent = \`\${Math.round(scale * 100)}%\`
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Zoom by \`factor\` keeping the stage point (cx, cy) fixed under the cursor.
|
|
338
|
-
* @param {number} factor
|
|
339
|
-
* @param {number} cx
|
|
340
|
-
* @param {number} cy
|
|
341
|
-
*/
|
|
342
|
-
function zoomAt(factor, cx, cy) {
|
|
343
|
-
const next = clampScale(scale * factor)
|
|
344
|
-
const k = next / scale
|
|
345
|
-
tx = cx - (cx - tx) * k
|
|
346
|
-
ty = cy - (cy - ty) * k
|
|
347
|
-
scale = next
|
|
348
|
-
apply()
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const fit = () => {
|
|
352
|
-
const r = stage.getBoundingClientRect()
|
|
353
|
-
const { w, h } = naturalSize()
|
|
354
|
-
scale = clampScale(Math.min(r.width / w, r.height / h) * 0.95)
|
|
355
|
-
tx = (r.width - w * scale) / 2
|
|
356
|
-
ty = (r.height - h * scale) / 2
|
|
357
|
-
apply()
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const oneToOne = () => {
|
|
361
|
-
const r = stage.getBoundingClientRect()
|
|
362
|
-
const { w, h } = naturalSize()
|
|
363
|
-
scale = 1
|
|
364
|
-
tx = Math.max(0, (r.width - w) / 2)
|
|
365
|
-
ty = Math.max(0, (r.height - h) / 2)
|
|
366
|
-
apply()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const stageCenter = () => {
|
|
370
|
-
const r = stage.getBoundingClientRect()
|
|
371
|
-
return { x: r.width / 2, y: r.height / 2 }
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// -- Wheel: plain scroll pans; ctrl/cmd + wheel (which is also what a
|
|
375
|
-
// trackpad pinch produces) zooms around the cursor. -------------------
|
|
376
|
-
|
|
377
|
-
stage.addEventListener(
|
|
378
|
-
'wheel',
|
|
379
|
-
(e) => {
|
|
380
|
-
e.preventDefault()
|
|
381
|
-
const r = stage.getBoundingClientRect()
|
|
382
|
-
if (e.ctrlKey || e.metaKey) {
|
|
383
|
-
zoomAt(
|
|
384
|
-
Math.exp(-e.deltaY * 0.01),
|
|
385
|
-
e.clientX - r.left,
|
|
386
|
-
e.clientY - r.top,
|
|
387
|
-
)
|
|
388
|
-
} else {
|
|
389
|
-
tx -= e.deltaX
|
|
390
|
-
ty -= e.deltaY
|
|
391
|
-
apply()
|
|
392
|
-
}
|
|
393
|
-
},
|
|
394
|
-
{ passive: false },
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
// -- Pointers: one pointer drags, two pinch-zoom around their midpoint. --
|
|
398
|
-
|
|
399
|
-
/** @type {Map<number, {x: number, y: number}>} */
|
|
400
|
-
const pointers = new Map()
|
|
401
|
-
/** @type {{dist: number, mid: {x: number, y: number}} | null} */
|
|
402
|
-
let lastPinch = null
|
|
403
|
-
|
|
404
|
-
stage.addEventListener('pointerdown', (e) => {
|
|
405
|
-
if (e.pointerType === 'mouse' && e.button !== 0) return
|
|
406
|
-
stage.setPointerCapture(e.pointerId)
|
|
407
|
-
pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })
|
|
408
|
-
stage.classList.add('dragging')
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
stage.addEventListener('pointermove', (e) => {
|
|
412
|
-
const prev = pointers.get(e.pointerId)
|
|
413
|
-
if (!prev) return
|
|
414
|
-
const current = { x: e.clientX, y: e.clientY }
|
|
415
|
-
pointers.set(e.pointerId, current)
|
|
416
|
-
if (pointers.size === 1) {
|
|
417
|
-
tx += current.x - prev.x
|
|
418
|
-
ty += current.y - prev.y
|
|
419
|
-
apply()
|
|
420
|
-
return
|
|
421
|
-
}
|
|
422
|
-
if (pointers.size === 2) {
|
|
423
|
-
const [a, b] = [...pointers.values()]
|
|
424
|
-
if (!a || !b) return
|
|
425
|
-
const dist = Math.hypot(a.x - b.x, a.y - b.y)
|
|
426
|
-
const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }
|
|
427
|
-
if (lastPinch && lastPinch.dist > 0) {
|
|
428
|
-
const r = stage.getBoundingClientRect()
|
|
429
|
-
zoomAt(dist / lastPinch.dist, mid.x - r.left, mid.y - r.top)
|
|
430
|
-
tx += mid.x - lastPinch.mid.x
|
|
431
|
-
ty += mid.y - lastPinch.mid.y
|
|
432
|
-
apply()
|
|
433
|
-
}
|
|
434
|
-
lastPinch = { dist, mid }
|
|
435
|
-
}
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
/** @param {PointerEvent} e */
|
|
439
|
-
const release = (e) => {
|
|
440
|
-
pointers.delete(e.pointerId)
|
|
441
|
-
if (pointers.size < 2) lastPinch = null
|
|
442
|
-
if (pointers.size === 0) stage.classList.remove('dragging')
|
|
443
|
-
}
|
|
444
|
-
stage.addEventListener('pointerup', release)
|
|
445
|
-
stage.addEventListener('pointercancel', release)
|
|
446
|
-
|
|
447
|
-
stage.addEventListener('dblclick', (e) => {
|
|
448
|
-
const r = stage.getBoundingClientRect()
|
|
449
|
-
zoomAt(scale < 1 ? 1 / scale : 2, e.clientX - r.left, e.clientY - r.top)
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
// -- Theme: follows prefers-color-scheme until the user toggles, unless
|
|
453
|
-
// the page was generated with an explicit --theme (data-theme preset). --
|
|
454
|
-
|
|
455
|
-
const root = document.documentElement
|
|
456
|
-
const darkMedia = matchMedia('(prefers-color-scheme: dark)')
|
|
457
|
-
let userChose = root.dataset.themeSource === 'theme'
|
|
458
|
-
|
|
459
|
-
function currentMode() {
|
|
460
|
-
const preset = root.dataset.theme
|
|
461
|
-
if (preset === 'dark' || preset === 'light') return preset
|
|
462
|
-
return darkMedia.matches ? 'dark' : 'light'
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/** @param {'light' | 'dark'} mode */
|
|
466
|
-
const setMode = (mode) => {
|
|
467
|
-
root.dataset.theme = mode
|
|
468
|
-
themeButton.textContent = mode === 'dark' ? 'Light' : 'Dark'
|
|
469
|
-
themeButton.setAttribute('aria-pressed', String(mode === 'dark'))
|
|
470
|
-
themeButton.setAttribute(
|
|
471
|
-
'aria-label',
|
|
472
|
-
\`Switch to \${mode === 'dark' ? 'light' : 'dark'} mode (T)\`,
|
|
473
|
-
)
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function toggleMode() {
|
|
477
|
-
userChose = true
|
|
478
|
-
setMode(currentMode() === 'dark' ? 'light' : 'dark')
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
darkMedia.addEventListener('change', () => {
|
|
482
|
-
if (!userChose) {
|
|
483
|
-
delete root.dataset.theme
|
|
484
|
-
setMode(currentMode())
|
|
485
|
-
delete root.dataset.theme
|
|
486
|
-
}
|
|
487
|
-
})
|
|
488
|
-
// Reflect the initial mode on the button without pinning it: leave
|
|
489
|
-
// data-theme unset for system mode so a later OS change still applies.
|
|
490
|
-
themeButton.textContent = currentMode() === 'dark' ? 'Light' : 'Dark'
|
|
491
|
-
themeButton.setAttribute('aria-pressed', String(currentMode() === 'dark'))
|
|
492
|
-
|
|
493
|
-
// -- Buttons -------------------------------------------------------------
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* @param {string} id
|
|
497
|
-
* @param {() => void} handler
|
|
498
|
-
*/
|
|
499
|
-
function onClick(id, handler) {
|
|
500
|
-
const el = document.getElementById(id)
|
|
501
|
-
if (el) el.addEventListener('click', handler)
|
|
502
|
-
}
|
|
503
|
-
onClick('zoom-in', () => {
|
|
504
|
-
const c = stageCenter()
|
|
505
|
-
zoomAt(1.25, c.x, c.y)
|
|
506
|
-
})
|
|
507
|
-
onClick('zoom-out', () => {
|
|
508
|
-
const c = stageCenter()
|
|
509
|
-
zoomAt(0.8, c.x, c.y)
|
|
510
|
-
})
|
|
511
|
-
onClick('fit', fit)
|
|
512
|
-
onClick('one', oneToOne)
|
|
513
|
-
onClick('theme', toggleMode)
|
|
514
|
-
|
|
515
|
-
// -- Keyboard (stage must be focused; it has tabindex=0) -----------------
|
|
516
|
-
|
|
517
|
-
stage.addEventListener('keydown', (e) => {
|
|
518
|
-
const c = stageCenter()
|
|
519
|
-
const step = e.shiftKey ? KEY_PAN_STEP_FAST : KEY_PAN_STEP
|
|
520
|
-
switch (e.key) {
|
|
521
|
-
case '+':
|
|
522
|
-
case '=':
|
|
523
|
-
zoomAt(1.25, c.x, c.y)
|
|
524
|
-
break
|
|
525
|
-
case '-':
|
|
526
|
-
case '_':
|
|
527
|
-
zoomAt(0.8, c.x, c.y)
|
|
528
|
-
break
|
|
529
|
-
case '0':
|
|
530
|
-
fit()
|
|
531
|
-
break
|
|
532
|
-
case '1':
|
|
533
|
-
oneToOne()
|
|
534
|
-
break
|
|
535
|
-
case 'ArrowLeft':
|
|
536
|
-
tx += step
|
|
537
|
-
apply()
|
|
538
|
-
break
|
|
539
|
-
case 'ArrowRight':
|
|
540
|
-
tx -= step
|
|
541
|
-
apply()
|
|
542
|
-
break
|
|
543
|
-
case 'ArrowUp':
|
|
544
|
-
ty += step
|
|
545
|
-
apply()
|
|
546
|
-
break
|
|
547
|
-
case 'ArrowDown':
|
|
548
|
-
ty -= step
|
|
549
|
-
apply()
|
|
550
|
-
break
|
|
551
|
-
case 't':
|
|
552
|
-
case 'T':
|
|
553
|
-
toggleMode()
|
|
554
|
-
break
|
|
555
|
-
default:
|
|
556
|
-
return
|
|
557
|
-
}
|
|
558
|
-
e.preventDefault()
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
fit()
|
|
562
|
-
stage.focus({ preventScroll: true })
|
|
563
|
-
})()
|
|
564
|
-
`, Me = `
|
|
565
|
-
:root {
|
|
566
|
-
color-scheme: light dark;
|
|
567
|
-
--bg: #f4f4f5;
|
|
568
|
-
--fg: #18181b;
|
|
569
|
-
--chrome-bg: #ffffff;
|
|
570
|
-
--chrome-border: #e4e4e7;
|
|
571
|
-
--chrome-fg: #3f3f46;
|
|
572
|
-
--chrome-fg-hover: #18181b;
|
|
573
|
-
--focus-ring: #2563eb;
|
|
574
|
-
}
|
|
575
|
-
:root[data-theme='dark'] {
|
|
576
|
-
--bg: #09090b;
|
|
577
|
-
--fg: #fafafa;
|
|
578
|
-
--chrome-bg: #18181b;
|
|
579
|
-
--chrome-border: #3f3f46;
|
|
580
|
-
--chrome-fg: #d4d4d8;
|
|
581
|
-
--chrome-fg-hover: #fafafa;
|
|
582
|
-
--focus-ring: #60a5fa;
|
|
583
|
-
}
|
|
584
|
-
@media (prefers-color-scheme: dark) {
|
|
585
|
-
:root:not([data-theme='light']) {
|
|
586
|
-
--bg: #09090b;
|
|
587
|
-
--fg: #fafafa;
|
|
588
|
-
--chrome-bg: #18181b;
|
|
589
|
-
--chrome-border: #3f3f46;
|
|
590
|
-
--chrome-fg: #d4d4d8;
|
|
591
|
-
--chrome-fg-hover: #fafafa;
|
|
592
|
-
--focus-ring: #60a5fa;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
* { box-sizing: border-box; }
|
|
596
|
-
html, body {
|
|
597
|
-
margin: 0;
|
|
598
|
-
height: 100%;
|
|
599
|
-
background: var(--bg);
|
|
600
|
-
color: var(--fg);
|
|
601
|
-
font: 14px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
602
|
-
}
|
|
603
|
-
#stage {
|
|
604
|
-
position: fixed;
|
|
605
|
-
inset: 0;
|
|
606
|
-
overflow: hidden;
|
|
607
|
-
cursor: grab;
|
|
608
|
-
touch-action: none;
|
|
609
|
-
outline: none;
|
|
610
|
-
}
|
|
611
|
-
#stage.dragging { cursor: grabbing; }
|
|
612
|
-
#diagram {
|
|
613
|
-
position: absolute;
|
|
614
|
-
top: 0;
|
|
615
|
-
left: 0;
|
|
616
|
-
transform-origin: 0 0;
|
|
617
|
-
will-change: transform;
|
|
618
|
-
}
|
|
619
|
-
#diagram svg { display: block; }
|
|
620
|
-
#toolbar {
|
|
621
|
-
position: fixed;
|
|
622
|
-
bottom: 16px;
|
|
623
|
-
left: 50%;
|
|
624
|
-
transform: translateX(-50%);
|
|
625
|
-
display: flex;
|
|
626
|
-
align-items: center;
|
|
627
|
-
gap: 4px;
|
|
628
|
-
padding: 6px;
|
|
629
|
-
border-radius: 10px;
|
|
630
|
-
background: var(--chrome-bg);
|
|
631
|
-
border: 1px solid var(--chrome-border);
|
|
632
|
-
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
633
|
-
}
|
|
634
|
-
#toolbar button {
|
|
635
|
-
appearance: none;
|
|
636
|
-
border: none;
|
|
637
|
-
background: transparent;
|
|
638
|
-
color: var(--chrome-fg);
|
|
639
|
-
font: inherit;
|
|
640
|
-
font-size: 13px;
|
|
641
|
-
padding: 6px 10px;
|
|
642
|
-
border-radius: 6px;
|
|
643
|
-
cursor: pointer;
|
|
644
|
-
min-width: 32px;
|
|
645
|
-
}
|
|
646
|
-
#toolbar button:hover { color: var(--chrome-fg-hover); background: var(--chrome-border); }
|
|
647
|
-
#toolbar button:focus-visible {
|
|
648
|
-
outline: 2px solid var(--focus-ring);
|
|
649
|
-
outline-offset: 1px;
|
|
650
|
-
}
|
|
651
|
-
#zoom {
|
|
652
|
-
min-width: 3.5em;
|
|
653
|
-
text-align: center;
|
|
654
|
-
color: var(--chrome-fg);
|
|
655
|
-
font-variant-numeric: tabular-nums;
|
|
656
|
-
}
|
|
657
|
-
#stage:focus-visible {
|
|
658
|
-
box-shadow: inset 0 0 0 2px var(--focus-ring);
|
|
659
|
-
}
|
|
660
|
-
@media print {
|
|
661
|
-
#toolbar { display: none; }
|
|
662
|
-
#stage { position: static; overflow: visible; }
|
|
663
|
-
/* Genuinely needs !important (zombie-mermaid#1080 audit): the client
|
|
664
|
-
* script's apply() sets #diagram's transform inline on every pan/zoom,
|
|
665
|
-
* so whatever pan/zoom state the viewer was left in is a live inline
|
|
666
|
-
* style this print rule must beat -- there's no static default to
|
|
667
|
-
* hoist it to instead. */
|
|
668
|
-
#diagram { position: static; transform: none !important; }
|
|
669
|
-
}
|
|
670
|
-
`.trim();
|
|
671
|
-
function ze(e) {
|
|
672
|
-
const n = e.title?.trim() || "Mermaid diagram";
|
|
673
|
-
return `<!doctype html>
|
|
21
|
+
function C(e) {
|
|
22
|
+
if (e !== void 0 && e !== "-") return S[t(e).toLowerCase()];
|
|
23
|
+
}
|
|
24
|
+
var w = {
|
|
25
|
+
svg: {
|
|
26
|
+
flag: "--svg",
|
|
27
|
+
ext: ".svg",
|
|
28
|
+
article: "a",
|
|
29
|
+
display: "SVG",
|
|
30
|
+
label: "raw SVG output"
|
|
31
|
+
},
|
|
32
|
+
html: {
|
|
33
|
+
flag: "--html",
|
|
34
|
+
ext: ".html",
|
|
35
|
+
article: "an",
|
|
36
|
+
display: "HTML",
|
|
37
|
+
label: "the HTML viewer"
|
|
38
|
+
},
|
|
39
|
+
png: {
|
|
40
|
+
flag: "--png",
|
|
41
|
+
ext: ".png",
|
|
42
|
+
article: "a",
|
|
43
|
+
display: "PNG",
|
|
44
|
+
label: "the PNG rasterization"
|
|
45
|
+
}
|
|
46
|
+
}, T = Object.keys(w);
|
|
47
|
+
function E(e, t) {
|
|
48
|
+
let i = r(e);
|
|
49
|
+
return n({
|
|
50
|
+
dir: i.dir,
|
|
51
|
+
name: i.name,
|
|
52
|
+
ext: t
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function D(e) {
|
|
56
|
+
let [t, ...n] = e;
|
|
57
|
+
if (t === void 0 || t === "--help" || t === "-h") return { command: "help" };
|
|
58
|
+
if (t === "--version" || t === "-v") return { command: "version" };
|
|
59
|
+
if (t === "themes") return { command: "themes" };
|
|
60
|
+
if (t === "render") return j(n);
|
|
61
|
+
if (t === "validate") {
|
|
62
|
+
let e = n.find((e, t) => t > 0 || e.startsWith("-"));
|
|
63
|
+
if (e !== void 0) throw Error(`Unexpected argument: ${e}`);
|
|
64
|
+
return {
|
|
65
|
+
command: "validate",
|
|
66
|
+
input: n[0]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (t === "web") return M(n);
|
|
70
|
+
if (t === "mcp") return { command: "mcp" };
|
|
71
|
+
throw Error(`Unknown command: ${t}`);
|
|
72
|
+
}
|
|
73
|
+
function O(e, t, n) {
|
|
74
|
+
let r = e[t + 1];
|
|
75
|
+
if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a numeric value`);
|
|
76
|
+
if (!/^\d+$/.test(r)) throw Error(`${n} requires a non-negative integer, got: "${r}"`);
|
|
77
|
+
return Number(r);
|
|
78
|
+
}
|
|
79
|
+
function k(e, t, n) {
|
|
80
|
+
let r = e[t + 1];
|
|
81
|
+
if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a value ("auto" or a positive integer)`);
|
|
82
|
+
if (r === "auto") return "auto";
|
|
83
|
+
let i = Number(r);
|
|
84
|
+
if (!/^\d+$/.test(r) || i === 0 || !Number.isSafeInteger(i)) throw Error(`${n} requires "auto" or a positive integer, got: "${r}"`);
|
|
85
|
+
return i;
|
|
86
|
+
}
|
|
87
|
+
function A(e, t, n) {
|
|
88
|
+
let r = e[t + 1];
|
|
89
|
+
if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a value (one of TD, TB, BT, LR, RL)`);
|
|
90
|
+
let i = r.toUpperCase();
|
|
91
|
+
if (!s(i)) throw Error(`${n} requires one of TD, TB, BT, LR, RL, got: "${r}"`);
|
|
92
|
+
return i;
|
|
93
|
+
}
|
|
94
|
+
function j(e) {
|
|
95
|
+
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 = 0;
|
|
96
|
+
for (; v < e.length;) {
|
|
97
|
+
let y = e[v];
|
|
98
|
+
if (y === void 0) throw Error(`parseArgs: index ${v} out of range while parsing arguments`);
|
|
99
|
+
if (y === "--ascii") n = !0, v++;
|
|
100
|
+
else if (y === "--svg") r = !0, v++;
|
|
101
|
+
else if (y === "--resolve-colors") i = !0, v++;
|
|
102
|
+
else if (y === "--html") a = !0, v++;
|
|
103
|
+
else if (y === "--png") o = !0, v++;
|
|
104
|
+
else if (y === "-o" || y === "--output") {
|
|
105
|
+
if (v + 1 >= e.length) throw Error("-o requires a file path (or - for stdout)");
|
|
106
|
+
s = e[v + 1], v += 2;
|
|
107
|
+
} else if (y === "-f" || y === "--force") c = !0, v++;
|
|
108
|
+
else if (y === "--theme") {
|
|
109
|
+
if (v + 1 >= e.length) throw Error("--theme requires a theme name");
|
|
110
|
+
l = e[v + 1], v += 2;
|
|
111
|
+
} else if (y === "-x" || y === "--paddingX") u = O(e, v, y), v += 2;
|
|
112
|
+
else if (y === "-y" || y === "--paddingY") d = O(e, v, y), v += 2;
|
|
113
|
+
else if (y === "-p" || y === "--borderPadding") f = O(e, v, y), v += 2;
|
|
114
|
+
else if (y === "--coords") p = !0, v++;
|
|
115
|
+
else if (y === "--hyperlinks") m = !0, v++;
|
|
116
|
+
else if (y === "-w" || y === "--max-width") h = k(e, v, y), v += 2;
|
|
117
|
+
else if (y === "--profile") {
|
|
118
|
+
let t = e[v + 1];
|
|
119
|
+
if (t !== "markdown") throw Error(`--profile requires one of: markdown, got: "${t ?? ""}"`);
|
|
120
|
+
g = t, v += 2;
|
|
121
|
+
} else if (y === "--direction") _ = A(e, v, y), v += 2;
|
|
122
|
+
else if (!y.startsWith("-") || y === "-") {
|
|
123
|
+
if (y === "-") throw Error("Unexpected argument: - (use -o - to write output to stdout; omit the input file to read stdin)");
|
|
124
|
+
if (t !== void 0) throw Error(`Unexpected argument: ${y} (input file already set to "${t}")`);
|
|
125
|
+
t = y, v++;
|
|
126
|
+
} else throw Error(`Unknown flag: ${y}`);
|
|
127
|
+
}
|
|
128
|
+
let y = {
|
|
129
|
+
svg: r,
|
|
130
|
+
html: a,
|
|
131
|
+
png: o
|
|
132
|
+
}, b = T.filter((e) => y[e]);
|
|
133
|
+
if (b.length > 1) {
|
|
134
|
+
let [e, t] = b;
|
|
135
|
+
throw Error(`${w[e].flag} and ${w[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.`);
|
|
136
|
+
}
|
|
137
|
+
let x = C(s);
|
|
138
|
+
if (x !== void 0 && x !== "ascii") {
|
|
139
|
+
let e = T.find((e) => e !== x && y[e]);
|
|
140
|
+
if (e !== void 0) {
|
|
141
|
+
let t = w[x], n = w[e];
|
|
142
|
+
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}.`);
|
|
143
|
+
}
|
|
144
|
+
x === "svg" ? r = !0 : x === "html" ? a = !0 : o = !0;
|
|
145
|
+
} else if (x === "ascii") {
|
|
146
|
+
let e = T.find((e) => y[e]);
|
|
147
|
+
if (e !== void 0) {
|
|
148
|
+
let t = w[e];
|
|
149
|
+
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.`);
|
|
150
|
+
}
|
|
151
|
+
n = !0;
|
|
152
|
+
}
|
|
153
|
+
if (!n && !r && !a && !o) {
|
|
154
|
+
if (s !== void 0 && s !== "-") {
|
|
155
|
+
let e = Object.keys(S).join(", ");
|
|
156
|
+
throw Error(`Cannot infer an output format from "${s}" — pass --ascii, --svg, --html, or --png, or use a recognised extension (${e})`);
|
|
157
|
+
}
|
|
158
|
+
throw Error("Specify --ascii, --svg, --html, and/or --png (or -o <path> with a .svg/.txt/.html/.png extension)");
|
|
159
|
+
}
|
|
160
|
+
let D = {
|
|
161
|
+
svg: r,
|
|
162
|
+
html: a,
|
|
163
|
+
png: o
|
|
164
|
+
}, j = T.find((e) => D[e]);
|
|
165
|
+
if (s === "-" && n && j !== void 0) {
|
|
166
|
+
let e = w[j].display;
|
|
167
|
+
throw Error(`-o - would send both ASCII and ${e} to stdout; drop --ascii, or write the ${e} to a file path`);
|
|
168
|
+
}
|
|
169
|
+
if (j !== void 0 && s === void 0) {
|
|
170
|
+
let { flag: e, ext: n } = w[j];
|
|
171
|
+
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`);
|
|
172
|
+
s = E(t, n);
|
|
173
|
+
}
|
|
174
|
+
if (h !== void 0 && !n) throw Error("-w/--max-width requires --ascii");
|
|
175
|
+
if (g !== void 0 && !n) throw Error("--profile requires --ascii");
|
|
176
|
+
if (m && !n) throw Error("--hyperlinks requires --ascii");
|
|
177
|
+
if (i && !r) throw Error("--resolve-colors requires --svg");
|
|
178
|
+
return {
|
|
179
|
+
command: "render",
|
|
180
|
+
input: t,
|
|
181
|
+
ascii: n,
|
|
182
|
+
svg: r,
|
|
183
|
+
resolveColors: i,
|
|
184
|
+
html: a,
|
|
185
|
+
png: o,
|
|
186
|
+
output: s,
|
|
187
|
+
force: c,
|
|
188
|
+
theme: l,
|
|
189
|
+
paddingX: u,
|
|
190
|
+
paddingY: d,
|
|
191
|
+
borderPadding: f,
|
|
192
|
+
coords: p,
|
|
193
|
+
hyperlinks: m,
|
|
194
|
+
maxWidth: h,
|
|
195
|
+
profile: g,
|
|
196
|
+
direction: _
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function M(e) {
|
|
200
|
+
let t = x, n = 0;
|
|
201
|
+
for (; n < e.length;) {
|
|
202
|
+
let r = e[n];
|
|
203
|
+
if (r === void 0) throw Error(`parseArgs: index ${n} out of range while parsing arguments`);
|
|
204
|
+
if (r === "--port") t = O(e, n, r), n += 2;
|
|
205
|
+
else throw Error(`Unknown flag: ${r}`);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
command: "web",
|
|
209
|
+
port: t
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region packages/ascii-renderer/src/display-width.ts
|
|
214
|
+
var ee = /\p{Mn}|\p{Me}/u;
|
|
215
|
+
function te(e) {
|
|
216
|
+
return ee.test(e);
|
|
217
|
+
}
|
|
218
|
+
var ne = new Intl.Segmenter(void 0, { granularity: "grapheme" }), re = "️";
|
|
219
|
+
function ie(e) {
|
|
220
|
+
if (/^[\x20-\x7e]*$/.test(e)) return e.split("");
|
|
221
|
+
let t = [];
|
|
222
|
+
for (let { segment: n } of ne.segment(e)) t.push(n);
|
|
223
|
+
return t;
|
|
224
|
+
}
|
|
225
|
+
function ae(e) {
|
|
226
|
+
if (e.includes(re)) return 2;
|
|
227
|
+
let t = !1, n = !1;
|
|
228
|
+
for (let r of e) c(r) && (t = !0), te(r) || (n = !0);
|
|
229
|
+
return t ? 2 : +!!n;
|
|
230
|
+
}
|
|
231
|
+
function oe(e) {
|
|
232
|
+
let t = 0;
|
|
233
|
+
for (let n of ie(e)) t += ae(n);
|
|
234
|
+
return t;
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region packages/ascii-renderer/src/hyperlinks.ts
|
|
238
|
+
var se = /\x1b\]8;[^\x07\x1b]*(?:\x1b\\|\x07)/g;
|
|
239
|
+
function ce(e) {
|
|
240
|
+
return e.replace(se, "");
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region packages/mcp/src/tools/parse-diagnostics.ts
|
|
244
|
+
var N = /^Line (\d+): ([\s\S]*)$/;
|
|
245
|
+
function P(e, t) {
|
|
246
|
+
if (!(e instanceof i) || e.line === void 0) return;
|
|
247
|
+
let { line: n, message: r } = e, a = N.exec(r), o = t.split(/\r?\n/)[n - 1];
|
|
248
|
+
if (!(n < 1 || o === void 0)) return {
|
|
249
|
+
line: n,
|
|
250
|
+
sourceLine: o,
|
|
251
|
+
message: a?.[2] ?? r
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/cli/diagnostics.ts
|
|
256
|
+
function F(e, t) {
|
|
257
|
+
if (!(e instanceof Error)) return e;
|
|
258
|
+
let n = P(e, t);
|
|
259
|
+
return n === void 0 ? e : /* @__PURE__ */ Error(`${e.message}\n ${n.line} | ${n.sourceLine}`);
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/cli/html-viewer-client-source.ts
|
|
263
|
+
var I = "// @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", L = ":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 }";
|
|
264
|
+
function R(e) {
|
|
265
|
+
let t = e.title?.trim() || "Mermaid diagram";
|
|
266
|
+
return `<!doctype html>
|
|
674
267
|
<html lang="en">
|
|
675
268
|
<head>
|
|
676
269
|
<meta charset="utf-8" />
|
|
677
270
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
678
|
-
<title>${
|
|
271
|
+
<title>${o(t)}</title>
|
|
679
272
|
<style>
|
|
680
|
-
${
|
|
273
|
+
${L}
|
|
681
274
|
</style>
|
|
682
275
|
</head>
|
|
683
276
|
<body>
|
|
@@ -695,460 +288,320 @@ ${e.svg}
|
|
|
695
288
|
<button id="theme" type="button" aria-pressed="false" aria-label="Switch to dark mode (T)">Dark</button>
|
|
696
289
|
</div>
|
|
697
290
|
<script>
|
|
698
|
-
${
|
|
699
|
-
<\/script>
|
|
700
|
-
</body>
|
|
701
|
-
</html>
|
|
702
|
-
`;
|
|
703
|
-
}
|
|
704
|
-
async function $e(e) {
|
|
705
|
-
let n;
|
|
706
|
-
try {
|
|
707
|
-
n = await import("@resvg/resvg-js");
|
|
708
|
-
} catch (t) {
|
|
709
|
-
const r = t instanceof Error ? t.message : String(t);
|
|
710
|
-
throw new Error(`--png requires the optional dependency "@resvg/resvg-js", which is not installed (it may have failed to build for this platform, or the install ran with --no-optional). Install it with \`pnpm add @resvg/resvg-js\` (or the npm/yarn equivalent) and try again.
|
|
711
|
-
${r}`);
|
|
712
|
-
}
|
|
713
|
-
return (await n.renderAsync(e)).asPng();
|
|
714
|
-
}
|
|
715
|
-
var Pe = 80, L = 1, R = 1, B = 0, Le = /\x1b\[[0-9;]*m/g;
|
|
716
|
-
function N(e) {
|
|
717
|
-
return e.split(`
|
|
718
|
-
`).reduce((n, t) => Math.max(n, Se(Ce(t.replace(Le, "")))), 0);
|
|
719
|
-
}
|
|
720
|
-
function Re(e) {
|
|
721
|
-
return e !== "auto" ? e : process.stdout.columns ?? Pe;
|
|
722
|
-
}
|
|
723
|
-
async function Be(e, n, t, r) {
|
|
724
|
-
const a = n ?? process.stdout, d = r ?? process.stderr;
|
|
725
|
-
let l;
|
|
726
|
-
if (e.input !== void 0) l = await G(e.input, "utf-8");
|
|
727
|
-
else if (t !== void 0) l = t;
|
|
728
|
-
else {
|
|
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.");
|
|
730
|
-
l = await U();
|
|
731
|
-
}
|
|
732
|
-
if (l.trim().length === 0) throw new Error("Empty input — provide a Mermaid diagram via file or stdin");
|
|
733
|
-
try {
|
|
734
|
-
await Oe(e, l, a, d);
|
|
735
|
-
} catch (m) {
|
|
736
|
-
throw X(m, l);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
async function Oe(e, n, t, r) {
|
|
740
|
-
let a;
|
|
741
|
-
if (e.theme !== void 0 && (a = O[e.theme], a === void 0)) {
|
|
742
|
-
const s = Object.keys(O).join(", ");
|
|
743
|
-
throw new Error(`Unknown theme: "${e.theme}". Available themes: ${s}`);
|
|
744
|
-
}
|
|
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;
|
|
746
|
-
for (const s of [
|
|
747
|
-
g,
|
|
748
|
-
E,
|
|
749
|
-
S,
|
|
750
|
-
k
|
|
751
|
-
]) s !== void 0 && await _e(s, e.force);
|
|
752
|
-
if (e.ascii) {
|
|
753
|
-
const s = a && g === void 0 ? {
|
|
754
|
-
colorMode: "auto",
|
|
755
|
-
theme: ne(a)
|
|
756
|
-
} : { colorMode: "none" };
|
|
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);
|
|
759
|
-
if (e.maxWidth !== void 0) {
|
|
760
|
-
const p = Re(e.maxWidth);
|
|
761
|
-
let h = N(u), v = !1;
|
|
762
|
-
if (h > p) {
|
|
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, {
|
|
766
|
-
...s,
|
|
767
|
-
paddingX: C,
|
|
768
|
-
paddingY: f,
|
|
769
|
-
boxBorderPadding: T
|
|
770
|
-
}), o = N(x);
|
|
771
|
-
o < h && (u = x, h = o, v = !0);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
if (h > p) {
|
|
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}
|
|
777
|
-
`);
|
|
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.
|
|
779
|
-
`);
|
|
780
|
-
}
|
|
781
|
-
await M(A(g === void 0, g), u + `
|
|
782
|
-
`, t, e.force);
|
|
783
|
-
}
|
|
784
|
-
if (e.svg) {
|
|
785
|
-
const s = {
|
|
786
|
-
...a,
|
|
787
|
-
resolveColors: e.resolveColors
|
|
788
|
-
};
|
|
789
|
-
e.direction !== void 0 && (s.direction = e.direction);
|
|
790
|
-
const u = z(n, s);
|
|
791
|
-
await M(A(d, E), u, t, e.force);
|
|
792
|
-
}
|
|
793
|
-
if (e.html) {
|
|
794
|
-
const s = z(n, a ?? {}), u = e.input ? F(e.input).name : void 0, p = ze({
|
|
795
|
-
svg: s,
|
|
796
|
-
title: u
|
|
797
|
-
});
|
|
798
|
-
await M(A(l, S), p, t, e.force);
|
|
799
|
-
}
|
|
800
|
-
if (e.png) {
|
|
801
|
-
const s = {
|
|
802
|
-
...a,
|
|
803
|
-
resolveColors: !0
|
|
804
|
-
};
|
|
805
|
-
e.direction !== void 0 && (s.direction = e.direction);
|
|
806
|
-
const u = z(n, s), p = await $e(u);
|
|
807
|
-
await M(A(m, k), p, t, e.force);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
function A(e, n) {
|
|
811
|
-
return e ? { kind: "stdout" } : n !== void 0 ? {
|
|
812
|
-
kind: "file",
|
|
813
|
-
path: n
|
|
814
|
-
} : { kind: "skip" };
|
|
815
|
-
}
|
|
816
|
-
async function M(e, n, t, r) {
|
|
817
|
-
switch (e.kind) {
|
|
818
|
-
case "stdout":
|
|
819
|
-
t.write(n);
|
|
820
|
-
break;
|
|
821
|
-
case "file":
|
|
822
|
-
await Ne(e.path, n, r);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
function Y(e) {
|
|
826
|
-
return /* @__PURE__ */ new Error(`Refusing to overwrite existing file "${e}" — pass --force to replace it`);
|
|
827
|
-
}
|
|
828
|
-
async function _e(e, n) {
|
|
829
|
-
if (n) return;
|
|
830
|
-
let t = !0;
|
|
831
|
-
try {
|
|
832
|
-
await ee(e);
|
|
833
|
-
} catch {
|
|
834
|
-
t = !1;
|
|
835
|
-
}
|
|
836
|
-
if (t) throw Y(e);
|
|
837
|
-
}
|
|
838
|
-
async function Ne(e, n, t) {
|
|
839
|
-
try {
|
|
840
|
-
await te(e, n, {
|
|
841
|
-
encoding: "utf-8",
|
|
842
|
-
flag: t ? "w" : "wx"
|
|
843
|
-
});
|
|
844
|
-
} catch (r) {
|
|
845
|
-
throw !t && r instanceof Error && "code" in r && r.code === "EEXIST" ? Y(e) : r;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
async function U() {
|
|
849
|
-
const e = [], n = process.stdin;
|
|
850
|
-
return n.setEncoding("utf-8"), new Promise((t, r) => {
|
|
851
|
-
n.on("data", (a) => e.push(a)), n.on("end", () => t(e.join(""))), n.on("error", r);
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
async function De(e, n) {
|
|
855
|
-
let t;
|
|
856
|
-
if (e.input !== void 0) t = await G(e.input, "utf-8");
|
|
857
|
-
else if (n !== void 0) t = n;
|
|
858
|
-
else {
|
|
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.");
|
|
860
|
-
t = await U();
|
|
861
|
-
}
|
|
862
|
-
const r = t;
|
|
863
|
-
if (r.trim().length === 0) throw new Error("Empty input — provide a Mermaid diagram via file or stdin");
|
|
864
|
-
try {
|
|
865
|
-
oe(r);
|
|
866
|
-
} catch (a) {
|
|
867
|
-
throw X(a, r);
|
|
868
|
-
}
|
|
869
|
-
console.log("valid");
|
|
870
|
-
}
|
|
871
|
-
var Fe = 1e6;
|
|
872
|
-
function Ge(e) {
|
|
873
|
-
return new Promise((n, t) => {
|
|
874
|
-
const r = [];
|
|
875
|
-
let a = 0;
|
|
876
|
-
e.on("data", (d) => {
|
|
877
|
-
if (a += d.length, a > Fe) {
|
|
878
|
-
t(/* @__PURE__ */ new Error("Request body too large")), e.destroy();
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
881
|
-
r.push(d);
|
|
882
|
-
}), e.on("end", () => n(Buffer.concat(r).toString("utf-8"))), e.on("error", t);
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
function V(e) {
|
|
886
|
-
return typeof e == "object" && e !== null;
|
|
887
|
-
}
|
|
888
|
-
function We(e) {
|
|
889
|
-
let n;
|
|
890
|
-
try {
|
|
891
|
-
n = JSON.parse(e);
|
|
892
|
-
} catch {
|
|
893
|
-
throw new Error("Invalid JSON in request body");
|
|
894
|
-
}
|
|
895
|
-
if (!V(n)) throw new Error("Request body must be a JSON object");
|
|
896
|
-
const t = n.source;
|
|
897
|
-
if (typeof t != "string" || t.trim().length === 0) throw new Error('Provide Mermaid source as a non-empty "source" string');
|
|
898
|
-
return {
|
|
899
|
-
source: t,
|
|
900
|
-
format: n.format === "svg" ? "svg" : "ascii"
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
function D(e, n, t) {
|
|
904
|
-
const r = JSON.stringify(t);
|
|
905
|
-
e.writeHead(n, {
|
|
906
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
907
|
-
"Content-Length": Buffer.byteLength(r)
|
|
908
|
-
}), e.end(r);
|
|
909
|
-
}
|
|
910
|
-
async function Xe(e, n) {
|
|
911
|
-
try {
|
|
912
|
-
const { source: t, format: r } = We(await Ge(e));
|
|
913
|
-
D(n, 200, {
|
|
914
|
-
output: r === "svg" ? z(t) : _(t),
|
|
915
|
-
format: r
|
|
916
|
-
});
|
|
917
|
-
} catch (t) {
|
|
918
|
-
D(n, 400, { error: t instanceof Error ? t.message : String(t) });
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
var Ye = `<!doctype html>
|
|
922
|
-
<html lang="en">
|
|
923
|
-
<head>
|
|
924
|
-
<meta charset="utf-8" />
|
|
925
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
926
|
-
<title>zombie-mermaid</title>
|
|
927
|
-
<style>
|
|
928
|
-
body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 900px; }
|
|
929
|
-
h1 { font-size: 1.25rem; }
|
|
930
|
-
textarea { width: 100%; height: 200px; font-family: monospace; font-size: 14px; box-sizing: border-box; }
|
|
931
|
-
pre { white-space: pre; overflow: auto; background: #111; color: #eee; padding: 1rem; border-radius: 4px; }
|
|
932
|
-
.row { display: flex; gap: 1rem; align-items: center; margin: 0.75rem 0; }
|
|
933
|
-
#error { color: #c00; white-space: pre-wrap; }
|
|
934
|
-
#output svg { max-width: 100%; }
|
|
935
|
-
</style>
|
|
936
|
-
</head>
|
|
937
|
-
<body>
|
|
938
|
-
<h1>zombie-mermaid</h1>
|
|
939
|
-
<textarea id="source" placeholder="graph LR
|
|
940
|
-
A --> B">graph LR
|
|
941
|
-
A --> B --> C</textarea>
|
|
942
|
-
<div class="row">
|
|
943
|
-
<label><input type="radio" name="format" value="ascii" checked /> ASCII</label>
|
|
944
|
-
<label><input type="radio" name="format" value="svg" /> SVG</label>
|
|
945
|
-
<button id="render" type="button">Render</button>
|
|
946
|
-
</div>
|
|
947
|
-
<div id="error"></div>
|
|
948
|
-
<div id="output"></div>
|
|
949
|
-
<script>
|
|
950
|
-
const sourceEl = document.getElementById('source')
|
|
951
|
-
const outputEl = document.getElementById('output')
|
|
952
|
-
const errorEl = document.getElementById('error')
|
|
953
|
-
|
|
954
|
-
async function render() {
|
|
955
|
-
errorEl.textContent = ''
|
|
956
|
-
outputEl.innerHTML = ''
|
|
957
|
-
const formatInput = document.querySelector('input[name="format"]:checked')
|
|
958
|
-
const format = formatInput ? formatInput.value : 'ascii'
|
|
959
|
-
try {
|
|
960
|
-
const res = await fetch('/render', {
|
|
961
|
-
method: 'POST',
|
|
962
|
-
headers: { 'Content-Type': 'application/json' },
|
|
963
|
-
body: JSON.stringify({ source: sourceEl.value, format }),
|
|
964
|
-
})
|
|
965
|
-
const data = await res.json()
|
|
966
|
-
if (!res.ok) {
|
|
967
|
-
errorEl.textContent = data.error || 'Render failed'
|
|
968
|
-
return
|
|
969
|
-
}
|
|
970
|
-
if (format === 'svg') {
|
|
971
|
-
outputEl.innerHTML = data.output
|
|
972
|
-
} else {
|
|
973
|
-
const pre = document.createElement('pre')
|
|
974
|
-
pre.textContent = data.output
|
|
975
|
-
outputEl.appendChild(pre)
|
|
976
|
-
}
|
|
977
|
-
} catch (err) {
|
|
978
|
-
errorEl.textContent = String(err)
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
document.getElementById('render').addEventListener('click', render)
|
|
291
|
+
${I}
|
|
983
292
|
<\/script>
|
|
984
293
|
</body>
|
|
985
294
|
</html>
|
|
986
295
|
`;
|
|
987
|
-
function Ue() {
|
|
988
|
-
return ie((e, n) => {
|
|
989
|
-
const t = (e.url ?? "/").split("?")[0];
|
|
990
|
-
if (e.method === "GET" && (t === "/" || t === "")) {
|
|
991
|
-
n.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), n.end(Ye);
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
if (e.method === "POST" && t === "/render") {
|
|
995
|
-
Xe(e, n);
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
n.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }), n.end("Not found");
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
function Ve(e) {
|
|
1002
|
-
return new Promise((n, t) => {
|
|
1003
|
-
const r = Ue();
|
|
1004
|
-
r.once("error", t), r.listen(e, "127.0.0.1", () => {
|
|
1005
|
-
r.removeListener("error", t), n(r);
|
|
1006
|
-
});
|
|
1007
|
-
});
|
|
1008
296
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/cli/png.ts
|
|
299
|
+
async function z(e) {
|
|
300
|
+
let t;
|
|
301
|
+
try {
|
|
302
|
+
t = await import("@resvg/resvg-js");
|
|
303
|
+
} catch (e) {
|
|
304
|
+
let t = e instanceof Error ? e.message : String(e);
|
|
305
|
+
throw Error(`--png requires the optional dependency "@resvg/resvg-js", which is not installed (it may have failed to build for this platform, or the install ran with --no-optional). Install it with \`pnpm add @resvg/resvg-js\` (or the npm/yarn equivalent) and try again.\n${t}`);
|
|
306
|
+
}
|
|
307
|
+
return (await t.renderAsync(e)).asPng();
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/cli/render.ts
|
|
311
|
+
var B = 80, V = 1, H = 1, U = 0, W = /\x1b\[[0-9;]*m/g;
|
|
312
|
+
function G(e) {
|
|
313
|
+
return e.split("\n").reduce((e, t) => Math.max(e, oe(ce(t.replace(W, "")))), 0);
|
|
1012
314
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
315
|
+
function le(e) {
|
|
316
|
+
return e === "auto" ? process.stdout.columns ?? B : e;
|
|
317
|
+
}
|
|
318
|
+
async function ue(e, t, n, r) {
|
|
319
|
+
let i = t ?? process.stdout, a = r ?? process.stderr, o;
|
|
320
|
+
if (e.input !== void 0) o = await u(e.input, "utf-8");
|
|
321
|
+
else if (n !== void 0) o = n;
|
|
322
|
+
else {
|
|
323
|
+
if (process.stdin.isTTY) throw Error("No input file specified and stdin is a terminal. Pipe a diagram or pass a file path.");
|
|
324
|
+
o = await Y();
|
|
325
|
+
}
|
|
326
|
+
if (o.trim().length === 0) throw Error("Empty input — provide a Mermaid diagram via file or stdin");
|
|
327
|
+
try {
|
|
328
|
+
await de(e, o, i, a);
|
|
329
|
+
} catch (e) {
|
|
330
|
+
throw F(e, o);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function de(e, t, n, i) {
|
|
334
|
+
let o;
|
|
335
|
+
if (e.theme !== void 0 && (o = a[e.theme], o === void 0)) {
|
|
336
|
+
let t = Object.keys(a).join(", ");
|
|
337
|
+
throw Error(`Unknown theme: "${e.theme}". Available themes: ${t}`);
|
|
338
|
+
}
|
|
339
|
+
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, m = e.html && !c ? e.output : void 0, g = e.png && !l ? e.output : void 0;
|
|
340
|
+
for (let t of [
|
|
341
|
+
u,
|
|
342
|
+
d,
|
|
343
|
+
m,
|
|
344
|
+
g
|
|
345
|
+
]) t !== void 0 && await fe(t, e.force);
|
|
346
|
+
if (e.ascii) {
|
|
347
|
+
let r = o && u === void 0 ? {
|
|
348
|
+
colorMode: "auto",
|
|
349
|
+
theme: f(o)
|
|
350
|
+
} : { colorMode: "none" };
|
|
351
|
+
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);
|
|
352
|
+
let a = p(t, r);
|
|
353
|
+
if (e.maxWidth !== void 0) {
|
|
354
|
+
let n = le(e.maxWidth), o = G(a), s = !1;
|
|
355
|
+
if (o > n) {
|
|
356
|
+
let e = r.paddingX ?? 5, n = r.paddingY ?? 5, i = r.boxBorderPadding ?? 1, c = Math.min(e, V), l = Math.min(n, H), u = Math.min(i, U);
|
|
357
|
+
if (c < e || l < n || u < i) {
|
|
358
|
+
let e = p(t, {
|
|
359
|
+
...r,
|
|
360
|
+
paddingX: c,
|
|
361
|
+
paddingY: l,
|
|
362
|
+
boxBorderPadding: u
|
|
363
|
+
}), n = G(e);
|
|
364
|
+
n < o && (a = e, o = n, s = !0);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (o > n) {
|
|
368
|
+
let t = e.maxWidth === "auto" ? "detected terminal width" : "--max-width", r = s ? `Compact spacing (-x ${V} -y ${H} -p ${U}) was already applied automatically. ` : "", a = s ? "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.";
|
|
369
|
+
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`);
|
|
370
|
+
} else s && 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`);
|
|
371
|
+
}
|
|
372
|
+
await q(K(u === void 0, u), a + "\n", n, e.force);
|
|
373
|
+
}
|
|
374
|
+
if (e.svg) {
|
|
375
|
+
let r = {
|
|
376
|
+
...o,
|
|
377
|
+
resolveColors: e.resolveColors
|
|
378
|
+
};
|
|
379
|
+
e.direction !== void 0 && (r.direction = e.direction);
|
|
380
|
+
let i = h(t, r);
|
|
381
|
+
await q(K(s, d), i, n, e.force);
|
|
382
|
+
}
|
|
383
|
+
if (e.html) {
|
|
384
|
+
let i = R({
|
|
385
|
+
svg: h(t, o ?? {}),
|
|
386
|
+
title: e.input ? r(e.input).name : void 0
|
|
387
|
+
});
|
|
388
|
+
await q(K(c, m), i, n, e.force);
|
|
389
|
+
}
|
|
390
|
+
if (e.png) {
|
|
391
|
+
let r = {
|
|
392
|
+
...o,
|
|
393
|
+
resolveColors: !0
|
|
394
|
+
};
|
|
395
|
+
e.direction !== void 0 && (r.direction = e.direction);
|
|
396
|
+
let i = await z(h(t, r));
|
|
397
|
+
await q(K(l, g), i, n, e.force);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function K(e, t) {
|
|
401
|
+
return e ? { kind: "stdout" } : t === void 0 ? { kind: "skip" } : {
|
|
402
|
+
kind: "file",
|
|
403
|
+
path: t
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
async function q(e, t, n, r) {
|
|
407
|
+
switch (e.kind) {
|
|
408
|
+
case "stdout":
|
|
409
|
+
n.write(t);
|
|
410
|
+
break;
|
|
411
|
+
case "file": await pe(e.path, t, r);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function J(e) {
|
|
415
|
+
return /* @__PURE__ */ Error(`Refusing to overwrite existing file "${e}" — pass --force to replace it`);
|
|
416
|
+
}
|
|
417
|
+
async function fe(e, t) {
|
|
418
|
+
if (t) return;
|
|
419
|
+
let n = !0;
|
|
420
|
+
try {
|
|
421
|
+
await l(e);
|
|
422
|
+
} catch {
|
|
423
|
+
n = !1;
|
|
424
|
+
}
|
|
425
|
+
if (n) throw J(e);
|
|
426
|
+
}
|
|
427
|
+
async function pe(e, t, n) {
|
|
428
|
+
try {
|
|
429
|
+
await d(e, t, {
|
|
430
|
+
encoding: "utf-8",
|
|
431
|
+
flag: n ? "w" : "wx"
|
|
432
|
+
});
|
|
433
|
+
} catch (t) {
|
|
434
|
+
throw !n && t instanceof Error && "code" in t && t.code === "EEXIST" ? J(e) : t;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async function Y() {
|
|
438
|
+
let e = [], t = process.stdin;
|
|
439
|
+
return t.setEncoding("utf-8"), new Promise((n, r) => {
|
|
440
|
+
t.on("data", (t) => e.push(t)), t.on("end", () => n(e.join(""))), t.on("error", r);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region src/cli/validate.ts
|
|
445
|
+
async function me(e, t) {
|
|
446
|
+
let n;
|
|
447
|
+
if (e.input !== void 0) n = await u(e.input, "utf-8");
|
|
448
|
+
else if (t !== void 0) n = t;
|
|
449
|
+
else {
|
|
450
|
+
if (process.stdin.isTTY) throw Error("No input file specified and stdin is a terminal. Pipe a diagram or pass a file path.");
|
|
451
|
+
n = await Y();
|
|
452
|
+
}
|
|
453
|
+
let r = n;
|
|
454
|
+
if (r.trim().length === 0) throw Error("Empty input — provide a Mermaid diagram via file or stdin");
|
|
455
|
+
try {
|
|
456
|
+
g(r);
|
|
457
|
+
} catch (e) {
|
|
458
|
+
throw F(e, r);
|
|
459
|
+
}
|
|
460
|
+
console.log("valid");
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region src/cli/web.ts
|
|
464
|
+
var he = 1e6;
|
|
465
|
+
function ge(e) {
|
|
466
|
+
return new Promise((t, n) => {
|
|
467
|
+
let r = [], i = 0;
|
|
468
|
+
e.on("data", (t) => {
|
|
469
|
+
if (i += t.length, i > he) {
|
|
470
|
+
n(/* @__PURE__ */ Error("Request body too large")), e.destroy();
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
r.push(t);
|
|
474
|
+
}), e.on("end", () => t(Buffer.concat(r).toString("utf-8"))), e.on("error", n);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function X(e) {
|
|
478
|
+
return typeof e == "object" && !!e;
|
|
479
|
+
}
|
|
480
|
+
function _e(e) {
|
|
481
|
+
let t;
|
|
482
|
+
try {
|
|
483
|
+
t = JSON.parse(e);
|
|
484
|
+
} catch {
|
|
485
|
+
throw Error("Invalid JSON in request body");
|
|
486
|
+
}
|
|
487
|
+
if (!X(t)) throw Error("Request body must be a JSON object");
|
|
488
|
+
let n = t.source;
|
|
489
|
+
if (typeof n != "string" || n.trim().length === 0) throw Error("Provide Mermaid source as a non-empty \"source\" string");
|
|
490
|
+
return {
|
|
491
|
+
source: n,
|
|
492
|
+
format: t.format === "svg" ? "svg" : "ascii"
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function Z(e, t, n) {
|
|
496
|
+
let r = JSON.stringify(n);
|
|
497
|
+
e.writeHead(t, {
|
|
498
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
499
|
+
"Content-Length": Buffer.byteLength(r)
|
|
500
|
+
}), e.end(r);
|
|
501
|
+
}
|
|
502
|
+
async function ve(e, t) {
|
|
503
|
+
try {
|
|
504
|
+
let { source: n, format: r } = _e(await ge(e));
|
|
505
|
+
Z(t, 200, {
|
|
506
|
+
output: r === "svg" ? h(n) : p(n),
|
|
507
|
+
format: r
|
|
508
|
+
});
|
|
509
|
+
} catch (e) {
|
|
510
|
+
Z(t, 400, { error: e instanceof Error ? e.message : String(e) });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
var ye = "<!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";
|
|
514
|
+
function be() {
|
|
515
|
+
return _((e, t) => {
|
|
516
|
+
let n = (e.url ?? "/").split("?")[0];
|
|
517
|
+
if (e.method === "GET" && (n === "/" || n === "")) {
|
|
518
|
+
t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(ye);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (e.method === "POST" && n === "/render") {
|
|
522
|
+
ve(e, t);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
t.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }), t.end("Not found");
|
|
526
|
+
});
|
|
1150
527
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
528
|
+
function xe(e) {
|
|
529
|
+
return new Promise((t, n) => {
|
|
530
|
+
let r = be();
|
|
531
|
+
r.once("error", n), r.listen(e, "127.0.0.1", () => {
|
|
532
|
+
r.removeListener("error", n), t(r);
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
async function Q(e, t = console.log) {
|
|
537
|
+
let n = await xe(e.port), r = n.address();
|
|
538
|
+
return t(`zombie-mermaid web running at http://localhost:${X(r) && typeof r.port == "number" ? r.port : e.port}`), n;
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/cli/mcp.ts
|
|
542
|
+
async function Se(e = (e) => console.error(e)) {
|
|
543
|
+
let t = y(), n = new v();
|
|
544
|
+
return await t.connect(n), e("zombie-mermaid MCP server running on stdio (experimental — feedback welcome: https://github.com/dfadler/zombie-mermaid/issues)"), t;
|
|
545
|
+
}
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/package-info.ts
|
|
548
|
+
function Ce() {
|
|
549
|
+
let t = e(import.meta.url)("../package.json");
|
|
550
|
+
return typeof t == "object" && t && "version" in t && typeof t.version == "string" ? t.version : "unknown";
|
|
551
|
+
}
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/cli.ts
|
|
554
|
+
m(b);
|
|
555
|
+
var we = Ce();
|
|
556
|
+
async function $() {
|
|
557
|
+
let e = process.argv.slice(2), t;
|
|
558
|
+
try {
|
|
559
|
+
t = D(e);
|
|
560
|
+
} catch (e) {
|
|
561
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
switch (t.command) {
|
|
564
|
+
case "help":
|
|
565
|
+
Te();
|
|
566
|
+
break;
|
|
567
|
+
case "version":
|
|
568
|
+
console.log(`zombie-mermaid ${we}`);
|
|
569
|
+
break;
|
|
570
|
+
case "themes":
|
|
571
|
+
console.log("Available themes:\n");
|
|
572
|
+
for (let e of Object.keys(a)) console.log(` ${e}`);
|
|
573
|
+
break;
|
|
574
|
+
case "render":
|
|
575
|
+
try {
|
|
576
|
+
await ue(t);
|
|
577
|
+
} catch (e) {
|
|
578
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
break;
|
|
581
|
+
case "validate":
|
|
582
|
+
try {
|
|
583
|
+
await me(t);
|
|
584
|
+
} catch (e) {
|
|
585
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
break;
|
|
588
|
+
case "web":
|
|
589
|
+
try {
|
|
590
|
+
await Q(t);
|
|
591
|
+
} catch (e) {
|
|
592
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
break;
|
|
595
|
+
case "mcp": try {
|
|
596
|
+
await Se();
|
|
597
|
+
} catch (e) {
|
|
598
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function Te() {
|
|
603
|
+
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 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)\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 --port <n> Port for the 'web' command (default: 3000)\n -h, --help Show help\n -v, --version Show version");
|
|
604
|
+
}
|
|
605
|
+
$();
|
|
606
|
+
//#endregion
|
|
607
|
+
export { $ as main };
|