Steven
Steven6 분 소요

릴리스를 CI에 넣다, 두 번

GeekBye v1.8.4는 릴리스 빌드를 macOS와 Windows 모두 CI로 옮겼습니다. 변경 로그가 말하지 않는 것은, 이 일을 이전에 한 번 시도했다가 러너 비용 때문에 한 달 뒤 지워 버렸고, 두 번째에서야 자리 잡게 했다는 사실입니다 — 그때쯤엔 릴리스 스크립트가 이미 손으로 잘 돌아가고 있었기 때문입니다.

엔지니어링
CI/CD
데스크톱
GeekBye 릴리스
릴리스를 CI에 넣다, 두 번

v1.8.4 변경 로그에는 순전히 잡무처럼 들리는 한 줄이 있습니다: "이제 릴리스 빌드가 macOS와 Windows 모두 CI에서 나온다." 한 번, 깔끔하게 일어나고, 다시는 떠올릴 일 없는 그런 종류처럼 읽힙니다. 그렇지 않았습니다. 팀은 이미 한 번 릴리스를 CI에 넣었고 — 그것을 뜯어냈습니다. 이 릴리스는 두 번째 시도이며, 그것이 자리 잡은 이유가 바로 진짜 엔지니어링 교훈이니, 순서대로 이야기합시다.

첫 시도, 그리고 왜 그것이 죽었나

2025년 10월로 거슬러 가면 release.yml이 하나 있었습니다. 태그 푸시에 트리거됐습니다 — v*.*.* 태그를 밀어 넣으면 macOS 러너가 올라와서 빌드하고 게시했습니다. 서명 설정을 자체적으로 손수 했습니다: 서명 인증서를 디코딩하고, 임시 키체인을 만들어 잠금 해제하고, codesign이 찾을 수 있도록 인증서를 임포트하는 단계, 그리고 끝에 그에 대응하는 정리 단계. 돌아갔습니다. 그리고 그것은 macOS 전용이기도 했습니다 — Windows 릴리스는 CI에 아예 없었습니다.

11월 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." 그게 첫 시도의 이야기 전체를 한 문장에 담은 것입니다. GitHub은 macOS 러너 시간을 Linux의 열 배 요율로 청구하고, 태그 트리거 릴리스는 모든 단일 태그가 — 버리는 것, 다시 붙인 것, "앗, 버전 올리는 걸 잊었네" 하는 것까지 포함해 — 조용히 100분의 청구를 소비한다는 뜻입니다. 당신이 통제하지 못하는 자동화는 당신이 자는 동안 돈을 쓰는 자동화입니다. 그래서 릴리스는 이후 석 달 반 동안 개발자의 Mac으로 돌아갔습니다.

어려운 문제가 실제로 풀린 곳

여기가 두 번째 시도를 작동하게 만든 부분이고, 그것은 완전히 CI _바깥_에서 일어났습니다. 그 석 달 반의 로컬 릴리스 동안, scripts/release.js — 개발자가 손으로 실행하는 평범한 Node 스크립트 — 는 앱이 가진 모든 패키징 문제를, 한 번에 한 수정씩, 전부 흡수했습니다:

  • 화이트라벨 복원. 이것은 하나의 소스에서 GeekBye와 Pavleur 둘 다 빌드하는 화이트라벨 코드베이스이고, 이는 곧 릴리스가 대상 제품에 맞춰 package.json 필드와 아이콘 에셋을 바꿔 낀다는 뜻입니다. 두 개의 수정이 스크립트에게 나중에 전부 원래대로 되돌려 놓는 법을 가르쳐서, Pavleur를 빌드해도 git 트리가 Pavleur의 정체성으로 더럽혀진 채 남지 않게 됐습니다.
  • 듀얼 아키 Windows. 하나가 아니라 x64와 arm64 둘 다 Windows 인스톨러를 빌드하는 수정.
  • 미서명 Windows 우회. 더 새로운 electron-builder가 Windows 서명을 끄기 위한 설정 플래그를 무시했기에, 스크립트는 대신 환경에서 CSC_IDENTITY_AUTO_DISCOVERY=false를 설정해 미서명 빌드를 강제하는 법을 배웠습니다 — 부딪쳐 봐야만 발견하는 종류의 주름입니다.
  • 공증 자동 감지. 스크립트는 자기 환경을 보고 판단하는 법을 배웠습니다: 서명 자격 증명이 있나? 서명한다. Apple 공증 자격 증명도 있나? 공증한다. 둘 다 없나? 미서명으로 빌드한다. 외워 둘 플래그는 없습니다; 시크릿의 존재 자체가 설정입니다.

그중 어느 것도 화려하지 않습니다. 그 전부가, CI 러너 안에서 당신을 놀라게 한다면 시도 한 번당 푸시-대기-실패-로그 읽기로 한 시간을, 그것도 10배 청구로 앗아 가는 종류의 것입니다. 당신이 앞에 앉아 있는 Mac에서 풀면, 하나당 1분이 듭니다.

두 번째 시도: 예순여섯 줄

CI가 v1.8.4에서 돌아왔을 때, 워크플로는 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, 태그 트리거가 아니라. 릴리스는 "Run workflow"를 클릭해서 시작합니다. 사람이 모든 유료 macOS 1분에 관문을 겁니다. 첫 버전을 죽인 비용 문제는 단지 트리거를 자동화하지 않음으로써 풀립니다 — 자동화가 적극적으로 해로웠던 유일한 지점입니다.
  • product 입력. 디스패치는 드롭다운 — geekbye 또는 pavleur — 을 받아서, 같은 워크플로가 어느 브랜드든 출시합니다. 화이트라벨 이음매가 릴리스 버튼까지 죽 뻗어 있습니다.
  • 두 개의 병렬 잡. macos-latest 위의 build-mac, windows-latest 위의 build-win이 동시에 돕니다. 여기서 CI의 Windows는 진짜로 새롭습니다; 10월 워크플로는 그것을 한 번도 빌드하지 않았습니다.
  • YAML 안에 서명 로직이 없다. 이것이 요점 전부입니다. 키체인 곡예도, 인증서 임포트 단계도, 정리도 없습니다. Mac 잡은 node scripts/release.js <product> --publish를 실행하고, Windows 잡은 --no-sign을 붙여 같은 것을 실행합니다. 첫 워크플로가 YAML 안에서 손으로 하던 모든 것이, 이제 이미 작동하는 스크립트 안에 삽니다. 워크플로는 오케스트레이터이지 구현이 아닙니다.

두 플랫폼은 정반대의 것을 원하고, 잡들은 그것을 정직하게 비춥니다. macOS는 하드닝된 런타임 아래 빌드하며, electron-builder의 내장 경로(@electron/notarize)를 통해 서명되고 공증되고, 자격 증명은 리포지토리 시크릿에서 공급돼 스크립트가 자동 감지합니다. Windows는 x64와 arm64용으로 미서명 NSIS 인스톨러를 빌드합니다. 하나의 워크플로, 두 개의 잡, "완료"에 대한 완전히 다른 두 개념입니다.

증거는 일어나지 않은 일 안에 있다

순서가 옳았다는 걸 이렇게 압니다: 실행에 이름을 붙이고 어떤 토큰 시크릿의 이름을 고친 같은 날의 두 미세 조정 뒤로, release.yml넉 달 동안 손대지 않았습니다. 서명 실패 핫픽스 없음. 공증 거부 허둥댐 없음. "러너에 네이티브 바이너리가 없다"는 패닉 없음. 코드 서명과 공증 파이프라인 — 요동치기로 가장 악명 높은 종류의 CI — 에게 넉 달의 침묵은 거의 들어 본 적 없는 일입니다.

조용했던 건 그 소음이 이미 더 싼 어딘가에서 일어났기 때문입니다. 보통 CI 로그 탭에서, 10배 청구로, 한 번에 포스 푸시 하나씩 벌어지는 싸움이, 로컬 시대에 개발자들의 Mac 위에서 벌어져 있었습니다. CI는 패키징이 디버깅되는 곳일 필요가 없었습니다. 패키징이 이미 디버깅돼 있었기 때문입니다. 그게 한 줄로 요약한 논지입니다: Mac이 당신을 더는 놀라게 하지 않게 된 뒤에야 빌드를 Mac에서 옮겨라.

여전히 물 수 있는 하나의 결합

전혀 약점이 없는 건 아니고, 그건 미묘해서 짚어 둘 값어치가 있습니다. macOS 앱이 의존하는 Swift 바이너리 — OCR, 화면 캡처, 트랜스크라이버들 — 는 git에 커밋돼 있습니다. 하지만 릴리스는 커밋된 사본을 출시하지 않습니다; 빌드는 그것들을 러너에서 다시 컴파일합니다. 그리고 check-swift-version.js 관문은 컴파일러가 고정된 버전이 아니면 릴리스 전체를 강제로 실패시킵니다. 이 릴리스 시점에 그 핀은 Swift 6.2.x였고, 워크플로 안에 그것을 설치하는 건 아무것도 없습니다 — 잡은 그냥 macos-latest의 기본 Swift가 일치하리라 믿습니다. GitHub이 러너 이미지를 그 핀 너머로 올리는 날, 릴리스는 멈춥니다. 앱에 뭐가 잘못돼서가 아니라, 파이프라인이 자기가 통제하지 못하는 머신 이미지에 조용히 결합돼 있기 때문입니다. 이 얇은 워크플로가 자기가 소유하는 무언가로 밀어 넣지 않은 유일한 구현 세부입니다.

릴리스의 나머지 절반: 더 작은 페이로드

이 릴리스는 언급할 만한 무관한 기능 하나를 실어 왔는데, 뺄셈에 의한 지연 시간 수정의 깔끔한 예이기 때문입니다. Assist Me 액션은 요청마다 백엔드로 필요 이상의 컨텍스트를 보내고 있었습니다. 어떤 커밋이 두 가지를 깎았습니다: 함께 보내던 트랜스크립트 히스토리가 최근 30개에서 최근 15개로 줄었고, 사용자의 프로파일 컨텍스트 파일 — 호출마다 시스템 프롬프트에 주입되는 — 은 잘림 표시와 함께 4,000자로 강제 상한이 걸렸습니다. 직렬화할 게 줄고, 업로드할 게 줄고, 모델이 답하기 시작하기 전에 읽을 게 준다, 그게 이 커밋이 최적화하는 지표입니다: time-to-first-token. 정직을 위해 말하자면: 이 커밋은 속도 향상을 주장하지만 그것을 측정하지 않습니다 — 히스토리에 전/후 숫자가 없으니, 벤치마크로 뒷받침된 승리라기보다 잘 따져 본 다듬기로 여기세요.

이 릴리스가 우리에게 가르쳐 준 세 가지

  1. CI는 이미 작동하는 스크립트 위의 얇은 래퍼다. 66줄짜리 워크플로에는 자체 서명 로직이 없다; 그것은 석 달간의 로컬 릴리스가 이미 디버깅한 스크립트를 호출한다. 메커니즘을 손으로 실행할 수 있는 것 안에 두고, CI에는 그것을 언제 실행할지만 정하게 하라.
  2. 일을 자동화하라, 반드시 트리거는 아니고. 첫 시도는 태그 트리거 릴리스가 10배 청구되는 러너 분을 자동으로 소비했기에 죽었다. 수동 workflow_dispatch는 자동화를 유지하면서 돈이 들던 부분을 제거한다 — 때로는 루프 안의 사람이 곧 기능이다.
  3. 반복이 싼 곳에서 디버깅하라. 개발자의 Mac에서 해결된 모든 패키징 싸움은, CI 로그 탭에서 열 배 가격으로는 결코 일어나지 않는 싸움이다. Mac에서 옮기는 건 처음이 아니라 마지막에.

v1 이야기의 이전 장은 안전망 없이 서른 개 언어를 출시하다(v1.8.3)를 보시고; 전체 궤적은 소프트웨어를 완벽하게 출시하는 것의 해부학을 보세요.

관련 글

클릭 통과 오버레이의 두 가지 실패 모드
Steven
Steven7 분 소요

클릭 통과 오버레이의 두 가지 실패 모드

GeekBye의 창은 모든 것 위에 떠 있으면서, 버튼이 있는 곳을 빼고는 당신의 클릭이 그것을 그대로 통과하게 둡니다. 그것은 양면 계약이고, v1.8.5와 v1.8.14는 각 면이 깨질 때 어떤 모습인지를 보여 줍니다: 한 릴리스에서는 오버레이가 시스템 대화상자를 삼켰고, 다른 하나에서는 당신의 키 입력을 훔쳤습니다. 두 번째를 이긴 해결책은 코드를 지우는 것이었습니다.

엔지니어링
Electron
데스크톱
안전망 없이 서른 개 언어를 출시하다
Steven
Steven6 분 소요

안전망 없이 서른 개 언어를 출시하다

GeekBye v1.8.3은 react-i18next를 배선하고 앱 전체를 30개 언어로 번역했습니다. 라이브러리를 붙이는 일은 판에 박힌 작업이었죠. 흥미로운 부분은 빠져 있던 것입니다: 키 정합성 검사도, 문자열 추출기도 없었기에 — 하드코딩된 영어가 새어 나갔고, 덴마크어 오타가 출시됐으며, 변경 로그는 이 앱이 28개인지 29개인지 30개 언어를 말하는지 자기들끼리도 합의하지 못했습니다.

엔지니어링
i18n
데스크톱
26시간 동안 네 번의 릴리스: 언어 설정 배관 공사와 "브리지 릴리스"
Steven
Steven5 분 소요

26시간 동안 네 번의 릴리스: 언어 설정 배관 공사와 "브리지 릴리스"

GeekBye v1.7.6부터 v1.8.1까지: 노트북 밖으로 한 번도 나가 본 적 없던 언어 설정, 진짜 키바인딩을 보여주는 Cmd+/ 오버레이, 코드 변경이 하나도 없는 버전 — 그리고 자동 업데이트 피드를 옮길 때의 닭과 달걀 문제.

엔지니어링
릴리스
데스크톱