lazyslides 0.3.0 → 0.3.1
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/lib/export-pdf.js +125 -27
- package/package.json +2 -2
package/lib/export-pdf.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import http from "node:http";
|
|
@@ -6,14 +6,18 @@ import http from "node:http";
|
|
|
6
6
|
const PORT = 4100;
|
|
7
7
|
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
8
8
|
const OUTPUT_DIR = "_pdfs";
|
|
9
|
-
const
|
|
9
|
+
const SERVER_TIMEOUT_SECONDS = 60;
|
|
10
|
+
const BUILD_IDLE_TIMEOUT_MS = 15000;
|
|
11
|
+
const BUILD_HARD_TIMEOUT_MS = 180000;
|
|
10
12
|
|
|
11
13
|
let serverProcess = null;
|
|
12
14
|
|
|
13
15
|
function cleanup() {
|
|
14
16
|
if (serverProcess) {
|
|
15
17
|
console.log("\n Stopping server...");
|
|
16
|
-
|
|
18
|
+
try {
|
|
19
|
+
serverProcess.kill("SIGTERM");
|
|
20
|
+
} catch {}
|
|
17
21
|
serverProcess = null;
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -29,9 +33,9 @@ function waitForServer() {
|
|
|
29
33
|
});
|
|
30
34
|
req.on("error", () => {
|
|
31
35
|
elapsed++;
|
|
32
|
-
if (elapsed >=
|
|
36
|
+
if (elapsed >= SERVER_TIMEOUT_SECONDS) {
|
|
33
37
|
clearInterval(interval);
|
|
34
|
-
reject(new Error(`Server failed to start within ${
|
|
38
|
+
reject(new Error(`Server failed to start within ${SERVER_TIMEOUT_SECONDS}s`));
|
|
35
39
|
}
|
|
36
40
|
});
|
|
37
41
|
req.end();
|
|
@@ -39,29 +43,107 @@ function waitForServer() {
|
|
|
39
43
|
});
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Run `pnpm run build`, but tolerate the eleventy child not exiting cleanly
|
|
48
|
+
* (known issue: d2 WASM / chokidar handles can keep the event loop alive after
|
|
49
|
+
* "Wrote N files"). We detect idle stdout and force-kill.
|
|
50
|
+
*/
|
|
51
|
+
function runBuild(cwd) {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
// Pipe stdio so we can observe activity and reset the idle timer.
|
|
54
|
+
const child = spawn("pnpm", ["run", "build"], {
|
|
55
|
+
cwd,
|
|
56
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
let idleTimer = null;
|
|
60
|
+
let hardTimer = null;
|
|
61
|
+
let resolved = false;
|
|
62
|
+
|
|
63
|
+
const done = (err) => {
|
|
64
|
+
if (resolved) return;
|
|
65
|
+
resolved = true;
|
|
66
|
+
clearTimeout(idleTimer);
|
|
67
|
+
clearTimeout(hardTimer);
|
|
68
|
+
try { child.kill("SIGKILL"); } catch {}
|
|
69
|
+
err ? reject(err) : resolve();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const bumpIdle = () => {
|
|
73
|
+
clearTimeout(idleTimer);
|
|
74
|
+
idleTimer = setTimeout(() => {
|
|
75
|
+
console.log(`\n Build child idle for ${BUILD_IDLE_TIMEOUT_MS / 1000}s — assuming done and force-exiting.`);
|
|
76
|
+
done();
|
|
77
|
+
}, BUILD_IDLE_TIMEOUT_MS);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
child.stdout.on("data", (chunk) => {
|
|
81
|
+
process.stdout.write(chunk);
|
|
82
|
+
bumpIdle();
|
|
83
|
+
});
|
|
84
|
+
child.stderr.on("data", (chunk) => {
|
|
85
|
+
process.stderr.write(chunk);
|
|
86
|
+
bumpIdle();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
bumpIdle();
|
|
90
|
+
|
|
91
|
+
hardTimer = setTimeout(() => {
|
|
92
|
+
done(new Error(`Build exceeded hard timeout ${BUILD_HARD_TIMEOUT_MS / 1000}s`));
|
|
93
|
+
}, BUILD_HARD_TIMEOUT_MS);
|
|
94
|
+
|
|
95
|
+
child.on("exit", (code) => {
|
|
96
|
+
if (code === 0 || code === null) done();
|
|
97
|
+
else done(new Error(`Build failed with exit code ${code}`));
|
|
98
|
+
});
|
|
99
|
+
child.on("error", (err) => done(err));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
45
102
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
103
|
+
function runDecktape(name, cwd) {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
const url = `${BASE_URL}/presentations/${name}/?pdf`;
|
|
106
|
+
const output = path.join(OUTPUT_DIR, `${name}.pdf`);
|
|
107
|
+
|
|
108
|
+
console.log(` → Exporting: ${name}`);
|
|
109
|
+
console.log(` URL: ${url}`);
|
|
110
|
+
console.log(` Output: ${output}\n`);
|
|
111
|
+
|
|
112
|
+
const child = spawn(
|
|
113
|
+
"npx",
|
|
114
|
+
[
|
|
115
|
+
"decktape",
|
|
116
|
+
"reveal",
|
|
117
|
+
"--size", "1920x1080",
|
|
118
|
+
"--pause", "1000",
|
|
119
|
+
"--load-pause", "2000",
|
|
120
|
+
url,
|
|
121
|
+
output,
|
|
122
|
+
],
|
|
123
|
+
{ cwd, stdio: ["ignore", "inherit", "inherit"] }
|
|
51
124
|
);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
125
|
+
|
|
126
|
+
child.on("exit", (code) => {
|
|
127
|
+
if (code === 0) {
|
|
128
|
+
console.log(`\n \u2713 ${output}`);
|
|
129
|
+
resolve(true);
|
|
130
|
+
} else {
|
|
131
|
+
console.log(`\n \u2717 Failed: ${name} (decktape exit code ${code})`);
|
|
132
|
+
resolve(false);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
child.on("error", (err) => {
|
|
136
|
+
console.log(`\n \u2717 Failed: ${name} (${err.message})`);
|
|
137
|
+
resolve(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
58
140
|
}
|
|
59
141
|
|
|
60
|
-
function getAvailablePresentations(cwd) {
|
|
142
|
+
function getAvailablePresentations(cwd, { includeHidden = false } = {}) {
|
|
61
143
|
const dir = path.join(cwd, "presentations");
|
|
62
144
|
return fs
|
|
63
145
|
.readdirSync(dir, { withFileTypes: true })
|
|
64
|
-
.filter((d) => d.isDirectory() && d.name
|
|
146
|
+
.filter((d) => d.isDirectory() && (includeHidden || !d.name.startsWith("_")))
|
|
65
147
|
.map((d) => d.name);
|
|
66
148
|
}
|
|
67
149
|
|
|
@@ -75,9 +157,10 @@ export async function run(opts = {}) {
|
|
|
75
157
|
const cwd = opts.cwd || process.cwd();
|
|
76
158
|
const requestedName = opts.name;
|
|
77
159
|
const available = getAvailablePresentations(cwd);
|
|
160
|
+
const allForLookup = getAvailablePresentations(cwd, { includeHidden: true });
|
|
78
161
|
|
|
79
162
|
if (requestedName) {
|
|
80
|
-
if (!
|
|
163
|
+
if (!allForLookup.includes(requestedName)) {
|
|
81
164
|
console.error(`Presentation not found: ${requestedName}`);
|
|
82
165
|
console.error(" Available presentations:");
|
|
83
166
|
for (const name of available) {
|
|
@@ -89,16 +172,25 @@ export async function run(opts = {}) {
|
|
|
89
172
|
|
|
90
173
|
const presentations = requestedName ? [requestedName] : available;
|
|
91
174
|
|
|
92
|
-
// Build the site
|
|
175
|
+
// Build the site (tolerate lingering child handles)
|
|
93
176
|
console.log("Building site...");
|
|
94
|
-
|
|
177
|
+
try {
|
|
178
|
+
await runBuild(cwd);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.error(`Build failed: ${err.message}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
95
183
|
|
|
96
184
|
// Start server
|
|
97
185
|
console.log(`\nStarting server on port ${PORT}...`);
|
|
98
186
|
serverProcess = spawn("npx", ["eleventy", "--serve", "--port", String(PORT)], {
|
|
99
|
-
stdio: "ignore",
|
|
187
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
100
188
|
cwd,
|
|
101
189
|
});
|
|
190
|
+
serverProcess.stdout.on("data", () => {}); // drain
|
|
191
|
+
serverProcess.stderr.on("data", (chunk) => {
|
|
192
|
+
process.stderr.write(`[server] ${chunk}`);
|
|
193
|
+
});
|
|
102
194
|
|
|
103
195
|
process.on("exit", cleanup);
|
|
104
196
|
process.on("SIGINT", () => { cleanup(); process.exit(1); });
|
|
@@ -106,7 +198,13 @@ export async function run(opts = {}) {
|
|
|
106
198
|
|
|
107
199
|
// Wait for server
|
|
108
200
|
console.log("Waiting for server...");
|
|
109
|
-
|
|
201
|
+
try {
|
|
202
|
+
await waitForServer();
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error(`\n${err.message}`);
|
|
205
|
+
cleanup();
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
110
208
|
console.log("Server ready");
|
|
111
209
|
|
|
112
210
|
// Create output directory
|
|
@@ -118,7 +216,7 @@ export async function run(opts = {}) {
|
|
|
118
216
|
console.log(`\nExporting ${presentations.length} presentation(s) to PDF...\n`);
|
|
119
217
|
|
|
120
218
|
for (const name of presentations) {
|
|
121
|
-
if (runDecktape(name)) {
|
|
219
|
+
if (await runDecktape(name, cwd)) {
|
|
122
220
|
success++;
|
|
123
221
|
} else {
|
|
124
222
|
failure++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazyslides",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Slide decks with native agentic AI integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chris Tietz",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"@11ty/eleventy": "^3.0.0"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"decktape": "^3.14.0",
|
|
53
54
|
"js-yaml": "^4.1.0"
|
|
54
55
|
},
|
|
55
56
|
"optionalDependencies": {
|
|
@@ -59,7 +60,6 @@
|
|
|
59
60
|
"@11ty/eleventy": "^3.0.0",
|
|
60
61
|
"@tailwindcss/cli": "4.1.18",
|
|
61
62
|
"concurrently": "^9.0.0",
|
|
62
|
-
"decktape": "^3.14.0",
|
|
63
63
|
"tailwindcss": "4.1.18",
|
|
64
64
|
"vitest": "^4.1.2"
|
|
65
65
|
},
|