Skip to content

feat: implement macOS manifest merging script and update CI workflow … #78

feat: implement macOS manifest merging script and update CI workflow …

feat: implement macOS manifest merging script and update CI workflow … #78

Workflow file for this run

name: Build/Release
on:
push:
tags:
- 'v*'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: write
jobs:
# Build every distributable in parallel. Nothing publishes here — artifacts go
# to the `publish` job below, which is the only writer of the GitHub release.
#
# macOS is split by architecture on purpose. Notarization is ~3.5 minutes of
# waiting on Apple per arch and electron-builder runs them serially, so a
# combined mac job spent ~7 minutes of its ~10 doing nothing but waiting.
# Running the two arches as concurrent jobs overlaps that wait.
build:
name: build (${{ matrix.label }})
runs-on: ${{ matrix.os }}
strategy:
# One target's failure shouldn't cancel the others — a macOS OOM took the
# Linux and Windows builds down with it on v0.11.0, so the run reported
# nothing about whether they would have succeeded.
fail-fast: false
matrix:
include:
- os: macos-latest
label: mac-x64
args: --mac --x64
- os: macos-latest
label: mac-arm64
args: --mac --arm64
- os: ubuntu-latest
label: linux
args: --linux
- os: windows-latest
label: win
args: --win
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Verify tag matches package.json version
shell: bash
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
PKG_VERSION=$(node -p "require('./package.json').version")
echo "Tag version: $TAG_VERSION"
echo "package.json version: $PKG_VERSION"
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "::error::Tag $GITHUB_REF_NAME does not match package.json version $PKG_VERSION. Refusing to build."
exit 1
fi
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Bundle agent server
run: npm run bundle:agent
- name: Build
uses: samuelmeuli/action-electron-builder@v1
env:
# Vite's sourcemap generation is the memory ceiling on this build: the
# main chunk is ~3.2MB and its map ~12.5MB, and producing that lot
# peaks around 2.8GB RSS. The macOS arm64 runner has 7GB, so Node 20
# sizes its old-space heap at roughly 2GB by default — which v0.10.2
# fit under and v0.11.0 does not. Raise it rather than shrink the
# build, so releases don't fail on the next few hundred KB either.
NODE_OPTIONS: --max-old-space-size=4096
# Apple Notarization
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Code Signing
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
# GitHub Device Flow client IDs — public values inlined into the
# Electron renderer at build time so the desktop app can do
# GitHub OAuth without contacting redstring.io. The desktop App
# slug is constant so it's literal here; falls back to the
# default in src/services/githubDeviceFlow.js if unset.
VITE_GITHUB_CLIENT_ID: ${{ secrets.VITE_GITHUB_CLIENT_ID }}
VITE_GITHUB_APP_CLIENT_ID: ${{ secrets.VITE_GITHUB_APP_CLIENT_ID }}
VITE_GITHUB_APP_SLUG: ${{ secrets.VITE_GITHUB_APP_SLUG }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
args: ${{ matrix.args }}
# Publishing happens once, in the `publish` job. Two mac jobs writing
# to the same release would race on latest-mac.yml.
release: false
# Only the distributables. dist-electron also holds the unpacked .app and
# win-unpacked trees, which are hundreds of MB and are already contained
# in the artifacts below.
- name: Collect distributables
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.label }}
if-no-files-found: error
retention-days: 7
path: |
dist-electron/*.dmg
dist-electron/*.zip
dist-electron/*.exe
dist-electron/*.AppImage
dist-electron/*.blockmap
dist-electron/*.yml
publish:
name: publish release
needs: build
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
# Each artifact lands in its own subdirectory, which is what keeps the two
# macOS `latest-mac.yml` files from overwriting each other before the merge
# below can see them both.
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Merge macOS update manifests
run: |
npm install js-yaml@4 --no-save --no-audit --no-fund
node scripts/merge-mac-update-manifest.js artifacts release
- name: Assemble release files
shell: bash
run: |
set -euo pipefail
mkdir -p release
# Everything except latest-mac.yml, which the merge step already wrote.
# -n so nothing can clobber the merged manifest.
find artifacts -type f ! -name 'latest-mac.yml' -exec cp -n {} release/ \;
echo "Release payload:"
ls -lh release/
- name: Create draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}"
TITLE="${TAG#v}"
# Created as a draft, matching how electron-builder's GitHub publisher
# behaved: the release is reviewed and published by hand.
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG exists — uploading assets over it."
gh release upload "$TAG" release/* --clobber
else
gh release create "$TAG" release/* \
--draft \
--title "$TITLE" \
--generate-notes
fi
echo "::notice::Draft release $TAG is ready. Review and publish it manually."