Repository: HiDeoo/starlight-links-validator
Pull request #24 (2023-12-14 17:59:15)

Fixes validation issues when using the Astro `base` option


Patch:
diff --git a/packages/starlight-links-validator/index.ts b/packages/starlight-links-validator/index.ts
index f50bd7e..aa31ef2 100644
--- a/packages/starlight-links-validator/index.ts
+++ b/packages/starlight-links-validator/index.ts
@@ -48,7 +48,7 @@ export default function starlightLinksValidatorPlugin(
   return {
     name: 'starlight-links-validator-plugin',
     hooks: {
-      setup({ addIntegration, config: starlightConfig, logger }) {
+      setup({ addIntegration, astroConfig, config: starlightConfig, logger }) {
         addIntegration({
           name: 'starlight-links-validator-integration',
           hooks: {
@@ -59,12 +59,12 @@ export default function starlightLinksValidatorPlugin(
 
               updateConfig({
                 markdown: {
-                  remarkPlugins: [remarkStarlightLinksValidator],
+                  remarkPlugins: [[remarkStarlightLinksValidator, astroConfig.base]],
                 },
               })
             },
             'astro:build:done': ({ dir, pages }) => {
-              const errors = validateLinks(pages, dir, starlightConfig, options.data)
+              const errors = validateLinks(pages, dir, astroConfig.base, starlightConfig, options.data)
 
               logErrors(logger, errors)
 
diff --git a/packages/starlight-links-validator/libs/path.ts b/packages/starlight-links-validator/libs/path.ts
index 70f523b..071427d 100644
--- a/packages/starlight-links-validator/libs/path.ts
+++ b/packages/starlight-links-validator/libs/path.ts
@@ -5,3 +5,7 @@ export function ensureLeadingSlash(path: string): string {
 export function ensureTrailingSlash(path: string): string {
   return path.endsWith('/') ? path : `${path}/`
 }
+
+export function stripLeadingSlash(path: string) {
+  return path.replace(/^\//, '')
+}
diff --git a/packages/starlight-links-validator/libs/remark.ts b/packages/starlight-links-validator/libs/remark.ts
index 738995a..60648f9 100644
--- a/packages/starlight-links-validator/libs/remark.ts
+++ b/packages/starlight-links-validator/libs/remark.ts
@@ -12,15 +12,17 @@ import { toString } from 'mdast-util-to-string'
 import type { Plugin } from 'unified'
 import { visit } from 'unist-util-visit'
 
+import { stripLeadingSlash } from './path'
+
 // All the headings keyed by file path.
 const headings: Headings = new Map()
 // All the internal links keyed by file path.
 const links: Links = new Map()
 
-export const remarkStarlightLinksValidator: Plugin<[], Root> = function () {
+export const remarkStarlightLinksValidator: Plugin<[base: string], Root> = function (base) {
   return (tree, file) => {
     const slugger = new GitHubSlugger()
-    const filePath = normalizeFilePath(file.history[0])
+    const filePath = normalizeFilePath(base, file.history[0])
 
     const fileHeadings: string[] = []
     const fileLinks: string[] = []
@@ -134,12 +136,12 @@ function isInternalLink(link: string) {
   return nodePath.isAbsolute(link) || link.startsWith('#') || link.startsWith('.')
 }
 
-function normalizeFilePath(filePath?: string) {
+function normalizeFilePath(base: string, filePath?: string) {
   if (!filePath) {
     throw new Error('Missing file path to validate links.')
   }
 
-  return nodePath
+  const path = nodePath
     .relative(nodePath.join(process.cwd(), 'src/content/docs'), filePath)
     .replace(/\.\w+$/, '')
     .replace(/index$/, '')
@@ -147,6 +149,12 @@ function normalizeFilePath(filePath?: string) {
     .split(/[/\\]/)
     .map((segment) => slug(segment))
     .join('/')
+
+  if (base !== '/') {
+    return nodePath.posix.join(stripLeadingSlash(base), path)
+  }
+
+  return path
 }
 
 function isMdxIdAttribute(attribute: MdxJsxAttribute | MdxJsxExpressionAttribute): attribute is MdxIdAttribute {
diff --git a/packages/starlight-links-validator/libs/validation.ts b/packages/starlight-links-validator/libs/validation.ts
index 1361abf..fcb8fbd 100644
--- a/packages/starlight-links-validator/libs/validation.ts
+++ b/packages/starlight-links-validator/libs/validation.ts
@@ -1,4 +1,5 @@
 import { statSync } from 'node:fs'
+import { posix } from 'node:path'
 import { fileURLToPath } from 'node:url'
 
 import type { StarlightPlugin } from '@astrojs/starlight/types'
@@ -8,7 +9,7 @@ import { bgGreen, black, blue, dim, green, red } from 'kleur/colors'
 import type { StarlightLinksValidatorOptions } from '..'
 
 import { getFallbackHeadings, getLocaleConfig, isInconsistentLocaleLink, type LocaleConfig } from './i18n'
-import { ensureTrailingSlash } from './path'
+import { ensureTrailingSlash, stripLeadingSlash } from './path'
 import { getValidationData, type Headings } from './remark'
 
 export const ValidationErrorType = {
@@ -21,6 +22,7 @@ export const ValidationErrorType = {
 export function validateLinks(
   pages: PageData[],
   outputDir: URL,
+  base: string,
   starlightConfig: StarlightUserConfig,
   options: StarlightLinksValidatorOptions,
 ): ValidationErrors {
@@ -28,7 +30,11 @@ export function validateLinks(
 
   const localeConfig = getLocaleConfig(starlightConfig)
   const { headings, links } = getValidationData()
-  const allPages: Pages = new Set(pages.map((page) => ensureTrailingSlash(page.pathname)))
+  const allPages: Pages = new Set(
+    pages.map((page) =>
+      ensureTrailingSlash(base === '/' ? page.pathname : posix.join(stripLeadingSlash(base), page.pathname)),
+    ),
+  )
 
   const errors: ValidationErrors = new Map()
 


Test patch:
diff --git a/packages/starlight-links-validator/tests/base-path.test.ts b/packages/starlight-links-validator/tests/base-path.test.ts
new file mode 100644
index 0000000..eb26253
--- /dev/null
+++ b/packages/starlight-links-validator/tests/base-path.test.ts
@@ -0,0 +1,26 @@
+import { expect, test } from 'vitest'
+
+import { ValidationErrorType } from '../libs/validation'
+
+import { expectValidationErrorCount, expectValidationErrors, loadFixture } from './utils'
+
+test('should validate links when the `base` Astro option is set', async () => {
+  expect.assertions(2)
+
+  try {
+    await loadFixture('base-path')
+  } catch (error) {
+    expectValidationErrorCount(error, 8, 1)
+
+    expectValidationErrors(error, 'test/test/', [
+      ['/guides/example', ValidationErrorType.InvalidLink],
+      ['/guides/example/', ValidationErrorType.InvalidLink],
+      ['/guides/example#description', ValidationErrorType.InvalidLink],
+      ['/guides/example/#description', ValidationErrorType.InvalidLink],
+      ['/unknown', ValidationErrorType.InvalidLink],
+      ['/unknown/', ValidationErrorType.InvalidLink],
+      ['/test/guides/example#unknown', ValidationErrorType.InvalidAnchor],
+      ['/test/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+    ])
+  }
+})
diff --git a/packages/starlight-links-validator/tests/fixtures/base-path/astro.config.ts b/packages/starlight-links-validator/tests/fixtures/base-path/astro.config.ts
new file mode 100644
index 0000000..936b5cd
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/base-path/astro.config.ts
@@ -0,0 +1,14 @@
+import starlight from '@astrojs/starlight'
+import { defineConfig } from 'astro/config'
+
+import starlightLinksValidator from '../..'
+
+export default defineConfig({
+  base: '/test',
+  integrations: [
+    starlight({
+      plugins: [starlightLinksValidator()],
+      title: 'Starlight Links Validator Tests - trailing always',
+    }),
+  ],
+})
diff --git a/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/guides/example.md b/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/guides/example.md
new file mode 100644
index 0000000..e78ca68
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/guides/example.md
@@ -0,0 +1,7 @@
+---
+title: Example
+---
+
+## Description
+
+This is an example page.
diff --git a/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/test.md b/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/test.md
new file mode 100644
index 0000000..e433187
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/test.md
@@ -0,0 +1,25 @@
+---
+title: Test
+---
+
+# Some links
+
+- [External link](https://starlight.astro.build/)
+
+- [Example page](/test/guides/example)
+- [Example page](/test/guides/example/)
+
+- [Example page with hash](/test/guides/example#description)
+- [Example page with hash](/test/guides/example/#description)
+
+- [Example page with missing base](/guides/example)
+- [Example page with missing base](/guides/example/)
+
+- [Example page with missing base and hash](/guides/example#description)
+- [Example page with missing base and hash](/guides/example/#description)
+
+- [Unknown page](/unknown)
+- [Unknown page](/unknown/)
+
+- [Example page with unknown hash](/test/guides/example#unknown)
+- [Example page with unknown hash](/test/guides/example/#unknown)