Skip to content

Commit 74f5ada

Browse files
committed
feat: detect dead links
1 parent f484f9a commit 74f5ada

File tree

6 files changed

+56
-14
lines changed

6 files changed

+56
-14
lines changed

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@
8787
"polka": "^0.5.2",
8888
"prismjs": "^1.23.0",
8989
"sirv": "^1.0.11",
90-
"slash": "^3.0.0",
9190
"vite": "^2.0.0-beta.70",
9291
"vue": "^3.0.5"
9392
},

src/node/build/bundle.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ora from 'ora'
22
import path from 'path'
3-
import slash from 'slash'
3+
import { slash } from '../utils/slash'
44
import { APP_PATH } from '../alias'
55
import { SiteConfig } from '../config'
66
import { RollupOutput } from 'rollup'

src/node/markdownToVue.ts

+33-4
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@ import LRUCache from 'lru-cache'
55
import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown'
66
import { deeplyParseHeader } from './utils/parseHeader'
77
import { PageData, HeadConfig } from '../../types/shared'
8-
import slash from 'slash'
8+
import { slash } from './utils/slash'
9+
import chalk from 'chalk'
910

1011
const debug = require('debug')('vitepress:md')
1112
const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 })
1213

1314
interface MarkdownCompileResult {
1415
vueSrc: string
1516
pageData: PageData
17+
deadLinks: string[]
1618
}
1719

1820
export function createMarkdownToVueRenderFn(
1921
root: string,
20-
options: MarkdownOptions = {}
22+
options: MarkdownOptions = {},
23+
pages: string[]
2124
) {
2225
const md = createMarkdownRenderer(root, options)
26+
pages = pages.map((p) => slash(p.replace(/\.md$/, '')))
2327

2428
return (src: string, file: string): MarkdownCompileResult => {
2529
const relativePath = slash(path.relative(root, file))
@@ -40,7 +44,31 @@ export function createMarkdownToVueRenderFn(
4044
.replace(/import\.meta/g, 'import.<wbr/>meta')
4145
.replace(/process\.env/g, 'process.<wbr/>env')
4246

43-
// TODO validate data.links?
47+
// validate data.links
48+
const deadLinks = []
49+
if (data.links) {
50+
const dir = path.dirname(file)
51+
for (let url of data.links) {
52+
url = url.replace(/[?#].*$/, '').replace(/\.(html|md)$/, '')
53+
if (url.endsWith('/')) url += `index`
54+
const resolved = slash(
55+
url.startsWith('/')
56+
? url.slice(1)
57+
: path.relative(root, path.resolve(dir, url))
58+
)
59+
if (!pages.includes(resolved)) {
60+
console.warn(
61+
chalk.yellow(
62+
`\n(!) Found dead link ${chalk.cyan(
63+
url
64+
)} in file ${chalk.white.dim(file)}`
65+
)
66+
)
67+
deadLinks.push(url)
68+
}
69+
}
70+
}
71+
4472
const pageData: PageData = {
4573
title: inferTitle(frontmatter, content),
4674
description: inferDescription(frontmatter),
@@ -59,7 +87,8 @@ export function createMarkdownToVueRenderFn(
5987

6088
const result = {
6189
vueSrc,
62-
pageData
90+
pageData,
91+
deadLinks
6392
}
6493
cache.set(src, result)
6594
return result

src/node/plugin.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { SiteConfig, resolveSiteData } from './config'
44
import { createMarkdownToVueRenderFn } from './markdownToVue'
55
import { APP_PATH, SITE_DATA_REQUEST_PATH } from './alias'
66
import createVuePlugin from '@vitejs/plugin-vue'
7-
import slash from 'slash'
7+
import { slash } from './utils/slash'
88
import { OutputAsset, OutputChunk } from 'rollup'
99

1010
const hashRE = /\.(\w+)\.js$/
@@ -24,18 +24,19 @@ const isPageChunk = (
2424

2525
export function createVitePressPlugin(
2626
root: string,
27-
{ configPath, alias, markdown, site, vueOptions }: SiteConfig,
27+
{ configPath, alias, markdown, site, vueOptions, pages }: SiteConfig,
2828
ssr = false,
2929
pageToHashMap?: Record<string, string>
3030
): Plugin[] {
31-
const markdownToVue = createMarkdownToVueRenderFn(root, markdown)
31+
const markdownToVue = createMarkdownToVueRenderFn(root, markdown, pages)
3232

3333
const vuePlugin = createVuePlugin({
3434
include: [/\.vue$/, /\.md$/],
3535
...vueOptions
3636
})
3737

3838
let siteData = site
39+
let hasDeadLinks = false
3940

4041
const vitePressPlugin: Plugin = {
4142
name: 'vitepress',
@@ -71,7 +72,17 @@ export function createVitePressPlugin(
7172
transform(code, id) {
7273
if (id.endsWith('.md')) {
7374
// transform .md files into vueSrc so plugin-vue can handle it
74-
return markdownToVue(code, id).vueSrc
75+
const { vueSrc, deadLinks } = markdownToVue(code, id)
76+
if (deadLinks.length) {
77+
hasDeadLinks = true
78+
}
79+
return vueSrc
80+
}
81+
},
82+
83+
renderStart() {
84+
if (hasDeadLinks) {
85+
throw new Error(`One or more pages contain dead links.`)
7586
}
7687
},
7788

src/node/utils/slash.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function slash(p: string): string {
2+
return p.replace(/\\/g, '/')
3+
}

yarn.lock

+4-4
Original file line numberDiff line numberDiff line change
@@ -6090,10 +6090,10 @@ verror@1.10.0:
60906090
core-util-is "1.0.2"
60916091
extsprintf "^1.2.0"
60926092

6093-
vite@^2.0.0-beta.67:
6094-
version "2.0.0-beta.67"
6095-
resolved "https://registry.yarnpkg.com/vite/-/vite-2.0.0-beta.67.tgz#2d4e7a62a925539448bd18154008afb2b4484a07"
6096-
integrity sha512-QNxIRajidVG3ejikBUb17NgCV1bJ9UyKHBdItgw1O/ljQ1hBoph5I2/DrviqV4G9H3WP7teXk5vwQWuCVS9fqQ==
6093+
vite@^2.0.0-beta.70:
6094+
version "2.0.0"
6095+
resolved "https://registry.yarnpkg.com/vite/-/vite-2.0.0.tgz#156f35eadaa7947629aa8a24eb23129b07116ee3"
6096+
integrity sha512-rNli5g0DaQ6+btlRqkmaR06neWaJGApmt40gocqrYDNi2XoEXYQgKiHSWzMeUgc1Cdva2HduqazaE+RaKjBpdQ==
60976097
dependencies:
60986098
esbuild "^0.8.34"
60996099
postcss "^8.2.1"

0 commit comments

Comments
 (0)