@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 +1 -0
- package/dist/cli.mjs +93 -0
- package/dist/export.d.mts +15 -0
- package/dist/export.mjs +80 -0
- package/dist/vite-plugin.d.mts +31 -0
- package/dist/vite-plugin.mjs +70 -0
- package/package.json +44 -0
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 };
|
package/dist/export.mjs
ADDED
|
@@ -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
|
+
}
|