env-runner 0.1.5 → 0.1.7
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 +45 -15
- package/dist/THIRD-PARTY-LICENSES.md +0 -29
- package/dist/_chunks/base-runner.d.mts +0 -1
- package/dist/_chunks/base-runner.mjs +0 -15
- package/dist/_chunks/libs/cjs-module-lexer.mjs +1 -1
- package/dist/_chunks/runner.mjs +17 -13
- package/dist/_chunks/runner2.d.mts +8 -0
- package/dist/_chunks/runner2.mjs +14 -2
- package/dist/_chunks/runner3.d.mts +19 -0
- package/dist/_chunks/runner3.mjs +62 -0
- package/dist/_chunks/runner4.d.mts +17 -0
- package/dist/_chunks/runner4.mjs +42 -0
- package/dist/_chunks/runner5.d.mts +17 -0
- package/dist/_chunks/runner5.mjs +47 -0
- package/dist/_chunks/server.mjs +29 -8
- package/dist/_chunks/types.d.mts +1 -1
- package/dist/cli.mjs +4 -4
- package/dist/index.d.mts +17 -6
- package/dist/index.mjs +3 -3
- package/dist/runners/bun-process/runner.mjs +0 -8
- package/dist/runners/deno-process/runner.mjs +0 -2
- package/dist/runners/deno-process/worker.mjs +31 -8
- package/dist/runners/miniflare/runner.mjs +0 -3
- package/dist/runners/netlify/runner.d.mts +3 -0
- package/dist/runners/netlify/runner.mjs +2 -0
- package/dist/runners/netlify/worker.d.mts +1 -0
- package/dist/runners/netlify/worker.mjs +43 -0
- package/dist/runners/node-process/runner.mjs +0 -6
- package/dist/runners/node-worker/runner.d.mts +3 -19
- package/dist/runners/node-worker/runner.mjs +1 -67
- package/dist/runners/self/runner.mjs +0 -1
- package/dist/runners/vercel/runner.d.mts +3 -0
- package/dist/runners/vercel/runner.mjs +2 -0
- package/dist/runners/vercel/worker.d.mts +1 -0
- package/dist/runners/vercel/worker.mjs +50 -0
- package/package.json +27 -18
- package/dist/_chunks/libs/std-env.mjs +0 -171
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<!-- /automd -->
|
|
9
9
|
|
|
10
|
-
Generic environment runner for JavaScript runtimes. Run your server apps across Node.js worker threads, child processes, Bun, Deno, Cloudflare Workers (via miniflare), or in-process — with hot-reload, WebSocket proxying, and bidirectional messaging.
|
|
10
|
+
Generic environment runner for JavaScript runtimes. Run your server apps across Node.js worker threads, child processes, Bun, Deno, Cloudflare Workers (via miniflare), Vercel, Netlify, or in-process — with hot-reload, WebSocket proxying, and bidirectional messaging.
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
@@ -56,13 +56,13 @@ const envServer = new EnvServer({
|
|
|
56
56
|
watchPaths: ["./src"],
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
envServer.onReady
|
|
59
|
+
envServer.onReady((_runner, address) => {
|
|
60
60
|
console.log(`Worker ready on ${address?.host}:${address?.port}`);
|
|
61
|
-
};
|
|
61
|
+
});
|
|
62
62
|
|
|
63
|
-
envServer.onReload
|
|
63
|
+
envServer.onReload(() => {
|
|
64
64
|
console.log("Reloaded!");
|
|
65
|
-
};
|
|
65
|
+
});
|
|
66
66
|
|
|
67
67
|
await envServer.start();
|
|
68
68
|
|
|
@@ -81,9 +81,9 @@ import { RunnerManager, NodeProcessEnvRunner } from "env-runner";
|
|
|
81
81
|
|
|
82
82
|
const manager = new RunnerManager();
|
|
83
83
|
|
|
84
|
-
manager.onReady
|
|
84
|
+
manager.onReady((_runner, address) => {
|
|
85
85
|
console.log("Ready:", address);
|
|
86
|
-
};
|
|
86
|
+
});
|
|
87
87
|
|
|
88
88
|
// Load initial runner
|
|
89
89
|
const runner = new NodeProcessEnvRunner({
|
|
@@ -120,6 +120,8 @@ import { BunProcessEnvRunner } from "env-runner/runners/bun-process";
|
|
|
120
120
|
import { DenoProcessEnvRunner } from "env-runner/runners/deno-process";
|
|
121
121
|
import { SelfEnvRunner } from "env-runner/runners/self";
|
|
122
122
|
import { MiniflareEnvRunner } from "env-runner/runners/miniflare";
|
|
123
|
+
import { VercelEnvRunner } from "env-runner/runners/vercel";
|
|
124
|
+
import { NetlifyEnvRunner } from "env-runner/runners/netlify";
|
|
123
125
|
```
|
|
124
126
|
|
|
125
127
|
All runners implement the [`EnvRunner`](./src/types.ts) interface:
|
|
@@ -160,14 +162,16 @@ await runner.close();
|
|
|
160
162
|
|
|
161
163
|
**Available runners:**
|
|
162
164
|
|
|
163
|
-
| Runner | Isolation
|
|
164
|
-
| ---------------------- |
|
|
165
|
-
| `NodeWorkerEnvRunner` | Worker thread
|
|
166
|
-
| `NodeProcessEnvRunner` | Child process (`fork`)
|
|
167
|
-
| `BunProcessEnvRunner` | Bun or Node.js process
|
|
168
|
-
| `DenoProcessEnvRunner` | Deno process
|
|
169
|
-
| `SelfEnvRunner` | In-process
|
|
170
|
-
| `MiniflareEnvRunner` | Cloudflare Workers (miniflare)
|
|
165
|
+
| Runner | Isolation | IPC mechanism |
|
|
166
|
+
| ---------------------- | ------------------------------- | ---------------------------------- |
|
|
167
|
+
| `NodeWorkerEnvRunner` | Worker thread | `workerData` / `parentPort` |
|
|
168
|
+
| `NodeProcessEnvRunner` | Child process (`fork`) | `ENV_RUNNER_DATA` / `process.send` |
|
|
169
|
+
| `BunProcessEnvRunner` | Bun or Node.js process | `Bun.spawn` IPC or `fork()` |
|
|
170
|
+
| `DenoProcessEnvRunner` | Deno process | `deno run` with IPC channel |
|
|
171
|
+
| `SelfEnvRunner` | In-process | In-memory channel |
|
|
172
|
+
| `MiniflareEnvRunner` | Cloudflare Workers (miniflare) | WebSocket pair via `dispatchFetch` |
|
|
173
|
+
| `VercelEnvRunner` | Worker thread (Vercel context) | `workerData` / `parentPort` |
|
|
174
|
+
| `NetlifyEnvRunner` | Worker thread (Netlify context) | `workerData` / `parentPort` |
|
|
171
175
|
|
|
172
176
|
#### Miniflare Runner
|
|
173
177
|
|
|
@@ -289,6 +293,32 @@ const runner2 = new MiniflareEnvRunner({
|
|
|
289
293
|
// Fully destroy: runner.dispose() or MiniflareEnvRunner.disposeAll()
|
|
290
294
|
```
|
|
291
295
|
|
|
296
|
+
#### Vercel Runner
|
|
297
|
+
|
|
298
|
+
Simulates a Vercel deployment environment with automatic header injection (`x-vercel-deployment-url`, `x-vercel-forwarded-for`, forwarding headers) and global context.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import { VercelEnvRunner } from "env-runner/runners/vercel";
|
|
302
|
+
|
|
303
|
+
const runner = new VercelEnvRunner({
|
|
304
|
+
name: "my-app",
|
|
305
|
+
data: { entry: "./app.ts" },
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### Netlify Runner
|
|
310
|
+
|
|
311
|
+
Simulates a Netlify deployment environment with automatic header injection (`x-nf-client-connection-ip`, `x-nf-account-id`, `x-nf-site-id`, `x-nf-deploy-id`, `x-nf-deploy-context`, `x-nf-geo`, `x-nf-request-id`, forwarding headers) and `globalThis.Netlify` setup:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
import { NetlifyEnvRunner } from "env-runner/runners/netlify";
|
|
315
|
+
|
|
316
|
+
const runner = new NetlifyEnvRunner({
|
|
317
|
+
name: "my-app",
|
|
318
|
+
data: { entry: "./app.ts" },
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
292
322
|
### Vite Environment API
|
|
293
323
|
|
|
294
324
|
env-runner provides helpers for integrating with Vite's [Environment API](https://vite.dev/guide/api-environment-runtimes.html):
|
|
@@ -21,32 +21,3 @@ Repository: https://github.com/nodejs/cjs-module-lexer
|
|
|
21
21
|
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
22
22
|
>
|
|
23
23
|
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
|
-
|
|
25
|
-
---------------------------------------
|
|
26
|
-
|
|
27
|
-
## std-env
|
|
28
|
-
|
|
29
|
-
License: MIT
|
|
30
|
-
Repository: https://github.com/unjs/std-env
|
|
31
|
-
|
|
32
|
-
> MIT License
|
|
33
|
-
>
|
|
34
|
-
> Copyright (c) Pooya Parsa <pooya@pi0.io>
|
|
35
|
-
>
|
|
36
|
-
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
37
|
-
> of this software and associated documentation files (the "Software"), to deal
|
|
38
|
-
> in the Software without restriction, including without limitation the rights
|
|
39
|
-
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
40
|
-
> copies of the Software, and to permit persons to whom the Software is
|
|
41
|
-
> furnished to do so, subject to the following conditions:
|
|
42
|
-
>
|
|
43
|
-
> The above copyright notice and this permission notice shall be included in all
|
|
44
|
-
> copies or substantial portions of the Software.
|
|
45
|
-
>
|
|
46
|
-
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
47
|
-
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
48
|
-
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
49
|
-
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
50
|
-
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
51
|
-
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
52
|
-
> SOFTWARE.
|
|
@@ -41,7 +41,6 @@ declare abstract class BaseEnvRunner implements EnvRunner {
|
|
|
41
41
|
close(cause?: unknown): Promise<void>;
|
|
42
42
|
protected _handleMessage(message: any): void;
|
|
43
43
|
protected _closeSocket(): Promise<void>;
|
|
44
|
-
protected _requestGracefulShutdown(sendShutdown: () => void, listenForExit: (resolve: () => void) => void, hasExited: () => boolean): Promise<void>;
|
|
45
44
|
protected abstract _hasRuntime(): boolean;
|
|
46
45
|
protected abstract _closeRuntime(): Promise<void>;
|
|
47
46
|
protected abstract _runtimeType(): string;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { n as v, t as m } from "./libs/std-env.mjs";
|
|
2
1
|
import { rm } from "node:fs/promises";
|
|
3
2
|
import { proxyFetch, proxyUpgrade } from "httpxy";
|
|
4
3
|
//#region src/common/base-runner.ts
|
|
@@ -125,20 +124,6 @@ var BaseEnvRunner = class {
|
|
|
125
124
|
if (socketPath && socketPath[0] !== "\0" && !socketPath.startsWith(String.raw`\\.\\pipe`)) await rm(socketPath).catch(() => {});
|
|
126
125
|
this._address = void 0;
|
|
127
126
|
}
|
|
128
|
-
async _requestGracefulShutdown(sendShutdown, listenForExit, hasExited) {
|
|
129
|
-
sendShutdown();
|
|
130
|
-
if (!hasExited() && !v && !m) await new Promise((resolve) => {
|
|
131
|
-
const gracefulShutdownTimeoutMs = Number.parseInt(process.env.ENV_RUNNER_SHUTDOWN_TIMEOUT || "", 10) || 5e3;
|
|
132
|
-
const timeout = setTimeout(() => {
|
|
133
|
-
console.warn(`force closing node env runner ${this._runtimeType()}...`);
|
|
134
|
-
resolve();
|
|
135
|
-
}, gracefulShutdownTimeoutMs);
|
|
136
|
-
listenForExit(() => {
|
|
137
|
-
clearTimeout(timeout);
|
|
138
|
-
resolve();
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
127
|
};
|
|
143
128
|
//#endregion
|
|
144
129
|
export { BaseEnvRunner as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region node_modules/.
|
|
1
|
+
//#region node_modules/.pnpm/cjs-module-lexer@2.2.0/node_modules/cjs-module-lexer/dist/lexer.mjs
|
|
2
2
|
let A;
|
|
3
3
|
const B = 1 === new Uint8Array(new Uint16Array([1]).buffer)[0];
|
|
4
4
|
function parse(E, g = "@") {
|
package/dist/_chunks/runner.mjs
CHANGED
|
@@ -26,13 +26,6 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
26
26
|
}
|
|
27
27
|
async _closeRuntime() {
|
|
28
28
|
if (!this.#process) return;
|
|
29
|
-
if (this.#process._exitCode == null) await this._requestGracefulShutdown(() => {
|
|
30
|
-
try {
|
|
31
|
-
this.#process.send({ event: "shutdown" });
|
|
32
|
-
} catch {}
|
|
33
|
-
}, (resolve) => {
|
|
34
|
-
this.#process?.exited.then(() => resolve());
|
|
35
|
-
}, () => this.#process?._exitCode != null);
|
|
36
29
|
this.#process.removeAllListeners?.();
|
|
37
30
|
try {
|
|
38
31
|
this.#process.kill();
|
|
@@ -51,8 +44,9 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
51
44
|
};
|
|
52
45
|
const child = spawn("deno", [
|
|
53
46
|
"run",
|
|
54
|
-
"
|
|
47
|
+
"-A",
|
|
55
48
|
"--node-modules-dir=auto",
|
|
49
|
+
"--no-lock",
|
|
56
50
|
...execArgv || [],
|
|
57
51
|
this._workerEntry
|
|
58
52
|
], {
|
|
@@ -60,8 +54,7 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
60
54
|
stdio: [
|
|
61
55
|
"pipe",
|
|
62
56
|
"pipe",
|
|
63
|
-
"pipe"
|
|
64
|
-
"ipc"
|
|
57
|
+
"pipe"
|
|
65
58
|
]
|
|
66
59
|
});
|
|
67
60
|
const exited = new Promise((resolve) => {
|
|
@@ -70,7 +63,9 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
70
63
|
const handle = {
|
|
71
64
|
pid: child.pid,
|
|
72
65
|
kill: () => child.kill(),
|
|
73
|
-
send: (message) =>
|
|
66
|
+
send: (message) => {
|
|
67
|
+
child.stdin.write(JSON.stringify(message) + "\n");
|
|
68
|
+
},
|
|
74
69
|
exited,
|
|
75
70
|
_exitCode: void 0,
|
|
76
71
|
removeAllListeners: () => child.removeAllListeners()
|
|
@@ -85,8 +80,17 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
85
80
|
this.close(error);
|
|
86
81
|
}
|
|
87
82
|
});
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
let buffer = "";
|
|
84
|
+
child.stdout.on("data", (chunk) => {
|
|
85
|
+
buffer += chunk.toString();
|
|
86
|
+
let newlineIdx;
|
|
87
|
+
while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
|
|
88
|
+
const line = buffer.slice(0, newlineIdx);
|
|
89
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
90
|
+
if (line.startsWith("{")) try {
|
|
91
|
+
this._handleMessage(JSON.parse(line));
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
90
94
|
});
|
|
91
95
|
this.#process = handle;
|
|
92
96
|
}
|
|
@@ -46,6 +46,14 @@ interface MiniflareEnvRunnerOptions {
|
|
|
46
46
|
persistent?: boolean;
|
|
47
47
|
/** Wrap the user's `fetch` in a try/catch that returns structured JSON error responses. Default: `true`. */
|
|
48
48
|
captureErrors?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Export conditions for bare-specifier module resolution in the module
|
|
51
|
+
* fallback service. Ensures packages with conditional exports (e.g.
|
|
52
|
+
* `"workerd"`) resolve to the correct entry instead of the Node.js one.
|
|
53
|
+
*
|
|
54
|
+
* Defaults to `["workerd", "worker"]`.
|
|
55
|
+
*/
|
|
56
|
+
exportConditions?: string[];
|
|
49
57
|
}
|
|
50
58
|
declare class MiniflareEnvRunner extends BaseEnvRunner {
|
|
51
59
|
#private;
|
package/dist/_chunks/runner2.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { proxyUpgrade } from "httpxy";
|
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { basename, dirname, resolve } from "node:path";
|
|
8
|
+
import { resolveModulePath } from "exsolve";
|
|
8
9
|
//#region src/runners/miniflare/wrapper.ts
|
|
9
10
|
const IPC_PATH$1 = "/__env_runner_ipc";
|
|
10
11
|
/** Service binding name used for cross-request IPC (worker → runner). */
|
|
@@ -207,6 +208,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
207
208
|
#cacheKey;
|
|
208
209
|
#exports;
|
|
209
210
|
#captureErrors;
|
|
211
|
+
#exportConditions;
|
|
210
212
|
constructor(opts) {
|
|
211
213
|
super({
|
|
212
214
|
...opts,
|
|
@@ -217,6 +219,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
217
219
|
this.#persistent = opts.persistent ?? false;
|
|
218
220
|
this.#exports = opts.exports ?? {};
|
|
219
221
|
this.#captureErrors = opts.captureErrors ?? true;
|
|
222
|
+
this.#exportConditions = opts.exportConditions ?? ["workerd", "worker"];
|
|
220
223
|
this.#init();
|
|
221
224
|
}
|
|
222
225
|
/** Dispose all persistent Miniflare instances from the cache. */
|
|
@@ -399,6 +402,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
399
402
|
if (!options.unsafeModuleFallbackService) {
|
|
400
403
|
const _require = createRequire(resolvedEntry);
|
|
401
404
|
const _transformRequest = this.#transformRequest;
|
|
405
|
+
const _exportConditions = this.#exportConditions;
|
|
402
406
|
options.unsafeUseModuleFallbackService = true;
|
|
403
407
|
const modulePathMap = /* @__PURE__ */ new Map();
|
|
404
408
|
const _cjsLexerReady = ensureCjsLexer();
|
|
@@ -422,6 +426,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
422
426
|
const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
|
|
423
427
|
const referrerReal = modulePathMap.get(referrerKey);
|
|
424
428
|
const contextRequire = referrerReal ? createRequire(referrerReal) : _require;
|
|
429
|
+
if (cleanRaw.startsWith("cloudflare:")) return new Response(null, { status: 404 });
|
|
425
430
|
if (cleanRaw.startsWith("node:")) {
|
|
426
431
|
const nodeName = cleanRaw.slice(5);
|
|
427
432
|
try {
|
|
@@ -430,7 +435,11 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
430
435
|
return new Response(null, { status: 404 });
|
|
431
436
|
}
|
|
432
437
|
} else try {
|
|
433
|
-
resolvedPath =
|
|
438
|
+
resolvedPath = resolveModulePath(cleanRaw, {
|
|
439
|
+
from: referrerReal || resolvedEntry,
|
|
440
|
+
conditions: _exportConditions,
|
|
441
|
+
try: true
|
|
442
|
+
}) || contextRequire.resolve(cleanRaw);
|
|
434
443
|
} catch {
|
|
435
444
|
const name = cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier;
|
|
436
445
|
return Response.json({
|
|
@@ -486,7 +495,10 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
486
495
|
}
|
|
487
496
|
}
|
|
488
497
|
if (this.#persistent && entryPath) {
|
|
489
|
-
this.#cacheKey = computeCacheKey(entryPath,
|
|
498
|
+
this.#cacheKey = computeCacheKey(entryPath, {
|
|
499
|
+
...this.#miniflareOptions,
|
|
500
|
+
_exportConditions: this.#exportConditions
|
|
501
|
+
});
|
|
490
502
|
const cached = _miniflareCache.get(this.#cacheKey);
|
|
491
503
|
if (cached) {
|
|
492
504
|
this.#miniflare = cached.mf;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { u as WorkerHooks } from "./types.mjs";
|
|
2
|
+
import { n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/runners/node-worker/runner.d.ts
|
|
5
|
+
declare class NodeWorkerEnvRunner extends BaseEnvRunner {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(opts: {
|
|
8
|
+
name: string;
|
|
9
|
+
workerEntry?: string;
|
|
10
|
+
hooks?: WorkerHooks;
|
|
11
|
+
data?: EnvRunnerData;
|
|
12
|
+
});
|
|
13
|
+
sendMessage(message: unknown): void;
|
|
14
|
+
protected _hasRuntime(): boolean;
|
|
15
|
+
protected _runtimeType(): string;
|
|
16
|
+
protected _closeRuntime(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { NodeWorkerEnvRunner as t };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { t as BaseEnvRunner } from "./base-runner.mjs";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { Worker } from "node:worker_threads";
|
|
5
|
+
//#region src/runners/node-worker/runner.ts
|
|
6
|
+
let _defaultEntry;
|
|
7
|
+
var NodeWorkerEnvRunner = class extends BaseEnvRunner {
|
|
8
|
+
#worker;
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
_defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/node-worker/worker"));
|
|
11
|
+
super({
|
|
12
|
+
...opts,
|
|
13
|
+
workerEntry: opts.workerEntry || _defaultEntry
|
|
14
|
+
});
|
|
15
|
+
this.#initWorker();
|
|
16
|
+
}
|
|
17
|
+
sendMessage(message) {
|
|
18
|
+
if (!this.#worker) throw new Error("Worker thread should be initialized before sending messages.");
|
|
19
|
+
this.#worker.postMessage(message);
|
|
20
|
+
}
|
|
21
|
+
_hasRuntime() {
|
|
22
|
+
return Boolean(this.#worker);
|
|
23
|
+
}
|
|
24
|
+
_runtimeType() {
|
|
25
|
+
return "worker";
|
|
26
|
+
}
|
|
27
|
+
async _closeRuntime() {
|
|
28
|
+
if (!this.#worker) return;
|
|
29
|
+
this.#worker.removeAllListeners();
|
|
30
|
+
await this.#worker.terminate().catch((error) => {
|
|
31
|
+
console.error(error);
|
|
32
|
+
});
|
|
33
|
+
this.#worker = void 0;
|
|
34
|
+
}
|
|
35
|
+
#initWorker() {
|
|
36
|
+
if (!existsSync(this._workerEntry)) {
|
|
37
|
+
this.close(`worker entry not found in "${this._workerEntry}".`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const worker = new Worker(this._workerEntry, {
|
|
41
|
+
env: { ...process.env },
|
|
42
|
+
workerData: {
|
|
43
|
+
name: this._name,
|
|
44
|
+
...this._data
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
worker.once("exit", (code) => {
|
|
48
|
+
worker._exitCode = code;
|
|
49
|
+
this.close(`worker exited with code ${code}`);
|
|
50
|
+
});
|
|
51
|
+
worker.once("error", (error) => {
|
|
52
|
+
console.error(`Worker error:`, error);
|
|
53
|
+
this.close(error);
|
|
54
|
+
});
|
|
55
|
+
worker.on("message", (message) => {
|
|
56
|
+
this._handleMessage(message);
|
|
57
|
+
});
|
|
58
|
+
this.#worker = worker;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { NodeWorkerEnvRunner as t };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { u as WorkerHooks } from "./types.mjs";
|
|
2
|
+
import { n as EnvRunnerData } from "./base-runner.mjs";
|
|
3
|
+
import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/runners/vercel/runner.d.ts
|
|
6
|
+
declare class VercelEnvRunner extends NodeWorkerEnvRunner {
|
|
7
|
+
constructor(opts: {
|
|
8
|
+
name: string;
|
|
9
|
+
workerEntry?: string;
|
|
10
|
+
hooks?: WorkerHooks;
|
|
11
|
+
data?: EnvRunnerData;
|
|
12
|
+
});
|
|
13
|
+
fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
14
|
+
protected _runtimeType(): string;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { VercelEnvRunner as t };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/runners/vercel/runner.ts
|
|
4
|
+
let _defaultEntry;
|
|
5
|
+
var VercelEnvRunner = class extends NodeWorkerEnvRunner {
|
|
6
|
+
constructor(opts) {
|
|
7
|
+
_defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/vercel/worker"));
|
|
8
|
+
super({
|
|
9
|
+
...opts,
|
|
10
|
+
workerEntry: opts.workerEntry || _defaultEntry
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async fetch(input, init) {
|
|
14
|
+
const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
|
|
15
|
+
if (this._address && this._address.port != null && !headers.has("x-vercel-deployment-url")) {
|
|
16
|
+
const host = this._address.host || "127.0.0.1";
|
|
17
|
+
headers.set("x-vercel-deployment-url", `http://${host}:${this._address.port}`);
|
|
18
|
+
}
|
|
19
|
+
const clientIp = headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || "127.0.0.1";
|
|
20
|
+
if (!headers.has("x-vercel-forwarded-for")) headers.set("x-vercel-forwarded-for", clientIp);
|
|
21
|
+
if (!headers.has("x-forwarded-for")) headers.set("x-forwarded-for", clientIp);
|
|
22
|
+
if (!headers.has("x-real-ip")) headers.set("x-real-ip", clientIp);
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(input instanceof Request ? input.url : input.toString());
|
|
25
|
+
if (!headers.has("x-forwarded-proto")) headers.set("x-forwarded-proto", url.protocol.replace(":", ""));
|
|
26
|
+
if (!headers.has("x-forwarded-host")) headers.set("x-forwarded-host", headers.get("host") || url.host);
|
|
27
|
+
} catch {}
|
|
28
|
+
if (input instanceof Request) return super.fetch(new Request(input, {
|
|
29
|
+
...init,
|
|
30
|
+
headers
|
|
31
|
+
}));
|
|
32
|
+
return super.fetch(input, {
|
|
33
|
+
...init,
|
|
34
|
+
headers
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
_runtimeType() {
|
|
38
|
+
return "vercel";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { VercelEnvRunner as t };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { u as WorkerHooks } from "./types.mjs";
|
|
2
|
+
import { n as EnvRunnerData } from "./base-runner.mjs";
|
|
3
|
+
import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/runners/netlify/runner.d.ts
|
|
6
|
+
declare class NetlifyEnvRunner extends NodeWorkerEnvRunner {
|
|
7
|
+
constructor(opts: {
|
|
8
|
+
name: string;
|
|
9
|
+
workerEntry?: string;
|
|
10
|
+
hooks?: WorkerHooks;
|
|
11
|
+
data?: EnvRunnerData;
|
|
12
|
+
});
|
|
13
|
+
fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
14
|
+
protected _runtimeType(): string;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { NetlifyEnvRunner as t };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { t as NodeWorkerEnvRunner } from "./runner3.mjs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/runners/netlify/runner.ts
|
|
4
|
+
let _defaultEntry;
|
|
5
|
+
var NetlifyEnvRunner = class extends NodeWorkerEnvRunner {
|
|
6
|
+
constructor(opts) {
|
|
7
|
+
_defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/netlify/worker"));
|
|
8
|
+
super({
|
|
9
|
+
...opts,
|
|
10
|
+
workerEntry: opts.workerEntry || _defaultEntry
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async fetch(input, init) {
|
|
14
|
+
const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
|
|
15
|
+
const clientIp = headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || "127.0.0.1";
|
|
16
|
+
if (!headers.has("x-nf-client-connection-ip")) headers.set("x-nf-client-connection-ip", clientIp);
|
|
17
|
+
if (!headers.has("x-nf-account-id")) headers.set("x-nf-account-id", "0");
|
|
18
|
+
if (!headers.has("x-nf-site-id")) headers.set("x-nf-site-id", "0");
|
|
19
|
+
if (!headers.has("x-nf-deploy-id")) headers.set("x-nf-deploy-id", "0");
|
|
20
|
+
if (!headers.has("x-nf-deploy-context")) headers.set("x-nf-deploy-context", "dev");
|
|
21
|
+
if (!headers.has("x-nf-geo")) headers.set("x-nf-geo", btoa(JSON.stringify({
|
|
22
|
+
city: "localhost",
|
|
23
|
+
country: { code: "dev" }
|
|
24
|
+
})));
|
|
25
|
+
if (!headers.has("x-nf-request-id")) headers.set("x-nf-request-id", crypto.randomUUID());
|
|
26
|
+
if (!headers.has("x-forwarded-for")) headers.set("x-forwarded-for", clientIp);
|
|
27
|
+
if (!headers.has("x-real-ip")) headers.set("x-real-ip", clientIp);
|
|
28
|
+
try {
|
|
29
|
+
const url = new URL(input instanceof Request ? input.url : input.toString());
|
|
30
|
+
if (!headers.has("x-forwarded-proto")) headers.set("x-forwarded-proto", url.protocol.replace(":", ""));
|
|
31
|
+
if (!headers.has("x-forwarded-host")) headers.set("x-forwarded-host", headers.get("host") || url.host);
|
|
32
|
+
} catch {}
|
|
33
|
+
if (input instanceof Request) return super.fetch(new Request(input, {
|
|
34
|
+
...init,
|
|
35
|
+
headers
|
|
36
|
+
}));
|
|
37
|
+
return super.fetch(input, {
|
|
38
|
+
...init,
|
|
39
|
+
headers
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
_runtimeType() {
|
|
43
|
+
return "netlify";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { NetlifyEnvRunner as t };
|
package/dist/_chunks/server.mjs
CHANGED
|
@@ -10,6 +10,8 @@ var RunnerManager = class {
|
|
|
10
10
|
_messageListeners = /* @__PURE__ */ new Set();
|
|
11
11
|
_closed = false;
|
|
12
12
|
_reloading = false;
|
|
13
|
+
_closeListeners = /* @__PURE__ */ new Set();
|
|
14
|
+
_readyListeners = /* @__PURE__ */ new Set();
|
|
13
15
|
constructor(runner) {
|
|
14
16
|
if (runner) this._attach(runner);
|
|
15
17
|
}
|
|
@@ -106,12 +108,22 @@ var RunnerManager = class {
|
|
|
106
108
|
this._detach();
|
|
107
109
|
if (runner) await runner.close();
|
|
108
110
|
}
|
|
109
|
-
onClose
|
|
110
|
-
|
|
111
|
+
onClose(listener) {
|
|
112
|
+
this._closeListeners.add(listener);
|
|
113
|
+
}
|
|
114
|
+
offClose(listener) {
|
|
115
|
+
this._closeListeners.delete(listener);
|
|
116
|
+
}
|
|
117
|
+
onReady(listener) {
|
|
118
|
+
this._readyListeners.add(listener);
|
|
119
|
+
}
|
|
120
|
+
offReady(listener) {
|
|
121
|
+
this._readyListeners.delete(listener);
|
|
122
|
+
}
|
|
111
123
|
_internalListener = (message) => {
|
|
112
124
|
if (message?.address) {
|
|
113
125
|
this._flushQueue();
|
|
114
|
-
this.
|
|
126
|
+
for (const fn of this._readyListeners) fn(this, message.address);
|
|
115
127
|
}
|
|
116
128
|
};
|
|
117
129
|
_attach(runner) {
|
|
@@ -123,7 +135,7 @@ var RunnerManager = class {
|
|
|
123
135
|
await originalClose();
|
|
124
136
|
if (this._runner === runner) {
|
|
125
137
|
this._runner = void 0;
|
|
126
|
-
this.
|
|
138
|
+
for (const fn of this._closeListeners) fn(this);
|
|
127
139
|
}
|
|
128
140
|
};
|
|
129
141
|
if (runner.ready) this._flushQueue();
|
|
@@ -163,7 +175,9 @@ const loaders = {
|
|
|
163
175
|
"bun-process": () => import("env-runner/runners/bun-process").then((m) => m.BunProcessEnvRunner),
|
|
164
176
|
"deno-process": () => import("env-runner/runners/deno-process").then((m) => m.DenoProcessEnvRunner),
|
|
165
177
|
self: () => import("env-runner/runners/self").then((m) => m.SelfEnvRunner),
|
|
166
|
-
miniflare: () => import("env-runner/runners/miniflare").then((m) => m.MiniflareEnvRunner)
|
|
178
|
+
miniflare: () => import("env-runner/runners/miniflare").then((m) => m.MiniflareEnvRunner),
|
|
179
|
+
vercel: () => import("env-runner/runners/vercel").then((m) => m.VercelEnvRunner),
|
|
180
|
+
netlify: () => import("env-runner/runners/netlify").then((m) => m.NetlifyEnvRunner)
|
|
167
181
|
};
|
|
168
182
|
async function loadRunner(runner, opts) {
|
|
169
183
|
return new (await (loaders[runner]()))(opts);
|
|
@@ -174,9 +188,16 @@ var EnvServer = class extends RunnerManager {
|
|
|
174
188
|
_opts;
|
|
175
189
|
_watchers = [];
|
|
176
190
|
_reloadTimeout;
|
|
191
|
+
_reloadListeners = /* @__PURE__ */ new Set();
|
|
177
192
|
runner = null;
|
|
178
|
-
/**
|
|
179
|
-
onReload
|
|
193
|
+
/** Register a listener called when the runner is reloaded due to a file change. */
|
|
194
|
+
onReload(listener) {
|
|
195
|
+
this._reloadListeners.add(listener);
|
|
196
|
+
}
|
|
197
|
+
/** Remove a previously registered reload listener. */
|
|
198
|
+
offReload(listener) {
|
|
199
|
+
this._reloadListeners.delete(listener);
|
|
200
|
+
}
|
|
180
201
|
constructor(opts) {
|
|
181
202
|
super();
|
|
182
203
|
this._opts = opts;
|
|
@@ -223,7 +244,7 @@ var EnvServer = class extends RunnerManager {
|
|
|
223
244
|
try {
|
|
224
245
|
this.runner = await this._createRunner();
|
|
225
246
|
await this.reload(this.runner);
|
|
226
|
-
this.
|
|
247
|
+
for (const fn of this._reloadListeners) fn();
|
|
227
248
|
} catch (error) {
|
|
228
249
|
console.error("Failed to reload runner:", error);
|
|
229
250
|
}
|
package/dist/_chunks/types.d.mts
CHANGED
|
@@ -54,7 +54,7 @@ interface RPCOptions {
|
|
|
54
54
|
timeout?: number;
|
|
55
55
|
}
|
|
56
56
|
/** Core runner interface combining lifecycle hooks, RPC, and request proxying. */
|
|
57
|
-
interface EnvRunner extends
|
|
57
|
+
interface EnvRunner extends RunnerRPCHooks {
|
|
58
58
|
/** Whether the worker is ready to accept requests. */
|
|
59
59
|
readonly ready: boolean;
|
|
60
60
|
/** Whether the runner has been closed. */
|
package/dist/cli.mjs
CHANGED
|
@@ -40,12 +40,12 @@ const envServer = new EnvServer({
|
|
|
40
40
|
entry: resolve(entry),
|
|
41
41
|
watch: values.watch
|
|
42
42
|
});
|
|
43
|
-
envServer.onReady
|
|
43
|
+
envServer.onReady((_runner, address) => {
|
|
44
44
|
console.log(`\x1B[2m➜ Worker ready on ${address?.host}:${address?.port || address?.socketPath}\x1B[0m`);
|
|
45
|
-
};
|
|
46
|
-
envServer.onReload
|
|
45
|
+
});
|
|
46
|
+
envServer.onReload(() => {
|
|
47
47
|
console.log(`\x1B[2m↻ Reloading...\x1B[0m`);
|
|
48
|
-
};
|
|
48
|
+
});
|
|
49
49
|
await envServer.start();
|
|
50
50
|
const server = serve({
|
|
51
51
|
port: values.port,
|