numux 1.17.0 → 1.19.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 +8 -5
- package/dist/config.d.ts +1 -1
- package/dist/numux.js +41 -20
- package/dist/types.d.ts +54 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,7 +191,8 @@ Top-level options apply to all processes (process-level settings override):
|
|
|
191
191
|
|-------|------|-------------|
|
|
192
192
|
| `cwd` | `string` | Working directory for all processes (process `cwd` overrides) |
|
|
193
193
|
| `env` | `Record<string, string>` | Environment variables merged into all processes (process `env` overrides per key) |
|
|
194
|
-
| `envFile` | `string \| string[]` | `.env` file(s) for all processes (process `envFile` replaces if set) |
|
|
194
|
+
| `envFile` | `string \| string[] \| false` | `.env` file(s) for all processes (process `envFile` replaces if set; `false` disables) |
|
|
195
|
+
| `showCommand` | `boolean` | Print the command being run as the first line of output (default: `true`) |
|
|
195
196
|
|
|
196
197
|
```ts
|
|
197
198
|
export default defineConfig({
|
|
@@ -211,10 +212,10 @@ Each process accepts:
|
|
|
211
212
|
|
|
212
213
|
| Field | Type | Default | Description |
|
|
213
214
|
|-------|------|---------|-------------|
|
|
214
|
-
| `command` | `string` | *required* | Shell command to run |
|
|
215
|
+
| `command` | `string` | *required* | Shell command to run. Supports `$dep.group` references from dependency capture groups |
|
|
215
216
|
| `cwd` | `string` | `process.cwd()` | Working directory |
|
|
216
|
-
| `env` | `Record<string, string>` | — | Extra environment variables |
|
|
217
|
-
| `envFile` | `string \| string[]` | — | `.env` file path(s) to load (relative to `cwd`) |
|
|
217
|
+
| `env` | `Record<string, string>` | — | Extra environment variables. Values support `$dep.group` references from dependency capture groups |
|
|
218
|
+
| `envFile` | `string \| string[] \| false` | — | `.env` file path(s) to load (relative to `cwd`); `false` disables inherited envFile |
|
|
218
219
|
| `dependsOn` | `string[]` | — | Processes that must be ready first |
|
|
219
220
|
| `readyPattern` | `string \| RegExp` | — | Regex matched against stdout to signal readiness. Use `RegExp` to capture groups (see below) |
|
|
220
221
|
| `readyTimeout` | `number` | — | Milliseconds to wait for `readyPattern` before failing |
|
|
@@ -227,6 +228,7 @@ Each process accepts:
|
|
|
227
228
|
| `watch` | `string \| string[]` | — | Glob patterns — restart process when matching files change |
|
|
228
229
|
| `interactive` | `boolean` | `false` | When `true`, keyboard input is forwarded to the process |
|
|
229
230
|
| `errorMatcher` | `boolean \| string` | — | `true` detects ANSI red output, string = regex pattern — shows error indicator on tab |
|
|
231
|
+
| `showCommand` | `boolean` | `true` | Print the command being run as the first line of output |
|
|
230
232
|
|
|
231
233
|
### File watching
|
|
232
234
|
|
|
@@ -311,7 +313,7 @@ Persistent processes that crash are auto-restarted with exponential backoff (1s
|
|
|
311
313
|
|
|
312
314
|
### Dependency output capture
|
|
313
315
|
|
|
314
|
-
When `readyPattern` is a `RegExp` (not a string), capture groups are extracted on match and expanded into dependent process
|
|
316
|
+
When `readyPattern` is a `RegExp` (not a string), capture groups are extracted on match and expanded into dependent process `command` and `env` values using `$process.group` syntax:
|
|
315
317
|
|
|
316
318
|
```ts
|
|
317
319
|
export default defineConfig({
|
|
@@ -323,6 +325,7 @@ export default defineConfig({
|
|
|
323
325
|
api: {
|
|
324
326
|
command: 'node server.js --db-port $db.port',
|
|
325
327
|
dependsOn: ['db'],
|
|
328
|
+
env: { DB_PORT: '$db.port' },
|
|
326
329
|
},
|
|
327
330
|
},
|
|
328
331
|
})
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { NumuxConfig } from './types';
|
|
2
2
|
export type { NumuxConfig, NumuxProcessConfig } from './types';
|
|
3
3
|
/** Type-safe helper for numux.config.ts files. */
|
|
4
|
-
export declare function defineConfig(config: NumuxConfig): NumuxConfig
|
|
4
|
+
export declare function defineConfig<K extends string>(config: NumuxConfig<K>): NumuxConfig<K>;
|
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: "1.
|
|
39
|
+
version: "1.19.0",
|
|
40
40
|
description: "Terminal multiplexer with dependency orchestration",
|
|
41
41
|
type: "module",
|
|
42
42
|
license: "MIT",
|
|
@@ -1030,8 +1030,10 @@ function validateConfig(raw, warnings) {
|
|
|
1030
1030
|
throw new Error(`Process "${name}" must have a non-empty "command" string`);
|
|
1031
1031
|
}
|
|
1032
1032
|
if (p.dependsOn !== undefined) {
|
|
1033
|
+
if (typeof p.dependsOn === "string")
|
|
1034
|
+
p.dependsOn = [p.dependsOn];
|
|
1033
1035
|
if (!Array.isArray(p.dependsOn)) {
|
|
1034
|
-
throw new Error(`Process "${name}".dependsOn must be
|
|
1036
|
+
throw new Error(`Process "${name}".dependsOn must be a string or array`);
|
|
1035
1037
|
}
|
|
1036
1038
|
for (const dep of p.dependsOn) {
|
|
1037
1039
|
if (typeof dep !== "string") {
|
|
@@ -1419,6 +1421,7 @@ class ProcessRunner {
|
|
|
1419
1421
|
restarting = false;
|
|
1420
1422
|
readyTimedOut = false;
|
|
1421
1423
|
commandOverride;
|
|
1424
|
+
envOverride;
|
|
1422
1425
|
constructor(name, config, handler) {
|
|
1423
1426
|
this.name = name;
|
|
1424
1427
|
this.config = config;
|
|
@@ -1432,9 +1435,11 @@ class ProcessRunner {
|
|
|
1432
1435
|
get signal() {
|
|
1433
1436
|
return this.config.stopSignal ?? "SIGTERM";
|
|
1434
1437
|
}
|
|
1435
|
-
start(cols, rows, commandOverride) {
|
|
1438
|
+
start(cols, rows, commandOverride, envOverride) {
|
|
1436
1439
|
if (commandOverride !== undefined)
|
|
1437
1440
|
this.commandOverride = commandOverride;
|
|
1441
|
+
if (envOverride !== undefined)
|
|
1442
|
+
this.envOverride = envOverride;
|
|
1438
1443
|
const command = this.commandOverride ?? this.config.command;
|
|
1439
1444
|
const gen = ++this.generation;
|
|
1440
1445
|
this.stopping = false;
|
|
@@ -1449,7 +1454,7 @@ class ProcessRunner {
|
|
|
1449
1454
|
...noColor ? {} : { FORCE_COLOR: "1" },
|
|
1450
1455
|
TERM: "xterm-256color",
|
|
1451
1456
|
...envFromFile,
|
|
1452
|
-
...this.config.env
|
|
1457
|
+
...this.envOverride ?? this.config.env
|
|
1453
1458
|
};
|
|
1454
1459
|
this.proc = Bun.spawn(["sh", "-c", command], {
|
|
1455
1460
|
cwd,
|
|
@@ -1567,7 +1572,7 @@ class ProcessRunner {
|
|
|
1567
1572
|
this.handler.onStatus("ready");
|
|
1568
1573
|
this.handler.onReady(this.readiness.captures);
|
|
1569
1574
|
}
|
|
1570
|
-
async restart(cols, rows, commandOverride) {
|
|
1575
|
+
async restart(cols, rows, commandOverride, envOverride) {
|
|
1571
1576
|
if (this.restarting)
|
|
1572
1577
|
return;
|
|
1573
1578
|
this.restarting = true;
|
|
@@ -1592,7 +1597,7 @@ class ProcessRunner {
|
|
|
1592
1597
|
this.readyTimedOut = false;
|
|
1593
1598
|
this.readiness = createReadinessChecker(this.config);
|
|
1594
1599
|
this.errorChecker = createErrorChecker(this.config);
|
|
1595
|
-
this.start(cols, rows, commandOverride);
|
|
1600
|
+
this.start(cols, rows, commandOverride, envOverride);
|
|
1596
1601
|
}
|
|
1597
1602
|
async stop(timeoutMs = 5000) {
|
|
1598
1603
|
if (!this.proc)
|
|
@@ -1737,7 +1742,7 @@ class ProcessManager {
|
|
|
1737
1742
|
this.setupWatchers();
|
|
1738
1743
|
}
|
|
1739
1744
|
startProcess(name, cols, rows) {
|
|
1740
|
-
const
|
|
1745
|
+
const { command, env } = this.expandDependencyCaptures(name);
|
|
1741
1746
|
const delay = this.config.processes[name].delay;
|
|
1742
1747
|
if (delay) {
|
|
1743
1748
|
log(`[${name}] Delaying start by ${delay}ms`);
|
|
@@ -1746,12 +1751,12 @@ class ProcessManager {
|
|
|
1746
1751
|
if (this.stopping)
|
|
1747
1752
|
return;
|
|
1748
1753
|
this.startTimes.set(name, Date.now());
|
|
1749
|
-
this.runners.get(name).start(cols, rows,
|
|
1754
|
+
this.runners.get(name).start(cols, rows, command, env);
|
|
1750
1755
|
}, delay);
|
|
1751
1756
|
this.restartTimers.set(name, timer);
|
|
1752
1757
|
} else {
|
|
1753
1758
|
this.startTimes.set(name, Date.now());
|
|
1754
|
-
this.runners.get(name).start(cols, rows,
|
|
1759
|
+
this.runners.get(name).start(cols, rows, command, env);
|
|
1755
1760
|
}
|
|
1756
1761
|
}
|
|
1757
1762
|
createRunner(name, onInitialReady) {
|
|
@@ -1860,7 +1865,7 @@ class ProcessManager {
|
|
|
1860
1865
|
const proc = this.config.processes[name];
|
|
1861
1866
|
const deps = proc.dependsOn;
|
|
1862
1867
|
if (!deps?.length)
|
|
1863
|
-
return;
|
|
1868
|
+
return {};
|
|
1864
1869
|
const allCaptures = new Map;
|
|
1865
1870
|
for (const dep of deps) {
|
|
1866
1871
|
const captures = this.readyCaptures.get(dep);
|
|
@@ -1868,19 +1873,33 @@ class ProcessManager {
|
|
|
1868
1873
|
allCaptures.set(dep, captures);
|
|
1869
1874
|
}
|
|
1870
1875
|
if (allCaptures.size === 0)
|
|
1871
|
-
return;
|
|
1876
|
+
return {};
|
|
1872
1877
|
const depNames = [...allCaptures.keys()].map((n) => escapeRegExp(n)).join("|");
|
|
1873
1878
|
const refPattern = new RegExp(`\\$(${depNames})\\.(\\w+)`, "g");
|
|
1874
|
-
|
|
1875
|
-
const expanded = proc.command.replace(refPattern, (match, dep, key) => {
|
|
1879
|
+
const replacer = (match, dep, key) => {
|
|
1876
1880
|
const captures = allCaptures.get(dep);
|
|
1877
|
-
if (captures && key in captures)
|
|
1878
|
-
hadReplacement = true;
|
|
1881
|
+
if (captures && key in captures)
|
|
1879
1882
|
return captures[key];
|
|
1880
|
-
}
|
|
1881
1883
|
return match;
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
+
};
|
|
1885
|
+
let command;
|
|
1886
|
+
const expandedCmd = proc.command.replace(refPattern, replacer);
|
|
1887
|
+
if (expandedCmd !== proc.command)
|
|
1888
|
+
command = expandedCmd;
|
|
1889
|
+
let env;
|
|
1890
|
+
if (proc.env) {
|
|
1891
|
+
const expandedEnv = {};
|
|
1892
|
+
let hadReplacement = false;
|
|
1893
|
+
for (const [k, v] of Object.entries(proc.env)) {
|
|
1894
|
+
const expanded = v.replace(refPattern, replacer);
|
|
1895
|
+
expandedEnv[k] = expanded;
|
|
1896
|
+
if (expanded !== v)
|
|
1897
|
+
hadReplacement = true;
|
|
1898
|
+
}
|
|
1899
|
+
if (hadReplacement)
|
|
1900
|
+
env = expandedEnv;
|
|
1901
|
+
}
|
|
1902
|
+
return { command, env };
|
|
1884
1903
|
}
|
|
1885
1904
|
updateStatus(name, status) {
|
|
1886
1905
|
const state = this.states.get(name);
|
|
@@ -1908,7 +1927,8 @@ class ProcessManager {
|
|
|
1908
1927
|
state.exitCode = null;
|
|
1909
1928
|
state.restartCount++;
|
|
1910
1929
|
this.startTimes.set(name, Date.now());
|
|
1911
|
-
|
|
1930
|
+
const { command, env } = this.expandDependencyCaptures(name);
|
|
1931
|
+
runner.restart(cols, rows, command, env);
|
|
1912
1932
|
}
|
|
1913
1933
|
async stop(name) {
|
|
1914
1934
|
const state = this.states.get(name);
|
|
@@ -1946,7 +1966,8 @@ class ProcessManager {
|
|
|
1946
1966
|
state.exitCode = null;
|
|
1947
1967
|
state.restartCount++;
|
|
1948
1968
|
this.startTimes.set(name, Date.now());
|
|
1949
|
-
|
|
1969
|
+
const { command, env } = this.expandDependencyCaptures(name);
|
|
1970
|
+
this.runners.get(name)?.restart(cols, rows, command, env);
|
|
1950
1971
|
}
|
|
1951
1972
|
restartAll(cols, rows) {
|
|
1952
1973
|
log("Restarting all processes");
|
package/dist/types.d.ts
CHANGED
|
@@ -1,42 +1,89 @@
|
|
|
1
|
-
export interface NumuxProcessConfig {
|
|
1
|
+
export interface NumuxProcessConfig<K extends string = string> {
|
|
2
|
+
/** Shell command to run. Supports `$dep.group` references from dependency capture groups */
|
|
2
3
|
command: string;
|
|
4
|
+
/** Working directory for the process */
|
|
3
5
|
cwd?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Extra environment variables. Values support `$dep.group` references
|
|
8
|
+
* from dependency capture groups.
|
|
9
|
+
* @example { DB_PORT: '$db.port' }
|
|
10
|
+
*/
|
|
4
11
|
env?: Record<string, string>;
|
|
12
|
+
/** .env file path(s) to load, or `false` to disable */
|
|
5
13
|
envFile?: string | string[] | false;
|
|
6
|
-
|
|
14
|
+
/** Processes that must be ready before this one starts */
|
|
15
|
+
dependsOn?: NoInfer<K> | NoInfer<K>[];
|
|
16
|
+
/** Regex matched against stdout to signal readiness. Use `RegExp` to capture groups for `$dep.group` expansion */
|
|
7
17
|
readyPattern?: string | RegExp;
|
|
18
|
+
/**
|
|
19
|
+
* Set to `false` for one-shot processes
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
8
22
|
persistent?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Limit auto-restart attempts
|
|
25
|
+
* @default Infinity
|
|
26
|
+
*/
|
|
9
27
|
maxRestarts?: number;
|
|
28
|
+
/** Milliseconds to wait for readyPattern before failing */
|
|
10
29
|
readyTimeout?: number;
|
|
30
|
+
/** Milliseconds to wait before starting the process */
|
|
11
31
|
delay?: number;
|
|
32
|
+
/** Env var name (prefix with `!` to negate); process skipped if condition is falsy */
|
|
12
33
|
condition?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Signal for graceful stop
|
|
36
|
+
* @default 'SIGTERM'
|
|
37
|
+
*/
|
|
13
38
|
stopSignal?: 'SIGTERM' | 'SIGINT' | 'SIGHUP';
|
|
39
|
+
/** Hex color (e.g. `"#ff6600"`) or color name. Array for round-robin in script patterns */
|
|
14
40
|
color?: string | string[];
|
|
41
|
+
/** Glob patterns — restart process when matching files change */
|
|
15
42
|
watch?: string | string[];
|
|
43
|
+
/**
|
|
44
|
+
* When true, keyboard input is forwarded to the process
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
16
47
|
interactive?: boolean;
|
|
48
|
+
/** `true` = detect ANSI red output, string = regex pattern */
|
|
17
49
|
errorMatcher?: boolean | string;
|
|
50
|
+
/**
|
|
51
|
+
* Print the command being run as the first line of output
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
18
54
|
showCommand?: boolean;
|
|
19
55
|
}
|
|
20
56
|
/** Config for npm: wildcard entries — command is derived from package.json scripts */
|
|
21
|
-
export type NumuxScriptPattern = Omit<NumuxProcessConfig
|
|
57
|
+
export type NumuxScriptPattern<K extends string = string> = Omit<NumuxProcessConfig<K>, 'command'> & {
|
|
22
58
|
command?: never;
|
|
23
59
|
};
|
|
24
60
|
/** Raw config as authored — processes can be string shorthand, full objects, or wildcard patterns */
|
|
25
|
-
export interface NumuxConfig {
|
|
61
|
+
export interface NumuxConfig<K extends string = string> {
|
|
62
|
+
/** Global working directory, inherited by all processes */
|
|
26
63
|
cwd?: string;
|
|
64
|
+
/** Global env vars, merged into each process (process-level overrides) */
|
|
27
65
|
env?: Record<string, string>;
|
|
66
|
+
/** Global .env file(s), inherited by processes without their own envFile; `false` disables */
|
|
28
67
|
envFile?: string | string[] | false;
|
|
68
|
+
/**
|
|
69
|
+
* Global showCommand flag, inherited by all processes
|
|
70
|
+
* @default true
|
|
71
|
+
*/
|
|
29
72
|
showCommand?: boolean;
|
|
30
|
-
processes: Record<
|
|
73
|
+
processes: Record<K, NumuxProcessConfig<K> | NumuxScriptPattern<K> | string>;
|
|
74
|
+
}
|
|
75
|
+
/** Process config after validation — dependsOn is always normalized to an array */
|
|
76
|
+
export interface ResolvedProcessConfig extends Omit<NumuxProcessConfig, 'dependsOn'> {
|
|
77
|
+
dependsOn?: string[];
|
|
31
78
|
}
|
|
32
79
|
/** Validated config with all shorthand expanded to full objects */
|
|
33
80
|
export interface ResolvedNumuxConfig {
|
|
34
|
-
processes: Record<string,
|
|
81
|
+
processes: Record<string, ResolvedProcessConfig>;
|
|
35
82
|
}
|
|
36
83
|
export type ProcessStatus = 'pending' | 'starting' | 'ready' | 'running' | 'stopping' | 'stopped' | 'finished' | 'failed' | 'skipped';
|
|
37
84
|
export interface ProcessState {
|
|
38
85
|
name: string;
|
|
39
|
-
config:
|
|
86
|
+
config: ResolvedProcessConfig;
|
|
40
87
|
status: ProcessStatus;
|
|
41
88
|
exitCode: number | null;
|
|
42
89
|
restartCount: number;
|