From 74f5adafcde8a597939fbb37422aa68187b6dad4 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 16 Feb 2021 19:59:10 -0500 Subject: [PATCH] feat: detect dead links --- package.json | 1 - src/node/build/bundle.ts | 2 +- src/node/markdownToVue.ts | 37 +++++++++++++++++++++++++++++++++---- src/node/plugin.ts | 19 +++++++++++++++---- src/node/utils/slash.ts | 3 +++ yarn.lock | 8 ++++---- 6 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 src/node/utils/slash.ts diff --git a/package.json b/package.json index 5ef87934aa86..4bf35b41231f 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "polka": "^0.5.2", "prismjs": "^1.23.0", "sirv": "^1.0.11", - "slash": "^3.0.0", "vite": "^2.0.0-beta.70", "vue": "^3.0.5" }, diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index f167ab6adde8..0142216f6845 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -1,6 +1,6 @@ import ora from 'ora' import path from 'path' -import slash from 'slash' +import { slash } from '../utils/slash' import { APP_PATH } from '../alias' import { SiteConfig } from '../config' import { RollupOutput } from 'rollup' diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 463f175c6539..dc6aeb023e84 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -5,7 +5,8 @@ import LRUCache from 'lru-cache' import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown' import { deeplyParseHeader } from './utils/parseHeader' import { PageData, HeadConfig } from '../../types/shared' -import slash from 'slash' +import { slash } from './utils/slash' +import chalk from 'chalk' const debug = require('debug')('vitepress:md') const cache = new LRUCache({ max: 1024 }) @@ -13,13 +14,16 @@ const cache = new LRUCache({ max: 1024 }) interface MarkdownCompileResult { vueSrc: string pageData: PageData + deadLinks: string[] } export function createMarkdownToVueRenderFn( root: string, - options: MarkdownOptions = {} + options: MarkdownOptions = {}, + pages: string[] ) { const md = createMarkdownRenderer(root, options) + pages = pages.map((p) => slash(p.replace(/\.md$/, ''))) return (src: string, file: string): MarkdownCompileResult => { const relativePath = slash(path.relative(root, file)) @@ -40,7 +44,31 @@ export function createMarkdownToVueRenderFn( .replace(/import\.meta/g, 'import.meta') .replace(/process\.env/g, 'process.env') - // TODO validate data.links? + // validate data.links + const deadLinks = [] + if (data.links) { + const dir = path.dirname(file) + for (let url of data.links) { + url = url.replace(/[?#].*$/, '').replace(/\.(html|md)$/, '') + if (url.endsWith('/')) url += `index` + const resolved = slash( + url.startsWith('/') + ? url.slice(1) + : path.relative(root, path.resolve(dir, url)) + ) + if (!pages.includes(resolved)) { + console.warn( + chalk.yellow( + `\n(!) Found dead link ${chalk.cyan( + url + )} in file ${chalk.white.dim(file)}` + ) + ) + deadLinks.push(url) + } + } + } + const pageData: PageData = { title: inferTitle(frontmatter, content), description: inferDescription(frontmatter), @@ -59,7 +87,8 @@ export function createMarkdownToVueRenderFn( const result = { vueSrc, - pageData + pageData, + deadLinks } cache.set(src, result) return result diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 7a6e3acfeae9..dbbff7095324 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -4,7 +4,7 @@ import { SiteConfig, resolveSiteData } from './config' import { createMarkdownToVueRenderFn } from './markdownToVue' import { APP_PATH, SITE_DATA_REQUEST_PATH } from './alias' import createVuePlugin from '@vitejs/plugin-vue' -import slash from 'slash' +import { slash } from './utils/slash' import { OutputAsset, OutputChunk } from 'rollup' const hashRE = /\.(\w+)\.js$/ @@ -24,11 +24,11 @@ const isPageChunk = ( export function createVitePressPlugin( root: string, - { configPath, alias, markdown, site, vueOptions }: SiteConfig, + { configPath, alias, markdown, site, vueOptions, pages }: SiteConfig, ssr = false, pageToHashMap?: Record ): Plugin[] { - const markdownToVue = createMarkdownToVueRenderFn(root, markdown) + const markdownToVue = createMarkdownToVueRenderFn(root, markdown, pages) const vuePlugin = createVuePlugin({ include: [/\.vue$/, /\.md$/], @@ -36,6 +36,7 @@ export function createVitePressPlugin( }) let siteData = site + let hasDeadLinks = false const vitePressPlugin: Plugin = { name: 'vitepress', @@ -71,7 +72,17 @@ export function createVitePressPlugin( transform(code, id) { if (id.endsWith('.md')) { // transform .md files into vueSrc so plugin-vue can handle it - return markdownToVue(code, id).vueSrc + const { vueSrc, deadLinks } = markdownToVue(code, id) + if (deadLinks.length) { + hasDeadLinks = true + } + return vueSrc + } + }, + + renderStart() { + if (hasDeadLinks) { + throw new Error(`One or more pages contain dead links.`) } }, diff --git a/src/node/utils/slash.ts b/src/node/utils/slash.ts new file mode 100644 index 000000000000..f06ed9399d04 --- /dev/null +++ b/src/node/utils/slash.ts @@ -0,0 +1,3 @@ +export function slash(p: string): string { + return p.replace(/\\/g, '/') +} diff --git a/yarn.lock b/yarn.lock index 76c4897592d3..4cc35a043ed5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6090,10 +6090,10 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vite@^2.0.0-beta.67: - version "2.0.0-beta.67" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.0.0-beta.67.tgz#2d4e7a62a925539448bd18154008afb2b4484a07" - integrity sha512-QNxIRajidVG3ejikBUb17NgCV1bJ9UyKHBdItgw1O/ljQ1hBoph5I2/DrviqV4G9H3WP7teXk5vwQWuCVS9fqQ== +vite@^2.0.0-beta.70: + version "2.0.0" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.0.0.tgz#156f35eadaa7947629aa8a24eb23129b07116ee3" + integrity sha512-rNli5g0DaQ6+btlRqkmaR06neWaJGApmt40gocqrYDNi2XoEXYQgKiHSWzMeUgc1Cdva2HduqazaE+RaKjBpdQ== dependencies: esbuild "^0.8.34" postcss "^8.2.1"