Skip to content

Commit 3e0a88c

Browse files
authored
fix: skip virtual file without a path (#113)
1 parent affd2ac commit 3e0a88c

File tree

7 files changed

+197
-137
lines changed

7 files changed

+197
-137
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'starlight-links-validator': patch
3+
---
4+
5+
Prevents plugin remark plugin from running on Markdown and MDX content when using the Astro [`renderMarkdown()`](https://docs.astro.build/en/reference/content-loader-reference/#rendermarkdown) content loader API.
Lines changed: 3 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,19 @@
11
import type { StarlightPlugin } from '@astrojs/starlight/types'
22
import type { IntegrationResolvedRoute } from 'astro'
33
import { AstroError } from 'astro/errors'
4-
import { z } from 'astro/zod'
54

65
import { clearContentLayerCache } from './libs/astro'
6+
import { StarlightLinksValidatorOptionsSchema, type StarlightLinksValidatorUserOptions } from './libs/config'
77
import { pathnameToSlug, stripTrailingSlash } from './libs/path'
88
import { remarkStarlightLinksValidator, type RemarkStarlightLinksValidatorConfig } from './libs/remark'
99
import { logErrors, validateLinks } from './libs/validation'
1010

11-
const starlightLinksValidatorOptionsSchema = z
12-
.object({
13-
/**
14-
* Defines a list of additional components and their props that should be validated as links.
15-
*
16-
* By default, the plugin will only validate links defined in the `href` prop of the `<LinkButton>` and `<LinkCard>`
17-
* built-in Starlight components.
18-
* Adding custom components to this list will allow the plugin to validate links in those components as well.
19-
*
20-
* @default []
21-
*/
22-
components: z.tuple([z.string(), z.string()]).array().default([]),
23-
/**
24-
* Defines whether the plugin should error on fallback pages.
25-
*
26-
* If you do not expect to have all pages translated in all configured locales and want to use the fallback pages
27-
* feature built-in into Starlight, you should set this option to `false`.
28-
*
29-
* @default true
30-
* @see https://starlight.astro.build/guides/i18n/#fallback-content
31-
*/
32-
errorOnFallbackPages: z.boolean().default(true),
33-
/**
34-
* Defines whether the plugin should error on inconsistent locale links.
35-
*
36-
* When set to `true`, the plugin will error on links that are pointing to a page in a different locale.
37-
*
38-
* @default false
39-
*/
40-
errorOnInconsistentLocale: z.boolean().default(false),
41-
/**
42-
* Defines whether the plugin should error on internal relative links.
43-
*
44-
* When set to `false`, the plugin will ignore relative links (e.g. `./foo` or `../bar`).
45-
*
46-
* @default true
47-
*/
48-
errorOnRelativeLinks: z.boolean().default(true),
49-
/**
50-
* Defines whether the plugin should error on invalid hashes.
51-
*
52-
* When set to `false`, the plugin will only validate link pages and ignore hashes.
53-
*
54-
* @default true
55-
*/
56-
errorOnInvalidHashes: z.boolean().default(true),
57-
/**
58-
* Defines whether the plugin should error on local links, e.g. URLs with a hostname of `localhost` or `127.0.0.1`.
59-
*
60-
* @default true
61-
*/
62-
errorOnLocalLinks: z.boolean().default(true),
63-
/**
64-
* Defines a list of links or glob patterns that should be excluded from validation or a function that will be
65-
* called for each link to determine if it should be excluded from validation or not.
66-
*
67-
* The links in this list or links where the function returns `true` will be ignored by the plugin and will not be
68-
* validated.
69-
*
70-
* @default []
71-
*/
72-
exclude: z
73-
.union([
74-
z.array(z.string()),
75-
z
76-
.function()
77-
.args(
78-
z.object({
79-
/**
80-
* The absolute path to the file where the link is defined.
81-
*/
82-
file: z.string(),
83-
/**
84-
* The link to validate as authored in the content.
85-
*/
86-
link: z.string(),
87-
/**
88-
* The slug of the page where the link is defined.
89-
*/
90-
slug: z.string(),
91-
}),
92-
)
93-
.returns(z.boolean()),
94-
])
95-
.default([]),
96-
/**
97-
* Defines the policy for external links with an origin matching the Astro `site` option.
98-
*
99-
* By default, all external links are ignored and not validated by the plugin.
100-
* Setting this option to `error` will make the plugin error on external links with an origin matching the Astro
101-
* `site` option and hint that the link can be rewritten without the origin.
102-
* Setting this option to `validate` will make the plugin validate external links with an origin matching the Astro
103-
* `site` option as if they were internal links.
104-
*
105-
* @default 'ignore'
106-
* @see https://docs.astro.build/en/reference/configuration-reference/#site
107-
* @see https://developer.mozilla.org/en-US/docs/Web/API/URL/origin
108-
*/
109-
sameSitePolicy: z.enum(['error', 'ignore', 'validate']).default('ignore'),
110-
})
111-
.default({})
11+
export type { StarlightLinksValidatorOptions } from './libs/config'
11212

11313
export default function starlightLinksValidatorPlugin(
11414
userOptions?: StarlightLinksValidatorUserOptions,
11515
): StarlightPlugin {
116-
const options = starlightLinksValidatorOptionsSchema.safeParse(userOptions)
16+
const options = StarlightLinksValidatorOptionsSchema.safeParse(userOptions)
11717

11818
if (!options.success) {
11919
throwPluginError('Invalid options passed to the starlight-links-validator plugin.')
@@ -195,6 +95,3 @@ function throwPluginError(message: string, additionalHint?: string): never {
19595

19696
throw new AstroError(message, hint)
19797
}
198-
199-
type StarlightLinksValidatorUserOptions = z.input<typeof starlightLinksValidatorOptionsSchema>
200-
export type StarlightLinksValidatorOptions = z.output<typeof starlightLinksValidatorOptionsSchema>
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { z } from 'astro/zod'
2+
3+
export const StarlightLinksValidatorOptionsSchema = z
4+
.object({
5+
/**
6+
* Defines a list of additional components and their props that should be validated as links.
7+
*
8+
* By default, the plugin will only validate links defined in the `href` prop of the `<LinkButton>` and `<LinkCard>`
9+
* built-in Starlight components.
10+
* Adding custom components to this list will allow the plugin to validate links in those components as well.
11+
*
12+
* @default []
13+
*/
14+
components: z.tuple([z.string(), z.string()]).array().default([]),
15+
/**
16+
* Defines whether the plugin should error on fallback pages.
17+
*
18+
* If you do not expect to have all pages translated in all configured locales and want to use the fallback pages
19+
* feature built-in into Starlight, you should set this option to `false`.
20+
*
21+
* @default true
22+
* @see https://starlight.astro.build/guides/i18n/#fallback-content
23+
*/
24+
errorOnFallbackPages: z.boolean().default(true),
25+
/**
26+
* Defines whether the plugin should error on inconsistent locale links.
27+
*
28+
* When set to `true`, the plugin will error on links that are pointing to a page in a different locale.
29+
*
30+
* @default false
31+
*/
32+
errorOnInconsistentLocale: z.boolean().default(false),
33+
/**
34+
* Defines whether the plugin should error on internal relative links.
35+
*
36+
* When set to `false`, the plugin will ignore relative links (e.g. `./foo` or `../bar`).
37+
*
38+
* @default true
39+
*/
40+
errorOnRelativeLinks: z.boolean().default(true),
41+
/**
42+
* Defines whether the plugin should error on invalid hashes.
43+
*
44+
* When set to `false`, the plugin will only validate link pages and ignore hashes.
45+
*
46+
* @default true
47+
*/
48+
errorOnInvalidHashes: z.boolean().default(true),
49+
/**
50+
* Defines whether the plugin should error on local links, e.g. URLs with a hostname of `localhost` or `127.0.0.1`.
51+
*
52+
* @default true
53+
*/
54+
errorOnLocalLinks: z.boolean().default(true),
55+
/**
56+
* Defines a list of links or glob patterns that should be excluded from validation or a function that will be
57+
* called for each link to determine if it should be excluded from validation or not.
58+
*
59+
* The links in this list or links where the function returns `true` will be ignored by the plugin and will not be
60+
* validated.
61+
*
62+
* @default []
63+
*/
64+
exclude: z
65+
.union([
66+
z.array(z.string()),
67+
z
68+
.function()
69+
.args(
70+
z.object({
71+
/**
72+
* The absolute path to the file where the link is defined.
73+
*/
74+
file: z.string(),
75+
/**
76+
* The link to validate as authored in the content.
77+
*/
78+
link: z.string(),
79+
/**
80+
* The slug of the page where the link is defined.
81+
*/
82+
slug: z.string(),
83+
}),
84+
)
85+
.returns(z.boolean()),
86+
])
87+
.default([]),
88+
/**
89+
* Defines the policy for external links with an origin matching the Astro `site` option.
90+
*
91+
* By default, all external links are ignored and not validated by the plugin.
92+
* Setting this option to `error` will make the plugin error on external links with an origin matching the Astro
93+
* `site` option and hint that the link can be rewritten without the origin.
94+
* Setting this option to `validate` will make the plugin validate external links with an origin matching the Astro
95+
* `site` option as if they were internal links.
96+
*
97+
* @default 'ignore'
98+
* @see https://docs.astro.build/en/reference/configuration-reference/#site
99+
* @see https://developer.mozilla.org/en-US/docs/Web/API/URL/origin
100+
*/
101+
sameSitePolicy: z.enum(['error', 'ignore', 'validate']).default('ignore'),
102+
})
103+
.default({})
104+
105+
export type StarlightLinksValidatorUserOptions = z.input<typeof StarlightLinksValidatorOptionsSchema>
106+
export type StarlightLinksValidatorOptions = z.output<typeof StarlightLinksValidatorOptionsSchema>

packages/starlight-links-validator/libs/remark.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export const remarkStarlightLinksValidator: Plugin<[RemarkStarlightLinksValidato
3636
)
3737

3838
return (tree, file) => {
39+
// If the content does not have a path, e.g. when rendered using the content loader `renderMarkdown()` API, skip it.
40+
if (!file.path) return
41+
3942
if (file.data.astro?.frontmatter?.['draft']) return
4043

4144
const originalPath = file.history[0]

packages/starlight-links-validator/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
"@types/mdast": "^4.0.4",
3131
"@types/node": "^18.19.68",
3232
"remark-custom-heading-id": "^2.0.0",
33+
"remark-parse": "^11.0.0",
34+
"remark-stringify": "^11.0.0",
3335
"unified": "^11.0.5",
3436
"vfile": "^6.0.3",
3537
"vitest": "2.1.6"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { fileURLToPath } from 'node:url'
2+
3+
import remarkParse from 'remark-parse'
4+
import remarkStringify from 'remark-stringify'
5+
import { unified } from 'unified'
6+
import { VFile } from 'vfile'
7+
import { expect, test } from 'vitest'
8+
9+
import { StarlightLinksValidatorOptionsSchema } from '../libs/config'
10+
import { getValidationData, remarkStarlightLinksValidator } from '../libs/remark'
11+
12+
const processor = createMarkdownProcessor()
13+
14+
test('does not run for file without a path', async () => {
15+
await renderMarkdown(`This is a test`, { url: null })
16+
17+
const validationData = getValidationData()
18+
19+
expect(validationData.size).toBe(0)
20+
})
21+
22+
function renderMarkdown(content: string, options?: { url?: URL | null }) {
23+
return processor.process(
24+
new VFile({
25+
path: options?.url === null ? undefined : fileURLToPath(new URL(`src/content/docs/index.md`, import.meta.url)),
26+
value: content,
27+
}),
28+
)
29+
}
30+
31+
function createMarkdownProcessor() {
32+
return unified()
33+
.use(remarkParse)
34+
.use(remarkStarlightLinksValidator, {
35+
base: '/',
36+
options: StarlightLinksValidatorOptionsSchema.parse({}),
37+
site: 'https://example.com',
38+
srcDir: new URL('src/content/docs/', import.meta.url),
39+
})
40+
.use(remarkStringify)
41+
}

0 commit comments

Comments
 (0)