@andrew_l/app 0.3.8 → 0.4.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/README.md +2 -1
- package/bin/vrun +0 -1
- package/dist/_chunks/cli.mjs +2 -0
- package/dist/index.d.mts +315 -320
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2026 -2348
- package/dist/index.mjs.map +1 -1
- package/dist/vrun.d.mts +1 -2
- package/dist/vrun.mjs +9 -10
- package/dist/vrun.mjs.map +1 -1
- package/package.json +10 -9
- package/dist/index.cjs +0 -2678
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -651
- package/dist/index.d.ts +0 -651
- package/dist/vrun.cjs +0 -13
- package/dist/vrun.cjs.map +0 -1
- package/dist/vrun.d.cts +0 -2
- package/dist/vrun.d.ts +0 -2
package/dist/index.mjs
CHANGED
|
@@ -1,527 +1,435 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
4
|
-
import {
|
|
5
|
-
import { isShuttingDown,
|
|
6
|
-
import * as cp from
|
|
7
|
-
import { EventEmitter } from
|
|
8
|
-
import {
|
|
9
|
-
import fs from
|
|
10
|
-
import dotenv from
|
|
11
|
-
import { getColor } from
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
1
|
+
import { AsyncIterableQueue, CancellablePromise, EJSON, ResourcePool, Scheduler, SimpleEventEmitter, arrayable, assert, asyncForEach, camelCase, capitalize, captureStackTrace, catchError, def, defer, env, getFileExtension, getLoggerLevel, isError, isFunction, isNumber, isObject, isSkip, isString, kebabCase, noop, pickPrefixed, toError, typeOf, uniq } from "@andrew_l/toolkit";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { LogLevels, createConsola } from "consola";
|
|
5
|
+
import { isShuttingDown, onShutdown, onShutdownError, processGraceful } from "@andrew_l/graceful";
|
|
6
|
+
import * as cp from "node:child_process";
|
|
7
|
+
import { EventEmitter } from "node:events";
|
|
8
|
+
import { Command, InvalidArgumentError, Option } from "commander";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import dotenv from "dotenv";
|
|
11
|
+
import { getColor } from "consola/utils";
|
|
12
|
+
import { Box, Text, render, useInput, useStdout } from "ink";
|
|
13
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
14
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
16
15
|
const CONFIG = Object.freeze({
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
APP_ROOT: env.string("APP_ROOT") || null,
|
|
23
|
-
/**
|
|
24
|
-
* Typescript resolver mode
|
|
25
|
-
*/
|
|
26
|
-
TS_MODE: env.string(
|
|
27
|
-
"VRUN_TS_MODE",
|
|
28
|
-
process.execArgv.some((v) => v.includes("tsx/dist/loader.mjs")) ? "tsx" : ""
|
|
29
|
-
) || null,
|
|
30
|
-
/**
|
|
31
|
-
* List of disabled workers
|
|
32
|
-
*/
|
|
33
|
-
WORKER_DISABLED: new Set(env.list("WORKER_DISABLE", "string"))
|
|
16
|
+
IS_VRUN: env.bool("VRUN", false),
|
|
17
|
+
WATCH_MODE: env.bool("VRUN_WATCH", false),
|
|
18
|
+
APP_ROOT: env.string("APP_ROOT") || null,
|
|
19
|
+
TS_MODE: env.string("VRUN_TS_MODE", process.execArgv.some((v) => v.includes("tsx/dist/loader.mjs")) ? "tsx" : "") || null,
|
|
20
|
+
WORKER_DISABLED: new Set(env.list("WORKER_DISABLE", "string"))
|
|
34
21
|
});
|
|
35
|
-
|
|
36
22
|
const isHelpArgument = (argv) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
return argv.includes("--help") || argv.includes("-h");
|
|
23
|
+
if (!Array.isArray(argv)) argv = [argv];
|
|
24
|
+
return argv.includes("--help") || argv.includes("-h");
|
|
41
25
|
};
|
|
42
26
|
function extractOptionsArgs(argv) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
27
|
+
const result = [];
|
|
28
|
+
let flag = false;
|
|
29
|
+
for (const value of argv) {
|
|
30
|
+
if (value.startsWith("--")) {
|
|
31
|
+
flag = true;
|
|
32
|
+
result.push(value);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (flag) {
|
|
36
|
+
result.push(value);
|
|
37
|
+
flag = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
57
41
|
}
|
|
58
|
-
|
|
59
42
|
function filePathFromStack(stack) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!filePath) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
if (filePath.startsWith("file://")) {
|
|
86
|
-
filePath = filePath.replace("file://", "");
|
|
87
|
-
}
|
|
88
|
-
if (process.platform === "win32") {
|
|
89
|
-
filePath = filePath.replace(/\//g, "\\");
|
|
90
|
-
if (filePath.match(/^[A-Za-z]:\\/)) ; else if (filePath.startsWith("\\")) ; else {
|
|
91
|
-
filePath = path.resolve(filePath);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return path.normalize(filePath);
|
|
43
|
+
const callerLine = stack.trim().split("\n")[0];
|
|
44
|
+
let filePath = null;
|
|
45
|
+
for (const pattern of [
|
|
46
|
+
/\((.+):(\d+):(\d+)\)$/,
|
|
47
|
+
/at\s+(.+):(\d+):(\d+)$/,
|
|
48
|
+
/\s+at\s+(.+):(\d+):(\d+)$/,
|
|
49
|
+
/\(file:\/\/(.+):(\d+):(\d+)\)$/,
|
|
50
|
+
/file:\/\/(.+):(\d+):(\d+)$/
|
|
51
|
+
]) {
|
|
52
|
+
const match = callerLine.match(pattern);
|
|
53
|
+
if (match) {
|
|
54
|
+
filePath = match[1];
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!filePath) return null;
|
|
59
|
+
if (filePath.startsWith("file://")) filePath = filePath.replace("file://", "");
|
|
60
|
+
if (process.platform === "win32") {
|
|
61
|
+
filePath = filePath.replace(/\//g, "\\");
|
|
62
|
+
if (filePath.match(/^[A-Za-z]:\\/)) {} else if (filePath.startsWith("\\")) {} else filePath = path.resolve(filePath);
|
|
63
|
+
}
|
|
64
|
+
return path.normalize(filePath);
|
|
95
65
|
}
|
|
96
|
-
|
|
97
66
|
function isMainFile(filepath) {
|
|
98
|
-
|
|
99
|
-
|
|
67
|
+
const cwd = process.cwd();
|
|
68
|
+
return process.argv.some((arg) => filepath.endsWith(path.resolve(cwd, arg)));
|
|
100
69
|
}
|
|
101
|
-
|
|
102
70
|
const createLogger = (tagName) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
log2.extend = (tagName2) => createLogger(tagName2);
|
|
112
|
-
return log2;
|
|
71
|
+
const log = createConsola({
|
|
72
|
+
formatOptions: { date: false },
|
|
73
|
+
level: LogLevels[getLoggerLevel()],
|
|
74
|
+
fancy: true,
|
|
75
|
+
defaults: { tag: tagName }
|
|
76
|
+
});
|
|
77
|
+
log.extend = (tagName) => createLogger(tagName);
|
|
78
|
+
return log;
|
|
113
79
|
};
|
|
114
80
|
const log = createLogger();
|
|
115
81
|
const noopLogger = {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
82
|
+
debug: noop,
|
|
83
|
+
error: noop,
|
|
84
|
+
extend: () => noopLogger,
|
|
85
|
+
info: noop,
|
|
86
|
+
log: noop,
|
|
87
|
+
warn: noop
|
|
122
88
|
};
|
|
123
|
-
|
|
124
89
|
function formatLogEvent(event, fields) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
90
|
+
if (!fields) return event;
|
|
91
|
+
const parts = [];
|
|
92
|
+
for (const key in fields) {
|
|
93
|
+
if (key === "vrun_app_thread_message") continue;
|
|
94
|
+
const v = fields[key];
|
|
95
|
+
if (v === void 0) continue;
|
|
96
|
+
parts.push(`${key}=${formatField(v)}`);
|
|
97
|
+
}
|
|
98
|
+
return parts.length ? `${event} ${parts.join(" ")}` : event;
|
|
134
99
|
}
|
|
135
100
|
function formatField(value) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return String(value);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
101
|
+
switch (typeOf(value)) {
|
|
102
|
+
case "array":
|
|
103
|
+
case "object": return EJSON.stringify(value);
|
|
104
|
+
default: return String(value);
|
|
105
|
+
}
|
|
144
106
|
}
|
|
145
107
|
function createAppLogger(definition) {
|
|
146
|
-
|
|
108
|
+
return definition.logger === false ? noopLogger : isFunction(definition.logger) ? definition.logger(definition) : definition.logger || createLogger(definition.name);
|
|
147
109
|
}
|
|
148
|
-
|
|
149
110
|
const APP_INSTANCE_STATE = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
111
|
+
INIT: "init",
|
|
112
|
+
IN_SETUP: "in-setup",
|
|
113
|
+
SETUP: "setup",
|
|
114
|
+
IN_STOP: "in-stop",
|
|
115
|
+
IN_RUN: "in-run",
|
|
116
|
+
RUN: "run",
|
|
117
|
+
STOP: "stop",
|
|
118
|
+
IN_SHUTDOWN: "in-shutdown",
|
|
119
|
+
SHUTDOWN: "shutdown",
|
|
120
|
+
ERROR: "error"
|
|
160
121
|
};
|
|
161
122
|
const APP_DEF = Symbol("app-definition");
|
|
162
123
|
function defineApp(definition) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
return result;
|
|
124
|
+
const result = {
|
|
125
|
+
filePath: filePathFromStack(captureStackTrace(defineApp)),
|
|
126
|
+
...definition
|
|
127
|
+
};
|
|
128
|
+
def(result, APP_DEF, true);
|
|
129
|
+
if (!CONFIG.IS_VRUN) {
|
|
130
|
+
if (result.filePath) {
|
|
131
|
+
if (isMainFile(result.filePath)) import("./_chunks/cli.mjs").then((m) => {
|
|
132
|
+
m.cli.runApp({
|
|
133
|
+
cliName: "app",
|
|
134
|
+
scriptFile: result.filePath,
|
|
135
|
+
argv: extractOptionsArgs(process.argv.slice(1))
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
182
141
|
}
|
|
183
142
|
function createAppInstance(definition) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
143
|
+
const setupState = createSetupState(definition);
|
|
144
|
+
return {
|
|
145
|
+
definition,
|
|
146
|
+
props: null,
|
|
147
|
+
setupState,
|
|
148
|
+
eventBus: new SimpleEventEmitter(),
|
|
149
|
+
mutexName: null,
|
|
150
|
+
mutexQueue: Promise.resolve(),
|
|
151
|
+
state: APP_INSTANCE_STATE.INIT,
|
|
152
|
+
logger: setupState.log
|
|
153
|
+
};
|
|
195
154
|
}
|
|
196
155
|
function isAppDefinition(value) {
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
instance.props = null;
|
|
347
|
-
instance.setupState = createSetupState(instance.definition);
|
|
348
|
-
mutexResolve();
|
|
349
|
-
if (shutdownErr) {
|
|
350
|
-
setState$1(instance, APP_INSTANCE_STATE.ERROR);
|
|
351
|
-
return {
|
|
352
|
-
skip: true,
|
|
353
|
-
code: "shutdown_app",
|
|
354
|
-
reason: "shutdown function throw error",
|
|
355
|
-
error: shutdownErr
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
setState$1(instance, APP_INSTANCE_STATE.SHUTDOWN);
|
|
359
|
-
return {
|
|
360
|
-
success: true,
|
|
361
|
-
code: "shutdown_app"
|
|
362
|
-
};
|
|
156
|
+
return value?.[APP_DEF] === true;
|
|
157
|
+
}
|
|
158
|
+
function startApp(app, props) {
|
|
159
|
+
const instance = isAppDefinition(app) ? createAppInstance(app) : app;
|
|
160
|
+
return setupApp(instance, props).then((setupResult) => {
|
|
161
|
+
if (isSkip(setupResult)) return setupResult;
|
|
162
|
+
return runApp(instance).then((runResult) => {
|
|
163
|
+
if (isSkip(runResult)) return shutdownApp(instance).then(() => runResult);
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
code: "start_app",
|
|
167
|
+
app: instance
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function setupApp(instance, props) {
|
|
173
|
+
return mutexAcquire(instance, "setup").then((mutexResolve) => {
|
|
174
|
+
if (instance.state !== APP_INSTANCE_STATE.INIT) {
|
|
175
|
+
mutexResolve();
|
|
176
|
+
return {
|
|
177
|
+
skip: true,
|
|
178
|
+
code: "setup_app",
|
|
179
|
+
reason: `application in ${instance.state} state`
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
setState$1(instance, APP_INSTANCE_STATE.IN_SETUP);
|
|
183
|
+
const { setup } = instance.definition;
|
|
184
|
+
let setupPromise = Promise.resolve();
|
|
185
|
+
if (isFunction(setup)) setupPromise = setupPromise.then(() => setup.call(instance.setupState, props)).then((setupResult) => void Object.assign(instance.setupState, setupResult));
|
|
186
|
+
return setupPromise.then(() => {
|
|
187
|
+
instance.props = props;
|
|
188
|
+
setState$1(instance, APP_INSTANCE_STATE.SETUP);
|
|
189
|
+
return {
|
|
190
|
+
success: true,
|
|
191
|
+
code: "setup_app"
|
|
192
|
+
};
|
|
193
|
+
}).catch((error) => {
|
|
194
|
+
setState$1(instance, APP_INSTANCE_STATE.ERROR);
|
|
195
|
+
return {
|
|
196
|
+
code: "setup_app",
|
|
197
|
+
skip: true,
|
|
198
|
+
reason: "setup function throw error",
|
|
199
|
+
error: toError(error)
|
|
200
|
+
};
|
|
201
|
+
}).finally(() => mutexResolve());
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function runApp(instance) {
|
|
205
|
+
return mutexAcquire(instance, "run").then((mutexResolve) => {
|
|
206
|
+
if (instance.state !== APP_INSTANCE_STATE.SETUP) {
|
|
207
|
+
mutexResolve();
|
|
208
|
+
return {
|
|
209
|
+
skip: true,
|
|
210
|
+
code: "execute_app",
|
|
211
|
+
reason: `application in ${instance.state} state`
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const { entry } = instance.definition;
|
|
215
|
+
setState$1(instance, APP_INSTANCE_STATE.IN_RUN);
|
|
216
|
+
instance.logger.info("Starting...");
|
|
217
|
+
let entryPromise = Promise.resolve();
|
|
218
|
+
if (isFunction(entry)) entryPromise = entryPromise.then(() => entry.call(instance.setupState, instance.props));
|
|
219
|
+
return entryPromise.then(() => {
|
|
220
|
+
setState$1(instance, APP_INSTANCE_STATE.RUN);
|
|
221
|
+
instance.logger.info("Started");
|
|
222
|
+
return {
|
|
223
|
+
success: true,
|
|
224
|
+
code: "execute_app"
|
|
225
|
+
};
|
|
226
|
+
}).catch((error) => {
|
|
227
|
+
setState$1(instance, APP_INSTANCE_STATE.ERROR);
|
|
228
|
+
return {
|
|
229
|
+
skip: true,
|
|
230
|
+
code: "execute_app",
|
|
231
|
+
reason: "entry function throw error",
|
|
232
|
+
error: toError(error)
|
|
233
|
+
};
|
|
234
|
+
}).finally(() => mutexResolve());
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function stopApp(instance) {
|
|
238
|
+
return mutexAcquire(instance, "stop").then((mutexResolve) => {
|
|
239
|
+
if (instance.state !== APP_INSTANCE_STATE.RUN) {
|
|
240
|
+
mutexResolve();
|
|
241
|
+
return {
|
|
242
|
+
skip: true,
|
|
243
|
+
code: "stop_app",
|
|
244
|
+
reason: `application in ${instance.state} state`
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const { stop } = instance.definition;
|
|
248
|
+
setState$1(instance, APP_INSTANCE_STATE.IN_STOP);
|
|
249
|
+
instance.logger.info("Stopping...");
|
|
250
|
+
let stopPromise = Promise.resolve();
|
|
251
|
+
if (isFunction(stop)) stopPromise = stopPromise.then(() => stop.call(instance.setupState, instance.props));
|
|
252
|
+
return stopPromise.then(() => {
|
|
253
|
+
setState$1(instance, APP_INSTANCE_STATE.STOP);
|
|
254
|
+
instance.logger.info("Stopped");
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
code: "stop_app"
|
|
258
|
+
};
|
|
259
|
+
}).catch((err) => {
|
|
260
|
+
setState$1(instance, APP_INSTANCE_STATE.ERROR);
|
|
261
|
+
return {
|
|
262
|
+
skip: true,
|
|
263
|
+
code: "stop_app",
|
|
264
|
+
reason: "stop function throw error",
|
|
265
|
+
error: toError(err)
|
|
266
|
+
};
|
|
267
|
+
}).finally(() => mutexResolve());
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function shutdownApp(instance) {
|
|
271
|
+
return stopApp(instance).then(() => mutexAcquire(instance, "shutdown")).then((mutexResolve) => {
|
|
272
|
+
if (instance.state !== APP_INSTANCE_STATE.STOP && instance.state !== APP_INSTANCE_STATE.SETUP && instance.state !== APP_INSTANCE_STATE.ERROR) {
|
|
273
|
+
mutexResolve();
|
|
274
|
+
return {
|
|
275
|
+
skip: true,
|
|
276
|
+
code: "shutdown_app",
|
|
277
|
+
reason: `application in ${instance.state} state`
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const { shutdown } = instance.definition;
|
|
281
|
+
setState$1(instance, APP_INSTANCE_STATE.IN_SHUTDOWN);
|
|
282
|
+
instance.logger.info("Shutdown...");
|
|
283
|
+
let shutdownPromise = Promise.resolve();
|
|
284
|
+
if (isFunction(shutdown)) shutdownPromise = shutdownPromise.then(() => shutdown.call(instance.setupState, instance.props));
|
|
285
|
+
return shutdownPromise.then(() => {
|
|
286
|
+
setState$1(instance, APP_INSTANCE_STATE.SHUTDOWN);
|
|
287
|
+
return {
|
|
288
|
+
success: true,
|
|
289
|
+
code: "shutdown_app"
|
|
290
|
+
};
|
|
291
|
+
}).catch((shutdownErr) => {
|
|
292
|
+
setState$1(instance, APP_INSTANCE_STATE.ERROR);
|
|
293
|
+
return {
|
|
294
|
+
skip: true,
|
|
295
|
+
code: "shutdown_app",
|
|
296
|
+
reason: "shutdown function throw error",
|
|
297
|
+
error: shutdownErr
|
|
298
|
+
};
|
|
299
|
+
}).finally(() => {
|
|
300
|
+
instance.props = null;
|
|
301
|
+
instance.setupState = createSetupState(instance.definition);
|
|
302
|
+
mutexResolve();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
363
305
|
}
|
|
364
306
|
function appWaitShutdown(instance) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
instance.eventBus.off("error", q.reject);
|
|
374
|
-
});
|
|
307
|
+
if (instance.state === APP_INSTANCE_STATE.SHUTDOWN) return Promise.resolve();
|
|
308
|
+
const q = defer();
|
|
309
|
+
instance.eventBus.once("state:shutdown", q.resolve);
|
|
310
|
+
instance.eventBus.once("error", q.reject);
|
|
311
|
+
return q.promise.finally(() => {
|
|
312
|
+
instance.eventBus.off("state:shutdown", q.resolve);
|
|
313
|
+
instance.eventBus.off("error", q.reject);
|
|
314
|
+
});
|
|
375
315
|
}
|
|
376
316
|
function createSetupState(definition) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (definition.methods) {
|
|
381
|
-
for (const [key, fn] of Object.entries(definition.methods)) {
|
|
382
|
-
setupState[key] = fn.bind(setupState);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return setupState;
|
|
317
|
+
const setupState = { log: createAppLogger(definition) };
|
|
318
|
+
if (definition.methods) for (const [key, fn] of Object.entries(definition.methods)) setupState[key] = fn.bind(setupState);
|
|
319
|
+
return setupState;
|
|
386
320
|
}
|
|
387
321
|
function mutexAcquire(instance, name) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
);
|
|
401
|
-
return acquirePromise;
|
|
322
|
+
let outerResolve;
|
|
323
|
+
const acquirePromise = new Promise((res) => {
|
|
324
|
+
outerResolve = res;
|
|
325
|
+
});
|
|
326
|
+
instance.mutexQueue = instance.mutexQueue.then(() => new Promise((innerResolve) => {
|
|
327
|
+
instance.mutexName = name;
|
|
328
|
+
outerResolve(() => {
|
|
329
|
+
instance.mutexName = null;
|
|
330
|
+
innerResolve();
|
|
331
|
+
});
|
|
332
|
+
}));
|
|
333
|
+
return acquirePromise;
|
|
402
334
|
}
|
|
403
335
|
function setState$1(app, newState) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
336
|
+
const oldState = app.state;
|
|
337
|
+
if (newState !== oldState) {
|
|
338
|
+
app.state = newState;
|
|
339
|
+
app.eventBus.emit("state", newState, oldState);
|
|
340
|
+
app.eventBus.emit(`state:${newState}`);
|
|
341
|
+
}
|
|
410
342
|
}
|
|
411
|
-
|
|
412
343
|
function createAppHub(definitions) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
return app;
|
|
344
|
+
return defineApp({
|
|
345
|
+
name: "app-hub",
|
|
346
|
+
description: `Application wrapper around: ${definitions.map((v) => v.name).join(", ")}`,
|
|
347
|
+
logger: false,
|
|
348
|
+
props: prefixifyProps(definitions),
|
|
349
|
+
setup(props) {
|
|
350
|
+
const instances = [];
|
|
351
|
+
const setupSkips = [];
|
|
352
|
+
return asyncForEach(definitions, (definition) => {
|
|
353
|
+
const appProps = getPrefixedProps(definition, props);
|
|
354
|
+
const instance = createAppInstance(definition);
|
|
355
|
+
instances.push(instance);
|
|
356
|
+
return setupApp(instance, appProps).then((setupResult) => {
|
|
357
|
+
if (isSkip(setupResult)) setupSkips.push({
|
|
358
|
+
...setupResult,
|
|
359
|
+
app: instance
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
}).then(() => {
|
|
363
|
+
if (setupSkips.length) throw skipExecsToError("setup", setupSkips);
|
|
364
|
+
return { instances };
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
entry() {
|
|
368
|
+
const runSkips = [];
|
|
369
|
+
return asyncForEach(this.instances, (instance) => {
|
|
370
|
+
return runApp(instance).then((runResult) => {
|
|
371
|
+
if (isSkip(runResult)) runSkips.push({
|
|
372
|
+
...runResult,
|
|
373
|
+
app: instance
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
}).then(() => {
|
|
377
|
+
if (runSkips.length) throw skipExecsToError("start", runSkips);
|
|
378
|
+
});
|
|
379
|
+
},
|
|
380
|
+
stop() {
|
|
381
|
+
const stopSkips = [];
|
|
382
|
+
return asyncForEach(this.instances, (instance) => {
|
|
383
|
+
return stopApp(instance).then((stopResult) => {
|
|
384
|
+
if (isSkip(stopResult)) stopSkips.push({
|
|
385
|
+
...stopResult,
|
|
386
|
+
app: instance
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
}).then(() => {
|
|
390
|
+
if (stopSkips.length) throw skipExecsToError("stop", stopSkips);
|
|
391
|
+
});
|
|
392
|
+
},
|
|
393
|
+
shutdown() {
|
|
394
|
+
const shutdownSkips = [];
|
|
395
|
+
return asyncForEach(this.instances, (instance) => {
|
|
396
|
+
return shutdownApp(instance).then((shutdownResult) => {
|
|
397
|
+
if (isSkip(shutdownResult)) shutdownSkips.push({
|
|
398
|
+
...shutdownResult,
|
|
399
|
+
app: instance
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
}).then(() => {
|
|
403
|
+
if (shutdownSkips.length) throw skipExecsToError("shutdown", shutdownSkips);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
});
|
|
481
407
|
}
|
|
482
408
|
function skipExecsToError(verb, skips) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
${
|
|
488
|
-
${
|
|
489
|
-
}
|
|
490
|
-
return "";
|
|
491
|
-
};
|
|
492
|
-
const lines = skips.map(
|
|
493
|
-
(s) => s.reason ? ` ${s.app.definition.name} [${s.code}]: ${s.reason}${getError(s).replaceAll("\n", "\n ")}` : ` ${s.app.definition.name} [${s.code}]${getError(s).replaceAll("\n", "\n ")}`
|
|
494
|
-
);
|
|
495
|
-
return new Error(
|
|
496
|
-
`Failed to ${verb} ${skips.length} app(s):
|
|
497
|
-
${lines.join("\n")}`,
|
|
498
|
-
{ cause: "error" in skips[0] ? skips[0].error : void 0 }
|
|
499
|
-
);
|
|
409
|
+
const getError = (r) => {
|
|
410
|
+
if ("error" in r && isError(r.error)) return r.error.stack ? `\n${r.error.message}\n${r.error.stack}` : `\n${r.error.message}`;
|
|
411
|
+
return "";
|
|
412
|
+
};
|
|
413
|
+
const lines = skips.map((s) => s.reason ? ` ${s.app.definition.name} [${s.code}]: ${s.reason}${getError(s).replaceAll("\n", "\n ")}` : ` ${s.app.definition.name} [${s.code}]${getError(s).replaceAll("\n", "\n ")}`);
|
|
414
|
+
return new Error(`Failed to ${verb} ${skips.length} app(s):\n${lines.join("\n")}`, { cause: "error" in skips[0] ? skips[0].error : void 0 });
|
|
500
415
|
}
|
|
501
416
|
function getPrefixedProps(definition, props) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
417
|
+
const result = pickPrefixed(props, {
|
|
418
|
+
prefix: camelCase(definition.name),
|
|
419
|
+
prefixTrim: true
|
|
420
|
+
});
|
|
421
|
+
for (const [key, value] of Object.entries(result)) {
|
|
422
|
+
const propName = key.charAt(0).toLowerCase() + key.slice(1).toLowerCase();
|
|
423
|
+
result[propName] = value;
|
|
424
|
+
delete result[key];
|
|
425
|
+
}
|
|
426
|
+
return result;
|
|
512
427
|
}
|
|
513
428
|
function prefixifyProps(definitions) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
for (const [propName, prop] of Object.entries(definition.props)) {
|
|
518
|
-
props[camelCase(`${definition.name}${capitalize(propName)}`)] = prop;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return props;
|
|
429
|
+
const props = {};
|
|
430
|
+
for (const definition of definitions) if (definition.props) for (const [propName, prop] of Object.entries(definition.props)) props[camelCase(`${definition.name}${capitalize(propName)}`)] = prop;
|
|
431
|
+
return props;
|
|
523
432
|
}
|
|
524
|
-
|
|
525
433
|
const MAX_RESTARTS = 3;
|
|
526
434
|
const HEARTBEAT_INTERVAL_MS = 5e3;
|
|
527
435
|
const HEARTBEAT_TIMEOUT_MS = 1e4;
|
|
@@ -589,2036 +497,1806 @@ onShutdown('app', async () => {
|
|
|
589
497
|
});
|
|
590
498
|
`;
|
|
591
499
|
function setState(w, next) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
500
|
+
const prev = w.state;
|
|
501
|
+
if (prev === next) return;
|
|
502
|
+
w.state = next;
|
|
503
|
+
w.writeLog("debug", "state", {
|
|
504
|
+
from: prev,
|
|
505
|
+
to: next
|
|
506
|
+
});
|
|
507
|
+
w.eventBus.emit("state", next, prev);
|
|
597
508
|
}
|
|
598
509
|
function spawnChild(w) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
510
|
+
w.writeLog("debug", "thread.spawn.start", {
|
|
511
|
+
script: path.basename(w.scriptFile),
|
|
512
|
+
thread: w.threadId
|
|
513
|
+
});
|
|
514
|
+
setState(w, "init");
|
|
515
|
+
const child = cp.spawn(process.execPath, [
|
|
516
|
+
"--input-type=module",
|
|
517
|
+
"-e",
|
|
518
|
+
bootstrapCode
|
|
519
|
+
], {
|
|
520
|
+
stdio: w.threadProps.__inheritIO !== false ? [
|
|
521
|
+
"pipe",
|
|
522
|
+
"inherit",
|
|
523
|
+
"inherit",
|
|
524
|
+
"ipc"
|
|
525
|
+
] : [
|
|
526
|
+
"pipe",
|
|
527
|
+
"pipe",
|
|
528
|
+
"pipe",
|
|
529
|
+
"ipc"
|
|
530
|
+
],
|
|
531
|
+
env: {
|
|
532
|
+
...process.env,
|
|
533
|
+
VRUN_PROCESS_SCRIPT_FILE: w.scriptFile,
|
|
534
|
+
VRUN_PROCESS_ID: String(w.threadId)
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
w.child = child;
|
|
538
|
+
const onData = (chunk) => {
|
|
539
|
+
const text = chunk.toString("utf8");
|
|
540
|
+
for (const line of text.split(/\r?\n/)) {
|
|
541
|
+
if (line.length === 0) continue;
|
|
542
|
+
w.eventBus.emit("log", parseRawLogLine(line));
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
child.stdout?.on("data", onData);
|
|
546
|
+
child.stderr?.on("data", onData);
|
|
547
|
+
child.on("message", (msg) => {
|
|
548
|
+
if (isThreadMessage(msg)) {
|
|
549
|
+
w.eventBus.emit("message", msg);
|
|
550
|
+
w.eventBus.emit(`msg:${msg.type}`, msg);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
child.once("exit", (code, signal) => {
|
|
554
|
+
w.eventBus.emit("exit", code, signal);
|
|
555
|
+
});
|
|
556
|
+
child.on("error", (err) => {
|
|
557
|
+
w.writeLog("error", "thread.spawn.error", { message: err.message });
|
|
558
|
+
w.eventBus.emit("error", err);
|
|
559
|
+
});
|
|
639
560
|
}
|
|
640
561
|
function startHeartbeat(w) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
657
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
562
|
+
w.lastPong = Date.now();
|
|
563
|
+
w.heartbeatTimer = setInterval(() => {
|
|
564
|
+
if (w.state !== APP_INSTANCE_STATE.RUN) {
|
|
565
|
+
clearInterval(w.heartbeatTimer);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const since = Date.now() - w.lastPong;
|
|
569
|
+
if (since > HEARTBEAT_TIMEOUT_MS) {
|
|
570
|
+
clearInterval(w.heartbeatTimer);
|
|
571
|
+
w.writeLog("warn", "heartbeat.miss", { since_ms: since });
|
|
572
|
+
restartThreadApp(w).catch((err) => w.eventBus.emit("error", err));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (w.child.connected) w.child.send(createThreadMessage("ping", {}));
|
|
576
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
658
577
|
}
|
|
659
578
|
function waitForExit(w, timeoutMs) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}, timeoutMs);
|
|
687
|
-
w.child.once("exit", onExit);
|
|
688
|
-
});
|
|
579
|
+
if (isThreadExit(w)) return Promise.resolve();
|
|
580
|
+
return new Promise((resolve) => {
|
|
581
|
+
w.writeLog("info", "thread.exit.waiting", { timeout_ms: timeoutMs });
|
|
582
|
+
const cleanup = () => {
|
|
583
|
+
clearTimeout(slowTimer);
|
|
584
|
+
clearTimeout(killTimer);
|
|
585
|
+
w.child.off("exit", onExit);
|
|
586
|
+
};
|
|
587
|
+
const onExit = () => {
|
|
588
|
+
cleanup();
|
|
589
|
+
resolve();
|
|
590
|
+
};
|
|
591
|
+
const halfway = Math.floor(timeoutMs / 2);
|
|
592
|
+
const slowTimer = setTimeout(() => {
|
|
593
|
+
w.writeLog("warn", "thread.exit.slow", { elapsed_ms: halfway });
|
|
594
|
+
}, halfway);
|
|
595
|
+
const killTimer = setTimeout(() => {
|
|
596
|
+
cleanup();
|
|
597
|
+
try {
|
|
598
|
+
w.writeLog("warn", "thread.exit.force-kill", { timeout_ms: timeoutMs });
|
|
599
|
+
w.child.kill("SIGKILL");
|
|
600
|
+
} catch {}
|
|
601
|
+
resolve();
|
|
602
|
+
}, timeoutMs);
|
|
603
|
+
w.child.once("exit", onExit);
|
|
604
|
+
});
|
|
689
605
|
}
|
|
690
606
|
function waitForThreadReady(w) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
w.eventBus.once("exit", onExit);
|
|
715
|
-
});
|
|
607
|
+
if (w.state === "ready") return Promise.resolve();
|
|
608
|
+
return new Promise((resolve, reject) => {
|
|
609
|
+
const cleanup = () => {
|
|
610
|
+
w.eventBus.off("ready", onReady);
|
|
611
|
+
w.eventBus.off("error", onError);
|
|
612
|
+
w.eventBus.off("exit", onExit);
|
|
613
|
+
};
|
|
614
|
+
const onReady = () => {
|
|
615
|
+
resolve();
|
|
616
|
+
cleanup();
|
|
617
|
+
};
|
|
618
|
+
const onExit = (code) => {
|
|
619
|
+
reject(/* @__PURE__ */ new Error(`Child exited before ready (code=${code ?? "null"})`));
|
|
620
|
+
cleanup();
|
|
621
|
+
};
|
|
622
|
+
const onError = (error) => {
|
|
623
|
+
reject(error);
|
|
624
|
+
cleanup();
|
|
625
|
+
};
|
|
626
|
+
w.eventBus.once("ready", onReady);
|
|
627
|
+
w.eventBus.once("error", onError);
|
|
628
|
+
w.eventBus.once("exit", onExit);
|
|
629
|
+
});
|
|
716
630
|
}
|
|
717
631
|
function sendAndWait(w, message, replyType, timeout = 3e4) {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
632
|
+
return new Promise((resolve) => {
|
|
633
|
+
const eventName = `msg:${replyType}`;
|
|
634
|
+
const cleanup = () => {
|
|
635
|
+
w.eventBus.off(eventName, onReply);
|
|
636
|
+
w.eventBus.off("exit", onExit);
|
|
637
|
+
clearTimeout(timer);
|
|
638
|
+
};
|
|
639
|
+
const timer = setTimeout(() => {
|
|
640
|
+
cleanup();
|
|
641
|
+
w.writeLog("error", "parent.ipc.timeout", {
|
|
642
|
+
waiting: replyType,
|
|
643
|
+
ms: timeout
|
|
644
|
+
});
|
|
645
|
+
resolve({
|
|
646
|
+
skip: true,
|
|
647
|
+
code: "parent.ipc.timeout",
|
|
648
|
+
reason: `Thread ${path.basename(w.scriptFile)} timed out waiting for "${replyType}"`
|
|
649
|
+
});
|
|
650
|
+
}, timeout);
|
|
651
|
+
const onReply = (reply) => {
|
|
652
|
+
cleanup();
|
|
653
|
+
resolve({
|
|
654
|
+
success: true,
|
|
655
|
+
code: "parent.ipc.recv",
|
|
656
|
+
reply
|
|
657
|
+
});
|
|
658
|
+
};
|
|
659
|
+
const onExit = () => {
|
|
660
|
+
const reason = `thread.exit (code=${w.child.exitCode} signal=${w.child.signalCode ?? "null"})`;
|
|
661
|
+
w.writeLog("debug", "parent.ipc.skipped", {
|
|
662
|
+
waiting: replyType,
|
|
663
|
+
reason
|
|
664
|
+
});
|
|
665
|
+
resolve({
|
|
666
|
+
skip: true,
|
|
667
|
+
code: "thread.exit",
|
|
668
|
+
reason
|
|
669
|
+
});
|
|
670
|
+
};
|
|
671
|
+
if (isThreadExit(w)) {
|
|
672
|
+
onExit();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
w.eventBus.once("exit", onExit);
|
|
676
|
+
w.eventBus.once(eventName, onReply);
|
|
677
|
+
w.writeLog("debug", "parent.ipc.send", message);
|
|
678
|
+
try {
|
|
679
|
+
w.child.send(message);
|
|
680
|
+
} catch (err) {
|
|
681
|
+
const error = toError(err);
|
|
682
|
+
w.writeLog("error", "parent.ipc.fail", {
|
|
683
|
+
message,
|
|
684
|
+
error: error.message
|
|
685
|
+
});
|
|
686
|
+
cleanup();
|
|
687
|
+
resolve({
|
|
688
|
+
skip: true,
|
|
689
|
+
code: "parent.ipc.fail",
|
|
690
|
+
reason: error.message
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
});
|
|
776
694
|
}
|
|
777
695
|
function initThread(threadId, scriptFile, threadProps) {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
break;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
spawnChild(w);
|
|
842
|
-
return w;
|
|
696
|
+
const w = {
|
|
697
|
+
threadId,
|
|
698
|
+
scriptFile,
|
|
699
|
+
threadProps,
|
|
700
|
+
pid: 0,
|
|
701
|
+
state: APP_INSTANCE_STATE.INIT,
|
|
702
|
+
restartCount: 0,
|
|
703
|
+
lastPong: 0,
|
|
704
|
+
child: null,
|
|
705
|
+
scheduler: new Scheduler(),
|
|
706
|
+
eventBus: new EventEmitter(),
|
|
707
|
+
writeLog(level, event, fields) {
|
|
708
|
+
w.eventBus.emit("log", {
|
|
709
|
+
level,
|
|
710
|
+
text: formatLogEvent(event, fields),
|
|
711
|
+
ts: Date.now()
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
w.eventBus.setMaxListeners(0);
|
|
716
|
+
const onJobPhase = (phase) => (job) => {
|
|
717
|
+
w.writeLog("debug", `job.${job.jobName ?? "untitled"}.${phase}`);
|
|
718
|
+
};
|
|
719
|
+
w.scheduler.onJob(onJobPhase("queue"));
|
|
720
|
+
w.scheduler.onJobStart(onJobPhase("start"));
|
|
721
|
+
w.scheduler.onJobComplete(onJobPhase("done"));
|
|
722
|
+
w.eventBus.on("exit", (code, signal) => {
|
|
723
|
+
const prevState = w.state;
|
|
724
|
+
if (signal) w.writeLog("warn", "thread.exit.signal", {
|
|
725
|
+
signal,
|
|
726
|
+
code
|
|
727
|
+
});
|
|
728
|
+
else if (code === 0 || code === null) w.writeLog("info", "thread.exit.clean", { code });
|
|
729
|
+
else w.writeLog("error", "thread.exit.crash", { code });
|
|
730
|
+
setState(w, "shutdown");
|
|
731
|
+
clearInterval(w.heartbeatTimer);
|
|
732
|
+
w.pid = 0;
|
|
733
|
+
if (prevState === APP_INSTANCE_STATE.RUN && !isShuttingDown()) restartThreadApp(w).catch((err) => w.eventBus.emit("error", err));
|
|
734
|
+
});
|
|
735
|
+
w.eventBus.on("message", (msg) => {
|
|
736
|
+
w.writeLog("debug", "parent.ipc.recv", msg);
|
|
737
|
+
switch (msg.type) {
|
|
738
|
+
case "app-state":
|
|
739
|
+
setState(w, msg.state);
|
|
740
|
+
break;
|
|
741
|
+
case "ready":
|
|
742
|
+
w.pid = msg.pid;
|
|
743
|
+
w.eventBus.emit("pid", msg.pid);
|
|
744
|
+
setState(w, "ready");
|
|
745
|
+
w.writeLog("debug", "thread.spawn.ready");
|
|
746
|
+
w.eventBus.emit("ready");
|
|
747
|
+
break;
|
|
748
|
+
case "pong":
|
|
749
|
+
w.lastPong = Date.now();
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
spawnChild(w);
|
|
754
|
+
return w;
|
|
843
755
|
}
|
|
844
756
|
function withLifecycle(w, phase, fn) {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
message: String(error && error.message || error)
|
|
854
|
-
});
|
|
855
|
-
throw error;
|
|
856
|
-
}
|
|
857
|
-
);
|
|
757
|
+
w.writeLog("debug", `lifecycle.${phase}.begin`);
|
|
758
|
+
return fn().then((result) => {
|
|
759
|
+
w.writeLog("debug", `lifecycle.${phase}.done`);
|
|
760
|
+
return result;
|
|
761
|
+
}, (error) => {
|
|
762
|
+
w.writeLog("error", `lifecycle.${phase}.fail`, { message: String(error && error.message || error) });
|
|
763
|
+
throw error;
|
|
764
|
+
});
|
|
858
765
|
}
|
|
859
766
|
function setupThreadApp(w) {
|
|
860
|
-
|
|
861
|
-
() => withLifecycle(w, "setupThreadApp", () => doSetupThread(w)),
|
|
862
|
-
{ jobName: "setupThreadApp" }
|
|
863
|
-
);
|
|
767
|
+
return w.scheduler.queueJobWait(() => withLifecycle(w, "setupThreadApp", () => doSetupThread(w)), { jobName: "setupThreadApp" });
|
|
864
768
|
}
|
|
865
769
|
function doSetupThread(w) {
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
w,
|
|
869
|
-
createThreadMessage("setup", {
|
|
870
|
-
props: w.threadProps
|
|
871
|
-
}),
|
|
872
|
-
"setup_done"
|
|
873
|
-
).then(noop);
|
|
770
|
+
setState(w, APP_INSTANCE_STATE.IN_SETUP);
|
|
771
|
+
return sendAndWait(w, createThreadMessage("setup", { props: w.threadProps }), "setup_done").then(noop);
|
|
874
772
|
}
|
|
875
773
|
function startThreadApp(w) {
|
|
876
|
-
|
|
877
|
-
() => withLifecycle(w, "startThreadApp", () => doStartThreadApp(w)),
|
|
878
|
-
{ jobName: "startThreadApp" }
|
|
879
|
-
);
|
|
774
|
+
return w.scheduler.queueJobWait(() => withLifecycle(w, "startThreadApp", () => doStartThreadApp(w)), { jobName: "startThreadApp" });
|
|
880
775
|
}
|
|
881
776
|
function doStartThreadApp(w) {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
startHeartbeat(w);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
);
|
|
777
|
+
setState(w, APP_INSTANCE_STATE.IN_RUN);
|
|
778
|
+
return sendAndWait(w, createThreadMessage("start", {}), "start_done").then(() => {
|
|
779
|
+
if (w.state === APP_INSTANCE_STATE.RUN) startHeartbeat(w);
|
|
780
|
+
});
|
|
890
781
|
}
|
|
891
782
|
function stopThreadApp(w) {
|
|
892
|
-
|
|
893
|
-
() => withLifecycle(w, "stopThreadApp", () => doStopThreadApp(w)),
|
|
894
|
-
{ jobName: "stopThreadApp" }
|
|
895
|
-
);
|
|
783
|
+
return w.scheduler.queueJobWait(() => withLifecycle(w, "stopThreadApp", () => doStopThreadApp(w)), { jobName: "stopThreadApp" });
|
|
896
784
|
}
|
|
897
785
|
function doStopThreadApp(w) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
noop
|
|
902
|
-
);
|
|
786
|
+
setState(w, APP_INSTANCE_STATE.IN_STOP);
|
|
787
|
+
clearInterval(w.heartbeatTimer);
|
|
788
|
+
return sendAndWait(w, createThreadMessage("stop", {}), "stop_done").then(noop);
|
|
903
789
|
}
|
|
904
790
|
function shutdownThreadApp(w) {
|
|
905
|
-
|
|
906
|
-
() => withLifecycle(w, "shutdownThreadApp", () => doShutdownThreadApp(w)),
|
|
907
|
-
{ jobName: "shutdownThreadApp" }
|
|
908
|
-
);
|
|
791
|
+
return w.scheduler.queueJobWait(() => withLifecycle(w, "shutdownThreadApp", () => doShutdownThreadApp(w)), { jobName: "shutdownThreadApp" });
|
|
909
792
|
}
|
|
910
793
|
function doShutdownThreadApp(w) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
w,
|
|
915
|
-
createThreadMessage("shutdown", {}),
|
|
916
|
-
"shutdown_done",
|
|
917
|
-
SHUTDOWN_REPLY_TIMEOUT_MS
|
|
918
|
-
) : Promise.resolve()).then(() => waitForExit(w, SHUTDOWN_EXIT_TIMEOUT_MS));
|
|
794
|
+
setState(w, "shutdown");
|
|
795
|
+
clearInterval(w.heartbeatTimer);
|
|
796
|
+
return (w.child.connected ? sendAndWait(w, createThreadMessage("shutdown", {}), "shutdown_done", SHUTDOWN_REPLY_TIMEOUT_MS) : Promise.resolve()).then(() => waitForExit(w, SHUTDOWN_EXIT_TIMEOUT_MS));
|
|
919
797
|
}
|
|
920
798
|
function restartThreadApp(w) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
() => withLifecycle(
|
|
928
|
-
w,
|
|
929
|
-
"restartThreadApp",
|
|
930
|
-
() => doShutdownThreadApp(w).then(() => {
|
|
931
|
-
w.restartCount++;
|
|
932
|
-
spawnChild(w);
|
|
933
|
-
return waitForThreadReady(w);
|
|
934
|
-
}).then(() => doSetupThread(w)).then(() => doStartThreadApp(w))
|
|
935
|
-
),
|
|
936
|
-
{ jobName: "restartThreadApp" }
|
|
937
|
-
);
|
|
799
|
+
if (w.restartCount >= MAX_RESTARTS) return Promise.reject(/* @__PURE__ */ new Error(`Thread ${w.threadId} exceeded max restarts (${MAX_RESTARTS})`));
|
|
800
|
+
return w.scheduler.queueJobWait(() => withLifecycle(w, "restartThreadApp", () => doShutdownThreadApp(w).then(() => {
|
|
801
|
+
w.restartCount++;
|
|
802
|
+
spawnChild(w);
|
|
803
|
+
return waitForThreadReady(w);
|
|
804
|
+
}).then(() => doSetupThread(w)).then(() => doStartThreadApp(w))), { jobName: "restartThreadApp" });
|
|
938
805
|
}
|
|
939
806
|
function isThreadMessage(value) {
|
|
940
|
-
|
|
807
|
+
return isObject(value) && !!value.vrun_app_thread_message;
|
|
941
808
|
}
|
|
942
809
|
function createThreadMessage(type, msg) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
810
|
+
return {
|
|
811
|
+
type,
|
|
812
|
+
...msg,
|
|
813
|
+
vrun_app_thread_message: true
|
|
814
|
+
};
|
|
948
815
|
}
|
|
949
816
|
function parseRawLogLine(line) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
817
|
+
const trimmed = line.trimEnd();
|
|
818
|
+
const lower = trimmed.toLowerCase();
|
|
819
|
+
let level = "info";
|
|
820
|
+
if (lower.includes("error") || lower.includes("✖") || lower.includes("[error]")) level = "error";
|
|
821
|
+
else if (lower.includes("warn") || lower.includes("⚠")) level = "warn";
|
|
822
|
+
else if (lower.includes("debug") || lower.includes("⚙")) level = "debug";
|
|
823
|
+
return {
|
|
824
|
+
ts: Date.now(),
|
|
825
|
+
level,
|
|
826
|
+
text: trimmed
|
|
827
|
+
};
|
|
961
828
|
}
|
|
962
829
|
function isThreadExit(w) {
|
|
963
|
-
|
|
830
|
+
return w.child.exitCode !== null || w.child.signalCode !== null || !w.child.connected;
|
|
964
831
|
}
|
|
965
|
-
|
|
966
832
|
function createAppThread(definition) {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
});
|
|
1070
|
-
});
|
|
1071
|
-
break;
|
|
1072
|
-
}
|
|
1073
|
-
case "ping": {
|
|
1074
|
-
postMessage(createThreadMessage("pong", {}));
|
|
1075
|
-
break;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
});
|
|
1079
|
-
postMessage(
|
|
1080
|
-
createThreadMessage("ready", {
|
|
1081
|
-
pid: process.pid
|
|
1082
|
-
})
|
|
1083
|
-
);
|
|
1084
|
-
return instance;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
const TYPESCRIPT_EXTENSIONS = ["mts", "cts", "ts"];
|
|
1088
|
-
const JAVASCRIPT_EXTENSIONS = ["js", "mjs", "cjs"];
|
|
833
|
+
const scriptFile = definition.filePath;
|
|
834
|
+
assert.ok(scriptFile, `Failed to detect definition file path for: ${definition.name}`);
|
|
835
|
+
return defineApp({
|
|
836
|
+
name: definition.name,
|
|
837
|
+
description: definition.description,
|
|
838
|
+
props: {
|
|
839
|
+
...definition.props || {},
|
|
840
|
+
threads: {
|
|
841
|
+
type: Number,
|
|
842
|
+
description: "Amount of threads",
|
|
843
|
+
default: () => 1,
|
|
844
|
+
parser: (v) => parseInt(v)
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
logger: false,
|
|
848
|
+
setup(props) {
|
|
849
|
+
const { threads = 1, ...threadProps } = props;
|
|
850
|
+
assert.greaterThan(threads, 0, "threads expected to be greater than 0");
|
|
851
|
+
const managedThreads = Array.from({ length: threads }, (_, i) => initThread(i + 1, scriptFile, threadProps));
|
|
852
|
+
if (props.__inheritIO !== false) for (const w of managedThreads) {
|
|
853
|
+
const childLogger = createAppLogger({
|
|
854
|
+
...definition,
|
|
855
|
+
name: `${definition.name}.${w.threadId}`
|
|
856
|
+
});
|
|
857
|
+
w.eventBus.on("log", (entry) => {
|
|
858
|
+
childLogger[entry.level](entry.text);
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
return Promise.all(managedThreads.map((w) => waitForThreadReady(w))).then(() => Promise.all(managedThreads.map((w) => setupThreadApp(w)))).then(() => ({ managedThreads }));
|
|
862
|
+
},
|
|
863
|
+
entry() {
|
|
864
|
+
return Promise.all(this.managedThreads.map((w) => startThreadApp(w))).then(noop);
|
|
865
|
+
},
|
|
866
|
+
stop() {
|
|
867
|
+
return Promise.all(this.managedThreads.map((w) => stopThreadApp(w))).then(noop);
|
|
868
|
+
},
|
|
869
|
+
shutdown() {
|
|
870
|
+
return Promise.all(this.managedThreads.map((w) => shutdownThreadApp(w))).then(() => {
|
|
871
|
+
this.managedThreads = [];
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
function createAppThreadInstance({ definition, parentPort, threadId, onShutdown = noop }) {
|
|
877
|
+
definition = { ...definition };
|
|
878
|
+
definition.name += "." + threadId;
|
|
879
|
+
const instance = createAppInstance(definition);
|
|
880
|
+
const log = instance.logger;
|
|
881
|
+
const postMessage = (message, cb) => {
|
|
882
|
+
log.debug(formatLogEvent("child.ipc.send", message));
|
|
883
|
+
parentPort.postMessage(message, cb);
|
|
884
|
+
};
|
|
885
|
+
instance.eventBus.on("state", (newState) => {
|
|
886
|
+
postMessage(createThreadMessage("app-state", { state: newState }));
|
|
887
|
+
});
|
|
888
|
+
parentPort.on("message", async (message) => {
|
|
889
|
+
if (!isThreadMessage(message)) return;
|
|
890
|
+
log.debug(formatLogEvent("child.ipc.recv", message));
|
|
891
|
+
switch (message.type) {
|
|
892
|
+
case "setup": {
|
|
893
|
+
const result = await setupApp(instance, message.props);
|
|
894
|
+
postMessage(createThreadMessage("setup_done", { result }));
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case "start": {
|
|
898
|
+
const result = await runApp(instance);
|
|
899
|
+
postMessage(createThreadMessage("start_done", { result }));
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
case "stop": {
|
|
903
|
+
const result = await stopApp(instance);
|
|
904
|
+
postMessage(createThreadMessage("stop_done", { result }));
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
case "shutdown": {
|
|
908
|
+
const result = await shutdownApp(instance);
|
|
909
|
+
postMessage(createThreadMessage("shutdown_done", { result }), () => {
|
|
910
|
+
setImmediate(() => {
|
|
911
|
+
catchError(onShutdown);
|
|
912
|
+
if (isFunction(parentPort.close)) parentPort.close();
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
case "ping":
|
|
918
|
+
postMessage(createThreadMessage("pong", {}));
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
postMessage(createThreadMessage("ready", { pid: process.pid }));
|
|
923
|
+
return instance;
|
|
924
|
+
}
|
|
925
|
+
const TYPESCRIPT_EXTENSIONS = [
|
|
926
|
+
"mts",
|
|
927
|
+
"cts",
|
|
928
|
+
"ts"
|
|
929
|
+
];
|
|
930
|
+
const JAVASCRIPT_EXTENSIONS = [
|
|
931
|
+
"js",
|
|
932
|
+
"mjs",
|
|
933
|
+
"cjs"
|
|
934
|
+
];
|
|
1089
935
|
const ALL_EXTENSIONS = [
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
936
|
+
...TYPESCRIPT_EXTENSIONS,
|
|
937
|
+
...JAVASCRIPT_EXTENSIONS,
|
|
938
|
+
"json"
|
|
1093
939
|
];
|
|
1094
940
|
function createProject(projectPath) {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
};
|
|
1114
|
-
project.autoImports = project.autoImports.map((v) => projectResolveFile(project, v)).filter(isString);
|
|
1115
|
-
return project;
|
|
941
|
+
const packageJsonPath = locateNearestFile(projectPath, "package.json");
|
|
942
|
+
assert.ok(packageJsonPath, "package.json not found in path: " + path.dirname(projectPath));
|
|
943
|
+
const packageJson = importJson(packageJsonPath);
|
|
944
|
+
const projectLocation = path.dirname(packageJsonPath);
|
|
945
|
+
const project = {
|
|
946
|
+
path: projectLocation,
|
|
947
|
+
name: packageJson.name || path.basename(projectPath),
|
|
948
|
+
description: packageJson.description || "",
|
|
949
|
+
version: packageJson.version || "0.0.0",
|
|
950
|
+
tsconfigPath: locateNearestFile(projectLocation, "tsconfig.json"),
|
|
951
|
+
dotenvPath: locateNearestFile(projectLocation, ".env"),
|
|
952
|
+
autoImports: ["inversify.config"],
|
|
953
|
+
importLookupPaths: createImportLookupPaths(projectLocation),
|
|
954
|
+
allowTs: false,
|
|
955
|
+
packageJson
|
|
956
|
+
};
|
|
957
|
+
project.autoImports = project.autoImports.map((v) => projectResolveFile(project, v)).filter(isString);
|
|
958
|
+
return project;
|
|
1116
959
|
}
|
|
1117
960
|
function projectImportFile(project, filePath) {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
);
|
|
1126
|
-
}
|
|
1127
|
-
const ext = getFileExtension(existedFile, false);
|
|
1128
|
-
if (ext === "json") {
|
|
1129
|
-
const data = importJson(existedFile);
|
|
1130
|
-
return Promise.resolve({ default: data });
|
|
1131
|
-
}
|
|
1132
|
-
return import(existedFile);
|
|
961
|
+
const existedFile = projectResolveFile(project, filePath);
|
|
962
|
+
if (!existedFile) throw new Error(`Cannot resolve import for path.\n Path:\n ${filePath}\n Available extensions: ${ALL_EXTENSIONS.join(", ")}`);
|
|
963
|
+
if (getFileExtension(existedFile, false) === "json") {
|
|
964
|
+
const data = importJson(existedFile);
|
|
965
|
+
return Promise.resolve({ default: data });
|
|
966
|
+
}
|
|
967
|
+
return import(existedFile);
|
|
1133
968
|
}
|
|
1134
969
|
function projectResolveFile(project, filePath) {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
}
|
|
1159
|
-
async function projectAutoImport(project) {
|
|
1160
|
-
const result = [];
|
|
1161
|
-
const filePaths = project.autoImports.map(
|
|
1162
|
-
(filePath) => projectResolveFile(project, filePath)
|
|
1163
|
-
);
|
|
1164
|
-
for (const filePath of filePaths) {
|
|
1165
|
-
if (filePath) {
|
|
1166
|
-
await projectImportFile(project, filePath);
|
|
1167
|
-
result.push(filePath.replace(project.path, ""));
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
return result;
|
|
970
|
+
const pathVariants = project.importLookupPaths.map((dir) => path.resolve(dir, filePath));
|
|
971
|
+
for (const filePath of pathVariants) {
|
|
972
|
+
const existedPath = (ALL_EXTENSIONS.includes(getFileExtension(filePath, false)) ? [filePath] : [...ALL_EXTENSIONS.map((ext) => `${filePath}.${ext}`)]).find((v) => fs.existsSync(v));
|
|
973
|
+
if (existedPath) return existedPath;
|
|
974
|
+
}
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
function projectLoadEnv(project) {
|
|
978
|
+
if (!project.dotenvPath) return Promise.resolve(false);
|
|
979
|
+
dotenv.config({
|
|
980
|
+
path: project.dotenvPath,
|
|
981
|
+
quiet: true
|
|
982
|
+
});
|
|
983
|
+
return Promise.resolve(true);
|
|
984
|
+
}
|
|
985
|
+
function projectAutoImport(project) {
|
|
986
|
+
const result = [];
|
|
987
|
+
return asyncForEach(project.autoImports.map((filePath) => projectResolveFile(project, filePath)), (filePath) => {
|
|
988
|
+
if (!filePath) return;
|
|
989
|
+
return projectImportFile(project, filePath).then(() => {
|
|
990
|
+
result.push(filePath.replace(project.path, ""));
|
|
991
|
+
});
|
|
992
|
+
}, { concurrency: 4 }).then(() => result);
|
|
1171
993
|
}
|
|
1172
994
|
function projectPrintInfo(project) {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
if (project.dotenvPath) {
|
|
1181
|
-
tags.push(getColor("bgYellow")(` ${path.basename(project.dotenvPath)} `));
|
|
1182
|
-
}
|
|
1183
|
-
if (project.autoImports.length) {
|
|
1184
|
-
tags.push(
|
|
1185
|
-
getColor("bgGreen")(
|
|
1186
|
-
` IMPORT: ${project.autoImports.map((v) => path.relative(project.path, v)).join(" | ")} `
|
|
1187
|
-
)
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
log.info(
|
|
1191
|
-
"%s (%s)%s",
|
|
1192
|
-
project.name,
|
|
1193
|
-
project.version,
|
|
1194
|
-
tags.length ? ` ${tags.join(" ")}` : ""
|
|
1195
|
-
);
|
|
995
|
+
const tags = [];
|
|
996
|
+
if (project.allowTs) tags.push(getColor("bgBlue")(" TS "));
|
|
997
|
+
if (CONFIG.WATCH_MODE) tags.push(getColor("bgMagenta")(" WATCH "));
|
|
998
|
+
if (project.dotenvPath) tags.push(getColor("bgYellow")(` ${path.basename(project.dotenvPath)} `));
|
|
999
|
+
if (project.autoImports.length) tags.push(getColor("bgGreen")(` IMPORT: ${project.autoImports.map((v) => path.relative(project.path, v)).join(" | ")} `));
|
|
1000
|
+
log.info("%s (%s)%s", project.name, project.version, tags.length ? ` ${tags.join(" ")}` : "");
|
|
1196
1001
|
}
|
|
1197
1002
|
function createImportLookupPaths(projectPath, appRoot = CONFIG.APP_ROOT) {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
result.push(path.resolve(projectPath));
|
|
1205
|
-
return uniq(result);
|
|
1003
|
+
const cwd = process.cwd();
|
|
1004
|
+
const result = [];
|
|
1005
|
+
if (appRoot) result.push(path.resolve(cwd, appRoot));
|
|
1006
|
+
result.push(path.resolve(cwd));
|
|
1007
|
+
result.push(path.resolve(projectPath));
|
|
1008
|
+
return uniq(result);
|
|
1206
1009
|
}
|
|
1207
1010
|
function locateNearestFile(location, fileName) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
return null;
|
|
1011
|
+
let currentDir = path.extname(location) ? path.dirname(path.resolve(location)) : path.resolve(location);
|
|
1012
|
+
while (currentDir) {
|
|
1013
|
+
const filePath = path.join(currentDir, fileName);
|
|
1014
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
1015
|
+
const parentDir = path.dirname(currentDir);
|
|
1016
|
+
if (parentDir === currentDir) break;
|
|
1017
|
+
currentDir = parentDir;
|
|
1018
|
+
}
|
|
1019
|
+
return null;
|
|
1219
1020
|
}
|
|
1220
1021
|
function importJson(filePath) {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
throw new Error(`JSON file not found.
|
|
1226
|
-
Path:
|
|
1227
|
-
${filePath}`);
|
|
1228
|
-
}
|
|
1229
|
-
const data = fs.readFileSync(filePath, { encoding: "utf-8" });
|
|
1230
|
-
return JSON.parse(data);
|
|
1022
|
+
if (!filePath.endsWith(".json")) filePath += ".json";
|
|
1023
|
+
if (!fs.existsSync(filePath)) throw new Error(`JSON file not found.\n Path:\n ${filePath}`);
|
|
1024
|
+
const data = fs.readFileSync(filePath, { encoding: "utf-8" });
|
|
1025
|
+
return JSON.parse(data);
|
|
1231
1026
|
}
|
|
1232
|
-
|
|
1233
1027
|
const LOG_BUFFER_CAP = 2e3;
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
}
|
|
1028
|
+
var RingBuffer = class {
|
|
1029
|
+
cap;
|
|
1030
|
+
buf = [];
|
|
1031
|
+
start = 0;
|
|
1032
|
+
constructor(cap) {
|
|
1033
|
+
this.cap = cap;
|
|
1034
|
+
}
|
|
1035
|
+
push(item) {
|
|
1036
|
+
if (this.buf.length < this.cap) this.buf.push(item);
|
|
1037
|
+
else {
|
|
1038
|
+
this.buf[this.start] = item;
|
|
1039
|
+
this.start = (this.start + 1) % this.cap;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
size() {
|
|
1043
|
+
return this.buf.length;
|
|
1044
|
+
}
|
|
1045
|
+
toArray() {
|
|
1046
|
+
if (this.start === 0) return this.buf.slice();
|
|
1047
|
+
return this.buf.slice(this.start).concat(this.buf.slice(0, this.start));
|
|
1048
|
+
}
|
|
1049
|
+
clear() {
|
|
1050
|
+
this.buf = [];
|
|
1051
|
+
this.start = 0;
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1260
1054
|
const noopHandlers = {
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1055
|
+
stop: () => Promise.resolve(),
|
|
1056
|
+
start: () => Promise.resolve(),
|
|
1057
|
+
restart: () => Promise.resolve()
|
|
1264
1058
|
};
|
|
1265
1059
|
function createTuiStore() {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
return buf ? buf.toArray() : [];
|
|
1380
|
-
},
|
|
1381
|
-
clearLogs(nodeId) {
|
|
1382
|
-
logs.get(nodeId)?.clear();
|
|
1383
|
-
notify();
|
|
1384
|
-
},
|
|
1385
|
-
subscribe(fn) {
|
|
1386
|
-
emitter.on("change", fn);
|
|
1387
|
-
return () => emitter.off("change", fn);
|
|
1388
|
-
}
|
|
1389
|
-
};
|
|
1390
|
-
return store;
|
|
1060
|
+
const emitter = new EventEmitter();
|
|
1061
|
+
emitter.setMaxListeners(0);
|
|
1062
|
+
const logs = /* @__PURE__ */ new Map();
|
|
1063
|
+
const apps = [];
|
|
1064
|
+
const bufferOf = (id) => {
|
|
1065
|
+
let buf = logs.get(id);
|
|
1066
|
+
if (!buf) {
|
|
1067
|
+
buf = new RingBuffer(LOG_BUFFER_CAP);
|
|
1068
|
+
logs.set(id, buf);
|
|
1069
|
+
}
|
|
1070
|
+
return buf;
|
|
1071
|
+
};
|
|
1072
|
+
const notify = () => {
|
|
1073
|
+
emitter.emit("change");
|
|
1074
|
+
};
|
|
1075
|
+
const isSelectedFeed = (nodeId) => {
|
|
1076
|
+
if (!store.selectedId) return false;
|
|
1077
|
+
if (store.selectedId === nodeId) return true;
|
|
1078
|
+
for (const app of apps) {
|
|
1079
|
+
if (app.id !== store.selectedId) continue;
|
|
1080
|
+
return !!app.threads?.some((t) => t.id === nodeId);
|
|
1081
|
+
}
|
|
1082
|
+
return false;
|
|
1083
|
+
};
|
|
1084
|
+
const store = {
|
|
1085
|
+
apps,
|
|
1086
|
+
logs,
|
|
1087
|
+
system: null,
|
|
1088
|
+
selectedId: null,
|
|
1089
|
+
filter: "info",
|
|
1090
|
+
logScroll: 0,
|
|
1091
|
+
handlers: noopHandlers,
|
|
1092
|
+
pushLog(nodeId, entry) {
|
|
1093
|
+
bufferOf(nodeId).push(entry);
|
|
1094
|
+
if (store.logScroll > 0 && isSelectedFeed(nodeId) && passesFilter(entry, store.filter)) store.logScroll += 1;
|
|
1095
|
+
notify();
|
|
1096
|
+
},
|
|
1097
|
+
setState(nodeId, state) {
|
|
1098
|
+
for (const app of apps) {
|
|
1099
|
+
if (app.id === nodeId) {
|
|
1100
|
+
app.state = state;
|
|
1101
|
+
notify();
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
if (app.threads) {
|
|
1105
|
+
for (const t of app.threads) if (t.id === nodeId) {
|
|
1106
|
+
t.state = state;
|
|
1107
|
+
notify();
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
setSelected(nodeId) {
|
|
1114
|
+
store.selectedId = nodeId;
|
|
1115
|
+
store.logScroll = 0;
|
|
1116
|
+
notify();
|
|
1117
|
+
},
|
|
1118
|
+
setFilter(filter) {
|
|
1119
|
+
store.filter = filter;
|
|
1120
|
+
store.logScroll = 0;
|
|
1121
|
+
notify();
|
|
1122
|
+
},
|
|
1123
|
+
setLogScroll(offset) {
|
|
1124
|
+
store.logScroll = Math.max(0, offset);
|
|
1125
|
+
notify();
|
|
1126
|
+
},
|
|
1127
|
+
setSystem(entry) {
|
|
1128
|
+
store.system = entry;
|
|
1129
|
+
notify();
|
|
1130
|
+
},
|
|
1131
|
+
toggleExpand(appId) {
|
|
1132
|
+
for (const app of apps) if (app.id === appId) {
|
|
1133
|
+
app.expanded = !app.expanded;
|
|
1134
|
+
notify();
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
addApp(node) {
|
|
1139
|
+
apps.push(node);
|
|
1140
|
+
if (!store.selectedId) store.selectedId = node.id;
|
|
1141
|
+
notify();
|
|
1142
|
+
},
|
|
1143
|
+
setThreads(appId, threads) {
|
|
1144
|
+
for (const app of apps) if (app.id === appId) {
|
|
1145
|
+
app.threads = threads;
|
|
1146
|
+
notify();
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
setPid(nodeId, pid) {
|
|
1151
|
+
for (const app of apps) if (app.threads) {
|
|
1152
|
+
for (const t of app.threads) if (t.id === nodeId) {
|
|
1153
|
+
t.pid = pid;
|
|
1154
|
+
notify();
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
getLogs(nodeId) {
|
|
1160
|
+
const buf = logs.get(nodeId);
|
|
1161
|
+
return buf ? buf.toArray() : [];
|
|
1162
|
+
},
|
|
1163
|
+
clearLogs(nodeId) {
|
|
1164
|
+
logs.get(nodeId)?.clear();
|
|
1165
|
+
notify();
|
|
1166
|
+
},
|
|
1167
|
+
subscribe(fn) {
|
|
1168
|
+
emitter.on("change", fn);
|
|
1169
|
+
return () => emitter.off("change", fn);
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
return store;
|
|
1391
1173
|
}
|
|
1392
1174
|
const LEVEL_RANK = {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1175
|
+
debug: 0,
|
|
1176
|
+
log: 1,
|
|
1177
|
+
info: 2,
|
|
1178
|
+
warn: 3,
|
|
1179
|
+
error: 4
|
|
1398
1180
|
};
|
|
1399
1181
|
const FILTER_THRESHOLD = {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1182
|
+
all: 0,
|
|
1183
|
+
info: LEVEL_RANK.info,
|
|
1184
|
+
warn: LEVEL_RANK.warn,
|
|
1185
|
+
error: LEVEL_RANK.error
|
|
1404
1186
|
};
|
|
1405
1187
|
function passesFilter(entry, filter) {
|
|
1406
|
-
|
|
1188
|
+
return LEVEL_RANK[entry.level] >= FILTER_THRESHOLD[filter];
|
|
1407
1189
|
}
|
|
1408
1190
|
function getSelectedFilteredCount(store) {
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
}
|
|
1420
|
-
return count;
|
|
1421
|
-
}
|
|
1422
|
-
return store.getLogs(store.selectedId).filter(passes).length;
|
|
1191
|
+
if (!store.selectedId) return 0;
|
|
1192
|
+
const passes = (e) => passesFilter(e, store.filter);
|
|
1193
|
+
for (const app of store.apps) {
|
|
1194
|
+
if (app.id !== store.selectedId) continue;
|
|
1195
|
+
if (!app.threads || app.threads.length === 0) return store.getLogs(app.id).filter(passes).length;
|
|
1196
|
+
let count = 0;
|
|
1197
|
+
for (const t of app.threads) for (const e of store.getLogs(t.id)) if (passes(e)) count += 1;
|
|
1198
|
+
return count;
|
|
1199
|
+
}
|
|
1200
|
+
return store.getLogs(store.selectedId).filter(passes).length;
|
|
1423
1201
|
}
|
|
1424
1202
|
function nextFilter(filter) {
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
return "error";
|
|
1432
|
-
case "error":
|
|
1433
|
-
return "all";
|
|
1434
|
-
}
|
|
1203
|
+
switch (filter) {
|
|
1204
|
+
case "all": return "info";
|
|
1205
|
+
case "info": return "warn";
|
|
1206
|
+
case "warn": return "error";
|
|
1207
|
+
case "error": return "all";
|
|
1208
|
+
}
|
|
1435
1209
|
}
|
|
1436
1210
|
function flattenVisibleTree(apps) {
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
}
|
|
1444
|
-
return out;
|
|
1211
|
+
const out = [];
|
|
1212
|
+
for (const app of apps) {
|
|
1213
|
+
out.push(app);
|
|
1214
|
+
if (app.expanded && app.threads) for (const t of app.threads) out.push(t);
|
|
1215
|
+
}
|
|
1216
|
+
return out;
|
|
1445
1217
|
}
|
|
1446
|
-
|
|
1447
1218
|
const TuiStoreContext = createContext(null);
|
|
1448
1219
|
function useTuiStore() {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1220
|
+
const store = useContext(TuiStoreContext);
|
|
1221
|
+
if (!store) throw new Error("TuiStoreContext is not provided");
|
|
1222
|
+
return store;
|
|
1452
1223
|
}
|
|
1453
1224
|
function useStoreSnapshot() {
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1225
|
+
const store = useTuiStore();
|
|
1226
|
+
const [version, setVersion] = useState(0);
|
|
1227
|
+
useEffect(() => {
|
|
1228
|
+
return store.subscribe(() => setVersion((v) => v + 1));
|
|
1229
|
+
}, [store]);
|
|
1230
|
+
return version;
|
|
1460
1231
|
}
|
|
1461
|
-
|
|
1462
1232
|
const STATE_COLORS = {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1233
|
+
"in-run": "yellow",
|
|
1234
|
+
run: "green",
|
|
1235
|
+
"in-stop": "yellow",
|
|
1236
|
+
"in-setup": "yellow",
|
|
1237
|
+
setup: "yellow",
|
|
1238
|
+
ready: "gray",
|
|
1239
|
+
"in-shutdown": "yellow",
|
|
1240
|
+
init: "gray",
|
|
1241
|
+
shutdown: "gray",
|
|
1242
|
+
stop: "gray",
|
|
1243
|
+
error: "red"
|
|
1474
1244
|
};
|
|
1475
1245
|
function isApp(n) {
|
|
1476
|
-
|
|
1246
|
+
return n.kind === "app";
|
|
1477
1247
|
}
|
|
1478
1248
|
function rowLength(n) {
|
|
1479
|
-
|
|
1480
|
-
|
|
1249
|
+
if (n.kind === "app") return 5 + (n.name?.length ?? 0);
|
|
1250
|
+
return 5 + `PID ${n.pid}`.length;
|
|
1481
1251
|
}
|
|
1482
1252
|
function AppList() {
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1253
|
+
useStoreSnapshot();
|
|
1254
|
+
const store = useTuiStore();
|
|
1255
|
+
const nodes = flattenVisibleTree(store.apps);
|
|
1256
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1257
|
+
flexDirection: "column",
|
|
1258
|
+
width: nodes.reduce((m, n) => Math.max(m, rowLength(n)), 4) + 4,
|
|
1259
|
+
flexShrink: 0,
|
|
1260
|
+
borderStyle: "single",
|
|
1261
|
+
paddingX: 1,
|
|
1262
|
+
overflow: "hidden",
|
|
1263
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1264
|
+
bold: true,
|
|
1265
|
+
children: "Apps"
|
|
1266
|
+
}), nodes.map((node) => {
|
|
1267
|
+
const selected = node.id === store.selectedId;
|
|
1268
|
+
const color = STATE_COLORS[node.state];
|
|
1269
|
+
const marker = selected ? "▌" : " ";
|
|
1270
|
+
if (isApp(node)) {
|
|
1271
|
+
const caret = node.threads && node.threads.length ? node.expanded ? "▾" : "▸" : " ";
|
|
1272
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
1273
|
+
wrap: "truncate-end",
|
|
1274
|
+
bold: selected,
|
|
1275
|
+
children: [
|
|
1276
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1277
|
+
color: "cyan",
|
|
1278
|
+
children: marker
|
|
1279
|
+
}),
|
|
1280
|
+
caret,
|
|
1281
|
+
" ",
|
|
1282
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1283
|
+
color,
|
|
1284
|
+
children: "●"
|
|
1285
|
+
}),
|
|
1286
|
+
" ",
|
|
1287
|
+
node.name
|
|
1288
|
+
]
|
|
1289
|
+
}, node.id);
|
|
1290
|
+
}
|
|
1291
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
1292
|
+
wrap: "truncate-end",
|
|
1293
|
+
bold: selected,
|
|
1294
|
+
children: [
|
|
1295
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1296
|
+
color: "cyan",
|
|
1297
|
+
children: marker
|
|
1298
|
+
}),
|
|
1299
|
+
" ",
|
|
1300
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1301
|
+
color,
|
|
1302
|
+
children: "●"
|
|
1303
|
+
}),
|
|
1304
|
+
" ",
|
|
1305
|
+
node.name
|
|
1306
|
+
]
|
|
1307
|
+
}, node.id);
|
|
1308
|
+
})]
|
|
1309
|
+
});
|
|
1528
1310
|
}
|
|
1529
|
-
|
|
1530
1311
|
const LEVEL_COLORS = {
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1312
|
+
debug: "gray",
|
|
1313
|
+
log: "white",
|
|
1314
|
+
info: "cyan",
|
|
1315
|
+
warn: "yellow",
|
|
1316
|
+
error: "red"
|
|
1536
1317
|
};
|
|
1537
1318
|
function formatTime(ts) {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1319
|
+
const d = new Date(ts);
|
|
1320
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
1321
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
1541
1322
|
}
|
|
1542
1323
|
function describeSelection(store) {
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
};
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
return null;
|
|
1324
|
+
if (!store.selectedId) return null;
|
|
1325
|
+
for (const app of store.apps) {
|
|
1326
|
+
if (app.id === store.selectedId) {
|
|
1327
|
+
const threads = app.threads ?? [];
|
|
1328
|
+
const pids = Array.from(new Set(threads.map((t) => t.pid).filter((p) => p > 0)));
|
|
1329
|
+
return {
|
|
1330
|
+
appName: app.name,
|
|
1331
|
+
processCount: threads.length,
|
|
1332
|
+
pids,
|
|
1333
|
+
states: threads.map((v) => v.state),
|
|
1334
|
+
showProcessTag: threads.length > 1
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
if (app.threads) {
|
|
1338
|
+
for (const t of app.threads) if (t.id === store.selectedId) return {
|
|
1339
|
+
appName: app.name,
|
|
1340
|
+
pid: t.pid,
|
|
1341
|
+
pids: t.pid > 0 ? [t.pid] : [],
|
|
1342
|
+
state: t.state,
|
|
1343
|
+
states: [t.state],
|
|
1344
|
+
processCount: app.threads.length,
|
|
1345
|
+
showProcessTag: false
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return null;
|
|
1575
1350
|
}
|
|
1576
1351
|
function formatPids(pids) {
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1352
|
+
if (pids.length === 0) return "?";
|
|
1353
|
+
if (pids.length === 1) return String(pids[0]);
|
|
1354
|
+
return pids.join(", ");
|
|
1580
1355
|
}
|
|
1581
1356
|
function gatherEntries(store, info) {
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
}
|
|
1596
|
-
merged.sort((a, b) => a.ts - b.ts);
|
|
1597
|
-
return merged;
|
|
1598
|
-
}
|
|
1599
|
-
return store.getLogs(store.selectedId);
|
|
1357
|
+
if (!store.selectedId) return [];
|
|
1358
|
+
for (const app of store.apps) {
|
|
1359
|
+
if (app.id !== store.selectedId) continue;
|
|
1360
|
+
if (!app.threads || app.threads.length === 0) return store.getLogs(app.id);
|
|
1361
|
+
const merged = [];
|
|
1362
|
+
for (const t of app.threads) for (const entry of store.getLogs(t.id)) merged.push(info.showProcessTag ? {
|
|
1363
|
+
...entry,
|
|
1364
|
+
text: `[${t.pid}] ${entry.text}`
|
|
1365
|
+
} : entry);
|
|
1366
|
+
merged.sort((a, b) => a.ts - b.ts);
|
|
1367
|
+
return merged;
|
|
1368
|
+
}
|
|
1369
|
+
return store.getLogs(store.selectedId);
|
|
1600
1370
|
}
|
|
1601
1371
|
function LogView() {
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1372
|
+
useStoreSnapshot();
|
|
1373
|
+
const store = useTuiStore();
|
|
1374
|
+
const { stdout } = useStdout();
|
|
1375
|
+
const rows = stdout?.rows ?? 24;
|
|
1376
|
+
const visibleRows = Math.max(1, rows - 5);
|
|
1377
|
+
const info = describeSelection(store);
|
|
1378
|
+
let entries = [];
|
|
1379
|
+
let scrollLabel = "";
|
|
1380
|
+
if (info) {
|
|
1381
|
+
const filtered = gatherEntries(store, info).filter((e) => passesFilter(e, store.filter));
|
|
1382
|
+
const total = filtered.length;
|
|
1383
|
+
const maxOffset = Math.max(0, total - visibleRows);
|
|
1384
|
+
const offset = Math.min(store.logScroll, maxOffset);
|
|
1385
|
+
const end = total - offset;
|
|
1386
|
+
const start = Math.max(0, end - visibleRows);
|
|
1387
|
+
entries = filtered.slice(start, end);
|
|
1388
|
+
if (offset > 0) scrollLabel = ` · scrolled +${offset}`;
|
|
1389
|
+
}
|
|
1390
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1391
|
+
flexDirection: "column",
|
|
1392
|
+
flexGrow: 1,
|
|
1393
|
+
flexShrink: 1,
|
|
1394
|
+
borderStyle: "single",
|
|
1395
|
+
paddingX: 1,
|
|
1396
|
+
overflow: "hidden",
|
|
1397
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
1398
|
+
flexShrink: 0,
|
|
1399
|
+
flexDirection: "column",
|
|
1400
|
+
children: info ? /* @__PURE__ */ jsxs(Box, {
|
|
1401
|
+
justifyContent: "space-between",
|
|
1402
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1403
|
+
bold: true,
|
|
1404
|
+
wrap: "truncate-end",
|
|
1405
|
+
children: [
|
|
1406
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1407
|
+
color: "cyan",
|
|
1408
|
+
children: info.appName
|
|
1409
|
+
}),
|
|
1410
|
+
info.processCount > 1 && info.pid == null ? /* @__PURE__ */ jsxs(Text, {
|
|
1411
|
+
color: "gray",
|
|
1412
|
+
children: [
|
|
1413
|
+
" (",
|
|
1414
|
+
info.processCount,
|
|
1415
|
+
" processes)"
|
|
1416
|
+
]
|
|
1417
|
+
}) : null,
|
|
1418
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1419
|
+
color: "gray",
|
|
1420
|
+
children: [
|
|
1421
|
+
" ",
|
|
1422
|
+
"· filter: ",
|
|
1423
|
+
store.filter,
|
|
1424
|
+
scrollLabel
|
|
1425
|
+
]
|
|
1426
|
+
})
|
|
1427
|
+
]
|
|
1428
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
1429
|
+
bold: true,
|
|
1430
|
+
wrap: "truncate-end",
|
|
1431
|
+
children: [
|
|
1432
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1433
|
+
color: "gray",
|
|
1434
|
+
children: ["STATE ", info.states.join(", ")]
|
|
1435
|
+
}),
|
|
1436
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1437
|
+
color: "gray",
|
|
1438
|
+
children: " · "
|
|
1439
|
+
}),
|
|
1440
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1441
|
+
color: "magenta",
|
|
1442
|
+
children: ["PID ", formatPids(info.pids)]
|
|
1443
|
+
})
|
|
1444
|
+
]
|
|
1445
|
+
})]
|
|
1446
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
1447
|
+
bold: true,
|
|
1448
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1449
|
+
color: "gray",
|
|
1450
|
+
children: " (no selection)"
|
|
1451
|
+
})
|
|
1452
|
+
})
|
|
1453
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
1454
|
+
flexDirection: "column",
|
|
1455
|
+
flexGrow: 1,
|
|
1456
|
+
flexShrink: 1,
|
|
1457
|
+
overflow: "hidden",
|
|
1458
|
+
children: entries.map((entry, idx) => /* @__PURE__ */ jsxs(Text, {
|
|
1459
|
+
wrap: "wrap",
|
|
1460
|
+
children: [
|
|
1461
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1462
|
+
color: "gray",
|
|
1463
|
+
children: [formatTime(entry.ts), " "]
|
|
1464
|
+
}),
|
|
1465
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1466
|
+
color: LEVEL_COLORS[entry.level],
|
|
1467
|
+
children: entry.level.toUpperCase().padEnd(5)
|
|
1468
|
+
}),
|
|
1469
|
+
" ",
|
|
1470
|
+
entry.text
|
|
1471
|
+
]
|
|
1472
|
+
}, idx))
|
|
1473
|
+
})]
|
|
1474
|
+
});
|
|
1670
1475
|
}
|
|
1671
|
-
|
|
1672
1476
|
function StatusBar() {
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1477
|
+
useStoreSnapshot();
|
|
1478
|
+
const sys = useTuiStore().system;
|
|
1479
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1480
|
+
flexDirection: "row",
|
|
1481
|
+
justifyContent: "space-between",
|
|
1482
|
+
paddingX: 1,
|
|
1483
|
+
flexShrink: 0,
|
|
1484
|
+
height: 1,
|
|
1485
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
1486
|
+
flexShrink: 1,
|
|
1487
|
+
overflow: "hidden",
|
|
1488
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1489
|
+
wrap: "truncate-end",
|
|
1490
|
+
children: sys ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1491
|
+
color: "gray",
|
|
1492
|
+
children: "[system] "
|
|
1493
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1494
|
+
color: sys.level === "error" ? "red" : "white",
|
|
1495
|
+
children: sys.text
|
|
1496
|
+
})] }) : /* @__PURE__ */ jsx(Text, {
|
|
1497
|
+
color: "gray",
|
|
1498
|
+
children: "[system] ready"
|
|
1499
|
+
})
|
|
1500
|
+
})
|
|
1501
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
1502
|
+
flexShrink: 0,
|
|
1503
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1504
|
+
color: "gray",
|
|
1505
|
+
children: "↑/↓ · →/← · PgUp/PgDn · s · ⇧S · r · f · q"
|
|
1506
|
+
})
|
|
1507
|
+
})]
|
|
1508
|
+
});
|
|
1693
1509
|
}
|
|
1694
|
-
|
|
1695
1510
|
function findNodeAndApp(store, id) {
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1511
|
+
for (const app of store.apps) {
|
|
1512
|
+
if (app.id === id) return {
|
|
1513
|
+
node: app,
|
|
1514
|
+
app
|
|
1515
|
+
};
|
|
1516
|
+
if (app.threads) {
|
|
1517
|
+
for (const t of app.threads) if (t.id === id) return {
|
|
1518
|
+
node: t,
|
|
1519
|
+
app
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return null;
|
|
1705
1524
|
}
|
|
1706
1525
|
function InnerTui({ store, onExit }) {
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
/* @__PURE__ */ jsx(StatusBar, {})
|
|
1794
|
-
] });
|
|
1526
|
+
useStoreSnapshot();
|
|
1527
|
+
const { stdout } = useStdout();
|
|
1528
|
+
const rows = stdout?.rows ?? 24;
|
|
1529
|
+
useInput((input, key) => {
|
|
1530
|
+
const visible = flattenVisibleTree(store.apps);
|
|
1531
|
+
if (visible.length === 0) return;
|
|
1532
|
+
const currentIdx = visible.findIndex((n) => n.id === store.selectedId);
|
|
1533
|
+
const idx = currentIdx < 0 ? 0 : currentIdx;
|
|
1534
|
+
const selected = visible[idx];
|
|
1535
|
+
const visibleLogRows = Math.max(1, rows - 5);
|
|
1536
|
+
const halfPage = Math.max(1, Math.floor(visibleLogRows / 2));
|
|
1537
|
+
if (key.upArrow && key.shift) {
|
|
1538
|
+
const maxOffset = Math.max(0, getSelectedFilteredCount(store) - visibleLogRows);
|
|
1539
|
+
store.setLogScroll(Math.min(maxOffset, store.logScroll + halfPage));
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
if (key.downArrow && key.shift) {
|
|
1543
|
+
store.setLogScroll(Math.max(0, store.logScroll - halfPage));
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
if (key.home) {
|
|
1547
|
+
const total = getSelectedFilteredCount(store);
|
|
1548
|
+
store.setLogScroll(Math.max(0, total - visibleLogRows));
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
if (key.end) {
|
|
1552
|
+
store.setLogScroll(0);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
if (key.upArrow) {
|
|
1556
|
+
const next = visible[Math.max(0, idx - 1)];
|
|
1557
|
+
if (next) store.setSelected(next.id);
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
if (key.downArrow) {
|
|
1561
|
+
const next = visible[Math.min(visible.length - 1, idx + 1)];
|
|
1562
|
+
if (next) store.setSelected(next.id);
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
if (key.rightArrow) {
|
|
1566
|
+
if (selected.kind === "app" && selected.threads && selected.threads.length) {
|
|
1567
|
+
if (!selected.expanded) store.toggleExpand(selected.id);
|
|
1568
|
+
}
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
if (key.leftArrow) {
|
|
1572
|
+
if (selected.kind === "app" && selected.expanded) store.toggleExpand(selected.id);
|
|
1573
|
+
else if (selected.kind === "thread") store.setSelected(selected.parentId);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
if (input === "f") {
|
|
1577
|
+
store.setFilter(nextFilter(store.filter));
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
if (input === "s") {
|
|
1581
|
+
const target = findNodeAndApp(store, selected.id);
|
|
1582
|
+
if (target) store.handlers.stop(target.node.id);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
if (input === "S") {
|
|
1586
|
+
const target = findNodeAndApp(store, selected.id);
|
|
1587
|
+
if (target) store.handlers.start(target.node.id);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
if (input === "r") {
|
|
1591
|
+
const target = findNodeAndApp(store, selected.id);
|
|
1592
|
+
if (target) store.handlers.restart(target.node.id);
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
if (input === "q" || key.ctrl && input === "c") {
|
|
1596
|
+
onExit();
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1600
|
+
const columns = stdout?.columns ?? 80;
|
|
1601
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1602
|
+
flexDirection: "column",
|
|
1603
|
+
width: columns,
|
|
1604
|
+
height: rows,
|
|
1605
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1606
|
+
flexDirection: "row",
|
|
1607
|
+
flexGrow: 1,
|
|
1608
|
+
width: columns,
|
|
1609
|
+
children: [/* @__PURE__ */ jsx(AppList, {}), /* @__PURE__ */ jsx(LogView, {})]
|
|
1610
|
+
}), /* @__PURE__ */ jsx(StatusBar, {})]
|
|
1611
|
+
});
|
|
1795
1612
|
}
|
|
1796
1613
|
function TuiRoot(props) {
|
|
1797
|
-
|
|
1614
|
+
return /* @__PURE__ */ jsx(TuiStoreContext.Provider, {
|
|
1615
|
+
value: props.store,
|
|
1616
|
+
children: /* @__PURE__ */ jsx(InnerTui, { ...props })
|
|
1617
|
+
});
|
|
1798
1618
|
}
|
|
1799
|
-
|
|
1800
1619
|
function getAppProps(definition, props, isSingle) {
|
|
1801
|
-
|
|
1802
|
-
|
|
1620
|
+
if (isSingle) return { ...props };
|
|
1621
|
+
return getPrefixedProps(definition, props);
|
|
1803
1622
|
}
|
|
1804
1623
|
function appIdFor(definition, idx) {
|
|
1805
|
-
|
|
1624
|
+
return `${definition.name}#${idx}`;
|
|
1806
1625
|
}
|
|
1807
1626
|
function resolveThreadCount(appProps) {
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
}
|
|
1811
|
-
return 1;
|
|
1627
|
+
if (isNumber(appProps.threads) && appProps.threads > 0) return appProps.threads;
|
|
1628
|
+
return 1;
|
|
1812
1629
|
}
|
|
1813
1630
|
const APP_STATE_RANK = {
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1631
|
+
error: 10,
|
|
1632
|
+
run: 9,
|
|
1633
|
+
"in-run": 8,
|
|
1634
|
+
setup: 7,
|
|
1635
|
+
"in-setup": 6,
|
|
1636
|
+
stop: 5,
|
|
1637
|
+
"in-stop": 4,
|
|
1638
|
+
shutdown: 3,
|
|
1639
|
+
"in-shutdown": 2,
|
|
1640
|
+
ready: 1,
|
|
1641
|
+
init: 0
|
|
1825
1642
|
};
|
|
1826
1643
|
function aggregateAppState(threads) {
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
if (s > APP_STATE_RANK[best]) best = w.state;
|
|
1831
|
-
}
|
|
1832
|
-
return best;
|
|
1644
|
+
let best = "init";
|
|
1645
|
+
for (const w of threads) if (APP_STATE_RANK[w.state] > APP_STATE_RANK[best]) best = w.state;
|
|
1646
|
+
return best;
|
|
1833
1647
|
}
|
|
1834
1648
|
function launchAppsTui(definitions, props) {
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
return Promise.all(
|
|
1973
|
-
records.map((r) => Promise.all(r.threads.map((w) => shutdownThreadApp(w))))
|
|
1974
|
-
).then(() => {
|
|
1975
|
-
store.setSystem({
|
|
1976
|
-
ts: Date.now(),
|
|
1977
|
-
level: "info",
|
|
1978
|
-
text: "shutdown complete"
|
|
1979
|
-
});
|
|
1980
|
-
}).then(() => inkInstance.waitUntilRenderFlush()).then(() => inkInstance.unmount()).then(() => q.resolve()).catch(console.error);
|
|
1981
|
-
});
|
|
1982
|
-
const onExit = () => {
|
|
1983
|
-
return Promise.resolve().then(() => processGraceful());
|
|
1984
|
-
};
|
|
1985
|
-
const inkInstance = render(/* @__PURE__ */ jsx(TuiRoot, { store, onExit }), {
|
|
1986
|
-
exitOnCtrlC: false
|
|
1987
|
-
});
|
|
1988
|
-
return Promise.all(records.map(startRecord)).then(() => q.promise);
|
|
1649
|
+
const store = createTuiStore();
|
|
1650
|
+
const records = [];
|
|
1651
|
+
const isSingle = definitions.length === 1;
|
|
1652
|
+
if (props.watch) store.setSystem({
|
|
1653
|
+
ts: Date.now(),
|
|
1654
|
+
level: "warn",
|
|
1655
|
+
text: "TUI does not yet support --watch mode;"
|
|
1656
|
+
});
|
|
1657
|
+
for (let i = 0; i < definitions.length; i++) {
|
|
1658
|
+
const def = definitions[i];
|
|
1659
|
+
const appId = appIdFor(def, i);
|
|
1660
|
+
const appProps = getAppProps(def, props, isSingle);
|
|
1661
|
+
const threadCount = resolveThreadCount(appProps);
|
|
1662
|
+
appProps.__inheritIO = false;
|
|
1663
|
+
delete appProps.threads;
|
|
1664
|
+
const appNode = {
|
|
1665
|
+
kind: "app",
|
|
1666
|
+
id: appId,
|
|
1667
|
+
name: def.name,
|
|
1668
|
+
state: APP_INSTANCE_STATE.INIT,
|
|
1669
|
+
expanded: false,
|
|
1670
|
+
threads: []
|
|
1671
|
+
};
|
|
1672
|
+
store.addApp(appNode);
|
|
1673
|
+
const threads = [];
|
|
1674
|
+
const threadNodes = [];
|
|
1675
|
+
for (let threadId = 1; threadId <= threadCount; threadId++) {
|
|
1676
|
+
const nodeId = `${appId}:${threadId}`;
|
|
1677
|
+
const threadNode = {
|
|
1678
|
+
id: `${appId}:${threadId}`,
|
|
1679
|
+
kind: "thread",
|
|
1680
|
+
name: `${appNode.name}.${threadId}`,
|
|
1681
|
+
parentId: appId,
|
|
1682
|
+
threadId,
|
|
1683
|
+
pid: 0,
|
|
1684
|
+
state: APP_INSTANCE_STATE.INIT
|
|
1685
|
+
};
|
|
1686
|
+
threadNodes.push(threadNode);
|
|
1687
|
+
const w = initThread(threadId, def.filePath, appProps);
|
|
1688
|
+
w.eventBus.on("log", (entry) => {
|
|
1689
|
+
store.pushLog(nodeId, {
|
|
1690
|
+
ts: entry.ts,
|
|
1691
|
+
level: entry.level,
|
|
1692
|
+
text: entry.text
|
|
1693
|
+
});
|
|
1694
|
+
});
|
|
1695
|
+
w.eventBus.on("state", (newState) => {
|
|
1696
|
+
store.setState(nodeId, newState);
|
|
1697
|
+
store.setState(appId, aggregateAppState(threads));
|
|
1698
|
+
});
|
|
1699
|
+
w.eventBus.on("pid", (pid) => {
|
|
1700
|
+
store.setPid(nodeId, pid);
|
|
1701
|
+
});
|
|
1702
|
+
w.eventBus.on("error", (err) => {
|
|
1703
|
+
store.pushLog(nodeId, {
|
|
1704
|
+
ts: Date.now(),
|
|
1705
|
+
level: "error",
|
|
1706
|
+
text: `Thread error: ${err.message}`
|
|
1707
|
+
});
|
|
1708
|
+
});
|
|
1709
|
+
threads.push(w);
|
|
1710
|
+
}
|
|
1711
|
+
store.setThreads(appId, threadNodes);
|
|
1712
|
+
records.push({
|
|
1713
|
+
appId,
|
|
1714
|
+
node: appNode,
|
|
1715
|
+
threads
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
const findRecordByAppId = (id) => records.find((r) => r.appId === id);
|
|
1719
|
+
const findThread = (id) => {
|
|
1720
|
+
for (const r of records) for (const w of r.threads) if (`${r.appId}:${w.threadId}` === id) return {
|
|
1721
|
+
record: r,
|
|
1722
|
+
thread: w
|
|
1723
|
+
};
|
|
1724
|
+
};
|
|
1725
|
+
const startSingleThread = (w) => {
|
|
1726
|
+
if (w.state === APP_INSTANCE_STATE.IN_RUN || APP_INSTANCE_STATE.RUN) return Promise.resolve();
|
|
1727
|
+
if (w.state === APP_INSTANCE_STATE.STOP) return startThreadApp(w);
|
|
1728
|
+
return setupThreadApp(w).then(() => startThreadApp(w));
|
|
1729
|
+
};
|
|
1730
|
+
store.handlers = {
|
|
1731
|
+
stop(id) {
|
|
1732
|
+
const r = findRecordByAppId(id);
|
|
1733
|
+
if (r) return Promise.all(r.threads.map((w) => stopThreadApp(w))).then(() => {});
|
|
1734
|
+
const found = findThread(id);
|
|
1735
|
+
if (found) return stopThreadApp(found.thread);
|
|
1736
|
+
return Promise.resolve();
|
|
1737
|
+
},
|
|
1738
|
+
start(id) {
|
|
1739
|
+
const r = findRecordByAppId(id);
|
|
1740
|
+
if (r) return Promise.all(r.threads.map(startSingleThread)).then(() => {});
|
|
1741
|
+
const found = findThread(id);
|
|
1742
|
+
if (found) return startSingleThread(found.thread);
|
|
1743
|
+
return Promise.resolve();
|
|
1744
|
+
},
|
|
1745
|
+
restart(id) {
|
|
1746
|
+
const r = findRecordByAppId(id);
|
|
1747
|
+
if (r) return Promise.all(r.threads.map((w) => {
|
|
1748
|
+
w.restartCount = 0;
|
|
1749
|
+
return restartThreadApp(w);
|
|
1750
|
+
})).then(() => {});
|
|
1751
|
+
const found = findThread(id);
|
|
1752
|
+
if (found) {
|
|
1753
|
+
found.thread.restartCount = 0;
|
|
1754
|
+
return restartThreadApp(found.thread);
|
|
1755
|
+
}
|
|
1756
|
+
return Promise.resolve();
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
const startRecord = (r) => Promise.all(r.threads.map((w) => waitForThreadReady(w).then(() => setupThreadApp(w)).then(() => startThreadApp(w)))).then(noop);
|
|
1760
|
+
onShutdownError((error) => {
|
|
1761
|
+
console.error("[Graceful Shutdown] error:", error);
|
|
1762
|
+
});
|
|
1763
|
+
const q = defer();
|
|
1764
|
+
onShutdown("app", () => {
|
|
1765
|
+
store.setSystem({
|
|
1766
|
+
ts: Date.now(),
|
|
1767
|
+
level: "warn",
|
|
1768
|
+
text: "shutdown initiated"
|
|
1769
|
+
});
|
|
1770
|
+
return Promise.all(records.map((r) => Promise.all(r.threads.map((w) => shutdownThreadApp(w))))).then(() => {
|
|
1771
|
+
store.setSystem({
|
|
1772
|
+
ts: Date.now(),
|
|
1773
|
+
level: "info",
|
|
1774
|
+
text: "shutdown complete"
|
|
1775
|
+
});
|
|
1776
|
+
}).then(() => inkInstance.waitUntilRenderFlush()).then(() => inkInstance.unmount()).then(() => q.resolve()).catch(console.error);
|
|
1777
|
+
});
|
|
1778
|
+
const onExit = () => {
|
|
1779
|
+
return Promise.resolve().then(() => processGraceful());
|
|
1780
|
+
};
|
|
1781
|
+
const inkInstance = render(/* @__PURE__ */ jsx(TuiRoot, {
|
|
1782
|
+
store,
|
|
1783
|
+
onExit
|
|
1784
|
+
}), { exitOnCtrlC: false });
|
|
1785
|
+
return Promise.all(records.map(startRecord)).then(() => q.promise);
|
|
1989
1786
|
}
|
|
1990
|
-
|
|
1991
1787
|
const DB_PATH = path.join(os.tmpdir(), "vrun.json");
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
}
|
|
1788
|
+
var TempDB = class {
|
|
1789
|
+
filepath;
|
|
1790
|
+
_storage = /* @__PURE__ */ new Map();
|
|
1791
|
+
_dirty = false;
|
|
1792
|
+
constructor(filepath) {
|
|
1793
|
+
this.filepath = filepath;
|
|
1794
|
+
}
|
|
1795
|
+
get isDirty() {
|
|
1796
|
+
return this._dirty;
|
|
1797
|
+
}
|
|
1798
|
+
get(key) {
|
|
1799
|
+
return this._storage.get(key);
|
|
1800
|
+
}
|
|
1801
|
+
set(key, value) {
|
|
1802
|
+
this._storage.set(key, value);
|
|
1803
|
+
this._dirty = true;
|
|
1804
|
+
}
|
|
1805
|
+
save() {
|
|
1806
|
+
if (!this._dirty) return;
|
|
1807
|
+
const data = EJSON.stringify(Array.from(this._storage.entries()));
|
|
1808
|
+
fs.writeFileSync(this.filepath, data);
|
|
1809
|
+
this._dirty = false;
|
|
1810
|
+
}
|
|
1811
|
+
load() {
|
|
1812
|
+
try {
|
|
1813
|
+
const data = EJSON.parse(fs.readFileSync(this.filepath, "utf-8"));
|
|
1814
|
+
for (const [key, value] of data) this._storage.set(key, value);
|
|
1815
|
+
return true;
|
|
1816
|
+
} catch (_) {
|
|
1817
|
+
return false;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
2026
1821
|
const db = new TempDB(DB_PATH);
|
|
2027
1822
|
db.load();
|
|
2028
|
-
|
|
2029
1823
|
const TYPE_TO_PLACEHOLDER = /* @__PURE__ */ new Map([
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
1824
|
+
[String, "<string>"],
|
|
1825
|
+
[Number, "<number>"],
|
|
1826
|
+
[BigInt, "<bigint>"],
|
|
1827
|
+
[Date, "<date>"],
|
|
1828
|
+
[Array, "<array>"]
|
|
2035
1829
|
]);
|
|
2036
1830
|
function propsToOptions(props) {
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
}
|
|
2067
|
-
if (required) {
|
|
2068
|
-
option.mandatory = true;
|
|
2069
|
-
}
|
|
2070
|
-
option.attributeName();
|
|
2071
|
-
switch (type) {
|
|
2072
|
-
case Boolean: {
|
|
2073
|
-
option.isBoolean();
|
|
2074
|
-
break;
|
|
2075
|
-
}
|
|
2076
|
-
case Number: {
|
|
2077
|
-
option.argParser(parseNumberArgument);
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
if (parser) {
|
|
2081
|
-
option.argParser(parser);
|
|
2082
|
-
}
|
|
2083
|
-
result.push(option);
|
|
2084
|
-
}
|
|
2085
|
-
return result;
|
|
1831
|
+
const result = [];
|
|
1832
|
+
for (const [name, { description, type, default: defaultValue, required, enum: variants, alias, env, parser }] of Object.entries(props)) {
|
|
1833
|
+
const flagName = kebabCase(name);
|
|
1834
|
+
const typePlaceholder = TYPE_TO_PLACEHOLDER.get(type);
|
|
1835
|
+
const envVariables = arrayable(env);
|
|
1836
|
+
const option = new Option([
|
|
1837
|
+
alias ? `-${alias}, ` : "",
|
|
1838
|
+
`--${flagName}`,
|
|
1839
|
+
typePlaceholder
|
|
1840
|
+
].filter(Boolean).join(" "), description);
|
|
1841
|
+
if (variants?.length) option.choices(variants);
|
|
1842
|
+
if (envVariables.length) {
|
|
1843
|
+
option.env(envVariables.join(", "));
|
|
1844
|
+
option.default(getEnv(envVariables) || defaultValue?.());
|
|
1845
|
+
} else if (defaultValue) option.default(defaultValue());
|
|
1846
|
+
if (required) option.mandatory = true;
|
|
1847
|
+
option.attributeName();
|
|
1848
|
+
switch (type) {
|
|
1849
|
+
case Number:
|
|
1850
|
+
option.argParser(parseNumberArgument);
|
|
1851
|
+
break;
|
|
1852
|
+
case BigInt:
|
|
1853
|
+
option.argParser(parseBigintArgument);
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
if (parser) option.argParser(parser);
|
|
1857
|
+
result.push(option);
|
|
1858
|
+
}
|
|
1859
|
+
return result;
|
|
2086
1860
|
}
|
|
2087
1861
|
function getEnv(variables) {
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
1862
|
+
for (const env of variables) {
|
|
1863
|
+
const value = process.env[env];
|
|
1864
|
+
if (value) return value;
|
|
1865
|
+
}
|
|
2094
1866
|
}
|
|
2095
1867
|
function parseNumberArgument(value) {
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
})
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
);
|
|
2206
|
-
}).then((apps) => buildStartCommand(apps)).then((appCmd) => {
|
|
2207
|
-
if (isHelpArgument(remainingArgs)) {
|
|
2208
|
-
return appCmd.help();
|
|
2209
|
-
}
|
|
2210
|
-
return appCmd.parseAsync(remainingArgs, { from: "user" });
|
|
2211
|
-
}).then(noop);
|
|
2212
|
-
});
|
|
2213
|
-
program.command("folder").description("Start all apps found in a folder.").argument("<string>", "Path to folder").addOption(
|
|
2214
|
-
new Option("--pattern <glob>", "Glob pattern for app files").default(
|
|
2215
|
-
"**/*.{ts,js}"
|
|
2216
|
-
)
|
|
2217
|
-
).allowUnknownOption(true).allowExcessArguments(true).action(function(folderPath, opts) {
|
|
2218
|
-
const cmd = this;
|
|
2219
|
-
if (!folderPath || isHelpArgument(folderPath)) {
|
|
2220
|
-
return cmd.help();
|
|
2221
|
-
}
|
|
2222
|
-
const remainingArgs = cmd.args;
|
|
2223
|
-
const project = createProject(path.resolve(folderPath));
|
|
2224
|
-
return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => findAppFiles(path.resolve(folderPath), opts.pattern)).then((scriptFiles) => {
|
|
2225
|
-
if (!scriptFiles.length) {
|
|
2226
|
-
log.error("No files found. Pattern: %s", opts.pattern);
|
|
2227
|
-
process.exit(1);
|
|
2228
|
-
}
|
|
2229
|
-
return Promise.all(
|
|
2230
|
-
scriptFiles.map((f) => loadAppDefinition(project, f))
|
|
2231
|
-
);
|
|
2232
|
-
}).then((apps) => {
|
|
2233
|
-
if (!apps.length) {
|
|
2234
|
-
log.error("No valid AppDefinitions found in folder.");
|
|
2235
|
-
process.exit(1);
|
|
2236
|
-
}
|
|
2237
|
-
return apps;
|
|
2238
|
-
}).then((apps) => buildStartCommand(apps)).then((appCmd) => {
|
|
2239
|
-
if (isHelpArgument(remainingArgs)) {
|
|
2240
|
-
return appCmd.help();
|
|
2241
|
-
}
|
|
2242
|
-
return appCmd.parseAsync(remainingArgs, { from: "user" });
|
|
2243
|
-
}).then(noop);
|
|
2244
|
-
});
|
|
2245
|
-
return program;
|
|
1868
|
+
const parsedValue = parseInt(value, 10);
|
|
1869
|
+
if (!isNumber(parsedValue)) throw new InvalidArgumentError("Not a number.");
|
|
1870
|
+
return parsedValue;
|
|
1871
|
+
}
|
|
1872
|
+
function parseBigintArgument(value) {
|
|
1873
|
+
try {
|
|
1874
|
+
return BigInt(value);
|
|
1875
|
+
} catch (_) {
|
|
1876
|
+
throw new InvalidArgumentError("Not a big number.");
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
function runApp$1({ scriptFile, cliName = "app", cliDescription = "Application cli", argv = process.argv }) {
|
|
1880
|
+
return (scriptFile ? loadScriptCommand(scriptFile) : Promise.resolve(buildRootCommand({
|
|
1881
|
+
cliName,
|
|
1882
|
+
cliDescription
|
|
1883
|
+
}))).then((program) => program.parseAsync([
|
|
1884
|
+
"node",
|
|
1885
|
+
cliName,
|
|
1886
|
+
...argv
|
|
1887
|
+
])).then(noop).catch((error) => {
|
|
1888
|
+
log.error("Failed to run app:", error);
|
|
1889
|
+
process.exit(1);
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
function buildRootCommand({ cliDescription, cliName }) {
|
|
1893
|
+
const program = new Command().name(cliName).description(cliDescription).enablePositionalOptions().passThroughOptions().helpOption(false).helpCommand(false).action(function() {
|
|
1894
|
+
const cmd = this;
|
|
1895
|
+
if (isHelpArgument(cmd.args)) return cmd.help();
|
|
1896
|
+
});
|
|
1897
|
+
program.command("start", { isDefault: true }).argument("<string...>", "Path to script file").description("Start application.").addOption(new Option("--watch", "Watch file changes")).addOption(new Option("--dev", "Allow to import ts files")).passThroughOptions(true).allowUnknownOption(true).allowExcessArguments(true).action(function(scriptFiles, _opts) {
|
|
1898
|
+
assert.arrayStrings(scriptFiles, "scriptFiles expected string[]");
|
|
1899
|
+
const cmd = this;
|
|
1900
|
+
const remainingArgs = cmd.args;
|
|
1901
|
+
const flagIdx = scriptFiles.findIndex((v) => v.startsWith("-"));
|
|
1902
|
+
scriptFiles = (flagIdx > -1 ? scriptFiles.slice(0, flagIdx) : scriptFiles).filter((v) => !v.startsWith("-"));
|
|
1903
|
+
if (!scriptFiles.length) return cmd.parent?.args?.includes("start") ? cmd.help() : program.help();
|
|
1904
|
+
const project = createProject(path.resolve(scriptFiles[0]));
|
|
1905
|
+
return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => Promise.all(scriptFiles.map((f) => loadAppDefinition(project, f)))).then((apps) => buildStartCommand(apps)).then((appCmd) => {
|
|
1906
|
+
if (isHelpArgument(remainingArgs)) return appCmd.help();
|
|
1907
|
+
return appCmd.parseAsync(remainingArgs, { from: "user" });
|
|
1908
|
+
}).then(noop);
|
|
1909
|
+
});
|
|
1910
|
+
program.command("json").description("Start apps from a JSON file containing an array of script paths.").argument("[string]", "Path to JSON file").allowUnknownOption(true).allowExcessArguments(true).action(function(jsonFile) {
|
|
1911
|
+
const cmd = this;
|
|
1912
|
+
if (!jsonFile || isHelpArgument(jsonFile)) return cmd.help();
|
|
1913
|
+
const project = createProject(path.dirname(path.resolve(jsonFile)));
|
|
1914
|
+
const remainingArgs = cmd.args;
|
|
1915
|
+
return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => projectImportFile(project, path.resolve(jsonFile))).then((m) => {
|
|
1916
|
+
const paths = m.default;
|
|
1917
|
+
assert.arrayStrings(paths, "JSON file must contain a string[].");
|
|
1918
|
+
return paths.map((p) => path.resolve(path.dirname(path.resolve(jsonFile)), p));
|
|
1919
|
+
}).then((scriptFiles) => Promise.all(scriptFiles.map((f) => loadAppDefinition(project, f)))).then((apps) => buildStartCommand(apps)).then((appCmd) => {
|
|
1920
|
+
if (isHelpArgument(remainingArgs)) return appCmd.help();
|
|
1921
|
+
return appCmd.parseAsync(remainingArgs, { from: "user" });
|
|
1922
|
+
}).then(noop);
|
|
1923
|
+
});
|
|
1924
|
+
program.command("list").description("Interactively pick apps to run from a folder.").argument("[string]", "Path to folder", ".").addOption(new Option("--pattern <glob>", "Glob pattern for app files").default("**/*.{app,worker}.{ts,js}")).allowUnknownOption(true).allowExcessArguments(true).action(function(folderPath, opts) {
|
|
1925
|
+
const cmd = this;
|
|
1926
|
+
if (isHelpArgument(cmd.args)) return cmd.help();
|
|
1927
|
+
const remainingArgs = cmd.args;
|
|
1928
|
+
const project = createProject(path.resolve(folderPath));
|
|
1929
|
+
return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => findAppFiles(path.resolve(folderPath), opts.pattern)).then((scriptFiles) => {
|
|
1930
|
+
if (!scriptFiles.length) {
|
|
1931
|
+
log.error("No files found. Pattern: %s", opts.pattern);
|
|
1932
|
+
process.exit(1);
|
|
1933
|
+
}
|
|
1934
|
+
return log.prompt("Pick apps to run.", {
|
|
1935
|
+
type: "multiselect",
|
|
1936
|
+
options: scriptFiles.map((f) => ({
|
|
1937
|
+
label: path.basename(f),
|
|
1938
|
+
value: f
|
|
1939
|
+
})),
|
|
1940
|
+
initial: db.get("app_last_run"),
|
|
1941
|
+
cancel: "null"
|
|
1942
|
+
});
|
|
1943
|
+
}).then((selected) => {
|
|
1944
|
+
const selectedFiles = selected || [];
|
|
1945
|
+
if (!selectedFiles.length) process.exit(0);
|
|
1946
|
+
db.set("app_last_run", selectedFiles);
|
|
1947
|
+
db.save();
|
|
1948
|
+
return Promise.all(selectedFiles.map((f) => loadAppDefinition(project, f)));
|
|
1949
|
+
}).then((apps) => buildStartCommand(apps)).then((appCmd) => {
|
|
1950
|
+
if (isHelpArgument(remainingArgs)) return appCmd.help();
|
|
1951
|
+
return appCmd.parseAsync(remainingArgs, { from: "user" });
|
|
1952
|
+
}).then(noop);
|
|
1953
|
+
});
|
|
1954
|
+
program.command("folder").description("Start all apps found in a folder.").argument("<string>", "Path to folder").addOption(new Option("--pattern <glob>", "Glob pattern for app files").default("**/*.{ts,js}")).allowUnknownOption(true).allowExcessArguments(true).action(function(folderPath, opts) {
|
|
1955
|
+
const cmd = this;
|
|
1956
|
+
if (!folderPath || isHelpArgument(folderPath)) return cmd.help();
|
|
1957
|
+
const remainingArgs = cmd.args;
|
|
1958
|
+
const project = createProject(path.resolve(folderPath));
|
|
1959
|
+
return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => findAppFiles(path.resolve(folderPath), opts.pattern)).then((scriptFiles) => {
|
|
1960
|
+
if (!scriptFiles.length) {
|
|
1961
|
+
log.error("No files found. Pattern: %s", opts.pattern);
|
|
1962
|
+
process.exit(1);
|
|
1963
|
+
}
|
|
1964
|
+
return Promise.all(scriptFiles.map((f) => loadAppDefinition(project, f)));
|
|
1965
|
+
}).then((apps) => {
|
|
1966
|
+
if (!apps.length) {
|
|
1967
|
+
log.error("No valid AppDefinitions found in folder.");
|
|
1968
|
+
process.exit(1);
|
|
1969
|
+
}
|
|
1970
|
+
return apps;
|
|
1971
|
+
}).then((apps) => buildStartCommand(apps)).then((appCmd) => {
|
|
1972
|
+
if (isHelpArgument(remainingArgs)) return appCmd.help();
|
|
1973
|
+
return appCmd.parseAsync(remainingArgs, { from: "user" });
|
|
1974
|
+
}).then(noop);
|
|
1975
|
+
});
|
|
1976
|
+
return program;
|
|
2246
1977
|
}
|
|
2247
1978
|
function findAppFiles(folderPath, pattern) {
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
1979
|
+
return fs.globSync(pattern, {
|
|
1980
|
+
cwd: folderPath,
|
|
1981
|
+
exclude: (f) => f.includes("node_modules")
|
|
1982
|
+
}).map((f) => path.resolve(folderPath, f)).sort();
|
|
2252
1983
|
}
|
|
2253
1984
|
function loadScriptCommand(scriptFile) {
|
|
2254
|
-
|
|
2255
|
-
|
|
1985
|
+
const project = createProject(path.dirname(scriptFile));
|
|
1986
|
+
return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => loadAppDefinition(project, scriptFile)).then((app) => buildStartCommand([app]));
|
|
2256
1987
|
}
|
|
2257
1988
|
function registerTsx(project) {
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
}
|
|
2267
|
-
return Promise.resolve();
|
|
1989
|
+
if (CONFIG.TS_MODE === "tsx") {
|
|
1990
|
+
project.allowTs = true;
|
|
1991
|
+
return Promise.resolve();
|
|
1992
|
+
}
|
|
1993
|
+
if (CONFIG.TS_MODE === "tsx-register") return Promise.resolve().then(() => import("tsx/esm/api")).then((m) => m.register({ tsconfig: project.tsconfigPath || false })).then(() => {
|
|
1994
|
+
project.allowTs = true;
|
|
1995
|
+
});
|
|
1996
|
+
return Promise.resolve();
|
|
2268
1997
|
}
|
|
2269
1998
|
function loadProject(project) {
|
|
2270
|
-
|
|
2271
|
-
|
|
1999
|
+
projectPrintInfo(project);
|
|
2000
|
+
return Promise.resolve().then(() => projectLoadEnv(project)).then(() => projectAutoImport(project)).then(noop);
|
|
2272
2001
|
}
|
|
2273
2002
|
function loadAppDefinition(project, scriptFile) {
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2003
|
+
return projectImportFile(project, scriptFile).then((appModule) => {
|
|
2004
|
+
const app = appModule.default;
|
|
2005
|
+
if (!isAppDefinition(app)) {
|
|
2006
|
+
log.warn(`Default export must be a app definition: ${scriptFile}`);
|
|
2007
|
+
process.exit(1);
|
|
2008
|
+
}
|
|
2009
|
+
return app;
|
|
2010
|
+
});
|
|
2282
2011
|
}
|
|
2283
2012
|
function buildStartCommand(definitions) {
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
return launchApp(
|
|
2297
|
-
definitions.length > 1 || props.threads > 1 ? rootDefinition : definitions[0],
|
|
2298
|
-
props
|
|
2299
|
-
);
|
|
2300
|
-
});
|
|
2301
|
-
return program;
|
|
2013
|
+
const rootDefinition = definitions.length > 1 ? createAppHub(definitions.map((v) => createAppThread(v))) : createAppThread(definitions[0]);
|
|
2014
|
+
const program = new Command().name(rootDefinition.name).description(rootDefinition.description || "An application");
|
|
2015
|
+
if (rootDefinition.props) for (const option of propsToOptions(rootDefinition.props)) program.addOption(option);
|
|
2016
|
+
program.addOption(new Option("--watch", "Watch file changes")).addOption(new Option("--dev", "Allow to import ts files")).addOption(new Option("--tui", "Launch interactive terminal UI")).helpCommand(false).command("start", {
|
|
2017
|
+
isDefault: true,
|
|
2018
|
+
hidden: true
|
|
2019
|
+
}).allowUnknownOption(true).allowExcessArguments(true).action((...args) => {
|
|
2020
|
+
const props = program.optsWithGlobals();
|
|
2021
|
+
if (props.tui) return launchAppsTui(definitions, props);
|
|
2022
|
+
return launchApp(definitions.length > 1 || props.threads > 1 ? rootDefinition : definitions[0], props);
|
|
2023
|
+
});
|
|
2024
|
+
return program;
|
|
2302
2025
|
}
|
|
2303
2026
|
function launchApp(definition, props) {
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2027
|
+
const startDefer = defer();
|
|
2028
|
+
let app;
|
|
2029
|
+
onShutdown("app", () => {
|
|
2030
|
+
log.warn("Graceful shutdown initiated, waiting for process to complete");
|
|
2031
|
+
if (CONFIG.TS_MODE === "tsx") log.warn("Graceful in tsx + watch mode is not supported.");
|
|
2032
|
+
return startDefer.promise.then(() => {
|
|
2033
|
+
if (app) return shutdownApp(app);
|
|
2034
|
+
});
|
|
2035
|
+
});
|
|
2036
|
+
return startApp(definition, props).then((startResult) => {
|
|
2037
|
+
if (isSkip(startResult)) {
|
|
2038
|
+
startDefer.resolve();
|
|
2039
|
+
log.warn("Failed to start %s", startResult, startResult.error);
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
app = startResult.app;
|
|
2043
|
+
startDefer.resolve();
|
|
2044
|
+
return appWaitShutdown(app);
|
|
2045
|
+
}).then(() => processGraceful());
|
|
2046
|
+
}
|
|
2047
|
+
const cli = Object.freeze({ runApp: runApp$1 });
|
|
2048
|
+
var IntervalStrategy = class {
|
|
2049
|
+
options;
|
|
2050
|
+
timerSequence = 0;
|
|
2051
|
+
timer = null;
|
|
2052
|
+
worker;
|
|
2053
|
+
warnOnBusy;
|
|
2054
|
+
constructor(options) {
|
|
2055
|
+
this.options = options;
|
|
2056
|
+
this.warnOnBusy = options.warnOnBusy ?? true;
|
|
2057
|
+
}
|
|
2058
|
+
doSetup({ worker }) {
|
|
2059
|
+
this.worker = worker;
|
|
2060
|
+
}
|
|
2061
|
+
startSignal() {
|
|
2062
|
+
this.timer = setInterval(() => {
|
|
2063
|
+
if (this.worker.isIdle) this.worker.addTask(this.createTask());
|
|
2064
|
+
else if (this.warnOnBusy) log.warn("[%s] Worker busy, skipping tick", this.worker.definition.name);
|
|
2065
|
+
}, this.options.intervalSeconds * 1e3);
|
|
2066
|
+
}
|
|
2067
|
+
stopSignal(done) {
|
|
2068
|
+
clearInterval(this.timer);
|
|
2069
|
+
this.timer = null;
|
|
2070
|
+
done();
|
|
2071
|
+
}
|
|
2072
|
+
doShutdown() {}
|
|
2073
|
+
createTask() {
|
|
2074
|
+
return { timerSequence: ++this.timerSequence };
|
|
2075
|
+
}
|
|
2336
2076
|
};
|
|
2337
|
-
|
|
2338
|
-
class IntervalStrategy {
|
|
2339
|
-
constructor(options) {
|
|
2340
|
-
this.options = options;
|
|
2341
|
-
}
|
|
2342
|
-
timerSequence = 0;
|
|
2343
|
-
timer = null;
|
|
2344
|
-
worker;
|
|
2345
|
-
doSetup({ worker }) {
|
|
2346
|
-
this.worker = worker;
|
|
2347
|
-
}
|
|
2348
|
-
startSignal() {
|
|
2349
|
-
this.timer = setInterval(() => {
|
|
2350
|
-
if (this.worker.isIdle) {
|
|
2351
|
-
this.worker.addTask(this.createTask());
|
|
2352
|
-
} else {
|
|
2353
|
-
log.warn(
|
|
2354
|
-
"[%s] Worker busy, skipping tick",
|
|
2355
|
-
this.worker.definition.name
|
|
2356
|
-
);
|
|
2357
|
-
}
|
|
2358
|
-
}, this.options.intervalSeconds * 1e3);
|
|
2359
|
-
}
|
|
2360
|
-
stopSignal(done) {
|
|
2361
|
-
clearInterval(this.timer);
|
|
2362
|
-
this.timer = null;
|
|
2363
|
-
done();
|
|
2364
|
-
}
|
|
2365
|
-
doShutdown() {
|
|
2366
|
-
}
|
|
2367
|
-
createTask() {
|
|
2368
|
-
return { timerSequence: ++this.timerSequence };
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
2077
|
const WORKER_DEF = Symbol("worker-definition");
|
|
2373
2078
|
function createWorkerPool(size) {
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2079
|
+
return new ResourcePool({
|
|
2080
|
+
poolSize: size,
|
|
2081
|
+
auto: true,
|
|
2082
|
+
createResource: () => Symbol("worker-slot")
|
|
2083
|
+
});
|
|
2379
2084
|
}
|
|
2380
2085
|
function createWorkerInstance(definition, logger) {
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2086
|
+
return {
|
|
2087
|
+
definition,
|
|
2088
|
+
executeStrategy: null,
|
|
2089
|
+
log: logger,
|
|
2090
|
+
queue: new AsyncIterableQueue(),
|
|
2091
|
+
pool: createWorkerPool(definition.taskParallel ?? 2),
|
|
2092
|
+
runTask: null,
|
|
2093
|
+
queueMaxSize: definition.taskLimit ?? 50,
|
|
2094
|
+
taskParallel: definition.taskParallel ?? 2,
|
|
2095
|
+
overloadedSignaled: false,
|
|
2096
|
+
addTask(ctx) {
|
|
2097
|
+
addWorkerTask(this, ctx);
|
|
2098
|
+
},
|
|
2099
|
+
get isOverloaded() {
|
|
2100
|
+
return this.queueSize > this.queueMaxSize * .8;
|
|
2101
|
+
},
|
|
2102
|
+
get isIdle() {
|
|
2103
|
+
return this.pool.isIdle && this.queueSize === 0;
|
|
2104
|
+
},
|
|
2105
|
+
get queueSize() {
|
|
2106
|
+
return this.queue._queue.items.length;
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2404
2109
|
}
|
|
2405
2110
|
function addWorkerTask(instance, ctx) {
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
if (executeStrategy.completeSignal) {
|
|
2463
|
-
const [completeError] = await catchError(
|
|
2464
|
-
() => executeStrategy.completeSignal(entryContext, executeResult)
|
|
2465
|
-
);
|
|
2466
|
-
if (completeError) {
|
|
2467
|
-
instance.log.error("Strategy completeSignal error", {
|
|
2468
|
-
error: completeError
|
|
2469
|
-
});
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2111
|
+
instance.queue.put(ctx);
|
|
2112
|
+
checkWorkerPressure(instance);
|
|
2113
|
+
}
|
|
2114
|
+
var NOOP_EXECUTE_SIGNAL = () => ({
|
|
2115
|
+
success: true,
|
|
2116
|
+
code: "ok"
|
|
2117
|
+
});
|
|
2118
|
+
function executeWorkerTask(instance, entryContext, props, abortSignal) {
|
|
2119
|
+
const { log } = instance;
|
|
2120
|
+
const entry = instance.definition.entry || noop;
|
|
2121
|
+
const executeStrategy = instance.executeStrategy;
|
|
2122
|
+
const completeSignal = executeStrategy.completeSignal || noop;
|
|
2123
|
+
const executeSignal = executeStrategy.executeSignal || NOOP_EXECUTE_SIGNAL;
|
|
2124
|
+
return Promise.resolve().then(() => catchError(() => executeSignal(entryContext))).then(({ 0: signalError, 1: signalResult }) => {
|
|
2125
|
+
if (signalError) {
|
|
2126
|
+
log.error("Strategy executeSignal error, dropping task", { error: signalError });
|
|
2127
|
+
return;
|
|
2128
|
+
} else if (isSkip(signalResult)) {
|
|
2129
|
+
log.warn("Strategy executeSignal returned skip, dropping task", signalResult);
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
const taskAbort = new AbortController();
|
|
2133
|
+
const handleAbort = () => {
|
|
2134
|
+
if (!taskAbort.signal.aborted) taskAbort.abort(abortSignal.reason);
|
|
2135
|
+
};
|
|
2136
|
+
abortSignal.addEventListener("abort", handleAbort);
|
|
2137
|
+
return Promise.resolve().then(() => catchError(() => entry.call(entryContext, props, taskAbort.signal))).then(({ 0: executeError, 1: executeResult }) => {
|
|
2138
|
+
abortSignal.removeEventListener("abort", handleAbort);
|
|
2139
|
+
if (executeError) {
|
|
2140
|
+
if (executeStrategy.handleEntryError) executeResult = executeStrategy.handleEntryError(executeError);
|
|
2141
|
+
if (!executeResult) executeResult = {
|
|
2142
|
+
skip: true,
|
|
2143
|
+
error: true,
|
|
2144
|
+
code: "critical_error",
|
|
2145
|
+
reason: executeError.message,
|
|
2146
|
+
stack: executeError.stack
|
|
2147
|
+
};
|
|
2148
|
+
} else if (!executeResult || Array.isArray(executeResult) && !executeResult.length) executeResult = {
|
|
2149
|
+
success: true,
|
|
2150
|
+
code: "void"
|
|
2151
|
+
};
|
|
2152
|
+
const results = arrayable(executeResult);
|
|
2153
|
+
let hasError = false;
|
|
2154
|
+
let hasSkip = false;
|
|
2155
|
+
for (const result of results) {
|
|
2156
|
+
hasError = hasError || isSkip(result) && !!result.error;
|
|
2157
|
+
hasSkip = isSkip(result);
|
|
2158
|
+
}
|
|
2159
|
+
if (hasError) log.error("Task error", results);
|
|
2160
|
+
else if (hasSkip) log.warn("Task skipped", results);
|
|
2161
|
+
else log.debug("Task completed", results);
|
|
2162
|
+
return catchError(() => completeSignal(entryContext, executeResult));
|
|
2163
|
+
}).then(({ 0: completeError }) => {
|
|
2164
|
+
if (completeError) log.error("Strategy completeSignal error", { error: completeError });
|
|
2165
|
+
});
|
|
2166
|
+
});
|
|
2472
2167
|
}
|
|
2473
2168
|
function defineWorker(definition) {
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2169
|
+
const result = defineApp({
|
|
2170
|
+
filePath: filePathFromStack(captureStackTrace(defineWorker)),
|
|
2171
|
+
...definition,
|
|
2172
|
+
setup(props) {
|
|
2173
|
+
return setupWorker(this, definition, props);
|
|
2174
|
+
},
|
|
2175
|
+
entry(props) {
|
|
2176
|
+
return runWorkerLoop(this, props);
|
|
2177
|
+
},
|
|
2178
|
+
stop(props) {
|
|
2179
|
+
return stopWorker(this, props);
|
|
2180
|
+
},
|
|
2181
|
+
shutdown(props) {
|
|
2182
|
+
return shutdownWorker(this, props);
|
|
2183
|
+
}
|
|
2184
|
+
});
|
|
2185
|
+
result.disabled = result.disabled || CONFIG.WORKER_DISABLED.has(result.name);
|
|
2186
|
+
def(result, WORKER_DEF, true);
|
|
2187
|
+
return result;
|
|
2493
2188
|
}
|
|
2494
2189
|
function setupWorker(setupContext, definition, props) {
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
worker.queue = new AsyncIterableQueue();
|
|
2559
|
-
worker.runTask = null;
|
|
2560
|
-
resolve();
|
|
2561
|
-
}
|
|
2562
|
-
);
|
|
2563
|
-
worker.runTask.catch((err) => {
|
|
2564
|
-
worker.log.error("Worker crash: %s", { error: toError(err) });
|
|
2565
|
-
});
|
|
2190
|
+
const setupFn = definition.setup;
|
|
2191
|
+
const ctx = {
|
|
2192
|
+
...setupContext,
|
|
2193
|
+
worker: createWorkerInstance(definition, setupContext.log)
|
|
2194
|
+
};
|
|
2195
|
+
if (isFunction(definition.executeStrategy)) ctx.worker.executeStrategy = definition.executeStrategy(props);
|
|
2196
|
+
else ctx.worker.executeStrategy = definition.executeStrategy;
|
|
2197
|
+
return Promise.resolve().then(() => ctx.worker.executeStrategy.doSetup(ctx)).then(() => setupFn ? setupFn.call(ctx, props) : void 0).then((setupResult) => {
|
|
2198
|
+
return {
|
|
2199
|
+
...ctx,
|
|
2200
|
+
...setupResult || {}
|
|
2201
|
+
};
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
function runWorkerLoop(runtimeContext, props) {
|
|
2205
|
+
const worker = runtimeContext.worker;
|
|
2206
|
+
const executeStrategy = worker.executeStrategy;
|
|
2207
|
+
const abortController = new AbortController();
|
|
2208
|
+
const abortSignal = abortController.signal;
|
|
2209
|
+
const onStopSignal = () => {
|
|
2210
|
+
return Promise.resolve().then(() => abortController.abort()).then(() => catchError(() => executeStrategy.stopSignal(() => worker.queue.close()))).then(({ 0: stopError }) => {
|
|
2211
|
+
if (stopError) {
|
|
2212
|
+
worker.queue.close();
|
|
2213
|
+
worker.log.error("Strategy stopSignal error", { error: stopError });
|
|
2214
|
+
}
|
|
2215
|
+
});
|
|
2216
|
+
};
|
|
2217
|
+
worker.runTask = new CancellablePromise(async (resolve, _reject, onCancel) => {
|
|
2218
|
+
onCancel(onStopSignal);
|
|
2219
|
+
const { 0: startError } = await catchError(() => executeStrategy.startSignal());
|
|
2220
|
+
if (startError) {
|
|
2221
|
+
worker.log.error("Strategy startSignal error", { error: startError });
|
|
2222
|
+
worker.runTask = null;
|
|
2223
|
+
return resolve();
|
|
2224
|
+
}
|
|
2225
|
+
worker.log.info("Worker started (parallel=%d)", worker.taskParallel);
|
|
2226
|
+
for await (const taskContext of worker.queue) {
|
|
2227
|
+
const jobTicket = await worker.pool.acquire();
|
|
2228
|
+
const entryContext = new Proxy(taskContext, {
|
|
2229
|
+
get(target, key, receiver) {
|
|
2230
|
+
if (Object.prototype.hasOwnProperty.call(target, key)) return Reflect.get(target, key, receiver);
|
|
2231
|
+
return Reflect.get(runtimeContext, key);
|
|
2232
|
+
},
|
|
2233
|
+
set(target, key, value) {
|
|
2234
|
+
if (Object.prototype.hasOwnProperty.call(target, key)) Reflect.set(target, key, value, target);
|
|
2235
|
+
else Reflect.set(runtimeContext, key, value, runtimeContext);
|
|
2236
|
+
return true;
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
2239
|
+
executeWorkerTask(worker, entryContext, props, abortSignal).finally(() => {
|
|
2240
|
+
worker.pool.release(jobTicket);
|
|
2241
|
+
checkWorkerPressure(worker);
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
if (worker.pool.usedCount > 0) worker.log.info("Draining task pool (active=%d)", worker.pool.usedCount);
|
|
2245
|
+
await worker.pool.destroy();
|
|
2246
|
+
worker.queue = new AsyncIterableQueue();
|
|
2247
|
+
worker.runTask = null;
|
|
2248
|
+
resolve();
|
|
2249
|
+
});
|
|
2250
|
+
worker.runTask.catch((err) => {
|
|
2251
|
+
worker.log.error("Worker crash: %s", { error: toError(err) });
|
|
2252
|
+
});
|
|
2566
2253
|
}
|
|
2567
2254
|
function checkWorkerPressure(worker) {
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
}
|
|
2606
|
-
async function shutdownWorker(runtimeContext, definition, props) {
|
|
2607
|
-
const worker = runtimeContext.worker;
|
|
2608
|
-
const userShutdown = definition.shutdown;
|
|
2609
|
-
if (isFunction(userShutdown)) {
|
|
2610
|
-
await userShutdown.call(runtimeContext, props);
|
|
2611
|
-
}
|
|
2612
|
-
await definition.executeStrategy.doShutdown();
|
|
2613
|
-
worker.queue = new AsyncIterableQueue();
|
|
2614
|
-
worker.pool = createWorkerPool(definition.taskParallel ?? 2);
|
|
2255
|
+
const executeStrategy = worker.executeStrategy;
|
|
2256
|
+
try {
|
|
2257
|
+
if (worker.isOverloaded) {
|
|
2258
|
+
if (!executeStrategy || !executeStrategy.overloadedSignal) return void worker.log.warn("Worker under pressure (queue=%d, limit=%d)", worker.queueSize, worker.queueMaxSize);
|
|
2259
|
+
if (worker.overloadedSignaled) return;
|
|
2260
|
+
worker.overloadedSignaled = true;
|
|
2261
|
+
executeStrategy.overloadedSignal();
|
|
2262
|
+
} else {
|
|
2263
|
+
if (!worker.overloadedSignaled) return;
|
|
2264
|
+
if (!executeStrategy || !executeStrategy.availableSignal) worker.log.warn("Worker capacity restored");
|
|
2265
|
+
else executeStrategy.availableSignal();
|
|
2266
|
+
worker.overloadedSignaled = false;
|
|
2267
|
+
}
|
|
2268
|
+
} catch (err) {
|
|
2269
|
+
worker.log.error("Backpressure signal error", { error: err });
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
function stopWorker(runtimeContext, props) {
|
|
2273
|
+
const worker = runtimeContext.worker;
|
|
2274
|
+
const userStop = worker.definition.stop || noop;
|
|
2275
|
+
const runTask = worker.runTask;
|
|
2276
|
+
if (!runTask) return Promise.resolve();
|
|
2277
|
+
return Promise.resolve().then(() => userStop.call(runtimeContext, props)).finally(() => {
|
|
2278
|
+
runTask.cancel();
|
|
2279
|
+
return runTask.finally(() => {
|
|
2280
|
+
worker.runTask = null;
|
|
2281
|
+
});
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
function shutdownWorker(runtimeContext, props) {
|
|
2285
|
+
const worker = runtimeContext.worker;
|
|
2286
|
+
const executeStrategy = worker.executeStrategy;
|
|
2287
|
+
const definition = worker.definition;
|
|
2288
|
+
const userShutdown = definition.shutdown || noop;
|
|
2289
|
+
return Promise.resolve().then(() => userShutdown.call(runtimeContext, props)).then(() => executeStrategy.doShutdown()).finally(() => {
|
|
2290
|
+
worker.queue = new AsyncIterableQueue();
|
|
2291
|
+
worker.pool = createWorkerPool(definition.taskParallel ?? 2);
|
|
2292
|
+
});
|
|
2615
2293
|
}
|
|
2616
2294
|
function isWorkerDefinition(value) {
|
|
2617
|
-
|
|
2295
|
+
return value?.[WORKER_DEF] === true;
|
|
2618
2296
|
}
|
|
2619
2297
|
function isWorkerInstance(value) {
|
|
2620
|
-
|
|
2298
|
+
return isWorkerDefinition(value?.definition);
|
|
2621
2299
|
}
|
|
2300
|
+
export { APP_INSTANCE_STATE, IntervalStrategy, addWorkerTask, appWaitShutdown, cli, createAppHub, createAppInstance, createAppThread, createAppThreadInstance, createThreadMessage, createWorkerInstance, defineApp, defineWorker, getPrefixedProps, initThread, isAppDefinition, isThreadExit, isThreadMessage, isWorkerDefinition, isWorkerInstance, prefixifyProps, restartThreadApp, runApp, setupApp, setupThreadApp, shutdownApp, shutdownThreadApp, startApp, startThreadApp, stopApp, stopThreadApp, waitForThreadReady };
|
|
2622
2301
|
|
|
2623
|
-
|
|
2624
|
-
//# sourceMappingURL=index.mjs.map
|
|
2302
|
+
//# sourceMappingURL=index.mjs.map
|