@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 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
@@ -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.4",
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,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.core.$ZodType;
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 extends BaseSchema
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).and(UserSchema)
162
+ ? deepMergeSchemas(StarlightFrontmatterSchema(context), UserSchema)
164
163
  : StarlightFrontmatterSchema(context)
165
164
  ) as ExtendedSchema<T>;
166
165
  };
@@ -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,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
+ }