@take-out/scripts 0.1.37 → 0.1.38-1772433507984
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/bin/run-group +0 -0
- package/package.json +3 -2
- package/src/helpers/run.ts +79 -6
- package/src/run-group/.zig-cache/z/31f4371889dc4e502f87d8ad03235caa +0 -0
- package/src/run-group/build.zig +24 -0
- package/src/run-group/main.zig +327 -0
- package/src/run-group/run-group +0 -0
- package/src/run-group/run-group.c +497 -0
- package/src/run.ts +161 -226
package/src/run.ts
CHANGED
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @description Run multiple scripts in parallel
|
|
4
|
+
* @description Run multiple scripts in parallel using run-group for proper process management
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { spawn } from 'node:child_process'
|
|
8
7
|
import fs from 'node:fs'
|
|
9
|
-
import { join, relative, resolve } from 'node:path'
|
|
8
|
+
import { dirname, join, relative, resolve } from 'node:path'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { getIsExiting } from './helpers/run'
|
|
14
|
-
import { checkNodeVersion } from './node-version-check'
|
|
15
|
-
|
|
16
|
-
const colors = [
|
|
17
|
-
'\x1b[38;5;245m',
|
|
18
|
-
'\x1b[38;5;240m',
|
|
19
|
-
'\x1b[38;5;250m',
|
|
20
|
-
'\x1b[38;5;243m',
|
|
21
|
-
'\x1b[38;5;248m',
|
|
22
|
-
'\x1b[38;5;238m',
|
|
23
|
-
'\x1b[38;5;252m',
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
const reset = '\x1b[0m'
|
|
27
|
-
|
|
28
|
-
// eslint-disable-next-line no-control-regex
|
|
29
|
-
const ansiPattern = /\x1b\[[0-9;]*m/g
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
30
12
|
|
|
31
13
|
const args = process.argv.slice(2)
|
|
32
14
|
const ownFlags = ['--no-root', '--bun', '--watch', '--flags=last']
|
|
@@ -50,40 +32,22 @@ for (let i = 0; i < args.length; i++) {
|
|
|
50
32
|
}
|
|
51
33
|
|
|
52
34
|
const noRoot = args.includes('--no-root')
|
|
53
|
-
const runBun = args.includes('--bun')
|
|
54
|
-
const watch = args.includes('--watch')
|
|
55
35
|
const flagsLast = args.includes('--flags=last')
|
|
56
36
|
|
|
57
|
-
const MAX_RESTARTS = 3
|
|
58
|
-
|
|
59
37
|
const parentRunningScripts = process.env.BUN_RUN_SCRIPTS
|
|
60
38
|
? process.env.BUN_RUN_SCRIPTS.split(',')
|
|
61
39
|
: []
|
|
62
40
|
|
|
63
|
-
interface ManagedProcess {
|
|
64
|
-
proc: ReturnType<typeof spawn>
|
|
65
|
-
name: string
|
|
66
|
-
cwd: string
|
|
67
|
-
prefixLabel: string
|
|
68
|
-
extraArgs: string[]
|
|
69
|
-
index: number
|
|
70
|
-
shortcut: string
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const managedProcesses: ManagedProcess[] = []
|
|
74
|
-
const { addChildProcess, exit } = handleProcessExit()
|
|
75
|
-
|
|
76
|
-
function getPrefix(index: number): string {
|
|
77
|
-
const managed = managedProcesses[index]
|
|
78
|
-
if (!managed) return ''
|
|
79
|
-
const color = colors[index % colors.length]
|
|
80
|
-
const sc = managed.shortcut || String(index + 1)
|
|
81
|
-
return `${color}${sc} ${managed.prefixLabel}${reset}`
|
|
82
|
-
}
|
|
83
|
-
|
|
84
41
|
if (runCommands.length === 0) {
|
|
85
42
|
console.error('Please provide at least one script name to run')
|
|
86
|
-
exit(1)
|
|
43
|
+
process.exit(1)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ResolvedCommand {
|
|
47
|
+
name: string
|
|
48
|
+
command: string
|
|
49
|
+
cwd: string
|
|
50
|
+
args: string[]
|
|
87
51
|
}
|
|
88
52
|
|
|
89
53
|
async function readPackageJson(directoryPath: string) {
|
|
@@ -179,6 +143,15 @@ async function findWorkspaceDirectories(): Promise<string[]> {
|
|
|
179
143
|
})
|
|
180
144
|
}
|
|
181
145
|
|
|
146
|
+
async function getScriptCommand(
|
|
147
|
+
directoryPath: string,
|
|
148
|
+
scriptName: string
|
|
149
|
+
): Promise<string | null> {
|
|
150
|
+
const packageJson = await readPackageJson(directoryPath)
|
|
151
|
+
if (!packageJson?.scripts?.[scriptName]) return null
|
|
152
|
+
return packageJson.scripts[scriptName]
|
|
153
|
+
}
|
|
154
|
+
|
|
182
155
|
async function findAvailableScripts(
|
|
183
156
|
directoryPath: string,
|
|
184
157
|
scriptNames: string[]
|
|
@@ -194,218 +167,180 @@ async function findAvailableScripts(
|
|
|
194
167
|
)
|
|
195
168
|
}
|
|
196
169
|
|
|
197
|
-
async function
|
|
198
|
-
|
|
199
|
-
|
|
170
|
+
async function resolveAllCommands(): Promise<ResolvedCommand[]> {
|
|
171
|
+
const commands: ResolvedCommand[] = []
|
|
172
|
+
const lastScript = runCommands[runCommands.length - 1]
|
|
173
|
+
|
|
174
|
+
// root scripts
|
|
175
|
+
if (!noRoot) {
|
|
176
|
+
const filteredCommands = runCommands.filter(
|
|
177
|
+
(name) => !parentRunningScripts.includes(name)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
for (const name of filteredCommands) {
|
|
181
|
+
const command = await getScriptCommand('.', name)
|
|
182
|
+
if (command) {
|
|
183
|
+
const scriptArgs = !flagsLast || name === lastScript ? forwardArgs : []
|
|
184
|
+
commands.push({
|
|
185
|
+
name,
|
|
186
|
+
command,
|
|
187
|
+
cwd: '.',
|
|
188
|
+
args: scriptArgs,
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// workspace scripts
|
|
200
195
|
const workspaceDirs = await findWorkspaceDirectories()
|
|
201
|
-
const workspaceScriptMap = new Map<string, { scripts: string[]; packageName: string }>()
|
|
202
196
|
|
|
203
197
|
for (const dir of workspaceDirs) {
|
|
204
|
-
const availableScripts = await findAvailableScripts(dir,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
198
|
+
const availableScripts = await findAvailableScripts(dir, runCommands)
|
|
199
|
+
const filteredScripts = availableScripts.filter(
|
|
200
|
+
(scriptName) => !parentRunningScripts.includes(scriptName)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
for (const scriptName of filteredScripts) {
|
|
204
|
+
const command = await getScriptCommand(dir, scriptName)
|
|
205
|
+
if (command) {
|
|
206
|
+
const packageJson = await readPackageJson(dir)
|
|
207
|
+
const packageName = packageJson?.name || dir
|
|
208
|
+
const scriptArgs = !flagsLast || scriptName === lastScript ? forwardArgs : []
|
|
209
|
+
|
|
210
|
+
commands.push({
|
|
211
|
+
name: `${packageName} ${scriptName}`,
|
|
212
|
+
command,
|
|
213
|
+
cwd: dir,
|
|
214
|
+
args: scriptArgs,
|
|
215
|
+
})
|
|
216
|
+
}
|
|
213
217
|
}
|
|
214
218
|
}
|
|
215
219
|
|
|
216
|
-
return
|
|
220
|
+
return commands
|
|
217
221
|
}
|
|
218
222
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
restarts = 0,
|
|
224
|
-
extraArgs: string[] = [],
|
|
225
|
-
managedIndex?: number
|
|
226
|
-
) => {
|
|
227
|
-
const index = managedIndex ?? managedProcesses.length
|
|
228
|
-
|
|
229
|
-
const runArgs = ['run', '--silent', runBun ? '--bun' : '', name, ...extraArgs].filter(
|
|
230
|
-
Boolean
|
|
231
|
-
)
|
|
223
|
+
function findRunGroup(): string | null {
|
|
224
|
+
// check local first (in takeout monorepo)
|
|
225
|
+
const localPath = join(__dirname, 'run-group', 'run-group')
|
|
226
|
+
if (fs.existsSync(localPath)) return localPath
|
|
232
227
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
237
|
-
shell: false,
|
|
238
|
-
detached: true,
|
|
239
|
-
env: {
|
|
240
|
-
...process.env,
|
|
241
|
-
FORCE_COLOR: '3',
|
|
242
|
-
BUN_RUN_PARENT_SCRIPT: name,
|
|
243
|
-
BUN_RUN_SCRIPTS: allRunningScripts,
|
|
244
|
-
TKO_SILENT: '1',
|
|
245
|
-
} as any,
|
|
246
|
-
cwd: resolve(cwd),
|
|
247
|
-
})
|
|
228
|
+
// check installed package
|
|
229
|
+
const installedPath = join(__dirname, '..', 'run-group', 'run-group')
|
|
230
|
+
if (fs.existsSync(installedPath)) return installedPath
|
|
248
231
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
name,
|
|
252
|
-
cwd,
|
|
253
|
-
prefixLabel,
|
|
254
|
-
extraArgs,
|
|
255
|
-
index,
|
|
256
|
-
shortcut: '',
|
|
257
|
-
}
|
|
232
|
+
return null
|
|
233
|
+
}
|
|
258
234
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
235
|
+
// fallback: use node spawn when run-group binary not available
|
|
236
|
+
async function runWithNodeFallback(commands: ResolvedCommand[]) {
|
|
237
|
+
const { spawn } = await import('node:child_process')
|
|
238
|
+
const { handleProcessExit } = await import('@take-out/scripts/helpers/handleProcessExit')
|
|
239
|
+
|
|
240
|
+
const colors = [
|
|
241
|
+
'\x1b[36m', '\x1b[35m', '\x1b[32m', '\x1b[33m', '\x1b[34m', '\x1b[31m',
|
|
242
|
+
]
|
|
243
|
+
const reset = '\x1b[0m'
|
|
244
|
+
|
|
245
|
+
const { addChildProcess } = handleProcessExit()
|
|
246
|
+
let exitCode = 0
|
|
247
|
+
|
|
248
|
+
const procs = commands.map((cmd, i) => {
|
|
249
|
+
const color = colors[i % colors.length]
|
|
250
|
+
const prefix = `${color}[${cmd.name}]${reset}`
|
|
251
|
+
const fullCwd = resolve(cmd.cwd)
|
|
252
|
+
const fullCommand = cmd.args.length > 0
|
|
253
|
+
? `${cmd.command} ${cmd.args.join(' ')}`
|
|
254
|
+
: cmd.command
|
|
255
|
+
|
|
256
|
+
const proc = spawn('sh', ['-c', fullCommand], {
|
|
257
|
+
cwd: fullCwd,
|
|
258
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
259
|
+
env: {
|
|
260
|
+
...process.env,
|
|
261
|
+
FORCE_COLOR: '3',
|
|
262
|
+
TKO_SILENT: '1',
|
|
263
|
+
BUN_RUN_SCRIPTS: runCommands.join(','),
|
|
264
|
+
},
|
|
265
|
+
})
|
|
264
266
|
|
|
265
|
-
|
|
267
|
+
addChildProcess(proc)
|
|
266
268
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (stripped.startsWith('$ ')) continue
|
|
273
|
-
if (line) console.info(`${getPrefix(index)} ${line}`)
|
|
274
|
-
}
|
|
275
|
-
})
|
|
269
|
+
proc.stdout?.on('data', (data) => {
|
|
270
|
+
for (const line of data.toString().split('\n')) {
|
|
271
|
+
if (line) console.log(`${prefix} ${line}`)
|
|
272
|
+
}
|
|
273
|
+
})
|
|
276
274
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (stripped.startsWith('$ ')) continue
|
|
283
|
-
if (line) console.error(`${getPrefix(index)} ${line}`)
|
|
284
|
-
}
|
|
285
|
-
})
|
|
275
|
+
proc.stderr?.on('data', (data) => {
|
|
276
|
+
for (const line of data.toString().split('\n')) {
|
|
277
|
+
if (line) console.error(`${prefix} ${line}`)
|
|
278
|
+
}
|
|
279
|
+
})
|
|
286
280
|
|
|
287
|
-
|
|
288
|
-
|
|
281
|
+
return new Promise<number>((resolve) => {
|
|
282
|
+
proc.on('close', (code) => resolve(code || 0))
|
|
283
|
+
})
|
|
289
284
|
})
|
|
290
285
|
|
|
291
|
-
|
|
292
|
-
|
|
286
|
+
const codes = await Promise.all(procs)
|
|
287
|
+
exitCode = Math.max(...codes)
|
|
288
|
+
process.exit(exitCode)
|
|
289
|
+
}
|
|
293
290
|
|
|
294
|
-
|
|
295
|
-
|
|
291
|
+
async function main() {
|
|
292
|
+
const commands = await resolveAllCommands()
|
|
296
293
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
} else {
|
|
302
|
-
exit(1)
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
})
|
|
294
|
+
if (commands.length === 0) {
|
|
295
|
+
console.error('No matching scripts found')
|
|
296
|
+
process.exit(0)
|
|
297
|
+
}
|
|
306
298
|
|
|
307
|
-
|
|
308
|
-
}
|
|
299
|
+
const runGroupPath = findRunGroup()
|
|
309
300
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
.split(/[^a-z]+/)
|
|
315
|
-
.filter(Boolean)
|
|
316
|
-
return words.map((w) => w[0]).join('')
|
|
317
|
-
})
|
|
301
|
+
// fallback to node if binary not available
|
|
302
|
+
if (!runGroupPath) {
|
|
303
|
+
return runWithNodeFallback(commands)
|
|
304
|
+
}
|
|
318
305
|
|
|
319
|
-
|
|
306
|
+
// build run-group command
|
|
307
|
+
// format: run-group -p -t @name <cmd1> --- @name2 <cmd2> --- ...
|
|
308
|
+
const runGroupArgs: string[] = ['-p', '-t']
|
|
320
309
|
|
|
321
|
-
for (let
|
|
322
|
-
const
|
|
310
|
+
for (let i = 0; i < commands.length; i++) {
|
|
311
|
+
const cmd = commands[i]!
|
|
312
|
+
const fullCwd = resolve(cmd.cwd)
|
|
323
313
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
314
|
+
// wrap command with cd and env setup
|
|
315
|
+
// use sh -c to handle complex commands with &&, ||, etc
|
|
316
|
+
const envSetup = `FORCE_COLOR=3 TKO_SILENT=1 BUN_RUN_SCRIPTS=${runCommands.join(',')}`
|
|
317
|
+
const fullCommand = cmd.args.length > 0
|
|
318
|
+
? `${cmd.command} ${cmd.args.join(' ')}`
|
|
319
|
+
: cmd.command
|
|
331
320
|
|
|
332
|
-
for
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
for (const idx of indices) {
|
|
336
|
-
lengths[idx]!++
|
|
337
|
-
}
|
|
338
|
-
}
|
|
321
|
+
// use @name prefix for display name in parallel mode
|
|
322
|
+
runGroupArgs.push(`@${cmd.name}`)
|
|
323
|
+
runGroupArgs.push('sh', '-c', `cd "${fullCwd}" && ${envSetup} ${fullCommand}`)
|
|
339
324
|
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
managedProcesses[i]!.shortcut = shortcuts[i]!
|
|
343
|
-
}
|
|
344
|
-
return
|
|
325
|
+
if (i < commands.length - 1) {
|
|
326
|
+
runGroupArgs.push('---')
|
|
345
327
|
}
|
|
346
328
|
}
|
|
347
329
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
managedProcesses[i]!.shortcut = sc || String(i + 1)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
async function main() {
|
|
355
|
-
checkNodeVersion().catch((err) => {
|
|
356
|
-
console.error(err.message)
|
|
357
|
-
exit(1)
|
|
358
|
-
})
|
|
330
|
+
// exec run-group (replaces this process)
|
|
331
|
+
const { execFileSync } = await import('node:child_process')
|
|
359
332
|
|
|
360
333
|
try {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
)
|
|
368
|
-
const scriptPromises = filteredCommands.map((name) => {
|
|
369
|
-
const scriptArgs = !flagsLast || name === lastScript ? forwardArgs : []
|
|
370
|
-
return runScript(name, '.', name, 0, scriptArgs)
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
await Promise.all(scriptPromises)
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const workspaceScriptMap = await mapWorkspacesToScripts(runCommands)
|
|
377
|
-
|
|
378
|
-
for (const [workspace, { scripts, packageName }] of workspaceScriptMap.entries()) {
|
|
379
|
-
const filteredScripts = scripts.filter(
|
|
380
|
-
(scriptName) => !parentRunningScripts.includes(scriptName)
|
|
381
|
-
)
|
|
382
|
-
const workspaceScriptPromises = filteredScripts.map((scriptName) => {
|
|
383
|
-
const scriptArgs = !flagsLast || scriptName === lastScript ? forwardArgs : []
|
|
384
|
-
return runScript(
|
|
385
|
-
scriptName,
|
|
386
|
-
workspace,
|
|
387
|
-
`${packageName} ${scriptName}`,
|
|
388
|
-
0,
|
|
389
|
-
scriptArgs
|
|
390
|
-
)
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
await Promise.all(workspaceScriptPromises)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (managedProcesses.length === 0) {
|
|
398
|
-
exit(0)
|
|
399
|
-
} else {
|
|
400
|
-
computeShortcuts()
|
|
401
|
-
}
|
|
402
|
-
} catch (error) {
|
|
403
|
-
console.error(`Error running scripts: ${error}`)
|
|
404
|
-
exit(1)
|
|
334
|
+
execFileSync(runGroupPath, runGroupArgs, {
|
|
335
|
+
stdio: 'inherit',
|
|
336
|
+
env: process.env,
|
|
337
|
+
})
|
|
338
|
+
} catch (error: any) {
|
|
339
|
+
process.exit(error.status || 1)
|
|
405
340
|
}
|
|
406
341
|
}
|
|
407
342
|
|
|
408
343
|
main().catch((error) => {
|
|
409
344
|
console.error(`Error running scripts: ${error}`)
|
|
410
|
-
exit(1)
|
|
345
|
+
process.exit(1)
|
|
411
346
|
})
|