radiant-docs 0.1.54 → 0.1.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +3 -75
- package/package.json +2 -4
- package/template/astro.config.mjs +16 -26
- package/template/package-lock.json +18 -0
- package/template/package.json +1 -1
- package/template/scripts/generate-og-metadata.mjs +8 -4
- package/template/src/components/Footer.astro +13 -4
- package/template/src/components/Header.astro +26 -6
- package/template/src/components/SidebarLink.astro +3 -2
- package/template/src/components/SidebarSubgroup.astro +14 -13
- package/template/src/components/sidebar/SidebarEndpointLink.astro +13 -3
- package/template/src/components/sidebar/SidebarOpenApi.astro +2 -0
- package/template/src/components/sidebar/SidebarOpenApiPageLink.astro +1 -0
- package/template/src/components/user/Accordion.astro +0 -13
- package/template/src/components/user/Callout.astro +0 -29
- package/template/src/components/user/Card.astro +31 -204
- package/template/src/components/user/CardGradient.astro +8 -1
- package/template/src/components/user/Column.astro +0 -17
- package/template/src/components/user/Columns.astro +4 -153
- package/template/src/components/user/Image.astro +0 -28
- package/template/src/components/user/Step.astro +0 -10
- package/template/src/components/user/Tab.astro +0 -12
- package/template/src/components/user/Tabs.astro +2 -9
- package/template/src/content.config.ts +1 -1
- package/template/src/lib/code/code-block.ts +1 -1
- package/template/src/lib/mdx/remark-code-block-component.ts +1 -20
- package/template/src/lib/mdx/remark-resolve-internal-links.ts +150 -204
- package/template/src/lib/routes.ts +150 -29
- package/template/src/lib/utils.ts +127 -12
- package/template/src/lib/validation.ts +5 -2826
- package/template/src/pages/[...slug].astro +16 -0
- package/template/src/lib/code/shiki-theme-config.ts +0 -16
- package/template/src/lib/component-error.ts +0 -202
- package/template/src/lib/frontmatter-schema.ts +0 -10
|
@@ -74,7 +74,6 @@ type NodeWithChildren = {
|
|
|
74
74
|
|
|
75
75
|
const COMPONENT_PREVIEW_NAME = "ComponentPreview";
|
|
76
76
|
const COMPONENT_PREVIEW_BLOCK_NAME = "ComponentPreviewBlock";
|
|
77
|
-
const COMPONENT_PREVIEW_LANGUAGES = new Set(["jsx", "tsx", "mdx"]);
|
|
78
77
|
const INTERNAL_CODE_BLOCK_NAME = "CodeBlockInternal";
|
|
79
78
|
|
|
80
79
|
const FILE_EXTENSION_BY_LANGUAGE: Record<string, string> = {
|
|
@@ -412,25 +411,7 @@ function transformCodeBlockNodes(tree: Root): void {
|
|
|
412
411
|
}
|
|
413
412
|
|
|
414
413
|
if (isInsideComponentPreview) {
|
|
415
|
-
|
|
416
|
-
throw new Error(
|
|
417
|
-
`[USER_ERROR]: <${COMPONENT_PREVIEW_NAME}>: Fenced code blocks must use jsx, tsx, or mdx language (received "${language}")`,
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
let previewChildren: unknown[] = [];
|
|
422
|
-
try {
|
|
423
|
-
previewChildren = parseComponentPreviewChildren(rawCode);
|
|
424
|
-
} catch (error) {
|
|
425
|
-
const reason =
|
|
426
|
-
error instanceof Error && error.message.trim().length > 0
|
|
427
|
-
? ` -> ${error.message}`
|
|
428
|
-
: "";
|
|
429
|
-
throw new Error(
|
|
430
|
-
`[USER_ERROR]: <${COMPONENT_PREVIEW_NAME}>: Failed to parse fenced code block as MDX content${reason}`,
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
|
|
414
|
+
const previewChildren = parseComponentPreviewChildren(rawCode);
|
|
434
415
|
const previewAttributes = attributes.filter(
|
|
435
416
|
(attribute) => attribute.name !== "inCodeGroup",
|
|
436
417
|
);
|
|
@@ -6,6 +6,7 @@ import { visitParents } from "unist-util-visit-parents";
|
|
|
6
6
|
import {
|
|
7
7
|
getConfig,
|
|
8
8
|
loadOpenApiSpec,
|
|
9
|
+
resolveDocsHref,
|
|
9
10
|
type DocsConfig,
|
|
10
11
|
type HiddenPageRoute,
|
|
11
12
|
type NavGroup,
|
|
@@ -16,8 +17,11 @@ import {
|
|
|
16
17
|
} from "../validation";
|
|
17
18
|
import { prependBasePath, withBasePath } from "../base-path";
|
|
18
19
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
buildMdxPageFallbackSlug,
|
|
21
|
+
buildMdxPageSlug,
|
|
22
|
+
buildOpenApiEndpointSlug,
|
|
23
|
+
buildOpenApiEndpointFallbackSlug,
|
|
24
|
+
createRouteSlugRegistry,
|
|
21
25
|
parseOpenApiEndpoint,
|
|
22
26
|
slugify,
|
|
23
27
|
} from "../utils";
|
|
@@ -29,11 +33,7 @@ type ResolvedRouteIndex = {
|
|
|
29
33
|
allHrefsByFilePath: Map<string, string[]>;
|
|
30
34
|
validRoutePaths: Set<string>;
|
|
31
35
|
allDocsFilePaths: Set<string>;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
type LinkSplit = {
|
|
35
|
-
base: string;
|
|
36
|
-
suffix: string;
|
|
36
|
+
routeSlugs: ReturnType<typeof createRouteSlugRegistry>;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
type VFileLike = {
|
|
@@ -48,11 +48,16 @@ type MdxJsxAttributeNode = {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
type MdxJsxElementNode = {
|
|
51
|
+
name?: string | null;
|
|
51
52
|
attributes?: unknown[];
|
|
52
53
|
};
|
|
53
54
|
|
|
55
|
+
type EstreeNode = {
|
|
56
|
+
type?: string;
|
|
57
|
+
[key: string]: unknown;
|
|
58
|
+
};
|
|
59
|
+
|
|
54
60
|
const DOCS_ROOT = path.resolve(process.cwd(), "src/content/docs");
|
|
55
|
-
const EXTERNAL_PROTOCOL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
|
|
56
61
|
const HTTP_METHODS = [
|
|
57
62
|
"get",
|
|
58
63
|
"post",
|
|
@@ -92,44 +97,6 @@ function normalizeRoutePath(value: string): string {
|
|
|
92
97
|
return normalized || "/";
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
function splitHref(url: string): LinkSplit {
|
|
96
|
-
const match = url.match(/^([^?#]*)(.*)$/);
|
|
97
|
-
return {
|
|
98
|
-
base: match?.[1] ?? url,
|
|
99
|
-
suffix: match?.[2] ?? "",
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function isLikelyExternalOrNonPageHref(baseHref: string): boolean {
|
|
104
|
-
if (!baseHref) return true;
|
|
105
|
-
if (baseHref.startsWith("#")) return true;
|
|
106
|
-
if (baseHref.startsWith("?")) return true;
|
|
107
|
-
if (baseHref.startsWith("//")) return true;
|
|
108
|
-
return EXTERNAL_PROTOCOL_REGEX.test(baseHref);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function normalizeForFileResolution(baseHref: string): string {
|
|
112
|
-
let normalized = baseHref.replace(/\\/g, "/").trim();
|
|
113
|
-
if (!normalized) return "";
|
|
114
|
-
|
|
115
|
-
const docsPathMarker = "/src/content/docs/";
|
|
116
|
-
const markerIndex = normalized.indexOf(docsPathMarker);
|
|
117
|
-
if (markerIndex >= 0) {
|
|
118
|
-
normalized = `/${normalized.slice(markerIndex + docsPathMarker.length)}`;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
normalized = normalized.replace(/^\/?src\/content\/docs\//, "/");
|
|
122
|
-
normalized = normalized.replace(/\.(md|mdx)$/i, "");
|
|
123
|
-
normalized = normalized.replace(/\/+$/, "");
|
|
124
|
-
normalized = normalized.replace(/\/{2,}/g, "/");
|
|
125
|
-
|
|
126
|
-
return normalized;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function isExplicitRelativeTarget(value: string): boolean {
|
|
130
|
-
return value.startsWith("./") || value.startsWith("../");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
100
|
function getCurrentDocFilePath(filePath: string | undefined): string | null {
|
|
134
101
|
if (!filePath) return null;
|
|
135
102
|
|
|
@@ -181,63 +148,6 @@ function collectDocsFilePaths(directory: string): string[] {
|
|
|
181
148
|
return results;
|
|
182
149
|
}
|
|
183
150
|
|
|
184
|
-
function buildFilePathCandidates(args: {
|
|
185
|
-
baseHref: string;
|
|
186
|
-
currentDocFilePath: string | null;
|
|
187
|
-
}): string[] {
|
|
188
|
-
const rawBase = normalizeForFileResolution(args.baseHref);
|
|
189
|
-
if (!rawBase) return [];
|
|
190
|
-
|
|
191
|
-
const candidates = new Set<string>();
|
|
192
|
-
|
|
193
|
-
if (rawBase.startsWith("/")) {
|
|
194
|
-
const absoluteCandidate = normalizeDocsFilePath(rawBase);
|
|
195
|
-
if (absoluteCandidate) {
|
|
196
|
-
candidates.add(absoluteCandidate);
|
|
197
|
-
}
|
|
198
|
-
return Array.from(candidates);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const currentDocDir = args.currentDocFilePath
|
|
202
|
-
? path.posix.dirname(args.currentDocFilePath)
|
|
203
|
-
: "";
|
|
204
|
-
|
|
205
|
-
const relativeCandidate = normalizeDocsFilePath(
|
|
206
|
-
path.posix.resolve("/", currentDocDir, rawBase).slice(1),
|
|
207
|
-
);
|
|
208
|
-
if (relativeCandidate) {
|
|
209
|
-
candidates.add(relativeCandidate);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (!isExplicitRelativeTarget(rawBase)) {
|
|
213
|
-
const rootCandidate = normalizeDocsFilePath(rawBase);
|
|
214
|
-
if (rootCandidate) {
|
|
215
|
-
candidates.add(rootCandidate);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return Array.from(candidates);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function shouldEnforceInternalRouteValidation(baseHref: string): boolean {
|
|
223
|
-
const normalized = normalizeForFileResolution(baseHref);
|
|
224
|
-
if (!normalized) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const extension = path.posix.extname(normalized).toLowerCase();
|
|
229
|
-
if (!extension) {
|
|
230
|
-
return true;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return (
|
|
234
|
-
extension === ".md" ||
|
|
235
|
-
extension === ".mdx" ||
|
|
236
|
-
extension === ".html" ||
|
|
237
|
-
extension === ".htm"
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
151
|
function addValidRoutePath(index: ResolvedRouteIndex, href: string): string {
|
|
242
152
|
const normalized = normalizeRoutePath(href);
|
|
243
153
|
index.validRoutePaths.add(normalized);
|
|
@@ -253,12 +163,21 @@ function addMdxRoute(args: {
|
|
|
253
163
|
const normalizedFilePath = normalizeDocsFilePath(args.filePath);
|
|
254
164
|
if (!normalizedFilePath) return;
|
|
255
165
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
166
|
+
const preferredSlug = buildMdxPageSlug({
|
|
167
|
+
filePath: normalizedFilePath,
|
|
168
|
+
groupSlug: args.groupSlug,
|
|
169
|
+
});
|
|
170
|
+
const registeredSlug = args.index.routeSlugs.register({
|
|
171
|
+
preferredSlug,
|
|
172
|
+
fallbackSlug: buildMdxPageFallbackSlug({
|
|
259
173
|
filePath: normalizedFilePath,
|
|
260
174
|
groupSlug: args.groupSlug,
|
|
261
175
|
}),
|
|
176
|
+
identity: `mdx:${normalizedFilePath}`,
|
|
177
|
+
}).slug;
|
|
178
|
+
const navHref = addValidRoutePath(
|
|
179
|
+
args.index,
|
|
180
|
+
prependBasePath(`/${registeredSlug}`),
|
|
262
181
|
);
|
|
263
182
|
|
|
264
183
|
if (!args.index.canonicalHrefByFilePath.has(normalizedFilePath)) {
|
|
@@ -284,16 +203,30 @@ function addMdxRoute(args: {
|
|
|
284
203
|
function addOpenApiEndpointRoute(args: {
|
|
285
204
|
index: ResolvedRouteIndex;
|
|
286
205
|
parentSlug: string;
|
|
206
|
+
source: string;
|
|
287
207
|
method: string;
|
|
288
208
|
endpointPath: string;
|
|
289
209
|
}): void {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
210
|
+
const endpointSlug = buildOpenApiEndpointSlug(args.endpointPath, args.method);
|
|
211
|
+
const normalizedParentSlug = args.parentSlug
|
|
212
|
+
.replace(/^\/+/, "")
|
|
213
|
+
.replace(/\/+$/, "");
|
|
214
|
+
const preferredSlug = normalizedParentSlug
|
|
215
|
+
? `${normalizedParentSlug}/${endpointSlug}`
|
|
216
|
+
: endpointSlug;
|
|
217
|
+
const registeredSlug = args.index.routeSlugs.register({
|
|
218
|
+
preferredSlug,
|
|
219
|
+
fallbackSlug: buildOpenApiEndpointFallbackSlug({
|
|
220
|
+
source: args.source,
|
|
293
221
|
path: args.endpointPath,
|
|
294
222
|
method: args.method,
|
|
295
223
|
groupSlug: args.parentSlug,
|
|
296
224
|
}),
|
|
225
|
+
identity: `openapi:${args.source}:${args.method.toUpperCase()} ${args.endpointPath.toLowerCase()}`,
|
|
226
|
+
}).slug;
|
|
227
|
+
addValidRoutePath(
|
|
228
|
+
args.index,
|
|
229
|
+
prependBasePath(`/${registeredSlug}`),
|
|
297
230
|
);
|
|
298
231
|
}
|
|
299
232
|
|
|
@@ -384,6 +317,7 @@ async function processOpenApiFile(args: {
|
|
|
384
317
|
addOpenApiEndpointRoute({
|
|
385
318
|
index: args.index,
|
|
386
319
|
parentSlug: args.parentSlug,
|
|
320
|
+
source: openApiSource,
|
|
387
321
|
method,
|
|
388
322
|
endpointPath: pathStr,
|
|
389
323
|
});
|
|
@@ -402,6 +336,7 @@ function processOpenApiPage(args: {
|
|
|
402
336
|
addOpenApiEndpointRoute({
|
|
403
337
|
index: args.index,
|
|
404
338
|
parentSlug: args.parentSlug,
|
|
339
|
+
source: args.item.openapi.source,
|
|
405
340
|
method: parsed.method,
|
|
406
341
|
endpointPath: parsed.path,
|
|
407
342
|
});
|
|
@@ -497,6 +432,7 @@ async function buildRouteIndex(
|
|
|
497
432
|
allHrefsByFilePath: new Map<string, string[]>(),
|
|
498
433
|
validRoutePaths: new Set<string>(),
|
|
499
434
|
allDocsFilePaths: new Set<string>(),
|
|
435
|
+
routeSlugs: createRouteSlugRegistry(),
|
|
500
436
|
};
|
|
501
437
|
|
|
502
438
|
for (const docFilePath of collectDocsFilePaths(DOCS_ROOT)) {
|
|
@@ -526,32 +462,22 @@ async function buildRouteIndex(
|
|
|
526
462
|
const rootOpenApi = (config.navigation as any).openapi;
|
|
527
463
|
if (
|
|
528
464
|
typeof rootOpenApi === "string" ||
|
|
529
|
-
(rootOpenApi &&
|
|
465
|
+
(rootOpenApi &&
|
|
466
|
+
typeof rootOpenApi === "object" &&
|
|
467
|
+
!Array.isArray(rootOpenApi))
|
|
530
468
|
) {
|
|
531
469
|
await processOpenApiFile({
|
|
532
470
|
index,
|
|
533
471
|
parentSlug: "",
|
|
534
472
|
openApiPathOrConfig: rootOpenApi,
|
|
535
473
|
});
|
|
536
|
-
} else if (Array.isArray(rootOpenApi)) {
|
|
537
|
-
for (const openApiItem of rootOpenApi) {
|
|
538
|
-
if (
|
|
539
|
-
typeof openApiItem !== "string" &&
|
|
540
|
-
(!openApiItem || typeof openApiItem !== "object")
|
|
541
|
-
) {
|
|
542
|
-
continue;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
await processOpenApiFile({
|
|
546
|
-
index,
|
|
547
|
-
parentSlug: "",
|
|
548
|
-
openApiPathOrConfig: openApiItem as string | NavOpenApi,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
474
|
}
|
|
552
475
|
|
|
553
476
|
if (homePath) {
|
|
554
|
-
|
|
477
|
+
addHiddenPageRoute(index, {
|
|
478
|
+
filePath: homePath,
|
|
479
|
+
href: "/",
|
|
480
|
+
});
|
|
555
481
|
}
|
|
556
482
|
|
|
557
483
|
for (const hiddenPageRoute of config.hiddenPageRoutes ?? []) {
|
|
@@ -572,38 +498,18 @@ async function getRouteIndex(): Promise<ResolvedRouteIndex> {
|
|
|
572
498
|
return cachedRouteIndex;
|
|
573
499
|
}
|
|
574
500
|
|
|
575
|
-
function
|
|
576
|
-
|
|
501
|
+
function buildInternalLinkRewriteError(args: {
|
|
502
|
+
rawHref: string;
|
|
577
503
|
currentDocFilePath: string | null;
|
|
578
|
-
|
|
579
|
-
existingFilePathCandidates: string[];
|
|
504
|
+
detail: string;
|
|
580
505
|
}): Error {
|
|
581
506
|
const sourceFile = args.currentDocFilePath
|
|
582
507
|
? `${args.currentDocFilePath}.mdx`
|
|
583
508
|
: "the current MDX file";
|
|
584
509
|
|
|
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
|
-
|
|
597
|
-
const candidateHint =
|
|
598
|
-
args.filePathCandidates.length > 0
|
|
599
|
-
? ` Tried file path candidates: ${args.filePathCandidates
|
|
600
|
-
.map((candidate) => `"${candidate}"`)
|
|
601
|
-
.join(", ")}.`
|
|
602
|
-
: "";
|
|
603
|
-
|
|
604
510
|
return new Error(
|
|
605
|
-
`[
|
|
606
|
-
|
|
511
|
+
`[RADIANT_INTERNAL_ERROR]: Docs link rewrite failed for "${args.rawHref}" in ${sourceFile}. ` +
|
|
512
|
+
`${args.detail} This should have been caught by radiant-docs-validator before build.`,
|
|
607
513
|
);
|
|
608
514
|
}
|
|
609
515
|
|
|
@@ -613,71 +519,52 @@ function resolveLinkToCanonicalHref(args: {
|
|
|
613
519
|
routeIndex: ResolvedRouteIndex;
|
|
614
520
|
warn: (message: string) => void;
|
|
615
521
|
}): string | null {
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
if (isLikelyExternalOrNonPageHref(baseHref)) {
|
|
522
|
+
const resolvedHref = resolveDocsHref(args.rawHref);
|
|
523
|
+
if (resolvedHref.kind === "ignored" || resolvedHref.kind === "local-asset") {
|
|
619
524
|
return null;
|
|
620
525
|
}
|
|
621
526
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
|
|
639
|
-
if (args.routeIndex.validRoutePaths.has(normalizedBaseRoute)) {
|
|
640
|
-
const publicRoute = normalizeRoutePath(withBasePath(normalizedBaseRoute));
|
|
641
|
-
if (baseHref.startsWith("/") && normalizedBaseRoute === publicRoute) {
|
|
642
|
-
return null;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return `${publicRoute}${parts.suffix}`;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const filePathCandidates = buildFilePathCandidates({
|
|
649
|
-
baseHref,
|
|
650
|
-
currentDocFilePath: args.currentDocFilePath,
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
for (const filePath of filePathCandidates) {
|
|
654
|
-
const canonicalHref = args.routeIndex.canonicalHrefByFilePath.get(filePath);
|
|
655
|
-
if (!canonicalHref) continue;
|
|
527
|
+
if (resolvedHref.kind === "invalid") {
|
|
528
|
+
const suggestion =
|
|
529
|
+
"suggestedHref" in resolvedHref && resolvedHref.suggestedHref
|
|
530
|
+
? ` Suggested href: "${resolvedHref.suggestedHref}".`
|
|
531
|
+
: "";
|
|
656
532
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
args.
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
return `${withBasePath(canonicalHref)}${parts.suffix}`;
|
|
533
|
+
throw buildInternalLinkRewriteError({
|
|
534
|
+
rawHref: args.rawHref,
|
|
535
|
+
currentDocFilePath: args.currentDocFilePath,
|
|
536
|
+
detail:
|
|
537
|
+
`The href was classified as invalid during rewrite (${resolvedHref.reason}).` +
|
|
538
|
+
suggestion,
|
|
539
|
+
});
|
|
665
540
|
}
|
|
666
541
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
542
|
+
const canonicalHref = args.routeIndex.canonicalHrefByFilePath.get(
|
|
543
|
+
resolvedHref.filePath,
|
|
544
|
+
);
|
|
545
|
+
if (!canonicalHref) {
|
|
546
|
+
const targetFileExists = args.routeIndex.allDocsFilePaths.has(
|
|
547
|
+
resolvedHref.filePath,
|
|
670
548
|
);
|
|
671
549
|
|
|
672
|
-
throw
|
|
673
|
-
|
|
550
|
+
throw buildInternalLinkRewriteError({
|
|
551
|
+
rawHref: args.rawHref,
|
|
674
552
|
currentDocFilePath: args.currentDocFilePath,
|
|
675
|
-
|
|
676
|
-
|
|
553
|
+
detail: targetFileExists
|
|
554
|
+
? `The target file "${resolvedHref.filePath}.mdx" exists but did not resolve to a canonical route.`
|
|
555
|
+
: `The target file "${resolvedHref.filePath}.mdx" was not found in the docs route index.`,
|
|
677
556
|
});
|
|
678
557
|
}
|
|
679
558
|
|
|
680
|
-
|
|
559
|
+
const aliases =
|
|
560
|
+
args.routeIndex.allHrefsByFilePath.get(resolvedHref.filePath) ?? [];
|
|
561
|
+
if (aliases.length > 1) {
|
|
562
|
+
args.warn(
|
|
563
|
+
`[INTERNAL_LINK_WARNING] "${resolvedHref.pathname}" matched "${resolvedHref.filePath}", which has multiple URLs (${aliases.join(", ")}). Rewriting to canonical URL "${canonicalHref}".`,
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return `${withBasePath(canonicalHref)}${resolvedHref.suffix}`;
|
|
681
568
|
}
|
|
682
569
|
|
|
683
570
|
export const remarkResolveInternalLinks: Plugin<[], Root> = () => {
|
|
@@ -685,6 +572,13 @@ export const remarkResolveInternalLinks: Plugin<[], Root> = () => {
|
|
|
685
572
|
const routeIndex = await getRouteIndex();
|
|
686
573
|
const vfile = file as VFileLike;
|
|
687
574
|
const currentDocFilePath = getCurrentDocFilePath(vfile.path);
|
|
575
|
+
if (
|
|
576
|
+
currentDocFilePath &&
|
|
577
|
+
!routeIndex.canonicalHrefByFilePath.has(currentDocFilePath)
|
|
578
|
+
) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
688
582
|
const emittedWarnings = new Set<string>();
|
|
689
583
|
|
|
690
584
|
const warnOnce = (message: string, place: unknown): void => {
|
|
@@ -734,6 +628,58 @@ export const remarkResolveInternalLinks: Plugin<[], Root> = () => {
|
|
|
734
628
|
hrefAttribute.value = rewrittenHref;
|
|
735
629
|
}
|
|
736
630
|
}
|
|
631
|
+
|
|
632
|
+
if (element.name !== "Card") return;
|
|
633
|
+
|
|
634
|
+
const buttonAttribute = element.attributes.find((attribute) => {
|
|
635
|
+
const candidate = attribute as MdxJsxAttributeNode;
|
|
636
|
+
return candidate.type === "mdxJsxAttribute" && candidate.name === "button";
|
|
637
|
+
}) as MdxJsxAttributeNode | undefined;
|
|
638
|
+
const program = (
|
|
639
|
+
buttonAttribute?.value as { data?: { estree?: EstreeNode } } | undefined
|
|
640
|
+
)?.data?.estree;
|
|
641
|
+
const body = program?.body;
|
|
642
|
+
if (!Array.isArray(body) || body.length !== 1) return;
|
|
643
|
+
|
|
644
|
+
const statement = body[0] as EstreeNode;
|
|
645
|
+
const expression = statement.expression as EstreeNode | undefined;
|
|
646
|
+
if (
|
|
647
|
+
statement.type !== "ExpressionStatement" ||
|
|
648
|
+
expression?.type !== "ObjectExpression" ||
|
|
649
|
+
!Array.isArray(expression.properties)
|
|
650
|
+
) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const hrefProperty = expression.properties.find((propertyValue) => {
|
|
655
|
+
const property = propertyValue as EstreeNode;
|
|
656
|
+
const key = property.key as EstreeNode | undefined;
|
|
657
|
+
return (
|
|
658
|
+
property.type === "Property" &&
|
|
659
|
+
property.computed !== true &&
|
|
660
|
+
(key?.type === "Identifier" || key?.type === "Literal") &&
|
|
661
|
+
(key.name === "href" || key.value === "href")
|
|
662
|
+
);
|
|
663
|
+
}) as EstreeNode | undefined;
|
|
664
|
+
const hrefValue = hrefProperty?.value as EstreeNode | undefined;
|
|
665
|
+
if (
|
|
666
|
+
hrefValue?.type !== "Literal" ||
|
|
667
|
+
typeof hrefValue.value !== "string"
|
|
668
|
+
) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const rewrittenHref = resolveLinkToCanonicalHref({
|
|
673
|
+
rawHref: hrefValue.value,
|
|
674
|
+
currentDocFilePath,
|
|
675
|
+
routeIndex,
|
|
676
|
+
warn: (message) => warnOnce(message, node),
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (rewrittenHref && rewrittenHref !== hrefValue.value) {
|
|
680
|
+
hrefValue.value = rewrittenHref;
|
|
681
|
+
hrefValue.raw = JSON.stringify(rewrittenHref);
|
|
682
|
+
}
|
|
737
683
|
};
|
|
738
684
|
|
|
739
685
|
visitParents(tree, "mdxJsxFlowElement", (node) => {
|