🦁

ある特定のファイルが更新されたらコマンドを実行する

2021/01/30に公開

フロントエンドやバックエンドを Node.js でチーム開発をしていると package.json が更新されているのに、気づかずそのまま実行してエラーが出るみたいな現象が生じがちです。

コミュニケーションその他でカバーすることも可能ですが、我々プログラマたるもの、全て自動でやるべきです。そこで、Git の pre commit hook に使われることの多い husky を使って、更新を検知してフック処理を実行してみましょう。

husky

Husky は package.json 上で、Git フックをいい感じに扱える便利なパッケージです。よく使われるパターンは lint-staged というパッケージと組み合わせて、コミット時に eslint や prettier あるいは TypeScript の型チェックを走らせるという用途が多いでしょう。それらの資料は山程転がってるので、そちらに興味がある人はいい感じにぐぐってください。

他者による package.json の更新を検知する

さて今回の目的は、誰かが package.json を更新したら npm i もしくは yarn を実行して、パッケージを更新したいというものです。

自分が package.json を更新した場合は、自分自身でパッケージをインストールしてるはずなので、誰か他の人のコミットを取り込んだときに、package.json の更新を検知できれば良いでしょう。

Husky が対応しているフックには post-merge とか post-checkout というものがあります。

"husky": {
  "hooks": {
    "post-merge": "node scripts/install.js",
    "post-checkout": "node scripts/install.js"
  }
}

Git で merge, rebase や checkout をしたら scripts/install.js を走らせるようにしましょう。

scripts/install.js

/* eslint-disable */
const { execSync } = require('child_process')

try {
  const stdout = execSync("git diff 'HEAD@{1}' --name-only").toString('utf-8')
  const changedFiles = stdout
    .trim()
    .split('\n')
    .filter((name) => ['package.json'].includes(name))
  // 他のファイル、たとえば `api.yaml` もチェックしたいなら配列に追加する

  if (changedFiles.length > 0) {
    execSync('npm i', { stdio: 'inherit' })
  }
} catch (e) {
  console.warn(e)
  console.log('you need npm install.')
}

process.exit(0)

git diff 'HEAD@{1}' --name-only で変更のあったファイル一覧を取得できるので、この中に package.json が含まれてないかを確認しています。変更があれば npm i を実行しています。

OpenAPI の更新を検知して、自動生成する

OpenAPI Generator typescript-fetch を使う に書いたような API 生成を行っている場合は、OpenAPI の定義ファイルが更新されたら、自動生成を走らせたいですよね???

先程の scripts/install.js.filter((name) => ["package.json"].includes(name)); に、対象ファイル名を追加しておきましょう。

その場合、OpenAPI の定義ファイルを更新したのに、なぜ npm i でパッケージ更新するのか?という疑問が出るのは当然です。

ここで少しトリックをいれます。

package.jsonscriptspostinstall という項目を追加します。

  "scripts": {
    "dev": "next dev",
    "postinstall": "npm run generate:api-client",
    "generate:api-client": "openapi-generator-cli generate -g typescript-fetch -i <OpenAPI定義ファイル> -o <生成ディレクトリ>  --additional-properties=modelPropertyNaming=camelCase,supportsES6=true,withInterfaces=true,typescriptThreePlus=true"
  }

たとえばこのように、postinstallnpm run generate:api-client を実行するようにしておくのです。

まとめ

  • Husky で、git checkout や merge/rebase などでスクリプトを走らせられる
  • スクリプトで、特定ファイルの更新を検知して npm i を走らせる
  • API クライアントの自動生成を package.jsonpostinstall で実行する

これによって、手動で更新するという非人道的なことをしなくて済む、素敵なプロダクトになりました。素晴らしいですね。

Discussion