env-runner 0.1.6 → 0.1.8

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 (49) hide show
  1. package/README.md +39 -9
  2. package/dist/_chunks/base-runner.d.mts +0 -3
  3. package/dist/_chunks/base-runner.mjs +0 -2
  4. package/dist/_chunks/libs/cjs-module-lexer.mjs +0 -2
  5. package/dist/_chunks/runner.d.mts +0 -3
  6. package/dist/_chunks/runner.mjs +17 -8
  7. package/dist/_chunks/runner2.d.mts +8 -3
  8. package/dist/_chunks/runner2.mjs +17 -42
  9. package/dist/_chunks/runner3.d.mts +16 -0
  10. package/dist/_chunks/runner3.mjs +60 -0
  11. package/dist/_chunks/runner4.d.mts +14 -0
  12. package/dist/_chunks/runner4.mjs +93 -0
  13. package/dist/_chunks/runner5.d.mts +14 -0
  14. package/dist/_chunks/runner5.mjs +45 -0
  15. package/dist/_chunks/server.mjs +3 -15
  16. package/dist/_chunks/types.d.mts +0 -3
  17. package/dist/_chunks/worker-utils.mjs +0 -6
  18. package/dist/cli.mjs +0 -2
  19. package/dist/index.d.mts +6 -11
  20. package/dist/index.mjs +3 -2
  21. package/dist/runners/bun-process/runner.d.mts +0 -3
  22. package/dist/runners/bun-process/runner.mjs +0 -2
  23. package/dist/runners/bun-process/worker.mjs +0 -2
  24. package/dist/runners/deno-process/runner.d.mts +1 -1
  25. package/dist/runners/deno-process/runner.mjs +0 -1
  26. package/dist/runners/deno-process/worker.mjs +31 -10
  27. package/dist/runners/miniflare/runner.d.mts +1 -1
  28. package/dist/runners/miniflare/runner.mjs +0 -2
  29. package/dist/runners/netlify/runner.d.mts +3 -0
  30. package/dist/runners/netlify/runner.mjs +2 -0
  31. package/dist/runners/netlify/worker.d.mts +1 -0
  32. package/dist/runners/netlify/worker.mjs +41 -0
  33. package/dist/runners/node-process/runner.d.mts +0 -3
  34. package/dist/runners/node-process/runner.mjs +0 -2
  35. package/dist/runners/node-process/worker.mjs +0 -2
  36. package/dist/runners/node-worker/runner.d.mts +2 -18
  37. package/dist/runners/node-worker/runner.mjs +1 -61
  38. package/dist/runners/node-worker/worker.mjs +0 -2
  39. package/dist/runners/self/runner.d.mts +2 -7
  40. package/dist/runners/self/runner.mjs +0 -2
  41. package/dist/runners/vercel/queue-dev.d.mts +44 -0
  42. package/dist/runners/vercel/queue-dev.mjs +36 -0
  43. package/dist/runners/vercel/runner.d.mts +3 -0
  44. package/dist/runners/vercel/runner.mjs +2 -0
  45. package/dist/runners/vercel/worker.d.mts +1 -0
  46. package/dist/runners/vercel/worker.mjs +51 -0
  47. package/dist/vite.d.mts +0 -3
  48. package/dist/vite.mjs +0 -14
  49. package/package.json +34 -18
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
 
@@ -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):
@@ -1,8 +1,6 @@
1
1
  import { a as RunnerMessageListener, l as WorkerAddress, t as EnvRunner, u as WorkerHooks } from "./types.mjs";
2
2
  import { IncomingMessage } from "node:http";
3
3
  import { Socket } from "node:net";
4
-
5
- //#region src/common/base-runner.d.ts
6
4
  interface EnvRunnerData {
7
5
  name?: string;
8
6
  [key: string]: unknown;
@@ -45,5 +43,4 @@ declare abstract class BaseEnvRunner implements EnvRunner {
45
43
  protected abstract _closeRuntime(): Promise<void>;
46
44
  protected abstract _runtimeType(): string;
47
45
  }
48
- //#endregion
49
46
  export { EnvRunnerData as n, BaseEnvRunner as t };
@@ -1,6 +1,5 @@
1
1
  import { rm } from "node:fs/promises";
2
2
  import { proxyFetch, proxyUpgrade } from "httpxy";
3
- //#region src/common/base-runner.ts
4
3
  var BaseEnvRunner = class {
5
4
  closed = false;
6
5
  _name;
@@ -125,5 +124,4 @@ var BaseEnvRunner = class {
125
124
  this._address = void 0;
126
125
  }
127
126
  };
128
- //#endregion
129
127
  export { BaseEnvRunner as t };
@@ -1,4 +1,3 @@
1
- //#region node_modules/.pnpm/cjs-module-lexer@2.2.0/node_modules/cjs-module-lexer/dist/lexer.mjs
2
1
  let A;
3
2
  const B = 1 === new Uint8Array(new Uint16Array([1]).buffer)[0];
4
3
  function parse(E, g = "@") {
@@ -143,5 +142,4 @@ function init() {
143
142
  A = Q;
144
143
  })());
145
144
  }
146
- //#endregion
147
145
  export { parse as n, init as t };
@@ -1,7 +1,5 @@
1
1
  import { u as WorkerHooks } from "./types.mjs";
2
2
  import { n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
3
-
4
- //#region src/runners/deno-process/runner.d.ts
5
3
  declare class DenoProcessEnvRunner extends BaseEnvRunner {
6
4
  #private;
7
5
  constructor(opts: {
@@ -16,5 +14,4 @@ declare class DenoProcessEnvRunner extends BaseEnvRunner {
16
14
  protected _runtimeType(): string;
17
15
  protected _closeRuntime(): Promise<void>;
18
16
  }
19
- //#endregion
20
17
  export { DenoProcessEnvRunner as t };
@@ -2,7 +2,6 @@ import { t as 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";
5
- //#region src/runners/deno-process/runner.ts
6
5
  let _defaultEntry;
7
6
  var DenoProcessEnvRunner = class extends BaseEnvRunner {
8
7
  #process;
@@ -44,8 +43,9 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
44
43
  };
45
44
  const child = spawn("deno", [
46
45
  "run",
47
- "--allow-all",
46
+ "-A",
48
47
  "--node-modules-dir=auto",
48
+ "--no-lock",
49
49
  ...execArgv || [],
50
50
  this._workerEntry
51
51
  ], {
@@ -53,8 +53,7 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
53
53
  stdio: [
54
54
  "pipe",
55
55
  "pipe",
56
- "pipe",
57
- "ipc"
56
+ "pipe"
58
57
  ]
59
58
  });
60
59
  const exited = new Promise((resolve) => {
@@ -63,7 +62,9 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
63
62
  const handle = {
64
63
  pid: child.pid,
65
64
  kill: () => child.kill(),
66
- send: (message) => child.send(message),
65
+ send: (message) => {
66
+ child.stdin.write(JSON.stringify(message) + "\n");
67
+ },
67
68
  exited,
68
69
  _exitCode: void 0,
69
70
  removeAllListeners: () => child.removeAllListeners()
@@ -78,11 +79,19 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
78
79
  this.close(error);
79
80
  }
80
81
  });
81
- child.on("message", (message) => {
82
- this._handleMessage(message);
82
+ let buffer = "";
83
+ child.stdout.on("data", (chunk) => {
84
+ buffer += chunk.toString();
85
+ let newlineIdx;
86
+ while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
87
+ const line = buffer.slice(0, newlineIdx);
88
+ buffer = buffer.slice(newlineIdx + 1);
89
+ if (line.startsWith("{")) try {
90
+ this._handleMessage(JSON.parse(line));
91
+ } catch {}
92
+ }
83
93
  });
84
94
  this.#process = handle;
85
95
  }
86
96
  };
87
- //#endregion
88
97
  export { DenoProcessEnvRunner as t };
@@ -2,8 +2,6 @@ import { u as WorkerHooks } from "./types.mjs";
2
2
  import { n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
3
3
  import { IncomingMessage } from "node:http";
4
4
  import { Socket } from "node:net";
5
-
6
- //#region src/runners/miniflare/runner.d.ts
7
5
  /** Result from a module transform (compatible with Vite's `TransformResult`). */
8
6
  interface TransformResult {
9
7
  code: string;
@@ -46,6 +44,14 @@ interface MiniflareEnvRunnerOptions {
46
44
  persistent?: boolean;
47
45
  /** Wrap the user's `fetch` in a try/catch that returns structured JSON error responses. Default: `true`. */
48
46
  captureErrors?: boolean;
47
+ /**
48
+ * Export conditions for bare-specifier module resolution in the module
49
+ * fallback service. Ensures packages with conditional exports (e.g.
50
+ * `"workerd"`) resolve to the correct entry instead of the Node.js one.
51
+ *
52
+ * Defaults to `["workerd", "worker"]`.
53
+ */
54
+ exportConditions?: string[];
49
55
  }
50
56
  declare class MiniflareEnvRunner extends BaseEnvRunner {
51
57
  #private;
@@ -75,5 +81,4 @@ declare class MiniflareEnvRunner extends BaseEnvRunner {
75
81
  };
76
82
  }): Promise<void>;
77
83
  }
78
- //#endregion
79
84
  export { TransformResult as i, MiniflareEnvRunnerOptions as n, MiniflareExportInfo as r, MiniflareEnvRunner as t };
@@ -5,26 +5,9 @@ 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
- //#region src/runners/miniflare/wrapper.ts
9
- const IPC_PATH$1 = "/__env_runner_ipc";
10
- /** Service binding name used for cross-request IPC (worker → runner). */
8
+ import { resolveModulePath } from "exsolve";
9
+ const IPC_PATH = "/__env_runner_ipc";
11
10
  const IPC_BINDING = "__ENV_RUNNER_IPC";
12
- /**
13
- * Generates a wrapper module that imports the user entry and adds IPC glue.
14
- *
15
- * The user module is expected to export `fetch` and optionally `ipc`.
16
- * The wrapper uses a persistent WebSocket pair for bidirectional IPC:
17
- * - Init: `fetch` with `upgrade: websocket` creates a WebSocketPair
18
- * - Messages: JSON over the WebSocket (no per-message `dispatchFetch`)
19
- * - Reload: `{ type: "reload" }` triggers cache-busted re-import
20
- * - Shutdown: `{ type: "shutdown" }` calls `ipc.onClose()`
21
- *
22
- * For outgoing messages during fetch request handling, uses a service binding
23
- * (`__ENV_RUNNER_IPC`) to avoid workerd's cross-request I/O restriction on
24
- * the WebSocket object.
25
- *
26
- * Passed as an in-memory `script` to Miniflare (no temp files needed).
27
- */
28
11
  function generateWrapper(entryPath, opts) {
29
12
  const staticReExport = opts?.dynamicOnly ? "" : `export * from ${JSON.stringify(entryPath)};`;
30
13
  const explicitExports = opts?.dynamicOnly && opts.exports?.length ? opts.exports.map((name) => `export { ${name} } from ${JSON.stringify(entryPath)};`).join("\n") : "";
@@ -47,7 +30,7 @@ if (!globalThis.process) { globalThis.process = __process; }
47
30
  ${staticReExport}
48
31
  ${explicitExports}
49
32
 
50
- const __IPC_PATH = "${IPC_PATH$1}";
33
+ const __IPC_PATH = "${IPC_PATH}";
51
34
  const __IPC_BINDING = "${IPC_BINDING}";
52
35
  const __entryPath = ${JSON.stringify(entryPath)};
53
36
  let __userEntry;
@@ -193,9 +176,6 @@ export default {
193
176
  };
194
177
  `;
195
178
  }
196
- //#endregion
197
- //#region src/runners/miniflare/runner.ts
198
- const IPC_PATH = "/__env_runner_ipc";
199
179
  const _miniflareCache = /* @__PURE__ */ new Map();
200
180
  var MiniflareEnvRunner = class extends BaseEnvRunner {
201
181
  #miniflare;
@@ -207,6 +187,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
207
187
  #cacheKey;
208
188
  #exports;
209
189
  #captureErrors;
190
+ #exportConditions;
210
191
  constructor(opts) {
211
192
  super({
212
193
  ...opts,
@@ -217,15 +198,14 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
217
198
  this.#persistent = opts.persistent ?? false;
218
199
  this.#exports = opts.exports ?? {};
219
200
  this.#captureErrors = opts.captureErrors ?? true;
201
+ this.#exportConditions = opts.exportConditions ?? ["workerd", "worker"];
220
202
  this.#init();
221
203
  }
222
- /** Dispose all persistent Miniflare instances from the cache. */
223
204
  static async disposeAll() {
224
205
  const entries = [..._miniflareCache.values()];
225
206
  _miniflareCache.clear();
226
207
  for (const entry of entries) await entry.mf.dispose().catch(() => {});
227
208
  }
228
- /** Fully dispose the Miniflare instance (even if persistent). */
229
209
  async dispose() {
230
210
  if (this.#miniflare) {
231
211
  if (this.#ws) {
@@ -264,13 +244,6 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
264
244
  data: message
265
245
  }));
266
246
  }
267
- /**
268
- * Hot-reload the user entry module without recreating the Miniflare instance.
269
- *
270
- * Sends `reload-module` event over the WebSocket. The worker wrapper uses
271
- * `unsafeEvalBinding` to re-import the entry with a cache-busting query string
272
- * and responds with `module-reloaded` when done.
273
- */
274
247
  async reloadModule(timeout = 5e3) {
275
248
  if (!this.#ws) throw new Error("Miniflare env runner should be initialized before reloading.");
276
249
  if (!this._data?.entry) return;
@@ -399,6 +372,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
399
372
  if (!options.unsafeModuleFallbackService) {
400
373
  const _require = createRequire(resolvedEntry);
401
374
  const _transformRequest = this.#transformRequest;
375
+ const _exportConditions = this.#exportConditions;
402
376
  options.unsafeUseModuleFallbackService = true;
403
377
  const modulePathMap = /* @__PURE__ */ new Map();
404
378
  const _cjsLexerReady = ensureCjsLexer();
@@ -422,6 +396,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
422
396
  const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
423
397
  const referrerReal = modulePathMap.get(referrerKey);
424
398
  const contextRequire = referrerReal ? createRequire(referrerReal) : _require;
399
+ if (cleanRaw.startsWith("cloudflare:")) return new Response(null, { status: 404 });
425
400
  if (cleanRaw.startsWith("node:")) {
426
401
  const nodeName = cleanRaw.slice(5);
427
402
  try {
@@ -430,7 +405,11 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
430
405
  return new Response(null, { status: 404 });
431
406
  }
432
407
  } else try {
433
- resolvedPath = contextRequire.resolve(cleanRaw);
408
+ resolvedPath = resolveModulePath(cleanRaw, {
409
+ from: referrerReal || resolvedEntry,
410
+ conditions: _exportConditions,
411
+ try: true
412
+ }) || contextRequire.resolve(cleanRaw);
434
413
  } catch {
435
414
  const name = cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier;
436
415
  return Response.json({
@@ -486,7 +465,10 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
486
465
  }
487
466
  }
488
467
  if (this.#persistent && entryPath) {
489
- this.#cacheKey = computeCacheKey(entryPath, this.#miniflareOptions);
468
+ this.#cacheKey = computeCacheKey(entryPath, {
469
+ ...this.#miniflareOptions,
470
+ _exportConditions: this.#exportConditions
471
+ });
490
472
  const cached = _miniflareCache.get(this.#cacheKey);
491
473
  if (cached) {
492
474
  this.#miniflare = cached.mf;
@@ -501,7 +483,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
501
483
  refCount: 1
502
484
  });
503
485
  }
504
- const initRes = await this.#miniflare.dispatchFetch("http://localhost" + IPC_PATH, { headers: { upgrade: "websocket" } });
486
+ const initRes = await this.#miniflare.dispatchFetch("http://localhost/__env_runner_ipc", { headers: { upgrade: "websocket" } });
505
487
  const ws = initRes.webSocket;
506
488
  if (!ws) {
507
489
  const body = await initRes.text().catch(() => "");
@@ -521,10 +503,6 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
521
503
  } });
522
504
  }
523
505
  };
524
- /**
525
- * Detect `export class` declarations in the entry file.
526
- * Merges with explicitly declared exports from options.
527
- */
528
506
  function detectExportedClasses(entryPath, explicit) {
529
507
  const names = new Set(Object.keys(explicit));
530
508
  try {
@@ -535,11 +513,9 @@ function detectExportedClasses(entryPath, explicit) {
535
513
  } catch {}
536
514
  return [...names];
537
515
  }
538
- /** Convert PascalCase/camelCase to SCREAMING_SNAKE_CASE (e.g. `Counter` → `COUNTER`, `MyDurableObject` → `MY_DURABLE_OBJECT`). */
539
516
  function toScreamingSnakeCase(name) {
540
517
  return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
541
518
  }
542
- /** Compute a stable cache key for persistent Miniflare instances. */
543
519
  function computeCacheKey(entryPath, opts) {
544
520
  const serializableOpts = {};
545
521
  for (const [k, v] of Object.entries(opts)) if (typeof v !== "function") serializableOpts[k] = v;
@@ -560,5 +536,4 @@ function createCjsEsmShim(cjsSpecifier, contents) {
560
536
  for (const name of namedExports) shim += `export var ${name} = __cjs_mod__["${name}"];\n`;
561
537
  return shim;
562
538
  }
563
- //#endregion
564
539
  export { MiniflareEnvRunner as t };
@@ -0,0 +1,16 @@
1
+ import { u as WorkerHooks } from "./types.mjs";
2
+ import { n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
3
+ declare class NodeWorkerEnvRunner extends BaseEnvRunner {
4
+ #private;
5
+ constructor(opts: {
6
+ name: string;
7
+ workerEntry?: string;
8
+ hooks?: WorkerHooks;
9
+ data?: EnvRunnerData;
10
+ });
11
+ sendMessage(message: unknown): void;
12
+ protected _hasRuntime(): boolean;
13
+ protected _runtimeType(): string;
14
+ protected _closeRuntime(): Promise<void>;
15
+ }
16
+ export { NodeWorkerEnvRunner as t };
@@ -0,0 +1,60 @@
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
+ let _defaultEntry;
6
+ var NodeWorkerEnvRunner = class extends BaseEnvRunner {
7
+ #worker;
8
+ constructor(opts) {
9
+ _defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/node-worker/worker"));
10
+ super({
11
+ ...opts,
12
+ workerEntry: opts.workerEntry || _defaultEntry
13
+ });
14
+ this.#initWorker();
15
+ }
16
+ sendMessage(message) {
17
+ if (!this.#worker) throw new Error("Worker thread should be initialized before sending messages.");
18
+ this.#worker.postMessage(message);
19
+ }
20
+ _hasRuntime() {
21
+ return Boolean(this.#worker);
22
+ }
23
+ _runtimeType() {
24
+ return "worker";
25
+ }
26
+ async _closeRuntime() {
27
+ if (!this.#worker) return;
28
+ this.#worker.removeAllListeners();
29
+ await this.#worker.terminate().catch((error) => {
30
+ console.error(error);
31
+ });
32
+ this.#worker = void 0;
33
+ }
34
+ #initWorker() {
35
+ if (!existsSync(this._workerEntry)) {
36
+ this.close(`worker entry not found in "${this._workerEntry}".`);
37
+ return;
38
+ }
39
+ const worker = new Worker(this._workerEntry, {
40
+ env: { ...process.env },
41
+ workerData: {
42
+ name: this._name,
43
+ ...this._data
44
+ }
45
+ });
46
+ worker.once("exit", (code) => {
47
+ worker._exitCode = code;
48
+ this.close(`worker exited with code ${code}`);
49
+ });
50
+ worker.once("error", (error) => {
51
+ console.error(`Worker error:`, error);
52
+ this.close(error);
53
+ });
54
+ worker.on("message", (message) => {
55
+ this._handleMessage(message);
56
+ });
57
+ this.#worker = worker;
58
+ }
59
+ };
60
+ export { NodeWorkerEnvRunner as t };
@@ -0,0 +1,14 @@
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
+ declare class VercelEnvRunner extends NodeWorkerEnvRunner {
5
+ constructor(opts: {
6
+ name: string;
7
+ workerEntry?: string;
8
+ hooks?: WorkerHooks;
9
+ data?: EnvRunnerData;
10
+ });
11
+ fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
12
+ protected _runtimeType(): string;
13
+ }
14
+ export { VercelEnvRunner as t };
@@ -0,0 +1,93 @@
1
+ import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { randomBytes } from "node:crypto";
4
+ let _warned = false;
5
+ function warnIfVercelOidcTokenInvalid(token) {
6
+ const result = _checkVercelOidcToken(token);
7
+ if (_warned) return result;
8
+ if (result.status === "missing") {
9
+ _warned = true;
10
+ console.warn("[env-runner:vercel] VERCEL_OIDC_TOKEN is not set. Vercel SDK features (e.g. @vercel/functions waitUntil, cache) may not work. Run `vercel env pull` to set it.");
11
+ } else if (result.status === "expired") {
12
+ _warned = true;
13
+ console.warn(`[env-runner:vercel] VERCEL_OIDC_TOKEN expired at ${result.expiresAt.toISOString()}. Vercel SDK authentication will fail. Run \`vercel env pull\` to refresh it.`);
14
+ } else if (result.status === "invalid") {
15
+ _warned = true;
16
+ console.warn("[env-runner:vercel] VERCEL_OIDC_TOKEN is malformed (not a valid JWT). Vercel SDK authentication will fail. Run `vercel env pull` to get a fresh token.");
17
+ }
18
+ return result;
19
+ }
20
+ function _checkVercelOidcToken(token = process.env.VERCEL_OIDC_TOKEN) {
21
+ if (!token) return { status: "missing" };
22
+ const parts = token.split(".");
23
+ if (parts.length !== 3) return { status: "invalid" };
24
+ let payload;
25
+ try {
26
+ const json = Buffer.from(parts[1], "base64url").toString("utf8");
27
+ payload = JSON.parse(json);
28
+ } catch {
29
+ return { status: "invalid" };
30
+ }
31
+ if (!payload || typeof payload !== "object") return { status: "invalid" };
32
+ const exp = payload.exp;
33
+ if (typeof exp !== "number" || !Number.isFinite(exp)) return { status: "invalid" };
34
+ const expiresAt = /* @__PURE__ */ new Date(exp * 1e3);
35
+ if (expiresAt.getTime() <= Date.now()) return {
36
+ status: "expired",
37
+ expiresAt
38
+ };
39
+ return {
40
+ status: "valid",
41
+ expiresAt
42
+ };
43
+ }
44
+ let _defaultEntry;
45
+ const _podId = Math.random().toString(32).slice(-5);
46
+ function generateVercelId() {
47
+ return `dev1::${_podId}-${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
48
+ }
49
+ var VercelEnvRunner = class extends NodeWorkerEnvRunner {
50
+ constructor(opts) {
51
+ _defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/vercel/worker"));
52
+ super({
53
+ ...opts,
54
+ workerEntry: opts.workerEntry || _defaultEntry
55
+ });
56
+ warnIfVercelOidcTokenInvalid();
57
+ }
58
+ async fetch(input, init) {
59
+ const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
60
+ const requestId = generateVercelId();
61
+ if (this._address && this._address.port != null && !headers.has("x-vercel-deployment-url")) {
62
+ const host = this._address.host || "127.0.0.1";
63
+ headers.set("x-vercel-deployment-url", `http://${host}:${this._address.port}`);
64
+ }
65
+ if (!headers.has("x-vercel-id")) headers.set("x-vercel-id", requestId);
66
+ const clientIp = headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || "127.0.0.1";
67
+ if (!headers.has("x-vercel-forwarded-for")) headers.set("x-vercel-forwarded-for", clientIp);
68
+ if (!headers.has("x-forwarded-for")) headers.set("x-forwarded-for", clientIp);
69
+ if (!headers.has("x-real-ip")) headers.set("x-real-ip", clientIp);
70
+ try {
71
+ const url = new URL(input instanceof Request ? input.url : input.toString());
72
+ if (!headers.has("x-forwarded-proto")) headers.set("x-forwarded-proto", url.protocol.replace(":", ""));
73
+ if (!headers.has("x-forwarded-host")) headers.set("x-forwarded-host", headers.get("host") || url.host);
74
+ } catch {}
75
+ const res = await super.fetch(input, {
76
+ ...init,
77
+ headers
78
+ });
79
+ const resHeaders = new Headers(res.headers);
80
+ if (!resHeaders.has("server")) resHeaders.set("server", "Vercel");
81
+ if (!resHeaders.has("x-vercel-id")) resHeaders.set("x-vercel-id", requestId);
82
+ if (!resHeaders.has("x-vercel-cache")) resHeaders.set("x-vercel-cache", "MISS");
83
+ return new Response(res.body, {
84
+ status: res.status,
85
+ statusText: res.statusText,
86
+ headers: resHeaders
87
+ });
88
+ }
89
+ _runtimeType() {
90
+ return "vercel";
91
+ }
92
+ };
93
+ export { VercelEnvRunner as t };
@@ -0,0 +1,14 @@
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
+ declare class NetlifyEnvRunner extends NodeWorkerEnvRunner {
5
+ constructor(opts: {
6
+ name: string;
7
+ workerEntry?: string;
8
+ hooks?: WorkerHooks;
9
+ data?: EnvRunnerData;
10
+ });
11
+ fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
12
+ protected _runtimeType(): string;
13
+ }
14
+ export { NetlifyEnvRunner as t };
@@ -0,0 +1,45 @@
1
+ import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
2
+ import { fileURLToPath } from "node:url";
3
+ let _defaultEntry;
4
+ var NetlifyEnvRunner = class extends NodeWorkerEnvRunner {
5
+ constructor(opts) {
6
+ _defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/netlify/worker"));
7
+ super({
8
+ ...opts,
9
+ workerEntry: opts.workerEntry || _defaultEntry
10
+ });
11
+ }
12
+ async fetch(input, init) {
13
+ const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
14
+ const clientIp = headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || "127.0.0.1";
15
+ if (!headers.has("x-nf-client-connection-ip")) headers.set("x-nf-client-connection-ip", clientIp);
16
+ if (!headers.has("x-nf-account-id")) headers.set("x-nf-account-id", "0");
17
+ if (!headers.has("x-nf-site-id")) headers.set("x-nf-site-id", "0");
18
+ if (!headers.has("x-nf-deploy-id")) headers.set("x-nf-deploy-id", "0");
19
+ if (!headers.has("x-nf-deploy-context")) headers.set("x-nf-deploy-context", "dev");
20
+ if (!headers.has("x-nf-geo")) headers.set("x-nf-geo", btoa(JSON.stringify({
21
+ city: "localhost",
22
+ country: { code: "dev" }
23
+ })));
24
+ if (!headers.has("x-nf-request-id")) headers.set("x-nf-request-id", crypto.randomUUID());
25
+ if (!headers.has("x-forwarded-for")) headers.set("x-forwarded-for", clientIp);
26
+ if (!headers.has("x-real-ip")) headers.set("x-real-ip", clientIp);
27
+ try {
28
+ const url = new URL(input instanceof Request ? input.url : input.toString());
29
+ if (!headers.has("x-forwarded-proto")) headers.set("x-forwarded-proto", url.protocol.replace(":", ""));
30
+ if (!headers.has("x-forwarded-host")) headers.set("x-forwarded-host", headers.get("host") || url.host);
31
+ } catch {}
32
+ if (input instanceof Request) return super.fetch(new Request(input, {
33
+ ...init,
34
+ headers
35
+ }));
36
+ return super.fetch(input, {
37
+ ...init,
38
+ headers
39
+ });
40
+ }
41
+ _runtimeType() {
42
+ return "netlify";
43
+ }
44
+ };
45
+ export { NetlifyEnvRunner as t };