diff --git a/packages/host/src/node/path-utils.test.ts b/packages/host/src/node/path-utils.test.ts index 08a788ff..96c44120 100644 --- a/packages/host/src/node/path-utils.test.ts +++ b/packages/host/src/node/path-utils.test.ts @@ -7,6 +7,7 @@ import fswin from "fswin"; import { determineModuleContext, findNodeApiModulePaths, + findNodeAddonForBindings, findPackageDependencyPaths, getLibraryName, isNodeApiModule, @@ -372,3 +373,30 @@ describe("determineModuleContext", () => { assert.equal(readCount, 1); }); }); + +describe("findNodeAddonForBindings()", () => { + const expectedPaths = { + "addon_1": "addon_1.node", + "addon_2": "build/Release/addon_2.node", + "addon_3": "build/Debug/addon_3.node", + "addon_4": "build/addon_4.node", + "addon_5": "out/Release/addon_5.node", + "addon_6": "out/Debug/addon_6.node", + "addon_7": "Release/addon_7.node", + "addon_8": "Debug/addon_8.node", + }; + + for (const [name, relPath] of Object.entries(expectedPaths)) { + it(`should look for addons in common paths (${name} in "${relPath}")`, (context) => { + // Arrange + const tempDirectoryPath = setupTempDirectory(context, { + [relPath]: "// This is supposed to be a binary file", + }); + // Act + const actualPath = 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 179688ce..6b2d8ed4 100644 --- a/packages/host/src/node/path-utils.ts +++ b/packages/host/src/node/path-utils.ts @@ -31,6 +31,15 @@ const packageNameCache = new Map(); * TODO: Consider checking for a specific platform extension. */ export function isNodeApiModule(modulePath: string): boolean { + { + // HACK: Take a shortcut (if applicable): existing `.node` files are addons + try { + fs.accessSync(modulePath.endsWith(".node") ? modulePath : `${modulePath}.node`); + return true; + } catch { + // intentionally left empty + } + } const dir = path.dirname(modulePath); const baseName = path.basename(modulePath, ".node"); let entries: string[]; @@ -385,3 +394,29 @@ export function getLatestMtime(fromPath: string): number { return latest; } + +// NOTE: List of paths influenced by `node-bindings` itself +// https://github.com/TooTallNate/node-bindings/blob/v1.3.0/bindings.js#L21 +const nodeBindingsSubdirs = [ + "./", + "./build/Release", + "./build/Debug", + "./build", + "./out/Release", + "./out/Debug", + "./Release", + "./Debug", +]; + +export function findNodeAddonForBindings(id: string, fromDir: string) { + const idWithExt = id.endsWith(".node") ? id : `${id}.node`; + // Support traversing the filesystem to find the Node-API module. + // Currently, we check the most common directories like `bindings` does. + for (const subdir of nodeBindingsSubdirs) { + const resolvedPath = path.join(fromDir, subdir, idWithExt); + if (isNodeApiModule(resolvedPath)) { + return resolvedPath; + } + } + return undefined; +}