radiant-docs 0.1.39 → 0.1.40
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/package.json +1 -1
- package/template/astro.config.mjs +38 -7
- package/template/package-lock.json +19 -7
- package/template/package.json +1 -1
- package/template/scripts/generate-robots-txt.mjs +29 -1
- package/template/scripts/stamp-image-versions.mjs +59 -33
- package/template/src/components/Footer.astro +2 -1
- package/template/src/components/Header.astro +8 -6
- package/template/src/components/LogoLink.astro +2 -1
- package/template/src/components/MdxPage.astro +15 -4
- package/template/src/components/PagePagination.astro +61 -0
- package/template/src/components/SidebarDropdown.astro +12 -8
- package/template/src/components/SidebarGroup.astro +1 -1
- package/template/src/components/SidebarMenu.astro +1 -1
- package/template/src/components/SidebarSegmented.astro +6 -5
- package/template/src/components/TableOfContents.astro +4 -13
- package/template/src/components/chat/AskAiWidget.tsx +274 -39
- package/template/src/components/endpoint/PlaygroundForm.astro +2 -1
- package/template/src/components/user/CodeBlock.astro +1 -1
- package/template/src/components/user/CodeGroup.astro +16 -1
- package/template/src/components/user/ComponentPreviewBlock.astro +1 -0
- package/template/src/components/user/Image.astro +43 -53
- package/template/src/layouts/Layout.astro +217 -7
- package/template/src/lib/base-path.ts +98 -0
- package/template/src/lib/component-error.ts +49 -10
- package/template/src/lib/mdx/remark-resolve-internal-links.ts +128 -18
- package/template/src/lib/pagefind.ts +62 -14
- package/template/src/lib/routes.ts +49 -1
- package/template/src/lib/static-asset-url.ts +3 -1
- package/template/src/lib/utils.ts +12 -4
- package/template/src/lib/validation.ts +376 -36
- package/template/src/pages/404.astro +2 -1
- package/template/src/pages/[...slug].astro +68 -6
- package/template/src/styles/global.css +62 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import type { Link, Root } from "mdast";
|
|
3
4
|
import type { Plugin } from "unified";
|
|
@@ -6,15 +7,17 @@ import {
|
|
|
6
7
|
getConfig,
|
|
7
8
|
loadOpenApiSpec,
|
|
8
9
|
type DocsConfig,
|
|
10
|
+
type HiddenPageRoute,
|
|
9
11
|
type NavGroup,
|
|
10
12
|
type NavMenuItem,
|
|
11
13
|
type NavOpenApi,
|
|
12
14
|
type NavOpenApiPage,
|
|
13
15
|
type NavPage,
|
|
14
16
|
} from "../validation";
|
|
17
|
+
import { prependBasePath, withBasePath } from "../base-path";
|
|
15
18
|
import {
|
|
16
19
|
buildMdxPageHref,
|
|
17
|
-
|
|
20
|
+
buildOpenApiEndpointHref,
|
|
18
21
|
parseOpenApiEndpoint,
|
|
19
22
|
slugify,
|
|
20
23
|
} from "../utils";
|
|
@@ -25,6 +28,7 @@ type ResolvedRouteIndex = {
|
|
|
25
28
|
canonicalHrefByFilePath: Map<string, string>;
|
|
26
29
|
allHrefsByFilePath: Map<string, string[]>;
|
|
27
30
|
validRoutePaths: Set<string>;
|
|
31
|
+
allDocsFilePaths: Set<string>;
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
type LinkSplit = {
|
|
@@ -145,6 +149,38 @@ function getCurrentDocFilePath(filePath: string | undefined): string | null {
|
|
|
145
149
|
return normalizeDocsFilePath(relativePath);
|
|
146
150
|
}
|
|
147
151
|
|
|
152
|
+
function collectDocsFilePaths(directory: string): string[] {
|
|
153
|
+
let entries: fs.Dirent[];
|
|
154
|
+
try {
|
|
155
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
156
|
+
} catch {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const results: string[] = [];
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
const entryPath = path.join(directory, entry.name);
|
|
163
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (entry.isDirectory()) {
|
|
168
|
+
results.push(...collectDocsFilePaths(entryPath));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (entry.isFile() && /\.(md|mdx)$/i.test(entry.name)) {
|
|
173
|
+
const relativePath = path.relative(DOCS_ROOT, entryPath);
|
|
174
|
+
const normalizedFilePath = normalizeDocsFilePath(relativePath);
|
|
175
|
+
if (normalizedFilePath) {
|
|
176
|
+
results.push(normalizedFilePath);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
183
|
+
|
|
148
184
|
function buildFilePathCandidates(args: {
|
|
149
185
|
baseHref: string;
|
|
150
186
|
currentDocFilePath: string | null;
|
|
@@ -235,11 +271,11 @@ function addMdxRoute(args: {
|
|
|
235
271
|
}
|
|
236
272
|
|
|
237
273
|
if (args.homePath && normalizedFilePath === args.homePath) {
|
|
238
|
-
addValidRoutePath(args.index, "/");
|
|
239
|
-
if (!aliases.includes(
|
|
240
|
-
aliases.unshift(
|
|
274
|
+
const homeHref = addValidRoutePath(args.index, prependBasePath("/"));
|
|
275
|
+
if (!aliases.includes(homeHref)) {
|
|
276
|
+
aliases.unshift(homeHref);
|
|
241
277
|
}
|
|
242
|
-
args.index.canonicalHrefByFilePath.set(normalizedFilePath,
|
|
278
|
+
args.index.canonicalHrefByFilePath.set(normalizedFilePath, homeHref);
|
|
243
279
|
}
|
|
244
280
|
|
|
245
281
|
args.index.allHrefsByFilePath.set(normalizedFilePath, aliases);
|
|
@@ -251,11 +287,33 @@ function addOpenApiEndpointRoute(args: {
|
|
|
251
287
|
method: string;
|
|
252
288
|
endpointPath: string;
|
|
253
289
|
}): void {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
290
|
+
addValidRoutePath(
|
|
291
|
+
args.index,
|
|
292
|
+
buildOpenApiEndpointHref({
|
|
293
|
+
path: args.endpointPath,
|
|
294
|
+
method: args.method,
|
|
295
|
+
groupSlug: args.parentSlug,
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function addHiddenPageRoute(
|
|
301
|
+
index: ResolvedRouteIndex,
|
|
302
|
+
route: HiddenPageRoute,
|
|
303
|
+
): void {
|
|
304
|
+
const normalizedFilePath = normalizeDocsFilePath(route.filePath);
|
|
305
|
+
if (!normalizedFilePath) return;
|
|
306
|
+
|
|
307
|
+
const href = addValidRoutePath(index, prependBasePath(route.href));
|
|
308
|
+
if (!index.canonicalHrefByFilePath.has(normalizedFilePath)) {
|
|
309
|
+
index.canonicalHrefByFilePath.set(normalizedFilePath, href);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const aliases = index.allHrefsByFilePath.get(normalizedFilePath) ?? [];
|
|
313
|
+
if (!aliases.includes(href)) {
|
|
314
|
+
aliases.push(href);
|
|
315
|
+
}
|
|
316
|
+
index.allHrefsByFilePath.set(normalizedFilePath, aliases);
|
|
259
317
|
}
|
|
260
318
|
|
|
261
319
|
function shouldIncludeEndpoint(
|
|
@@ -431,13 +489,20 @@ async function processMenuItems(args: {
|
|
|
431
489
|
}
|
|
432
490
|
}
|
|
433
491
|
|
|
434
|
-
async function buildRouteIndex(
|
|
492
|
+
async function buildRouteIndex(
|
|
493
|
+
config: DocsConfig,
|
|
494
|
+
): Promise<ResolvedRouteIndex> {
|
|
435
495
|
const index: ResolvedRouteIndex = {
|
|
436
496
|
canonicalHrefByFilePath: new Map<string, string>(),
|
|
437
497
|
allHrefsByFilePath: new Map<string, string[]>(),
|
|
438
498
|
validRoutePaths: new Set<string>(),
|
|
499
|
+
allDocsFilePaths: new Set<string>(),
|
|
439
500
|
};
|
|
440
501
|
|
|
502
|
+
for (const docFilePath of collectDocsFilePaths(DOCS_ROOT)) {
|
|
503
|
+
index.allDocsFilePaths.add(docFilePath);
|
|
504
|
+
}
|
|
505
|
+
|
|
441
506
|
const homePath =
|
|
442
507
|
typeof config.home === "string" ? normalizeDocsFilePath(config.home) : null;
|
|
443
508
|
|
|
@@ -459,7 +524,10 @@ async function buildRouteIndex(config: DocsConfig): Promise<ResolvedRouteIndex>
|
|
|
459
524
|
}
|
|
460
525
|
|
|
461
526
|
const rootOpenApi = (config.navigation as any).openapi;
|
|
462
|
-
if (
|
|
527
|
+
if (
|
|
528
|
+
typeof rootOpenApi === "string" ||
|
|
529
|
+
(rootOpenApi && typeof rootOpenApi === "object")
|
|
530
|
+
) {
|
|
463
531
|
await processOpenApiFile({
|
|
464
532
|
index,
|
|
465
533
|
parentSlug: "",
|
|
@@ -467,7 +535,10 @@ async function buildRouteIndex(config: DocsConfig): Promise<ResolvedRouteIndex>
|
|
|
467
535
|
});
|
|
468
536
|
} else if (Array.isArray(rootOpenApi)) {
|
|
469
537
|
for (const openApiItem of rootOpenApi) {
|
|
470
|
-
if (
|
|
538
|
+
if (
|
|
539
|
+
typeof openApiItem !== "string" &&
|
|
540
|
+
(!openApiItem || typeof openApiItem !== "object")
|
|
541
|
+
) {
|
|
471
542
|
continue;
|
|
472
543
|
}
|
|
473
544
|
|
|
@@ -480,7 +551,11 @@ async function buildRouteIndex(config: DocsConfig): Promise<ResolvedRouteIndex>
|
|
|
480
551
|
}
|
|
481
552
|
|
|
482
553
|
if (homePath) {
|
|
483
|
-
addValidRoutePath(index, "/");
|
|
554
|
+
addValidRoutePath(index, prependBasePath("/"));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
for (const hiddenPageRoute of config.hiddenPageRoutes ?? []) {
|
|
558
|
+
addHiddenPageRoute(index, hiddenPageRoute);
|
|
484
559
|
}
|
|
485
560
|
|
|
486
561
|
return index;
|
|
@@ -501,11 +576,24 @@ function buildInvalidInternalLinkError(args: {
|
|
|
501
576
|
baseHref: string;
|
|
502
577
|
currentDocFilePath: string | null;
|
|
503
578
|
filePathCandidates: string[];
|
|
579
|
+
existingFilePathCandidates: string[];
|
|
504
580
|
}): Error {
|
|
505
581
|
const sourceFile = args.currentDocFilePath
|
|
506
582
|
? `${args.currentDocFilePath}.mdx`
|
|
507
583
|
: "the current MDX file";
|
|
508
584
|
|
|
585
|
+
if (args.existingFilePathCandidates.length > 0) {
|
|
586
|
+
const existingFiles = args.existingFilePathCandidates
|
|
587
|
+
.map((candidate) => `"${candidate}.mdx"`)
|
|
588
|
+
.join(", ");
|
|
589
|
+
|
|
590
|
+
return new Error(
|
|
591
|
+
`[USER_ERROR]: Invalid internal link "${args.baseHref}" in ${sourceFile}. ` +
|
|
592
|
+
`The target file exists (${existingFiles}), but it is not a routable docs page. ` +
|
|
593
|
+
`Add it to docs.json navigation, set it as the home page, or link to it from the navbar or footer so it is included in the built site.`,
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
509
597
|
const candidateHint =
|
|
510
598
|
args.filePathCandidates.length > 0
|
|
511
599
|
? ` Tried file path candidates: ${args.filePathCandidates
|
|
@@ -515,7 +603,7 @@ function buildInvalidInternalLinkError(args: {
|
|
|
515
603
|
|
|
516
604
|
return new Error(
|
|
517
605
|
`[USER_ERROR]: Invalid internal link "${args.baseHref}" in ${sourceFile}. ` +
|
|
518
|
-
`
|
|
606
|
+
`No matching docs page file was found. Create the target .mdx file or update the link.${candidateHint}`,
|
|
519
607
|
);
|
|
520
608
|
}
|
|
521
609
|
|
|
@@ -532,12 +620,29 @@ function resolveLinkToCanonicalHref(args: {
|
|
|
532
620
|
}
|
|
533
621
|
|
|
534
622
|
const normalizedBaseRoute = normalizeRoutePath(baseHref);
|
|
623
|
+
const normalizedBaseRouteWithBase = normalizeRoutePath(
|
|
624
|
+
baseHref.startsWith("/")
|
|
625
|
+
? withBasePath(normalizedBaseRoute)
|
|
626
|
+
: prependBasePath(normalizedBaseRoute),
|
|
627
|
+
);
|
|
628
|
+
if (args.routeIndex.validRoutePaths.has(normalizedBaseRouteWithBase)) {
|
|
629
|
+
if (
|
|
630
|
+
baseHref.startsWith("/") &&
|
|
631
|
+
normalizedBaseRoute === normalizedBaseRouteWithBase
|
|
632
|
+
) {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return `${normalizedBaseRouteWithBase}${parts.suffix}`;
|
|
637
|
+
}
|
|
638
|
+
|
|
535
639
|
if (args.routeIndex.validRoutePaths.has(normalizedBaseRoute)) {
|
|
536
|
-
|
|
640
|
+
const publicRoute = normalizeRoutePath(withBasePath(normalizedBaseRoute));
|
|
641
|
+
if (baseHref.startsWith("/") && normalizedBaseRoute === publicRoute) {
|
|
537
642
|
return null;
|
|
538
643
|
}
|
|
539
644
|
|
|
540
|
-
return `${
|
|
645
|
+
return `${publicRoute}${parts.suffix}`;
|
|
541
646
|
}
|
|
542
647
|
|
|
543
648
|
const filePathCandidates = buildFilePathCandidates({
|
|
@@ -556,14 +661,19 @@ function resolveLinkToCanonicalHref(args: {
|
|
|
556
661
|
);
|
|
557
662
|
}
|
|
558
663
|
|
|
559
|
-
return `${canonicalHref}${parts.suffix}`;
|
|
664
|
+
return `${withBasePath(canonicalHref)}${parts.suffix}`;
|
|
560
665
|
}
|
|
561
666
|
|
|
562
667
|
if (shouldEnforceInternalRouteValidation(baseHref)) {
|
|
668
|
+
const existingFilePathCandidates = filePathCandidates.filter((filePath) =>
|
|
669
|
+
args.routeIndex.allDocsFilePaths.has(filePath),
|
|
670
|
+
);
|
|
671
|
+
|
|
563
672
|
throw buildInvalidInternalLinkError({
|
|
564
673
|
baseHref,
|
|
565
674
|
currentDocFilePath: args.currentDocFilePath,
|
|
566
675
|
filePathCandidates,
|
|
676
|
+
existingFilePathCandidates,
|
|
567
677
|
});
|
|
568
678
|
}
|
|
569
679
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Pagefind TypeScript types and wrapper
|
|
2
|
+
import { getDocsBasePath, withBasePath } from "./base-path";
|
|
2
3
|
import { resolveStaticAssetUrl } from "./static-asset-url";
|
|
3
4
|
|
|
4
5
|
export interface PagefindSearchResult {
|
|
@@ -38,11 +39,11 @@ export interface PagefindSearchResponse {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface PagefindInstance {
|
|
41
|
-
options?: (options: { baseUrl?: string
|
|
42
|
+
options?: (options: { baseUrl?: string }) => Promise<void>;
|
|
42
43
|
init: () => Promise<void>;
|
|
43
44
|
search: (
|
|
44
45
|
query: string,
|
|
45
|
-
options?: { filters?: Record<string, string> }
|
|
46
|
+
options?: { filters?: Record<string, string> },
|
|
46
47
|
) => Promise<PagefindSearchResponse>;
|
|
47
48
|
filters: () => Promise<Record<string, Record<string, number>>>;
|
|
48
49
|
preload: (query: string) => Promise<void>;
|
|
@@ -54,6 +55,57 @@ function getPagefindScriptUrl(): string {
|
|
|
54
55
|
return resolveStaticAssetUrl("/pagefind/pagefind.js");
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
function getPagefindResultBaseUrl(): string {
|
|
59
|
+
if (typeof window === "undefined") return "/";
|
|
60
|
+
|
|
61
|
+
return `${window.location.origin}${getDocsBasePath()}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function withBasePathForSearchResultUrl(url: string): string {
|
|
65
|
+
const basePath = getDocsBasePath();
|
|
66
|
+
if (!basePath || typeof window === "undefined") {
|
|
67
|
+
return withBasePath(url);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const isAbsoluteUrl =
|
|
71
|
+
/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url) || url.startsWith("//");
|
|
72
|
+
if (!isAbsoluteUrl) {
|
|
73
|
+
return withBasePath(url);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const parsed = new URL(url, window.location.href);
|
|
78
|
+
if (
|
|
79
|
+
parsed.origin === window.location.origin &&
|
|
80
|
+
parsed.pathname !== basePath &&
|
|
81
|
+
!parsed.pathname.startsWith(`${basePath}/`)
|
|
82
|
+
) {
|
|
83
|
+
const pathname = parsed.pathname.startsWith("/")
|
|
84
|
+
? parsed.pathname
|
|
85
|
+
: `/${parsed.pathname}`;
|
|
86
|
+
parsed.pathname = `${basePath}${pathname}`;
|
|
87
|
+
return parsed.toString();
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Fall through to normal relative URL handling.
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return withBasePath(url);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function withBasePathForSearchResult(
|
|
97
|
+
data: PagefindResultData,
|
|
98
|
+
): PagefindResultData {
|
|
99
|
+
return {
|
|
100
|
+
...data,
|
|
101
|
+
url: withBasePathForSearchResultUrl(data.url),
|
|
102
|
+
sub_results: data.sub_results?.map((subResult) => ({
|
|
103
|
+
...subResult,
|
|
104
|
+
url: withBasePathForSearchResultUrl(subResult.url),
|
|
105
|
+
})),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
57
109
|
export async function getPagefind(): Promise<PagefindInstance | null> {
|
|
58
110
|
if (pagefindInstance) return pagefindInstance;
|
|
59
111
|
|
|
@@ -62,17 +114,15 @@ export async function getPagefind(): Promise<PagefindInstance | null> {
|
|
|
62
114
|
// This keeps Pagefind loading purely runtime and allows versioned query params.
|
|
63
115
|
const importPagefind = new Function(
|
|
64
116
|
"moduleUrl",
|
|
65
|
-
"return import(moduleUrl)"
|
|
117
|
+
"return import(moduleUrl)",
|
|
66
118
|
) as (moduleUrl: string) => Promise<PagefindInstance>;
|
|
67
119
|
|
|
68
120
|
const pagefind = await importPagefind(getPagefindScriptUrl());
|
|
69
|
-
// Pagefind uses the
|
|
70
|
-
//
|
|
71
|
-
//
|
|
121
|
+
// Pagefind uses the pagefind.js import URL to locate its index files.
|
|
122
|
+
// Only override result URLs; passing Pagefind's basePath would move
|
|
123
|
+
// pagefind-entry.json and chunk fetches back onto the docs origin.
|
|
72
124
|
if (typeof pagefind.options === "function") {
|
|
73
|
-
|
|
74
|
-
typeof window !== "undefined" ? window.location.origin : "/";
|
|
75
|
-
await pagefind.options({ baseUrl });
|
|
125
|
+
await pagefind.options({ baseUrl: getPagefindResultBaseUrl() });
|
|
76
126
|
}
|
|
77
127
|
await pagefind.init();
|
|
78
128
|
pagefindInstance = pagefind;
|
|
@@ -85,7 +135,7 @@ export async function getPagefind(): Promise<PagefindInstance | null> {
|
|
|
85
135
|
|
|
86
136
|
export async function search(
|
|
87
137
|
query: string,
|
|
88
|
-
limit: number = 8
|
|
138
|
+
limit: number = 8,
|
|
89
139
|
): Promise<PagefindResultData[]> {
|
|
90
140
|
const pagefind = await getPagefind();
|
|
91
141
|
if (!pagefind || !query.trim()) return [];
|
|
@@ -94,10 +144,8 @@ export async function search(
|
|
|
94
144
|
|
|
95
145
|
// Load full data for top results
|
|
96
146
|
const results = await Promise.all(
|
|
97
|
-
response.results.slice(0, limit).map((result) => result.data())
|
|
147
|
+
response.results.slice(0, limit).map((result) => result.data()),
|
|
98
148
|
);
|
|
99
149
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return results;
|
|
150
|
+
return results.map(withBasePathForSearchResult);
|
|
103
151
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type NavOpenApiPage,
|
|
6
6
|
type NavMenuItem,
|
|
7
7
|
type NavOpenApi,
|
|
8
|
+
type HiddenPageRoute,
|
|
8
9
|
loadOpenApiSpec,
|
|
9
10
|
} from "./validation";
|
|
10
11
|
import {
|
|
@@ -21,6 +22,7 @@ type MdxNavPageItem = string | NavPage;
|
|
|
21
22
|
export interface BaseRoute {
|
|
22
23
|
slug: string;
|
|
23
24
|
title: string;
|
|
25
|
+
hidden?: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// MDX route
|
|
@@ -95,6 +97,35 @@ function processPageItem(
|
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
function processHiddenPageRoute(
|
|
101
|
+
route: HiddenPageRoute,
|
|
102
|
+
docs: any[],
|
|
103
|
+
): MdxRoute {
|
|
104
|
+
const entry = docs.find((doc: any) => {
|
|
105
|
+
const docPath = doc.id.replace(/\.mdx$/, "");
|
|
106
|
+
return docPath === route.filePath;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!entry) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Could not find content collection entry for path: ${route.filePath}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const slug = route.href.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
type: "mdx",
|
|
119
|
+
slug,
|
|
120
|
+
filePath: route.filePath,
|
|
121
|
+
title: resolveMdxPageTitle({
|
|
122
|
+
entry,
|
|
123
|
+
filePath: route.filePath,
|
|
124
|
+
}),
|
|
125
|
+
hidden: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
type OpenApiOperationLookup = {
|
|
99
130
|
method: string;
|
|
100
131
|
path: string;
|
|
@@ -368,7 +399,7 @@ function assertUniqueRouteSlugs(routes: Route[]): void {
|
|
|
368
399
|
: `openapi:${route.filePath}:${route.openApiMethod.toUpperCase()} ${route.openApiPath}`;
|
|
369
400
|
|
|
370
401
|
throw new Error(
|
|
371
|
-
`Duplicate route slug "${route.slug}" generated by "${existingLabel}" and "${candidateLabel}".`,
|
|
402
|
+
`[USER_ERROR]: Invalid docs.json: Duplicate route slug "${route.slug}" generated by "${existingLabel}" and "${candidateLabel}". Remove one of the duplicate references or change navigation structure so each route resolves to a unique URL.`,
|
|
372
403
|
);
|
|
373
404
|
}
|
|
374
405
|
}
|
|
@@ -405,6 +436,23 @@ export async function getAllRoutes(): Promise<Route[]> {
|
|
|
405
436
|
}
|
|
406
437
|
}
|
|
407
438
|
|
|
439
|
+
for (const hiddenPageRoute of config.hiddenPageRoutes ?? []) {
|
|
440
|
+
const hiddenRoute = processHiddenPageRoute(hiddenPageRoute, docs);
|
|
441
|
+
const existingRoute = allRoutes.find(
|
|
442
|
+
(route) => route.slug === hiddenRoute.slug,
|
|
443
|
+
);
|
|
444
|
+
if (existingRoute) {
|
|
445
|
+
if (
|
|
446
|
+
existingRoute.type === "mdx" &&
|
|
447
|
+
existingRoute.filePath === hiddenRoute.filePath
|
|
448
|
+
) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
allRoutes.push(hiddenRoute);
|
|
454
|
+
}
|
|
455
|
+
|
|
408
456
|
assertUniqueRouteSlugs(allRoutes);
|
|
409
457
|
return allRoutes;
|
|
410
458
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withBasePath } from "./base-path";
|
|
2
|
+
|
|
1
3
|
type AssetsPrefixValue = string | Record<string, string> | undefined;
|
|
2
4
|
|
|
3
5
|
function normalizePrefix(value: string): string {
|
|
@@ -54,7 +56,7 @@ export function resolveStaticAssetUrl(rawPath: string): string {
|
|
|
54
56
|
|
|
55
57
|
const prefix = resolveAssetsPrefix(import.meta.env.ASSETS_PREFIX);
|
|
56
58
|
if (!prefix) {
|
|
57
|
-
return `${normalizedPathname}${parsed.search}${parsed.hash}`;
|
|
59
|
+
return `${withBasePath(normalizedPathname)}${parsed.search}${parsed.hash}`;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
const normalizedPrefixPath = `${prefix}/${normalizedPathname.replace(/^\/+/, "")}`;
|
|
@@ -5,6 +5,7 @@ import remarkRehype from "remark-rehype";
|
|
|
5
5
|
import rehypeStringify from "rehype-stringify";
|
|
6
6
|
import rehypeExternalLinks from "./mdx/rehype-external-links";
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import { prependBasePath } from "./base-path";
|
|
8
9
|
|
|
9
10
|
export function slugify(text: string): string {
|
|
10
11
|
if (typeof text !== "string") {
|
|
@@ -66,7 +67,7 @@ export function buildMdxPageHref(args: {
|
|
|
66
67
|
homePath?: string;
|
|
67
68
|
}): string {
|
|
68
69
|
if (args.homePath && args.filePath === args.homePath) {
|
|
69
|
-
return "/";
|
|
70
|
+
return prependBasePath("/");
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const filename = path.basename(args.filePath);
|
|
@@ -75,9 +76,11 @@ export function buildMdxPageHref(args: {
|
|
|
75
76
|
.replace(/^\/+/, "")
|
|
76
77
|
.replace(/\/+$/, "");
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
const href = normalizedGroupSlug
|
|
79
80
|
? `/${normalizedGroupSlug}/${pageSlug}`
|
|
80
81
|
: `/${pageSlug}`;
|
|
82
|
+
|
|
83
|
+
return prependBasePath(href);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
export function parseOpenApiEndpoint(
|
|
@@ -103,7 +106,10 @@ export function parseOpenApiEndpoint(
|
|
|
103
106
|
};
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
export function buildOpenApiEndpointSlug(
|
|
109
|
+
export function buildOpenApiEndpointSlug(
|
|
110
|
+
pathStr: string,
|
|
111
|
+
method: string,
|
|
112
|
+
): string {
|
|
107
113
|
const normalizedPath = pathStr.startsWith("/") ? pathStr : `/${pathStr}`;
|
|
108
114
|
const pathSlug = normalizedPath
|
|
109
115
|
.replace(/^\//, "")
|
|
@@ -123,7 +129,9 @@ export function buildOpenApiEndpointHref(args: {
|
|
|
123
129
|
.replace(/^\/+/, "")
|
|
124
130
|
.replace(/\/+$/, "");
|
|
125
131
|
|
|
126
|
-
|
|
132
|
+
const href = normalizedGroupSlug
|
|
127
133
|
? `/${normalizedGroupSlug}/${endpointSlug}`
|
|
128
134
|
: `/${endpointSlug}`;
|
|
135
|
+
|
|
136
|
+
return prependBasePath(href);
|
|
129
137
|
}
|