@astrojs/starlight 0.19.1 → 0.20.1

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,27 @@
1
1
  # @astrojs/starlight
2
2
 
3
+ ## 0.20.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1553](https://github.com/withastro/starlight/pull/1553) [`8e091147`](https://github.com/withastro/starlight/commit/8e09114755d37322d6e97b0dc90a5dfd781de8cc) Thanks [@hippotastic](https://github.com/hippotastic)! - Updates Expressive Code to v0.33.4 to fix potential race condition bug in Shiki.
8
+
9
+ ## 0.20.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#1541](https://github.com/withastro/starlight/pull/1541) [`1043052f`](https://github.com/withastro/starlight/commit/1043052f3890a577a73276472f3773924909406b) Thanks [@hippotastic](https://github.com/hippotastic)! - Updates `astro-expressive-code` dependency to the latest minor release (0.33).
14
+
15
+ This unlocks support for [word wrap](https://expressive-code.com/key-features/word-wrap/) and [line numbers](https://expressive-code.com/plugins/line-numbers/), as well as updating the syntax highlighter to the latest Shiki release, which includes new and updated language grammars.
16
+
17
+ See the [Expressive Code release notes](https://expressive-code.com/releases/) for more information including details of potentially breaking changes.
18
+
19
+ ### Patch Changes
20
+
21
+ - [#1542](https://github.com/withastro/starlight/pull/1542) [`b3b7a606`](https://github.com/withastro/starlight/commit/b3b7a6069952d5f27a49b2fd097aa4db065e1718) Thanks [@delucis](https://github.com/delucis)! - Improves error messages shown by Starlight for configuration errors.
22
+
23
+ - [#1544](https://github.com/withastro/starlight/pull/1544) [`65dc6586`](https://github.com/withastro/starlight/commit/65dc6586ef7c1754875db1d48c49e709051a0b13) Thanks [@torn4dom4n](https://github.com/torn4dom4n)! - Update Vietnamese UI translations
24
+
3
25
  ## 0.19.1
4
26
 
5
27
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.19.1",
3
+ "version": "0.20.1",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
@@ -172,9 +172,9 @@
172
172
  "devDependencies": {
173
173
  "@astrojs/markdown-remark": "^4.2.1",
174
174
  "@types/node": "^18.16.19",
175
- "@vitest/coverage-v8": "^1.2.2",
175
+ "@vitest/coverage-v8": "^1.3.1",
176
176
  "astro": "^4.3.5",
177
- "vitest": "^1.2.2"
177
+ "vitest": "^1.3.1"
178
178
  },
179
179
  "dependencies": {
180
180
  "@astrojs/mdx": "^2.1.1",
@@ -182,7 +182,7 @@
182
182
  "@pagefind/default-ui": "^1.0.3",
183
183
  "@types/hast": "^3.0.3",
184
184
  "@types/mdast": "^4.0.3",
185
- "astro-expressive-code": "^0.32.4",
185
+ "astro-expressive-code": "^0.33.4",
186
186
  "bcp-47": "^2.1.0",
187
187
  "hast-util-select": "^6.0.2",
188
188
  "hastscript": "^8.0.0",
@@ -19,8 +19,8 @@
19
19
  "page.previousLink": "Tiếp",
20
20
  "page.nextLink": "Trước",
21
21
  "404.text": "Không tìm thấy trang. Kiểm tra URL hoặc thử sử dụng thanh tìm kiếm.",
22
- "aside.note": "Note",
23
- "aside.tip": "Tip",
24
- "aside.caution": "Caution",
25
- "aside.danger": "Danger"
22
+ "aside.note": "Ghi chú",
23
+ "aside.tip": "Mẹo",
24
+ "aside.caution": "Thận trọng",
25
+ "aside.danger": "Nguy hiểm"
26
26
  }
@@ -3,6 +3,7 @@
3
3
  * source: https://github.com/withastro/astro/blob/main/packages/astro/src/content/error-map.ts
4
4
  */
5
5
 
6
+ import { AstroError } from 'astro/errors';
6
7
  import type { z } from 'astro:content';
7
8
 
8
9
  type TypeOrLiteralErrByPathEntry = {
@@ -11,11 +12,27 @@ type TypeOrLiteralErrByPathEntry = {
11
12
  expected: unknown[];
12
13
  };
13
14
 
14
- export function throwValidationError(error: z.ZodError, message: string): never {
15
- throw new Error(`${message}\n${error.issues.map((i) => i.message).join('\n')}`);
15
+ /**
16
+ * Parse data with a Zod schema and throw a nicely formatted error if it is invalid.
17
+ *
18
+ * @param schema The Zod schema to use to parse the input.
19
+ * @param input Input data that should match the schema.
20
+ * @param message Error message preamble to use if the input fails to parse.
21
+ * @returns Validated data parsed by Zod.
22
+ */
23
+ export function parseWithFriendlyErrors<T extends z.Schema>(
24
+ schema: T,
25
+ input: z.input<T>,
26
+ message: string
27
+ ): z.output<T> {
28
+ const parsedConfig = schema.safeParse(input, { errorMap });
29
+ if (!parsedConfig.success) {
30
+ throw new AstroError(message, parsedConfig.error.issues.map((i) => i.message).join('\n'));
31
+ }
32
+ return parsedConfig.data;
16
33
  }
17
34
 
18
- export const errorMap: z.ZodErrorMap = (baseError, ctx) => {
35
+ const errorMap: z.ZodErrorMap = (baseError, ctx) => {
19
36
  const baseErrorPath = flattenErrorPath(baseError.path);
20
37
  if (baseError.code === 'invalid_union') {
21
38
  // Optimization: Combine type and literal errors for keys that are common across ALL union types
@@ -38,30 +55,51 @@ export const errorMap: z.ZodErrorMap = (baseError, ctx) => {
38
55
  }
39
56
  }
40
57
  }
41
- let messages: string[] = [
42
- prefix(
43
- baseErrorPath,
44
- typeOrLiteralErrByPath.size ? 'Did not match union:' : 'Did not match union.'
45
- ),
46
- ];
58
+ const messages: string[] = [prefix(baseErrorPath, 'Did not match union.')];
59
+ const details: string[] = [...typeOrLiteralErrByPath.entries()]
60
+ // If type or literal error isn't common to ALL union types,
61
+ // filter it out. Can lead to confusing noise.
62
+ .filter(([, error]) => error.expected.length === baseError.unionErrors.length)
63
+ .map(([key, error]) =>
64
+ key === baseErrorPath
65
+ ? // Avoid printing the key again if it's a base error
66
+ `> ${getTypeOrLiteralMsg(error)}`
67
+ : `> ${prefix(key, getTypeOrLiteralMsg(error))}`
68
+ );
69
+
70
+ if (details.length === 0) {
71
+ const expectedShapes: string[] = [];
72
+ for (const unionError of baseError.unionErrors) {
73
+ const expectedShape: string[] = [];
74
+ for (const issue of unionError.issues) {
75
+ // If the issue is a nested union error, show the associated error message instead of the
76
+ // base error message.
77
+ if (issue.code === 'invalid_union') {
78
+ return errorMap(issue, ctx);
79
+ }
80
+ const relativePath = flattenErrorPath(issue.path)
81
+ .replace(baseErrorPath, '')
82
+ .replace(leadingPeriod, '');
83
+ if ('expected' in issue && typeof issue.expected === 'string') {
84
+ expectedShape.push(
85
+ relativePath ? `${relativePath}: ${issue.expected}` : issue.expected
86
+ );
87
+ } else {
88
+ expectedShape.push(relativePath);
89
+ }
90
+ }
91
+ expectedShapes.push(`{ ${expectedShape.join('; ')} }`);
92
+ }
93
+ if (expectedShapes.length) {
94
+ details.push('> Expected type `' + expectedShapes.join(' | ') + '`');
95
+ details.push('> Received `' + stringify(ctx.data) + '`');
96
+ }
97
+ }
98
+
47
99
  return {
48
- message: messages
49
- .concat(
50
- [...typeOrLiteralErrByPath.entries()]
51
- // If type or literal error isn't common to ALL union types,
52
- // filter it out. Can lead to confusing noise.
53
- .filter(([, error]) => error.expected.length === baseError.unionErrors.length)
54
- .map(([key, error]) =>
55
- key === baseErrorPath
56
- ? // Avoid printing the key again if it's a base error
57
- `> ${getTypeOrLiteralMsg(error)}`
58
- : `> ${prefix(key, getTypeOrLiteralMsg(error))}`
59
- )
60
- )
61
- .join('\n'),
100
+ message: messages.concat(details).join('\n'),
62
101
  };
63
- }
64
- if (baseError.code === 'invalid_literal' || baseError.code === 'invalid_type') {
102
+ } else if (baseError.code === 'invalid_literal' || baseError.code === 'invalid_type') {
65
103
  return {
66
104
  message: prefix(
67
105
  baseErrorPath,
@@ -84,25 +122,25 @@ const getTypeOrLiteralMsg = (error: TypeOrLiteralErrByPathEntry): string => {
84
122
  const expectedDeduped = new Set(error.expected);
85
123
  switch (error.code) {
86
124
  case 'invalid_type':
87
- return `Expected type \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify(
125
+ return `Expected type \`${unionExpectedVals(expectedDeduped)}\`, received \`${stringify(
88
126
  error.received
89
- )}`;
127
+ )}\``;
90
128
  case 'invalid_literal':
91
- return `Expected \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify(
129
+ return `Expected \`${unionExpectedVals(expectedDeduped)}\`, received \`${stringify(
92
130
  error.received
93
- )}`;
131
+ )}\``;
94
132
  }
95
133
  };
96
134
 
97
135
  const prefix = (key: string, msg: string) => (key.length ? `**${key}**: ${msg}` : msg);
98
136
 
99
137
  const unionExpectedVals = (expectedVals: Set<unknown>) =>
100
- [...expectedVals]
101
- .map((expectedVal, idx) => {
102
- if (idx === 0) return JSON.stringify(expectedVal);
103
- const sep = ' | ';
104
- return `${sep}${JSON.stringify(expectedVal)}`;
105
- })
106
- .join('');
138
+ [...expectedVals].map((expectedVal) => stringify(expectedVal)).join(' | ');
107
139
 
108
140
  const flattenErrorPath = (errorPath: (string | number)[]) => errorPath.join('.');
141
+
142
+ /** `JSON.stringify()` a value with spaces around object/array entries. */
143
+ const stringify = (val: unknown) =>
144
+ JSON.stringify(val, null, 1).split(newlinePlusWhitespace).join(' ');
145
+ const newlinePlusWhitespace = /\n\s*/;
146
+ const leadingPeriod = /^\./;
package/utils/plugins.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { AstroIntegration } from 'astro';
2
2
  import { z } from 'astro/zod';
3
3
  import { StarlightConfigSchema, type StarlightUserConfig } from '../utils/user-config';
4
- import { errorMap, throwValidationError } from '../utils/error-map';
4
+ import { parseWithFriendlyErrors } from '../utils/error-map';
5
5
 
6
6
  /**
7
7
  * Runs Starlight plugins in the order that they are configured after validating the user-provided
@@ -15,23 +15,19 @@ export async function runPlugins(
15
15
  ) {
16
16
  // Validate the user-provided configuration.
17
17
  let userConfig = starlightUserConfig;
18
- let starlightConfig = StarlightConfigSchema.safeParse(userConfig, { errorMap });
19
18
 
20
- if (!starlightConfig.success) {
21
- throwValidationError(starlightConfig.error, 'Invalid config passed to starlight integration');
22
- }
19
+ let starlightConfig = parseWithFriendlyErrors(
20
+ StarlightConfigSchema,
21
+ userConfig,
22
+ 'Invalid config passed to starlight integration'
23
+ );
23
24
 
24
25
  // Validate the user-provided plugins configuration.
25
- const pluginsConfig = starlightPluginsConfigSchema.safeParse(pluginsUserConfig, {
26
- errorMap,
27
- });
28
-
29
- if (!pluginsConfig.success) {
30
- throwValidationError(
31
- pluginsConfig.error,
32
- 'Invalid plugins config passed to starlight integration'
33
- );
34
- }
26
+ const pluginsConfig = parseWithFriendlyErrors(
27
+ starlightPluginsConfigSchema,
28
+ pluginsUserConfig,
29
+ 'Invalid plugins config passed to starlight integration'
30
+ );
35
31
 
36
32
  // A list of Astro integrations added by the various plugins.
37
33
  const integrations: AstroIntegration[] = [];
@@ -39,7 +35,7 @@ export async function runPlugins(
39
35
  for (const {
40
36
  name,
41
37
  hooks: { setup },
42
- } of pluginsConfig.data) {
38
+ } of pluginsConfig) {
43
39
  await setup({
44
40
  config: pluginsUserConfig ? { ...userConfig, plugins: pluginsUserConfig } : userConfig,
45
41
  updateConfig(newConfig) {
@@ -52,14 +48,11 @@ export async function runPlugins(
52
48
 
53
49
  // If the plugin is updating the user config, re-validate it.
54
50
  const mergedUserConfig = { ...userConfig, ...newConfig };
55
- const mergedConfig = StarlightConfigSchema.safeParse(mergedUserConfig, { errorMap });
56
-
57
- if (!mergedConfig.success) {
58
- throwValidationError(
59
- mergedConfig.error,
60
- `Invalid config update provided by the '${name}' plugin`
61
- );
62
- }
51
+ const mergedConfig = parseWithFriendlyErrors(
52
+ StarlightConfigSchema,
53
+ mergedUserConfig,
54
+ `Invalid config update provided by the '${name}' plugin`
55
+ );
63
56
 
64
57
  // If the updated config is valid, keep track of both the user config and parsed config.
65
58
  userConfig = mergedUserConfig;
@@ -79,7 +72,7 @@ export async function runPlugins(
79
72
  });
80
73
  }
81
74
 
82
- return { integrations, starlightConfig: starlightConfig.data };
75
+ return { integrations, starlightConfig };
83
76
  }
84
77
 
85
78
  // https://github.com/withastro/astro/blob/910eb00fe0b70ca80bd09520ae100e8c78b675b5/packages/astro/src/core/config/schema.ts#L113
@@ -1,7 +1,7 @@
1
1
  import { z } from 'astro/zod';
2
2
  import { type ContentConfig, type SchemaContext } from 'astro:content';
3
3
  import config from 'virtual:starlight/user-config';
4
- import { errorMap, throwValidationError } from './error-map';
4
+ import { parseWithFriendlyErrors } from './error-map';
5
5
  import { stripLeadingAndTrailingSlashes } from './path';
6
6
  import { getToC, type PageProps, type StarlightRouteData } from './route-data';
7
7
  import type { StarlightDocsEntry } from './routing';
@@ -138,14 +138,11 @@ type StarlightPageSidebarUserConfig = z.input<typeof StarlightPageSidebarSchema>
138
138
  const normalizeSidebarProp = (
139
139
  sidebarProp: StarlightPageSidebarUserConfig
140
140
  ): StarlightRouteData['sidebar'] => {
141
- const sidebar = StarlightPageSidebarSchema.safeParse(sidebarProp, { errorMap });
142
- if (!sidebar.success) {
143
- throwValidationError(
144
- sidebar.error,
145
- 'Invalid sidebar prop passed to the `<StarlightPage/>` component.'
146
- );
147
- }
148
- return sidebar.data;
141
+ return parseWithFriendlyErrors(
142
+ StarlightPageSidebarSchema,
143
+ sidebarProp,
144
+ 'Invalid sidebar prop passed to the `<StarlightPage/>` component.'
145
+ );
149
146
  };
150
147
 
151
148
  /**
@@ -267,16 +264,11 @@ async function getStarlightPageFrontmatter(frontmatter: StarlightPageFrontmatter
267
264
  }),
268
265
  });
269
266
 
270
- const pageFrontmatter = schema.safeParse(frontmatter, { errorMap });
271
-
272
- if (!pageFrontmatter.success) {
273
- throwValidationError(
274
- pageFrontmatter.error,
275
- 'Invalid frontmatter props passed to the `<StarlightPage/>` component.'
276
- );
277
- }
278
-
279
- return pageFrontmatter.data;
267
+ return parseWithFriendlyErrors(
268
+ schema,
269
+ frontmatter,
270
+ 'Invalid frontmatter props passed to the `<StarlightPage/>` component.'
271
+ );
280
272
  }
281
273
 
282
274
  /** Returns the user docs schema and falls back to the default schema if needed. */