@slidev/cli 51.6.0 → 51.7.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 CHANGED
@@ -1,23 +1,6 @@
1
- import {
2
- createServer
3
- } from "./chunk-BCA62OS7.js";
4
- import {
5
- getThemeMeta,
6
- loadSetups,
7
- parser,
8
- resolveAddons,
9
- resolveOptions,
10
- resolveTheme,
11
- updateFrontmatterPatch,
12
- version
13
- } from "./chunk-Y2DHOFI5.js";
14
- import {
15
- getRoots,
16
- isInstalledGlobally,
17
- resolveEntry
18
- } from "./chunk-TJFRPB4N.js";
19
-
20
- // node/cli.ts
1
+ import { getThemeMeta, loadSetups, parser, resolveAddons, resolveOptions, resolveTheme, updateFrontmatterPatch, version } from "./shared-bpaLqEdy.js";
2
+ import { getRoots, isInstalledGlobally, resolveEntry } from "./resolver-BShaA6qw.js";
3
+ import { createServer } from "./serve-Cg-fgcfD.js";
21
4
  import { exec } from "node:child_process";
22
5
  import fs from "node:fs/promises";
23
6
  import os from "node:os";
@@ -30,593 +13,534 @@ import equal from "fast-deep-equal";
30
13
  import { getPort } from "get-port-please";
31
14
  import openBrowser from "open";
32
15
  import yargs from "yargs";
33
-
34
- // node/setups/preparser.ts
35
16
  import { uniq } from "@antfu/utils";
36
17
  import { injectPreparserExtensionLoader } from "@slidev/parser/fs";
18
+
19
+ //#region node/setups/preparser.ts
37
20
  function setupPreparser() {
38
- injectPreparserExtensionLoader(async (headmatter, filepath, mode) => {
39
- const addons = Array.isArray(headmatter?.addons) ? headmatter.addons : [];
40
- const { userRoot } = await getRoots();
41
- const roots = uniq([
42
- ...await resolveAddons(addons),
43
- userRoot
44
- ]);
45
- const returns = await loadSetups(roots, "preparser.ts", [{ filepath, headmatter, mode }]);
46
- return returns.flat();
47
- });
21
+ injectPreparserExtensionLoader(async (headmatter, filepath, mode) => {
22
+ const addons = Array.isArray(headmatter?.addons) ? headmatter.addons : [];
23
+ const { userRoot } = await getRoots();
24
+ const roots = uniq([...await resolveAddons(addons), userRoot]);
25
+ const returns = await loadSetups(roots, "preparser.ts", [{
26
+ filepath,
27
+ headmatter,
28
+ mode
29
+ }]);
30
+ return returns.flat();
31
+ });
48
32
  }
49
33
 
50
- // node/cli.ts
51
- var CONFIG_RESTART_FIELDS = [
52
- "monaco",
53
- "routerMode",
54
- "fonts",
55
- "css",
56
- "mdc",
57
- "editor",
58
- "theme",
59
- "seoMeta"
34
+ //#endregion
35
+ //#region node/cli.ts
36
+ const CONFIG_RESTART_FIELDS = [
37
+ "monaco",
38
+ "routerMode",
39
+ "fonts",
40
+ "css",
41
+ "mdc",
42
+ "editor",
43
+ "theme",
44
+ "seoMeta"
60
45
  ];
61
- var FILES_CREATE_RESTART = [
62
- "global-bottom.vue",
63
- "global-top.vue",
64
- "uno.config.js",
65
- "uno.config.ts",
66
- "unocss.config.js",
67
- "unocss.config.ts"
46
+ /**
47
+ * Files that triggers a restart when added or removed
48
+ */
49
+ const FILES_CREATE_RESTART = [
50
+ "global-bottom.vue",
51
+ "global-top.vue",
52
+ "uno.config.js",
53
+ "uno.config.ts",
54
+ "unocss.config.js",
55
+ "unocss.config.ts"
68
56
  ];
69
- var FILES_CHANGE_RESTART = [
70
- "setup/shiki.ts",
71
- "setup/katex.ts",
72
- "setup/preparser.ts"
57
+ const FILES_CHANGE_RESTART = [
58
+ "setup/shiki.ts",
59
+ "setup/katex.ts",
60
+ "setup/preparser.ts"
73
61
  ];
74
62
  setupPreparser();
75
- var cli = yargs(process.argv.slice(2)).scriptName("slidev").usage("$0 [args]").version(version).strict().showHelpOnFail(false).alias("h", "help").alias("v", "version");
76
- cli.command(
77
- "* [entry]",
78
- "Start a local server for Slidev",
79
- (args) => commonOptions(args).option("port", {
80
- alias: "p",
81
- type: "number",
82
- describe: "port"
83
- }).option("open", {
84
- alias: "o",
85
- default: false,
86
- type: "boolean",
87
- describe: "open in browser"
88
- }).option("remote", {
89
- type: "string",
90
- describe: "listen public host and enable remote control"
91
- }).option("tunnel", {
92
- default: false,
93
- type: "boolean",
94
- describe: "open a Cloudflare Quick Tunnel to make Slidev available on the internet"
95
- }).option("log", {
96
- default: "warn",
97
- type: "string",
98
- choices: ["error", "warn", "info", "silent"],
99
- describe: "log level"
100
- }).option("inspect", {
101
- default: false,
102
- type: "boolean",
103
- describe: "enable the inspect plugin for debugging"
104
- }).option("force", {
105
- alias: "f",
106
- default: false,
107
- type: "boolean",
108
- describe: "force the optimizer to ignore the cache and re-bundle"
109
- }).option("bind", {
110
- type: "string",
111
- default: "0.0.0.0",
112
- describe: "specify which IP addresses the server should listen on in remote mode"
113
- }).option("base", {
114
- type: "string",
115
- describe: "base URL. Example: /demo/",
116
- default: "/"
117
- }).strict().help(),
118
- async ({ entry, theme, port: userPort, open, log, remote, tunnel, force, inspect, bind, base }) => {
119
- let server;
120
- let port = 3030;
121
- let lastRemoteUrl;
122
- let restartTimer;
123
- function restartServer() {
124
- clearTimeout(restartTimer);
125
- restartTimer = setTimeout(() => {
126
- console.log(yellow("\n restarting...\n"));
127
- initServer();
128
- }, 500);
129
- }
130
- async function initServer() {
131
- if (server)
132
- await server.close();
133
- const options = await resolveOptions({ entry, remote, theme, inspect, base }, "dev");
134
- const host = remote !== void 0 ? bind : "localhost";
135
- port = userPort || await getPort({
136
- port: 3030,
137
- random: false,
138
- portRange: [3030, 4e3],
139
- host
140
- });
141
- server = await createServer(
142
- options,
143
- {
144
- server: {
145
- port,
146
- strictPort: true,
147
- open,
148
- host,
149
- // @ts-expect-error Vite <= 4
150
- force
151
- },
152
- optimizeDeps: {
153
- // Vite 5
154
- force
155
- },
156
- logLevel: log,
157
- base
158
- },
159
- {
160
- async loadData(loadedSource) {
161
- const { data: oldData, entry: entry2 } = options;
162
- const loaded = await parser.load(options.userRoot, entry2, loadedSource, "dev");
163
- const themeRaw = theme || loaded.headmatter.theme || "default";
164
- if (options.themeRaw !== themeRaw) {
165
- console.log(yellow("\n restarting on theme change\n"));
166
- restartServer();
167
- return false;
168
- }
169
- const themeMeta = options.themeRoots[0] ? await getThemeMeta(themeRaw, options.themeRoots[0]) : void 0;
170
- const newData = {
171
- ...loaded,
172
- themeMeta,
173
- config: parser.resolveConfig(loaded.headmatter, themeMeta, entry2)
174
- };
175
- if (CONFIG_RESTART_FIELDS.some((i) => !equal(newData.config[i], oldData.config[i]))) {
176
- console.log(yellow("\n restarting on config change\n"));
177
- restartServer();
178
- return false;
179
- }
180
- if (newData.features.katex && !oldData.features.katex || newData.features.monaco && !oldData.features.monaco) {
181
- console.log(yellow("\n restarting on feature change\n"));
182
- restartServer();
183
- return false;
184
- }
185
- return newData;
186
- }
187
- }
188
- );
189
- await server.listen();
190
- let tunnelUrl = "";
191
- if (tunnel) {
192
- if (remote != null)
193
- tunnelUrl = await openTunnel(port);
194
- else
195
- console.log(yellow("\n --remote is required for tunneling, Cloudflare Quick Tunnel is not enabled.\n"));
196
- }
197
- let publicIp;
198
- if (remote)
199
- publicIp = await import("public-ip").then((r) => r.publicIpv4());
200
- lastRemoteUrl = printInfo(options, port, base, remote, tunnelUrl, publicIp);
201
- }
202
- async function openTunnel(port2) {
203
- const { startTunnel } = await import("untun");
204
- const tunnel2 = await startTunnel({
205
- port: port2,
206
- acceptCloudflareNotice: true
207
- });
208
- return await tunnel2?.getURL() ?? "";
209
- }
210
- const SHORTCUTS = [
211
- {
212
- name: "r",
213
- fullname: "restart",
214
- action() {
215
- restartServer();
216
- }
217
- },
218
- {
219
- name: "o",
220
- fullname: "open",
221
- action() {
222
- openBrowser(`http://localhost:${port}${base}`);
223
- }
224
- },
225
- {
226
- name: "e",
227
- fullname: "edit",
228
- action() {
229
- exec(`code "${entry}"`);
230
- }
231
- },
232
- {
233
- name: "q",
234
- fullname: "quit",
235
- action() {
236
- try {
237
- server?.close();
238
- } finally {
239
- process.exit();
240
- }
241
- }
242
- },
243
- {
244
- name: "c",
245
- fullname: "qrcode",
246
- async action() {
247
- if (!lastRemoteUrl)
248
- return;
249
- await import("uqr").then(async (r) => {
250
- const code = r.renderUnicodeCompact(lastRemoteUrl);
251
- console.log(`
252
- ${dim(" QR Code for remote control: ")}
253
- ${blue(lastRemoteUrl)}
254
- `);
255
- console.log(code.split("\n").map((i) => ` ${i}`).join("\n"));
256
- const publicIp = await import("public-ip").then((r2) => r2.publicIpv4());
257
- if (publicIp)
258
- console.log(`
259
- ${dim(" Public IP: ")} ${blue(publicIp)}
260
- `);
261
- });
262
- }
263
- }
264
- ];
265
- function bindShortcut() {
266
- process.stdin.resume();
267
- process.stdin.setEncoding("utf8");
268
- readline.emitKeypressEvents(process.stdin);
269
- if (process.stdin.isTTY)
270
- process.stdin.setRawMode(true);
271
- process.stdin.on("keypress", (str, key) => {
272
- if (key.ctrl && key.name === "c") {
273
- process.exit();
274
- } else {
275
- const [sh] = SHORTCUTS.filter((item) => item.name === str);
276
- if (sh) {
277
- try {
278
- sh.action();
279
- } catch (err) {
280
- console.error(`Failed to execute shortcut ${sh.fullname}`, err);
281
- }
282
- }
283
- }
284
- });
285
- }
286
- initServer();
287
- bindShortcut();
288
- const { watch } = await import("chokidar");
289
- const watcher = watch([
290
- ...FILES_CREATE_RESTART,
291
- ...FILES_CHANGE_RESTART
292
- ], {
293
- ignored: ["node_modules", ".git"],
294
- ignoreInitial: true
295
- });
296
- watcher.on("unlink", (file) => {
297
- console.log(yellow(`
298
- file ${file} removed, restarting...
299
- `));
300
- restartServer();
301
- });
302
- watcher.on("add", (file) => {
303
- console.log(yellow(`
304
- file ${file} added, restarting...
305
- `));
306
- restartServer();
307
- });
308
- watcher.on("change", (file) => {
309
- if (typeof file !== "string")
310
- return;
311
- if (FILES_CREATE_RESTART.includes(file))
312
- return;
313
- console.log(yellow(`
314
- file ${file} changed, restarting...
315
- `));
316
- restartServer();
317
- });
318
- }
319
- );
320
- cli.command(
321
- "build [entry..]",
322
- "Build hostable SPA",
323
- (args) => exportOptions(commonOptions(args)).option("out", {
324
- alias: "o",
325
- type: "string",
326
- default: "dist",
327
- describe: "output dir"
328
- }).option("base", {
329
- type: "string",
330
- describe: "output base. Example: /demo/"
331
- }).option("download", {
332
- alias: "d",
333
- type: "boolean",
334
- describe: "allow download as PDF"
335
- }).option("inspect", {
336
- default: false,
337
- type: "boolean",
338
- describe: "enable the inspect plugin for debugging"
339
- }).strict().help(),
340
- async (args) => {
341
- const { entry, theme, base, download, out, inspect } = args;
342
- const { build } = await import("./build-Z7GTSSXD.js");
343
- for (const entryFile of entry) {
344
- const options = await resolveOptions({ entry: entryFile, theme, inspect, download, base }, "build");
345
- printInfo(options);
346
- await build(
347
- options,
348
- {
349
- base,
350
- build: {
351
- outDir: entry.length === 1 ? out : path.join(out, path.basename(entryFile, ".md"))
352
- }
353
- },
354
- { ...args, entry: entryFile }
355
- );
356
- }
357
- }
358
- );
359
- cli.command(
360
- "format [entry..]",
361
- "Format the markdown file",
362
- (args) => commonOptions(args).strict().help(),
363
- async ({ entry }) => {
364
- for (const entryFile of entry) {
365
- const md = await parser.parse(await fs.readFile(entryFile, "utf-8"), entryFile);
366
- parser.prettify(md);
367
- await parser.save(md);
368
- }
369
- }
370
- );
371
- cli.command(
372
- "theme [subcommand]",
373
- "Theme related operations",
374
- (command) => {
375
- return command.command(
376
- "eject",
377
- "Eject current theme into local file system",
378
- (args) => commonOptions(args).option("dir", {
379
- type: "string",
380
- default: "theme"
381
- }),
382
- async ({ entry: entryRaw, dir, theme: themeInput }) => {
383
- const entry = await resolveEntry(entryRaw);
384
- const roots = await getRoots(entry);
385
- const data = await parser.load(roots.userRoot, entry);
386
- let themeRaw = themeInput || data.headmatter.theme;
387
- themeRaw = themeRaw === null ? "none" : themeRaw || "default";
388
- if (themeRaw === "none") {
389
- console.error('Cannot eject theme "none"');
390
- process.exit(1);
391
- }
392
- if ("/.".includes(themeRaw[0]) || themeRaw[0] !== "@" && themeRaw.includes("/")) {
393
- console.error("Theme is already ejected");
394
- process.exit(1);
395
- }
396
- const [name, root] = await resolveTheme(themeRaw, entry);
397
- await fs.mkdir(path.resolve(dir), { recursive: true });
398
- await fs.cp(
399
- root,
400
- path.resolve(dir),
401
- {
402
- recursive: true,
403
- filter: (i) => !/node_modules|\.git/.test(path.relative(root, i))
404
- }
405
- );
406
- const dirPath = `./${dir}`;
407
- const firstSlide = data.entry.slides[0];
408
- updateFrontmatterPatch(firstSlide, { theme: dirPath });
409
- parser.prettifySlide(firstSlide);
410
- await parser.save(data.entry);
411
- console.log(`Theme "${name}" ejected successfully to "${dirPath}"`);
412
- }
413
- );
414
- },
415
- () => {
416
- cli.showHelp();
417
- process.exit(1);
418
- }
419
- );
420
- cli.command(
421
- "export [entry..]",
422
- "Export slides to PDF",
423
- (args) => exportOptions(commonOptions(args)).strict().help(),
424
- async (args) => {
425
- const { entry, theme } = args;
426
- const { exportSlides, getExportOptions } = await import("./export-5TR5BGLG.js");
427
- const port = await getPort(12445);
428
- let warned = false;
429
- for (const entryFile of entry) {
430
- const options = await resolveOptions({ entry: entryFile, theme }, "export");
431
- if (options.data.config.browserExporter !== false && !warned) {
432
- warned = true;
433
- console.log(cyanBright("[Slidev] Try the new browser exporter!"));
434
- console.log(
435
- cyanBright("You can use the browser exporter instead by starting the dev server as normal and visit"),
436
- `${blue("localhost:")}${dim("<port>")}${blue("/export")}
437
- `
438
- );
439
- }
440
- const server = await createServer(
441
- options,
442
- {
443
- server: { port },
444
- clearScreen: false
445
- }
446
- );
447
- await server.listen(port);
448
- printInfo(options);
449
- const result = await exportSlides({
450
- port,
451
- ...getExportOptions({ ...args, entry: entryFile }, options)
452
- });
453
- console.log(`${green(" \u2713 ")}${dim("exported to ")}${result}
454
- `);
455
- server.close();
456
- }
457
- process.exit(0);
458
- }
459
- );
460
- cli.command(
461
- "export-notes [entry..]",
462
- "Export slide notes to PDF",
463
- (args) => args.positional("entry", {
464
- default: "slides.md",
465
- type: "string",
466
- describe: "path to the slides markdown entry"
467
- }).option("output", {
468
- type: "string",
469
- describe: "path to the output"
470
- }).option("timeout", {
471
- default: 3e4,
472
- type: "number",
473
- describe: "timeout for rendering the print page"
474
- }).option("wait", {
475
- default: 0,
476
- type: "number",
477
- describe: "wait for the specified ms before exporting"
478
- }).strict().help(),
479
- async ({
480
- entry,
481
- output,
482
- timeout,
483
- wait
484
- }) => {
485
- const { exportNotes } = await import("./export-5TR5BGLG.js");
486
- const port = await getPort(12445);
487
- for (const entryFile of entry) {
488
- const options = await resolveOptions({ entry: entryFile }, "export");
489
- const server = await createServer(
490
- options,
491
- {
492
- server: { port },
493
- clearScreen: false
494
- }
495
- );
496
- await server.listen(port);
497
- printInfo(options);
498
- const result = await exportNotes({
499
- port,
500
- output: output || (options.data.config.exportFilename ? `${options.data.config.exportFilename}-notes` : `${path.basename(entryFile, ".md")}-export-notes`),
501
- timeout,
502
- wait
503
- });
504
- console.log(`${green(" \u2713 ")}${dim("exported to ")}${result}
505
- `);
506
- server.close();
507
- }
508
- process.exit(0);
509
- }
510
- );
63
+ const cli = yargs(process.argv.slice(2)).scriptName("slidev").usage("$0 [args]").version(version).strict().showHelpOnFail(false).alias("h", "help").alias("v", "version");
64
+ cli.command("* [entry]", "Start a local server for Slidev", (args) => commonOptions(args).option("port", {
65
+ alias: "p",
66
+ type: "number",
67
+ describe: "port"
68
+ }).option("open", {
69
+ alias: "o",
70
+ default: false,
71
+ type: "boolean",
72
+ describe: "open in browser"
73
+ }).option("remote", {
74
+ type: "string",
75
+ describe: "listen public host and enable remote control"
76
+ }).option("tunnel", {
77
+ default: false,
78
+ type: "boolean",
79
+ describe: "open a Cloudflare Quick Tunnel to make Slidev available on the internet"
80
+ }).option("log", {
81
+ default: "warn",
82
+ type: "string",
83
+ choices: [
84
+ "error",
85
+ "warn",
86
+ "info",
87
+ "silent"
88
+ ],
89
+ describe: "log level"
90
+ }).option("inspect", {
91
+ default: false,
92
+ type: "boolean",
93
+ describe: "enable the inspect plugin for debugging"
94
+ }).option("force", {
95
+ alias: "f",
96
+ default: false,
97
+ type: "boolean",
98
+ describe: "force the optimizer to ignore the cache and re-bundle"
99
+ }).option("bind", {
100
+ type: "string",
101
+ default: "0.0.0.0",
102
+ describe: "specify which IP addresses the server should listen on in remote mode"
103
+ }).option("base", {
104
+ type: "string",
105
+ describe: "base URL. Example: /demo/",
106
+ default: "/"
107
+ }).strict().help(), async ({ entry, theme, port: userPort, open, log, remote, tunnel, force, inspect, bind, base }) => {
108
+ let server;
109
+ let port = 3030;
110
+ let lastRemoteUrl;
111
+ let restartTimer;
112
+ function restartServer() {
113
+ clearTimeout(restartTimer);
114
+ restartTimer = setTimeout(() => {
115
+ console.log(yellow("\n restarting...\n"));
116
+ initServer();
117
+ }, 500);
118
+ }
119
+ async function initServer() {
120
+ if (server) await server.close();
121
+ const options = await resolveOptions({
122
+ entry,
123
+ remote,
124
+ theme,
125
+ inspect,
126
+ base
127
+ }, "dev");
128
+ const host = remote !== void 0 ? bind : "localhost";
129
+ port = userPort || await getPort({
130
+ port: 3030,
131
+ random: false,
132
+ portRange: [3030, 4e3],
133
+ host
134
+ });
135
+ server = await createServer(options, {
136
+ server: {
137
+ port,
138
+ strictPort: true,
139
+ open,
140
+ host,
141
+ force
142
+ },
143
+ optimizeDeps: { force },
144
+ logLevel: log,
145
+ base
146
+ }, { async loadData(loadedSource) {
147
+ const { data: oldData, entry: entry$1 } = options;
148
+ const loaded = await parser.load(options.userRoot, entry$1, loadedSource, "dev");
149
+ const themeRaw = theme || loaded.headmatter.theme || "default";
150
+ if (options.themeRaw !== themeRaw) {
151
+ console.log(yellow("\n restarting on theme change\n"));
152
+ restartServer();
153
+ return false;
154
+ }
155
+ const themeMeta = options.themeRoots[0] ? await getThemeMeta(themeRaw, options.themeRoots[0]) : void 0;
156
+ const newData = {
157
+ ...loaded,
158
+ themeMeta,
159
+ config: parser.resolveConfig(loaded.headmatter, themeMeta, entry$1)
160
+ };
161
+ if (CONFIG_RESTART_FIELDS.some((i) => !equal(newData.config[i], oldData.config[i]))) {
162
+ console.log(yellow("\n restarting on config change\n"));
163
+ restartServer();
164
+ return false;
165
+ }
166
+ if (newData.features.katex && !oldData.features.katex || newData.features.monaco && !oldData.features.monaco) {
167
+ console.log(yellow("\n restarting on feature change\n"));
168
+ restartServer();
169
+ return false;
170
+ }
171
+ return newData;
172
+ } });
173
+ await server.listen();
174
+ let tunnelUrl = "";
175
+ if (tunnel) if (remote != null) tunnelUrl = await openTunnel(port);
176
+ else console.log(yellow("\n --remote is required for tunneling, Cloudflare Quick Tunnel is not enabled.\n"));
177
+ let publicIp;
178
+ if (remote) publicIp = await import("public-ip").then((r) => r.publicIpv4());
179
+ lastRemoteUrl = printInfo(options, port, base, remote, tunnelUrl, publicIp);
180
+ }
181
+ async function openTunnel(port$1) {
182
+ const { startTunnel } = await import("untun");
183
+ const tunnel$1 = await startTunnel({
184
+ port: port$1,
185
+ acceptCloudflareNotice: true
186
+ });
187
+ return await tunnel$1?.getURL() ?? "";
188
+ }
189
+ const SHORTCUTS = [
190
+ {
191
+ name: "r",
192
+ fullname: "restart",
193
+ action() {
194
+ restartServer();
195
+ }
196
+ },
197
+ {
198
+ name: "o",
199
+ fullname: "open",
200
+ action() {
201
+ openBrowser(`http://localhost:${port}${base}`);
202
+ }
203
+ },
204
+ {
205
+ name: "e",
206
+ fullname: "edit",
207
+ action() {
208
+ exec(`code "${entry}"`);
209
+ }
210
+ },
211
+ {
212
+ name: "q",
213
+ fullname: "quit",
214
+ action() {
215
+ try {
216
+ server?.close();
217
+ } finally {
218
+ process.exit();
219
+ }
220
+ }
221
+ },
222
+ {
223
+ name: "c",
224
+ fullname: "qrcode",
225
+ async action() {
226
+ if (!lastRemoteUrl) return;
227
+ await import("uqr").then(async (r) => {
228
+ const code = r.renderUnicodeCompact(lastRemoteUrl);
229
+ console.log(`\n${dim(" QR Code for remote control: ")}\n ${blue(lastRemoteUrl)}\n`);
230
+ console.log(code.split("\n").map((i) => ` ${i}`).join("\n"));
231
+ const publicIp = await import("public-ip").then((r$1) => r$1.publicIpv4());
232
+ if (publicIp) console.log(`\n${dim(" Public IP: ")} ${blue(publicIp)}\n`);
233
+ });
234
+ }
235
+ }
236
+ ];
237
+ function bindShortcut() {
238
+ process.stdin.resume();
239
+ process.stdin.setEncoding("utf8");
240
+ readline.emitKeypressEvents(process.stdin);
241
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
242
+ const onKeyPress = (str, key) => {
243
+ if (key.ctrl && key.name === "c") process.exit();
244
+ else {
245
+ const [sh] = SHORTCUTS.filter((item) => item.name === str);
246
+ if (sh) try {
247
+ sh.action();
248
+ } catch (err) {
249
+ console.error(`Failed to execute shortcut ${sh.fullname}`, err);
250
+ }
251
+ }
252
+ };
253
+ process.stdin.on("keypress", onKeyPress);
254
+ server?.httpServer?.on("close", () => {
255
+ process.stdin.off("keypress", onKeyPress);
256
+ });
257
+ }
258
+ initServer();
259
+ bindShortcut();
260
+ const { watch } = await import("chokidar");
261
+ const watcher = watch([...FILES_CREATE_RESTART, ...FILES_CHANGE_RESTART], {
262
+ ignored: ["node_modules", ".git"],
263
+ ignoreInitial: true
264
+ });
265
+ watcher.on("unlink", (file) => {
266
+ console.log(yellow(`\n file ${file} removed, restarting...\n`));
267
+ restartServer();
268
+ });
269
+ watcher.on("add", (file) => {
270
+ console.log(yellow(`\n file ${file} added, restarting...\n`));
271
+ restartServer();
272
+ });
273
+ watcher.on("change", (file) => {
274
+ if (typeof file !== "string") return;
275
+ if (FILES_CREATE_RESTART.includes(file)) return;
276
+ console.log(yellow(`\n file ${file} changed, restarting...\n`));
277
+ restartServer();
278
+ });
279
+ });
280
+ cli.command("build [entry..]", "Build hostable SPA", (args) => exportOptions(commonOptions(args)).option("out", {
281
+ alias: "o",
282
+ type: "string",
283
+ default: "dist",
284
+ describe: "output dir"
285
+ }).option("base", {
286
+ type: "string",
287
+ describe: "output base. Example: /demo/"
288
+ }).option("download", {
289
+ alias: "d",
290
+ type: "boolean",
291
+ describe: "allow download as PDF"
292
+ }).option("inspect", {
293
+ default: false,
294
+ type: "boolean",
295
+ describe: "enable the inspect plugin for debugging"
296
+ }).strict().help(), async (args) => {
297
+ const { entry, theme, base, download, out, inspect } = args;
298
+ const { build } = await import("./build-B6Mgy-Y-.js");
299
+ for (const entryFile of entry) {
300
+ const options = await resolveOptions({
301
+ entry: entryFile,
302
+ theme,
303
+ inspect,
304
+ download,
305
+ base
306
+ }, "build");
307
+ printInfo(options);
308
+ await build(options, {
309
+ base,
310
+ build: { outDir: entry.length === 1 ? out : path.join(out, path.basename(entryFile, ".md")) }
311
+ }, {
312
+ ...args,
313
+ entry: entryFile
314
+ });
315
+ }
316
+ });
317
+ cli.command("format [entry..]", "Format the markdown file", (args) => commonOptions(args).strict().help(), async ({ entry }) => {
318
+ for (const entryFile of entry) {
319
+ const md = await parser.parse(await fs.readFile(entryFile, "utf-8"), entryFile);
320
+ parser.prettify(md);
321
+ await parser.save(md);
322
+ }
323
+ });
324
+ cli.command("theme [subcommand]", "Theme related operations", (command) => {
325
+ return command.command("eject", "Eject current theme into local file system", (args) => commonOptions(args).option("dir", {
326
+ type: "string",
327
+ default: "theme"
328
+ }), async ({ entry: entryRaw, dir, theme: themeInput }) => {
329
+ const entry = await resolveEntry(entryRaw);
330
+ const roots = await getRoots(entry);
331
+ const data = await parser.load(roots.userRoot, entry);
332
+ let themeRaw = themeInput || data.headmatter.theme;
333
+ themeRaw = themeRaw === null ? "none" : themeRaw || "default";
334
+ if (themeRaw === "none") {
335
+ console.error("Cannot eject theme \"none\"");
336
+ process.exit(1);
337
+ }
338
+ if ("/.".includes(themeRaw[0]) || themeRaw[0] !== "@" && themeRaw.includes("/")) {
339
+ console.error("Theme is already ejected");
340
+ process.exit(1);
341
+ }
342
+ const [name, root] = await resolveTheme(themeRaw, entry);
343
+ await fs.mkdir(path.resolve(dir), { recursive: true });
344
+ await fs.cp(root, path.resolve(dir), {
345
+ recursive: true,
346
+ filter: (i) => !/node_modules|\.git/.test(path.relative(root, i))
347
+ });
348
+ const dirPath = `./${dir}`;
349
+ const firstSlide = data.entry.slides[0];
350
+ updateFrontmatterPatch(firstSlide, { theme: dirPath });
351
+ parser.prettifySlide(firstSlide);
352
+ await parser.save(data.entry);
353
+ console.log(`Theme "${name}" ejected successfully to "${dirPath}"`);
354
+ });
355
+ }, () => {
356
+ cli.showHelp();
357
+ process.exit(1);
358
+ });
359
+ cli.command("export [entry..]", "Export slides to PDF", (args) => exportOptions(commonOptions(args)).strict().help(), async (args) => {
360
+ const { entry, theme } = args;
361
+ const { exportSlides, getExportOptions } = await import("./export-D1Zqc-n1.js");
362
+ const port = await getPort(12445);
363
+ let warned = false;
364
+ for (const entryFile of entry) {
365
+ const options = await resolveOptions({
366
+ entry: entryFile,
367
+ theme
368
+ }, "export");
369
+ if (options.data.config.browserExporter !== false && !warned) {
370
+ warned = true;
371
+ console.log(cyanBright("[Slidev] Try the new browser exporter!"));
372
+ console.log(cyanBright("You can use the browser exporter instead by starting the dev server as normal and visit"), `${blue("localhost:")}${dim("<port>")}${blue("/export")}\n`);
373
+ }
374
+ const server = await createServer(options, {
375
+ server: { port },
376
+ clearScreen: false
377
+ });
378
+ await server.listen(port);
379
+ printInfo(options);
380
+ const result = await exportSlides({
381
+ port,
382
+ ...getExportOptions({
383
+ ...args,
384
+ entry: entryFile
385
+ }, options)
386
+ });
387
+ console.log(`${green(" ✓ ")}${dim("exported to ")}${result}\n`);
388
+ server.close();
389
+ }
390
+ process.exit(0);
391
+ });
392
+ cli.command("export-notes [entry..]", "Export slide notes to PDF", (args) => args.positional("entry", {
393
+ default: "slides.md",
394
+ type: "string",
395
+ describe: "path to the slides markdown entry"
396
+ }).option("output", {
397
+ type: "string",
398
+ describe: "path to the output"
399
+ }).option("timeout", {
400
+ default: 3e4,
401
+ type: "number",
402
+ describe: "timeout for rendering the print page"
403
+ }).option("wait", {
404
+ default: 0,
405
+ type: "number",
406
+ describe: "wait for the specified ms before exporting"
407
+ }).strict().help(), async ({ entry, output, timeout, wait }) => {
408
+ const { exportNotes } = await import("./export-D1Zqc-n1.js");
409
+ const port = await getPort(12445);
410
+ for (const entryFile of entry) {
411
+ const options = await resolveOptions({ entry: entryFile }, "export");
412
+ const server = await createServer(options, {
413
+ server: { port },
414
+ clearScreen: false
415
+ });
416
+ await server.listen(port);
417
+ printInfo(options);
418
+ const result = await exportNotes({
419
+ port,
420
+ output: output || (options.data.config.exportFilename ? `${options.data.config.exportFilename}-notes` : `${path.basename(entryFile, ".md")}-export-notes`),
421
+ timeout,
422
+ wait
423
+ });
424
+ console.log(`${green("")}${dim("exported to ")}${result}\n`);
425
+ server.close();
426
+ }
427
+ process.exit(0);
428
+ });
511
429
  cli.help().parse();
512
430
  function commonOptions(args) {
513
- return args.positional("entry", {
514
- default: "slides.md",
515
- type: "string",
516
- describe: "path to the slides markdown entry"
517
- }).option("theme", {
518
- alias: "t",
519
- type: "string",
520
- describe: "override theme"
521
- });
431
+ return args.positional("entry", {
432
+ default: "slides.md",
433
+ type: "string",
434
+ describe: "path to the slides markdown entry"
435
+ }).option("theme", {
436
+ alias: "t",
437
+ type: "string",
438
+ describe: "override theme"
439
+ });
522
440
  }
523
441
  function exportOptions(args) {
524
- return args.option("output", {
525
- type: "string",
526
- describe: "path to the output"
527
- }).option("format", {
528
- type: "string",
529
- choices: ["pdf", "png", "pptx", "md"],
530
- describe: "output format"
531
- }).option("timeout", {
532
- type: "number",
533
- describe: "timeout for rendering the print page"
534
- }).option("wait", {
535
- type: "number",
536
- describe: "wait for the specified ms before exporting"
537
- }).option("wait-until", {
538
- type: "string",
539
- choices: ["networkidle", "load", "domcontentloaded", "none"],
540
- describe: "wait until the specified event before exporting each slide"
541
- }).option("range", {
542
- type: "string",
543
- describe: 'page ranges to export, for example "1,4-5,6"'
544
- }).option("dark", {
545
- type: "boolean",
546
- describe: "export as dark theme"
547
- }).option("with-clicks", {
548
- alias: "c",
549
- type: "boolean",
550
- describe: "export pages for every clicks"
551
- }).option("executable-path", {
552
- type: "string",
553
- describe: "executable to override playwright bundled browser"
554
- }).option("with-toc", {
555
- type: "boolean",
556
- describe: "export pages with outline"
557
- }).option("per-slide", {
558
- type: "boolean",
559
- describe: "slide slides slide by slide. Works better with global components, but will break cross slide links and TOC in PDF"
560
- }).option("scale", {
561
- type: "number",
562
- describe: "scale factor for image export"
563
- }).option("omit-background", {
564
- type: "boolean",
565
- describe: "export png pages without the default browser background"
566
- });
442
+ return args.option("output", {
443
+ type: "string",
444
+ describe: "path to the output"
445
+ }).option("format", {
446
+ type: "string",
447
+ choices: [
448
+ "pdf",
449
+ "png",
450
+ "pptx",
451
+ "md"
452
+ ],
453
+ describe: "output format"
454
+ }).option("timeout", {
455
+ type: "number",
456
+ describe: "timeout for rendering the print page"
457
+ }).option("wait", {
458
+ type: "number",
459
+ describe: "wait for the specified ms before exporting"
460
+ }).option("wait-until", {
461
+ type: "string",
462
+ choices: [
463
+ "networkidle",
464
+ "load",
465
+ "domcontentloaded",
466
+ "none"
467
+ ],
468
+ describe: "wait until the specified event before exporting each slide"
469
+ }).option("range", {
470
+ type: "string",
471
+ describe: "page ranges to export, for example \"1,4-5,6\""
472
+ }).option("dark", {
473
+ type: "boolean",
474
+ describe: "export as dark theme"
475
+ }).option("with-clicks", {
476
+ alias: "c",
477
+ type: "boolean",
478
+ describe: "export pages for every clicks"
479
+ }).option("executable-path", {
480
+ type: "string",
481
+ describe: "executable to override playwright bundled browser"
482
+ }).option("with-toc", {
483
+ type: "boolean",
484
+ describe: "export pages with outline"
485
+ }).option("per-slide", {
486
+ type: "boolean",
487
+ describe: "slide slides slide by slide. Works better with global components, but will break cross slide links and TOC in PDF"
488
+ }).option("scale", {
489
+ type: "number",
490
+ describe: "scale factor for image export"
491
+ }).option("omit-background", {
492
+ type: "boolean",
493
+ describe: "export png pages without the default browser background"
494
+ });
567
495
  }
568
496
  function printInfo(options, port, base, remote, tunnelUrl, publicIp) {
569
- if (base && (!base.startsWith("/") || !base.endsWith("/"))) {
570
- console.error('Base URL must start and end with a slash "/"');
571
- process.exit(1);
572
- }
573
- console.log();
574
- console.log();
575
- console.log(` ${cyan("\u25CF") + blue("\u25A0") + yellow("\u25B2")}`);
576
- console.log(`${bold(" Slidev")} ${blue(`v${version}`)} ${isInstalledGlobally.value ? yellow("(global)") : ""}`);
577
- console.log();
578
- verifyConfig(options.data.config, options.data.themeMeta, (v) => console.warn(yellow(` ! ${v}`)));
579
- console.log(dim(" theme ") + (options.theme ? green(options.theme) : gray("none")));
580
- console.log(dim(" css engine ") + blue("unocss"));
581
- console.log(dim(" entry ") + dim(path.normalize(path.dirname(options.entry)) + path.sep) + path.basename(options.entry));
582
- if (port) {
583
- const baseText = base?.slice(0, -1) || "";
584
- const portAndBase = port + baseText;
585
- const baseUrl = `http://localhost:${bold(portAndBase)}`;
586
- const query = remote ? `?password=${remote}` : "";
587
- const presenterPath = `${options.data.config.routerMode === "hash" ? "/#/" : "/"}presenter/${query}`;
588
- const entryPath = `${options.data.config.routerMode === "hash" ? "/#/" : "/"}entry${query}/`;
589
- const overviewPath = `${options.data.config.routerMode === "hash" ? "/#/" : "/"}overview${query}/`;
590
- console.log();
591
- console.log(`${dim(" public slide show ")} > ${cyan(`${baseUrl}/`)}`);
592
- if (query)
593
- console.log(`${dim(" private slide show ")} > ${cyan(`${baseUrl}/${query}`)}`);
594
- if (options.utils.define.__SLIDEV_FEATURE_PRESENTER__)
595
- console.log(`${dim(" presenter mode ")} > ${blue(`${baseUrl}${presenterPath}`)}`);
596
- console.log(`${dim(" slides overview ")} > ${blue(`${baseUrl}${overviewPath}`)}`);
597
- if (options.utils.define.__SLIDEV_FEATURE_BROWSER_EXPORTER__)
598
- console.log(`${dim(" export slides")} > ${blue(`${baseUrl}/export/`)}`);
599
- if (options.inspect)
600
- console.log(`${dim(" vite inspector")} > ${yellow(`${baseUrl}/__inspect/`)}`);
601
- let lastRemoteUrl = "";
602
- if (remote !== void 0) {
603
- Object.values(os.networkInterfaces()).forEach((v) => (v || []).filter((details) => String(details.family).slice(-1) === "4" && !details.address.includes("127.0.0.1")).forEach(({ address }) => {
604
- lastRemoteUrl = `http://${address}:${portAndBase}${entryPath}`;
605
- console.log(`${dim(" remote control ")} > ${blue(lastRemoteUrl)}`);
606
- }));
607
- if (publicIp) {
608
- lastRemoteUrl = `http://${publicIp}:${portAndBase}${entryPath}`;
609
- console.log(`${dim(" remote control ")} > ${blue(lastRemoteUrl)}`);
610
- }
611
- if (tunnelUrl) {
612
- lastRemoteUrl = `${tunnelUrl}${baseText}${entryPath}`;
613
- console.log(`${dim(" remote via tunnel")} > ${yellow(lastRemoteUrl)}`);
614
- }
615
- } else {
616
- console.log(`${dim(" remote control ")} > ${dim("pass --remote to enable")}`);
617
- }
618
- console.log();
619
- console.log(`${dim(" shortcuts ")} > ${underline("r")}${dim("estart | ")}${underline("o")}${dim("pen | ")}${underline("e")}${dim("dit | ")}${underline("q")}${dim("uit")}${lastRemoteUrl ? ` | ${dim("qr")}${underline("c")}${dim("ode")}` : ""}`);
620
- return lastRemoteUrl;
621
- }
497
+ if (base && (!base.startsWith("/") || !base.endsWith("/"))) {
498
+ console.error("Base URL must start and end with a slash \"/\"");
499
+ process.exit(1);
500
+ }
501
+ console.log();
502
+ console.log();
503
+ console.log(` ${cyan("") + blue("") + yellow("")}`);
504
+ console.log(`${bold(" Slidev")} ${blue(`v${version}`)} ${isInstalledGlobally.value ? yellow("(global)") : ""}`);
505
+ console.log();
506
+ verifyConfig(options.data.config, options.data.themeMeta, (v) => console.warn(yellow(` ! ${v}`)));
507
+ console.log(dim(" theme ") + (options.theme ? green(options.theme) : gray("none")));
508
+ console.log(dim(" css engine ") + blue("unocss"));
509
+ console.log(dim(" entry ") + dim(path.normalize(path.dirname(options.entry)) + path.sep) + path.basename(options.entry));
510
+ if (port) {
511
+ const baseText = base?.slice(0, -1) || "";
512
+ const portAndBase = port + baseText;
513
+ const baseUrl = `http://localhost:${bold(portAndBase)}`;
514
+ const query = remote ? `?password=${remote}` : "";
515
+ const presenterPath = `${options.data.config.routerMode === "hash" ? "/#/" : "/"}presenter/${query}`;
516
+ const entryPath = `${options.data.config.routerMode === "hash" ? "/#/" : "/"}entry${query}/`;
517
+ const overviewPath = `${options.data.config.routerMode === "hash" ? "/#/" : "/"}overview${query}/`;
518
+ console.log();
519
+ console.log(`${dim(" public slide show ")} > ${cyan(`${baseUrl}/`)}`);
520
+ if (query) console.log(`${dim(" private slide show ")} > ${cyan(`${baseUrl}/${query}`)}`);
521
+ if (options.utils.define.__SLIDEV_FEATURE_PRESENTER__) console.log(`${dim(" presenter mode ")} > ${blue(`${baseUrl}${presenterPath}`)}`);
522
+ console.log(`${dim(" slides overview ")} > ${blue(`${baseUrl}${overviewPath}`)}`);
523
+ if (options.utils.define.__SLIDEV_FEATURE_BROWSER_EXPORTER__) console.log(`${dim(" export slides")} > ${blue(`${baseUrl}/export/`)}`);
524
+ if (options.inspect) console.log(`${dim(" vite inspector")} > ${yellow(`${baseUrl}/__inspect/`)}`);
525
+ let lastRemoteUrl = "";
526
+ if (remote !== void 0) {
527
+ Object.values(os.networkInterfaces()).forEach((v) => (v || []).filter((details) => String(details.family).slice(-1) === "4" && !details.address.includes("127.0.0.1")).forEach(({ address }) => {
528
+ lastRemoteUrl = `http://${address}:${portAndBase}${entryPath}`;
529
+ console.log(`${dim(" remote control ")} > ${blue(lastRemoteUrl)}`);
530
+ }));
531
+ if (publicIp) {
532
+ lastRemoteUrl = `http://${publicIp}:${portAndBase}${entryPath}`;
533
+ console.log(`${dim(" remote control ")} > ${blue(lastRemoteUrl)}`);
534
+ }
535
+ if (tunnelUrl) {
536
+ lastRemoteUrl = `${tunnelUrl}${baseText}${entryPath}`;
537
+ console.log(`${dim(" remote via tunnel")} > ${yellow(lastRemoteUrl)}`);
538
+ }
539
+ } else console.log(`${dim(" remote control ")} > ${dim("pass --remote to enable")}`);
540
+ console.log();
541
+ console.log(`${dim(" shortcuts ")} > ${underline("r")}${dim("estart | ")}${underline("o")}${dim("pen | ")}${underline("e")}${dim("dit | ")}${underline("q")}${dim("uit")}${lastRemoteUrl ? ` | ${dim("qr")}${underline("c")}${dim("ode")}` : ""}`);
542
+ return lastRemoteUrl;
543
+ }
622
544
  }
545
+
546
+ //#endregion