env-runner 0.1.9 → 0.1.10

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.
Files changed (43) hide show
  1. package/README.md +112 -15
  2. package/dist/_chunks/base-runner.d.mts +46 -3
  3. package/dist/_chunks/base-runner.mjs +28 -1
  4. package/dist/_chunks/libs/cjs-module-lexer.mjs +1 -1
  5. package/dist/_chunks/runner.d.mts +3 -3
  6. package/dist/_chunks/runner.mjs +6 -3
  7. package/dist/_chunks/runner2.d.mts +3 -3
  8. package/dist/_chunks/runner2.mjs +66 -16
  9. package/dist/_chunks/runner3.d.mts +3 -3
  10. package/dist/_chunks/runner3.mjs +3 -3
  11. package/dist/_chunks/runner4.d.mts +4 -4
  12. package/dist/_chunks/runner4.mjs +2 -2
  13. package/dist/_chunks/runner5.d.mts +4 -4
  14. package/dist/_chunks/runner5.mjs +2 -2
  15. package/dist/_chunks/server.mjs +25 -9
  16. package/dist/_chunks/types.d.mts +4 -2
  17. package/dist/_chunks/virtual-loader.mjs +39 -0
  18. package/dist/_chunks/worker-utils.mjs +86 -9
  19. package/dist/cli.mjs +1 -1
  20. package/dist/index.d.mts +23 -11
  21. package/dist/index.mjs +6 -6
  22. package/dist/runners/bun-process/runner.d.mts +2 -2
  23. package/dist/runners/bun-process/runner.mjs +12 -2
  24. package/dist/runners/bun-process/worker.mjs +19 -3
  25. package/dist/runners/deno-process/runner.d.mts +2 -2
  26. package/dist/runners/deno-process/runner.mjs +1 -1
  27. package/dist/runners/deno-process/worker.mjs +18 -3
  28. package/dist/runners/miniflare/runner.d.mts +2 -2
  29. package/dist/runners/miniflare/runner.mjs +1 -1
  30. package/dist/runners/netlify/runner.d.mts +2 -2
  31. package/dist/runners/netlify/runner.mjs +1 -1
  32. package/dist/runners/node-process/runner.d.mts +2 -2
  33. package/dist/runners/node-process/runner.mjs +4 -2
  34. package/dist/runners/node-process/worker.mjs +19 -3
  35. package/dist/runners/node-worker/runner.d.mts +2 -2
  36. package/dist/runners/node-worker/runner.mjs +1 -1
  37. package/dist/runners/node-worker/worker.mjs +18 -3
  38. package/dist/runners/self/runner.d.mts +2 -2
  39. package/dist/runners/self/runner.mjs +2 -2
  40. package/dist/runners/vercel/runner.d.mts +2 -2
  41. package/dist/runners/vercel/runner.mjs +1 -1
  42. package/dist/vite.d.mts +1 -1
  43. package/package.json +11 -11
package/README.md CHANGED
@@ -50,7 +50,7 @@ import { serve } from "srvx";
50
50
  import { EnvServer } from "env-runner";
51
51
 
52
52
  const envServer = new EnvServer({
53
- runner: "node-process",
53
+ runner: "node-process", // optional, defaults to "node-worker"
54
54
  entry: "./app.ts",
55
55
  watch: true,
56
56
  watchPaths: ["./src"],
@@ -64,6 +64,7 @@ envServer.onReload(() => {
64
64
  console.log("Reloaded!");
65
65
  });
66
66
 
67
+ // Optional — the server auto-starts on first fetch()
67
68
  await envServer.start();
68
69
 
69
70
  // Use with any HTTP server
@@ -79,7 +80,7 @@ Proxy manager for hot-reload with message queueing and listener forwarding:
79
80
  ```ts
80
81
  import { RunnerManager, NodeProcessEnvRunner } from "env-runner";
81
82
 
82
- const manager = new RunnerManager();
83
+ await using manager = new RunnerManager();
83
84
 
84
85
  manager.onReady((_runner, address) => {
85
86
  console.log("Ready:", address);
@@ -106,9 +107,11 @@ await manager.reload(newRunner); // old runner is closed automatically
106
107
  manager.sendMessage({ type: "config", value: 42 });
107
108
  manager.onMessage((msg) => console.log("From worker:", msg));
108
109
 
109
- await manager.close();
110
+ // manager.close() is awaited automatically at the end of the scope (`await using`)
110
111
  ```
111
112
 
113
+ All runners, `RunnerManager`, and `EnvServer` implement `AsyncDisposable`, so they can be auto-closed with [explicit resource management](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/await_using) (`await using`) — or closed manually with `await runner.close()`.
114
+
112
115
  ### Runners
113
116
 
114
117
  Use runners directly for lower-level control:
@@ -127,7 +130,7 @@ import { NetlifyEnvRunner } from "env-runner/runners/netlify";
127
130
  All runners implement the [`EnvRunner`](./src/types.ts) interface:
128
131
 
129
132
  ```ts
130
- const runner = new NodeProcessEnvRunner({
133
+ await using runner = new NodeProcessEnvRunner({
131
134
  name: "my-app",
132
135
  data: { entry: "./app.ts" },
133
136
  hooks: {
@@ -156,8 +159,8 @@ const result = await runner.rpc<string>("transformHTML", "<html>...</html>");
156
159
  // Hot-reload entry module without restarting the worker
157
160
  await runner.reloadModule();
158
161
 
159
- // Graceful shutdown
160
- await runner.close();
162
+ // Graceful shutdown happens automatically at the end of the scope
163
+ // (`await using`) — or call `await runner.close()` explicitly
161
164
  ```
162
165
 
163
166
  **Available runners:**
@@ -173,6 +176,98 @@ await runner.close();
173
176
  | `VercelEnvRunner` | Worker thread (Vercel context) | `workerData` / `parentPort` |
174
177
  | `NetlifyEnvRunner` | Worker thread (Netlify context) | `workerData` / `parentPort` |
175
178
 
179
+ #### Virtual Modules
180
+
181
+ The Node.js runners (`NodeWorkerEnvRunner`, `NodeProcessEnvRunner`, and the runners built on top of them), `BunProcessEnvRunner`, `DenoProcessEnvRunner` (Deno >= 2.x), and `MiniflareEnvRunner` can serve **virtual modules** from an in-memory `specifier => source` map passed via `data.virtual`. The entry (and its dependencies) can then `import` them as if they were real files:
182
+
183
+ ```ts
184
+ import { NodeWorkerEnvRunner } from "env-runner/runners/node-worker";
185
+
186
+ await using runner = new NodeWorkerEnvRunner({
187
+ name: "my-app",
188
+ data: {
189
+ entry: "./app.ts",
190
+ virtual: {
191
+ "#config": `export const apiBase = "https://api.example.com";`,
192
+ "#banner": `export default "Hello from a virtual module!";`,
193
+ },
194
+ },
195
+ });
196
+ ```
197
+
198
+ ```ts
199
+ // app.ts
200
+ import banner from "#banner";
201
+ import { apiBase } from "#config";
202
+
203
+ export default {
204
+ fetch: () => new Response(`${banner} (${apiBase})`),
205
+ };
206
+ ```
207
+
208
+ The **entry itself can be virtual** — set `data.entry` to one of the `data.virtual` keys to run an entry whose source lives in memory (it may import other virtual modules too):
209
+
210
+ ```ts
211
+ await using runner = new NodeWorkerEnvRunner({
212
+ name: "my-app",
213
+ data: {
214
+ entry: "#entry",
215
+ virtual: {
216
+ "#entry": `import { body } from "#dep";
217
+ export default { fetch: () => new Response(body) };`,
218
+ "#dep": `export const body = "Hello from a virtual entry!";`,
219
+ },
220
+ },
221
+ });
222
+ ```
223
+
224
+ Each source may also be a **factory** `() => string | Promise<string>` instead of a literal string — useful for lazily computed or asynchronously loaded sources:
225
+
226
+ ```ts
227
+ await using runner = new NodeWorkerEnvRunner({
228
+ name: "my-app",
229
+ data: {
230
+ entry: "./app.ts",
231
+ virtual: {
232
+ "#config": () => `export const apiBase = ${JSON.stringify(getApiBase())};`,
233
+ "#schema": async () => `export default ${await loadSchemaJson()};`,
234
+ },
235
+ },
236
+ });
237
+ ```
238
+
239
+ Factories are invoked once on the host (before the worker is spawned), so the worker always receives plain strings — functions can't cross the `workerData`/`JSON` boundary, and Node's synchronous load hook can't await. For the same reason, **all** factories are resolved eagerly at startup (in parallel), not lazily on first import — so keep them cheap, or use plain strings for sources that don't need computation. Maps containing only strings skip this step entirely.
240
+
241
+ The module format is derived from the specifier extension: `.ts`/`.mts` sources are served as **TypeScript** and `.json` sources as **JSON modules**; everything else is plain JavaScript ESM:
242
+
243
+ ```ts
244
+ await using runner = new NodeWorkerEnvRunner({
245
+ name: "my-app",
246
+ data: {
247
+ entry: "#entry.ts",
248
+ virtual: {
249
+ "#entry.ts": `
250
+ import { getGreeting } from "#util.ts";
251
+ import config from "#config.json";
252
+ const handler: () => Response = () => new Response(getGreeting(config.name));
253
+ export default { fetch: handler };
254
+ `,
255
+ "#util.ts": `export function getGreeting(name: string): string {
256
+ return \`Hello, \${name}!\`;
257
+ }`,
258
+ "#config.json": JSON.stringify({ name: "virtual" }),
259
+ },
260
+ },
261
+ });
262
+ ```
263
+
264
+ - **TypeScript** is type-stripped by Node's native [type stripping](https://nodejs.org/api/typescript.html#type-stripping) (Node.js >= 22.18 / 23.6 — erasable syntax only) and by Bun's `ts` loader. On Deno, custom load hooks bypass its native type stripping, so sources are pre-stripped with [`module.stripTypeScriptTypes`](https://docs.deno.com/api/node/module/~/Module.stripTypeScriptTypes) (Deno >= 2.8.2); on older Deno without it, virtual `.ts`/`.mts` sources **throw at registration** — pass pre-transpiled JavaScript instead. On miniflare, sources are likewise pre-stripped with `module.stripTypeScriptTypes` on the host (workerd does not parse TypeScript).
265
+ - **JSON** sources expose the parsed value as the default export on all runtimes. The `with { type: "json" }` import attribute is optional on Node.js and Bun; on Deno and miniflare it must be **omitted** (static imports carrying an import attribute bypass `registerHooks` resolution on Deno, and workerd rejects import attributes outright).
266
+
267
+ Virtual modules are registered inside the worker, before the entry is imported. On Node.js (>= 22.15 / 23.5) and Deno (>= 2.x) this uses [ESM customization hooks](https://nodejs.org/api/module.html#moduleregisterhooksoptions) (`module.registerHooks`); on Bun (which does not implement `registerHooks`) it uses [`Bun.plugin()`](https://bun.com/docs/runtime/plugins) virtual modules instead. The source string is treated as an ES module, and virtual specifiers (including a virtual entry) resolve across `reloadModule()`. On runtimes supporting neither mechanism, a warning is logged and registration is skipped. When the worker shuts down gracefully the registration is unregistered again (the `registerHooks` registration is deregistered; on Bun, which has no plugin-removal API, the in-memory source map is detached so fresh loads and reloads stop resolving).
268
+
269
+ On `MiniflareEnvRunner` there is no in-worker registration: the runner's module fallback service serves virtual specifiers to workerd directly (taking precedence over disk files and the `transformRequest` pipeline, so a virtual key overrides a real file with the same path). One limitation: named `exports` (Durable Objects / WorkerEntrypoints) cannot be combined with a **virtual entry** — the wrapper would need a static re-export that miniflare cannot resolve at startup — and the runner fails fast with a clear error in that case.
270
+
176
271
  #### Miniflare Runner
177
272
 
178
273
  Run your app in the Cloudflare Workers runtime using [miniflare](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare):
@@ -184,7 +279,7 @@ npm install miniflare
184
279
  ```ts
185
280
  import { MiniflareEnvRunner } from "env-runner/runners/miniflare";
186
281
 
187
- const runner = new MiniflareEnvRunner({
282
+ await using runner = new MiniflareEnvRunner({
188
283
  name: "my-worker",
189
284
  data: { entry: "./worker.ts" },
190
285
  miniflareOptions: {
@@ -194,7 +289,6 @@ const runner = new MiniflareEnvRunner({
194
289
  });
195
290
 
196
291
  const response = await runner.fetch("http://localhost/api");
197
- await runner.close();
198
292
  ```
199
293
 
200
294
  The `miniflareOptions` object is passed directly to the [Miniflare constructor](https://developers.cloudflare.com/workers/testing/miniflare/) — you can configure bindings, KV, D1, Durable Objects, and any other Miniflare option.
@@ -206,7 +300,7 @@ Pass a `transformRequest` callback to route module resolution through Vite's (or
206
300
  ```ts
207
301
  import { MiniflareEnvRunner } from "env-runner/runners/miniflare";
208
302
 
209
- const runner = new MiniflareEnvRunner({
303
+ await using runner = new MiniflareEnvRunner({
210
304
  name: "my-worker",
211
305
  data: { entry: "./worker.ts" },
212
306
  // Route module resolution through Vite's transform pipeline
@@ -245,7 +339,7 @@ export default {
245
339
  To explicitly declare exports or override auto-detection:
246
340
 
247
341
  ```ts
248
- const runner = new MiniflareEnvRunner({
342
+ await using runner = new MiniflareEnvRunner({
249
343
  name: "my-worker",
250
344
  data: { entry: "./worker.ts" },
251
345
  // Explicit exports (merged with auto-detected ones)
@@ -300,7 +394,7 @@ Simulates a Vercel deployment environment with automatic header injection (`x-ve
300
394
  ```ts
301
395
  import { VercelEnvRunner } from "env-runner/runners/vercel";
302
396
 
303
- const runner = new VercelEnvRunner({
397
+ await using runner = new VercelEnvRunner({
304
398
  name: "my-app",
305
399
  data: { entry: "./app.ts" },
306
400
  });
@@ -313,7 +407,7 @@ Simulates a Netlify deployment environment with automatic header injection (`x-n
313
407
  ```ts
314
408
  import { NetlifyEnvRunner } from "env-runner/runners/netlify";
315
409
 
316
- const runner = new NetlifyEnvRunner({
410
+ await using runner = new NetlifyEnvRunner({
317
411
  name: "my-app",
318
412
  data: { entry: "./app.ts" },
319
413
  });
@@ -343,7 +437,10 @@ const env = new DevEnvironment("ssr", config, { hot: true, transport });
343
437
  import { createViteTransport } from "env-runner/vite";
344
438
 
345
439
  const transport = createViteTransport(sendMessage, onMessage, "ssr");
346
- const runner = new ModuleRunner({ transport, sourcemapInterceptor: "prepareStackTrace" });
440
+ const runner = new ModuleRunner({
441
+ transport,
442
+ sourcemapInterceptor: "prepareStackTrace",
443
+ });
347
444
  ```
348
445
 
349
446
  Messages are namespaced by environment name, so multiple Vite environments can share a single runner's IPC channel.
@@ -389,7 +486,7 @@ You can also use `loadRunner()` to dynamically load a runner by name:
389
486
  ```ts
390
487
  import { loadRunner } from "env-runner";
391
488
 
392
- const runner = await loadRunner("node-worker", {
489
+ await using runner = await loadRunner("node-worker", {
393
490
  name: "my-app",
394
491
  data: { entry: "./app.ts" },
395
492
  });
@@ -448,7 +545,7 @@ The built-in worker automatically:
448
545
  For advanced use cases, you can provide a custom worker entry:
449
546
 
450
547
  ```ts
451
- const runner = new NodeProcessEnvRunner({
548
+ await using runner = new NodeProcessEnvRunner({
452
549
  name: "my-app",
453
550
  workerEntry: "/path/to/custom-worker.ts",
454
551
  data: { entry: "./app.ts" },
@@ -1,11 +1,38 @@
1
- import { a as RunnerMessageListener, l as WorkerAddress, t as EnvRunner, u as WorkerHooks } from "./types.mjs";
1
+ import { EnvRunner, RunnerMessageListener, WorkerAddress, WorkerHooks } from "./types.mjs";
2
2
  import { IncomingMessage } from "node:http";
3
3
  import { Socket } from "node:net";
4
+ /**
5
+ * Source for a virtual module: either a literal ES module string or a factory
6
+ * that returns one (sync or async).
7
+ *
8
+ * Factories are evaluated **once on the host side** before the worker is spawned
9
+ * (functions can't cross the `workerData`/`JSON` boundary, and Node's synchronous
10
+ * load hook can't await), so the worker always receives plain strings. See
11
+ * {@link resolveVirtualModules}.
12
+ */
13
+ type VirtualModuleSource = string | (() => string | Promise<string>);
14
+ /** Virtual modules as a `specifier => source` map. */
15
+ type VirtualModules = Record<string, VirtualModuleSource>;
4
16
  interface EnvRunnerData {
5
17
  name?: string;
18
+ /**
19
+ * Virtual modules as a `specifier => source` map.
20
+ *
21
+ * Registered as Node.js ESM customization hooks in the worker so the entry
22
+ * (and its dependencies) can `import` them, e.g.
23
+ * `{ "#virtual-import": "export const foo = 1" }`.
24
+ *
25
+ * Each source may be a string or a factory `() => string | Promise<string>`.
26
+ * Factories are evaluated once on the host before the worker is spawned (so the
27
+ * worker always receives plain strings).
28
+ *
29
+ * Supported by the `node-worker`, `node-process`, `bun-process`,
30
+ * `deno-process`, `vercel`, `netlify`, and `miniflare` runners.
31
+ */
32
+ virtual?: VirtualModules;
6
33
  [key: string]: unknown;
7
34
  }
8
- declare abstract class BaseEnvRunner implements EnvRunner {
35
+ declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
9
36
  closed: boolean;
10
37
  protected _name: string;
11
38
  protected _workerEntry: string;
@@ -37,10 +64,26 @@ declare abstract class BaseEnvRunner implements EnvRunner {
37
64
  }): Promise<T>;
38
65
  reloadModule(timeout?: number): Promise<void>;
39
66
  close(cause?: unknown): Promise<void>;
67
+ [Symbol.asyncDispose](): Promise<void>;
40
68
  protected _handleMessage(message: any): void;
69
+ /**
70
+ * Resolve any factory-valued `data.virtual` sources to strings before the
71
+ * worker is spawned. Returns a pending promise only when there is async work
72
+ * to do (a factory is present); otherwise returns `undefined` so subclasses can
73
+ * keep their synchronous spawn path. Factories must be resolved here because
74
+ * functions can't cross the worker boundary and the load hook can't await.
75
+ */
76
+ protected _resolveVirtualData(): Promise<void> | undefined;
77
+ /**
78
+ * Run a subclass spawn callback after `data.virtual` is resolved.
79
+ * Synchronous when no factory-valued source is present; otherwise defers
80
+ * `init` until factories resolve. A throwing/rejecting factory closes the
81
+ * runner with the error as cause instead of leaving an unhandled rejection.
82
+ */
83
+ protected _initWithVirtualData(init: () => void): void;
41
84
  protected _closeSocket(): Promise<void>;
42
85
  protected abstract _hasRuntime(): boolean;
43
86
  protected abstract _closeRuntime(): Promise<void>;
44
87
  protected abstract _runtimeType(): string;
45
88
  }
46
- export { EnvRunnerData as n, BaseEnvRunner as t };
89
+ export { BaseEnvRunner, EnvRunnerData, VirtualModuleSource, VirtualModules };
@@ -1,3 +1,4 @@
1
+ import { resolveVirtualModules } from "./virtual-loader.mjs";
1
2
  import { rm } from "node:fs/promises";
2
3
  import { proxyFetch, proxyUpgrade } from "httpxy";
3
4
  var BaseEnvRunner = class {
@@ -35,6 +36,7 @@ var BaseEnvRunner = class {
35
36
  }
36
37
  waitForReady(timeout = 5e3) {
37
38
  if (this.ready) return Promise.resolve();
39
+ if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
38
40
  return new Promise((resolve, reject) => {
39
41
  const timer = setTimeout(() => {
40
42
  this._messageListeners.delete(listener);
@@ -45,6 +47,10 @@ var BaseEnvRunner = class {
45
47
  clearTimeout(timer);
46
48
  this._messageListeners.delete(listener);
47
49
  resolve();
50
+ } else if (this.closed) {
51
+ clearTimeout(timer);
52
+ this._messageListeners.delete(listener);
53
+ reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
48
54
  }
49
55
  };
50
56
  this._messageListeners.add(listener);
@@ -107,6 +113,9 @@ var BaseEnvRunner = class {
107
113
  await this._closeRuntime().catch(onError);
108
114
  await this._closeSocket().catch(onError);
109
115
  }
116
+ async [Symbol.asyncDispose]() {
117
+ await this.close();
118
+ }
110
119
  [Symbol.for("nodejs.util.inspect.custom")]() {
111
120
  const status = this.closed ? "closed" : this.ready ? "ready" : "pending";
112
121
  return `${this.constructor.name}#${this._name}(${status})`;
@@ -116,12 +125,30 @@ var BaseEnvRunner = class {
116
125
  this._address = message.address;
117
126
  this._hooks.onReady?.(this, this._address);
118
127
  }
128
+ if (message?.event === "init-error" && !this.ready && !this.closed) this.close(new Error(String(message.error || "Worker initialization failed")));
119
129
  for (const listener of this._messageListeners) listener(message);
120
130
  }
131
+ _resolveVirtualData() {
132
+ const virtual = this._data?.virtual;
133
+ if (!virtual || !Object.values(virtual).some((v) => typeof v === "function")) return;
134
+ return resolveVirtualModules(virtual).then((resolved) => {
135
+ this._data = {
136
+ ...this._data,
137
+ virtual: resolved
138
+ };
139
+ });
140
+ }
141
+ _initWithVirtualData(init) {
142
+ const pending = this._resolveVirtualData();
143
+ if (pending) pending.then(() => {
144
+ if (!this.closed) init();
145
+ }, (error) => this.close(error));
146
+ else init();
147
+ }
121
148
  async _closeSocket() {
122
149
  const socketPath = this._address?.socketPath;
123
150
  if (socketPath && socketPath[0] !== "\0" && !socketPath.startsWith(String.raw`\\.\\pipe`)) await rm(socketPath).catch(() => {});
124
151
  this._address = void 0;
125
152
  }
126
153
  };
127
- export { BaseEnvRunner as t };
154
+ export { BaseEnvRunner };
@@ -142,4 +142,4 @@ function init() {
142
142
  A = Q;
143
143
  })());
144
144
  }
145
- export { parse as n, init as t };
145
+ export { init, parse };
@@ -1,5 +1,5 @@
1
- import { u as WorkerHooks } from "./types.mjs";
2
- import { n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
1
+ import { WorkerHooks } from "./types.mjs";
2
+ import { BaseEnvRunner, EnvRunnerData } from "./base-runner.mjs";
3
3
  declare class DenoProcessEnvRunner extends BaseEnvRunner {
4
4
  #private;
5
5
  constructor(opts: {
@@ -14,4 +14,4 @@ declare class DenoProcessEnvRunner extends BaseEnvRunner {
14
14
  protected _runtimeType(): string;
15
15
  protected _closeRuntime(): Promise<void>;
16
16
  }
17
- export { DenoProcessEnvRunner as t };
17
+ export { DenoProcessEnvRunner };
@@ -1,4 +1,4 @@
1
- import { t as BaseEnvRunner } from "./base-runner.mjs";
1
+ import { BaseEnvRunner } from "./base-runner.mjs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { spawn } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
@@ -11,7 +11,7 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
11
11
  ...opts,
12
12
  workerEntry: opts.workerEntry || _defaultEntry
13
13
  });
14
- this.#initProcess(opts.execArgv);
14
+ this._initWithVirtualData(() => this.#initProcess(opts.execArgv));
15
15
  }
16
16
  sendMessage(message) {
17
17
  if (!this.#process) throw new Error("Deno env process should be initialized before sending messages.");
@@ -88,10 +88,13 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
88
88
  buffer = buffer.slice(newlineIdx + 1);
89
89
  if (line.startsWith("{")) try {
90
90
  this._handleMessage(JSON.parse(line));
91
+ continue;
91
92
  } catch {}
93
+ process.stdout.write(line + "\n");
92
94
  }
93
95
  });
96
+ child.stderr?.pipe(process.stderr);
94
97
  this.#process = handle;
95
98
  }
96
99
  };
97
- export { DenoProcessEnvRunner as t };
100
+ export { DenoProcessEnvRunner };
@@ -1,5 +1,5 @@
1
- import { u as WorkerHooks } from "./types.mjs";
2
- import { n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
1
+ import { WorkerHooks } from "./types.mjs";
2
+ import { BaseEnvRunner, EnvRunnerData } from "./base-runner.mjs";
3
3
  import { IncomingMessage } from "node:http";
4
4
  import { Socket } from "node:net";
5
5
  /** Result from a module transform (compatible with Vite's `TransformResult`). */
@@ -81,4 +81,4 @@ declare class MiniflareEnvRunner extends BaseEnvRunner {
81
81
  };
82
82
  }): Promise<void>;
83
83
  }
84
- export { TransformResult as i, MiniflareEnvRunnerOptions as n, MiniflareExportInfo as r, MiniflareEnvRunner as t };
84
+ export { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult };
@@ -1,10 +1,12 @@
1
- import { t as BaseEnvRunner } from "./base-runner.mjs";
2
- import { n as parse, t as init } from "./libs/cjs-module-lexer.mjs";
1
+ import { virtualModuleFormat } from "./virtual-loader.mjs";
2
+ import { BaseEnvRunner } from "./base-runner.mjs";
3
+ import { init, parse } from "./libs/cjs-module-lexer.mjs";
4
+ import { isVirtualSpecifier } from "./worker-utils.mjs";
3
5
  import { createRequire } from "node:module";
4
6
  import { proxyUpgrade } from "httpxy";
5
7
  import { readFileSync } from "node:fs";
6
8
  import { fileURLToPath } from "node:url";
7
- import { basename, dirname, resolve } from "node:path";
9
+ import { basename, dirname, isAbsolute, resolve } from "node:path";
8
10
  import { resolveModulePath } from "exsolve";
9
11
  const IPC_PATH = "/__env_runner_ipc";
10
12
  const IPC_BINDING = "__ENV_RUNNER_IPC";
@@ -199,7 +201,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
199
201
  this.#exports = opts.exports ?? {};
200
202
  this.#captureErrors = opts.captureErrors ?? true;
201
203
  this.#exportConditions = opts.exportConditions ?? ["workerd", "worker"];
202
- this.#init();
204
+ this._initWithVirtualData(() => this.#init());
203
205
  }
204
206
  static async disposeAll() {
205
207
  const entries = [..._miniflareCache.values()];
@@ -220,6 +222,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
220
222
  if (!this.closed) await this.close();
221
223
  }
222
224
  async fetch(input, init) {
225
+ for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
223
226
  if (!this.#miniflare || this.closed) return new Response("miniflare env runner is unavailable", { status: 503 });
224
227
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
225
228
  const res = await this.#miniflare.dispatchFetch(url, init);
@@ -315,9 +318,25 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
315
318
  port: Number(address.port)
316
319
  }, context.node.req, context.node.socket, context.node.head);
317
320
  }
321
+ async #prepareVirtualModules() {
322
+ const virtual = this._data?.virtual;
323
+ if (!virtual || Object.keys(virtual).length === 0) return;
324
+ const out = {};
325
+ let strip;
326
+ for (const [specifier, source] of Object.entries(virtual)) if (virtualModuleFormat(specifier) === "module-typescript") {
327
+ if (!strip) {
328
+ const { stripTypeScriptTypes } = await import("node:module");
329
+ if (typeof stripTypeScriptTypes !== "function") throw new TypeError(`[env-runner] virtual TypeScript module "${specifier}" requires \`module.stripTypeScriptTypes\` on the host (workerd does not parse TypeScript); upgrade Node.js or provide a pre-transpiled JavaScript source instead.`);
330
+ strip = stripTypeScriptTypes;
331
+ }
332
+ out[specifier] = strip(source);
333
+ } else out[specifier] = source;
334
+ return out;
335
+ }
318
336
  async #initAsync() {
319
337
  const { Miniflare } = await import("miniflare");
320
338
  const entryPath = this._data?.entry;
339
+ const virtual = await this.#prepareVirtualModules();
321
340
  const userFlags = this.#miniflareOptions.compatibilityFlags || [];
322
341
  const userDirectSockets = this.#miniflareOptions.unsafeDirectSockets || [];
323
342
  const options = {
@@ -331,9 +350,13 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
331
350
  }, ...userDirectSockets]
332
351
  };
333
352
  if (entryPath && !options.script && !options.scriptPath) {
334
- const resolvedEntry = resolve(entryPath);
335
- const entryDir = dirname(resolvedEntry);
336
- const detectedExports = this.#exports === false || this.#exports === void 0 ? [] : detectExportedClasses(resolvedEntry, typeof this.#exports === "object" ? this.#exports : {});
353
+ const entryIsVirtual = isVirtualSpecifier(entryPath, virtual);
354
+ const resolvedEntry = entryIsVirtual ? entryPath : resolve(entryPath);
355
+ const entryBase = isAbsolute(resolvedEntry) ? resolvedEntry : resolve("__env_runner_virtual_entry__.mjs");
356
+ const entryDir = dirname(entryBase);
357
+ const entrySource = entryIsVirtual ? virtual[entryPath] : _tryReadFile(resolvedEntry);
358
+ const detectedExports = this.#exports === false || this.#exports === void 0 ? [] : detectExportedClasses(entrySource, typeof this.#exports === "object" ? this.#exports : {});
359
+ 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.`);
337
360
  if (detectedExports.length > 0 && !options.durableObjects) {
338
361
  const autoDOs = { ...this.#miniflareOptions.durableObjects || {} };
339
362
  for (const name of detectedExports) {
@@ -370,7 +393,8 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
370
393
  ]
371
394
  }];
372
395
  if (!options.unsafeModuleFallbackService) {
373
- const _require = createRequire(resolvedEntry);
396
+ const _require = createRequire(entryBase);
397
+ const _virtual = virtual;
374
398
  const _transformRequest = this.#transformRequest;
375
399
  const _exportConditions = this.#exportConditions;
376
400
  options.unsafeUseModuleFallbackService = true;
@@ -385,6 +409,25 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
385
409
  if (!specifier) return new Response(null, { status: 404 });
386
410
  const cleanSpecifier = specifier.split("?")[0] || specifier;
387
411
  const cleanRaw = rawSpecifier?.split("?")[0];
412
+ if (_virtual) {
413
+ const bareSpecifier = cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier;
414
+ const virtualKey = [
415
+ cleanRaw,
416
+ cleanSpecifier,
417
+ bareSpecifier
418
+ ].find((key) => key !== void 0 && Object.hasOwn(_virtual, key));
419
+ if (virtualKey !== void 0) {
420
+ const name = bareSpecifier + (specifier.includes("?") ? specifier.slice(specifier.indexOf("?")) : "");
421
+ const source = _virtual[virtualKey];
422
+ return virtualModuleFormat(virtualKey) === "json" ? Response.json({
423
+ name,
424
+ json: source
425
+ }) : Response.json({
426
+ name,
427
+ esModule: source
428
+ });
429
+ }
430
+ }
388
431
  let resolvedPath;
389
432
  const fileUrlRaw = cleanRaw || cleanSpecifier;
390
433
  if (fileUrlRaw.startsWith("file://")) try {
@@ -406,7 +449,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
406
449
  }
407
450
  } else try {
408
451
  resolvedPath = resolveModulePath(cleanRaw, {
409
- from: referrerReal || resolvedEntry,
452
+ from: referrerReal || entryBase,
410
453
  conditions: _exportConditions,
411
454
  try: true
412
455
  }) || contextRequire.resolve(cleanRaw);
@@ -467,7 +510,8 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
467
510
  if (this.#persistent && entryPath) {
468
511
  this.#cacheKey = computeCacheKey(entryPath, {
469
512
  ...this.#miniflareOptions,
470
- _exportConditions: this.#exportConditions
513
+ _exportConditions: this.#exportConditions,
514
+ _virtual: virtual
471
515
  });
472
516
  const cached = _miniflareCache.get(this.#cacheKey);
473
517
  if (cached) {
@@ -503,16 +547,22 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
503
547
  } });
504
548
  }
505
549
  };
506
- function detectExportedClasses(entryPath, explicit) {
550
+ function detectExportedClasses(entrySource, explicit) {
507
551
  const names = new Set(Object.keys(explicit));
508
- try {
509
- const source = readFileSync(entryPath, "utf8");
552
+ if (entrySource) {
510
553
  const re = /\bexport\s+class\s+(\w+)/g;
511
554
  let match;
512
- while (match = re.exec(source)) if (match[1]) names.add(match[1]);
513
- } catch {}
555
+ while (match = re.exec(entrySource)) if (match[1]) names.add(match[1]);
556
+ }
514
557
  return [...names];
515
558
  }
559
+ function _tryReadFile(path) {
560
+ try {
561
+ return readFileSync(path, "utf8");
562
+ } catch {
563
+ return;
564
+ }
565
+ }
516
566
  function toScreamingSnakeCase(name) {
517
567
  return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
518
568
  }
@@ -536,4 +586,4 @@ function createCjsEsmShim(cjsSpecifier, contents) {
536
586
  for (const name of namedExports) shim += `export var ${name} = __cjs_mod__["${name}"];\n`;
537
587
  return shim;
538
588
  }
539
- export { MiniflareEnvRunner as t };
589
+ export { MiniflareEnvRunner };
@@ -1,5 +1,5 @@
1
- import { u as WorkerHooks } from "./types.mjs";
2
- import { n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
1
+ import { WorkerHooks } from "./types.mjs";
2
+ import { BaseEnvRunner, EnvRunnerData } from "./base-runner.mjs";
3
3
  declare class NodeWorkerEnvRunner extends BaseEnvRunner {
4
4
  #private;
5
5
  constructor(opts: {
@@ -13,4 +13,4 @@ declare class NodeWorkerEnvRunner extends BaseEnvRunner {
13
13
  protected _runtimeType(): string;
14
14
  protected _closeRuntime(): Promise<void>;
15
15
  }
16
- export { NodeWorkerEnvRunner as t };
16
+ export { NodeWorkerEnvRunner };
@@ -1,4 +1,4 @@
1
- import { t as BaseEnvRunner } from "./base-runner.mjs";
1
+ import { BaseEnvRunner } from "./base-runner.mjs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { Worker } from "node:worker_threads";
@@ -11,7 +11,7 @@ var NodeWorkerEnvRunner = class extends BaseEnvRunner {
11
11
  ...opts,
12
12
  workerEntry: opts.workerEntry || _defaultEntry
13
13
  });
14
- this.#initWorker();
14
+ this._initWithVirtualData(() => this.#initWorker());
15
15
  }
16
16
  sendMessage(message) {
17
17
  if (!this.#worker) throw new Error("Worker thread should be initialized before sending messages.");
@@ -57,4 +57,4 @@ var NodeWorkerEnvRunner = class extends BaseEnvRunner {
57
57
  this.#worker = worker;
58
58
  }
59
59
  };
60
- export { NodeWorkerEnvRunner as t };
60
+ export { NodeWorkerEnvRunner };
@@ -1,6 +1,6 @@
1
- import { u as WorkerHooks } from "./types.mjs";
2
- import { n as EnvRunnerData } from "./base-runner.mjs";
3
- import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
1
+ import { WorkerHooks } from "./types.mjs";
2
+ import { EnvRunnerData } from "./base-runner.mjs";
3
+ import { NodeWorkerEnvRunner } from "./runner3.mjs";
4
4
  declare class VercelEnvRunner extends NodeWorkerEnvRunner {
5
5
  constructor(opts: {
6
6
  name: string;
@@ -11,4 +11,4 @@ declare class VercelEnvRunner extends NodeWorkerEnvRunner {
11
11
  fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
12
12
  protected _runtimeType(): string;
13
13
  }
14
- export { VercelEnvRunner as t };
14
+ export { VercelEnvRunner };
@@ -1,4 +1,4 @@
1
- import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
1
+ import { NodeWorkerEnvRunner } from "./runner3.mjs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { randomBytes } from "node:crypto";
4
4
  let _warned = false;
@@ -90,4 +90,4 @@ var VercelEnvRunner = class extends NodeWorkerEnvRunner {
90
90
  return "vercel";
91
91
  }
92
92
  };
93
- export { VercelEnvRunner as t };
93
+ export { VercelEnvRunner };