feat(release): 发布 Windows Portable 与 MSI 双产物

- Windows AMD64 与 ARM64 同时生成 Portable EXE 和 MSI 安装包
- 统一 stable 与 dev 的 MSI 产品身份并支持通道切换升级
- 发布清单同时包含两类资产及对应 SHA256 校验信息
- 在 Windows workflow 中执行双模式更新与 WiX 契约测试
This commit is contained in:
Syngnat
2026-07-13 09:25:21 +08:00
parent 431f740476
commit 7bdce19d01
5 changed files with 354 additions and 18 deletions

View File

@@ -25,6 +25,11 @@ jobs:
with:
go-version-file: 'go.mod'
- name: Test release asset contracts
run: |
python3 tools/generate-update-latest-manifest.test.py
python3 tools/windows-release-artifacts.test.py
- name: Setup Node
uses: actions/setup-node@v5
with:
@@ -391,6 +396,24 @@ jobs:
}
& $upxCmd --version
- name: Install WiX Toolset (Windows)
if: contains(matrix.platform, 'windows')
shell: pwsh
run: |
$wixVersion = "6.0.2"
$wixTools = Join-Path $env:RUNNER_TEMP "wix-tools"
dotnet tool install wix --tool-path $wixTools --version $wixVersion
if ($LASTEXITCODE -ne 0) {
throw "WiX Toolset 安装失败,退出码: $LASTEXITCODE"
}
$wixTools | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
$env:PATH = "$wixTools;$env:PATH"
wix extension add --global "WixToolset.UI.wixext/$wixVersion"
if ($LASTEXITCODE -ne 0) {
throw "WiX UI 扩展安装失败,退出码: $LASTEXITCODE"
}
wix --version
- name: Install Linux Dependencies
if: contains(matrix.platform, 'linux')
run: |
@@ -519,7 +542,7 @@ jobs:
- name: Test Windows Online Updater (Windows AMD64)
if: ${{ matrix.platform == 'windows/amd64' }}
shell: pwsh
run: go test ./internal/app -run '^TestWindowsPowerShellUpdater' -count=1 -timeout=3m
run: go test ./internal/app -run '^(TestWindowsPowerShellUpdater|TestExpectedAssetNameForExecutableUsesWindowsPortableSuffix|TestShouldWindowsUpdateLaunchDownloadedAssetDirectly|TestResolveUpdateInstallMode|TestExpectedAssetNameForWindowsInstallMode|TestResolveUpdateWorkspaceDirForPlatformSeparatesMSIFromInstallDirectory|TestValidateUpdatePackageForCurrentInstallMode|TestInstallUpdateAndRestartMSI|TestBuildWindowsMSIUpdatePowerShellScript|TestBuildWindowsMSILaunchCommand|TestResolveWindowsMSIExecPath)' -count=1 -timeout=3m
# ---- 生成 dev 版本号 ----
- name: Generate Dev Version
@@ -861,14 +884,16 @@ jobs:
mv "$DMG_NAME" "../../$FINAL_NAME"
# Windows Packaging
- name: Package Windows EXE
- name: Package Windows Portable EXE and MSI
if: contains(matrix.platform, 'windows')
shell: pwsh
run: |
Set-Location build/bin
$version = "${{ steps.version.outputs.version }}"
$target = "${{ matrix.build_name }}"
$finalExeName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.exe"
$archName = "${{ matrix.arch_name }}"
$portableExeName = "GoNavi-$version-${{ matrix.os_name }}-$archName${{ matrix.artifact_suffix }}-Portable.exe"
$msiName = "GoNavi-$version-${{ matrix.os_name }}-$archName${{ matrix.artifact_suffix }}-Installer.msi"
if (Test-Path "$target.exe") {
$finalExe = "$target.exe"
@@ -911,8 +936,55 @@ jobs:
}
}
Write-Host "📦 输出 Windows 可执行文件 $finalExeName..."
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$finalExeName" -Force
Write-Host "📦 输出 Windows Portable 可执行文件 $portableExeName..."
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$portableExeName" -Force
$runNumber = [int64]$env:GITHUB_RUN_NUMBER
if ($runNumber -le 0 -or $runNumber -gt 65535) {
throw "GitHub run number 无法映射为 MSI 版本号: $runNumber"
}
$productVersion = "255.0.$runNumber"
$wixArch = if ($archName -eq "Arm64") { "arm64" } else { "x64" }
$componentGuid = if ($archName -eq "Arm64") {
"7F4C7757-329F-42B0-B312-D4B8AD50415E"
} else {
"0BCEE70B-9CF2-449C-9ADE-190188493234"
}
$licenseText = Get-Content -LiteralPath "..\\..\\LICENSE" -Raw
$escapedLicense = $licenseText.Replace('\', '\\').Replace('{', '\{').Replace('}', '\}')
$escapedLicense = $escapedLicense -replace "`r?`n", "\par`r`n"
$licenseRtf = Join-Path $env:RUNNER_TEMP "GoNavi-License.rtf"
$rtfContent = "{\rtf1\ansi\deff0{\fonttbl{\f0 Segoe UI;}}\f0\fs18 $escapedLicense}"
Set-Content -LiteralPath $licenseRtf -Value $rtfContent -Encoding ascii
$sourceExe = (Resolve-Path -LiteralPath $finalExe).Path
$wxsPath = (Resolve-Path -LiteralPath "..\\windows\\installer.wxs").Path
$iconPath = (Resolve-Path -LiteralPath "..\\windows\\icon.ico").Path
$installMarkerPath = Join-Path $env:RUNNER_TEMP ".gonavi-msi-install"
Set-Content -LiteralPath $installMarkerPath -Value "MSI" -Encoding ascii -NoNewline
$installMarkerPath = (Resolve-Path -LiteralPath $installMarkerPath).Path
$wixIntermediateDir = Join-Path $env:RUNNER_TEMP "wixobj-$archName"
Write-Host "📦 生成 Windows MSI 安装程序 $msiName (version=$productVersion arch=$wixArch)..."
wix build $wxsPath `
-arch $wixArch `
-ext "WixToolset.UI.wixext/6.0.2" `
-d "ProductName=GoNavi" `
-d "ProductVersion=$productVersion" `
-d "UpgradeCode=CDD6BF2F-ED1E-4345-A0AB-DCDB7E15FB23" `
-d "ComponentGuid=$componentGuid" `
-d "InstallFolderName=GoNavi" `
-d "RegistryKeyName=GoNavi" `
-d "SourceExe=$sourceExe" `
-d "InstallMarker=$installMarkerPath" `
-d "IconPath=$iconPath" `
-d "LicenseRtf=$licenseRtf" `
-pdbtype none `
-intermediatefolder $wixIntermediateDir `
-out "..\\..\\$msiName"
if ($LASTEXITCODE -ne 0) {
throw "WiX MSI 构建失败,退出码: $LASTEXITCODE"
}
# Linux Packaging
- name: Package Linux
@@ -1003,6 +1075,7 @@ jobs:
path: |
GoNavi-*.dmg
GoNavi-*.exe
GoNavi-*.msi
GoNavi-*.tar.gz
GoNavi-*.AppImage
drivers/**

View File

@@ -21,6 +21,11 @@ jobs:
with:
go-version-file: 'go.mod'
- name: Test release asset contracts
run: |
python3 tools/generate-update-latest-manifest.test.py
python3 tools/windows-release-artifacts.test.py
- name: Setup Node
uses: actions/setup-node@v5
with:
@@ -343,6 +348,24 @@ jobs:
}
& $upxCmd --version
- name: Install WiX Toolset (Windows)
if: contains(matrix.platform, 'windows')
shell: pwsh
run: |
$wixVersion = "6.0.2"
$wixTools = Join-Path $env:RUNNER_TEMP "wix-tools"
dotnet tool install wix --tool-path $wixTools --version $wixVersion
if ($LASTEXITCODE -ne 0) {
throw "WiX Toolset 安装失败,退出码: $LASTEXITCODE"
}
$wixTools | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
$env:PATH = "$wixTools;$env:PATH"
wix extension add --global "WixToolset.UI.wixext/$wixVersion"
if ($LASTEXITCODE -ne 0) {
throw "WiX UI 扩展安装失败,退出码: $LASTEXITCODE"
}
wix --version
# Linux Dependencies (GTK3, WebKit2GTK required by Wails)
- name: Install Linux Dependencies
if: contains(matrix.platform, 'linux')
@@ -475,7 +498,7 @@ jobs:
- name: Test Windows Online Updater (Windows AMD64)
if: ${{ matrix.platform == 'windows/amd64' }}
shell: pwsh
run: go test ./internal/app -run '^TestWindowsPowerShellUpdater' -count=1 -timeout=3m
run: go test ./internal/app -run '^(TestWindowsPowerShellUpdater|TestExpectedAssetNameForExecutableUsesWindowsPortableSuffix|TestShouldWindowsUpdateLaunchDownloadedAssetDirectly|TestResolveUpdateInstallMode|TestExpectedAssetNameForWindowsInstallMode|TestResolveUpdateWorkspaceDirForPlatformSeparatesMSIFromInstallDirectory|TestValidateUpdatePackageForCurrentInstallMode|TestInstallUpdateAndRestartMSI|TestBuildWindowsMSIUpdatePowerShellScript|TestBuildWindowsMSILaunchCommand|TestResolveWindowsMSIExecPath)' -count=1 -timeout=3m
- name: Build
shell: bash
@@ -806,7 +829,7 @@ jobs:
mv "$DMG_NAME" "../../$FINAL_NAME"
# Windows Packaging
- name: Package Windows EXE
- name: Package Windows Portable EXE and MSI
if: contains(matrix.platform, 'windows')
shell: pwsh
run: |
@@ -816,7 +839,9 @@ jobs:
$version = $version.Substring(1)
}
$target = "${{ matrix.build_name }}"
$finalExeName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.exe"
$archName = "${{ matrix.arch_name }}"
$portableExeName = "GoNavi-$version-${{ matrix.os_name }}-$archName${{ matrix.artifact_suffix }}-Portable.exe"
$msiName = "GoNavi-$version-${{ matrix.os_name }}-$archName${{ matrix.artifact_suffix }}-Installer.msi"
if (Test-Path "$target.exe") {
$finalExe = "$target.exe"
@@ -859,8 +884,61 @@ jobs:
}
}
Write-Host "📦 输出 Windows 可执行文件 $finalExeName..."
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$finalExeName" -Force
Write-Host "📦 输出 Windows Portable 可执行文件 $portableExeName..."
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$portableExeName" -Force
if ($version -match '^(?<major>\d+)\.(?<minor>\d+)\.(?<build>\d+)(?:[-+].*)?$') {
$major = [int]$Matches.major
$minor = [int]$Matches.minor
$build = [int]$Matches.build
} else {
throw "Windows MSI 版本必须是 major.minor.patch 格式,当前为: $version"
}
if ($major -gt 255 -or $minor -gt 255 -or $build -gt 65535) {
throw "Windows MSI 版本超出范围 (major/minor <= 255, patch <= 65535): $version"
}
$productVersion = "$major.$minor.$build"
$wixArch = if ($archName -eq "Arm64") { "arm64" } else { "x64" }
$componentGuid = if ($archName -eq "Arm64") {
"7F4C7757-329F-42B0-B312-D4B8AD50415E"
} else {
"0BCEE70B-9CF2-449C-9ADE-190188493234"
}
$licenseText = Get-Content -LiteralPath "..\\..\\LICENSE" -Raw
$escapedLicense = $licenseText.Replace('\', '\\').Replace('{', '\{').Replace('}', '\}')
$escapedLicense = $escapedLicense -replace "`r?`n", "\par`r`n"
$licenseRtf = Join-Path $env:RUNNER_TEMP "GoNavi-License.rtf"
$rtfContent = "{\rtf1\ansi\deff0{\fonttbl{\f0 Segoe UI;}}\f0\fs18 $escapedLicense}"
Set-Content -LiteralPath $licenseRtf -Value $rtfContent -Encoding ascii
$sourceExe = (Resolve-Path -LiteralPath $finalExe).Path
$wxsPath = (Resolve-Path -LiteralPath "..\\windows\\installer.wxs").Path
$iconPath = (Resolve-Path -LiteralPath "..\\windows\\icon.ico").Path
$installMarkerPath = Join-Path $env:RUNNER_TEMP ".gonavi-msi-install"
Set-Content -LiteralPath $installMarkerPath -Value "MSI" -Encoding ascii -NoNewline
$installMarkerPath = (Resolve-Path -LiteralPath $installMarkerPath).Path
$wixIntermediateDir = Join-Path $env:RUNNER_TEMP "wixobj-$archName"
Write-Host "📦 生成 Windows MSI 安装程序 $msiName (version=$productVersion arch=$wixArch)..."
wix build $wxsPath `
-arch $wixArch `
-ext "WixToolset.UI.wixext/6.0.2" `
-d "ProductName=GoNavi" `
-d "ProductVersion=$productVersion" `
-d "UpgradeCode=CDD6BF2F-ED1E-4345-A0AB-DCDB7E15FB23" `
-d "ComponentGuid=$componentGuid" `
-d "InstallFolderName=GoNavi" `
-d "RegistryKeyName=GoNavi" `
-d "SourceExe=$sourceExe" `
-d "InstallMarker=$installMarkerPath" `
-d "IconPath=$iconPath" `
-d "LicenseRtf=$licenseRtf" `
-pdbtype none `
-intermediatefolder $wixIntermediateDir `
-out "..\\..\\$msiName"
if ($LASTEXITCODE -ne 0) {
throw "WiX MSI 构建失败,退出码: $LASTEXITCODE"
}
# Linux Packaging (tar.gz and AppImage)
- name: Package Linux
@@ -962,6 +1040,7 @@ jobs:
path: |
GoNavi-*.dmg
GoNavi-*.exe
GoNavi-*.msi
GoNavi-*.tar.gz
GoNavi-*.AppImage
drivers/**