Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 193 additions & 148 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,148 +1,193 @@
name: Release

on:
pull_request:
branches: [main]
types: [closed]

permissions:
contents: write

jobs:
release:
if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'dev'
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Get version
id: version
shell: pwsh
run: |
$version = ([xml](Get-Content src/PlanViewer.App/PlanViewer.App.csproj)).Project.PropertyGroup.Version | Where-Object { $_ }
echo "VERSION=$version" >> $env:GITHUB_OUTPUT

- name: Check if release already exists
id: check
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "v${{ steps.version.outputs.VERSION }}" > /dev/null 2>&1; then
echo "EXISTS=true" >> $GITHUB_OUTPUT
else
echo "EXISTS=false" >> $GITHUB_OUTPUT
fi

- name: Create release
if: steps.check.outputs.EXISTS == 'false'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "v${{ steps.version.outputs.VERSION }}" --title "v${{ steps.version.outputs.VERSION }}" --generate-notes --target main

- name: Setup .NET 8.0
if: steps.check.outputs.EXISTS == 'false'
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Build and test
if: steps.check.outputs.EXISTS == 'false'
run: |
dotnet restore
dotnet build -c Release
dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-build --verbosity normal

- name: Publish App (all platforms)
if: steps.check.outputs.EXISTS == 'false'
run: |
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r win-x64 --self-contained -o publish/win-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r linux-x64 --self-contained -o publish/linux-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-x64 --self-contained -o publish/osx-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-arm64 --self-contained -o publish/osx-arm64

- name: Create Velopack release (Windows)
if: steps.check.outputs.EXISTS == 'false'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
dotnet tool install -g vpk
New-Item -ItemType Directory -Force -Path releases/velopack

# Download previous release for delta generation
vpk download github --repoUrl https://github.com/${{ github.repository }} --channel win -o releases/velopack --token $env:GH_TOKEN

# Pack Windows release
vpk pack -u PerformanceStudio -v $env:VERSION -p publish/win-x64 -e PlanViewer.App.exe -o releases/velopack --channel win

- name: Package and upload
if: steps.check.outputs.EXISTS == 'false'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
New-Item -ItemType Directory -Force -Path releases

# Package Windows and Linux as flat zips
foreach ($rid in @('win-x64', 'linux-x64')) {
if (Test-Path 'README.md') { Copy-Item 'README.md' "publish/$rid/" }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "publish/$rid/" }
Compress-Archive -Path "publish/$rid/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force
}

# Package macOS as proper .app bundles
foreach ($rid in @('osx-x64', 'osx-arm64')) {
$appName = "PerformanceStudio.app"
$bundleDir = "publish/$rid-bundle/$appName"

# Create .app bundle structure
New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/MacOS"
New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/Resources"

# Copy all published files into Contents/MacOS
Copy-Item -Path "publish/$rid/*" -Destination "$bundleDir/Contents/MacOS/" -Recurse

# Move Info.plist to Contents/ (it was copied to MacOS/ with the publish output)
if (Test-Path "$bundleDir/Contents/MacOS/Info.plist") {
Move-Item -Path "$bundleDir/Contents/MacOS/Info.plist" -Destination "$bundleDir/Contents/Info.plist" -Force
}

# Update version in Info.plist to match csproj
$plist = Get-Content "$bundleDir/Contents/Info.plist" -Raw
$plist = $plist -replace '(<key>CFBundleVersion</key>\s*<string>)[^<]*(</string>)', "`${1}$env:VERSION`${2}"
$plist = $plist -replace '(<key>CFBundleShortVersionString</key>\s*<string>)[^<]*(</string>)', "`${1}$env:VERSION`${2}"
Set-Content -Path "$bundleDir/Contents/Info.plist" -Value $plist -NoNewline

# Move icon to Contents/Resources
if (Test-Path "$bundleDir/Contents/MacOS/EDD.icns") {
Move-Item -Path "$bundleDir/Contents/MacOS/EDD.icns" -Destination "$bundleDir/Contents/Resources/EDD.icns" -Force
}

# Add README and LICENSE alongside the .app bundle
$wrapperDir = "publish/$rid-bundle"
if (Test-Path 'README.md') { Copy-Item 'README.md' "$wrapperDir/" }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "$wrapperDir/" }

Compress-Archive -Path "$wrapperDir/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force
}

# Checksums (zips only, Velopack has its own checksums)
$checksums = Get-ChildItem releases/*.zip | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
"$hash $($_.Name)"
}
$checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8
Write-Host "Checksums:"
$checksums | ForEach-Object { Write-Host $_ }

# Upload zips + checksums
gh release upload "v$env:VERSION" releases/*.zip releases/SHA256SUMS.txt --clobber

# Upload Velopack artifacts
vpk upload github --repoUrl https://github.com/${{ github.repository }} --channel win -o releases/velopack --releaseName "v$env:VERSION" --tag "v$env:VERSION" --merge --token $env:GH_TOKEN
name: Release

on:
pull_request:
branches: [main]
types: [closed]

permissions:
contents: write
id-token: write
actions: read

jobs:
release:
if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'dev'
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Get version
id: version
shell: pwsh
run: |
$version = ([xml](Get-Content src/PlanViewer.App/PlanViewer.App.csproj)).Project.PropertyGroup.Version | Where-Object { $_ }
echo "VERSION=$version" >> $env:GITHUB_OUTPUT

- name: Check if release already exists
id: check
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "v${{ steps.version.outputs.VERSION }}" > /dev/null 2>&1; then
echo "EXISTS=true" >> $GITHUB_OUTPUT
else
echo "EXISTS=false" >> $GITHUB_OUTPUT
fi

- name: Create release
if: steps.check.outputs.EXISTS == 'false'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "v${{ steps.version.outputs.VERSION }}" --title "v${{ steps.version.outputs.VERSION }}" --generate-notes --target main

- name: Setup .NET 8.0
if: steps.check.outputs.EXISTS == 'false'
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Build and test
if: steps.check.outputs.EXISTS == 'false'
run: |
dotnet restore
dotnet build -c Release
dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-build --verbosity normal

- name: Publish App (all platforms)
if: steps.check.outputs.EXISTS == 'false'
run: |
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r win-x64 --self-contained -o publish/win-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r linux-x64 --self-contained -o publish/linux-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-x64 --self-contained -o publish/osx-x64
dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-arm64 --self-contained -o publish/osx-arm64

# ── SignPath code signing (Windows only, skipped if secret not configured) ──
- name: Check if signing is configured
if: steps.check.outputs.EXISTS == 'false'
id: signing
shell: bash
run: |
if [ -n "${{ secrets.SIGNPATH_API_TOKEN }}" ]; then
echo "ENABLED=true" >> $GITHUB_OUTPUT
else
echo "ENABLED=false" >> $GITHUB_OUTPUT
echo "::warning::SIGNPATH_API_TOKEN not configured — releasing unsigned binaries"
fi

- name: Upload Windows build for signing
if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true'
id: upload-unsigned
uses: actions/upload-artifact@v4
with:
name: App-unsigned
path: publish/win-x64/

- name: Sign Windows build
if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true'
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0'
project-slug: 'PerformanceStudio'
signing-policy-slug: 'test-signing'
artifact-configuration-slug: 'App'
github-artifact-id: '${{ steps.upload-unsigned.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'signed/win-x64'

- name: Replace unsigned Windows build with signed
if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true'
shell: pwsh
run: |
Remove-Item -Recurse -Force publish/win-x64
Copy-Item -Recurse signed/win-x64 publish/win-x64

# ── Velopack (uses signed Windows binaries) ───────────────────────
- name: Create Velopack release (Windows)
if: steps.check.outputs.EXISTS == 'false'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
dotnet tool install -g vpk
New-Item -ItemType Directory -Force -Path releases/velopack

# Download previous release for delta generation
vpk download github --repoUrl https://github.com/${{ github.repository }} --channel win -o releases/velopack --token $env:GH_TOKEN

# Pack Windows release (now signed)
vpk pack -u PerformanceStudio -v $env:VERSION -p publish/win-x64 -e PlanViewer.App.exe -o releases/velopack --channel win

# ── Package and upload ────────────────────────────────────────────
- name: Package and upload
if: steps.check.outputs.EXISTS == 'false'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
New-Item -ItemType Directory -Force -Path releases

# Package Windows (signed) and Linux as flat zips
foreach ($rid in @('win-x64', 'linux-x64')) {
if (Test-Path 'README.md') { Copy-Item 'README.md' "publish/$rid/" }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "publish/$rid/" }
Compress-Archive -Path "publish/$rid/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force
}

# Package macOS as proper .app bundles
foreach ($rid in @('osx-x64', 'osx-arm64')) {
$appName = "PerformanceStudio.app"
$bundleDir = "publish/$rid-bundle/$appName"

# Create .app bundle structure
New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/MacOS"
New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/Resources"

# Copy all published files into Contents/MacOS
Copy-Item -Path "publish/$rid/*" -Destination "$bundleDir/Contents/MacOS/" -Recurse

# Move Info.plist to Contents/ (it was copied to MacOS/ with the publish output)
if (Test-Path "$bundleDir/Contents/MacOS/Info.plist") {
Move-Item -Path "$bundleDir/Contents/MacOS/Info.plist" -Destination "$bundleDir/Contents/Info.plist" -Force
}

# Update version in Info.plist to match csproj
$plist = Get-Content "$bundleDir/Contents/Info.plist" -Raw
$plist = $plist -replace '(<key>CFBundleVersion</key>\s*<string>)[^<]*(</string>)', "`${1}$env:VERSION`${2}"
$plist = $plist -replace '(<key>CFBundleShortVersionString</key>\s*<string>)[^<]*(</string>)', "`${1}$env:VERSION`${2}"
Set-Content -Path "$bundleDir/Contents/Info.plist" -Value $plist -NoNewline

# Move icon to Contents/Resources
if (Test-Path "$bundleDir/Contents/MacOS/EDD.icns") {
Move-Item -Path "$bundleDir/Contents/MacOS/EDD.icns" -Destination "$bundleDir/Contents/Resources/EDD.icns" -Force
}

# Add README and LICENSE alongside the .app bundle
$wrapperDir = "publish/$rid-bundle"
if (Test-Path 'README.md') { Copy-Item 'README.md' "$wrapperDir/" }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "$wrapperDir/" }

Compress-Archive -Path "$wrapperDir/*" -DestinationPath "releases/PerformanceStudio-$rid.zip" -Force
}

# Checksums (zips only, Velopack has its own checksums)
$checksums = Get-ChildItem releases/*.zip | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
"$hash $($_.Name)"
}
$checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8
Write-Host "Checksums:"
$checksums | ForEach-Object { Write-Host $_ }

# Upload zips + checksums
gh release upload "v$env:VERSION" releases/*.zip releases/SHA256SUMS.txt --clobber

# Upload Velopack artifacts
vpk upload github --repoUrl https://github.com/${{ github.repository }} --channel win -o releases/velopack --releaseName "v$env:VERSION" --tag "v$env:VERSION" --merge --token $env:GH_TOKEN
Loading