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