🐧

tfaction でトランスパイルした JS をコミットするのを止めた

2024/10/18に公開2

tfaction という、 GitHub Actions で Terraform Workflow を構築するための Action を開発しています。

https://github.com/suzuki-shunsuke/tfaction

本記事では tfaction の main branch 及び feature branch にトランスパイルした JS をコミットするのをやめて CI 及びリリースフローを改善した話を紹介します。
主に JavaScript Action を開発している方の参考になれば幸いです。

背景

tfaction では 1 つのリポジトリで複数の Composite Action 及び JavaScript Action を管理しています。
JavaScript Action は TypeScript を使って書き、 JavaScript にトランスパイルしています。
複数の JavaScript Action が使用する共通コードは lib というディレクトリで管理し、参照するようにしています。

https://github.com/suzuki-shunsuke/tfaction/blob/690f5db74be036a60945c4b4627db440bb6c4838/get-target-config/package.json#L10

そのため、 lib に変更が入ると依存する全ての action のトランスパイルした結果に変更が入ることになります。

また action 同士には依存関係があり、ある action から他の action を実行していたりします。

https://github.com/suzuki-shunsuke/tfaction/blob/690f5db74be036a60945c4b4627db440bb6c4838/list-targets/action.yaml#L44

そのため、リリース時にはこの tag を書き換える必要があります。
例えば次のコミットは v1.9.0 リリース時に CI で生成されたコミットです。

https://github.com/suzuki-shunsuke/tfaction/commit/e27bdb36be639c0d30af1e63590f71453808bdee

同様に Pull Request (以下 PR) の action を実際に使ってみて動作確認したい場合も tag を書き換える必要があり、それも GitHub Actions で自動化していて、 pr/<Pull Request 番号> というブランチ (以下 PR ブランチ) が生成されるようになっています。
例えば以下のコミットは #1910 の動作確認用に作られたコミットです。

https://github.com/suzuki-shunsuke/tfaction/commit/67ed80be4561ef0b358058c8ff3a3fb6719f5133

なお、 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 の変更にはトランスパイルした結果が含まれなくなりました。

ローカルではトランスパイルした結果をコミットしないように .gitignoredist を追加しています。

https://github.com/suzuki-shunsuke/tfaction/blob/690f5db74be036a60945c4b4627db440bb6c4838/.gitignore#L5

そして CI でコミットする際は git add コマンドの --force option をつけています。

https://github.com/suzuki-shunsuke/tfaction/blob/690f5db74be036a60945c4b4627db440bb6c4838/.github/workflows/release.yaml#L55

こうすることで人間がトランスパイルしてコミットする必要がなくなり、 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

Shunsuke SuzukiShunsuke Suzuki

補足:

記事を読んでもらえば分かると思いますが、 tfaction でトランスパイルした JS をコミットするのを止めた というタイトルは正確ではなく、正確には main branch や feature branch に コミットするのを止めたですね。
リリース時には JS をコミットしないと JavaScript Action として実行できませんしね。
ただ正確に書こうとするとタイトルが冗長になるので、若干正確性を犠牲にしたタイトルになっています