@10stars/config 15.1.0 → 15.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@10stars/config",
3
- "version": "15.1.0",
3
+ "version": "15.1.2",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "10config": "./src/index.ts"
package/src/index.ts CHANGED
@@ -145,7 +145,7 @@ Examples:
145
145
  commands.push({ label, command })
146
146
  i += 3
147
147
  } else {
148
- commands.push({ label: `cmd${commands.length + 1}`, command: arg! })
148
+ commands.push({ command: arg! })
149
149
  i++
150
150
  }
151
151
  }
package/src/runner.ts CHANGED
@@ -2,7 +2,7 @@ import { colors, getNextColor, type ColorName } from "./colors"
2
2
 
3
3
  export interface CommandConfig {
4
4
  /** Label shown as prefix in output */
5
- label: string
5
+ label?: string
6
6
  /** Command to run (string will be split by spaces, array used as-is) */
7
7
  command: string | string[]
8
8
  /** Color for the label (auto-assigned if not provided) */
@@ -10,18 +10,25 @@ export interface CommandConfig {
10
10
  }
11
11
 
12
12
  interface RunningProcess {
13
- label: string
13
+ label?: string
14
14
  proc: ReturnType<typeof Bun.spawn>
15
15
  prefix: string
16
16
  }
17
17
 
18
- async function pipeOutput(stream: ReadableStream<Uint8Array>, prefix: string): Promise<void> {
18
+ async function pipeOutput(
19
+ stream: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>,
20
+ prefix: string,
21
+ ): Promise<void> {
19
22
  const decoder = new TextDecoder()
20
- for await (const chunk of stream) {
23
+ for await (const chunk of stream as AsyncIterable<Uint8Array>) {
21
24
  const text = decoder.decode(chunk).trimEnd()
22
25
  if (text) {
23
26
  for (const line of text.split("\n")) {
24
- console.log(`${prefix} ${line}`)
27
+ if (prefix) {
28
+ console.log(`${prefix} ${line}`)
29
+ } else {
30
+ console.log(line)
31
+ }
25
32
  }
26
33
  }
27
34
  }
@@ -37,14 +44,22 @@ export async function runConcurrently(commands: CommandConfig[]): Promise<void>
37
44
  const processes: RunningProcess[] = []
38
45
 
39
46
  for (const config of commands) {
40
- const colorName = config.color ?? getNextColor()
41
- const colorFn = colors[colorName]
42
- const prefix = `[${colorFn(config.label)}]`
47
+ let prefix = ""
48
+ if (config.label) {
49
+ const colorName = config.color ?? getNextColor()
50
+ const colorFn = colors[colorName]
51
+ prefix = `[${colorFn(config.label)}]`
52
+ }
53
+
43
54
  const args = parseCommand(config.command)
44
55
 
45
56
  const proc = Bun.spawn(args, {
46
57
  stdout: "pipe",
47
58
  stderr: "pipe",
59
+ env: {
60
+ ...process.env, // Inherit existing env
61
+ FORCE_COLOR: "1", // Force color output despite pipe
62
+ },
48
63
  })
49
64
 
50
65
  processes.push({ label: config.label, proc, prefix })
@@ -54,30 +69,40 @@ export async function runConcurrently(commands: CommandConfig[]): Promise<void>
54
69
  pipeOutput(proc.stderr, prefix)
55
70
  }
56
71
 
57
- // Handle SIGINT to kill all child processes
58
- const cleanup = () => {
72
+ // Kill all processes and exit
73
+ const killAll = (exitCode: number) => {
59
74
  for (const { proc } of processes) {
60
- proc.kill()
75
+ try {
76
+ proc.kill()
77
+ } catch {
78
+ // Process may already be dead
79
+ }
61
80
  }
62
- process.exit(0)
81
+ process.exit(exitCode)
63
82
  }
64
- process.on("SIGINT", cleanup)
65
- process.on("SIGTERM", cleanup)
66
83
 
67
- // Wait for all processes to complete
68
- const results = await Promise.all(
69
- processes.map(async ({ label, proc, prefix }) => {
70
- const exitCode = await proc.exited
71
- if (exitCode !== 0) {
72
- console.log(`${prefix} exited with code ${exitCode}`)
73
- }
74
- return { label, exitCode }
75
- }),
76
- )
84
+ // Handle SIGINT/SIGTERM to kill all child processes
85
+ process.on("SIGINT", () => killAll(0))
86
+ process.on("SIGTERM", () => killAll(0))
87
+
88
+ // Create a promise for each process that resolves on completion or rejects on failure
89
+ const processPromises = processes.map(async ({ label, proc, prefix }) => {
90
+ const exitCode = await proc.exited
91
+ if (exitCode !== 0) {
92
+ const labelText = label ? ` (${label})` : ""
93
+ console.error(
94
+ `\n${prefix} Process${labelText} exited with code ${exitCode}. Stopping all processes...`,
95
+ )
96
+ throw new Error(`Process failed with exit code ${exitCode}`)
97
+ }
98
+ return { label, exitCode }
99
+ })
77
100
 
78
- // Exit with non-zero if any process failed
79
- const failed = results.filter((r) => r.exitCode !== 0)
80
- if (failed.length > 0) {
81
- process.exit(1)
101
+ try {
102
+ // Wait for all to succeed - if any fails, it will throw and we catch it
103
+ await Promise.all(processPromises)
104
+ } catch {
105
+ // One process failed - kill all others and exit
106
+ killAll(1)
82
107
  }
83
108
  }