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