name: Build & Release on: push: tags: - 'v*' pull_request: branches: [main] workflow_dispatch: inputs: publish: description: 'Publish to GitHub Releases' required: false type: boolean default: false concurrency: group: build-${{ github.ref }} cancel-in-progress: true env: PNPM_VERSION: '10.24.0' NODE_VERSION: '22' CI_RELEASE_TAG: ci-build-${{ github.run_id }} jobs: # ───────────────────────────────────────────── # Stage 1: Lint & Typecheck (fast feedback) # ───────────────────────────────────────────── check: name: Lint & Typecheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: ${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm typecheck - run: pnpm lint - run: pnpm typecheck:all # ───────────────────────────────────────────── # Stage 2: Unit Tests # ───────────────────────────────────────────── test: name: Unit Tests runs-on: ubuntu-latest needs: check steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: ${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm test:unit - run: pnpm test:integration # ───────────────────────────────────────────── # Stage 3: Build matrix (parallel per platform) # ───────────────────────────────────────────── build-macos: name: macOS (${{ matrix.arch }}) runs-on: ${{ matrix.runner }} needs: test permissions: contents: write strategy: fail-fast: false matrix: include: - arch: arm64 runner: macos-14 builder_args: --mac --arm64 - arch: x64 runner: macos-15 builder_args: --mac --x64 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: ${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - run: pnpm install --frozen-lockfile - name: Decode signing certificate if: env.CERT_B64 != '' env: CERT_B64: ${{ secrets.MAC_CERTIFICATE_P12_BASE64 }} run: echo "$CERT_B64" | base64 --decode > /tmp/cert.p12 - name: Build electron-vite run: pnpm exec electron-vite build - name: Package & sign run: pnpm exec electron-builder ${{ matrix.builder_args }} --config electron-builder.yml --publish never env: CSC_LINK: ${{ secrets.MAC_CERTIFICATE_P12_BASE64 != '' && '/tmp/cert.p12' || '' }} CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} CSC_IDENTITY_AUTO_DISCOVERY: ${{ secrets.MAC_CERTIFICATE_P12_BASE64 != '' && 'true' || 'false' }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - name: Upload to CI release (Cloudflare transport) env: GH_TOKEN: ${{ github.token }} run: | gh release view "$CI_RELEASE_TAG" >/dev/null 2>&1 || \ gh release create "$CI_RELEASE_TAG" \ --draft \ --prerelease \ --title "CI Build $GITHUB_RUN_ID" \ --notes "Temporary build artifact transport for Cloudflare publication." \ --target "$GITHUB_SHA" gh release upload "$CI_RELEASE_TAG" \ build/release/*.dmg \ build/release/*.zip \ build/release/*.blockmap \ build/release/latest-mac.yml \ --clobber - name: Upload artifacts for release job uses: actions/upload-artifact@v4 with: name: macos-${{ matrix.arch }} path: | build/release/*.dmg build/release/*.zip build/release/*.blockmap build/release/latest-mac.yml build-windows: name: Windows (${{ matrix.arch }}) runs-on: windows-latest needs: test permissions: contents: write strategy: fail-fast: false matrix: include: - arch: x64 builder_args: --win --x64 - arch: arm64 builder_args: --win --arm64 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: ${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - run: pnpm install --frozen-lockfile - name: Build electron-vite run: pnpm exec electron-vite build - name: Package run: pnpm exec electron-builder ${{ matrix.builder_args }} --config electron-builder.yml --publish never env: CSC_LINK: ${{ secrets.WIN_CERTIFICATE_P12_BASE64 }} CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }} - name: Upload to CI release (Cloudflare transport) shell: bash env: GH_TOKEN: ${{ github.token }} run: | gh release view "$CI_RELEASE_TAG" >/dev/null 2>&1 || \ gh release create "$CI_RELEASE_TAG" \ --draft \ --prerelease \ --title "CI Build $GITHUB_RUN_ID" \ --notes "Temporary build artifact transport for Cloudflare publication." \ --target "$GITHUB_SHA" gh release upload "$CI_RELEASE_TAG" \ build/release/*.exe \ build/release/*.blockmap \ build/release/latest.yml \ --clobber - name: Upload artifacts for release job uses: actions/upload-artifact@v4 with: name: windows-${{ matrix.arch }} path: | build/release/*.exe build/release/*.blockmap build/release/latest.yml build-linux: name: Linux (${{ matrix.arch }}) runs-on: ubuntu-latest needs: test permissions: contents: write strategy: fail-fast: false matrix: include: - arch: x64 builder_args: --linux --x64 - arch: arm64 builder_args: --linux --arm64 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: ${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - run: pnpm install --frozen-lockfile - name: Build electron-vite run: pnpm exec electron-vite build - name: Package run: pnpm exec electron-builder ${{ matrix.builder_args }} --config electron-builder.yml --publish never - name: Upload release files env: GH_TOKEN: ${{ github.token }} run: | gh release view "$CI_RELEASE_TAG" >/dev/null 2>&1 || \ gh release create "$CI_RELEASE_TAG" \ --draft \ --prerelease \ --title "CI Build $GITHUB_RUN_ID" \ --notes "Temporary build artifact transport for Cloudflare publication." \ --target "$GITHUB_SHA" gh release upload "$CI_RELEASE_TAG" \ build/release/*.AppImage \ build/release/*.deb \ build/release/*.rpm \ build/release/*.blockmap \ build/release/latest-linux.yml \ --clobber - name: Upload artifacts for release job uses: actions/upload-artifact@v4 with: name: linux-${{ matrix.arch }} path: | build/release/*.AppImage build/release/*.deb build/release/*.rpm build/release/*.blockmap build/release/latest-linux.yml # ───────────────────────────────────────────── # Stage 4: Publish to GitHub Releases # ───────────────────────────────────────────── release: name: Publish Release runs-on: ubuntu-latest needs: [build-macos, build-windows, build-linux] if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish == 'true' permissions: contents: write steps: - uses: actions/checkout@v4 - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts merge-multiple: false - name: Collect release files run: | mkdir -p release-files find artifacts -type f \( \ -name "*.dmg" -o -name "*.zip" -o -name "*.exe" \ -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \ -o -name "*.snap" -o -name "*.blockmap" \ -o -name "latest*.yml" \ \) -exec cp {} release-files/ \; ls -lh release-files/ - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: draft: true generate_release_notes: true files: release-files/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}