@astrojs/starlight 0.19.0 → 0.20.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 +26 -0
- package/package.json +4 -4
- package/props.ts +1 -0
- package/translations/index.ts +2 -0
- package/translations/vi.json +4 -4
- package/translations/zh-TW.json +26 -0
- package/utils/error-map.ts +74 -36
- package/utils/plugins.ts +18 -25
- package/utils/starlight-page.ts +100 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#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).
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
See the [Expressive Code release notes](https://expressive-code.com/releases/) for more information including details of potentially breaking changes.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#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.
|
|
16
|
+
|
|
17
|
+
- [#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
|
|
18
|
+
|
|
19
|
+
## 0.19.1
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#1527](https://github.com/withastro/starlight/pull/1527) [`163bc84`](https://github.com/withastro/starlight/commit/163bc848e173eecca92d1cb034045fdb42aa4ff1) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Exports the `StarlightPageProps` TypeScript type representing the props expected by the `<StarlightPage />` component.
|
|
24
|
+
|
|
25
|
+
- [#1504](https://github.com/withastro/starlight/pull/1504) [`fc83a05`](https://github.com/withastro/starlight/commit/fc83a05235b74be2bfe6ba8e7f95a8a5a618ead3) Thanks [@mingjunlu](https://github.com/mingjunlu)! - Adds Traditional Chinese UI translations
|
|
26
|
+
|
|
27
|
+
- [#1534](https://github.com/withastro/starlight/pull/1534) [`aada680`](https://github.com/withastro/starlight/commit/aada6805abc0068f07393585b86978ef5200439c) Thanks [@delucis](https://github.com/delucis)! - Improves DX of the `sidebar` prop used by the new `<StarlightPage>` component.
|
|
28
|
+
|
|
3
29
|
## 0.19.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/starlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
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.
|
|
175
|
+
"@vitest/coverage-v8": "^1.3.1",
|
|
176
176
|
"astro": "^4.3.5",
|
|
177
|
-
"vitest": "^1.
|
|
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.
|
|
185
|
+
"astro-expressive-code": "^0.33.2",
|
|
186
186
|
"bcp-47": "^2.1.0",
|
|
187
187
|
"hast-util-select": "^6.0.2",
|
|
188
188
|
"hastscript": "^8.0.0",
|
package/props.ts
CHANGED
package/translations/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ import ru from './ru.json';
|
|
|
24
24
|
import vi from './vi.json';
|
|
25
25
|
import uk from './uk.json';
|
|
26
26
|
import hi from './hi.json';
|
|
27
|
+
import zhTW from './zh-TW.json';
|
|
27
28
|
|
|
28
29
|
const { parse } = builtinI18nSchema();
|
|
29
30
|
|
|
@@ -54,5 +55,6 @@ export default Object.fromEntries(
|
|
|
54
55
|
vi,
|
|
55
56
|
uk,
|
|
56
57
|
hi,
|
|
58
|
+
'zh-TW': zhTW,
|
|
57
59
|
}).map(([key, dict]) => [key, parse(dict)])
|
|
58
60
|
);
|
package/translations/vi.json
CHANGED
|
@@ -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": "
|
|
23
|
-
"aside.tip": "
|
|
24
|
-
"aside.caution": "
|
|
25
|
-
"aside.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
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skipLink.label": "跳到內容",
|
|
3
|
+
"search.label": "搜尋",
|
|
4
|
+
"search.shortcutLabel": "(按 / 鍵開始搜尋)",
|
|
5
|
+
"search.cancelLabel": "取消",
|
|
6
|
+
"search.devWarning": "正式版本才能使用搜尋功能。\n如要在本地測試,請先建置並預覽網站。",
|
|
7
|
+
"themeSelect.accessibleLabel": "選擇佈景主題",
|
|
8
|
+
"themeSelect.dark": "深色",
|
|
9
|
+
"themeSelect.light": "淺色",
|
|
10
|
+
"themeSelect.auto": "自動",
|
|
11
|
+
"languageSelect.accessibleLabel": "選擇語言",
|
|
12
|
+
"menuButton.accessibleLabel": "選單",
|
|
13
|
+
"sidebarNav.accessibleLabel": "主要",
|
|
14
|
+
"tableOfContents.onThisPage": "本頁內容",
|
|
15
|
+
"tableOfContents.overview": "概述",
|
|
16
|
+
"i18n.untranslatedContent": "本頁內容尚未翻譯。",
|
|
17
|
+
"page.editLink": "編輯頁面",
|
|
18
|
+
"page.lastUpdated": "最後更新於:",
|
|
19
|
+
"page.previousLink": "前一則",
|
|
20
|
+
"page.nextLink": "下一則",
|
|
21
|
+
"404.text": "找不到頁面。請檢查網址或改用搜尋功能。",
|
|
22
|
+
"aside.note": "注意",
|
|
23
|
+
"aside.tip": "提示",
|
|
24
|
+
"aside.caution": "警告",
|
|
25
|
+
"aside.danger": "危險"
|
|
26
|
+
}
|
package/utils/error-map.ts
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
21
|
-
|
|
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 =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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 =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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
|
package/utils/starlight-page.ts
CHANGED
|
@@ -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 {
|
|
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';
|
|
@@ -9,6 +9,8 @@ import { slugToLocaleData, urlToSlug } from './slugs';
|
|
|
9
9
|
import { getPrevNextLinks, getSidebar } from './navigation';
|
|
10
10
|
import { useTranslations } from './translations';
|
|
11
11
|
import { docsSchema } from '../schema';
|
|
12
|
+
import { BadgeConfigSchema } from '../schemas/badge';
|
|
13
|
+
import { SidebarLinkItemHTMLAttributesSchema } from '../schemas/sidebar';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* The frontmatter schema for Starlight pages derived from the default schema for Starlight’s
|
|
@@ -56,6 +58,93 @@ type StarlightPageFrontmatter = Omit<
|
|
|
56
58
|
'editUrl' | 'sidebar'
|
|
57
59
|
> & { editUrl?: string | false };
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Link configuration schema for `<StarlightPage>`.
|
|
63
|
+
* Sets default values where possible to be more user friendly than raw `SidebarEntry` type.
|
|
64
|
+
*/
|
|
65
|
+
const LinkSchema = z
|
|
66
|
+
.object({
|
|
67
|
+
/** @deprecated Specifying `type` is no longer required. */
|
|
68
|
+
type: z.literal('link').default('link'),
|
|
69
|
+
label: z.string(),
|
|
70
|
+
href: z.string(),
|
|
71
|
+
isCurrent: z.boolean().default(false),
|
|
72
|
+
badge: BadgeConfigSchema(),
|
|
73
|
+
attrs: SidebarLinkItemHTMLAttributesSchema(),
|
|
74
|
+
})
|
|
75
|
+
// Make sure badge is in the object even if undefined — Zod doesn’t seem to have a way to set `undefined` as a default.
|
|
76
|
+
.transform((item) => ({ badge: undefined, ...item }));
|
|
77
|
+
|
|
78
|
+
/** Base schema for link groups without the recursive `items` array. */
|
|
79
|
+
const LinkGroupBase = z.object({
|
|
80
|
+
/** @deprecated Specifying `type` is no longer required. */
|
|
81
|
+
type: z.literal('group').default('group'),
|
|
82
|
+
label: z.string(),
|
|
83
|
+
collapsed: z.boolean().default(false),
|
|
84
|
+
badge: BadgeConfigSchema(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// These manual types are needed to correctly type the recursive link group type.
|
|
88
|
+
type ManualLinkGroupInput = Prettify<
|
|
89
|
+
z.input<typeof LinkGroupBase> &
|
|
90
|
+
// The original implementation of `<StarlightPage>` in v0.19.0 used `entries`.
|
|
91
|
+
// We want to use `items` so it matches the sidebar config in `astro.config.mjs`.
|
|
92
|
+
// Keeping `entries` support for now to not break anyone.
|
|
93
|
+
// TODO: warn about `entries` usage in a future version
|
|
94
|
+
// TODO: remove support for `entries` in a future version
|
|
95
|
+
(| {
|
|
96
|
+
/** Array of links and subcategories to display in this category. */
|
|
97
|
+
items: Array<z.input<typeof LinkSchema> | ManualLinkGroupInput>;
|
|
98
|
+
}
|
|
99
|
+
| {
|
|
100
|
+
/**
|
|
101
|
+
* @deprecated Use `items` instead of `entries`.
|
|
102
|
+
* Support for `entries` will be removed in a future version of Starlight.
|
|
103
|
+
*/
|
|
104
|
+
entries: Array<z.input<typeof LinkSchema> | ManualLinkGroupInput>;
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
>;
|
|
108
|
+
type ManualLinkGroupOutput = z.output<typeof LinkGroupBase> & {
|
|
109
|
+
entries: Array<z.output<typeof LinkSchema> | ManualLinkGroupOutput>;
|
|
110
|
+
badge: z.output<typeof LinkGroupBase>['badge'];
|
|
111
|
+
};
|
|
112
|
+
type LinkGroupSchemaType = z.ZodType<ManualLinkGroupOutput, z.ZodTypeDef, ManualLinkGroupInput>;
|
|
113
|
+
/**
|
|
114
|
+
* Link group configuration schema for `<StarlightPage>`.
|
|
115
|
+
* Sets default values where possible to be more user friendly than raw `SidebarEntry` type.
|
|
116
|
+
*/
|
|
117
|
+
const LinkGroupSchema: LinkGroupSchemaType = z.preprocess(
|
|
118
|
+
// Map `items` to `entries` as expected by the `SidebarEntry` type.
|
|
119
|
+
(arg) => {
|
|
120
|
+
if (arg && typeof arg === 'object' && 'items' in arg) {
|
|
121
|
+
const { items, ...rest } = arg;
|
|
122
|
+
return { ...rest, entries: items };
|
|
123
|
+
}
|
|
124
|
+
return arg;
|
|
125
|
+
},
|
|
126
|
+
LinkGroupBase.extend({
|
|
127
|
+
entries: z.lazy(() => z.union([LinkSchema, LinkGroupSchema]).array()),
|
|
128
|
+
})
|
|
129
|
+
// Make sure badge is in the object even if undefined.
|
|
130
|
+
.transform((item) => ({ badge: undefined, ...item }))
|
|
131
|
+
) as LinkGroupSchemaType;
|
|
132
|
+
|
|
133
|
+
/** Sidebar configuration schema for `<StarlightPage>` */
|
|
134
|
+
const StarlightPageSidebarSchema = z.union([LinkSchema, LinkGroupSchema]).array();
|
|
135
|
+
type StarlightPageSidebarUserConfig = z.input<typeof StarlightPageSidebarSchema>;
|
|
136
|
+
|
|
137
|
+
/** Parse sidebar prop to ensure all required defaults are in place. */
|
|
138
|
+
const normalizeSidebarProp = (
|
|
139
|
+
sidebarProp: StarlightPageSidebarUserConfig
|
|
140
|
+
): StarlightRouteData['sidebar'] => {
|
|
141
|
+
return parseWithFriendlyErrors(
|
|
142
|
+
StarlightPageSidebarSchema,
|
|
143
|
+
sidebarProp,
|
|
144
|
+
'Invalid sidebar prop passed to the `<StarlightPage/>` component.'
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
59
148
|
/**
|
|
60
149
|
* The props accepted by the `<StarlightPage/>` component.
|
|
61
150
|
*/
|
|
@@ -63,7 +152,8 @@ export type StarlightPageProps = Prettify<
|
|
|
63
152
|
// Remove the index signature from `Route`, omit undesired properties and make the rest optional.
|
|
64
153
|
Partial<Omit<RemoveIndexSignature<PageProps>, 'entry' | 'entryMeta' | 'id' | 'locale' | 'slug'>> &
|
|
65
154
|
// Add the sidebar definitions for a Starlight page.
|
|
66
|
-
Partial<Pick<StarlightRouteData, 'hasSidebar'
|
|
155
|
+
Partial<Pick<StarlightRouteData, 'hasSidebar'>> & {
|
|
156
|
+
sidebar?: StarlightPageSidebarUserConfig;
|
|
67
157
|
// And finally add the Starlight page frontmatter properties in a `frontmatter` property.
|
|
68
158
|
frontmatter: StarlightPageFrontmatter;
|
|
69
159
|
}
|
|
@@ -94,7 +184,9 @@ export async function generateStarlightPageRouteData({
|
|
|
94
184
|
const pageFrontmatter = await getStarlightPageFrontmatter(frontmatter);
|
|
95
185
|
const id = `${stripLeadingAndTrailingSlashes(slug)}.md`;
|
|
96
186
|
const localeData = slugToLocaleData(slug);
|
|
97
|
-
const sidebar = props.sidebar
|
|
187
|
+
const sidebar = props.sidebar
|
|
188
|
+
? normalizeSidebarProp(props.sidebar)
|
|
189
|
+
: getSidebar(url.pathname, localeData.locale);
|
|
98
190
|
const headings = props.headings ?? [];
|
|
99
191
|
const pageDocsEntry: StarlightPageDocsEntry = {
|
|
100
192
|
id,
|
|
@@ -172,16 +264,11 @@ async function getStarlightPageFrontmatter(frontmatter: StarlightPageFrontmatter
|
|
|
172
264
|
}),
|
|
173
265
|
});
|
|
174
266
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
'Invalid frontmatter props passed to the `<StarlightPage/>` component.'
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return pageFrontmatter.data;
|
|
267
|
+
return parseWithFriendlyErrors(
|
|
268
|
+
schema,
|
|
269
|
+
frontmatter,
|
|
270
|
+
'Invalid frontmatter props passed to the `<StarlightPage/>` component.'
|
|
271
|
+
);
|
|
185
272
|
}
|
|
186
273
|
|
|
187
274
|
/** Returns the user docs schema and falls back to the default schema if needed. */
|