slidev-workspace 0.1.10 → 0.2.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
@@ -10,6 +10,7 @@ import tailwindcss from "@tailwindcss/vite";
10
10
  import { existsSync as existsSync$1, readFileSync, readdirSync as readdirSync$1, watch } from "fs";
11
11
  import { basename, join as join$1, resolve as resolve$1 } from "path";
12
12
  import { parse } from "yaml";
13
+ import { spawn } from "child_process";
13
14
 
14
15
  //#region src/scripts/config.ts
15
16
  const DEFAULT_CONFIG = {
@@ -113,15 +114,109 @@ if (import.meta.url === `file://${process.argv[1]}`) {
113
114
  console.log(JSON.stringify(slides, null, 2));
114
115
  }
115
116
 
117
+ //#endregion
118
+ //#region src/scripts/devServer.ts
119
+ const runningServers = new Map();
120
+ async function startAllSlidesDevServer(workspaceCwd) {
121
+ const cwd = workspaceCwd || process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
122
+ const config = loadConfig(cwd);
123
+ const slidesDirs = resolveSlidesDirs(config, cwd);
124
+ let currentPort = 3001;
125
+ const devServers = [];
126
+ console.log("🚀 Starting Slidev dev servers for all slides...");
127
+ console.log("📁 Working directory:", cwd);
128
+ console.log("📂 Slides directories found:", slidesDirs);
129
+ for (const slidesDir of slidesDirs) {
130
+ if (!existsSync$1(slidesDir)) {
131
+ console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
132
+ continue;
133
+ }
134
+ const slides = readdirSync$1(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
135
+ for (const slideName of slides) {
136
+ const slideDir = join$1(slidesDir, slideName);
137
+ const packageJsonPath = join$1(slideDir, "package.json");
138
+ const slideKey = slideDir;
139
+ if (runningServers.has(slideKey)) {
140
+ console.log(`⏭️ ${slideName} dev server already running, skipping...`);
141
+ devServers.push(runningServers.get(slideKey));
142
+ continue;
143
+ }
144
+ if (!existsSync$1(packageJsonPath)) {
145
+ console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
146
+ continue;
147
+ }
148
+ const nodeModulesPath = join$1(slideDir, "node_modules");
149
+ if (!existsSync$1(nodeModulesPath)) {
150
+ console.warn(`⚠️ Skipping ${slideName}: dependencies not installed (run pnpm install)`);
151
+ continue;
152
+ }
153
+ console.log(`📦 Starting Slidev dev server for ${slideName} on port ${currentPort}...`);
154
+ try {
155
+ const devProcess = spawn("pnpm", [
156
+ "run",
157
+ "dev",
158
+ "--port",
159
+ currentPort.toString()
160
+ ], {
161
+ cwd: slideDir,
162
+ stdio: [
163
+ "ignore",
164
+ "pipe",
165
+ "pipe"
166
+ ],
167
+ detached: false,
168
+ env: {
169
+ ...process.env,
170
+ PATH: process.env.PATH
171
+ },
172
+ shell: true
173
+ });
174
+ devProcess.stdout?.on("data", (data) => {
175
+ const output = data.toString();
176
+ if (output.includes("Local:") || output.includes("ready")) console.log(`✅ ${slideName} dev server ready on port ${currentPort}`);
177
+ });
178
+ devProcess.stderr?.on("data", (data) => {
179
+ console.error(`❌ ${slideName} dev server error:`, data.toString());
180
+ });
181
+ const serverInfo = {
182
+ name: slideName,
183
+ port: currentPort,
184
+ process: devProcess
185
+ };
186
+ devServers.push(serverInfo);
187
+ runningServers.set(slideKey, serverInfo);
188
+ currentPort++;
189
+ } catch (error) {
190
+ console.error(`❌ Failed to start dev server for ${slideName}:`, error);
191
+ }
192
+ }
193
+ }
194
+ return devServers;
195
+ }
196
+ function stopAllDevServers(devServers) {
197
+ console.log("🛑 Shutting down all dev servers...");
198
+ devServers.forEach(({ name, process: process$1 }) => {
199
+ console.log(` Stopping ${name}...`);
200
+ process$1.kill();
201
+ });
202
+ runningServers.clear();
203
+ }
204
+
116
205
  //#endregion
117
206
  //#region src/vite/plugin-slides.ts
118
207
  function slidesPlugin() {
208
+ let devServers = [];
119
209
  return {
120
210
  name: "vite-plugin-slides",
121
- configureServer(server) {
211
+ async configureServer(server) {
122
212
  const watchers = [];
123
213
  const config = loadConfig();
124
214
  const slidesDirs = resolveSlidesDirs(config);
215
+ try {
216
+ devServers = await startAllSlidesDevServer();
217
+ } catch (error) {
218
+ console.error("❌ Failed to start slides dev servers:", error);
219
+ }
125
220
  slidesDirs.forEach((slidesDir) => {
126
221
  const watcher = watch(slidesDir, { recursive: true }, (eventType, filename) => {
127
222
  if (filename && filename.endsWith("slides.md")) try {
@@ -139,6 +234,7 @@ function slidesPlugin() {
139
234
  });
140
235
  server.httpServer?.once("close", () => {
141
236
  watchers.forEach((watcher) => watcher.close());
237
+ if (devServers.length > 0) stopAllDevServers(devServers);
142
238
  });
143
239
  },
144
240
  resolveId(id) {
@@ -293,6 +389,7 @@ For more information, visit: https://github.com/author/slidev-workspace
293
389
  }
294
390
  async function main() {
295
391
  switch (command) {
392
+ case "dev":
296
393
  case "preview":
297
394
  process.env.SLIDEV_WORKSPACE_CWD = process.cwd();
298
395
  await runVitePreview();
package/dist/index.js CHANGED
@@ -15,17 +15,21 @@ function useSlides() {
15
15
  loadSlidesData();
16
16
  const slides = computed(() => {
17
17
  if (!slidesData.value || slidesData.value.length === 0) return [];
18
- return slidesData.value.map((slide) => ({
19
- title: slide.frontmatter.title || slide.path,
20
- url: slide.path,
21
- description: slide.frontmatter.info || slide.frontmatter.seoMeta?.ogDescription || "No description available",
22
- image: slide.frontmatter.background || slide.frontmatter.seoMeta?.ogImage || "https://cover.sli.dev",
23
- author: slide.frontmatter.author || "Unknown Author",
24
- date: slide.frontmatter.date || new Date().toISOString().split("T")[0],
25
- theme: slide.frontmatter.theme,
26
- transition: slide.frontmatter.transition,
27
- class: slide.frontmatter.class
28
- }));
18
+ return slidesData.value.map((slide, index) => {
19
+ const port = 3001 + index;
20
+ const devServerUrl = `http://localhost:${port}`;
21
+ return {
22
+ title: slide.frontmatter.title || slide.path,
23
+ url: process.env.NODE_ENV === "development" ? devServerUrl : slide.path,
24
+ description: slide.frontmatter.info || slide.frontmatter.seoMeta?.ogDescription || "No description available",
25
+ image: slide.frontmatter.background || slide.frontmatter.seoMeta?.ogImage || "https://cover.sli.dev",
26
+ author: slide.frontmatter.author || "Unknown Author",
27
+ date: slide.frontmatter.date || new Date().toISOString().split("T")[0],
28
+ theme: slide.frontmatter.theme,
29
+ transition: slide.frontmatter.transition,
30
+ class: slide.frontmatter.class
31
+ };
32
+ });
29
33
  });
30
34
  const slidesCount = computed(() => slides.value.length);
31
35
  return {
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, readdirSync, watch } from "fs";
2
2
  import { basename, join, resolve } from "path";
3
3
  import { parse } from "yaml";
4
+ import { spawn } from "child_process";
4
5
 
5
6
  //#region src/scripts/config.ts
6
7
  const DEFAULT_CONFIG = {
@@ -104,15 +105,109 @@ if (import.meta.url === `file://${process.argv[1]}`) {
104
105
  console.log(JSON.stringify(slides, null, 2));
105
106
  }
106
107
 
108
+ //#endregion
109
+ //#region src/scripts/devServer.ts
110
+ const runningServers = new Map();
111
+ async function startAllSlidesDevServer(workspaceCwd) {
112
+ const cwd = workspaceCwd || process.env.SLIDEV_WORKSPACE_CWD || process.cwd();
113
+ const config = loadConfig(cwd);
114
+ const slidesDirs = resolveSlidesDirs(config, cwd);
115
+ let currentPort = 3001;
116
+ const devServers = [];
117
+ console.log("🚀 Starting Slidev dev servers for all slides...");
118
+ console.log("📁 Working directory:", cwd);
119
+ console.log("📂 Slides directories found:", slidesDirs);
120
+ for (const slidesDir of slidesDirs) {
121
+ if (!existsSync(slidesDir)) {
122
+ console.warn(`⚠️ Slides directory not found: ${slidesDir}`);
123
+ continue;
124
+ }
125
+ const slides = readdirSync(slidesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
126
+ for (const slideName of slides) {
127
+ const slideDir = join(slidesDir, slideName);
128
+ const packageJsonPath = join(slideDir, "package.json");
129
+ const slideKey = slideDir;
130
+ if (runningServers.has(slideKey)) {
131
+ console.log(`⏭️ ${slideName} dev server already running, skipping...`);
132
+ devServers.push(runningServers.get(slideKey));
133
+ continue;
134
+ }
135
+ if (!existsSync(packageJsonPath)) {
136
+ console.warn(`⚠️ Skipping ${slideName}: no package.json found`);
137
+ continue;
138
+ }
139
+ const nodeModulesPath = join(slideDir, "node_modules");
140
+ if (!existsSync(nodeModulesPath)) {
141
+ console.warn(`⚠️ Skipping ${slideName}: dependencies not installed (run pnpm install)`);
142
+ continue;
143
+ }
144
+ console.log(`📦 Starting Slidev dev server for ${slideName} on port ${currentPort}...`);
145
+ try {
146
+ const devProcess = spawn("pnpm", [
147
+ "run",
148
+ "dev",
149
+ "--port",
150
+ currentPort.toString()
151
+ ], {
152
+ cwd: slideDir,
153
+ stdio: [
154
+ "ignore",
155
+ "pipe",
156
+ "pipe"
157
+ ],
158
+ detached: false,
159
+ env: {
160
+ ...process.env,
161
+ PATH: process.env.PATH
162
+ },
163
+ shell: true
164
+ });
165
+ devProcess.stdout?.on("data", (data) => {
166
+ const output = data.toString();
167
+ if (output.includes("Local:") || output.includes("ready")) console.log(`✅ ${slideName} dev server ready on port ${currentPort}`);
168
+ });
169
+ devProcess.stderr?.on("data", (data) => {
170
+ console.error(`❌ ${slideName} dev server error:`, data.toString());
171
+ });
172
+ const serverInfo = {
173
+ name: slideName,
174
+ port: currentPort,
175
+ process: devProcess
176
+ };
177
+ devServers.push(serverInfo);
178
+ runningServers.set(slideKey, serverInfo);
179
+ currentPort++;
180
+ } catch (error) {
181
+ console.error(`❌ Failed to start dev server for ${slideName}:`, error);
182
+ }
183
+ }
184
+ }
185
+ return devServers;
186
+ }
187
+ function stopAllDevServers(devServers) {
188
+ console.log("🛑 Shutting down all dev servers...");
189
+ devServers.forEach(({ name, process: process$1 }) => {
190
+ console.log(` Stopping ${name}...`);
191
+ process$1.kill();
192
+ });
193
+ runningServers.clear();
194
+ }
195
+
107
196
  //#endregion
108
197
  //#region src/vite/plugin-slides.ts
109
198
  function slidesPlugin() {
199
+ let devServers = [];
110
200
  return {
111
201
  name: "vite-plugin-slides",
112
- configureServer(server) {
202
+ async configureServer(server) {
113
203
  const watchers = [];
114
204
  const config = loadConfig();
115
205
  const slidesDirs = resolveSlidesDirs(config);
206
+ try {
207
+ devServers = await startAllSlidesDevServer();
208
+ } catch (error) {
209
+ console.error("❌ Failed to start slides dev servers:", error);
210
+ }
116
211
  slidesDirs.forEach((slidesDir) => {
117
212
  const watcher = watch(slidesDir, { recursive: true }, (eventType, filename) => {
118
213
  if (filename && filename.endsWith("slides.md")) try {
@@ -130,6 +225,7 @@ function slidesPlugin() {
130
225
  });
131
226
  server.httpServer?.once("close", () => {
132
227
  watchers.forEach((watcher) => watcher.close());
228
+ if (devServers.length > 0) stopAllDevServers(devServers);
133
229
  });
134
230
  },
135
231
  resolveId(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidev-workspace",
3
- "version": "0.1.10",
3
+ "version": "0.2.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",
@@ -1,58 +1,60 @@
1
1
  <template>
2
- <Card
3
- class="group hover:shadow-lg transition-all duration-200 cursor-pointer"
4
- @click="$emit('click')"
5
- >
6
- <div class="relative overflow-hidden rounded-t-lg">
7
- <img
8
- :src="image || '/placeholder.svg'"
9
- :alt="title"
10
- class="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-200"
11
- />
12
- </div>
13
-
14
- <CardHeader class="pb-2">
15
- <CardTitle
16
- class="text-lg line-clamp-2 group-hover:text-primary transition-colors"
17
- >
18
- {{ title }}
19
- </CardTitle>
20
- </CardHeader>
21
-
22
- <CardContent class="space-y-3">
23
- <CardDescription class="line-clamp-3 text-sm h-[40px]">{{
24
- description
25
- }}</CardDescription>
2
+ <a :href="url" target="_blank">
3
+ <Card
4
+ class="group hover:shadow-lg transition-all duration-200 cursor-pointer"
5
+ @click="$emit('click')"
6
+ >
7
+ <div class="relative overflow-hidden rounded-t-lg">
8
+ <img
9
+ :src="image || '/placeholder.svg'"
10
+ :alt="title"
11
+ class="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-200"
12
+ />
13
+ </div>
26
14
 
27
- <div class="flex flex-wrap gap-1 mb-2">
28
- <span
29
- v-if="theme"
30
- class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800"
15
+ <CardHeader class="pb-2">
16
+ <CardTitle
17
+ class="text-lg line-clamp-2 group-hover:text-primary transition-colors"
31
18
  >
32
- {{ theme }}
33
- </span>
34
- <span
35
- v-if="sourceDir"
36
- class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-green-100 text-green-800"
37
- >
38
- {{ sourceDir.split("/").pop() }}
39
- </span>
40
- </div>
19
+ {{ title }}
20
+ </CardTitle>
21
+ </CardHeader>
41
22
 
42
- <div
43
- class="flex items-center justify-between text-xs text-muted-foreground pt-2 border-t"
44
- >
45
- <div class="flex items-center gap-1">
46
- <User class="h-3 w-3" />
47
- <span>{{ author }}</span>
23
+ <CardContent class="space-y-3">
24
+ <CardDescription class="line-clamp-3 text-sm h-[40px]">{{
25
+ description
26
+ }}</CardDescription>
27
+
28
+ <div class="flex flex-wrap gap-1 mb-2">
29
+ <span
30
+ v-if="theme"
31
+ class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800"
32
+ >
33
+ {{ theme }}
34
+ </span>
35
+ <span
36
+ v-if="sourceDir"
37
+ class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-green-100 text-green-800"
38
+ >
39
+ {{ sourceDir.split("/").pop() }}
40
+ </span>
48
41
  </div>
49
- <div class="flex items-center gap-1">
50
- <Calendar class="h-3 w-3" />
51
- <span>{{ date }}</span>
42
+
43
+ <div
44
+ class="flex items-center justify-between text-xs text-muted-foreground pt-2 border-t"
45
+ >
46
+ <div class="flex items-center gap-1">
47
+ <User class="h-3 w-3" />
48
+ <span>{{ author }}</span>
49
+ </div>
50
+ <div class="flex items-center gap-1">
51
+ <Calendar class="h-3 w-3" />
52
+ <span>{{ date }}</span>
53
+ </div>
52
54
  </div>
53
- </div>
54
- </CardContent>
55
- </Card>
55
+ </CardContent>
56
+ </Card>
57
+ </a>
56
58
  </template>
57
59
 
58
60
  <script setup lang="ts">
@@ -43,7 +43,6 @@
43
43
  :url="slide.url"
44
44
  :author="slide.author"
45
45
  :date="slide.date"
46
- @click="() => openSlide(slide)"
47
46
  />
48
47
  </div>
49
48
  </div>
@@ -55,7 +54,6 @@ import { ref, computed } from "vue";
55
54
  import { useSlides } from "../composables/useSlides";
56
55
  import { Input } from "../components/ui/input";
57
56
  import SlideCard from "./SlideCard.vue";
58
- import type { SlideData } from "../../types/slide";
59
57
 
60
58
  const searchTerm = ref("");
61
59
  const { slides, slidesCount } = useSlides();
@@ -71,9 +69,4 @@ const filteredSlides = computed(() => {
71
69
  slide.author.toLowerCase().includes(searchTerm.value.toLowerCase()),
72
70
  );
73
71
  });
74
-
75
- const openSlide = (slide: SlideData) => {
76
- const url = `${window.location.href}/${slide.url}`;
77
- window.open(url, "_blank");
78
- };
79
72
  </script>
@@ -21,23 +21,30 @@ export function useSlides() {
21
21
  const slides = computed<SlideData[]>(() => {
22
22
  if (!slidesData.value || slidesData.value.length === 0) return [];
23
23
 
24
- return slidesData.value.map((slide) => ({
25
- title: slide.frontmatter.title || slide.path,
26
- url: slide.path,
27
- description:
28
- slide.frontmatter.info ||
29
- slide.frontmatter.seoMeta?.ogDescription ||
30
- "No description available",
31
- image:
32
- slide.frontmatter.background ||
33
- slide.frontmatter.seoMeta?.ogImage ||
34
- "https://cover.sli.dev",
35
- author: slide.frontmatter.author || "Unknown Author",
36
- date: slide.frontmatter.date || new Date().toISOString().split("T")[0],
37
- theme: slide.frontmatter.theme,
38
- transition: slide.frontmatter.transition,
39
- class: slide.frontmatter.class,
40
- }));
24
+ return slidesData.value.map((slide, index) => {
25
+ // Generate port based on slide index: 3001, 3002, 3003...
26
+ const port = 3001 + index;
27
+ // Create dev server URL
28
+ const devServerUrl = `http://localhost:${port}`;
29
+
30
+ return {
31
+ title: slide.frontmatter.title || slide.path,
32
+ url: process.env.NODE_ENV === "development" ? devServerUrl : slide.path,
33
+ description:
34
+ slide.frontmatter.info ||
35
+ slide.frontmatter.seoMeta?.ogDescription ||
36
+ "No description available",
37
+ image:
38
+ slide.frontmatter.background ||
39
+ slide.frontmatter.seoMeta?.ogImage ||
40
+ "https://cover.sli.dev",
41
+ author: slide.frontmatter.author || "Unknown Author",
42
+ date: slide.frontmatter.date || new Date().toISOString().split("T")[0],
43
+ theme: slide.frontmatter.theme,
44
+ transition: slide.frontmatter.transition,
45
+ class: slide.frontmatter.class,
46
+ };
47
+ });
41
48
  });
42
49
 
43
50
  const slidesCount = computed(() => slides.value.length);