radiant-docs 0.1.56 → 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.
Files changed (33) hide show
  1. package/dist/index.js +3 -76
  2. package/package.json +2 -4
  3. package/template/astro.config.mjs +16 -26
  4. package/template/package-lock.json +18 -0
  5. package/template/package.json +1 -1
  6. package/template/src/components/Footer.astro +13 -4
  7. package/template/src/components/Header.astro +26 -6
  8. package/template/src/components/SidebarLink.astro +3 -2
  9. package/template/src/components/SidebarSubgroup.astro +14 -13
  10. package/template/src/components/sidebar/SidebarEndpointLink.astro +13 -3
  11. package/template/src/components/sidebar/SidebarOpenApi.astro +2 -0
  12. package/template/src/components/sidebar/SidebarOpenApiPageLink.astro +1 -0
  13. package/template/src/components/user/Accordion.astro +0 -13
  14. package/template/src/components/user/Callout.astro +0 -29
  15. package/template/src/components/user/Card.astro +31 -204
  16. package/template/src/components/user/CardGradient.astro +8 -1
  17. package/template/src/components/user/Column.astro +0 -17
  18. package/template/src/components/user/Columns.astro +4 -153
  19. package/template/src/components/user/Image.astro +0 -28
  20. package/template/src/components/user/Step.astro +0 -10
  21. package/template/src/components/user/Tab.astro +0 -12
  22. package/template/src/components/user/Tabs.astro +2 -9
  23. package/template/src/content.config.ts +1 -1
  24. package/template/src/lib/code/code-block.ts +1 -1
  25. package/template/src/lib/mdx/remark-code-block-component.ts +1 -20
  26. package/template/src/lib/mdx/remark-resolve-internal-links.ts +150 -204
  27. package/template/src/lib/routes.ts +150 -29
  28. package/template/src/lib/utils.ts +127 -12
  29. package/template/src/lib/validation.ts +5 -2826
  30. package/template/src/pages/[...slug].astro +16 -0
  31. package/template/src/lib/code/shiki-theme-config.ts +0 -16
  32. package/template/src/lib/component-error.ts +0 -202
  33. 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
- if (!COMPONENT_PREVIEW_LANGUAGES.has(normalizedLanguage)) {
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
- buildMdxPageHref,
20
- buildOpenApiEndpointHref,
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 navHref = addValidRoutePath(
257
- args.index,
258
- buildMdxPageHref({
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
- addValidRoutePath(
291
- args.index,
292
- buildOpenApiEndpointHref({
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 && typeof rootOpenApi === "object")
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
- addValidRoutePath(index, prependBasePath("/"));
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 buildInvalidInternalLinkError(args: {
576
- baseHref: string;
501
+ function buildInternalLinkRewriteError(args: {
502
+ rawHref: string;
577
503
  currentDocFilePath: string | null;
578
- filePathCandidates: string[];
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
- `[USER_ERROR]: Invalid internal link "${args.baseHref}" in ${sourceFile}. ` +
606
- `No matching docs page file was found. Create the target .mdx file or update the link.${candidateHint}`,
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 parts = splitHref(args.rawHref);
617
- const baseHref = parts.base.trim();
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
- 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
-
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
- const aliases = args.routeIndex.allHrefsByFilePath.get(filePath) ?? [];
658
- if (aliases.length > 1) {
659
- args.warn(
660
- `[INTERNAL_LINK_WARNING] "${baseHref}" matched "${filePath}", which has multiple URLs (${aliases.join(", ")}). Rewriting to canonical URL "${canonicalHref}".`,
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
- if (shouldEnforceInternalRouteValidation(baseHref)) {
668
- const existingFilePathCandidates = filePathCandidates.filter((filePath) =>
669
- args.routeIndex.allDocsFilePaths.has(filePath),
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 buildInvalidInternalLinkError({
673
- baseHref,
550
+ throw buildInternalLinkRewriteError({
551
+ rawHref: args.rawHref,
674
552
  currentDocFilePath: args.currentDocFilePath,
675
- filePathCandidates,
676
- existingFilePathCandidates,
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
- return null;
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) => {