everything-dev 0.3.1 → 0.3.3

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.
@@ -1,11 +1,8 @@
1
- import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
1
+ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { Effect } from "effect";
5
- import {
6
- createSnapshotWithPlatform,
7
- runSilent,
8
- } from "../resource-monitor";
5
+ import { createSnapshotWithPlatform, runSilent } from "../resource-monitor";
9
6
  import { ServerNotReady, ServerStartFailed } from "./errors";
10
7
  import type { ServerHandle, ServerOrchestrator } from "./types";
11
8
 
@@ -13,255 +10,259 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
13
10
  const CLI_DIR = resolve(__dirname, "../../..");
14
11
 
15
12
  const sleep = (ms: number): Promise<void> =>
16
- new Promise((resolve) => setTimeout(resolve, ms));
13
+ new Promise((resolve) => setTimeout(resolve, ms));
17
14
 
18
15
  interface SpawnOptions {
19
- port?: number;
20
- account?: string;
21
- domain?: string;
22
- interactive?: boolean;
16
+ port?: number;
17
+ account?: string;
18
+ domain?: string;
19
+ interactive?: boolean;
23
20
  }
24
21
 
25
22
  const createServerHandle = (
26
- proc: ReturnType<typeof spawn>,
27
- name: string,
28
- port: number
23
+ proc: ReturnType<typeof spawn>,
24
+ name: string,
25
+ port: number,
29
26
  ): ServerHandle => {
30
- proc.stdout?.on("data", () => {});
31
- proc.stderr?.on("data", () => {});
32
-
33
- let exitHandled = false;
34
- let exitCode: number | null = null;
35
- const exitPromise = new Promise<number | null>((resolve) => {
36
- (proc as unknown as NodeJS.EventEmitter).on("exit", (code: number | null) => {
37
- exitHandled = true;
38
- exitCode = code;
39
- resolve(code);
40
- });
41
- });
42
-
43
- return {
44
- pid: proc.pid!,
45
- port,
46
- name,
47
- kill: async () => {
48
- proc.kill("SIGTERM");
49
- const killPromise = new Promise<void>((res) => {
50
- const timeout = setTimeout(() => {
51
- proc.kill("SIGKILL");
52
- res();
53
- }, 5000);
54
- if (exitHandled) {
55
- clearTimeout(timeout);
56
- res();
57
- } else {
58
- exitPromise.then(() => {
59
- clearTimeout(timeout);
60
- res();
61
- });
62
- }
63
- });
64
- await killPromise;
65
- },
66
- waitForExit: (timeoutMs = 10000): Promise<number | null> =>
67
- new Promise((res) => {
68
- const timeout = setTimeout(() => res(null), timeoutMs);
69
- if (exitHandled) {
70
- clearTimeout(timeout);
71
- res(exitCode);
72
- } else {
73
- exitPromise.then((code) => {
74
- clearTimeout(timeout);
75
- res(code);
76
- });
77
- }
78
- }),
79
- };
27
+ proc.stdout?.on("data", () => {});
28
+ proc.stderr?.on("data", () => {});
29
+
30
+ let exitHandled = false;
31
+ let exitCode: number | null = null;
32
+ const exitPromise = new Promise<number | null>((resolve) => {
33
+ (proc as unknown as NodeJS.EventEmitter).on(
34
+ "exit",
35
+ (code: number | null) => {
36
+ exitHandled = true;
37
+ exitCode = code;
38
+ resolve(code);
39
+ },
40
+ );
41
+ });
42
+
43
+ return {
44
+ pid: proc.pid!,
45
+ port,
46
+ name,
47
+ kill: async () => {
48
+ proc.kill("SIGTERM");
49
+ const killPromise = new Promise<void>((res) => {
50
+ const timeout = setTimeout(() => {
51
+ proc.kill("SIGKILL");
52
+ res();
53
+ }, 5000);
54
+ if (exitHandled) {
55
+ clearTimeout(timeout);
56
+ res();
57
+ } else {
58
+ exitPromise.then(() => {
59
+ clearTimeout(timeout);
60
+ res();
61
+ });
62
+ }
63
+ });
64
+ await killPromise;
65
+ },
66
+ waitForExit: (timeoutMs = 10000): Promise<number | null> =>
67
+ new Promise((res) => {
68
+ const timeout = setTimeout(() => res(null), timeoutMs);
69
+ if (exitHandled) {
70
+ clearTimeout(timeout);
71
+ res(exitCode);
72
+ } else {
73
+ exitPromise.then((code) => {
74
+ clearTimeout(timeout);
75
+ res(code);
76
+ });
77
+ }
78
+ }),
79
+ };
80
80
  };
81
81
 
82
82
  const spawnBosStart = (options: SpawnOptions = {}): ServerHandle => {
83
- const args = [
84
- "run",
85
- "src/cli.ts",
86
- "start",
87
- "--account", options.account || "every.near",
88
- "--domain", options.domain || "everything.dev",
89
- ];
90
-
91
- if (!options.interactive) {
92
- args.push("--no-interactive");
93
- }
94
-
95
- if (options.port) {
96
- args.push("--port", String(options.port));
97
- }
98
-
99
- const proc = spawn("bun", args, {
100
- cwd: CLI_DIR,
101
- stdio: ["ignore", "pipe", "pipe"],
102
- detached: false,
103
- env: { ...process.env, NODE_ENV: "production" },
104
- });
105
-
106
- return createServerHandle(proc, "bos-start", options.port || 3000);
83
+ const args = [
84
+ "src/cli.ts",
85
+ "start",
86
+ "--account",
87
+ options.account || "every.near",
88
+ "--domain",
89
+ options.domain || "everything.dev",
90
+ ];
91
+
92
+ if (!options.interactive) {
93
+ args.push("--no-interactive");
94
+ }
95
+
96
+ if (options.port) {
97
+ args.push("--port", String(options.port));
98
+ }
99
+
100
+ const proc = spawn("bun", args, {
101
+ cwd: CLI_DIR,
102
+ stdio: ["ignore", "pipe", "pipe"],
103
+ detached: false,
104
+ env: { ...process.env, NODE_ENV: "production" },
105
+ });
106
+
107
+ return createServerHandle(proc, "bos-start", options.port || 3000);
107
108
  };
108
109
 
109
110
  const spawnBosDev = (options: SpawnOptions = {}): ServerHandle => {
110
- const args = ["run", "src/cli.ts", "dev"];
111
+ const args = ["src/cli.ts", "dev"];
111
112
 
112
- if (!options.interactive) {
113
- args.push("--no-interactive");
114
- }
113
+ if (!options.interactive) {
114
+ args.push("--no-interactive");
115
+ }
115
116
 
116
- if (options.port) {
117
- args.push("--port", String(options.port));
118
- }
117
+ if (options.port) {
118
+ args.push("--port", String(options.port));
119
+ }
119
120
 
120
- const proc = spawn("bun", args, {
121
- cwd: CLI_DIR,
122
- stdio: ["ignore", "pipe", "pipe"],
123
- detached: false,
124
- env: { ...process.env, NODE_ENV: "development" },
125
- });
121
+ const proc = spawn("bun", args, {
122
+ cwd: CLI_DIR,
123
+ stdio: ["ignore", "pipe", "pipe"],
124
+ detached: false,
125
+ env: { ...process.env, NODE_ENV: "development" },
126
+ });
126
127
 
127
- return createServerHandle(proc, "bos-dev", options.port || 3000);
128
+ return createServerHandle(proc, "bos-dev", options.port || 3000);
128
129
  };
129
130
 
130
131
  const waitForPortBound = async (
131
- port: number,
132
- timeoutMs = 60000
132
+ port: number,
133
+ timeoutMs = 60000,
133
134
  ): Promise<boolean> => {
134
- const start = Date.now();
135
-
136
- while (Date.now() - start < timeoutMs) {
137
- try {
138
- const snapshot = await runSilent(
139
- createSnapshotWithPlatform({ ports: [port] })
140
- );
141
- if (snapshot.ports[port]?.state === "LISTEN") {
142
- return true;
143
- }
144
- } catch {
145
- // ignore errors during polling
146
- }
147
- await sleep(500);
148
- }
149
-
150
- return false;
135
+ const start = Date.now();
136
+
137
+ while (Date.now() - start < timeoutMs) {
138
+ try {
139
+ const snapshot = await runSilent(
140
+ createSnapshotWithPlatform({ ports: [port] }),
141
+ );
142
+ if (snapshot.ports[port]?.state === "LISTEN") {
143
+ return true;
144
+ }
145
+ } catch {
146
+ // ignore errors during polling
147
+ }
148
+ await sleep(500);
149
+ }
150
+
151
+ return false;
151
152
  };
152
153
 
153
154
  const waitForPortFree = async (
154
- port: number,
155
- timeoutMs = 15000
155
+ port: number,
156
+ timeoutMs = 15000,
156
157
  ): Promise<boolean> => {
157
- const start = Date.now();
158
-
159
- while (Date.now() - start < timeoutMs) {
160
- try {
161
- const snapshot = await runSilent(
162
- createSnapshotWithPlatform({ ports: [port] })
163
- );
164
- if (snapshot.ports[port]?.state === "FREE") {
165
- return true;
166
- }
167
- } catch {
168
- // ignore errors during polling
169
- }
170
- await sleep(200);
171
- }
172
-
173
- return false;
158
+ const start = Date.now();
159
+
160
+ while (Date.now() - start < timeoutMs) {
161
+ try {
162
+ const snapshot = await runSilent(
163
+ createSnapshotWithPlatform({ ports: [port] }),
164
+ );
165
+ if (snapshot.ports[port]?.state === "FREE") {
166
+ return true;
167
+ }
168
+ } catch {
169
+ // ignore errors during polling
170
+ }
171
+ await sleep(200);
172
+ }
173
+
174
+ return false;
174
175
  };
175
176
 
176
177
  export const startServers = (
177
- mode: "start" | "dev" = "start",
178
- options: SpawnOptions = {}
178
+ mode: "start" | "dev" = "start",
179
+ options: SpawnOptions = {},
179
180
  ): Effect.Effect<ServerOrchestrator, ServerStartFailed | ServerNotReady> =>
180
- Effect.gen(function* () {
181
- const port = options.port || 3000;
182
-
183
- yield* Effect.logInfo(`Starting BOS in ${mode} mode on port ${port}`);
184
-
185
- const handle = mode === "dev"
186
- ? spawnBosDev(options)
187
- : spawnBosStart(options);
188
-
189
- const ready = yield* Effect.tryPromise({
190
- try: () => waitForPortBound(port, 90000),
191
- catch: (e) => new ServerStartFailed({
192
- server: handle.name,
193
- port,
194
- reason: String(e),
195
- }),
196
- });
197
-
198
- if (!ready) {
199
- yield* Effect.promise(() => handle.kill());
200
- return yield* Effect.fail(
201
- new ServerNotReady({
202
- servers: [handle.name],
203
- timeoutMs: 90000,
204
- })
205
- );
206
- }
207
-
208
- yield* Effect.logInfo(`Server ready on port ${port}`);
209
-
210
- const orchestrator: ServerOrchestrator = {
211
- handles: [handle],
212
- ports: [port],
213
- shutdown: async () => {
214
- console.log("Shutting down servers");
215
- await handle.kill();
216
- await waitForPortFree(port, 15000);
217
- console.log("Servers stopped");
218
- },
219
- waitForReady: async () => {
220
- return waitForPortBound(port, 30000);
221
- },
222
- };
223
-
224
- return orchestrator;
225
- });
181
+ Effect.gen(function* () {
182
+ const port = options.port || 3000;
183
+
184
+ yield* Effect.logInfo(`Starting BOS in ${mode} mode on port ${port}`);
185
+
186
+ const handle =
187
+ mode === "dev" ? spawnBosDev(options) : spawnBosStart(options);
188
+
189
+ const ready = yield* Effect.tryPromise({
190
+ try: () => waitForPortBound(port, 90000),
191
+ catch: (e) =>
192
+ new ServerStartFailed({
193
+ server: handle.name,
194
+ port,
195
+ reason: String(e),
196
+ }),
197
+ });
198
+
199
+ if (!ready) {
200
+ yield* Effect.promise(() => handle.kill());
201
+ return yield* Effect.fail(
202
+ new ServerNotReady({
203
+ servers: [handle.name],
204
+ timeoutMs: 90000,
205
+ }),
206
+ );
207
+ }
208
+
209
+ yield* Effect.logInfo(`Server ready on port ${port}`);
210
+
211
+ const orchestrator: ServerOrchestrator = {
212
+ handles: [handle],
213
+ ports: [port],
214
+ shutdown: async () => {
215
+ console.log("Shutting down servers");
216
+ await handle.kill();
217
+ await waitForPortFree(port, 15000);
218
+ console.log("Servers stopped");
219
+ },
220
+ waitForReady: async () => {
221
+ return waitForPortBound(port, 30000);
222
+ },
223
+ };
224
+
225
+ return orchestrator;
226
+ });
226
227
 
227
228
  export const shutdownServers = (
228
- orchestrator: ServerOrchestrator
229
+ orchestrator: ServerOrchestrator,
229
230
  ): Effect.Effect<void> =>
230
- Effect.gen(function* () {
231
- yield* Effect.logInfo(`Shutting down ${orchestrator.handles.length} server(s)`);
232
-
233
- for (const handle of orchestrator.handles) {
234
- yield* Effect.logDebug(`Killing ${handle.name} (PID ${handle.pid})`);
235
- yield* Effect.promise(() => handle.kill());
236
- }
237
-
238
- for (const port of orchestrator.ports) {
239
- yield* Effect.logDebug(`Waiting for port ${port} to be free`);
240
- const freed = yield* Effect.promise(() => waitForPortFree(port, 15000));
241
- if (!freed) {
242
- yield* Effect.logWarning(`Port ${port} still bound after shutdown`);
243
- }
244
- }
245
-
246
- yield* Effect.logInfo("All servers stopped");
247
- });
248
-
249
- export const checkPortsAvailable = (
250
- ports: number[]
251
- ): Effect.Effect<boolean> =>
252
- Effect.gen(function* () {
253
- const snapshot = yield* Effect.promise(() =>
254
- runSilent(createSnapshotWithPlatform({ ports }))
255
- );
256
-
257
- for (const port of ports) {
258
- if (snapshot.ports[port]?.state !== "FREE") {
259
- yield* Effect.logWarning(`Port ${port} is already in use`);
260
- return false;
261
- }
262
- }
263
-
264
- return true;
265
- });
231
+ Effect.gen(function* () {
232
+ yield* Effect.logInfo(
233
+ `Shutting down ${orchestrator.handles.length} server(s)`,
234
+ );
235
+
236
+ for (const handle of orchestrator.handles) {
237
+ yield* Effect.logDebug(`Killing ${handle.name} (PID ${handle.pid})`);
238
+ yield* Effect.promise(() => handle.kill());
239
+ }
240
+
241
+ for (const port of orchestrator.ports) {
242
+ yield* Effect.logDebug(`Waiting for port ${port} to be free`);
243
+ const freed = yield* Effect.promise(() => waitForPortFree(port, 15000));
244
+ if (!freed) {
245
+ yield* Effect.logWarning(`Port ${port} still bound after shutdown`);
246
+ }
247
+ }
248
+
249
+ yield* Effect.logInfo("All servers stopped");
250
+ });
251
+
252
+ export const checkPortsAvailable = (ports: number[]): Effect.Effect<boolean> =>
253
+ Effect.gen(function* () {
254
+ const snapshot = yield* Effect.promise(() =>
255
+ runSilent(createSnapshotWithPlatform({ ports })),
256
+ );
257
+
258
+ for (const port of ports) {
259
+ if (snapshot.ports[port]?.state !== "FREE") {
260
+ yield* Effect.logWarning(`Port ${port} is already in use`);
261
+ return false;
262
+ }
263
+ }
264
+
265
+ return true;
266
+ });
266
267
 
267
268
  export { waitForPortBound, waitForPortFree };