env-runner 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.
@@ -1,23 +1,27 @@
1
1
  import { expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";
2
2
  import { BaseEnvRunner } from "./common-base-runner.mjs";
3
+ import { isVirtualSpecifier } from "./common-worker-utils.mjs";
3
4
  import { resolveRuntimeDep } from "./common-runtime-deps.mjs";
4
5
  import { init, parse } from "./libs/cjs-module-lexer.mjs";
5
6
  import { init as init$1, parse as parse$1 } from "./libs/es-module-lexer.mjs";
6
- import { isVirtualSpecifier } from "./common-worker-utils.mjs";
7
7
  import { createRequire } from "node:module";
8
8
  import { proxyUpgrade } from "httpxy";
9
9
  import { fileURLToPath } from "node:url";
10
- import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
10
+ import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
11
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
11
12
  import { resolveModulePath } from "exsolve";
12
- import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
13
13
  import { tmpdir } from "node:os";
14
+ import { parseEnv } from "node:util";
14
15
  const IPC_PATH = "/__env_runner_ipc";
15
16
  const IPC_BINDING = "__ENV_RUNNER_IPC";
17
+ const UNSAFE_EVAL_BINDING = "__ENV_RUNNER_UNSAFE_EVAL__";
16
18
  function generateWrapper(entryPath, opts) {
19
+ const processShim = opts?.nodeCompat === false ? "" : `import __process from "node:process";
20
+ if (!globalThis.process) { globalThis.process = __process; }`;
17
21
  const staticReExport = opts?.dynamicOnly ? "" : `export * from ${JSON.stringify(entryPath)};`;
18
22
  const explicitExports = opts?.dynamicOnly && opts.exports?.length ? opts.exports.map((name) => `export { ${name} } from ${JSON.stringify(entryPath)};`).join("\n") : "";
19
23
  const fetchBody = opts?.captureErrors ?? true ? `try {
20
- return await entryFetch(request, env, ctx);
24
+ return await __server.fetch(request, env, ctx);
21
25
  } catch (e) {
22
26
  const error = e instanceof Error ? e : new Error(String(e));
23
27
  const body = JSON.stringify({
@@ -29,20 +33,94 @@ function generateWrapper(entryPath, opts) {
29
33
  status: 500,
30
34
  headers: { "Content-Type": "application/json", "X-Env-Runner-Error": "1" },
31
35
  });
32
- }` : `return entryFetch(request, env, ctx);`;
33
- return `import __process from "node:process";
34
- if (!globalThis.process) { globalThis.process = __process; }
36
+ }` : `return __server.fetch(request, env, ctx);`;
37
+ return `${processShim}
35
38
  ${staticReExport}
36
39
  ${explicitExports}
37
40
 
38
41
  const __IPC_PATH = "${IPC_PATH}";
39
42
  const __IPC_BINDING = "${IPC_BINDING}";
43
+ const __UNSAFE_EVAL_BINDING = "${UNSAFE_EVAL_BINDING}";
40
44
  const __entryPath = ${JSON.stringify(entryPath)};
41
45
  let __userEntry;
46
+ let __server;
42
47
  let __ipcInitialized = false;
43
48
  let __serverWs;
44
49
  let __currentEnv;
45
50
 
51
+ const __userEnvs = new WeakMap();
52
+
53
+ // \`env\` without env-runner's internal bindings (cached per env object).
54
+ function __userEnv(env) {
55
+ let userEnv = __userEnvs.get(env);
56
+ if (!userEnv) {
57
+ userEnv = { ...env };
58
+ delete userEnv[__IPC_BINDING];
59
+ delete userEnv[__UNSAFE_EVAL_BINDING];
60
+ __userEnvs.set(env, userEnv);
61
+ }
62
+ return userEnv;
63
+ }
64
+
65
+ // Mirrors srvx's CloudflareServer (srvx/cloudflare): plugins, then the
66
+ // \`error\` handler as the outermost middleware, then the middleware chain.
67
+ function __createServer(entry) {
68
+ const server = {
69
+ runtime: "cloudflare",
70
+ options: {
71
+ ...entry,
72
+ middleware: [...(entry.middleware || [])],
73
+ fetch: entry.fetch
74
+ ? (request) =>
75
+ entry.fetch(request, request.runtime?.cloudflare?.env, request.runtime?.cloudflare?.context)
76
+ : () => new Response("No fetch handler exported", { status: 500 }),
77
+ },
78
+ serve() {},
79
+ ready: () => Promise.resolve(server),
80
+ close: () => Promise.resolve(),
81
+ };
82
+ for (const plugin of entry.plugins || []) {
83
+ plugin(server);
84
+ }
85
+ const errorHandler = server.options.error;
86
+ if (errorHandler) {
87
+ server.options.middleware.unshift((_request, next) => {
88
+ try {
89
+ const res = next();
90
+ return typeof res?.then === "function" ? res.then(undefined, (error) => errorHandler(error)) : res;
91
+ } catch (error) {
92
+ return errorHandler(error);
93
+ }
94
+ });
95
+ }
96
+ let handler = server.options.fetch;
97
+ const middleware = server.options.middleware;
98
+ for (let i = middleware.length - 1; i >= 0; i--) {
99
+ const mw = middleware[i];
100
+ const next = handler;
101
+ handler = (request) => mw(request, () => next(request));
102
+ }
103
+ server.fetch = (request, env, context) => {
104
+ const userEnv = __userEnv(env);
105
+ Object.defineProperties(request, {
106
+ waitUntil: { value: context.waitUntil.bind(context) },
107
+ runtime: {
108
+ enumerable: true,
109
+ value: { name: "cloudflare", cloudflare: { env: userEnv, context } },
110
+ },
111
+ ip: {
112
+ enumerable: true,
113
+ configurable: true,
114
+ get() {
115
+ return request.headers.get("cf-connecting-ip");
116
+ },
117
+ },
118
+ });
119
+ return handler(request);
120
+ };
121
+ return server;
122
+ }
123
+
46
124
  async function __loadEntry(env, path) {
47
125
  globalThis.__ENV_RUNNER_UNSAFE_EVAL__ = env.__ENV_RUNNER_UNSAFE_EVAL__;
48
126
  const importFn = env.__ENV_RUNNER_UNSAFE_EVAL__.newAsyncFunction(
@@ -84,10 +162,12 @@ async function __handleWsMessage(env, data) {
84
162
  const version = msg.version || 0;
85
163
  try {
86
164
  const newEntry = await __loadEntry(env, __entryPath + "?t=" + version);
165
+ const newServer = __createServer(newEntry);
87
166
  if (__userEntry?.ipc?.onClose) {
88
167
  await __userEntry.ipc.onClose();
89
168
  }
90
169
  __userEntry = newEntry;
170
+ __server = newServer;
91
171
  __crosswsAdapter = undefined;
92
172
  __ipcInitialized = false;
93
173
  if (__userEntry.ipc?.onOpen) {
@@ -130,7 +210,9 @@ export default {
130
210
  if (url.pathname === __IPC_PATH && request.headers.get("upgrade") === "websocket") {
131
211
  try {
132
212
  if (!__userEntry) {
133
- __userEntry = await __loadEntry(env, __entryPath);
213
+ const entry = await __loadEntry(env, __entryPath);
214
+ __server = __createServer(entry);
215
+ __userEntry = entry;
134
216
  }
135
217
  } catch (e) {
136
218
  return new Response("Failed to load entry: " + String(e), { status: 500 });
@@ -164,13 +246,9 @@ export default {
164
246
  // Handle WebSocket upgrade via crossws cloudflare adapter
165
247
  if (__userEntry.websocket && request.headers.get("upgrade") === "websocket") {
166
248
  const adapter = await __initCrossws(env, __userEntry.websocket);
167
- return adapter.handleUpgrade(request, env, ctx);
249
+ return adapter.handleUpgrade(request, __userEnv(env), ctx);
168
250
  }
169
251
 
170
- const entryFetch = __userEntry.fetch;
171
- if (!entryFetch) {
172
- return new Response("No fetch handler exported", { status: 500 });
173
- }
174
252
  __currentEnv = env;
175
253
  try {
176
254
  ${fetchBody}
@@ -181,6 +259,67 @@ export default {
181
259
  };
182
260
  `;
183
261
  }
262
+ function expandDotenv(parsed, processEnv) {
263
+ const running = {};
264
+ for (const [key, raw] of Object.entries(parsed)) running[key] = processEnv[key] = (processEnv[key] && processEnv[key] !== raw ? processEnv[key] : expandDotenvValue(raw, processEnv, running)).replace(/\\\$/g, "$");
265
+ return processEnv;
266
+ }
267
+ function expandDotenvValue(value, processEnv, running) {
268
+ const env = {
269
+ ...running,
270
+ ...processEnv
271
+ };
272
+ const regex = /(?<!\\)\$\{([^{}]+)\}|(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)/g;
273
+ const seen = /* @__PURE__ */ new Set();
274
+ let result = value;
275
+ let match;
276
+ while ((match = regex.exec(result)) !== null) {
277
+ seen.add(result);
278
+ const [template, braced, unbraced] = match;
279
+ const expression = braced || unbraced;
280
+ const splitter = expression.match(/(:\+|\+|:-|-)/)?.[0];
281
+ const parts = splitter ? expression.split(splitter) : [expression];
282
+ const key = parts.shift();
283
+ let replacement;
284
+ if (splitter === ":+" || splitter === "+") replacement = env[key] ? parts.join(splitter) : "";
285
+ else {
286
+ const found = env[key];
287
+ replacement = found && !seen.has(found) ? found : parts.join(splitter ?? "");
288
+ }
289
+ result = result.replace(template, replacement);
290
+ if (result === running[key]) break;
291
+ regex.lastIndex = 0;
292
+ }
293
+ return result;
294
+ }
295
+ function readDotenvFile(path) {
296
+ return existsSync(path) ? parseEnv(readFileSync(path, "utf8")) : void 0;
297
+ }
298
+ function booleanEnv(name, defaultValue) {
299
+ const value = process.env[name]?.toLowerCase();
300
+ return value === "true" ? true : value === "false" ? false : defaultValue;
301
+ }
302
+ function loadDevVars(opts) {
303
+ const { configDir, env, envFiles } = opts;
304
+ if (!envFiles?.length) {
305
+ const devVars = resolve(configDir, ".dev.vars");
306
+ const loaded = env !== void 0 && readDotenvFile(`${devVars}.${env}`) || readDotenvFile(devVars);
307
+ if (loaded) return loaded;
308
+ }
309
+ if (!booleanEnv("CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV", true)) return;
310
+ const files = envFiles ?? [
311
+ ".env",
312
+ ".env.local",
313
+ ...env ? [`.env.${env}`, `.env.${env}.local`] : []
314
+ ];
315
+ const parsed = {};
316
+ for (const file of files) Object.assign(parsed, readDotenvFile(resolve(configDir, file)));
317
+ const processEnv = {};
318
+ if (opts.hasSecrets || booleanEnv("CLOUDFLARE_INCLUDE_PROCESS_ENV", false)) {
319
+ for (const [key, value] of Object.entries(process.env)) if (typeof value === "string") processEnv[key] = value;
320
+ }
321
+ return expandDotenv(parsed, processEnv);
322
+ }
184
323
  const WRANGLER_CONFIG_FILENAMES = [
185
324
  "wrangler.json",
186
325
  "wrangler.jsonc",
@@ -198,45 +337,143 @@ const WRANGLER_OPTION_DENYLIST = /* @__PURE__ */ new Set([
198
337
  "unsafeModuleFallbackService",
199
338
  "unsafeUseModuleFallbackService"
200
339
  ]);
340
+ const WRANGLER_OPTION_DROPLIST = /* @__PURE__ */ new Set([
341
+ "assets",
342
+ "serviceBindings",
343
+ "workflows",
344
+ "queueConsumers",
345
+ "tails",
346
+ "streamingTails"
347
+ ]);
348
+ const WRANGLER_DROPPED_OPTION_NAMES = {
349
+ assets: "assets",
350
+ serviceBindings: "services",
351
+ workflows: "workflows",
352
+ queueConsumers: "queues.consumers",
353
+ tails: "tail_consumers",
354
+ streamingTails: "streaming_tail_consumers",
355
+ durableObjects: "durable_objects"
356
+ };
357
+ function addDropped(dropped, name, details) {
358
+ let set = dropped.get(name);
359
+ if (!set) {
360
+ set = /* @__PURE__ */ new Set();
361
+ dropped.set(name, set);
362
+ }
363
+ for (const detail of details) set.add(detail);
364
+ }
365
+ function warnDroppedWranglerOptions(dropped) {
366
+ if (dropped.size === 0) return;
367
+ const list = [...dropped].map(([name, details]) => details.size > 0 ? `${name} (${[...details].join(", ")})` : name).join(", ");
368
+ console.warn(`[env-runner] wrangler config options not supported by the miniflare dev runner were ignored: ${list}; pass them via miniflareOptions to opt in.`);
369
+ }
201
370
  function isPlainObject(value) {
202
371
  return typeof value === "object" && value !== null && !Array.isArray(value);
203
372
  }
204
373
  function isInlineWranglerConfig(opt) {
205
374
  return isPlainObject(opt);
206
375
  }
207
- async function loadWranglerConfig(opt, env, entryPath, wranglerModule) {
208
- if (!opt) return;
376
+ async function loadWranglerConfig(opts) {
377
+ const { wrangler: opt, env, entryPath, wranglerModule, envFiles } = opts;
378
+ if (!opt) return {};
209
379
  const inline = isInlineWranglerConfig(opt) ? opt : void 0;
210
380
  let configPath;
211
- if (typeof opt === "string") {
212
- configPath = resolve(opt);
381
+ const explicitPath = typeof opt === "string" ? opt : opts.configPath;
382
+ if (explicitPath) {
383
+ configPath = resolve(explicitPath);
213
384
  if (!existsSync(configPath)) {
214
385
  console.warn(`[env-runner] wrangler config requested but not found at "${configPath}"`);
215
- return;
386
+ if (!inline) return {};
387
+ configPath = void 0;
216
388
  }
217
- } else if (opt === true) {
389
+ } else {
218
390
  configPath = findWranglerConfig(entryPath);
219
- if (!configPath) {
220
- console.warn("[env-runner] wrangler config requested but none found near the entry or cwd");
221
- return;
391
+ if (!configPath && !inline) {
392
+ console.warn("[env-runner] wrangler config requested but none found (searched the entry's directory, then from the cwd up to the filesystem root)");
393
+ return {};
222
394
  }
223
- } else configPath = findWranglerConfig(entryPath);
395
+ }
224
396
  const wrangler = await resolveRuntimeDep({
225
397
  name: "wrangler",
226
398
  option: "wranglerModule",
227
399
  value: wranglerModule
228
400
  });
229
- if (!wrangler?.unstable_readConfig || !wrangler.unstable_getMiniflareWorkerOptions) return mergeWranglerMiniflareOptions(configPath ? readWranglerConfigMinimal(configPath, env) : void 0, inline ? mapWranglerConfigToMiniflare(applyWranglerEnv(inline, env)) : void 0);
230
- try {
231
- return mergeWranglerMiniflareOptions(configPath ? pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(wrangler.unstable_readConfig({
401
+ const dropped = /* @__PURE__ */ new Map();
402
+ if (!wrangler?.unstable_readConfig || !wrangler.unstable_getMiniflareWorkerOptions) {
403
+ const unsupported = /* @__PURE__ */ new Map();
404
+ const file = configPath ? readWranglerConfigMinimal(configPath, env) : void 0;
405
+ const inlineConfig = inline ? applyWranglerEnv(inline, env) : void 0;
406
+ let options = mergeWranglerMiniflareOptions(file && (mapWranglerConfigToMiniflare(file.config, dropped, unsupported) ?? {}), inlineConfig && mapWranglerConfigToMiniflare(inlineConfig, dropped, unsupported));
407
+ if (file || inlineConfig) options = applyMinimalDevVars(options, [file?.config, inlineConfig], {
408
+ configDir: configPath ? dirname(configPath) : process.cwd(),
409
+ env,
410
+ envFiles
411
+ });
412
+ filterLocalDurableObjects(options, dropped, (inline && wranglerWorkerName(inline, env)) ?? file?.workerName);
413
+ warnDroppedWranglerOptions(dropped);
414
+ warnUnsupportedMinimalBindings(unsupported);
415
+ return {
416
+ options,
417
+ configFile: file ? configPath : void 0
418
+ };
419
+ }
420
+ let fileLoaded = false;
421
+ let fileOptions;
422
+ let fileSecrets;
423
+ let fileWorkerName;
424
+ if (configPath) try {
425
+ const config = wrangler.unstable_readConfig({
232
426
  config: configPath,
233
427
  env
234
- }, { hideWarnings: true }), env).workerOptions) : void 0, inline ? pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(readInlineWranglerConfig(wrangler, inline, env), env).workerOptions) : void 0);
428
+ }, { hideWarnings: !claimWranglerWarnings(configPath, env) });
429
+ fileOptions = pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(config, env, { envFiles }).workerOptions, dropped);
430
+ fileWorkerName = config?.name;
431
+ fileSecrets = config?.secrets;
432
+ fileLoaded = true;
235
433
  } catch (error) {
236
- const desc = [configPath && `"${configPath}"`, inline && "(inline)"].filter(Boolean).join(" + ");
237
- console.warn(`[env-runner] failed to load wrangler config ${desc}: ${error.message}`);
238
- return;
434
+ warnWranglerLoadError(`"${configPath}"`, error);
239
435
  }
436
+ let inlineOptions;
437
+ if (inline) try {
438
+ const inlineEnv = env && isPlainObject(inline.env) && inline.env[env] ? env : void 0;
439
+ const { env: _env, ...inlineTopLevel } = inline;
440
+ const inlineConfig = readInlineWranglerConfig(wrangler, inlineEnv ? inline : inlineTopLevel, inlineEnv);
441
+ inlineConfig.userConfigPath = configPath;
442
+ if (fileLoaded && inlineConfig.secrets === void 0) inlineConfig.secrets = fileSecrets;
443
+ inlineOptions = pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(inlineConfig, env, { envFiles }).workerOptions, dropped);
444
+ if (inlineOptions) {
445
+ delete inlineOptions.rootPath;
446
+ if (fileLoaded) filterInlineDevVarBindings(inlineOptions, fileOptions, inlineConfig);
447
+ }
448
+ } catch (error) {
449
+ warnWranglerLoadError("(inline)", error);
450
+ }
451
+ const options = mergeWranglerMiniflareOptions(fileOptions, inlineOptions);
452
+ filterLocalDurableObjects(options, dropped, (inline && wranglerWorkerName(inline, env)) ?? fileWorkerName);
453
+ warnDroppedWranglerOptions(dropped);
454
+ return {
455
+ options,
456
+ configFile: fileLoaded ? configPath : void 0
457
+ };
458
+ }
459
+ const _shownWranglerWarnings = /* @__PURE__ */ new Set();
460
+ function claimWranglerWarnings(configPath, env) {
461
+ let version = "";
462
+ try {
463
+ const stat = statSync(configPath);
464
+ version = `${stat.mtimeMs}:${stat.size}`;
465
+ } catch {}
466
+ const key = JSON.stringify([
467
+ resolve(configPath),
468
+ env ?? null,
469
+ version
470
+ ]);
471
+ if (_shownWranglerWarnings.has(key)) return false;
472
+ _shownWranglerWarnings.add(key);
473
+ return true;
474
+ }
475
+ function warnWranglerLoadError(desc, error) {
476
+ console.warn(`[env-runner] failed to load wrangler config ${desc}: ${error.message}`);
240
477
  }
241
478
  function mergeWranglerMiniflareOptions(base, override) {
242
479
  if (!base) return override;
@@ -253,6 +490,12 @@ function mergeWranglerMiniflareOptions(base, override) {
253
490
  }
254
491
  return out;
255
492
  }
493
+ function filterInlineDevVarBindings(inlineOptions, fileOptions, inlineConfig) {
494
+ if (!isPlainObject(inlineOptions.bindings)) return;
495
+ const fileBindings = isPlainObject(fileOptions?.bindings) ? fileOptions.bindings : {};
496
+ const declared = /* @__PURE__ */ new Set([...Object.keys(inlineConfig.vars ?? {}), ...inlineConfig.secrets?.required ?? []]);
497
+ inlineOptions.bindings = Object.fromEntries(Object.entries(inlineOptions.bindings).filter(([key]) => declared.has(key) || Object.hasOwn(fileBindings, key)));
498
+ }
256
499
  function readInlineWranglerConfig(wrangler, inline, env) {
257
500
  const dir = mkdtempSync(join(tmpdir(), "env-runner-wrangler-"));
258
501
  const file = join(dir, "wrangler.json");
@@ -269,29 +512,173 @@ function readInlineWranglerConfig(wrangler, inline, env) {
269
512
  });
270
513
  }
271
514
  }
515
+ const _announcedWranglerConfigs = /* @__PURE__ */ new Set();
272
516
  function findWranglerConfig(entryPath) {
273
- const dirs = [];
517
+ const cwd = process.cwd();
518
+ let start = cwd;
274
519
  if (entryPath) {
275
- const resolved = isAbsolute(entryPath) ? entryPath : resolve(entryPath);
276
- dirs.push(dirname(resolved));
520
+ const entryDir = dirname(resolve(entryPath));
521
+ const rel = relative(cwd, entryDir);
522
+ if (rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)) start = entryDir;
523
+ else {
524
+ const local = findWranglerConfigInDir(entryDir);
525
+ if (local) return local;
526
+ }
527
+ }
528
+ for (let dir = start;; dir = dirname(dir)) {
529
+ const found = findWranglerConfigInDir(dir);
530
+ if (found) {
531
+ if (dir !== cwd && dir !== start && !_announcedWranglerConfigs.has(found)) {
532
+ _announcedWranglerConfigs.add(found);
533
+ console.info(`[env-runner] using wrangler config from a parent directory: ${found}`);
534
+ }
535
+ return found;
536
+ }
537
+ if (dirname(dir) === dir) return;
277
538
  }
278
- dirs.push(process.cwd());
279
- for (const dir of dirs) for (const name of WRANGLER_CONFIG_FILENAMES) {
539
+ }
540
+ function findWranglerConfigInDir(dir) {
541
+ for (const name of WRANGLER_CONFIG_FILENAMES) {
280
542
  const candidate = join(dir, name);
281
543
  if (existsSync(candidate)) return candidate;
282
544
  }
283
545
  }
284
- function pickWranglerMiniflareOptions(workerOptions) {
546
+ function pickWranglerMiniflareOptions(workerOptions, dropped) {
285
547
  const out = {};
286
548
  for (const [key, value] of Object.entries(workerOptions)) {
287
549
  if (value === void 0 || WRANGLER_OPTION_DENYLIST.has(key)) continue;
550
+ if (WRANGLER_OPTION_DROPLIST.has(key)) {
551
+ if (!isEmptyOption(value)) addDropped(dropped, WRANGLER_DROPPED_OPTION_NAMES[key], describeDroppedOption(value));
552
+ continue;
553
+ }
554
+ if (isEmptyPickedOption(key, value)) continue;
288
555
  out[key] = value;
289
556
  }
290
557
  return Object.keys(out).length > 0 ? out : void 0;
291
558
  }
559
+ function isEmptyPickedOption(key, value) {
560
+ const isEmptyArray = (v) => Array.isArray(v) && v.length === 0;
561
+ if (isEmptyArray(value) || isPlainObject(value) && Object.keys(value).length === 0) return true;
562
+ return key !== "bindings" && isPlainObject(value) && Object.values(value).every(isEmptyArray);
563
+ }
564
+ function isEmptyOption(value) {
565
+ return !value || Array.isArray(value) && value.length === 0 || isPlainObject(value) && Object.keys(value).length === 0;
566
+ }
567
+ function describeDroppedOption(value) {
568
+ if (Array.isArray(value)) return value.map((entry) => entry?.name).filter((name) => typeof name === "string");
569
+ if (isPlainObject(value)) {
570
+ if ("directory" in value) return typeof value.binding === "string" ? [value.binding] : [];
571
+ return Object.keys(value);
572
+ }
573
+ return [];
574
+ }
575
+ function filterLocalDurableObjects(options, dropped, workerName) {
576
+ const value = options?.durableObjects;
577
+ if (!options || !isPlainObject(value)) return;
578
+ const out = {};
579
+ for (const [name, binding] of Object.entries(value)) if (typeof binding === "string" || isPlainObject(binding) && !binding.scriptName) out[name] = binding;
580
+ else if (isPlainObject(binding) && workerName && binding.scriptName === workerName) {
581
+ const { scriptName: _scriptName, ...local } = binding;
582
+ out[name] = local;
583
+ } else {
584
+ const scriptName = isPlainObject(binding) ? binding.scriptName : void 0;
585
+ addDropped(dropped, "durable_objects", [`${name} → script "${String(scriptName)}"`]);
586
+ }
587
+ if (Object.keys(out).length > 0) options.durableObjects = out;
588
+ else delete options.durableObjects;
589
+ }
590
+ const WRANGLER_NON_INHERITABLE_KEYS = /* @__PURE__ */ new Set([
591
+ "vars",
592
+ "secrets",
593
+ "define",
594
+ "durable_objects",
595
+ "workflows",
596
+ "kv_namespaces",
597
+ "cloudchamber",
598
+ "containers",
599
+ "send_email",
600
+ "queues",
601
+ "connect",
602
+ "r2_buckets",
603
+ "d1_databases",
604
+ "vectorize",
605
+ "ai_search_namespaces",
606
+ "ai_search",
607
+ "websearch",
608
+ "agent_memory",
609
+ "hyperdrive",
610
+ "services",
611
+ "analytics_engine_datasets",
612
+ "dispatch_namespaces",
613
+ "mtls_certificates",
614
+ "tail_consumers",
615
+ "streaming_tail_consumers",
616
+ "unsafe",
617
+ "browser",
618
+ "ai",
619
+ "images",
620
+ "stream",
621
+ "media",
622
+ "pipelines",
623
+ "secrets_store_secrets",
624
+ "artifacts",
625
+ "unsafe_hello_world",
626
+ "flagship",
627
+ "worker_loaders",
628
+ "ratelimits",
629
+ "vpc_services",
630
+ "vpc_networks",
631
+ "version_metadata"
632
+ ]);
633
+ const MINIMAL_UNSUPPORTED_BINDING_KEYS = [
634
+ "hyperdrive",
635
+ "analytics_engine_datasets",
636
+ "ai",
637
+ "ai_search_namespaces",
638
+ "ai_search",
639
+ "websearch",
640
+ "agent_memory",
641
+ "version_metadata",
642
+ "ratelimits",
643
+ "send_email",
644
+ "secrets_store_secrets",
645
+ "vectorize",
646
+ "browser",
647
+ "images",
648
+ "stream",
649
+ "media",
650
+ "pipelines",
651
+ "dispatch_namespaces",
652
+ "mtls_certificates",
653
+ "worker_loaders",
654
+ "vpc_services",
655
+ "vpc_networks",
656
+ "artifacts",
657
+ "flagship",
658
+ "containers",
659
+ "unsafe",
660
+ "unsafe_hello_world",
661
+ "logfwdr",
662
+ "wasm_modules",
663
+ "text_blobs",
664
+ "data_blobs"
665
+ ];
666
+ const MINIMAL_BINDING_OPTIONS = [
667
+ "kvNamespaces",
668
+ "r2Buckets",
669
+ "d1Databases",
670
+ "queueProducers",
671
+ "durableObjects"
672
+ ];
673
+ function warnUnsupportedMinimalBindings(unsupported) {
674
+ if (unsupported.size === 0) return;
675
+ const list = [...unsupported].map(([name, details]) => details.size > 0 ? `${name} (${[...details].join(", ")})` : name).join(", ");
676
+ console.warn(`[env-runner] wrangler config bindings not supported by the built-in minimal wrangler config reader were ignored: ${list}; install 'wrangler' (or pass it as \`wranglerModule\`) to use them.`);
677
+ }
292
678
  function readWranglerConfigMinimal(configPath, env) {
293
- if (extname(configPath).toLowerCase() !== ".json") {
294
- console.warn(`[env-runner] reading "${basename(configPath)}" requires the 'wrangler' package; the built-in reader supports plain JSON only (install 'wrangler' for JSONC/TOML).`);
679
+ const ext = extname(configPath).toLowerCase();
680
+ if (ext !== ".json" && ext !== ".jsonc") {
681
+ console.warn(`[env-runner] reading "${basename(configPath)}" requires the 'wrangler' package; the built-in reader supports JSON/JSONC only (install 'wrangler' for TOML).`);
295
682
  return;
296
683
  }
297
684
  let raw;
@@ -302,45 +689,203 @@ function readWranglerConfigMinimal(configPath, env) {
302
689
  }
303
690
  let config;
304
691
  try {
305
- config = JSON.parse(raw);
692
+ config = parseJSONC(raw);
306
693
  } catch (error) {
307
694
  console.warn(`[env-runner] failed to parse wrangler config "${configPath}": ${error.message}`);
308
695
  return;
309
696
  }
310
- return mapWranglerConfigToMiniflare(applyWranglerEnv(config, env));
697
+ if (!isPlainObject(config)) {
698
+ warnWranglerLoadError(`"${configPath}"`, /* @__PURE__ */ new Error("config must be an object"));
699
+ return;
700
+ }
701
+ let envConfig;
702
+ try {
703
+ envConfig = applyWranglerEnv(config, env, configPath);
704
+ } catch (error) {
705
+ warnWranglerLoadError(`"${configPath}"`, error);
706
+ return;
707
+ }
708
+ return {
709
+ config: envConfig,
710
+ workerName: wranglerWorkerName(config, env)
711
+ };
712
+ }
713
+ function applyWranglerEnv(config, env, configPath) {
714
+ const { env: envs, ...topLevel } = config;
715
+ if (!env) return topLevel;
716
+ const rawEnv = isPlainObject(envs) ? envs[env] : void 0;
717
+ const warnings = [];
718
+ let out;
719
+ if (isPlainObject(rawEnv)) {
720
+ out = {};
721
+ for (const [key, value] of Object.entries(topLevel)) if (!WRANGLER_NON_INHERITABLE_KEYS.has(key)) out[key] = value;
722
+ else if (rawEnv[key] === void 0) warnings.push(`"${key}" exists at the top level, but is not inherited by "env.${env}"`);
723
+ Object.assign(out, rawEnv);
724
+ } else {
725
+ if (configPath && isPlainObject(envs)) throw new Error(`No environment found in configuration with name "${env}". The available configured environment names are: ${JSON.stringify(Object.keys(envs))}`);
726
+ warnings.push(`No environment found in configuration with name "${env}"`);
727
+ out = topLevel;
728
+ }
729
+ if (configPath && warnings.length > 0 && claimWranglerWarnings(configPath, env)) console.warn(`[env-runner] wrangler config "${configPath}": ${warnings.join("; ")}.`);
730
+ return out;
311
731
  }
312
- function applyWranglerEnv(config, env) {
313
- return env && config.env?.[env] ? {
314
- ...config,
315
- ...config.env[env]
316
- } : config;
732
+ function wranglerWorkerName(config, env) {
733
+ const envName = env ? config.env?.[env]?.name : void 0;
734
+ if (typeof envName === "string") return envName;
735
+ if (typeof config.name !== "string" || !config.name) return void 0;
736
+ return env ? `${config.name}-${env}` : config.name;
317
737
  }
318
- function mapWranglerConfigToMiniflare(config) {
738
+ function mapWranglerConfigToMiniflare(config, dropped, unsupported) {
319
739
  const out = {};
740
+ for (const [name, value, field] of [
741
+ [
742
+ "services",
743
+ config.services,
744
+ "binding"
745
+ ],
746
+ [
747
+ "assets",
748
+ config.assets,
749
+ "binding"
750
+ ],
751
+ [
752
+ "queues.consumers",
753
+ config.queues?.consumers,
754
+ "queue"
755
+ ],
756
+ [
757
+ "workflows",
758
+ config.workflows,
759
+ "binding"
760
+ ],
761
+ [
762
+ "tail_consumers",
763
+ config.tail_consumers,
764
+ "service"
765
+ ],
766
+ [
767
+ "streaming_tail_consumers",
768
+ config.streaming_tail_consumers,
769
+ "service"
770
+ ]
771
+ ]) if (!isEmptyOption(value)) addDropped(dropped, name, (Array.isArray(value) ? value : [value]).map((e) => e?.[field]).filter((d) => typeof d === "string"));
772
+ for (const key of MINIMAL_UNSUPPORTED_BINDING_KEYS) {
773
+ const value = config[key];
774
+ const bindings = isPlainObject(value) && Array.isArray(value.bindings) ? value.bindings : value;
775
+ if (!isEmptyOption(bindings)) addDropped(unsupported, key, describeBindingNames(bindings));
776
+ }
320
777
  if (typeof config.compatibility_date === "string") out.compatibilityDate = config.compatibility_date;
321
778
  if (Array.isArray(config.compatibility_flags)) out.compatibilityFlags = config.compatibility_flags;
322
779
  if (config.vars && typeof config.vars === "object") out.bindings = { ...config.vars };
323
- const kv = mapBindingArray(config.kv_namespaces, "binding", (n) => n.id ?? n.binding);
780
+ const kv = mapBindingArray(config.kv_namespaces, "binding", (n) => firstString(n.preview_id, n.id, n.binding));
324
781
  if (kv) out.kvNamespaces = kv;
325
- const r2 = mapBindingArray(config.r2_buckets, "binding", (n) => n.bucket_name ?? n.binding);
782
+ const r2 = mapBindingArray(config.r2_buckets, "binding", (n) => firstString(n.preview_bucket_name, n.bucket_name, n.binding));
326
783
  if (r2) out.r2Buckets = r2;
327
- const d1 = mapBindingArray(config.d1_databases, "binding", (n) => n.database_id ?? n.preview_database_id ?? n.binding);
784
+ const d1 = mapBindingArray(config.d1_databases, "binding", (n) => firstString(n.preview_database_id, n.database_id, n.binding));
328
785
  if (d1) out.d1Databases = d1;
329
786
  const queues = mapBindingArray(config.queues?.producers, "binding", (n) => n.queue);
330
787
  if (queues) out.queueProducers = queues;
331
- if (Array.isArray(config.durable_objects?.bindings)) {
332
- const dos = {};
333
- for (const b of config.durable_objects.bindings) {
334
- if (!b?.name || !b?.class_name) continue;
335
- dos[b.name] = b.script_name ? {
336
- className: b.class_name,
337
- scriptName: b.script_name
338
- } : b.class_name;
339
- }
340
- if (Object.keys(dos).length > 0) out.durableObjects = dos;
788
+ const useSQLite = durableObjectClassStorage(config);
789
+ const doBindings = Array.isArray(config.durable_objects?.bindings) ? config.durable_objects.bindings : [];
790
+ const dos = {};
791
+ for (const b of doBindings) {
792
+ if (!b?.name || !b?.class_name) continue;
793
+ dos[b.name] = {
794
+ className: b.class_name,
795
+ ...b.script_name ? { scriptName: b.script_name } : {},
796
+ ...useSQLite.has(b.class_name) ? { useSQLite: useSQLite.get(b.class_name) } : {}
797
+ };
341
798
  }
799
+ if (Object.keys(dos).length > 0) out.durableObjects = dos;
800
+ const unbound = [...useSQLite].filter(([className]) => !doBindings.some((b) => b?.class_name === className)).map(([className, sqlite]) => ({
801
+ className,
802
+ useSQLite: sqlite
803
+ }));
804
+ if (unbound.length > 0) out.additionalUnboundDurableObjects = unbound;
342
805
  return Object.keys(out).length > 0 ? out : void 0;
343
806
  }
807
+ function durableObjectClassStorage(config) {
808
+ const classes = /* @__PURE__ */ new Map();
809
+ for (const migration of Array.isArray(config.migrations) ? config.migrations : []) {
810
+ for (const name of migration?.deleted_classes ?? []) classes.delete(name);
811
+ for (const { from, to } of migration?.renamed_classes ?? []) {
812
+ const sqlite = classes.get(from);
813
+ if (sqlite !== void 0) {
814
+ classes.delete(from);
815
+ classes.set(to, sqlite);
816
+ }
817
+ }
818
+ for (const name of migration?.new_classes ?? []) classes.set(name, false);
819
+ for (const name of migration?.new_sqlite_classes ?? []) classes.set(name, true);
820
+ }
821
+ if (isPlainObject(config.exports)) for (const [name, entry] of Object.entries(config.exports)) {
822
+ const state = entry?.state;
823
+ if (entry?.type === "durable-object" && (state === void 0 || state === "created" || state === "expecting-transfer")) classes.set(name, entry.storage === "sqlite");
824
+ }
825
+ return classes;
826
+ }
827
+ function describeBindingNames(value) {
828
+ if (isPlainObject(value) && typeof value.binding !== "string") return Object.keys(value);
829
+ return (Array.isArray(value) ? value : [value]).map((e) => e?.binding ?? e?.name ?? e?.class_name).filter((name) => typeof name === "string");
830
+ }
831
+ function applyMinimalDevVars(options, configs, opts) {
832
+ const secrets = configs.map((c) => c?.secrets).filter(isPlainObject);
833
+ const loaded = loadDevVars({
834
+ ...opts,
835
+ hasSecrets: secrets.length > 0
836
+ });
837
+ if (!loaded) return options;
838
+ const out = { ...options };
839
+ const bindings = isPlainObject(out.bindings) ? { ...out.bindings } : {};
840
+ const taken = new Set(MINIMAL_BINDING_OPTIONS.flatMap((key) => isPlainObject(out[key]) ? Object.keys(out[key]) : []));
841
+ const required = new Set(secrets.flatMap((s) => Array.isArray(s.required) ? s.required : []));
842
+ let changed = false;
843
+ for (const [key, value] of Object.entries(loaded)) {
844
+ if (taken.has(key) || secrets.length > 0 && !Object.hasOwn(bindings, key) && !required.has(key)) continue;
845
+ bindings[key] = value;
846
+ changed = true;
847
+ }
848
+ const missing = [...required].filter((key) => !Object.hasOwn(loaded, key));
849
+ if (missing.length > 0) console.warn(`[env-runner] missing required wrangler secrets: ${missing.join(", ")}. Add them to .dev.vars, .env, or set as environment variables.`);
850
+ if (!changed) return options;
851
+ out.bindings = bindings;
852
+ return out;
853
+ }
854
+ function firstString(...values) {
855
+ return values.find((value) => typeof value === "string");
856
+ }
857
+ function parseJSONC(text) {
858
+ let out = "";
859
+ let comma = -1;
860
+ const src = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
861
+ for (let i = 0; i < src.length; i++) {
862
+ const ch = src[i];
863
+ if (ch === "/" && (src[i + 1] === "/" || src[i + 1] === "*")) {
864
+ const line = src[i + 1] === "/";
865
+ let end = line ? src.indexOf("\n", i) : src.indexOf("*/", i + 2);
866
+ end = end === -1 ? src.length : line ? end : end + 2;
867
+ out += src.slice(i, end).replace(/[^\n]/g, " ");
868
+ i = end - 1;
869
+ continue;
870
+ }
871
+ if (/\s/.test(ch)) {
872
+ out += ch;
873
+ continue;
874
+ }
875
+ if ((ch === "}" || ch === "]") && comma !== -1) out = `${out.slice(0, comma)} ${out.slice(comma + 1)}`;
876
+ comma = -1;
877
+ if (ch === "\"") {
878
+ let end = i + 1;
879
+ while (end < src.length && src[end] !== "\"") end += src[end] === "\\" ? 2 : 1;
880
+ out += src.slice(i, end + 1);
881
+ i = end;
882
+ continue;
883
+ }
884
+ if (ch === ",") comma = out.length;
885
+ out += ch;
886
+ }
887
+ return JSON.parse(out);
888
+ }
344
889
  function mapBindingArray(arr, keyField, value) {
345
890
  if (!Array.isArray(arr)) return;
346
891
  const out = {};
@@ -367,6 +912,9 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
367
912
  #exportConditions;
368
913
  #wrangler;
369
914
  #wranglerEnv;
915
+ #wranglerConfigPath;
916
+ #wranglerEnvFiles;
917
+ #compatibilityDate;
370
918
  #wranglerModule;
371
919
  #miniflareModule;
372
920
  constructor(opts) {
@@ -384,6 +932,9 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
384
932
  this.#wrangler = opts.wrangler ?? false;
385
933
  this.#wranglerEnv = opts.wranglerEnv ?? process.env.CLOUDFLARE_ENV;
386
934
  this.#wranglerModule = opts.wranglerModule;
935
+ this.#wranglerConfigPath = opts.wranglerConfigPath;
936
+ this.#wranglerEnvFiles = opts.wranglerEnvFiles;
937
+ this.#compatibilityDate = opts.compatibilityDate;
387
938
  this._initWithVirtualData(() => this.#init());
388
939
  }
389
940
  static async disposeAll() {
@@ -408,8 +959,12 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
408
959
  for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
409
960
  if (!this.#miniflare || this.closed) return new Response("miniflare env runner is unavailable", { status: 503 });
410
961
  const resolved = this._resolveFetchInput(input);
411
- const url = typeof resolved === "string" ? resolved : resolved instanceof URL ? resolved.href : resolved.url;
412
- const res = await this.#miniflare.dispatchFetch(url, init);
962
+ const request = typeof resolved === "string" || resolved instanceof URL ? new Request(resolved, init) : new Request(new Request(resolved.url, resolved), {
963
+ ...init,
964
+ referrer: init?.referrer ?? resolved.referrer,
965
+ referrerPolicy: init?.referrerPolicy ?? resolved.referrerPolicy
966
+ });
967
+ const res = await this.#miniflare.dispatchFetch(request.url, request);
413
968
  if (res instanceof Response) return res;
414
969
  return new Response(res.body, {
415
970
  status: res.status,
@@ -529,20 +1084,28 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
529
1084
  const entryPath = this._data?.entry;
530
1085
  const virtual = await this.#prepareVirtualModules();
531
1086
  this.#virtual = virtual;
532
- const wranglerOptions = await loadWranglerConfig(this.#wrangler, this.#wranglerEnv, entryPath, this.#wranglerModule);
1087
+ const { options: wranglerOptions, configFile: wranglerConfigFile } = await loadWranglerConfig({
1088
+ wrangler: this.#wrangler,
1089
+ env: this.#wranglerEnv,
1090
+ entryPath,
1091
+ configPath: this.#wranglerConfigPath,
1092
+ wranglerModule: this.#wranglerModule,
1093
+ envFiles: this.#wranglerEnvFiles
1094
+ });
533
1095
  const userFlags = this.#miniflareOptions.compatibilityFlags || [];
534
1096
  const wranglerFlags = wranglerOptions?.compatibilityFlags || [];
535
1097
  const userDirectSockets = this.#miniflareOptions.unsafeDirectSockets || [];
536
1098
  const options = {
537
- compatibilityDate: supportedCompatibilityDate,
538
1099
  modules: true,
1100
+ ...this.#wrangler && !hasUserPersistOptions(this.#miniflareOptions) ? { defaultPersistRoot: wranglerPersistRoot(wranglerConfigFile, typeof this.#wrangler === "string" ? this.#wrangler : this.#wranglerConfigPath) } : void 0,
539
1101
  ...wranglerOptions,
540
1102
  ...this.#miniflareOptions,
541
- compatibilityFlags: [.../* @__PURE__ */ new Set([
542
- "nodejs_compat",
543
- ...wranglerFlags,
544
- ...userFlags
545
- ])],
1103
+ compatibilityDate: resolveCompatibilityDate([
1104
+ this.#miniflareOptions.compatibilityDate,
1105
+ this.#compatibilityDate === "latest" ? supportedCompatibilityDate : this.#compatibilityDate,
1106
+ wranglerOptions?.compatibilityDate
1107
+ ], supportedCompatibilityDate),
1108
+ compatibilityFlags: resolveCompatibilityFlags(wranglerFlags, userFlags),
546
1109
  unsafeDirectSockets: [{
547
1110
  host: "127.0.0.1",
548
1111
  port: 0
@@ -563,22 +1126,25 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
563
1126
  const entrySource = entryIsVirtual ? virtual[entryPath] : _tryReadFile(resolvedEntry);
564
1127
  const detectedExports = this.#exports === false || this.#exports === void 0 ? [] : detectExportedClasses(entrySource, typeof this.#exports === "object" ? this.#exports : {});
565
1128
  if (entryIsVirtual && detectedExports.length > 0) throw new Error(`[env-runner] named exports (${detectedExports.join(", ")}) are not supported with a virtual entry on the miniflare runner; pass \`exports: false\` or use a real entry file.`);
566
- if (detectedExports.length > 0 && !options.durableObjects) {
567
- const autoDOs = { ...this.#miniflareOptions.durableObjects || {} };
1129
+ if (detectedExports.length > 0) {
1130
+ const existingDOs = isPlainObject(options.durableObjects) ? options.durableObjects : {};
1131
+ const boundClasses = new Set(Object.values(existingDOs).map((b) => typeof b === "string" ? b : isPlainObject(b) && !b.scriptName ? b.className : void 0));
1132
+ const autoDOs = { ...existingDOs };
568
1133
  for (const name of detectedExports) {
569
1134
  const bindingName = toScreamingSnakeCase(name);
570
- if (!autoDOs[bindingName]) autoDOs[bindingName] = name;
1135
+ if (!autoDOs[bindingName] && !boundClasses.has(name)) autoDOs[bindingName] = name;
571
1136
  }
572
1137
  options.durableObjects = autoDOs;
573
1138
  }
574
1139
  options.script = generateWrapper(resolvedEntry, {
575
1140
  dynamicOnly: true,
576
1141
  captureErrors: this.#captureErrors,
577
- exports: detectedExports
1142
+ exports: detectedExports,
1143
+ nodeCompat: !options.compatibilityFlags.includes("no_nodejs_compat")
578
1144
  });
579
1145
  options.scriptPath = entryDir + "/__env_runner_wrapper.mjs";
580
1146
  if (!options.modulesRoot) options.modulesRoot = "/";
581
- options.unsafeEvalBinding = "__ENV_RUNNER_UNSAFE_EVAL__";
1147
+ options.unsafeEvalBinding = UNSAFE_EVAL_BINDING;
582
1148
  options.serviceBindings = {
583
1149
  ...options.serviceBindings || {},
584
1150
  [IPC_BINDING]: async (request) => {
@@ -773,6 +1339,30 @@ function detectExportedClasses(entrySource, explicit) {
773
1339
  }
774
1340
  return [...names];
775
1341
  }
1342
+ function resolveCompatibilityDate(candidates, supported) {
1343
+ const date = candidates.find((d) => typeof d === "string" && d) ?? supported;
1344
+ if (date && supported && _isDateString(date) && _isDateString(supported) && date > supported) {
1345
+ console.warn(`[env-runner] compatibility date "${date}" is newer than the installed workerd supports; falling back to "${supported}".`);
1346
+ return supported;
1347
+ }
1348
+ return date;
1349
+ }
1350
+ function resolveCompatibilityFlags(wranglerFlags, userFlags) {
1351
+ const opposite = (flag) => flag === "nodejs_compat" ? "no_nodejs_compat" : flag === "no_nodejs_compat" ? "nodejs_compat" : void 0;
1352
+ const flags = [...wranglerFlags.filter((flag) => !userFlags.includes(opposite(flag))), ...userFlags];
1353
+ const defaults = flags.includes("no_nodejs_compat") ? [] : ["nodejs_compat"];
1354
+ return [.../* @__PURE__ */ new Set([...defaults, ...flags])];
1355
+ }
1356
+ function _isDateString(value) {
1357
+ return /^\d{4}-\d{2}-\d{2}$/.test(value);
1358
+ }
1359
+ function hasUserPersistOptions(options) {
1360
+ return Object.keys(options).some((key) => key === "defaultPersistRoot" || key.endsWith("Persist"));
1361
+ }
1362
+ function wranglerPersistRoot(configFile, explicitPath) {
1363
+ const file = configFile ?? (explicitPath ? resolve(explicitPath) : void 0);
1364
+ return join(file ? dirname(file) : process.cwd(), ".wrangler/state/v3");
1365
+ }
776
1366
  function _tryReadFile(path) {
777
1367
  try {
778
1368
  return readFileSync(path, "utf8");
@@ -799,16 +1389,15 @@ function applyVirtualVersions(code, versions) {
799
1389
  let out = "";
800
1390
  let last = 0;
801
1391
  for (const imp of imports) {
802
- let specifier = imp.n;
803
- if (specifier === void 0 && imp.d > -1) {
804
- const expr = code.slice(imp.s, imp.e);
805
- if (expr.length > 1 && expr[0] === "`" && expr.endsWith("`") && !expr.includes("${")) specifier = expr.slice(1, -1);
806
- }
1392
+ if (imp.type === "import-meta") continue;
1393
+ const dynamic = imp.type === "dynamic";
1394
+ if (dynamic && imp.glob) continue;
1395
+ const specifier = imp.specifier;
807
1396
  const version = specifier === void 0 ? void 0 : versions.get(specifier);
808
1397
  if (!version) continue;
809
1398
  const versioned = `${specifier}?v=${version}`;
810
- out += code.slice(last, imp.s) + (imp.d > -1 ? JSON.stringify(versioned) : versioned);
811
- last = imp.e;
1399
+ out += code.slice(last, imp.start) + (dynamic ? JSON.stringify(versioned) : versioned);
1400
+ last = imp.end;
812
1401
  }
813
1402
  return out + code.slice(last);
814
1403
  }