
Å legge releasen i CI, to ganger
GeekBye v1.8.4 flyttet release-buildene til CI for macOS og Windows. Det endringsloggen ikke sier, er at vi prøvde dette én gang før, slettet det en måned senere på grunn av runner-kostnader og først fikk det til å holde andre gang — for da fungerte release-skriptet allerede for hånd.
v1.8.4-endringsloggen har en linje som høres ut som ren rutine: "Release-buildene kommer nå fra CI for både macOS og Windows." Den leses som den typen ting som skjer én gang, rent, og som man aldri tenker på igjen. Det gjorde den ikke. Teamet hadde allerede lagt releaser i CI én gang — og revet det ut. Denne releasen er det andre forsøket, og grunnen til at det holdt er selve ingeniørlærdommen, så la oss fortelle den i rekkefølge.
Det første forsøket, og hvorfor det døde
Tilbake i oktober 2025 fantes en release.yml. Den ble utløst på tag push — dytt inn en v*.*.*-tag, og en macOS-runner ville starte opp, bygge og publisere. Den gjorde sitt eget signeringsoppsett for hånd: et steg som dekodet signeringssertifikatet, opprettet og låste opp en midlertidig keychain, importerte sertet slik at codesign kunne finne det, og et tilhørende oppryddingssteg til slutt. Det fungerte. Den var også macOS-kun — Windows-releaser var ikke i CI i det hele tatt.
Den 4. november ble den slettet. Commit-meldingen er uvanlig ærlig om hvorfor: "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." Det er hele historien om det første forsøket i én setning. GitHub fakturerer macOS-runnertid til ti ganger Linux-taksten, og en tag-utløst release betyr at hver eneste tag — inkludert engangstagene, om-tagene, "oi, glemte versjonsbumpet"-tagene — stille bruker hundre fakturerte minutter. Automatisering du ikke kontrollerer er automatisering som bruker penger uten at du vet det. Så releasene gikk tilbake til en utviklers Mac de neste tre og en halv månedene.
Der de vanskelige problemene faktisk ble løst
Her er delen som får det andre forsøket til å fungere, og det skjedde helt utenfor CI. I løpet av de tre og en halv månedene med lokale releaser sugde scripts/release.js — det vanlige Node-skriptet som en utvikler kjører for hånd — til seg hvert eneste pakkeproblem appen hadde, én fiks om gangen:
- White-label-gjenoppretting. Dette er en white-label-kodebase som bygger både GeekBye og Pavleur fra én kilde, som betyr at en release bytter ut
package.json-felter og ikon-ressurser for målproduktet. To fikser lærte skriptet å legge alt tilbake etterpå, slik at det å bygge Pavleur ikke etterlot git-treet ditt skittent med Pavleurs identitet. - Dobbeltarkitektur for Windows. En fiks for å bygge både x64- og arm64-installere for Windows i stedet for én.
- Omgåelsen for usignert Windows. Nyere electron-builder ignorerte config-flagget som skulle slå av Windows-signering, så skriptet lærte å tvinge frem et usignert build ved i stedet å sette
CSC_IDENTITY_AUTO_DISCOVERY=falsei miljøet — en krøll du bare finner ved å gå på den. - Autodeteksjon av notarisering. Skriptet lærte å se på miljøet sitt og bestemme: signeringsopplysninger til stede? Signer. Apples notariseringsopplysninger også til stede? Notariser. Ingen av delene? Bygg usignert. Ingen flagg å huske; tilstedeværelsen av hemmelighetene er konfigurasjonen.
Ingenting av det er glamorøst. Alt sammen er den typen ting som, hvis den overrasker deg inne i en CI-runner, koster deg en time med push-vent-feil-les-logger per forsøk — med 10x-fakturering. Løst på en Mac du sitter foran, koster hver enkelt av dem ett minutt.
Det andre forsøket: sekstiseks linjer
Da CI kom tilbake i v1.8.4, var workflowen 66 linjer, og dens definerende kvalitet er hvor lite den gjør. Commiten beskriver den rett frem: "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." Hver designbeslutning i den springer ut av det det første forsøket lærte den på den harde måten:
workflow_dispatch, ikke tag-utløst. Du starter en release ved å klikke "Run workflow." Et menneske porter hvert betalte macOS-minutt. Kostnadsproblemet som drepte den første versjonen løses ved rett og slett ikke å automatisere utløseren — det ene stedet der automatisering var aktivt skadelig.- Et
product-input. Dispatchen tar en nedtrekksmeny —geekbyeellerpavleur— slik at den samme workflowen shipper begge merkevarene. White-label-sømmen løper hele veien ut til release-knappen. - To parallelle jobber.
build-macpåmacos-latest,build-winpåwindows-latest, kjørende samtidig. Windows i CI er reelt nytt her; oktober-workflowen bygde det aldri. - Ingen signeringslogikk i YAML-en. Det er hele poenget. Ingen keychain-sjonglering, intet sert-importsteg, ingen opprydding. Mac-jobben kjører
node scripts/release.js <product> --publishog Windows-jobben kjører det samme med--no-sign. Alt den første workflowen gjorde for hånd i YAML bor nå i skriptet som allerede fungerer. Workflowen er en orkestrator, ikke en implementasjon.
De to plattformene vil ha motsatte ting, og jobbene gjenspeiler det ærlig. macOS bygger under en herdet runtime, signert og notarisert gjennom electron-builders innebygde vei (@electron/notarize), med opplysningene levert fra repoets hemmeligheter og autodetektert av skriptet. Windows bygger usignerte NSIS-installere for x64 og arm64. Én workflow, to jobber, to helt forskjellige oppfatninger av "ferdig."
Beviset ligger i det som ikke skjedde
Slik vet du at rekkefølgen var riktig: etter de to samme-dags-justeringene som ga kjøringene navn og fikset navnet på en token-hemmelighet, ble release.yml ikke rørt igjen på fire måneder. Ingen signeringsfeil-hotfix. Ingen notariserings-avvisnings-panikk. Ingen "den native binæren mangler på runneren"-panikk. For en pipeline for kodesignering-og-notarisering — den sjangeren av CI som er mest beryktet for å slå seg vrang — er fire måneders stillhet nesten uhørt.
Det var stille fordi støyen allerede hadde skjedd et billigere sted. Kampene som vanligvis utspiller seg i en CI-logg-fane, med 10x-fakturering, ett force-push om gangen, hadde utspilt seg på utviklernes Mac-er i den lokale æraen. CI trengte ikke å være der pakkingen ble debugget, fordi pakkingen allerede var debugget. Det er tesen på én linje: flytt buildet vekk fra Macen din først når Macen din har sluttet å overraske deg.
Den ene koblingen som fortsatt kan slå tilbake
Den er ikke helt uten et svakt punkt, og det er verdt å nevne fordi det er subtilt. Swift-binærene som macOS-appen avhenger av — OCR, skjermopptak, transkribererne — er sjekket inn i git. Men releasen shipper ikke de innsjekkede kopiene; buildet rekompilerer dem på runneren. Og en check-swift-version.js-gate får hele releasen til å feile hardt hvis kompilatoren ikke er den låste versjonen. Ved denne releasen var den låsingen Swift 6.2.x, og ingenting i workflowen installerer den — jobben stoler rett og slett på at macos-latests standard-Swift stemmer. Den dagen GitHub hever runner-imaget sitt forbi låsingen, stopper releasen, ikke fordi noe er galt med appen, men fordi pipelinen er stille koblet til et maskin-image den ikke kontrollerer. Det er den ene implementasjonsdetaljen som denne tynne workflowen ikke dyttet ned i noe den selv eier.
Releasens andre halvdel: mindre payloads
Releasen bar på én urelatert funksjon verdt å nevne, fordi det er et rent eksempel på en latensfiks ved fratrekning. Assist Me-handlingen sendte backenden mer kontekst enn den trengte ved hver forespørsel. Én commit slanket to ting: den medsendte transkripsjonshistorikken falt fra de siste 30 oppføringene til de siste 15, og brukerens profil-kontekstfiler — injisert i systempromten ved hvert kall — fikk et hardt tak ved 4,000 tegn med en avkortingsmarkør. Mindre å serialisere, mindre å laste opp, mindre for modellen å lese før den begynner å svare, som er metrikken commiten optimaliserer for: time-to-first-token. For ærlighetens skyld: commiten hevder speeduppen, men måler den ikke — det finnes ingen før/etter-tall i historikken, så behandle det som en velbegrunnet slanking snarere enn en benchmarket seier.
Tre ting denne releasen lærte oss
- CI er en tynn innpakning over et skript som allerede fungerer. 66-linjers-workflowen har ingen egen signeringslogikk; den kaller et skript som tre måneder med lokale releaser allerede hadde debugget. Legg mekanismen i noe du kan kjøre for hånd, og la CI bare bestemme når det skal kjøres.
- Automatiser arbeidet, ikke nødvendigvis utløseren. Det første forsøket døde fordi tag-utløste releaser automatisk brukte 10x-fakturerte runner-minutter. Manuell
workflow_dispatchbeholder automatiseringen og fjerner den delen som kostet penger — noen ganger er mennesket i loopen selve funksjonen. - Debug der iterasjon er billig. Hver pakkekamp som løses på en utviklers Mac er en kamp som aldri skjer i en CI-logg-fane til ti ganger prisen. Flytt vekk fra Macen sist, ikke først.
For det forrige kapittelet i v1-historien, å shippe tretti språk uten sikkerhetsnett (v1.8.3); og for hele buen, anatomien i å shippe programvare til perfeksjon.