tfaction でトランスパイルした JS をコミットするのを止めた
tfaction という、 GitHub Actions で Terraform Workflow を構築するための Action を開発しています。
本記事では tfaction の main branch 及び feature branch にトランスパイルした JS をコミットするのをやめて CI 及びリリースフローを改善した話を紹介します。
主に JavaScript Action を開発している方の参考になれば幸いです。
背景
tfaction では 1 つのリポジトリで複数の Composite Action 及び JavaScript Action を管理しています。
JavaScript Action は TypeScript を使って書き、 JavaScript にトランスパイルしています。
複数の JavaScript Action が使用する共通コードは lib というディレクトリで管理し、参照するようにしています。
そのため、 lib
に変更が入ると依存する全ての action のトランスパイルした結果に変更が入ることになります。
また action 同士には依存関係があり、ある action から他の action を実行していたりします。
そのため、リリース時にはこの tag を書き換える必要があります。
例えば次のコミットは v1.9.0 リリース時に CI で生成されたコミットです。
同様に Pull Request (以下 PR) の action を実際に使ってみて動作確認したい場合も tag を書き換える必要があり、それも GitHub Actions で自動化していて、 pr/<Pull Request 番号>
というブランチ (以下 PR ブランチ
) が生成されるようになっています。
例えば以下のコミットは #1910 の動作確認用に作られたコミットです。
なお、 fork からの Pull Request の場合 pull_request
event で secret の参照やコミットの生成が難しいので、 PR の内容を確認したうえで workflow_dispatch
で手動で PR ブランチ
を生成・更新しています。
課題
従来はローカルで TypeScript をトランスパイルしてコミットしていました。
つまり default branch にもトランスパイルされた JavaScript がありましたし、 PR の変更にも含まれていました。
公式の JavaScript Action を幾つか確認しましたが、これは割と一般的なやり方かと思います。
しかし、このやり方には大きな問題がありました。
- ローカルで最新のコードをトランスパイルするのを忘れる
- 最新のコードでトランスパイルしているのかがそもそもわからない
- コントリビューターに「トランスパイルして」ってお願いするのが面倒
- 環境によってトランスパイルの結果に差異が出る可能性がある
- Node.js や package のバージョンは固定しているが、それでも起こるかも
- コントリビューターが指定した Node.js のバージョン使ってるとも限らない
- Pull Request で大量のコードがトランスパイルによって変更され、レビューが困難になる
- 数千、数万行修正されることもある
- 例えば #1909 は 100 行も修正していないのにトランスパイルした結果 1,000 行以上になっている
- .gitattribute でトランスパイルされたコードを非表示にしても邪魔
- 後述の通り非表示にするのはセキュリティ的に危険
- 数千、数万行修正されることもある
- トランスパイルされた結果に悪意のあるコードが仕込まれる可能性がある
- 大量のコードが修正された場合、レビューで気づくのが困難
- トランスパイルされたコードがコンフリクトを起こす可能性がある
解決策
そこでローカルで TypeScript をトランスパイルしてコミットするのをやめることにしました。
上述の通り、元々 tfaction ではリリース時や PR の検証用ブランチの生成・更新時に自動で tag を書き換えるということをやっています。
そこでそのタイミングで CI でトランスパイルしてコミットするようにしました。
このときの push 先は main branch や PR の feature branch とは別なため、 main branch や feature branch, 及び PR の変更にはトランスパイルした結果が含まれなくなりました。
ローカルではトランスパイルした結果をコミットしないように .gitignore
に dist
を追加しています。
そして CI でコミットする際は git add
コマンドの --force
option をつけています。
こうすることで人間がトランスパイルしてコミットする必要がなくなり、 PR にもトランスパイルした結果が含まれなくなり、上記の問題が解決されました。
補足
CI で全ての JavaScript Action をトランスパイルしているためそこそこ時間がかかり CI が若干遅くなりました。
xargs
の -P
option で並列実行することで 1.5 倍(1分 => 40秒)くらいは高速化していますが、それでも若干体験を損なっている面はあります。
しかし、上記の課題が解決されることを考えればたいした問題ではありません。
なお、現状 tfaction の CI では JavaScript Action を実際に動かすインテグレーションテストのようなものは実施していない (一部の action でやっていたがやめた) のでトランスパイルしたものを消しても問題ありませんでした。
やるとしたら CI で生成した PR ブランチ
でやることになるでしょう。
JavaScript Action の Unit test はある程度ありますが、正直 tfaction のテストは手動テストに依存する部分が大きく自動テストについてはまだまだ課題があります。
今後自動テストを拡充できればと思っています。
Discussion
補足:
記事を読んでもらえば分かると思いますが、
tfaction でトランスパイルした JS をコミットするのを止めた
というタイトルは正確ではなく、正確にはmain branch や feature branch に
コミットするのを止めたですね。リリース時には JS をコミットしないと JavaScript Action として実行できませんしね。
ただ正確に書こうとするとタイトルが冗長になるので、若干正確性を犠牲にしたタイトルになっています
本記事のリリースフローを実現するための action を作りました。
tfaction や lock-action で既に導入済みなので、そちらをみると使い方もわかるかと思います。