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