Published on

使用 pre-commit hook 在提交前追加更新日期

问题

与采用 CMS 实现技术博客不同,在 GatsbyNext 等框架中获取文章的修改日期可能会有一些麻烦。 因为这种方式实现的博客是静态的,无服务端的。只能在 Markdown 中手动维护 frontmatter。 手动维护成本太高了,并且也懒得每次改文章的时候打开日历看下日期。 除非你在 .git 文件夹中去查找修改日期。 但如果 .git 目录未与存储库一起部署,则无法访问 git 日志,也无法拉取时间戳; 或者某一天把这个博客开源出去信息就泄露了。

寻找解决方案

冲浪的时候,发现这样一个帖子 https://twitter.com/monicalent/status/1353327937085464576. Ta 是在 commit 的时候向 frontmatter 追加一个 updateOn 的字段。 值得参考,觉得可行。

设置 Husky 和 Lint-staged

npm i -D husky lint-staged
# 添加 Husky 的 hook;它会创建 .husky 目录
npx husky install
# 添加 precommit hook,这个 hook 在 commit 之前执行 npm run lint:staged
npx husky add .husky/pre-commit "npm run lint:staged"

创建 .lintstagedrc 或者直接在 package.json 中添加 lint:staged 属性。

{
  // 我的文章在 data/blog 目录下,所以这个目录下的 md / mdx 文件有 commit 时,会执行这段 script
  "data/blog/*.{md,mdx}": "node scripts/updateFrontmatter.js"
}

lint-staged 配置到 package.json 的 script 属性下,因为最终 husky 是执行的 npm run xx 指令

{
  // ...
  "scripts": {
    // ...
    "lint:staged": "lint-staged"
  }
}

添加 script

gray-matter 是一个专门用来解析 Markdown frontmatter 的库。

const fs = require('fs').promises
const matter = require('gray-matter')

// 给修改的 mdx 文件添加 updatedOn 字段
const updateFrontmatter = async () => {
  // mdFilePaths 就是 commit 时更改的文件
  const [, , ...mdFilePaths] = process.argv

  mdFilePaths.forEach(async (path) => {
    const file = matter.read(path)
    const { data: currentFrontmatter } = file

    // 当前文章不是草稿
    if (!currentFrontmatter.draft) {
      const updatedFrontmatter = {
        ...currentFrontmatter,
        // 追加更新时间
        updatedOn: new Date().toISOString().slice(0, 10),
      }
      file.data = updatedFrontmatter
      // 转成 markdown 文本
      const updatedFileContent = matter.stringify(file)
      // 重新写入
      fs.writeFile(path, updatedFileContent)
    }
  })
}

updateFrontmatter()

process.argv 是什么❓ 因为我们使用 Huskylint-staged 创建了一个 git commit hook。 这里的意思是,当我们提交包含 Markdown 或 MDX 文件时, 我们可以运行 JS 脚本并将路径作为参数传递给 process.argv

发散思维

pre-commit hook 并不是只能做这一件事,其实还可以用 pre-commit 将文章中用到的图片上传到 CDN 上并在文章中替换,Wow!这个想法不错, 有时间可以试试。