name: Addon CI # Per-OS build + load smoke-test for the native audio addon # (slopsmith_audio.node), on every pull request. # # Why this exists, separate from build.yml: # build.yml runs the FULL electron-builder packaging/signing matrix and is # deliberately gated off the pull_request event (every review commit would # otherwise re-run a ~3x multi-GB release build). That left a gap: a PR that # breaks the macOS/Linux *addon* compile or link was never built on those # OSes until it had already merged to main. That is exactly how the #250 # regression shipped — an undefined symbol (typeinfo for # slopsmith::sandbox::SandboxedProcessor) that links fine but fails at # runtime dlopen, leaving AppImage/dmg users with empty audio device lists # (issue #266, regression fixed in #263). # # This job is the cheap counterpart: it builds ONLY the addon # (`npm run build:audio` — no Python, no electron-builder, no # signing) on all three OSes, then loads the resulting .node and asserts the # device-enumeration API is present. A plain build can't catch the #250 class # of bug: Node native modules link with undefined symbols permitted # (macOS uses -undefined dynamic_lookup; Linux has no -Wl,--no-undefined), so # the compile and link succeed even with an unresolved symbol — the failure # only surfaces at load time. The smoke-test load is what makes that fail CI. on: pull_request: push: branches: [main] workflow_dispatch: # Cancel an in-flight run when new commits land on the same PR/branch — no # point burning three runners on a superseded commit during a review loop. concurrency: group: addon-ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true # Least privilege: this workflow only checks out code. permissions: contents: read jobs: addon: strategy: fail-fast: false matrix: include: - os: ubuntu-22.04 platform: linux arch: x64 - os: macos-14 platform: mac arch: arm64 - os: windows-latest platform: win arch: x64 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: submodules: recursive # JUCE (and any vendored audio third_party) # No step after checkout uses GITHUB_TOKEN (public repo, public # submodules, no push), and this runs on pull_request — including # from forks — so don't leave the token persisted in .git/config. persist-credentials: false # Match the Node version the app ships with — the addon is built against # the Electron ABI, but node-addon-api headers come from node_modules and # the toolchain is pinned via .build-config.json (single source of truth, # same as build.yml). - name: Read build configuration id: config shell: bash run: | NODE_VERSION=$(node -p "require('./.build-config.json').versions.node") echo "node=$NODE_VERSION" >> "$GITHUB_OUTPUT" echo "Node ${NODE_VERSION}" - uses: actions/setup-node@v4 with: node-version: ${{ steps.config.outputs.node }} # Pin the Node binary architecture per runner (arm64 on macos-14, # x64 elsewhere) so the addon's node-addon-api headers and the # toolchain match the OS the build targets. architecture: ${{ matrix.arch }} # Linux: the addon links juce_audio_devices (ALSA/JACK) and pulls in # juce_audio_processors' transitive X11/freetype/fontconfig deps at build # time. Install from .packages/apt.txt — the same canonical list # build-linux-ubuntu.sh uses — so this stays in sync with the real build. # xvfb is added on top (not in apt.txt) purely as insurance for the # load-time smoke-test below. - name: Install Linux build deps if: matrix.platform == 'linux' run: | sudo apt-get update PACKAGES=$(grep -v '^[[:space:]]*#' .packages/apt.txt | grep -v '^[[:space:]]*$' | tr '\n' ' ') sudo apt-get install -y --no-install-recommends $PACKAGES xvfb # cmake-js + node-addon-api come from node_modules. No package-lock.json # in this repo, so `npm install` (not `npm ci`). - name: Install npm dependencies run: npm install - name: Build native audio addon run: npm run build:audio shell: bash # Load the freshly built .node and assert the device-enumeration entry # points are present. slopsmith_audio.node is an N-API addon (ABI-stable # across runtimes), so plain `node` can load an Electron-built binary — # this is the exact reproduction Byron confirmed locally for #250, where # the load fails with "undefined symbol: ...SandboxedProcessor..." even # though the build was green. typeof getDeviceTypes === 'function' also # verifies the device API the empty-device-list regression was about. - name: Smoke-test addon load (Linux) if: matrix.platform == 'linux' shell: bash run: | xvfb-run -a node -e "const a=require('./build/Release/slopsmith_audio.node'); if(typeof a.getDeviceTypes!=='function'){console.error('FAIL: getDeviceTypes export missing'); process.exit(1);} console.log('addon loaded; device API present');" - name: Smoke-test addon load (macOS / Windows) if: matrix.platform != 'linux' shell: bash run: | node -e "const a=require('./build/Release/slopsmith_audio.node'); if(typeof a.getDeviceTypes!=='function'){console.error('FAIL: getDeviceTypes export missing'); process.exit(1);} console.log('addon loaded; device API present');"