Skip to main content

elf-tauri

Elf Express 團隊 Tauri 2 桌面應用慣例(src-tauri 目錄結構、command 註冊、capabilities 權限、 updater 與 createUpdaterArtifacts、簽章金鑰保管、版號同步、前端 IPC 橋接)。當任務涉及新增或修改 src-tauri/ 下的任何檔案、#[tauri::command]、generate_handler!、capabilities/default.json、 tauri.conf.json、tauri-plugin-updater、自動更新、@tauri-apps/* 在前端的使用、簽章金鑰或 docs/signing-and-keys.md 時觸發。

Quellinformationen

Repository
elf-express/skillshare-source
Letzte Quellaktivität
11. Oktober 2026 um 13:45
Erkannte Sprache von SKILL.md
Chinesisch
Sterne
0
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
8 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
elf-tauri
description
Elf Express 團隊 Tauri 2 桌面應用慣例(src-tauri 目錄結構、command 註冊、capabilities 權限、 updater 與 createUpdaterArtifacts、簽章金鑰保管、版號同步、前端 IPC 橋接)。當任務涉及新增或修改 src-tauri/ 下的任何檔案、#[tauri::command]、generate_handler!、capabilities/default.json、 tauri.conf.json、tauri-plugin-updater、自動更新、@tauri-apps/* 在前端的使用、簽章金鑰或 docs/signing-and-keys.md 時觸發。
metadata
{"version":"1.0.1","owner":"Elf Express"}
# Elf Express Tauri 2 桌面應用慣例 > 參考實作:`TypingTrainer/src-tauri/`、`apps/src/api/desktop.ts`、`docs/signing-and-keys.md`。 > 這是**團隊層**規範;Tauri 2 本身的 API、IPC(emit / channel)、state、行動版建置等通用知識請載入 > `tauri-v2`(`desktop/tauri-v2`),本 skill 不重複。兩者衝突時以本 skill 為準。 相關 skill: - `elf-cicd-desktop` — 三平台 release workflow、secrets 檢查、latest.json 驗證 - `elf-cicd-versioning` — `scripts/version.mjs`、版號 PR 與在 `release` 上打 tag - `elf-vue` — 前端元件 / store 規範(desktop 橋接只能在 `api/` 層被呼叫) - `elf-stack` — 團隊版本總表(Node 24.18、pnpm 版號) --- ## 1. 何時使用 - 新專案加入桌面殼:依第 3 節複製範本 - 新增 Rust command、新增 Tauri plugin、調整權限 - 設定或排查自動更新(updater、pubkey、endpoints) - 產生 / 輪替 / 備份簽章金鑰 - 前端需要呼叫桌面功能(檔案路徑、版本、更新) --- ## 2. 固定規則 ### 專案結構 1. **MUST** 使用 Tauri 標準結構,桌面殼只存在於 `src-tauri/`,前端在獨立 workspace(例:`apps/`): ``` <repo>/ ├─ package.json # packageManager: pnpm@<PNPM_VERSION>;scripts: tauri / desktop:dev / desktop:build ├─ pnpm-workspace.yaml ├─ <web-dir>/ # Vue 前端;同一份 bundle 給瀏覽器與桌面用 │ └─ src/api/desktop.ts # 唯一 import @tauri-apps/* 的檔案 ├─ src-tauri/ │ ├─ tauri.conf.json │ ├─ Cargo.toml / Cargo.lock / build.rs │ ├─ capabilities/default.json │ ├─ icons/ │ └─ src/ │ ├─ main.rs # 只呼叫 <appname>_lib::run() │ └─ lib.rs # 所有 command 與 Builder ├─ scripts/version.mjs # → elf-cicd-versioning └─ docs/signing-and-keys.md ``` 2. **MUST** `main.rs` 只做轉呼叫;所有邏輯與 command 放在 `lib.rs`(或 `lib.rs` 引入的模組)。 WHY:行動版目標會產生自己的進入點,只有 lib 裡的程式碼可被共用。 3. **MUST** `Cargo.toml` 的 `[lib] name = "<appname>_lib"`、`crate-type = ["staticlib", "cdylib", "rlib"]`。 4. **MUST** `identifier` 格式為 `tw.elf.<appname>`(全小寫)。WHY:決定各 OS 的 app data 路徑,**上線後不可更改**,否則使用者資料「消失」。 ### Command 與 IPC 5. **MUST** 每個 `#[tauri::command]` 都加進 `lib.rs` 的 `tauri::generate_handler![...]`。 WHY:沒註冊的 command 編譯不會錯,`invoke` 在執行期才失敗。 6. **MUST** 會失敗的 command 回傳 `Result<T, String>`,錯誤訊息含上下文(`format!("cannot create {}: {e}", path)`)。**MUST NOT** 在 command 內 `unwrap()` / `expect()`。 WHY:panic 會讓前端只拿到無意義的錯誤;`String` 錯誤可直接顯示。 7. **MUST** 回傳結構體 `#[derive(Serialize)]`,並在 `desktop.ts` 定義同名欄位的 TypeScript 型別。 8. **MUST** 前端所有 `@tauri-apps/*` 只能在 `<web-dir>/src/api/desktop.ts` 內以 **動態 `import()`** 使用;view / store 只呼叫 `desktop.ts` 匯出的函式。 WHY:同一份 bundle 也跑在瀏覽器,靜態 import 會把 Tauri API 打進主 chunk,且瀏覽器端沒有 `__TAURI_INTERNALS__`。 9. **MUST** 每個橋接函式開頭 `if (!IS_DESKTOP) return null`,`IS_DESKTOP = '__TAURI_INTERNALS__' in window`。 10. **MUST** 大型檔案用 `app.path().app_local_data_dir()`,**MUST NOT** 用 roaming 的 `app_data_dir()` 存 GB 級資料。WHY:Windows roaming 目錄會在機器間同步。 ### 權限(capabilities) 11. **MUST** 所有權限寫在 `src-tauri/capabilities/default.json`,`windows: ["main"]`,基線為 `["core:default", "updater:default", "process:allow-restart"]`。WHY:Tauri 2 預設全部拒絕。 12. **MUST** 新增 plugin 時同時:`Cargo.toml` 加依賴 → `lib.rs` 註冊 → `default.json` 加**最小**權限(例:`fs:allow-read-text-file`,不用 `fs:default` 以外的萬用權限)→ 前端套件 `pnpm --filter <web> add @tauri-apps/plugin-xxx`。 13. **MUST NOT** 為了「先跑起來」加入過寬權限或把 CSP 設為 `null`。 13a. **MUST** CSP 含 `connect-src 'self' ipc: http://ipc.localhost <api-origin>`(範本見 `templates/tauri.conf.json`): - `ipc:`(macOS / Linux)與 `http://ipc.localhost`(Windows)是 Tauri 2 的 IPC 通道,少了它 `invoke` 會被 CSP 擋下; - `<api-origin>` 是**每個專案必填**的後端來源(例 `https://api.example.com`,只寫 origin、不含路徑)。桌面版的 bundle 由 `tauri://localhost` / `http://tauri.localhost` 提供, 相對路徑 `/api` 不會經過 nginx,因此桌面建置 **MUST** 以絕對網址設定 `VITE_API_BASE_URL`,其 origin 必須同時出現在 CSP `connect-src` 與後端 `Cors:Origins`(後端需允許 `tauri://localhost`、`http://tauri.localhost`)。 - 純離線、不連後端的 App 刪掉 `<api-origin>` 這一段即可,**MUST NOT** 留著佔位字串,也 **MUST NOT** 改成 `connect-src *` 或 `https:`。 13b. **MUST** 桌面建置明確設定 `VITE_USE_MOCK`:`VITE_USE_MOCK` 程式預設為開(`!== 'false'`,見 `elf-vue` 規則 31),而桌面 release 不經過 `web.Dockerfile`,沒設就會出貨 fixture 資料。 有後端的 App **MUST** 在 release workflow 的建置環境設 `VITE_USE_MOCK=false` 與 `VITE_API_BASE_URL=<api-origin>/api`(workflow 由 `elf-cicd-desktop` 維護);刻意以 fixture / 本機資料運作的離線 App 須在 PR 說明。 ### Updater 14. **MUST** `tauri.conf.json` 設 `"bundle": { "createUpdaterArtifacts": true }`。 WHY:沒有簽章金鑰時 `tauri build` 直接失敗,確保未簽章版本不會出貨。 15. **MUST** `plugins.updater`: - `pubkey`:`~/.tauri/<appname>.key.pub` 的**完整內容**(佔位字串會讓 build 在簽章步驟報 `failed to decode base64 pubkey`) - `endpoints`:`["https://github.com/<owner>/<repo>/releases/latest/download/latest.json"]` - `windows.installMode`:`"passive"`(只顯示進度視窗,不需點擊) 16. **MUST** `tauri-plugin-updater` 與 `tauri-plugin-process` 放在 `[target.'cfg(any(target_os = "macos", windows, target_os = "linux"))'.dependencies]`, 並在 `.setup()` 內以 `#[cfg(desktop)]` 註冊(不是 Builder 上的 `.plugin()`)。WHY:這兩個 plugin 不支援行動版,否則行動版無法編譯。 16a. **MUST** release workflow 的 `tauri-action` 設 `updaterJsonPreferNsis: true`,讓 `latest.json` 的 `windows-x86_64` 指向使用者實際安裝的 NSIS `-setup.exe`(而非 `.msi`),否則更新後會出現兩套安裝紀錄或更新失敗(workflow 見 `elf-cicd-desktop`)。 17. **MUST** 更新流程:`check()` → `downloadAndInstall(progress)` → `relaunch()`;進度最多顯示 99% 直到安裝返回。 WHY:Windows 安裝程式接手後行程會在 `downloadAndInstall` 內結束;macOS / Linux 需要 `relaunch` 才會跑新版。 ### 簽章金鑰 18. **MUST** 以 `pnpm tauri signer generate -w ~/.tauri/<appname>.key` 產生,密碼留空;公鑰進 `tauri.conf.json`,私鑰只進 `gh secret set TAURI_SIGNING_PRIVATE_KEY`。 19. **MUST** 私鑰備份到團隊密碼管理器。WHY:GitHub secret 無法讀回;遺失私鑰 = 所有已安裝的 App 永遠無法更新。 20. **MUST NOT** 把私鑰、`.p12`、App 專用密碼放進 repo、issue、PR、log 或對話。 21. **MUST** 輪替金鑰後同時更新 `pubkey` 與 secret,並用 `gh secret list` 確認 secret 時間晚於金鑰檔。WHY:不成對的金鑰會通過所有 CI 關卡,直到使用者按安裝才失敗。 22. **MUST** 專案內有 `docs/signing-and-keys.md`,內容複製自 [`references/signing-and-keys.md`](references/signing-and-keys.md)。 ### 版號 23. **MUST NOT** 手動修改 `tauri.conf.json`、`Cargo.toml`、`Cargo.lock` 的版號;一律 `pnpm version:patch|minor|major`(`scripts/version.mjs`,見 `elf-cicd-versioning`)。 WHY:版號同時存在 package.json ×2、tauri.conf.json、Cargo.toml `[package]`、Cargo.lock、.csproj;`Cargo.lock` 落後會讓 `cargo build --locked` 失敗。 24. **MUST** `version.mjs` 的 Cargo.lock 規則使用 `name = "<appname>"`(Cargo.toml `[package] name`)比對;改 crate 名稱時同步修改。 ### 測試 25. **MUST** e2e 測試桌面專屬 UI 時以 `page.addInitScript(() => { (window as any).__TAURI_INTERNALS__ = {} })` 模擬殼層。 WHY:否則 `v-if="IS_DESKTOP"` 區塊不會渲染,量到的是少了一截的頁面。前提是規則 8(動態 import)。 26. **MUST** 單元測試以 `vi.mock('@tauri-apps/plugin-updater', …)`、`vi.mock('@tauri-apps/plugin-process', …)` mock,不實際呼叫殼層。 --- ## 3. 標準範本 | 範本 | 複製到 | 替換 | |---|---|---| | [`templates/tauri.conf.json`](templates/tauri.conf.json) | `src-tauri/tauri.conf.json` | `<AppName>` `<appname>` `<web-dir>` `<owner>/<repo>` `<api-origin>`(CSP,必填或整段移除)、pubkey、描述 | | [`templates/Cargo.toml`](templates/Cargo.toml) | `src-tauri/Cargo.toml` | `<appname>` `<AppName>` | | [`templates/main.rs`](templates/main.rs) | `src-tauri/src/main.rs` | `<appname>` | | [`templates/lib.rs`](templates/lib.rs) | `src-tauri/src/lib.rs` | `<AppName>`、範例 command | | [`templates/capabilities/default.json`](templates/capabilities/default.json) | `src-tauri/capabilities/default.json` | — | | [`templates/desktop.ts`](templates/desktop.ts) | `<web-dir>/src/api/desktop.ts` | 對應的 command | | [`references/signing-and-keys.md`](references/signing-and-keys.md) | `docs/signing-and-keys.md` | `<AppName>` `<appname>` `<owner>/<repo>` | `src-tauri/build.rs`: ```rust fn main() { tauri_build::build() } ``` ### 根目錄 package.json ```jsonc { "packageManager": "pnpm@<PNPM_VERSION>", // full version; exact team version in elf-stack. No corepack. "engines": { "node": ">=24.18" }, "scripts": { "dev": "pnpm --filter <web> dev", "build": "pnpm --filter <web> build", "tauri": "tauri", "desktop:dev": "tauri dev", "desktop:build": "tauri build", "version:show": "node scripts/version.mjs", "version:patch": "node scripts/version.mjs patch", "version:minor": "node scripts/version.mjs minor", "version:major": "node scripts/version.mjs major" }, "devDependencies": { "@tauri-apps/cli": "^2" } } ``` 前端依賴:`pnpm --filter <web> add @tauri-apps/api @tauri-apps/plugin-updater @tauri-apps/plugin-process` ### 新增一個 command(固定四步) ```rust // 1. src-tauri/src/lib.rs #[tauri::command] fn read_setting(app: tauri::AppHandle, key: String) -> Result<String, String> { // ... 失敗時 Err(format!("cannot read {key}: {e}")) } // 2. 同檔案 generate_handler! .invoke_handler(tauri::generate_handler![shell_info, data_dir, read_setting]) ``` ```ts // 3. <web-dir>/src/api/desktop.ts export async function readSetting(key: string): Promise<string | null> { if (!IS_DESKTOP) return null const { invoke } = await import('@tauri-apps/api/core') return await invoke<string>('read_setting', { key }) // 參數名 = Rust 參數名(camelCase 對應) } ``` 4. 若 command 使用了 plugin API,在 `capabilities/default.json` 加上對應最小權限。自訂 command 本身不需要在 capabilities 列出(見 `tauri-v2` skill 的權限說明)。 --- ## 4. 檢查清單 - [ ] `main.rs` 只有 `<appname>_lib::run()`;邏輯全在 `lib.rs` - [ ] 每個 `#[tauri::command]` 都在 `generate_handler!` - [ ] command 無 `unwrap()` / `expect()`,失敗回 `Result<_, String>` - [ ] 前端只有 `api/desktop.ts` import `@tauri-apps/*`,且為動態 `import()` - [ ] `capabilities/default.json` 只含必要權限;CSP 未被放寬為 `null` - [ ] CSP 有 `connect-src 'self' ipc: http://ipc.localhost <api-origin>`,`<api-origin>` 已換成真實 origin(或離線 App 已移除) - [ ] 桌面建置已明確設定 `VITE_USE_MOCK` / `VITE_API_BASE_URL`;release 使用 `updaterJsonPreferNsis: true` - [ ] `createUpdaterArtifacts: true`;`pubkey` 為真實公鑰;`endpoints` 指向本 repo;`installMode: "passive"` - [ ] updater / process plugin 在 desktop-only target 依賴,並於 `setup` 內 `#[cfg(desktop)]` 註冊 - [ ] `identifier` = `tw.elf.<appname>`,且與既有版本相同 - [ ] 版號只透過 `pnpm version:*` 修改;`Cargo.lock` 已一併更新 - [ ] `docs/signing-and-keys.md` 存在;私鑰已備份到密碼管理器;`gh secret list` 有 `TAURI_SIGNING_PRIVATE_KEY` - [ ] 本機 `TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/<appname>.key)" pnpm desktop:build` 產出 bundle **與** `.sig`
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen