@astrojs/starlight 0.41.3 → 0.41.4
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 +10 -0
- package/components-internals/SidebarPersistState.ts +0 -1
- package/package.json +1 -1
- package/schema.ts +4 -5
- package/utils/navigation.ts +3 -3
- package/utils/url.ts +4 -0
- package/utils/zodDeepMerge.ts +121 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.41.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#3936](https://github.com/withastro/starlight/pull/3936) [`712eedd`](https://github.com/withastro/starlight/commit/712eedd8e0d28329feb361edc392438f37ba2095) Thanks [@miichom](https://github.com/miichom)! - Fixes support for modifying Zod enums when passing an [`extend` option](https://starlight.astro.build/reference/frontmatter/#extend) to Starlight’s `docsSchema()`
|
|
8
|
+
|
|
9
|
+
- [#4092](https://github.com/withastro/starlight/pull/4092) [`0896b91`](https://github.com/withastro/starlight/commit/0896b91607325b9d8494eb665cd1716a40025a5a) Thanks [@delucis](https://github.com/delucis)! - Fixes support for links containing a protocol like `mailto:` in the sidebar
|
|
10
|
+
|
|
11
|
+
- [#4088](https://github.com/withastro/starlight/pull/4088) [`4486ba4`](https://github.com/withastro/starlight/commit/4486ba432afe9e206f0b05651de24a9c25bdf6dd) Thanks [@delucis](https://github.com/delucis)! - Simplifies Starlight’s client-side sidebar state persistence script slightly
|
|
12
|
+
|
|
3
13
|
## 0.41.3
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/package.json
CHANGED
package/schema.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { FrontmatterTableOfContentsSchema } from './schemas/tableOfContents';
|
|
|
6
6
|
import { BadgeConfigSchema } from './schemas/badge';
|
|
7
7
|
import { HeroSchema } from './schemas/hero';
|
|
8
8
|
import { SidebarLinkItemHTMLAttributesSchema } from './schemas/sidebar';
|
|
9
|
+
import { deepMergeSchemas, type DeepMergedSchema } from './utils/zodDeepMerge';
|
|
9
10
|
export { i18nSchema } from './schemas/i18n';
|
|
10
11
|
|
|
11
12
|
/** Default content collection schema for Starlight’s `docs` collection. */
|
|
@@ -114,14 +115,12 @@ const StarlightFrontmatterSchema = (context: SchemaContext) =>
|
|
|
114
115
|
type DefaultSchema = ReturnType<typeof StarlightFrontmatterSchema>;
|
|
115
116
|
|
|
116
117
|
/** Base subset of Zod types that we support passing to the `extend` option. */
|
|
117
|
-
type BaseSchema = z.
|
|
118
|
+
type BaseSchema<T extends z.ZodRawShape = z.ZodRawShape> = z.ZodObject<T>;
|
|
118
119
|
|
|
119
120
|
/** Type that extends Starlight’s default schema with an optional, user-defined schema. */
|
|
120
121
|
type ExtendedSchema<T extends BaseSchema = never> = [T] extends [never]
|
|
121
122
|
? DefaultSchema
|
|
122
|
-
: T
|
|
123
|
-
? z.ZodIntersection<DefaultSchema, T>
|
|
124
|
-
: DefaultSchema;
|
|
123
|
+
: DeepMergedSchema<DefaultSchema, T>;
|
|
125
124
|
|
|
126
125
|
interface DocsSchemaOpts<T extends BaseSchema> {
|
|
127
126
|
/**
|
|
@@ -160,7 +159,7 @@ export function docsSchema<T extends BaseSchema = never>(
|
|
|
160
159
|
|
|
161
160
|
return (
|
|
162
161
|
UserSchema
|
|
163
|
-
? StarlightFrontmatterSchema(context)
|
|
162
|
+
? deepMergeSchemas(StarlightFrontmatterSchema(context), UserSchema)
|
|
164
163
|
: StarlightFrontmatterSchema(context)
|
|
165
164
|
) as ExtendedSchema<T>;
|
|
166
165
|
};
|
package/utils/navigation.ts
CHANGED
|
@@ -33,7 +33,7 @@ import type {
|
|
|
33
33
|
SidebarAutogenerateRouteData,
|
|
34
34
|
} from './routing/types';
|
|
35
35
|
import { localeToLang, localizedFilePath, slugToPathname } from './slugs';
|
|
36
|
-
import {
|
|
36
|
+
import { hasProtocol } from './url';
|
|
37
37
|
import type { StarlightConfig } from './user-config';
|
|
38
38
|
|
|
39
39
|
const DirKey = Symbol('DirKey');
|
|
@@ -120,7 +120,7 @@ function entriesFromAutogenerateConfig(
|
|
|
120
120
|
/** Create a link entry from a manual link item in user config. */
|
|
121
121
|
function linkFromSidebarLinkItem(item: SidebarLinkItem, locale: string | undefined) {
|
|
122
122
|
let href = item.link;
|
|
123
|
-
if (!
|
|
123
|
+
if (!hasProtocol(href)) {
|
|
124
124
|
href = ensureLeadingSlash(href);
|
|
125
125
|
// Inject current locale into link.
|
|
126
126
|
if (locale) href = '/' + locale + href;
|
|
@@ -188,7 +188,7 @@ function makeSidebarLink(
|
|
|
188
188
|
opts: MakeLinkOptions & { autogenerate: SidebarAutogenerateRouteData }
|
|
189
189
|
): SidebarAutoLink;
|
|
190
190
|
function makeSidebarLink({ attrs, badge, href, label, autogenerate }: MakeLinkOptions) {
|
|
191
|
-
if (!
|
|
191
|
+
if (!hasProtocol(href)) {
|
|
192
192
|
href = formatPath(href);
|
|
193
193
|
}
|
|
194
194
|
return makeLink({ label, href, badge, attrs, autogenerate });
|
package/utils/url.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
const HTTPProtocolRegEx = /^https?:\/\//;
|
|
2
|
+
const ProtocolRegEx = /^[a-z][a-z\d+.-]*:/i;
|
|
2
3
|
|
|
3
4
|
/** Check if a string starts with one of `http://` or `https://`. */
|
|
4
5
|
export const isAbsoluteUrl = (link: string) => HTTPProtocolRegEx.test(link);
|
|
6
|
+
|
|
7
|
+
/** Check if a string contains a protocol (e.g., `http:`, `https:`, `mailto:`). */
|
|
8
|
+
export const hasProtocol = (link: string) => ProtocolRegEx.test(link);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { z } from 'astro/zod';
|
|
2
|
+
|
|
3
|
+
/** Type-level equivalent of {@link mergeWithDefaultSchema}. */
|
|
4
|
+
export type DeepMergedSchema<Default extends z.core.$ZodType, User extends z.core.$ZodType> =
|
|
5
|
+
User extends z.ZodOptional<infer WrappedSchema extends z.core.$ZodType>
|
|
6
|
+
? z.ZodOptional<DeepMergedSchema<UnwrappedSchema<Default>, WrappedSchema>>
|
|
7
|
+
: User extends z.ZodNullable<infer WrappedSchema extends z.core.$ZodType>
|
|
8
|
+
? z.ZodNullable<DeepMergedSchema<UnwrappedSchema<Default>, WrappedSchema>>
|
|
9
|
+
: User extends z.ZodDefault<infer WrappedSchema extends z.core.$ZodType>
|
|
10
|
+
? z.ZodDefault<DeepMergedSchema<UnwrappedSchema<Default>, WrappedSchema>>
|
|
11
|
+
: User extends z.ZodPrefault<infer WrappedSchema extends z.core.$ZodType>
|
|
12
|
+
? z.ZodPrefault<DeepMergedSchema<UnwrappedSchema<Default>, WrappedSchema>>
|
|
13
|
+
: UnwrappedSchema<Default> extends z.ZodObject<infer DefaultShape>
|
|
14
|
+
? User extends z.ZodObject<infer UserShape, infer UserConfig>
|
|
15
|
+
? z.ZodObject<DeepMergedShape<DefaultShape, UserShape>, UserConfig>
|
|
16
|
+
: User
|
|
17
|
+
: UnwrappedSchema<Default> extends z.ZodArray<
|
|
18
|
+
infer DefaultItemSchema extends z.core.$ZodType
|
|
19
|
+
>
|
|
20
|
+
? User extends z.ZodArray<infer UserItemSchema extends z.core.$ZodType>
|
|
21
|
+
? z.ZodArray<DeepMergedSchema<DefaultItemSchema, UserItemSchema>>
|
|
22
|
+
: User
|
|
23
|
+
: User;
|
|
24
|
+
|
|
25
|
+
/** Type-level equivalent of {@link unwrapSchema}. */
|
|
26
|
+
type UnwrappedSchema<T extends z.core.$ZodType> =
|
|
27
|
+
T extends z.ZodOptional<infer Wrapped extends z.core.$ZodType>
|
|
28
|
+
? UnwrappedSchema<Wrapped>
|
|
29
|
+
: T extends z.ZodNullable<infer Wrapped extends z.core.$ZodType>
|
|
30
|
+
? UnwrappedSchema<Wrapped>
|
|
31
|
+
: T extends z.ZodDefault<infer Wrapped extends z.core.$ZodType>
|
|
32
|
+
? UnwrappedSchema<Wrapped>
|
|
33
|
+
: T extends z.ZodPrefault<infer Wrapped extends z.core.$ZodType>
|
|
34
|
+
? UnwrappedSchema<Wrapped>
|
|
35
|
+
: T;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Merged Zod object shapes, preserving default properties, adding user-only properties, and
|
|
39
|
+
* recursively merging shared properties.
|
|
40
|
+
*/
|
|
41
|
+
type DeepMergedShape<Default extends z.ZodRawShape, User extends z.ZodRawShape> = {
|
|
42
|
+
[Key in keyof Default | keyof User]: Key extends keyof User
|
|
43
|
+
? Key extends keyof Default
|
|
44
|
+
? DeepMergedSchema<Default[Key], User[Key]>
|
|
45
|
+
: User[Key]
|
|
46
|
+
: Key extends keyof Default
|
|
47
|
+
? Default[Key]
|
|
48
|
+
: never;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export function deepMergeSchemas(defaultSchema: z.ZodType, userSchema: z.ZodType): z.ZodType {
|
|
52
|
+
const unwrappedDefaultSchema = unwrapSchema(defaultSchema);
|
|
53
|
+
|
|
54
|
+
if (isWrappedSchema(userSchema)) {
|
|
55
|
+
return userSchema.clone({
|
|
56
|
+
...userSchema._zod.def,
|
|
57
|
+
innerType: deepMergeSchemas(unwrappedDefaultSchema, userSchema._zod.def.innerType),
|
|
58
|
+
} as Parameters<typeof userSchema.clone>[0]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isZodObject(unwrappedDefaultSchema) && isZodObject(userSchema)) {
|
|
62
|
+
const shape: z.core.$ZodLooseShape = { ...unwrappedDefaultSchema.shape };
|
|
63
|
+
|
|
64
|
+
for (const key of Object.keys(userSchema.shape)) {
|
|
65
|
+
const userFieldSchema = userSchema.shape[key] as z.ZodType;
|
|
66
|
+
const defaultFieldSchema = shape[key] as z.ZodType | undefined;
|
|
67
|
+
|
|
68
|
+
shape[key] = defaultFieldSchema
|
|
69
|
+
? deepMergeSchemas(defaultFieldSchema, userFieldSchema)
|
|
70
|
+
: userFieldSchema;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return userSchema.safeExtend(shape);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isZodArray(unwrappedDefaultSchema) && isZodArray(userSchema)) {
|
|
77
|
+
return userSchema.clone({
|
|
78
|
+
...userSchema._zod.def,
|
|
79
|
+
element: deepMergeSchemas(unwrappedDefaultSchema.element, userSchema.element),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return userSchema;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Unwrap any Zod schema that wraps other schemas, such as `z.optional()`, `z.nullable()`,
|
|
88
|
+
* `z.default()`, etc. until we reach the innermost schema.
|
|
89
|
+
*/
|
|
90
|
+
function unwrapSchema(schema: z.ZodType): z.ZodType {
|
|
91
|
+
let current = schema;
|
|
92
|
+
|
|
93
|
+
while (isWrappedSchema(current)) {
|
|
94
|
+
current = current._zod.def.innerType;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return current;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if a schema wraps another schema which can happen when using modifiers like
|
|
102
|
+
* `z.optional()`, `z.nullable()`, `z.default()`, etc.
|
|
103
|
+
*/
|
|
104
|
+
function isWrappedSchema(schema: z.ZodType): schema is z.ZodType & {
|
|
105
|
+
_zod: { def: z.ZodType['_zod']['def'] & { innerType: z.ZodType } };
|
|
106
|
+
} {
|
|
107
|
+
return (
|
|
108
|
+
schema._zod.def.type === 'optional' ||
|
|
109
|
+
schema._zod.def.type === 'nullable' ||
|
|
110
|
+
schema._zod.def.type === 'default' ||
|
|
111
|
+
schema._zod.def.type === 'prefault'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isZodObject(schema: z.ZodType): schema is z.ZodObject<z.ZodRawShape> {
|
|
116
|
+
return schema._zod.def.type === 'object';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isZodArray(schema: z.ZodType): schema is z.ZodArray<z.ZodType> {
|
|
120
|
+
return schema._zod.def.type === 'array';
|
|
121
|
+
}
|