hono-decks 0.2.0 → 0.2.1

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,919 @@
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
+ for (const match of markdown.matchAll(/^\s*(?:background|image|src|asset):\s*['"]?([^'"\n]+)['"]?\s*$/gim)) candidates.push(match[1].trim());
353
+ for (const match of markdown.matchAll(/!\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g)) candidates.push(match[1].trim());
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 pathname = path.replace(/\?.*$/, "").toLowerCase();
364
+ if (/\.png(?:#.*)?$/.test(pathname)) return "image/png";
365
+ if (/\.jpe?g(?:#.*)?$/.test(pathname)) return "image/jpeg";
366
+ if (/\.gif(?:#.*)?$/.test(pathname)) return "image/gif";
367
+ if (/\.svg(?:#.*)?$/.test(pathname)) return "image/svg+xml";
368
+ if (/\.webp(?:#.*)?$/.test(pathname)) return "image/webp";
396
369
  }
397
370
  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;
371
+ if (/^https?:\/\//i.test(value)) return "remote";
372
+ if (/^r2:\/\//i.test(value)) return "r2";
373
+ if (value.startsWith("/")) return "public";
402
374
  }
403
-
404
- // src/deck/speaker-notes.ts
375
+ //#endregion
376
+ //#region src/deck/speaker-notes.ts
405
377
  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
- };
378
+ const notes = [];
379
+ return {
380
+ body: source.replace(/\{\/\*([\s\S]*?)\*\/\}/g, (_match, rawNote) => {
381
+ const note = normalizeSpeakerNote(rawNote);
382
+ if (note) notes.push(note);
383
+ return "";
384
+ }),
385
+ ...notes.length ? { notes: notes.join("\n\n") } : {}
386
+ };
416
387
  }
417
388
  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;
389
+ const combined = notes.map((note) => note?.trim()).filter((note) => Boolean(note));
390
+ return combined.length ? combined.join("\n\n") : void 0;
420
391
  }
421
392
  function normalizeSpeakerNote(value) {
422
- return value.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).join("\n").trim();
393
+ return value.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).join("\n").trim();
423
394
  }
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;
395
+ //#endregion
396
+ //#region src/parser/parser.ts
397
+ const slideSeparator = /^---\s*$/m;
431
398
  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
- };
399
+ const normalized = markdown.replace(/\r\n/g, "\n").trim();
400
+ 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);
401
+ return {
402
+ slides: parsedSlides.map(({ slide }) => slide),
403
+ warnings: parsedSlides.flatMap(({ warnings }) => warnings)
404
+ };
439
405
  }
440
406
  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
- };
407
+ const { attrs, body } = readSlideAttributes(source);
408
+ const parsed = parseContent(body, index);
409
+ const firstHeading = parsed.blocks.find((block) => block.type === "heading");
410
+ return {
411
+ slide: {
412
+ index,
413
+ title: stringAttr(attrs.title) ?? firstHeading?.text,
414
+ layout: stringAttr(attrs.layout) ?? (index === 0 ? "cover" : "default"),
415
+ className: stringAttr(attrs.class),
416
+ blocks: parsed.blocks,
417
+ nodes: parsed.nodes,
418
+ raw: source
419
+ },
420
+ warnings: parsed.warnings
421
+ };
456
422
  }
457
423
  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() };
424
+ const lines = source.split("\n");
425
+ const attrs = {};
426
+ let cursor = 0;
427
+ while (cursor < lines.length) {
428
+ const line = lines[cursor];
429
+ const match = /^(title|layout|class):\s*(.+)$/.exec(line);
430
+ if (!match) break;
431
+ attrs[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, "");
432
+ cursor += 1;
433
+ }
434
+ return {
435
+ attrs,
436
+ body: lines.slice(cursor).join("\n").trim()
437
+ };
469
438
  }
470
439
  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
- };
440
+ const { root, warnings: parseWarnings } = parseMarkdownTree(body, slideIndex);
441
+ const blockResults = (root.children ?? []).map((child) => toBlocks(child, slideIndex, body));
442
+ const nodeResults = (root.children ?? []).map((child) => toNodes(child, slideIndex, body));
443
+ return {
444
+ blocks: blockResults.flatMap(({ blocks }) => blocks),
445
+ nodes: nodeResults.flatMap(({ nodes }) => nodes),
446
+ warnings: [
447
+ ...fenceWarningsFor(body, slideIndex),
448
+ ...parseWarnings,
449
+ ...blockResults.flatMap(({ warnings }) => warnings),
450
+ ...nodeResults.flatMap(({ warnings }) => warnings)
451
+ ]
452
+ };
484
453
  }
485
454
  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
- }
455
+ try {
456
+ return {
457
+ root: unified().use(remarkParse).use(remarkGfm).use(remarkMdx).parse(body),
458
+ warnings: []
459
+ };
460
+ } catch (error) {
461
+ const message = error instanceof Error ? error.message : "Unknown MDX parse error";
462
+ return {
463
+ root: unified().use(remarkParse).use(remarkGfm).parse(body),
464
+ warnings: [parserWarning("mdx-parse-error", `Slide ${slideIndex + 1}: ${message}`, slideIndex)]
465
+ };
466
+ }
498
467
  }
499
468
  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)];
469
+ let openFence;
470
+ for (const line of body.split("\n")) {
471
+ const match = /^(```|~~~)/.exec(line);
472
+ if (!match) continue;
473
+ if (!openFence) {
474
+ openFence = match[1];
475
+ continue;
476
+ }
477
+ if (line.startsWith(openFence)) openFence = void 0;
478
+ }
479
+ if (!openFence) return [];
480
+ return [parserWarning("code-fence-unclosed", `Slide ${slideIndex + 1}: code fence is not closed.`, slideIndex)];
512
481
  }
513
482
  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
- }
483
+ switch (node.type) {
484
+ case "heading": return blockResult([{
485
+ type: "heading",
486
+ depth: headingDepth(node.depth),
487
+ text: textContent(node)
488
+ }]);
489
+ case "paragraph": {
490
+ const image = soleImage(node);
491
+ if (image) return blockResult([{
492
+ type: "image",
493
+ alt: image.alt ?? "",
494
+ src: image.url ?? "",
495
+ ...image.title ? { title: image.title } : {}
496
+ }]);
497
+ return blockResult([{
498
+ type: "paragraph",
499
+ text: textContent(node).replace(/\s+/g, " ").trim()
500
+ }]);
501
+ }
502
+ case "list": return blockResult([{
503
+ type: "list",
504
+ ordered: node.ordered === true,
505
+ items: (node.children ?? []).map((item) => taskListItemText(item)).filter(Boolean)
506
+ }]);
507
+ case "code": return blockResult([{
508
+ type: "code",
509
+ lang: node.lang ?? void 0,
510
+ code: node.value ?? ""
511
+ }]);
512
+ case "blockquote": return blockResult([{
513
+ type: "blockquote",
514
+ text: textContent(node).trim()
515
+ }]);
516
+ case "table": return blockResult([tableBlock(node)]);
517
+ case "mdxJsxFlowElement":
518
+ case "mdxJsxTextElement": return jsxElementBlock(node, slideIndex, source);
519
+ case "mdxjsEsm":
520
+ case "mdxFlowExpression":
521
+ case "mdxTextExpression":
522
+ case "thematicBreak": return blockResult([]);
523
+ default: return blockResult(textContent(node).trim() ? [{
524
+ type: "paragraph",
525
+ text: textContent(node).trim()
526
+ }] : []);
527
+ }
556
528
  }
557
529
  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() }]);
530
+ const name = node.name ?? "";
531
+ if (!name) return blockResult([]);
532
+ if (isComponentName(name)) {
533
+ const parsedProps = parseProps(node.attributes ?? [], slideIndex, name);
534
+ return blockResult([{
535
+ type: "component",
536
+ name,
537
+ props: parsedProps.props,
538
+ raw: sourceForNode(node, source) ?? `<${name} />`
539
+ }], parsedProps.warnings);
540
+ }
541
+ return blockResult([{
542
+ type: "paragraph",
543
+ text: textContent(node).trim()
544
+ }]);
575
545
  }
576
546
  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
- }
547
+ switch (node.type) {
548
+ case "text": return nodeResult(node.value ? [{
549
+ type: "text",
550
+ value: node.value
551
+ }] : []);
552
+ case "emphasis": return elementNodeResult(node, "em", slideIndex, source);
553
+ case "strong": return elementNodeResult(node, "strong", slideIndex, source);
554
+ case "delete": return elementNodeResult(node, "del", slideIndex, source);
555
+ case "inlineCode": return nodeResult([{
556
+ type: "element",
557
+ tag: "code",
558
+ props: {},
559
+ children: [{
560
+ type: "text",
561
+ value: node.value ?? ""
562
+ }]
563
+ }]);
564
+ case "break": return nodeResult([{
565
+ type: "element",
566
+ tag: "br",
567
+ props: {},
568
+ children: []
569
+ }]);
570
+ case "heading": return elementNodeResult(node, `h${headingDepth(node.depth)}`, slideIndex, source);
571
+ case "paragraph":
572
+ if (soleImage(node)) return childrenToNodes(node, slideIndex, source);
573
+ if (isJsxOnlyParagraph(node)) return childrenToNodes(node, slideIndex, source);
574
+ return elementNodeResult(node, "p", slideIndex, source);
575
+ case "list": {
576
+ const tag = node.ordered === true ? "ol" : "ul";
577
+ const items = node.children ?? [];
578
+ const childResults = items.map((child) => childrenToNodes(child, slideIndex, source));
579
+ return nodeResult([{
580
+ type: "element",
581
+ tag,
582
+ props: {},
583
+ children: childResults.map(({ nodes }, itemIndex) => listItemNode(nodes, items[itemIndex]?.checked))
584
+ }], childResults.flatMap(({ warnings }) => warnings));
585
+ }
586
+ case "listItem": return childrenToNodes(node, slideIndex, source);
587
+ case "code": return nodeResult([{
588
+ type: "code",
589
+ lang: node.lang ?? void 0,
590
+ value: node.value ?? ""
591
+ }]);
592
+ case "blockquote": return elementNodeResult(node, "blockquote", slideIndex, source);
593
+ case "image": return nodeResult([{
594
+ type: "element",
595
+ tag: "img",
596
+ props: {
597
+ src: node.url ?? "",
598
+ alt: node.alt ?? "",
599
+ ...node.title ? { title: node.title } : {}
600
+ },
601
+ children: []
602
+ }]);
603
+ case "link": return elementNodeResult(node, "a", slideIndex, source, { href: node.url ?? "" });
604
+ case "table": return tableNodes(node, slideIndex, source);
605
+ case "mdxJsxFlowElement":
606
+ case "mdxJsxTextElement": return jsxElementNodes(node, slideIndex, source);
607
+ case "mdxjsEsm": return nodeResult([], [parserWarning("mdx-import-export-ignored", `Slide ${slideIndex + 1}: MDX import/export syntax is ignored.`, slideIndex)]);
608
+ case "mdxFlowExpression":
609
+ case "mdxTextExpression": return nodeResult([], [parserWarning("mdx-expression-ignored", `Slide ${slideIndex + 1}: MDX JavaScript expressions are ignored.`, slideIndex)]);
610
+ case "thematicBreak": return nodeResult([{
611
+ type: "element",
612
+ tag: "hr",
613
+ props: {},
614
+ children: []
615
+ }]);
616
+ default: return childrenToNodes(node, slideIndex, source);
617
+ }
663
618
  }
664
619
  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
- );
620
+ const children = childrenToNodes(node, slideIndex, source);
621
+ return nodeResult([{
622
+ type: "element",
623
+ tag,
624
+ props,
625
+ children: children.nodes
626
+ }], children.warnings);
677
627
  }
678
628
  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
- };
629
+ if (typeof checked !== "boolean") return {
630
+ type: "element",
631
+ tag: "li",
632
+ props: {},
633
+ children
634
+ };
635
+ return {
636
+ type: "element",
637
+ tag: "li",
638
+ props: { class: "task-list-item" },
639
+ children: [
640
+ {
641
+ type: "element",
642
+ tag: "input",
643
+ props: {
644
+ type: "checkbox",
645
+ disabled: true,
646
+ ...checked ? { checked: true } : {}
647
+ },
648
+ children: []
649
+ },
650
+ {
651
+ type: "text",
652
+ value: " "
653
+ },
654
+ ...children
655
+ ]
656
+ };
695
657
  }
696
658
  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
- );
659
+ const align = node.align ?? [];
660
+ const [headerRow, ...bodyRows] = node.children ?? [];
661
+ const headerResult = headerRow ? tableRowCells(headerRow, "th", align, slideIndex, source) : cellsResult([]);
662
+ const bodyResults = bodyRows.map((row) => tableRowCells(row, "td", align, slideIndex, source));
663
+ return nodeResult([{
664
+ type: "element",
665
+ tag: "table",
666
+ props: {},
667
+ children: [{
668
+ type: "element",
669
+ tag: "thead",
670
+ props: {},
671
+ children: [{
672
+ type: "element",
673
+ tag: "tr",
674
+ props: {},
675
+ children: headerResult.cells
676
+ }]
677
+ }, {
678
+ type: "element",
679
+ tag: "tbody",
680
+ props: {},
681
+ children: bodyResults.map(({ cells }) => ({
682
+ type: "element",
683
+ tag: "tr",
684
+ props: {},
685
+ children: cells
686
+ }))
687
+ }]
688
+ }], [...headerResult.warnings, ...bodyResults.flatMap(({ warnings }) => warnings)]);
725
689
  }
726
690
  function cellsResult(cells, emittedWarnings = []) {
727
- return { cells, warnings: emittedWarnings };
691
+ return {
692
+ cells,
693
+ warnings: emittedWarnings
694
+ };
728
695
  }
729
696
  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
- );
697
+ const results = (row.children ?? []).map((cell, cellIndex) => {
698
+ const children = childrenToNodes(cell, slideIndex, source);
699
+ const cellAlign = toTableAlign(align[cellIndex]);
700
+ return {
701
+ cell: {
702
+ type: "element",
703
+ tag,
704
+ props: cellAlign ? { style: `text-align:${cellAlign}` } : {},
705
+ children: children.nodes
706
+ },
707
+ warnings: children.warnings
708
+ };
709
+ });
710
+ return cellsResult(results.map(({ cell }) => cell), results.flatMap(({ warnings }) => warnings));
747
711
  }
748
712
  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);
713
+ const name = node.name ?? "";
714
+ if (!name) return childrenToNodes(node, slideIndex, source);
715
+ if (isUnsafeHtmlElement(name)) return nodeResult([{
716
+ type: "text",
717
+ value: sourceForNode(node, source) ?? textContent(node)
718
+ }]);
719
+ const parsedProps = parseProps(node.attributes ?? [], slideIndex, name);
720
+ const parsedChildren = childrenToNodes(node, slideIndex, source);
721
+ const children = parsedChildren.nodes.filter((child) => child.type !== "text" || child.value.trim() !== "");
722
+ const warnings = [...parsedProps.warnings, ...parsedChildren.warnings];
723
+ if (isComponentName(name)) return nodeResult([{
724
+ type: "component",
725
+ name,
726
+ props: parsedProps.props,
727
+ children
728
+ }], warnings);
729
+ return nodeResult([{
730
+ type: "element",
731
+ tag: name,
732
+ props: parsedProps.props,
733
+ children
734
+ }], warnings);
760
735
  }
761
736
  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
- );
737
+ const results = (node.children ?? []).map((child) => toNodes(child, slideIndex, source));
738
+ return nodeResult(results.flatMap(({ nodes }) => nodes), results.flatMap(({ warnings }) => warnings));
767
739
  }
768
740
  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 };
741
+ const props = {};
742
+ return {
743
+ props,
744
+ warnings: attributes.flatMap((attr) => {
745
+ if (attr.type !== "mdxJsxAttribute" || !attr.name) return [];
746
+ if (attr.value === null || attr.value === void 0) {
747
+ props[attr.name] = true;
748
+ return [];
749
+ }
750
+ if (typeof attr.value === "string" || typeof attr.value === "number" || typeof attr.value === "boolean") {
751
+ props[attr.name] = attr.value;
752
+ return [];
753
+ }
754
+ return [parserWarning("mdx-expression-prop-ignored", `Slide ${slideIndex + 1}: MDX JavaScript expression props are ignored on ${ignoredPropTarget(componentName, attr.name)}.`, slideIndex)];
755
+ })
756
+ };
789
757
  }
790
758
  function blockResult(blocks, emittedWarnings = []) {
791
- return { blocks, warnings: emittedWarnings };
759
+ return {
760
+ blocks,
761
+ warnings: emittedWarnings
762
+ };
792
763
  }
793
764
  function nodeResult(nodes, emittedWarnings = []) {
794
- return { nodes, warnings: emittedWarnings };
765
+ return {
766
+ nodes,
767
+ warnings: emittedWarnings
768
+ };
795
769
  }
796
770
  function parserWarning(code, message, slideIndex) {
797
- return { code, message, slideIndex };
771
+ return {
772
+ code,
773
+ message,
774
+ slideIndex
775
+ };
798
776
  }
799
777
  function ignoredPropTarget(componentName, propName) {
800
- if (propName.startsWith("$")) return `${componentName} dynamic prop`;
801
- return `${componentName}.${propName}`;
778
+ if (propName.startsWith("$")) return `${componentName} dynamic prop`;
779
+ return `${componentName}.${propName}`;
802
780
  }
803
781
  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("");
782
+ if (typeof node.value === "string") return node.value;
783
+ if (node.type === "strong") return `**${(node.children ?? []).map(textContent).join("")}**`;
784
+ if (node.type === "emphasis") return `*${(node.children ?? []).map(textContent).join("")}*`;
785
+ if (node.type === "delete") return `~~${(node.children ?? []).map(textContent).join("")}~~`;
786
+ if (node.type === "inlineCode") return `\`${node.value ?? ""}\``;
787
+ if ((node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.name && !isComponentName(node.name)) return `<${node.name}>${(node.children ?? []).map(textContent).join("")}</${node.name}>`;
788
+ return (node.children ?? []).map(textContent).join("");
813
789
  }
814
790
  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;
791
+ const text = textContent(item).replace(/\s+/g, " ").trim();
792
+ if (typeof item.checked === "boolean") return `${item.checked ? "[x]" : "[ ]"} ${text}`.trim();
793
+ return text;
818
794
  }
819
795
  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
- };
796
+ const [header = [], ...bodyRows] = (node.children ?? []).map((row) => (row.children ?? []).map((cell) => textContent(cell).replace(/\s+/g, " ").trim()));
797
+ return {
798
+ type: "table",
799
+ align: (node.align ?? []).map(toTableAlign),
800
+ header,
801
+ rows: bodyRows
802
+ };
830
803
  }
831
804
  function toTableAlign(value) {
832
- return value === "left" || value === "center" || value === "right" ? value : void 0;
805
+ return value === "left" || value === "center" || value === "right" ? value : void 0;
833
806
  }
834
807
  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;
808
+ const meaningful = (node.children ?? []).filter((child) => child.type !== "text" || child.value?.trim());
809
+ return meaningful.length === 1 && meaningful[0].type === "image" ? meaningful[0] : void 0;
837
810
  }
838
811
  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
- );
812
+ const children = node.children ?? [];
813
+ return children.length > 0 && children.every((child) => child.type === "mdxJsxTextElement" || child.type === "mdxJsxFlowElement" || child.type === "text" && child.value?.trim() === "");
843
814
  }
844
815
  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;
816
+ const start = node.position?.start?.offset;
817
+ const end = node.position?.end?.offset;
818
+ return typeof start === "number" && typeof end === "number" ? source.slice(start, end) : void 0;
848
819
  }
849
820
  function isComponentName(name) {
850
- return /^[A-Z]/.test(name);
821
+ return /^[A-Z]/.test(name);
851
822
  }
852
823
  function isUnsafeHtmlElement(name) {
853
- return /^(script|style|iframe|object|embed)$/i.test(name);
824
+ return /^(script|style|iframe|object|embed)$/i.test(name);
854
825
  }
855
826
  function headingDepth(value) {
856
- if (value === 1 || value === 2 || value === 3) return value;
857
- return 3;
827
+ if (value === 1 || value === 2 || value === 3) return value;
828
+ return 3;
858
829
  }
859
830
  function stringAttr(value) {
860
- return value && value.length > 0 ? value : void 0;
831
+ return value && value.length > 0 ? value : void 0;
861
832
  }
862
-
863
- // src/generator/mdx/assets.ts
833
+ //#endregion
834
+ //#region src/generator/mdx/assets.ts
864
835
  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;
836
+ let result = source;
837
+ for (const asset of assets) {
838
+ const assetPath = localAssetRelativePath(asset.sourcePath);
839
+ result = result.replaceAll(`./assets/${assetPath}`, asset.publicPath);
840
+ result = result.replace(new RegExp(`(?<!/)assets/${escapeRegExp(assetPath)}`, "g"), asset.publicPath);
841
+ }
842
+ return result;
872
843
  }
873
844
  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
- });
845
+ return source.replace(/(from\s+|import\s+)(["'])(\.[^"']+)\2/g, (_match, prefix, quote, specifier) => {
846
+ return `${prefix}${quote}${toRelativeImportPath(generatedDir, normalizePath$2(`${sourceDir}/${specifier}`))}${quote}`;
847
+ });
878
848
  }
879
849
  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);
850
+ if (!sourcePath) return void 0;
851
+ return toRelativeImportPath(outDir, sourcePath.replace(/\/index\.(tsx|ts|jsx|js)$/, ""));
883
852
  }
884
- function dirname(path) {
885
- const normalized = normalizePath2(path);
886
- const index = normalized.lastIndexOf("/");
887
- return index === -1 ? "." : normalized.slice(0, index);
853
+ function dirname$1(path) {
854
+ const normalized = normalizePath$2(path);
855
+ const index = normalized.lastIndexOf("/");
856
+ return index === -1 ? "." : normalized.slice(0, index);
888
857
  }
889
858
  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);
859
+ const marker = "/assets/";
860
+ const normalized = normalizePath$2(sourcePath);
861
+ const markerIndex = normalized.indexOf(marker);
862
+ return markerIndex === -1 ? normalized.split("/").at(-1) ?? normalized : normalized.slice(markerIndex + 8);
894
863
  }
895
- function normalizePath2(path) {
896
- return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
864
+ function normalizePath$2(path) {
865
+ return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
897
866
  }
898
867
  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 || "."}`;
868
+ const fromParts = normalizePath$2(fromDir).split("/").filter(Boolean);
869
+ const targetParts = normalizePath$2(target).split("/").filter(Boolean);
870
+ while (fromParts.length > 0 && targetParts.length > 0 && fromParts[0] === targetParts[0]) {
871
+ fromParts.shift();
872
+ targetParts.shift();
873
+ }
874
+ const relative = [...fromParts.map(() => ".."), ...targetParts].join("/");
875
+ return relative.startsWith(".") ? relative : `./${relative || "."}`;
907
876
  }
908
877
  function escapeRegExp(value) {
909
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
878
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
910
879
  }
911
-
912
- // src/generator/assets.ts
880
+ //#endregion
881
+ //#region src/generator/assets.ts
913
882
  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
- );
883
+ return Promise.all(assetPaths.map(async (sourcePath) => ({
884
+ sourcePath,
885
+ publicPath: `${normalizeMountPath(input.mountPath ?? `/${input.root}`)}/${encodeURIComponent(slug)}/assets/${assetName(sourcePath, input.root, slug)}`,
886
+ type: "local",
887
+ contentType: contentTypeForPath(sourcePath),
888
+ body: input.readBinary ? await input.readBinary(sourcePath) : void 0
889
+ })));
927
890
  }
928
891
  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("/");
892
+ const normalizedPath = normalizePath$1(sourcePath);
893
+ const prefix = `${normalizePath$1(root).replace(/\/$/, "")}/${slug}/assets/`;
894
+ return (normalizedPath.startsWith(prefix) ? normalizedPath.slice(prefix.length) : normalizedPath.split("/").at(-1) ?? normalizedPath).split("/").map(encodeURIComponent).join("/");
933
895
  }
934
896
  function normalizeMountPath(value) {
935
- const withLeadingSlash = value.startsWith("/") ? value : `/${value}`;
936
- return withLeadingSlash.replace(/\/$/, "");
897
+ return (value.startsWith("/") ? value : `/${value}`).replace(/\/$/, "");
937
898
  }
938
- function normalizePath3(path) {
939
- return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
899
+ function normalizePath$1(path) {
900
+ return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
940
901
  }
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
902
+ //#endregion
903
+ //#region src/generator/package-entry.ts
904
+ const DECKS_RUNTIME_ENTRY = "hono-decks";
905
+ const DECKS_ADVANCED_ENTRY = "hono-decks/advanced";
906
+ //#endregion
907
+ //#region src/generator/mdx/emit.ts
952
908
  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};
909
+ const runtimeEntry = JSON.stringify(DECKS_RUNTIME_ENTRY);
910
+ return `// @ts-nocheck
911
+ import { configureDecks, defineDecks } from ${JSON.stringify(DECKS_ADVANCED_ENTRY)};
963
912
  import type { ConfiguredDecks, DecksConfig } from ${runtimeEntry};
964
913
  import type { Env } from "hono";
965
914
  import { decksClientEntry } from "./client-entry";
966
- ${slideImports}
967
- ${componentImports}
915
+ ${input.decks.flatMap((deck) => deck.slideModules.map((slide, index) => `import ${slideImportName(deck.deck.slug, index)} from ${JSON.stringify(slide.importPath)};`)).join("\n")}
916
+ ${input.decks.filter((deck) => deck.componentModulePath).map((deck) => `import * as ${componentImportName(deck.deck.slug)} from ${JSON.stringify(deck.componentModulePath)};`).join("\n")}
968
917
 
969
918
  function withClientComponentIds(module, clientIds) {
970
919
  const registry = {};
@@ -998,589 +947,510 @@ function definedDecksFor<E extends Env>() {
998
947
  `;
999
948
  }
1000
949
  function emitDeckObject(deck) {
1001
- return ` {
950
+ return ` {
1002
951
  slug: ${JSON.stringify(deck.deck.slug)},
1003
952
  sourcePath: ${JSON.stringify(deck.deck.sourcePath)},
1004
953
  kind: ${JSON.stringify(deck.deck.kind)},
1005
954
  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
- ` : ""}
955
+ ${deck.deck.themeStyle ? ` "themeStyle": ${serializeValue(deck.deck.themeStyle, 3)},\n` : ""}${deck.deck.themeSourcePath ? ` "themeSourcePath": ${JSON.stringify(deck.deck.themeSourcePath)},\n` : ""}
1009
956
  assets: ${serializeValue(deck.deck.assets, 3)},
1010
957
  componentRegistry: ${deck.componentModulePath ? `withClientComponentIds(${componentImportName(deck.deck.slug)}, ${serializeValue(deck.clientComponentIds ?? {}, 3)})` : "{}"},
1011
958
  warnings: ${serializeValue(deck.deck.warnings, 3)},
1012
959
  slides: [
1013
- ${deck.deck.slides.map(
1014
- (slide) => ` {
960
+ ${deck.deck.slides.map((slide) => ` {
1015
961
  index: ${slide.index},
1016
962
  meta: ${serializeValue(slide.meta, 5)},
1017
963
  html: "",
1018
964
  components: [],
1019
965
  notes: ${serializeValue(slide.notes, 5)},
1020
966
  render: ${slideImportName(deck.deck.slug, slide.index)}
1021
- }`
1022
- ).join(",\n")}
967
+ }`).join(",\n")}
1023
968
  ]
1024
969
  }`;
1025
970
  }
1026
971
  function slideImportName(slug, index) {
1027
- return `Slide_${safeIdentifier(slug)}_${index}`;
972
+ return `Slide_${safeIdentifier$1(slug)}_${index}`;
1028
973
  }
1029
974
  function componentImportName(slug) {
1030
- return `Components_${safeIdentifier(slug)}`;
975
+ return `Components_${safeIdentifier$1(slug)}`;
1031
976
  }
1032
- function safeIdentifier(value) {
1033
- return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
977
+ function safeIdentifier$1(value) {
978
+ return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
1034
979
  }
1035
980
  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
981
+ const indent = " ".repeat(depth);
982
+ const nextIndent = " ".repeat(depth + 1);
983
+ if (value === void 0) return "undefined";
984
+ if (value instanceof Uint8Array) return `new Uint8Array([${[...value].join(", ")}])`;
985
+ if (Array.isArray(value)) {
986
+ if (value.length === 0) return "[]";
987
+ return `[\n${value.map((item) => `${nextIndent}${serializeValue(item, depth + 1)}`).join(",\n")}\n${indent}]`;
988
+ }
989
+ if (typeof value === "object" && value !== null) {
990
+ const entries = Object.entries(value).filter(([, item]) => item !== void 0);
991
+ if (entries.length === 0) return "{}";
992
+ return `{\n${entries.map(([key, item]) => `${nextIndent}${JSON.stringify(key)}: ${serializeValue(item, depth + 1)}`).join(",\n")}\n${indent}}`;
993
+ }
994
+ return JSON.stringify(value);
995
+ }
996
+ //#endregion
997
+ //#region src/generator/mdx/ogp.ts
1057
998
  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;
999
+ const result = /* @__PURE__ */ new Map();
1000
+ if (!resolveOgp) return result;
1001
+ for (const url of collectLinkCardUrls(source)) try {
1002
+ const metadata = await resolveOgp(url);
1003
+ if (metadata) result.set(url, metadata);
1004
+ } catch {}
1005
+ return result;
1068
1006
  }
1069
1007
  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];
1008
+ const urls = /* @__PURE__ */ new Set();
1009
+ for (const match of source.matchAll(/@\[card\]\(([^)\s]+)\)/g)) urls.add(match[1]);
1010
+ return [...urls];
1076
1011
  }
1077
-
1078
- // src/generator/mdx/syntax.ts
1079
- import { codeToHtml } from "shiki";
1012
+ //#endregion
1013
+ //#region src/generator/mdx/syntax.ts
1080
1014
  function remarkDeckSyntax(input = {}) {
1081
- return () => (tree) => {
1082
- transformDeckSyntaxChildren(tree, input);
1083
- };
1015
+ return () => (tree) => {
1016
+ transformDeckSyntaxChildren(tree, input);
1017
+ };
1084
1018
  }
1085
1019
  function remarkCodeHighlight() {
1086
- return async (tree) => {
1087
- await highlightMarkdownNode(tree);
1088
- };
1020
+ return async (tree) => {
1021
+ await highlightMarkdownNode(tree);
1022
+ };
1089
1023
  }
1090
1024
  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;
1025
+ if (!Array.isArray(node.children)) return;
1026
+ const children = [];
1027
+ for (const child of node.children) {
1028
+ transformDeckSyntaxChildren(child, input);
1029
+ rejectRemovedFireAuthoring(child);
1030
+ children.push(fireAttributeNode(child) ?? zennEmbedNode(child, input) ?? plainUrlLinkNode(child) ?? fireDirectiveNode(child) ?? unknownDirectiveFallback(child) ?? child);
1031
+ }
1032
+ node.children = children;
1101
1033
  }
1102
1034
  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
- }
1035
+ if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return;
1036
+ if (!Array.isArray(node.attributes)) return;
1037
+ 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.");
1038
+ 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.");
1039
+ if (node.name === "Fire") {
1040
+ const atAttribute = node.attributes.find((attribute) => attribute.name === "at");
1041
+ if (atAttribute) fireAtAttributeValue(atAttribute.value);
1042
+ }
1115
1043
  }
1116
1044
  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
- );
1045
+ if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return void 0;
1046
+ if (!Array.isArray(node.attributes)) return void 0;
1047
+ const fireAttribute = node.attributes.find((attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "fire");
1048
+ if (!fireAttribute) return void 0;
1049
+ const atAttribute = node.attributes.find((attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "at");
1050
+ if (node.name === "Fire") throw new Error("The \"fire\" attribute is not supported on <Fire>. Use the effect prop instead.");
1051
+ 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.");
1052
+ 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.");
1053
+ 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\".");
1054
+ const effect = typeof fireAttribute.value === "string" ? fireAttribute.value.trim() : "";
1055
+ const at = atAttribute ? fireAtAttributeValue(atAttribute.value) : void 0;
1056
+ node.attributes = node.attributes.filter((attribute) => attribute !== fireAttribute && attribute !== atAttribute);
1057
+ return mdxElement("Fire", [...effect ? [mdxAttribute("effect", effect)] : [], ...at ? [mdxAttribute("at", at)] : []], [node]);
1149
1058
  }
1150
1059
  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".');
1060
+ if (typeof value === "string") {
1061
+ const at = value.trim();
1062
+ if (/^(?:\d+|[+-]\d+)$/.test(at)) return at;
1063
+ }
1064
+ throw new Error("The fire \"at\" attribute accepts a non-negative integer or a relative value such as \"+1\".");
1156
1065
  }
1157
1066
  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;
1067
+ if (node.type !== "paragraph" || !Array.isArray(node.children) || node.children.length !== 2) return void 0;
1068
+ const [prefix, link] = node.children;
1069
+ if (prefix?.type !== "text" || String(prefix.value ?? "").trim() !== "@") return void 0;
1070
+ if (link?.type !== "link" || typeof link.url !== "string") return void 0;
1071
+ const name = collectMarkdownText(link).trim().toLowerCase();
1072
+ if (name === "youtube") return mdxElement("EmbedFrame", [
1073
+ mdxAttribute("provider", "youtube"),
1074
+ mdxAttribute("src", toYoutubeEmbedUrl(link.url)),
1075
+ mdxAttribute("fallbackHref", link.url),
1076
+ mdxAttribute("title", "YouTube embed example")
1077
+ ], [{
1078
+ type: "text",
1079
+ value: "Open YouTube embed"
1080
+ }]);
1081
+ if (name === "x") return mdxElement("TweetEmbed", [mdxAttribute("href", link.url), mdxAttribute("label", "Open post on X")], []);
1082
+ if (name === "card") {
1083
+ const metadata = input.linkCardMetadata?.get(link.url);
1084
+ return mdxElement("LinkCard", [mdxAttribute("href", link.url), ...metadataAttributes(metadata)], []);
1085
+ }
1086
+ if (name === "embed" || name === "iframe") return mdxElement("EmbedFrame", [mdxAttribute("src", link.url), mdxAttribute("title", "Embedded content")], [{
1087
+ type: "text",
1088
+ value: "Open embed"
1089
+ }]);
1197
1090
  }
1198
1091
  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
- ];
1092
+ if (!metadata) return [];
1093
+ return [
1094
+ ...metadata.title ? [mdxAttribute("title", metadata.title)] : [],
1095
+ ...metadata.description ? [mdxAttribute("description", metadata.description)] : [],
1096
+ ...metadata.image ? [mdxAttribute("image", metadata.image)] : [],
1097
+ ...metadata.siteName ? [mdxAttribute("siteName", metadata.siteName)] : []
1098
+ ];
1206
1099
  }
1207
1100
  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
- }
1101
+ if (node.type !== "paragraph" || !Array.isArray(node.children) || node.children.length !== 1) return void 0;
1102
+ const [child] = node.children;
1103
+ if (child?.type !== "text" || typeof child.value !== "string") return void 0;
1104
+ const value = child.value.trim();
1105
+ if (!isHttpUrl$1(value)) return void 0;
1106
+ return {
1107
+ type: "paragraph",
1108
+ children: [{
1109
+ type: "link",
1110
+ url: value,
1111
+ children: [{
1112
+ type: "text",
1113
+ value
1114
+ }]
1115
+ }]
1116
+ };
1117
+ }
1118
+ function isHttpUrl$1(value) {
1119
+ try {
1120
+ const url = new URL(value);
1121
+ return url.protocol === "https:" || url.protocol === "http:";
1122
+ } catch {
1123
+ return false;
1124
+ }
1231
1125
  }
1232
1126
  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 ?? []);
1127
+ if (node.type !== "containerDirective" || node.name !== "fire") return void 0;
1128
+ const attributes = directiveAttributes(node);
1129
+ if (attributes.order !== void 0) throw new Error("The fire \"order\" attribute is not supported. Fires reveal in source order.");
1130
+ 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\".");
1131
+ if (attributes.each !== void 0) return fireEachItemNode(node, attributes);
1132
+ const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
1133
+ return mdxElement("Fire", [...typeof attributes.effect === "string" ? [mdxAttribute("effect", attributes.effect)] : [], ...at ? [mdxAttribute("at", at)] : []], node.children ?? []);
1248
1134
  }
1249
1135
  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;
1136
+ if (attributes.each !== "item") throw new Error("The fire \"each\" attribute only accepts \"item\".");
1137
+ const children = node.children ?? [];
1138
+ const list = children.length === 1 && children[0]?.type === "list" ? children[0] : void 0;
1139
+ if (!list) throw new Error("fire each=\"item\" must contain exactly one Markdown list.");
1140
+ const depth = positiveFireInteger(attributes.depth, "depth", 1);
1141
+ const every = positiveFireInteger(attributes.every, "every", 1);
1142
+ const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
1143
+ const effect = attributes.effect ? fireEffectToken(attributes.effect) : void 0;
1144
+ const items = fireListItems(list, depth);
1145
+ for (const [itemIndex, item] of items.entries()) {
1146
+ const itemAt = fireListItemAt(at, every, itemIndex);
1147
+ item.data = {
1148
+ ...item.data,
1149
+ hProperties: {
1150
+ ...item.data?.hProperties,
1151
+ "data-hono-decks-fire": "true",
1152
+ ...itemAt ? { "data-fire-at": itemAt } : {},
1153
+ ...effect ? { "data-fire-effect": effect } : {}
1154
+ }
1155
+ };
1156
+ }
1157
+ return list;
1276
1158
  }
1277
1159
  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.`);
1160
+ if (value === void 0) return fallback;
1161
+ const parsed = Number(value);
1162
+ if (Number.isInteger(parsed) && parsed > 0) return parsed;
1163
+ throw new Error(`The fire "${name}" attribute accepts a positive integer.`);
1282
1164
  }
1283
1165
  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;
1166
+ const items = [];
1167
+ for (const item of list.children ?? []) {
1168
+ if (item.type !== "listItem") continue;
1169
+ items.push(item);
1170
+ if (depth >= maxDepth) continue;
1171
+ for (const child of item.children ?? []) if (child.type === "list") items.push(...fireListItems(child, maxDepth, depth + 1));
1172
+ }
1173
+ return items;
1294
1174
  }
1295
1175
  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";
1176
+ if (at && /^\d+$/.test(at)) return String(Number(at) + Math.floor(itemIndex / every));
1177
+ if (!at && every === 1) return void 0;
1178
+ if (itemIndex === 0) return at;
1179
+ return itemIndex % every === 0 ? "+1" : "+0";
1300
1180
  }
1301
1181
  function fireEffectToken(value) {
1302
- return value.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "fade";
1182
+ return value.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "fade";
1303
1183
  }
1304
1184
  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;
1185
+ if (node.type === "textDirective") return {
1186
+ type: "text",
1187
+ value: `:${node.name ?? ""}`
1188
+ };
1189
+ if (node.type === "leafDirective") return {
1190
+ type: "text",
1191
+ value: `::${node.name ?? ""}`
1192
+ };
1193
+ if (node.type === "containerDirective") return {
1194
+ type: "paragraph",
1195
+ children: [{
1196
+ type: "text",
1197
+ value: `:::${node.name ?? ""}`
1198
+ }, ...node.children ?? []]
1199
+ };
1314
1200
  }
1315
1201
  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;
1202
+ if (!node.attributes || Array.isArray(node.attributes)) return {};
1203
+ const result = {};
1204
+ for (const [key, value] of Object.entries(node.attributes)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") result[key] = String(value);
1205
+ return result;
1324
1206
  }
1325
1207
  function mdxElement(name, attributes, children) {
1326
- return {
1327
- type: "mdxJsxFlowElement",
1328
- name,
1329
- attributes,
1330
- children
1331
- };
1208
+ return {
1209
+ type: "mdxJsxFlowElement",
1210
+ name,
1211
+ attributes,
1212
+ children
1213
+ };
1332
1214
  }
1333
1215
  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
- }
1216
+ try {
1217
+ const url = new URL(value);
1218
+ const host = url.hostname.replace(/^www\./, "");
1219
+ if (host === "youtu.be") {
1220
+ const id = url.pathname.split("/").filter(Boolean)[0];
1221
+ return id ? `https://www.youtube.com/embed/${id}` : value;
1222
+ }
1223
+ if (host === "youtube.com" || host === "m.youtube.com") {
1224
+ if (url.pathname.startsWith("/embed/")) return value;
1225
+ const id = url.searchParams.get("v");
1226
+ return id ? `https://www.youtube.com/embed/${id}` : value;
1227
+ }
1228
+ return value;
1229
+ } catch {
1230
+ return value;
1231
+ }
1350
1232
  }
1351
1233
  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)));
1234
+ if (node.type === "code") {
1235
+ const code = typeof node.value === "string" ? node.value : "";
1236
+ const lang = typeof node.lang === "string" && node.lang ? node.lang : void 0;
1237
+ const highlightedHtml = await highlightCodeBlock(code, lang);
1238
+ node.type = "mdxJsxFlowElement";
1239
+ node.name = "CodeBlock";
1240
+ node.attributes = [...lang ? [mdxAttribute("lang", lang)] : [], mdxAttribute("highlightedHtml", highlightedHtml)];
1241
+ node.children = [{
1242
+ type: "text",
1243
+ value: code
1244
+ }];
1245
+ delete node.value;
1246
+ delete node.lang;
1247
+ delete node.meta;
1248
+ return;
1249
+ }
1250
+ if (node.type === "mdxJsxFlowElement" && node.name === "CodeBlock") {
1251
+ const code = collectMarkdownText(node);
1252
+ if (code.trim()) {
1253
+ const highlightedHtml = await highlightCodeBlock(code, getMdxStringAttribute(node, "lang"));
1254
+ node.attributes = upsertMdxStringAttribute(node.attributes, "highlightedHtml", highlightedHtml);
1255
+ }
1256
+ return;
1257
+ }
1258
+ if (!Array.isArray(node.children)) return;
1259
+ await Promise.all(node.children.map((child) => highlightMarkdownNode(child)));
1379
1260
  }
1380
1261
  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
- }
1262
+ const language = lang && /^[A-Za-z0-9_#+.-]+$/.test(lang) ? lang : "text";
1263
+ try {
1264
+ return await codeToHtml(code, {
1265
+ lang: language,
1266
+ theme: "github-dark"
1267
+ });
1268
+ } catch (error) {
1269
+ if (language === "text") throw error;
1270
+ return codeToHtml(code, {
1271
+ lang: "text",
1272
+ theme: "github-dark"
1273
+ });
1274
+ }
1388
1275
  }
1389
1276
  function mdxAttribute(name, value) {
1390
- return { type: "mdxJsxAttribute", name, value };
1277
+ return {
1278
+ type: "mdxJsxAttribute",
1279
+ name,
1280
+ value
1281
+ };
1391
1282
  }
1392
1283
  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;
1284
+ const attribute = (Array.isArray(node.attributes) ? node.attributes : []).find((item) => item.type === "mdxJsxAttribute" && item.name === name);
1285
+ return typeof attribute?.value === "string" ? attribute.value : void 0;
1396
1286
  }
1397
1287
  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;
1288
+ const next = Array.isArray(attributes) ? [...attributes] : [];
1289
+ const index = next.findIndex((item) => item.type === "mdxJsxAttribute" && item.name === name);
1290
+ const attribute = mdxAttribute(name, value);
1291
+ if (index === -1) return [...next, attribute];
1292
+ next[index] = attribute;
1293
+ return next;
1404
1294
  }
1405
1295
  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" : "");
1296
+ if (typeof node.value === "string") return node.value;
1297
+ if (!Array.isArray(node.children)) return "";
1298
+ return node.children.map((child) => collectMarkdownText(child)).join(node.type === "paragraph" ? "\n" : "");
1409
1299
  }
1410
-
1411
- // src/generator/mdx-module-generator.ts
1300
+ //#endregion
1301
+ //#region src/generator/mdx-module-generator.ts
1412
1302
  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
- };
1303
+ const decks = await Promise.all(input.decks.map((entry) => compileMdxModuleDeck(input, entry)));
1304
+ return {
1305
+ decks,
1306
+ routerModule: emitModuleDecksRouter({ decks })
1307
+ };
1418
1308
  }
1419
1309
  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
- };
1310
+ const { attrs, body } = readFrontmatter(await input.readText(entry.sourcePath));
1311
+ const { prelude, body: contentBody } = extractDeckPrelude(body);
1312
+ const slideSources = splitSlideSources(contentBody);
1313
+ let warnings = [];
1314
+ const deckMeta = toDeckFrontmatter(attrs, warnings);
1315
+ addUnknownFrontmatterWarnings(warnings, deckMeta.meta, "deck");
1316
+ const assets = [...await buildAssetRefs(entry.slug, entry.assetPaths, input), ...collectGeneratedExternalAssetRefs(contentBody, deckMeta)];
1317
+ const componentModulePath = componentImportPath(input.outDir, input.componentModulePaths?.[entry.slug]);
1318
+ const themeStyle = entry.kind === "directory" ? input.themeStyles?.[entry.slug] : void 0;
1319
+ const slideModules = [];
1320
+ const slides = [];
1321
+ for (let index = 0; index < slideSources.length; index += 1) {
1322
+ const { attrs: slideAttrs, body: slideBody } = readFrontmatter(slideSources[index]);
1323
+ const speakerNotes = extractMdxCommentSpeakerNotes(slideBody);
1324
+ const parsed = parseDeckWithWarnings(speakerNotes.body);
1325
+ warnings = warnings.concat(toGeneratedParserCompileWarnings(parsed.warnings, index));
1326
+ const firstParsedSlide = parsed.slides[0];
1327
+ const slideModulePath = `${input.outDir}/decks/${entry.slug}/slide-${index}.ts`;
1328
+ const slideMeta = toSlideFrontmatter(slideAttrs, warnings, {
1329
+ slideIndex: index,
1330
+ fallbackTransition: deckMeta.transition,
1331
+ fallbackTransitionDuration: deckMeta.transitionDuration,
1332
+ fallbackTransitionEasing: deckMeta.transitionEasing,
1333
+ fallbackTitle: firstParsedSlide?.title,
1334
+ fallbackLayout: firstParsedSlide?.layout,
1335
+ fallbackClassName: firstParsedSlide?.className
1336
+ });
1337
+ addUnknownFrontmatterWarnings(warnings, slideMeta.meta, "slide", index);
1338
+ 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);
1339
+ slideModules.push({
1340
+ path: slideModulePath,
1341
+ importPath: `./decks/${entry.slug}/slide-${index}`,
1342
+ code
1343
+ });
1344
+ slides.push({
1345
+ index,
1346
+ meta: slideMeta,
1347
+ html: "",
1348
+ components: [],
1349
+ notes: combineSpeakerNotes(slideMeta.notes, speakerNotes.notes)
1350
+ });
1351
+ }
1352
+ addExternalAssetWarnings(warnings, assets);
1353
+ return {
1354
+ componentModulePath,
1355
+ clientComponentIds: input.clientComponentIds?.[entry.slug],
1356
+ deck: {
1357
+ slug: entry.slug,
1358
+ sourcePath: entry.sourcePath,
1359
+ kind: entry.kind,
1360
+ meta: deckMeta,
1361
+ themeStyle: themeStyle?.style,
1362
+ themeSourcePath: themeStyle?.sourcePath,
1363
+ slides,
1364
+ assets,
1365
+ warnings
1366
+ },
1367
+ slideModules
1368
+ };
1485
1369
  }
1486
1370
  function collectGeneratedExternalAssetRefs(contentBody, deckMeta) {
1487
- return buildExternalAssetRefs([
1488
- ...collectMarkdownAssetCandidates(contentBody),
1489
- ...collectFrontmatterAssetCandidates(deckMeta.assets)
1490
- ]);
1371
+ return buildExternalAssetRefs([...collectMarkdownAssetCandidates(contentBody), ...collectFrontmatterAssetCandidates(deckMeta.assets)]);
1491
1372
  }
1492
1373
  function generatedMdxParserWarnings(warnings) {
1493
- return warnings.filter((warning) => warning.code === "code-fence-unclosed");
1374
+ return warnings.filter((warning) => warning.code === "code-fence-unclosed");
1494
1375
  }
1495
1376
  function toGeneratedParserCompileWarnings(warnings, slideIndex) {
1496
- return generatedMdxParserWarnings(warnings).map((warning) => ({
1497
- code: "parse-warning",
1498
- message: warning.message,
1499
- slideIndex
1500
- }));
1377
+ return generatedMdxParserWarnings(warnings).map((warning) => ({
1378
+ code: "parse-warning",
1379
+ message: warning.message,
1380
+ slideIndex
1381
+ }));
1501
1382
  }
1502
1383
  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
- }
1384
+ try {
1385
+ const linkCardMetadata = await resolveLinkCardMetadataByUrl(source, resolveOgp);
1386
+ return `// @ts-nocheck\n${String(await compile({
1387
+ path: `${sourcePath}#slide-${slideIndex + 1}`,
1388
+ value: source
1389
+ }, {
1390
+ jsxRuntime: "automatic",
1391
+ jsxImportSource: "hono/jsx",
1392
+ format: "mdx",
1393
+ elementAttributeNameCase: "html",
1394
+ remarkPlugins: [
1395
+ remarkGfm,
1396
+ remarkDirective,
1397
+ remarkDeckSyntax({ linkCardMetadata }),
1398
+ remarkCodeHighlight
1399
+ ]
1400
+ }))}`;
1401
+ } catch (error) {
1402
+ const message = error instanceof Error ? error.message : String(error);
1403
+ throw new CompileError(`MDX compile failed in ${sourcePath} slide ${slideIndex + 1}: ${message}`, "mdx-compile-error");
1404
+ }
1531
1405
  }
1532
1406
  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";
1407
+ const lines = source.replace(/\r\n/g, "\n").split("\n");
1408
+ const prelude = [];
1409
+ let cursor = 0;
1410
+ while (cursor < lines.length) {
1411
+ const line = lines[cursor];
1412
+ if (line.trim() === "") {
1413
+ prelude.push(line);
1414
+ cursor += 1;
1415
+ continue;
1416
+ }
1417
+ if (/^\s*(import|export)\s/.test(line)) {
1418
+ prelude.push(line);
1419
+ cursor += 1;
1420
+ continue;
1421
+ }
1422
+ break;
1423
+ }
1424
+ return {
1425
+ prelude: prelude.join("\n").trim(),
1426
+ body: lines.slice(cursor).join("\n").trim()
1427
+ };
1428
+ }
1429
+ //#endregion
1430
+ //#region src/node/client-entry.ts
1562
1431
  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;
1432
+ const result = {};
1433
+ for (const entry of input.clientEntries) {
1434
+ const exports = extractComponentExportNames(await readFile(join(input.cwd, entry.sourcePath), "utf8"));
1435
+ result[entry.slug] = Object.fromEntries(exports.map((name) => [name, clientComponentId(entry.slug, name)]));
1436
+ }
1437
+ return result;
1570
1438
  }
1571
1439
  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";
1440
+ const imports = [];
1441
+ const registrations = [];
1442
+ for (const entry of input.clientEntries) {
1443
+ const ids = input.clientComponentIds[entry.slug] ?? {};
1444
+ for (const [exportName, clientId] of Object.entries(ids)) {
1445
+ const localName = clientImportName(entry.slug, exportName);
1446
+ imports.push(`import { ${exportName} as ${localName} } from ${JSON.stringify(join(input.cwd, entry.sourcePath))};`);
1447
+ registrations.push(`${JSON.stringify(clientId)}: ${localName}`);
1448
+ }
1449
+ }
1450
+ if (registrations.length === 0) return "export const decksClientEntry = \"\";\n";
1451
+ const output = (await build({
1452
+ stdin: {
1453
+ contents: `import { hydrateSlideIslands } from "hono-decks/client";
1584
1454
  ${imports.join("\n")}
1585
1455
 
1586
1456
  hydrateSlideIslands({
@@ -1588,588 +1458,559 @@ hydrateSlideIslands({
1588
1458
  ${registrations.join(",\n ")}
1589
1459
  }
1590
1460
  });
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
- `;
1461
+ `,
1462
+ resolveDir: input.cwd,
1463
+ sourcefile: "hono-decks-client-entry.tsx",
1464
+ loader: "tsx"
1465
+ },
1466
+ bundle: true,
1467
+ write: false,
1468
+ format: "esm",
1469
+ platform: "browser",
1470
+ target: "es2022",
1471
+ jsx: "automatic",
1472
+ jsxImportSource: "hono/jsx/dom",
1473
+ nodePaths: nodeModuleFallbackPaths(input.cwd),
1474
+ alias: { "hono-decks/client": resolveClientRuntimeEntry() },
1475
+ sourcemap: false,
1476
+ minify: false
1477
+ })).outputFiles[0];
1478
+ if (!output) throw new Error("Client entry did not produce output.");
1479
+ return `export const decksClientEntry = ${JSON.stringify(output.text)};\n`;
1615
1480
  }
1616
1481
  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));
1482
+ const built = fileURLToPath(new URL("./client.js", import.meta.url));
1483
+ if (existsSync(built)) return built;
1484
+ return fileURLToPath(new URL("../client.ts", import.meta.url));
1620
1485
  }
1621
1486
  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();
1487
+ const names = /* @__PURE__ */ new Set();
1488
+ for (const match of source.matchAll(/\bexport\s+function\s+([A-Z][A-Za-z0-9_]*)\b/g)) names.add(match[1]);
1489
+ for (const match of source.matchAll(/\bexport\s+const\s+([A-Z][A-Za-z0-9_]*)\b/g)) names.add(match[1]);
1490
+ return [...names].sort();
1630
1491
  }
1631
1492
  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
- ];
1493
+ const current = process.cwd();
1494
+ return [
1495
+ join(cwd, "node_modules"),
1496
+ join(cwd, "..", "node_modules"),
1497
+ join(cwd, "..", "..", "node_modules"),
1498
+ join(current, "node_modules"),
1499
+ join(current, "..", "node_modules"),
1500
+ join(current, "..", "..", "node_modules")
1501
+ ];
1641
1502
  }
1642
1503
  function clientComponentId(slug, exportName) {
1643
- const base = `${exportName}__${safeIdentifier2(slug)}`;
1644
- return `${base}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
1504
+ return `${`${exportName}__${safeIdentifier(slug)}`}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
1645
1505
  }
1646
1506
  function clientImportName(slug, exportName) {
1647
- return `${safeIdentifier2(exportName)}__${safeIdentifier2(slug)}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
1507
+ return `${safeIdentifier(exportName)}__${safeIdentifier(slug)}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
1648
1508
  }
1649
1509
  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);
1510
+ let hash = 5381;
1511
+ for (let index = 0; index < value.length; index += 1) hash = hash * 33 ^ value.charCodeAt(index);
1512
+ return (hash >>> 0).toString(36);
1655
1513
  }
1656
- function safeIdentifier2(value) {
1657
- return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
1514
+ function safeIdentifier(value) {
1515
+ return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
1658
1516
  }
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("/")) : ".";
1517
+ //#endregion
1518
+ //#region src/node/path-utils.ts
1519
+ function dirname(path) {
1520
+ const normalized = normalizePath(path);
1521
+ return normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
1670
1522
  }
1671
- function normalizePath4(path) {
1672
- return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
1523
+ function normalizePath(path) {
1524
+ return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
1673
1525
  }
1674
1526
  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;
1527
+ const normalized = normalizeRelativePath(root, "Deck root").replace(/\/$/, "");
1528
+ if (normalized === ".") throw new Error("Deck root must be a relative path inside the current working directory");
1529
+ return normalized;
1680
1530
  }
1681
1531
  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;
1532
+ const normalized = normalizePath(path).replace(/\/$/, "");
1533
+ const segments = normalized.split("/");
1534
+ 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`);
1535
+ return normalized;
1688
1536
  }
1689
-
1690
- // src/node/local-deck-io.ts
1537
+ //#endregion
1538
+ //#region src/node/local-deck-io.ts
1691
1539
  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;
1540
+ const entries = await readdir(dir, { withFileTypes: true });
1541
+ return (await Promise.all(entries.map(async (entry) => {
1542
+ const fullPath = join(dir, entry.name);
1543
+ if (entry.isDirectory()) return listFiles(cwd, fullPath);
1544
+ if (entry.isFile()) return [normalizePath(relative(cwd, fullPath))];
1545
+ return [];
1546
+ }))).flat().sort();
1547
+ }
1548
+ //#endregion
1549
+ //#region src/node/ogp.ts
1550
+ const MAX_OGP_BYTES = 256 * 1024;
1551
+ const MAX_OGP_REDIRECTS = 3;
1709
1552
  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
- }
1553
+ if (!isHttpUrl(url) || typeof fetch === "undefined") return void 0;
1554
+ try {
1555
+ const response = await fetchPublicHttpUrl(url);
1556
+ if (!response) return void 0;
1557
+ const contentType = response.headers.get("content-type") ?? "";
1558
+ if (!response.ok || !contentType.toLowerCase().includes("text/html") || isTooLarge(response)) return void 0;
1559
+ const html = await readTextWithLimit(response, MAX_OGP_BYTES);
1560
+ if (!html) return void 0;
1561
+ return parseOgpHtml(html, response.url || url);
1562
+ } catch {
1563
+ return;
1564
+ }
1722
1565
  }
1723
1566
  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;
1567
+ let current = url;
1568
+ for (let redirect = 0; redirect <= MAX_OGP_REDIRECTS; redirect += 1) {
1569
+ if (!await isPublicHttpUrl(current)) return void 0;
1570
+ const response = await fetch(current, {
1571
+ headers: { accept: "text/html,application/xhtml+xml" },
1572
+ redirect: "manual",
1573
+ signal: AbortSignal.timeout(1500)
1574
+ });
1575
+ if (!isRedirect(response.status)) return response;
1576
+ await response.body?.cancel();
1577
+ const location = response.headers.get("location");
1578
+ if (!location) return void 0;
1579
+ current = new URL(location, current).toString();
1580
+ }
1739
1581
  }
1740
1582
  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));
1583
+ let url;
1584
+ try {
1585
+ url = new URL(value);
1586
+ } catch {
1587
+ return false;
1588
+ }
1589
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
1590
+ if (isLocalHostname(url.hostname)) return false;
1591
+ if (isPrivateIpAddress(url.hostname)) return false;
1592
+ const addresses = await lookup(url.hostname, {
1593
+ all: true,
1594
+ verbatim: true
1595
+ });
1596
+ return addresses.length > 0 && addresses.every(({ address }) => !isPrivateIpAddress(address));
1752
1597
  }
1753
1598
  function isLocalHostname(hostname) {
1754
- const normalized = hostname.toLowerCase().replace(/\.$/, "");
1755
- return normalized === "localhost" || normalized.endsWith(".localhost");
1599
+ const normalized = hostname.toLowerCase().replace(/\.$/, "");
1600
+ return normalized === "localhost" || normalized.endsWith(".localhost");
1756
1601
  }
1757
1602
  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;
1603
+ const normalized = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
1604
+ const version = isIP(normalized);
1605
+ if (version === 4) return isPrivateIpv4(normalized);
1606
+ if (version === 6) return isPrivateIpv6(normalized);
1607
+ return false;
1763
1608
  }
1764
1609
  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;
1610
+ const parts = address.split(".").map((part) => Number.parseInt(part, 10));
1611
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;
1612
+ const [a, b] = parts;
1613
+ 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
1614
  }
1770
1615
  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;
1616
+ const normalized = address.toLowerCase();
1617
+ if (normalized === "::" || normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fec0:") || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff")) return true;
1618
+ if (normalized.startsWith("::ffff:")) {
1619
+ const tail = normalized.slice(7);
1620
+ const dotted = tail.match(/^(?:0:)?(\d+\.\d+\.\d+\.\d+)$/);
1621
+ if (dotted) return isPrivateIpv4(dotted[1]);
1622
+ const hex = tail.match(/^(?:0:)?([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
1623
+ if (hex) {
1624
+ const hi = Number.parseInt(hex[1], 16);
1625
+ const lo = Number.parseInt(hex[2], 16);
1626
+ const value32 = hi << 16 | lo;
1627
+ return isPrivateIpv4([
1628
+ value32 >>> 24 & 255,
1629
+ value32 >>> 16 & 255,
1630
+ value32 >>> 8 & 255,
1631
+ value32 & 255
1632
+ ].join("."));
1633
+ }
1634
+ }
1635
+ return false;
1789
1636
  }
1790
1637
  function isRedirect(status) {
1791
- return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
1638
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
1792
1639
  }
1793
1640
  function isTooLarge(response) {
1794
- const contentLength = response.headers.get("content-length");
1795
- return contentLength != null && Number(contentLength) > MAX_OGP_BYTES;
1641
+ const contentLength = response.headers.get("content-length");
1642
+ return contentLength != null && Number(contentLength) > MAX_OGP_BYTES;
1796
1643
  }
1797
1644
  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);
1645
+ const reader = response.body?.getReader();
1646
+ if (!reader) {
1647
+ const text = await response.text();
1648
+ return new TextEncoder().encode(text).byteLength > maxBytes ? void 0 : text;
1649
+ }
1650
+ const chunks = [];
1651
+ let bytes = 0;
1652
+ while (true) {
1653
+ const { done, value } = await reader.read();
1654
+ if (done) break;
1655
+ bytes += value.byteLength;
1656
+ if (bytes > maxBytes) {
1657
+ await reader.cancel();
1658
+ return;
1659
+ }
1660
+ chunks.push(value);
1661
+ }
1662
+ const buffer = new Uint8Array(bytes);
1663
+ let offset = 0;
1664
+ for (const chunk of chunks) {
1665
+ buffer.set(chunk, offset);
1666
+ offset += chunk.byteLength;
1667
+ }
1668
+ return new TextDecoder().decode(buffer);
1822
1669
  }
1823
1670
  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;
1671
+ const meta = collectHtmlMeta(html);
1672
+ const result = {
1673
+ title: firstValue(meta, ["og:title", "twitter:title"]) ?? htmlTitle(html),
1674
+ description: firstValue(meta, [
1675
+ "og:description",
1676
+ "twitter:description",
1677
+ "description"
1678
+ ]),
1679
+ image: absoluteUrl(firstValue(meta, [
1680
+ "og:image",
1681
+ "og:image:url",
1682
+ "twitter:image"
1683
+ ]), pageUrl),
1684
+ siteName: firstValue(meta, ["og:site_name", "application-name"])
1685
+ };
1686
+ return Object.values(result).some(Boolean) ? result : void 0;
1831
1687
  }
1832
1688
  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;
1689
+ const meta = /* @__PURE__ */ new Map();
1690
+ for (const match of html.matchAll(/<meta\b[^>]*>/gi)) {
1691
+ const attributes = parseHtmlAttributes(match[0]);
1692
+ const key = attributes.property ?? attributes.name;
1693
+ const content = attributes.content;
1694
+ if (key && content && !meta.has(key.toLowerCase())) meta.set(key.toLowerCase(), decodeHtmlEntities(content.trim()));
1695
+ }
1696
+ return meta;
1844
1697
  }
1845
1698
  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;
1699
+ const attributes = {};
1700
+ for (const match of tag.matchAll(/([^\s"'<>/=]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/g)) attributes[match[1].toLowerCase()] = match[2] ?? match[3] ?? match[4] ?? "";
1701
+ return attributes;
1852
1702
  }
1853
1703
  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;
1704
+ for (const key of keys) {
1705
+ const value = meta.get(key);
1706
+ if (value) return value;
1707
+ }
1859
1708
  }
1860
1709
  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;
1710
+ const match = html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i);
1711
+ return match ? decodeHtmlEntities(match[1].replace(/\s+/g, " ").trim()) : void 0;
1863
1712
  }
1864
1713
  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
- }
1714
+ if (!value) return void 0;
1715
+ try {
1716
+ return new URL(value, base).toString();
1717
+ } catch {
1718
+ return value;
1719
+ }
1871
1720
  }
1872
1721
  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
- }
1722
+ return value.replaceAll("&amp;", "&").replaceAll("&quot;", "\"").replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">");
1882
1723
  }
1883
-
1884
- // src/node/compile-decks.ts
1724
+ function isHttpUrl(value) {
1725
+ try {
1726
+ const url = new URL(value);
1727
+ return url.protocol === "http:" || url.protocol === "https:";
1728
+ } catch {
1729
+ return false;
1730
+ }
1731
+ }
1732
+ //#endregion
1733
+ //#region src/node/compile-decks.ts
1885
1734
  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) };
1735
+ const out = normalizeRelativePath(input.out, "Output directory");
1736
+ const root = normalizeDeckRoot(input.root);
1737
+ const paths = await listFiles(input.cwd, join(input.cwd, root));
1738
+ const resolved = resolveDeckFiles(paths, root);
1739
+ const componentModulePaths = Object.fromEntries(resolved.flatMap((deck) => {
1740
+ const path = deck.kind === "directory" ? findComponentModule(paths, root, deck.slug) : void 0;
1741
+ return path ? [[deck.slug, path]] : [];
1742
+ }));
1743
+ const clientEntryPaths = resolved.flatMap((deck) => {
1744
+ const path = deck.kind === "directory" ? findClientEntryModule(paths, root, deck.slug) : void 0;
1745
+ return path ? [{
1746
+ slug: deck.slug,
1747
+ sourcePath: path
1748
+ }] : [];
1749
+ });
1750
+ const themeStyles = await discoverDeckThemeStyles({
1751
+ cwd: input.cwd,
1752
+ root,
1753
+ paths,
1754
+ decks: resolved
1755
+ });
1756
+ const clientComponentIds = await discoverClientComponentIds({
1757
+ cwd: input.cwd,
1758
+ clientEntries: clientEntryPaths
1759
+ });
1760
+ const ogpCache = await createOgpCacheResolver({
1761
+ cwd: input.cwd,
1762
+ cacheFile: input.ogpCacheFile,
1763
+ refresh: input.refreshOgp,
1764
+ resolveOgp: input.resolveOgp ?? resolveOgpMetadata
1765
+ });
1766
+ const generated = await compileMdxModuleDecks({
1767
+ root,
1768
+ outDir: out,
1769
+ mountPath: input.mountPath,
1770
+ decks: resolved,
1771
+ componentModulePaths,
1772
+ clientComponentIds,
1773
+ themeStyles,
1774
+ resolveOgp: ogpCache.resolveOgp,
1775
+ readText: (path) => readFile(join(input.cwd, path), "utf8"),
1776
+ readBinary: (path) => readFile(join(input.cwd, path))
1777
+ });
1778
+ for (const deck of generated.decks) for (const slide of deck.slideModules) await writeTextFile(join(input.cwd, slide.path), slide.code);
1779
+ await writeTextFile(join(input.cwd, out, "decks.ts"), generated.routerModule);
1780
+ await writeTextFile(join(input.cwd, out, "client-entry.ts"), await emitClientEntryModule({
1781
+ cwd: input.cwd,
1782
+ clientEntries: clientEntryPaths,
1783
+ clientComponentIds
1784
+ }));
1785
+ await ogpCache.write();
1786
+ return { decks: generated.decks.map((deck) => deck.deck) };
1935
1787
  }
1936
1788
  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
- };
1789
+ if (!input.cacheFile) return {
1790
+ resolveOgp: input.resolveOgp,
1791
+ write: async () => void 0
1792
+ };
1793
+ const cachePath = join(input.cwd, normalizeRelativePath(input.cacheFile, "OGP cache file"));
1794
+ const cache = await readOgpCache(cachePath);
1795
+ let dirty = false;
1796
+ return {
1797
+ async resolveOgp(url) {
1798
+ if (!input.refresh && cache[url]) return cache[url];
1799
+ if (!input.refresh) return void 0;
1800
+ const metadata = await input.resolveOgp(url);
1801
+ if (metadata) {
1802
+ cache[url] = metadata;
1803
+ dirty = true;
1804
+ }
1805
+ return metadata;
1806
+ },
1807
+ async write() {
1808
+ if (!dirty) return;
1809
+ await writeTextFile(cachePath, `${JSON.stringify(cache, null, 2)}\n`);
1810
+ }
1811
+ };
1963
1812
  }
1964
1813
  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;
1814
+ let raw;
1815
+ try {
1816
+ raw = await readFile(path, "utf8");
1817
+ } catch (error) {
1818
+ if (isNodeError(error) && error.code === "ENOENT") return {};
1819
+ throw error;
1820
+ }
1821
+ const parsed = JSON.parse(raw);
1822
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OGP cache file must contain a JSON object");
1823
+ const cache = {};
1824
+ for (const [url, value] of Object.entries(parsed)) {
1825
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
1826
+ const metadata = value;
1827
+ cache[url] = {
1828
+ ...typeof metadata.title === "string" ? { title: metadata.title } : {},
1829
+ ...typeof metadata.description === "string" ? { description: metadata.description } : {},
1830
+ ...typeof metadata.image === "string" ? { image: metadata.image } : {},
1831
+ ...typeof metadata.siteName === "string" ? { siteName: metadata.siteName } : {}
1832
+ };
1833
+ }
1834
+ return cache;
1988
1835
  }
1989
1836
  function isNodeError(error) {
1990
- return error instanceof Error && "code" in error;
1837
+ return error instanceof Error && "code" in error;
1991
1838
  }
1992
1839
  async function writeTextFile(path, contents) {
1993
- await mkdir(dirname2(path), { recursive: true });
1994
- await writeFile(path, contents, "utf8");
1840
+ await mkdir(dirname(path), { recursive: true });
1841
+ await writeFile(path, contents, "utf8");
1995
1842
  }
1996
1843
  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
- });
1844
+ const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}/components/index`;
1845
+ return paths.find((path) => {
1846
+ const normalized = normalizePath(path);
1847
+ return normalized === `${base}.tsx` || normalized === `${base}.ts` || normalized === `${base}.jsx` || normalized === `${base}.js`;
1848
+ });
2002
1849
  }
2003
1850
  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
- });
1851
+ const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}/components/client/index`;
1852
+ return paths.find((path) => {
1853
+ const normalized = normalizePath(path);
1854
+ return normalized === `${base}.tsx` || normalized === `${base}.ts` || normalized === `${base}.jsx` || normalized === `${base}.js`;
1855
+ });
2009
1856
  }
2010
1857
  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;
1858
+ const result = {};
1859
+ for (const deck of input.decks) {
1860
+ if (deck.kind !== "directory") continue;
1861
+ const sourcePath = findDeckThemeStyle(input.paths, input.root, deck.slug);
1862
+ if (!sourcePath) continue;
1863
+ result[deck.slug] = {
1864
+ sourcePath,
1865
+ style: await readFile(join(input.cwd, sourcePath), "utf8")
1866
+ };
1867
+ }
1868
+ return result;
2022
1869
  }
2023
1870
  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;
1871
+ const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}`;
1872
+ const themePath = `${base}/theme.css`;
1873
+ const stylesEntryPath = `${base}/styles/index.css`;
1874
+ const hasTheme = paths.includes(themePath);
1875
+ const hasStylesEntry = paths.includes(stylesEntryPath);
1876
+ if (hasTheme && hasStylesEntry) throw new Error(`Deck ${slug} has both ${themePath} and ${stylesEntryPath}. Use only one theme CSS entry.`);
1877
+ return hasTheme ? themePath : hasStylesEntry ? stylesEntryPath : void 0;
2033
1878
  }
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
1879
  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
- };
1880
+ const path = resolve(input.cwd, input.configFile ?? "hono-decks.config.ts");
1881
+ if (!existsSync(path)) throw new Error(`Config file not found: ${input.configFile ?? "hono-decks.config.ts"}. Run \`hono-decks init\` first.`);
1882
+ const source = (await build({
1883
+ absWorkingDir: input.cwd,
1884
+ entryPoints: [path],
1885
+ bundle: true,
1886
+ format: "esm",
1887
+ platform: "node",
1888
+ target: "node20",
1889
+ write: false,
1890
+ logLevel: "silent",
1891
+ alias: { "hono-decks": resolveRuntimeEntry() }
1892
+ })).outputFiles[0]?.text;
1893
+ if (!source) throw new Error(`Could not load config file: ${path}`);
1894
+ const config = (await import(`data:text/javascript;charset=utf-8,${encodeURIComponent(source)}#${Date.now()}`)).default;
1895
+ if (!config || typeof config !== "object") throw new Error(`Config must have a default export: ${path}`);
1896
+ if (typeof config.mountPath !== "string") throw new Error(`Config mountPath must be a string: ${path}`);
1897
+ return {
1898
+ path,
1899
+ config,
1900
+ root: config.build?.root ?? "decks",
1901
+ outDir: config.build?.outDir ?? "src/generated",
1902
+ ogpCacheFile: config.build?.ogpCacheFile
1903
+ };
2072
1904
  }
2073
1905
  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
1906
+ const built = fileURLToPath(new URL("./mod.js", import.meta.url));
1907
+ if (existsSync(built)) return built;
1908
+ return fileURLToPath(new URL("../mod.ts", import.meta.url));
1909
+ }
1910
+ //#endregion
1911
+ //#region src/vite.ts
1912
+ /**
1913
+ * Compiles decks before Vite starts and regenerates them while authoring.
1914
+ *
1915
+ * Add this plugin to the same Vite config that hosts Hono or HonoX. The
1916
+ * project's ordinary `vite` development command is then sufficient; no
1917
+ * separate `hono-decks compile --watch` process is required.
1918
+ */
2080
1919
  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
- };
1920
+ let projectRoot = process.cwd();
1921
+ let configPath = resolve(projectRoot, options.configFile ?? "hono-decks.config.ts");
1922
+ let deckRoot = resolve(projectRoot, "decks");
1923
+ let resolvedConfig;
1924
+ let server;
1925
+ let watchedPaths = /* @__PURE__ */ new Set();
1926
+ let timer;
1927
+ let compiling = false;
1928
+ let queued = false;
1929
+ const syncWatchedPaths = async () => {
1930
+ if (!server) return;
1931
+ const next = /* @__PURE__ */ new Set([deckRoot, configPath]);
1932
+ const removed = [...watchedPaths].filter((path) => !next.has(path));
1933
+ if (removed.length > 0) await server.watcher.unwatch(removed);
1934
+ server.watcher.add([...next]);
1935
+ watchedPaths = next;
1936
+ };
1937
+ const compileOnce = async () => {
1938
+ const loaded = await loadDecksConfig({
1939
+ cwd: projectRoot,
1940
+ configFile: options.configFile
1941
+ });
1942
+ const manifest = await compileDecks({
1943
+ cwd: projectRoot,
1944
+ root: loaded.root,
1945
+ out: loaded.outDir,
1946
+ mountPath: loaded.config.mountPath,
1947
+ ogpCacheFile: loaded.ogpCacheFile,
1948
+ refreshOgp: options.refreshOgp
1949
+ });
1950
+ configPath = loaded.path;
1951
+ deckRoot = resolve(projectRoot, loaded.root);
1952
+ await syncWatchedPaths();
1953
+ resolvedConfig?.logger.info(`[hono-decks] Compiled ${manifest.decks.length} decks to ${loaded.outDir}`);
1954
+ };
1955
+ const compile = async (fatal) => {
1956
+ if (compiling) {
1957
+ queued = true;
1958
+ return;
1959
+ }
1960
+ compiling = true;
1961
+ let latestCompileSucceeded = false;
1962
+ try {
1963
+ do {
1964
+ queued = false;
1965
+ try {
1966
+ await compileOnce();
1967
+ latestCompileSucceeded = true;
1968
+ } catch (error) {
1969
+ latestCompileSucceeded = false;
1970
+ if (fatal) throw error;
1971
+ resolvedConfig?.logger.error(`[hono-decks] ${error instanceof Error ? error.message : String(error)}`);
1972
+ }
1973
+ } while (queued);
1974
+ if (latestCompileSucceeded && server) server.ws.send({ type: "full-reload" });
1975
+ } finally {
1976
+ compiling = false;
1977
+ }
1978
+ };
1979
+ const scheduleCompile = () => {
1980
+ if (timer) clearTimeout(timer);
1981
+ timer = setTimeout(() => void compile(false), 75);
1982
+ };
1983
+ const onWatcherEvent = (event, path) => {
1984
+ if (event !== "add" && event !== "change" && event !== "unlink") return;
1985
+ const absolutePath = resolve(projectRoot, path);
1986
+ if (absolutePath === configPath || isWithin(deckRoot, absolutePath)) scheduleCompile();
1987
+ };
1988
+ return {
1989
+ name: "hono-decks",
1990
+ enforce: "pre",
1991
+ async configResolved(config) {
1992
+ resolvedConfig = config;
1993
+ projectRoot = config.root;
1994
+ configPath = resolve(projectRoot, options.configFile ?? "hono-decks.config.ts");
1995
+ await compile(true);
1996
+ },
1997
+ configureServer(devServer) {
1998
+ server = devServer;
1999
+ syncWatchedPaths();
2000
+ devServer.watcher.on("all", onWatcherEvent);
2001
+ const cleanup = () => {
2002
+ if (timer) clearTimeout(timer);
2003
+ devServer.watcher.off("all", onWatcherEvent);
2004
+ server = void 0;
2005
+ watchedPaths.clear();
2006
+ };
2007
+ devServer.httpServer?.once("close", cleanup);
2008
+ }
2009
+ };
2168
2010
  }
2169
2011
  function isWithin(root, path) {
2170
- const child = relative3(root, path);
2171
- return child === "" || child !== ".." && !child.startsWith("../") && !child.startsWith("..\\");
2012
+ const child = relative(root, path);
2013
+ return child === "" || child !== ".." && !child.startsWith("../") && !child.startsWith("..\\");
2172
2014
  }
2173
- export {
2174
- honoDecks
2175
- };
2015
+ //#endregion
2016
+ export { honoDecks };