@astrojs/starlight 0.41.3 → 0.41.5

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @astrojs/starlight
2
2
 
3
+ ## 0.41.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4095](https://github.com/withastro/starlight/pull/4095) [`bb06434`](https://github.com/withastro/starlight/commit/bb06434a1bc199b59f9d7953d0e41c6c287b1aa2) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes a regression when using a union to [extend](https://starlight.astro.build/reference/frontmatter/#extend) Starlight’s `docsSchema()`.
8
+
9
+ ## 0.41.4
10
+
11
+ ### Patch Changes
12
+
13
+ - [#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()`
14
+
15
+ - [#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
16
+
17
+ - [#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
18
+
3
19
  ## 0.41.3
4
20
 
5
21
  ### Patch Changes
@@ -69,4 +69,3 @@ target?.addEventListener('click', (event) => {
69
69
  addEventListener('visibilitychange', () => {
70
70
  if (document.visibilityState === 'hidden') updateState();
71
71
  });
72
- addEventListener('pageHide', updateState);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.41.3",
3
+ "version": "0.41.5",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
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,13 @@ 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.core.$ZodType;
118
+ type BaseObjectSchema = z.ZodObject<z.ZodRawShape, z.core.$ZodObjectConfig>;
119
+ type BaseSchema = BaseObjectSchema | z.ZodUnion<readonly BaseObjectSchema[]>;
118
120
 
119
121
  /** Type that extends Starlight’s default schema with an optional, user-defined schema. */
120
122
  type ExtendedSchema<T extends BaseSchema = never> = [T] extends [never]
121
123
  ? DefaultSchema
122
- : T extends BaseSchema
123
- ? z.ZodIntersection<DefaultSchema, T>
124
- : DefaultSchema;
124
+ : DeepMergedSchema<DefaultSchema, T>;
125
125
 
126
126
  interface DocsSchemaOpts<T extends BaseSchema> {
127
127
  /**
@@ -160,7 +160,7 @@ export function docsSchema<T extends BaseSchema = never>(
160
160
 
161
161
  return (
162
162
  UserSchema
163
- ? StarlightFrontmatterSchema(context).and(UserSchema)
163
+ ? deepMergeSchemas(StarlightFrontmatterSchema(context), UserSchema)
164
164
  : StarlightFrontmatterSchema(context)
165
165
  ) as ExtendedSchema<T>;
166
166
  };
@@ -33,7 +33,7 @@ import type {
33
33
  SidebarAutogenerateRouteData,
34
34
  } from './routing/types';
35
35
  import { localeToLang, localizedFilePath, slugToPathname } from './slugs';
36
- import { isAbsoluteUrl } from './url';
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 (!isAbsoluteUrl(href)) {
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 (!isAbsoluteUrl(href)) {
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,144 @@
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
+ : User extends z.ZodUnion<infer Users extends readonly z.core.$ZodType[]>
14
+ ? z.ZodUnion<DeepMergedUnionMembers<UnwrappedSchema<Default>, Users>>
15
+ : UnwrappedSchema<Default> extends z.ZodObject<infer DefaultShape>
16
+ ? User extends z.ZodObject<infer UserShape, infer UserConfig>
17
+ ? z.ZodObject<DeepMergedShape<DefaultShape, UserShape>, UserConfig>
18
+ : User
19
+ : UnwrappedSchema<Default> extends z.ZodArray<
20
+ infer DefaultItemSchema extends z.core.$ZodType
21
+ >
22
+ ? User extends z.ZodArray<infer UserItemSchema extends z.core.$ZodType>
23
+ ? z.ZodArray<DeepMergedSchema<DefaultItemSchema, UserItemSchema>>
24
+ : User
25
+ : User;
26
+
27
+ /** Type-level equivalent of {@link unwrapSchema}. */
28
+ type UnwrappedSchema<T extends z.core.$ZodType> =
29
+ T extends z.ZodOptional<infer Wrapped extends z.core.$ZodType>
30
+ ? UnwrappedSchema<Wrapped>
31
+ : T extends z.ZodNullable<infer Wrapped extends z.core.$ZodType>
32
+ ? UnwrappedSchema<Wrapped>
33
+ : T extends z.ZodDefault<infer Wrapped extends z.core.$ZodType>
34
+ ? UnwrappedSchema<Wrapped>
35
+ : T extends z.ZodPrefault<infer Wrapped extends z.core.$ZodType>
36
+ ? UnwrappedSchema<Wrapped>
37
+ : T;
38
+
39
+ /**
40
+ * Merged Zod object shapes, preserving default properties, adding user-only properties, and
41
+ * recursively merging shared properties.
42
+ */
43
+ type DeepMergedShape<Default extends z.ZodRawShape, User extends z.ZodRawShape> = {
44
+ [Key in keyof Default | keyof User]: Key extends keyof User
45
+ ? Key extends keyof Default
46
+ ? DeepMergedSchema<Default[Key], User[Key]>
47
+ : User[Key]
48
+ : Key extends keyof Default
49
+ ? Default[Key]
50
+ : never;
51
+ };
52
+
53
+ /** Merged union schemas where each union member is deep-merged with the default schema. */
54
+ type DeepMergedUnionMembers<
55
+ Default extends z.core.$ZodType,
56
+ Users extends readonly z.core.$ZodType[],
57
+ > = {
58
+ [Key in keyof Users]: Users[Key] extends z.core.$ZodType
59
+ ? DeepMergedSchema<Default, Users[Key]>
60
+ : Users[Key];
61
+ };
62
+
63
+ export function deepMergeSchemas(defaultSchema: z.ZodType, userSchema: z.ZodType): z.ZodType {
64
+ const unwrappedDefaultSchema = unwrapSchema(defaultSchema);
65
+
66
+ if (isWrappedSchema(userSchema)) {
67
+ return userSchema.clone({
68
+ ...userSchema._zod.def,
69
+ innerType: deepMergeSchemas(unwrappedDefaultSchema, userSchema._zod.def.innerType),
70
+ } as Parameters<typeof userSchema.clone>[0]);
71
+ }
72
+
73
+ if (isZodUnion(userSchema)) {
74
+ return userSchema.clone({
75
+ ...userSchema._zod.def,
76
+ options: userSchema.options.map((schema) => deepMergeSchemas(unwrappedDefaultSchema, schema)),
77
+ });
78
+ }
79
+
80
+ if (isZodObject(unwrappedDefaultSchema) && isZodObject(userSchema)) {
81
+ const shape: z.core.$ZodLooseShape = { ...unwrappedDefaultSchema.shape };
82
+
83
+ for (const key of Object.keys(userSchema.shape)) {
84
+ const userFieldSchema = userSchema.shape[key] as z.ZodType;
85
+ const defaultFieldSchema = shape[key] as z.ZodType | undefined;
86
+
87
+ shape[key] = defaultFieldSchema
88
+ ? deepMergeSchemas(defaultFieldSchema, userFieldSchema)
89
+ : userFieldSchema;
90
+ }
91
+
92
+ return userSchema.safeExtend(shape);
93
+ }
94
+
95
+ if (isZodArray(unwrappedDefaultSchema) && isZodArray(userSchema)) {
96
+ return userSchema.clone({
97
+ ...userSchema._zod.def,
98
+ element: deepMergeSchemas(unwrappedDefaultSchema.element, userSchema.element),
99
+ });
100
+ }
101
+
102
+ return userSchema;
103
+ }
104
+
105
+ /**
106
+ * Unwrap any Zod schema that wraps other schemas, such as `z.optional()`, `z.nullable()`,
107
+ * `z.default()`, etc. until we reach the innermost schema.
108
+ */
109
+ function unwrapSchema(schema: z.ZodType): z.ZodType {
110
+ let current = schema;
111
+
112
+ while (isWrappedSchema(current)) {
113
+ current = current._zod.def.innerType;
114
+ }
115
+
116
+ return current;
117
+ }
118
+
119
+ /**
120
+ * Check if a schema wraps another schema which can happen when using modifiers like
121
+ * `z.optional()`, `z.nullable()`, `z.default()`, etc.
122
+ */
123
+ function isWrappedSchema(schema: z.ZodType): schema is z.ZodType & {
124
+ _zod: { def: z.ZodType['_zod']['def'] & { innerType: z.ZodType } };
125
+ } {
126
+ return (
127
+ schema._zod.def.type === 'optional' ||
128
+ schema._zod.def.type === 'nullable' ||
129
+ schema._zod.def.type === 'default' ||
130
+ schema._zod.def.type === 'prefault'
131
+ );
132
+ }
133
+
134
+ function isZodObject(schema: z.ZodType): schema is z.ZodObject<z.ZodRawShape> {
135
+ return schema._zod.def.type === 'object';
136
+ }
137
+
138
+ function isZodArray(schema: z.ZodType): schema is z.ZodArray<z.ZodType> {
139
+ return schema._zod.def.type === 'array';
140
+ }
141
+
142
+ function isZodUnion(schema: z.ZodType): schema is z.ZodUnion<readonly z.ZodType[]> {
143
+ return schema._zod.def.type === 'union';
144
+ }