|
| 1 | +name: Release |
| 2 | + |
| 3 | +# Fires on a version tag: git tag v0.4.0 && git push origin v0.4.0 |
| 4 | +on: |
| 5 | + push: |
| 6 | + tags: |
| 7 | + - "v*" |
| 8 | + |
| 9 | +permissions: |
| 10 | + contents: write # to create the release and upload assets |
| 11 | + |
| 12 | +jobs: |
| 13 | + release: |
| 14 | + runs-on: windows-latest |
| 15 | + steps: |
| 16 | + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| 17 | + |
| 18 | + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 |
| 19 | + with: |
| 20 | + toolchain: stable |
| 21 | + components: clippy, rustfmt |
| 22 | + |
| 23 | + # Without the signing key the build still produces an installer, then fails |
| 24 | + # at the very end when it cannot write the updater signature. That wastes a |
| 25 | + # full Tauri release build, so refuse up front with a message that says what |
| 26 | + # to do rather than a cryptic tauri error ten minutes later. |
| 27 | + - name: Require the signing key |
| 28 | + shell: pwsh |
| 29 | + env: |
| 30 | + KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} |
| 31 | + run: | |
| 32 | + if (-not $env:KEY) { |
| 33 | + Write-Output "::error::Secret TAURI_SIGNING_PRIVATE_KEY is not set. Without it the release cannot be signed and no installed copy would accept the update. Add it in Settings, Secrets and variables, Actions." |
| 34 | + exit 1 |
| 35 | + } |
| 36 | + Write-Output "Signing key present." |
| 37 | +
|
| 38 | + # The version lives in three manifests and the updater compares the running |
| 39 | + # build against the tag. If they disagree, every user is offered an update |
| 40 | + # that installs and then offers itself again forever. Fail here instead. |
| 41 | + - name: Verify version consistency |
| 42 | + shell: pwsh |
| 43 | + env: |
| 44 | + TAG: ${{ github.ref_name }} |
| 45 | + run: | |
| 46 | + $expected = $env:TAG -replace '^v', '' |
| 47 | + $checks = [ordered]@{ |
| 48 | + 'src-tauri/tauri.conf.json' = [regex]::Match((Get-Content src-tauri/tauri.conf.json -Raw), '"version"\s*:\s*"([^"]+)"').Groups[1].Value |
| 49 | + 'src-tauri/Cargo.toml' = [regex]::Match((Get-Content src-tauri/Cargo.toml -Raw), '(?m)^version\s*=\s*"([^"]+)"').Groups[1].Value |
| 50 | + 'package.json' = [regex]::Match((Get-Content package.json -Raw), '"version"\s*:\s*"([^"]+)"').Groups[1].Value |
| 51 | + } |
| 52 | + $bad = $false |
| 53 | + foreach ($name in $checks.Keys) { |
| 54 | + if ($checks[$name] -ne $expected) { |
| 55 | + Write-Output "::error::$name is '$($checks[$name])' but tag v$expected requires '$expected'" |
| 56 | + $bad = $true |
| 57 | + } |
| 58 | + } |
| 59 | + if ($bad) { |
| 60 | + Write-Output "Refusing to release: bump every version reference to $expected, commit, delete the tag and retag." |
| 61 | + exit 1 |
| 62 | + } |
| 63 | + Write-Output "All three manifests agree: $expected" |
| 64 | +
|
| 65 | + # A tag can point at a commit CI never saw, so re-run the gates here rather |
| 66 | + # than trusting that they were green somewhere. Never ship a broken build. |
| 67 | + - name: Frontend |
| 68 | + run: | |
| 69 | + npm ci |
| 70 | + npm run build |
| 71 | +
|
| 72 | + - name: Frontend tests |
| 73 | + run: | |
| 74 | + node src/lib/format.test.js |
| 75 | + node src/lib/icons.test.js |
| 76 | + node src/lib/tiles.test.js |
| 77 | +
|
| 78 | + - name: Format check |
| 79 | + working-directory: src-tauri |
| 80 | + run: cargo fmt --check |
| 81 | + |
| 82 | + - name: Clippy |
| 83 | + working-directory: src-tauri |
| 84 | + run: cargo clippy --no-deps --all-targets -- -D warnings |
| 85 | + |
| 86 | + - name: Test |
| 87 | + working-directory: src-tauri |
| 88 | + run: cargo test |
| 89 | + |
| 90 | + - name: Build signed |
| 91 | + env: |
| 92 | + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} |
| 93 | + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} |
| 94 | + run: npx tauri build |
| 95 | + |
| 96 | + # Two artifacts from ONE build, so the exe inside the zip is byte identical |
| 97 | + # to the one the installer writes: |
| 98 | + # the NSIS setup exe for people who want a normal Windows install, |
| 99 | + # the portable zip for install.ps1 and for voli, whose schema rejects |
| 100 | + # standalone executables outright. |
| 101 | + # The zip keeps a stable, unversioned name so that |
| 102 | + # releases/latest/download/AgentsBar-x64.zip always resolves, which is what |
| 103 | + # install.ps1 fetches. |
| 104 | + - name: Package |
| 105 | + id: package |
| 106 | + shell: pwsh |
| 107 | + env: |
| 108 | + TAG: ${{ github.ref_name }} |
| 109 | + run: | |
| 110 | + $version = $env:TAG -replace '^v', '' |
| 111 | + $rel = "src-tauri/target/release" |
| 112 | + $setup = "$rel/bundle/nsis/AgentsBar_${version}_x64-setup.exe" |
| 113 | + if (-not (Test-Path $setup)) { |
| 114 | + Write-Output "::error::Expected installer not found at $setup" |
| 115 | + Get-ChildItem "$rel/bundle/nsis" | ForEach-Object { Write-Output " found: $($_.Name)" } |
| 116 | + exit 1 |
| 117 | + } |
| 118 | + if (-not (Test-Path "$setup.sig")) { |
| 119 | + Write-Output "::error::No .sig beside the installer: the build was not signed and the updater would reject this release." |
| 120 | + exit 1 |
| 121 | + } |
| 122 | +
|
| 123 | + # agentsbar.exe sits at the archive ROOT, which is why the voli manifest |
| 124 | + # omits extract_dir and install.ps1 can copy the extraction into place. |
| 125 | + Compress-Archive -Path "$rel/agentsbar.exe" -DestinationPath "AgentsBar-x64.zip" -Force |
| 126 | + Copy-Item $setup "AgentsBar_${version}_x64-setup.exe" |
| 127 | + Copy-Item "$setup.sig" "AgentsBar_${version}_x64-setup.exe.sig" |
| 128 | +
|
| 129 | + # sha256sum format, ASCII: install.ps1 parses the hash out of the first |
| 130 | + # field, and PowerShell's utf8 would prepend a BOM that corrupts it. |
| 131 | + foreach ($f in @("AgentsBar-x64.zip", "AgentsBar_${version}_x64-setup.exe")) { |
| 132 | + $h = (Get-FileHash $f -Algorithm SHA256).Hash.ToLower() |
| 133 | + "$h $f" | Out-File "$f.sha256" -Encoding ascii -NoNewline |
| 134 | + Write-Output "$f $h" |
| 135 | + } |
| 136 | + "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append |
| 137 | +
|
| 138 | + # The updater fetches this manifest, checks the signature against the public |
| 139 | + # key pinned in tauri.conf.json, and only then offers the update. |
| 140 | + - name: Build updater manifest |
| 141 | + shell: pwsh |
| 142 | + env: |
| 143 | + TAG: ${{ github.ref_name }} |
| 144 | + run: | |
| 145 | + $version = $env:TAG -replace '^v', '' |
| 146 | + $sig = Get-Content "AgentsBar_${version}_x64-setup.exe.sig" -Raw |
| 147 | + $manifest = [ordered]@{ |
| 148 | + version = $version |
| 149 | + notes = "See https://github.com/${{ github.repository }}/releases/tag/$env:TAG" |
| 150 | + pub_date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") |
| 151 | + platforms = [ordered]@{ |
| 152 | + "windows-x86_64" = [ordered]@{ |
| 153 | + signature = $sig.Trim() |
| 154 | + url = "https://github.com/${{ github.repository }}/releases/download/$env:TAG/AgentsBar_${version}_x64-setup.exe" |
| 155 | + } |
| 156 | + } |
| 157 | + } |
| 158 | + $manifest | ConvertTo-Json -Depth 5 | Out-File latest.json -Encoding utf8 |
| 159 | + Write-Output "latest.json written for $version" |
| 160 | +
|
| 161 | + - name: Create release |
| 162 | + uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 |
| 163 | + with: |
| 164 | + generate_release_notes: true |
| 165 | + files: | |
| 166 | + AgentsBar_${{ steps.package.outputs.version }}_x64-setup.exe |
| 167 | + AgentsBar_${{ steps.package.outputs.version }}_x64-setup.exe.sha256 |
| 168 | + AgentsBar_${{ steps.package.outputs.version }}_x64-setup.exe.sig |
| 169 | + AgentsBar-x64.zip |
| 170 | + AgentsBar-x64.zip.sha256 |
| 171 | + latest.json |
| 172 | + install.ps1 |
| 173 | +
|
| 174 | + # The voli manifest needs the hash of the artifact as published, and it can |
| 175 | + # only be written after the upload. Print it so the registry PR is a copy |
| 176 | + # and paste rather than a download and hash by hand. |
| 177 | + - name: Voli manifest snippet |
| 178 | + shell: pwsh |
| 179 | + run: | |
| 180 | + $v = "${{ steps.package.outputs.version }}" |
| 181 | + $h = (Get-FileHash "AgentsBar-x64.zip" -Algorithm SHA256).Hash.ToLower() |
| 182 | + Write-Output "Add manifests/a/agentsbar/$v.toml to Topurrra/voli-registry with:" |
| 183 | + Write-Output "" |
| 184 | + Write-Output "[source.x64]" |
| 185 | + Write-Output "url = `"https://github.com/${{ github.repository }}/releases/download/v$v/AgentsBar-x64.zip`"" |
| 186 | + Write-Output "sha256 = `"$h`"" |
0 commit comments