@granite-js/vitest 1.0.20
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/LICENSE +202 -0
- package/README.md +283 -0
- package/dist/assets-DoucBOz0.js +16 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +524 -0
- package/dist/reactNativeRuntime.d.ts +9 -0
- package/dist/reactNativeRuntime.js +1711 -0
- package/dist/runtimeBootstrap-CHWitCG1.js +291 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +48 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
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";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import * as vite from "vitest/node";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import transform from "fast-flow-transform";
|
|
9
|
+
|
|
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
|
+
//#region src/reactNative.ts
|
|
400
|
+
const PLATFORM_RESOLUTION_ORDER = [
|
|
401
|
+
DEFAULT_PLATFORM,
|
|
402
|
+
"native",
|
|
403
|
+
...REACT_NATIVE_PLATFORMS.filter((platform) => platform !== DEFAULT_PLATFORM && platform !== "native")
|
|
404
|
+
];
|
|
405
|
+
const PLATFORM_EXTENSION_SUFFIXES = [
|
|
406
|
+
"tsx",
|
|
407
|
+
"ts",
|
|
408
|
+
"jsx",
|
|
409
|
+
"js"
|
|
410
|
+
];
|
|
411
|
+
const REACT_NATIVE_RESOLVE_EXTENSIONS = [
|
|
412
|
+
...PLATFORM_RESOLUTION_ORDER.flatMap((platform) => PLATFORM_EXTENSION_SUFFIXES.map((extension) => `.${platform}.${extension}`)),
|
|
413
|
+
".tsx",
|
|
414
|
+
".ts",
|
|
415
|
+
".jsx",
|
|
416
|
+
".js",
|
|
417
|
+
".json"
|
|
418
|
+
];
|
|
419
|
+
const REACT_NATIVE_EXPORT_CONDITIONS = ["require", "react-native"];
|
|
420
|
+
const JEST_LIKE_TEST_PATTERNS = [
|
|
421
|
+
"**/__tests__/**/*.test.{js,jsx,ts,tsx}",
|
|
422
|
+
"**/__tests__/**/*.spec.{js,jsx,ts,tsx}",
|
|
423
|
+
"**/?(*.)test.{js,jsx,ts,tsx}",
|
|
424
|
+
"**/?(*.)spec.{js,jsx,ts,tsx}"
|
|
425
|
+
];
|
|
426
|
+
const currentFilePath = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
427
|
+
const currentDirectory = path.dirname(currentFilePath);
|
|
428
|
+
function getSetupFileExtension() {
|
|
429
|
+
const extension = path.extname(currentFilePath);
|
|
430
|
+
if (extension === ".ts" || extension === ".mts") return ".ts";
|
|
431
|
+
return ".js";
|
|
432
|
+
}
|
|
433
|
+
function resolveReactNativeSetupFiles() {
|
|
434
|
+
const extension = getSetupFileExtension();
|
|
435
|
+
return [path.join(currentDirectory, `reactNativeRuntime${extension}`), path.join(currentDirectory, `setup${extension}`)];
|
|
436
|
+
}
|
|
437
|
+
function reactNative() {
|
|
438
|
+
return {
|
|
439
|
+
enforce: "pre",
|
|
440
|
+
name: "granite-react-native",
|
|
441
|
+
async config(conf) {
|
|
442
|
+
const workspaceRoot = conf.root ?? conf.test?.root ?? process.cwd();
|
|
443
|
+
const cacheDir = conf.cacheDir ?? ".vitest";
|
|
444
|
+
const reactNativeMirrorRoot = await buildReactNativeMirror(workspaceRoot, path.isAbsolute(cacheDir) ? cacheDir : path.join(workspaceRoot, cacheDir));
|
|
445
|
+
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
|
+
conditions: [...REACT_NATIVE_EXPORT_CONDITIONS],
|
|
477
|
+
extensions: [...REACT_NATIVE_RESOLVE_EXTENSIONS]
|
|
478
|
+
};
|
|
479
|
+
const commonConfig = {
|
|
480
|
+
define: { "globalThis.__GRANITE_VITEST_RN_CACHE_ROOT__": JSON.stringify(reactNativeMirrorRoot) },
|
|
481
|
+
resolve,
|
|
482
|
+
test: {
|
|
483
|
+
environment: "node",
|
|
484
|
+
globals: true,
|
|
485
|
+
include: [...JEST_LIKE_TEST_PATTERNS],
|
|
486
|
+
setupFiles: resolveReactNativeSetupFiles()
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
if ("rolldownVersion" in vite) return {
|
|
490
|
+
oxc: { jsx: {
|
|
491
|
+
importSource: "react",
|
|
492
|
+
runtime: "automatic"
|
|
493
|
+
} },
|
|
494
|
+
...commonConfig
|
|
495
|
+
};
|
|
496
|
+
return {
|
|
497
|
+
esbuild: {
|
|
498
|
+
jsx: "automatic",
|
|
499
|
+
jsxImportSource: "react"
|
|
500
|
+
},
|
|
501
|
+
...commonConfig
|
|
502
|
+
};
|
|
503
|
+
},
|
|
504
|
+
load(id) {
|
|
505
|
+
if (!REACT_NATIVE_ASSET_MODULE_ID_PATTERN.test(id)) return;
|
|
506
|
+
const asset = { testUri: path.relative(currentDirectory, id.replace(/[?#].*$/, "")).replace(/\\/g, "/") };
|
|
507
|
+
return [
|
|
508
|
+
`const asset = ${JSON.stringify(asset)};`,
|
|
509
|
+
"export const testUri = asset.testUri;",
|
|
510
|
+
"export default asset;"
|
|
511
|
+
].join("\n");
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/index.ts
|
|
518
|
+
var src_default = {
|
|
519
|
+
reactNative,
|
|
520
|
+
resolveReactNativeSetupFiles
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
//#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 };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "@react-native/js-polyfills/error-guard";
|
|
2
|
+
import * as react from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/reactNativeRuntime.d.ts
|
|
5
|
+
type ReactModule = typeof react;
|
|
6
|
+
declare function createMockHostComponent(React: ReactModule, displayName: string, hostType: string, instanceMethods?: Record<string, unknown>, realComponent?: any): any;
|
|
7
|
+
declare function createMockNativeComponent(React: ReactModule, viewName: string): any;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { createMockHostComponent, createMockNativeComponent };
|