diff --git a/README.md b/README.md index eb9ba02..6718a7d 100644 --- a/README.md +++ b/README.md @@ -220,8 +220,8 @@ export default defineConfig({ | `-e,` `--env-file` `` | Env file path, or "false" to disable env file loading | | `--config` `` | Config file path (default: auto-detect) | | `-p,` `--prefix` | Prefixed output mode (no TUI, for CI/scripts) | -| `--only` `` | Only run these processes (+ their dependencies) | -| `--exclude` `` | Exclude these processes | +| `-o,` `--only` `` | Only run these processes (+ their dependencies) | +| `-x,` `--exclude` `` | Exclude these processes | | `--kill-others` | Kill all processes when any exits (regardless of exit code) | | `--kill-others-on-fail` | Kill all processes when any exits with non-zero code | | `--max-restarts` `` | Max auto-restarts for crashed processes | diff --git a/src/cli-flags.ts b/src/cli-flags.ts index 8c06f0b..cba3d13 100644 --- a/src/cli-flags.ts +++ b/src/cli-flags.ts @@ -138,6 +138,7 @@ export const FLAGS: FlagDef[] = [ { type: 'value', long: '--only', + short: '-o', key: 'only', description: 'Only run these processes (+ their dependencies)', valueName: '', @@ -147,6 +148,7 @@ export const FLAGS: FlagDef[] = [ { type: 'value', long: '--exclude', + short: '-x', key: 'exclude', description: 'Exclude these processes', valueName: '', diff --git a/src/cli.test.ts b/src/cli.test.ts index 2ea1df3..35f33f6 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -175,12 +175,48 @@ describe('parseArgs', () => { expect(result.exclude).toEqual(['migrate']) }) + test('-o is short for --only', () => { + const result = parseArgs(argv('-o', 'api,web')) + expect(result.only).toEqual(['api', 'web']) + }) + + test('-x is short for --exclude', () => { + const result = parseArgs(argv('-x', 'migrate')) + expect(result.exclude).toEqual(['migrate']) + }) + + test('repeated --only merges values', () => { + const result = parseArgs(argv('--only', 'api', '--only', 'web')) + expect(result.only).toEqual(['api', 'web']) + }) + + test('repeated -o merges comma-separated values', () => { + const result = parseArgs(argv('-o', 'api,db', '-o', 'web')) + expect(result.only).toEqual(['api', 'db', 'web']) + }) + + test('repeated --exclude merges values', () => { + const result = parseArgs(argv('--exclude', 'db', '-x', 'migrate')) + expect(result.exclude).toEqual(['db', 'migrate']) + }) + + test('mixed short and long flags merge', () => { + const result = parseArgs(argv('-o', 'api', '--only', 'web')) + expect(result.only).toEqual(['api', 'web']) + }) + test('--only and --exclude can coexist', () => { const result = parseArgs(argv('--only', 'api,web,db', '--exclude', 'db')) expect(result.only).toEqual(['api', 'web', 'db']) expect(result.exclude).toEqual(['db']) }) + test('-o and -x can coexist', () => { + const result = parseArgs(argv('-o', 'api,web', '-x', 'web')) + expect(result.only).toEqual(['api', 'web']) + expect(result.exclude).toEqual(['web']) + }) + test('init sets init flag', () => { expect(parseArgs(argv('init')).init).toBe(true) }) diff --git a/src/cli.ts b/src/cli.ts index 3063ac4..7b2f496 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -93,7 +93,8 @@ export function parseArgs(argv: string[]): ParsedArgs { const value = flag.parse ? flag.parse(next, arg) : next const current = (result as any)[flag.key] if (Array.isArray(current)) { - current.push(value) + if (Array.isArray(value)) current.push(...value) + else current.push(value) } else { ;(result as any)[flag.key] = value } diff --git a/src/ui/prefix.test.ts b/src/ui/prefix.test.ts index fd679d9..5775965 100644 --- a/src/ui/prefix.test.ts +++ b/src/ui/prefix.test.ts @@ -292,6 +292,42 @@ describe('PrefixDisplay (integration)', () => { expect(exitCode).toBe(1) }, 10000) + test('-o filters to named process and its deps', async () => { + const config = writeConfig( + 'only-filter.json', + JSON.stringify({ + processes: { + db: { command: "echo 'db ready'" }, + api: { command: "echo 'api ready'", dependsOn: ['db'] }, + web: { command: "echo 'web ready'" } + } + }) + ) + const { stdout, exitCode } = await runPrefix(config, ['-o', 'api']) + expect(stdout).toContain('[api]') + expect(stdout).toContain('[db]') + expect(stdout).not.toContain('[web]') + expect(exitCode).toBe(0) + }, 10000) + + test('repeated -o merges filters', async () => { + const config = writeConfig( + 'only-repeated.json', + JSON.stringify({ + processes: { + a: { command: "echo 'a'" }, + b: { command: "echo 'b'" }, + c: { command: "echo 'c'" } + } + }) + ) + const { stdout, exitCode } = await runPrefix(config, ['-o', 'a', '-o', 'b']) + expect(stdout).toContain('[a]') + expect(stdout).toContain('[b]') + expect(stdout).not.toContain('[c]') + expect(exitCode).toBe(0) + }, 10000) + test('cursor-up sequences are stripped to preserve prefix', async () => { // Use bun -e to emit real escape bytes — avoids printf portability issues const config = writeConfig(