env-runner 0.1.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,21 @@
1
1
  import { WorkerHooks } from "./types.mjs";
2
2
  import { BaseEnvRunner, EnvRunnerData } from "./common-base-runner.mjs";
3
+ import { RuntimeDep } from "./common-runtime-deps.mjs";
3
4
  import { IncomingMessage } from "node:http";
4
5
  import { Socket } from "node:net";
6
+ /**
7
+ * The `wrangler` package, as imported by the consumer.
8
+ *
9
+ * `wrangler` is not a dependency of `env-runner` — pass the module namespace
10
+ * (`import * as wrangler from "wrangler"`) or a specifier for it, so the
11
+ * dependency stays owned by the application. It is only imported here as a
12
+ * fallback when neither is passed.
13
+ */
14
+ interface WranglerModule {
15
+ unstable_readConfig?: (...args: any[]) => any;
16
+ unstable_getMiniflareWorkerOptions?: (...args: any[]) => any;
17
+ [key: string]: unknown;
18
+ }
5
19
  /** Raw (snake_case) Wrangler config object, mirroring `wrangler.json` contents. */
6
20
  type WranglerInlineConfig = Record<string, unknown>;
7
21
  /** Result from a module transform (compatible with Vite's `TransformResult`). */
@@ -12,10 +26,45 @@ interface TransformResult {
12
26
  interface MiniflareExportInfo {
13
27
  type?: "DurableObject" | "WorkerEntrypoint" | "class";
14
28
  }
29
+ /**
30
+ * The `miniflare` package, as imported by the consumer.
31
+ *
32
+ * `miniflare` is not a dependency of `env-runner` — pass the module namespace
33
+ * (`import * as miniflare from "miniflare"`) so the dependency stays owned by
34
+ * the application. The runner only falls back to importing it itself when this
35
+ * is omitted.
36
+ */
37
+ interface MiniflareModule {
38
+ Miniflare: new (options: any) => any;
39
+ /**
40
+ * Newest compatibility date supported by the installed `workerd` binary,
41
+ * clamped to today. Used as the default `compatibilityDate`.
42
+ */
43
+ supportedCompatibilityDate?: string;
44
+ [key: string]: unknown;
45
+ }
15
46
  interface MiniflareEnvRunnerOptions {
16
47
  name: string;
17
48
  hooks?: WorkerHooks;
18
49
  data?: EnvRunnerData;
50
+ /**
51
+ * The `miniflare` package: the imported module, or a specifier for it.
52
+ *
53
+ * ```ts
54
+ * import * as miniflare from "miniflare";
55
+ * new MiniflareEnvRunner({ name: "app", miniflare, data: { entry } });
56
+ *
57
+ * // or, equivalently
58
+ * new MiniflareEnvRunner({ name: "app", miniflare: "miniflare", data: { entry } });
59
+ * ```
60
+ *
61
+ * Passing it explicitly is preferred (`miniflare` is not a dependency of
62
+ * `env-runner`, so the version you install is the version that runs). Bare
63
+ * specifiers resolve from the current working directory. When omitted, the
64
+ * runner falls back to importing `miniflare` itself and only fails if that
65
+ * is unavailable too.
66
+ */
67
+ miniflare?: RuntimeDep<MiniflareModule>;
19
68
  /** Options passed directly to the Miniflare constructor. */
20
69
  miniflareOptions?: Record<string, unknown>;
21
70
  /**
@@ -68,12 +117,12 @@ interface MiniflareEnvRunnerOptions {
68
117
  * merged on top of it (inline wins per key, binding records merge,
69
118
  * `compatibilityFlags` are unioned).
70
119
  *
71
- * The installed `wrangler` package is preferred (full fidelity: TOML,
120
+ * The `wrangler` package is used for full fidelity when available (TOML,
72
121
  * `env` inheritance, `.dev.vars`, every binding type; an inline config is
73
- * normalized through a short-lived temp file). When `wrangler` is not
74
- * installed, a built-in minimal reader handles plain JSON files and inline
75
- * objects (common fields only) and a one-time warning is logged. JSONC and
76
- * TOML files without `wrangler` are skipped with a warning. Values from
122
+ * normalized through a short-lived temp file) — passed explicitly as
123
+ * `wranglerModule`, or imported optionally. Otherwise a built-in minimal
124
+ * reader handles plain JSON files and inline objects (common fields only);
125
+ * JSONC and TOML files are skipped with a warning. Values from
77
126
  * `miniflareOptions` always win over config-derived ones; binding records
78
127
  * (e.g. `bindings`) merge per key and `compatibilityFlags` are unioned.
79
128
  */
@@ -83,6 +132,16 @@ interface MiniflareEnvRunnerOptions {
83
132
  * Defaults to the `CLOUDFLARE_ENV` environment variable.
84
133
  */
85
134
  wranglerEnv?: string;
135
+ /**
136
+ * The imported `wrangler` package (`import * as wrangler from "wrangler"`),
137
+ * used to parse the {@link MiniflareEnvRunnerOptions.wrangler} config with
138
+ * full fidelity. When omitted, `import("wrangler")` is tried, and a built-in
139
+ * minimal reader (plain JSON configs and inline objects) handles the rest.
140
+ *
141
+ * Pass `false` to skip the `wrangler` package entirely and always use the
142
+ * built-in minimal reader.
143
+ */
144
+ wranglerModule?: RuntimeDep<WranglerModule>;
86
145
  }
87
146
  declare class MiniflareEnvRunner extends BaseEnvRunner {
88
147
  #private;
@@ -124,4 +183,4 @@ declare class MiniflareEnvRunner extends BaseEnvRunner {
124
183
  };
125
184
  }): Promise<void>;
126
185
  }
127
- export { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult, WranglerInlineConfig };
186
+ export { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, MiniflareModule, TransformResult, WranglerInlineConfig, WranglerModule };
@@ -1,5 +1,6 @@
1
1
  import { expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";
2
2
  import { BaseEnvRunner } from "./common-base-runner.mjs";
3
+ import { resolveRuntimeDep } from "./common-runtime-deps.mjs";
3
4
  import { init, parse } from "./libs/cjs-module-lexer.mjs";
4
5
  import { init as init$1, parse as parse$1 } from "./libs/es-module-lexer.mjs";
5
6
  import { isVirtualSpecifier } from "./common-worker-utils.mjs";
@@ -7,8 +8,8 @@ import { createRequire } from "node:module";
7
8
  import { proxyUpgrade } from "httpxy";
8
9
  import { fileURLToPath } from "node:url";
9
10
  import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
10
- import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
11
11
  import { resolveModulePath } from "exsolve";
12
+ import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
12
13
  import { tmpdir } from "node:os";
13
14
  const IPC_PATH = "/__env_runner_ipc";
14
15
  const IPC_BINDING = "__ENV_RUNNER_IPC";
@@ -180,10 +181,6 @@ export default {
180
181
  };
181
182
  `;
182
183
  }
183
- function importWrangler() {
184
- return import("wrangler");
185
- }
186
- let _warnedNoWrangler = false;
187
184
  const WRANGLER_CONFIG_FILENAMES = [
188
185
  "wrangler.json",
189
186
  "wrangler.jsonc",
@@ -207,7 +204,7 @@ function isPlainObject(value) {
207
204
  function isInlineWranglerConfig(opt) {
208
205
  return isPlainObject(opt);
209
206
  }
210
- async function loadWranglerConfig(opt, env, entryPath) {
207
+ async function loadWranglerConfig(opt, env, entryPath, wranglerModule) {
211
208
  if (!opt) return;
212
209
  const inline = isInlineWranglerConfig(opt) ? opt : void 0;
213
210
  let configPath;
@@ -224,16 +221,12 @@ async function loadWranglerConfig(opt, env, entryPath) {
224
221
  return;
225
222
  }
226
223
  } else configPath = findWranglerConfig(entryPath);
227
- let wrangler;
228
- try {
229
- wrangler = await importWrangler();
230
- } catch {
231
- if (!_warnedNoWrangler) {
232
- _warnedNoWrangler = true;
233
- console.warn("[env-runner] 'wrangler' is not installed; using the built-in minimal config reader (plain JSON, common fields only). Install 'wrangler' for full fidelity (JSONC, TOML, env inheritance, all binding types).");
234
- }
235
- return mergeWranglerMiniflareOptions(configPath ? readWranglerConfigMinimal(configPath, env) : void 0, inline ? mapWranglerConfigToMiniflare(applyWranglerEnv(inline, env)) : void 0);
236
- }
224
+ const wrangler = await resolveRuntimeDep({
225
+ name: "wrangler",
226
+ option: "wranglerModule",
227
+ value: wranglerModule
228
+ });
229
+ if (!wrangler?.unstable_readConfig || !wrangler.unstable_getMiniflareWorkerOptions) return mergeWranglerMiniflareOptions(configPath ? readWranglerConfigMinimal(configPath, env) : void 0, inline ? mapWranglerConfigToMiniflare(applyWranglerEnv(inline, env)) : void 0);
237
230
  try {
238
231
  return mergeWranglerMiniflareOptions(configPath ? pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(wrangler.unstable_readConfig({
239
232
  config: configPath,
@@ -374,11 +367,14 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
374
367
  #exportConditions;
375
368
  #wrangler;
376
369
  #wranglerEnv;
370
+ #wranglerModule;
371
+ #miniflareModule;
377
372
  constructor(opts) {
378
373
  super({
379
374
  ...opts,
380
375
  workerEntry: ""
381
376
  });
377
+ this.#miniflareModule = opts.miniflare;
382
378
  this.#miniflareOptions = opts.miniflareOptions || {};
383
379
  this.#transformRequest = opts.transformRequest;
384
380
  this.#persistent = opts.persistent ?? false;
@@ -387,6 +383,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
387
383
  this.#exportConditions = opts.exportConditions ?? ["workerd", "worker"];
388
384
  this.#wrangler = opts.wrangler ?? false;
389
385
  this.#wranglerEnv = opts.wranglerEnv ?? process.env.CLOUDFLARE_ENV;
386
+ this.#wranglerModule = opts.wranglerModule;
390
387
  this._initWithVirtualData(() => this.#init());
391
388
  }
392
389
  static async disposeAll() {
@@ -482,6 +479,17 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
482
479
  } else await this.#miniflare.dispose();
483
480
  this.#miniflare = void 0;
484
481
  }
482
+ async #resolveMiniflare() {
483
+ this.#miniflareModule = await resolveRuntimeDep({
484
+ name: "miniflare",
485
+ option: "miniflare",
486
+ value: this.#miniflareModule,
487
+ expect: "Miniflare",
488
+ required: true,
489
+ hint: "MiniflareEnvRunner cannot run without it (`import * as miniflare from \"miniflare\"`)."
490
+ });
491
+ return this.#miniflareModule;
492
+ }
485
493
  #init() {
486
494
  this.#initAsync().catch((error) => {
487
495
  console.error("Miniflare runner init error:", error);
@@ -517,11 +525,11 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
517
525
  });
518
526
  }
519
527
  async #initAsync() {
520
- const { Miniflare, supportedCompatibilityDate } = await import("miniflare");
528
+ const { Miniflare, supportedCompatibilityDate } = await this.#resolveMiniflare();
521
529
  const entryPath = this._data?.entry;
522
530
  const virtual = await this.#prepareVirtualModules();
523
531
  this.#virtual = virtual;
524
- const wranglerOptions = await loadWranglerConfig(this.#wrangler, this.#wranglerEnv, entryPath);
532
+ const wranglerOptions = await loadWranglerConfig(this.#wrangler, this.#wranglerEnv, entryPath, this.#wranglerModule);
525
533
  const userFlags = this.#miniflareOptions.compatibilityFlags || [];
526
534
  const wranglerFlags = wranglerOptions?.compatibilityFlags || [];
527
535
  const userDirectSockets = this.#miniflareOptions.unsafeDirectSockets || [];
@@ -662,7 +670,8 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
662
670
  }
663
671
  } else {
664
672
  const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
665
- const referrerDir = dirname(modulePathMap.get(referrerKey) || (referrer.startsWith("/") ? referrer : "/" + referrer));
673
+ const referrerReal = modulePathMap.get(referrerKey) || (referrer.startsWith("/") ? referrer : "/" + referrer);
674
+ const referrerDir = dirname(referrerReal);
666
675
  const raw = cleanRaw || cleanSpecifier;
667
676
  if (raw.startsWith(".")) resolvedPath = resolve(referrerDir, raw);
668
677
  else if (cleanSpecifier.startsWith("/")) resolvedPath = cleanSpecifier;
@@ -1,14 +1,39 @@
1
1
  import { WorkerHooks } from "./types.mjs";
2
2
  import { EnvRunnerData } from "./common-base-runner.mjs";
3
3
  import { NodeWorkerEnvRunner } from "./node-worker-runner.mjs";
4
+ interface NetlifyEnvRunnerOptions {
5
+ name: string;
6
+ workerEntry?: string;
7
+ hooks?: WorkerHooks;
8
+ data?: EnvRunnerData;
9
+ /**
10
+ * Module specifier for the `@netlify/runtime` package, used inside the
11
+ * worker to call `startRuntime()` (full `globalThis.Netlify` + `caches`
12
+ * setup). `env-runner` does not depend on `@netlify/runtime`.
13
+ *
14
+ * Unlike the other runtime-dependency options, this one takes a **specifier
15
+ * only**: the runtime has to be instantiated inside the worker thread, and a
16
+ * live module instance cannot cross that boundary.
17
+ *
18
+ * ```ts
19
+ * new NetlifyEnvRunner({
20
+ * name: "app",
21
+ * netlifyRuntime: import.meta.resolve("@netlify/runtime"),
22
+ * data: { entry },
23
+ * });
24
+ * ```
25
+ *
26
+ * A bare specifier (e.g. `"@netlify/runtime"`) is resolved from the current
27
+ * working directory. When omitted, the worker tries
28
+ * `import("@netlify/runtime")` and falls back to a lightweight
29
+ * `globalThis.Netlify` shim (env access only) if it isn't installed. Pass
30
+ * `false` to always use the shim.
31
+ */
32
+ netlifyRuntime?: string | URL | false;
33
+ }
4
34
  declare class NetlifyEnvRunner extends NodeWorkerEnvRunner {
5
- constructor(opts: {
6
- name: string;
7
- workerEntry?: string;
8
- hooks?: WorkerHooks;
9
- data?: EnvRunnerData;
10
- });
35
+ constructor(opts: NetlifyEnvRunnerOptions);
11
36
  fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
12
37
  protected _runtimeType(): string;
13
38
  }
14
- export { NetlifyEnvRunner };
39
+ export { NetlifyEnvRunner, NetlifyEnvRunnerOptions };
@@ -1,12 +1,18 @@
1
+ import { resolveRuntimeDepSpecifier } from "./common-runtime-deps.mjs";
1
2
  import { NodeWorkerEnvRunner } from "./node-worker-runner.mjs";
2
3
  import { fileURLToPath } from "node:url";
3
4
  let _defaultEntry;
4
5
  var NetlifyEnvRunner = class extends NodeWorkerEnvRunner {
5
6
  constructor(opts) {
6
7
  _defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/netlify/worker"));
8
+ const netlifyRuntime = resolveNetlifyRuntime(opts.netlifyRuntime);
7
9
  super({
8
10
  ...opts,
9
- workerEntry: opts.workerEntry || _defaultEntry
11
+ workerEntry: opts.workerEntry || _defaultEntry,
12
+ data: netlifyRuntime === void 0 ? opts.data : {
13
+ ...opts.data,
14
+ netlifyRuntime
15
+ }
10
16
  });
11
17
  }
12
18
  async fetch(input, init) {
@@ -43,4 +49,7 @@ var NetlifyEnvRunner = class extends NodeWorkerEnvRunner {
43
49
  return "netlify";
44
50
  }
45
51
  };
52
+ function resolveNetlifyRuntime(runtime) {
53
+ return resolveRuntimeDepSpecifier(runtime, "netlifyRuntime");
54
+ }
46
55
  export { NetlifyEnvRunner };
@@ -1,4 +1,34 @@
1
1
  import { watch } from "node:fs";
2
+ async function createRunnerWSProxyPlugin(getRunner) {
3
+ const isBun = "Bun" in globalThis;
4
+ const isDeno = "Deno" in globalThis;
5
+ if (!isBun && !isDeno) return (server) => {
6
+ server.ready().then(() => {
7
+ (server.node?.server)?.on("upgrade", (req, socket, head) => {
8
+ getRunner()?.upgrade?.({ node: {
9
+ req,
10
+ socket,
11
+ head
12
+ } });
13
+ });
14
+ }).catch(() => {});
15
+ };
16
+ const { createWebSocketProxy } = await import("crossws");
17
+ const { plugin } = isBun ? await import("crossws/server/bun") : await import("crossws/server/deno");
18
+ const proxy = createWebSocketProxy({ target: async (peer) => {
19
+ await getRunner()?.waitForReady?.().catch(() => {});
20
+ return resolveWSProxyTarget(getRunner()?.address, peer.request.url);
21
+ } });
22
+ return plugin({ resolve: () => proxy });
23
+ }
24
+ function resolveWSProxyTarget(address, requestUrl) {
25
+ if (!address) throw new Error("env runner worker is not ready");
26
+ const { pathname, search } = new URL(requestUrl);
27
+ if (address.socketPath) return `ws+unix://${address.socketPath}:${pathname}${search}`;
28
+ if (!address.port) throw new Error("env runner worker is not ready");
29
+ const host = address.host || "127.0.0.1";
30
+ return `ws://${host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${address.port}${pathname}${search}`;
31
+ }
2
32
  var RunnerManager = class {
3
33
  _runner;
4
34
  _messageQueue = [];
@@ -9,6 +39,7 @@ var RunnerManager = class {
9
39
  _pendingModuleReload;
10
40
  _closeListeners = /* @__PURE__ */ new Set();
11
41
  _readyListeners = /* @__PURE__ */ new Set();
42
+ _readyRejectors = /* @__PURE__ */ new Set();
12
43
  constructor(runner) {
13
44
  if (runner) this._attach(runner);
14
45
  }
@@ -18,6 +49,9 @@ var RunnerManager = class {
18
49
  get closed() {
19
50
  return this._closed;
20
51
  }
52
+ get address() {
53
+ return this._runner?.address;
54
+ }
21
55
  async reload(runner) {
22
56
  this._reloading = true;
23
57
  try {
@@ -49,9 +83,17 @@ var RunnerManager = class {
49
83
  });
50
84
  return this._pendingModuleReload;
51
85
  }
52
- upgrade = (context) => {
53
- this._runner?.upgrade?.(context);
86
+ upgrade = async (context) => {
87
+ const runner = await this._waitForRunner();
88
+ if (!runner?.upgrade) {
89
+ context.node.socket.destroy();
90
+ return;
91
+ }
92
+ await runner.upgrade(context);
54
93
  };
94
+ wsSrvxPlugin() {
95
+ return createRunnerWSProxyPlugin(() => this);
96
+ }
55
97
  sendMessage(message) {
56
98
  if (!this._runner || !this._runner.ready) {
57
99
  this._messageQueue.push(message);
@@ -67,21 +109,31 @@ var RunnerManager = class {
67
109
  this._messageListeners.delete(listener);
68
110
  this._runner?.offMessage(listener);
69
111
  }
70
- waitForReady(timeout = 5e3) {
112
+ waitForReady(timeout = 15e3) {
71
113
  if (this.ready) return Promise.resolve();
114
+ if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
72
115
  return new Promise((resolve, reject) => {
116
+ const cleanup = () => {
117
+ clearTimeout(timer);
118
+ this.offMessage(listener);
119
+ this._readyRejectors.delete(onClose);
120
+ };
73
121
  const timer = setTimeout(() => {
74
- this._messageListeners.delete(listener);
122
+ cleanup();
75
123
  reject(/* @__PURE__ */ new Error("Runner did not become ready in time"));
76
124
  }, timeout);
77
125
  const listener = (message) => {
78
126
  if (message?.address || this.ready) {
79
- clearTimeout(timer);
80
- this._messageListeners.delete(listener);
127
+ cleanup();
81
128
  resolve();
82
129
  }
83
130
  };
84
- this._messageListeners.add(listener);
131
+ const onClose = () => {
132
+ cleanup();
133
+ reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
134
+ };
135
+ this.onMessage(listener);
136
+ this._readyRejectors.add(onClose);
85
137
  });
86
138
  }
87
139
  rpc(name, data, opts) {
@@ -124,6 +176,8 @@ var RunnerManager = class {
124
176
  async close() {
125
177
  this._closed = true;
126
178
  this._messageQueue.length = 0;
179
+ for (const rejectReady of this._readyRejectors) rejectReady();
180
+ this._readyRejectors.clear();
127
181
  const runner = this._runner;
128
182
  this._detach();
129
183
  if (runner) await runner.close();
@@ -246,6 +300,7 @@ var EnvServer = class extends RunnerManager {
246
300
  }
247
301
  async _createRunner() {
248
302
  return loadRunner(this._opts.runner || "node-worker", {
303
+ ...this._opts.runnerOptions,
249
304
  name: this._opts.name || this._opts.entry,
250
305
  hooks: this._opts.hooks,
251
306
  data: {
@@ -62,6 +62,8 @@ interface EnvRunner extends RunnerRPCHooks, AsyncDisposable {
62
62
  readonly ready: boolean;
63
63
  /** Whether the runner has been closed. */
64
64
  readonly closed: boolean;
65
+ /** Address the worker is listening at, once ready (`undefined` before then). */
66
+ readonly address?: WorkerAddress;
65
67
  /** Proxy an HTTP request to the worker. */
66
68
  fetch: FetchHandler;
67
69
  /** Proxy a WebSocket upgrade request to the worker. */
@@ -44,7 +44,9 @@ function _checkVercelOidcToken(token = process.env.VERCEL_OIDC_TOKEN) {
44
44
  let _defaultEntry;
45
45
  const _podId = Math.random().toString(32).slice(-5);
46
46
  function generateVercelId() {
47
- return `dev1::${_podId}-${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
47
+ const timestamp = Date.now().toString(36);
48
+ const random = randomBytes(6).toString("hex");
49
+ return `dev1::${_podId}-${timestamp}-${random}`;
48
50
  }
49
51
  var VercelEnvRunner = class extends NodeWorkerEnvRunner {
50
52
  constructor(opts) {
package/dist/cli.d.mts CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {}
package/dist/cli.mjs CHANGED
@@ -50,16 +50,10 @@ const server = serve({
50
50
  port: values.port,
51
51
  hostname: values.host,
52
52
  gracefulShutdown: false,
53
- fetch: (request) => envServer.fetch(request)
53
+ fetch: (request) => envServer.fetch(request),
54
+ plugins: [await envServer.wsSrvxPlugin()]
54
55
  });
55
56
  await server.ready();
56
- server.node?.server?.on("upgrade", (req, socket, head) => {
57
- envServer.upgrade({ node: {
58
- req,
59
- socket,
60
- head
61
- } });
62
- });
63
57
  for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, async () => {
64
58
  console.log(`\n\x1B[2mShutting down...\x1B[0m`);
65
59
  await envServer.close();
package/dist/index.d.mts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { EnvRunner, FetchHandler, NodeUpgradeContext, RPCOptions, RunnerMessageListener, RunnerRPCHooks, UpgradeContext, UpgradeHandler, WorkerAddress, WorkerHooks } from "./_chunks/types.mjs";
2
2
  import { BaseEnvRunner, EnvRunnerData, VirtualModuleSource, VirtualModules } from "./_chunks/common-base-runner.mjs";
3
+ import { RuntimeDep, resolveRuntimeDep } from "./_chunks/common-runtime-deps.mjs";
3
4
  import { DenoProcessEnvRunner } from "./_chunks/deno-process-runner.mjs";
4
5
  import { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult } from "./_chunks/miniflare-runner.mjs";
5
6
  import { VercelEnvRunner } from "./_chunks/vercel-runner.mjs";
6
7
  import { NetlifyEnvRunner } from "./_chunks/netlify-runner.mjs";
7
- import { ServerOptions } from "srvx";
8
+ import { ServerOptions, ServerPlugin } from "srvx";
8
9
  import { Hooks } from "crossws";
9
10
  /**
10
11
  * Manages an active `EnvRunner` instance, proxying all calls to it.
@@ -20,9 +21,11 @@ declare class RunnerManager implements EnvRunner, AsyncDisposable {
20
21
  private _pendingModuleReload;
21
22
  private _closeListeners;
22
23
  private _readyListeners;
24
+ private _readyRejectors;
23
25
  constructor(runner?: EnvRunner);
24
26
  get ready(): boolean;
25
27
  get closed(): boolean;
28
+ get address(): WorkerAddress | undefined;
26
29
  /**
27
30
  * Replace the active runner with a new one. Closes the previous runner.
28
31
  *
@@ -41,6 +44,14 @@ declare class RunnerManager implements EnvRunner, AsyncDisposable {
41
44
  */
42
45
  private _flushInvalidation;
43
46
  upgrade: UpgradeHandler;
47
+ /**
48
+ * Create a runtime-native WebSocket reverse-proxy plugin for the public srvx
49
+ * server. Attach it via `serve({ plugins: [await manager.wsSrvxPlugin()] })`:
50
+ * on Node it proxies the raw upgrade socket to the worker, and on Bun/Deno it
51
+ * bridges the WebSocket with crossws. The plugin reads the active runner
52
+ * lazily, so it keeps working across hot-reloads.
53
+ */
54
+ wsSrvxPlugin(): Promise<ServerPlugin>;
44
55
  sendMessage(message: unknown): void;
45
56
  onMessage(listener: RunnerMessageListener): void;
46
57
  offMessage(listener: RunnerMessageListener): void;
@@ -74,7 +85,11 @@ interface LoadRunnerOptions {
74
85
  hooks?: WorkerHooks;
75
86
  data?: EnvRunnerData;
76
87
  execArgv?: string[];
77
- /** Additional runner-specific options (passed through to the runner constructor). */
88
+ /**
89
+ * Additional runner-specific options (passed through to the runner
90
+ * constructor) — e.g. `miniflare` (the imported `miniflare` package) for the
91
+ * `miniflare` runner, which this package never imports itself.
92
+ */
78
93
  [key: string]: unknown;
79
94
  }
80
95
  declare function loadRunner(runner: RunnerName, opts: LoadRunnerOptions): Promise<EnvRunner>;
@@ -95,6 +110,14 @@ interface EnvServerOptions {
95
110
  watch?: boolean;
96
111
  /** Additional paths to watch (directories or files). */
97
112
  watchPaths?: string[];
113
+ /**
114
+ * Extra runner-specific constructor options, spread into the runner.
115
+ *
116
+ * Runners that build on external packages take them as explicit options —
117
+ * e.g. `{ miniflare }` for the `miniflare` runner (env-runner never imports
118
+ * those packages itself).
119
+ */
120
+ runnerOptions?: Record<string, unknown>;
98
121
  }
99
122
  declare class EnvServer extends RunnerManager {
100
123
  private _opts;
@@ -146,4 +169,4 @@ interface AppEntry {
146
169
  plugins?: ServerOptions["plugins"];
147
170
  ipc?: AppEntryIPC;
148
171
  }
149
- export { type AppEntry, type AppEntryIPC, type AppEntryIPCContext, BaseEnvRunner, DenoProcessEnvRunner, type EnvRunnerData as DenoProcessEnvRunnerData, type EnvRunner, type EnvRunnerData, EnvServer, type EnvServerOptions, type FetchHandler, type LoadRunnerOptions, MiniflareEnvRunner, type MiniflareEnvRunnerOptions, type MiniflareExportInfo, NetlifyEnvRunner, type NodeUpgradeContext, type RPCOptions, RunnerManager, type RunnerMessageListener, type RunnerName, type RunnerRPCHooks, type TransformResult, type UpgradeContext, type UpgradeHandler, VercelEnvRunner, type VirtualModuleSource, type VirtualModules, type WorkerAddress, type WorkerHooks, loadRunner };
172
+ export { type AppEntry, type AppEntryIPC, type AppEntryIPCContext, BaseEnvRunner, DenoProcessEnvRunner, type EnvRunnerData as DenoProcessEnvRunnerData, type EnvRunner, type EnvRunnerData, EnvServer, type EnvServerOptions, type FetchHandler, type LoadRunnerOptions, MiniflareEnvRunner, type MiniflareEnvRunnerOptions, type MiniflareExportInfo, NetlifyEnvRunner, type NodeUpgradeContext, type RPCOptions, RunnerManager, type RunnerMessageListener, type RunnerName, type RunnerRPCHooks, type RuntimeDep, type TransformResult, type UpgradeContext, type UpgradeHandler, VercelEnvRunner, type VirtualModuleSource, type VirtualModules, type WorkerAddress, type WorkerHooks, loadRunner, resolveRuntimeDep };
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import { BaseEnvRunner } from "./_chunks/common-base-runner.mjs";
2
2
  import { EnvServer, RunnerManager, loadRunner } from "./_chunks/server.mjs";
3
+ import { resolveRuntimeDep } from "./_chunks/common-runtime-deps.mjs";
3
4
  import { DenoProcessEnvRunner } from "./_chunks/deno-process-runner.mjs";
4
5
  import { MiniflareEnvRunner } from "./_chunks/miniflare-runner.mjs";
5
6
  import { VercelEnvRunner } from "./_chunks/vercel-runner.mjs";
6
7
  import { NetlifyEnvRunner } from "./_chunks/netlify-runner.mjs";
7
- export { BaseEnvRunner, DenoProcessEnvRunner, EnvServer, MiniflareEnvRunner, NetlifyEnvRunner, RunnerManager, VercelEnvRunner, loadRunner };
8
+ export { BaseEnvRunner, DenoProcessEnvRunner, EnvServer, MiniflareEnvRunner, NetlifyEnvRunner, RunnerManager, VercelEnvRunner, loadRunner, resolveRuntimeDep };
@@ -1 +1 @@
1
- export { };
1
+ export {}
@@ -1 +1 @@
1
- export { };
1
+ export {}
@@ -1,3 +1,3 @@
1
1
  import { EnvRunnerData } from "../../_chunks/common-base-runner.mjs";
2
- import { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult, WranglerInlineConfig } from "../../_chunks/miniflare-runner.mjs";
3
- export { MiniflareEnvRunner, type EnvRunnerData as MiniflareEnvRunnerData, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult, type WranglerInlineConfig };
2
+ import { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, MiniflareModule, TransformResult, WranglerInlineConfig, WranglerModule } from "../../_chunks/miniflare-runner.mjs";
3
+ export { MiniflareEnvRunner, type EnvRunnerData as MiniflareEnvRunnerData, MiniflareEnvRunnerOptions, MiniflareExportInfo, MiniflareModule, TransformResult, type WranglerInlineConfig, type WranglerModule };
@@ -1,3 +1,3 @@
1
1
  import { EnvRunnerData } from "../../_chunks/common-base-runner.mjs";
2
- import { NetlifyEnvRunner } from "../../_chunks/netlify-runner.mjs";
3
- export { type EnvRunnerData, NetlifyEnvRunner };
2
+ import { NetlifyEnvRunner, NetlifyEnvRunnerOptions } from "../../_chunks/netlify-runner.mjs";
3
+ export { type EnvRunnerData, NetlifyEnvRunner, NetlifyEnvRunnerOptions };
@@ -1 +1 @@
1
- export { };
1
+ export {}
@@ -1,41 +1,40 @@
1
- try {
2
- const { startRuntime } = await import("@netlify/runtime");
1
+ import { resolveRuntimeDep } from "../../_chunks/common-runtime-deps.mjs";
2
+ import { workerData } from "node:worker_threads";
3
+ const netlifyEnv = {
4
+ get: (key) => process.env[key],
5
+ has: (key) => key in process.env,
6
+ set: (key, value) => {
7
+ process.env[key] = value;
8
+ },
9
+ delete: (key) => {
10
+ delete process.env[key];
11
+ },
12
+ toObject: () => ({ ...process.env })
13
+ };
14
+ const runtimeSpecifier = (workerData || {}).netlifyRuntime;
15
+ let started = false;
16
+ if (runtimeSpecifier !== false) try {
17
+ const startRuntime = (await resolveRuntimeDep({
18
+ name: "@netlify/runtime",
19
+ option: "netlifyRuntime",
20
+ value: runtimeSpecifier,
21
+ expect: "startRuntime"
22
+ }))?.startRuntime;
23
+ if (!startRuntime) throw new Error("`@netlify/runtime` is not installed");
3
24
  startRuntime({
4
25
  deployID: "0",
5
26
  siteID: "0",
6
- env: {
7
- get: (key) => process.env[key],
8
- has: (key) => key in process.env,
9
- set: (key, value) => {
10
- process.env[key] = value;
11
- },
12
- delete: (key) => {
13
- delete process.env[key];
14
- },
15
- toObject: () => ({ ...process.env })
16
- },
27
+ env: netlifyEnv,
17
28
  getRequestContext: () => null,
18
29
  cache: { getCacheAPIContext: () => null }
19
30
  });
20
- } catch {
21
- if (!process.env.__ENV_RUNNER_NETLIFY_WARNED) {
22
- process.env.__ENV_RUNNER_NETLIFY_WARNED = "1";
23
- console.warn("@netlify/runtime is not installed. Install it for full Netlify runtime emulation: npx nypm i -D @netlify/runtime");
24
- }
25
- globalThis.Netlify = {
26
- context: null,
27
- env: {
28
- get: (key) => process.env[key],
29
- has: (key) => key in process.env,
30
- set: (key, value) => {
31
- process.env[key] = value;
32
- },
33
- delete: (key) => {
34
- delete process.env[key];
35
- },
36
- toObject: () => ({ ...process.env })
37
- }
38
- };
31
+ started = true;
32
+ } catch (error) {
33
+ if (runtimeSpecifier) console.warn(`[env-runner] failed to start the Netlify runtime from "${runtimeSpecifier}": ${error?.cause?.message || error?.message || error}`);
39
34
  }
35
+ if (!started) globalThis.Netlify = {
36
+ context: null,
37
+ env: netlifyEnv
38
+ };
40
39
  await import("../node-worker/worker.mjs");
41
40
  export {};
@@ -1 +1 @@
1
- export { };
1
+ export {}