@docubook/flame 2.0.3 → 2.1.0
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/Context.tsx +6 -5
- package/.docu/components/DocsLayout.tsx +7 -4
- package/.docu/components/Menu.tsx +10 -9
- package/.docu/components/Navbar.tsx +24 -22
- package/.docu/components/Pagination.tsx +3 -2
- package/.docu/components/Search.tsx +6 -1
- package/.docu/components/Sidebar.tsx +22 -21
- package/.docu/components/Sublink.tsx +3 -3
- package/.docu/lib/build.deno.js +1 -1
- package/.docu/lib/{build.impl-BSt6Vikt.js → build.impl-DK485Nx1.js} +60 -31
- package/.docu/lib/build.impl-DK485Nx1.js.map +1 -0
- package/.docu/lib/build.impl-Dmybgcj2.js +2 -0
- package/.docu/lib/build.node.js +1 -1
- package/.docu/lib/clean.js +1 -1
- package/.docu/lib/deploy.deno.js +1 -1
- package/.docu/lib/deploy.node.js +1 -1
- package/.docu/lib/{deploy.shared-D6S_FE7d.js → deploy.shared-BW2KXFXs.js} +27 -8
- package/.docu/lib/deploy.shared-BW2KXFXs.js.map +1 -0
- package/.docu/lib/{html.shared-CsEFmy_Y.js → html.shared-RPCM1y5Q.js} +264 -73
- package/.docu/lib/html.shared-RPCM1y5Q.js.map +1 -0
- package/.docu/lib/{logger-DFKQUy8M.js → logger-DYFD47f8.js} +3 -3
- package/.docu/lib/{logger-DFKQUy8M.js.map → logger-DYFD47f8.js.map} +1 -1
- package/.docu/lib/paths-CHXABfXa.js +332 -0
- package/.docu/lib/paths-CHXABfXa.js.map +1 -0
- package/.docu/lib/preview.deno.js +1 -1
- package/.docu/lib/{preview.impl-BfESMUM7.js → preview.impl-DMXVsGs0.js} +4 -4
- package/.docu/lib/preview.impl-DMXVsGs0.js.map +1 -0
- package/.docu/lib/preview.node.js +1 -1
- package/.docu/lib/{utils-B_CoyKie.js → server-utils-BYWB7A6T.js} +3 -21
- package/.docu/lib/server-utils-BYWB7A6T.js.map +1 -0
- package/.docu/lib/server.deno.js +1 -1
- package/.docu/lib/{server.impl-CZ4eMe5L.js → server.impl-DcyZhPA_.js} +24 -19
- package/.docu/lib/server.impl-DcyZhPA_.js.map +1 -0
- package/.docu/lib/server.node.js +1 -1
- package/.docu/node/base-path.ts +306 -0
- package/.docu/node/build.impl.ts +50 -17
- package/.docu/node/build.ts +50 -17
- package/.docu/node/cache-key.ts +92 -4
- package/.docu/node/client-routes.ts +16 -1
- package/.docu/node/deploy.shared.ts +51 -15
- package/.docu/node/deploy.ts +7 -3
- package/.docu/node/html.shared.ts +33 -3
- package/.docu/node/html.ts +18 -3
- package/.docu/node/hydrate.node.ts +6 -0
- package/.docu/node/hydrate.ts +16 -1
- package/.docu/node/mdx.ts +102 -23
- package/.docu/node/paths.ts +115 -0
- package/.docu/node/preview.impl.ts +1 -1
- package/.docu/node/preview.ts +1 -1
- package/.docu/node/search-indexer.ts +20 -3
- package/.docu/node/seo.ts +41 -7
- package/.docu/node/server-routes.ts +34 -11
- package/.docu/node/server-utils.ts +152 -0
- package/.docu/node/server.impl.ts +22 -12
- package/.docu/node/server.ts +23 -27
- package/.docu/node/types.ts +13 -0
- package/.docu/node/utils.ts +56 -149
- package/.docu/pages/404.tsx +2 -1
- package/.docu/pages/index.tsx +6 -13
- package/bin/cli.js +5 -0
- package/docu.schema.json +10 -1
- package/package.json +5 -5
- package/template/docu.json +1 -0
- package/.docu/lib/build.impl-BSt6Vikt.js.map +0 -1
- package/.docu/lib/build.impl-xNDEh8DJ.js +0 -2
- package/.docu/lib/deploy.shared-D6S_FE7d.js.map +0 -1
- package/.docu/lib/html.shared-CsEFmy_Y.js.map +0 -1
- package/.docu/lib/paths-Cbz-dj4s.js +0 -72
- package/.docu/lib/paths-Cbz-dj4s.js.map +0 -1
- package/.docu/lib/preview.impl-BfESMUM7.js.map +0 -1
- package/.docu/lib/server.impl-CZ4eMe5L.js.map +0 -1
- package/.docu/lib/utils-B_CoyKie.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as DOCS_DIR,
|
|
2
|
-
import { D as
|
|
3
|
-
import {
|
|
4
|
-
import { t as logger } from "./logger-
|
|
1
|
+
import { E as resolveBasePath, O as stripDocsHtmlSuffix, S as docsDepth, _ as loadDocuConfig, a as DOCS_DIR, f as assertValidBasePath, h as defaultFavicon, l as PAGES_DIR, r as DIST_DIR, u as PROJECT_ROOT, w as matchDocsSlug, y as servedBasePath } from "./paths-CHXABfXa.js";
|
|
2
|
+
import { D as BuildPluginBuilder, O as loadPlugins, a as IndexPage, b as buildClientBundle, c as captureException, f as compileMdx, i as DocsLayout, l as initSentry, m as frontmatterField, n as hmrScript, o as NotFoundPage, r as htmlShell, s as DocsPage, t as errorHtml, u as generateSearchIndex, x as computeInlineThemeCss } from "./html.shared-RPCM1y5Q.js";
|
|
3
|
+
import { c as htmlResponse, d as isSlugSafe, i as SECURITY_HEADERS, p as wrapPluginResponse, s as generateNonce, t as getContentType, u as isPathSafe } from "./server-utils-BYWB7A6T.js";
|
|
4
|
+
import { t as logger } from "./logger-DYFD47f8.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { existsSync, readFileSync, statSync, watch } from "node:fs";
|
|
7
7
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -11,7 +11,7 @@ import { renderToString } from "react-dom/server";
|
|
|
11
11
|
//#region .docu/node/server-routes.ts
|
|
12
12
|
async function createHtmlResponse(title, description, body, status, state, pageType, page, depth = 0) {
|
|
13
13
|
const nonce = generateNonce();
|
|
14
|
-
const favicon = state.docuConfig.meta?.favicon ||
|
|
14
|
+
const favicon = state.docuConfig.meta?.favicon || defaultFavicon();
|
|
15
15
|
const assets = state.assetManifest[pageType];
|
|
16
16
|
const context = {
|
|
17
17
|
...page,
|
|
@@ -32,6 +32,8 @@ async function createHtmlResponse(title, description, body, status, state, pageT
|
|
|
32
32
|
headExtra: state.builder?.collectHead(context),
|
|
33
33
|
bodyExtra: state.builder?.collectBody(context),
|
|
34
34
|
depth,
|
|
35
|
+
basePath: servedBasePath(),
|
|
36
|
+
deployPath: "",
|
|
35
37
|
absoluteAssets: status === 404
|
|
36
38
|
});
|
|
37
39
|
if (state.builder) html = await state.builder.runTransformHtmlChain(html, context);
|
|
@@ -112,7 +114,7 @@ async function renderDocsServerPage(doc, slug, pathname, state) {
|
|
|
112
114
|
compiledSource: doc.compiledSource
|
|
113
115
|
}));
|
|
114
116
|
const body = renderToString(page);
|
|
115
|
-
const depth = slug.
|
|
117
|
+
const depth = docsDepth(slug.join("/"), servedBasePath());
|
|
116
118
|
return createHtmlResponse(title, description, body, 200, state, "docs", {
|
|
117
119
|
slug: slug.join("/"),
|
|
118
120
|
filePath: doc.filePath,
|
|
@@ -129,14 +131,14 @@ async function renderPage(Component, title, description, status, state, props =
|
|
|
129
131
|
}
|
|
130
132
|
async function handleDocsIndex(state) {
|
|
131
133
|
const doc = await getDocsForSlug("", state);
|
|
132
|
-
if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {},
|
|
133
|
-
return renderDocsServerPage(doc, [], "/
|
|
134
|
+
if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, docsDepth("", servedBasePath()));
|
|
135
|
+
return renderDocsServerPage(doc, [], servedBasePath() || "/", state);
|
|
134
136
|
}
|
|
135
137
|
async function handleDocsRoute(slug, state) {
|
|
136
138
|
const path = slug.join("/");
|
|
137
139
|
const doc = await getDocsForSlug(path, state);
|
|
138
|
-
if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {},
|
|
139
|
-
return renderDocsServerPage(doc, slug,
|
|
140
|
+
if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, docsDepth(path, servedBasePath()));
|
|
141
|
+
return renderDocsServerPage(doc, slug, `${servedBasePath()}/${path}`, state);
|
|
140
142
|
}
|
|
141
143
|
async function handleIndex(state) {
|
|
142
144
|
const page = React.createElement(IndexPage);
|
|
@@ -164,9 +166,10 @@ function serveStatic(pathname) {
|
|
|
164
166
|
} catch (err) {
|
|
165
167
|
if (err.code !== "ENOENT") throw err;
|
|
166
168
|
}
|
|
167
|
-
|
|
169
|
+
const docsAssetsPrefix = `${servedBasePath()}/assets/`;
|
|
170
|
+
if (decoded.startsWith(docsAssetsPrefix)) {
|
|
168
171
|
const docsAssetsDir = resolve(DOCS_DIR, "assets");
|
|
169
|
-
const requestedRelative = decoded.slice(
|
|
172
|
+
const requestedRelative = decoded.slice(docsAssetsPrefix.length);
|
|
170
173
|
const docsAsset = resolve(docsAssetsDir, requestedRelative);
|
|
171
174
|
const docsAssetsDirWithSep = docsAssetsDir.endsWith("/") ? docsAssetsDir : docsAssetsDir + "/";
|
|
172
175
|
if (docsAsset !== docsAssetsDir && !docsAsset.startsWith(docsAssetsDirWithSep)) return null;
|
|
@@ -200,6 +203,8 @@ function serverErrorResponse(error) {
|
|
|
200
203
|
*/
|
|
201
204
|
async function runServer(adapter) {
|
|
202
205
|
const docuConfig = loadDocuConfig();
|
|
206
|
+
const resolvedBasePath = resolveBasePath(docuConfig);
|
|
207
|
+
for (const warning of assertValidBasePath()) logger.warn(warning.message);
|
|
203
208
|
const parsedPort = parseInt(process.env.PORT ?? "3000", 10);
|
|
204
209
|
const PORT = Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : 3e3;
|
|
205
210
|
logger.buildStart();
|
|
@@ -283,7 +288,7 @@ async function runServer(adapter) {
|
|
|
283
288
|
let handle = null;
|
|
284
289
|
const fetchHandler = async (req) => {
|
|
285
290
|
const url = new URL(req.url);
|
|
286
|
-
const pathname = stripDocsHtmlSuffix(url.pathname);
|
|
291
|
+
const pathname = stripDocsHtmlSuffix(url.pathname, resolvedBasePath);
|
|
287
292
|
const startTime = performance.now();
|
|
288
293
|
if (builder) {
|
|
289
294
|
const pluginResponse = await builder.runHandleRequest(req, {
|
|
@@ -320,11 +325,11 @@ async function runServer(adapter) {
|
|
|
320
325
|
}
|
|
321
326
|
let response;
|
|
322
327
|
if (pathname === "/") response = await handleIndex(state);
|
|
323
|
-
else
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
response =
|
|
327
|
-
}
|
|
328
|
+
else {
|
|
329
|
+
const docsSlug = matchDocsSlug(pathname, resolvedBasePath);
|
|
330
|
+
if (docsSlug) response = docsSlug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(docsSlug, state);
|
|
331
|
+
else response = await handleNotFound(state);
|
|
332
|
+
}
|
|
328
333
|
logger.request(req.method, pathname, response.status, Math.round(performance.now() - startTime));
|
|
329
334
|
return response;
|
|
330
335
|
} catch (err) {
|
|
@@ -346,4 +351,4 @@ async function runServer(adapter) {
|
|
|
346
351
|
//#endregion
|
|
347
352
|
export { runServer as t };
|
|
348
353
|
|
|
349
|
-
//# sourceMappingURL=server.impl-
|
|
354
|
+
//# sourceMappingURL=server.impl-DcyZhPA_.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.impl-DcyZhPA_.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 \"./server-utils\";\nimport { defaultFavicon } from \"./paths\";\nimport { DOCS_DIR, DIST_DIR, PAGES_DIR, PROJECT_ROOT, servedBasePath, docsDepth } from \"./paths\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport type { PageContext, PageType } from \"./plugin\";\nimport type { AssetEntry, AssetManifest, 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: AssetManifest;\n inlineThemeCss?: string;\n builder: BuildPluginBuilder | null;\n}\n\nasync function createHtmlResponse(\n title: string,\n description: string,\n body: string,\n status: number,\n state: ServerState,\n pageType: PageType,\n page: Omit<PageContext, \"pageType\" | \"assets\" | \"config\">,\n depth = 0\n): Promise<Response> {\n const nonce = generateNonce();\n const favicon = state.docuConfig.meta?.favicon || defaultFavicon();\n const assets: AssetEntry = state.assetManifest[pageType];\n const context: PageContext = {\n ...page,\n pageType,\n assets,\n config: state.docuConfig,\n };\n let html = createHtmlShell({\n title,\n description,\n body,\n favicon,\n css: assets.css,\n js: assets.js,\n nonce,\n extraScripts: hmrScript(nonce),\n themeCss: state.inlineThemeCss,\n headExtra: state.builder?.collectHead(context),\n bodyExtra: state.builder?.collectBody(context),\n depth,\n basePath: servedBasePath(),\n // Dev serves the project from the origin root, so the host's deployment\n // path never applies here — only the static build writes it into HTML.\n deployPath: \"\",\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 if (state.builder) html = await state.builder.runTransformHtmlChain(html, context);\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,\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 // Same relative climb as the static build — see docsDepth.\n const depth = docsDepth(slug.join(\"/\"), servedBasePath());\n\n return createHtmlResponse(\n title,\n description,\n body,\n 200,\n state,\n \"docs\",\n {\n slug: slug.join(\"/\"),\n filePath: doc.filePath,\n frontmatter: doc.frontmatter,\n content: doc.resolvedContent,\n },\n depth\n );\n}\n\nasync function 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): Promise<Response> {\n const body = renderToString(React.createElement(Component, props));\n return createHtmlResponse(\n title,\n description,\n body,\n status,\n state,\n \"notFound\",\n {\n slug: \"404\",\n filePath: resolve(PAGES_DIR, \"404.tsx\"),\n frontmatter: {},\n },\n depth\n );\n}\n\nexport async function handleDocsIndex(state: ServerState): Promise<Response> {\n const doc = await getDocsForSlug(\"\", state);\n if (!doc)\n return renderPage(\n NotFoundPage,\n \"404 - Not Found\",\n \"\",\n 404,\n state,\n {},\n docsDepth(\"\", servedBasePath())\n );\n return renderDocsServerPage(doc, [], servedBasePath() || \"/\", 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(\n NotFoundPage,\n \"404 - Not Found\",\n \"\",\n 404,\n state,\n {},\n docsDepth(path, servedBasePath())\n );\n return renderDocsServerPage(doc, slug, `${servedBasePath()}/${path}`, state);\n}\n\nexport async function handleIndex(state: ServerState): Promise<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 \"home\",\n {\n slug: \"\",\n filePath: resolve(PAGES_DIR, \"index.tsx\"),\n frontmatter: { ...state.docuConfig.meta } as Record<string, unknown>,\n }\n );\n}\n\nexport async function handleNotFound(state: ServerState, depth = 0): Promise<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 const docsAssetsPrefix = `${servedBasePath()}/assets/`;\n if (decoded.startsWith(docsAssetsPrefix)) {\n const docsAssetsDir = resolve(DOCS_DIR, \"assets\");\n const requestedRelative = decoded.slice(docsAssetsPrefix.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, resolveBasePath, assertValidBasePath } 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 { matchDocsSlug, stripDocsHtmlSuffix } from \"./utils\";\n\nexport async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle> {\n const docuConfig = loadDocuConfig();\n const resolvedBasePath = resolveBasePath(docuConfig);\n\n // Report normalized prefixes (and stop on a value that cannot be honored)\n // before the server announces a site that would be built somewhere else.\n for (const warning of assertValidBasePath()) logger.warn(warning.message);\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 await builder.runOnStart();\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, resolvedBasePath);\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 assetManifest: state.assetManifest,\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 // The landing page owns `/` in every deployment. At the deployment root\n // (`meta.basePath: \"\"`) docs would otherwise claim it, but the build\n // skips the docs index there — so dev must not serve it either.\n // Static assets were already handled above, so a root deployment may\n // still match every other path as a docs route.\n if (pathname === \"/\") {\n response = await handleIndex(state);\n } else {\n const docsSlug = matchDocsSlug(pathname, resolvedBasePath);\n if (docsSlug) {\n response =\n docsSlug.length === 0\n ? await handleDocsIndex(state)\n : await handleDocsRoute(docsSlug, state);\n } else {\n response = await handleNotFound(state);\n }\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
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import type { DocuConfig } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure base-path helpers shared by server modules and client-side components.
|
|
5
|
+
*
|
|
6
|
+
* This module MUST stay free of Node built-ins (`node:path`, `node:fs`) and of
|
|
7
|
+
* modules that evaluate them at import time (like `./paths`): it is bundled
|
|
8
|
+
* into the client, where `import.meta.dirname` and `process.cwd()` do not
|
|
9
|
+
* exist. All functions here are string operations over a resolved prefix.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Default URL prefix the docs site is served under. Kept as `/docs` so
|
|
13
|
+
* existing deployments keep byte-identical output. */
|
|
14
|
+
export const DEFAULT_BASE_PATH = "/docs";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a configured base path into `/prefix` form with no trailing slash.
|
|
18
|
+
* `""` and `"/"` mean "served at the domain root"; a missing value is not
|
|
19
|
+
* handled here — `resolveBasePath` owns the default. Absolute URLs
|
|
20
|
+
* (`https://host/x`) contribute only their pathname, so a `meta.baseURL` can be
|
|
21
|
+
* passed straight in.
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeBasePath(value: string | undefined | null): string {
|
|
24
|
+
if (typeof value !== "string") return DEFAULT_BASE_PATH;
|
|
25
|
+
let path = value.trim();
|
|
26
|
+
if (path.length === 0 || path === "/") return "";
|
|
27
|
+
try {
|
|
28
|
+
if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(path)) path = new URL(path).pathname;
|
|
29
|
+
} catch {
|
|
30
|
+
// malformed URL — fall through and treat the raw value as a path
|
|
31
|
+
}
|
|
32
|
+
path = `/${path.replace(/^\/+|\/+$/g, "")}`;
|
|
33
|
+
return path === "/" ? "" : path;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the docs URL prefix from config.
|
|
38
|
+
*
|
|
39
|
+
* An explicit `meta.basePath` wins, including `""` for a root deployment.
|
|
40
|
+
* Otherwise `/docs` (the historical behaviour).
|
|
41
|
+
*
|
|
42
|
+
* Deliberately NOT derived from `meta.baseURL`'s pathname: `baseURL` is the
|
|
43
|
+
* origin + deployment root and `basePath` is the prefix beneath it. Deriving
|
|
44
|
+
* one from the other's pathname double-counts that path — the SEO/canonical
|
|
45
|
+
* builder appends the prefix to `baseURL`, so `baseURL: ".../repo"` deriving
|
|
46
|
+
* `basePath: "/repo"` produced `.../repo/repo/...`. They must be configured
|
|
47
|
+
* independently; the schema documents both.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveBasePath(config?: DocuConfig | null): string {
|
|
50
|
+
const meta = config?.meta;
|
|
51
|
+
if (typeof meta?.basePath !== "string") return DEFAULT_BASE_PATH;
|
|
52
|
+
// Root forms (`""`, `"/"`, `"///"`) stay root. Everything else is
|
|
53
|
+
// canonicalized so routing, output directories and every generated URL agree
|
|
54
|
+
// on one spelling — `"/Docs"` and `"/docs me"` are honored as `"/docs"`
|
|
55
|
+
// and `"/docs-me"` (reported by `validateBasePath`).
|
|
56
|
+
if (normalizeBasePath(meta.basePath) === "") return "";
|
|
57
|
+
return canonicalBasePath(meta.basePath) || DEFAULT_BASE_PATH;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Deployment-root path contributed by the host, taken from `meta.baseURL`:
|
|
62
|
+
* `""` when the origin serves the dist at `/`, `/repo` for a GitHub Pages
|
|
63
|
+
* project site (which serves the artifact under `<origin>/repo/`).
|
|
64
|
+
*
|
|
65
|
+
* Distinct from the base path — `basePath` is the prefix *inside* the dist,
|
|
66
|
+
* this is the prefix *above* it. Only root-absolute references need it (the 404
|
|
67
|
+
* fallback and the search index); relative ones line up already because pages
|
|
68
|
+
* and assets share the deployment root.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveDeployPath(config?: DocuConfig | null): string {
|
|
71
|
+
const raw = config?.meta?.baseURL;
|
|
72
|
+
if (typeof raw !== "string") return "";
|
|
73
|
+
try {
|
|
74
|
+
return normalizeBasePath(new URL(raw).pathname);
|
|
75
|
+
} catch {
|
|
76
|
+
// Not an absolute URL. A root-relative value is still a usable path; a bare
|
|
77
|
+
// hostname without a scheme is a config mistake, not a prefix — ignore it.
|
|
78
|
+
return raw.startsWith("/") ? normalizeBasePath(raw) : "";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface BasePathIssue {
|
|
83
|
+
/** `error` blocks the build; `warning` only reports. */
|
|
84
|
+
level: "error" | "warning";
|
|
85
|
+
message: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Canonical prefix form: lowercase segments with whitespace collapsed to dashes
|
|
90
|
+
* and characters a static host cannot serve dropped (`"/Docs"` → `"/docs"`,
|
|
91
|
+
* `"/docs me"` → `"/docs-me"`). Structural normalization runs first, so an
|
|
92
|
+
* absolute value (`https://host/x`) works too. Returns `""` when nothing
|
|
93
|
+
* servable is left, which callers treat as a config mistake rather than a
|
|
94
|
+
* silent root deployment.
|
|
95
|
+
*/
|
|
96
|
+
export function canonicalBasePath(value: string): string {
|
|
97
|
+
const structural = normalizeBasePath(value);
|
|
98
|
+
if (structural === "") return "";
|
|
99
|
+
// Whitespace and unservable characters fold away, then dot segments are
|
|
100
|
+
// dropped: a prefix is a URL path *and* an output directory, so `.`/`..`
|
|
101
|
+
// would either escape the dist or resolve differently than the URL does.
|
|
102
|
+
const cleaned = structural
|
|
103
|
+
.replace(/\s+/g, "-")
|
|
104
|
+
.replace(/[^A-Za-z0-9\-._~/]+/g, "")
|
|
105
|
+
.split("/")
|
|
106
|
+
.filter((segment) => segment.length > 0 && segment !== "." && segment !== "..")
|
|
107
|
+
.join("/")
|
|
108
|
+
.replace(/-{2,}/g, "-")
|
|
109
|
+
.toLowerCase();
|
|
110
|
+
return cleaned.length > 0 ? `/${cleaned}` : "";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validate an authored `meta.basePath`.
|
|
115
|
+
*
|
|
116
|
+
* `resolveBasePath` normalizes what a static host cannot serve, so this reports
|
|
117
|
+
* what it had to change instead of blocking the build: only a non-string value
|
|
118
|
+
* (which cannot be normalized) is an error. Warnings name the reasons and the
|
|
119
|
+
* canonical value, so the author can fix `docu.json` and keep the editor schema
|
|
120
|
+
* — which only accepts the canonical form — quiet.
|
|
121
|
+
*/
|
|
122
|
+
export function validateBasePath(value: unknown): BasePathIssue[] {
|
|
123
|
+
if (value === undefined) return [];
|
|
124
|
+
if (typeof value !== "string") {
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
level: "error",
|
|
128
|
+
message: `must be a string (received ${
|
|
129
|
+
value === null ? "null" : typeof value
|
|
130
|
+
}) — remove it or set "${DEFAULT_BASE_PATH}"`,
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// `""`, `"/"`, `"///"` and whitespace-only values all mean the deployment
|
|
136
|
+
// root, which is always servable.
|
|
137
|
+
const structural = normalizeBasePath(value);
|
|
138
|
+
if (structural === "") return [];
|
|
139
|
+
|
|
140
|
+
const canonical = canonicalBasePath(value);
|
|
141
|
+
if (canonical === "") {
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
level: "warning",
|
|
145
|
+
message: `"${value}" has no characters a static host can serve — falling back to "${DEFAULT_BASE_PATH}"`,
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
if (canonical === structural) return [];
|
|
150
|
+
|
|
151
|
+
const segments = structural.replace(/^\/+/, "");
|
|
152
|
+
const reasons: string[] = [];
|
|
153
|
+
if (/\s/.test(segments)) reasons.push("whitespace");
|
|
154
|
+
// Whitespace is already reported above — list only the other unservable
|
|
155
|
+
// characters here, or the message prints a blank `( )`.
|
|
156
|
+
const invalid = [
|
|
157
|
+
...new Set(segments.replace(/[\s]/g, "").replace(/[A-Za-z0-9\-._~/]/g, "")),
|
|
158
|
+
].join(" ");
|
|
159
|
+
if (invalid.length > 0) reasons.push(`characters that need percent-encoding (${invalid})`);
|
|
160
|
+
if (segments !== segments.toLowerCase()) reasons.push("non-lowercase letters");
|
|
161
|
+
if (/\.\.|(^|\/)\.(\/|$)|--|\/\//.test(segments))
|
|
162
|
+
reasons.push("dot segments or doubled separators");
|
|
163
|
+
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
level: "warning",
|
|
167
|
+
message: `"${value}" ${
|
|
168
|
+
reasons.length > 0 ? `contains ${reasons.join(", ")}` : "is not canonical"
|
|
169
|
+
} — normalized to "${canonical}"; update docu.json to match`,
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Join a serve-prefix with a slash-relative path, keeping exactly one slash. */
|
|
175
|
+
export function withBasePath(path: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {
|
|
176
|
+
const suffix = path.length === 0 || path === "/" ? "" : `/${path.replace(/^\/+|\/+$/g, "")}`;
|
|
177
|
+
return `${resolvedBasePath}${suffix}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Prefix a root-relative asset path (e.g. `/assets/x.js`) with the base path. */
|
|
181
|
+
export function assetHref(assetPath: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {
|
|
182
|
+
return withBasePath(assetPath, resolvedBasePath);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Apply the base path to a docs route unless it already carries it.
|
|
187
|
+
* Idempotent, which matters because routes are prefixed at more than one
|
|
188
|
+
* layer (nav components, SEO, search indexer) and a route may already be
|
|
189
|
+
* absolute when it reaches us.
|
|
190
|
+
*/
|
|
191
|
+
export function applyBasePath(path: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {
|
|
192
|
+
if (resolvedBasePath.length === 0) return path;
|
|
193
|
+
if (path === resolvedBasePath || path.startsWith(`${resolvedBasePath}/`)) return path;
|
|
194
|
+
return withBasePath(path, resolvedBasePath);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Is `pathname` inside the docs site?
|
|
199
|
+
*
|
|
200
|
+
* True under the configured prefix (when there is one) and for paths still
|
|
201
|
+
* authored with the default `/docs` prefix — those are what callers re-base.
|
|
202
|
+
* Anything else is a sibling route on the same origin (an app at `/app`, a
|
|
203
|
+
* status page) and has to be left exactly as the author wrote it.
|
|
204
|
+
*/
|
|
205
|
+
export function isDocsPath(
|
|
206
|
+
pathname: string,
|
|
207
|
+
resolvedBasePath: string = DEFAULT_BASE_PATH
|
|
208
|
+
): boolean {
|
|
209
|
+
if (
|
|
210
|
+
resolvedBasePath.length > 0 &&
|
|
211
|
+
(pathname === resolvedBasePath || pathname.startsWith(`${resolvedBasePath}/`))
|
|
212
|
+
) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return pathname === DEFAULT_BASE_PATH || pathname.startsWith(`${DEFAULT_BASE_PATH}/`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Point an author-written root-relative content path at the configured prefix.
|
|
220
|
+
*
|
|
221
|
+
* Authors reference content assets as they appear on disk
|
|
222
|
+
* (`/docs/assets/images/favicon.ico`, `/docs/assets/images/og.png`). When the
|
|
223
|
+
* site is served under a different prefix that leading segment must be
|
|
224
|
+
* rewritten, otherwise the asset 404s at the old path. Paths that already carry
|
|
225
|
+
* the configured prefix, or sit outside the default one, are left untouched.
|
|
226
|
+
*/
|
|
227
|
+
export function rebaseContentPath(
|
|
228
|
+
path: string,
|
|
229
|
+
resolvedBasePath: string = DEFAULT_BASE_PATH
|
|
230
|
+
): string {
|
|
231
|
+
// A path already carrying the configured prefix is final. At the deployment
|
|
232
|
+
// root ("") no path can carry it, so that guard must not swallow the
|
|
233
|
+
// default-prefix rewrite below.
|
|
234
|
+
if (
|
|
235
|
+
resolvedBasePath.length > 0 &&
|
|
236
|
+
(path === resolvedBasePath || path.startsWith(`${resolvedBasePath}/`))
|
|
237
|
+
) {
|
|
238
|
+
return path;
|
|
239
|
+
}
|
|
240
|
+
// The authored default prefix collapses to the deployment root ("") or is
|
|
241
|
+
// rewritten onto the configured prefix; content assets follow the copy in
|
|
242
|
+
// `<dist>/<prefix>/assets/`, which is the root at a root deployment.
|
|
243
|
+
if (path === DEFAULT_BASE_PATH) return resolvedBasePath || "/";
|
|
244
|
+
if (path.startsWith(`${DEFAULT_BASE_PATH}/`)) {
|
|
245
|
+
return `${resolvedBasePath}${path.slice(DEFAULT_BASE_PATH.length)}`;
|
|
246
|
+
}
|
|
247
|
+
return path;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Map an `/{basePath}` request back to its extensionless route (dev server).
|
|
251
|
+
* Handles both `/{basePath}/*.html` (pages) and `/{basePath}.html` (the
|
|
252
|
+
* directory-index edge case), with `basePath` taken from `meta.basePath`. */
|
|
253
|
+
export function stripDocsHtmlSuffix(
|
|
254
|
+
pathname: string,
|
|
255
|
+
resolvedBasePath: string = DEFAULT_BASE_PATH
|
|
256
|
+
): string {
|
|
257
|
+
if (resolvedBasePath.length === 0) {
|
|
258
|
+
// Root deployment: docs live at `/…`, so there is no prefix to match. Only
|
|
259
|
+
// the directory-index edge case can be mapped back safely.
|
|
260
|
+
return pathname === "/index.html" ? "/" : pathname;
|
|
261
|
+
}
|
|
262
|
+
if (pathname === `${resolvedBasePath}.html`) return resolvedBasePath;
|
|
263
|
+
if (pathname.startsWith(`${resolvedBasePath}/`) && pathname.endsWith(".html"))
|
|
264
|
+
return pathname.slice(0, -".html".length);
|
|
265
|
+
return pathname;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Strip the configured docs prefix from a request path, returning the slug
|
|
270
|
+
* segments. `null` means the request is not a docs route.
|
|
271
|
+
*
|
|
272
|
+
* Root deployments match every request (docs own `/`), so the caller must
|
|
273
|
+
* dispatch `/assets/…` and other static paths to `serveStatic` first — which
|
|
274
|
+
* both dev servers already do.
|
|
275
|
+
*/
|
|
276
|
+
export function matchDocsSlug(
|
|
277
|
+
pathname: string,
|
|
278
|
+
resolvedBasePath: string = DEFAULT_BASE_PATH
|
|
279
|
+
): string[] | null {
|
|
280
|
+
if (resolvedBasePath.length === 0) {
|
|
281
|
+
if (pathname !== "/" && pathname.endsWith("/")) return null;
|
|
282
|
+
return pathname === "/" ? [] : pathname.slice(1).split("/").filter(Boolean);
|
|
283
|
+
}
|
|
284
|
+
if (pathname === resolvedBasePath || pathname === `${resolvedBasePath}/`) return [];
|
|
285
|
+
if (pathname.startsWith(`${resolvedBasePath}/`))
|
|
286
|
+
return pathname
|
|
287
|
+
.slice(resolvedBasePath.length + 1)
|
|
288
|
+
.split("/")
|
|
289
|
+
.filter(Boolean);
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Relative climb from a built page to the dist root — the `../` count the HTML
|
|
295
|
+
* shell needs for bundle assets.
|
|
296
|
+
*
|
|
297
|
+
* A page lands at `<dist>/<basePath>/<slug>.html`, so it reaches the dist root
|
|
298
|
+
* through the slug's own directories plus every prefix segment. The host's
|
|
299
|
+
* deployment path is deliberately not counted: pages and assets share it (both
|
|
300
|
+
* resolve under `<deployPath>/`), so it cancels out of the relative math.
|
|
301
|
+
*/
|
|
302
|
+
export function docsDepth(slug: string, resolvedBasePath: string = DEFAULT_BASE_PATH): number {
|
|
303
|
+
const pageDirs = slug ? slug.split("/").filter(Boolean).length - 1 : 0;
|
|
304
|
+
const prefixSegments = resolvedBasePath.split("/").filter(Boolean).length;
|
|
305
|
+
return Math.max(0, pageDirs) + prefixSegments;
|
|
306
|
+
}
|