starlight-links-validator 0.5.3 → 0.7.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/index.ts +1 -1
- package/libs/remark.ts +18 -3
- package/libs/validation.ts +26 -12
- package/package.json +2 -1
package/index.ts
CHANGED
|
@@ -64,7 +64,7 @@ export default function starlightLinksValidatorPlugin(
|
|
|
64
64
|
})
|
|
65
65
|
},
|
|
66
66
|
'astro:build:done': ({ dir, pages }) => {
|
|
67
|
-
const errors = validateLinks(pages, dir, astroConfig
|
|
67
|
+
const errors = validateLinks(pages, dir, astroConfig, starlightConfig, options.data)
|
|
68
68
|
|
|
69
69
|
logErrors(logger, errors)
|
|
70
70
|
|
package/libs/remark.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { toString } from 'mdast-util-to-string'
|
|
|
13
13
|
import type { Plugin } from 'unified'
|
|
14
14
|
import { visit } from 'unist-util-visit'
|
|
15
15
|
|
|
16
|
-
import { stripLeadingSlash } from './path'
|
|
16
|
+
import { ensureTrailingSlash, stripLeadingSlash } from './path'
|
|
17
17
|
|
|
18
18
|
// All the headings keyed by file path.
|
|
19
19
|
const headings: Headings = new Map()
|
|
@@ -24,6 +24,7 @@ export const remarkStarlightLinksValidator: Plugin<[base: string], Root> = funct
|
|
|
24
24
|
return (tree, file) => {
|
|
25
25
|
const slugger = new GitHubSlugger()
|
|
26
26
|
const filePath = normalizeFilePath(base, file.history[0])
|
|
27
|
+
const slug = file.data.astro?.frontmatter?.slug
|
|
27
28
|
|
|
28
29
|
const fileHeadings: string[] = []
|
|
29
30
|
const fileLinks: string[] = []
|
|
@@ -124,8 +125,8 @@ export const remarkStarlightLinksValidator: Plugin<[base: string], Root> = funct
|
|
|
124
125
|
}
|
|
125
126
|
})
|
|
126
127
|
|
|
127
|
-
headings.set(filePath, fileHeadings)
|
|
128
|
-
links.set(filePath, fileLinks)
|
|
128
|
+
headings.set(getFilePath(filePath, slug), fileHeadings)
|
|
129
|
+
links.set(getFilePath(filePath, slug), fileLinks)
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
|
|
@@ -137,6 +138,10 @@ function isInternalLink(link: string) {
|
|
|
137
138
|
return !isAbsoluteUrl(link)
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
function getFilePath(filePath: string, slug: string | undefined) {
|
|
142
|
+
return slug ? stripLeadingSlash(ensureTrailingSlash(slug)) : filePath
|
|
143
|
+
}
|
|
144
|
+
|
|
140
145
|
function normalizeFilePath(base: string, filePath?: string) {
|
|
141
146
|
if (!filePath) {
|
|
142
147
|
throw new Error('Missing file path to validate links.')
|
|
@@ -170,3 +175,13 @@ interface MdxIdAttribute {
|
|
|
170
175
|
type: 'mdxJsxAttribute'
|
|
171
176
|
value: string
|
|
172
177
|
}
|
|
178
|
+
|
|
179
|
+
declare module 'vfile' {
|
|
180
|
+
interface DataMap {
|
|
181
|
+
astro?: {
|
|
182
|
+
frontmatter?: {
|
|
183
|
+
slug?: string
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
package/libs/validation.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { posix } from 'node:path'
|
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
|
|
5
5
|
import type { StarlightPlugin } from '@astrojs/starlight/types'
|
|
6
|
-
import type { AstroIntegrationLogger } from 'astro'
|
|
6
|
+
import type { AstroConfig, AstroIntegrationLogger } from 'astro'
|
|
7
7
|
import { bgGreen, black, blue, dim, green, red } from 'kleur/colors'
|
|
8
8
|
|
|
9
9
|
import type { StarlightLinksValidatorOptions } from '..'
|
|
@@ -17,12 +17,13 @@ export const ValidationErrorType = {
|
|
|
17
17
|
InvalidAnchor: 'invalid anchor',
|
|
18
18
|
InvalidLink: 'invalid link',
|
|
19
19
|
RelativeLink: 'relative link',
|
|
20
|
+
TrailingSlash: 'trailing slash',
|
|
20
21
|
} as const
|
|
21
22
|
|
|
22
23
|
export function validateLinks(
|
|
23
24
|
pages: PageData[],
|
|
24
25
|
outputDir: URL,
|
|
25
|
-
|
|
26
|
+
astroConfig: AstroConfig,
|
|
26
27
|
starlightConfig: StarlightUserConfig,
|
|
27
28
|
options: StarlightLinksValidatorOptions,
|
|
28
29
|
): ValidationErrors {
|
|
@@ -32,7 +33,11 @@ export function validateLinks(
|
|
|
32
33
|
const { headings, links } = getValidationData()
|
|
33
34
|
const allPages: Pages = new Set(
|
|
34
35
|
pages.map((page) =>
|
|
35
|
-
ensureTrailingSlash(
|
|
36
|
+
ensureTrailingSlash(
|
|
37
|
+
astroConfig.base === '/'
|
|
38
|
+
? stripLeadingSlash(page.pathname)
|
|
39
|
+
: posix.join(stripLeadingSlash(astroConfig.base), page.pathname),
|
|
40
|
+
),
|
|
36
41
|
),
|
|
37
42
|
)
|
|
38
43
|
|
|
@@ -41,7 +46,7 @@ export function validateLinks(
|
|
|
41
46
|
for (const [filePath, fileLinks] of links) {
|
|
42
47
|
for (const link of fileLinks) {
|
|
43
48
|
const validationContext: ValidationContext = {
|
|
44
|
-
|
|
49
|
+
astroConfig,
|
|
45
50
|
errors,
|
|
46
51
|
filePath,
|
|
47
52
|
headings,
|
|
@@ -101,12 +106,12 @@ export function logErrors(pluginLogger: AstroIntegrationLogger, errors: Validati
|
|
|
101
106
|
* Validate a link to another internal page that may or may not have a hash.
|
|
102
107
|
*/
|
|
103
108
|
function validateLink(context: ValidationContext) {
|
|
104
|
-
const { errors, filePath, link, localeConfig, options, pages } = context
|
|
109
|
+
const { astroConfig, errors, filePath, link, localeConfig, options, pages } = context
|
|
105
110
|
|
|
106
111
|
const sanitizedLink = link.replace(/^\//, '')
|
|
107
112
|
const segments = sanitizedLink.split('#')
|
|
108
113
|
|
|
109
|
-
|
|
114
|
+
const path = segments[0]
|
|
110
115
|
const hash = segments[1]
|
|
111
116
|
|
|
112
117
|
if (path === undefined) {
|
|
@@ -125,10 +130,10 @@ function validateLink(context: ValidationContext) {
|
|
|
125
130
|
return
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
|
|
133
|
+
const sanitizedPath = ensureTrailingSlash(path)
|
|
129
134
|
|
|
130
|
-
const isValidPage = pages.has(
|
|
131
|
-
const fileHeadings = getFileHeadings(
|
|
135
|
+
const isValidPage = pages.has(sanitizedPath)
|
|
136
|
+
const fileHeadings = getFileHeadings(sanitizedPath, context)
|
|
132
137
|
|
|
133
138
|
if (!isValidPage || !fileHeadings) {
|
|
134
139
|
addError(errors, filePath, link, ValidationErrorType.InvalidLink)
|
|
@@ -142,6 +147,15 @@ function validateLink(context: ValidationContext) {
|
|
|
142
147
|
|
|
143
148
|
if (hash && !fileHeadings.includes(hash)) {
|
|
144
149
|
addError(errors, filePath, link, ValidationErrorType.InvalidAnchor)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
(astroConfig.trailingSlash === 'always' && !path.endsWith('/')) ||
|
|
155
|
+
(astroConfig.trailingSlash === 'never' && path.endsWith('/'))
|
|
156
|
+
) {
|
|
157
|
+
addError(errors, filePath, link, ValidationErrorType.TrailingSlash)
|
|
158
|
+
return
|
|
145
159
|
}
|
|
146
160
|
}
|
|
147
161
|
|
|
@@ -175,8 +189,8 @@ function validateSelfAnchor({ errors, link, filePath, headings }: ValidationCont
|
|
|
175
189
|
* Check if a link is a valid asset in the build output directory.
|
|
176
190
|
*/
|
|
177
191
|
function isValidAsset(path: string, context: ValidationContext) {
|
|
178
|
-
if (context.base !== '/') {
|
|
179
|
-
const base = stripLeadingSlash(context.base)
|
|
192
|
+
if (context.astroConfig.base !== '/') {
|
|
193
|
+
const base = stripLeadingSlash(context.astroConfig.base)
|
|
180
194
|
|
|
181
195
|
if (path.startsWith(base)) {
|
|
182
196
|
path = path.replace(new RegExp(`^${stripLeadingSlash(base)}/?`), '')
|
|
@@ -224,7 +238,7 @@ interface PageData {
|
|
|
224
238
|
type Pages = Set<PageData['pathname']>
|
|
225
239
|
|
|
226
240
|
interface ValidationContext {
|
|
227
|
-
|
|
241
|
+
astroConfig: AstroConfig
|
|
228
242
|
errors: ValidationErrors
|
|
229
243
|
filePath: string
|
|
230
244
|
headings: Headings
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-links-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Starlight plugin to validate internal links.",
|
|
6
6
|
"author": "HiDeoo <github@hideoo.dev> (https://hideoo.dev)",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"mdast-util-mdx-jsx": "3.0.0",
|
|
28
28
|
"typescript": "5.1.3",
|
|
29
29
|
"unified": "11.0.4",
|
|
30
|
+
"vfile": "6.0.1",
|
|
30
31
|
"vitest": "1.0.4"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|