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 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.base, starlightConfig, options.data)
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
+ }
@@ -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
- base: string,
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(base === '/' ? page.pathname : posix.join(stripLeadingSlash(base), page.pathname)),
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
- base,
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
- let path = segments[0]
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
- path = ensureTrailingSlash(path)
133
+ const sanitizedPath = ensureTrailingSlash(path)
129
134
 
130
- const isValidPage = pages.has(path)
131
- const fileHeadings = getFileHeadings(path, context)
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
- base: string
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.5.3",
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": {