env-runner 0.1.9 → 0.1.11
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.
- package/README.md +130 -16
- package/dist/THIRD-PARTY-LICENSES.md +19 -0
- package/dist/_chunks/base-runner.d.mts +80 -3
- package/dist/_chunks/base-runner.mjs +108 -44
- package/dist/_chunks/libs/cjs-module-lexer.mjs +1 -1
- package/dist/_chunks/libs/es-module-lexer.mjs +83 -0
- package/dist/_chunks/runner.d.mts +3 -3
- package/dist/_chunks/runner.mjs +6 -3
- package/dist/_chunks/runner2.d.mts +15 -3
- package/dist/_chunks/runner2.mjs +145 -56
- package/dist/_chunks/runner3.d.mts +3 -3
- package/dist/_chunks/runner3.mjs +3 -3
- package/dist/_chunks/runner4.d.mts +4 -4
- package/dist/_chunks/runner4.mjs +3 -2
- package/dist/_chunks/runner5.d.mts +4 -4
- package/dist/_chunks/runner5.mjs +3 -2
- package/dist/_chunks/server.mjs +60 -17
- package/dist/_chunks/types.d.mts +15 -3
- package/dist/_chunks/virtual-loader.mjs +63 -0
- package/dist/_chunks/worker-utils.mjs +123 -9
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +52 -14
- package/dist/index.mjs +6 -6
- package/dist/runners/bun-process/runner.d.mts +2 -2
- package/dist/runners/bun-process/runner.mjs +12 -2
- package/dist/runners/bun-process/worker.mjs +23 -3
- package/dist/runners/deno-process/runner.d.mts +2 -2
- package/dist/runners/deno-process/runner.mjs +1 -1
- package/dist/runners/deno-process/worker.mjs +22 -3
- package/dist/runners/miniflare/runner.d.mts +2 -2
- package/dist/runners/miniflare/runner.mjs +1 -1
- package/dist/runners/netlify/runner.d.mts +2 -2
- package/dist/runners/netlify/runner.mjs +1 -1
- package/dist/runners/node-process/runner.d.mts +2 -2
- package/dist/runners/node-process/runner.mjs +4 -2
- package/dist/runners/node-process/worker.mjs +23 -3
- package/dist/runners/node-worker/runner.d.mts +2 -2
- package/dist/runners/node-worker/runner.mjs +1 -1
- package/dist/runners/node-worker/worker.mjs +22 -3
- package/dist/runners/self/runner.d.mts +3 -2
- package/dist/runners/self/runner.mjs +6 -3
- package/dist/runners/vercel/runner.d.mts +2 -2
- package/dist/runners/vercel/runner.mjs +1 -1
- package/dist/vite.d.mts +1 -1
- package/package.json +12 -11
package/dist/_chunks/server.mjs
CHANGED
|
@@ -5,6 +5,8 @@ var RunnerManager = class {
|
|
|
5
5
|
_messageListeners = /* @__PURE__ */ new Set();
|
|
6
6
|
_closed = false;
|
|
7
7
|
_reloading = false;
|
|
8
|
+
_moduleInvalidated = false;
|
|
9
|
+
_pendingModuleReload;
|
|
8
10
|
_closeListeners = /* @__PURE__ */ new Set();
|
|
9
11
|
_readyListeners = /* @__PURE__ */ new Set();
|
|
10
12
|
constructor(runner) {
|
|
@@ -18,17 +20,35 @@ var RunnerManager = class {
|
|
|
18
20
|
}
|
|
19
21
|
async reload(runner) {
|
|
20
22
|
this._reloading = true;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
try {
|
|
24
|
+
runner ??= await this._createRunner();
|
|
25
|
+
const prev = this._runner;
|
|
26
|
+
this._detach();
|
|
27
|
+
this._attach(runner);
|
|
28
|
+
this._moduleInvalidated = false;
|
|
29
|
+
if (prev) await prev.close();
|
|
30
|
+
} finally {
|
|
31
|
+
this._reloading = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
_createRunner() {
|
|
35
|
+
throw new Error("reload() requires a runner argument (this manager has no runner factory)");
|
|
26
36
|
}
|
|
27
|
-
fetch =
|
|
37
|
+
fetch = (input, init) => this._fetch(input, init);
|
|
38
|
+
async _fetch(input, init) {
|
|
28
39
|
const runner = await this._waitForRunner();
|
|
29
40
|
if (!runner) return new Response("Runner is unavailable", { status: 503 });
|
|
41
|
+
if (this._moduleInvalidated) await this._flushInvalidation(runner);
|
|
30
42
|
return runner.fetch(input, init);
|
|
31
|
-
}
|
|
43
|
+
}
|
|
44
|
+
_flushInvalidation(runner) {
|
|
45
|
+
this._pendingModuleReload ??= Promise.resolve(runner.reloadModule?.()).then(() => {
|
|
46
|
+
this._moduleInvalidated = false;
|
|
47
|
+
}).finally(() => {
|
|
48
|
+
this._pendingModuleReload = void 0;
|
|
49
|
+
});
|
|
50
|
+
return this._pendingModuleReload;
|
|
51
|
+
}
|
|
32
52
|
upgrade = (context) => {
|
|
33
53
|
this._runner?.upgrade?.(context);
|
|
34
54
|
};
|
|
@@ -93,7 +113,13 @@ var RunnerManager = class {
|
|
|
93
113
|
}
|
|
94
114
|
async reloadModule(timeout) {
|
|
95
115
|
if (!this._runner?.reloadModule) throw new Error("Active runner does not support reloadModule()");
|
|
96
|
-
|
|
116
|
+
await this._runner.reloadModule(timeout);
|
|
117
|
+
this._moduleInvalidated = false;
|
|
118
|
+
}
|
|
119
|
+
async invalidateModule(specifier, timeout) {
|
|
120
|
+
if (!this._runner?.invalidateModule) throw new Error("Active runner does not support invalidateModule()");
|
|
121
|
+
await this._runner.invalidateModule(specifier, timeout);
|
|
122
|
+
this._moduleInvalidated = true;
|
|
97
123
|
}
|
|
98
124
|
async close() {
|
|
99
125
|
this._closed = true;
|
|
@@ -102,6 +128,9 @@ var RunnerManager = class {
|
|
|
102
128
|
this._detach();
|
|
103
129
|
if (runner) await runner.close();
|
|
104
130
|
}
|
|
131
|
+
async [Symbol.asyncDispose]() {
|
|
132
|
+
await this.close();
|
|
133
|
+
}
|
|
105
134
|
onClose(listener) {
|
|
106
135
|
this._closeListeners.add(listener);
|
|
107
136
|
}
|
|
@@ -179,6 +208,7 @@ var EnvServer = class extends RunnerManager {
|
|
|
179
208
|
_watchers = [];
|
|
180
209
|
_reloadTimeout;
|
|
181
210
|
_reloadListeners = /* @__PURE__ */ new Set();
|
|
211
|
+
_startPromise;
|
|
182
212
|
runner = null;
|
|
183
213
|
onReload(listener) {
|
|
184
214
|
this._reloadListeners.add(listener);
|
|
@@ -190,18 +220,32 @@ var EnvServer = class extends RunnerManager {
|
|
|
190
220
|
super();
|
|
191
221
|
this._opts = opts;
|
|
192
222
|
}
|
|
193
|
-
|
|
194
|
-
this.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
223
|
+
start() {
|
|
224
|
+
this._startPromise ??= this._start().catch((error) => {
|
|
225
|
+
this._startPromise = void 0;
|
|
226
|
+
throw error;
|
|
227
|
+
});
|
|
228
|
+
return this._startPromise;
|
|
229
|
+
}
|
|
230
|
+
async reload(runner) {
|
|
231
|
+
this.runner = runner ?? await this._createRunner();
|
|
232
|
+
await super.reload(this.runner);
|
|
198
233
|
}
|
|
199
234
|
async close() {
|
|
200
235
|
this._stopWatching();
|
|
201
236
|
await super.close();
|
|
202
237
|
}
|
|
238
|
+
async _fetch(input, init) {
|
|
239
|
+
if (!this.closed) await this.start();
|
|
240
|
+
return super._fetch(input, init);
|
|
241
|
+
}
|
|
242
|
+
async _start() {
|
|
243
|
+
await this.reload();
|
|
244
|
+
if (this._opts.watch) this._startWatching();
|
|
245
|
+
return this;
|
|
246
|
+
}
|
|
203
247
|
async _createRunner() {
|
|
204
|
-
return loadRunner(this._opts.runner, {
|
|
248
|
+
return loadRunner(this._opts.runner || "node-worker", {
|
|
205
249
|
name: this._opts.name || this._opts.entry,
|
|
206
250
|
hooks: this._opts.hooks,
|
|
207
251
|
data: {
|
|
@@ -229,8 +273,7 @@ var EnvServer = class extends RunnerManager {
|
|
|
229
273
|
clearTimeout(this._reloadTimeout);
|
|
230
274
|
this._reloadTimeout = setTimeout(async () => {
|
|
231
275
|
try {
|
|
232
|
-
|
|
233
|
-
await this.reload(this.runner);
|
|
276
|
+
await this.reload();
|
|
234
277
|
for (const fn of this._reloadListeners) fn();
|
|
235
278
|
} catch (error) {
|
|
236
279
|
console.error("Failed to reload runner:", error);
|
|
@@ -238,4 +281,4 @@ var EnvServer = class extends RunnerManager {
|
|
|
238
281
|
}, 100);
|
|
239
282
|
}
|
|
240
283
|
};
|
|
241
|
-
export {
|
|
284
|
+
export { EnvServer, RunnerManager, loadRunner };
|
package/dist/_chunks/types.d.mts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { IncomingMessage } from "node:http";
|
|
2
2
|
import { Socket } from "node:net";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Handler for proxying HTTP requests to the worker.
|
|
5
|
+
*
|
|
6
|
+
* A relative string input (e.g. `"/path"`) is resolved against a placeholder
|
|
7
|
+
* `http://localhost` origin before dispatching.
|
|
8
|
+
*/
|
|
4
9
|
type FetchHandler = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
5
10
|
/** Callback for receiving messages from the worker. */
|
|
6
11
|
type RunnerMessageListener = (data: unknown) => void;
|
|
@@ -52,7 +57,7 @@ interface RPCOptions {
|
|
|
52
57
|
timeout?: number;
|
|
53
58
|
}
|
|
54
59
|
/** Core runner interface combining lifecycle hooks, RPC, and request proxying. */
|
|
55
|
-
interface EnvRunner extends RunnerRPCHooks {
|
|
60
|
+
interface EnvRunner extends RunnerRPCHooks, AsyncDisposable {
|
|
56
61
|
/** Whether the worker is ready to accept requests. */
|
|
57
62
|
readonly ready: boolean;
|
|
58
63
|
/** Whether the runner has been closed. */
|
|
@@ -67,7 +72,14 @@ interface EnvRunner extends RunnerRPCHooks {
|
|
|
67
72
|
rpc<T = unknown>(name: string, data?: unknown, opts?: RPCOptions): Promise<T>;
|
|
68
73
|
/** Re-import the entry module without restarting the worker/process. */
|
|
69
74
|
reloadModule?(timeout?: number): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Invalidate a virtual module so the next `reloadModule()` re-evaluates it.
|
|
77
|
+
* A factory-valued source is re-run for fresh contents.
|
|
78
|
+
*/
|
|
79
|
+
invalidateModule?(specifier: string, timeout?: number): Promise<void>;
|
|
70
80
|
/** Gracefully shut down the worker. */
|
|
71
81
|
close(): Promise<void>;
|
|
82
|
+
/** Alias for `close()`, enabling `await using` (explicit resource management). */
|
|
83
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
72
84
|
}
|
|
73
|
-
export {
|
|
85
|
+
export { EnvRunner, FetchHandler, NodeUpgradeContext, RPCOptions, RunnerMessageListener, RunnerRPCHooks, UpgradeContext, UpgradeHandler, WorkerAddress, WorkerHooks };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
async function resolveVirtualModules(virtual) {
|
|
2
|
+
const entries = await Promise.all(Object.entries(virtual).map(async ([key, value]) => [key, typeof value === "function" ? await value() : value]));
|
|
3
|
+
return Object.fromEntries(entries);
|
|
4
|
+
}
|
|
5
|
+
const VIRTUAL_SCHEME = "virtual:";
|
|
6
|
+
function createVirtualHooks(virtual, versions) {
|
|
7
|
+
const resolve = (specifier, context, nextResolve) => {
|
|
8
|
+
const key = _stripQuery(specifier);
|
|
9
|
+
if (Object.hasOwn(virtual, key)) {
|
|
10
|
+
const version = versions?.get(key);
|
|
11
|
+
return {
|
|
12
|
+
url: VIRTUAL_SCHEME + encodeURIComponent(specifier) + (version ? `?v=${version}` : ""),
|
|
13
|
+
shortCircuit: true
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return nextResolve(specifier, context);
|
|
17
|
+
};
|
|
18
|
+
const load = (url, context, nextLoad) => {
|
|
19
|
+
if (url.startsWith(VIRTUAL_SCHEME)) {
|
|
20
|
+
const key = _stripQuery(decodeURIComponent(url.slice(8)));
|
|
21
|
+
if (Object.hasOwn(virtual, key)) return {
|
|
22
|
+
format: virtualModuleFormat(key),
|
|
23
|
+
source: virtual[key],
|
|
24
|
+
shortCircuit: true
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return nextLoad(url, context);
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
resolve,
|
|
31
|
+
load
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function virtualModuleFormat(specifier) {
|
|
35
|
+
if (specifier.endsWith(".json")) return "json";
|
|
36
|
+
if (specifier.endsWith(".ts") || specifier.endsWith(".mts")) return "module-typescript";
|
|
37
|
+
return "module";
|
|
38
|
+
}
|
|
39
|
+
function stripVirtualTypeScript(specifier, source, stripTypeScriptTypes, hints) {
|
|
40
|
+
if (typeof stripTypeScriptTypes !== "function") throw new TypeError(`[env-runner] virtual TypeScript module "${specifier}" requires \`module.stripTypeScriptTypes\` ${hints.requirement}; ${hints.remedy} or provide a pre-transpiled JavaScript source instead.`);
|
|
41
|
+
return stripTypeScriptTypes(source);
|
|
42
|
+
}
|
|
43
|
+
function expandVirtualInvalidation(virtual, specifier) {
|
|
44
|
+
const invalidated = [specifier];
|
|
45
|
+
const seen = new Set(invalidated);
|
|
46
|
+
for (const target of invalidated) {
|
|
47
|
+
const refs = [
|
|
48
|
+
`"${target}"`,
|
|
49
|
+
`'${target}'`,
|
|
50
|
+
"`" + target + "`"
|
|
51
|
+
];
|
|
52
|
+
for (const [key, source] of Object.entries(virtual)) if (!seen.has(key) && refs.some((ref) => source.includes(ref))) {
|
|
53
|
+
seen.add(key);
|
|
54
|
+
invalidated.push(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return invalidated;
|
|
58
|
+
}
|
|
59
|
+
function _stripQuery(specifier) {
|
|
60
|
+
const qIndex = specifier.indexOf("?");
|
|
61
|
+
return qIndex === -1 ? specifier : specifier.slice(0, qIndex);
|
|
62
|
+
}
|
|
63
|
+
export { createVirtualHooks, expandVirtualInvalidation, resolveVirtualModules, stripVirtualTypeScript, virtualModuleFormat };
|
|
@@ -1,8 +1,115 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createVirtualHooks, expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { pathToFileURL } from "node:url";
|
|
3
4
|
import { isAbsolute } from "node:path";
|
|
4
|
-
async function
|
|
5
|
-
|
|
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
|
+
let transformSource;
|
|
10
|
+
if ("Deno" in globalThis) {
|
|
11
|
+
transformSource = (specifier, source) => _transformSourceForDeno(specifier, source, stripTypeScriptTypes);
|
|
12
|
+
const transformed = {};
|
|
13
|
+
for (const [specifier, source] of Object.entries(virtual)) transformed[specifier] = transformSource(specifier, source);
|
|
14
|
+
virtual = transformed;
|
|
15
|
+
}
|
|
16
|
+
const registration = {
|
|
17
|
+
virtual,
|
|
18
|
+
versions: /* @__PURE__ */ new Map(),
|
|
19
|
+
transformSource
|
|
20
|
+
};
|
|
21
|
+
const hooks = registerHooks(createVirtualHooks(virtual, registration.versions));
|
|
22
|
+
_hooksRegistrations.unshift(registration);
|
|
23
|
+
return _once(() => {
|
|
24
|
+
const index = _hooksRegistrations.indexOf(registration);
|
|
25
|
+
if (index !== -1) _hooksRegistrations.splice(index, 1);
|
|
26
|
+
hooks.deregister();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (typeof globalThis.Bun?.plugin === "function") {
|
|
30
|
+
_bunVirtual = virtual;
|
|
31
|
+
_registerBunModules(Object.keys(virtual));
|
|
32
|
+
return _once(() => {
|
|
33
|
+
if (_bunVirtual === virtual) _bunVirtual = void 0;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
console.warn("[env-runner] virtual modules require `module.registerHooks` (Node.js >= 22.15 / Deno >= 2.x) or `Bun.plugin`; skipping registration.");
|
|
37
|
+
return _noop;
|
|
38
|
+
}
|
|
39
|
+
function refreshVirtualModule(specifier) {
|
|
40
|
+
if (_bunVirtual?.[specifier] === void 0) return false;
|
|
41
|
+
_registerBunModules([specifier]);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
function invalidateVirtualModule(specifier, source) {
|
|
45
|
+
for (const registration of _hooksRegistrations) {
|
|
46
|
+
if (!Object.hasOwn(registration.virtual, specifier)) continue;
|
|
47
|
+
const { virtual, versions, transformSource } = registration;
|
|
48
|
+
if (source !== void 0) virtual[specifier] = transformSource ? transformSource(specifier, source) : source;
|
|
49
|
+
for (const key of expandVirtualInvalidation(virtual, specifier)) versions.set(key, (versions.get(key) ?? 0) + 1);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (_bunVirtual && Object.hasOwn(_bunVirtual, specifier)) {
|
|
53
|
+
if (source !== void 0) _bunVirtual[specifier] = source;
|
|
54
|
+
_registerBunModules(expandVirtualInvalidation(_bunVirtual, specifier));
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function handleInvalidateModule(message, sendMessage) {
|
|
60
|
+
const ok = invalidateVirtualModule(message.specifier, message.source);
|
|
61
|
+
sendMessage({
|
|
62
|
+
event: "module-invalidated",
|
|
63
|
+
specifier: message.specifier,
|
|
64
|
+
error: ok ? void 0 : `Cannot invalidate "${message.specifier}" (not a registered virtual module)`
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const _hooksRegistrations = [];
|
|
68
|
+
let _bunVirtual;
|
|
69
|
+
function _registerBunModules(specifiers) {
|
|
70
|
+
globalThis.Bun.plugin({
|
|
71
|
+
name: "env-runner-virtual",
|
|
72
|
+
setup(build) {
|
|
73
|
+
for (const specifier of specifiers) build.module(specifier, () => {
|
|
74
|
+
const source = _bunVirtual?.[specifier];
|
|
75
|
+
if (source === void 0) throw new Error(`Cannot find virtual module "${specifier}" (unregistered)`);
|
|
76
|
+
const format = virtualModuleFormat(specifier);
|
|
77
|
+
if (format === "json") return {
|
|
78
|
+
exports: { default: JSON.parse(source) },
|
|
79
|
+
loader: "object"
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
contents: source,
|
|
83
|
+
loader: format === "module-typescript" ? "ts" : "js"
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function _transformSourceForDeno(specifier, source, stripTypeScriptTypes) {
|
|
90
|
+
const format = virtualModuleFormat(specifier);
|
|
91
|
+
if (format === "module-typescript") return stripVirtualTypeScript(specifier, source, stripTypeScriptTypes, {
|
|
92
|
+
requirement: "(custom load hooks bypass Deno's native type stripping)",
|
|
93
|
+
remedy: "upgrade Deno"
|
|
94
|
+
});
|
|
95
|
+
if (format === "json") return `export default JSON.parse(${JSON.stringify(source)});`;
|
|
96
|
+
return source;
|
|
97
|
+
}
|
|
98
|
+
const _noop = () => {};
|
|
99
|
+
function _once(fn) {
|
|
100
|
+
let done = false;
|
|
101
|
+
return () => {
|
|
102
|
+
if (!done) {
|
|
103
|
+
done = true;
|
|
104
|
+
fn();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function isVirtualSpecifier(specifier, virtual) {
|
|
109
|
+
return Boolean(specifier && virtual && Object.hasOwn(virtual, specifier));
|
|
110
|
+
}
|
|
111
|
+
async function resolveEntry(entryPath, virtual) {
|
|
112
|
+
const mod = await (virtual ? import(entryPath) : import(_toImportPath(entryPath)));
|
|
6
113
|
const entry = mod.default || mod;
|
|
7
114
|
if (typeof entry.fetch !== "function") throw new Error(`[env-runner] Entry module "${entryPath}" must export a \`fetch\` handler (export default { fetch(req) { ... } }).`);
|
|
8
115
|
return entry;
|
|
@@ -14,9 +121,9 @@ function parseServerAddress(server) {
|
|
|
14
121
|
port: Number(url.port)
|
|
15
122
|
};
|
|
16
123
|
}
|
|
17
|
-
async function reloadEntryModule(entryPath, currentEntry, sendMessage) {
|
|
124
|
+
async function reloadEntryModule(entryPath, currentEntry, sendMessage, virtual) {
|
|
18
125
|
await currentEntry.ipc?.onClose?.();
|
|
19
|
-
const newEntry = await _importFresh(entryPath);
|
|
126
|
+
const newEntry = await _importFresh(entryPath, virtual);
|
|
20
127
|
await newEntry.ipc?.onOpen?.({ sendMessage });
|
|
21
128
|
return newEntry;
|
|
22
129
|
}
|
|
@@ -27,11 +134,18 @@ function _toImportPath(entryPath) {
|
|
|
27
134
|
if (isAbsolute(filePath)) return pathToFileURL(filePath).href + query;
|
|
28
135
|
return entryPath;
|
|
29
136
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
137
|
+
let _reloadCounter = 0;
|
|
138
|
+
async function _importFresh(entryPath, virtual) {
|
|
139
|
+
const qIndex = entryPath.indexOf("?");
|
|
140
|
+
const filePath = qIndex === -1 ? entryPath : entryPath.slice(0, qIndex);
|
|
141
|
+
let mod;
|
|
142
|
+
if (!virtual && existsSync(filePath)) {
|
|
143
|
+
const code = readFileSync(filePath, "utf8");
|
|
144
|
+
mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
|
|
145
|
+
} else if (virtual && refreshVirtualModule(filePath)) mod = await import(filePath);
|
|
146
|
+
else mod = await import(entryPath + (qIndex === -1 ? "?" : "&") + "__envRunnerReload=" + _reloadCounter++);
|
|
33
147
|
const entry = mod.default || mod;
|
|
34
148
|
if (typeof entry.fetch !== "function") throw new Error(`[env-runner] Entry module "${entryPath}" must export a \`fetch\` handler (export default { fetch(req) { ... } }).`);
|
|
35
149
|
return entry;
|
|
36
150
|
}
|
|
37
|
-
export {
|
|
151
|
+
export { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry };
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,29 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { EnvRunner, FetchHandler, NodeUpgradeContext, RPCOptions, RunnerMessageListener, RunnerRPCHooks, UpgradeContext, UpgradeHandler, WorkerAddress, WorkerHooks } from "./_chunks/types.mjs";
|
|
2
|
+
import { BaseEnvRunner, EnvRunnerData, VirtualModuleSource, VirtualModules } from "./_chunks/base-runner.mjs";
|
|
3
|
+
import { DenoProcessEnvRunner } from "./_chunks/runner.mjs";
|
|
4
|
+
import { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult } from "./_chunks/runner2.mjs";
|
|
5
|
+
import { VercelEnvRunner } from "./_chunks/runner4.mjs";
|
|
6
|
+
import { NetlifyEnvRunner } from "./_chunks/runner5.mjs";
|
|
7
7
|
import { ServerOptions } from "srvx";
|
|
8
8
|
import { Hooks } from "crossws";
|
|
9
9
|
/**
|
|
10
10
|
* Manages an active `EnvRunner` instance, proxying all calls to it.
|
|
11
11
|
* Supports hot-reload, auto-restart on unexpected exit, and message queueing.
|
|
12
12
|
*/
|
|
13
|
-
declare class RunnerManager implements EnvRunner {
|
|
13
|
+
declare class RunnerManager implements EnvRunner, AsyncDisposable {
|
|
14
14
|
private _runner;
|
|
15
15
|
private _messageQueue;
|
|
16
16
|
private _messageListeners;
|
|
17
17
|
private _closed;
|
|
18
18
|
private _reloading;
|
|
19
|
+
private _moduleInvalidated;
|
|
20
|
+
private _pendingModuleReload;
|
|
19
21
|
private _closeListeners;
|
|
20
22
|
private _readyListeners;
|
|
21
23
|
constructor(runner?: EnvRunner);
|
|
22
24
|
get ready(): boolean;
|
|
23
25
|
get closed(): boolean;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Replace the active runner with a new one. Closes the previous runner.
|
|
28
|
+
*
|
|
29
|
+
* When called without a runner, a fresh one is created via `_createRunner()`
|
|
30
|
+
* (only available on subclasses with a runner factory, e.g. `EnvServer`).
|
|
31
|
+
*/
|
|
32
|
+
reload(runner?: EnvRunner): Promise<void>;
|
|
33
|
+
/** Create a fresh runner for argument-less `reload()`. Overridden by subclasses with a runner factory. */
|
|
34
|
+
protected _createRunner(): EnvRunner | Promise<EnvRunner>;
|
|
26
35
|
fetch: FetchHandler;
|
|
36
|
+
protected _fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
37
|
+
/**
|
|
38
|
+
* Lazily satisfy a pending `invalidateModule()` with a single entry reload
|
|
39
|
+
* before serving — concurrent fetches share the same reload. A failed reload
|
|
40
|
+
* keeps the invalidation pending, so the next fetch retries.
|
|
41
|
+
*/
|
|
42
|
+
private _flushInvalidation;
|
|
27
43
|
upgrade: UpgradeHandler;
|
|
28
44
|
sendMessage(message: unknown): void;
|
|
29
45
|
onMessage(listener: RunnerMessageListener): void;
|
|
@@ -33,7 +49,14 @@ declare class RunnerManager implements EnvRunner {
|
|
|
33
49
|
timeout?: number;
|
|
34
50
|
}): Promise<T>;
|
|
35
51
|
reloadModule(timeout?: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Invalidate a virtual module on the active runner and mark the manager
|
|
54
|
+
* dirty: the next `fetch()` reloads the entry automatically, so callers
|
|
55
|
+
* don't need to pair the call with an explicit `reloadModule()`.
|
|
56
|
+
*/
|
|
57
|
+
invalidateModule(specifier: string, timeout?: number): Promise<void>;
|
|
36
58
|
close(): Promise<void>;
|
|
59
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
37
60
|
onClose(listener: (runner: EnvRunner, cause?: unknown) => void): void;
|
|
38
61
|
offClose(listener: (runner: EnvRunner, cause?: unknown) => void): void;
|
|
39
62
|
onReady(listener: (runner: EnvRunner, address?: WorkerAddress) => void): void;
|
|
@@ -56,8 +79,8 @@ interface LoadRunnerOptions {
|
|
|
56
79
|
}
|
|
57
80
|
declare function loadRunner(runner: RunnerName, opts: LoadRunnerOptions): Promise<EnvRunner>;
|
|
58
81
|
interface EnvServerOptions {
|
|
59
|
-
/** Runner implementation to use. */
|
|
60
|
-
runner
|
|
82
|
+
/** Runner implementation to use (defaults to `"node-worker"`). */
|
|
83
|
+
runner?: RunnerName;
|
|
61
84
|
/** Path to the user entry module (passed as `data.entry`). */
|
|
62
85
|
entry: string;
|
|
63
86
|
/** Runner instance name. */
|
|
@@ -78,16 +101,31 @@ declare class EnvServer extends RunnerManager {
|
|
|
78
101
|
private _watchers;
|
|
79
102
|
private _reloadTimeout;
|
|
80
103
|
private _reloadListeners;
|
|
104
|
+
private _startPromise;
|
|
81
105
|
runner: Awaited<ReturnType<typeof loadRunner>> | null;
|
|
82
106
|
/** Register a listener called when the runner is reloaded due to a file change. */
|
|
83
107
|
onReload(listener: () => void): void;
|
|
84
108
|
/** Remove a previously registered reload listener. */
|
|
85
109
|
offReload(listener: () => void): void;
|
|
86
110
|
constructor(opts: EnvServerOptions);
|
|
87
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* Start the server by loading and attaching the runner.
|
|
113
|
+
*
|
|
114
|
+
* Idempotent — concurrent and repeated calls share one startup. Calling
|
|
115
|
+
* `start()` explicitly is optional: the first `fetch()` auto-starts the
|
|
116
|
+
* server. A failed start resets so a later call can retry.
|
|
117
|
+
*/
|
|
88
118
|
start(): Promise<this>;
|
|
119
|
+
/**
|
|
120
|
+
* Replace the active runner. When called without an argument, a fresh
|
|
121
|
+
* runner is created from the server options.
|
|
122
|
+
*/
|
|
123
|
+
reload(runner?: EnvRunner): Promise<void>;
|
|
89
124
|
close(): Promise<void>;
|
|
90
|
-
|
|
125
|
+
/** Auto-start on first fetch so an explicit `start()` call is optional. */
|
|
126
|
+
protected _fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
127
|
+
private _start;
|
|
128
|
+
protected _createRunner(): Promise<EnvRunner>;
|
|
91
129
|
private _startWatching;
|
|
92
130
|
private _stopWatching;
|
|
93
131
|
private _scheduleReload;
|
|
@@ -108,4 +146,4 @@ interface AppEntry {
|
|
|
108
146
|
plugins?: ServerOptions["plugins"];
|
|
109
147
|
ipc?: AppEntryIPC;
|
|
110
148
|
}
|
|
111
|
-
export { type AppEntry, type AppEntryIPC, type AppEntryIPCContext, BaseEnvRunner, DenoProcessEnvRunner, type EnvRunnerData as DenoProcessEnvRunnerData, type
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { BaseEnvRunner } from "./_chunks/base-runner.mjs";
|
|
2
|
+
import { EnvServer, RunnerManager, loadRunner } from "./_chunks/server.mjs";
|
|
3
|
+
import { DenoProcessEnvRunner } from "./_chunks/runner.mjs";
|
|
4
|
+
import { MiniflareEnvRunner } from "./_chunks/runner2.mjs";
|
|
5
|
+
import { VercelEnvRunner } from "./_chunks/runner4.mjs";
|
|
6
|
+
import { NetlifyEnvRunner } from "./_chunks/runner5.mjs";
|
|
7
7
|
export { BaseEnvRunner, DenoProcessEnvRunner, EnvServer, MiniflareEnvRunner, NetlifyEnvRunner, RunnerManager, VercelEnvRunner, loadRunner };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { WorkerHooks } from "../../_chunks/types.mjs";
|
|
2
|
+
import { BaseEnvRunner, EnvRunnerData } from "../../_chunks/base-runner.mjs";
|
|
3
3
|
declare class BunProcessEnvRunner extends BaseEnvRunner {
|
|
4
4
|
#private;
|
|
5
5
|
constructor(opts: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseEnvRunner } from "../../_chunks/base-runner.mjs";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { execSync, spawn } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -7,6 +7,12 @@ import { homedir } from "node:os";
|
|
|
7
7
|
let _defaultEntry;
|
|
8
8
|
let _bunPath;
|
|
9
9
|
const _isBun = typeof Bun !== "undefined";
|
|
10
|
+
async function forwardStream(stream, dest) {
|
|
11
|
+
if (!stream) return;
|
|
12
|
+
try {
|
|
13
|
+
for await (const chunk of stream) dest.write(chunk);
|
|
14
|
+
} catch {}
|
|
15
|
+
}
|
|
10
16
|
function resolveBunPath() {
|
|
11
17
|
if (_bunPath) return _bunPath;
|
|
12
18
|
const candidates = [join(homedir(), ".bun", "bin", "bun")];
|
|
@@ -25,7 +31,7 @@ var BunProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
25
31
|
...opts,
|
|
26
32
|
workerEntry: opts.workerEntry || _defaultEntry
|
|
27
33
|
});
|
|
28
|
-
this.#initProcess(opts.execArgv);
|
|
34
|
+
this._initWithVirtualData(() => this.#initProcess(opts.execArgv));
|
|
29
35
|
}
|
|
30
36
|
sendMessage(message) {
|
|
31
37
|
if (!this.#process) throw new Error("Bun env process should be initialized before sending messages.");
|
|
@@ -86,6 +92,8 @@ var BunProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
86
92
|
child._exitCode = code;
|
|
87
93
|
this.close(`process exited with code ${code}`);
|
|
88
94
|
});
|
|
95
|
+
forwardStream(proc.stdout, process.stdout);
|
|
96
|
+
forwardStream(proc.stderr, process.stderr);
|
|
89
97
|
this.#process = child;
|
|
90
98
|
}
|
|
91
99
|
#initNodeProcess(execArgv, env) {
|
|
@@ -122,6 +130,8 @@ var BunProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
122
130
|
child.on("message", (message) => {
|
|
123
131
|
this._handleMessage(message);
|
|
124
132
|
});
|
|
133
|
+
child.stdout?.pipe(process.stdout);
|
|
134
|
+
child.stderr?.pipe(process.stderr);
|
|
125
135
|
this.#process = handle;
|
|
126
136
|
}
|
|
127
137
|
};
|
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
|
|
2
2
|
import { serve } from "srvx";
|
|
3
3
|
import { plugin } from "crossws/server";
|
|
4
|
+
process.on("disconnect", () => process.exit(0));
|
|
4
5
|
const data = JSON.parse(process.env.ENV_RUNNER_DATA || "{}");
|
|
5
|
-
let entry = await resolveEntry(data.entry);
|
|
6
6
|
const sendMessage = (message) => process.send(message);
|
|
7
|
+
const virtualEntry = isVirtualSpecifier(data.entry, data.virtual);
|
|
8
|
+
let unregisterVirtualModules;
|
|
9
|
+
let entry;
|
|
10
|
+
try {
|
|
11
|
+
unregisterVirtualModules = await registerVirtualModules(data.virtual);
|
|
12
|
+
entry = await resolveEntry(data.entry, virtualEntry);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
const message = error?.message || String(error);
|
|
15
|
+
sendMessage({
|
|
16
|
+
event: "init-error",
|
|
17
|
+
error: message
|
|
18
|
+
});
|
|
19
|
+
console.error(`[env-runner] worker init failed: ${message}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
7
22
|
const server = serve({
|
|
8
23
|
port: 0,
|
|
9
24
|
hostname: "127.0.0.1",
|
|
@@ -26,13 +41,14 @@ process.send({ address: parseServerAddress(server) });
|
|
|
26
41
|
process.on("message", async (message) => {
|
|
27
42
|
if (message?.event === "shutdown") {
|
|
28
43
|
Promise.resolve(entry.ipc?.onClose?.()).then(() => server.close()).then(() => {
|
|
44
|
+
unregisterVirtualModules();
|
|
29
45
|
process.send({ event: "exit" });
|
|
30
46
|
});
|
|
31
47
|
return;
|
|
32
48
|
}
|
|
33
49
|
if (message?.event === "reload-module") {
|
|
34
50
|
try {
|
|
35
|
-
entry = await reloadEntryModule(data.entry, entry, sendMessage);
|
|
51
|
+
entry = await reloadEntryModule(data.entry, entry, sendMessage, virtualEntry);
|
|
36
52
|
process.send({ event: "module-reloaded" });
|
|
37
53
|
} catch (error) {
|
|
38
54
|
process.send({
|
|
@@ -42,6 +58,10 @@ process.on("message", async (message) => {
|
|
|
42
58
|
}
|
|
43
59
|
return;
|
|
44
60
|
}
|
|
61
|
+
if (message?.event === "invalidate-module") {
|
|
62
|
+
handleInvalidateModule(message, sendMessage);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
45
65
|
if (message?.type === "ping") {
|
|
46
66
|
process.send({
|
|
47
67
|
type: "pong",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { EnvRunnerData } from "../../_chunks/base-runner.mjs";
|
|
2
|
+
import { DenoProcessEnvRunner } from "../../_chunks/runner.mjs";
|
|
3
3
|
export { DenoProcessEnvRunner, type EnvRunnerData as DenoProcessEnvRunnerData };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DenoProcessEnvRunner } from "../../_chunks/runner.mjs";
|
|
2
2
|
export { DenoProcessEnvRunner };
|