@astrojs/starlight 0.37.6 → 0.38.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/CHANGELOG.md +76 -0
- package/components/ContentNotice.astro +1 -1
- package/components/MobileTableOfContents.astro +2 -2
- package/components/Page.astro +3 -3
- package/components/Select.astro +2 -2
- package/components/SocialIcons.astro +1 -1
- package/components/TableOfContents.astro +2 -2
- package/global.d.ts +9 -3
- package/index.ts +39 -1
- package/integrations/asides.ts +1 -1
- package/integrations/remark-rehype.ts +2 -11
- package/integrations/virtual-user-config.ts +31 -17
- package/integrations/vite-layer-order.ts +38 -35
- package/package.json +12 -148
- package/schema.ts +3 -9
- package/schemas/badge.ts +9 -8
- package/schemas/components.ts +1 -1
- package/schemas/expressiveCode.ts +0 -3
- package/schemas/favicon.ts +4 -6
- package/schemas/head.ts +3 -2
- package/schemas/hero.ts +1 -1
- package/schemas/i18n.ts +148 -153
- package/schemas/icon.ts +1 -1
- package/schemas/pagefind.ts +5 -4
- package/schemas/prevNextLink.ts +6 -8
- package/schemas/sidebar.ts +33 -27
- package/schemas/site-title.ts +4 -6
- package/schemas/social.ts +3 -2
- package/schemas/tableOfContents.ts +3 -3
- package/style/props.css +7 -2
- package/types.ts +1 -1
- package/user-components/Aside.astro +1 -1
- package/user-components/Card.astro +1 -1
- package/user-components/Icon.astro +1 -1
- package/user-components/LinkButton.astro +1 -1
- package/user-components/TabItem.astro +1 -1
- package/user-components/rehype-file-tree.ts +1 -1
- package/user-components/rehype-steps.ts +10 -1
- package/user-components/rehype-tabs.ts +1 -1
- package/utils/createTranslationSystem.ts +1 -1
- package/utils/error-map.ts +98 -57
- package/utils/navigation.ts +11 -10
- package/utils/plugins.ts +75 -40
- package/utils/routing/data.ts +3 -9
- package/utils/routing/index.ts +16 -24
- package/utils/routing/types.ts +5 -17
- package/utils/slugs.ts +11 -12
- package/utils/starlight-page.ts +13 -23
- package/utils/translations.ts +3 -4
- package/utils/user-config.ts +28 -65
- package/virtual.d.ts +6 -3
- /package/{components → components-internals}/Icons.ts +0 -0
- /package/{components → components-internals}/SidebarPersistState.ts +0 -0
- /package/{components → components-internals}/TableOfContents/TableOfContentsList.astro +0 -0
- /package/{components → components-internals}/TableOfContents/starlight-toc.ts +0 -0
package/schemas/sidebar.ts
CHANGED
|
@@ -8,12 +8,13 @@ const SidebarBaseSchema = z.object({
|
|
|
8
8
|
/** The visible label for this item in the sidebar. */
|
|
9
9
|
label: z.string(),
|
|
10
10
|
/** Translations of the `label` for each supported language. */
|
|
11
|
-
translations: z.record(z.string()).default({}),
|
|
11
|
+
translations: z.record(z.string(), z.string()).default({}),
|
|
12
12
|
/** Adds a badge to the item */
|
|
13
13
|
badge: I18nBadgeConfigSchema(),
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
const SidebarGroupSchema =
|
|
16
|
+
const SidebarGroupSchema = z.object({
|
|
17
|
+
...SidebarBaseSchema.shape,
|
|
17
18
|
/**
|
|
18
19
|
* Explicitly prevent custom attributes on groups as the final type for supported sidebar item
|
|
19
20
|
* is a non-discriminated union where TypeScript will not perform excess property checks.
|
|
@@ -30,21 +31,27 @@ const SidebarGroupSchema = SidebarBaseSchema.extend({
|
|
|
30
31
|
// `Record<string, string | number | boolean | undefined>` but typed as `HTMLAttributes<'a'>`
|
|
31
32
|
// for user convenience.
|
|
32
33
|
const linkHTMLAttributesSchema = z.record(
|
|
34
|
+
z.string(),
|
|
33
35
|
z.union([z.string(), z.number(), z.boolean(), z.undefined(), z.null()])
|
|
34
|
-
) as z.
|
|
35
|
-
export type LinkHTMLAttributes =
|
|
36
|
+
) as z.ZodType<LinkHTMLAttributes, LinkHTMLAttributes>;
|
|
37
|
+
export type LinkHTMLAttributes = Omit<
|
|
38
|
+
HTMLAttributes<'a'>,
|
|
39
|
+
keyof AstroBuiltinAttributes | 'children'
|
|
40
|
+
>;
|
|
36
41
|
|
|
37
42
|
export const SidebarLinkItemHTMLAttributesSchema = () => linkHTMLAttributesSchema.default({});
|
|
38
43
|
|
|
39
|
-
const SidebarLinkItemSchema =
|
|
44
|
+
const SidebarLinkItemSchema = z.strictObject({
|
|
45
|
+
...SidebarBaseSchema.shape,
|
|
40
46
|
/** The link to this item’s content. Can be a relative link to local files or the full URL of an external page. */
|
|
41
47
|
link: z.string(),
|
|
42
48
|
/** HTML attributes to add to the link item. */
|
|
43
49
|
attrs: SidebarLinkItemHTMLAttributesSchema(),
|
|
44
|
-
})
|
|
50
|
+
});
|
|
45
51
|
export type SidebarLinkItem = z.infer<typeof SidebarLinkItemSchema>;
|
|
46
52
|
|
|
47
|
-
const AutoSidebarGroupSchema =
|
|
53
|
+
const AutoSidebarGroupSchema = z.strictObject({
|
|
54
|
+
...SidebarGroupSchema.shape,
|
|
48
55
|
/** Enable autogenerating a sidebar category from a specific docs directory. */
|
|
49
56
|
autogenerate: z.object({
|
|
50
57
|
/** The directory to generate sidebar items for. */
|
|
@@ -60,7 +67,7 @@ const AutoSidebarGroupSchema = SidebarGroupSchema.extend({
|
|
|
60
67
|
/** How many directories deep to include from this directory in the sidebar. Default: `Infinity`. */
|
|
61
68
|
// depth: z.number().optional(),
|
|
62
69
|
}),
|
|
63
|
-
})
|
|
70
|
+
});
|
|
64
71
|
export type AutoSidebarGroup = z.infer<typeof AutoSidebarGroupSchema>;
|
|
65
72
|
|
|
66
73
|
type ManualSidebarGroupInput = z.input<typeof SidebarGroupSchema> & {
|
|
@@ -85,26 +92,25 @@ type ManualSidebarGroupOutput = z.output<typeof SidebarGroupSchema> & {
|
|
|
85
92
|
>;
|
|
86
93
|
};
|
|
87
94
|
|
|
88
|
-
const ManualSidebarGroupSchema: z.ZodType<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
),
|
|
105
|
-
}).strict();
|
|
95
|
+
const ManualSidebarGroupSchema: z.ZodType<ManualSidebarGroupOutput, ManualSidebarGroupInput> =
|
|
96
|
+
z.strictObject({
|
|
97
|
+
...SidebarGroupSchema.shape,
|
|
98
|
+
/** Array of links and subcategories to display in this category. */
|
|
99
|
+
items: z.lazy(() =>
|
|
100
|
+
z
|
|
101
|
+
.union([
|
|
102
|
+
SidebarLinkItemSchema,
|
|
103
|
+
ManualSidebarGroupSchema,
|
|
104
|
+
AutoSidebarGroupSchema,
|
|
105
|
+
InternalSidebarLinkItemSchema,
|
|
106
|
+
InternalSidebarLinkItemShorthandSchema,
|
|
107
|
+
])
|
|
108
|
+
.array()
|
|
109
|
+
),
|
|
110
|
+
});
|
|
106
111
|
|
|
107
|
-
const InternalSidebarLinkItemSchema =
|
|
112
|
+
const InternalSidebarLinkItemSchema = z.object({
|
|
113
|
+
...SidebarBaseSchema.partial({ label: true }).shape,
|
|
108
114
|
/** The link to this item’s content. Must be a slug of a Content Collection entry. */
|
|
109
115
|
slug: z.string(),
|
|
110
116
|
/** HTML attributes to add to the link item. */
|
package/schemas/site-title.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { z } from 'astro/zod';
|
|
2
2
|
|
|
3
|
-
export const TitleConfigSchema = () =>
|
|
4
|
-
z
|
|
5
|
-
.union([z.string(), z.record(z.string())])
|
|
6
|
-
.describe('Title for your website. Will be used in metadata and as browser tab title.');
|
|
3
|
+
export const TitleConfigSchema = () => z.union([z.string(), z.record(z.string(), z.string())]);
|
|
7
4
|
|
|
8
5
|
// transform the title for runtime use
|
|
9
6
|
export const TitleTransformConfigSchema = (defaultLang: string) =>
|
|
@@ -12,9 +9,10 @@ export const TitleTransformConfigSchema = (defaultLang: string) =>
|
|
|
12
9
|
return { [defaultLang]: title };
|
|
13
10
|
}
|
|
14
11
|
if (!title[defaultLang] && title[defaultLang] !== '') {
|
|
15
|
-
ctx.
|
|
16
|
-
code:
|
|
12
|
+
ctx.issues.push({
|
|
13
|
+
code: 'custom',
|
|
17
14
|
message: `Title must have a key for the default language "${defaultLang}"`,
|
|
15
|
+
input: title,
|
|
18
16
|
});
|
|
19
17
|
return z.NEVER;
|
|
20
18
|
}
|
package/schemas/social.ts
CHANGED
|
@@ -11,11 +11,12 @@ export const SocialLinksSchema = () =>
|
|
|
11
11
|
// TODO: remove once most people have updated to v0.33 or higher (e.g. when releasing Starlight v1)
|
|
12
12
|
z.preprocess((value, ctx) => {
|
|
13
13
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
14
|
-
ctx.
|
|
15
|
-
code:
|
|
14
|
+
ctx.issues.push({
|
|
15
|
+
code: 'custom',
|
|
16
16
|
message:
|
|
17
17
|
'Starlight v0.33.0 changed the `social` configuration syntax. Please specify an array of link items instead of an object.\n' +
|
|
18
18
|
'See the Starlight changelog for details: https://github.com/withastro/starlight/blob/main/packages/starlight/CHANGELOG.md#0330\n',
|
|
19
|
+
input: value,
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
return value;
|
|
@@ -7,13 +7,13 @@ export const TableOfContentsSchema = () =>
|
|
|
7
7
|
.union([
|
|
8
8
|
z.object({
|
|
9
9
|
/** The level to start including headings at in the table of contents. Default: 2. */
|
|
10
|
-
minHeadingLevel: z.
|
|
10
|
+
minHeadingLevel: z.int().min(1).max(6).optional().default(2),
|
|
11
11
|
/** The level to stop including headings at in the table of contents. Default: 3. */
|
|
12
|
-
maxHeadingLevel: z.
|
|
12
|
+
maxHeadingLevel: z.int().min(1).max(6).optional().default(3),
|
|
13
13
|
}),
|
|
14
14
|
z.boolean().transform((enabled) => (enabled ? defaults : false)),
|
|
15
15
|
])
|
|
16
16
|
.default(defaults)
|
|
17
17
|
.refine((toc) => (toc ? toc.minHeadingLevel <= toc.maxHeadingLevel : true), {
|
|
18
|
-
|
|
18
|
+
error: 'minHeadingLevel must be less than or equal to maxHeadingLevel',
|
|
19
19
|
});
|
package/style/props.css
CHANGED
|
@@ -84,8 +84,13 @@
|
|
|
84
84
|
--sl-line-height: 1.75;
|
|
85
85
|
--sl-line-height-headings: 1.2;
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
/*
|
|
88
|
+
Although technically superseded by the `system-ui` font-family, we use `-apple-system` and
|
|
89
|
+
`BlinkMacSystemFont` because `system-ui` causes issues on Windows computers in some languages.
|
|
90
|
+
See: https://github.com/withastro/starlight/issues/3721
|
|
91
|
+
*/
|
|
92
|
+
--sl-font-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
|
93
|
+
'Noto Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
|
89
94
|
'Noto Color Emoji';
|
|
90
95
|
--sl-font-system-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
|
91
96
|
'Courier New', monospace;
|
package/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { AstroError } from 'astro/errors';
|
|
3
3
|
import Icon from './Icon.astro';
|
|
4
|
-
import { Icons, type StarlightIcon } from '../components/Icons';
|
|
4
|
+
import { Icons, type StarlightIcon } from '../components-internals/Icons';
|
|
5
5
|
import { throwInvalidAsideIconError } from '../integrations/asides-error';
|
|
6
6
|
|
|
7
7
|
const asideVariants = ['note', 'tip', 'caution', 'danger'] as const;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
-
import type { StarlightIcon } from '../components/Icons';
|
|
3
|
+
import type { StarlightIcon } from '../components-internals/Icons';
|
|
4
4
|
import Icon from './Icon.astro';
|
|
5
5
|
|
|
6
6
|
interface Props extends Omit<HTMLAttributes<'a'>, 'href'> {
|
|
@@ -6,7 +6,7 @@ import { fromHtml } from 'hast-util-from-html';
|
|
|
6
6
|
import { toString } from 'hast-util-to-string';
|
|
7
7
|
import { rehype } from 'rehype';
|
|
8
8
|
import { CONTINUE, SKIP, visit } from 'unist-util-visit';
|
|
9
|
-
import { Icons, type StarlightIcon } from '../components/Icons';
|
|
9
|
+
import { Icons, type StarlightIcon } from '../components-internals/Icons';
|
|
10
10
|
import { definitions } from './file-tree-icons';
|
|
11
11
|
|
|
12
12
|
declare module 'vfile' {
|
|
@@ -12,7 +12,16 @@ const stepsProcessor = rehype()
|
|
|
12
12
|
.data('settings', { fragment: true })
|
|
13
13
|
.use(function steps() {
|
|
14
14
|
return (tree: Root, vfile: VFile) => {
|
|
15
|
-
const rootElements = tree.children.filter(
|
|
15
|
+
const rootElements = tree.children.filter(
|
|
16
|
+
(item): item is Element =>
|
|
17
|
+
item.type === 'element' &&
|
|
18
|
+
// Since Astro 5.16.9, `<script>` elements from nested child elements can end up hoisted
|
|
19
|
+
// into the `<Steps>` slot due to our use of `Astro.slots.render()`. We can safely ignore
|
|
20
|
+
// these, so we filter them out here.
|
|
21
|
+
// TODO: we may be able to remove this in the future if the upstream issue is fixed.
|
|
22
|
+
// See: https://github.com/withastro/astro/issues/15627
|
|
23
|
+
item.tagName !== 'script'
|
|
24
|
+
);
|
|
16
25
|
const [rootElement] = rootElements;
|
|
17
26
|
|
|
18
27
|
if (!rootElement) {
|
|
@@ -2,7 +2,7 @@ import type { Element } from 'hast';
|
|
|
2
2
|
import { select } from 'hast-util-select';
|
|
3
3
|
import { rehype } from 'rehype';
|
|
4
4
|
import { CONTINUE, SKIP, visit } from 'unist-util-visit';
|
|
5
|
-
import type { StarlightIcon } from '../components/Icons';
|
|
5
|
+
import type { StarlightIcon } from '../components-internals/Icons';
|
|
6
6
|
|
|
7
7
|
interface Panel {
|
|
8
8
|
panelId: string;
|
|
@@ -107,7 +107,7 @@ function localeToLang(
|
|
|
107
107
|
type BuiltInStrings = (typeof builtinTranslations)['en'];
|
|
108
108
|
|
|
109
109
|
/** Build an i18next resources dictionary by layering preferred translation sources. */
|
|
110
|
-
function buildResources<T extends
|
|
110
|
+
function buildResources<T extends i18nSchemaOutput>(
|
|
111
111
|
...dictionaries: (T | BuiltInStrings | undefined)[]
|
|
112
112
|
): { [I18nextNamespace]: BuiltInStrings & T } {
|
|
113
113
|
const dictionary: Partial<BuiltInStrings> = {};
|
package/utils/error-map.ts
CHANGED
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { AstroError } from 'astro/errors';
|
|
7
|
-
import
|
|
7
|
+
import { z, locales } from 'astro/zod';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// The default Zod error map that we use to retrieve default error messages.
|
|
10
|
+
const zodErrorMap = locales.en().localeError;
|
|
11
|
+
|
|
12
|
+
type TypeErrByPathEntry = {
|
|
13
|
+
code: 'invalid_type';
|
|
11
14
|
received: unknown;
|
|
12
15
|
expected: unknown[];
|
|
16
|
+
message: string | undefined;
|
|
13
17
|
};
|
|
14
18
|
|
|
15
19
|
/**
|
|
@@ -20,12 +24,15 @@ type TypeOrLiteralErrByPathEntry = {
|
|
|
20
24
|
* @param message Error message preamble to use if the input fails to parse.
|
|
21
25
|
* @returns Validated data parsed by Zod.
|
|
22
26
|
*/
|
|
23
|
-
export function parseWithFriendlyErrors<T extends z.
|
|
27
|
+
export function parseWithFriendlyErrors<T extends z.ZodType>(
|
|
24
28
|
schema: T,
|
|
25
29
|
input: z.input<T>,
|
|
26
30
|
message: string
|
|
27
31
|
): z.output<T> {
|
|
28
|
-
return processParsedData<T>(
|
|
32
|
+
return processParsedData<T>(
|
|
33
|
+
schema.safeParse(input, { error: errorMap, reportInput: true }),
|
|
34
|
+
message
|
|
35
|
+
);
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
/**
|
|
@@ -37,16 +44,19 @@ export function parseWithFriendlyErrors<T extends z.Schema>(
|
|
|
37
44
|
* @param message Error message preamble to use if the input fails to parse.
|
|
38
45
|
* @returns Validated data parsed by Zod.
|
|
39
46
|
*/
|
|
40
|
-
export async function parseAsyncWithFriendlyErrors<T extends z.
|
|
47
|
+
export async function parseAsyncWithFriendlyErrors<T extends z.ZodType>(
|
|
41
48
|
schema: T,
|
|
42
49
|
input: z.input<T>,
|
|
43
50
|
message: string
|
|
44
51
|
): Promise<z.output<T>> {
|
|
45
|
-
return processParsedData<T>(
|
|
52
|
+
return processParsedData<T>(
|
|
53
|
+
await schema.safeParseAsync(input, { error: errorMap, reportInput: true }),
|
|
54
|
+
message
|
|
55
|
+
);
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
function processParsedData<T extends z.
|
|
49
|
-
parsedData: z.
|
|
58
|
+
function processParsedData<T extends z.ZodType>(
|
|
59
|
+
parsedData: z.ZodSafeParseResult<z.output<T>>,
|
|
50
60
|
message: string
|
|
51
61
|
) {
|
|
52
62
|
if (!parsedData.success) {
|
|
@@ -55,25 +65,27 @@ function processParsedData<T extends z.Schema>(
|
|
|
55
65
|
return parsedData.data;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
|
-
const errorMap: z.ZodErrorMap = (
|
|
59
|
-
const baseErrorPath = flattenErrorPath(
|
|
60
|
-
if (
|
|
68
|
+
const errorMap: z.core.$ZodErrorMap = (issue) => {
|
|
69
|
+
const baseErrorPath = flattenErrorPath(issue.path ?? []);
|
|
70
|
+
if (issue.code === 'invalid_union') {
|
|
61
71
|
// Optimization: Combine type and literal errors for keys that are common across ALL union types
|
|
62
72
|
// Ex. a union between `{ key: z.literal('tutorial') }` and `{ key: z.literal('blog') }` will
|
|
63
73
|
// raise a single error when `key` does not match:
|
|
64
74
|
// > Did not match union.
|
|
65
75
|
// > key: Expected `'tutorial' | 'blog'`, received 'foo'
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
const unionErrors = issue.errors.flat();
|
|
77
|
+
const typeOrLiteralErrByPath: Map<string, TypeErrByPathEntry> = new Map();
|
|
78
|
+
for (const unionError of unionErrors) {
|
|
79
|
+
if (unionError.code === 'invalid_type') {
|
|
80
|
+
const flattenedErrorPath = flattenErrorPath([baseErrorPath, ...unionError.path]);
|
|
70
81
|
if (typeOrLiteralErrByPath.has(flattenedErrorPath)) {
|
|
71
82
|
typeOrLiteralErrByPath.get(flattenedErrorPath)!.expected.push(unionError.expected);
|
|
72
83
|
} else {
|
|
73
84
|
typeOrLiteralErrByPath.set(flattenedErrorPath, {
|
|
74
85
|
code: unionError.code,
|
|
75
|
-
received:
|
|
86
|
+
received: parsedType(issue.input),
|
|
76
87
|
expected: [unionError.expected],
|
|
88
|
+
message: unionError.message,
|
|
77
89
|
});
|
|
78
90
|
}
|
|
79
91
|
}
|
|
@@ -82,83 +94,84 @@ const errorMap: z.ZodErrorMap = (baseError, ctx) => {
|
|
|
82
94
|
const details: string[] = [...typeOrLiteralErrByPath.entries()]
|
|
83
95
|
// If type or literal error isn't common to ALL union types,
|
|
84
96
|
// filter it out. Can lead to confusing noise.
|
|
85
|
-
.filter(([, error]) => error.expected.length ===
|
|
97
|
+
.filter(([, error]) => error.expected.length === unionErrors.length)
|
|
86
98
|
.map(([key, error]) =>
|
|
87
99
|
key === baseErrorPath
|
|
88
100
|
? // Avoid printing the key again if it's a base error
|
|
89
|
-
`> ${
|
|
90
|
-
: `> ${prefix(key,
|
|
101
|
+
`> ${getTypeErrMsg(error)}`
|
|
102
|
+
: `> ${prefix(key, getTypeErrMsg(error))}`
|
|
91
103
|
);
|
|
92
104
|
|
|
93
105
|
if (details.length === 0) {
|
|
94
106
|
const expectedShapes: string[] = [];
|
|
95
|
-
for (const unionError of
|
|
107
|
+
for (const unionError of issue.errors) {
|
|
96
108
|
const expectedShape: string[] = [];
|
|
97
|
-
for (const issue of unionError
|
|
109
|
+
for (const issue of unionError) {
|
|
98
110
|
// If the issue is a nested union error, show the associated error message instead of the
|
|
99
111
|
// base error message.
|
|
100
112
|
if (issue.code === 'invalid_union') {
|
|
101
|
-
return errorMap(issue,
|
|
113
|
+
return errorMap({ ...issue, input: issue.input, path: [baseErrorPath, ...issue.path] });
|
|
102
114
|
}
|
|
103
115
|
const relativePath = flattenErrorPath(issue.path)
|
|
104
116
|
.replace(baseErrorPath, '')
|
|
105
117
|
.replace(leadingPeriod, '');
|
|
106
|
-
if (
|
|
118
|
+
if (issue.code === 'invalid_type') {
|
|
107
119
|
expectedShape.push(
|
|
108
120
|
relativePath ? `${relativePath}: ${issue.expected}` : issue.expected
|
|
109
121
|
);
|
|
110
|
-
} else {
|
|
122
|
+
} else if (issue.code === 'custom') {
|
|
111
123
|
expectedShape.push(relativePath);
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
if (expectedShape.length === 1 && !expectedShape[0]?.includes(':')) {
|
|
115
127
|
// In this case the expected shape is not an object, but probably a literal type, e.g. `['string']`.
|
|
116
128
|
expectedShapes.push(expectedShape.join(''));
|
|
117
|
-
} else {
|
|
129
|
+
} else if (expectedShape.length > 0) {
|
|
118
130
|
expectedShapes.push(`{ ${expectedShape.join('; ')} }`);
|
|
119
131
|
}
|
|
120
132
|
}
|
|
121
133
|
if (expectedShapes.length) {
|
|
122
134
|
details.push('> Expected type `' + expectedShapes.join(' | ') + '`');
|
|
123
|
-
details.push('> Received `' + stringify(
|
|
135
|
+
details.push('> Received `' + stringify(issue.input) + '`');
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
};
|
|
141
|
-
} else if (baseError.message) {
|
|
142
|
-
return { message: prefix(baseErrorPath, baseError.message) };
|
|
139
|
+
return messages.concat(details).join('\n');
|
|
140
|
+
} else if (issue.code === 'invalid_type') {
|
|
141
|
+
return prefix(
|
|
142
|
+
baseErrorPath,
|
|
143
|
+
getTypeErrMsg({
|
|
144
|
+
code: issue.code,
|
|
145
|
+
received: parsedType(issue.input),
|
|
146
|
+
expected: [issue.expected],
|
|
147
|
+
message: issue.message,
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
} else if (issue.message) {
|
|
151
|
+
return prefix(baseErrorPath, issue.message);
|
|
143
152
|
} else {
|
|
144
|
-
|
|
153
|
+
// By design, the default Zod error may not be provided in Zod 4 error maps. Instead, error
|
|
154
|
+
// maps are supposed to return `undefined` in order to yield control to the next error map in
|
|
155
|
+
// the precedence chain. Unfortunately, this prevents us from prefixing all errors with their
|
|
156
|
+
// paths so we have to manually invoke the default Zod error map here.
|
|
157
|
+
const defaultError = zodErrorMap(issue);
|
|
158
|
+
if (!defaultError) return;
|
|
159
|
+
|
|
160
|
+
return prefix(
|
|
161
|
+
baseErrorPath,
|
|
162
|
+
typeof defaultError === 'string' ? defaultError : defaultError.message
|
|
163
|
+
);
|
|
145
164
|
}
|
|
146
165
|
};
|
|
147
166
|
|
|
148
|
-
const
|
|
167
|
+
const getTypeErrMsg = (error: TypeErrByPathEntry): string => {
|
|
149
168
|
// received could be `undefined` or the string `'undefined'`
|
|
150
|
-
if (typeof error.received === 'undefined' || error.received === 'undefined')
|
|
169
|
+
if (typeof error.received === 'undefined' || error.received === 'undefined')
|
|
170
|
+
return error.message ?? 'Required';
|
|
151
171
|
const expectedDeduped = new Set(error.expected);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
error.received
|
|
156
|
-
)}\``;
|
|
157
|
-
case 'invalid_literal':
|
|
158
|
-
return `Expected \`${unionExpectedVals(expectedDeduped)}\`, received \`${stringify(
|
|
159
|
-
error.received
|
|
160
|
-
)}\``;
|
|
161
|
-
}
|
|
172
|
+
return `Expected type \`${unionExpectedVals(expectedDeduped)}\`, received \`${stringify(
|
|
173
|
+
error.received
|
|
174
|
+
)}\``;
|
|
162
175
|
};
|
|
163
176
|
|
|
164
177
|
const prefix = (key: string, msg: string) => (key.length ? `**${key}**: ${msg}` : msg);
|
|
@@ -166,10 +179,38 @@ const prefix = (key: string, msg: string) => (key.length ? `**${key}**: ${msg}`
|
|
|
166
179
|
const unionExpectedVals = (expectedVals: Set<unknown>) =>
|
|
167
180
|
[...expectedVals].map((expectedVal) => stringify(expectedVal)).join(' | ');
|
|
168
181
|
|
|
169
|
-
const flattenErrorPath = (errorPath:
|
|
182
|
+
const flattenErrorPath = (errorPath: PropertyKey[]) => errorPath.join('.');
|
|
170
183
|
|
|
171
184
|
/** `JSON.stringify()` a value with spaces around object/array entries. */
|
|
172
185
|
const stringify = (val: unknown) =>
|
|
173
186
|
JSON.stringify(val, null, 1).split(newlinePlusWhitespace).join(' ');
|
|
174
187
|
const newlinePlusWhitespace = /\n\s*/;
|
|
175
188
|
const leadingPeriod = /^\./;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* In Zod 4, we don't necessarily get a human-readable representation of input data types. For such
|
|
192
|
+
* cases, we use the same logic as Zod's own `parsedType()` function.
|
|
193
|
+
* @see https://github.com/colinhacks/zod/blob/73b071d7d08825dedb6b48b78718739118ee1308/packages/zod/src/v4/locales/en.ts#L5
|
|
194
|
+
*/
|
|
195
|
+
const parsedType = (data: unknown): string => {
|
|
196
|
+
const t = typeof data;
|
|
197
|
+
|
|
198
|
+
switch (t) {
|
|
199
|
+
case 'number': {
|
|
200
|
+
return Number.isNaN(data) ? 'NaN' : 'number';
|
|
201
|
+
}
|
|
202
|
+
case 'object': {
|
|
203
|
+
if (Array.isArray(data)) {
|
|
204
|
+
return 'array';
|
|
205
|
+
}
|
|
206
|
+
if (data === null) {
|
|
207
|
+
return 'null';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (data && Object.getPrototypeOf(data) !== Object.prototype && data.constructor) {
|
|
211
|
+
return data.constructor.name;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return t;
|
|
216
|
+
};
|
package/utils/navigation.ts
CHANGED
|
@@ -28,7 +28,7 @@ import type {
|
|
|
28
28
|
Route,
|
|
29
29
|
SidebarEntry,
|
|
30
30
|
} from './routing/types';
|
|
31
|
-
import { localeToLang,
|
|
31
|
+
import { localeToLang, localizedFilePath, slugToPathname } from './slugs';
|
|
32
32
|
import { isAbsoluteUrl } from './url';
|
|
33
33
|
import type { StarlightConfig } from './user-config';
|
|
34
34
|
|
|
@@ -145,7 +145,7 @@ function linkFromInternalSidebarLinkItem(
|
|
|
145
145
|
// Astro passes root `index.[md|mdx]` entries with a slug of `index`
|
|
146
146
|
const slug = item.slug === 'index' ? '' : item.slug;
|
|
147
147
|
const localizedSlug = locale ? (slug ? locale + '/' + slug : locale) : slug;
|
|
148
|
-
const route = routes.find((entry) => localizedSlug === entry.
|
|
148
|
+
const route = routes.find((entry) => localizedSlug === entry.id);
|
|
149
149
|
if (!route) {
|
|
150
150
|
const hasExternalSlashes = item.slug.at(0) === '/' || item.slug.at(-1) === '/';
|
|
151
151
|
if (hasExternalSlashes) {
|
|
@@ -170,7 +170,7 @@ function linkFromInternalSidebarLinkItem(
|
|
|
170
170
|
const badge = item.badge ?? frontmatter.sidebar?.badge;
|
|
171
171
|
const attrs = { ...frontmatter.sidebar?.attrs, ...item.attrs };
|
|
172
172
|
return makeSidebarLink(
|
|
173
|
-
slugToPathname(route.
|
|
173
|
+
slugToPathname(route.id),
|
|
174
174
|
label,
|
|
175
175
|
getSidebarBadge(badge, locale, label),
|
|
176
176
|
attrs
|
|
@@ -225,12 +225,13 @@ function getBreadcrumbs(path: string, baseDir: string): string[] {
|
|
|
225
225
|
return relativePath.split('/');
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
/** Return the path of a route relative to the root of the collection
|
|
228
|
+
/** Return the path of a route relative to the root of the collection. */
|
|
229
229
|
function getRoutePathRelativeToCollectionRoot(route: Route, locale: string | undefined) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
// Use a localized filePath relative to the collection
|
|
231
|
+
return localizedFilePath(
|
|
232
|
+
route.entry.filePath.replace(`${docsCollectionPathFromRoot}/`, ''),
|
|
233
|
+
locale
|
|
234
|
+
);
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
/** Turn a flat array of routes into a tree structure. */
|
|
@@ -274,7 +275,7 @@ function treeify(routes: Route[], locale: string | undefined, baseDir: string):
|
|
|
274
275
|
/** Create a link entry for a given content collection entry. */
|
|
275
276
|
function linkFromRoute(route: Route, attrs?: LinkHTMLAttributes): SidebarLink {
|
|
276
277
|
return makeSidebarLink(
|
|
277
|
-
slugToPathname(route.
|
|
278
|
+
slugToPathname(route.id),
|
|
278
279
|
route.entry.data.sidebar.label || route.entry.data.title,
|
|
279
280
|
route.entry.data.sidebar.badge,
|
|
280
281
|
{ ...attrs, ...route.entry.data.sidebar.attrs }
|
|
@@ -300,7 +301,7 @@ function sortDirEntries(dir: [string, Dir | Route][]): [string, Dir | Route][] {
|
|
|
300
301
|
// Pages are sorted by order in ascending order.
|
|
301
302
|
if (aOrder !== bOrder) return aOrder < bOrder ? -1 : 1;
|
|
302
303
|
// If two pages have the same order value they will be sorted by their slug.
|
|
303
|
-
return collator.compare(isDir(a) ? a[SlugKey] : a.
|
|
304
|
+
return collator.compare(isDir(a) ? a[SlugKey] : a.id, isDir(b) ? b[SlugKey] : b.id);
|
|
304
305
|
});
|
|
305
306
|
}
|
|
306
307
|
|