diff --git a/package-lock.json b/package-lock.json index 0b4f113c..980e5e7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -137,7 +137,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2334,6 +2333,16 @@ "tslib": "^2.4.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", @@ -4832,7 +4841,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.4.tgz", "integrity": "sha512-jOT8V1Ba5BdC79sKrRWDdMT5l1R+XNHTPR6CPWzUP2EcfAcvIHZWF0eAbmRcpOOP5gVIwnqNg0C4nvh6Abc3OA==", "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -6017,7 +6025,6 @@ "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.81.4.tgz", "integrity": "sha512-aEXhRMsz6yN5X63Zk+cdKByQ0j3dsKv+ETRP9lLARdZ82fBOCMuK6IfmZMwK3A/3bI7gSvt2MFPn3QHy3WnByw==", "license": "MIT", - "peer": true, "dependencies": { "@react-native/js-polyfills": "0.81.4", "@react-native/metro-babel-transformer": "0.81.4", @@ -6641,7 +6648,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -6735,7 +6741,6 @@ "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/types": "8.44.0", @@ -7076,7 +7081,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7670,7 +7674,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -9071,7 +9074,6 @@ "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -12264,8 +12266,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.5.0.tgz", "integrity": "sha512-Yi/FgnN8IU/Cd6KeLxyHkylBUvDTsSScT0Tna2zTrz8klmc8qF2ppj6Q1LHsmOueJWhigQwR4cO2p0XBGW5IaQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-int64": { "version": "0.4.0", @@ -13115,7 +13116,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -13162,7 +13162,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.4.tgz", "integrity": "sha512-bt5bz3A/+Cv46KcjV0VQa+fo7MKxs17RCcpzjftINlen4ZDUl0I6Ut+brQ2FToa5oD0IB0xvQHfmsg2EDqsZdQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.4", @@ -14600,7 +14599,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15179,7 +15177,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", "license": "MIT", - "peer": true, "engines": { "node": ">=20" } diff --git a/packages/host/src/node/path-utils.ts b/packages/host/src/node/path-utils.ts index a2a954a6..f20c655d 100644 --- a/packages/host/src/node/path-utils.ts +++ b/packages/host/src/node/path-utils.ts @@ -4,6 +4,7 @@ import fs from "node:fs"; import { packageDirectorySync } from "pkg-dir"; import { readPackageSync } from "read-pkg"; import { createRequire } from "node:module"; +import * as zod from "zod"; import { chalk, prettyPath } from "@react-native-node-api/cli-utils"; @@ -294,9 +295,49 @@ export function visualizeLibraryMap(libraryMap: LibraryMap) { return result.join("\n"); } +export const ReactNativeNodeAPIConfigurationSchema = zod.object({ + reactNativeNodeApi: zod + .object({ + scan: zod + .object({ + dependencies: zod.array(zod.string()).optional(), + }) + .optional(), + }) + .optional(), +}); + +export const PackageJsonDependenciesSchema = zod.object({ + dependencies: zod.record(zod.string(), zod.string()).optional(), +}); + +export type ReactNativeNodeAPIConfiguration = zod.infer< + typeof ReactNativeNodeAPIConfigurationSchema +>; +export type PackageJsonDependencies = zod.infer< + typeof PackageJsonDependenciesSchema +>; + +type PackageJsonWithNodeApi = PackageJsonDependencies & + ReactNativeNodeAPIConfiguration; + +export function findPackageConfigurationByPath( + fromPath: string, +): ReactNativeNodeAPIConfiguration { + const packageRoot = packageDirectorySync({ cwd: fromPath }); + assert(packageRoot, `Could not find package root from ${fromPath}`); + + const packageJson = readPackageSync({ + cwd: packageRoot, + }); + + return ReactNativeNodeAPIConfigurationSchema.parse(packageJson); +} + /** * Search upwards from a directory to find a package.json and - * return a record mapping from each dependencies of that package to their path on disk. + * return a record mapping from each dependency of that package to their path on disk. + * Also checks all dependencies from reactNativeNodeApi field in dependencies package.json */ export function findPackageDependencyPaths( fromPath: string, @@ -304,21 +345,40 @@ export function findPackageDependencyPaths( const packageRoot = packageDirectorySync({ cwd: fromPath }); assert(packageRoot, `Could not find package root from ${fromPath}`); - const requireFromPackageRoot = createRequire( - path.join(packageRoot, "noop.js"), - ); + const requireFromRoot = createRequire(path.join(packageRoot, "noop.js")); + + const packageJson = readPackageSync({ + cwd: packageRoot, + }) as PackageJsonWithNodeApi; - const { dependencies = {} } = readPackageSync({ cwd: packageRoot }); + const { dependencies = {} } = + PackageJsonDependenciesSchema.parse(packageJson); + const { reactNativeNodeApi } = + ReactNativeNodeAPIConfigurationSchema.parse(packageJson); + + const initialDeps = Object.keys(dependencies).concat( + reactNativeNodeApi?.scan?.dependencies ?? [], + ); return Object.fromEntries( - Object.keys(dependencies).flatMap((dependencyName) => { - const resolvedDependencyRoot = resolvePackageRoot( - requireFromPackageRoot, - dependencyName, - ); - return resolvedDependencyRoot - ? [[dependencyName, resolvedDependencyRoot]] - : []; + initialDeps.flatMap((name) => { + const root = resolvePackageRoot(requireFromRoot, name); + if (!root) return []; + + const nested = + findPackageConfigurationByPath(root)?.reactNativeNodeApi?.scan + ?.dependencies ?? []; + + const nestedEntries = nested + .map((nestedName) => { + const nestedRoot = resolvePackageRoot(requireFromRoot, nestedName); + return nestedRoot + ? ([nestedName, nestedRoot] as [string, string]) + : null; + }) + .filter((entry): entry is [string, string] => entry !== null); + + return [[name, root] as [string, string], ...nestedEntries]; }), ); }