Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7887733
Cut pre-releases from a "next" branch
kraenhansen Aug 10, 2026
fb7a3c0
Run Check on the next branch too
kraenhansen Aug 10, 2026
c3c321e
Adopt Hermes' first-party Node-API (static_h) (#372)
kraenhansen Aug 11, 2026
cf5ed4e
Implement hermes_napi_host for async work and thread-safe functions (…
kraenhansen Aug 12, 2026
2b3cb61
ci: bring host-cpp-tests in line with the rest of the workflow (#408)
kraenhansen Aug 12, 2026
3cc30a9
docs: clarify the Android Hermes vendoring steps (#411)
kraenhansen Aug 12, 2026
8cc8e59
fix(host): make vendor-hermes --silent actually silent (#410)
kraenhansen Aug 12, 2026
7531199
ci: verify the ferric Apple binaries depend on weak-node-api (#409)
kraenhansen Aug 13, 2026
43c2b8f
docs: fill in HOW-IT-WORKS.md and CLI.md placeholders (#429)
kraenhansen Aug 13, 2026
c73d30c
ci: run linting without building native code (#435)
kraenhansen Aug 13, 2026
0c1d597
fix(cmake-rn): let ANDROID_STL be overridden via --define (#433)
kraenhansen Aug 13, 2026
715a24e
Route napi_fatal_exception through ErrorUtils.reportFatalError (#432)
kraenhansen Aug 13, 2026
c22f39c
Drop the host's shadowing implementations of runtime Node-API functio…
kraenhansen Aug 13, 2026
d9ab417
cmake-rn: make CODE_SIGNING_ALLOWED configurable for Apple builds (#430)
kraenhansen Aug 13, 2026
0b3df68
fix(host): compile out log_debug in release (NDEBUG) builds (#431)
kraenhansen Aug 13, 2026
48fa7fc
Upgrade bufout to v1.0.0 and remove EventEmitter listener limits (#438)
kraenhansen Aug 13, 2026
1ab6a11
Multi addon projects (#413)
kraenhansen Aug 13, 2026
166b3bf
Add a prebuilt-hermes command and a workflow that publishes its archi…
kraenhansen Aug 13, 2026
263a3bc
Configure the host Hermes compiler for one architecture (#442)
kraenhansen Aug 13, 2026
8f91084
Stop the host Hermes compiler build from targeting visionOS (#443)
kraenhansen Aug 13, 2026
2ba5227
Skip the Hermes build when the archive is already published (#444)
kraenhansen Aug 13, 2026
56ae5f8
Use the prebuilt Hermes archive for iOS builds (#441)
kraenhansen Aug 13, 2026
f41deb0
Load addons through Hermes' `hermes_napi_load_module` (#445)
kraenhansen Aug 13, 2026
0a29fbd
Add a fixture registering via the deprecated `napi_module_register` (…
kraenhansen Aug 13, 2026
28eb6f6
feat(gyp-to-cmake): support cflags
huytdps13400 Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/bufout-one-point-oh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@react-native-node-api/cli-utils": patch
"react-native-node-api": patch
"cmake-rn": patch
"ferric-cli": patch
---

Upgrade `bufout` to v1.0.0, which keeps the number of listeners on the process
and the output streams constant regardless of how many children are spawned
concurrently: a single shared `exit`/`SIGINT` listener is attached only while
children are running, and every child pipes into one shared pass-through per
destination stream.

That removes the reason for the CLIs to raise `EventEmitter.defaultMaxListeners`
to 100, so those assignments are gone and Node's default limit again applies —
restoring the leak warning it exists to give.
23 changes: 23 additions & 0 deletions .changeset/chilly-trains-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"cmake-rn": minor
---

Add support for building projects declaring multiple shared object libraries into Node-API addons.

Each addon is emitted next to the sources it was built from, so that a project
declaring many addons produces the same layout as building each of them on its
own. Both the location and the name of an artifact are derived from the target
that produced it:

- `--out` supports a new `{targetSourceDir}` placeholder, expanding to the source
directory of the target being emitted, and now defaults to
`{targetSourceDir}/build/{configuration}`. This resolves to the same path as
before, unless `--build` is pointed outside of the source directory.
- The artifact is named after the target's `OUTPUT_NAME` rather than the CMake
target name. These are the same unless `OUTPUT_NAME` is set explicitly, which is
how a project can give its targets the unique names CMake requires without
affecting the name of the addon.

Also adds `--concurrency`, limiting how many build tasks run at once. It defaults
to the available parallelism, or to 1 when `--verbose` is enabled, since
interleaved output from concurrent builds is hard to read.
26 changes: 26 additions & 0 deletions .changeset/drop-shadowing-napi-shims.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"react-native-node-api": patch
---

Drop the host's shadowing implementations of Node-API functions that Hermes'
first-party Node-API already provides, so addons observe Hermes' behavior
instead of the host's older shims:

- `napi_get_node_version` now reports Hermes' own version (release name
`"hermes"`) instead of unconditionally failing with `napi_generic_failure`.
- `napi_is_buffer` now returns `true` only for `Uint8Array`, matching Node,
instead of any `ArrayBuffer`/`TypedArray`.
- `napi_get_buffer_info` now returns `napi_invalid_arg` for non-`Uint8Array`
values, matching Node, instead of `napi_ok` with zeroed output.
- `napi_create_buffer_copy` now writes a non-`NULL` `result_data` argument, as
documented, instead of silently ignoring it.
- `napi_create_buffer`, `napi_create_external_buffer` and `napi_get_version`
are unchanged in observable behavior, now served by Hermes directly.

This also fixes a bug where calling `napi_get_buffer_info` on a non-`Uint8Array`
typed array (e.g. a `Float64Array`) left a process-global flag corrupted, so
that every subsequent `napi_create_buffer`/`napi_create_external_buffer` call
produced the wrong typed array view.

`napi_fatal_error` keeps its host-side implementation, so fatal Node-API
errors keep reaching logcat on Android instead of only stderr.
14 changes: 14 additions & 0 deletions .changeset/eighty-moons-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"react-native-node-api": major
"weak-node-api": minor
---

Adopt Hermes' first-party Node-API (the `hermesNapi` target on the `static_h`
branch) instead of patching Hermes with our own implementation. Addons now run
against a real Node-API environment created with `hermes_napi_create_env()`, one
per addon as in Node, and Node-API is bumped from v8 to v10.

This drops support for React Native 0.79–0.81: the vendored Hermes is built from
a pinned `static_h` commit and requires the Hermes build scripts shipped with
React Native 0.87 and later. Older React Native versions are still served by
previously published releases.
15 changes: 15 additions & 0 deletions .changeset/gentle-pandas-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"cmake-rn": minor
---

Let a consumer override the Android `ANDROID_STL` CMake cache variable via
the existing `-D`/`--define` option (e.g. `--define ANDROID_STL=c++_static`).
It still defaults to `c++_shared`, matching what React Native itself uses,
but an addon that must match a prebuilt third-party dependency's STL, or one
that's genuinely self-contained, can now ask for a different value.

This also fixes an ordering bug where a `--define` targeting any of the
Android platform's own default CMake variables (including `ANDROID_STL`) was
silently discarded: our hardcoded defaults were appended to the CMake
command line _after_ the user-provided `-D` arguments, and CMake resolves a
cache variable set multiple times via `-D` to its last occurrence.
7 changes: 7 additions & 0 deletions .changeset/gyp-cflags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"gyp-to-cmake": minor
---

Translate target-level `cflags` from `binding.gyp` into private CMake compile
options. The parser now validates that `cflags` is an array of strings, and the
generated options preserve command expansion and escaped spaces.
21 changes: 21 additions & 0 deletions .changeset/hermes-loads-addons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
"react-native-node-api": patch
---

Load addons through Hermes' `hermes_napi_load_module` instead of the host's own
`dlopen` + `dlsym` implementation:

- Addons that register themselves by calling the deprecated
`napi_module_register` are now supported. Previously only addons exporting a
`napi_register_module_v1` symbol could be loaded, and the rest resolved to
`undefined`.
- A failing `requireNodeAddon` now throws an error naming the addon, the path
that was tried and the underlying reason (e.g. the `dlopen` error), instead
of silently resolving to `undefined`.
- `node_api_get_module_file_name` now reports the path the addon was loaded
from, instead of an empty string.

This also opens a Node-API handle scope around loading and initializing an
addon. Without one, the `exports` object handed to the addon's initialization
function was not reachable by the garbage collector, so a collection triggered
during initialization could free it while the addon was still populating it.
5 changes: 5 additions & 0 deletions .changeset/hermes-napi-host-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-node-api": minor
---

Provide a `hermes_napi_host` implementation to the Hermes Node-API environments. This enables thread-safe functions (`napi_create_threadsafe_function` and friends) and moves `napi_async_work` execution onto a worker pool — previously the `execute` callback ran on the JavaScript thread, blocking it for the duration of the work. The host is also in place before an addon's module init runs, so async work and thread-safe functions can now be created during initialization.
18 changes: 18 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"mode": "pre",
"tag": "rc",
"initialVersions": {
"@react-native-node-api/test-app": "0.2.2",
"@react-native-node-api/cli-utils": "0.1.4",
"cmake-file-api": "0.1.2",
"cmake-rn": "0.6.3",
"ferric-cli": "0.3.11",
"@react-native-node-api/ferric-example": "0.1.2",
"gyp-to-cmake": "0.5.3",
"react-native-node-api": "1.0.1",
"@react-native-node-api/node-addon-examples": "0.1.1",
"@react-native-node-api/node-tests": "0.1.1",
"weak-node-api": "0.1.1"
},
"changesets": []
}
15 changes: 15 additions & 0 deletions .changeset/prebuilt-hermes-archive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"react-native-node-api": minor
---

Stop compiling Hermes as part of every iOS app build. The Cocoapods integration
now resolves the pinned commit with `prebuilt-hermes` and hands the archive's
path to React Native through `HERMES_ENGINE_TARBALL_PATH` — so
`hermes-engine.podspec` vendors the prebuilt frameworks instead of running its
"Build Hermesc" and "Build Hermes" script phases.

Building Hermes from source remains available and is the faster loop while
iterating on Hermes itself, since Xcode then rebuilds it incrementally: set
`REACT_NATIVE_NODE_API_HERMES_FROM_SOURCE=1` before `pod install`. Setting
`REACT_NATIVE_OVERRIDE_HERMES_DIR` or `HERMES_ENGINE_TARBALL_PATH` yourself
still takes precedence, and Android is unchanged.
18 changes: 18 additions & 0 deletions .changeset/prebuilt-hermes-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"react-native-node-api": minor
---

Add a `prebuilt-hermes` command, which resolves an archive of the pinned Hermes
commit prebuilt for Apple platforms and prints its path. The archive holds the
`destroot` layout React Native's `hermes-engine.podspec` expects from a tarball
pointed at by `HERMES_ENGINE_TARBALL_PATH`, so an app that sets that variable
vendors the prebuilt frameworks rather than compiling Hermes as part of its own
build.

It is resolved from a local cache, then from a release asset published for the
pinned commit, and only built locally if neither has it. Its name covers
everything that changes its contents — the pinned commit, the React Native
version whose `ReactCommon/jsi` it is compiled against, the build type and the
platforms — so a stale archive can never be mistaken for a matching one.

Nothing consumes this yet: `pod install` still builds Hermes from source.
15 changes: 15 additions & 0 deletions .changeset/prebuilt-hermes-host-compiler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"react-native-node-api": patch
---

Fix `prebuilt-hermes` failing to configure the host Hermes compiler. It passed
`CMAKE_OSX_ARCHITECTURES=arm64;x86_64` to build a universal `hermesc`, but a
multi-arch host configure makes llvh's feature try-compiles fail — standard
headers report as missing and the configure dies with "Host compiler appears to
require libatomic, but cannot find it". The host compiler is now configured the
way Hermes and React Native configure it, for the host architecture only.

`hermesc` is consequently native to the Mac that built the archive, so the
archive name now carries the host architecture. An Intel Mac finds no published
archive for its architecture and builds its own, rather than downloading one
whose `hermesc` it cannot execute.
13 changes: 13 additions & 0 deletions .changeset/prebuilt-hermes-visionos-env.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"react-native-node-api": patch
---

Fix `prebuilt-hermes` failing to configure the host Hermes compiler with "Host
compiler appears to require libatomic, but cannot find it". It exported all
three deployment targets Hermes' `build-apple-framework.sh` can ask for to every
command it ran, including the host compiler build. `XROS_DEPLOYMENT_TARGET` is
also a clang driver variable, so clang targeted visionOS against the macOS
sysroot, and every API marked unavailable there — `pthread_mutexattr_init`, the
`fd_set` helpers reached through `unistd.h` — failed to compile. Each platform
build now gets only the deployment target it needs, and the host compiler build
gets none.
10 changes: 10 additions & 0 deletions .changeset/real-emus-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"gyp-to-cmake": minor
---

Add --namespaced-targets to allow a root project to add many sub-projects.

CMake requires target names to be unique across a project tree, so sub-projects
that each declare an `addon` target cannot be added to a single root project. This
prefixes the target name with the project name, while setting `OUTPUT_NAME` so the
artifact keeps the name a `require` resolves against.
10 changes: 10 additions & 0 deletions .changeset/release-debug-logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"react-native-node-api": patch
---

Stop emitting `log_debug`'s per-addon diagnostic chatter (library
found/loaded, symbol resolution, ...) in release builds. It is now compiled
out in `NDEBUG` builds (CMake's `Release`/`MinSizeRel`/`RelWithDebInfo`
configurations, and Xcode's default `Release` configuration), mirroring React
Native's own dev/release logging split. `log_warning` and `log_error` are
unaffected and keep firing in every build type.
5 changes: 5 additions & 0 deletions .changeset/route-fatal-exception-through-errorutils.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-node-api": minor
---

Route `napi_fatal_exception` through React Native's `ErrorUtils.reportFatalError` instead of unconditionally logging and calling `abort()`. This is what node-addon-api calls whenever an exception escapes a thread-safe-function callback, so a single throwing tsfn callback no longer hard-kills the app: in dev the error and its stack now surface in LogBox, in release RN's default handler rethrows into the native crash path, and apps can observe or handle it via `ErrorUtils.setGlobalHandler` — the moral equivalent of Node's `'uncaughtException'`. The previous stringify-and-abort behavior remains as a fallback for when `ErrorUtils`/`reportFatalError` isn't available (non-RN embedders, very early startup) or the handler itself throws.
9 changes: 9 additions & 0 deletions .changeset/silent-vendor-hermes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"react-native-node-api": patch
---

Make `vendor-hermes --silent` actually silent. The spinners were passed
`isEnabled: false`, which stops the animation but still writes the spinner text
and its final symbol to stderr. They now use `isSilent`, which suppresses the
output entirely, leaving the vendored Hermes path on stdout as the command's
only output.
5 changes: 5 additions & 0 deletions .changeset/silly-cobras-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"cmake-rn": minor
---

Add a `--code-signing-allowed` flag to `cmake-rn`. `CODE_SIGNING_ALLOWED=NO` remains the default (needed for the free-standing dynamic libraries we produce), but a consumer who needs signed binaries in the XCFramework can now pass `--code-signing-allowed` to opt in.
5 changes: 5 additions & 0 deletions .changeset/wet-carrots-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ferric-cli": patch
---

Add `--dts-only` flag to `ferric build`, generating just the TypeScript declaration file and JS entrypoint without cross-compiling any Android/Apple binaries. It still runs a real host `cargo build` (napi-rs has no lighter typegen-only mode), so it's meant for regenerating a checked-in declarations fixture rather than for environments without a Rust toolchain.
27 changes: 27 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ Guidance specific to Claude Code sessions that open pull requests against this
repository (including automated/scheduled sessions). See the root `CLAUDE.md`
and `AGENTS.md` for everything else.

## Keep inline comments very brief — put the color in the PR description

Default to **no comment at all**. Write one only when it carries knowledge a
reader cannot get from the code itself plus a `git blame` pointing at the PR
that introduced it. When you do write one, keep it to a line or two.

Rationale, rejected alternatives, benchmark numbers, "we tried X and it
didn't work", links to upstream issues, and anything that reads as a history
lesson belong in the **PR description** (and, where user-facing, the
changeset) — not in the source. Those places are where a reader who has
already found the line via `git blame` will end up anyway, and they don't
have to be maintained as the code around them changes.

Concretely, do not write comments that:

- restate what the next line already says;
- explain why an alternative implementation was _not_ chosen;
- narrate the change (`// now compiled out in release builds`) — that is a
commit message, and it goes stale the moment the code moves;
- document a well-known toolchain fact (e.g. what `NDEBUG` means) that a
reader can look up.

Comments that _do_ earn their place: a non-obvious constraint the compiler or
platform imposes, a workaround with the exact condition that makes it
removable (see the upstream-fix guidance in `AGENTS.md`), or a subtle
invariant a future edit could silently break.

## Attach CI labels when you open a PR

`.github/workflows/check.yml`'s `pull_request` trigger only fires on
Expand Down
Loading