背景 项目 ZImgTools 是基于 Tauri 2 + Vue 3 + Vite 的图片压缩与格式转换桌面工具,源码放在 Gitee 私有仓库 。
安装包需要发布到 GitHub Release ,供用户下载。
约束与目标:
GitHub 侧不存放源码 ,只放安装包(独立仓库 psvmc/z-img-tools-releases)
需要 Windows + Linux + macOS 三平台产物
本机是 Windows,没有 macOS;Linux 也不想依赖本机 WSL / Docker
Tip
Gitee Go 收费。
Github Actions 公开仓库免费。
本地构建 Linux 环境需要用 WSL 或 Docker 并安装编译环境,Mac 环境无法在 Windows 下创建。
方案演进
阶段
做法
结果
1
Gitee Go CI 构建 + GitHub API 上传
Gitee Go 收费,放弃
2
本机 Win 构建 + WSL 构建 Linux + GHA 构建 macOS
环境维护成本高
3(最终)
三平台全部 GitHub Actions
本机只负责触发
最终方案:源码在 Gitee,构建在 GitHub Actions,发布到 GitHub Releases 空壳仓 。
架构
要点:
源码仓 (Gitee):正常开发、git tag、git push
发布仓 (GitHub psvmc/z-img-tools-releases):只有 README + workflow,建议 Public (Actions 免费)
workflow 不在 Gitee 跑 ,而是通过脚本同步到发布仓后触发
Actions 用 GITEE_TOKEN 从 Gitee 克隆私有仓库对应 tag
一次性配置 准备 GitHub Releases 仓库 在 GitHub 创建 psvmc/z-img-tools-releases,main 分支至少有一个 commit(例如 README)。
GitHub Token Token 创建入口(登录 GitHub 后打开):
Classic:https://github.com/settings/tokens/new
勾选下面的权限:
安装 GitHub CLI 1 winget install GitHub.cli
如果安装不成功,执行下面的命令
1 2 3 winget source reset --force winget source update winget install GitHub.cli --accept-source-agreements --accept-package-agreements
安装完要重启终端
配置 Gitee 私人令牌 在 Gitee 私人令牌 创建令牌,勾选 projects 读权限。
私人令牌 - Gitee.com
写入发布仓 Secret(须带 --body,不要用不带 --body 的交互命令,否则 Windows 下容易写入空值):
1 gh secret set GITEE_TOKEN -R psvmc/z-img-tools-releases --body "你的Gitee令牌"
验证:触发 workflow 后,日志中 GITEE_TOKEN: 应显示 ***,而不是空白。
注意每个发布仓都要单独配置一次 Secret。
项目内脚本 源码仓库 scripts/ 目录下保留以下文件:
1 2 3 4 5 6 7 8 scripts/ ├── publish-release.bat # 发版入口 ├── set-version.bat # 版本号一键同步(交互式) ├── set-version.ps1 # 版本脚本实现 └── release/ ├── ensure-release-gha.bat # 同步 workflow 到发布仓 ├── ensure-release-gha.ps1 # 通过 GitHub Contents API 上传 workflow └── github-workflow-release-all.yml # 三平台构建 workflow 模板
publish-release.bat 会 call scripts\release\ensure-release-gha.bat,上述 release/ 目录必须完整,否则报「系统找不到指定的路径」。
版本号与打包配置 升级版本脚本 发版前用 scripts\set-version.bat 同步各文件版本号:
1 2 3 scripts\set -version.bat REM 显示当前版本并交互输入 scripts\set -version.bat 1 .2 .0 REM 指定版本 scripts\set -version.bat patch REM 1 .2 .0 -> 1 .2 .1
自动更新:package.json、package-lock.json、src-tauri/Cargo.toml、src-tauri/Cargo.lock、src-tauri/tauri.conf.json(含窗口标题中的 vX.Y.Z)。
程序命名 tauri.conf.json 中配置:
1 2 3 4 { "productName" : "ZImgTools" , "mainBinaryName" : "ZImgTools" }
productName:安装包、开始菜单等显示名称
mainBinaryName:可执行文件名(打包后为 ZImgTools.exe,不再是 Cargo 包名 z-img-tools)
图标配置 图标文件放在 src-tauri/icons/,可在项目根 icons/ 维护设计稿后复制过去:
文件
用途
32x32.png / 128x128.png / 128x128@2x.png
Tauri bundle 多尺寸 PNG
icon.ico
Windows 主程序 + NSIS 安装包图标
icon.icns
macOS
tauri.conf.json 关键配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { "bundle" : { "icon" : [ "icons/32x32.png" , "icons/128x128.png" , "icons/128x128@2x.png" , "icons/icon.icns" , "icons/icon.ico" ] , "windows" : { "nsis" : { "installerIcon" : "icons/icon.ico" } } } }
说明:
bundle.icon 只作用于主程序 exe 嵌入图标
NSIS 安装包需单独配置 installerIcon,否则安装程序会使用 NSIS 默认图标
当前 tauri-build 尚不支持 uninstallerIcon,不要写入配置,否则构建报错
本地 Windows 打包(NSIS) 按平台拆分的配置文件,开发时主配置 bundle.active 仍为 false,Windows 打包时自动合并:
src-tauri/tauri.windows.conf.json:
1 2 3 4 5 6 { "bundle" : { "active" : true , "targets" : "nsis" } }
本地打包命令:
1 2 npm run tauri build REM Windows 下自动生成 NSIS npm run build:app REM 显式指定 NSIS
产物:src-tauri/target/release/ZImgTools.exe 与 src-tauri/target/release/bundle/nsis/ZImgTools_<version>_x64-setup.exe。
Linux / macOS 对应 tauri.linux.conf.json(deb)、tauri.macos.conf.json(dmg),CI 构建时同样生效。
发版流程 流程 1 2 3 4 5 scripts\set -version.bat 1 .2 .0 git add -A && git commit -m "chore: bump version to 1 .2 .0 " git tag 1 .2 .0 git push origin master --tags scripts\publish-release.bat
或指定版本触发(tag 须已推到 Gitee):
1 scripts\publish-release.bat 1 .2 .0
注意 :Gitee tag 与 src-tauri/Cargo.toml、src-tauri/tauri.conf.json、package.json 中的版本号保持一致,不加 v 前缀 (如 1.0.0,不是 v1.0.0)。
发版前务必先 git push origin <tag>,否则 Actions 克隆不到对应 tag。
脚本会:
从 src-tauri/Cargo.toml 读取版本号(未传参时)
将 github-workflow-release-all.yml 同步到 psvmc/z-img-tools-releases 的 .github/workflows/release-all.yml
触发 release-all.yml workflow
约 10~30 分钟后在 Release 页查看:
产物
文件
平台
ZImgTools_*_x64-setup.exe(NSIS 安装包)
Windows
ZImgTools_*_amd64.deb
Linux
ZImgTools_*_universal.dmg
macOS(Universal 二进制)
Actions 中只打 NSIS / deb / dmg 三种格式。本地 Windows 默认打 NSIS(见上文 tauri.windows.conf.json)。
项目开发时 tauri.conf.json 中 bundle.active 为 false;CI 与各平台配置文件(tauri.windows.conf.json 等)在构建时合并为 active: true,无需 workflow 内再 patch 配置。
Release 更新日志 GitHub Release 的说明文字不使用 generate_release_notes: true(那是 GitHub 按 PR 自动生成的),而是从 Gitee 源码仓库 取两个 tag 之间的 git 提交记录 生成。
publish job 在汇总 artifact 后、发布 Release 前会:
用 GITEE_TOKEN 克隆 Gitee 仓库并 git fetch --tags
解析当前发版 tag(兼容 1.0.0 / v1.0.0)
按版本号排序 tag 列表,找到上一个 tag 作为对比基准
执行 git log PREV_TAG..TAG --pretty=format:'- %s (%h)' --no-merges
将结果写入 Release 的 body
生成示例:
1 2 3 4 ## Changes since 1.0.0 - 修复批量压缩进度显示 (a1b2c3d)- 新增 WebP 转换支持 (e4f5g6h)
若是首个版本(没有更早 tag),标题为 ## Initial release,内容为该 tag 可达的全部提交。
应用内检查更新也会读取 Release 的 body 字段展示给用户,因此 commit message 写清楚有助于用户了解变更内容。
应用内更新实现见 Tauri 2 项目 GitHub Release 应用版本更新 :从本 Release 仓下载对应平台安装包,Windows 为 *-setup.exe,下载时弹窗显示进度,完成后 detached 启动 NSIS(/UPDATE 覆盖安装)。
脚本代码 发版入口 路径:scripts/publish-release.bat
读版本号、同步 workflow、触发 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 @echo off setlocal EnableDelayedExpansioncd /d "%~dp0.."set "GITHUB_REPO=psvmc/z-img-tools-releases"set "GITEE_REPO=https://gitee.com/psvmc/z-img-tools-tauri.git"set "TAG=":parse_args if "%~1 "=="" goto args_doneif not defined TAG set "TAG=%~1 "shift goto parse_args:args_done if not defined TAG ( for /f "usebackq tokens=2 delims== " %%a in (`findstr /R /C:"^version " src-tauri\Cargo.toml`) do set "VER =%%a " set "VER =!VER:"=! " for /f "tokens=* delims= " %%a in ("!VER! ") do set "VER =%%a " set "TAG=!VER! " ) where gh >nul 2 >&1 || ( echo [error] Install GitHub CLI and run: gh auth login exit /b 1 ) echo .echo === ZImgTools release (GitHub Actions: Windows + Linux + macOS) ===echo Tag: %TAG% echo GitHub repo: %GITHUB_REPO% echo Gitee repo: %GITEE_REPO% echo .echo Prerequisites:echo 1 . Gitee token in GitHub secret GITEE_TOKENecho gh secret set GITEE_TOKEN -R %GITHUB_REPO% --body "YOUR_GITEE_TOKEN"echo 2 . Push tag to Gitee: git push origin %TAG% echo .set "GITHUB_REPO=%GITHUB_REPO% "call "%~dp0release\ensure-release-gha.bat"if errorlevel 1 exit /b 1 echo Triggering release-all workflow...gh workflow run release-all.yml -R "%GITHUB_REPO% " -f "tag=%TAG% " -f "gitee_repo=%GITEE_REPO% " if errorlevel 1 ( echo [error] Failed to start workflow exit /b 1 ) echo .echo Build started. Wait 10 -30 min, then check:echo https://github.com/%GITHUB_REPO% /releases/tag/%TAG% echo https://github.com/%GITHUB_REPO% /actionsecho .echo Watch: gh run watch -R %GITHUB_REPO% exit /b 0
升级版本脚本 路径:scripts/set-version.bat(调用同目录 set-version.ps1)
发版前同步各文件版本号。无参数时显示当前版本并交互输入;也支持 1.2.0、patch、minor、major。
1 2 3 4 5 6 @echo off setlocal cd /d "%~dp0.."powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0set-version.ps1" %* exit /b %errorlevel%
用法示例:
1 2 3 4 5 scripts\set -version.bat scripts\set -version.bat 1 .2 .0 scripts\set -version.bat patch scripts\set -version.bat minor scripts\set -version.bat major
版本脚本实现 路径:scripts/set-version.ps1
读取 src-tauri/Cargo.toml 当前版本,通过 npm version --no-git-tag-version 更新 package.json / package-lock.json,并同步 Rust 与 Tauri 配置(UTF-8 读写,避免中文乱码)。
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 135 136 137 138 139 140 141 142 143 144 145 146 param ( [Parameter (Position = 0 )] [string ]$Target ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest$Root = Split-Path -Parent $PSScriptRoot Set-Location $Root $Utf8NoBom = New-Object System.Text.UTF8Encoding $false function Read-Utf8File ([string]$Path ) { return [System.IO.File ]::ReadAllText($Path , [System.Text.Encoding ]::UTF8) } function Write-Utf8NoBomFile ([string]$Path , [string]$Content ) { [System.IO.File ]::WriteAllText($Path , $Content , $Utf8NoBom ) } function Get-CurrentVersion { $cargo = Join-Path $Root 'src-tauri\Cargo.toml' foreach ($line in ((Read-Utf8File $cargo ) -split "`r?`n" )) { if ($line -match '^\s*version\s*=\s*"([^"]+)"\s*$' ) { return $Matches [1 ] } } throw 'Could not read version from src-tauri\Cargo.toml' } function Test-SemVer ([string]$Version ) { return $Version -match '^\d+\.\d+\.\d+$' } function Bump-SemVer ([string]$Version , [string]$Kind ) { $parts = $Version -split '\.' if ($parts .Count -ne 3 ) { throw "Invalid version format: $Version " } $major = [int ]$parts [0 ] $minor = [int ]$parts [1 ] $patch = [int ]$parts [2 ] switch ($Kind .ToLowerInvariant()) { 'major' { return '{0}.0.0' -f ($major + 1 ) } 'minor' { return '{0}.{1}.0' -f $major , ($minor + 1 ) } 'patch' { return '{0}.{1}.{2}' -f $major , $minor , ($patch + 1 ) } default { throw "Unknown bump kind: $Kind " } } } function Update-TauriConf ([string]$NewVersion ) { $path = Join-Path $Root 'src-tauri\tauri.conf.json' $content = Read-Utf8File $path $content = $content -replace '"version"\s*:\s*"\d+\.\d+\.\d+"' , ('"version": "{0}"' -f $NewVersion ) $content = [regex ]::Replace( $content , '("title"\s*:\s*"[^"]*v)\d+\.\d+\.\d+(")' , { param ($match ) $match .Groups[1 ].Value + $NewVersion + $match .Groups[2 ].Value } ) Write-Utf8NoBomFile $path $content } function Update-CargoToml ([string]$NewVersion ) { $path = Join-Path $Root 'src-tauri\Cargo.toml' $content = Read-Utf8File $path $content = $content -replace '(?m)^version\s*=\s*"\d+\.\d+\.\d+"' , ('version = "{0}"' -f $NewVersion ) Write-Utf8NoBomFile $path $content } function Update-CargoLock ([string]$NewVersion ) { $path = Join-Path $Root 'src-tauri\Cargo.lock' $content = Read-Utf8File $path $content = [regex ]::Replace( $content , '(?ms)(name = "z-img-tools"\r?\nversion = ")\d+\.\d+\.\d+(")' , { param ($match ) $match .Groups[1 ].Value + $NewVersion + $match .Groups[2 ].Value } ) Write-Utf8NoBomFile $path $content } $current = Get-CurrentVersion if (-not $Target ) { Write-Host '' Write-Host "Current version: $current " Write-Host 'Enter new version (e.g. 1.2.0, or patch / minor / major):' $Target = Read-Host 'New version' Write-Host '' } if (-not $Target -or -not $Target .Trim()) { Write-Host 'Cancelled: no version entered.' exit 1 } $Target = $Target .Trim()$kind = $Target .ToLowerInvariant()if ($kind -in @ ('patch' , 'minor' , 'major' )) { $newVersion = Bump-SemVer $current $kind } elseif (Test-SemVer $Target ) { $newVersion = $Target } else { Write-Error "Invalid target: $Target . Use semver (e.g. 1.2.0) or patch/minor/major." exit 1 } if ($newVersion -eq $current ) { Write-Host "Version unchanged: $current " exit 0 } Write-Host "Updating version: $current -> $newVersion " Write-Host '' $npm = Get-Command npm -ErrorAction SilentlyContinueif (-not $npm ) { Write-Error 'npm not found. Install Node.js to update package.json and package-lock.json.' exit 1 } Push-Location $Root try { npm version $newVersion --no-git-tag-version --allow-same-version | Out-Null if ($LASTEXITCODE -ne 0 ) { throw 'npm version failed' } } finally { Pop-Location } Update-CargoToml $newVersion Update-CargoLock $newVersion Update-TauriConf $newVersion Write-Host 'Updated files:' Write-Host ' - package.json' Write-Host ' - package-lock.json' Write-Host ' - src-tauri\Cargo.toml' Write-Host ' - src-tauri\Cargo.lock' Write-Host ' - src-tauri\tauri.conf.json' Write-Host '' Write-Host "Done. Current version: $newVersion "
同步的文件:
文件
更新内容
package.json / package-lock.json
version(npm version)
src-tauri/Cargo.toml
[package] 的 version
src-tauri/Cargo.lock
z-img-tools 包版本
src-tauri/tauri.conf.json
version、窗口 title 中的 vX.Y.Z
同步 workflow 路径:scripts/release/ensure-release-gha.bat
调用 PowerShell,将 workflow 上传到发布仓。
1 2 3 4 5 6 7 8 9 10 11 12 13 @echo off setlocal cd /d "%~dp0..\.."if not defined GITHUB_REPO set "GITHUB_REPO=psvmc/z-img-tools-releases"where gh >nul 2 >&1 || ( echo [error] gh not found. Run: gh auth login exit /b 1 ) powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0ensure-release-gha.ps1" -Repo "%GITHUB_REPO% " exit /b %errorlevel%
上传 workflow 路径:scripts/release/ensure-release-gha.ps1
通过 GitHub Contents API 创建或更新 .github/workflows/release-all.yml。
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 param ( [string ]$Repo = $env:GITHUB_REPO ) $ErrorActionPreference = 'Stop' if (-not $Repo ) { $Repo = 'psvmc/z-img-tools-releases' }$workflowName = 'release-all.yml' $workflowPath = ".github/workflows/$workflowName " $sourceFile = Join-Path $PSScriptRoot 'github-workflow-release-all.yml' if (-not (Test-Path $sourceFile )) { Write-Error "Missing $sourceFile " } $content = [IO.File ]::ReadAllText($sourceFile )$b64 = [Convert ]::ToBase64String([Text.Encoding ]::UTF8.GetBytes($content ))$apiArgs = @ ( "repos/$Repo /contents/$workflowPath " , '-X' , 'PUT' , '-f' , "message=Add or update release-all workflow" , '-f' , "content=$b64 " , '-f' , 'branch=main' ) $prevEap = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' $existingJson = gh api "repos/$Repo /contents/$workflowPath " 2 >$null $checkCode = $LASTEXITCODE $ErrorActionPreference = $prevEap if ($checkCode -eq 0 -and $existingJson ) { $existing = $existingJson | ConvertFrom-Json if ($existing .sha) { $apiArgs += '-f' , "sha=$ ($existing .sha)" Write-Host "Updating workflow on $Repo ..." } } else { Write-Host "Creating workflow on $Repo ..." } gh api @apiArgs if ($LASTEXITCODE -ne 0 ) { exit $LASTEXITCODE }Write-Host "Workflow ready: https://github.com/$Repo /blob/main/$workflowPath "
三平台构建 workflow 路径:scripts/release/github-workflow-release-all.yml(同步到发布仓后为 .github/workflows/release-all.yml)
matrix 并行构建 Win / Linux / macOS,汇总 artifact 后发布 Release。构建使用 Rust + npm + Tauri CLI ,与 Wails 方案不同。
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 name: Release All Platforms on: workflow_dispatch: inputs: tag: description: Release tag (e.g. 1.0 .0 ) required: true gitee_repo: description: Gitee source URL (no credentials) required: false default: https://gitee.com/psvmc/z-img-tools-tauri.git permissions: contents: write jobs: build: strategy: fail-fast: false matrix: include: - os: windows-latest platform: windows - os: ubuntu-22.04 platform: linux - os: macos-latest platform: macos runs-on: ${{ matrix.os }} steps: - name: Clone from Gitee env: GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }} GITEE_REPO: ${{ inputs.gitee_repo }} RELEASE_TAG: ${{ inputs.tag }} shell: bash run: | set -euo pipefail if [ -z "${GITEE_TOKEN:-}" ]; then echo "::error::Set secret GITEE_TOKEN on this repository (Gitee private token, scope: projects)" exit 1 fi REPO_PATH="${GITEE_REPO#https://}" AUTH_URL="https://oauth2:${GITEE_TOKEN}@${REPO_PATH}" git config --global http.postBuffer 524288000 git config --global http.version HTTP/1.1 TAG_CANDIDATES=("$RELEASE_TAG") if [[ "$RELEASE_TAG" == v* ]]; then TAG_CANDIDATES+=("${RELEASE_TAG#v}") else TAG_CANDIDATES+=("v${RELEASE_TAG}") fi clone_ok=false for TAG in "${TAG_CANDIDATES[@]}" ; do for attempt in 1 2 3 ; do echo "Cloning tag ${TAG} (attempt ${attempt})..." rm -rf repo if git clone --depth 1 --branch "$TAG" "$AUTH_URL" repo; then echo "Cloned tag ${TAG}" clone_ok=true break 2 fi sleep $((attempt * 5 )) done done if [ "$clone_ok" != true ]; then echo "::error::Failed to clone tag ${RELEASE_TAG} from Gitee. Push the tag first: git tag ${RELEASE_TAG#v} && git push origin ${RELEASE_TAG#v}" exit 1 fi - name: Install NSIS (Windows) if: matrix.platform == 'windows' shell: pwsh run: choco install nsis -y --no-progress - name: Install Linux deps if: matrix.platform == 'linux' run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - uses: actions/setup-node@v4 with: node-version: "20" cache: npm cache-dependency-path: repo/package-lock.json - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.platform == 'macos' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - uses: swatinem/rust-cache@v2 with: workspaces: repo/src-tauri -> target - name: Build working-directory: repo shell: bash run: | set -euo pipefail npm ci mkdir -p release if [ "${{ matrix.platform }} " = "windows" ]; then npm run tauri build -- --bundles nsis find src-tauri/target -path '*/release/bundle/nsis/*-setup.exe' -exec cp -f {} release/ \; elif [ "${{ matrix.platform }} " = "linux" ]; then npm run tauri build -- --bundles deb find src-tauri/target -path '*/release/bundle/deb/*.deb' -exec cp -f {} release/ \; else npm run tauri build -- --target universal-apple-darwin --bundles dmg find src-tauri/target -path '*/release/bundle/dmg/*.dmg' -exec cp -f {} release/ \; fi if [ -z "$(ls -A release 2>/dev/null)" ]; then echo "::error::No release artifacts found" find src-tauri/target -path '*/release/bundle/*' -type f | head -50 exit 1 fi ls -la release/ - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ matrix.platform }} path: repo/release/* publish: needs: build runs-on: ubuntu-latest steps: - name: Download artifacts uses: actions/download-artifact@v4 with: path: dist merge-multiple: true - name: List release files run: find dist -type f -ls - name: Generate release notes from git log id: changelog env: GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }} GITEE_REPO: ${{ inputs.gitee_repo }} RELEASE_TAG: ${{ inputs.tag }} shell: bash run: | set -euo pipefail if [ -z "${GITEE_TOKEN:-}" ]; then echo "::error::Set secret GITEE_TOKEN on this repository" exit 1 fi REPO_PATH="${GITEE_REPO#https://}" AUTH_URL="https://oauth2:${GITEE_TOKEN}@${REPO_PATH}" git clone --filter=blob:none "$AUTH_URL" repo cd repo git fetch --tags origin resolve_tag() { local input="$1" local candidates=("$input") if [[ "$input" == v* ]]; then candidates+=("$ {input#v }") else candidates+=(" v$ {input }") fi for candidate in " $ {candidates [@ ]}"; do if git rev-parse -q --verify " refs/tags/$ {candidate }^ {commit }" >/dev/null; then echo " $candidate" return 0 fi done echo "::error::Tag ${input} not found in Gitee repository" exit 1 } TAG="$(resolve_tag "$RELEASE_TAG" )" mapfile -t SORTED_TAGS < <(git tag -l --sort=-version:refname) PREV_TAG="" for i in "${!SORTED_TAGS[@]}" ; do if [ "${SORTED_TAGS[$i]}" = "$TAG" ]; then if [ $((i + 1 )) -lt $ { PREV_TAG="$ {SORTED_TAGS [$((i + 1 )) ]}" fi break fi done if [ -n " $PREV_TAG" ]; then RANGE="$ {PREV_TAG }..$ {TAG }" HEADER=" else RANGE="$TAG" HEADER="## Initial release" fi NOTES="$(git log "$RANGE" --pretty=format:'- %s (%h)' --no-merges || true )" if [ -z "$NOTES" ]; then NOTES="- No commits in this range" fi BODY="$ {HEADER } $ {NOTES }" { echo 'body<<CHANGELOG_EOF' echo " $BODY" echo 'CHANGELOG_EOF' } >> "$GITHUB_OUTPUT" - name: Publish to GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ inputs.tag }} target_commitish: main name: ZImgTools ${{ inputs.tag }} body: ${{ steps.changelog.outputs.body }} files: dist/** env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
小结 这套方案的核心思路:
源码与发布分离 — Gitee 管代码,GitHub 管安装包
构建全部上云 — 三平台 matrix,本机零构建环境依赖
workflow 随源码版本管理 — 发版时 ensure-release-gha 同步到发布仓,改 workflow 不用去 GitHub 网页手改
更新日志来自 git log — 自动取上一 tag 到当前 tag 之间的 commit,写入 Release 说明与应用内更新提示
与应用内更新联动 — Release 资源命名需与 updater 后缀规则一致(ZImgTools_*-setup.exe 等);发版后客户端通过 GitHub API 拉取本仓 latest Release
本地开发构建仍可用 npm run tauri build(Windows 下自动出 NSIS),与发版流程无关。
发版检查清单
执行 scripts\set-version.bat 升级版本并提交(含 src-tauri/icons/ 图标若有更新)
代码已 push 到 Gitee,git tag 且 git push origin <tag>
发布仓 Secret GITEE_TOKEN 有效(日志中为 ***)
执行 scripts\publish-release.bat,用 gh run watch -R psvmc/z-img-tools-releases 查看进度