Skip to content

Real-world Reflow 08: a polyphonic synthesizer with ctx.pool (C++) #25

Real-world Reflow 08: a polyphonic synthesizer with ctx.pool (C++)

Real-world Reflow 08: a polyphonic synthesizer with ctx.pool (C++) #25

Workflow file for this run

# Build and publish the @offbit-ai/reflow Node SDK.
#
# Triggers
# --------
# - Tag push matching `node-v*` (e.g. `node-v0.2.0`) → build every
# platform addon, run tests, then publish to npm.
# - Manual `workflow_dispatch` → build + test only.
#
# Publishing auth
# ---------------
# Uses an automation token stored as the `NPM_TOKEN` secret. Flip to
# npm trusted publishing (OIDC) once the first release is up.
name: publish-node
on:
push:
tags:
- 'node-v*'
workflow_dispatch:
permissions:
contents: read
jobs:
build:
name: build / ${{ matrix.settings.target }}
runs-on: ${{ matrix.settings.host }}
strategy:
fail-fast: false
matrix:
settings:
# `napi build --target X` is required on every entry, not just
# cross-builds: without it the output `.node` file lacks the
# platform suffix that `napi artifacts` looks for in publish.
- host: macos-14
target: aarch64-apple-darwin
build: npm run build -- --target aarch64-apple-darwin
# macos-13 (Intel) runners have long queue times and the x86_64
# SDK is already present on macos-14, so we cross-compile the
# Intel addon from the aarch64 runner instead.
- host: macos-14
target: x86_64-apple-darwin
build: npm run build -- --target x86_64-apple-darwin
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
build: npm run build -- --target x86_64-unknown-linux-gnu
- host: ubuntu-latest
target: aarch64-unknown-linux-gnu
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
build: npm run build -- --target aarch64-unknown-linux-gnu
- host: windows-latest
target: x86_64-pc-windows-msvc
build: npm run build -- --target x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Rust toolchain
if: ${{ !matrix.settings.docker }}
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.settings.target }}
- uses: mozilla-actions/sccache-action@v0.0.5
if: ${{ !matrix.settings.docker }}
- name: Install npm deps
working-directory: sdk/node
run: npm ci || npm install
- name: Build addon (host)
if: ${{ !matrix.settings.docker }}
working-directory: sdk/node
run: ${{ matrix.settings.build }}
- name: Build addon (docker)
if: ${{ matrix.settings.docker }}
uses: addnab/docker-run-action@v3
with:
image: ${{ matrix.settings.docker }}
options: -v ${{ github.workspace }}:/build -w /build/sdk/node
# The napi-rs nodejs-rust images invoke scripts through sh
# (dash), which rejects `set -o pipefail`. `-eux` is portable.
#
# The lts-debian images ship an older Rust (1.82 at time of
# writing); reflow_network requires edition 2024, which
# stabilized in 1.85. Bump the toolchain before building.
run: |
set -eux
# rquickjs-sys's build script patches QuickJS sources with
# `patch`, which isn't installed in the napi-rs base images.
(command -v patch >/dev/null) || (apt-get update && apt-get install -y patch)
rustup update stable
rustup default stable
rustup target add ${{ matrix.settings.target }}
npm ci || npm install
${{ matrix.settings.build }}
- uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.settings.target }}
# napi build emits the .node addon plus a small JS/TS loader
# shim (index.js / index.d.ts) that tests `require()` to find
# the right native binary. Both are gitignored, so test jobs
# need them shipped via the artifact.
path: |
sdk/node/*.node
sdk/node/index.js
sdk/node/index.d.ts
if-no-files-found: error
test:
name: test / ${{ matrix.settings.host }}
needs: build
runs-on: ${{ matrix.settings.host }}
strategy:
fail-fast: false
matrix:
settings:
- host: macos-14
target: aarch64-apple-darwin
# No macos-13 test run — cross-compiled x86_64 addon would
# need Rosetta-Node to execute, and x86_64 code paths are
# exercised by the Linux x86_64 test anyway.
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
- host: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Download addon for host
uses: actions/download-artifact@v4
with:
name: bindings-${{ matrix.settings.target }}
path: sdk/node
- name: Install + run tests
working-directory: sdk/node
shell: bash
run: |
npm ci || npm install
npm test
publish:
name: publish to npm
needs: [build, test]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/node-v')
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: Install + download all addons
working-directory: sdk/node
run: npm ci || npm install
- uses: actions/download-artifact@v4
with:
path: sdk/node/artifacts
# Build the wasm32 browser bundle and stage it into sdk/node/wasm/
# so it ships inside the same `@offbit-ai/reflow` npm package.
# The `"browser"` conditional export in package.json picks this
# up when bundlers target the browser; Node imports the napi
# addon as before.
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: jetli/wasm-pack-action@v0.4.0
with:
version: latest
- name: Build browser-WASM bundle
# `--target web` emits an ES module + .wasm pair that loads
# via `await init()`. wasm-pack also writes a per-package
# `package.json` and `.gitignore` into the out-dir; we strip
# them so the wasm/ subtree doesn't shadow the parent
# package.json or look like a standalone npm package.
run: |
wasm-pack build crates/reflow_rt_wasm \
--target web \
--release \
--out-dir ../../sdk/node/wasm
rm -f sdk/node/wasm/package.json sdk/node/wasm/.gitignore
- name: Distribute addons to per-platform npm packages
working-directory: sdk/node
# `napi create-npm-dir` scaffolds `npm/<platform>/` (one
# package.json per triple in package.json's napi.triples) with
# versions already matching the main package. `napi artifacts`
# copies the per-triple `.node` files in. After this step,
# each `npm/<platform>/` is a publishable package — we then
# `npm publish` them one by one in the next step.
#
# We deliberately don't run `napi prepublish` here: in v2.18 it
# also tries to `npm publish` each platform itself, doubling up
# with our manual loop and obscuring real failures.
run: |
npx napi create-npm-dir -t .
npx napi artifacts --dir artifacts
- name: Publish platform packages
working-directory: sdk/node
# No `--provenance`: would require `id-token: write` and a
# trusted-publisher OIDC token. We use a classic NPM_TOKEN here
# which can't sign provenance attestations. Add provenance once
# the org is set up for npm trusted publishing.
#
# First-publish of a new scoped package needs the NPM_TOKEN to
# have *write access to the scope* (granular tokens scoped to
# specific packages will return 404 here). Use a classic
# Automation token, or a granular token with the full scope.
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -e
for dir in npm/*/; do
[ -f "$dir/package.json" ] || continue
echo "::group::publish $(basename "$dir")"
(cd "$dir" && npm publish --access public)
echo "::endgroup::"
done
- name: Publish main package
working-directory: sdk/node
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public