Steven
Steven6 dk okuma

Release'i CI'ye Koymak, İki Kez

GeekBye v1.8.4 release build'lerini macOS ve Windows için CI'ye taşıdı. Changelog'un söylemediği şey şu: bunu bir kez daha önce denedik, runner maliyetleri yüzünden bir ay sonra sildik, ve ancak ikinci seferde kalıcı kıldık — çünkü o zamana dek release script'i zaten elle çalışıyordu.

Mühendislik
CI/CD
Desktop
GeekBye Releases
Release'i CI'ye Koymak, İki Kez

v1.8.4 changelog'unda saf ev işi gibi görünen bir satır var: "Release build'leri artık hem macOS hem Windows için CI'den geliyor." Bir kez, temiz bir şekilde olan ve bir daha asla düşünülmeyen türden bir şey gibi okunuyor. Öyle değildi. Ekip release'leri CI'ye zaten bir kez koymuştu — ve söküp atmıştı. Bu release ikinci deneme, ve kalıcı olmasının nedeni asıl mühendislik dersi, o yüzden sırayla anlatalım.

İlk deneme, ve neden öldüğü

Ekim 2025'te bir release.yml vardı. Tag push'unda tetikleniyordu — bir v*.*.* tag'i it, bir macOS runner'ı ayağa kalkar, derler ve yayınlardı. Kendi imzalama kurulumunu elle yapıyordu: imzalama sertifikasını çözen, geçici bir keychain oluşturup kilidini açan, codesign'ın bulabilmesi için sertifikayı içe aktaran bir adım, ve sonda ona karşılık gelen bir temizlik adımı. Çalışıyordu. Ayrıca yalnızca macOS'tu — Windows release'leri hiç CI'de değildi.

4 Kasım'da silindi. Commit mesajı nedeni konusunda alışılmadık derecede açık sözlü: "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." İlk denemenin bütün hikâyesi tek cümlede bu. GitHub, macOS runner süresini Linux'un on katı ücretle faturalar, ve tag ile tetiklenen bir release demek her bir tag'in — usulüne uygun atılıp atılanlar, yeniden-tag'ler, "eyvah, sürüm yükseltmeyi unuttum"lar dahil — sessizce yüz faturalı dakika harcaması demek. Kontrol etmediğin otomasyon, sen uyurken para harcayan otomasyondur. Böylece release'ler sonraki üç buçuk ay boyunca bir geliştiricinin Mac'ine geri döndü.

Zor sorunların asıl nerede çözüldüğü

İkinci denemeyi çalıştıran kısım işte burada, ve tamamen CI'nin dışında oldu. O üç buçuk aylık yerel release boyunca, scripts/release.js — bir geliştiricinin elle çalıştırdığı sade Node script'i — uygulamanın sahip olduğu her paketleme sorununu, tek tek düzeltme olarak emdi:

  • White-label restore. Bu, GeekBye ve Pavleur'ün ikisini de tek kaynaktan derleyen white-label bir kod tabanı, yani bir release hedef ürün için package.json alanlarını ve icon asset'lerini takas eder. İki düzeltme script'e sonradan her şeyi geri koymayı öğretti, böylece Pavleur derlemek git ağacını Pavleur'ün kimliğiyle kirli bırakmadı.
  • Dual-arch Windows. Tek yerine hem x64 hem arm64 Windows installer'ı derlemek için bir düzeltme.
  • İmzasız-Windows geçici çözümü. Daha yeni electron-builder, Windows imzalamayı devre dışı bırakması gereken config flag'ini görmezden geldi, bu yüzden script bunun yerine ortama CSC_IDENTITY_AUTO_DISCOVERY=false koyarak imzasız bir build'i zorlamayı öğrendi — ancak çarpınca bulduğun bir kırışıklık.
  • Noterleme otomatik-algılaması. Script ortamına bakıp karar vermeyi öğrendi: imzalama kimlik bilgileri var mı? İmzala. Apple noterleme kimlik bilgileri de var mı? Noterle. Hiçbiri yok mu? İmzasız derle. Hatırlanacak flag yok; secret'ların varlığı kendisi yapılandırmadır.

Bunların hiçbiri gösterişli değil. Hepsi, eğer bir CI runner'ının içinde seni şaşırtırsa, deneme başına bir saatlik push-bekle-başarısız-ol-logları-oku'ya mal olan türden şeyler — 10x faturalamayla. Önünde oturduğun bir Mac'te çözülünce, her biri bir dakikaya mal olur.

İkinci deneme: altmış altı satır

CI v1.8.4'te geri geldiğinde, workflow 66 satırdı, ve onu tanımlayan nitelik ne kadar az iş yaptığı. Commit bunu açıkça anlatıyor: "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." İçindeki her tasarım kararı ilk denemenin acıyla öğrettiği bir ders:

  • workflow_dispatch, tag ile tetiklenmiyor. Bir release'i "Run workflow"a tıklayarak başlatırsın. Her ücretli macOS dakikasını bir insan kapıda tutar. İlk sürümü öldüren maliyet sorunu, tetikleyiciyi basitçe otomatikleştirmeyerek çözülüyor — otomasyonun etkin biçimde zararlı olduğu tek yer.
  • Bir product girdisi. Dispatch bir açılır menü alır — geekbye ya da pavleur — böylece aynı workflow iki markadan birini sevk eder. White-label dikişi ta release düğmesine kadar uzanıyor.
  • İki paralel job. macos-latest üzerinde build-mac, windows-latest üzerinde build-win, aynı anda çalışıyor. Burada CI'de Windows gerçekten yeni; Ekim workflow'u onu hiç derlemedi.
  • YAML'da imzalama mantığı yok. Bütün mesele bu. Keychain hokkabazlığı yok, sertifika içe aktarma adımı yok, temizlik yok. Mac job'ı node scripts/release.js <product> --publish çalıştırır ve Windows job'ı aynısını --no-sign ile çalıştırır. İlk workflow'un YAML'da elle yaptığı her şey artık zaten çalışan script'te yaşıyor. Workflow bir orkestratör, bir implementasyon değil.

İki platform zıt şeyler ister, ve job'lar bunu dürüstçe yansıtır. macOS, hardened runtime altında, electron-builder'ın yerleşik yolu (@electron/notarize) üzerinden, kimlik bilgileri repository secret'larından sağlanıp script tarafından otomatik algılanarak, imzalı ve noterli derler. Windows, x64 ve arm64 için imzasız NSIS installer'ları derler. Tek workflow, iki job, "bitti"nin tamamen farklı iki anlayışı.

Kanıt, olmayan şeyde

Sıralamanın doğru olduğunu şöyle anlarsın: çalıştırmalara ad veren ve bir token secret'ının adını düzelten aynı gün yapılan iki ince ayardan sonra, release.yml'e dört ay boyunca bir daha dokunulmadı. İmzalama-başarısızlığı hotfix'i yok. Noterleme-reddi telaşı yok. "Native binary runner'da eksik" paniği yok. Bir kod-imzalama-ve-noterleme pipeline'ı için — çırpınmasıyla en kötü ünlü CI türü — dört ay sessizlik neredeyse duyulmamış bir şey.

Sessizdi çünkü gürültü zaten daha ucuz bir yerde olmuştu. Genellikle bir CI logları sekmesinde, 10x faturalamayla, her seferinde bir force-push ile yaşanan kavgalar, yerel dönemde geliştiricilerin Mac'lerinde yaşanmıştı. Paketlemenin ayıklandığı yer CI olmak zorunda değildi, çünkü paketleme zaten ayıklanmıştı. Tek satırlık tez bu: build'i Mac'inden ancak Mac'in seni şaşırtmayı bıraktıktan sonra taşı.

Hâlâ canını yakabilecek tek bağlılık

Tamamen zayıf noktasız değil, ve incelikli olduğu için adını koymaya değer. macOS uygulamasının bağımlı olduğu Swift binary'leri — OCR, ekran yakalama, transkripsiyoncular — git'e commit'li. Ama release commit'li kopyaları sevk etmez; build onları runner'da yeniden derler. Ve bir check-swift-version.js kapısı, derleyici sabitlenmiş sürüm değilse tüm release'i sert biçimde başarısız kılar. Bu release'de o sabit sürüm Swift 6.2.x'ti, ve workflow'da hiçbir şey onu kurmuyor — job basitçe macos-latest'in varsayılan Swift'inin eşleştiğine güveniyor. GitHub runner imajını sabitlenmiş sürümün ötesine yükselttiği gün release durur, uygulamada bir sorun olduğu için değil, pipeline sessizce kontrol etmediği bir makine imajına bağlı olduğu için. Bu ince workflow'un sahip olduğu bir şeye aşağı itmediği tek implementasyon detayı bu.

Release'in diğer yarısı: daha küçük payload'lar

Release, anmaya değer alakasız bir özellik taşıdı, çünkü çıkarma yoluyla gecikme düzeltmesinin temiz bir örneği. Assist Me eylemi her istekte backend'e ihtiyacından fazla context gönderiyordu. Bir commit iki şeyi kırptı: birlikte gönderilen transcript geçmişi son 30 girdiden son 15 girdiye düştü, ve kullanıcının profil context dosyaları — her çağrıda system prompt'a enjekte edilen — bir kırpma işaretiyle 4,000 karaktere sert biçimde sınırlandı. Serileştirilecek daha az, yüklenecek daha az, modelin yanıtlamaya başlamadan önce okuyacağı daha az şey — ki commit'in optimize ettiği metrik de bu: time-to-first-token. Dürüstlük adına: commit hızlanmayı iddia ediyor ama ölçmüyor — geçmişte önce/sonra rakamı yok, o yüzden bunu ölçülmüş bir zafer değil, iyi gerekçelendirilmiş bir kırpma olarak değerlendir.

Bu release'in bize öğrettiği üç şey

  1. CI, zaten çalışan bir script üzerinde ince bir sarmalayıcıdır. 66 satırlık workflow'un kendine özgü imzalama mantığı yok; üç aylık yerel release'in zaten ayıkladığı bir script'i çağırır. Mekanizmayı elle çalıştırabileceğin bir şeye koy, ve CI'nin yalnızca onu ne zaman çalıştıracağına karar vermesine izin ver.
  2. İşi otomatikleştir, illa tetikleyiciyi değil. İlk deneme öldü çünkü tag ile tetiklenen release'ler 10x-faturalı runner dakikalarını otomatik harcadı. Manuel workflow_dispatch otomasyonu korur ve para harcayan kısmı kaldırır — bazen döngüdeki insan asıl özelliktir.
  3. Yinelemenin ucuz olduğu yerde ayıkla. Bir geliştiricinin Mac'inde çözülen her paketleme kavgası, on kat fiyata bir CI logları sekmesinde asla yaşanmayan bir kavgadır. Mac'ten en son çık, ilk değil.

v1 hikâyesinin bir önceki bölümü için, güvenlik ağı olmadan otuz dil sevk etmek (v1.8.3); tüm yay için de yazılımı kusursuzluğa kadar sevk etmenin anatomisi.