From 271458c2e620664f09568d257d1f25fe9ee88e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 14 Sep 2025 21:25:00 +0200 Subject: [PATCH 1/9] Ensure REACT_NATIVE_OVERRIDE_HERMES_DIR is set --- packages/host/android/build.gradle | 18 ++++++++++- packages/host/src/node/gradle.test.ts | 45 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 packages/host/src/node/gradle.test.ts diff --git a/packages/host/android/build.gradle b/packages/host/android/build.gradle index ee47d91a..15ed0377 100644 --- a/packages/host/android/build.gradle +++ b/packages/host/android/build.gradle @@ -134,6 +134,22 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } +task checkHermesOverride { + doFirst { + if (!System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR")) { + throw new GradleException([ + "React Native Node-API needs a custom version of Hermes with Node-API enabled.", + "Run the following in your terminal, to clone Hermes and instruct React Native to use it:", + "", + "export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api vendor-hermes --silent --force`", + "", + "And follow this guide to build React Native from source:", + "https://reactnative.dev/contributing/how-to-build-from-source#update-your-project-to-build-from-source" + ].join('\n')) + } + } +} + // Custom task to fetch jniLibs paths via CLI task linkNodeApiModules { doLast { @@ -150,5 +166,5 @@ task linkNodeApiModules { } } -preBuild.dependsOn linkNodeApiModules +preBuild.dependsOn checkHermesOverride, linkNodeApiModules diff --git a/packages/host/src/node/gradle.test.ts b/packages/host/src/node/gradle.test.ts new file mode 100644 index 00000000..cea3e2af --- /dev/null +++ b/packages/host/src/node/gradle.test.ts @@ -0,0 +1,45 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import cp from "node:child_process"; +import path from "node:path"; + +const PACKAGE_ROOT = path.join(__dirname, "../.."); +const MONOREPO_ROOT = path.join(PACKAGE_ROOT, "../.."); +const TEST_APP_ANDROID_PATH = path.join(MONOREPO_ROOT, "apps/test-app/android"); + +describe("Gradle tasks", () => { + describe("checkHermesOverride task", () => { + it("should fail if REACT_NATIVE_OVERRIDE_HERMES_DIR is not set", () => { + const { status, stdout, stderr } = cp.spawnSync( + "sh", + ["gradlew", "react-native-node-api:checkHermesOverride"], + { + cwd: TEST_APP_ANDROID_PATH, + env: { + ...process.env, + REACT_NATIVE_OVERRIDE_HERMES_DIR: undefined, + }, + encoding: "utf-8", + }, + ); + + assert.notEqual(status, 0, `Expected failure: ${stdout} ${stderr}`); + assert.match( + stderr, + /React Native Node-API needs a custom version of Hermes with Node-API enabled/, + ); + assert.match( + stderr, + /Run the following in your terminal, to clone Hermes and instruct React Native to use it/, + ); + assert.match( + stderr, + /export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api vendor-hermes --silent --force`/, + ); + assert.match( + stderr, + /And follow this guide to build React Native from source/, + ); + }); + }); +}); From 44471b029024bbc625c9ffb7adaaa1a2901fadf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 14 Sep 2025 21:25:16 +0200 Subject: [PATCH 2/9] Add test for linkNodeApiModules task --- packages/host/src/node/gradle.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/host/src/node/gradle.test.ts b/packages/host/src/node/gradle.test.ts index cea3e2af..04da14f1 100644 --- a/packages/host/src/node/gradle.test.ts +++ b/packages/host/src/node/gradle.test.ts @@ -42,4 +42,20 @@ describe("Gradle tasks", () => { ); }); }); + + describe("linkNodeApiModules task", () => { + it("should call the CLI to autolink", () => { + const { status, stdout, stderr } = cp.spawnSync( + "sh", + ["gradlew", "react-native-node-api:linkNodeApiModules"], + { + cwd: TEST_APP_ANDROID_PATH, + encoding: "utf-8", + }, + ); + + assert.equal(status, 0, `Expected failure: ${stdout} ${stderr}`); + assert.match(stdout, /Auto-linking Node-API modules/); + }); + }); }); From 02b69359d171e27a5f6cff4be22996da7204616d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 14 Sep 2025 22:20:51 +0200 Subject: [PATCH 3/9] Use relative path to CLI instead of "npx" --- packages/host/android/build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/host/android/build.gradle b/packages/host/android/build.gradle index 15ed0377..b5ee06fe 100644 --- a/packages/host/android/build.gradle +++ b/packages/host/android/build.gradle @@ -150,12 +150,13 @@ task checkHermesOverride { } } +def cliPath = file("../bin/react-native-node-api.mjs") + // Custom task to fetch jniLibs paths via CLI task linkNodeApiModules { doLast { exec { - // TODO: Support --strip-path-suffix - commandLine 'npx', 'react-native-node-api', 'link', '--android', rootProject.rootDir.absolutePath + commandLine cliPath, 'link', '--android', rootProject.rootDir.absolutePath standardOutput = System.out errorOutput = System.err // Enable color output From d43744674da6a4bda9a965a0974ca7770a7eb025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 15 Sep 2025 11:31:50 +0200 Subject: [PATCH 4/9] Add test calling directly into bin --- packages/host/src/node/cli/bin.test.ts | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/host/src/node/cli/bin.test.ts diff --git a/packages/host/src/node/cli/bin.test.ts b/packages/host/src/node/cli/bin.test.ts new file mode 100644 index 00000000..e68006a6 --- /dev/null +++ b/packages/host/src/node/cli/bin.test.ts @@ -0,0 +1,49 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import cp from "node:child_process"; +import path from "node:path"; + +const PACKAGE_ROOT = path.join(__dirname, "../../.."); +const BIN_PATH = path.join(PACKAGE_ROOT, "bin/react-native-node-api.mjs"); + +describe("bin", () => { + describe("help command", () => { + it("should succeed with a mention of usage", () => { + const { status, stdout, stderr } = cp.spawnSync( + process.execPath, + [BIN_PATH, "help"], + { + cwd: PACKAGE_ROOT, + encoding: "utf8", + }, + ); + + assert.equal( + status, + 0, + `Expected success (got ${status}): ${stdout} ${stderr}`, + ); + assert.match(stdout, /Usage: react-native-node-api/); + }); + }); + + describe("link command", () => { + it("should succeed with a mention of Node-API modules", () => { + const { status, stdout, stderr } = cp.spawnSync( + process.execPath, + [BIN_PATH, "link", "--android", "--apple"], + { + cwd: PACKAGE_ROOT, + encoding: "utf8", + }, + ); + + assert.equal( + status, + 0, + `Expected success (got ${status}): ${stdout} ${stderr}`, + ); + assert.match(stdout, /Auto-linking Node-API modules/); + }); + }); +}); From f6be1a2c78c63525bac55854adeb228e918014b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 15 Sep 2025 13:07:48 +0200 Subject: [PATCH 5/9] Include stderr in assert message --- packages/host/src/node/cli/bin.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/host/src/node/cli/bin.test.ts b/packages/host/src/node/cli/bin.test.ts index e68006a6..f38dead1 100644 --- a/packages/host/src/node/cli/bin.test.ts +++ b/packages/host/src/node/cli/bin.test.ts @@ -23,7 +23,11 @@ describe("bin", () => { 0, `Expected success (got ${status}): ${stdout} ${stderr}`, ); - assert.match(stdout, /Usage: react-native-node-api/); + assert.match( + stdout, + /Usage: react-native-node-api/, + `Failed to find expected output (stdout: ${stdout} stderr: ${stderr})`, + ); }); }); @@ -43,7 +47,11 @@ describe("bin", () => { 0, `Expected success (got ${status}): ${stdout} ${stderr}`, ); - assert.match(stdout, /Auto-linking Node-API modules/); + assert.match( + stdout + stderr, + /Auto-linking Node-API modules/, + `Failed to find expected output (stdout: ${stdout} stderr: ${stderr})`, + ); }); }); }); From c6a7d6816821af130133a73d895c5f4447509888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 15 Sep 2025 13:51:11 +0200 Subject: [PATCH 6/9] Add optional commandLinePrefix on windows to run CLI in a shell --- packages/host/android/build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/host/android/build.gradle b/packages/host/android/build.gradle index b5ee06fe..0eedddfb 100644 --- a/packages/host/android/build.gradle +++ b/packages/host/android/build.gradle @@ -1,5 +1,6 @@ import java.nio.file.Paths import groovy.json.JsonSlurper +import org.gradle.internal.os.OperatingSystem buildscript { ext.getExtOrDefault = {name -> @@ -150,13 +151,14 @@ task checkHermesOverride { } } +def commandLinePrefix = OperatingSystem.current().isWindows() ? ["cmd", "/c", "node"] : [] def cliPath = file("../bin/react-native-node-api.mjs") // Custom task to fetch jniLibs paths via CLI task linkNodeApiModules { doLast { exec { - commandLine cliPath, 'link', '--android', rootProject.rootDir.absolutePath + commandLine commandLinePrefix + [cliPath, 'link', '--android', rootProject.rootDir.absolutePath] standardOutput = System.out errorOutput = System.err // Enable color output From cf40320a37cda31117e3010a1ffe707e101672bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 15 Sep 2025 14:55:45 +0200 Subject: [PATCH 7/9] Skip Gradle tests by default and enable it on CI --- .github/workflows/check.yml | 2 + packages/host/package.json | 1 + packages/host/src/node/gradle.test.ts | 97 ++++++++++++++------------- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 52aa06c7..53c7f734 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -3,6 +3,8 @@ name: Check env: # Version here should match the one in React Native template and packages/cmake-rn/src/cli.ts NDK_VERSION: 27.1.12297006 + # Enabling the Gradle test on CI (disabled by default because it downloads a lot) + ENABLE_GRADLE_TESTS: true on: push: diff --git a/packages/host/package.json b/packages/host/package.json index f85eb021..45a096b6 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -47,6 +47,7 @@ "build-weak-node-api": "cmake-rn --no-auto-link --no-weak-node-api-linkage --xcframework-extension --source ./weak-node-api --out ./weak-node-api", "build-weak-node-api:all-triplets": "cmake-rn --android --apple --no-auto-link --no-weak-node-api-linkage --xcframework-extension --source ./weak-node-api --out ./weak-node-api", "test": "tsx --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout src/node/**/*.test.ts src/node/*.test.ts", + "test:gradle": "ENABLE_GRADLE_TESTS=true node --run test", "bootstrap": "node --run copy-node-api-headers && node --run generate-weak-node-api-injector && node --run generate-weak-node-api && node --run build-weak-node-api", "prerelease": "node --run copy-node-api-headers && node --run generate-weak-node-api-injector && node --run generate-weak-node-api && node --run build-weak-node-api:all-triplets" }, diff --git a/packages/host/src/node/gradle.test.ts b/packages/host/src/node/gradle.test.ts index 04da14f1..2c1d8ef6 100644 --- a/packages/host/src/node/gradle.test.ts +++ b/packages/host/src/node/gradle.test.ts @@ -7,55 +7,60 @@ const PACKAGE_ROOT = path.join(__dirname, "../.."); const MONOREPO_ROOT = path.join(PACKAGE_ROOT, "../.."); const TEST_APP_ANDROID_PATH = path.join(MONOREPO_ROOT, "apps/test-app/android"); -describe("Gradle tasks", () => { - describe("checkHermesOverride task", () => { - it("should fail if REACT_NATIVE_OVERRIDE_HERMES_DIR is not set", () => { - const { status, stdout, stderr } = cp.spawnSync( - "sh", - ["gradlew", "react-native-node-api:checkHermesOverride"], - { - cwd: TEST_APP_ANDROID_PATH, - env: { - ...process.env, - REACT_NATIVE_OVERRIDE_HERMES_DIR: undefined, +describe( + "Gradle tasks", + // Skipping these tests by default, as they download a lot and takes a long time + { skip: process.env.ENABLE_GRADLE_TESTS !== "true" }, + () => { + describe("checkHermesOverride task", () => { + it("should fail if REACT_NATIVE_OVERRIDE_HERMES_DIR is not set", () => { + const { status, stdout, stderr } = cp.spawnSync( + "sh", + ["gradlew", "react-native-node-api:checkHermesOverride"], + { + cwd: TEST_APP_ANDROID_PATH, + env: { + ...process.env, + REACT_NATIVE_OVERRIDE_HERMES_DIR: undefined, + }, + encoding: "utf-8", }, - encoding: "utf-8", - }, - ); + ); - assert.notEqual(status, 0, `Expected failure: ${stdout} ${stderr}`); - assert.match( - stderr, - /React Native Node-API needs a custom version of Hermes with Node-API enabled/, - ); - assert.match( - stderr, - /Run the following in your terminal, to clone Hermes and instruct React Native to use it/, - ); - assert.match( - stderr, - /export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api vendor-hermes --silent --force`/, - ); - assert.match( - stderr, - /And follow this guide to build React Native from source/, - ); + assert.notEqual(status, 0, `Expected failure: ${stdout} ${stderr}`); + assert.match( + stderr, + /React Native Node-API needs a custom version of Hermes with Node-API enabled/, + ); + assert.match( + stderr, + /Run the following in your terminal, to clone Hermes and instruct React Native to use it/, + ); + assert.match( + stderr, + /export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api vendor-hermes --silent --force`/, + ); + assert.match( + stderr, + /And follow this guide to build React Native from source/, + ); + }); }); - }); - describe("linkNodeApiModules task", () => { - it("should call the CLI to autolink", () => { - const { status, stdout, stderr } = cp.spawnSync( - "sh", - ["gradlew", "react-native-node-api:linkNodeApiModules"], - { - cwd: TEST_APP_ANDROID_PATH, - encoding: "utf-8", - }, - ); + describe("linkNodeApiModules task", () => { + it("should call the CLI to autolink", () => { + const { status, stdout, stderr } = cp.spawnSync( + "sh", + ["gradlew", "react-native-node-api:linkNodeApiModules"], + { + cwd: TEST_APP_ANDROID_PATH, + encoding: "utf-8", + }, + ); - assert.equal(status, 0, `Expected failure: ${stdout} ${stderr}`); - assert.match(stdout, /Auto-linking Node-API modules/); + assert.equal(status, 0, `Expected failure: ${stdout} ${stderr}`); + assert.match(stdout, /Auto-linking Node-API modules/); + }); }); - }); -}); + }, +); From ecbb5461082c2993b2ba997d9bf2af0ee03a9d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 15 Sep 2025 14:59:37 +0200 Subject: [PATCH 8/9] Add changesets --- .changeset/many-candies-retire.md | 5 +++++ .changeset/silver-suits-double.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/many-candies-retire.md create mode 100644 .changeset/silver-suits-double.md diff --git a/.changeset/many-candies-retire.md b/.changeset/many-candies-retire.md new file mode 100644 index 00000000..ec0192e5 --- /dev/null +++ b/.changeset/many-candies-retire.md @@ -0,0 +1,5 @@ +--- +"react-native-node-api": patch +--- + +Fix auto-linking from Gradle builds on Windows diff --git a/.changeset/silver-suits-double.md b/.changeset/silver-suits-double.md new file mode 100644 index 00000000..cbfd9bbb --- /dev/null +++ b/.changeset/silver-suits-double.md @@ -0,0 +1,5 @@ +--- +"react-native-node-api": minor +--- + +Assert that REACT_NATIVE_OVERRIDE_HERMES_DIR is set when Android / Gradle projects depend on the host package From 4d2ceadfc83511589d927d01cafb3c45f7140888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 15 Sep 2025 15:22:43 +0200 Subject: [PATCH 9/9] Update packages/host/src/node/gradle.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/host/src/node/gradle.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/host/src/node/gradle.test.ts b/packages/host/src/node/gradle.test.ts index 2c1d8ef6..b1da4a10 100644 --- a/packages/host/src/node/gradle.test.ts +++ b/packages/host/src/node/gradle.test.ts @@ -58,7 +58,7 @@ describe( }, ); - assert.equal(status, 0, `Expected failure: ${stdout} ${stderr}`); + assert.equal(status, 0, `Expected success: ${stdout} ${stderr}`); assert.match(stdout, /Auto-linking Node-API modules/); }); });