starlight-links-validator 0.14.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/index.ts +38 -5
- package/libs/i18n.ts +10 -4
- package/libs/remark.ts +66 -23
- package/libs/validation.ts +25 -20
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# starlight-links-validator
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#93](https://github.com/HiDeoo/starlight-links-validator/pull/93) [`6d7174b`](https://github.com/HiDeoo/starlight-links-validator/commit/6d7174bcc6a2bb39f287a50bbdda29a6af4c16c8) Thanks [@HiDeoo](https://github.com/HiDeoo)! - ⚠️ **BREAKING CHANGE:** The minimum supported version of Starlight is now version `0.32.0`.
|
|
8
|
+
|
|
9
|
+
Please use the `@astrojs/upgrade` command to upgrade your project:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npx @astrojs/upgrade
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- [#100](https://github.com/HiDeoo/starlight-links-validator/pull/100) [`b238cb7`](https://github.com/HiDeoo/starlight-links-validator/commit/b238cb7bd3db5f8fe848c317ba52d5ab44eb853e) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds a new [`sameSitePolicy` option](https://starlight-links-validator.vercel.app/configuration#samesitepolicy) to configure how external links pointing to the same origin as the one configured in the [Astro `site` option](https://docs.astro.build/en/reference/configuration-reference/#site) should be handled.
|
|
16
|
+
|
|
17
|
+
The current default behavior to ignore all external links remains unchanged. This new option allows to error on such links so they can be rewritten without the origin or to validate them as if they were internal links.
|
|
18
|
+
|
|
19
|
+
- [#100](https://github.com/HiDeoo/starlight-links-validator/pull/100) [`b238cb7`](https://github.com/HiDeoo/starlight-links-validator/commit/b238cb7bd3db5f8fe848c317ba52d5ab44eb853e) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds a new [`components`](https://starlight-links-validator.vercel.app/configuration#components) option to define additional components and their props to validate as links on top of the built-in `<LinkButton>` and `<LinkCard>` Starlight components.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#99](https://github.com/HiDeoo/starlight-links-validator/pull/99) [`56ea78c`](https://github.com/HiDeoo/starlight-links-validator/commit/56ea78cefa40f554f88a32181daae1a82ec2fa9a) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes validation issue with the [Astro `base` option](https://docs.astro.build/en/reference/configuration-reference/#base) and the [`errorOnFallbackPages` plugin option](https://starlight-links-validator.vercel.app/configuration#erroronfallbackpages) set to `false` in a multilingual project.
|
|
24
|
+
|
|
3
25
|
## 0.14.3
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/index.ts
CHANGED
|
@@ -4,12 +4,22 @@ import { AstroError } from 'astro/errors'
|
|
|
4
4
|
import { z } from 'astro/zod'
|
|
5
5
|
|
|
6
6
|
import { clearContentLayerCache } from './libs/astro'
|
|
7
|
-
import { pathnameToSlug } from './libs/path'
|
|
8
|
-
import { remarkStarlightLinksValidator } from './libs/remark'
|
|
7
|
+
import { pathnameToSlug, stripTrailingSlash } from './libs/path'
|
|
8
|
+
import { remarkStarlightLinksValidator, type RemarkStarlightLinksValidatorConfig } from './libs/remark'
|
|
9
9
|
import { logErrors, validateLinks } from './libs/validation'
|
|
10
10
|
|
|
11
11
|
const starlightLinksValidatorOptionsSchema = z
|
|
12
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([]),
|
|
13
23
|
/**
|
|
14
24
|
* Defines whether the plugin should error on fallback pages.
|
|
15
25
|
*
|
|
@@ -58,6 +68,20 @@ const starlightLinksValidatorOptionsSchema = z
|
|
|
58
68
|
* @default []
|
|
59
69
|
*/
|
|
60
70
|
exclude: z.array(z.string()).default([]),
|
|
71
|
+
/**
|
|
72
|
+
* Defines the policy for external links with an origin matching the Astro `site` option.
|
|
73
|
+
*
|
|
74
|
+
* By default, all external links are ignored and not validated by the plugin.
|
|
75
|
+
* Setting this option to `error` will make the plugin error on external links with an origin matching the Astro
|
|
76
|
+
* `site` option and hint that the link can be rewritten without the origin.
|
|
77
|
+
* Setting this option to `validate` will make the plugin validate external links with an origin matching the Astro
|
|
78
|
+
* `site` option as if they were internal links.
|
|
79
|
+
*
|
|
80
|
+
* @default 'ignore'
|
|
81
|
+
* @see https://docs.astro.build/en/reference/configuration-reference/#site
|
|
82
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/URL/origin
|
|
83
|
+
*/
|
|
84
|
+
sameSitePolicy: z.enum(['error', 'ignore', 'validate']).default('ignore'),
|
|
61
85
|
})
|
|
62
86
|
.default({})
|
|
63
87
|
|
|
@@ -73,8 +97,9 @@ export default function starlightLinksValidatorPlugin(
|
|
|
73
97
|
return {
|
|
74
98
|
name: 'starlight-links-validator-plugin',
|
|
75
99
|
hooks: {
|
|
76
|
-
setup({ addIntegration, astroConfig, config: starlightConfig, logger }) {
|
|
100
|
+
'config:setup'({ addIntegration, astroConfig, config: starlightConfig, logger }) {
|
|
77
101
|
let routes: IntegrationResolvedRoute[] = []
|
|
102
|
+
const site = astroConfig.site ? stripTrailingSlash(astroConfig.site) : undefined
|
|
78
103
|
|
|
79
104
|
addIntegration({
|
|
80
105
|
name: 'starlight-links-validator-integration',
|
|
@@ -89,7 +114,15 @@ export default function starlightLinksValidatorPlugin(
|
|
|
89
114
|
updateConfig({
|
|
90
115
|
markdown: {
|
|
91
116
|
remarkPlugins: [
|
|
92
|
-
[
|
|
117
|
+
[
|
|
118
|
+
remarkStarlightLinksValidator,
|
|
119
|
+
{
|
|
120
|
+
base: astroConfig.base,
|
|
121
|
+
options: options.data,
|
|
122
|
+
site,
|
|
123
|
+
srcDir: astroConfig.srcDir,
|
|
124
|
+
} satisfies RemarkStarlightLinksValidatorConfig,
|
|
125
|
+
],
|
|
93
126
|
],
|
|
94
127
|
},
|
|
95
128
|
})
|
|
@@ -111,7 +144,7 @@ export default function starlightLinksValidatorPlugin(
|
|
|
111
144
|
|
|
112
145
|
const errors = validateLinks(pages, customPages, dir, astroConfig, starlightConfig, options.data)
|
|
113
146
|
|
|
114
|
-
const hasInvalidLinkToCustomPage = logErrors(logger, errors)
|
|
147
|
+
const hasInvalidLinkToCustomPage = logErrors(logger, errors, site)
|
|
115
148
|
|
|
116
149
|
if (errors.size > 0) {
|
|
117
150
|
throwPluginError(
|
package/libs/i18n.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ensureLeadingSlash, ensureTrailingSlash } from './path'
|
|
1
|
+
import { ensureLeadingSlash, ensureTrailingSlash, stripLeadingSlash } from './path'
|
|
2
2
|
import type { Headings } from './remark'
|
|
3
3
|
import type { StarlightUserConfig } from './validation'
|
|
4
4
|
|
|
@@ -32,14 +32,20 @@ export function getFallbackHeadings(
|
|
|
32
32
|
path: string,
|
|
33
33
|
headings: Headings,
|
|
34
34
|
localeConfig: LocaleConfig | undefined,
|
|
35
|
+
base: string,
|
|
35
36
|
): string[] | undefined {
|
|
36
37
|
if (!localeConfig) return
|
|
37
38
|
|
|
39
|
+
const isPathWithBase = base !== '/'
|
|
40
|
+
const normalizedBase = isPathWithBase ? ensureTrailingSlash(stripLeadingSlash(base)) : ''
|
|
41
|
+
const normalizedPath = isPathWithBase ? path.replace(normalizedBase, '') : path
|
|
42
|
+
|
|
38
43
|
for (const locale of localeConfig.locales) {
|
|
39
|
-
if (
|
|
44
|
+
if (normalizedPath.startsWith(`${locale}/`)) {
|
|
40
45
|
const fallbackPath = path.replace(
|
|
41
|
-
new RegExp(`^${locale}/`),
|
|
42
|
-
|
|
46
|
+
new RegExp(`^${normalizedBase}${locale}/`),
|
|
47
|
+
normalizedBase +
|
|
48
|
+
(localeConfig.defaultLocale === '' ? localeConfig.defaultLocale : `${localeConfig.defaultLocale}/`),
|
|
43
49
|
)
|
|
44
50
|
|
|
45
51
|
return headings.get(fallbackPath === '' ? '/' : fallbackPath)
|
package/libs/remark.ts
CHANGED
|
@@ -3,6 +3,7 @@ import 'mdast-util-mdx-jsx'
|
|
|
3
3
|
import nodePath from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
|
|
6
|
+
import type { AstroConfig } from 'astro'
|
|
6
7
|
import GitHubSlugger, { slug } from 'github-slugger'
|
|
7
8
|
import type { Nodes } from 'hast'
|
|
8
9
|
import { fromHtml } from 'hast-util-from-html'
|
|
@@ -14,17 +15,28 @@ import { toString } from 'mdast-util-to-string'
|
|
|
14
15
|
import type { Plugin } from 'unified'
|
|
15
16
|
import { visit } from 'unist-util-visit'
|
|
16
17
|
|
|
18
|
+
import type { StarlightLinksValidatorOptions } from '..'
|
|
19
|
+
|
|
17
20
|
import { ensureTrailingSlash, stripLeadingSlash } from './path'
|
|
21
|
+
import { ValidationErrorType } from './validation'
|
|
22
|
+
|
|
23
|
+
const builtInComponents: StarlightLinksValidatorOptions['components'] = [
|
|
24
|
+
['LinkButton', 'href'],
|
|
25
|
+
['LinkCard', 'href'],
|
|
26
|
+
]
|
|
18
27
|
|
|
19
28
|
// All the headings keyed by file path.
|
|
20
29
|
const headings: Headings = new Map()
|
|
21
30
|
// All the internal links keyed by file path.
|
|
22
31
|
const links: Links = new Map()
|
|
23
32
|
|
|
24
|
-
export const remarkStarlightLinksValidator: Plugin<[
|
|
25
|
-
base,
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
export const remarkStarlightLinksValidator: Plugin<[RemarkStarlightLinksValidatorConfig], Root> = function (config) {
|
|
34
|
+
const { base, options, srcDir } = config
|
|
35
|
+
|
|
36
|
+
const linkComponents: Record<string, string> = Object.fromEntries(
|
|
37
|
+
[...builtInComponents, ...options.components].map(([name, attribute]) => [name, attribute]),
|
|
38
|
+
)
|
|
39
|
+
|
|
28
40
|
return (tree, file) => {
|
|
29
41
|
if (file.data.astro?.frontmatter?.['draft']) return
|
|
30
42
|
|
|
@@ -34,7 +46,7 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
|
|
|
34
46
|
typeof file.data.astro?.frontmatter?.['slug'] === 'string' ? file.data.astro.frontmatter['slug'] : undefined
|
|
35
47
|
|
|
36
48
|
const fileHeadings: string[] = []
|
|
37
|
-
const fileLinks:
|
|
49
|
+
const fileLinks: Link[] = []
|
|
38
50
|
const fileDefinitions = new Map<string, string>()
|
|
39
51
|
|
|
40
52
|
visit(tree, 'definition', (node) => {
|
|
@@ -64,18 +76,17 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
|
|
|
64
76
|
break
|
|
65
77
|
}
|
|
66
78
|
case 'link': {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
79
|
+
const link = getLinkToValidate(node.url, config)
|
|
80
|
+
if (link) fileLinks.push(link)
|
|
70
81
|
|
|
71
82
|
break
|
|
72
83
|
}
|
|
73
84
|
case 'linkReference': {
|
|
74
85
|
const definition = fileDefinitions.get(node.identifier)
|
|
86
|
+
if (!definition) break
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
88
|
+
const link = getLinkToValidate(definition, config)
|
|
89
|
+
if (link) fileLinks.push(link)
|
|
79
90
|
|
|
80
91
|
break
|
|
81
92
|
}
|
|
@@ -86,22 +97,27 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
|
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
99
|
|
|
89
|
-
if (node.name
|
|
100
|
+
if (!node.name) {
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const componentProp = linkComponents[node.name]
|
|
105
|
+
|
|
106
|
+
if (node.name !== 'a' && !componentProp) {
|
|
90
107
|
break
|
|
91
108
|
}
|
|
92
109
|
|
|
93
110
|
for (const attribute of node.attributes) {
|
|
94
111
|
if (
|
|
95
112
|
attribute.type !== 'mdxJsxAttribute' ||
|
|
96
|
-
attribute.name !== 'href' ||
|
|
113
|
+
attribute.name !== (componentProp ?? 'href') ||
|
|
97
114
|
typeof attribute.value !== 'string'
|
|
98
115
|
) {
|
|
99
116
|
continue
|
|
100
117
|
}
|
|
101
118
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
119
|
+
const link = getLinkToValidate(attribute.value, config)
|
|
120
|
+
if (link) fileLinks.push(link)
|
|
105
121
|
}
|
|
106
122
|
|
|
107
123
|
break
|
|
@@ -127,10 +143,10 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
|
|
|
127
143
|
htmlNode.type === 'element' &&
|
|
128
144
|
htmlNode.tagName === 'a' &&
|
|
129
145
|
hasProperty(htmlNode, 'href') &&
|
|
130
|
-
typeof htmlNode.properties.href === 'string'
|
|
131
|
-
shouldValidateLink(htmlNode.properties.href)
|
|
146
|
+
typeof htmlNode.properties.href === 'string'
|
|
132
147
|
) {
|
|
133
|
-
|
|
148
|
+
const link = getLinkToValidate(htmlNode.properties.href, config)
|
|
149
|
+
if (link) fileLinks.push(link)
|
|
134
150
|
}
|
|
135
151
|
})
|
|
136
152
|
|
|
@@ -148,17 +164,31 @@ export function getValidationData() {
|
|
|
148
164
|
return { headings, links }
|
|
149
165
|
}
|
|
150
166
|
|
|
151
|
-
function
|
|
167
|
+
function getLinkToValidate(link: string, { options, site }: RemarkStarlightLinksValidatorConfig): Link | undefined {
|
|
168
|
+
const linkTovalidate = { raw: link }
|
|
169
|
+
|
|
152
170
|
if (!isAbsoluteUrl(link)) {
|
|
153
|
-
return
|
|
171
|
+
return linkTovalidate
|
|
154
172
|
}
|
|
155
173
|
|
|
156
174
|
try {
|
|
157
175
|
const url = new URL(link)
|
|
158
176
|
|
|
177
|
+
if (options.sameSitePolicy !== 'ignore' && url.origin === site) {
|
|
178
|
+
if (options.sameSitePolicy === 'error') {
|
|
179
|
+
return { ...linkTovalidate, error: ValidationErrorType.SameSite }
|
|
180
|
+
} else {
|
|
181
|
+
let transformed = link.replace(url.origin, '')
|
|
182
|
+
if (!transformed) transformed = '/'
|
|
183
|
+
return { ...linkTovalidate, transformed }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
159
187
|
return url.hostname === 'localhost' || url.hostname === '127.0.0.1'
|
|
188
|
+
? { ...linkTovalidate, error: ValidationErrorType.LocalLink }
|
|
189
|
+
: undefined
|
|
160
190
|
} catch {
|
|
161
|
-
return
|
|
191
|
+
return undefined
|
|
162
192
|
}
|
|
163
193
|
}
|
|
164
194
|
|
|
@@ -195,8 +225,21 @@ function isMdxIdAttribute(attribute: MdxJsxAttribute | MdxJsxExpressionAttribute
|
|
|
195
225
|
return attribute.type === 'mdxJsxAttribute' && attribute.name === 'id' && typeof attribute.value === 'string'
|
|
196
226
|
}
|
|
197
227
|
|
|
228
|
+
export interface RemarkStarlightLinksValidatorConfig {
|
|
229
|
+
base: string
|
|
230
|
+
options: StarlightLinksValidatorOptions
|
|
231
|
+
site: AstroConfig['site']
|
|
232
|
+
srcDir: URL
|
|
233
|
+
}
|
|
234
|
+
|
|
198
235
|
export type Headings = Map<string, string[]>
|
|
199
|
-
export type Links = Map<string,
|
|
236
|
+
export type Links = Map<string, Link[]>
|
|
237
|
+
|
|
238
|
+
export interface Link {
|
|
239
|
+
error?: ValidationErrorType
|
|
240
|
+
raw: string
|
|
241
|
+
transformed?: string
|
|
242
|
+
}
|
|
200
243
|
|
|
201
244
|
interface MdxIdAttribute {
|
|
202
245
|
name: 'id'
|
package/libs/validation.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { StarlightLinksValidatorOptions } from '..'
|
|
|
11
11
|
|
|
12
12
|
import { getFallbackHeadings, getLocaleConfig, isInconsistentLocaleLink, type LocaleConfig } from './i18n'
|
|
13
13
|
import { ensureTrailingSlash, stripLeadingSlash, stripTrailingSlash } from './path'
|
|
14
|
-
import { getValidationData, type Headings } from './remark'
|
|
14
|
+
import { getValidationData, type Headings, type Link } from './remark'
|
|
15
15
|
|
|
16
16
|
export const ValidationErrorType = {
|
|
17
17
|
InconsistentLocale: 'inconsistent locale',
|
|
@@ -20,6 +20,7 @@ export const ValidationErrorType = {
|
|
|
20
20
|
InvalidLinkToCustomPage: 'invalid link to custom page',
|
|
21
21
|
LocalLink: 'local link',
|
|
22
22
|
RelativeLink: 'relative link',
|
|
23
|
+
SameSite: '{{site}} can be omitted',
|
|
23
24
|
TrailingSlashMissing: 'missing trailing slash',
|
|
24
25
|
TrailingSlashForbidden: 'forbidden trailing slash',
|
|
25
26
|
} as const
|
|
@@ -63,7 +64,7 @@ export function validateLinks(
|
|
|
63
64
|
pages: allPages,
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
if (link.startsWith('#') || link.startsWith('?')) {
|
|
67
|
+
if (link.raw.startsWith('#') || link.raw.startsWith('?')) {
|
|
67
68
|
if (options.errorOnInvalidHashes) {
|
|
68
69
|
validateSelfHash(validationContext)
|
|
69
70
|
}
|
|
@@ -76,7 +77,7 @@ export function validateLinks(
|
|
|
76
77
|
return errors
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
export function logErrors(pluginLogger: AstroIntegrationLogger, errors: ValidationErrors) {
|
|
80
|
+
export function logErrors(pluginLogger: AstroIntegrationLogger, errors: ValidationErrors, site: AstroConfig['site']) {
|
|
80
81
|
const logger = pluginLogger.fork('')
|
|
81
82
|
|
|
82
83
|
if (errors.size === 0) {
|
|
@@ -103,7 +104,7 @@ export function logErrors(pluginLogger: AstroIntegrationLogger, errors: Validati
|
|
|
103
104
|
for (const [index, validationError] of validationErrors.entries()) {
|
|
104
105
|
logger.info(
|
|
105
106
|
` ${blue(`${index < validationErrors.length - 1 ? '├' : '└'}─`)} ${validationError.link}${dim(
|
|
106
|
-
` - ${validationError
|
|
107
|
+
` - ${formatValidationError(validationError, site)}`,
|
|
107
108
|
)}`,
|
|
108
109
|
)
|
|
109
110
|
hasInvalidLinkToCustomPage = validationError.type === ValidationErrorType.InvalidLinkToCustomPage
|
|
@@ -125,15 +126,13 @@ function validateLink(context: ValidationContext) {
|
|
|
125
126
|
return
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
addError(errors, filePath, link, ValidationErrorType.LocalLink)
|
|
131
|
-
}
|
|
132
|
-
|
|
129
|
+
if (link.error) {
|
|
130
|
+
addError(errors, filePath, link, link.error)
|
|
133
131
|
return
|
|
134
132
|
}
|
|
135
133
|
|
|
136
|
-
const
|
|
134
|
+
const linkToValidate = link.transformed ?? link.raw
|
|
135
|
+
const sanitizedLink = linkToValidate.replace(/^\//, '')
|
|
137
136
|
const segments = sanitizedLink.split('#')
|
|
138
137
|
|
|
139
138
|
const path = segments[0]
|
|
@@ -143,7 +142,7 @@ function validateLink(context: ValidationContext) {
|
|
|
143
142
|
throw new Error('Failed to validate a link with no path.')
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
if (path.startsWith('.') || (!
|
|
145
|
+
if (path.startsWith('.') || (!linkToValidate.startsWith('/') && !linkToValidate.startsWith('?'))) {
|
|
147
146
|
if (options.errorOnRelativeLinks) {
|
|
148
147
|
addError(errors, filePath, link, ValidationErrorType.RelativeLink)
|
|
149
148
|
}
|
|
@@ -172,7 +171,7 @@ function validateLink(context: ValidationContext) {
|
|
|
172
171
|
return
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
if (options.errorOnInconsistentLocale && localeConfig && isInconsistentLocaleLink(filePath, link, localeConfig)) {
|
|
174
|
+
if (options.errorOnInconsistentLocale && localeConfig && isInconsistentLocaleLink(filePath, link.raw, localeConfig)) {
|
|
176
175
|
addError(errors, filePath, link, ValidationErrorType.InconsistentLocale)
|
|
177
176
|
return
|
|
178
177
|
}
|
|
@@ -195,11 +194,11 @@ function validateLink(context: ValidationContext) {
|
|
|
195
194
|
}
|
|
196
195
|
}
|
|
197
196
|
|
|
198
|
-
function getFileHeadings(path: string, { headings, localeConfig, options }: ValidationContext) {
|
|
197
|
+
function getFileHeadings(path: string, { astroConfig, headings, localeConfig, options }: ValidationContext) {
|
|
199
198
|
let heading = headings.get(path === '' ? '/' : path)
|
|
200
199
|
|
|
201
200
|
if (!options.errorOnFallbackPages && !heading && localeConfig) {
|
|
202
|
-
heading = getFallbackHeadings(path, headings, localeConfig)
|
|
201
|
+
heading = getFallbackHeadings(path, headings, localeConfig, astroConfig.base)
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
return heading
|
|
@@ -209,7 +208,7 @@ function getFileHeadings(path: string, { headings, localeConfig, options }: Vali
|
|
|
209
208
|
* Validate a link to an hash in the same page.
|
|
210
209
|
*/
|
|
211
210
|
function validateSelfHash({ errors, link, filePath, headings }: ValidationContext) {
|
|
212
|
-
const hash = link.split('#')[1] ?? link
|
|
211
|
+
const hash = link.raw.split('#')[1] ?? link.raw
|
|
213
212
|
const sanitizedHash = hash.replace(/^#/, '')
|
|
214
213
|
const fileHeadings = headings.get(filePath)
|
|
215
214
|
|
|
@@ -249,17 +248,17 @@ function isValidAsset(path: string, context: ValidationContext) {
|
|
|
249
248
|
/**
|
|
250
249
|
* Check if a link is excluded from validation by the user.
|
|
251
250
|
*/
|
|
252
|
-
function isExcludedLink(link:
|
|
253
|
-
return picomatch(context.options.exclude)(link)
|
|
251
|
+
function isExcludedLink(link: Link, context: ValidationContext) {
|
|
252
|
+
return picomatch(context.options.exclude)(link.raw)
|
|
254
253
|
}
|
|
255
254
|
|
|
256
255
|
function stripQueryString(path: string): string {
|
|
257
256
|
return path.split('?')[0] ?? path
|
|
258
257
|
}
|
|
259
258
|
|
|
260
|
-
function addError(errors: ValidationErrors, filePath: string, link:
|
|
259
|
+
function addError(errors: ValidationErrors, filePath: string, link: Link, type: ValidationErrorType) {
|
|
261
260
|
const fileErrors = errors.get(filePath) ?? []
|
|
262
|
-
fileErrors.push({ link, type })
|
|
261
|
+
fileErrors.push({ link: link.raw, type })
|
|
263
262
|
|
|
264
263
|
errors.set(filePath, fileErrors)
|
|
265
264
|
}
|
|
@@ -268,6 +267,12 @@ function pluralize(count: number, singular: string) {
|
|
|
268
267
|
return count === 1 ? singular : `${singular}s`
|
|
269
268
|
}
|
|
270
269
|
|
|
270
|
+
function formatValidationError(error: ValidationError, site: AstroConfig['site']) {
|
|
271
|
+
if (error.type !== ValidationErrorType.SameSite || !site) return error.type
|
|
272
|
+
|
|
273
|
+
return error.type.replace('{{site}}', site)
|
|
274
|
+
}
|
|
275
|
+
|
|
271
276
|
// The validation errors keyed by file path.
|
|
272
277
|
type ValidationErrors = Map<string, ValidationError[]>
|
|
273
278
|
|
|
@@ -290,7 +295,7 @@ interface ValidationContext {
|
|
|
290
295
|
errors: ValidationErrors
|
|
291
296
|
filePath: string
|
|
292
297
|
headings: Headings
|
|
293
|
-
link:
|
|
298
|
+
link: Link
|
|
294
299
|
localeConfig: LocaleConfig | undefined
|
|
295
300
|
options: StarlightLinksValidatorOptions
|
|
296
301
|
outputDir: URL
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-links-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Starlight plugin to validate internal links.",
|
|
6
6
|
"author": "HiDeoo <github@hideoo.dev> (https://hideoo.dev)",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"vitest": "2.1.6"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@astrojs/starlight": ">=0.
|
|
34
|
+
"@astrojs/starlight": ">=0.32.0"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=18.17.1"
|