numux 1.25.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -14
- package/dist/numux.js +56 -54
- package/dist/types.d.ts +11 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# numux
|
|
2
2
|
|
|
3
|
-
Terminal multiplexer with dependency orchestration. Run multiple processes in a tabbed TUI with a dependency graph controlling startup order.
|
|
3
|
+
Terminal multiplexer with dependency orchestration. Run multiple processes in a tabbed TUI with a dependency graph controlling startup order, readiness detection, and output capture between processes.
|
|
4
|
+
|
|
5
|
+
Works with zero configuration — pass commands as arguments, run a script across monorepo workspaces with `-w`, or match multiple scripts with glob patterns like `'dev:*'`. For advanced setups, define a typed config with conditional processes, file watching with auto-restart, error detection, log persistence, and output capture — e.g. extract a port from one process's stdout and pass it to another process's command or env.
|
|
4
6
|
|
|
5
7
|
Inspired by `sst dev` and `concurrently`
|
|
6
8
|
|
|
@@ -38,7 +40,6 @@ export default defineConfig({
|
|
|
38
40
|
migrate: {
|
|
39
41
|
command: 'bun run migrate',
|
|
40
42
|
dependsOn: ['db'],
|
|
41
|
-
persistent: false,
|
|
42
43
|
},
|
|
43
44
|
api: {
|
|
44
45
|
command: 'bun run dev:api',
|
|
@@ -51,7 +52,6 @@ export default defineConfig({
|
|
|
51
52
|
confirm: {
|
|
52
53
|
command: 'sh -c "printf \'Deploy to staging? [y/n] \' && read answer && echo $answer"',
|
|
53
54
|
interactive: true,
|
|
54
|
-
persistent: false,
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
57
|
})
|
|
@@ -164,8 +164,9 @@ Template properties (color, env, dependsOn, etc.) are inherited by all matched p
|
|
|
164
164
|
| `-p, --prefix` | Prefixed output mode (no TUI, for CI/scripts) |
|
|
165
165
|
| `--only <a,b,...>` | Only run these processes (+ their dependencies) |
|
|
166
166
|
| `--exclude <a,b,...>` | Exclude these processes |
|
|
167
|
-
| `--kill-others` | Kill all processes when any exits |
|
|
168
|
-
| `--
|
|
167
|
+
| `--kill-others` | Kill all processes when any exits (regardless of exit code) |
|
|
168
|
+
| `--kill-others-on-fail` | Kill all processes when any exits with a non-zero exit code |
|
|
169
|
+
| `--max-restarts <n>` | Max auto-restarts for crashed processes |
|
|
169
170
|
| `-s, --sort <mode>` | Tab display order: `config` (default), `alphabetical`, `topological` |
|
|
170
171
|
| `--no-watch` | Disable file watching even if config has `watch` patterns |
|
|
171
172
|
| `-t, --timestamps` | Add `[HH:MM:SS]` timestamps to prefixed output |
|
|
@@ -196,8 +197,7 @@ Top-level options apply to all processes (process-level settings override):
|
|
|
196
197
|
| `env` | `Record<string, string>` | Environment variables merged into all processes (process `env` overrides per key) |
|
|
197
198
|
| `envFile` | `string \| string[] \| false` | `.env` file(s) for all processes (process `envFile` replaces if set; `false` disables) |
|
|
198
199
|
| `showCommand` | `boolean` | Print the command being run as the first line of output (default: `true`) |
|
|
199
|
-
| `
|
|
200
|
-
| `maxRestarts` | `number` | Restart limit for all processes (default: `Infinity`) |
|
|
200
|
+
| `maxRestarts` | `number` | Restart limit for all processes (default: `0`) |
|
|
201
201
|
| `readyTimeout` | `number` | Ready timeout in ms for all processes |
|
|
202
202
|
| `stopSignal` | `'SIGTERM' \| 'SIGINT' \| 'SIGHUP'` | Stop signal for all processes (default: `'SIGTERM'`) |
|
|
203
203
|
| `errorMatcher` | `boolean \| string` | Error detection for all processes (`true` = ANSI red, string = regex) |
|
|
@@ -229,8 +229,7 @@ Each process accepts:
|
|
|
229
229
|
| `dependsOn` | `string[]` | — | Processes that must be ready first |
|
|
230
230
|
| `readyPattern` | `string \| RegExp` | — | Regex matched against stdout to signal readiness. Use `RegExp` to capture groups (see below) |
|
|
231
231
|
| `readyTimeout` | `number` | — | Milliseconds to wait for `readyPattern` before failing |
|
|
232
|
-
| `
|
|
233
|
-
| `maxRestarts` | `number` | `Infinity` | Max auto-restart attempts before giving up |
|
|
232
|
+
| `maxRestarts` | `number` | `0` | Max auto-restart attempts on non-zero exit (0 = no restarts) |
|
|
234
233
|
| `delay` | `number` | — | Milliseconds to wait before starting the process |
|
|
235
234
|
| `condition` | `string` | — | Env var name; process skipped if falsy. Prefix with `!` to negate |
|
|
236
235
|
| `platform` | `string \| string[]` | — | OS(es) this process runs on (e.g. `'darwin'`, `'linux'`). Non-matching processes are removed; dependents still start |
|
|
@@ -298,7 +297,6 @@ export default defineConfig({
|
|
|
298
297
|
processes: {
|
|
299
298
|
seed: {
|
|
300
299
|
command: 'bun run seed',
|
|
301
|
-
persistent: false,
|
|
302
300
|
condition: 'SEED_DB', // only runs when SEED_DB is set and truthy
|
|
303
301
|
},
|
|
304
302
|
storybook: {
|
|
@@ -316,11 +314,10 @@ Falsy values: unset, empty string, `"0"`, `"false"`, `"no"`, `"off"` (case-insen
|
|
|
316
314
|
Each process starts as soon as its declared `dependsOn` dependencies are ready — it does not wait for unrelated processes. If a process fails, its dependents are skipped.
|
|
317
315
|
|
|
318
316
|
A process becomes **ready** when:
|
|
319
|
-
- **
|
|
320
|
-
- **
|
|
321
|
-
- **non-persistent** — exits with code 0
|
|
317
|
+
- **Has `readyPattern`** — the pattern matches in stdout (long-running server)
|
|
318
|
+
- **No `readyPattern`** — exits with code 0 (one-shot task)
|
|
322
319
|
|
|
323
|
-
|
|
320
|
+
Processes that crash (non-zero exit) can be auto-restarted by setting `maxRestarts` (default: `0`). Restarts use exponential backoff (1s–30s), which resets after 10s of uptime.
|
|
324
321
|
|
|
325
322
|
### Dependency output capture
|
|
326
323
|
|
package/dist/numux.js
CHANGED
|
@@ -36,7 +36,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
|
|
|
36
36
|
var require_package = __commonJS((exports, module) => {
|
|
37
37
|
module.exports = {
|
|
38
38
|
name: "numux",
|
|
39
|
-
version: "
|
|
39
|
+
version: "2.0.0",
|
|
40
40
|
description: "Terminal multiplexer with dependency orchestration",
|
|
41
41
|
type: "module",
|
|
42
42
|
license: "MIT",
|
|
@@ -192,13 +192,27 @@ var FLAGS = [
|
|
|
192
192
|
type: "boolean",
|
|
193
193
|
long: "--kill-others",
|
|
194
194
|
key: "killOthers",
|
|
195
|
-
description: "Kill all processes when any exits"
|
|
195
|
+
description: "Kill all processes when any exits (regardless of exit code)"
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
type: "boolean",
|
|
199
|
-
long: "--
|
|
200
|
-
key: "
|
|
201
|
-
description: "
|
|
199
|
+
long: "--kill-others-on-fail",
|
|
200
|
+
key: "killOthersOnFail",
|
|
201
|
+
description: "Kill all processes when any exits with non-zero code"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: "value",
|
|
205
|
+
long: "--max-restarts",
|
|
206
|
+
key: "maxRestarts",
|
|
207
|
+
description: "Max auto-restarts for crashed processes",
|
|
208
|
+
valueName: "<n>",
|
|
209
|
+
completionHint: "none",
|
|
210
|
+
parse(raw, flag) {
|
|
211
|
+
const n = Number(raw);
|
|
212
|
+
if (!Number.isInteger(n) || n < 0)
|
|
213
|
+
throw new Error(`${flag} must be a non-negative integer, got "${raw}"`);
|
|
214
|
+
return n;
|
|
215
|
+
}
|
|
202
216
|
},
|
|
203
217
|
{
|
|
204
218
|
type: "boolean",
|
|
@@ -342,8 +356,8 @@ function parseArgs(argv) {
|
|
|
342
356
|
exec: false,
|
|
343
357
|
prefix: false,
|
|
344
358
|
killOthers: false,
|
|
359
|
+
killOthersOnFail: false,
|
|
345
360
|
timestamps: false,
|
|
346
|
-
noRestart: false,
|
|
347
361
|
noWatch: false,
|
|
348
362
|
autoColors: false,
|
|
349
363
|
configPath: undefined,
|
|
@@ -390,12 +404,11 @@ function parseArgs(argv) {
|
|
|
390
404
|
}
|
|
391
405
|
function buildConfigFromArgs(commands, named, options) {
|
|
392
406
|
const processes = {};
|
|
393
|
-
const maxRestarts = options?.noRestart ? 0 : undefined;
|
|
394
407
|
const colors = options?.colors;
|
|
395
408
|
let colorIndex = 0;
|
|
396
409
|
for (const { name, command } of named) {
|
|
397
410
|
const color = colors?.[colorIndex++ % colors.length];
|
|
398
|
-
processes[name] = { command,
|
|
411
|
+
processes[name] = { command, ...color ? { color } : {} };
|
|
399
412
|
}
|
|
400
413
|
for (let i = 0;i < commands.length; i++) {
|
|
401
414
|
const cmd = commands[i];
|
|
@@ -404,7 +417,7 @@ function buildConfigFromArgs(commands, named, options) {
|
|
|
404
417
|
name = `${name}-${i}`;
|
|
405
418
|
}
|
|
406
419
|
const color = colors?.[colorIndex++ % colors.length];
|
|
407
|
-
processes[name] = { command: cmd,
|
|
420
|
+
processes[name] = { command: cmd, ...color ? { color } : {} };
|
|
408
421
|
}
|
|
409
422
|
return { processes };
|
|
410
423
|
}
|
|
@@ -1028,7 +1041,7 @@ function buildProcessHexColorMap(names, config) {
|
|
|
1028
1041
|
}
|
|
1029
1042
|
|
|
1030
1043
|
// src/config/validator.ts
|
|
1031
|
-
function validateConfig(raw,
|
|
1044
|
+
function validateConfig(raw, _warnings) {
|
|
1032
1045
|
if (!raw || typeof raw !== "object") {
|
|
1033
1046
|
throw new Error("Config must be an object");
|
|
1034
1047
|
}
|
|
@@ -1046,7 +1059,6 @@ function validateConfig(raw, warnings) {
|
|
|
1046
1059
|
const globalEnvFile = validateEnvFile(config.envFile);
|
|
1047
1060
|
const globalMaxRestarts = typeof config.maxRestarts === "number" && config.maxRestarts >= 0 ? config.maxRestarts : undefined;
|
|
1048
1061
|
const globalReadyTimeout = typeof config.readyTimeout === "number" && config.readyTimeout > 0 ? config.readyTimeout : undefined;
|
|
1049
|
-
const globalPersistent = typeof config.persistent === "boolean" ? config.persistent : undefined;
|
|
1050
1062
|
const globalStopSignal = validateStopSignal(config.stopSignal);
|
|
1051
1063
|
const globalErrorMatcher = validateErrorMatcher("(global)", config.errorMatcher);
|
|
1052
1064
|
const globalWatch = validateStringOrStringArray(config.watch);
|
|
@@ -1063,6 +1075,7 @@ function validateConfig(raw, warnings) {
|
|
|
1063
1075
|
const prefix = config.prefix === true ? true : undefined;
|
|
1064
1076
|
const timestamps = config.timestamps === true ? true : undefined;
|
|
1065
1077
|
const killOthers = config.killOthers === true ? true : undefined;
|
|
1078
|
+
const killOthersOnFail = config.killOthersOnFail === true ? true : undefined;
|
|
1066
1079
|
const noWatch = config.noWatch === true ? true : undefined;
|
|
1067
1080
|
const logDir = typeof config.logDir === "string" && config.logDir.trim() ? config.logDir.trim() : undefined;
|
|
1068
1081
|
const validated = {};
|
|
@@ -1107,7 +1120,6 @@ function validateConfig(raw, warnings) {
|
|
|
1107
1120
|
}
|
|
1108
1121
|
}
|
|
1109
1122
|
}
|
|
1110
|
-
const persistent = typeof p.persistent === "boolean" ? p.persistent : globalPersistent ?? true;
|
|
1111
1123
|
const readyPattern = p.readyPattern instanceof RegExp ? p.readyPattern : typeof p.readyPattern === "string" ? p.readyPattern : undefined;
|
|
1112
1124
|
if (typeof readyPattern === "string") {
|
|
1113
1125
|
try {
|
|
@@ -1118,12 +1130,6 @@ function validateConfig(raw, warnings) {
|
|
|
1118
1130
|
});
|
|
1119
1131
|
}
|
|
1120
1132
|
}
|
|
1121
|
-
if (readyPattern && !persistent) {
|
|
1122
|
-
warnings?.push({
|
|
1123
|
-
process: name,
|
|
1124
|
-
message: "readyPattern is ignored on non-persistent processes (readiness is determined by exit code)"
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
1133
|
if (p.env && typeof p.env === "object") {
|
|
1128
1134
|
for (const [k, v] of Object.entries(p.env)) {
|
|
1129
1135
|
if (typeof v !== "string") {
|
|
@@ -1148,8 +1154,7 @@ function validateConfig(raw, warnings) {
|
|
|
1148
1154
|
envFile: processEnvFile ?? globalEnvFile,
|
|
1149
1155
|
dependsOn: Array.isArray(p.dependsOn) ? p.dependsOn : undefined,
|
|
1150
1156
|
readyPattern,
|
|
1151
|
-
|
|
1152
|
-
maxRestarts: processMaxRestarts ?? globalMaxRestarts,
|
|
1157
|
+
maxRestarts: processMaxRestarts ?? globalMaxRestarts ?? 0,
|
|
1153
1158
|
readyTimeout: processReadyTimeout ?? globalReadyTimeout,
|
|
1154
1159
|
delay: typeof p.delay === "number" && p.delay > 0 ? p.delay : undefined,
|
|
1155
1160
|
condition: typeof p.condition === "string" && p.condition.trim() ? p.condition.trim() : undefined,
|
|
@@ -1167,6 +1172,7 @@ function validateConfig(raw, warnings) {
|
|
|
1167
1172
|
...prefix ? { prefix } : {},
|
|
1168
1173
|
...timestamps ? { timestamps } : {},
|
|
1169
1174
|
...killOthers ? { killOthers } : {},
|
|
1175
|
+
...killOthersOnFail ? { killOthersOnFail } : {},
|
|
1170
1176
|
...noWatch ? { noWatch } : {},
|
|
1171
1177
|
...logDir ? { logDir } : {},
|
|
1172
1178
|
processes: validated
|
|
@@ -1284,8 +1290,7 @@ function resolveWorkspaceProcesses(script, cwd) {
|
|
|
1284
1290
|
usedNames.add(name);
|
|
1285
1291
|
processes[name] = {
|
|
1286
1292
|
command: `${pm} run ${script}`,
|
|
1287
|
-
cwd: dir
|
|
1288
|
-
persistent: true
|
|
1293
|
+
cwd: dir
|
|
1289
1294
|
};
|
|
1290
1295
|
}
|
|
1291
1296
|
if (Object.keys(processes).length === 0) {
|
|
@@ -1458,12 +1463,11 @@ function extractCaptures(match) {
|
|
|
1458
1463
|
function createReadinessChecker(config) {
|
|
1459
1464
|
const shouldCapture = config.readyPattern instanceof RegExp;
|
|
1460
1465
|
const pattern = config.readyPattern ? config.readyPattern instanceof RegExp ? config.readyPattern : new RegExp(config.readyPattern) : null;
|
|
1461
|
-
const persistent = config.persistent !== false;
|
|
1462
1466
|
let outputBuffer = "";
|
|
1463
1467
|
let _captures = null;
|
|
1464
1468
|
return {
|
|
1465
1469
|
feedOutput(data) {
|
|
1466
|
-
if (!
|
|
1470
|
+
if (!pattern)
|
|
1467
1471
|
return false;
|
|
1468
1472
|
outputBuffer += data;
|
|
1469
1473
|
if (outputBuffer.length > BUFFER_CAP2) {
|
|
@@ -1482,11 +1486,8 @@ function createReadinessChecker(config) {
|
|
|
1482
1486
|
get captures() {
|
|
1483
1487
|
return _captures;
|
|
1484
1488
|
},
|
|
1485
|
-
get isImmediatelyReady() {
|
|
1486
|
-
return persistent && !pattern;
|
|
1487
|
-
},
|
|
1488
1489
|
get dependsOnExit() {
|
|
1489
|
-
return !
|
|
1490
|
+
return !pattern;
|
|
1490
1491
|
}
|
|
1491
1492
|
};
|
|
1492
1493
|
}
|
|
@@ -1576,10 +1577,7 @@ class ProcessRunner {
|
|
|
1576
1577
|
`;
|
|
1577
1578
|
this.handler.onOutput(encoder.encode(msg));
|
|
1578
1579
|
}
|
|
1579
|
-
this.handler.onStatus(this.config.
|
|
1580
|
-
if (this.readiness.isImmediatelyReady) {
|
|
1581
|
-
this.markReady();
|
|
1582
|
-
}
|
|
1580
|
+
this.handler.onStatus(this.config.readyPattern ? "running" : "starting");
|
|
1583
1581
|
this.startReadyTimeout(gen);
|
|
1584
1582
|
this.proc.exited.then((code) => {
|
|
1585
1583
|
if (this.generation !== gen)
|
|
@@ -1627,7 +1625,7 @@ class ProcessRunner {
|
|
|
1627
1625
|
}
|
|
1628
1626
|
startReadyTimeout(gen) {
|
|
1629
1627
|
const timeout = this.config.readyTimeout;
|
|
1630
|
-
if (!(timeout && this.config.readyPattern)
|
|
1628
|
+
if (!(timeout && this.config.readyPattern))
|
|
1631
1629
|
return;
|
|
1632
1630
|
this.readyTimer = setTimeout(() => {
|
|
1633
1631
|
this.readyTimer = null;
|
|
@@ -1885,8 +1883,6 @@ class ProcessManager {
|
|
|
1885
1883
|
if (this.stopping)
|
|
1886
1884
|
return;
|
|
1887
1885
|
const proc = this.config.processes[name];
|
|
1888
|
-
if (proc.persistent === false)
|
|
1889
|
-
return;
|
|
1890
1886
|
if (exitCode === 0)
|
|
1891
1887
|
return;
|
|
1892
1888
|
if (exitCode === null)
|
|
@@ -1897,8 +1893,8 @@ class ProcessManager {
|
|
|
1897
1893
|
this.restartAttempts.set(name, 0);
|
|
1898
1894
|
}
|
|
1899
1895
|
const attempt = this.restartAttempts.get(name) ?? 0;
|
|
1900
|
-
const maxRestarts = proc.maxRestarts;
|
|
1901
|
-
if (
|
|
1896
|
+
const maxRestarts = proc.maxRestarts ?? 0;
|
|
1897
|
+
if (attempt >= maxRestarts) {
|
|
1902
1898
|
log(`[${name}] Reached maxRestarts limit (${maxRestarts}), not restarting`);
|
|
1903
1899
|
if (maxRestarts > 0) {
|
|
1904
1900
|
const encoder2 = new TextEncoder;
|
|
@@ -1913,7 +1909,7 @@ class ProcessManager {
|
|
|
1913
1909
|
this.restartAttempts.set(name, attempt + 1);
|
|
1914
1910
|
const encoder = new TextEncoder;
|
|
1915
1911
|
const msg = `\r
|
|
1916
|
-
\x1B[33m[numux] restarting in ${(delay / 1000).toFixed(0)}s (attempt ${attempt + 1}${maxRestarts
|
|
1912
|
+
\x1B[33m[numux] restarting in ${(delay / 1000).toFixed(0)}s (attempt ${attempt + 1}${Number.isFinite(maxRestarts) ? `/${maxRestarts}` : ""})...\x1B[0m\r
|
|
1917
1913
|
`;
|
|
1918
1914
|
this.emit({ type: "output", name, data: encoder.encode(msg) });
|
|
1919
1915
|
const timer = setTimeout(() => {
|
|
@@ -1944,7 +1940,7 @@ class ProcessManager {
|
|
|
1944
1940
|
const state = this.states.get(name);
|
|
1945
1941
|
if (!state)
|
|
1946
1942
|
return;
|
|
1947
|
-
if (state.status === "pending" || state.status === "stopped" || state.status === "
|
|
1943
|
+
if (state.status === "pending" || state.status === "stopped" || state.status === "stopping" || state.status === "skipped")
|
|
1948
1944
|
return;
|
|
1949
1945
|
log(`[${name}] File changed: ${changedFile}, restarting`);
|
|
1950
1946
|
const msg = `\r
|
|
@@ -2187,10 +2183,14 @@ function openLink(link) {
|
|
|
2187
2183
|
}
|
|
2188
2184
|
|
|
2189
2185
|
// src/ui/pane.ts
|
|
2186
|
+
var MAX_SCROLLBACK_LINES = 50000;
|
|
2187
|
+
var MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
2188
|
+
|
|
2190
2189
|
class Pane {
|
|
2191
2190
|
scrollBox;
|
|
2192
2191
|
terminal;
|
|
2193
2192
|
decoder = new TextDecoder;
|
|
2193
|
+
bytesFed = 0;
|
|
2194
2194
|
_onScroll = null;
|
|
2195
2195
|
_onCopy = null;
|
|
2196
2196
|
_onLinkClick = null;
|
|
@@ -2238,6 +2238,11 @@ class Pane {
|
|
|
2238
2238
|
this.scrollBox.add(this.terminal);
|
|
2239
2239
|
}
|
|
2240
2240
|
feed(data) {
|
|
2241
|
+
this.bytesFed += data.length;
|
|
2242
|
+
if (this.terminal.lineCount > MAX_SCROLLBACK_LINES || this.bytesFed > MAX_BUFFER_BYTES) {
|
|
2243
|
+
this.terminal.reset();
|
|
2244
|
+
this.bytesFed = 0;
|
|
2245
|
+
}
|
|
2241
2246
|
const text = this.decoder.decode(data, { stream: true });
|
|
2242
2247
|
this.terminal.feed(text);
|
|
2243
2248
|
}
|
|
@@ -2315,6 +2320,7 @@ class Pane {
|
|
|
2315
2320
|
}
|
|
2316
2321
|
clear() {
|
|
2317
2322
|
this.terminal.reset();
|
|
2323
|
+
this.bytesFed = 0;
|
|
2318
2324
|
}
|
|
2319
2325
|
destroy() {
|
|
2320
2326
|
this.terminal.destroy();
|
|
@@ -3185,6 +3191,7 @@ class PrefixDisplay {
|
|
|
3185
3191
|
buffers = new Map;
|
|
3186
3192
|
logWriter;
|
|
3187
3193
|
killOthers;
|
|
3194
|
+
killOthersOnFail;
|
|
3188
3195
|
timestamps;
|
|
3189
3196
|
stopping = false;
|
|
3190
3197
|
startTime = 0;
|
|
@@ -3192,6 +3199,7 @@ class PrefixDisplay {
|
|
|
3192
3199
|
this.manager = manager;
|
|
3193
3200
|
this.logWriter = options.logWriter;
|
|
3194
3201
|
this.killOthers = options.killOthers ?? false;
|
|
3202
|
+
this.killOthersOnFail = options.killOthersOnFail ?? false;
|
|
3195
3203
|
this.timestamps = options.timestamps ?? false;
|
|
3196
3204
|
this.noColor = "NO_COLOR" in process.env;
|
|
3197
3205
|
const names = manager.getProcessNames();
|
|
@@ -3233,7 +3241,8 @@ class PrefixDisplay {
|
|
|
3233
3241
|
this.handleStatus(event.name, event.status);
|
|
3234
3242
|
} else if (event.type === "exit") {
|
|
3235
3243
|
this.flushBuffer(event.name);
|
|
3236
|
-
|
|
3244
|
+
const exitCode = this.manager.getState(event.name)?.exitCode ?? null;
|
|
3245
|
+
if (this.killOthers || this.killOthersOnFail && exitCode !== 0) {
|
|
3237
3246
|
this.killAllAndExit(event.name);
|
|
3238
3247
|
} else {
|
|
3239
3248
|
this.checkAllDone();
|
|
@@ -3662,7 +3671,7 @@ async function main() {
|
|
|
3662
3671
|
flags.push(`depends on: ${proc.dependsOn.join(", ")}`);
|
|
3663
3672
|
if (proc.readyPattern)
|
|
3664
3673
|
flags.push(`ready: /${proc.readyPattern}/`);
|
|
3665
|
-
if (proc.
|
|
3674
|
+
if (!proc.readyPattern)
|
|
3666
3675
|
flags.push("one-shot");
|
|
3667
3676
|
if (proc.delay)
|
|
3668
3677
|
flags.push(`delay: ${proc.delay}ms`);
|
|
@@ -3739,7 +3748,6 @@ async function main() {
|
|
|
3739
3748
|
config = validateConfig(expanded, warnings);
|
|
3740
3749
|
} else {
|
|
3741
3750
|
config = buildConfigFromArgs(parsed.commands, parsed.named, {
|
|
3742
|
-
noRestart: parsed.noRestart,
|
|
3743
3751
|
colors: parsed.colors
|
|
3744
3752
|
});
|
|
3745
3753
|
}
|
|
@@ -3753,8 +3761,6 @@ async function main() {
|
|
|
3753
3761
|
suffix++;
|
|
3754
3762
|
finalName = `${finalName}-${suffix}`;
|
|
3755
3763
|
}
|
|
3756
|
-
if (parsed.noRestart)
|
|
3757
|
-
proc.maxRestarts = 0;
|
|
3758
3764
|
config.processes[finalName] = proc;
|
|
3759
3765
|
}
|
|
3760
3766
|
}
|
|
@@ -3762,11 +3768,6 @@ async function main() {
|
|
|
3762
3768
|
const raw = expandScriptPatterns(await loadConfig(parsed.configPath));
|
|
3763
3769
|
config = validateConfig(raw, warnings);
|
|
3764
3770
|
config = filterByPlatform(config);
|
|
3765
|
-
if (parsed.noRestart) {
|
|
3766
|
-
for (const proc of Object.values(config.processes)) {
|
|
3767
|
-
proc.maxRestarts = 0;
|
|
3768
|
-
}
|
|
3769
|
-
}
|
|
3770
3771
|
}
|
|
3771
3772
|
if (parsed.sort) {
|
|
3772
3773
|
config.sort = parsed.sort;
|
|
@@ -3776,6 +3777,11 @@ async function main() {
|
|
|
3776
3777
|
proc.envFile = parsed.envFile;
|
|
3777
3778
|
}
|
|
3778
3779
|
}
|
|
3780
|
+
if (parsed.maxRestarts !== undefined) {
|
|
3781
|
+
for (const proc of Object.values(config.processes)) {
|
|
3782
|
+
proc.maxRestarts = parsed.maxRestarts;
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3779
3785
|
if (parsed.noWatch || config.noWatch) {
|
|
3780
3786
|
for (const proc of Object.values(config.processes)) {
|
|
3781
3787
|
delete proc.watch;
|
|
@@ -3797,14 +3803,10 @@ async function main() {
|
|
|
3797
3803
|
printWarnings(warnings);
|
|
3798
3804
|
const usePrefix = parsed.prefix || config.prefix;
|
|
3799
3805
|
if (usePrefix) {
|
|
3800
|
-
if (!parsed.noRestart) {
|
|
3801
|
-
for (const proc of Object.values(config.processes)) {
|
|
3802
|
-
proc.maxRestarts ??= 0;
|
|
3803
|
-
}
|
|
3804
|
-
}
|
|
3805
3806
|
const display = new PrefixDisplay(manager, config, {
|
|
3806
3807
|
logWriter,
|
|
3807
3808
|
killOthers: parsed.killOthers || config.killOthers,
|
|
3809
|
+
killOthersOnFail: parsed.killOthersOnFail || config.killOthersOnFail,
|
|
3808
3810
|
timestamps: parsed.timestamps || config.timestamps
|
|
3809
3811
|
});
|
|
3810
3812
|
await display.start();
|
package/dist/types.d.ts
CHANGED
|
@@ -17,13 +17,8 @@ export interface NumuxProcessConfig<K extends string = string> {
|
|
|
17
17
|
/** Regex matched against stdout to signal readiness. Use `RegExp` to capture groups for `$dep.group` expansion */
|
|
18
18
|
readyPattern?: string | RegExp;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
* @default
|
|
22
|
-
*/
|
|
23
|
-
persistent?: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Limit auto-restart attempts
|
|
26
|
-
* @default Infinity
|
|
20
|
+
* Limit auto-restart attempts (only restarts on non-zero exit)
|
|
21
|
+
* @default 0
|
|
27
22
|
*/
|
|
28
23
|
maxRestarts?: number;
|
|
29
24
|
/** Milliseconds to wait for readyPattern before failing */
|
|
@@ -74,18 +69,12 @@ export interface NumuxConfig<K extends string = string> {
|
|
|
74
69
|
*/
|
|
75
70
|
showCommand?: boolean;
|
|
76
71
|
/**
|
|
77
|
-
* Global restart limit, inherited by all processes
|
|
78
|
-
* @default
|
|
72
|
+
* Global restart limit, inherited by all processes (only restarts on non-zero exit)
|
|
73
|
+
* @default 0
|
|
79
74
|
*/
|
|
80
75
|
maxRestarts?: number;
|
|
81
76
|
/** Global ready timeout (ms), inherited by all processes */
|
|
82
77
|
readyTimeout?: number;
|
|
83
|
-
/**
|
|
84
|
-
* Set to `false` to make all processes non-persistent (one-shot) by default.
|
|
85
|
-
* Individual processes can still override with their own `persistent` value.
|
|
86
|
-
* @default true
|
|
87
|
-
*/
|
|
88
|
-
persistent?: boolean;
|
|
89
78
|
/**
|
|
90
79
|
* Global stop signal, inherited by all processes
|
|
91
80
|
* @default 'SIGTERM'
|
|
@@ -109,10 +98,15 @@ export interface NumuxConfig<K extends string = string> {
|
|
|
109
98
|
/** Add timestamps to prefixed output lines (only applies when `prefix` is true) */
|
|
110
99
|
timestamps?: boolean;
|
|
111
100
|
/**
|
|
112
|
-
* Kill all processes when any one exits
|
|
101
|
+
* Kill all processes when any one exits (regardless of exit code)
|
|
113
102
|
* @default false
|
|
114
103
|
*/
|
|
115
104
|
killOthers?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Kill all processes when any one exits with a non-zero exit code
|
|
107
|
+
* @default false
|
|
108
|
+
*/
|
|
109
|
+
killOthersOnFail?: boolean;
|
|
116
110
|
/**
|
|
117
111
|
* Disable file watching even if processes have watch patterns
|
|
118
112
|
* @default false
|
|
@@ -133,6 +127,7 @@ export interface ResolvedNumuxConfig {
|
|
|
133
127
|
prefix?: boolean;
|
|
134
128
|
timestamps?: boolean;
|
|
135
129
|
killOthers?: boolean;
|
|
130
|
+
killOthersOnFail?: boolean;
|
|
136
131
|
noWatch?: boolean;
|
|
137
132
|
logDir?: string;
|
|
138
133
|
processes: Record<string, ResolvedProcessConfig>;
|