
At lægge releasen i CI, to gange
GeekBye v1.8.4 flyttede release-buildene til CI for macOS og Windows. Det, ændringsloggen ikke siger, er, at vi prøvede det her én gang før, slettede det en måned senere på grund af runner-omkostninger og først fik det til at holde anden gang — for på det tidspunkt virkede release-scriptet allerede i hånden.
v1.8.4-ændringsloggen har en linje, der lyder som ren rutine: "Release-buildene kommer nu fra CI for både macOS og Windows." Det læses som den slags ting, der sker én gang, rent, og som man aldrig tænker på igen. Det gjorde det ikke. Teamet havde allerede lagt releaser i CI én gang — og revet det ud. Denne release er det andet forsøg, og grunden til, at det holdt, er den egentlige ingeniørlektie, så lad os fortælle den i rækkefølge.
Det første forsøg, og hvorfor det døde
Tilbage i oktober 2025 fandtes en release.yml. Den blev udløst på tag push — skub et v*.*.*-tag ind, og en macOS-runner ville starte op, bygge og udgive. Den lavede sin egen signeringsopsætning i hånden: et trin, der afkodede signeringscertifikatet, oprettede og låste en midlertidig keychain op, importerede certet, så codesign kunne finde det, og et tilsvarende oprydningstrin til sidst. Det virkede. Den var også macOS-kun — Windows-releaser var slet ikke i CI.
Den 4. november blev den slettet. Commit-beskeden er usædvanligt æ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øg i én sætning. GitHub afregner macOS-runnertid til ti gange Linux-taksten, og en tag-udløst release betyder, at hvert eneste tag — inklusive engangstaggene, om-taggene, "ups, glemte versionsbumpet"-taggene — stille bruger hundrede afregnede minutter. Automatisering, du ikke kontrollerer, er automatisering, der bruger penge uden din viden. Så releaserne vendte tilbage til en udviklers Mac de næste tre en halv måned.
Hvor de svære problemer faktisk blev løst
Her er den del, der får det andet forsøg til at virke, og det skete helt uden for CI. I løbet af de tre en halv måneders lokale releaser opsugede scripts/release.js — det almindelige Node-script, som en udvikler kører i hånden — hvert eneste pakningsproblem, appen havde, ét fix ad gangen:
- White-label-gendannelse. Det her er en white-label-kodebase, der bygger både GeekBye og Pavleur fra én kilde, hvilket betyder, at en release bytter
package.json-felter og ikon-ressourcer ud til målproduktet. To fixes lærte scriptet at lægge alt tilbage bagefter, så det at bygge Pavleur ikke efterlod dit git-træ snavset med Pavleurs identitet. - Dobbeltarkitektur til Windows. Et fix til at bygge både x64- og arm64-installere til Windows i stedet for én.
- Omgåelsen for usigneret Windows. Nyere electron-builder ignorerede config-flaget, der skulle slå Windows-signering fra, så scriptet lærte at tvinge et usigneret build igennem ved i stedet at sætte
CSC_IDENTITY_AUTO_DISCOVERY=falsei miljøet — en krølle, man kun finder ved at ramme den. - Autodetektering af notarisering. Scriptet lærte at kigge på sit miljø og beslutte: signeringsoplysninger til stede? Signér. Apples notariseringsoplysninger også til stede? Notarisér. Ingen af delene? Byg usigneret. Ingen flag at huske; tilstedeværelsen af hemmelighederne er konfigurationen.
Intet af det er glamourøst. Alt sammen er den slags ting, der, hvis den overrasker dig inde i en CI-runner, koster dig en time med push-vent-fejl-læs-logs pr. forsøg — med 10x-afregning. Løst på en Mac, du sidder foran, koster hver enkelt af dem et minut.
Det andet forsøg: seksogtres linjer
Da CI kom tilbage i v1.8.4, var workflowen 66 linjer, og dens definerende kvalitet er, hvor lidt den gør. Commiten beskriver den ligeud: "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 udspringer af, hvad det første forsøg lærte den på den hårde måde:
workflow_dispatch, ikke tag-udløst. Du starter en release ved at klikke "Run workflow." Et menneske sætter en port for hvert betalt macOS-minut. Omkostningsproblemet, der dræbte den første version, løses ved simpelthen ikke at automatisere udløseren — det ene sted, hvor automatisering var aktivt skadelig.- Et
product-input. Dispatchen tager en dropdown —geekbyeellerpavleur— så den samme workflow shipper begge brands. White-label-sømmen løber hele vejen ud til release-knappen. - To parallelle jobs.
build-macpåmacos-latest,build-winpåwindows-latest, kørende samtidig. Windows i CI er reelt nyt her; oktober-workflowen byggede det aldrig. - Ingen signeringslogik i YAML'en. Det er hele pointen. Ingen keychain-jonglering, intet cert-importtrin, ingen oprydning. Mac-jobbet kører
node scripts/release.js <product> --publish, og Windows-jobbet kører det samme med--no-sign. Alt, hvad den første workflow gjorde i hånden i YAML, bor nu i scriptet, der allerede virker. Workflowen er en orkestrator, ikke en implementering.
De to platforme vil have modsatte ting, og jobsene afspejler det ærligt. macOS bygger under en hærdet runtime, signeret og notariseret gennem electron-builders indbyggede vej (@electron/notarize), med oplysningerne leveret fra repoets hemmeligheder og autodetekteret af scriptet. Windows bygger usignerede NSIS-installere til x64 og arm64. Én workflow, to jobs, to helt forskellige forestillinger om "færdig."
Beviset ligger i det, der ikke skete
Sådan ved du, at rækkefølgen var rigtig: efter de to samme-dags-justeringer, der navngav kørslerne og rettede navnet på en token-hemmelighed, blev release.yml ikke rørt igen i fire måneder. Ingen signeringsfejl-hotfix. Ingen notariserings-afvisnings-panik. Ingen "den native binær mangler på runneren"-panik. For en pipeline til kodesignering-og-notarisering — den genre af CI, der er mest berygtet for at slå gnister — er fire måneders stilhed næsten uhørt.
Der var stille, fordi støjen allerede var sket et billigere sted. Kampene, der normalt udspiller sig i en CI-log-fane, med 10x-afregning, ét force-push ad gangen, havde udspillet sig på udviklernes Mac'er i den lokale æra. CI behøvede ikke at være der, hvor pakningen blev debugget, fordi pakningen allerede var debugget. Det er tesen på én linje: flyt buildet væk fra din Mac, først når din Mac er holdt op med at overraske dig.
Den ene kobling, der stadig kan give bagslag
Den er ikke helt uden et svagt punkt, og det er værd at nævne, fordi det er subtilt. Swift-binærerne, som macOS-appen afhænger af — OCR, skærmoptagelse, transskribererne — er tjekket ind i git. Men releasen shipper ikke de indtjekkede kopier; buildet genkompilerer dem på runneren. Og en check-swift-version.js-gate får hele releasen til at fejle hårdt, hvis compileren ikke er den fastlåste version. Ved denne release var den fastlåsning Swift 6.2.x, og intet i workflowen installerer den — jobbet stoler simpelthen på, at macos-latests standard-Swift matcher. Den dag GitHub hæver sit runner-image forbi fastlåsningen, stopper releasen, ikke fordi der er noget galt med appen, men fordi pipelinen er stille koblet til et maskin-image, den ikke kontrollerer. Det er den ene implementeringsdetalje, som denne tynde workflow ikke skubbede ned i noget, den selv ejer.
Releasens anden halvdel: mindre payloads
Releasen bar på én urelateret feature værd at nævne, fordi det er et rent eksempel på en latensfix ved fratrækning. Assist Me-handlingen sendte backenden mere kontekst, end den havde brug for ved hver forespørgsel. Én commit slankede to ting: den medsendte transskriptionshistorik faldt fra de sidste 30 poster til de sidste 15, og brugerens profil-kontekstfiler — injiceret i systempromten ved hvert kald — fik et hårdt loft ved 4,000 tegn med en afkortningsmarkør. Mindre at serialisere, mindre at uploade, mindre for modellen at læse, før den begynder at svare, hvilket er den metrik, commiten optimerer for: time-to-first-token. For ærlighedens skyld: commiten hævder speeduppen, men måler den ikke — der er ingen før/efter-tal i historikken, så behandl det som en velbegrundet slankning snarere end en benchmarket sejr.
Tre ting denne release lærte os
- CI er en tynd indpakning over et script, der allerede virker. 66-linjers-workflowen har ingen egen signeringslogik; den kalder et script, som tre måneders lokale releaser allerede havde debugget. Læg mekanismen i noget, du kan køre i hånden, og lad CI bare bestemme, hvornår det skal køre.
- Automatisér arbejdet, ikke nødvendigvis udløseren. Det første forsøg døde, fordi tag-udløste releaser automatisk brugte 10x-afregnede runner-minutter. Manuel
workflow_dispatchbevarer automatiseringen og fjerner den del, der kostede penge — nogle gange er mennesket i løkken selve featuren. - Debug der, hvor iteration er billig. Hver pakningskamp, der løses på en udviklers Mac, er en kamp, der aldrig sker i en CI-log-fane til ti gange prisen. Flyt væk fra Mac'en sidst, ikke først.
For det forrige kapitel i v1-historien, at shippe tredive sprog uden sikkerhedsnet (v1.8.3); og for hele buen, anatomien i at shippe software til perfektion.