はじめに
今回は、自作OSSのTauri用プラグイン tauri-plugin-configurate のCI/CDを構築し、バージョンタグをpushすると自動でcrates.ioとnpm、GitHub Releasesへ公開されるパイプラインを組んだ際の備忘録です。
なぜCI/CDを組んだのか
その前に、このプラグインについて軽く説明させてください。
このプラグインは、Tauriというデスクトップアプリ向けフレームワークのプラグインで、アプリが型安全(Type-Safe)に設定ファイルを読み書きできるようにするものです。
これまでは、package.jsonとCargo.tomlのバージョンを変更したあとにcargo publishとnpm publishを実行し、GitHub上でタグ付けとChangelogの更新を手動で行うという、なんとも非効率な運用でした。
バージョンリリースのたびに毎回これを手作業でやっていては埒が明きません。そこで、GitHub Actions経由でこれらの処理を自動化しようと考えました。
CI/CDの動作フロー
主に、次のような動作フローで組みました。(画像はGPT-Image-2)

バージョンアップする際は、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 publish と npm 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のような攻撃に対して脆弱になります。最悪の場合、広範囲に被害が出る可能性があります。
npm
npmでは主に、アクセストークンを発行する方法とTrusted Publishingを使う方法の2つがあります。前者は非推奨のため、Trusted Publishingを使います。
この方法を使うには、あらかじめnpmにパッケージを公開しておく必要があります。
設定したいパッケージのページに移動し、Settingsを開きます。その後、Trusted Publisherの設定を行います。

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もお待ちしています。
