Skip to content

Commit 675dd08

Browse files
committed
feat: add support for validating emitted file assets
1 parent f6bd50a commit 675dd08

File tree

10 files changed

+55
-8
lines changed

10 files changed

+55
-8
lines changed

packages/starlight-links-validator/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export default function starlightLinksValidatorIntegration(): AstroIntegration {
1818
},
1919
})
2020
},
21-
'astro:build:done': ({ pages }) => {
22-
const errors = validateLinks(pages)
21+
'astro:build:done': ({ dir, pages }) => {
22+
const errors = validateLinks(pages, dir)
2323

2424
logErrors(errors)
2525

packages/starlight-links-validator/libs/validation.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { statSync } from 'node:fs'
2+
import { fileURLToPath } from 'node:url'
3+
14
import { bgGreen, black, bold, cyan, dim, red } from 'kleur/colors'
25

36
import { getValidationData, type Headings } from './remark'
47

5-
export function validateLinks(pages: PageData[]): ValidationErrors {
8+
export function validateLinks(pages: PageData[], outputDir: URL): ValidationErrors {
69
process.stdout.write(`\n${bgGreen(black(` validating links `))}\n`)
710

811
const { headings, links } = getValidationData()
@@ -15,7 +18,7 @@ export function validateLinks(pages: PageData[]): ValidationErrors {
1518
if (link.startsWith('#')) {
1619
validateSelfAnchor(errors, link, filePath, headings)
1720
} else {
18-
validateLink(errors, link, filePath, headings, allPages)
21+
validateLink(errors, link, filePath, headings, allPages, outputDir)
1922
}
2023
}
2124
}
@@ -58,7 +61,14 @@ export function logErrors(errors: ValidationErrors) {
5861
/**
5962
* Validate a link to another internal page that may or may not have a hash.
6063
*/
61-
function validateLink(errors: ValidationErrors, link: string, filePath: string, headings: Headings, pages: Pages) {
64+
function validateLink(
65+
errors: ValidationErrors,
66+
link: string,
67+
filePath: string,
68+
headings: Headings,
69+
pages: Pages,
70+
outputDir: URL
71+
) {
6272
const sanitizedLink = link.replace(/^\//, '')
6373
const segments = sanitizedLink.split('#')
6474

@@ -67,7 +77,13 @@ function validateLink(errors: ValidationErrors, link: string, filePath: string,
6777

6878
if (path === undefined) {
6979
throw new Error('Failed to validate a link with no path.')
70-
} else if (path.length > 0 && !path.endsWith('/')) {
80+
}
81+
82+
if (isValidAsset(path, outputDir)) {
83+
return
84+
}
85+
86+
if (path.length > 0 && !path.endsWith('/')) {
7187
path += '/'
7288
}
7389

@@ -100,6 +116,21 @@ function validateSelfAnchor(errors: ValidationErrors, hash: string, filePath: st
100116
}
101117
}
102118

119+
/**
120+
* Check if a link is a valid asset in the build output directory.
121+
*/
122+
function isValidAsset(path: string, outputDir: URL) {
123+
const filePath = fileURLToPath(new URL(path, outputDir))
124+
125+
try {
126+
const stats = statSync(filePath)
127+
128+
return stats.isFile()
129+
} catch {
130+
return false
131+
}
132+
}
133+
103134
function addError(errors: ValidationErrors, filePath: string, link: string) {
104135
const fileErrors = errors.get(filePath) ?? []
105136
fileErrors.push(link)
Lines changed: 1 addition & 0 deletions
Loading
Binary file not shown.

packages/starlight-links-validator/tests/fixtures/with-invalid-links/src/content/docs/guides/example.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ some content
3131
test
3232
</a>
3333
</div>
34+
35+
<a href="/icon.svg">Link to invalid asset</a>
36+
<a href="/guidelines/ui.pdf">Link to another invalid asset</a>

packages/starlight-links-validator/tests/fixtures/with-invalid-links/src/content/docs/test.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ title: Test
2020
- [Link to invalid anchor in this page](#links)
2121
- [Link to valid anchor in another MDX page](/guides/example/#some-links)
2222
- [Link to invalid anchor in another MDX page](/guides/example/#links)
23+
- [Link to invalid asset](/icon.svg)
24+
- [Link to another invalid asset](/guidelines/ui.pdf)
2325

2426
<div id="aDiv">
2527
some content

packages/starlight-links-validator/tests/fixtures/with-valid-links/src/content/docs/guides/example.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ some content
3434
test text
3535
</a>
3636
</div>
37+
38+
<a href="/favicon.svg">Link to an asset</a>
39+
<a href="/guidelines/dummy.pdf">Link to another asset</a>

packages/starlight-links-validator/tests/fixtures/with-valid-links/src/content/docs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ title: Index
2121

2222
- [Link to anchor in this page](#some-links)
2323
- [Link to anchor in another MDX page](/guides/example/#some-links)
24+
- [Link to an asset](/favicon.svg)
25+
- [Link to another asset](/guidelines/dummy.pdf)
2426

2527
## A more `complex` heading
2628

packages/starlight-links-validator/tests/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export async function loadFixture(name: string) {
1818

1919
// Copy the base fixture files.
2020
await cp(join(baseFixturePath, 'src'), join(testPath, 'src'), { recursive: true })
21+
await cp(join(baseFixturePath, 'public'), join(testPath, 'public'), { recursive: true })
2122

2223
// Copy the fixture under test files that may override the base fixture files.
2324
await cp(join(fixturePath, 'src'), join(testPath, 'src'), { force: true, recursive: true })

packages/starlight-links-validator/tests/validation.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test('should not build with invalid links', async () => {
1616
try {
1717
await loadFixture('with-invalid-links')
1818
} catch (error) {
19-
expect(error).toMatch(/Found 14 invalid links in 3 files./)
19+
expect(error).toMatch(/Found 18 invalid links in 3 files./)
2020

2121
expect(error).toMatch(
2222
new RegExp(`▶ test/
@@ -27,6 +27,8 @@ test('should not build with invalid links', async () => {
2727
├─ /unknown/#title
2828
├─ #links
2929
├─ /guides/example/#links
30+
├─ /icon.svg
31+
├─ /guidelines/ui.pdf
3032
└─ #anotherDiv`)
3133
)
3234

@@ -35,7 +37,9 @@ test('should not build with invalid links', async () => {
3537
├─ #links
3638
├─ /unknown/#links
3739
├─ /unknown
38-
└─ #anotherBlock`)
40+
├─ #anotherBlock
41+
├─ /icon.svg
42+
└─ /guidelines/ui.pdf`)
3943
)
4044

4145
expect(error).toMatch(

0 commit comments

Comments
 (0)