
เอาการรีลีสใส่ไว้ใน CI, สองครั้ง
GeekBye v1.8.4 ย้าย release build ทั้ง macOS และ Windows เข้าไปใน CI สิ่งที่ changelog ไม่ได้บอกคือ เราเคยลองแบบนี้มาก่อนหนึ่งครั้ง, ลบทิ้งหนึ่งเดือนต่อมาเพราะค่า runner, และเพิ่งทำให้มันอยู่ตัวได้ในครั้งที่สอง — เพราะพอถึงตอนนั้น release script ก็ทำงานด้วยมือได้แล้ว
changelog ของ v1.8.4 มีบรรทัดหนึ่งที่ฟังดูเหมือนงานบ้านล้วน ๆ: "release build ตอนนี้มาจาก CI ทั้ง macOS และ Windows แล้ว" มันอ่านเหมือนเรื่องที่เกิดขึ้นครั้งเดียว เรียบร้อย แล้วไม่ต้องคิดถึงอีกเลย มันไม่ใช่ ทีมเคยเอา release ใส่ CI มาแล้วครั้งหนึ่ง — แล้วก็รื้อมันออก รีลีสนี้คือความพยายามครั้งที่สอง และเหตุผลที่มันอยู่ตัวได้คือบทเรียนทางวิศวกรรมตัวจริง ดังนั้นเรามาเล่ามันตามลำดับกัน
ความพยายามครั้งแรก และทำไมมันถึงตาย
ย้อนไปเดือนตุลาคม 2025 มี release.yml อยู่ตัวหนึ่ง มันถูกทริกด้วยการ push tag — ยัด tag แบบ v*.*.* เข้าไป แล้ว macOS runner ก็จะสตาร์ทขึ้นมา, build, และ publish มันตั้งค่า signing ด้วยมือของมันเอง: ขั้นตอนที่ decode signing certificate, สร้างและปลดล็อก keychain ชั่วคราว, import cert เข้าไปเพื่อให้ codesign หามันเจอ, และขั้นตอน cleanup ที่คู่กันตรงท้าย มันทำงานได้ และมันก็ทำเฉพาะ macOS ด้วย — release ของ Windows ไม่ได้อยู่ใน CI เลย
วันที่ 4 พฤศจิกายน มันถูกลบ 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 คิดเงินเวลา runner ของ macOS ที่อัตราสิบเท่าของ Linux และ release ที่ทริกด้วย tag ก็แปลว่าทุก ๆ tag เดียว — รวมทั้งพวกที่ใช้ครั้งเดียวทิ้ง, พวกที่ tag ซ้ำ, พวก "อุ๊ย ลืม bump เวอร์ชัน" — เผา 100 นาทีที่ต้องจ่ายเงินไปเงียบ ๆ automation ที่คุณคุมไม่ได้คือ automation ที่ใช้เงินตอนคุณหลับ ดังนั้น release จึงกลับไปอยู่บน Mac ของ developer อีกสามเดือนครึ่งถัดมา
ที่ที่ปัญหายาก ๆ ถูกแก้จริง ๆ
นี่คือส่วนที่ทำให้ความพยายามครั้งที่สองได้ผล และมันเกิดขึ้น_นอก_ CI ทั้งหมด ในช่วงสามเดือนครึ่งของ release ในเครื่องนั้น scripts/release.js — Node script ธรรมดา ๆ ที่ developer รันด้วยมือ — ดูดซับปัญหา packaging ทุกอย่างที่แอปเคยมีเข้าไป ทีละหนึ่งการแก้:
- การ restore แบบ white-label นี่คือ codebase แบบ white-label ที่ build ทั้ง GeekBye และ Pavleur จาก source เดียว ซึ่งแปลว่า release หนึ่งครั้งจะสลับ field ใน
package.jsonและ asset ไอคอนไปตามผลิตภัณฑ์เป้าหมาย สองการแก้สอน script ให้เอาทุกอย่างกลับที่เดิมภายหลัง เพื่อว่าการ build Pavleur จะไม่ทิ้ง git tree ของคุณให้เลอะไปด้วยตัวตนของ Pavleur - Windows แบบ dual-arch การแก้ที่ build Windows installer ทั้ง x64 และ arm64 แทนที่จะทำแค่ตัวเดียว
- ทางเลี่ยงสำหรับ Windows ที่ไม่ sign electron-builder รุ่นใหม่กว่าเมิน config flag ที่ตั้งใจไว้ปิด signing ของ Windows ดังนั้น script จึงเรียนรู้ที่จะบังคับให้ build แบบไม่ sign ด้วยการตั้ง
CSC_IDENTITY_AUTO_DISCOVERY=falseใน environment แทน — รอยยับที่คุณจะเจอก็ต่อเมื่อคุณชนมันเข้าเท่านั้น - การ auto-detect การ notarize script เรียนรู้ที่จะดู environment ของตัวเองแล้วตัดสินใจ: มี credential สำหรับ signing ไหม? sign มี credential สำหรับ notarize ของ Apple ด้วยไหม? notarize ไม่มีทั้งคู่? build แบบไม่ sign ไม่มี flag ให้ต้องจำ; การมีอยู่ของ secret เหล่านั้น_คือ_การตั้งค่าในตัว
ไม่มีอันไหนที่หวือหวาเลย ทั้งหมดล้วนเป็นเรื่องแบบที่ ถ้ามันทำให้คุณตกใจอยู่ข้างใน CI runner จะกินเวลาคุณไปหนึ่งชั่วโมงต่อความพยายามหนึ่งครั้งกับ push-รอ-พัง-อ่าน log — ที่การคิดเงิน 10 เท่า พอแก้บน 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." ทุกการตัดสินใจด้านดีไซน์ในนั้นคือแผลเป็นจากความพยายามครั้งแรก:
workflow_dispatch, ไม่ใช่ทริกด้วย tag คุณเริ่ม release ด้วยการคลิก "Run workflow" มนุษย์เป็นด่านกั้นทุก ๆ นาที macOS ที่ต้องจ่ายเงิน ปัญหาค่าใช้จ่ายที่ฆ่าเวอร์ชันแรกถูกแก้ด้วยการแค่ไม่ทำ trigger ให้เป็นอัตโนมัติ — จุดเดียวที่ automation กำลังทำร้ายอย่างจริงจัง- input ชื่อ
productdispatch รับ dropdown —geekbyeหรือpavleur— ดังนั้น workflow เดียวกัน ship ได้ทั้งสองแบรนด์ ตะเข็บ white-label ทอดยาวออกไปจนถึงปุ่ม release เลยทีเดียว - สอง job คู่ขนาน
build-macบนmacos-latest,build-winบนwindows-latest, รันพร้อมกัน Windows ใน CI เป็นของใหม่จริง ๆ ตรงนี้; workflow เดือนตุลาคมไม่เคย build มันเลย - ไม่มี signing logic ใน YAML นี่คือประเด็นทั้งหมด ไม่มีการเล่นกล keychain, ไม่มีขั้นตอน import cert, ไม่มี cleanup job ฝั่ง Mac รัน
node scripts/release.js <product> --publishและ job ฝั่ง Windows รันอันเดียวกันด้วย--no-signทุกอย่างที่ workflow แรกทำด้วยมือใน YAML ตอนนี้ไปอยู่ใน script ที่ทำงานได้อยู่แล้ว workflow เป็น orchestrator ไม่ใช่ implementation
สองแพลตฟอร์มต้องการสิ่งที่ตรงข้ามกัน และ job ก็สะท้อนมันอย่างซื่อสัตย์ macOS build ภายใต้ hardened runtime, sign และ notarize ผ่านเส้นทาง built-in ของ electron-builder (@electron/notarize) โดย credential ถูกป้อนมาจาก secret ใน repository และถูก auto-detect โดย script Windows build NSIS installer ที่ไม่ sign สำหรับ x64 และ arm64 workflow เดียว, สอง job, สองแนวคิดของคำว่า "เสร็จ" ที่ต่างกันคนละเรื่อง
หลักฐานอยู่ในสิ่งที่ไม่ได้เกิดขึ้น
นี่คือวิธีที่คุณรู้ว่าลำดับมันถูกต้อง: หลังจากการปรับเล็ก ๆ สองครั้งในวันเดียวกันที่ตั้งชื่อให้ run และแก้ชื่อ token secret แล้ว release.yml ก็ไม่ถูกแตะอีกเลยเป็นเวลา สี่เดือน ไม่มี hotfix เรื่อง signing ล้ม ไม่มีการวิ่งวุ่นเพราะ notarize ถูกปฏิเสธ ไม่มีอาการแพนิก "native binary หายไปบน runner" สำหรับ pipeline แบบ code-signing-and-notarization — CI ประเภทที่ขึ้นชื่อเรื่องกวัดแกว่งที่สุด — ความเงียบสี่เดือนแทบเป็นเรื่องไม่เคยได้ยินมาก่อน
มันเงียบเพราะเสียงรบกวนเกิดขึ้นในที่ที่ถูกกว่าไปแล้ว การต่อสู้ที่ปกติมักเล่นออกมาในแท็บ log ของ CI, ที่การคิดเงิน 10 เท่า, ทีละหนึ่ง force-push, ได้เล่นจบไปแล้วบน Mac ของเหล่า developer ในยุคทำในเครื่อง CI ไม่ต้องเป็นที่ที่ packaging ถูก debug เพราะ packaging ถูก debug ไปแล้ว นั่นคือใจความในบรรทัดเดียว: ย้าย build ออกจาก Mac ของคุณก็ต่อเมื่อ Mac ของคุณเลิกทำให้คุณตกใจแล้วเท่านั้น
coupling หนึ่งเดียวที่ยังกัดได้
มันไม่ได้ไร้จุดอ่อนไปเสียทั้งหมด และคุ้มค่าที่จะชี้ให้เห็นเพราะมันแนบเนียน Swift binary ที่แอป macOS พึ่งพา — OCR, screen capture, พวก transcriber — ถูก commit ไว้ใน git แต่ release ไม่ได้ ship สำเนาที่ commit ไว้; build จะ recompile มันใหม่บน runner และด่าน check-swift-version.js จะทำให้ release ล้มทั้งหมดถ้า compiler ไม่ใช่เวอร์ชันที่ pin ไว้ ณ รีลีสนี้ pin นั้นคือ Swift 6.2.x และไม่มีอะไรใน workflow ที่ติดตั้งมัน — job แค่เชื่อใจว่า Swift เริ่มต้นของ macos-latest จะตรงกัน วันที่ GitHub ดัน runner image ของมันข้ามเลย pin ไป release จะหยุด ไม่ใช่เพราะมีอะไรผิดกับแอป แต่เพราะ pipeline ผูกติดอยู่กับ machine image ที่มันคุมไม่ได้อย่างเงียบ ๆ มันคือรายละเอียด implementation หนึ่งเดียวที่ workflow บางเบานี้ไม่ได้กดลงไปในอะไรที่มันเป็นเจ้าของเอง
อีกครึ่งของรีลีส: payload ที่เล็กลง
รีลีสนี้พาฟีเจอร์ที่ไม่เกี่ยวกันมาหนึ่งอย่างที่ควรค่าแก่การเอ่ยถึง เพราะมันเป็นตัวอย่างสะอาดหมดจดของการแก้ latency ด้วยการลบทิ้ง แอ็กชัน Assist Me ส่ง context ให้ backend มากกว่าที่มันต้องการในทุก request commit หนึ่งตัดสองอย่าง: ประวัติ transcript ที่ส่งไปด้วยลดจาก 30 รายการหลังสุดเหลือ 15 รายการหลังสุด และไฟล์ context โปรไฟล์ของผู้ใช้ — ที่ถูกฉีดเข้า system prompt ใน_ทุก_ครั้งที่เรียก — ถูกจำกัดตายตัวที่ 4,000 ตัวอักษรพร้อมเครื่องหมาย truncation มีอะไรให้ serialize น้อยลง, ให้ upload น้อยลง, ให้ model อ่านก่อนจะเริ่มตอบน้อยลง ซึ่งคือ metric ที่ commit นี้ปรับให้ดีที่สุด: time-to-first-token เพื่อความซื่อสัตย์: commit นี้อ้างว่าเร็วขึ้นแต่ไม่ได้วัดมัน — ไม่มีตัวเลข ก่อน/หลัง ในประวัติ ดังนั้นจงมองมันเป็นการเฉือนที่มีเหตุผลรองรับดี มากกว่าจะเป็นชัยชนะที่มี benchmark ยืนยัน
สามสิ่งที่รีลีสนี้สอนเรา
- CI คือ wrapper บาง ๆ ที่ครอบอยู่บน script ที่ทำงานได้อยู่แล้ว workflow 66 บรรทัดไม่มี signing logic ของตัวเอง; มันเรียก script ที่สามเดือนของ release ในเครื่อง debug จนเรียบร้อยแล้ว วางกลไกไว้ในอะไรที่คุณรันด้วยมือได้ แล้วให้ CI แค่ตัดสินใจว่าจะรันมัน_เมื่อไหร่_
- ทำงานให้เป็นอัตโนมัติ ไม่จำเป็นต้องทำ trigger เป็นอัตโนมัติ ความพยายามครั้งแรกตายเพราะ release ที่ทริกด้วย tag เผานาที runner ที่คิดเงิน 10 เท่าไปเอง
workflow_dispatchแบบ manual เก็บ automation ไว้และเอาส่วนที่กินเงินออกไป — บางครั้งมนุษย์ในลูปนั่นแหละคือฟีเจอร์ - debug ในที่ที่การวนซ้ำถูก ทุกการต่อสู้เรื่อง packaging ที่คลี่คลายบน Mac ของ developer คือการต่อสู้ที่จะไม่มีวันเกิดขึ้นในแท็บ log ของ CI ที่ราคาสิบเท่า ย้ายออกจาก Mac เป็นอย่างสุดท้าย ไม่ใช่อย่างแรก
สำหรับบทก่อนหน้าของเรื่องยุค v1 ดูส่งมอบสามสิบภาษาโดยไม่มีตาข่ายรองรับ (v1.8.3); และสำหรับภาพรวมทั้งเส้นเรื่อง ดูกายวิภาคของการส่งมอบซอฟต์แวร์ให้สมบูรณ์แบบ