Release #3
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |