diff --git a/.changeset/quiet-files-resolve.md b/.changeset/quiet-files-resolve.md new file mode 100644 index 00000000..6d7bc85f --- /dev/null +++ b/.changeset/quiet-files-resolve.md @@ -0,0 +1,5 @@ +--- +"react-native-node-api": patch +--- + +Fix `require('./addon')` being rewritten to a native addon when `addon.js` or `addon.json` exists next to `addon.node`, matching Node.js resolution order. diff --git a/packages/host/src/node/babel-plugin/plugin.test.ts b/packages/host/src/node/babel-plugin/plugin.test.ts index 7dae2979..d3fe5fac 100644 --- a/packages/host/src/node/babel-plugin/plugin.test.ts +++ b/packages/host/src/node/babel-plugin/plugin.test.ts @@ -129,7 +129,7 @@ describe("plugin", () => { itTransforms("and does not touch required JS files", { files: { "package.json": `{ "name": "my-package" }`, - // TODO: Add a ./my-addon.node to make this test complete + "my-addon.node": "// This is supposed to be a binary file", "my-addon.js": "// Some JS file", "index.js": ` const addon = require('./my-addon'); diff --git a/packages/host/src/node/path-utils.ts b/packages/host/src/node/path-utils.ts index 0cb4506a..c6294b28 100644 --- a/packages/host/src/node/path-utils.ts +++ b/packages/host/src/node/path-utils.ts @@ -66,6 +66,13 @@ const packageNameCache = new Map(); * TODO: Consider checking for a specific platform extension. */ export function isNodeApiModule(modulePath: string): boolean { + // Node resolves `.js` and `.json` before `.node` + if ( + !modulePath.endsWith(".node") && + [".js", ".json"].some((extension) => fs.existsSync(modulePath + extension)) + ) { + return false; + } { // HACK: Take a shortcut (if applicable): existing `.node` files are addons try { diff --git a/packages/node-addon-examples/package.json b/packages/node-addon-examples/package.json index acfd70ec..803fcdbf 100644 --- a/packages/node-addon-examples/package.json +++ b/packages/node-addon-examples/package.json @@ -30,11 +30,13 @@ "bootstrap": "node --run copy-and-build" }, "devDependencies": { + "@expo/plist": "0.4.7", "cmake-rn": "workspace:*", "node-addon-examples": "github:nodejs/node-addon-examples#4b7dd86a85644610e6de80154df9acac9329b509", "gyp-to-cmake": "workspace:*", "read-pkg": "^9.0.1", - "weak-node-api": "workspace:*" + "weak-node-api": "workspace:*", + "zod": "^4.1.11" }, "dependencies": { "assert": "^2.1.0", diff --git a/packages/node-addon-examples/scripts/verify-prebuilds.mts b/packages/node-addon-examples/scripts/verify-prebuilds.mts index 94b4e1bb..6e93bd85 100644 --- a/packages/node-addon-examples/scripts/verify-prebuilds.mts +++ b/packages/node-addon-examples/scripts/verify-prebuilds.mts @@ -2,6 +2,9 @@ import fs from "node:fs"; import assert from "node:assert/strict"; import path from "node:path"; +import plist from "@expo/plist"; +import * as z from "zod"; + import { DIRS } from "./cmake-projects.mjs"; const EXPECTED_ANDROID_ARCHS = ["armeabi-v7a", "arm64-v8a", "x86_64", "x86"]; @@ -37,6 +40,27 @@ async function verifyAndroidPrebuild(dirent: fs.Dirent) { } } +const InfoPlistSchema = z.object({ + CFBundleExecutable: z.string(), + CFBundleName: z.string(), + CFBundleIdentifier: z.string().regex(/^[A-Za-z0-9-.]+$/), +}); + +async function verifyInfoPlist(frameworkDir: string) { + const libraryName = path.basename(frameworkDir, ".framework"); + const info = InfoPlistSchema.parse( + plist.default.parse( + await fs.promises.readFile(path.join(frameworkDir, "Info.plist"), "utf8"), + ), + ); + assert.equal(info.CFBundleExecutable, libraryName); + assert.equal(info.CFBundleName, libraryName); + assert( + fs.existsSync(path.join(frameworkDir, libraryName)), + `Missing executable ${libraryName} in ${frameworkDir}`, + ); +} + async function verifyApplePrebuild(dirent: fs.Dirent) { console.log("Verifying Apple prebuild", dirent.name, "in", dirent.parentPath); for (const arch of EXPECTED_XCFRAMEWORK_PLATFORMS) { @@ -65,8 +89,7 @@ async function verifyApplePrebuild(dirent: fs.Dirent) { "Expected only directory and files in framework", ); if (file.name === "Info.plist") { - // TODO: Verify the contents of the Info.plist file - continue; + await verifyInfoPlist(frameworkDir); } else { assert( !file.name.endsWith(".node"), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9632a67c..87b9f1b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -281,6 +281,9 @@ importers: specifier: workspace:* version: link:../host devDependencies: + '@expo/plist': + specifier: 0.4.7 + version: 0.4.7 cmake-rn: specifier: workspace:* version: link:../cmake-rn @@ -296,6 +299,9 @@ importers: weak-node-api: specifier: workspace:* version: link:../weak-node-api + zod: + specifier: ^4.1.11 + version: 4.4.3 packages/node-tests: devDependencies: