@docubook/flame 2.0.0-beta.0 → 2.0.0-beta.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/.docu/components/ScrollTo.tsx +17 -11
- package/.docu/components/Toc.tsx +32 -7
- package/.docu/lib/build.deno.js +6 -9
- package/.docu/lib/build.deno.js.map +1 -0
- package/.docu/lib/build.impl-BJsi28mD.js +377 -0
- package/.docu/lib/build.impl-BJsi28mD.js.map +1 -0
- package/.docu/lib/build.impl-yVLaa1eQ.js +2 -0
- package/.docu/lib/build.node.js +6 -9
- package/.docu/lib/build.node.js.map +1 -0
- package/.docu/lib/clean.js +25 -25
- package/.docu/lib/clean.js.map +1 -0
- package/.docu/lib/deno-DmEaKggj.js +20 -0
- package/.docu/lib/deno-DmEaKggj.js.map +1 -0
- package/.docu/lib/deploy.deno.js +9 -9
- package/.docu/lib/deploy.deno.js.map +1 -0
- package/.docu/lib/deploy.node.js +8 -8
- package/.docu/lib/deploy.node.js.map +1 -0
- package/.docu/lib/deploy.shared-D3UWafa6.js +295 -0
- package/.docu/lib/deploy.shared-D3UWafa6.js.map +1 -0
- package/.docu/lib/html.shared-DSn-7_6t.js +2467 -0
- package/.docu/lib/html.shared-DSn-7_6t.js.map +1 -0
- package/.docu/lib/logger-CycvLCrQ.js +312 -0
- package/.docu/lib/logger-CycvLCrQ.js.map +1 -0
- package/.docu/lib/node-DPTySdwG.js +80 -0
- package/.docu/lib/node-DPTySdwG.js.map +1 -0
- package/.docu/lib/paths-BtOIPBQ9.js +52 -0
- package/.docu/lib/paths-BtOIPBQ9.js.map +1 -0
- package/.docu/lib/preview.deno.js +7 -11
- package/.docu/lib/preview.deno.js.map +1 -0
- package/.docu/lib/preview.impl-CXJS2tQS.js +77 -0
- package/.docu/lib/preview.impl-CXJS2tQS.js.map +1 -0
- package/.docu/lib/preview.node.js +7 -11
- package/.docu/lib/preview.node.js.map +1 -0
- package/.docu/lib/server.deno.js +7 -12
- package/.docu/lib/server.deno.js.map +1 -0
- package/.docu/lib/server.impl-CJuWimfN.js +358 -0
- package/.docu/lib/server.impl-CJuWimfN.js.map +1 -0
- package/.docu/lib/server.node.js +7 -12
- package/.docu/lib/server.node.js.map +1 -0
- package/.docu/lib/utils-DA17MyQG.js +167 -0
- package/.docu/lib/utils-DA17MyQG.js.map +1 -0
- package/.docu/node/hydrate.node.ts +116 -166
- package/bin/compile-lib.mjs +30 -28
- package/package.json +6 -6
- package/.docu/lib/build.impl-S4DS5XPH.js +0 -12
- package/.docu/lib/chunk-4IQXHHPF.js +0 -62
- package/.docu/lib/chunk-C3RCUTUE.js +0 -470
- package/.docu/lib/chunk-D3QTSBR4.js +0 -434
- package/.docu/lib/chunk-EOK6KATZ.js +0 -191
- package/.docu/lib/chunk-IVM5UM44.js +0 -301
- package/.docu/lib/chunk-JMQI3FKV.js +0 -92
- package/.docu/lib/chunk-LXJLSXQI.js +0 -2691
- package/.docu/lib/chunk-O326MYJE.js +0 -330
- package/.docu/lib/chunk-UISOJ4RW.js +0 -114
package/.docu/lib/server.deno.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import "./chunk-LXJLSXQI.js";
|
|
5
|
-
import {
|
|
6
|
-
denoAdapter
|
|
7
|
-
} from "./chunk-UISOJ4RW.js";
|
|
8
|
-
import "./chunk-EOK6KATZ.js";
|
|
9
|
-
import "./chunk-IVM5UM44.js";
|
|
10
|
-
import "./chunk-4IQXHHPF.js";
|
|
11
|
-
|
|
12
|
-
// .docu/node/server.deno.ts
|
|
1
|
+
import { t as denoAdapter } from "./deno-DmEaKggj.js";
|
|
2
|
+
import { t as runServer } from "./server.impl-CJuWimfN.js";
|
|
3
|
+
//#region .docu/node/server.deno.ts
|
|
13
4
|
await runServer(denoAdapter);
|
|
5
|
+
//#endregion
|
|
6
|
+
export {};
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=server.deno.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.deno.js","names":[],"sources":["../node/server.deno.ts"],"sourcesContent":["import { denoAdapter } from \"./runtime\";\nimport { runServer } from \"./server.impl\";\n\nawait runServer(denoAdapter);\n"],"mappings":";;;AAGA,MAAM,UAAU,WAAW"}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { a as DOCS_DIR, c as PROJECT_ROOT, d as loadDocuConfig, r as DIST_DIR } from "./paths-BtOIPBQ9.js";
|
|
2
|
+
import { S as loadPlugins, a as IndexPage, b as computeInlineThemeCss, c as captureException, d as compileMdx, i as DocsLayout, l as initSentry, n as hmrScript, o as NotFoundPage, p as frontmatterField, r as htmlShell, s as DocsPage, t as errorHtml, u as generateSearchIndex, x as BuildPluginBuilder, y as buildClientBundle } from "./html.shared-DSn-7_6t.js";
|
|
3
|
+
import { c as SECURITY_HEADERS, d as htmlResponse, g as wrapPluginResponse, i as getContentType, m as isSlugSafe, p as isPathSafe, s as stripDocsHtmlSuffix, u as generateNonce } from "./utils-DA17MyQG.js";
|
|
4
|
+
import { t as logger } from "./logger-CycvLCrQ.js";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { existsSync, readFileSync, statSync, watch } from "node:fs";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { readFile, stat } from "node:fs/promises";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { renderToString } from "react-dom/server";
|
|
11
|
+
//#region .docu/node/server-routes.ts
|
|
12
|
+
function createHtmlResponse(title, description, body, status, state, depth = 0) {
|
|
13
|
+
const nonce = generateNonce();
|
|
14
|
+
const favicon = state.docuConfig.meta?.favicon || "/favicon.ico";
|
|
15
|
+
const html = htmlShell({
|
|
16
|
+
title,
|
|
17
|
+
description,
|
|
18
|
+
body,
|
|
19
|
+
favicon,
|
|
20
|
+
css: state.assetManifest.css,
|
|
21
|
+
js: state.assetManifest.js,
|
|
22
|
+
nonce,
|
|
23
|
+
extraScripts: hmrScript(nonce),
|
|
24
|
+
themeCss: state.inlineThemeCss,
|
|
25
|
+
depth,
|
|
26
|
+
absoluteAssets: status === 404
|
|
27
|
+
});
|
|
28
|
+
/** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */
|
|
29
|
+
return htmlResponse(html, nonce, status, true);
|
|
30
|
+
}
|
|
31
|
+
var docsCache = /* @__PURE__ */ new Map();
|
|
32
|
+
async function getDocsForSlug(slug, state) {
|
|
33
|
+
if (!isSlugSafe(slug, DOCS_DIR)) return null;
|
|
34
|
+
const paths = [
|
|
35
|
+
resolve(DOCS_DIR, slug, "index.mdx"),
|
|
36
|
+
resolve(DOCS_DIR, `${slug}.mdx`),
|
|
37
|
+
resolve(DOCS_DIR, slug, "index.md"),
|
|
38
|
+
resolve(DOCS_DIR, `${slug}.md`)
|
|
39
|
+
];
|
|
40
|
+
const resolvedDocsDir = resolve(DOCS_DIR);
|
|
41
|
+
let filePath = null;
|
|
42
|
+
let raw = null;
|
|
43
|
+
for (const p of paths) {
|
|
44
|
+
const resolvedCandidate = resolve(p);
|
|
45
|
+
if (resolvedCandidate !== resolvedDocsDir && !resolvedCandidate.startsWith(resolvedDocsDir + "/")) continue;
|
|
46
|
+
try {
|
|
47
|
+
raw = await readFile(resolvedCandidate, "utf-8");
|
|
48
|
+
filePath = resolvedCandidate;
|
|
49
|
+
break;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (err.code !== "ENOENT") throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!filePath || !raw) return null;
|
|
55
|
+
const relPath = filePath.replace(PROJECT_ROOT + "/", "");
|
|
56
|
+
const mtimeMs = (await stat(filePath)).mtimeMs;
|
|
57
|
+
const cached = docsCache.get(relPath);
|
|
58
|
+
if (cached && cached.mtimeMs === mtimeMs) return cached.doc;
|
|
59
|
+
let content = raw;
|
|
60
|
+
if (state.builder) {
|
|
61
|
+
const transformed = await state.builder.runOnLoad(relPath, content);
|
|
62
|
+
if (transformed?.contents) content = transformed.contents;
|
|
63
|
+
}
|
|
64
|
+
const remarkPlugins = state.builder?.collectRemarkPlugins();
|
|
65
|
+
const rehypePlugins = state.builder?.collectRehypePlugins();
|
|
66
|
+
const result = await compileMdx(content, relPath, void 0, remarkPlugins, rehypePlugins);
|
|
67
|
+
let frontmatter = result.frontmatter;
|
|
68
|
+
if (state.builder) frontmatter = await state.builder.runTransformFrontmatterChain(frontmatter, {
|
|
69
|
+
slug: slug || "/",
|
|
70
|
+
filePath: relPath,
|
|
71
|
+
content
|
|
72
|
+
});
|
|
73
|
+
const doc = {
|
|
74
|
+
content: result.content,
|
|
75
|
+
compiledSource: result.compiledSource,
|
|
76
|
+
frontmatter,
|
|
77
|
+
tocs: result.tocs,
|
|
78
|
+
filePath: relPath,
|
|
79
|
+
resolvedContent: content
|
|
80
|
+
};
|
|
81
|
+
docsCache.set(relPath, {
|
|
82
|
+
mtimeMs,
|
|
83
|
+
doc
|
|
84
|
+
});
|
|
85
|
+
return doc;
|
|
86
|
+
}
|
|
87
|
+
async function renderDocsServerPage(doc, slug, pathname, state) {
|
|
88
|
+
const title = frontmatterField(doc.frontmatter, "title") || slug.join("/") || "Docs";
|
|
89
|
+
const description = frontmatterField(doc.frontmatter, "description");
|
|
90
|
+
const page = React.createElement(DocsLayout, {
|
|
91
|
+
repoUrl: state.docuConfig.repo?.url,
|
|
92
|
+
pathname
|
|
93
|
+
}, React.createElement(DocsPage, {
|
|
94
|
+
slug,
|
|
95
|
+
title,
|
|
96
|
+
description,
|
|
97
|
+
date: frontmatterField(doc.frontmatter, "date") || void 0,
|
|
98
|
+
content: renderToString(doc.content),
|
|
99
|
+
tocs: doc.tocs,
|
|
100
|
+
filePath: doc.filePath,
|
|
101
|
+
repoUrl: state.docuConfig.repo?.url,
|
|
102
|
+
compiledSource: doc.compiledSource
|
|
103
|
+
}));
|
|
104
|
+
const body = renderToString(page);
|
|
105
|
+
const depth = slug.length || 1;
|
|
106
|
+
if (state.builder) {
|
|
107
|
+
const ctx = {
|
|
108
|
+
slug: slug.join("/") || "/",
|
|
109
|
+
filePath: doc.filePath,
|
|
110
|
+
frontmatter: doc.frontmatter,
|
|
111
|
+
content: doc.resolvedContent,
|
|
112
|
+
config: state.docuConfig
|
|
113
|
+
};
|
|
114
|
+
const headExtra = state.builder.collectHead(ctx);
|
|
115
|
+
const bodyExtra = state.builder.collectBody(ctx);
|
|
116
|
+
const nonce = generateNonce();
|
|
117
|
+
const favicon = state.docuConfig.meta?.favicon || "/favicon.ico";
|
|
118
|
+
let html = htmlShell({
|
|
119
|
+
title,
|
|
120
|
+
description,
|
|
121
|
+
body,
|
|
122
|
+
favicon,
|
|
123
|
+
css: state.assetManifest.css,
|
|
124
|
+
js: state.assetManifest.js,
|
|
125
|
+
nonce,
|
|
126
|
+
extraScripts: hmrScript(nonce),
|
|
127
|
+
themeCss: state.inlineThemeCss,
|
|
128
|
+
headExtra,
|
|
129
|
+
bodyExtra,
|
|
130
|
+
depth
|
|
131
|
+
});
|
|
132
|
+
html = await state.builder.runTransformHtmlChain(html, ctx);
|
|
133
|
+
/** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */
|
|
134
|
+
return htmlResponse(html, nonce, 200, true);
|
|
135
|
+
}
|
|
136
|
+
return createHtmlResponse(title, description, body, 200, state, depth);
|
|
137
|
+
}
|
|
138
|
+
function renderPage(Component, title, description, status, state, props = {}, depth = 0) {
|
|
139
|
+
const page = React.createElement(DocsLayout, {
|
|
140
|
+
repoUrl: state.docuConfig.repo?.url,
|
|
141
|
+
pathname: "/docs"
|
|
142
|
+
}, React.createElement(Component, props));
|
|
143
|
+
return createHtmlResponse(title, description, renderToString(page), status, state, depth);
|
|
144
|
+
}
|
|
145
|
+
async function handleDocsIndex(state) {
|
|
146
|
+
const doc = await getDocsForSlug("", state);
|
|
147
|
+
if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, 1);
|
|
148
|
+
return renderDocsServerPage(doc, [], "/docs", state);
|
|
149
|
+
}
|
|
150
|
+
async function handleDocsRoute(slug, state) {
|
|
151
|
+
const path = slug.join("/");
|
|
152
|
+
const doc = await getDocsForSlug(path, state);
|
|
153
|
+
if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, slug.length || 1);
|
|
154
|
+
return renderDocsServerPage(doc, slug, `/docs/${path}`, state);
|
|
155
|
+
}
|
|
156
|
+
function handleIndex(state) {
|
|
157
|
+
const page = React.createElement(IndexPage);
|
|
158
|
+
const body = renderToString(page);
|
|
159
|
+
return createHtmlResponse(state.docuConfig.meta?.title || "DocuBook", state.docuConfig.meta?.description || "", body, 200, state);
|
|
160
|
+
}
|
|
161
|
+
function handleNotFound(state, depth = 0) {
|
|
162
|
+
return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, depth);
|
|
163
|
+
}
|
|
164
|
+
function serveStatic(pathname) {
|
|
165
|
+
let decoded;
|
|
166
|
+
try {
|
|
167
|
+
decoded = decodeURIComponent(pathname);
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (!isPathSafe(pathname, DIST_DIR)) return null;
|
|
172
|
+
const assetPath = resolve(DIST_DIR, decoded.slice(1));
|
|
173
|
+
try {
|
|
174
|
+
if (statSync(assetPath).isFile()) return new Response(readFileSync(assetPath), { headers: { "Content-Type": getContentType(pathname) } });
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (err.code !== "ENOENT") throw err;
|
|
177
|
+
}
|
|
178
|
+
if (decoded.startsWith("/docs/assets/")) {
|
|
179
|
+
const docsAssetsDir = resolve(DOCS_DIR, "assets");
|
|
180
|
+
const requestedRelative = decoded.slice(13);
|
|
181
|
+
const docsAsset = resolve(docsAssetsDir, requestedRelative);
|
|
182
|
+
const docsAssetsDirWithSep = docsAssetsDir.endsWith("/") ? docsAssetsDir : docsAssetsDir + "/";
|
|
183
|
+
if (docsAsset !== docsAssetsDir && !docsAsset.startsWith(docsAssetsDirWithSep)) return null;
|
|
184
|
+
try {
|
|
185
|
+
if (statSync(docsAsset).isFile()) return new Response(readFileSync(docsAsset), { headers: { "Content-Type": getContentType(pathname) } });
|
|
186
|
+
} catch (err) {
|
|
187
|
+
if (err.code !== "ENOENT") throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
function serverErrorResponse(error) {
|
|
193
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
194
|
+
const st = process.env.NODE_ENV !== "production" && error instanceof Error ? error.stack : void 0;
|
|
195
|
+
return new Response(errorHtml(msg, st), {
|
|
196
|
+
status: 500,
|
|
197
|
+
headers: {
|
|
198
|
+
"Content-Type": "text/html",
|
|
199
|
+
...SECURITY_HEADERS,
|
|
200
|
+
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'"
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region .docu/node/server.impl.ts
|
|
206
|
+
/**
|
|
207
|
+
* Runtime-neutral dev server — mirror of `server.ts` (Bun-only, protected)
|
|
208
|
+
* driven by a `RuntimeAdapter` (./runtime) instead of `Bun.serve`,
|
|
209
|
+
* with manual route matching instead of `Bun.FileSystemRouter`. The page set
|
|
210
|
+
* is static (`/`, `/docs/[[...slug]]`, `/404`), so a router is unnecessary.
|
|
211
|
+
*/
|
|
212
|
+
async function runServer(adapter) {
|
|
213
|
+
const docuConfig = loadDocuConfig();
|
|
214
|
+
const parsedPort = parseInt(process.env.PORT ?? "3000", 10);
|
|
215
|
+
const PORT = Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : 3e3;
|
|
216
|
+
logger.buildStart();
|
|
217
|
+
await initSentry();
|
|
218
|
+
logger.bundleStart();
|
|
219
|
+
let t = performance.now();
|
|
220
|
+
const assetManifest = await buildClientBundle();
|
|
221
|
+
logger.bundleDone(Math.round(performance.now() - t));
|
|
222
|
+
const inlineThemeCss = computeInlineThemeCss();
|
|
223
|
+
logger.indexStart();
|
|
224
|
+
t = performance.now();
|
|
225
|
+
const records = await generateSearchIndex();
|
|
226
|
+
logger.indexDone(records, Math.round(performance.now() - t));
|
|
227
|
+
logger.routes();
|
|
228
|
+
const pluginsConfig = docuConfig.plugins ?? [];
|
|
229
|
+
const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;
|
|
230
|
+
if (builder) {
|
|
231
|
+
const plugins = await loadPlugins(pluginsConfig);
|
|
232
|
+
for (const plugin of plugins) await plugin.setup(builder);
|
|
233
|
+
}
|
|
234
|
+
const state = {
|
|
235
|
+
docuConfig,
|
|
236
|
+
assetManifest,
|
|
237
|
+
inlineThemeCss,
|
|
238
|
+
builder
|
|
239
|
+
};
|
|
240
|
+
const hmrClients = /* @__PURE__ */ new Set();
|
|
241
|
+
const require = createRequire(import.meta.url);
|
|
242
|
+
/** Locate the installed @docubook/markdown source dir (workspace link), if any. */
|
|
243
|
+
function resolveMdxContentSrc() {
|
|
244
|
+
try {
|
|
245
|
+
const pkgDir = dirname(require.resolve("@docubook/markdown/package.json"));
|
|
246
|
+
const src = join(pkgDir, "src");
|
|
247
|
+
return existsSync(src) ? src : null;
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
let hmrTimeout = null;
|
|
253
|
+
const watcher = watch(DOCS_DIR, { recursive: true }, (_event, filename) => {
|
|
254
|
+
if (!filename || !filename.endsWith(".mdx") && !filename.endsWith(".md")) return;
|
|
255
|
+
if (hmrTimeout) clearTimeout(hmrTimeout);
|
|
256
|
+
hmrTimeout = setTimeout(() => {
|
|
257
|
+
for (const client of [...hmrClients]) try {
|
|
258
|
+
client.enqueue(new TextEncoder().encode("data: reload\n\n"));
|
|
259
|
+
} catch {
|
|
260
|
+
hmrClients.delete(client);
|
|
261
|
+
}
|
|
262
|
+
}, 300);
|
|
263
|
+
});
|
|
264
|
+
let srcHmrTimeout = null;
|
|
265
|
+
const mdxContentSrc = resolveMdxContentSrc();
|
|
266
|
+
const srcWatcher = mdxContentSrc ? watch(mdxContentSrc, { recursive: true }, (_event, filename) => {
|
|
267
|
+
if (!filename || !/\.(ts|tsx|js|jsx|css)$/.test(filename)) return;
|
|
268
|
+
if (srcHmrTimeout) clearTimeout(srcHmrTimeout);
|
|
269
|
+
srcHmrTimeout = setTimeout(async () => {
|
|
270
|
+
try {
|
|
271
|
+
state.assetManifest = await buildClientBundle();
|
|
272
|
+
logger.warn("[hmr] client bundle rebuilt (component change)");
|
|
273
|
+
} catch (e) {
|
|
274
|
+
logger.warn(`[hmr] bundle rebuild failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
275
|
+
}
|
|
276
|
+
for (const client of [...hmrClients]) try {
|
|
277
|
+
client.enqueue(new TextEncoder().encode("data: reload\n\n"));
|
|
278
|
+
} catch {
|
|
279
|
+
hmrClients.delete(client);
|
|
280
|
+
}
|
|
281
|
+
}, 300);
|
|
282
|
+
}) : null;
|
|
283
|
+
process.on("SIGINT", () => {
|
|
284
|
+
watcher.close();
|
|
285
|
+
srcWatcher?.close();
|
|
286
|
+
process.exit(0);
|
|
287
|
+
});
|
|
288
|
+
process.on("SIGTERM", () => {
|
|
289
|
+
watcher.close();
|
|
290
|
+
srcWatcher?.close();
|
|
291
|
+
process.exit(0);
|
|
292
|
+
});
|
|
293
|
+
let handle = null;
|
|
294
|
+
const fetchHandler = async (req) => {
|
|
295
|
+
const url = new URL(req.url);
|
|
296
|
+
const pathname = stripDocsHtmlSuffix(url.pathname);
|
|
297
|
+
const startTime = performance.now();
|
|
298
|
+
if (builder) {
|
|
299
|
+
const pluginResponse = await builder.runHandleRequest(req, {
|
|
300
|
+
port: handle?.port ?? PORT,
|
|
301
|
+
hostname: handle?.hostname ?? "localhost"
|
|
302
|
+
});
|
|
303
|
+
if (pluginResponse) {
|
|
304
|
+
const securedResponse = wrapPluginResponse(pluginResponse, true);
|
|
305
|
+
logger.request(req.method, pathname, securedResponse.status, Math.round(performance.now() - startTime));
|
|
306
|
+
return securedResponse;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
if (pathname === "/__hmr") {
|
|
311
|
+
const stream = new ReadableStream({
|
|
312
|
+
start(controller) {
|
|
313
|
+
hmrClients.add(controller);
|
|
314
|
+
controller.enqueue(new TextEncoder().encode("data: connected\n\n"));
|
|
315
|
+
},
|
|
316
|
+
cancel(controller) {
|
|
317
|
+
hmrClients.delete(controller);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
return new Response(stream, { headers: {
|
|
321
|
+
"Content-Type": "text/event-stream",
|
|
322
|
+
"Cache-Control": "no-cache",
|
|
323
|
+
Connection: "keep-alive"
|
|
324
|
+
} });
|
|
325
|
+
}
|
|
326
|
+
if (pathname.startsWith("/assets/") || /\.\w+$/.test(pathname)) {
|
|
327
|
+
const staticRes = serveStatic(pathname);
|
|
328
|
+
if (staticRes) return staticRes;
|
|
329
|
+
}
|
|
330
|
+
let response;
|
|
331
|
+
if (pathname === "/") response = handleIndex(state);
|
|
332
|
+
else if (pathname === "/docs" || pathname === "/docs/") response = await handleDocsIndex(state);
|
|
333
|
+
else if (pathname.startsWith("/docs/")) {
|
|
334
|
+
const slug = pathname.slice(6).split("/").filter(Boolean);
|
|
335
|
+
response = slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);
|
|
336
|
+
} else response = handleNotFound(state);
|
|
337
|
+
logger.request(req.method, pathname, response.status, Math.round(performance.now() - startTime));
|
|
338
|
+
return response;
|
|
339
|
+
} catch (err) {
|
|
340
|
+
captureException(err, {
|
|
341
|
+
method: req.method,
|
|
342
|
+
pathname
|
|
343
|
+
});
|
|
344
|
+
logger.request(req.method, pathname, 500, Math.round(performance.now() - startTime));
|
|
345
|
+
return serverErrorResponse(err);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
handle = await adapter.serve(fetchHandler, {
|
|
349
|
+
port: PORT,
|
|
350
|
+
idleTimeout: 255
|
|
351
|
+
});
|
|
352
|
+
logger.ready(handle.port, true);
|
|
353
|
+
return handle;
|
|
354
|
+
}
|
|
355
|
+
//#endregion
|
|
356
|
+
export { runServer as t };
|
|
357
|
+
|
|
358
|
+
//# sourceMappingURL=server.impl-CJuWimfN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.impl-CJuWimfN.js","names":[],"sources":["../node/server-routes.ts","../node/server.impl.ts"],"sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readFileSync, statSync } from \"node:fs\";\nimport React, { type ReactNode } from \"react\";\nimport { renderToString } from \"react-dom/server\";\nimport { compileMdx, frontmatterField } from \"./mdx\";\nimport { getContentType } from \"./utils\";\nimport { DOCS_DIR, DIST_DIR, PROJECT_ROOT } from \"./paths\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport type { PageContext } from \"./plugin\";\nimport type { DocuConfig, TocItem } from \"./types\";\nimport DocsPage from \"../pages/docs/[[...slug]]\";\nimport NotFoundPage from \"../pages/404\";\nimport IndexPage from \"../pages/index\";\nimport { DocsLayout } from \"../components/DocsLayout\";\nimport { generateNonce, isPathSafe, isSlugSafe, htmlResponse, SECURITY_HEADERS } from \"./security\";\nimport { htmlShell as createHtmlShell, hmrScript, errorHtml } from \"./html.shared\";\n\nexport interface ServerState {\n docuConfig: DocuConfig;\n assetManifest: { css: string; js: string };\n inlineThemeCss?: string;\n builder: BuildPluginBuilder | null;\n}\n\nfunction createHtmlResponse(\n title: string,\n description: string,\n body: string,\n status: number,\n state: ServerState,\n depth = 0\n): Response {\n const nonce = generateNonce();\n const favicon = state.docuConfig.meta?.favicon || \"/favicon.ico\";\n const html = createHtmlShell({\n title,\n description,\n body,\n favicon,\n css: state.assetManifest.css,\n js: state.assetManifest.js,\n nonce,\n extraScripts: hmrScript(nonce),\n themeCss: state.inlineThemeCss,\n depth,\n // 404 pages can be requested at arbitrary depths (e.g. a noLink section\n // path typed in the address bar) — relative asset paths would resolve\n // against the wrong directory and break CSS/JS.\n absoluteAssets: status === 404,\n });\n /** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */\n return htmlResponse(html, nonce, status, true);\n}\n\n// Dev-only memo: compileMdx per request is ~70ms; repeat navigations with\n// an unchanged file (same mtime) reuse the compiled result. The dev server\n// has no other per-page cache — without this every navigation re-parses MDX.\n// ponytail: unbounded Map is fine — docs sites have a handful of pages; cap\n// with an LRU only if a project exceeds thousands of routes.\nconst docsCache = new Map<\n string,\n { mtimeMs: number; doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>> }\n>();\n\nasync function getDocsForSlug(\n slug: string,\n state: ServerState\n): Promise<{\n content: ReactNode;\n compiledSource: string;\n frontmatter: Record<string, unknown>;\n tocs: TocItem[];\n filePath: string;\n resolvedContent: string;\n} | null> {\n if (!isSlugSafe(slug, DOCS_DIR)) return null;\n\n const paths = [\n resolve(DOCS_DIR, slug, \"index.mdx\"),\n resolve(DOCS_DIR, `${slug}.mdx`),\n resolve(DOCS_DIR, slug, \"index.md\"),\n resolve(DOCS_DIR, `${slug}.md`),\n ];\n\n const resolvedDocsDir = resolve(DOCS_DIR);\n let filePath: string | null = null;\n let raw: string | null = null;\n for (const p of paths) {\n const resolvedCandidate = resolve(p);\n if (\n resolvedCandidate !== resolvedDocsDir &&\n !resolvedCandidate.startsWith(resolvedDocsDir + \"/\")\n ) {\n continue;\n }\n try {\n raw = await readFile(resolvedCandidate, \"utf-8\");\n filePath = resolvedCandidate;\n break;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n if (!filePath || !raw) return null;\n\n const relPath = filePath.replace(PROJECT_ROOT + \"/\", \"\");\n\n const mtimeMs = (await stat(filePath)).mtimeMs;\n const cached = docsCache.get(relPath);\n if (cached && cached.mtimeMs === mtimeMs) {\n return cached.doc;\n }\n\n let content = raw;\n if (state.builder) {\n const transformed = await state.builder.runOnLoad(relPath, content);\n if (transformed?.contents) {\n content = transformed.contents;\n }\n }\n\n const remarkPlugins = state.builder?.collectRemarkPlugins();\n const rehypePlugins = state.builder?.collectRehypePlugins();\n const result = await compileMdx(content, relPath, undefined, remarkPlugins, rehypePlugins);\n\n let frontmatter = result.frontmatter as Record<string, unknown>;\n if (state.builder) {\n frontmatter = await state.builder.runTransformFrontmatterChain(frontmatter, {\n slug: slug || \"/\",\n filePath: relPath,\n content,\n });\n }\n\n const doc = {\n content: result.content,\n compiledSource: result.compiledSource,\n frontmatter,\n tocs: result.tocs,\n filePath: relPath,\n resolvedContent: content,\n };\n docsCache.set(relPath, { mtimeMs, doc });\n return doc;\n}\n\nasync function renderDocsServerPage(\n doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>>,\n slug: string[],\n pathname: string,\n state: ServerState\n): Promise<Response> {\n const title = frontmatterField(doc.frontmatter, \"title\") || slug.join(\"/\") || \"Docs\";\n const description = frontmatterField(doc.frontmatter, \"description\");\n\n const page = React.createElement(\n DocsLayout,\n { repoUrl: state.docuConfig.repo?.url, pathname },\n React.createElement(DocsPage, {\n slug,\n title,\n description,\n date: frontmatterField(doc.frontmatter, \"date\") || undefined,\n // Same root-relative SSR as build — see build.ts.\n content: renderToString(doc.content),\n tocs: doc.tocs,\n filePath: doc.filePath,\n repoUrl: state.docuConfig.repo?.url,\n compiledSource: doc.compiledSource,\n })\n );\n\n const body = renderToString(page);\n\n // Match build.ts depth calculation: slug.split(\"/\").length, fallback to 1 for empty\n const depth = slug.length || 1;\n\n if (state.builder) {\n const ctx: PageContext = {\n slug: slug.join(\"/\") || \"/\",\n filePath: doc.filePath,\n frontmatter: doc.frontmatter,\n content: doc.resolvedContent,\n config: state.docuConfig,\n };\n const headExtra = state.builder.collectHead(ctx);\n const bodyExtra = state.builder.collectBody(ctx);\n const nonce = generateNonce();\n const favicon = state.docuConfig.meta?.favicon || \"/favicon.ico\";\n let html = createHtmlShell({\n title,\n description,\n body,\n favicon,\n css: state.assetManifest.css,\n js: state.assetManifest.js,\n nonce,\n extraScripts: hmrScript(nonce),\n themeCss: state.inlineThemeCss,\n headExtra,\n bodyExtra,\n depth,\n });\n html = await state.builder.runTransformHtmlChain(html, ctx);\n /** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */\n return htmlResponse(html, nonce, 200, true);\n }\n\n return createHtmlResponse(title, description, body, 200, state, depth);\n}\n\nfunction renderPage(\n Component: React.ComponentType<Record<string, unknown>>,\n title: string,\n description: string,\n status: number,\n state: ServerState,\n props: Record<string, unknown> = {},\n depth = 0\n): Response {\n const page = React.createElement(\n DocsLayout,\n { repoUrl: state.docuConfig.repo?.url, pathname: \"/docs\" },\n React.createElement(Component, props)\n );\n const body = renderToString(page);\n return createHtmlResponse(title, description, body, status, state, depth);\n}\n\nexport async function handleDocsIndex(state: ServerState): Promise<Response> {\n const doc = await getDocsForSlug(\"\", state);\n if (!doc) return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, 1);\n return renderDocsServerPage(doc, [], \"/docs\", state);\n}\n\nexport async function handleDocsRoute(slug: string[], state: ServerState): Promise<Response> {\n const path = slug.join(\"/\");\n const doc = await getDocsForSlug(path, state);\n if (!doc)\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, slug.length || 1);\n return renderDocsServerPage(doc, slug, `/docs/${path}`, state);\n}\n\nexport function handleIndex(state: ServerState): Response {\n const page = React.createElement(IndexPage);\n const body = renderToString(page);\n return createHtmlResponse(\n state.docuConfig.meta?.title || \"DocuBook\",\n state.docuConfig.meta?.description || \"\",\n body,\n 200,\n state\n );\n}\n\nexport function handleNotFound(state: ServerState, depth = 0): Response {\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, depth);\n}\n\nexport function serveStatic(pathname: string): Response | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n if (!isPathSafe(pathname, DIST_DIR)) return null;\n const assetPath = resolve(DIST_DIR, decoded.slice(1));\n try {\n const s = statSync(assetPath);\n if (s.isFile()) {\n return new Response(readFileSync(assetPath), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n\n if (decoded.startsWith(\"/docs/assets/\")) {\n const docsAssetsDir = resolve(DOCS_DIR, \"assets\");\n const requestedRelative = decoded.slice(\"/docs/assets/\".length);\n const docsAsset = resolve(docsAssetsDir, requestedRelative);\n const docsAssetsDirWithSep = docsAssetsDir.endsWith(\"/\") ? docsAssetsDir : docsAssetsDir + \"/\";\n if (docsAsset !== docsAssetsDir && !docsAsset.startsWith(docsAssetsDirWithSep)) return null;\n try {\n const s = statSync(docsAsset);\n if (s.isFile()) {\n return new Response(readFileSync(docsAsset), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n return null;\n}\n\nexport function serverErrorResponse(error: unknown): Response {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n const st =\n process.env.NODE_ENV !== \"production\" && error instanceof Error ? error.stack : undefined;\n return new Response(errorHtml(msg, st), {\n status: 500,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": \"default-src 'none'; style-src 'unsafe-inline'\",\n },\n });\n}\n","/**\n * Runtime-neutral dev server — mirror of `server.ts` (Bun-only, protected)\n * driven by a `RuntimeAdapter` (./runtime) instead of `Bun.serve`,\n * with manual route matching instead of `Bun.FileSystemRouter`. The page set\n * is static (`/`, `/docs/[[...slug]]`, `/404`), so a router is unnecessary.\n */\n\nimport { watch, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport type { RuntimeAdapter, ServerHandle } from \"./runtime\";\nimport { DOCS_DIR, loadDocuConfig } from \"./paths\";\nimport { loadPlugins } from \"./plugin-loader\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport { buildClientBundle, computeInlineThemeCss } from \"./hydrate.node\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { logger } from \"./logger\";\nimport { initSentry, captureException } from \"./sentry\";\nimport {\n serveStatic,\n handleDocsIndex,\n handleDocsRoute,\n handleIndex,\n handleNotFound,\n serverErrorResponse,\n type ServerState,\n} from \"./server-routes\";\nimport { wrapPluginResponse } from \"./security\";\nimport { stripDocsHtmlSuffix } from \"./utils\";\n\nexport async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle> {\n const docuConfig = loadDocuConfig();\n\n const parsedPort = parseInt(process.env.PORT ?? \"3000\", 10);\n const PORT =\n Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : 3000;\n\n logger.buildStart();\n\n await initSentry();\n\n logger.bundleStart();\n let t = performance.now();\n const assetManifest = await buildClientBundle();\n logger.bundleDone(Math.round(performance.now() - t));\n\n const inlineThemeCss = computeInlineThemeCss();\n\n logger.indexStart();\n t = performance.now();\n const records = await generateSearchIndex();\n logger.indexDone(records, Math.round(performance.now() - t));\n\n logger.routes();\n\n // Plugin setup — all hooks active (onLoad, remark/rehype, frontmatter, head/body, html transform, handleRequest)\n const pluginsConfig = docuConfig.plugins ?? [];\n const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;\n\n if (builder) {\n const plugins = await loadPlugins(pluginsConfig);\n\n for (const plugin of plugins) {\n await plugin.setup(builder);\n }\n }\n\n const state: ServerState = {\n docuConfig,\n assetManifest,\n inlineThemeCss,\n builder,\n };\n\n const hmrClients = new Set<ReadableStreamDefaultController>();\n\n const require = createRequire(import.meta.url);\n /** Locate the installed @docubook/markdown source dir (workspace link), if any. */\n function resolveMdxContentSrc(): string | null {\n try {\n const pkgDir = dirname(require.resolve(\"@docubook/markdown/package.json\"));\n const src = join(pkgDir, \"src\");\n return existsSync(src) ? src : null;\n } catch {\n return null;\n }\n }\n\n let hmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const watcher = watch(DOCS_DIR, { recursive: true }, (_event, filename) => {\n if (!filename || (!filename.endsWith(\".mdx\") && !filename.endsWith(\".md\"))) return;\n if (hmrTimeout) clearTimeout(hmrTimeout);\n hmrTimeout = setTimeout(() => {\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n });\n\n // Rebuild the client bundle when component sources change (e.g. the\n // @docubook/markdown workspace package) so interactive islands pick up\n // edits, then reload connected clients. MDX edits are handled by the watcher\n // above — no rebuild needed, content recompiles per request.\n let srcHmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const mdxContentSrc = resolveMdxContentSrc();\n const srcWatcher = mdxContentSrc\n ? watch(mdxContentSrc, { recursive: true }, (_event, filename) => {\n if (!filename || !/\\.(ts|tsx|js|jsx|css)$/.test(filename)) return;\n if (srcHmrTimeout) clearTimeout(srcHmrTimeout);\n srcHmrTimeout = setTimeout(async () => {\n try {\n state.assetManifest = await buildClientBundle();\n logger.warn(\"[hmr] client bundle rebuilt (component change)\");\n } catch (e) {\n logger.warn(\n `[hmr] bundle rebuild failed: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n })\n : null;\n\n process.on(\"SIGINT\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n\n let handle: ServerHandle | null = null;\n\n const fetchHandler = async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n // Generated links carry `.html` (matching the static build output);\n // route them to the same handler as their extensionless form.\n const pathname = stripDocsHtmlSuffix(url.pathname);\n const startTime = performance.now();\n\n if (builder) {\n const pluginResponse = await builder.runHandleRequest(req, {\n port: handle?.port ?? PORT,\n hostname: handle?.hostname ?? \"localhost\",\n });\n if (pluginResponse) {\n const securedResponse = wrapPluginResponse(pluginResponse, true);\n logger.request(\n req.method,\n pathname,\n securedResponse.status,\n Math.round(performance.now() - startTime)\n );\n return securedResponse;\n }\n }\n\n try {\n if (pathname === \"/__hmr\") {\n const stream = new ReadableStream({\n start(controller) {\n hmrClients.add(controller);\n controller.enqueue(new TextEncoder().encode(\"data: connected\\n\\n\"));\n },\n cancel(controller) {\n hmrClients.delete(controller);\n },\n });\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n }\n\n if (pathname.startsWith(\"/assets/\") || /\\.\\w+$/.test(pathname)) {\n const staticRes = serveStatic(pathname);\n if (staticRes) return staticRes;\n }\n\n let response: Response;\n\n // Manual route matching — same routes Bun.FileSystemRouter derives\n // from `.docu/pages/` (\"/\", \"/docs/[[...slug]]\", \"/404\").\n if (pathname === \"/\") {\n response = handleIndex(state);\n } else if (pathname === \"/docs\" || pathname === \"/docs/\") {\n response = await handleDocsIndex(state);\n } else if (pathname.startsWith(\"/docs/\")) {\n const slug = pathname.slice(\"/docs/\".length).split(\"/\").filter(Boolean);\n response =\n slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);\n } else {\n response = handleNotFound(state);\n }\n\n logger.request(\n req.method,\n pathname,\n response.status,\n Math.round(performance.now() - startTime)\n );\n return response;\n } catch (err) {\n captureException(err, { method: req.method, pathname });\n logger.request(req.method, pathname, 500, Math.round(performance.now() - startTime));\n return serverErrorResponse(err);\n }\n };\n\n handle = await adapter.serve(fetchHandler, { port: PORT, idleTimeout: 255 });\n\n logger.ready(handle.port, true);\n return handle;\n}\n"],"mappings":"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"}
|
package/.docu/lib/server.node.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import "./chunk-LXJLSXQI.js";
|
|
5
|
-
import {
|
|
6
|
-
nodeAdapter
|
|
7
|
-
} from "./chunk-UISOJ4RW.js";
|
|
8
|
-
import "./chunk-EOK6KATZ.js";
|
|
9
|
-
import "./chunk-IVM5UM44.js";
|
|
10
|
-
import "./chunk-4IQXHHPF.js";
|
|
11
|
-
|
|
12
|
-
// .docu/node/server.node.ts
|
|
1
|
+
import { t as nodeAdapter } from "./node-DPTySdwG.js";
|
|
2
|
+
import { t as runServer } from "./server.impl-CJuWimfN.js";
|
|
3
|
+
//#region .docu/node/server.node.ts
|
|
13
4
|
await runServer(nodeAdapter);
|
|
5
|
+
//#endregion
|
|
6
|
+
export {};
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=server.node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.node.js","names":[],"sources":["../node/server.node.ts"],"sourcesContent":["import { nodeAdapter } from \"./runtime\";\nimport { runServer } from \"./server.impl\";\n\nawait runServer(nodeAdapter);\n"],"mappings":";;;AAGA,MAAM,UAAU,WAAW"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { readdir, stat } from "node:fs/promises";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { cn, formatDate2 } from "@docubook/core";
|
|
6
|
+
//#region .docu/node/security.ts
|
|
7
|
+
var SECURITY_HEADERS = {
|
|
8
|
+
"Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
|
|
9
|
+
"X-Frame-Options": "DENY",
|
|
10
|
+
"X-Content-Type-Options": "nosniff",
|
|
11
|
+
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
12
|
+
"Permissions-Policy": "camera=(), microphone=(), geolocation=()"
|
|
13
|
+
};
|
|
14
|
+
function generateNonce() {
|
|
15
|
+
return randomBytes(16).toString("base64");
|
|
16
|
+
}
|
|
17
|
+
function cspHeader(nonce, allowEval = false) {
|
|
18
|
+
return [
|
|
19
|
+
"default-src 'self'",
|
|
20
|
+
allowEval ? `script-src 'self' 'nonce-${nonce}' 'unsafe-eval'` : `script-src 'self' 'nonce-${nonce}'`,
|
|
21
|
+
"style-src 'self' 'unsafe-inline'",
|
|
22
|
+
"img-src 'self' https: data:",
|
|
23
|
+
"font-src 'self' data:",
|
|
24
|
+
"connect-src 'self' https:",
|
|
25
|
+
"frame-src https://www.youtube-nocookie.com",
|
|
26
|
+
"frame-ancestors 'none'"
|
|
27
|
+
].join("; ");
|
|
28
|
+
}
|
|
29
|
+
function isPathSafe(pathname, baseDir) {
|
|
30
|
+
const resolved = resolve(baseDir, decodeURIComponent(pathname).slice(1));
|
|
31
|
+
const baseDirSlash = baseDir.endsWith("/") ? baseDir : baseDir + "/";
|
|
32
|
+
if (!(resolved === baseDir || resolved.startsWith(baseDirSlash))) return false;
|
|
33
|
+
if (resolved === baseDir) return true;
|
|
34
|
+
try {
|
|
35
|
+
const real = realpathSync(resolved);
|
|
36
|
+
const realBase = realpathSync(baseDir);
|
|
37
|
+
const realBaseDirSlash = realBase.endsWith("/") ? realBase : realBase + "/";
|
|
38
|
+
return real.startsWith(realBaseDirSlash);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
const nodeErr = err;
|
|
41
|
+
if (nodeErr.code === "ENOENT") return true;
|
|
42
|
+
console.error(`[security] isPathSafe error for pathname="${pathname}": ${nodeErr.message} (code=${nodeErr.code})`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isSlugSafe(slug, docsDir) {
|
|
47
|
+
const resolved = resolve(docsDir, slug);
|
|
48
|
+
const docsDirSlash = docsDir.endsWith("/") ? docsDir : docsDir + "/";
|
|
49
|
+
if (!(resolved === docsDir || resolved.startsWith(docsDirSlash))) return false;
|
|
50
|
+
if (resolved === docsDir) return true;
|
|
51
|
+
try {
|
|
52
|
+
const real = realpathSync(resolved);
|
|
53
|
+
const realDocsDir = realpathSync(docsDir);
|
|
54
|
+
const realDocsDirSlash = realDocsDir.endsWith("/") ? realDocsDir : realDocsDir + "/";
|
|
55
|
+
return real.startsWith(realDocsDirSlash);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const nodeErr = err;
|
|
58
|
+
if (nodeErr.code === "ENOENT") return true;
|
|
59
|
+
console.error(`[security] isSlugSafe error for slug="${slug}": ${nodeErr.message} (code=${nodeErr.code})`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Normalize an esbuild importer path to a canonical forward-slash absolute form. */
|
|
64
|
+
function normalizeImporterPath(importer) {
|
|
65
|
+
return resolve(importer).replace(/\\/g, "/");
|
|
66
|
+
}
|
|
67
|
+
function injectNonce(html, nonce) {
|
|
68
|
+
return html.replace(/<script\b(?![^>]*\bsrc\s*=)([^>]*)>/gi, (match) => {
|
|
69
|
+
if (/nonce\s*=/i.test(match)) return match.replace(/nonce="[^"]*"/i, `nonce="${nonce}"`);
|
|
70
|
+
return match.replace(/>$/, ` nonce="${nonce}">`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Wrap a plugin response with security headers.
|
|
75
|
+
* - Fills in SECURITY_HEADERS defaults where plugin hasn't set a value
|
|
76
|
+
* - Adds Content-Security-Policy for HTML responses (with optional unsafe-eval)
|
|
77
|
+
* - Preserves plugin body, status, statusText unchanged
|
|
78
|
+
*/
|
|
79
|
+
function wrapPluginResponse(pluginResponse, allowEval = false) {
|
|
80
|
+
const securedHeaders = new Headers(pluginResponse.headers);
|
|
81
|
+
for (const [key, value] of Object.entries(SECURITY_HEADERS)) if (!securedHeaders.has(key)) securedHeaders.set(key, value);
|
|
82
|
+
let body = pluginResponse.body;
|
|
83
|
+
if ((securedHeaders.get("Content-Type") || "").includes("text/html") && !securedHeaders.has("Content-Security-Policy")) {
|
|
84
|
+
const nonce = generateNonce();
|
|
85
|
+
securedHeaders.set("Content-Security-Policy", cspHeader(nonce, allowEval));
|
|
86
|
+
if (typeof body === "string") body = injectNonce(body, nonce);
|
|
87
|
+
}
|
|
88
|
+
return new Response(body, {
|
|
89
|
+
status: pluginResponse.status,
|
|
90
|
+
statusText: pluginResponse.statusText,
|
|
91
|
+
headers: securedHeaders
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function htmlResponse(html, nonce, status = 200, allowEval = false) {
|
|
95
|
+
return new Response(html, {
|
|
96
|
+
status,
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "text/html",
|
|
99
|
+
...SECURITY_HEADERS,
|
|
100
|
+
"Content-Security-Policy": cspHeader(nonce, allowEval)
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region .docu/node/utils.ts
|
|
106
|
+
/**
|
|
107
|
+
* Scan a directory recursively for MDX/MD files.
|
|
108
|
+
* Skips "assets" directories, hidden directories (dot-prefixed), and root-level index.mdx.
|
|
109
|
+
* Shared between build.ts and search-indexer.ts.
|
|
110
|
+
*/
|
|
111
|
+
async function scanMdxFiles(dir, baseDir = "") {
|
|
112
|
+
const files = [];
|
|
113
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const fullPath = join(dir, entry.name);
|
|
116
|
+
const relativePath = baseDir ? `${baseDir}/${entry.name}` : entry.name;
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
if (entry.name === "assets" || entry.name.startsWith(".")) continue;
|
|
119
|
+
files.push(...await scanMdxFiles(fullPath, relativePath));
|
|
120
|
+
} else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) {
|
|
121
|
+
if (entry.name === "index.mdx" && !baseDir) continue;
|
|
122
|
+
const stats = await stat(fullPath);
|
|
123
|
+
let path = relativePath.replace(/\.(mdx|md)$/, "");
|
|
124
|
+
if (/\/index$/.test(path)) path = path.replace(/\/index$/, "");
|
|
125
|
+
files.push({
|
|
126
|
+
path,
|
|
127
|
+
absPath: fullPath,
|
|
128
|
+
mtime: stats.mtimeMs
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return files;
|
|
133
|
+
}
|
|
134
|
+
function isExternalUrl(url) {
|
|
135
|
+
return /^(https?:\/\/|\/\/)/.test(url);
|
|
136
|
+
}
|
|
137
|
+
/** Suffix an internal docs link with `.html` to match the flat static build output. */
|
|
138
|
+
function docsHtmlHref(path) {
|
|
139
|
+
return `${path}.html`;
|
|
140
|
+
}
|
|
141
|
+
/** Map a `/docs{.html,/*.html}` request back to its extensionless route (dev server).
|
|
142
|
+
* Handles both `/docs/*.html` (pages) and `/docs.html` (edge case). */
|
|
143
|
+
function stripDocsHtmlSuffix(pathname) {
|
|
144
|
+
if (pathname === "/docs.html") return "/docs";
|
|
145
|
+
if (pathname.startsWith("/docs/") && pathname.endsWith(".html")) return pathname.slice(0, -5);
|
|
146
|
+
return pathname;
|
|
147
|
+
}
|
|
148
|
+
var MIME_TYPES = {
|
|
149
|
+
html: "text/html",
|
|
150
|
+
css: "text/css",
|
|
151
|
+
js: "application/javascript",
|
|
152
|
+
json: "application/json",
|
|
153
|
+
png: "image/png",
|
|
154
|
+
jpg: "image/jpeg",
|
|
155
|
+
jpeg: "image/jpeg",
|
|
156
|
+
svg: "image/svg+xml",
|
|
157
|
+
ico: "image/x-icon",
|
|
158
|
+
woff: "font/woff",
|
|
159
|
+
woff2: "font/woff2"
|
|
160
|
+
};
|
|
161
|
+
function getContentType(pathname) {
|
|
162
|
+
return MIME_TYPES[pathname.split(".").pop()?.toLowerCase() || ""] || "application/octet-stream";
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
export { isExternalUrl as a, SECURITY_HEADERS as c, htmlResponse as d, injectNonce as f, wrapPluginResponse as g, normalizeImporterPath as h, getContentType as i, cspHeader as l, isSlugSafe as m, docsHtmlHref as n, scanMdxFiles as o, isPathSafe as p, formatDate2 as r, stripDocsHtmlSuffix as s, cn as t, generateNonce as u };
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=utils-DA17MyQG.js.map
|