Publish Release #45
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: Publish Release | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Build artifacts only, skip uploading to GitHub Release" | |
| required: false | |
| type: boolean | |
| default: true | |
| allow_update_published_release: | |
| description: "Allow uploading assets to an already published release" | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| build: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-latest | |
| platform: win32 | |
| arch: x64 | |
| - os: windows-11-arm | |
| platform: win32 | |
| arch: arm64 | |
| - os: macos-15-intel | |
| platform: darwin | |
| arch: x64 | |
| - os: macos-14 | |
| platform: darwin | |
| arch: arm64 | |
| - os: ubuntu-latest | |
| platform: linux | |
| arch: x64 | |
| - os: ubuntu-24.04-arm | |
| platform: linux | |
| arch: arm64 | |
| runs-on: ${{ matrix.os }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout git repo | |
| uses: actions/checkout@v4 | |
| - name: Install Node and NPM | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: "npm" | |
| - name: Install Linux dependencies | |
| if: matrix.platform == 'linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y fakeroot rpm libsecret-1-dev pkg-config | |
| - name: Ensure WiX Toolset | |
| if: matrix.platform == 'win32' && matrix.arch == 'x64' | |
| shell: powershell | |
| run: | | |
| $installed = choco list --local-only wixtoolset --exact --limit-output | |
| if (-not $installed) { | |
| choco install wixtoolset --version=3.14.0 -y | |
| } elseif ($installed -match '^wixtoolset\|3\.14(\.|$)') { | |
| Write-Host "WiX Toolset already compatible: $installed" | |
| } else { | |
| Write-Host "Incompatible WiX Toolset detected: $installed" | |
| choco upgrade wixtoolset --version=3.14.0 --allow-downgrade -y | |
| } | |
| - name: Install dependencies | |
| uses: nick-fields/retry@v3 | |
| with: | |
| max_attempts: 3 | |
| timeout_minutes: 20 | |
| retry_wait_seconds: 30 | |
| command: npm ci | |
| - name: Make Artifacts | |
| if: matrix.platform != 'darwin' | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_ORG: ${{ secrets.SENTRY_ORG }} | |
| SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} | |
| run: npm run make -- --platform=${{ matrix.platform }} --arch=${{ matrix.arch }} | |
| # DMG creation may be flaky on GitHub macOS runners, so retry the full darwin make. | |
| - name: Make Artifacts (darwin with retry) | |
| if: matrix.platform == 'darwin' | |
| uses: nick-fields/retry@v3 | |
| with: | |
| max_attempts: 3 | |
| timeout_minutes: 20 | |
| retry_wait_seconds: 20 | |
| command: npm run make -- --platform=${{ matrix.platform }} --arch=${{ matrix.arch }} | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_ORG: ${{ secrets.SENTRY_ORG }} | |
| SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} | |
| - name: Upload Build Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: make-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: | | |
| out/make/**/*.exe | |
| out/make/**/*.dmg | |
| out/make/**/*.zip | |
| out/make/**/*.AppImage | |
| out/make/**/*.deb | |
| out/make/**/*.rpm | |
| out/make/**/*.msi | |
| out/make/**/*.yml | |
| out/make/**/*.txt | |
| if-no-files-found: error | |
| retention-days: 14 | |
| - name: Dry-run summary | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true' | |
| run: | | |
| echo "Dry run enabled: build artifacts generated and uploaded; release publish step will be skipped." | |
| publish: | |
| name: Publish Release (Manual Approval) | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: needs.build.result == 'success' && (github.event_name != 'workflow_dispatch' || github.event.inputs.dry_run != 'true') | |
| permissions: | |
| contents: write | |
| # Configure the `release` environment with required reviewers in GitHub settings | |
| # to enforce a manual approval gate before assets are uploaded. | |
| environment: | |
| name: release | |
| steps: | |
| - name: Checkout git repo | |
| uses: actions/checkout@v4 | |
| - name: Download Build Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: make-* | |
| merge-multiple: true | |
| path: release-assets | |
| - name: Install Node and NPM | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: "npm" | |
| - name: Resolve release tag | |
| id: release_tag | |
| env: | |
| RELEASE_EVENT_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }} | |
| run: | | |
| if [ -n "$RELEASE_EVENT_TAG" ]; then | |
| tag="$RELEASE_EVENT_TAG" | |
| else | |
| version="$(node -e "const v=require('./package.json').version; if (!v) { throw new Error('package.json version is empty'); } process.stdout.write(v);")" | |
| tag="v$version" | |
| fi | |
| if [ -z "$tag" ]; then | |
| echo "::error::Resolved release tag is empty" | |
| exit 1 | |
| fi | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| echo "Using release tag: $tag" | |
| - name: Ensure release is safe to update | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_TAG: ${{ steps.release_tag.outputs.tag }} | |
| ALLOW_UPDATE_PUBLISHED_RELEASE: ${{ github.event_name == 'release' && 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.allow_update_published_release) || 'false' }} | |
| run: | | |
| if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then | |
| is_draft="$(gh release view "$RELEASE_TAG" --json isDraft --jq '.isDraft')" | |
| if [ "$is_draft" != "true" ] && [ "$ALLOW_UPDATE_PUBLISHED_RELEASE" != "true" ]; then | |
| echo "::error::Release $RELEASE_TAG is already published. Refusing to upload assets." | |
| echo "::error::Re-run with workflow_dispatch input allow_update_published_release=true if you explicitly want to update it." | |
| exit 1 | |
| fi | |
| else | |
| gh release create "$RELEASE_TAG" --title "$RELEASE_TAG" --draft --notes "Automated release draft." | |
| fi | |
| - name: Upload assets to release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_TAG: ${{ steps.release_tag.outputs.tag }} | |
| run: | | |
| mapfile -t files < <(find release-assets -type f \( \ | |
| -name '*.exe' -o \ | |
| -name '*.dmg' -o \ | |
| -name '*.zip' -o \ | |
| -name '*.AppImage' -o \ | |
| -name '*.deb' -o \ | |
| -name '*.rpm' -o \ | |
| -name '*.msi' -o \ | |
| -name '*.yml' -o \ | |
| -name '*.txt' \ | |
| \) | sort) | |
| if [ "${#files[@]}" -eq 0 ]; then | |
| echo "::error::No release assets found under release-assets/" | |
| exit 1 | |
| fi | |
| printf 'Uploading %s assets to %s\n' "${#files[@]}" "$RELEASE_TAG" | |
| gh release upload "$RELEASE_TAG" "${files[@]}" --clobber |