前言 根据官方文档 Tauri Updater: Static JSON File ,我们可以通过 Github Action 和 Github Release 实现项目的自动编译及更新。
参考 浮之静: Tauri 应用篇 - 自动通知应用升级 及 GyDi: clash-verge 实现,希望对你有所帮助。
跨平台编译 根据 Tauri: Cross-Platform Compilation 实现,使用 pnpm
作为包管理器。
添加 workflow 脚本 1 2 3 $ cd ${Your project path} $ mkdir .github/workflows $ touch .github/workflows/release.yml
编写 workflow,当提交的 tag 信息为 v*
的格式即自动触发,或在 Github Actions 页面手动触发,默认全平台编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 name: Release CI on: push: tags: - 'v*' workflow_dispatch: jobs: release: permissions: contents: write strategy: fail-fast: false matrix: platform: [macos-latest , ubuntu-20.04 , windows-latest ] runs-on: ${{ matrix.platform }} steps: - name: Checkout repository uses: actions/checkout@v3 - name: Install dependencies (ubuntu only) if: matrix.platform == 'ubuntu-20.04' run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev - name: Rust setup uses: dtolnay/rust-toolchain@stable - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: './src-tauri -> target' - name: Sync node version and insatll nodejs uses: actions/setup-node@v3 with: node-version: 'lts/*' - name: Install pnpm uses: pnpm/action-setup@v2 id: pnpm-install with: version: 7 run_install: false - name: Get pnpm store directory id: pnpm-cache shell: bash run: | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - uses: actions/cache@v3 name: Setup pnpm cache with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install frontend dependencies run: pnpm install - name: Build the app uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} with: tagName: ${{ github.ref_name }} releaseName: 'App Name v__VERSION__' releaseBody: 'See the assets to download and install this version.' releaseDraft: true prerelease: false
添加环境变量 在项目的 github 仓库添加所需环境变量。
Name
Value
TAURI_PRIVATE_KEY
${Your project private key}
TAURI_KEY_PASSWORD
${Your project password}
查看使用情况 访问 Github Account: billing ,看到 Usage this month
第一项即是 Github Actions
当前使用情况。
自动更新 根据 Tauri Updater:Static JSON File 实现,通过 github release
实现分发。
添加项目签名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 $ cd ${Your project path} $ pnpm tauri signer generate -w ~/.tauri/${Custom sign name} .key pnpm tauri signer generate -w ~/.tauri/tauri-app.key $ pnpm tauri signer generate -w $HOME /.tauri/${Custom sign name} .key > tauri-app@0.0.0 tauri /home/user/Desktop/tauri-app > tauri "signer" "generate" "-w" "/home/user/.tauri/tauri-app.key" Generating new private key without password. Please enter a password to protect the secret key. Password: Password (one more time): Deriving a key from the password in order to encrypt the secret key... done Your keypair was generated successfully Private: /home/user/.tauri/tauri-app.key (Keep it secret!) Public: /home/user/.tauri/tauri-app.key.pub --------------------------- Environment variables used to sign: `TAURI_PRIVATE_KEY` Path or String of your private key `TAURI_KEY_PASSWORD` Your private key password (optional) ATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not work. ---------------------------
创建 updater release 通过在 github release
中创建 updater
tag 并添加 update.json
文件,我们可以在 tauri.conf.json
中 updater
属性里 endpoionts
中添加该文件地址。
这样只要程序能够访问 github release
,就能够获取更新信息,我们只需要在发布新版本后更新文件即可。
1 2 3 $ cd ${Your project path} $ git tag updater $ git push --tags
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 { "version" : "v1.0.0" , "notes" : "Test version" , "pub_date" : "2020-06-22T19:25:57Z" , "platforms" : { "darwin-x86_64" : { "signature" : "Content of app.tar.gz.sig" , "url" : "https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz" } , "darwin-aarch64" : { "signature" : "Content of app.tar.gz.sig" , "url" : "https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz" } , "linux-x86_64" : { "signature" : "Content of app.AppImage.tar.gz.sig" , "url" : "https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz" } , "windows-x86_64" : { "signature" : "Content of app.msi.sig" , "url" : "https://github.com/username/reponame/releases/download/v1.0.0/app-x64.msi.zip" } } }
修改配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { ... "tauri" : { ... "updater" : { "active" : true , "endpoints" : [ "https://github.com/${{Your github username}}/${{Your repo name}}/releases/download/updater/update.json" ] , "dialog" : false , "pubkey" : "${{Your signature pub key}}" , "windows" : { "installMode" : "passive" } } ... } ...}
本地推送完成更新 通过本地脚本和 github actions
的结合,实现本地推送完成 update.json
的更新。
添加依赖及相关文件 1 2 3 4 5 $ cd ${Your project path} $ pnpm install -D node-fetch fs-extra @actions/github $ touch UPDATELOG.md $ mkdir script && cd ./script $ touch updatelog.mjs publish.mjs update.mjs
更新 package.json
。
1 2 3 4 5 6 7 8 "scripts" : { "dev" : "vite" , "build" : "tsc && vite build" , "preview" : "vite preview" , "tauri" : "tauri" , "updater" : "node scripts/updater.mjs" , "publish" : "node scripts/publish.mjs" } ,
编写更新信息脚本 updatelog.mjs 负责从 UPDATELOG.md
文件获取更新描述信息,对应 update.json
中 notes
项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import fs from "fs-extra" ;import path from "path" ;const UPDATE_LOG = "UPDATELOG.md" ;export async function resolveUpdateLog (tag ) { const cwd = process.cwd (); const reTitle = /^## v[\d\.]+/ ; const reEnd = /^---/ ; const file = path.join (cwd, UPDATE_LOG ); if (!(await fs.pathExists (file))) { throw new Error ("could not found UPDATELOG.md" ); } const data = await fs.readFile (file).then ((d ) => d.toString ("utf8" )); const map = {}; let p = "" ; data.split ("\n" ).forEach ((line ) => { if (reTitle.test (line)) { p = line.slice (3 ).trim (); if (!map[p]) { map[p] = []; } else { throw new Error (`Tag ${p} dup` ); } } else if (reEnd.test (line)) { p = "" ; } else if (p) { map[p].push (line); } }); if (!map[tag]) { throw new Error (`could not found "${tag} " in UPDATELOG.md` ); } return map[tag].join ("\n" ).trim (); }
UPDATELOG.md
文件中更新信息格式如下。
1 2 3 4 5 6 7 8 9 10 11 ## v0.0.1 ### Features - some feature- another feature### Bug Fixes - some bugs- another bugs
编写版本更新及发布脚本 publish.mjs 负责自动更新版本信息并提交 git,注意须先写好更新日志 UPDATELOG.md
!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import fs from "fs-extra" ;import { createRequire } from "module" ;import { execSync } from "child_process" ;import { resolveUpdateLog } from "./updatelog.mjs" ;const require = createRequire (import .meta .url );async function resolvePublish ( ) { const flag = process.argv [2 ] ?? "patch" ; const packageJson = require ("../package.json" ); const tauriJson = require ("../src-tauri/tauri.conf.json" ); let [a, b, c] = packageJson.version .split ("." ).map (Number ); if (flag === "major" ) { a += 1 ; b = 0 ; c = 0 ; } else if (flag === "minor" ) { b += 1 ; c = 0 ; } else if (flag === "patch" ) { c += 1 ; } else throw new Error (`invalid flag "${flag} "` ); const nextVersion = `${a} .${b} .${c} ` ; packageJson.version = nextVersion; tauriJson.package .version = nextVersion; const nextTag = `v${nextVersion} ` ; await resolveUpdateLog (nextTag); await fs.writeFile ( "./package.json" , JSON .stringify (packageJson, undefined , 2 ) ); await fs.writeFile ( "./src-tauri/tauri.conf.json" , JSON .stringify (tauriJson, undefined , 2 ) ); execSync ("git add ./package.json" ); execSync ("git add ./src-tauri/tauri.conf.json" ); execSync (`git commit -m "release v${nextVersion} "` ); execSync (`git tag -a v${nextVersion} -m "v${nextVersion} "` ); execSync (`git push` ); execSync (`git push origin v${nextVersion} ` ); console .log (`Publish Successfully...` ); }resolvePublish ();
添加静态文件更新脚本 update.mjs 负责更新文件 update.json
的生成与发布,在 github actions
中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 import fetch from "node-fetch" ;import { getOctokit, context } from "@actions/github" ;import { resolveUpdateLog } from "./updatelog.mjs" ;const UPDATE_TAG_NAME = "updater" ;const UPDATE_JSON_FILE = "update.json" ;async function resolveUpdater ( ) { if (process.env .GITHUB_TOKEN === undefined ) { throw new Error ("GITHUB_TOKEN is required" ); } const options = { owner : context.repo .owner , repo : context.repo .repo }; const github = getOctokit (process.env .GITHUB_TOKEN ); const { data : tags } = await github.rest .repos .listTags ({ ...options, per_page : 10 , page : 1 , }); const tag = tags.find ((t ) => t.name .startsWith ("v" )); console .log (tag); const { data : latestRelease } = await github.rest .repos .getReleaseByTag ({ ...options, tag : tag.name , }); const updateData = { version : tag.name , notes : await resolveUpdateLog (tag.name ), pub_date : new Date ().toISOString (), platforms : { "windows-x86_64" : { signature : "" , url : "" }, "darwin-x86_64" : { signature : "" , url : "" }, "linux-x86_64" : { signature : "" , url : "" }, }, }; const promises = latestRelease.assets .map (async (asset) => { const { name, browser_download_url } = asset; if (name.endsWith (".msi.zip" )) { updateData.platforms ["windows-x86_64" ].url = browser_download_url; } if (name.endsWith (".msi.zip.sig" )) { const sig = await getSignature (browser_download_url); updateData.platforms ["windows-x86_64" ].signature = sig; } if (name.endsWith (".app.tar.gz" ) && !name.includes ("aarch" )) { updateData.platforms ["darwin-x86_64" ].url = browser_download_url; } if (name.endsWith (".app.tar.gz.sig" ) && !name.includes ("aarch" )) { const sig = await getSignature (browser_download_url); updateData.platforms ["darwin-x86_64" ].signature = sig; } if (name.endsWith ("aarch64.app.tar.gz" )) { updateData.platforms ["darwin-aarch64" ].url = browser_download_url; } if (name.endsWith ("aarch64.app.tar.gz.sig" )) { const sig = await getSignature (browser_download_url); updateData.platforms ["darwin-aarch64" ].signature = sig; } if (name.endsWith (".AppImage.tar.gz" )) { updateData.platforms ["linux-x86_64" ].url = browser_download_url; } if (name.endsWith (".AppImage.tar.gz.sig" )) { const sig = await getSignature (browser_download_url); updateData.platforms ["linux-x86_64" ].signature = sig; } }); await Promise .allSettled (promises); console .log (updateData); Object .entries (updateData.platforms ).forEach (([key, value] ) => { if (!value.url ) { console .log (`[Error]: failed to parse release for "${key} "` ); delete updateData.platforms [key]; } }); const { data : updateRelease } = await github.rest .repos .getReleaseByTag ({ ...options, tag : UPDATE_TAG_NAME , }); for (let asset of updateRelease.assets ) { if (asset.name === UPDATE_JSON_FILE ) { await github.rest .repos .deleteReleaseAsset ({ ...options, asset_id : asset.id , }); } } await github.rest .repos .uploadReleaseAsset ({ ...options, release_id : updateRelease.id , name : UPDATE_JSON_FILE , data : JSON .stringify (updateData, null , 2 ), }); }async function getSignature (url ) { const response = await fetch (url, { method : "GET" , headers : { "Content-Type" : "application/octet-stream" }, }); return response.text (); }resolveUpdater ().catch (console .error );
编写发布更新 workflow 当项目新版本 release 发布时自动触发,或手动触发,使用 pnpm
作为包管理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 $ cd ${Your project path} $ touch .github/workflows/updater.yml BASH name: Updater CI run-name: Release update.json on: release: types: [published ] workflow_dispatch: jobs: release-update: permissions: contents: write runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 - name: Install pnpm uses: pnpm/action-setup@v2 id: pnpm-install with: version: 7 run_install: false - name: Get pnpm store directory id: pnpm-cache shell: bash run: | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - uses: actions/cache@v3 name: Setup pnpm cache with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install frontend dependencies run: pnpm install - name: Release updater file run: pnpm run updater env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
更新流程
完成新版本程序编写并提交
向 UPDATELOG.md
中添加新版本描述
执行 pnpm run publish
, 发布新版本,由于提交信息中带有版本 tag,触发 Release CI
编译完成,repo release
中出现新版本的 draft
,根据需要修改相关信息并发布
发布完成自动触发 Updater CI
,更新 updater release
中的 update.json
程序此时可以给通过访问 github release
中的 update.json
获得新版本更新信息并实现自动更新