@react-native-harness/metro 1.1.0 → 1.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/dist/__tests__/paths.test.d.ts +2 -0
  3. package/dist/__tests__/paths.test.d.ts.map +1 -0
  4. package/dist/__tests__/paths.test.js +43 -0
  5. package/dist/babel-transformer.d.ts +6 -0
  6. package/dist/babel-transformer.d.ts.map +1 -0
  7. package/dist/babel-transformer.js +31 -0
  8. package/dist/errors.d.ts +4 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +10 -0
  11. package/dist/getHarnessSerializer.d.ts +4 -0
  12. package/dist/getHarnessSerializer.d.ts.map +1 -0
  13. package/dist/getHarnessSerializer.js +33 -0
  14. package/dist/jest-globals-mock.d.ts +2 -0
  15. package/dist/jest-globals-mock.d.ts.map +1 -0
  16. package/dist/jest-globals-mock.js +5 -0
  17. package/dist/json-reporter.d.ts +2 -0
  18. package/dist/json-reporter.d.ts.map +1 -0
  19. package/dist/json-reporter.js +9 -0
  20. package/dist/manifest.d.ts +3 -0
  21. package/dist/manifest.d.ts.map +1 -0
  22. package/dist/manifest.js +22 -0
  23. package/dist/metro-cache.d.ts +4 -0
  24. package/dist/metro-cache.d.ts.map +1 -0
  25. package/dist/metro-cache.js +16 -0
  26. package/dist/moduleSystem.d.ts +2 -0
  27. package/dist/moduleSystem.d.ts.map +1 -0
  28. package/dist/moduleSystem.js +30 -0
  29. package/dist/node_modules/.cache/rn-harness/manifest.js +1 -0
  30. package/dist/paths.d.ts +5 -0
  31. package/dist/paths.d.ts.map +1 -0
  32. package/dist/paths.js +29 -0
  33. package/dist/resolver.d.ts +6 -0
  34. package/dist/resolver.d.ts.map +1 -0
  35. package/dist/resolver.js +30 -0
  36. package/dist/resolvers/composite-resolver.d.ts +3 -0
  37. package/dist/resolvers/composite-resolver.d.ts.map +1 -0
  38. package/dist/resolvers/composite-resolver.js +15 -0
  39. package/dist/resolvers/resolver.d.ts +8 -0
  40. package/dist/resolvers/resolver.d.ts.map +1 -0
  41. package/dist/resolvers/resolver.js +83 -0
  42. package/dist/resolvers/tsconfig-resolver.d.ts +18 -0
  43. package/dist/resolvers/tsconfig-resolver.d.ts.map +1 -0
  44. package/dist/resolvers/tsconfig-resolver.js +160 -0
  45. package/dist/resolvers/types.d.ts +4 -0
  46. package/dist/resolvers/types.d.ts.map +1 -0
  47. package/dist/resolvers/types.js +2 -0
  48. package/dist/utils.d.ts +4 -0
  49. package/dist/utils.d.ts.map +1 -0
  50. package/dist/utils.js +2 -0
  51. package/package.json +1 -1
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm run typecheck:*)",
5
+ "Bash(npm run:*)",
6
+ "Bash(npx tsc:*)"
7
+ ]
8
+ }
9
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=paths.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/paths.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
5
+ const node_os_1 = tslib_1.__importDefault(require("node:os"));
6
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
7
+ const vitest_1 = require("vitest");
8
+ const paths_js_1 = require("../paths.js");
9
+ const tempDirs = [];
10
+ const createTempProjectRoot = () => {
11
+ const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'rn-harness-metro-'));
12
+ tempDirs.push(tempDir);
13
+ return tempDir;
14
+ };
15
+ (0, vitest_1.afterEach)(() => {
16
+ for (const tempDir of tempDirs.splice(0)) {
17
+ node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
18
+ }
19
+ });
20
+ (0, vitest_1.describe)('metro paths', () => {
21
+ (0, vitest_1.it)('resolves the harness root under the project root', () => {
22
+ const projectRoot = createTempProjectRoot();
23
+ (0, vitest_1.expect)((0, paths_js_1.getHarnessRootPath)(projectRoot)).toBe(node_path_1.default.join(projectRoot, '.harness'));
24
+ (0, vitest_1.expect)((0, paths_js_1.getHarnessManifestPath)(projectRoot)).toBe(node_path_1.default.join(projectRoot, '.harness', 'manifest.js'));
25
+ (0, vitest_1.expect)((0, paths_js_1.getHarnessMetroCachePath)(projectRoot)).toBe(node_path_1.default.join(projectRoot, '.harness', 'metro-cache'));
26
+ });
27
+ (0, vitest_1.it)('returns false when the metro cache directory is missing', () => {
28
+ const projectRoot = createTempProjectRoot();
29
+ (0, vitest_1.expect)((0, paths_js_1.isMetroCacheReusable)(projectRoot)).toBe(false);
30
+ });
31
+ (0, vitest_1.it)('returns false when the metro cache directory is empty', () => {
32
+ const projectRoot = createTempProjectRoot();
33
+ node_fs_1.default.mkdirSync((0, paths_js_1.getHarnessMetroCachePath)(projectRoot), { recursive: true });
34
+ (0, vitest_1.expect)((0, paths_js_1.isMetroCacheReusable)(projectRoot)).toBe(false);
35
+ });
36
+ (0, vitest_1.it)('returns true when the metro cache directory contains entries', () => {
37
+ const projectRoot = createTempProjectRoot();
38
+ const metroCachePath = (0, paths_js_1.getHarnessMetroCachePath)(projectRoot);
39
+ node_fs_1.default.mkdirSync(metroCachePath, { recursive: true });
40
+ node_fs_1.default.writeFileSync(node_path_1.default.join(metroCachePath, 'entry'), 'cached');
41
+ (0, vitest_1.expect)((0, paths_js_1.isMetroCacheReusable)(projectRoot)).toBe(true);
42
+ });
43
+ });
@@ -0,0 +1,6 @@
1
+ import type { BabelTransformer } from 'metro-babel-transformer';
2
+ import { MetroConfig } from '@react-native/metro-config';
3
+ export declare const getHarnessBabelTransformerPath: (metroConfig: MetroConfig) => string;
4
+ declare const transform: BabelTransformer['transform'];
5
+ export { transform };
6
+ //# sourceMappingURL=babel-transformer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"babel-transformer.d.ts","sourceRoot":"","sources":["../src/babel-transformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,eAAO,MAAM,8BAA8B,GACzC,aAAa,WAAW,KACvB,MASF,CAAC;AAEF,QAAA,MAAM,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAoB5C,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transform = exports.getHarnessBabelTransformerPath = void 0;
4
+ const babel_preset_1 = require("@react-native-harness/babel-preset");
5
+ const getHarnessBabelTransformerPath = (metroConfig) => {
6
+ const upstreamTransformerPath = metroConfig.transformer?.babelTransformerPath;
7
+ if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') {
8
+ throw new Error('Upstream transformer path is not a string');
9
+ }
10
+ process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH = upstreamTransformerPath;
11
+ return require.resolve('./babel-transformer.js');
12
+ };
13
+ exports.getHarnessBabelTransformerPath = getHarnessBabelTransformerPath;
14
+ const transform = (args) => {
15
+ const { plugins } = args;
16
+ const upstreamTransformerPath = process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH;
17
+ if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') {
18
+ throw new Error('Upstream transformer path is not a string');
19
+ }
20
+ const upstreamTransformer = require(upstreamTransformerPath);
21
+ const pluginsWithHarness = [
22
+ // Checked against @babel/core's type definitions - plugins are an array of PluginItem
23
+ ...(plugins ?? []),
24
+ ...babel_preset_1.rnHarnessPlugins,
25
+ ];
26
+ return upstreamTransformer.transform({
27
+ ...args,
28
+ plugins: pluginsWithHarness,
29
+ });
30
+ };
31
+ exports.transform = transform;
@@ -0,0 +1,4 @@
1
+ export declare class CouldNotPatchModuleSystemError extends Error {
2
+ constructor();
3
+ }
4
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,8BAA+B,SAAQ,KAAK;;CAKxD"}
package/dist/errors.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CouldNotPatchModuleSystemError = void 0;
4
+ class CouldNotPatchModuleSystemError extends Error {
5
+ constructor() {
6
+ super('Could not patch module system');
7
+ this.name = 'CouldNotPatchModuleSystemError';
8
+ }
9
+ }
10
+ exports.CouldNotPatchModuleSystemError = CouldNotPatchModuleSystemError;
@@ -0,0 +1,4 @@
1
+ import type { MetroConfig } from 'metro-config';
2
+ export type Serializer = NonNullable<NonNullable<MetroConfig['serializer']>['customSerializer']>;
3
+ export declare const getHarnessSerializer: () => Serializer;
4
+ //# sourceMappingURL=getHarnessSerializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getHarnessSerializer.d.ts","sourceRoot":"","sources":["../src/getHarnessSerializer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,MAAM,UAAU,GAAG,WAAW,CAClC,WAAW,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAC3D,CAAC;AAYF,eAAO,MAAM,oBAAoB,QAAO,UA6BvC,CAAC"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHarnessSerializer = void 0;
4
+ const getBaseSerializer = () => {
5
+ const baseJSBundle = require('metro/private/DeltaBundler/Serializers/baseJSBundle');
6
+ const bundleToString = require('metro/private/lib/bundleToString');
7
+ return (entryPoint, prepend, graph, bundleOptions) => bundleToString(baseJSBundle(entryPoint, prepend, graph, bundleOptions));
8
+ };
9
+ const getAllFiles = require('metro/private/DeltaBundler/Serializers/getAllFiles');
10
+ const getHarnessSerializer = () => {
11
+ const baseSerializer = getBaseSerializer();
12
+ let mainEntryPointModules = new Set();
13
+ return async (entryPoint, preModules, graph, options) => {
14
+ if (options.modulesOnly) {
15
+ // This is most likely a test file
16
+ return baseSerializer(entryPoint, preModules, graph, {
17
+ ...options,
18
+ processModuleFilter: (mod) => {
19
+ if (options.processModuleFilter &&
20
+ !options.processModuleFilter(mod)) {
21
+ // If the module is not allowed by the processModuleFilter, skip it
22
+ return false;
23
+ }
24
+ // If the module is in the main entry point, skip it
25
+ return !mainEntryPointModules.has(mod.path);
26
+ },
27
+ });
28
+ }
29
+ mainEntryPointModules = new Set(await getAllFiles(preModules, graph, options));
30
+ return baseSerializer(entryPoint, preModules, graph, options);
31
+ };
32
+ };
33
+ exports.getHarnessSerializer = getHarnessSerializer;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jest-globals-mock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jest-globals-mock.d.ts","sourceRoot":"","sources":["../src/jest-globals-mock.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ // Mock module for @jest/globals imports
3
+ // This module throws immediately when imported to warn users about using Jest APIs
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ throw new Error("Importing '@jest/globals' is not supported in Harness tests. Import from 'react-native-harness' instead.");
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=json-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-reporter.d.ts","sourceRoot":"","sources":["../src/json-reporter.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const JsonReporter = require('metro/private/lib/JsonReporter');
4
+ class CustomReporter extends JsonReporter {
5
+ constructor() {
6
+ super(process.stdout);
7
+ }
8
+ }
9
+ module.exports = CustomReporter;
@@ -0,0 +1,3 @@
1
+ import { Config as HarnessConfig } from '@react-native-harness/config';
2
+ export declare const getHarnessManifest: (harnessConfig: HarnessConfig) => string;
3
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAWvE,eAAO,MAAM,kBAAkB,GAAI,eAAe,aAAa,KAAG,MAQjE,CAAC"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHarnessManifest = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
6
+ const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
7
+ const paths_1 = require("./paths");
8
+ const getManifestContent = (harnessConfig) => {
9
+ return `global.RN_HARNESS = {
10
+ appRegistryComponentName: '${harnessConfig.appRegistryComponentName}',
11
+ webSocketPort: ${harnessConfig.webSocketPort},
12
+ disableViewFlattening: ${harnessConfig.disableViewFlattening},
13
+ };`;
14
+ };
15
+ const getHarnessManifest = (harnessConfig) => {
16
+ const manifestContent = getManifestContent(harnessConfig);
17
+ const manifestPath = (0, paths_1.getHarnessManifestPath)();
18
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(manifestPath), { recursive: true });
19
+ node_fs_1.default.writeFileSync(manifestPath, manifestContent);
20
+ return manifestPath;
21
+ };
22
+ exports.getHarnessManifest = getHarnessManifest;
@@ -0,0 +1,4 @@
1
+ import { MetroCache } from 'metro-cache';
2
+ import type { CacheStoresConfigT } from 'metro-config';
3
+ export declare const getHarnessCacheStores: () => ((metroCache: MetroCache) => CacheStoresConfigT);
4
+ //# sourceMappingURL=metro-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metro-cache.d.ts","sourceRoot":"","sources":["../src/metro-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,UAAU,EAAE,MAAM,aAAa,CAAC;AAGrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGvD,eAAO,MAAM,qBAAqB,QAAO,CAAC,CACxC,UAAU,EAAE,UAAU,KACnB,kBAAkB,CAYtB,CAAC"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHarnessCacheStores = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
6
+ const paths_1 = require("./paths");
7
+ const getHarnessCacheStores = () => {
8
+ return ({ FileStore }) => {
9
+ const cacheRoot = (0, paths_1.getHarnessMetroCachePath)();
10
+ node_fs_1.default.mkdirSync(cacheRoot, { recursive: true });
11
+ return [
12
+ new FileStore({ root: cacheRoot }),
13
+ ];
14
+ };
15
+ };
16
+ exports.getHarnessCacheStores = getHarnessCacheStores;
@@ -0,0 +1,2 @@
1
+ export declare const patchModuleSystem: () => void;
2
+ //# sourceMappingURL=moduleSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moduleSystem.d.ts","sourceRoot":"","sources":["../src/moduleSystem.ts"],"names":[],"mappings":"AAqCA,eAAO,MAAM,iBAAiB,QAAO,IAOpC,CAAC"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.patchModuleSystem = void 0;
4
+ const errors_1 = require("./errors");
5
+ const optionalResolve = (path, from) => {
6
+ try {
7
+ return require.resolve(path, { paths: [from] });
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ };
13
+ const getMetroDefaultsPath = () => {
14
+ const reactNativeMetroConfigPath = require.resolve('@react-native/metro-config', { paths: [process.cwd()] });
15
+ const preExportsDefaults = optionalResolve('metro-config/src/defaults/defaults', reactNativeMetroConfigPath);
16
+ if (preExportsDefaults) {
17
+ return preExportsDefaults;
18
+ }
19
+ const privateDefaults = optionalResolve('metro-config/private/defaults/defaults', reactNativeMetroConfigPath);
20
+ if (privateDefaults) {
21
+ return privateDefaults;
22
+ }
23
+ throw new errors_1.CouldNotPatchModuleSystemError();
24
+ };
25
+ const patchModuleSystem = () => {
26
+ const metroConfigPath = getMetroDefaultsPath();
27
+ const metroConfig = require(metroConfigPath);
28
+ metroConfig.moduleSystem = require.resolve('@react-native-harness/runtime/moduleSystem');
29
+ };
30
+ exports.patchModuleSystem = patchModuleSystem;
@@ -0,0 +1 @@
1
+ global.RN_HARNESS = { appRegistryComponentName: 'Playground' };
@@ -0,0 +1,5 @@
1
+ export declare const getHarnessRootPath: (projectRoot?: string) => string;
2
+ export declare const getHarnessManifestPath: (projectRoot?: string) => string;
3
+ export declare const getHarnessMetroCachePath: (projectRoot?: string) => string;
4
+ export declare const isMetroCacheReusable: (projectRoot?: string) => boolean;
5
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,kBAAkB,GAAI,oBAA2B,KAAG,MACrB,CAAC;AAE7C,eAAO,MAAM,sBAAsB,GAAI,oBAA2B,KAAG,MACN,CAAC;AAEhE,eAAO,MAAM,wBAAwB,GACnC,oBAA2B,KAC1B,MAAyE,CAAC;AAE7E,eAAO,MAAM,oBAAoB,GAAI,oBAA2B,KAAG,OAclE,CAAC"}
package/dist/paths.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isMetroCacheReusable = exports.getHarnessMetroCachePath = exports.getHarnessManifestPath = exports.getHarnessRootPath = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
6
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
7
+ const HARNESS_DIRNAME = '.harness';
8
+ const MANIFEST_FILENAME = 'manifest.js';
9
+ const METRO_CACHE_DIRNAME = 'metro-cache';
10
+ const getHarnessRootPath = (projectRoot = process.cwd()) => node_path_1.default.resolve(projectRoot, HARNESS_DIRNAME);
11
+ exports.getHarnessRootPath = getHarnessRootPath;
12
+ const getHarnessManifestPath = (projectRoot = process.cwd()) => node_path_1.default.join((0, exports.getHarnessRootPath)(projectRoot), MANIFEST_FILENAME);
13
+ exports.getHarnessManifestPath = getHarnessManifestPath;
14
+ const getHarnessMetroCachePath = (projectRoot = process.cwd()) => node_path_1.default.join((0, exports.getHarnessRootPath)(projectRoot), METRO_CACHE_DIRNAME);
15
+ exports.getHarnessMetroCachePath = getHarnessMetroCachePath;
16
+ const isMetroCacheReusable = (projectRoot = process.cwd()) => {
17
+ const metroCachePath = (0, exports.getHarnessMetroCachePath)(projectRoot);
18
+ try {
19
+ const stat = node_fs_1.default.statSync(metroCachePath);
20
+ if (!stat.isDirectory()) {
21
+ return false;
22
+ }
23
+ return node_fs_1.default.readdirSync(metroCachePath).length > 0;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ };
29
+ exports.isMetroCacheReusable = isMetroCacheReusable;
@@ -0,0 +1,6 @@
1
+ import type { MetroConfig } from '@react-native/metro-config';
2
+ import type { Config as HarnessConfig } from '@react-native-harness/config';
3
+ type CustomResolver = NonNullable<NonNullable<MetroConfig['resolver']>['resolveRequest']>;
4
+ export declare const getHarnessResolver: (metroConfig: MetroConfig, harnessConfig: HarnessConfig) => CustomResolver;
5
+ export {};
6
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE5E,KAAK,cAAc,GAAG,WAAW,CAC/B,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,gBAAgB,CAAC,CACvD,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,aAAa,WAAW,EACxB,eAAe,aAAa,KAC3B,cAgCF,CAAC"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHarnessResolver = void 0;
4
+ const getHarnessResolver = (metroConfig, harnessConfig) => {
5
+ // Can be relative to the project root or absolute, need to normalize it
6
+ const resolvedEntryPointPath = require.resolve(harnessConfig.entryPoint, {
7
+ paths: [process.cwd()],
8
+ });
9
+ return (context, moduleName, platform) => {
10
+ const existingResolver = metroConfig.resolver?.resolveRequest ?? context.resolveRequest;
11
+ const resolvedModule = existingResolver(context, moduleName, platform);
12
+ // Replace the entry point with Harness
13
+ if (resolvedModule.type === 'sourceFile' &&
14
+ resolvedModule.filePath === resolvedEntryPointPath) {
15
+ return {
16
+ type: 'sourceFile',
17
+ filePath: require.resolve('@react-native-harness/runtime/entry-point'),
18
+ };
19
+ }
20
+ // Intercept @jest/globals imports and redirect to mock module
21
+ if (moduleName === '@jest/globals') {
22
+ return {
23
+ type: 'sourceFile',
24
+ filePath: require.resolve('./jest-globals-mock'),
25
+ };
26
+ }
27
+ return resolvedModule;
28
+ };
29
+ };
30
+ exports.getHarnessResolver = getHarnessResolver;
@@ -0,0 +1,3 @@
1
+ import type { HarnessResolver, MetroResolver } from './types';
2
+ export declare const createHarnessResolver: (resolvers: HarnessResolver[]) => MetroResolver;
3
+ //# sourceMappingURL=composite-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite-resolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/composite-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE9D,eAAO,MAAM,qBAAqB,GAChC,WAAW,eAAe,EAAE,KAC3B,aAWF,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createHarnessResolver = void 0;
4
+ const createHarnessResolver = (resolvers) => {
5
+ return (context, moduleName, platform) => {
6
+ for (const resolver of resolvers) {
7
+ const result = resolver(context, moduleName, platform);
8
+ if (result != null) {
9
+ return result;
10
+ }
11
+ }
12
+ return context.resolveRequest(context, moduleName, platform);
13
+ };
14
+ };
15
+ exports.createHarnessResolver = createHarnessResolver;
@@ -0,0 +1,8 @@
1
+ import type { MetroConfig } from '@react-native/metro-config';
2
+ import type { Config as HarnessConfig } from '@react-native-harness/config';
3
+ import type { HarnessResolver, MetroResolver } from './types';
4
+ export declare const createHarnessEntryPointResolver: (harnessConfig: HarnessConfig) => HarnessResolver;
5
+ export declare const createJestGlobalsResolver: () => HarnessResolver;
6
+ export declare const createJsxRuntimeResolver: () => HarnessResolver;
7
+ export declare const getHarnessResolver: (metroConfig: MetroConfig, harnessConfig: HarnessConfig) => MetroResolver;
8
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAI5E,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAS9D,eAAO,MAAM,+BAA+B,GAAI,eAAe,aAAa,KAAG,eA4B9E,CAAC;AAEF,eAAO,MAAM,yBAAyB,QAAO,eAY5C,CAAC;AAEF,eAAO,MAAM,wBAAwB,QAAO,eAyB3C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,aAAa,WAAW,EACxB,eAAe,aAAa,KAC3B,aAWF,CAAC"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHarnessResolver = exports.createJsxRuntimeResolver = exports.createJestGlobalsResolver = exports.createHarnessEntryPointResolver = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
6
+ const composite_resolver_1 = require("./composite-resolver");
7
+ const tsconfig_resolver_1 = require("./tsconfig-resolver");
8
+ // Safely resolves a path and strips its extension
9
+ const getExtensionlessAbsolutePath = (basePath, relativePath = '') => {
10
+ const fullPath = node_path_1.default.resolve(basePath, relativePath);
11
+ const parsed = node_path_1.default.parse(fullPath);
12
+ return node_path_1.default.join(parsed.dir, parsed.name);
13
+ };
14
+ const createHarnessEntryPointResolver = (harnessConfig) => {
15
+ const rootPath = node_path_1.default.resolve(process.cwd());
16
+ const expectedEntryPoint = getExtensionlessAbsolutePath(rootPath, harnessConfig.entryPoint);
17
+ const resolvedHarnessPath = require.resolve('@react-native-harness/runtime/entry-point');
18
+ return (context, moduleName, _platform) => {
19
+ // 1. Resolve the origin path of the file making the import
20
+ const currentOrigin = node_path_1.default.resolve(context.originModulePath);
21
+ // Fast Fail: If the import isn't happening from the root directory, skip it immediately
22
+ if (currentOrigin !== rootPath) {
23
+ return null;
24
+ }
25
+ // 2. Resolve the module being imported and strip its extension
26
+ // This safely normalizes './index', './index.js', 'index.js', etc.
27
+ const requestedModule = getExtensionlessAbsolutePath(currentOrigin, moduleName);
28
+ // 3. String comparison
29
+ if (requestedModule === expectedEntryPoint) {
30
+ return {
31
+ type: 'sourceFile',
32
+ filePath: resolvedHarnessPath,
33
+ };
34
+ }
35
+ return null;
36
+ };
37
+ };
38
+ exports.createHarnessEntryPointResolver = createHarnessEntryPointResolver;
39
+ const createJestGlobalsResolver = () => {
40
+ return (_context, moduleName, _platform) => {
41
+ // Intercept @jest/globals imports and redirect to mock module
42
+ if (moduleName === '@jest/globals') {
43
+ return {
44
+ type: 'sourceFile',
45
+ filePath: require.resolve('../jest-globals-mock'),
46
+ };
47
+ }
48
+ return null;
49
+ };
50
+ };
51
+ exports.createJestGlobalsResolver = createJestGlobalsResolver;
52
+ const createJsxRuntimeResolver = () => {
53
+ const resolvedJsxRuntimePath = require.resolve('@react-native-harness/runtime/jsx-runtime');
54
+ const resolvedJsxDevRuntimePath = require.resolve('@react-native-harness/runtime/jsx-dev-runtime');
55
+ return (_context, moduleName, _platform) => {
56
+ if (moduleName === '@react-native-harness/runtime/jsx-runtime') {
57
+ return {
58
+ type: 'sourceFile',
59
+ filePath: resolvedJsxRuntimePath,
60
+ };
61
+ }
62
+ if (moduleName === '@react-native-harness/runtime/jsx-dev-runtime') {
63
+ return {
64
+ type: 'sourceFile',
65
+ filePath: resolvedJsxDevRuntimePath,
66
+ };
67
+ }
68
+ return null;
69
+ };
70
+ };
71
+ exports.createJsxRuntimeResolver = createJsxRuntimeResolver;
72
+ const getHarnessResolver = (metroConfig, harnessConfig) => {
73
+ const userResolver = metroConfig.resolver?.resolveRequest;
74
+ const resolvers = [
75
+ (0, exports.createHarnessEntryPointResolver)(harnessConfig),
76
+ (0, exports.createJestGlobalsResolver)(),
77
+ (0, exports.createJsxRuntimeResolver)(),
78
+ (0, tsconfig_resolver_1.createTsConfigResolver)(process.cwd()),
79
+ userResolver,
80
+ ].filter((resolver) => !!resolver);
81
+ return (0, composite_resolver_1.createHarnessResolver)(resolvers);
82
+ };
83
+ exports.getHarnessResolver = getHarnessResolver;
@@ -0,0 +1,18 @@
1
+ import type { Resolution, CustomResolutionContext } from 'metro-resolver';
2
+ import type { HarnessResolver } from './types';
3
+ export type TsConfigPaths = {
4
+ paths: Record<string, string[]>;
5
+ baseUrl: string;
6
+ hasBaseUrl: boolean;
7
+ };
8
+ /**
9
+ * Load tsconfig.json or jsconfig.json and extract path mappings
10
+ */
11
+ export declare const loadTsConfigPaths: (projectRoot: string) => TsConfigPaths | null;
12
+ /**
13
+ * Resolve module using tsconfig path mappings
14
+ * Use this directly in your custom resolver
15
+ */
16
+ export declare const resolveWithTsConfigPaths: (tsConfig: TsConfigPaths, context: CustomResolutionContext, moduleName: string, platform: string | null) => Resolution | null;
17
+ export declare const createTsConfigResolver: (projectRoot: string) => HarnessResolver;
18
+ //# sourceMappingURL=tsconfig-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsconfig-resolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/tsconfig-resolver.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAO/C,MAAM,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACpB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC7B,aAAa,MAAM,KACjB,aAAa,GAAG,IA+BlB,CAAC;AAsFF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACpC,UAAU,aAAa,EACvB,SAAS,uBAAuB,EAChC,YAAY,MAAM,EAClB,UAAU,MAAM,GAAG,IAAI,KACrB,UAAU,GAAG,IAqCf,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAClC,aAAa,MAAM,KACjB,eAqBF,CAAC"}
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTsConfigResolver = exports.resolveWithTsConfigPaths = exports.loadTsConfigPaths = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path_1 = tslib_1.__importDefault(require("path"));
6
+ const fs_1 = tslib_1.__importDefault(require("fs"));
7
+ /**
8
+ * Load tsconfig.json or jsconfig.json and extract path mappings
9
+ */
10
+ const loadTsConfigPaths = (projectRoot) => {
11
+ const configFiles = ['tsconfig.json', 'jsconfig.json'];
12
+ for (const configFile of configFiles) {
13
+ const configPath = path_1.default.join(projectRoot, configFile);
14
+ if (!fs_1.default.existsSync(configPath))
15
+ continue;
16
+ try {
17
+ const content = fs_1.default.readFileSync(configPath, 'utf8');
18
+ // Strip comments without touching string literals
19
+ const jsonContent = stripJsonComments(content);
20
+ const config = JSON.parse(jsonContent);
21
+ const compilerOptions = config.compilerOptions || {};
22
+ const paths = compilerOptions.paths || {};
23
+ const baseUrl = compilerOptions.baseUrl;
24
+ if (Object.keys(paths).length > 0 || baseUrl) {
25
+ return {
26
+ paths,
27
+ baseUrl: baseUrl ? path_1.default.resolve(projectRoot, baseUrl) : projectRoot,
28
+ hasBaseUrl: !!baseUrl,
29
+ };
30
+ }
31
+ }
32
+ catch (error) {
33
+ console.warn(`Failed to parse ${configFile}:`, error);
34
+ }
35
+ }
36
+ return null;
37
+ };
38
+ exports.loadTsConfigPaths = loadTsConfigPaths;
39
+ const stripJsonComments = (input) => {
40
+ let result = '';
41
+ let inString = false;
42
+ let stringChar = '';
43
+ let isEscaped = false;
44
+ let inLineComment = false;
45
+ let inBlockComment = false;
46
+ for (let i = 0; i < input.length; i += 1) {
47
+ const char = input[i];
48
+ const nextChar = input[i + 1];
49
+ if (inLineComment) {
50
+ if (char === '\n') {
51
+ inLineComment = false;
52
+ result += char;
53
+ }
54
+ continue;
55
+ }
56
+ if (inBlockComment) {
57
+ if (char === '*' && nextChar === '/') {
58
+ inBlockComment = false;
59
+ i += 1;
60
+ }
61
+ continue;
62
+ }
63
+ if (inString) {
64
+ result += char;
65
+ if (!isEscaped && char === stringChar) {
66
+ inString = false;
67
+ stringChar = '';
68
+ }
69
+ isEscaped = !isEscaped && char === '\\';
70
+ continue;
71
+ }
72
+ if (char === '"' || char === "'") {
73
+ inString = true;
74
+ stringChar = char;
75
+ result += char;
76
+ isEscaped = false;
77
+ continue;
78
+ }
79
+ if (char === '/' && nextChar === '/') {
80
+ inLineComment = true;
81
+ i += 1;
82
+ continue;
83
+ }
84
+ if (char === '/' && nextChar === '*') {
85
+ inBlockComment = true;
86
+ i += 1;
87
+ continue;
88
+ }
89
+ result += char;
90
+ }
91
+ return result;
92
+ };
93
+ /**
94
+ * Match module name against tsconfig path pattern (supports wildcards)
95
+ */
96
+ const matchPattern = (pattern, moduleName) => {
97
+ const escapedPattern = pattern
98
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
99
+ .replace(/\*/g, '(.*)');
100
+ const regex = new RegExp(`^${escapedPattern}$`);
101
+ const match = moduleName.match(regex);
102
+ return {
103
+ matched: !!match,
104
+ captured: match?.[1] || '',
105
+ };
106
+ };
107
+ /**
108
+ * Resolve module using tsconfig path mappings
109
+ * Use this directly in your custom resolver
110
+ */
111
+ const resolveWithTsConfigPaths = (tsConfig, context, moduleName, platform) => {
112
+ const { paths, baseUrl, hasBaseUrl } = tsConfig;
113
+ const resolveRequest = context.resolveRequest;
114
+ if (!resolveRequest) {
115
+ return null;
116
+ }
117
+ // Try path mappings first
118
+ for (const [pattern, targets] of Object.entries(paths)) {
119
+ const { matched, captured } = matchPattern(pattern, moduleName);
120
+ if (!matched)
121
+ continue;
122
+ // Try each target
123
+ for (const target of targets) {
124
+ const resolvedTarget = target.replace('*', captured);
125
+ const absolutePath = path_1.default.resolve(baseUrl, resolvedTarget);
126
+ try {
127
+ return resolveRequest(context, absolutePath, platform);
128
+ }
129
+ catch {
130
+ continue;
131
+ }
132
+ }
133
+ }
134
+ // Try baseUrl for non-relative imports
135
+ if (hasBaseUrl && !moduleName.startsWith('.') && !moduleName.startsWith('/')) {
136
+ const absolutePath = path_1.default.resolve(baseUrl, moduleName);
137
+ try {
138
+ return resolveRequest(context, absolutePath, platform);
139
+ }
140
+ catch {
141
+ // Fall through
142
+ }
143
+ }
144
+ return null;
145
+ };
146
+ exports.resolveWithTsConfigPaths = resolveWithTsConfigPaths;
147
+ const createTsConfigResolver = (projectRoot) => {
148
+ const tsConfig = (0, exports.loadTsConfigPaths)(projectRoot);
149
+ return (context, moduleName, platform) => {
150
+ if (!tsConfig) {
151
+ return null;
152
+ }
153
+ if (!context.resolveRequest) {
154
+ return null;
155
+ }
156
+ const resolved = (0, exports.resolveWithTsConfigPaths)(tsConfig, context, moduleName, platform);
157
+ return resolved ?? null;
158
+ };
159
+ };
160
+ exports.createTsConfigResolver = createTsConfigResolver;
@@ -0,0 +1,4 @@
1
+ import type { CustomResolutionContext, Resolution } from 'metro-resolver';
2
+ export type HarnessResolver = (context: CustomResolutionContext, moduleName: string, platform: string | null) => Resolution | null;
3
+ export type MetroResolver = (context: CustomResolutionContext, moduleName: string, platform: string | null) => Resolution;
4
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/resolvers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE1E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,UAAU,GAAG,IAAI,CAAC;AACnI,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,UAAU,CAAC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export type NotReadOnly<T> = {
2
+ -readonly [K in keyof T]: T[K];
3
+ };
4
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAC/B,CAAC"}
package/dist/utils.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-harness/metro",
3
- "version": "1.1.0",
3
+ "version": "1.2.0-rc.1",
4
4
  "type": "commonjs",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",