Skip to content

Commit f6cf04b

Browse files
docs: correct local relative links (#1086)
* docs: correct local relative links * Fix PR
1 parent 96e32a6 commit f6cf04b

File tree

5 files changed

+44
-49
lines changed

5 files changed

+44
-49
lines changed

.github/workflows/pr.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ jobs:
3232
run: pnpm exec playwright install chromium
3333
- name: Run Checks
3434
run: pnpm run test:pr
35-
- name: Verify Links
36-
run: pnpm run verify-links
3735
preview:
3836
name: Preview
3937
runs-on: ubuntu-latest

nx.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
]
2323
},
2424
"targetDefaults": {
25+
"test:docs": {
26+
"cache": true,
27+
"inputs": ["{workspaceRoot}/docs/**/*"]
28+
},
2529
"test:knip": {
2630
"cache": true,
2731
"inputs": ["{workspaceRoot}/**/*"]

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
"type": "git",
66
"url": "https://github.com/TanStack/virtual.git"
77
},
8-
"packageManager": "pnpm@10.17.0",
8+
"packageManager": "pnpm@10.24.0",
99
"type": "module",
1010
"scripts": {
1111
"clean": "pnpm --filter \"./packages/**\" run clean",
12-
"preinstall": "node -e \"if(process.env.CI == 'true') {console.log('Skipping preinstall...'); process.exit(1)}\" || npx -y only-allow pnpm",
1312
"test": "pnpm run test:ci",
14-
"test:pr": "nx affected --targets=test:sherif,test:knip,test:eslint,test:lib,test:e2e,test:types,test:build,build",
15-
"test:ci": "nx run-many --targets=test:sherif,test:knip,test:eslint,test:lib,test:e2e,test:types,test:build,build",
13+
"test:pr": "nx affected --targets=test:sherif,test:knip,test:docs,test:eslint,test:lib,test:e2e,test:types,test:build,build",
14+
"test:ci": "nx run-many --targets=test:sherif,test:knip,test:docs,test:eslint,test:lib,test:e2e,test:types,test:build,build",
1615
"test:eslint": "nx affected --target=test:eslint",
1716
"test:format": "pnpm run prettier --check",
1817
"test:sherif": "sherif",
@@ -22,19 +21,20 @@
2221
"test:types": "nx affected --target=test:types --exclude=examples/**",
2322
"test:e2e": "nx affected --target=test:e2e --exclude=examples/**",
2423
"test:knip": "knip",
24+
"test:docs": "node scripts/verify-links.ts",
2525
"build": "nx affected --target=build --exclude=examples/**",
2626
"build:all": "nx run-many --target=build --exclude=examples/**",
2727
"watch": "pnpm run build:all && nx watch --all -- pnpm run build:all",
2828
"dev": "pnpm run watch",
2929
"prettier": "prettier --ignore-unknown '**/*'",
3030
"prettier:write": "pnpm run prettier --write",
31-
"verify-links": "node scripts/verify-links.ts",
3231
"changeset": "changeset",
3332
"changeset:version": "changeset version && pnpm install --no-frozen-lockfile && pnpm prettier:write",
3433
"changeset:publish": "changeset publish"
3534
},
3635
"nx": {
3736
"includedScripts": [
37+
"test:docs",
3838
"test:knip",
3939
"test:sherif"
4040
]

scripts/verify-links.ts

Lines changed: 34 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,34 @@ 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+
relativeLinkExists(link, file)
115108
})
116109
}
117110
}
@@ -120,7 +113,7 @@ async function findMarkdownLinks() {
120113
console.log(`\n❌ Found ${errors.length} broken links:`)
121114
errors.forEach((err) => {
122115
console.log(
123-
`${err.link}\n in: ${err.markdownFile}\n path: ${err.resolvedPath}\n why: ${err.reason}\n`,
116+
`${err.file}\n link: ${err.link}\n resolved: ${err.resolvedPath}\n why: ${err.reason}\n`,
124117
)
125118
})
126119
process.exit(1)
@@ -129,4 +122,4 @@ async function findMarkdownLinks() {
129122
}
130123
}
131124

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

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@
2323
"strict": true,
2424
"target": "ES2020"
2525
},
26-
"include": ["eslint.config.js", "prettier.config.js", "scripts"]
26+
"include": ["*.config.*", "scripts"]
2727
}

0 commit comments

Comments
 (0)