env-runner 0.1.5 → 0.1.7

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 (37) hide show
  1. package/README.md +45 -15
  2. package/dist/THIRD-PARTY-LICENSES.md +0 -29
  3. package/dist/_chunks/base-runner.d.mts +0 -1
  4. package/dist/_chunks/base-runner.mjs +0 -15
  5. package/dist/_chunks/libs/cjs-module-lexer.mjs +1 -1
  6. package/dist/_chunks/runner.mjs +17 -13
  7. package/dist/_chunks/runner2.d.mts +8 -0
  8. package/dist/_chunks/runner2.mjs +14 -2
  9. package/dist/_chunks/runner3.d.mts +19 -0
  10. package/dist/_chunks/runner3.mjs +62 -0
  11. package/dist/_chunks/runner4.d.mts +17 -0
  12. package/dist/_chunks/runner4.mjs +42 -0
  13. package/dist/_chunks/runner5.d.mts +17 -0
  14. package/dist/_chunks/runner5.mjs +47 -0
  15. package/dist/_chunks/server.mjs +29 -8
  16. package/dist/_chunks/types.d.mts +1 -1
  17. package/dist/cli.mjs +4 -4
  18. package/dist/index.d.mts +17 -6
  19. package/dist/index.mjs +3 -3
  20. package/dist/runners/bun-process/runner.mjs +0 -8
  21. package/dist/runners/deno-process/runner.mjs +0 -2
  22. package/dist/runners/deno-process/worker.mjs +31 -8
  23. package/dist/runners/miniflare/runner.mjs +0 -3
  24. package/dist/runners/netlify/runner.d.mts +3 -0
  25. package/dist/runners/netlify/runner.mjs +2 -0
  26. package/dist/runners/netlify/worker.d.mts +1 -0
  27. package/dist/runners/netlify/worker.mjs +43 -0
  28. package/dist/runners/node-process/runner.mjs +0 -6
  29. package/dist/runners/node-worker/runner.d.mts +3 -19
  30. package/dist/runners/node-worker/runner.mjs +1 -67
  31. package/dist/runners/self/runner.mjs +0 -1
  32. package/dist/runners/vercel/runner.d.mts +3 -0
  33. package/dist/runners/vercel/runner.mjs +2 -0
  34. package/dist/runners/vercel/worker.d.mts +1 -0
  35. package/dist/runners/vercel/worker.mjs +50 -0
  36. package/package.json +27 -18
  37. package/dist/_chunks/libs/std-env.mjs +0 -171
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <!-- /automd -->
9
9
 
10
- Generic environment runner for JavaScript runtimes. Run your server apps across Node.js worker threads, child processes, Bun, Deno, Cloudflare Workers (via miniflare), or in-process — with hot-reload, WebSocket proxying, and bidirectional messaging.
10
+ Generic environment runner for JavaScript runtimes. Run your server apps across Node.js worker threads, child processes, Bun, Deno, Cloudflare Workers (via miniflare), Vercel, Netlify, or in-process — with hot-reload, WebSocket proxying, and bidirectional messaging.
11
11
 
12
12
  ## Usage
13
13
 
@@ -56,13 +56,13 @@ const envServer = new EnvServer({
56
56
  watchPaths: ["./src"],
57
57
  });
58
58
 
59
- envServer.onReady = (_runner, address) => {
59
+ envServer.onReady((_runner, address) => {
60
60
  console.log(`Worker ready on ${address?.host}:${address?.port}`);
61
- };
61
+ });
62
62
 
63
- envServer.onReload = () => {
63
+ envServer.onReload(() => {
64
64
  console.log("Reloaded!");
65
- };
65
+ });
66
66
 
67
67
  await envServer.start();
68
68
 
@@ -81,9 +81,9 @@ import { RunnerManager, NodeProcessEnvRunner } from "env-runner";
81
81
 
82
82
  const manager = new RunnerManager();
83
83
 
84
- manager.onReady = (_runner, address) => {
84
+ manager.onReady((_runner, address) => {
85
85
  console.log("Ready:", address);
86
- };
86
+ });
87
87
 
88
88
  // Load initial runner
89
89
  const runner = new NodeProcessEnvRunner({
@@ -120,6 +120,8 @@ import { BunProcessEnvRunner } from "env-runner/runners/bun-process";
120
120
  import { DenoProcessEnvRunner } from "env-runner/runners/deno-process";
121
121
  import { SelfEnvRunner } from "env-runner/runners/self";
122
122
  import { MiniflareEnvRunner } from "env-runner/runners/miniflare";
123
+ import { VercelEnvRunner } from "env-runner/runners/vercel";
124
+ import { NetlifyEnvRunner } from "env-runner/runners/netlify";
123
125
  ```
124
126
 
125
127
  All runners implement the [`EnvRunner`](./src/types.ts) interface:
@@ -160,14 +162,16 @@ await runner.close();
160
162
 
161
163
  **Available runners:**
162
164
 
163
- | Runner | Isolation | IPC mechanism |
164
- | ---------------------- | ------------------------------ | ---------------------------------- |
165
- | `NodeWorkerEnvRunner` | Worker thread | `workerData` / `parentPort` |
166
- | `NodeProcessEnvRunner` | Child process (`fork`) | `ENV_RUNNER_DATA` / `process.send` |
167
- | `BunProcessEnvRunner` | Bun or Node.js process | `Bun.spawn` IPC or `fork()` |
168
- | `DenoProcessEnvRunner` | Deno process | `deno run` with IPC channel |
169
- | `SelfEnvRunner` | In-process | In-memory channel |
170
- | `MiniflareEnvRunner` | Cloudflare Workers (miniflare) | WebSocket pair via `dispatchFetch` |
165
+ | Runner | Isolation | IPC mechanism |
166
+ | ---------------------- | ------------------------------- | ---------------------------------- |
167
+ | `NodeWorkerEnvRunner` | Worker thread | `workerData` / `parentPort` |
168
+ | `NodeProcessEnvRunner` | Child process (`fork`) | `ENV_RUNNER_DATA` / `process.send` |
169
+ | `BunProcessEnvRunner` | Bun or Node.js process | `Bun.spawn` IPC or `fork()` |
170
+ | `DenoProcessEnvRunner` | Deno process | `deno run` with IPC channel |
171
+ | `SelfEnvRunner` | In-process | In-memory channel |
172
+ | `MiniflareEnvRunner` | Cloudflare Workers (miniflare) | WebSocket pair via `dispatchFetch` |
173
+ | `VercelEnvRunner` | Worker thread (Vercel context) | `workerData` / `parentPort` |
174
+ | `NetlifyEnvRunner` | Worker thread (Netlify context) | `workerData` / `parentPort` |
171
175
 
172
176
  #### Miniflare Runner
173
177
 
@@ -289,6 +293,32 @@ const runner2 = new MiniflareEnvRunner({
289
293
  // Fully destroy: runner.dispose() or MiniflareEnvRunner.disposeAll()
290
294
  ```
291
295
 
296
+ #### Vercel Runner
297
+
298
+ Simulates a Vercel deployment environment with automatic header injection (`x-vercel-deployment-url`, `x-vercel-forwarded-for`, forwarding headers) and global context.
299
+
300
+ ```ts
301
+ import { VercelEnvRunner } from "env-runner/runners/vercel";
302
+
303
+ const runner = new VercelEnvRunner({
304
+ name: "my-app",
305
+ data: { entry: "./app.ts" },
306
+ });
307
+ ```
308
+
309
+ #### Netlify Runner
310
+
311
+ Simulates a Netlify deployment environment with automatic header injection (`x-nf-client-connection-ip`, `x-nf-account-id`, `x-nf-site-id`, `x-nf-deploy-id`, `x-nf-deploy-context`, `x-nf-geo`, `x-nf-request-id`, forwarding headers) and `globalThis.Netlify` setup:
312
+
313
+ ```ts
314
+ import { NetlifyEnvRunner } from "env-runner/runners/netlify";
315
+
316
+ const runner = new NetlifyEnvRunner({
317
+ name: "my-app",
318
+ data: { entry: "./app.ts" },
319
+ });
320
+ ```
321
+
292
322
  ### Vite Environment API
293
323
 
294
324
  env-runner provides helpers for integrating with Vite's [Environment API](https://vite.dev/guide/api-environment-runtimes.html):
@@ -21,32 +21,3 @@ Repository: https://github.com/nodejs/cjs-module-lexer
21
21
  > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
22
22
  >
23
23
  > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
-
25
- ---------------------------------------
26
-
27
- ## std-env
28
-
29
- License: MIT
30
- Repository: https://github.com/unjs/std-env
31
-
32
- > MIT License
33
- >
34
- > Copyright (c) Pooya Parsa <pooya@pi0.io>
35
- >
36
- > Permission is hereby granted, free of charge, to any person obtaining a copy
37
- > of this software and associated documentation files (the "Software"), to deal
38
- > in the Software without restriction, including without limitation the rights
39
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
40
- > copies of the Software, and to permit persons to whom the Software is
41
- > furnished to do so, subject to the following conditions:
42
- >
43
- > The above copyright notice and this permission notice shall be included in all
44
- > copies or substantial portions of the Software.
45
- >
46
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
47
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
48
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
49
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
50
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
51
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
52
- > SOFTWARE.
@@ -41,7 +41,6 @@ declare abstract class BaseEnvRunner implements EnvRunner {
41
41
  close(cause?: unknown): Promise<void>;
42
42
  protected _handleMessage(message: any): void;
43
43
  protected _closeSocket(): Promise<void>;
44
- protected _requestGracefulShutdown(sendShutdown: () => void, listenForExit: (resolve: () => void) => void, hasExited: () => boolean): Promise<void>;
45
44
  protected abstract _hasRuntime(): boolean;
46
45
  protected abstract _closeRuntime(): Promise<void>;
47
46
  protected abstract _runtimeType(): string;
@@ -1,4 +1,3 @@
1
- import { n as v, t as m } from "./libs/std-env.mjs";
2
1
  import { rm } from "node:fs/promises";
3
2
  import { proxyFetch, proxyUpgrade } from "httpxy";
4
3
  //#region src/common/base-runner.ts
@@ -125,20 +124,6 @@ var BaseEnvRunner = class {
125
124
  if (socketPath && socketPath[0] !== "\0" && !socketPath.startsWith(String.raw`\\.\\pipe`)) await rm(socketPath).catch(() => {});
126
125
  this._address = void 0;
127
126
  }
128
- async _requestGracefulShutdown(sendShutdown, listenForExit, hasExited) {
129
- sendShutdown();
130
- if (!hasExited() && !v && !m) await new Promise((resolve) => {
131
- const gracefulShutdownTimeoutMs = Number.parseInt(process.env.ENV_RUNNER_SHUTDOWN_TIMEOUT || "", 10) || 5e3;
132
- const timeout = setTimeout(() => {
133
- console.warn(`force closing node env runner ${this._runtimeType()}...`);
134
- resolve();
135
- }, gracefulShutdownTimeoutMs);
136
- listenForExit(() => {
137
- clearTimeout(timeout);
138
- resolve();
139
- });
140
- });
141
- }
142
127
  };
143
128
  //#endregion
144
129
  export { BaseEnvRunner as t };
@@ -1,4 +1,4 @@
1
- //#region node_modules/.deno/cjs-module-lexer@2.2.0/node_modules/cjs-module-lexer/dist/lexer.mjs
1
+ //#region node_modules/.pnpm/cjs-module-lexer@2.2.0/node_modules/cjs-module-lexer/dist/lexer.mjs
2
2
  let A;
3
3
  const B = 1 === new Uint8Array(new Uint16Array([1]).buffer)[0];
4
4
  function parse(E, g = "@") {
@@ -26,13 +26,6 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
26
26
  }
27
27
  async _closeRuntime() {
28
28
  if (!this.#process) return;
29
- if (this.#process._exitCode == null) await this._requestGracefulShutdown(() => {
30
- try {
31
- this.#process.send({ event: "shutdown" });
32
- } catch {}
33
- }, (resolve) => {
34
- this.#process?.exited.then(() => resolve());
35
- }, () => this.#process?._exitCode != null);
36
29
  this.#process.removeAllListeners?.();
37
30
  try {
38
31
  this.#process.kill();
@@ -51,8 +44,9 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
51
44
  };
52
45
  const child = spawn("deno", [
53
46
  "run",
54
- "--allow-all",
47
+ "-A",
55
48
  "--node-modules-dir=auto",
49
+ "--no-lock",
56
50
  ...execArgv || [],
57
51
  this._workerEntry
58
52
  ], {
@@ -60,8 +54,7 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
60
54
  stdio: [
61
55
  "pipe",
62
56
  "pipe",
63
- "pipe",
64
- "ipc"
57
+ "pipe"
65
58
  ]
66
59
  });
67
60
  const exited = new Promise((resolve) => {
@@ -70,7 +63,9 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
70
63
  const handle = {
71
64
  pid: child.pid,
72
65
  kill: () => child.kill(),
73
- send: (message) => child.send(message),
66
+ send: (message) => {
67
+ child.stdin.write(JSON.stringify(message) + "\n");
68
+ },
74
69
  exited,
75
70
  _exitCode: void 0,
76
71
  removeAllListeners: () => child.removeAllListeners()
@@ -85,8 +80,17 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
85
80
  this.close(error);
86
81
  }
87
82
  });
88
- child.on("message", (message) => {
89
- this._handleMessage(message);
83
+ let buffer = "";
84
+ child.stdout.on("data", (chunk) => {
85
+ buffer += chunk.toString();
86
+ let newlineIdx;
87
+ while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
88
+ const line = buffer.slice(0, newlineIdx);
89
+ buffer = buffer.slice(newlineIdx + 1);
90
+ if (line.startsWith("{")) try {
91
+ this._handleMessage(JSON.parse(line));
92
+ } catch {}
93
+ }
90
94
  });
91
95
  this.#process = handle;
92
96
  }
@@ -46,6 +46,14 @@ interface MiniflareEnvRunnerOptions {
46
46
  persistent?: boolean;
47
47
  /** Wrap the user's `fetch` in a try/catch that returns structured JSON error responses. Default: `true`. */
48
48
  captureErrors?: boolean;
49
+ /**
50
+ * Export conditions for bare-specifier module resolution in the module
51
+ * fallback service. Ensures packages with conditional exports (e.g.
52
+ * `"workerd"`) resolve to the correct entry instead of the Node.js one.
53
+ *
54
+ * Defaults to `["workerd", "worker"]`.
55
+ */
56
+ exportConditions?: string[];
49
57
  }
50
58
  declare class MiniflareEnvRunner extends BaseEnvRunner {
51
59
  #private;
@@ -5,6 +5,7 @@ import { proxyUpgrade } from "httpxy";
5
5
  import { readFileSync } from "node:fs";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { basename, dirname, resolve } from "node:path";
8
+ import { resolveModulePath } from "exsolve";
8
9
  //#region src/runners/miniflare/wrapper.ts
9
10
  const IPC_PATH$1 = "/__env_runner_ipc";
10
11
  /** Service binding name used for cross-request IPC (worker → runner). */
@@ -207,6 +208,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
207
208
  #cacheKey;
208
209
  #exports;
209
210
  #captureErrors;
211
+ #exportConditions;
210
212
  constructor(opts) {
211
213
  super({
212
214
  ...opts,
@@ -217,6 +219,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
217
219
  this.#persistent = opts.persistent ?? false;
218
220
  this.#exports = opts.exports ?? {};
219
221
  this.#captureErrors = opts.captureErrors ?? true;
222
+ this.#exportConditions = opts.exportConditions ?? ["workerd", "worker"];
220
223
  this.#init();
221
224
  }
222
225
  /** Dispose all persistent Miniflare instances from the cache. */
@@ -399,6 +402,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
399
402
  if (!options.unsafeModuleFallbackService) {
400
403
  const _require = createRequire(resolvedEntry);
401
404
  const _transformRequest = this.#transformRequest;
405
+ const _exportConditions = this.#exportConditions;
402
406
  options.unsafeUseModuleFallbackService = true;
403
407
  const modulePathMap = /* @__PURE__ */ new Map();
404
408
  const _cjsLexerReady = ensureCjsLexer();
@@ -422,6 +426,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
422
426
  const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
423
427
  const referrerReal = modulePathMap.get(referrerKey);
424
428
  const contextRequire = referrerReal ? createRequire(referrerReal) : _require;
429
+ if (cleanRaw.startsWith("cloudflare:")) return new Response(null, { status: 404 });
425
430
  if (cleanRaw.startsWith("node:")) {
426
431
  const nodeName = cleanRaw.slice(5);
427
432
  try {
@@ -430,7 +435,11 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
430
435
  return new Response(null, { status: 404 });
431
436
  }
432
437
  } else try {
433
- resolvedPath = contextRequire.resolve(cleanRaw);
438
+ resolvedPath = resolveModulePath(cleanRaw, {
439
+ from: referrerReal || resolvedEntry,
440
+ conditions: _exportConditions,
441
+ try: true
442
+ }) || contextRequire.resolve(cleanRaw);
434
443
  } catch {
435
444
  const name = cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier;
436
445
  return Response.json({
@@ -486,7 +495,10 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
486
495
  }
487
496
  }
488
497
  if (this.#persistent && entryPath) {
489
- this.#cacheKey = computeCacheKey(entryPath, this.#miniflareOptions);
498
+ this.#cacheKey = computeCacheKey(entryPath, {
499
+ ...this.#miniflareOptions,
500
+ _exportConditions: this.#exportConditions
501
+ });
490
502
  const cached = _miniflareCache.get(this.#cacheKey);
491
503
  if (cached) {
492
504
  this.#miniflare = cached.mf;
@@ -0,0 +1,19 @@
1
+ import { u as WorkerHooks } from "./types.mjs";
2
+ import { n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
3
+
4
+ //#region src/runners/node-worker/runner.d.ts
5
+ declare class NodeWorkerEnvRunner extends BaseEnvRunner {
6
+ #private;
7
+ constructor(opts: {
8
+ name: string;
9
+ workerEntry?: string;
10
+ hooks?: WorkerHooks;
11
+ data?: EnvRunnerData;
12
+ });
13
+ sendMessage(message: unknown): void;
14
+ protected _hasRuntime(): boolean;
15
+ protected _runtimeType(): string;
16
+ protected _closeRuntime(): Promise<void>;
17
+ }
18
+ //#endregion
19
+ export { NodeWorkerEnvRunner as t };
@@ -0,0 +1,62 @@
1
+ import { t as BaseEnvRunner } from "./base-runner.mjs";
2
+ import { existsSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { Worker } from "node:worker_threads";
5
+ //#region src/runners/node-worker/runner.ts
6
+ let _defaultEntry;
7
+ var NodeWorkerEnvRunner = class extends BaseEnvRunner {
8
+ #worker;
9
+ constructor(opts) {
10
+ _defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/node-worker/worker"));
11
+ super({
12
+ ...opts,
13
+ workerEntry: opts.workerEntry || _defaultEntry
14
+ });
15
+ this.#initWorker();
16
+ }
17
+ sendMessage(message) {
18
+ if (!this.#worker) throw new Error("Worker thread should be initialized before sending messages.");
19
+ this.#worker.postMessage(message);
20
+ }
21
+ _hasRuntime() {
22
+ return Boolean(this.#worker);
23
+ }
24
+ _runtimeType() {
25
+ return "worker";
26
+ }
27
+ async _closeRuntime() {
28
+ if (!this.#worker) return;
29
+ this.#worker.removeAllListeners();
30
+ await this.#worker.terminate().catch((error) => {
31
+ console.error(error);
32
+ });
33
+ this.#worker = void 0;
34
+ }
35
+ #initWorker() {
36
+ if (!existsSync(this._workerEntry)) {
37
+ this.close(`worker entry not found in "${this._workerEntry}".`);
38
+ return;
39
+ }
40
+ const worker = new Worker(this._workerEntry, {
41
+ env: { ...process.env },
42
+ workerData: {
43
+ name: this._name,
44
+ ...this._data
45
+ }
46
+ });
47
+ worker.once("exit", (code) => {
48
+ worker._exitCode = code;
49
+ this.close(`worker exited with code ${code}`);
50
+ });
51
+ worker.once("error", (error) => {
52
+ console.error(`Worker error:`, error);
53
+ this.close(error);
54
+ });
55
+ worker.on("message", (message) => {
56
+ this._handleMessage(message);
57
+ });
58
+ this.#worker = worker;
59
+ }
60
+ };
61
+ //#endregion
62
+ export { NodeWorkerEnvRunner as t };
@@ -0,0 +1,17 @@
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";
4
+
5
+ //#region src/runners/vercel/runner.d.ts
6
+ declare class VercelEnvRunner extends NodeWorkerEnvRunner {
7
+ constructor(opts: {
8
+ name: string;
9
+ workerEntry?: string;
10
+ hooks?: WorkerHooks;
11
+ data?: EnvRunnerData;
12
+ });
13
+ fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
14
+ protected _runtimeType(): string;
15
+ }
16
+ //#endregion
17
+ export { VercelEnvRunner as t };
@@ -0,0 +1,42 @@
1
+ import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
2
+ import { fileURLToPath } from "node:url";
3
+ //#region src/runners/vercel/runner.ts
4
+ let _defaultEntry;
5
+ var VercelEnvRunner = class extends NodeWorkerEnvRunner {
6
+ constructor(opts) {
7
+ _defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/vercel/worker"));
8
+ super({
9
+ ...opts,
10
+ workerEntry: opts.workerEntry || _defaultEntry
11
+ });
12
+ }
13
+ async fetch(input, init) {
14
+ const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
15
+ if (this._address && this._address.port != null && !headers.has("x-vercel-deployment-url")) {
16
+ const host = this._address.host || "127.0.0.1";
17
+ headers.set("x-vercel-deployment-url", `http://${host}:${this._address.port}`);
18
+ }
19
+ const clientIp = headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || "127.0.0.1";
20
+ if (!headers.has("x-vercel-forwarded-for")) headers.set("x-vercel-forwarded-for", clientIp);
21
+ if (!headers.has("x-forwarded-for")) headers.set("x-forwarded-for", clientIp);
22
+ if (!headers.has("x-real-ip")) headers.set("x-real-ip", clientIp);
23
+ try {
24
+ const url = new URL(input instanceof Request ? input.url : input.toString());
25
+ if (!headers.has("x-forwarded-proto")) headers.set("x-forwarded-proto", url.protocol.replace(":", ""));
26
+ if (!headers.has("x-forwarded-host")) headers.set("x-forwarded-host", headers.get("host") || url.host);
27
+ } catch {}
28
+ if (input instanceof Request) return super.fetch(new Request(input, {
29
+ ...init,
30
+ headers
31
+ }));
32
+ return super.fetch(input, {
33
+ ...init,
34
+ headers
35
+ });
36
+ }
37
+ _runtimeType() {
38
+ return "vercel";
39
+ }
40
+ };
41
+ //#endregion
42
+ export { VercelEnvRunner as t };
@@ -0,0 +1,17 @@
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";
4
+
5
+ //#region src/runners/netlify/runner.d.ts
6
+ declare class NetlifyEnvRunner extends NodeWorkerEnvRunner {
7
+ constructor(opts: {
8
+ name: string;
9
+ workerEntry?: string;
10
+ hooks?: WorkerHooks;
11
+ data?: EnvRunnerData;
12
+ });
13
+ fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
14
+ protected _runtimeType(): string;
15
+ }
16
+ //#endregion
17
+ export { NetlifyEnvRunner as t };
@@ -0,0 +1,47 @@
1
+ import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
2
+ import { fileURLToPath } from "node:url";
3
+ //#region src/runners/netlify/runner.ts
4
+ let _defaultEntry;
5
+ var NetlifyEnvRunner = class extends NodeWorkerEnvRunner {
6
+ constructor(opts) {
7
+ _defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/netlify/worker"));
8
+ super({
9
+ ...opts,
10
+ workerEntry: opts.workerEntry || _defaultEntry
11
+ });
12
+ }
13
+ async fetch(input, init) {
14
+ const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
15
+ const clientIp = headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || "127.0.0.1";
16
+ if (!headers.has("x-nf-client-connection-ip")) headers.set("x-nf-client-connection-ip", clientIp);
17
+ if (!headers.has("x-nf-account-id")) headers.set("x-nf-account-id", "0");
18
+ if (!headers.has("x-nf-site-id")) headers.set("x-nf-site-id", "0");
19
+ if (!headers.has("x-nf-deploy-id")) headers.set("x-nf-deploy-id", "0");
20
+ if (!headers.has("x-nf-deploy-context")) headers.set("x-nf-deploy-context", "dev");
21
+ if (!headers.has("x-nf-geo")) headers.set("x-nf-geo", btoa(JSON.stringify({
22
+ city: "localhost",
23
+ country: { code: "dev" }
24
+ })));
25
+ if (!headers.has("x-nf-request-id")) headers.set("x-nf-request-id", crypto.randomUUID());
26
+ if (!headers.has("x-forwarded-for")) headers.set("x-forwarded-for", clientIp);
27
+ if (!headers.has("x-real-ip")) headers.set("x-real-ip", clientIp);
28
+ try {
29
+ const url = new URL(input instanceof Request ? input.url : input.toString());
30
+ if (!headers.has("x-forwarded-proto")) headers.set("x-forwarded-proto", url.protocol.replace(":", ""));
31
+ if (!headers.has("x-forwarded-host")) headers.set("x-forwarded-host", headers.get("host") || url.host);
32
+ } catch {}
33
+ if (input instanceof Request) return super.fetch(new Request(input, {
34
+ ...init,
35
+ headers
36
+ }));
37
+ return super.fetch(input, {
38
+ ...init,
39
+ headers
40
+ });
41
+ }
42
+ _runtimeType() {
43
+ return "netlify";
44
+ }
45
+ };
46
+ //#endregion
47
+ export { NetlifyEnvRunner as t };
@@ -10,6 +10,8 @@ var RunnerManager = class {
10
10
  _messageListeners = /* @__PURE__ */ new Set();
11
11
  _closed = false;
12
12
  _reloading = false;
13
+ _closeListeners = /* @__PURE__ */ new Set();
14
+ _readyListeners = /* @__PURE__ */ new Set();
13
15
  constructor(runner) {
14
16
  if (runner) this._attach(runner);
15
17
  }
@@ -106,12 +108,22 @@ var RunnerManager = class {
106
108
  this._detach();
107
109
  if (runner) await runner.close();
108
110
  }
109
- onClose;
110
- onReady;
111
+ onClose(listener) {
112
+ this._closeListeners.add(listener);
113
+ }
114
+ offClose(listener) {
115
+ this._closeListeners.delete(listener);
116
+ }
117
+ onReady(listener) {
118
+ this._readyListeners.add(listener);
119
+ }
120
+ offReady(listener) {
121
+ this._readyListeners.delete(listener);
122
+ }
111
123
  _internalListener = (message) => {
112
124
  if (message?.address) {
113
125
  this._flushQueue();
114
- this.onReady?.(this, message.address);
126
+ for (const fn of this._readyListeners) fn(this, message.address);
115
127
  }
116
128
  };
117
129
  _attach(runner) {
@@ -123,7 +135,7 @@ var RunnerManager = class {
123
135
  await originalClose();
124
136
  if (this._runner === runner) {
125
137
  this._runner = void 0;
126
- this.onClose?.(this);
138
+ for (const fn of this._closeListeners) fn(this);
127
139
  }
128
140
  };
129
141
  if (runner.ready) this._flushQueue();
@@ -163,7 +175,9 @@ const loaders = {
163
175
  "bun-process": () => import("env-runner/runners/bun-process").then((m) => m.BunProcessEnvRunner),
164
176
  "deno-process": () => import("env-runner/runners/deno-process").then((m) => m.DenoProcessEnvRunner),
165
177
  self: () => import("env-runner/runners/self").then((m) => m.SelfEnvRunner),
166
- miniflare: () => import("env-runner/runners/miniflare").then((m) => m.MiniflareEnvRunner)
178
+ miniflare: () => import("env-runner/runners/miniflare").then((m) => m.MiniflareEnvRunner),
179
+ vercel: () => import("env-runner/runners/vercel").then((m) => m.VercelEnvRunner),
180
+ netlify: () => import("env-runner/runners/netlify").then((m) => m.NetlifyEnvRunner)
167
181
  };
168
182
  async function loadRunner(runner, opts) {
169
183
  return new (await (loaders[runner]()))(opts);
@@ -174,9 +188,16 @@ var EnvServer = class extends RunnerManager {
174
188
  _opts;
175
189
  _watchers = [];
176
190
  _reloadTimeout;
191
+ _reloadListeners = /* @__PURE__ */ new Set();
177
192
  runner = null;
178
- /** Called when the runner is reloaded due to a file change. */
179
- onReload;
193
+ /** Register a listener called when the runner is reloaded due to a file change. */
194
+ onReload(listener) {
195
+ this._reloadListeners.add(listener);
196
+ }
197
+ /** Remove a previously registered reload listener. */
198
+ offReload(listener) {
199
+ this._reloadListeners.delete(listener);
200
+ }
180
201
  constructor(opts) {
181
202
  super();
182
203
  this._opts = opts;
@@ -223,7 +244,7 @@ var EnvServer = class extends RunnerManager {
223
244
  try {
224
245
  this.runner = await this._createRunner();
225
246
  await this.reload(this.runner);
226
- this.onReload?.();
247
+ for (const fn of this._reloadListeners) fn();
227
248
  } catch (error) {
228
249
  console.error("Failed to reload runner:", error);
229
250
  }
@@ -54,7 +54,7 @@ interface RPCOptions {
54
54
  timeout?: number;
55
55
  }
56
56
  /** Core runner interface combining lifecycle hooks, RPC, and request proxying. */
57
- interface EnvRunner extends WorkerHooks, RunnerRPCHooks {
57
+ interface EnvRunner extends RunnerRPCHooks {
58
58
  /** Whether the worker is ready to accept requests. */
59
59
  readonly ready: boolean;
60
60
  /** Whether the runner has been closed. */
package/dist/cli.mjs CHANGED
@@ -40,12 +40,12 @@ const envServer = new EnvServer({
40
40
  entry: resolve(entry),
41
41
  watch: values.watch
42
42
  });
43
- envServer.onReady = (_runner, address) => {
43
+ envServer.onReady((_runner, address) => {
44
44
  console.log(`\x1B[2m➜ Worker ready on ${address?.host}:${address?.port || address?.socketPath}\x1B[0m`);
45
- };
46
- envServer.onReload = () => {
45
+ });
46
+ envServer.onReload(() => {
47
47
  console.log(`\x1B[2m↻ Reloading...\x1B[0m`);
48
- };
48
+ });
49
49
  await envServer.start();
50
50
  const server = serve({
51
51
  port: values.port,