@10stars/config 15.0.14 → 15.1.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/package.json +6 -5
- package/src/colors.ts +26 -0
- package/src/index.ts +82 -11
- package/src/runner.ts +90 -0
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@10stars/config",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"bin": {
|
|
6
|
-
"
|
|
6
|
+
"10config": "./src/index.ts"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
".oxfmtrc.json",
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@10stars/oxlint-plugin-react-hooks": "^1.0.5",
|
|
21
21
|
"@oxc-node/core": "0.0.35",
|
|
22
|
+
"@types/bun": "^1.0.0",
|
|
22
23
|
"@types/node": "^24.0.0",
|
|
23
24
|
"@types/react": "^19.0.0",
|
|
24
25
|
"husky": "^9.0.0",
|
|
25
|
-
"oxfmt": "0.
|
|
26
|
-
"oxlint": "1.
|
|
27
|
-
"oxlint-tsgolint": "0.11.0",
|
|
26
|
+
"oxfmt": "^0.24.0",
|
|
27
|
+
"oxlint": "^1.39.0",
|
|
28
|
+
"oxlint-tsgolint": "^0.11.0",
|
|
28
29
|
"tsx": "^4.21.0",
|
|
29
30
|
"type-fest": "^5.0.0",
|
|
30
31
|
"typescript": "~5.9.3"
|
package/src/colors.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const reset = "\x1b[0m"
|
|
2
|
+
|
|
3
|
+
export const colors = {
|
|
4
|
+
yellow: (t: string) => "\x1b[33m" + t + reset,
|
|
5
|
+
cyan: (t: string) => "\x1b[36m" + t + reset,
|
|
6
|
+
green: (t: string) => "\x1b[32m" + t + reset,
|
|
7
|
+
blue: (t: string) => "\x1b[34m" + t + reset,
|
|
8
|
+
magenta: (t: string) => "\x1b[35m" + t + reset,
|
|
9
|
+
red: (t: string) => "\x1b[31m" + t + reset,
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
export type ColorName = keyof typeof colors
|
|
13
|
+
|
|
14
|
+
const colorNames = Object.keys(colors) as ColorName[]
|
|
15
|
+
|
|
16
|
+
let colorIndex = 0
|
|
17
|
+
|
|
18
|
+
export function getNextColor(): ColorName {
|
|
19
|
+
const color = colorNames[colorIndex % colorNames.length]!
|
|
20
|
+
colorIndex++
|
|
21
|
+
return color
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resetColorIndex(): void {
|
|
25
|
+
colorIndex = 0
|
|
26
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
1
2
|
import { execSync } from "node:child_process"
|
|
2
3
|
import fs from "node:fs/promises"
|
|
3
4
|
import path from "node:path"
|
|
4
5
|
|
|
6
|
+
import { resetColorIndex } from "./colors"
|
|
5
7
|
import { checkIsMonorepo, linkPackages } from "./link-packages"
|
|
8
|
+
import { runConcurrently, type CommandConfig } from "./runner"
|
|
6
9
|
import { setupVSCode } from "./vscode-config"
|
|
7
10
|
|
|
8
11
|
const cwd = process.env.PWD!
|
|
@@ -101,23 +104,91 @@ const actions = {
|
|
|
101
104
|
}
|
|
102
105
|
},
|
|
103
106
|
},
|
|
107
|
+
run: {
|
|
108
|
+
info: `Run multiple commands concurrently (usage: 10config run -l label1 "cmd1" -l label2 "cmd2")`,
|
|
109
|
+
action: async () => {
|
|
110
|
+
const args = process.argv.slice(3) // skip 'node', 'config', 'run'
|
|
111
|
+
|
|
112
|
+
const printHelp = () => {
|
|
113
|
+
console.log(`
|
|
114
|
+
Run multiple commands concurrently with colored prefixes
|
|
115
|
+
|
|
116
|
+
Usage:
|
|
117
|
+
10config run --label <name> "<command>" [--label <name> "<command>" ...]
|
|
118
|
+
10config run -l <name> "<command>" [-l <name> "<command>" ...]
|
|
119
|
+
10config run "<command1>" "<command2>" ...
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
10config run -l server "bun run dev.server" -l client "bun run dev.client"
|
|
123
|
+
10config run -l hono "bun run dev.hono" -l astro "bun run dev.astro"
|
|
124
|
+
`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
128
|
+
printHelp()
|
|
129
|
+
process.exit(0)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const commands: CommandConfig[] = []
|
|
133
|
+
let i = 0
|
|
134
|
+
|
|
135
|
+
while (i < args.length) {
|
|
136
|
+
const arg = args[i]
|
|
137
|
+
if (arg === "-l" || arg === "--label") {
|
|
138
|
+
const label = args[i + 1]
|
|
139
|
+
const command = args[i + 2]
|
|
140
|
+
if (!label || !command) {
|
|
141
|
+
console.error(`Error: --label requires a label and a command`)
|
|
142
|
+
console.error(`Usage: 10config run --label <name> "<command>"`)
|
|
143
|
+
process.exit(1)
|
|
144
|
+
}
|
|
145
|
+
commands.push({ label, command })
|
|
146
|
+
i += 3
|
|
147
|
+
} else {
|
|
148
|
+
commands.push({ label: `cmd${commands.length + 1}`, command: arg! })
|
|
149
|
+
i++
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (commands.length === 0) {
|
|
154
|
+
printHelp()
|
|
155
|
+
process.exit(0)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
resetColorIndex()
|
|
159
|
+
await runConcurrently(commands)
|
|
160
|
+
},
|
|
161
|
+
},
|
|
104
162
|
} satisfies Record<string, { info: string; action: () => void | Promise<void> }>
|
|
105
163
|
|
|
106
164
|
export const run = async () => {
|
|
107
|
-
const cmd = process.argv[2]
|
|
165
|
+
const cmd = process.argv[2]
|
|
108
166
|
const availableActions = Object.keys(actions)
|
|
167
|
+
|
|
168
|
+
const getAvailableCommands = () =>
|
|
169
|
+
availableActions
|
|
170
|
+
.map((action) => ` ${action} - ${actions[action as keyof typeof actions].info}`)
|
|
171
|
+
.join("\n")
|
|
172
|
+
|
|
173
|
+
if (!cmd || cmd === "-h" || cmd === "--help") {
|
|
174
|
+
console.log(`Usage: 10config <command> [options]\n`)
|
|
175
|
+
console.log(`Available commands:`)
|
|
176
|
+
console.log(getAvailableCommands())
|
|
177
|
+
process.exit(0)
|
|
178
|
+
}
|
|
179
|
+
|
|
109
180
|
if (!availableActions.includes(cmd)) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
181
|
+
throw new Error(
|
|
182
|
+
[
|
|
183
|
+
`Unknown command: ${cmd}\n`,
|
|
184
|
+
`Example CLI cmd: $ 10config lint`,
|
|
185
|
+
`Available commands:`,
|
|
186
|
+
getAvailableCommands(),
|
|
187
|
+
"",
|
|
188
|
+
].join(`\n`),
|
|
189
|
+
)
|
|
119
190
|
}
|
|
120
|
-
await actions[cmd].action()
|
|
191
|
+
await actions[cmd as keyof typeof actions].action()
|
|
121
192
|
}
|
|
122
193
|
|
|
123
194
|
void run()
|
package/src/runner.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { colors, getNextColor, type ColorName } from "./colors"
|
|
2
|
+
|
|
3
|
+
export interface CommandConfig {
|
|
4
|
+
/** Label shown as prefix in output */
|
|
5
|
+
label: string
|
|
6
|
+
/** Command to run (string will be split by spaces, array used as-is) */
|
|
7
|
+
command: string | string[]
|
|
8
|
+
/** Color for the label (auto-assigned if not provided) */
|
|
9
|
+
color?: ColorName
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface RunningProcess {
|
|
13
|
+
label: string
|
|
14
|
+
proc: ReturnType<typeof Bun.spawn>
|
|
15
|
+
prefix: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function pipeOutput(
|
|
19
|
+
stream: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>,
|
|
20
|
+
prefix: string,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const decoder = new TextDecoder()
|
|
23
|
+
for await (const chunk of stream as AsyncIterable<Uint8Array>) {
|
|
24
|
+
const text = decoder.decode(chunk).trimEnd()
|
|
25
|
+
if (text) {
|
|
26
|
+
for (const line of text.split("\n")) {
|
|
27
|
+
console.log(`${prefix} ${line}`)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseCommand(command: string | string[]): string[] {
|
|
34
|
+
if (Array.isArray(command)) return command
|
|
35
|
+
// Simple split by spaces - doesn't handle quoted strings
|
|
36
|
+
return command.split(/\s+/).filter(Boolean)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runConcurrently(commands: CommandConfig[]): Promise<void> {
|
|
40
|
+
const processes: RunningProcess[] = []
|
|
41
|
+
|
|
42
|
+
for (const config of commands) {
|
|
43
|
+
const colorName = config.color ?? getNextColor()
|
|
44
|
+
const colorFn = colors[colorName]
|
|
45
|
+
const prefix = `[${colorFn(config.label)}]`
|
|
46
|
+
const args = parseCommand(config.command)
|
|
47
|
+
|
|
48
|
+
const proc = Bun.spawn(args, {
|
|
49
|
+
stdout: "pipe",
|
|
50
|
+
stderr: "pipe",
|
|
51
|
+
env: {
|
|
52
|
+
...process.env, // Inherit existing env
|
|
53
|
+
FORCE_COLOR: "1", // Force color output despite pipe
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
processes.push({ label: config.label, proc, prefix })
|
|
58
|
+
|
|
59
|
+
// Start piping output (don't await - run concurrently)
|
|
60
|
+
pipeOutput(proc.stdout, prefix)
|
|
61
|
+
pipeOutput(proc.stderr, prefix)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Handle SIGINT to kill all child processes
|
|
65
|
+
const cleanup = () => {
|
|
66
|
+
for (const { proc } of processes) {
|
|
67
|
+
proc.kill()
|
|
68
|
+
}
|
|
69
|
+
process.exit(0)
|
|
70
|
+
}
|
|
71
|
+
process.on("SIGINT", cleanup)
|
|
72
|
+
process.on("SIGTERM", cleanup)
|
|
73
|
+
|
|
74
|
+
// Wait for all processes to complete
|
|
75
|
+
const results = await Promise.all(
|
|
76
|
+
processes.map(async ({ label, proc, prefix }) => {
|
|
77
|
+
const exitCode = await proc.exited
|
|
78
|
+
if (exitCode !== 0) {
|
|
79
|
+
console.log(`${prefix} exited with code ${exitCode}`)
|
|
80
|
+
}
|
|
81
|
+
return { label, exitCode }
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Exit with non-zero if any process failed
|
|
86
|
+
const failed = results.filter((r) => r.exitCode !== 0)
|
|
87
|
+
if (failed.length > 0) {
|
|
88
|
+
process.exit(1)
|
|
89
|
+
}
|
|
90
|
+
}
|