Steven
Steven8 мин четене

Слагане на издаването в CI, два пъти

GeekBye v1.8.4 премести release build-овете в CI за macOS и Windows. Това, което changelog-ът не казва, е че вече опитахме това веднъж, изтрихме го месец по-късно заради цената на runner-ите и го накарахме да се задържи чак на втория път — защото дотогава release скриптът вече работеше на ръка.

Инженерство
CI/CD
Desktop
Издания на GeekBye
Слагане на издаването в CI, два пъти

Changelog-ът на v1.8.4 има ред, който звучи като чиста домакинска работа: „Release build-овете вече идват от CI и за macOS, и за Windows." Чете се като нещо, което се случва веднъж, чисто, и за което повече не се мисли. Не беше така. Екипът вече беше сложил изданията в CI веднъж — и ги изтръгна оттам. Това издание е вторият опит, а причината да се задържи е истинският инженерен урок, така че нека го разкажем по ред.

Първият опит и защо умря

Още през октомври 2025 съществуваше release.yml. Задействаше се при push на таг — бутваш таг v*.*.* и macOS runner се вдигаше, изграждаше и публикуваше. Правеше си собствената настройка за подписване на ръка: стъпка, която декодираше сертификата за подписване, създаваше и отключваше временен keychain, импортираше сертификата, за да може codesign да го намери, и съответстваща стъпка за почистване накрая. Работеше. Беше и само за macOS — изданията за Windows изобщо не бяха в CI.

На 4 ноември беше изтрит. Съобщението на комита е необичайно откровено за причината: "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." (премахни release workflow-а на GitHub Actions за пестене на минути / macOS runner-ите струват 10x множител — 100 таксувани минути на 10-минутно издание. Изданията ще се правят локално.) Това е цялата история на първия опит в едно изречение. GitHub таксува времето на macOS runner-ите десет пъти по-скъпо от Linux, а издание, задействано от таг, означава, че всеки таг — включително еднократните, повторните тагове, тези от типа „опа, забравих да вдигна версията" — тихо харчи сто таксувани минути. Автоматизация, която не контролираш, е автоматизация, която харчи пари, докато спиш. Така че изданията се върнаха на Mac-а на един разработчик за следващите три месеца и половина.

Къде всъщност се решиха трудните проблеми

Ето частта, която кара вторият опит да проработи, и тя се случи изцяло извън CI. През тези три месеца и половина локални издания scripts/release.js — обикновеният Node скрипт, който разработчик пуска на ръка — погълна всеки проблем с пакетирането, който приложението имаше, по един fix наведнъж:

  • White-label възстановяване. Това е white-label codebase, който изгражда и GeekBye, и Pavleur от един източник, което значи, че изданието разменя полета в package.json и иконни asset-и за целевия продукт. Два fix-а научиха скрипта да връща всичко обратно след това, така че изграждането на Pavleur не оставяше git дървото ти мръсно с идентичността на Pavleur.
  • Dual-arch Windows. Fix, който да изгражда и двата Windows инсталатора, x64 и arm64, вместо един.
  • Workaround-ът за неподписан Windows. По-нов electron-builder игнорираше config флага, предназначен да изключи подписването на Windows, така че скриптът се научи да форсира неподписан build, като вместо това задава CSC_IDENTITY_AUTO_DISCOVERY=false в средата — гънка, която откриваш само като я удариш.
  • Автоматично разпознаване на нотаризацията. Скриптът се научи да гледа средата си и да решава: налични ли са credentials за подписване? Подписвай. Налични ли са и Apple credentials за нотаризация? Нотаризирай. Никакви? Неподписан build. Няма флагове за помнене; наличието на секретите е конфигурацията.

Нищо от това не е бляскаво. Всичко е от онзи вид, който, ако те изненада вътре в CI runner, ти струва час push-чакай-провал-чети-логовете на опит — при 10x таксуване. Решени на Mac, пред който седиш, всеки струва по минута.

Вторият опит: шейсет и шест реда

Когато CI се върна във v1.8.4, workflow-ът беше 66 реда, и определящото му качество е колко малко прави. Комитът го описва просто: "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." (ръчен workflow_dispatch тригер, който изгражда двете платформи паралелно, преизползвайки съществуващия release.js скрипт. macOS build-овете са подписани и нотаризирани, Windows build-овете са неподписани.) Всяко дизайнерско решение в него е белег от първия опит:

  • workflow_dispatch, не задействан от таг. Започваш издание, като натиснеш „Run workflow". Човек пази всяка платена macOS минута. Проблемът с цената, който уби първата версия, се решава просто като не автоматизираш тригера — единственото място, където автоматизацията беше активно вредна.
  • Вход product. Dispatch-ът приема dropdown — geekbye или pavleur — така че същият workflow доставя всеки от брандовете. White-label шевът минава чак до бутона за издание.
  • Два паралелни job-а. build-mac на macos-latest, build-win на windows-latest, работещи по едно и също време. Windows в CI е наистина нов тук; октомврийският workflow никога не го изграждаше.
  • Никаква логика за подписване в YAML. Това е целият смисъл. Няма жонглиране с keychain, няма стъпка за импорт на сертификат, няма почистване. Mac job-ът пуска node scripts/release.js <product> --publish, а Windows job-ът пуска същото с --no-sign. Всичко, което първият workflow правеше на ръка в YAML, сега живее в скрипта, който вече работи. Workflow-ът е оркестратор, не имплементация.

Двете платформи искат противоположни неща, и job-овете го отразяват честно. macOS изгражда под hardened runtime, подписан и нотаризиран през вградения път на electron-builder (@electron/notarize), с credentials, подадени от секретите на repository-то и автоматично разпознати от скрипта. Windows изгражда неподписани NSIS инсталатори за x64 и arm64. Един workflow, два job-а, две напълно различни представи за „готово".

Доказателството е в това, което не се случи

Ето как разбираш, че подредбата беше правилна: след двете корекции от същия ден, които именуваха пусканията и поправиха името на един token секрет, release.yml не беше пипан цели четири месеца. Никакъв hotfix за провал на подписването. Никаква паника от отхвърлена нотаризация. Никакво „нативният бинар липсва на runner-а". За pipeline за подписване-и-нотаризация — жанрът CI, най-прословут с мятането си — четири месеца тишина е почти нечувано.

Беше тихо, защото шумът вече беше станал някъде по-евтино. Битките, които обикновено се разиграват в таб с CI логове, при 10x таксуване, по един force-push наведнъж, се бяха разиграли на Mac-овете на разработчиците през локалната ера. CI не трябваше да е мястото, където пакетирането се дебъгва, защото пакетирането вече беше дебъгнато. Това е тезата в един ред: премести build-а от своя Mac едва след като своят Mac спре да те изненадва.

Единствената свързаност, която още може да ухапе

Не е съвсем без слабо място, и си струва да се назове, защото е фино. Swift бинарите, от които зависи macOS приложението — OCR, заснемане на екрана, транскрайбърите — са комитнати в git. Но изданието не доставя комитнатите копия; build-ът ги прекомпилира на runner-а. А порта check-swift-version.js сурово проваля цялото издание, ако компилаторът не е фиксираната версия. При това издание тази фиксация беше Swift 6.2.x, и нищо в workflow-а не го инсталира — job-ът просто вярва, че Swift-ът по подразбиране на macos-latest съвпада. В деня, в който GitHub вдигне образа на runner-а си над фиксацията, изданието спира, не защото нещо е наред с приложението, а защото pipeline-ът е тихо свързан с образ на машина, който не контролира. Това е единственият имплементационен детайл, който този тънък workflow не избута надолу в нещо, което притежава.

Другата половина на изданието: по-малки payload-и

Изданието носеше една несвързана функция, достойна за споменаване, защото е чист пример за fix на латентност чрез изваждане. Действието Assist Me пращаше на backend-а повече контекст, отколкото му трябваше при всяка заявка. Един комит отряза две неща: изпращаната история на транскрипцията падна от последните 30 записа до последните 15, а контекстните файлове на профила на потребителя — инжектирани в system prompt-а при всяко извикване — бяха сурово ограничени до 4,000 знака с маркер за отрязване. По-малко за сериализиране, по-малко за качване, по-малко за четене от модела преди да започне да отговаря, което е метриката, която комитът оптимизира: time-to-first-token. В името на честността: комитът твърди ускорението, но не го измерва — няма число преди/след в историята, така че го третирай като добре обоснована резитба, а не като измерена с бенчмарк печалба.

Три неща, на които ни научи това издание

  1. CI е тънка обвивка над скрипт, който вече работи. Workflow-ът от 66 реда няма собствена логика за подписване; той извиква скрипт, който три месеца локални издания вече бяха дебъгнали. Сложи механизма в нещо, което можеш да пуснеш на ръка, и остави CI просто да решава кога да го пусне.
  2. Автоматизирай работата, не непременно тригера. Първият опит умря, защото изданията, задействани от таг, автоматично харчеха runner минути, таксувани с 10x. Ръчен workflow_dispatch запазва автоматизацията и премахва частта, която струваше пари — понякога човекът в цикъла е функцията.
  3. Дебъгвай там, където итерацията е евтина. Всяка битка с пакетирането, решена на Mac-а на разработчик, е битка, която никога не се случва в таб с CI логове при десет пъти цената. Премести се от Mac-а последен, не първи.

За предишната глава от историята на v1 — доставяне на трийсет езика без предпазна мрежа (v1.8.3); а за цялата дъга — анатомията на доставянето на софтуер до съвършенство.

Свързани статии

Двата режима на отказ на click-through overlay
Steven
Steven8 мин четене

Двата режима на отказ на click-through overlay

Прозорецът на GeekBye плава над всичко и оставя кликовете ти да минават през него — освен там, където има бутони. Това е двустранен договор, а v1.8.5 и v1.8.14 показват как изглежда, когато всяка от страните се счупи: едно издание, в което overlay-ът глътна системен диалог, и едно, в което открадна натисканията на клавишите ти. Печелившият fix за второто беше изтриване на код.

Инженерство
Electron
Desktop
Доставяне на трийсет езика без предпазна мрежа
Steven
Steven8 мин четене

Доставяне на трийсет езика без предпазна мрежа

GeekBye v1.8.3 закачи react-i18next и преведе цялото приложение на 30 езика. Частта с библиотеката беше рутинна. Интересната част е това, което липсваше: никаква проверка за паритет на ключовете и никакъв екстрактор на стрингове — така hardcoded английски се промъкна, датска печатна грешка стигна до производство, а changelog-ът не можеше да реши дали приложението говори 28, 29 или 30 езика.

Инженерство
i18n
Desktop
Четири издания за двайсет и шест часа: езиков водопровод и едно „bridge release"
Steven
Steven6 мин четене

Четири издания за двайсет и шест часа: езиков водопровод и едно „bridge release"

GeekBye от v1.7.6 до v1.8.1: езикова настройка, която никога не беше напускала лаптопа, overlay на Cmd+/, който показва истинските ти клавишни комбинации, версия с нула промени по кода — и парадоксът кокошката или яйцето при миграция на auto-update фийда.

Инженерство
Release
Desktop