Repository navigation
fix: allow unsigned macOS release packaging #23
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: ci | |
| on: | |
| push: | |
| branches: | |
| - master | |
| tags: | |
| - v*.*.* | |
| pull_request: | |
| permissions: | |
| contents: read | |
| jobs: | |
| verify: | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: 10.0.203 | |
| cache: true | |
| cache-dependency-path: | | |
| Directory.Packages.props | |
| CodexSwitch/CodexSwitch.csproj | |
| CodexSwitchUI/src/CodexSwitchUI/CodexSwitchUI.csproj | |
| CodexSwitchUI/src/CodexSwitchUI.ECharts/CodexSwitchUI.ECharts.csproj | |
| CodexSwitch.Tests/CodexSwitch.Tests.csproj | |
| - name: Validate changelog | |
| shell: pwsh | |
| run: ./build/Validate-Changelog.ps1 | |
| - name: Restore | |
| run: dotnet restore CodexSwitch.Tests/CodexSwitch.Tests.csproj | |
| - name: Build | |
| run: dotnet build CodexSwitch.Tests/CodexSwitch.Tests.csproj -c Release --no-restore | |
| - name: Test | |
| run: dotnet test CodexSwitch.Tests/CodexSwitch.Tests.csproj -c Release --no-build --no-restore --verbosity normal | |
| publish: | |
| needs: verify | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: windows-latest | |
| rid: win-x64 | |
| - runner: ubuntu-latest | |
| rid: linux-x64 | |
| - runner: macos-14 | |
| rid: osx-arm64 | |
| - runner: macos-15-intel | |
| rid: osx-x64 | |
| runs-on: ${{ matrix.runner }} | |
| env: | |
| MACOS_CERTIFICATE_INPUT: ${{ secrets.MACOS_CERTIFICATE_BASE64 || secrets.CSC_LINK || '' }} | |
| MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD || secrets.CSC_KEY_PASSWORD || '' }} | |
| MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD || '' }} | |
| MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY || '' }} | |
| MACOS_NOTARY_APPLE_ID: ${{ secrets.MACOS_NOTARY_APPLE_ID || secrets.APPLE_ID || '' }} | |
| MACOS_NOTARY_TEAM_ID: ${{ secrets.MACOS_NOTARY_TEAM_ID || secrets.APPLE_TEAM_ID || '' }} | |
| MACOS_NOTARY_PASSWORD: ${{ secrets.MACOS_NOTARY_PASSWORD || secrets.APPLE_APP_SPECIFIC_PASSWORD || '' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: 10.0.203 | |
| cache: true | |
| cache-dependency-path: | | |
| Directory.Packages.props | |
| CodexSwitch/CodexSwitch.csproj | |
| CodexSwitchUI/src/CodexSwitchUI/CodexSwitchUI.csproj | |
| CodexSwitchUI/src/CodexSwitchUI.ECharts/CodexSwitchUI.ECharts.csproj | |
| CodexSwitch.Tests/CodexSwitch.Tests.csproj | |
| - name: Read version | |
| id: version | |
| shell: pwsh | |
| run: | | |
| [xml]$props = Get-Content -LiteralPath "Directory.Build.props" | |
| $version = $props.Project.PropertyGroup.Version | |
| "value=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Publish ${{ matrix.rid }} | |
| run: > | |
| dotnet publish CodexSwitch/CodexSwitch.csproj | |
| -c Release | |
| -r ${{ matrix.rid }} | |
| --self-contained true | |
| -p:PublishAot=true | |
| -p:SelfContained=true | |
| -p:DebugType=none | |
| -p:DebugSymbols=false | |
| -o artifacts/publish/${{ matrix.rid }} | |
| - name: Import macOS signing certificate | |
| if: runner.os == 'macOS' && env.MACOS_CERTIFICATE_INPUT != '' | |
| shell: pwsh | |
| run: | | |
| function Invoke-Security { | |
| & security @args | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "security $($args[0]) failed." | |
| } | |
| } | |
| $certificatePath = Join-Path $env:RUNNER_TEMP "codexswitch_developer_id.p12" | |
| $keychainPath = Join-Path $env:RUNNER_TEMP "codexswitch-signing.keychain-db" | |
| $certificateInput = $env:MACOS_CERTIFICATE_INPUT | |
| $keychainPassword = $env:MACOS_KEYCHAIN_PASSWORD | |
| if ([string]::IsNullOrWhiteSpace($keychainPassword)) { | |
| $keychainPassword = [guid]::NewGuid().ToString("N") | |
| } | |
| if ($certificateInput -match '^https?://') { | |
| Invoke-WebRequest -Uri $certificateInput -OutFile $certificatePath | |
| } | |
| elseif (Test-Path -LiteralPath $certificateInput -PathType Leaf) { | |
| Copy-Item -LiteralPath $certificateInput -Destination $certificatePath -Force | |
| } | |
| else { | |
| $normalizedCertificate = $certificateInput -replace '\s', '' | |
| try { | |
| [IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($normalizedCertificate)) | |
| } | |
| catch { | |
| throw "MACOS_CERTIFICATE_BASE64/CSC_LINK must be a certificate URL, local .p12 path, or base64-encoded .p12 data." | |
| } | |
| } | |
| Invoke-Security create-keychain -p $keychainPassword $keychainPath | |
| Invoke-Security set-keychain-settings -lut 21600 $keychainPath | |
| Invoke-Security unlock-keychain -p $keychainPassword $keychainPath | |
| Invoke-Security import $certificatePath -P $env:MACOS_CERTIFICATE_PASSWORD -A -t cert -f pkcs12 -k $keychainPath | |
| Invoke-Security set-key-partition-list -S apple-tool:,apple: -s -k $keychainPassword $keychainPath | |
| Invoke-Security list-keychains -d user -s $keychainPath | |
| Invoke-Security default-keychain -s $keychainPath | |
| - name: Resolve macOS signing identity | |
| id: mac-signing | |
| if: runner.os == 'macOS' | |
| shell: pwsh | |
| run: | | |
| $identity = $env:MACOS_SIGNING_IDENTITY | |
| if ([string]::IsNullOrWhiteSpace($identity)) { | |
| $identityLine = & security find-identity -v -p codesigning 2>$null | | |
| Select-String -Pattern "Developer ID Application" | | |
| Select-Object -First 1 | |
| if ($null -ne $identityLine) { | |
| $match = [regex]::Match($identityLine.Line, '"(?<identity>[^"]+)"') | |
| if ($match.Success) { | |
| $identity = $match.Groups["identity"].Value | |
| } | |
| } | |
| } | |
| if ([string]::IsNullOrWhiteSpace($identity)) { | |
| Write-Host "No macOS Developer ID Application signing identity was found." | |
| } | |
| else { | |
| Write-Host "Using macOS signing identity: $identity" | |
| } | |
| "identity=$identity" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Install Windows packaging tools | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: choco install innosetup --yes --no-progress | |
| - name: Package installer | |
| id: package | |
| shell: pwsh | |
| run: | | |
| $version = "${{ steps.version.outputs.value }}" | |
| ./build/Package-ReleaseArtifact.ps1 ` | |
| -Version $version ` | |
| -RuntimeIdentifier "${{ matrix.rid }}" ` | |
| -PublishDirectory "artifacts/publish/${{ matrix.rid }}" ` | |
| -OutputDirectory "artifacts/package" ` | |
| -MacSigningIdentity "${{ steps.mac-signing.outputs.identity }}" ` | |
| -MacNotaryAppleId "$env:MACOS_NOTARY_APPLE_ID" ` | |
| -MacNotaryTeamId "$env:MACOS_NOTARY_TEAM_ID" ` | |
| -MacNotaryPassword "$env:MACOS_NOTARY_PASSWORD" | |
| - name: Upload artifact | |
| if: steps.package.outputs.path != '' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ steps.package.outputs.name }} | |
| path: ${{ steps.package.outputs.path }} | |
| if-no-files-found: error | |
| release: | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| needs: publish | |
| runs-on: windows-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - name: Read version | |
| id: version | |
| shell: pwsh | |
| run: | | |
| [xml]$props = Get-Content -LiteralPath "Directory.Build.props" | |
| $version = $props.Project.PropertyGroup.Version | |
| $expectedTag = "v$version" | |
| if ($expectedTag -ne "${{ github.ref_name }}") { | |
| throw "Release tag '${{ github.ref_name }}' does not match Directory.Build.props version '$expectedTag'." | |
| } | |
| "value=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Download release artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: CodexSwitch-v${{ steps.version.outputs.value }}-* | |
| path: artifacts/release | |
| merge-multiple: true | |
| - name: Build release notes | |
| shell: pwsh | |
| run: | | |
| $version = "v${{ steps.version.outputs.value }}" | |
| $content = Get-Content -LiteralPath "CHANGELOG.md" -Raw | |
| $pattern = "(?s)## \[$([regex]::Escape($version))\].*?(?=\r?\n## \[v\d+\.\d+\.\d+\]|\z)" | |
| $match = [regex]::Match($content, $pattern) | |
| if (-not $match.Success) { | |
| throw "Unable to find changelog section for $version." | |
| } | |
| $match.Value | Out-File -LiteralPath "release-notes.md" -Encoding utf8 | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: ${{ github.ref_name }} | |
| body_path: release-notes.md | |
| files: artifacts/release/* | |
| fail_on_unmatched_files: true |