3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

今回は、自作OSSのTauri用プラグイン tauri-plugin-configurate のCI/CDを構築し、バージョンタグをpushすると自動でcrates.ioとnpm、GitHub Releasesへ公開されるパイプラインを組んだ際の備忘録です。

なぜCI/CDを組んだのか

その前に、このプラグインについて軽く説明させてください。
このプラグインは、Tauriというデスクトップアプリ向けフレームワークのプラグインで、アプリが型安全(Type-Safe)に設定ファイルを読み書きできるようにするものです。
これまでは、package.jsonとCargo.tomlのバージョンを変更したあとにcargo publishnpm publishを実行し、GitHub上でタグ付けとChangelogの更新を手動で行うという、なんとも非効率な運用でした。
バージョンリリースのたびに毎回これを手作業でやっていては埒が明きません。そこで、GitHub Actions経由でこれらの処理を自動化しようと考えました。

CI/CDの動作フロー

主に、次のような動作フローで組みました。(画像はGPT-Image-2)
image.png
バージョンアップする際は、package.jsonとCargo.tomlのバージョンを更新し、CHANGELOG.mdに対応するバージョンの変更点を記載しておきます。
次に、Pull Requestのマージ時あるいはmainへのコミット時に、git tag v1.2.3 && git push origin v1.2.3 のようにタグを作成してプッシュします。
あとは、GitHub Actionsが走り、バージョンの不整合がないか、Rust側・TypeScript側のテストが通過するか確認したうえで、crates.ioとnpmへのリリース、GitHub Releasesへの登録を自動で行います。
人間が手作業でこの一連の作業を行うよりも、はるかに楽にpublishできるようになりました。

ワークフローの工夫点

このワークフローでいくつか工夫した点を説明します。実際のワークフローは👇️にあります。

バージョンの整合性チェック

publishやテストを実行する前に、タグ・Cargo.toml・package.jsonの3箇所のバージョンが一致しているかを確認しています。

- name: Verify manifest versions match tag
  run: |
    TAG_VERSION="${{ steps.version.outputs.version }}"
    CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/')
    PKG_VERSION=$(bun -e "console.log(require('./package.json').version)")
    if [ "$CARGO_VERSION" != "$TAG_VERSION" ] || [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
      echo "Version mismatch: tag=v${TAG_VERSION}, Cargo.toml=${CARGO_VERSION}, package.json=${PKG_VERSION}"
      exit 1
    fi

タグだけ打ち間違えてバージョンファイルの更新を忘れる、といった事故をここで検知できます。

Releaseバージョンの重複チェック

cargo publishnpm publish は、対象バージョンが既に公開済みかどうかを cargo search / npm view で確認してからスキップするようにしています。

- name: Publish to crates.io
  run: |
    VERSION="${{ steps.version.outputs.version }}"
    if cargo search tauri-plugin-configurate --limit 1 | grep -q "= \"${VERSION}\""; then
      echo "tauri-plugin-configurate ${VERSION} is already on crates.io; skipping publish"
    else
      cargo publish --locked
    fi

これにより、ワークフローが途中で失敗して再実行した場合でも、publish時に公開済みバージョンによるエラーになることがなくなります。

CHANGELOGからリリースノートを自動生成

GitHub Releaseの本文は、CHANGELOG.md から該当バージョンのセクションを awk で抜き出して、そのまま使っています。

- name: Extract changelog for release
  run: |
    VERSION="${{ steps.version.outputs.version }}"
    awk -v version="$VERSION" '
      $0 ~ "^## \\[" version "\\]" { flag=1; next }
      /^## \[/ && flag { exit }
      flag { print }
    ' CHANGELOG.md > RELEASE_NOTES.md
    if [ ! -s RELEASE_NOTES.md ]; then
      echo "No changelog section found for version ${VERSION}"
      exit 1
    fi

Changelogさえ書いておけば、リリースノートのコピペ作業が丸ごとなくなります。該当バージョンのセクションが見つからない場合はそこでCIを失敗させているので、Changelogの記載漏れにも気づけます。

トークンの管理

GitHub Actions経由でcrates.ioとnpmにpublishするには、あらかじめトークンを発行しておく必要があります。

crates.io

crates.ioの設定からトークンを発行します。

トークンの失効期限はデフォルトの90日で行いました。もちろん期限を延ばすこともできますが、無期限はおすすめしません。
次にトークンのScopeですが、publishするだけなので publish-update だけにチェックを入れます。なお、今回は既存crateへの追加publishだったため publish-update のみで足りましたが、新規にcrateを公開する場合は publish-new の権限も別途必要になります。
最後に、このトークンの適用範囲(=対象となるcrate)を設定します。パターンに一致するcrateでしかこのトークンが使えないようになります。

トークン発行後、GitHubのリポジトリページからActions用のSecrets and variablesを探し、Repository secrets内で CARGO_REGISTRY_TOKEN として設定しておきます。

複数のOSSライブラリを扱う際、scopeや権限範囲を正しく制限しないと、Shai-Huludのような攻撃に対して脆弱になります。最悪の場合、広範囲に被害が出る可能性があります。

image.png

npm

npmでは主に、アクセストークンを発行する方法とTrusted Publishingを使う方法の2つがあります。前者は非推奨のため、Trusted Publishingを使います。
この方法を使うには、あらかじめnpmにパッケージを公開しておく必要があります。
設定したいパッケージのページに移動し、Settingsを開きます。その後、Trusted Publisherの設定を行います。
image.png
GitHubのリポジトリを設定したうえで、リリースを行うワークフローのファイルを指定します。最後に、許可するアクションとして「npmにpublishする」にチェックを入れて完了です。

Trusted Publishingの仕組みとしては、ここで設定した内容をもとに、GitHub ActionsのワークフローがOIDC(OpenID Connect)トークンをnpmに提示し、npm側でそれを検証したうえで短命なpublish用トークンをその場で発行する、という流れで動作します。長期間有効なnpmトークンをあらかじめ発行してsecretsに保存しておく必要がなくなるため、トークン漏れによる他パッケージの侵害の影響を受けにくくなります。

GitHub Actionsのワークフロー側では、次の権限を設定する必要があります。id-token: write がnpmのOIDC認証に使われる権限で、contents: write はこのあと作成するGitHub Releaseのために必要です。

permissions:
  contents: write
  id-token: write

CI/CDと格闘

はじめは、動作フローに則ったワークフローファイルを記述しましたが、なかなかうまくいきませんでした。調べたところ、次の問題がありました。

  • oven-sh/setup-bun@v2 を導入し忘れていた
  • dtolnay/rust-toolchain@master で導入されるRustのバージョンが古かった
  • GitHub Actions用のUbuntuランナーに、Tauriのビルド時に必要な依存パッケージがインストールされていなかった

一番上に関しては完全に見落としでした。そりゃプロジェクトでBunを使っているのだから、Actionsでも必要に決まっています。しかし、残り2つに関しては完全に盲点でした。本プラグインで使用している一部のライブラリがRust 1.85以降を要求しており、それが原因でエラーになっていることがわかりました。そのため、@master から @stable に切り替え、MSRV(Minimum Supported Rust Version、サポートする最小のRustバージョン)も1.85に引き上げることでget 事なきしました。
また、Tauriプラグインのビルドに必要なwebkitなど、一部のLinuxパッケージがランナーには入っていないため、追加でapt-getで入れる必要がありました。

導入してみて

今までCI/CDといえば、WebサイトをVercelやCloudflareにアップロードする際に、プラットフォーム側が用意してくれるものをそのまま使っていたため、自分で構築するのは初めてでした。
CI/CDは沼という話をよく聞きますが、実際そうでした。ただ、今回のケースでは導入によりタグ付きリリースするだけで全部完結するようになったので、結果的に良かったです。
これを機に、皆さんも身近なプロジェクトのCI/CD化をしてみましょう!

終わりに

宣伝と言ってはなんですが、Tauriを触っている方、ぜひこのプラグインを使ってみませんか!?
まだissueもPRもないので、ぜひ感想を教えてください! Starもお待ちしています。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?