memd-cli 3.2.1 → 3.3.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/CLAUDE.md +4 -2
- package/main.js +31 -7
- package/package.json +1 -1
- package/test/memd.test.js +3 -3
- package/.claude/settings.local.json +0 -28
package/CLAUDE.md
CHANGED
|
@@ -41,7 +41,8 @@ node main.js test/test1.md
|
|
|
41
41
|
node main.js test/test2.md
|
|
42
42
|
node main.js test/complex.md
|
|
43
43
|
node main.js --html test/test1.md # HTML output to stdout
|
|
44
|
-
node main.js serve
|
|
44
|
+
node main.js serve test --port 3000 # HTTP serve mode (directory)
|
|
45
|
+
node main.js serve test/test1.md # HTTP serve mode (single file)
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
## Key CLI flags
|
|
@@ -70,4 +71,5 @@ nord-light, zinc-dark, zinc-light
|
|
|
70
71
|
- `render-shared.js` converts Mermaid fenced blocks to SVG via `@ktrysmt/beautiful-mermaid`, then renders full HTML with `marked`
|
|
71
72
|
- `render-worker.js` runs `renderToHTML` in a worker thread for non-blocking serve mode
|
|
72
73
|
- `render-utils.js` provides theme color resolution and HTML escaping (shared by main.js and render-shared.js)
|
|
73
|
-
- Serve mode supports: directory listing, ETag/304 caching, gzip, static file serving (images/css), sidebar navigation, `--watch` with SSE live reload, CSP nonce
|
|
74
|
+
- Serve mode supports: directory or single-file serving, directory listing, ETag/304 caching, gzip, static file serving (images/css), sidebar navigation, `--watch` with SSE live reload, CSP nonce
|
|
75
|
+
- Single-file mode (`serve foo.md`): sets baseDir to the file's parent directory, root `/` redirects to `/<filename>`
|
package/main.js
CHANGED
|
@@ -707,13 +707,13 @@ async function main() {
|
|
|
707
707
|
program
|
|
708
708
|
.command('serve')
|
|
709
709
|
.description('Start HTTP server to serve .md files as HTML')
|
|
710
|
-
.
|
|
710
|
+
.argument('[path]', 'directory or .md file to serve', '.')
|
|
711
711
|
.option('-p, --port <number>', 'port number (0-65535)', Number, 8888)
|
|
712
712
|
.option('--host <string>', 'host to bind', '127.0.0.1')
|
|
713
713
|
.option('--workers <number>', 'number of render workers (default: min(cpus-1, 4))', Number)
|
|
714
714
|
.option('--watch', 'watch for file changes and live-reload')
|
|
715
715
|
.option('--theme <name>', `color theme (env: MEMD_THEME)\n${THEME_NAMES.join(', ')}`, process.env.MEMD_THEME || 'nord')
|
|
716
|
-
.action(async (options) => {
|
|
716
|
+
.action(async (servePath, options) => {
|
|
717
717
|
if (!(options.theme in THEME_MAP)) {
|
|
718
718
|
const names = Object.keys(THEME_MAP).join(', ');
|
|
719
719
|
console.error(`Unknown theme: ${options.theme}\nAvailable themes: ${names}`);
|
|
@@ -732,14 +732,26 @@ async function main() {
|
|
|
732
732
|
}
|
|
733
733
|
|
|
734
734
|
let baseDir;
|
|
735
|
+
let singleFile = null; // basename of .md file in single-file mode
|
|
736
|
+
let resolvedServePath;
|
|
735
737
|
try {
|
|
736
|
-
|
|
738
|
+
resolvedServePath = fs.realpathSync(path.resolve(servePath));
|
|
737
739
|
} catch {
|
|
738
|
-
console.error(`
|
|
740
|
+
console.error(`Path not found: ${servePath}`);
|
|
739
741
|
process.exit(1);
|
|
740
742
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
+
const serveStat = fs.statSync(resolvedServePath);
|
|
744
|
+
if (serveStat.isFile()) {
|
|
745
|
+
if (!resolvedServePath.endsWith('.md')) {
|
|
746
|
+
console.error(`Not a .md file: ${servePath}`);
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
singleFile = path.basename(resolvedServePath);
|
|
750
|
+
baseDir = path.dirname(resolvedServePath);
|
|
751
|
+
} else if (serveStat.isDirectory()) {
|
|
752
|
+
baseDir = resolvedServePath;
|
|
753
|
+
} else {
|
|
754
|
+
console.error(`Not a file or directory: ${servePath}`);
|
|
743
755
|
process.exit(1);
|
|
744
756
|
}
|
|
745
757
|
if (baseDir === '/') {
|
|
@@ -1069,6 +1081,14 @@ body:has(.memd-layout) { max-width: none; margin: 0; padding: 0; }
|
|
|
1069
1081
|
return;
|
|
1070
1082
|
}
|
|
1071
1083
|
|
|
1084
|
+
// Single-file mode: redirect root to the served file
|
|
1085
|
+
if (singleFile && urlPath === '/') {
|
|
1086
|
+
const target = '/' + encodeURIComponent(singleFile) + parsedUrl.search;
|
|
1087
|
+
res.writeHead(302, { Location: target });
|
|
1088
|
+
res.end();
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1072
1092
|
if (isDotPath(urlPath)) {
|
|
1073
1093
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
1074
1094
|
res.end('Forbidden');
|
|
@@ -1277,7 +1297,11 @@ body:has(.memd-layout) { max-width: none; margin: 0; padding: 0; }
|
|
|
1277
1297
|
let displayHost = options.host === '0.0.0.0' || options.host === '::' ? 'localhost' : options.host;
|
|
1278
1298
|
if (displayHost.includes(':')) displayHost = `[${displayHost}]`;
|
|
1279
1299
|
console.log(`memd serve`);
|
|
1280
|
-
|
|
1300
|
+
if (singleFile) {
|
|
1301
|
+
console.log(` File: ${path.join(baseDir, singleFile)}`);
|
|
1302
|
+
} else {
|
|
1303
|
+
console.log(` Directory: ${baseDir}`);
|
|
1304
|
+
}
|
|
1281
1305
|
console.log(` Theme: ${options.theme}`);
|
|
1282
1306
|
if (options.watch) console.log(' Watch: enabled');
|
|
1283
1307
|
console.log(` URL: http://${displayHost}:${addr.port}/`);
|
package/package.json
CHANGED
package/test/memd.test.js
CHANGED
|
@@ -416,7 +416,7 @@ describe('memd serve', () => {
|
|
|
416
416
|
let serverProcess
|
|
417
417
|
|
|
418
418
|
beforeAll(async () => {
|
|
419
|
-
serverProcess = spawn('node', [MAIN, 'serve', '--port', String(PORT), '--
|
|
419
|
+
serverProcess = spawn('node', [MAIN, 'serve', __dirname, '--port', String(PORT), '--host', '127.0.0.1'], {
|
|
420
420
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
421
421
|
})
|
|
422
422
|
await new Promise((resolve, reject) => {
|
|
@@ -444,7 +444,7 @@ describe('memd serve', () => {
|
|
|
444
444
|
|
|
445
445
|
it('serve --help shows options', () => {
|
|
446
446
|
const output = runSync(['serve', '--help'])
|
|
447
|
-
expect(output).toContain('
|
|
447
|
+
expect(output).toContain('[path]')
|
|
448
448
|
expect(output).toContain('--port')
|
|
449
449
|
expect(output).toContain('--host')
|
|
450
450
|
expect(output).toContain('--watch')
|
|
@@ -655,7 +655,7 @@ describe('memd serve --watch', () => {
|
|
|
655
655
|
let watchProcess
|
|
656
656
|
|
|
657
657
|
beforeAll(async () => {
|
|
658
|
-
watchProcess = spawn('node', [MAIN, 'serve', '--port', String(WATCH_PORT), '--
|
|
658
|
+
watchProcess = spawn('node', [MAIN, 'serve', __dirname, '--port', String(WATCH_PORT), '--host', '127.0.0.1', '--watch'], {
|
|
659
659
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
660
660
|
})
|
|
661
661
|
await new Promise((resolve, reject) => {
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(echo:*)",
|
|
5
|
-
"Bash(FORCE_COLOR=1 echo:*)",
|
|
6
|
-
"Bash(script:*)",
|
|
7
|
-
"Bash(FORCE_COLOR=1 node:*)",
|
|
8
|
-
"Bash(node -e \"import\\('beautiful-mermaid/src/ascii/ansi.ts'\\).then\\(m=>console.log\\(Object.keys\\(m\\)\\)\\).catch\\(e=>console.error\\('ERROR:',e.message\\)\\)\" 2>&1 | head -5)",
|
|
9
|
-
"Bash(node main.js test/test-highlight.md --no-pager --theme catppuccin-mocha 2>&1 | head -3 | xxd | head -10)",
|
|
10
|
-
"Bash(FORCE_COLOR=1 node main.js test/test-highlight.md --no-pager --theme catppuccin-mocha 2>&1 | head -3 | xxd | head -5)",
|
|
11
|
-
"Bash(FORCE_COLOR=1 node main.js test/test-highlight.md --no-pager --theme zinc-dark 2>&1 | head -3 | xxd | head -5)",
|
|
12
|
-
"mcp__grep-github__searchGitHub",
|
|
13
|
-
"Bash(npx npm-check-updates:*)",
|
|
14
|
-
"Bash(MEMD_THEME= pnpm test 2>&1 | tail -20)",
|
|
15
|
-
"Bash(MEMD_THEME= npx vitest run 2>&1)",
|
|
16
|
-
"Bash(node:*)",
|
|
17
|
-
"Bash(printf:*)",
|
|
18
|
-
"WebFetch(domain:registry.npmjs.org)",
|
|
19
|
-
"WebFetch(domain:api.github.com)",
|
|
20
|
-
"Bash(mkdir:*)",
|
|
21
|
-
"Bash(# Restore original dist\ncp node_modules/@ktrysmt/beautiful-mermaid/dist/index.js.bak node_modules/@ktrysmt/beautiful-mermaid/dist/index.js && rm node_modules/@ktrysmt/beautiful-mermaid/dist/index.js.bak && rm -f debug-mermaid*.mjs bm-debug.mjs)",
|
|
22
|
-
"Bash(cp dist/index.js.bak dist/index.js)",
|
|
23
|
-
"Bash(rm -f dist/index.js.debug)",
|
|
24
|
-
"Bash(rm -f pnpm-lock.yaml)",
|
|
25
|
-
"Bash(rm -rf node_modules)"
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
}
|