@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/dist/index.mjs CHANGED
@@ -1,527 +1,435 @@
1
- import { env, getLoggerLevel, noop, isFunction, typeOf, EJSON, captureStackTrace, def, isSkip, defer, toError, SimpleEventEmitter, camelCase, capitalize, asyncForEach, pickPrefixed, isError, Scheduler, isObject, assert, catchError, isString, getFileExtension, uniq, isNumber, kebabCase, arrayable, AsyncIterableQueue, ResourcePool, CancellablePromise } from '@andrew_l/toolkit';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { createConsola, LogLevels } from 'consola';
5
- import { isShuttingDown, onShutdownError, onShutdown, processGraceful } from '@andrew_l/graceful';
6
- import * as cp from 'node:child_process';
7
- import { EventEmitter } from 'node:events';
8
- import { Option, InvalidArgumentError, Command } from 'commander';
9
- import fs from 'node:fs';
10
- import dotenv from 'dotenv';
11
- import { getColor } from 'consola/utils';
12
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
- import { Box, Text, useStdout, useInput, render } from 'ink';
14
- import { createContext, useState, useEffect, useContext } from 'react';
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
- IS_VRUN: env.bool("VRUN", false),
18
- WATCH_MODE: env.bool("VRUN_WATCH", false),
19
- /**
20
- * Root path of application
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
- if (!Array.isArray(argv)) {
38
- argv = [argv];
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
- const result = [];
44
- let flag = false;
45
- for (const value of argv) {
46
- if (value.startsWith("--")) {
47
- flag = true;
48
- result.push(value);
49
- continue;
50
- }
51
- if (flag) {
52
- result.push(value);
53
- flag = false;
54
- }
55
- }
56
- return result;
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
- const stackLines = stack.trim().split("\n");
61
- const callerLine = stackLines[0];
62
- let filePath = null;
63
- const patterns = [
64
- /\((.+):(\d+):(\d+)\)$/,
65
- // (file:line:col)
66
- /at\s+(.+):(\d+):(\d+)$/,
67
- // at file:line:col
68
- /\s+at\s+(.+):(\d+):(\d+)$/,
69
- // at file:line:col with spaces
70
- /\(file:\/\/(.+):(\d+):(\d+)\)$/,
71
- // (file://path:line:col)
72
- /file:\/\/(.+):(\d+):(\d+)$/
73
- // file://path:line:col
74
- ];
75
- for (const pattern of patterns) {
76
- const match = callerLine.match(pattern);
77
- if (match) {
78
- filePath = match[1];
79
- break;
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
- const cwd = process.cwd();
99
- return process.argv.some((arg) => filepath.endsWith(path.resolve(cwd, arg)));
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
- const log2 = createConsola({
104
- formatOptions: { date: false },
105
- level: LogLevels[getLoggerLevel()],
106
- fancy: true,
107
- defaults: {
108
- tag: tagName
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
- debug: noop,
117
- error: noop,
118
- extend: () => noopLogger,
119
- info: noop,
120
- log: noop,
121
- warn: noop
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
- if (!fields) return event;
126
- const parts = [];
127
- for (const key in fields) {
128
- if (key === "vrun_app_thread_message") continue;
129
- const v = fields[key];
130
- if (v === void 0) continue;
131
- parts.push(`${key}=${formatField(v)}`);
132
- }
133
- return parts.length ? `${event} ${parts.join(" ")}` : event;
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
- switch (typeOf(value)) {
137
- case "array":
138
- case "object":
139
- return EJSON.stringify(value);
140
- default: {
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
- return definition.logger === false ? noopLogger : isFunction(definition.logger) ? definition.logger(definition) : definition.logger || createLogger(definition.name);
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
- INIT: "init",
151
- IN_SETUP: "in-setup",
152
- SETUP: "setup",
153
- IN_STOP: "in-stop",
154
- IN_RUN: "in-run",
155
- RUN: "run",
156
- STOP: "stop",
157
- IN_SHUTDOWN: "in-shutdown",
158
- SHUTDOWN: "shutdown",
159
- ERROR: "error"
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
- const result = {
164
- filePath: filePathFromStack(captureStackTrace(defineApp)),
165
- ...definition
166
- };
167
- def(result, APP_DEF, true);
168
- if (!CONFIG.IS_VRUN) {
169
- if (result.filePath) {
170
- if (isMainFile(result.filePath)) {
171
- Promise.resolve().then(function () { return index; }).then((m) => {
172
- m.cli.runApp({
173
- cliName: "app",
174
- scriptFile: result.filePath,
175
- argv: extractOptionsArgs(process.argv.slice(1))
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
- const setupState = createSetupState(definition);
185
- return {
186
- definition,
187
- props: null,
188
- setupState,
189
- eventBus: new SimpleEventEmitter(),
190
- mutexName: null,
191
- mutexQueue: Promise.resolve(),
192
- state: APP_INSTANCE_STATE.INIT,
193
- logger: setupState.log
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
- return value?.[APP_DEF] === true;
198
- }
199
- async function startApp(app, props) {
200
- const instance = isAppDefinition(app) ? createAppInstance(app) : app;
201
- const setupResult = await setupApp(instance, props);
202
- if (isSkip(setupResult)) {
203
- return setupResult;
204
- }
205
- const runResult = await runApp$1(instance);
206
- if (isSkip(runResult)) {
207
- await shutdownApp(instance);
208
- return runResult;
209
- }
210
- return {
211
- success: true,
212
- code: "start_app",
213
- app: instance
214
- };
215
- }
216
- async function setupApp(instance, props) {
217
- const mutexResolve = await mutexAcquire(instance, "setup");
218
- if (instance.state !== APP_INSTANCE_STATE.INIT) {
219
- mutexResolve();
220
- return {
221
- skip: true,
222
- code: "setup_app",
223
- reason: `application in ${instance.state} state`
224
- };
225
- }
226
- setState$1(instance, APP_INSTANCE_STATE.IN_SETUP);
227
- const { setup } = instance.definition;
228
- if (isFunction(setup)) {
229
- try {
230
- const setupResult = await setup.call(instance.setupState, props);
231
- Object.assign(instance.setupState, setupResult);
232
- } catch (error) {
233
- setState$1(instance, APP_INSTANCE_STATE.ERROR);
234
- mutexResolve();
235
- return {
236
- code: "setup_app",
237
- skip: true,
238
- reason: "setup function throw error",
239
- error: toError(error)
240
- };
241
- }
242
- }
243
- instance.props = props;
244
- setState$1(instance, APP_INSTANCE_STATE.SETUP);
245
- mutexResolve();
246
- return {
247
- success: true,
248
- code: "setup_app"
249
- };
250
- }
251
- async function runApp$1(instance) {
252
- const mutexResolve = await mutexAcquire(instance, "run");
253
- if (instance.state !== APP_INSTANCE_STATE.SETUP) {
254
- mutexResolve();
255
- return {
256
- skip: true,
257
- code: "execute_app",
258
- reason: `application in ${instance.state} state`
259
- };
260
- }
261
- const { entry } = instance.definition;
262
- setState$1(instance, APP_INSTANCE_STATE.IN_RUN);
263
- instance.logger.info("Starting...");
264
- if (isFunction(entry)) {
265
- try {
266
- await entry.call(instance.setupState, instance.props);
267
- } catch (error) {
268
- setState$1(instance, APP_INSTANCE_STATE.ERROR);
269
- mutexResolve();
270
- return {
271
- skip: true,
272
- code: "execute_app",
273
- reason: "entry function throw error",
274
- error: toError(error)
275
- };
276
- }
277
- }
278
- setState$1(instance, APP_INSTANCE_STATE.RUN);
279
- mutexResolve();
280
- instance.logger.info("Started");
281
- return {
282
- success: true,
283
- code: "execute_app"
284
- };
285
- }
286
- async function stopApp(instance) {
287
- const mutexResolve = await mutexAcquire(instance, "stop");
288
- if (instance.state !== APP_INSTANCE_STATE.RUN) {
289
- mutexResolve();
290
- return {
291
- skip: true,
292
- code: "stop_app",
293
- reason: `application in ${instance.state} state`
294
- };
295
- }
296
- const { stop } = instance.definition;
297
- setState$1(instance, APP_INSTANCE_STATE.IN_STOP);
298
- instance.logger.info("Stopping...");
299
- let stopErr;
300
- if (isFunction(stop)) {
301
- try {
302
- await stop.call(instance.setupState, instance.props);
303
- } catch (err) {
304
- setState$1(instance, APP_INSTANCE_STATE.ERROR);
305
- stopErr = toError(err);
306
- }
307
- }
308
- mutexResolve();
309
- if (stopErr) {
310
- return {
311
- skip: true,
312
- code: "stop_app",
313
- reason: "stop function throw error",
314
- error: stopErr
315
- };
316
- }
317
- setState$1(instance, APP_INSTANCE_STATE.STOP);
318
- instance.logger.info("Stopped");
319
- return {
320
- success: true,
321
- code: "stop_app"
322
- };
323
- }
324
- async function shutdownApp(instance) {
325
- await stopApp(instance);
326
- const mutexResolve = await mutexAcquire(instance, "shutdown");
327
- if (instance.state !== APP_INSTANCE_STATE.STOP && instance.state !== APP_INSTANCE_STATE.SETUP && instance.state !== APP_INSTANCE_STATE.ERROR) {
328
- mutexResolve();
329
- return {
330
- skip: true,
331
- code: "shutdown_app",
332
- reason: `application in ${instance.state} state`
333
- };
334
- }
335
- const { shutdown } = instance.definition;
336
- setState$1(instance, APP_INSTANCE_STATE.IN_SHUTDOWN);
337
- instance.logger.info("Shutdown...");
338
- let shutdownErr;
339
- if (isFunction(shutdown)) {
340
- try {
341
- await shutdown.call(instance.setupState, instance.props);
342
- } catch (err) {
343
- shutdownErr = toError(err);
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
- if (instance.state === APP_INSTANCE_STATE.SHUTDOWN) {
366
- return Promise.resolve();
367
- }
368
- const q = defer();
369
- instance.eventBus.once("state:shutdown", q.resolve);
370
- instance.eventBus.once("error", q.reject);
371
- return q.promise.finally(() => {
372
- instance.eventBus.off("state:shutdown", q.resolve);
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
- const setupState = {
378
- log: createAppLogger(definition)
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
- let outerResolve;
389
- const acquirePromise = new Promise((res) => {
390
- outerResolve = res;
391
- });
392
- instance.mutexQueue = instance.mutexQueue.then(
393
- () => new Promise((innerResolve) => {
394
- instance.mutexName = name;
395
- outerResolve(() => {
396
- instance.mutexName = null;
397
- innerResolve();
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
- const oldState = app.state;
405
- if (newState !== oldState) {
406
- app.state = newState;
407
- app.eventBus.emit("state", newState, oldState);
408
- app.eventBus.emit(`state:${newState}`);
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
- const app = defineApp({
414
- name: "app-hub",
415
- description: `Application wrapper around: ${definitions.map((v) => v.name).join(", ")}`,
416
- logger: false,
417
- props: prefixifyProps(definitions),
418
- setup(props) {
419
- const instances = [];
420
- const setupSkips = [];
421
- return asyncForEach(definitions, (definition) => {
422
- const appProps = getPrefixedProps(definition, props);
423
- const instance = createAppInstance(definition);
424
- instances.push(instance);
425
- return setupApp(instance, appProps).then((setupResult) => {
426
- if (isSkip(setupResult)) {
427
- setupSkips.push({ ...setupResult, app: instance });
428
- }
429
- });
430
- }).then(() => {
431
- if (setupSkips.length) {
432
- throw skipExecsToError("setup", setupSkips);
433
- }
434
- return { instances };
435
- });
436
- },
437
- entry() {
438
- const runSkips = [];
439
- return asyncForEach(this.instances, (instance) => {
440
- return runApp$1(instance).then((runResult) => {
441
- if (isSkip(runResult)) {
442
- runSkips.push({ ...runResult, app: instance });
443
- }
444
- });
445
- }).then(() => {
446
- if (runSkips.length) {
447
- throw skipExecsToError("start", runSkips);
448
- }
449
- });
450
- },
451
- stop() {
452
- const stopSkips = [];
453
- return asyncForEach(this.instances, (instance) => {
454
- return stopApp(instance).then((stopResult) => {
455
- if (isSkip(stopResult)) {
456
- stopSkips.push({ ...stopResult, app: instance });
457
- }
458
- });
459
- }).then(() => {
460
- if (stopSkips.length) {
461
- throw skipExecsToError("stop", stopSkips);
462
- }
463
- });
464
- },
465
- shutdown() {
466
- const shutdownSkips = [];
467
- return asyncForEach(this.instances, (instance) => {
468
- return shutdownApp(instance).then((shutdownResult) => {
469
- if (isSkip(shutdownResult)) {
470
- shutdownSkips.push({ ...shutdownResult, app: instance });
471
- }
472
- });
473
- }).then(() => {
474
- if (shutdownSkips.length) {
475
- throw skipExecsToError("shutdown", shutdownSkips);
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
- const getError = (r) => {
484
- if ("error" in r && isError(r.error)) {
485
- return r.error.stack ? `
486
- ${r.error.message}
487
- ${r.error.stack}` : `
488
- ${r.error.message}`;
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
- const result = pickPrefixed(props, {
503
- prefix: camelCase(definition.name),
504
- prefixTrim: true
505
- });
506
- for (const [key, value] of Object.entries(result)) {
507
- const propName = key.charAt(0).toLowerCase() + key.slice(1).toLowerCase();
508
- result[propName] = value;
509
- delete result[key];
510
- }
511
- return result;
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
- const props = {};
515
- for (const definition of definitions) {
516
- if (definition.props) {
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
- const prev = w.state;
593
- if (prev === next) return;
594
- w.state = next;
595
- w.writeLog("debug", "state", { from: prev, to: next });
596
- w.eventBus.emit("state", next, prev);
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
- w.writeLog("debug", "thread.spawn.start", {
600
- script: path.basename(w.scriptFile),
601
- thread: w.threadId
602
- });
603
- setState(w, "init");
604
- const child = cp.spawn(
605
- process.execPath,
606
- ["--input-type=module", "-e", bootstrapCode],
607
- {
608
- stdio: w.threadProps.__inheritIO !== false ? ["pipe", "inherit", "inherit", "ipc"] : ["pipe", "pipe", "pipe", "ipc"],
609
- env: {
610
- ...process.env,
611
- VRUN_PROCESS_SCRIPT_FILE: w.scriptFile,
612
- VRUN_PROCESS_ID: String(w.threadId)
613
- }
614
- }
615
- );
616
- w.child = child;
617
- const onData = (chunk) => {
618
- const text = chunk.toString("utf8");
619
- for (const line of text.split(/\r?\n/)) {
620
- if (line.length === 0) continue;
621
- w.eventBus.emit("log", parseRawLogLine(line));
622
- }
623
- };
624
- child.stdout?.on("data", onData);
625
- child.stderr?.on("data", onData);
626
- child.on("message", (msg) => {
627
- if (isThreadMessage(msg)) {
628
- w.eventBus.emit("message", msg);
629
- w.eventBus.emit(`msg:${msg.type}`, msg);
630
- }
631
- });
632
- child.once("exit", (code, signal) => {
633
- w.eventBus.emit("exit", code, signal);
634
- });
635
- child.on("error", (err) => {
636
- w.writeLog("error", "thread.spawn.error", { message: err.message });
637
- w.eventBus.emit("error", err);
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
- w.lastPong = Date.now();
642
- w.heartbeatTimer = setInterval(() => {
643
- if (w.state !== APP_INSTANCE_STATE.RUN) {
644
- clearInterval(w.heartbeatTimer);
645
- return;
646
- }
647
- const since = Date.now() - w.lastPong;
648
- if (since > HEARTBEAT_TIMEOUT_MS) {
649
- clearInterval(w.heartbeatTimer);
650
- w.writeLog("warn", "heartbeat.miss", { since_ms: since });
651
- restartThreadApp(w).catch((err) => w.eventBus.emit("error", err));
652
- return;
653
- }
654
- if (w.child.connected) {
655
- w.child.send(createThreadMessage("ping", {}));
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
- if (isThreadExit(w)) {
661
- return Promise.resolve();
662
- }
663
- return new Promise((resolve) => {
664
- w.writeLog("info", "thread.exit.waiting", { timeout_ms: timeoutMs });
665
- const cleanup = () => {
666
- clearTimeout(slowTimer);
667
- clearTimeout(killTimer);
668
- w.child.off("exit", onExit);
669
- };
670
- const onExit = () => {
671
- cleanup();
672
- resolve();
673
- };
674
- const halfway = Math.floor(timeoutMs / 2);
675
- const slowTimer = setTimeout(() => {
676
- w.writeLog("warn", "thread.exit.slow", { elapsed_ms: halfway });
677
- }, halfway);
678
- const killTimer = setTimeout(() => {
679
- cleanup();
680
- try {
681
- w.writeLog("warn", "thread.exit.force-kill", { timeout_ms: timeoutMs });
682
- w.child.kill("SIGKILL");
683
- } catch {
684
- }
685
- resolve();
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
- if (w.state === "ready") {
692
- return Promise.resolve();
693
- }
694
- return new Promise((resolve, reject) => {
695
- const cleanup = () => {
696
- w.eventBus.off("ready", onReady);
697
- w.eventBus.off("error", onError);
698
- w.eventBus.off("exit", onExit);
699
- };
700
- const onReady = () => {
701
- resolve();
702
- cleanup();
703
- };
704
- const onExit = (code) => {
705
- reject(new Error(`Child exited before ready (code=${code ?? "null"})`));
706
- cleanup();
707
- };
708
- const onError = (error) => {
709
- reject(error);
710
- cleanup();
711
- };
712
- w.eventBus.once("ready", onReady);
713
- w.eventBus.once("error", onError);
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
- return new Promise((resolve) => {
719
- const eventName = `msg:${replyType}`;
720
- const cleanup = () => {
721
- w.eventBus.off(eventName, onReply);
722
- w.eventBus.off("exit", onExit);
723
- clearTimeout(timer);
724
- };
725
- const timer = setTimeout(() => {
726
- cleanup();
727
- w.writeLog("error", "parent.ipc.timeout", {
728
- waiting: replyType,
729
- ms: timeout
730
- });
731
- resolve({
732
- skip: true,
733
- code: "parent.ipc.timeout",
734
- reason: `Thread ${path.basename(w.scriptFile)} timed out waiting for "${replyType}"`
735
- });
736
- }, timeout);
737
- const onReply = (reply) => {
738
- cleanup();
739
- resolve({
740
- success: true,
741
- code: "parent.ipc.recv",
742
- reply
743
- });
744
- };
745
- const onExit = () => {
746
- const reason = `thread.exit (code=${w.child.exitCode} signal=${w.child.signalCode ?? "null"})`;
747
- w.writeLog("debug", "parent.ipc.skipped", {
748
- waiting: replyType,
749
- reason
750
- });
751
- resolve({ skip: true, code: "thread.exit", reason });
752
- return;
753
- };
754
- if (isThreadExit(w)) {
755
- return void onExit();
756
- }
757
- w.eventBus.once("exit", onExit);
758
- w.eventBus.once(eventName, onReply);
759
- w.writeLog("debug", "parent.ipc.send", message);
760
- try {
761
- w.child.send(message);
762
- } catch (err) {
763
- const error = toError(err);
764
- w.writeLog("error", "parent.ipc.fail", {
765
- message,
766
- error: error.message
767
- });
768
- cleanup();
769
- resolve({
770
- skip: true,
771
- code: "parent.ipc.fail",
772
- reason: error.message
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
- const w = {
779
- threadId,
780
- scriptFile,
781
- threadProps,
782
- pid: 0,
783
- state: APP_INSTANCE_STATE.INIT,
784
- restartCount: 0,
785
- lastPong: 0,
786
- child: null,
787
- scheduler: new Scheduler(),
788
- eventBus: new EventEmitter(),
789
- writeLog(level, event, fields) {
790
- w.eventBus.emit("log", {
791
- level,
792
- text: formatLogEvent(event, fields),
793
- ts: Date.now()
794
- });
795
- }
796
- };
797
- w.eventBus.setMaxListeners(0);
798
- const onJobPhase = (phase) => (job) => {
799
- w.writeLog("debug", `job.${job.jobName ?? "untitled"}.${phase}`);
800
- };
801
- w.scheduler.onJob(onJobPhase("queue"));
802
- w.scheduler.onJobStart(onJobPhase("start"));
803
- w.scheduler.onJobComplete(onJobPhase("done"));
804
- w.eventBus.on("exit", (code, signal) => {
805
- const prevState = w.state;
806
- if (signal) {
807
- w.writeLog("warn", "thread.exit.signal", { signal, code });
808
- } else if (code === 0 || code === null) {
809
- w.writeLog("info", "thread.exit.clean", { code });
810
- } else {
811
- w.writeLog("error", "thread.exit.crash", { code });
812
- }
813
- setState(w, "shutdown");
814
- clearInterval(w.heartbeatTimer);
815
- w.pid = 0;
816
- if (prevState === APP_INSTANCE_STATE.RUN && !isShuttingDown()) {
817
- restartThreadApp(w).catch((err) => w.eventBus.emit("error", err));
818
- }
819
- });
820
- w.eventBus.on("message", (msg) => {
821
- w.writeLog("debug", "parent.ipc.recv", msg);
822
- switch (msg.type) {
823
- case "app-state": {
824
- setState(w, msg.state);
825
- break;
826
- }
827
- case "ready": {
828
- w.pid = msg.pid;
829
- w.eventBus.emit("pid", msg.pid);
830
- setState(w, "ready");
831
- w.writeLog("debug", "thread.spawn.ready");
832
- w.eventBus.emit("ready");
833
- break;
834
- }
835
- case "pong": {
836
- w.lastPong = Date.now();
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
- w.writeLog("debug", `lifecycle.${phase}.begin`);
846
- return fn().then(
847
- (result) => {
848
- w.writeLog("debug", `lifecycle.${phase}.done`);
849
- return result;
850
- },
851
- (error) => {
852
- w.writeLog("error", `lifecycle.${phase}.fail`, {
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
- return w.scheduler.queueJobWait(
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
- setState(w, APP_INSTANCE_STATE.IN_SETUP);
867
- return sendAndWait(
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
- return w.scheduler.queueJobWait(
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
- setState(w, APP_INSTANCE_STATE.IN_RUN);
883
- return sendAndWait(w, createThreadMessage("start", {}), "start_done").then(
884
- () => {
885
- if (w.state === APP_INSTANCE_STATE.RUN) {
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
- return w.scheduler.queueJobWait(
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
- setState(w, APP_INSTANCE_STATE.IN_STOP);
899
- clearInterval(w.heartbeatTimer);
900
- return sendAndWait(w, createThreadMessage("stop", {}), "stop_done").then(
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
- return w.scheduler.queueJobWait(
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
- setState(w, "shutdown");
912
- clearInterval(w.heartbeatTimer);
913
- return (w.child.connected ? sendAndWait(
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
- if (w.restartCount >= MAX_RESTARTS) {
922
- return Promise.reject(
923
- new Error(`Thread ${w.threadId} exceeded max restarts (${MAX_RESTARTS})`)
924
- );
925
- }
926
- return w.scheduler.queueJobWait(
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
- return isObject(value) && !!value.vrun_app_thread_message;
807
+ return isObject(value) && !!value.vrun_app_thread_message;
941
808
  }
942
809
  function createThreadMessage(type, msg) {
943
- return {
944
- type,
945
- ...msg,
946
- vrun_app_thread_message: true
947
- };
810
+ return {
811
+ type,
812
+ ...msg,
813
+ vrun_app_thread_message: true
814
+ };
948
815
  }
949
816
  function parseRawLogLine(line) {
950
- const trimmed = line.trimEnd();
951
- const lower = trimmed.toLowerCase();
952
- let level = "info";
953
- if (lower.includes("error") || lower.includes("\u2716") || lower.includes("[error]")) {
954
- level = "error";
955
- } else if (lower.includes("warn") || lower.includes("\u26A0")) {
956
- level = "warn";
957
- } else if (lower.includes("debug") || lower.includes("\u2699")) {
958
- level = "debug";
959
- }
960
- return { ts: Date.now(), level, text: trimmed };
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
- return w.child.exitCode !== null || w.child.signalCode !== null || !w.child.connected;
830
+ return w.child.exitCode !== null || w.child.signalCode !== null || !w.child.connected;
964
831
  }
965
-
966
832
  function createAppThread(definition) {
967
- const scriptFile = definition.filePath;
968
- assert.ok(
969
- scriptFile,
970
- `Failed to detect definition file path for: ${definition.name}`
971
- );
972
- const app = defineApp({
973
- name: definition.name,
974
- description: definition.description,
975
- props: {
976
- ...definition.props || {},
977
- threads: {
978
- type: Number,
979
- description: "Amount of threads",
980
- default: () => 1,
981
- parser: (v) => parseInt(v)
982
- }
983
- },
984
- logger: false,
985
- setup(props) {
986
- const { threads = 1, ...threadProps } = props;
987
- assert.greaterThan(threads, 0, "threads expected to be greater than 0");
988
- const managedThreads = Array.from(
989
- { length: threads },
990
- (_, i) => initThread(i + 1, scriptFile, threadProps)
991
- );
992
- if (props.__inheritIO !== false) {
993
- for (const w of managedThreads) {
994
- const childLogger = createAppLogger({
995
- ...definition,
996
- name: `${definition.name}.${w.threadId}`
997
- });
998
- w.eventBus.on("log", (entry) => {
999
- childLogger[entry.level](entry.text);
1000
- });
1001
- }
1002
- }
1003
- return Promise.all(managedThreads.map((w) => waitForThreadReady(w))).then(() => Promise.all(managedThreads.map((w) => setupThreadApp(w)))).then(() => ({ managedThreads }));
1004
- },
1005
- entry() {
1006
- return Promise.all(this.managedThreads.map((w) => startThreadApp(w))).then(
1007
- noop
1008
- );
1009
- },
1010
- stop() {
1011
- return Promise.all(this.managedThreads.map((w) => stopThreadApp(w))).then(
1012
- noop
1013
- );
1014
- },
1015
- shutdown() {
1016
- return Promise.all(
1017
- this.managedThreads.map((w) => shutdownThreadApp(w))
1018
- ).then(() => {
1019
- this.managedThreads = [];
1020
- });
1021
- }
1022
- });
1023
- return app;
1024
- }
1025
- function createAppThreadInstance({
1026
- definition,
1027
- parentPort,
1028
- threadId,
1029
- onShutdown = noop
1030
- }) {
1031
- definition = { ...definition };
1032
- definition.name += "." + threadId;
1033
- const instance = createAppInstance(definition);
1034
- const log = instance.logger;
1035
- const postMessage = (message, cb) => {
1036
- log.debug(formatLogEvent("child.ipc.send", message));
1037
- parentPort.postMessage(message, cb);
1038
- };
1039
- instance.eventBus.on("state", (newState) => {
1040
- postMessage(createThreadMessage("app-state", { state: newState }));
1041
- });
1042
- parentPort.on("message", async (message) => {
1043
- if (!isThreadMessage(message)) return;
1044
- log.debug(formatLogEvent("child.ipc.recv", message));
1045
- switch (message.type) {
1046
- case "setup": {
1047
- const result = await setupApp(instance, message.props);
1048
- postMessage(createThreadMessage("setup_done", { result }));
1049
- break;
1050
- }
1051
- case "start": {
1052
- const result = await runApp$1(instance);
1053
- postMessage(createThreadMessage("start_done", { result }));
1054
- break;
1055
- }
1056
- case "stop": {
1057
- const result = await stopApp(instance);
1058
- postMessage(createThreadMessage("stop_done", { result }));
1059
- break;
1060
- }
1061
- case "shutdown": {
1062
- const result = await shutdownApp(instance);
1063
- postMessage(createThreadMessage("shutdown_done", { result }), () => {
1064
- setImmediate(() => {
1065
- catchError(onShutdown);
1066
- if (isFunction(parentPort.close)) {
1067
- parentPort.close();
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
- ...TYPESCRIPT_EXTENSIONS,
1091
- ...JAVASCRIPT_EXTENSIONS,
1092
- "json"
936
+ ...TYPESCRIPT_EXTENSIONS,
937
+ ...JAVASCRIPT_EXTENSIONS,
938
+ "json"
1093
939
  ];
1094
940
  function createProject(projectPath) {
1095
- const packageJsonPath = locateNearestFile(projectPath, "package.json");
1096
- assert.ok(
1097
- packageJsonPath,
1098
- "package.json not found in path: " + path.dirname(projectPath)
1099
- );
1100
- const packageJson = importJson(packageJsonPath);
1101
- const projectLocation = path.dirname(packageJsonPath);
1102
- const project = {
1103
- path: projectLocation,
1104
- name: packageJson.name || path.basename(projectPath),
1105
- description: packageJson.description || "",
1106
- version: packageJson.version || "0.0.0",
1107
- tsconfigPath: locateNearestFile(projectLocation, "tsconfig.json"),
1108
- dotenvPath: locateNearestFile(projectLocation, ".env"),
1109
- autoImports: ["inversify.config"],
1110
- importLookupPaths: createImportLookupPaths(projectLocation),
1111
- allowTs: false,
1112
- packageJson
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
- const existedFile = projectResolveFile(project, filePath);
1119
- if (!existedFile) {
1120
- throw new Error(
1121
- `Cannot resolve import for path.
1122
- Path:
1123
- ${filePath}
1124
- Available extensions: ${ALL_EXTENSIONS.join(", ")}`
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
- const pathVariants = project.importLookupPaths.map(
1136
- (dir) => path.resolve(dir, filePath)
1137
- );
1138
- for (const filePath2 of pathVariants) {
1139
- const pathVariants2 = ALL_EXTENSIONS.includes(
1140
- getFileExtension(filePath2, false)
1141
- ) ? [filePath2] : [...ALL_EXTENSIONS.map((ext) => `${filePath2}.${ext}`)];
1142
- const existedPath = pathVariants2.find((v) => fs.existsSync(v));
1143
- if (existedPath) {
1144
- return existedPath;
1145
- }
1146
- }
1147
- return null;
1148
- }
1149
- async function projectLoadEnv(project) {
1150
- if (!project.dotenvPath) {
1151
- return false;
1152
- }
1153
- dotenv.config({
1154
- path: project.dotenvPath,
1155
- quiet: true
1156
- });
1157
- return true;
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
- const tags = [];
1174
- if (project.allowTs) {
1175
- tags.push(getColor("bgBlue")(" TS "));
1176
- }
1177
- if (CONFIG.WATCH_MODE) {
1178
- tags.push(getColor("bgMagenta")(" WATCH "));
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
- const cwd = process.cwd();
1199
- const result = [];
1200
- if (appRoot) {
1201
- result.push(path.resolve(cwd, appRoot));
1202
- }
1203
- result.push(path.resolve(cwd));
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
- let currentDir = path.extname(location) ? path.dirname(path.resolve(location)) : path.resolve(location);
1209
- while (currentDir) {
1210
- const filePath = path.join(currentDir, fileName);
1211
- if (fs.existsSync(filePath)) {
1212
- return filePath;
1213
- }
1214
- const parentDir = path.dirname(currentDir);
1215
- if (parentDir === currentDir) break;
1216
- currentDir = parentDir;
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
- if (!filePath.endsWith(".json")) {
1222
- filePath += ".json";
1223
- }
1224
- if (!fs.existsSync(filePath)) {
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
- class RingBuffer {
1235
- constructor(cap) {
1236
- this.cap = cap;
1237
- }
1238
- buf = [];
1239
- start = 0;
1240
- push(item) {
1241
- if (this.buf.length < this.cap) {
1242
- this.buf.push(item);
1243
- } else {
1244
- this.buf[this.start] = item;
1245
- this.start = (this.start + 1) % this.cap;
1246
- }
1247
- }
1248
- size() {
1249
- return this.buf.length;
1250
- }
1251
- toArray() {
1252
- if (this.start === 0) return this.buf.slice();
1253
- return this.buf.slice(this.start).concat(this.buf.slice(0, this.start));
1254
- }
1255
- clear() {
1256
- this.buf = [];
1257
- this.start = 0;
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
- stop: () => Promise.resolve(),
1262
- start: () => Promise.resolve(),
1263
- restart: () => Promise.resolve()
1055
+ stop: () => Promise.resolve(),
1056
+ start: () => Promise.resolve(),
1057
+ restart: () => Promise.resolve()
1264
1058
  };
1265
1059
  function createTuiStore() {
1266
- const emitter = new EventEmitter();
1267
- emitter.setMaxListeners(0);
1268
- const logs = /* @__PURE__ */ new Map();
1269
- const apps = [];
1270
- const bufferOf = (id) => {
1271
- let buf = logs.get(id);
1272
- if (!buf) {
1273
- buf = new RingBuffer(LOG_BUFFER_CAP);
1274
- logs.set(id, buf);
1275
- }
1276
- return buf;
1277
- };
1278
- const notify = () => {
1279
- emitter.emit("change");
1280
- };
1281
- const isSelectedFeed = (nodeId) => {
1282
- if (!store.selectedId) return false;
1283
- if (store.selectedId === nodeId) return true;
1284
- for (const app of apps) {
1285
- if (app.id !== store.selectedId) continue;
1286
- return !!app.threads?.some((t) => t.id === nodeId);
1287
- }
1288
- return false;
1289
- };
1290
- const store = {
1291
- apps,
1292
- logs,
1293
- system: null,
1294
- selectedId: null,
1295
- filter: "info",
1296
- logScroll: 0,
1297
- handlers: noopHandlers,
1298
- pushLog(nodeId, entry) {
1299
- bufferOf(nodeId).push(entry);
1300
- if (store.logScroll > 0 && isSelectedFeed(nodeId) && passesFilter(entry, store.filter)) {
1301
- store.logScroll += 1;
1302
- }
1303
- notify();
1304
- },
1305
- setState(nodeId, state) {
1306
- for (const app of apps) {
1307
- if (app.id === nodeId) {
1308
- app.state = state;
1309
- notify();
1310
- return;
1311
- }
1312
- if (app.threads) {
1313
- for (const t of app.threads) {
1314
- if (t.id === nodeId) {
1315
- t.state = state;
1316
- notify();
1317
- return;
1318
- }
1319
- }
1320
- }
1321
- }
1322
- },
1323
- setSelected(nodeId) {
1324
- store.selectedId = nodeId;
1325
- store.logScroll = 0;
1326
- notify();
1327
- },
1328
- setFilter(filter) {
1329
- store.filter = filter;
1330
- store.logScroll = 0;
1331
- notify();
1332
- },
1333
- setLogScroll(offset) {
1334
- store.logScroll = Math.max(0, offset);
1335
- notify();
1336
- },
1337
- setSystem(entry) {
1338
- store.system = entry;
1339
- notify();
1340
- },
1341
- toggleExpand(appId) {
1342
- for (const app of apps) {
1343
- if (app.id === appId) {
1344
- app.expanded = !app.expanded;
1345
- notify();
1346
- return;
1347
- }
1348
- }
1349
- },
1350
- addApp(node) {
1351
- apps.push(node);
1352
- if (!store.selectedId) store.selectedId = node.id;
1353
- notify();
1354
- },
1355
- setThreads(appId, threads) {
1356
- for (const app of apps) {
1357
- if (app.id === appId) {
1358
- app.threads = threads;
1359
- notify();
1360
- return;
1361
- }
1362
- }
1363
- },
1364
- setPid(nodeId, pid) {
1365
- for (const app of apps) {
1366
- if (app.threads) {
1367
- for (const t of app.threads) {
1368
- if (t.id === nodeId) {
1369
- t.pid = pid;
1370
- notify();
1371
- return;
1372
- }
1373
- }
1374
- }
1375
- }
1376
- },
1377
- getLogs(nodeId) {
1378
- const buf = logs.get(nodeId);
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
- debug: 0,
1394
- log: 1,
1395
- info: 2,
1396
- warn: 3,
1397
- error: 4
1175
+ debug: 0,
1176
+ log: 1,
1177
+ info: 2,
1178
+ warn: 3,
1179
+ error: 4
1398
1180
  };
1399
1181
  const FILTER_THRESHOLD = {
1400
- all: 0,
1401
- info: LEVEL_RANK.info,
1402
- warn: LEVEL_RANK.warn,
1403
- error: LEVEL_RANK.error
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
- return LEVEL_RANK[entry.level] >= FILTER_THRESHOLD[filter];
1188
+ return LEVEL_RANK[entry.level] >= FILTER_THRESHOLD[filter];
1407
1189
  }
1408
1190
  function getSelectedFilteredCount(store) {
1409
- if (!store.selectedId) return 0;
1410
- const passes = (e) => passesFilter(e, store.filter);
1411
- for (const app of store.apps) {
1412
- if (app.id !== store.selectedId) continue;
1413
- if (!app.threads || app.threads.length === 0) {
1414
- return store.getLogs(app.id).filter(passes).length;
1415
- }
1416
- let count = 0;
1417
- for (const t of app.threads) {
1418
- for (const e of store.getLogs(t.id)) if (passes(e)) count += 1;
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
- switch (filter) {
1426
- case "all":
1427
- return "info";
1428
- case "info":
1429
- return "warn";
1430
- case "warn":
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
- const out = [];
1438
- for (const app of apps) {
1439
- out.push(app);
1440
- if (app.expanded && app.threads) {
1441
- for (const t of app.threads) out.push(t);
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
- const store = useContext(TuiStoreContext);
1450
- if (!store) throw new Error("TuiStoreContext is not provided");
1451
- return store;
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
- const store = useTuiStore();
1455
- const [version, setVersion] = useState(0);
1456
- useEffect(() => {
1457
- return store.subscribe(() => setVersion((v) => v + 1));
1458
- }, [store]);
1459
- return version;
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
- "in-run": "yellow",
1464
- run: "green",
1465
- "in-stop": "yellow",
1466
- "in-setup": "yellow",
1467
- setup: "yellow",
1468
- ready: "gray",
1469
- "in-shutdown": "yellow",
1470
- init: "gray",
1471
- shutdown: "gray",
1472
- stop: "gray",
1473
- error: "red"
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
- return n.kind === "app";
1246
+ return n.kind === "app";
1477
1247
  }
1478
1248
  function rowLength(n) {
1479
- if (n.kind === "app") return 1 + 1 + 1 + 1 + 1 + (n.name?.length ?? 0);
1480
- return 1 + 2 + 1 + 1 + `PID ${n.pid}`.length;
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
- useStoreSnapshot();
1484
- const store = useTuiStore();
1485
- const nodes = flattenVisibleTree(store.apps);
1486
- const maxRow = nodes.reduce(
1487
- (m, n) => Math.max(m, rowLength(n)),
1488
- "Apps".length
1489
- );
1490
- const width = maxRow + 4;
1491
- return /* @__PURE__ */ jsxs(
1492
- Box,
1493
- {
1494
- flexDirection: "column",
1495
- width,
1496
- flexShrink: 0,
1497
- borderStyle: "single",
1498
- paddingX: 1,
1499
- overflow: "hidden",
1500
- children: [
1501
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Apps" }),
1502
- nodes.map((node) => {
1503
- const selected = node.id === store.selectedId;
1504
- const color = STATE_COLORS[node.state];
1505
- const marker = selected ? "\u258C" : " ";
1506
- if (isApp(node)) {
1507
- const caret = node.threads && node.threads.length ? node.expanded ? "\u25BE" : "\u25B8" : " ";
1508
- return /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", bold: selected, children: [
1509
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: marker }),
1510
- caret,
1511
- " ",
1512
- /* @__PURE__ */ jsx(Text, { color, children: "\u25CF" }),
1513
- " ",
1514
- node.name
1515
- ] }, node.id);
1516
- }
1517
- return /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", bold: selected, children: [
1518
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: marker }),
1519
- " ",
1520
- /* @__PURE__ */ jsx(Text, { color, children: "\u25CF" }),
1521
- " ",
1522
- node.name
1523
- ] }, node.id);
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
- debug: "gray",
1532
- log: "white",
1533
- info: "cyan",
1534
- warn: "yellow",
1535
- error: "red"
1312
+ debug: "gray",
1313
+ log: "white",
1314
+ info: "cyan",
1315
+ warn: "yellow",
1316
+ error: "red"
1536
1317
  };
1537
1318
  function formatTime(ts) {
1538
- const d = new Date(ts);
1539
- const pad = (n) => n.toString().padStart(2, "0");
1540
- return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
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
- if (!store.selectedId) return null;
1544
- for (const app of store.apps) {
1545
- if (app.id === store.selectedId) {
1546
- const threads = app.threads ?? [];
1547
- const pids = Array.from(
1548
- new Set(threads.map((t) => t.pid).filter((p) => p > 0))
1549
- );
1550
- return {
1551
- appName: app.name,
1552
- processCount: threads.length,
1553
- pids,
1554
- states: threads.map((v) => v.state),
1555
- showProcessTag: threads.length > 1
1556
- };
1557
- }
1558
- if (app.threads) {
1559
- for (const t of app.threads) {
1560
- if (t.id === store.selectedId) {
1561
- return {
1562
- appName: app.name,
1563
- pid: t.pid,
1564
- pids: t.pid > 0 ? [t.pid] : [],
1565
- state: t.state,
1566
- states: [t.state],
1567
- processCount: app.threads.length,
1568
- showProcessTag: false
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
- if (pids.length === 0) return "?";
1578
- if (pids.length === 1) return String(pids[0]);
1579
- return pids.join(", ");
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
- if (!store.selectedId) return [];
1583
- for (const app of store.apps) {
1584
- if (app.id !== store.selectedId) continue;
1585
- if (!app.threads || app.threads.length === 0) {
1586
- return store.getLogs(app.id);
1587
- }
1588
- const merged = [];
1589
- for (const t of app.threads) {
1590
- for (const entry of store.getLogs(t.id)) {
1591
- merged.push(
1592
- info.showProcessTag ? { ...entry, text: `[${t.pid}] ${entry.text}` } : entry
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
- useStoreSnapshot();
1603
- const store = useTuiStore();
1604
- const { stdout } = useStdout();
1605
- const rows = stdout?.rows ?? 24;
1606
- const visibleRows = Math.max(1, rows - 5);
1607
- const info = describeSelection(store);
1608
- let entries = [];
1609
- let scrollLabel = "";
1610
- if (info) {
1611
- const all = gatherEntries(store, info);
1612
- const filtered = all.filter((e) => passesFilter(e, store.filter));
1613
- const total = filtered.length;
1614
- const maxOffset = Math.max(0, total - visibleRows);
1615
- const offset = Math.min(store.logScroll, maxOffset);
1616
- const end = total - offset;
1617
- const start = Math.max(0, end - visibleRows);
1618
- entries = filtered.slice(start, end);
1619
- if (offset > 0) scrollLabel = ` \xB7 scrolled +${offset}`;
1620
- }
1621
- return /* @__PURE__ */ jsxs(
1622
- Box,
1623
- {
1624
- flexDirection: "column",
1625
- flexGrow: 1,
1626
- flexShrink: 1,
1627
- borderStyle: "single",
1628
- paddingX: 1,
1629
- overflow: "hidden",
1630
- children: [
1631
- /* @__PURE__ */ jsx(Box, { flexShrink: 0, flexDirection: "column", children: info ? /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
1632
- /* @__PURE__ */ jsxs(Text, { bold: true, wrap: "truncate-end", children: [
1633
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: info.appName }),
1634
- info.processCount > 1 && info.pid == null ? /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1635
- " (",
1636
- info.processCount,
1637
- " processes)"
1638
- ] }) : null,
1639
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1640
- " ",
1641
- "\xB7 filter: ",
1642
- store.filter,
1643
- scrollLabel
1644
- ] })
1645
- ] }),
1646
- /* @__PURE__ */ jsxs(Text, { bold: true, wrap: "truncate-end", children: [
1647
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1648
- "STATE ",
1649
- info.states.join(", ")
1650
- ] }),
1651
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " \xB7 " }),
1652
- /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
1653
- "PID ",
1654
- formatPids(info.pids)
1655
- ] })
1656
- ] })
1657
- ] }) : /* @__PURE__ */ jsx(Text, { bold: true, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: " (no selection)" }) }) }),
1658
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", children: entries.map((entry, idx) => /* @__PURE__ */ jsxs(Text, { wrap: "wrap", children: [
1659
- /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1660
- formatTime(entry.ts),
1661
- " "
1662
- ] }),
1663
- /* @__PURE__ */ jsx(Text, { color: LEVEL_COLORS[entry.level], children: entry.level.toUpperCase().padEnd(5) }),
1664
- " ",
1665
- entry.text
1666
- ] }, idx)) })
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
- useStoreSnapshot();
1674
- const store = useTuiStore();
1675
- const sys = store.system;
1676
- return /* @__PURE__ */ jsxs(
1677
- Box,
1678
- {
1679
- flexDirection: "row",
1680
- justifyContent: "space-between",
1681
- paddingX: 1,
1682
- flexShrink: 0,
1683
- height: 1,
1684
- children: [
1685
- /* @__PURE__ */ jsx(Box, { flexShrink: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(Text, { wrap: "truncate-end", children: sys ? /* @__PURE__ */ jsxs(Fragment, { children: [
1686
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "[system] " }),
1687
- /* @__PURE__ */ jsx(Text, { color: sys.level === "error" ? "red" : "white", children: sys.text })
1688
- ] }) : /* @__PURE__ */ jsx(Text, { color: "gray", children: "[system] ready" }) }) }),
1689
- /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u2191/\u2193 \xB7 \u2192/\u2190 \xB7 PgUp/PgDn \xB7 s \xB7 \u21E7S \xB7 r \xB7 f \xB7 q" }) })
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
- for (const app of store.apps) {
1697
- if (app.id === id) return { node: app, app };
1698
- if (app.threads) {
1699
- for (const t of app.threads) {
1700
- if (t.id === id) return { node: t, app };
1701
- }
1702
- }
1703
- }
1704
- return null;
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
- useStoreSnapshot();
1708
- const { stdout } = useStdout();
1709
- const rows = stdout?.rows ?? 24;
1710
- useInput((input, key) => {
1711
- const visible = flattenVisibleTree(store.apps);
1712
- if (visible.length === 0) return;
1713
- const currentIdx = visible.findIndex((n) => n.id === store.selectedId);
1714
- const idx = currentIdx < 0 ? 0 : currentIdx;
1715
- const selected = visible[idx];
1716
- const visibleLogRows = Math.max(1, rows - 5);
1717
- const halfPage = Math.max(1, Math.floor(visibleLogRows / 2));
1718
- if (key.upArrow && key.shift) {
1719
- const maxOffset = Math.max(
1720
- 0,
1721
- getSelectedFilteredCount(store) - visibleLogRows
1722
- );
1723
- store.setLogScroll(Math.min(maxOffset, store.logScroll + halfPage));
1724
- return;
1725
- }
1726
- if (key.downArrow && key.shift) {
1727
- store.setLogScroll(Math.max(0, store.logScroll - halfPage));
1728
- return;
1729
- }
1730
- if (key.home) {
1731
- const total = getSelectedFilteredCount(store);
1732
- store.setLogScroll(Math.max(0, total - visibleLogRows));
1733
- return;
1734
- }
1735
- if (key.end) {
1736
- store.setLogScroll(0);
1737
- return;
1738
- }
1739
- if (key.upArrow) {
1740
- const next = visible[Math.max(0, idx - 1)];
1741
- if (next) store.setSelected(next.id);
1742
- return;
1743
- }
1744
- if (key.downArrow) {
1745
- const next = visible[Math.min(visible.length - 1, idx + 1)];
1746
- if (next) store.setSelected(next.id);
1747
- return;
1748
- }
1749
- if (key.rightArrow) {
1750
- if (selected.kind === "app" && selected.threads && selected.threads.length) {
1751
- if (!selected.expanded) store.toggleExpand(selected.id);
1752
- }
1753
- return;
1754
- }
1755
- if (key.leftArrow) {
1756
- if (selected.kind === "app" && selected.expanded) {
1757
- store.toggleExpand(selected.id);
1758
- } else if (selected.kind === "thread") {
1759
- store.setSelected(selected.parentId);
1760
- }
1761
- return;
1762
- }
1763
- if (input === "f") {
1764
- store.setFilter(nextFilter(store.filter));
1765
- return;
1766
- }
1767
- if (input === "s") {
1768
- const target = findNodeAndApp(store, selected.id);
1769
- if (target) void store.handlers.stop(target.node.id);
1770
- return;
1771
- }
1772
- if (input === "S") {
1773
- const target = findNodeAndApp(store, selected.id);
1774
- if (target) void store.handlers.start(target.node.id);
1775
- return;
1776
- }
1777
- if (input === "r") {
1778
- const target = findNodeAndApp(store, selected.id);
1779
- if (target) void store.handlers.restart(target.node.id);
1780
- return;
1781
- }
1782
- if (input === "q" || key.ctrl && input === "c") {
1783
- void onExit();
1784
- return;
1785
- }
1786
- });
1787
- const columns = stdout?.columns ?? 80;
1788
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [
1789
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, width: columns, children: [
1790
- /* @__PURE__ */ jsx(AppList, {}),
1791
- /* @__PURE__ */ jsx(LogView, {})
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
- return /* @__PURE__ */ jsx(TuiStoreContext.Provider, { value: props.store, children: /* @__PURE__ */ jsx(InnerTui, { ...props }) });
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
- if (isSingle) return { ...props };
1802
- return getPrefixedProps(definition, props);
1620
+ if (isSingle) return { ...props };
1621
+ return getPrefixedProps(definition, props);
1803
1622
  }
1804
1623
  function appIdFor(definition, idx) {
1805
- return `${definition.name}#${idx}`;
1624
+ return `${definition.name}#${idx}`;
1806
1625
  }
1807
1626
  function resolveThreadCount(appProps) {
1808
- if (isNumber(appProps.threads) && appProps.threads > 0) {
1809
- return appProps.threads;
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
- error: 10,
1815
- run: 9,
1816
- "in-run": 8,
1817
- setup: 7,
1818
- "in-setup": 6,
1819
- stop: 5,
1820
- "in-stop": 4,
1821
- shutdown: 3,
1822
- "in-shutdown": 2,
1823
- ready: 1,
1824
- init: 0
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
- let best = "init";
1828
- for (const w of threads) {
1829
- const s = APP_STATE_RANK[w.state];
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
- const store = createTuiStore();
1836
- const records = [];
1837
- const isSingle = definitions.length === 1;
1838
- if (props.watch) {
1839
- store.setSystem({
1840
- ts: Date.now(),
1841
- level: "warn",
1842
- text: "TUI does not yet support --watch mode;"
1843
- });
1844
- }
1845
- for (let i = 0; i < definitions.length; i++) {
1846
- const def = definitions[i];
1847
- const appId = appIdFor(def, i);
1848
- const appProps = getAppProps(def, props, isSingle);
1849
- const threadCount = resolveThreadCount(appProps);
1850
- appProps.__inheritIO = false;
1851
- delete appProps.threads;
1852
- const appNode = {
1853
- kind: "app",
1854
- id: appId,
1855
- name: def.name,
1856
- state: APP_INSTANCE_STATE.INIT,
1857
- expanded: false,
1858
- threads: []
1859
- };
1860
- store.addApp(appNode);
1861
- const threads = [];
1862
- const threadNodes = [];
1863
- for (let threadId = 1; threadId <= threadCount; threadId++) {
1864
- const nodeId = `${appId}:${threadId}`;
1865
- const threadNode = {
1866
- id: `${appId}:${threadId}`,
1867
- kind: "thread",
1868
- name: `${appNode.name}.${threadId}`,
1869
- parentId: appId,
1870
- threadId,
1871
- pid: 0,
1872
- state: APP_INSTANCE_STATE.INIT
1873
- };
1874
- threadNodes.push(threadNode);
1875
- const w = initThread(threadId, def.filePath, appProps);
1876
- w.eventBus.on("log", (entry) => {
1877
- store.pushLog(nodeId, {
1878
- ts: entry.ts,
1879
- level: entry.level,
1880
- text: entry.text
1881
- });
1882
- });
1883
- w.eventBus.on("state", (newState) => {
1884
- store.setState(nodeId, newState);
1885
- store.setState(appId, aggregateAppState(threads));
1886
- });
1887
- w.eventBus.on("pid", (pid) => {
1888
- store.setPid(nodeId, pid);
1889
- });
1890
- w.eventBus.on("error", (err) => {
1891
- store.pushLog(nodeId, {
1892
- ts: Date.now(),
1893
- level: "error",
1894
- text: `Thread error: ${err.message}`
1895
- });
1896
- });
1897
- threads.push(w);
1898
- }
1899
- store.setThreads(appId, threadNodes);
1900
- records.push({ appId, node: appNode, threads });
1901
- }
1902
- const findRecordByAppId = (id) => records.find((r) => r.appId === id);
1903
- const findThread = (id) => {
1904
- for (const r of records) {
1905
- for (const w of r.threads) {
1906
- if (`${r.appId}:${w.threadId}` === id) return { record: r, thread: w };
1907
- }
1908
- }
1909
- return void 0;
1910
- };
1911
- const startSingleThread = (w) => {
1912
- if (w.state === APP_INSTANCE_STATE.IN_RUN || APP_INSTANCE_STATE.RUN) {
1913
- return Promise.resolve();
1914
- }
1915
- if (w.state === APP_INSTANCE_STATE.STOP) return startThreadApp(w);
1916
- return setupThreadApp(w).then(() => startThreadApp(w));
1917
- };
1918
- store.handlers = {
1919
- stop(id) {
1920
- const r = findRecordByAppId(id);
1921
- if (r)
1922
- return Promise.all(r.threads.map((w) => stopThreadApp(w))).then(() => {
1923
- });
1924
- const found = findThread(id);
1925
- if (found) return stopThreadApp(found.thread);
1926
- return Promise.resolve();
1927
- },
1928
- start(id) {
1929
- const r = findRecordByAppId(id);
1930
- if (r) {
1931
- return Promise.all(r.threads.map(startSingleThread)).then(() => {
1932
- });
1933
- }
1934
- const found = findThread(id);
1935
- if (found) return startSingleThread(found.thread);
1936
- return Promise.resolve();
1937
- },
1938
- restart(id) {
1939
- const r = findRecordByAppId(id);
1940
- if (r) {
1941
- return Promise.all(
1942
- r.threads.map((w) => {
1943
- w.restartCount = 0;
1944
- return restartThreadApp(w);
1945
- })
1946
- ).then(() => {
1947
- });
1948
- }
1949
- const found = findThread(id);
1950
- if (found) {
1951
- found.thread.restartCount = 0;
1952
- return restartThreadApp(found.thread);
1953
- }
1954
- return Promise.resolve();
1955
- }
1956
- };
1957
- const startRecord = (r) => Promise.all(
1958
- r.threads.map(
1959
- (w) => waitForThreadReady(w).then(() => setupThreadApp(w)).then(() => startThreadApp(w))
1960
- )
1961
- ).then(noop);
1962
- onShutdownError((error) => {
1963
- console.error("[Graceful Shutdown] error:", error);
1964
- });
1965
- const q = defer();
1966
- onShutdown("app", () => {
1967
- store.setSystem({
1968
- ts: Date.now(),
1969
- level: "warn",
1970
- text: "shutdown initiated"
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
- class TempDB {
1993
- constructor(filepath) {
1994
- this.filepath = filepath;
1995
- }
1996
- _storage = /* @__PURE__ */ new Map();
1997
- _dirty = false;
1998
- get isDirty() {
1999
- return this._dirty;
2000
- }
2001
- get(key) {
2002
- return this._storage.get(key);
2003
- }
2004
- set(key, value) {
2005
- this._storage.set(key, value);
2006
- this._dirty = true;
2007
- }
2008
- save() {
2009
- if (!this._dirty) return;
2010
- const data = EJSON.stringify(Array.from(this._storage.entries()));
2011
- fs.writeFileSync(this.filepath, data);
2012
- this._dirty = false;
2013
- }
2014
- load() {
2015
- try {
2016
- const data = EJSON.parse(fs.readFileSync(this.filepath, "utf-8"));
2017
- for (const [key, value] of data) {
2018
- this._storage.set(key, value);
2019
- }
2020
- return true;
2021
- } catch (_) {
2022
- return false;
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
- [String, "<string>"],
2031
- [Boolean, "<boolean>"],
2032
- [Number, "<number>"],
2033
- [Date, "<date>"],
2034
- [Array, "<array>"]
1824
+ [String, "<string>"],
1825
+ [Number, "<number>"],
1826
+ [BigInt, "<bigint>"],
1827
+ [Date, "<date>"],
1828
+ [Array, "<array>"]
2035
1829
  ]);
2036
1830
  function propsToOptions(props) {
2037
- const result = [];
2038
- for (const [
2039
- name,
2040
- {
2041
- description,
2042
- type,
2043
- default: defaultValue,
2044
- required,
2045
- enum: variants,
2046
- alias,
2047
- env,
2048
- parser
2049
- }
2050
- ] of Object.entries(props)) {
2051
- const flagName = kebabCase(name);
2052
- const typePlaceholder = TYPE_TO_PLACEHOLDER.get(type);
2053
- const envVariables = arrayable(env);
2054
- const option = new Option(
2055
- [alias ? `-${alias}, ` : "", `--${flagName}`, typePlaceholder].filter(Boolean).join(" "),
2056
- description
2057
- );
2058
- if (variants?.length) {
2059
- option.choices(variants);
2060
- }
2061
- if (envVariables.length) {
2062
- option.env(envVariables.join(", "));
2063
- option.default(getEnv(envVariables) || defaultValue?.());
2064
- } else if (defaultValue) {
2065
- option.default(defaultValue());
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
- for (const env of variables) {
2089
- const value = process.env[env];
2090
- if (value) {
2091
- return value;
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
- const parsedValue = parseInt(value, 10);
2097
- if (!isNumber(parsedValue)) {
2098
- throw new InvalidArgumentError("Not a number.");
2099
- }
2100
- return parsedValue;
2101
- }
2102
-
2103
- function runApp({
2104
- scriptFile,
2105
- cliName = "app",
2106
- cliDescription = "Application cli",
2107
- argv = process.argv
2108
- }) {
2109
- const promise = scriptFile ? loadScriptCommand(scriptFile) : Promise.resolve(buildRootCommand({ cliName, cliDescription }));
2110
- return promise.then((program) => program.parseAsync(["node", cliName, ...argv])).then(noop).catch((error) => {
2111
- log.error("Failed to run app:", error);
2112
- process.exit(1);
2113
- });
2114
- }
2115
- function buildRootCommand({
2116
- cliDescription,
2117
- cliName
2118
- }) {
2119
- const program = new Command().name(cliName).description(cliDescription).enablePositionalOptions().passThroughOptions().helpOption(false).helpCommand(false).action(function() {
2120
- const cmd = this;
2121
- if (isHelpArgument(cmd.args)) {
2122
- return cmd.help();
2123
- }
2124
- });
2125
- 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) {
2126
- assert.arrayStrings(scriptFiles, "scriptFiles expected string[]");
2127
- const cmd = this;
2128
- const remainingArgs = cmd.args;
2129
- const flagIdx = scriptFiles.findIndex((v) => v.startsWith("-"));
2130
- scriptFiles = (flagIdx > -1 ? scriptFiles.slice(0, flagIdx) : scriptFiles).filter((v) => !v.startsWith("-"));
2131
- if (!scriptFiles.length) {
2132
- return cmd.parent?.args?.includes("start") ? cmd.help() : program.help();
2133
- }
2134
- const project = createProject(path.resolve(scriptFiles[0]));
2135
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(
2136
- () => Promise.all(scriptFiles.map((f) => loadAppDefinition(project, f)))
2137
- ).then((apps) => buildStartCommand(apps)).then((appCmd) => {
2138
- if (isHelpArgument(remainingArgs)) {
2139
- return appCmd.help();
2140
- }
2141
- return appCmd.parseAsync(remainingArgs, { from: "user" });
2142
- }).then(noop);
2143
- });
2144
- program.command("json").description(
2145
- "Start apps from a JSON file containing an array of script paths."
2146
- ).argument("[string]", "Path to JSON file").allowUnknownOption(true).allowExcessArguments(true).action(function(jsonFile) {
2147
- const cmd = this;
2148
- if (!jsonFile || isHelpArgument(jsonFile)) {
2149
- return cmd.help();
2150
- }
2151
- const project = createProject(path.dirname(path.resolve(jsonFile)));
2152
- const remainingArgs = cmd.args;
2153
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(
2154
- () => projectImportFile(
2155
- project,
2156
- path.resolve(jsonFile)
2157
- )
2158
- ).then((m) => {
2159
- const paths = m.default;
2160
- assert.arrayStrings(paths, "JSON file must contain a string[].");
2161
- return paths.map(
2162
- (p) => path.resolve(path.dirname(path.resolve(jsonFile)), p)
2163
- );
2164
- }).then(
2165
- (scriptFiles) => Promise.all(scriptFiles.map((f) => loadAppDefinition(project, f)))
2166
- ).then((apps) => buildStartCommand(apps)).then((appCmd) => {
2167
- if (isHelpArgument(remainingArgs)) {
2168
- return appCmd.help();
2169
- }
2170
- return appCmd.parseAsync(remainingArgs, { from: "user" });
2171
- }).then(noop);
2172
- });
2173
- program.command("list").description("Interactively pick apps to run from a folder.").argument("[string]", "Path to folder", ".").addOption(
2174
- new Option("--pattern <glob>", "Glob pattern for app files").default(
2175
- "**/*.{app,worker}.{ts,js}"
2176
- )
2177
- ).allowUnknownOption(true).allowExcessArguments(true).action(function(folderPath, opts) {
2178
- const cmd = this;
2179
- if (isHelpArgument(cmd.args)) {
2180
- return cmd.help();
2181
- }
2182
- const remainingArgs = cmd.args;
2183
- const project = createProject(path.resolve(folderPath));
2184
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => findAppFiles(path.resolve(folderPath), opts.pattern)).then((scriptFiles) => {
2185
- if (!scriptFiles.length) {
2186
- log.error("No files found. Pattern: %s", opts.pattern);
2187
- process.exit(1);
2188
- }
2189
- return log.prompt("Pick apps to run.", {
2190
- type: "multiselect",
2191
- options: scriptFiles.map((f) => ({
2192
- label: path.basename(f),
2193
- value: f
2194
- })),
2195
- initial: db.get("app_last_run"),
2196
- cancel: "null"
2197
- });
2198
- }).then((selected) => {
2199
- const selectedFiles = selected || [];
2200
- if (!selectedFiles.length) process.exit(0);
2201
- db.set("app_last_run", selectedFiles);
2202
- db.save();
2203
- return Promise.all(
2204
- selectedFiles.map((f) => loadAppDefinition(project, f))
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
- return fs.globSync(pattern, {
2249
- cwd: folderPath,
2250
- exclude: (f) => f.includes("node_modules")
2251
- }).map((f) => path.resolve(folderPath, f)).sort();
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
- const project = createProject(path.dirname(scriptFile));
2255
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => loadAppDefinition(project, scriptFile)).then((app) => buildStartCommand([app]));
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
- if (CONFIG.TS_MODE === "tsx") {
2259
- project.allowTs = true;
2260
- return Promise.resolve();
2261
- }
2262
- if (CONFIG.TS_MODE === "tsx-register") {
2263
- return Promise.resolve().then(() => import('tsx/esm/api')).then((m) => m.register({ tsconfig: project.tsconfigPath || false })).then(() => {
2264
- project.allowTs = true;
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
- projectPrintInfo(project);
2271
- return Promise.resolve().then(() => projectLoadEnv(project)).then(() => projectAutoImport(project)).then(noop);
1999
+ projectPrintInfo(project);
2000
+ return Promise.resolve().then(() => projectLoadEnv(project)).then(() => projectAutoImport(project)).then(noop);
2272
2001
  }
2273
2002
  function loadAppDefinition(project, scriptFile) {
2274
- return projectImportFile(project, scriptFile).then((appModule) => {
2275
- const app = appModule.default;
2276
- if (!isAppDefinition(app)) {
2277
- log.warn(`Default export must be a app definition: ${scriptFile}`);
2278
- process.exit(1);
2279
- }
2280
- return app;
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
- const rootDefinition = definitions.length > 1 ? createAppHub(definitions.map((v) => createAppThread(v))) : createAppThread(definitions[0]);
2285
- const program = new Command().name(rootDefinition.name).description(rootDefinition.description || "An application");
2286
- if (rootDefinition.props) {
2287
- for (const option of propsToOptions(rootDefinition.props)) {
2288
- program.addOption(option);
2289
- }
2290
- }
2291
- 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", { isDefault: true, hidden: true }).allowUnknownOption(true).allowExcessArguments(true).action((...args) => {
2292
- const props = program.optsWithGlobals();
2293
- if (props.tui) {
2294
- return launchAppsTui(definitions, props);
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
- const startDefer = defer();
2305
- let app;
2306
- onShutdown("app", () => {
2307
- log.warn("Graceful shutdown initiated, waiting for process to complete");
2308
- if (CONFIG.TS_MODE === "tsx") {
2309
- log.warn("Graceful in tsx + watch mode is not supported.");
2310
- }
2311
- return startDefer.promise.then(() => {
2312
- if (app) {
2313
- return shutdownApp(app);
2314
- }
2315
- });
2316
- });
2317
- return startApp(definition, props).then((startResult) => {
2318
- if (isSkip(startResult)) {
2319
- startDefer.resolve();
2320
- log.warn("Failed to start %s", startResult, startResult.error);
2321
- return;
2322
- }
2323
- app = startResult.app;
2324
- startDefer.resolve();
2325
- return appWaitShutdown(app);
2326
- }).then(() => processGraceful());
2327
- }
2328
-
2329
- const cli = Object.freeze({
2330
- runApp
2331
- });
2332
-
2333
- const index = {
2334
- __proto__: null,
2335
- cli: cli
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
- return new ResourcePool({
2375
- poolSize: size,
2376
- auto: true,
2377
- createResource: () => Symbol("worker-slot")
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
- const instance = {
2382
- definition,
2383
- log: logger,
2384
- queue: new AsyncIterableQueue(),
2385
- pool: createWorkerPool(definition.taskParallel ?? 2),
2386
- runTask: null,
2387
- queueMaxSize: definition.taskLimit ?? 50,
2388
- taskParallel: definition.taskParallel ?? 2,
2389
- overloadedSignaled: false,
2390
- addTask(ctx) {
2391
- addWorkerTask(this, ctx);
2392
- },
2393
- get isOverloaded() {
2394
- return this.queueSize > this.queueMaxSize * 0.8;
2395
- },
2396
- get isIdle() {
2397
- return this.pool.isIdle && this.queueSize === 0;
2398
- },
2399
- get queueSize() {
2400
- return this.queue._queue.items.length;
2401
- }
2402
- };
2403
- return instance;
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
- instance.queue.put(ctx);
2407
- checkWorkerPressure(instance);
2408
- }
2409
- async function executeWorkerTask(instance, entryContext, props, abortSignal) {
2410
- const { executeStrategy, entry } = instance.definition;
2411
- if (isFunction(executeStrategy.executeSignal)) {
2412
- const [signalError, signalResult] = await catchError(
2413
- () => executeStrategy.executeSignal(entryContext)
2414
- );
2415
- if (signalError) {
2416
- instance.log.error("Strategy executeSignal error, dropping task", {
2417
- error: signalError
2418
- });
2419
- return;
2420
- }
2421
- if (isSkip(signalResult)) {
2422
- instance.log.warn(
2423
- "Strategy executeSignal returned skip, dropping task",
2424
- signalResult
2425
- );
2426
- return;
2427
- }
2428
- }
2429
- let [executeError, executeResult] = await catchError(
2430
- () => entry?.call(entryContext, props)
2431
- );
2432
- if (executeError) {
2433
- if (executeStrategy.handleEntryError) {
2434
- executeResult = executeStrategy.handleEntryError(executeError);
2435
- }
2436
- if (!executeResult) {
2437
- executeResult = {
2438
- skip: true,
2439
- error: true,
2440
- code: "critical_error",
2441
- reason: executeError.message,
2442
- stack: executeError.stack
2443
- };
2444
- }
2445
- } else if (!executeResult || Array.isArray(executeResult) && !executeResult.length) {
2446
- executeResult = { success: true, code: "void" };
2447
- }
2448
- const results = Array.isArray(executeResult) ? executeResult : [executeResult];
2449
- let hasError = false;
2450
- let hasSkip = false;
2451
- for (const result of results) {
2452
- hasError = hasError || isSkip(result) && !!result.error;
2453
- hasSkip = isSkip(result);
2454
- }
2455
- if (hasError) {
2456
- instance.log.error("Task error", results);
2457
- } else if (hasSkip) {
2458
- instance.log.warn("Task skipped", results);
2459
- } else {
2460
- instance.log.debug("Task completed", results);
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
- const result = defineApp({
2475
- filePath: filePathFromStack(captureStackTrace(defineWorker)),
2476
- ...definition,
2477
- setup(props) {
2478
- return setupWorker(this, definition, props);
2479
- },
2480
- entry(props) {
2481
- return runWorkerLoop(this, definition, props);
2482
- },
2483
- stop(props) {
2484
- return stopWorker(this, definition, props);
2485
- },
2486
- shutdown(props) {
2487
- return shutdownWorker(this, definition, props);
2488
- }
2489
- });
2490
- result.disabled = result.disabled || CONFIG.WORKER_DISABLED.has(result.name);
2491
- def(result, WORKER_DEF, true);
2492
- return result;
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
- const setupFn = definition.setup;
2496
- const ctx = {
2497
- ...setupContext,
2498
- worker: createWorkerInstance(definition, setupContext.log)
2499
- };
2500
- return Promise.resolve().then(() => setupFn ? setupFn.call(ctx, props) : void 0).then((setupResult) => {
2501
- return {
2502
- ...ctx,
2503
- ...setupResult || {}
2504
- };
2505
- });
2506
- }
2507
- async function runWorkerLoop(runtimeContext, definition, props) {
2508
- const worker = runtimeContext.worker;
2509
- const { executeStrategy } = definition;
2510
- await executeStrategy.doSetup({ worker });
2511
- const abortController = new AbortController();
2512
- abortController.signal;
2513
- const onStopSignal = () => {
2514
- return Promise.resolve().then(() => abortController.abort()).then(
2515
- () => catchError(
2516
- () => executeStrategy.stopSignal(() => worker.queue.close())
2517
- )
2518
- ).then((stopResult) => {
2519
- const stopError = stopResult[0];
2520
- if (stopError) {
2521
- worker.queue.close();
2522
- worker.log.error("Strategy stopSignal error", { error: stopError });
2523
- }
2524
- });
2525
- };
2526
- worker.runTask = new CancellablePromise(
2527
- async (resolve, _reject, onCancel) => {
2528
- onCancel(onStopSignal);
2529
- const [startError] = await catchError(
2530
- () => executeStrategy.startSignal()
2531
- );
2532
- if (startError) {
2533
- worker.log.error("Strategy startSignal error", { error: startError });
2534
- worker.runTask = null;
2535
- return resolve();
2536
- }
2537
- worker.log.info("Worker started (parallel=%d)", worker.taskParallel);
2538
- for await (const taskContext of worker.queue) {
2539
- const jobTicket = await worker.pool.acquire();
2540
- const entryContext = {
2541
- ...runtimeContext,
2542
- ...taskContext
2543
- };
2544
- executeWorkerTask(worker, entryContext, props).finally(
2545
- () => {
2546
- worker.pool.release(jobTicket);
2547
- checkWorkerPressure(worker);
2548
- }
2549
- );
2550
- }
2551
- if (worker.pool.usedCount > 0) {
2552
- worker.log.info(
2553
- "Draining task pool (active=%d)",
2554
- worker.pool.usedCount
2555
- );
2556
- }
2557
- await worker.pool.destroy();
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
- try {
2569
- if (worker.isOverloaded) {
2570
- if (!worker.definition.executeStrategy.overloadedSignal)
2571
- return void worker.log.warn(
2572
- "Worker under pressure (queue=%d, limit=%d)",
2573
- worker.queueSize,
2574
- worker.queueMaxSize
2575
- );
2576
- if (worker.overloadedSignaled) return;
2577
- worker.overloadedSignaled = true;
2578
- worker.definition.executeStrategy.overloadedSignal();
2579
- } else {
2580
- if (!worker.overloadedSignaled) return;
2581
- if (!worker.definition.executeStrategy.availableSignal) {
2582
- worker.log.warn("Worker capacity restored");
2583
- } else {
2584
- worker.definition.executeStrategy.availableSignal();
2585
- }
2586
- worker.overloadedSignaled = false;
2587
- }
2588
- } catch (err) {
2589
- worker.log.error("Backpressure signal error", { error: err });
2590
- }
2591
- }
2592
- async function stopWorker(runtimeContext, definition, props) {
2593
- const worker = runtimeContext.worker;
2594
- const userStop = definition.stop;
2595
- if (worker.runTask) {
2596
- if (isFunction(userStop)) {
2597
- await userStop.call(runtimeContext, props);
2598
- }
2599
- if (!worker.runTask) return;
2600
- worker.runTask.cancel();
2601
- await worker.runTask.catch(() => {
2602
- });
2603
- worker.runTask = null;
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
- return value?.[WORKER_DEF] === true;
2295
+ return value?.[WORKER_DEF] === true;
2618
2296
  }
2619
2297
  function isWorkerInstance(value) {
2620
- return isWorkerDefinition(value?.definition);
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
- 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$1 as runApp, setupApp, setupThreadApp, shutdownApp, shutdownThreadApp, startApp, startThreadApp, stopApp, stopThreadApp, waitForThreadReady };
2624
- //# sourceMappingURL=index.mjs.map
2302
+ //# sourceMappingURL=index.mjs.map