Skip to content

Commit 3c8f697

Browse files
docs: correct local relative links (#324)
1 parent 99aaace commit 3c8f697

File tree

3 files changed

+51
-48
lines changed

3 files changed

+51
-48
lines changed

docs/overview.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ The following tools are required to use these packages:
1818
1919
## Utilities
2020

21-
- [ESLint](../eslint.md)
22-
- [Publish](../publish.md)
23-
- [Vite](../vite.md)
21+
- [ESLint](./eslint.md)
22+
- [Publish](./publish.md)
23+
- [Vite](./vite.md)
2424

2525
## Conventions
2626

27-
- [CI/CD](../ci-cd.md)
28-
- [Dependencies](../dependencies.md)
29-
- [Package Structure](../package-structure.md)
27+
- [CI/CD](./ci-cd.md)
28+
- [Dependencies](./dependencies.md)
29+
- [Package Structure](./package-structure.md)

docs/package-structure.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The following structure ensures packages work optimally with our monorepo/Nx wor
1919

2020
### `./vite.config.ts`
2121

22-
- Includes config for Vitest, and for Vite if [@tanstack/vite-config](../vite.md) is used
22+
- Includes config for Vitest, and for Vite if [@tanstack/vite-config](./vite.md) is used
2323

2424
### `./src`
2525

scripts/verify-links.ts

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import { existsSync, readFileSync, statSync } from 'node:fs'
2-
import path, { resolve } from 'node:path'
2+
import { extname, resolve } from 'node:path'
33
import { glob } from 'tinyglobby'
44
// @ts-ignore Could not find a declaration file for module 'markdown-link-extractor'.
55
import markdownLinkExtractor from 'markdown-link-extractor'
66

7+
const errors: Array<{
8+
file: string
9+
link: string
10+
resolvedPath: string
11+
reason: string
12+
}> = []
13+
714
function isRelativeLink(link: string) {
815
return (
9-
link &&
16+
!link.startsWith(`/`) &&
1017
!link.startsWith('http://') &&
1118
!link.startsWith('https://') &&
1219
!link.startsWith('//') &&
@@ -15,39 +22,33 @@ function isRelativeLink(link: string) {
1522
)
1623
}
1724

18-
function normalizePath(p: string): string {
19-
// Remove any trailing .md
20-
p = p.replace(`${path.extname(p)}`, '')
21-
return p
25+
/** Remove any trailing .md */
26+
function stripExtension(p: string): string {
27+
return p.replace(`${extname(p)}`, '')
2228
}
2329

24-
function fileExistsForLink(
25-
link: string,
26-
markdownFile: string,
27-
errors: Array<any>,
28-
): boolean {
30+
function relativeLinkExists(link: string, file: string): boolean {
2931
// Remove hash if present
30-
const filePart = link.split('#')[0]
32+
const linkWithoutHash = link.split('#')[0]
3133
// If the link is empty after removing hash, it's not a file
32-
if (!filePart) return false
33-
34-
// Normalize the markdown file path
35-
markdownFile = normalizePath(markdownFile)
34+
if (!linkWithoutHash) return false
3635

37-
// Normalize the path
38-
const normalizedPath = normalizePath(filePart)
36+
// Strip the file/link extensions
37+
const filePath = stripExtension(file)
38+
const linkPath = stripExtension(linkWithoutHash)
3939

4040
// Resolve the path relative to the markdown file's directory
41-
let absPath = resolve(markdownFile, normalizedPath)
41+
// Nav up a level to simulate how links are resolved on the web
42+
let absPath = resolve(filePath, '..', linkPath)
4243

4344
// Ensure the resolved path is within /docs
4445
const docsRoot = resolve('docs')
4546
if (!absPath.startsWith(docsRoot)) {
4647
errors.push({
4748
link,
48-
markdownFile,
49+
file,
4950
resolvedPath: absPath,
50-
reason: 'navigates above /docs, invalid',
51+
reason: 'Path outside /docs',
5152
})
5253
return false
5354
}
@@ -76,42 +77,44 @@ function fileExistsForLink(
7677
if (!exists) {
7778
errors.push({
7879
link,
79-
markdownFile,
80+
file,
8081
resolvedPath: absPath,
81-
reason: 'not found',
82+
reason: 'Not found',
8283
})
8384
}
8485
return exists
8586
}
8687

87-
async function findMarkdownLinks() {
88+
async function verifyMarkdownLinks() {
8889
// Find all markdown files in docs directory
8990
const markdownFiles = await glob('docs/**/*.md', {
9091
ignore: ['**/node_modules/**'],
9192
})
9293

9394
console.log(`Found ${markdownFiles.length} markdown files\n`)
9495

95-
const errors: Array<any> = []
96-
9796
// Process each file
9897
for (const file of markdownFiles) {
9998
const content = readFileSync(file, 'utf-8')
100-
const links: Array<any> = markdownLinkExtractor(content)
101-
102-
const filteredLinks = links.filter((link: any) => {
103-
if (typeof link === 'string') {
104-
return isRelativeLink(link)
105-
} else if (link && typeof link.href === 'string') {
106-
return isRelativeLink(link.href)
107-
}
108-
return false
99+
const links: Array<string> = markdownLinkExtractor(content)
100+
101+
const relativeLinks = links.filter((link: string) => {
102+
return isRelativeLink(link)
109103
})
110104

111-
if (filteredLinks.length > 0) {
112-
filteredLinks.forEach((link) => {
113-
const href = typeof link === 'string' ? link : link.href
114-
fileExistsForLink(href, file, errors)
105+
if (relativeLinks.length > 0) {
106+
relativeLinks.forEach((link) => {
107+
if (!link.startsWith('.')) {
108+
errors.push({
109+
link,
110+
file,
111+
resolvedPath: '',
112+
reason: 'Does not start with ./ or ../',
113+
})
114+
return
115+
}
116+
117+
relativeLinkExists(link, file)
115118
})
116119
}
117120
}
@@ -120,7 +123,7 @@ async function findMarkdownLinks() {
120123
console.log(`\n❌ Found ${errors.length} broken links:`)
121124
errors.forEach((err) => {
122125
console.log(
123-
`${err.link}\n in: ${err.markdownFile}\n path: ${err.resolvedPath}\n why: ${err.reason}\n`,
126+
`${err.file}\n link: ${err.link}\n resolved: ${err.resolvedPath}\n why: ${err.reason}\n`,
124127
)
125128
})
126129
process.exit(1)
@@ -129,4 +132,4 @@ async function findMarkdownLinks() {
129132
}
130133
}
131134

132-
findMarkdownLinks().catch(console.error)
135+
verifyMarkdownLinks().catch(console.error)

0 commit comments

Comments
 (0)