@granite-js/vitest 1.0.21 → 1.0.23

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.23
4
+
5
+ ### Patch Changes
6
+
7
+ - faf7ec4: feat: change react-native package tree to object + virtual
8
+
9
+ ## 1.0.22
10
+
11
+ ### Patch Changes
12
+
13
+ - 3553158: feat(vitest): fast-flow-transform -> flow-remove-types
14
+
3
15
  ## 1.0.21
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -21,31 +21,37 @@ export default defineConfig({
21
21
  });
22
22
  ```
23
23
 
24
- `reactNative()` injects the React Native mirror aliases, the Vitest `node` environment, `globals: true`, Jest-like test globs, the packaged React Native runtime/setup files, and Jest-preset-style asset mocks.
24
+ `reactNative()` injects the React Native manifest resolver, the Vitest `node` environment, `globals: true`, Jest-like test globs, the packaged React Native runtime/setup files, and Jest-preset-style asset mocks.
25
25
 
26
- ## Mirror Cache
26
+ ## Transform Cache
27
27
 
28
- The React Native mirror is stored as a content-addressed local cache:
28
+ React Native sources are stored as a content-addressed flat transform cache:
29
29
 
30
30
  - cache root: `<cacheDir>/vitest-react-native-cache`
31
- - entries: `<cacheDir>/vitest-react-native-cache/entries/<cache-key>/packages`
31
+ - transformed objects: `<cacheDir>/vitest-react-native-cache/entries/<cache-key>/objects/<content-hash>.<ext>`
32
+ - manifest: `<cacheDir>/vitest-react-native-cache/entries/<cache-key>/manifest.json`
32
33
  - metadata: `<cacheDir>/vitest-react-native-cache/entries/<cache-key>/meta.json`
33
34
 
34
35
  By default, `cacheDir` is Vitest's `.vitest` directory unless your `vitest.config.*` overrides it.
35
36
 
36
37
  The cache key is derived from:
37
38
 
38
- - the mirrored `react-native` and installed RN-family allowlist package contents
39
+ - the `react-native` and installed RN-family allowlist package contents
39
40
  - the local mirror/transform implementation
40
41
  - transform dependency versions
41
42
 
42
- If the cache key matches, `reactNative()` reuses the existing mirror immediately instead of rebuilding it.
43
+ If the cache key matches, `reactNative()` reuses the existing transform cache immediately instead of rebuilding it.
44
+ Only one completed React Native transform cache entry is retained per cache directory. When the cache key changes,
45
+ the previous entry is removed before the new cache is transformed and materialized.
43
46
 
44
- Local garbage collection runs opportunistically while resolving the mirror cache:
47
+ The cache intentionally does not materialize package trees such as `packages/react-native/package.json`.
48
+ React Native package and relative imports are resolved through `manifest.json` and then redirected to flat
49
+ object files, so file scanners do not discover duplicate package boundaries inside the cache.
50
+
51
+ Local garbage collection runs opportunistically while resolving the transform cache:
45
52
 
46
- - stale entries older than 7 days are removed
47
53
  - temporary entries left behind by interrupted builds are removed
48
- - the cache is trimmed back to roughly 1 GB using least-recently-used eviction
54
+ - inactive completed entries are removed
49
55
 
50
56
  ## React Native Jest Preset Parity
51
57
 
package/dist/index.d.ts CHANGED
@@ -7,11 +7,43 @@ declare const REACT_NATIVE_ASSET_EXTENSIONS: readonly ["bmp", "gif", "jpg", "jpe
7
7
  declare const DEFAULT_PLATFORM = "ios";
8
8
  declare const GRANITE_VITEST_RN_CACHE_DIRECTORY = "vitest-react-native-cache";
9
9
  declare const GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY = "entries";
10
+ declare const GRANITE_VITEST_RN_OBJECTS_DIRECTORY = "objects";
11
+ declare const GRANITE_VITEST_RN_MANIFEST_FILENAME = "manifest.json";
10
12
  declare const GRANITE_VITEST_RN_PACKAGES_DIRECTORY = "packages";
11
13
  declare const REACT_NATIVE_PLATFORMS: readonly ["android", "ios", "native"];
14
+ type ReactNativePackageEntry = {
15
+ packageName: string;
16
+ packageRoot: string;
17
+ requireRoot: string;
18
+ };
19
+ type ReactNativeTransformCacheManifest = {
20
+ cacheKey: string;
21
+ createdAt: string;
22
+ lastAccessedAt: string;
23
+ objectsRoot: string;
24
+ packageRoots: string[];
25
+ packages: ReactNativePackageEntry[];
26
+ sourceToObject: Record<string, string>;
27
+ objectToSource: Record<string, string>;
28
+ transformDependencyVersions: {
29
+ flowRemoveTypes: string;
30
+ };
31
+ transformImplementationHash: string;
32
+ };
33
+ type ReactNativeTransformCache = {
34
+ entryRoot: string;
35
+ manifest: ReactNativeTransformCacheManifest;
36
+ manifestPath: string;
37
+ objectsRoot: string;
38
+ };
39
+ type ReactNativeResolvedModule = {
40
+ objectPath: string;
41
+ sourcePath: string;
42
+ };
12
43
  declare function resolvePackageRoot(packageName: string, requireRoot: string): string;
13
44
  declare function resolveReactNativePackageRoots(workspaceRoot: string): string[];
14
- declare function synthesizeDefaultPlatformFiles(destinationRoot: string): void;
45
+ declare function resolveReactNativeModuleFromManifest(request: string, importer: string | undefined, manifest: ReactNativeTransformCacheManifest): ReactNativeResolvedModule | null;
46
+ declare function buildReactNativeTransformCache(workspaceRoot: string, resolvedCacheDir?: string): Promise<ReactNativeTransformCache>;
15
47
  declare function buildReactNativeMirror(workspaceRoot: string, resolvedCacheDir?: string): Promise<string>;
16
48
  //#endregion
17
49
  //#region src/transpile.d.ts
@@ -33,4 +65,4 @@ declare const _default: {
33
65
  resolveReactNativeSetupFiles: typeof resolveReactNativeSetupFiles;
34
66
  };
35
67
  //#endregion
36
- export { DEFAULT_PLATFORM, GRANITE_VITEST_RN_CACHE_DIRECTORY, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY, GRANITE_VITEST_RN_PACKAGES_DIRECTORY, JEST_LIKE_TEST_PATTERNS, REACT_NATIVE_ASSET_EXTENSIONS, REACT_NATIVE_EXPORT_CONDITIONS, REACT_NATIVE_PLATFORMS, REACT_NATIVE_RESOLVE_EXTENSIONS, REACT_NATIVE_TRANSFORM_ALLOWLIST, REACT_NATIVE_TRANSFORM_EXTENSIONS, buildReactNativeMirror, _default as default, reactNative, resolvePackageRoot, resolveReactNativePackageRoots, resolveReactNativeSetupFiles, shouldInlineReactNativeDependency, shouldTransformReactNativeFile, synthesizeDefaultPlatformFiles };
68
+ export { DEFAULT_PLATFORM, GRANITE_VITEST_RN_CACHE_DIRECTORY, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY, GRANITE_VITEST_RN_MANIFEST_FILENAME, GRANITE_VITEST_RN_OBJECTS_DIRECTORY, GRANITE_VITEST_RN_PACKAGES_DIRECTORY, JEST_LIKE_TEST_PATTERNS, REACT_NATIVE_ASSET_EXTENSIONS, REACT_NATIVE_EXPORT_CONDITIONS, REACT_NATIVE_PLATFORMS, REACT_NATIVE_RESOLVE_EXTENSIONS, REACT_NATIVE_TRANSFORM_ALLOWLIST, REACT_NATIVE_TRANSFORM_EXTENSIONS, buildReactNativeMirror, buildReactNativeTransformCache, _default as default, reactNative, resolvePackageRoot, resolveReactNativeModuleFromManifest, resolveReactNativePackageRoots, resolveReactNativeSetupFiles, shouldInlineReactNativeDependency, shouldTransformReactNativeFile };
package/dist/index.js CHANGED
@@ -1,401 +1,9 @@
1
- import { n as REACT_NATIVE_ASSET_MODULE_ID_PATTERN, t as REACT_NATIVE_ASSET_EXTENSIONS } from "./assets-DoucBOz0.js";
2
- import { createRequire } from "node:module";
1
+ import { _ as shouldTransformReactNativeFile, a as GRANITE_VITEST_RN_OBJECTS_DIRECTORY, c as buildReactNativeMirror, d as resolvePackageRoot, f as resolveReactNativeModuleFromManifest, g as shouldInlineReactNativeDependency, h as REACT_NATIVE_TRANSFORM_EXTENSIONS, i as GRANITE_VITEST_RN_MANIFEST_FILENAME, l as buildReactNativeTransformCache, m as REACT_NATIVE_TRANSFORM_ALLOWLIST, n as GRANITE_VITEST_RN_CACHE_DIRECTORY, o as GRANITE_VITEST_RN_PACKAGES_DIRECTORY, p as resolveReactNativePackageRoots, r as GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY, s as REACT_NATIVE_PLATFORMS, t as DEFAULT_PLATFORM, v as REACT_NATIVE_ASSET_EXTENSIONS, y as REACT_NATIVE_ASSET_MODULE_ID_PATTERN } from "./mirror-e92G5HdG.js";
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
5
  import * as vite from "vitest/node";
7
- import { createHash } from "node:crypto";
8
- import transform from "fast-flow-transform";
9
6
 
10
- //#region src/transpile.ts
11
- const REACT_NATIVE_TRANSFORM_EXTENSIONS = [
12
- ".js",
13
- ".jsx",
14
- ".ts",
15
- ".tsx"
16
- ];
17
- const REACT_NATIVE_TRANSFORM_ALLOWLIST = [
18
- "react-native",
19
- "jest-react-native",
20
- "@react-native",
21
- "@react-native-community"
22
- ];
23
- function normalizePath(filename) {
24
- return filename.replace(/\\/g, "/");
25
- }
26
- function isWithinPackageRoots(filename, packageRoots) {
27
- const normalizedFilename = normalizePath(path.resolve(filename));
28
- return packageRoots.some((packageRoot) => {
29
- const normalizedPackageRoot = normalizePath(path.resolve(packageRoot));
30
- return normalizedFilename === normalizedPackageRoot || normalizedFilename.startsWith(`${normalizedPackageRoot}/`);
31
- });
32
- }
33
- function shouldTransformReactNativeFile(filename, packageRoots = []) {
34
- const normalized = normalizePath(filename);
35
- const isReactNativeSource = packageRoots.length > 0 ? isWithinPackageRoots(normalized, packageRoots) : normalized.includes("/react-native/") || normalized.includes("/@react-native/");
36
- return /\.(?:js|jsx|ts|tsx)$/.test(normalized) && !normalized.includes("/Renderer/implementations/") && isReactNativeSource;
37
- }
38
- function shouldInlineReactNativeDependency(filename, packageRoots = []) {
39
- const normalized = normalizePath(filename);
40
- if (packageRoots.length > 0) return isWithinPackageRoots(normalized, packageRoots);
41
- return REACT_NATIVE_TRANSFORM_ALLOWLIST.some((allowlistEntry) => normalized.includes(`/${allowlistEntry}/`));
42
- }
43
- async function transformReactNativeSource(sourcePath, source) {
44
- return (await transform({
45
- filename: sourcePath,
46
- source,
47
- dialect: "flow-detect",
48
- format: "compact",
49
- comments: false,
50
- reactRuntimeTarget: "19",
51
- sourcemap: false
52
- })).code;
53
- }
54
-
55
- //#endregion
56
- //#region src/mirror.ts
57
- const DEFAULT_PLATFORM = "ios";
58
- const GRANITE_VITEST_RN_CACHE_DIRECTORY = "vitest-react-native-cache";
59
- const GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY = "entries";
60
- const GRANITE_VITEST_RN_PACKAGES_DIRECTORY = "packages";
61
- const REACT_NATIVE_PLATFORMS = [
62
- "android",
63
- "ios",
64
- "native"
65
- ];
66
- const GRANITE_VITEST_RN_CACHE_GC_LOCK_FILENAME = ".gc.lock";
67
- const GRANITE_VITEST_RN_CACHE_METADATA_FILENAME = "meta.json";
68
- const GRANITE_VITEST_RN_CACHE_TMP_PREFIX = ".tmp-";
69
- const GRANITE_VITEST_RN_CACHE_MAX_AGE_MS = 10080 * 60 * 1e3;
70
- const GRANITE_VITEST_RN_CACHE_MAX_SIZE_BYTES = 1024 * 1024 * 1024;
71
- const GRANITE_VITEST_RN_TEMP_ENTRY_MAX_AGE_MS = 3600 * 1e3;
72
- const DEFAULT_VITEST_CACHE_DIRECTORY = ".vitest";
73
- const MIRROR_CACHE_KEY_VERSION = "v1";
74
- const currentMirrorModulePath = fs.realpathSync(fileURLToPath(import.meta.url));
75
- const currentMirrorModuleDirectory = path.dirname(currentMirrorModulePath);
76
- function getFastFlowTransformVersion() {
77
- const fastFlowTransformEntryPath = createRequire(import.meta.url).resolve("fast-flow-transform");
78
- const fastFlowTransformPackageRoot = path.dirname(path.dirname(fastFlowTransformEntryPath));
79
- const fastFlowTransformPackageJsonPath = path.join(fastFlowTransformPackageRoot, "package.json");
80
- return JSON.parse(fs.readFileSync(fastFlowTransformPackageJsonPath, "utf8")).version ?? "unknown";
81
- }
82
- function getPackageManifestDependencyNames(packageManifest) {
83
- return Object.keys({
84
- ...packageManifest.dependencies ?? {},
85
- ...packageManifest.devDependencies ?? {},
86
- ...packageManifest.optionalDependencies ?? {},
87
- ...packageManifest.peerDependencies ?? {}
88
- }).sort();
89
- }
90
- function shouldMirrorReactNativeDependency(packageName) {
91
- return packageName === "jest-react-native" || packageName.startsWith("@react-native/") || packageName.startsWith("@react-native-community/");
92
- }
93
- function hashDirectoryContents(hasher, rootPath, currentPath = rootPath) {
94
- const entries = fs.readdirSync(currentPath, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
95
- for (const entry of entries) {
96
- const entryPath = path.join(currentPath, entry.name);
97
- if (entry.isDirectory()) {
98
- hashDirectoryContents(hasher, rootPath, entryPath);
99
- continue;
100
- }
101
- if (!entry.isFile()) continue;
102
- hasher.update(path.relative(rootPath, entryPath));
103
- hasher.update("\0");
104
- hasher.update(fs.readFileSync(entryPath));
105
- hasher.update("\0");
106
- }
107
- }
108
- function hashImplementationFiles() {
109
- const hasher = createHash("sha256");
110
- const extension = path.extname(currentMirrorModulePath);
111
- const implementationFiles = [currentMirrorModulePath, path.join(currentMirrorModuleDirectory, `transpile${extension}`)].filter((implementationFile, index, allFiles) => {
112
- if (!fs.existsSync(implementationFile)) return false;
113
- return allFiles.indexOf(implementationFile) === index;
114
- }).sort();
115
- for (const implementationFile of implementationFiles) {
116
- hasher.update(path.basename(implementationFile));
117
- hasher.update("\0");
118
- hasher.update(fs.readFileSync(implementationFile));
119
- hasher.update("\0");
120
- }
121
- return hasher.digest("hex");
122
- }
123
- function collectMirroredReactNativePackageNames(workspacePackageManifest, reactNativePackageManifest) {
124
- const packageNames = /* @__PURE__ */ new Set();
125
- for (const dependencyName of Object.keys(reactNativePackageManifest.dependencies ?? {})) if (dependencyName.startsWith("@react-native/")) packageNames.add(dependencyName);
126
- for (const dependencyName of getPackageManifestDependencyNames(workspacePackageManifest)) if (dependencyName !== "react-native" && shouldMirrorReactNativeDependency(dependencyName)) packageNames.add(dependencyName);
127
- return [...packageNames].sort();
128
- }
129
- function getMirroredReactNativePackages(workspaceRoot) {
130
- const reactNativeRoot = resolvePackageRoot("react-native", workspaceRoot);
131
- const workspacePackageManifest = JSON.parse(fs.readFileSync(path.join(workspaceRoot, "package.json"), "utf8"));
132
- const reactNativePackageManifest = JSON.parse(fs.readFileSync(path.join(reactNativeRoot, "package.json"), "utf8"));
133
- const workspaceDependencyNames = new Set(getPackageManifestDependencyNames(workspacePackageManifest));
134
- return collectMirroredReactNativePackageNames(workspacePackageManifest, reactNativePackageManifest).map((packageName) => ({
135
- packageName,
136
- requireRoot: workspaceDependencyNames.has(packageName) ? workspaceRoot : reactNativeRoot
137
- }));
138
- }
139
- function computeReactNativeMirrorCacheKey(workspaceRoot) {
140
- const hasher = createHash("sha256");
141
- const packageRoots = resolveReactNativePackageRoots(workspaceRoot).sort();
142
- const transformImplementationHash = hashImplementationFiles();
143
- const transformDependencyVersions = { fastFlowTransform: getFastFlowTransformVersion() };
144
- hasher.update(`granite-vitest-rn-cache:${MIRROR_CACHE_KEY_VERSION}`);
145
- hasher.update("\0");
146
- hasher.update(`fast-flow-transform:${transformDependencyVersions.fastFlowTransform}`);
147
- hasher.update("\0");
148
- hasher.update(`transform:${transformImplementationHash}`);
149
- hasher.update("\0");
150
- for (const packageRoot of packageRoots) {
151
- hasher.update(path.basename(packageRoot));
152
- hasher.update("\0");
153
- hashDirectoryContents(hasher, packageRoot);
154
- }
155
- return {
156
- cacheKey: hasher.digest("hex"),
157
- packageRoots,
158
- transformDependencyVersions,
159
- transformImplementationHash
160
- };
161
- }
162
- function getDirectorySize(targetPath) {
163
- const stat = fs.statSync(targetPath);
164
- if (stat.isFile()) return stat.size;
165
- if (!stat.isDirectory()) return 0;
166
- return fs.readdirSync(targetPath, { withFileTypes: true }).reduce((totalSize, entry) => {
167
- const entryPath = path.join(targetPath, entry.name);
168
- if (entry.isDirectory() || entry.isFile()) return totalSize + getDirectorySize(entryPath);
169
- return totalSize;
170
- }, 0);
171
- }
172
- function readMirrorCacheMetadata(entryRoot) {
173
- const metadataPath = path.join(entryRoot, GRANITE_VITEST_RN_CACHE_METADATA_FILENAME);
174
- if (!fs.existsSync(metadataPath)) return null;
175
- try {
176
- return JSON.parse(fs.readFileSync(metadataPath, "utf8"));
177
- } catch {
178
- return null;
179
- }
180
- }
181
- function withMirrorCacheGcLock(workspaceRoot, resolvedCacheDir = path.join(workspaceRoot, DEFAULT_VITEST_CACHE_DIRECTORY), callback) {
182
- const cacheRoot = path.join(resolvedCacheDir, GRANITE_VITEST_RN_CACHE_DIRECTORY);
183
- const lockPath = path.join(cacheRoot, GRANITE_VITEST_RN_CACHE_GC_LOCK_FILENAME);
184
- fs.mkdirSync(cacheRoot, { recursive: true });
185
- let lockHandle = null;
186
- try {
187
- lockHandle = fs.openSync(lockPath, "wx");
188
- } catch (error) {
189
- if (error.code === "EEXIST") return;
190
- throw error;
191
- }
192
- try {
193
- callback();
194
- } finally {
195
- if (lockHandle != null) {
196
- fs.closeSync(lockHandle);
197
- fs.rmSync(lockPath, {
198
- force: true,
199
- recursive: true
200
- });
201
- }
202
- }
203
- }
204
- function gcReactNativeMirrorCache(workspaceRoot, activeCacheKey, resolvedCacheDir = path.join(workspaceRoot, DEFAULT_VITEST_CACHE_DIRECTORY)) {
205
- withMirrorCacheGcLock(workspaceRoot, resolvedCacheDir, () => {
206
- const entriesRoot = path.join(resolvedCacheDir, GRANITE_VITEST_RN_CACHE_DIRECTORY, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY);
207
- if (!fs.existsSync(entriesRoot)) return;
208
- const now = Date.now();
209
- const retainedEntries = [];
210
- for (const entry of fs.readdirSync(entriesRoot, { withFileTypes: true })) {
211
- if (!entry.isDirectory()) continue;
212
- const entryRoot = path.join(entriesRoot, entry.name);
213
- if (entry.name.startsWith(GRANITE_VITEST_RN_CACHE_TMP_PREFIX)) {
214
- if (now - fs.statSync(entryRoot).mtimeMs > GRANITE_VITEST_RN_TEMP_ENTRY_MAX_AGE_MS) fs.rmSync(entryRoot, {
215
- force: true,
216
- recursive: true
217
- });
218
- continue;
219
- }
220
- if (entry.name === activeCacheKey) {
221
- const activeMetadata = readMirrorCacheMetadata(entryRoot);
222
- retainedEntries.push({
223
- cacheKey: entry.name,
224
- entryRoot,
225
- lastAccessedAtMs: activeMetadata == null ? now : Date.parse(activeMetadata.lastAccessedAt),
226
- sizeBytes: activeMetadata?.sizeBytes ?? getDirectorySize(path.join(entryRoot, GRANITE_VITEST_RN_PACKAGES_DIRECTORY))
227
- });
228
- continue;
229
- }
230
- const metadata = readMirrorCacheMetadata(entryRoot);
231
- const packagesRoot = path.join(entryRoot, GRANITE_VITEST_RN_PACKAGES_DIRECTORY);
232
- if (metadata == null || !fs.existsSync(packagesRoot)) {
233
- fs.rmSync(entryRoot, {
234
- force: true,
235
- recursive: true
236
- });
237
- continue;
238
- }
239
- const lastAccessedAtMs = Date.parse(metadata.lastAccessedAt);
240
- if (Number.isNaN(lastAccessedAtMs) || now - lastAccessedAtMs > GRANITE_VITEST_RN_CACHE_MAX_AGE_MS) {
241
- fs.rmSync(entryRoot, {
242
- force: true,
243
- recursive: true
244
- });
245
- continue;
246
- }
247
- retainedEntries.push({
248
- cacheKey: entry.name,
249
- entryRoot,
250
- lastAccessedAtMs,
251
- sizeBytes: metadata.sizeBytes
252
- });
253
- }
254
- let totalSizeBytes = retainedEntries.reduce((totalSize, entry) => totalSize + entry.sizeBytes, 0);
255
- for (const entry of retainedEntries.filter((cacheEntry) => cacheEntry.cacheKey !== activeCacheKey).sort((left, right) => left.lastAccessedAtMs - right.lastAccessedAtMs)) {
256
- if (totalSizeBytes <= GRANITE_VITEST_RN_CACHE_MAX_SIZE_BYTES) break;
257
- fs.rmSync(entry.entryRoot, {
258
- force: true,
259
- recursive: true
260
- });
261
- totalSizeBytes -= entry.sizeBytes;
262
- }
263
- });
264
- }
265
- function resolvePackageRoot(packageName, requireRoot) {
266
- const packageJsonPath = createRequire(path.join(requireRoot, "package.json")).resolve(`${packageName}/package.json`);
267
- return path.dirname(packageJsonPath);
268
- }
269
- function resolveReactNativePackageRoots(workspaceRoot) {
270
- return [resolvePackageRoot("react-native", workspaceRoot), ...getMirroredReactNativePackages(workspaceRoot).map(({ packageName, requireRoot }) => resolvePackageRoot(packageName, requireRoot))];
271
- }
272
- async function mirrorFile(sourcePath, destinationPath, packageRoots) {
273
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
274
- if (shouldTransformReactNativeFile(sourcePath, packageRoots)) {
275
- const source = fs.readFileSync(sourcePath, "utf8");
276
- try {
277
- const transformed = await transformReactNativeSource(sourcePath, source);
278
- fs.writeFileSync(destinationPath, transformed);
279
- return;
280
- } catch {
281
- fs.writeFileSync(destinationPath, source);
282
- return;
283
- }
284
- }
285
- fs.copyFileSync(sourcePath, destinationPath);
286
- }
287
- async function mirrorTree(sourceRoot, destinationRoot, packageRoots) {
288
- const entries = fs.readdirSync(sourceRoot, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
289
- for (const entry of entries) {
290
- const sourcePath = path.join(sourceRoot, entry.name);
291
- const destinationPath = path.join(destinationRoot, entry.name);
292
- if (entry.isDirectory()) {
293
- await mirrorTree(sourcePath, destinationPath, packageRoots);
294
- continue;
295
- }
296
- if (!entry.isFile()) continue;
297
- await mirrorFile(sourcePath, destinationPath, packageRoots);
298
- }
299
- }
300
- function synthesizeDefaultPlatformFiles(destinationRoot) {
301
- const platformFallbackOrder = [
302
- DEFAULT_PLATFORM,
303
- "native",
304
- ...REACT_NATIVE_PLATFORMS.filter((platform) => platform !== DEFAULT_PLATFORM && platform !== "native")
305
- ];
306
- const platformPriority = new Map(platformFallbackOrder.map((platform, index) => [platform, index]));
307
- const fallbackCandidates = [];
308
- function collectCandidates(currentRoot) {
309
- const entries = fs.readdirSync(currentRoot, { withFileTypes: true });
310
- for (const entry of entries) {
311
- const entryPath = path.join(currentRoot, entry.name);
312
- if (entry.isDirectory()) {
313
- collectCandidates(entryPath);
314
- continue;
315
- }
316
- const match = entry.name.match(new RegExp(`^(.*)\\.(${REACT_NATIVE_PLATFORMS.join("|")})\\.(js|jsx|ts|tsx)$`));
317
- if (match == null) continue;
318
- const [, basename, platform, extension] = match;
319
- if (basename == null || platform == null || extension == null) continue;
320
- fallbackCandidates.push({
321
- sourcePath: entryPath,
322
- fallbackPath: path.join(currentRoot, `${basename}.${extension}`),
323
- priority: platformPriority.get(platform) ?? platformFallbackOrder.length
324
- });
325
- }
326
- }
327
- collectCandidates(destinationRoot);
328
- fallbackCandidates.sort((left, right) => left.priority - right.priority).forEach(({ sourcePath, fallbackPath }) => {
329
- if (!fs.existsSync(fallbackPath)) fs.copyFileSync(sourcePath, fallbackPath);
330
- });
331
- }
332
- async function buildReactNativeMirror(workspaceRoot, resolvedCacheDir = path.join(workspaceRoot, DEFAULT_VITEST_CACHE_DIRECTORY)) {
333
- const cacheEntry = computeReactNativeMirrorCacheKey(workspaceRoot);
334
- const entriesRoot = path.join(resolvedCacheDir, GRANITE_VITEST_RN_CACHE_DIRECTORY, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY);
335
- const entryRoot = path.join(entriesRoot, cacheEntry.cacheKey);
336
- const packagesRoot = path.join(entryRoot, GRANITE_VITEST_RN_PACKAGES_DIRECTORY);
337
- const metadataPath = path.join(entryRoot, GRANITE_VITEST_RN_CACHE_METADATA_FILENAME);
338
- fs.mkdirSync(entriesRoot, { recursive: true });
339
- if (fs.existsSync(entryRoot) && (!fs.existsSync(packagesRoot) || !fs.existsSync(metadataPath))) fs.rmSync(entryRoot, {
340
- force: true,
341
- recursive: true
342
- });
343
- if (fs.existsSync(packagesRoot) && fs.existsSync(metadataPath)) {
344
- const metadata = readMirrorCacheMetadata(entryRoot);
345
- if (metadata != null) {
346
- metadata.lastAccessedAt = (/* @__PURE__ */ new Date()).toISOString();
347
- fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
348
- }
349
- gcReactNativeMirrorCache(workspaceRoot, cacheEntry.cacheKey, resolvedCacheDir);
350
- return packagesRoot;
351
- }
352
- const temporaryEntryRoot = path.join(entriesRoot, `${GRANITE_VITEST_RN_CACHE_TMP_PREFIX}${process.pid}-${Date.now()}-${cacheEntry.cacheKey}`);
353
- const temporaryPackagesRoot = path.join(temporaryEntryRoot, GRANITE_VITEST_RN_PACKAGES_DIRECTORY);
354
- const temporaryMetadataPath = path.join(temporaryEntryRoot, GRANITE_VITEST_RN_CACHE_METADATA_FILENAME);
355
- fs.rmSync(temporaryEntryRoot, {
356
- force: true,
357
- recursive: true
358
- });
359
- fs.mkdirSync(temporaryPackagesRoot, { recursive: true });
360
- try {
361
- await mirrorTree(resolvePackageRoot("react-native", workspaceRoot), path.join(temporaryPackagesRoot, "react-native"), cacheEntry.packageRoots);
362
- for (const { packageName, requireRoot } of getMirroredReactNativePackages(workspaceRoot)) await mirrorTree(resolvePackageRoot(packageName, requireRoot), path.join(temporaryPackagesRoot, ...packageName.split("/")), cacheEntry.packageRoots);
363
- synthesizeDefaultPlatformFiles(temporaryPackagesRoot);
364
- const now = (/* @__PURE__ */ new Date()).toISOString();
365
- fs.writeFileSync(temporaryMetadataPath, JSON.stringify({
366
- cacheKey: cacheEntry.cacheKey,
367
- createdAt: now,
368
- lastAccessedAt: now,
369
- packageRoots: cacheEntry.packageRoots,
370
- sizeBytes: getDirectorySize(temporaryPackagesRoot),
371
- transformDependencyVersions: cacheEntry.transformDependencyVersions,
372
- transformImplementationHash: cacheEntry.transformImplementationHash
373
- }, null, 2));
374
- try {
375
- fs.renameSync(temporaryEntryRoot, entryRoot);
376
- } catch (error) {
377
- const errorCode = error.code;
378
- if (errorCode !== "EEXIST" && errorCode !== "ENOTEMPTY") throw error;
379
- }
380
- } finally {
381
- fs.rmSync(temporaryEntryRoot, {
382
- force: true,
383
- recursive: true
384
- });
385
- }
386
- if (fs.existsSync(packagesRoot) && fs.existsSync(metadataPath)) {
387
- const metadata = readMirrorCacheMetadata(entryRoot);
388
- if (metadata != null) {
389
- metadata.lastAccessedAt = (/* @__PURE__ */ new Date()).toISOString();
390
- fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
391
- }
392
- gcReactNativeMirrorCache(workspaceRoot, cacheEntry.cacheKey, resolvedCacheDir);
393
- return packagesRoot;
394
- }
395
- throw new Error(`Failed to materialize React Native mirror cache at ${entryRoot}`);
396
- }
397
-
398
- //#endregion
399
7
  //#region src/reactNative.ts
400
8
  const PLATFORM_RESOLUTION_ORDER = [
401
9
  DEFAULT_PLATFORM,
@@ -435,49 +43,20 @@ function resolveReactNativeSetupFiles() {
435
43
  return [path.join(currentDirectory, `reactNativeRuntime${extension}`), path.join(currentDirectory, `setup${extension}`)];
436
44
  }
437
45
  function reactNative() {
46
+ let reactNativeTransformCache = null;
438
47
  return {
439
48
  enforce: "pre",
440
49
  name: "granite-react-native",
441
50
  async config(conf) {
442
51
  const workspaceRoot = conf.root ?? conf.test?.root ?? process.cwd();
443
52
  const cacheDir = conf.cacheDir ?? ".vitest";
444
- const reactNativeMirrorRoot = await buildReactNativeMirror(workspaceRoot, path.isAbsolute(cacheDir) ? cacheDir : path.join(workspaceRoot, cacheDir));
53
+ reactNativeTransformCache = await buildReactNativeTransformCache(workspaceRoot, path.isAbsolute(cacheDir) ? cacheDir : path.join(workspaceRoot, cacheDir));
445
54
  const resolve = {
446
- alias: [
447
- {
448
- find: /^react-native$/,
449
- replacement: path.join(reactNativeMirrorRoot, "react-native", "index.js")
450
- },
451
- {
452
- find: /^react-native\/(.*)$/,
453
- replacement: path.join(reactNativeMirrorRoot, "react-native", "$1")
454
- },
455
- {
456
- find: /^@react-native\/(.*)$/,
457
- replacement: path.join(reactNativeMirrorRoot, "@react-native", "$1")
458
- },
459
- {
460
- find: /^@react-native-community\/([^/]+)$/,
461
- replacement: path.join(reactNativeMirrorRoot, "@react-native-community", "$1")
462
- },
463
- {
464
- find: /^@react-native-community\/([^/]+)\/(.*)$/,
465
- replacement: path.join(reactNativeMirrorRoot, "@react-native-community", "$1", "$2")
466
- },
467
- {
468
- find: /^jest-react-native$/,
469
- replacement: path.join(reactNativeMirrorRoot, "jest-react-native")
470
- },
471
- {
472
- find: /^jest-react-native\/(.*)$/,
473
- replacement: path.join(reactNativeMirrorRoot, "jest-react-native", "$1")
474
- }
475
- ],
476
55
  conditions: [...REACT_NATIVE_EXPORT_CONDITIONS],
477
56
  extensions: [...REACT_NATIVE_RESOLVE_EXTENSIONS]
478
57
  };
479
58
  const commonConfig = {
480
- define: { "globalThis.__GRANITE_VITEST_RN_CACHE_ROOT__": JSON.stringify(reactNativeMirrorRoot) },
59
+ define: { "globalThis.__GRANITE_VITEST_RN_CACHE_MANIFEST__": JSON.stringify(reactNativeTransformCache.manifestPath) },
481
60
  resolve,
482
61
  test: {
483
62
  environment: "node",
@@ -501,6 +80,10 @@ function reactNative() {
501
80
  ...commonConfig
502
81
  };
503
82
  },
83
+ resolveId(id, importer) {
84
+ if (reactNativeTransformCache == null) return;
85
+ return resolveReactNativeModuleFromManifest(id, importer, reactNativeTransformCache.manifest)?.objectPath;
86
+ },
504
87
  load(id) {
505
88
  if (!REACT_NATIVE_ASSET_MODULE_ID_PATTERN.test(id)) return;
506
89
  const asset = { testUri: path.relative(currentDirectory, id.replace(/[?#].*$/, "")).replace(/\\/g, "/") };
@@ -521,4 +104,4 @@ var src_default = {
521
104
  };
522
105
 
523
106
  //#endregion
524
- export { DEFAULT_PLATFORM, GRANITE_VITEST_RN_CACHE_DIRECTORY, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY, GRANITE_VITEST_RN_PACKAGES_DIRECTORY, JEST_LIKE_TEST_PATTERNS, REACT_NATIVE_ASSET_EXTENSIONS, REACT_NATIVE_EXPORT_CONDITIONS, REACT_NATIVE_PLATFORMS, REACT_NATIVE_RESOLVE_EXTENSIONS, REACT_NATIVE_TRANSFORM_ALLOWLIST, REACT_NATIVE_TRANSFORM_EXTENSIONS, buildReactNativeMirror, src_default as default, reactNative, resolvePackageRoot, resolveReactNativePackageRoots, resolveReactNativeSetupFiles, shouldInlineReactNativeDependency, shouldTransformReactNativeFile, synthesizeDefaultPlatformFiles };
107
+ export { DEFAULT_PLATFORM, GRANITE_VITEST_RN_CACHE_DIRECTORY, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY, GRANITE_VITEST_RN_MANIFEST_FILENAME, GRANITE_VITEST_RN_OBJECTS_DIRECTORY, GRANITE_VITEST_RN_PACKAGES_DIRECTORY, JEST_LIKE_TEST_PATTERNS, REACT_NATIVE_ASSET_EXTENSIONS, REACT_NATIVE_EXPORT_CONDITIONS, REACT_NATIVE_PLATFORMS, REACT_NATIVE_RESOLVE_EXTENSIONS, REACT_NATIVE_TRANSFORM_ALLOWLIST, REACT_NATIVE_TRANSFORM_EXTENSIONS, buildReactNativeMirror, buildReactNativeTransformCache, src_default as default, reactNative, resolvePackageRoot, resolveReactNativeModuleFromManifest, resolveReactNativePackageRoots, resolveReactNativeSetupFiles, shouldInlineReactNativeDependency, shouldTransformReactNativeFile };
@@ -0,0 +1,484 @@
1
+ import { createRequire } from "node:module";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createHash } from "node:crypto";
6
+ import flowRemoveTypes from "flow-remove-types";
7
+
8
+ //#region src/assets.ts
9
+ const REACT_NATIVE_ASSET_EXTENSIONS = [
10
+ "bmp",
11
+ "gif",
12
+ "jpg",
13
+ "jpeg",
14
+ "mp4",
15
+ "png",
16
+ "psd",
17
+ "svg",
18
+ "webp"
19
+ ];
20
+ const REACT_NATIVE_ASSET_MODULE_ID_PATTERN = new RegExp(`\\.(${REACT_NATIVE_ASSET_EXTENSIONS.join("|")})(?:[?#].*)?$`, "i");
21
+
22
+ //#endregion
23
+ //#region src/transpile.ts
24
+ const REACT_NATIVE_TRANSFORM_EXTENSIONS = [
25
+ ".js",
26
+ ".jsx",
27
+ ".ts",
28
+ ".tsx"
29
+ ];
30
+ const REACT_NATIVE_TRANSFORM_ALLOWLIST = [
31
+ "react-native",
32
+ "jest-react-native",
33
+ "@react-native",
34
+ "@react-native-community"
35
+ ];
36
+ function normalizePath$1(filename) {
37
+ return filename.replace(/\\/g, "/");
38
+ }
39
+ function isWithinPackageRoots(filename, packageRoots) {
40
+ const normalizedFilename = normalizePath$1(path.resolve(filename));
41
+ return packageRoots.some((packageRoot) => {
42
+ const normalizedPackageRoot = normalizePath$1(path.resolve(packageRoot));
43
+ return normalizedFilename === normalizedPackageRoot || normalizedFilename.startsWith(`${normalizedPackageRoot}/`);
44
+ });
45
+ }
46
+ function shouldTransformReactNativeFile(filename, packageRoots = []) {
47
+ const normalized = normalizePath$1(filename);
48
+ const isReactNativeSource = packageRoots.length > 0 ? isWithinPackageRoots(normalized, packageRoots) : normalized.includes("/react-native/") || normalized.includes("/@react-native/");
49
+ return /\.(?:js|jsx|ts|tsx)$/.test(normalized) && !normalized.includes("/Renderer/implementations/") && isReactNativeSource;
50
+ }
51
+ function shouldInlineReactNativeDependency(filename, packageRoots = []) {
52
+ const normalized = normalizePath$1(filename);
53
+ if (packageRoots.length > 0) return isWithinPackageRoots(normalized, packageRoots);
54
+ return REACT_NATIVE_TRANSFORM_ALLOWLIST.some((allowlistEntry) => normalized.includes(`/${allowlistEntry}/`));
55
+ }
56
+ async function transformReactNativeSource(_sourcePath, source) {
57
+ return flowRemoveTypes(source, {
58
+ all: true,
59
+ pretty: true,
60
+ removeEmptyImports: true
61
+ }).toString();
62
+ }
63
+
64
+ //#endregion
65
+ //#region src/mirror.ts
66
+ const DEFAULT_PLATFORM = "ios";
67
+ const GRANITE_VITEST_RN_CACHE_DIRECTORY = "vitest-react-native-cache";
68
+ const GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY = "entries";
69
+ const GRANITE_VITEST_RN_OBJECTS_DIRECTORY = "objects";
70
+ const GRANITE_VITEST_RN_MANIFEST_FILENAME = "manifest.json";
71
+ const GRANITE_VITEST_RN_PACKAGES_DIRECTORY = "packages";
72
+ const REACT_NATIVE_PLATFORMS = [
73
+ "android",
74
+ "ios",
75
+ "native"
76
+ ];
77
+ const GRANITE_VITEST_RN_CACHE_GC_LOCK_FILENAME = ".gc.lock";
78
+ const GRANITE_VITEST_RN_CACHE_METADATA_FILENAME = "meta.json";
79
+ const GRANITE_VITEST_RN_CACHE_TMP_PREFIX = ".tmp-";
80
+ const GRANITE_VITEST_RN_TEMP_ENTRY_MAX_AGE_MS = 3600 * 1e3;
81
+ const DEFAULT_VITEST_CACHE_DIRECTORY = ".vitest";
82
+ const MIRROR_CACHE_KEY_VERSION = "v2-flat";
83
+ const PLATFORM_EXTENSION_SUFFIXES = [
84
+ "tsx",
85
+ "ts",
86
+ "jsx",
87
+ "js"
88
+ ];
89
+ const FALLBACK_EXTENSIONS = [
90
+ ...[
91
+ DEFAULT_PLATFORM,
92
+ "native",
93
+ ...REACT_NATIVE_PLATFORMS.filter((platform) => platform !== DEFAULT_PLATFORM && platform !== "native")
94
+ ].flatMap((platform) => PLATFORM_EXTENSION_SUFFIXES.map((extension) => `.${platform}.${extension}`)),
95
+ ".tsx",
96
+ ".ts",
97
+ ".jsx",
98
+ ".js",
99
+ ".json",
100
+ ...REACT_NATIVE_ASSET_EXTENSIONS.map((extension) => `.${extension}`)
101
+ ];
102
+ const currentMirrorModulePath = fs.realpathSync(fileURLToPath(import.meta.url));
103
+ const currentMirrorModuleDirectory = path.dirname(currentMirrorModulePath);
104
+ function normalizePath(filename) {
105
+ return path.resolve(filename.replace(/[?#].*$/, ""));
106
+ }
107
+ function getFlowRemoveTypesVersion() {
108
+ const flowRemoveTypesPackageJsonPath = createRequire(import.meta.url).resolve("flow-remove-types/package.json");
109
+ return JSON.parse(fs.readFileSync(flowRemoveTypesPackageJsonPath, "utf8")).version ?? "unknown";
110
+ }
111
+ function getPackageManifestDependencyNames(packageManifest) {
112
+ return Object.keys({
113
+ ...packageManifest.dependencies ?? {},
114
+ ...packageManifest.devDependencies ?? {},
115
+ ...packageManifest.optionalDependencies ?? {},
116
+ ...packageManifest.peerDependencies ?? {}
117
+ }).sort();
118
+ }
119
+ function shouldMirrorReactNativeDependency(packageName) {
120
+ return packageName === "jest-react-native" || packageName.startsWith("@react-native/") || packageName.startsWith("@react-native-community/");
121
+ }
122
+ function hashDirectoryContents(hasher, rootPath, currentPath = rootPath) {
123
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
124
+ for (const entry of entries) {
125
+ const entryPath = path.join(currentPath, entry.name);
126
+ if (entry.isDirectory()) {
127
+ hashDirectoryContents(hasher, rootPath, entryPath);
128
+ continue;
129
+ }
130
+ if (!entry.isFile()) continue;
131
+ hasher.update(path.relative(rootPath, entryPath));
132
+ hasher.update("\0");
133
+ hasher.update(fs.readFileSync(entryPath));
134
+ hasher.update("\0");
135
+ }
136
+ }
137
+ function hashImplementationFiles() {
138
+ const hasher = createHash("sha256");
139
+ const extension = path.extname(currentMirrorModulePath);
140
+ const implementationFiles = [currentMirrorModulePath, path.join(currentMirrorModuleDirectory, `transpile${extension}`)].filter((implementationFile, index, allFiles) => {
141
+ if (!fs.existsSync(implementationFile)) return false;
142
+ return allFiles.indexOf(implementationFile) === index;
143
+ }).sort();
144
+ for (const implementationFile of implementationFiles) {
145
+ hasher.update(path.basename(implementationFile));
146
+ hasher.update("\0");
147
+ hasher.update(fs.readFileSync(implementationFile));
148
+ hasher.update("\0");
149
+ }
150
+ return hasher.digest("hex");
151
+ }
152
+ function collectMirroredReactNativePackageNames(workspacePackageManifest, reactNativePackageManifest) {
153
+ const packageNames = /* @__PURE__ */ new Set();
154
+ for (const dependencyName of Object.keys(reactNativePackageManifest.dependencies ?? {})) if (dependencyName.startsWith("@react-native/")) packageNames.add(dependencyName);
155
+ for (const dependencyName of getPackageManifestDependencyNames(workspacePackageManifest)) if (dependencyName !== "react-native" && shouldMirrorReactNativeDependency(dependencyName)) packageNames.add(dependencyName);
156
+ return [...packageNames].sort();
157
+ }
158
+ function getMirroredReactNativePackages(workspaceRoot) {
159
+ const reactNativeRoot = resolvePackageRoot("react-native", workspaceRoot);
160
+ const workspacePackageManifest = JSON.parse(fs.readFileSync(path.join(workspaceRoot, "package.json"), "utf8"));
161
+ const reactNativePackageManifest = JSON.parse(fs.readFileSync(path.join(reactNativeRoot, "package.json"), "utf8"));
162
+ const workspaceDependencyNames = new Set(getPackageManifestDependencyNames(workspacePackageManifest));
163
+ return collectMirroredReactNativePackageNames(workspacePackageManifest, reactNativePackageManifest).map((packageName) => ({
164
+ packageName,
165
+ requireRoot: workspaceDependencyNames.has(packageName) ? workspaceRoot : reactNativeRoot
166
+ }));
167
+ }
168
+ function getReactNativePackageEntries(workspaceRoot) {
169
+ return [{
170
+ packageName: "react-native",
171
+ packageRoot: resolvePackageRoot("react-native", workspaceRoot),
172
+ requireRoot: workspaceRoot
173
+ }, ...getMirroredReactNativePackages(workspaceRoot).map(({ packageName, requireRoot }) => ({
174
+ packageName,
175
+ packageRoot: resolvePackageRoot(packageName, requireRoot),
176
+ requireRoot
177
+ }))];
178
+ }
179
+ function computeReactNativeTransformCacheKey(workspaceRoot) {
180
+ const hasher = createHash("sha256");
181
+ const packageRoots = resolveReactNativePackageRoots(workspaceRoot).sort();
182
+ const transformImplementationHash = hashImplementationFiles();
183
+ const transformDependencyVersions = { flowRemoveTypes: getFlowRemoveTypesVersion() };
184
+ hasher.update(`granite-vitest-rn-cache:${MIRROR_CACHE_KEY_VERSION}`);
185
+ hasher.update("\0");
186
+ hasher.update(`flow-remove-types:${transformDependencyVersions.flowRemoveTypes}`);
187
+ hasher.update("\0");
188
+ hasher.update(`transform:${transformImplementationHash}`);
189
+ hasher.update("\0");
190
+ for (const packageRoot of packageRoots) {
191
+ hasher.update(path.basename(packageRoot));
192
+ hasher.update("\0");
193
+ hashDirectoryContents(hasher, packageRoot);
194
+ }
195
+ return {
196
+ cacheKey: hasher.digest("hex"),
197
+ packageRoots,
198
+ transformDependencyVersions,
199
+ transformImplementationHash
200
+ };
201
+ }
202
+ function getDirectorySize(targetPath) {
203
+ const stat = fs.statSync(targetPath);
204
+ if (stat.isFile()) return stat.size;
205
+ if (!stat.isDirectory()) return 0;
206
+ return fs.readdirSync(targetPath, { withFileTypes: true }).reduce((totalSize, entry) => {
207
+ const entryPath = path.join(targetPath, entry.name);
208
+ if (entry.isDirectory() || entry.isFile()) return totalSize + getDirectorySize(entryPath);
209
+ return totalSize;
210
+ }, 0);
211
+ }
212
+ function readMirrorCacheMetadata(entryRoot) {
213
+ const metadataPath = path.join(entryRoot, GRANITE_VITEST_RN_CACHE_METADATA_FILENAME);
214
+ if (!fs.existsSync(metadataPath)) return null;
215
+ try {
216
+ return JSON.parse(fs.readFileSync(metadataPath, "utf8"));
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ function gcReactNativeMirrorCache(workspaceRoot, activeCacheKey, resolvedCacheDir = path.join(workspaceRoot, DEFAULT_VITEST_CACHE_DIRECTORY)) {
222
+ gcReactNativeMirrorCacheAsync(activeCacheKey, resolvedCacheDir).catch(() => void 0);
223
+ }
224
+ async function gcReactNativeMirrorCacheAsync(activeCacheKey, resolvedCacheDir) {
225
+ const cacheRoot = path.join(resolvedCacheDir, GRANITE_VITEST_RN_CACHE_DIRECTORY);
226
+ const lockPath = path.join(cacheRoot, GRANITE_VITEST_RN_CACHE_GC_LOCK_FILENAME);
227
+ await fs.promises.mkdir(cacheRoot, { recursive: true });
228
+ let lockHandle = null;
229
+ try {
230
+ lockHandle = await fs.promises.open(lockPath, "wx");
231
+ } catch (error) {
232
+ if (error.code === "EEXIST") return;
233
+ throw error;
234
+ }
235
+ try {
236
+ const entriesRoot = path.join(resolvedCacheDir, GRANITE_VITEST_RN_CACHE_DIRECTORY, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY);
237
+ if (!await fs.promises.access(entriesRoot).then(() => true).catch(() => false)) return;
238
+ const now = Date.now();
239
+ const removeEntryPromises = [];
240
+ for (const entry of await fs.promises.readdir(entriesRoot, { withFileTypes: true })) {
241
+ if (!entry.isDirectory()) continue;
242
+ const entryRoot = path.join(entriesRoot, entry.name);
243
+ if (entry.name.startsWith(GRANITE_VITEST_RN_CACHE_TMP_PREFIX)) {
244
+ const stat = await fs.promises.stat(entryRoot).catch(() => null);
245
+ if ((stat == null ? 0 : now - stat.mtimeMs) > GRANITE_VITEST_RN_TEMP_ENTRY_MAX_AGE_MS) removeEntryPromises.push(fs.promises.rm(entryRoot, {
246
+ force: true,
247
+ recursive: true
248
+ }));
249
+ continue;
250
+ }
251
+ if (entry.name === activeCacheKey) continue;
252
+ removeEntryPromises.push(fs.promises.rm(entryRoot, {
253
+ force: true,
254
+ recursive: true
255
+ }));
256
+ }
257
+ await Promise.allSettled(removeEntryPromises);
258
+ } catch {} finally {
259
+ if (lockHandle != null) {
260
+ await lockHandle.close().catch(() => void 0);
261
+ await fs.promises.rm(lockPath, {
262
+ force: true,
263
+ recursive: true
264
+ }).catch(() => void 0);
265
+ }
266
+ }
267
+ }
268
+ function resolvePackageRoot(packageName, requireRoot) {
269
+ const packageJsonPath = createRequire(path.join(requireRoot, "package.json")).resolve(`${packageName}/package.json`);
270
+ return path.dirname(packageJsonPath);
271
+ }
272
+ function resolveReactNativePackageRoots(workspaceRoot) {
273
+ return getReactNativePackageEntries(workspaceRoot).map(({ packageRoot }) => packageRoot);
274
+ }
275
+ function readPackageManifest(packageRoot) {
276
+ return JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
277
+ }
278
+ function findPackageForRequest(request, manifest) {
279
+ return manifest.packages.sort((left, right) => right.packageName.length - left.packageName.length).find(({ packageName }) => request === packageName || request.startsWith(`${packageName}/`));
280
+ }
281
+ function getPackageSubpath(request, packageName) {
282
+ if (request === packageName) return null;
283
+ return request.slice(packageName.length + 1);
284
+ }
285
+ function getObjectForSourcePath(sourcePath, manifest) {
286
+ const normalizedSourcePath = normalizePath(sourcePath);
287
+ const objectPath = manifest.sourceToObject[normalizedSourcePath];
288
+ if (objectPath == null) return null;
289
+ return {
290
+ objectPath,
291
+ sourcePath: normalizedSourcePath
292
+ };
293
+ }
294
+ function resolvePackageEntrySourcePath(packageEntry, manifest) {
295
+ const packageManifest = readPackageManifest(packageEntry.packageRoot);
296
+ const entryPath = packageEntry.packageName === "react-native" ? "index.js" : packageManifest["react-native"] ?? packageManifest.module ?? packageManifest.main ?? "index.js";
297
+ if (typeof entryPath === "string") return resolveSourcePathCandidate(path.join(packageEntry.packageRoot, entryPath), manifest);
298
+ return resolveSourcePathCandidate(path.join(packageEntry.packageRoot, "index"), manifest);
299
+ }
300
+ function resolveSourcePathCandidate(candidatePath, manifest) {
301
+ const normalizedCandidatePath = normalizePath(candidatePath);
302
+ if (fs.existsSync(normalizedCandidatePath)) {
303
+ const stat = fs.statSync(normalizedCandidatePath);
304
+ if (stat.isFile()) return getObjectForSourcePath(normalizedCandidatePath, manifest);
305
+ if (stat.isDirectory()) {
306
+ const packageJsonPath = path.join(normalizedCandidatePath, "package.json");
307
+ if (fs.existsSync(packageJsonPath)) {
308
+ const packageManifest = readPackageManifest(normalizedCandidatePath);
309
+ const entryPath = packageManifest["react-native"] ?? packageManifest.module ?? packageManifest.main ?? "index.js";
310
+ if (typeof entryPath === "string") {
311
+ const resolvedEntry = resolveSourcePathCandidate(path.join(normalizedCandidatePath, entryPath), manifest);
312
+ if (resolvedEntry != null) return resolvedEntry;
313
+ }
314
+ }
315
+ return resolveSourcePathCandidate(path.join(normalizedCandidatePath, "index"), manifest);
316
+ }
317
+ }
318
+ if (path.extname(normalizedCandidatePath).length > 0) return null;
319
+ for (const extension of FALLBACK_EXTENSIONS) {
320
+ const resolved = getObjectForSourcePath(`${normalizedCandidatePath}${extension}`, manifest);
321
+ if (resolved != null) return resolved;
322
+ }
323
+ return null;
324
+ }
325
+ function resolveReactNativeModuleFromManifest(request, importer, manifest) {
326
+ if (request.startsWith(".") && importer != null) {
327
+ const importerSourcePath = manifest.objectToSource[normalizePath(importer)];
328
+ if (importerSourcePath == null) return null;
329
+ return resolveSourcePathCandidate(path.resolve(path.dirname(importerSourcePath), request), manifest);
330
+ }
331
+ const packageEntry = findPackageForRequest(request, manifest);
332
+ if (packageEntry == null) return null;
333
+ const subpath = getPackageSubpath(request, packageEntry.packageName);
334
+ if (subpath == null) return resolvePackageEntrySourcePath(packageEntry, manifest);
335
+ return resolveSourcePathCandidate(path.join(packageEntry.packageRoot, subpath), manifest);
336
+ }
337
+ async function writeObjectFile(sourcePath, objectsRoot, packageRoots, manifest) {
338
+ const normalizedSourcePath = normalizePath(sourcePath);
339
+ const sourceBuffer = fs.readFileSync(normalizedSourcePath);
340
+ const extension = path.extname(normalizedSourcePath) || ".js";
341
+ let outputBuffer = sourceBuffer;
342
+ if (shouldTransformReactNativeFile(normalizedSourcePath, packageRoots)) try {
343
+ const transformed = await transformReactNativeSource(normalizedSourcePath, sourceBuffer.toString("utf8"));
344
+ outputBuffer = Buffer.from(transformed);
345
+ } catch {
346
+ outputBuffer = sourceBuffer;
347
+ }
348
+ const objectHash = createHash("sha256").update(normalizedSourcePath).update("\0").update(outputBuffer).digest("hex");
349
+ const objectPath = path.join(objectsRoot, `${objectHash}${extension}`);
350
+ if (!fs.existsSync(objectPath)) fs.writeFileSync(objectPath, outputBuffer);
351
+ manifest.sourceToObject[normalizedSourcePath] = objectPath;
352
+ manifest.objectToSource[objectPath] = normalizedSourcePath;
353
+ }
354
+ async function writePackageTreeObjects(sourceRoot, objectsRoot, packageRoots, manifest) {
355
+ const entries = fs.readdirSync(sourceRoot, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
356
+ for (const entry of entries) {
357
+ const sourcePath = path.join(sourceRoot, entry.name);
358
+ if (entry.isDirectory()) {
359
+ await writePackageTreeObjects(sourcePath, objectsRoot, packageRoots, manifest);
360
+ continue;
361
+ }
362
+ if (!entry.isFile()) continue;
363
+ await writeObjectFile(sourcePath, objectsRoot, packageRoots, manifest);
364
+ }
365
+ }
366
+ function readTransformCacheManifest(manifestPath) {
367
+ return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
368
+ }
369
+ function loadReactNativeTransformCacheManifest(manifestPath) {
370
+ return readTransformCacheManifest(manifestPath);
371
+ }
372
+ async function buildReactNativeTransformCache(workspaceRoot, resolvedCacheDir = path.join(workspaceRoot, DEFAULT_VITEST_CACHE_DIRECTORY)) {
373
+ const cacheEntry = computeReactNativeTransformCacheKey(workspaceRoot);
374
+ const entriesRoot = path.join(resolvedCacheDir, GRANITE_VITEST_RN_CACHE_DIRECTORY, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY);
375
+ const entryRoot = path.join(entriesRoot, cacheEntry.cacheKey);
376
+ const objectsRoot = path.join(entryRoot, GRANITE_VITEST_RN_OBJECTS_DIRECTORY);
377
+ const manifestPath = path.join(entryRoot, GRANITE_VITEST_RN_MANIFEST_FILENAME);
378
+ const metadataPath = path.join(entryRoot, GRANITE_VITEST_RN_CACHE_METADATA_FILENAME);
379
+ fs.mkdirSync(entriesRoot, { recursive: true });
380
+ if (fs.existsSync(entryRoot) && (!fs.existsSync(objectsRoot) || !fs.existsSync(manifestPath))) fs.rmSync(entryRoot, {
381
+ force: true,
382
+ recursive: true
383
+ });
384
+ gcReactNativeMirrorCache(workspaceRoot, cacheEntry.cacheKey, resolvedCacheDir);
385
+ if (fs.existsSync(objectsRoot) && fs.existsSync(manifestPath) && fs.existsSync(metadataPath)) {
386
+ const manifest = readTransformCacheManifest(manifestPath);
387
+ const metadata = readMirrorCacheMetadata(entryRoot);
388
+ const lastAccessedAt = (/* @__PURE__ */ new Date()).toISOString();
389
+ manifest.lastAccessedAt = lastAccessedAt;
390
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
391
+ if (metadata != null) {
392
+ metadata.lastAccessedAt = lastAccessedAt;
393
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
394
+ }
395
+ return {
396
+ entryRoot,
397
+ manifest,
398
+ manifestPath,
399
+ objectsRoot
400
+ };
401
+ }
402
+ const temporaryEntryRoot = path.join(entriesRoot, `${GRANITE_VITEST_RN_CACHE_TMP_PREFIX}${process.pid}-${Date.now()}-${cacheEntry.cacheKey}`);
403
+ const temporaryObjectsRoot = path.join(temporaryEntryRoot, GRANITE_VITEST_RN_OBJECTS_DIRECTORY);
404
+ const temporaryManifestPath = path.join(temporaryEntryRoot, GRANITE_VITEST_RN_MANIFEST_FILENAME);
405
+ const temporaryMetadataPath = path.join(temporaryEntryRoot, GRANITE_VITEST_RN_CACHE_METADATA_FILENAME);
406
+ const now = (/* @__PURE__ */ new Date()).toISOString();
407
+ const packages = getReactNativePackageEntries(workspaceRoot);
408
+ const manifest = {
409
+ cacheKey: cacheEntry.cacheKey,
410
+ createdAt: now,
411
+ lastAccessedAt: now,
412
+ objectsRoot,
413
+ packageRoots: cacheEntry.packageRoots,
414
+ packages,
415
+ sourceToObject: {},
416
+ objectToSource: {},
417
+ transformDependencyVersions: cacheEntry.transformDependencyVersions,
418
+ transformImplementationHash: cacheEntry.transformImplementationHash
419
+ };
420
+ fs.rmSync(temporaryEntryRoot, {
421
+ force: true,
422
+ recursive: true
423
+ });
424
+ fs.mkdirSync(temporaryObjectsRoot, { recursive: true });
425
+ try {
426
+ const temporaryManifest = {
427
+ ...manifest,
428
+ objectsRoot: temporaryObjectsRoot
429
+ };
430
+ for (const { packageRoot } of packages) await writePackageTreeObjects(packageRoot, temporaryObjectsRoot, cacheEntry.packageRoots, temporaryManifest);
431
+ const finalizedManifest = {
432
+ ...temporaryManifest,
433
+ objectsRoot,
434
+ sourceToObject: Object.fromEntries(Object.entries(temporaryManifest.sourceToObject).map(([sourcePath, objectPath]) => [sourcePath, path.join(objectsRoot, path.basename(objectPath))])),
435
+ objectToSource: Object.fromEntries(Object.entries(temporaryManifest.objectToSource).map(([objectPath, sourcePath]) => [path.join(objectsRoot, path.basename(objectPath)), sourcePath]))
436
+ };
437
+ fs.writeFileSync(temporaryManifestPath, JSON.stringify(finalizedManifest, null, 2));
438
+ fs.writeFileSync(temporaryMetadataPath, JSON.stringify({
439
+ cacheKey: cacheEntry.cacheKey,
440
+ createdAt: now,
441
+ lastAccessedAt: now,
442
+ packageRoots: cacheEntry.packageRoots,
443
+ sizeBytes: getDirectorySize(temporaryObjectsRoot),
444
+ transformDependencyVersions: cacheEntry.transformDependencyVersions,
445
+ transformImplementationHash: cacheEntry.transformImplementationHash
446
+ }, null, 2));
447
+ try {
448
+ fs.renameSync(temporaryEntryRoot, entryRoot);
449
+ } catch (error) {
450
+ const errorCode = error.code;
451
+ if (errorCode !== "EEXIST" && errorCode !== "ENOTEMPTY") throw error;
452
+ }
453
+ } finally {
454
+ fs.rmSync(temporaryEntryRoot, {
455
+ force: true,
456
+ recursive: true
457
+ });
458
+ }
459
+ if (fs.existsSync(objectsRoot) && fs.existsSync(manifestPath) && fs.existsSync(metadataPath)) {
460
+ const manifest = readTransformCacheManifest(manifestPath);
461
+ const metadata = readMirrorCacheMetadata(entryRoot);
462
+ const lastAccessedAt = (/* @__PURE__ */ new Date()).toISOString();
463
+ manifest.lastAccessedAt = lastAccessedAt;
464
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
465
+ if (metadata != null) {
466
+ metadata.lastAccessedAt = lastAccessedAt;
467
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
468
+ }
469
+ gcReactNativeMirrorCache(workspaceRoot, cacheEntry.cacheKey, resolvedCacheDir);
470
+ return {
471
+ entryRoot,
472
+ manifest,
473
+ manifestPath,
474
+ objectsRoot
475
+ };
476
+ }
477
+ throw new Error(`Failed to materialize React Native transform cache at ${entryRoot}`);
478
+ }
479
+ async function buildReactNativeMirror(workspaceRoot, resolvedCacheDir = path.join(workspaceRoot, DEFAULT_VITEST_CACHE_DIRECTORY)) {
480
+ return (await buildReactNativeTransformCache(workspaceRoot, resolvedCacheDir)).objectsRoot;
481
+ }
482
+
483
+ //#endregion
484
+ export { shouldTransformReactNativeFile as _, GRANITE_VITEST_RN_OBJECTS_DIRECTORY as a, buildReactNativeMirror as c, resolvePackageRoot as d, resolveReactNativeModuleFromManifest as f, shouldInlineReactNativeDependency as g, REACT_NATIVE_TRANSFORM_EXTENSIONS as h, GRANITE_VITEST_RN_MANIFEST_FILENAME as i, buildReactNativeTransformCache as l, REACT_NATIVE_TRANSFORM_ALLOWLIST as m, GRANITE_VITEST_RN_CACHE_DIRECTORY as n, GRANITE_VITEST_RN_PACKAGES_DIRECTORY as o, resolveReactNativePackageRoots as p, GRANITE_VITEST_RN_CACHE_ENTRIES_DIRECTORY as r, REACT_NATIVE_PLATFORMS as s, DEFAULT_PLATFORM as t, loadReactNativeTransformCacheManifest as u, REACT_NATIVE_ASSET_EXTENSIONS as v, REACT_NATIVE_ASSET_MODULE_ID_PATTERN as y };
@@ -1,4 +1,4 @@
1
- import { n as REACT_NATIVE_ASSET_MODULE_ID_PATTERN } from "./assets-DoucBOz0.js";
1
+ import { f as resolveReactNativeModuleFromManifest, u as loadReactNativeTransformCacheManifest, y as REACT_NATIVE_ASSET_MODULE_ID_PATTERN } from "./mirror-e92G5HdG.js";
2
2
  import { n as installNativeModuleProxy, r as installReactNativeGlobals, t as defineGlobalProperty } from "./runtimeBootstrap-CHWitCG1.js";
3
3
  import Module, { createRequire } from "node:module";
4
4
  import path from "node:path";
@@ -9,11 +9,16 @@ import { vi } from "vitest";
9
9
  const runtimeGlobals = globalThis;
10
10
  const runtimeDirectory = path.dirname(fileURLToPath(import.meta.url));
11
11
  const runtimeRequire = createRequire(import.meta.url);
12
- function resolveReactNativeMirrorRoot() {
13
- if (typeof globalThis.__GRANITE_VITEST_RN_CACHE_ROOT__ === "string" && globalThis.__GRANITE_VITEST_RN_CACHE_ROOT__.length > 0) return path.resolve(globalThis.__GRANITE_VITEST_RN_CACHE_ROOT__);
14
- throw new Error("[@granite-js/vitest] reactNativeRuntime requires reactNative() in vitest.config to set __GRANITE_VITEST_RN_CACHE_ROOT__.");
12
+ function resolveReactNativeManifestPath() {
13
+ if (typeof globalThis.__GRANITE_VITEST_RN_CACHE_MANIFEST__ === "string" && globalThis.__GRANITE_VITEST_RN_CACHE_MANIFEST__.length > 0) return path.resolve(globalThis.__GRANITE_VITEST_RN_CACHE_MANIFEST__);
14
+ throw new Error("[@granite-js/vitest] reactNativeRuntime requires reactNative() in vitest.config to set __GRANITE_VITEST_RN_CACHE_MANIFEST__.");
15
+ }
16
+ const reactNativeTransformCacheManifest = loadReactNativeTransformCacheManifest(resolveReactNativeManifestPath());
17
+ function resolveReactNativeObjectPath(request, importer, manifest = reactNativeTransformCacheManifest) {
18
+ const resolved = resolveReactNativeModuleFromManifest(request, importer, manifest);
19
+ if (resolved == null) throw new Error(`[@granite-js/vitest] Failed to resolve React Native module: ${request}`);
20
+ return resolved.objectPath;
15
21
  }
16
- const reactNativeMirrorRoot = resolveReactNativeMirrorRoot();
17
22
  let nextNativeTag = 1;
18
23
  function createMockNativeMethods() {
19
24
  return {
@@ -1625,83 +1630,52 @@ function patchReactNativeResolution() {
1625
1630
  const moduleWithPrivateResolver = Module;
1626
1631
  const originalResolveFilename = moduleWithPrivateResolver._resolveFilename;
1627
1632
  const originalLoad = moduleWithPrivateResolver._load;
1628
- const reactNativeIndexPath = path.join(reactNativeMirrorRoot, "react-native", "index.js");
1629
- const nativeComponentRegistryMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "NativeComponent", "NativeComponentRegistry.js");
1630
- const requireNativeComponentMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "ReactNative", "requireNativeComponent.js");
1631
- const initializeCoreMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Core", "InitializeCore.js");
1632
- const nativeExceptionsManagerMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Core", "NativeExceptionsManager.js");
1633
- const accessibilityInfoMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Components", "AccessibilityInfo", "AccessibilityInfo.js");
1634
- const clipboardMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Components", "Clipboard", "Clipboard.js");
1635
- const codegenNativeCommandsMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Utilities", "codegenNativeCommands.js");
1636
- const codegenNativeComponentMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Utilities", "codegenNativeComponent.js");
1637
- const refreshControlMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Components", "RefreshControl", "RefreshControl.js");
1638
- const activityIndicatorMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Components", "ActivityIndicator", "ActivityIndicator.js");
1639
- const imageMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Image", "Image.js");
1640
- const textMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Text", "Text.js");
1641
- const textInputMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Components", "TextInput", "TextInput.js");
1642
- const modalMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Modal", "Modal.js");
1643
- const viewMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Components", "View", "View.js");
1644
- const scrollViewMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Components", "ScrollView", "ScrollView.js");
1645
- const nativeModulesMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "BatchedBridge", "NativeModules.js");
1646
- const uiManagerMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "ReactNative", "UIManager.js");
1647
- const appStateMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "AppState", "AppState.js");
1648
- const linkingMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Linking", "Linking.js");
1649
- const vibrationMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Vibration", "Vibration.js");
1650
- const viewNativeComponentMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Components", "View", "ViewNativeComponent.js");
1651
- const useColorSchemeMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Utilities", "useColorScheme.js");
1652
- const platformMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "Utilities", "Platform.js");
1653
- const styleSheetMockPath = path.join(reactNativeMirrorRoot, "react-native", "Libraries", "StyleSheet", "StyleSheet.js");
1633
+ const reactNativeIndexPath = resolveReactNativeObjectPath("react-native");
1654
1634
  const sharedMockModules = getSharedReactNativeMockModules();
1655
- const tryResolveMirroredPackageRequest = (request) => {
1656
- if (request === "jest-react-native") return path.join(reactNativeMirrorRoot, "jest-react-native");
1657
- if (request.startsWith("jest-react-native/")) return path.join(reactNativeMirrorRoot, "jest-react-native", request.slice(18));
1658
- if (request.startsWith("@react-native-community/")) return path.join(reactNativeMirrorRoot, ...request.split("/"));
1659
- return null;
1660
- };
1661
- const isMirroredPackageManifestRequest = (request) => request === "react-native/package.json" || request === "jest-react-native/package.json" || request.startsWith("@react-native/") && request.endsWith("/package.json") || request.startsWith("@react-native-community/") && request.endsWith("/package.json");
1635
+ const sharedMockModuleByPath = new Map([
1636
+ [resolveReactNativeObjectPath("react-native/Libraries/NativeComponent/NativeComponentRegistry.js"), sharedMockModules.nativeComponentRegistryModule],
1637
+ [resolveReactNativeObjectPath("react-native/Libraries/ReactNative/requireNativeComponent.js"), sharedMockModules.requireNativeComponentModule],
1638
+ [resolveReactNativeObjectPath("react-native/Libraries/Core/InitializeCore.js"), sharedMockModules.initializeCoreModule],
1639
+ [resolveReactNativeObjectPath("react-native/Libraries/Core/NativeExceptionsManager.js"), sharedMockModules.nativeExceptionsManagerModule],
1640
+ [resolveReactNativeObjectPath("react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js"), sharedMockModules.accessibilityInfoModule],
1641
+ [resolveReactNativeObjectPath("react-native/Libraries/Components/Clipboard/Clipboard.js"), sharedMockModules.clipboardModule],
1642
+ [resolveReactNativeObjectPath("react-native/Libraries/Utilities/codegenNativeCommands.js"), sharedMockModules.codegenNativeCommandsModule],
1643
+ [resolveReactNativeObjectPath("react-native/Libraries/Utilities/codegenNativeComponent.js"), sharedMockModules.codegenNativeComponentModule],
1644
+ [resolveReactNativeObjectPath("react-native/Libraries/Components/RefreshControl/RefreshControl.js"), sharedMockModules.refreshControlModule],
1645
+ [resolveReactNativeObjectPath("react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js"), sharedMockModules.activityIndicatorModule],
1646
+ [resolveReactNativeObjectPath("react-native/Libraries/Image/Image.js"), sharedMockModules.imageModule],
1647
+ [resolveReactNativeObjectPath("react-native/Libraries/Text/Text.js"), sharedMockModules.textModule],
1648
+ [resolveReactNativeObjectPath("react-native/Libraries/Components/TextInput/TextInput.js"), sharedMockModules.textInputModule],
1649
+ [resolveReactNativeObjectPath("react-native/Libraries/Modal/Modal.js"), sharedMockModules.modalModule],
1650
+ [resolveReactNativeObjectPath("react-native/Libraries/Components/View/View.js"), sharedMockModules.viewModule],
1651
+ [resolveReactNativeObjectPath("react-native/Libraries/Components/ScrollView/ScrollView.js"), sharedMockModules.scrollViewModule],
1652
+ [resolveReactNativeObjectPath("react-native/Libraries/BatchedBridge/NativeModules.js"), sharedMockModules.nativeModulesModule],
1653
+ [resolveReactNativeObjectPath("react-native/Libraries/ReactNative/UIManager.js"), sharedMockModules.uiManagerModule],
1654
+ [resolveReactNativeObjectPath("react-native/Libraries/AppState/AppState.js"), sharedMockModules.appStateModule],
1655
+ [resolveReactNativeObjectPath("react-native/Libraries/Linking/Linking.js"), sharedMockModules.linkingModule],
1656
+ [resolveReactNativeObjectPath("react-native/Libraries/Vibration/Vibration.js"), sharedMockModules.vibrationModule],
1657
+ [resolveReactNativeObjectPath("react-native/Libraries/Components/View/ViewNativeComponent.js"), sharedMockModules.viewNativeComponentModule],
1658
+ [resolveReactNativeObjectPath("react-native/Libraries/Utilities/useColorScheme.js"), sharedMockModules.useColorSchemeModule],
1659
+ [resolveReactNativeObjectPath("react-native/Libraries/Utilities/Platform.js"), sharedMockModules.platformModule],
1660
+ [resolveReactNativeObjectPath("react-native/Libraries/StyleSheet/StyleSheet.js"), sharedMockModules.styleSheetModule]
1661
+ ]);
1662
+ const isReactNativePackageManifestRequest = (request) => request === "react-native/package.json" || request === "jest-react-native/package.json" || request.startsWith("@react-native/") && request.endsWith("/package.json") || request.startsWith("@react-native-community/") && request.endsWith("/package.json");
1662
1663
  moduleWithPrivateResolver._resolveFilename = function patchedResolveFilename(request, parent, isMain, options) {
1663
- if (isMirroredPackageManifestRequest(request)) return originalResolveFilename.call(this, request, parent, isMain, options);
1664
- if (request === "react-native") return originalResolveFilename.call(this, reactNativeIndexPath, parent, isMain, options);
1665
- if (request.startsWith("react-native/")) return originalResolveFilename.call(this, path.join(reactNativeMirrorRoot, "react-native", request.slice(13)), parent, isMain, options);
1666
- if (request.startsWith("@react-native/")) return originalResolveFilename.call(this, path.join(reactNativeMirrorRoot, request), parent, isMain, options);
1667
- const mirroredPackageRequest = tryResolveMirroredPackageRequest(request);
1668
- if (mirroredPackageRequest != null) return originalResolveFilename.call(this, mirroredPackageRequest, parent, isMain, options);
1664
+ if (isReactNativePackageManifestRequest(request)) return originalResolveFilename.call(this, request, parent, isMain, options);
1665
+ const resolved = resolveReactNativeModuleFromManifest(request, parent?.filename, reactNativeTransformCacheManifest);
1666
+ if (resolved != null) return resolved.objectPath;
1669
1667
  return originalResolveFilename.call(this, request, parent, isMain, options);
1670
1668
  };
1671
1669
  moduleWithPrivateResolver._load = function patchedLoad(request, parent, isMain) {
1672
1670
  const resolved = moduleWithPrivateResolver._resolveFilename(request, parent, isMain);
1673
1671
  if (resolved === reactNativeIndexPath) return getSharedReactNativeExports();
1674
- if (resolved === nativeComponentRegistryMockPath) return sharedMockModules.nativeComponentRegistryModule;
1675
- if (resolved === requireNativeComponentMockPath) return sharedMockModules.requireNativeComponentModule;
1676
- if (resolved === initializeCoreMockPath) return sharedMockModules.initializeCoreModule;
1677
- if (resolved === nativeExceptionsManagerMockPath) return sharedMockModules.nativeExceptionsManagerModule;
1678
- if (resolved === accessibilityInfoMockPath) return sharedMockModules.accessibilityInfoModule;
1679
- if (resolved === clipboardMockPath) return sharedMockModules.clipboardModule;
1680
- if (resolved === codegenNativeCommandsMockPath) return sharedMockModules.codegenNativeCommandsModule;
1681
- if (resolved === codegenNativeComponentMockPath) return sharedMockModules.codegenNativeComponentModule;
1682
- if (resolved === refreshControlMockPath) return sharedMockModules.refreshControlModule;
1683
- if (resolved === activityIndicatorMockPath) return sharedMockModules.activityIndicatorModule;
1684
- if (resolved === imageMockPath) return sharedMockModules.imageModule;
1685
- if (resolved === textMockPath) return sharedMockModules.textModule;
1686
- if (resolved === textInputMockPath) return sharedMockModules.textInputModule;
1687
- if (resolved === modalMockPath) return sharedMockModules.modalModule;
1688
- if (resolved === viewMockPath) return sharedMockModules.viewModule;
1689
- if (resolved === scrollViewMockPath) return sharedMockModules.scrollViewModule;
1690
- if (resolved === nativeModulesMockPath) return sharedMockModules.nativeModulesModule;
1691
- if (resolved === uiManagerMockPath) return sharedMockModules.uiManagerModule;
1692
- if (resolved === appStateMockPath) return sharedMockModules.appStateModule;
1693
- if (resolved === linkingMockPath) return sharedMockModules.linkingModule;
1694
- if (resolved === vibrationMockPath) return sharedMockModules.vibrationModule;
1695
- if (resolved === viewNativeComponentMockPath) return sharedMockModules.viewNativeComponentModule;
1696
- if (resolved === useColorSchemeMockPath) return sharedMockModules.useColorSchemeModule;
1697
- if (resolved === platformMockPath) return sharedMockModules.platformModule;
1698
- if (resolved === styleSheetMockPath) return sharedMockModules.styleSheetModule;
1672
+ if (sharedMockModuleByPath.has(resolved)) return sharedMockModuleByPath.get(resolved);
1699
1673
  if (REACT_NATIVE_ASSET_MODULE_ID_PATTERN.test(resolved)) return { testUri: path.relative(runtimeDirectory, resolved.replace(/[?#].*$/, "")).replace(/\\/g, "/") };
1700
1674
  return originalLoad.call(this, request, parent, isMain);
1701
1675
  };
1702
1676
  runtimeGlobals.__rntlVitestRnResolverPatched__ = true;
1703
1677
  }
1704
- runtimeRequire(path.join(reactNativeMirrorRoot, "@react-native", "js-polyfills", "error-guard.js"));
1678
+ runtimeRequire(resolveReactNativeObjectPath("@react-native/js-polyfills/error-guard.js"));
1705
1679
  vi.mock("react-native", () => getSharedReactNativeExports());
1706
1680
  installReactNativeGlobals(runtimeGlobals);
1707
1681
  installNativeModuleProxy(runtimeGlobals);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@granite-js/vitest",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "type": "module",
5
5
  "description": "Vitest helpers for React Native",
6
6
  "types": "./dist/index.d.ts",
@@ -26,7 +26,7 @@
26
26
  "build": "tsdown"
27
27
  },
28
28
  "dependencies": {
29
- "fast-flow-transform": "0.0.3"
29
+ "flow-remove-types": "2.291.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "24.10.12",
@@ -1,16 +0,0 @@
1
- //#region src/assets.ts
2
- const REACT_NATIVE_ASSET_EXTENSIONS = [
3
- "bmp",
4
- "gif",
5
- "jpg",
6
- "jpeg",
7
- "mp4",
8
- "png",
9
- "psd",
10
- "svg",
11
- "webp"
12
- ];
13
- const REACT_NATIVE_ASSET_MODULE_ID_PATTERN = new RegExp(`\\.(${REACT_NATIVE_ASSET_EXTENSIONS.join("|")})(?:[?#].*)?$`, "i");
14
-
15
- //#endregion
16
- export { REACT_NATIVE_ASSET_MODULE_ID_PATTERN as n, REACT_NATIVE_ASSET_EXTENSIONS as t };