starlight-links-validator 0.4.2 → 0.5.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/README.md +8 -36
- package/index.ts +77 -23
- package/libs/i18n.ts +78 -0
- package/libs/path.ts +7 -0
- package/libs/remark.ts +2 -2
- package/libs/validation.ts +112 -44
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>starlight-links-validator 🦺</h1>
|
|
3
|
-
<p>
|
|
3
|
+
<p>Starlight plugin to validate internal links.</p>
|
|
4
4
|
<p>
|
|
5
|
-
<a href="https://
|
|
6
|
-
<img alt="Screenshot of starlight-links-validator" src="https://
|
|
5
|
+
<a href="https://github.com/HiDeoo/starlight-links-validator/assets/494699/fe5f797a-8089-4271-b090-7158bb053dfa" title="Screenshot of starlight-links-validator">
|
|
6
|
+
<img alt="Screenshot of starlight-links-validator" src="https://github.com/HiDeoo/starlight-links-validator/assets/494699/fe5f797a-8089-4271-b090-7158bb053dfa" width="520" />
|
|
7
7
|
</a>
|
|
8
8
|
</p>
|
|
9
9
|
</div>
|
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
<br />
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
+
## Getting Started
|
|
22
|
+
|
|
23
|
+
Want to get started immediately? Check out the [getting started guide](https://starlight-links-validator.vercel.app/getting-started/).
|
|
24
|
+
|
|
21
25
|
## Features
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
A [Starlight](https://starlight.astro.build) plugin to validate **_internal_** links in Markdown and MDX files.
|
|
24
28
|
|
|
25
29
|
- Validate internal links to other pages
|
|
26
30
|
- Validate internal links to anchors in other pages
|
|
@@ -28,38 +32,6 @@ An [Astro](https://astro.build) integration for [Starlight](https://starlight.as
|
|
|
28
32
|
- Ignore external links
|
|
29
33
|
- Run only during a production build
|
|
30
34
|
|
|
31
|
-
## Installation
|
|
32
|
-
|
|
33
|
-
Install the Starlight Links Validator integration using your favorite package manager, e.g. with [pnpm](https://pnpm.io):
|
|
34
|
-
|
|
35
|
-
```shell
|
|
36
|
-
pnpm add starlight-links-validator
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Update your [Astro configuration](https://docs.astro.build/en/guides/configuring-astro/#supported-config-file-types) to include the Starlight Links Validator integration **_before_** the Starlight integration:
|
|
40
|
-
|
|
41
|
-
```diff
|
|
42
|
-
import starlight from '@astrojs/starlight'
|
|
43
|
-
import { defineConfig } from 'astro/config'
|
|
44
|
-
+ import starlightLinksValidator from 'starlight-links-validator'
|
|
45
|
-
|
|
46
|
-
export default defineConfig({
|
|
47
|
-
// …
|
|
48
|
-
integrations: [
|
|
49
|
-
+ starlightLinksValidator(),
|
|
50
|
-
starlight({
|
|
51
|
-
sidebar: [
|
|
52
|
-
{
|
|
53
|
-
label: 'Guides',
|
|
54
|
-
items: [{ label: 'Example Guide', link: '/guides/example/' }],
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
title: 'My Docs',
|
|
58
|
-
}),
|
|
59
|
-
],
|
|
60
|
-
})
|
|
61
|
-
```
|
|
62
|
-
|
|
63
35
|
## License
|
|
64
36
|
|
|
65
37
|
Licensed under the MIT License, Copyright © HiDeoo.
|
package/index.ts
CHANGED
|
@@ -1,36 +1,90 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StarlightPlugin } from '@astrojs/starlight/types'
|
|
2
2
|
import { AstroError } from 'astro/errors'
|
|
3
|
+
import { z } from 'astro/zod'
|
|
3
4
|
|
|
4
5
|
import { remarkStarlightLinksValidator } from './libs/remark'
|
|
5
6
|
import { logErrors, validateLinks } from './libs/validation'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
const starlightLinksValidatorOptionsSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
/**
|
|
11
|
+
* Defines whether the plugin should error on fallback pages.
|
|
12
|
+
*
|
|
13
|
+
* If you do not expect to have all pages translated in all configured locales and want to use the fallback pages
|
|
14
|
+
* feature built-in into Starlight, you should set this option to `false`.
|
|
15
|
+
*
|
|
16
|
+
* @default true
|
|
17
|
+
* @see https://starlight.astro.build/guides/i18n/#fallback-content
|
|
18
|
+
*/
|
|
19
|
+
errorOnFallbackPages: z.boolean().default(true),
|
|
20
|
+
/**
|
|
21
|
+
* Defines whether the plugin should error on inconsistent locale links.
|
|
22
|
+
*
|
|
23
|
+
* When set to `true`, the plugin will error on links that are pointing to a page in a different locale.
|
|
24
|
+
*
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
errorOnInconsistentLocale: z.boolean().default(false),
|
|
28
|
+
/**
|
|
29
|
+
* Defines whether the plugin should error on internal relative links.
|
|
30
|
+
*
|
|
31
|
+
* When set to `false`, the plugin will ignore relative links (e.g. `./foo` or `../bar`).
|
|
32
|
+
*
|
|
33
|
+
* @default true
|
|
34
|
+
*/
|
|
35
|
+
errorOnRelativeLinks: z.boolean().default(true),
|
|
36
|
+
})
|
|
37
|
+
.default({})
|
|
38
|
+
|
|
39
|
+
export default function starlightLinksValidatorPlugin(
|
|
40
|
+
userOptions?: StarlightLinksValidatorUserOptions,
|
|
41
|
+
): StarlightPlugin {
|
|
42
|
+
const options = starlightLinksValidatorOptionsSchema.safeParse(userOptions)
|
|
43
|
+
|
|
44
|
+
if (!options.success) {
|
|
45
|
+
throwPluginError('Invalid options passed to the starlight-links-validator plugin.')
|
|
46
|
+
}
|
|
47
|
+
|
|
8
48
|
return {
|
|
9
|
-
name: 'starlight-links-validator',
|
|
49
|
+
name: 'starlight-links-validator-plugin',
|
|
10
50
|
hooks: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
})
|
|
21
|
-
},
|
|
22
|
-
'astro:build:done': ({ dir, pages }) => {
|
|
23
|
-
const errors = validateLinks(pages, dir)
|
|
51
|
+
setup({ addIntegration, config: starlightConfig, logger }) {
|
|
52
|
+
addIntegration({
|
|
53
|
+
name: 'starlight-links-validator-integration',
|
|
54
|
+
hooks: {
|
|
55
|
+
'astro:config:setup': ({ command, updateConfig }) => {
|
|
56
|
+
if (command !== 'build') {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
24
59
|
|
|
25
|
-
|
|
60
|
+
updateConfig({
|
|
61
|
+
markdown: {
|
|
62
|
+
remarkPlugins: [remarkStarlightLinksValidator],
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
'astro:build:done': ({ dir, pages }) => {
|
|
67
|
+
const errors = validateLinks(pages, dir, starlightConfig, options.data)
|
|
26
68
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
69
|
+
logErrors(logger, errors)
|
|
70
|
+
|
|
71
|
+
if (errors.size > 0) {
|
|
72
|
+
throwPluginError('Links validation failed.')
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
})
|
|
33
77
|
},
|
|
34
78
|
},
|
|
35
79
|
}
|
|
36
80
|
}
|
|
81
|
+
|
|
82
|
+
function throwPluginError(message: string): never {
|
|
83
|
+
throw new AstroError(
|
|
84
|
+
message,
|
|
85
|
+
`See the error report above for more informations.\n\nIf you believe this is a bug, please file an issue at https://github.com/HiDeoo/starlight-links-validator/issues/new/choose.`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type StarlightLinksValidatorUserOptions = z.input<typeof starlightLinksValidatorOptionsSchema>
|
|
90
|
+
export type StarlightLinksValidatorOptions = z.output<typeof starlightLinksValidatorOptionsSchema>
|
package/libs/i18n.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ensureLeadingSlash, ensureTrailingSlash } from './path'
|
|
2
|
+
import type { Headings } from './remark'
|
|
3
|
+
import type { StarlightUserConfig } from './validation'
|
|
4
|
+
|
|
5
|
+
export function getLocaleConfig(config: StarlightUserConfig): LocaleConfig | undefined {
|
|
6
|
+
if (!config.locales || Object.keys(config.locales).length === 0) return
|
|
7
|
+
|
|
8
|
+
let defaultLocale = config.defaultLocale
|
|
9
|
+
const locales: string[] = []
|
|
10
|
+
|
|
11
|
+
for (const [dir, locale] of Object.entries(config.locales)) {
|
|
12
|
+
if (!locale) continue
|
|
13
|
+
|
|
14
|
+
if (dir === 'root') {
|
|
15
|
+
if (!locale.lang) continue
|
|
16
|
+
|
|
17
|
+
defaultLocale = ''
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
locales.push(dir)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (defaultLocale === undefined) return
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
defaultLocale,
|
|
27
|
+
locales,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getFallbackHeadings(
|
|
32
|
+
path: string,
|
|
33
|
+
headings: Headings,
|
|
34
|
+
localeConfig: LocaleConfig | undefined,
|
|
35
|
+
): string[] | undefined {
|
|
36
|
+
if (!localeConfig) return
|
|
37
|
+
|
|
38
|
+
for (const locale of localeConfig.locales) {
|
|
39
|
+
if (path.startsWith(`${locale}/`)) {
|
|
40
|
+
const fallbackPath = path.replace(
|
|
41
|
+
new RegExp(`^${locale}/`),
|
|
42
|
+
localeConfig.defaultLocale === '' ? localeConfig.defaultLocale : `${localeConfig.defaultLocale}/`,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return headings.get(fallbackPath === '' ? '/' : fallbackPath)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isInconsistentLocaleLink(path: string, link: string, localeConfig: LocaleConfig) {
|
|
53
|
+
const pathLocale = getLocale(path, localeConfig)
|
|
54
|
+
const linkLocale = getLocale(link, localeConfig)
|
|
55
|
+
|
|
56
|
+
if (pathLocale !== undefined || linkLocale !== undefined) {
|
|
57
|
+
return pathLocale !== linkLocale
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getLocale(path: string, localeConfig: LocaleConfig) {
|
|
64
|
+
const normalizedPath = ensureTrailingSlash(ensureLeadingSlash(path))
|
|
65
|
+
|
|
66
|
+
for (const locale of localeConfig.locales) {
|
|
67
|
+
if (normalizedPath.startsWith(`/${locale}/`)) {
|
|
68
|
+
return locale
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface LocaleConfig {
|
|
76
|
+
defaultLocale: string
|
|
77
|
+
locales: string[]
|
|
78
|
+
}
|
package/libs/path.ts
ADDED
package/libs/remark.ts
CHANGED
|
@@ -131,7 +131,7 @@ export function getValidationData() {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
function isInternalLink(link: string) {
|
|
134
|
-
return nodePath.isAbsolute(link) || link.startsWith('#')
|
|
134
|
+
return nodePath.isAbsolute(link) || link.startsWith('#') || link.startsWith('.')
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
function normalizeFilePath(filePath?: string) {
|
|
@@ -143,7 +143,7 @@ function normalizeFilePath(filePath?: string) {
|
|
|
143
143
|
.relative(nodePath.join(process.cwd(), 'src/content/docs'), filePath)
|
|
144
144
|
.replace(/\.\w+$/, '')
|
|
145
145
|
.replace(/index$/, '')
|
|
146
|
-
.replace(
|
|
146
|
+
.replace(/[/\\]?$/, '/')
|
|
147
147
|
.split(/[/\\]/)
|
|
148
148
|
.map((segment) => slug(segment))
|
|
149
149
|
.join('/')
|
package/libs/validation.ts
CHANGED
|
@@ -1,24 +1,54 @@
|
|
|
1
1
|
import { statSync } from 'node:fs'
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import type { StarlightPlugin } from '@astrojs/starlight/types'
|
|
5
|
+
import type { AstroIntegrationLogger } from 'astro'
|
|
6
|
+
import { bgGreen, black, blue, dim, green, red } from 'kleur/colors'
|
|
5
7
|
|
|
8
|
+
import type { StarlightLinksValidatorOptions } from '..'
|
|
9
|
+
|
|
10
|
+
import { getFallbackHeadings, getLocaleConfig, isInconsistentLocaleLink, type LocaleConfig } from './i18n'
|
|
11
|
+
import { ensureTrailingSlash } from './path'
|
|
6
12
|
import { getValidationData, type Headings } from './remark'
|
|
7
13
|
|
|
8
|
-
export
|
|
14
|
+
export const ValidationErrorType = {
|
|
15
|
+
InconsistentLocale: 'inconsistent locale',
|
|
16
|
+
InvalidAnchor: 'invalid anchor',
|
|
17
|
+
InvalidLink: 'invalid link',
|
|
18
|
+
RelativeLink: 'relative link',
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
export function validateLinks(
|
|
22
|
+
pages: PageData[],
|
|
23
|
+
outputDir: URL,
|
|
24
|
+
starlightConfig: StarlightUserConfig,
|
|
25
|
+
options: StarlightLinksValidatorOptions,
|
|
26
|
+
): ValidationErrors {
|
|
9
27
|
process.stdout.write(`\n${bgGreen(black(` validating links `))}\n`)
|
|
10
28
|
|
|
29
|
+
const localeConfig = getLocaleConfig(starlightConfig)
|
|
11
30
|
const { headings, links } = getValidationData()
|
|
12
|
-
const allPages: Pages = new Set(pages.map((page) => page.pathname))
|
|
31
|
+
const allPages: Pages = new Set(pages.map((page) => ensureTrailingSlash(page.pathname)))
|
|
13
32
|
|
|
14
33
|
const errors: ValidationErrors = new Map()
|
|
15
34
|
|
|
16
35
|
for (const [filePath, fileLinks] of links) {
|
|
17
36
|
for (const link of fileLinks) {
|
|
37
|
+
const validationContext: ValidationContext = {
|
|
38
|
+
errors,
|
|
39
|
+
filePath,
|
|
40
|
+
headings,
|
|
41
|
+
link,
|
|
42
|
+
localeConfig,
|
|
43
|
+
options,
|
|
44
|
+
outputDir,
|
|
45
|
+
pages: allPages,
|
|
46
|
+
}
|
|
47
|
+
|
|
18
48
|
if (link.startsWith('#')) {
|
|
19
|
-
validateSelfAnchor(
|
|
49
|
+
validateSelfAnchor(validationContext)
|
|
20
50
|
} else {
|
|
21
|
-
validateLink(
|
|
51
|
+
validateLink(validationContext)
|
|
22
52
|
}
|
|
23
53
|
}
|
|
24
54
|
}
|
|
@@ -26,49 +56,46 @@ export function validateLinks(pages: PageData[], outputDir: URL): ValidationErro
|
|
|
26
56
|
return errors
|
|
27
57
|
}
|
|
28
58
|
|
|
29
|
-
export function logErrors(errors: ValidationErrors) {
|
|
59
|
+
export function logErrors(pluginLogger: AstroIntegrationLogger, errors: ValidationErrors) {
|
|
60
|
+
const logger = pluginLogger.fork('')
|
|
61
|
+
|
|
30
62
|
if (errors.size === 0) {
|
|
31
|
-
|
|
63
|
+
logger.info(green('✓ All internal links are valid.\n'))
|
|
32
64
|
return
|
|
33
65
|
}
|
|
34
66
|
|
|
35
67
|
const errorCount = [...errors.values()].reduce((acc, links) => acc + links.length, 0)
|
|
36
68
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
),
|
|
45
|
-
)}\n\n`,
|
|
69
|
+
logger.error(
|
|
70
|
+
red(
|
|
71
|
+
`✗ Found ${errorCount} invalid ${pluralize(errorCount, 'link')} in ${errors.size} ${pluralize(
|
|
72
|
+
errors.size,
|
|
73
|
+
'file',
|
|
74
|
+
)}.`,
|
|
75
|
+
),
|
|
46
76
|
)
|
|
47
77
|
|
|
48
|
-
for (const [file,
|
|
49
|
-
|
|
78
|
+
for (const [file, validationErrors] of errors) {
|
|
79
|
+
logger.info(`${red('▶')} ${blue(file)}`)
|
|
50
80
|
|
|
51
|
-
for (const [index,
|
|
52
|
-
|
|
81
|
+
for (const [index, validationError] of validationErrors.entries()) {
|
|
82
|
+
logger.info(
|
|
83
|
+
` ${blue(`${index < validationErrors.length - 1 ? '├' : '└'}─`)} ${validationError.link}${dim(
|
|
84
|
+
` - ${validationError.type}`,
|
|
85
|
+
)}`,
|
|
86
|
+
)
|
|
53
87
|
}
|
|
54
|
-
|
|
55
|
-
process.stdout.write(dim('\n'))
|
|
56
88
|
}
|
|
57
89
|
|
|
58
|
-
process.stdout.write(
|
|
90
|
+
process.stdout.write('\n')
|
|
59
91
|
}
|
|
60
92
|
|
|
61
93
|
/**
|
|
62
94
|
* Validate a link to another internal page that may or may not have a hash.
|
|
63
95
|
*/
|
|
64
|
-
function validateLink(
|
|
65
|
-
errors
|
|
66
|
-
|
|
67
|
-
filePath: string,
|
|
68
|
-
headings: Headings,
|
|
69
|
-
pages: Pages,
|
|
70
|
-
outputDir: URL,
|
|
71
|
-
) {
|
|
96
|
+
function validateLink(context: ValidationContext) {
|
|
97
|
+
const { errors, filePath, link, localeConfig, options, outputDir, pages } = context
|
|
98
|
+
|
|
72
99
|
const sanitizedLink = link.replace(/^\//, '')
|
|
73
100
|
const segments = sanitizedLink.split('#')
|
|
74
101
|
|
|
@@ -79,32 +106,53 @@ function validateLink(
|
|
|
79
106
|
throw new Error('Failed to validate a link with no path.')
|
|
80
107
|
}
|
|
81
108
|
|
|
82
|
-
if (
|
|
109
|
+
if (path.startsWith('.')) {
|
|
110
|
+
if (options.errorOnRelativeLinks) {
|
|
111
|
+
addError(errors, filePath, link, ValidationErrorType.RelativeLink)
|
|
112
|
+
}
|
|
113
|
+
|
|
83
114
|
return
|
|
84
115
|
}
|
|
85
116
|
|
|
86
|
-
if (path
|
|
87
|
-
|
|
117
|
+
if (isValidAsset(path, outputDir)) {
|
|
118
|
+
return
|
|
88
119
|
}
|
|
89
120
|
|
|
121
|
+
path = ensureTrailingSlash(path)
|
|
122
|
+
|
|
90
123
|
const isValidPage = pages.has(path)
|
|
91
|
-
const fileHeadings =
|
|
124
|
+
const fileHeadings = getFileHeadings(path, context)
|
|
92
125
|
|
|
93
126
|
if (!isValidPage || !fileHeadings) {
|
|
94
|
-
addError(errors, filePath, link)
|
|
127
|
+
addError(errors, filePath, link, ValidationErrorType.InvalidLink)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (options.errorOnInconsistentLocale && localeConfig && isInconsistentLocaleLink(filePath, link, localeConfig)) {
|
|
132
|
+
addError(errors, filePath, link, ValidationErrorType.InconsistentLocale)
|
|
95
133
|
return
|
|
96
134
|
}
|
|
97
135
|
|
|
98
136
|
if (hash && !fileHeadings.includes(hash)) {
|
|
99
|
-
addError(errors, filePath, link)
|
|
137
|
+
addError(errors, filePath, link, ValidationErrorType.InvalidAnchor)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getFileHeadings(path: string, { headings, localeConfig, options }: ValidationContext) {
|
|
142
|
+
let heading = headings.get(path === '' ? '/' : path)
|
|
143
|
+
|
|
144
|
+
if (!options.errorOnFallbackPages && !heading && localeConfig) {
|
|
145
|
+
heading = getFallbackHeadings(path, headings, localeConfig)
|
|
100
146
|
}
|
|
147
|
+
|
|
148
|
+
return heading
|
|
101
149
|
}
|
|
102
150
|
|
|
103
151
|
/**
|
|
104
152
|
* Validate a link to an anchor in the same page.
|
|
105
153
|
*/
|
|
106
|
-
function validateSelfAnchor(errors
|
|
107
|
-
const sanitizedHash =
|
|
154
|
+
function validateSelfAnchor({ errors, link, filePath, headings }: ValidationContext) {
|
|
155
|
+
const sanitizedHash = link.replace(/^#/, '')
|
|
108
156
|
const fileHeadings = headings.get(filePath)
|
|
109
157
|
|
|
110
158
|
if (!fileHeadings) {
|
|
@@ -112,7 +160,7 @@ function validateSelfAnchor(errors: ValidationErrors, hash: string, filePath: st
|
|
|
112
160
|
}
|
|
113
161
|
|
|
114
162
|
if (!fileHeadings.includes(sanitizedHash)) {
|
|
115
|
-
addError(errors, filePath,
|
|
163
|
+
addError(errors, filePath, link, 'invalid anchor')
|
|
116
164
|
}
|
|
117
165
|
}
|
|
118
166
|
|
|
@@ -131,9 +179,9 @@ function isValidAsset(path: string, outputDir: URL) {
|
|
|
131
179
|
}
|
|
132
180
|
}
|
|
133
181
|
|
|
134
|
-
function addError(errors: ValidationErrors, filePath: string, link: string) {
|
|
182
|
+
function addError(errors: ValidationErrors, filePath: string, link: string, type: ValidationErrorType) {
|
|
135
183
|
const fileErrors = errors.get(filePath) ?? []
|
|
136
|
-
fileErrors.push(link)
|
|
184
|
+
fileErrors.push({ link, type })
|
|
137
185
|
|
|
138
186
|
errors.set(filePath, fileErrors)
|
|
139
187
|
}
|
|
@@ -142,11 +190,31 @@ function pluralize(count: number, singular: string) {
|
|
|
142
190
|
return count === 1 ? singular : `${singular}s`
|
|
143
191
|
}
|
|
144
192
|
|
|
145
|
-
// The
|
|
146
|
-
type ValidationErrors = Map<string,
|
|
193
|
+
// The validation errors keyed by file path.
|
|
194
|
+
type ValidationErrors = Map<string, ValidationError[]>
|
|
195
|
+
|
|
196
|
+
export type ValidationErrorType = (typeof ValidationErrorType)[keyof typeof ValidationErrorType]
|
|
197
|
+
|
|
198
|
+
interface ValidationError {
|
|
199
|
+
link: string
|
|
200
|
+
type: ValidationErrorType
|
|
201
|
+
}
|
|
147
202
|
|
|
148
203
|
interface PageData {
|
|
149
204
|
pathname: string
|
|
150
205
|
}
|
|
151
206
|
|
|
152
207
|
type Pages = Set<PageData['pathname']>
|
|
208
|
+
|
|
209
|
+
interface ValidationContext {
|
|
210
|
+
errors: ValidationErrors
|
|
211
|
+
filePath: string
|
|
212
|
+
headings: Headings
|
|
213
|
+
link: string
|
|
214
|
+
localeConfig: LocaleConfig | undefined
|
|
215
|
+
options: StarlightLinksValidatorOptions
|
|
216
|
+
outputDir: URL
|
|
217
|
+
pages: Pages
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export type StarlightUserConfig = Parameters<StarlightPlugin['hooks']['setup']>['0']['config']
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-links-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Starlight plugin to validate internal links.",
|
|
6
6
|
"author": "HiDeoo <github@hideoo.dev> (https://hideoo.dev)",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
|
@@ -18,19 +18,19 @@
|
|
|
18
18
|
"unist-util-visit": "5.0.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@astrojs/starlight": "0.
|
|
22
|
-
"@types/hast": "3.0.
|
|
23
|
-
"@types/mdast": "4.0.
|
|
21
|
+
"@astrojs/starlight": "0.15.0",
|
|
22
|
+
"@types/hast": "3.0.3",
|
|
23
|
+
"@types/mdast": "4.0.3",
|
|
24
24
|
"@types/node": "18.17.18",
|
|
25
|
-
"astro": "
|
|
25
|
+
"astro": "4.0.4",
|
|
26
26
|
"mdast-util-mdx-jsx": "3.0.0",
|
|
27
27
|
"typescript": "5.1.3",
|
|
28
|
-
"unified": "11.0.
|
|
29
|
-
"vitest": "0.
|
|
28
|
+
"unified": "11.0.4",
|
|
29
|
+
"vitest": "1.0.4"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@astrojs/starlight": ">=0.
|
|
33
|
-
"astro": ">=
|
|
32
|
+
"@astrojs/starlight": ">=0.15.0",
|
|
33
|
+
"astro": ">=4.0.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.14.1"
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"sideEffects": false,
|
|
43
43
|
"keywords": [
|
|
44
44
|
"starlight",
|
|
45
|
+
"plugin",
|
|
45
46
|
"links",
|
|
46
47
|
"validation",
|
|
47
|
-
"astro"
|
|
48
|
-
"astro-integration"
|
|
48
|
+
"astro"
|
|
49
49
|
],
|
|
50
50
|
"homepage": "https://github.com/HiDeoo/starlight-links-validator",
|
|
51
51
|
"repository": {
|
|
@@ -55,6 +55,6 @@
|
|
|
55
55
|
"bugs": "https://github.com/HiDeoo/starlight-links-validator/issues",
|
|
56
56
|
"scripts": {
|
|
57
57
|
"test": "vitest",
|
|
58
|
-
"lint": "prettier -c --cache . && eslint . --cache --max-warnings=0
|
|
58
|
+
"lint": "prettier -c --cache . && eslint . --cache --max-warnings=0"
|
|
59
59
|
}
|
|
60
60
|
}
|