numux 1.6.0 → 1.8.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 +1 -1
- package/dist/numux.js +167 -22
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,7 +178,7 @@ Each process accepts:
|
|
|
178
178
|
| `delay` | `number` | — | Milliseconds to wait before starting the process |
|
|
179
179
|
| `condition` | `string` | — | Env var name; process skipped if falsy. Prefix with `!` to negate |
|
|
180
180
|
| `stopSignal` | `string` | `SIGTERM` | Signal for graceful stop (`SIGTERM`, `SIGINT`, or `SIGHUP`) |
|
|
181
|
-
| `color` | `string` | auto | Hex
|
|
181
|
+
| `color` | `string \| string[]` | auto | Hex (e.g. `"#ff6600"`) or basic name: black, red, green, yellow, blue, magenta, cyan, white, gray, orange, purple |
|
|
182
182
|
| `watch` | `string \| string[]` | — | Glob patterns — restart process when matching files change |
|
|
183
183
|
| `interactive` | `boolean` | `false` | When `true`, keyboard input is forwarded to the process |
|
|
184
184
|
|
package/dist/numux.js
CHANGED
|
@@ -22,7 +22,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
|
|
|
22
22
|
var require_package = __commonJS((exports, module) => {
|
|
23
23
|
module.exports = {
|
|
24
24
|
name: "numux",
|
|
25
|
-
version: "1.
|
|
25
|
+
version: "1.8.0",
|
|
26
26
|
description: "Terminal multiplexer with dependency orchestration",
|
|
27
27
|
type: "module",
|
|
28
28
|
license: "MIT",
|
|
@@ -92,6 +92,7 @@ function parseArgs(argv) {
|
|
|
92
92
|
timestamps: false,
|
|
93
93
|
noRestart: false,
|
|
94
94
|
noWatch: false,
|
|
95
|
+
autoColors: false,
|
|
95
96
|
configPath: undefined,
|
|
96
97
|
commands: [],
|
|
97
98
|
named: []
|
|
@@ -123,6 +124,8 @@ function parseArgs(argv) {
|
|
|
123
124
|
result.noRestart = true;
|
|
124
125
|
} else if (arg === "--no-watch") {
|
|
125
126
|
result.noWatch = true;
|
|
127
|
+
} else if (arg === "--colors") {
|
|
128
|
+
result.autoColors = true;
|
|
126
129
|
} else if (arg === "--config") {
|
|
127
130
|
result.configPath = consumeValue(arg);
|
|
128
131
|
} else if (arg === "-c" || arg === "--color") {
|
|
@@ -279,7 +282,7 @@ _numux() {
|
|
|
279
282
|
esac
|
|
280
283
|
|
|
281
284
|
if [[ "$cur" == -* ]]; then
|
|
282
|
-
COMPREPLY=( $(compgen -W "-h --help -v --version -c --color --config -n --name -p --prefix --only --exclude --kill-others --no-restart --no-watch -t --timestamps --log-dir --debug" -- "$cur") )
|
|
285
|
+
COMPREPLY=( $(compgen -W "-h --help -v --version -c --color --colors --config -n --name -p --prefix --only --exclude --kill-others --no-restart --no-watch -t --timestamps --log-dir --debug" -- "$cur") )
|
|
283
286
|
else
|
|
284
287
|
local subcmds="init validate exec completions"
|
|
285
288
|
COMPREPLY=( $(compgen -W "$subcmds" -- "$cur") )
|
|
@@ -304,6 +307,7 @@ _numux() {
|
|
|
304
307
|
'(-h --help)'{-h,--help}'[Show help]' \\
|
|
305
308
|
'(-v --version)'{-v,--version}'[Show version]' \\
|
|
306
309
|
'(-c --color)'{-c,--color}'[Comma-separated colors for processes]' \\
|
|
310
|
+
'--colors[Auto-assign colors based on process name]' \\
|
|
307
311
|
'--config[Config file path]:file:_files' \\
|
|
308
312
|
'(-n --name)'{-n,--name}'[Named process (name=command)]:named process' \\
|
|
309
313
|
'(-p --prefix)'{-p,--prefix}'[Prefixed output mode]' \\
|
|
@@ -346,6 +350,7 @@ complete -c numux -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish
|
|
|
346
350
|
complete -c numux -s h -l help -d 'Show help'
|
|
347
351
|
complete -c numux -s v -l version -d 'Show version'
|
|
348
352
|
complete -c numux -s c -l color -r -d 'Comma-separated colors for processes'
|
|
353
|
+
complete -c numux -l colors -d 'Auto-assign colors based on process name'
|
|
349
354
|
complete -c numux -l config -rF -d 'Config file path'
|
|
350
355
|
complete -c numux -s n -l name -r -d 'Named process (name=command)'
|
|
351
356
|
complete -c numux -s p -l prefix -d 'Prefixed output mode'
|
|
@@ -592,6 +597,31 @@ function findCycle(remaining, config) {
|
|
|
592
597
|
}
|
|
593
598
|
|
|
594
599
|
// src/utils/color.ts
|
|
600
|
+
var BASIC_COLORS = {
|
|
601
|
+
black: "#000000",
|
|
602
|
+
red: "#ff0000",
|
|
603
|
+
green: "#00ff00",
|
|
604
|
+
yellow: "#ffff00",
|
|
605
|
+
blue: "#0000ff",
|
|
606
|
+
magenta: "#ff00ff",
|
|
607
|
+
cyan: "#00ffff",
|
|
608
|
+
white: "#ffffff",
|
|
609
|
+
gray: "#808080",
|
|
610
|
+
grey: "#808080",
|
|
611
|
+
orange: "#ffa500",
|
|
612
|
+
purple: "#800080"
|
|
613
|
+
};
|
|
614
|
+
function isValidColor(color) {
|
|
615
|
+
if (HEX_COLOR_RE.test(color))
|
|
616
|
+
return true;
|
|
617
|
+
return color.toLowerCase() in BASIC_COLORS;
|
|
618
|
+
}
|
|
619
|
+
function resolveToHex(color) {
|
|
620
|
+
if (HEX_COLOR_RE.test(color))
|
|
621
|
+
return color.startsWith("#") ? color : `#${color}`;
|
|
622
|
+
const hex = BASIC_COLORS[color.toLowerCase()];
|
|
623
|
+
return hex ?? "";
|
|
624
|
+
}
|
|
595
625
|
function hexToAnsi(hex) {
|
|
596
626
|
const h = hex.replace("#", "");
|
|
597
627
|
const r = Number.parseInt(h.slice(0, 2), 16);
|
|
@@ -626,6 +656,13 @@ var DEFAULT_ANSI_COLORS = [
|
|
|
626
656
|
"\x1B[95m"
|
|
627
657
|
];
|
|
628
658
|
var DEFAULT_HEX_COLORS = ["#00cccc", "#cccc00", "#cc00cc", "#0000cc", "#00cc00", "#ff5555", "#ffff55", "#ff55ff"];
|
|
659
|
+
function colorFromName(name) {
|
|
660
|
+
let hash = 0;
|
|
661
|
+
for (let i = 0;i < name.length; i++) {
|
|
662
|
+
hash = (hash << 5) - hash + name.charCodeAt(i) | 0;
|
|
663
|
+
}
|
|
664
|
+
return DEFAULT_HEX_COLORS[Math.abs(hash) % DEFAULT_HEX_COLORS.length];
|
|
665
|
+
}
|
|
629
666
|
function resolveColor(color) {
|
|
630
667
|
if (typeof color === "string")
|
|
631
668
|
return color;
|
|
@@ -641,7 +678,11 @@ function buildProcessColorMap(names, config) {
|
|
|
641
678
|
for (const name of names) {
|
|
642
679
|
const explicit = resolveColor(config.processes[name]?.color);
|
|
643
680
|
if (explicit) {
|
|
644
|
-
|
|
681
|
+
const hex = resolveToHex(explicit);
|
|
682
|
+
if (hex)
|
|
683
|
+
map.set(name, hexToAnsi(hex));
|
|
684
|
+
else
|
|
685
|
+
map.set(name, DEFAULT_ANSI_COLORS[paletteIndex++ % DEFAULT_ANSI_COLORS.length]);
|
|
645
686
|
} else {
|
|
646
687
|
map.set(name, DEFAULT_ANSI_COLORS[paletteIndex % DEFAULT_ANSI_COLORS.length]);
|
|
647
688
|
paletteIndex++;
|
|
@@ -657,7 +698,11 @@ function buildProcessHexColorMap(names, config) {
|
|
|
657
698
|
for (const name of names) {
|
|
658
699
|
const explicit = resolveColor(config.processes[name]?.color);
|
|
659
700
|
if (explicit) {
|
|
660
|
-
|
|
701
|
+
const hex = resolveToHex(explicit);
|
|
702
|
+
if (hex)
|
|
703
|
+
map.set(name, hex);
|
|
704
|
+
else
|
|
705
|
+
map.set(name, DEFAULT_HEX_COLORS[paletteIndex++ % DEFAULT_HEX_COLORS.length]);
|
|
661
706
|
} else {
|
|
662
707
|
map.set(name, DEFAULT_HEX_COLORS[paletteIndex % DEFAULT_HEX_COLORS.length]);
|
|
663
708
|
paletteIndex++;
|
|
@@ -721,13 +766,13 @@ function validateConfig(raw, warnings) {
|
|
|
721
766
|
}
|
|
722
767
|
}
|
|
723
768
|
if (typeof p.color === "string") {
|
|
724
|
-
if (!
|
|
725
|
-
throw new Error(`Process "${name}".color must be a
|
|
769
|
+
if (!isValidColor(p.color)) {
|
|
770
|
+
throw new Error(`Process "${name}".color must be a hex color (e.g. "#ff8800") or basic name (black, red, green, yellow, blue, magenta, cyan, white, gray, orange, purple), got "${p.color}"`);
|
|
726
771
|
}
|
|
727
772
|
} else if (Array.isArray(p.color)) {
|
|
728
773
|
for (const c of p.color) {
|
|
729
|
-
if (typeof c !== "string" || !
|
|
730
|
-
throw new Error(`Process "${name}".color entries must be
|
|
774
|
+
if (typeof c !== "string" || !isValidColor(c)) {
|
|
775
|
+
throw new Error(`Process "${name}".color entries must be hex or basic names (black, red, green, yellow, blue, magenta, cyan, white, gray, orange), got "${c}"`);
|
|
731
776
|
}
|
|
732
777
|
}
|
|
733
778
|
}
|
|
@@ -764,7 +809,8 @@ function validateConfig(raw, warnings) {
|
|
|
764
809
|
stopSignal: validateStopSignal(p.stopSignal),
|
|
765
810
|
color: typeof p.color === "string" ? p.color : Array.isArray(p.color) ? p.color : undefined,
|
|
766
811
|
watch: validateStringOrStringArray(p.watch),
|
|
767
|
-
interactive: typeof p.interactive === "boolean" ? p.interactive : false
|
|
812
|
+
interactive: typeof p.interactive === "boolean" ? p.interactive : false,
|
|
813
|
+
errorMatcher: validateErrorMatcher(name, p.errorMatcher)
|
|
768
814
|
};
|
|
769
815
|
}
|
|
770
816
|
return { processes: validated };
|
|
@@ -777,6 +823,22 @@ function validateStringOrStringArray(value) {
|
|
|
777
823
|
return;
|
|
778
824
|
}
|
|
779
825
|
var validateEnvFile = validateStringOrStringArray;
|
|
826
|
+
function validateErrorMatcher(name, value) {
|
|
827
|
+
if (value === true)
|
|
828
|
+
return true;
|
|
829
|
+
if (typeof value === "string" && value.trim()) {
|
|
830
|
+
try {
|
|
831
|
+
new RegExp(value);
|
|
832
|
+
} catch (err) {
|
|
833
|
+
throw new Error(`Process "${name}".errorMatcher is not a valid regex: "${value}"`, { cause: err });
|
|
834
|
+
}
|
|
835
|
+
return value;
|
|
836
|
+
}
|
|
837
|
+
if (value !== undefined && value !== false) {
|
|
838
|
+
throw new Error(`Process "${name}".errorMatcher must be true or a regex string`);
|
|
839
|
+
}
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
780
842
|
var VALID_STOP_SIGNALS = new Set(["SIGTERM", "SIGINT", "SIGHUP"]);
|
|
781
843
|
function validateStopSignal(value) {
|
|
782
844
|
if (typeof value === "string" && VALID_STOP_SIGNALS.has(value)) {
|
|
@@ -884,8 +946,49 @@ function loadEnvFiles(envFile, cwd) {
|
|
|
884
946
|
return merged;
|
|
885
947
|
}
|
|
886
948
|
|
|
887
|
-
// src/process/
|
|
949
|
+
// src/process/error.ts
|
|
888
950
|
var BUFFER_CAP = 65536;
|
|
951
|
+
var SGR_RE = /\x1b\[([0-9;]*)m/g;
|
|
952
|
+
function hasAnsiRed(text) {
|
|
953
|
+
SGR_RE.lastIndex = 0;
|
|
954
|
+
for (let match = SGR_RE.exec(text);match !== null; match = SGR_RE.exec(text)) {
|
|
955
|
+
const params = match[1].split(";");
|
|
956
|
+
if (params.includes("31") || params.includes("91"))
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
function createErrorChecker(config) {
|
|
962
|
+
const matcher = config.errorMatcher;
|
|
963
|
+
if (!matcher)
|
|
964
|
+
return null;
|
|
965
|
+
const pattern = typeof matcher === "string" ? new RegExp(matcher) : null;
|
|
966
|
+
let buffer = "";
|
|
967
|
+
let triggered = false;
|
|
968
|
+
return {
|
|
969
|
+
feedOutput(data) {
|
|
970
|
+
if (triggered)
|
|
971
|
+
return false;
|
|
972
|
+
buffer += data;
|
|
973
|
+
if (buffer.length > BUFFER_CAP) {
|
|
974
|
+
buffer = buffer.slice(-BUFFER_CAP);
|
|
975
|
+
}
|
|
976
|
+
if (pattern) {
|
|
977
|
+
if (pattern.test(stripAnsi(buffer))) {
|
|
978
|
+
triggered = true;
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
} else if (hasAnsiRed(buffer)) {
|
|
982
|
+
triggered = true;
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// src/process/ready.ts
|
|
991
|
+
var BUFFER_CAP2 = 65536;
|
|
889
992
|
function createReadinessChecker(config) {
|
|
890
993
|
const pattern = config.readyPattern ? new RegExp(config.readyPattern) : null;
|
|
891
994
|
const persistent = config.persistent !== false;
|
|
@@ -895,8 +998,8 @@ function createReadinessChecker(config) {
|
|
|
895
998
|
if (!(persistent && pattern))
|
|
896
999
|
return false;
|
|
897
1000
|
outputBuffer += data;
|
|
898
|
-
if (outputBuffer.length >
|
|
899
|
-
outputBuffer = outputBuffer.slice(-
|
|
1001
|
+
if (outputBuffer.length > BUFFER_CAP2) {
|
|
1002
|
+
outputBuffer = outputBuffer.slice(-BUFFER_CAP2);
|
|
900
1003
|
}
|
|
901
1004
|
return pattern.test(outputBuffer);
|
|
902
1005
|
},
|
|
@@ -916,9 +1019,11 @@ class ProcessRunner {
|
|
|
916
1019
|
handler;
|
|
917
1020
|
proc = null;
|
|
918
1021
|
readiness;
|
|
1022
|
+
errorChecker;
|
|
919
1023
|
_ready = false;
|
|
920
1024
|
stopping = false;
|
|
921
1025
|
decoder = new TextDecoder;
|
|
1026
|
+
errorDecoder = new TextDecoder;
|
|
922
1027
|
generation = 0;
|
|
923
1028
|
readyTimer = null;
|
|
924
1029
|
restarting = false;
|
|
@@ -928,6 +1033,7 @@ class ProcessRunner {
|
|
|
928
1033
|
this.config = config;
|
|
929
1034
|
this.handler = handler;
|
|
930
1035
|
this.readiness = createReadinessChecker(config);
|
|
1036
|
+
this.errorChecker = createErrorChecker(config);
|
|
931
1037
|
}
|
|
932
1038
|
get isReady() {
|
|
933
1039
|
return this._ready;
|
|
@@ -962,6 +1068,7 @@ class ProcessRunner {
|
|
|
962
1068
|
return;
|
|
963
1069
|
this.handler.onOutput(data);
|
|
964
1070
|
this.checkReadiness(data);
|
|
1071
|
+
this.checkError(data);
|
|
965
1072
|
}
|
|
966
1073
|
}
|
|
967
1074
|
});
|
|
@@ -1017,6 +1124,14 @@ class ProcessRunner {
|
|
|
1017
1124
|
this.markReady();
|
|
1018
1125
|
}
|
|
1019
1126
|
}
|
|
1127
|
+
checkError(data) {
|
|
1128
|
+
if (!this.errorChecker)
|
|
1129
|
+
return;
|
|
1130
|
+
const text = this.errorDecoder.decode(data, { stream: true });
|
|
1131
|
+
if (this.errorChecker.feedOutput(text)) {
|
|
1132
|
+
this.handler.onError();
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1020
1135
|
startReadyTimeout(gen) {
|
|
1021
1136
|
const timeout = this.config.readyTimeout;
|
|
1022
1137
|
if (!(timeout && this.config.readyPattern) || this.config.persistent === false)
|
|
@@ -1075,6 +1190,7 @@ class ProcessRunner {
|
|
|
1075
1190
|
this.restarting = false;
|
|
1076
1191
|
this.readyTimedOut = false;
|
|
1077
1192
|
this.readiness = createReadinessChecker(this.config);
|
|
1193
|
+
this.errorChecker = createErrorChecker(this.config);
|
|
1078
1194
|
this.start(cols, rows);
|
|
1079
1195
|
}
|
|
1080
1196
|
async stop(timeoutMs = 5000) {
|
|
@@ -1244,7 +1360,8 @@ class ProcessManager {
|
|
|
1244
1360
|
readyResolved = true;
|
|
1245
1361
|
onInitialReady();
|
|
1246
1362
|
}
|
|
1247
|
-
}
|
|
1363
|
+
},
|
|
1364
|
+
onError: () => this.emit({ type: "error", name })
|
|
1248
1365
|
});
|
|
1249
1366
|
this.runners.set(name, runner);
|
|
1250
1367
|
}
|
|
@@ -1651,20 +1768,24 @@ class ColoredSelectRenderable extends SelectRenderable {
|
|
|
1651
1768
|
const scrollOffset = this.scrollOffset;
|
|
1652
1769
|
const maxVisibleItems = this.maxVisibleItems;
|
|
1653
1770
|
const linesPerItem = this.linesPerItem;
|
|
1771
|
+
const selectedIndex = this.getSelectedIndex();
|
|
1654
1772
|
const options = this.options;
|
|
1655
1773
|
const visibleCount = Math.min(maxVisibleItems, options.length - scrollOffset);
|
|
1774
|
+
const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
|
|
1775
|
+
const selectedTextColor = this._selectedTextColor;
|
|
1656
1776
|
for (let i = 0;i < visibleCount; i++) {
|
|
1657
1777
|
const actualIndex = scrollOffset + i;
|
|
1658
|
-
const colors = this._optionColors[actualIndex];
|
|
1659
|
-
if (!colors)
|
|
1660
|
-
continue;
|
|
1661
1778
|
const itemY = i * linesPerItem;
|
|
1662
1779
|
const optName = options[actualIndex].name;
|
|
1663
|
-
|
|
1664
|
-
|
|
1780
|
+
const isSelected = actualIndex === selectedIndex;
|
|
1781
|
+
const defaultColor = isSelected ? selectedTextColor : baseTextColor;
|
|
1782
|
+
const colors = this._optionColors[actualIndex];
|
|
1783
|
+
fb.drawText(`${optName} `, 1, itemY, defaultColor);
|
|
1784
|
+
if (colors?.icon) {
|
|
1785
|
+
fb.drawText(optName.charAt(0), 1, itemY, colors.icon);
|
|
1665
1786
|
}
|
|
1666
|
-
if (colors
|
|
1667
|
-
fb.drawText(optName.slice(2),
|
|
1787
|
+
if (colors?.name) {
|
|
1788
|
+
fb.drawText(optName.slice(2), 3, itemY, colors.name);
|
|
1668
1789
|
}
|
|
1669
1790
|
}
|
|
1670
1791
|
}
|
|
@@ -1678,6 +1799,7 @@ class TabBar {
|
|
|
1678
1799
|
baseDescriptions;
|
|
1679
1800
|
processColors;
|
|
1680
1801
|
inputWaiting = new Set;
|
|
1802
|
+
erroredProcesses = new Set;
|
|
1681
1803
|
constructor(renderer, names, colors) {
|
|
1682
1804
|
this.originalNames = names;
|
|
1683
1805
|
this.names = [...names];
|
|
@@ -1716,6 +1838,9 @@ class TabBar {
|
|
|
1716
1838
|
if (TERMINAL_STATUSES.has(status) || status === "stopping") {
|
|
1717
1839
|
this.inputWaiting.delete(name);
|
|
1718
1840
|
}
|
|
1841
|
+
if (status === "starting") {
|
|
1842
|
+
this.erroredProcesses.delete(name);
|
|
1843
|
+
}
|
|
1719
1844
|
this.refreshOptions();
|
|
1720
1845
|
}
|
|
1721
1846
|
setInputWaiting(name, waiting) {
|
|
@@ -1725,6 +1850,13 @@ class TabBar {
|
|
|
1725
1850
|
this.inputWaiting.delete(name);
|
|
1726
1851
|
this.refreshOptions();
|
|
1727
1852
|
}
|
|
1853
|
+
setError(name, hasError) {
|
|
1854
|
+
if (hasError)
|
|
1855
|
+
this.erroredProcesses.add(name);
|
|
1856
|
+
else
|
|
1857
|
+
this.erroredProcesses.delete(name);
|
|
1858
|
+
this.refreshOptions();
|
|
1859
|
+
}
|
|
1728
1860
|
getNameAtIndex(index) {
|
|
1729
1861
|
return this.names[index];
|
|
1730
1862
|
}
|
|
@@ -1753,13 +1885,16 @@ class TabBar {
|
|
|
1753
1885
|
getDescription(name) {
|
|
1754
1886
|
if (this.inputWaiting.has(name))
|
|
1755
1887
|
return "awaiting input";
|
|
1888
|
+
if (this.erroredProcesses.has(name))
|
|
1889
|
+
return "error detected";
|
|
1756
1890
|
return this.baseDescriptions.get(name) ?? "pending";
|
|
1757
1891
|
}
|
|
1758
1892
|
updateOptionColors() {
|
|
1759
1893
|
const colors = this.names.map((name) => {
|
|
1760
1894
|
const status = this.statuses.get(name);
|
|
1761
1895
|
const waiting = this.inputWaiting.has(name);
|
|
1762
|
-
const
|
|
1896
|
+
const errored = this.erroredProcesses.has(name);
|
|
1897
|
+
const statusHex = waiting ? "#ffaa00" : errored ? "#ff5555" : STATUS_ICON_HEX[status];
|
|
1763
1898
|
const processHex = this.processColors.get(name);
|
|
1764
1899
|
return {
|
|
1765
1900
|
icon: parseColor(statusHex ?? processHex ?? "#888888"),
|
|
@@ -1881,6 +2016,8 @@ class App {
|
|
|
1881
2016
|
if (this.config.processes[event.name]?.interactive) {
|
|
1882
2017
|
this.checkInputWaiting(event.name, event.data);
|
|
1883
2018
|
}
|
|
2019
|
+
} else if (event.type === "error") {
|
|
2020
|
+
this.tabBar.setError(event.name, true);
|
|
1884
2021
|
} else if (event.type === "status") {
|
|
1885
2022
|
const state = this.manager.getState(event.name);
|
|
1886
2023
|
this.tabBar.updateStatus(event.name, event.status, state?.exitCode, state?.restartCount);
|
|
@@ -2403,7 +2540,8 @@ Usage:
|
|
|
2403
2540
|
|
|
2404
2541
|
Options:
|
|
2405
2542
|
-n, --name <name=command> Add a named process
|
|
2406
|
-
-c, --color <colors> Comma-separated colors
|
|
2543
|
+
-c, --color <colors> Comma-separated colors (hex or names: black, red, green, yellow, blue, magenta, cyan, white, gray, orange, purple)
|
|
2544
|
+
--colors Auto-assign colors to processes based on their name
|
|
2407
2545
|
--config <path> Config file path (default: auto-detect)
|
|
2408
2546
|
-p, --prefix Prefixed output mode (no TUI, for CI/scripts)
|
|
2409
2547
|
--only <a,b,...> Only run these processes (+ their dependencies)
|
|
@@ -2581,6 +2719,13 @@ async function main() {
|
|
|
2581
2719
|
if (parsed.only || parsed.exclude) {
|
|
2582
2720
|
config = filterConfig(config, parsed.only, parsed.exclude);
|
|
2583
2721
|
}
|
|
2722
|
+
if (parsed.autoColors) {
|
|
2723
|
+
for (const [name, proc] of Object.entries(config.processes)) {
|
|
2724
|
+
if (!proc.color) {
|
|
2725
|
+
proc.color = colorFromName(name);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2584
2729
|
const manager = new ProcessManager(config);
|
|
2585
2730
|
let logWriter;
|
|
2586
2731
|
if (parsed.logDir) {
|
package/dist/types.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface NumuxProcessConfig {
|
|
|
14
14
|
color?: string | string[];
|
|
15
15
|
watch?: string | string[];
|
|
16
16
|
interactive?: boolean;
|
|
17
|
+
errorMatcher?: boolean | string;
|
|
17
18
|
}
|
|
18
19
|
/** Config for npm: wildcard entries — command is derived from package.json scripts */
|
|
19
20
|
export type NumuxScriptPattern = Omit<NumuxProcessConfig, 'command'> & {
|
|
@@ -50,4 +51,7 @@ export type ProcessEvent = {
|
|
|
50
51
|
type: 'exit';
|
|
51
52
|
name: string;
|
|
52
53
|
code: number | null;
|
|
54
|
+
} | {
|
|
55
|
+
type: 'error';
|
|
56
|
+
name: string;
|
|
53
57
|
};
|