summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiDeoo2024-02-16 23:38:08 +0100
committerGitHub2024-02-16 23:38:08 +0100
commitdd11b9538abdf4b5ba2ef70e07c0edda03e95add (patch)
tree02589e2096b963ee46d16290415e6415c5ae2a33
parent2cb35782dace67c7c418a31005419fa95493b3d3 (diff)
downloadIT.starlight-dd11b9538abdf4b5ba2ef70e07c0edda03e95add.tar.gz
IT.starlight-dd11b9538abdf4b5ba2ef70e07c0edda03e95add.tar.bz2
IT.starlight-dd11b9538abdf4b5ba2ef70e07c0edda03e95add.zip
"Virtual" pages prototype (#1175)
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
-rw-r--r--docs/package.json2
-rw-r--r--docs/src/content/docs/getting-started.mdx38
-rw-r--r--docs/src/content/docs/guides/pages.mdx151
-rw-r--r--examples/basics/package.json2
-rw-r--r--examples/tailwind/package.json2
-rw-r--r--package.json2
-rw-r--r--packages/starlight/__tests__/basics/slugs.test.ts35
-rw-r--r--packages/starlight/__tests__/basics/starlight-page-route-data-extend.test.ts61
-rw-r--r--packages/starlight/__tests__/basics/starlight-page-route-data.test.ts376
-rw-r--r--packages/starlight/__tests__/test-utils.ts11
-rw-r--r--packages/starlight/components/StarlightPage.astro13
-rw-r--r--packages/starlight/integrations/virtual-user-config.ts5
-rw-r--r--packages/starlight/package.json6
-rw-r--r--packages/starlight/utils/error-map.ts4
-rw-r--r--packages/starlight/utils/plugins.ts6
-rw-r--r--packages/starlight/utils/route-data.ts4
-rw-r--r--packages/starlight/utils/slugs.ts18
-rw-r--r--packages/starlight/utils/starlight-page.ts209
-rw-r--r--packages/starlight/virtual.d.ts4
-rw-r--r--pnpm-lock.yaml56
20 files changed, 928 insertions, 77 deletions
diff --git a/docs/package.json b/docs/package.json
index 7e46df58..8a751b95 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -18,7 +18,7 @@
"@astrojs/starlight": "workspace:*",
"@lunariajs/core": "^0.0.25",
"@types/culori": "^2.0.0",
- "astro": "^4.3.4",
+ "astro": "^4.3.5",
"culori": "^3.2.0",
"sharp": "^0.32.5"
},
diff --git a/docs/src/content/docs/getting-started.mdx b/docs/src/content/docs/getting-started.mdx
index d6580614..eb3a8f3e 100644
--- a/docs/src/content/docs/getting-started.mdx
+++ b/docs/src/content/docs/getting-started.mdx
@@ -83,43 +83,9 @@ Open this URL to start browsing your site.
Starlight is ready for you to add new content, or bring your existing files!
-#### File formats
+Add new pages to your site by creating Markdown files in the `src/content/docs/` directory.
-Starlight supports authoring content in Markdown and MDX with no configuration required.
-You can add support for Markdoc by installing the experimental [Astro Markdoc integration](https://docs.astro.build/en/guides/integrations-guide/markdoc/).
-
-#### Add pages
-
-Add new pages to your site by creating `.md` or `.mdx` files in `src/content/docs/`.
-Use sub-folders to organize your files and to create multiple path segments.
-
-For example, the following file structure will generate pages at `example.com/hello-world` and `example.com/guides/faq`:
-
-import FileTree from '~/components/file-tree.astro';
-
-<FileTree>
-
-- src/
- - content/
- - docs/
- - guides/
- - faq.md
- - hello-world.md
-
-</FileTree>
-
-#### Type-safe frontmatter
-
-All Starlight pages share a customizable [common set of frontmatter properties](/reference/frontmatter/) to control how the page appears:
-
-```md
----
-title: Hello, World!
-description: This is a page in my Starlight-powered site
----
-```
-
-If you forget anything important, Starlight will let you know.
+Read more about file-based routing and support for MDX and Markdoc files in the [“Pages”](/guides/pages/) guide.
### Next steps
diff --git a/docs/src/content/docs/guides/pages.mdx b/docs/src/content/docs/guides/pages.mdx
new file mode 100644
index 00000000..0daeef16
--- /dev/null
+++ b/docs/src/content/docs/guides/pages.mdx
@@ -0,0 +1,151 @@
+---
+title: Pages
+description: Learn how to create and manage your documentation site’s pages with Starlight.
+sidebar:
+ order: 1
+---
+
+Starlight generates your site’s HTML pages based on your content, with flexible options provided via Markdown frontmatter.
+In addition, Starlight projects have full access to [Astro’s powerful page generation tools](https://docs.astro.build/en/basics/astro-pages/).
+This guide shows how page generation works in Starlight.
+
+## Content pages
+
+### File formats
+
+Starlight supports authoring content in Markdown and MDX with no configuration required.
+You can add support for Markdoc by installing the experimental [Astro Markdoc integration](https://docs.astro.build/en/guides/integrations-guide/markdoc/).
+
+### Add pages
+
+Add new pages to your site by creating `.md` or `.mdx` files in `src/content/docs/`.
+Use sub-folders to organize your files and to create multiple path segments.
+
+For example, the following file structure will generate pages at `example.com/hello-world` and `example.com/reference/faq`:
+
+import FileTree from '~/components/file-tree.astro';
+
+<FileTree>
+
+- src/
+ - content/
+ - docs/
+ - hello-world.md
+ - reference/
+ - faq.md
+
+</FileTree>
+
+### Type-safe frontmatter
+
+All Starlight pages share a customizable [common set of frontmatter properties](/reference/frontmatter/) to control how the page appears:
+
+```md
+---
+title: Hello, World!
+description: This is a page in my Starlight-powered site
+---
+```
+
+If you forget anything important, Starlight will let you know.
+
+## Custom pages
+
+For advanced use cases, you can add custom pages by creating a `src/pages/` directory.
+The `src/pages/` directory uses [Astro's file-based routing](https://docs.astro.build/en/basics/astro-pages/#file-based-routing) and includes support for `.astro` files amongst other page formats.
+This is helpful if you need to build pages with a completely custom layout or generate a page from an alternative data source.
+
+For example, this project mixes Markdown content in `src/content/docs/` with Astro and HTML routes in `src/pages/`:
+
+<FileTree>
+
+- src/
+ - content/
+ - docs/
+ - hello-world.md
+ - pages/
+ - custom.astro
+ - archived.html
+
+</FileTree>
+
+Read more in the [“Pages” guide in the Astro docs](https://docs.astro.build/en/basics/astro-pages/).
+
+### Using Starlight’s design in custom pages
+
+To use the Starlight layout in custom pages, wrap your page content with the `<StarlightPage />` component.
+This can be helpful if you are generating content dynamically but still want to use Starlight’s design.
+
+```astro
+---
+// src/pages/custom-page/example.astro
+import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
+import CustomComponent from './CustomComponent.astro';
+---
+
+<StarlightPage frontmatter={{ title: 'My custom page' }}>
+ <p>This is a custom page with a custom component:</p>
+ <CustomComponent />
+</StarlightPage>
+```
+
+#### Props
+
+The `<StarlightPage />` component accepts the following props.
+
+##### `frontmatter` (required)
+
+**type:** `StarlightPageFrontmatter`
+
+Set the [frontmatter properties](/reference/frontmatter/) for this page, similar to frontmatter in Markdown pages.
+The [`title`](/reference/frontmatter/#title-required) property is required and all other properties are optional.
+
+The following properties differ from Markdown frontmatter:
+
+- The [`slug`](/reference/frontmatter/#slug) property is not supported and is automatically set based on the custom page’s URL.
+- The [`editUrl`](/reference/frontmatter/#editurl) option requires a URL to display an edit link.
+- The [`sidebar`](/reference/frontmatter/#sidebar) property is not supported. In Markdown frontmatter, this option allows customization of [autogenerated link groups](/reference/configuration/#sidebar), which is not applicable to pages using the `<StarlightPage />` component.
+
+{/* ##### `sidebar` */}
+
+{/* **type:** `SidebarEntry[] | undefined` */}
+{/* **default:** the sidebar generated based on the [global `sidebar` config](/reference/configuration/#sidebar) */}
+
+{/* Provide a custom site navigation sidebar for this page. */}
+{/* If not set, the page will use the default global sidebar. */}
+
+##### `hasSidebar`
+
+**type:** `boolean`
+**default:** `false` if [`frontmatter.template`](/reference/frontmatter/#template) is `'splash'`, otherwise `true`
+
+Control whether or not the sidebar should be displayed on this page.
+
+##### `headings`
+
+**type:** `{ depth: number; slug: string; text: string }[]`
+**default:** `[]`
+
+Provide an array of all the headings on this page.
+Starlight will generate the page table of contents from these headings if provided.
+
+##### `dir`
+
+**type:** `'ltr' | 'rtl'`
+**default:** the writing direction for the current locale
+
+Set the writing direction for this page’s content.
+
+##### `lang`
+
+**type:** `string`
+**default:** the language of the current locale
+
+Set the BCP-47 language tag for this page’s content, e.g. `en`, `zh-CN`, or `pt-BR`.
+
+##### `isFallback`
+
+**type:** `boolean`
+**default:** `false`
+
+Indicate if this page is using [fallback content](/guides/i18n/#fallback-content) because there is no translation for the current language.
diff --git a/examples/basics/package.json b/examples/basics/package.json
index 13c0e86d..99b3dea6 100644
--- a/examples/basics/package.json
+++ b/examples/basics/package.json
@@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/starlight": "^0.18.1",
- "astro": "^4.3.4",
+ "astro": "^4.3.5",
"sharp": "^0.32.5"
}
}
diff --git a/examples/tailwind/package.json b/examples/tailwind/package.json
index c088c537..9ddf8583 100644
--- a/examples/tailwind/package.json
+++ b/examples/tailwind/package.json
@@ -14,7 +14,7 @@
"@astrojs/starlight": "^0.18.1",
"@astrojs/starlight-tailwind": "^2.0.1",
"@astrojs/tailwind": "^5.1.0",
- "astro": "^4.3.4",
+ "astro": "^4.3.5",
"sharp": "^0.32.5",
"tailwindcss": "^3.4.1"
}
diff --git a/package.json b/package.json
index 697b1ebd..e0609e2a 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@size-limit/file": "^8.2.4",
- "astro": "^4.3.4",
+ "astro": "^4.3.5",
"prettier": "^3.0.0",
"prettier-plugin-astro": "^0.13.0",
"size-limit": "^8.2.4"
diff --git a/packages/starlight/__tests__/basics/slugs.test.ts b/packages/starlight/__tests__/basics/slugs.test.ts
index 6c17414b..6b7c9bd1 100644
--- a/packages/starlight/__tests__/basics/slugs.test.ts
+++ b/packages/starlight/__tests__/basics/slugs.test.ts
@@ -1,4 +1,4 @@
-import { describe, expect, test } from 'vitest';
+import { describe, expect, test, vi } from 'vitest';
import {
localeToLang,
localizedId,
@@ -6,6 +6,7 @@ import {
slugToLocaleData,
slugToParam,
slugToPathname,
+ urlToSlug,
} from '../../utils/slugs';
describe('slugToLocaleData', () => {
@@ -76,3 +77,35 @@ describe('localizedSlug', () => {
expect(localizedSlug('test', undefined)).toBe('test');
});
});
+
+describe('urlToSlug', () => {
+ test('returns slugs with `build.output: "directory"`', () => {
+ expect(urlToSlug(new URL('https://example.com'))).toBe('');
+ expect(urlToSlug(new URL('https://example.com/slug'))).toBe('slug');
+ expect(urlToSlug(new URL('https://example.com/dir/page/'))).toBe('dir/page');
+ expect(urlToSlug(new URL('https://example.com/dir/sub-dir/page/'))).toBe('dir/sub-dir/page');
+ });
+
+ test('returns slugs with `build.output: "file"`', () => {
+ expect(urlToSlug(new URL('https://example.com/index.html'))).toBe('');
+ expect(urlToSlug(new URL('https://example.com/slug.html'))).toBe('slug');
+ expect(urlToSlug(new URL('https://example.com/dir/page/index.html'))).toBe('dir/page');
+ expect(urlToSlug(new URL('https://example.com/dir/sub-dir/page.html'))).toBe(
+ 'dir/sub-dir/page'
+ );
+ });
+
+ // It is currently not possible to test this as stubbing BASE_URL is not supported due to
+ // `vite-plugin-env` controlling it and the lack of a way to pass in an Astro config using
+ // `getViteConfig()` from `astro/config`.
+ test.todo('returns slugs with a custom `base` option', () => {
+ vi.stubEnv('BASE_URL', '/base/');
+ expect(urlToSlug(new URL('https://example.com/base'))).toBe('');
+ expect(urlToSlug(new URL('https://example.com/base/slug'))).toBe('slug');
+ expect(urlToSlug(new URL('https://example.com/base/dir/page/'))).toBe('dir/page');
+ expect(urlToSlug(new URL('https://example.com/base/dir/sub-dir/page/'))).toBe(
+ 'dir/sub-dir/page'
+ );
+ vi.unstubAllEnvs();
+ });
+});
diff --git a/packages/starlight/__tests__/basics/starlight-page-route-data-extend.test.ts b/packages/starlight/__tests__/basics/starlight-page-route-data-extend.test.ts
new file mode 100644
index 00000000..39e60955
--- /dev/null
+++ b/packages/starlight/__tests__/basics/starlight-page-route-data-extend.test.ts
@@ -0,0 +1,61 @@
+import { assert, expect, test, vi } from 'vitest';
+import {
+ generateStarlightPageRouteData,
+ type StarlightPageProps,
+} from '../../utils/starlight-page';
+
+vi.mock('virtual:starlight/collection-config', async () => {
+ const { z } = await vi.importActual<typeof import('astro:content')>('astro:content');
+ return (await import('../test-utils')).mockedCollectionConfig({
+ extend: z.object({
+ // Make the built-in description field required.
+ description: z.string(),
+ // Add a new optional field.
+ category: z.string().optional(),
+ }),
+ });
+});
+
+const starlightPageProps: StarlightPageProps = {
+ frontmatter: { title: 'This is a test title' },
+};
+
+test('throws a validation error if a built-in field required by the user schema is not passed down', async () => {
+ expect.assertions(3);
+
+ try {
+ await generateStarlightPageRouteData({
+ props: starlightPageProps,
+ url: new URL('https://example.com/test-slug'),
+ });
+ } catch (error) {
+ assert(error instanceof Error);
+ const lines = error.message.split('\n');
+ // The first line should be a user-friendly error message describing the exact issue and the second line should be
+ // the missing description field.
+ expect(lines).toHaveLength(2);
+ const [message, missingField] = lines;
+ expect(message).toMatchInlineSnapshot(
+ `"Invalid frontmatter props passed to the \`<StarlightPage/>\` component."`
+ );
+ expect(missingField).toMatchInlineSnapshot(`"**description**: Required"`);
+ }
+});
+
+test('returns new field defined in the user schema', async () => {
+ const category = 'test category';
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ frontmatter: {
+ ...starlightPageProps.frontmatter,
+ description: 'test description',
+ // @ts-expect-error - Custom field defined in the user schema.
+ category,
+ },
+ },
+ url: new URL('https://example.com/test-slug'),
+ });
+ // @ts-expect-error - Custom field defined in the user schema.
+ expect(data.entry.data.category).toBe(category);
+});
diff --git a/packages/starlight/__tests__/basics/starlight-page-route-data.test.ts b/packages/starlight/__tests__/basics/starlight-page-route-data.test.ts
new file mode 100644
index 00000000..0484f610
--- /dev/null
+++ b/packages/starlight/__tests__/basics/starlight-page-route-data.test.ts
@@ -0,0 +1,376 @@
+import { expect, test, vi } from 'vitest';
+import {
+ generateStarlightPageRouteData,
+ type StarlightPageProps,
+} from '../../utils/starlight-page';
+
+vi.mock('virtual:starlight/collection-config', async () =>
+ (await import('../test-utils')).mockedCollectionConfig()
+);
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ docs: [
+ ['index.mdx', { title: 'Home Page' }],
+ ['getting-started.mdx', { title: 'Getting Started' }],
+ ],
+ })
+);
+
+const starlightPageProps: StarlightPageProps = {
+ frontmatter: { title: 'This is a test title' },
+};
+
+const starlightPageUrl = new URL('https://example.com/test-slug');
+
+test('adds data to route shape', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: starlightPageProps,
+ url: starlightPageUrl,
+ });
+ // Starlight pages infer the slug from the URL.
+ expect(data.slug).toBe('test-slug');
+ // Starlight pages generate an ID based on their slug.
+ expect(data.id).toBeDefined();
+ // Starlight pages cannot be fallbacks.
+ expect(data.isFallback).toBeUndefined();
+ // Starlight pages are not editable if no edit URL is passed.
+ expect(data.editUrl).toBeUndefined();
+ expect(data.entry.data.editUrl).toBe(false);
+ // Starlight pages are part of the docs collection.
+ expect(data.entry.collection).toBe('docs');
+ // Starlight pages get dedicated frontmatter defaults.
+ expect(data.entry.data.head).toEqual([]);
+ expect(data.entry.data.pagefind).toBe(true);
+ expect(data.entry.data.template).toBe('doc');
+ // Starlight pages respect the passed data.
+ expect(data.entry.data.title).toBe(starlightPageProps.frontmatter.title);
+ // Starlight pages get expected defaults.
+ expect(data.hasSidebar).toBe(true);
+ expect(data.headings).toEqual([]);
+ expect(data.entryMeta.dir).toBe('ltr');
+ expect(data.entryMeta.lang).toBe('en');
+});
+
+test('adds custom data to route shape', async () => {
+ const props: StarlightPageProps = {
+ ...starlightPageProps,
+ hasSidebar: false,
+ dir: 'rtl',
+ lang: 'ks',
+ };
+ const data = await generateStarlightPageRouteData({ props, url: starlightPageUrl });
+ expect(data.hasSidebar).toBe(props.hasSidebar);
+ expect(data.entryMeta.dir).toBe(props.dir);
+ expect(data.entryMeta.lang).toBe(props.lang);
+});
+
+test('adds custom frontmatter data to route shape', async () => {
+ const props: StarlightPageProps = {
+ ...starlightPageProps,
+ frontmatter: {
+ ...starlightPageProps.frontmatter,
+ head: [{ tag: 'meta', attrs: { name: 'og:test', content: 'test' } }],
+ lastUpdated: new Date(),
+ pagefind: false,
+ template: 'splash',
+ },
+ };
+ const data = await generateStarlightPageRouteData({ props, url: starlightPageUrl });
+ expect(data.entry.data.head).toMatchInlineSnapshot(`
+ [
+ {
+ "attrs": {
+ "content": "test",
+ "name": "og:test",
+ },
+ "content": "",
+ "tag": "meta",
+ },
+ ]
+ `);
+ expect(data.entry.data.lastUpdated).toEqual(props.frontmatter.lastUpdated);
+ expect(data.entry.data.pagefind).toBe(props.frontmatter.pagefind);
+ expect(data.entry.data.template).toBe(props.frontmatter.template);
+});
+
+test('uses generated sidebar when no sidebar is provided', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: starlightPageProps,
+ url: starlightPageUrl,
+ });
+ expect(data.sidebar.map((entry) => entry.label)).toMatchInlineSnapshot(`
+ [
+ "Home Page",
+ "Getting Started",
+ ]
+ `);
+});
+
+test('uses provided sidebar if any', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ sidebar: [
+ {
+ type: 'link',
+ label: 'Custom link 1',
+ href: '/test/1',
+ isCurrent: false,
+ badge: undefined,
+ attrs: {},
+ },
+ {
+ type: 'link',
+ label: 'Custom link 2',
+ href: '/test/2',
+ isCurrent: false,
+ badge: undefined,
+ attrs: {},
+ },
+ ],
+ },
+ url: starlightPageUrl,
+ });
+ expect(data.sidebar.map((entry) => entry.label)).toMatchInlineSnapshot(`
+ [
+ "Custom link 1",
+ "Custom link 2",
+ ]
+ `);
+});
+
+test('uses provided pagination if any', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ frontmatter: {
+ ...starlightPageProps.frontmatter,
+ prev: {
+ label: 'Previous link',
+ link: '/test/prev',
+ },
+ next: {
+ label: 'Next link',
+ link: '/test/next',
+ },
+ },
+ },
+ url: starlightPageUrl,
+ });
+ expect(data.pagination).toMatchInlineSnapshot(`
+ {
+ "next": {
+ "attrs": {},
+ "badge": undefined,
+ "href": "/test/next",
+ "isCurrent": false,
+ "label": "Next link",
+ "type": "link",
+ },
+ "prev": {
+ "attrs": {},
+ "badge": undefined,
+ "href": "/test/prev",
+ "isCurrent": false,
+ "label": "Previous link",
+ "type": "link",
+ },
+ }
+ `);
+});
+
+test('uses provided headings if any', async () => {
+ const headings = [
+ { depth: 2, slug: 'heading-1', text: 'Heading 1' },
+ { depth: 3, slug: 'heading-2', text: 'Heading 2' },
+ ];
+ const data = await generateStarlightPageRouteData({
+ props: { ...starlightPageProps, headings },
+ url: starlightPageUrl,
+ });
+ expect(data.headings).toEqual(headings);
+});
+
+test('generates the table of contents for provided headings', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ headings: [
+ { depth: 2, slug: 'heading-1', text: 'Heading 1' },
+ { depth: 3, slug: 'heading-2', text: 'Heading 2' },
+ // Should be ignored as it's too deep with default config.
+ { depth: 4, slug: 'heading-3', text: 'Heading 3' },
+ ],
+ },
+ url: starlightPageUrl,
+ });
+ expect(data.toc).toMatchInlineSnapshot(`
+ {
+ "items": [
+ {
+ "children": [],
+ "depth": 2,
+ "slug": "_top",
+ "text": "Overview",
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "depth": 3,
+ "slug": "heading-2",
+ "text": "Heading 2",
+ },
+ ],
+ "depth": 2,
+ "slug": "heading-1",
+ "text": "Heading 1",
+ },
+ ],
+ "maxHeadingLevel": 3,
+ "minHeadingLevel": 2,
+ }
+ `);
+});
+
+test('respects the `tableOfContents` level configuration', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ headings: [
+ // Should be ignored as it's not deep enough.
+ { depth: 2, slug: 'heading-1', text: 'Heading 1' },
+ { depth: 3, slug: 'heading-2', text: 'Heading 2' },
+ { depth: 4, slug: 'heading-3', text: 'Heading 3' },
+ ],
+ frontmatter: {
+ ...starlightPageProps.frontmatter,
+ tableOfContents: {
+ minHeadingLevel: 3,
+ maxHeadingLevel: 4,
+ },
+ },
+ },
+ url: starlightPageUrl,
+ });
+ expect(data.toc).toMatchInlineSnapshot(`
+ {
+ "items": [
+ {
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "depth": 4,
+ "slug": "heading-3",
+ "text": "Heading 3",
+ },
+ ],
+ "depth": 3,
+ "slug": "heading-2",
+ "text": "Heading 2",
+ },
+ ],
+ "depth": 2,
+ "slug": "_top",
+ "text": "Overview",
+ },
+ ],
+ "maxHeadingLevel": 4,
+ "minHeadingLevel": 3,
+ }
+ `);
+});
+
+test('disables table of contents if frontmatter includes `tableOfContents: false`', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ headings: [
+ { depth: 2, slug: 'heading-1', text: 'Heading 1' },
+ { depth: 3, slug: 'heading-2', text: 'Heading 2' },
+ ],
+ frontmatter: {
+ ...starlightPageProps.frontmatter,
+ tableOfContents: false,
+ },
+ },
+ url: starlightPageUrl,
+ });
+ expect(data.toc).toBeUndefined();
+});
+
+test('disables table of contents for splash template', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ headings: [
+ { depth: 2, slug: 'heading-1', text: 'Heading 1' },
+ { depth: 3, slug: 'heading-2', text: 'Heading 2' },
+ ],
+ frontmatter: {
+ ...starlightPageProps.frontmatter,
+ template: 'splash',
+ },
+ },
+ url: starlightPageUrl,
+ });
+ expect(data.toc).toBeUndefined();
+});
+
+test('hides the sidebar if the `hasSidebar` option is not specified and the splash template is used', async () => {
+ const { hasSidebar, ...otherProps } = starlightPageProps;
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...otherProps,
+ frontmatter: {
+ ...otherProps.frontmatter,
+ template: 'splash',
+ },
+ },
+ url: starlightPageUrl,
+ });
+ expect(data.hasSidebar).toBe(false);
+});
+
+test('includes localized labels', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: starlightPageProps,
+ url: starlightPageUrl,
+ });
+ expect(data.labels).toBeDefined();
+ expect(data.labels['skipLink.label']).toBe('Skip to content');
+});
+
+test('uses provided edit URL if any', async () => {
+ const editUrl = 'https://example.com/edit';
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ frontmatter: {
+ ...starlightPageProps.frontmatter,
+ editUrl,
+ },
+ },
+ url: starlightPageUrl,
+ });
+ expect(data.editUrl).toEqual(new URL(editUrl));
+ expect(data.entry.data.editUrl).toEqual(editUrl);
+});
+
+test('strips unknown frontmatter properties', async () => {
+ const data = await generateStarlightPageRouteData({
+ props: {
+ ...starlightPageProps,
+ frontmatter: {
+ ...starlightPageProps.frontmatter,
+ // @ts-expect-error - This is an unknown property.
+ unknown: 'test',
+ },
+ },
+ url: starlightPageUrl,
+ });
+ expect('unknown' in data.entry.data).toBe(false);
+});
diff --git a/packages/starlight/__tests__/test-utils.ts b/packages/starlight/__tests__/test-utils.ts
index 96ff4f64..7c145378 100644
--- a/packages/starlight/__tests__/test-utils.ts
+++ b/packages/starlight/__tests__/test-utils.ts
@@ -56,3 +56,14 @@ export async function mockedAstroContent({
getCollection: (collection: 'docs' | 'i18n') => (collection === 'i18n' ? mockDicts : mockDocs),
};
}
+
+export async function mockedCollectionConfig(docsUserSchema?: Parameters<typeof docsSchema>[0]) {
+ const content = await vi.importActual<typeof import('astro:content')>('astro:content');
+ const schemas = await vi.importActual<typeof import('../schema')>('../schema');
+ return {
+ collections: {
+ docs: content.defineCollection({ schema: schemas.docsSchema(docsUserSchema) }),
+ i18n: content.defineCollection({ type: 'data', schema: schemas.i18nSchema() }),
+ },
+ };
+}
diff --git a/packages/starlight/components/StarlightPage.astro b/packages/starlight/components/StarlightPage.astro
new file mode 100644
index 00000000..52e5c58e
--- /dev/null
+++ b/packages/starlight/components/StarlightPage.astro
@@ -0,0 +1,13 @@
+---
+import {
+ generateStarlightPageRouteData,
+ type StarlightPageProps as Props,
+} from '../utils/starlight-page';
+import Page from './Page.astro';
+
+export type StarlightPageProps = Props;
+---
+
+<Page {...await generateStarlightPageRouteData({ props: Astro.props, url: Astro.url })}>
+ <slot />
+</Page>
diff --git a/packages/starlight/integrations/virtual-user-config.ts b/packages/starlight/integrations/virtual-user-config.ts
index 29a2e512..d752a966 100644
--- a/packages/starlight/integrations/virtual-user-config.ts
+++ b/packages/starlight/integrations/virtual-user-config.ts
@@ -48,6 +48,11 @@ export function vitePluginStarlightUserConfig(
opts.logo.light
)}; export const logos = { dark, light };`
: 'export const logos = {};',
+ 'virtual:starlight/collection-config': `let userCollections;
+ try {
+ userCollections = (await import('/src/content/config.ts')).collections;
+ } catch {}
+ export const collections = userCollections;`,
...virtualComponentModules,
} satisfies Record<string, string>;
diff --git a/packages/starlight/package.json b/packages/starlight/package.json
index ef7fc4c5..e5fa2d2d 100644
--- a/packages/starlight/package.json
+++ b/packages/starlight/package.json
@@ -102,6 +102,10 @@
"types": "./components/Page.astro.tsx",
"import": "./components/Page.astro"
},
+ "./components/StarlightPage.astro": {
+ "types": "./components/StarlightPage.astro.tsx",
+ "import": "./components/StarlightPage.astro"
+ },
"./components/Footer.astro": {
"types": "./components/Footer.astro.tsx",
"import": "./components/Footer.astro"
@@ -173,7 +177,7 @@
"@astrojs/markdown-remark": "^4.2.1",
"@types/node": "^18.16.19",
"@vitest/coverage-v8": "^1.2.2",
- "astro": "^4.3.4",
+ "astro": "^4.3.5",
"vitest": "^1.2.2"
},
"dependencies": {
diff --git a/packages/starlight/utils/error-map.ts b/packages/starlight/utils/error-map.ts
index d1006d35..fd5c3660 100644
--- a/packages/starlight/utils/error-map.ts
+++ b/packages/starlight/utils/error-map.ts
@@ -11,6 +11,10 @@ type TypeOrLiteralErrByPathEntry = {
expected: unknown[];
};
+export function throwValidationError(error: z.ZodError, message: string): never {
+ throw new Error(`${message}\n${error.issues.map((i) => i.message).join('\n')}`);
+}
+
export const errorMap: z.ZodErrorMap = (baseError, ctx) => {
const baseErrorPath = flattenErrorPath(baseError.path);
if (baseError.code === 'invalid_union') {
diff --git a/packages/starlight/utils/plugins.ts b/packages/starlight/utils/plugins.ts
index 40e449d5..bca72087 100644
--- a/packages/starlight/utils/plugins.ts
+++ b/packages/starlight/utils/plugins.ts
@@ -1,7 +1,7 @@
import type { AstroIntegration } from 'astro';
import { z } from 'astro/zod';
import { StarlightConfigSchema, type StarlightUserConfig } from '../utils/user-config';
-import { errorMap } from '../utils/error-map';
+import { errorMap, throwValidationError } from '../utils/error-map';
/**
* Runs Starlight plugins in the order that they are configured after validating the user-provided
@@ -82,10 +82,6 @@ export async function runPlugins(
return { integrations, starlightConfig: starlightConfig.data };
}
-function throwValidationError(error: z.ZodError, message: string): never {
- throw new Error(`${message}\n${error.issues.map((i) => i.message).join('\n')}`);
-}
-
// https://github.com/withastro/astro/blob/910eb00fe0b70ca80bd09520ae100e8c78b675b5/packages/astro/src/core/config/schema.ts#L113
const astroIntegrationSchema = z.object({
name: z.string(),
diff --git a/packages/starlight/utils/route-data.ts b/packages/starlight/utils/route-data.ts
index b7a23e6c..1ad5c9ca 100644
--- a/packages/starlight/utils/route-data.ts
+++ b/packages/starlight/utils/route-data.ts
@@ -10,7 +10,7 @@ import type { Route } from './routing';
import { localizedId } from './slugs';
import { useTranslations } from './translations';
-interface PageProps extends Route {
+export interface PageProps extends Route {
headings: MarkdownHeading[];
}
@@ -54,7 +54,7 @@ export function generateRouteData({
};
}
-function getToC({ entry, locale, headings }: PageProps) {
+export function getToC({ entry, locale, headings }: PageProps) {
const tocConfig =
entry.data.template === 'splash'
? false
diff --git a/packages/starlight/utils/slugs.ts b/packages/starlight/utils/slugs.ts
index 2b45fd8d..b7e076c9 100644
--- a/packages/starlight/utils/slugs.ts
+++ b/packages/starlight/utils/slugs.ts
@@ -101,3 +101,21 @@ export function localizedId(id: string, locale: string | undefined): string {
return id;
}
}
+
+/** Extract the slug from a URL. */
+export function urlToSlug(url: URL): string {
+ let pathname = url.pathname;
+ const base = import.meta.env.BASE_URL.replace(/\/$/, '');
+ if (pathname.startsWith(base)) pathname = pathname.replace(base, '');
+ const segments = pathname.split('/');
+ const htmlExt = '.html';
+ if (segments.at(-1) === 'index.html') {
+ // Remove trailing `index.html`.
+ segments.pop();
+ } else if (segments.at(-1)?.endsWith(htmlExt)) {
+ // Remove trailing `.html`.
+ const last = segments.pop();
+ if (last) segments.push(last.slice(0, -1 * htmlExt.length));
+ }
+ return segments.filter(Boolean).join('/');
+}
diff --git a/packages/starlight/utils/starlight-page.ts b/packages/starlight/utils/starlight-page.ts
new file mode 100644
index 00000000..c08ce4fa
--- /dev/null
+++ b/packages/starlight/utils/starlight-page.ts
@@ -0,0 +1,209 @@
+import { z } from 'astro/zod';
+import { type ContentConfig, type SchemaContext } from 'astro:content';
+import config from 'virtual:starlight/user-config';
+import { errorMap, throwValidationError } from './error-map';
+import { stripLeadingAndTrailingSlashes } from './path';
+import { getToC, type PageProps, type StarlightRouteData } from './route-data';
+import type { StarlightDocsEntry } from './routing';
+import { slugToLocaleData, urlToSlug } from './slugs';
+import { getPrevNextLinks, getSidebar } from './navigation';
+import { useTranslations } from './translations';
+import { docsSchema } from '../schema';
+
+/**
+ * The frontmatter schema for Starlight pages derived from the default schema for Starlight’s
+ * `docs` content collection.
+ * The frontmatter schema for Starlight pages cannot include some properties which will be omitted
+ * and some others needs to be refined to a stricter type.
+ */
+const StarlightPageFrontmatterSchema = async (context: SchemaContext) => {
+ const userDocsSchema = await getUserDocsSchema();
+ const schema = typeof userDocsSchema === 'function' ? userDocsSchema(context) : userDocsSchema;
+
+ return schema.transform((frontmatter) => {
+ /**
+ * Starlight pages can only be edited if an edit URL is explicitly provided.
+ * The `sidebar` frontmatter prop only works for pages in an autogenerated links group.
+ * Starlight pages edit links cannot be autogenerated.
+ *
+ * These changes to the schema are done using a transformer and not using the usual `omit`
+ * method because when the frontmatter schema is extended by the user, an intersection between
+ * the default schema and the user schema is created using the `and` method. Intersections in
+ * Zod returns a `ZodIntersection` object which does not have some methods like `omit` or
+ * `pick`.
+ *
+ * This transformer only sets the `editUrl` default value and removes the `sidebar` property
+ * from the validated output but does not appply any changes to the input schema type itself so
+ * this needs to be done manually.
+ *
+ * @see StarlightPageFrontmatter
+ * @see https://github.com/colinhacks/zod#intersections
+ */
+ const { editUrl, sidebar, ...others } = frontmatter;
+ const pageEditUrl = editUrl === undefined || editUrl === true ? false : editUrl;
+ return { ...others, editUrl: pageEditUrl };
+ });
+};
+
+/**
+ * Type of Starlight pages frontmatter schema.
+ * We manually refines the `editUrl` type and omit the `sidebar` property as it's not possible to
+ * do that on the schema itself using Zod but the proper validation is still using a transformer.
+ * @see StarlightPageFrontmatterSchema
+ */
+type StarlightPageFrontmatter = Omit<
+ z.input<Awaited<ReturnType<typeof StarlightPageFrontmatterSchema>>>,
+ 'editUrl' | 'sidebar'
+> & { editUrl?: string | false };
+
+/**
+ * The props accepted by the `<StarlightPage/>` component.
+ */
+export type StarlightPageProps = Prettify<
+ // Remove the index signature from `Route`, omit undesired properties and make the rest optional.
+ Partial<Omit<RemoveIndexSignature<PageProps>, 'entry' | 'entryMeta' | 'id' | 'locale' | 'slug'>> &
+ // Add the sidebar definitions for a Starlight page.
+ Partial<Pick<StarlightRouteData, 'hasSidebar' | 'sidebar'>> & {
+ // And finally add the Starlight page frontmatter properties in a `frontmatter` property.
+ frontmatter: StarlightPageFrontmatter;
+ }
+>;
+
+/**
+ * A docs entry used for Starlight pages meant to be rendered by plugins and which is safe to cast
+ * to a `StarlightDocsEntry`.
+ * A Starlight page docs entry cannot be rendered like a content collection entry.
+ */
+type StarlightPageDocsEntry = Omit<StarlightDocsEntry, 'id' | 'render'> & {
+ /**
+ * The unique ID for this Starlight page which cannot be inferred from codegen like content
+ * collection entries.
+ */
+ id: string;
+};
+
+export async function generateStarlightPageRouteData({
+ props,
+ url,
+}: {
+ props: StarlightPageProps;
+ url: URL;
+}): Promise<StarlightRouteData> {
+ const { isFallback, frontmatter, ...routeProps } = props;
+ const slug = urlToSlug(url);
+ const pageFrontmatter = await getStarlightPageFrontmatter(frontmatter);
+ const id = `${stripLeadingAndTrailingSlashes(slug)}.md`;
+ const localeData = slugToLocaleData(slug);
+ const sidebar = props.sidebar ?? getSidebar(url.pathname, localeData.locale);
+ const headings = props.headings ?? [];
+ const pageDocsEntry: StarlightPageDocsEntry = {
+ id,
+ slug,
+ body: '',
+ collection: 'docs',
+ data: {
+ ...pageFrontmatter,
+ sidebar: {
+ attrs: {},
+ hidden: false,
+ },
+ },
+ };
+ const entry = pageDocsEntry as StarlightDocsEntry;
+ const entryMeta: StarlightRouteData['entryMeta'] = {
+ dir: props.dir ?? localeData.dir,
+ lang: props.lang ?? localeData.lang,
+ locale: localeData.locale,
+ };
+ const editUrl = pageFrontmatter.editUrl ? new URL(pageFrontmatter.editUrl) : undefined;
+ const lastUpdated =
+ pageFrontmatter.lastUpdated instanceof Date ? pageFrontmatter.lastUpdated : undefined;
+ const routeData: StarlightRouteData = {
+ ...routeProps,
+ ...localeData,
+ id,
+ editUrl,
+ entry,
+ entryMeta,
+ hasSidebar: props.hasSidebar ?? entry.data.template !== 'splash',
+ headings,
+ labels: useTranslations(localeData.locale).all(),
+ lastUpdated,
+ pagination: getPrevNextLinks(sidebar, config.pagination, entry.data),
+ sidebar,
+ slug,
+ toc: getToC({
+ ...routeProps,
+ ...localeData,
+ entry,
+ entryMeta,
+ headings,
+ id,
+ locale: localeData.locale,
+ slug,
+ }),
+ };
+ if (isFallback) {
+ routeData.isFallback = true;
+ }
+ return routeData;
+}
+
+/** Validates the Starlight page frontmatter properties from the props received by a Starlight page. */
+async function getStarlightPageFrontmatter(frontmatter: StarlightPageFrontmatter) {
+ // This needs to be in sync with ImageMetadata.
+ // https://github.com/withastro/astro/blob/cf993bc263b58502096f00d383266cd179f331af/packages/astro/src/assets/types.ts#L32
+ const schema = await StarlightPageFrontmatterSchema({
+ image: () =>
+ z.object({
+ src: z.string(),
+ width: z.number(),
+ height: z.number(),
+ format: z.union([
+ z.literal('png'),
+ z.literal('jpg'),
+ z.literal('jpeg'),
+ z.literal('tiff'),
+ z.literal('webp'),
+ z.literal('gif'),
+ z.literal('svg'),
+ z.literal('avif'),
+ ]),
+ }),
+ });
+
+ const pageFrontmatter = schema.safeParse(frontmatter, { errorMap });
+
+ if (!pageFrontmatter.success) {
+ throwValidationError(
+ pageFrontmatter.error,
+ 'Invalid frontmatter props passed to the `<StarlightPage/>` component.'
+ );
+ }
+
+ return pageFrontmatter.data;
+}
+
+/** Returns the user docs schema and falls back to the default schema if needed. */
+async function getUserDocsSchema(): Promise<
+ NonNullable<ContentConfig['collections']['docs']['schema']>
+> {
+ const userCollections = (await import('virtual:starlight/collection-config')).collections;
+ return userCollections?.docs.schema ?? docsSchema();
+}
+
+// https://stackoverflow.com/a/66252656/1945960
+type RemoveIndexSignature<T> = {
+ [K in keyof T as string extends K
+ ? never
+ : number extends K
+ ? never
+ : symbol extends K
+ ? never
+ : K]: T[K];
+};
+
+// https://www.totaltypescript.com/concepts/the-prettify-helper
+type Prettify<T> = {
+ [K in keyof T]: T[K];
+} & {};
diff --git a/packages/starlight/virtual.d.ts b/packages/starlight/virtual.d.ts
index 9a04a572..4dfce6e3 100644
--- a/packages/starlight/virtual.d.ts
+++ b/packages/starlight/virtual.d.ts
@@ -24,6 +24,10 @@ declare module 'virtual:starlight/user-images' {
};
}
+declare module 'virtual:starlight/collection-config' {
+ export const collections: import('astro:content').ContentConfig['collections'] | undefined;
+}
+
declare module 'virtual:starlight/components/Banner' {
const Banner: typeof import('./components/Banner.astro').default;
export default Banner;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cbf70c87..f9b5845f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -18,8 +18,8 @@ importers:
specifier: ^8.2.4
version: 8.2.4(size-limit@8.2.4)
astro:
- specifier: ^4.3.4
- version: 4.3.4(@types/node@18.16.19)
+ specifier: ^4.3.5
+ version: 4.3.5(@types/node@18.16.19)
prettier:
specifier: ^3.0.0
version: 3.0.0
@@ -34,7 +34,7 @@ importers:
dependencies:
'@astro-community/astro-embed-youtube':
specifier: ^0.4.4
- version: 0.4.4(astro@4.3.4)
+ version: 0.4.4(astro@4.3.5)
'@astrojs/starlight':
specifier: workspace:*
version: link:../packages/starlight
@@ -45,8 +45,8 @@ importers:
specifier: ^2.0.0
version: 2.0.0
astro:
- specifier: ^4.3.4
- version: 4.3.4(@types/node@18.16.19)
+ specifier: ^4.3.5
+ version: 4.3.5(@types/node@18.16.19)
culori:
specifier: ^3.2.0
version: 3.2.0
@@ -74,7 +74,7 @@ importers:
version: 13.0.1
starlight-links-validator:
specifier: ^0.5.3
- version: 0.5.3(@astrojs/starlight@packages+starlight)(astro@4.3.4)
+ version: 0.5.3(@astrojs/starlight@packages+starlight)(astro@4.3.5)
start-server-and-test:
specifier: ^2.0.0
version: 2.0.0
@@ -88,8 +88,8 @@ importers:
specifier: ^0.18.1
version: link:../../packages/starlight
astro:
- specifier: ^4.3.4
- version: 4.3.4(@types/node@18.16.19)
+ specifier: ^4.3.5
+ version: 4.3.5(@types/node@18.16.19)
sharp:
specifier: ^0.32.5
version: 0.32.6
@@ -104,10 +104,10 @@ importers:
version: link:../../packages/tailwind
'@astrojs/tailwind':
specifier: ^5.1.0
- version: 5.1.0(astro@4.3.4)(tailwindcss@3.4.1)
+ version: 5.1.0(astro@4.3.5)(tailwindcss@3.4.1)
astro:
- specifier: ^4.3.4
- version: 4.3.4(@types/node@18.16.19)
+ specifier: ^4.3.5
+ version: 4.3.5(@types/node@18.16.19)
sharp:
specifier: ^0.32.5
version: 0.32.6
@@ -131,7 +131,7 @@ importers:
dependencies:
'@astrojs/mdx':
specifier: ^2.1.1
- version: 2.1.1(astro@4.3.4)
+ version: 2.1.1(astro@4.3.5)
'@astrojs/sitemap':
specifier: ^3.0.5
version: 3.0.5
@@ -146,7 +146,7 @@ importers:
version: 4.0.3
astro-expressive-code:
specifier: ^0.32.4
- version: 0.32.4(astro@4.3.4)
+ version: 0.32.4(astro@4.3.5)
bcp-47:
specifier: ^2.1.0
version: 2.1.0
@@ -194,8 +194,8 @@ importers:
specifier: ^1.2.2
version: 1.2.2(vitest@1.2.2)
astro:
- specifier: ^4.3.4
- version: 4.3.4(@types/node@18.16.19)
+ specifier: ^4.3.5
+ version: 4.3.5(@types/node@18.16.19)
vitest:
specifier: ^1.2.2
version: 1.2.2(@types/node@18.16.19)
@@ -207,7 +207,7 @@ importers:
version: link:../starlight
'@astrojs/tailwind':
specifier: ^5.0.0
- version: 5.1.0(astro@4.3.4)(tailwindcss@3.4.1)
+ version: 5.1.0(astro@4.3.5)(tailwindcss@3.4.1)
tailwindcss:
specifier: ^3.3.3
version: 3.4.1
@@ -370,12 +370,12 @@ packages:
'@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.19
- /@astro-community/astro-embed-youtube@0.4.4(astro@4.3.4):
+ /@astro-community/astro-embed-youtube@0.4.4(astro@4.3.5):
resolution: {integrity: sha512-fYlycLrJFNnibZ9VHPSJO766kO2IgqYQU4mBd4iaDMaicL0gGX9cVZ80QdnpzGrI6w0XOJOY7prx86eWEVBy8w==}
peerDependencies:
astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta
dependencies:
- astro: 4.3.4(@types/node@18.16.19)
+ astro: 4.3.5(@types/node@18.16.19)
lite-youtube-embed: 0.2.0
dev: false
@@ -409,7 +409,7 @@ packages:
transitivePeerDependencies:
- supports-color
- /@astrojs/mdx@2.1.1(astro@4.3.4):
+ /@astrojs/mdx@2.1.1(astro@4.3.5):
resolution: {integrity: sha512-AgGFdE7HOGmoFooGvMSatkA9FiSKwyVW7ImHot/bXJ6uAbFfu6iG2ht18Cf1pT22Hda/6iSCGWusFvBv0/EnKQ==}
engines: {node: '>=18.14.1'}
peerDependencies:
@@ -418,7 +418,7 @@ packages:
'@astrojs/markdown-remark': 4.2.1
'@mdx-js/mdx': 3.0.0
acorn: 8.11.3
- astro: 4.3.4(@types/node@18.16.19)
+ astro: 4.3.5(@types/node@18.16.19)
es-module-lexer: 1.4.1
estree-util-visit: 2.0.0
github-slugger: 2.0.0
@@ -448,13 +448,13 @@ packages:
zod: 3.22.4
dev: false
- /@astrojs/tailwind@5.1.0(astro@4.3.4)(tailwindcss@3.4.1):
+ /@astrojs/tailwind@5.1.0(astro@4.3.5)(tailwindcss@3.4.1):
resolution: {integrity: sha512-BJoCDKuWhU9FT2qYg+fr6Nfb3qP4ShtyjXGHKA/4mHN94z7BGcmauQK23iy+YH5qWvTnhqkd6mQPQ1yTZTe9Ig==}
peerDependencies:
astro: ^3.0.0 || ^4.0.0
tailwindcss: ^3.0.24
dependencies:
- astro: 4.3.4(@types/node@18.16.19)
+ astro: 4.3.5(@types/node@18.16.19)
autoprefixer: 10.4.15(postcss@8.4.33)
postcss: 8.4.33
postcss-load-config: 4.0.2(postcss@8.4.33)
@@ -1873,18 +1873,18 @@ packages:
hasBin: true
dev: false
- /astro-expressive-code@0.32.4(astro@4.3.4):
+ /astro-expressive-code@0.32.4(astro@4.3.5):
resolution: {integrity: sha512-/Kq8wLMz0X2gbLWGmPryqEdFV/om/GROsoLtPFqLrLCRD5CpwxXAW185BIGZKf4iYsyJim1vvcpQm5Y9hV5B1g==}
peerDependencies:
astro: ^3.3.0 || ^4.0.0-beta
dependencies:
- astro: 4.3.4(@types/node@18.16.19)
+ astro: 4.3.5(@types/node@18.16.19)
hast-util-to-html: 8.0.4
remark-expressive-code: 0.32.4
dev: false
- /astro@4.3.4(@types/node@18.16.19):
- resolution: {integrity: sha512-BWzGGn/PuwmT0DWX+yXa/jUq99e85AGh9/C5IFAKIfp22Nk88dOfX89RoqKBWPPp2BrK2vsdCFd0WUv8XJh80w==}
+ /astro@4.3.5(@types/node@18.16.19):
+ resolution: {integrity: sha512-7jPffNlcmDO94NlkWe/hUWta/pIjlx1LVD/DZb/fyjT1Jv+7mGhKZBIjkDfeVpequW70mep8cAS5RM7Pxa0Gdg==}
engines: {node: '>=18.14.1', npm: '>=6.14.0'}
hasBin: true
dependencies:
@@ -6125,7 +6125,7 @@ packages:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
dev: true
- /starlight-links-validator@0.5.3(@astrojs/starlight@packages+starlight)(astro@4.3.4):
+ /starlight-links-validator@0.5.3(@astrojs/starlight@packages+starlight)(astro@4.3.5):
resolution: {integrity: sha512-v79rwmzjQlEMVL8sZ4dalD/jhFOUvGZ2/f4CvxCySZ9KbEN9nDmgV8zJgfpmTzhbcYQ35wzyUinF4QNxgKVA4g==}
engines: {node: '>=18.14.1'}
peerDependencies:
@@ -6133,7 +6133,7 @@ packages:
astro: '>=4.0.0'
dependencies:
'@astrojs/starlight': link:packages/starlight
- astro: 4.3.4(@types/node@18.16.19)
+ astro: 4.3.5(@types/node@18.16.19)
github-slugger: 2.0.0
hast-util-from-html: 2.0.1
hast-util-has-property: 3.0.0