Files
coa-server-manager/.github/workflows/release.yml
ДмитрийandClaude Opus 5.5 2224afd2ea release: build the frontend before the tests
tauri::generate_context! needs ../dist. The tests run before tauri-action builds the frontend, so a
run without a warm target cache cannot compile the app (seen on preview/modules-v2, run 42224568581).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 19:47:11 +05:00

124 lines
6.2 KiB
YAML

name: release
# Builds the signed Windows installer and the updater manifest (latest.json) for CoA Server Manager itself.
# Trigger: push a tag like v0.2.0, or run it by hand. The installer is signed with the updater key (secrets below);
# server packages are a separate pipeline in coa-server-build.
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
publish_release:
description: Publish the GitHub release (leave off to prepare signed artifacts)
type: boolean
default: false
permissions:
contents: write
jobs:
release-intent:
runs-on: ubuntu-latest
outputs:
build: ${{ steps.intent.outputs.build }}
steps:
- id: intent
name: Keep already published release assets immutable
env:
GH_TOKEN: ${{ github.token }}
REQUEST_PUBLISH: ${{ inputs.publish_release && 'true' || 'false' }}
run: |
python3 - <<'PY'
import json, os, subprocess
build = True
if os.environ['GITHUB_EVENT_NAME'] == 'push' or os.environ.get('REQUEST_PUBLISH') == 'true':
result = subprocess.run(['gh', 'api', 'repos/' + os.environ['GITHUB_REPOSITORY'] + '/releases/tags/' + os.environ['GITHUB_REF_NAME']], capture_output=True, text=True)
if result.returncode == 0:
build = json.loads(result.stdout)['draft']
if not build:
print('Published release already exists; preserve its assets and prerelease status.')
elif 'HTTP 404' not in result.stderr:
raise RuntimeError('Could not establish whether this release already exists: ' + result.stderr)
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
output.write('build=' + str(build).lower() + '\n')
PY
installer:
needs: release-intent
if: needs.release-intent.outputs.build == 'true'
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- name: Validate release version
shell: pwsh
run: |
$version = (Get-Content package.json -Raw | ConvertFrom-Json).version
if ($env:GITHUB_EVENT_NAME -eq 'push' -and $env:GITHUB_REF_NAME -ne "v$version") {
throw 'The release tag does not match the application version.'
}
- name: Cache Rust dependencies and compiled targets
uses: actions/cache@v6
continue-on-error: true
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: manager-installer-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}-${{ github.run_id }}
restore-keys: manager-installer-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}-
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
# tauri::generate_context! needs ../dist; without a warm target cache the tests do not compile without it.
- run: npm run build
- name: Tests
run: cargo test --workspace --locked -- --test-threads=1
- run: node --experimental-strip-types --test tools/*.test.mjs
- run: python -B -m unittest discover -s tools -p test_launcher_bridge.py
- name: Build and sign (publish only when requested)
id: tauri
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ (github.event_name == 'push' || inputs.publish_release) && github.token || '' }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: ${{ (github.event_name == 'push' || inputs.publish_release) && 'v__VERSION__' || '' }}
releaseName: CoA Server Manager v__VERSION__
releaseBody: See the assets to download the installer. Existing installations update themselves.
releaseDraft: false
prerelease: false
args: --bundles nsis
includeUpdaterJson: true
- name: Preserve the exact signed installer and updater metadata
shell: pwsh
env:
MANAGER_ARTIFACTS: ${{ steps.tauri.outputs.artifactPaths }}
run: |
$paths = @($env:MANAGER_ARTIFACTS | ConvertFrom-Json)
$installers = @($paths | Where-Object { $_ -like '*-setup.exe' })
if ($installers.Count -ne 1) { throw 'Expected exactly one Windows NSIS installer.' }
$installer = [IO.Path]::GetFullPath($installers[0])
$workspace = [IO.Path]::GetFullPath((Get-Location).Path).TrimEnd('\') + '\'
if (-not $installer.StartsWith($workspace, [StringComparison]::OrdinalIgnoreCase)) { throw 'Installer escaped the workspace.' }
if (-not (Test-Path -LiteralPath "$installer.sig")) { throw 'The installer has no updater signature.' }
$version = (Get-Content package.json -Raw | ConvertFrom-Json).version
$name = [IO.Path]::GetFileName($installer).Replace(' ', '.')
New-Item -ItemType Directory -Force _prepared-manager | Out-Null
Copy-Item -LiteralPath $installer -Destination "_prepared-manager/$name"
Copy-Item -LiteralPath "$installer.sig" -Destination "_prepared-manager/$name.sig"
$platform = @{
signature = (Get-Content -LiteralPath "$installer.sig" -Raw).Trim()
url = "https://github.com/$env:GITHUB_REPOSITORY/releases/download/v$version/$([Uri]::EscapeDataString($name))"
}
@{ version = $version; notes = 'See the release notes.'; pub_date = [DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ'); platforms = @{ 'windows-x86_64' = $platform } } |
ConvertTo-Json -Depth 8 | Set-Content _prepared-manager/latest.json -Encoding utf8NoBOM
@{ schema = 1; version = $version; source = $env:GITHUB_SHA; run = $env:GITHUB_RUN_ID; installer = $name; sha256 = (Get-FileHash -LiteralPath $installer -Algorithm SHA256).Hash.ToLowerInvariant() } |
ConvertTo-Json | Set-Content _prepared-manager/prepared-source.json -Encoding utf8NoBOM
- uses: actions/upload-artifact@v4
with:
name: prepared-manager-windows
path: _prepared-manager/*
if-no-files-found: error
retention-days: 14