numux 2.8.0 → 2.10.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 +27 -0
- package/dist/numux.js +60 -6
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,7 @@ numux
|
|
|
73
73
|
numux init # Create a starter numux.config.ts
|
|
74
74
|
numux validate # Validate config and show process dependency graph
|
|
75
75
|
numux exec <name> [--] <command> # Run a command in a process's environment
|
|
76
|
+
numux logs [name] # Open log directory or view a process log
|
|
76
77
|
numux completions <shell> # Generate shell completions (bash, zsh, fish)
|
|
77
78
|
```
|
|
78
79
|
|
|
@@ -85,6 +86,13 @@ numux exec api -- npx prisma migrate
|
|
|
85
86
|
numux exec web npm run build
|
|
86
87
|
```
|
|
87
88
|
|
|
89
|
+
`logs` prints the log directory path, or a specific process's log contents:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
numux logs # Print log directory path
|
|
93
|
+
numux logs api # Print the api process log
|
|
94
|
+
```
|
|
95
|
+
|
|
88
96
|
Set up completions for your shell:
|
|
89
97
|
|
|
90
98
|
```sh
|
|
@@ -248,6 +256,7 @@ Each process accepts:
|
|
|
248
256
|
| `readyTimeout` | `number` | — | Milliseconds to wait for `readyPattern` before failing |
|
|
249
257
|
| `maxRestarts` | `number` | `0` | Max auto-restart attempts on non-zero exit (0 = no restarts) |
|
|
250
258
|
| `delay` | `number` | — | Milliseconds to wait before starting the process |
|
|
259
|
+
| `optional` | `boolean` | `false` | Process is visible as a tab but not started automatically. Use Alt+S to start manually |
|
|
251
260
|
| `condition` | `string` | — | Env var name; process skipped if falsy. Prefix with `!` to negate |
|
|
252
261
|
| `platform` | `string \| string[]` | — | OS(es) this process runs on (e.g. `'darwin'`, `'linux'`). Non-matching processes are removed; dependents still start |
|
|
253
262
|
| `stopSignal` | `string` | `SIGTERM` | Signal for graceful stop (`SIGTERM`, `SIGINT`, or `SIGHUP`) |
|
|
@@ -352,6 +361,24 @@ export default defineConfig({
|
|
|
352
361
|
|
|
353
362
|
Falsy values: unset, empty string, `"0"`, `"false"`, `"no"`, `"off"` (case-insensitive). If a conditional process is skipped, its dependents are also skipped.
|
|
354
363
|
|
|
364
|
+
### Optional processes
|
|
365
|
+
|
|
366
|
+
Use `optional` for tools you want visible in tabs but not auto-started (e.g. Prisma Studio, debug servers):
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
export default defineConfig({
|
|
370
|
+
processes: {
|
|
371
|
+
app: { command: 'bun run dev' },
|
|
372
|
+
studio: {
|
|
373
|
+
command: 'bunx prisma studio',
|
|
374
|
+
optional: true, // shows as stopped tab, start with Alt+S
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
})
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Unlike `condition`, optional processes don't cascade — their dependents still start normally.
|
|
381
|
+
|
|
355
382
|
### Dependency orchestration
|
|
356
383
|
|
|
357
384
|
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.
|
package/dist/numux.js
CHANGED
|
@@ -31,12 +31,13 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
31
31
|
return to;
|
|
32
32
|
};
|
|
33
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __require = import.meta.require;
|
|
34
35
|
|
|
35
36
|
// package.json
|
|
36
37
|
var require_package = __commonJS((exports, module) => {
|
|
37
38
|
module.exports = {
|
|
38
39
|
name: "numux",
|
|
39
|
-
version: "2.
|
|
40
|
+
version: "2.10.0",
|
|
40
41
|
description: "Terminal multiplexer with dependency orchestration",
|
|
41
42
|
type: "module",
|
|
42
43
|
license: "MIT",
|
|
@@ -300,6 +301,20 @@ var SUBCOMMANDS = [
|
|
|
300
301
|
return "break";
|
|
301
302
|
}
|
|
302
303
|
},
|
|
304
|
+
{
|
|
305
|
+
name: "logs",
|
|
306
|
+
description: "Open the log directory or a specific process log",
|
|
307
|
+
usage: "logs [name]",
|
|
308
|
+
parse: (args, i, result) => {
|
|
309
|
+
result.logs = true;
|
|
310
|
+
const next = args[i + 1];
|
|
311
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
312
|
+
result.logsProcess = next;
|
|
313
|
+
i++;
|
|
314
|
+
}
|
|
315
|
+
return i;
|
|
316
|
+
}
|
|
317
|
+
},
|
|
303
318
|
{
|
|
304
319
|
name: "completions",
|
|
305
320
|
description: "Generate shell completions (bash, zsh, fish)",
|
|
@@ -364,6 +379,7 @@ function parseArgs(argv) {
|
|
|
364
379
|
init: false,
|
|
365
380
|
validate: false,
|
|
366
381
|
exec: false,
|
|
382
|
+
logs: false,
|
|
367
383
|
prefix: false,
|
|
368
384
|
killOthers: false,
|
|
369
385
|
killOthersOnFail: false,
|
|
@@ -1221,6 +1237,7 @@ function validateConfig(raw, _warnings) {
|
|
|
1221
1237
|
const processWatch = validateStringOrStringArray(p.watch);
|
|
1222
1238
|
validated[name] = {
|
|
1223
1239
|
command: p.command,
|
|
1240
|
+
...p.optional === true ? { optional: true } : {},
|
|
1224
1241
|
cwd: processCwd ?? globalCwd,
|
|
1225
1242
|
env: globalEnv || processEnv ? { ...globalEnv, ...processEnv } : undefined,
|
|
1226
1243
|
envFile: processEnvFile ?? globalEnvFile,
|
|
@@ -1951,6 +1968,12 @@ class ProcessManager {
|
|
|
1951
1968
|
const launches = this.tiers.flat().map(async (name) => {
|
|
1952
1969
|
const proc = this.config.processes[name];
|
|
1953
1970
|
const resolve8 = readyResolvers.get(name);
|
|
1971
|
+
if (proc.optional) {
|
|
1972
|
+
this.updateStatus(name, "stopped");
|
|
1973
|
+
this.createRunner(name);
|
|
1974
|
+
resolve8();
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1954
1977
|
const deps = proc.dependsOn ?? [];
|
|
1955
1978
|
if (deps.length > 0) {
|
|
1956
1979
|
await Promise.all(deps.map((d) => readyPromises.get(d)));
|
|
@@ -3513,8 +3536,8 @@ class PrefixDisplay {
|
|
|
3513
3536
|
this.shutdown();
|
|
3514
3537
|
});
|
|
3515
3538
|
process.on("unhandledRejection", (reason) => {
|
|
3516
|
-
const
|
|
3517
|
-
process.stderr.write(`numux: unhandled rejection: ${
|
|
3539
|
+
const stack = reason instanceof Error ? reason.stack : String(reason);
|
|
3540
|
+
process.stderr.write(`numux: unhandled rejection: ${stack}
|
|
3518
3541
|
`);
|
|
3519
3542
|
this.shutdown();
|
|
3520
3543
|
});
|
|
@@ -3925,10 +3948,10 @@ function setupShutdownHandlers(app, logWriter) {
|
|
|
3925
3948
|
});
|
|
3926
3949
|
});
|
|
3927
3950
|
process.on("unhandledRejection", (reason) => {
|
|
3928
|
-
const
|
|
3929
|
-
log("Unhandled rejection:",
|
|
3951
|
+
const stack = reason instanceof Error ? reason.stack : String(reason);
|
|
3952
|
+
log("Unhandled rejection:", stack);
|
|
3930
3953
|
app.shutdown().finally(() => {
|
|
3931
|
-
process.stderr.write(`numux: unhandled rejection: ${
|
|
3954
|
+
process.stderr.write(`numux: unhandled rejection: ${stack}
|
|
3932
3955
|
`);
|
|
3933
3956
|
logWriter?.cleanup();
|
|
3934
3957
|
process.exit(1);
|
|
@@ -3985,6 +4008,28 @@ async function main() {
|
|
|
3985
4008
|
console.info(generateCompletions(parsed.completions));
|
|
3986
4009
|
process.exit(0);
|
|
3987
4010
|
}
|
|
4011
|
+
if (parsed.logs) {
|
|
4012
|
+
const logDir2 = parsed.logDir ?? await resolveLogDir(parsed.configPath);
|
|
4013
|
+
const latestDir = resolve8(logDir2, "latest");
|
|
4014
|
+
const target = existsSync6(latestDir) ? latestDir : logDir2;
|
|
4015
|
+
if (parsed.logsProcess) {
|
|
4016
|
+
const logFile2 = resolve8(target, `${parsed.logsProcess}.log`);
|
|
4017
|
+
if (!existsSync6(logFile2)) {
|
|
4018
|
+
const { readdirSync } = await import("fs");
|
|
4019
|
+
const files = readdirSync(target).filter((f) => f.endsWith(".log")).map((f) => f.replace(/\.log$/, ""));
|
|
4020
|
+
const available = files.length > 0 ? `Available: ${files.join(", ")}` : "No log files found";
|
|
4021
|
+
console.error(`No log file for "${parsed.logsProcess}". ${available}`);
|
|
4022
|
+
process.exit(1);
|
|
4023
|
+
}
|
|
4024
|
+
const child = Bun.spawn(["cat", logFile2], {
|
|
4025
|
+
stdout: "inherit",
|
|
4026
|
+
stderr: "inherit"
|
|
4027
|
+
});
|
|
4028
|
+
process.exit(await child.exited);
|
|
4029
|
+
}
|
|
4030
|
+
console.info(target);
|
|
4031
|
+
process.exit(0);
|
|
4032
|
+
}
|
|
3988
4033
|
if (parsed.validate) {
|
|
3989
4034
|
const raw = expandWorkspaces(expandScriptPatterns(await loadConfig(parsed.configPath)));
|
|
3990
4035
|
const warnings2 = [];
|
|
@@ -4161,6 +4206,15 @@ function printWarnings(warnings) {
|
|
|
4161
4206
|
console.warn(`Warning: process "${w.process}": ${w.message}`);
|
|
4162
4207
|
}
|
|
4163
4208
|
}
|
|
4209
|
+
async function resolveLogDir(configPath) {
|
|
4210
|
+
try {
|
|
4211
|
+
const raw = await loadConfig(configPath);
|
|
4212
|
+
if (typeof raw.logDir === "string" && raw.logDir.trim()) {
|
|
4213
|
+
return resolve8(raw.logDir.trim());
|
|
4214
|
+
}
|
|
4215
|
+
} catch {}
|
|
4216
|
+
return defaultLogDir(process.cwd());
|
|
4217
|
+
}
|
|
4164
4218
|
main().catch((err) => {
|
|
4165
4219
|
console.error(err instanceof Error ? err.message : err);
|
|
4166
4220
|
process.exit(1);
|
package/dist/types.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface NumuxProcessConfig<K extends string = string> {
|
|
|
43
43
|
* @default false
|
|
44
44
|
*/
|
|
45
45
|
interactive?: boolean;
|
|
46
|
+
/** Process is visible but not started automatically. Use Alt+S to start manually */
|
|
47
|
+
optional?: boolean;
|
|
46
48
|
/** `true` = detect ANSI red output, string = regex pattern */
|
|
47
49
|
errorMatcher?: boolean | string;
|
|
48
50
|
/**
|