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
|
@@ -9,11 +9,16 @@ import {
|
|
|
9
9
|
loadOpenApiSpec,
|
|
10
10
|
} from "./validation";
|
|
11
11
|
import {
|
|
12
|
+
buildMdxPageFallbackSlug,
|
|
13
|
+
buildMdxPageSlug,
|
|
12
14
|
buildOpenApiEndpointSlug,
|
|
15
|
+
buildOpenApiEndpointFallbackSlug,
|
|
16
|
+
createRouteSlugRegistry,
|
|
13
17
|
deriveTitleFromEntryId,
|
|
14
18
|
parseOpenApiEndpoint,
|
|
15
19
|
slugify,
|
|
16
20
|
} from "./utils";
|
|
21
|
+
import { prependBasePath, withBasePath } from "./base-path";
|
|
17
22
|
import { getCollection } from "astro:content";
|
|
18
23
|
|
|
19
24
|
type MdxNavPageItem = string | NavPage;
|
|
@@ -21,6 +26,9 @@ type MdxNavPageItem = string | NavPage;
|
|
|
21
26
|
// Base route interface
|
|
22
27
|
export interface BaseRoute {
|
|
23
28
|
slug: string;
|
|
29
|
+
preferredSlug: string;
|
|
30
|
+
fallbackSlug?: string;
|
|
31
|
+
routeIdentity: string;
|
|
24
32
|
title: string;
|
|
25
33
|
hidden?: boolean;
|
|
26
34
|
}
|
|
@@ -56,7 +64,9 @@ export function resolveMdxPageTitle(args: {
|
|
|
56
64
|
const frontmatterTitle = normalizeTitle(args.entry?.data?.title);
|
|
57
65
|
const docsJsonTitle = normalizeTitle(args.docsTitle);
|
|
58
66
|
|
|
59
|
-
return
|
|
67
|
+
return (
|
|
68
|
+
frontmatterTitle || docsJsonTitle || deriveTitleFromEntryId(args.filePath)
|
|
69
|
+
);
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
function processPageItem(
|
|
@@ -65,9 +75,10 @@ function processPageItem(
|
|
|
65
75
|
docs: any[],
|
|
66
76
|
): MdxRoute {
|
|
67
77
|
const filePath = typeof item === "string" ? item : item.page;
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
const preferredSlug = buildMdxPageSlug({
|
|
79
|
+
filePath,
|
|
80
|
+
groupSlug: parentSlug,
|
|
81
|
+
});
|
|
71
82
|
|
|
72
83
|
// Find the entry matching this filePath
|
|
73
84
|
const entry = docs.find((doc: any) => {
|
|
@@ -91,16 +102,19 @@ function processPageItem(
|
|
|
91
102
|
|
|
92
103
|
return {
|
|
93
104
|
type: "mdx",
|
|
94
|
-
slug:
|
|
105
|
+
slug: preferredSlug,
|
|
106
|
+
preferredSlug,
|
|
107
|
+
fallbackSlug: buildMdxPageFallbackSlug({
|
|
108
|
+
filePath,
|
|
109
|
+
groupSlug: parentSlug,
|
|
110
|
+
}),
|
|
111
|
+
routeIdentity: `mdx:${filePath}`,
|
|
95
112
|
filePath: filePath,
|
|
96
113
|
title: title,
|
|
97
114
|
};
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
function processHiddenPageRoute(
|
|
101
|
-
route: HiddenPageRoute,
|
|
102
|
-
docs: any[],
|
|
103
|
-
): MdxRoute {
|
|
117
|
+
function processHiddenPageRoute(route: HiddenPageRoute, docs: any[]): MdxRoute {
|
|
104
118
|
const entry = docs.find((doc: any) => {
|
|
105
119
|
const docPath = doc.id.replace(/\.mdx$/, "");
|
|
106
120
|
return docPath === route.filePath;
|
|
@@ -117,6 +131,12 @@ function processHiddenPageRoute(
|
|
|
117
131
|
return {
|
|
118
132
|
type: "mdx",
|
|
119
133
|
slug,
|
|
134
|
+
preferredSlug: slug,
|
|
135
|
+
fallbackSlug: buildMdxPageFallbackSlug({
|
|
136
|
+
filePath: route.filePath,
|
|
137
|
+
groupSlug: "",
|
|
138
|
+
}),
|
|
139
|
+
routeIdentity: `mdx:${route.filePath}`,
|
|
120
140
|
filePath: route.filePath,
|
|
121
141
|
title: resolveMdxPageTitle({
|
|
122
142
|
entry,
|
|
@@ -195,6 +215,14 @@ async function processOpenApiPageItem(
|
|
|
195
215
|
return {
|
|
196
216
|
type: "openapi",
|
|
197
217
|
slug: fullSlug,
|
|
218
|
+
preferredSlug: fullSlug,
|
|
219
|
+
fallbackSlug: buildOpenApiEndpointFallbackSlug({
|
|
220
|
+
source: item.openapi.source,
|
|
221
|
+
path: matchedOperation.path,
|
|
222
|
+
method: matchedOperation.method,
|
|
223
|
+
groupSlug: parentSlug,
|
|
224
|
+
}),
|
|
225
|
+
routeIdentity: `openapi:${item.openapi.source}:${matchedOperation.method.toUpperCase()} ${matchedOperation.path.toLowerCase()}`,
|
|
198
226
|
filePath: item.openapi.source,
|
|
199
227
|
title,
|
|
200
228
|
openApiPath: matchedOperation.path,
|
|
@@ -328,6 +356,14 @@ async function processOpenApiFile(
|
|
|
328
356
|
routes.push({
|
|
329
357
|
type: "openapi",
|
|
330
358
|
slug: fullSlug,
|
|
359
|
+
preferredSlug: fullSlug,
|
|
360
|
+
fallbackSlug: buildOpenApiEndpointFallbackSlug({
|
|
361
|
+
source: openApiPath,
|
|
362
|
+
path: pathStr,
|
|
363
|
+
method,
|
|
364
|
+
groupSlug: parentSlug,
|
|
365
|
+
}),
|
|
366
|
+
routeIdentity: `openapi:${openApiPath}:${method.toUpperCase()} ${pathStr.toLowerCase()}`,
|
|
331
367
|
filePath: openApiPath,
|
|
332
368
|
title: title,
|
|
333
369
|
openApiPath: pathStr,
|
|
@@ -379,29 +415,28 @@ async function processMenuItem(
|
|
|
379
415
|
return routes;
|
|
380
416
|
}
|
|
381
417
|
|
|
382
|
-
function
|
|
383
|
-
const
|
|
418
|
+
function resolveRouteSlugs(routes: Route[]): Route[] {
|
|
419
|
+
const registry = createRouteSlugRegistry();
|
|
420
|
+
const resolvedRoutes: Route[] = [];
|
|
384
421
|
|
|
385
422
|
for (const route of routes) {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
423
|
+
const registered = registry.register({
|
|
424
|
+
preferredSlug: route.preferredSlug,
|
|
425
|
+
fallbackSlug: route.fallbackSlug,
|
|
426
|
+
identity: route.routeIdentity,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (registered.duplicate) {
|
|
389
430
|
continue;
|
|
390
431
|
}
|
|
391
432
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const candidateLabel =
|
|
397
|
-
route.type === "mdx"
|
|
398
|
-
? `mdx:${route.filePath}`
|
|
399
|
-
: `openapi:${route.filePath}:${route.openApiMethod.toUpperCase()} ${route.openApiPath}`;
|
|
400
|
-
|
|
401
|
-
throw new Error(
|
|
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.`,
|
|
403
|
-
);
|
|
433
|
+
resolvedRoutes.push({
|
|
434
|
+
...route,
|
|
435
|
+
slug: registered.slug,
|
|
436
|
+
});
|
|
404
437
|
}
|
|
438
|
+
|
|
439
|
+
return resolvedRoutes;
|
|
405
440
|
}
|
|
406
441
|
|
|
407
442
|
export async function getAllRoutes(): Promise<Route[]> {
|
|
@@ -422,7 +457,9 @@ export async function getAllRoutes(): Promise<Route[]> {
|
|
|
422
457
|
if (typeof item === "string" || "page" in item) {
|
|
423
458
|
allRoutes.push(processPageItem(item, "", docs));
|
|
424
459
|
} else if ("group" in item) {
|
|
425
|
-
allRoutes = allRoutes.concat(
|
|
460
|
+
allRoutes = allRoutes.concat(
|
|
461
|
+
await processGroup(item as NavGroup, "", docs),
|
|
462
|
+
);
|
|
426
463
|
} else if ("openapi" in item) {
|
|
427
464
|
allRoutes.push(await processOpenApiPageItem(item, ""));
|
|
428
465
|
}
|
|
@@ -434,6 +471,9 @@ export async function getAllRoutes(): Promise<Route[]> {
|
|
|
434
471
|
const menuItemRoutes = await processMenuItem(menuItem, "", docs);
|
|
435
472
|
allRoutes = allRoutes.concat(menuItemRoutes);
|
|
436
473
|
}
|
|
474
|
+
} else if (navigation.openapi) {
|
|
475
|
+
const openApiRoutes = await processOpenApiFile(navigation.openapi, "");
|
|
476
|
+
allRoutes = allRoutes.concat(openApiRoutes);
|
|
437
477
|
}
|
|
438
478
|
|
|
439
479
|
for (const hiddenPageRoute of config.hiddenPageRoutes ?? []) {
|
|
@@ -453,6 +493,87 @@ export async function getAllRoutes(): Promise<Route[]> {
|
|
|
453
493
|
allRoutes.push(hiddenRoute);
|
|
454
494
|
}
|
|
455
495
|
|
|
456
|
-
|
|
457
|
-
|
|
496
|
+
return resolveRouteSlugs(allRoutes);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export async function getMdxRouteHref(args: {
|
|
500
|
+
filePath: string;
|
|
501
|
+
groupSlug?: string;
|
|
502
|
+
homePath?: string;
|
|
503
|
+
preferredSlug?: string;
|
|
504
|
+
}): Promise<string> {
|
|
505
|
+
if (!args.preferredSlug && args.homePath && args.filePath === args.homePath) {
|
|
506
|
+
return prependBasePath("/");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const preferredSlug =
|
|
510
|
+
args.preferredSlug?.replace(/^\/+/, "").replace(/\/+$/, "") ||
|
|
511
|
+
buildMdxPageSlug({
|
|
512
|
+
filePath: args.filePath,
|
|
513
|
+
groupSlug: args.groupSlug,
|
|
514
|
+
});
|
|
515
|
+
const routes = await getAllRoutes();
|
|
516
|
+
const route = routes.find(
|
|
517
|
+
(candidate) =>
|
|
518
|
+
candidate.type === "mdx" &&
|
|
519
|
+
candidate.filePath === args.filePath &&
|
|
520
|
+
candidate.preferredSlug === preferredSlug,
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
return prependBasePath(`/${route?.slug ?? preferredSlug}`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export async function resolveConfiguredHref(
|
|
527
|
+
href: string,
|
|
528
|
+
hiddenPageRoutes: HiddenPageRoute[] | undefined,
|
|
529
|
+
): Promise<string> {
|
|
530
|
+
const normalizedHref = href.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
531
|
+
const hiddenPageRoute = hiddenPageRoutes?.find(
|
|
532
|
+
(route) => route.href.replace(/^\/+/, "").replace(/\/+$/, "") === normalizedHref,
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
if (!hiddenPageRoute) {
|
|
536
|
+
return withBasePath(href);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return getMdxRouteHref({
|
|
540
|
+
filePath: hiddenPageRoute.filePath,
|
|
541
|
+
preferredSlug: hiddenPageRoute.href,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export async function getOpenApiEndpointRouteHref(args: {
|
|
546
|
+
source: string;
|
|
547
|
+
path: string;
|
|
548
|
+
method: string;
|
|
549
|
+
groupSlug?: string;
|
|
550
|
+
}): Promise<string> {
|
|
551
|
+
const endpointSlug = buildOpenApiEndpointSlug(args.path, args.method);
|
|
552
|
+
const normalizedGroupSlug = (args.groupSlug || "")
|
|
553
|
+
.replace(/^\/+/, "")
|
|
554
|
+
.replace(/\/+$/, "");
|
|
555
|
+
const preferredSlug = normalizedGroupSlug
|
|
556
|
+
? `${normalizedGroupSlug}/${endpointSlug}`
|
|
557
|
+
: endpointSlug;
|
|
558
|
+
const config = await getConfig();
|
|
559
|
+
const routes = await getAllRoutes();
|
|
560
|
+
const visibleRoutes = routes.filter((candidate) => !candidate.hidden);
|
|
561
|
+
const route = routes.find(
|
|
562
|
+
(candidate) =>
|
|
563
|
+
candidate.type === "openapi" &&
|
|
564
|
+
candidate.filePath === args.source &&
|
|
565
|
+
candidate.openApiMethod.toLowerCase() === args.method.toLowerCase() &&
|
|
566
|
+
candidate.openApiPath.toLowerCase() === args.path.toLowerCase() &&
|
|
567
|
+
candidate.preferredSlug === preferredSlug,
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
if (
|
|
571
|
+
route &&
|
|
572
|
+
!config.home &&
|
|
573
|
+
visibleRoutes[0] === route
|
|
574
|
+
) {
|
|
575
|
+
return prependBasePath("/");
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return prependBasePath(`/${route?.slug ?? preferredSlug}`);
|
|
458
579
|
}
|
|
@@ -61,6 +61,38 @@ export function deriveTitleFromEntryId(filePath: string): string {
|
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function normalizeSlugPath(value: string): string {
|
|
65
|
+
return value.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function buildMdxPageSlug(args: {
|
|
69
|
+
filePath: string;
|
|
70
|
+
groupSlug?: string;
|
|
71
|
+
}): string {
|
|
72
|
+
const filename = path.basename(args.filePath);
|
|
73
|
+
const pageSlug = slugify(filename);
|
|
74
|
+
const normalizedGroupSlug = normalizeSlugPath(args.groupSlug || "");
|
|
75
|
+
|
|
76
|
+
return normalizedGroupSlug ? `${normalizedGroupSlug}/${pageSlug}` : pageSlug;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function buildMdxPageFallbackSlug(args: {
|
|
80
|
+
filePath: string;
|
|
81
|
+
groupSlug?: string;
|
|
82
|
+
}): string {
|
|
83
|
+
const pathSlug = args.filePath
|
|
84
|
+
.split(/[\\/]+/)
|
|
85
|
+
.map((segment) => slugify(segment.replace(/\.(md|mdx)$/i, "")))
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.join("-");
|
|
88
|
+
const normalizedGroupSlug = normalizeSlugPath(args.groupSlug || "");
|
|
89
|
+
const fallbackSlug = pathSlug || buildMdxPageSlug(args);
|
|
90
|
+
|
|
91
|
+
return normalizedGroupSlug
|
|
92
|
+
? `${normalizedGroupSlug}/${fallbackSlug}`
|
|
93
|
+
: fallbackSlug;
|
|
94
|
+
}
|
|
95
|
+
|
|
64
96
|
export function buildMdxPageHref(args: {
|
|
65
97
|
filePath: string;
|
|
66
98
|
groupSlug?: string;
|
|
@@ -70,15 +102,7 @@ export function buildMdxPageHref(args: {
|
|
|
70
102
|
return prependBasePath("/");
|
|
71
103
|
}
|
|
72
104
|
|
|
73
|
-
const
|
|
74
|
-
const pageSlug = slugify(filename);
|
|
75
|
-
const normalizedGroupSlug = (args.groupSlug || "")
|
|
76
|
-
.replace(/^\/+/, "")
|
|
77
|
-
.replace(/\/+$/, "");
|
|
78
|
-
|
|
79
|
-
const href = normalizedGroupSlug
|
|
80
|
-
? `/${normalizedGroupSlug}/${pageSlug}`
|
|
81
|
-
: `/${pageSlug}`;
|
|
105
|
+
const href = `/${buildMdxPageSlug(args)}`;
|
|
82
106
|
|
|
83
107
|
return prependBasePath(href);
|
|
84
108
|
}
|
|
@@ -119,15 +143,35 @@ export function buildOpenApiEndpointSlug(
|
|
|
119
143
|
return pathSlug ? `${pathSlug}-${methodSlug}` : methodSlug;
|
|
120
144
|
}
|
|
121
145
|
|
|
146
|
+
export function buildOpenApiEndpointFallbackSlug(args: {
|
|
147
|
+
source: string;
|
|
148
|
+
path: string;
|
|
149
|
+
method: string;
|
|
150
|
+
groupSlug?: string;
|
|
151
|
+
}): string {
|
|
152
|
+
const sourceSlug =
|
|
153
|
+
args.source
|
|
154
|
+
.replace(/\.(json|ya?ml)$/i, "")
|
|
155
|
+
.split(/[\\/]+/)
|
|
156
|
+
.map((segment) => slugify(segment))
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.join("-") || "openapi";
|
|
159
|
+
const endpointSlug = buildOpenApiEndpointSlug(args.path, args.method);
|
|
160
|
+
const normalizedGroupSlug = normalizeSlugPath(args.groupSlug || "");
|
|
161
|
+
const fallbackSlug = `${sourceSlug}-${endpointSlug}`;
|
|
162
|
+
|
|
163
|
+
return normalizedGroupSlug
|
|
164
|
+
? `${normalizedGroupSlug}/${fallbackSlug}`
|
|
165
|
+
: fallbackSlug;
|
|
166
|
+
}
|
|
167
|
+
|
|
122
168
|
export function buildOpenApiEndpointHref(args: {
|
|
123
169
|
path: string;
|
|
124
170
|
method: string;
|
|
125
171
|
groupSlug?: string;
|
|
126
172
|
}): string {
|
|
127
173
|
const endpointSlug = buildOpenApiEndpointSlug(args.path, args.method);
|
|
128
|
-
const normalizedGroupSlug = (args.groupSlug || "")
|
|
129
|
-
.replace(/^\/+/, "")
|
|
130
|
-
.replace(/\/+$/, "");
|
|
174
|
+
const normalizedGroupSlug = normalizeSlugPath(args.groupSlug || "");
|
|
131
175
|
|
|
132
176
|
const href = normalizedGroupSlug
|
|
133
177
|
? `/${normalizedGroupSlug}/${endpointSlug}`
|
|
@@ -135,3 +179,74 @@ export function buildOpenApiEndpointHref(args: {
|
|
|
135
179
|
|
|
136
180
|
return prependBasePath(href);
|
|
137
181
|
}
|
|
182
|
+
|
|
183
|
+
export type RouteSlugRegistration = {
|
|
184
|
+
preferredSlug: string;
|
|
185
|
+
fallbackSlug?: string;
|
|
186
|
+
identity: string;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export function createRouteSlugRegistry() {
|
|
190
|
+
const slugIdentityBySlug = new Map<string, string>();
|
|
191
|
+
const slugByExactRoute = new Map<string, string>();
|
|
192
|
+
|
|
193
|
+
function normalizeCandidate(value: string | undefined): string {
|
|
194
|
+
const normalized = normalizeSlugPath(value || "");
|
|
195
|
+
return normalized || "page";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function nextAvailableSlug(baseSlug: string): string {
|
|
199
|
+
let index = 2;
|
|
200
|
+
let candidate = baseSlug;
|
|
201
|
+
|
|
202
|
+
while (slugIdentityBySlug.has(candidate)) {
|
|
203
|
+
candidate = `${baseSlug}-${index}`;
|
|
204
|
+
index += 1;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return candidate;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
register({ preferredSlug, fallbackSlug, identity }: RouteSlugRegistration) {
|
|
212
|
+
const preferred = normalizeCandidate(preferredSlug);
|
|
213
|
+
const exactKey = `${preferred}\0${identity}`;
|
|
214
|
+
const existingExactSlug = slugByExactRoute.get(exactKey);
|
|
215
|
+
|
|
216
|
+
if (existingExactSlug) {
|
|
217
|
+
return {
|
|
218
|
+
slug: existingExactSlug,
|
|
219
|
+
duplicate: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const preferredIdentity = slugIdentityBySlug.get(preferred);
|
|
224
|
+
if (!preferredIdentity) {
|
|
225
|
+
slugIdentityBySlug.set(preferred, identity);
|
|
226
|
+
slugByExactRoute.set(exactKey, preferred);
|
|
227
|
+
return {
|
|
228
|
+
slug: preferred,
|
|
229
|
+
duplicate: false,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (preferredIdentity === identity) {
|
|
234
|
+
slugByExactRoute.set(exactKey, preferred);
|
|
235
|
+
return {
|
|
236
|
+
slug: preferred,
|
|
237
|
+
duplicate: true,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const fallbackBase = normalizeCandidate(fallbackSlug || preferred);
|
|
242
|
+
const resolvedSlug = nextAvailableSlug(fallbackBase);
|
|
243
|
+
slugIdentityBySlug.set(resolvedSlug, identity);
|
|
244
|
+
slugByExactRoute.set(exactKey, resolvedSlug);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
slug: resolvedSlug,
|
|
248
|
+
duplicate: false,
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|