
release को CI में डालना, दो बार
GeekBye v1.8.4 ने macOS और Windows दोनों के release builds को CI में ले जाया। changelog जो नहीं बताता वह यह है: हमने यह एक बार पहले भी आज़माया था, runner cost की वजह से एक महीने बाद उसे मिटा दिया, और दूसरी बार में ही जाकर वह टिका — क्योंकि तब तक release script हाथ से पहले ही चल रहा था।
v1.8.4 के changelog में एक लाइन है जो शुद्ध घर-गृहस्थी जैसी सुनाई देती है: "release builds अब macOS और Windows दोनों के लिए CI से आते हैं।" यह ऐसी चीज़ की तरह पढ़ी जाती है जो एक बार, साफ़-सुथरे तरीक़े से होती है और फिर उसके बारे में कभी नहीं सोचा जाता। वह ऐसी नहीं थी। team ने release को CI में एक बार पहले ही डाल दिया था — और उसे उखाड़ भी दिया था। यह release दूसरी कोशिश है, और इसके टिकने की वजह ही असली engineering सबक़ है, तो चलिए इसे क्रम से सुनाते हैं।
पहली कोशिश, और वह क्यों मरी
अक्टूबर 2025 में एक release.yml था। यह tag push पर trigger होता था — एक v*.*.* tag ठेल दो और एक macOS runner खड़ा होकर build और publish कर देगा। यह अपनी signing setup ख़ुद हाथ से करता था: एक step जो signing certificate को decode करता, एक अस्थायी keychain बनाकर unlock करता, cert को import करता ताकि codesign उसे ढूँढ़ सके, और अंत में एक मेल खाता cleanup step। यह काम करता था। यह सिर्फ़-macOS भी था — Windows releases CI में थीं ही नहीं।
4 नवंबर को इसे delete कर दिया गया। commit message वजह के बारे में असामान्य रूप से बेबाक है: "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।" यही पहली कोशिश की पूरी कहानी एक वाक्य में है। GitHub macOS runner समय को Linux से दस गुना दर पर bill करता है, और एक tag-triggered release का मतलब है कि हर एक tag — जिनमें फेंकने वाले, फिर से tag किए गए, "अरे, version bump करना भूल गया" वाले भी शामिल हैं — चुपचाप सौ billed मिनट ख़र्च कर देता है। जो automation आपके क़ाबू में नहीं, वह ऐसा automation है जो आपके सोते वक़्त पैसे ख़र्च करता है। तो अगले साढ़े तीन महीने के लिए release वापस एक developer के Mac पर चली गई।
जहाँ मुश्किल समस्याएँ असल में हल हुईं
यहीं वह हिस्सा है जो दूसरी कोशिश को कारगर बनाता है, और यह पूरी तरह CI के बाहर हुआ। local releases के उन साढ़े तीन महीनों में, scripts/release.js — वह सादा Node script जिसे developer हाथ से चलाता है — ने app की हर packaging समस्या को सोख लिया, एक बार में एक fix:
- white-label restore। यह एक white-label codebase है जो एक ही source से GeekBye और Pavleur दोनों build करता है, जिसका मतलब है कि एक release target product के लिए
package.jsonfields और icon assets बदल देती है। दो fixes ने script को सिखाया कि बाद में सब कुछ वापस अपनी जगह रख दे, ताकि Pavleur build करने से आपका git tree Pavleur की पहचान से गंदा न रह जाए। - dual-arch Windows। एक fix जो एक की बजाय x64 और arm64 दोनों Windows installers build करता है।
- unsigned-Windows वाला जुगाड़। नए electron-builder ने उस config flag को नज़रअंदाज़ कर दिया जो Windows signing बंद करने के लिए था, तो script ने इसके बदले environment में
CSC_IDENTITY_AUTO_DISCOVERY=falseसेट करके एक unsigned build को मजबूर करना सीखा — यह उस किस्म की सिलवट है जो आपको सिर्फ़ इससे टकराकर ही मिलती है। - notarization का auto-detection। script ने अपने environment को देखकर तय करना सीखा: signing credentials मौजूद हैं? sign करो। Apple notarization credentials भी मौजूद हैं? notarize करो। दोनों नहीं? unsigned build करो। याद रखने को कोई flag नहीं; उन secrets का होना ही configuration है।
इनमें से कुछ भी चमकीला नहीं है। ये सब उसी किस्म की चीज़ें हैं कि अगर वे एक CI runner के अंदर आपको चौंका दें, तो हर कोशिश पर push-रुको-fail-logs-पढ़ो में आपका एक घंटा ले लेती हैं — वह भी 10x billing पर। जिस Mac के आगे आप बैठे हैं उस पर हल करो, तो हर एक में एक मिनट लगता है।
दूसरी कोशिश: छियासठ लाइनें
जब CI v1.8.4 में लौटा, तो workflow 66 लाइनों का था, और इसकी पहचान वाली ख़ूबी यह है कि यह कितना कम करता है। commit इसे सादगी से बताता है: "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।" इसमें हर design फ़ैसला पहली कोशिश का एक ज़ख़्म है:
workflow_dispatch, tag-triggered नहीं। आप "Run workflow" पर click करके एक release शुरू करते हैं। एक इंसान हर paid macOS मिनट पर द्वार पर खड़ा रहता है। पहली version को मारने वाली cost समस्या बस trigger को automate न करके हल हो जाती है — वही इकलौती जगह जहाँ automation सक्रिय रूप से नुक़सानदेह था।- एक
productinput। dispatch एक dropdown लेता है —geekbyeयाpavleur— तो वही workflow किसी भी brand को ship करता है। white-label वाली सीवन ठेठ release button तक जा पहुँचती है। - दो parallel jobs।
macos-latestपरbuild-mac,windows-latestपरbuild-win, एक ही समय पर चलते हुए। CI में Windows यहाँ सचमुच नया है; अक्टूबर वाले workflow ने इसे कभी build नहीं किया। - YAML में कोई signing logic नहीं। यही पूरा मुद्दा है। कोई keychain की बाज़ीगरी नहीं, कोई cert import step नहीं, कोई cleanup नहीं। Mac job
node scripts/release.js <product> --publishचलाता है और Windows job वही--no-signके साथ चलाता है। पहला workflow YAML में हाथ से जो कुछ करता था, वह सब अब उस script में रहता है जो पहले से चलता है। workflow एक orchestrator है, एक implementation नहीं।
दोनों platforms उलटी चीज़ें चाहते हैं, और jobs इसे ईमानदारी से दर्शाते हैं। macOS एक hardened runtime के तहत build होता है, electron-builder के built-in रास्ते (@electron/notarize) से signed और notarized, जहाँ credentials repository secrets से दिए जाते हैं और script द्वारा auto-detect होते हैं। Windows x64 और arm64 के लिए unsigned NSIS installers build करता है। एक workflow, दो jobs, "done" की दो बिल्कुल अलग धारणाएँ।
सबूत उसमें है जो नहीं हुआ
आपको कैसे पता चलता है कि क्रम सही था: उन्हीं दिन के दो छोटे बदलावों के बाद जिन्होंने runs को नाम दिया और एक token secret का नाम ठीक किया, release.yml को चार महीने तक फिर छुआ नहीं गया। कोई signing-failure hotfix नहीं। कोई notarization-rejection वाली हड़बड़ी नहीं। कोई "runner पर native binary ग़ायब है" वाली घबराहट नहीं। एक code-signing-and-notarization pipeline के लिए — जो अपनी उथल-पुथल के लिए सबसे बदनाम CI किस्म है — चार महीने की ख़ामोशी क़रीब-क़रीब अनसुनी बात है।
यह ख़ामोश था क्योंकि शोर पहले ही कहीं और, सस्ती जगह पर हो चुका था। वे लड़ाइयाँ जो आम तौर पर एक CI logs tab में, 10x billing पर, एक बार में एक force-push के साथ लड़ी जाती हैं, local दौर में developers के Macs पर लड़ी जा चुकी थीं। CI को वह जगह होने की ज़रूरत नहीं थी जहाँ packaging debug हो, क्योंकि packaging पहले ही debug हो चुका था। एक लाइन में यही थीसिस है: build को अपने Mac से तभी हटाओ जब तुम्हारे Mac ने तुम्हें चौंकाना बंद कर दिया हो।
वह इकलौती coupling जो अब भी काट सकती है
यह पूरी तरह कमज़ोरी-रहित नहीं है, और इसका नाम लेना ज़रूरी है क्योंकि यह सूक्ष्म है। जिन Swift binaries पर macOS app निर्भर करता है — OCR, screen capture, transcribers — वे git में commit हैं। पर release commit की गई copies ship नहीं करती; build उन्हें runner पर फिर से compile करता है। और एक check-swift-version.js gate पूरी release को hard-fail कर देता है अगर compiler pinned version का न हो। इस release पर वह pin Swift 6.2.x था, और workflow में कुछ भी उसे install नहीं करता — job बस भरोसा कर लेता है कि macos-latest का default Swift मेल खाएगा। जिस दिन GitHub अपनी runner image को उस pin से आगे धकेल देगा, release रुक जाएगी, इसलिए नहीं कि app में कुछ ग़लत है, बल्कि इसलिए कि pipeline चुपचाप एक ऐसी machine image से coupled है जिसे वह क़ाबू नहीं करता। यह इकलौता implementation detail है जिसे इस पतले workflow ने अपनी मालिकी वाली किसी चीज़ में नीचे नहीं धकेला।
release का दूसरा आधा हिस्सा: छोटे payloads
release एक असंबंधित feature भी साथ लाई जो ज़िक्र के लायक़ है, क्योंकि यह घटाव से latency ठीक करने का एक साफ़ उदाहरण है। Assist Me action हर request पर backend को उससे ज़्यादा context भेज रहा था जितनी ज़रूरत थी। एक commit ने दो चीज़ें छाँटीं: साथ भेजी जाने वाली transcript history पिछली 30 entries से घटकर पिछली 15 हो गई, और user की profile context files — जो हर call पर system prompt में inject होती हैं — एक truncation marker के साथ 4,000 characters पर hard-cap कर दी गईं। serialize करने को कम, upload करने को कम, model के जवाब शुरू करने से पहले पढ़ने को कम — और यही वह metric है जिसे यह commit optimize करता है: time-to-first-token। ईमानदारी के नाते: commit तेज़ी का दावा करता है पर उसे नापता नहीं — history में कोई before/after आँकड़ा नहीं, तो इसे एक benchmark-समर्थित जीत की बजाय एक सोच-समझकर की गई छँटाई मानिए।
इस release ने हमें तीन बातें सिखाईं
- CI एक ऐसे script के ऊपर एक पतली परत है जो पहले से चलता है। 66-लाइन workflow की अपनी कोई signing logic नहीं; यह एक ऐसे script को call करता है जिसे तीन महीने की local releases पहले ही debug कर चुकी थीं। तंत्र को किसी ऐसी चीज़ में रखो जिसे तुम हाथ से चला सको, और CI को बस यह तय करने दो कि उसे कब चलाना है।
- काम को automate करो, ज़रूरी नहीं कि trigger को। पहली कोशिश इसलिए मरी कि tag-triggered releases 10x-billed runner minutes अपने-आप ख़र्च कर देती थीं। manual
workflow_dispatchautomation को रखता है और उस हिस्से को हटा देता है जो पैसे ख़र्च कर रहा था — कभी-कभी loop में मौजूद इंसान ही feature होता है। - वहाँ debug करो जहाँ दोहराना सस्ता है। एक developer के Mac पर सुलझी हर packaging लड़ाई एक ऐसी लड़ाई है जो CI logs tab में दस गुना क़ीमत पर कभी नहीं होती। Mac से हटना आख़िर में, पहले नहीं।
v1 कहानी के पिछले अध्याय के लिए देखें बिना किसी safety net के तीस भाषाएँ ship करना (v1.8.3); और पूरे चाप के लिए, सॉफ़्टवेयर को परिपूर्णता तक पहुँचाकर भेजने की शारीर-रचना।