こんにちは、CX事業本部 Delivery部の若槻です。
今回は npm workspaces 環境で dorny/paths-filter
を使って、workspaces ごとの GitHub Actions の実行を制御してみました。
npm workspaces 環境で workspaces ごとのアクション実行を良い感じに制御したい
npm workspaces を利用すると、複数の npm プロジェクト(workspace)から成るモノリポ環境を容易に管理することができるようになります。
sub1
およびsub2
workspace を持つ npm workspaces 環境を構築します。
npm init -y
npm init -w ./sub1 -y
npm init -w ./sub2 -y
するとnode_modules
およびpackage-lock.json
はルートに作成され、各 workspaces から参照されるようになります。
$ tree -L 2
.
├── README.md
├── node_modules
│ ├── sub1 -> ../sub1
│ └── sub2 -> ../sub2
├── package-lock.json
├── package.json
├── sub1
│ └── package.json
└── sub2
└── package.json
この環境に対して GitHub Actions workflow を実装するなら次のようになります。これにより各 workspaces に対して、sub1
およびsub2
の変更があった場合にのみ workflow が実行されるようになります。
.github/workflows/sub1-build.yml
on:
pull_request:
paths:
- sub1/**
- .github/workflows/sub1.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Build sub1!"
.github/workflows/sub2-build.yml
on:
pull_request:
paths:
- sub2/**
- .github/workflows/sub2.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Build sub2!"
しかし上記の workflow のみを実装した場合の問題点として、ルートのpackage.json
やpackage-lock.json
などに変更があった場合には、各 workspaces に対して workflow が実行されません。ESLint や Prettier など一部の devDependency はルートから各 workspaces に対して利用するため、ルートのpackage.json
に記述する場合があります。
理想としては、次のようにルートのファイルが更新された場合は、各 workspaces に対して workflow が実行されるようにしたいです。また同時に workspace のファイルに更新があった場合に、同じ workspace に対する重複実行を回避するようにしたいです。
更新ファイル | 実行するワークフロー |
---|---|
package.json , package-lock.json |
sub1-build.yml , sub2-build.yml |
sub1/** |
sub1-build.yml |
sub2/** |
sub2-build.yml |
dorny/paths-filter GitHub Actions を使う
そこで、更新が行われた workspace ごとに良い感じに workflow を実行するための方法としてdorny/paths-filter
を利用します。
まず workspace ごとの workflow の on イベントにworkflow_call
を追加して、別の workflow から呼び出せるようにします。
.github/workflows/sub1-build.yml
on:
pull_request:
paths:
- sub1/**
- .github/workflows/sub1.yml
workflow_call:
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Build sub1!"
.github/workflows/sub2-build.yml
on:
pull_request:
paths:
- sub2/**
- .github/workflows/sub2.yml
workflow_call:
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Build sub2!"
そして、追加の workflow では paths
でpackage.json
およびpackage-lock.json
を指定して、ルートの変更を検知します。そしてsub1-build
およびsub2-build
ジョブで 前述の2つの workflow を呼び出すのですが、この時 workflow が2重起動しないように、dorny/paths-filter
を利用して変更があったファイルを抽出し、呼び出す workflow を制御するようにしています。
.github/workflows/root-build.yml
on:
pull_request:
paths:
- package.json
- package-lock.json
- .github/workflows/root-build.yml
jobs:
changes:
runs-on: ubuntu-latest
permissions:
pull-requests: read
contents: read
outputs:
sub1: ${{ steps.filter.outputs.sub1 }}
sub2: ${{ steps.filter.outputs.sub2 }}
steps:
- uses: actions/checkout@v3
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
sub1:
- sub1/**
- .github/workflows/sub1.yml
sub2:
- sub2/**
- .github/workflows/sub2.yml
sub1-build:
needs: changes
if: ${{ needs.changes.outputs.sub1 == 'false' }}
uses: ./.github/workflows/sub1-build.yml
sub2-build:
needs: changes
if: ${{ needs.changes.outputs.sub2 == 'false' }}
uses: ./.github/workflows/sub2-build.yml
この dorny/paths-filter のフィルターの対象となるのは Pull Request 中で変更されたファイルです。push されたコミットではありません。よって permission として pull-requests: read を指定する必要があります。
動作確認
ルートのファイルのみ更新された場合
まず、ルートのpackage.json
およびpackage-lock.json
のみ更新し、変更を Pull Request に push します。
$ npm i eslint@latest
added 96 packages, and audited 104 packages in 4s
24 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: package-lock.json
modified: package.json
no changes added to commit (use "git add" and/or "git commit -a")
Pull Request への push による CI が実行されました。
ルートの workflow が実行され、そこからsub1-build
およびsub2-build
workflow が呼び出されて実行されています。
- .github/workflows/root-build.yml / changes (pull_request)
- .github/workflows/root-build.yml / sub1-build / build (pull_request)
- .github/workflows/root-build.yml / sub2-build / build (pull_request)
sub1 ディレクトリ配下のファイルが更新された場合
続いて、sub1
ディレクトリ配下のpackage.json
と、ルートのpackage-lock.json
を更新し、変更を Pull Request に push します。
$ git commit -m "npm i eslint@latest"
[feature-hoge-1 0402536] npm i eslint@latest
2 files changed, 1003 insertions(+), 1 deletion(-)
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: sub1/package-lock.json
modified: sub1/package.json
no changes added to commit (use "git add" and/or "git commit -a")
Pull Request への push による CI が実行されました。
今度は先程とは異なり、ルートの workflow からsub2-build
workflow のみが呼び出されて実行されています。一方 sub1-build
workflow は直接呼び出されています。
- .github/workflows/root-build.yml / changes (pull_request)
- .github/workflows/sub1-build.yml / build (pull_request)
- .github/workflows/root-build.yml / sub2-build / build (pull_request)
そしてルートの workflow からsub1-build
workflow の呼び出しはスキップされています。
- .github/workflows/root-build.yml / sub1-build (pull_request)
おわりに
npm workspaces 環境で dorny/paths-filter を使って、workspaces ごとの GitHub Actions の実行を制御してみました。
dorny/paths-filter GitHub Actions を上手く使うことにより、ルートのファイルが更新された場合は各 workspaces に対して workflow が実行され、また workspace のファイルに更新があった場合に同じ workspace に対する重複実行を回避するすることができました。これで npm workspaces 環境でも過不足なく GitHub Actions workflow をトリガーさせることができそうです。
参考
以上