marp-dev-preview 0.3.1 → 0.3.3
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 +44 -4
- package/package.json +1 -1
- package/src/args.mjs +33 -2
- package/src/marp-dev-preview.mjs +7 -2
- package/src/marp-utils.mjs +1 -1
- package/src/server.mjs +4 -1
- package/test/args.test.mjs +54 -8
- package/test/marp-dev-preview.test.mjs +22 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Originally built as a dependency for the [marp-dev-preview.nvim](https://github.
|
|
|
27
27
|
The simplest way to run the previewer:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npx marp-dev-preview --theme-
|
|
30
|
+
npx marp-dev-preview --markdown-file <presentation.md> --theme-set <themes-dir>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
---
|
|
@@ -53,19 +53,59 @@ npm install marp-dev-preview
|
|
|
53
53
|
Start the preview server with:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
mdp
|
|
56
|
+
mdp [options]
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
**Example:**
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
mdp my-slides/presentation.md --port 3000 --theme-
|
|
62
|
+
mdp --markdown-file my-slides/presentation.md --port 3000 --theme-set my-themes
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
### Options
|
|
66
66
|
|
|
67
|
-
- `-
|
|
67
|
+
- `-m, --markdown-file <path>` — Path to the markdown file to preview
|
|
68
|
+
- `-t, --theme-set <path...>` — One or more directories containing custom Marp themes (CSS files)
|
|
69
|
+
- `--theme-dir <path...>` — Alias for `--theme-set`
|
|
68
70
|
- `-p, --port <number>` — Port for the preview server (default: `8080`)
|
|
71
|
+
- `-c, --containers <name...>` — Container names to register for `markdown-it-container`
|
|
72
|
+
- `-v, --verbose` — Enable verbose logging
|
|
73
|
+
- `--config <path>` — Path to a JSON config file (default: `.mp-config.json`)
|
|
74
|
+
- `--example-config` — Print an example JSON config file and exit
|
|
75
|
+
|
|
76
|
+
### Configuration File
|
|
77
|
+
|
|
78
|
+
Options can be loaded from a JSON config file. If `.mp-config.json` is present in the current working directory, it is read automatically without passing `--config`. Command-line flags override values from that file.
|
|
79
|
+
|
|
80
|
+
Example `.mp-config.json`:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"markdown-file": "my-slides/presentation.md",
|
|
85
|
+
"theme-set": ["my-themes"],
|
|
86
|
+
"port": 3000,
|
|
87
|
+
"verbose": true,
|
|
88
|
+
"containers": ["note", "info", "warn", "important"]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Run and auto-load `.mp-config.json` if it exists:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
mdp
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Or point to a different file:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
mdp --config ./preview-config.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Print an example config file:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
mdp --example-config
|
|
108
|
+
```
|
|
69
109
|
|
|
70
110
|
---
|
|
71
111
|
|
package/package.json
CHANGED
package/src/args.mjs
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import yargs from 'yargs';
|
|
2
2
|
import { hideBin } from 'yargs/helpers';
|
|
3
3
|
|
|
4
|
+
export const exampleConfig = {
|
|
5
|
+
'markdown-file': 'my-slides/presentation.md',
|
|
6
|
+
'theme-set': ['my-themes'],
|
|
7
|
+
port: 3000,
|
|
8
|
+
verbose: true,
|
|
9
|
+
containers: ['note', 'info', 'warn', 'important']
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function toCamelCase(name) {
|
|
13
|
+
return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
14
|
+
}
|
|
15
|
+
|
|
4
16
|
export function parseArgs() {
|
|
5
17
|
return yargs(hideBin(process.argv))
|
|
6
18
|
.usage('Usage: $0 [options]')
|
|
19
|
+
.parserConfiguration({
|
|
20
|
+
'strip-aliased': true,
|
|
21
|
+
})
|
|
22
|
+
.example('$0 --markdown-file slides.md --theme-set themes', 'Preview a markdown deck with custom themes')
|
|
23
|
+
.example('$0', 'Automatically load .mp-config.json when it is present')
|
|
24
|
+
.example('$0 --config preview-config.json', 'Load options from a specific JSON config file')
|
|
25
|
+
.example('$0 --example-config', 'Print an example .mp-config.json and exit')
|
|
7
26
|
.option('markdown-file', {
|
|
8
27
|
alias: 'm',
|
|
9
28
|
describe: 'Path to the markdown file to preview',
|
|
@@ -16,7 +35,7 @@ export function parseArgs() {
|
|
|
16
35
|
default: ["note", "info", "warn", "important"]
|
|
17
36
|
})
|
|
18
37
|
.option('theme-set', {
|
|
19
|
-
alias: 't',
|
|
38
|
+
alias: ['t', 'theme-dir'],
|
|
20
39
|
describe: 'Directories for custom themes',
|
|
21
40
|
type: 'array'
|
|
22
41
|
})
|
|
@@ -32,7 +51,19 @@ export function parseArgs() {
|
|
|
32
51
|
type: 'boolean',
|
|
33
52
|
default: false
|
|
34
53
|
})
|
|
35
|
-
.
|
|
54
|
+
.option('example-config', {
|
|
55
|
+
describe: 'Print an example JSON config file and exit',
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
default: false
|
|
58
|
+
})
|
|
59
|
+
.config('config', 'Path to a JSON config file. .mp-config.json is loaded automatically when present')
|
|
36
60
|
.default('config', '.mp-config.json')
|
|
61
|
+
.middleware(argv => {
|
|
62
|
+
if (!argv.markdownFile && Array.isArray(argv._) && argv._.length > 0) {
|
|
63
|
+
[argv.markdownFile] = argv._;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
argv.exampleConfig = argv.exampleConfig ?? argv[toCamelCase('example-config')] ?? argv['example-config'] ?? false;
|
|
67
|
+
}, true)
|
|
37
68
|
.argv;
|
|
38
69
|
}
|
package/src/marp-dev-preview.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'url';
|
|
|
9
9
|
/* Sub-modules */
|
|
10
10
|
import { createServer } from './server.mjs';
|
|
11
11
|
import { initializeMarp, getMarp } from './marp-utils.mjs';
|
|
12
|
-
import { parseArgs } from './args.mjs';
|
|
12
|
+
import { exampleConfig, parseArgs } from './args.mjs';
|
|
13
13
|
|
|
14
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
15
|
const __dirname = path.dirname(__filename);
|
|
@@ -28,6 +28,11 @@ if (argv.version) {
|
|
|
28
28
|
process.exit(0);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
if (argv.exampleConfig) {
|
|
32
|
+
console.log(JSON.stringify(exampleConfig, null, 2));
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
if (verbose) {
|
|
32
37
|
console.debug = console.log;
|
|
33
38
|
} else {
|
|
@@ -168,7 +173,7 @@ if (themeSet) {
|
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
initializeMarp(themeSet, containers).then(() => {
|
|
171
|
-
const app = createServer(markdownDir, renderMarp, reload, wss, __dirname);
|
|
176
|
+
const app = createServer(markdownDir, themeSet ?? [], renderMarp, reload, wss, __dirname);
|
|
172
177
|
const server = http.createServer(app);
|
|
173
178
|
|
|
174
179
|
server.on('upgrade', (request, socket, head) => {
|
package/src/marp-utils.mjs
CHANGED
|
@@ -13,7 +13,7 @@ export function getMarp() {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
export async function initializeMarp(themeSet, containers) {
|
|
16
|
+
export async function initializeMarp(themeSet, containers = []) {
|
|
17
17
|
const options = { html: true, linkify: true, };
|
|
18
18
|
marp = new Marp(options)
|
|
19
19
|
.use(markdownItFootnote)
|
package/src/server.mjs
CHANGED
|
@@ -2,9 +2,12 @@ import express from 'express';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { promises as fs } from 'fs';
|
|
4
4
|
|
|
5
|
-
export function createServer(markdownDir, renderMarp, reload, wss, __dirname) {
|
|
5
|
+
export function createServer(markdownDir, themeDirs, renderMarp, reload, wss, __dirname) {
|
|
6
6
|
const app = express();
|
|
7
7
|
|
|
8
|
+
for (const themeDir of themeDirs) {
|
|
9
|
+
app.use(express.static(themeDir));
|
|
10
|
+
}
|
|
8
11
|
app.use(express.static(markdownDir));
|
|
9
12
|
app.use(express.text({ type: 'text/markdown' }));
|
|
10
13
|
app.use(express.json());
|
package/test/args.test.mjs
CHANGED
|
@@ -3,13 +3,20 @@ import { parseArgs } from '../src/args.mjs';
|
|
|
3
3
|
const mockArgv = {};
|
|
4
4
|
const defaultValues = {};
|
|
5
5
|
|
|
6
|
+
function mockToCamelCase(name) {
|
|
7
|
+
return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
jest.mock('yargs', () => {
|
|
7
11
|
const yargsMock = jest.fn(() => yargsMock);
|
|
8
12
|
yargsMock.usage = jest.fn().mockReturnThis();
|
|
13
|
+
yargsMock.parserConfiguration = jest.fn().mockReturnThis();
|
|
14
|
+
yargsMock.example = jest.fn().mockReturnThis();
|
|
9
15
|
yargsMock.positional = jest.fn().mockReturnThis();
|
|
10
16
|
yargsMock.option = jest.fn().mockImplementation((name, options) => {
|
|
11
17
|
if (options.default !== undefined) {
|
|
12
18
|
defaultValues[name] = options.default;
|
|
19
|
+
defaultValues[mockToCamelCase(name)] = options.default;
|
|
13
20
|
}
|
|
14
21
|
return yargsMock;
|
|
15
22
|
});
|
|
@@ -18,12 +25,25 @@ jest.mock('yargs', () => {
|
|
|
18
25
|
defaultValues[name] = value;
|
|
19
26
|
return yargsMock;
|
|
20
27
|
});
|
|
28
|
+
yargsMock.middleware = jest.fn().mockImplementation((fn) => {
|
|
29
|
+
yargsMock.__middleware = fn;
|
|
30
|
+
return yargsMock;
|
|
31
|
+
});
|
|
21
32
|
yargsMock.demandCommand = jest.fn().mockReturnThis();
|
|
22
33
|
Object.defineProperty(yargsMock, 'argv', {
|
|
23
|
-
get: () =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
get: () => {
|
|
35
|
+
const argv = {
|
|
36
|
+
...defaultValues,
|
|
37
|
+
...mockArgv,
|
|
38
|
+
};
|
|
39
|
+
if (!Array.isArray(argv._)) {
|
|
40
|
+
argv._ = [];
|
|
41
|
+
}
|
|
42
|
+
if (yargsMock.__middleware) {
|
|
43
|
+
yargsMock.__middleware(argv);
|
|
44
|
+
}
|
|
45
|
+
return argv;
|
|
46
|
+
},
|
|
27
47
|
});
|
|
28
48
|
return yargsMock;
|
|
29
49
|
});
|
|
@@ -71,14 +91,21 @@ describe('Args', () => {
|
|
|
71
91
|
it('should capture markdown file positional argument', () => {
|
|
72
92
|
mockArgv._ = ['my-presentation.md'];
|
|
73
93
|
const argv = parseArgs();
|
|
74
|
-
expect(argv.
|
|
94
|
+
expect(argv.markdownFile).toBe('my-presentation.md');
|
|
75
95
|
});
|
|
76
96
|
|
|
77
|
-
it('should return theme-
|
|
97
|
+
it('should return theme-set if specified', () => {
|
|
78
98
|
mockArgv._ = ['test.md'];
|
|
79
|
-
mockArgv.
|
|
99
|
+
mockArgv.themeSet = ['/path/to/themes'];
|
|
80
100
|
const argv = parseArgs();
|
|
81
|
-
expect(argv.
|
|
101
|
+
expect(argv.themeSet).toEqual(['/path/to/themes']);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should support the theme-dir alias', () => {
|
|
105
|
+
mockArgv._ = ['test.md'];
|
|
106
|
+
mockArgv.themeSet = ['/path/to/themes'];
|
|
107
|
+
const argv = parseArgs();
|
|
108
|
+
expect(argv.themeSet).toEqual(['/path/to/themes']);
|
|
82
109
|
});
|
|
83
110
|
|
|
84
111
|
it('should handle config file path', () => {
|
|
@@ -87,4 +114,23 @@ describe('Args', () => {
|
|
|
87
114
|
const argv = parseArgs();
|
|
88
115
|
expect(argv.config).toBe('./my-config.json');
|
|
89
116
|
});
|
|
117
|
+
|
|
118
|
+
it('should keep an explicit markdown-file option over the positional argument', () => {
|
|
119
|
+
mockArgv._ = ['positional.md'];
|
|
120
|
+
mockArgv.markdownFile = 'flag.md';
|
|
121
|
+
const argv = parseArgs();
|
|
122
|
+
expect(argv.markdownFile).toBe('flag.md');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return example-config as false by default', () => {
|
|
126
|
+
mockArgv._ = ['test.md'];
|
|
127
|
+
const argv = parseArgs();
|
|
128
|
+
expect(argv.exampleConfig).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should return example-config as true if specified', () => {
|
|
132
|
+
mockArgv.exampleConfig = true;
|
|
133
|
+
const argv = parseArgs();
|
|
134
|
+
expect(argv.exampleConfig).toBe(true);
|
|
135
|
+
});
|
|
90
136
|
});
|
|
@@ -3,20 +3,41 @@
|
|
|
3
3
|
// This is necessary because jest has issues with importing express, which is a CJS module,
|
|
4
4
|
// in a project that uses ES modules ("type": "module" in package.json).
|
|
5
5
|
import { createServer } from '../src/server.mjs';
|
|
6
|
+
import express from 'express';
|
|
6
7
|
|
|
7
8
|
describe('Server', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
express.static.mockClear();
|
|
11
|
+
});
|
|
12
|
+
|
|
8
13
|
it('should create a server', () => {
|
|
9
14
|
const markdownDir = '.';
|
|
15
|
+
const themeDirs = [];
|
|
10
16
|
const renderMarp = jest.fn();
|
|
11
17
|
const reload = jest.fn();
|
|
12
18
|
const wss = { clients: [] };
|
|
13
19
|
const __dirname = '.';
|
|
14
20
|
|
|
15
|
-
const app = createServer(markdownDir, renderMarp, reload, wss, __dirname);
|
|
21
|
+
const app = createServer(markdownDir, themeDirs, renderMarp, reload, wss, __dirname);
|
|
16
22
|
|
|
17
23
|
expect(app).toBeDefined();
|
|
18
24
|
expect(typeof app.use).toBe('function');
|
|
19
25
|
expect(typeof app.get).toBe('function');
|
|
20
26
|
expect(typeof app.post).toBe('function');
|
|
21
27
|
});
|
|
28
|
+
|
|
29
|
+
it('should mount theme directories before the markdown directory', () => {
|
|
30
|
+
const markdownDir = '/slides';
|
|
31
|
+
const themeDirs = ['/themes/a', '/themes/b'];
|
|
32
|
+
const renderMarp = jest.fn();
|
|
33
|
+
const reload = jest.fn();
|
|
34
|
+
const wss = { clients: [] };
|
|
35
|
+
const __dirname = '.';
|
|
36
|
+
|
|
37
|
+
createServer(markdownDir, themeDirs, renderMarp, reload, wss, __dirname);
|
|
38
|
+
|
|
39
|
+
expect(express.static).toHaveBeenNthCalledWith(1, '/themes/a');
|
|
40
|
+
expect(express.static).toHaveBeenNthCalledWith(2, '/themes/b');
|
|
41
|
+
expect(express.static).toHaveBeenNthCalledWith(3, '/slides');
|
|
42
|
+
});
|
|
22
43
|
});
|