Repository navigation
Conversation
🦋 Changeset detectedLatest commit: 14b5364 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for astro-starlight ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
size-limit report 📦
|
| filePath.ext === '.json' | ||
| ? JSON.parse(content) | ||
| : yaml.load(content, { filename: fileURLToPath(url) }) | ||
| filePath.ext === '.json' ? JSON.parse(content) : parse(content) |
There was a problem hiding this comment.
js-yaml’s load() method accepted a filename to display in error messages, which yaml’s parse() method does not. I guess this will be a slight DX reduction, but perhaps it’s acceptable?
If not, we could probably wrap the parse() block here to augment any error thrown with the filename as before.
There was a problem hiding this comment.
It's nice to have if you have many translation files I guess. And we could fix it in a way that also improves the DX for JSON as they don't have paths. Maybe something like:
for (const file of files) {
const filePath = path.parse(file);
if (!contentCollectionFileExtensions.includes(filePath.ext)) continue;
const id = filePath.name;
const url = new URL(file, i18nDir);
const content = fs.readFileSync(url, 'utf-8');
try {
userTranslations[id] = (
filePath.ext === '.json' ? JSON.parse(content) : parse(content)
) as i18nSchemaOutput;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to parse '${fileURLToPath(url)}':\n\n${message}`, {
cause: error,
});
}
}(but this would mean updating the test to check part of the message rather than the error type)
There was a problem hiding this comment.
Updated the PR with the latest releases of astro & co. to confirm js-yaml is now gone from the lock file. Hit a few small details while doing this, but otherwise the basics here seem to work well.
I did not update peer deps as well: everything should continue to work with the current peers, but people can get rid of some more deps by updating astro at the same time they update Starlight. This also lets us keep this a patch change.
| if (!shouldTransformPath(fileURL, allowedPaths)) return; | ||
|
|
||
| const plugins = [satteriRtlCodeSupportPlugin()]; | ||
| const plugins: HastPluginEntry[] = [satteriRtlCodeSupportPlugin()]; |
There was a problem hiding this comment.
Not directly related to the YAML chanegs, but a typing change required by updating astro.
| relativePath ? `${relativePath}: ${issue.expected}` : issue.expected | ||
| ); | ||
| } else if (issue.code === 'custom') { | ||
| } else if (issue.code === 'custom' && relativePath) { |
There was a problem hiding this comment.
I didn’t have time to dig further, but seems like a change in Zod means that we now sometimes get a relativePath of "" for custom errors, which ends up getting added to the union display like SomeType | | SomeOtherType with the empty string joined by pipes as if it were a possible type in the union.
I’d still like to investigate further as IIUC the specific case we were hitting this was with the custom error here:
starlight/packages/starlight/src/schemas/sidebar.ts
Lines 106 to 126 in ab8f8a3
If I’m not mistaken, this message is not currently actually displayed, perhaps because it was silently swallowed by this part of the error map?
There was a problem hiding this comment.
It's probably the last sentence of this change.
Separately, an
unrecognized_keysissue no longer aborts the schema it came from, so a strict object with an extra key and a bad value now reports both issues instead of just the first.
I guess we could add a comment to the change, maybe something like:
// Ignore custom issues on the value of the union option as they have no path
// associated with them to show in the expected type.
// Such issues come from `z.custom()` or `superRefine()` and since Zod 4.5,
// `superRefine()` runs even when a strict object has unknown keys.
HiDeoo
left a comment
There was a problem hiding this comment.
I left a few comments but overall, the changes look good to me. Thanks for the PR 🙌
| "@astrojs/starlight": patch | ||
| --- | ||
|
|
||
| Reduces install size by ~400 KB by switching the YAML parser used internally |
There was a problem hiding this comment.
| Reduces install size by ~400 KB by switching the YAML parser used internally | |
| Reduces install size by ~400 KB by switching the YAML parser used internally for projects using Astro 7.3.6 or later |
Maybe to incentivize users to upgrade, we could mention the requirement?
| }; | ||
| const code = | ||
| source === 'config' ? JSON.stringify(correctTag, null, 2) : yaml.dump([correctTag]); | ||
| source === 'config' ? JSON.stringify(correctTag, null, 2) : stringify([correctTag]); |
There was a problem hiding this comment.
| source === 'config' ? JSON.stringify(correctTag, null, 2) : stringify([correctTag]); | |
| source === 'config' | |
| ? JSON.stringify(correctTag, null, 2) | |
| : stringify([correctTag], { customTags: ['timestamp'] }); |
I think we need to use the same timestamp custom tag as Astro does.
Let's imagine the following invalid frontmatter:
head:
- tag: meta
attrs:
name: date
content: '2026-10-07'You get the following error and hint:
head.0: The `head` configuration includes a `meta` tag with `content` which is invalid HTML.
You should instead use a `content` attribute in the `attrs` object:
- tag: meta
attrs:
name: date
content: 2026-10-07
But if you copy the hint, you get a second error due to the content now missing the quotes and Astro parsing it as a date.
head.0.attrs.content**: **head.0.attrs.content: Did not match union.
> Expected type `"string"`, received `"object"`
Using { customTags: ['timestamp'] } shows the proper hint:
head.0: The `head` configuration includes a `meta` tag with `content` which is invalid HTML.
You should instead use a `content` attribute in the `attrs` object:
- tag: meta
attrs:
name: date
content: "2026-10-07"
I guess if we want a test, we could do something like:
test('suggests valid YAML for date-like `meta` content', () => {
// Parse an invalid head config and get the raw error message.
const result = HeadConfigSchema({ source: 'content' }).safeParse([
{ tag: 'meta', attrs: { name: 'date' }, content: '2026-10-07' },
]);
// Extract the suggested YAML snippet from the error message.
const suggestion = result.error?.issues[0]?.message.split('\n\n').at(-1);
// Parse the suggestion like Astro parses frontmatter YAML.
// https://github.com/withastro/astro/blob/88f0bd642b082274a57999bb9e74f89d7477e47e/packages/internal-helpers/src/yaml.ts#L4-L8
const parsed: unknown = parse(suggestion ?? '', {
customTags: ['timestamp'],
merge: true,
schema: 'core',
});
expect(parsed).toEqual([{ tag: 'meta', attrs: { name: 'date', content: '2026-10-07' } }]);
});There was a problem hiding this comment.
Oh wow, great find! I definitely completely missed that detail in the Astro migration PR. Will adjust.
There was a problem hiding this comment.
Lucky coincidence that I hit a similar issue in a plugin using the same dep, feels like déjà vu 😅
| relativePath ? `${relativePath}: ${issue.expected}` : issue.expected | ||
| ); | ||
| } else if (issue.code === 'custom') { | ||
| } else if (issue.code === 'custom' && relativePath) { |
There was a problem hiding this comment.
It's probably the last sentence of this change.
Separately, an
unrecognized_keysissue no longer aborts the schema it came from, so a strict object with an extra key and a bad value now reports both issues instead of just the first.
I guess we could add a comment to the change, maybe something like:
// Ignore custom issues on the value of the union option as they have no path
// associated with them to show in the expected type.
// Such issues come from `z.custom()` or `superRefine()` and since Zod 4.5,
// `superRefine()` runs even when a strict object has unknown keys.
| filePath.ext === '.json' | ||
| ? JSON.parse(content) | ||
| : yaml.load(content, { filename: fileURLToPath(url) }) | ||
| filePath.ext === '.json' ? JSON.parse(content) : parse(content) |
There was a problem hiding this comment.
It's nice to have if you have many translation files I guess. And we could fix it in a way that also improves the DX for JSON as they don't have paths. Maybe something like:
for (const file of files) {
const filePath = path.parse(file);
if (!contentCollectionFileExtensions.includes(filePath.ext)) continue;
const id = filePath.name;
const url = new URL(file, i18nDir);
const content = fs.readFileSync(url, 'utf-8');
try {
userTranslations[id] = (
filePath.ext === '.json' ? JSON.parse(content) : parse(content)
) as i18nSchemaOutput;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to parse '${fileURLToPath(url)}':\n\n${message}`, {
cause: error,
});
}
}(but this would mean updating the test to check part of the message rather than the error type)

Description
js-yamldependency in@astrojs/starlightwithyamlas recommended by e18e. (yamlis significantly smaller and we have been avoiding updatingjs-yamlto latest in both Starlight and Astro due to recent increases in install size.)astroas a whole to avoid users having bothjs-yamlandyamlin the dependency tree.yamlformats stringified data by default. We could avoid it by settingsingleQuotein the stringify options, but it seemed like there was no need to enforce the old style necessarily so we may as well use the default ofyaml.To-do
astroonce Switch from js-yaml to yaml astro#18099 is released to confirmjs-yamlis no longer present in the lock file. Release PR: [ci] release astro#18120