Add manual beta release workflow
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
name: Manual Beta Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_notes:
|
||||
description: Markdown release notes for this beta release
|
||||
required: true
|
||||
type: string
|
||||
release_title:
|
||||
description: Optional GitHub release title override
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: read
|
||||
|
||||
jobs:
|
||||
release-beta:
|
||||
name: Build and publish beta release
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
PROJECT_PATH: Wino.Mail.WinUI/Wino.Mail.WinUI.csproj
|
||||
MANIFEST_PATH: Wino.Mail.WinUI/Package.appxmanifest
|
||||
PACKAGE_OUTPUT_DIR: ${{ github.workspace }}\artifacts\package
|
||||
RELEASE_OUTPUT_DIR: ${{ github.workspace }}\artifacts\release
|
||||
CERTIFICATE_PFX_PATH: ${{ github.workspace }}\artifacts\signing\beta-signing-cert.pfx
|
||||
CERTIFICATE_CER_PATH: ${{ github.workspace }}\artifacts\release\Wino-Mail-Beta.cer
|
||||
steps:
|
||||
- name: Checkout main branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch tags from origin
|
||||
shell: pwsh
|
||||
run: git fetch origin --force --tags refs/heads/main:refs/remotes/origin/main
|
||||
|
||||
- name: Validate release secrets
|
||||
shell: pwsh
|
||||
env:
|
||||
BETA_SIGNING_CERT_PFX_BASE64: ${{ secrets.BETA_SIGNING_CERT_PFX_BASE64 }}
|
||||
BETA_SIGNING_CERT_PASSWORD: ${{ secrets.BETA_SIGNING_CERT_PASSWORD }}
|
||||
run: |
|
||||
if ([string]::IsNullOrWhiteSpace($env:BETA_SIGNING_CERT_PFX_BASE64)) {
|
||||
throw "Missing required secret: BETA_SIGNING_CERT_PFX_BASE64"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:BETA_SIGNING_CERT_PASSWORD)) {
|
||||
throw "Missing required secret: BETA_SIGNING_CERT_PASSWORD"
|
||||
}
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
source-url: https://nuget.pkg.github.com/bkaankose/index.json
|
||||
env:
|
||||
NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Compute beta version and release metadata
|
||||
id: metadata
|
||||
shell: pwsh
|
||||
env:
|
||||
RELEASE_NOTES_INPUT: ${{ github.event.inputs.release_notes }}
|
||||
RELEASE_TITLE_INPUT: ${{ github.event.inputs.release_title }}
|
||||
run: |
|
||||
$manifestPath = Join-Path $env:GITHUB_WORKSPACE $env:MANIFEST_PATH
|
||||
if (-not (Test-Path $manifestPath)) {
|
||||
throw "Package manifest not found: $manifestPath"
|
||||
}
|
||||
|
||||
[xml]$manifest = Get-Content -LiteralPath $manifestPath
|
||||
$identityNode = $manifest.Package.Identity
|
||||
if (-not $identityNode) {
|
||||
throw "Could not locate the Package/Identity node in $manifestPath"
|
||||
}
|
||||
|
||||
$currentVersionText = [string]$identityNode.Version
|
||||
if ($currentVersionText -notmatch '^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)\.(?<revision>\d+)$') {
|
||||
throw "Manifest version '$currentVersionText' is not a four-part numeric version."
|
||||
}
|
||||
|
||||
$baseVersion = '{0}.{1}.{2}' -f $Matches.major, $Matches.minor, $Matches.patch
|
||||
$nextRevision = [int]$Matches.revision + 1
|
||||
$packageVersion = '{0}.{1}.{2}.{3}' -f $Matches.major, $Matches.minor, $Matches.patch, $nextRevision
|
||||
|
||||
$matchingTags = git tag --list "v$baseVersion.*"
|
||||
$releaseNumbers = @()
|
||||
foreach ($tag in $matchingTags) {
|
||||
if ($tag -match "^v$([regex]::Escape($baseVersion))\.(\d+)$") {
|
||||
$releaseNumbers += [int]$Matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
$nextReleaseNumber = if ($releaseNumbers.Count -gt 0) { ($releaseNumbers | Measure-Object -Maximum).Maximum + 1 } else { 1 }
|
||||
$releaseTag = "v$baseVersion.$nextReleaseNumber"
|
||||
$releaseTitleInput = $env:RELEASE_TITLE_INPUT
|
||||
$releaseTitle = if ([string]::IsNullOrWhiteSpace($releaseTitleInput)) { $releaseTag } else { $releaseTitleInput.Trim() }
|
||||
|
||||
$headSha = (git rev-parse HEAD).Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($headSha)) {
|
||||
throw "Failed to resolve the checked out main commit SHA."
|
||||
}
|
||||
|
||||
$previousReleaseTag = git tag --sort=-creatordate --list 'v*.*.*.*' | Select-Object -First 1
|
||||
$commitLines = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($previousReleaseTag)) {
|
||||
$commitLines = git log "$previousReleaseTag..$headSha" --pretty=format:"- %s (%h)"
|
||||
}
|
||||
else {
|
||||
$commitLines = git log $headSha -n 20 --pretty=format:"- %s (%h)"
|
||||
}
|
||||
|
||||
$buildDateUtc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm 'UTC'")
|
||||
$notesInput = $env:RELEASE_NOTES_INPUT.Trim()
|
||||
$metadataSection = @"
|
||||
## Build metadata
|
||||
|
||||
- Package version: `$packageVersion`
|
||||
- Release tag: `$releaseTag`
|
||||
- Commit: `$headSha`
|
||||
- Built: $buildDateUtc
|
||||
"@
|
||||
|
||||
$commitSection = if ($commitLines.Count -gt 0) {
|
||||
$commitHeader = if ([string]::IsNullOrWhiteSpace($previousReleaseTag)) { "## Recent commits" } else { "## Commits since $previousReleaseTag" }
|
||||
@"
|
||||
$commitHeader
|
||||
|
||||
$($commitLines -join [Environment]::NewLine)
|
||||
"@
|
||||
}
|
||||
else {
|
||||
''
|
||||
}
|
||||
|
||||
$releaseNotesBody = @(
|
||||
$notesInput
|
||||
$metadataSection.Trim()
|
||||
$commitSection.Trim()
|
||||
) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
|
||||
New-Item -ItemType Directory -Path $env:RELEASE_OUTPUT_DIR -Force | Out-Null
|
||||
$releaseNotesPath = Join-Path $env:RELEASE_OUTPUT_DIR 'beta-release-notes.md'
|
||||
$releaseNotesBody -join ([Environment]::NewLine + [Environment]::NewLine) | Set-Content -LiteralPath $releaseNotesPath -Encoding utf8
|
||||
|
||||
"package_version=$packageVersion" >> $env:GITHUB_OUTPUT
|
||||
"base_version=$baseVersion" >> $env:GITHUB_OUTPUT
|
||||
"release_tag=$releaseTag" >> $env:GITHUB_OUTPUT
|
||||
"release_title=$releaseTitle" >> $env:GITHUB_OUTPUT
|
||||
"release_notes_path=$releaseNotesPath" >> $env:GITHUB_OUTPUT
|
||||
"head_sha=$headSha" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Materialize signing certificate
|
||||
shell: pwsh
|
||||
env:
|
||||
BETA_SIGNING_CERT_PFX_BASE64: ${{ secrets.BETA_SIGNING_CERT_PFX_BASE64 }}
|
||||
BETA_SIGNING_CERT_PASSWORD: ${{ secrets.BETA_SIGNING_CERT_PASSWORD }}
|
||||
run: |
|
||||
$signingDir = Split-Path -Parent $env:CERTIFICATE_PFX_PATH
|
||||
New-Item -ItemType Directory -Path $signingDir -Force | Out-Null
|
||||
[IO.File]::WriteAllBytes($env:CERTIFICATE_PFX_PATH, [Convert]::FromBase64String($env:BETA_SIGNING_CERT_PFX_BASE64))
|
||||
|
||||
$certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($env:CERTIFICATE_PFX_PATH, $env:BETA_SIGNING_CERT_PASSWORD, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
|
||||
|
||||
New-Item -ItemType Directory -Path (Split-Path -Parent $env:CERTIFICATE_CER_PATH) -Force | Out-Null
|
||||
[IO.File]::WriteAllBytes($env:CERTIFICATE_CER_PATH, $certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert))
|
||||
|
||||
- name: Restore WinUI project dependencies
|
||||
run: dotnet restore ${{ env.PROJECT_PATH }} --configfile nuget.config -p:Platform=x64 -p:RuntimeIdentifier=win-x64
|
||||
|
||||
- name: Build MSIX bundle
|
||||
shell: pwsh
|
||||
env:
|
||||
BETA_SIGNING_CERT_PASSWORD: ${{ secrets.BETA_SIGNING_CERT_PASSWORD }}
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path $env:PACKAGE_OUTPUT_DIR -Force | Out-Null
|
||||
|
||||
dotnet build $env:PROJECT_PATH `
|
||||
--configuration Release `
|
||||
--no-restore `
|
||||
/p:Platform=x64 `
|
||||
/p:RuntimeIdentifier=win-x64 `
|
||||
/p:GenerateAppxPackageOnBuild=true `
|
||||
/p:UapAppxPackageBuildMode=SideloadOnly `
|
||||
/p:AppxBundle=Always `
|
||||
/p:AppxBundlePlatforms="x86|x64|arm64" `
|
||||
/p:AppxPackageDir="$env:PACKAGE_OUTPUT_DIR\\" `
|
||||
/p:AppxPackageVersion=${{ steps.metadata.outputs.package_version }} `
|
||||
/p:PackageCertificateKeyFile="$env:CERTIFICATE_PFX_PATH" `
|
||||
/p:PackageCertificatePassword="$env:BETA_SIGNING_CERT_PASSWORD" `
|
||||
/p:PackageCertificateThumbprint= `
|
||||
/p:AppxPackageSigningEnabled=true
|
||||
|
||||
- name: Collect packaged artifacts
|
||||
id: package
|
||||
shell: pwsh
|
||||
run: |
|
||||
$bundle = Get-ChildItem -Path $env:PACKAGE_OUTPUT_DIR -Recurse -Filter *.msixbundle | Select-Object -First 1
|
||||
if (-not $bundle) {
|
||||
throw "No .msixbundle file was generated under $env:PACKAGE_OUTPUT_DIR"
|
||||
}
|
||||
|
||||
$packageZipPath = Join-Path $env:RELEASE_OUTPUT_DIR 'Wino-Mail-Beta-PackageOutput.zip'
|
||||
if (Test-Path $packageZipPath) {
|
||||
Remove-Item -LiteralPath $packageZipPath -Force
|
||||
}
|
||||
|
||||
Compress-Archive -Path (Join-Path $env:PACKAGE_OUTPUT_DIR '*') -DestinationPath $packageZipPath -Force
|
||||
|
||||
"bundle_path=$($bundle.FullName)" >> $env:GITHUB_OUTPUT
|
||||
"bundle_name=$($bundle.Name)" >> $env:GITHUB_OUTPUT
|
||||
"package_zip_path=$packageZipPath" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload workflow artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: beta-release-assets-${{ steps.metadata.outputs.release_tag }}
|
||||
path: |
|
||||
${{ steps.package.outputs.bundle_path }}
|
||||
${{ env.CERTIFICATE_CER_PATH }}
|
||||
${{ steps.metadata.outputs.release_notes_path }}
|
||||
${{ steps.package.outputs.package_zip_path }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create GitHub prerelease
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create "${{ steps.metadata.outputs.release_tag }}" `
|
||||
"${{ steps.package.outputs.bundle_path }}" `
|
||||
"${{ env.CERTIFICATE_CER_PATH }}" `
|
||||
"${{ steps.metadata.outputs.release_notes_path }}" `
|
||||
"${{ steps.package.outputs.package_zip_path }}" `
|
||||
--repo "${{ github.repository }}" `
|
||||
--target "${{ steps.metadata.outputs.head_sha }}" `
|
||||
--title "${{ steps.metadata.outputs.release_title }}" `
|
||||
--notes-file "${{ steps.metadata.outputs.release_notes_path }}" `
|
||||
--prerelease
|
||||
Reference in New Issue
Block a user