numux 1.4.0 → 1.5.1
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 -17
- package/dist/bin.js +2611 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +7 -0
- package/dist/types.d.ts +53 -0
- package/package.json +13 -9
- package/src/cli.ts +0 -217
- package/src/completions.ts +0 -121
- package/src/config/expand-scripts.ts +0 -96
- package/src/config/interpolate.ts +0 -50
- package/src/config/loader.ts +0 -76
- package/src/config/resolver.ts +0 -67
- package/src/config/validator.ts +0 -150
- package/src/config.ts +0 -8
- package/src/index.ts +0 -258
- package/src/process/manager.ts +0 -379
- package/src/process/ready.ts +0 -45
- package/src/process/runner.ts +0 -243
- package/src/types.ts +0 -57
- package/src/ui/app.ts +0 -454
- package/src/ui/pane.ts +0 -125
- package/src/ui/prefix.ts +0 -207
- package/src/ui/status-bar.ts +0 -58
- package/src/ui/tabs.ts +0 -246
- package/src/utils/color.ts +0 -93
- package/src/utils/env-file.ts +0 -58
- package/src/utils/log-writer.ts +0 -48
- package/src/utils/logger.ts +0 -32
- package/src/utils/shutdown.ts +0 -39
- package/src/utils/watcher.ts +0 -53
package/src/config/resolver.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { ResolvedNumuxConfig } from '../types'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Kahn's topological sort — groups processes into tiers.
|
|
5
|
-
* Tier 0: no deps, Tier 1: deps all in tier 0, etc.
|
|
6
|
-
* Throws if a cycle is detected.
|
|
7
|
-
*/
|
|
8
|
-
export function resolveDependencyTiers(config: ResolvedNumuxConfig): string[][] {
|
|
9
|
-
const names = Object.keys(config.processes)
|
|
10
|
-
const inDegree = new Map<string, number>()
|
|
11
|
-
const dependents = new Map<string, string[]>()
|
|
12
|
-
|
|
13
|
-
for (const name of names) {
|
|
14
|
-
inDegree.set(name, 0)
|
|
15
|
-
dependents.set(name, [])
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
for (const name of names) {
|
|
19
|
-
const deps = config.processes[name].dependsOn ?? []
|
|
20
|
-
inDegree.set(name, deps.length)
|
|
21
|
-
for (const dep of deps) {
|
|
22
|
-
dependents.get(dep)!.push(name)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const tiers: string[][] = []
|
|
27
|
-
const remaining = new Set(names)
|
|
28
|
-
|
|
29
|
-
while (remaining.size > 0) {
|
|
30
|
-
const tier = [...remaining].filter(n => inDegree.get(n) === 0)
|
|
31
|
-
|
|
32
|
-
if (tier.length === 0) {
|
|
33
|
-
const cycle = findCycle(remaining, config)
|
|
34
|
-
throw new Error(`Dependency cycle detected: ${cycle.join(' → ')} → ${cycle[0]}`)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
tiers.push(tier)
|
|
38
|
-
|
|
39
|
-
for (const name of tier) {
|
|
40
|
-
remaining.delete(name)
|
|
41
|
-
for (const dep of dependents.get(name)!) {
|
|
42
|
-
inDegree.set(dep, inDegree.get(dep)! - 1)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return tiers
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Trace from any node in `remaining` to find one cycle. */
|
|
51
|
-
function findCycle(remaining: Set<string>, config: ResolvedNumuxConfig): string[] {
|
|
52
|
-
const start = remaining.values().next().value!
|
|
53
|
-
const visited = new Set<string>()
|
|
54
|
-
const path: string[] = []
|
|
55
|
-
|
|
56
|
-
let current = start
|
|
57
|
-
while (!visited.has(current)) {
|
|
58
|
-
visited.add(current)
|
|
59
|
-
path.push(current)
|
|
60
|
-
const deps = (config.processes[current].dependsOn ?? []).filter(d => remaining.has(d))
|
|
61
|
-
current = deps[0]
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// `current` is where the cycle starts — trim the path to just the cycle
|
|
65
|
-
const cycleStart = path.indexOf(current)
|
|
66
|
-
return path.slice(cycleStart)
|
|
67
|
-
}
|
package/src/config/validator.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import type { NumuxProcessConfig, ResolvedNumuxConfig } from '../types'
|
|
2
|
-
import { HEX_COLOR_RE } from '../utils/color'
|
|
3
|
-
|
|
4
|
-
export type ValidationWarning = { process: string; message: string }
|
|
5
|
-
|
|
6
|
-
export function validateConfig(raw: unknown, warnings?: ValidationWarning[]): ResolvedNumuxConfig {
|
|
7
|
-
if (!raw || typeof raw !== 'object') {
|
|
8
|
-
throw new Error('Config must be an object')
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const config = raw as Record<string, unknown>
|
|
12
|
-
if (!config.processes || typeof config.processes !== 'object') {
|
|
13
|
-
throw new Error('Config must have a "processes" object')
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const processes = config.processes as Record<string, unknown>
|
|
17
|
-
const names = Object.keys(processes)
|
|
18
|
-
|
|
19
|
-
if (names.length === 0) {
|
|
20
|
-
throw new Error('Config must define at least one process')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Extract global options
|
|
24
|
-
const globalCwd = typeof config.cwd === 'string' ? config.cwd : undefined
|
|
25
|
-
const globalEnvFile = validateStringOrStringArray(config.envFile)
|
|
26
|
-
let globalEnv: Record<string, string> | undefined
|
|
27
|
-
if (config.env && typeof config.env === 'object') {
|
|
28
|
-
for (const [k, v] of Object.entries(config.env as Record<string, unknown>)) {
|
|
29
|
-
if (typeof v !== 'string') {
|
|
30
|
-
throw new Error(`env.${k} must be a string, got ${typeof v}`)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
globalEnv = config.env as Record<string, string>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const validated: Record<string, NumuxProcessConfig> = {}
|
|
37
|
-
|
|
38
|
-
for (const name of names) {
|
|
39
|
-
let proc = processes[name]
|
|
40
|
-
|
|
41
|
-
// String shorthand: "command" → { command: "command" }
|
|
42
|
-
if (typeof proc === 'string') {
|
|
43
|
-
proc = { command: proc }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!proc || typeof proc !== 'object') {
|
|
47
|
-
throw new Error(`Process "${name}" must be an object or a command string`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const p = proc as Record<string, unknown>
|
|
51
|
-
|
|
52
|
-
if (typeof p.command !== 'string' || !p.command.trim()) {
|
|
53
|
-
throw new Error(`Process "${name}" must have a non-empty "command" string`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Validate dependsOn references
|
|
57
|
-
if (p.dependsOn !== undefined) {
|
|
58
|
-
if (!Array.isArray(p.dependsOn)) {
|
|
59
|
-
throw new Error(`Process "${name}".dependsOn must be an array`)
|
|
60
|
-
}
|
|
61
|
-
for (const dep of p.dependsOn) {
|
|
62
|
-
if (typeof dep !== 'string') {
|
|
63
|
-
throw new Error(`Process "${name}".dependsOn entries must be strings`)
|
|
64
|
-
}
|
|
65
|
-
if (!names.includes(dep)) {
|
|
66
|
-
throw new Error(`Process "${name}" depends on unknown process "${dep}"`)
|
|
67
|
-
}
|
|
68
|
-
if (dep === name) {
|
|
69
|
-
throw new Error(`Process "${name}" cannot depend on itself`)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Validate color hex format
|
|
75
|
-
if (typeof p.color === 'string') {
|
|
76
|
-
if (!HEX_COLOR_RE.test(p.color)) {
|
|
77
|
-
throw new Error(`Process "${name}".color must be a valid hex color (e.g. "#ff8800"), got "${p.color}"`)
|
|
78
|
-
}
|
|
79
|
-
} else if (Array.isArray(p.color)) {
|
|
80
|
-
for (const c of p.color) {
|
|
81
|
-
if (typeof c !== 'string' || !HEX_COLOR_RE.test(c)) {
|
|
82
|
-
throw new Error(
|
|
83
|
-
`Process "${name}".color entries must be valid hex colors (e.g. "#ff8800"), got "${c}"`
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const persistent = typeof p.persistent === 'boolean' ? p.persistent : true
|
|
90
|
-
const readyPattern = typeof p.readyPattern === 'string' ? p.readyPattern : undefined
|
|
91
|
-
|
|
92
|
-
// Warn when readyPattern is set on non-persistent processes (it's ignored at runtime)
|
|
93
|
-
if (readyPattern && !persistent) {
|
|
94
|
-
warnings?.push({
|
|
95
|
-
process: name,
|
|
96
|
-
message: 'readyPattern is ignored on non-persistent processes (readiness is determined by exit code)'
|
|
97
|
-
})
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Validate env values are strings
|
|
101
|
-
if (p.env && typeof p.env === 'object') {
|
|
102
|
-
for (const [k, v] of Object.entries(p.env as Record<string, unknown>)) {
|
|
103
|
-
if (typeof v !== 'string') {
|
|
104
|
-
throw new Error(`Process "${name}".env.${k} must be a string, got ${typeof v}`)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const processCwd = typeof p.cwd === 'string' ? p.cwd : undefined
|
|
110
|
-
const processEnv = p.env && typeof p.env === 'object' ? (p.env as Record<string, string>) : undefined
|
|
111
|
-
const processEnvFile = validateEnvFile(p.envFile)
|
|
112
|
-
|
|
113
|
-
validated[name] = {
|
|
114
|
-
command: p.command,
|
|
115
|
-
cwd: processCwd ?? globalCwd,
|
|
116
|
-
env: globalEnv || processEnv ? { ...globalEnv, ...processEnv } : undefined,
|
|
117
|
-
envFile: processEnvFile ?? globalEnvFile,
|
|
118
|
-
dependsOn: Array.isArray(p.dependsOn) ? (p.dependsOn as string[]) : undefined,
|
|
119
|
-
readyPattern,
|
|
120
|
-
persistent,
|
|
121
|
-
maxRestarts: typeof p.maxRestarts === 'number' && p.maxRestarts >= 0 ? p.maxRestarts : undefined,
|
|
122
|
-
readyTimeout: typeof p.readyTimeout === 'number' && p.readyTimeout > 0 ? p.readyTimeout : undefined,
|
|
123
|
-
delay: typeof p.delay === 'number' && p.delay > 0 ? p.delay : undefined,
|
|
124
|
-
condition: typeof p.condition === 'string' && p.condition.trim() ? p.condition.trim() : undefined,
|
|
125
|
-
stopSignal: validateStopSignal(p.stopSignal),
|
|
126
|
-
color: typeof p.color === 'string' ? p.color : Array.isArray(p.color) ? (p.color as string[]) : undefined,
|
|
127
|
-
watch: validateStringOrStringArray(p.watch),
|
|
128
|
-
interactive: typeof p.interactive === 'boolean' ? p.interactive : false
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return { processes: validated }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function validateStringOrStringArray(value: unknown): string | string[] | undefined {
|
|
136
|
-
if (typeof value === 'string') return value
|
|
137
|
-
if (Array.isArray(value) && value.every(v => typeof v === 'string')) return value as string[]
|
|
138
|
-
return undefined
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const validateEnvFile = validateStringOrStringArray
|
|
142
|
-
|
|
143
|
-
const VALID_STOP_SIGNALS = new Set(['SIGTERM', 'SIGINT', 'SIGHUP'])
|
|
144
|
-
|
|
145
|
-
function validateStopSignal(value: unknown): 'SIGTERM' | 'SIGINT' | 'SIGHUP' | undefined {
|
|
146
|
-
if (typeof value === 'string' && VALID_STOP_SIGNALS.has(value)) {
|
|
147
|
-
return value as 'SIGTERM' | 'SIGINT' | 'SIGHUP'
|
|
148
|
-
}
|
|
149
|
-
return undefined
|
|
150
|
-
}
|
package/src/config.ts
DELETED
package/src/index.ts
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { existsSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import { resolve } from 'node:path'
|
|
4
|
-
import { buildConfigFromArgs, filterConfig, parseArgs } from './cli'
|
|
5
|
-
import { generateCompletions } from './completions'
|
|
6
|
-
import { expandScriptPatterns } from './config/expand-scripts'
|
|
7
|
-
import { loadConfig } from './config/loader'
|
|
8
|
-
import { resolveDependencyTiers } from './config/resolver'
|
|
9
|
-
import { type ValidationWarning, validateConfig } from './config/validator'
|
|
10
|
-
import { ProcessManager } from './process/manager'
|
|
11
|
-
import type { NumuxProcessConfig, ResolvedNumuxConfig } from './types'
|
|
12
|
-
import { App } from './ui/app'
|
|
13
|
-
import { PrefixDisplay } from './ui/prefix'
|
|
14
|
-
import { loadEnvFiles } from './utils/env-file'
|
|
15
|
-
import { LogWriter } from './utils/log-writer'
|
|
16
|
-
import { enableDebugLog } from './utils/logger'
|
|
17
|
-
import { setupShutdownHandlers } from './utils/shutdown'
|
|
18
|
-
|
|
19
|
-
const HELP = `numux — terminal multiplexer with dependency orchestration
|
|
20
|
-
|
|
21
|
-
Usage:
|
|
22
|
-
numux Run processes from config file
|
|
23
|
-
numux <cmd1> <cmd2> ... Run ad-hoc commands in parallel
|
|
24
|
-
numux -n name1=cmd1 -n name2=cmd2 Named ad-hoc commands
|
|
25
|
-
numux init Create a starter config file
|
|
26
|
-
numux validate Validate config and show process graph
|
|
27
|
-
numux exec <name> [--] <cmd> Run a command in a process's environment
|
|
28
|
-
numux completions <shell> Generate shell completions (bash, zsh, fish)
|
|
29
|
-
|
|
30
|
-
Options:
|
|
31
|
-
-n, --name <name=command> Add a named process
|
|
32
|
-
-c, --color <colors> Comma-separated colors for processes (hex, e.g. #ff0,#0f0)
|
|
33
|
-
--config <path> Config file path (default: auto-detect)
|
|
34
|
-
-p, --prefix Prefixed output mode (no TUI, for CI/scripts)
|
|
35
|
-
--only <a,b,...> Only run these processes (+ their dependencies)
|
|
36
|
-
--exclude <a,b,...> Exclude these processes
|
|
37
|
-
--kill-others Kill all processes when any exits
|
|
38
|
-
--no-restart Disable auto-restart for crashed processes
|
|
39
|
-
--no-watch Disable file watching even if config has watch patterns
|
|
40
|
-
-t, --timestamps Add timestamps to prefixed output lines
|
|
41
|
-
--log-dir <path> Write per-process logs to directory
|
|
42
|
-
--debug Enable debug logging to .numux/debug.log
|
|
43
|
-
-h, --help Show this help
|
|
44
|
-
-v, --version Show version
|
|
45
|
-
|
|
46
|
-
Config files (auto-detected):
|
|
47
|
-
numux.config.ts, numux.config.js, numux.config.yaml,
|
|
48
|
-
numux.config.yml, numux.config.json,
|
|
49
|
-
or "numux" key in package.json`
|
|
50
|
-
|
|
51
|
-
const INIT_TEMPLATE = `import { defineConfig } from 'numux'
|
|
52
|
-
|
|
53
|
-
export default defineConfig({
|
|
54
|
-
// Global options (inherited by all processes):
|
|
55
|
-
// cwd: './packages/backend',
|
|
56
|
-
// env: { NODE_ENV: 'development' },
|
|
57
|
-
// envFile: '.env',
|
|
58
|
-
|
|
59
|
-
processes: {
|
|
60
|
-
// dev: 'npm run dev',
|
|
61
|
-
// api: {
|
|
62
|
-
// command: 'npm run dev:api',
|
|
63
|
-
// readyPattern: 'listening on port',
|
|
64
|
-
// watch: 'src/**/*.ts',
|
|
65
|
-
// },
|
|
66
|
-
// web: {
|
|
67
|
-
// command: 'npm run dev:web',
|
|
68
|
-
// dependsOn: ['api'],
|
|
69
|
-
// },
|
|
70
|
-
},
|
|
71
|
-
})
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
async function main() {
|
|
75
|
-
const parsed = parseArgs(process.argv)
|
|
76
|
-
|
|
77
|
-
if (parsed.help) {
|
|
78
|
-
console.info(HELP)
|
|
79
|
-
process.exit(0)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (parsed.version) {
|
|
83
|
-
const pkg = await import('../package.json')
|
|
84
|
-
console.info(`numux v${(pkg.default ?? pkg).version}`)
|
|
85
|
-
process.exit(0)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (parsed.init) {
|
|
89
|
-
const target = resolve('numux.config.ts')
|
|
90
|
-
if (existsSync(target)) {
|
|
91
|
-
console.error(`Already exists: ${target}`)
|
|
92
|
-
process.exit(1)
|
|
93
|
-
}
|
|
94
|
-
writeFileSync(target, INIT_TEMPLATE)
|
|
95
|
-
console.info(`Created ${target}`)
|
|
96
|
-
process.exit(0)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (parsed.completions) {
|
|
100
|
-
console.info(generateCompletions(parsed.completions))
|
|
101
|
-
process.exit(0)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (parsed.validate) {
|
|
105
|
-
const raw = expandScriptPatterns(await loadConfig(parsed.configPath))
|
|
106
|
-
const warnings: ValidationWarning[] = []
|
|
107
|
-
let config = validateConfig(raw, warnings)
|
|
108
|
-
|
|
109
|
-
if (parsed.only || parsed.exclude) {
|
|
110
|
-
config = filterConfig(config, parsed.only, parsed.exclude)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const tiers = resolveDependencyTiers(config)
|
|
114
|
-
const names = Object.keys(config.processes)
|
|
115
|
-
const filterNote = parsed.only || parsed.exclude ? ' (filtered)' : ''
|
|
116
|
-
console.info(`Config valid: ${names.length} process${names.length === 1 ? '' : 'es'}${filterNote}\n`)
|
|
117
|
-
for (let i = 0; i < tiers.length; i++) {
|
|
118
|
-
console.info(`Tier ${i}:`)
|
|
119
|
-
for (const name of tiers[i]) {
|
|
120
|
-
const proc = config.processes[name]
|
|
121
|
-
const flags: string[] = []
|
|
122
|
-
if (proc.dependsOn?.length) flags.push(`depends on: ${proc.dependsOn.join(', ')}`)
|
|
123
|
-
if (proc.readyPattern) flags.push(`ready: /${proc.readyPattern}/`)
|
|
124
|
-
if (proc.persistent === false) flags.push('one-shot')
|
|
125
|
-
if (proc.delay) flags.push(`delay: ${proc.delay}ms`)
|
|
126
|
-
if (proc.condition) flags.push(`if: ${proc.condition}`)
|
|
127
|
-
if (proc.watch) {
|
|
128
|
-
const patterns = Array.isArray(proc.watch) ? proc.watch : [proc.watch]
|
|
129
|
-
flags.push(`watch: ${patterns.join(', ')}`)
|
|
130
|
-
}
|
|
131
|
-
const suffix = flags.length > 0 ? ` (${flags.join(', ')})` : ''
|
|
132
|
-
console.info(` ${name}: ${proc.command}${suffix}`)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
printWarnings(warnings)
|
|
136
|
-
process.exit(0)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (parsed.exec) {
|
|
140
|
-
const raw = expandScriptPatterns(await loadConfig(parsed.configPath))
|
|
141
|
-
const config = validateConfig(raw)
|
|
142
|
-
const proc = config.processes[parsed.execName!]
|
|
143
|
-
if (!proc) {
|
|
144
|
-
const names = Object.keys(config.processes)
|
|
145
|
-
throw new Error(`Unknown process "${parsed.execName}". Available: ${names.join(', ')}`)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const cwd = proc.cwd ? resolve(proc.cwd) : process.cwd()
|
|
149
|
-
const envFromFile = proc.envFile ? loadEnvFiles(proc.envFile, cwd) : {}
|
|
150
|
-
const env: Record<string, string> = {
|
|
151
|
-
...(process.env as Record<string, string>),
|
|
152
|
-
...envFromFile,
|
|
153
|
-
...proc.env
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const child = Bun.spawn(['sh', '-c', parsed.execCommand!], {
|
|
157
|
-
cwd,
|
|
158
|
-
env,
|
|
159
|
-
stdout: 'inherit',
|
|
160
|
-
stdin: 'inherit',
|
|
161
|
-
stderr: 'inherit'
|
|
162
|
-
})
|
|
163
|
-
process.exit(await child.exited)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (parsed.debug) {
|
|
167
|
-
enableDebugLog()
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
let config: ResolvedNumuxConfig
|
|
171
|
-
const warnings: ValidationWarning[] = []
|
|
172
|
-
|
|
173
|
-
if (parsed.commands.length > 0 || parsed.named.length > 0) {
|
|
174
|
-
const hasNpmPatterns = parsed.commands.some(c => c.startsWith('npm:'))
|
|
175
|
-
if (hasNpmPatterns) {
|
|
176
|
-
// Expand npm: patterns into named processes, pass remaining commands as-is
|
|
177
|
-
const npmPatterns = parsed.commands.filter(c => c.startsWith('npm:'))
|
|
178
|
-
const otherCommands = parsed.commands.filter(c => !c.startsWith('npm:'))
|
|
179
|
-
const processes: Record<string, NumuxProcessConfig | string> = {}
|
|
180
|
-
for (const pattern of npmPatterns) {
|
|
181
|
-
const entry: Partial<NumuxProcessConfig> = {}
|
|
182
|
-
if (parsed.colors?.length) entry.color = parsed.colors
|
|
183
|
-
processes[pattern] = entry as NumuxProcessConfig
|
|
184
|
-
}
|
|
185
|
-
for (let i = 0; i < otherCommands.length; i++) {
|
|
186
|
-
const cmd = otherCommands[i]
|
|
187
|
-
let name = cmd.split(/\s+/)[0].split('/').pop()!
|
|
188
|
-
if (processes[name]) name = `${name}-${i}`
|
|
189
|
-
processes[name] = cmd
|
|
190
|
-
}
|
|
191
|
-
for (const { name, command } of parsed.named) {
|
|
192
|
-
processes[name] = command
|
|
193
|
-
}
|
|
194
|
-
const expanded = expandScriptPatterns({ processes })
|
|
195
|
-
config = validateConfig(expanded, warnings)
|
|
196
|
-
} else {
|
|
197
|
-
config = buildConfigFromArgs(parsed.commands, parsed.named, {
|
|
198
|
-
noRestart: parsed.noRestart,
|
|
199
|
-
colors: parsed.colors
|
|
200
|
-
})
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
const raw = expandScriptPatterns(await loadConfig(parsed.configPath))
|
|
204
|
-
config = validateConfig(raw, warnings)
|
|
205
|
-
|
|
206
|
-
if (parsed.noRestart) {
|
|
207
|
-
for (const proc of Object.values(config.processes)) {
|
|
208
|
-
proc.maxRestarts = 0
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (parsed.noWatch) {
|
|
214
|
-
for (const proc of Object.values(config.processes)) {
|
|
215
|
-
delete proc.watch
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (parsed.only || parsed.exclude) {
|
|
220
|
-
config = filterConfig(config, parsed.only, parsed.exclude)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const manager = new ProcessManager(config)
|
|
224
|
-
|
|
225
|
-
let logWriter: LogWriter | undefined
|
|
226
|
-
if (parsed.logDir) {
|
|
227
|
-
logWriter = new LogWriter(parsed.logDir)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
printWarnings(warnings)
|
|
231
|
-
|
|
232
|
-
if (parsed.prefix) {
|
|
233
|
-
const display = new PrefixDisplay(manager, config, {
|
|
234
|
-
logWriter,
|
|
235
|
-
killOthers: parsed.killOthers,
|
|
236
|
-
timestamps: parsed.timestamps
|
|
237
|
-
})
|
|
238
|
-
await display.start()
|
|
239
|
-
} else {
|
|
240
|
-
if (logWriter) {
|
|
241
|
-
manager.on(logWriter.handleEvent)
|
|
242
|
-
}
|
|
243
|
-
const app = new App(manager, config)
|
|
244
|
-
setupShutdownHandlers(app, logWriter)
|
|
245
|
-
await app.start()
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function printWarnings(warnings: ValidationWarning[]): void {
|
|
250
|
-
for (const w of warnings) {
|
|
251
|
-
console.warn(`Warning: process "${w.process}": ${w.message}`)
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
main().catch(err => {
|
|
256
|
-
console.error(err instanceof Error ? err.message : err)
|
|
257
|
-
process.exit(1)
|
|
258
|
-
})
|