From 5b89deb27a2c1a0074d51fb44bb90106425e86a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 27 Oct 2025 22:33:14 +0100 Subject: [PATCH 01/28] Add script to init a MacOS test app --- .gitignore | 3 + package.json | 4 +- scripts/init-macos-test-app.ts | 110 +++++++++++++++++++++++++++++++++ tsconfig.json | 1 + tsconfig.scripts.json | 10 +++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 scripts/init-macos-test-app.ts create mode 100644 tsconfig.scripts.json diff --git a/.gitignore b/.gitignore index 42f6c1a8..6c7c7bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ node_modules/ dist/ *.tsbuildinfo + +# Treading the MacOS app as ephemeral +apps/macos-test-app diff --git a/package.json b/package.json index eb2f9dff..be2dbf31 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "test": "npm test --workspace react-native-node-api --workspace cmake-rn --workspace gyp-to-cmake --workspace node-addon-examples", "bootstrap": "node --run build && npm run bootstrap --workspaces --if-present", "prerelease": "node --run build && npm run prerelease --workspaces --if-present", - "release": "changeset publish" + "release": "changeset publish", + "init-macos-test-app": "node scripts/init-macos-test-app.ts" }, "author": { "name": "Callstack", @@ -64,6 +65,7 @@ "globals": "^16.0.0", "prettier": "^3.6.2", "react-native": "0.81.4", + "read-pkg": "^9.0.1", "tsx": "^4.20.5", "typescript": "^5.8.0", "typescript-eslint": "^8.38.0" diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts new file mode 100644 index 00000000..8c97beeb --- /dev/null +++ b/scripts/init-macos-test-app.ts @@ -0,0 +1,110 @@ +import assert from "node:assert/strict"; +import cp from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const REACT_NATIVE_VERSION = "0.79.6"; +const ROOT_PATH = path.join(import.meta.dirname, ".."); +const APP_PATH = path.join(ROOT_PATH, "apps", "macos-test-app"); +const HOST_PACKAGE_PATH = path.join(ROOT_PATH, "packages", "host"); + +function exec(command: string, args: string[], options: cp.SpawnOptions = {}) { + const { status } = cp.spawnSync(command, args, { + stdio: "inherit", + ...options, + }); + assert.equal(status, 0, `Failed to execute '${command}'`); +} + +async function deletePreviousApp() { + if (fs.existsSync(APP_PATH)) { + console.log("Deleting existing app directory"); + await fs.promises.rm(APP_PATH, { recursive: true, force: true }); + } +} + +async function initializeReactNativeTemplate() { + console.log("Initializing community template"); + exec("npx", [ + "@react-native-community/cli", + "init", + "MacOSTestApp", + "--skip-install", + "--skip-git-init", + // "--platform-name", + // "react-native-macos", + "--version", + REACT_NATIVE_VERSION, + "--directory", + APP_PATH, + ]); + + // Clean up + const CLEANUP_PATHS = ["ios", "android", "__tests__"]; + + for (const cleanupPath of CLEANUP_PATHS) { + await fs.promises.rm(path.join(APP_PATH, cleanupPath), { + recursive: true, + force: true, + }); + } +} + +function installDependencies() { + console.log("Installing dependencies"); + exec( + "npm", + [ + "install", + "--save-dev", + "--prefer-offline", + "--install-links", + "react-native-macos-init", + path.relative(APP_PATH, HOST_PACKAGE_PATH), + ], + { + cwd: APP_PATH, + }, + ); +} + +function initializeReactNativeMacOSTemplate() { + console.log("Initializing react-native-macos template"); + exec("npx", ["react-native-macos-init"], { + cwd: APP_PATH, + }); +} + +async function patchPodfile() { + console.log("Patching Podfile"); + // Patch Podfile as per https://github.com/microsoft/react-native-macos/issues/2723#issuecomment-3392930688 + const podfilePath = path.join(APP_PATH, "macos", "Podfile"); + const podfileContents = await fs.promises.readFile(podfilePath, "utf8"); + const unwantedLines = [ + "require_relative '../node_modules/react-native-macos/scripts/react_native_pods'", + "require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'", + "require_relative '../node_modules/react-native-macos/scripts/cocoapods/autolinking'", + ]; + const podfilePatched = [ + "require_relative '../node_modules/react-native-macos/scripts/cocoapods/autolinking'", + ...podfileContents + .split("\n") + .filter((line) => !unwantedLines.includes(line)), + ]; + + await fs.promises.writeFile(podfilePath, podfilePatched.join("\n"), "utf8"); +} + +function installCocoapods() { + console.log("Installing cocoapods"); + exec("pod", ["install", "--project-directory=macos"], { + cwd: APP_PATH, + }); +} + +await deletePreviousApp(); +await initializeReactNativeTemplate(); +installDependencies(); +initializeReactNativeMacOSTemplate(); +await patchPodfile(); +installCocoapods(); diff --git a/tsconfig.json b/tsconfig.json index a733c3ff..4ca25b74 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ }, "files": ["prettier.config.js", "eslint.config.js"], "references": [ + { "path": "./tsconfig.scripts.json" }, { "path": "./packages/cli-utils/tsconfig.json" }, { "path": "./packages/cmake-file-api/tsconfig.json" }, { "path": "./packages/cmake-file-api/tsconfig.tests.json" }, diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 00000000..88041106 --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,10 @@ +{ + "extends": "./configs/tsconfig.node.json", + "compilerOptions": { + "composite": true, + "emitDeclarationOnly": true, + "declarationMap": false, + "rootDir": "scripts" + }, + "include": ["scripts"] +} From b4c7469c7e3bc87f7af3c4d66bcb58b58717cd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 27 Oct 2025 22:57:39 +0100 Subject: [PATCH 02/28] Update Podspec to detect React Native package name --- packages/host/scripts/patch-hermes.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/host/scripts/patch-hermes.rb b/packages/host/scripts/patch-hermes.rb index a6cb11f7..76252154 100644 --- a/packages/host/scripts/patch-hermes.rb +++ b/packages/host/scripts/patch-hermes.rb @@ -4,8 +4,18 @@ raise "React Native Node-API cannot reliably patch JSI when React Native Core is prebuilt." end +def get_react_native_package + if caller.any? { |frame| frame.include?("node_modules/react-native-macos/") } + return "react-native-macos" + elsif caller.any? { |frame| frame.include?("node_modules/react-native/") } + return "react-native" + else + raise "Unable to determine React Native package from call stack." + end +end + if ENV['REACT_NATIVE_OVERRIDE_HERMES_DIR'].nil? - VENDORED_HERMES_DIR ||= `npx react-native-node-api vendor-hermes --silent '#{Pod::Config.instance.installation_root}'`.strip + VENDORED_HERMES_DIR ||= `npx react-native-node-api vendor-hermes --react-native-package '#{get_react_native_package()}' --silent '#{Pod::Config.instance.installation_root}'`.strip # Signal the patched Hermes to React Native ENV['BUILD_FROM_SOURCE'] = 'true' ENV['REACT_NATIVE_OVERRIDE_HERMES_DIR'] = VENDORED_HERMES_DIR From e67510a39c481098a0befc3874e4ea26199c2709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 27 Oct 2025 23:06:16 +0100 Subject: [PATCH 03/28] Add changeset --- .changeset/silly-mice-warn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silly-mice-warn.md diff --git a/.changeset/silly-mice-warn.md b/.changeset/silly-mice-warn.md new file mode 100644 index 00000000..7ebdc578 --- /dev/null +++ b/.changeset/silly-mice-warn.md @@ -0,0 +1,5 @@ +--- +"react-native-node-api": patch +--- + +Detects "pod install" from React Native MacOS apps and vendors Hermes accordingly From 4f1711e901bec0954fcd71352790aef940dda2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 27 Oct 2025 23:18:46 +0100 Subject: [PATCH 04/28] Add job in the check workflow to initialize and build the MacOS test app --- .github/workflows/check.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ec9c7cee..b51968db 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -107,6 +107,38 @@ jobs: # TODO: Enable release mode when it works # run: npm run test:ios -- --mode Release working-directory: apps/test-app + test-macos: + # Disabling this on main for now, as initializing the template takes a long time and + # we don't have macOS-specific code yet + if: contains(github.event.pull_request.labels.*.name, 'MacOS 💻') + name: Test app (macOS) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/jod + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: "17" + distribution: "temurin" + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + packages: tools platform-tools ndk;${{ env.NDK_VERSION }} + - run: npm ci + - run: npm run bootstrap + env: + CMAKE_RN_TRIPLETS: arm64-apple-darwin + FERRIC_TARGETS: aarch64-apple-darwin + - run: npm run init-macos-test-app + - run: pod install --project-directory=macos + working-directory: apps/macos-test-app + - name: Build MacOS test app + run: npx react-native build-macos + working-directory: apps/macos-test-app + # TODO: Run the app eventually test-android: if: github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Android 🤖') name: Test app (Android) From 1f23c01183b3646e2b4610ff26c1d769e3b04afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 00:09:29 +0100 Subject: [PATCH 05/28] Fall-back on non-universal libraries when getting path for weak-node-api framework. --- packages/ferric/src/cargo.ts | 58 ++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/ferric/src/cargo.ts b/packages/ferric/src/cargo.ts index 9afa0770..4671a47c 100644 --- a/packages/ferric/src/cargo.ts +++ b/packages/ferric/src/cargo.ts @@ -19,22 +19,46 @@ import { isThirdTierTarget, } from "./targets.js"; -const APPLE_XCFRAMEWORK_CHILDS_PER_TARGET: Record = { - "aarch64-apple-darwin": "macos-arm64_x86_64", // Universal - "x86_64-apple-darwin": "macos-arm64_x86_64", // Universal +/** + * A per apple target mapping to a list of xcframework slices in order of priority + */ +const APPLE_XCFRAMEWORK_SLICES_PER_TARGET: Record = { + "aarch64-apple-darwin": [ + "macos-arm64_x86_64", // Universal + "macos-arm64", + ], + "x86_64-apple-darwin": [ + "macos-arm64_x86_64", // Universal + "macos-x86_64", + ], - "aarch64-apple-ios": "ios-arm64", - "aarch64-apple-ios-sim": "ios-arm64_x86_64-simulator", // Universal - "x86_64-apple-ios": "ios-arm64_x86_64-simulator", // Universal + "aarch64-apple-ios": ["ios-arm64"], + "aarch64-apple-ios-sim": [ + "ios-arm64_x86_64-simulator", // Universal + "ios-arm64-simulator", + ], + "x86_64-apple-ios": [ + "ios-arm64_x86_64-simulator", // Universal + "ios-x86_64-simulator", + ], - "aarch64-apple-visionos": "xros-arm64", - "aarch64-apple-visionos-sim": "xros-arm64_x86_64-simulator", // Universal + "aarch64-apple-visionos": ["xros-arm64"], + "aarch64-apple-visionos-sim": [ + "xros-arm64_x86_64-simulator", // Universal + "xros-arm64-simulator", + ], // The x86_64 target for vision simulator isn't supported // see https://doc.rust-lang.org/rustc/platform-support.html - "aarch64-apple-tvos": "tvos-arm64", - "aarch64-apple-tvos-sim": "tvos-arm64_x86_64-simulator", - "x86_64-apple-tvos": "tvos-arm64_x86_64-simulator", + "aarch64-apple-tvos": ["tvos-arm64"], + "aarch64-apple-tvos-sim": [ + "tvos-arm64_x86_64-simulator", // Universal + "tvos-arm64-simulator", + ], + "x86_64-apple-tvos": [ + "tvos-arm64_x86_64-simulator", // Universal + "tvos-x86_64-simulator", + ], // "aarch64-apple-ios-macabi": "", // Catalyst // "x86_64-apple-ios-macabi": "ios-x86_64-simulator", @@ -145,11 +169,19 @@ export function getTargetAndroidPlatform(target: AndroidTargetName) { } export function getWeakNodeApiFrameworkPath(target: AppleTargetName) { - return joinPathAndAssertExistence( + const xcframeworkPath = joinPathAndAssertExistence( weakNodeApiPath, "weak-node-api.xcframework", - APPLE_XCFRAMEWORK_CHILDS_PER_TARGET[target], ); + const result = APPLE_XCFRAMEWORK_SLICES_PER_TARGET[target].find((slice) => { + const candidatePath = path.join(xcframeworkPath, slice); + return fs.existsSync(candidatePath); + }); + assert( + result, + `No matching slice found in weak-node-api.xcframework for target ${target}`, + ); + return joinPathAndAssertExistence(xcframeworkPath, result); } export function getWeakNodeApiAndroidLibraryPath(target: AndroidTargetName) { From afb5f61b0d4931235816800e43ba90869284c2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 01:20:50 +0100 Subject: [PATCH 06/28] Enable Hermes and Fabric --- scripts/init-macos-test-app.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts index 8c97beeb..190528fd 100644 --- a/scripts/init-macos-test-app.ts +++ b/scripts/init-macos-test-app.ts @@ -77,22 +77,25 @@ function initializeReactNativeMacOSTemplate() { async function patchPodfile() { console.log("Patching Podfile"); - // Patch Podfile as per https://github.com/microsoft/react-native-macos/issues/2723#issuecomment-3392930688 - const podfilePath = path.join(APP_PATH, "macos", "Podfile"); - const podfileContents = await fs.promises.readFile(podfilePath, "utf8"); - const unwantedLines = [ - "require_relative '../node_modules/react-native-macos/scripts/react_native_pods'", - "require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'", - "require_relative '../node_modules/react-native-macos/scripts/cocoapods/autolinking'", - ]; - const podfilePatched = [ - "require_relative '../node_modules/react-native-macos/scripts/cocoapods/autolinking'", - ...podfileContents - .split("\n") - .filter((line) => !unwantedLines.includes(line)), + const replacements = [ + [ + // As per https://github.com/microsoft/react-native-macos/issues/2723#issuecomment-3392930688 + "require_relative '../node_modules/react-native-macos/scripts/react_native_pods'\nrequire_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'", + "require_relative '../node_modules/react-native-macos/scripts/cocoapods/autolinking'", + ], + [":hermes_enabled => false,", ":hermes_enabled => true,"], + [ + ":fabric_enabled => ENV['RCT_NEW_ARCH_ENABLED'] == '1',", + ":fabric_enabled => true,", + ], ]; - await fs.promises.writeFile(podfilePath, podfilePatched.join("\n"), "utf8"); + const podfilePath = path.join(APP_PATH, "macos", "Podfile"); + let podfileContents = await fs.promises.readFile(podfilePath, "utf8"); + for (const [searchValue, replaceValue] of replacements) { + podfileContents = podfileContents.replace(searchValue, replaceValue); + } + await fs.promises.writeFile(podfilePath, podfileContents, "utf8"); } function installCocoapods() { From 17ff79e3bc8c5d3c0a753a8d54830c4315753488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 08:52:35 +0100 Subject: [PATCH 07/28] No need to Setup Android SDK --- .github/workflows/check.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b51968db..3c6e3b00 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -123,10 +123,6 @@ jobs: with: java-version: "17" distribution: "temurin" - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - with: - packages: tools platform-tools ndk;${{ env.NDK_VERSION }} - run: npm ci - run: npm run bootstrap env: From e1b3ceaad172979628375d198bcdc5061570d548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 10:10:51 +0100 Subject: [PATCH 08/28] Debugging with Copilot --- .github/workflows/check.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3c6e3b00..d6d4c069 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -123,6 +123,25 @@ jobs: with: java-version: "17" distribution: "temurin" + - name: Debug environment before build + run: | + echo "=== Xcode and build tools ===" + xcode-select -p + which clang || echo "clang not found" + which clang++ || echo "clang++ not found" + which make || echo "make not found" + which cmake || echo "cmake not found" + echo "=== Environment variables ===" + echo "PATH=$PATH" + echo "SDKROOT=$SDKROOT" + echo "CMAKE_BINARY=$CMAKE_BINARY" + echo "=== CMake version ===" + cmake --version || echo "cmake command failed" + - name: Set required environment variables for Hermes build + run: | + # React Native's Hermes build script expects CMAKE_BINARY to be set + # Without it, the PATH preservation in env -i fails + echo "CMAKE_BINARY=$(which cmake)" >> $GITHUB_ENV - run: npm ci - run: npm run bootstrap env: From 4b9faf2e70c3f63f91a653784cd982cc581df4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 10:33:30 +0100 Subject: [PATCH 09/28] Pass --mode when building from CLI --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d6d4c069..2aa96805 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -151,7 +151,7 @@ jobs: - run: pod install --project-directory=macos working-directory: apps/macos-test-app - name: Build MacOS test app - run: npx react-native build-macos + run: npx react-native build-macos --mode Release working-directory: apps/macos-test-app # TODO: Run the app eventually test-android: From 44609d5c4332fda8291b8b4c036776963ec1f08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 10:34:26 +0100 Subject: [PATCH 10/28] Don't pod install when initializing --- scripts/init-macos-test-app.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts index 190528fd..53837c06 100644 --- a/scripts/init-macos-test-app.ts +++ b/scripts/init-macos-test-app.ts @@ -98,16 +98,8 @@ async function patchPodfile() { await fs.promises.writeFile(podfilePath, podfileContents, "utf8"); } -function installCocoapods() { - console.log("Installing cocoapods"); - exec("pod", ["install", "--project-directory=macos"], { - cwd: APP_PATH, - }); -} - await deletePreviousApp(); await initializeReactNativeTemplate(); installDependencies(); initializeReactNativeMacOSTemplate(); await patchPodfile(); -installCocoapods(); From d589923b858e1670e286ced7fae3be6112464e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 11:09:42 +0100 Subject: [PATCH 11/28] Moved patching of JSI headers to a separate function --- packages/host/src/node/cli/hermes.ts | 49 ++++++++++++++++++---------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/host/src/node/cli/hermes.ts b/packages/host/src/node/cli/hermes.ts index 6bd3daa3..4b41692c 100644 --- a/packages/host/src/node/cli/hermes.ts +++ b/packages/host/src/node/cli/hermes.ts @@ -23,6 +23,32 @@ const platformOption = new Option( "The React Native package to vendor Hermes into", ).default("react-native"); +type PatchJSIHeadersOptions = { + reactNativePath: string; + hermesJsiPath: string; + silent: boolean; +}; + +async function patchJsiHeaders({ + reactNativePath, + hermesJsiPath, + silent, +}: PatchJSIHeadersOptions) { + const reactNativeJsiPath = path.join(reactNativePath, "ReactCommon/jsi/jsi/"); + await oraPromise( + fs.promises.cp(hermesJsiPath, reactNativeJsiPath, { + recursive: true, + }), + { + text: `Copying JSI from patched Hermes to React Native`, + successText: "Copied JSI from patched Hermes to React Native", + failText: (err) => + `Failed to copy JSI from Hermes to React Native: ${err.message}`, + isEnabled: !silent, + }, + ); +} + export const command = new Command("vendor-hermes") .argument("[from]", "Path to a file inside the app package", process.cwd()) .option("--silent", "Don't print anything except the final path", false) @@ -64,11 +90,6 @@ export const command = new Command("vendor-hermes") console.log(`Using Hermes version: ${hermesVersion}`); } - const reactNativeJsiPath = path.join( - reactNativePath, - "ReactCommon/jsi/jsi/", - ); - const hermesPath = path.join(reactNativePath, "sdks", "node-api-hermes"); if (force && fs.existsSync(hermesPath)) { await oraPromise( @@ -125,19 +146,11 @@ export const command = new Command("vendor-hermes") fs.existsSync(hermesJsiPath), `Hermes JSI path does not exist: ${hermesJsiPath}`, ); - - await oraPromise( - fs.promises.cp(hermesJsiPath, reactNativeJsiPath, { - recursive: true, - }), - { - text: `Copying JSI from patched Hermes to React Native`, - successText: "Copied JSI from patched Hermes to React Native", - failText: (err) => - `Failed to copy JSI from Hermes to React Native: ${err.message}`, - isEnabled: !silent, - }, - ); + await patchJsiHeaders({ + reactNativePath, + hermesJsiPath, + silent, + }); console.log(hermesPath); }), ); From 0f98a0ec5a24e089968264f823f8cd25c7a6f7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 11:10:33 +0100 Subject: [PATCH 12/28] Patch react_native_post_install to pass react native path --- scripts/init-macos-test-app.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts index 53837c06..9d0fd083 100644 --- a/scripts/init-macos-test-app.ts +++ b/scripts/init-macos-test-app.ts @@ -88,6 +88,10 @@ async function patchPodfile() { ":fabric_enabled => ENV['RCT_NEW_ARCH_ENABLED'] == '1',", ":fabric_enabled => true,", ], + [ + "react_native_post_install(installer)", + "react_native_post_install(installer, '../node_modules/react-native-macos')", + ], ]; const podfilePath = path.join(APP_PATH, "macos", "Podfile"); From ab26a44de3b7685ee428dc4605fd25fcbfc2329a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 11:47:56 +0100 Subject: [PATCH 13/28] Install CMake 3.22 --- .github/workflows/check.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2aa96805..3f314f33 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -123,6 +123,11 @@ jobs: with: java-version: "17" distribution: "temurin" + # Install CMake 3.22.1 since 4.x may have compatibility issues with Hermes build system + - name: Install compatible CMake version + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: "3.22.1" - name: Debug environment before build run: | echo "=== Xcode and build tools ===" @@ -137,11 +142,6 @@ jobs: echo "CMAKE_BINARY=$CMAKE_BINARY" echo "=== CMake version ===" cmake --version || echo "cmake command failed" - - name: Set required environment variables for Hermes build - run: | - # React Native's Hermes build script expects CMAKE_BINARY to be set - # Without it, the PATH preservation in env -i fails - echo "CMAKE_BINARY=$(which cmake)" >> $GITHUB_ENV - run: npm ci - run: npm run bootstrap env: From df8f1a02903075ac51e11b68ef523b56b2a7b15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 12:10:06 +0100 Subject: [PATCH 14/28] Fix bootstrap issue --- .github/workflows/check.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3f314f33..8d0a278c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -147,6 +147,11 @@ jobs: env: CMAKE_RN_TRIPLETS: arm64-apple-darwin FERRIC_TARGETS: aarch64-apple-darwin + # Set explicit compiler paths for CMake 3.22.1 compatibility + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + CMAKE_C_COMPILER: /usr/bin/clang + CMAKE_CXX_COMPILER: /usr/bin/clang++ - run: npm run init-macos-test-app - run: pod install --project-directory=macos working-directory: apps/macos-test-app From f52a4029482b219643166e3e62f525828ba11c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 12:31:38 +0100 Subject: [PATCH 15/28] Trying a higher CMake version --- .github/workflows/check.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8d0a278c..caef1d38 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -123,11 +123,11 @@ jobs: with: java-version: "17" distribution: "temurin" - # Install CMake 3.22.1 since 4.x may have compatibility issues with Hermes build system + # Install CMake 3 since 4.x may have compatibility issues with Hermes build system - name: Install compatible CMake version uses: jwlawson/actions-setup-cmake@v2 with: - cmake-version: "3.22.1" + cmake-version: "3.31.2" - name: Debug environment before build run: | echo "=== Xcode and build tools ===" @@ -147,11 +147,6 @@ jobs: env: CMAKE_RN_TRIPLETS: arm64-apple-darwin FERRIC_TARGETS: aarch64-apple-darwin - # Set explicit compiler paths for CMake 3.22.1 compatibility - CC: /usr/bin/clang - CXX: /usr/bin/clang++ - CMAKE_C_COMPILER: /usr/bin/clang - CMAKE_CXX_COMPILER: /usr/bin/clang++ - run: npm run init-macos-test-app - run: pod install --project-directory=macos working-directory: apps/macos-test-app From 18c9857454e9f648f3bcd7af32f305faf1efe613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 13:50:55 +0100 Subject: [PATCH 16/28] Set up files in private packages --- package-lock.json | 2 ++ packages/ferric-example/package.json | 8 +++++++- packages/node-addon-examples/.gitignore | 1 + packages/node-addon-examples/package.json | 10 ++++++++++ packages/node-addon-examples/tests/.gitignore | 1 - packages/node-tests/package.json | 7 +++++++ 6 files changed, 27 insertions(+), 2 deletions(-) delete mode 100644 packages/node-addon-examples/tests/.gitignore diff --git a/package-lock.json b/package-lock.json index ccc7603b..76bde18c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14897,6 +14897,7 @@ }, "packages/node-addon-examples": { "name": "@react-native-node-api/node-addon-examples", + "version": "0.1.0", "dependencies": { "assert": "^2.1.0" }, @@ -14909,6 +14910,7 @@ }, "packages/node-tests": { "name": "@react-native-node-api/node-tests", + "version": "0.1.0", "devDependencies": { "cmake-rn": "*", "gyp-to-cmake": "*", diff --git a/packages/ferric-example/package.json b/packages/ferric-example/package.json index 73678bb3..0695c049 100644 --- a/packages/ferric-example/package.json +++ b/packages/ferric-example/package.json @@ -1,8 +1,8 @@ { "name": "@react-native-node-api/ferric-example", + "version": "0.1.1", "private": true, "type": "commonjs", - "version": "0.1.1", "homepage": "https://github.com/callstackincubator/react-native-node-api", "repository": { "type": "git", @@ -11,6 +11,12 @@ }, "main": "ferric_example.js", "types": "ferric_example.d.ts", + "files": [ + "ferric_example.js", + "ferric_example.d.ts", + "ferric_example.apple.node", + "ferric_example.android.node" + ], "scripts": { "build": "ferric build", "bootstrap": "node --run build" diff --git a/packages/node-addon-examples/.gitignore b/packages/node-addon-examples/.gitignore index d838da98..7470cb91 100644 --- a/packages/node-addon-examples/.gitignore +++ b/packages/node-addon-examples/.gitignore @@ -1 +1,2 @@ examples/ +build/ diff --git a/packages/node-addon-examples/package.json b/packages/node-addon-examples/package.json index db48db3f..010d2f31 100644 --- a/packages/node-addon-examples/package.json +++ b/packages/node-addon-examples/package.json @@ -1,7 +1,17 @@ { "name": "@react-native-node-api/node-addon-examples", + "version": "0.1.0", "type": "commonjs", "main": "dist/index.js", + "files": [ + "dist", + "examples/**/package.json", + "examples/**/*.js", + "tests/**/package.json", + "tests/**/*.js", + "**/*.apple.node/**", + "**/*.android.node/**" + ], "private": true, "homepage": "https://github.com/callstackincubator/react-native-node-api", "repository": { diff --git a/packages/node-addon-examples/tests/.gitignore b/packages/node-addon-examples/tests/.gitignore deleted file mode 100644 index 378eac25..00000000 --- a/packages/node-addon-examples/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/packages/node-tests/package.json b/packages/node-tests/package.json index eec9e5ed..fd1d497b 100644 --- a/packages/node-tests/package.json +++ b/packages/node-tests/package.json @@ -1,8 +1,15 @@ { "name": "@react-native-node-api/node-tests", + "version": "0.1.0", "description": "Harness for running the Node.js tests from https://github.com/nodejs/node/tree/main/test", "type": "commonjs", "main": "tests.generated.js", + "files": [ + "dist", + "tests/**/*.js", + "**/*.apple.node/**", + "**/*.android.node/**" + ], "private": true, "homepage": "https://github.com/callstackincubator/react-native-node-api", "repository": { From 723f7b98deb95d5d7483e1beb85d2bb8793d7e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 14:13:03 +0100 Subject: [PATCH 17/28] Patch macos app with scripts and source files --- package-lock.json | 27 +++++------ scripts/init-macos-test-app.ts | 83 +++++++++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76bde18c..9604cb48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "globals": "^16.0.0", "prettier": "^3.6.2", "react-native": "0.81.4", + "read-pkg": "^9.0.1", "tsx": "^4.20.5", "typescript": "^5.8.0", "typescript-eslint": "^8.38.0" @@ -14607,7 +14608,7 @@ }, "packages/cli-utils": { "name": "@react-native-node-api/cli-utils", - "version": "0.1.0", + "version": "0.1.1", "dependencies": { "@commander-js/extra-typings": "^14.0.0", "bufout": "^0.3.2", @@ -14824,11 +14825,11 @@ } }, "packages/cmake-rn": { - "version": "0.4.1", + "version": "0.5.1", "dependencies": { - "@react-native-node-api/cli-utils": "0.1.0", + "@react-native-node-api/cli-utils": "0.1.1", "cmake-file-api": "0.1.0", - "react-native-node-api": "0.5.2", + "react-native-node-api": "0.6.1", "zod": "^4.1.11" }, "bin": { @@ -14841,11 +14842,11 @@ }, "packages/ferric": { "name": "ferric-cli", - "version": "0.3.4", + "version": "0.3.6", "dependencies": { "@napi-rs/cli": "~3.0.3", - "@react-native-node-api/cli-utils": "0.1.0", - "react-native-node-api": "0.5.2" + "@react-native-node-api/cli-utils": "0.1.1", + "react-native-node-api": "0.6.1" }, "bin": { "ferric": "bin/ferric.js" @@ -14859,9 +14860,9 @@ } }, "packages/gyp-to-cmake": { - "version": "0.3.0", + "version": "0.4.0", "dependencies": { - "@react-native-node-api/cli-utils": "0.1.0", + "@react-native-node-api/cli-utils": "0.1.1", "gyp-parser": "^1.0.4", "pkg-dir": "^8.0.0", "read-pkg": "^9.0.1" @@ -14872,11 +14873,11 @@ }, "packages/host": { "name": "react-native-node-api", - "version": "0.5.2", + "version": "0.6.1", "license": "MIT", "dependencies": { "@expo/plist": "^0.4.7", - "@react-native-node-api/cli-utils": "0.1.0", + "@react-native-node-api/cli-utils": "0.1.1", "pkg-dir": "^8.0.0", "read-pkg": "^9.0.1", "zod": "^4.1.11" @@ -14892,7 +14893,7 @@ }, "peerDependencies": { "@babel/core": "^7.26.10", - "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4" + "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.79.7 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4" } }, "packages/node-addon-examples": { @@ -14915,7 +14916,7 @@ "cmake-rn": "*", "gyp-to-cmake": "*", "prebuildify": "^6.0.1", - "react-native-node-api": "^0.5.2", + "react-native-node-api": "^0.6.1", "read-pkg": "^9.0.1", "rolldown": "1.0.0-beta.29" } diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts index 9d0fd083..821db42a 100644 --- a/scripts/init-macos-test-app.ts +++ b/scripts/init-macos-test-app.ts @@ -2,11 +2,12 @@ import assert from "node:assert/strict"; import cp from "node:child_process"; import fs from "node:fs"; import path from "node:path"; +import { readPackage } from "read-pkg"; const REACT_NATIVE_VERSION = "0.79.6"; const ROOT_PATH = path.join(import.meta.dirname, ".."); const APP_PATH = path.join(ROOT_PATH, "apps", "macos-test-app"); -const HOST_PACKAGE_PATH = path.join(ROOT_PATH, "packages", "host"); +const OTHER_APP_PATH = path.join(ROOT_PATH, "apps", "test-app"); function exec(command: string, args: string[], options: cp.SpawnOptions = {}) { const { status } = cp.spawnSync(command, args, { @@ -50,6 +51,63 @@ async function initializeReactNativeTemplate() { } } +async function patchPackageJson() { + console.log("Patching package.json scripts"); + const packageJson = await readPackage({ cwd: APP_PATH }); + const otherPackageJson = await readPackage({ cwd: OTHER_APP_PATH }); + + packageJson.scripts = { + ...packageJson.scripts, + metro: "react-native start --reset-cache --no-interactive", + "mocha-and-metro": "mocha-remote --exit-on-error -- node --run metro", + premacos: "killall 'MacOSTestApp' || true", + macos: "react-native run-macos --no-packager", + test: "mocha-remote --exit-on-error -- concurrently --passthrough-arguments --kill-others-on-fail npm:metro 'npm:macos -- {@}' --", + "test:allTests": "MOCHA_REMOTE_CONTEXT=allTests node --run test:ios -- ", + "test:nodeAddonExamples": + "MOCHA_REMOTE_CONTEXT=nodeAddonExamples node --run test -- ", + "test:nodeTests": "MOCHA_REMOTE_CONTEXT=nodeTests node --run test -- ", + "test:ferricExample": + "MOCHA_REMOTE_CONTEXT=ferricExample node --run test -- ", + }; + + const { + "mocha-remote-cli": mochaRemoteCliSpec, + "mocha-remote-react-native": mochaRemoteReactNativeSpec, + } = otherPackageJson.dependencies || {}; + + assert(typeof mochaRemoteCliSpec === "string"); + assert(typeof mochaRemoteReactNativeSpec === "string"); + + packageJson.dependencies = { + "@react-native-node-api/node-addon-examples": path.relative( + APP_PATH, + path.join(ROOT_PATH, "packages", "node-addon-examples"), + ), + "@react-native-node-api/node-tests": path.relative( + APP_PATH, + path.join(ROOT_PATH, "packages", "node-tests"), + ), + "@react-native-node-api/ferric-example": path.relative( + APP_PATH, + path.join(ROOT_PATH, "packages", "ferric-example"), + ), + "react-native-node-api": path.relative( + APP_PATH, + path.join(ROOT_PATH, "packages", "host"), + ), + ["mocha-remote-cli"]: mochaRemoteCliSpec, + ["mocha-remote-react-native"]: mochaRemoteReactNativeSpec, + ...packageJson.dependencies, + }; + + await fs.promises.writeFile( + path.join(APP_PATH, "package.json"), + JSON.stringify(packageJson, null, 2), + "utf8", + ); +} + function installDependencies() { console.log("Installing dependencies"); exec( @@ -60,7 +118,6 @@ function installDependencies() { "--prefer-offline", "--install-links", "react-native-macos-init", - path.relative(APP_PATH, HOST_PACKAGE_PATH), ], { cwd: APP_PATH, @@ -102,8 +159,22 @@ async function patchPodfile() { await fs.promises.writeFile(podfilePath, podfileContents, "utf8"); } -await deletePreviousApp(); -await initializeReactNativeTemplate(); +async function copySourceFiles() { + console.log("Copying source files from test-app into macos-test-app:"); + const FILE_NAMES = ["App.tsx", "babel.config.js"]; + for (const fileName of FILE_NAMES) { + console.log(`↳ ${fileName}`); + await fs.promises.copyFile( + path.join(OTHER_APP_PATH, fileName), + path.join(APP_PATH, fileName), + ); + } +} + +// await deletePreviousApp(); +// await initializeReactNativeTemplate(); +await patchPackageJson(); installDependencies(); -initializeReactNativeMacOSTemplate(); -await patchPodfile(); +// initializeReactNativeMacOSTemplate(); +// await patchPodfile(); +await copySourceFiles(); From f6609462a9968822b3c4d4af69eab3944f78f30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 14:19:04 +0100 Subject: [PATCH 18/28] Remove platform restriction from host package's podspec --- packages/host/react-native-node-api.podspec | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/host/react-native-node-api.podspec b/packages/host/react-native-node-api.podspec index 5066e82d..e2beaae9 100644 --- a/packages/host/react-native-node-api.podspec +++ b/packages/host/react-native-node-api.podspec @@ -25,7 +25,6 @@ Pod::Spec.new do |s| s.license = package["license"] s.authors = package["author"] - s.platforms = { :ios => min_ios_version_supported } s.source = { :git => "https://github.com/callstackincubator/react-native-node-api.git", :tag => "#{s.version}" } s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}", "weak-node-api/include/*.h", "weak-node-api/*.hpp" From c23325b20f2844e1e041e4c0d37976d703bdadc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 14:58:09 +0100 Subject: [PATCH 19/28] Commit update from bootstrapping --- packages/node-addon-examples/tests/async/CMakeLists.txt | 6 +++--- packages/node-addon-examples/tests/buffers/CMakeLists.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/node-addon-examples/tests/async/CMakeLists.txt b/packages/node-addon-examples/tests/async/CMakeLists.txt index 659e3461..2b6b2b81 100644 --- a/packages/node-addon-examples/tests/async/CMakeLists.txt +++ b/packages/node-addon-examples/tests/async/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.15...3.31) -project(async_test) +project(async-test) include(${WEAK_NODE_API_CONFIG}) @@ -10,12 +10,12 @@ option(BUILD_APPLE_FRAMEWORK "Wrap addon in an Apple framework" ON) if(APPLE AND BUILD_APPLE_FRAMEWORK) set_target_properties(addon PROPERTIES FRAMEWORK TRUE - MACOSX_FRAMEWORK_IDENTIFIER async_test.addon + MACOSX_FRAMEWORK_IDENTIFIER async-test.addon MACOSX_FRAMEWORK_SHORT_VERSION_STRING 1.0 MACOSX_FRAMEWORK_BUNDLE_VERSION 1.0 XCODE_ATTRIBUTE_SKIP_INSTALL NO ) -elseif(APPLE) +else() set_target_properties(addon PROPERTIES PREFIX "" SUFFIX .node diff --git a/packages/node-addon-examples/tests/buffers/CMakeLists.txt b/packages/node-addon-examples/tests/buffers/CMakeLists.txt index bca19bce..8d7ac2d2 100644 --- a/packages/node-addon-examples/tests/buffers/CMakeLists.txt +++ b/packages/node-addon-examples/tests/buffers/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.15...3.31) -project(buffers_test) +project(buffers-test) include(${WEAK_NODE_API_CONFIG}) @@ -10,12 +10,12 @@ option(BUILD_APPLE_FRAMEWORK "Wrap addon in an Apple framework" ON) if(APPLE AND BUILD_APPLE_FRAMEWORK) set_target_properties(addon PROPERTIES FRAMEWORK TRUE - MACOSX_FRAMEWORK_IDENTIFIER buffers_test.addon + MACOSX_FRAMEWORK_IDENTIFIER buffers-test.addon MACOSX_FRAMEWORK_SHORT_VERSION_STRING 1.0 MACOSX_FRAMEWORK_BUNDLE_VERSION 1.0 XCODE_ATTRIBUTE_SKIP_INSTALL NO ) -elseif(APPLE) +else() set_target_properties(addon PROPERTIES PREFIX "" SUFFIX .node From 838e783bb6777c3b3622d77274582f9a1fcfc1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 15:43:58 +0100 Subject: [PATCH 20/28] Reconstruct missing symbolic links if needed --- packages/host/src/node/cli/apple.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/host/src/node/cli/apple.ts b/packages/host/src/node/cli/apple.ts index c5eec200..6053bd5f 100644 --- a/packages/host/src/node/cli/apple.ts +++ b/packages/host/src/node/cli/apple.ts @@ -201,6 +201,15 @@ export async function linkVersionedFramework({ "darwin", "Linking Apple addons are only supported on macOS", ); + // Reconstruct missing symbolic links if needed + const versionAPath = path.join(frameworkPath, "Versions", "A"); + const versionCurrentPath = path.join(frameworkPath, "Versions", "Current"); + if (fs.existsSync(versionAPath) && !fs.existsSync(versionCurrentPath)) { + await fs.promises.symlink( + path.relative(path.dirname(versionCurrentPath), versionAPath), + versionCurrentPath, + ); + } const frameworkInfoPath = path.join( frameworkPath, "Versions", @@ -209,6 +218,23 @@ export async function linkVersionedFramework({ "Info.plist", ); const frameworkInfo = await readFrameworkInfo(frameworkInfoPath); + const libraryRealPath = path.join( + frameworkPath, + "Versions", + "Current", + frameworkInfo.CFBundleExecutable, + ); + const libraryLinkPath = path.join( + frameworkPath, + frameworkInfo.CFBundleExecutable, + ); + // Reconstruct missing symbolic links if needed + if (fs.existsSync(libraryRealPath) && !fs.existsSync(libraryLinkPath)) { + await fs.promises.symlink( + path.relative(path.dirname(libraryLinkPath), libraryRealPath), + libraryLinkPath, + ); + } // Update install name await spawn( "install_name_tool", From 070e61ff8d4876bbe365f1d6c2251dc27ff1af2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 15:44:27 +0100 Subject: [PATCH 21/28] Remove debug info from workflow --- .github/workflows/check.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index caef1d38..a8a57f87 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -128,20 +128,6 @@ jobs: uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: "3.31.2" - - name: Debug environment before build - run: | - echo "=== Xcode and build tools ===" - xcode-select -p - which clang || echo "clang not found" - which clang++ || echo "clang++ not found" - which make || echo "make not found" - which cmake || echo "cmake not found" - echo "=== Environment variables ===" - echo "PATH=$PATH" - echo "SDKROOT=$SDKROOT" - echo "CMAKE_BINARY=$CMAKE_BINARY" - echo "=== CMake version ===" - cmake --version || echo "cmake command failed" - run: npm ci - run: npm run bootstrap env: From eaaef561ac8e74290cb428538d78dcbaae1b760f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 17:05:26 +0100 Subject: [PATCH 22/28] Moved "ios" into a shared "apple" directory --- packages/host/{ios => apple}/NodeApiHostModuleProvider.mm | 0 packages/host/react-native-node-api.podspec | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/host/{ios => apple}/NodeApiHostModuleProvider.mm (100%) diff --git a/packages/host/ios/NodeApiHostModuleProvider.mm b/packages/host/apple/NodeApiHostModuleProvider.mm similarity index 100% rename from packages/host/ios/NodeApiHostModuleProvider.mm rename to packages/host/apple/NodeApiHostModuleProvider.mm diff --git a/packages/host/react-native-node-api.podspec b/packages/host/react-native-node-api.podspec index e2beaae9..ff2cbe9d 100644 --- a/packages/host/react-native-node-api.podspec +++ b/packages/host/react-native-node-api.podspec @@ -27,7 +27,7 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/callstackincubator/react-native-node-api.git", :tag => "#{s.version}" } - s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}", "weak-node-api/include/*.h", "weak-node-api/*.hpp" + s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}", "weak-node-api/include/*.h", "weak-node-api/*.hpp" s.public_header_files = "weak-node-api/include/*.h" s.vendored_frameworks = "auto-linked/apple/*.xcframework", "weak-node-api/weak-node-api.xcframework" From 46e731b9e7f5e643299f0584e0b9d8be01de6dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 17:06:54 +0100 Subject: [PATCH 23/28] Simplify Apple host module provider --- .../host/apple/NodeApiHostModuleProvider.mm | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/packages/host/apple/NodeApiHostModuleProvider.mm b/packages/host/apple/NodeApiHostModuleProvider.mm index d4ecd94f..b01c5306 100644 --- a/packages/host/apple/NodeApiHostModuleProvider.mm +++ b/packages/host/apple/NodeApiHostModuleProvider.mm @@ -1,26 +1,12 @@ #import "CxxNodeApiHostModule.hpp" #import "WeakNodeApiInjector.hpp" -#define USE_CXX_TURBO_MODULE_UTILS 0 -#if defined(__has_include) -#if __has_include() -#undef USE_CXX_TURBO_MODULE_UTILS -#define USE_CXX_TURBO_MODULE_UTILS 1 -#endif -#endif - -#if USE_CXX_TURBO_MODULE_UTILS #import @interface NodeApiHost : NSObject -#else -#import -@interface NodeApiHost : NSObject -#endif // USE_CXX_TURBO_MODULE_UTILS @end @implementation NodeApiHost -#if USE_CXX_TURBO_MODULE_UTILS + (void)load { callstack::nodeapihost::injectIntoWeakNodeApi(); @@ -31,14 +17,5 @@ + (void)load { jsInvoker); }); } -#else -RCT_EXPORT_MODULE() - -- (std::shared_ptr)getTurboModule: - (const facebook::react::ObjCTurboModule::InitParams &)params { - return std::make_shared( - params.jsInvoker); -} -#endif // USE_CXX_TURBO_MODULE_UTILS @end \ No newline at end of file From 012d85f1340b489231cbad2d214ca70925f567b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 17:07:15 +0100 Subject: [PATCH 24/28] Restore weak-node-api symlinks --- packages/host/src/node/cli/apple.ts | 54 ++++++++++++++++----------- packages/host/src/node/cli/program.ts | 36 +++++++++++++++++- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/packages/host/src/node/cli/apple.ts b/packages/host/src/node/cli/apple.ts index 6053bd5f..b92487db 100644 --- a/packages/host/src/node/cli/apple.ts +++ b/packages/host/src/node/cli/apple.ts @@ -192,15 +192,11 @@ export async function linkFlatFramework({ } } -export async function linkVersionedFramework({ - frameworkPath, - newLibraryName, -}: LinkFrameworkOptions) { - assert.equal( - process.platform, - "darwin", - "Linking Apple addons are only supported on macOS", - ); +/** + * NPM packages aren't preserving internal symlinks inside versioned frameworks. + * This function attempts to restore those. + */ +export async function restoreFrameworkLinks(frameworkPath: string) { // Reconstruct missing symbolic links if needed const versionAPath = path.join(frameworkPath, "Versions", "A"); const versionCurrentPath = path.join(frameworkPath, "Versions", "Current"); @@ -210,24 +206,18 @@ export async function linkVersionedFramework({ versionCurrentPath, ); } - const frameworkInfoPath = path.join( - frameworkPath, - "Versions", - "Current", - "Resources", - "Info.plist", + + const { CFBundleExecutable } = await readFrameworkInfo( + path.join(frameworkPath, "Versions", "Current", "Resources", "Info.plist"), ); - const frameworkInfo = await readFrameworkInfo(frameworkInfoPath); + const libraryRealPath = path.join( frameworkPath, "Versions", "Current", - frameworkInfo.CFBundleExecutable, - ); - const libraryLinkPath = path.join( - frameworkPath, - frameworkInfo.CFBundleExecutable, + CFBundleExecutable, ); + const libraryLinkPath = path.join(frameworkPath, CFBundleExecutable); // Reconstruct missing symbolic links if needed if (fs.existsSync(libraryRealPath) && !fs.existsSync(libraryLinkPath)) { await fs.promises.symlink( @@ -235,6 +225,28 @@ export async function linkVersionedFramework({ libraryLinkPath, ); } +} + +export async function linkVersionedFramework({ + frameworkPath, + newLibraryName, +}: LinkFrameworkOptions) { + assert.equal( + process.platform, + "darwin", + "Linking Apple addons are only supported on macOS", + ); + + await restoreFrameworkLinks(frameworkPath); + + const frameworkInfoPath = path.join( + frameworkPath, + "Versions", + "Current", + "Resources", + "Info.plist", + ); + const frameworkInfo = await readFrameworkInfo(frameworkInfoPath); // Update install name await spawn( "install_name_tool", diff --git a/packages/host/src/node/cli/program.ts b/packages/host/src/node/cli/program.ts index 169ca85b..85b9016f 100644 --- a/packages/host/src/node/cli/program.ts +++ b/packages/host/src/node/cli/program.ts @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import path from "node:path"; import { EventEmitter } from "node:stream"; +import fs from "node:fs"; import { Command, @@ -26,8 +27,9 @@ import { import { command as vendorHermes } from "./hermes"; import { packageNameOption, pathSuffixOption } from "./options"; import { linkModules, pruneLinkedModules, ModuleLinker } from "./link-modules"; -import { linkXcframework } from "./apple"; +import { linkXcframework, restoreFrameworkLinks } from "./apple"; import { linkAndroidDir } from "./android"; +import { weakNodeApiPath } from "../weak-node-api"; // We're attaching a lot of listeners when spawning in parallel EventEmitter.defaultMaxListeners = 100; @@ -169,6 +171,38 @@ program await pruneLinkedModules(platform, modules); } } + + if (apple) { + await oraPromise( + async () => { + const xcframeworkPath = path.join( + weakNodeApiPath, + "weak-node-api.xcframework", + ); + await Promise.all( + [ + path.join(xcframeworkPath, "macos-x86_64"), + path.join(xcframeworkPath, "macos-arm64"), + path.join(xcframeworkPath, "macos-arm64_x86_64"), + ].map(async (slicePath) => { + const frameworkPath = path.join( + slicePath, + "weak-node-api.framework", + ); + if (fs.existsSync(frameworkPath)) { + await restoreFrameworkLinks(frameworkPath); + } + }), + ); + }, + { + text: "Restoring weak-node-api symlinks", + successText: "Restored weak-node-api symlinks", + failText: (error) => + `Failed to restore weak-node-api symlinks: ${error.message}`, + }, + ); + } }, ), ); From 4cf901187d3848cc520ebe0665d4f93be0c9fec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 17:25:23 +0100 Subject: [PATCH 25/28] Re-arm the init script --- scripts/init-macos-test-app.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts index 821db42a..6e7ed1ec 100644 --- a/scripts/init-macos-test-app.ts +++ b/scripts/init-macos-test-app.ts @@ -171,10 +171,10 @@ async function copySourceFiles() { } } -// await deletePreviousApp(); -// await initializeReactNativeTemplate(); +await deletePreviousApp(); +await initializeReactNativeTemplate(); await patchPackageJson(); installDependencies(); -// initializeReactNativeMacOSTemplate(); -// await patchPodfile(); +initializeReactNativeMacOSTemplate(); +await patchPodfile(); await copySourceFiles(); From 77ed18dea940fef8c33d8477fdf05c46964f566f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 28 Oct 2025 18:47:09 +0100 Subject: [PATCH 26/28] Build universal Darwin libraries --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a8a57f87..a6e0057d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -131,8 +131,8 @@ jobs: - run: npm ci - run: npm run bootstrap env: - CMAKE_RN_TRIPLETS: arm64-apple-darwin - FERRIC_TARGETS: aarch64-apple-darwin + CMAKE_RN_TRIPLETS: arm64;x86_64-apple-darwin + FERRIC_TARGETS: aarch64-apple-darwin,x86_64-apple-darwin - run: npm run init-macos-test-app - run: pod install --project-directory=macos working-directory: apps/macos-test-app From 5080c23659ea5e4f0532a88fd216c5492db9cc54 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 19:59:20 +0100 Subject: [PATCH 27/28] Add missing x86_64-apple-darwin Rust target to macOS CI job (#298) * Initial plan * Add missing x86_64-apple-darwin Rust target to macOS job Co-authored-by: kraenhansen <1243959+kraenhansen@users.noreply.github.com> * Add only missing x86_64-apple-darwin target (aarch64 is host) Co-authored-by: kraenhansen <1243959+kraenhansen@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kraenhansen <1243959+kraenhansen@users.noreply.github.com> --- .github/workflows/check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a6e0057d..812af311 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -128,6 +128,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: "3.31.2" + - run: rustup target add x86_64-apple-darwin - run: npm ci - run: npm run bootstrap env: From ba65a41ade96eeb345e5f839d73d7213413b1069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 29 Oct 2025 08:04:29 +0100 Subject: [PATCH 28/28] Move linked deps into install command to make --install-links effective --- scripts/init-macos-test-app.ts | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts index 6e7ed1ec..a3ac2ab9 100644 --- a/scripts/init-macos-test-app.ts +++ b/scripts/init-macos-test-app.ts @@ -80,22 +80,6 @@ async function patchPackageJson() { assert(typeof mochaRemoteReactNativeSpec === "string"); packageJson.dependencies = { - "@react-native-node-api/node-addon-examples": path.relative( - APP_PATH, - path.join(ROOT_PATH, "packages", "node-addon-examples"), - ), - "@react-native-node-api/node-tests": path.relative( - APP_PATH, - path.join(ROOT_PATH, "packages", "node-tests"), - ), - "@react-native-node-api/ferric-example": path.relative( - APP_PATH, - path.join(ROOT_PATH, "packages", "ferric-example"), - ), - "react-native-node-api": path.relative( - APP_PATH, - path.join(ROOT_PATH, "packages", "host"), - ), ["mocha-remote-cli"]: mochaRemoteCliSpec, ["mocha-remote-react-native"]: mochaRemoteReactNativeSpec, ...packageJson.dependencies, @@ -114,10 +98,20 @@ function installDependencies() { "npm", [ "install", - "--save-dev", + "--save", "--prefer-offline", "--install-links", "react-native-macos-init", + path.relative( + APP_PATH, + path.join(ROOT_PATH, "packages", "node-addon-examples"), + ), + path.relative(APP_PATH, path.join(ROOT_PATH, "packages", "node-tests")), + path.relative( + APP_PATH, + path.join(ROOT_PATH, "packages", "ferric-example"), + ), + path.relative(APP_PATH, path.join(ROOT_PATH, "packages", "host")), ], { cwd: APP_PATH,