Steven
Steven7 menit baca

Menaruh Release ke dalam CI, Dua Kali

GeekBye v1.8.4 memindahkan build release ke CI untuk macOS dan Windows. Yang tidak dikatakan changelog adalah bahwa kami pernah mencoba ini sekali sebelumnya, menghapusnya sebulan kemudian karena biaya runner, dan baru berhasil membuatnya bertahan pada percobaan kedua — karena saat itu script release sudah bekerja secara manual.

Engineering
CI/CD
Desktop
GeekBye Releases
Menaruh Release ke dalam CI, Dua Kali

Changelog v1.8.4 punya satu baris yang terdengar seperti kerja rumah tangga belaka: "Build release kini datang dari CI untuk macOS maupun Windows." Ia terbaca seperti jenis hal yang terjadi sekali, rapi, dan tak pernah dipikirkan lagi. Ternyata tidak. Tim sudah pernah menaruh release ke CI sekali — dan mencabutnya. Release ini adalah percobaan kedua, dan alasan ia bertahan adalah pelajaran engineering yang sebenarnya, jadi mari kita ceritakan berurutan.

Percobaan pertama, dan mengapa ia mati

Dulu pada Oktober 2025 ada sebuah release.yml. Ia dipicu pada push tag — dorong sebuah tag v*.*.* dan sebuah runner macOS akan menyala, mem-build, dan mempublikasikan. Ia melakukan setup penandatanganannya sendiri secara manual: sebuah langkah yang men-decode sertifikat penandatanganan, membuat dan membuka kunci sebuah keychain sementara, mengimpor sertifikat itu agar codesign bisa menemukannya, dan sebuah langkah pembersihan yang bersesuaian di akhir. Ia bekerja. Ia juga khusus-macOS — release Windows sama sekali tidak ada di CI.

Pada 4 November, ia dihapus. Pesan commit-nya luar biasa terus terang soal alasannya: "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." Itulah seluruh cerita percobaan pertama dalam satu kalimat. GitHub menagih waktu runner macOS pada sepuluh kali tarif Linux, dan sebuah release yang dipicu-tag berarti setiap tag — termasuk yang sekali-pakai, yang di-tag-ulang, yang "aduh, lupa menaikkan versi" — diam-diam menghabiskan seratus menit tertagih. Otomasi yang tidak kamu kendalikan adalah otomasi yang menghabiskan uang selagi kamu tidur. Jadi release kembali ke Mac seorang developer selama tiga setengah bulan berikutnya.

Di mana masalah-masalah sulit sebenarnya terpecahkan

Inilah bagian yang membuat percobaan kedua berhasil, dan ia terjadi sepenuhnya di luar CI. Selama tiga setengah bulan release lokal itu, scripts/release.js — script Node polos yang dijalankan developer secara manual — menyerap setiap masalah packaging yang dimiliki aplikasi, satu perbaikan pada satu waktu:

  • Restore white-label. Ini adalah codebase white-label yang mem-build baik GeekBye maupun Pavleur dari satu sumber, yang berarti sebuah release menukar field package.json dan aset ikon untuk produk sasaran. Dua perbaikan mengajari script untuk mengembalikan semuanya setelahnya, jadi mem-build Pavleur tidak meninggalkan git tree-mu kotor dengan identitas Pavleur.
  • Windows dual-arch. Sebuah perbaikan untuk mem-build kedua installer Windows x64 dan arm64 alih-alih satu.
  • Akal-akalan Windows tak-bertanda-tangan. electron-builder yang lebih baru mengabaikan flag config yang dimaksudkan untuk menonaktifkan penandatanganan Windows, jadi script belajar memaksa build tak-bertanda-tangan dengan menyetel CSC_IDENTITY_AUTO_DISCOVERY=false di environment sebagai gantinya — sebuah kerutan yang hanya kamu temukan dengan menabraknya.
  • Deteksi-otomatis notarisasi. Script belajar menengok environment-nya dan memutuskan: kredensial penandatanganan ada? Tanda tangani. Kredensial notarisasi Apple juga ada? Notarisasi. Tidak keduanya? Build tak-bertanda-tangan. Tak ada flag yang perlu diingat; kehadiran secret itulah konfigurasinya.

Tak ada satu pun dari itu yang glamor. Semuanya adalah jenis hal yang, jika mengejutkanmu di dalam sebuah runner CI, menghabiskan satu jam push-tunggu-gagal-baca-log per percobaan — dengan tagihan 10x. Terpecahkan di sebuah Mac yang ada di depanmu, masing-masing menghabiskan satu menit.

Percobaan kedua: enam puluh enam baris

Ketika CI kembali di v1.8.4, workflow-nya 66 baris, dan kualitas yang mendefinisikannya adalah betapa sedikitnya ia bekerja. Commit-nya menggambarkannya dengan gamblang: "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." Setiap keputusan desain di dalamnya adalah pelajaran yang ditinggalkan oleh sakitnya percobaan pertama:

  • workflow_dispatch, bukan dipicu-tag. Kamu memulai sebuah release dengan mengklik "Run workflow." Seorang manusia menjaga setiap menit macOS berbayar. Masalah biaya yang membunuh versi pertama terpecahkan cukup dengan tidak mengotomasi pemicunya — satu-satunya tempat otomasi justru merugikan.
  • Sebuah input product. Dispatch mengambil sebuah dropdown — geekbye atau pavleur — jadi workflow yang sama merilis salah satu merek. Jahitan white-label membentang sampai ke tombol release.
  • Dua job paralel. build-mac di macos-latest, build-win di windows-latest, berjalan pada saat yang sama. Windows di CI benar-benar baru di sini; workflow Oktober tidak pernah mem-build-nya.
  • Tak ada logika penandatanganan di YAML. Inilah seluruh intinya. Tak ada juggling keychain, tak ada langkah impor sertifikat, tak ada pembersihan. Job Mac menjalankan node scripts/release.js <product> --publish dan job Windows menjalankan yang sama dengan --no-sign. Segala yang dilakukan workflow pertama secara manual di YAML kini tinggal di script yang sudah bekerja. Workflow adalah orkestrator, bukan implementasi.

Kedua platform menginginkan hal yang berlawanan, dan job-job itu mencerminkannya dengan jujur. macOS mem-build di bawah hardened runtime, ditandatangani dan dinotarisasi lewat jalur bawaan electron-builder (@electron/notarize), dengan kredensial dipasok dari repository secret dan dideteksi-otomatis oleh script. Windows mem-build installer NSIS tak-bertanda-tangan untuk x64 dan arm64. Satu workflow, dua job, dua pengertian "selesai" yang sepenuhnya berbeda.

Buktinya ada pada apa yang tidak terjadi

Beginilah cara kamu tahu urutannya benar: setelah dua penyesuaian di hari yang sama yang memberi nama pada run dan memperbaiki nama sebuah token secret, release.yml tidak disentuh lagi selama empat bulan. Tak ada hotfix kegagalan-penandatanganan. Tak ada kepanikan penolakan-notarisasi. Tak ada panik "native binary hilang di runner." Untuk sebuah pipeline penandatanganan-kode-dan-notarisasi — genre CI yang paling terkenal buruk karena kekisruhannya — empat bulan keheningan nyaris tak terdengar.

Ia hening karena keributannya sudah terjadi di suatu tempat yang lebih murah. Pertarungan yang biasanya berlangsung di sebuah tab log CI, dengan tagihan 10x, satu force-push demi satu, sudah berlangsung di Mac para developer selama era lokal. CI tidak harus menjadi tempat packaging di-debug, karena packaging sudah di-debug. Itulah tesisnya dalam satu baris: pindahkan build dari Mac-mu hanya setelah Mac-mu berhenti mengejutkanmu.

Satu keterkaitan yang masih bisa menyusahkan

Ia tidak sepenuhnya tanpa titik lemah, dan itu layak disebut karena ia halus. Binary Swift yang diandalkan aplikasi macOS — OCR, tangkapan layar, para transcriber — di-commit ke git. Tapi release tidak mengirim salinan yang di-commit itu; build mengompilasinya ulang di runner. Dan sebuah gerbang check-swift-version.js menggagalkan-keras seluruh release jika compiler-nya bukan versi yang dipatok. Pada release ini patokan itu adalah Swift 6.2.x, dan tak ada apa pun di workflow yang memasangnya — job itu semata-mata percaya bahwa Swift default macos-latest cocok. Pada hari GitHub menaikkan image runner-nya melewati patokan, release berhenti, bukan karena ada yang salah dengan aplikasi, melainkan karena pipeline diam-diam terkait pada sebuah image mesin yang tidak ia kendalikan. Ia adalah satu detail implementasi yang tidak didorong turun oleh workflow tipis ini ke dalam sesuatu yang ia miliki sendiri.

Separuh lain dari release: payload yang lebih kecil

Release ini membawa satu fitur tak terkait yang layak disebut, karena ia contoh bersih dari perbaikan latensi lewat pengurangan. Aksi Assist Me mengirimkan lebih banyak konteks ke backend daripada yang dibutuhkannya pada setiap request. Satu commit memangkas dua hal: riwayat transkrip yang ikut dikirim turun dari 30 entri terakhir menjadi 15 entri terakhir, dan berkas konteks profil pengguna — yang disuntikkan ke system prompt pada setiap panggilan — dibatasi-keras pada 4,000 karakter dengan sebuah penanda pemotongan. Lebih sedikit untuk diserialkan, lebih sedikit untuk diunggah, lebih sedikit bagi model untuk dibaca sebelum ia mulai menjawab, yang merupakan metrik yang dioptimalkan commit itu: time-to-first-token. Demi kejujuran: commit itu mengklaim percepatan tetapi tidak mengukurnya — tak ada angka sebelum/sesudah di riwayat, jadi perlakukan itu sebagai pemangkasan yang beralasan baik alih-alih kemenangan yang diukur.

Tiga hal yang diajarkan release ini

  1. CI adalah pembungkus tipis di atas script yang sudah bekerja. Workflow 66 baris tidak punya logika penandatanganan sendiri; ia memanggil sebuah script yang sudah di-debug oleh tiga bulan release lokal. Taruh mekanismenya di sesuatu yang bisa kamu jalankan secara manual, dan biarkan CI hanya memutuskan kapan menjalankannya.
  2. Otomasikan pekerjaannya, tak harus pemicunya. Percobaan pertama mati karena release yang dipicu-tag otomatis menghabiskan menit runner bertagihan-10x. workflow_dispatch manual mempertahankan otomasi dan membuang bagian yang menghabiskan uang — kadang manusia dalam lingkaran itulah fiturnya.
  3. Debug di tempat iterasi murah. Setiap pertarungan packaging yang diselesaikan di Mac seorang developer adalah pertarungan yang tak pernah terjadi di tab log CI dengan harga sepuluh kali lipat. Pindah dari Mac paling akhir, bukan paling awal.

Untuk bab sebelumnya dalam kisah v1, merilis tiga puluh bahasa tanpa jaring pengaman (v1.8.3); dan untuk keseluruhan alurnya, anatomi merilis software sampai sempurna.