你重命名了一个文件,重组了一个文件夹,微调了一个标题——然后悄无声息地在你的自述文件和文档中留下了一堆失效链接。直到有读者点击 ./old-guide.md 并收到一个 404 错误时,才有人注意到。
市面上有一些工具可以解决这个问题,但每个工具都有各自的门槛:
- markdown-link-check 虽然可用,但它引入了大约 9 个依赖项,而且其主要功能是通过 HTTP 请求外部统一资源定位符——速度慢、受速率限制,且在持续集成环境中不稳定(如果某人的博客宕机,你的构建过程不应因此报错)。
- lychee 速度快且优秀,但它是一个需要安装的 Rust 二进制文件。
- 旧的 Python 选项
mlc自 2021 年起已无人维护。
我希望处理本地链接这一确定性部分能够即时完成且无需依赖。因此,我构建了 linkbust:
npx linkbust
README.md:8 ✗ ./setup.md (未找到文件)
docs/api.md:14 ✗ #usage (该文件中无此锚点)
guide.md:3 ✗ ./api.md#missing (./api.md 中无此锚点)
✗ 3 个失效链接 · 已检查 142 个本地链接 · 跳过 38 个外部链接 (从未获取)
它检查什么(以及跳过什么)
- 相对文件链接和图片源 会在磁盘上进行解析。
-
锚点 — 无论是同一文件中的
[x](#section)还是跨文件的[x](other.md#section)— 都会匹配真实的标题(GitHub 风格的 slug)或显式的<a id="…">。 -
引用式链接
[text][ref]拥有匹配的[ref]: …定义。 - 围栏代码块或行内代码块中的链接会被忽略,因此示例代码不会引发误报。
它刻意绝不发起网络请求 — http(s)、mailto: 和 /absolute 路径会被分类并跳过。这就是整个设计理念:因为它是离线运行的,所以速度极快且不会出现不稳定情况。这使其成为理想的 预提交钩子:
- repo: local
hooks:
- id: linkbust
name: linkbust
entry: npx linkbust
language: system
pass_filenames: false
如果有任何链接失效,退出代码为 1,因此持续集成会干净地失败。
零依赖,双生态支持
纯标准库实现 — 包含一个小型标记语言解析器(代码掩码、引用式链接、GitHub 锚点 slug)以及文件系统检查。Node 和 Python 版本的行为完全一致,逐字节相同(相同的输出,相同的退出代码)。
npx linkbust # Node >= 18
pip install linkbust # Python >= 3.8