diff --git a/apps/test-app/.gitignore b/apps/test-app/.gitignore index b0dd8f88..4550bc8f 100644 --- a/apps/test-app/.gitignore +++ b/apps/test-app/.gitignore @@ -13,3 +13,5 @@ msbuild.binlog # Ignoring the Podfile.lock as the `react-native-node-api-modules` hash updates too frequently Podfile.lock + +hermes/ diff --git a/packages/react-native-node-api-modules/package.json b/packages/react-native-node-api-modules/package.json index 425ec03c..c7f07a71 100644 --- a/packages/react-native-node-api-modules/package.json +++ b/packages/react-native-node-api-modules/package.json @@ -31,6 +31,7 @@ "!android/build", "ios", "include", + "babel-plugin.js", "scripts/patch-hermes.rb", "weak-node-api/**", "!weak-node-api/build/", @@ -89,7 +90,7 @@ }, "peerDependencies": { "@babel/core": "^7.26.10", - "react-native": "0.79.1" + "react-native": "0.79.1 || 0.79.2" }, "codegenConfig": { "name": "NodeApiHostSpec", diff --git a/packages/react-native-node-api-modules/scripts/patch-hermes.rb b/packages/react-native-node-api-modules/scripts/patch-hermes.rb index 93737ffc..6e200250 100644 --- a/packages/react-native-node-api-modules/scripts/patch-hermes.rb +++ b/packages/react-native-node-api-modules/scripts/patch-hermes.rb @@ -1,6 +1,6 @@ Pod::UI.warn "!!! PATCHING HERMES WITH NODE-API SUPPORT !!!" -VENDORED_HERMES_DIR ||= `npx react-native-node-api-modules vendor-hermes --silent`.strip +VENDORED_HERMES_DIR ||= `npx react-native-node-api-modules vendor-hermes --silent '#{Pod::Config.instance.installation_root}'`.strip if Dir.exist?(VENDORED_HERMES_DIR) Pod::UI.info "Hermes vendored into #{VENDORED_HERMES_DIR.inspect}" else diff --git a/packages/react-native-node-api-modules/src/node/cli/hermes.ts b/packages/react-native-node-api-modules/src/node/cli/hermes.ts index 50e184a1..40128e9b 100644 --- a/packages/react-native-node-api-modules/src/node/cli/hermes.ts +++ b/packages/react-native-node-api-modules/src/node/cli/hermes.ts @@ -1,31 +1,45 @@ +import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { Command } from "@commander-js/extra-typings"; import { spawn, SpawnFailure } from "bufout"; import { oraPromise } from "ora"; -import { prettyPath } from "../path-utils"; +import { packageDirectorySync } from "pkg-dir"; -const HERMES_PATH = path.resolve(__dirname, "../../../hermes"); +import { getLatestMtime, prettyPath } from "../path-utils"; + +const HOST_PACKAGE_ROOT = path.resolve(__dirname, "../../.."); +// FIXME: make this configurable with reasonable fallback before public release const HERMES_GIT_URL = "https://github.com/kraenhansen/hermes.git"; const HERMES_GIT_TAG = "node-api-for-react-native-0.79.0"; -const REACT_NATIVE_DIR = path.dirname( - require.resolve("react-native/package.json") -); 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) .option( "--force", "Don't check timestamps of input files to skip unnecessary rebuilds", false ) - .action(async ({ force, silent }) => { + .action(async (from, { force, silent }) => { try { - if (force) { - fs.rmSync(HERMES_PATH, { recursive: true, force: true }); + const appPackageRoot = packageDirectorySync({ cwd: from }); + assert(appPackageRoot, "Failed to find package root"); + const hermesPath = path.join(HOST_PACKAGE_ROOT, "hermes"); + if (force && fs.existsSync(hermesPath)) { + await oraPromise( + fs.promises.rm(hermesPath, { recursive: true, force: true }), + { + text: "Removing existing Hermes clone", + successText: "Removed existing Hermes clone", + failText: (error) => + `Failed to remove existing Hermes clone: ${error.message}`, + isEnabled: !silent, + } + ); } - if (!fs.existsSync(HERMES_PATH)) { + if (!fs.existsSync(hermesPath)) { await oraPromise( spawn( "git", @@ -37,27 +51,41 @@ export const command = new Command("vendor-hermes") "--branch", HERMES_GIT_TAG, HERMES_GIT_URL, - HERMES_PATH, + hermesPath, ], { outputMode: "buffered", } ), { - text: `Cloning custom Hermes into ${prettyPath(HERMES_PATH)}`, + text: `Cloning custom Hermes into ${prettyPath(hermesPath)}`, successText: "Cloned custom Hermes", failText: (err) => `Failed to clone custom Hermes: ${err.message}`, isEnabled: !silent, } ); + } + const hermesJsiPath = path.join(hermesPath, "API/jsi/jsi"); + const reactNativePath = path.dirname( + require.resolve("react-native/package.json", { + // Ensures we'll be patching the React Native package actually used by the app + paths: [appPackageRoot], + }) + ); + const reactNativeJsiPath = path.join( + reactNativePath, + "ReactCommon/jsi/jsi/" + ); + + if ( + fs.existsSync(reactNativeJsiPath) && + (force || + getLatestMtime(hermesJsiPath) > getLatestMtime(reactNativeJsiPath)) + ) { await oraPromise( - fs.promises.cp( - path.join(HERMES_PATH, "API/jsi/jsi"), - path.join(REACT_NATIVE_DIR, "ReactCommon/jsi/jsi/"), - { - recursive: true, - } - ), + fs.promises.cp(hermesJsiPath, reactNativeJsiPath, { + recursive: true, + }), { text: `Copying JSI from Hermes to React Native`, successText: "Copied JSI from Hermes to React Native", @@ -67,7 +95,7 @@ export const command = new Command("vendor-hermes") } ); } - console.log(HERMES_PATH); + console.log(hermesPath); } catch (error) { if (error instanceof SpawnFailure) { error.flushOutput("both");