numux 2.5.1 → 2.6.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 +26 -0
- package/dist/numux.js +102 -21
- package/dist/types.d.ts +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -244,6 +244,32 @@ Each process accepts:
|
|
|
244
244
|
| `interactive` | `boolean` | `false` | When `true`, keyboard input is forwarded to the process |
|
|
245
245
|
| `errorMatcher` | `boolean \| string` | — | `true` detects ANSI red output, string = regex pattern — shows error indicator on tab |
|
|
246
246
|
| `showCommand` | `boolean` | `true` | Print the command being run as the first line of output |
|
|
247
|
+
| `workspaces` | `boolean \| string \| string[]` | — | Run command in monorepo workspaces (see below) |
|
|
248
|
+
|
|
249
|
+
### Workspace expansion
|
|
250
|
+
|
|
251
|
+
Use `workspaces` on a process to expand it into per-workspace processes. Reads the `workspaces` field from your root `package.json`.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
export default defineConfig({
|
|
255
|
+
processes: {
|
|
256
|
+
// All workspaces — filters by script availability for PM run commands
|
|
257
|
+
lint: { command: 'npm run lint', workspaces: true },
|
|
258
|
+
|
|
259
|
+
// Specific workspace by package name
|
|
260
|
+
validate: { command: 'npm run validate', workspaces: '@repo/image-worker' },
|
|
261
|
+
|
|
262
|
+
// Multiple specific workspaces
|
|
263
|
+
dev: { command: 'npm run dev', workspaces: ['@repo/api', '@repo/web'] },
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Each entry expands into `{name}:{wsName}` processes (e.g. `lint:api`, `lint:web`) with `cwd` set to the workspace directory. All other config (env, dependsOn, color, etc.) is inherited from the template.
|
|
269
|
+
|
|
270
|
+
When `workspaces: true` is used with a PM run command (`npm run lint`), only workspaces that have the matching script are included. Raw commands (`eslint .`) run in all workspaces.
|
|
271
|
+
|
|
272
|
+
String values resolve by package name first (with or without scope), then fall back to relative path. Cannot be combined with `cwd`.
|
|
247
273
|
|
|
248
274
|
### File watching
|
|
249
275
|
|
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: "2.
|
|
39
|
+
version: "2.6.0",
|
|
40
40
|
description: "Terminal multiplexer with dependency orchestration",
|
|
41
41
|
type: "module",
|
|
42
42
|
license: "MIT",
|
|
@@ -1278,13 +1278,12 @@ function validateStopSignal(value) {
|
|
|
1278
1278
|
// src/config/workspaces.ts
|
|
1279
1279
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1280
1280
|
import { basename, resolve as resolve4 } from "path";
|
|
1281
|
-
function
|
|
1281
|
+
function discoverWorkspaces(cwd) {
|
|
1282
1282
|
const pkgPath = resolve4(cwd, "package.json");
|
|
1283
1283
|
if (!existsSync4(pkgPath)) {
|
|
1284
1284
|
throw new Error(`No package.json found in ${cwd}`);
|
|
1285
1285
|
}
|
|
1286
1286
|
const pkgJson = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1287
|
-
const pm = detectPackageManager(pkgJson, cwd);
|
|
1288
1287
|
const raw = pkgJson.workspaces;
|
|
1289
1288
|
let patterns;
|
|
1290
1289
|
if (Array.isArray(raw)) {
|
|
@@ -1294,31 +1293,113 @@ function resolveWorkspaceProcesses(script, cwd) {
|
|
|
1294
1293
|
} else {
|
|
1295
1294
|
throw new Error('No "workspaces" field found in package.json');
|
|
1296
1295
|
}
|
|
1297
|
-
const
|
|
1296
|
+
const workspaces = [];
|
|
1298
1297
|
for (const pattern of patterns) {
|
|
1299
1298
|
const glob = new Bun.Glob(pattern);
|
|
1300
1299
|
for (const match of glob.scanSync({ cwd, onlyFiles: false })) {
|
|
1301
1300
|
const abs = resolve4(cwd, match);
|
|
1302
1301
|
const wsPkgPath = resolve4(abs, "package.json");
|
|
1303
|
-
if (existsSync4(wsPkgPath))
|
|
1304
|
-
|
|
1302
|
+
if (!existsSync4(wsPkgPath))
|
|
1303
|
+
continue;
|
|
1304
|
+
const wsPkg = JSON.parse(readFileSync3(wsPkgPath, "utf-8"));
|
|
1305
|
+
const pkgName = typeof wsPkg.name === "string" && wsPkg.name ? wsPkg.name : undefined;
|
|
1306
|
+
const name = pkgName ? pkgName.replace(/^@[^/]+\//, "") : basename(abs);
|
|
1307
|
+
const scripts = wsPkg.scripts ?? {};
|
|
1308
|
+
workspaces.push({ dir: abs, name, pkgName, scripts });
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return workspaces.sort((a, b) => a.name.localeCompare(b.name));
|
|
1312
|
+
}
|
|
1313
|
+
function findWorkspace(nameOrPath, workspaces, cwd) {
|
|
1314
|
+
const byPkgName = workspaces.find((ws) => ws.pkgName === nameOrPath);
|
|
1315
|
+
if (byPkgName)
|
|
1316
|
+
return byPkgName;
|
|
1317
|
+
const byName = workspaces.find((ws) => ws.name === nameOrPath);
|
|
1318
|
+
if (byName)
|
|
1319
|
+
return byName;
|
|
1320
|
+
const absPath = resolve4(cwd, nameOrPath);
|
|
1321
|
+
return workspaces.find((ws) => ws.dir === absPath);
|
|
1322
|
+
}
|
|
1323
|
+
function extractScriptFromCommand(command) {
|
|
1324
|
+
const match = command.match(/^(?:npm|yarn|pnpm|bun)\s+run\s+(\S+)/);
|
|
1325
|
+
return match ? match[1] : null;
|
|
1326
|
+
}
|
|
1327
|
+
function expandWorkspaces(config) {
|
|
1328
|
+
const cwd = config.cwd ? resolve4(config.cwd) : process.cwd();
|
|
1329
|
+
const newProcesses = {};
|
|
1330
|
+
let discoveredWorkspaces = null;
|
|
1331
|
+
for (const [name, entry] of Object.entries(config.processes)) {
|
|
1332
|
+
if (typeof entry === "string" || !entry.workspaces) {
|
|
1333
|
+
newProcesses[name] = entry;
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
const proc = entry;
|
|
1337
|
+
const wsField = proc.workspaces;
|
|
1338
|
+
if (!proc.command) {
|
|
1339
|
+
throw new Error(`Process "${name}": workspaces requires a "command"`);
|
|
1340
|
+
}
|
|
1341
|
+
if (proc.cwd) {
|
|
1342
|
+
throw new Error(`Process "${name}": cannot set both "workspaces" and "cwd"`);
|
|
1343
|
+
}
|
|
1344
|
+
if (!discoveredWorkspaces) {
|
|
1345
|
+
discoveredWorkspaces = discoverWorkspaces(cwd);
|
|
1346
|
+
}
|
|
1347
|
+
if (discoveredWorkspaces.length === 0) {
|
|
1348
|
+
throw new Error(`Process "${name}": no workspaces found`);
|
|
1349
|
+
}
|
|
1350
|
+
const { workspaces: _, ...template } = proc;
|
|
1351
|
+
let targets;
|
|
1352
|
+
if (wsField === true) {
|
|
1353
|
+
const script = extractScriptFromCommand(proc.command);
|
|
1354
|
+
if (script) {
|
|
1355
|
+
targets = discoveredWorkspaces.filter((ws) => ws.scripts[script]);
|
|
1356
|
+
if (targets.length === 0) {
|
|
1357
|
+
throw new Error(`Process "${name}": no workspaces have a "${script}" script`);
|
|
1358
|
+
}
|
|
1359
|
+
} else {
|
|
1360
|
+
targets = discoveredWorkspaces;
|
|
1305
1361
|
}
|
|
1362
|
+
} else {
|
|
1363
|
+
const names = Array.isArray(wsField) ? wsField : [wsField];
|
|
1364
|
+
targets = [];
|
|
1365
|
+
for (const wsName of names) {
|
|
1366
|
+
const ws = findWorkspace(wsName, discoveredWorkspaces, cwd);
|
|
1367
|
+
if (!ws) {
|
|
1368
|
+
const available = discoveredWorkspaces.map((w) => w.name).join(", ");
|
|
1369
|
+
throw new Error(`Process "${name}": workspace "${wsName}" not found. Available: ${available}`);
|
|
1370
|
+
}
|
|
1371
|
+
targets.push(ws);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
const usedNames = new Set(Object.keys(newProcesses));
|
|
1375
|
+
for (const ws of targets) {
|
|
1376
|
+
let wsKey = `${name}:${ws.name}`;
|
|
1377
|
+
if (usedNames.has(wsKey)) {
|
|
1378
|
+
let suffix = 1;
|
|
1379
|
+
while (usedNames.has(`${wsKey}-${suffix}`))
|
|
1380
|
+
suffix++;
|
|
1381
|
+
wsKey = `${wsKey}-${suffix}`;
|
|
1382
|
+
}
|
|
1383
|
+
usedNames.add(wsKey);
|
|
1384
|
+
newProcesses[wsKey] = { ...template, cwd: ws.dir };
|
|
1306
1385
|
}
|
|
1307
1386
|
}
|
|
1387
|
+
return { ...config, processes: newProcesses };
|
|
1388
|
+
}
|
|
1389
|
+
function resolveWorkspaceProcesses(script, cwd) {
|
|
1390
|
+
const pkgPath = resolve4(cwd, "package.json");
|
|
1391
|
+
if (!existsSync4(pkgPath)) {
|
|
1392
|
+
throw new Error(`No package.json found in ${cwd}`);
|
|
1393
|
+
}
|
|
1394
|
+
const pkgJson = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1395
|
+
const pm = detectPackageManager(pkgJson, cwd);
|
|
1396
|
+
const workspaces = discoverWorkspaces(cwd);
|
|
1308
1397
|
const processes = {};
|
|
1309
1398
|
const usedNames = new Set;
|
|
1310
|
-
for (const
|
|
1311
|
-
|
|
1312
|
-
const wsPkg = JSON.parse(readFileSync3(wsPkgPath, "utf-8"));
|
|
1313
|
-
const scripts = wsPkg.scripts;
|
|
1314
|
-
if (!scripts?.[script])
|
|
1399
|
+
for (const ws of workspaces) {
|
|
1400
|
+
if (!ws.scripts[script])
|
|
1315
1401
|
continue;
|
|
1316
|
-
let name;
|
|
1317
|
-
if (typeof wsPkg.name === "string" && wsPkg.name) {
|
|
1318
|
-
name = wsPkg.name.replace(/^@[^/]+\//, "");
|
|
1319
|
-
} else {
|
|
1320
|
-
name = basename(dir);
|
|
1321
|
-
}
|
|
1402
|
+
let name = ws.name;
|
|
1322
1403
|
if (usedNames.has(name)) {
|
|
1323
1404
|
let suffix = 1;
|
|
1324
1405
|
while (usedNames.has(`${name}-${suffix}`))
|
|
@@ -1328,7 +1409,7 @@ function resolveWorkspaceProcesses(script, cwd) {
|
|
|
1328
1409
|
usedNames.add(name);
|
|
1329
1410
|
processes[name] = {
|
|
1330
1411
|
command: `${pm} run ${script}`,
|
|
1331
|
-
cwd: dir
|
|
1412
|
+
cwd: ws.dir
|
|
1332
1413
|
};
|
|
1333
1414
|
}
|
|
1334
1415
|
if (Object.keys(processes).length === 0) {
|
|
@@ -3851,7 +3932,7 @@ async function main() {
|
|
|
3851
3932
|
process.exit(0);
|
|
3852
3933
|
}
|
|
3853
3934
|
if (parsed.validate) {
|
|
3854
|
-
const raw = expandScriptPatterns(await loadConfig(parsed.configPath));
|
|
3935
|
+
const raw = expandWorkspaces(expandScriptPatterns(await loadConfig(parsed.configPath)));
|
|
3855
3936
|
const warnings2 = [];
|
|
3856
3937
|
let config2 = validateConfig(raw, warnings2);
|
|
3857
3938
|
config2 = filterByPlatform(config2);
|
|
@@ -3894,7 +3975,7 @@ async function main() {
|
|
|
3894
3975
|
process.exit(0);
|
|
3895
3976
|
}
|
|
3896
3977
|
if (parsed.exec) {
|
|
3897
|
-
const raw = expandScriptPatterns(await loadConfig(parsed.configPath));
|
|
3978
|
+
const raw = expandWorkspaces(expandScriptPatterns(await loadConfig(parsed.configPath)));
|
|
3898
3979
|
const config2 = validateConfig(raw);
|
|
3899
3980
|
const proc = config2.processes[parsed.execName];
|
|
3900
3981
|
if (!proc) {
|
|
@@ -3966,7 +4047,7 @@ async function main() {
|
|
|
3966
4047
|
}
|
|
3967
4048
|
}
|
|
3968
4049
|
} else {
|
|
3969
|
-
const raw = expandScriptPatterns(await loadConfig(parsed.configPath));
|
|
4050
|
+
const raw = expandWorkspaces(expandScriptPatterns(await loadConfig(parsed.configPath)));
|
|
3970
4051
|
config = validateConfig(raw, warnings);
|
|
3971
4052
|
config = filterByPlatform(config);
|
|
3972
4053
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -45,6 +45,11 @@ export interface NumuxProcessConfig<K extends string = string> {
|
|
|
45
45
|
interactive?: boolean;
|
|
46
46
|
/** `true` = detect ANSI red output, string = regex pattern */
|
|
47
47
|
errorMatcher?: boolean | string;
|
|
48
|
+
/**
|
|
49
|
+
* Run command in monorepo workspaces.
|
|
50
|
+
* `true` = all workspaces, string = specific workspace by name/path, string[] = multiple workspaces
|
|
51
|
+
*/
|
|
52
|
+
workspaces?: boolean | string | string[];
|
|
48
53
|
/**
|
|
49
54
|
* Print the command being run as the first line of output
|
|
50
55
|
* @default true
|
|
@@ -118,7 +123,7 @@ export interface NumuxConfig<K extends string = string> {
|
|
|
118
123
|
}
|
|
119
124
|
export type SortOrder = 'config' | 'alphabetical' | 'topological';
|
|
120
125
|
/** Process config after validation — dependsOn is always normalized to an array */
|
|
121
|
-
export interface ResolvedProcessConfig extends Omit<NumuxProcessConfig, 'dependsOn'> {
|
|
126
|
+
export interface ResolvedProcessConfig extends Omit<NumuxProcessConfig, 'dependsOn' | 'workspaces'> {
|
|
122
127
|
dependsOn?: string[];
|
|
123
128
|
}
|
|
124
129
|
/** Validated config with all shorthand expanded to full objects */
|