@helping-ai-workflow/md2doc 1.1.2 → 2.0.1
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 +28 -9
- package/bin/md2doc.js +249 -0
- package/lib/md2doc.js +9 -1
- package/package.json +3 -4
- package/bin/md2html.js +0 -156
- package/bin/md2pdf.js +0 -153
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Markdown → HTML / PDF renderer with WaveDrom, Mermaid, and Graphviz support.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A single global CLI (`md2doc`) you can call from any directory.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -43,25 +43,44 @@ npm install -g @helping-ai-workflow/md2doc
|
|
|
43
43
|
## Usage
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
md2doc foo.md # render HTML to OS temp dir, open viewer
|
|
47
|
+
md2doc --pdf foo.md # render PDF instead
|
|
48
|
+
md2doc --html --pdf foo.md # render both formats
|
|
49
|
+
md2doc *.md # batch: each file → temp + open
|
|
50
|
+
|
|
51
|
+
md2doc foo.md --out bar.html # write to a specific file (no auto-open)
|
|
52
|
+
md2doc foo.md --out ./build/ # write to ./build/foo.html (no auto-open)
|
|
53
|
+
md2doc *.md --out ./build/ # batch into ./build/
|
|
54
|
+
md2doc foo.md --out ./build/ --open # explicit open with --out
|
|
51
55
|
```
|
|
52
56
|
|
|
53
|
-
`
|
|
57
|
+
By default, `md2doc` writes to your OS temp directory and launches the platform viewer.
|
|
58
|
+
Pass `--out <path>` to write somewhere specific; doing so disables auto-open unless you
|
|
59
|
+
also pass `--open`.
|
|
54
60
|
|
|
55
61
|
### Flags
|
|
56
62
|
|
|
57
63
|
| Flag | Meaning |
|
|
58
64
|
|---|---|
|
|
59
|
-
| `--
|
|
60
|
-
| `--
|
|
65
|
+
| `--html` | Render HTML (default if neither `--html` nor `--pdf` is given). |
|
|
66
|
+
| `--pdf` | Render PDF. Combine with `--html` to render both. |
|
|
67
|
+
| `--out <path>` | Output path. Ends with `/` or an existing directory → directory mode. Ends with `.html` / `.pdf` → file mode (single input only). Implies `--no-open` unless `--open` is also passed. |
|
|
68
|
+
| `--open` | Launch the platform viewer (`xdg-open` / `open` / `start`) after render. Default when `--out` is absent. |
|
|
69
|
+
| `--no-open` | Skip the viewer launch. |
|
|
61
70
|
| `--quiet` | Suppress per-file progress messages. |
|
|
62
71
|
| `--version`, `-v` | Print version. |
|
|
63
72
|
| `--help`, `-h` | Print help. |
|
|
64
73
|
|
|
74
|
+
### Migration from md2html / md2pdf (v1.x → v2.0.0)
|
|
75
|
+
|
|
76
|
+
| Old | New |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `md2html foo.md` | `md2doc foo.md` |
|
|
79
|
+
| `md2pdf foo.md` | `md2doc --pdf foo.md` |
|
|
80
|
+
| `md2html foo.md --out f.html` | `md2doc foo.md --out f.html` |
|
|
81
|
+
| `md2html foo.md --open` | `md2doc foo.md` (open is default) |
|
|
82
|
+
| `md2html *.md` (output next to source) | `md2doc *.md --out ./build/` (or accept temp output) |
|
|
83
|
+
|
|
65
84
|
## Supported diagram types
|
|
66
85
|
|
|
67
86
|
Embedded in fenced code blocks inside your Markdown:
|
package/bin/md2doc.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
const { spawnSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const LIB = path.resolve(__dirname, '..', 'lib', 'md2doc.js');
|
|
11
|
+
const VERSION = require('../package.json').version;
|
|
12
|
+
|
|
13
|
+
function printHelp() {
|
|
14
|
+
process.stdout.write([
|
|
15
|
+
'md2doc — render Markdown to HTML / PDF (WaveDrom / Mermaid / Graphviz supported)',
|
|
16
|
+
'',
|
|
17
|
+
'Usage:',
|
|
18
|
+
' md2doc <input.md>... Render to HTML, write to OS temp dir, open viewer',
|
|
19
|
+
' md2doc --pdf <input.md>... Render to PDF instead',
|
|
20
|
+
' md2doc --html --pdf <input.md> Render both formats',
|
|
21
|
+
' md2doc <input.md> --out <file.html> Write to a specific file (no auto-open)',
|
|
22
|
+
' md2doc <input.md>... --out <dir>/ Write each to <dir>/<stem>.html (no auto-open)',
|
|
23
|
+
'',
|
|
24
|
+
'Flags:',
|
|
25
|
+
' --html Render HTML (default if neither --html nor --pdf is given).',
|
|
26
|
+
' --pdf Render PDF.',
|
|
27
|
+
' --out <path> Output path. Ends with \'/\' or existing dir → directory mode.',
|
|
28
|
+
' Ends with .html/.pdf → file mode (single input only).',
|
|
29
|
+
' Implies --no-open unless --open is also passed.',
|
|
30
|
+
' --open Launch the platform viewer after render (default when --out is absent).',
|
|
31
|
+
' --no-open Skip the viewer launch.',
|
|
32
|
+
' --quiet Suppress per-file progress messages.',
|
|
33
|
+
' --version, -v Print version.',
|
|
34
|
+
' --help, -h Print this help.',
|
|
35
|
+
''
|
|
36
|
+
].join('\n'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv) {
|
|
40
|
+
const inputs = [];
|
|
41
|
+
let html = false;
|
|
42
|
+
let pdf = false;
|
|
43
|
+
let out = null;
|
|
44
|
+
let openExplicit = null; // null = unset; true/false = user-specified
|
|
45
|
+
let quiet = false;
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < argv.length; i++) {
|
|
48
|
+
const a = argv[i];
|
|
49
|
+
if (a === '--version' || a === '-v') {
|
|
50
|
+
process.stdout.write(VERSION + '\n');
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
if (a === '--help' || a === '-h') {
|
|
54
|
+
printHelp();
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
if (a === '--html') { html = true; continue; }
|
|
58
|
+
if (a === '--pdf') { pdf = true; continue; }
|
|
59
|
+
if (a === '--open') { openExplicit = true; continue; }
|
|
60
|
+
if (a === '--no-open') { openExplicit = false; continue; }
|
|
61
|
+
if (a === '--quiet') { quiet = true; continue; }
|
|
62
|
+
if (a === '--out') {
|
|
63
|
+
if (out !== null) {
|
|
64
|
+
process.stderr.write('error: --out specified more than once\n');
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
i++;
|
|
68
|
+
if (i >= argv.length) {
|
|
69
|
+
process.stderr.write('error: --out requires a value\n');
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
out = argv[i];
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (a.startsWith('-')) {
|
|
76
|
+
process.stderr.write('error: unknown flag ' + a + '\n');
|
|
77
|
+
process.exit(2);
|
|
78
|
+
}
|
|
79
|
+
inputs.push(a);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (inputs.length === 0) {
|
|
83
|
+
process.stderr.write('error: no input file\n');
|
|
84
|
+
printHelp();
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Format defaults: neither flag → HTML only.
|
|
89
|
+
const formats = [];
|
|
90
|
+
if (html || (!html && !pdf)) formats.push('html');
|
|
91
|
+
if (pdf) formats.push('pdf');
|
|
92
|
+
|
|
93
|
+
// Open default: ON when --out absent, OFF when --out present; user can override either way.
|
|
94
|
+
let open;
|
|
95
|
+
if (openExplicit !== null) {
|
|
96
|
+
open = openExplicit;
|
|
97
|
+
} else {
|
|
98
|
+
open = (out === null);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { inputs, formats, out, open, quiet };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function shortHash(absPath) {
|
|
105
|
+
return crypto.createHash('sha1').update(absPath).digest('hex').slice(0, 6);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function defaultOutputPath(input, format) {
|
|
109
|
+
const abs = path.resolve(input);
|
|
110
|
+
const stem = path.basename(input).replace(/\.md$/i, '');
|
|
111
|
+
const dir = path.join(os.tmpdir(), 'md2doc');
|
|
112
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
113
|
+
return path.join(dir, stem + '-' + shortHash(abs) + '.' + format);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function classifyOut(outValue) {
|
|
117
|
+
// Returns { kind: 'dir' | 'file', ext: 'html'|'pdf'|null }
|
|
118
|
+
if (outValue.endsWith('/') || outValue.endsWith(path.sep)) {
|
|
119
|
+
return { kind: 'dir', ext: null };
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
if (fs.existsSync(outValue) && fs.statSync(outValue).isDirectory()) {
|
|
123
|
+
return { kind: 'dir', ext: null };
|
|
124
|
+
}
|
|
125
|
+
} catch (_) { /* fall through */ }
|
|
126
|
+
if (/\.html$/i.test(outValue)) return { kind: 'file', ext: 'html' };
|
|
127
|
+
if (/\.pdf$/i.test(outValue)) return { kind: 'file', ext: 'pdf' };
|
|
128
|
+
return { kind: 'ambiguous', ext: null };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveOutputs(args) {
|
|
132
|
+
const pairs = [];
|
|
133
|
+
if (args.out === null) {
|
|
134
|
+
for (const input of args.inputs) {
|
|
135
|
+
for (const format of args.formats) {
|
|
136
|
+
pairs.push({ input, format, output: defaultOutputPath(input, format) });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return pairs;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const cls = classifyOut(args.out);
|
|
143
|
+
|
|
144
|
+
if (cls.kind === 'ambiguous') {
|
|
145
|
+
process.stderr.write(
|
|
146
|
+
'error: --out \'' + args.out + '\' must end with \'/\' to mean a directory ' +
|
|
147
|
+
'or \'.html\'/\'.pdf\' to mean a file\n'
|
|
148
|
+
);
|
|
149
|
+
process.exit(2);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (cls.kind === 'file') {
|
|
153
|
+
if (args.inputs.length > 1) {
|
|
154
|
+
process.stderr.write('error: --out file path is only valid with one input\n');
|
|
155
|
+
process.exit(2);
|
|
156
|
+
}
|
|
157
|
+
if (args.formats.length > 1) {
|
|
158
|
+
process.stderr.write('error: --out file path is not valid when producing both formats\n');
|
|
159
|
+
process.exit(2);
|
|
160
|
+
}
|
|
161
|
+
if (cls.ext !== args.formats[0]) {
|
|
162
|
+
process.stderr.write(
|
|
163
|
+
'error: --out \'' + args.out + '\' extension does not match selected format\n'
|
|
164
|
+
);
|
|
165
|
+
process.exit(2);
|
|
166
|
+
}
|
|
167
|
+
pairs.push({ input: args.inputs[0], format: args.formats[0], output: args.out });
|
|
168
|
+
return pairs;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// cls.kind === 'dir'
|
|
172
|
+
fs.mkdirSync(args.out, { recursive: true });
|
|
173
|
+
for (const input of args.inputs) {
|
|
174
|
+
const stem = path.basename(input).replace(/\.md$/i, '');
|
|
175
|
+
for (const format of args.formats) {
|
|
176
|
+
pairs.push({
|
|
177
|
+
input,
|
|
178
|
+
format,
|
|
179
|
+
output: path.join(args.out, stem + '.' + format),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return pairs;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isWSL() {
|
|
187
|
+
if (process.platform !== 'linux') return false;
|
|
188
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
189
|
+
try {
|
|
190
|
+
return fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop');
|
|
191
|
+
} catch (_) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function openViewer(filePath) {
|
|
197
|
+
const platform = process.platform;
|
|
198
|
+
let cmd, args;
|
|
199
|
+
if (platform === 'darwin') {
|
|
200
|
+
cmd = 'open'; args = [filePath];
|
|
201
|
+
} else if (platform === 'win32') {
|
|
202
|
+
cmd = 'cmd'; args = ['/c', 'start', '""', filePath];
|
|
203
|
+
} else if (isWSL()) {
|
|
204
|
+
const r = spawnSync('wslpath', ['-w', filePath], { encoding: 'utf8' });
|
|
205
|
+
if (r.status === 0 && r.stdout) {
|
|
206
|
+
cmd = 'explorer.exe'; args = [r.stdout.trim()];
|
|
207
|
+
} else {
|
|
208
|
+
cmd = 'xdg-open'; args = [filePath];
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
cmd = 'xdg-open'; args = [filePath];
|
|
212
|
+
}
|
|
213
|
+
const r = spawnSync(cmd, args, { stdio: 'ignore' });
|
|
214
|
+
if (r.error) {
|
|
215
|
+
process.stderr.write('warning: could not launch viewer for ' + filePath + ': ' + r.error.message + '\n');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function main() {
|
|
220
|
+
const args = parseArgs(process.argv.slice(2));
|
|
221
|
+
|
|
222
|
+
for (const input of args.inputs) {
|
|
223
|
+
if (!fs.existsSync(input)) {
|
|
224
|
+
process.stderr.write('error: input not found: ' + input + '\n');
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const pairs = resolveOutputs(args);
|
|
230
|
+
const outputs = [];
|
|
231
|
+
for (const { input, output } of pairs) {
|
|
232
|
+
const stdio = args.quiet ? ['inherit', 'ignore', 'inherit'] : 'inherit';
|
|
233
|
+
const r = spawnSync(process.execPath, [LIB, input, output], { stdio });
|
|
234
|
+
if (r.status !== 0) {
|
|
235
|
+
process.stderr.write('error: render failed for ' + input + ' (exit ' + r.status + ')\n');
|
|
236
|
+
process.exit(r.status || 1);
|
|
237
|
+
}
|
|
238
|
+
if (!args.quiet) {
|
|
239
|
+
process.stdout.write(output + '\n');
|
|
240
|
+
}
|
|
241
|
+
outputs.push(output);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (args.open) {
|
|
245
|
+
for (const o of outputs) openViewer(o);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
main();
|
package/lib/md2doc.js
CHANGED
|
@@ -838,7 +838,15 @@ const html = `<!DOCTYPE html>
|
|
|
838
838
|
}
|
|
839
839
|
.content p,
|
|
840
840
|
.content li,
|
|
841
|
-
.content blockquote
|
|
841
|
+
.content blockquote,
|
|
842
|
+
.content h1,
|
|
843
|
+
.content h2,
|
|
844
|
+
.content h3,
|
|
845
|
+
.content h4,
|
|
846
|
+
.content h5,
|
|
847
|
+
.content h6,
|
|
848
|
+
.content dt,
|
|
849
|
+
.content dd {
|
|
842
850
|
overflow-wrap: anywhere;
|
|
843
851
|
word-break: break-word;
|
|
844
852
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helping-ai-workflow/md2doc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, and Graphviz support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"main": "lib/md2doc.js",
|
|
15
15
|
"bin": {
|
|
16
|
-
"
|
|
17
|
-
"md2pdf": "bin/md2pdf.js"
|
|
16
|
+
"md2doc": "bin/md2doc.js"
|
|
18
17
|
},
|
|
19
18
|
"files": [
|
|
20
19
|
"lib/",
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
},
|
|
33
32
|
"scripts": {
|
|
34
33
|
"preinstall": "node scripts/preinstall.js",
|
|
35
|
-
"test": "node test/md2doc.test.js"
|
|
34
|
+
"test": "node test/md2doc.test.js && node test/cli.test.js"
|
|
36
35
|
},
|
|
37
36
|
"repository": {
|
|
38
37
|
"type": "git",
|
package/bin/md2html.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const { spawnSync } = require('child_process');
|
|
7
|
-
|
|
8
|
-
const FORMAT = 'html';
|
|
9
|
-
const LIB = path.resolve(__dirname, '..', 'lib', 'md2doc.js');
|
|
10
|
-
const VERSION = require('../package.json').version;
|
|
11
|
-
|
|
12
|
-
function printHelp() {
|
|
13
|
-
process.stdout.write([
|
|
14
|
-
`md2${FORMAT} — render Markdown to ${FORMAT.toUpperCase()} (WaveDrom / Mermaid / Graphviz supported)`,
|
|
15
|
-
'',
|
|
16
|
-
'Usage:',
|
|
17
|
-
` md2${FORMAT} <input.md> [<input2.md> ...] # render each to <stem>_gen.${FORMAT} next to source`,
|
|
18
|
-
` md2${FORMAT} <input.md> --out <path.${FORMAT}> # explicit output (single-file mode)`,
|
|
19
|
-
` md2${FORMAT} <input.md> --open # render then open viewer`,
|
|
20
|
-
` md2${FORMAT} <input.md> --quiet # suppress progress output`,
|
|
21
|
-
'',
|
|
22
|
-
'Flags:',
|
|
23
|
-
' --out <path> Explicit output path. Only valid with exactly one input.',
|
|
24
|
-
' --open Launch the platform viewer after render.',
|
|
25
|
-
' --quiet Suppress per-file progress messages.',
|
|
26
|
-
' --version, -v Print version.',
|
|
27
|
-
' --help, -h Print this help.',
|
|
28
|
-
''
|
|
29
|
-
].join('\n'));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function parseArgs(argv) {
|
|
33
|
-
const inputs = [];
|
|
34
|
-
let out = null;
|
|
35
|
-
let open = false;
|
|
36
|
-
let quiet = false;
|
|
37
|
-
|
|
38
|
-
for (let i = 0; i < argv.length; i++) {
|
|
39
|
-
const a = argv[i];
|
|
40
|
-
if (a === '--version' || a === '-v') {
|
|
41
|
-
process.stdout.write(VERSION + '\n');
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
if (a === '--help' || a === '-h') {
|
|
45
|
-
printHelp();
|
|
46
|
-
process.exit(0);
|
|
47
|
-
}
|
|
48
|
-
if (a === '--out') {
|
|
49
|
-
if (out !== null) {
|
|
50
|
-
process.stderr.write('error: --out specified more than once\n');
|
|
51
|
-
process.exit(2);
|
|
52
|
-
}
|
|
53
|
-
i++;
|
|
54
|
-
if (i >= argv.length) {
|
|
55
|
-
process.stderr.write('error: --out requires a value\n');
|
|
56
|
-
process.exit(2);
|
|
57
|
-
}
|
|
58
|
-
out = argv[i];
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (a === '--open') { open = true; continue; }
|
|
62
|
-
if (a === '--quiet') { quiet = true; continue; }
|
|
63
|
-
if (a.startsWith('-')) {
|
|
64
|
-
process.stderr.write(`error: unknown flag ${a}\n`);
|
|
65
|
-
process.exit(2);
|
|
66
|
-
}
|
|
67
|
-
inputs.push(a);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (inputs.length === 0) {
|
|
71
|
-
printHelp();
|
|
72
|
-
process.exit(2);
|
|
73
|
-
}
|
|
74
|
-
if (out !== null && inputs.length !== 1) {
|
|
75
|
-
process.stderr.write('error: --out is only valid with exactly one input file\n');
|
|
76
|
-
process.exit(2);
|
|
77
|
-
}
|
|
78
|
-
return { inputs, out, open, quiet };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function deriveOutput(input, format) {
|
|
82
|
-
// Default suffix matches the workspace Makefile convention: <stem>_gen.<format>.
|
|
83
|
-
const stem = input.replace(/\.md$/i, '');
|
|
84
|
-
if (stem === input) {
|
|
85
|
-
// No .md extension — append _gen.<format>.
|
|
86
|
-
return input + '_gen.' + format;
|
|
87
|
-
}
|
|
88
|
-
return stem + '_gen.' + format;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function isWSL() {
|
|
92
|
-
if (process.platform !== 'linux') return false;
|
|
93
|
-
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
94
|
-
try {
|
|
95
|
-
return fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop');
|
|
96
|
-
} catch (_) {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function openViewer(filePath) {
|
|
102
|
-
const platform = process.platform;
|
|
103
|
-
let cmd, args;
|
|
104
|
-
if (platform === 'darwin') {
|
|
105
|
-
cmd = 'open'; args = [filePath];
|
|
106
|
-
} else if (platform === 'win32') {
|
|
107
|
-
cmd = 'cmd'; args = ['/c', 'start', '""', filePath];
|
|
108
|
-
} else if (isWSL()) {
|
|
109
|
-
// Convert the WSL Linux path to a Windows-side path then launch via explorer.exe,
|
|
110
|
-
// which uses Windows file associations (.html → default browser, .pdf → default reader).
|
|
111
|
-
const r = spawnSync('wslpath', ['-w', filePath], { encoding: 'utf8' });
|
|
112
|
-
if (r.status === 0 && r.stdout) {
|
|
113
|
-
cmd = 'explorer.exe'; args = [r.stdout.trim()];
|
|
114
|
-
} else {
|
|
115
|
-
cmd = 'xdg-open'; args = [filePath];
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
cmd = 'xdg-open'; args = [filePath];
|
|
119
|
-
}
|
|
120
|
-
const r = spawnSync(cmd, args, { stdio: 'ignore' });
|
|
121
|
-
// explorer.exe returns exit code 1 on success (Windows quirk via WSL interop),
|
|
122
|
-
// so a non-zero exit is not necessarily a failure when launching via explorer.
|
|
123
|
-
if (r.error) {
|
|
124
|
-
process.stderr.write(`warning: could not launch viewer for ${filePath}: ${r.error.message}\n`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function main() {
|
|
129
|
-
const args = parseArgs(process.argv.slice(2));
|
|
130
|
-
const outputs = [];
|
|
131
|
-
|
|
132
|
-
for (const input of args.inputs) {
|
|
133
|
-
if (!fs.existsSync(input)) {
|
|
134
|
-
process.stderr.write(`error: input not found: ${input}\n`);
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
const output = (args.out !== null) ? args.out : deriveOutput(input, FORMAT);
|
|
138
|
-
// lib/md2doc.js prints its own "[FORMAT] input → output" line; redirect its stdout
|
|
139
|
-
// when --quiet so neither layer emits progress.
|
|
140
|
-
const stdio = args.quiet ? ['inherit', 'ignore', 'inherit'] : 'inherit';
|
|
141
|
-
const r = spawnSync(process.execPath, [LIB, input, output], { stdio });
|
|
142
|
-
if (r.status !== 0) {
|
|
143
|
-
process.stderr.write(`error: render failed for ${input} (exit ${r.status})\n`);
|
|
144
|
-
process.exit(r.status || 1);
|
|
145
|
-
}
|
|
146
|
-
outputs.push(output);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (args.open) {
|
|
150
|
-
for (const o of outputs) {
|
|
151
|
-
openViewer(o);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
main();
|
package/bin/md2pdf.js
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const { spawnSync } = require('child_process');
|
|
7
|
-
|
|
8
|
-
const FORMAT = 'pdf';
|
|
9
|
-
const LIB = path.resolve(__dirname, '..', 'lib', 'md2doc.js');
|
|
10
|
-
const VERSION = require('../package.json').version;
|
|
11
|
-
|
|
12
|
-
function printHelp() {
|
|
13
|
-
process.stdout.write([
|
|
14
|
-
`md2${FORMAT} — render Markdown to ${FORMAT.toUpperCase()} (WaveDrom / Mermaid / Graphviz supported)`,
|
|
15
|
-
'',
|
|
16
|
-
'Usage:',
|
|
17
|
-
` md2${FORMAT} <input.md> [<input2.md> ...] # render each to <stem>_gen.${FORMAT} next to source`,
|
|
18
|
-
` md2${FORMAT} <input.md> --out <path.${FORMAT}> # explicit output (single-file mode)`,
|
|
19
|
-
` md2${FORMAT} <input.md> --open # render then open viewer`,
|
|
20
|
-
` md2${FORMAT} <input.md> --quiet # suppress progress output`,
|
|
21
|
-
'',
|
|
22
|
-
'Flags:',
|
|
23
|
-
' --out <path> Explicit output path. Only valid with exactly one input.',
|
|
24
|
-
' --open Launch the platform viewer after render.',
|
|
25
|
-
' --quiet Suppress per-file progress messages.',
|
|
26
|
-
' --version, -v Print version.',
|
|
27
|
-
' --help, -h Print this help.',
|
|
28
|
-
''
|
|
29
|
-
].join('\n'));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function parseArgs(argv) {
|
|
33
|
-
const inputs = [];
|
|
34
|
-
let out = null;
|
|
35
|
-
let open = false;
|
|
36
|
-
let quiet = false;
|
|
37
|
-
|
|
38
|
-
for (let i = 0; i < argv.length; i++) {
|
|
39
|
-
const a = argv[i];
|
|
40
|
-
if (a === '--version' || a === '-v') {
|
|
41
|
-
process.stdout.write(VERSION + '\n');
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
if (a === '--help' || a === '-h') {
|
|
45
|
-
printHelp();
|
|
46
|
-
process.exit(0);
|
|
47
|
-
}
|
|
48
|
-
if (a === '--out') {
|
|
49
|
-
if (out !== null) {
|
|
50
|
-
process.stderr.write('error: --out specified more than once\n');
|
|
51
|
-
process.exit(2);
|
|
52
|
-
}
|
|
53
|
-
i++;
|
|
54
|
-
if (i >= argv.length) {
|
|
55
|
-
process.stderr.write('error: --out requires a value\n');
|
|
56
|
-
process.exit(2);
|
|
57
|
-
}
|
|
58
|
-
out = argv[i];
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (a === '--open') { open = true; continue; }
|
|
62
|
-
if (a === '--quiet') { quiet = true; continue; }
|
|
63
|
-
if (a.startsWith('-')) {
|
|
64
|
-
process.stderr.write(`error: unknown flag ${a}\n`);
|
|
65
|
-
process.exit(2);
|
|
66
|
-
}
|
|
67
|
-
inputs.push(a);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (inputs.length === 0) {
|
|
71
|
-
printHelp();
|
|
72
|
-
process.exit(2);
|
|
73
|
-
}
|
|
74
|
-
if (out !== null && inputs.length !== 1) {
|
|
75
|
-
process.stderr.write('error: --out is only valid with exactly one input file\n');
|
|
76
|
-
process.exit(2);
|
|
77
|
-
}
|
|
78
|
-
return { inputs, out, open, quiet };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function deriveOutput(input, format) {
|
|
82
|
-
// Default suffix matches the workspace Makefile convention: <stem>_gen.<format>.
|
|
83
|
-
const stem = input.replace(/\.md$/i, '');
|
|
84
|
-
if (stem === input) {
|
|
85
|
-
return input + '_gen.' + format;
|
|
86
|
-
}
|
|
87
|
-
return stem + '_gen.' + format;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isWSL() {
|
|
91
|
-
if (process.platform !== 'linux') return false;
|
|
92
|
-
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
93
|
-
try {
|
|
94
|
-
return fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop');
|
|
95
|
-
} catch (_) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function openViewer(filePath) {
|
|
101
|
-
const platform = process.platform;
|
|
102
|
-
let cmd, args;
|
|
103
|
-
if (platform === 'darwin') {
|
|
104
|
-
cmd = 'open'; args = [filePath];
|
|
105
|
-
} else if (platform === 'win32') {
|
|
106
|
-
cmd = 'cmd'; args = ['/c', 'start', '""', filePath];
|
|
107
|
-
} else if (isWSL()) {
|
|
108
|
-
// Convert the WSL Linux path to a Windows-side path then launch via explorer.exe,
|
|
109
|
-
// which uses Windows file associations (.html → default browser, .pdf → default reader).
|
|
110
|
-
const r = spawnSync('wslpath', ['-w', filePath], { encoding: 'utf8' });
|
|
111
|
-
if (r.status === 0 && r.stdout) {
|
|
112
|
-
cmd = 'explorer.exe'; args = [r.stdout.trim()];
|
|
113
|
-
} else {
|
|
114
|
-
cmd = 'xdg-open'; args = [filePath];
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
cmd = 'xdg-open'; args = [filePath];
|
|
118
|
-
}
|
|
119
|
-
const r = spawnSync(cmd, args, { stdio: 'ignore' });
|
|
120
|
-
if (r.error) {
|
|
121
|
-
process.stderr.write(`warning: could not launch viewer for ${filePath}: ${r.error.message}\n`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function main() {
|
|
126
|
-
const args = parseArgs(process.argv.slice(2));
|
|
127
|
-
const outputs = [];
|
|
128
|
-
|
|
129
|
-
for (const input of args.inputs) {
|
|
130
|
-
if (!fs.existsSync(input)) {
|
|
131
|
-
process.stderr.write(`error: input not found: ${input}\n`);
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
134
|
-
const output = (args.out !== null) ? args.out : deriveOutput(input, FORMAT);
|
|
135
|
-
// lib/md2doc.js prints its own "[FORMAT] input → output" line; redirect its stdout
|
|
136
|
-
// when --quiet so neither layer emits progress.
|
|
137
|
-
const stdio = args.quiet ? ['inherit', 'ignore', 'inherit'] : 'inherit';
|
|
138
|
-
const r = spawnSync(process.execPath, [LIB, input, output], { stdio });
|
|
139
|
-
if (r.status !== 0) {
|
|
140
|
-
process.stderr.write(`error: render failed for ${input} (exit ${r.status})\n`);
|
|
141
|
-
process.exit(r.status || 1);
|
|
142
|
-
}
|
|
143
|
-
outputs.push(output);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (args.open) {
|
|
147
|
-
for (const o of outputs) {
|
|
148
|
-
openViewer(o);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
main();
|