Repository: HiDeoo/starlight-links-validator (ts)

Issue:
False-positive validation errors when using `base` + `slug`
### Describe the bug

In case of using the `base` configuration parameter (needed for [GH Pages deployment](https://docs.astro.build/en/guides/deploy/github/)) and explicit `slug` for a page, the validator fails.

### To Reproduce

1. Set the `base` parameter for the site. (e.g. `base: 'project'`)
2. Define a `slug` for a page in the Frontmatter part. (e.g. `slug: reference`)
3. Reference the page. e.g. `[Reference](/project/reference/)`
4. Run `npm run build` and see the validation error:

```console
 validating links 
12:05:19 [ERROR] ✗ Found 1 invalid link in 1 file.
12:05:19 ▶ project/
12:05:19   └─ /project/reference/ - invalid link
```

### Expected behavior

The validator does not fail on such links. It works well when navigating through the UI.

### How often does this bug happen?

Every time

### System Info

_No response_

### Additional Context

Stackblitz reproducible example: https://stackblitz.com/edit/github-td5cw8

Pull request description:
Fix validation issue with the `base` option and a custom slug
Close #60 

Patch:
diff --git a/packages/starlight-links-validator/libs/remark.ts b/packages/starlight-links-validator/libs/remark.ts
index bf316bd..14cb896 100644
--- a/packages/starlight-links-validator/libs/remark.ts
+++ b/packages/starlight-links-validator/libs/remark.ts
@@ -134,8 +134,8 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
       }
     })
 
-    headings.set(getFilePath(filePath, slug), fileHeadings)
-    links.set(getFilePath(filePath, slug), fileLinks)
+    headings.set(getFilePath(base, filePath, slug), fileHeadings)
+    links.set(getFilePath(base, filePath, slug), fileLinks)
   }
 }
 
@@ -147,8 +147,12 @@ function isInternalLink(link: string) {
   return !isAbsoluteUrl(link)
 }
 
-function getFilePath(filePath: string, slug: string | undefined) {
-  return slug ? stripLeadingSlash(ensureTrailingSlash(slug)) : filePath
+function getFilePath(base: string, filePath: string, slug: string | undefined) {
+  if (slug) {
+    return nodePath.posix.join(stripLeadingSlash(base), stripLeadingSlash(ensureTrailingSlash(slug)))
+  }
+
+  return filePath
 }
 
 function normalizeFilePath(base: string, srcDir: URL, filePath?: string) {


Test patch:
diff --git a/packages/starlight-links-validator/tests/base-path.test.ts b/packages/starlight-links-validator/tests/base-path.test.ts
index ff38f26..0171bcb 100644
--- a/packages/starlight-links-validator/tests/base-path.test.ts
+++ b/packages/starlight-links-validator/tests/base-path.test.ts
@@ -9,7 +9,7 @@ test('validates links when the `base` Astro option is set', async () => {
 
   expect(status).toBe('error')
 
-  expectValidationErrorCount(output, 10, 1)
+  expectValidationErrorCount(output, 16, 1)
 
   expectValidationErrors(output, 'test/test/', [
     ['/guides/example', ValidationErrorType.InvalidLink],
@@ -22,5 +22,11 @@ test('validates links when the `base` Astro option is set', async () => {
     ['/test/guides/example/#unknown', ValidationErrorType.InvalidHash],
     ['/favicon.svg', ValidationErrorType.InvalidLink],
     ['/guidelines/dummy.pdf', ValidationErrorType.InvalidLink],
+    ['/release/@pkg/v0.1.0', ValidationErrorType.InvalidLink],
+    ['/release/@pkg/v0.1.0/', ValidationErrorType.InvalidLink],
+    ['/release/@pkg/v0.1.0#some-content', ValidationErrorType.InvalidLink],
+    ['/release/@pkg/v0.1.0/#some-content', ValidationErrorType.InvalidLink],
+    ['/guides/page-with-custom-slug', ValidationErrorType.InvalidLink],
+    ['/guides/page-with-custom-slug/', ValidationErrorType.InvalidLink],
   ])
 })
diff --git a/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/guides/page-with-custom-slug.md b/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/guides/page-with-custom-slug.md
new file mode 100644
index 0000000..294a153
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/base-path/src/content/docs/guides/page-with-custom-slug.md
@@ -0,0 +1,8 @@
+---
+title: Page with custom slug
+slug: release/@pkg/v0.1.0
+---
+
+Some content
+
+# Some content
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
index b541dbd..2c63e28 100644
--- 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
@@ -29,3 +29,18 @@ title: Test
 
 - [Link to an asset with missing base](/favicon.svg)
 - [Link to another asset with missing base](/guidelines/dummy.pdf)
+
+- [Example page with custom slug](/test/release/@pkg/v0.1.0)
+- [Example page with custom slug](/test/release/@pkg/v0.1.0/)
+
+- [Example page with custom slug and hash](/test/release/@pkg/v0.1.0#some-content)
+- [Example page with custom slug and hash](/test/release/@pkg/v0.1.0/#some-content)
+
+- [Example page with custom slug and missing base](/release/@pkg/v0.1.0)
+- [Example page with custom slug and missing base](/release/@pkg/v0.1.0/)
+
+- [Example page with custom slug, hash, and missing baase](/release/@pkg/v0.1.0#some-content)
+- [Example page with custom slug, hash, and missing baase](/release/@pkg/v0.1.0/#some-content)
+
+- [Example page with custom slug and old link](/guides/page-with-custom-slug)
+- [Example page with custom slug and old link](/guides/page-with-custom-slug/)