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 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", 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 a5bc0bc0..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,14 +317,15 @@ 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": "", "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", }); @@ -334,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/], @@ -352,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", }); @@ -415,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 bcbcf181..fd352e34 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( @@ -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) ); }