Skip to content

Build & Release

Build & Release #77

Workflow file for this run

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