@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 +1 -1
- package/src/index.ts +1 -1
- package/src/runner.ts +53 -28
package/package.json
CHANGED
package/src/index.ts
CHANGED
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
|
|
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
|
|
13
|
+
label?: string
|
|
14
14
|
proc: ReturnType<typeof Bun.spawn>
|
|
15
15
|
prefix: string
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
async function pipeOutput(
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
//
|
|
58
|
-
const
|
|
72
|
+
// Kill all processes and exit
|
|
73
|
+
const killAll = (exitCode: number) => {
|
|
59
74
|
for (const { proc } of processes) {
|
|
60
|
-
|
|
75
|
+
try {
|
|
76
|
+
proc.kill()
|
|
77
|
+
} catch {
|
|
78
|
+
// Process may already be dead
|
|
79
|
+
}
|
|
61
80
|
}
|
|
62
|
-
process.exit(
|
|
81
|
+
process.exit(exitCode)
|
|
63
82
|
}
|
|
64
|
-
process.on("SIGINT", cleanup)
|
|
65
|
-
process.on("SIGTERM", cleanup)
|
|
66
83
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
}
|