env-runner 0.2.2 → 0.3.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.
- package/README.md +203 -26
- package/dist/_chunks/common-base-runner.d.mts +58 -51
- package/dist/_chunks/common-base-runner.mjs +127 -33
- package/dist/_chunks/common-process-data.mjs +13 -0
- package/dist/_chunks/common-runtime-deps.d.mts +7 -30
- package/dist/_chunks/common-worker-utils.mjs +464 -73
- package/dist/_chunks/deno-process-runner.mjs +11 -37
- package/dist/_chunks/lexer.mjs +2 -0
- package/dist/_chunks/libs/cjs-module-lexer.mjs +6 -1
- package/dist/_chunks/miniflare-runner.d.mts +56 -107
- package/dist/_chunks/miniflare-runner.mjs +834 -154
- package/dist/_chunks/netlify-runner.d.mts +3 -21
- package/dist/_chunks/node-worker-runner.mjs +1 -2
- package/dist/_chunks/server.mjs +20 -5
- package/dist/_chunks/types.d.mts +36 -9
- package/dist/_chunks/vercel-runner.mjs +1 -0
- package/dist/_chunks/virtual-loader.mjs +303 -29
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +31 -69
- package/dist/runners/bun-process/runner.mjs +4 -8
- package/dist/runners/bun-process/worker.mjs +8 -8
- package/dist/runners/deno-process/worker.mjs +17 -39
- package/dist/runners/node-process/runner.mjs +2 -6
- package/dist/runners/node-process/worker.mjs +8 -8
- package/dist/runners/node-worker/worker.mjs +6 -7
- package/dist/runners/self/runner.d.mts +2 -1
- package/dist/runners/self/runner.mjs +8 -0
- package/dist/runners/vercel/queue-dev.d.mts +9 -45
- package/dist/vite.d.mts +2 -12
- package/package.json +13 -13
- package/dist/_chunks/common-host-env.mjs +0 -11
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import { resolveVirtualModules } from "./virtual-loader.mjs";
|
|
1
|
+
import { encodeVirtualModules, normalizeVirtualModules, resolveVirtualModules, warnVirtualPathCollisions } from "./virtual-loader.mjs";
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
3
|
import { proxyFetch, proxyUpgrade } from "httpxy";
|
|
4
|
+
function hostEnv(extra) {
|
|
5
|
+
const env = {
|
|
6
|
+
...process.env,
|
|
7
|
+
...extra
|
|
8
|
+
};
|
|
9
|
+
const stdout = process.stdout;
|
|
10
|
+
if (!("FORCE_COLOR" in env) && !env.NO_COLOR && stdout?.isTTY) env.FORCE_COLOR = "1";
|
|
11
|
+
if (!("COLUMNS" in env) && stdout?.columns) env.COLUMNS = String(stdout.columns);
|
|
12
|
+
return env;
|
|
13
|
+
}
|
|
4
14
|
var BaseEnvRunner = class {
|
|
5
15
|
closed = false;
|
|
6
16
|
_name;
|
|
@@ -11,7 +21,11 @@ var BaseEnvRunner = class {
|
|
|
11
21
|
_address;
|
|
12
22
|
_messageListeners;
|
|
13
23
|
_pendingRequests;
|
|
24
|
+
_closeCause;
|
|
14
25
|
_virtualResolved;
|
|
26
|
+
_virtualUpdates = Promise.resolve();
|
|
27
|
+
#virtualUpdateId = 0;
|
|
28
|
+
_processData;
|
|
15
29
|
constructor(opts) {
|
|
16
30
|
this._name = opts.name;
|
|
17
31
|
this._workerEntry = opts.workerEntry;
|
|
@@ -27,7 +41,7 @@ var BaseEnvRunner = class {
|
|
|
27
41
|
return this._address;
|
|
28
42
|
}
|
|
29
43
|
async fetch(input, init) {
|
|
30
|
-
|
|
44
|
+
await this._waitForAddress();
|
|
31
45
|
if (!this._address) return new Response(`${this._runtimeType()} env runner is unavailable`, { status: 503 });
|
|
32
46
|
return proxyFetch(this._address, this._resolveFetchInput(input), init);
|
|
33
47
|
}
|
|
@@ -49,24 +63,29 @@ var BaseEnvRunner = class {
|
|
|
49
63
|
}
|
|
50
64
|
waitForReady(timeout = 15e3) {
|
|
51
65
|
if (this.ready) return Promise.resolve();
|
|
52
|
-
if (this.closed) return Promise.reject(
|
|
66
|
+
if (this.closed) return Promise.reject(closedBeforeReadyError(this._closeCause));
|
|
53
67
|
return new Promise((resolve, reject) => {
|
|
54
68
|
const timer = setTimeout(() => {
|
|
55
|
-
|
|
69
|
+
cleanup();
|
|
56
70
|
reject(/* @__PURE__ */ new Error("Runner did not become ready in time"));
|
|
57
71
|
}, timeout);
|
|
58
72
|
const listener = () => {
|
|
59
73
|
if (this.ready) {
|
|
60
|
-
|
|
61
|
-
this._messageListeners.delete(listener);
|
|
74
|
+
cleanup();
|
|
62
75
|
resolve();
|
|
63
|
-
} else if (this.closed) {
|
|
64
|
-
clearTimeout(timer);
|
|
65
|
-
this._messageListeners.delete(listener);
|
|
66
|
-
reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
|
|
67
76
|
}
|
|
68
77
|
};
|
|
78
|
+
const onClose = (cause) => {
|
|
79
|
+
cleanup();
|
|
80
|
+
reject(closedBeforeReadyError(cause));
|
|
81
|
+
};
|
|
82
|
+
const cleanup = () => {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
this._messageListeners.delete(listener);
|
|
85
|
+
this._pendingRequests.delete(onClose);
|
|
86
|
+
};
|
|
69
87
|
this._messageListeners.add(listener);
|
|
88
|
+
this._pendingRequests.add(onClose);
|
|
70
89
|
});
|
|
71
90
|
}
|
|
72
91
|
rpc(name, data, opts) {
|
|
@@ -82,27 +101,27 @@ var BaseEnvRunner = class {
|
|
|
82
101
|
}).then((msg) => msg.data);
|
|
83
102
|
}
|
|
84
103
|
async reloadModule(timeout = 5e3) {
|
|
104
|
+
await this._virtualUpdates;
|
|
85
105
|
await this._request({ event: "reload-module" }, {
|
|
86
106
|
match: (msg) => msg?.event === "module-reloaded",
|
|
87
107
|
timeout,
|
|
88
108
|
timeoutError: "Module reload timed out"
|
|
89
109
|
});
|
|
90
110
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
source
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
timeoutError: `Module invalidation timed out for "${specifier}"`
|
|
101
|
-
});
|
|
111
|
+
updateVirtualModules(changes, timeout = 5e3) {
|
|
112
|
+
return this._enqueueVirtualUpdate(() => changes, timeout);
|
|
113
|
+
}
|
|
114
|
+
invalidateModule(specifier, timeout = 5e3) {
|
|
115
|
+
return this._enqueueVirtualUpdate(() => {
|
|
116
|
+
const source = this._virtualSources?.[specifier];
|
|
117
|
+
if (source === void 0) throw new Error(`Cannot invalidate "${specifier}" (not a registered virtual module)`);
|
|
118
|
+
return { [specifier]: source };
|
|
119
|
+
}, timeout);
|
|
102
120
|
}
|
|
103
121
|
async close(cause) {
|
|
104
122
|
if (this.closed) return;
|
|
105
123
|
this.closed = true;
|
|
124
|
+
this._closeCause = cause;
|
|
106
125
|
for (const rejectPending of this._pendingRequests) rejectPending(cause);
|
|
107
126
|
this._pendingRequests.clear();
|
|
108
127
|
this._hooks.onClose?.(this, cause);
|
|
@@ -118,6 +137,9 @@ var BaseEnvRunner = class {
|
|
|
118
137
|
const status = this.closed ? "closed" : this.ready ? "ready" : "pending";
|
|
119
138
|
return `${this.constructor.name}#${this._name}(${status})`;
|
|
120
139
|
}
|
|
140
|
+
async _waitForAddress() {
|
|
141
|
+
for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
|
|
142
|
+
}
|
|
121
143
|
_resolveFetchInput(input) {
|
|
122
144
|
if (typeof input === "string" && !URL.canParse(input)) return new URL(input, "http://localhost");
|
|
123
145
|
return input;
|
|
@@ -130,6 +152,36 @@ var BaseEnvRunner = class {
|
|
|
130
152
|
if (message?.event === "init-error" && !this.ready && !this.closed) this.close(new Error(String(message.error || "Worker initialization failed")));
|
|
131
153
|
for (const listener of this._messageListeners) listener(message);
|
|
132
154
|
}
|
|
155
|
+
_processEnv() {
|
|
156
|
+
const data = this._data || {};
|
|
157
|
+
const virtual = data.virtual;
|
|
158
|
+
try {
|
|
159
|
+
this._processData = JSON.stringify(virtual ? {
|
|
160
|
+
...data,
|
|
161
|
+
virtual: encodeVirtualModules(virtual)
|
|
162
|
+
} : data);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
throw new TypeError(`Runner data must be JSON-serializable: ${error?.message || error}`, { cause: error });
|
|
165
|
+
}
|
|
166
|
+
return hostEnv({ ENV_RUNNER_NAME: this._name });
|
|
167
|
+
}
|
|
168
|
+
_handleProcessMessage(message) {
|
|
169
|
+
if (message?.event !== "request-init-data") {
|
|
170
|
+
this._handleMessage(message);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (this.closed) return;
|
|
174
|
+
try {
|
|
175
|
+
this.sendMessage({
|
|
176
|
+
event: "init-data",
|
|
177
|
+
data: this._processData ?? "{}"
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const cause = new Error(`Failed to send runner data to the worker over IPC: ${error?.message || error}`, { cause: error });
|
|
181
|
+
console.error(`[env-runner] ${cause.message}`);
|
|
182
|
+
this.close(cause);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
133
185
|
_request(message, opts) {
|
|
134
186
|
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Runner is closed"));
|
|
135
187
|
return new Promise((resolve, reject) => {
|
|
@@ -165,8 +217,17 @@ var BaseEnvRunner = class {
|
|
|
165
217
|
}
|
|
166
218
|
_resolveVirtualData() {
|
|
167
219
|
const virtual = this._data?.virtual;
|
|
168
|
-
this._virtualSources = virtual;
|
|
169
|
-
|
|
220
|
+
this._virtualSources = { ...virtual };
|
|
221
|
+
this._data = { ...this._data };
|
|
222
|
+
warnVirtualPathCollisions(Object.keys(virtual ?? {}));
|
|
223
|
+
if (!virtual) return;
|
|
224
|
+
if (!Object.values(virtual).some((v) => typeof v === "function")) try {
|
|
225
|
+
this._data.virtual = normalizeVirtualModules(virtual);
|
|
226
|
+
return;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
this._virtualResolved = Promise.reject(error);
|
|
229
|
+
return this._virtualResolved;
|
|
230
|
+
}
|
|
170
231
|
this._virtualResolved = resolveVirtualModules(virtual).then((resolved) => {
|
|
171
232
|
this._data = {
|
|
172
233
|
...this._data,
|
|
@@ -175,20 +236,50 @@ var BaseEnvRunner = class {
|
|
|
175
236
|
});
|
|
176
237
|
return this._virtualResolved;
|
|
177
238
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
239
|
+
_enqueueVirtualUpdate(changes, timeout) {
|
|
240
|
+
const update = this._virtualUpdates.then(async () => {
|
|
241
|
+
await this._virtualResolved;
|
|
242
|
+
if (this.closed) throw new Error("Runner is closed");
|
|
243
|
+
const entries = Object.entries(changes()).filter(([, source]) => source !== void 0);
|
|
244
|
+
if (entries.length === 0) return;
|
|
245
|
+
const sets = Object.fromEntries(entries.filter((entry) => entry[1] !== null));
|
|
246
|
+
const resolved = await resolveVirtualModules(sets);
|
|
247
|
+
const sources = this._virtualSources ??= {};
|
|
248
|
+
const virtual = (this._data ??= {}).virtual ??= {};
|
|
249
|
+
let added = false;
|
|
250
|
+
for (const [key, source] of entries) if (source === null) {
|
|
251
|
+
resolved[key] = null;
|
|
252
|
+
delete sources[key];
|
|
253
|
+
delete virtual[key];
|
|
254
|
+
} else {
|
|
255
|
+
added ||= !Object.hasOwn(sources, key);
|
|
256
|
+
sources[key] = source;
|
|
257
|
+
virtual[key] = resolved[key];
|
|
258
|
+
}
|
|
259
|
+
if (added) warnVirtualPathCollisions(Object.keys(virtual));
|
|
260
|
+
if (!this.ready) await this.waitForReady();
|
|
261
|
+
await this._applyVirtualUpdates(resolved, timeout);
|
|
262
|
+
});
|
|
263
|
+
this._virtualUpdates = update.catch(() => {});
|
|
264
|
+
return update;
|
|
265
|
+
}
|
|
266
|
+
async _applyVirtualUpdates(changes, timeout) {
|
|
267
|
+
const id = ++this.#virtualUpdateId;
|
|
268
|
+
await this._request({
|
|
269
|
+
event: "update-virtual-modules",
|
|
270
|
+
id,
|
|
271
|
+
changes: encodeVirtualModules(changes)
|
|
272
|
+
}, {
|
|
273
|
+
match: (msg) => msg?.event === "virtual-modules-updated" && msg.id === id,
|
|
274
|
+
timeout,
|
|
275
|
+
timeoutError: "Virtual module update timed out"
|
|
276
|
+
});
|
|
186
277
|
}
|
|
187
278
|
_initWithVirtualData(init) {
|
|
188
279
|
const pending = this._resolveVirtualData();
|
|
189
280
|
if (pending) pending.then(() => {
|
|
190
281
|
if (!this.closed) init();
|
|
191
|
-
}
|
|
282
|
+
}).catch((error) => this.close(error));
|
|
192
283
|
else init();
|
|
193
284
|
}
|
|
194
285
|
async _closeSocket() {
|
|
@@ -197,4 +288,7 @@ var BaseEnvRunner = class {
|
|
|
197
288
|
this._address = void 0;
|
|
198
289
|
}
|
|
199
290
|
};
|
|
200
|
-
|
|
291
|
+
function closedBeforeReadyError(cause) {
|
|
292
|
+
return new Error("Runner closed before becoming ready", cause ? { cause } : void 0);
|
|
293
|
+
}
|
|
294
|
+
export { BaseEnvRunner, hostEnv };
|
|
@@ -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,18 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* shapes, so the choice is about ergonomics rather than per-runner API:
|
|
6
|
-
*
|
|
7
|
-
* - the **imported module** (`import * as miniflare from "miniflare"`) — the
|
|
8
|
-
* version installed by the app is the version that runs
|
|
9
|
-
* - a **module specifier** (`"miniflare"`, `import.meta.resolve("miniflare")`,
|
|
10
|
-
* or a `URL`) — resolved from {@link ResolveRuntimeDepOptions.from} (cwd by
|
|
11
|
-
* default), so a bare specifier resolves against the app rather than against
|
|
12
|
-
* `env-runner`'s own `node_modules`
|
|
13
|
-
* - `false` — opt out of the package entirely
|
|
14
|
-
*
|
|
15
|
-
* Omitting the option falls back to an optional `import()` of the package.
|
|
2
|
+
* An app-owned package: the imported module, a specifier (resolved from the
|
|
3
|
+
* app, not `env-runner`), or `false` to opt out. Omitted falls back to an
|
|
4
|
+
* optional `import()`.
|
|
16
5
|
*/
|
|
17
6
|
export type RuntimeDep<T> = T | string | URL | false;
|
|
18
7
|
interface ResolveRuntimeDepOptions<T> {
|
|
@@ -22,29 +11,17 @@ interface ResolveRuntimeDepOptions<T> {
|
|
|
22
11
|
option: string;
|
|
23
12
|
/** Value as passed by the caller. */
|
|
24
13
|
value?: RuntimeDep<T>;
|
|
25
|
-
/**
|
|
26
|
-
* Named export the resolved module must expose. A module that lacks it is
|
|
27
|
-
* treated as the wrong package (throws for an explicit value).
|
|
28
|
-
*/
|
|
14
|
+
/** Named export identifying the package (a mismatch throws for an explicit value). */
|
|
29
15
|
expect?: string;
|
|
30
16
|
/** Directory bare specifiers resolve from. @default process.cwd() */
|
|
31
17
|
from?: string;
|
|
32
|
-
/**
|
|
33
|
-
* Throw when the package cannot be resolved at all. Otherwise an
|
|
34
|
-
* unavailable optional package resolves to `undefined` and the caller
|
|
35
|
-
* degrades (minimal reader, shim, no-op).
|
|
36
|
-
*/
|
|
18
|
+
/** Throw when unresolvable instead of resolving `undefined` (callers degrade). */
|
|
37
19
|
required?: boolean;
|
|
38
20
|
/** Extra sentence appended to the "not installed" error when `required`. */
|
|
39
21
|
hint?: string;
|
|
40
22
|
}
|
|
41
23
|
/**
|
|
42
|
-
* Resolve a {@link RuntimeDep} to
|
|
43
|
-
*
|
|
44
|
-
* Resolution order: `false` → `undefined`; an imported module → validated and
|
|
45
|
-
* returned as-is; a specifier → imported (errors propagate, since an explicit
|
|
46
|
-
* specifier that cannot load is a mistake worth surfacing); omitted → optional
|
|
47
|
-
* `import(name)`, which yields `undefined` when the package isn't installed
|
|
48
|
-
* unless {@link ResolveRuntimeDepOptions.required} is set.
|
|
24
|
+
* Resolve a {@link RuntimeDep} to a module. An explicit specifier that fails to
|
|
25
|
+
* import throws; an omitted one resolves `undefined` unless `required`.
|
|
49
26
|
*/
|
|
50
27
|
export declare function resolveRuntimeDep<T>(opts: ResolveRuntimeDepOptions<T>): Promise<T | undefined>;
|