env-runner 0.2.3 → 0.3.0

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,25 +1,24 @@
1
- import { expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";
1
+ import { encodeVirtualModules, expandVirtualInvalidation, stripVirtualTypeScript, unsupportedVirtualJSXError, virtualModuleCode, virtualModuleCodeSource, virtualModuleFormat } from "./virtual-loader.mjs";
2
2
  import { BaseEnvRunner } from "./common-base-runner.mjs";
3
- import { isVirtualSpecifier } from "./common-worker-utils.mjs";
4
3
  import { resolveRuntimeDep } from "./common-runtime-deps.mjs";
5
4
  import { init, parse } from "./libs/cjs-module-lexer.mjs";
6
5
  import { init as init$1, parse as parse$1 } from "./libs/es-module-lexer.mjs";
7
6
  import { createRequire } from "node:module";
8
7
  import { proxyUpgrade } from "httpxy";
9
- import { fileURLToPath } from "node:url";
10
- import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
11
8
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
12
11
  import { resolveModulePath } from "exsolve";
13
12
  import { tmpdir } from "node:os";
14
13
  import { parseEnv } from "node:util";
15
- const IPC_PATH = "/__env_runner_ipc";
14
+ const IPC_PATH$1 = "/__env_runner_ipc";
16
15
  const IPC_BINDING = "__ENV_RUNNER_IPC";
17
16
  const UNSAFE_EVAL_BINDING = "__ENV_RUNNER_UNSAFE_EVAL__";
18
17
  function generateWrapper(entryPath, opts) {
19
18
  const processShim = opts?.nodeCompat === false ? "" : `import __process from "node:process";
20
19
  if (!globalThis.process) { globalThis.process = __process; }`;
21
20
  const staticReExport = opts?.dynamicOnly ? "" : `export * from ${JSON.stringify(entryPath)};`;
22
- const explicitExports = opts?.dynamicOnly && opts.exports?.length ? opts.exports.map((name) => `export { ${name} } from ${JSON.stringify(entryPath)};`).join("\n") : "";
21
+ const explicitExports = typeof opts?.exports === "string" ? `export * from ${JSON.stringify(opts.exports)};` : opts?.dynamicOnly && opts.exports?.length ? opts.exports.map((name) => `export { ${name} } from ${JSON.stringify(entryPath)};`).join("\n") : "";
23
22
  const fetchBody = opts?.captureErrors ?? true ? `try {
24
23
  return await __server.fetch(request, env, ctx);
25
24
  } catch (e) {
@@ -38,7 +37,7 @@ if (!globalThis.process) { globalThis.process = __process; }`;
38
37
  ${staticReExport}
39
38
  ${explicitExports}
40
39
 
41
- const __IPC_PATH = "${IPC_PATH}";
40
+ const __IPC_PATH = "${IPC_PATH$1}";
42
41
  const __IPC_BINDING = "${IPC_BINDING}";
43
42
  const __UNSAFE_EVAL_BINDING = "${UNSAFE_EVAL_BINDING}";
44
43
  const __entryPath = ${JSON.stringify(entryPath)};
@@ -46,7 +45,10 @@ let __userEntry;
46
45
  let __server;
47
46
  let __ipcInitialized = false;
48
47
  let __serverWs;
49
- let __currentEnv;
48
+ // Raw env of the latest user request. Kept after the request ends: requests
49
+ // overlap and streamed bodies outlive fetch(), and unlike \`__serverWs\` the
50
+ // IPC binding works from any request context.
51
+ let __ipcEnv;
50
52
 
51
53
  const __userEnvs = new WeakMap();
52
54
 
@@ -132,9 +134,16 @@ async function __loadEntry(env, path) {
132
134
  return mod.default || mod;
133
135
  }
134
136
 
137
+ // Where an entry load error was thrown (first stack frame), so one thrown by a
138
+ // virtual module names its key (\`#config:2:7\`). workerd's messages don't.
139
+ function __errorLocation(e) {
140
+ const location = /\\n\\s+at (?:async )?(?:.* \\()?([^\\n()]+:\\d+:\\d+)\\)?/.exec(String(e?.stack))?.[1];
141
+ return location ? " (at " + location + ")" : "";
142
+ }
143
+
135
144
  function __sendMessage(message) {
136
145
  const payload = JSON.stringify(message);
137
- const env = __currentEnv;
146
+ const env = __ipcEnv;
138
147
  if (env && env[__IPC_BINDING]) {
139
148
  env[__IPC_BINDING].fetch("http://localhost/__ipc", {
140
149
  method: "POST",
@@ -206,8 +215,9 @@ export default {
206
215
  async fetch(request, env, ctx) {
207
216
  const url = new URL(request.url);
208
217
 
209
- // WebSocket IPC handshake
210
- if (url.pathname === __IPC_PATH && request.headers.get("upgrade") === "websocket") {
218
+ // IPC: a plain request loads the entry (204, or a 500 with the error), then
219
+ // the WebSocket upgrade opens the channel.
220
+ if (url.pathname === __IPC_PATH) {
211
221
  try {
212
222
  if (!__userEntry) {
213
223
  const entry = await __loadEntry(env, __entryPath);
@@ -215,7 +225,11 @@ export default {
215
225
  __userEntry = entry;
216
226
  }
217
227
  } catch (e) {
218
- return new Response("Failed to load entry: " + String(e), { status: 500 });
228
+ const message = "Failed to load entry: " + String(e) + __errorLocation(e);
229
+ return new Response(message, { status: 500 });
230
+ }
231
+ if (request.headers.get("upgrade") !== "websocket") {
232
+ return new Response(null, { status: 204 });
219
233
  }
220
234
 
221
235
  const pair = new WebSocketPair();
@@ -243,18 +257,15 @@ export default {
243
257
  return new Response("Worker not initialized", { status: 503 });
244
258
  }
245
259
 
260
+ __ipcEnv = env;
261
+
246
262
  // Handle WebSocket upgrade via crossws cloudflare adapter
247
263
  if (__userEntry.websocket && request.headers.get("upgrade") === "websocket") {
248
264
  const adapter = await __initCrossws(env, __userEntry.websocket);
249
265
  return adapter.handleUpgrade(request, __userEnv(env), ctx);
250
266
  }
251
267
 
252
- __currentEnv = env;
253
- try {
254
- ${fetchBody}
255
- } finally {
256
- __currentEnv = undefined;
257
- }
268
+ ${fetchBody}
258
269
  }
259
270
  };
260
271
  `;
@@ -895,6 +906,7 @@ function mapBindingArray(arr, keyField, value) {
895
906
  }
896
907
  return Object.keys(out).length > 0 ? out : void 0;
897
908
  }
909
+ const IPC_PATH = "/__env_runner_ipc";
898
910
  const _miniflareCache = /* @__PURE__ */ new Map();
899
911
  var MiniflareEnvRunner = class extends BaseEnvRunner {
900
912
  #miniflare;
@@ -902,7 +914,6 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
902
914
  #transformRequest;
903
915
  #reloadCounter = 0;
904
916
  #virtual;
905
- #virtualVersions = /* @__PURE__ */ new Map();
906
917
  #cacheEntry;
907
918
  #ws;
908
919
  #persistent;
@@ -959,8 +970,12 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
959
970
  for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
960
971
  if (!this.#miniflare || this.closed) return new Response("miniflare env runner is unavailable", { status: 503 });
961
972
  const resolved = this._resolveFetchInput(input);
962
- const request = typeof resolved === "string" || resolved instanceof URL ? new Request(resolved, init) : new Request(new Request(resolved.url, resolved), {
973
+ const request = typeof resolved === "string" || resolved instanceof URL ? new Request(resolved, {
974
+ ...init,
975
+ redirect: "manual"
976
+ }) : new Request(new Request(resolved.url, resolved), {
963
977
  ...init,
978
+ redirect: "manual",
964
979
  referrer: init?.referrer ?? resolved.referrer,
965
980
  referrerPolicy: init?.referrerPolicy ?? resolved.referrerPolicy
966
981
  });
@@ -987,6 +1002,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
987
1002
  }));
988
1003
  }
989
1004
  async reloadModule(timeout = 5e3) {
1005
+ await this._virtualUpdates;
990
1006
  if (!this.#ws) throw new Error("Miniflare env runner should be initialized before reloading.");
991
1007
  if (!this._data?.entry) return;
992
1008
  this.#reloadCounter++;
@@ -1000,15 +1016,27 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1000
1016
  send: (message) => this.#ws.send(JSON.stringify(message))
1001
1017
  });
1002
1018
  }
1003
- async invalidateModule(specifier, _timeout) {
1019
+ async _applyVirtualUpdates(changes) {
1004
1020
  const virtual = this.#virtual;
1005
- if (!virtual || !Object.hasOwn(virtual, specifier)) {
1006
- const hasVirtual = Object.keys(this._data?.virtual ?? {}).length > 0;
1007
- throw !virtual && hasVirtual && !this.closed ? /* @__PURE__ */ new Error("Miniflare env runner should be initialized before invalidating modules.") : /* @__PURE__ */ new Error(`Cannot invalidate "${specifier}" (not a registered virtual module)`);
1021
+ if (!virtual) throw new Error("Miniflare env runner should be initialized before updating modules.");
1022
+ const prepared = {};
1023
+ for (const [key, module] of Object.entries(changes)) if (module !== null) prepared[key] = await this.#prepareVirtualModule(key, module);
1024
+ await init$1;
1025
+ const merged = {
1026
+ ...virtual.sources,
1027
+ ...prepared
1028
+ };
1029
+ const importers = virtualImporters(merged, createVirtualKeyResolver(Object.keys(merged)));
1030
+ const invalidated = expandVirtualInvalidation(merged, Object.keys(changes), importers);
1031
+ for (const key of Object.keys(changes)) if (Object.hasOwn(prepared, key)) {
1032
+ virtual.sources[key] = prepared[key];
1033
+ virtual.removed.delete(key);
1034
+ } else if (Object.hasOwn(virtual.sources, key)) {
1035
+ delete virtual.sources[key];
1036
+ virtual.removed.add(key);
1008
1037
  }
1009
- const source = await this._refreshVirtualSource(specifier);
1010
- if (source !== void 0) virtual[specifier] = await this.#prepareVirtualSource(specifier, source);
1011
- for (const key of expandVirtualInvalidation(virtual, specifier)) this.#virtualVersions.set(key, (this.#virtualVersions.get(key) ?? 0) + 1);
1038
+ for (const key of invalidated) virtual.versions.set(key, ++virtual.version);
1039
+ refreshVirtualKeyResolvers(virtual);
1012
1040
  if (this.#cacheKey && _miniflareCache.get(this.#cacheKey) === this.#cacheEntry) _miniflareCache.delete(this.#cacheKey);
1013
1041
  }
1014
1042
  _hasRuntime() {
@@ -1066,23 +1094,37 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1066
1094
  } catch {}
1067
1095
  }
1068
1096
  async #prepareVirtualModules() {
1069
- const virtual = this._data?.virtual;
1070
- if (!virtual || Object.keys(virtual).length === 0) return;
1097
+ const virtual = this._data?.virtual ?? {};
1071
1098
  const out = {};
1072
- for (const [specifier, source] of Object.entries(virtual)) out[specifier] = await this.#prepareVirtualSource(specifier, source);
1099
+ for (const [key, module] of Object.entries(virtual)) out[key] = await this.#prepareVirtualModule(key, module);
1073
1100
  return out;
1074
1101
  }
1075
- async #prepareVirtualSource(specifier, source) {
1076
- if (virtualModuleFormat(specifier) !== "module-typescript") return source;
1077
- return stripVirtualTypeScript(specifier, source, await _getStripTypeScriptTypes(), {
1078
- requirement: "on the host (workerd does not parse TypeScript)",
1079
- remedy: "upgrade Node.js"
1080
- });
1102
+ async #prepareVirtualModule(key, module) {
1103
+ const format = virtualModuleFormat(key, module);
1104
+ const source = typeof module === "string" ? module : module.source;
1105
+ switch (format) {
1106
+ case "module-typescript":
1107
+ case "commonjs-typescript": return {
1108
+ source: stripVirtualTypeScript(key, source, await _getStripTypeScriptTypes(), {
1109
+ requirement: "on the host (workerd does not parse TypeScript)",
1110
+ remedy: "upgrade Node.js"
1111
+ }),
1112
+ format: format === "module-typescript" ? "module" : "commonjs"
1113
+ };
1114
+ case "jsx":
1115
+ case "tsx": throw unsupportedVirtualJSXError(key, format, "workerd");
1116
+ }
1117
+ return {
1118
+ source,
1119
+ format
1120
+ };
1081
1121
  }
1082
1122
  async #initAsync() {
1083
- const { Miniflare, supportedCompatibilityDate } = await this.#resolveMiniflare();
1123
+ const miniflare = await this.#resolveMiniflare();
1124
+ const supportedCompatibilityDate = await resolveSupportedCompatibilityDate(miniflare);
1084
1125
  const entryPath = this._data?.entry;
1085
- const virtual = await this.#prepareVirtualModules();
1126
+ const initialVirtual = await this.#prepareVirtualModules();
1127
+ const virtual = createMiniflareVirtualModules({ ...initialVirtual });
1086
1128
  this.#virtual = virtual;
1087
1129
  const { options: wranglerOptions, configFile: wranglerConfigFile } = await loadWranglerConfig({
1088
1130
  wrangler: this.#wrangler,
@@ -1118,14 +1160,15 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1118
1160
  ...uValue
1119
1161
  };
1120
1162
  }
1163
+ const ipc = { runner: this };
1121
1164
  if (entryPath && !options.script && !options.scriptPath) {
1122
- const entryIsVirtual = isVirtualSpecifier(entryPath, virtual);
1123
- const resolvedEntry = entryIsVirtual ? entryPath : resolve(entryPath);
1165
+ const entryKey = virtual.keyOf(entryPath) ?? virtual.keyOf(resolve(entryPath));
1166
+ const entryIsVirtual = entryKey !== void 0;
1167
+ const resolvedEntry = entryIsVirtual ? virtualKeyPath(entryKey) ?? entryKey : resolve(entryPath);
1124
1168
  const entryBase = isAbsolute(resolvedEntry) ? resolvedEntry : resolve("__env_runner_virtual_entry__.mjs");
1125
1169
  const entryDir = dirname(entryBase);
1126
- const entrySource = entryIsVirtual ? virtual[entryPath] : _tryReadFile(resolvedEntry);
1127
- const detectedExports = this.#exports === false || this.#exports === void 0 ? [] : detectExportedClasses(entrySource, typeof this.#exports === "object" ? this.#exports : {});
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.`);
1170
+ const entrySource = entryIsVirtual ? virtualModuleCodeSource(entryKey, virtual.sources[entryKey]) : _tryReadFile(resolvedEntry);
1171
+ const detectedExports = this.#exports === false || typeof this.#exports === "string" ? [] : detectExportedClasses(entrySource, typeof this.#exports === "object" ? this.#exports : {});
1129
1172
  if (detectedExports.length > 0) {
1130
1173
  const existingDOs = isPlainObject(options.durableObjects) ? options.durableObjects : {};
1131
1174
  const boundClasses = new Set(Object.values(existingDOs).map((b) => typeof b === "string" ? b : isPlainObject(b) && !b.scriptName ? b.className : void 0));
@@ -1136,13 +1179,23 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1136
1179
  }
1137
1180
  options.durableObjects = autoDOs;
1138
1181
  }
1139
- options.script = generateWrapper(resolvedEntry, {
1182
+ const script = generateWrapper(entryIsVirtual ? toWorkerdPath(resolvedEntry) : resolvedEntry, {
1140
1183
  dynamicOnly: true,
1141
1184
  captureErrors: this.#captureErrors,
1142
- exports: detectedExports,
1185
+ exports: typeof this.#exports === "string" ? this.#exports : detectedExports,
1143
1186
  nodeCompat: !options.compatibilityFlags.includes("no_nodejs_compat")
1144
1187
  });
1145
- options.scriptPath = entryDir + "/__env_runner_wrapper.mjs";
1188
+ const scriptPath = entryDir + "/__env_runner_wrapper.mjs";
1189
+ const skipLocator = typeof this.#exports === "string" || entryIsVirtual && detectedExports.length > 0;
1190
+ if (skipLocator) options.modules = [{
1191
+ type: "ESModule",
1192
+ path: scriptPath,
1193
+ contents: script
1194
+ }];
1195
+ else {
1196
+ options.script = script;
1197
+ options.scriptPath = scriptPath;
1198
+ }
1146
1199
  if (!options.modulesRoot) options.modulesRoot = "/";
1147
1200
  options.unsafeEvalBinding = UNSAFE_EVAL_BINDING;
1148
1201
  options.serviceBindings = {
@@ -1150,12 +1203,12 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1150
1203
  [IPC_BINDING]: async (request) => {
1151
1204
  try {
1152
1205
  const message = await request.json();
1153
- this._handleMessage(message);
1206
+ ipc.runner._handleMessage(message);
1154
1207
  } catch {}
1155
1208
  return new Response(null, { status: 204 });
1156
1209
  }
1157
1210
  };
1158
- if (this.#transformRequest && !options.modulesRules) options.modulesRules = [{
1211
+ if (this.#transformRequest && !options.modulesRules && !miniflare.convertV4MiniflareOptions && !skipLocator) options.modulesRules = [{
1159
1212
  type: "ESModule",
1160
1213
  include: [
1161
1214
  "**/*.ts",
@@ -1167,10 +1220,23 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1167
1220
  if (!options.unsafeModuleFallbackService) {
1168
1221
  const _require = createRequire(entryBase);
1169
1222
  const _virtual = virtual;
1170
- const _virtualVersions = this.#virtualVersions;
1171
1223
  const _transformRequest = this.#transformRequest;
1172
1224
  const _exportConditions = this.#exportConditions;
1173
- const _applyVirtualVersions = (code) => applyVirtualVersions(code, _virtualVersions);
1225
+ const _applyVirtualVersions = (code, modulePath) => applyVirtualVersions(code, _virtual.versions, (specifier) => _virtual.versionedKeyOf(specifier, modulePath));
1226
+ const _serveVirtual = (module, modulePath) => {
1227
+ const { source } = module;
1228
+ switch (module.format) {
1229
+ case "module": return { esModule: _applyVirtualVersions(source, modulePath) };
1230
+ case "commonjs": return {
1231
+ commonJsModule: source,
1232
+ namedExports: commonJSExports(source).filter((e) => e !== "default")
1233
+ };
1234
+ case "json": return { json: source };
1235
+ case "text": return { text: source };
1236
+ case "bytes": return { esModule: virtualModuleCode("bytes", source) };
1237
+ case "wasm": return { wasm: Array.from(source) };
1238
+ }
1239
+ };
1174
1240
  options.unsafeUseModuleFallbackService = true;
1175
1241
  const modulePathMap = /* @__PURE__ */ new Map();
1176
1242
  const _lexersReady = Promise.all([ensureCjsLexer(), init$1]);
@@ -1183,24 +1249,30 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1183
1249
  if (!specifier) return new Response(null, { status: 404 });
1184
1250
  const cleanSpecifier = specifier.split("?")[0] || specifier;
1185
1251
  const cleanRaw = rawSpecifier?.split("?")[0];
1186
- if (_virtual) {
1187
- const bareSpecifier = cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier;
1188
- const virtualKey = [
1189
- cleanRaw,
1190
- cleanSpecifier,
1191
- bareSpecifier
1192
- ].find((key) => key !== void 0 && Object.hasOwn(_virtual, key));
1193
- if (virtualKey !== void 0) {
1194
- const name = bareSpecifier + (specifier.includes("?") ? specifier.slice(specifier.indexOf("?")) : "");
1195
- const source = _virtual[virtualKey];
1196
- return virtualModuleFormat(virtualKey) === "json" ? Response.json({
1197
- name,
1198
- json: source
1199
- }) : Response.json({
1200
- name,
1201
- esModule: _applyVirtualVersions(source)
1252
+ const bareSpecifier = cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier;
1253
+ const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
1254
+ const referrerPath = modulePathMap.get(referrerKey);
1255
+ const virtualKey = [
1256
+ cleanRaw,
1257
+ cleanSpecifier,
1258
+ bareSpecifier
1259
+ ].find((key) => key !== void 0 && Object.hasOwn(_virtual.sources, key)) ?? (cleanRaw ? _virtual.keyOf(cleanRaw, referrerPath) : void 0) ?? _virtual.keyOf(cleanSpecifier);
1260
+ if (virtualKey !== void 0) {
1261
+ const query = specifier.includes("?") ? specifier.slice(specifier.indexOf("?")) : "";
1262
+ const keyPath = virtualKeyPath(virtualKey);
1263
+ if (keyPath && cleanRaw?.startsWith("file:") && cleanSpecifier !== keyPath && cleanSpecifier.includes("file:")) {
1264
+ const location = Buffer.from(toWorkerdPath(keyPath) + query, "utf8").toString("latin1");
1265
+ return new Response(null, {
1266
+ status: 301,
1267
+ headers: { location }
1202
1268
  });
1203
1269
  }
1270
+ const name = bareSpecifier + query;
1271
+ if (keyPath) modulePathMap.set(name, keyPath);
1272
+ return Response.json({
1273
+ name,
1274
+ ..._serveVirtual(_virtual.sources[virtualKey], keyPath)
1275
+ });
1204
1276
  }
1205
1277
  let resolvedPath;
1206
1278
  const fileUrlRaw = cleanRaw || cleanSpecifier;
@@ -1209,9 +1281,8 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1209
1281
  } catch {
1210
1282
  return new Response(null, { status: 404 });
1211
1283
  }
1212
- else if (cleanRaw && !cleanRaw.startsWith(".") && !cleanRaw.startsWith("/")) {
1213
- const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
1214
- const referrerReal = modulePathMap.get(referrerKey);
1284
+ else if (cleanRaw && !cleanRaw.startsWith(".") && !cleanRaw.startsWith("/") && !isAbsolute(cleanRaw)) {
1285
+ const referrerReal = referrerPath;
1215
1286
  const contextRequire = referrerReal ? createRequire(referrerReal) : _require;
1216
1287
  if (cleanRaw.startsWith("cloudflare:")) return new Response(null, { status: 404 });
1217
1288
  if (cleanRaw.startsWith("node:")) {
@@ -1235,11 +1306,11 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1235
1306
  });
1236
1307
  }
1237
1308
  } else {
1238
- const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
1239
- const referrerReal = modulePathMap.get(referrerKey) || (referrer.startsWith("/") ? referrer : "/" + referrer);
1309
+ const referrerReal = referrerPath || (referrer.startsWith("/") ? referrer : "/" + referrer);
1240
1310
  const referrerDir = dirname(referrerReal);
1241
1311
  const raw = cleanRaw || cleanSpecifier;
1242
1312
  if (raw.startsWith(".")) resolvedPath = resolve(referrerDir, raw);
1313
+ else if (cleanRaw && isAbsolute(cleanRaw) && !cleanRaw.startsWith("/")) resolvedPath = cleanRaw;
1243
1314
  else if (cleanSpecifier.startsWith("/")) resolvedPath = cleanSpecifier;
1244
1315
  else try {
1245
1316
  resolvedPath = _require.resolve(raw);
@@ -1255,7 +1326,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1255
1326
  modulePathMap.set(name, resolvedPath);
1256
1327
  return Response.json({
1257
1328
  name,
1258
- esModule: _applyVirtualVersions(result.code)
1329
+ esModule: _applyVirtualVersions(result.code, resolvedPath)
1259
1330
  });
1260
1331
  }
1261
1332
  } catch {}
@@ -1264,7 +1335,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1264
1335
  modulePathMap.set(name, resolvedPath);
1265
1336
  if (resolvedPath.endsWith(".mjs") || !resolvedPath.endsWith(".cjs") && /\b(import\s|import\(|export\s|export\{|import\.meta\b)/.test(contents)) return Response.json({
1266
1337
  name,
1267
- esModule: _applyVirtualVersions(contents)
1338
+ esModule: _applyVirtualVersions(contents, resolvedPath)
1268
1339
  });
1269
1340
  const cjsSuffix = "?__cjs";
1270
1341
  if (specifier.endsWith(cjsSuffix)) return Response.json({
@@ -1286,31 +1357,35 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
1286
1357
  this.#cacheKey = computeCacheKey(entryPath, {
1287
1358
  ...this.#miniflareOptions,
1288
1359
  _exportConditions: this.#exportConditions,
1289
- _virtual: virtual
1360
+ _exports: this.#exports,
1361
+ _virtual: encodeVirtualModules(initialVirtual)
1290
1362
  });
1291
1363
  const cached = _miniflareCache.get(this.#cacheKey);
1292
1364
  if (cached) {
1293
1365
  this.#miniflare = cached.mf;
1294
1366
  cached.refCount++;
1295
1367
  this.#cacheEntry = cached;
1368
+ cached.ipc.runner = this;
1296
1369
  this.#virtual = cached.virtual;
1297
- this.#virtualVersions = cached.versions;
1298
1370
  }
1299
1371
  }
1300
1372
  if (!this.#miniflare) {
1301
- this.#miniflare = new Miniflare(options);
1373
+ this.#miniflare = new miniflare.Miniflare(toMiniflareOptions(miniflare, options));
1302
1374
  await this.#miniflare.ready;
1303
1375
  if (this.#persistent && this.#cacheKey) {
1304
1376
  this.#cacheEntry = {
1305
1377
  mf: this.#miniflare,
1306
1378
  refCount: 1,
1307
1379
  virtual,
1308
- versions: this.#virtualVersions
1380
+ ipc
1309
1381
  };
1310
1382
  _miniflareCache.set(this.#cacheKey, this.#cacheEntry);
1311
1383
  }
1312
1384
  }
1313
- const initRes = await this.#miniflare.dispatchFetch("http://localhost/__env_runner_ipc", { headers: { upgrade: "websocket" } });
1385
+ const loadRes = await this.#miniflare.dispatchFetch("http://localhost" + IPC_PATH);
1386
+ const loadBody = await loadRes.text().catch(() => "");
1387
+ if (!loadRes.ok) throw new Error(`Failed to establish WebSocket IPC channel (${loadRes.status}: ${loadBody})`);
1388
+ const initRes = await this.#miniflare.dispatchFetch("http://localhost" + IPC_PATH, { headers: { upgrade: "websocket" } });
1314
1389
  const ws = initRes.webSocket;
1315
1390
  if (!ws) {
1316
1391
  const body = await initRes.text().catch(() => "");
@@ -1339,6 +1414,31 @@ function detectExportedClasses(entrySource, explicit) {
1339
1414
  }
1340
1415
  return [...names];
1341
1416
  }
1417
+ function toMiniflareOptions(miniflare, options) {
1418
+ if (!miniflare.convertV4MiniflareOptions) return options;
1419
+ const { defaultPersistRoot, ...rest } = options;
1420
+ return miniflare.convertV4MiniflareOptions({
1421
+ resourcePersistencePath: defaultPersistRoot,
1422
+ ...rest
1423
+ });
1424
+ }
1425
+ async function resolveSupportedCompatibilityDate(miniflare) {
1426
+ if (typeof miniflare.supportedCompatibilityDate === "string") return miniflare.supportedCompatibilityDate;
1427
+ try {
1428
+ const from = [process.cwd() + "/", import.meta.url];
1429
+ const miniflarePath = resolveModulePath("miniflare", {
1430
+ from,
1431
+ try: true
1432
+ });
1433
+ const workerdPath = resolveModulePath("workerd", { from: miniflarePath || from });
1434
+ const { compatibilityDate } = await import(pathToFileURL(workerdPath).href);
1435
+ if (!_isDateString(compatibilityDate)) return;
1436
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1437
+ return compatibilityDate > today ? today : compatibilityDate;
1438
+ } catch {
1439
+ return;
1440
+ }
1441
+ }
1342
1442
  function resolveCompatibilityDate(candidates, supported) {
1343
1443
  const date = candidates.find((d) => typeof d === "string" && d) ?? supported;
1344
1444
  if (date && supported && _isDateString(date) && _isDateString(supported) && date > supported) {
@@ -1357,7 +1457,7 @@ function _isDateString(value) {
1357
1457
  return /^\d{4}-\d{2}-\d{2}$/.test(value);
1358
1458
  }
1359
1459
  function hasUserPersistOptions(options) {
1360
- return Object.keys(options).some((key) => key === "defaultPersistRoot" || key.endsWith("Persist"));
1460
+ return Object.keys(options).some((key) => key === "defaultPersistRoot" || key === "resourcePersistencePath" || key.endsWith("Persist"));
1361
1461
  }
1362
1462
  function wranglerPersistRoot(configFile, explicitPath) {
1363
1463
  const file = configFile ?? (explicitPath ? resolve(explicitPath) : void 0);
@@ -1378,26 +1478,93 @@ function computeCacheKey(entryPath, opts) {
1378
1478
  for (const [k, v] of Object.entries(opts)) if (typeof v !== "function") serializableOpts[k] = v;
1379
1479
  return `${resolve(entryPath)}::${JSON.stringify(serializableOpts)}`;
1380
1480
  }
1381
- function applyVirtualVersions(code, versions) {
1382
- if (versions.size === 0) return code;
1481
+ const _isWin = process.platform === "win32";
1482
+ function virtualKeyPath(key) {
1483
+ if (_isWin && /^\/[A-Za-z]:\//.test(key)) key = key.slice(1);
1484
+ if (key.startsWith("file:")) try {
1485
+ return fileURLToPath(key);
1486
+ } catch {
1487
+ return;
1488
+ }
1489
+ return isAbsolute(key) ? resolve(key) : void 0;
1490
+ }
1491
+ function toWorkerdPath(path) {
1492
+ return _isWin && /^[A-Za-z]:[\\/]/.test(path) ? "/" + path.replaceAll("\\", "/") : path;
1493
+ }
1494
+ function createMiniflareVirtualModules(sources) {
1495
+ return refreshVirtualKeyResolvers({
1496
+ sources,
1497
+ removed: /* @__PURE__ */ new Set(),
1498
+ versions: /* @__PURE__ */ new Map(),
1499
+ version: 0
1500
+ });
1501
+ }
1502
+ function refreshVirtualKeyResolvers(virtual) {
1503
+ const keys = Object.keys(virtual.sources);
1504
+ virtual.keyOf = createVirtualKeyResolver(keys);
1505
+ virtual.versionedKeyOf = virtual.removed.size > 0 ? createVirtualKeyResolver([...keys, ...virtual.removed]) : virtual.keyOf;
1506
+ return virtual;
1507
+ }
1508
+ function createVirtualKeyResolver(keys) {
1509
+ const verbatim = new Set(keys);
1510
+ const pathKeys = /* @__PURE__ */ new Map();
1511
+ for (const key of verbatim) {
1512
+ const path = virtualKeyPath(key);
1513
+ if (path !== void 0) pathKeys.set(path, key);
1514
+ }
1515
+ return (specifier, importerPath) => {
1516
+ const clean = specifier.split("?")[0];
1517
+ if (verbatim.has(clean)) return clean;
1518
+ if (pathKeys.size === 0) return;
1519
+ const path = /^\.\.?\//.test(clean) ? importerPath && resolve(dirname(importerPath), clean) : virtualKeyPath(clean);
1520
+ return path ? pathKeys.get(path) : void 0;
1521
+ };
1522
+ }
1523
+ function virtualImporters(virtual, keyOf) {
1524
+ const importers = /* @__PURE__ */ new Map();
1525
+ for (const [importer, { source, format }] of Object.entries(virtual)) {
1526
+ if (format !== "module") continue;
1527
+ const importerPath = virtualKeyPath(importer);
1528
+ for (const { specifier } of versionableImports(source) ?? []) {
1529
+ const key = keyOf(specifier, importerPath);
1530
+ if (key !== void 0 && key !== importer) {
1531
+ let set = importers.get(key);
1532
+ if (!set) importers.set(key, set = /* @__PURE__ */ new Set());
1533
+ set.add(importer);
1534
+ }
1535
+ }
1536
+ }
1537
+ return importers;
1538
+ }
1539
+ function versionableImports(code) {
1383
1540
  let imports;
1384
1541
  try {
1385
1542
  [imports] = parse$1(code);
1386
1543
  } catch {
1387
- return code;
1544
+ return;
1545
+ }
1546
+ const result = [];
1547
+ for (const imp of imports) {
1548
+ if (typeof imp.specifier !== "string" || imp.type === "dynamic" && imp.glob) continue;
1549
+ result.push({
1550
+ specifier: imp.specifier,
1551
+ end: imp.type === "dynamic" ? imp.end - 1 : imp.end
1552
+ });
1388
1553
  }
1554
+ return result;
1555
+ }
1556
+ function applyVirtualVersions(code, versions, keyOf) {
1557
+ if (versions.size === 0) return code;
1558
+ const imports = versionableImports(code);
1559
+ if (!imports) return code;
1389
1560
  let out = "";
1390
1561
  let last = 0;
1391
- for (const imp of imports) {
1392
- if (imp.type === "import-meta") continue;
1393
- const dynamic = imp.type === "dynamic";
1394
- if (dynamic && imp.glob) continue;
1395
- const specifier = imp.specifier;
1396
- const version = specifier === void 0 ? void 0 : versions.get(specifier);
1562
+ for (const { specifier, end } of imports) {
1563
+ const key = keyOf(specifier);
1564
+ const version = key === void 0 ? void 0 : versions.get(key);
1397
1565
  if (!version) continue;
1398
- const versioned = `${specifier}?v=${version}`;
1399
- out += code.slice(last, imp.start) + (dynamic ? JSON.stringify(versioned) : versioned);
1400
- last = imp.end;
1566
+ out += code.slice(last, end) + (specifier.includes("?") ? "&" : "?") + `v=${version}`;
1567
+ last = end;
1401
1568
  }
1402
1569
  return out + code.slice(last);
1403
1570
  }
@@ -1411,12 +1578,15 @@ function ensureCjsLexer() {
1411
1578
  if (!_cjsLexerReady) _cjsLexerReady = init();
1412
1579
  return _cjsLexerReady;
1413
1580
  }
1414
- function createCjsEsmShim(cjsSpecifier, contents) {
1415
- let namedExports = [];
1581
+ function commonJSExports(contents) {
1416
1582
  try {
1417
- const { exports } = parse(contents);
1418
- namedExports = exports.filter((e) => e !== "default" && e !== "__esModule");
1419
- } catch {}
1583
+ return parse(contents).exports;
1584
+ } catch {
1585
+ return [];
1586
+ }
1587
+ }
1588
+ function createCjsEsmShim(cjsSpecifier, contents) {
1589
+ const namedExports = commonJSExports(contents).filter((e) => e !== "default" && e !== "__esModule");
1420
1590
  let shim = `import __cjs_mod__ from ${JSON.stringify(cjsSpecifier)};\nexport default __cjs_mod__;\n`;
1421
1591
  for (const name of namedExports) shim += `export var ${name} = __cjs_mod__["${name}"];\n`;
1422
1592
  return shim;
@@ -1,5 +1,4 @@
1
- import { BaseEnvRunner } from "./common-base-runner.mjs";
2
- import { hostEnv } from "./common-host-env.mjs";
1
+ import { BaseEnvRunner, hostEnv } from "./common-base-runner.mjs";
3
2
  import { fileURLToPath } from "node:url";
4
3
  import { existsSync } from "node:fs";
5
4
  import { Worker } from "node:worker_threads";