slidev-workspace 0.7.1 → 0.8.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 CHANGED
@@ -4,6 +4,7 @@ import { dirname, join, resolve } from "node:path";
4
4
  import { existsSync, mkdirSync, readdirSync } from "node:fs";
5
5
  import { cp, rm } from "node:fs/promises";
6
6
  import { execSync } from "node:child_process";
7
+ import { Command } from "commander";
7
8
  import { build, createServer } from "vite";
8
9
  import vue from "@vitejs/plugin-vue";
9
10
  import tailwindcss from "@tailwindcss/vite";
@@ -124,83 +125,114 @@ if (import.meta.url === `file://${process.argv[1]}`) {
124
125
  console.log(JSON.stringify(slides, null, 2));
125
126
  }
126
127
 
128
+ //#endregion
129
+ //#region src/scripts/collectSlides.ts
130
+ function collectSlides({ slidesDirs, names = [], exclude = [] }) {
131
+ const entries = [];
132
+ for (const slidesDir of slidesDirs) {
133
+ if (!existsSync(slidesDir)) {
134
+ console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
135
+ continue;
136
+ }
137
+ if (names.length > 0) {
138
+ for (const slideName of names) {
139
+ if (exclude.includes(slideName)) continue;
140
+ const slideDir = join(slidesDir, slideName);
141
+ if (!existsSync(slideDir)) continue;
142
+ entries.push({
143
+ slidesDir,
144
+ slideName,
145
+ slideDir
146
+ });
147
+ }
148
+ continue;
149
+ }
150
+ const slideNames = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).filter((dirent) => !exclude.includes(dirent.name)).map((dirent) => dirent.name);
151
+ for (const slideName of slideNames) {
152
+ const slideDir = join(slidesDir, slideName);
153
+ entries.push({
154
+ slidesDir,
155
+ slideName,
156
+ slideDir
157
+ });
158
+ }
159
+ }
160
+ return entries;
161
+ }
162
+
127
163
  //#endregion
128
164
  //#region src/scripts/devServer.ts
129
165
  const runningServers = new Map();
130
- async function startAllSlidesDevServer(workspaceCwd) {
131
- const cwd = workspaceCwd || process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
166
+ async function startAllSlidesDevServer({ basePort = 3001 } = {}) {
167
+ const cwd = process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
132
168
  const config = loadConfig(cwd);
133
169
  const slidesDirs = resolveSlidesDirs(config, cwd);
134
- let currentPort = 3001;
170
+ let currentPort = basePort;
135
171
  const devServers = [];
136
172
  console.log("🚀 Starting Slidev dev servers for all slides...");
137
173
  console.log("📁 Working directory:", cwd);
138
174
  console.log("📂 Slides directories found:", slidesDirs);
139
- for (const slidesDir of slidesDirs) {
140
- if (!existsSync$1(slidesDir)) {
141
- console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
175
+ const slides = collectSlides({
176
+ slidesDirs,
177
+ exclude: config.exclude
178
+ });
179
+ for (const { slideDir, slideName } of slides) {
180
+ const packageJsonPath = join$1(slideDir, "package.json");
181
+ const slideKey = slideDir;
182
+ if (runningServers.has(slideKey)) {
183
+ console.log(`⏭️ ${slideName} dev server already running, skipping...`);
184
+ devServers.push(runningServers.get(slideKey));
142
185
  continue;
143
186
  }
144
- const slides = readdirSync$1(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
145
- for (const slideName of slides) {
146
- const slideDir = join$1(slidesDir, slideName);
147
- const packageJsonPath = join$1(slideDir, "package.json");
148
- const slideKey = slideDir;
149
- if (runningServers.has(slideKey)) {
150
- console.log(`⏭️ ${slideName} dev server already running, skipping...`);
151
- devServers.push(runningServers.get(slideKey));
152
- continue;
153
- }
154
- if (!existsSync$1(packageJsonPath)) {
155
- console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
156
- continue;
157
- }
158
- const nodeModulesPath = join$1(slideDir, "node_modules");
159
- if (!existsSync$1(nodeModulesPath)) {
160
- console.warn(`⚠️ Skipping ${slideName}: dependencies not installed (run pnpm install)`);
161
- continue;
162
- }
163
- console.log(`📦 Starting Slidev dev server for ${slideName} on port ${currentPort}...`);
164
- try {
165
- const devProcess = spawn("pnpm", [
166
- "run",
167
- "dev",
168
- "--port",
169
- currentPort.toString(),
170
- "--open",
171
- "false"
172
- ], {
173
- cwd: slideDir,
174
- stdio: [
175
- "pipe",
176
- "pipe",
177
- "pipe"
178
- ],
179
- detached: false,
180
- env: {
181
- ...process.env,
182
- PATH: process.env.PATH
183
- },
184
- shell: true
185
- });
186
- devProcess.stdout?.on("data", (data) => {
187
- const output = data.toString();
188
- if (output.includes("Local:") || output.includes("ready")) console.log(`✅ ${slideName} dev server ready on port ${currentPort}`);
189
- });
190
- devProcess.stderr?.on("data", (data) => {
191
- console.error(`❌ ${slideName} dev server error:`, data.toString());
192
- });
193
- const serverInfo = {
194
- name: slideName,
195
- port: currentPort,
196
- process: devProcess
197
- };
198
- devServers.push(serverInfo);
199
- runningServers.set(slideKey, serverInfo);
200
- currentPort++;
201
- } catch (error) {
202
- console.error(`❌ Failed to start dev server for ${slideName}:`, error);
203
- }
187
+ if (!existsSync$1(packageJsonPath)) {
188
+ console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
189
+ continue;
190
+ }
191
+ const nodeModulesPath = join$1(slideDir, "node_modules");
192
+ if (!existsSync$1(nodeModulesPath)) {
193
+ console.warn(`⚠️ Skipping ${slideName}: dependencies not installed (run pnpm install)`);
194
+ continue;
195
+ }
196
+ console.log(`📦 Starting Slidev dev server for ${slideName} on port ${currentPort}...`);
197
+ try {
198
+ const devProcess = spawn("pnpm", [
199
+ "run",
200
+ "dev",
201
+ "--port",
202
+ currentPort.toString(),
203
+ "--open",
204
+ "false"
205
+ ], {
206
+ cwd: slideDir,
207
+ stdio: [
208
+ "pipe",
209
+ "pipe",
210
+ "pipe"
211
+ ],
212
+ detached: false,
213
+ env: {
214
+ ...process.env,
215
+ PATH: process.env.PATH
216
+ },
217
+ shell: true
218
+ });
219
+ devProcess.stdout?.on("data", (data) => {
220
+ const output = data.toString();
221
+ if (output.includes("Local:") || output.includes("ready")) console.log(`✅ ${slideName} dev server ready on port ${currentPort}`);
222
+ });
223
+ devProcess.stderr?.on("data", (data) => {
224
+ console.error(`❌ ${slideName} dev server error:`, data.toString());
225
+ });
226
+ const serverInfo = {
227
+ name: slideName,
228
+ port: currentPort,
229
+ process: devProcess
230
+ };
231
+ devServers.push(serverInfo);
232
+ runningServers.set(slideKey, serverInfo);
233
+ currentPort++;
234
+ } catch (error) {
235
+ console.error(`❌ Failed to start dev server for ${slideName}:`, error);
204
236
  }
205
237
  }
206
238
  return devServers;
@@ -242,8 +274,9 @@ async function transformIndexHtml(html) {
242
274
 
243
275
  //#endregion
244
276
  //#region src/vite/plugin-slides.ts
245
- function slidesPlugin() {
277
+ function slidesPlugin(options = {}) {
246
278
  let devServers = [];
279
+ const devServerBasePort = options.devServerBasePort ?? 3001;
247
280
  return {
248
281
  name: "vite-plugin-slides",
249
282
  async transformIndexHtml(html) {
@@ -253,23 +286,23 @@ function slidesPlugin() {
253
286
  try {
254
287
  const config = loadConfig();
255
288
  const slidesDirs = resolveSlidesDirs(config);
256
- for (const slidesDir of slidesDirs) {
257
- if (!existsSync$1(slidesDir)) continue;
258
- const slideDirs = readdirSync$1(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).filter((dirent) => !(config.exclude || []).includes(dirent.name)).map((dirent) => dirent.name);
259
- for (const slideDir of slideDirs) {
260
- const slideDistPath = join$1(slidesDir, slideDir, "dist");
261
- const assetsPath = join$1(slideDistPath, "assets");
262
- if (!existsSync$1(assetsPath)) continue;
263
- const assetFiles = readdirSync$1(assetsPath);
264
- const ogImageFile = assetFiles.find((file) => /^og-image-[a-zA-Z0-9]+\.png$/.test(file));
265
- if (ogImageFile) {
266
- const sourceFile = join$1(assetsPath, ogImageFile);
267
- const destFile = join$1(slideDistPath, "og-image.png");
268
- try {
269
- cpSync(sourceFile, destFile, { force: true });
270
- } catch (error) {
271
- console.warn(`⚠ Failed to copy og-image for ${slideDir}:`, error);
272
- }
289
+ const slides = collectSlides({
290
+ slidesDirs,
291
+ exclude: config.exclude
292
+ });
293
+ for (const { slideDir } of slides) {
294
+ const slideDistPath = join$1(slideDir, "dist");
295
+ const assetsPath = join$1(slideDistPath, "assets");
296
+ if (!existsSync$1(assetsPath)) continue;
297
+ const assetFiles = readdirSync$1(assetsPath);
298
+ const ogImageFile = assetFiles.find((file) => /^og-image-[a-zA-Z0-9]+\.png$/.test(file));
299
+ if (ogImageFile) {
300
+ const sourceFile = join$1(assetsPath, ogImageFile);
301
+ const destFile = join$1(slideDistPath, "og-image.png");
302
+ try {
303
+ cpSync(sourceFile, destFile, { force: true });
304
+ } catch (error) {
305
+ console.warn(`⚠ Failed to copy og-image for ${slideDir}:`, error);
273
306
  }
274
307
  }
275
308
  }
@@ -282,7 +315,7 @@ function slidesPlugin() {
282
315
  const config = loadConfig();
283
316
  const slidesDirs = resolveSlidesDirs(config);
284
317
  try {
285
- devServers = await startAllSlidesDevServer();
318
+ devServers = await startAllSlidesDevServer({ basePort: devServerBasePort });
286
319
  } catch (error) {
287
320
  console.error("❌ Failed to start slides dev servers:", error);
288
321
  }
@@ -337,61 +370,64 @@ export default configData;`;
337
370
  //#region src/cli.ts
338
371
  const __filename = fileURLToPath(import.meta.url);
339
372
  const __dirname = dirname(__filename);
340
- const args = process.argv.slice(2);
341
- const command = args[0];
342
373
  const packageRoot = join(__dirname, "..");
343
- function createViteConfig() {
374
+ const DEFAULT_PREVIEW_PORT = 3e3;
375
+ function parsePortOption(value) {
376
+ const port = Number(value);
377
+ if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${value}`);
378
+ return port;
379
+ }
380
+ function createViteConfig(previewPort = DEFAULT_PREVIEW_PORT) {
344
381
  const workspaceCwd = process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
345
382
  const config = loadConfig(workspaceCwd);
383
+ const devServerBasePort = previewPort + 1;
346
384
  return {
347
385
  root: resolve(packageRoot, "src/preview"),
348
386
  base: config.baseUrl,
349
387
  plugins: [
350
388
  vue(),
351
389
  tailwindcss(),
352
- slidesPlugin()
390
+ slidesPlugin({ devServerBasePort })
353
391
  ],
354
392
  resolve: { alias: { "@": resolve(packageRoot, "src/preview") } },
393
+ define: { __SLIDEV_WORKSPACE_DEV_PORT_BASE__: devServerBasePort },
355
394
  build: { outDir: resolve(workspaceCwd, config.outputDir) },
356
395
  server: {
357
- port: 3e3,
396
+ port: previewPort,
358
397
  open: true
359
398
  }
360
399
  };
361
400
  }
362
- async function buildAllSlides() {
401
+ async function buildSlides(names) {
363
402
  const workspaceCwd = process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
364
403
  const config = loadConfig(workspaceCwd);
365
404
  const slidesDirs = resolveSlidesDirs(config, workspaceCwd);
366
- console.log("🔨 Building all slides...");
367
- for (const slidesDir of slidesDirs) {
368
- if (!existsSync(slidesDir)) {
369
- console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
405
+ console.log(names ? `🔨 Building slides: ${names.join(", ")}...` : "🔨 Building all slides...");
406
+ const slides = collectSlides({
407
+ slidesDirs,
408
+ names,
409
+ exclude: config.exclude
410
+ });
411
+ for (const { slideDir, slideName } of slides) {
412
+ const packageJsonPath = join(slideDir, "package.json");
413
+ if (!existsSync(packageJsonPath)) {
414
+ console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
370
415
  continue;
371
416
  }
372
- const slides = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
373
- for (const slideName of slides) {
374
- const slideDir = join(slidesDir, slideName);
375
- const packageJsonPath = join(slideDir, "package.json");
376
- if (!existsSync(packageJsonPath)) {
377
- console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
378
- continue;
379
- }
380
- console.log(`📦 Building slide: ${slideName}`);
381
- try {
382
- const baseUrl = config.baseUrl.endsWith("/") ? config.baseUrl : config.baseUrl + "/";
383
- const subDir = slideDir.startsWith(workspaceCwd) ? slideDir.replace(workspaceCwd, "").replace(/^\//, "") : slideDir;
384
- const buildCmd = `pnpm --filter "./${subDir}" run build --base ${baseUrl}${slideName}/`;
385
- console.log(buildCmd);
386
- execSync(buildCmd, {
387
- cwd: workspaceCwd,
388
- stdio: "inherit"
389
- });
390
- console.log(`✅ Built slide: ${slideName}`);
391
- } catch (error) {
392
- console.error(`❌ Failed to build slide ${slideName}:`, error);
393
- process.exit(1);
394
- }
417
+ console.log(`📦 Building slide: ${slideName}`);
418
+ try {
419
+ const baseUrl = config.baseUrl.endsWith("/") ? config.baseUrl : config.baseUrl + "/";
420
+ const subDir = slideDir.startsWith(workspaceCwd) ? slideDir.replace(workspaceCwd, "").replace(/^\//, "") : slideDir;
421
+ const buildCmd = `pnpm --filter "./${subDir}" run build --base ${baseUrl}${slideName}/`;
422
+ console.log(buildCmd);
423
+ execSync(buildCmd, {
424
+ cwd: workspaceCwd,
425
+ stdio: "inherit"
426
+ });
427
+ console.log(`✅ Built slide: ${slideName}`);
428
+ } catch (error) {
429
+ console.error(`❌ Failed to build slide ${slideName}:`, error);
430
+ process.exit(1);
395
431
  }
396
432
  }
397
433
  }
@@ -406,28 +442,24 @@ async function exportOgImages() {
406
442
  stdio: "inherit"
407
443
  });
408
444
  console.log("📦 Copying exported images to og-image.png...");
409
- for (const slidesDir of slidesDirs) {
410
- if (!existsSync(slidesDir)) {
411
- console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
412
- continue;
413
- }
414
- const slides = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
415
- for (const slideName of slides) {
416
- const slideDir = join(slidesDir, slideName);
417
- const packageJsonPath = join(slideDir, "package.json");
418
- if (!existsSync(packageJsonPath)) continue;
419
- const exportedFile = join(slideDir, "slides-export", "1.png");
420
- const targetFile = join(slideDir, "og-image.png");
421
- const exportDir = join(slideDir, "slides-export");
422
- if (existsSync(exportedFile)) {
423
- await cp(exportedFile, targetFile);
424
- console.log(`✅ Generated OG image for: ${slideName}`);
425
- await rm(exportDir, {
426
- recursive: true,
427
- force: true
428
- });
429
- } else console.warn(`⚠️ Export file not found for ${slideName}: ${exportedFile}`);
430
- }
445
+ const slides = collectSlides({
446
+ slidesDirs,
447
+ exclude: config.exclude
448
+ });
449
+ for (const { slideDir, slideName } of slides) {
450
+ const packageJsonPath = join(slideDir, "package.json");
451
+ if (!existsSync(packageJsonPath)) continue;
452
+ const exportedFile = join(slideDir, "slides-export", "1.png");
453
+ const targetFile = join(slideDir, "og-image.png");
454
+ const exportDir = join(slideDir, "slides-export");
455
+ if (existsSync(exportedFile)) {
456
+ await cp(exportedFile, targetFile);
457
+ console.log(`✅ Generated OG image for: ${slideName}`);
458
+ await rm(exportDir, {
459
+ recursive: true,
460
+ force: true
461
+ });
462
+ } else console.warn(`⚠️ Export file not found for ${slideName}: ${exportedFile}`);
431
463
  }
432
464
  console.log("✅ All OG images exported successfully!");
433
465
  } catch (error) {
@@ -435,44 +467,45 @@ async function exportOgImages() {
435
467
  process.exit(1);
436
468
  }
437
469
  }
438
- async function copySlidesToOutputDir() {
470
+ async function copySlidesToOutputDir(names) {
439
471
  const workspaceCwd = process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
440
472
  const config = loadConfig(workspaceCwd);
441
473
  const slidesDirs = resolveSlidesDirs(config, workspaceCwd);
442
474
  const deployDir = resolve(workspaceCwd, config.outputDir);
443
475
  console.log(`📁 Copying slide builds into ${deployDir}...`);
444
476
  if (!existsSync(deployDir)) mkdirSync(deployDir, { recursive: true });
445
- for (const slidesDir of slidesDirs) {
446
- if (!existsSync(slidesDir)) continue;
447
- const slides = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
448
- for (const slideName of slides) {
449
- const slideDistDir = join(slidesDir, slideName, "dist");
450
- const targetDir = join(deployDir, slideName);
451
- if (existsSync(slideDistDir)) {
452
- console.log(`📋 Copying ${slideName}...`);
453
- await cp(slideDistDir, targetDir, { recursive: true });
454
- }
477
+ const slides = collectSlides({
478
+ slidesDirs,
479
+ names,
480
+ exclude: config.exclude
481
+ });
482
+ for (const { slideDir, slideName } of slides) {
483
+ const slideDistDir = join(slideDir, "dist");
484
+ const targetDir = join(deployDir, slideName);
485
+ if (existsSync(slideDistDir)) {
486
+ console.log(`📋 Copying ${slideName}...`);
487
+ await cp(slideDistDir, targetDir, { recursive: true });
455
488
  }
456
489
  }
457
490
  console.log(`✅ All slide assets copied into ${deployDir}!`);
458
491
  }
459
- async function runViteBuild() {
492
+ async function runViteBuild(names) {
460
493
  try {
461
- await buildAllSlides();
494
+ await buildSlides(names);
462
495
  console.log("📦 Building Slidev Workspace for production...");
463
496
  const config = createViteConfig();
464
497
  await build(config);
465
- await copySlidesToOutputDir();
498
+ await copySlidesToOutputDir(names);
466
499
  console.log("✅ Build completed successfully!");
467
500
  } catch (error) {
468
501
  console.error("❌ Build failed:", error);
469
502
  process.exit(1);
470
503
  }
471
504
  }
472
- async function runVitePreview() {
505
+ async function runVitePreview(previewPort) {
473
506
  try {
474
507
  console.log("🚀 Starting Slidev Workspace development server...");
475
- const config = createViteConfig();
508
+ const config = createViteConfig(previewPort);
476
509
  const server = await createServer(config);
477
510
  await server.listen();
478
511
  server.printUrls();
@@ -489,14 +522,19 @@ Usage:
489
522
  slidev-workspace <command> [options]
490
523
 
491
524
  Commands:
492
- dev Start the development server
493
- build Build the project for production
494
- export-og Export OG images for all slides
495
- help Show this help message
525
+ dev Start the development server
526
+ build [names] Build the preview app and selected slides (or all if omitted)
527
+ [names]: Optional slide folder names (comma-separated or space-separated)
528
+ export-og Export OG images for all slides
529
+ help Show this help message
530
+ Options:
531
+ --port, -p <number> Set the preview server port (dev/preview only)
496
532
 
497
533
  Examples:
498
534
  slidev-workspace dev # Start development server
535
+ slidev-workspace dev --port 3030 # Start dev server on custom port
499
536
  slidev-workspace build # Build all slides and preview app
537
+ slidev-workspace build slide1,slide2 # Build only specific slides by name
500
538
  slidev-workspace export-og # Export OG images for all slides
501
539
 
502
540
  Configuration:
@@ -505,33 +543,37 @@ Configuration:
505
543
  For more information, visit: https://github.com/author/slidev-workspace
506
544
  `);
507
545
  }
546
+ function setWorkspaceCwd() {
547
+ process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
548
+ }
549
+ function parseNames(names) {
550
+ if (!names || names.length === 0) return void 0;
551
+ const parsed = names.flatMap((name) => name.split(",")).map((name) => name.trim()).filter(Boolean);
552
+ return parsed.length > 0 ? parsed : void 0;
553
+ }
508
554
  async function main() {
509
- switch (command) {
510
- case "dev":
511
- case "preview":
512
- process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
513
- await runVitePreview();
514
- break;
515
- case "build":
516
- process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
517
- await runViteBuild();
518
- break;
519
- case "export-og":
520
- process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
521
- await exportOgImages();
522
- break;
523
- case "help":
524
- case "--help":
525
- case "-h":
526
- showHelp();
527
- break;
528
- default: if (!command) showHelp();
529
- else {
530
- console.error(`Unknown command: ${command}`);
531
- console.error("Run \"slidev-workspace help\" for available commands.");
532
- process.exit(1);
533
- }
555
+ const program = new Command();
556
+ program.name("slidev-workspace").description("A tool for managing multiple Slidev presentations with a workspace preview app").showHelpAfterError();
557
+ program.command("dev").alias("preview").description("Start the development server").option("-p, --port <number>", "Set the preview server port", (value) => parsePortOption(value)).action(async (options) => {
558
+ setWorkspaceCwd();
559
+ await runVitePreview(options.port);
560
+ });
561
+ program.command("build").description("Build the preview app and selected slides (or all if omitted)").argument("[names...]", "Optional slide folder names (comma-separated or space-separated)").action(async (names) => {
562
+ setWorkspaceCwd();
563
+ await runViteBuild(parseNames(names));
564
+ });
565
+ program.command("export-og").description("Export OG images for all slides").action(async () => {
566
+ setWorkspaceCwd();
567
+ await exportOgImages();
568
+ });
569
+ program.command("help").description("Show this help message").action(() => {
570
+ showHelp();
571
+ });
572
+ if (process.argv.length <= 2) {
573
+ program.outputHelp();
574
+ return;
534
575
  }
576
+ await program.parseAsync(process.argv);
535
577
  }
536
578
  main().catch((error) => {
537
579
  console.error("❌ An error occurred:", error);
package/dist/index.js CHANGED
@@ -94,6 +94,7 @@ function resolveImageUrl(slide, domain) {
94
94
  function useSlides() {
95
95
  const slidesData = ref([]);
96
96
  const isLoading = ref(true);
97
+ const devServerBasePort = typeof __SLIDEV_WORKSPACE_DEV_PORT_BASE__ === "number" ? __SLIDEV_WORKSPACE_DEV_PORT_BASE__ : 3001;
97
98
  const loadSlidesData = async () => {
98
99
  try {
99
100
  const module = await import("slidev:content");
@@ -109,7 +110,7 @@ function useSlides() {
109
110
  const slides = computed(() => {
110
111
  if (!slidesData.value || slidesData.value.length === 0) return [];
111
112
  return slidesData.value.map((slide, index) => {
112
- const port = 3001 + index;
113
+ const port = devServerBasePort + index;
113
114
  const devServerUrl = `http://localhost:${port}`;
114
115
  const domain = IS_DEVELOPMENT ? devServerUrl : window.location.origin;
115
116
  const imageUrl = resolveImageUrl(slide, domain);
@@ -1,6 +1,9 @@
1
1
  import { Plugin } from "vite";
2
2
 
3
3
  //#region src/vite/plugin-slides.d.ts
4
- declare function slidesPlugin(): Plugin;
4
+ interface SlidesPluginOptions {
5
+ devServerBasePort?: number;
6
+ }
7
+ declare function slidesPlugin(options?: SlidesPluginOptions): Plugin;
5
8
  //#endregion
6
9
  export { slidesPlugin };
@@ -2,6 +2,8 @@ import { cpSync, existsSync, readFileSync, readdirSync, watch } from "fs";
2
2
  import { basename, join, resolve } from "path";
3
3
  import { parse } from "yaml";
4
4
  import { spawn } from "child_process";
5
+ import { existsSync as existsSync$1, readdirSync as readdirSync$1 } from "node:fs";
6
+ import { join as join$1 } from "node:path";
5
7
  import { createHead, transformHtmlTemplate } from "unhead/server";
6
8
 
7
9
  //#region src/scripts/config.ts
@@ -115,83 +117,114 @@ if (import.meta.url === `file://${process.argv[1]}`) {
115
117
  console.log(JSON.stringify(slides, null, 2));
116
118
  }
117
119
 
120
+ //#endregion
121
+ //#region src/scripts/collectSlides.ts
122
+ function collectSlides({ slidesDirs, names = [], exclude = [] }) {
123
+ const entries = [];
124
+ for (const slidesDir of slidesDirs) {
125
+ if (!existsSync$1(slidesDir)) {
126
+ console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
127
+ continue;
128
+ }
129
+ if (names.length > 0) {
130
+ for (const slideName of names) {
131
+ if (exclude.includes(slideName)) continue;
132
+ const slideDir = join$1(slidesDir, slideName);
133
+ if (!existsSync$1(slideDir)) continue;
134
+ entries.push({
135
+ slidesDir,
136
+ slideName,
137
+ slideDir
138
+ });
139
+ }
140
+ continue;
141
+ }
142
+ const slideNames = readdirSync$1(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).filter((dirent) => !exclude.includes(dirent.name)).map((dirent) => dirent.name);
143
+ for (const slideName of slideNames) {
144
+ const slideDir = join$1(slidesDir, slideName);
145
+ entries.push({
146
+ slidesDir,
147
+ slideName,
148
+ slideDir
149
+ });
150
+ }
151
+ }
152
+ return entries;
153
+ }
154
+
118
155
  //#endregion
119
156
  //#region src/scripts/devServer.ts
120
157
  const runningServers = new Map();
121
- async function startAllSlidesDevServer(workspaceCwd) {
122
- const cwd = workspaceCwd || process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
158
+ async function startAllSlidesDevServer({ basePort = 3001 } = {}) {
159
+ const cwd = process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
123
160
  const config = loadConfig(cwd);
124
161
  const slidesDirs = resolveSlidesDirs(config, cwd);
125
- let currentPort = 3001;
162
+ let currentPort = basePort;
126
163
  const devServers = [];
127
164
  console.log("🚀 Starting Slidev dev servers for all slides...");
128
165
  console.log("📁 Working directory:", cwd);
129
166
  console.log("📂 Slides directories found:", slidesDirs);
130
- for (const slidesDir of slidesDirs) {
131
- if (!existsSync(slidesDir)) {
132
- console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
167
+ const slides = collectSlides({
168
+ slidesDirs,
169
+ exclude: config.exclude
170
+ });
171
+ for (const { slideDir, slideName } of slides) {
172
+ const packageJsonPath = join(slideDir, "package.json");
173
+ const slideKey = slideDir;
174
+ if (runningServers.has(slideKey)) {
175
+ console.log(`⏭️ ${slideName} dev server already running, skipping...`);
176
+ devServers.push(runningServers.get(slideKey));
133
177
  continue;
134
178
  }
135
- const slides = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
136
- for (const slideName of slides) {
137
- const slideDir = join(slidesDir, slideName);
138
- const packageJsonPath = join(slideDir, "package.json");
139
- const slideKey = slideDir;
140
- if (runningServers.has(slideKey)) {
141
- console.log(`⏭️ ${slideName} dev server already running, skipping...`);
142
- devServers.push(runningServers.get(slideKey));
143
- continue;
144
- }
145
- if (!existsSync(packageJsonPath)) {
146
- console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
147
- continue;
148
- }
149
- const nodeModulesPath = join(slideDir, "node_modules");
150
- if (!existsSync(nodeModulesPath)) {
151
- console.warn(`⚠️ Skipping ${slideName}: dependencies not installed (run pnpm install)`);
152
- continue;
153
- }
154
- console.log(`📦 Starting Slidev dev server for ${slideName} on port ${currentPort}...`);
155
- try {
156
- const devProcess = spawn("pnpm", [
157
- "run",
158
- "dev",
159
- "--port",
160
- currentPort.toString(),
161
- "--open",
162
- "false"
163
- ], {
164
- cwd: slideDir,
165
- stdio: [
166
- "pipe",
167
- "pipe",
168
- "pipe"
169
- ],
170
- detached: false,
171
- env: {
172
- ...process.env,
173
- PATH: process.env.PATH
174
- },
175
- shell: true
176
- });
177
- devProcess.stdout?.on("data", (data) => {
178
- const output = data.toString();
179
- if (output.includes("Local:") || output.includes("ready")) console.log(`✅ ${slideName} dev server ready on port ${currentPort}`);
180
- });
181
- devProcess.stderr?.on("data", (data) => {
182
- console.error(`❌ ${slideName} dev server error:`, data.toString());
183
- });
184
- const serverInfo = {
185
- name: slideName,
186
- port: currentPort,
187
- process: devProcess
188
- };
189
- devServers.push(serverInfo);
190
- runningServers.set(slideKey, serverInfo);
191
- currentPort++;
192
- } catch (error) {
193
- console.error(`❌ Failed to start dev server for ${slideName}:`, error);
194
- }
179
+ if (!existsSync(packageJsonPath)) {
180
+ console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
181
+ continue;
182
+ }
183
+ const nodeModulesPath = join(slideDir, "node_modules");
184
+ if (!existsSync(nodeModulesPath)) {
185
+ console.warn(`⚠️ Skipping ${slideName}: dependencies not installed (run pnpm install)`);
186
+ continue;
187
+ }
188
+ console.log(`📦 Starting Slidev dev server for ${slideName} on port ${currentPort}...`);
189
+ try {
190
+ const devProcess = spawn("pnpm", [
191
+ "run",
192
+ "dev",
193
+ "--port",
194
+ currentPort.toString(),
195
+ "--open",
196
+ "false"
197
+ ], {
198
+ cwd: slideDir,
199
+ stdio: [
200
+ "pipe",
201
+ "pipe",
202
+ "pipe"
203
+ ],
204
+ detached: false,
205
+ env: {
206
+ ...process.env,
207
+ PATH: process.env.PATH
208
+ },
209
+ shell: true
210
+ });
211
+ devProcess.stdout?.on("data", (data) => {
212
+ const output = data.toString();
213
+ if (output.includes("Local:") || output.includes("ready")) console.log(`✅ ${slideName} dev server ready on port ${currentPort}`);
214
+ });
215
+ devProcess.stderr?.on("data", (data) => {
216
+ console.error(`❌ ${slideName} dev server error:`, data.toString());
217
+ });
218
+ const serverInfo = {
219
+ name: slideName,
220
+ port: currentPort,
221
+ process: devProcess
222
+ };
223
+ devServers.push(serverInfo);
224
+ runningServers.set(slideKey, serverInfo);
225
+ currentPort++;
226
+ } catch (error) {
227
+ console.error(`❌ Failed to start dev server for ${slideName}:`, error);
195
228
  }
196
229
  }
197
230
  return devServers;
@@ -233,8 +266,9 @@ async function transformIndexHtml(html) {
233
266
 
234
267
  //#endregion
235
268
  //#region src/vite/plugin-slides.ts
236
- function slidesPlugin() {
269
+ function slidesPlugin(options = {}) {
237
270
  let devServers = [];
271
+ const devServerBasePort = options.devServerBasePort ?? 3001;
238
272
  return {
239
273
  name: "vite-plugin-slides",
240
274
  async transformIndexHtml(html) {
@@ -244,23 +278,23 @@ function slidesPlugin() {
244
278
  try {
245
279
  const config = loadConfig();
246
280
  const slidesDirs = resolveSlidesDirs(config);
247
- for (const slidesDir of slidesDirs) {
248
- if (!existsSync(slidesDir)) continue;
249
- const slideDirs = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).filter((dirent) => !(config.exclude || []).includes(dirent.name)).map((dirent) => dirent.name);
250
- for (const slideDir of slideDirs) {
251
- const slideDistPath = join(slidesDir, slideDir, "dist");
252
- const assetsPath = join(slideDistPath, "assets");
253
- if (!existsSync(assetsPath)) continue;
254
- const assetFiles = readdirSync(assetsPath);
255
- const ogImageFile = assetFiles.find((file) => /^og-image-[a-zA-Z0-9]+\.png$/.test(file));
256
- if (ogImageFile) {
257
- const sourceFile = join(assetsPath, ogImageFile);
258
- const destFile = join(slideDistPath, "og-image.png");
259
- try {
260
- cpSync(sourceFile, destFile, { force: true });
261
- } catch (error) {
262
- console.warn(`⚠ Failed to copy og-image for ${slideDir}:`, error);
263
- }
281
+ const slides = collectSlides({
282
+ slidesDirs,
283
+ exclude: config.exclude
284
+ });
285
+ for (const { slideDir } of slides) {
286
+ const slideDistPath = join(slideDir, "dist");
287
+ const assetsPath = join(slideDistPath, "assets");
288
+ if (!existsSync(assetsPath)) continue;
289
+ const assetFiles = readdirSync(assetsPath);
290
+ const ogImageFile = assetFiles.find((file) => /^og-image-[a-zA-Z0-9]+\.png$/.test(file));
291
+ if (ogImageFile) {
292
+ const sourceFile = join(assetsPath, ogImageFile);
293
+ const destFile = join(slideDistPath, "og-image.png");
294
+ try {
295
+ cpSync(sourceFile, destFile, { force: true });
296
+ } catch (error) {
297
+ console.warn(`⚠ Failed to copy og-image for ${slideDir}:`, error);
264
298
  }
265
299
  }
266
300
  }
@@ -273,7 +307,7 @@ function slidesPlugin() {
273
307
  const config = loadConfig();
274
308
  const slidesDirs = resolveSlidesDirs(config);
275
309
  try {
276
- devServers = await startAllSlidesDevServer();
310
+ devServers = await startAllSlidesDevServer({ basePort: devServerBasePort });
277
311
  } catch (error) {
278
312
  console.error("❌ Failed to start slides dev servers:", error);
279
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidev-workspace",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "A workspace tool for managing multiple Slidev presentations with API-based content management",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,6 +40,7 @@
40
40
  "@vueuse/core": "^13.5.0",
41
41
  "class-variance-authority": "^0.7.1",
42
42
  "clsx": "^2.1.1",
43
+ "commander": "^14.0.0",
43
44
  "lucide-vue-next": "^0.525.0",
44
45
  "tailwind-merge": "^3.3.1",
45
46
  "tw-animate-css": "^1.3.5",
@@ -89,6 +89,10 @@ export function resolveImageUrl(slide: SlideInfo, domain: string): string {
89
89
  export function useSlides() {
90
90
  const slidesData = ref<SlideInfo[]>([]);
91
91
  const isLoading = ref(true);
92
+ const devServerBasePort =
93
+ typeof __SLIDEV_WORKSPACE_DEV_PORT_BASE__ === "number"
94
+ ? __SLIDEV_WORKSPACE_DEV_PORT_BASE__
95
+ : 3001;
92
96
 
93
97
  // Dynamically import slidev:content to avoid build-time issues
94
98
  const loadSlidesData = async () => {
@@ -110,8 +114,8 @@ export function useSlides() {
110
114
  if (!slidesData.value || slidesData.value.length === 0) return [];
111
115
 
112
116
  return slidesData.value.map((slide, index) => {
113
- // Generate port based on slide index: 3001, 3002, 3003...
114
- const port = 3001 + index;
117
+ // Generate port based on slide index: base, base + 1, base + 2...
118
+ const port = devServerBasePort + index;
115
119
  // Create dev server URL
116
120
  const devServerUrl = `http://localhost:${port}`;
117
121
  const domain = IS_DEVELOPMENT ? devServerUrl : window.location.origin;