env-runner 0.2.3 → 0.3.1

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 (33) hide show
  1. package/README.md +370 -20
  2. package/dist/_chunks/common-base-runner.d.mts +486 -15
  3. package/dist/_chunks/common-base-runner.mjs +712 -32
  4. package/dist/_chunks/common-process-data.mjs +13 -0
  5. package/dist/_chunks/common-worker-utils.mjs +47 -114
  6. package/dist/_chunks/deno-process-runner.d.mts +4 -1
  7. package/dist/_chunks/deno-process-runner.mjs +9 -6
  8. package/dist/_chunks/lexer.mjs +2 -0
  9. package/dist/_chunks/libs/cjs-module-lexer.mjs +6 -1
  10. package/dist/_chunks/miniflare-runner.d.mts +26 -14
  11. package/dist/_chunks/miniflare-runner.mjs +430 -134
  12. package/dist/_chunks/netlify-runner.d.mts +3 -1
  13. package/dist/_chunks/node-worker-runner.d.mts +3 -1
  14. package/dist/_chunks/node-worker-runner.mjs +8 -4
  15. package/dist/_chunks/plugin-hooks.mjs +1656 -0
  16. package/dist/_chunks/server.mjs +21 -5
  17. package/dist/_chunks/types.d.mts +37 -1
  18. package/dist/_chunks/vercel-runner.d.mts +3 -1
  19. package/dist/cli.mjs +0 -0
  20. package/dist/index.d.mts +21 -4
  21. package/dist/runners/bun-process/runner.d.mts +4 -1
  22. package/dist/runners/bun-process/runner.mjs +12 -9
  23. package/dist/runners/bun-process/worker.mjs +12 -11
  24. package/dist/runners/deno-process/worker.mjs +12 -11
  25. package/dist/runners/node-process/runner.d.mts +3 -1
  26. package/dist/runners/node-process/runner.mjs +2 -6
  27. package/dist/runners/node-process/worker.mjs +12 -11
  28. package/dist/runners/node-worker/worker.mjs +10 -10
  29. package/dist/runners/self/runner.d.mts +5 -2
  30. package/dist/runners/self/runner.mjs +12 -0
  31. package/package.json +26 -26
  32. package/dist/_chunks/common-host-env.mjs +0 -11
  33. package/dist/_chunks/virtual-loader.mjs +0 -71
@@ -0,0 +1,13 @@
1
+ function receiveProcessData() {
2
+ return new Promise((resolve) => {
3
+ const onMessage = (message) => {
4
+ if (message?.event === "init-data") {
5
+ process.off("message", onMessage);
6
+ resolve(message.data);
7
+ }
8
+ };
9
+ process.on("message", onMessage);
10
+ process.send({ event: "request-init-data" });
11
+ }).then((json) => JSON.parse(json));
12
+ }
13
+ export { receiveProcessData };
@@ -1,111 +1,7 @@
1
- import { createVirtualHooks, expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";
2
- import { pathToFileURL } from "node:url";
3
- import { existsSync, readFileSync } from "node:fs";
1
+ import { PLUGINS_DATA_KEY, findVirtualPathKey, refreshVirtualModule, registerPluginHooks, registerVirtualModules, registeredVirtualModules, servedByPluginHooks } from "./plugin-hooks.mjs";
2
+ import { createRequire } from "node:module";
4
3
  import { isAbsolute } from "node:path";
5
- async function registerVirtualModules(virtual) {
6
- if (!virtual || Object.keys(virtual).length === 0) return _noop;
7
- const { registerHooks, stripTypeScriptTypes } = await import("node:module");
8
- if (typeof registerHooks === "function") {
9
- const isDeno = "Deno" in globalThis;
10
- let transformSource;
11
- if (isDeno) {
12
- transformSource = (specifier, source) => _transformSourceForDeno(specifier, source, stripTypeScriptTypes);
13
- const transformed = {};
14
- for (const [specifier, source] of Object.entries(virtual)) transformed[specifier] = transformSource(specifier, source);
15
- virtual = transformed;
16
- }
17
- const registration = {
18
- virtual,
19
- versions: /* @__PURE__ */ new Map(),
20
- transformSource
21
- };
22
- const hooks = registerHooks(createVirtualHooks(virtual, registration.versions, void 0, isDeno));
23
- _hooksRegistrations.unshift(registration);
24
- return _once(() => {
25
- const index = _hooksRegistrations.indexOf(registration);
26
- if (index !== -1) _hooksRegistrations.splice(index, 1);
27
- hooks.deregister();
28
- });
29
- }
30
- if (typeof globalThis.Bun?.plugin === "function") {
31
- _bunVirtual = virtual;
32
- _registerBunModules(Object.keys(virtual));
33
- return _once(() => {
34
- if (_bunVirtual === virtual) _bunVirtual = void 0;
35
- });
36
- }
37
- console.warn("[env-runner] virtual modules require `module.registerHooks` (Node.js >= 22.15 / Deno >= 2.x) or `Bun.plugin`; skipping registration.");
38
- return _noop;
39
- }
40
- function refreshVirtualModule(specifier) {
41
- if (_bunVirtual?.[specifier] === void 0) return false;
42
- _registerBunModules([specifier]);
43
- return true;
44
- }
45
- function invalidateVirtualModule(specifier, source) {
46
- for (const registration of _hooksRegistrations) {
47
- if (!Object.hasOwn(registration.virtual, specifier)) continue;
48
- const { virtual, versions, transformSource } = registration;
49
- if (source !== void 0) virtual[specifier] = transformSource ? transformSource(specifier, source) : source;
50
- for (const key of expandVirtualInvalidation(virtual, specifier)) versions.set(key, (versions.get(key) ?? 0) + 1);
51
- return true;
52
- }
53
- if (_bunVirtual && Object.hasOwn(_bunVirtual, specifier)) {
54
- if (source !== void 0) _bunVirtual[specifier] = source;
55
- _registerBunModules(expandVirtualInvalidation(_bunVirtual, specifier));
56
- return true;
57
- }
58
- return false;
59
- }
60
- function handleInvalidateModule(message, sendMessage) {
61
- const ok = invalidateVirtualModule(message.specifier, message.source);
62
- sendMessage({
63
- event: "module-invalidated",
64
- specifier: message.specifier,
65
- error: ok ? void 0 : `Cannot invalidate "${message.specifier}" (not a registered virtual module)`
66
- });
67
- }
68
- const _hooksRegistrations = [];
69
- let _bunVirtual;
70
- function _registerBunModules(specifiers) {
71
- globalThis.Bun.plugin({
72
- name: "env-runner-virtual",
73
- setup(build) {
74
- for (const specifier of specifiers) build.module(specifier, () => {
75
- const source = _bunVirtual?.[specifier];
76
- if (source === void 0) throw new Error(`Cannot find virtual module "${specifier}" (unregistered)`);
77
- const format = virtualModuleFormat(specifier);
78
- if (format === "json") return {
79
- exports: { default: JSON.parse(source) },
80
- loader: "object"
81
- };
82
- return {
83
- contents: source,
84
- loader: format === "module-typescript" ? "ts" : "js"
85
- };
86
- });
87
- }
88
- });
89
- }
90
- function _transformSourceForDeno(specifier, source, stripTypeScriptTypes) {
91
- const format = virtualModuleFormat(specifier);
92
- if (format === "module-typescript") return stripVirtualTypeScript(specifier, source, stripTypeScriptTypes, {
93
- requirement: "(custom load hooks bypass Deno's native type stripping)",
94
- remedy: "upgrade Deno"
95
- });
96
- if (format === "json") return `export default JSON.parse(${JSON.stringify(source)});`;
97
- return source;
98
- }
99
- const _noop = () => {};
100
- function _once(fn) {
101
- let done = false;
102
- return () => {
103
- if (!done) {
104
- done = true;
105
- fn();
106
- }
107
- };
108
- }
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
109
5
  const RESERVED_SERVER_OPTIONS = [
110
6
  "port",
111
7
  "hostname",
@@ -155,8 +51,22 @@ function toServerOptions(entry) {
155
51
  gracefulShutdown: false
156
52
  };
157
53
  }
158
- function isVirtualSpecifier(specifier, virtual) {
159
- return Boolean(specifier && virtual && Object.hasOwn(virtual, specifier));
54
+ function isVirtualSpecifier(specifier, virtual, matchPaths = false) {
55
+ if (!specifier || !virtual) return false;
56
+ return Object.hasOwn(virtual, specifier) || matchPaths && findVirtualPathKey(virtual, specifier) !== void 0;
57
+ }
58
+ function isVirtualEntry(entry, virtual = registeredVirtualModules()) {
59
+ const registerHooks = process.getBuiltinModule?.("node:module")?.registerHooks;
60
+ return isVirtualSpecifier(entry, virtual, typeof registerHooks === "function");
61
+ }
62
+ async function registerWorkerHooks(data) {
63
+ const unregisterVirtualFirst = "Bun" in globalThis ? await registerVirtualModules(data.virtual) : void 0;
64
+ const unregisterPlugins = await registerPluginHooks(data[PLUGINS_DATA_KEY], data.entry);
65
+ const unregisterVirtual = unregisterVirtualFirst ?? await registerVirtualModules(data.virtual);
66
+ return () => {
67
+ unregisterVirtual();
68
+ unregisterPlugins();
69
+ };
160
70
  }
161
71
  async function resolveEntry(entryPath, virtual) {
162
72
  const mod = await (virtual ? import(entryPath) : import(_toImportPath(entryPath)));
@@ -164,6 +74,19 @@ async function resolveEntry(entryPath, virtual) {
164
74
  if (typeof entry.fetch !== "function") throw new Error(`[env-runner] Entry module "${entryPath}" must export a \`fetch\` handler (export default { fetch(req) { ... } }).`);
165
75
  return entry;
166
76
  }
77
+ function formatInitError(error) {
78
+ const message = (error?.message || String(error)).replace(/^(?:Error: )+(?=\[env-runner\])/, "");
79
+ if (message.startsWith("[env-runner]") || /:\d+:\d+/.test(message)) return message;
80
+ const location = _errorLocation(error);
81
+ return location ? `${message} (at ${location})` : message;
82
+ }
83
+ function _errorLocation(error) {
84
+ const position = error?.position;
85
+ if (position?.file) return `${position.file}:${position.line}:${position.column}`;
86
+ const stack = typeof error?.stack === "string" ? error.stack : "";
87
+ const location = stack.startsWith(String(error?.name)) ? /\n\s+at (?:async )?(?:.* \()?([^\n()]+:\d+:\d+)\)?/.exec(stack)?.[1] : /^([^\n]+:\d+)\n/.exec(stack)?.[1];
88
+ return location && !/^(?:node|ext|native):/.test(location) ? location : void 0;
89
+ }
167
90
  function parseServerAddress(server) {
168
91
  const url = new URL(server.url);
169
92
  return {
@@ -184,18 +107,28 @@ function _toImportPath(entryPath) {
184
107
  if (isAbsolute(filePath)) return pathToFileURL(filePath).href + query;
185
108
  return entryPath;
186
109
  }
110
+ function _toReloadPath(entryPath) {
111
+ if (!("Bun" in globalThis)) return _toImportPath(entryPath);
112
+ if (!entryPath.startsWith("file:")) return entryPath;
113
+ const qIndex = entryPath.indexOf("?");
114
+ return qIndex === -1 ? fileURLToPath(entryPath) : fileURLToPath(entryPath.slice(0, qIndex)) + entryPath.slice(qIndex);
115
+ }
187
116
  let _reloadCounter = 0;
188
117
  async function _importFresh(entryPath, virtual) {
189
118
  const qIndex = entryPath.indexOf("?");
190
119
  const filePath = qIndex === -1 ? entryPath : entryPath.slice(0, qIndex);
191
120
  let mod;
192
- if (!virtual && existsSync(filePath)) {
193
- const code = readFileSync(filePath, "utf8");
194
- mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
121
+ const bunPath = filePath.startsWith("file:") ? fileURLToPath(filePath) : filePath;
122
+ if (!virtual && "Bun" in globalThis && servedByPluginHooks(bunPath)) {
123
+ delete createRequire(import.meta.url).cache[bunPath];
124
+ mod = await import(bunPath);
195
125
  } else if (virtual && refreshVirtualModule(filePath)) mod = await import(filePath);
196
- else mod = await import(entryPath + (qIndex === -1 ? "?" : "&") + "__envRunnerReload=" + _reloadCounter++);
126
+ else {
127
+ const importPath = virtual ? entryPath : _toReloadPath(entryPath);
128
+ mod = await import(importPath + (importPath.includes("?") ? "&" : "?") + "__envRunnerReload=" + _reloadCounter++);
129
+ }
197
130
  const entry = mod.default || mod;
198
131
  if (typeof entry.fetch !== "function") throw new Error(`[env-runner] Entry module "${entryPath}" must export a \`fetch\` handler (export default { fetch(req) { ... } }).`);
199
132
  return entry;
200
133
  }
201
- export { RESERVED_RUNTIME_OPTIONS, RESERVED_SERVER_OPTIONS, handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry, toServerOptions };
134
+ export { RESERVED_RUNTIME_OPTIONS, RESERVED_SERVER_OPTIONS, formatInitError, isVirtualEntry, parseServerAddress, registerWorkerHooks, reloadEntryModule, resolveEntry, toServerOptions };
@@ -1,5 +1,5 @@
1
1
  import { WorkerHooks } from "./types.mjs";
2
- import { BaseEnvRunner, EnvRunnerData } from "./common-base-runner.mjs";
2
+ import { BaseEnvRunner, EnvRunnerData, EnvRunnerPluginOption } from "./common-base-runner.mjs";
3
3
  export declare class DenoProcessEnvRunner extends BaseEnvRunner {
4
4
  #private;
5
5
  constructor(opts: {
@@ -7,10 +7,13 @@ export declare class DenoProcessEnvRunner extends BaseEnvRunner {
7
7
  workerEntry?: string;
8
8
  hooks?: WorkerHooks;
9
9
  data?: EnvRunnerData;
10
+ /** Host-side transform plugins (see `EnvRunnerPluginOption`). */
11
+ plugins?: EnvRunnerPluginOption[];
10
12
  execArgv?: string[];
11
13
  });
12
14
  sendMessage(message: unknown): void;
13
15
  protected _hasRuntime(): boolean;
14
16
  protected _runtimeType(): string;
17
+ protected _resolveConditions(): string[];
15
18
  protected _closeRuntime(): Promise<void>;
16
19
  }
@@ -1,5 +1,4 @@
1
1
  import { BaseEnvRunner } from "./common-base-runner.mjs";
2
- import { hostEnv } from "./common-host-env.mjs";
3
2
  import { fileURLToPath } from "node:url";
4
3
  import { existsSync } from "node:fs";
5
4
  import { spawn } from "node:child_process";
@@ -24,6 +23,13 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
24
23
  _runtimeType() {
25
24
  return "process";
26
25
  }
26
+ _resolveConditions() {
27
+ return [
28
+ "deno",
29
+ "node",
30
+ "import"
31
+ ];
32
+ }
27
33
  async _closeRuntime() {
28
34
  if (!this.#process) return;
29
35
  this.#process.removeAllListeners();
@@ -45,10 +51,7 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
45
51
  ...execArgv || [],
46
52
  this._workerEntry
47
53
  ], {
48
- env: hostEnv({
49
- ENV_RUNNER_NAME: this._name,
50
- ENV_RUNNER_DATA: JSON.stringify(this._data || {})
51
- }),
54
+ env: this._processEnv(),
52
55
  stdio: [
53
56
  "pipe",
54
57
  "pipe",
@@ -68,7 +71,7 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
68
71
  }
69
72
  });
70
73
  child.on("message", (message) => {
71
- this._handleMessage(message);
74
+ this._handleProcessMessage(message);
72
75
  });
73
76
  child.stdout?.pipe(process.stdout);
74
77
  child.stderr?.pipe(process.stderr);
@@ -0,0 +1,2 @@
1
+ import { init, initSync, parse } from "./libs/cjs-module-lexer.mjs";
2
+ export { init, initSync, parse };
@@ -142,4 +142,9 @@ function init() {
142
142
  A = Q;
143
143
  })());
144
144
  }
145
- export { init, parse };
145
+ function initSync() {
146
+ if (A) return;
147
+ const B = new WebAssembly.Module(w()), { exports: Q } = new WebAssembly.Instance(B);
148
+ A = Q;
149
+ }
150
+ export { init, initSync, parse };
@@ -1,8 +1,8 @@
1
- import { WorkerHooks } from "./types.mjs";
2
- import { BaseEnvRunner, EnvRunnerData } from "./common-base-runner.mjs";
1
+ import { ResolvedVirtualModule, WorkerHooks } from "./types.mjs";
2
+ import { BaseEnvRunner, EnvRunnerData, EnvRunnerPluginOption } from "./common-base-runner.mjs";
3
3
  import { RuntimeDep } from "./common-runtime-deps.mjs";
4
- import { IncomingMessage } from "node:http";
5
4
  import { Socket } from "node:net";
5
+ import { IncomingMessage } from "node:http";
6
6
  /** The `wrangler` package namespace, as imported by the app. */
7
7
  export interface WranglerModule {
8
8
  unstable_readConfig?: (...args: any[]) => any;
@@ -19,17 +19,21 @@ export interface TransformResult {
19
19
  export interface MiniflareExportInfo {
20
20
  type?: "DurableObject" | "WorkerEntrypoint" | "class";
21
21
  }
22
- /** The `miniflare` package namespace, as imported by the app. */
22
+ /** The `miniflare` package namespace (v4 or v5), as imported by the app. */
23
23
  export interface MiniflareModule {
24
24
  Miniflare: new (options: any) => any;
25
- /** Newest date the installed `workerd` supports, clamped to today. */
25
+ /** Newest date the installed `workerd` supports, clamped to today (v4 only). */
26
26
  supportedCompatibilityDate?: string;
27
+ /** Converts v4 options, which the runner builds, to the v5 format (v5 only). */
28
+ convertV4MiniflareOptions?: (options: any) => any;
27
29
  [key: string]: unknown;
28
30
  }
29
31
  export interface MiniflareEnvRunnerOptions {
30
32
  name: string;
31
33
  hooks?: WorkerHooks;
32
34
  data?: EnvRunnerData;
35
+ /** Host-side transform plugins (see `EnvRunnerPluginOption`). */
36
+ plugins?: EnvRunnerPluginOption[];
33
37
  /**
34
38
  * The `miniflare` package (`import * as miniflare from "miniflare"`) or a
35
39
  * specifier resolved from cwd. Omitted: imported optionally.
@@ -51,9 +55,15 @@ export interface MiniflareEnvRunnerOptions {
51
55
  transformRequest?: (id: string) => Promise<TransformResult | null | undefined>;
52
56
  /**
53
57
  * Named exports (Durable Objects, WorkerEntrypoints) to bind and re-export.
54
- * `true` detects `export class`; a record merges with detected ones.
58
+ * Default (or `true`): detect `export class` in the entry and auto-bind them;
59
+ * a record merges with detected ones; `false` disables it.
60
+ * A module specifier (absolute path or `data.virtual` key; relative paths
61
+ * resolve from the entry's directory) is re-exported with `export *` instead:
62
+ * nothing is detected or auto-bound (configure bindings with `wrangler` or
63
+ * `miniflareOptions`) and the entry's own classes are not re-exported.
64
+ * Exports load at startup, so changes need a new runner (not `reloadModule()`).
55
65
  */
56
- exports?: Record<string, MiniflareExportInfo> | boolean;
66
+ exports?: Record<string, MiniflareExportInfo> | boolean | string;
57
67
  /** Reuse the Miniflare instance across runner swaps; only `dispose()` destroys it. */
58
68
  persistent?: boolean;
59
69
  /** Wrap the user's `fetch` in a try/catch that returns structured JSON error responses. Default: `true`. */
@@ -71,10 +81,11 @@ export interface MiniflareEnvRunnerOptions {
71
81
  *
72
82
  * Options a single dev worker can't run (`assets`, services, queue consumers,
73
83
  * workflows, tails, other-script Durable Objects) are dropped with a warning.
74
- * `defaultPersistRoot` defaults to `.wrangler/state/v3` next to the config
75
- * (else cwd), shared with `wrangler dev`. The `wrangler` package gives full
76
- * fidelity (and may run its npm update check); without it only plain JSON is
77
- * read. `miniflareOptions` always win.
84
+ * `defaultPersistRoot` (v5: `resourcePersistencePath`) defaults to
85
+ * `.wrangler/state/v3` next to the config (else cwd), shared with
86
+ * `wrangler dev`. The `wrangler` package gives full fidelity (and may run its
87
+ * npm update check); without it only plain JSON is read. `miniflareOptions`
88
+ * always win.
78
89
  */
79
90
  wrangler?: boolean | string | WranglerInlineConfig;
80
91
  /**
@@ -108,12 +119,13 @@ export declare class MiniflareEnvRunner extends BaseEnvRunner {
108
119
  /** Hot-reload the entry without recreating the Miniflare instance. */
109
120
  reloadModule(timeout?: number): Promise<void>;
110
121
  /**
111
- * Host-side only: the fallback service serves a live map, so bumping versions
112
- * (see {@link rewriteVirtualImports}) is enough.
122
+ * Host-side only: the fallback service serves a live map, so changing it and
123
+ * bumping versions (see {@link applyVirtualVersions}) is enough.
113
124
  */
114
- invalidateModule(specifier: string, _timeout?: number): Promise<void>;
125
+ protected _applyVirtualUpdates(changes: Record<string, ResolvedVirtualModule | null>): Promise<void>;
115
126
  protected _hasRuntime(): boolean;
116
127
  protected _runtimeType(): string;
128
+ protected _resolveConditions(): string[];
117
129
  protected _closeRuntime(): Promise<void>;
118
130
  upgrade(context: {
119
131
  node: {