From 79d7a0478c7c5179c9dbed1ccba1ba3cbbbcd3ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 14 Jul 2025 17:12:26 +0200 Subject: [PATCH 1/4] Fix findNodeApiModulePaths to exclude from root node_modules of the app itself --- packages/host/src/node/path-utils.test.ts | 1 + packages/host/src/node/path-utils.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/host/src/node/path-utils.test.ts b/packages/host/src/node/path-utils.test.ts index a5bc0bc0..37fc4ba4 100644 --- a/packages/host/src/node/path-utils.test.ts +++ b/packages/host/src/node/path-utils.test.ts @@ -320,6 +320,7 @@ describe("findNodeApiModulePaths", () => { it("respects default exclude patterns", (context) => { const tempDir = setupTempDirectory(context, { "root.apple.node/react-native-node-api-module": "", + "node_modules/dependency/lib.apple.node/react-native-node-api-module": "", "child-dir/dependency/lib.apple.node/react-native-node-api-module": "", "child-dir/node_modules/dependency/lib.apple.node/react-native-node-api-module": "", diff --git a/packages/host/src/node/path-utils.ts b/packages/host/src/node/path-utils.ts index bcbcf181..79addae9 100644 --- a/packages/host/src/node/path-utils.ts +++ b/packages/host/src/node/path-utils.ts @@ -279,9 +279,9 @@ export const MAGIC_FILENAME = "react-native-node-api-module"; * Default patterns to use when excluding paths from the search for Node-API modules. */ export const DEFAULT_EXCLUDE_PATTERNS = [ - /\/react-native-node-api\//, - /\/node_modules\//, - /\/.git\//, + /(^|\/)react-native-node-api\//, + /(^|\/)node_modules\//, + /(^|\/).git\//, ]; export function hasPlatformExtension( From 358d7bad85843ad60a5381aa8eac8738210a7267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 14 Jul 2025 17:31:59 +0200 Subject: [PATCH 2/4] Turn findNodeApiModulePaths async --- packages/host/src/node/cli/link-modules.ts | 9 +-- packages/host/src/node/cli/program.ts | 2 +- packages/host/src/node/path-utils.test.ts | 23 ++++--- packages/host/src/node/path-utils.ts | 72 +++++++++++++--------- 4 files changed, 61 insertions(+), 45 deletions(-) diff --git a/packages/host/src/node/cli/link-modules.ts b/packages/host/src/node/cli/link-modules.ts index 2c21c187..5bb18d80 100644 --- a/packages/host/src/node/cli/link-modules.ts +++ b/packages/host/src/node/cli/link-modules.ts @@ -61,7 +61,7 @@ export async function linkModules({ linker, }: LinkModulesOptions): Promise { // Find all their xcframeworks - const dependenciesByName = findNodeApiModulePathsByDependency({ + const dependenciesByName = await findNodeApiModulePathsByDependency({ fromPath, platform, includeSelf: true, @@ -69,9 +69,10 @@ export async function linkModules({ // Find absolute paths to xcframeworks const absoluteModulePaths = Object.values(dependenciesByName).flatMap( - (dependency) => dependency.modulePaths.map( - (modulePath) => path.join(dependency.path, modulePath) - ) + (dependency) => + dependency.modulePaths.map((modulePath) => + path.join(dependency.path, modulePath) + ) ); if (hasDuplicateLibraryNames(absoluteModulePaths, naming)) { diff --git a/packages/host/src/node/cli/program.ts b/packages/host/src/node/cli/program.ts index d0f0f63f..0b629245 100644 --- a/packages/host/src/node/cli/program.ts +++ b/packages/host/src/node/cli/program.ts @@ -169,7 +169,7 @@ program .addOption(pathSuffixOption) .action(async (fromArg, { json, pathSuffix }) => { const rootPath = path.resolve(fromArg); - const dependencies = findNodeApiModulePathsByDependency({ + const dependencies = await findNodeApiModulePathsByDependency({ fromPath: rootPath, platform: PLATFORMS, includeSelf: true, diff --git a/packages/host/src/node/path-utils.test.ts b/packages/host/src/node/path-utils.test.ts index 37fc4ba4..abaf9342 100644 --- a/packages/host/src/node/path-utils.test.ts +++ b/packages/host/src/node/path-utils.test.ts @@ -300,13 +300,13 @@ describe("findPackageDependencyPaths", () => { }); describe("findNodeApiModulePaths", () => { - it("should find .apple.node paths", (context) => { + it("should find .apple.node paths", async (context) => { const tempDir = setupTempDirectory(context, { "root.apple.node/react-native-node-api-module": "", "sub-directory/lib-a.apple.node/react-native-node-api-module": "", "sub-directory/lib-b.apple.node/react-native-node-api-module": "", }); - const result = findNodeApiModulePaths({ + const result = await findNodeApiModulePaths({ fromPath: tempDir, platform: "apple", }); @@ -317,7 +317,7 @@ describe("findNodeApiModulePaths", () => { ]); }); - it("respects default exclude patterns", (context) => { + it("respects default exclude patterns", async (context) => { const tempDir = setupTempDirectory(context, { "root.apple.node/react-native-node-api-module": "", "node_modules/dependency/lib.apple.node/react-native-node-api-module": "", @@ -325,7 +325,7 @@ describe("findNodeApiModulePaths", () => { "child-dir/node_modules/dependency/lib.apple.node/react-native-node-api-module": "", }); - const result = findNodeApiModulePaths({ + const result = await findNodeApiModulePaths({ fromPath: tempDir, platform: "apple", }); @@ -335,14 +335,14 @@ describe("findNodeApiModulePaths", () => { ]); }); - it("respects explicit exclude patterns", (context) => { + it("respects explicit exclude patterns", async (context) => { const tempDir = setupTempDirectory(context, { "root.apple.node/react-native-node-api-module": "", "child-dir/dependency/lib.apple.node/react-native-node-api-module": "", "child-dir/node_modules/dependency/lib.apple.node/react-native-node-api-module": "", }); - const result = findNodeApiModulePaths({ + const result = await findNodeApiModulePaths({ fromPath: tempDir, platform: "apple", excludePatterns: [/root/], @@ -353,13 +353,13 @@ describe("findNodeApiModulePaths", () => { ]); }); - it("disregards parts futher up in filesystem when excluding", (context) => { + it("disregards parts futher up in filesystem when excluding", async (context) => { const tempDir = setupTempDirectory(context, { "node_modules/root.apple.node/react-native-node-api-module": "", "node_modules/child-dir/node_modules/dependency/lib.apple.node/react-native-node-api-module": "", }); - const result = findNodeApiModulePaths({ + const result = await findNodeApiModulePaths({ fromPath: path.join(tempDir, "node_modules"), platform: "apple", }); @@ -416,13 +416,16 @@ describe("findNodeAddonForBindings()", () => { }; for (const [name, relPath] of Object.entries(expectedPaths)) { - it(`should look for addons in common paths (${name} in "${relPath}")`, (context) => { + it(`should look for addons in common paths (${name} in "${relPath}")`, async (context) => { // Arrange const tempDirectoryPath = setupTempDirectory(context, { [relPath]: "// This is supposed to be a binary file", }); // Act - const actualPath = findNodeAddonForBindings(name, tempDirectoryPath); + const actualPath = await findNodeAddonForBindings( + name, + tempDirectoryPath + ); // Assert const expectedAbsPath = path.join(tempDirectoryPath, relPath); assert.equal(actualPath, expectedAbsPath); diff --git a/packages/host/src/node/path-utils.ts b/packages/host/src/node/path-utils.ts index 79addae9..fd352e34 100644 --- a/packages/host/src/node/path-utils.ts +++ b/packages/host/src/node/path-utils.ts @@ -302,13 +302,12 @@ export type FindNodeApiModuleOptions = { }; /** - * Recursively search into a directory for xcframeworks containing Node-API modules. - * TODO: Turn this asynchronous + * Recursively search into a directory for directories containing Node-API modules. */ -export function findNodeApiModulePaths( +export async function findNodeApiModulePaths( options: FindNodeApiModuleOptions, suffix = "" -): string[] { +): Promise { const { fromPath, platform, @@ -325,27 +324,33 @@ export function findNodeApiModulePaths( return []; } - return fs - .readdirSync(candidatePath, { withFileTypes: true }) - .flatMap((file) => { - if ( - file.isFile() && - file.name === MAGIC_FILENAME && - hasPlatformExtension(platform, candidatePath) - ) { - return [candidatePath]; - } else if (file.isDirectory()) { - // Traverse into the child directory - return findNodeApiModulePaths(options, path.join(suffix, file.name)); - } - return []; - }); + const result: string[] = []; + const pendingResults: Promise[] = []; + + for await (const dirent of await fs.promises.opendir(candidatePath)) { + if ( + dirent.isFile() && + dirent.name === MAGIC_FILENAME && + hasPlatformExtension(platform, candidatePath) + ) { + result.push(candidatePath); + } else if (dirent.isDirectory()) { + // Traverse into the child directory + // Pushing result into a list instead of awaiting immediately to parallelize the search + pendingResults.push( + findNodeApiModulePaths(options, path.join(suffix, dirent.name)) + ); + } + } + const childResults = await Promise.all(pendingResults); + result.push(...childResults.flatMap((filePath) => filePath)); + return result; } /** * Finds all dependencies of the app package and their xcframeworks. */ -export function findNodeApiModulePathsByDependency({ +export async function findNodeApiModulePathsByDependency({ fromPath, includeSelf, ...options @@ -360,25 +365,32 @@ export function findNodeApiModulePathsByDependency({ const { name } = readPackageSync({ cwd: packageRoot }); packagePathsByName[name] = packageRoot; } - // Find all their xcframeworks - return Object.fromEntries( - Object.entries(packagePathsByName) - .map(([dependencyName, dependencyPath]) => { + + // Find all their node api module paths + const resultEntries = await Promise.all( + Object.entries(packagePathsByName).map( + async ([dependencyName, dependencyPath]) => { // Make all the xcframeworks relative to the dependency path - const modulePaths = findNodeApiModulePaths({ + const absoluteModulePaths = await findNodeApiModulePaths({ fromPath: dependencyPath, ...options, - }).map((p) => path.relative(dependencyPath, p)); + }); return [ dependencyName, { path: dependencyPath, - modulePaths, + modulePaths: absoluteModulePaths.map((p) => + path.relative(dependencyPath, p) + ), }, ] as const; - }) - // Remove any dependencies without module paths - .filter(([, { modulePaths }]) => modulePaths.length > 0) + } + ) + ); + // Return an object by dependency name + return Object.fromEntries( + // Remove any dependencies without Node-API module paths + resultEntries.filter(([, { modulePaths }]) => modulePaths.length > 0) ); } From 44439036952d47e0eb37157fa7f8430b787bb442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 14 Jul 2025 17:34:12 +0200 Subject: [PATCH 3/4] Add changeset --- .changeset/three-pugs-relate.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/three-pugs-relate.md diff --git a/.changeset/three-pugs-relate.md b/.changeset/three-pugs-relate.md new file mode 100644 index 00000000..c36f75b9 --- /dev/null +++ b/.changeset/three-pugs-relate.md @@ -0,0 +1,5 @@ +--- +"react-native-node-api": patch +--- + +Fix hasDuplicateLibraryNames by filtering out node_modules in package rootse From a8f956368013cc33885180ad71149bdbd6ae8c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 14 Jul 2025 17:34:29 +0200 Subject: [PATCH 4/4] Commit change to package-lock.json --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65b9eeff..9baf8c3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ }, "apps/test-app": { "name": "react-native-node-api-test-app", - "version": "0.1.0", + "version": "0.1.1", "dependencies": { "@babel/core": "^7.26.10", "@babel/preset-env": "^7.26.9", @@ -13089,7 +13089,7 @@ } }, "packages/cmake-rn": { - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "@commander-js/extra-typings": "^13.1.0", "bufout": "^0.3.2", @@ -13097,7 +13097,7 @@ "cmake-js": "^7.3.1", "commander": "^13.1.0", "ora": "^8.2.0", - "react-native-node-api": "0.3.0" + "react-native-node-api": "0.3.1" }, "bin": { "cmake-rn": "bin/cmake-rn.js" @@ -13322,7 +13322,7 @@ }, "packages/ferric": { "name": "ferric-cli", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "@commander-js/extra-typings": "^13.1.0", "@napi-rs/cli": "3.0.0-alpha.89", @@ -13330,7 +13330,7 @@ "chalk": "^5.4.1", "commander": "^13.1.0", "ora": "^8.2.0", - "react-native-node-api": "0.3.0" + "react-native-node-api": "0.3.1" }, "bin": { "ferric": "bin/ferric.js" @@ -13586,7 +13586,7 @@ }, "packages/host": { "name": "react-native-node-api", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { "@commander-js/extra-typings": "^13.1.0",