lazyslides 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/LICENSE +21 -0
- package/README.md +207 -0
- package/_includes/slides/_nested-list.njk +26 -0
- package/_includes/slides/agenda.njk +17 -0
- package/_includes/slides/center.njk +16 -0
- package/_includes/slides/code.njk +15 -0
- package/_includes/slides/columns.njk +35 -0
- package/_includes/slides/comparison.njk +29 -0
- package/_includes/slides/content.njk +22 -0
- package/_includes/slides/footer.njk +58 -0
- package/_includes/slides/funnel.njk +30 -0
- package/_includes/slides/hero.njk +27 -0
- package/_includes/slides/image-overlay.njk +21 -0
- package/_includes/slides/metrics.njk +27 -0
- package/_includes/slides/quote.njk +17 -0
- package/_includes/slides/section.njk +6 -0
- package/_includes/slides/split-wide.njk +30 -0
- package/_includes/slides/split.njk +30 -0
- package/_includes/slides/table.njk +31 -0
- package/_includes/slides/timeline.njk +30 -0
- package/_includes/slides/title.njk +17 -0
- package/_layouts/default.njk +20 -0
- package/_layouts/presentation.njk +240 -0
- package/assets/css/themes/default.css +62 -0
- package/assets/css/vendor/glightbox.min.css +1 -0
- package/assets/js/vendor/glightbox.min.js +1 -0
- package/assets/reveal.js/LICENSE +19 -0
- package/assets/reveal.js/dist/reset.css +30 -0
- package/assets/reveal.js/dist/reveal.css +8 -0
- package/assets/reveal.js/dist/reveal.esm.js +9 -0
- package/assets/reveal.js/dist/reveal.esm.js.map +1 -0
- package/assets/reveal.js/dist/reveal.js +9 -0
- package/assets/reveal.js/dist/reveal.js.map +1 -0
- package/assets/reveal.js/dist/theme/beige.css +366 -0
- package/assets/reveal.js/dist/theme/black-contrast.css +362 -0
- package/assets/reveal.js/dist/theme/black.css +359 -0
- package/assets/reveal.js/dist/theme/blood.css +392 -0
- package/assets/reveal.js/dist/theme/dracula.css +385 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/LICENSE +2 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/league-gothic.css +10 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/league-gothic.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/league-gothic.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/league-gothic/league-gothic.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/LICENSE +45 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff +0 -0
- package/assets/reveal.js/dist/theme/fonts/source-sans-pro/source-sans-pro.css +39 -0
- package/assets/reveal.js/dist/theme/league.css +368 -0
- package/assets/reveal.js/dist/theme/moon.css +362 -0
- package/assets/reveal.js/dist/theme/night.css +360 -0
- package/assets/reveal.js/dist/theme/serif.css +363 -0
- package/assets/reveal.js/dist/theme/simple.css +362 -0
- package/assets/reveal.js/dist/theme/sky.css +370 -0
- package/assets/reveal.js/dist/theme/solarized.css +363 -0
- package/assets/reveal.js/dist/theme/white-contrast.css +362 -0
- package/assets/reveal.js/dist/theme/white.css +359 -0
- package/assets/reveal.js/dist/theme/white_contrast_compact_verbatim_headers.css +360 -0
- package/assets/reveal.js/plugin/highlight/highlight.esm.js +5 -0
- package/assets/reveal.js/plugin/highlight/highlight.js +5 -0
- package/assets/reveal.js/plugin/highlight/monokai.css +71 -0
- package/assets/reveal.js/plugin/highlight/plugin.js +439 -0
- package/assets/reveal.js/plugin/highlight/zenburn.css +80 -0
- package/assets/reveal.js/plugin/markdown/markdown.esm.js +7 -0
- package/assets/reveal.js/plugin/markdown/markdown.js +7 -0
- package/assets/reveal.js/plugin/markdown/plugin.js +491 -0
- package/assets/reveal.js/plugin/notes/notes.esm.js +1 -0
- package/assets/reveal.js/plugin/notes/notes.js +1 -0
- package/assets/reveal.js/plugin/notes/plugin.js +267 -0
- package/assets/reveal.js/plugin/notes/speaker-view.html +898 -0
- package/cli.js +80 -0
- package/data/site.json +3 -0
- package/index.js +153 -0
- package/lib/export-pdf.js +137 -0
- package/lib/init.js +154 -0
- package/lib/renumber.js +181 -0
- package/lib/validate.js +196 -0
- package/package.json +76 -0
- package/scaffold/CLAUDE.md +283 -0
- package/scaffold/README.md +29 -0
- package/scaffold/_template/index.md +78 -0
- package/scaffold/_template/outline.md +32 -0
- package/scaffold/claude-commands/add-slide.md +61 -0
- package/scaffold/claude-commands/create-outline.md +75 -0
- package/scaffold/claude-commands/new-presentation.md +218 -0
- package/scaffold/claude-commands/refine-slides.md +62 -0
- package/scaffold/claude-commands/research-topic.md +66 -0
- package/scaffold/claude-commands/validate.md +47 -0
- package/scaffold/claude-settings.json +7 -0
- package/scaffold/eleventy.config.js +11 -0
- package/scaffold/gitignore +5 -0
- package/scaffold/my-first-deck/index.md +26 -0
- package/scaffold/nvmrc +1 -0
- package/scaffold/package.json.tmpl +25 -0
- package/scaffold/presentations.11tydata.js +11 -0
- package/scaffold/styles.css +3 -0
- package/src/styles.css +2077 -0
package/cli.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const command = args[0];
|
|
5
|
+
|
|
6
|
+
const HELP = `
|
|
7
|
+
Usage: lazyslides <command> [options]
|
|
8
|
+
|
|
9
|
+
Commands:
|
|
10
|
+
init [dir] Scaffold a new LazySlides project
|
|
11
|
+
validate Validate presentation YAML files
|
|
12
|
+
renumber [file] Renumber slide comments
|
|
13
|
+
pdf [name] Export presentations to PDF
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--help Show this help message
|
|
17
|
+
--version Show version number
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
if (!command || command === "--help" || command === "-h") {
|
|
22
|
+
console.log(HELP.trim());
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (command === "--version" || command === "-v") {
|
|
27
|
+
const { createRequire } = await import("node:module");
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const pkg = require("./package.json");
|
|
30
|
+
console.log(pkg.version);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
switch (command) {
|
|
35
|
+
case "init": {
|
|
36
|
+
const dir = args[1];
|
|
37
|
+
if (!dir) {
|
|
38
|
+
console.error("Error: Please specify a project directory.\n");
|
|
39
|
+
console.error(" lazyslides init my-deck");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const { run } = await import("./lib/init.js");
|
|
43
|
+
await run({ dir });
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
case "validate": {
|
|
48
|
+
const { run } = await import("./lib/validate.js");
|
|
49
|
+
const result = await run({
|
|
50
|
+
renumber: args.includes("--renumber"),
|
|
51
|
+
});
|
|
52
|
+
if (result.errors.length > 0) process.exit(1);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case "renumber": {
|
|
57
|
+
const { run } = await import("./lib/renumber.js");
|
|
58
|
+
const files = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
59
|
+
await run({ files: files.length > 0 ? files : undefined });
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case "pdf": {
|
|
64
|
+
const { run } = await import("./lib/export-pdf.js");
|
|
65
|
+
const name = args[1];
|
|
66
|
+
await run({ name });
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
console.error(`Unknown command: ${command}\n`);
|
|
72
|
+
console.log(HELP.trim());
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch((err) => {
|
|
78
|
+
console.error(err.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|
package/data/site.json
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const pkgRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* LazySlides Eleventy plugin.
|
|
9
|
+
*
|
|
10
|
+
* Registers layouts, templates, assets, filters, and a virtual
|
|
11
|
+
* presentations index page so that a user project only needs a
|
|
12
|
+
* six-line eleventy.config.js to get the full engine.
|
|
13
|
+
*/
|
|
14
|
+
export default function lazyslides(eleventyConfig, options = {}) {
|
|
15
|
+
// ---------------------------------------------------------------
|
|
16
|
+
// 1. Filters & Nunjucks test (unchanged logic from .eleventy.js)
|
|
17
|
+
// ---------------------------------------------------------------
|
|
18
|
+
eleventyConfig.addFilter("resolveImage", (src) => {
|
|
19
|
+
if (!src || src.includes("://") || src.startsWith("/")) return src;
|
|
20
|
+
return "../" + src;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
eleventyConfig.addFilter("isMapping", (val) => {
|
|
24
|
+
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------
|
|
28
|
+
// 2. Nunjucks search paths — makes {% include "slides/…" %} and
|
|
29
|
+
// layout: presentation.njk resolve to files inside the package.
|
|
30
|
+
// User-local paths are searched first (Eleventy default) so
|
|
31
|
+
// users can override any template.
|
|
32
|
+
// ---------------------------------------------------------------
|
|
33
|
+
eleventyConfig.amendLibrary("njk", (env) => {
|
|
34
|
+
// Register the "mapping" test
|
|
35
|
+
env.addTest("mapping", (val) => {
|
|
36
|
+
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Add package paths *after* the user's local paths
|
|
40
|
+
env.loaders[0].searchPaths.push(path.join(pkgRoot, "_includes"));
|
|
41
|
+
env.loaders[0].searchPaths.push(path.join(pkgRoot, "_layouts"));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------
|
|
45
|
+
// 3. Passthrough copies — map engine assets to output
|
|
46
|
+
// ---------------------------------------------------------------
|
|
47
|
+
const realPkgRoot = fs.realpathSync(pkgRoot);
|
|
48
|
+
|
|
49
|
+
eleventyConfig.addPassthroughCopy({
|
|
50
|
+
[path.join(realPkgRoot, "assets/reveal.js")]: "assets/reveal.js",
|
|
51
|
+
});
|
|
52
|
+
eleventyConfig.addPassthroughCopy({
|
|
53
|
+
[path.join(realPkgRoot, "assets/css/themes")]: "assets/css/themes",
|
|
54
|
+
});
|
|
55
|
+
eleventyConfig.addPassthroughCopy({
|
|
56
|
+
[path.join(realPkgRoot, "assets/css/vendor")]: "assets/css/vendor",
|
|
57
|
+
});
|
|
58
|
+
eleventyConfig.addPassthroughCopy({
|
|
59
|
+
[path.join(realPkgRoot, "assets/js/vendor")]: "assets/js/vendor",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// User's local theme overrides
|
|
63
|
+
eleventyConfig.addPassthroughCopy({
|
|
64
|
+
"themes/": "assets/css/themes/",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Per-presentation images
|
|
68
|
+
eleventyConfig.addPassthroughCopy("presentations/*/images");
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------
|
|
71
|
+
// 3b. Collection — only index.md files are presentations
|
|
72
|
+
// ---------------------------------------------------------------
|
|
73
|
+
eleventyConfig.addCollection("presentations", (collectionApi) => {
|
|
74
|
+
return collectionApi.getFilteredByGlob("presentations/*/index.md");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------
|
|
78
|
+
// 4. Global data — site.title with option override
|
|
79
|
+
// ---------------------------------------------------------------
|
|
80
|
+
const siteData = JSON.parse(
|
|
81
|
+
fs.readFileSync(path.join(realPkgRoot, "data/site.json"), "utf-8")
|
|
82
|
+
);
|
|
83
|
+
eleventyConfig.addGlobalData("site", {
|
|
84
|
+
title: options.siteTitle || siteData.title,
|
|
85
|
+
...options.siteData,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------
|
|
89
|
+
// 5. Virtual layouts — loaded from the package so users don't
|
|
90
|
+
// need _layouts/ locally (but can override by creating one)
|
|
91
|
+
// ---------------------------------------------------------------
|
|
92
|
+
const layoutsDir = path.join(realPkgRoot, "_layouts");
|
|
93
|
+
for (const file of fs.readdirSync(layoutsDir)) {
|
|
94
|
+
if (!file.endsWith(".njk")) continue;
|
|
95
|
+
const content = fs.readFileSync(path.join(layoutsDir, file), "utf-8");
|
|
96
|
+
eleventyConfig.addTemplate(`_layouts/${file}`, content);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------
|
|
100
|
+
// 6. Virtual template — presentations index page
|
|
101
|
+
// ---------------------------------------------------------------
|
|
102
|
+
eleventyConfig.addTemplate("presentations-index.njk", `---
|
|
103
|
+
layout: default.njk
|
|
104
|
+
title: Presentations
|
|
105
|
+
permalink: /presentations/
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
<div style="text-align: center; margin-bottom: 2rem;">
|
|
109
|
+
<h1 style="font-family: 'Source Serif 4', Georgia, serif; font-size: 2rem; font-weight: 700; color: #0f172a; margin-bottom: 0.5rem;">LazySlides</h1>
|
|
110
|
+
<p style="color: #64748b; font-size: 1rem;">AI-native presentations from YAML. Powered by Eleventy + Reveal.js + Tailwind CSS.</p>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; margin-bottom: 3rem;">
|
|
114
|
+
{% for presentation in collections.presentations %}
|
|
115
|
+
<a href="{{ presentation.url | url }}" style="display: block; padding: 1.5rem; border: 1px solid #e2e8f0; border-radius: 8px; text-decoration: none; transition: all 0.2s ease; background: white;">
|
|
116
|
+
<h2 style="font-family: 'Source Serif 4', Georgia, serif; font-size: 1.1rem; font-weight: 600; color: #0f172a; margin: 0 0 0.5rem 0;">{{ presentation.data.title }}</h2>
|
|
117
|
+
{% if presentation.data.description %}
|
|
118
|
+
<p style="font-size: 0.85rem; color: #64748b; margin: 0;">{{ presentation.data.description }}</p>
|
|
119
|
+
{% endif %}
|
|
120
|
+
</a>
|
|
121
|
+
{% endfor %}
|
|
122
|
+
</div>
|
|
123
|
+
`);
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------
|
|
126
|
+
// 6b. Virtual template — root redirect to presentations index
|
|
127
|
+
// ---------------------------------------------------------------
|
|
128
|
+
eleventyConfig.addTemplate("root-redirect.njk", `---
|
|
129
|
+
permalink: /index.html
|
|
130
|
+
eleventyExcludeFromCollections: true
|
|
131
|
+
---
|
|
132
|
+
<!DOCTYPE html>
|
|
133
|
+
<html>
|
|
134
|
+
<head><meta http-equiv="refresh" content="0; url={{ '/presentations/example/' | url }}"></head>
|
|
135
|
+
<body></body>
|
|
136
|
+
</html>
|
|
137
|
+
`);
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------
|
|
140
|
+
// 7. Ignores
|
|
141
|
+
// ---------------------------------------------------------------
|
|
142
|
+
eleventyConfig.ignores.add("node_modules/**");
|
|
143
|
+
eleventyConfig.ignores.add("README.md");
|
|
144
|
+
eleventyConfig.ignores.add("CONTRIBUTING.md");
|
|
145
|
+
eleventyConfig.ignores.add("CLAUDE.md");
|
|
146
|
+
eleventyConfig.ignores.add("LICENSE");
|
|
147
|
+
eleventyConfig.ignores.add(".claude/**");
|
|
148
|
+
eleventyConfig.ignores.add("presentations/_template/**");
|
|
149
|
+
eleventyConfig.ignores.add("pages/**");
|
|
150
|
+
eleventyConfig.ignores.add("scaffold/**");
|
|
151
|
+
eleventyConfig.ignores.add("docs/**");
|
|
152
|
+
eleventyConfig.ignores.add("bin/**");
|
|
153
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import http from "node:http";
|
|
5
|
+
|
|
6
|
+
const PORT = 4100;
|
|
7
|
+
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
8
|
+
const OUTPUT_DIR = "_pdfs";
|
|
9
|
+
const TIMEOUT_SECONDS = 30;
|
|
10
|
+
|
|
11
|
+
let serverProcess = null;
|
|
12
|
+
|
|
13
|
+
function cleanup() {
|
|
14
|
+
if (serverProcess) {
|
|
15
|
+
console.log("\n Stopping server...");
|
|
16
|
+
serverProcess.kill();
|
|
17
|
+
serverProcess = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function waitForServer() {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
let elapsed = 0;
|
|
24
|
+
const interval = setInterval(() => {
|
|
25
|
+
const req = http.get(BASE_URL, (res) => {
|
|
26
|
+
res.resume();
|
|
27
|
+
clearInterval(interval);
|
|
28
|
+
resolve();
|
|
29
|
+
});
|
|
30
|
+
req.on("error", () => {
|
|
31
|
+
elapsed++;
|
|
32
|
+
if (elapsed >= TIMEOUT_SECONDS) {
|
|
33
|
+
clearInterval(interval);
|
|
34
|
+
reject(new Error(`Server failed to start within ${TIMEOUT_SECONDS}s`));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
req.end();
|
|
38
|
+
}, 1000);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function runDecktape(name) {
|
|
43
|
+
const url = `${BASE_URL}/presentations/${name}/?pdf`;
|
|
44
|
+
const output = path.join(OUTPUT_DIR, `${name}.pdf`);
|
|
45
|
+
|
|
46
|
+
console.log(` → Exporting: ${name}`);
|
|
47
|
+
try {
|
|
48
|
+
execSync(
|
|
49
|
+
`npx decktape reveal --size 1920x1080 --pause 1000 --load-pause 2000 "${url}" "${output}"`,
|
|
50
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
51
|
+
);
|
|
52
|
+
console.log(` \u2713 ${output}`);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
console.log(` \u2717 Failed: ${name}`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getAvailablePresentations(cwd) {
|
|
61
|
+
const dir = path.join(cwd, "presentations");
|
|
62
|
+
return fs
|
|
63
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
64
|
+
.filter((d) => d.isDirectory() && d.name !== "_template")
|
|
65
|
+
.map((d) => d.name);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Export presentations to PDF using DeckTape.
|
|
70
|
+
* @param {object} [opts]
|
|
71
|
+
* @param {string} [opts.cwd] - working directory (defaults to process.cwd())
|
|
72
|
+
* @param {string} [opts.name] - specific presentation name to export
|
|
73
|
+
*/
|
|
74
|
+
export async function run(opts = {}) {
|
|
75
|
+
const cwd = opts.cwd || process.cwd();
|
|
76
|
+
const requestedName = opts.name;
|
|
77
|
+
const available = getAvailablePresentations(cwd);
|
|
78
|
+
|
|
79
|
+
if (requestedName) {
|
|
80
|
+
if (!available.includes(requestedName)) {
|
|
81
|
+
console.error(`Presentation not found: ${requestedName}`);
|
|
82
|
+
console.error(" Available presentations:");
|
|
83
|
+
for (const name of available) {
|
|
84
|
+
console.error(` - ${name}`);
|
|
85
|
+
}
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const presentations = requestedName ? [requestedName] : available;
|
|
91
|
+
|
|
92
|
+
// Build the site
|
|
93
|
+
console.log("Building site...");
|
|
94
|
+
execSync("pnpm run build", { stdio: "inherit", cwd });
|
|
95
|
+
|
|
96
|
+
// Start server
|
|
97
|
+
console.log(`\nStarting server on port ${PORT}...`);
|
|
98
|
+
serverProcess = spawn("npx", ["eleventy", "--serve", "--port", String(PORT)], {
|
|
99
|
+
stdio: "ignore",
|
|
100
|
+
cwd,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
process.on("exit", cleanup);
|
|
104
|
+
process.on("SIGINT", () => { cleanup(); process.exit(1); });
|
|
105
|
+
process.on("SIGTERM", () => { cleanup(); process.exit(1); });
|
|
106
|
+
|
|
107
|
+
// Wait for server
|
|
108
|
+
console.log("Waiting for server...");
|
|
109
|
+
await waitForServer();
|
|
110
|
+
console.log("Server ready");
|
|
111
|
+
|
|
112
|
+
// Create output directory
|
|
113
|
+
fs.mkdirSync(path.join(cwd, OUTPUT_DIR), { recursive: true });
|
|
114
|
+
|
|
115
|
+
// Export presentations
|
|
116
|
+
let success = 0;
|
|
117
|
+
let failure = 0;
|
|
118
|
+
console.log(`\nExporting ${presentations.length} presentation(s) to PDF...\n`);
|
|
119
|
+
|
|
120
|
+
for (const name of presentations) {
|
|
121
|
+
if (runDecktape(name)) {
|
|
122
|
+
success++;
|
|
123
|
+
} else {
|
|
124
|
+
failure++;
|
|
125
|
+
}
|
|
126
|
+
console.log();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
cleanup();
|
|
130
|
+
|
|
131
|
+
// Summary
|
|
132
|
+
console.log("\u2501".repeat(30));
|
|
133
|
+
console.log(`Exported: ${success} Failed: ${failure}`);
|
|
134
|
+
if (failure > 0) {
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const pkgRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
6
|
+
const scaffoldDir = path.join(pkgRoot, "scaffold");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Copy a directory recursively.
|
|
10
|
+
*/
|
|
11
|
+
function copyDir(src, dest) {
|
|
12
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
13
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
14
|
+
const srcPath = path.join(src, entry.name);
|
|
15
|
+
const destPath = path.join(dest, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
copyDir(srcPath, destPath);
|
|
18
|
+
} else {
|
|
19
|
+
fs.copyFileSync(srcPath, destPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Scaffold a new LazySlides project.
|
|
26
|
+
* @param {object} opts
|
|
27
|
+
* @param {string} opts.dir - target directory path
|
|
28
|
+
*/
|
|
29
|
+
export async function run(opts) {
|
|
30
|
+
const targetDir = path.resolve(opts.dir);
|
|
31
|
+
const projectName = path.basename(targetDir);
|
|
32
|
+
|
|
33
|
+
// Check if directory already has files
|
|
34
|
+
if (fs.existsSync(targetDir)) {
|
|
35
|
+
const existing = fs.readdirSync(targetDir);
|
|
36
|
+
if (existing.length > 0) {
|
|
37
|
+
throw new Error(`Directory "${targetDir}" is not empty.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
console.log(`\nCreating a new LazySlides project in ${targetDir}\n`);
|
|
44
|
+
|
|
45
|
+
// 1. Copy scaffold/eleventy.config.js
|
|
46
|
+
fs.copyFileSync(
|
|
47
|
+
path.join(scaffoldDir, "eleventy.config.js"),
|
|
48
|
+
path.join(targetDir, "eleventy.config.js")
|
|
49
|
+
);
|
|
50
|
+
console.log(" Created eleventy.config.js");
|
|
51
|
+
|
|
52
|
+
// 2. Create src/styles.css from scaffold
|
|
53
|
+
fs.mkdirSync(path.join(targetDir, "src"), { recursive: true });
|
|
54
|
+
fs.copyFileSync(
|
|
55
|
+
path.join(scaffoldDir, "styles.css"),
|
|
56
|
+
path.join(targetDir, "src", "styles.css")
|
|
57
|
+
);
|
|
58
|
+
console.log(" Created src/styles.css");
|
|
59
|
+
|
|
60
|
+
// 3. Create presentations/ with template and starter deck
|
|
61
|
+
const presentationsDir = path.join(targetDir, "presentations");
|
|
62
|
+
fs.mkdirSync(presentationsDir, { recursive: true });
|
|
63
|
+
|
|
64
|
+
// presentations.11tydata.js
|
|
65
|
+
fs.copyFileSync(
|
|
66
|
+
path.join(scaffoldDir, "presentations.11tydata.js"),
|
|
67
|
+
path.join(presentationsDir, "presentations.11tydata.js")
|
|
68
|
+
);
|
|
69
|
+
console.log(" Created presentations/presentations.11tydata.js");
|
|
70
|
+
|
|
71
|
+
// _template/
|
|
72
|
+
copyDir(
|
|
73
|
+
path.join(scaffoldDir, "_template"),
|
|
74
|
+
path.join(presentationsDir, "_template")
|
|
75
|
+
);
|
|
76
|
+
console.log(" Created presentations/_template/");
|
|
77
|
+
|
|
78
|
+
// my-first-deck/
|
|
79
|
+
copyDir(
|
|
80
|
+
path.join(scaffoldDir, "my-first-deck"),
|
|
81
|
+
path.join(presentationsDir, "my-first-deck")
|
|
82
|
+
);
|
|
83
|
+
console.log(" Created presentations/my-first-deck/");
|
|
84
|
+
|
|
85
|
+
// 4. Create themes/ directory (empty, for user overrides)
|
|
86
|
+
fs.mkdirSync(path.join(targetDir, "themes"), { recursive: true });
|
|
87
|
+
// Add a .gitkeep so git tracks the empty directory
|
|
88
|
+
fs.writeFileSync(path.join(targetDir, "themes", ".gitkeep"), "");
|
|
89
|
+
console.log(" Created themes/");
|
|
90
|
+
|
|
91
|
+
// 5. package.json from template
|
|
92
|
+
const pkgTemplate = fs.readFileSync(
|
|
93
|
+
path.join(scaffoldDir, "package.json.tmpl"),
|
|
94
|
+
"utf-8"
|
|
95
|
+
);
|
|
96
|
+
const pkgContent = pkgTemplate.replace(/\{\{name\}\}/g, projectName);
|
|
97
|
+
fs.writeFileSync(path.join(targetDir, "package.json"), pkgContent);
|
|
98
|
+
console.log(" Created package.json");
|
|
99
|
+
|
|
100
|
+
// 6. Rename-on-copy files (.gitignore, .nvmrc)
|
|
101
|
+
fs.copyFileSync(
|
|
102
|
+
path.join(scaffoldDir, "gitignore"),
|
|
103
|
+
path.join(targetDir, ".gitignore")
|
|
104
|
+
);
|
|
105
|
+
console.log(" Created .gitignore");
|
|
106
|
+
|
|
107
|
+
fs.copyFileSync(
|
|
108
|
+
path.join(scaffoldDir, "nvmrc"),
|
|
109
|
+
path.join(targetDir, ".nvmrc")
|
|
110
|
+
);
|
|
111
|
+
console.log(" Created .nvmrc");
|
|
112
|
+
|
|
113
|
+
// 7. README.md
|
|
114
|
+
const readmeTemplate = fs.readFileSync(
|
|
115
|
+
path.join(scaffoldDir, "README.md"),
|
|
116
|
+
"utf-8"
|
|
117
|
+
);
|
|
118
|
+
const readmeContent = readmeTemplate.replace(/\{\{name\}\}/g, projectName);
|
|
119
|
+
fs.writeFileSync(path.join(targetDir, "README.md"), readmeContent);
|
|
120
|
+
console.log(" Created README.md");
|
|
121
|
+
|
|
122
|
+
// 8. CLAUDE.md (Claude Code integration)
|
|
123
|
+
fs.copyFileSync(
|
|
124
|
+
path.join(scaffoldDir, "CLAUDE.md"),
|
|
125
|
+
path.join(targetDir, "CLAUDE.md")
|
|
126
|
+
);
|
|
127
|
+
console.log(" Created CLAUDE.md");
|
|
128
|
+
|
|
129
|
+
// 9. .claude/ directory (commands + settings)
|
|
130
|
+
const claudeDir = path.join(targetDir, ".claude");
|
|
131
|
+
const commandsDir = path.join(claudeDir, "commands");
|
|
132
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
133
|
+
const scaffoldCommandsDir = path.join(scaffoldDir, "claude-commands");
|
|
134
|
+
for (const file of fs.readdirSync(scaffoldCommandsDir)) {
|
|
135
|
+
fs.copyFileSync(
|
|
136
|
+
path.join(scaffoldCommandsDir, file),
|
|
137
|
+
path.join(commandsDir, file)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
fs.copyFileSync(
|
|
141
|
+
path.join(scaffoldDir, "claude-settings.json"),
|
|
142
|
+
path.join(claudeDir, "settings.json")
|
|
143
|
+
);
|
|
144
|
+
console.log(" Created .claude/ (Claude Code integration)");
|
|
145
|
+
|
|
146
|
+
// Done!
|
|
147
|
+
console.log(`
|
|
148
|
+
Done! To get started:
|
|
149
|
+
|
|
150
|
+
cd ${projectName}
|
|
151
|
+
pnpm install
|
|
152
|
+
pnpm run dev
|
|
153
|
+
`);
|
|
154
|
+
}
|