Skip to content

Merge pull request #169 from realgarit/codex/release-recovery-notes #312

Merge pull request #169 from realgarit/codex/release-recovery-notes

Merge pull request #169 from realgarit/codex/release-recovery-notes #312

Workflow file for this run

name: Build and Release
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
validate_ref:
description: Release Please branch to validate
required: false
type: string
release_tag:
description: Existing draft release tag to rebuild after a failed release run
required: false
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || inputs.release_tag || inputs.validate_ref || 'release' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' || inputs.validate_ref != '' }}
jobs:
release-please:
name: Manage Release
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.validate_ref == '')
permissions:
actions: write
contents: write
issues: write
pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created || steps.recover.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name || steps.recover.outputs.tag_name }}
version: ${{ steps.release.outputs.version || steps.recover.outputs.version }}
release_sha: ${{ github.sha }}
steps:
- name: Create or update release pull request
id: release
if: github.event_name != 'workflow_dispatch' || inputs.release_tag == ''
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
- name: Checkout recovery source
if: github.event_name == 'workflow_dispatch' && inputs.release_tag != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.sha }}
fetch-depth: 0
- name: Recover existing draft release
id: recover
if: github.event_name == 'workflow_dispatch' && inputs.release_tag != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ inputs.release_tag }}
run: |
set -euo pipefail
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Release tag '$RELEASE_TAG' is not a stable semantic version."
exit 1
fi
VERSION="${RELEASE_TAG#v}"
bash scripts/validate-version-sync.sh "$VERSION"
MANIFEST_VERSION="$(jq -r '."."' .release-please-manifest.json)"
if [[ "$MANIFEST_VERSION" != "$VERSION" ]]; then
echo "::error::.release-please-manifest.json contains '$MANIFEST_VERSION', expected '$VERSION'."
exit 1
fi
TAG_SHA="$(git ls-remote --tags origin "refs/tags/$RELEASE_TAG" | awk '{print $1}')"
if [[ -z "$TAG_SHA" ]]; then
echo "::error::Release tag '$RELEASE_TAG' was not found on origin."
exit 1
fi
gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json databaseId --jq .databaseId > /dev/null
echo "release_created=true" >> "$GITHUB_OUTPUT"
echo "tag_name=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Start release pull request validation
if: steps.release.outputs.prs_created == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_PR: ${{ steps.release.outputs.pr }}
run: |
RELEASE_REF="$(jq -r '.headBranchName' <<< "$RELEASE_PR")"
gh workflow run build.yml --repo "$GITHUB_REPOSITORY" --ref "$RELEASE_REF" -f validate_ref="$RELEASE_REF"
validate-release:
name: Validate Release Version
runs-on: ubuntu-latest
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
outputs:
tag_name: ${{ steps.version.outputs.tag }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
# Draft GitHub releases do not create their tag until publication.
ref: ${{ needs.release-please.outputs.release_sha }}
fetch-depth: 0
- name: Match tag and project versions
id: version
env:
RELEASE_TAG: ${{ needs.release-please.outputs.tag_name }}
RELEASE_SHA: ${{ needs.release-please.outputs.release_sha }}
run: |
set -euo pipefail
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Release tag '$RELEASE_TAG' is not a stable semantic version."
exit 1
fi
VERSION="${RELEASE_TAG#v}"
bash scripts/validate-version-sync.sh "$VERSION"
MANIFEST_VERSION="$(jq -r '."."' .release-please-manifest.json)"
if [[ "$MANIFEST_VERSION" != "$VERSION" ]]; then
echo "::error::.release-please-manifest.json contains '$MANIFEST_VERSION', expected '$VERSION'."
exit 1
fi
if [[ "$(git rev-parse HEAD)" != "$RELEASE_SHA" ]]; then
echo "::error::Checked out commit does not match Release Please SHA '$RELEASE_SHA'."
exit 1
fi
git fetch origin main
if ! git merge-base --is-ancestor HEAD origin/main; then
echo "::error::Release commit '$RELEASE_SHA' is not reachable from main."
exit 1
fi
echo "tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.runtime }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: [release-please, validate-release]
if: needs.release-please.outputs.release_created == 'true'
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
runtime: win-x64
- os: macos-latest
runtime: osx-x64
- os: macos-latest
runtime: osx-arm64
- os: ubuntu-latest
runtime: linux-x64
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.release-please.outputs.release_sha }}
- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: '10.0.x'
- name: Download PowerShell modules (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: ./scripts/download-modules.ps1
- name: Download PowerShell modules (macOS/Linux)
if: matrix.os != 'windows-latest'
shell: bash
run: |
chmod +x scripts/download-modules.sh
./scripts/download-modules.sh
- name: Restore dependencies
run: dotnet restore PhoneDesk.slnx
- name: Build
run: dotnet build PhoneDesk.slnx --configuration Release --no-restore
- name: Run tests
run: dotnet test PhoneDesk.Tests/PhoneDesk.Tests.csproj --configuration Release --no-build --verbosity normal
- name: Publish
# Self-contained so users need no .NET runtime install.
# OutputType is set in the project (WinExe). It must NOT be passed as a global -p:OutputType,
# because that propagates to the referenced class libraries and makes them fail with CS5001
# (no Main).
run: dotnet publish phonedesk.csproj --configuration Release --runtime ${{ matrix.runtime }} --self-contained true --output ./publish/${{ matrix.runtime }}
- name: Import signing certificate (macOS)
if: startsWith(matrix.runtime, 'osx-')
env:
P12_BASE64: ${{ secrets.SIGNING_CERT_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.SIGNING_CERT_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
printf '%s' "$P12_BASE64" | base64 --decode > "$RUNNER_TEMP/signing.p12"
export KEYCHAIN="$RUNNER_TEMP/tpm-ci.keychain-db"
chmod +x scripts/import-cert.sh
scripts/import-cert.sh "$RUNNER_TEMP/signing.p12"
rm -f "$RUNNER_TEMP/signing.p12"
echo "KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV"
- name: Package (macOS .app bundle, stable self-signed identity)
if: startsWith(matrix.runtime, 'osx-')
env:
VERSION: ${{ needs.validate-release.outputs.version }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
chmod +x scripts/package-macos.sh
SIGN_IDENTITY="Teams Phone Manager Self-Signed" \
./scripts/package-macos.sh ./publish/${{ matrix.runtime }} "$VERSION" ./dist
ditto -c -k --keepParent "./dist/PhoneDesk.app" "./dist/phonedesk-${{ matrix.runtime }}.zip"
- name: Package (Windows zip + Inno Setup installer)
if: matrix.runtime == 'win-x64'
shell: pwsh
env:
VERSION: ${{ needs.validate-release.outputs.version }}
run: |
New-Item -ItemType Directory -Force -Path ./dist | Out-Null
Compress-Archive -Path ./publish/win-x64/* -DestinationPath ./dist/phonedesk-win-x64.zip
iscc /DAppVersion=$env:VERSION /DPublishDir=..\..\publish\win-x64 /O.\dist installer\windows\phonedesk.iss
- name: Package (Linux zip)
if: matrix.runtime == 'linux-x64'
run: |
mkdir -p ./dist
chmod +x ./publish/linux-x64/phonedesk
cd ./publish/linux-x64
zip -r ../../dist/phonedesk-linux-x64.zip .
- name: Upload build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: phonedesk-${{ matrix.runtime }}
path: ./dist/*
retention-days: 30
upload-release-assets:
name: Upload Release Assets
needs: [release-please, validate-release, build]
runs-on: ubuntu-latest
if: needs.release-please.outputs.release_created == 'true'
permissions:
contents: write
steps:
- name: Download all artifacts
# Build jobs upload finished assets (zips / setup exe), so a flat merge is enough.
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: phonedesk-*
path: ./artifacts
merge-multiple: true
- name: List downloaded artifacts
run: ls -lh artifacts/
- name: Add legacy-named installer for pre-rebrand updaters
# In-app updaters shipped before the PhoneDesk rebrand only accept a release
# asset named exactly teams-phonemanager-win-x64-setup.exe (strict filename
# check in their GitHubUpdateInstallerService). Attach the same installer
# under the old name so existing installs can still self-update.
working-directory: artifacts
run: cp phonedesk-win-x64-setup.exe teams-phonemanager-win-x64-setup.exe
- name: Generate SHA-256 manifest
working-directory: artifacts
run: sha256sum phonedesk-* teams-phonemanager-win-x64-setup.exe > SHA256SUMS
- name: Publish draft release
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
TAG: ${{ needs.validate-release.outputs.tag_name }}
RELEASE_SHA: ${{ needs.release-please.outputs.release_sha }}
run: |
set -euo pipefail
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
artifacts/phonedesk-win-x64.zip \
artifacts/phonedesk-win-x64-setup.exe \
artifacts/teams-phonemanager-win-x64-setup.exe \
artifacts/phonedesk-osx-x64.zip \
artifacts/phonedesk-osx-arm64.zip \
artifacts/phonedesk-linux-x64.zip \
artifacts/SHA256SUMS
RELEASE_ID="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json databaseId --jq .databaseId)"
gh api -X PATCH "repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID" \
-F draft=false \
-f target_commitish="$RELEASE_SHA"
bump-homebrew-cask:
name: Update Homebrew cask
needs: [release-please, validate-release, upload-release-assets]
runs-on: ubuntu-latest
if: needs.release-please.outputs.release_created == 'true'
steps:
- name: Checkout tap
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: realgarit/homebrew-tap
token: ${{ secrets.PAT_TOKEN }}
- name: Regenerate cask
env:
TAG: ${{ needs.validate-release.outputs.tag_name }}
run: |
VERSION="${TAG#v}"
BASE="https://github.com/realgarit/phonedesk/releases/download/${TAG}"
curl -fsSL -o arm64.zip "${BASE}/phonedesk-osx-arm64.zip"
curl -fsSL -o x64.zip "${BASE}/phonedesk-osx-x64.zip"
SHA_ARM=$(sha256sum arm64.zip | cut -d' ' -f1)
SHA_X64=$(sha256sum x64.zip | cut -d' ' -f1)
rm arm64.zip x64.zip
mkdir -p Casks
cat > Casks/phonedesk.rb <<EOF
cask "phonedesk" do
version "${VERSION}"
on_arm do
sha256 "${SHA_ARM}"
url "https://github.com/realgarit/phonedesk/releases/download/v#{version}/phonedesk-osx-arm64.zip"
end
on_intel do
sha256 "${SHA_X64}"
url "https://github.com/realgarit/phonedesk/releases/download/v#{version}/phonedesk-osx-x64.zip"
end
name "PhoneDesk"
desc "Microsoft Teams Phone System administration made simple"
homepage "https://github.com/realgarit/phonedesk"
depends_on macos: :monterey
app "PhoneDesk.app"
# The app is ad-hoc signed (not notarized); clear quarantine so
# Gatekeeper does not block the launch.
postflight do
system_command "/usr/bin/xattr",
args: ["-dr", "com.apple.quarantine", "#{appdir}/PhoneDesk.app"],
sudo: false
end
zap trash: [
"~/Library/Preferences/ch.realgar.teams-phonemanager.plist",
]
end
EOF
- name: Commit and push
env:
TAG: ${{ needs.validate-release.outputs.tag_name }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Casks/phonedesk.rb
if git diff --cached --quiet; then
echo "Cask unchanged, nothing to push"
else
git commit -m "phonedesk ${TAG}"
git push
fi
pr-validate:
name: PR Validate ${{ matrix.runtime }}
# Pre-merge gate: build + test + publish on every runtime without releasing.
if: github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.validate_ref != '')
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
runtime: win-x64
- os: macos-latest
runtime: osx-x64
- os: macos-latest
runtime: osx-arm64
- os: ubuntu-latest
runtime: linux-x64
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
# For PR events, use the head SHA (not the merge ref) to avoid race
# where the merge ref is deleted before the runner picks up the job.
ref: ${{ inputs.validate_ref || github.event.pull_request.head.sha || github.ref }}
- name: Validate version source alignment
shell: bash
run: bash scripts/validate-version-sync.sh
- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore PhoneDesk.slnx
- name: Build
run: dotnet build PhoneDesk.slnx --configuration Release --no-restore
- name: Run tests
run: dotnet test PhoneDesk.Tests/PhoneDesk.Tests.csproj --configuration Release --no-build --verbosity normal
- name: Publish (packaging smoke, not released)
run: dotnet publish phonedesk.csproj --configuration Release --runtime ${{ matrix.runtime }} --self-contained true --output ./publish/${{ matrix.runtime }}
- name: Package smoke (macOS .app bundle)
if: startsWith(matrix.runtime, 'osx-')
run: |
chmod +x scripts/package-macos.sh
./scripts/package-macos.sh ./publish/${{ matrix.runtime }} 0.0.0 ./dist
ditto -c -k --keepParent "./dist/PhoneDesk.app" "./dist/phonedesk-${{ matrix.runtime }}.zip"
- name: Package smoke (Windows installer)
if: matrix.runtime == 'win-x64'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path ./dist | Out-Null
iscc /DAppVersion=0.0.0 /DPublishDir=..\..\publish\win-x64 /O.\dist installer\windows\phonedesk.iss