supaslidev 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/README.md +54 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/index-BvGsouIQ.css +1 -0
- package/dist/assets/index-gXkJYUDL.js +49 -0
- package/dist/cli/index.js +605 -0
- package/dist/favicon-96x96.png +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.svg +1 -0
- package/dist/index.html +24 -0
- package/dist/site.webmanifest +21 -0
- package/dist/ssl-logo.png +0 -0
- package/dist/web-app-manifest-192x192.png +0 -0
- package/dist/web-app-manifest-512x512.png +0 -0
- package/package.json +70 -0
- package/scripts/generate-presentations.mjs +122 -0
- package/server/api.js +1122 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
8
|
+
import { addImportedPresentation, findWorkspaceRoot } from "create-supaslidev";
|
|
9
|
+
|
|
10
|
+
//#region src/cli/utils.ts
|
|
11
|
+
function findProjectRoot(cwd = process.cwd()) {
|
|
12
|
+
let dir = cwd;
|
|
13
|
+
while (dir !== dirname(dir)) {
|
|
14
|
+
if (existsSync(join(dir, "presentations")) && existsSync(join(dir, "package.json"))) return dir;
|
|
15
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml"))) return dir;
|
|
16
|
+
dir = dirname(dir);
|
|
17
|
+
}
|
|
18
|
+
if (existsSync(join(cwd, "presentations"))) return cwd;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function getPresentations$1(presentationsDir) {
|
|
22
|
+
if (!existsSync(presentationsDir)) return [];
|
|
23
|
+
return readdirSync(presentationsDir).filter((name) => {
|
|
24
|
+
const fullPath = join(presentationsDir, name);
|
|
25
|
+
return statSync(fullPath).isDirectory() && existsSync(join(fullPath, "slides.md"));
|
|
26
|
+
}).sort();
|
|
27
|
+
}
|
|
28
|
+
function printAvailablePresentations(presentations) {
|
|
29
|
+
console.error("\nAvailable presentations:");
|
|
30
|
+
if (presentations.length === 0) console.error(" No presentations found");
|
|
31
|
+
else for (const name of presentations) console.error(` ${name}`);
|
|
32
|
+
}
|
|
33
|
+
function createVercelConfig() {
|
|
34
|
+
return JSON.stringify({
|
|
35
|
+
buildCommand: "npm run build",
|
|
36
|
+
outputDirectory: "dist",
|
|
37
|
+
rewrites: [{
|
|
38
|
+
source: "/(.*)",
|
|
39
|
+
destination: "/index.html"
|
|
40
|
+
}]
|
|
41
|
+
}, null, 2) + "\n";
|
|
42
|
+
}
|
|
43
|
+
function createNetlifyConfig() {
|
|
44
|
+
return `[build]
|
|
45
|
+
publish = "dist"
|
|
46
|
+
command = "npm run build"
|
|
47
|
+
|
|
48
|
+
[build.environment]
|
|
49
|
+
NODE_VERSION = "20"
|
|
50
|
+
|
|
51
|
+
[[redirects]]
|
|
52
|
+
from = "/*"
|
|
53
|
+
to = "/index.html"
|
|
54
|
+
status = 200
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
function createDeployPackageJson(name) {
|
|
58
|
+
return JSON.stringify({
|
|
59
|
+
name: `${name}-deploy`,
|
|
60
|
+
version: "1.0.0",
|
|
61
|
+
private: true,
|
|
62
|
+
scripts: {
|
|
63
|
+
build: "echo \"Already built - static files ready in dist/\"",
|
|
64
|
+
start: "npx serve dist"
|
|
65
|
+
}
|
|
66
|
+
}, null, 2) + "\n";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/cli/commands/dev.ts
|
|
71
|
+
function findSupaslidevPackageRoot() {
|
|
72
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
73
|
+
while (dir !== dirname(dir)) {
|
|
74
|
+
const packageJsonPath = join(dir, "package.json");
|
|
75
|
+
if (existsSync(packageJsonPath)) try {
|
|
76
|
+
if (JSON.parse(readFileSync(packageJsonPath, "utf-8")).name === "supaslidev") return dir;
|
|
77
|
+
} catch {}
|
|
78
|
+
dir = dirname(dir);
|
|
79
|
+
}
|
|
80
|
+
throw new Error("Could not find supaslidev package root");
|
|
81
|
+
}
|
|
82
|
+
const packageRoot = findSupaslidevPackageRoot();
|
|
83
|
+
async function dev() {
|
|
84
|
+
const projectRoot = findProjectRoot();
|
|
85
|
+
if (!projectRoot) {
|
|
86
|
+
console.error("Error: Could not find a Supaslidev project.");
|
|
87
|
+
console.error("Make sure you are in a directory with a \"presentations\" folder.");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const presentationsDir = join(projectRoot, "presentations");
|
|
91
|
+
if (!existsSync(presentationsDir)) {
|
|
92
|
+
console.error(`Error: No "presentations" folder found at ${presentationsDir}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
console.log(`Starting Supaslidev for project: ${projectRoot}`);
|
|
96
|
+
console.log(`Presentations directory: ${presentationsDir}`);
|
|
97
|
+
process.env.SUPASLIDEV_PROJECT_ROOT = projectRoot;
|
|
98
|
+
process.env.SUPASLIDEV_PRESENTATIONS_DIR = presentationsDir;
|
|
99
|
+
const generateScript = join(packageRoot, "scripts", "generate-presentations.mjs");
|
|
100
|
+
const apiServer = join(packageRoot, "server", "api.js");
|
|
101
|
+
spawn("node", [generateScript], {
|
|
102
|
+
stdio: "inherit",
|
|
103
|
+
env: process.env
|
|
104
|
+
}).on("close", (code) => {
|
|
105
|
+
if (code !== 0) {
|
|
106
|
+
console.error("Failed to generate presentations data");
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
const api = spawn("node", [apiServer], {
|
|
110
|
+
stdio: "inherit",
|
|
111
|
+
env: process.env,
|
|
112
|
+
detached: false
|
|
113
|
+
});
|
|
114
|
+
const vite = spawn("npx", [
|
|
115
|
+
"vite",
|
|
116
|
+
"--config",
|
|
117
|
+
join(packageRoot, "vite.config.ts")
|
|
118
|
+
], {
|
|
119
|
+
cwd: packageRoot,
|
|
120
|
+
stdio: "inherit",
|
|
121
|
+
env: process.env,
|
|
122
|
+
shell: true
|
|
123
|
+
});
|
|
124
|
+
const cleanup = (processes) => {
|
|
125
|
+
for (const proc of processes) proc.kill("SIGTERM");
|
|
126
|
+
process.exit(0);
|
|
127
|
+
};
|
|
128
|
+
process.on("SIGINT", () => cleanup([api, vite]));
|
|
129
|
+
process.on("SIGTERM", () => cleanup([api, vite]));
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/cli/commands/create.ts
|
|
135
|
+
function validateName$1(name) {
|
|
136
|
+
if (!/^[a-z0-9-]+$/.test(name)) throw new Error("Name must be lowercase alphanumeric with hyphens only");
|
|
137
|
+
if (name.startsWith("-") || name.endsWith("-")) throw new Error("Name cannot start or end with a hyphen");
|
|
138
|
+
}
|
|
139
|
+
function checkDuplicateName(presentationsDir, name) {
|
|
140
|
+
if (existsSync(join(presentationsDir, name))) throw new Error(`Presentation "${name}" already exists`);
|
|
141
|
+
}
|
|
142
|
+
function hasSharedPackage$1(projectRoot) {
|
|
143
|
+
return existsSync(join(projectRoot, "packages", "shared", "package.json"));
|
|
144
|
+
}
|
|
145
|
+
function addSharedAddonToSlides$1(slidesPath) {
|
|
146
|
+
const content = readFileSync(slidesPath, "utf-8");
|
|
147
|
+
const frontmatterMatch = content.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
148
|
+
if (!frontmatterMatch) return;
|
|
149
|
+
const [fullMatch, openDelim, frontmatter, closeDelim] = frontmatterMatch;
|
|
150
|
+
const restOfFile = content.slice(fullMatch.length);
|
|
151
|
+
if (frontmatter.includes("addons:")) return;
|
|
152
|
+
const themeMatch = frontmatter.match(/^(theme:\s*.+)$/m);
|
|
153
|
+
if (themeMatch) writeFileSync(slidesPath, `${openDelim}${frontmatter.replace(themeMatch[1], `${themeMatch[1]}\naddons:\n - '@supaslidev/shared'`)}\n${closeDelim}${restOfFile}`);
|
|
154
|
+
}
|
|
155
|
+
function addSharedDependencyToPackageJson$1(packageJsonPath) {
|
|
156
|
+
const content = readFileSync(packageJsonPath, "utf-8");
|
|
157
|
+
const packageJson = JSON.parse(content);
|
|
158
|
+
if (!packageJson.dependencies) packageJson.dependencies = {};
|
|
159
|
+
if (!packageJson.dependencies["@supaslidev/shared"]) {
|
|
160
|
+
packageJson.dependencies["@supaslidev/shared"] = "workspace:*";
|
|
161
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function getExistingPresentations(presentationsDir) {
|
|
165
|
+
if (!existsSync(presentationsDir)) return /* @__PURE__ */ new Set();
|
|
166
|
+
return new Set(readdirSync(presentationsDir).filter((name) => {
|
|
167
|
+
return statSync(join(presentationsDir, name)).isDirectory();
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
function configureSharedPackage(projectRoot, presentationDir) {
|
|
171
|
+
if (!hasSharedPackage$1(projectRoot)) return;
|
|
172
|
+
const slidesPath = join(presentationDir, "slides.md");
|
|
173
|
+
const packageJsonPath = join(presentationDir, "package.json");
|
|
174
|
+
if (existsSync(slidesPath)) addSharedAddonToSlides$1(slidesPath);
|
|
175
|
+
if (existsSync(packageJsonPath)) addSharedDependencyToPackageJson$1(packageJsonPath);
|
|
176
|
+
}
|
|
177
|
+
async function create(name) {
|
|
178
|
+
const projectRoot = findProjectRoot();
|
|
179
|
+
if (!projectRoot) {
|
|
180
|
+
console.error("Error: Could not find a Supaslidev project.");
|
|
181
|
+
console.error("Make sure you are in a directory with a \"presentations\" folder.");
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const presentationsDir = join(projectRoot, "presentations");
|
|
185
|
+
if (!existsSync(presentationsDir)) {
|
|
186
|
+
console.error(`Error: No "presentations" folder found at ${presentationsDir}`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
if (name) try {
|
|
190
|
+
validateName$1(name);
|
|
191
|
+
checkDuplicateName(presentationsDir, name);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error(`Error: ${err instanceof Error ? err.message : "Invalid name"}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
const existingPresentations = getExistingPresentations(presentationsDir);
|
|
197
|
+
const args = ["create", "slidev"];
|
|
198
|
+
if (name) args.push(name);
|
|
199
|
+
console.log(`Creating new presentation in: ${presentationsDir}`);
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const child = spawn("pnpm", args, {
|
|
202
|
+
cwd: presentationsDir,
|
|
203
|
+
stdio: "inherit"
|
|
204
|
+
});
|
|
205
|
+
child.on("close", (code) => {
|
|
206
|
+
if (code !== 0) {
|
|
207
|
+
console.error(`Failed to create presentation (exit code: ${code})`);
|
|
208
|
+
reject(/* @__PURE__ */ new Error(`Process exited with code ${code}`));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const presentationName = name ?? findNewPresentation(presentationsDir, existingPresentations);
|
|
212
|
+
if (presentationName) configureSharedPackage(projectRoot, join(presentationsDir, presentationName));
|
|
213
|
+
console.log("\nPresentation created successfully!");
|
|
214
|
+
console.log("Run \"supaslidev present <name>\" to start a dev server for your presentation.");
|
|
215
|
+
resolve();
|
|
216
|
+
});
|
|
217
|
+
child.on("error", (err) => {
|
|
218
|
+
console.error("Failed to create presentation:", err.message);
|
|
219
|
+
reject(err);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function findNewPresentation(presentationsDir, existingPresentations) {
|
|
224
|
+
const currentPresentations = getExistingPresentations(presentationsDir);
|
|
225
|
+
for (const name of currentPresentations) if (!existingPresentations.has(name)) return name;
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/cli/commands/present.ts
|
|
231
|
+
function getPresentations(presentationsDir) {
|
|
232
|
+
if (!existsSync(presentationsDir)) return [];
|
|
233
|
+
return readdirSync(presentationsDir).filter((name) => {
|
|
234
|
+
const fullPath = join(presentationsDir, name);
|
|
235
|
+
return statSync(fullPath).isDirectory() && existsSync(join(fullPath, "slides.md"));
|
|
236
|
+
}).sort();
|
|
237
|
+
}
|
|
238
|
+
function printAvailable(presentations) {
|
|
239
|
+
console.error("\nAvailable presentations:");
|
|
240
|
+
if (presentations.length === 0) console.error(" No presentations found");
|
|
241
|
+
else presentations.forEach((name) => {
|
|
242
|
+
console.error(` ${name}`);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async function present(name) {
|
|
246
|
+
const projectRoot = findProjectRoot();
|
|
247
|
+
if (!projectRoot) {
|
|
248
|
+
console.error("Error: Could not find a Supaslidev project.");
|
|
249
|
+
console.error("Make sure you are in a directory with a \"presentations\" folder.");
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
const presentationsDir = join(projectRoot, "presentations");
|
|
253
|
+
if (!existsSync(presentationsDir)) {
|
|
254
|
+
console.error(`Error: No "presentations" folder found at ${presentationsDir}`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
const presentations = getPresentations(presentationsDir);
|
|
258
|
+
if (!presentations.includes(name)) {
|
|
259
|
+
console.error(`Error: Presentation "${name}" not found`);
|
|
260
|
+
printAvailable(presentations);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
const packageName = `@supaslidev/${name}`;
|
|
264
|
+
console.log(`\nStarting dev server for ${name}...\n`);
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
const pnpm = spawn("pnpm", [
|
|
267
|
+
"--filter",
|
|
268
|
+
packageName,
|
|
269
|
+
"dev"
|
|
270
|
+
], {
|
|
271
|
+
cwd: projectRoot,
|
|
272
|
+
stdio: "inherit",
|
|
273
|
+
shell: true
|
|
274
|
+
});
|
|
275
|
+
pnpm.on("error", (err) => {
|
|
276
|
+
console.error(`Failed to start dev server: ${err.message}`);
|
|
277
|
+
reject(err);
|
|
278
|
+
});
|
|
279
|
+
pnpm.on("close", (code) => {
|
|
280
|
+
if (code !== 0) {
|
|
281
|
+
reject(/* @__PURE__ */ new Error(`Process exited with code ${code}`));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
resolve();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/cli/commands/export.ts
|
|
291
|
+
async function exportPdf(name, options = {}) {
|
|
292
|
+
const projectRoot = findProjectRoot();
|
|
293
|
+
if (!projectRoot) {
|
|
294
|
+
console.error("Error: Could not find a Supaslidev project.");
|
|
295
|
+
console.error("Make sure you are in a directory with a \"presentations\" folder.");
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
const presentationsDir = join(projectRoot, "presentations");
|
|
299
|
+
const distDir = join(projectRoot, "dist");
|
|
300
|
+
const presentations = getPresentations$1(presentationsDir);
|
|
301
|
+
if (!presentations.includes(name)) {
|
|
302
|
+
console.error(`Error: Presentation "${name}" not found`);
|
|
303
|
+
printAvailablePresentations(presentations);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
const presentationDir = join(presentationsDir, name);
|
|
307
|
+
const outputPath = options.output ?? join(distDir, `${name}.pdf`);
|
|
308
|
+
if (!existsSync(dirname(outputPath))) mkdirSync(dirname(outputPath), { recursive: true });
|
|
309
|
+
console.log("\n" + "=".repeat(50));
|
|
310
|
+
console.log(` Exporting PDF: ${name}`);
|
|
311
|
+
console.log(` Output: ${outputPath}`);
|
|
312
|
+
console.log("=".repeat(50) + "\n");
|
|
313
|
+
const slidev = spawn("npx", [
|
|
314
|
+
"slidev",
|
|
315
|
+
"export",
|
|
316
|
+
"--output",
|
|
317
|
+
outputPath
|
|
318
|
+
], {
|
|
319
|
+
cwd: presentationDir,
|
|
320
|
+
stdio: "inherit",
|
|
321
|
+
shell: true
|
|
322
|
+
});
|
|
323
|
+
slidev.on("error", (err) => {
|
|
324
|
+
console.error(`Failed to export presentation: ${err.message}`);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
});
|
|
327
|
+
slidev.on("close", (code) => {
|
|
328
|
+
if (code !== 0) {
|
|
329
|
+
console.error(`\nExport failed with exit code ${code}`);
|
|
330
|
+
process.exit(code ?? 1);
|
|
331
|
+
}
|
|
332
|
+
console.log("\n" + "=".repeat(50));
|
|
333
|
+
console.log(` Export complete!`);
|
|
334
|
+
console.log(` Output: ${outputPath}`);
|
|
335
|
+
console.log("=".repeat(50) + "\n");
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/cli/commands/deploy.ts
|
|
341
|
+
async function deploy(name, options = {}) {
|
|
342
|
+
const projectRoot = findProjectRoot();
|
|
343
|
+
if (!projectRoot) {
|
|
344
|
+
console.error("Error: Could not find a Supaslidev project.");
|
|
345
|
+
console.error("Make sure you are in a directory with a \"presentations\" folder.");
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
const presentationsDir = join(projectRoot, "presentations");
|
|
349
|
+
const deployDir = join(projectRoot, "deploy");
|
|
350
|
+
const presentations = getPresentations$1(presentationsDir);
|
|
351
|
+
if (!presentations.includes(name)) {
|
|
352
|
+
console.error(`Error: Presentation "${name}" not found`);
|
|
353
|
+
printAvailablePresentations(presentations);
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
const presentationDir = join(presentationsDir, name);
|
|
357
|
+
const presentationDistDir = join(presentationDir, "dist");
|
|
358
|
+
const outputDir = options.output ?? join(deployDir, name);
|
|
359
|
+
const outputDistDir = join(outputDir, "dist");
|
|
360
|
+
console.log("\n" + "=".repeat(50));
|
|
361
|
+
console.log(` Preparing deployment: ${name}`);
|
|
362
|
+
console.log("=".repeat(50) + "\n");
|
|
363
|
+
console.log("Step 1/3: Building presentation...");
|
|
364
|
+
const slidev = spawn("npx", ["slidev", "build"], {
|
|
365
|
+
cwd: presentationDir,
|
|
366
|
+
stdio: "inherit",
|
|
367
|
+
shell: true
|
|
368
|
+
});
|
|
369
|
+
slidev.on("error", (err) => {
|
|
370
|
+
console.error(`Failed to build presentation: ${err.message}`);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
});
|
|
373
|
+
slidev.on("close", (code) => {
|
|
374
|
+
if (code !== 0) {
|
|
375
|
+
console.error(`\nBuild failed with exit code ${code}`);
|
|
376
|
+
process.exit(code ?? 1);
|
|
377
|
+
}
|
|
378
|
+
console.log("\nStep 2/3: Creating deploy package...");
|
|
379
|
+
if (existsSync(outputDir)) rmSync(outputDir, { recursive: true });
|
|
380
|
+
mkdirSync(outputDir, { recursive: true });
|
|
381
|
+
cpSync(presentationDistDir, outputDistDir, { recursive: true });
|
|
382
|
+
console.log("Step 3/3: Adding deployment configurations...");
|
|
383
|
+
writeFileSync(join(outputDir, "vercel.json"), createVercelConfig());
|
|
384
|
+
writeFileSync(join(outputDir, "netlify.toml"), createNetlifyConfig());
|
|
385
|
+
writeFileSync(join(outputDir, "package.json"), createDeployPackageJson(name));
|
|
386
|
+
console.log("\n" + "=".repeat(50));
|
|
387
|
+
console.log(" Deployment package ready!");
|
|
388
|
+
console.log("=".repeat(50));
|
|
389
|
+
console.log(`\n Output: ${outputDir}/`);
|
|
390
|
+
console.log("\n Deploy with Vercel:");
|
|
391
|
+
console.log(` cd ${outputDir} && vercel`);
|
|
392
|
+
console.log("\n Deploy with Netlify:");
|
|
393
|
+
console.log(` cd ${outputDir} && netlify deploy --prod`);
|
|
394
|
+
console.log("\n Or push to Git and import in Vercel/Netlify dashboard.");
|
|
395
|
+
console.log("");
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/cli/commands/import.ts
|
|
401
|
+
function findPnpmWorkspaceRoot(startDir) {
|
|
402
|
+
let currentDir = startDir;
|
|
403
|
+
while (currentDir !== dirname(currentDir)) {
|
|
404
|
+
if (existsSync(join(currentDir, "pnpm-workspace.yaml"))) return currentDir;
|
|
405
|
+
currentDir = dirname(currentDir);
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
const IGNORE_PATTERNS = [
|
|
410
|
+
"node_modules",
|
|
411
|
+
".git",
|
|
412
|
+
"dist",
|
|
413
|
+
".nuxt",
|
|
414
|
+
".output",
|
|
415
|
+
"pnpm-lock.yaml",
|
|
416
|
+
"package-lock.json",
|
|
417
|
+
"yarn.lock",
|
|
418
|
+
".DS_Store"
|
|
419
|
+
];
|
|
420
|
+
function hasSharedPackage(projectRoot) {
|
|
421
|
+
return existsSync(join(projectRoot, "packages", "shared", "package.json"));
|
|
422
|
+
}
|
|
423
|
+
function addSharedAddonToSlides(slidesPath) {
|
|
424
|
+
const content = readFileSync(slidesPath, "utf-8");
|
|
425
|
+
const frontmatterMatch = content.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
426
|
+
if (!frontmatterMatch) return;
|
|
427
|
+
const [fullMatch, openDelim, frontmatter, closeDelim] = frontmatterMatch;
|
|
428
|
+
const restOfFile = content.slice(fullMatch.length);
|
|
429
|
+
const sharedAddon = "@supaslidev/shared";
|
|
430
|
+
if (frontmatter.includes(sharedAddon)) return;
|
|
431
|
+
let updatedFrontmatter = frontmatter;
|
|
432
|
+
const addonsMatch = frontmatter.match(/^(addons:\s*)(\[.*?\])?$/m);
|
|
433
|
+
if (addonsMatch) if (addonsMatch[2]) {
|
|
434
|
+
const arrayContent = addonsMatch[2].slice(1, -1).trim();
|
|
435
|
+
if (arrayContent === "") updatedFrontmatter = frontmatter.replace(addonsMatch[0], `addons: ['${sharedAddon}']`);
|
|
436
|
+
else updatedFrontmatter = frontmatter.replace(addonsMatch[0], `addons: [${arrayContent}, '${sharedAddon}']`);
|
|
437
|
+
} else {
|
|
438
|
+
const addonsBlockMatch = frontmatter.match(/^addons:\s*\n((?: - .+\n?)*)/m);
|
|
439
|
+
if (addonsBlockMatch) {
|
|
440
|
+
const existingBlock = addonsBlockMatch[0].trimEnd();
|
|
441
|
+
updatedFrontmatter = frontmatter.replace(existingBlock, `${existingBlock}\n - '${sharedAddon}'`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
const themeMatch = frontmatter.match(/^(theme:\s*.+)$/m);
|
|
446
|
+
if (themeMatch) updatedFrontmatter = frontmatter.replace(themeMatch[1], `${themeMatch[1]}\naddons:\n - '${sharedAddon}'`);
|
|
447
|
+
}
|
|
448
|
+
if (updatedFrontmatter !== frontmatter) writeFileSync(slidesPath, `${openDelim}${updatedFrontmatter}\n${closeDelim}${restOfFile}`);
|
|
449
|
+
}
|
|
450
|
+
function addSharedDependencyToPackageJson(packageJson) {
|
|
451
|
+
if (!packageJson.dependencies) packageJson.dependencies = {};
|
|
452
|
+
if (!packageJson.dependencies["@supaslidev/shared"]) packageJson.dependencies["@supaslidev/shared"] = "workspace:*";
|
|
453
|
+
}
|
|
454
|
+
function validateName(name) {
|
|
455
|
+
if (!/^[a-z0-9-]+$/.test(name)) throw new Error("Name must be lowercase alphanumeric with hyphens only");
|
|
456
|
+
if (name.startsWith("-") || name.endsWith("-")) throw new Error("Name cannot start or end with a hyphen");
|
|
457
|
+
}
|
|
458
|
+
function validateSourceDirectory(sourcePath) {
|
|
459
|
+
if (!existsSync(sourcePath)) throw new Error(`Source directory does not exist: ${sourcePath}`);
|
|
460
|
+
if (!statSync(sourcePath).isDirectory()) throw new Error(`Source path is not a directory: ${sourcePath}`);
|
|
461
|
+
if (!existsSync(join(sourcePath, "slides.md"))) throw new Error(`No slides.md found in source directory: ${sourcePath}`);
|
|
462
|
+
if (!existsSync(join(sourcePath, "package.json"))) throw new Error(`No package.json found in source directory: ${sourcePath}`);
|
|
463
|
+
}
|
|
464
|
+
function shouldIgnore(name) {
|
|
465
|
+
return IGNORE_PATTERNS.includes(name);
|
|
466
|
+
}
|
|
467
|
+
function copyDirectorySelective(source, destination) {
|
|
468
|
+
mkdirSync(destination, { recursive: true });
|
|
469
|
+
const entries = readdirSync(source);
|
|
470
|
+
for (const entry of entries) {
|
|
471
|
+
if (shouldIgnore(entry)) continue;
|
|
472
|
+
const sourcePath = join(source, entry);
|
|
473
|
+
const destPath = join(destination, entry);
|
|
474
|
+
if (statSync(sourcePath).isDirectory()) cpSync(sourcePath, destPath, { recursive: true });
|
|
475
|
+
else cpSync(sourcePath, destPath);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function transformPackageJson(sourcePath, name, projectRoot) {
|
|
479
|
+
const content = readFileSync(join(sourcePath, "package.json"), "utf-8");
|
|
480
|
+
const packageJson = JSON.parse(content);
|
|
481
|
+
packageJson.name = `@supaslidev/${name}`;
|
|
482
|
+
packageJson.private = true;
|
|
483
|
+
packageJson.scripts = {
|
|
484
|
+
dev: "slidev --open",
|
|
485
|
+
build: "slidev build",
|
|
486
|
+
export: "slidev export"
|
|
487
|
+
};
|
|
488
|
+
if (hasSharedPackage(projectRoot)) addSharedDependencyToPackageJson(packageJson);
|
|
489
|
+
return JSON.stringify(packageJson, null, 2) + "\n";
|
|
490
|
+
}
|
|
491
|
+
function runPnpmInstall(projectRoot) {
|
|
492
|
+
return new Promise((resolve, reject) => {
|
|
493
|
+
console.log("\nRunning pnpm install...");
|
|
494
|
+
const child = spawn("pnpm", ["install"], {
|
|
495
|
+
cwd: projectRoot,
|
|
496
|
+
stdio: "inherit"
|
|
497
|
+
});
|
|
498
|
+
child.on("close", (code) => {
|
|
499
|
+
if (code !== 0) {
|
|
500
|
+
reject(/* @__PURE__ */ new Error(`pnpm install failed with exit code ${code}`));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
resolve();
|
|
504
|
+
});
|
|
505
|
+
child.on("error", (err) => {
|
|
506
|
+
reject(err);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
async function importPresentation(source, options = {}) {
|
|
511
|
+
const { name, install = true } = options;
|
|
512
|
+
const projectRoot = findProjectRoot();
|
|
513
|
+
if (!projectRoot) {
|
|
514
|
+
console.error("Error: Could not find a Supaslidev project.");
|
|
515
|
+
console.error("Make sure you are in a directory with a \"presentations\" folder.");
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
const presentationsDir = join(projectRoot, "presentations");
|
|
519
|
+
if (!existsSync(presentationsDir)) {
|
|
520
|
+
console.error(`Error: No "presentations" folder found at ${presentationsDir}`);
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
const sourcePath = resolve(source);
|
|
524
|
+
try {
|
|
525
|
+
validateSourceDirectory(sourcePath);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
console.error(`Error: ${err instanceof Error ? err.message : "Invalid source"}`);
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
const presentationName = name ?? basename(sourcePath).toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
531
|
+
try {
|
|
532
|
+
validateName(presentationName);
|
|
533
|
+
} catch (err) {
|
|
534
|
+
console.error(`Error: ${err instanceof Error ? err.message : "Invalid name"}`);
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
if (getPresentations$1(presentationsDir).includes(presentationName)) {
|
|
538
|
+
console.error(`Error: Presentation "${presentationName}" already exists`);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
const destinationPath = join(presentationsDir, presentationName);
|
|
542
|
+
console.log(`Importing presentation from: ${sourcePath}`);
|
|
543
|
+
console.log(`Destination: ${destinationPath}`);
|
|
544
|
+
copyDirectorySelective(sourcePath, destinationPath);
|
|
545
|
+
const transformedPackageJson = transformPackageJson(sourcePath, presentationName, projectRoot);
|
|
546
|
+
writeFileSync(join(destinationPath, "package.json"), transformedPackageJson);
|
|
547
|
+
if (hasSharedPackage(projectRoot)) {
|
|
548
|
+
const slidesPath = join(destinationPath, "slides.md");
|
|
549
|
+
if (existsSync(slidesPath)) addSharedAddonToSlides(slidesPath);
|
|
550
|
+
}
|
|
551
|
+
console.log("\nFiles copied successfully!");
|
|
552
|
+
console.log("Ignored: " + IGNORE_PATTERNS.join(", "));
|
|
553
|
+
const workspaceRoot = findWorkspaceRoot(projectRoot);
|
|
554
|
+
const pnpmRoot = findPnpmWorkspaceRoot(projectRoot);
|
|
555
|
+
if (install) await runPnpmInstall(pnpmRoot ?? projectRoot);
|
|
556
|
+
else console.log("\nSkipped pnpm install. Run \"pnpm install\" manually before using the presentation.");
|
|
557
|
+
if (workspaceRoot) addImportedPresentation(workspaceRoot, {
|
|
558
|
+
name: presentationName,
|
|
559
|
+
importedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
560
|
+
sourcePath
|
|
561
|
+
});
|
|
562
|
+
console.log("\nPresentation imported successfully!");
|
|
563
|
+
console.log(`Run "supaslidev present ${presentationName}" to start a dev server.`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/cli/index.ts
|
|
568
|
+
const program = new Command();
|
|
569
|
+
program.name("supaslidev").description("Supaslidev presentation management CLI").version("0.1.0");
|
|
570
|
+
program.command("dev", { isDefault: true }).description("Start the Supaslidev UI and development server").action(async () => {
|
|
571
|
+
await dev();
|
|
572
|
+
});
|
|
573
|
+
program.command("new").description("Create a new presentation").argument("[name]", "Name of the presentation").action(async (name) => {
|
|
574
|
+
await create(name);
|
|
575
|
+
});
|
|
576
|
+
program.command("present").description("Start a presentation dev server").argument("<name>", "Name of the presentation to start").action(async (name) => {
|
|
577
|
+
await present(name);
|
|
578
|
+
});
|
|
579
|
+
program.command("export").description("Export a presentation to PDF").argument("<name>", "Name of the presentation to export").option("-o, --output <path>", "Output path for the PDF").action(async (name, options) => {
|
|
580
|
+
await exportPdf(name, options);
|
|
581
|
+
});
|
|
582
|
+
program.command("deploy").description("Build and prepare a presentation for deployment").argument("<name>", "Name of the presentation to deploy").option("-o, --output <path>", "Output directory for deployment files").action(async (name, options) => {
|
|
583
|
+
await deploy(name, options);
|
|
584
|
+
});
|
|
585
|
+
program.command("import").description("Import existing Sli.dev presentation(s)").argument("<source>", "Path to existing Slidev presentation directory").option("-n, --name <name>", "Name for the imported presentation (defaults to source directory name)").option("--no-install", "Skip pnpm install after import").action(async (source, options) => {
|
|
586
|
+
await importPresentation(source, {
|
|
587
|
+
name: options.name,
|
|
588
|
+
install: options.install ?? true
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
async function run() {
|
|
592
|
+
await program.parseAsync();
|
|
593
|
+
}
|
|
594
|
+
function isMainModule() {
|
|
595
|
+
if (!process.argv[1]) return false;
|
|
596
|
+
try {
|
|
597
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
598
|
+
} catch {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (isMainModule()) run();
|
|
603
|
+
|
|
604
|
+
//#endregion
|
|
605
|
+
export { run };
|
|
Binary file
|
package/dist/favicon.ico
ADDED
|
Binary file
|