memd-cli 1.5.0 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memd-cli",
3
- "version": "1.5.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "main": "main.js",
6
6
  "bin": {
@@ -12,15 +12,18 @@
12
12
  "dependencies": {
13
13
  "beautiful-mermaid": "^1.1.2",
14
14
  "chalk": "^5.6.2",
15
- "cli-highlight": "^2.1.11",
16
15
  "commander": "^14.0.3",
17
16
  "marked": "^17.0.3",
18
- "marked-terminal": "^7.3.0"
17
+ "marked-terminal": "^7.3.0",
18
+ "shiki": "^4.0.2"
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
22
22
  "url": "https://github.com/ktrysmt/memd.git"
23
23
  },
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
24
27
  "license": "MIT",
25
28
  "packageManager": "pnpm@10.15.1",
26
29
  "devDependencies": {
package/poc-html.mjs ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ // PoC: Markdown + Mermaid -> HTML w/ inline SVG
3
+ // Tests: marker ID uniquification, theme CSS, mermaid-error styling
4
+ import { Marked } from 'marked';
5
+ import { renderMermaidSVG, THEMES } from 'beautiful-mermaid';
6
+ import * as fs from 'fs';
7
+
8
+ const THEME_NAMES = Object.keys(THEMES);
9
+ const themeName = process.argv[2] || 'github-dark';
10
+ const inputFile = process.argv[3] || 'test/test3.md';
11
+
12
+ if (!(themeName in THEMES)) {
13
+ console.error(`Unknown theme: ${themeName}`);
14
+ console.error(`Available: ${THEME_NAMES.join(', ')}`);
15
+ process.exit(1);
16
+ }
17
+
18
+ const theme = THEMES[themeName];
19
+ console.log(`Theme: ${themeName}, Input: ${inputFile}`);
20
+ console.log(`Theme fields: ${JSON.stringify(theme)}`);
21
+
22
+ const testMarkdown = fs.readFileSync(inputFile, 'utf-8');
23
+
24
+ function escapeHtml(str) {
25
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
26
+ }
27
+
28
+ // Resolve optional DiagramColors fields with color mixing fallback
29
+ // Matches beautiful-mermaid's internal MIX ratios (theme.ts:64-87)
30
+ function mixHex(hex1, hex2, pct) {
31
+ const p = pct / 100;
32
+ const r1 = parseInt(hex1.slice(1, 3), 16), g1 = parseInt(hex1.slice(3, 5), 16), b1 = parseInt(hex1.slice(5, 7), 16);
33
+ const r2 = parseInt(hex2.slice(1, 3), 16), g2 = parseInt(hex2.slice(3, 5), 16), b2 = parseInt(hex2.slice(5, 7), 16);
34
+ const toHex = x => Math.round(x).toString(16).padStart(2, '0');
35
+ return `#${toHex(r1 * p + r2 * (1 - p))}${toHex(g1 * p + g2 * (1 - p))}${toHex(b1 * p + b2 * (1 - p))}`;
36
+ }
37
+
38
+ // Resolve theme colors with fallback derivation for optional fields
39
+ function resolveThemeColors(colors) {
40
+ const line = colors.line ?? mixHex(colors.fg, colors.bg, 50);
41
+ const accent = colors.accent ?? mixHex(colors.fg, colors.bg, 85);
42
+ const muted = colors.muted ?? mixHex(colors.fg, colors.bg, 60);
43
+ const border = colors.border ?? mixHex(colors.fg, colors.bg, 20);
44
+ return { bg: colors.bg, fg: colors.fg, line, accent, muted, border };
45
+ }
46
+
47
+ function convertMermaidToSVG(markdown, diagramTheme) {
48
+ const mermaidRegex = /```mermaid\s+([\s\S]+?)```/g;
49
+ let svgIndex = 0;
50
+ return markdown.replace(mermaidRegex, (_, code) => {
51
+ try {
52
+ const prefix = `m${svgIndex++}`;
53
+ let svg = renderMermaidSVG(code.trim(), diagramTheme);
54
+ svg = svg.replace(/@import url\([^)]+\);\s*/g, '');
55
+ // Prefix all id="..." and url(#...) to avoid cross-SVG collisions
56
+ svg = svg.replace(/ id="([^"]+)"/g, ` id="${prefix}-$1"`);
57
+ svg = svg.replace(/url\(#([^)]+)\)/g, `url(#${prefix}-$1)`);
58
+ return svg;
59
+ } catch (e) {
60
+ return `<pre class="mermaid-error">${escapeHtml(e.message)}\n\n${escapeHtml(code.trim())}</pre>`;
61
+ }
62
+ });
63
+ }
64
+
65
+ function renderToHTML(markdown, diagramTheme) {
66
+ const t = resolveThemeColors(diagramTheme);
67
+ const processed = convertMermaidToSVG(markdown, diagramTheme);
68
+ const htmlMarked = new Marked();
69
+ const htmlBody = htmlMarked.parse(processed);
70
+
71
+ return `<!DOCTYPE html>
72
+ <html lang="en">
73
+ <head>
74
+ <meta charset="UTF-8">
75
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
76
+ <title>memd preview</title>
77
+ <style>
78
+ body {
79
+ max-width: 800px;
80
+ margin: 2rem auto;
81
+ padding: 0 1rem;
82
+ font-family: system-ui, -apple-system, sans-serif;
83
+ line-height: 1.6;
84
+ background: ${t.bg};
85
+ color: ${t.fg};
86
+ }
87
+ a { color: ${t.accent}; }
88
+ hr { border-color: ${t.line}; }
89
+ blockquote { border-left: 3px solid ${t.line}; padding-left: 1rem; color: ${t.muted}; }
90
+ svg { max-width: 100%; height: auto; }
91
+ pre { background: color-mix(in srgb, ${t.fg} 8%, ${t.bg}); padding: 1rem; border-radius: 6px; overflow-x: auto; }
92
+ code { font-size: 0.9em; color: ${t.accent}; }
93
+ pre code { color: inherit; }
94
+ table { border-collapse: collapse; }
95
+ th, td { border: 1px solid ${t.line}; padding: 0.4rem 0.8rem; }
96
+ th { background: color-mix(in srgb, ${t.fg} 5%, ${t.bg}); }
97
+ .mermaid-error {
98
+ background: color-mix(in srgb, ${t.accent} 10%, ${t.bg});
99
+ border: 1px solid color-mix(in srgb, ${t.accent} 40%, ${t.bg});
100
+ color: ${t.fg};
101
+ padding: 1rem;
102
+ border-radius: 6px;
103
+ overflow-x: auto;
104
+ white-space: pre-wrap;
105
+ }
106
+ </style>
107
+ </head>
108
+ <body>
109
+ ${htmlBody}
110
+ </body>
111
+ </html>`;
112
+ }
113
+
114
+ const html = renderToHTML(testMarkdown, theme);
115
+ const outputFile = 'poc-output.html';
116
+ fs.writeFileSync(outputFile, html);
117
+
118
+ // Validation
119
+ const svgCount = (html.match(/<svg/g) || []).length;
120
+ const markerIds = [...html.matchAll(/ id="([^"]+)"/g)].map(m => m[1]);
121
+ const uniqueMarkerIds = new Set(markerIds);
122
+ const errorBlocks = (html.match(/mermaid-error/g) || []).length;
123
+
124
+ console.log(`Written to ${outputFile} (${html.length} bytes)`);
125
+ console.log(`SVGs: ${svgCount}`);
126
+ console.log(`Marker IDs (${markerIds.length}): ${markerIds.join(', ')}`);
127
+ console.log(`Unique: ${uniqueMarkerIds.size}, Duplicates: ${markerIds.length - uniqueMarkerIds.size}`);
128
+ console.log(`Mermaid errors: ${errorBlocks / 2}`); // each error has class + selector = 2 matches
129
+ if (markerIds.length !== uniqueMarkerIds.size) {
130
+ console.error('ERROR: Duplicate marker IDs detected!');
131
+ process.exit(1);
132
+ } else {
133
+ console.log('OK: All marker IDs are unique');
134
+ }