name: Release on: push: tags: - 'v*' workflow_call: inputs: tag: description: Tag to release, for example v1.2.3-rc.0 required: true type: string workflow_dispatch: inputs: tag: description: Existing tag to release, for example v1.2.3-rc.0 required: true type: string jobs: resolve-release: runs-on: ubuntu-latest outputs: tag: ${{ steps.resolve.outputs.tag }} ref: ${{ steps.resolve.outputs.ref }} steps: - name: Resolve release tag and ref id: resolve run: | if [[ "${{ github.event_name }}" == "push" ]]; then release_tag="${{ github.ref_name }}" release_ref="${{ github.ref }}" else release_tag="${{ inputs.tag }}" release_ref="refs/tags/${{ inputs.tag }}" fi echo "tag=$release_tag" >>"$GITHUB_OUTPUT" echo "ref=$release_ref" >>"$GITHUB_OUTPUT" create-release: needs: resolve-release runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout uses: actions/checkout@v4 with: ref: ${{ needs.resolve-release.outputs.ref }} - name: Create draft release with auto-generated notes run: | if gh release view "${{ needs.resolve-release.outputs.tag }}" --repo "${{ github.repository }}" >/dev/null 2>&1; then echo "Release ${{ needs.resolve-release.outputs.tag }} already exists." exit 0 fi gh release create "${{ needs.resolve-release.outputs.tag }}" \ --draft \ --generate-notes \ --prerelease=${{ contains(needs.resolve-release.outputs.tag, 'rc') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Why: E2E runs alongside the release for visibility (failures surface as a # red check on the tag), but it is NOT in `release`'s needs list. Releases # already take a while and the suite is already a required check on PRs, so # gating here would mostly just delay shipping without adding much signal. # Matches the pattern used by noqa's app deploy, which runs E2E with # continue-on-error so failures are visible but don't block the deploy. e2e: needs: resolve-release uses: ./.github/workflows/e2e.yml with: ref: ${{ needs.resolve-release.outputs.ref }} release: needs: - resolve-release - create-release strategy: fail-fast: false matrix: include: - os: macos-15 platform: mac release_command: ORCA_MAC_RELEASE=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish always - os: windows-latest platform: win release_command: pnpm exec electron-builder --config config/electron-builder.config.cjs --win --publish always - os: ubuntu-latest platform: linux release_command: pnpm exec electron-builder --config config/electron-builder.config.cjs --linux --publish always runs-on: ${{ matrix.os }} permissions: contents: write steps: - name: Checkout uses: actions/checkout@v4 with: ref: ${{ needs.resolve-release.outputs.ref }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 24 - name: Setup pnpm uses: pnpm/action-setup@v4 with: run_install: false # Why: pnpm install triggers electron's postinstall, which downloads the # Electron binary from GitHub release assets. GitHub's download CDN # occasionally returns 504s that fail the whole release. Retry on # failure so transient network errors don't require a manual re-run. - name: Install dependencies uses: nick-fields/retry@v3 with: timeout_minutes: 10 max_attempts: 3 retry_wait_seconds: 30 command: pnpm install --frozen-lockfile - name: Verify macOS signing environment if: matrix.platform == 'mac' run: node config/scripts/verify-macos-release-env.mjs env: CSC_LINK: ${{ secrets.MAC_CERTS }} CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Build app run: pnpm build:release # Why: macOS signing secrets (CSC_LINK, CSC_KEY_PASSWORD) must NOT be # passed to non-macOS builds. electron-builder uses CSC_LINK as the # code-signing certificate on any platform, so leaking the Apple # Developer ID cert to the Windows build causes the NSIS installer to # be signed with an Apple cert whose chain Windows cannot validate, # breaking the auto-updater with "certificate chain could not be built # to a trusted root authority" (issue #631). # # Why retry: electron-builder downloads NSIS/winCodeSign/squirrel # binaries and the Electron runtime from GitHub release assets during # publish. GitHub's download CDN occasionally returns 504s that fail # the whole release. Retry on failure so transient network errors # don't require a manual re-run. - name: Publish release artifacts (macOS) if: matrix.platform == 'mac' uses: nick-fields/retry@v3 with: timeout_minutes: 45 max_attempts: 3 retry_wait_seconds: 30 command: ${{ matrix.release_command }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} CSC_LINK: ${{ secrets.MAC_CERTS }} CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Publish release artifacts if: matrix.platform != 'mac' uses: nick-fields/retry@v3 with: timeout_minutes: 30 max_attempts: 3 retry_wait_seconds: 30 command: ${{ matrix.release_command }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish-release: needs: - resolve-release - release runs-on: ubuntu-latest permissions: contents: write steps: - name: Publish release run: gh release edit "${{ needs.resolve-release.outputs.tag }}" --draft=false --repo "${{ github.repository }}" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}