
リリースをCIに載せる、二度
GeekBye v1.8.4はリリースビルドをmacOSとWindowsの両方でCIへ移しました。変更履歴が語らないのは、これを一度前にも試し、ランナーコストのせいで一か月後に削除し、二度目でようやく定着させた — という事実です。なぜなら、その頃にはリリーススクリプトがすでに手作業で機能していたからです。
v1.8.4の変更履歴には、純粋な雑務のように聞こえる一行があります: 「リリースビルドがmacOSとWindowsの両方でCIから来るようになった。」それは一度きり、きれいに起き、二度と考えられることのない類のもののように読めます。そうではありませんでした。チームは以前にも一度リリースをCIに載せていた — そしてそれを引き剥がしていた。このリリースは二度目の試みであり、それが定着した理由こそが本当のエンジニアリングの教訓なので、順を追って語りましょう。
最初の試み、そしてなぜそれが死んだのか
2025年10月に遡ると、そこにはrelease.ymlがありました。タグのプッシュで起動されました — v*.*.*のタグを押し込めば、macOSランナーが立ち上がり、ビルドし、公開する。それは署名のセットアップを自前で手作業でやっていました: 署名証明書をデコードし、一時的なキーチェーンを作成してアンロックし、codesignが見つけられるように証明書を取り込むステップと、末尾に対応するクリーンアップステップ。動いていました。それはまたmacOS専用でもありました — WindowsのリリースはCIには一切ありませんでした。
11月4日、それは削除されました。コミットメッセージは、その理由について異例なほど率直です: "remove GitHub Actions release workflow to conserve minutes / macOS runners cost 10x multiplier (100 billed minutes per 10-minute release). Releases will be done locally." それが最初の試みの物語のすべてを一文にしたものです。GitHubはmacOSのランナー時間をLinuxの十倍のレートで請求し、タグ起動のリリースは、あらゆるタグ — 使い捨てのもの、付け直したもの、「しまった、バージョンを上げ忘れた」というもの含めて — が静かに百分の請求を消費することを意味します。あなたが制御しない自動化は、あなたが眠っている間にお金を使う自動化です。だからリリースは次の三か月半のあいだ開発者のMacへ戻りました。
難しい問題が実際に解決された場所
ここが、二度目の試みを機能させる部分であり、それは完全にCIの_外_で起きました。そのローカルリリースの三か月半のあいだに、scripts/release.js — 開発者が手で実行するただのNodeスクリプト — が、アプリの抱えるパッケージングの問題を、一度に一つの修正ずつ、すべて吸収しました:
- ホワイトラベルの復元。 これは一つのソースからGeekByeとPavleurの両方をビルドするホワイトラベルのコードベースであり、つまりリリースはターゲット製品向けに
package.jsonのフィールドとアイコンアセットを差し替えます。二つの修正が、あとですべてを元に戻すことをスクリプトに教えたので、PavleurをビルドしてもgitツリーがPavleurの識別子で汚れたままにならなくなりました。 - デュアルアーキのWindows。 一つではなくx64とarm64の_両方_のWindowsインストーラーをビルドする修正。
- 未署名Windowsの回避策。 新しいelectron-builderがWindowsの署名を無効にするための設定フラグを無視するようになったので、スクリプトは代わりに環境で
CSC_IDENTITY_AUTO_DISCOVERY=falseを設定して未署名ビルドを強制することを学びました — ぶつかって初めて見つかる類のしわです。 - 公証の自動検出。 スクリプトは自分の環境を見て判断することを学びました: 署名の資格情報がある? 署名する。Appleの公証の資格情報もある? 公証する。どちらもない? 未署名でビルドする。覚えておくフラグはなし; シークレットの存在そのものが設定なのです。
そのどれも華やかではありません。そのすべてが、もしCIランナーの中であなたを不意打ちすれば、一回の試行につきプッシュ・待つ・失敗・ログを読むで一時間を、しかも10倍の課金で、あなたから奪う類のものです。あなたが目の前に座っているMacで解決すれば、一つにつき一分で済みます。
二度目の試み: 六十六行
CIがv1.8.4で戻ってきたとき、ワークフローは66行で、その決定的な性質は、それがいかに何もしないかにあります。コミットはそれを平明に描写しています: "Manual workflow_dispatch trigger that builds both platforms in parallel, reusing existing release.js script. macOS builds are signed and notarized, Windows builds are unsigned." その中のあらゆる設計判断が、最初の試みが残した傷跡です:
workflow_dispatchであって、タグ起動ではない。 リリースは「Run workflow」をクリックして始めます。人間があらゆる有料のmacOSの一分にゲートをかけます。最初のバージョンを殺したコスト問題は、トリガーを自動化しないという単純なやり方で解決されます — 自動化が積極的に有害だった唯一の場所です。product入力。 ディスパッチはドロップダウン —geekbyeかpavleur— を取るので、同じワークフローがどちらのブランドも出荷します。ホワイトラベルの継ぎ目は、リリースボタンのところまでずっと伸びています。- 二つの並列ジョブ。
macos-latest上のbuild-mac、windows-latest上のbuild-winが、同時に走ります。ここではCIでのWindowsが本当に新しい; 10月のワークフローは一度もそれをビルドしませんでした。 - YAMLの中に署名ロジックがない。 これこそが要点のすべてです。キーチェーンの曲芸も、証明書の取り込みステップも、クリーンアップもありません。Macジョブは
node scripts/release.js <product> --publishを実行し、Windowsジョブは--no-sign付きで同じものを実行します。最初のワークフローがYAMLの中で手作業でやっていたことのすべてが、今やすでに機能するスクリプトの中に住んでいます。ワークフローはオーケストレーターであって、実装ではありません。
二つのプラットフォームは正反対のものを求め、ジョブはそれを正直に映します。macOSはハードンドランタイムのもとでビルドし、electron-builderの組み込みの経路(@electron/notarize)を通じて署名され公証され、資格情報はリポジトリのシークレットから供給されスクリプトによって自動検出されます。Windowsはx64とarm64向けに未署名のNSISインストーラーをビルドします。一つのワークフロー、二つのジョブ、「完了」の二つのまったく異なる概念です。
証拠は、起きなかったことの中にある
順序が正しかったとどうしてわかるか、をこう示せます: 実行に名前を付け、あるトークンシークレットの名前を直した同日の二つの微調整のあと、release.ymlは四か月触られませんでした。署名失敗のホットフィックスなし。公証拒否のあわてふためきなし。「ネイティブバイナリがランナー上で行方不明」のパニックなし。コード署名と公証のパイプライン — 荒れることで最も悪名高い種類のCI — にとって、四か月の沈黙は、ほとんど前代未聞です。
静かだったのは、その騒音がすでにもっと安いどこかで起きていたからです。普段ならCIのログタブで、10倍の課金で、一度に一回のフォースプッシュずつ繰り広げられる戦いが、ローカルの時代に開発者のMacの上で繰り広げられていたのです。CIはパッケージングがデバッグされる場所である必要がありませんでした。なぜならパッケージングはすでにデバッグされていたからです。それが一行にしたテーゼです: Macがあなたを不意打ちしてこなくなってから、初めてビルドをMacから移せ。
まだ噛みつきうる唯一の結合
まったく弱点がないわけではなく、それは微妙なので名指しする価値があります。macOSアプリが依存するSwiftバイナリ — OCR、スクリーンキャプチャ、トランスクライバー群 — はgitにコミットされています。しかしリリースはコミットされたコピーを出荷しません; ビルドはそれらをランナー上で再コンパイルします。そしてcheck-swift-version.jsのゲートは、コンパイラがピン留めされたバージョンでなければリリース全体を強制的に失敗させます。このリリースの時点でそのピンはSwift 6.2.xで、ワークフローの中にそれをインストールするものは何もありません — ジョブはただmacos-latestのデフォルトのSwiftが一致することを信じています。GitHubがランナーイメージをそのピンを越えて上げた日、リリースは止まります。アプリに何か問題があるからではなく、パイプラインが、自分の制御しないマシンイメージへ静かに結合しているからです。それは、この薄いワークフローが、自分が所有する何かへ押し下げなかった唯一の実装の詳細です。
リリースのもう半分: より小さなペイロード
このリリースは、言及に値する無関係な機能を一つ運びました。なぜならそれは、引き算によるレイテンシ修正のきれいな一例だからです。Assist Meのアクションは、リクエストのたびにバックエンドへ必要以上のコンテキストを送っていました。あるコミットが二つのものを削りました: 一緒に送られるトランスクリプト履歴が直近30件から直近15件へ落ち、ユーザーのプロファイルコンテキストファイル — _あらゆる_呼び出しでシステムプロンプトに注入される — が、切り詰めマーカー付きで4,000文字に強制的に上限設定されました。シリアライズするものが減り、アップロードするものが減り、モデルが答え始める前に読むものが減る、それがこのコミットが最適化する指標です: time-to-first-token。正直のために言えば: このコミットは高速化を主張しますが、それを測定していません — 履歴の中にビフォー/アフターの数字はないので、ベンチマークで裏付けられた勝利というより、よく考えられた削減として扱ってください。
このリリースが教えてくれた三つのこと
- CIは、すでに機能するスクリプトの上に載る薄いラッパーだ。 66行のワークフローには独自の署名ロジックがない; それは三か月間のローカルリリースがすでにデバッグし終えていたスクリプトを呼びます。仕組みを手で実行できるものの中に置き、CIには_いつ_それを実行するかだけを決めさせなさい。
- 仕事を自動化せよ、必ずしもトリガーではなく。 最初の試みは、タグ起動のリリースが10倍課金のランナーの分を自動で消費したせいで死にました。手動の
workflow_dispatchは自動化を残し、お金を食っていた部分を取り除きます — 時にはループの中の人間こそが機能なのです。 - 反復が安い場所でデバッグせよ。 開発者のMacで解決されたあらゆるパッケージングの戦いは、CIのログタブで十倍の値段では決して起きない戦いです。Macから移すのは最後に、最初にではなく。
v1の物語の前の章については、セーフティネットなしで30言語を出荷する(v1.8.3)を; そして弧の全体については、ソフトウェアを完璧に出荷することの解剖学をご覧ください。