Skip to content

Release

Release #3

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: Existing release tag to build (vYYYY.M.D.N or vYYYY.M.D.N-beta)
required: true
type: string
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
name: Publish ${{ inputs.tag || github.ref_name }}
runs-on: windows-latest
timeout-minutes: 45
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ inputs.tag || github.ref }}
- name: Resolve tag
id: tag
shell: pwsh
run: |
$tag = $env:RELEASE_TAG
$sha = (git rev-list -n 1 $tag).Trim()
if ($LASTEXITCODE -ne 0 -or -not $sha) { throw "Tag '$tag' does not exist in this checkout." }
$isBeta = $tag -match '^v\d{4}\.\d+\.\d+\.\d+-beta$'
if (-not $isBeta -and $tag -notmatch '^v\d{4}\.\d+\.\d+\.\d+$') {
throw "Tag '$tag' must be vYYYY.M.D.N or vYYYY.M.D.N-beta."
}
./.github/scripts/Assert-ReleaseVersionSequence.ps1 -Tag $tag
$version = $tag.Substring(1)
$channel = if ($isBeta) { 'beta' } else { 'release' }
Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value "version=$version"
Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value "channel=$channel"
Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value "prerelease=$($isBeta.ToString().ToLowerInvariant())"
Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value "sha=$sha"
Write-Host "tag=$tag version=$version channel=$channel prerelease=$isBeta"
- name: Script self-tests
shell: pwsh
run: |
./.github/scripts/Test-WorkflowSyntax.ps1
./.github/scripts/Test-ReleaseVersionSequence.ps1
./.github/scripts/Test-GenerateReleaseNotes.ps1
- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
- name: Cache NuGet
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('Directory.Packages.props', '**/*.csproj') }}
restore-keys: nuget-${{ runner.os }}-
- name: Build and test
shell: pwsh
run: |
dotnet restore VRCFaceTracking.sln -p:Platform=x64
if ($LASTEXITCODE -ne 0) { throw "restore failed ($LASTEXITCODE)" }
dotnet build VRCFaceTracking.sln -c Release -p:Platform=x64 "-p:VftBuildChannel=${{ steps.tag.outputs.channel }}" "-p:VftVersion=${{ steps.tag.outputs.version }}" --no-restore
if ($LASTEXITCODE -ne 0) { throw "build failed ($LASTEXITCODE)" }
dotnet test VRCFaceTracking.Core.Tests/VRCFaceTracking.Core.Tests.csproj -c Release -p:Platform=x64 --no-build
if ($LASTEXITCODE -ne 0) { throw "tests failed ($LASTEXITCODE)" }
- name: Package
id: package
shell: pwsh
run: |
$name = "VRCFaceTracking-${{ steps.tag.outputs.version }}-win-x64"
./.github/scripts/Publish-App.ps1 -Channel '${{ steps.tag.outputs.channel }}' -Version '${{ steps.tag.outputs.version }}' -OutDir "build/$name"
$zip = "build/$name.zip"
Compress-Archive -Path "build/$name/*" -DestinationPath $zip -Force
$hash = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant()
[System.IO.File]::WriteAllText("$zip.sha256", "$hash $name.zip`n", (New-Object System.Text.UTF8Encoding($false)))
./.github/scripts/Generate-ReleaseNotes.ps1 -Tag $env:RELEASE_TAG -ZipPath $zip -OutFile build/notes.md | Out-Null
Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value "zip=$zip"
Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value "sha256=$hash"
Get-Content build/notes.md
- name: Publish release
shell: pwsh
run: |
$tag = $env:RELEASE_TAG
$extra = @()
if ('${{ steps.tag.outputs.prerelease }}' -eq 'true') { $extra = @('--prerelease', '--latest=false') }
gh release create $tag '${{ steps.package.outputs.zip }}' '${{ steps.package.outputs.zip }}.sha256' --repo '${{ github.repository }}' --target '${{ steps.tag.outputs.sha }}' --title $tag --notes-file build/notes.md @extra
if ($LASTEXITCODE -ne 0) { throw "gh release create failed ($LASTEXITCODE)" }
- name: Verify uploaded asset
shell: pwsh
run: |
$tag = $env:RELEASE_TAG
$expected = 'sha256:${{ steps.package.outputs.sha256 }}'
$zipName = Split-Path -Leaf '${{ steps.package.outputs.zip }}'
$ok = $false
for ($i = 0; $i -lt 6 -and -not $ok; $i++) {
Start-Sleep -Seconds 2
$json = gh api "repos/${{ github.repository }}/releases/tags/$tag" 2>$null
$fetched = $LASTEXITCODE -eq 0
$global:LASTEXITCODE = 0
if (-not $fetched) { continue }
$release = $json | ConvertFrom-Json
$asset = $release.assets | Where-Object { $_.name -eq $zipName }
if ($asset -and $asset.digest -eq $expected) { $ok = $true }
}
if (-not $ok) { throw "Uploaded $zipName digest does not match $expected." }
Write-Host "Verified $zipName digest $expected"
- name: Remove partial release on failure
if: failure()
shell: pwsh
run: |
gh release delete $env:RELEASE_TAG --repo '${{ github.repository }}' --yes 2>$null
$global:LASTEXITCODE = 0