@react-native-harness/metro 1.0.0 → 1.1.0-rc.2

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 (69) hide show
  1. package/README.md +6 -1
  2. package/dist/__tests__/withRnHarness.test.d.ts +2 -0
  3. package/dist/__tests__/withRnHarness.test.d.ts.map +1 -0
  4. package/dist/__tests__/withRnHarness.test.js +25 -0
  5. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  6. package/dist/withRnHarness.d.ts +1 -2
  7. package/dist/withRnHarness.d.ts.map +1 -1
  8. package/dist/withRnHarness.js +6 -76
  9. package/package.json +4 -9
  10. package/src/__tests__/withRnHarness.test.ts +32 -0
  11. package/src/withRnHarness.ts +9 -101
  12. package/tsconfig.json +0 -9
  13. package/tsconfig.lib.json +1 -12
  14. package/vite.config.ts +18 -0
  15. package/.claude/settings.local.json +0 -9
  16. package/dist/babel-transformer.d.ts +0 -6
  17. package/dist/babel-transformer.d.ts.map +0 -1
  18. package/dist/babel-transformer.js +0 -31
  19. package/dist/errors.d.ts +0 -4
  20. package/dist/errors.d.ts.map +0 -1
  21. package/dist/errors.js +0 -10
  22. package/dist/getHarnessSerializer.d.ts +0 -4
  23. package/dist/getHarnessSerializer.d.ts.map +0 -1
  24. package/dist/getHarnessSerializer.js +0 -33
  25. package/dist/jest-globals-mock.d.ts +0 -2
  26. package/dist/jest-globals-mock.d.ts.map +0 -1
  27. package/dist/jest-globals-mock.js +0 -5
  28. package/dist/json-reporter.d.ts +0 -2
  29. package/dist/json-reporter.d.ts.map +0 -1
  30. package/dist/json-reporter.js +0 -9
  31. package/dist/manifest.d.ts +0 -3
  32. package/dist/manifest.d.ts.map +0 -1
  33. package/dist/manifest.js +0 -21
  34. package/dist/metro-cache.d.ts +0 -4
  35. package/dist/metro-cache.d.ts.map +0 -1
  36. package/dist/metro-cache.js +0 -16
  37. package/dist/moduleSystem.d.ts +0 -2
  38. package/dist/moduleSystem.d.ts.map +0 -1
  39. package/dist/moduleSystem.js +0 -30
  40. package/dist/node_modules/.cache/rn-harness/manifest.js +0 -1
  41. package/dist/resolver.d.ts +0 -6
  42. package/dist/resolver.d.ts.map +0 -1
  43. package/dist/resolver.js +0 -30
  44. package/dist/resolvers/composite-resolver.d.ts +0 -3
  45. package/dist/resolvers/composite-resolver.d.ts.map +0 -1
  46. package/dist/resolvers/composite-resolver.js +0 -15
  47. package/dist/resolvers/resolver.d.ts +0 -8
  48. package/dist/resolvers/resolver.d.ts.map +0 -1
  49. package/dist/resolvers/resolver.js +0 -83
  50. package/dist/resolvers/tsconfig-resolver.d.ts +0 -18
  51. package/dist/resolvers/tsconfig-resolver.d.ts.map +0 -1
  52. package/dist/resolvers/tsconfig-resolver.js +0 -160
  53. package/dist/resolvers/types.d.ts +0 -4
  54. package/dist/resolvers/types.d.ts.map +0 -1
  55. package/dist/resolvers/types.js +0 -2
  56. package/dist/utils.d.ts +0 -4
  57. package/dist/utils.d.ts.map +0 -1
  58. package/dist/utils.js +0 -2
  59. package/src/babel-transformer.ts +0 -40
  60. package/src/errors.ts +0 -6
  61. package/src/getHarnessSerializer.ts +0 -46
  62. package/src/jest-globals-mock.ts +0 -6
  63. package/src/manifest.ts +0 -24
  64. package/src/metro-cache.ts +0 -24
  65. package/src/resolvers/composite-resolver.ts +0 -16
  66. package/src/resolvers/resolver.ts +0 -100
  67. package/src/resolvers/tsconfig-resolver.ts +0 -210
  68. package/src/resolvers/types.ts +0 -4
  69. package/src/utils.ts +0 -3
@@ -1,160 +0,0 @@
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;
@@ -1,4 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
package/dist/utils.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export type NotReadOnly<T> = {
2
- -readonly [K in keyof T]: T[K];
3
- };
4
- //# sourceMappingURL=utils.d.ts.map
@@ -1 +0,0 @@
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 DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,40 +0,0 @@
1
- import type { BabelTransformer } from 'metro-babel-transformer';
2
- import { rnHarnessPlugins } from '@react-native-harness/babel-preset';
3
- import { MetroConfig } from '@react-native/metro-config';
4
-
5
- export const getHarnessBabelTransformerPath = (
6
- metroConfig: MetroConfig
7
- ): string => {
8
- const upstreamTransformerPath = metroConfig.transformer?.babelTransformerPath;
9
-
10
- if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') {
11
- throw new Error('Upstream transformer path is not a string');
12
- }
13
-
14
- process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH = upstreamTransformerPath;
15
- return require.resolve('./babel-transformer.js');
16
- };
17
-
18
- const transform: BabelTransformer['transform'] = (args) => {
19
- const { plugins } = args;
20
- const upstreamTransformerPath =
21
- process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH;
22
-
23
- if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') {
24
- throw new Error('Upstream transformer path is not a string');
25
- }
26
-
27
- const upstreamTransformer = require(upstreamTransformerPath);
28
- const pluginsWithHarness = [
29
- // Checked against @babel/core's type definitions - plugins are an array of PluginItem
30
- ...((plugins as unknown[]) ?? []),
31
- ...rnHarnessPlugins,
32
- ];
33
-
34
- return upstreamTransformer.transform({
35
- ...args,
36
- plugins: pluginsWithHarness,
37
- });
38
- };
39
-
40
- export { transform };
package/src/errors.ts DELETED
@@ -1,6 +0,0 @@
1
- export class CouldNotPatchModuleSystemError extends Error {
2
- constructor() {
3
- super('Could not patch module system');
4
- this.name = 'CouldNotPatchModuleSystemError';
5
- }
6
- }
@@ -1,46 +0,0 @@
1
- import type { MetroConfig } from 'metro-config';
2
-
3
- export type Serializer = NonNullable<
4
- NonNullable<MetroConfig['serializer']>['customSerializer']
5
- >;
6
-
7
- const getBaseSerializer = (): Serializer => {
8
- const baseJSBundle = require('metro/private/DeltaBundler/Serializers/baseJSBundle');
9
- const bundleToString = require('metro/private/lib/bundleToString');
10
-
11
- return (entryPoint, prepend, graph, bundleOptions) =>
12
- bundleToString(baseJSBundle(entryPoint, prepend, graph, bundleOptions));
13
- };
14
-
15
- const getAllFiles = require('metro/private/DeltaBundler/Serializers/getAllFiles');
16
-
17
- export const getHarnessSerializer = (): Serializer => {
18
- const baseSerializer = getBaseSerializer();
19
- let mainEntryPointModules = new Set<string>();
20
-
21
- return async (entryPoint, preModules, graph, options) => {
22
- if (options.modulesOnly) {
23
- // This is most likely a test file
24
- return baseSerializer(entryPoint, preModules, graph, {
25
- ...options,
26
- processModuleFilter: (mod) => {
27
- if (
28
- options.processModuleFilter &&
29
- !options.processModuleFilter(mod)
30
- ) {
31
- // If the module is not allowed by the processModuleFilter, skip it
32
- return false;
33
- }
34
-
35
- // If the module is in the main entry point, skip it
36
- return !mainEntryPointModules.has(mod.path);
37
- },
38
- });
39
- }
40
-
41
- mainEntryPointModules = new Set(
42
- await getAllFiles(preModules, graph, options)
43
- );
44
- return baseSerializer(entryPoint, preModules, graph, options);
45
- };
46
- };
@@ -1,6 +0,0 @@
1
- // Mock module for @jest/globals imports
2
- // This module throws immediately when imported to warn users about using Jest APIs
3
-
4
- throw new Error(
5
- "Importing '@jest/globals' is not supported in Harness tests. Import from 'react-native-harness' instead."
6
- );
package/src/manifest.ts DELETED
@@ -1,24 +0,0 @@
1
- import path from 'node:path';
2
- import fs from 'node:fs';
3
- import { Config as HarnessConfig } from '@react-native-harness/config';
4
-
5
- const getManifestContent = (harnessConfig: HarnessConfig): string => {
6
- return `global.RN_HARNESS = {
7
- appRegistryComponentName: '${harnessConfig.appRegistryComponentName}',
8
- webSocketPort: ${harnessConfig.webSocketPort},
9
- disableViewFlattening: ${harnessConfig.disableViewFlattening},
10
- };`;
11
- };
12
-
13
- export const getHarnessManifest = (harnessConfig: HarnessConfig): string => {
14
- const manifestContent = getManifestContent(harnessConfig);
15
- const manifestPath = path.resolve(
16
- process.cwd(),
17
- 'node_modules/.cache/rn-harness/manifest.js'
18
- );
19
-
20
- fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
21
- fs.writeFileSync(manifestPath, manifestContent);
22
-
23
- return manifestPath;
24
- };
@@ -1,24 +0,0 @@
1
- import { CacheStore, MetroCache } from 'metro-cache';
2
- import type { MixedOutput, TransformResult } from 'metro';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import type { CacheStoresConfigT } from 'metro-config';
6
-
7
- const CACHE_ROOT = path.resolve(
8
- process.cwd(),
9
- 'node_modules/.cache/rn-harness/metro-cache'
10
- );
11
-
12
- export const getHarnessCacheStores = (): ((
13
- metroCache: MetroCache
14
- ) => CacheStoresConfigT) => {
15
- return ({ FileStore }) => {
16
- fs.mkdirSync(CACHE_ROOT, { recursive: true });
17
-
18
- return [
19
- new FileStore({ root: CACHE_ROOT }) as CacheStore<
20
- TransformResult<MixedOutput>
21
- >,
22
- ];
23
- };
24
- };
@@ -1,16 +0,0 @@
1
- import type { HarnessResolver, MetroResolver } from './types';
2
-
3
- export const createHarnessResolver = (
4
- resolvers: HarnessResolver[]
5
- ): MetroResolver => {
6
- return (context, moduleName, platform) => {
7
- for (const resolver of resolvers) {
8
- const result = resolver(context, moduleName, platform);
9
- if (result != null) {
10
- return result;
11
- }
12
- }
13
-
14
- return context.resolveRequest(context, moduleName, platform);
15
- };
16
- };
@@ -1,100 +0,0 @@
1
- import type { MetroConfig } from '@react-native/metro-config';
2
- import type { Config as HarnessConfig } from '@react-native-harness/config';
3
- import path from 'node:path';
4
- import { createHarnessResolver } from './composite-resolver';
5
- import { createTsConfigResolver } from './tsconfig-resolver';
6
- import type { HarnessResolver, MetroResolver } from './types';
7
-
8
- // Safely resolves a path and strips its extension
9
- const getExtensionlessAbsolutePath = (basePath: string, relativePath = ''): string => {
10
- const fullPath = path.resolve(basePath, relativePath);
11
- const parsed = path.parse(fullPath);
12
- return path.join(parsed.dir, parsed.name);
13
- }
14
-
15
- export const createHarnessEntryPointResolver = (harnessConfig: HarnessConfig): HarnessResolver => {
16
- const rootPath = path.resolve(process.cwd());
17
- const expectedEntryPoint = getExtensionlessAbsolutePath(rootPath, harnessConfig.entryPoint);
18
- const resolvedHarnessPath = require.resolve('@react-native-harness/runtime/entry-point');
19
-
20
- return (context, moduleName, _platform) => {
21
- // 1. Resolve the origin path of the file making the import
22
- const currentOrigin = path.resolve(context.originModulePath);
23
-
24
- // Fast Fail: If the import isn't happening from the root directory, skip it immediately
25
- if (currentOrigin !== rootPath) {
26
- return null;
27
- }
28
-
29
- // 2. Resolve the module being imported and strip its extension
30
- // This safely normalizes './index', './index.js', 'index.js', etc.
31
- const requestedModule = getExtensionlessAbsolutePath(currentOrigin, moduleName);
32
-
33
- // 3. String comparison
34
- if (requestedModule === expectedEntryPoint) {
35
- return {
36
- type: 'sourceFile',
37
- filePath: resolvedHarnessPath,
38
- };
39
- }
40
-
41
- return null;
42
- };
43
- };
44
-
45
- export const createJestGlobalsResolver = (): HarnessResolver => {
46
- return (_context, moduleName, _platform) => {
47
- // Intercept @jest/globals imports and redirect to mock module
48
- if (moduleName === '@jest/globals') {
49
- return {
50
- type: 'sourceFile',
51
- filePath: require.resolve('../jest-globals-mock'),
52
- };
53
- }
54
-
55
- return null;
56
- };
57
- };
58
-
59
- export const createJsxRuntimeResolver = (): HarnessResolver => {
60
- const resolvedJsxRuntimePath = require.resolve(
61
- '@react-native-harness/runtime/jsx-runtime'
62
- );
63
- const resolvedJsxDevRuntimePath = require.resolve(
64
- '@react-native-harness/runtime/jsx-dev-runtime'
65
- );
66
-
67
- return (_context, moduleName, _platform) => {
68
- if (moduleName === '@react-native-harness/runtime/jsx-runtime') {
69
- return {
70
- type: 'sourceFile',
71
- filePath: resolvedJsxRuntimePath,
72
- };
73
- }
74
-
75
- if (moduleName === '@react-native-harness/runtime/jsx-dev-runtime') {
76
- return {
77
- type: 'sourceFile',
78
- filePath: resolvedJsxDevRuntimePath,
79
- };
80
- }
81
-
82
- return null;
83
- };
84
- };
85
-
86
- export const getHarnessResolver = (
87
- metroConfig: MetroConfig,
88
- harnessConfig: HarnessConfig
89
- ): MetroResolver => {
90
- const userResolver = metroConfig.resolver?.resolveRequest;
91
- const resolvers: HarnessResolver[] = [
92
- createHarnessEntryPointResolver(harnessConfig),
93
- createJestGlobalsResolver(),
94
- createJsxRuntimeResolver(),
95
- createTsConfigResolver(process.cwd()),
96
- userResolver,
97
- ].filter((resolver): resolver is HarnessResolver => !!resolver);
98
-
99
- return createHarnessResolver(resolvers);
100
- };
@@ -1,210 +0,0 @@
1
- import path from 'path';
2
- import fs from 'fs';
3
- import type { Resolution, CustomResolutionContext } from 'metro-resolver';
4
- import type { HarnessResolver } from './types';
5
-
6
- // This resolver is based on the Expo's implementation.
7
- // https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/start/server/metro/withMetroMultiPlatform.ts
8
- // The reason to have it in Harness is that Expo doesn't set the resolveRequest function in the context.
9
- // In order for tsconfig's paths to work, we need to recreate this logic ourselves.
10
-
11
- export type TsConfigPaths = {
12
- paths: Record<string, string[]>;
13
- baseUrl: string;
14
- hasBaseUrl: boolean;
15
- }
16
-
17
- /**
18
- * Load tsconfig.json or jsconfig.json and extract path mappings
19
- */
20
- export const loadTsConfigPaths = (
21
- projectRoot: string
22
- ): TsConfigPaths | null => {
23
- const configFiles = ['tsconfig.json', 'jsconfig.json'];
24
-
25
- for (const configFile of configFiles) {
26
- const configPath = path.join(projectRoot, configFile);
27
-
28
- if (!fs.existsSync(configPath)) continue;
29
-
30
- try {
31
- const content = fs.readFileSync(configPath, 'utf8');
32
- // Strip comments without touching string literals
33
- const jsonContent = stripJsonComments(content);
34
- const config = JSON.parse(jsonContent);
35
-
36
- const compilerOptions = config.compilerOptions || {};
37
- const paths = compilerOptions.paths || {};
38
- const baseUrl = compilerOptions.baseUrl;
39
-
40
- if (Object.keys(paths).length > 0 || baseUrl) {
41
- return {
42
- paths,
43
- baseUrl: baseUrl ? path.resolve(projectRoot, baseUrl) : projectRoot,
44
- hasBaseUrl: !!baseUrl,
45
- };
46
- }
47
- } catch (error) {
48
- console.warn(`Failed to parse ${configFile}:`, error);
49
- }
50
- }
51
-
52
- return null;
53
- };
54
-
55
- const stripJsonComments = (input: string): string => {
56
- let result = '';
57
- let inString = false;
58
- let stringChar = '';
59
- let isEscaped = false;
60
- let inLineComment = false;
61
- let inBlockComment = false;
62
-
63
- for (let i = 0; i < input.length; i += 1) {
64
- const char = input[i];
65
- const nextChar = input[i + 1];
66
-
67
- if (inLineComment) {
68
- if (char === '\n') {
69
- inLineComment = false;
70
- result += char;
71
- }
72
- continue;
73
- }
74
-
75
- if (inBlockComment) {
76
- if (char === '*' && nextChar === '/') {
77
- inBlockComment = false;
78
- i += 1;
79
- }
80
- continue;
81
- }
82
-
83
- if (inString) {
84
- result += char;
85
- if (!isEscaped && char === stringChar) {
86
- inString = false;
87
- stringChar = '';
88
- }
89
- isEscaped = !isEscaped && char === '\\';
90
- continue;
91
- }
92
-
93
- if (char === '"' || char === "'") {
94
- inString = true;
95
- stringChar = char;
96
- result += char;
97
- isEscaped = false;
98
- continue;
99
- }
100
-
101
- if (char === '/' && nextChar === '/') {
102
- inLineComment = true;
103
- i += 1;
104
- continue;
105
- }
106
-
107
- if (char === '/' && nextChar === '*') {
108
- inBlockComment = true;
109
- i += 1;
110
- continue;
111
- }
112
-
113
- result += char;
114
- }
115
-
116
- return result;
117
- };
118
-
119
- /**
120
- * Match module name against tsconfig path pattern (supports wildcards)
121
- */
122
- const matchPattern = (
123
- pattern: string,
124
- moduleName: string
125
- ): { matched: boolean; captured: string } => {
126
- const escapedPattern = pattern
127
- .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
128
- .replace(/\*/g, '(.*)');
129
-
130
- const regex = new RegExp(`^${escapedPattern}$`);
131
- const match = moduleName.match(regex);
132
-
133
- return {
134
- matched: !!match,
135
- captured: match?.[1] || '',
136
- };
137
- };
138
-
139
- /**
140
- * Resolve module using tsconfig path mappings
141
- * Use this directly in your custom resolver
142
- */
143
- export const resolveWithTsConfigPaths = (
144
- tsConfig: TsConfigPaths,
145
- context: CustomResolutionContext,
146
- moduleName: string,
147
- platform: string | null
148
- ): Resolution | null => {
149
- const { paths, baseUrl, hasBaseUrl } = tsConfig;
150
- const resolveRequest = context.resolveRequest;
151
-
152
- if (!resolveRequest) {
153
- return null;
154
- }
155
-
156
- // Try path mappings first
157
- for (const [pattern, targets] of Object.entries(paths)) {
158
- const { matched, captured } = matchPattern(pattern, moduleName);
159
- if (!matched) continue;
160
-
161
- // Try each target
162
- for (const target of targets) {
163
- const resolvedTarget = target.replace('*', captured);
164
- const absolutePath = path.resolve(baseUrl, resolvedTarget);
165
-
166
- try {
167
- return resolveRequest(context, absolutePath, platform);
168
- } catch {
169
- continue;
170
- }
171
- }
172
- }
173
-
174
- // Try baseUrl for non-relative imports
175
- if (hasBaseUrl && !moduleName.startsWith('.') && !moduleName.startsWith('/')) {
176
- const absolutePath = path.resolve(baseUrl, moduleName);
177
- try {
178
- return resolveRequest(context, absolutePath, platform);
179
- } catch {
180
- // Fall through
181
- }
182
- }
183
-
184
- return null;
185
- };
186
-
187
- export const createTsConfigResolver = (
188
- projectRoot: string
189
- ): HarnessResolver => {
190
- const tsConfig = loadTsConfigPaths(projectRoot);
191
-
192
- return (context, moduleName, platform) => {
193
- if (!tsConfig) {
194
- return null;
195
- }
196
-
197
- if (!context.resolveRequest) {
198
- return null;
199
- }
200
-
201
- const resolved = resolveWithTsConfigPaths(
202
- tsConfig,
203
- context,
204
- moduleName,
205
- platform
206
- );
207
-
208
- return resolved ?? null;
209
- };
210
- };
@@ -1,4 +0,0 @@
1
- import type { CustomResolutionContext, Resolution } from 'metro-resolver';
2
-
3
- export type HarnessResolver = (context: CustomResolutionContext, moduleName: string, platform: string | null) => Resolution | null;
4
- export type MetroResolver = (context: CustomResolutionContext, moduleName: string, platform: string | null) => Resolution;
package/src/utils.ts DELETED
@@ -1,3 +0,0 @@
1
- export type NotReadOnly<T> = {
2
- -readonly [K in keyof T]: T[K];
3
- };