Build & Release #77
Workflow file for this run
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: Build & Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| prepare-release: | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Clean existing release assets | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="${GITHUB_REF_NAME}" | |
| if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" &>/dev/null; then | |
| echo "Release $TAG exists, deleting all assets to avoid upload conflicts..." | |
| gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets -q '.assets[].name' | while read -r asset; do | |
| echo "Deleting: $asset" | |
| gh release delete-asset "$TAG" "$asset" --repo "$GITHUB_REPOSITORY" --yes || true | |
| done | |
| else | |
| echo "No existing release for $TAG, nothing to clean" | |
| fi | |
| test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install | |
| - name: Run tests | |
| run: pnpm test | |
| # macOS: build both arm64 + x64 in one job so electron-builder writes a single | |
| # latest-mac.yml containing both architectures. Separate jobs race and the last | |
| # one to finish overwrites the other's YAML → wrong arch served to half the users. | |
| build-mac: | |
| needs: [prepare-release, test] | |
| if: ${{ always() && needs.test.result == 'success' && (needs.prepare-release.result == 'success' || needs.prepare-release.result == 'skipped') }} | |
| runs-on: macos-26 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install | |
| - name: Build app | |
| run: pnpm build | |
| - name: Package (mac arm64 + x64) | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CSC_IDENTITY_AUTO_DISCOVERY: "false" | |
| run: > | |
| pnpm exec electron-builder | |
| --mac | |
| --arm64 --x64 | |
| --config electron-builder.config.js | |
| --publish ${{ startsWith(github.ref, 'refs/tags/v') && 'always' || 'never' }} | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Harnss-mac | |
| path: | | |
| release/**/*.dmg | |
| release/**/*.zip | |
| release/**/*.yml | |
| release/**/*.yaml | |
| !release/**/*.blockmap | |
| retention-days: 30 | |
| # Windows: same approach as macOS — one job builds both arches so electron-builder | |
| # writes a single latest.yml with both installers listed. | |
| build-win: | |
| needs: [prepare-release, test] | |
| if: ${{ always() && needs.test.result == 'success' && (needs.prepare-release.result == 'success' || needs.prepare-release.result == 'skipped') }} | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install | |
| - name: Build app | |
| run: pnpm build | |
| - name: Package (win x64 + arm64) | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: > | |
| pnpm exec electron-builder | |
| --win | |
| --x64 --arm64 | |
| --config electron-builder.config.js | |
| --publish ${{ startsWith(github.ref, 'refs/tags/v') && 'always' || 'never' }} | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Harnss-win | |
| path: | | |
| release/**/*.exe | |
| release/**/*.yml | |
| release/**/*.yaml | |
| !release/**/*.blockmap | |
| retention-days: 30 | |
| # Linux: can't use --x64 --arm64 in one invocation (AppImage builder tries to | |
| # access cross-arch vendor binaries that don't exist in the other arch's staging | |
| # dir). Build sequentially, then merge latest-linux.yml before publishing. | |
| build-linux: | |
| needs: [prepare-release, test] | |
| if: ${{ always() && needs.test.result == 'success' && (needs.prepare-release.result == 'success' || needs.prepare-release.result == 'skipped') }} | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Install Linux build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential python3 libsecret-1-dev libnotify-dev | |
| - name: Install dependencies | |
| run: pnpm install | |
| - name: Build app | |
| run: pnpm build | |
| - name: Package (linux x64) | |
| run: > | |
| pnpm exec electron-builder | |
| --linux --x64 | |
| --config electron-builder.config.js | |
| --publish never | |
| - name: Save x64 update manifest | |
| run: cp release/*/latest-linux.yml latest-linux-x64.yml 2>/dev/null || true | |
| - name: Package (linux arm64) | |
| run: > | |
| pnpm exec electron-builder | |
| --linux --arm64 | |
| --config electron-builder.config.js | |
| --publish never | |
| - name: Merge update manifests | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| DIR="release/${VERSION}" | |
| # arm64 build overwrites latest-linux.yml — merge x64 entries back in | |
| if [ -f "latest-linux-x64.yml" ] && [ -f "${DIR}/latest-linux.yml" ]; then | |
| yq eval-all ' | |
| select(fileIndex == 0).files += select(fileIndex == 1).files | |
| | select(fileIndex == 0) | |
| ' latest-linux-x64.yml "${DIR}/latest-linux.yml" > "${DIR}/latest-linux-merged.yml" | |
| mv "${DIR}/latest-linux-merged.yml" "${DIR}/latest-linux.yml" | |
| elif [ -f "latest-linux-x64.yml" ]; then | |
| cp latest-linux-x64.yml "${DIR}/latest-linux.yml" | |
| fi | |
| - name: Publish to GitHub Release | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| TAG="${GITHUB_REF_NAME}" | |
| DIR="release/${VERSION}" | |
| for file in "${DIR}"/*.AppImage "${DIR}"/*.deb "${DIR}"/latest-linux.yml; do | |
| if [ -f "$file" ]; then | |
| echo "Uploading: $(basename "$file")" | |
| gh release upload "$TAG" "$file" --clobber || true | |
| fi | |
| done | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Harnss-linux | |
| path: | | |
| release/**/*.AppImage | |
| release/**/*.deb | |
| release/**/*.yml | |
| release/**/*.yaml | |
| !release/**/*.blockmap | |
| retention-days: 30 |