@procedures/server 0.41.1 → 0.42.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/CHANGELOG.md +22 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/host.d.ts +11 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +31 -2
- package/dist/host.js.map +1 -1
- package/dist/idle.d.ts +56 -0
- package/dist/idle.d.ts.map +1 -0
- package/dist/idle.js +90 -0
- package/dist/idle.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/procedures/server.start.d.ts +39 -0
- package/dist/procedures/server.start.d.ts.map +1 -1
- package/dist/procedures/server.start.js +76 -38
- package/dist/procedures/server.start.js.map +1 -1
- package/package.json +6 -6
- package/src/host.ts +40 -2
- package/src/idle.ts +119 -0
- package/src/index.ts +3 -1
- package/src/procedures/server.start.ts +113 -46
package/src/host.ts
CHANGED
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
|
|
22
22
|
import { daemon, localDev, service, type Lifecycle, type Profile, type RunOptions, type ServiceBuilder } from "@mark1russell7/service";
|
|
23
23
|
import { probeServer, type ProbeKind } from "@mark1russell7/service/http";
|
|
24
|
-
import { seconds, type Duration } from "@procedures/client";
|
|
24
|
+
import { hostPolicy, realClock, seconds, type Duration, type HostPolicy, type Interceptor } from "@procedures/client";
|
|
25
25
|
import { LOCAL_LISTENER_KNOBS, createProcedureHost, type ProcedureHost, type ProcedureHostOptions } from "@procedures/peer";
|
|
26
26
|
import { lockfileHandle, type LockfileData } from "@procedures/peer/lockfile";
|
|
27
|
+
import { idleInterceptor, idleTimer, idleTimes, type HostIdle } from "./idle.js";
|
|
27
28
|
|
|
28
29
|
/** The mode of a run: in the foreground of a terminal, or as the background host of `mark`. */
|
|
29
30
|
export type HostRunMode = "foreground" | "background";
|
|
@@ -90,6 +91,16 @@ export interface ProcedureHostRunConfig {
|
|
|
90
91
|
* The return value has no effect.
|
|
91
92
|
*/
|
|
92
93
|
readonly declare?: ((svc: ServiceBuilder, host: ProcedureHost) => void) | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* The idle stop (CLI-27): the host stops by itself after this time with no call. Without it, the
|
|
96
|
+
* host stops only at a signal or a stop request, as before.
|
|
97
|
+
*
|
|
98
|
+
* - Each call resets the timer at its start and at its end. An open stream or subscription is a
|
|
99
|
+
* call in flight, and the host does not stop while it is open.
|
|
100
|
+
* - The stop is `life.shutdown()` with the exit code 0. Thus the stop in stages of `run()` applies,
|
|
101
|
+
* and the lockfile goes at the end.
|
|
102
|
+
*/
|
|
103
|
+
readonly idle?: HostIdle | undefined;
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
/** A host that runs under `run()`: the host, the service of the process and its lifecycle. */
|
|
@@ -114,8 +125,25 @@ export async function runProcedureHost(config: ProcedureHostRunConfig): Promise<
|
|
|
114
125
|
const clock = config.run?.clock;
|
|
115
126
|
// A probe server of its own: the listeners of the host then answer no probes, unless the host options say so (D5)
|
|
116
127
|
const dedicated = config.probes;
|
|
128
|
+
// The idle stop (CLI-27): an interceptor of the policy tells the timer about each call. The timer
|
|
129
|
+
// starts after the startup, and it asks the lifecycle for the stop.
|
|
130
|
+
let life: Lifecycle | undefined;
|
|
131
|
+
const idle = config.idle
|
|
132
|
+
? idleTimer({
|
|
133
|
+
...idleTimes(config.idle),
|
|
134
|
+
clock: clock ?? realClock,
|
|
135
|
+
inFlight: () => life?.inFlight() ?? 0,
|
|
136
|
+
stop: (cause) => {
|
|
137
|
+
if (life?.state !== "running") return;
|
|
138
|
+
config.log?.(`\nIdle: ${cause}.`);
|
|
139
|
+
void life.shutdown(cause, { code: 0 });
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
: undefined;
|
|
143
|
+
const policy = idle ? withInterceptor(config.host.policy ?? hostPolicy(), idleInterceptor(idle)) : config.host.policy;
|
|
117
144
|
const host = createProcedureHost({
|
|
118
145
|
...config.host,
|
|
146
|
+
...(policy ? { policy } : {}),
|
|
119
147
|
...(clock ? { clock } : {}),
|
|
120
148
|
...(dedicated && config.host.probes === undefined ? { probes: false } : {}),
|
|
121
149
|
});
|
|
@@ -145,7 +173,12 @@ export async function runProcedureHost(config: ProcedureHostRunConfig): Promise<
|
|
|
145
173
|
if (config.lockfile) svc.dependency("lockfile", { system: null, knobs: {}, operations: {} }, lockfileHandle(config.lockfile));
|
|
146
174
|
const log = config.log;
|
|
147
175
|
if (log && profile.phases?.some((phase) => phase.name === "stop intake")) svc.onPhase("stop intake", () => log("\nShutting down..."));
|
|
148
|
-
|
|
176
|
+
life = await svc.run(config.run);
|
|
177
|
+
if (idle) {
|
|
178
|
+
// The timer stops with the lifecycle, also after a stop for another cause
|
|
179
|
+
void life.done.then(() => idle.cancel());
|
|
180
|
+
if (life.state === "running") idle.start();
|
|
181
|
+
}
|
|
149
182
|
const probeBase = (): string[] => {
|
|
150
183
|
const address = probes?.server.address();
|
|
151
184
|
if (typeof address !== "object" || address === null) return [];
|
|
@@ -155,3 +188,8 @@ export async function runProcedureHost(config: ProcedureHostRunConfig): Promise<
|
|
|
155
188
|
};
|
|
156
189
|
return { host, service: svc, life, probes: () => (probes ? probeBase() : host.info.probes()) };
|
|
157
190
|
}
|
|
191
|
+
|
|
192
|
+
/** This function gives a policy with one more interceptor, after the interceptors of the policy. */
|
|
193
|
+
function withInterceptor(policy: HostPolicy, interceptor: Interceptor): HostPolicy {
|
|
194
|
+
return { ...policy, interceptors: [...policy.interceptors, interceptor] };
|
|
195
|
+
}
|
package/src/idle.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The idle stop of a procedures host (CLI-27).
|
|
3
|
+
*
|
|
4
|
+
* A host with the option `idle` of `runProcedureHost()` stops by itself after a time with no call.
|
|
5
|
+
* The background host that `mark` starts after a cold command uses it (`MARK_AUTO_WARM`).
|
|
6
|
+
*
|
|
7
|
+
* - `run()` of `@mark1russell7/service` has no idle trigger. The host uses the stop request of the
|
|
8
|
+
* lifecycle, `life.shutdown()`, as an exit signal does. Thus the runner keeps the stop in stages
|
|
9
|
+
* and the exit, and this file has no signal handler, no listener and no `process.exit`.
|
|
10
|
+
* - An interceptor of the host policy resets the timer at the start and at the end of each call.
|
|
11
|
+
* - When the timer fires and a call is in flight (`life.inFlight()`), the host does not stop. An
|
|
12
|
+
* open stream or subscription is a call in flight until its last item (decision D4). The timer
|
|
13
|
+
* then reads the count again at each poll, and the full time starts again after the last call ends.
|
|
14
|
+
* - The timer uses the clock of the run, so a test gives a fake clock.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { toMs, type Clock, type Duration, type Interceptor } from "@procedures/client";
|
|
18
|
+
|
|
19
|
+
/** The default time between two reads of the calls in flight while a call is open: 5 s. */
|
|
20
|
+
export const IDLE_POLL_MS = 5_000;
|
|
21
|
+
|
|
22
|
+
/** The option `idle` of `runProcedureHost()`. */
|
|
23
|
+
export interface HostIdle {
|
|
24
|
+
/** The time with no call, after which the host stops. */
|
|
25
|
+
readonly after: Duration;
|
|
26
|
+
/** The time between two reads of the calls in flight while a call is open. The default is 5 s, or `after` when it is shorter. */
|
|
27
|
+
readonly poll?: Duration | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The options of `idleTimer()`. */
|
|
31
|
+
export interface IdleTimerOptions {
|
|
32
|
+
/** The time with no call, in milliseconds. */
|
|
33
|
+
readonly afterMs: number;
|
|
34
|
+
/** The time between two reads of the calls in flight while a call is open, in milliseconds. */
|
|
35
|
+
readonly pollMs: number;
|
|
36
|
+
readonly clock: Clock;
|
|
37
|
+
/** This function gives the count of the calls in flight. */
|
|
38
|
+
readonly inFlight: () => number;
|
|
39
|
+
/** This function stops the host. The timer calls it one time, with the cause of the stop. */
|
|
40
|
+
readonly stop: (cause: string) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The timer of the idle stop. */
|
|
44
|
+
export interface IdleTimer {
|
|
45
|
+
/** This method starts the timer. Before the start, `touch()` has no effect. */
|
|
46
|
+
start(): void;
|
|
47
|
+
/** This method tells the timer about a call: the full time starts again. */
|
|
48
|
+
touch(): void;
|
|
49
|
+
/** This method stops the timer for good. */
|
|
50
|
+
cancel(): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** This function gives the timer of an idle stop. */
|
|
54
|
+
export function idleTimer(options: IdleTimerOptions): IdleTimer {
|
|
55
|
+
const { afterMs, pollMs, clock } = options;
|
|
56
|
+
let started = false;
|
|
57
|
+
let ended = false;
|
|
58
|
+
// True when the last fire found a call in flight: the full time starts after that call ends
|
|
59
|
+
let open = false;
|
|
60
|
+
let cancelTimer: (() => void) | undefined;
|
|
61
|
+
const arm = (delay: number): void => {
|
|
62
|
+
cancelTimer?.();
|
|
63
|
+
cancelTimer = clock.after(delay, fire);
|
|
64
|
+
};
|
|
65
|
+
const fire = (): void => {
|
|
66
|
+
cancelTimer = undefined;
|
|
67
|
+
if (ended) return;
|
|
68
|
+
if (options.inFlight() > 0) {
|
|
69
|
+
open = true;
|
|
70
|
+
arm(pollMs);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (open) {
|
|
74
|
+
open = false;
|
|
75
|
+
arm(afterMs);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
ended = true;
|
|
79
|
+
options.stop(`the host had no call for ${afterMs} ms`);
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
start() {
|
|
83
|
+
if (started || ended) return;
|
|
84
|
+
started = true;
|
|
85
|
+
arm(afterMs);
|
|
86
|
+
},
|
|
87
|
+
touch() {
|
|
88
|
+
if (!started || ended) return;
|
|
89
|
+
open = false;
|
|
90
|
+
arm(afterMs);
|
|
91
|
+
},
|
|
92
|
+
cancel() {
|
|
93
|
+
ended = true;
|
|
94
|
+
cancelTimer?.();
|
|
95
|
+
cancelTimer = undefined;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The timer of the option `idle` of a host, in milliseconds. */
|
|
101
|
+
export function idleTimes(idle: HostIdle): { afterMs: number; pollMs: number } {
|
|
102
|
+
const afterMs = toMs(idle.after, "idle.after");
|
|
103
|
+
if (!Number.isFinite(afterMs) || afterMs <= 0) throw new RangeError(`idle.after must be more than 0 ms, got ${afterMs} ms`);
|
|
104
|
+
const pollMs = idle.poll === undefined ? Math.min(IDLE_POLL_MS, afterMs) : toMs(idle.poll, "idle.poll");
|
|
105
|
+
if (!Number.isFinite(pollMs) || pollMs <= 0) throw new RangeError(`idle.poll must be more than 0 ms, got ${pollMs} ms`);
|
|
106
|
+
return { afterMs, pollMs };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** This function gives the interceptor that tells a timer about each call: at its start and at its end. */
|
|
110
|
+
export function idleInterceptor(timer: Pick<IdleTimer, "touch">): Interceptor {
|
|
111
|
+
return async (_invocation, next) => {
|
|
112
|
+
timer.touch();
|
|
113
|
+
try {
|
|
114
|
+
return await next();
|
|
115
|
+
} finally {
|
|
116
|
+
timer.touch();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { parseConfig, parseTopicPatterns } from "./config.js";
|
|
|
10
10
|
export { runServer, startServer, loadProcedureModule, type RunningServer, type ServerRunOptions } from "./run.js";
|
|
11
11
|
// The runner entry of the procedures host (CLI-8): the only entry that starts run()
|
|
12
12
|
export { runProcedureHost, HOST_PROFILES, type ProcedureHostRunConfig, type RunningHost, type HostRunMode } from "./host.js";
|
|
13
|
+
// The idle stop of a host (CLI-27): the option `idle` of runProcedureHost()
|
|
14
|
+
export { IDLE_POLL_MS, type HostIdle } from "./idle.js";
|
|
13
15
|
export type {
|
|
14
16
|
ServerConfig,
|
|
15
17
|
TransportConfig,
|
|
@@ -25,7 +27,7 @@ export type {
|
|
|
25
27
|
// server.start, server.stop and server.status (from @procedures/peer). The root registers
|
|
26
28
|
// nothing: import "@procedures/server/register", or give the module to createRegistry().
|
|
27
29
|
export * from "./module.js";
|
|
28
|
-
export { serverStartProcedure } from "./procedures/server.start.js";
|
|
30
|
+
export { serverStartProcedure, spawnMarkServer, type MarkServerSpawn, type ServerSpawner, type SpawnedMarkServer } from "./procedures/server.start.js";
|
|
29
31
|
export { serverStopProcedure } from "./procedures/server.stop.js";
|
|
30
32
|
export { serverStatusProcedure } from "./procedures/server.status.js";
|
|
31
33
|
export {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* peer package, because the peer of `mark --server` writes them.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
12
|
+
import { spawn, type ChildProcess, type SpawnOptions } from "node:child_process";
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
import * as os from "node:os";
|
|
@@ -185,6 +185,116 @@ export function findCliPath(): string {
|
|
|
185
185
|
return "mark";
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/** The function that starts a process. The default is `spawn` of `node:child_process`. The tests give their own. */
|
|
189
|
+
export type ServerSpawner = (command: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
|
|
190
|
+
|
|
191
|
+
const defaultSpawner: ServerSpawner = (command, args, options) => spawn(command, [...args], options);
|
|
192
|
+
|
|
193
|
+
/** The options of `spawnMarkServer()`. */
|
|
194
|
+
export interface MarkServerSpawn {
|
|
195
|
+
readonly port: number;
|
|
196
|
+
/** The address of the listener. */
|
|
197
|
+
readonly host: string;
|
|
198
|
+
/** The transport. The default is `http`. */
|
|
199
|
+
readonly transport?: "http" | "websocket" | "both" | undefined;
|
|
200
|
+
/** The idle stop of the server, in milliseconds (`--idle-timeout`, CLI-27). Without it, the server has no idle stop. */
|
|
201
|
+
readonly idleTimeoutMs?: number | undefined;
|
|
202
|
+
/** The root of the `fs.*` procedures of the server (`--fs-root`). Without it, the server has no root. */
|
|
203
|
+
readonly fsRoot?: string | undefined;
|
|
204
|
+
/** The function that starts the process. The default is `spawn`. */
|
|
205
|
+
readonly spawner?: ServerSpawner | undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The process of a server that `spawnMarkServer()` started, and the command line of the process. */
|
|
209
|
+
export interface SpawnedMarkServer {
|
|
210
|
+
readonly child: ChildProcess;
|
|
211
|
+
readonly command: string;
|
|
212
|
+
readonly args: readonly string[];
|
|
213
|
+
/** The log file of the server: its standard output and its standard error. */
|
|
214
|
+
readonly logFile: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* This function starts `mark --server` as a detached background process in the current folder,
|
|
219
|
+
* and gives the process at once. It does not wait for the readiness. `server.start` and the
|
|
220
|
+
* auto-warm of `mark` (CLI-27) use it.
|
|
221
|
+
*
|
|
222
|
+
* - The command line is the command that a user runs: `mark --server --port <port> --host <host>`.
|
|
223
|
+
* - The output goes to `~/.mark/server.log`. The old log moves to `server.log.1`.
|
|
224
|
+
* - The process is detached and unref'd, so the caller can exit at once.
|
|
225
|
+
* - An error of the spawn goes to the event `error` of the process, and it does not throw.
|
|
226
|
+
*
|
|
227
|
+
* This function does not use `runCommand()` or `shell.run`. They wait for the end of the process,
|
|
228
|
+
* and this process must outlive its caller. `SERVER-LAYERS.md` records the exception.
|
|
229
|
+
*/
|
|
230
|
+
export function spawnMarkServer(options: MarkServerSpawn): SpawnedMarkServer {
|
|
231
|
+
const transport = options.transport ?? "http";
|
|
232
|
+
|
|
233
|
+
// Rotate log file
|
|
234
|
+
rotateLog();
|
|
235
|
+
|
|
236
|
+
// Ensure directories exist
|
|
237
|
+
fs.mkdirSync(SERVERS_DIR, { recursive: true });
|
|
238
|
+
|
|
239
|
+
// Open log file for writing
|
|
240
|
+
const logFd = fs.openSync(LOG_PATH, "a");
|
|
241
|
+
|
|
242
|
+
// Find CLI path
|
|
243
|
+
const cliPath = findCliPath();
|
|
244
|
+
|
|
245
|
+
// Build arguments: the root is a global flag, before --server
|
|
246
|
+
const args = [
|
|
247
|
+
...(options.fsRoot !== undefined ? ["--fs-root", options.fsRoot] : []),
|
|
248
|
+
"--server",
|
|
249
|
+
"--port",
|
|
250
|
+
String(options.port),
|
|
251
|
+
"--host",
|
|
252
|
+
options.host,
|
|
253
|
+
];
|
|
254
|
+
if (transport !== "http") {
|
|
255
|
+
args.push("--transport", transport);
|
|
256
|
+
}
|
|
257
|
+
if (options.idleTimeoutMs !== undefined) {
|
|
258
|
+
args.push("--idle-timeout", String(options.idleTimeoutMs));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Determine how to spawn
|
|
262
|
+
let spawnCmd: string;
|
|
263
|
+
let spawnArgs: string[];
|
|
264
|
+
|
|
265
|
+
if (cliPath === "mark" || cliPath.endsWith(".bin/mark") || cliPath.endsWith(".bin\\mark")) {
|
|
266
|
+
// Use mark command directly
|
|
267
|
+
spawnCmd = cliPath;
|
|
268
|
+
spawnArgs = args;
|
|
269
|
+
} else {
|
|
270
|
+
// Use node to run the JS file
|
|
271
|
+
spawnCmd = process.execPath;
|
|
272
|
+
spawnArgs = [cliPath, ...args];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Spawn daemon process
|
|
276
|
+
let child: ChildProcess;
|
|
277
|
+
try {
|
|
278
|
+
child = (options.spawner ?? defaultSpawner)(spawnCmd, spawnArgs, {
|
|
279
|
+
detached: true,
|
|
280
|
+
stdio: ["ignore", logFd, logFd],
|
|
281
|
+
env: { ...process.env },
|
|
282
|
+
cwd: process.cwd(),
|
|
283
|
+
// No console window on Windows (deep dive CLI-16)
|
|
284
|
+
windowsHide: true,
|
|
285
|
+
});
|
|
286
|
+
} finally {
|
|
287
|
+
fs.closeSync(logFd);
|
|
288
|
+
}
|
|
289
|
+
// A spawn that fails, for example without `mark` in PATH, emits "error". Without a listener,
|
|
290
|
+
// the event ends the process of the caller.
|
|
291
|
+
child.once("error", () => undefined);
|
|
292
|
+
|
|
293
|
+
// Unref so parent can exit
|
|
294
|
+
child.unref();
|
|
295
|
+
return { child, command: spawnCmd, args: spawnArgs, logFile: LOG_PATH };
|
|
296
|
+
}
|
|
297
|
+
|
|
188
298
|
/**
|
|
189
299
|
* The rule under a root of the files (roadmap R33, tracker item O36): a root refuses it. It starts
|
|
190
300
|
* a mark server without the root, and it writes the log and the lock files in `~/.mark`.
|
|
@@ -227,55 +337,12 @@ export const serverStartProcedure: Procedure<
|
|
|
227
337
|
};
|
|
228
338
|
}
|
|
229
339
|
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// Ensure directories exist
|
|
234
|
-
fs.mkdirSync(SERVERS_DIR, { recursive: true });
|
|
235
|
-
|
|
236
|
-
// Open log file for writing
|
|
237
|
-
const logFd = fs.openSync(LOG_PATH, "a");
|
|
238
|
-
|
|
239
|
-
// Find CLI path
|
|
240
|
-
const cliPath = findCliPath();
|
|
241
|
-
|
|
242
|
-
// Build arguments
|
|
243
|
-
const args = ["--server", "--port", String(port), "--host", host];
|
|
244
|
-
if (transport !== "http") {
|
|
245
|
-
args.push("--transport", transport);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Determine how to spawn
|
|
249
|
-
let spawnCmd: string;
|
|
250
|
-
let spawnArgs: string[];
|
|
251
|
-
|
|
252
|
-
if (cliPath === "mark" || cliPath.endsWith(".bin/mark") || cliPath.endsWith(".bin\\mark")) {
|
|
253
|
-
// Use mark command directly
|
|
254
|
-
spawnCmd = cliPath;
|
|
255
|
-
spawnArgs = args;
|
|
256
|
-
} else {
|
|
257
|
-
// Use node to run the JS file
|
|
258
|
-
spawnCmd = process.execPath;
|
|
259
|
-
spawnArgs = [cliPath, ...args];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Spawn daemon process
|
|
263
|
-
const child = spawn(spawnCmd, spawnArgs, {
|
|
264
|
-
detached: true,
|
|
265
|
-
stdio: ["ignore", logFd, logFd],
|
|
266
|
-
env: { ...process.env },
|
|
267
|
-
cwd: process.cwd(),
|
|
268
|
-
// No console window on Windows (deep dive CLI-16)
|
|
269
|
-
windowsHide: true,
|
|
270
|
-
});
|
|
340
|
+
// The detached process of the server, with its log (CLI-27 shares this start with the auto-warm of mark)
|
|
341
|
+
const { child } = spawnMarkServer({ port, host, transport });
|
|
271
342
|
// The end of the process stops the wait. The listener of "error" also catches a spawn that
|
|
272
343
|
// fails, for example without `mark` in PATH.
|
|
273
344
|
const end = endOf(child);
|
|
274
345
|
|
|
275
|
-
// Unref so parent can exit
|
|
276
|
-
child.unref();
|
|
277
|
-
fs.closeSync(logFd);
|
|
278
|
-
|
|
279
346
|
// The wait for the readiness (CLI-9): the readiness probe, and host.info for the identity of the server
|
|
280
347
|
const result = await waitForServer({ url: readinessUrl(host, port), port, before: existing?.peerId, end, timeoutMs });
|
|
281
348
|
if ("ready" in result) {
|