slidev-workspace 0.7.2 → 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,25 +370,30 @@ 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
- const dirsArg = args[1];
343
373
  const packageRoot = join(__dirname, "..");
344
- 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) {
345
381
  const workspaceCwd = process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
346
382
  const config = loadConfig(workspaceCwd);
383
+ const devServerBasePort = previewPort + 1;
347
384
  return {
348
385
  root: resolve(packageRoot, "src/preview"),
349
386
  base: config.baseUrl,
350
387
  plugins: [
351
388
  vue(),
352
389
  tailwindcss(),
353
- slidesPlugin()
390
+ slidesPlugin({ devServerBasePort })
354
391
  ],
355
392
  resolve: { alias: { "@": resolve(packageRoot, "src/preview") } },
393
+ define: { __SLIDEV_WORKSPACE_DEV_PORT_BASE__: devServerBasePort },
356
394
  build: { outDir: resolve(workspaceCwd, config.outputDir) },
357
395
  server: {
358
- port: 3e3,
396
+ port: previewPort,
359
397
  open: true
360
398
  }
361
399
  };
@@ -365,34 +403,31 @@ async function buildSlides(names) {
365
403
  const config = loadConfig(workspaceCwd);
366
404
  const slidesDirs = resolveSlidesDirs(config, workspaceCwd);
367
405
  console.log(names ? `🔨 Building slides: ${names.join(", ")}...` : "🔨 Building all slides...");
368
- for (const slidesDir of slidesDirs) {
369
- if (!existsSync(slidesDir)) {
370
- console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
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`);
371
415
  continue;
372
416
  }
373
- const slides = names ? names.filter((name) => existsSync(join(slidesDir, name))) : readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
374
- for (const slideName of slides) {
375
- const slideDir = join(slidesDir, slideName);
376
- const packageJsonPath = join(slideDir, "package.json");
377
- if (!existsSync(packageJsonPath)) {
378
- console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
379
- continue;
380
- }
381
- console.log(`📦 Building slide: ${slideName}`);
382
- try {
383
- const baseUrl = config.baseUrl.endsWith("/") ? config.baseUrl : config.baseUrl + "/";
384
- const subDir = slideDir.startsWith(workspaceCwd) ? slideDir.replace(workspaceCwd, "").replace(/^\//, "") : slideDir;
385
- const buildCmd = `pnpm --filter "./${subDir}" run build --base ${baseUrl}${slideName}/`;
386
- console.log(buildCmd);
387
- execSync(buildCmd, {
388
- cwd: workspaceCwd,
389
- stdio: "inherit"
390
- });
391
- console.log(`✅ Built slide: ${slideName}`);
392
- } catch (error) {
393
- console.error(`❌ Failed to build slide ${slideName}:`, error);
394
- process.exit(1);
395
- }
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);
396
431
  }
397
432
  }
398
433
  }
@@ -407,28 +442,24 @@ async function exportOgImages() {
407
442
  stdio: "inherit"
408
443
  });
409
444
  console.log("📦 Copying exported images to og-image.png...");
410
- for (const slidesDir of slidesDirs) {
411
- if (!existsSync(slidesDir)) {
412
- console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
413
- continue;
414
- }
415
- const slides = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
416
- for (const slideName of slides) {
417
- const slideDir = join(slidesDir, slideName);
418
- const packageJsonPath = join(slideDir, "package.json");
419
- if (!existsSync(packageJsonPath)) continue;
420
- const exportedFile = join(slideDir, "slides-export", "1.png");
421
- const targetFile = join(slideDir, "og-image.png");
422
- const exportDir = join(slideDir, "slides-export");
423
- if (existsSync(exportedFile)) {
424
- await cp(exportedFile, targetFile);
425
- console.log(`✅ Generated OG image for: ${slideName}`);
426
- await rm(exportDir, {
427
- recursive: true,
428
- force: true
429
- });
430
- } else console.warn(`⚠️ Export file not found for ${slideName}: ${exportedFile}`);
431
- }
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}`);
432
463
  }
433
464
  console.log("✅ All OG images exported successfully!");
434
465
  } catch (error) {
@@ -443,16 +474,17 @@ async function copySlidesToOutputDir(names) {
443
474
  const deployDir = resolve(workspaceCwd, config.outputDir);
444
475
  console.log(`📁 Copying slide builds into ${deployDir}...`);
445
476
  if (!existsSync(deployDir)) mkdirSync(deployDir, { recursive: true });
446
- for (const slidesDir of slidesDirs) {
447
- if (!existsSync(slidesDir)) continue;
448
- const slides = names ? names.filter((name) => existsSync(join(slidesDir, name))) : readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
449
- for (const slideName of slides) {
450
- const slideDistDir = join(slidesDir, slideName, "dist");
451
- const targetDir = join(deployDir, slideName);
452
- if (existsSync(slideDistDir)) {
453
- console.log(`📋 Copying ${slideName}...`);
454
- await cp(slideDistDir, targetDir, { recursive: true });
455
- }
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 });
456
488
  }
457
489
  }
458
490
  console.log(`✅ All slide assets copied into ${deployDir}!`);
@@ -470,10 +502,10 @@ async function runViteBuild(names) {
470
502
  process.exit(1);
471
503
  }
472
504
  }
473
- async function runVitePreview() {
505
+ async function runVitePreview(previewPort) {
474
506
  try {
475
507
  console.log("🚀 Starting Slidev Workspace development server...");
476
- const config = createViteConfig();
508
+ const config = createViteConfig(previewPort);
477
509
  const server = await createServer(config);
478
510
  await server.listen();
479
511
  server.printUrls();
@@ -491,13 +523,16 @@ Usage:
491
523
 
492
524
  Commands:
493
525
  dev Start the development server
494
- build [names] Build the project for production
495
- [names]: Optional comma-separated list of slide folder names to build
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)
496
528
  export-og Export OG images for all slides
497
529
  help Show this help message
530
+ Options:
531
+ --port, -p <number> Set the preview server port (dev/preview only)
498
532
 
499
533
  Examples:
500
534
  slidev-workspace dev # Start development server
535
+ slidev-workspace dev --port 3030 # Start dev server on custom port
501
536
  slidev-workspace build # Build all slides and preview app
502
537
  slidev-workspace build slide1,slide2 # Build only specific slides by name
503
538
  slidev-workspace export-og # Export OG images for all slides
@@ -508,35 +543,37 @@ Configuration:
508
543
  For more information, visit: https://github.com/author/slidev-workspace
509
544
  `);
510
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
+ }
511
554
  async function main() {
512
- switch (command) {
513
- case "dev":
514
- case "preview":
515
- process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
516
- await runVitePreview();
517
- break;
518
- case "build": {
519
- process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
520
- const names = dirsArg ? dirsArg.split(",").map((d) => d.trim()) : void 0;
521
- await runViteBuild(names);
522
- break;
523
- }
524
- case "export-og":
525
- process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
526
- await exportOgImages();
527
- break;
528
- case "help":
529
- case "--help":
530
- case "-h":
531
- showHelp();
532
- break;
533
- default: if (!command) showHelp();
534
- else {
535
- console.error(`Unknown command: ${command}`);
536
- console.error("Run \"slidev-workspace help\" for available commands.");
537
- process.exit(1);
538
- }
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;
539
575
  }
576
+ await program.parseAsync(process.argv);
540
577
  }
541
578
  main().catch((error) => {
542
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.2",
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;