name: Build on: workflow_dispatch: inputs: tag: description: Existing stable release tag to rebuild without moving it (vX.Y.Z). required: true type: string push: tags: - 'v*' permissions: contents: read concurrency: group: release-${{ inputs.tag || github.ref_name }} cancel-in-progress: false env: WORKFLOW_COMMIT: ${{ github.workflow_sha }} jobs: plan: runs-on: ubuntu-latest timeout-minutes: 10 outputs: tag: ${{ steps.release.outputs.tag }} version: ${{ steps.release.outputs.version }} source: ${{ steps.release.outputs.source }} matrix: ${{ steps.release.outputs.matrix }} steps: - uses: actions/checkout@v7 with: ref: ${{ github.workflow_sha }} fetch-depth: 0 persist-credentials: false - uses: ./.github/actions/setup-bun - id: release name: Resolve immutable source and native matrix env: RELEASE_TAG: ${{ inputs.tag || github.ref_name }} run: bun tools/release/release-packages/src/native/resolve.ts frontend: needs: plan runs-on: ubuntu-latest timeout-minutes: 20 outputs: sha256: ${{ steps.frontend.outputs.sha256 }} steps: - uses: actions/checkout@v7 with: ref: ${{ needs.plan.outputs.source }} persist-credentials: false - uses: actions/checkout@v7 with: ref: ${{ github.workflow_sha }} path: .pipeline persist-credentials: false - uses: actions/setup-node@v7 with: node-version: 24.x - uses: ./.pipeline/.github/actions/setup-bun with: cache-scope: desktop - name: Install pinned workflow tooling run: bun install --cwd .pipeline --frozen-lockfile - name: Build shared desktop frontend and package outputs run: | bun run generate:icons --target desktop bun run generate:tauri-menu bun run build - name: Prepare and validate npm tarballs run: | bun .pipeline/tools/release/release-packages/src/cli.ts prepare bun .pipeline/tools/release/release-packages/src/cli.ts pack - name: Extract exact release notes env: RELEASE_VERSION: ${{ needs.plan.outputs.version }} run: bun .pipeline/tools/release/release-packages/src/extract-release-notes.ts "$RELEASE_VERSION" release-notes.md - name: Archive platform-independent native inputs id: frontend run: | tar -cf frontend.tar dist desktop/icons desktop/generated echo "sha256=$(sha256sum frontend.tar | cut -d ' ' -f 1)" >> "$GITHUB_OUTPUT" - uses: actions/upload-artifact@v7 with: name: release-frontend path: | frontend.tar release-notes.md if-no-files-found: error compression-level: 0 - uses: actions/upload-artifact@v7 with: name: release-npm path: .npm-packages/*.tgz include-hidden-files: true if-no-files-found: error compression-level: 0 native: needs: [plan, frontend] strategy: fail-fast: false matrix: ${{ fromJSON(needs.plan.outputs.matrix) }} runs-on: ${{ matrix.platform }} timeout-minutes: 40 env: RELEASE_TAG: ${{ needs.plan.outputs.tag }} SOURCE_COMMIT: ${{ needs.plan.outputs.source }} FRONTEND_SHA256: ${{ needs.frontend.outputs.sha256 }} RELEASE_TARGET: ${{ matrix.target }} steps: - uses: actions/checkout@v7 with: ref: ${{ needs.plan.outputs.source }} persist-credentials: false - uses: actions/checkout@v7 with: ref: ${{ github.workflow_sha }} path: .pipeline persist-credentials: false - uses: actions/setup-node@v7 with: node-version: 24.x - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 with: workspaces: desktop -> target key: ${{ matrix.target }} - name: Install native dependencies on Linux if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - uses: ./.pipeline/.github/actions/setup-bun with: cache-scope: desktop cache-save: ${{ matrix.saveBunCache && runner.os != 'Linux' }} - name: Install pinned workflow tooling run: bun install --cwd .pipeline --frozen-lockfile - uses: actions/download-artifact@v8 with: name: release-frontend - name: Verify shared inputs and disable only the redundant build hook run: bun .pipeline/tools/release/release-packages/src/native/frontend.ts - name: Extract shared frontend, icons and menu run: tar -xf frontend.tar - name: Verify Node and Tauri startup timeout-minutes: 1 run: | node --version node node_modules/@tauri-apps/cli/tauri.js --version - name: Build and sign native bundles (no release uploads) id: tauri timeout-minutes: 30 uses: tauri-apps/tauri-action@v0 env: TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || '' }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} with: tauriScript: node node_modules/@tauri-apps/cli/tauri.js args: --target ${{ matrix.target }} --config "${{ github.workspace }}/release-build-config.json" --verbose retryAttempts: 0 - name: Collect only reported bundles and signatures env: TAURI_ARTIFACT_PATHS: ${{ steps.tauri.outputs.artifactPaths }} run: bun .pipeline/tools/release/release-packages/src/native/collect.ts - uses: actions/upload-artifact@v7 with: name: native-${{ matrix.target }} path: native-output/* if-no-files-found: error compression-level: 0 publish: needs: [plan, frontend, native] runs-on: ubuntu-latest timeout-minutes: 25 permissions: contents: write id-token: write attestations: write env: RELEASE_TAG: ${{ needs.plan.outputs.tag }} SOURCE_COMMIT: ${{ needs.plan.outputs.source }} FRONTEND_SHA256: ${{ needs.frontend.outputs.sha256 }} GH_TOKEN: ${{ github.token }} steps: - uses: actions/checkout@v7 with: ref: ${{ needs.plan.outputs.source }} persist-credentials: false - uses: actions/checkout@v7 with: ref: ${{ github.workflow_sha }} path: .pipeline persist-credentials: false - uses: actions/setup-node@v7 with: node-version: 24.x registry-url: https://registry.npmjs.org/ - uses: ./.pipeline/.github/actions/setup-bun with: cache-scope: desktop cache-save: 'false' - name: Install pinned workflow tooling run: bun install --cwd .pipeline --frozen-lockfile - uses: actions/download-artifact@v8 with: name: release-frontend - uses: actions/download-artifact@v8 with: name: release-npm path: .npm-packages - uses: actions/download-artifact@v8 with: pattern: native-* path: native-artifacts - name: Install signature verifier run: sudo apt-get update && sudo apt-get install -y minisign - name: Verify all targets, digests and updater signatures run: bun .pipeline/tools/release/release-packages/src/native/assemble.ts - name: Refuse published releases or moved tags run: bun .pipeline/tools/release/release-packages/src/native/publish.ts check - name: Attest the complete binary set and source/workflow manifest id: provenance uses: actions/attest@v4 with: subject-path: release-output/* - name: Verify attestations against the pinned workflow env: ATTESTATION_BUNDLE: ${{ steps.provenance.outputs.bundle-path }} run: | for artifact in release-output/*; do gh attestation verify "$artifact" --bundle "$ATTESTATION_BUNDLE" \ --repo "$GITHUB_REPOSITORY" \ --signer-workflow "$GITHUB_REPOSITORY/.github/workflows/build.yml" \ --signer-digest "$WORKFLOW_COMMIT" --deny-self-hosted-runners done - name: Publish verified npm artifacts with provenance run: bun .pipeline/tools/release/release-packages/src/cli.ts publish - name: Replace and verify the entire draft asset set run: bun .pipeline/tools/release/release-packages/src/native/publish.ts upload