
Att lägga releasen i CI, två gånger
GeekBye v1.8.4 flyttade release-byggena till CI för macOS och Windows. Det ändringsloggen inte säger är att vi provade det här en gång tidigare, tog bort det en månad senare på grund av runner-kostnader och fick det att fastna först andra gången — för då fungerade release-skriptet redan för hand.
v1.8.4:s ändringslogg har en rad som låter som ren rutin: "Release-byggena kommer nu från CI för både macOS och Windows." Det läser sig som den sortens sak som händer en gång, rent, och aldrig tänks på igen. Det gjorde det inte. Teamet hade redan lagt releaser i CI en gång — och slitit ut det. Den här releasen är det andra försöket, och skälet till att det fastnade är själva ingenjörslärdomen, så låt oss berätta det i ordning.
Det första försöket, och varför det dog
Tillbaka i oktober 2025 fanns en release.yml. Den utlöstes på tag push — knuffa in en v*.*.*-tag och en macOS-runner skulle snurra igång, bygga och publicera. Den gjorde sin egen signeringsuppsättning för hand: ett steg som avkodade signeringscertifikatet, skapade och låste upp en tillfällig keychain, importerade certet så att codesign kunde hitta det, och ett matchande städsteg i slutet. Det fungerade. Den var också macOS-endast — Windows-releaser fanns inte i CI alls.
Den 4 november togs den bort. Commit-meddelandet är ovanligt uppriktigt om varför: "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 är hela historien om det första försöket i en mening. GitHub debiterar macOS-runnertid till tio gånger Linux-taxan, och en tag-utlöst release betyder att varje enskild tag — inklusive slit-och-släng-taggarna, om-taggningarna, "hoppsan, glömde versionshöjningen"-taggarna — tyst spenderar hundra debiterade minuter. Automatisering du inte kontrollerar är automatisering som gör av med pengar utan din vetskap. Så releaserna återgick till en utvecklares Mac de kommande tre och en halv månaderna.
Där de svåra problemen faktiskt löstes
Här är delen som får det andra försöket att fungera, och det hände helt utanför CI. Under de tre och en halv månaderna av lokala releaser sög scripts/release.js — det vanliga Node-skriptet som en utvecklare kör för hand — åt sig varje paketeringsproblem appen hade, en fix i taget:
- White-label-återställning. Det här är en white-label-kodbas som bygger både GeekBye och Pavleur från en källa, vilket betyder att en release byter ut
package.json-fält och ikonresurser för målprodukten. Två fixar lärde skriptet att lägga tillbaka allt efteråt, så att bygga Pavleur inte lämnade ditt git-träd smutsigt med Pavleurs identitet. - Dubbelarkitektur för Windows. En fix för att bygga både x64- och arm64-installers för Windows i stället för en.
- Kringgåendet för osignerad Windows. Nyare electron-builder ignorerade config-flaggan som var tänkt att stänga av Windows-signering, så skriptet lärde sig att tvinga fram ett osignerat bygge genom att i stället sätta
CSC_IDENTITY_AUTO_DISCOVERY=falsei miljön — en krök du bara hittar genom att gå på den. - Autodetektering av notarisering. Skriptet lärde sig att titta på sin miljö och avgöra: signeringsuppgifter närvarande? Signera. Apples notariseringsuppgifter också närvarande? Notarisera. Ingendera? Bygg osignerat. Inga flaggor att komma ihåg; närvaron av hemligheterna är konfigurationen.
Inget av det är glamoröst. Allt är den sortens sak som, om den överraskar dig inuti en CI-runner, kostar dig en timme av push-vänta-fela-läs-loggar per försök — med 10x-debitering. Löst på en Mac du sitter framför kostar var och en av dem en minut.
Det andra försöket: sextiosex rader
När CI kom tillbaka i v1.8.4 var workflowen 66 rader, och dess definierande egenskap är hur lite den gör. Commiten beskriver den rakt på sak: "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." Varje designbeslut i den kommer ur vad det första försöket lärde den hårda vägen:
workflow_dispatch, inte tag-utlöst. Du startar en release genom att klicka "Run workflow." En människa grindar varje betald macOS-minut. Kostnadsproblemet som dödade den första versionen löses genom att helt enkelt inte automatisera utlösaren — det enda ställe där automatisering var aktivt skadlig.- En
product-input. Dispatchen tar en rullgardinsmeny —geekbyeellerpavleur— så att samma workflow skeppar endera varumärket. White-label-sömmen löper hela vägen ut till release-knappen. - Två parallella jobb.
build-macpåmacos-latest,build-winpåwindows-latest, körande samtidigt. Windows i CI är genuint nytt här; oktober-workflowen byggde det aldrig. - Ingen signeringslogik i YAML:en. Det är hela poängen. Ingen keychain-jonglering, inget cert-importsteg, ingen städning. Mac-jobbet kör
node scripts/release.js <product> --publishoch Windows-jobbet kör samma med--no-sign. Allt den första workflowen gjorde för hand i YAML bor nu i skriptet som redan fungerar. Workflowen är en orkestrerare, inte en implementation.
De två plattformarna vill ha motsatta saker, och jobben speglar det ärligt. macOS bygger under en härdad runtime, signerat och notariserat genom electron-builders inbyggda väg (@electron/notarize), med uppgifterna hämtade från repots hemligheter och autodetekterade av skriptet. Windows bygger osignerade NSIS-installers för x64 och arm64. En workflow, två jobb, två helt olika föreställningar om "klart."
Beviset ligger i det som inte hände
Så här vet du att sekvenseringen var rätt: efter de två samma-dags-justeringarna som namngav körningarna och fixade namnet på en token-hemlighet rördes release.yml inte igen på fyra månader. Ingen signeringsfel-hotfix. Ingen notariserings-avslags-panik. Ingen "den nativa binären saknas på runnern"-panik. För en pipeline för kodsignering-och-notarisering — den genre av CI som är mest ökänd för att slå bakut — är fyra månaders tystnad nästan aldrig hört talas om.
Det var tyst för att oväsendet redan hade hänt någonstans billigare. Striderna som vanligtvis utspelar sig i en CI-loggflik, med 10x-debitering, en force-push i taget, hade utspelat sig på utvecklarnas Mac:ar under den lokala eran. CI behövde inte vara där paketeringen debuggades, för paketeringen var redan debuggad. Det är tesen på en rad: flytta bygget från din Mac först när din Mac har slutat överraska dig.
Den enda kopplingen som fortfarande kan slå tillbaka
Den är inte helt utan en svag punkt, och den är värd att namnge för att den är subtil. Swift-binärerna som macOS-appen beror på — OCR, skärminspelning, transkriberarna — är incheckade i git. Men releasen skeppar inte de incheckade kopiorna; bygget kompilerar om dem på runnern. Och en check-swift-version.js-grind får hela releasen att fallera hårt om kompilatorn inte är den fästa versionen. Vid den här releasen var den fästningen Swift 6.2.x, och inget i workflowen installerar den — jobbet litar helt enkelt på att macos-latest:s standard-Swift matchar. Den dag GitHub höjer sin runner-image förbi fästningen stannar releasen, inte för att något är fel med appen, utan för att pipelinen är tyst kopplad till en maskin-image den inte kontrollerar. Det är den enda implementationsdetalj som denna tunna workflow inte tryckte ned i något den själv äger.
Releasens andra halva: mindre payloads
Releasen bar på en orelaterad funktion värd att nämna, för att det är ett rent exempel på en latensfix genom subtraktion. Assist Me-åtgärden skickade backenden mer kontext än den behövde vid varje förfrågan. En commit bantade två saker: transkripthistoriken som skickades med föll från de senaste 30 posterna till de senaste 15, och användarens profilkontextfiler — injicerade i systemprompten vid varje anrop — fick ett hårt tak vid 4,000 tecken med en trunkeringsmarkör. Mindre att serialisera, mindre att ladda upp, mindre för modellen att läsa innan den börjar svara, vilket är måttet commiten optimerar för: time-to-first-token. För ärlighetens skull: commiten hävdar snabbheten men mäter den inte — det finns ingen före/efter-siffra i historiken, så behandla det som en välmotiverad bantning snarare än en benchmarkad vinst.
Tre saker den här releasen lärde oss
- CI är ett tunt hölje över ett skript som redan fungerar. 66-raders-workflowen har ingen egen signeringslogik; den anropar ett skript som tre månader av lokala releaser redan hade debuggat. Lägg mekanismen i något du kan köra för hand, och låt CI bara bestämma när det ska köras.
- Automatisera arbetet, inte nödvändigtvis utlösaren. Det första försöket dog för att tag-utlösta releaser automatiskt spenderade 10x-debiterade runner-minuter. Manuell
workflow_dispatchbehåller automatiseringen och tar bort den del som kostade pengar — ibland är människan i loopen själva funktionen. - Debugga där iteration är billig. Varje paketeringsstrid som löses på en utvecklares Mac är en strid som aldrig händer i en CI-loggflik till tio gånger priset. Flytta bort från Mac:en sist, inte först.
För det föregående kapitlet i v1-historien, att skeppa trettio språk utan skyddsnät (v1.8.3); och för hela bågen, anatomin i att skeppa mjukvara till perfektion.