diff --git a/packages/bundler-metro/babel-transformer.cjs b/packages/bundler-metro/babel-transformer.cjs new file mode 100644 index 00000000..1d988855 --- /dev/null +++ b/packages/bundler-metro/babel-transformer.cjs @@ -0,0 +1,24 @@ +const { rnHarnessPlugins } = require('@react-native-harness/babel-preset'); + +const transform = (args) => { + const { plugins } = args; + const upstreamTransformerPath = + process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH; + + if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') { + throw new Error('Upstream transformer path is not a string'); + } + + const upstreamTransformer = require(upstreamTransformerPath); + const pluginsWithHarness = [ + ...((plugins ?? [])), + ...rnHarnessPlugins, + ]; + + return upstreamTransformer.transform({ + ...args, + plugins: pluginsWithHarness, + }); +}; + +module.exports = { transform }; diff --git a/packages/bundler-metro/eslint.config.mjs b/packages/bundler-metro/eslint.config.mjs index c334bc0b..755eba37 100644 --- a/packages/bundler-metro/eslint.config.mjs +++ b/packages/bundler-metro/eslint.config.mjs @@ -9,6 +9,7 @@ export default [ 'error', { ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'], + ignoredDependencies: ['@react-native-harness/babel-preset', 'vitest'], }, ], }, diff --git a/packages/bundler-metro/package.json b/packages/bundler-metro/package.json index 859a8ecd..02da4008 100644 --- a/packages/bundler-metro/package.json +++ b/packages/bundler-metro/package.json @@ -16,21 +16,27 @@ } }, "dependencies": { - "@react-native-harness/metro": "workspace:*", + "@react-native/metro-config": "*", + "@react-native-harness/babel-preset": "workspace:*", "@react-native-harness/tools": "workspace:*", "@react-native-harness/config": "workspace:*", + "@react-native-harness/runtime": "workspace:*", "connect": "^3.7.0", "nocache": "^4.0.0", "tslib": "^2.3.0" }, "peerDependencies": { "metro": "*", - "metro-config": "*" + "metro-cache": "*", + "metro-config": "*", + "metro-resolver": "*" }, "devDependencies": { "@types/connect": "^3.4.38", "metro": "*", - "metro-config": "*" + "metro-cache": "*", + "metro-config": "*", + "metro-resolver": "*" }, "license": "MIT" } diff --git a/packages/metro/src/__tests__/paths.test.ts b/packages/bundler-metro/src/__tests__/paths.test.ts similarity index 93% rename from packages/metro/src/__tests__/paths.test.ts rename to packages/bundler-metro/src/__tests__/paths.test.ts index 708f8c6d..80300e29 100644 --- a/packages/metro/src/__tests__/paths.test.ts +++ b/packages/bundler-metro/src/__tests__/paths.test.ts @@ -12,7 +12,9 @@ import { const tempDirs: string[] = []; const createTempProjectRoot = (): string => { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rn-harness-metro-')); + const tempDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'rn-harness-bundler-metro-') + ); tempDirs.push(tempDir); return tempDir; }; @@ -23,7 +25,7 @@ afterEach(() => { } }); -describe('metro paths', () => { +describe('bundler metro paths', () => { it('resolves the harness root under the project root', () => { const projectRoot = createTempProjectRoot(); diff --git a/packages/bundler-metro/src/babel-transformer.ts b/packages/bundler-metro/src/babel-transformer.ts new file mode 100644 index 00000000..a1cf4e49 --- /dev/null +++ b/packages/bundler-metro/src/babel-transformer.ts @@ -0,0 +1,16 @@ +import { fileURLToPath } from 'node:url'; +import { MetroConfig } from '@react-native/metro-config'; + +export const getHarnessBabelTransformerPath = ( + metroConfig: MetroConfig +): string => { + const upstreamTransformerPath = metroConfig.transformer?.babelTransformerPath; + + if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') { + throw new Error('Upstream transformer path is not a string'); + } + + process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH = upstreamTransformerPath; + + return fileURLToPath(new URL('../babel-transformer.cjs', import.meta.url)); +}; diff --git a/packages/bundler-metro/src/factory.ts b/packages/bundler-metro/src/factory.ts index 0d3b7f9a..cee27e51 100644 --- a/packages/bundler-metro/src/factory.ts +++ b/packages/bundler-metro/src/factory.ts @@ -1,4 +1,3 @@ -import { withRnHarness } from '@react-native-harness/metro'; import { logger } from '@react-native-harness/tools'; import type { Server as HttpServer } from 'node:http'; import type { Server as HttpsServer } from 'node:https'; @@ -15,6 +14,7 @@ import { } from './reporter.js'; import { getExpoMiddleware } from './middlewares/expo-middleware.js'; import { getStatusMiddleware } from './middlewares/status-middleware.js'; +import { withRnHarness } from './withRnHarness.js'; const waitForBundler = async ( reporter: Reporter, diff --git a/packages/metro/src/getHarnessSerializer.ts b/packages/bundler-metro/src/getHarnessSerializer.ts similarity index 82% rename from packages/metro/src/getHarnessSerializer.ts rename to packages/bundler-metro/src/getHarnessSerializer.ts index 4dd0b569..25fde3db 100644 --- a/packages/metro/src/getHarnessSerializer.ts +++ b/packages/bundler-metro/src/getHarnessSerializer.ts @@ -1,11 +1,16 @@ +import { createRequire } from 'node:module'; import type { MetroConfig } from 'metro-config'; +const require = createRequire(import.meta.url); + export type Serializer = NonNullable< NonNullable['customSerializer'] >; const getBaseSerializer = (): Serializer => { - const baseJSBundle = require('metro/private/DeltaBundler/Serializers/baseJSBundle'); + const baseJSBundle = require( + 'metro/private/DeltaBundler/Serializers/baseJSBundle' + ); const bundleToString = require('metro/private/lib/bundleToString'); return (entryPoint, prepend, graph, bundleOptions) => @@ -20,7 +25,6 @@ export const getHarnessSerializer = (): Serializer => { return async (entryPoint, preModules, graph, options) => { if (options.modulesOnly) { - // This is most likely a test file return baseSerializer(entryPoint, preModules, graph, { ...options, processModuleFilter: (mod) => { @@ -28,11 +32,9 @@ export const getHarnessSerializer = (): Serializer => { options.processModuleFilter && !options.processModuleFilter(mod) ) { - // If the module is not allowed by the processModuleFilter, skip it return false; } - // If the module is in the main entry point, skip it return !mainEntryPointModules.has(mod.path); }, }); diff --git a/packages/bundler-metro/src/index.ts b/packages/bundler-metro/src/index.ts index d786fc17..a5da200d 100644 --- a/packages/bundler-metro/src/index.ts +++ b/packages/bundler-metro/src/index.ts @@ -2,3 +2,4 @@ export { getMetroInstance } from './factory.js'; export type { MetroInstance, MetroFactory, MetroOptions } from './types.js'; export { prewarmMetroBundle } from './prewarm.js'; export type { Reporter, ReportableEvent } from './reporter.js'; +export { isMetroCacheReusable } from './paths.js'; diff --git a/packages/metro/src/jest-globals-mock.ts b/packages/bundler-metro/src/jest-globals-mock.ts similarity index 67% rename from packages/metro/src/jest-globals-mock.ts rename to packages/bundler-metro/src/jest-globals-mock.ts index 20ecdd65..6bb8887b 100644 --- a/packages/metro/src/jest-globals-mock.ts +++ b/packages/bundler-metro/src/jest-globals-mock.ts @@ -1,5 +1,5 @@ // Mock module for @jest/globals imports -// This module throws immediately when imported to warn users about using Jest APIs +// This module throws immediately to explain the supported import path. throw new Error( "Importing '@jest/globals' is not supported in Harness tests. Import from 'react-native-harness' instead." diff --git a/packages/metro/src/manifest.ts b/packages/bundler-metro/src/manifest.ts similarity index 93% rename from packages/metro/src/manifest.ts rename to packages/bundler-metro/src/manifest.ts index b3ab99c1..856ec5ff 100644 --- a/packages/metro/src/manifest.ts +++ b/packages/bundler-metro/src/manifest.ts @@ -1,7 +1,7 @@ -import path from 'node:path'; import fs from 'node:fs'; +import path from 'node:path'; import { Config as HarnessConfig } from '@react-native-harness/config'; -import { getHarnessManifestPath } from './paths'; +import { getHarnessManifestPath } from './paths.js'; const getManifestContent = (harnessConfig: HarnessConfig): string => { return `global.RN_HARNESS = { diff --git a/packages/metro/src/metro-cache.ts b/packages/bundler-metro/src/metro-cache.ts similarity index 91% rename from packages/metro/src/metro-cache.ts rename to packages/bundler-metro/src/metro-cache.ts index dd9de62e..e625004d 100644 --- a/packages/metro/src/metro-cache.ts +++ b/packages/bundler-metro/src/metro-cache.ts @@ -1,8 +1,8 @@ -import { CacheStore, MetroCache } from 'metro-cache'; -import type { MixedOutput, TransformResult } from 'metro'; import fs from 'node:fs'; import type { CacheStoresConfigT } from 'metro-config'; -import { getHarnessMetroCachePath } from './paths'; +import { CacheStore, MetroCache } from 'metro-cache'; +import type { MixedOutput, TransformResult } from 'metro'; +import { getHarnessMetroCachePath } from './paths.js'; export const getHarnessCacheStores = (): (( metroCache: MetroCache diff --git a/packages/metro/src/paths.ts b/packages/bundler-metro/src/paths.ts similarity index 100% rename from packages/metro/src/paths.ts rename to packages/bundler-metro/src/paths.ts diff --git a/packages/metro/src/resolvers/composite-resolver.ts b/packages/bundler-metro/src/resolvers/composite-resolver.ts similarity index 85% rename from packages/metro/src/resolvers/composite-resolver.ts rename to packages/bundler-metro/src/resolvers/composite-resolver.ts index 079613ff..181828c5 100644 --- a/packages/metro/src/resolvers/composite-resolver.ts +++ b/packages/bundler-metro/src/resolvers/composite-resolver.ts @@ -1,4 +1,4 @@ -import type { HarnessResolver, MetroResolver } from './types'; +import type { HarnessResolver, MetroResolver } from './types.js'; export const createHarnessResolver = ( resolvers: HarnessResolver[] diff --git a/packages/metro/src/resolvers/resolver.ts b/packages/bundler-metro/src/resolvers/resolver.ts similarity index 61% rename from packages/metro/src/resolvers/resolver.ts rename to packages/bundler-metro/src/resolvers/resolver.ts index 0593d23a..54f48d2f 100644 --- a/packages/metro/src/resolvers/resolver.ts +++ b/packages/bundler-metro/src/resolvers/resolver.ts @@ -1,36 +1,47 @@ +import { createRequire } from 'node:module'; +import path from 'node:path'; 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'; +import { createHarnessResolver } from './composite-resolver.js'; +import { createTsConfigResolver } from './tsconfig-resolver.js'; +import type { HarnessResolver, MetroResolver } from './types.js'; -// Safely resolves a path and strips its extension -const getExtensionlessAbsolutePath = (basePath: string, relativePath = ''): string => { +const require = createRequire(import.meta.url); + +const getExtensionlessAbsolutePath = ( + basePath: string, + relativePath = '' +): string => { const fullPath = path.resolve(basePath, relativePath); const parsed = path.parse(fullPath); return path.join(parsed.dir, parsed.name); -} +}; -export const createHarnessEntryPointResolver = (harnessConfig: HarnessConfig): HarnessResolver => { +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'); + 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 + return (context, moduleName, platform) => { + void platform; 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; } - // 2. Resolve the module being imported and strip its extension - // This safely normalizes './index', './index.js', 'index.js', etc. - const requestedModule = getExtensionlessAbsolutePath(currentOrigin, moduleName); + const requestedModule = getExtensionlessAbsolutePath( + currentOrigin, + moduleName + ); - // 3. String comparison if (requestedModule === expectedEntryPoint) { return { type: 'sourceFile', @@ -43,12 +54,12 @@ export const createHarnessEntryPointResolver = (harnessConfig: HarnessConfig): H }; export const createJestGlobalsResolver = (): HarnessResolver => { - return (_context, moduleName, _platform) => { - // Intercept @jest/globals imports and redirect to mock module + return (_context, moduleName, platform) => { + void platform; if (moduleName === '@jest/globals') { return { type: 'sourceFile', - filePath: require.resolve('../jest-globals-mock'), + filePath: require.resolve('../jest-globals-mock.js'), }; } @@ -64,7 +75,8 @@ export const createJsxRuntimeResolver = (): HarnessResolver => { '@react-native-harness/runtime/jsx-dev-runtime' ); - return (_context, moduleName, _platform) => { + return (_context, moduleName, platform) => { + void platform; if (moduleName === '@react-native-harness/runtime/jsx-runtime') { return { type: 'sourceFile', diff --git a/packages/bundler-metro/src/resolvers/tsconfig-resolver.ts b/packages/bundler-metro/src/resolvers/tsconfig-resolver.ts new file mode 100644 index 00000000..d42d326f --- /dev/null +++ b/packages/bundler-metro/src/resolvers/tsconfig-resolver.ts @@ -0,0 +1,191 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import type { CustomResolutionContext, Resolution } from 'metro-resolver'; +import type { HarnessResolver } from './types.js'; + +export type TsConfigPaths = { + paths: Record; + baseUrl: string; + hasBaseUrl: boolean; +}; + +export const loadTsConfigPaths = ( + projectRoot: string +): TsConfigPaths | null => { + const configFiles = ['tsconfig.json', 'jsconfig.json']; + + for (const configFile of configFiles) { + const configPath = path.join(projectRoot, configFile); + + if (!fs.existsSync(configPath)) continue; + + try { + const content = fs.readFileSync(configPath, 'utf8'); + const jsonContent = stripJsonComments(content); + const config = JSON.parse(jsonContent); + + const compilerOptions = config.compilerOptions || {}; + const paths = compilerOptions.paths || {}; + const baseUrl = compilerOptions.baseUrl; + + if (Object.keys(paths).length > 0 || baseUrl) { + return { + paths, + baseUrl: baseUrl ? path.resolve(projectRoot, baseUrl) : projectRoot, + hasBaseUrl: !!baseUrl, + }; + } + } catch (error) { + console.warn(`Failed to parse ${configFile}:`, error); + } + } + + return null; +}; + +const stripJsonComments = (input: string): string => { + let result = ''; + let inString = false; + let stringChar = ''; + let isEscaped = false; + let inLineComment = false; + let inBlockComment = false; + + for (let i = 0; i < input.length; i += 1) { + const char = input[i]; + const nextChar = input[i + 1]; + + if (inLineComment) { + if (char === '\n') { + inLineComment = false; + result += char; + } + continue; + } + + if (inBlockComment) { + if (char === '*' && nextChar === '/') { + inBlockComment = false; + i += 1; + } + continue; + } + + if (inString) { + result += char; + if (!isEscaped && char === stringChar) { + inString = false; + stringChar = ''; + } + isEscaped = !isEscaped && char === '\\'; + continue; + } + + if (char === '"' || char === "'") { + inString = true; + stringChar = char; + result += char; + isEscaped = false; + continue; + } + + if (char === '/' && nextChar === '/') { + inLineComment = true; + i += 1; + continue; + } + + if (char === '/' && nextChar === '*') { + inBlockComment = true; + i += 1; + continue; + } + + result += char; + } + + return result; +}; + +const matchPattern = ( + pattern: string, + moduleName: string +): { matched: boolean; captured: string } => { + const escapedPattern = pattern + .replace(/[.+?^${}()|[\]\\]/g, '\\$&') + .replace(/\*/g, '(.*)'); + + const regex = new RegExp(`^${escapedPattern}$`); + const match = moduleName.match(regex); + + return { + matched: !!match, + captured: match?.[1] || '', + }; +}; + +export const resolveWithTsConfigPaths = ( + tsConfig: TsConfigPaths, + context: CustomResolutionContext, + moduleName: string, + platform: string | null +): Resolution | null => { + const { paths, baseUrl, hasBaseUrl } = tsConfig; + const resolveRequest = context.resolveRequest; + + if (!resolveRequest) { + return null; + } + + for (const [pattern, targets] of Object.entries(paths)) { + const { matched, captured } = matchPattern(pattern, moduleName); + if (!matched) continue; + + for (const target of targets) { + const resolvedTarget = target.replace('*', captured); + const absolutePath = path.resolve(baseUrl, resolvedTarget); + + try { + return resolveRequest(context, absolutePath, platform); + } catch { + continue; + } + } + } + + if ( + hasBaseUrl && + !moduleName.startsWith('.') && + !moduleName.startsWith('/') + ) { + const absolutePath = path.resolve(baseUrl, moduleName); + try { + return resolveRequest(context, absolutePath, platform); + } catch { + // Fall through to the default Metro resolution. + } + } + + return null; +}; + +export const createTsConfigResolver = ( + projectRoot: string +): HarnessResolver => { + const tsConfig = loadTsConfigPaths(projectRoot); + + return (context, moduleName, platform) => { + if (!tsConfig || !context.resolveRequest) { + return null; + } + + const resolved = resolveWithTsConfigPaths( + tsConfig, + context, + moduleName, + platform + ); + + return resolved ?? null; + }; +}; diff --git a/packages/bundler-metro/src/resolvers/types.ts b/packages/bundler-metro/src/resolvers/types.ts new file mode 100644 index 00000000..636e1e46 --- /dev/null +++ b/packages/bundler-metro/src/resolvers/types.ts @@ -0,0 +1,13 @@ +import type { CustomResolutionContext, Resolution } from 'metro-resolver'; + +export type HarnessResolver = ( + context: CustomResolutionContext, + moduleName: string, + platform: string | null +) => Resolution | null; + +export type MetroResolver = ( + context: CustomResolutionContext, + moduleName: string, + platform: string | null +) => Resolution; diff --git a/packages/bundler-metro/src/withRnHarness.ts b/packages/bundler-metro/src/withRnHarness.ts new file mode 100644 index 00000000..0d51f9a1 --- /dev/null +++ b/packages/bundler-metro/src/withRnHarness.ts @@ -0,0 +1,108 @@ +import { createRequire } from 'node:module'; +import type { MetroConfig } from 'metro-config'; +import { getConfig } from '@react-native-harness/config'; +import { getHarnessBabelTransformerPath } from './babel-transformer.js'; +import { getHarnessSerializer } from './getHarnessSerializer.js'; +import { getHarnessManifest } from './manifest.js'; +import { getHarnessCacheStores } from './metro-cache.js'; +import { getHarnessResolver } from './resolvers/resolver.js'; +import type { NotReadOnly } from './utils.js'; + +const require = createRequire(import.meta.url); + +const INTERNAL_CALLSITES_REGEX = + /(^|[\\/])(node_modules[/\\]@react-native-harness)([\\/]|$)/; + +export const withRnHarness = ( + config: T | Promise, + isInvokedByHarness = false +): (() => Promise) => { + return async () => { + if (!isInvokedByHarness) { + return config; + } + + const metroConfig = await config; + const { config: harnessConfig } = await getConfig(process.cwd()); + + const harnessResolver = getHarnessResolver(metroConfig, harnessConfig); + const harnessManifest = getHarnessManifest(harnessConfig); + const harnessBabelTransformerPath = + getHarnessBabelTransformerPath(metroConfig); + + const patchedConfig: MetroConfig = { + ...metroConfig, + cacheVersion: 'react-native-harness', + server: { + ...metroConfig.server, + forwardClientLogs: harnessConfig.forwardClientLogs ?? false, + }, + serializer: { + ...metroConfig.serializer, + getPolyfills: (...args) => [ + ...(metroConfig.serializer?.getPolyfills?.(...args) ?? []), + harnessManifest, + require.resolve( + '@react-native-harness/runtime/polyfills/harness-module-system' + ), + ], + isThirdPartyModule({ path: modulePath }) { + const isThirdPartyByDefault = + metroConfig.serializer?.isThirdPartyModule?.({ + path: modulePath, + }) ?? false; + + if (isThirdPartyByDefault) { + return true; + } + + return INTERNAL_CALLSITES_REGEX.test(modulePath); + }, + }, + resolver: { + ...metroConfig.resolver, + blockList: undefined, + resolveRequest: harnessResolver, + }, + transformer: { + ...metroConfig.transformer, + babelTransformerPath: harnessBabelTransformerPath, + }, + symbolicator: { + ...metroConfig.symbolicator, + customizeFrame: async (frame) => { + const defaultCustomizeFrame = + await metroConfig.symbolicator?.customizeFrame?.(frame); + const shouldCollapseByDefault = + defaultCustomizeFrame?.collapse ?? false; + + if (shouldCollapseByDefault) { + return { + collapse: true, + }; + } + + return { + collapse: + frame.file != null && INTERNAL_CALLSITES_REGEX.test(frame.file), + }; + }, + }, + }; + + if (harnessConfig.unstable__enableMetroCache) { + (patchedConfig.cacheStores as NotReadOnly) = + getHarnessCacheStores(); + } + + if (harnessConfig.unstable__skipAlreadyIncludedModules) { + ( + patchedConfig.serializer as NonNullable< + NotReadOnly + > + ).customSerializer = getHarnessSerializer(); + } + + return patchedConfig as T; + }; +}; diff --git a/packages/bundler-metro/tsconfig.json b/packages/bundler-metro/tsconfig.json index 4d3bd484..403a9dfe 100644 --- a/packages/bundler-metro/tsconfig.json +++ b/packages/bundler-metro/tsconfig.json @@ -3,6 +3,9 @@ "files": [], "include": [], "references": [ + { + "path": "../runtime" + }, { "path": "../config" }, @@ -10,7 +13,7 @@ "path": "../tools" }, { - "path": "../metro" + "path": "../babel-preset" }, { "path": "./tsconfig.lib.json" diff --git a/packages/bundler-metro/tsconfig.lib.json b/packages/bundler-metro/tsconfig.lib.json index d9db878c..41b56969 100644 --- a/packages/bundler-metro/tsconfig.lib.json +++ b/packages/bundler-metro/tsconfig.lib.json @@ -12,14 +12,17 @@ }, "include": ["src/**/*.ts"], "references": [ + { + "path": "../babel-preset/tsconfig.lib.json" + }, { "path": "../config/tsconfig.lib.json" }, { - "path": "../tools/tsconfig.lib.json" + "path": "../runtime/tsconfig.lib.json" }, { - "path": "../metro/tsconfig.lib.json" + "path": "../tools/tsconfig.lib.json" } ] } diff --git a/packages/jest/package.json b/packages/jest/package.json index a56a3213..88954a93 100644 --- a/packages/jest/package.json +++ b/packages/jest/package.json @@ -37,7 +37,6 @@ "@react-native-harness/bridge": "workspace:*", "@react-native-harness/bundler-metro": "workspace:*", "@react-native-harness/config": "workspace:*", - "@react-native-harness/metro": "workspace:*", "@react-native-harness/platforms": "workspace:*", "@react-native-harness/tools": "workspace:*", "chalk": "^4.1.2", diff --git a/packages/jest/src/__tests__/harness-cache.test.ts b/packages/jest/src/__tests__/harness-cache.test.ts index 09b846d8..fb09219e 100644 --- a/packages/jest/src/__tests__/harness-cache.test.ts +++ b/packages/jest/src/__tests__/harness-cache.test.ts @@ -8,7 +8,7 @@ const mocks = vi.hoisted(() => ({ logMetroPrewarmCompleted: vi.fn(), })); -vi.mock('@react-native-harness/metro', () => ({ +vi.mock('@react-native-harness/bundler-metro', () => ({ isMetroCacheReusable: mocks.isMetroCacheReusable, })); diff --git a/packages/jest/src/harness.ts b/packages/jest/src/harness.ts index 29a09cef..2f30c4ed 100644 --- a/packages/jest/src/harness.ts +++ b/packages/jest/src/harness.ts @@ -14,9 +14,9 @@ import { } from '@react-native-harness/platforms'; import { getMetroInstance, + isMetroCacheReusable, prewarmMetroBundle, } from '@react-native-harness/bundler-metro'; -import { isMetroCacheReusable } from '@react-native-harness/metro'; import { createCrashArtifactWriter } from '@react-native-harness/tools'; import { InitializationTimeoutError } from './errors.js'; import { Config as HarnessConfig } from '@react-native-harness/config'; diff --git a/packages/jest/tsconfig.json b/packages/jest/tsconfig.json index 931da274..e0031d84 100644 --- a/packages/jest/tsconfig.json +++ b/packages/jest/tsconfig.json @@ -3,9 +3,6 @@ "files": [], "include": [], "references": [ - { - "path": "../metro" - }, { "path": "../cli" }, diff --git a/packages/jest/tsconfig.lib.json b/packages/jest/tsconfig.lib.json index ae4910ff..4115cc50 100644 --- a/packages/jest/tsconfig.lib.json +++ b/packages/jest/tsconfig.lib.json @@ -12,9 +12,6 @@ }, "include": ["src/**/*.ts"], "references": [ - { - "path": "../metro/tsconfig.lib.json" - }, { "path": "../cli/tsconfig.lib.json" }, diff --git a/packages/metro/README.md b/packages/metro/README.md index 71dbd465..f6260e20 100644 --- a/packages/metro/README.md +++ b/packages/metro/README.md @@ -7,7 +7,12 @@ [![Chat][chat-badge]][chat] [![PRs Welcome][prs-welcome-badge]][prs-welcome] -Metro bundler integration that patches Metro configuration to enable test file bundling and execution in React Native environments with custom module system support. +Deprecated Metro compatibility package for React Native Harness. + +> [!WARNING] +> `@react-native-harness/metro` is deprecated and will be removed in the next release. +> React Native Harness now patches Metro internally through `@react-native-harness/bundler-metro`. +> Do not add this package to new projects, and remove `withRnHarness` from your Metro config if you still use it. ## Made with ❤️ at Callstack diff --git a/packages/metro/package.json b/packages/metro/package.json index a38aea8b..5e5e93d1 100644 --- a/packages/metro/package.json +++ b/packages/metro/package.json @@ -15,17 +15,12 @@ } }, "peerDependencies": { - "@react-native-harness/runtime": "workspace:*", "metro": "*" }, "dependencies": { - "tslib": "^2.3.0", - "@react-native-harness/config": "workspace:*", - "@react-native-harness/babel-preset": "workspace:*" + "tslib": "^2.3.0" }, "devDependencies": { - "@types/babel__core": "^7.20.5", - "@react-native-harness/runtime": "workspace:*", "metro": "*" }, "license": "MIT" diff --git a/packages/metro/src/__tests__/withRnHarness.test.ts b/packages/metro/src/__tests__/withRnHarness.test.ts new file mode 100644 index 00000000..e8946a0b --- /dev/null +++ b/packages/metro/src/__tests__/withRnHarness.test.ts @@ -0,0 +1,32 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +const consoleWarnSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => undefined); + +afterEach(() => { + consoleWarnSpy.mockClear(); + vi.resetModules(); +}); + +describe('withRnHarness', () => { + it('returns the provided config unchanged', async () => { + const { withRnHarness } = await import('../withRnHarness.js'); + const config = { resolver: { blockList: [] } }; + + await expect(withRnHarness(config)()).resolves.toBe(config); + expect(consoleWarnSpy).toHaveBeenCalledTimes(1); + }); + + it('warns only once across repeated calls', async () => { + const { withRnHarness } = await import('../withRnHarness.js'); + + await withRnHarness({ projectRoot: '/tmp/app' }, true)(); + await withRnHarness(Promise.resolve({ projectRoot: '/tmp/app' }), true)(); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(1); + expect(consoleWarnSpy.mock.calls[0]?.[0]).toContain( + 'Remove `withRnHarness` from your Metro config' + ); + }); +}); diff --git a/packages/metro/src/babel-transformer.ts b/packages/metro/src/babel-transformer.ts deleted file mode 100644 index 31227be5..00000000 --- a/packages/metro/src/babel-transformer.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { BabelTransformer } from 'metro-babel-transformer'; -import { rnHarnessPlugins } from '@react-native-harness/babel-preset'; -import { MetroConfig } from '@react-native/metro-config'; - -export const getHarnessBabelTransformerPath = ( - metroConfig: MetroConfig -): string => { - const upstreamTransformerPath = metroConfig.transformer?.babelTransformerPath; - - if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') { - throw new Error('Upstream transformer path is not a string'); - } - - process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH = upstreamTransformerPath; - return require.resolve('./babel-transformer.js'); -}; - -const transform: BabelTransformer['transform'] = (args) => { - const { plugins } = args; - const upstreamTransformerPath = - process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH; - - if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') { - throw new Error('Upstream transformer path is not a string'); - } - - const upstreamTransformer = require(upstreamTransformerPath); - const pluginsWithHarness = [ - // Checked against @babel/core's type definitions - plugins are an array of PluginItem - ...((plugins as unknown[]) ?? []), - ...rnHarnessPlugins, - ]; - - return upstreamTransformer.transform({ - ...args, - plugins: pluginsWithHarness, - }); -}; - -export { transform }; diff --git a/packages/metro/src/errors.ts b/packages/metro/src/errors.ts deleted file mode 100644 index aa4ecb18..00000000 --- a/packages/metro/src/errors.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class CouldNotPatchModuleSystemError extends Error { - constructor() { - super('Could not patch module system'); - this.name = 'CouldNotPatchModuleSystemError'; - } -} diff --git a/packages/metro/src/index.ts b/packages/metro/src/index.ts index 67a41352..0496818a 100644 --- a/packages/metro/src/index.ts +++ b/packages/metro/src/index.ts @@ -1,7 +1 @@ export { withRnHarness } from './withRnHarness.js'; -export { - getHarnessManifestPath, - getHarnessMetroCachePath, - getHarnessRootPath, - isMetroCacheReusable, -} from './paths.js'; diff --git a/packages/metro/src/resolvers/tsconfig-resolver.ts b/packages/metro/src/resolvers/tsconfig-resolver.ts deleted file mode 100644 index 16ec3094..00000000 --- a/packages/metro/src/resolvers/tsconfig-resolver.ts +++ /dev/null @@ -1,210 +0,0 @@ -import path from 'path'; -import fs from 'fs'; -import type { Resolution, CustomResolutionContext } from 'metro-resolver'; -import type { HarnessResolver } from './types'; - -// This resolver is based on the Expo's implementation. -// https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/start/server/metro/withMetroMultiPlatform.ts -// The reason to have it in Harness is that Expo doesn't set the resolveRequest function in the context. -// In order for tsconfig's paths to work, we need to recreate this logic ourselves. - -export type TsConfigPaths = { - paths: Record; - baseUrl: string; - hasBaseUrl: boolean; -} - -/** - * Load tsconfig.json or jsconfig.json and extract path mappings - */ -export const loadTsConfigPaths = ( - projectRoot: string -): TsConfigPaths | null => { - const configFiles = ['tsconfig.json', 'jsconfig.json']; - - for (const configFile of configFiles) { - const configPath = path.join(projectRoot, configFile); - - if (!fs.existsSync(configPath)) continue; - - try { - const content = fs.readFileSync(configPath, 'utf8'); - // Strip comments without touching string literals - const jsonContent = stripJsonComments(content); - const config = JSON.parse(jsonContent); - - const compilerOptions = config.compilerOptions || {}; - const paths = compilerOptions.paths || {}; - const baseUrl = compilerOptions.baseUrl; - - if (Object.keys(paths).length > 0 || baseUrl) { - return { - paths, - baseUrl: baseUrl ? path.resolve(projectRoot, baseUrl) : projectRoot, - hasBaseUrl: !!baseUrl, - }; - } - } catch (error) { - console.warn(`Failed to parse ${configFile}:`, error); - } - } - - return null; -}; - -const stripJsonComments = (input: string): string => { - let result = ''; - let inString = false; - let stringChar = ''; - let isEscaped = false; - let inLineComment = false; - let inBlockComment = false; - - for (let i = 0; i < input.length; i += 1) { - const char = input[i]; - const nextChar = input[i + 1]; - - if (inLineComment) { - if (char === '\n') { - inLineComment = false; - result += char; - } - continue; - } - - if (inBlockComment) { - if (char === '*' && nextChar === '/') { - inBlockComment = false; - i += 1; - } - continue; - } - - if (inString) { - result += char; - if (!isEscaped && char === stringChar) { - inString = false; - stringChar = ''; - } - isEscaped = !isEscaped && char === '\\'; - continue; - } - - if (char === '"' || char === "'") { - inString = true; - stringChar = char; - result += char; - isEscaped = false; - continue; - } - - if (char === '/' && nextChar === '/') { - inLineComment = true; - i += 1; - continue; - } - - if (char === '/' && nextChar === '*') { - inBlockComment = true; - i += 1; - continue; - } - - result += char; - } - - return result; -}; - -/** - * Match module name against tsconfig path pattern (supports wildcards) - */ -const matchPattern = ( - pattern: string, - moduleName: string -): { matched: boolean; captured: string } => { - const escapedPattern = pattern - .replace(/[.+?^${}()|[\]\\]/g, '\\$&') - .replace(/\*/g, '(.*)'); - - const regex = new RegExp(`^${escapedPattern}$`); - const match = moduleName.match(regex); - - return { - matched: !!match, - captured: match?.[1] || '', - }; -}; - -/** - * Resolve module using tsconfig path mappings - * Use this directly in your custom resolver - */ -export const resolveWithTsConfigPaths = ( - tsConfig: TsConfigPaths, - context: CustomResolutionContext, - moduleName: string, - platform: string | null -): Resolution | null => { - const { paths, baseUrl, hasBaseUrl } = tsConfig; - const resolveRequest = context.resolveRequest; - - if (!resolveRequest) { - return null; - } - - // Try path mappings first - for (const [pattern, targets] of Object.entries(paths)) { - const { matched, captured } = matchPattern(pattern, moduleName); - if (!matched) continue; - - // Try each target - for (const target of targets) { - const resolvedTarget = target.replace('*', captured); - const absolutePath = path.resolve(baseUrl, resolvedTarget); - - try { - return resolveRequest(context, absolutePath, platform); - } catch { - continue; - } - } - } - - // Try baseUrl for non-relative imports - if (hasBaseUrl && !moduleName.startsWith('.') && !moduleName.startsWith('/')) { - const absolutePath = path.resolve(baseUrl, moduleName); - try { - return resolveRequest(context, absolutePath, platform); - } catch { - // Fall through - } - } - - return null; -}; - -export const createTsConfigResolver = ( - projectRoot: string -): HarnessResolver => { - const tsConfig = loadTsConfigPaths(projectRoot); - - return (context, moduleName, platform) => { - if (!tsConfig) { - return null; - } - - if (!context.resolveRequest) { - return null; - } - - const resolved = resolveWithTsConfigPaths( - tsConfig, - context, - moduleName, - platform - ); - - return resolved ?? null; - }; -}; diff --git a/packages/metro/src/resolvers/types.ts b/packages/metro/src/resolvers/types.ts deleted file mode 100644 index 6244f47d..00000000 --- a/packages/metro/src/resolvers/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { CustomResolutionContext, Resolution } from 'metro-resolver'; - -export type HarnessResolver = (context: CustomResolutionContext, moduleName: string, platform: string | null) => Resolution | null; -export type MetroResolver = (context: CustomResolutionContext, moduleName: string, platform: string | null) => Resolution; \ No newline at end of file diff --git a/packages/metro/src/utils.ts b/packages/metro/src/utils.ts deleted file mode 100644 index f5ff9e1d..00000000 --- a/packages/metro/src/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type NotReadOnly = { - -readonly [K in keyof T]: T[K]; -}; diff --git a/packages/metro/src/withRnHarness.ts b/packages/metro/src/withRnHarness.ts index 24d9b406..641437a7 100644 --- a/packages/metro/src/withRnHarness.ts +++ b/packages/metro/src/withRnHarness.ts @@ -1,109 +1,17 @@ -import type { MetroConfig } from 'metro-config'; -import { getConfig } from '@react-native-harness/config'; -import { getHarnessResolver } from './resolvers/resolver'; -import { getHarnessManifest } from './manifest'; -import { getHarnessBabelTransformerPath } from './babel-transformer'; -import { getHarnessCacheStores } from './metro-cache'; -import type { NotReadOnly } from './utils'; +let hasWarned = false; -const INTERNAL_CALLSITES_REGEX = - /(^|[\\/])(node_modules[/\\]@react-native-harness)([\\/]|$)/; - -export const withRnHarness = ( +export const withRnHarness = ( config: T | Promise, - isInvokedByHarness = false + _isInvokedByHarness = false ): (() => Promise) => { - // This is a workaround for a regression in Metro 0.83, when promises are not handled correctly. return async () => { - // If the function is not invoked by the Harness, return the config as is. - // We'll remove it in the next major version. - if (!isInvokedByHarness) { - return config; - } - - const metroConfig = await config; - const { config: harnessConfig } = await getConfig(process.cwd()); - - const harnessResolver = getHarnessResolver(metroConfig, harnessConfig); - const harnessManifest = getHarnessManifest(harnessConfig); - const harnessBabelTransformerPath = - getHarnessBabelTransformerPath(metroConfig); - - const patchedConfig: MetroConfig = { - ...metroConfig, - cacheVersion: 'react-native-harness', - server: { - ...metroConfig.server, - forwardClientLogs: harnessConfig.forwardClientLogs ?? false, - }, - serializer: { - ...metroConfig.serializer, - getPolyfills: (...args) => [ - ...(metroConfig.serializer?.getPolyfills?.(...args) ?? []), - harnessManifest, - require.resolve( - '@react-native-harness/runtime/polyfills/harness-module-system' - ), - ], - isThirdPartyModule({ path: modulePath }) { - const isThirdPartyByDefault = - metroConfig.serializer?.isThirdPartyModule?.({ - path: modulePath, - }) ?? false; - - if (isThirdPartyByDefault) { - return true; - } - - return INTERNAL_CALLSITES_REGEX.test(modulePath); - }, - }, - resolver: { - ...metroConfig.resolver, - // Unlock __tests__ directory - blockList: undefined, - resolveRequest: harnessResolver, - }, - transformer: { - ...metroConfig.transformer, - babelTransformerPath: harnessBabelTransformerPath, - }, - symbolicator: { - ...metroConfig.symbolicator, - customizeFrame: async (frame) => { - const defaultCustomizeFrame = - await metroConfig.symbolicator?.customizeFrame?.(frame); - const shouldCollapseByDefault = - defaultCustomizeFrame?.collapse ?? false; - - if (shouldCollapseByDefault) { - return { - collapse: true, - }; - } - - return { - collapse: - frame.file != null && INTERNAL_CALLSITES_REGEX.test(frame.file), - }; - }, - }, - }; - - if (harnessConfig.unstable__enableMetroCache) { - (patchedConfig.cacheStores as NotReadOnly) = - getHarnessCacheStores(); - } - - if (harnessConfig.unstable__skipAlreadyIncludedModules) { - ( - patchedConfig.serializer as NonNullable< - NotReadOnly - > - ).customSerializer = - require('./getHarnessSerializer').getHarnessSerializer(); + if (!hasWarned) { + hasWarned = true; + console.warn( + "[react-native-harness] `withRnHarness` in Metro configs is deprecated and will be removed in a future release. Remove `withRnHarness` from your Metro config; React Native Harness now patches Metro internally." + ); } - return patchedConfig as T; + return await config; }; }; diff --git a/packages/metro/tsconfig.json b/packages/metro/tsconfig.json index 464929d7..c23e61c8 100644 --- a/packages/metro/tsconfig.json +++ b/packages/metro/tsconfig.json @@ -3,15 +3,6 @@ "files": [], "include": [], "references": [ - { - "path": "../babel-preset" - }, - { - "path": "../config" - }, - { - "path": "../runtime" - }, { "path": "./tsconfig.lib.json" } diff --git a/packages/metro/tsconfig.lib.json b/packages/metro/tsconfig.lib.json index a38e9002..a70ad78a 100644 --- a/packages/metro/tsconfig.lib.json +++ b/packages/metro/tsconfig.lib.json @@ -9,16 +9,5 @@ "forceConsistentCasingInFileNames": true, "types": ["node"] }, - "include": ["src/**/*.ts"], - "references": [ - { - "path": "../babel-preset/tsconfig.lib.json" - }, - { - "path": "../config/tsconfig.lib.json" - }, - { - "path": "../runtime/tsconfig.lib.json" - } - ] + "include": ["src/**/*.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e19e442b..1ec71b17 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,13 +31,13 @@ importers: version: 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/react-native': specifier: 22.0.4 - version: 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@types/node@20.19.25)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.29.0(jiti@2.4.2))(metro-config@0.83.3)(metro-resolver@0.83.3)(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))) + version: 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@types/node@20.19.25)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.29.0(jiti@2.4.2))(metro-config@0.83.3)(metro-resolver@0.84.2)(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/rollup': specifier: 22.0.4 version: 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3) '@nx/vite': specifier: 22.0.4 - version: 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0)) + version: 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4) '@nx/web': specifier: 22.0.4 version: 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) @@ -64,7 +64,7 @@ importers: version: 4.6.0(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0)) '@vitest/coverage-v8': specifier: ^3.0.5 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0)) + version: 3.2.4(vitest@3.2.4) '@vitest/ui': specifier: ^3.0.0 version: 3.2.4(vitest@3.2.4) @@ -245,15 +245,21 @@ importers: packages/bundler-metro: dependencies: + '@react-native-harness/babel-preset': + specifier: workspace:* + version: link:../babel-preset '@react-native-harness/config': specifier: workspace:* version: link:../config - '@react-native-harness/metro': + '@react-native-harness/runtime': specifier: workspace:* - version: link:../metro + version: link:../runtime '@react-native-harness/tools': specifier: workspace:* version: link:../tools + '@react-native/metro-config': + specifier: '*' + version: 0.82.1 connect: specifier: ^3.7.0 version: 3.7.0 @@ -270,9 +276,15 @@ importers: metro: specifier: '*' version: 0.83.3 + metro-cache: + specifier: '*' + version: 0.84.2 metro-config: specifier: '*' version: 0.83.3 + metro-resolver: + specifier: '*' + version: 0.84.2 packages/cli: dependencies: @@ -341,9 +353,6 @@ importers: '@react-native-harness/config': specifier: workspace:* version: link:../config - '@react-native-harness/metro': - specifier: workspace:* - version: link:../metro '@react-native-harness/platforms': specifier: workspace:* version: link:../platforms @@ -378,22 +387,10 @@ importers: packages/metro: dependencies: - '@react-native-harness/babel-preset': - specifier: workspace:* - version: link:../babel-preset - '@react-native-harness/config': - specifier: workspace:* - version: link:../config tslib: specifier: ^2.3.0 version: 2.8.1 devDependencies: - '@react-native-harness/runtime': - specifier: workspace:* - version: link:../runtime - '@types/babel__core': - specifier: ^7.20.5 - version: 7.20.5 metro: specifier: '*' version: 0.83.3 @@ -6108,6 +6105,10 @@ packages: resolution: {integrity: sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==} engines: {node: '>=20.19.4'} + metro-cache@0.84.2: + resolution: {integrity: sha512-jPX2fwOc/MmP2KRScSg2jFtVN9BTd+QN6j/3qZ+HIbEAsePLONozbKR2kCIBGvVeBTe7js48WXziI4+AdfwfFQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-config@0.83.3: resolution: {integrity: sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==} engines: {node: '>=20.19.4'} @@ -6116,6 +6117,10 @@ packages: resolution: {integrity: sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==} engines: {node: '>=20.19.4'} + metro-core@0.84.2: + resolution: {integrity: sha512-s9Ko372nzfbu5Y2uhWDlB/g3E6mba3Es95QzF/8IwNM4ynZgqM9rfnU0PR54onGvDGDfj44jbooSxaA1D09rDA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-file-map@0.83.3: resolution: {integrity: sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==} engines: {node: '>=20.19.4'} @@ -6128,6 +6133,10 @@ packages: resolution: {integrity: sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==} engines: {node: '>=20.19.4'} + metro-resolver@0.84.2: + resolution: {integrity: sha512-2i6OQJIv18+olvLnmcM20uhi1T729+25izZozqOugSaV0YGzMV/EXkYFqxkXC9iNsantGcI/w9PgaI89wLK6JQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-runtime@0.83.3: resolution: {integrity: sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==} engines: {node: '>=20.19.4'} @@ -10093,13 +10102,13 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@nx/detox@22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@types/node@20.19.25)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))': + '@nx/detox@22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@types/node@20.19.25)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))': dependencies: '@nx/devkit': 22.0.4(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/eslint': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/jest': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@types/node@20.19.25)(babel-plugin-macros@3.1.0)(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3) '@nx/js': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) - '@nx/react': 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))) + '@nx/react': 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))) tslib: 2.8.1 transitivePeerDependencies: - '@babel/core' @@ -10324,23 +10333,23 @@ snapshots: '@nx/nx-win32-x64-msvc@22.0.4': optional: true - '@nx/react-native@22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@types/node@20.19.25)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.29.0(jiti@2.4.2))(metro-config@0.83.3)(metro-resolver@0.83.3)(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))': + '@nx/react-native@22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@types/node@20.19.25)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.29.0(jiti@2.4.2))(metro-config@0.83.3)(metro-resolver@0.84.2)(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))': dependencies: '@nx/devkit': 22.0.4(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/eslint': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/js': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) - '@nx/react': 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))) + '@nx/react': 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))) ajv: 8.17.1 enhanced-resolve: 5.18.3 ignore: 5.3.2 metro-config: 0.83.3 - metro-resolver: 0.83.3 + metro-resolver: 0.84.2 picocolors: 1.1.1 tinyglobby: 0.2.15 tsconfig-paths: 4.2.0 tslib: 2.8.1 optionalDependencies: - '@nx/detox': 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@types/node@20.19.25)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))) + '@nx/detox': 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@types/node@20.19.25)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/rollup': 22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3) transitivePeerDependencies: - '@babel/core' @@ -10375,7 +10384,7 @@ snapshots: - webpack - webpack-cli - '@nx/react@22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))': + '@nx/react@22.0.4(@babel/core@7.27.4)(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/helpers@0.5.17)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))': dependencies: '@nx/devkit': 22.0.4(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/eslint': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) @@ -10393,7 +10402,7 @@ snapshots: semver: 7.7.2 tslib: 2.8.1 optionalDependencies: - '@nx/vite': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0)) + '@nx/vite': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4) transitivePeerDependencies: - '@babel/core' - '@babel/traverse' @@ -10454,7 +10463,7 @@ snapshots: - typescript - verdaccio - '@nx/vite@22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))': + '@nx/vite@22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17)))(typescript@5.9.3)(vite@7.2.2(@types/node@20.19.25)(jiti@2.4.2)(terser@5.42.0)(yaml@2.8.0))(vitest@3.2.4)': dependencies: '@nx/devkit': 22.0.4(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) '@nx/js': 22.0.4(@babel/traverse@7.27.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))(nx@22.0.4(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.17))(@swc/types@0.1.23)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.17))) @@ -10808,9 +10817,7 @@ snapshots: metro-config: 0.83.3 metro-runtime: 0.83.3 transitivePeerDependencies: - - bufferutil - supports-color - - utf-8-validate '@react-native/normalize-colors@0.74.89': {} @@ -11904,7 +11911,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.25)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.42.0)(yaml@2.8.0))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -15664,6 +15671,15 @@ snapshots: transitivePeerDependencies: - supports-color + metro-cache@0.84.2: + dependencies: + exponential-backoff: 3.1.2 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.84.2 + transitivePeerDependencies: + - supports-color + metro-config@0.83.3: dependencies: connect: 3.7.0 @@ -15685,6 +15701,12 @@ snapshots: lodash.throttle: 4.1.1 metro-resolver: 0.83.3 + metro-core@0.84.2: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.84.2 + metro-file-map@0.83.3: dependencies: debug: 4.4.1 @@ -15708,6 +15730,10 @@ snapshots: dependencies: flow-enums-runtime: 0.0.6 + metro-resolver@0.84.2: + dependencies: + flow-enums-runtime: 0.0.6 + metro-runtime@0.83.3: dependencies: '@babel/runtime': 7.27.6