Skip to main content

elf-cicd-desktop

Elf Express Tauri 桌面應用的 GitHub Actions 發佈流程規範(Windows / macOS universal / Ubuntu 三平台矩陣、updater 簽章金鑰檢查、Apple 公證憑證檢查、tauri-action draft release、 latest.json 驗證、中英雙語 release 說明)。當任務涉及撰寫或修改 `.github/workflows/release.yml` 的桌面建置、tauri-apps/tauri-action、TAURI_SIGNING_PRIVATE_KEY、APPLE_* secrets、 latest.json / updater manifest、draft release 發佈,或排查桌面 release 失敗時觸發。

소스 정보

저장소
elf-express/skillshare-source
최근 소스 활동
2026년 10월 11일 13:45
감지된 SKILL.md 언어
중국어
스타
0
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
3 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
elf-cicd-desktop
description
Elf Express Tauri 桌面應用的 GitHub Actions 發佈流程規範(Windows / macOS universal / Ubuntu 三平台矩陣、updater 簽章金鑰檢查、Apple 公證憑證檢查、tauri-action draft release、 latest.json 驗證、中英雙語 release 說明)。當任務涉及撰寫或修改 `.github/workflows/release.yml` 的桌面建置、tauri-apps/tauri-action、TAURI_SIGNING_PRIVATE_KEY、APPLE_* secrets、 latest.json / updater manifest、draft release 發佈,或排查桌面 release 失敗時觸發。
metadata
{"version":"1.1.0","owner":"Elf Express"}
# Elf Express 桌面發佈流程(Tauri release pipeline) > 參考實作:`TypingTrainer/.github/workflows/release.yml`(v0.1.9 / v0.1.10 已實機驗證:Windows 自動更新、 > macOS Apple Silicon 無 Gatekeeper 警告)。本 skill 以團隊標準(Node 24.18、pnpm 11.x)改寫。 相關 skill: - `elf-tauri` — `src-tauri/` 專案慣例、updater 設定、簽章金鑰產生與保管(`docs/signing-and-keys.md`) - `elf-cicd-versioning` — 版號、在 `release` 上打 tag、本流程的觸發與 `verify` job - `elf-cicd-docker` — 同一個 `release.yml` 內的 `docker` job(GHCR 映像) - `elf-cicd-frontend` / `elf-cicd-backend` — PR 階段 CI(lint、test、build) - `desktop/tauri-v2` — 第三方通用 Tauri 2 知識(本 skill 不重複) --- ## 1. 何時使用 - 新專案要加上桌面安裝檔發佈 → 直接複製 [`templates/release.yml`](templates/release.yml) - 修改 release.yml 的 `desktop` / `updater-manifest` job - release 失敗排查:簽章金鑰為空、latest.json 缺平台、draft 看不到、macOS 打不開 - 撰寫 / 修改 GitHub Release 頁面說明文字([`templates/release-body.md`](templates/release-body.md)) 不適用:PR CI(→ `elf-cicd-frontend`)、版號與 tag(→ `elf-cicd-versioning`)、容器映像(→ `elf-cicd-docker`)。 --- ## 2. 固定規則 ### 觸發與呼叫 1. **MUST** 只保留兩種觸發:`push: tags: ['v*']`、`workflow_dispatch`(input `tag`),並有 `verify` job,`desktop` job `needs: verify`(範本見 `elf-cicd-versioning` 3.2)。 WHY:tag 由人在 `release` 上推送,會直接觸發 `push: tags`;`verify` 擋下版號不符或不在 `release` 上的 tag。`workflow_call` 只是給已移除的 `version.yml` 用。 2. **MUST** 所有 checkout / tagName 都寫 `${{ inputs.tag || github.ref }}` / `${{ inputs.tag || github.ref_name }}`。 WHY:兩種觸發來源的 tag 放在不同欄位。 3. (併入規則 13,編號保留)secret 檢查見規則 13。 ### 矩陣 4. **MUST** 矩陣固定三個平台:`windows-latest`、`macos-latest`(`args: '--target universal-apple-darwin'`)、`ubuntu-latest`。 5. **MUST** `strategy.max-parallel: 1`、`fail-fast: false`。 WHY:tauri-action 以「讀取 → 合併 → 刪除 → 重傳」更新 `latest.json`,**沒有鎖**。平行跑時最後寫入者勝出,其他平台從清單消失,但三個 job 都顯示綠燈。 6. **MUST** macOS 的 rust toolchain 安裝 `aarch64-apple-darwin,x86_64-apple-darwin` 兩個 target。WHY:universal 需要兩個架構。 7. **MUST** Ubuntu 安裝 `libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf`。WHY:Tauri 2 使用 webkit2gtk **4.1**,裝 4.0 會編譯失敗。 8. **MUST NOT** 自行加入 ARM 的 Windows / Linux 列(`windows-11-arm`、`ubuntu-24.04-arm`),除非需求單明確要求。 WHY:`max-parallel: 1` 無法放寬,每多一列約多 8 分鐘;目前無使用者需求。 ### 工具鏈(團隊標準,覆蓋參考專案的 npm) 9. **MUST** 順序為 `pnpm/action-setup@v4` → `actions/setup-node@v5`(`node-version-file: .nvmrc`、`cache: pnpm`)。`.nvmrc` 內容 `24.18`,規範見 `elf-cicd-frontend`;**MUST NOT** 寫死 `node-version`。 WHY:`cache: pnpm` 需要 pnpm 已在 PATH,順序反了 setup-node 會報找不到 pnpm。 10. **MUST NOT** 在 `pnpm/action-setup` 寫 `version:`;版本由根目錄 `package.json` 的 `"packageManager": "pnpm@<PNPM_VERSION>"` 決定。MUST NOT 使用 corepack。 WHY:兩處都寫且不一致時 action 直接失敗;單一來源也保證 CI 與本機一致。 11. **MUST** 安裝依賴用 `pnpm install --frozen-lockfile`(對應舊的 `npm ci`)。WHY:lockfile 與 package.json 不一致時要失敗,而不是悄悄改 lockfile。 12. **MUST** 使用 `swatinem/rust-cache@v2`,`workspaces: src-tauri`。WHY:Rust 編譯是最慢的步驟。 ### 簽章與憑證 13. **MUST** 在 `tauri-action` 前放「Require the signing key」step:`TAURI_SIGNING_PRIVATE_KEY` 為空就 `exit 1`。 WHY:空金鑰會先編譯十分鐘,最後報 `Missing comment in secret key`,看起來像金鑰格式錯,其實是沒傳進來。TypingTrainer 當時是 `workflow_call` 的呼叫端沒寫 `secrets: inherit`,金鑰以空字串送進來,連掛三次 release;現行流程(人推 tag 直接觸發)下等價的原因是 secret 沒設定或名稱不符。 14. **MUST** macOS 放「Check the Apple credentials」step,缺憑證時只 `::warning::`,**不** fail。 WHY:未簽章的 macOS 版仍可右鍵 → 打開使用;但必須在建置前就讓人看到警告,而不是等使用者回報打不開。 15. **MUST** `tauri-action` 的 `env` 帶入全部簽章變數(見下方 Secrets 表)。WHY:tauri-action 只從環境變數讀取。 16. **MUST NOT** 在 workflow、log、release body 寫出任何 secret 值;只能用 `${{ secrets.NAME }}`。 ### tauri-action 參數 17. **MUST** 使用 `tauri-apps/tauri-action@v0`,並固定以下參數: | 參數 | 值 | WHY | |---|---|---| | `releaseDraft` | `true` | 先人工檢查資產再公開 | | `prerelease` | `false` | 更新端點只看 latest release | | `retryAttempts` | `3` | updater JSON 合併時的上傳沒有 backoff,舊 asset id 會先 404 | | `updaterJsonPreferNsis` | `true` | `windows-x86_64` 指向 NSIS `-setup.exe`,與使用者實際安裝的格式一致(從 `.exe` 安裝卻用 `.msi` 更新會變成兩套安裝紀錄);明確寫出是因為 action 預設值文件註明可能改變 | | `updaterJsonKeepUniversal` | `false` | universal 已展開成 `darwin-aarch64` + `darwin-x86_64`,多留一個沒人測的路徑 | | `releaseName` | `'<AppName> ${{ inputs.tag \|\| github.ref_name }}'` | — | | `releaseBody` | `${{ steps.notes.outputs.body }}`(由「Read the release notes」step 讀 `.github/release-body.md`) | 給從搜尋引擎進來的一般使用者看;內文只有一個來源 | 18. **MUST** job 權限 `permissions: contents: write`。WHY:要建立 release 與上傳資產。 ### 驗證與發佈 19. **MUST** 保留 `updater-manifest` job(`needs: desktop`),斷言 `latest.json` 含四個 key: `windows-x86_64`、`darwin-aarch64`、`darwin-x86_64`、`linux-x86_64`。WHY:綠燈不代表清單完整(見規則 5)。 20. **MUST** `updater-manifest` 的權限是 `contents: write`,**不是** `read`。 WHY:此時 release 還是 draft,唯讀 token 看不到 draft,會報 `release not found`。 21. **MUST** 人工發佈:`gh release edit vX.Y.Z --draft=false --latest`。 WHY:`/releases/latest/download/latest.json` 只解析已公開的 release;draft 對 App 而言等於清單不存在。 22. **MUST** 更新 release body 時 Windows / macOS / Linux 表格、「其他檔案」說明、「只提供 x86-64」聲明三段都保留,且中英並列。 23. **MUST** release 說明只有一個來源:專案的 `.github/release-body.md`(複製自 [`templates/release-body.md`](templates/release-body.md)),由 release.yml 的「Read the release notes」step 讀入。**MUST NOT** 在 release.yml 內嵌另一份 `releaseBody` 文字。 WHY:兩份內文必然走鐘,最後改了一份、發出去的是另一份。 24. **MUST** 專案同時發佈容器映像(release.yml 內有 `docker` job,見 `elf-cicd-docker`)時保留 release-body.md 最後的 GHCR 行,並把 `<owner>/<repo>` 換成**小寫**;沒有容器映像時刪除該行與其上方的 `---`。 25. **MUST** `tauri-action` step 的 `env` 設 `VITE_USE_MOCK: 'false'` 與 `VITE_API_BASE_URL: ${{ vars.VITE_API_BASE_URL }}`,並在 repo **Settings → Secrets and variables → Actions → Variables** 建立 `VITE_API_BASE_URL`(正式 API 的完整網址,例如 `https://api.example.com/api`)。 WHY:桌面版由 `beforeBuildCommand` 建置,不經過 `web.Dockerfile`,那裡的 `ARG VITE_USE_MOCK=false` 管不到;`VITE_*` 在建置時就寫死,漏設的話發出去的安裝檔會用假資料、而且打到不存在的相對路徑 `/api`(參考專案 TypingTrainer 就是這樣)。見 `elf-tauri` 規則 13b。 --- ## 3. 標準範本 | 檔案 | 用途 | |---|---| | [`templates/release.yml`](templates/release.yml) | 完整 `.github/workflows/release.yml`(desktop + updater-manifest),替換 `<AppName>` 即可 | | [`templates/release-body.md`](templates/release-body.md) | 放到 `.github/release-body.md`:雙語 release 說明的**唯一來源**;替換 `<AppName>`;有容器映像時保留最後一行 GHCR 連結(`<owner>/<repo>` 小寫),沒有就刪除 | ### 前置條件(專案端) ```jsonc // package.json(根目錄) { "packageManager": "pnpm@<PNPM_VERSION>", // 待確認:pnpm 11.x 或 12 的確切版本 "engines": { "node": ">=24.18" }, "scripts": { "tauri": "tauri", "build": "<建置前端的指令>" // tauri.conf.json 的 beforeBuildCommand 會呼叫 `pnpm build` } } ``` `src-tauri/tauri.conf.json` 必須 `"bundle": { "createUpdaterArtifacts": true }`,且 `plugins.updater.pubkey` 為真實公鑰(見 `elf-tauri`)。 ### Secrets(名稱固定,值絕不寫進 repo) | Secret | 必要性 | 建立方式 | |---|---|---| | `TAURI_SIGNING_PRIVATE_KEY` | **必要** | `pnpm tauri signer generate -w ~/.tauri/<app>.key` → `gh secret set TAURI_SIGNING_PRIVATE_KEY < ~/.tauri/<app>.key` | | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | 金鑰有密碼時 | `gh secret set TAURI_SIGNING_PRIVATE_KEY_PASSWORD`(團隊建議金鑰不設密碼) | | `APPLE_CERTIFICATE` | 選用(macOS 簽章) | `openssl base64 -A -in Certificates.p12` 的輸出 | | `APPLE_CERTIFICATE_PASSWORD` | 選用 | 匯出 `.p12` 時設定的密碼 | | `APPLE_SIGNING_IDENTITY` | 選用 | `security find-identity -v -p codesigning` → `Developer ID Application: …` | | `APPLE_ID` | 選用(公證) | Apple 帳號 email | | `APPLE_PASSWORD` | 選用(公證) | account.apple.com 產生的 **App 專用密碼**(不是帳號密碼) | | `APPLE_TEAM_ID` | 選用(公證) | Apple Developer 會員頁面 | | `GITHUB_TOKEN` | 自動 | 不需建立 | 完整步驟(CSR、Developer ID Application 憑證、備份金鑰)見 `elf-tauri` 的 `references/signing-and-keys.md`。 ### 發佈後確認 ```bash gh release edit vX.Y.Z --draft=false --latest curl -sSL -o /dev/null -w '%{http_code}\n' \ https://github.com/<owner>/<repo>/releases/latest/download/latest.json # 200 = 已安裝的 App 看得到更新;404 = 還是 draft ``` --- ## 4. 檢查清單 提交 release.yml 前: - [ ] 只有 push tag / workflow_dispatch 兩種觸發;有 `verify` job,`desktop` job `needs: verify` - [ ] `max-parallel: 1`、`fail-fast: false` - [ ] macOS 列 `args: '--target universal-apple-darwin'` + 兩個 rust target - [ ] `pnpm/action-setup@v4` 在 `setup-node` 之前,且沒有 `version:` - [ ] `node-version-file: .nvmrc`、`cache: pnpm`、`pnpm install --frozen-lockfile` - [ ] 「Require the signing key」step 存在且會 `exit 1` - [ ] macOS「Check the Apple credentials」step 只警告不失敗 - [ ] `updaterJsonPreferNsis: true`、`updaterJsonKeepUniversal: false`、`retryAttempts: 3`、`releaseDraft: true` - [ ] `updater-manifest` job 存在、權限 `contents: write`、斷言四個平台 key - [ ] `<AppName>` 已全部替換;`.github/release-body.md` 存在、中英雙語完整,release.yml 沒有內嵌的 release 文字 - [ ] 有 `docker` job ⇔ release-body.md 有 GHCR 行(小寫 `<owner>/<repo>`) 首次發佈後: - [ ] `updater-manifest` 綠燈 - [ ] `gh release edit … --draft=false --latest` 已執行,`latest.json` 回 200 - [ ] 至少一台 Windows 實機「檢查更新 → 下載安裝 → 重新啟動」成功 - [ ] macOS 實機從 `universal.dmg` 開啟無 Gatekeeper 警告(已設 Apple 憑證時) --- ## 5. 常見錯誤 | 症狀 | 原因 | 修正 | |---|---|---| | CI 報 `Missing comment in secret key` | 金鑰**是空的**(Tauri 先 base64 解碼再驗證,空字串當成格式錯) | 確認 `gh secret list` 有 `TAURI_SIGNING_PRIVATE_KEY`(名稱完全一致) | | Docker job 成功、desktop job 金鑰錯誤 | docker job 只用自動提供的 `GITHUB_TOKEN`;桌面簽章金鑰的 secret 沒設定或名稱不符 | 同上 | | 三個 job 都綠,但部分平台收不到更新 | 平行寫 `latest.json`,後寫覆蓋先寫 | `max-parallel: 1`;靠 `updater-manifest` job 抓 | | `updater-manifest` 報 `release not found`,資產明明在 | 權限 `contents: read` 看不到 draft | 改 `contents: write` | | App 顯示 `Could not fetch a valid release JSON from the remote` | release 仍是 draft | `gh release edit vX.Y.Z --draft=false --latest` | | `tauri build` 在簽章步驟報 `failed to decode base64 pubkey` | `tauri.conf.json` 的 `pubkey` 是佔位字串或不完整 | 貼上 `<app>.key.pub` **完整內容**(→ `elf-tauri`) | | 建置、發佈、清單檢查全過,使用者按「下載並安裝」才失敗 | 私鑰與 `pubkey` 不成對(輪替金鑰後沒更新 secret) | 重新 `gh secret set`,比對 `gh secret list` 時間晚於金鑰檔 | | macOS 使用者:「無法打開,Apple 無法檢查是否有惡意軟體」 | 未簽章或只簽章未公證 | 設齊 `APPLE_*` secrets;`xcrun notarytool log <id>` 看拒絕原因 | | 很久沒問題的 macOS 發佈突然失敗 | Developer ID 憑證(5 年)到期或 API key 被撤銷 | 重新產生憑證並更新 secrets | | setup-node 報找不到 pnpm | `pnpm/action-setup` 放在 `setup-node` 之後 | 調換順序 | | `pnpm/action-setup` 報版本衝突 | 同時寫了 `version:` 與 `packageManager` 且不一致 | 移除 `version:` | | tag 推上去但 release 沒跑 | tag 由 workflow 用 `GITHUB_TOKEN` 推送(不觸發事件) | 由人在 `release` 上 `git tag -a` 後推送(→ `elf-cicd-versioning`) | | `verify` 失敗,其他 job 都 skipped | tag 不等於 `package.json` 版號,或 tag 不在 `release` 上 | 依 `elf-cicd-versioning` 常見錯誤處理 | | Ubuntu 編譯 `webkit2gtk` 找不到 | 安裝了 4.0 版套件 | 用 `libwebkit2gtk-4.1-dev` | | Windows 使用者更新後「程式和功能」出現兩筆、或更新失敗 | 使用者裝的是 `-setup.exe`(NSIS),`latest.json` 卻指向 `.msi` | `updaterJsonPreferNsis: true` | | Release 頁面文字與 repo 裡的 release-body.md 不同 | release.yml 內嵌了另一份 `releaseBody` | 刪除內嵌文字,改由「Read the release notes」step 讀檔(規則 23) | | 「Read the release notes」step 報找不到檔案 | 沒把範本複製到 `.github/release-body.md` | 複製範本並替換佔位符 | --- ## 6. 待確認 - pnpm 確切版本:`packageManager` 欄位要填 11.x 的哪一版,或改用 12(團隊尚未定案)。 - `pnpm/action-setup` 的主版號(範本用 `@v4`;是否已有更新的 major 需團隊確認後統一)。 - 以 pnpm 取代 npm 後,tauri-action 依 `pnpm-lock.yaml` 自動改用 `pnpm tauri build` —— 參考專案只用 npm 驗證過,第一次 pnpm 發佈需實際確認 log。 - Windows 程式碼簽章(`WINDOWS_CERTIFICATE` / `WINDOWS_CERTIFICATE_PASSWORD`)團隊是否要購買與導入;目前範本未含。 - Apple 公證要用 Apple ID 方式(`APPLE_ID`/`APPLE_PASSWORD`/`APPLE_TEAM_ID`,範本採用)還是 App Store Connect API key 方式(`APPLE_API_ISSUER`/`APPLE_API_KEY`/`APPLE_API_KEY_PATH`);團隊帳號由誰持有。 - Windows / Linux ARM 是否要納入矩陣(目前刻意不做)。
GitHub에서 보기