@reslide-dev/cli 0.1.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.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ import { reslide } from "./vite-plugin.mjs";
3
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { cac } from "cac";
6
+ import { build, createServer } from "vite";
7
+ //#region src/cli.ts
8
+ const cli = cac("reslide");
9
+ function generateEntryFiles(slidesPath, outDir) {
10
+ const absSlides = resolve(slidesPath);
11
+ mkdirSync(outDir, { recursive: true });
12
+ writeFileSync(resolve(outDir, "index.html"), `<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="UTF-8" />
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
17
+ <title>reslide</title>
18
+ <style>
19
+ * { margin: 0; padding: 0; box-sizing: border-box; }
20
+ html, body, #root { width: 100%; height: 100%; overflow: hidden; }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <div id="root"></div>
25
+ <script type="module" src="./main.tsx"><\/script>
26
+ </body>
27
+ </html>`);
28
+ writeFileSync(resolve(outDir, "main.tsx"), `import { StrictMode } from "react";
29
+ import { createRoot } from "react-dom/client";
30
+ import { Deck, Slide, Click, ClickSteps, Mark, Notes, SlotRight } from "@reslide-dev/core";
31
+ import Slides from "${absSlides}";
32
+
33
+ // Make components available to MDX
34
+ const components = { Deck, Slide, Click, ClickSteps, Mark, Notes, SlotRight };
35
+
36
+ createRoot(document.getElementById("root")!).render(
37
+ <StrictMode>
38
+ <Slides components={components} />
39
+ </StrictMode>
40
+ );
41
+ `);
42
+ }
43
+ cli.command("dev <slides>", "Start development server").option("--port <port>", "Port number", { default: 3030 }).option("--host", "Expose to network").action(async (slides, options) => {
44
+ if (!existsSync(slides)) {
45
+ console.error(`Error: File not found: ${slides}`);
46
+ process.exit(1);
47
+ }
48
+ const tmpDir = resolve(dirname(slides), ".reslide");
49
+ generateEntryFiles(slides, tmpDir);
50
+ const server = await createServer({
51
+ root: tmpDir,
52
+ plugins: [reslide()],
53
+ server: {
54
+ port: options.port,
55
+ host: options.host,
56
+ open: true
57
+ }
58
+ });
59
+ await server.listen();
60
+ server.printUrls();
61
+ });
62
+ cli.command("build <slides>", "Build static presentation").option("--out <dir>", "Output directory", { default: "dist" }).action(async (slides, options) => {
63
+ if (!existsSync(slides)) {
64
+ console.error(`Error: File not found: ${slides}`);
65
+ process.exit(1);
66
+ }
67
+ const tmpDir = resolve(dirname(slides), ".reslide");
68
+ generateEntryFiles(slides, tmpDir);
69
+ await build({
70
+ root: tmpDir,
71
+ plugins: [reslide()],
72
+ build: {
73
+ outDir: resolve(options.out),
74
+ emptyOutDir: true
75
+ }
76
+ });
77
+ console.log(`\nBuild complete. Output: ${resolve(options.out)}`);
78
+ });
79
+ cli.command("export <slides>", "Export slides to PDF or PNG (requires Playwright)").option("--format <format>", "Export format: pdf or png", { default: "pdf" }).option("--out <dir>", "Output directory", { default: "export" }).option("--width <width>", "Viewport width", { default: 1920 }).option("--height <height>", "Viewport height", { default: 1080 }).action(async (slides, options) => {
80
+ const { exportSlides } = await import("./export.mjs");
81
+ await exportSlides(slides, generateEntryFiles, {
82
+ format: options.format,
83
+ out: options.out,
84
+ width: options.width,
85
+ height: options.height,
86
+ port: 4173
87
+ });
88
+ });
89
+ cli.help();
90
+ cli.version("0.0.0");
91
+ cli.parse();
92
+ //#endregion
93
+ export {};
@@ -0,0 +1,15 @@
1
+ //#region src/export.d.ts
2
+ interface ExportOptions {
3
+ format: "pdf" | "png";
4
+ out: string;
5
+ width: number;
6
+ height: number;
7
+ port: number;
8
+ }
9
+ /**
10
+ * Export slides to PDF or PNG using Playwright.
11
+ * Playwright must be installed by the user: `vp add playwright`
12
+ */
13
+ declare function exportSlides(slidesPath: string, generateEntryFiles: (slidesPath: string, outDir: string) => void, options: ExportOptions): Promise<void>;
14
+ //#endregion
15
+ export { exportSlides };
@@ -0,0 +1,80 @@
1
+ import { reslide } from "./vite-plugin.mjs";
2
+ import { existsSync, mkdirSync } from "node:fs";
3
+ import { dirname, resolve } from "node:path";
4
+ import { createServer } from "vite";
5
+ //#region src/export.ts
6
+ /**
7
+ * Export slides to PDF or PNG using Playwright.
8
+ * Playwright must be installed by the user: `vp add playwright`
9
+ */
10
+ async function exportSlides(slidesPath, generateEntryFiles, options) {
11
+ if (!existsSync(slidesPath)) {
12
+ console.error(`Error: File not found: ${slidesPath}`);
13
+ process.exit(1);
14
+ }
15
+ let pw;
16
+ try {
17
+ pw = await Function("return import(\"playwright\")")();
18
+ } catch {
19
+ console.error("Error: Playwright is required for export.\nInstall it: vp add playwright && vp dlx playwright install chromium");
20
+ process.exit(1);
21
+ }
22
+ const tmpDir = resolve(dirname(slidesPath), ".reslide");
23
+ generateEntryFiles(slidesPath, tmpDir);
24
+ const server = await createServer({
25
+ root: tmpDir,
26
+ plugins: [reslide()],
27
+ server: { port: options.port }
28
+ });
29
+ await server.listen();
30
+ const url = `http://localhost:${options.port}`;
31
+ console.log("Starting export...");
32
+ const browser = await pw.chromium.launch();
33
+ const page = await browser.newPage();
34
+ await page.setViewportSize({
35
+ width: options.width,
36
+ height: options.height
37
+ });
38
+ await page.goto(url, { waitUntil: "networkidle" });
39
+ await page.waitForSelector(".reslide-deck");
40
+ const totalText = await page.textContent(".reslide-slide-number");
41
+ const total = totalText ? parseInt(totalText.split("/")[1].trim(), 10) : 1;
42
+ console.log(`Found ${total} slides`);
43
+ const outDir = resolve(options.out);
44
+ mkdirSync(outDir, { recursive: true });
45
+ if (options.format === "png") for (let i = 0; i < total; i++) {
46
+ if (i > 0) {
47
+ await page.keyboard.press("ArrowRight");
48
+ await page.waitForTimeout(400);
49
+ }
50
+ const path = resolve(outDir, `slide-${String(i + 1).padStart(3, "0")}.png`);
51
+ await page.screenshot({ path });
52
+ console.log(` Exported: ${path}`);
53
+ }
54
+ else {
55
+ const screenshots = [];
56
+ for (let i = 0; i < total; i++) {
57
+ if (i > 0) {
58
+ await page.keyboard.press("ArrowRight");
59
+ await page.waitForTimeout(400);
60
+ }
61
+ screenshots.push(await page.screenshot({ type: "png" }));
62
+ }
63
+ const pdfPage = await browser.newPage();
64
+ const htmlSlides = screenshots.map((buf) => `<div style="page-break-after:always;width:${options.width}px;height:${options.height}px"><img src="data:image/png;base64,${buf.toString("base64")}" style="width:100%;height:100%;object-fit:contain"/></div>`).join("");
65
+ await pdfPage.setContent(`<html><head><style>@page{size:${options.width}px ${options.height}px;margin:0}body{margin:0}</style></head><body>${htmlSlides}</body></html>`);
66
+ const pdfPath = resolve(outDir, "slides.pdf");
67
+ await pdfPage.pdf({
68
+ path: pdfPath,
69
+ width: `${options.width}px`,
70
+ height: `${options.height}px`,
71
+ printBackground: true
72
+ });
73
+ console.log(` Exported: ${pdfPath}`);
74
+ }
75
+ await browser.close();
76
+ await server.close();
77
+ console.log("\nExport complete!");
78
+ }
79
+ //#endregion
80
+ export { exportSlides };
@@ -0,0 +1,31 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/vite-plugin.d.ts
4
+ interface ReslidePluginOptions {
5
+ /** Additional remark plugins */
6
+ remarkPlugins?: unknown[];
7
+ /** Additional rehype plugins */
8
+ rehypePlugins?: unknown[];
9
+ /** Enable Mermaid diagram rendering via CDN */
10
+ mermaid?: boolean;
11
+ }
12
+ /**
13
+ * Vite plugin for reslide presentations.
14
+ * Sets up MDX processing with reslide's remark plugins.
15
+ *
16
+ * For LaTeX math support:
17
+ * ```ts
18
+ * import remarkMath from 'remark-math'
19
+ * import rehypeKatex from 'rehype-katex'
20
+ * reslide({ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex] })
21
+ * ```
22
+ *
23
+ * For Shiki syntax highlighting:
24
+ * ```ts
25
+ * import rehypePrettyCode from 'rehype-pretty-code'
26
+ * reslide({ rehypePlugins: [[rehypePrettyCode, { theme: 'one-dark-pro' }]] })
27
+ * ```
28
+ */
29
+ declare function reslide(options?: ReslidePluginOptions): Plugin[];
30
+ //#endregion
31
+ export { ReslidePluginOptions, reslide };
@@ -0,0 +1,70 @@
1
+ import mdx from "@mdx-js/rollup";
2
+ import { remarkClick, remarkMark, remarkSlides } from "@reslide-dev/mdx";
3
+ import remarkDirective from "remark-directive";
4
+ //#region src/vite-plugin.ts
5
+ /**
6
+ * Vite plugin for reslide presentations.
7
+ * Sets up MDX processing with reslide's remark plugins.
8
+ *
9
+ * For LaTeX math support:
10
+ * ```ts
11
+ * import remarkMath from 'remark-math'
12
+ * import rehypeKatex from 'rehype-katex'
13
+ * reslide({ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex] })
14
+ * ```
15
+ *
16
+ * For Shiki syntax highlighting:
17
+ * ```ts
18
+ * import rehypePrettyCode from 'rehype-pretty-code'
19
+ * reslide({ rehypePlugins: [[rehypePrettyCode, { theme: 'one-dark-pro' }]] })
20
+ * ```
21
+ */
22
+ function reslide(options = {}) {
23
+ const { remarkPlugins = [], rehypePlugins = [], mermaid = false } = options;
24
+ const plugins = [mdx({
25
+ remarkPlugins: [
26
+ remarkDirective,
27
+ remarkSlides,
28
+ remarkClick,
29
+ remarkMark,
30
+ ...remarkPlugins
31
+ ],
32
+ rehypePlugins,
33
+ providerImportSource: void 0
34
+ }), {
35
+ name: "reslide:inject-react",
36
+ config() {
37
+ return { optimizeDeps: { include: [
38
+ "react",
39
+ "react-dom",
40
+ "@reslide-dev/core"
41
+ ] } };
42
+ }
43
+ }];
44
+ if (mermaid) plugins.push({
45
+ name: "reslide:mermaid",
46
+ transformIndexHtml() {
47
+ return [{
48
+ tag: "script",
49
+ attrs: { type: "module" },
50
+ children: `
51
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
52
+ mermaid.initialize({ startOnLoad: false, theme: 'default' });
53
+ const observer = new MutationObserver(() => {
54
+ document.querySelectorAll('pre code.language-mermaid').forEach(async (el) => {
55
+ if (el.dataset.mermaidRendered) return;
56
+ el.dataset.mermaidRendered = 'true';
57
+ const container = el.closest('pre');
58
+ const { svg } = await mermaid.render('mermaid-' + Math.random().toString(36).slice(2), el.textContent);
59
+ if (container) container.outerHTML = '<div class="reslide-mermaid">' + svg + '</div>';
60
+ });
61
+ });
62
+ observer.observe(document.body, { childList: true, subtree: true });
63
+ `
64
+ }];
65
+ }
66
+ });
67
+ return plugins;
68
+ }
69
+ //#endregion
70
+ export { reslide };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@reslide-dev/cli",
3
+ "version": "0.1.0",
4
+ "description": "Vite-based dev/build CLI for reslide presentations",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "reslide": "./dist/cli.mjs"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "template"
12
+ ],
13
+ "type": "module",
14
+ "exports": {
15
+ "./cli": "./dist/cli.mjs",
16
+ "./export": "./dist/export.mjs",
17
+ "./vite-plugin": "./dist/vite-plugin.mjs",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "vp pack",
25
+ "dev": "vp pack --watch"
26
+ },
27
+ "dependencies": {
28
+ "@mdx-js/rollup": "^3.1.0",
29
+ "@reslide-dev/core": "workspace:*",
30
+ "@reslide-dev/mdx": "workspace:*",
31
+ "cac": "^6.7.14",
32
+ "react": "^19.1.0",
33
+ "react-dom": "^19.1.0",
34
+ "remark-directive": "catalog:"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^25",
38
+ "@types/react": "^19",
39
+ "@types/react-dom": "^19",
40
+ "vite": "catalog:",
41
+ "vite-plus": "catalog:",
42
+ "vitest": "catalog:"
43
+ }
44
+ }