@astrojs/starlight 0.29.3 → 0.30.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,74 @@
1
1
  # @astrojs/starlight
2
2
 
3
+ ## 0.30.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2688](https://github.com/withastro/starlight/pull/2688) [`5c6996c`](https://github.com/withastro/starlight/commit/5c6996cd248e9da735a14e7fcaf638b51f2796bc) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes an issue with autogenerated sidebars when using Starlight with Astro's new Content Layer API where group names would be sluggified.
8
+
9
+ ## 0.30.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#2612](https://github.com/withastro/starlight/pull/2612) [`8d5a4e8`](https://github.com/withastro/starlight/commit/8d5a4e8000d9e3a4bb9ca8178767cf3d8bc48773) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds support for Astro v5, drops support for Astro v4.
14
+
15
+ #### Upgrade Astro and dependencies
16
+
17
+ ⚠️ **BREAKING CHANGE:** Astro v4 is no longer supported. Make sure you [update Astro](https://docs.astro.build/en/guides/upgrade-to/v5/) and any other official integrations at the same time as updating Starlight:
18
+
19
+ ```sh
20
+ npx @astrojs/upgrade
21
+ ```
22
+
23
+ _Community Starlight plugins and Astro integrations may also need to be manually updated to work with Astro v5. If you encounter any issues, please reach out to the plugin or integration author to see if it is a known issue or if an updated version is being worked on._
24
+
25
+ #### Update your collections
26
+
27
+ ⚠️ **BREAKING CHANGE:** Starlight's internal [content collections](https://docs.astro.build/en/guides/content-collections/), which organize, validate, and render your content, have been updated to use Astro's new Content Layer API and require configuration changes in your project.
28
+
29
+ 1. **Move the content config file.** This file no longer lives within the `src/content/config.ts` folder and should now exist at `src/content.config.ts`.
30
+ 1. **Edit the collection definition(s).** To update the `docs` collection, a `loader` is now required:
31
+
32
+ ```diff
33
+ // src/content.config.ts
34
+ import { defineCollection } from "astro:content";
35
+ +import { docsLoader } from "@astrojs/starlight/loaders";
36
+ import { docsSchema } from "@astrojs/starlight/schema";
37
+
38
+ export const collections = {
39
+ - docs: defineCollection({ schema: docsSchema() }),
40
+ + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
41
+ };
42
+ ```
43
+
44
+ If you are using the [`i18n` collection](https://starlight.astro.build/guides/i18n/#translate-starlights-ui) to provide translations for additional languages you support or override our default labels, you will need to update the collection definition in a similar way and remove the collection `type` which is no longer available:
45
+
46
+ ```diff
47
+ // src/content.config.ts
48
+ import { defineCollection } from "astro:content";
49
+ +import { docsLoader, i18nLoader } from "@astrojs/starlight/loaders";
50
+ import { docsSchema, i18nSchema } from "@astrojs/starlight/schema";
51
+
52
+ export const collections = {
53
+ - docs: defineCollection({ schema: docsSchema() }),
54
+ + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
55
+ - i18n: defineCollection({ type: 'data', schema: i18nSchema() }),
56
+ + i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),
57
+ };
58
+ ```
59
+
60
+ 1. **Update other collections.** To update any other collections you may have, follow the [“Updating existing collections”](https://docs.astro.build/en/guides/upgrade-to/v5/#updating-existing-collections) section in the Astro 5 upgrade guide.
61
+
62
+ If you are unable to make any changes to your collections at this time, including Starlight's default `docs` and `i18n` collections, you can enable the [`legacy.collections` flag](https://docs.astro.build/en/reference/legacy-flags/) to upgrade to v5 without updating your collections. This legacy flag exists to provide temporary backwards compatibility, and will allow you to keep your collections in their current state until the legacy flag is no longer supported.
63
+
64
+ ### Patch Changes
65
+
66
+ - [#2669](https://github.com/withastro/starlight/pull/2669) [`310df7d`](https://github.com/withastro/starlight/commit/310df7d6b01f5c4a56540bdba9243fb60dace323) Thanks [@aaronperezaguilera](https://github.com/aaronperezaguilera)! - Adds Catalan UI translations
67
+
68
+ - [#2664](https://github.com/withastro/starlight/pull/2664) [`62ff007`](https://github.com/withastro/starlight/commit/62ff0074d9a3f82e46f5c62db85c04d87ff5e931) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Publishes provenance containing verifiable data to link a package back to its source repository and the specific build instructions used to publish it.
69
+
70
+ - [#2670](https://github.com/withastro/starlight/pull/2670) [`0223b42`](https://github.com/withastro/starlight/commit/0223b425249f8d1fa468e367c632467276c9c208) Thanks [@aaronperezaguilera](https://github.com/aaronperezaguilera)! - Adds Spanish UI translations for the Pagefind search modal
71
+
3
72
  ## 0.29.3
4
73
 
5
74
  ### Patch Changes
@@ -35,16 +35,14 @@ const pagefindEnabled =
35
35
  Astro.props.entry.slug !== '404' &&
36
36
  !Astro.props.entry.slug.endsWith('/404') &&
37
37
  Astro.props.entry.data.pagefind !== false;
38
+
39
+ const dataAttributes: DOMStringMap = { 'data-theme': 'dark' };
40
+ if (Boolean(Astro.props.toc)) dataAttributes['data-has-toc'] = '';
41
+ if (Astro.props.hasSidebar) dataAttributes['data-has-sidebar'] = '';
42
+ if (Boolean(Astro.props.entry.data.hero)) dataAttributes['data-has-hero'] = '';
38
43
  ---
39
44
 
40
- <html
41
- lang={Astro.props.lang}
42
- dir={Astro.props.dir}
43
- data-has-toc={Boolean(Astro.props.toc)}
44
- data-has-sidebar={Astro.props.hasSidebar}
45
- data-has-hero={Boolean(Astro.props.entry.data.hero)}
46
- data-theme="dark"
47
- >
45
+ <html lang={Astro.props.lang} dir={Astro.props.dir} {...dataAttributes}>
48
46
  <head>
49
47
  <Head {...Astro.props} />
50
48
  <style>
@@ -12,12 +12,12 @@ const pagefindTranslations = {
12
12
  .map(([key, value]) => [key.replace('pagefind.', ''), value])
13
13
  ),
14
14
  };
15
+
16
+ const dataAttributes: DOMStringMap = { 'data-translations': JSON.stringify(pagefindTranslations) };
17
+ if (project.trailingSlash === 'never') dataAttributes['data-strip-trailing-slash'] = '';
15
18
  ---
16
19
 
17
- <site-search
18
- data-translations={JSON.stringify(pagefindTranslations)}
19
- data-strip-trailing-slash={project.trailingSlash === 'never'}
20
- >
20
+ <site-search class={Astro.props.class} {...dataAttributes}>
21
21
  <button
22
22
  data-open-modal
23
23
  disabled
package/index.ts CHANGED
@@ -137,9 +137,6 @@ export default function StarlightIntegration(
137
137
  scopedStyleStrategy: 'where',
138
138
  // If not already configured, default to prefetching all links on hover.
139
139
  prefetch: config.prefetch ?? { prefetchAll: true },
140
- experimental: {
141
- globalRoutePriority: true,
142
- },
143
140
  i18n: astroI18nConfig,
144
141
  });
145
142
  },
@@ -1,5 +1,6 @@
1
1
  import type { AstroConfig } from 'astro';
2
2
  import type { StarlightConfig } from '../../types';
3
+ import { getCollectionPath } from '../../utils/collection';
3
4
  import { slugToLocale } from './slugToLocale';
4
5
 
5
6
  /** Get current locale from the full file path. */
@@ -13,15 +14,14 @@ export function pathToLocale(
13
14
  astroConfig: { root: AstroConfig['root']; srcDir: AstroConfig['srcDir'] };
14
15
  }
15
16
  ): string | undefined {
16
- const srcDir = new URL(astroConfig.srcDir, astroConfig.root);
17
- const docsDir = new URL('content/docs/', srcDir);
17
+ const docsPath = getCollectionPath('docs', astroConfig.srcDir);
18
18
  // Format path to unix style path.
19
19
  path = path?.replace(/\\/g, '/');
20
20
  // Ensure that the page path starts with a slash if the docs directory also does,
21
21
  // which makes stripping the docs path in the next step work on Windows, too.
22
- if (path && !path.startsWith('/') && docsDir.pathname.startsWith('/')) path = '/' + path;
22
+ if (path && !path.startsWith('/') && docsPath.startsWith('/')) path = '/' + path;
23
23
  // Strip docs path leaving only content collection file ID.
24
24
  // Example: /Users/houston/repo/src/content/docs/en/guide.md => en/guide.md
25
- const slug = path?.replace(docsDir.pathname, '');
25
+ const slug = path?.replace(docsPath, '');
26
26
  return slugToLocale(slug, starlightConfig);
27
27
  }
@@ -1,6 +1,8 @@
1
1
  import type { AstroConfig, HookParameters, ViteUserConfig } from 'astro';
2
+ import { existsSync } from 'node:fs';
2
3
  import { resolve } from 'node:path';
3
4
  import { fileURLToPath } from 'node:url';
5
+ import { resolveCollectionPath } from '../utils/collection';
4
6
  import type { StarlightConfig } from '../utils/user-config';
5
7
  import { getAllNewestCommitDate } from '../utils/git';
6
8
  import type { PluginTranslations } from '../utils/plugins';
@@ -15,11 +17,13 @@ export function vitePluginStarlightUserConfig(
15
17
  opts: StarlightConfig,
16
18
  {
17
19
  build,
20
+ legacy,
18
21
  root,
19
22
  srcDir,
20
23
  trailingSlash,
21
24
  }: Pick<AstroConfig, 'root' | 'srcDir' | 'trailingSlash'> & {
22
25
  build: Pick<AstroConfig['build'], 'format'>;
26
+ legacy: Pick<AstroConfig['legacy'], 'collections'>;
23
27
  },
24
28
  pluginTranslations: PluginTranslations
25
29
  ): NonNullable<ViteUserConfig['plugins']>[number] {
@@ -42,7 +46,19 @@ export function vitePluginStarlightUserConfig(
42
46
  const resolveLocalPath = (path: string) =>
43
47
  JSON.stringify(fileURLToPath(new URL(path, import.meta.url)));
44
48
 
45
- const docsPath = resolve(fileURLToPath(srcDir), 'content/docs');
49
+ const rootPath = fileURLToPath(root);
50
+ const docsPath = resolveCollectionPath('docs', srcDir);
51
+
52
+ let collectionConfigImportPath = resolve(
53
+ fileURLToPath(srcDir),
54
+ legacy.collections ? './content/config.ts' : './content.config.ts'
55
+ );
56
+ // If not using legacy collections and the config doesn't exist, fallback to the legacy location.
57
+ // We need to test this ahead of time as we cannot `try/catch` a failing import in the virtual
58
+ // module as this would fail at build time when Rollup tries to resolve a non-existent path.
59
+ if (!legacy.collections && !existsSync(collectionConfigImportPath)) {
60
+ collectionConfigImportPath = resolve(fileURLToPath(srcDir), './content/config.ts');
61
+ }
46
62
 
47
63
  const virtualComponentModules = Object.fromEntries(
48
64
  Object.entries(opts.components).map(([name, path]) => [
@@ -56,6 +72,7 @@ export function vitePluginStarlightUserConfig(
56
72
  'virtual:starlight/user-config': `export default ${JSON.stringify(opts)}`,
57
73
  'virtual:starlight/project-context': `export default ${JSON.stringify({
58
74
  build: { format: build.format },
75
+ legacyCollections: legacy.collections,
59
76
  root,
60
77
  srcDir,
61
78
  trailingSlash,
@@ -63,9 +80,9 @@ export function vitePluginStarlightUserConfig(
63
80
  'virtual:starlight/git-info':
64
81
  (command !== 'build'
65
82
  ? `import { makeAPI } from ${resolveLocalPath('../utils/git.ts')};` +
66
- `const api = makeAPI(${JSON.stringify(docsPath)});`
83
+ `const api = makeAPI(${JSON.stringify(rootPath)});`
67
84
  : `import { makeAPI } from ${resolveLocalPath('../utils/gitInlined.ts')};` +
68
- `const api = makeAPI(${JSON.stringify(getAllNewestCommitDate(docsPath))});`) +
85
+ `const api = makeAPI(${JSON.stringify(getAllNewestCommitDate(rootPath, docsPath))});`) +
69
86
  'export const getNewestCommitDate = api.getNewestCommitDate;',
70
87
  'virtual:starlight/user-css': opts.customCss.map((id) => `import ${resolveId(id)};`).join(''),
71
88
  'virtual:starlight/user-images': opts.logo
@@ -79,7 +96,7 @@ export function vitePluginStarlightUserConfig(
79
96
  : 'export const logos = {};',
80
97
  'virtual:starlight/collection-config': `let userCollections;
81
98
  try {
82
- userCollections = (await import(${resolveId('./content/config.ts', srcDir)})).collections;
99
+ userCollections = (await import(${JSON.stringify(collectionConfigImportPath)})).collections;
83
100
  } catch {}
84
101
  export const collections = userCollections;`,
85
102
  'virtual:starlight/plugin-translations': `export default ${JSON.stringify(pluginTranslations)}`,
package/loaders.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { glob, type Loader, type LoaderContext } from 'astro/loaders';
2
+ import { getCollectionPathFromRoot, type StarlightCollection } from './utils/collection';
3
+
4
+ // https://github.com/withastro/astro/blob/main/packages/astro/src/core/constants.ts#L87
5
+ // https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/index.ts#L59
6
+ const docsExtensions = ['markdown', 'mdown', 'mkdn', 'mkd', 'mdwn', 'md', 'mdx'];
7
+ const i18nExtensions = ['json', 'yml', 'yaml'];
8
+
9
+ export function docsLoader(): Loader {
10
+ return {
11
+ name: 'starlight-docs-loader',
12
+ load: createGlobLoadFn('docs'),
13
+ };
14
+ }
15
+
16
+ export function i18nLoader(): Loader {
17
+ return {
18
+ name: 'starlight-i18n-loader',
19
+ load: createGlobLoadFn('i18n'),
20
+ };
21
+ }
22
+
23
+ function createGlobLoadFn(collection: StarlightCollection): Loader['load'] {
24
+ return (context: LoaderContext) => {
25
+ const extensions = collection === 'docs' ? docsExtensions : i18nExtensions;
26
+
27
+ if (
28
+ collection === 'docs' &&
29
+ context.config.integrations.find(({ name }) => name === '@astrojs/markdoc')
30
+ ) {
31
+ // https://github.com/withastro/astro/blob/main/packages/integrations/markdoc/src/content-entry-type.ts#L28
32
+ extensions.push('mdoc');
33
+ }
34
+
35
+ return glob({
36
+ base: getCollectionPathFromRoot(collection, context.config),
37
+ pattern: `**/[^_]*.{${extensions.join(',')}}`,
38
+ }).load(context);
39
+ };
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.29.3",
3
+ "version": "0.30.1",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
@@ -158,6 +158,7 @@
158
158
  "./internal": "./internal.ts",
159
159
  "./props": "./props.ts",
160
160
  "./schema": "./schema.ts",
161
+ "./loaders": "./loaders.ts",
161
162
  "./types": "./types.ts",
162
163
  "./expressive-code": {
163
164
  "types": "./expressive-code.d.ts",
@@ -171,19 +172,19 @@
171
172
  "./style/markdown.css": "./style/markdown.css"
172
173
  },
173
174
  "peerDependencies": {
174
- "astro": "^4.14.0"
175
+ "astro": "^5.0.0"
175
176
  },
176
177
  "devDependencies": {
177
- "@astrojs/markdown-remark": "^5.3.0",
178
+ "@astrojs/markdown-remark": "^6.0.0",
178
179
  "@playwright/test": "^1.45.0",
179
180
  "@types/node": "^18.16.19",
180
- "@vitest/coverage-v8": "^1.6.0",
181
- "astro": "^4.16.10",
181
+ "@vitest/coverage-v8": "2.1.6",
182
+ "astro": "^5.0.2",
182
183
  "linkedom": "^0.18.4",
183
- "vitest": "^1.6.0"
184
+ "vitest": "2.1.6"
184
185
  },
185
186
  "dependencies": {
186
- "@astrojs/mdx": "^3.1.3",
187
+ "@astrojs/mdx": "^4.0.1",
187
188
  "@astrojs/sitemap": "^3.1.6",
188
189
  "@pagefind/default-ui": "^1.0.3",
189
190
  "@types/hast": "^3.0.4",
@@ -208,8 +209,12 @@
208
209
  "unist-util-visit": "^5.0.0",
209
210
  "vfile": "^6.0.2"
210
211
  },
212
+ "publishConfig": {
213
+ "provenance": true
214
+ },
211
215
  "scripts": {
212
216
  "test": "vitest",
217
+ "test:legacy": "LEGACY_COLLECTIONS=true vitest",
213
218
  "test:coverage": "vitest run --coverage",
214
219
  "test:e2e": "playwright install --with-deps chromium && playwright test"
215
220
  }
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { render } from 'astro:content';
2
3
  import { generateRouteData } from '../utils/route-data';
3
4
  import type { Route } from '../utils/routing';
4
5
  import Page from '../components/Page.astro';
@@ -9,7 +10,7 @@ export type Props = {
9
10
 
10
11
  const { route } = Astro.props;
11
12
 
12
- const { Content, headings } = await route.entry.render();
13
+ const { Content, headings } = await render(route.entry);
13
14
  const routeData = generateRouteData({ props: { ...route, headings }, url: Astro.url });
14
15
  ---
15
16
 
@@ -1,8 +1,14 @@
1
1
  ---
2
2
  import { getEntry } from 'astro:content';
3
+ import project from 'virtual:starlight/project-context';
3
4
  import config from 'virtual:starlight/user-config';
4
- import EmptyContent from '../../components/EmptyMarkdown.md';
5
- import type { Route, StarlightDocsEntry } from '../../utils/routing';
5
+ import { getCollectionPathFromRoot } from '../../utils/collection';
6
+ import {
7
+ normalizeCollectionEntry,
8
+ type Route,
9
+ type StarlightDocsCollectionEntry,
10
+ type StarlightDocsEntry,
11
+ } from '../../utils/routing';
6
12
  import { BuiltInDefaultLocale } from '../../utils/i18n';
7
13
  import CommonPage from '../common.astro';
8
14
 
@@ -17,7 +23,7 @@ const entryMeta = { dir, lang, locale };
17
23
 
18
24
  const fallbackEntry: StarlightDocsEntry = {
19
25
  slug: '404',
20
- id: '404.md' as StarlightDocsEntry['id'],
26
+ id: '404',
21
27
  body: '',
22
28
  collection: 'docs',
23
29
  data: {
@@ -30,15 +36,11 @@ const fallbackEntry: StarlightDocsEntry = {
30
36
  sidebar: { hidden: false, attrs: {} },
31
37
  draft: false,
32
38
  },
33
- render: async () => ({
34
- Content: EmptyContent,
35
- headings: [],
36
- remarkPluginFrontmatter: {},
37
- }),
39
+ filePath: `${getCollectionPathFromRoot('docs', project)}/404.md`,
38
40
  };
39
41
 
40
- const userEntry = await getEntry('docs', '404');
41
- const entry = userEntry || fallbackEntry;
42
+ const userEntry = (await getEntry('docs', '404')) as StarlightDocsCollectionEntry;
43
+ const entry = userEntry ? normalizeCollectionEntry(userEntry) : fallbackEntry;
42
44
  const route: Route = { ...entryMeta, entryMeta, entry, id: entry.id, slug: entry.slug };
43
45
  ---
44
46
 
package/style/shiki.css CHANGED
@@ -1,6 +1,6 @@
1
1
  :root {
2
- --astro-code-color-text: var(--sl-color-white);
3
- --astro-code-color-background: var(--sl-color-gray-6);
2
+ --astro-code-foreground: var(--sl-color-white);
3
+ --astro-code-background: var(--sl-color-gray-6);
4
4
  --astro-code-token-constant: var(--sl-color-blue-high);
5
5
  --astro-code-token-string: var(--sl-color-green-high);
6
6
  --astro-code-token-comment: var(--sl-color-gray-2);
@@ -0,0 +1,42 @@
1
+ {
2
+ "skipLink.label": "Saltar al contingut",
3
+ "search.label": "Cercar",
4
+ "search.ctrlKey": "Ctrl",
5
+ "search.cancelLabel": "Cancel·lar",
6
+ "search.devWarning": "La cerca només està disponible a les versions de producció. \nProva de construir i previsualitzar el lloc per provar-ho localment.",
7
+ "themeSelect.accessibleLabel": "Seleccionar tema",
8
+ "themeSelect.dark": "Fosc",
9
+ "themeSelect.light": "Clar",
10
+ "themeSelect.auto": "Automàtic",
11
+ "languageSelect.accessibleLabel": "Seleccionar idioma",
12
+ "menuButton.accessibleLabel": "Menú",
13
+ "sidebarNav.accessibleLabel": "Primari",
14
+ "tableOfContents.onThisPage": "En aquesta pàgina",
15
+ "tableOfContents.overview": "Sinopsi",
16
+ "i18n.untranslatedContent": "Aquesta pàgina encara no està disponible en el teu idioma.",
17
+ "page.editLink": "Edita aquesta pàgina",
18
+ "page.lastUpdated": "Última actualització:",
19
+ "page.previousLink": "Pàgina anterior",
20
+ "page.nextLink": "Pàgina següent",
21
+ "page.draft": "Aquest contingut és un esborrany i no s'inclourà en les versions de producció.",
22
+ "404.text": "Pàgina no trobada. Comprova la URL o intenta utilitzar la barra de cerca.",
23
+ "aside.note": "Nota",
24
+ "aside.tip": "Consell",
25
+ "aside.caution": "Precaució",
26
+ "aside.danger": "Perill",
27
+ "expressiveCode.copyButtonCopied": "Copiat!",
28
+ "expressiveCode.copyButtonTooltip": "Copiar al porta-retalls",
29
+ "expressiveCode.terminalWindowFallbackTitle": "Finestra del terminal",
30
+ "fileTree.directory": "Directori",
31
+ "builtWithStarlight.label": "Fet amb Starlight",
32
+ "pagefind.clear_search": "Netejar",
33
+ "pagefind.load_more": "Carregar més resultats",
34
+ "pagefind.search_label": "Cercar pàgina",
35
+ "pagefind.filters_label": "Filtres",
36
+ "pagefind.zero_results": "Cap resultat per a: [SEARCH_TERM]",
37
+ "pagefind.many_results": "[COUNT] resultats per a: [SEARCH_TERM]",
38
+ "pagefind.one_result": "[COUNT] resultat per a: [SEARCH_TERM]",
39
+ "pagefind.alt_search": "Cap resultat per a [SEARCH_TERM]. Mostrant resultats per a: [DIFFERENT_TERM]",
40
+ "pagefind.search_suggestion": "Cap resultat per a [SEARCH_TERM]. Prova alguna d’aquestes cerques:",
41
+ "pagefind.searching": "Cercant [SEARCH_TERM]..."
42
+ }
@@ -28,5 +28,15 @@
28
28
  "expressiveCode.copyButtonTooltip": "Copiar al portapapeles",
29
29
  "expressiveCode.terminalWindowFallbackTitle": "Ventana de terminal",
30
30
  "fileTree.directory": "Directory",
31
- "builtWithStarlight.label": "Hecho con Starlight"
31
+ "builtWithStarlight.label": "Hecho con Starlight",
32
+ "pagefind.clear_search": "Limpiar",
33
+ "pagefind.load_more": "Cargar más resultados",
34
+ "pagefind.search_label": "Buscar página",
35
+ "pagefind.filters_label": "Filtros",
36
+ "pagefind.zero_results": "Ningún resultado para: [SEARCH_TERM]",
37
+ "pagefind.many_results": "[COUNT] resultados para: [SEARCH_TERM]",
38
+ "pagefind.one_result": "[COUNT] resultado para: [SEARCH_TERM]",
39
+ "pagefind.alt_search": "Ningún resultado para [SEARCH_TERM]. Mostrando resultados para: [DIFFERENT_TERM]",
40
+ "pagefind.search_suggestion": "Ningún resultado para [SEARCH_TERM]. Prueba alguna de estas búsquedas:",
41
+ "pagefind.searching": "Buscando [SEARCH_TERM]..."
32
42
  }
@@ -2,6 +2,7 @@ import { builtinI18nSchema } from '../schemas/i18n';
2
2
  import cs from './cs.json';
3
3
  import en from './en.json';
4
4
  import es from './es.json';
5
+ import ca from './ca.json';
5
6
  import de from './de.json';
6
7
  import ja from './ja.json';
7
8
  import pt from './pt.json';
@@ -35,6 +36,7 @@ export default Object.fromEntries(
35
36
  cs,
36
37
  en,
37
38
  es,
39
+ ca,
38
40
  de,
39
41
  ja,
40
42
  pt,
@@ -0,0 +1,45 @@
1
+ import { resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ const collectionNames = ['docs', 'i18n'] as const;
5
+ export type StarlightCollection = (typeof collectionNames)[number];
6
+
7
+ /**
8
+ * We still rely on the content collection folder structure to be fixed for now:
9
+ *
10
+ * - At build time, if the feature is enabled, we get all the last commit dates for each file in
11
+ * the docs folder ahead of time. In the current approach, we cannot know at this time the
12
+ * user-defined content folder path in the integration context as this would only be available
13
+ * from the loader. A potential solution could be to do that from a custom loader re-implementing
14
+ * the glob loader or built on top of it. Although, we don't have access to the Starlight
15
+ * configuration from the loader to even know we should do that.
16
+ * - Remark plugins get passed down an absolute path to a content file and we need to figure out
17
+ * the language from that path. Without knowing the content folder path, we cannot reliably do
18
+ * so.
19
+ *
20
+ * Below are various functions to easily get paths to these collections and avoid having to
21
+ * hardcode them throughout the codebase. When user-defined content folder locations are supported,
22
+ * these helper functions should be updated to reflect that in one place.
23
+ */
24
+
25
+ export function getCollectionPath(collection: StarlightCollection, srcDir: URL) {
26
+ return new URL(`content/${collection}/`, srcDir).pathname;
27
+ }
28
+
29
+ export function resolveCollectionPath(collection: StarlightCollection, srcDir: URL) {
30
+ return resolve(fileURLToPath(srcDir), `content/${collection}`);
31
+ }
32
+
33
+ export function getCollectionPathFromRoot(
34
+ collection: StarlightCollection,
35
+ { root, srcDir }: { root: URL | string; srcDir: URL | string }
36
+ ) {
37
+ return (
38
+ (typeof srcDir === 'string' ? srcDir : srcDir.pathname).replace(
39
+ typeof root === 'string' ? root : root.pathname,
40
+ ''
41
+ ) +
42
+ 'content/' +
43
+ collection
44
+ );
45
+ }
package/utils/git.ts CHANGED
@@ -55,8 +55,8 @@ function getRepoRoot(directory: string): string {
55
55
  }
56
56
  }
57
57
 
58
- export function getAllNewestCommitDate(directory: string): [string, number][] {
59
- const repoRoot = getRepoRoot(directory);
58
+ export function getAllNewestCommitDate(rootPath: string, docsPath: string): [string, number][] {
59
+ const repoRoot = getRepoRoot(docsPath);
60
60
 
61
61
  const gitLog = spawnSync(
62
62
  'git',
@@ -67,7 +67,7 @@ export function getAllNewestCommitDate(directory: string): [string, number][] {
67
67
  // In each entry include the name and status for each modified file
68
68
  '--name-status',
69
69
  '--',
70
- directory,
70
+ docsPath,
71
71
  ],
72
72
  {
73
73
  cwd: repoRoot,
@@ -105,7 +105,9 @@ export function getAllNewestCommitDate(directory: string): [string, number][] {
105
105
 
106
106
  return Array.from(latestDates.entries()).map(([file, date]) => {
107
107
  const fileFullPath = resolve(repoRoot, file);
108
- const fileInDirectory = relative(directory, fileFullPath);
108
+ let fileInDirectory = relative(rootPath, fileFullPath);
109
+ // Format path to unix style path.
110
+ fileInDirectory = fileInDirectory?.replace(/\\/g, '/');
109
111
 
110
112
  return [fileInDirectory, date];
111
113
  });
@@ -1,5 +1,6 @@
1
1
  import { AstroError } from 'astro/errors';
2
2
  import config from 'virtual:starlight/user-config';
3
+ import project from 'virtual:starlight/project-context';
3
4
  import type { Badge, I18nBadge, I18nBadgeConfig } from '../schemas/badge';
4
5
  import type { PrevNextLinkConfig } from '../schemas/prevNextLink';
5
6
  import type {
@@ -14,8 +15,9 @@ import { formatPath } from './format-path';
14
15
  import { BuiltInDefaultLocale, pickLang } from './i18n';
15
16
  import { ensureLeadingSlash, ensureTrailingSlash, stripLeadingAndTrailingSlashes } from './path';
16
17
  import { getLocaleRoutes, routes, type Route } from './routing';
17
- import { localeToLang, slugToPathname } from './slugs';
18
+ import { localeToLang, localizedId, slugToPathname } from './slugs';
18
19
  import type { StarlightConfig } from './user-config';
20
+ import { getCollectionPathFromRoot } from './collection';
19
21
 
20
22
  const DirKey = Symbol('DirKey');
21
23
  const SlugKey = Symbol('SlugKey');
@@ -108,7 +110,7 @@ function groupFromAutogenerateConfig(
108
110
  // Match against `foo/anything/else.md`.
109
111
  doc.id.startsWith(localeDir + '/')
110
112
  );
111
- const tree = treeify(dirDocs, localeDir);
113
+ const tree = treeify(dirDocs, locale, localeDir);
112
114
  const label = pickLang(item.translations, localeToLang(locale)) || item.label;
113
115
  return {
114
116
  type: 'group',
@@ -218,16 +220,28 @@ function getBreadcrumbs(path: string, baseDir: string): string[] {
218
220
  }
219
221
 
220
222
  /** Turn a flat array of routes into a tree structure. */
221
- function treeify(routes: Route[], baseDir: string): Dir {
223
+ function treeify(routes: Route[], locale: string | undefined, baseDir: string): Dir {
222
224
  const treeRoot: Dir = makeDir(baseDir);
225
+ const collectionPathFromRoot = getCollectionPathFromRoot('docs', project);
223
226
  routes
224
227
  // Remove any entries that should be hidden
225
228
  .filter((doc) => !doc.entry.data.sidebar.hidden)
229
+ // Compute the path of each entry from the root of the collection ahead of time.
230
+ .map(
231
+ (doc) =>
232
+ [
233
+ project.legacyCollections
234
+ ? doc.id
235
+ : // For collections with a loader, use a localized filePath relative to the collection
236
+ localizedId(doc.entry.filePath.replace(`${collectionPathFromRoot}/`, ''), locale),
237
+ doc,
238
+ ] as const
239
+ )
226
240
  // Sort by depth, to build the tree depth first.
227
- .sort((a, b) => b.id.split('/').length - a.id.split('/').length)
241
+ .sort(([a], [b]) => b.split('/').length - a.split('/').length)
228
242
  // Build the tree
229
- .forEach((doc) => {
230
- const parts = getBreadcrumbs(doc.id, baseDir);
243
+ .forEach(([filePathFromContentDir, doc]) => {
244
+ const parts = getBreadcrumbs(filePathFromContentDir, baseDir);
231
245
  let currentNode = treeRoot;
232
246
 
233
247
  parts.forEach((part, index) => {
@@ -374,7 +388,7 @@ function getIntermediateSidebarFromConfig(
374
388
  if (sidebarConfig) {
375
389
  return sidebarConfig.map((group) => configItemToEntry(group, pathname, locale, routes));
376
390
  } else {
377
- const tree = treeify(routes, locale || '');
391
+ const tree = treeify(routes, locale, locale || '');
378
392
  return sidebarFromDir(tree, pathname, locale, false);
379
393
  }
380
394
  }
package/utils/plugins.ts CHANGED
@@ -2,7 +2,6 @@ import type { AstroIntegration, HookParameters } from 'astro';
2
2
  import { z } from 'astro/zod';
3
3
  import { StarlightConfigSchema, type StarlightUserConfig } from '../utils/user-config';
4
4
  import { parseWithFriendlyErrors } from '../utils/error-map';
5
- import { AstroError } from 'astro/errors';
6
5
  import type { UserI18nSchema } from './translations';
7
6
 
8
7
  /**
@@ -83,14 +82,6 @@ export async function runPlugins(
83
82
  });
84
83
  }
85
84
 
86
- if (context.config.output === 'static' && !starlightConfig.prerender) {
87
- throw new AstroError(
88
- 'Starlight’s `prerender: false` option requires `output: "hybrid"` or `"server"` in your Astro config.',
89
- 'Either set `output` in your Astro config or set `prerender: true` in the Starlight options.\n\n' +
90
- 'Learn more about rendering modes in the Astro docs: https://docs.astro.build/en/basics/rendering-modes/'
91
- );
92
- }
93
-
94
85
  return { integrations, starlightConfig, pluginTranslations };
95
86
  }
96
87
 
@@ -1,12 +1,10 @@
1
1
  import type { MarkdownHeading } from 'astro';
2
- import project from 'virtual:starlight/project-context';
3
2
  import config from 'virtual:starlight/user-config';
4
3
  import { generateToC, type TocItem } from './generateToC';
5
4
  import { getNewestCommitDate } from 'virtual:starlight/git-info';
6
5
  import { getPrevNextLinks, getSidebar, type SidebarEntry } from './navigation';
7
6
  import { ensureTrailingSlash } from './path';
8
7
  import type { Route } from './routing';
9
- import { localizedId } from './slugs';
10
8
  import { formatPath } from './format-path';
11
9
  import { useTranslations } from './translations';
12
10
  import { DeprecatedLabelsPropProxy } from './i18n';
@@ -85,7 +83,7 @@ function getLastUpdated({ entry }: PageProps): Date | undefined {
85
83
  try {
86
84
  return frontmatterLastUpdated instanceof Date
87
85
  ? frontmatterLastUpdated
88
- : getNewestCommitDate(entry.id);
86
+ : getNewestCommitDate(entry.filePath);
89
87
  } catch {
90
88
  // If the git command fails, ignore the error.
91
89
  return undefined;
@@ -95,7 +93,7 @@ function getLastUpdated({ entry }: PageProps): Date | undefined {
95
93
  return undefined;
96
94
  }
97
95
 
98
- function getEditUrl({ entry, id, isFallback }: PageProps): URL | undefined {
96
+ function getEditUrl({ entry }: PageProps): URL | undefined {
99
97
  const { editUrl } = entry.data;
100
98
  // If frontmatter value is false, editing is disabled for this page.
101
99
  if (editUrl === false) return;
@@ -105,10 +103,8 @@ function getEditUrl({ entry, id, isFallback }: PageProps): URL | undefined {
105
103
  // If a URL was provided in frontmatter, use that.
106
104
  url = editUrl;
107
105
  } else if (config.editLink.baseUrl) {
108
- const srcPath = project.srcDir.replace(project.root, '');
109
- const filePath = isFallback ? localizedId(id, config.defaultLocale.locale) : id;
110
106
  // If a base URL was added in Starlight config, synthesize the edit URL from it.
111
- url = ensureTrailingSlash(config.editLink.baseUrl) + srcPath + 'content/docs/' + filePath;
107
+ url = ensureTrailingSlash(config.editLink.baseUrl) + entry.filePath;
112
108
  }
113
109
  return url ? new URL(url) : undefined;
114
110
  }
package/utils/routing.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { GetStaticPathsItem } from 'astro';
2
2
  import { type CollectionEntry, getCollection } from 'astro:content';
3
3
  import config from 'virtual:starlight/user-config';
4
+ import project from 'virtual:starlight/project-context';
5
+ import { getCollectionPathFromRoot } from './collection';
4
6
  import {
5
7
  type LocaleData,
6
8
  localizedId,
@@ -15,7 +17,22 @@ import { BuiltInDefaultLocale } from './i18n';
15
17
  // We do this here so all pages trigger it and at the top level so it runs just once.
16
18
  validateLogoImports();
17
19
 
18
- export type StarlightDocsEntry = Omit<CollectionEntry<'docs'>, 'slug'> & {
20
+ // The type returned from `CollectionEntry` is different for legacy collections and collections
21
+ // using a loader. This type is a common subset of both types.
22
+ export type StarlightDocsCollectionEntry = Omit<
23
+ CollectionEntry<'docs'>,
24
+ 'id' | 'filePath' | 'render' | 'slug'
25
+ > & {
26
+ // Update the `id` property to be a string like in the loader type.
27
+ id: string;
28
+ // Add the `filePath` property which is only present in the loader type.
29
+ filePath?: string;
30
+ // Add the `slug` property which is only present in the legacy type.
31
+ slug?: string;
32
+ };
33
+
34
+ export type StarlightDocsEntry = StarlightDocsCollectionEntry & {
35
+ filePath: string;
19
36
  slug: string;
20
37
  };
21
38
 
@@ -24,9 +41,9 @@ export interface Route extends LocaleData {
24
41
  entry: StarlightDocsEntry;
25
42
  /** Locale metadata for the page content. Can be different from top-level locale values when a page is using fallback content. */
26
43
  entryMeta: LocaleData;
27
- /** The slug, a.k.a. permalink, for this page. */
44
+ /** @deprecated Migrate to the new Content Layer API and use `id` instead. */
28
45
  slug: string;
29
- /** The unique ID for this page. */
46
+ /** The slug or unique ID if using the `legacy.collections` flag. */
30
47
  id: string;
31
48
  /** True if this page is untranslated in the current language and using fallback content from the default locale. */
32
49
  isFallback?: true;
@@ -45,16 +62,27 @@ interface Path extends GetStaticPathsItem {
45
62
  */
46
63
  const normalizeIndexSlug = (slug: string) => (slug === 'index' ? '' : slug);
47
64
 
65
+ /** Normalize the different collection entry we can get from a legacy collection or a loader. */
66
+ export function normalizeCollectionEntry(entry: StarlightDocsCollectionEntry): StarlightDocsEntry {
67
+ const slug = normalizeIndexSlug(entry.slug ?? entry.id);
68
+ return {
69
+ ...entry,
70
+ // In a collection with a loader, the `id` is a slug and should be normalized.
71
+ id: entry.slug ? entry.id : slug,
72
+ // In a legacy collection, the `filePath` property doesn't exist.
73
+ filePath: entry.filePath ?? `${getCollectionPathFromRoot('docs', project)}/${entry.id}`,
74
+ // In a collection with a loader, the `slug` property is replaced by the `id`.
75
+ slug: normalizeIndexSlug(entry.slug ?? entry.id),
76
+ };
77
+ }
78
+
48
79
  /** All entries in the docs content collection. */
49
80
  const docs: StarlightDocsEntry[] = (
50
81
  (await getCollection('docs', ({ data }) => {
51
82
  // In production, filter out drafts.
52
83
  return import.meta.env.MODE !== 'production' || data.draft === false;
53
84
  })) ?? []
54
- ).map(({ slug, ...entry }) => ({
55
- ...entry,
56
- slug: normalizeIndexSlug(slug),
57
- }));
85
+ ).map(normalizeCollectionEntry);
58
86
 
59
87
  function getRoutes(): Route[] {
60
88
  const routes: Route[] = docs.map((entry) => ({
@@ -79,7 +107,7 @@ function getRoutes(): Route[] {
79
107
  const localeDocs = getLocaleDocs(locale);
80
108
  for (const fallback of defaultLocaleDocs) {
81
109
  const slug = localizedSlug(fallback.slug, locale);
82
- const id = localizedId(fallback.id, locale);
110
+ const id = project.legacyCollections ? localizedId(fallback.id, locale) : slug;
83
111
  const doesNotNeedFallback = localeDocs.some((doc) => doc.slug === slug);
84
112
  if (doesNotNeedFallback) continue;
85
113
  routes.push({
package/utils/slugs.ts CHANGED
@@ -82,7 +82,8 @@ export function localizedSlug(slug: string, locale: string | undefined): string
82
82
  }
83
83
 
84
84
  /**
85
- * Convert a collection entry ID to a different locale.
85
+ * Convert a legacy collection entry ID or filePath relative to the collection root to a different
86
+ * locale.
86
87
  * For example, passing an ID of `en/home.md` and a locale of `fr` results in `fr/home.md`.
87
88
  * An undefined locale is treated as the root locale, resulting in `home.md`.
88
89
  * @param id A collection entry ID
@@ -1,6 +1,8 @@
1
1
  import { z } from 'astro/zod';
2
2
  import { type ContentConfig, type SchemaContext } from 'astro:content';
3
+ import project from 'virtual:starlight/project-context';
3
4
  import config from 'virtual:starlight/user-config';
5
+ import { getCollectionPathFromRoot } from './collection';
4
6
  import { parseWithFriendlyErrors, parseAsyncWithFriendlyErrors } from './error-map';
5
7
  import { stripLeadingAndTrailingSlashes } from './path';
6
8
  import {
@@ -97,8 +99,8 @@ export type StarlightPageProps = Prettify<
97
99
  */
98
100
  type StarlightPageDocsEntry = Omit<StarlightDocsEntry, 'id' | 'render'> & {
99
101
  /**
100
- * The unique ID for this Starlight page which cannot be inferred from codegen like content
101
- * collection entries.
102
+ * The unique ID if using the `legacy.collections` for this Starlight page which cannot be
103
+ * inferred from codegen like content collection entries or the slug.
102
104
  */
103
105
  id: string;
104
106
  };
@@ -113,7 +115,7 @@ export async function generateStarlightPageRouteData({
113
115
  const { isFallback, frontmatter, ...routeProps } = props;
114
116
  const slug = urlToSlug(url);
115
117
  const pageFrontmatter = await getStarlightPageFrontmatter(frontmatter);
116
- const id = `${stripLeadingAndTrailingSlashes(slug)}.md`;
118
+ const id = project.legacyCollections ? `${stripLeadingAndTrailingSlashes(slug)}.md` : slug;
117
119
  const localeData = slugToLocaleData(slug);
118
120
  const sidebar = props.sidebar
119
121
  ? getSidebarFromConfig(validateSidebarProp(props.sidebar), url.pathname, localeData.locale)
@@ -124,6 +126,7 @@ export async function generateStarlightPageRouteData({
124
126
  slug,
125
127
  body: '',
126
128
  collection: 'docs',
129
+ filePath: `${getCollectionPathFromRoot('docs', project)}/${stripLeadingAndTrailingSlashes(slug)}.md`,
127
130
  data: {
128
131
  ...pageFrontmatter,
129
132
  sidebar: {
@@ -6,6 +6,7 @@ declare module 'virtual:starlight/project-context' {
6
6
  build: {
7
7
  format: import('astro').AstroConfig['build']['format'];
8
8
  };
9
+ legacyCollections: boolean;
9
10
  };
10
11
  export default ProjectContext;
11
12
  }
File without changes