@10stars/config 15.0.13 → 15.1.0

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,9 +1,9 @@
1
1
  {
2
2
  "name": "@10stars/config",
3
- "version": "15.0.13",
3
+ "version": "15.1.0",
4
4
  "private": false,
5
5
  "bin": {
6
- "config": "./src/index.ts"
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.21.0",
26
- "oxlint": "1.36.0",
27
- "oxlint-tsgolint": "0.10.1",
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!
@@ -58,6 +61,10 @@ const actions = {
58
61
  info: `Typecheck code (no watch)`,
59
62
  action: () => exec(`tsc --project ./tsconfig.json --noEmit`),
60
63
  },
64
+ typecheckGo: {
65
+ info: `Typecheck using tsgo (@typescript/native-preview, experimental)`,
66
+ action: () => exec(`@typescript/native-preview --project ./tsconfig.json`),
67
+ },
61
68
  lint: {
62
69
  info: `Format & Lint code (no watch)`,
63
70
  action: async () => exec(`oxfmt && oxlint --fix --type-aware ${await getOxlintCommonCmd()}`),
@@ -97,23 +104,91 @@ const actions = {
97
104
  }
98
105
  },
99
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
+ },
100
162
  } satisfies Record<string, { info: string; action: () => void | Promise<void> }>
101
163
 
102
164
  export const run = async () => {
103
- const cmd = process.argv[2] as keyof typeof actions
165
+ const cmd = process.argv[2]
104
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
+
105
180
  if (!availableActions.includes(cmd)) {
106
- let error = [
107
- `Unknown command: ${cmd}\n`,
108
- `Example CLI cmd: $ config lint`,
109
- `Available commands:`,
110
- ].join(`\n`)
111
- for (const availableAction of availableActions) {
112
- error += `\n ${availableAction} - ${actions[availableAction].info}`
113
- }
114
- throw new Error(`${error}\n`)
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
+ )
115
190
  }
116
- await actions[cmd].action()
191
+ await actions[cmd as keyof typeof actions].action()
117
192
  }
118
193
 
119
194
  void run()
package/src/runner.ts ADDED
@@ -0,0 +1,83 @@
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(stream: ReadableStream<Uint8Array>, prefix: string): Promise<void> {
19
+ const decoder = new TextDecoder()
20
+ for await (const chunk of stream) {
21
+ const text = decoder.decode(chunk).trimEnd()
22
+ if (text) {
23
+ for (const line of text.split("\n")) {
24
+ console.log(`${prefix} ${line}`)
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ function parseCommand(command: string | string[]): string[] {
31
+ if (Array.isArray(command)) return command
32
+ // Simple split by spaces - doesn't handle quoted strings
33
+ return command.split(/\s+/).filter(Boolean)
34
+ }
35
+
36
+ export async function runConcurrently(commands: CommandConfig[]): Promise<void> {
37
+ const processes: RunningProcess[] = []
38
+
39
+ for (const config of commands) {
40
+ const colorName = config.color ?? getNextColor()
41
+ const colorFn = colors[colorName]
42
+ const prefix = `[${colorFn(config.label)}]`
43
+ const args = parseCommand(config.command)
44
+
45
+ const proc = Bun.spawn(args, {
46
+ stdout: "pipe",
47
+ stderr: "pipe",
48
+ })
49
+
50
+ processes.push({ label: config.label, proc, prefix })
51
+
52
+ // Start piping output (don't await - run concurrently)
53
+ pipeOutput(proc.stdout, prefix)
54
+ pipeOutput(proc.stderr, prefix)
55
+ }
56
+
57
+ // Handle SIGINT to kill all child processes
58
+ const cleanup = () => {
59
+ for (const { proc } of processes) {
60
+ proc.kill()
61
+ }
62
+ process.exit(0)
63
+ }
64
+ process.on("SIGINT", cleanup)
65
+ process.on("SIGTERM", cleanup)
66
+
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
+ )
77
+
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)
82
+ }
83
+ }