From 20aba81dd23e10921cc298875c9a2db73b128a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 12 May 2025 09:43:17 +0200 Subject: [PATCH 1/5] Use ".apple.node" instead of ".xcframeworks" --- packages/ferric-example/.gitignore | 1 + packages/ferric/src/build.ts | 13 +++++++++++-- .../react-native-node-api-cmake/src/cli.ts | 12 ++++++++++-- .../package.json | 2 +- .../src/node/path-utils.ts | 3 +-- .../src/node/prebuilds/apple.ts | 19 ++++++++++++++++--- 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/ferric-example/.gitignore b/packages/ferric-example/.gitignore index c73e3a58..31c8570c 100644 --- a/packages/ferric-example/.gitignore +++ b/packages/ferric-example/.gitignore @@ -2,6 +2,7 @@ Cargo.lock /*.xcframework/ +/*.apple.node/ /*.android.node/ # Generated files diff --git a/packages/ferric/src/build.ts b/packages/ferric/src/build.ts index 739c049e..c07ee9aa 100644 --- a/packages/ferric/src/build.ts +++ b/packages/ferric/src/build.ts @@ -66,6 +66,11 @@ const ndkVersionOption = new Option( "--ndk-version ", "The NDK version to use for Android builds" ).default(DEFAULT_NDK_VERSION); +const xcframeworkExtensionOption = new Option( + "--xcframework-extension", + "Don't rename the xcframework to .apple.node" +).default(false); + const outputPathOption = new Option( "--output ", "Writing outputs to this directory" @@ -85,6 +90,7 @@ export const buildCommand = new Command("build") .addOption(ndkVersionOption) .addOption(outputPathOption) .addOption(configurationOption) + .addOption(xcframeworkExtensionOption) .action( async ({ target: targetArg, @@ -93,6 +99,7 @@ export const buildCommand = new Command("build") ndkVersion, output: outputPath, configuration, + xcframeworkExtension, }) => { try { const targets = new Set([...targetArg]); @@ -221,8 +228,10 @@ export const buildCommand = new Command("build") if (appleLibraries.length > 0) { const libraryPaths = await combineLibraries(appleLibraries); const frameworkPaths = libraryPaths.map(createAppleFramework); - const xcframeworkFilename = - determineXCFrameworkFilename(frameworkPaths); + const xcframeworkFilename = determineXCFrameworkFilename( + frameworkPaths, + xcframeworkExtension ? ".xcframework" : ".apple.node" + ); // Create the xcframework const xcframeworkOutputPath = path.resolve( diff --git a/packages/react-native-node-api-cmake/src/cli.ts b/packages/react-native-node-api-cmake/src/cli.ts index 5a58b736..4465366c 100644 --- a/packages/react-native-node-api-cmake/src/cli.ts +++ b/packages/react-native-node-api-cmake/src/cli.ts @@ -91,6 +91,11 @@ const noWeakNodeApiLinkageOption = new Option( "Don't pass the path of the weak-node-api library from react-native-node-api-modules" ); +const xcframeworkExtensionOption = new Option( + "--xcframework-extension", + "Don't rename the xcframework to .apple.node" +).default(false); + export const program = new Command("react-native-node-api-cmake") .description("Build React Native Node API modules with CMake") .addOption(sourcePathOption) @@ -104,6 +109,7 @@ export const program = new Command("react-native-node-api-cmake") .addOption(ndkVersionOption) .addOption(noAutoLinkOption) .addOption(noWeakNodeApiLinkageOption) + .addOption(xcframeworkExtensionOption) .action(async ({ triplet: tripletValues, ...globalContext }) => { try { const buildPath = getBuildPath(globalContext); @@ -212,8 +218,10 @@ export const program = new Command("react-native-node-api-cmake") }) ); const frameworkPaths = libraryPaths.map(createAppleFramework); - const xcframeworkFilename = - determineXCFrameworkFilename(frameworkPaths); + const xcframeworkFilename = determineXCFrameworkFilename( + frameworkPaths, + globalContext.xcframeworkExtension ? ".xcframework" : ".apple.node" + ); // Create the xcframework const xcframeworkOutputPath = path.resolve( diff --git a/packages/react-native-node-api-modules/package.json b/packages/react-native-node-api-modules/package.json index 8bdc38f2..425ec03c 100644 --- a/packages/react-native-node-api-modules/package.json +++ b/packages/react-native-node-api-modules/package.json @@ -42,7 +42,7 @@ "copy-node-api-headers": "tsx scripts/copy-node-api-headers.ts", "generate-weak-node-api": "tsx scripts/generate-weak-node-api.ts", "generate-weak-node-api-injector": "tsx scripts/generate-weak-node-api-injector.ts", - "build-weak-node-api": "npm run generate-weak-node-api && react-native-node-api-cmake --android --apple --no-auto-link --no-weak-node-api-linkage --source ./weak-node-api", + "build-weak-node-api": "npm run generate-weak-node-api && react-native-node-api-cmake --android --apple --no-auto-link --no-weak-node-api-linkage --xcframework-extension --source ./weak-node-api", "test": "tsx --test src/node/**/*.test.ts src/node/*.test.ts" }, "keywords": [ diff --git a/packages/react-native-node-api-modules/src/node/path-utils.ts b/packages/react-native-node-api-modules/src/node/path-utils.ts index 2eb67a18..afbe3f7a 100644 --- a/packages/react-native-node-api-modules/src/node/path-utils.ts +++ b/packages/react-native-node-api-modules/src/node/path-utils.ts @@ -13,8 +13,7 @@ export type PlatformName = "android" | "apple"; export const PLATFORM_EXTENSIONS = { android: ".android.node", - // TODO: Change to .apple.node - apple: ".xcframework", + apple: ".apple.node", } as const satisfies Record; export type PlatformExtentions = (typeof PLATFORM_EXTENSIONS)[PlatformName]; diff --git a/packages/react-native-node-api-modules/src/node/prebuilds/apple.ts b/packages/react-native-node-api-modules/src/node/prebuilds/apple.ts index 3e9455ce..769f7857 100644 --- a/packages/react-native-node-api-modules/src/node/prebuilds/apple.ts +++ b/packages/react-native-node-api-modules/src/node/prebuilds/apple.ts @@ -95,18 +95,28 @@ export async function createXCframework({ // Ideally, it would only be necessary to delete the specific platform+arch, to allow selectively building from source. fs.rmSync(outputPath, { recursive: true, force: true }); + // Xcodebuild requires the output path to end with ".xcframework" + const xcodeOutputPath = + path.extname(outputPath) === ".xcframework" + ? outputPath + : `${outputPath}.xcframework`; + await spawn( "xcodebuild", [ "-create-xcframework", ...frameworkPaths.flatMap((p) => ["-framework", p]), "-output", - outputPath, + xcodeOutputPath, ], { outputMode: "buffered", } ); + if (xcodeOutputPath !== outputPath) { + // Rename the xcframework to the original output path + await fs.promises.rename(xcodeOutputPath, outputPath); + } if (autoLink) { // Write a file to mark the xcframework is a Node-API module // TODO: Consider including this in the Info.plist file instead @@ -122,9 +132,12 @@ export async function createXCframework({ * Determine the filename of the xcframework based on the framework paths. * Ensuring that all framework paths have the same base name. */ -export function determineXCFrameworkFilename(frameworkPaths: string[]) { +export function determineXCFrameworkFilename( + frameworkPaths: string[], + extension: ".xcframework" | ".apple.node" = ".xcframework" +) { const name = determineLibraryBasename(frameworkPaths); - return `${name}.xcframework`; + return `${name}${extension}`; } export async function createUniversalAppleLibrary(libraryPaths: string[]) { From e7364fe8c83e858cb4a1d6282e5c38df3aae5641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 12 May 2025 11:14:47 +0200 Subject: [PATCH 2/5] Delete unused replaceWithNodeExtension --- .../src/node/path-utils.test.ts | 14 +------------- .../src/node/path-utils.ts | 11 ----------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/packages/react-native-node-api-modules/src/node/path-utils.test.ts b/packages/react-native-node-api-modules/src/node/path-utils.test.ts index 6ead343e..ef80a342 100644 --- a/packages/react-native-node-api-modules/src/node/path-utils.test.ts +++ b/packages/react-native-node-api-modules/src/node/path-utils.test.ts @@ -9,7 +9,6 @@ import { findPackageDependencyPaths, getLibraryName, isNodeApiModule, - replaceWithNodeExtension, stripExtension, } from "./path-utils.js"; import { setupTempDirectory } from "./test-utils.js"; @@ -98,18 +97,7 @@ describe("stripExtension", () => { assert.equal(stripExtension("./addon.node"), "./addon"); assert.equal(stripExtension("./addon.android.node"), "./addon"); // assert.equal(stripExtension("./addon.apple.node"), "./addon"); - assert.equal(stripExtension("./addon.xcframework"), "./addon"); - }); -}); - -describe("replaceExtensionWithNode", () => { - it("replaces extension with .node", () => { - assert.equal(replaceWithNodeExtension("./addon"), "./addon.node"); - assert.equal(replaceWithNodeExtension("./addon.node"), "./addon.node"); - assert.equal( - replaceWithNodeExtension("./addon.xcframework"), - "./addon.node" - ); + assert.equal(stripExtension("./addon.apple.node"), "./addon"); }); }); diff --git a/packages/react-native-node-api-modules/src/node/path-utils.ts b/packages/react-native-node-api-modules/src/node/path-utils.ts index afbe3f7a..6c99e176 100644 --- a/packages/react-native-node-api-modules/src/node/path-utils.ts +++ b/packages/react-native-node-api-modules/src/node/path-utils.ts @@ -70,17 +70,6 @@ export function stripExtension(modulePath: string) { ); } -/** - * Replaces any platform specific extensions with the common .node extension. - */ -export function replaceWithNodeExtension(modulePath: string) { - return path.format({ - ...path.parse(modulePath), - base: undefined, - ext: ".node", - }); -} - export type ModuleContext = { packageName: string; relativePath: string; From baea882756ca34f92d9e1469b56ff057697c8d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 12 May 2025 11:14:55 +0200 Subject: [PATCH 3/5] Fix tests --- .../src/node/babel-plugin/plugin.test.ts | 4 +- .../src/node/path-utils.test.ts | 59 +++++++++---------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/packages/react-native-node-api-modules/src/node/babel-plugin/plugin.test.ts b/packages/react-native-node-api-modules/src/node/babel-plugin/plugin.test.ts index d8fbcf01..73e884aa 100644 --- a/packages/react-native-node-api-modules/src/node/babel-plugin/plugin.test.ts +++ b/packages/react-native-node-api-modules/src/node/babel-plugin/plugin.test.ts @@ -12,9 +12,9 @@ describe("plugin", () => { it("transforms require calls, regardless", (context) => { const tempDirectoryPath = setupTempDirectory(context, { "package.json": `{ "name": "my-package" }`, - "addon-1.xcframework/addon-1.node": + "addon-1.apple.node/addon-1.node": "// This is supposed to be a binary file", - "addon-2.xcframework/addon-2.node": + "addon-2.apple.node/addon-2.node": "// This is supposed to be a binary file", "addon-1.js": ` const addon = require('./addon-1.node'); diff --git a/packages/react-native-node-api-modules/src/node/path-utils.test.ts b/packages/react-native-node-api-modules/src/node/path-utils.test.ts index ef80a342..fcd95ad3 100644 --- a/packages/react-native-node-api-modules/src/node/path-utils.test.ts +++ b/packages/react-native-node-api-modules/src/node/path-utils.test.ts @@ -16,8 +16,7 @@ import { setupTempDirectory } from "./test-utils.js"; describe("isNodeApiModule", () => { it("returns true for .node", (context) => { const tempDirectoryPath = setupTempDirectory(context, { - "addon.xcframework/addon.node": - "// This is supposted to be a binary file", + "addon.apple.node/addon.node": "// This is supposted to be a binary file", }); assert(isNodeApiModule(path.join(tempDirectoryPath, "addon"))); @@ -63,9 +62,9 @@ describe("isNodeApiModule", () => { assert.equal(isNodeApiModule(fakePath), false); }); - it("recognize .xcframeworks", (context) => { + it("recognize .apple.node directories", (context) => { const tempDirectoryPath = setupTempDirectory(context, { - "addon.xcframework/addon.node": "// This is supposed to be a binary file", + "addon.apple.node/addon.node": "// This is supposed to be a binary file", }); assert.equal(isNodeApiModule(path.join(tempDirectoryPath, "addon")), true); assert.equal( @@ -78,7 +77,7 @@ describe("isNodeApiModule", () => { it("throws when one module unreadable but another readable", (context) => { const tempDirectoryPath = setupTempDirectory(context, { "addon.android.node": "", - "addon.xcframework": "", + "addon.apple.node": "", }); const unreadable = path.join(tempDirectoryPath, "addon.android.node"); // only android module is unreadable @@ -160,8 +159,8 @@ describe("getLibraryName", () => { it("works when including relative path", (context) => { const tempDirectoryPath = setupTempDirectory(context, { "package.json": `{ "name": "my-package" }`, - "addon.xcframework/addon.node": "// This is supposed to be a binary file", - "sub-directory/addon.xcframework/addon.node": + "addon.apple.node/addon.node": "// This is supposed to be a binary file", + "sub-directory/addon.apple.node/addon.node": "// This is supposed to be a binary file", }); assert.equal( @@ -182,8 +181,8 @@ describe("getLibraryName", () => { it("works when stripping relative path", (context) => { const tempDirectoryPath = setupTempDirectory(context, { "package.json": `{ "name": "my-package" }`, - "addon.xcframework/addon.node": "// This is supposed to be a binary file", - "sub-directory/addon.xcframework/addon.node": + "addon.apple.node/addon.node": "// This is supposed to be a binary file", + "sub-directory/addon.apple.node/addon.node": "// This is supposed to be a binary file", }); assert.equal( @@ -237,28 +236,28 @@ describe("findPackageDependencyPaths", () => { }); describe("findNodeApiModulePaths", () => { - it("should find xcframework paths", (context) => { + it("should find .apple.node paths", (context) => { const tempDir = setupTempDirectory(context, { - "root.xcframework/react-native-node-api-module": "", - "sub-directory/lib-a.xcframework/react-native-node-api-module": "", - "sub-directory/lib-b.xcframework/react-native-node-api-module": "", + "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({ fromPath: tempDir, platform: "apple", }); assert.deepEqual(result.sort(), [ - path.join(tempDir, "root.xcframework"), - path.join(tempDir, "sub-directory/lib-a.xcframework"), - path.join(tempDir, "sub-directory/lib-b.xcframework"), + path.join(tempDir, "root.apple.node"), + path.join(tempDir, "sub-directory/lib-a.apple.node"), + path.join(tempDir, "sub-directory/lib-b.apple.node"), ]); }); it("respects default exclude patterns", (context) => { const tempDir = setupTempDirectory(context, { - "root.xcframework/react-native-node-api-module": "", - "child-dir/dependency/lib.xcframework/react-native-node-api-module": "", - "child-dir/node_modules/dependency/lib.xcframework/react-native-node-api-module": + "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({ @@ -266,16 +265,16 @@ describe("findNodeApiModulePaths", () => { platform: "apple", }); assert.deepEqual(result.sort(), [ - path.join(tempDir, "child-dir/dependency/lib.xcframework"), - path.join(tempDir, "root.xcframework"), + path.join(tempDir, "child-dir/dependency/lib.apple.node"), + path.join(tempDir, "root.apple.node"), ]); }); it("respects explicit exclude patterns", (context) => { const tempDir = setupTempDirectory(context, { - "root.xcframework/react-native-node-api-module": "", - "child-dir/dependency/lib.xcframework/react-native-node-api-module": "", - "child-dir/node_modules/dependency/lib.xcframework/react-native-node-api-module": + "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({ @@ -284,15 +283,15 @@ describe("findNodeApiModulePaths", () => { excludePatterns: [/root/], }); assert.deepEqual(result.sort(), [ - path.join(tempDir, "child-dir/dependency/lib.xcframework"), - path.join(tempDir, "child-dir/node_modules/dependency/lib.xcframework"), + path.join(tempDir, "child-dir/dependency/lib.apple.node"), + path.join(tempDir, "child-dir/node_modules/dependency/lib.apple.node"), ]); }); it("disregards parts futher up in filesystem when excluding", (context) => { const tempDir = setupTempDirectory(context, { - "node_modules/root.xcframework/react-native-node-api-module": "", - "node_modules/child-dir/node_modules/dependency/lib.xcframework/react-native-node-api-module": + "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({ @@ -300,7 +299,7 @@ describe("findNodeApiModulePaths", () => { platform: "apple", }); assert.deepEqual(result, [ - path.join(tempDir, "node_modules/root.xcframework"), + path.join(tempDir, "node_modules/root.apple.node"), ]); }); }); @@ -311,7 +310,7 @@ describe("determineModuleContext", () => { "package.json": `{ "name": "cached-pkg" }`, "subdir1/file1.node": "", "subdir2/file2.node": "", - "subdir1/file1.xcframework": "", + "subdir1/file1.apple.node": "", }); let readCount = 0; const orig = fs.readFileSync; From f608f14804dee102d60d9fcd38a4e68a8d8e093d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 12 May 2025 12:01:54 +0200 Subject: [PATCH 4/5] Update and add documentation --- docs/ANDROID.md | 6 +++++- docs/AUTO-LINKING.md | 27 +++++++++++++++++++++++++++ docs/PREBUILDS.md | 33 +++++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 docs/AUTO-LINKING.md diff --git a/docs/ANDROID.md b/docs/ANDROID.md index c75e5caa..2ddca25f 100644 --- a/docs/ANDROID.md +++ b/docs/ANDROID.md @@ -1,4 +1,6 @@ -# Building Hermes from source +# Android support + +## Building Hermes from source Because we're using a version of Hermes patched with Node-API support, we need to build React Native from source. @@ -8,6 +10,8 @@ export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api-modules vendo ## Cleaning your React Native build folders +If you've accidentally built your app without Hermes patched, you can clean things up by deleting the `ReactAndroid` build folder. + ``` rm -rf node_modules/react-native/ReactAndroid/build ``` diff --git a/docs/AUTO-LINKING.md b/docs/AUTO-LINKING.md new file mode 100644 index 00000000..5cfdb3fc --- /dev/null +++ b/docs/AUTO-LINKING.md @@ -0,0 +1,27 @@ +# Auto-linking + +The `react-native-node-api-modules` package (sometimes referred to as "the host package") has mechanisms to automatically find and link prebuilt binaries with Node-API modules. + +When auto-linking, prebuilt binaries are copied (sometimes referred to as vendored) from dependencies of the app into the host package. As they're copied, they get renamed to avoid conflicts in naming as the library files across multiple dependency packages will be sharing a namespace when building the app. + +## Naming scheme of libraries when linked into the host + +The name of the library when linked / copied into the host is based on two things: + +- The package name of the encapsulating package: The directory tree is walked from the original library path to the nearest `package.json` (this is the Node-API module's package root). +- The relative path of the library to the package root: + - Normalized (any "lib" prefix or file extension is stripped from the filename). + - Escaped (any non-alphanumeric character is replaced with "-"). + +## How do I link Node-API module libraries into my app? + +Linking will run when you `pod install` and as part of building your app with Gradle as long as your app has a dependency on the `react-native-node-api-modules` package. + +You can also manually link by running the following in your app directory: + +```bash +npx react-native-node-api-modules link --android --apple +``` + +> [!NOTE] +> Because vendored frameworks must be present when running `pod install`, you have to run `pod install` if you add or remove a dependency with a Node-API module (or after creation if you're doing active development on it). diff --git a/docs/PREBUILDS.md b/docs/PREBUILDS.md index 511fec26..4930ac91 100644 --- a/docs/PREBUILDS.md +++ b/docs/PREBUILDS.md @@ -1,17 +1,38 @@ # Prebuilds -This document will codify the naming and directory structure of prebuilt binaries, expected by the `react-native-node-api-modules` package and tools. +This document codifies the naming and directory structure of prebuilt binaries, expected by the auto-linking mechanism. -To align with prior art and established patterns around the distribution of Node-API modules for Node.js (and other engines supporting this), +At the time of writing, our auto-linking host package (`react-native-node-api-modules`) support two kinds of prebuilds: -## Apple: XCFrameworks of dynamic libraries in frameworks +## `*.android.node` (for Android) + +A jniLibs-like directory structure of CPU-architecture specific directories containing a single `.so` library file. + +The name of all the `.so` library files: + +- must be the same across all CPU-architectures +- can have a "lib" prefix, but doesn't have to +- must have an `.so` or `.node` file extension + +> [!NOTE] +> The `SONAME` doesn't have to match and is not updated as the .so is copied into the host package. +> This might cause trouble if you're trying to link with the library from other native code. +> We're tracking [#14](https://github.com/callstackincubator/react-native-node-api-modules/issues/14) to fix this 🤞 + +The directory must have a `react-native-node-api-module` file (the content doesn't matter), to signal that the directory is intended for auto-linking by the `react-native-node-api-module` package. + +## `*.apple.node` (for Apple) + +An XCFramework of dynamic libraries wrapped in `.framework` bundles, renamed from `.xcframework` to `.apple.node` to ease discoverability. The Apple Developer documentation on ["Creating a multiplatform binary framework bundle"](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle#Avoid-issues-when-using-alternate-build-systems) mentions: > An XCFramework can include dynamic library files, but only macOS supports these libraries for dynamic linking. Dynamic linking on iOS, watchOS, and tvOS requires the XCFramework to contain .framework bundles. - +The directory must have a `react-native-node-api-module` file (the content doesn't matter), to signal that the directory is intended for auto-linking by the `react-native-node-api-module` package. + +## Why did we choose this naming scheme? -## Android: Directory of architecture specific directories of shared object library files. +To align with prior art and established patterns around the distribution of Node-API modules for Node.js, we've chosen to use the ".node" filename extension for prebuilds of Node-API modules, targeting React Native. - +To enable distribution of packages with multiple co-existing platform-specific prebuilts, we've chosen to lean into the pattern of platform-specific filename extensions, used by the Metro bundler. From a61f9fa3ca767eabdff7c31be040388f02881492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 18 May 2025 00:06:37 +0200 Subject: [PATCH 5/5] Fix verify script --- .../scripts/verify-prebuilds.mts | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/node-addon-examples/scripts/verify-prebuilds.mts b/packages/node-addon-examples/scripts/verify-prebuilds.mts index 58868e3d..666eeba3 100644 --- a/packages/node-addon-examples/scripts/verify-prebuilds.mts +++ b/packages/node-addon-examples/scripts/verify-prebuilds.mts @@ -16,7 +16,13 @@ const EXPECTED_XCFRAMEWORK_PLATFORMS = [ "xros-arm64-simulator", ]; -async function verifyAndroidDirectory(dirent: fs.Dirent) { +async function verifyAndroidPrebuild(dirent: fs.Dirent) { + console.log( + "Verifying Android prebuild", + dirent.name, + "in", + dirent.parentPath + ); for (const arch of EXPECTED_ANDROID_ARCHS) { const archDir = path.join(dirent.parentPath, dirent.name, arch); for (const file of await fs.promises.readdir(archDir, { @@ -31,7 +37,8 @@ async function verifyAndroidDirectory(dirent: fs.Dirent) { } } -async function verifyXcframework(dirent: fs.Dirent) { +async function verifyApplePrebuild(dirent: fs.Dirent) { + console.log("Verifying Apple prebuild", dirent.name, "in", dirent.parentPath); for (const arch of EXPECTED_XCFRAMEWORK_PLATFORMS) { const archDir = path.join(dirent.parentPath, dirent.name, arch); for (const file of await fs.promises.readdir(archDir, { @@ -75,16 +82,17 @@ async function verifyXcframework(dirent: fs.Dirent) { } } -for (const dirent of await fs.promises.readdir(EXAMPLES_DIR, { +for await (const dirent of fs.promises.glob("**/*.*.node", { + cwd: EXAMPLES_DIR, withFileTypes: true, - recursive: true, })) { - if (!dirent.isDirectory()) { - continue; - } if (dirent.name.endsWith(".android.node")) { - await verifyAndroidDirectory(dirent); - } else if (dirent.name.endsWith(".xcframework")) { - await verifyXcframework(dirent); + await verifyAndroidPrebuild(dirent); + } else if (dirent.name.endsWith(".apple.node")) { + await verifyApplePrebuild(dirent); + } else { + throw new Error( + `Unexpected prebuild file: ${dirent.name} in ${dirent.parentPath}` + ); } }