Skip to content

fix(core): meeting recorder bundling + full-screen capture, renderer … #42

fix(core): meeting recorder bundling + full-screen capture, renderer …

fix(core): meeting recorder bundling + full-screen capture, renderer … #42

Workflow file for this run

name: Build and Release
on:
push:
branches:
- main
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
# macOS-only for now. ONE signed + notarized DMG: the build bundles the private pro
# source (cloned from off-grid-ai/desktop-pro) and is license-gated at runtime —
# free = unlicensed (pro tabs locked), a valid Keygen key unlocks in place (no
# re-download). The ASAR integrity fuse (electron-builder.yml) makes a tampered
# app.asar refuse to load — our local-first anti-tamper lever (we don't phone home).
# Actions are pinned to commit SHAs. Windows is parked.
jobs:
# Bump the version once and push it so the build job builds that exact commit.
version:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
runs-on: ubuntu-latest
outputs:
version: ${{ steps.bump.outputs.version }}
sha: ${{ steps.bump.outputs.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
- name: Bump patch version
id: bump
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
npm version patch --no-git-tag-version
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
git add package.json package-lock.json
git commit -m "chore: bump version to $VERSION [skip ci]"
git push
# Pin the build to THIS exact commit, not floating main (avoids the
# concurrent-bump version race).
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
# Single build: pro code bundled, license-gated. Publishes to the default update
# channel (latest-mac.yml).
build-mac:
needs: version
runs-on: macos-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ needs.version.outputs.sha }} # the exact bumped commit
fetch-depth: 0
lfs: true # resources/bin/* (llama-server, sd-cli, whisper, ffmpeg, dylibs)
# are Git LFS — without this CI bundles pointer stubs and every
# runtime spawn fails with ENOEXEC.
- name: Pull LFS binaries
run: git lfs pull
# Rebuild the chat engine from source with a PINNED macOS deployment target.
# The committed/LFS llama-server is minos 26 (built on a macOS-26 toolchain),
# so it only launches on macOS 26 — every macOS 13/14/15 user saw "Chat model
# Down". This compiles llama-server targeting macOS 13, GATES on its minos,
# and overwrites resources/bin/llama/ BEFORE electron-builder signs+notarizes,
# so the shipped engine is correct by construction. (~2 min, zero deps.)
- name: Build llama-server (pinned deployment target)
run: |
command -v cmake >/dev/null || brew install cmake
MACOS_DEPLOYMENT_TARGET=13.0 LLAMA_REF=b9838 bash scripts/build-llama.sh
otool -l resources/bin/llama/llama-server | awk '/LC_BUILD_VERSION/{f=1} f&&/minos/{print "[ci] bundled engine minos="$2; exit}'
# Compile the native meeting recorder (ScreenCaptureKit + AVFoundation) and stage
# it into resources/bin so extraResources bundles it at Contents/Resources/bin —
# the first path recorderBin() resolves in the packaged app (pro meeting-native.ts).
# Built fresh against the pinned macOS 13 target (NOT the committed dev copy), and
# signed automatically by the electron-builder pass. Without this the binary is
# never staged into the .app and packaged users hit "Meeting recorder binary not
# found" — same class of bug as the un-pinned/un-staged engine failures above.
- name: Build meeting recorder (native ScreenCaptureKit helper)
run: |
bash scripts/build-meeting-recorder.sh
mkdir -p resources/bin
cp scripts/meeting-recorder/meeting-recorder resources/bin/meeting-recorder
chmod +x resources/bin/meeting-recorder
file resources/bin/meeting-recorder
otool -l resources/bin/meeting-recorder | awk '/LC_BUILD_VERSION/{f=1} f&&/minos/{print "[ci] meeting-recorder minos="$2; exit}'
# Private pro source into pro/ → __OFFGRID_PRO__ true. The checkout action
# injects the token as an http.extraheader (never in a clone URL) and scrubs
# it afterwards — safer than embedding ${TOKEN} in a git URL.
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
repository: off-grid-ai/desktop-pro
token: ${{ secrets.PRO_REPO_TOKEN }}
path: pro
fetch-depth: 1
- name: Drop nested .git from pro/
run: rm -rf pro/.git # don't nest a repo inside the build tree
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build (typecheck + bundle)
run: npm run build
# Same approach as mobile (.github/workflows/release.yml): collect the commits
# since the previous release tag into release-notes.md. Generated BEFORE publish
# so the tag electron-builder is about to create (v$VERSION) is not yet the
# "last tag" — git describe resolves to the PREVIOUS release. We drop the
# bot version-bump commit ([skip ci]) so notes are only real changes.
- name: Generate release notes
run: |
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
node scripts/release-notes.mjs \
"$LAST_TAG" \
"${{ needs.version.outputs.version }}" \
"${{ github.repository }}" \
> release-notes.md
cat release-notes.md
- name: Package, sign, notarize & publish (DMG + auto-update metadata)
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# notarytool needs the API key as a file; write it from the secret.
printf '%s' "$APPLE_API_KEY_CONTENT" > "$RUNNER_TEMP/AuthKey.p8"
export APPLE_API_KEY="$RUNNER_TEMP/AuthKey.p8"
# ONE signed + notarized build, published to the default update channel
# (the flow that shipped 0.0.22–0.0.24). electron-builder uploads the DMG
# (human download) plus the zip + latest-mac.yml + blockmap that
# electron-updater needs, so existing users keep auto-updating.
npx electron-builder --mac \
-c.dmg.artifactName='OffGrid-${version}.${ext}' \
--publish always
- name: Migrate legacy Pro update channel (publish pro-mac.yml)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.version.outputs.version }}
run: |
# Older Pro builds poll a SEPARATE channel (pro-mac.yml); the single build
# now publishes only the default latest-mac.yml. Upload a copy as
# pro-mac.yml (identical - same signed zip + version) so those installs see
# this release, auto-update, and land on the default channel. After that
# they follow latest-mac.yml. One-time bridge so no Pro user is orphaned.
cp dist/latest-mac.yml dist/pro-mac.yml
gh release upload "v$VERSION" dist/pro-mac.yml --clobber
# electron-builder creates the GitHub release with empty notes; attach the
# notes generated above (mobile sets them at create time via --notes-file; the
# desktop release is created by electron-builder, so we set them after).
- name: Attach release notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.version.outputs.version }}
run: gh release edit "v$VERSION" --notes-file release-notes.md