ci(forgejo): release workflow on tag push -> MSI artifact + release asset
.forgejo/workflows/release.yml triggers on annotated tag pushes matching v*.*.*. The workflow runs on a Windows runner (required for WiX MSI), restores and builds the Windows solution filter, runs unit tests (skipping the requires=ndi tier — CI runners don't have NDI), publishes TeamsISO.App + TeamsISO.Console for win-x64 framework-dependent, builds the WiX MSI scaffold, and uploads the MSI both as a workflow artifact (downloadable from the run page) and as an asset on the auto-created Release for the tag. Tag version is parsed from refs/tags/vX.Y.Z and threaded into /p:Version on every dotnet build invocation so the publish output, the assembly metadata, and the MSI ProductVersion all agree. Release-asset upload uses the Forgejo REST API directly via curl + Invoke-RestMethod rather than depending on a third-party action; if the auto-create-release-on-tag-push setting is off, the workflow creates the release itself. Pre-release flag is set when the tag contains -alpha/-beta/-rc. docs/RELEASING.md walks through cutting a release and flags the code-signing TODO (SignOutput property is wired but no cert; SmartScreen will warn on first launch until that lands).
This commit is contained in:
parent
1d85396a90
commit
f07aad1c6a
2 changed files with 182 additions and 0 deletions
132
.forgejo/workflows/release.yml
Normal file
132
.forgejo/workflows/release.yml
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
name: Release
|
||||
|
||||
# Triggered by pushing an annotated tag of the form v1.2.3 (or any v-prefixed
|
||||
# semver). The job runs on a Windows runner because building the WiX MSI
|
||||
# requires the WiX SDK which is supported on Windows; the engine + console
|
||||
# can in principle be built on Linux, but for simplicity we do everything in
|
||||
# one job here so the publish output paths line up for the installer.
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
build-msi:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # tags + full history needed to derive the version
|
||||
|
||||
- name: Derive version from tag
|
||||
id: ver
|
||||
shell: pwsh
|
||||
run: |
|
||||
# GITHUB_REF is refs/tags/vX.Y.Z; strip the prefix.
|
||||
$tag = "${env:GITHUB_REF}".Replace('refs/tags/', '')
|
||||
$version = $tag.TrimStart('v')
|
||||
"tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
"version=$version" >> $env:GITHUB_OUTPUT
|
||||
Write-Host "Building version $version (tag $tag)"
|
||||
|
||||
- name: Setup .NET 8
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore (Windows solution filter)
|
||||
run: dotnet restore TeamsISO.Windows.slnf
|
||||
|
||||
- name: Build (Release, treat warnings as errors)
|
||||
run: dotnet build TeamsISO.Windows.slnf --configuration Release --no-restore /p:Version=${{ steps.ver.outputs.version }}
|
||||
|
||||
- name: Run unit tests (excluding requires=ndi)
|
||||
run: >
|
||||
dotnet test TeamsISO.Windows.slnf
|
||||
--configuration Release
|
||||
--no-build
|
||||
--filter "Category!=ndi&requires!=ndi"
|
||||
|
||||
- name: Publish TeamsISO.App (framework-dependent, win-x64)
|
||||
run: >
|
||||
dotnet publish src/TeamsISO.App/TeamsISO.App.csproj
|
||||
--configuration Release
|
||||
--runtime win-x64
|
||||
--self-contained false
|
||||
--output publish/TeamsISO
|
||||
/p:Version=${{ steps.ver.outputs.version }}
|
||||
|
||||
- name: Publish TeamsISO.Console (framework-dependent, win-x64)
|
||||
run: >
|
||||
dotnet publish src/TeamsISO.Console/TeamsISO.Console.csproj
|
||||
--configuration Release
|
||||
--runtime win-x64
|
||||
--self-contained false
|
||||
--output publish/TeamsISO-Console
|
||||
/p:Version=${{ steps.ver.outputs.version }}
|
||||
|
||||
- name: Build MSI installer
|
||||
run: >
|
||||
dotnet build installer/TeamsISO.Installer.wixproj
|
||||
--configuration Release
|
||||
/p:Version=${{ steps.ver.outputs.version }}
|
||||
|
||||
- name: Locate MSI
|
||||
id: msi
|
||||
shell: pwsh
|
||||
run: |
|
||||
$msi = Get-ChildItem -Path installer/bin -Recurse -Filter '*.msi' | Select-Object -First 1
|
||||
if (-not $msi) { throw "No MSI produced under installer/bin." }
|
||||
"path=$($msi.FullName)" >> $env:GITHUB_OUTPUT
|
||||
"name=$($msi.Name)" >> $env:GITHUB_OUTPUT
|
||||
Write-Host "MSI: $($msi.FullName) ($($msi.Length) bytes)"
|
||||
|
||||
- name: Upload MSI as workflow artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ steps.msi.outputs.name }}
|
||||
path: ${{ steps.msi.outputs.path }}
|
||||
|
||||
# Forgejo doesn't ship a stable upload-release-asset action, so we use
|
||||
# the REST API directly. This: (1) finds the release that the tag push
|
||||
# auto-created, (2) uploads the MSI as an asset on it. Requires that
|
||||
# the repo's "Create a release on tag push" setting is on, OR that the
|
||||
# release was created beforehand. If no release exists, we create one.
|
||||
- name: Attach MSI to release
|
||||
shell: pwsh
|
||||
env:
|
||||
FORGE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
FORGE_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
MSI_PATH: ${{ steps.msi.outputs.path }}
|
||||
MSI_NAME: ${{ steps.msi.outputs.name }}
|
||||
run: |
|
||||
$headers = @{ Authorization = "token $env:FORGE_TOKEN" }
|
||||
|
||||
# Find the release for this tag.
|
||||
try {
|
||||
$release = Invoke-RestMethod -Method Get `
|
||||
-Uri "$env:FORGE_API/releases/tags/$env:TAG" -Headers $headers
|
||||
} catch {
|
||||
Write-Host "No release found for $env:TAG; creating one."
|
||||
$body = @{
|
||||
tag_name = $env:TAG
|
||||
name = "TeamsISO $env:TAG"
|
||||
body = "Automated build from tag $env:TAG."
|
||||
draft = $false
|
||||
prerelease = $env:TAG -match '-(alpha|beta|rc)'
|
||||
} | ConvertTo-Json
|
||||
$release = Invoke-RestMethod -Method Post `
|
||||
-Uri "$env:FORGE_API/releases" -Headers $headers `
|
||||
-ContentType 'application/json' -Body $body
|
||||
}
|
||||
|
||||
# Upload the MSI as an asset.
|
||||
$uploadUri = "$env:FORGE_API/releases/$($release.id)/assets?name=$env:MSI_NAME"
|
||||
curl.exe -fSL `
|
||||
-H "Authorization: token $env:FORGE_TOKEN" `
|
||||
-H "Content-Type: application/octet-stream" `
|
||||
--upload-file "$env:MSI_PATH" `
|
||||
"$uploadUri"
|
||||
Write-Host "Asset $env:MSI_NAME attached to release $env:TAG."
|
||||
50
docs/RELEASING.md
Normal file
50
docs/RELEASING.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Releasing TeamsISO
|
||||
|
||||
The release workflow at `.forgejo/workflows/release.yml` runs on **annotated tag pushes
|
||||
matching `v*.*.*`**. It builds, tests, publishes, packages an MSI, and uploads the
|
||||
MSI as a release asset.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A **Windows runner** registered to this Forgejo instance. WiX MSI builds require
|
||||
Windows; the existing CI runs on Linux for unit tests, but releases need a
|
||||
separate Windows runner. Register one with `forgejo-runner register` against a
|
||||
Windows host that has the .NET 8 SDK + WiX SDK access (the WiX SDK pulls itself
|
||||
via NuGet at build time, so no separate install).
|
||||
- The repository's **Create release on tag push** setting on (default), or skip it —
|
||||
the workflow will create the release if one doesn't exist.
|
||||
|
||||
## Cutting a release
|
||||
|
||||
```sh
|
||||
# Bump the version in Directory.Build.props if you haven't already.
|
||||
git tag -a v1.0.0 -m "TeamsISO 1.0.0"
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
The workflow will:
|
||||
|
||||
1. Restore + build `TeamsISO.Windows.slnf` in Release with the tag's version.
|
||||
2. Run unit tests (the `requires=ndi` integration tier is skipped — it needs a
|
||||
real NDI runtime which a CI runner won't have).
|
||||
3. Publish `TeamsISO.App` and `TeamsISO.Console` for `win-x64`,
|
||||
framework-dependent (.NET 8 Desktop runtime is the user's responsibility).
|
||||
4. Build `installer/TeamsISO.Installer.wixproj`, producing
|
||||
`TeamsISO-Setup-<version>.msi`.
|
||||
5. Upload the MSI as a workflow artifact (downloadable from the run page).
|
||||
6. Attach the MSI to the GitHub-style Release for the tag, creating the release
|
||||
first if it doesn't exist. Pre-release flag is set automatically when the
|
||||
tag contains `-alpha`, `-beta`, or `-rc`.
|
||||
|
||||
## Code signing (TODO)
|
||||
|
||||
The `wixproj` has a `SignOutput` property hook but no actual cert wiring. For a
|
||||
v1.0 release, sign the MSI with an EV cert before publishing:
|
||||
|
||||
1. Add a `SIGNING_CERT_BASE64` and `SIGNING_CERT_PASSWORD` to repo Secrets.
|
||||
2. Decode the cert into the runner's cert store at the start of the workflow.
|
||||
3. Set `/p:SignOutput=true` on the `dotnet build` of the wixproj and configure
|
||||
`signtool` invocation (the installer project will need a custom target).
|
||||
|
||||
Until that lands, downstream users will see the standard Windows SmartScreen
|
||||
warning on first launch — annoying but not blocking for early adopters.
|
||||
Loading…
Reference in a new issue