From 3dee815f21c68b5f381b9249c98af334ac685ae5 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Wed, 25 Feb 2026 13:11:34 +0100 Subject: [PATCH 1/2] fix: entry point resolver hijacking relative imports --- packages/metro/src/resolvers/resolver.ts | 59 +++++++++++------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/packages/metro/src/resolvers/resolver.ts b/packages/metro/src/resolvers/resolver.ts index a46cc1f..0593d23 100644 --- a/packages/metro/src/resolvers/resolver.ts +++ b/packages/metro/src/resolvers/resolver.ts @@ -1,50 +1,43 @@ import type { MetroConfig } from '@react-native/metro-config'; import type { Config as HarnessConfig } from '@react-native-harness/config'; +import path from 'node:path'; import { createHarnessResolver } from './composite-resolver'; import { createTsConfigResolver } from './tsconfig-resolver'; import type { HarnessResolver, MetroResolver } from './types'; -export const createHarnessEntryPointResolver = ( - harnessConfig: HarnessConfig -): HarnessResolver => { - // Can be relative to the project root or absolute, need to normalize it - const resolvedEntryPointPath = require.resolve(harnessConfig.entryPoint, { - paths: [process.cwd()], - }); +// Safely resolves a path and strips its extension +const getExtensionlessAbsolutePath = (basePath: string, relativePath = ''): string => { + const fullPath = path.resolve(basePath, relativePath); + const parsed = path.parse(fullPath); + return path.join(parsed.dir, parsed.name); +} - return (_context, moduleName, _platform) => { - if (moduleName === resolvedEntryPointPath) { - return { - type: 'sourceFile', - filePath: require.resolve('@react-native-harness/runtime/entry-point'), - }; +export const createHarnessEntryPointResolver = (harnessConfig: HarnessConfig): HarnessResolver => { + const rootPath = path.resolve(process.cwd()); + const expectedEntryPoint = getExtensionlessAbsolutePath(rootPath, harnessConfig.entryPoint); + const resolvedHarnessPath = require.resolve('@react-native-harness/runtime/entry-point'); + + return (context, moduleName, _platform) => { + // 1. Resolve the origin path of the file making the import + const currentOrigin = path.resolve(context.originModulePath); + + // Fast Fail: If the import isn't happening from the root directory, skip it immediately + if (currentOrigin !== rootPath) { + return null; } - if (moduleName === harnessConfig.entryPoint) { + // 2. Resolve the module being imported and strip its extension + // This safely normalizes './index', './index.js', 'index.js', etc. + const requestedModule = getExtensionlessAbsolutePath(currentOrigin, moduleName); + + // 3. String comparison + if (requestedModule === expectedEntryPoint) { return { type: 'sourceFile', - filePath: require.resolve('@react-native-harness/runtime/entry-point'), + filePath: resolvedHarnessPath, }; } - if (typeof moduleName === 'string') { - try { - const resolvedModuleName = require.resolve(moduleName, { - paths: [process.cwd()], - }); - if (resolvedModuleName === resolvedEntryPointPath) { - return { - type: 'sourceFile', - filePath: require.resolve( - '@react-native-harness/runtime/entry-point' - ), - }; - } - } catch { - // Ignore and fall through - } - } - return null; }; }; From b38a1359617260295d5c27edda8220db22460ccd Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Wed, 25 Feb 2026 13:13:19 +0100 Subject: [PATCH 2/2] chore: add version plan --- .nx/version-plans/version-plan-1772021508023.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .nx/version-plans/version-plan-1772021508023.md diff --git a/.nx/version-plans/version-plan-1772021508023.md b/.nx/version-plans/version-plan-1772021508023.md new file mode 100644 index 0000000..cf48f70 --- /dev/null +++ b/.nx/version-plans/version-plan-1772021508023.md @@ -0,0 +1,5 @@ +--- +'@react-native-harness/metro': prerelease +--- + +Rewrites the implementation of the entry point resolver so it no longer mistakenly hijacks relative imports that originate from third-party packages instead of the root directory.