@cloudflare/vitest-pool-workers 0.2.1 → 0.2.3
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/dist/config/d1.d.ts +7 -0
- package/dist/config/index.cjs +130 -0
- package/dist/config/index.cjs.map +6 -0
- package/dist/config/index.d.ts +21 -0
- package/dist/pool/config.d.ts +82 -0
- package/dist/pool/helpers.d.ts +5 -0
- package/dist/pool/index.mjs +1529 -0
- package/dist/pool/index.mjs.map +6 -0
- package/dist/shared/d1.d.ts +4 -0
- package/dist/worker/index.mjs +352 -0
- package/dist/worker/index.mjs.map +6 -0
- package/dist/worker/lib/cloudflare/empty-internal.cjs +27 -0
- package/dist/worker/lib/cloudflare/empty-internal.cjs.map +6 -0
- package/dist/worker/lib/cloudflare/mock-agent.cjs +2056 -0
- package/dist/worker/lib/cloudflare/mock-agent.cjs.map +6 -0
- package/dist/worker/lib/cloudflare/test-internal.mjs +739 -0
- package/dist/worker/lib/cloudflare/test-internal.mjs.map +6 -0
- package/dist/worker/lib/cloudflare/test-runner.mjs +222 -0
- package/dist/worker/lib/cloudflare/test-runner.mjs.map +6 -0
- package/dist/worker/lib/cloudflare/test.mjs +30 -0
- package/dist/worker/lib/cloudflare/test.mjs.map +6 -0
- package/dist/worker/lib/debug.mjs +9 -0
- package/dist/worker/lib/debug.mjs.map +6 -0
- package/dist/worker/lib/mlly.mjs +48 -0
- package/dist/worker/lib/mlly.mjs.map +6 -0
- package/dist/worker/lib/node/console.mjs +104 -0
- package/dist/worker/lib/node/console.mjs.map +6 -0
- package/dist/worker/lib/node/dns.mjs +6 -0
- package/dist/worker/lib/node/dns.mjs.map +6 -0
- package/dist/worker/lib/node/fs/promises.mjs +6 -0
- package/dist/worker/lib/node/fs/promises.mjs.map +6 -0
- package/dist/worker/lib/node/fs.mjs +25 -0
- package/dist/worker/lib/node/fs.mjs.map +6 -0
- package/dist/worker/lib/node/http.cjs +97 -0
- package/dist/worker/lib/node/http.cjs.map +6 -0
- package/dist/worker/lib/node/module.mjs +15 -0
- package/dist/worker/lib/node/module.mjs.map +6 -0
- package/dist/worker/lib/node/net.cjs +27 -0
- package/dist/worker/lib/node/net.cjs.map +6 -0
- package/dist/worker/lib/node/perf_hooks.mjs +6 -0
- package/dist/worker/lib/node/perf_hooks.mjs.map +6 -0
- package/dist/worker/lib/node/querystring.cjs +44 -0
- package/dist/worker/lib/node/querystring.cjs.map +6 -0
- package/dist/worker/lib/node/timers.mjs +6 -0
- package/dist/worker/lib/node/timers.mjs.map +6 -0
- package/dist/worker/lib/node/tty.mjs +8 -0
- package/dist/worker/lib/node/tty.mjs.map +6 -0
- package/dist/worker/lib/node/url.mjs +75 -0
- package/dist/worker/lib/node/url.mjs.map +6 -0
- package/dist/worker/lib/node/vm.mjs +17 -0
- package/dist/worker/lib/node/vm.mjs.map +6 -0
- package/dist/worker/lib/tinypool.mjs +6 -0
- package/dist/worker/lib/tinypool.mjs.map +6 -0
- package/package.json +4 -4
|
@@ -0,0 +1,1529 @@
|
|
|
1
|
+
// <define:VITEST_POOL_WORKERS_DEFINE_BUILTIN_MODULES>
|
|
2
|
+
var define_VITEST_POOL_WORKERS_DEFINE_BUILTIN_MODULES_default = ["workerd:compatibility-flags", "node-internal:async_hooks", "node-internal:buffer", "node-internal:crypto", "node-internal:util", "node-internal:diagnostics_channel", "node:_stream_duplex", "node:_stream_passthrough", "node:_stream_readable", "node:_stream_transform", "node:_stream_writable", "node:assert", "node:async_hooks", "node:buffer", "node:crypto", "node:diagnostics_channel", "node:events", "node:path", "node:process", "node:stream", "node:stream/consumers", "node:stream/promises", "node:stream/web", "node:string_decoder", "node:test", "node:util", "node-internal:constants", "node-internal:crypto_dh", "node-internal:crypto_hash", "node-internal:crypto_hkdf", "node-internal:crypto_keys", "node-internal:crypto_pbkdf2", "node-internal:crypto_random", "node-internal:crypto_util", "node-internal:debuglog", "node-internal:events", "node-internal:internal_assert", "node-internal:internal_assertionerror", "node-internal:internal_buffer", "node-internal:internal_comparisons", "node-internal:internal_diffs", "node-internal:internal_errors", "node-internal:internal_inspect", "node-internal:internal_path", "node-internal:internal_stringdecoder", "node-internal:internal_types", "node-internal:internal_utils", "node-internal:mock", "node-internal:process", "node-internal:streams_adapters", "node-internal:streams_compose", "node-internal:streams_duplex", "node-internal:streams_legacy", "node-internal:streams_pipeline", "node-internal:streams_promises", "node-internal:streams_readable", "node-internal:streams_transform", "node-internal:streams_util", "node-internal:streams_writable", "node-internal:validators", "internal:unsafe-eval", "cloudflare-internal:sockets", "cloudflare:ai", "cloudflare:email", "cloudflare:sockets", "cloudflare:vectorize", "cloudflare:workers", "cloudflare-internal:ai-api", "cloudflare-internal:d1-api", "cloudflare-internal:vectorize-api", "cloudflare-internal:workers", "workerd:unsafe"];
|
|
3
|
+
|
|
4
|
+
// src/pool/index.ts
|
|
5
|
+
import assert4 from "node:assert";
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
import events from "node:events";
|
|
8
|
+
import fs3 from "node:fs";
|
|
9
|
+
import path4 from "node:path";
|
|
10
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
11
|
+
import util2 from "node:util";
|
|
12
|
+
import { createBirpc } from "birpc";
|
|
13
|
+
import * as devalue from "devalue";
|
|
14
|
+
import {
|
|
15
|
+
compileModuleRules,
|
|
16
|
+
kCurrentWorker,
|
|
17
|
+
kUnsafeEphemeralUniqueKey,
|
|
18
|
+
Log,
|
|
19
|
+
LogLevel,
|
|
20
|
+
maybeApply,
|
|
21
|
+
Miniflare,
|
|
22
|
+
structuredSerializableReducers,
|
|
23
|
+
structuredSerializableRevivers,
|
|
24
|
+
testRegExps,
|
|
25
|
+
WebSocket
|
|
26
|
+
} from "miniflare";
|
|
27
|
+
import { createMethodsRPC } from "vitest/node";
|
|
28
|
+
|
|
29
|
+
// src/shared/chunking-socket.ts
|
|
30
|
+
import assert from "node:assert";
|
|
31
|
+
import { Buffer } from "node:buffer";
|
|
32
|
+
function createChunkingSocket(socket, maxChunkByteLength = 1048576) {
|
|
33
|
+
const listeners = [];
|
|
34
|
+
const decoder = new TextDecoder();
|
|
35
|
+
let chunks;
|
|
36
|
+
socket.on((message) => {
|
|
37
|
+
if (typeof message === "string") {
|
|
38
|
+
if (chunks !== void 0) {
|
|
39
|
+
assert.strictEqual(message, "", "Expected end-of-chunks");
|
|
40
|
+
message = chunks + decoder.decode();
|
|
41
|
+
chunks = void 0;
|
|
42
|
+
}
|
|
43
|
+
for (const listener of listeners)
|
|
44
|
+
listener(message);
|
|
45
|
+
} else {
|
|
46
|
+
chunks ??= "";
|
|
47
|
+
chunks += decoder.decode(message, { stream: true });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
post(value) {
|
|
52
|
+
if (Buffer.byteLength(value) > maxChunkByteLength) {
|
|
53
|
+
const encoded = Buffer.from(value);
|
|
54
|
+
for (let i = 0; i < encoded.byteLength; i += maxChunkByteLength) {
|
|
55
|
+
socket.post(encoded.subarray(i, i + maxChunkByteLength));
|
|
56
|
+
}
|
|
57
|
+
socket.post("");
|
|
58
|
+
} else {
|
|
59
|
+
socket.post(value);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
on(listener) {
|
|
63
|
+
listeners.push(listener);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/pool/config.ts
|
|
69
|
+
import path2 from "node:path";
|
|
70
|
+
import {
|
|
71
|
+
formatZodError,
|
|
72
|
+
getRootPath,
|
|
73
|
+
mergeWorkerOptions,
|
|
74
|
+
parseWithRootPath,
|
|
75
|
+
PLUGINS
|
|
76
|
+
} from "miniflare";
|
|
77
|
+
import { z } from "zod";
|
|
78
|
+
|
|
79
|
+
// src/pool/helpers.ts
|
|
80
|
+
import path from "node:path";
|
|
81
|
+
var WORKER_NAME_PREFIX = "vitest-pool-workers-";
|
|
82
|
+
function isFileNotFoundError(e) {
|
|
83
|
+
return typeof e === "object" && e !== null && "code" in e && e.code === "ENOENT";
|
|
84
|
+
}
|
|
85
|
+
function getProjectPath(project) {
|
|
86
|
+
return project.config.config ?? project.path;
|
|
87
|
+
}
|
|
88
|
+
function getRelativeProjectPath(project) {
|
|
89
|
+
const projectPath = getProjectPath(project);
|
|
90
|
+
if (typeof projectPath === "number")
|
|
91
|
+
return projectPath;
|
|
92
|
+
else
|
|
93
|
+
return path.relative("", projectPath);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/pool/config.ts
|
|
97
|
+
var PLUGIN_VALUES = Object.values(PLUGINS);
|
|
98
|
+
var OPTIONS_PATH_ARRAY = ["test", "poolOptions", "workers"];
|
|
99
|
+
var OPTIONS_PATH = OPTIONS_PATH_ARRAY.join(".");
|
|
100
|
+
var WorkersPoolOptionsSchema = z.object({
|
|
101
|
+
/**
|
|
102
|
+
* Entrypoint to Worker run in the same isolate/context as tests. This is
|
|
103
|
+
* required to use `import { SELF } from "cloudflare:test"`, or Durable
|
|
104
|
+
* Objects without an explicit `scriptName`. Note this goes through Vite
|
|
105
|
+
* transforms and can be a TypeScript file. Note also
|
|
106
|
+
* `import module from "<path-to-main>"` inside tests gives exactly the same
|
|
107
|
+
* `module` instance as is used internally for the `SELF` and Durable Object
|
|
108
|
+
* bindings.
|
|
109
|
+
*/
|
|
110
|
+
main: z.ostring(),
|
|
111
|
+
/**
|
|
112
|
+
* Enables per-test isolated storage. If enabled, any writes to storage
|
|
113
|
+
* performed in a test will be undone at the end of the test. The test storage
|
|
114
|
+
* environment is copied from the containing suite, meaning `beforeAll()`
|
|
115
|
+
* hooks can be used to seed data. If this is disabled, all tests will share
|
|
116
|
+
* the same storage.
|
|
117
|
+
*/
|
|
118
|
+
isolatedStorage: z.boolean().default(true),
|
|
119
|
+
/**
|
|
120
|
+
* Runs all tests in this project serially in the same worker, using the same
|
|
121
|
+
* module cache. This can significantly speed up tests if you've got lots of
|
|
122
|
+
* small test files.
|
|
123
|
+
*/
|
|
124
|
+
singleWorker: z.boolean().default(false),
|
|
125
|
+
miniflare: z.object({
|
|
126
|
+
workers: z.array(z.object({}).passthrough()).optional()
|
|
127
|
+
}).passthrough().optional(),
|
|
128
|
+
wrangler: z.object({ configPath: z.ostring(), environment: z.ostring() }).optional()
|
|
129
|
+
});
|
|
130
|
+
function isZodErrorLike(value) {
|
|
131
|
+
return typeof value === "object" && value !== null && "issues" in value && Array.isArray(value.issues);
|
|
132
|
+
}
|
|
133
|
+
function coalesceZodErrors(ref, thrown) {
|
|
134
|
+
if (!isZodErrorLike(thrown))
|
|
135
|
+
throw thrown;
|
|
136
|
+
if (ref.value === void 0)
|
|
137
|
+
ref.value = thrown;
|
|
138
|
+
else
|
|
139
|
+
ref.value.issues.push(...thrown.issues);
|
|
140
|
+
}
|
|
141
|
+
function parseWorkerOptions(rootPath, value, withoutScript, opts) {
|
|
142
|
+
if (withoutScript) {
|
|
143
|
+
value["script"] = "";
|
|
144
|
+
delete value["scriptPath"];
|
|
145
|
+
delete value["modules"];
|
|
146
|
+
delete value["modulesRoot"];
|
|
147
|
+
}
|
|
148
|
+
const result = {};
|
|
149
|
+
const errorRef = {};
|
|
150
|
+
for (const plugin of PLUGIN_VALUES) {
|
|
151
|
+
try {
|
|
152
|
+
const parsed = parseWithRootPath(rootPath, plugin.options, value, opts);
|
|
153
|
+
Object.assign(result, parsed);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
coalesceZodErrors(errorRef, e);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (errorRef.value !== void 0)
|
|
159
|
+
throw errorRef.value;
|
|
160
|
+
if (withoutScript)
|
|
161
|
+
delete value["script"];
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
async function parseCustomPoolOptions(rootPath, value, opts) {
|
|
165
|
+
const options = WorkersPoolOptionsSchema.parse(
|
|
166
|
+
value,
|
|
167
|
+
opts
|
|
168
|
+
);
|
|
169
|
+
options.miniflare ??= {};
|
|
170
|
+
const errorRef = {};
|
|
171
|
+
const workers = options.miniflare?.workers;
|
|
172
|
+
const rootPathOption = getRootPath(options.miniflare);
|
|
173
|
+
rootPath = path2.resolve(rootPath, rootPathOption);
|
|
174
|
+
try {
|
|
175
|
+
options.miniflare = parseWorkerOptions(
|
|
176
|
+
rootPath,
|
|
177
|
+
options.miniflare,
|
|
178
|
+
/* withoutScript */
|
|
179
|
+
true,
|
|
180
|
+
// (script provided by runner)
|
|
181
|
+
{ path: [...opts.path, "miniflare"] }
|
|
182
|
+
);
|
|
183
|
+
} catch (e) {
|
|
184
|
+
coalesceZodErrors(errorRef, e);
|
|
185
|
+
}
|
|
186
|
+
if (workers !== void 0) {
|
|
187
|
+
options.miniflare.workers = workers.map((worker, i) => {
|
|
188
|
+
try {
|
|
189
|
+
const workerRootPathOption = getRootPath(worker);
|
|
190
|
+
const workerRootPath = path2.resolve(rootPath, workerRootPathOption);
|
|
191
|
+
return parseWorkerOptions(
|
|
192
|
+
workerRootPath,
|
|
193
|
+
worker,
|
|
194
|
+
/* withoutScript */
|
|
195
|
+
false,
|
|
196
|
+
{
|
|
197
|
+
path: [...opts.path, "miniflare", "workers", i]
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
} catch (e) {
|
|
201
|
+
coalesceZodErrors(errorRef, e);
|
|
202
|
+
return { script: "" };
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (errorRef.value !== void 0)
|
|
207
|
+
throw errorRef.value;
|
|
208
|
+
if (options.wrangler?.configPath !== void 0) {
|
|
209
|
+
const configPath = path2.resolve(rootPath, options.wrangler.configPath);
|
|
210
|
+
options.wrangler.configPath = configPath;
|
|
211
|
+
const wrangler = await import("wrangler");
|
|
212
|
+
const { workerOptions, define, main } = wrangler.unstable_getMiniflareWorkerOptions(
|
|
213
|
+
configPath,
|
|
214
|
+
options.wrangler.environment
|
|
215
|
+
);
|
|
216
|
+
options.main ??= main;
|
|
217
|
+
options.miniflare = mergeWorkerOptions(
|
|
218
|
+
workerOptions,
|
|
219
|
+
options.miniflare
|
|
220
|
+
);
|
|
221
|
+
options.defines = define;
|
|
222
|
+
}
|
|
223
|
+
return options;
|
|
224
|
+
}
|
|
225
|
+
async function parseProjectOptions(project) {
|
|
226
|
+
const environment = project.config.environment;
|
|
227
|
+
if (environment !== void 0 && environment !== "node") {
|
|
228
|
+
const quotedEnvironment = JSON.stringify(environment);
|
|
229
|
+
let migrationGuide = ".";
|
|
230
|
+
if (environment === "miniflare") {
|
|
231
|
+
migrationGuide = ", and refer to the migration guide if upgrading from `vitest-environment-miniflare`:\nhttps://developers.cloudflare.com/workers/testing/vitest-integration/get-started/migrate-from-miniflare-2/";
|
|
232
|
+
}
|
|
233
|
+
const relativePath = getRelativeProjectPath(project);
|
|
234
|
+
const message = [
|
|
235
|
+
`Unexpected custom \`environment\` ${quotedEnvironment} in project ${relativePath}.`,
|
|
236
|
+
"The Workers pool always runs your tests inside of an environment providing Workers runtime APIs.",
|
|
237
|
+
`Please remove the \`environment\` configuration${migrationGuide}`,
|
|
238
|
+
"Use `poolMatchGlobs`/`environmentMatchGlobs` to run a subset of your tests in a different pool/environment."
|
|
239
|
+
].join("\n");
|
|
240
|
+
throw new TypeError(message);
|
|
241
|
+
}
|
|
242
|
+
const projectPath = getProjectPath(project);
|
|
243
|
+
const rootPath = typeof projectPath === "string" ? path2.dirname(projectPath) : "";
|
|
244
|
+
const poolOptions = project.config.poolOptions;
|
|
245
|
+
let workersPoolOptions = poolOptions?.workers ?? {};
|
|
246
|
+
try {
|
|
247
|
+
if (typeof workersPoolOptions === "function") {
|
|
248
|
+
const inject = (key) => {
|
|
249
|
+
return project.getProvidedContext()[key];
|
|
250
|
+
};
|
|
251
|
+
workersPoolOptions = await workersPoolOptions({ inject });
|
|
252
|
+
}
|
|
253
|
+
return await parseCustomPoolOptions(rootPath, workersPoolOptions, {
|
|
254
|
+
path: OPTIONS_PATH_ARRAY
|
|
255
|
+
});
|
|
256
|
+
} catch (e) {
|
|
257
|
+
if (!isZodErrorLike(e))
|
|
258
|
+
throw e;
|
|
259
|
+
let formatted;
|
|
260
|
+
try {
|
|
261
|
+
formatted = formatZodError(e, {
|
|
262
|
+
test: { poolOptions: { workers: workersPoolOptions } }
|
|
263
|
+
});
|
|
264
|
+
} catch (error) {
|
|
265
|
+
throw e;
|
|
266
|
+
}
|
|
267
|
+
const relativePath = getRelativeProjectPath(project);
|
|
268
|
+
throw new TypeError(
|
|
269
|
+
`Unexpected pool options in project ${relativePath}:
|
|
270
|
+
${formatted}`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/pool/loopback.ts
|
|
276
|
+
import assert2 from "node:assert";
|
|
277
|
+
import fs from "node:fs/promises";
|
|
278
|
+
import path3 from "node:path";
|
|
279
|
+
import {
|
|
280
|
+
CACHE_PLUGIN_NAME,
|
|
281
|
+
D1_PLUGIN_NAME,
|
|
282
|
+
DURABLE_OBJECTS_PLUGIN_NAME,
|
|
283
|
+
KV_PLUGIN_NAME,
|
|
284
|
+
Mutex,
|
|
285
|
+
R2_PLUGIN_NAME,
|
|
286
|
+
Response
|
|
287
|
+
} from "miniflare";
|
|
288
|
+
async function handleSnapshotRequest(request, url) {
|
|
289
|
+
const filePath = url.searchParams.get("path");
|
|
290
|
+
if (filePath === null)
|
|
291
|
+
return new Response(null, { status: 400 });
|
|
292
|
+
if (request.method === "POST") {
|
|
293
|
+
await fs.mkdir(filePath, { recursive: true });
|
|
294
|
+
return new Response(null, { status: 204 });
|
|
295
|
+
}
|
|
296
|
+
if (request.method === "PUT") {
|
|
297
|
+
const snapshot = await request.arrayBuffer();
|
|
298
|
+
await fs.mkdir(path3.posix.dirname(filePath), { recursive: true });
|
|
299
|
+
await fs.writeFile(filePath, new Uint8Array(snapshot));
|
|
300
|
+
return new Response(null, { status: 204 });
|
|
301
|
+
}
|
|
302
|
+
if (request.method === "GET") {
|
|
303
|
+
try {
|
|
304
|
+
return new Response(await fs.readFile(filePath));
|
|
305
|
+
} catch (e) {
|
|
306
|
+
if (!isFileNotFoundError(e))
|
|
307
|
+
throw e;
|
|
308
|
+
return new Response(null, { status: 404 });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (request.method === "DELETE") {
|
|
312
|
+
try {
|
|
313
|
+
await fs.unlink(filePath);
|
|
314
|
+
} catch (e) {
|
|
315
|
+
if (!isFileNotFoundError(e))
|
|
316
|
+
throw e;
|
|
317
|
+
}
|
|
318
|
+
return new Response(null, { status: 204 });
|
|
319
|
+
}
|
|
320
|
+
return new Response(null, { status: 405 });
|
|
321
|
+
}
|
|
322
|
+
async function emptyDir(dirPath) {
|
|
323
|
+
let names;
|
|
324
|
+
try {
|
|
325
|
+
names = await fs.readdir(dirPath);
|
|
326
|
+
} catch (e) {
|
|
327
|
+
if (isFileNotFoundError(e))
|
|
328
|
+
return;
|
|
329
|
+
throw e;
|
|
330
|
+
}
|
|
331
|
+
for (const name of names) {
|
|
332
|
+
await fs.rm(path3.join(dirPath, name), { recursive: true, force: true });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
var stackStates = /* @__PURE__ */ new WeakMap();
|
|
336
|
+
function getState(mf) {
|
|
337
|
+
let state = stackStates.get(mf);
|
|
338
|
+
if (state === void 0) {
|
|
339
|
+
const persistPaths = mf.unsafeGetPersistPaths();
|
|
340
|
+
const durableObjectPersistPath = persistPaths.get("do");
|
|
341
|
+
assert2(
|
|
342
|
+
durableObjectPersistPath !== void 0,
|
|
343
|
+
"Expected Durable Object persist path"
|
|
344
|
+
);
|
|
345
|
+
state = {
|
|
346
|
+
mutex: new Mutex(),
|
|
347
|
+
depth: 0,
|
|
348
|
+
broken: false,
|
|
349
|
+
persistPaths: Array.from(new Set(persistPaths.values())),
|
|
350
|
+
durableObjectPersistPath
|
|
351
|
+
};
|
|
352
|
+
stackStates.set(mf, state);
|
|
353
|
+
}
|
|
354
|
+
return state;
|
|
355
|
+
}
|
|
356
|
+
var ABORT_ALL_WORKER_NAME = `${WORKER_NAME_PREFIX}abort-all`;
|
|
357
|
+
var ABORT_ALL_WORKER = {
|
|
358
|
+
name: ABORT_ALL_WORKER_NAME,
|
|
359
|
+
compatibilityFlags: ["unsafe_module"],
|
|
360
|
+
modules: [
|
|
361
|
+
{
|
|
362
|
+
type: "ESModule",
|
|
363
|
+
path: "index.mjs",
|
|
364
|
+
contents: `
|
|
365
|
+
import workerdUnsafe from "workerd:unsafe";
|
|
366
|
+
export default {
|
|
367
|
+
async fetch(request) {
|
|
368
|
+
if (request.method !== "DELETE") return new Response(null, { status: 405 });
|
|
369
|
+
await workerdUnsafe.abortAllDurableObjects();
|
|
370
|
+
return new Response(null, { status: 204 });
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
`
|
|
374
|
+
}
|
|
375
|
+
]
|
|
376
|
+
};
|
|
377
|
+
function scheduleStorageReset(mf) {
|
|
378
|
+
const state = getState(mf);
|
|
379
|
+
assert2(state.storageResetPromise === void 0);
|
|
380
|
+
state.storageResetPromise = state.mutex.runWith(async () => {
|
|
381
|
+
const abortAllWorker = await mf.getWorker(ABORT_ALL_WORKER_NAME);
|
|
382
|
+
await abortAllWorker.fetch("http://placeholder", { method: "DELETE" });
|
|
383
|
+
for (const persistPath of state.persistPaths) {
|
|
384
|
+
await emptyDir(persistPath);
|
|
385
|
+
}
|
|
386
|
+
state.depth = 0;
|
|
387
|
+
state.storageResetPromise = void 0;
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
async function waitForStorageReset(mf) {
|
|
391
|
+
await getState(mf).storageResetPromise;
|
|
392
|
+
}
|
|
393
|
+
var BLOBS_DIR_NAME = "blobs";
|
|
394
|
+
var STACK_DIR_NAME = "__vitest_pool_workers_stack";
|
|
395
|
+
async function pushStackedStorage(intoDepth, persistPath) {
|
|
396
|
+
const stackFramePath = path3.join(
|
|
397
|
+
persistPath,
|
|
398
|
+
STACK_DIR_NAME,
|
|
399
|
+
intoDepth.toString()
|
|
400
|
+
);
|
|
401
|
+
await fs.mkdir(stackFramePath, { recursive: true });
|
|
402
|
+
for (const key of await fs.readdir(persistPath, { withFileTypes: true })) {
|
|
403
|
+
if (key.name === STACK_DIR_NAME)
|
|
404
|
+
continue;
|
|
405
|
+
const keyPath = path3.join(persistPath, key.name);
|
|
406
|
+
const stackFrameKeyPath = path3.join(stackFramePath, key.name);
|
|
407
|
+
assert2(key.isDirectory(), `Expected ${keyPath} to be a directory`);
|
|
408
|
+
let createdStackFrameKeyPath = false;
|
|
409
|
+
for (const name of await fs.readdir(keyPath)) {
|
|
410
|
+
if (name === BLOBS_DIR_NAME)
|
|
411
|
+
break;
|
|
412
|
+
if (!createdStackFrameKeyPath) {
|
|
413
|
+
createdStackFrameKeyPath = true;
|
|
414
|
+
await fs.mkdir(stackFrameKeyPath);
|
|
415
|
+
}
|
|
416
|
+
const namePath = path3.join(keyPath, name);
|
|
417
|
+
const stackFrameNamePath = path3.join(stackFrameKeyPath, name);
|
|
418
|
+
assert2(name.endsWith(".sqlite"), `Expected .sqlite, got ${namePath}`);
|
|
419
|
+
await fs.copyFile(namePath, stackFrameNamePath);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async function popStackedStorage(fromDepth, persistPath) {
|
|
424
|
+
for (const key of await fs.readdir(persistPath, { withFileTypes: true })) {
|
|
425
|
+
if (key.name === STACK_DIR_NAME)
|
|
426
|
+
continue;
|
|
427
|
+
const keyPath = path3.join(persistPath, key.name);
|
|
428
|
+
for (const name of await fs.readdir(keyPath)) {
|
|
429
|
+
if (name === BLOBS_DIR_NAME)
|
|
430
|
+
break;
|
|
431
|
+
const namePath = path3.join(keyPath, name);
|
|
432
|
+
assert2(name.endsWith(".sqlite"), `Expected .sqlite, got ${namePath}`);
|
|
433
|
+
await fs.unlink(namePath);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const stackFramePath = path3.join(
|
|
437
|
+
persistPath,
|
|
438
|
+
STACK_DIR_NAME,
|
|
439
|
+
fromDepth.toString()
|
|
440
|
+
);
|
|
441
|
+
await fs.cp(stackFramePath, persistPath, { recursive: true });
|
|
442
|
+
await fs.rm(stackFramePath, { recursive: true, force: true });
|
|
443
|
+
}
|
|
444
|
+
var PLUGIN_PRODUCT_NAMES = {
|
|
445
|
+
[CACHE_PLUGIN_NAME]: "Cache",
|
|
446
|
+
[D1_PLUGIN_NAME]: "D1",
|
|
447
|
+
[DURABLE_OBJECTS_PLUGIN_NAME]: "Durable Objects",
|
|
448
|
+
[KV_PLUGIN_NAME]: "KV",
|
|
449
|
+
[R2_PLUGIN_NAME]: "R2"
|
|
450
|
+
};
|
|
451
|
+
var LIST_FORMAT = new Intl.ListFormat("en-US");
|
|
452
|
+
function checkAllStorageOperationsResolved(action, source, persistPaths, results) {
|
|
453
|
+
const failedProducts = [];
|
|
454
|
+
const lines = [];
|
|
455
|
+
for (let i = 0; i < results.length; i++) {
|
|
456
|
+
const result = results[i];
|
|
457
|
+
if (result.status === "rejected") {
|
|
458
|
+
const pluginName = path3.basename(persistPaths[i]);
|
|
459
|
+
const productName = PLUGIN_PRODUCT_NAMES[pluginName] ?? pluginName;
|
|
460
|
+
failedProducts.push(productName);
|
|
461
|
+
lines.push(`- ${result.reason}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (failedProducts.length > 0) {
|
|
465
|
+
const separator = "=".repeat(80);
|
|
466
|
+
lines.unshift(
|
|
467
|
+
"",
|
|
468
|
+
separator,
|
|
469
|
+
`Failed to ${action} isolated storage stack frame in ${source}.`,
|
|
470
|
+
"This usually means your Worker tried to access storage outside of a test.",
|
|
471
|
+
`In particular, we were unable to ${action} ${LIST_FORMAT.format(
|
|
472
|
+
failedProducts
|
|
473
|
+
)} storage.`,
|
|
474
|
+
"Ensure you `await` all `Promise`s that read or write to these services.",
|
|
475
|
+
"\x1B[2m"
|
|
476
|
+
);
|
|
477
|
+
lines.push("\x1B[22m" + separator, "");
|
|
478
|
+
console.error(lines.join("\n"));
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
async function handleStorageRequest(request, mf) {
|
|
484
|
+
const state = getState(mf);
|
|
485
|
+
if (state.broken) {
|
|
486
|
+
return new Response(
|
|
487
|
+
"Isolated storage failed. There should be additional logs above.",
|
|
488
|
+
{ status: 500 }
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
const source = request.headers.get("MF-Vitest-Source") ?? "an unknown location";
|
|
492
|
+
let success;
|
|
493
|
+
if (request.method === "POST") {
|
|
494
|
+
success = await state.mutex.runWith(async () => {
|
|
495
|
+
state.depth++;
|
|
496
|
+
const results = await Promise.allSettled(
|
|
497
|
+
state.persistPaths.map(
|
|
498
|
+
(persistPath) => pushStackedStorage(state.depth, persistPath)
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
return checkAllStorageOperationsResolved(
|
|
502
|
+
"push",
|
|
503
|
+
source,
|
|
504
|
+
state.persistPaths,
|
|
505
|
+
results
|
|
506
|
+
);
|
|
507
|
+
});
|
|
508
|
+
} else if (request.method === "DELETE") {
|
|
509
|
+
success = await state.mutex.runWith(async () => {
|
|
510
|
+
assert2(state.depth > 0, "Stack underflow");
|
|
511
|
+
const results = await Promise.allSettled(
|
|
512
|
+
state.persistPaths.map(
|
|
513
|
+
(persistPath) => popStackedStorage(state.depth, persistPath)
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
state.depth--;
|
|
517
|
+
return checkAllStorageOperationsResolved(
|
|
518
|
+
"pop",
|
|
519
|
+
source,
|
|
520
|
+
state.persistPaths,
|
|
521
|
+
results
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
} else {
|
|
525
|
+
return new Response(null, { status: 405 });
|
|
526
|
+
}
|
|
527
|
+
if (success) {
|
|
528
|
+
return new Response(null, { status: 204 });
|
|
529
|
+
} else {
|
|
530
|
+
state.broken = true;
|
|
531
|
+
return new Response(
|
|
532
|
+
"Isolated storage failed. There should be additional logs above.",
|
|
533
|
+
{ status: 500 }
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async function handleDurableObjectsRequest(request, mf, url) {
|
|
538
|
+
if (request.method !== "GET")
|
|
539
|
+
return new Response(null, { status: 405 });
|
|
540
|
+
const { durableObjectPersistPath } = getState(mf);
|
|
541
|
+
const uniqueKey = url.searchParams.get("unique_key");
|
|
542
|
+
if (uniqueKey === null)
|
|
543
|
+
return new Response(null, { status: 400 });
|
|
544
|
+
const namespacePath = path3.join(durableObjectPersistPath, uniqueKey);
|
|
545
|
+
const ids = [];
|
|
546
|
+
try {
|
|
547
|
+
const names = await fs.readdir(namespacePath);
|
|
548
|
+
for (const name of names) {
|
|
549
|
+
if (name.endsWith(".sqlite")) {
|
|
550
|
+
ids.push(name.substring(
|
|
551
|
+
0,
|
|
552
|
+
name.length - 7
|
|
553
|
+
/* ".sqlite".length */
|
|
554
|
+
));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} catch (e) {
|
|
558
|
+
if (!isFileNotFoundError(e))
|
|
559
|
+
throw e;
|
|
560
|
+
}
|
|
561
|
+
return Response.json(ids);
|
|
562
|
+
}
|
|
563
|
+
function handleLoopbackRequest(request, mf) {
|
|
564
|
+
const url = new URL(request.url);
|
|
565
|
+
if (url.pathname === "/snapshot")
|
|
566
|
+
return handleSnapshotRequest(request, url);
|
|
567
|
+
if (url.pathname === "/storage")
|
|
568
|
+
return handleStorageRequest(request, mf);
|
|
569
|
+
if (url.pathname === "/durable-objects") {
|
|
570
|
+
return handleDurableObjectsRequest(request, mf, url);
|
|
571
|
+
}
|
|
572
|
+
return new Response(null, { status: 404 });
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/pool/module-fallback.ts
|
|
576
|
+
import assert3 from "node:assert";
|
|
577
|
+
import fs2 from "node:fs";
|
|
578
|
+
import { createRequire } from "node:module";
|
|
579
|
+
import platformPath from "node:path";
|
|
580
|
+
import posixPath from "node:path/posix";
|
|
581
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
582
|
+
import util from "node:util";
|
|
583
|
+
import * as cjsModuleLexer from "cjs-module-lexer";
|
|
584
|
+
import { buildSync } from "esbuild";
|
|
585
|
+
import { ModuleRuleTypeSchema, Response as Response2 } from "miniflare";
|
|
586
|
+
var debuglog = util.debuglog(
|
|
587
|
+
"vitest-pool-workers:module-fallback",
|
|
588
|
+
(log2) => debuglog = log2
|
|
589
|
+
);
|
|
590
|
+
var isWindows = process.platform === "win32";
|
|
591
|
+
function ensurePosixLikePath(filePath) {
|
|
592
|
+
return isWindows ? filePath.replaceAll("\\", "/") : filePath;
|
|
593
|
+
}
|
|
594
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
595
|
+
var __dirname = platformPath.dirname(__filename);
|
|
596
|
+
var require2 = createRequire(__filename);
|
|
597
|
+
var distPath = ensurePosixLikePath(platformPath.resolve(__dirname, ".."));
|
|
598
|
+
var libPath = posixPath.join(distPath, "worker", "lib");
|
|
599
|
+
var emptyLibPath = posixPath.join(libPath, "cloudflare/empty-internal.cjs");
|
|
600
|
+
var disableCjsEsmShimSuffix = "?mf_vitest_no_cjs_esm_shim";
|
|
601
|
+
function trimSuffix(suffix, value) {
|
|
602
|
+
assert3(value.endsWith(suffix));
|
|
603
|
+
return value.substring(0, value.length - suffix.length);
|
|
604
|
+
}
|
|
605
|
+
var forceModuleTypeRegexp = new RegExp(
|
|
606
|
+
`\\?mf_vitest_force=(${ModuleRuleTypeSchema.options.join("|")})$`
|
|
607
|
+
);
|
|
608
|
+
var workerdBuiltinModules = /* @__PURE__ */ new Set([
|
|
609
|
+
...define_VITEST_POOL_WORKERS_DEFINE_BUILTIN_MODULES_default,
|
|
610
|
+
"__STATIC_CONTENT_MANIFEST"
|
|
611
|
+
]);
|
|
612
|
+
var bundleDependencies = ["chai"];
|
|
613
|
+
function isFile(filePath) {
|
|
614
|
+
try {
|
|
615
|
+
return fs2.statSync(filePath).isFile();
|
|
616
|
+
} catch (e) {
|
|
617
|
+
if (isFileNotFoundError(e))
|
|
618
|
+
return false;
|
|
619
|
+
throw e;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function getParentPaths(filePath) {
|
|
623
|
+
const parentPaths = [];
|
|
624
|
+
while (true) {
|
|
625
|
+
const parentPath = posixPath.dirname(filePath);
|
|
626
|
+
if (parentPath === filePath)
|
|
627
|
+
return parentPaths;
|
|
628
|
+
parentPaths.push(parentPath);
|
|
629
|
+
filePath = parentPath;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
var dirPathTypeModuleCache = /* @__PURE__ */ new Map();
|
|
633
|
+
function isWithinTypeModuleContext(filePath) {
|
|
634
|
+
const parentPaths = getParentPaths(filePath);
|
|
635
|
+
for (const parentPath of parentPaths) {
|
|
636
|
+
const cache = dirPathTypeModuleCache.get(parentPath);
|
|
637
|
+
if (cache !== void 0)
|
|
638
|
+
return cache;
|
|
639
|
+
}
|
|
640
|
+
for (const parentPath of parentPaths) {
|
|
641
|
+
try {
|
|
642
|
+
const pkgPath = posixPath.join(parentPath, "package.json");
|
|
643
|
+
const pkgJson = fs2.readFileSync(pkgPath, "utf8");
|
|
644
|
+
const pkg = JSON.parse(pkgJson);
|
|
645
|
+
const maybeModulePath = pkg.module ? posixPath.join(parentPath, pkg.module) : "";
|
|
646
|
+
const cache = pkg.type === "module" || maybeModulePath === filePath;
|
|
647
|
+
dirPathTypeModuleCache.set(parentPath, cache);
|
|
648
|
+
return cache;
|
|
649
|
+
} catch (e) {
|
|
650
|
+
if (!isFileNotFoundError(e))
|
|
651
|
+
throw e;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
await cjsModuleLexer.init();
|
|
657
|
+
async function getCjsNamedExports(vite, filePath, contents, seen = /* @__PURE__ */ new Set()) {
|
|
658
|
+
const { exports, reexports } = cjsModuleLexer.parse(contents);
|
|
659
|
+
const result = new Set(exports);
|
|
660
|
+
for (const reexport of reexports) {
|
|
661
|
+
const resolved = await viteResolve(
|
|
662
|
+
vite,
|
|
663
|
+
reexport,
|
|
664
|
+
filePath,
|
|
665
|
+
/* isRequire */
|
|
666
|
+
true
|
|
667
|
+
);
|
|
668
|
+
if (seen.has(resolved))
|
|
669
|
+
continue;
|
|
670
|
+
try {
|
|
671
|
+
const resolvedContents = fs2.readFileSync(resolved, "utf8");
|
|
672
|
+
seen.add(filePath);
|
|
673
|
+
const resolvedNames = await getCjsNamedExports(
|
|
674
|
+
vite,
|
|
675
|
+
resolved,
|
|
676
|
+
resolvedContents,
|
|
677
|
+
seen
|
|
678
|
+
);
|
|
679
|
+
seen.delete(filePath);
|
|
680
|
+
for (const name of resolvedNames)
|
|
681
|
+
result.add(name);
|
|
682
|
+
} catch (e) {
|
|
683
|
+
if (!isFileNotFoundError(e))
|
|
684
|
+
throw e;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
result.delete("default");
|
|
688
|
+
result.delete("__esModule");
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
function withSourceUrl(contents, url) {
|
|
692
|
+
if (contents.lastIndexOf("//# sourceURL=") !== -1)
|
|
693
|
+
return contents;
|
|
694
|
+
const sourceURL = `
|
|
695
|
+
//# sourceURL=${url.toString()}
|
|
696
|
+
`;
|
|
697
|
+
return contents + sourceURL;
|
|
698
|
+
}
|
|
699
|
+
function withImportMetaUrl(contents, url) {
|
|
700
|
+
return contents.replaceAll("import.meta.url", JSON.stringify(url.toString()));
|
|
701
|
+
}
|
|
702
|
+
var bundleCache = /* @__PURE__ */ new Map();
|
|
703
|
+
function bundleDependency(entryPath) {
|
|
704
|
+
let output = bundleCache.get(entryPath);
|
|
705
|
+
if (output !== void 0)
|
|
706
|
+
return output;
|
|
707
|
+
debuglog(`Bundling ${entryPath}...`);
|
|
708
|
+
const result = buildSync({
|
|
709
|
+
platform: "node",
|
|
710
|
+
target: "esnext",
|
|
711
|
+
format: "cjs",
|
|
712
|
+
bundle: true,
|
|
713
|
+
packages: "external",
|
|
714
|
+
sourcemap: "inline",
|
|
715
|
+
sourcesContent: false,
|
|
716
|
+
entryPoints: [entryPath],
|
|
717
|
+
write: false
|
|
718
|
+
});
|
|
719
|
+
assert3(result.outputFiles.length === 1);
|
|
720
|
+
output = result.outputFiles[0].text;
|
|
721
|
+
bundleCache.set(entryPath, output);
|
|
722
|
+
return output;
|
|
723
|
+
}
|
|
724
|
+
var jsExtensions = [".js", ".mjs", ".cjs"];
|
|
725
|
+
function maybeGetTargetFilePath(target) {
|
|
726
|
+
if (isFile(target))
|
|
727
|
+
return target;
|
|
728
|
+
for (const extension of jsExtensions) {
|
|
729
|
+
const targetWithExtension = target + extension;
|
|
730
|
+
if (fs2.existsSync(targetWithExtension))
|
|
731
|
+
return targetWithExtension;
|
|
732
|
+
}
|
|
733
|
+
if (target.endsWith(disableCjsEsmShimSuffix))
|
|
734
|
+
return target;
|
|
735
|
+
}
|
|
736
|
+
function getApproximateSpecifier(target, referrerDir) {
|
|
737
|
+
if (/^(node|cloudflare|workerd):/.test(target))
|
|
738
|
+
return target;
|
|
739
|
+
return posixPath.relative(referrerDir, target);
|
|
740
|
+
}
|
|
741
|
+
async function viteResolve(vite, specifier, referrer, isRequire) {
|
|
742
|
+
const resolved = await vite.pluginContainer.resolveId(specifier, referrer, {
|
|
743
|
+
ssr: true,
|
|
744
|
+
// https://github.com/vitejs/vite/blob/v5.1.4/packages/vite/src/node/plugins/resolve.ts#L178-L179
|
|
745
|
+
custom: { "node-resolve": { isRequire } }
|
|
746
|
+
});
|
|
747
|
+
if (resolved === null) {
|
|
748
|
+
if (isRequire && specifier[0] === ".") {
|
|
749
|
+
return require2.resolve(specifier, { paths: [referrer] });
|
|
750
|
+
}
|
|
751
|
+
throw new Error("Not found");
|
|
752
|
+
}
|
|
753
|
+
if (resolved.id === "__vite-browser-external")
|
|
754
|
+
return emptyLibPath;
|
|
755
|
+
if (resolved.external) {
|
|
756
|
+
let { id } = resolved;
|
|
757
|
+
if (workerdBuiltinModules.has(id))
|
|
758
|
+
return `/${id}`;
|
|
759
|
+
id = `node:${id}`;
|
|
760
|
+
if (workerdBuiltinModules.has(id))
|
|
761
|
+
return `/${id}`;
|
|
762
|
+
throw new Error("Not found");
|
|
763
|
+
}
|
|
764
|
+
return resolved.id;
|
|
765
|
+
}
|
|
766
|
+
async function resolve(vite, method, target, specifier, referrer) {
|
|
767
|
+
const referrerDir = posixPath.dirname(referrer);
|
|
768
|
+
let filePath = maybeGetTargetFilePath(target);
|
|
769
|
+
if (filePath !== void 0)
|
|
770
|
+
return filePath;
|
|
771
|
+
if (referrerDir !== "/" && workerdBuiltinModules.has(specifier)) {
|
|
772
|
+
return `/${specifier}`;
|
|
773
|
+
}
|
|
774
|
+
const specifierLibPath = posixPath.join(
|
|
775
|
+
libPath,
|
|
776
|
+
specifier.replaceAll(":", "/")
|
|
777
|
+
);
|
|
778
|
+
filePath = maybeGetTargetFilePath(specifierLibPath);
|
|
779
|
+
if (filePath !== void 0)
|
|
780
|
+
return filePath;
|
|
781
|
+
return viteResolve(vite, specifier, referrer, method === "require");
|
|
782
|
+
}
|
|
783
|
+
function buildRedirectResponse(filePath) {
|
|
784
|
+
if (isWindows && filePath[0] !== "/")
|
|
785
|
+
filePath = `/${filePath}`;
|
|
786
|
+
return new Response2(null, { status: 301, headers: { Location: filePath } });
|
|
787
|
+
}
|
|
788
|
+
function maybeGetForceTypeModuleContents(filePath) {
|
|
789
|
+
const match = forceModuleTypeRegexp.exec(filePath);
|
|
790
|
+
if (match === null)
|
|
791
|
+
return;
|
|
792
|
+
filePath = trimSuffix(match[0], filePath);
|
|
793
|
+
const type = match[1];
|
|
794
|
+
const contents = fs2.readFileSync(filePath);
|
|
795
|
+
switch (type) {
|
|
796
|
+
case "ESModule":
|
|
797
|
+
return { esModule: contents.toString() };
|
|
798
|
+
case "CommonJS":
|
|
799
|
+
return { commonJsModule: contents.toString() };
|
|
800
|
+
case "NodeJsCompatModule":
|
|
801
|
+
return { nodeJsCompatModule: contents.toString() };
|
|
802
|
+
case "Text":
|
|
803
|
+
return { text: contents.toString() };
|
|
804
|
+
case "Data":
|
|
805
|
+
return { data: contents };
|
|
806
|
+
case "CompiledWasm":
|
|
807
|
+
return { wasm: contents };
|
|
808
|
+
case "PythonModule":
|
|
809
|
+
return { pythonModule: contents.toString() };
|
|
810
|
+
case "PythonRequirement":
|
|
811
|
+
return { pythonRequirement: contents.toString() };
|
|
812
|
+
default: {
|
|
813
|
+
const exhaustive = type;
|
|
814
|
+
assert3.fail(`Unreachable: ${exhaustive} modules are unsupported`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
function buildModuleResponse(target, contents) {
|
|
819
|
+
let name = target;
|
|
820
|
+
if (!isWindows)
|
|
821
|
+
name = posixPath.relative("/", target);
|
|
822
|
+
assert3(name[0] !== "/");
|
|
823
|
+
const result = { name };
|
|
824
|
+
for (const key in contents) {
|
|
825
|
+
const value = contents[key];
|
|
826
|
+
result[key] = value instanceof Uint8Array ? Array.from(value) : value;
|
|
827
|
+
}
|
|
828
|
+
return Response2.json(result);
|
|
829
|
+
}
|
|
830
|
+
async function load(vite, logBase, method, target, specifier, filePath) {
|
|
831
|
+
if (target !== filePath) {
|
|
832
|
+
if (method === "require" && !specifier.startsWith("node:")) {
|
|
833
|
+
filePath += disableCjsEsmShimSuffix;
|
|
834
|
+
}
|
|
835
|
+
debuglog(logBase, "redirect:", filePath);
|
|
836
|
+
return buildRedirectResponse(filePath);
|
|
837
|
+
}
|
|
838
|
+
if (filePath.endsWith(".wasm"))
|
|
839
|
+
filePath += `?mf_vitest_force=CompiledWasm`;
|
|
840
|
+
const maybeContents = maybeGetForceTypeModuleContents(filePath);
|
|
841
|
+
if (maybeContents !== void 0) {
|
|
842
|
+
debuglog(logBase, "forced:", filePath);
|
|
843
|
+
return buildModuleResponse(target, maybeContents);
|
|
844
|
+
}
|
|
845
|
+
const disableCjsEsmShim = filePath.endsWith(disableCjsEsmShimSuffix);
|
|
846
|
+
if (disableCjsEsmShim) {
|
|
847
|
+
filePath = trimSuffix(disableCjsEsmShimSuffix, filePath);
|
|
848
|
+
}
|
|
849
|
+
let isEsm = filePath.endsWith(".mjs") || filePath.endsWith(".js") && isWithinTypeModuleContext(filePath);
|
|
850
|
+
let contents;
|
|
851
|
+
const maybeBundled = bundleCache.get(filePath);
|
|
852
|
+
if (maybeBundled !== void 0) {
|
|
853
|
+
contents = maybeBundled;
|
|
854
|
+
isEsm = false;
|
|
855
|
+
} else {
|
|
856
|
+
contents = fs2.readFileSync(filePath, "utf8");
|
|
857
|
+
}
|
|
858
|
+
const targetUrl = pathToFileURL(target);
|
|
859
|
+
contents = withSourceUrl(contents, targetUrl);
|
|
860
|
+
if (isEsm) {
|
|
861
|
+
contents = withImportMetaUrl(contents, targetUrl);
|
|
862
|
+
debuglog(logBase, "esm:", filePath);
|
|
863
|
+
return buildModuleResponse(target, { esModule: contents });
|
|
864
|
+
}
|
|
865
|
+
const insertCjsEsmShim = method === "import" || specifier.startsWith("node:");
|
|
866
|
+
if (insertCjsEsmShim && !disableCjsEsmShim) {
|
|
867
|
+
const fileName = posixPath.basename(filePath);
|
|
868
|
+
const disableShimSpecifier = `./${fileName}${disableCjsEsmShimSuffix}`;
|
|
869
|
+
const quotedDisableShimSpecifier = JSON.stringify(disableShimSpecifier);
|
|
870
|
+
let esModule = `import mod from ${quotedDisableShimSpecifier}; export default mod;`;
|
|
871
|
+
for (const name of await getCjsNamedExports(vite, filePath, contents)) {
|
|
872
|
+
esModule += ` export const ${name} = mod.${name};`;
|
|
873
|
+
}
|
|
874
|
+
debuglog(logBase, "cjs-esm-shim:", filePath);
|
|
875
|
+
return buildModuleResponse(target, { esModule });
|
|
876
|
+
}
|
|
877
|
+
debuglog(logBase, "cjs:", filePath);
|
|
878
|
+
return buildModuleResponse(target, { nodeJsCompatModule: contents });
|
|
879
|
+
}
|
|
880
|
+
async function handleModuleFallbackRequest(vite, request) {
|
|
881
|
+
const method = request.headers.get("X-Resolve-Method");
|
|
882
|
+
assert3(method === "import" || method === "require");
|
|
883
|
+
const url = new URL(request.url);
|
|
884
|
+
let target = url.searchParams.get("specifier");
|
|
885
|
+
let referrer = url.searchParams.get("referrer");
|
|
886
|
+
assert3(target !== null, "Expected specifier search param");
|
|
887
|
+
assert3(referrer !== null, "Expected referrer search param");
|
|
888
|
+
const referrerDir = posixPath.dirname(referrer);
|
|
889
|
+
let specifier = getApproximateSpecifier(target, referrerDir);
|
|
890
|
+
if (specifier.startsWith("file:"))
|
|
891
|
+
specifier = specifier.substring(5);
|
|
892
|
+
if (isWindows) {
|
|
893
|
+
if (target[0] === "/")
|
|
894
|
+
target = target.substring(1);
|
|
895
|
+
if (referrer[0] === "/")
|
|
896
|
+
referrer = referrer.substring(1);
|
|
897
|
+
}
|
|
898
|
+
const quotedTarget = JSON.stringify(target);
|
|
899
|
+
const logBase = `${method}(${quotedTarget}) relative to ${referrer}:`;
|
|
900
|
+
try {
|
|
901
|
+
const filePath = await resolve(vite, method, target, specifier, referrer);
|
|
902
|
+
if (bundleDependencies.includes(specifier))
|
|
903
|
+
bundleDependency(filePath);
|
|
904
|
+
return await load(vite, logBase, method, target, specifier, filePath);
|
|
905
|
+
} catch (e) {
|
|
906
|
+
debuglog(logBase, "error:", e);
|
|
907
|
+
}
|
|
908
|
+
return new Response2(null, { status: 404 });
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/pool/index.ts
|
|
912
|
+
assert4(
|
|
913
|
+
typeof __vite_ssr_import__ === "undefined",
|
|
914
|
+
"Expected `@cloudflare/vitest-pool-workers` not to be transformed by Vite"
|
|
915
|
+
);
|
|
916
|
+
function structuredSerializableStringify(value) {
|
|
917
|
+
return devalue.stringify(value, structuredSerializableReducers);
|
|
918
|
+
}
|
|
919
|
+
function structuredSerializableParse(value) {
|
|
920
|
+
return devalue.parse(value, structuredSerializableRevivers);
|
|
921
|
+
}
|
|
922
|
+
var debuglog2 = util2.debuglog(
|
|
923
|
+
"vitest-pool-workers:index",
|
|
924
|
+
(fn) => debuglog2 = fn
|
|
925
|
+
);
|
|
926
|
+
var log = new Log(LogLevel.VERBOSE, { prefix: "vpw" });
|
|
927
|
+
var mfLog = new Log(LogLevel.WARN);
|
|
928
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
929
|
+
var __dirname2 = path4.dirname(__filename2);
|
|
930
|
+
var DIST_PATH = path4.resolve(__dirname2, "..");
|
|
931
|
+
var POOL_WORKER_PATH = path4.join(DIST_PATH, "worker", "index.mjs");
|
|
932
|
+
var symbolizerWarning = "warning: Not symbolizing stack traces because $LLVM_SYMBOLIZER is not set.";
|
|
933
|
+
var ignoreMessages = [
|
|
934
|
+
// Not user actionable
|
|
935
|
+
// TODO(someday): this is normal operation and really shouldn't error
|
|
936
|
+
"disconnected: operation canceled",
|
|
937
|
+
"disconnected: worker_do_not_log; Request failed due to internal error",
|
|
938
|
+
"disconnected: WebSocket was aborted"
|
|
939
|
+
];
|
|
940
|
+
function trimSymbolizerWarning(chunk) {
|
|
941
|
+
return chunk.includes(symbolizerWarning) ? chunk.substring(chunk.indexOf("\n") + 1) : chunk;
|
|
942
|
+
}
|
|
943
|
+
function handleRuntimeStdio(stdout, stderr) {
|
|
944
|
+
stdout.on("data", (chunk) => {
|
|
945
|
+
process.stdout.write(chunk);
|
|
946
|
+
});
|
|
947
|
+
stderr.on("data", (chunk) => {
|
|
948
|
+
const str = trimSymbolizerWarning(chunk.toString());
|
|
949
|
+
if (ignoreMessages.some((message) => str.includes(message))) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
process.stderr.write(str);
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
function forEachMiniflare(mfs, callback) {
|
|
956
|
+
if (mfs instanceof Miniflare)
|
|
957
|
+
return callback(mfs);
|
|
958
|
+
const promises = [];
|
|
959
|
+
for (const mf of mfs.values())
|
|
960
|
+
promises.push(callback(mf));
|
|
961
|
+
return Promise.all(promises);
|
|
962
|
+
}
|
|
963
|
+
var allProjects = /* @__PURE__ */ new Map();
|
|
964
|
+
function getRunnerName(project, testFile) {
|
|
965
|
+
const name = `${WORKER_NAME_PREFIX}runner-${project.getName()}`;
|
|
966
|
+
if (testFile === void 0)
|
|
967
|
+
return name;
|
|
968
|
+
const testFileHash = crypto.createHash("sha1").update(testFile).digest("hex");
|
|
969
|
+
testFile = testFile.replace(/[^a-z0-9-]/gi, "_");
|
|
970
|
+
return `${name}-${testFileHash}-${testFile}`;
|
|
971
|
+
}
|
|
972
|
+
function isDurableObjectDesignatorToSelf(value) {
|
|
973
|
+
if (typeof value === "string")
|
|
974
|
+
return true;
|
|
975
|
+
return typeof value === "object" && value !== null && "className" in value && typeof value.className === "string" && (!("scriptName" in value) || value.scriptName === void 0);
|
|
976
|
+
}
|
|
977
|
+
function getDurableObjectDesignators(options) {
|
|
978
|
+
const result = /* @__PURE__ */ new Map();
|
|
979
|
+
const durableObjects = options.miniflare?.durableObjects ?? {};
|
|
980
|
+
for (const [key, designator] of Object.entries(durableObjects)) {
|
|
981
|
+
if (typeof designator === "string") {
|
|
982
|
+
result.set(key, { className: USER_OBJECT_MODULE_NAME + designator });
|
|
983
|
+
} else if (typeof designator.unsafeUniqueKey !== "symbol") {
|
|
984
|
+
let className = designator.className;
|
|
985
|
+
if (designator.scriptName === void 0) {
|
|
986
|
+
className = USER_OBJECT_MODULE_NAME + className;
|
|
987
|
+
}
|
|
988
|
+
result.set(key, {
|
|
989
|
+
className,
|
|
990
|
+
scriptName: designator.scriptName,
|
|
991
|
+
unsafeUniqueKey: designator.unsafeUniqueKey
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return result;
|
|
996
|
+
}
|
|
997
|
+
var POOL_WORKER_DIR = path4.dirname(POOL_WORKER_PATH);
|
|
998
|
+
var USER_OBJECT_MODULE_NAME = "__VITEST_POOL_WORKERS_USER_OBJECT";
|
|
999
|
+
var USER_OBJECT_MODULE_PATH = path4.join(
|
|
1000
|
+
POOL_WORKER_DIR,
|
|
1001
|
+
USER_OBJECT_MODULE_NAME
|
|
1002
|
+
);
|
|
1003
|
+
var DEFINES_MODULE_PATH = path4.join(
|
|
1004
|
+
POOL_WORKER_DIR,
|
|
1005
|
+
"__VITEST_POOL_WORKERS_DEFINES"
|
|
1006
|
+
);
|
|
1007
|
+
function fixupDurableObjectBindingsToSelf(worker) {
|
|
1008
|
+
const result = /* @__PURE__ */ new Set();
|
|
1009
|
+
if (worker.durableObjects === void 0)
|
|
1010
|
+
return result;
|
|
1011
|
+
for (const key of Object.keys(worker.durableObjects)) {
|
|
1012
|
+
const designator = worker.durableObjects[key];
|
|
1013
|
+
if (typeof designator === "string") {
|
|
1014
|
+
result.add(designator);
|
|
1015
|
+
worker.durableObjects[key] = USER_OBJECT_MODULE_NAME + designator;
|
|
1016
|
+
} else if (isDurableObjectDesignatorToSelf(designator)) {
|
|
1017
|
+
result.add(designator.className);
|
|
1018
|
+
worker.durableObjects[key] = {
|
|
1019
|
+
...designator,
|
|
1020
|
+
className: USER_OBJECT_MODULE_NAME + designator.className
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return result;
|
|
1025
|
+
}
|
|
1026
|
+
var SELF_NAME_BINDING = "__VITEST_POOL_WORKERS_SELF_NAME";
|
|
1027
|
+
var SELF_SERVICE_BINDING = "__VITEST_POOL_WORKERS_SELF_SERVICE";
|
|
1028
|
+
var LOOPBACK_SERVICE_BINDING = "__VITEST_POOL_WORKERS_LOOPBACK_SERVICE";
|
|
1029
|
+
var RUNNER_OBJECT_BINDING = "__VITEST_POOL_WORKERS_RUNNER_OBJECT";
|
|
1030
|
+
var numericCompare = new Intl.Collator("en", { numeric: true }).compare;
|
|
1031
|
+
function assertCompatibilityFlagEnabled(opts) {
|
|
1032
|
+
const hasWranglerConfig = opts.relativeWranglerConfigPath !== void 0;
|
|
1033
|
+
if (opts.disableFlag !== void 0 && opts.compatibilityFlags.includes(opts.disableFlag)) {
|
|
1034
|
+
let message = `In project ${opts.relativeProjectPath}`;
|
|
1035
|
+
if (hasWranglerConfig) {
|
|
1036
|
+
message += `'s configuration file ${opts.relativeWranglerConfigPath}, \`compatibility_flags\` must not contain "${opts.disableFlag}".
|
|
1037
|
+
Similarly`;
|
|
1038
|
+
}
|
|
1039
|
+
message += `, \`${OPTIONS_PATH}.miniflare.compatibilityFlags\` must not contain "${opts.disableFlag}".
|
|
1040
|
+
This flag is incompatible with \`@cloudflare/vitest-pool-workers\`.`;
|
|
1041
|
+
throw new Error(message);
|
|
1042
|
+
}
|
|
1043
|
+
const enabledByFlag = opts.compatibilityFlags.includes(opts.enableFlag);
|
|
1044
|
+
const enabledByDate = opts.compatibilityDate !== void 0 && opts.defaultOnDate !== void 0 && numericCompare(opts.compatibilityDate, opts.defaultOnDate) >= 0;
|
|
1045
|
+
if (!(enabledByFlag || enabledByDate)) {
|
|
1046
|
+
let message = `In project ${opts.relativeProjectPath}`;
|
|
1047
|
+
if (hasWranglerConfig) {
|
|
1048
|
+
message += `'s configuration file ${opts.relativeWranglerConfigPath}, \`compatibility_flags\` must contain "${opts.enableFlag}"`;
|
|
1049
|
+
} else {
|
|
1050
|
+
message += `, \`${OPTIONS_PATH}.miniflare.compatibilityFlags\` must contain "${opts.enableFlag}"`;
|
|
1051
|
+
}
|
|
1052
|
+
if (opts.defaultOnDate !== void 0) {
|
|
1053
|
+
if (hasWranglerConfig) {
|
|
1054
|
+
message += `, or \`compatibility_date\` must be >= "${opts.defaultOnDate}"`;
|
|
1055
|
+
} else {
|
|
1056
|
+
message += `, or \`${OPTIONS_PATH}.miniflare.compatibilityDate\` must be >= "${opts.defaultOnDate}"`;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
message += ".\nThis flag is required to use `@cloudflare/vitest-pool-workers`.";
|
|
1060
|
+
throw new Error(message);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function buildProjectWorkerOptions(project) {
|
|
1064
|
+
const relativeWranglerConfigPath = maybeApply(
|
|
1065
|
+
(v) => path4.relative("", v),
|
|
1066
|
+
project.options.wrangler?.configPath
|
|
1067
|
+
);
|
|
1068
|
+
const runnerWorker = project.options.miniflare ?? {};
|
|
1069
|
+
runnerWorker.name = getRunnerName(project.project);
|
|
1070
|
+
runnerWorker.bindings ??= {};
|
|
1071
|
+
runnerWorker.bindings[SELF_NAME_BINDING] = runnerWorker.name;
|
|
1072
|
+
runnerWorker.compatibilityFlags ??= [];
|
|
1073
|
+
assertCompatibilityFlagEnabled({
|
|
1074
|
+
compatibilityFlags: runnerWorker.compatibilityFlags,
|
|
1075
|
+
compatibilityDate: runnerWorker.compatibilityDate,
|
|
1076
|
+
relativeProjectPath: project.relativePath,
|
|
1077
|
+
relativeWranglerConfigPath,
|
|
1078
|
+
// https://developers.cloudflare.com/workers/configuration/compatibility-dates/#commonjs-modules-do-not-export-a-module-namespace
|
|
1079
|
+
enableFlag: "export_commonjs_default",
|
|
1080
|
+
disableFlag: "export_commonjs_namespace",
|
|
1081
|
+
defaultOnDate: "2022-10-31"
|
|
1082
|
+
});
|
|
1083
|
+
assertCompatibilityFlagEnabled({
|
|
1084
|
+
compatibilityFlags: runnerWorker.compatibilityFlags,
|
|
1085
|
+
compatibilityDate: runnerWorker.compatibilityDate,
|
|
1086
|
+
relativeProjectPath: project.relativePath,
|
|
1087
|
+
relativeWranglerConfigPath,
|
|
1088
|
+
enableFlag: "nodejs_compat"
|
|
1089
|
+
});
|
|
1090
|
+
if (!runnerWorker.compatibilityFlags.includes("unsafe_module")) {
|
|
1091
|
+
runnerWorker.compatibilityFlags.push("unsafe_module");
|
|
1092
|
+
}
|
|
1093
|
+
runnerWorker.unsafeEvalBinding = "__VITEST_POOL_WORKERS_UNSAFE_EVAL";
|
|
1094
|
+
runnerWorker.unsafeUseModuleFallbackService = true;
|
|
1095
|
+
runnerWorker.serviceBindings ??= {};
|
|
1096
|
+
runnerWorker.serviceBindings[SELF_SERVICE_BINDING] = kCurrentWorker;
|
|
1097
|
+
runnerWorker.serviceBindings[LOOPBACK_SERVICE_BINDING] = handleLoopbackRequest;
|
|
1098
|
+
runnerWorker.durableObjects ??= {};
|
|
1099
|
+
const durableObjectClassNames = fixupDurableObjectBindingsToSelf(runnerWorker);
|
|
1100
|
+
const durableObjectWrappers = Array.from(durableObjectClassNames).sort().map((className) => {
|
|
1101
|
+
const quotedClassName = JSON.stringify(className);
|
|
1102
|
+
return `export const ${USER_OBJECT_MODULE_NAME}${className} = createDurableObjectWrapper(${quotedClassName});`;
|
|
1103
|
+
});
|
|
1104
|
+
durableObjectWrappers.unshift(
|
|
1105
|
+
'import { createDurableObjectWrapper } from "cloudflare:test-internal";'
|
|
1106
|
+
);
|
|
1107
|
+
runnerWorker.durableObjects[RUNNER_OBJECT_BINDING] = {
|
|
1108
|
+
className: "RunnerObject",
|
|
1109
|
+
// Make the runner object ephemeral, so it doesn't write any `.sqlite` files
|
|
1110
|
+
// that would disrupt stacked storage because we prevent eviction
|
|
1111
|
+
unsafeUniqueKey: kUnsafeEphemeralUniqueKey,
|
|
1112
|
+
unsafePreventEviction: true
|
|
1113
|
+
};
|
|
1114
|
+
const defines = `export default {
|
|
1115
|
+
${Object.entries(project.options.defines ?? {}).map(([key, value]) => `${JSON.stringify(key)}: ${value}`).join(",\n")}
|
|
1116
|
+
};
|
|
1117
|
+
`;
|
|
1118
|
+
if ("script" in runnerWorker)
|
|
1119
|
+
delete runnerWorker.script;
|
|
1120
|
+
if ("scriptPath" in runnerWorker)
|
|
1121
|
+
delete runnerWorker.scriptPath;
|
|
1122
|
+
const modulesRoot = process.platform === "win32" ? "Z:\\" : "/";
|
|
1123
|
+
runnerWorker.modulesRoot = modulesRoot;
|
|
1124
|
+
runnerWorker.modules = [
|
|
1125
|
+
{
|
|
1126
|
+
type: "ESModule",
|
|
1127
|
+
path: path4.join(modulesRoot, POOL_WORKER_PATH),
|
|
1128
|
+
contents: fs3.readFileSync(POOL_WORKER_PATH)
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
type: "ESModule",
|
|
1132
|
+
path: path4.join(modulesRoot, USER_OBJECT_MODULE_PATH),
|
|
1133
|
+
contents: durableObjectWrappers.join("\n")
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
type: "ESModule",
|
|
1137
|
+
path: path4.join(modulesRoot, DEFINES_MODULE_PATH),
|
|
1138
|
+
contents: defines
|
|
1139
|
+
}
|
|
1140
|
+
];
|
|
1141
|
+
const workers = [runnerWorker];
|
|
1142
|
+
if (runnerWorker.workers !== void 0) {
|
|
1143
|
+
for (let i = 0; i < runnerWorker.workers.length; i++) {
|
|
1144
|
+
const worker = runnerWorker.workers[i];
|
|
1145
|
+
if (typeof worker !== "object" || worker === null || !("name" in worker) || typeof worker.name !== "string" || worker.name === "") {
|
|
1146
|
+
throw new Error(
|
|
1147
|
+
`In project ${project.relativePath}, \`${OPTIONS_PATH}.miniflare.workers[${i}].name\` must be non-empty`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
if (worker.name.startsWith(WORKER_NAME_PREFIX)) {
|
|
1151
|
+
throw new Error(
|
|
1152
|
+
`In project ${project.relativePath}, \`${OPTIONS_PATH}.miniflare.workers[${i}].name\` must not start with "${WORKER_NAME_PREFIX}", got ${worker.name}`
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
workers.push(worker);
|
|
1156
|
+
}
|
|
1157
|
+
delete runnerWorker.workers;
|
|
1158
|
+
}
|
|
1159
|
+
return workers;
|
|
1160
|
+
}
|
|
1161
|
+
var SHARED_MINIFLARE_OPTIONS = {
|
|
1162
|
+
log: mfLog,
|
|
1163
|
+
verbose: true,
|
|
1164
|
+
handleRuntimeStdio,
|
|
1165
|
+
unsafeStickyBlobs: true
|
|
1166
|
+
};
|
|
1167
|
+
var moduleFallbackServices = /* @__PURE__ */ new WeakMap();
|
|
1168
|
+
function getModuleFallbackService(ctx) {
|
|
1169
|
+
let service = moduleFallbackServices.get(ctx);
|
|
1170
|
+
if (service !== void 0)
|
|
1171
|
+
return service;
|
|
1172
|
+
service = handleModuleFallbackRequest.bind(void 0, ctx.vitenode.server);
|
|
1173
|
+
moduleFallbackServices.set(ctx, service);
|
|
1174
|
+
return service;
|
|
1175
|
+
}
|
|
1176
|
+
function buildProjectMiniflareOptions(ctx, project) {
|
|
1177
|
+
const moduleFallbackService = getModuleFallbackService(ctx);
|
|
1178
|
+
const [runnerWorker, ...auxiliaryWorkers] = buildProjectWorkerOptions(project);
|
|
1179
|
+
assert4(runnerWorker.name !== void 0);
|
|
1180
|
+
assert4(runnerWorker.name.startsWith(WORKER_NAME_PREFIX));
|
|
1181
|
+
if (project.options.singleWorker || project.options.isolatedStorage) {
|
|
1182
|
+
return {
|
|
1183
|
+
...SHARED_MINIFLARE_OPTIONS,
|
|
1184
|
+
unsafeModuleFallbackService: moduleFallbackService,
|
|
1185
|
+
workers: [runnerWorker, ABORT_ALL_WORKER, ...auxiliaryWorkers]
|
|
1186
|
+
};
|
|
1187
|
+
} else {
|
|
1188
|
+
const testWorkers = [];
|
|
1189
|
+
for (const testFile of project.testFiles) {
|
|
1190
|
+
const testWorker = { ...runnerWorker };
|
|
1191
|
+
testWorker.name = getRunnerName(project.project, testFile);
|
|
1192
|
+
assert4(testWorker.bindings !== void 0);
|
|
1193
|
+
testWorker.bindings = { ...testWorker.bindings };
|
|
1194
|
+
testWorker.bindings[SELF_NAME_BINDING] = testWorker.name;
|
|
1195
|
+
testWorkers.push(testWorker);
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
...SHARED_MINIFLARE_OPTIONS,
|
|
1199
|
+
unsafeModuleFallbackService: moduleFallbackService,
|
|
1200
|
+
workers: [...testWorkers, ABORT_ALL_WORKER, ...auxiliaryWorkers]
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
async function getProjectMiniflare(ctx, project) {
|
|
1205
|
+
const mfOptions = buildProjectMiniflareOptions(ctx, project);
|
|
1206
|
+
const changed = !util2.isDeepStrictEqual(project.previousMfOptions, mfOptions);
|
|
1207
|
+
project.previousMfOptions = mfOptions;
|
|
1208
|
+
const previousSingleInstance = project.mf instanceof Miniflare;
|
|
1209
|
+
const singleInstance = project.options.singleWorker || !project.options.isolatedStorage;
|
|
1210
|
+
if (project.mf !== void 0 && previousSingleInstance !== singleInstance) {
|
|
1211
|
+
log.info(`Isolation changed for ${project.relativePath}, resetting...`);
|
|
1212
|
+
await forEachMiniflare(project.mf, (mf) => mf.dispose());
|
|
1213
|
+
project.mf = void 0;
|
|
1214
|
+
}
|
|
1215
|
+
if (project.mf === void 0) {
|
|
1216
|
+
if (singleInstance) {
|
|
1217
|
+
log.info(`Starting single runtime for ${project.relativePath}...`);
|
|
1218
|
+
project.mf = new Miniflare(mfOptions);
|
|
1219
|
+
} else {
|
|
1220
|
+
log.info(`Starting isolated runtimes for ${project.relativePath}...`);
|
|
1221
|
+
project.mf = /* @__PURE__ */ new Map();
|
|
1222
|
+
for (const testFile of project.testFiles) {
|
|
1223
|
+
project.mf.set(testFile, new Miniflare(mfOptions));
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
await forEachMiniflare(project.mf, (mf) => mf.ready);
|
|
1227
|
+
} else if (changed) {
|
|
1228
|
+
log.info(`Options changed for ${project.relativePath}, updating...`);
|
|
1229
|
+
await forEachMiniflare(project.mf, (mf) => mf.setOptions(mfOptions));
|
|
1230
|
+
} else {
|
|
1231
|
+
log.debug(`Reusing runtime for ${project.relativePath}...`);
|
|
1232
|
+
}
|
|
1233
|
+
return project.mf;
|
|
1234
|
+
}
|
|
1235
|
+
function maybeGetResolvedMainPath(project) {
|
|
1236
|
+
const projectPath = getProjectPath(project.project);
|
|
1237
|
+
const main = project.options.main;
|
|
1238
|
+
if (main === void 0)
|
|
1239
|
+
return;
|
|
1240
|
+
if (typeof projectPath === "string") {
|
|
1241
|
+
return path4.resolve(path4.dirname(projectPath), main);
|
|
1242
|
+
} else {
|
|
1243
|
+
return path4.resolve(main);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async function runTests(ctx, mf, workerName, project, config, files, invalidates = []) {
|
|
1247
|
+
let workerPath = path4.join(ctx.distPath, "worker.js");
|
|
1248
|
+
let threadsWorkerPath = path4.join(ctx.distPath, "workers", "threads.js");
|
|
1249
|
+
if (process.platform === "win32") {
|
|
1250
|
+
workerPath = `/${ensurePosixLikePath(workerPath)}`;
|
|
1251
|
+
threadsWorkerPath = `/${ensurePosixLikePath(threadsWorkerPath)}`;
|
|
1252
|
+
}
|
|
1253
|
+
ctx.state.clearFiles(project.project, files);
|
|
1254
|
+
const data = {
|
|
1255
|
+
pool: "threads",
|
|
1256
|
+
worker: threadsWorkerPath,
|
|
1257
|
+
port: void 0,
|
|
1258
|
+
config,
|
|
1259
|
+
files,
|
|
1260
|
+
invalidates,
|
|
1261
|
+
environment: { name: "node", options: null },
|
|
1262
|
+
workerId: 0,
|
|
1263
|
+
projectName: project.project.getName(),
|
|
1264
|
+
providedContext: project.project.getProvidedContext()
|
|
1265
|
+
};
|
|
1266
|
+
await waitForStorageReset(mf);
|
|
1267
|
+
const ns = await mf.getDurableObjectNamespace(
|
|
1268
|
+
RUNNER_OBJECT_BINDING,
|
|
1269
|
+
workerName
|
|
1270
|
+
);
|
|
1271
|
+
const stub = ns.get("singleton");
|
|
1272
|
+
const res = await stub.fetch("http://placeholder", {
|
|
1273
|
+
headers: {
|
|
1274
|
+
Upgrade: "websocket",
|
|
1275
|
+
"MF-Vitest-Worker-Data": structuredSerializableStringify({
|
|
1276
|
+
filePath: workerPath,
|
|
1277
|
+
name: "run",
|
|
1278
|
+
data
|
|
1279
|
+
})
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
const webSocket = res.webSocket;
|
|
1283
|
+
assert4(webSocket !== null);
|
|
1284
|
+
const chunkingSocket = createChunkingSocket({
|
|
1285
|
+
post(message) {
|
|
1286
|
+
webSocket.send(message);
|
|
1287
|
+
},
|
|
1288
|
+
on(listener) {
|
|
1289
|
+
webSocket.addEventListener("message", (event2) => {
|
|
1290
|
+
listener(event2.data);
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
const rules = project.options.miniflare?.modulesRules;
|
|
1295
|
+
const compiledRules = compileModuleRules(rules ?? []);
|
|
1296
|
+
const localRpcFunctions = createMethodsRPC(project.project);
|
|
1297
|
+
const patchedLocalRpcFunctions = {
|
|
1298
|
+
...localRpcFunctions,
|
|
1299
|
+
async fetch(...args) {
|
|
1300
|
+
const specifier = args[0];
|
|
1301
|
+
if (/^(cloudflare|workerd):/.test(specifier) || workerdBuiltinModules.has(specifier)) {
|
|
1302
|
+
return { externalize: specifier };
|
|
1303
|
+
}
|
|
1304
|
+
const maybeRule = compiledRules.find(
|
|
1305
|
+
(rule) => testRegExps(rule.include, specifier)
|
|
1306
|
+
);
|
|
1307
|
+
if (maybeRule !== void 0) {
|
|
1308
|
+
const externalize = specifier + `?mf_vitest_force=${maybeRule.type}`;
|
|
1309
|
+
return { externalize };
|
|
1310
|
+
}
|
|
1311
|
+
return localRpcFunctions.fetch(...args);
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
let startupError;
|
|
1315
|
+
const rpc = createBirpc(patchedLocalRpcFunctions, {
|
|
1316
|
+
eventNames: ["onCancel"],
|
|
1317
|
+
post(value) {
|
|
1318
|
+
if (webSocket.readyState === WebSocket.READY_STATE_OPEN) {
|
|
1319
|
+
debuglog2("POOL-->WORKER", value);
|
|
1320
|
+
chunkingSocket.post(structuredSerializableStringify(value));
|
|
1321
|
+
} else {
|
|
1322
|
+
debuglog2("POOL--* ", value);
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
on(listener) {
|
|
1326
|
+
chunkingSocket.on((message) => {
|
|
1327
|
+
const value = structuredSerializableParse(message);
|
|
1328
|
+
debuglog2("POOL<--WORKER", value);
|
|
1329
|
+
if (typeof value === "object" && value !== null && "vitestPoolWorkersError" in value) {
|
|
1330
|
+
startupError = value.vitestPoolWorkersError;
|
|
1331
|
+
} else {
|
|
1332
|
+
listener(value);
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
project.project.ctx.onCancel((reason) => rpc.onCancel(reason));
|
|
1338
|
+
webSocket.accept();
|
|
1339
|
+
const [event] = await events.once(webSocket, "close");
|
|
1340
|
+
if (webSocket.readyState === WebSocket.READY_STATE_CLOSING) {
|
|
1341
|
+
if (event.code === 1005) {
|
|
1342
|
+
webSocket.close();
|
|
1343
|
+
} else {
|
|
1344
|
+
webSocket.close(event.code, event.reason);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
if (event.code !== 1e3) {
|
|
1348
|
+
throw startupError ?? new Error("Failed to run tests");
|
|
1349
|
+
}
|
|
1350
|
+
debuglog2("DONE", files);
|
|
1351
|
+
}
|
|
1352
|
+
function getPackageJson(dirPath) {
|
|
1353
|
+
while (true) {
|
|
1354
|
+
const pkgJsonPath = path4.join(dirPath, "package.json");
|
|
1355
|
+
try {
|
|
1356
|
+
const contents = fs3.readFileSync(pkgJsonPath, "utf8");
|
|
1357
|
+
return JSON.parse(contents);
|
|
1358
|
+
} catch (e) {
|
|
1359
|
+
if (!isFileNotFoundError(e))
|
|
1360
|
+
throw e;
|
|
1361
|
+
}
|
|
1362
|
+
const nextDirPath = path4.dirname(dirPath);
|
|
1363
|
+
if (nextDirPath === dirPath)
|
|
1364
|
+
return;
|
|
1365
|
+
dirPath = nextDirPath;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function assertCompatibleVitestVersion(ctx) {
|
|
1369
|
+
const poolPkgJson = getPackageJson(__dirname2);
|
|
1370
|
+
const vitestPkgJson = getPackageJson(ctx.distPath);
|
|
1371
|
+
assert4(
|
|
1372
|
+
poolPkgJson !== void 0,
|
|
1373
|
+
"Expected to find `package.json` for `@cloudflare/vitest-pool-workers`"
|
|
1374
|
+
);
|
|
1375
|
+
assert4(
|
|
1376
|
+
vitestPkgJson !== void 0,
|
|
1377
|
+
"Expected to find `package.json` for `vitest`"
|
|
1378
|
+
);
|
|
1379
|
+
const expectedVitestVersion = poolPkgJson.peerDependencies?.vitest;
|
|
1380
|
+
const actualVitestVersion = vitestPkgJson.version;
|
|
1381
|
+
assert4(
|
|
1382
|
+
expectedVitestVersion !== void 0,
|
|
1383
|
+
"Expected to find `@cloudflare/vitest-pool-workers`'s `vitest` version constraint"
|
|
1384
|
+
);
|
|
1385
|
+
assert4(
|
|
1386
|
+
actualVitestVersion !== void 0,
|
|
1387
|
+
"Expected to find `vitest`'s version"
|
|
1388
|
+
);
|
|
1389
|
+
if (expectedVitestVersion !== actualVitestVersion) {
|
|
1390
|
+
const message = [
|
|
1391
|
+
`You're running \`vitest@${actualVitestVersion}\`, but this version of \`@cloudflare/vitest-pool-workers\` only supports \`vitest@${expectedVitestVersion}\`.`,
|
|
1392
|
+
"`@cloudflare/vitest-pool-workers` currently depends on internal Vitest APIs that are not protected by semantic-versioning guarantees.",
|
|
1393
|
+
`Please install \`vitest@${expectedVitestVersion}\` to continue using \`@cloudflare/vitest-pool-workers\`.`
|
|
1394
|
+
].join("\n");
|
|
1395
|
+
throw new Error(message);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
function pool_default(ctx) {
|
|
1399
|
+
assertCompatibleVitestVersion(ctx);
|
|
1400
|
+
return {
|
|
1401
|
+
name: "vitest-pool-workers",
|
|
1402
|
+
async runTests(specs, invalidates) {
|
|
1403
|
+
const parsedProjectOptions = /* @__PURE__ */ new Set();
|
|
1404
|
+
for (const [project, testFile] of specs) {
|
|
1405
|
+
const projectName = project.getName();
|
|
1406
|
+
let workersProject = allProjects.get(projectName);
|
|
1407
|
+
if (workersProject === void 0) {
|
|
1408
|
+
workersProject = {
|
|
1409
|
+
project,
|
|
1410
|
+
options: await parseProjectOptions(project),
|
|
1411
|
+
testFiles: /* @__PURE__ */ new Set(),
|
|
1412
|
+
relativePath: getRelativeProjectPath(project)
|
|
1413
|
+
};
|
|
1414
|
+
allProjects.set(projectName, workersProject);
|
|
1415
|
+
} else if (!parsedProjectOptions.has(project)) {
|
|
1416
|
+
workersProject.project = project;
|
|
1417
|
+
workersProject.options = await parseProjectOptions(project);
|
|
1418
|
+
workersProject.relativePath = getRelativeProjectPath(project);
|
|
1419
|
+
}
|
|
1420
|
+
workersProject.testFiles.add(testFile);
|
|
1421
|
+
parsedProjectOptions.add(project);
|
|
1422
|
+
}
|
|
1423
|
+
const resultPromises = [];
|
|
1424
|
+
const filesByProject = /* @__PURE__ */ new Map();
|
|
1425
|
+
for (const [project, file] of specs) {
|
|
1426
|
+
let group = filesByProject.get(project);
|
|
1427
|
+
if (group === void 0)
|
|
1428
|
+
filesByProject.set(project, group = []);
|
|
1429
|
+
group.push(file);
|
|
1430
|
+
}
|
|
1431
|
+
for (const [workspaceProject, files] of filesByProject) {
|
|
1432
|
+
const project = allProjects.get(workspaceProject.getName());
|
|
1433
|
+
assert4(project !== void 0);
|
|
1434
|
+
const options = project.options;
|
|
1435
|
+
const config = workspaceProject.getSerializableConfig();
|
|
1436
|
+
config.runner = "cloudflare:test-runner";
|
|
1437
|
+
config.fakeTimers.toFake = config.fakeTimers.toFake?.filter(
|
|
1438
|
+
(method) => method !== "setImmediate" && method !== "clearImmediate"
|
|
1439
|
+
);
|
|
1440
|
+
config.poolOptions = {
|
|
1441
|
+
threads: {
|
|
1442
|
+
// Allow workers to be re-used by removing the isolation requirement
|
|
1443
|
+
isolate: false
|
|
1444
|
+
},
|
|
1445
|
+
workers: {
|
|
1446
|
+
// Include resolved `main` if defined, and the names of Durable Object
|
|
1447
|
+
// bindings that point to classes in the current isolate in the
|
|
1448
|
+
// serialized config
|
|
1449
|
+
main: maybeGetResolvedMainPath(project),
|
|
1450
|
+
// Include designators of all Durable Object namespaces bound in the
|
|
1451
|
+
// runner worker. We'll use this to list IDs in a namespace. We'll
|
|
1452
|
+
// also use this to check Durable Object test runner helpers are
|
|
1453
|
+
// only used with classes defined in the current worker, as these
|
|
1454
|
+
// helpers rely on wrapping the object.
|
|
1455
|
+
durableObjectBindingDesignators: getDurableObjectDesignators(
|
|
1456
|
+
project.options
|
|
1457
|
+
),
|
|
1458
|
+
// Include whether isolated storage has been enabled for this
|
|
1459
|
+
// project, so we know whether to call out to the loopback service
|
|
1460
|
+
// to push/pop the storage stack between tests.
|
|
1461
|
+
isolatedStorage: project.options.isolatedStorage
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
const mf = await getProjectMiniflare(ctx, project);
|
|
1465
|
+
if (options.singleWorker) {
|
|
1466
|
+
assert4(mf instanceof Miniflare, "Expected single instance");
|
|
1467
|
+
const name = getRunnerName(workspaceProject);
|
|
1468
|
+
resultPromises.push(
|
|
1469
|
+
runTests(ctx, mf, name, project, config, files, invalidates)
|
|
1470
|
+
);
|
|
1471
|
+
} else if (options.isolatedStorage) {
|
|
1472
|
+
assert4(mf instanceof Map, "Expected multiple isolated instances");
|
|
1473
|
+
const name = getRunnerName(workspaceProject);
|
|
1474
|
+
for (const file of files) {
|
|
1475
|
+
const fileMf = mf.get(file);
|
|
1476
|
+
assert4(fileMf !== void 0);
|
|
1477
|
+
resultPromises.push(
|
|
1478
|
+
runTests(ctx, fileMf, name, project, config, [file], invalidates)
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
} else {
|
|
1482
|
+
assert4(mf instanceof Miniflare, "Expected single instance");
|
|
1483
|
+
for (const file of files) {
|
|
1484
|
+
const name = getRunnerName(workspaceProject, file);
|
|
1485
|
+
resultPromises.push(
|
|
1486
|
+
runTests(ctx, mf, name, project, config, [file], invalidates)
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
const results = await Promise.allSettled(resultPromises);
|
|
1492
|
+
const errors = results.filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
1493
|
+
for (const project of allProjects.values()) {
|
|
1494
|
+
if (project.mf !== void 0) {
|
|
1495
|
+
void forEachMiniflare(
|
|
1496
|
+
project.mf,
|
|
1497
|
+
async (mf) => scheduleStorageReset(mf)
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (errors.length > 0) {
|
|
1502
|
+
throw new AggregateError(
|
|
1503
|
+
errors,
|
|
1504
|
+
"Errors occurred while running tests. For more information, see serialized error."
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1508
|
+
async close() {
|
|
1509
|
+
log.debug("Shutting down runtimes...");
|
|
1510
|
+
const promises = [];
|
|
1511
|
+
for (const project of allProjects.values()) {
|
|
1512
|
+
if (project.mf !== void 0) {
|
|
1513
|
+
promises.push(
|
|
1514
|
+
forEachMiniflare(project.mf, async (mf) => {
|
|
1515
|
+
await waitForStorageReset(mf);
|
|
1516
|
+
await mf.dispose();
|
|
1517
|
+
})
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
allProjects.clear();
|
|
1522
|
+
await Promise.all(promises);
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
export {
|
|
1527
|
+
pool_default as default
|
|
1528
|
+
};
|
|
1529
|
+
//# sourceMappingURL=index.mjs.map
|