hono-decks 0.2.0 → 0.2.2

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/vite.js CHANGED
@@ -1,970 +1,982 @@
1
- // src/vite.ts
2
- import { relative as relative3, resolve as resolve2 } from "path";
3
-
4
- // src/node/compile-decks.ts
5
- import { mkdir, readFile as readFile3, writeFile } from "fs/promises";
6
- import { join as join3 } from "path";
7
-
8
- // src/routing/file-routing.ts
1
+ import { join, relative, resolve } from "node:path";
2
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
3
+ import { unified } from "unified";
4
+ import remarkGfm from "remark-gfm";
5
+ import remarkMdx from "remark-mdx";
6
+ import remarkParse from "remark-parse";
7
+ import { compile } from "@mdx-js/mdx";
8
+ import remarkDirective from "remark-directive";
9
+ import { codeToHtml } from "shiki";
10
+ import { existsSync } from "node:fs";
11
+ import { fileURLToPath } from "node:url";
12
+ import { build } from "esbuild";
13
+ import { lookup } from "node:dns/promises";
14
+ import { isIP } from "node:net";
15
+ //#region src/routing/file-routing.ts
9
16
  function resolveDeckFiles(paths, root = "decks") {
10
- const normalizedRoot = normalizePath(root).replace(/\/$/, "");
11
- const decks = /* @__PURE__ */ new Map();
12
- const assetsBySlug = /* @__PURE__ */ new Map();
13
- for (const inputPath of paths.map(normalizePath).sort()) {
14
- assertInsideRoot(inputPath, normalizedRoot);
15
- const relative4 = inputPath.slice(normalizedRoot.length + 1);
16
- const segments = relative4.split("/");
17
- if (segments.length === 1 && segments[0].endsWith(".mdx")) {
18
- const slug = segments[0].replace(/\.mdx$/, "");
19
- addDeck(decks, {
20
- slug,
21
- sourcePath: inputPath,
22
- kind: "single-file",
23
- assetPaths: []
24
- });
25
- continue;
26
- }
27
- if (segments.length === 2 && segments[1] === "deck.mdx") {
28
- addDeck(decks, {
29
- slug: segments[0],
30
- sourcePath: inputPath,
31
- kind: "directory",
32
- assetPaths: assetsBySlug.get(segments[0]) ?? []
33
- });
34
- continue;
35
- }
36
- if (segments.length >= 3 && segments[1] === "assets") {
37
- const slug = segments[0];
38
- const assetPaths = assetsBySlug.get(slug) ?? [];
39
- assetPaths.push(inputPath);
40
- assetsBySlug.set(slug, assetPaths);
41
- const deck = decks.get(slug);
42
- if (deck?.kind === "directory") deck.assetPaths = assetPaths;
43
- continue;
44
- }
45
- if (inputPath.endsWith(".mdx")) {
46
- throw new Error(`Nested deck slugs are not supported in this slice: ${inputPath}`);
47
- }
48
- }
49
- return [...decks.values()].map((deck) => ({
50
- ...deck,
51
- assetPaths: [...deck.assetPaths].sort()
52
- }));
17
+ const normalizedRoot = normalizePath$3(root).replace(/\/$/, "");
18
+ const decks = /* @__PURE__ */ new Map();
19
+ const assetsBySlug = /* @__PURE__ */ new Map();
20
+ for (const inputPath of paths.map(normalizePath$3).sort()) {
21
+ assertInsideRoot(inputPath, normalizedRoot);
22
+ const segments = inputPath.slice(normalizedRoot.length + 1).split("/");
23
+ if (segments.length === 1 && segments[0].endsWith(".mdx")) {
24
+ addDeck(decks, {
25
+ slug: segments[0].replace(/\.mdx$/, ""),
26
+ sourcePath: inputPath,
27
+ kind: "single-file",
28
+ assetPaths: []
29
+ });
30
+ continue;
31
+ }
32
+ if (segments.length === 2 && segments[1] === "deck.mdx") {
33
+ addDeck(decks, {
34
+ slug: segments[0],
35
+ sourcePath: inputPath,
36
+ kind: "directory",
37
+ assetPaths: assetsBySlug.get(segments[0]) ?? []
38
+ });
39
+ continue;
40
+ }
41
+ if (segments.length >= 3 && segments[1] === "assets") {
42
+ const slug = segments[0];
43
+ const assetPaths = assetsBySlug.get(slug) ?? [];
44
+ assetPaths.push(inputPath);
45
+ assetsBySlug.set(slug, assetPaths);
46
+ const deck = decks.get(slug);
47
+ if (deck?.kind === "directory") deck.assetPaths = assetPaths;
48
+ continue;
49
+ }
50
+ if (inputPath.endsWith(".mdx")) throw new Error(`Nested deck slugs are not supported in this slice: ${inputPath}`);
51
+ }
52
+ return [...decks.values()].map((deck) => ({
53
+ ...deck,
54
+ assetPaths: [...deck.assetPaths].sort()
55
+ }));
53
56
  }
54
57
  function addDeck(decks, next) {
55
- const current = decks.get(next.slug);
56
- if (current) {
57
- throw new Error(`Deck slug conflict for "${next.slug}": ${current.sourcePath} and ${next.sourcePath}`);
58
- }
59
- decks.set(next.slug, next);
58
+ const current = decks.get(next.slug);
59
+ if (current) throw new Error(`Deck slug conflict for "${next.slug}": ${current.sourcePath} and ${next.sourcePath}`);
60
+ decks.set(next.slug, next);
60
61
  }
61
62
  function assertInsideRoot(path, root) {
62
- if (path.includes("/../") || path.endsWith("/..") || path.startsWith("../")) {
63
- throw new Error(`Deck path escapes the root: ${path}`);
64
- }
65
- if (path !== root && !path.startsWith(`${root}/`)) {
66
- throw new Error(`Deck path is outside ${root}: ${path}`);
67
- }
68
- }
69
- function normalizePath(path) {
70
- return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
71
- }
72
-
73
- // src/deck/model.ts
74
- var SLIDE_TRANSITIONS = [
75
- "none",
76
- "fade",
77
- "fade-out",
78
- "slide-left",
79
- "slide-right",
80
- "slide-up",
81
- "slide-down",
82
- "view-transition"
63
+ if (path.includes("/../") || path.endsWith("/..") || path.startsWith("../")) throw new Error(`Deck path escapes the root: ${path}`);
64
+ if (path !== root && !path.startsWith(`${root}/`)) throw new Error(`Deck path is outside ${root}: ${path}`);
65
+ }
66
+ function normalizePath$3(path) {
67
+ return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
68
+ }
69
+ //#endregion
70
+ //#region src/deck/model.ts
71
+ const SLIDE_TRANSITIONS = [
72
+ "none",
73
+ "fade",
74
+ "fade-out",
75
+ "slide-left",
76
+ "slide-right",
77
+ "slide-up",
78
+ "slide-down",
79
+ "view-transition"
83
80
  ];
84
81
  var CompileError = class extends Error {
85
- constructor(message, code) {
86
- super(message);
87
- this.code = code;
88
- this.name = "CompileError";
89
- }
82
+ code;
83
+ constructor(message, code) {
84
+ super(message);
85
+ this.code = code;
86
+ this.name = "CompileError";
87
+ }
90
88
  };
91
-
92
- // src/deck/frontmatter.ts
89
+ //#endregion
90
+ //#region src/deck/frontmatter.ts
93
91
  function readFrontmatter(source) {
94
- const normalized = source.replace(/\r\n/g, "\n").trimStart();
95
- if (!normalized.startsWith("---\n")) return { attrs: {}, body: source.trim() };
96
- const end = normalized.indexOf("\n---", 4);
97
- if (end === -1) throw new CompileError("Frontmatter block is not closed.", "frontmatter-unclosed");
98
- const rawAttrs = normalized.slice(4, end).trim();
99
- const body = normalized.slice(end + 4).replace(/^\n/, "").trim();
100
- return { attrs: parseFrontmatterAttrs(rawAttrs), body };
92
+ const normalized = source.replace(/\r\n/g, "\n").trimStart();
93
+ if (!normalized.startsWith("---\n")) return {
94
+ attrs: {},
95
+ body: source.trim()
96
+ };
97
+ const end = normalized.indexOf("\n---", 4);
98
+ if (end === -1) throw new CompileError("Frontmatter block is not closed.", "frontmatter-unclosed");
99
+ const rawAttrs = normalized.slice(4, end).trim();
100
+ const body = normalized.slice(end + 4).replace(/^\n/, "").trim();
101
+ return {
102
+ attrs: parseFrontmatterAttrs(rawAttrs),
103
+ body
104
+ };
101
105
  }
102
106
  function toDeckFrontmatter(attrs, warnings) {
103
- const meta = { ...attrs };
104
- const deck = { meta };
105
- deck.title = takeString(meta, "title");
106
- deck.description = takeString(meta, "description");
107
- deck.author = takeString(meta, "author");
108
- deck.date = takeString(meta, "date");
109
- deck.theme = takeString(meta, "theme");
110
- deck.transition = takeKnownStringWithWarning(meta, "transition", SLIDE_TRANSITIONS, "none", warnings, "unknown-transition");
111
- deck.transitionDuration = takeTransitionDuration(meta, warnings);
112
- deck.transitionEasing = takeTransitionEasing(meta, warnings);
113
- deck.assets = takeStringOrStringArray(meta, "assets");
114
- deck.draft = takeBoolean(meta, "draft");
115
- deck.presenter = takeBoolean(meta, "presenter");
116
- const tags = meta.tags;
117
- if (Array.isArray(tags)) {
118
- deck.tags = tags.map(String);
119
- delete meta.tags;
120
- }
121
- return deck;
107
+ const meta = { ...attrs };
108
+ const deck = { meta };
109
+ deck.title = takeString(meta, "title");
110
+ deck.description = takeString(meta, "description");
111
+ deck.author = takeString(meta, "author");
112
+ deck.date = takeString(meta, "date");
113
+ deck.theme = takeString(meta, "theme");
114
+ deck.transition = takeKnownStringWithWarning(meta, "transition", SLIDE_TRANSITIONS, "none", warnings, "unknown-transition");
115
+ deck.transitionDuration = takeTransitionDuration(meta, warnings);
116
+ deck.transitionEasing = takeTransitionEasing(meta, warnings);
117
+ deck.assets = takeStringOrStringArray(meta, "assets");
118
+ deck.draft = takeBoolean(meta, "draft");
119
+ deck.presenter = takeBoolean(meta, "presenter");
120
+ const tags = meta.tags;
121
+ if (Array.isArray(tags)) {
122
+ deck.tags = tags.map(String);
123
+ delete meta.tags;
124
+ }
125
+ return deck;
122
126
  }
123
127
  function toSlideFrontmatter(attrs, warnings, input) {
124
- const meta = { ...attrs };
125
- const slide = {
126
- title: takeString(meta, "title") ?? input.fallbackTitle,
127
- layout: takeString(meta, "layout") ?? input.fallbackLayout,
128
- className: takeString(meta, "class") ?? input.fallbackClassName,
129
- notes: takeString(meta, "notes"),
130
- background: takeString(meta, "background"),
131
- transition: takeKnownStringWithWarning(
132
- meta,
133
- "transition",
134
- SLIDE_TRANSITIONS,
135
- "none",
136
- warnings,
137
- "unknown-transition",
138
- input.slideIndex
139
- ) ?? input.fallbackTransition,
140
- transitionDuration: takeTransitionDuration(meta, warnings, input.slideIndex) ?? input.fallbackTransitionDuration,
141
- transitionEasing: takeTransitionEasing(meta, warnings, input.slideIndex) ?? input.fallbackTransitionEasing,
142
- meta
143
- };
144
- return slide;
128
+ const meta = { ...attrs };
129
+ return {
130
+ title: takeString(meta, "title") ?? input.fallbackTitle,
131
+ layout: takeString(meta, "layout") ?? input.fallbackLayout,
132
+ className: takeString(meta, "class") ?? input.fallbackClassName,
133
+ notes: takeString(meta, "notes"),
134
+ background: takeString(meta, "background"),
135
+ transition: takeKnownStringWithWarning(meta, "transition", SLIDE_TRANSITIONS, "none", warnings, "unknown-transition", input.slideIndex) ?? input.fallbackTransition,
136
+ transitionDuration: takeTransitionDuration(meta, warnings, input.slideIndex) ?? input.fallbackTransitionDuration,
137
+ transitionEasing: takeTransitionEasing(meta, warnings, input.slideIndex) ?? input.fallbackTransitionEasing,
138
+ meta
139
+ };
145
140
  }
146
141
  function addUnknownFrontmatterWarnings(warnings, meta, scope, slideIndex) {
147
- for (const key of Object.keys(meta)) {
148
- warnings.push({
149
- code: "unknown-frontmatter-key",
150
- message: `Unknown ${scope} frontmatter key "${key}" is preserved in meta.`,
151
- ...slideIndex !== void 0 ? { slideIndex } : {}
152
- });
153
- }
142
+ for (const key of Object.keys(meta)) warnings.push({
143
+ code: "unknown-frontmatter-key",
144
+ message: `Unknown ${scope} frontmatter key "${key}" is preserved in meta.`,
145
+ ...slideIndex !== void 0 ? { slideIndex } : {}
146
+ });
154
147
  }
155
148
  function splitSlideSources(source) {
156
- const lines = source.replace(/\r\n/g, "\n").trim().split("\n");
157
- const slides = [];
158
- let current = [];
159
- let cursor = 0;
160
- while (cursor < lines.length) {
161
- if (isFence(lines[cursor])) {
162
- if (looksLikeFrontmatterFence(lines, cursor) && findFrontmatterEnd(lines, cursor) === -1) {
163
- throw new CompileError("Frontmatter block is not closed.", "frontmatter-unclosed");
164
- }
165
- if (isFrontmatterStart(lines, cursor)) {
166
- if (hasMeaningfulLines(current)) {
167
- slides.push(current.join("\n").trim());
168
- }
169
- current = [];
170
- const frontmatterEnd = findFrontmatterEnd(lines, cursor);
171
- current.push(...lines.slice(cursor, frontmatterEnd + 1));
172
- cursor = frontmatterEnd + 1;
173
- continue;
174
- }
175
- if (hasMeaningfulLines(current)) {
176
- slides.push(current.join("\n").trim());
177
- current = [];
178
- }
179
- cursor += 1;
180
- continue;
181
- }
182
- current.push(lines[cursor]);
183
- cursor += 1;
184
- }
185
- if (hasMeaningfulLines(current)) {
186
- slides.push(current.join("\n").trim());
187
- }
188
- return slides;
149
+ const lines = source.replace(/\r\n/g, "\n").trim().split("\n");
150
+ const slides = [];
151
+ let current = [];
152
+ let cursor = 0;
153
+ while (cursor < lines.length) {
154
+ if (isFence(lines[cursor])) {
155
+ if (looksLikeFrontmatterFence(lines, cursor) && findFrontmatterEnd(lines, cursor) === -1) throw new CompileError("Frontmatter block is not closed.", "frontmatter-unclosed");
156
+ if (isFrontmatterStart(lines, cursor)) {
157
+ if (hasMeaningfulLines(current)) slides.push(current.join("\n").trim());
158
+ current = [];
159
+ const frontmatterEnd = findFrontmatterEnd(lines, cursor);
160
+ current.push(...lines.slice(cursor, frontmatterEnd + 1));
161
+ cursor = frontmatterEnd + 1;
162
+ continue;
163
+ }
164
+ if (hasMeaningfulLines(current)) {
165
+ slides.push(current.join("\n").trim());
166
+ current = [];
167
+ }
168
+ cursor += 1;
169
+ continue;
170
+ }
171
+ current.push(lines[cursor]);
172
+ cursor += 1;
173
+ }
174
+ if (hasMeaningfulLines(current)) slides.push(current.join("\n").trim());
175
+ return slides;
189
176
  }
190
177
  function parseFrontmatterAttrs(source) {
191
- const attrs = {};
192
- const lines = source.split("\n");
193
- for (let index = 0; index < lines.length; index += 1) {
194
- const line = lines[index];
195
- const match = /^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
196
- if (!match) {
197
- if (line.trim()) {
198
- throw new CompileError(`Invalid frontmatter line ${index + 1}: "${line.trim()}"`, "frontmatter-invalid-line");
199
- }
200
- continue;
201
- }
202
- const key = match[1];
203
- const value = match[2];
204
- if (value === "|") {
205
- const block = [];
206
- index += 1;
207
- while (index < lines.length && /^\s+/.test(lines[index])) {
208
- block.push(lines[index].trim());
209
- index += 1;
210
- }
211
- index -= 1;
212
- attrs[key] = block.join("\n").trim();
213
- continue;
214
- }
215
- if (value.trim() === "") {
216
- const nested = [];
217
- index += 1;
218
- while (index < lines.length && /^\s+/.test(lines[index])) {
219
- nested.push(lines[index]);
220
- index += 1;
221
- }
222
- index -= 1;
223
- attrs[key] = parseNestedFrontmatterValue(nested);
224
- continue;
225
- }
226
- attrs[key] = parseScalar(value);
227
- }
228
- return attrs;
178
+ const attrs = {};
179
+ const lines = source.split("\n");
180
+ for (let index = 0; index < lines.length; index += 1) {
181
+ const line = lines[index];
182
+ const match = /^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
183
+ if (!match) {
184
+ if (line.trim()) throw new CompileError(`Invalid frontmatter line ${index + 1}: "${line.trim()}"`, "frontmatter-invalid-line");
185
+ continue;
186
+ }
187
+ const key = match[1];
188
+ const value = match[2];
189
+ if (value === "|") {
190
+ const block = [];
191
+ index += 1;
192
+ while (index < lines.length && /^\s+/.test(lines[index])) {
193
+ block.push(lines[index].trim());
194
+ index += 1;
195
+ }
196
+ index -= 1;
197
+ attrs[key] = block.join("\n").trim();
198
+ continue;
199
+ }
200
+ if (value.trim() === "") {
201
+ const nested = [];
202
+ index += 1;
203
+ while (index < lines.length && /^\s+/.test(lines[index])) {
204
+ nested.push(lines[index]);
205
+ index += 1;
206
+ }
207
+ index -= 1;
208
+ attrs[key] = parseNestedFrontmatterValue(nested);
209
+ continue;
210
+ }
211
+ attrs[key] = parseScalar(value);
212
+ }
213
+ return attrs;
229
214
  }
230
215
  function isFrontmatterStart(lines, index) {
231
- return looksLikeFrontmatterFence(lines, index) && findFrontmatterEnd(lines, index) > index;
216
+ return looksLikeFrontmatterFence(lines, index) && findFrontmatterEnd(lines, index) > index;
232
217
  }
233
218
  function looksLikeFrontmatterFence(lines, index) {
234
- if (!isFence(lines[index])) return false;
235
- const next = lines[index + 1];
236
- return next != null && /^([A-Za-z_][A-Za-z0-9_-]*):\s*/.test(next);
219
+ if (!isFence(lines[index])) return false;
220
+ const next = lines[index + 1];
221
+ return next != null && /^([A-Za-z_][A-Za-z0-9_-]*):\s*/.test(next);
237
222
  }
238
223
  function findFrontmatterEnd(lines, start) {
239
- for (let index = start + 1; index < lines.length; index += 1) {
240
- if (isFence(lines[index])) return index;
241
- }
242
- return -1;
224
+ for (let index = start + 1; index < lines.length; index += 1) if (isFence(lines[index])) return index;
225
+ return -1;
243
226
  }
244
227
  function isFence(line) {
245
- return /^---\s*$/.test(line);
228
+ return /^---\s*$/.test(line);
246
229
  }
247
230
  function hasMeaningfulLines(lines) {
248
- return lines.some((line) => line.trim() !== "");
231
+ return lines.some((line) => line.trim() !== "");
249
232
  }
250
233
  function parseNestedFrontmatterValue(lines) {
251
- const meaningful = lines.map((line) => line.trim()).filter(Boolean);
252
- if (meaningful.every((line) => line.startsWith("- "))) {
253
- return meaningful.map((line) => parseScalar(line.slice(2)));
254
- }
255
- const object = {};
256
- for (const line of meaningful) {
257
- const match = /^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
258
- if (!match) {
259
- throw new CompileError(`Invalid nested frontmatter line: "${line}"`, "frontmatter-invalid-line");
260
- }
261
- object[match[1]] = parseScalar(match[2]);
262
- }
263
- return object;
234
+ const meaningful = lines.map((line) => line.trim()).filter(Boolean);
235
+ if (meaningful.every((line) => line.startsWith("- "))) return meaningful.map((line) => parseScalar(line.slice(2)));
236
+ const object = {};
237
+ for (const line of meaningful) {
238
+ const match = /^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
239
+ if (!match) throw new CompileError(`Invalid nested frontmatter line: "${line}"`, "frontmatter-invalid-line");
240
+ object[match[1]] = parseScalar(match[2]);
241
+ }
242
+ return object;
264
243
  }
265
244
  function parseScalar(value) {
266
- const trimmed = value.trim().replace(/^['"]|['"]$/g, "");
267
- if (trimmed === "true") return true;
268
- if (trimmed === "false") return false;
269
- if (/^\[.*\]$/.test(trimmed)) {
270
- return trimmed.slice(1, -1).split(",").map((item) => item.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
271
- }
272
- return trimmed;
245
+ const trimmed = value.trim().replace(/^['"]|['"]$/g, "");
246
+ if (trimmed === "true") return true;
247
+ if (trimmed === "false") return false;
248
+ if (/^\[.*\]$/.test(trimmed)) return trimmed.slice(1, -1).split(",").map((item) => item.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
249
+ return trimmed;
273
250
  }
274
251
  function takeString(attrs, key) {
275
- const value = attrs[key];
276
- if (typeof value !== "string") return void 0;
277
- delete attrs[key];
278
- return value;
252
+ const value = attrs[key];
253
+ if (typeof value !== "string") return void 0;
254
+ delete attrs[key];
255
+ return value;
279
256
  }
280
257
  function takeKnownStringWithWarning(attrs, key, values, fallback, warnings, code, slideIndex) {
281
- const value = attrs[key];
282
- delete attrs[key];
283
- if (value === void 0) return void 0;
284
- if (typeof value === "string" && values.includes(value)) return value;
285
- warnings.push({
286
- code,
287
- message: `Unknown ${key} value "${String(value)}"; using ${fallback}.`,
288
- ...slideIndex !== void 0 ? { slideIndex } : {}
289
- });
290
- return fallback;
258
+ const value = attrs[key];
259
+ delete attrs[key];
260
+ if (value === void 0) return void 0;
261
+ if (typeof value === "string" && values.includes(value)) return value;
262
+ warnings.push({
263
+ code,
264
+ message: `Unknown ${key} value "${String(value)}"; using ${fallback}.`,
265
+ ...slideIndex !== void 0 ? { slideIndex } : {}
266
+ });
267
+ return fallback;
291
268
  }
292
269
  function takeTransitionDuration(attrs, warnings, slideIndex) {
293
- const value = attrs.transitionDuration;
294
- delete attrs.transitionDuration;
295
- if (value === void 0) return void 0;
296
- if (typeof value === "string" && isValidTransitionDuration(value)) return value;
297
- warnings.push({
298
- code: "invalid-transition-duration",
299
- message: `Invalid transitionDuration value "${String(value)}"; ignoring it.`,
300
- ...slideIndex !== void 0 ? { slideIndex } : {}
301
- });
302
- return void 0;
270
+ const value = attrs.transitionDuration;
271
+ delete attrs.transitionDuration;
272
+ if (value === void 0) return void 0;
273
+ if (typeof value === "string" && isValidTransitionDuration(value)) return value;
274
+ warnings.push({
275
+ code: "invalid-transition-duration",
276
+ message: `Invalid transitionDuration value "${String(value)}"; ignoring it.`,
277
+ ...slideIndex !== void 0 ? { slideIndex } : {}
278
+ });
303
279
  }
304
280
  function takeTransitionEasing(attrs, warnings, slideIndex) {
305
- const value = attrs.transitionEasing;
306
- delete attrs.transitionEasing;
307
- if (value === void 0) return void 0;
308
- if (typeof value === "string" && isValidTransitionEasing(value)) return value;
309
- warnings.push({
310
- code: "invalid-transition-easing",
311
- message: `Invalid transitionEasing value "${String(value)}"; ignoring it.`,
312
- ...slideIndex !== void 0 ? { slideIndex } : {}
313
- });
314
- return void 0;
281
+ const value = attrs.transitionEasing;
282
+ delete attrs.transitionEasing;
283
+ if (value === void 0) return void 0;
284
+ if (typeof value === "string" && isValidTransitionEasing(value)) return value;
285
+ warnings.push({
286
+ code: "invalid-transition-easing",
287
+ message: `Invalid transitionEasing value "${String(value)}"; ignoring it.`,
288
+ ...slideIndex !== void 0 ? { slideIndex } : {}
289
+ });
315
290
  }
316
291
  function isValidTransitionDuration(value) {
317
- return value.split(",").map((item) => item.trim()).every((item) => /^(?:\d+|\d*\.\d+)(?:ms|s)$/.test(item));
292
+ return value.split(",").map((item) => item.trim()).every((item) => /^(?:\d+|\d*\.\d+)(?:ms|s)$/.test(item));
318
293
  }
319
294
  function isValidTransitionEasing(value) {
320
- const easing = value.trim();
321
- return ["linear", "ease", "ease-in", "ease-out", "ease-in-out", "step-start", "step-end"].includes(easing) || /^cubic-bezier\(\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*\)$/.test(
322
- easing
323
- ) || /^steps\(\s*\d+\s*(?:,\s*(?:jump-start|jump-end|jump-none|jump-both|start|end))?\s*\)$/.test(easing) || /^linear\([^)]+\)$/.test(easing);
295
+ const easing = value.trim();
296
+ return [
297
+ "linear",
298
+ "ease",
299
+ "ease-in",
300
+ "ease-out",
301
+ "ease-in-out",
302
+ "step-start",
303
+ "step-end"
304
+ ].includes(easing) || /^cubic-bezier\(\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*\)$/.test(easing) || /^steps\(\s*\d+\s*(?:,\s*(?:jump-start|jump-end|jump-none|jump-both|start|end))?\s*\)$/.test(easing) || /^linear\([^)]+\)$/.test(easing);
324
305
  }
325
306
  function takeStringOrStringArray(attrs, key) {
326
- const value = attrs[key];
327
- if (typeof value === "string") {
328
- delete attrs[key];
329
- return value;
330
- }
331
- if (Array.isArray(value)) {
332
- delete attrs[key];
333
- return value.map(String);
334
- }
335
- return void 0;
307
+ const value = attrs[key];
308
+ if (typeof value === "string") {
309
+ delete attrs[key];
310
+ return value;
311
+ }
312
+ if (Array.isArray(value)) {
313
+ delete attrs[key];
314
+ return value.map(String);
315
+ }
336
316
  }
337
317
  function takeBoolean(attrs, key) {
338
- const value = attrs[key];
339
- if (typeof value !== "boolean") return void 0;
340
- delete attrs[key];
341
- return value;
318
+ const value = attrs[key];
319
+ if (typeof value !== "boolean") return void 0;
320
+ delete attrs[key];
321
+ return value;
342
322
  }
343
-
344
- // src/deck/assets.ts
323
+ //#endregion
324
+ //#region src/deck/assets.ts
345
325
  function buildExternalAssetRefs(candidates) {
346
- const refs = /* @__PURE__ */ new Map();
347
- for (const candidate of candidates) {
348
- const type = assetRefType(candidate);
349
- if (!type || refs.has(candidate)) continue;
350
- const contentType = contentTypeForPath(candidate);
351
- refs.set(candidate, {
352
- sourcePath: candidate,
353
- publicPath: candidate,
354
- type,
355
- ...contentType ? { contentType } : {}
356
- });
357
- }
358
- return [...refs.values()];
326
+ const refs = /* @__PURE__ */ new Map();
327
+ for (const candidate of candidates) {
328
+ const type = assetRefType(candidate);
329
+ if (!type || refs.has(candidate)) continue;
330
+ const contentType = contentTypeForPath(candidate);
331
+ refs.set(candidate, {
332
+ sourcePath: candidate,
333
+ publicPath: candidate,
334
+ type,
335
+ ...contentType ? { contentType } : {}
336
+ });
337
+ }
338
+ return [...refs.values()];
359
339
  }
360
340
  function addExternalAssetWarnings(warnings, assets) {
361
- for (const asset of assets) {
362
- if (asset.type !== "remote" && asset.type !== "r2") continue;
363
- const label = asset.type === "r2" ? "R2" : "Remote";
364
- warnings.push({
365
- code: "external-asset-unverified",
366
- message: `${label} asset existence cannot be verified at compile time: ${asset.sourcePath}`
367
- });
368
- }
341
+ for (const asset of assets) {
342
+ if (asset.type !== "remote" && asset.type !== "r2") continue;
343
+ const label = asset.type === "r2" ? "R2" : "Remote";
344
+ warnings.push({
345
+ code: "external-asset-unverified",
346
+ message: `${label} asset existence cannot be verified at compile time: ${asset.sourcePath}`
347
+ });
348
+ }
369
349
  }
370
350
  function collectMarkdownAssetCandidates(markdown) {
371
- const candidates = [];
372
- for (const match of markdown.matchAll(/^\s*(?:background|image|src|asset):\s*['"]?([^'"\n]+)['"]?\s*$/gim)) {
373
- candidates.push(match[1].trim());
374
- }
375
- for (const match of markdown.matchAll(/!\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g)) {
376
- candidates.push(match[1].trim());
377
- }
378
- for (const match of markdown.matchAll(/\b(?:src|href|image|background)=["']([^"']+)["']/g)) {
379
- candidates.push(match[1].trim());
380
- }
381
- return candidates;
351
+ const candidates = [];
352
+ candidates.push(...collectDirectiveAssetCandidates(markdown));
353
+ candidates.push(...collectMarkdownImageCandidates(markdown));
354
+ for (const match of markdown.matchAll(/\b(?:src|href|image|background)=["']([^"']+)["']/g)) candidates.push(match[1].trim());
355
+ return candidates;
382
356
  }
383
357
  function collectFrontmatterAssetCandidates(value) {
384
- if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
385
- if (typeof value === "string") return [value.trim()].filter(Boolean);
386
- return [];
358
+ if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
359
+ if (typeof value === "string") return [value.trim()].filter(Boolean);
360
+ return [];
387
361
  }
388
362
  function contentTypeForPath(path) {
389
- const pathname = path.replace(/\?.*$/, "").toLowerCase();
390
- if (/\.png(?:#.*)?$/.test(pathname)) return "image/png";
391
- if (/\.jpe?g(?:#.*)?$/.test(pathname)) return "image/jpeg";
392
- if (/\.gif(?:#.*)?$/.test(pathname)) return "image/gif";
393
- if (/\.svg(?:#.*)?$/.test(pathname)) return "image/svg+xml";
394
- if (/\.webp(?:#.*)?$/.test(pathname)) return "image/webp";
395
- return void 0;
363
+ const queryIndex = path.indexOf("?");
364
+ const pathname = (queryIndex === -1 ? path : path.slice(0, queryIndex)).toLowerCase();
365
+ if (hasExtension(pathname, ".png")) return "image/png";
366
+ if (hasExtension(pathname, ".jpg") || hasExtension(pathname, ".jpeg")) return "image/jpeg";
367
+ if (hasExtension(pathname, ".gif")) return "image/gif";
368
+ if (hasExtension(pathname, ".svg")) return "image/svg+xml";
369
+ if (hasExtension(pathname, ".webp")) return "image/webp";
370
+ }
371
+ function hasExtension(path, extension) {
372
+ return path.endsWith(extension) || path.includes(`${extension}#`);
373
+ }
374
+ function collectDirectiveAssetCandidates(markdown) {
375
+ const candidates = [];
376
+ for (const sourceLine of markdown.split("\n")) {
377
+ const line = sourceLine.endsWith("\r") ? sourceLine.slice(0, -1).trim() : sourceLine.trim();
378
+ const separator = line.indexOf(":");
379
+ if (separator === -1) continue;
380
+ const key = line.slice(0, separator).toLowerCase();
381
+ if (key !== "background" && key !== "image" && key !== "src" && key !== "asset") continue;
382
+ let value = line.slice(separator + 1).trim();
383
+ if (value.startsWith("\"") || value.startsWith("'")) value = value.slice(1);
384
+ if (value.endsWith("\"") || value.endsWith("'")) value = value.slice(0, -1);
385
+ value = value.trim();
386
+ if (value && !value.includes("\"") && !value.includes("'")) candidates.push(value);
387
+ }
388
+ return candidates;
389
+ }
390
+ function collectMarkdownImageCandidates(markdown) {
391
+ const candidates = [];
392
+ let index = 0;
393
+ while (index < markdown.length - 1) {
394
+ const imageStart = markdown.indexOf("![", index);
395
+ if (imageStart === -1) break;
396
+ const labelEnd = markdown.indexOf("]", imageStart + 2);
397
+ if (labelEnd === -1) break;
398
+ if (markdown[labelEnd + 1] !== "(") {
399
+ index = labelEnd + 1;
400
+ continue;
401
+ }
402
+ let cursor = labelEnd + 2;
403
+ const destinationStart = cursor;
404
+ while (cursor < markdown.length && markdown[cursor] !== ")" && !isMarkdownWhitespace(markdown[cursor])) cursor += 1;
405
+ if (cursor === destinationStart) {
406
+ index = cursor + 1;
407
+ continue;
408
+ }
409
+ const destination = markdown.slice(destinationStart, cursor);
410
+ while (cursor < markdown.length && isMarkdownWhitespace(markdown[cursor])) cursor += 1;
411
+ if (markdown[cursor] !== ")") {
412
+ const quote = markdown[cursor];
413
+ if (quote !== "\"" && quote !== "'") {
414
+ index = cursor + 1;
415
+ continue;
416
+ }
417
+ cursor += 1;
418
+ while (cursor < markdown.length && markdown[cursor] !== "\"" && markdown[cursor] !== "'") cursor += 1;
419
+ if (cursor >= markdown.length) break;
420
+ cursor += 1;
421
+ while (cursor < markdown.length && isMarkdownWhitespace(markdown[cursor])) cursor += 1;
422
+ }
423
+ if (markdown[cursor] === ")") {
424
+ candidates.push(destination);
425
+ index = cursor + 1;
426
+ } else index = cursor + 1;
427
+ }
428
+ return candidates;
429
+ }
430
+ function isMarkdownWhitespace(value) {
431
+ return value === " " || value === " " || value === "\r" || value === "\n";
396
432
  }
397
433
  function assetRefType(value) {
398
- if (/^https?:\/\//i.test(value)) return "remote";
399
- if (/^r2:\/\//i.test(value)) return "r2";
400
- if (value.startsWith("/")) return "public";
401
- return void 0;
434
+ if (/^https?:\/\//i.test(value)) return "remote";
435
+ if (/^r2:\/\//i.test(value)) return "r2";
436
+ if (value.startsWith("/")) return "public";
402
437
  }
403
-
404
- // src/deck/speaker-notes.ts
438
+ //#endregion
439
+ //#region src/deck/speaker-notes.ts
405
440
  function extractMdxCommentSpeakerNotes(source) {
406
- const notes = [];
407
- const body = source.replace(/\{\/\*([\s\S]*?)\*\/\}/g, (_match, rawNote) => {
408
- const note = normalizeSpeakerNote(rawNote);
409
- if (note) notes.push(note);
410
- return "";
411
- });
412
- return {
413
- body,
414
- ...notes.length ? { notes: notes.join("\n\n") } : {}
415
- };
441
+ const notes = [];
442
+ return {
443
+ body: source.replace(/\{\/\*([\s\S]*?)\*\/\}/g, (_match, rawNote) => {
444
+ const note = normalizeSpeakerNote(rawNote);
445
+ if (note) notes.push(note);
446
+ return "";
447
+ }),
448
+ ...notes.length ? { notes: notes.join("\n\n") } : {}
449
+ };
416
450
  }
417
451
  function combineSpeakerNotes(...notes) {
418
- const combined = notes.map((note) => note?.trim()).filter((note) => Boolean(note));
419
- return combined.length ? combined.join("\n\n") : void 0;
452
+ const combined = notes.map((note) => note?.trim()).filter((note) => Boolean(note));
453
+ return combined.length ? combined.join("\n\n") : void 0;
420
454
  }
421
455
  function normalizeSpeakerNote(value) {
422
- return value.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).join("\n").trim();
456
+ return value.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).join("\n").trim();
423
457
  }
424
-
425
- // src/parser/parser.ts
426
- import { unified } from "unified";
427
- import remarkGfm from "remark-gfm";
428
- import remarkMdx from "remark-mdx";
429
- import remarkParse from "remark-parse";
430
- var slideSeparator = /^---\s*$/m;
458
+ //#endregion
459
+ //#region src/parser/parser.ts
460
+ const slideSeparator = /^---\s*$/m;
431
461
  function parseDeckWithWarnings(markdown) {
432
- const normalized = markdown.replace(/\r\n/g, "\n").trim();
433
- const chunks = normalized.length > 0 ? normalized.split(slideSeparator) : [""];
434
- const parsedSlides = chunks.map((chunk, index) => parseSlide(chunk.trim(), index)).filter(({ slide }) => slide.raw.length > 0 || slide.blocks.length > 0 || slide.nodes.length > 0);
435
- return {
436
- slides: parsedSlides.map(({ slide }) => slide),
437
- warnings: parsedSlides.flatMap(({ warnings }) => warnings)
438
- };
462
+ const normalized = markdown.replace(/\r\n/g, "\n").trim();
463
+ const parsedSlides = (normalized.length > 0 ? normalized.split(slideSeparator) : [""]).map((chunk, index) => parseSlide(chunk.trim(), index)).filter(({ slide }) => slide.raw.length > 0 || slide.blocks.length > 0 || slide.nodes.length > 0);
464
+ return {
465
+ slides: parsedSlides.map(({ slide }) => slide),
466
+ warnings: parsedSlides.flatMap(({ warnings }) => warnings)
467
+ };
439
468
  }
440
469
  function parseSlide(source, index) {
441
- const { attrs, body } = readSlideAttributes(source);
442
- const parsed = parseContent(body, index);
443
- const firstHeading = parsed.blocks.find((block) => block.type === "heading");
444
- return {
445
- slide: {
446
- index,
447
- title: stringAttr(attrs.title) ?? firstHeading?.text,
448
- layout: stringAttr(attrs.layout) ?? (index === 0 ? "cover" : "default"),
449
- className: stringAttr(attrs.class),
450
- blocks: parsed.blocks,
451
- nodes: parsed.nodes,
452
- raw: source
453
- },
454
- warnings: parsed.warnings
455
- };
470
+ const { attrs, body } = readSlideAttributes(source);
471
+ const parsed = parseContent(body, index);
472
+ const firstHeading = parsed.blocks.find((block) => block.type === "heading");
473
+ return {
474
+ slide: {
475
+ index,
476
+ title: stringAttr(attrs.title) ?? firstHeading?.text,
477
+ layout: stringAttr(attrs.layout) ?? (index === 0 ? "cover" : "default"),
478
+ className: stringAttr(attrs.class),
479
+ blocks: parsed.blocks,
480
+ nodes: parsed.nodes,
481
+ raw: source
482
+ },
483
+ warnings: parsed.warnings
484
+ };
456
485
  }
457
486
  function readSlideAttributes(source) {
458
- const lines = source.split("\n");
459
- const attrs = {};
460
- let cursor = 0;
461
- while (cursor < lines.length) {
462
- const line = lines[cursor];
463
- const match = /^(title|layout|class):\s*(.+)$/.exec(line);
464
- if (!match) break;
465
- attrs[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, "");
466
- cursor += 1;
467
- }
468
- return { attrs, body: lines.slice(cursor).join("\n").trim() };
487
+ const lines = source.split("\n");
488
+ const attrs = {};
489
+ let cursor = 0;
490
+ while (cursor < lines.length) {
491
+ const line = lines[cursor];
492
+ const match = /^(title|layout|class):\s*(.+)$/.exec(line);
493
+ if (!match) break;
494
+ attrs[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, "");
495
+ cursor += 1;
496
+ }
497
+ return {
498
+ attrs,
499
+ body: lines.slice(cursor).join("\n").trim()
500
+ };
469
501
  }
470
502
  function parseContent(body, slideIndex) {
471
- const { root, warnings: parseWarnings } = parseMarkdownTree(body, slideIndex);
472
- const blockResults = (root.children ?? []).map((child) => toBlocks(child, slideIndex, body));
473
- const nodeResults = (root.children ?? []).map((child) => toNodes(child, slideIndex, body));
474
- return {
475
- blocks: blockResults.flatMap(({ blocks }) => blocks),
476
- nodes: nodeResults.flatMap(({ nodes }) => nodes),
477
- warnings: [
478
- ...fenceWarningsFor(body, slideIndex),
479
- ...parseWarnings,
480
- ...blockResults.flatMap(({ warnings }) => warnings),
481
- ...nodeResults.flatMap(({ warnings }) => warnings)
482
- ]
483
- };
503
+ const { root, warnings: parseWarnings } = parseMarkdownTree(body, slideIndex);
504
+ const blockResults = (root.children ?? []).map((child) => toBlocks(child, slideIndex, body));
505
+ const nodeResults = (root.children ?? []).map((child) => toNodes(child, slideIndex, body));
506
+ return {
507
+ blocks: blockResults.flatMap(({ blocks }) => blocks),
508
+ nodes: nodeResults.flatMap(({ nodes }) => nodes),
509
+ warnings: [
510
+ ...fenceWarningsFor(body, slideIndex),
511
+ ...parseWarnings,
512
+ ...blockResults.flatMap(({ warnings }) => warnings),
513
+ ...nodeResults.flatMap(({ warnings }) => warnings)
514
+ ]
515
+ };
484
516
  }
485
517
  function parseMarkdownTree(body, slideIndex) {
486
- try {
487
- return {
488
- root: unified().use(remarkParse).use(remarkGfm).use(remarkMdx).parse(body),
489
- warnings: []
490
- };
491
- } catch (error) {
492
- const message = error instanceof Error ? error.message : "Unknown MDX parse error";
493
- return {
494
- root: unified().use(remarkParse).use(remarkGfm).parse(body),
495
- warnings: [parserWarning("mdx-parse-error", `Slide ${slideIndex + 1}: ${message}`, slideIndex)]
496
- };
497
- }
518
+ try {
519
+ return {
520
+ root: unified().use(remarkParse).use(remarkGfm).use(remarkMdx).parse(body),
521
+ warnings: []
522
+ };
523
+ } catch (error) {
524
+ const message = error instanceof Error ? error.message : "Unknown MDX parse error";
525
+ return {
526
+ root: unified().use(remarkParse).use(remarkGfm).parse(body),
527
+ warnings: [parserWarning("mdx-parse-error", `Slide ${slideIndex + 1}: ${message}`, slideIndex)]
528
+ };
529
+ }
498
530
  }
499
531
  function fenceWarningsFor(body, slideIndex) {
500
- let openFence;
501
- for (const line of body.split("\n")) {
502
- const match = /^(```|~~~)/.exec(line);
503
- if (!match) continue;
504
- if (!openFence) {
505
- openFence = match[1];
506
- continue;
507
- }
508
- if (line.startsWith(openFence)) openFence = void 0;
509
- }
510
- if (!openFence) return [];
511
- return [parserWarning("code-fence-unclosed", `Slide ${slideIndex + 1}: code fence is not closed.`, slideIndex)];
532
+ let openFence;
533
+ for (const line of body.split("\n")) {
534
+ const match = /^(```|~~~)/.exec(line);
535
+ if (!match) continue;
536
+ if (!openFence) {
537
+ openFence = match[1];
538
+ continue;
539
+ }
540
+ if (line.startsWith(openFence)) openFence = void 0;
541
+ }
542
+ if (!openFence) return [];
543
+ return [parserWarning("code-fence-unclosed", `Slide ${slideIndex + 1}: code fence is not closed.`, slideIndex)];
512
544
  }
513
545
  function toBlocks(node, slideIndex, source) {
514
- switch (node.type) {
515
- case "heading":
516
- return blockResult([{ type: "heading", depth: headingDepth(node.depth), text: textContent(node) }]);
517
- case "paragraph": {
518
- const image = soleImage(node);
519
- if (image) {
520
- return blockResult([
521
- {
522
- type: "image",
523
- alt: image.alt ?? "",
524
- src: image.url ?? "",
525
- ...image.title ? { title: image.title } : {}
526
- }
527
- ]);
528
- }
529
- return blockResult([{ type: "paragraph", text: textContent(node).replace(/\s+/g, " ").trim() }]);
530
- }
531
- case "list":
532
- return blockResult([
533
- {
534
- type: "list",
535
- ordered: node.ordered === true,
536
- items: (node.children ?? []).map((item) => taskListItemText(item)).filter(Boolean)
537
- }
538
- ]);
539
- case "code":
540
- return blockResult([{ type: "code", lang: node.lang ?? void 0, code: node.value ?? "" }]);
541
- case "blockquote":
542
- return blockResult([{ type: "blockquote", text: textContent(node).trim() }]);
543
- case "table":
544
- return blockResult([tableBlock(node)]);
545
- case "mdxJsxFlowElement":
546
- case "mdxJsxTextElement":
547
- return jsxElementBlock(node, slideIndex, source);
548
- case "mdxjsEsm":
549
- case "mdxFlowExpression":
550
- case "mdxTextExpression":
551
- case "thematicBreak":
552
- return blockResult([]);
553
- default:
554
- return blockResult(textContent(node).trim() ? [{ type: "paragraph", text: textContent(node).trim() }] : []);
555
- }
546
+ switch (node.type) {
547
+ case "heading": return blockResult([{
548
+ type: "heading",
549
+ depth: headingDepth(node.depth),
550
+ text: textContent(node)
551
+ }]);
552
+ case "paragraph": {
553
+ const image = soleImage(node);
554
+ if (image) return blockResult([{
555
+ type: "image",
556
+ alt: image.alt ?? "",
557
+ src: image.url ?? "",
558
+ ...image.title ? { title: image.title } : {}
559
+ }]);
560
+ return blockResult([{
561
+ type: "paragraph",
562
+ text: textContent(node).replace(/\s+/g, " ").trim()
563
+ }]);
564
+ }
565
+ case "list": return blockResult([{
566
+ type: "list",
567
+ ordered: node.ordered === true,
568
+ items: (node.children ?? []).map((item) => taskListItemText(item)).filter(Boolean)
569
+ }]);
570
+ case "code": return blockResult([{
571
+ type: "code",
572
+ lang: node.lang ?? void 0,
573
+ code: node.value ?? ""
574
+ }]);
575
+ case "blockquote": return blockResult([{
576
+ type: "blockquote",
577
+ text: textContent(node).trim()
578
+ }]);
579
+ case "table": return blockResult([tableBlock(node)]);
580
+ case "mdxJsxFlowElement":
581
+ case "mdxJsxTextElement": return jsxElementBlock(node, slideIndex, source);
582
+ case "mdxjsEsm":
583
+ case "mdxFlowExpression":
584
+ case "mdxTextExpression":
585
+ case "thematicBreak": return blockResult([]);
586
+ default: return blockResult(textContent(node).trim() ? [{
587
+ type: "paragraph",
588
+ text: textContent(node).trim()
589
+ }] : []);
590
+ }
556
591
  }
557
592
  function jsxElementBlock(node, slideIndex, source) {
558
- const name = node.name ?? "";
559
- if (!name) return blockResult([]);
560
- if (isComponentName(name)) {
561
- const parsedProps = parseProps(node.attributes ?? [], slideIndex, name);
562
- return blockResult(
563
- [
564
- {
565
- type: "component",
566
- name,
567
- props: parsedProps.props,
568
- raw: sourceForNode(node, source) ?? `<${name} />`
569
- }
570
- ],
571
- parsedProps.warnings
572
- );
573
- }
574
- return blockResult([{ type: "paragraph", text: textContent(node).trim() }]);
593
+ const name = node.name ?? "";
594
+ if (!name) return blockResult([]);
595
+ if (isComponentName(name)) {
596
+ const parsedProps = parseProps(node.attributes ?? [], slideIndex, name);
597
+ return blockResult([{
598
+ type: "component",
599
+ name,
600
+ props: parsedProps.props,
601
+ raw: sourceForNode(node, source) ?? `<${name} />`
602
+ }], parsedProps.warnings);
603
+ }
604
+ return blockResult([{
605
+ type: "paragraph",
606
+ text: textContent(node).trim()
607
+ }]);
575
608
  }
576
609
  function toNodes(node, slideIndex, source) {
577
- switch (node.type) {
578
- case "text":
579
- return nodeResult(node.value ? [{ type: "text", value: node.value }] : []);
580
- case "emphasis":
581
- return elementNodeResult(node, "em", slideIndex, source);
582
- case "strong":
583
- return elementNodeResult(node, "strong", slideIndex, source);
584
- case "delete":
585
- return elementNodeResult(node, "del", slideIndex, source);
586
- case "inlineCode":
587
- return nodeResult([{ type: "element", tag: "code", props: {}, children: [{ type: "text", value: node.value ?? "" }] }]);
588
- case "break":
589
- return nodeResult([{ type: "element", tag: "br", props: {}, children: [] }]);
590
- case "heading":
591
- return elementNodeResult(node, `h${headingDepth(node.depth)}`, slideIndex, source);
592
- case "paragraph": {
593
- if (soleImage(node)) return childrenToNodes(node, slideIndex, source);
594
- if (isJsxOnlyParagraph(node)) return childrenToNodes(node, slideIndex, source);
595
- return elementNodeResult(node, "p", slideIndex, source);
596
- }
597
- case "list": {
598
- const tag = node.ordered === true ? "ol" : "ul";
599
- const items = node.children ?? [];
600
- const childResults = items.map((child) => childrenToNodes(child, slideIndex, source));
601
- return nodeResult(
602
- [
603
- {
604
- type: "element",
605
- tag,
606
- props: {},
607
- children: childResults.map(({ nodes }, itemIndex) => listItemNode(nodes, items[itemIndex]?.checked))
608
- }
609
- ],
610
- childResults.flatMap(({ warnings }) => warnings)
611
- );
612
- }
613
- case "listItem":
614
- return childrenToNodes(node, slideIndex, source);
615
- case "code":
616
- return nodeResult([{ type: "code", lang: node.lang ?? void 0, value: node.value ?? "" }]);
617
- case "blockquote":
618
- return elementNodeResult(node, "blockquote", slideIndex, source);
619
- case "image":
620
- return nodeResult([
621
- {
622
- type: "element",
623
- tag: "img",
624
- props: { src: node.url ?? "", alt: node.alt ?? "", ...node.title ? { title: node.title } : {} },
625
- children: []
626
- }
627
- ]);
628
- case "link":
629
- return elementNodeResult(node, "a", slideIndex, source, { href: node.url ?? "" });
630
- case "table":
631
- return tableNodes(node, slideIndex, source);
632
- case "mdxJsxFlowElement":
633
- case "mdxJsxTextElement":
634
- return jsxElementNodes(node, slideIndex, source);
635
- case "mdxjsEsm":
636
- return nodeResult(
637
- [],
638
- [
639
- parserWarning(
640
- "mdx-import-export-ignored",
641
- `Slide ${slideIndex + 1}: MDX import/export syntax is ignored.`,
642
- slideIndex
643
- )
644
- ]
645
- );
646
- case "mdxFlowExpression":
647
- case "mdxTextExpression":
648
- return nodeResult(
649
- [],
650
- [
651
- parserWarning(
652
- "mdx-expression-ignored",
653
- `Slide ${slideIndex + 1}: MDX JavaScript expressions are ignored.`,
654
- slideIndex
655
- )
656
- ]
657
- );
658
- case "thematicBreak":
659
- return nodeResult([{ type: "element", tag: "hr", props: {}, children: [] }]);
660
- default:
661
- return childrenToNodes(node, slideIndex, source);
662
- }
610
+ switch (node.type) {
611
+ case "text": return nodeResult(node.value ? [{
612
+ type: "text",
613
+ value: node.value
614
+ }] : []);
615
+ case "emphasis": return elementNodeResult(node, "em", slideIndex, source);
616
+ case "strong": return elementNodeResult(node, "strong", slideIndex, source);
617
+ case "delete": return elementNodeResult(node, "del", slideIndex, source);
618
+ case "inlineCode": return nodeResult([{
619
+ type: "element",
620
+ tag: "code",
621
+ props: {},
622
+ children: [{
623
+ type: "text",
624
+ value: node.value ?? ""
625
+ }]
626
+ }]);
627
+ case "break": return nodeResult([{
628
+ type: "element",
629
+ tag: "br",
630
+ props: {},
631
+ children: []
632
+ }]);
633
+ case "heading": return elementNodeResult(node, `h${headingDepth(node.depth)}`, slideIndex, source);
634
+ case "paragraph":
635
+ if (soleImage(node)) return childrenToNodes(node, slideIndex, source);
636
+ if (isJsxOnlyParagraph(node)) return childrenToNodes(node, slideIndex, source);
637
+ return elementNodeResult(node, "p", slideIndex, source);
638
+ case "list": {
639
+ const tag = node.ordered === true ? "ol" : "ul";
640
+ const items = node.children ?? [];
641
+ const childResults = items.map((child) => childrenToNodes(child, slideIndex, source));
642
+ return nodeResult([{
643
+ type: "element",
644
+ tag,
645
+ props: {},
646
+ children: childResults.map(({ nodes }, itemIndex) => listItemNode(nodes, items[itemIndex]?.checked))
647
+ }], childResults.flatMap(({ warnings }) => warnings));
648
+ }
649
+ case "listItem": return childrenToNodes(node, slideIndex, source);
650
+ case "code": return nodeResult([{
651
+ type: "code",
652
+ lang: node.lang ?? void 0,
653
+ value: node.value ?? ""
654
+ }]);
655
+ case "blockquote": return elementNodeResult(node, "blockquote", slideIndex, source);
656
+ case "image": return nodeResult([{
657
+ type: "element",
658
+ tag: "img",
659
+ props: {
660
+ src: node.url ?? "",
661
+ alt: node.alt ?? "",
662
+ ...node.title ? { title: node.title } : {}
663
+ },
664
+ children: []
665
+ }]);
666
+ case "link": return elementNodeResult(node, "a", slideIndex, source, { href: node.url ?? "" });
667
+ case "table": return tableNodes(node, slideIndex, source);
668
+ case "mdxJsxFlowElement":
669
+ case "mdxJsxTextElement": return jsxElementNodes(node, slideIndex, source);
670
+ case "mdxjsEsm": return nodeResult([], [parserWarning("mdx-import-export-ignored", `Slide ${slideIndex + 1}: MDX import/export syntax is ignored.`, slideIndex)]);
671
+ case "mdxFlowExpression":
672
+ case "mdxTextExpression": return nodeResult([], [parserWarning("mdx-expression-ignored", `Slide ${slideIndex + 1}: MDX JavaScript expressions are ignored.`, slideIndex)]);
673
+ case "thematicBreak": return nodeResult([{
674
+ type: "element",
675
+ tag: "hr",
676
+ props: {},
677
+ children: []
678
+ }]);
679
+ default: return childrenToNodes(node, slideIndex, source);
680
+ }
663
681
  }
664
682
  function elementNodeResult(node, tag, slideIndex, source, props = {}) {
665
- const children = childrenToNodes(node, slideIndex, source);
666
- return nodeResult(
667
- [
668
- {
669
- type: "element",
670
- tag,
671
- props,
672
- children: children.nodes
673
- }
674
- ],
675
- children.warnings
676
- );
683
+ const children = childrenToNodes(node, slideIndex, source);
684
+ return nodeResult([{
685
+ type: "element",
686
+ tag,
687
+ props,
688
+ children: children.nodes
689
+ }], children.warnings);
677
690
  }
678
691
  function listItemNode(children, checked) {
679
- if (typeof checked !== "boolean") return { type: "element", tag: "li", props: {}, children };
680
- return {
681
- type: "element",
682
- tag: "li",
683
- props: { class: "task-list-item" },
684
- children: [
685
- {
686
- type: "element",
687
- tag: "input",
688
- props: { type: "checkbox", disabled: true, ...checked ? { checked: true } : {} },
689
- children: []
690
- },
691
- { type: "text", value: " " },
692
- ...children
693
- ]
694
- };
692
+ if (typeof checked !== "boolean") return {
693
+ type: "element",
694
+ tag: "li",
695
+ props: {},
696
+ children
697
+ };
698
+ return {
699
+ type: "element",
700
+ tag: "li",
701
+ props: { class: "task-list-item" },
702
+ children: [
703
+ {
704
+ type: "element",
705
+ tag: "input",
706
+ props: {
707
+ type: "checkbox",
708
+ disabled: true,
709
+ ...checked ? { checked: true } : {}
710
+ },
711
+ children: []
712
+ },
713
+ {
714
+ type: "text",
715
+ value: " "
716
+ },
717
+ ...children
718
+ ]
719
+ };
695
720
  }
696
721
  function tableNodes(node, slideIndex, source) {
697
- const align = node.align ?? [];
698
- const [headerRow, ...bodyRows] = node.children ?? [];
699
- const headerResult = headerRow ? tableRowCells(headerRow, "th", align, slideIndex, source) : cellsResult([]);
700
- const bodyResults = bodyRows.map((row) => tableRowCells(row, "td", align, slideIndex, source));
701
- return nodeResult(
702
- [
703
- {
704
- type: "element",
705
- tag: "table",
706
- props: {},
707
- children: [
708
- {
709
- type: "element",
710
- tag: "thead",
711
- props: {},
712
- children: [{ type: "element", tag: "tr", props: {}, children: headerResult.cells }]
713
- },
714
- {
715
- type: "element",
716
- tag: "tbody",
717
- props: {},
718
- children: bodyResults.map(({ cells }) => ({ type: "element", tag: "tr", props: {}, children: cells }))
719
- }
720
- ]
721
- }
722
- ],
723
- [...headerResult.warnings, ...bodyResults.flatMap(({ warnings }) => warnings)]
724
- );
722
+ const align = node.align ?? [];
723
+ const [headerRow, ...bodyRows] = node.children ?? [];
724
+ const headerResult = headerRow ? tableRowCells(headerRow, "th", align, slideIndex, source) : cellsResult([]);
725
+ const bodyResults = bodyRows.map((row) => tableRowCells(row, "td", align, slideIndex, source));
726
+ return nodeResult([{
727
+ type: "element",
728
+ tag: "table",
729
+ props: {},
730
+ children: [{
731
+ type: "element",
732
+ tag: "thead",
733
+ props: {},
734
+ children: [{
735
+ type: "element",
736
+ tag: "tr",
737
+ props: {},
738
+ children: headerResult.cells
739
+ }]
740
+ }, {
741
+ type: "element",
742
+ tag: "tbody",
743
+ props: {},
744
+ children: bodyResults.map(({ cells }) => ({
745
+ type: "element",
746
+ tag: "tr",
747
+ props: {},
748
+ children: cells
749
+ }))
750
+ }]
751
+ }], [...headerResult.warnings, ...bodyResults.flatMap(({ warnings }) => warnings)]);
725
752
  }
726
753
  function cellsResult(cells, emittedWarnings = []) {
727
- return { cells, warnings: emittedWarnings };
754
+ return {
755
+ cells,
756
+ warnings: emittedWarnings
757
+ };
728
758
  }
729
759
  function tableRowCells(row, tag, align, slideIndex, source) {
730
- const results = (row.children ?? []).map((cell, cellIndex) => {
731
- const children = childrenToNodes(cell, slideIndex, source);
732
- const cellAlign = toTableAlign(align[cellIndex]);
733
- return {
734
- cell: {
735
- type: "element",
736
- tag,
737
- props: cellAlign ? { style: `text-align:${cellAlign}` } : {},
738
- children: children.nodes
739
- },
740
- warnings: children.warnings
741
- };
742
- });
743
- return cellsResult(
744
- results.map(({ cell }) => cell),
745
- results.flatMap(({ warnings }) => warnings)
746
- );
760
+ const results = (row.children ?? []).map((cell, cellIndex) => {
761
+ const children = childrenToNodes(cell, slideIndex, source);
762
+ const cellAlign = toTableAlign(align[cellIndex]);
763
+ return {
764
+ cell: {
765
+ type: "element",
766
+ tag,
767
+ props: cellAlign ? { style: `text-align:${cellAlign}` } : {},
768
+ children: children.nodes
769
+ },
770
+ warnings: children.warnings
771
+ };
772
+ });
773
+ return cellsResult(results.map(({ cell }) => cell), results.flatMap(({ warnings }) => warnings));
747
774
  }
748
775
  function jsxElementNodes(node, slideIndex, source) {
749
- const name = node.name ?? "";
750
- if (!name) return childrenToNodes(node, slideIndex, source);
751
- if (isUnsafeHtmlElement(name)) {
752
- return nodeResult([{ type: "text", value: sourceForNode(node, source) ?? textContent(node) }]);
753
- }
754
- const parsedProps = parseProps(node.attributes ?? [], slideIndex, name);
755
- const parsedChildren = childrenToNodes(node, slideIndex, source);
756
- const children = parsedChildren.nodes.filter((child) => child.type !== "text" || child.value.trim() !== "");
757
- const warnings = [...parsedProps.warnings, ...parsedChildren.warnings];
758
- if (isComponentName(name)) return nodeResult([{ type: "component", name, props: parsedProps.props, children }], warnings);
759
- return nodeResult([{ type: "element", tag: name, props: parsedProps.props, children }], warnings);
776
+ const name = node.name ?? "";
777
+ if (!name) return childrenToNodes(node, slideIndex, source);
778
+ if (isUnsafeHtmlElement(name)) return nodeResult([{
779
+ type: "text",
780
+ value: sourceForNode(node, source) ?? textContent(node)
781
+ }]);
782
+ const parsedProps = parseProps(node.attributes ?? [], slideIndex, name);
783
+ const parsedChildren = childrenToNodes(node, slideIndex, source);
784
+ const children = parsedChildren.nodes.filter((child) => child.type !== "text" || child.value.trim() !== "");
785
+ const warnings = [...parsedProps.warnings, ...parsedChildren.warnings];
786
+ if (isComponentName(name)) return nodeResult([{
787
+ type: "component",
788
+ name,
789
+ props: parsedProps.props,
790
+ children
791
+ }], warnings);
792
+ return nodeResult([{
793
+ type: "element",
794
+ tag: name,
795
+ props: parsedProps.props,
796
+ children
797
+ }], warnings);
760
798
  }
761
799
  function childrenToNodes(node, slideIndex, source) {
762
- const results = (node.children ?? []).map((child) => toNodes(child, slideIndex, source));
763
- return nodeResult(
764
- results.flatMap(({ nodes }) => nodes),
765
- results.flatMap(({ warnings }) => warnings)
766
- );
800
+ const results = (node.children ?? []).map((child) => toNodes(child, slideIndex, source));
801
+ return nodeResult(results.flatMap(({ nodes }) => nodes), results.flatMap(({ warnings }) => warnings));
767
802
  }
768
803
  function parseProps(attributes, slideIndex, componentName) {
769
- const props = {};
770
- const warnings = attributes.flatMap((attr) => {
771
- if (attr.type !== "mdxJsxAttribute" || !attr.name) return [];
772
- if (attr.value === null || attr.value === void 0) {
773
- props[attr.name] = true;
774
- return [];
775
- }
776
- if (typeof attr.value === "string" || typeof attr.value === "number" || typeof attr.value === "boolean") {
777
- props[attr.name] = attr.value;
778
- return [];
779
- }
780
- return [
781
- parserWarning(
782
- "mdx-expression-prop-ignored",
783
- `Slide ${slideIndex + 1}: MDX JavaScript expression props are ignored on ${ignoredPropTarget(componentName, attr.name)}.`,
784
- slideIndex
785
- )
786
- ];
787
- });
788
- return { props, warnings };
804
+ const props = {};
805
+ return {
806
+ props,
807
+ warnings: attributes.flatMap((attr) => {
808
+ if (attr.type !== "mdxJsxAttribute" || !attr.name) return [];
809
+ if (attr.value === null || attr.value === void 0) {
810
+ props[attr.name] = true;
811
+ return [];
812
+ }
813
+ if (typeof attr.value === "string" || typeof attr.value === "number" || typeof attr.value === "boolean") {
814
+ props[attr.name] = attr.value;
815
+ return [];
816
+ }
817
+ return [parserWarning("mdx-expression-prop-ignored", `Slide ${slideIndex + 1}: MDX JavaScript expression props are ignored on ${ignoredPropTarget(componentName, attr.name)}.`, slideIndex)];
818
+ })
819
+ };
789
820
  }
790
821
  function blockResult(blocks, emittedWarnings = []) {
791
- return { blocks, warnings: emittedWarnings };
822
+ return {
823
+ blocks,
824
+ warnings: emittedWarnings
825
+ };
792
826
  }
793
827
  function nodeResult(nodes, emittedWarnings = []) {
794
- return { nodes, warnings: emittedWarnings };
828
+ return {
829
+ nodes,
830
+ warnings: emittedWarnings
831
+ };
795
832
  }
796
833
  function parserWarning(code, message, slideIndex) {
797
- return { code, message, slideIndex };
834
+ return {
835
+ code,
836
+ message,
837
+ slideIndex
838
+ };
798
839
  }
799
840
  function ignoredPropTarget(componentName, propName) {
800
- if (propName.startsWith("$")) return `${componentName} dynamic prop`;
801
- return `${componentName}.${propName}`;
841
+ if (propName.startsWith("$")) return `${componentName} dynamic prop`;
842
+ return `${componentName}.${propName}`;
802
843
  }
803
844
  function textContent(node) {
804
- if (typeof node.value === "string") return node.value;
805
- if (node.type === "strong") return `**${(node.children ?? []).map(textContent).join("")}**`;
806
- if (node.type === "emphasis") return `*${(node.children ?? []).map(textContent).join("")}*`;
807
- if (node.type === "delete") return `~~${(node.children ?? []).map(textContent).join("")}~~`;
808
- if (node.type === "inlineCode") return `\`${node.value ?? ""}\``;
809
- if ((node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.name && !isComponentName(node.name)) {
810
- return `<${node.name}>${(node.children ?? []).map(textContent).join("")}</${node.name}>`;
811
- }
812
- return (node.children ?? []).map(textContent).join("");
845
+ if (typeof node.value === "string") return node.value;
846
+ if (node.type === "strong") return `**${(node.children ?? []).map(textContent).join("")}**`;
847
+ if (node.type === "emphasis") return `*${(node.children ?? []).map(textContent).join("")}*`;
848
+ if (node.type === "delete") return `~~${(node.children ?? []).map(textContent).join("")}~~`;
849
+ if (node.type === "inlineCode") return `\`${node.value ?? ""}\``;
850
+ if ((node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.name && !isComponentName(node.name)) return `<${node.name}>${(node.children ?? []).map(textContent).join("")}</${node.name}>`;
851
+ return (node.children ?? []).map(textContent).join("");
813
852
  }
814
853
  function taskListItemText(item) {
815
- const text = textContent(item).replace(/\s+/g, " ").trim();
816
- if (typeof item.checked === "boolean") return `${item.checked ? "[x]" : "[ ]"} ${text}`.trim();
817
- return text;
854
+ const text = textContent(item).replace(/\s+/g, " ").trim();
855
+ if (typeof item.checked === "boolean") return `${item.checked ? "[x]" : "[ ]"} ${text}`.trim();
856
+ return text;
818
857
  }
819
858
  function tableBlock(node) {
820
- const rows = (node.children ?? []).map(
821
- (row) => (row.children ?? []).map((cell) => textContent(cell).replace(/\s+/g, " ").trim())
822
- );
823
- const [header = [], ...bodyRows] = rows;
824
- return {
825
- type: "table",
826
- align: (node.align ?? []).map(toTableAlign),
827
- header,
828
- rows: bodyRows
829
- };
859
+ const [header = [], ...bodyRows] = (node.children ?? []).map((row) => (row.children ?? []).map((cell) => textContent(cell).replace(/\s+/g, " ").trim()));
860
+ return {
861
+ type: "table",
862
+ align: (node.align ?? []).map(toTableAlign),
863
+ header,
864
+ rows: bodyRows
865
+ };
830
866
  }
831
867
  function toTableAlign(value) {
832
- return value === "left" || value === "center" || value === "right" ? value : void 0;
868
+ return value === "left" || value === "center" || value === "right" ? value : void 0;
833
869
  }
834
870
  function soleImage(node) {
835
- const meaningful = (node.children ?? []).filter((child) => child.type !== "text" || child.value?.trim());
836
- return meaningful.length === 1 && meaningful[0].type === "image" ? meaningful[0] : void 0;
871
+ const meaningful = (node.children ?? []).filter((child) => child.type !== "text" || child.value?.trim());
872
+ return meaningful.length === 1 && meaningful[0].type === "image" ? meaningful[0] : void 0;
837
873
  }
838
874
  function isJsxOnlyParagraph(node) {
839
- const children = node.children ?? [];
840
- return children.length > 0 && children.every(
841
- (child) => child.type === "mdxJsxTextElement" || child.type === "mdxJsxFlowElement" || child.type === "text" && child.value?.trim() === ""
842
- );
875
+ const children = node.children ?? [];
876
+ return children.length > 0 && children.every((child) => child.type === "mdxJsxTextElement" || child.type === "mdxJsxFlowElement" || child.type === "text" && child.value?.trim() === "");
843
877
  }
844
878
  function sourceForNode(node, source) {
845
- const start = node.position?.start?.offset;
846
- const end = node.position?.end?.offset;
847
- return typeof start === "number" && typeof end === "number" ? source.slice(start, end) : void 0;
879
+ const start = node.position?.start?.offset;
880
+ const end = node.position?.end?.offset;
881
+ return typeof start === "number" && typeof end === "number" ? source.slice(start, end) : void 0;
848
882
  }
849
883
  function isComponentName(name) {
850
- return /^[A-Z]/.test(name);
884
+ return /^[A-Z]/.test(name);
851
885
  }
852
886
  function isUnsafeHtmlElement(name) {
853
- return /^(script|style|iframe|object|embed)$/i.test(name);
887
+ return /^(script|style|iframe|object|embed)$/i.test(name);
854
888
  }
855
889
  function headingDepth(value) {
856
- if (value === 1 || value === 2 || value === 3) return value;
857
- return 3;
890
+ if (value === 1 || value === 2 || value === 3) return value;
891
+ return 3;
858
892
  }
859
893
  function stringAttr(value) {
860
- return value && value.length > 0 ? value : void 0;
894
+ return value && value.length > 0 ? value : void 0;
861
895
  }
862
-
863
- // src/generator/mdx/assets.ts
896
+ //#endregion
897
+ //#region src/generator/mdx/assets.ts
864
898
  function rewriteAssetUrls(source, assets) {
865
- let result = source;
866
- for (const asset of assets) {
867
- const assetPath = localAssetRelativePath(asset.sourcePath);
868
- result = result.replaceAll(`./assets/${assetPath}`, asset.publicPath);
869
- result = result.replace(new RegExp(`(?<!/)assets/${escapeRegExp(assetPath)}`, "g"), asset.publicPath);
870
- }
871
- return result;
899
+ let result = source;
900
+ for (const asset of assets) {
901
+ const assetPath = localAssetRelativePath(asset.sourcePath);
902
+ result = result.replaceAll(`./assets/${assetPath}`, asset.publicPath);
903
+ result = result.replace(new RegExp(`(?<!/)assets/${escapeRegExp(assetPath)}`, "g"), asset.publicPath);
904
+ }
905
+ return result;
872
906
  }
873
907
  function rewriteRelativeMdxImports(source, sourceDir, generatedDir) {
874
- return source.replace(/(from\s+|import\s+)(["'])(\.[^"']+)\2/g, (_match, prefix, quote, specifier) => {
875
- const target = normalizePath2(`${sourceDir}/${specifier}`);
876
- return `${prefix}${quote}${toRelativeImportPath(generatedDir, target)}${quote}`;
877
- });
908
+ return source.replace(/(from\s+|import\s+)(["'])(\.[^"']+)\2/g, (_match, prefix, quote, specifier) => {
909
+ return `${prefix}${quote}${toRelativeImportPath(generatedDir, normalizePath$2(`${sourceDir}/${specifier}`))}${quote}`;
910
+ });
878
911
  }
879
912
  function componentImportPath(outDir, sourcePath) {
880
- if (!sourcePath) return void 0;
881
- const source = sourcePath.replace(/\/index\.(tsx|ts|jsx|js)$/, "");
882
- return toRelativeImportPath(outDir, source);
913
+ if (!sourcePath) return void 0;
914
+ return toRelativeImportPath(outDir, sourcePath.replace(/\/index\.(tsx|ts|jsx|js)$/, ""));
883
915
  }
884
- function dirname(path) {
885
- const normalized = normalizePath2(path);
886
- const index = normalized.lastIndexOf("/");
887
- return index === -1 ? "." : normalized.slice(0, index);
916
+ function dirname$1(path) {
917
+ const normalized = normalizePath$2(path);
918
+ const index = normalized.lastIndexOf("/");
919
+ return index === -1 ? "." : normalized.slice(0, index);
888
920
  }
889
921
  function localAssetRelativePath(sourcePath) {
890
- const marker = "/assets/";
891
- const normalized = normalizePath2(sourcePath);
892
- const markerIndex = normalized.indexOf(marker);
893
- return markerIndex === -1 ? normalized.split("/").at(-1) ?? normalized : normalized.slice(markerIndex + marker.length);
922
+ const marker = "/assets/";
923
+ const normalized = normalizePath$2(sourcePath);
924
+ const markerIndex = normalized.indexOf(marker);
925
+ return markerIndex === -1 ? normalized.split("/").at(-1) ?? normalized : normalized.slice(markerIndex + 8);
894
926
  }
895
- function normalizePath2(path) {
896
- return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
927
+ function normalizePath$2(path) {
928
+ return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
897
929
  }
898
930
  function toRelativeImportPath(fromDir, target) {
899
- const fromParts = normalizePath2(fromDir).split("/").filter(Boolean);
900
- const targetParts = normalizePath2(target).split("/").filter(Boolean);
901
- while (fromParts.length > 0 && targetParts.length > 0 && fromParts[0] === targetParts[0]) {
902
- fromParts.shift();
903
- targetParts.shift();
904
- }
905
- const relative4 = [...fromParts.map(() => ".."), ...targetParts].join("/");
906
- return relative4.startsWith(".") ? relative4 : `./${relative4 || "."}`;
931
+ const fromParts = normalizePath$2(fromDir).split("/").filter(Boolean);
932
+ const targetParts = normalizePath$2(target).split("/").filter(Boolean);
933
+ while (fromParts.length > 0 && targetParts.length > 0 && fromParts[0] === targetParts[0]) {
934
+ fromParts.shift();
935
+ targetParts.shift();
936
+ }
937
+ const relative = [...fromParts.map(() => ".."), ...targetParts].join("/");
938
+ return relative.startsWith(".") ? relative : `./${relative || "."}`;
907
939
  }
908
940
  function escapeRegExp(value) {
909
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
941
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
910
942
  }
911
-
912
- // src/generator/assets.ts
943
+ //#endregion
944
+ //#region src/generator/assets.ts
913
945
  async function buildAssetRefs(slug, assetPaths, input) {
914
- return Promise.all(
915
- assetPaths.map(async (sourcePath) => ({
916
- sourcePath,
917
- publicPath: `${normalizeMountPath(input.mountPath ?? `/${input.root}`)}/${encodeURIComponent(slug)}/assets/${assetName(
918
- sourcePath,
919
- input.root,
920
- slug
921
- )}`,
922
- type: "local",
923
- contentType: contentTypeForPath(sourcePath),
924
- body: input.readBinary ? await input.readBinary(sourcePath) : void 0
925
- }))
926
- );
946
+ return Promise.all(assetPaths.map(async (sourcePath) => ({
947
+ sourcePath,
948
+ publicPath: `${normalizeMountPath(input.mountPath ?? `/${input.root}`)}/${encodeURIComponent(slug)}/assets/${assetName(sourcePath, input.root, slug)}`,
949
+ type: "local",
950
+ contentType: contentTypeForPath(sourcePath),
951
+ body: input.readBinary ? await input.readBinary(sourcePath) : void 0
952
+ })));
927
953
  }
928
954
  function assetName(sourcePath, root, slug) {
929
- const normalizedPath = normalizePath3(sourcePath);
930
- const prefix = `${normalizePath3(root).replace(/\/$/, "")}/${slug}/assets/`;
931
- const relative4 = normalizedPath.startsWith(prefix) ? normalizedPath.slice(prefix.length) : normalizedPath.split("/").at(-1) ?? normalizedPath;
932
- return relative4.split("/").map(encodeURIComponent).join("/");
955
+ const normalizedPath = normalizePath$1(sourcePath);
956
+ const prefix = `${normalizePath$1(root).replace(/\/$/, "")}/${slug}/assets/`;
957
+ return (normalizedPath.startsWith(prefix) ? normalizedPath.slice(prefix.length) : normalizedPath.split("/").at(-1) ?? normalizedPath).split("/").map(encodeURIComponent).join("/");
933
958
  }
934
959
  function normalizeMountPath(value) {
935
- const withLeadingSlash = value.startsWith("/") ? value : `/${value}`;
936
- return withLeadingSlash.replace(/\/$/, "");
960
+ return (value.startsWith("/") ? value : `/${value}`).replace(/\/$/, "");
937
961
  }
938
- function normalizePath3(path) {
939
- return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
962
+ function normalizePath$1(path) {
963
+ return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
940
964
  }
941
-
942
- // src/generator/package-entry.ts
943
- var DECKS_RUNTIME_ENTRY = "hono-decks";
944
- var DECKS_ADVANCED_ENTRY = "hono-decks/advanced";
945
-
946
- // src/generator/mdx-module-generator.ts
947
- import { compile } from "@mdx-js/mdx";
948
- import remarkDirective from "remark-directive";
949
- import remarkGfm2 from "remark-gfm";
950
-
951
- // src/generator/mdx/emit.ts
965
+ //#endregion
966
+ //#region src/generator/package-entry.ts
967
+ const DECKS_RUNTIME_ENTRY = "hono-decks";
968
+ const DECKS_ADVANCED_ENTRY = "hono-decks/advanced";
969
+ //#endregion
970
+ //#region src/generator/mdx/emit.ts
952
971
  function emitModuleDecksRouter(input) {
953
- const runtimeEntry = JSON.stringify(DECKS_RUNTIME_ENTRY);
954
- const advancedEntry = JSON.stringify(DECKS_ADVANCED_ENTRY);
955
- const slideImports = input.decks.flatMap(
956
- (deck) => deck.slideModules.map(
957
- (slide, index) => `import ${slideImportName(deck.deck.slug, index)} from ${JSON.stringify(slide.importPath)};`
958
- )
959
- ).join("\n");
960
- const componentImports = input.decks.filter((deck) => deck.componentModulePath).map((deck) => `import * as ${componentImportName(deck.deck.slug)} from ${JSON.stringify(deck.componentModulePath)};`).join("\n");
961
- return `// @ts-nocheck
962
- import { configureDecks, defineDecks } from ${advancedEntry};
972
+ const runtimeEntry = JSON.stringify(DECKS_RUNTIME_ENTRY);
973
+ return `// @ts-nocheck
974
+ import { configureDecks, defineDecks } from ${JSON.stringify(DECKS_ADVANCED_ENTRY)};
963
975
  import type { ConfiguredDecks, DecksConfig } from ${runtimeEntry};
964
976
  import type { Env } from "hono";
965
977
  import { decksClientEntry } from "./client-entry";
966
- ${slideImports}
967
- ${componentImports}
978
+ ${input.decks.flatMap((deck) => deck.slideModules.map((slide, index) => `import ${slideImportName(deck.deck.slug, index)} from ${JSON.stringify(slide.importPath)};`)).join("\n")}
979
+ ${input.decks.filter((deck) => deck.componentModulePath).map((deck) => `import * as ${componentImportName(deck.deck.slug)} from ${JSON.stringify(deck.componentModulePath)};`).join("\n")}
968
980
 
969
981
  function withClientComponentIds(module, clientIds) {
970
982
  const registry = {};
@@ -998,589 +1010,510 @@ function definedDecksFor<E extends Env>() {
998
1010
  `;
999
1011
  }
1000
1012
  function emitDeckObject(deck) {
1001
- return ` {
1013
+ return ` {
1002
1014
  slug: ${JSON.stringify(deck.deck.slug)},
1003
1015
  sourcePath: ${JSON.stringify(deck.deck.sourcePath)},
1004
1016
  kind: ${JSON.stringify(deck.deck.kind)},
1005
1017
  meta: ${serializeValue(deck.deck.meta, 3)},
1006
- ${deck.deck.themeStyle ? ` "themeStyle": ${serializeValue(deck.deck.themeStyle, 3)},
1007
- ` : ""}${deck.deck.themeSourcePath ? ` "themeSourcePath": ${JSON.stringify(deck.deck.themeSourcePath)},
1008
- ` : ""}
1018
+ ${deck.deck.themeStyle ? ` "themeStyle": ${serializeValue(deck.deck.themeStyle, 3)},\n` : ""}${deck.deck.themeSourcePath ? ` "themeSourcePath": ${JSON.stringify(deck.deck.themeSourcePath)},\n` : ""}
1009
1019
  assets: ${serializeValue(deck.deck.assets, 3)},
1010
1020
  componentRegistry: ${deck.componentModulePath ? `withClientComponentIds(${componentImportName(deck.deck.slug)}, ${serializeValue(deck.clientComponentIds ?? {}, 3)})` : "{}"},
1011
1021
  warnings: ${serializeValue(deck.deck.warnings, 3)},
1012
1022
  slides: [
1013
- ${deck.deck.slides.map(
1014
- (slide) => ` {
1023
+ ${deck.deck.slides.map((slide) => ` {
1015
1024
  index: ${slide.index},
1016
1025
  meta: ${serializeValue(slide.meta, 5)},
1017
1026
  html: "",
1018
1027
  components: [],
1019
1028
  notes: ${serializeValue(slide.notes, 5)},
1020
1029
  render: ${slideImportName(deck.deck.slug, slide.index)}
1021
- }`
1022
- ).join(",\n")}
1030
+ }`).join(",\n")}
1023
1031
  ]
1024
1032
  }`;
1025
1033
  }
1026
1034
  function slideImportName(slug, index) {
1027
- return `Slide_${safeIdentifier(slug)}_${index}`;
1035
+ return `Slide_${safeIdentifier$1(slug)}_${index}`;
1028
1036
  }
1029
1037
  function componentImportName(slug) {
1030
- return `Components_${safeIdentifier(slug)}`;
1038
+ return `Components_${safeIdentifier$1(slug)}`;
1031
1039
  }
1032
- function safeIdentifier(value) {
1033
- return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
1040
+ function safeIdentifier$1(value) {
1041
+ return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
1034
1042
  }
1035
1043
  function serializeValue(value, depth) {
1036
- const indent = " ".repeat(depth);
1037
- const nextIndent = " ".repeat(depth + 1);
1038
- if (value === void 0) return "undefined";
1039
- if (value instanceof Uint8Array) return `new Uint8Array([${[...value].join(", ")}])`;
1040
- if (Array.isArray(value)) {
1041
- if (value.length === 0) return "[]";
1042
- return `[
1043
- ${value.map((item) => `${nextIndent}${serializeValue(item, depth + 1)}`).join(",\n")}
1044
- ${indent}]`;
1045
- }
1046
- if (typeof value === "object" && value !== null) {
1047
- const entries = Object.entries(value).filter(([, item]) => item !== void 0);
1048
- if (entries.length === 0) return "{}";
1049
- return `{
1050
- ${entries.map(([key, item]) => `${nextIndent}${JSON.stringify(key)}: ${serializeValue(item, depth + 1)}`).join(",\n")}
1051
- ${indent}}`;
1052
- }
1053
- return JSON.stringify(value);
1054
- }
1055
-
1056
- // src/generator/mdx/ogp.ts
1044
+ const indent = " ".repeat(depth);
1045
+ const nextIndent = " ".repeat(depth + 1);
1046
+ if (value === void 0) return "undefined";
1047
+ if (value instanceof Uint8Array) return `new Uint8Array([${[...value].join(", ")}])`;
1048
+ if (Array.isArray(value)) {
1049
+ if (value.length === 0) return "[]";
1050
+ return `[\n${value.map((item) => `${nextIndent}${serializeValue(item, depth + 1)}`).join(",\n")}\n${indent}]`;
1051
+ }
1052
+ if (typeof value === "object" && value !== null) {
1053
+ const entries = Object.entries(value).filter(([, item]) => item !== void 0);
1054
+ if (entries.length === 0) return "{}";
1055
+ return `{\n${entries.map(([key, item]) => `${nextIndent}${JSON.stringify(key)}: ${serializeValue(item, depth + 1)}`).join(",\n")}\n${indent}}`;
1056
+ }
1057
+ return JSON.stringify(value);
1058
+ }
1059
+ //#endregion
1060
+ //#region src/generator/mdx/ogp.ts
1057
1061
  async function resolveLinkCardMetadataByUrl(source, resolveOgp) {
1058
- const result = /* @__PURE__ */ new Map();
1059
- if (!resolveOgp) return result;
1060
- for (const url of collectLinkCardUrls(source)) {
1061
- try {
1062
- const metadata = await resolveOgp(url);
1063
- if (metadata) result.set(url, metadata);
1064
- } catch {
1065
- }
1066
- }
1067
- return result;
1062
+ const result = /* @__PURE__ */ new Map();
1063
+ if (!resolveOgp) return result;
1064
+ for (const url of collectLinkCardUrls(source)) try {
1065
+ const metadata = await resolveOgp(url);
1066
+ if (metadata) result.set(url, metadata);
1067
+ } catch {}
1068
+ return result;
1068
1069
  }
1069
1070
  function collectLinkCardUrls(source) {
1070
- const urls = /* @__PURE__ */ new Set();
1071
- const pattern = /@\[card\]\(([^)\s]+)\)/g;
1072
- for (const match of source.matchAll(pattern)) {
1073
- urls.add(match[1]);
1074
- }
1075
- return [...urls];
1071
+ const urls = /* @__PURE__ */ new Set();
1072
+ for (const match of source.matchAll(/@\[card\]\(([^)\s]+)\)/g)) urls.add(match[1]);
1073
+ return [...urls];
1076
1074
  }
1077
-
1078
- // src/generator/mdx/syntax.ts
1079
- import { codeToHtml } from "shiki";
1075
+ //#endregion
1076
+ //#region src/generator/mdx/syntax.ts
1080
1077
  function remarkDeckSyntax(input = {}) {
1081
- return () => (tree) => {
1082
- transformDeckSyntaxChildren(tree, input);
1083
- };
1078
+ return () => (tree) => {
1079
+ transformDeckSyntaxChildren(tree, input);
1080
+ };
1084
1081
  }
1085
1082
  function remarkCodeHighlight() {
1086
- return async (tree) => {
1087
- await highlightMarkdownNode(tree);
1088
- };
1083
+ return async (tree) => {
1084
+ await highlightMarkdownNode(tree);
1085
+ };
1089
1086
  }
1090
1087
  function transformDeckSyntaxChildren(node, input) {
1091
- if (!Array.isArray(node.children)) return;
1092
- const children = [];
1093
- for (const child of node.children) {
1094
- transformDeckSyntaxChildren(child, input);
1095
- rejectRemovedFireAuthoring(child);
1096
- children.push(
1097
- fireAttributeNode(child) ?? zennEmbedNode(child, input) ?? plainUrlLinkNode(child) ?? fireDirectiveNode(child) ?? unknownDirectiveFallback(child) ?? child
1098
- );
1099
- }
1100
- node.children = children;
1088
+ if (!Array.isArray(node.children)) return;
1089
+ const children = [];
1090
+ for (const child of node.children) {
1091
+ transformDeckSyntaxChildren(child, input);
1092
+ rejectRemovedFireAuthoring(child);
1093
+ children.push(fireAttributeNode(child) ?? zennEmbedNode(child, input) ?? plainUrlLinkNode(child) ?? fireDirectiveNode(child) ?? unknownDirectiveFallback(child) ?? child);
1094
+ }
1095
+ node.children = children;
1101
1096
  }
1102
1097
  function rejectRemovedFireAuthoring(node) {
1103
- if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return;
1104
- if (!Array.isArray(node.attributes)) return;
1105
- if (node.attributes.some((attribute) => attribute.name === "$fire")) {
1106
- throw new Error('The "$fire" prop is not supported. Use fire or fire="effect" on a block-level custom component.');
1107
- }
1108
- if (node.name === "Fire" && node.attributes.some((attribute) => attribute.name === "order")) {
1109
- throw new Error('The Fire "order" prop is not supported. Fires reveal in source order.');
1110
- }
1111
- if (node.name === "Fire") {
1112
- const atAttribute = node.attributes.find((attribute) => attribute.name === "at");
1113
- if (atAttribute) fireAtAttributeValue(atAttribute.value);
1114
- }
1098
+ if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return;
1099
+ if (!Array.isArray(node.attributes)) return;
1100
+ if (node.attributes.some((attribute) => attribute.name === "$fire")) throw new Error("The \"$fire\" prop is not supported. Use fire or fire=\"effect\" on a block-level custom component.");
1101
+ if (node.name === "Fire" && node.attributes.some((attribute) => attribute.name === "order")) throw new Error("The Fire \"order\" prop is not supported. Fires reveal in source order.");
1102
+ if (node.name === "Fire") {
1103
+ const atAttribute = node.attributes.find((attribute) => attribute.name === "at");
1104
+ if (atAttribute) fireAtAttributeValue(atAttribute.value);
1105
+ }
1115
1106
  }
1116
1107
  function fireAttributeNode(node) {
1117
- if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return void 0;
1118
- if (!Array.isArray(node.attributes)) return void 0;
1119
- const fireAttribute = node.attributes.find(
1120
- (attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "fire"
1121
- );
1122
- if (!fireAttribute) return void 0;
1123
- const atAttribute = node.attributes.find(
1124
- (attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "at"
1125
- );
1126
- if (node.name === "Fire") {
1127
- throw new Error('The "fire" attribute is not supported on <Fire>. Use the effect prop instead.');
1128
- }
1129
- if (!node.name || !/^[A-Z]/.test(node.name)) {
1130
- throw new Error('The "fire" attribute is only supported on custom components. Use :::fire for Markdown content.');
1131
- }
1132
- if (node.type !== "mdxJsxFlowElement") {
1133
- throw new Error('The "fire" attribute is only supported on block-level custom components. Move the component to its own line.');
1134
- }
1135
- if (fireAttribute.value !== null && fireAttribute.value !== void 0 && typeof fireAttribute.value !== "string") {
1136
- throw new Error('The "fire" attribute accepts no value or a static effect name such as fire="fade-up".');
1137
- }
1138
- const effect = typeof fireAttribute.value === "string" ? fireAttribute.value.trim() : "";
1139
- const at = atAttribute ? fireAtAttributeValue(atAttribute.value) : void 0;
1140
- node.attributes = node.attributes.filter((attribute) => attribute !== fireAttribute && attribute !== atAttribute);
1141
- return mdxElement(
1142
- "Fire",
1143
- [
1144
- ...effect ? [mdxAttribute("effect", effect)] : [],
1145
- ...at ? [mdxAttribute("at", at)] : []
1146
- ],
1147
- [node]
1148
- );
1108
+ if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return void 0;
1109
+ if (!Array.isArray(node.attributes)) return void 0;
1110
+ const fireAttribute = node.attributes.find((attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "fire");
1111
+ if (!fireAttribute) return void 0;
1112
+ const atAttribute = node.attributes.find((attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "at");
1113
+ if (node.name === "Fire") throw new Error("The \"fire\" attribute is not supported on <Fire>. Use the effect prop instead.");
1114
+ if (!node.name || !/^[A-Z]/.test(node.name)) throw new Error("The \"fire\" attribute is only supported on custom components. Use :::fire for Markdown content.");
1115
+ if (node.type !== "mdxJsxFlowElement") throw new Error("The \"fire\" attribute is only supported on block-level custom components. Move the component to its own line.");
1116
+ if (fireAttribute.value !== null && fireAttribute.value !== void 0 && typeof fireAttribute.value !== "string") throw new Error("The \"fire\" attribute accepts no value or a static effect name such as fire=\"fade-up\".");
1117
+ const effect = typeof fireAttribute.value === "string" ? fireAttribute.value.trim() : "";
1118
+ const at = atAttribute ? fireAtAttributeValue(atAttribute.value) : void 0;
1119
+ node.attributes = node.attributes.filter((attribute) => attribute !== fireAttribute && attribute !== atAttribute);
1120
+ return mdxElement("Fire", [...effect ? [mdxAttribute("effect", effect)] : [], ...at ? [mdxAttribute("at", at)] : []], [node]);
1149
1121
  }
1150
1122
  function fireAtAttributeValue(value) {
1151
- if (typeof value === "string") {
1152
- const at = value.trim();
1153
- if (/^(?:\d+|[+-]\d+)$/.test(at)) return at;
1154
- }
1155
- throw new Error('The fire "at" attribute accepts a non-negative integer or a relative value such as "+1".');
1123
+ if (typeof value === "string") {
1124
+ const at = value.trim();
1125
+ if (/^(?:\d+|[+-]\d+)$/.test(at)) return at;
1126
+ }
1127
+ throw new Error("The fire \"at\" attribute accepts a non-negative integer or a relative value such as \"+1\".");
1156
1128
  }
1157
1129
  function zennEmbedNode(node, input) {
1158
- if (node.type !== "paragraph" || !Array.isArray(node.children) || node.children.length !== 2) return void 0;
1159
- const [prefix, link] = node.children;
1160
- if (prefix?.type !== "text" || String(prefix.value ?? "").trim() !== "@") return void 0;
1161
- if (link?.type !== "link" || typeof link.url !== "string") return void 0;
1162
- const name = collectMarkdownText(link).trim().toLowerCase();
1163
- if (name === "youtube") {
1164
- return mdxElement(
1165
- "EmbedFrame",
1166
- [
1167
- mdxAttribute("provider", "youtube"),
1168
- mdxAttribute("src", toYoutubeEmbedUrl(link.url)),
1169
- mdxAttribute("fallbackHref", link.url),
1170
- mdxAttribute("title", "YouTube embed example")
1171
- ],
1172
- [{ type: "text", value: "Open YouTube embed" }]
1173
- );
1174
- }
1175
- if (name === "x") {
1176
- return mdxElement("TweetEmbed", [mdxAttribute("href", link.url), mdxAttribute("label", "Open post on X")], []);
1177
- }
1178
- if (name === "card") {
1179
- const metadata = input.linkCardMetadata?.get(link.url);
1180
- return mdxElement(
1181
- "LinkCard",
1182
- [
1183
- mdxAttribute("href", link.url),
1184
- ...metadataAttributes(metadata)
1185
- ],
1186
- []
1187
- );
1188
- }
1189
- if (name === "embed" || name === "iframe") {
1190
- return mdxElement(
1191
- "EmbedFrame",
1192
- [mdxAttribute("src", link.url), mdxAttribute("title", "Embedded content")],
1193
- [{ type: "text", value: "Open embed" }]
1194
- );
1195
- }
1196
- return void 0;
1130
+ if (node.type !== "paragraph" || !Array.isArray(node.children) || node.children.length !== 2) return void 0;
1131
+ const [prefix, link] = node.children;
1132
+ if (prefix?.type !== "text" || String(prefix.value ?? "").trim() !== "@") return void 0;
1133
+ if (link?.type !== "link" || typeof link.url !== "string") return void 0;
1134
+ const name = collectMarkdownText(link).trim().toLowerCase();
1135
+ if (name === "youtube") return mdxElement("EmbedFrame", [
1136
+ mdxAttribute("provider", "youtube"),
1137
+ mdxAttribute("src", toYoutubeEmbedUrl(link.url)),
1138
+ mdxAttribute("fallbackHref", link.url),
1139
+ mdxAttribute("title", "YouTube embed example")
1140
+ ], [{
1141
+ type: "text",
1142
+ value: "Open YouTube embed"
1143
+ }]);
1144
+ if (name === "x") return mdxElement("TweetEmbed", [mdxAttribute("href", link.url), mdxAttribute("label", "Open post on X")], []);
1145
+ if (name === "card") {
1146
+ const metadata = input.linkCardMetadata?.get(link.url);
1147
+ return mdxElement("LinkCard", [mdxAttribute("href", link.url), ...metadataAttributes(metadata)], []);
1148
+ }
1149
+ if (name === "embed" || name === "iframe") return mdxElement("EmbedFrame", [mdxAttribute("src", link.url), mdxAttribute("title", "Embedded content")], [{
1150
+ type: "text",
1151
+ value: "Open embed"
1152
+ }]);
1197
1153
  }
1198
1154
  function metadataAttributes(metadata) {
1199
- if (!metadata) return [];
1200
- return [
1201
- ...metadata.title ? [mdxAttribute("title", metadata.title)] : [],
1202
- ...metadata.description ? [mdxAttribute("description", metadata.description)] : [],
1203
- ...metadata.image ? [mdxAttribute("image", metadata.image)] : [],
1204
- ...metadata.siteName ? [mdxAttribute("siteName", metadata.siteName)] : []
1205
- ];
1155
+ if (!metadata) return [];
1156
+ return [
1157
+ ...metadata.title ? [mdxAttribute("title", metadata.title)] : [],
1158
+ ...metadata.description ? [mdxAttribute("description", metadata.description)] : [],
1159
+ ...metadata.image ? [mdxAttribute("image", metadata.image)] : [],
1160
+ ...metadata.siteName ? [mdxAttribute("siteName", metadata.siteName)] : []
1161
+ ];
1206
1162
  }
1207
1163
  function plainUrlLinkNode(node) {
1208
- if (node.type !== "paragraph" || !Array.isArray(node.children) || node.children.length !== 1) return void 0;
1209
- const [child] = node.children;
1210
- if (child?.type !== "text" || typeof child.value !== "string") return void 0;
1211
- const value = child.value.trim();
1212
- if (!isHttpUrl(value)) return void 0;
1213
- return {
1214
- type: "paragraph",
1215
- children: [
1216
- {
1217
- type: "link",
1218
- url: value,
1219
- children: [{ type: "text", value }]
1220
- }
1221
- ]
1222
- };
1223
- }
1224
- function isHttpUrl(value) {
1225
- try {
1226
- const url = new URL(value);
1227
- return url.protocol === "https:" || url.protocol === "http:";
1228
- } catch {
1229
- return false;
1230
- }
1164
+ if (node.type !== "paragraph" || !Array.isArray(node.children) || node.children.length !== 1) return void 0;
1165
+ const [child] = node.children;
1166
+ if (child?.type !== "text" || typeof child.value !== "string") return void 0;
1167
+ const value = child.value.trim();
1168
+ if (!isHttpUrl$1(value)) return void 0;
1169
+ return {
1170
+ type: "paragraph",
1171
+ children: [{
1172
+ type: "link",
1173
+ url: value,
1174
+ children: [{
1175
+ type: "text",
1176
+ value
1177
+ }]
1178
+ }]
1179
+ };
1180
+ }
1181
+ function isHttpUrl$1(value) {
1182
+ try {
1183
+ const url = new URL(value);
1184
+ return url.protocol === "https:" || url.protocol === "http:";
1185
+ } catch {
1186
+ return false;
1187
+ }
1231
1188
  }
1232
1189
  function fireDirectiveNode(node) {
1233
- if (node.type !== "containerDirective" || node.name !== "fire") return void 0;
1234
- const attributes = directiveAttributes(node);
1235
- if (attributes.order !== void 0) {
1236
- throw new Error('The fire "order" attribute is not supported. Fires reveal in source order.');
1237
- }
1238
- if (attributes.each === void 0 && (attributes.depth !== void 0 || attributes.every !== void 0)) {
1239
- throw new Error('The fire "depth" and "every" attributes require each="item".');
1240
- }
1241
- if (attributes.each !== void 0) return fireEachItemNode(node, attributes);
1242
- const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
1243
- const fireAttributes = [
1244
- ...typeof attributes.effect === "string" ? [mdxAttribute("effect", attributes.effect)] : [],
1245
- ...at ? [mdxAttribute("at", at)] : []
1246
- ];
1247
- return mdxElement("Fire", fireAttributes, node.children ?? []);
1190
+ if (node.type !== "containerDirective" || node.name !== "fire") return void 0;
1191
+ const attributes = directiveAttributes(node);
1192
+ if (attributes.order !== void 0) throw new Error("The fire \"order\" attribute is not supported. Fires reveal in source order.");
1193
+ if (attributes.each === void 0 && (attributes.depth !== void 0 || attributes.every !== void 0)) throw new Error("The fire \"depth\" and \"every\" attributes require each=\"item\".");
1194
+ if (attributes.each !== void 0) return fireEachItemNode(node, attributes);
1195
+ const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
1196
+ return mdxElement("Fire", [...typeof attributes.effect === "string" ? [mdxAttribute("effect", attributes.effect)] : [], ...at ? [mdxAttribute("at", at)] : []], node.children ?? []);
1248
1197
  }
1249
1198
  function fireEachItemNode(node, attributes) {
1250
- if (attributes.each !== "item") {
1251
- throw new Error('The fire "each" attribute only accepts "item".');
1252
- }
1253
- const children = node.children ?? [];
1254
- const list = children.length === 1 && children[0]?.type === "list" ? children[0] : void 0;
1255
- if (!list) {
1256
- throw new Error('fire each="item" must contain exactly one Markdown list.');
1257
- }
1258
- const depth = positiveFireInteger(attributes.depth, "depth", 1);
1259
- const every = positiveFireInteger(attributes.every, "every", 1);
1260
- const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
1261
- const effect = attributes.effect ? fireEffectToken(attributes.effect) : void 0;
1262
- const items = fireListItems(list, depth);
1263
- for (const [itemIndex, item] of items.entries()) {
1264
- const itemAt = fireListItemAt(at, every, itemIndex);
1265
- item.data = {
1266
- ...item.data,
1267
- hProperties: {
1268
- ...item.data?.hProperties,
1269
- "data-hono-decks-fire": "true",
1270
- ...itemAt ? { "data-fire-at": itemAt } : {},
1271
- ...effect ? { "data-fire-effect": effect } : {}
1272
- }
1273
- };
1274
- }
1275
- return list;
1199
+ if (attributes.each !== "item") throw new Error("The fire \"each\" attribute only accepts \"item\".");
1200
+ const children = node.children ?? [];
1201
+ const list = children.length === 1 && children[0]?.type === "list" ? children[0] : void 0;
1202
+ if (!list) throw new Error("fire each=\"item\" must contain exactly one Markdown list.");
1203
+ const depth = positiveFireInteger(attributes.depth, "depth", 1);
1204
+ const every = positiveFireInteger(attributes.every, "every", 1);
1205
+ const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
1206
+ const effect = attributes.effect ? fireEffectToken(attributes.effect) : void 0;
1207
+ const items = fireListItems(list, depth);
1208
+ for (const [itemIndex, item] of items.entries()) {
1209
+ const itemAt = fireListItemAt(at, every, itemIndex);
1210
+ item.data = {
1211
+ ...item.data,
1212
+ hProperties: {
1213
+ ...item.data?.hProperties,
1214
+ "data-hono-decks-fire": "true",
1215
+ ...itemAt ? { "data-fire-at": itemAt } : {},
1216
+ ...effect ? { "data-fire-effect": effect } : {}
1217
+ }
1218
+ };
1219
+ }
1220
+ return list;
1276
1221
  }
1277
1222
  function positiveFireInteger(value, name, fallback) {
1278
- if (value === void 0) return fallback;
1279
- const parsed = Number(value);
1280
- if (Number.isInteger(parsed) && parsed > 0) return parsed;
1281
- throw new Error(`The fire "${name}" attribute accepts a positive integer.`);
1223
+ if (value === void 0) return fallback;
1224
+ const parsed = Number(value);
1225
+ if (Number.isInteger(parsed) && parsed > 0) return parsed;
1226
+ throw new Error(`The fire "${name}" attribute accepts a positive integer.`);
1282
1227
  }
1283
1228
  function fireListItems(list, maxDepth, depth = 1) {
1284
- const items = [];
1285
- for (const item of list.children ?? []) {
1286
- if (item.type !== "listItem") continue;
1287
- items.push(item);
1288
- if (depth >= maxDepth) continue;
1289
- for (const child of item.children ?? []) {
1290
- if (child.type === "list") items.push(...fireListItems(child, maxDepth, depth + 1));
1291
- }
1292
- }
1293
- return items;
1229
+ const items = [];
1230
+ for (const item of list.children ?? []) {
1231
+ if (item.type !== "listItem") continue;
1232
+ items.push(item);
1233
+ if (depth >= maxDepth) continue;
1234
+ for (const child of item.children ?? []) if (child.type === "list") items.push(...fireListItems(child, maxDepth, depth + 1));
1235
+ }
1236
+ return items;
1294
1237
  }
1295
1238
  function fireListItemAt(at, every, itemIndex) {
1296
- if (at && /^\d+$/.test(at)) return String(Number(at) + Math.floor(itemIndex / every));
1297
- if (!at && every === 1) return void 0;
1298
- if (itemIndex === 0) return at;
1299
- return itemIndex % every === 0 ? "+1" : "+0";
1239
+ if (at && /^\d+$/.test(at)) return String(Number(at) + Math.floor(itemIndex / every));
1240
+ if (!at && every === 1) return void 0;
1241
+ if (itemIndex === 0) return at;
1242
+ return itemIndex % every === 0 ? "+1" : "+0";
1300
1243
  }
1301
1244
  function fireEffectToken(value) {
1302
- return value.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "fade";
1245
+ return value.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "fade";
1303
1246
  }
1304
1247
  function unknownDirectiveFallback(node) {
1305
- if (node.type === "textDirective") return { type: "text", value: `:${node.name ?? ""}` };
1306
- if (node.type === "leafDirective") return { type: "text", value: `::${node.name ?? ""}` };
1307
- if (node.type === "containerDirective") {
1308
- return {
1309
- type: "paragraph",
1310
- children: [{ type: "text", value: `:::${node.name ?? ""}` }, ...node.children ?? []]
1311
- };
1312
- }
1313
- return void 0;
1248
+ if (node.type === "textDirective") return {
1249
+ type: "text",
1250
+ value: `:${node.name ?? ""}`
1251
+ };
1252
+ if (node.type === "leafDirective") return {
1253
+ type: "text",
1254
+ value: `::${node.name ?? ""}`
1255
+ };
1256
+ if (node.type === "containerDirective") return {
1257
+ type: "paragraph",
1258
+ children: [{
1259
+ type: "text",
1260
+ value: `:::${node.name ?? ""}`
1261
+ }, ...node.children ?? []]
1262
+ };
1314
1263
  }
1315
1264
  function directiveAttributes(node) {
1316
- if (!node.attributes || Array.isArray(node.attributes)) return {};
1317
- const result = {};
1318
- for (const [key, value] of Object.entries(node.attributes)) {
1319
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1320
- result[key] = String(value);
1321
- }
1322
- }
1323
- return result;
1265
+ if (!node.attributes || Array.isArray(node.attributes)) return {};
1266
+ const result = {};
1267
+ for (const [key, value] of Object.entries(node.attributes)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") result[key] = String(value);
1268
+ return result;
1324
1269
  }
1325
1270
  function mdxElement(name, attributes, children) {
1326
- return {
1327
- type: "mdxJsxFlowElement",
1328
- name,
1329
- attributes,
1330
- children
1331
- };
1271
+ return {
1272
+ type: "mdxJsxFlowElement",
1273
+ name,
1274
+ attributes,
1275
+ children
1276
+ };
1332
1277
  }
1333
1278
  function toYoutubeEmbedUrl(value) {
1334
- try {
1335
- const url = new URL(value);
1336
- const host = url.hostname.replace(/^www\./, "");
1337
- if (host === "youtu.be") {
1338
- const id = url.pathname.split("/").filter(Boolean)[0];
1339
- return id ? `https://www.youtube.com/embed/${id}` : value;
1340
- }
1341
- if (host === "youtube.com" || host === "m.youtube.com") {
1342
- if (url.pathname.startsWith("/embed/")) return value;
1343
- const id = url.searchParams.get("v");
1344
- return id ? `https://www.youtube.com/embed/${id}` : value;
1345
- }
1346
- return value;
1347
- } catch {
1348
- return value;
1349
- }
1279
+ try {
1280
+ const url = new URL(value);
1281
+ const host = url.hostname.replace(/^www\./, "");
1282
+ if (host === "youtu.be") {
1283
+ const id = url.pathname.split("/").filter(Boolean)[0];
1284
+ return id ? `https://www.youtube.com/embed/${id}` : value;
1285
+ }
1286
+ if (host === "youtube.com" || host === "m.youtube.com") {
1287
+ if (url.pathname.startsWith("/embed/")) return value;
1288
+ const id = url.searchParams.get("v");
1289
+ return id ? `https://www.youtube.com/embed/${id}` : value;
1290
+ }
1291
+ return value;
1292
+ } catch {
1293
+ return value;
1294
+ }
1350
1295
  }
1351
1296
  async function highlightMarkdownNode(node) {
1352
- if (node.type === "code") {
1353
- const code = typeof node.value === "string" ? node.value : "";
1354
- const lang = typeof node.lang === "string" && node.lang ? node.lang : void 0;
1355
- const highlightedHtml = await highlightCodeBlock(code, lang);
1356
- node.type = "mdxJsxFlowElement";
1357
- node.name = "CodeBlock";
1358
- node.attributes = [
1359
- ...lang ? [mdxAttribute("lang", lang)] : [],
1360
- mdxAttribute("highlightedHtml", highlightedHtml)
1361
- ];
1362
- node.children = [{ type: "text", value: code }];
1363
- delete node.value;
1364
- delete node.lang;
1365
- delete node.meta;
1366
- return;
1367
- }
1368
- if (node.type === "mdxJsxFlowElement" && node.name === "CodeBlock") {
1369
- const code = collectMarkdownText(node);
1370
- if (code.trim()) {
1371
- const lang = getMdxStringAttribute(node, "lang");
1372
- const highlightedHtml = await highlightCodeBlock(code, lang);
1373
- node.attributes = upsertMdxStringAttribute(node.attributes, "highlightedHtml", highlightedHtml);
1374
- }
1375
- return;
1376
- }
1377
- if (!Array.isArray(node.children)) return;
1378
- await Promise.all(node.children.map((child) => highlightMarkdownNode(child)));
1297
+ if (node.type === "code") {
1298
+ const code = typeof node.value === "string" ? node.value : "";
1299
+ const lang = typeof node.lang === "string" && node.lang ? node.lang : void 0;
1300
+ const highlightedHtml = await highlightCodeBlock(code, lang);
1301
+ node.type = "mdxJsxFlowElement";
1302
+ node.name = "CodeBlock";
1303
+ node.attributes = [...lang ? [mdxAttribute("lang", lang)] : [], mdxAttribute("highlightedHtml", highlightedHtml)];
1304
+ node.children = [{
1305
+ type: "text",
1306
+ value: code
1307
+ }];
1308
+ delete node.value;
1309
+ delete node.lang;
1310
+ delete node.meta;
1311
+ return;
1312
+ }
1313
+ if (node.type === "mdxJsxFlowElement" && node.name === "CodeBlock") {
1314
+ const code = collectMarkdownText(node);
1315
+ if (code.trim()) {
1316
+ const highlightedHtml = await highlightCodeBlock(code, getMdxStringAttribute(node, "lang"));
1317
+ node.attributes = upsertMdxStringAttribute(node.attributes, "highlightedHtml", highlightedHtml);
1318
+ }
1319
+ return;
1320
+ }
1321
+ if (!Array.isArray(node.children)) return;
1322
+ await Promise.all(node.children.map((child) => highlightMarkdownNode(child)));
1379
1323
  }
1380
1324
  async function highlightCodeBlock(code, lang) {
1381
- const language = lang && /^[A-Za-z0-9_#+.-]+$/.test(lang) ? lang : "text";
1382
- try {
1383
- return await codeToHtml(code, { lang: language, theme: "github-dark" });
1384
- } catch (error) {
1385
- if (language === "text") throw error;
1386
- return codeToHtml(code, { lang: "text", theme: "github-dark" });
1387
- }
1325
+ const language = lang && /^[A-Za-z0-9_#+.-]+$/.test(lang) ? lang : "text";
1326
+ try {
1327
+ return await codeToHtml(code, {
1328
+ lang: language,
1329
+ theme: "github-dark"
1330
+ });
1331
+ } catch (error) {
1332
+ if (language === "text") throw error;
1333
+ return codeToHtml(code, {
1334
+ lang: "text",
1335
+ theme: "github-dark"
1336
+ });
1337
+ }
1388
1338
  }
1389
1339
  function mdxAttribute(name, value) {
1390
- return { type: "mdxJsxAttribute", name, value };
1340
+ return {
1341
+ type: "mdxJsxAttribute",
1342
+ name,
1343
+ value
1344
+ };
1391
1345
  }
1392
1346
  function getMdxStringAttribute(node, name) {
1393
- const attributes = Array.isArray(node.attributes) ? node.attributes : [];
1394
- const attribute = attributes.find((item) => item.type === "mdxJsxAttribute" && item.name === name);
1395
- return typeof attribute?.value === "string" ? attribute.value : void 0;
1347
+ const attribute = (Array.isArray(node.attributes) ? node.attributes : []).find((item) => item.type === "mdxJsxAttribute" && item.name === name);
1348
+ return typeof attribute?.value === "string" ? attribute.value : void 0;
1396
1349
  }
1397
1350
  function upsertMdxStringAttribute(attributes, name, value) {
1398
- const next = Array.isArray(attributes) ? [...attributes] : [];
1399
- const index = next.findIndex((item) => item.type === "mdxJsxAttribute" && item.name === name);
1400
- const attribute = mdxAttribute(name, value);
1401
- if (index === -1) return [...next, attribute];
1402
- next[index] = attribute;
1403
- return next;
1351
+ const next = Array.isArray(attributes) ? [...attributes] : [];
1352
+ const index = next.findIndex((item) => item.type === "mdxJsxAttribute" && item.name === name);
1353
+ const attribute = mdxAttribute(name, value);
1354
+ if (index === -1) return [...next, attribute];
1355
+ next[index] = attribute;
1356
+ return next;
1404
1357
  }
1405
1358
  function collectMarkdownText(node) {
1406
- if (typeof node.value === "string") return node.value;
1407
- if (!Array.isArray(node.children)) return "";
1408
- return node.children.map((child) => collectMarkdownText(child)).join(node.type === "paragraph" ? "\n" : "");
1359
+ if (typeof node.value === "string") return node.value;
1360
+ if (!Array.isArray(node.children)) return "";
1361
+ return node.children.map((child) => collectMarkdownText(child)).join(node.type === "paragraph" ? "\n" : "");
1409
1362
  }
1410
-
1411
- // src/generator/mdx-module-generator.ts
1363
+ //#endregion
1364
+ //#region src/generator/mdx-module-generator.ts
1412
1365
  async function compileMdxModuleDecks(input) {
1413
- const decks = await Promise.all(input.decks.map((entry) => compileMdxModuleDeck(input, entry)));
1414
- return {
1415
- decks,
1416
- routerModule: emitModuleDecksRouter({ decks })
1417
- };
1366
+ const decks = await Promise.all(input.decks.map((entry) => compileMdxModuleDeck(input, entry)));
1367
+ return {
1368
+ decks,
1369
+ routerModule: emitModuleDecksRouter({ decks })
1370
+ };
1418
1371
  }
1419
1372
  async function compileMdxModuleDeck(input, entry) {
1420
- const source = await input.readText(entry.sourcePath);
1421
- const { attrs, body } = readFrontmatter(source);
1422
- const { prelude, body: contentBody } = extractDeckPrelude(body);
1423
- const slideSources = splitSlideSources(contentBody);
1424
- let warnings = [];
1425
- const deckMeta = toDeckFrontmatter(attrs, warnings);
1426
- addUnknownFrontmatterWarnings(warnings, deckMeta.meta, "deck");
1427
- const assets = [
1428
- ...await buildAssetRefs(entry.slug, entry.assetPaths, input),
1429
- ...collectGeneratedExternalAssetRefs(contentBody, deckMeta)
1430
- ];
1431
- const componentModulePath = componentImportPath(input.outDir, input.componentModulePaths?.[entry.slug]);
1432
- const themeStyle = entry.kind === "directory" ? input.themeStyles?.[entry.slug] : void 0;
1433
- const slideModules = [];
1434
- const slides = [];
1435
- for (let index = 0; index < slideSources.length; index += 1) {
1436
- const { attrs: slideAttrs, body: slideBody } = readFrontmatter(slideSources[index]);
1437
- const speakerNotes = extractMdxCommentSpeakerNotes(slideBody);
1438
- const parsed = parseDeckWithWarnings(speakerNotes.body);
1439
- warnings = warnings.concat(toGeneratedParserCompileWarnings(parsed.warnings, index));
1440
- const firstParsedSlide = parsed.slides[0];
1441
- const slideModulePath = `${input.outDir}/decks/${entry.slug}/slide-${index}.ts`;
1442
- const slideMeta = toSlideFrontmatter(slideAttrs, warnings, {
1443
- slideIndex: index,
1444
- fallbackTransition: deckMeta.transition,
1445
- fallbackTransitionDuration: deckMeta.transitionDuration,
1446
- fallbackTransitionEasing: deckMeta.transitionEasing,
1447
- fallbackTitle: firstParsedSlide?.title,
1448
- fallbackLayout: firstParsedSlide?.layout,
1449
- fallbackClassName: firstParsedSlide?.className
1450
- });
1451
- addUnknownFrontmatterWarnings(warnings, slideMeta.meta, "slide", index);
1452
- const moduleSource = [prelude, rewriteAssetUrls(speakerNotes.body, assets)].filter(Boolean).join("\n\n");
1453
- const rewrittenSource = rewriteRelativeMdxImports(moduleSource, dirname(entry.sourcePath), dirname(slideModulePath));
1454
- const code = await compileMdxModule(rewrittenSource, entry.sourcePath, index, input.resolveOgp);
1455
- slideModules.push({
1456
- path: slideModulePath,
1457
- importPath: `./decks/${entry.slug}/slide-${index}`,
1458
- code
1459
- });
1460
- slides.push({
1461
- index,
1462
- meta: slideMeta,
1463
- html: "",
1464
- components: [],
1465
- notes: combineSpeakerNotes(slideMeta.notes, speakerNotes.notes)
1466
- });
1467
- }
1468
- addExternalAssetWarnings(warnings, assets);
1469
- return {
1470
- componentModulePath,
1471
- clientComponentIds: input.clientComponentIds?.[entry.slug],
1472
- deck: {
1473
- slug: entry.slug,
1474
- sourcePath: entry.sourcePath,
1475
- kind: entry.kind,
1476
- meta: deckMeta,
1477
- themeStyle: themeStyle?.style,
1478
- themeSourcePath: themeStyle?.sourcePath,
1479
- slides,
1480
- assets,
1481
- warnings
1482
- },
1483
- slideModules
1484
- };
1373
+ const { attrs, body } = readFrontmatter(await input.readText(entry.sourcePath));
1374
+ const { prelude, body: contentBody } = extractDeckPrelude(body);
1375
+ const slideSources = splitSlideSources(contentBody);
1376
+ let warnings = [];
1377
+ const deckMeta = toDeckFrontmatter(attrs, warnings);
1378
+ addUnknownFrontmatterWarnings(warnings, deckMeta.meta, "deck");
1379
+ const assets = [...await buildAssetRefs(entry.slug, entry.assetPaths, input), ...collectGeneratedExternalAssetRefs(contentBody, deckMeta)];
1380
+ const componentModulePath = componentImportPath(input.outDir, input.componentModulePaths?.[entry.slug]);
1381
+ const themeStyle = entry.kind === "directory" ? input.themeStyles?.[entry.slug] : void 0;
1382
+ const slideModules = [];
1383
+ const slides = [];
1384
+ for (let index = 0; index < slideSources.length; index += 1) {
1385
+ const { attrs: slideAttrs, body: slideBody } = readFrontmatter(slideSources[index]);
1386
+ const speakerNotes = extractMdxCommentSpeakerNotes(slideBody);
1387
+ const parsed = parseDeckWithWarnings(speakerNotes.body);
1388
+ warnings = warnings.concat(toGeneratedParserCompileWarnings(parsed.warnings, index));
1389
+ const firstParsedSlide = parsed.slides[0];
1390
+ const slideModulePath = `${input.outDir}/decks/${entry.slug}/slide-${index}.ts`;
1391
+ const slideMeta = toSlideFrontmatter(slideAttrs, warnings, {
1392
+ slideIndex: index,
1393
+ fallbackTransition: deckMeta.transition,
1394
+ fallbackTransitionDuration: deckMeta.transitionDuration,
1395
+ fallbackTransitionEasing: deckMeta.transitionEasing,
1396
+ fallbackTitle: firstParsedSlide?.title,
1397
+ fallbackLayout: firstParsedSlide?.layout,
1398
+ fallbackClassName: firstParsedSlide?.className
1399
+ });
1400
+ addUnknownFrontmatterWarnings(warnings, slideMeta.meta, "slide", index);
1401
+ const code = await compileMdxModule(rewriteRelativeMdxImports([prelude, rewriteAssetUrls(speakerNotes.body, assets)].filter(Boolean).join("\n\n"), dirname$1(entry.sourcePath), dirname$1(slideModulePath)), entry.sourcePath, index, input.resolveOgp);
1402
+ slideModules.push({
1403
+ path: slideModulePath,
1404
+ importPath: `./decks/${entry.slug}/slide-${index}`,
1405
+ code
1406
+ });
1407
+ slides.push({
1408
+ index,
1409
+ meta: slideMeta,
1410
+ html: "",
1411
+ components: [],
1412
+ notes: combineSpeakerNotes(slideMeta.notes, speakerNotes.notes)
1413
+ });
1414
+ }
1415
+ addExternalAssetWarnings(warnings, assets);
1416
+ return {
1417
+ componentModulePath,
1418
+ clientComponentIds: input.clientComponentIds?.[entry.slug],
1419
+ deck: {
1420
+ slug: entry.slug,
1421
+ sourcePath: entry.sourcePath,
1422
+ kind: entry.kind,
1423
+ meta: deckMeta,
1424
+ themeStyle: themeStyle?.style,
1425
+ themeSourcePath: themeStyle?.sourcePath,
1426
+ slides,
1427
+ assets,
1428
+ warnings
1429
+ },
1430
+ slideModules
1431
+ };
1485
1432
  }
1486
1433
  function collectGeneratedExternalAssetRefs(contentBody, deckMeta) {
1487
- return buildExternalAssetRefs([
1488
- ...collectMarkdownAssetCandidates(contentBody),
1489
- ...collectFrontmatterAssetCandidates(deckMeta.assets)
1490
- ]);
1434
+ return buildExternalAssetRefs([...collectMarkdownAssetCandidates(contentBody), ...collectFrontmatterAssetCandidates(deckMeta.assets)]);
1491
1435
  }
1492
1436
  function generatedMdxParserWarnings(warnings) {
1493
- return warnings.filter((warning) => warning.code === "code-fence-unclosed");
1437
+ return warnings.filter((warning) => warning.code === "code-fence-unclosed");
1494
1438
  }
1495
1439
  function toGeneratedParserCompileWarnings(warnings, slideIndex) {
1496
- return generatedMdxParserWarnings(warnings).map((warning) => ({
1497
- code: "parse-warning",
1498
- message: warning.message,
1499
- slideIndex
1500
- }));
1440
+ return generatedMdxParserWarnings(warnings).map((warning) => ({
1441
+ code: "parse-warning",
1442
+ message: warning.message,
1443
+ slideIndex
1444
+ }));
1501
1445
  }
1502
1446
  async function compileMdxModule(source, sourcePath, slideIndex, resolveOgp) {
1503
- try {
1504
- const linkCardMetadata = await resolveLinkCardMetadataByUrl(source, resolveOgp);
1505
- const compiled = String(
1506
- await compile(
1507
- { path: `${sourcePath}#slide-${slideIndex + 1}`, value: source },
1508
- {
1509
- jsxRuntime: "automatic",
1510
- jsxImportSource: "hono/jsx",
1511
- format: "mdx",
1512
- elementAttributeNameCase: "html",
1513
- remarkPlugins: [
1514
- remarkGfm2,
1515
- remarkDirective,
1516
- remarkDeckSyntax({ linkCardMetadata }),
1517
- remarkCodeHighlight
1518
- ]
1519
- }
1520
- )
1521
- );
1522
- return `// @ts-nocheck
1523
- ${compiled}`;
1524
- } catch (error) {
1525
- const message = error instanceof Error ? error.message : String(error);
1526
- throw new CompileError(
1527
- `MDX compile failed in ${sourcePath} slide ${slideIndex + 1}: ${message}`,
1528
- "mdx-compile-error"
1529
- );
1530
- }
1447
+ try {
1448
+ const linkCardMetadata = await resolveLinkCardMetadataByUrl(source, resolveOgp);
1449
+ return `// @ts-nocheck\n${String(await compile({
1450
+ path: `${sourcePath}#slide-${slideIndex + 1}`,
1451
+ value: source
1452
+ }, {
1453
+ jsxRuntime: "automatic",
1454
+ jsxImportSource: "hono/jsx",
1455
+ format: "mdx",
1456
+ elementAttributeNameCase: "html",
1457
+ remarkPlugins: [
1458
+ remarkGfm,
1459
+ remarkDirective,
1460
+ remarkDeckSyntax({ linkCardMetadata }),
1461
+ remarkCodeHighlight
1462
+ ]
1463
+ }))}`;
1464
+ } catch (error) {
1465
+ const message = error instanceof Error ? error.message : String(error);
1466
+ throw new CompileError(`MDX compile failed in ${sourcePath} slide ${slideIndex + 1}: ${message}`, "mdx-compile-error");
1467
+ }
1531
1468
  }
1532
1469
  function extractDeckPrelude(source) {
1533
- const lines = source.replace(/\r\n/g, "\n").split("\n");
1534
- const prelude = [];
1535
- let cursor = 0;
1536
- while (cursor < lines.length) {
1537
- const line = lines[cursor];
1538
- if (line.trim() === "") {
1539
- prelude.push(line);
1540
- cursor += 1;
1541
- continue;
1542
- }
1543
- if (/^\s*(import|export)\s/.test(line)) {
1544
- prelude.push(line);
1545
- cursor += 1;
1546
- continue;
1547
- }
1548
- break;
1549
- }
1550
- return {
1551
- prelude: prelude.join("\n").trim(),
1552
- body: lines.slice(cursor).join("\n").trim()
1553
- };
1554
- }
1555
-
1556
- // src/node/client-entry.ts
1557
- import { existsSync } from "fs";
1558
- import { readFile } from "fs/promises";
1559
- import { join } from "path";
1560
- import { fileURLToPath } from "url";
1561
- import { build as buildBrowserBundle } from "esbuild";
1470
+ const lines = source.replace(/\r\n/g, "\n").split("\n");
1471
+ const prelude = [];
1472
+ let cursor = 0;
1473
+ while (cursor < lines.length) {
1474
+ const line = lines[cursor];
1475
+ if (line.trim() === "") {
1476
+ prelude.push(line);
1477
+ cursor += 1;
1478
+ continue;
1479
+ }
1480
+ if (/^\s*(import|export)\s/.test(line)) {
1481
+ prelude.push(line);
1482
+ cursor += 1;
1483
+ continue;
1484
+ }
1485
+ break;
1486
+ }
1487
+ return {
1488
+ prelude: prelude.join("\n").trim(),
1489
+ body: lines.slice(cursor).join("\n").trim()
1490
+ };
1491
+ }
1492
+ //#endregion
1493
+ //#region src/node/client-entry.ts
1562
1494
  async function discoverClientComponentIds(input) {
1563
- const result = {};
1564
- for (const entry of input.clientEntries) {
1565
- const source = await readFile(join(input.cwd, entry.sourcePath), "utf8");
1566
- const exports = extractComponentExportNames(source);
1567
- result[entry.slug] = Object.fromEntries(exports.map((name) => [name, clientComponentId(entry.slug, name)]));
1568
- }
1569
- return result;
1495
+ const result = {};
1496
+ for (const entry of input.clientEntries) {
1497
+ const exports = extractComponentExportNames(await readFile(join(input.cwd, entry.sourcePath), "utf8"));
1498
+ result[entry.slug] = Object.fromEntries(exports.map((name) => [name, clientComponentId(entry.slug, name)]));
1499
+ }
1500
+ return result;
1570
1501
  }
1571
1502
  async function emitClientEntryModule(input) {
1572
- const imports = [];
1573
- const registrations = [];
1574
- for (const entry of input.clientEntries) {
1575
- const ids = input.clientComponentIds[entry.slug] ?? {};
1576
- for (const [exportName, clientId] of Object.entries(ids)) {
1577
- const localName = clientImportName(entry.slug, exportName);
1578
- imports.push(`import { ${exportName} as ${localName} } from ${JSON.stringify(join(input.cwd, entry.sourcePath))};`);
1579
- registrations.push(`${JSON.stringify(clientId)}: ${localName}`);
1580
- }
1581
- }
1582
- if (registrations.length === 0) return 'export const decksClientEntry = "";\n';
1583
- const entryContents = `import { hydrateSlideIslands } from "hono-decks/client";
1503
+ const imports = [];
1504
+ const registrations = [];
1505
+ for (const entry of input.clientEntries) {
1506
+ const ids = input.clientComponentIds[entry.slug] ?? {};
1507
+ for (const [exportName, clientId] of Object.entries(ids)) {
1508
+ const localName = clientImportName(entry.slug, exportName);
1509
+ imports.push(`import { ${exportName} as ${localName} } from ${JSON.stringify(join(input.cwd, entry.sourcePath))};`);
1510
+ registrations.push(`${JSON.stringify(clientId)}: ${localName}`);
1511
+ }
1512
+ }
1513
+ if (registrations.length === 0) return "export const decksClientEntry = \"\";\n";
1514
+ const output = (await build({
1515
+ stdin: {
1516
+ contents: `import { hydrateSlideIslands } from "hono-decks/client";
1584
1517
  ${imports.join("\n")}
1585
1518
 
1586
1519
  hydrateSlideIslands({
@@ -1588,588 +1521,559 @@ hydrateSlideIslands({
1588
1521
  ${registrations.join(",\n ")}
1589
1522
  }
1590
1523
  });
1591
- `;
1592
- const result = await buildBrowserBundle({
1593
- stdin: {
1594
- contents: entryContents,
1595
- resolveDir: input.cwd,
1596
- sourcefile: "hono-decks-client-entry.tsx",
1597
- loader: "tsx"
1598
- },
1599
- bundle: true,
1600
- write: false,
1601
- format: "esm",
1602
- platform: "browser",
1603
- target: "es2022",
1604
- jsx: "automatic",
1605
- jsxImportSource: "hono/jsx/dom",
1606
- nodePaths: nodeModuleFallbackPaths(input.cwd),
1607
- alias: { "hono-decks/client": resolveClientRuntimeEntry() },
1608
- sourcemap: false,
1609
- minify: false
1610
- });
1611
- const output = result.outputFiles[0];
1612
- if (!output) throw new Error("Client entry did not produce output.");
1613
- return `export const decksClientEntry = ${JSON.stringify(output.text)};
1614
- `;
1524
+ `,
1525
+ resolveDir: input.cwd,
1526
+ sourcefile: "hono-decks-client-entry.tsx",
1527
+ loader: "tsx"
1528
+ },
1529
+ bundle: true,
1530
+ write: false,
1531
+ format: "esm",
1532
+ platform: "browser",
1533
+ target: "es2022",
1534
+ jsx: "automatic",
1535
+ jsxImportSource: "hono/jsx/dom",
1536
+ nodePaths: nodeModuleFallbackPaths(input.cwd),
1537
+ alias: { "hono-decks/client": resolveClientRuntimeEntry() },
1538
+ sourcemap: false,
1539
+ minify: false
1540
+ })).outputFiles[0];
1541
+ if (!output) throw new Error("Client entry did not produce output.");
1542
+ return `export const decksClientEntry = ${JSON.stringify(output.text)};\n`;
1615
1543
  }
1616
1544
  function resolveClientRuntimeEntry() {
1617
- const built = fileURLToPath(new URL("./client.js", import.meta.url));
1618
- if (existsSync(built)) return built;
1619
- return fileURLToPath(new URL("../client.ts", import.meta.url));
1545
+ const built = fileURLToPath(new URL("./client.js", import.meta.url));
1546
+ if (existsSync(built)) return built;
1547
+ return fileURLToPath(new URL("../client.ts", import.meta.url));
1620
1548
  }
1621
1549
  function extractComponentExportNames(source) {
1622
- const names = /* @__PURE__ */ new Set();
1623
- for (const match of source.matchAll(/\bexport\s+function\s+([A-Z][A-Za-z0-9_]*)\b/g)) {
1624
- names.add(match[1]);
1625
- }
1626
- for (const match of source.matchAll(/\bexport\s+const\s+([A-Z][A-Za-z0-9_]*)\b/g)) {
1627
- names.add(match[1]);
1628
- }
1629
- return [...names].sort();
1550
+ const names = /* @__PURE__ */ new Set();
1551
+ for (const match of source.matchAll(/\bexport\s+function\s+([A-Z][A-Za-z0-9_]*)\b/g)) names.add(match[1]);
1552
+ for (const match of source.matchAll(/\bexport\s+const\s+([A-Z][A-Za-z0-9_]*)\b/g)) names.add(match[1]);
1553
+ return [...names].sort();
1630
1554
  }
1631
1555
  function nodeModuleFallbackPaths(cwd) {
1632
- const current = process.cwd();
1633
- return [
1634
- join(cwd, "node_modules"),
1635
- join(cwd, "..", "node_modules"),
1636
- join(cwd, "..", "..", "node_modules"),
1637
- join(current, "node_modules"),
1638
- join(current, "..", "node_modules"),
1639
- join(current, "..", "..", "node_modules")
1640
- ];
1556
+ const current = process.cwd();
1557
+ return [
1558
+ join(cwd, "node_modules"),
1559
+ join(cwd, "..", "node_modules"),
1560
+ join(cwd, "..", "..", "node_modules"),
1561
+ join(current, "node_modules"),
1562
+ join(current, "..", "node_modules"),
1563
+ join(current, "..", "..", "node_modules")
1564
+ ];
1641
1565
  }
1642
1566
  function clientComponentId(slug, exportName) {
1643
- const base = `${exportName}__${safeIdentifier2(slug)}`;
1644
- return `${base}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
1567
+ return `${`${exportName}__${safeIdentifier(slug)}`}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
1645
1568
  }
1646
1569
  function clientImportName(slug, exportName) {
1647
- return `${safeIdentifier2(exportName)}__${safeIdentifier2(slug)}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
1570
+ return `${safeIdentifier(exportName)}__${safeIdentifier(slug)}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
1648
1571
  }
1649
1572
  function hashString(value) {
1650
- let hash = 5381;
1651
- for (let index = 0; index < value.length; index += 1) {
1652
- hash = hash * 33 ^ value.charCodeAt(index);
1653
- }
1654
- return (hash >>> 0).toString(36);
1573
+ let hash = 5381;
1574
+ for (let index = 0; index < value.length; index += 1) hash = hash * 33 ^ value.charCodeAt(index);
1575
+ return (hash >>> 0).toString(36);
1655
1576
  }
1656
- function safeIdentifier2(value) {
1657
- return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
1577
+ function safeIdentifier(value) {
1578
+ return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
1658
1579
  }
1659
-
1660
- // src/node/local-deck-io.ts
1661
- import { existsSync as existsSync2, watch } from "fs";
1662
- import { readdir, readFile as readFile2 } from "fs/promises";
1663
- import { join as join2, relative as relative2 } from "path";
1664
-
1665
- // src/node/path-utils.ts
1666
- import { relative } from "path";
1667
- function dirname2(path) {
1668
- const normalized = normalizePath4(path);
1669
- return normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
1580
+ //#endregion
1581
+ //#region src/node/path-utils.ts
1582
+ function dirname(path) {
1583
+ const normalized = normalizePath(path);
1584
+ return normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
1670
1585
  }
1671
- function normalizePath4(path) {
1672
- return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
1586
+ function normalizePath(path) {
1587
+ return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
1673
1588
  }
1674
1589
  function normalizeDeckRoot(root) {
1675
- const normalized = normalizeRelativePath(root, "Deck root").replace(/\/$/, "");
1676
- if (normalized === ".") {
1677
- throw new Error("Deck root must be a relative path inside the current working directory");
1678
- }
1679
- return normalized;
1590
+ const normalized = normalizeRelativePath(root, "Deck root").replace(/\/$/, "");
1591
+ if (normalized === ".") throw new Error("Deck root must be a relative path inside the current working directory");
1592
+ return normalized;
1680
1593
  }
1681
1594
  function normalizeRelativePath(path, label) {
1682
- const normalized = normalizePath4(path).replace(/\/$/, "");
1683
- const segments = normalized.split("/");
1684
- if (normalized === "" || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || segments.includes("..")) {
1685
- throw new Error(`${label} must be a relative path inside the current working directory`);
1686
- }
1687
- return normalized;
1595
+ const normalized = normalizePath(path).replace(/\/$/, "");
1596
+ const segments = normalized.split("/");
1597
+ if (normalized === "" || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || segments.includes("..")) throw new Error(`${label} must be a relative path inside the current working directory`);
1598
+ return normalized;
1688
1599
  }
1689
-
1690
- // src/node/local-deck-io.ts
1600
+ //#endregion
1601
+ //#region src/node/local-deck-io.ts
1691
1602
  async function listFiles(cwd, dir) {
1692
- const entries = await readdir(dir, { withFileTypes: true });
1693
- const paths = await Promise.all(
1694
- entries.map(async (entry) => {
1695
- const fullPath = join2(dir, entry.name);
1696
- if (entry.isDirectory()) return listFiles(cwd, fullPath);
1697
- if (entry.isFile()) return [normalizePath4(relative2(cwd, fullPath))];
1698
- return [];
1699
- })
1700
- );
1701
- return paths.flat().sort();
1702
- }
1703
-
1704
- // src/node/ogp.ts
1705
- import { lookup } from "dns/promises";
1706
- import { isIP } from "net";
1707
- var MAX_OGP_BYTES = 256 * 1024;
1708
- var MAX_OGP_REDIRECTS = 3;
1603
+ const entries = await readdir(dir, { withFileTypes: true });
1604
+ return (await Promise.all(entries.map(async (entry) => {
1605
+ const fullPath = join(dir, entry.name);
1606
+ if (entry.isDirectory()) return listFiles(cwd, fullPath);
1607
+ if (entry.isFile()) return [normalizePath(relative(cwd, fullPath))];
1608
+ return [];
1609
+ }))).flat().sort();
1610
+ }
1611
+ //#endregion
1612
+ //#region src/node/ogp.ts
1613
+ const MAX_OGP_BYTES = 256 * 1024;
1614
+ const MAX_OGP_REDIRECTS = 3;
1709
1615
  async function resolveOgpMetadata(url) {
1710
- if (!isHttpUrl2(url) || typeof fetch === "undefined") return void 0;
1711
- try {
1712
- const response = await fetchPublicHttpUrl(url);
1713
- if (!response) return void 0;
1714
- const contentType = response.headers.get("content-type") ?? "";
1715
- if (!response.ok || !contentType.toLowerCase().includes("text/html") || isTooLarge(response)) return void 0;
1716
- const html = await readTextWithLimit(response, MAX_OGP_BYTES);
1717
- if (!html) return void 0;
1718
- return parseOgpHtml(html, response.url || url);
1719
- } catch {
1720
- return void 0;
1721
- }
1616
+ if (!isHttpUrl(url) || typeof fetch === "undefined") return void 0;
1617
+ try {
1618
+ const response = await fetchPublicHttpUrl(url);
1619
+ if (!response) return void 0;
1620
+ const contentType = response.headers.get("content-type") ?? "";
1621
+ if (!response.ok || !contentType.toLowerCase().includes("text/html") || isTooLarge(response)) return void 0;
1622
+ const html = await readTextWithLimit(response, MAX_OGP_BYTES);
1623
+ if (!html) return void 0;
1624
+ return parseOgpHtml(html, response.url || url);
1625
+ } catch {
1626
+ return;
1627
+ }
1722
1628
  }
1723
1629
  async function fetchPublicHttpUrl(url) {
1724
- let current = url;
1725
- for (let redirect = 0; redirect <= MAX_OGP_REDIRECTS; redirect += 1) {
1726
- if (!await isPublicHttpUrl(current)) return void 0;
1727
- const response = await fetch(current, {
1728
- headers: { accept: "text/html,application/xhtml+xml" },
1729
- redirect: "manual",
1730
- signal: AbortSignal.timeout(1500)
1731
- });
1732
- if (!isRedirect(response.status)) return response;
1733
- await response.body?.cancel();
1734
- const location = response.headers.get("location");
1735
- if (!location) return void 0;
1736
- current = new URL(location, current).toString();
1737
- }
1738
- return void 0;
1630
+ let current = url;
1631
+ for (let redirect = 0; redirect <= MAX_OGP_REDIRECTS; redirect += 1) {
1632
+ if (!await isPublicHttpUrl(current)) return void 0;
1633
+ const response = await fetch(current, {
1634
+ headers: { accept: "text/html,application/xhtml+xml" },
1635
+ redirect: "manual",
1636
+ signal: AbortSignal.timeout(1500)
1637
+ });
1638
+ if (!isRedirect(response.status)) return response;
1639
+ await response.body?.cancel();
1640
+ const location = response.headers.get("location");
1641
+ if (!location) return void 0;
1642
+ current = new URL(location, current).toString();
1643
+ }
1739
1644
  }
1740
1645
  async function isPublicHttpUrl(value) {
1741
- let url;
1742
- try {
1743
- url = new URL(value);
1744
- } catch {
1745
- return false;
1746
- }
1747
- if (url.protocol !== "http:" && url.protocol !== "https:") return false;
1748
- if (isLocalHostname(url.hostname)) return false;
1749
- if (isPrivateIpAddress(url.hostname)) return false;
1750
- const addresses = await lookup(url.hostname, { all: true, verbatim: true });
1751
- return addresses.length > 0 && addresses.every(({ address }) => !isPrivateIpAddress(address));
1646
+ let url;
1647
+ try {
1648
+ url = new URL(value);
1649
+ } catch {
1650
+ return false;
1651
+ }
1652
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
1653
+ if (isLocalHostname(url.hostname)) return false;
1654
+ if (isPrivateIpAddress(url.hostname)) return false;
1655
+ const addresses = await lookup(url.hostname, {
1656
+ all: true,
1657
+ verbatim: true
1658
+ });
1659
+ return addresses.length > 0 && addresses.every(({ address }) => !isPrivateIpAddress(address));
1752
1660
  }
1753
1661
  function isLocalHostname(hostname) {
1754
- const normalized = hostname.toLowerCase().replace(/\.$/, "");
1755
- return normalized === "localhost" || normalized.endsWith(".localhost");
1662
+ const normalized = hostname.toLowerCase().replace(/\.$/, "");
1663
+ return normalized === "localhost" || normalized.endsWith(".localhost");
1756
1664
  }
1757
1665
  function isPrivateIpAddress(value) {
1758
- const normalized = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
1759
- const version = isIP(normalized);
1760
- if (version === 4) return isPrivateIpv4(normalized);
1761
- if (version === 6) return isPrivateIpv6(normalized);
1762
- return false;
1666
+ const normalized = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
1667
+ const version = isIP(normalized);
1668
+ if (version === 4) return isPrivateIpv4(normalized);
1669
+ if (version === 6) return isPrivateIpv6(normalized);
1670
+ return false;
1763
1671
  }
1764
1672
  function isPrivateIpv4(address) {
1765
- const parts = address.split(".").map((part) => Number.parseInt(part, 10));
1766
- if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;
1767
- const [a, b] = parts;
1768
- return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224;
1673
+ const parts = address.split(".").map((part) => Number.parseInt(part, 10));
1674
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;
1675
+ const [a, b] = parts;
1676
+ return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224;
1769
1677
  }
1770
1678
  function isPrivateIpv6(address) {
1771
- const normalized = address.toLowerCase();
1772
- if (normalized === "::" || normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fec0:") || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff")) {
1773
- return true;
1774
- }
1775
- if (normalized.startsWith("::ffff:")) {
1776
- const tail = normalized.slice("::ffff:".length);
1777
- const dotted = tail.match(/^(?:0:)?(\d+\.\d+\.\d+\.\d+)$/);
1778
- if (dotted) return isPrivateIpv4(dotted[1]);
1779
- const hex = tail.match(/^(?:0:)?([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
1780
- if (hex) {
1781
- const hi = Number.parseInt(hex[1], 16);
1782
- const lo = Number.parseInt(hex[2], 16);
1783
- const value32 = hi << 16 | lo;
1784
- const ipv4 = [value32 >>> 24 & 255, value32 >>> 16 & 255, value32 >>> 8 & 255, value32 & 255].join(".");
1785
- return isPrivateIpv4(ipv4);
1786
- }
1787
- }
1788
- return false;
1679
+ const normalized = address.toLowerCase();
1680
+ if (normalized === "::" || normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fec0:") || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff")) return true;
1681
+ if (normalized.startsWith("::ffff:")) {
1682
+ const tail = normalized.slice(7);
1683
+ const dotted = tail.match(/^(?:0:)?(\d+\.\d+\.\d+\.\d+)$/);
1684
+ if (dotted) return isPrivateIpv4(dotted[1]);
1685
+ const hex = tail.match(/^(?:0:)?([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
1686
+ if (hex) {
1687
+ const hi = Number.parseInt(hex[1], 16);
1688
+ const lo = Number.parseInt(hex[2], 16);
1689
+ const value32 = hi << 16 | lo;
1690
+ return isPrivateIpv4([
1691
+ value32 >>> 24 & 255,
1692
+ value32 >>> 16 & 255,
1693
+ value32 >>> 8 & 255,
1694
+ value32 & 255
1695
+ ].join("."));
1696
+ }
1697
+ }
1698
+ return false;
1789
1699
  }
1790
1700
  function isRedirect(status) {
1791
- return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
1701
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
1792
1702
  }
1793
1703
  function isTooLarge(response) {
1794
- const contentLength = response.headers.get("content-length");
1795
- return contentLength != null && Number(contentLength) > MAX_OGP_BYTES;
1704
+ const contentLength = response.headers.get("content-length");
1705
+ return contentLength != null && Number(contentLength) > MAX_OGP_BYTES;
1796
1706
  }
1797
1707
  async function readTextWithLimit(response, maxBytes) {
1798
- const reader = response.body?.getReader();
1799
- if (!reader) {
1800
- const text = await response.text();
1801
- return new TextEncoder().encode(text).byteLength > maxBytes ? void 0 : text;
1802
- }
1803
- const chunks = [];
1804
- let bytes = 0;
1805
- while (true) {
1806
- const { done, value } = await reader.read();
1807
- if (done) break;
1808
- bytes += value.byteLength;
1809
- if (bytes > maxBytes) {
1810
- await reader.cancel();
1811
- return void 0;
1812
- }
1813
- chunks.push(value);
1814
- }
1815
- const buffer = new Uint8Array(bytes);
1816
- let offset = 0;
1817
- for (const chunk of chunks) {
1818
- buffer.set(chunk, offset);
1819
- offset += chunk.byteLength;
1820
- }
1821
- return new TextDecoder().decode(buffer);
1708
+ const reader = response.body?.getReader();
1709
+ if (!reader) {
1710
+ const text = await response.text();
1711
+ return new TextEncoder().encode(text).byteLength > maxBytes ? void 0 : text;
1712
+ }
1713
+ const chunks = [];
1714
+ let bytes = 0;
1715
+ while (true) {
1716
+ const { done, value } = await reader.read();
1717
+ if (done) break;
1718
+ bytes += value.byteLength;
1719
+ if (bytes > maxBytes) {
1720
+ await reader.cancel();
1721
+ return;
1722
+ }
1723
+ chunks.push(value);
1724
+ }
1725
+ const buffer = new Uint8Array(bytes);
1726
+ let offset = 0;
1727
+ for (const chunk of chunks) {
1728
+ buffer.set(chunk, offset);
1729
+ offset += chunk.byteLength;
1730
+ }
1731
+ return new TextDecoder().decode(buffer);
1822
1732
  }
1823
1733
  function parseOgpHtml(html, pageUrl) {
1824
- const meta = collectHtmlMeta(html);
1825
- const title = firstValue(meta, ["og:title", "twitter:title"]) ?? htmlTitle(html);
1826
- const description = firstValue(meta, ["og:description", "twitter:description", "description"]);
1827
- const image = absoluteUrl(firstValue(meta, ["og:image", "og:image:url", "twitter:image"]), pageUrl);
1828
- const siteName = firstValue(meta, ["og:site_name", "application-name"]);
1829
- const result = { title, description, image, siteName };
1830
- return Object.values(result).some(Boolean) ? result : void 0;
1734
+ const meta = collectHtmlMeta(html);
1735
+ const result = {
1736
+ title: firstValue(meta, ["og:title", "twitter:title"]) ?? htmlTitle(html),
1737
+ description: firstValue(meta, [
1738
+ "og:description",
1739
+ "twitter:description",
1740
+ "description"
1741
+ ]),
1742
+ image: absoluteUrl(firstValue(meta, [
1743
+ "og:image",
1744
+ "og:image:url",
1745
+ "twitter:image"
1746
+ ]), pageUrl),
1747
+ siteName: firstValue(meta, ["og:site_name", "application-name"])
1748
+ };
1749
+ return Object.values(result).some(Boolean) ? result : void 0;
1831
1750
  }
1832
1751
  function collectHtmlMeta(html) {
1833
- const meta = /* @__PURE__ */ new Map();
1834
- const tagPattern = /<meta\b[^>]*>/gi;
1835
- for (const match of html.matchAll(tagPattern)) {
1836
- const attributes = parseHtmlAttributes(match[0]);
1837
- const key = attributes.property ?? attributes.name;
1838
- const content = attributes.content;
1839
- if (key && content && !meta.has(key.toLowerCase())) {
1840
- meta.set(key.toLowerCase(), decodeHtmlEntities(content.trim()));
1841
- }
1842
- }
1843
- return meta;
1752
+ const meta = /* @__PURE__ */ new Map();
1753
+ for (const match of html.matchAll(/<meta\b[^>]*>/gi)) {
1754
+ const attributes = parseHtmlAttributes(match[0]);
1755
+ const key = attributes.property ?? attributes.name;
1756
+ const content = attributes.content;
1757
+ if (key && content && !meta.has(key.toLowerCase())) meta.set(key.toLowerCase(), decodeHtmlEntities(content.trim()));
1758
+ }
1759
+ return meta;
1844
1760
  }
1845
1761
  function parseHtmlAttributes(tag) {
1846
- const attributes = {};
1847
- const attributePattern = /([^\s"'<>/=]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/g;
1848
- for (const match of tag.matchAll(attributePattern)) {
1849
- attributes[match[1].toLowerCase()] = match[2] ?? match[3] ?? match[4] ?? "";
1850
- }
1851
- return attributes;
1762
+ const attributes = {};
1763
+ for (const match of tag.matchAll(/([^\s"'<>/=]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/g)) attributes[match[1].toLowerCase()] = match[2] ?? match[3] ?? match[4] ?? "";
1764
+ return attributes;
1852
1765
  }
1853
1766
  function firstValue(meta, keys) {
1854
- for (const key of keys) {
1855
- const value = meta.get(key);
1856
- if (value) return value;
1857
- }
1858
- return void 0;
1767
+ for (const key of keys) {
1768
+ const value = meta.get(key);
1769
+ if (value) return value;
1770
+ }
1859
1771
  }
1860
1772
  function htmlTitle(html) {
1861
- const match = html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i);
1862
- return match ? decodeHtmlEntities(match[1].replace(/\s+/g, " ").trim()) : void 0;
1773
+ const match = html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i);
1774
+ return match ? decodeHtmlEntities(match[1].replace(/\s+/g, " ").trim()) : void 0;
1863
1775
  }
1864
1776
  function absoluteUrl(value, base) {
1865
- if (!value) return void 0;
1866
- try {
1867
- return new URL(value, base).toString();
1868
- } catch {
1869
- return value;
1870
- }
1777
+ if (!value) return void 0;
1778
+ try {
1779
+ return new URL(value, base).toString();
1780
+ } catch {
1781
+ return value;
1782
+ }
1871
1783
  }
1872
1784
  function decodeHtmlEntities(value) {
1873
- return value.replaceAll("&amp;", "&").replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">");
1874
- }
1875
- function isHttpUrl2(value) {
1876
- try {
1877
- const url = new URL(value);
1878
- return url.protocol === "http:" || url.protocol === "https:";
1879
- } catch {
1880
- return false;
1881
- }
1785
+ return value.replaceAll("&quot;", "\"").replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
1882
1786
  }
1883
-
1884
- // src/node/compile-decks.ts
1787
+ function isHttpUrl(value) {
1788
+ try {
1789
+ const url = new URL(value);
1790
+ return url.protocol === "http:" || url.protocol === "https:";
1791
+ } catch {
1792
+ return false;
1793
+ }
1794
+ }
1795
+ //#endregion
1796
+ //#region src/node/compile-decks.ts
1885
1797
  async function compileDecks(input) {
1886
- const out = normalizeRelativePath(input.out, "Output directory");
1887
- const root = normalizeDeckRoot(input.root);
1888
- const paths = await listFiles(input.cwd, join3(input.cwd, root));
1889
- const resolved = resolveDeckFiles(paths, root);
1890
- const componentModulePaths = Object.fromEntries(
1891
- resolved.flatMap((deck) => {
1892
- const path = deck.kind === "directory" ? findComponentModule(paths, root, deck.slug) : void 0;
1893
- return path ? [[deck.slug, path]] : [];
1894
- })
1895
- );
1896
- const clientEntryPaths = resolved.flatMap((deck) => {
1897
- const path = deck.kind === "directory" ? findClientEntryModule(paths, root, deck.slug) : void 0;
1898
- return path ? [{ slug: deck.slug, sourcePath: path }] : [];
1899
- });
1900
- const themeStyles = await discoverDeckThemeStyles({ cwd: input.cwd, root, paths, decks: resolved });
1901
- const clientComponentIds = await discoverClientComponentIds({
1902
- cwd: input.cwd,
1903
- clientEntries: clientEntryPaths
1904
- });
1905
- const ogpCache = await createOgpCacheResolver({
1906
- cwd: input.cwd,
1907
- cacheFile: input.ogpCacheFile,
1908
- refresh: input.refreshOgp,
1909
- resolveOgp: input.resolveOgp ?? resolveOgpMetadata
1910
- });
1911
- const generated = await compileMdxModuleDecks({
1912
- root,
1913
- outDir: out,
1914
- mountPath: input.mountPath,
1915
- decks: resolved,
1916
- componentModulePaths,
1917
- clientComponentIds,
1918
- themeStyles,
1919
- resolveOgp: ogpCache.resolveOgp,
1920
- readText: (path) => readFile3(join3(input.cwd, path), "utf8"),
1921
- readBinary: (path) => readFile3(join3(input.cwd, path))
1922
- });
1923
- for (const deck of generated.decks) {
1924
- for (const slide of deck.slideModules) {
1925
- await writeTextFile(join3(input.cwd, slide.path), slide.code);
1926
- }
1927
- }
1928
- await writeTextFile(join3(input.cwd, out, "decks.ts"), generated.routerModule);
1929
- await writeTextFile(
1930
- join3(input.cwd, out, "client-entry.ts"),
1931
- await emitClientEntryModule({ cwd: input.cwd, clientEntries: clientEntryPaths, clientComponentIds })
1932
- );
1933
- await ogpCache.write();
1934
- return { decks: generated.decks.map((deck) => deck.deck) };
1798
+ const out = normalizeRelativePath(input.out, "Output directory");
1799
+ const root = normalizeDeckRoot(input.root);
1800
+ const paths = await listFiles(input.cwd, join(input.cwd, root));
1801
+ const resolved = resolveDeckFiles(paths, root);
1802
+ const componentModulePaths = Object.fromEntries(resolved.flatMap((deck) => {
1803
+ const path = deck.kind === "directory" ? findComponentModule(paths, root, deck.slug) : void 0;
1804
+ return path ? [[deck.slug, path]] : [];
1805
+ }));
1806
+ const clientEntryPaths = resolved.flatMap((deck) => {
1807
+ const path = deck.kind === "directory" ? findClientEntryModule(paths, root, deck.slug) : void 0;
1808
+ return path ? [{
1809
+ slug: deck.slug,
1810
+ sourcePath: path
1811
+ }] : [];
1812
+ });
1813
+ const themeStyles = await discoverDeckThemeStyles({
1814
+ cwd: input.cwd,
1815
+ root,
1816
+ paths,
1817
+ decks: resolved
1818
+ });
1819
+ const clientComponentIds = await discoverClientComponentIds({
1820
+ cwd: input.cwd,
1821
+ clientEntries: clientEntryPaths
1822
+ });
1823
+ const ogpCache = await createOgpCacheResolver({
1824
+ cwd: input.cwd,
1825
+ cacheFile: input.ogpCacheFile,
1826
+ refresh: input.refreshOgp,
1827
+ resolveOgp: input.resolveOgp ?? resolveOgpMetadata
1828
+ });
1829
+ const generated = await compileMdxModuleDecks({
1830
+ root,
1831
+ outDir: out,
1832
+ mountPath: input.mountPath,
1833
+ decks: resolved,
1834
+ componentModulePaths,
1835
+ clientComponentIds,
1836
+ themeStyles,
1837
+ resolveOgp: ogpCache.resolveOgp,
1838
+ readText: (path) => readFile(join(input.cwd, path), "utf8"),
1839
+ readBinary: (path) => readFile(join(input.cwd, path))
1840
+ });
1841
+ for (const deck of generated.decks) for (const slide of deck.slideModules) await writeTextFile(join(input.cwd, slide.path), slide.code);
1842
+ await writeTextFile(join(input.cwd, out, "decks.ts"), generated.routerModule);
1843
+ await writeTextFile(join(input.cwd, out, "client-entry.ts"), await emitClientEntryModule({
1844
+ cwd: input.cwd,
1845
+ clientEntries: clientEntryPaths,
1846
+ clientComponentIds
1847
+ }));
1848
+ await ogpCache.write();
1849
+ return { decks: generated.decks.map((deck) => deck.deck) };
1935
1850
  }
1936
1851
  async function createOgpCacheResolver(input) {
1937
- if (!input.cacheFile) {
1938
- return {
1939
- resolveOgp: input.resolveOgp,
1940
- write: async () => void 0
1941
- };
1942
- }
1943
- const cachePath = join3(input.cwd, normalizeRelativePath(input.cacheFile, "OGP cache file"));
1944
- const cache = await readOgpCache(cachePath);
1945
- let dirty = false;
1946
- return {
1947
- async resolveOgp(url) {
1948
- if (!input.refresh && cache[url]) return cache[url];
1949
- if (!input.refresh) return void 0;
1950
- const metadata = await input.resolveOgp(url);
1951
- if (metadata) {
1952
- cache[url] = metadata;
1953
- dirty = true;
1954
- }
1955
- return metadata;
1956
- },
1957
- async write() {
1958
- if (!dirty) return;
1959
- await writeTextFile(cachePath, `${JSON.stringify(cache, null, 2)}
1960
- `);
1961
- }
1962
- };
1852
+ if (!input.cacheFile) return {
1853
+ resolveOgp: input.resolveOgp,
1854
+ write: async () => void 0
1855
+ };
1856
+ const cachePath = join(input.cwd, normalizeRelativePath(input.cacheFile, "OGP cache file"));
1857
+ const cache = await readOgpCache(cachePath);
1858
+ let dirty = false;
1859
+ return {
1860
+ async resolveOgp(url) {
1861
+ if (!input.refresh && cache[url]) return cache[url];
1862
+ if (!input.refresh) return void 0;
1863
+ const metadata = await input.resolveOgp(url);
1864
+ if (metadata) {
1865
+ cache[url] = metadata;
1866
+ dirty = true;
1867
+ }
1868
+ return metadata;
1869
+ },
1870
+ async write() {
1871
+ if (!dirty) return;
1872
+ await writeTextFile(cachePath, `${JSON.stringify(cache, null, 2)}\n`);
1873
+ }
1874
+ };
1963
1875
  }
1964
1876
  async function readOgpCache(path) {
1965
- let raw;
1966
- try {
1967
- raw = await readFile3(path, "utf8");
1968
- } catch (error) {
1969
- if (isNodeError(error) && error.code === "ENOENT") return {};
1970
- throw error;
1971
- }
1972
- const parsed = JSON.parse(raw);
1973
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1974
- throw new Error("OGP cache file must contain a JSON object");
1975
- }
1976
- const cache = {};
1977
- for (const [url, value] of Object.entries(parsed)) {
1978
- if (!value || typeof value !== "object" || Array.isArray(value)) continue;
1979
- const metadata = value;
1980
- cache[url] = {
1981
- ...typeof metadata.title === "string" ? { title: metadata.title } : {},
1982
- ...typeof metadata.description === "string" ? { description: metadata.description } : {},
1983
- ...typeof metadata.image === "string" ? { image: metadata.image } : {},
1984
- ...typeof metadata.siteName === "string" ? { siteName: metadata.siteName } : {}
1985
- };
1986
- }
1987
- return cache;
1877
+ let raw;
1878
+ try {
1879
+ raw = await readFile(path, "utf8");
1880
+ } catch (error) {
1881
+ if (isNodeError(error) && error.code === "ENOENT") return {};
1882
+ throw error;
1883
+ }
1884
+ const parsed = JSON.parse(raw);
1885
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OGP cache file must contain a JSON object");
1886
+ const cache = {};
1887
+ for (const [url, value] of Object.entries(parsed)) {
1888
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
1889
+ const metadata = value;
1890
+ cache[url] = {
1891
+ ...typeof metadata.title === "string" ? { title: metadata.title } : {},
1892
+ ...typeof metadata.description === "string" ? { description: metadata.description } : {},
1893
+ ...typeof metadata.image === "string" ? { image: metadata.image } : {},
1894
+ ...typeof metadata.siteName === "string" ? { siteName: metadata.siteName } : {}
1895
+ };
1896
+ }
1897
+ return cache;
1988
1898
  }
1989
1899
  function isNodeError(error) {
1990
- return error instanceof Error && "code" in error;
1900
+ return error instanceof Error && "code" in error;
1991
1901
  }
1992
1902
  async function writeTextFile(path, contents) {
1993
- await mkdir(dirname2(path), { recursive: true });
1994
- await writeFile(path, contents, "utf8");
1903
+ await mkdir(dirname(path), { recursive: true });
1904
+ await writeFile(path, contents, "utf8");
1995
1905
  }
1996
1906
  function findComponentModule(paths, root, slug) {
1997
- const base = `${normalizePath4(root).replace(/\/$/, "")}/${slug}/components/index`;
1998
- return paths.find((path) => {
1999
- const normalized = normalizePath4(path);
2000
- return normalized === `${base}.tsx` || normalized === `${base}.ts` || normalized === `${base}.jsx` || normalized === `${base}.js`;
2001
- });
1907
+ const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}/components/index`;
1908
+ return paths.find((path) => {
1909
+ const normalized = normalizePath(path);
1910
+ return normalized === `${base}.tsx` || normalized === `${base}.ts` || normalized === `${base}.jsx` || normalized === `${base}.js`;
1911
+ });
2002
1912
  }
2003
1913
  function findClientEntryModule(paths, root, slug) {
2004
- const base = `${normalizePath4(root).replace(/\/$/, "")}/${slug}/components/client/index`;
2005
- return paths.find((path) => {
2006
- const normalized = normalizePath4(path);
2007
- return normalized === `${base}.tsx` || normalized === `${base}.ts` || normalized === `${base}.jsx` || normalized === `${base}.js`;
2008
- });
1914
+ const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}/components/client/index`;
1915
+ return paths.find((path) => {
1916
+ const normalized = normalizePath(path);
1917
+ return normalized === `${base}.tsx` || normalized === `${base}.ts` || normalized === `${base}.jsx` || normalized === `${base}.js`;
1918
+ });
2009
1919
  }
2010
1920
  async function discoverDeckThemeStyles(input) {
2011
- const result = {};
2012
- for (const deck of input.decks) {
2013
- if (deck.kind !== "directory") continue;
2014
- const sourcePath = findDeckThemeStyle(input.paths, input.root, deck.slug);
2015
- if (!sourcePath) continue;
2016
- result[deck.slug] = {
2017
- sourcePath,
2018
- style: await readFile3(join3(input.cwd, sourcePath), "utf8")
2019
- };
2020
- }
2021
- return result;
1921
+ const result = {};
1922
+ for (const deck of input.decks) {
1923
+ if (deck.kind !== "directory") continue;
1924
+ const sourcePath = findDeckThemeStyle(input.paths, input.root, deck.slug);
1925
+ if (!sourcePath) continue;
1926
+ result[deck.slug] = {
1927
+ sourcePath,
1928
+ style: await readFile(join(input.cwd, sourcePath), "utf8")
1929
+ };
1930
+ }
1931
+ return result;
2022
1932
  }
2023
1933
  function findDeckThemeStyle(paths, root, slug) {
2024
- const base = `${normalizePath4(root).replace(/\/$/, "")}/${slug}`;
2025
- const themePath = `${base}/theme.css`;
2026
- const stylesEntryPath = `${base}/styles/index.css`;
2027
- const hasTheme = paths.includes(themePath);
2028
- const hasStylesEntry = paths.includes(stylesEntryPath);
2029
- if (hasTheme && hasStylesEntry) {
2030
- throw new Error(`Deck ${slug} has both ${themePath} and ${stylesEntryPath}. Use only one theme CSS entry.`);
2031
- }
2032
- return hasTheme ? themePath : hasStylesEntry ? stylesEntryPath : void 0;
1934
+ const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}`;
1935
+ const themePath = `${base}/theme.css`;
1936
+ const stylesEntryPath = `${base}/styles/index.css`;
1937
+ const hasTheme = paths.includes(themePath);
1938
+ const hasStylesEntry = paths.includes(stylesEntryPath);
1939
+ if (hasTheme && hasStylesEntry) throw new Error(`Deck ${slug} has both ${themePath} and ${stylesEntryPath}. Use only one theme CSS entry.`);
1940
+ return hasTheme ? themePath : hasStylesEntry ? stylesEntryPath : void 0;
2033
1941
  }
2034
-
2035
- // src/node/config.ts
2036
- import { existsSync as existsSync3 } from "fs";
2037
- import { resolve } from "path";
2038
- import { fileURLToPath as fileURLToPath2 } from "url";
2039
- import { build } from "esbuild";
2040
- var DEFAULT_DECKS_CONFIG_FILE = "hono-decks.config.ts";
2041
1942
  async function loadDecksConfig(input) {
2042
- const path = resolve(input.cwd, input.configFile ?? DEFAULT_DECKS_CONFIG_FILE);
2043
- if (!existsSync3(path)) {
2044
- throw new Error(`Config file not found: ${input.configFile ?? DEFAULT_DECKS_CONFIG_FILE}. Run \`hono-decks init\` first.`);
2045
- }
2046
- const result = await build({
2047
- absWorkingDir: input.cwd,
2048
- entryPoints: [path],
2049
- bundle: true,
2050
- format: "esm",
2051
- platform: "node",
2052
- target: "node20",
2053
- write: false,
2054
- logLevel: "silent",
2055
- alias: {
2056
- "hono-decks": resolveRuntimeEntry()
2057
- }
2058
- });
2059
- const source = result.outputFiles[0]?.text;
2060
- if (!source) throw new Error(`Could not load config file: ${path}`);
2061
- const module = await import(`data:text/javascript;charset=utf-8,${encodeURIComponent(source)}#${Date.now()}`);
2062
- const config = module.default;
2063
- if (!config || typeof config !== "object") throw new Error(`Config must have a default export: ${path}`);
2064
- if (typeof config.mountPath !== "string") throw new Error(`Config mountPath must be a string: ${path}`);
2065
- return {
2066
- path,
2067
- config,
2068
- root: config.build?.root ?? "decks",
2069
- outDir: config.build?.outDir ?? "src/generated",
2070
- ogpCacheFile: config.build?.ogpCacheFile
2071
- };
1943
+ const path = resolve(input.cwd, input.configFile ?? "hono-decks.config.ts");
1944
+ if (!existsSync(path)) throw new Error(`Config file not found: ${input.configFile ?? "hono-decks.config.ts"}. Run \`hono-decks init\` first.`);
1945
+ const source = (await build({
1946
+ absWorkingDir: input.cwd,
1947
+ entryPoints: [path],
1948
+ bundle: true,
1949
+ format: "esm",
1950
+ platform: "node",
1951
+ target: "node20",
1952
+ write: false,
1953
+ logLevel: "silent",
1954
+ alias: { "hono-decks": resolveRuntimeEntry() }
1955
+ })).outputFiles[0]?.text;
1956
+ if (!source) throw new Error(`Could not load config file: ${path}`);
1957
+ const config = (await import(`data:text/javascript;charset=utf-8,${encodeURIComponent(source)}#${Date.now()}`)).default;
1958
+ if (!config || typeof config !== "object") throw new Error(`Config must have a default export: ${path}`);
1959
+ if (typeof config.mountPath !== "string") throw new Error(`Config mountPath must be a string: ${path}`);
1960
+ return {
1961
+ path,
1962
+ config,
1963
+ root: config.build?.root ?? "decks",
1964
+ outDir: config.build?.outDir ?? "src/generated",
1965
+ ogpCacheFile: config.build?.ogpCacheFile
1966
+ };
2072
1967
  }
2073
1968
  function resolveRuntimeEntry() {
2074
- const built = fileURLToPath2(new URL("./mod.js", import.meta.url));
2075
- if (existsSync3(built)) return built;
2076
- return fileURLToPath2(new URL("../mod.ts", import.meta.url));
2077
- }
2078
-
2079
- // src/vite.ts
1969
+ const built = fileURLToPath(new URL("./mod.js", import.meta.url));
1970
+ if (existsSync(built)) return built;
1971
+ return fileURLToPath(new URL("../mod.ts", import.meta.url));
1972
+ }
1973
+ //#endregion
1974
+ //#region src/vite.ts
1975
+ /**
1976
+ * Compiles decks before Vite starts and regenerates them while authoring.
1977
+ *
1978
+ * Add this plugin to the same Vite config that hosts Hono or HonoX. The
1979
+ * project's ordinary `vite` development command is then sufficient; no
1980
+ * separate `hono-decks compile --watch` process is required.
1981
+ */
2080
1982
  function honoDecks(options = {}) {
2081
- let projectRoot = process.cwd();
2082
- let configPath = resolve2(projectRoot, options.configFile ?? DEFAULT_DECKS_CONFIG_FILE);
2083
- let deckRoot = resolve2(projectRoot, "decks");
2084
- let resolvedConfig;
2085
- let server;
2086
- let watchedPaths = /* @__PURE__ */ new Set();
2087
- let timer;
2088
- let compiling = false;
2089
- let queued = false;
2090
- const syncWatchedPaths = async () => {
2091
- if (!server) return;
2092
- const next = /* @__PURE__ */ new Set([deckRoot, configPath]);
2093
- const removed = [...watchedPaths].filter((path) => !next.has(path));
2094
- if (removed.length > 0) await server.watcher.unwatch(removed);
2095
- server.watcher.add([...next]);
2096
- watchedPaths = next;
2097
- };
2098
- const compileOnce = async () => {
2099
- const loaded = await loadDecksConfig({ cwd: projectRoot, configFile: options.configFile });
2100
- const manifest = await compileDecks({
2101
- cwd: projectRoot,
2102
- root: loaded.root,
2103
- out: loaded.outDir,
2104
- mountPath: loaded.config.mountPath,
2105
- ogpCacheFile: loaded.ogpCacheFile,
2106
- refreshOgp: options.refreshOgp
2107
- });
2108
- configPath = loaded.path;
2109
- deckRoot = resolve2(projectRoot, loaded.root);
2110
- await syncWatchedPaths();
2111
- resolvedConfig?.logger.info(`[hono-decks] Compiled ${manifest.decks.length} decks to ${loaded.outDir}`);
2112
- };
2113
- const compile2 = async (fatal) => {
2114
- if (compiling) {
2115
- queued = true;
2116
- return;
2117
- }
2118
- compiling = true;
2119
- let latestCompileSucceeded = false;
2120
- try {
2121
- do {
2122
- queued = false;
2123
- try {
2124
- await compileOnce();
2125
- latestCompileSucceeded = true;
2126
- } catch (error) {
2127
- latestCompileSucceeded = false;
2128
- if (fatal) throw error;
2129
- resolvedConfig?.logger.error(`[hono-decks] ${error instanceof Error ? error.message : String(error)}`);
2130
- }
2131
- } while (queued);
2132
- if (latestCompileSucceeded && server) server.ws.send({ type: "full-reload" });
2133
- } finally {
2134
- compiling = false;
2135
- }
2136
- };
2137
- const scheduleCompile = () => {
2138
- if (timer) clearTimeout(timer);
2139
- timer = setTimeout(() => void compile2(false), 75);
2140
- };
2141
- const onWatcherEvent = (event, path) => {
2142
- if (event !== "add" && event !== "change" && event !== "unlink") return;
2143
- const absolutePath = resolve2(projectRoot, path);
2144
- if (absolutePath === configPath || isWithin(deckRoot, absolutePath)) scheduleCompile();
2145
- };
2146
- return {
2147
- name: "hono-decks",
2148
- enforce: "pre",
2149
- async configResolved(config) {
2150
- resolvedConfig = config;
2151
- projectRoot = config.root;
2152
- configPath = resolve2(projectRoot, options.configFile ?? DEFAULT_DECKS_CONFIG_FILE);
2153
- await compile2(true);
2154
- },
2155
- configureServer(devServer) {
2156
- server = devServer;
2157
- void syncWatchedPaths();
2158
- devServer.watcher.on("all", onWatcherEvent);
2159
- const cleanup = () => {
2160
- if (timer) clearTimeout(timer);
2161
- devServer.watcher.off("all", onWatcherEvent);
2162
- server = void 0;
2163
- watchedPaths.clear();
2164
- };
2165
- devServer.httpServer?.once("close", cleanup);
2166
- }
2167
- };
1983
+ let projectRoot = process.cwd();
1984
+ let configPath = resolve(projectRoot, options.configFile ?? "hono-decks.config.ts");
1985
+ let deckRoot = resolve(projectRoot, "decks");
1986
+ let resolvedConfig;
1987
+ let server;
1988
+ let watchedPaths = /* @__PURE__ */ new Set();
1989
+ let timer;
1990
+ let compiling = false;
1991
+ let queued = false;
1992
+ const syncWatchedPaths = async () => {
1993
+ if (!server) return;
1994
+ const next = /* @__PURE__ */ new Set([deckRoot, configPath]);
1995
+ const removed = [...watchedPaths].filter((path) => !next.has(path));
1996
+ if (removed.length > 0) await server.watcher.unwatch(removed);
1997
+ server.watcher.add([...next]);
1998
+ watchedPaths = next;
1999
+ };
2000
+ const compileOnce = async () => {
2001
+ const loaded = await loadDecksConfig({
2002
+ cwd: projectRoot,
2003
+ configFile: options.configFile
2004
+ });
2005
+ const manifest = await compileDecks({
2006
+ cwd: projectRoot,
2007
+ root: loaded.root,
2008
+ out: loaded.outDir,
2009
+ mountPath: loaded.config.mountPath,
2010
+ ogpCacheFile: loaded.ogpCacheFile,
2011
+ refreshOgp: options.refreshOgp
2012
+ });
2013
+ configPath = loaded.path;
2014
+ deckRoot = resolve(projectRoot, loaded.root);
2015
+ await syncWatchedPaths();
2016
+ resolvedConfig?.logger.info(`[hono-decks] Compiled ${manifest.decks.length} decks to ${loaded.outDir}`);
2017
+ };
2018
+ const compile = async (fatal) => {
2019
+ if (compiling) {
2020
+ queued = true;
2021
+ return;
2022
+ }
2023
+ compiling = true;
2024
+ let latestCompileSucceeded = false;
2025
+ try {
2026
+ do {
2027
+ queued = false;
2028
+ try {
2029
+ await compileOnce();
2030
+ latestCompileSucceeded = true;
2031
+ } catch (error) {
2032
+ latestCompileSucceeded = false;
2033
+ if (fatal) throw error;
2034
+ resolvedConfig?.logger.error(`[hono-decks] ${error instanceof Error ? error.message : String(error)}`);
2035
+ }
2036
+ } while (queued);
2037
+ if (latestCompileSucceeded && server) server.ws.send({ type: "full-reload" });
2038
+ } finally {
2039
+ compiling = false;
2040
+ }
2041
+ };
2042
+ const scheduleCompile = () => {
2043
+ if (timer) clearTimeout(timer);
2044
+ timer = setTimeout(() => void compile(false), 75);
2045
+ };
2046
+ const onWatcherEvent = (event, path) => {
2047
+ if (event !== "add" && event !== "change" && event !== "unlink") return;
2048
+ const absolutePath = resolve(projectRoot, path);
2049
+ if (absolutePath === configPath || isWithin(deckRoot, absolutePath)) scheduleCompile();
2050
+ };
2051
+ return {
2052
+ name: "hono-decks",
2053
+ enforce: "pre",
2054
+ async configResolved(config) {
2055
+ resolvedConfig = config;
2056
+ projectRoot = config.root;
2057
+ configPath = resolve(projectRoot, options.configFile ?? "hono-decks.config.ts");
2058
+ await compile(true);
2059
+ },
2060
+ configureServer(devServer) {
2061
+ server = devServer;
2062
+ syncWatchedPaths();
2063
+ devServer.watcher.on("all", onWatcherEvent);
2064
+ const cleanup = () => {
2065
+ if (timer) clearTimeout(timer);
2066
+ devServer.watcher.off("all", onWatcherEvent);
2067
+ server = void 0;
2068
+ watchedPaths.clear();
2069
+ };
2070
+ devServer.httpServer?.once("close", cleanup);
2071
+ }
2072
+ };
2168
2073
  }
2169
2074
  function isWithin(root, path) {
2170
- const child = relative3(root, path);
2171
- return child === "" || child !== ".." && !child.startsWith("../") && !child.startsWith("..\\");
2075
+ const child = relative(root, path);
2076
+ return child === "" || child !== ".." && !child.startsWith("../") && !child.startsWith("..\\");
2172
2077
  }
2173
- export {
2174
- honoDecks
2175
- };
2078
+ //#endregion
2079
+ export { honoDecks };