create-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/dist/cli.js ADDED
@@ -0,0 +1,1279 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
5
+ import { basename, dirname, join, relative } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { spawn } from "node:child_process";
8
+ import * as p from "@clack/prompts";
9
+ import ejs from "ejs";
10
+ import pc from "picocolors";
11
+ import { tmpdir } from "node:os";
12
+
13
+ //#region src/create.ts
14
+ const CLI_VERSION$1 = "0.1.0";
15
+ function createSafeSpinner() {
16
+ if (process.stdout.isTTY && process.stdin.isTTY) {
17
+ const spinner = p.spinner();
18
+ return {
19
+ start: (msg) => spinner.start(msg),
20
+ stop: (msg) => spinner.stop(msg),
21
+ message: (msg) => spinner.message(msg)
22
+ };
23
+ }
24
+ return {
25
+ start: () => {},
26
+ stop: () => {},
27
+ message: () => {}
28
+ };
29
+ }
30
+ const templatesDir = join(dirname(fileURLToPath(import.meta.url)), "..", "templates");
31
+ const createdPaths = [];
32
+ function trackPath(path) {
33
+ createdPaths.push(path);
34
+ }
35
+ function cleanup() {
36
+ for (const path of createdPaths.reverse()) try {
37
+ if (existsSync(path)) rmSync(path, {
38
+ recursive: true,
39
+ force: true
40
+ });
41
+ } catch {}
42
+ }
43
+ async function renderTemplate(templatePath, data) {
44
+ return ejs.renderFile(templatePath, data);
45
+ }
46
+ function getOutputFileName(templateFileName) {
47
+ const name = templateFileName.replace(".ejs", "");
48
+ if (name === "gitignore") return ".gitignore";
49
+ if (name === "npmrc") return ".npmrc";
50
+ return name;
51
+ }
52
+ function runCommand(command, args, cwd) {
53
+ return new Promise((resolve, reject) => {
54
+ const child = spawn(command, args, {
55
+ cwd,
56
+ stdio: "inherit",
57
+ shell: true
58
+ });
59
+ child.on("error", reject);
60
+ child.on("close", (code) => {
61
+ if (code === 0) resolve();
62
+ else reject(/* @__PURE__ */ new Error(`Command "${command} ${args.join(" ")}" exited with code ${code}`));
63
+ });
64
+ });
65
+ }
66
+ async function renderWorkspaceTemplates(targetDir, templateName, data) {
67
+ const templateDir = join(templatesDir, templateName);
68
+ if (!existsSync(templateDir)) throw new Error(`Template "${templateName}" not found`);
69
+ await renderTemplatesRecursively(templateDir, targetDir, data);
70
+ }
71
+ async function renderTemplatesRecursively(sourceDir, targetDir, data) {
72
+ const entries = readdirSync(sourceDir);
73
+ for (const entry of entries) {
74
+ const sourcePath = join(sourceDir, entry);
75
+ if (statSync(sourcePath).isDirectory()) {
76
+ const subTargetDir = join(targetDir, entry);
77
+ mkdirSync(subTargetDir, { recursive: true });
78
+ await renderTemplatesRecursively(sourcePath, subTargetDir, data);
79
+ } else if (entry.endsWith(".ejs")) writeFileSync(join(targetDir, getOutputFileName(entry)), await renderTemplate(sourcePath, data), "utf-8");
80
+ }
81
+ }
82
+ function createDirectoryStructure(targetDir) {
83
+ for (const dir of [
84
+ "presentations",
85
+ "packages",
86
+ "scripts"
87
+ ]) {
88
+ const fullPath = join(targetDir, dir);
89
+ mkdirSync(fullPath, { recursive: true });
90
+ trackPath(fullPath);
91
+ }
92
+ }
93
+ async function createPresentation(targetDir, presentationName) {
94
+ const presentationDir = join(targetDir, "presentations", presentationName);
95
+ mkdirSync(presentationDir, { recursive: true });
96
+ trackPath(presentationDir);
97
+ const packageJson = {
98
+ name: `@supaslidev/${presentationName}`,
99
+ private: true,
100
+ type: "module",
101
+ scripts: {
102
+ build: "slidev build",
103
+ dev: "slidev --open",
104
+ export: "slidev export"
105
+ },
106
+ dependencies: {
107
+ "@slidev/cli": "catalog:",
108
+ "@slidev/theme-default": "catalog:",
109
+ "@slidev/theme-seriph": "catalog:",
110
+ "@slidev/theme-apple-basic": "catalog:",
111
+ "@supaslidev/shared": "workspace:*",
112
+ vue: "catalog:"
113
+ },
114
+ devDependencies: {}
115
+ };
116
+ writeFileSync(join(presentationDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
117
+ const slidesContent = `---
118
+ theme: default
119
+ title: ${presentationName}
120
+ addons:
121
+ - '@supaslidev/shared'
122
+ info: |
123
+ A new Slidev presentation
124
+ class: text-center
125
+ transition: slide-left
126
+ mdc: true
127
+ ---
128
+
129
+ # ${presentationName}
130
+
131
+ Welcome to your new presentation
132
+
133
+ ---
134
+
135
+ # Slide 2
136
+
137
+ Add your content here
138
+
139
+ ---
140
+
141
+ # Learn More
142
+
143
+ [Slidev Documentation](https://sli.dev/)
144
+ `;
145
+ writeFileSync(join(presentationDir, "slides.md"), slidesContent, "utf-8");
146
+ writeFileSync(join(presentationDir, ".gitignore"), "node_modules\ndist\n.slidev\n", "utf-8");
147
+ writeFileSync(join(presentationDir, ".npmrc"), "shamefully-hoist=true\n", "utf-8");
148
+ }
149
+ function createScripts(targetDir) {
150
+ writeFileSync(join(join(targetDir, "scripts"), "dev-presentation.mjs"), `#!/usr/bin/env node
151
+
152
+ import { existsSync, readdirSync, statSync } from 'node:fs';
153
+ import { join, dirname } from 'node:path';
154
+ import { fileURLToPath } from 'node:url';
155
+ import { spawn } from 'node:child_process';
156
+
157
+ const __dirname = dirname(fileURLToPath(import.meta.url));
158
+ const rootDir = join(__dirname, '..');
159
+ const presentationsDir = join(rootDir, 'presentations');
160
+
161
+ function getPresentations() {
162
+ if (!existsSync(presentationsDir)) {
163
+ return [];
164
+ }
165
+
166
+ return readdirSync(presentationsDir)
167
+ .filter((name) => {
168
+ const fullPath = join(presentationsDir, name);
169
+ return statSync(fullPath).isDirectory() && existsSync(join(fullPath, 'slides.md'));
170
+ })
171
+ .sort();
172
+ }
173
+
174
+ function printUsage(presentations) {
175
+ console.error('Usage: pnpm dev <presentation-name>');
176
+ console.error('\\nAvailable presentations:');
177
+
178
+ if (presentations.length === 0) {
179
+ console.error(' No presentations found');
180
+ } else {
181
+ presentations.forEach((name) => {
182
+ console.error(\` \${name}\`);
183
+ });
184
+ }
185
+ }
186
+
187
+ function runDev(name) {
188
+ const packageName = \`@supaslidev/\${name}\`;
189
+
190
+ console.log(\`\\nStarting dev server for \${name}...\\n\`);
191
+
192
+ const pnpm = spawn('pnpm', ['--filter', packageName, 'dev'], {
193
+ cwd: rootDir,
194
+ stdio: 'inherit',
195
+ shell: true,
196
+ });
197
+
198
+ pnpm.on('error', (err) => {
199
+ console.error(\`Failed to start dev server: \${err.message}\`);
200
+ process.exit(1);
201
+ });
202
+
203
+ pnpm.on('close', (code) => {
204
+ process.exit(code ?? 0);
205
+ });
206
+ }
207
+
208
+ function main() {
209
+ const args = process.argv.slice(2);
210
+ const name = args[0];
211
+ const presentations = getPresentations();
212
+
213
+ if (!name) {
214
+ console.error('Error: Presentation name is required');
215
+ printUsage(presentations);
216
+ process.exit(1);
217
+ }
218
+
219
+ if (!presentations.includes(name)) {
220
+ console.error(\`Error: Presentation "\${name}" not found\`);
221
+ printUsage(presentations);
222
+ process.exit(1);
223
+ }
224
+
225
+ runDev(name);
226
+ }
227
+
228
+ main();
229
+ `, "utf-8");
230
+ }
231
+ function createSharedPackage(targetDir) {
232
+ const sharedDir = join(targetDir, "packages", "shared");
233
+ mkdirSync(sharedDir, { recursive: true });
234
+ trackPath(sharedDir);
235
+ for (const subdir of [
236
+ "components",
237
+ "layouts",
238
+ "styles"
239
+ ]) {
240
+ const fullPath = join(sharedDir, subdir);
241
+ mkdirSync(fullPath, { recursive: true });
242
+ trackPath(fullPath);
243
+ }
244
+ writeFileSync(join(sharedDir, "package.json"), JSON.stringify({
245
+ name: "@supaslidev/shared",
246
+ private: true,
247
+ type: "module",
248
+ keywords: ["slidev-addon", "slidev"],
249
+ dependencies: { vue: "catalog:" }
250
+ }, null, 2) + "\n", "utf-8");
251
+ writeFileSync(join(sharedDir, "components", "SharedBadge.vue"), `<template>
252
+ <span class="shared-badge">
253
+ <slot />
254
+ </span>
255
+ </template>
256
+
257
+ <style scoped>
258
+ .shared-badge {
259
+ display: inline-block;
260
+ padding: 0.25rem 0.5rem;
261
+ border-radius: 0.25rem;
262
+ background-color: var(--slidev-theme-primary, #3b82f6);
263
+ color: white;
264
+ font-size: 0.875rem;
265
+ font-weight: 500;
266
+ }
267
+ </style>
268
+ `, "utf-8");
269
+ writeFileSync(join(sharedDir, "README.md"), `# @supaslidev/shared
270
+
271
+ Shared components, layouts, and styles for your Slidev presentations.
272
+
273
+ ## Usage
274
+
275
+ This package is configured as a Slidev addon. Components in the \`components\` directory are automatically available in all presentations that include this addon.
276
+
277
+ ## Structure
278
+
279
+ - \`components/\` - Shared Vue components
280
+ - \`layouts/\` - Custom slide layouts
281
+ - \`styles/\` - Global styles
282
+ `, "utf-8");
283
+ writeFileSync(join(sharedDir, "tsconfig.json"), JSON.stringify({
284
+ compilerOptions: {
285
+ target: "ESNext",
286
+ module: "ESNext",
287
+ moduleResolution: "bundler",
288
+ strict: true,
289
+ jsx: "preserve",
290
+ skipLibCheck: true
291
+ },
292
+ include: ["**/*.ts", "**/*.vue"]
293
+ }, null, 2) + "\n", "utf-8");
294
+ }
295
+ async function create(options = {}) {
296
+ const spinner = createSafeSpinner();
297
+ try {
298
+ let projectName;
299
+ let presentationName;
300
+ let initGit;
301
+ let runInstall;
302
+ if (options.name !== void 0 || options.presentation !== void 0) {
303
+ projectName = options.name ?? "my-presentations";
304
+ presentationName = options.presentation ?? "my-first-deck";
305
+ initGit = options.git ?? true;
306
+ runInstall = options.install ?? true;
307
+ if (!/^[a-z0-9-]+$/.test(projectName)) {
308
+ p.log.error("Project name must be lowercase alphanumeric with hyphens only");
309
+ process.exit(1);
310
+ }
311
+ if (projectName.startsWith("-") || projectName.endsWith("-")) {
312
+ p.log.error("Project name cannot start or end with a hyphen");
313
+ process.exit(1);
314
+ }
315
+ if (!/^[a-z0-9-]+$/.test(presentationName)) {
316
+ p.log.error("Presentation name must be lowercase alphanumeric with hyphens only");
317
+ process.exit(1);
318
+ }
319
+ if (presentationName.startsWith("-") || presentationName.endsWith("-")) {
320
+ p.log.error("Presentation name cannot start or end with a hyphen");
321
+ process.exit(1);
322
+ }
323
+ } else {
324
+ p.intro(pc.cyan("Create a new Supaslidev workspace"));
325
+ const projectNameResult = await p.text({
326
+ message: "What is your project name?",
327
+ placeholder: "my-presentations",
328
+ validate: (value) => {
329
+ if (!value.trim()) return "Project name is required";
330
+ if (!/^[a-z0-9-]+$/.test(value)) return "Project name must be lowercase alphanumeric with hyphens only";
331
+ if (value.startsWith("-") || value.endsWith("-")) return "Project name cannot start or end with a hyphen";
332
+ }
333
+ });
334
+ if (p.isCancel(projectNameResult)) {
335
+ p.cancel("Operation cancelled");
336
+ process.exit(0);
337
+ }
338
+ projectName = projectNameResult;
339
+ const presentationNameResult = await p.text({
340
+ message: "What is the name of your first presentation?",
341
+ placeholder: "my-first-deck",
342
+ initialValue: "my-first-deck",
343
+ validate: (value) => {
344
+ if (!value.trim()) return "Presentation name is required";
345
+ if (!/^[a-z0-9-]+$/.test(value)) return "Presentation name must be lowercase alphanumeric with hyphens only";
346
+ if (value.startsWith("-") || value.endsWith("-")) return "Presentation name cannot start or end with a hyphen";
347
+ }
348
+ });
349
+ if (p.isCancel(presentationNameResult)) {
350
+ p.cancel("Operation cancelled");
351
+ process.exit(0);
352
+ }
353
+ presentationName = presentationNameResult;
354
+ const initGitResult = await p.confirm({
355
+ message: "Initialize a git repository?",
356
+ initialValue: true
357
+ });
358
+ if (p.isCancel(initGitResult)) {
359
+ p.cancel("Operation cancelled");
360
+ process.exit(0);
361
+ }
362
+ initGit = initGitResult;
363
+ const runInstallResult = await p.confirm({
364
+ message: "Run pnpm install after scaffolding?",
365
+ initialValue: true
366
+ });
367
+ if (p.isCancel(runInstallResult)) {
368
+ p.cancel("Operation cancelled");
369
+ process.exit(0);
370
+ }
371
+ runInstall = runInstallResult;
372
+ }
373
+ const targetDir = join(process.cwd(), projectName);
374
+ if (existsSync(targetDir)) {
375
+ p.log.error(`Directory "${projectName}" already exists`);
376
+ process.exit(1);
377
+ }
378
+ mkdirSync(targetDir, { recursive: true });
379
+ trackPath(targetDir);
380
+ spinner.start("Creating workspace structure...");
381
+ createDirectoryStructure(targetDir);
382
+ const templateData = {
383
+ projectName,
384
+ presentationName,
385
+ description: `${projectName} - Slidev presentations monorepo`,
386
+ cliVersion: CLI_VERSION$1,
387
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
388
+ };
389
+ await renderWorkspaceTemplates(targetDir, options.template ?? "default", templateData);
390
+ spinner.message("Creating presentation...");
391
+ await createPresentation(targetDir, presentationName);
392
+ spinner.message("Creating shared package...");
393
+ createSharedPackage(targetDir);
394
+ spinner.message("Creating scripts...");
395
+ createScripts(targetDir);
396
+ spinner.stop("Workspace structure created");
397
+ if (initGit) {
398
+ spinner.start("Initializing git repository...");
399
+ try {
400
+ await runCommand("git", ["init"], targetDir);
401
+ spinner.stop("Git repository initialized");
402
+ } catch {
403
+ spinner.stop("Failed to initialize git repository");
404
+ p.log.warn("Git initialization failed. You can run \"git init\" manually.");
405
+ }
406
+ }
407
+ if (runInstall) {
408
+ spinner.start("Installing dependencies (this may take a while)...");
409
+ try {
410
+ await runCommand("pnpm", ["install"], targetDir);
411
+ spinner.stop("Dependencies installed");
412
+ } catch {
413
+ spinner.stop("Failed to install dependencies");
414
+ p.log.warn("Dependency installation failed. You can run \"pnpm install\" manually.");
415
+ }
416
+ }
417
+ createdPaths.length = 0;
418
+ p.outro(pc.green("Workspace created successfully!"));
419
+ console.log("");
420
+ console.log(pc.cyan("Next steps:"));
421
+ console.log(` ${pc.dim("$")} cd ${projectName}`);
422
+ if (!runInstall) console.log(` ${pc.dim("$")} pnpm install`);
423
+ console.log(` ${pc.dim("$")} pnpm dev ${presentationName}`);
424
+ console.log("");
425
+ } catch (error) {
426
+ spinner.stop("Failed");
427
+ if (createdPaths.length > 0) {
428
+ p.log.warn("Cleaning up partial files...");
429
+ cleanup();
430
+ p.log.info("Cleanup complete");
431
+ }
432
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
433
+ p.log.error(message);
434
+ process.exit(1);
435
+ }
436
+ }
437
+
438
+ //#endregion
439
+ //#region src/state.ts
440
+ const STATE_DIR = ".supaslidev";
441
+ const STATE_FILE = "state.json";
442
+ function getStatePath(workspaceDir) {
443
+ return join(workspaceDir, STATE_DIR, STATE_FILE);
444
+ }
445
+ function getStateDir(workspaceDir) {
446
+ return join(workspaceDir, STATE_DIR);
447
+ }
448
+ function readState(workspaceDir) {
449
+ const statePath = getStatePath(workspaceDir);
450
+ if (!existsSync(statePath)) return null;
451
+ try {
452
+ const content = readFileSync(statePath, "utf-8");
453
+ return JSON.parse(content);
454
+ } catch {
455
+ return null;
456
+ }
457
+ }
458
+ function writeState(workspaceDir, state) {
459
+ const stateDir = getStateDir(workspaceDir);
460
+ const statePath = getStatePath(workspaceDir);
461
+ if (!existsSync(stateDir)) mkdirSync(stateDir, { recursive: true });
462
+ const updatedState = {
463
+ ...state,
464
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
465
+ };
466
+ writeFileSync(statePath, JSON.stringify(updatedState, null, 2) + "\n", "utf-8");
467
+ }
468
+ function stateExists(workspaceDir) {
469
+ return existsSync(getStatePath(workspaceDir));
470
+ }
471
+ function addMigration(workspaceDir, migrationId) {
472
+ const state = readState(workspaceDir);
473
+ if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
474
+ if (state.appliedMigrations.some((m) => m.id === migrationId)) return;
475
+ state.appliedMigrations.push({
476
+ id: migrationId,
477
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString()
478
+ });
479
+ writeState(workspaceDir, state);
480
+ }
481
+ function hasMigration(workspaceDir, migrationId) {
482
+ const state = readState(workspaceDir);
483
+ if (!state) return false;
484
+ return state.appliedMigrations.some((m) => m.id === migrationId);
485
+ }
486
+ function findWorkspaceRoot(startDir = process.cwd()) {
487
+ let currentDir = startDir;
488
+ while (currentDir !== dirname(currentDir)) {
489
+ if (stateExists(currentDir)) return currentDir;
490
+ currentDir = dirname(currentDir);
491
+ }
492
+ return null;
493
+ }
494
+ function getImportedPresentations(workspaceDir) {
495
+ return readState(workspaceDir)?.importedPresentations ?? [];
496
+ }
497
+
498
+ //#endregion
499
+ //#region src/migrations/manifest.ts
500
+ const MIGRATIONS_MANIFEST_FILE = "migrations.json";
501
+ function readManifest(migrationsDir) {
502
+ const manifestPath = join(migrationsDir, MIGRATIONS_MANIFEST_FILE);
503
+ if (!existsSync(manifestPath)) return null;
504
+ const content = readFileSync(manifestPath, "utf-8");
505
+ return JSON.parse(content);
506
+ }
507
+ function validateManifest(manifest) {
508
+ const errors = [];
509
+ if (!manifest.version) errors.push("Manifest missing version field");
510
+ if (!Array.isArray(manifest.migrations)) {
511
+ errors.push("Manifest migrations field must be an array");
512
+ return errors;
513
+ }
514
+ const ids = /* @__PURE__ */ new Set();
515
+ for (const migration of manifest.migrations) {
516
+ if (!migration.id) {
517
+ errors.push("Migration entry missing id field");
518
+ continue;
519
+ }
520
+ if (ids.has(migration.id)) errors.push(`Duplicate migration id: ${migration.id}`);
521
+ ids.add(migration.id);
522
+ if (!migration.description) errors.push(`Migration ${migration.id} missing description field`);
523
+ if (!migration.version) errors.push(`Migration ${migration.id} missing version field`);
524
+ if (migration.dependencies) {
525
+ for (const dep of migration.dependencies) if (!ids.has(dep)) {
526
+ if (!manifest.migrations.some((m) => m.id === dep)) errors.push(`Migration ${migration.id} has unknown dependency: ${dep}`);
527
+ }
528
+ }
529
+ }
530
+ return errors;
531
+ }
532
+ function getMigrationOrder(manifest) {
533
+ const visited = /* @__PURE__ */ new Set();
534
+ const order = [];
535
+ const migrationMap = new Map(manifest.migrations.map((m) => [m.id, m]));
536
+ function visit(id, path) {
537
+ if (visited.has(id)) return;
538
+ if (path.has(id)) throw new Error(`Circular dependency detected involving migration: ${id}`);
539
+ const migration = migrationMap.get(id);
540
+ if (!migration) return;
541
+ path.add(id);
542
+ for (const dep of migration.dependencies ?? []) visit(dep, path);
543
+ path.delete(id);
544
+ visited.add(id);
545
+ order.push(id);
546
+ }
547
+ for (const migration of manifest.migrations) visit(migration.id, /* @__PURE__ */ new Set());
548
+ return order;
549
+ }
550
+
551
+ //#endregion
552
+ //#region src/version.ts
553
+ const CLI_VERSION = "0.1.0";
554
+ const PACKAGE_NAME = "@supaslidev/cli";
555
+ const CACHE_DIR = join(tmpdir(), "supaslidev-cli");
556
+ const CACHE_FILE = join(CACHE_DIR, "version-cache.json");
557
+ const CACHE_TTL_MS = 1440 * 60 * 1e3;
558
+ function compareVersions(current, latest) {
559
+ const parseVersion = (v) => v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
560
+ const currentParts = parseVersion(current);
561
+ const latestParts = parseVersion(latest);
562
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
563
+ const curr = currentParts[i] ?? 0;
564
+ const lat = latestParts[i] ?? 0;
565
+ if (lat > curr) return true;
566
+ if (lat < curr) return false;
567
+ }
568
+ return false;
569
+ }
570
+ function readCache() {
571
+ try {
572
+ if (!existsSync(CACHE_FILE)) return null;
573
+ const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
574
+ if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
575
+ return data;
576
+ } catch {
577
+ return null;
578
+ }
579
+ }
580
+ function writeCache(latestVersion) {
581
+ try {
582
+ if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });
583
+ const cache = {
584
+ latestVersion,
585
+ checkedAt: Date.now()
586
+ };
587
+ writeFileSync(CACHE_FILE, JSON.stringify(cache));
588
+ } catch {}
589
+ }
590
+ async function fetchLatestVersion() {
591
+ try {
592
+ const controller = new AbortController();
593
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
594
+ const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`, { signal: controller.signal });
595
+ clearTimeout(timeoutId);
596
+ if (!response.ok) return null;
597
+ const version = (await response.json())["dist-tags"].latest;
598
+ writeCache(version);
599
+ return version;
600
+ } catch {
601
+ return null;
602
+ }
603
+ }
604
+ function getCachedLatestVersion() {
605
+ return readCache()?.latestVersion ?? null;
606
+ }
607
+ async function checkForUpdates() {
608
+ const latestVersion = await fetchLatestVersion();
609
+ return {
610
+ currentVersion: CLI_VERSION,
611
+ latestVersion,
612
+ updateAvailable: latestVersion ? compareVersions(CLI_VERSION, latestVersion) : false
613
+ };
614
+ }
615
+ function checkForUpdatesCached() {
616
+ const latestVersion = getCachedLatestVersion();
617
+ return {
618
+ currentVersion: CLI_VERSION,
619
+ latestVersion,
620
+ updateAvailable: latestVersion ? compareVersions(CLI_VERSION, latestVersion) : false
621
+ };
622
+ }
623
+
624
+ //#endregion
625
+ //#region src/commands/status.ts
626
+ function getPendingMigrationsCount(workspaceDir) {
627
+ const manifest = readManifest(join(workspaceDir, ".supaslidev", "migrations"));
628
+ if (!manifest) return 0;
629
+ return manifest.migrations.filter((m) => !hasMigration(workspaceDir, m.id)).length;
630
+ }
631
+ function getAllPresentations(workspaceDir) {
632
+ const presentationsDir = join(workspaceDir, "presentations");
633
+ if (!existsSync(presentationsDir)) return [];
634
+ return readdirSync(presentationsDir).filter((name) => {
635
+ const fullPath = join(presentationsDir, name);
636
+ return statSync(fullPath).isDirectory() && existsSync(join(fullPath, "slides.md"));
637
+ }).sort();
638
+ }
639
+ async function getStatus(workspaceDir) {
640
+ const resolvedDir = workspaceDir ?? findWorkspaceRoot() ?? process.cwd();
641
+ const state = readState(resolvedDir);
642
+ const latestVersion = await fetchLatestVersion();
643
+ const updateAvailable = latestVersion ? compareVersions(CLI_VERSION, latestVersion) : false;
644
+ const importedPresentations = getImportedPresentations(resolvedDir);
645
+ const importedNames = new Set(importedPresentations.map((p) => p.name));
646
+ const nativePresentations = getAllPresentations(resolvedDir).filter((name) => !importedNames.has(name));
647
+ return {
648
+ cliVersion: CLI_VERSION,
649
+ stateVersion: state?.cliVersion ?? null,
650
+ createdAt: state?.createdAt ?? null,
651
+ lastUpdatedAt: state?.lastUpdatedAt ?? null,
652
+ pendingMigrations: state ? getPendingMigrationsCount(resolvedDir) : 0,
653
+ latestVersion,
654
+ updateAvailable,
655
+ nativePresentations,
656
+ importedPresentations
657
+ };
658
+ }
659
+ function formatDate(isoDate) {
660
+ return new Date(isoDate).toLocaleDateString("en-US", {
661
+ year: "numeric",
662
+ month: "short",
663
+ day: "numeric",
664
+ hour: "2-digit",
665
+ minute: "2-digit"
666
+ });
667
+ }
668
+ function formatStatus(status) {
669
+ const lines = [];
670
+ lines.push(pc.bold("Supaslidev Status"));
671
+ lines.push("─".repeat(40));
672
+ lines.push("");
673
+ lines.push(`${pc.dim("CLI Version:")} ${status.cliVersion}`);
674
+ if (status.stateVersion) lines.push(`${pc.dim("State Version:")} ${status.stateVersion}`);
675
+ else lines.push(`${pc.dim("State Version:")} ${pc.yellow("Not initialized")}`);
676
+ lines.push("");
677
+ if (status.createdAt) lines.push(`${pc.dim("Created:")} ${formatDate(status.createdAt)}`);
678
+ if (status.lastUpdatedAt) lines.push(`${pc.dim("Last Updated:")} ${formatDate(status.lastUpdatedAt)}`);
679
+ lines.push("");
680
+ if (status.pendingMigrations > 0) lines.push(`${pc.dim("Pending Migrations:")} ${pc.yellow(String(status.pendingMigrations))}`);
681
+ else lines.push(`${pc.dim("Pending Migrations:")} ${pc.green("0")}`);
682
+ lines.push("");
683
+ lines.push(pc.bold("Presentations"));
684
+ lines.push("─".repeat(40));
685
+ lines.push("");
686
+ lines.push(pc.dim("Native:"));
687
+ if (status.nativePresentations.length === 0) lines.push(" No native presentations");
688
+ else for (const name of status.nativePresentations) lines.push(` ${name}`);
689
+ lines.push("");
690
+ lines.push(pc.dim("Imported:"));
691
+ if (status.importedPresentations.length === 0) lines.push(" No imported presentations");
692
+ else for (const presentation of status.importedPresentations) {
693
+ lines.push(` ${pc.bold(presentation.name)}`);
694
+ lines.push(` ${pc.dim("Source:")} ${presentation.sourcePath}`);
695
+ lines.push(` ${pc.dim("Imported:")} ${formatDate(presentation.importedAt)}`);
696
+ }
697
+ lines.push("");
698
+ if (status.updateAvailable && status.latestVersion) {
699
+ lines.push(pc.yellow(`Update available: ${status.cliVersion} → ${status.latestVersion}`));
700
+ lines.push(pc.dim(" Run `pnpm add -g @supaslidev/cli` to update"));
701
+ } else if (status.latestVersion) lines.push(pc.green("✓ CLI is up to date"));
702
+ else lines.push(pc.dim("Could not check for updates"));
703
+ return lines.join("\n");
704
+ }
705
+ async function status(workspaceDir) {
706
+ const result = await getStatus(workspaceDir);
707
+ console.log(formatStatus(result));
708
+ }
709
+
710
+ //#endregion
711
+ //#region src/migrations/backup.ts
712
+ const BACKUP_DIR = ".supaslidev/backups";
713
+ const BACKUP_METADATA_FILE = "backup.json";
714
+ const EXCLUDED_PATTERNS = [
715
+ "node_modules",
716
+ ".git",
717
+ ".supaslidev/backups",
718
+ "dist",
719
+ ".turbo",
720
+ ".cache"
721
+ ];
722
+ function getBackupDir(workspaceDir) {
723
+ return join(workspaceDir, BACKUP_DIR);
724
+ }
725
+ function getBackupPath(workspaceDir, backupId) {
726
+ return join(getBackupDir(workspaceDir), backupId);
727
+ }
728
+ function generateBackupId() {
729
+ return `backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("Z", "")}`;
730
+ }
731
+ function shouldExclude(relativePath) {
732
+ return EXCLUDED_PATTERNS.some((pattern) => relativePath === pattern || relativePath.startsWith(`${pattern}/`));
733
+ }
734
+ function collectFiles(dir, baseDir) {
735
+ const files = [];
736
+ const entries = readdirSync(dir, { withFileTypes: true });
737
+ for (const entry of entries) {
738
+ const fullPath = join(dir, entry.name);
739
+ const relativePath = relative(baseDir, fullPath);
740
+ if (shouldExclude(relativePath)) continue;
741
+ if (entry.isDirectory()) files.push(...collectFiles(fullPath, baseDir));
742
+ else if (entry.isFile()) files.push(relativePath);
743
+ }
744
+ return files;
745
+ }
746
+ function createBackup(workspaceDir) {
747
+ const state = readState(workspaceDir);
748
+ if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
749
+ const backupId = generateBackupId();
750
+ const backupPath = getBackupPath(workspaceDir, backupId);
751
+ mkdirSync(backupPath, { recursive: true });
752
+ const files = collectFiles(workspaceDir, workspaceDir);
753
+ for (const file of files) {
754
+ const sourcePath = join(workspaceDir, file);
755
+ const destPath = join(backupPath, "files", file);
756
+ const destDir = join(destPath, "..");
757
+ if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
758
+ cpSync(sourcePath, destPath);
759
+ }
760
+ const metadata = {
761
+ backupId,
762
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
763
+ workspaceDir,
764
+ cliVersion: state.cliVersion,
765
+ files
766
+ };
767
+ writeFileSync(join(backupPath, BACKUP_METADATA_FILE), JSON.stringify(metadata, null, 2) + "\n", "utf-8");
768
+ return backupId;
769
+ }
770
+ function restoreBackup(workspaceDir, backupId) {
771
+ const backupPath = getBackupPath(workspaceDir, backupId);
772
+ const metadataPath = join(backupPath, BACKUP_METADATA_FILE);
773
+ if (!existsSync(metadataPath)) throw new Error(`Backup not found: ${backupId}`);
774
+ const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
775
+ for (const file of metadata.files) {
776
+ const sourcePath = join(backupPath, "files", file);
777
+ const destPath = join(workspaceDir, file);
778
+ if (existsSync(sourcePath)) {
779
+ const destDir = join(destPath, "..");
780
+ if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
781
+ cpSync(sourcePath, destPath);
782
+ }
783
+ }
784
+ }
785
+ function deleteBackup(workspaceDir, backupId) {
786
+ const backupPath = getBackupPath(workspaceDir, backupId);
787
+ if (existsSync(backupPath)) rmSync(backupPath, {
788
+ recursive: true,
789
+ force: true
790
+ });
791
+ }
792
+
793
+ //#endregion
794
+ //#region src/migrations/journal.ts
795
+ const JOURNAL_DIR = ".supaslidev";
796
+ const JOURNAL_FILE = "migration-journal.json";
797
+ function getJournalPath(workspaceDir) {
798
+ return join(workspaceDir, JOURNAL_DIR, JOURNAL_FILE);
799
+ }
800
+ function getJournalDir(workspaceDir) {
801
+ return join(workspaceDir, JOURNAL_DIR);
802
+ }
803
+ function ensureJournalDir(workspaceDir) {
804
+ const journalDir = getJournalDir(workspaceDir);
805
+ if (!existsSync(journalDir)) mkdirSync(journalDir, { recursive: true });
806
+ }
807
+ function readJournal(workspaceDir) {
808
+ const journalPath = getJournalPath(workspaceDir);
809
+ if (!existsSync(journalPath)) return { entries: [] };
810
+ const content = readFileSync(journalPath, "utf-8");
811
+ return JSON.parse(content);
812
+ }
813
+ function writeJournal(workspaceDir, journal) {
814
+ ensureJournalDir(workspaceDir);
815
+ writeFileSync(getJournalPath(workspaceDir), JSON.stringify(journal, null, 2) + "\n", "utf-8");
816
+ }
817
+ function addJournalEntry(workspaceDir, entry) {
818
+ const journal = readJournal(workspaceDir);
819
+ journal.entries.push(entry);
820
+ writeJournal(workspaceDir, journal);
821
+ }
822
+ function createJournalEntry(migrationId, backupId, success, rolledBack = false, error) {
823
+ return {
824
+ migrationId,
825
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
826
+ backupId,
827
+ success,
828
+ rolledBack,
829
+ error: error?.message
830
+ };
831
+ }
832
+
833
+ //#endregion
834
+ //#region src/migrations/runner.ts
835
+ function loadMigration(migrations, id) {
836
+ return migrations.find((m) => m.id === id) ?? null;
837
+ }
838
+ function dryRun(options) {
839
+ const { workspaceDir, migrationsDir } = options;
840
+ const results = [];
841
+ const manifest = readManifest(migrationsDir);
842
+ if (!manifest) throw new Error(`No migrations.json found in ${migrationsDir}`);
843
+ const errors = validateManifest(manifest);
844
+ if (errors.length > 0) throw new Error(`Invalid migrations.json:\n${errors.join("\n")}`);
845
+ const order = getMigrationOrder(manifest);
846
+ const manifestMap = new Map(manifest.migrations.map((m) => [m.id, m]));
847
+ for (const id of order) {
848
+ const entry = manifestMap.get(id);
849
+ if (!entry) continue;
850
+ const alreadyApplied = hasMigration(workspaceDir, id);
851
+ results.push({
852
+ migrationId: id,
853
+ description: entry.description,
854
+ wouldApply: !alreadyApplied,
855
+ alreadyApplied,
856
+ breaking: entry.breaking
857
+ });
858
+ }
859
+ return results;
860
+ }
861
+ function formatDryRunOutput(results) {
862
+ const lines = [];
863
+ lines.push("Migration Preview (Dry Run)");
864
+ lines.push("=".repeat(50));
865
+ lines.push("");
866
+ const toApply = results.filter((r) => r.wouldApply);
867
+ const alreadyApplied = results.filter((r) => r.alreadyApplied);
868
+ if (toApply.length === 0) lines.push("No migrations to apply. Workspace is up to date.");
869
+ else {
870
+ lines.push(`Migrations to apply: ${toApply.length}`);
871
+ lines.push("");
872
+ for (const result of toApply) {
873
+ const breakingTag = result.breaking ? " [BREAKING]" : "";
874
+ lines.push(` → ${result.migrationId}${breakingTag}`);
875
+ lines.push(` ${result.description}`);
876
+ }
877
+ }
878
+ if (alreadyApplied.length > 0) {
879
+ lines.push("");
880
+ lines.push(`Already applied: ${alreadyApplied.length}`);
881
+ for (const result of alreadyApplied) lines.push(` ✓ ${result.migrationId}`);
882
+ }
883
+ lines.push("");
884
+ lines.push("Run with --apply to execute migrations.");
885
+ return lines.join("\n");
886
+ }
887
+ async function executeMigration(migration, context) {
888
+ try {
889
+ await migration.up(context);
890
+ return {
891
+ migrationId: migration.id,
892
+ success: true,
893
+ rolledBack: false
894
+ };
895
+ } catch (error) {
896
+ return {
897
+ migrationId: migration.id,
898
+ success: false,
899
+ error: error instanceof Error ? error : new Error(String(error)),
900
+ rolledBack: false
901
+ };
902
+ }
903
+ }
904
+ async function run$1(options) {
905
+ const { workspaceDir, migrationsDir, apply = false, migrations = [], migrationOptions = {} } = options;
906
+ if (!apply) return {
907
+ success: true,
908
+ applied: [],
909
+ skipped: dryRun(options).filter((r) => r.alreadyApplied).map((r) => r.migrationId),
910
+ failed: null,
911
+ backupId: null,
912
+ rolledBack: false
913
+ };
914
+ const manifest = readManifest(migrationsDir);
915
+ if (!manifest) throw new Error(`No migrations.json found in ${migrationsDir}`);
916
+ const errors = validateManifest(manifest);
917
+ if (errors.length > 0) throw new Error(`Invalid migrations.json:\n${errors.join("\n")}`);
918
+ const order = getMigrationOrder(manifest);
919
+ const toApply = order.filter((id) => !hasMigration(workspaceDir, id));
920
+ if (toApply.length === 0) return {
921
+ success: true,
922
+ applied: [],
923
+ skipped: order,
924
+ failed: null,
925
+ backupId: null,
926
+ rolledBack: false
927
+ };
928
+ const backupId = createBackup(workspaceDir);
929
+ const state = readState(workspaceDir);
930
+ if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
931
+ const baseContext = {
932
+ workspaceDir,
933
+ state,
934
+ backupPath: backupId ? join(workspaceDir, ".supaslidev", "backups", backupId) : null
935
+ };
936
+ const applied = [];
937
+ let failed = null;
938
+ let rolledBack = false;
939
+ for (const id of toApply) {
940
+ const migration = loadMigration(migrations, id);
941
+ if (!migration) {
942
+ failed = {
943
+ migrationId: id,
944
+ success: false,
945
+ error: /* @__PURE__ */ new Error(`Migration implementation not found: ${id}`),
946
+ rolledBack: false
947
+ };
948
+ break;
949
+ }
950
+ const result = await executeMigration(migration, {
951
+ ...baseContext,
952
+ options: migrationOptions[id]
953
+ });
954
+ if (result.success) {
955
+ applied.push(result);
956
+ addMigration(workspaceDir, id);
957
+ addJournalEntry(workspaceDir, createJournalEntry(id, backupId, true, false));
958
+ } else {
959
+ failed = result;
960
+ addJournalEntry(workspaceDir, createJournalEntry(id, backupId, false, false, result.error));
961
+ break;
962
+ }
963
+ }
964
+ if (failed) {
965
+ restoreBackup(workspaceDir, backupId);
966
+ rolledBack = true;
967
+ addJournalEntry(workspaceDir, createJournalEntry(failed.migrationId, backupId, false, true, failed.error));
968
+ failed.rolledBack = true;
969
+ } else deleteBackup(workspaceDir, backupId);
970
+ const skipped = order.filter((id) => !toApply.includes(id) || !applied.some((a) => a.migrationId === id) && failed?.migrationId !== id);
971
+ return {
972
+ success: !failed,
973
+ applied,
974
+ skipped,
975
+ failed,
976
+ backupId: failed ? backupId : null,
977
+ rolledBack
978
+ };
979
+ }
980
+ function formatRunOutput(result) {
981
+ const lines = [];
982
+ if (result.success) {
983
+ lines.push("Migration Complete");
984
+ lines.push("=".repeat(50));
985
+ lines.push("");
986
+ if (result.applied.length === 0) lines.push("No migrations were applied. Workspace is up to date.");
987
+ else {
988
+ lines.push(`Applied ${result.applied.length} migration(s):`);
989
+ for (const migration of result.applied) lines.push(` ✓ ${migration.migrationId}`);
990
+ }
991
+ if (result.skipped.length > 0) {
992
+ lines.push("");
993
+ lines.push(`Skipped ${result.skipped.length} (already applied):`);
994
+ for (const id of result.skipped) lines.push(` - ${id}`);
995
+ }
996
+ } else {
997
+ lines.push("Migration Failed");
998
+ lines.push("=".repeat(50));
999
+ lines.push("");
1000
+ if (result.failed) {
1001
+ lines.push(`Failed migration: ${result.failed.migrationId}`);
1002
+ if (result.failed.error) lines.push(`Error: ${result.failed.error.message}`);
1003
+ }
1004
+ if (result.rolledBack) {
1005
+ lines.push("");
1006
+ lines.push("Workspace has been restored from backup.");
1007
+ if (result.backupId) lines.push(`Backup preserved: ${result.backupId}`);
1008
+ }
1009
+ if (result.applied.length > 0) {
1010
+ lines.push("");
1011
+ lines.push(`Rolled back ${result.applied.length} migration(s):`);
1012
+ for (const migration of result.applied) lines.push(` ↩ ${migration.migrationId}`);
1013
+ }
1014
+ }
1015
+ return lines.join("\n");
1016
+ }
1017
+
1018
+ //#endregion
1019
+ //#region src/migrations/loader.ts
1020
+ const MIGRATIONS = [];
1021
+ async function loadMigrations() {
1022
+ return {
1023
+ migrations: MIGRATIONS.map((entry) => ({
1024
+ id: entry.id,
1025
+ description: entry.description,
1026
+ up: entry.module.up,
1027
+ down: entry.module.down
1028
+ })),
1029
+ order: MIGRATIONS.map((entry) => entry.id)
1030
+ };
1031
+ }
1032
+ async function loadInteractiveMigration(id) {
1033
+ const entry = MIGRATIONS.find((m) => m.id === id);
1034
+ if (!entry) return null;
1035
+ return {
1036
+ migration: {
1037
+ id: entry.id,
1038
+ description: entry.description,
1039
+ up: entry.module.up,
1040
+ down: entry.module.down
1041
+ },
1042
+ getAffectedPresentations: entry.module.getAffectedPresentations
1043
+ };
1044
+ }
1045
+
1046
+ //#endregion
1047
+ //#region src/prompts.ts
1048
+ async function promptForCatalogSelection(presentations) {
1049
+ p.intro(pc.cyan("Catalog Conversion Selection"));
1050
+ p.note(`${pc.bold("Select presentations to convert to catalog: references")}\n\nSelected presentations will use ${pc.green("catalog:")} (managed versions)\nUnselected presentations will use ${pc.yellow("^52.11.3")} (pinned version)\n\n${pc.dim("Controls:")} ${pc.bold("Space")} ${pc.dim("toggle |")} ${pc.bold("a")} ${pc.dim("toggle all |")} ${pc.bold("Enter")} ${pc.dim("confirm")}`, "Migration Options");
1051
+ const options = presentations.map((pres) => ({
1052
+ value: pres.name,
1053
+ label: pres.name,
1054
+ hint: `currently ${pres.currentVersion}`
1055
+ }));
1056
+ const selected = await p.multiselect({
1057
+ message: "Which presentations should use catalog: references?",
1058
+ options,
1059
+ initialValues: presentations.map((p) => p.name),
1060
+ required: false
1061
+ });
1062
+ if (p.isCancel(selected)) {
1063
+ p.cancel("Migration cancelled");
1064
+ return {
1065
+ selectedForCatalog: [],
1066
+ cancelled: true
1067
+ };
1068
+ }
1069
+ const selectedCount = selected.length;
1070
+ const pinnedCount = presentations.length - selectedCount;
1071
+ if (selectedCount > 0) p.log.success(`${selectedCount} presentation(s) will use catalog: references`);
1072
+ if (pinnedCount > 0) p.log.info(`${pinnedCount} presentation(s) will use pinned ^52.11.3`);
1073
+ return {
1074
+ selectedForCatalog: selected,
1075
+ cancelled: false
1076
+ };
1077
+ }
1078
+
1079
+ //#endregion
1080
+ //#region src/commands/migrate.ts
1081
+ async function getMigrateResult(options = {}) {
1082
+ const workspaceDir = findWorkspaceRoot();
1083
+ if (!workspaceDir) return {
1084
+ success: false,
1085
+ dryRun: !options.apply,
1086
+ migrationsToApply: 0,
1087
+ migrationsApplied: 0,
1088
+ output: "Not a Supaslidev workspace. No .supaslidev/state.json found."
1089
+ };
1090
+ const migrationsDir = join(workspaceDir, ".supaslidev", "migrations");
1091
+ if (options.apply) {
1092
+ const { migrations } = await loadMigrations();
1093
+ const migrationOptions = {};
1094
+ const slidev51to52 = await loadInteractiveMigration("slidev-51-to-52");
1095
+ if (slidev51to52?.getAffectedPresentations) {
1096
+ const affected = slidev51to52.getAffectedPresentations(workspaceDir);
1097
+ if (affected.length > 0) {
1098
+ const selectionResult = await promptForCatalogSelection(affected);
1099
+ if (selectionResult.cancelled) return {
1100
+ success: false,
1101
+ dryRun: false,
1102
+ migrationsToApply: 0,
1103
+ migrationsApplied: 0,
1104
+ output: "Migration cancelled by user."
1105
+ };
1106
+ migrationOptions["slidev-51-to-52"] = {
1107
+ interactive: true,
1108
+ selectedForCatalog: selectionResult.selectedForCatalog
1109
+ };
1110
+ }
1111
+ }
1112
+ const result = await run$1({
1113
+ workspaceDir,
1114
+ migrationsDir,
1115
+ apply: true,
1116
+ migrations,
1117
+ migrationOptions
1118
+ });
1119
+ return {
1120
+ success: result.success,
1121
+ dryRun: false,
1122
+ migrationsToApply: result.applied.length + (result.failed ? 1 : 0),
1123
+ migrationsApplied: result.applied.length,
1124
+ output: formatRunOutput(result)
1125
+ };
1126
+ }
1127
+ try {
1128
+ const results = dryRun({
1129
+ workspaceDir,
1130
+ migrationsDir
1131
+ });
1132
+ return {
1133
+ success: true,
1134
+ dryRun: true,
1135
+ migrationsToApply: results.filter((r) => r.wouldApply).length,
1136
+ migrationsApplied: 0,
1137
+ output: formatDryRunOutput(results)
1138
+ };
1139
+ } catch (error) {
1140
+ const message = error instanceof Error ? error.message : String(error);
1141
+ if (message.includes("No migrations.json found")) return {
1142
+ success: true,
1143
+ dryRun: true,
1144
+ migrationsToApply: 0,
1145
+ migrationsApplied: 0,
1146
+ output: "No migrations available. Workspace is up to date."
1147
+ };
1148
+ return {
1149
+ success: false,
1150
+ dryRun: true,
1151
+ migrationsToApply: 0,
1152
+ migrationsApplied: 0,
1153
+ output: `Error: ${message}`
1154
+ };
1155
+ }
1156
+ }
1157
+ function formatMigrateOutput(result) {
1158
+ const lines = [];
1159
+ lines.push(pc.bold("Supaslidev Migrate"));
1160
+ lines.push("─".repeat(40));
1161
+ lines.push("");
1162
+ if (result.dryRun) {
1163
+ lines.push(pc.cyan("Mode: Dry Run (no changes will be made)"));
1164
+ lines.push("");
1165
+ }
1166
+ lines.push(result.output);
1167
+ if (!result.success) {
1168
+ lines.push("");
1169
+ lines.push(pc.red("✗ Migration failed"));
1170
+ } else if (result.dryRun && result.migrationsToApply > 0) {
1171
+ lines.push("");
1172
+ lines.push(pc.yellow(`Run ${pc.bold("supaslidev migrate --apply")} to execute migrations.`));
1173
+ } else if (!result.dryRun && result.migrationsApplied > 0) {
1174
+ lines.push("");
1175
+ lines.push(pc.green("✓ All migrations applied successfully"));
1176
+ }
1177
+ return lines.join("\n");
1178
+ }
1179
+ async function migrate(options = {}) {
1180
+ const result = await getMigrateResult(options);
1181
+ console.log(formatMigrateOutput(result));
1182
+ if (!result.success) process.exit(1);
1183
+ }
1184
+
1185
+ //#endregion
1186
+ //#region src/commands/update.ts
1187
+ async function getUpdateResult() {
1188
+ const check = await checkForUpdates();
1189
+ return {
1190
+ currentVersion: check.currentVersion,
1191
+ latestVersion: check.latestVersion,
1192
+ updateAvailable: check.updateAvailable,
1193
+ error: check.latestVersion === null ? "Could not reach npm registry" : null
1194
+ };
1195
+ }
1196
+ function formatUpdateResult(result) {
1197
+ const lines = [];
1198
+ lines.push(pc.bold("Supaslidev Update Check"));
1199
+ lines.push("─".repeat(40));
1200
+ lines.push("");
1201
+ lines.push(`${pc.dim("Current Version:")} ${result.currentVersion}`);
1202
+ if (result.error) {
1203
+ lines.push("");
1204
+ lines.push(pc.yellow(`⚠ ${result.error}`));
1205
+ return lines.join("\n");
1206
+ }
1207
+ lines.push(`${pc.dim("Latest Version:")} ${result.latestVersion}`);
1208
+ lines.push("");
1209
+ if (result.updateAvailable) {
1210
+ lines.push(pc.yellow(`Update available: ${result.currentVersion} → ${result.latestVersion}`));
1211
+ lines.push("");
1212
+ lines.push(pc.bold("To update, run:"));
1213
+ lines.push(` ${pc.cyan(`npm install -g ${PACKAGE_NAME}`)}`);
1214
+ lines.push("");
1215
+ lines.push(pc.dim("or with pnpm:"));
1216
+ lines.push(` ${pc.cyan(`pnpm add -g ${PACKAGE_NAME}`)}`);
1217
+ } else lines.push(pc.green("✓ You are using the latest version"));
1218
+ return lines.join("\n");
1219
+ }
1220
+ async function update() {
1221
+ const result = await getUpdateResult();
1222
+ console.log(formatUpdateResult(result));
1223
+ if (result.error) process.exit(1);
1224
+ }
1225
+
1226
+ //#endregion
1227
+ //#region src/background-update.ts
1228
+ function startBackgroundUpdateCheck() {
1229
+ const cached = checkForUpdatesCached();
1230
+ if (cached.updateAvailable && cached.latestVersion) {
1231
+ scheduleNotification(cached.latestVersion);
1232
+ return;
1233
+ }
1234
+ fetchLatestVersion().then((latestVersion) => {
1235
+ if (latestVersion && compareVersions(CLI_VERSION, latestVersion)) scheduleNotification(latestVersion);
1236
+ }).catch(() => {});
1237
+ }
1238
+ function scheduleNotification(latestVersion) {
1239
+ process.on("beforeExit", () => {
1240
+ printUpdateNotification(latestVersion);
1241
+ });
1242
+ }
1243
+ function printUpdateNotification(latestVersion) {
1244
+ const border = "─".repeat(50);
1245
+ console.log("");
1246
+ console.log(pc.yellow(border));
1247
+ console.log(pc.yellow(` Update available: ${pc.dim(CLI_VERSION)} → ${pc.green(latestVersion)}`));
1248
+ console.log(pc.yellow(` Run ${pc.cyan(`npm install -g ${PACKAGE_NAME}`)} to update`));
1249
+ console.log(pc.yellow(border));
1250
+ }
1251
+
1252
+ //#endregion
1253
+ //#region src/cli.ts
1254
+ const program = new Command();
1255
+ program.name("create-supaslidev").description("CLI tool for scaffolding Supaslidev presentations").version("0.1.0");
1256
+ program.command("create", { isDefault: true }).description("Create a new Supaslidev workspace").option("-n, --name <name>", "Name of the workspace").option("-p, --presentation <name>", "Name of the first presentation").option("-t, --template <template>", "Template to use", "default").option("--git", "Initialize a git repository").option("--no-git", "Skip git initialization").option("--install", "Run pnpm install after scaffolding").option("--no-install", "Skip pnpm install").action(async (options) => {
1257
+ await create(options);
1258
+ });
1259
+ program.command("status").description("Show project status and check for updates").action(async () => {
1260
+ await status();
1261
+ });
1262
+ program.command("migrate").description("Run migrations to update the workspace").option("--apply", "Execute migrations (default is dry-run mode)").action(async (options) => {
1263
+ await migrate(options);
1264
+ });
1265
+ program.command("update").description("Check for CLI updates").action(async () => {
1266
+ await update();
1267
+ });
1268
+ async function run() {
1269
+ startBackgroundUpdateCheck();
1270
+ await program.parseAsync();
1271
+ }
1272
+ const scriptName = process.argv[1] ? basename(process.argv[1]) : "";
1273
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href || scriptName === "cli.js") run().catch((err) => {
1274
+ console.error(err);
1275
+ process.exit(1);
1276
+ });
1277
+
1278
+ //#endregion
1279
+ export { run };