env-runner 0.0.0 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/THIRD-PARTY-LICENSES.md +33 -0
- package/dist/_chunks/base-runner.d.mts +105 -0
- package/dist/_chunks/base-runner.mjs +79 -0
- package/dist/_chunks/libs/std-env.mjs +171 -0
- package/dist/_chunks/runner.d.mts +19 -0
- package/dist/_chunks/runner.mjs +95 -0
- package/dist/_chunks/runner2.d.mts +21 -0
- package/dist/_chunks/runner2.mjs +146 -0
- package/dist/_chunks/server.mjs +186 -0
- package/dist/_chunks/worker-utils.mjs +16 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +71 -0
- package/dist/index.d.mts +100 -0
- package/dist/index.mjs +6 -0
- package/dist/runners/bun-process/runner.d.mts +19 -0
- package/dist/runners/bun-process/runner.mjs +138 -0
- package/dist/runners/bun-process/worker.d.mts +1 -0
- package/dist/runners/bun-process/worker.mjs +34 -0
- package/dist/runners/deno-process/runner.d.mts +3 -0
- package/dist/runners/deno-process/runner.mjs +4 -0
- package/dist/runners/deno-process/worker.d.mts +1 -0
- package/dist/runners/deno-process/worker.mjs +34 -0
- package/dist/runners/miniflare/runner.d.mts +3 -0
- package/dist/runners/miniflare/runner.mjs +4 -0
- package/dist/runners/node-process/runner.d.mts +19 -0
- package/dist/runners/node-process/runner.mjs +73 -0
- package/dist/runners/node-process/worker.d.mts +1 -0
- package/dist/runners/node-process/worker.mjs +34 -0
- package/dist/runners/node-worker/runner.d.mts +18 -0
- package/dist/runners/node-worker/runner.mjs +68 -0
- package/dist/runners/node-worker/worker.d.mts +1 -0
- package/dist/runners/node-worker/worker.mjs +35 -0
- package/dist/runners/self/runner.d.mts +18 -0
- package/dist/runners/self/runner.mjs +66 -0
- package/package.json +64 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { t as BaseEnvRunner } from "./base-runner.mjs";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
//#region src/runners/deno-process/runner.ts
|
|
6
|
+
let _defaultEntry;
|
|
7
|
+
var DenoProcessEnvRunner = class extends BaseEnvRunner {
|
|
8
|
+
#process;
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
_defaultEntry ||= fileURLToPath(import.meta.resolve("env-runner/runners/deno-process/worker"));
|
|
11
|
+
super({
|
|
12
|
+
...opts,
|
|
13
|
+
workerEntry: opts.workerEntry || _defaultEntry
|
|
14
|
+
});
|
|
15
|
+
this.#initProcess(opts.execArgv);
|
|
16
|
+
}
|
|
17
|
+
sendMessage(message) {
|
|
18
|
+
if (!this.#process) throw new Error("Deno env process should be initialized before sending messages.");
|
|
19
|
+
this.#process.send(message);
|
|
20
|
+
}
|
|
21
|
+
_hasRuntime() {
|
|
22
|
+
return Boolean(this.#process);
|
|
23
|
+
}
|
|
24
|
+
_runtimeType() {
|
|
25
|
+
return "process";
|
|
26
|
+
}
|
|
27
|
+
async _closeRuntime() {
|
|
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
|
+
this.#process.removeAllListeners?.();
|
|
37
|
+
try {
|
|
38
|
+
this.#process.kill();
|
|
39
|
+
} catch {}
|
|
40
|
+
this.#process = void 0;
|
|
41
|
+
}
|
|
42
|
+
#initProcess(execArgv) {
|
|
43
|
+
if (!existsSync(this._workerEntry)) {
|
|
44
|
+
this.close(`process entry not found in "${this._workerEntry}".`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const env = {
|
|
48
|
+
...process.env,
|
|
49
|
+
ENV_RUNNER_NAME: this._name,
|
|
50
|
+
ENV_RUNNER_DATA: JSON.stringify(this._data || {})
|
|
51
|
+
};
|
|
52
|
+
const child = spawn("deno", [
|
|
53
|
+
"run",
|
|
54
|
+
"--allow-all",
|
|
55
|
+
"--node-modules-dir=auto",
|
|
56
|
+
...execArgv || [],
|
|
57
|
+
this._workerEntry
|
|
58
|
+
], {
|
|
59
|
+
env,
|
|
60
|
+
stdio: [
|
|
61
|
+
"pipe",
|
|
62
|
+
"pipe",
|
|
63
|
+
"pipe",
|
|
64
|
+
"ipc"
|
|
65
|
+
]
|
|
66
|
+
});
|
|
67
|
+
const exited = new Promise((resolve) => {
|
|
68
|
+
child.once("exit", (code) => resolve(code ?? 1));
|
|
69
|
+
});
|
|
70
|
+
const handle = {
|
|
71
|
+
pid: child.pid,
|
|
72
|
+
kill: () => child.kill(),
|
|
73
|
+
send: (message) => child.send(message),
|
|
74
|
+
exited,
|
|
75
|
+
_exitCode: void 0,
|
|
76
|
+
removeAllListeners: () => child.removeAllListeners()
|
|
77
|
+
};
|
|
78
|
+
child.once("exit", (code) => {
|
|
79
|
+
handle._exitCode = code;
|
|
80
|
+
this.close(`process exited with code ${code}`);
|
|
81
|
+
});
|
|
82
|
+
child.on("error", (error) => {
|
|
83
|
+
if (!this.closed) {
|
|
84
|
+
console.error(`Process error:`, error);
|
|
85
|
+
this.close(error);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
child.on("message", (message) => {
|
|
89
|
+
this._handleMessage(message);
|
|
90
|
+
});
|
|
91
|
+
this.#process = handle;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
export { DenoProcessEnvRunner as t };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { d as WorkerHooks, n as EnvRunnerData, t as BaseEnvRunner } from "./base-runner.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/runners/miniflare/runner.d.ts
|
|
4
|
+
interface MiniflareEnvRunnerOptions {
|
|
5
|
+
name: string;
|
|
6
|
+
hooks?: WorkerHooks;
|
|
7
|
+
data?: EnvRunnerData;
|
|
8
|
+
/** Options passed directly to the Miniflare constructor. */
|
|
9
|
+
miniflareOptions?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
declare class MiniflareEnvRunner extends BaseEnvRunner {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(opts: MiniflareEnvRunnerOptions);
|
|
14
|
+
fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
15
|
+
sendMessage(message: unknown): void;
|
|
16
|
+
protected _hasRuntime(): boolean;
|
|
17
|
+
protected _runtimeType(): string;
|
|
18
|
+
protected _closeRuntime(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { MiniflareEnvRunnerOptions as n, MiniflareEnvRunner as t };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { t as BaseEnvRunner } from "./base-runner.mjs";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
//#region src/runners/miniflare/runner.ts
|
|
4
|
+
const IPC_HEADER = "x-env-runner-ipc";
|
|
5
|
+
var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
6
|
+
#miniflare;
|
|
7
|
+
#miniflareOptions;
|
|
8
|
+
constructor(opts) {
|
|
9
|
+
super({
|
|
10
|
+
...opts,
|
|
11
|
+
workerEntry: ""
|
|
12
|
+
});
|
|
13
|
+
this.#miniflareOptions = opts.miniflareOptions || {};
|
|
14
|
+
this.#init();
|
|
15
|
+
}
|
|
16
|
+
async fetch(input, init) {
|
|
17
|
+
if (!this.#miniflare || this.closed) return new Response("miniflare env runner is unavailable", { status: 503 });
|
|
18
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
19
|
+
return this.#miniflare.dispatchFetch(url, init);
|
|
20
|
+
}
|
|
21
|
+
sendMessage(message) {
|
|
22
|
+
if (!this.#miniflare) throw new Error("Miniflare env runner should be initialized before sending messages.");
|
|
23
|
+
if (message?.type === "ping") {
|
|
24
|
+
queueMicrotask(() => this._handleMessage({
|
|
25
|
+
type: "pong",
|
|
26
|
+
data: message.data
|
|
27
|
+
}));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.#miniflare.dispatchFetch("http://localhost/__env_runner_ipc", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { [IPC_HEADER]: "message" },
|
|
33
|
+
body: JSON.stringify(message)
|
|
34
|
+
}).catch(() => {});
|
|
35
|
+
}
|
|
36
|
+
_hasRuntime() {
|
|
37
|
+
return Boolean(this.#miniflare);
|
|
38
|
+
}
|
|
39
|
+
_runtimeType() {
|
|
40
|
+
return "miniflare";
|
|
41
|
+
}
|
|
42
|
+
async _closeRuntime() {
|
|
43
|
+
if (!this.#miniflare) return;
|
|
44
|
+
await this.#miniflare.dispatchFetch("http://localhost/__env_runner_ipc", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { [IPC_HEADER]: "shutdown" }
|
|
47
|
+
}).catch(() => {});
|
|
48
|
+
await this.#miniflare.dispose();
|
|
49
|
+
this.#miniflare = void 0;
|
|
50
|
+
}
|
|
51
|
+
#init() {
|
|
52
|
+
this.#initAsync().catch((error) => {
|
|
53
|
+
console.error("Miniflare runner init error:", error);
|
|
54
|
+
this.close(error);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async #initAsync() {
|
|
58
|
+
const { Miniflare } = await import("miniflare");
|
|
59
|
+
const entryPath = this._data?.entry;
|
|
60
|
+
const options = {
|
|
61
|
+
compatibilityDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
62
|
+
modules: true,
|
|
63
|
+
...this.#miniflareOptions
|
|
64
|
+
};
|
|
65
|
+
options.serviceBindings = {
|
|
66
|
+
...options.serviceBindings || {},
|
|
67
|
+
__ENV_RUNNER_IPC: async (request) => {
|
|
68
|
+
const message = await request.json().catch(() => null);
|
|
69
|
+
if (message !== null) this._handleMessage(message);
|
|
70
|
+
return new Response(null, { status: 204 });
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
if (entryPath && !options.script) {
|
|
74
|
+
options.script = wrapScript(readFileSync(entryPath, "utf8"));
|
|
75
|
+
delete options.scriptPath;
|
|
76
|
+
}
|
|
77
|
+
this.#miniflare = new Miniflare(options);
|
|
78
|
+
await this.#miniflare.ready;
|
|
79
|
+
await this.#miniflare.dispatchFetch("http://localhost/__env_runner_ipc", {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { [IPC_HEADER]: "init" }
|
|
82
|
+
}).catch(() => {});
|
|
83
|
+
this._handleMessage({ address: {
|
|
84
|
+
host: "127.0.0.1",
|
|
85
|
+
port: 0
|
|
86
|
+
} });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Wraps user module code with IPC glue.
|
|
91
|
+
*
|
|
92
|
+
* The user module is expected to `export default { fetch, ipc? }`.
|
|
93
|
+
* The wrapper renames the user's default export, intercepts IPC requests,
|
|
94
|
+
* and bridges the `ipc` hooks via `env.__ENV_RUNNER_IPC` service binding.
|
|
95
|
+
*/
|
|
96
|
+
function wrapScript(userCode) {
|
|
97
|
+
return `${userCode.replace(/export\s+default\s+/, "const __userEntry = ").replace(/export\s*\{([^}]*?\b(\w+)\b\s+as\s+default\b[^}]*)\}/, (_match, _inner, identifier) => `const __userEntry = ${identifier};`)}
|
|
98
|
+
|
|
99
|
+
// --- env-runner IPC glue ---
|
|
100
|
+
let __ipcInitialized = false;
|
|
101
|
+
const __IPC_HEADER = "${IPC_HEADER}";
|
|
102
|
+
|
|
103
|
+
export default {
|
|
104
|
+
async fetch(request, env, ctx) {
|
|
105
|
+
// Handle IPC control messages from runner
|
|
106
|
+
const ipcType = request.headers.get(__IPC_HEADER);
|
|
107
|
+
if (ipcType) {
|
|
108
|
+
if (ipcType === "init") {
|
|
109
|
+
if (!__ipcInitialized && __userEntry.ipc && env.__ENV_RUNNER_IPC) {
|
|
110
|
+
__ipcInitialized = true;
|
|
111
|
+
const sendMessage = (message) => {
|
|
112
|
+
env.__ENV_RUNNER_IPC.fetch("http://ipc/", {
|
|
113
|
+
method: "POST",
|
|
114
|
+
body: JSON.stringify(message),
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
if (__userEntry.ipc.onOpen) {
|
|
118
|
+
await __userEntry.ipc.onOpen({ sendMessage });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return new Response(null, { status: 204 });
|
|
122
|
+
}
|
|
123
|
+
if (ipcType === "message") {
|
|
124
|
+
const message = await request.json();
|
|
125
|
+
if (__userEntry.ipc?.onMessage) {
|
|
126
|
+
__userEntry.ipc.onMessage(message);
|
|
127
|
+
}
|
|
128
|
+
return new Response(null, { status: 204 });
|
|
129
|
+
}
|
|
130
|
+
if (ipcType === "shutdown") {
|
|
131
|
+
if (__userEntry.ipc?.onClose) {
|
|
132
|
+
await __userEntry.ipc.onClose();
|
|
133
|
+
}
|
|
134
|
+
return new Response(null, { status: 204 });
|
|
135
|
+
}
|
|
136
|
+
return new Response(null, { status: 400 });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Delegate to user's fetch handler
|
|
140
|
+
return __userEntry.fetch(request, env, ctx);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
export { MiniflareEnvRunner as t };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
|
+
//#region src/manager.ts
|
|
3
|
+
/**
|
|
4
|
+
* Manages an active `EnvRunner` instance, proxying all calls to it.
|
|
5
|
+
* Supports hot-reload, auto-restart on unexpected exit, and message queueing.
|
|
6
|
+
*/
|
|
7
|
+
var RunnerManager = class {
|
|
8
|
+
_runner;
|
|
9
|
+
_messageQueue = [];
|
|
10
|
+
_messageListeners = /* @__PURE__ */ new Set();
|
|
11
|
+
_closed = false;
|
|
12
|
+
_reloading = false;
|
|
13
|
+
constructor(runner) {
|
|
14
|
+
if (runner) this._attach(runner);
|
|
15
|
+
}
|
|
16
|
+
get ready() {
|
|
17
|
+
return this._runner?.ready ?? false;
|
|
18
|
+
}
|
|
19
|
+
get closed() {
|
|
20
|
+
return this._closed;
|
|
21
|
+
}
|
|
22
|
+
/** Replace the active runner with a new one. Closes the previous runner. */
|
|
23
|
+
async reload(runner) {
|
|
24
|
+
this._reloading = true;
|
|
25
|
+
const prev = this._runner;
|
|
26
|
+
this._detach();
|
|
27
|
+
this._attach(runner);
|
|
28
|
+
this._reloading = false;
|
|
29
|
+
if (prev) await prev.close();
|
|
30
|
+
}
|
|
31
|
+
fetch = async (input, init) => {
|
|
32
|
+
const runner = await this._waitForRunner();
|
|
33
|
+
if (!runner) return new Response("Runner is unavailable", { status: 503 });
|
|
34
|
+
return runner.fetch(input, init);
|
|
35
|
+
};
|
|
36
|
+
upgrade = (context) => {
|
|
37
|
+
this._runner?.upgrade?.(context);
|
|
38
|
+
};
|
|
39
|
+
sendMessage(message) {
|
|
40
|
+
if (!this._runner || !this._runner.ready) {
|
|
41
|
+
this._messageQueue.push(message);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this._runner.sendMessage(message);
|
|
45
|
+
}
|
|
46
|
+
onMessage(listener) {
|
|
47
|
+
this._messageListeners.add(listener);
|
|
48
|
+
this._runner?.onMessage(listener);
|
|
49
|
+
}
|
|
50
|
+
offMessage(listener) {
|
|
51
|
+
this._messageListeners.delete(listener);
|
|
52
|
+
this._runner?.offMessage(listener);
|
|
53
|
+
}
|
|
54
|
+
async close() {
|
|
55
|
+
this._closed = true;
|
|
56
|
+
this._messageQueue.length = 0;
|
|
57
|
+
const runner = this._runner;
|
|
58
|
+
this._detach();
|
|
59
|
+
if (runner) await runner.close();
|
|
60
|
+
}
|
|
61
|
+
onClose;
|
|
62
|
+
onReady;
|
|
63
|
+
_internalListener = (message) => {
|
|
64
|
+
if (message?.address) {
|
|
65
|
+
this._flushQueue();
|
|
66
|
+
this.onReady?.(this, message.address);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
_attach(runner) {
|
|
70
|
+
this._runner = runner;
|
|
71
|
+
runner.onMessage(this._internalListener);
|
|
72
|
+
for (const listener of this._messageListeners) runner.onMessage(listener);
|
|
73
|
+
const originalClose = runner.close.bind(runner);
|
|
74
|
+
runner.close = async () => {
|
|
75
|
+
await originalClose();
|
|
76
|
+
if (this._runner === runner) {
|
|
77
|
+
this._runner = void 0;
|
|
78
|
+
this.onClose?.(this);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
if (runner.ready) this._flushQueue();
|
|
82
|
+
}
|
|
83
|
+
_detach() {
|
|
84
|
+
const runner = this._runner;
|
|
85
|
+
if (!runner) return;
|
|
86
|
+
this._runner = void 0;
|
|
87
|
+
runner.offMessage(this._internalListener);
|
|
88
|
+
for (const listener of this._messageListeners) runner.offMessage(listener);
|
|
89
|
+
}
|
|
90
|
+
_waitForRunner(timeout = 3e3) {
|
|
91
|
+
if (this._runner) return Promise.resolve(this._runner);
|
|
92
|
+
if (!this._reloading) return Promise.resolve(void 0);
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
const start = Date.now();
|
|
95
|
+
const check = () => {
|
|
96
|
+
if (this._runner) return resolve(this._runner);
|
|
97
|
+
if (this._closed || Date.now() - start >= timeout) return resolve(void 0);
|
|
98
|
+
setTimeout(check, 50);
|
|
99
|
+
};
|
|
100
|
+
setTimeout(check, 50);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
_flushQueue() {
|
|
104
|
+
if (!this._runner || this._messageQueue.length === 0) return;
|
|
105
|
+
const queue = [...this._messageQueue];
|
|
106
|
+
this._messageQueue.length = 0;
|
|
107
|
+
for (const msg of queue) this._runner.sendMessage(msg);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/loader.ts
|
|
112
|
+
const loaders = {
|
|
113
|
+
"node-worker": () => import("env-runner/runners/node-worker").then((m) => m.NodeWorkerEnvRunner),
|
|
114
|
+
"node-process": () => import("env-runner/runners/node-process").then((m) => m.NodeProcessEnvRunner),
|
|
115
|
+
"bun-process": () => import("env-runner/runners/bun-process").then((m) => m.BunProcessEnvRunner),
|
|
116
|
+
"deno-process": () => import("env-runner/runners/deno-process").then((m) => m.DenoProcessEnvRunner),
|
|
117
|
+
self: () => import("env-runner/runners/self").then((m) => m.SelfEnvRunner),
|
|
118
|
+
miniflare: () => import("env-runner/runners/miniflare").then((m) => m.MiniflareEnvRunner)
|
|
119
|
+
};
|
|
120
|
+
async function loadRunner(runner, opts) {
|
|
121
|
+
return new (await (loaders[runner]()))(opts);
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/server.ts
|
|
125
|
+
var EnvServer = class extends RunnerManager {
|
|
126
|
+
_opts;
|
|
127
|
+
_watchers = [];
|
|
128
|
+
_reloadTimeout;
|
|
129
|
+
runner = null;
|
|
130
|
+
/** Called when the runner is reloaded due to a file change. */
|
|
131
|
+
onReload;
|
|
132
|
+
constructor(opts) {
|
|
133
|
+
super();
|
|
134
|
+
this._opts = opts;
|
|
135
|
+
}
|
|
136
|
+
/** Start the server by loading and attaching the runner. */
|
|
137
|
+
async start() {
|
|
138
|
+
this.runner = await this._createRunner();
|
|
139
|
+
await this.reload(this.runner);
|
|
140
|
+
if (this._opts.watch) this._startWatching();
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
async close() {
|
|
144
|
+
this._stopWatching();
|
|
145
|
+
await super.close();
|
|
146
|
+
}
|
|
147
|
+
async _createRunner() {
|
|
148
|
+
return loadRunner(this._opts.runner, {
|
|
149
|
+
name: this._opts.name || this._opts.entry,
|
|
150
|
+
hooks: this._opts.hooks,
|
|
151
|
+
data: {
|
|
152
|
+
...this._opts.data,
|
|
153
|
+
entry: this._opts.entry
|
|
154
|
+
},
|
|
155
|
+
execArgv: this._opts.execArgv
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
_startWatching() {
|
|
159
|
+
const paths = [this._opts.entry, ...this._opts.watchPaths || []];
|
|
160
|
+
for (const path of paths) try {
|
|
161
|
+
const watcher = watch(path, { recursive: true }, () => {
|
|
162
|
+
this._scheduleReload();
|
|
163
|
+
});
|
|
164
|
+
this._watchers.push(watcher);
|
|
165
|
+
} catch {}
|
|
166
|
+
}
|
|
167
|
+
_stopWatching() {
|
|
168
|
+
clearTimeout(this._reloadTimeout);
|
|
169
|
+
for (const watcher of this._watchers) watcher.close();
|
|
170
|
+
this._watchers.length = 0;
|
|
171
|
+
}
|
|
172
|
+
_scheduleReload() {
|
|
173
|
+
clearTimeout(this._reloadTimeout);
|
|
174
|
+
this._reloadTimeout = setTimeout(async () => {
|
|
175
|
+
try {
|
|
176
|
+
this.runner = await this._createRunner();
|
|
177
|
+
await this.reload(this.runner);
|
|
178
|
+
this.onReload?.();
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error("Failed to reload runner:", error);
|
|
181
|
+
}
|
|
182
|
+
}, 100);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
//#endregion
|
|
186
|
+
export { loadRunner as n, RunnerManager as r, EnvServer as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/common/worker-utils.ts
|
|
2
|
+
async function resolveEntry(entryPath) {
|
|
3
|
+
const mod = await import(entryPath);
|
|
4
|
+
const entry = mod.default || mod;
|
|
5
|
+
if (typeof entry.fetch !== "function") throw new Error(`[env-runner] Entry module "${entryPath}" must export a \`fetch\` handler (export default { fetch(req) { ... } }).`);
|
|
6
|
+
return entry;
|
|
7
|
+
}
|
|
8
|
+
function parseServerAddress(server) {
|
|
9
|
+
const url = new URL(server.url);
|
|
10
|
+
return {
|
|
11
|
+
host: url.hostname,
|
|
12
|
+
port: Number(url.port)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { resolveEntry as n, parseServerAddress as t };
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { t as EnvServer } from "./_chunks/server.mjs";
|
|
2
|
+
import { serve } from "srvx";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
//#region src/cli.ts
|
|
6
|
+
const { values, positionals } = parseArgs({
|
|
7
|
+
allowPositionals: true,
|
|
8
|
+
options: {
|
|
9
|
+
runner: {
|
|
10
|
+
type: "string",
|
|
11
|
+
default: "node-process"
|
|
12
|
+
},
|
|
13
|
+
port: { type: "string" },
|
|
14
|
+
host: { type: "string" },
|
|
15
|
+
watch: {
|
|
16
|
+
type: "boolean",
|
|
17
|
+
short: "w"
|
|
18
|
+
},
|
|
19
|
+
help: { type: "boolean" }
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const usage = `
|
|
23
|
+
\x1B[1mUsage:\x1B[0m env-runner \x1B[36m<entry>\x1B[0m [options]
|
|
24
|
+
|
|
25
|
+
\x1B[1mOptions:\x1B[0m
|
|
26
|
+
\x1B[33m--runner\x1B[0m <name> Runner to use (node-worker, node-process, bun-process, self, miniflare) \x1B[2m(default: node-process)\x1B[0m
|
|
27
|
+
\x1B[33m--port\x1B[0m <port> Port to listen on \x1B[2m(default: 3000)\x1B[0m
|
|
28
|
+
\x1B[33m--host\x1B[0m <host> Host to bind to \x1B[2m(default: localhost)\x1B[0m
|
|
29
|
+
\x1B[33m-w, --watch\x1B[0m Watch entry file for changes and auto-reload
|
|
30
|
+
\x1B[33m-h, --help\x1B[0m Show this help message
|
|
31
|
+
`.trim();
|
|
32
|
+
const entry = positionals[0];
|
|
33
|
+
if (values.help || !entry) {
|
|
34
|
+
console.log(usage);
|
|
35
|
+
process.exit(values.help ? 0 : 1);
|
|
36
|
+
}
|
|
37
|
+
const runner = values.runner;
|
|
38
|
+
const envServer = new EnvServer({
|
|
39
|
+
runner,
|
|
40
|
+
entry: resolve(entry),
|
|
41
|
+
watch: values.watch
|
|
42
|
+
});
|
|
43
|
+
envServer.onReady = (_runner, address) => {
|
|
44
|
+
console.log(`\x1B[2m➜ Worker ready on ${address?.host}:${address?.port || address?.socketPath}\x1B[0m`);
|
|
45
|
+
};
|
|
46
|
+
envServer.onReload = () => {
|
|
47
|
+
console.log(`\x1B[2m↻ Reloading...\x1B[0m`);
|
|
48
|
+
};
|
|
49
|
+
await envServer.start();
|
|
50
|
+
const server = serve({
|
|
51
|
+
port: values.port,
|
|
52
|
+
hostname: values.host,
|
|
53
|
+
gracefulShutdown: false,
|
|
54
|
+
fetch: (request) => envServer.fetch(request)
|
|
55
|
+
});
|
|
56
|
+
await server.ready();
|
|
57
|
+
server.node?.server?.on("upgrade", (req, socket, head) => {
|
|
58
|
+
envServer.upgrade({ node: {
|
|
59
|
+
req,
|
|
60
|
+
socket,
|
|
61
|
+
head
|
|
62
|
+
} });
|
|
63
|
+
});
|
|
64
|
+
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, async () => {
|
|
65
|
+
console.log(`\n\x1B[2mShutting down...\x1B[0m`);
|
|
66
|
+
await envServer.close();
|
|
67
|
+
await server.close();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
});
|
|
70
|
+
//#endregion
|
|
71
|
+
export {};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { a as NodeUpgradeContext, c as UpgradeContext, d as WorkerHooks, i as FetchHandler, l as UpgradeHandler, n as EnvRunnerData, o as RunnerMessageListener, r as EnvRunner, s as RunnerRPCHooks, t as BaseEnvRunner, u as WorkerAddress } from "./_chunks/base-runner.mjs";
|
|
2
|
+
import { t as DenoProcessEnvRunner } from "./_chunks/runner.mjs";
|
|
3
|
+
import { n as MiniflareEnvRunnerOptions, t as MiniflareEnvRunner } from "./_chunks/runner2.mjs";
|
|
4
|
+
import { ServerOptions } from "srvx";
|
|
5
|
+
|
|
6
|
+
//#region src/manager.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Manages an active `EnvRunner` instance, proxying all calls to it.
|
|
9
|
+
* Supports hot-reload, auto-restart on unexpected exit, and message queueing.
|
|
10
|
+
*/
|
|
11
|
+
declare class RunnerManager implements EnvRunner {
|
|
12
|
+
private _runner;
|
|
13
|
+
private _messageQueue;
|
|
14
|
+
private _messageListeners;
|
|
15
|
+
private _closed;
|
|
16
|
+
private _reloading;
|
|
17
|
+
constructor(runner?: EnvRunner);
|
|
18
|
+
get ready(): boolean;
|
|
19
|
+
get closed(): boolean;
|
|
20
|
+
/** Replace the active runner with a new one. Closes the previous runner. */
|
|
21
|
+
reload(runner: EnvRunner): Promise<void>;
|
|
22
|
+
fetch: FetchHandler;
|
|
23
|
+
upgrade: UpgradeHandler;
|
|
24
|
+
sendMessage(message: unknown): void;
|
|
25
|
+
onMessage(listener: RunnerMessageListener): void;
|
|
26
|
+
offMessage(listener: RunnerMessageListener): void;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
onClose?: (runner: EnvRunner, cause?: unknown) => void;
|
|
29
|
+
onReady?: (runner: EnvRunner, address?: WorkerAddress) => void;
|
|
30
|
+
private _internalListener;
|
|
31
|
+
private _attach;
|
|
32
|
+
private _detach;
|
|
33
|
+
private _waitForRunner;
|
|
34
|
+
private _flushQueue;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/loader.d.ts
|
|
38
|
+
type RunnerName = "node-worker" | "node-process" | "bun-process" | "deno-process" | "self" | "miniflare";
|
|
39
|
+
interface LoadRunnerOptions {
|
|
40
|
+
name: string;
|
|
41
|
+
workerEntry?: string;
|
|
42
|
+
hooks?: WorkerHooks;
|
|
43
|
+
data?: EnvRunnerData;
|
|
44
|
+
execArgv?: string[];
|
|
45
|
+
}
|
|
46
|
+
declare function loadRunner(runner: RunnerName, opts: LoadRunnerOptions): Promise<EnvRunner>;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/server.d.ts
|
|
49
|
+
interface EnvServerOptions {
|
|
50
|
+
/** Runner implementation to use. */
|
|
51
|
+
runner: RunnerName;
|
|
52
|
+
/** Path to the user entry module (passed as `data.entry`). */
|
|
53
|
+
entry: string;
|
|
54
|
+
/** Runner instance name. */
|
|
55
|
+
name?: string;
|
|
56
|
+
/** Lifecycle hooks. */
|
|
57
|
+
hooks?: WorkerHooks;
|
|
58
|
+
/** Additional data passed to the runner. */
|
|
59
|
+
data?: Record<string, unknown>;
|
|
60
|
+
/** Custom exec arguments (e.g. `--inspect`). */
|
|
61
|
+
execArgv?: string[];
|
|
62
|
+
/** Enable watch mode to auto-reload on entry file changes. */
|
|
63
|
+
watch?: boolean;
|
|
64
|
+
/** Additional paths to watch (directories or files). */
|
|
65
|
+
watchPaths?: string[];
|
|
66
|
+
}
|
|
67
|
+
declare class EnvServer extends RunnerManager {
|
|
68
|
+
private _opts;
|
|
69
|
+
private _watchers;
|
|
70
|
+
private _reloadTimeout;
|
|
71
|
+
runner: Awaited<ReturnType<typeof loadRunner>> | null;
|
|
72
|
+
/** Called when the runner is reloaded due to a file change. */
|
|
73
|
+
onReload?: () => void;
|
|
74
|
+
constructor(opts: EnvServerOptions);
|
|
75
|
+
/** Start the server by loading and attaching the runner. */
|
|
76
|
+
start(): Promise<this>;
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
private _createRunner;
|
|
79
|
+
private _startWatching;
|
|
80
|
+
private _stopWatching;
|
|
81
|
+
private _scheduleReload;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/common/worker-utils.d.ts
|
|
85
|
+
interface AppEntryIPCContext {
|
|
86
|
+
sendMessage: (message: unknown) => void;
|
|
87
|
+
}
|
|
88
|
+
interface AppEntryIPC {
|
|
89
|
+
onOpen?: (ctx: AppEntryIPCContext) => void | Promise<void>;
|
|
90
|
+
onMessage?: (message: unknown) => void | Promise<void>;
|
|
91
|
+
onClose?: () => void | Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
interface AppEntry {
|
|
94
|
+
fetch: ServerOptions["fetch"];
|
|
95
|
+
middleware?: ServerOptions["middleware"];
|
|
96
|
+
plugins?: ServerOptions["plugins"];
|
|
97
|
+
ipc?: AppEntryIPC;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { type AppEntry, type AppEntryIPC, type AppEntryIPCContext, BaseEnvRunner, DenoProcessEnvRunner, type EnvRunnerData as DenoProcessEnvRunnerData, type EnvRunnerData, type EnvRunner, EnvServer, type EnvServerOptions, type FetchHandler, type LoadRunnerOptions, MiniflareEnvRunner, type MiniflareEnvRunnerOptions, type NodeUpgradeContext, RunnerManager, type RunnerMessageListener, type RunnerName, type RunnerRPCHooks, type UpgradeContext, type UpgradeHandler, type WorkerAddress, type WorkerHooks, loadRunner };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "./_chunks/libs/std-env.mjs";
|
|
2
|
+
import { t as BaseEnvRunner } from "./_chunks/base-runner.mjs";
|
|
3
|
+
import { n as loadRunner, r as RunnerManager, t as EnvServer } from "./_chunks/server.mjs";
|
|
4
|
+
import { t as DenoProcessEnvRunner } from "./_chunks/runner.mjs";
|
|
5
|
+
import { t as MiniflareEnvRunner } from "./_chunks/runner2.mjs";
|
|
6
|
+
export { BaseEnvRunner, DenoProcessEnvRunner, EnvServer, MiniflareEnvRunner, RunnerManager, loadRunner };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { d as WorkerHooks, n as EnvRunnerData, t as BaseEnvRunner } from "../../_chunks/base-runner.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/runners/bun-process/runner.d.ts
|
|
4
|
+
declare class BunProcessEnvRunner extends BaseEnvRunner {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(opts: {
|
|
7
|
+
name: string;
|
|
8
|
+
workerEntry?: string;
|
|
9
|
+
hooks?: WorkerHooks;
|
|
10
|
+
data?: EnvRunnerData;
|
|
11
|
+
execArgv?: string[];
|
|
12
|
+
});
|
|
13
|
+
sendMessage(message: unknown): void;
|
|
14
|
+
protected _hasRuntime(): boolean;
|
|
15
|
+
protected _runtimeType(): string;
|
|
16
|
+
protected _closeRuntime(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { BunProcessEnvRunner, type EnvRunnerData as BunProcessEnvRunnerData };
|