Steven
Steven10 phút đọc

Đưa Bản Phát Hành Vào CI, Hai Lần

GeekBye v1.8.4 chuyển các bản build phát hành cho macOS và Windows vào CI. Điều changelog không nói là chúng tôi đã thử việc này một lần trước đó, xóa nó đi một tháng sau vì chi phí runner, và chỉ khiến nó trụ được ở lần thứ hai — bởi tới khi ấy release script đã chạy được bằng tay rồi.

Kỹ thuật
CI/CD
Desktop
Bản phát hành GeekBye
Đưa Bản Phát Hành Vào CI, Hai Lần

Changelog của v1.8.4 có một dòng nghe như thuần túy việc nhà: "Các bản build phát hành giờ đến từ CI cho cả macOS và Windows." Nó đọc lên như loại việc xảy ra một lần, gọn ghẽ, rồi chẳng ai nghĩ tới nữa. Nó không phải vậy. Đội ngũ đã từng đưa bản phát hành vào CI một lần — và giật nó ra. Bản phát hành này là lần thử thứ hai, và lý do nó trụ được mới chính là bài học kỹ thuật thật sự, nên hãy kể theo thứ tự.

Lần thử đầu tiên, và vì sao nó chết

Quay về tháng Mười 2025, có một release.yml. Nó được kích bằng tag push — đẩy một tag v*.*.* là một macOS runner sẽ khởi lên, build, và publish. Nó tự làm phần thiết lập ký bằng tay: một bước decode chứng chỉ ký, tạo và mở khóa một keychain tạm, import cert vào để codesign tìm thấy nó, và một bước dọn dẹp tương ứng ở cuối. Nó chạy được. Nó cũng chỉ dành cho macOS — các bản phát hành Windows hoàn toàn không nằm trong CI.

Ngày 4 tháng Mười Một, nó bị xóa. Thông điệp commit thẳng thắn một cách bất thường về lý do: "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." Đó là toàn bộ câu chuyện của lần thử đầu tiên gói gọn trong một câu. GitHub tính giờ runner macOS gấp mười lần Linux, và một bản phát hành kích bằng tag nghĩa là mỗi tag đơn lẻ — kể cả những tag dùng một lần rồi bỏ, những tag đánh lại, những tag "ối, quên bump phiên bản" — đều âm thầm ngốn một trăm phút tính tiền. Tự động hóa mà bạn không kiểm soát là tự động hóa tiêu tiền trong lúc bạn ngủ. Nên việc phát hành quay về máy Mac của một developer trong ba tháng rưỡi kế tiếp.

Nơi những bài toán khó thực sự được giải

Đây là phần khiến lần thử thứ hai chạy được, và nó xảy ra hoàn toàn bên ngoài CI. Trong ba tháng rưỡi phát hành tại chỗ ấy, scripts/release.js — cái script Node bình thường mà một developer chạy bằng tay — đã hấp thụ mọi bài toán đóng gói mà ứng dụng từng gặp, mỗi lần một cách sửa:

  • Khôi phục white-label. Đây là một codebase white-label build cả GeekBye lẫn Pavleur từ một nguồn, nghĩa là một lần phát hành sẽ tráo các trường trong package.json và asset icon theo sản phẩm đích. Hai cách sửa dạy cho script biết đặt mọi thứ về chỗ cũ sau đó, để build Pavleur không để lại cây git của bạn nhem nhuốc danh tính của Pavleur.
  • Windows dual-arch. Một cách sửa để build cả installer Windows x64 lẫn arm64 thay vì một cái.
  • Cách lách cho Windows không ký. electron-builder mới hơn phớt lờ cái config flag lẽ ra để tắt việc ký Windows, nên script học cách ép một bản build không ký bằng cách đặt CSC_IDENTITY_AUTO_DISCOVERY=false trong environment thay thế — một nếp gấp bạn chỉ tìm ra khi đâm vào nó.
  • Tự động phát hiện việc công chứng. Script học cách nhìn environment của mình rồi quyết định: có chứng danh để ký không? Ký. Có cả chứng danh công chứng của Apple không? Công chứng. Không có cái nào? Build không ký. Không có flag nào phải nhớ; sự hiện diện của các secret chính là cấu hình.

Chẳng cái nào trong đó hào nhoáng cả. Tất cả đều là loại việc mà, nếu nó làm bạn bất ngờ bên trong một CI runner, sẽ ngốn của bạn một giờ push-chờ-hỏng-đọc-log mỗi lần thử — với mức tính tiền 10x. Giải trên một chiếc Mac mà bạn đang ngồi trước mặt, mỗi cái tốn một phút.

Lần thử thứ hai: sáu mươi sáu dòng

Khi CI trở lại ở v1.8.4, workflow dài 66 dòng, và phẩm chất định nghĩa nó là nó làm ít đến mức nào. Commit mô tả nó một cách mộc mạc: "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." Mỗi quyết định thiết kế trong đó là một vết sẹo từ lần thử đầu:

  • workflow_dispatch, không phải kích bằng tag. Bạn khởi một bản phát hành bằng cách bấm "Run workflow." Một con người canh cổng mỗi phút macOS tính tiền. Bài toán chi phí giết chết phiên bản đầu được giải chỉ bằng cách đơn giản là không tự động hóa cái trigger — chỗ duy nhất mà tự động hóa đang chủ động gây hại.
  • Một input product. Dispatch nhận một dropdown — geekbye hoặc pavleur — nên cùng một workflow ship được cả hai thương hiệu. Đường nối white-label chạy tuốt ra tới cái nút phát hành.
  • Hai job song song. build-mac trên macos-latest, build-win trên windows-latest, chạy cùng lúc. Windows trong CI thật sự là điều mới ở đây; workflow tháng Mười chưa từng build nó.
  • Không có logic ký trong YAML. Đây chính là toàn bộ điểm cốt lõi. Không có màn tung hứng keychain, không có bước import cert, không có dọn dẹp. Job Mac chạy node scripts/release.js <product> --publish và job Windows chạy đúng thứ đó với --no-sign. Mọi thứ workflow đầu tiên từng làm bằng tay trong YAML nay sống trong cái script vốn đã chạy được. Workflow là một bộ điều phối, không phải một phần triển khai.

Hai nền tảng muốn những thứ trái ngược, và các job phản ánh điều đó một cách trung thực. macOS build dưới hardened runtime, được ký và công chứng qua đường built-in của electron-builder (@electron/notarize), với chứng danh cấp từ secret của repository và được script tự phát hiện. Windows build installer NSIS không ký cho x64 và arm64. Một workflow, hai job, hai khái niệm "xong" hoàn toàn khác nhau.

Bằng chứng nằm ở điều đã không xảy ra

Đây là cách bạn biết thứ tự là đúng: sau hai lần chỉnh nhỏ cùng ngày đặt tên cho các lần chạy và sửa tên một token secret, release.yml không bị đụng tới lần nào nữa suốt bốn tháng. Không hotfix lỗi ký. Không cuống cuồng vì bị từ chối công chứng. Không hoảng loạn "native binary biến mất trên runner." Với một đường ống ký-code-và-công-chứng — thể loại CI khét tiếng nhất về chuyện quăng quật — bốn tháng im lặng gần như là chuyện chưa từng nghe.

Nó im ắng vì tiếng ồn đã xảy ra ở đâu đó rẻ hơn rồi. Những cuộc chiến thường diễn ra trong một tab log CI, ở mức tính tiền 10x, mỗi lần một cú force-push, đã diễn ra trên máy Mac của các developer trong thời kỳ tại chỗ. CI không phải là nơi việc đóng gói được debug, bởi việc đóng gói đã được debug rồi. Đó là luận điểm gói trong một dòng: chỉ chuyển bản build ra khỏi máy Mac sau khi máy Mac của bạn đã thôi làm bạn bất ngờ.

Mối kết dính duy nhất vẫn có thể cắn

Nó không hoàn toàn không có điểm yếu, và điều đó đáng nêu tên vì nó tinh vi. Các Swift binary mà ứng dụng macOS phụ thuộc vào — OCR, chụp màn hình, các transcriber — được commit vào git. Nhưng bản phát hành không ship các bản sao đã commit; bản build biên dịch lại chúng trên runner. Và một cổng check-swift-version.js sẽ làm cả bản phát hành thất bại cứng nếu trình biên dịch không phải phiên bản đã ghim. Ở bản phát hành này, cái ghim đó là Swift 6.2.x, và chẳng có gì trong workflow cài đặt nó — job chỉ đơn giản tin rằng Swift mặc định của macos-latest sẽ khớp. Cái ngày GitHub đẩy image runner của họ vượt qua cái ghim, bản phát hành dừng lại, không phải vì có gì sai với ứng dụng, mà vì đường ống bị kết dính một cách lặng lẽ vào một image máy nó không kiểm soát. Đó là chi tiết triển khai duy nhất mà workflow mỏng này đã không đẩy xuống thành thứ nó sở hữu.

Nửa còn lại của bản phát hành: payload nhỏ hơn

Bản phát hành chở theo một tính năng không liên quan đáng nhắc, vì nó là một ví dụ sạch sẽ của việc sửa độ trễ bằng phép trừ. Hành động Assist Me đang gửi cho backend nhiều context hơn nó cần ở mỗi request. Một commit cắt hai thứ: lịch sử transcript gửi kèm giảm từ 30 mục gần nhất xuống 15 mục gần nhất, và các file context hồ sơ của người dùng — được tiêm vào system prompt ở mỗi lần gọi — bị giới hạn cứng ở 4.000 ký tự kèm một dấu cắt bớt. Ít thứ để serialize hơn, ít thứ để upload hơn, ít thứ để model đọc trước khi bắt đầu trả lời hơn, và đó chính là chỉ số mà commit này tối ưu: time-to-first-token. Vì sự trung thực: commit này tuyên bố có tăng tốc nhưng không đo nó — không có con số trước/sau nào trong lịch sử, nên hãy xem nó như một sự cắt tỉa có lập luận vững chứ không phải một chiến thắng đã được benchmark.

Ba điều bản phát hành này dạy chúng tôi

  1. CI là một lớp bọc mỏng đặt trên một script vốn đã chạy được. Workflow 66 dòng không có logic ký của riêng nó; nó gọi một script mà ba tháng phát hành tại chỗ đã debug xong. Hãy đặt cơ chế vào một thứ bạn chạy được bằng tay, và để CI chỉ quyết định khi nào chạy nó.
  2. Tự động hóa công việc, không nhất thiết là cái trigger. Lần thử đầu chết vì các bản phát hành kích bằng tag tự động ngốn phút runner tính tiền 10x. workflow_dispatch thủ công giữ lại phần tự động hóa và loại bỏ phần đang tiêu tiền — đôi khi con người trong vòng lặp mới chính là tính năng.
  3. Debug ở nơi lặp lại rẻ. Mỗi cuộc chiến đóng gói được giải trên máy Mac của một developer là một cuộc chiến không bao giờ xảy ra trong một tab log CI với giá gấp mười lần. Chuyển ra khỏi Mac là bước cuối, không phải bước đầu.

Về chương trước trong câu chuyện v1, xem Ship Ba Mươi Ngôn Ngữ Mà Không Có Lưới An Toàn (v1.8.3); và cho toàn bộ vòng cung, xem giải phẫu việc xuất xưởng phần mềm đến độ hoàn hảo.

Bài Viết Liên Quan

Hai Chế Độ Hỏng Của Một Lớp Phủ Cho Click Xuyên Qua
Steven
Steven11 phút đọc

Hai Chế Độ Hỏng Của Một Lớp Phủ Cho Click Xuyên Qua

Cửa sổ của GeekBye nổi lên trên mọi thứ và để các cú click của bạn xuyên qua nó — trừ những chỗ nó có nút. Đó là một hợp đồng hai mặt, và v1.8.5 cùng v1.8.14 chính là dáng vẻ của lúc mỗi mặt vỡ: một bản phát hành lớp phủ nuốt mất một dialog hệ thống, một bản khác nó cướp các phím gõ của bạn. Cách sửa thắng cuộc cho vấn đề thứ hai là xóa code.

Kỹ thuật
Electron
Desktop
Ship Ba Mươi Ngôn Ngữ Mà Không Có Lưới An Toàn
Steven
Steven10 phút đọc

Ship Ba Mươi Ngôn Ngữ Mà Không Có Lưới An Toàn

GeekBye v1.8.3 đấu nối react-i18next và dịch toàn bộ ứng dụng sang 30 ngôn ngữ. Phần thư viện chỉ là việc thường ngày. Phần thú vị nằm ở những gì còn thiếu: không có kiểm tra tính đồng nhất của key và không có công cụ trích xuất chuỗi — nên tiếng Anh hardcode lọt lưới, một lỗi chính tả tiếng Đan Mạch bị ship, và cái changelog thì không thống nhất nổi rằng ứng dụng nói 28, 29, hay 30 ngôn ngữ.

Kỹ thuật
i18n
Desktop
Bốn bản phát hành trong 26 tiếng: đi ống cho cài đặt ngôn ngữ và một "bridge release"
Steven
Steven7 phút đọc

Bốn bản phát hành trong 26 tiếng: đi ống cho cài đặt ngôn ngữ và một "bridge release"

GeekBye v1.7.6 đến v1.8.1: một cài đặt ngôn ngữ chưa từng rời khỏi chiếc laptop, một overlay Cmd+/ hiển thị đúng keybinding thật của bạn, một phiên bản không đổi một dòng code nào — và bài toán con gà quả trứng khi di trú feed tự cập nhật.

Kỹ thuật
Phát hành
Desktop