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