脆弱性が検知されたり、ちょっと開発環境を改善したい、となって npm パッケージをいざバージョンアップしようと思っても、日常的にアップデートをしていないと期待するバージョンにアップするまでが茨の道デス(諦めたくなることもしばしば)。
dependabot はよく見かけるんですが、最近ではどうも Renovate がいいぞという記事をよく目にするので、今回は Renovate をこのブログのリポジトリに導入してみようと思い、色々と設定を調べてみました。
GitHub Apps として公開されているので、以下のリンクから Install します。
https://github.com/marketplace/renovate
必要な項目を入力して、Save。
Complete order and begin installation とかあるので、クリックして進みます。スクショ忘れましたが、このあと導入先のリポジトリの選択が求められるので、対象のリポジトリを指定します。
Renovate をインストールしたリポジトリに対して、自動で Renovate 設定のための PR が立ち上がります。
これをマージすると、renovate.json という設定ファイルが作成され、Renovate によるライブラリアップデートが開始されます(PR が作成されるようになる)。
作成された renovate.json は以下のようになっています。
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"]
}
$schema は JSON の定義なのでさておき、extends というプロパティがあります。これは Renovate やサードパーティ・ローカルが公開したプリセットを読み込むためのものです。
config:base は renovate が提供しているプリセットであり、具体的には以下の内容が適用されています。
{
"extends": [
":dependencyDashboard", // GitHub Issueとしてrenovateのダッシュボードを作成する。
":semanticPrefixFixDepsChoreOthers", // semantic commit messageを見つけたときに、依存関係のあるコミットにはfix、それ以外のコミットにはchoreでコミットする
":ignoreModulesAndTests", // node_modules, bower_components, vendorとテスト関連のフォルダ(/__tests__/**とか)を無視する
":autodetectRangeStrategy", // アップデート可能なバージョンの範囲に関する設定をautoにする。
":prHourlyLimit2", // 1時間に作成できるPRの数を2つに制限する
":prConcurrentLimit10", // 同時にOpen可能なPR数を10個に制限する
"group:monorepos", // 有名なmonorepo構成のパッケージのアップデートを1つのPRにまとめる
"group:recommended", // monorepo以外でよく使われているパッケージ群のアップデートを1つのPRにまとめる
"workarounds:all" // アップデートでよくあるワークグラウンドを一挙に適用する
]
}
より詳細な説明は下記リンクを参照してください。
ベースとして適用されている設定を把握したところで、設定ファイルを自分のリポジトリに合わせて修正していきます。
ライブラリアップデート方針はチームの状況や言語のエコシステムに依存するので一概にこうとは言えません。そのため、renovate の Docs にある Key concepts を理解し、その上でチームとして必要な設定を行うのが妥当に思えます。
Key Concepts では次の 5 つのトピックスが紹介されています。
Dependency Dashboard はそこまで設定に重要ではないのて、それ以外を見てみます。
Presets は先ほどの config:base のようなもので、よく使用されるルールをグルーピングしたものです。Presets を使うことで、
と記載があります。ESLint でもできるだけ個別適用は避け、既存のルールを利用するのが良いように、設定の簡易化のために Presets を使用するのが良いと感じました。
具体的には個別のプロパティが表示されている「Configuration > Repository」から設定が必要な値を探すのではなく、「Included Presets」の中から、自分がやりたいことをセットで提供しているルールを適用するのが良さそうです。
アップデートの PR をグループしない限りは、PR のタイトルやブランチ名は一意になります。
アップデートを見送りたいためユーザーが Close した PR があった場合、次回の renovate bot の稼働時にそのブランチ名と PR タイトルから該当のライブラリのアップデートの PR を検索することで状況をチェックし、同一バージョンでのアップデートの PR を再度送らない仕様です。
ただし「アップデートの PR をグループしない限りは」ということなので、グループ化は例外です。
例えばすべてのメジャーアップデート以外をまとめるグループ化設定を付与した結果、「All non-major updates」というタイトルでアップデート PR が作成されたとします。もし一度この PR を Close してしまうと、次回以降の PR も「All non-major updates」というタイトルであるがために、renovate は PR を作成しません。
そのため、設定時にグループ化を行う際には、そのグループにライブラリアップデートを無視する可能性がないか、グループの粒度は適切かを検討した方が良さそうです。
カスタマイズの方針がそのまま書いてありました。
- Tell Renovate what timezone you want to use
デフォルトでは UTC でスケジュールされているため、ローカルタイムでわかりやすくするには、チームのタイムゾーンを設定した方が良さそうです。
{
"timezone": "Asia/Tokyo"
}
- Learn about the scheduling syntax
renovate のスケジューリングは以下の制約があります
例としてはここら辺が設定できるようです。
every weekend
before 5:00am
[after 10pm, before 5:00am]
[after 10pm every weekday, before 5am every weekday]
on friday and saturday
- Optional: configure a “in repository schedule”
先の制約に基づいて時刻を設定します。例では毎日午前二時と一般的な影響時間外での設定が紹介されています。
{
"schedule": ["before 2am"]
}
{
"schedule": [
"after 10pm every weekday",
"before 5am every weekday",
"every weekend"
]
}
またスケジュール自体にも Presets があるようです。
Schedule Presets - Renovate Docs | Renovate Docs
個人的には月初日の午前 3 時以前が起動範囲の schedule:monthly なんか良さそうな気がしています。
- Optional: create packageRules with a custom schedule for specific packages
スケジュールは全体に対するものだけではなく、特定のパッケージに対するスケジュールも指定できます。例では aws-sdk のような頻繁に更新されるパッケージだけ週次実行にするサンプルが掲載されています。
{
"packageRules": [
{
"matchPackageNames": ["aws-sdk"],
"schedule": ["after 9pm on sunday"]
}
]
}
ライブラリアップデートを自動化して楽にするためのツールがノイズになってしまっては本末転倒なので、更新頻度が予見できるもの・稼働中に気になったライブラリは、独自スケジュールを設定すると良さそうです。
renovate には CI がとおった PR を自動マージする機能があります。
ドキュメントでは devDependencies や自動テストがしっかりしている PJ の dependencies であれば自動マージが効果的とありますそりゃそうだ)。
設定例が上がっていたので抜粋します。
lockFile のメンテナンスの自動化(依存関係は崩さずに、lockFile だけ更新する)。
{
"lockFileMaintenance": {
"enabled": true,
"automerge": true
}
}
Linter の自動化
{
"packageRules": [
{
"matchDepTypes": ["devDependencies"],
"matchPackagePatterns": ["lint", "prettier"],
"automerge": true
}
]
}
非メジャーバージョンの自動化
{
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentVersion": "!/^0/",
"automerge": true
}
]
}
自動マージはやや怖いですが、運用の中でチェックが形骸化しているライブラリについては自動マージを検討してもいいかもです。
ここまでは公式の方針に則って汎用的な設定方針を見てきましたが、ここからは実際に個人や仕事で使いたいようなことが実現できるのかを確認していきます。
Renovate はベースブランチの直近の 10 件のコミットメッセージを見て、そのリポジトリがセマンティックコミットを採用しているかどうかを判断し、セマンティックコミットがある場合にセマンティックコミットメッセージを採用するようです(おそらくこれが semanticCommits オプションの auto の挙動)。
Semantic Commit messages - Renovate Docs | Renovate Docs
PR では Prefix を採用しているが、コミットまではルール化していない場合にはルールが適用されないので、明示的にセマンティックコミットを使うような Presets を指定します。加えて、セマンティックコミットメッセージのスコープも設定することができるので、チームに必要な形式でそれぞれ以下のように設定すると良いでしょう。
// https://docs.renovatebot.com/presets-default/#semanticcommits
// 結果: chore(deps)
{
"extends": [":semanticCommits"]
}
// https://docs.renovatebot.com/presets-default/#semanticcommittypeallarg0
// 結果: ci(deps)
{
"extends": [":semanticCommits", ":semanticCommitTypeAll(ci)"]
}
// https://docs.renovatebot.com/presets-default/#semanticcommittypearg0
// 結果: ci(package)
{
"extends": [":semanticCommits", ":semanticCommitTypeAll(ci)", ":semanticCommitScope(package)"]
}
// https://docs.renovatebot.com/presets-default/#semanticcommitscopedisabled
// 結果: ci
{
"extends": [":semanticCommits", ":semanticCommitTypeAll(ci)", ":semanticCommitScopeDisabled"]
}
なお commitMessagePrefix や prTitle という指定もあるのですが、前述の通り現状の Renovate が PR タイトルで Close 済みかどうか等を判断しているため、こちらでの設定は推奨されていないようです。
config:base を設定することで、node_modules, bower_components, vendor とテスト関連のフォルダ(/tests/**とか)を無視することができますが、リポジトリにあるそれ以外のファイルはすべてアップデートの対象になります。
ignorePaths を指定することで、無視するパターンを追加することができます。
{
"ignorePaths": ["**/backend/**", "**/hoge/**"]
}
https://docs.renovatebot.com/configuration-options/#ignorepaths
{
"extends": [":onlyNpm"]
}
https://docs.renovatebot.com/presets-default/#onlynpm
{
"packageRules": [
{
"packagePatterns": ["^@types/"],
"automerge": true,
"major": {
"automerge": false
}
}
]
}
{
"packageRules": [
{
"excludePackageNames": ["typescript"]
}
]
}
https://docs.renovatebot.com/configuration-options/#excludepackagenames
{
"extends": ["packages:linters"]
}
Presets である”packages:linters”が次のようになっています。
{
"extends": [
"packages:emberTemplateLint",
"packages:eslint",
"packages:stylelint",
"packages:tslint"
],
"matchPackageNames": ["prettier", "remark-lint", "standard"]
}
さらに”packages:eslint”は以下のようになっています。
{
"matchPackageNames": ["@types/eslint", "babel-eslint"],
"matchPackagePrefixes": ["@typescript-eslint/", "eslint"]
}
TypeScript プロジェクトの場合、ESLint(eslint と@typescript-eslint)と Prettier がカバーできれば良さそうなので、結果として”packages:linters”だけの指定で良さそうです。
https://docs.renovatebot.com/presets-packages/#packageslinters https://docs.renovatebot.com/presets-packages/#packageseslint
https://github.com/apps/renovate-approve
Renovate が提供しているこの Bot を利用することで、最低 1 人の Approve が必要な場合でも自動でマージが可能になるようです。
https://github.com/apps/renovate-approve-2
最低 2 人の Approve が必要な場合は、こちらのボットも追加で導入が必要です。
デフォルトではラベルは適用されず、labels を指定することで適用されます。あまり差分はありませんが、1~2 個のラベルを指定できる Presets もあります。単純にラベルを付与するだけであればこちらで良いでしょう。
https://docs.renovatebot.com/presets-default/#labelarg0 https://docs.renovatebot.com/presets-default/#labelsarg0-arg1
{
"extends": [":labels(dependencies, renovate)"]
}
またラベルはパッケージレベルで付与することもでき、この場合は labels オプションを個別に適用します。
https://docs.renovatebot.com/configuration-options/#labels
以下のようにすることで、eslint 系のライブラリである場合は「linting」が適用されます(ラベルルールはマージされず、最後にマッチしたものが適用される)。
{
"labels": ["dependencies"],
"packageRules": [
{
"matchPackagePatterns": ["eslint"],
"labels": ["linting"]
}
]
}
「pin の PR を自動マージ」という設定が何箇所かで出てきて、「pin is 何?」と思っていたんですがこれは npm のバージョン固定の話みたいですね。
package.json のチルダ(~) とキャレット(^) - Qiita
https://docs.renovatebot.com/dependency-pinning/
https://github.com/commitizen/cz-conventional-changelog-default-export/pull/4#issuecomment-358038966
こちらも参照とのこと。
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":timezone(Asia/Tokyo)",
"schedule:monthly",
":label(renovate)",
"packages:linters",
":onlyNpm",
":semanticCommits",
":semanticCommitTypeAll(task)",
":semanticCommitScopeDisabled"
],
"ignorePaths": ["**/backend/**", "**/hoge/**"],
"packageRules": [
{
"packagePatterns": ["^@types/"],
"automerge": true,
"major": {
"automerge": false
}
},
{
"excludePackageNames": ["typescript"]
}
]
}
まずはこのブログのリポジトリでこの設定で動かしてみて、問題があればまた追記・修正したいと思います。
dependabot より柔軟、されど設定項目が多いという前評判は確かに,,,という感じでした。 CLI 版を使ってローカルで起動することもできるっぽいので色々実験しつつ、仕事で使っている環境にも入れていきたい所存です。