Skip to content

Commit 358f7f9

Browse files
committed
ci(node): publish the npm package with cross-platform prebuilds on release
Add .github/workflows/npm-publish.yml: on each `v*` release tag (pushed by scripts/release.sh), build the native addon on a matrix of native runners — Linux x64 (ubuntu-22.04), Linux arm64 (ubuntu-24.04-arm), macOS x64 (macos-13), macOS arm64 (macos-14), Windows x64 — then a publish job assembles the main `fleischwolf` package plus per-platform `fleischwolf-<triple>` packages (optionalDependencies) via napi-rs and publishes them all to npm. The version is taken from the tag so it tracks the crate release; already-published versions are skipped for idempotent re-runs. Configure the package for the napi-rs v2 multi-platform flow: napi.name + napi.triples for the five targets, and prepublishOnly / artifacts / version scripts. The main package ships only the JS loader + types (~50 kB); the prebuilt .node binaries ride in the platform packages. README documents `npm install fleischwolf` (prebuilt) alongside build-from-source. Requires an NPM_TOKEN repository secret with publish rights. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L7XwjsWSHWjrh39JxMLuB3
1 parent 639bd01 commit 358f7f9

4 files changed

Lines changed: 212 additions & 14 deletions

File tree

.github/workflows/npm-publish.yml

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Publish the `fleischwolf` npm package (Node.js / Bun native bindings) on each
2+
# release. The package is a native N-API addon, so it can't be a one-line
3+
# `npm publish`: it's built on a matrix of native runners (one per OS/arch),
4+
# each producing a platform `.node`, then a publish job assembles the main
5+
# package plus per-platform `optionalDependencies` (fleischwolf-<triple>) via
6+
# napi-rs and publishes them all.
7+
#
8+
# Trigger: the `v*` tag that scripts/release.sh pushes for a crates.io release
9+
# (pushed with RELEASE_PAT, so it triggers this workflow). The npm version is
10+
# taken from the tag, so it tracks the crate version. Can also be dispatched
11+
# manually with an explicit version.
12+
#
13+
# Requires one repository secret: NPM_TOKEN — an npm automation token with
14+
# publish rights to `fleischwolf` and the `fleischwolf-*` platform packages.
15+
16+
name: npm publish
17+
18+
on:
19+
push:
20+
tags:
21+
- "v*"
22+
workflow_dispatch:
23+
inputs:
24+
version:
25+
description: "Version to publish (e.g. 0.7.0). Blank = derive from the tag."
26+
required: false
27+
default: ""
28+
29+
concurrency:
30+
group: npm-publish-${{ github.ref }}
31+
cancel-in-progress: false
32+
33+
defaults:
34+
run:
35+
working-directory: crates/fleischwolf-node
36+
37+
jobs:
38+
build:
39+
name: build ${{ matrix.target }}
40+
runs-on: ${{ matrix.host }}
41+
strategy:
42+
fail-fast: false
43+
matrix:
44+
include:
45+
- target: x86_64-unknown-linux-gnu
46+
host: ubuntu-22.04
47+
# GitHub-hosted ARM64 Linux runner (native — avoids cross-compiling the
48+
# ONNX/ort build). Requires the arm64 runner image to be available.
49+
- target: aarch64-unknown-linux-gnu
50+
host: ubuntu-24.04-arm
51+
- target: x86_64-apple-darwin
52+
host: macos-13
53+
- target: aarch64-apple-darwin
54+
host: macos-14
55+
- target: x86_64-pc-windows-msvc
56+
host: windows-latest
57+
steps:
58+
- uses: actions/checkout@v4
59+
60+
- uses: dtolnay/rust-toolchain@stable
61+
with:
62+
targets: ${{ matrix.target }}
63+
64+
- uses: Swatinem/rust-cache@v2
65+
with:
66+
# One workspace, key the cache per target so the 5 jobs don't collide.
67+
key: ${{ matrix.target }}
68+
workspaces: "."
69+
70+
- uses: actions/setup-node@v4
71+
with:
72+
node-version: 20
73+
74+
- name: Install napi CLI
75+
run: npm install
76+
77+
# Native addon + the JS loader / d.ts. `--strip` keeps the (ONNX-linked)
78+
# binary as small as possible; strip isn't available under MSVC, so the
79+
# Windows build omits it.
80+
- name: Build (unix)
81+
if: runner.os != 'Windows'
82+
run: npx napi build --platform --release --strip --target ${{ matrix.target }} --js native.js --dts native.d.ts
83+
84+
- name: Build (windows)
85+
if: runner.os == 'Windows'
86+
run: npx napi build --platform --release --target ${{ matrix.target }} --js native.js --dts native.d.ts
87+
88+
- name: Upload prebuilt binary
89+
uses: actions/upload-artifact@v4
90+
with:
91+
name: bindings-${{ matrix.target }}
92+
path: crates/fleischwolf-node/fleischwolf.*.node
93+
if-no-files-found: error
94+
95+
# The JS loader + types are platform-agnostic; upload them once (from the
96+
# linux-x64 build) for the publish job to include in the main package.
97+
- name: Upload JS binding
98+
if: matrix.target == 'x86_64-unknown-linux-gnu'
99+
uses: actions/upload-artifact@v4
100+
with:
101+
name: js-binding
102+
path: |
103+
crates/fleischwolf-node/native.js
104+
crates/fleischwolf-node/native.d.ts
105+
if-no-files-found: error
106+
107+
publish:
108+
name: publish to npm
109+
needs: build
110+
runs-on: ubuntu-latest
111+
steps:
112+
- uses: actions/checkout@v4
113+
114+
- uses: actions/setup-node@v4
115+
with:
116+
node-version: 20
117+
registry-url: "https://registry.npmjs.org"
118+
119+
- name: Install napi CLI
120+
run: npm install
121+
122+
# Prebuilt binaries → artifacts/<name>/fleischwolf.<triple>.node
123+
- name: Download prebuilt binaries
124+
uses: actions/download-artifact@v4
125+
with:
126+
pattern: bindings-*
127+
path: crates/fleischwolf-node/artifacts
128+
129+
# The JS loader + types → the package root.
130+
- name: Download JS binding
131+
uses: actions/download-artifact@v4
132+
with:
133+
name: js-binding
134+
path: crates/fleischwolf-node
135+
136+
- name: Resolve version
137+
id: ver
138+
run: |
139+
v="${{ github.event.inputs.version }}"
140+
if [ -z "$v" ]; then v="${GITHUB_REF_NAME#v}"; fi
141+
echo "version=$v" >> "$GITHUB_OUTPUT"
142+
143+
# Skip cleanly if this version is already on npm (idempotent re-runs).
144+
- name: Check if already published
145+
id: check
146+
run: |
147+
v="${{ steps.ver.outputs.version }}"
148+
if npm view "fleischwolf@$v" version >/dev/null 2>&1; then
149+
echo "published=true" >> "$GITHUB_OUTPUT"
150+
echo "fleischwolf@$v is already on npm — skipping."
151+
else
152+
echo "published=false" >> "$GITHUB_OUTPUT"
153+
fi
154+
155+
- name: Set package version
156+
if: steps.check.outputs.published == 'false'
157+
run: npm version "${{ steps.ver.outputs.version }}" --no-git-tag-version --allow-same-version
158+
159+
# Create the per-platform package dirs and move each prebuilt .node into
160+
# its dir. `npm publish` then runs `prepublishOnly` (napi prepublish), which
161+
# publishes the fleischwolf-<triple> packages and wires them into the main
162+
# package's optionalDependencies before the main package is published.
163+
- name: Assemble platform packages
164+
if: steps.check.outputs.published == 'false'
165+
run: |
166+
npx napi create-npm-dir -t .
167+
npx napi artifacts --dir artifacts
168+
169+
- name: Publish
170+
if: steps.check.outputs.published == 'false'
171+
run: npm publish --access public
172+
env:
173+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

crates/fleischwolf-node/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ native.d.ts
1111

1212
# Example scratch output
1313
examples/artifacts/
14+
15+
# Cross-platform publish scaffolding, generated in CI by `napi create-npm-dir`
16+
# and `napi artifacts` (per-platform packages + downloaded prebuilt binaries)
17+
npm/
18+
artifacts/

crates/fleischwolf-node/README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,30 @@ Built with [napi-rs](https://napi.rs), so it ships a real native addon (`.node`)
1010
that loads in both Node.js and Bun (Bun implements N-API) — the same binary, no
1111
rebuild between runtimes.
1212

13-
## Install & build
13+
## Install
1414

15-
This package lives in the Fleischwolf Cargo workspace and builds the addon from
16-
the Rust source. You need a Rust toolchain (1.82+) and Node.js 14+ (or Bun).
15+
Released versions ship **prebuilt** native binaries, so no Rust toolchain is
16+
needed to use the package:
17+
18+
```bash
19+
npm install fleischwolf # or: bun add fleischwolf
20+
```
21+
22+
Prebuilt platforms: Linux x64 / arm64 (glibc), macOS x64 / arm64, Windows x64.
23+
The right binary is pulled in automatically as a platform-specific
24+
`optionalDependency` (`fleischwolf-<triple>`). Each release is published to npm
25+
from CI (`.github/workflows/npm-publish.yml`) on the version tag.
26+
27+
## Build from source
28+
29+
This package lives in the Fleischwolf Cargo workspace and can also build the
30+
addon from Rust source — needed for local development or an unsupported
31+
platform. You need a Rust toolchain (1.82+) and Node.js 14+ (or Bun).
1732

1833
```bash
1934
cd crates/fleischwolf-node
2035
npm install # installs @napi-rs/cli
21-
npm run build # release build → index.<platform>.node + native.js/.d.ts
36+
npm run build # release build → fleischwolf.<platform>.node + native.js/.d.ts
2237
# npm run build:debug # faster, unoptimized
2338
```
2439

crates/fleischwolf-node/package.json

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,17 @@
2929
}
3030
},
3131
"napi": {
32-
"binaryName": "fleischwolf",
33-
"targets": [
34-
"x86_64-unknown-linux-gnu",
35-
"aarch64-unknown-linux-gnu",
36-
"x86_64-apple-darwin",
37-
"aarch64-apple-darwin",
38-
"x86_64-pc-windows-msvc"
39-
]
32+
"name": "fleischwolf",
33+
"triples": {
34+
"defaults": false,
35+
"additional": [
36+
"x86_64-unknown-linux-gnu",
37+
"aarch64-unknown-linux-gnu",
38+
"x86_64-apple-darwin",
39+
"aarch64-apple-darwin",
40+
"x86_64-pc-windows-msvc"
41+
]
42+
}
4043
},
4144
"engines": {
4245
"node": ">= 14"
@@ -47,12 +50,14 @@
4750
"deps.js",
4851
"native.js",
4952
"native.d.ts",
50-
"index.*.node",
5153
"README.md"
5254
],
5355
"scripts": {
54-
"build": "napi build --platform --release --js native.js --dts native.d.ts",
56+
"build": "napi build --platform --release --strip --js native.js --dts native.d.ts",
5557
"build:debug": "napi build --platform --js native.js --dts native.d.ts",
58+
"artifacts": "napi artifacts",
59+
"prepublishOnly": "napi prepublish -t npm --skip-gh-release",
60+
"version": "napi version",
5661
"test": "node test/smoke.mjs",
5762
"example": "node examples/node-basic.mjs",
5863
"example:bun": "bun run examples/bun-basic.ts"

0 commit comments

Comments
 (0)