@astrojs/starlight 0.31.0 → 0.32.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/components/Banner.astro +1 -3
  3. package/components/ContentNotice.astro +2 -2
  4. package/components/ContentPanel.astro +0 -4
  5. package/components/DraftContentNotice.astro +0 -1
  6. package/components/EditLink.astro +1 -2
  7. package/components/FallbackContentNotice.astro +0 -1
  8. package/components/Footer.astro +3 -5
  9. package/components/Head.astro +1 -2
  10. package/components/Header.astro +5 -6
  11. package/components/Hero.astro +1 -2
  12. package/components/Icons.ts +2 -0
  13. package/components/LanguageSelect.astro +2 -3
  14. package/components/LastUpdated.astro +1 -3
  15. package/components/MarkdownContent.astro +0 -1
  16. package/components/MobileMenuFooter.astro +3 -4
  17. package/components/MobileMenuToggle.astro +0 -1
  18. package/components/MobileTableOfContents.astro +1 -2
  19. package/components/Page.astro +32 -32
  20. package/components/PageFrame.astro +2 -3
  21. package/components/PageSidebar.astro +3 -5
  22. package/components/PageTitle.astro +1 -2
  23. package/components/Pagination.astro +1 -2
  24. package/components/Search.astro +17 -3
  25. package/components/Sidebar.astro +3 -5
  26. package/components/SidebarPersister.astro +1 -2
  27. package/components/SidebarSublist.astro +2 -1
  28. package/components/SiteTitle.astro +1 -2
  29. package/components/SkipLink.astro +0 -1
  30. package/components/SocialIcons.astro +0 -1
  31. package/components/StarlightPage.astro +7 -3
  32. package/components/TableOfContents.astro +1 -2
  33. package/components/ThemeProvider.astro +0 -1
  34. package/components/ThemeSelect.astro +0 -1
  35. package/components/TwoColumnContent.astro +1 -5
  36. package/index.ts +7 -9
  37. package/integrations/asides.ts +4 -7
  38. package/integrations/expressive-code/index.ts +15 -10
  39. package/integrations/shared/{pathToLocale.ts → absolutePathToLang.ts} +7 -5
  40. package/integrations/virtual-user-config.ts +27 -0
  41. package/locals.d.ts +26 -0
  42. package/locals.ts +36 -2
  43. package/package.json +5 -3
  44. package/props.ts +13 -1
  45. package/route-data.ts +11 -0
  46. package/routes/common.astro +5 -11
  47. package/routes/ssr/index.astro +1 -1
  48. package/routes/static/404.astro +1 -41
  49. package/routes/static/index.astro +1 -4
  50. package/schemas/hero.ts +2 -3
  51. package/schemas/pagefind.ts +97 -33
  52. package/types.ts +2 -0
  53. package/user-components/Card.astro +2 -2
  54. package/user-components/Icon.astro +2 -2
  55. package/user-components/LinkButton.astro +2 -2
  56. package/user-components/TabItem.astro +2 -2
  57. package/user-components/rehype-file-tree.ts +2 -2
  58. package/user-components/rehype-tabs.ts +3 -3
  59. package/utils/i18n.ts +0 -20
  60. package/utils/navigation.ts +19 -36
  61. package/utils/plugins.ts +316 -141
  62. package/utils/{route-data.ts → routing/data.ts} +56 -30
  63. package/utils/{routing.ts → routing/index.ts} +6 -44
  64. package/utils/routing/middleware.ts +81 -0
  65. package/utils/routing/types.ts +96 -0
  66. package/utils/slugs.ts +2 -10
  67. package/utils/starlight-page.ts +2 -10
  68. package/utils/user-config.ts +8 -0
  69. package/virtual-internal.d.ts +4 -0
package/utils/plugins.ts CHANGED
@@ -1,8 +1,15 @@
1
- import type { AstroIntegration, HookParameters } from 'astro';
1
+ import type { AstroIntegration, HookParameters as AstroHookParameters } from 'astro';
2
+ import { AstroError } from 'astro/errors';
2
3
  import { z } from 'astro/zod';
3
- import { StarlightConfigSchema, type StarlightUserConfig } from '../utils/user-config';
4
4
  import { parseWithFriendlyErrors } from '../utils/error-map';
5
+ import {
6
+ StarlightConfigSchema,
7
+ type StarlightConfig,
8
+ type StarlightUserConfig,
9
+ } from '../utils/user-config';
5
10
  import type { UserI18nSchema } from './translations';
11
+ import { createTranslationSystemFromFs } from './translations-fs';
12
+ import { absolutePathToLang as getAbsolutePathFromLang } from '../integrations/shared/absolutePathToLang';
6
13
 
7
14
  /**
8
15
  * Runs Starlight plugins in the order that they are configured after validating the user-provided
@@ -30,22 +37,61 @@ export async function runPlugins(
30
37
  'Invalid plugins config passed to starlight integration'
31
38
  );
32
39
 
33
- // A list of Astro integrations added by the various plugins.
34
- const integrations: AstroIntegration[] = [];
35
40
  // A list of translations injected by the various plugins keyed by locale.
36
41
  const pluginTranslations: PluginTranslations = {};
42
+ // A list of route middleware added by the various plugins.
43
+ const routeMiddlewareConfigs: Array<z.output<typeof routeMiddlewareConfigSchema>> = [];
44
+
45
+ for (const {
46
+ hooks: { 'i18n:setup': i18nSetup },
47
+ } of pluginsConfig) {
48
+ if (i18nSetup) {
49
+ await i18nSetup({
50
+ injectTranslations(translations) {
51
+ // Merge the translations injected by the plugin.
52
+ for (const [locale, localeTranslations] of Object.entries(translations)) {
53
+ pluginTranslations[locale] ??= {};
54
+ Object.assign(pluginTranslations[locale]!, localeTranslations);
55
+ }
56
+ },
57
+ });
58
+ }
59
+ }
60
+
61
+ const useTranslations = createTranslationSystemFromFs(
62
+ starlightConfig,
63
+ context.config,
64
+ pluginTranslations
65
+ );
66
+
67
+ function absolutePathToLang(path: string) {
68
+ return getAbsolutePathFromLang(path, { astroConfig: context.config, starlightConfig });
69
+ }
70
+
71
+ // A list of Astro integrations added by the various plugins.
72
+ const integrations: AstroIntegration[] = [];
37
73
 
38
74
  for (const {
39
75
  name,
40
- hooks: { setup },
76
+ hooks: { 'config:setup': configSetup, setup: deprecatedSetup },
41
77
  } of pluginsConfig) {
78
+ // A refinement in the schema ensures that at least one of the two hooks is defined.
79
+ const setup = (configSetup ?? deprecatedSetup)!;
80
+
42
81
  await setup({
43
82
  config: pluginsUserConfig ? { ...userConfig, plugins: pluginsUserConfig } : userConfig,
44
83
  updateConfig(newConfig) {
45
84
  // Ensure that plugins do not update the `plugins` config key.
46
85
  if ('plugins' in newConfig) {
47
- throw new Error(
48
- `The '${name}' plugin tried to update the 'plugins' config key which is not supported.`
86
+ throw new AstroError(
87
+ `The \`${name}\` plugin tried to update the \`plugins\` config key which is not supported.`
88
+ );
89
+ }
90
+ if ('routeMiddleware' in newConfig) {
91
+ throw new AstroError(
92
+ `The \`${name}\` plugin tried to update the \`routeMiddleware\` config key which is not supported.`,
93
+ 'Use the `addRouteMiddleware()` utility instead.\n' +
94
+ 'See https://starlight.astro.build/reference/plugins/#addroutemiddleware for more details.'
49
95
  );
50
96
  }
51
97
 
@@ -65,6 +111,9 @@ export async function runPlugins(
65
111
  // Collect any Astro integrations added by the plugin.
66
112
  integrations.push(integration);
67
113
  },
114
+ addRouteMiddleware(middlewareConfig) {
115
+ routeMiddlewareConfigs.push(middlewareConfig);
116
+ },
68
117
  astroConfig: {
69
118
  ...context.config,
70
119
  integrations: [...context.config.integrations, ...integrations],
@@ -72,22 +121,37 @@ export async function runPlugins(
72
121
  command: context.command,
73
122
  isRestart: context.isRestart,
74
123
  logger: context.logger.fork(name),
75
- injectTranslations(translations) {
76
- // Merge the translations injected by the plugin.
77
- for (const [locale, localeTranslations] of Object.entries(translations)) {
78
- pluginTranslations[locale] ??= {};
79
- Object.assign(pluginTranslations[locale]!, localeTranslations);
80
- }
81
- },
124
+ useTranslations,
125
+ absolutePathToLang,
82
126
  });
83
127
  }
84
128
 
85
- return { integrations, starlightConfig, pluginTranslations };
129
+ applyPluginMiddleware(routeMiddlewareConfigs, starlightConfig);
130
+
131
+ return { integrations, starlightConfig, pluginTranslations, useTranslations, absolutePathToLang };
132
+ }
133
+
134
+ /** Updates `routeMiddleware` in the Starlight config to add plugin middlewares in the correct order. */
135
+ function applyPluginMiddleware(
136
+ routeMiddlewareConfigs: { entrypoint: string; order: 'default' | 'pre' | 'post' }[],
137
+ starlightConfig: StarlightConfig
138
+ ) {
139
+ const middlewareBuckets = routeMiddlewareConfigs.reduce<
140
+ Record<'pre' | 'default' | 'post', string[]>
141
+ >(
142
+ (buckets, { entrypoint, order = 'default' }) => {
143
+ buckets[order].push(entrypoint);
144
+ return buckets;
145
+ },
146
+ { pre: [], default: [], post: [] }
147
+ );
148
+ starlightConfig.routeMiddleware.unshift(...middlewareBuckets.pre);
149
+ starlightConfig.routeMiddleware.push(...middlewareBuckets.default, ...middlewareBuckets.post);
86
150
  }
87
151
 
88
152
  export function injectPluginTranslationsTypes(
89
153
  translations: PluginTranslations,
90
- injectTypes: HookParameters<'astro:config:done'>['injectTypes']
154
+ injectTypes: AstroHookParameters<'astro:config:done'>['injectTypes']
91
155
  ) {
92
156
  const allKeys = new Set<string>();
93
157
 
@@ -118,140 +182,246 @@ const astroIntegrationSchema = z.object({
118
182
  hooks: z.object({}).passthrough().default({}),
119
183
  }) as z.Schema<AstroIntegration>;
120
184
 
185
+ const routeMiddlewareConfigSchema = z.object({
186
+ entrypoint: z.string(),
187
+ order: z.enum(['pre', 'post', 'default']).default('default'),
188
+ });
189
+
121
190
  const baseStarlightPluginSchema = z.object({
122
191
  /** Name of the Starlight plugin. */
123
192
  name: z.string(),
124
193
  });
125
194
 
195
+ const configSetupHookSchema = z
196
+ .function(
197
+ z.tuple([
198
+ z.object({
199
+ /**
200
+ * A read-only copy of the user-supplied Starlight configuration.
201
+ *
202
+ * Note that this configuration may have been updated by other plugins configured
203
+ * before this one.
204
+ */
205
+ config: z.any() as z.Schema<
206
+ // The configuration passed to plugins should contains the list of plugins.
207
+ StarlightUserConfig & { plugins?: z.input<typeof baseStarlightPluginSchema>[] }
208
+ >,
209
+ /**
210
+ * A callback function to update the user-supplied Starlight configuration.
211
+ *
212
+ * You only need to provide the configuration values that you want to update but no deep
213
+ * merge is performed.
214
+ *
215
+ * @example
216
+ * {
217
+ * name: 'My Starlight Plugin',
218
+ * hooks: {
219
+ * 'config:setup'({ updateConfig }) {
220
+ * updateConfig({
221
+ * description: 'Custom description',
222
+ * });
223
+ * }
224
+ * }
225
+ * }
226
+ */
227
+ updateConfig: z.function(
228
+ z.tuple([
229
+ z.record(z.any()) as z.Schema<Partial<Omit<StarlightUserConfig, 'routeMiddleware'>>>,
230
+ ]),
231
+ z.void()
232
+ ),
233
+ /**
234
+ * A callback function to add an Astro integration required by this plugin.
235
+ *
236
+ * @see https://docs.astro.build/en/reference/integrations-reference/
237
+ *
238
+ * @example
239
+ * {
240
+ * name: 'My Starlight Plugin',
241
+ * hooks: {
242
+ * 'config:setup'({ addIntegration }) {
243
+ * addIntegration({
244
+ * name: 'My Plugin Astro Integration',
245
+ * hooks: {
246
+ * 'astro:config:setup': () => {
247
+ * // …
248
+ * },
249
+ * },
250
+ * });
251
+ * }
252
+ * }
253
+ * }
254
+ */
255
+ addIntegration: z.function(z.tuple([astroIntegrationSchema]), z.void()),
256
+ /**
257
+ * A callback function to register additional route middleware handlers.
258
+ *
259
+ * If the order of execution is important, a plugin can use the `order` option to enforce
260
+ * running first or last.
261
+ *
262
+ * @example
263
+ * {
264
+ * name: 'My Starlight Plugin',
265
+ * hooks: {
266
+ * setup({ addRouteMiddleware }) {
267
+ * addRouteMiddleware({ entrypoint: '@me/my-plugin/route-middleware' });
268
+ * },
269
+ * },
270
+ * }
271
+ */
272
+ addRouteMiddleware: z.function(z.tuple([routeMiddlewareConfigSchema]), z.void()),
273
+ /**
274
+ * A read-only copy of the user-supplied Astro configuration.
275
+ *
276
+ * Note that this configuration is resolved before any other integrations have run.
277
+ *
278
+ * @see https://docs.astro.build/en/reference/integrations-reference/#config-option
279
+ */
280
+ astroConfig: z.any() as z.Schema<StarlightPluginContext['config']>,
281
+ /**
282
+ * The command used to run Starlight.
283
+ *
284
+ * @see https://docs.astro.build/en/reference/integrations-reference/#command-option
285
+ */
286
+ command: z.any() as z.Schema<StarlightPluginContext['command']>,
287
+ /**
288
+ * `false` when the dev server starts, `true` when a reload is triggered.
289
+ *
290
+ * @see https://docs.astro.build/en/reference/integrations-reference/#isrestart-option
291
+ */
292
+ isRestart: z.any() as z.Schema<StarlightPluginContext['isRestart']>,
293
+ /**
294
+ * An instance of the Astro integration logger with all logged messages prefixed with the
295
+ * plugin name.
296
+ *
297
+ * @see https://docs.astro.build/en/reference/integrations-reference/#astrointegrationlogger
298
+ */
299
+ logger: z.any() as z.Schema<StarlightPluginContext['logger']>,
300
+ /**
301
+ * A callback function to generate a utility function to access UI strings for a given
302
+ * language.
303
+ *
304
+ * @see https://starlight.astro.build/guides/i18n/#using-ui-translations
305
+ *
306
+ * @example
307
+ * {
308
+ * name: 'My Starlight Plugin',
309
+ * hooks: {
310
+ * 'config:setup'({ useTranslations, logger }) {
311
+ * const t = useTranslations('en');
312
+ * logger.info(t('builtWithStarlight.label'));
313
+ * // ^ Logs 'Built with Starlight' to the console.
314
+ * }
315
+ * }
316
+ * }
317
+ */
318
+ useTranslations: z.any() as z.Schema<ReturnType<typeof createTranslationSystemFromFs>>,
319
+ /**
320
+ * A callback function to get the language for a given absolute file path. The returned
321
+ * language can be used with the `useTranslations` helper to get UI strings for that
322
+ * language.
323
+ *
324
+ * This can be particularly useful in remark or rehype plugins to get the language for
325
+ * the current file being processed and use it to get the appropriate UI strings for that
326
+ * language.
327
+ *
328
+ * @example
329
+ * {
330
+ * name: 'My Starlight Plugin',
331
+ * hooks: {
332
+ * 'config:setup'({ absolutePathToLang, useTranslations, logger }) {
333
+ * const lang = absolutePathToLang('/absolute/path/to/project/src/content/docs/fr/index.mdx');
334
+ * const t = useTranslations(lang);
335
+ * logger.info(t('aside.tip'));
336
+ * // ^ Logs 'Astuce' to the console.
337
+ * }
338
+ * }
339
+ * }
340
+ */
341
+ absolutePathToLang: z.function(z.tuple([z.string()]), z.string()),
342
+ }),
343
+ ]),
344
+ z.union([z.void(), z.promise(z.void())])
345
+ )
346
+ .optional();
347
+
126
348
  /**
127
349
  * A plugin `config` and `updateConfig` argument are purposely not validated using the Starlight
128
350
  * user config schema but properly typed for user convenience because we do not want to run any of
129
351
  * the Zod `transform`s used in the user config schema when running plugins.
130
352
  */
131
- const starlightPluginSchema = baseStarlightPluginSchema.extend({
132
- /** The different hooks available to the plugin. */
133
- hooks: z.object({
134
- /**
135
- * Plugin setup function called with an object containing various values that can be used by
136
- * the plugin to interact with Starlight.
137
- */
138
- setup: z.function(
139
- z.tuple([
140
- z.object({
141
- /**
142
- * A read-only copy of the user-supplied Starlight configuration.
143
- *
144
- * Note that this configuration may have been updated by other plugins configured
145
- * before this one.
146
- */
147
- config: z.any() as z.Schema<
148
- // The configuration passed to plugins should contains the list of plugins.
149
- StarlightUserConfig & { plugins?: z.input<typeof baseStarlightPluginSchema>[] }
150
- >,
151
- /**
152
- * A callback function to update the user-supplied Starlight configuration.
153
- *
154
- * You only need to provide the configuration values that you want to update but no deep
155
- * merge is performed.
156
- *
157
- * @example
158
- * {
159
- * name: 'My Starlight Plugin',
160
- * hooks: {
161
- * setup({ updateConfig }) {
162
- * updateConfig({
163
- * description: 'Custom description',
164
- * });
165
- * }
166
- * }
167
- * }
168
- */
169
- updateConfig: z.function(
170
- z.tuple([z.record(z.any()) as z.Schema<Partial<StarlightUserConfig>>]),
171
- z.void()
172
- ),
173
- /**
174
- * A callback function to add an Astro integration required by this plugin.
175
- *
176
- * @see https://docs.astro.build/en/reference/integrations-reference/
177
- *
178
- * @example
179
- * {
180
- * name: 'My Starlight Plugin',
181
- * hooks: {
182
- * setup({ addIntegration }) {
183
- * addIntegration({
184
- * name: 'My Plugin Astro Integration',
185
- * hooks: {
186
- * 'astro:config:setup': () => {
187
- * //
188
- * },
189
- * },
190
- * });
191
- * }
192
- * }
193
- * }
194
- */
195
- addIntegration: z.function(z.tuple([astroIntegrationSchema]), z.void()),
196
- /**
197
- * A read-only copy of the user-supplied Astro configuration.
198
- *
199
- * Note that this configuration is resolved before any other integrations have run.
200
- *
201
- * @see https://docs.astro.build/en/reference/integrations-reference/#config-option
202
- */
203
- astroConfig: z.any() as z.Schema<StarlightPluginContext['config']>,
204
- /**
205
- * The command used to run Starlight.
206
- *
207
- * @see https://docs.astro.build/en/reference/integrations-reference/#command-option
208
- */
209
- command: z.any() as z.Schema<StarlightPluginContext['command']>,
210
- /**
211
- * `false` when the dev server starts, `true` when a reload is triggered.
212
- *
213
- * @see https://docs.astro.build/en/reference/integrations-reference/#isrestart-option
214
- */
215
- isRestart: z.any() as z.Schema<StarlightPluginContext['isRestart']>,
216
- /**
217
- * An instance of the Astro integration logger with all logged messages prefixed with the
218
- * plugin name.
219
- *
220
- * @see https://docs.astro.build/en/reference/integrations-reference/#astrointegrationlogger
221
- */
222
- logger: z.any() as z.Schema<StarlightPluginContext['logger']>,
223
- /**
224
- * A callback function to add or update translations strings.
225
- *
226
- * @see https://starlight.astro.build/guides/i18n/#extend-translation-schema
227
- *
228
- * @example
229
- * {
230
- * name: 'My Starlight Plugin',
231
- * hooks: {
232
- * setup({ injectTranslations }) {
233
- * injectTranslations({
234
- * en: {
235
- * 'myPlugin.doThing': 'Do the thing',
236
- * },
237
- * fr: {
238
- * 'myPlugin.doThing': 'Faire le truc',
239
- * },
240
- * });
241
- * }
242
- * }
243
- * }
244
- */
245
- injectTranslations: z.function(
246
- z.tuple([z.record(z.string(), z.record(z.string(), z.string()))]),
247
- z.void()
248
- ),
249
- }),
250
- ]),
251
- z.union([z.void(), z.promise(z.void())])
252
- ),
253
- }),
254
- });
353
+ const starlightPluginSchema = baseStarlightPluginSchema
354
+ .extend({
355
+ /** The different hooks available to the plugin. */
356
+ hooks: z.object({
357
+ /**
358
+ * Plugin internationalization setup function allowing to inject translations strings for the
359
+ * plugin in various locales. These translations will be available in the `config:setup` hook
360
+ * and plugin UI.
361
+ */
362
+ 'i18n:setup': z
363
+ .function(
364
+ z.tuple([
365
+ z.object({
366
+ /**
367
+ * A callback function to add or update translations strings.
368
+ *
369
+ * @see https://starlight.astro.build/guides/i18n/#extend-translation-schema
370
+ *
371
+ * @example
372
+ * {
373
+ * name: 'My Starlight Plugin',
374
+ * hooks: {
375
+ * 'i18n:setup'({ injectTranslations }) {
376
+ * injectTranslations({
377
+ * en: {
378
+ * 'myPlugin.doThing': 'Do the thing',
379
+ * },
380
+ * fr: {
381
+ * 'myPlugin.doThing': 'Faire le truc',
382
+ * },
383
+ * });
384
+ * }
385
+ * }
386
+ * }
387
+ */
388
+ injectTranslations: z.function(
389
+ z.tuple([z.record(z.string(), z.record(z.string(), z.string()))]),
390
+ z.void()
391
+ ),
392
+ }),
393
+ ]),
394
+ z.union([z.void(), z.promise(z.void())])
395
+ )
396
+ .optional(),
397
+ /**
398
+ * Plugin configuration setup function called with an object containing various values that
399
+ * can be used by the plugin to interact with Starlight.
400
+ */
401
+ 'config:setup': configSetupHookSchema,
402
+ /**
403
+ * @deprecated Use the `config:setup` hook instead as `setup` will be removed in a future
404
+ * version.
405
+ */
406
+ setup: configSetupHookSchema,
407
+ }),
408
+ })
409
+ .superRefine((plugin, ctx) => {
410
+ if (!plugin.hooks['config:setup'] && !plugin.hooks.setup) {
411
+ ctx.addIssue({
412
+ code: z.ZodIssueCode.custom,
413
+ message: 'A plugin must define at least a `config:setup` hook.',
414
+ });
415
+ } else if (plugin.hooks['config:setup'] && plugin.hooks.setup) {
416
+ ctx.addIssue({
417
+ code: z.ZodIssueCode.custom,
418
+ message:
419
+ 'A plugin cannot define both a `config:setup` and `setup` hook. ' +
420
+ 'As `setup` is deprecated and will be removed in a future version, ' +
421
+ 'consider using `config:setup` instead.',
422
+ });
423
+ }
424
+ });
255
425
 
256
426
  const starlightPluginsConfigSchema = z.array(starlightPluginSchema).default([]);
257
427
 
@@ -259,6 +429,11 @@ type StarlightPluginsUserConfig = z.input<typeof starlightPluginsConfigSchema>;
259
429
 
260
430
  export type StarlightPlugin = z.input<typeof starlightPluginSchema>;
261
431
 
432
+ export type HookParameters<
433
+ Hook extends keyof StarlightPlugin['hooks'],
434
+ HookFn = StarlightPlugin['hooks'][Hook],
435
+ > = HookFn extends (...args: any) => any ? Parameters<HookFn>[0] : never;
436
+
262
437
  export type StarlightUserConfigWithPlugins = StarlightUserConfig & {
263
438
  /**
264
439
  * A list of plugins to extend Starlight with.
@@ -273,7 +448,7 @@ export type StarlightUserConfigWithPlugins = StarlightUserConfig & {
273
448
  };
274
449
 
275
450
  export type StarlightPluginContext = Pick<
276
- Parameters<NonNullable<AstroIntegration['hooks']['astro:config:setup']>>[0],
451
+ AstroHookParameters<'astro:config:setup'>,
277
452
  'command' | 'config' | 'isRestart' | 'logger'
278
453
  >;
279
454
 
@@ -1,39 +1,34 @@
1
- import type { MarkdownHeading } from 'astro';
1
+ import type { APIContext, MarkdownHeading } from 'astro';
2
+ import project from 'virtual:starlight/project-context';
2
3
  import config from 'virtual:starlight/user-config';
3
- import { generateToC, type TocItem } from './generateToC';
4
+ import { generateToC } from '../generateToC';
4
5
  import { getNewestCommitDate } from 'virtual:starlight/git-info';
5
- import { getPrevNextLinks, getSidebar, type SidebarEntry } from './navigation';
6
- import { ensureTrailingSlash } from './path';
7
- import type { Route } from './routing';
8
- import { formatPath } from './format-path';
9
- import { useTranslations } from './translations';
10
- import { DeprecatedLabelsPropProxy } from './i18n';
6
+ import { getPrevNextLinks, getSidebar } from '../navigation';
7
+ import { ensureTrailingSlash } from '../path';
8
+ import { getRouteBySlugParam, normalizeCollectionEntry } from '../routing';
9
+ import type {
10
+ Route,
11
+ StarlightDocsCollectionEntry,
12
+ StarlightDocsEntry,
13
+ StarlightRouteData,
14
+ } from './types';
15
+ import { formatPath } from '../format-path';
16
+ import { useTranslations } from '../translations';
17
+ import { BuiltInDefaultLocale } from '../i18n';
18
+ import { getEntry, render } from 'astro:content';
19
+ import { getCollectionPathFromRoot } from '../collection';
11
20
 
12
21
  export interface PageProps extends Route {
13
22
  headings: MarkdownHeading[];
14
23
  }
15
24
 
16
- export interface StarlightRouteData extends Route {
17
- /** Title of the site. */
18
- siteTitle: string;
19
- /** URL or path used as the link when clicking on the site title. */
20
- siteTitleHref: string;
21
- /** Array of Markdown headings extracted from the current page. */
22
- headings: MarkdownHeading[];
23
- /** Site navigation sidebar entries for this page. */
24
- sidebar: SidebarEntry[];
25
- /** Whether or not the sidebar should be displayed on this page. */
26
- hasSidebar: boolean;
27
- /** Links to the previous and next page in the sidebar if enabled. */
28
- pagination: ReturnType<typeof getPrevNextLinks>;
29
- /** Table of contents for this page if enabled. */
30
- toc: { minHeadingLevel: number; maxHeadingLevel: number; items: TocItem[] } | undefined;
31
- /** JS Date object representing when this page was last updated if enabled. */
32
- lastUpdated: Date | undefined;
33
- /** URL object for the address where this page can be edited if enabled. */
34
- editUrl: URL | undefined;
35
- /** @deprecated Use `Astro.locals.t()` instead. */
36
- labels: Record<string, never>;
25
+ export async function useRouteData(context: APIContext): Promise<StarlightRouteData> {
26
+ const route =
27
+ ('slug' in context.params && getRouteBySlugParam(context.params.slug)) ||
28
+ (await get404Route(context.locals));
29
+ const { Content, headings } = await render(route.entry);
30
+ const routeData = generateRouteData({ props: { ...route, headings }, url: context.url });
31
+ return { ...routeData, Content };
37
32
  }
38
33
 
39
34
  export function generateRouteData({
@@ -56,7 +51,6 @@ export function generateRouteData({
56
51
  toc: getToC(props),
57
52
  lastUpdated: getLastUpdated(props),
58
53
  editUrl: getEditUrl(props),
59
- labels: DeprecatedLabelsPropProxy,
60
54
  };
61
55
  }
62
56
 
@@ -121,3 +115,35 @@ export function getSiteTitle(lang: string): string {
121
115
  export function getSiteTitleHref(locale: string | undefined): string {
122
116
  return formatPath(locale || '/');
123
117
  }
118
+
119
+ /** Generate a route object for Starlight’s 404 page. */
120
+ async function get404Route(locals: App.Locals): Promise<Route> {
121
+ const { lang = BuiltInDefaultLocale.lang, dir = BuiltInDefaultLocale.dir } =
122
+ config.defaultLocale || {};
123
+ let locale = config.defaultLocale?.locale;
124
+ if (locale === 'root') locale = undefined;
125
+
126
+ const entryMeta = { dir, lang, locale };
127
+
128
+ const fallbackEntry: StarlightDocsEntry = {
129
+ slug: '404',
130
+ id: '404',
131
+ body: '',
132
+ collection: 'docs',
133
+ data: {
134
+ title: '404',
135
+ template: 'splash',
136
+ editUrl: false,
137
+ head: [],
138
+ hero: { tagline: locals.t('404.text'), actions: [] },
139
+ pagefind: false,
140
+ sidebar: { hidden: false, attrs: {} },
141
+ draft: false,
142
+ },
143
+ filePath: `${getCollectionPathFromRoot('docs', project)}/404.md`,
144
+ };
145
+
146
+ const userEntry = (await getEntry('docs', '404')) as StarlightDocsCollectionEntry;
147
+ const entry = userEntry ? normalizeCollectionEntry(userEntry) : fallbackEntry;
148
+ return { ...entryMeta, entryMeta, entry, id: entry.id, slug: entry.slug };
149
+ }