Skip to content

Commit 95c2a21

Browse files
clawd21Clawd
andauthored
ci: Add GitHub Actions workflow for lni_nodejs multi-platform builds (#28)
* ci: Add GitHub Actions workflow for lni_nodejs multi-platform builds Builds native Node.js bindings for: - Linux x64 (gnu) - Linux ARM64 (gnu) - macOS x64 - macOS ARM64 - macOS Universal (lipo) - Windows x64 (msvc) Features: - Runs on push to master/main and tags - Creates GitHub Release on version tags (v*) - Uploads platform-specific .node binaries as release assets - Optional npm publish (requires NPM_TOKEN secret) - Test step to verify bindings load correctly * fix(ci): Add protobuf-compiler and fix napi build command - Install protobuf-compiler on all platforms (needed for prost-build) - Use 'yarn napi build' with --target flag directly - Add brew/choco install steps for macOS/Windows * security(ci): Pin all GitHub Actions to commit SHAs Replace mutable tag references with reproducible commit SHA pins: - actions/checkout@v4 → @34e114876b0b11c390a56381ad16ebd13914f8d5 - actions/setup-node@v4 → @49933ea5288caeca8642d1e84afbd3f7d6820020 - dtolnay/rust-toolchain@stable → @4be9e76fd7c4901c61fb841f559994984270fce7 - actions/upload-artifact@v4 → @ea165f8d65b6e75b540449e92b4886f43607fa02 - actions/download-artifact@v4 → @d3f86a106a0bac45b974a628896c90dbdf5c8093 - softprops/action-gh-release@v1 → @de2c0eb89ae2a093876385947365aca7b0e5f844 Original tags preserved in comments above each uses: line for clarity. * fix(ci): Use architecture-specific macOS runners - darwin-x64: Use macos-13 (Intel runner) for native x86_64 build - darwin-arm64: Use macos-14 (Apple Silicon runner) for native aarch64 build - Update test and universal-macos jobs to use macos-14 macos-latest now defaults to arm64, causing cross-compilation issues. * security(ci): Check NODE_AUTH_TOKEN env var instead of interpolating secret Avoid interpolating ${{ secrets.NPM_TOKEN }} directly into shell script. Instead, check the NODE_AUTH_TOKEN environment variable which is already set via the env block. This prevents potential secret exposure in logs. * chore(ci): Update softprops/action-gh-release to v2 - v1 → v2 (SHA: a06a81a03ee405af7f2048a818ed3f03bbf83c7b) - v2 uses node20 runtime (vs node16 in v1) - All existing inputs (files, generate_release_notes, draft, prerelease) are compatible * fix(nodejs): add macOS linker flags for N-API undefined symbols The darwin builds were failing because the linker couldn't find N-API symbols (_napi_create_error, _napi_create_function, etc.). These symbols are provided by Node.js at runtime, so we need -undefined dynamic_lookup to tell the linker to defer resolution. * ci: update macOS runners to non-deprecated versions - macos-13 (Intel) → macos-15-intel - macos-14 (ARM64) → macos-latest Per GitHub deprecation notice: actions/runner-images#13046 --------- Co-authored-by: Clawd <clawd@clawd.bot>
1 parent 99dad54 commit 95c2a21

File tree

2 files changed

+276
-1
lines changed

2 files changed

+276
-1
lines changed

.github/workflows/build-nodejs.yml

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
name: Build lni_nodejs
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
tags:
7+
- 'v*'
8+
pull_request:
9+
branches: [master, main]
10+
workflow_dispatch:
11+
12+
env:
13+
CARGO_TERM_COLOR: always
14+
15+
jobs:
16+
build:
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
include:
21+
# Linux
22+
- os: ubuntu-latest
23+
target: x86_64-unknown-linux-gnu
24+
name: linux-x64-gnu
25+
- os: ubuntu-latest
26+
target: aarch64-unknown-linux-gnu
27+
name: linux-arm64-gnu
28+
# macOS (use specific runners for native builds)
29+
- os: macos-15-intel # Intel runner for x64 (macos-13 deprecated)
30+
target: x86_64-apple-darwin
31+
name: darwin-x64
32+
- os: macos-latest # Apple Silicon runner for arm64 (macos-15)
33+
target: aarch64-apple-darwin
34+
name: darwin-arm64
35+
# Windows
36+
- os: windows-latest
37+
target: x86_64-pc-windows-msvc
38+
name: win32-x64-msvc
39+
40+
runs-on: ${{ matrix.os }}
41+
name: Build - ${{ matrix.name }}
42+
43+
steps:
44+
# actions/checkout@v4
45+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
46+
47+
- name: Setup Node.js
48+
# actions/setup-node@v4
49+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
50+
with:
51+
node-version: '20'
52+
53+
- name: Install Rust
54+
# dtolnay/rust-toolchain@stable
55+
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7
56+
with:
57+
toolchain: stable
58+
targets: ${{ matrix.target }}
59+
60+
- name: Install build dependencies (Linux)
61+
if: runner.os == 'Linux'
62+
run: |
63+
sudo apt-get update
64+
sudo apt-get install -y protobuf-compiler
65+
66+
- name: Install cross-compilation tools (Linux ARM64)
67+
if: matrix.target == 'aarch64-unknown-linux-gnu'
68+
run: |
69+
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
70+
71+
- name: Install build dependencies (macOS)
72+
if: runner.os == 'macOS'
73+
run: |
74+
brew install protobuf
75+
76+
- name: Install build dependencies (Windows)
77+
if: runner.os == 'Windows'
78+
run: |
79+
choco install protoc -y
80+
81+
- name: Install dependencies
82+
working-directory: bindings/lni_nodejs
83+
run: yarn install
84+
85+
- name: Build native module
86+
working-directory: bindings/lni_nodejs
87+
env:
88+
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
89+
run: |
90+
yarn napi build --platform --release --target ${{ matrix.target }}
91+
92+
- name: Upload artifact
93+
# actions/upload-artifact@v4
94+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
95+
with:
96+
name: bindings-${{ matrix.name }}
97+
path: bindings/lni_nodejs/*.node
98+
if-no-files-found: error
99+
100+
# Universal macOS binary
101+
universal-macos:
102+
needs: build
103+
runs-on: macos-latest # Apple Silicon (lipo works on any arch)
104+
name: Universal macOS Binary
105+
106+
steps:
107+
# actions/checkout@v4
108+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
109+
110+
- name: Download x64 artifact
111+
# actions/download-artifact@v4
112+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
113+
with:
114+
name: bindings-darwin-x64
115+
path: artifacts/x64
116+
117+
- name: Download arm64 artifact
118+
# actions/download-artifact@v4
119+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
120+
with:
121+
name: bindings-darwin-arm64
122+
path: artifacts/arm64
123+
124+
- name: Create universal binary
125+
run: |
126+
lipo -create \
127+
artifacts/x64/*.node \
128+
artifacts/arm64/*.node \
129+
-output lni_js.darwin-universal.node
130+
131+
- name: Upload universal artifact
132+
# actions/upload-artifact@v4
133+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
134+
with:
135+
name: bindings-darwin-universal
136+
path: lni_js.darwin-universal.node
137+
138+
# Test the builds
139+
test:
140+
needs: build
141+
strategy:
142+
fail-fast: false
143+
matrix:
144+
include:
145+
- os: ubuntu-latest
146+
artifact: bindings-linux-x64-gnu
147+
- os: macos-latest # Apple Silicon runner
148+
artifact: bindings-darwin-arm64
149+
- os: windows-latest
150+
artifact: bindings-win32-x64-msvc
151+
152+
runs-on: ${{ matrix.os }}
153+
name: Test - ${{ matrix.os }}
154+
155+
steps:
156+
# actions/checkout@v4
157+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
158+
159+
- name: Setup Node.js
160+
# actions/setup-node@v4
161+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
162+
with:
163+
node-version: '20'
164+
165+
- name: Download artifact
166+
# actions/download-artifact@v4
167+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
168+
with:
169+
name: ${{ matrix.artifact }}
170+
path: bindings/lni_nodejs
171+
172+
- name: Install dependencies
173+
working-directory: bindings/lni_nodejs
174+
run: yarn install --ignore-scripts
175+
176+
- name: Test import
177+
working-directory: bindings/lni_nodejs
178+
run: |
179+
node -e "const lni = require('./'); console.log('✅ LNI loaded:', Object.keys(lni).slice(0,5).join(', '))"
180+
181+
# Create release on tag
182+
release:
183+
if: startsWith(github.ref, 'refs/tags/v')
184+
needs: [build, universal-macos, test]
185+
runs-on: ubuntu-latest
186+
name: Create Release
187+
permissions:
188+
contents: write
189+
190+
steps:
191+
# actions/checkout@v4
192+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
193+
194+
- name: Download all artifacts
195+
# actions/download-artifact@v4
196+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
197+
with:
198+
path: artifacts
199+
200+
- name: Prepare release assets
201+
run: |
202+
mkdir -p release
203+
for dir in artifacts/bindings-*; do
204+
name=$(basename "$dir")
205+
platform="${name#bindings-}"
206+
for file in "$dir"/*.node; do
207+
if [ -f "$file" ]; then
208+
cp "$file" "release/lni_js.${platform}.node"
209+
fi
210+
done
211+
done
212+
ls -la release/
213+
214+
- name: Create Release
215+
# softprops/action-gh-release@v2
216+
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
217+
with:
218+
files: release/*
219+
generate_release_notes: true
220+
draft: false
221+
prerelease: ${{ contains(github.ref, '-') }}
222+
env:
223+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
224+
225+
# Optional: Publish to npm
226+
publish:
227+
if: startsWith(github.ref, 'refs/tags/v')
228+
needs: [release]
229+
runs-on: ubuntu-latest
230+
name: Publish to npm
231+
232+
steps:
233+
# actions/checkout@v4
234+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
235+
236+
- name: Setup Node.js
237+
# actions/setup-node@v4
238+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
239+
with:
240+
node-version: '20'
241+
registry-url: 'https://registry.npmjs.org'
242+
243+
- name: Download all artifacts
244+
# actions/download-artifact@v4
245+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
246+
with:
247+
path: artifacts
248+
249+
- name: Prepare npm package
250+
working-directory: bindings/lni_nodejs
251+
run: |
252+
# Copy platform-specific binaries
253+
for dir in ../../artifacts/bindings-*; do
254+
cp "$dir"/*.node . 2>/dev/null || true
255+
done
256+
ls -la *.node
257+
258+
- name: Publish to npm
259+
working-directory: bindings/lni_nodejs
260+
env:
261+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
262+
run: |
263+
# Only publish if NODE_AUTH_TOKEN is set (via secrets.NPM_TOKEN)
264+
if [ -n "${NODE_AUTH_TOKEN}" ]; then
265+
npm publish --access public
266+
else
267+
echo "NPM_TOKEN not configured, skipping publish"
268+
fi
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
[target.aarch64-unknown-linux-musl]
22
linker = "aarch64-linux-musl-gcc"
33
rustflags = ["-C", "target-feature=-crt-static"]
4+
45
[target.x86_64-pc-windows-msvc]
5-
rustflags = ["-C", "target-feature=+crt-static"]
6+
rustflags = ["-C", "target-feature=+crt-static"]
7+
8+
[target.aarch64-apple-darwin]
9+
rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
10+
11+
[target.x86_64-apple-darwin]
12+
rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]

0 commit comments

Comments
 (0)