@take-out/scripts 0.0.28

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/src/release.ts ADDED
@@ -0,0 +1,352 @@
1
+ // note! this is an helper script used by tamagui team for publishing the takeout packages
2
+ // you can delete this from your own app
3
+
4
+ import { run } from '@take-out/scripts/helpers/run'
5
+ import fs, { ensureDir, writeJSON } from 'fs-extra'
6
+ import path, { join } from 'node:path'
7
+ import pMap from 'p-map'
8
+
9
+ // avoid emitter error
10
+ process.setMaxListeners(50)
11
+ process.stderr.setMaxListeners(50)
12
+ process.stdout.setMaxListeners(50)
13
+
14
+ // for failed publishes that need to re-run
15
+ const reRun = process.argv.includes('--rerun')
16
+ const rePublish = reRun || process.argv.includes('--republish')
17
+ const finish = process.argv.includes('--finish')
18
+ const skipFinish = process.argv.includes('--skip-finish')
19
+
20
+ const canary = process.argv.includes('--canary')
21
+ const skipVersion = finish || rePublish || process.argv.includes('--skip-version')
22
+ const shouldMajor = process.argv.includes('--major')
23
+ const shouldMinor = process.argv.includes('--minor')
24
+ const shouldPatch = process.argv.includes('--patch')
25
+ const dirty = finish || process.argv.includes('--dirty')
26
+ const skipTest =
27
+ finish ||
28
+ rePublish ||
29
+ process.argv.includes('--skip-test') ||
30
+ process.argv.includes('--skip-tests')
31
+ const skipBuild = finish || rePublish || process.argv.includes('--skip-build')
32
+ const dryRun = process.argv.includes('--dry-run')
33
+ const tamaguiGitUser = process.argv.includes('--tamagui-git-user')
34
+
35
+ const curVersion = fs.readJSONSync('./packages/helpers/package.json').version
36
+
37
+ // must specify version (unless republishing):
38
+ if (!rePublish && !skipVersion && !shouldPatch && !shouldMinor && !shouldMajor) {
39
+ console.error(`Must specify one of --patch, --minor, or --major`)
40
+ process.exit(1)
41
+ }
42
+
43
+ const nextVersion = (() => {
44
+ if (rePublish || skipVersion) {
45
+ return curVersion
46
+ }
47
+
48
+ if (canary) {
49
+ return `${curVersion.replace(/(-\d+)+$/, '')}-${Date.now()}`
50
+ }
51
+
52
+ const curMajor = +curVersion.split('.')[0] || 0
53
+ const curMinor = +curVersion.split('.')[1] || 0
54
+ const patchAndCanary = curVersion.split('.')[2]
55
+ const [curPatch] = patchAndCanary.split('-')
56
+ const patchVersion = shouldPatch ? +curPatch + 1 : 0
57
+ const minorVersion = curMinor + (shouldMinor ? 1 : 0)
58
+ const majorVersion = curMajor + (shouldMajor ? 1 : 0)
59
+ const next = `${majorVersion}.${minorVersion}.${patchVersion}`
60
+
61
+ return next
62
+ })()
63
+
64
+ if (!skipVersion) {
65
+ console.info(` 🚀 Releasing:`)
66
+ console.info(' Current:', curVersion)
67
+ console.info(` Next: ${nextVersion}`)
68
+ }
69
+
70
+ async function main() {
71
+ try {
72
+ // ensure we are up to date
73
+ // ensure we are on main
74
+ if (!canary) {
75
+ if ((await run(`git rev-parse --abbrev-ref HEAD`)).stdout.trim() !== 'main') {
76
+ throw new Error(`Not on main`)
77
+ }
78
+ if (!dirty && !rePublish && !finish) {
79
+ await run(`git pull --rebase origin main`)
80
+ }
81
+ }
82
+
83
+ const packagePaths = await getWorkspacePackages()
84
+ const { allPackageJsons, publishablePackages: packageJsons } =
85
+ await loadPackageJsons(packagePaths)
86
+
87
+ if (!finish) {
88
+ console.info(
89
+ `Publishing in order:\n\n${packageJsons.map((x) => x.name).join('\n')}`
90
+ )
91
+ }
92
+
93
+ async function checkDistDirs() {
94
+ await Promise.all(
95
+ packageJsons.map(async ({ cwd, json }) => {
96
+ const distDir = join(cwd, 'dist')
97
+ if (json.scripts?.build) {
98
+ if (!(await fs.pathExists(distDir))) {
99
+ console.warn('no dist dir!', distDir)
100
+ process.exit(1)
101
+ }
102
+ }
103
+ })
104
+ )
105
+ }
106
+
107
+ if (tamaguiGitUser) {
108
+ await run(`git config --global user.name 'Tamagui'`)
109
+ await run(`git config --global user.email 'tamagui@users.noreply.github.com`)
110
+ }
111
+
112
+ console.info('install and build')
113
+
114
+ if (!rePublish && !finish) {
115
+ await run(`bun install`)
116
+ }
117
+
118
+ if (!skipBuild && !finish) {
119
+ await run(`bun clean`)
120
+ await run(`bun run build`)
121
+ await checkDistDirs()
122
+ }
123
+
124
+ if (!finish) {
125
+ console.info('run checks')
126
+
127
+ if (!skipTest) {
128
+ await run(`bun lint`)
129
+ await run(`bun check`)
130
+ // await run(`bun test`)
131
+ }
132
+ }
133
+
134
+ if (!dirty && !dryRun && !rePublish) {
135
+ const out = await run(`git status --porcelain`)
136
+ if (out.stdout) {
137
+ throw new Error(`Has unsaved git changes: ${out.stdout}`)
138
+ }
139
+ }
140
+
141
+ if (!skipVersion && !finish) {
142
+ await Promise.all(
143
+ allPackageJsons.map(async ({ json, path }) => {
144
+ const next = { ...json }
145
+
146
+ next.version = nextVersion
147
+
148
+ for (const field of [
149
+ 'dependencies',
150
+ 'devDependencies',
151
+ 'optionalDependencies',
152
+ 'peerDependencies',
153
+ ]) {
154
+ const nextDeps = next[field]
155
+ if (!nextDeps) continue
156
+ for (const depName in nextDeps) {
157
+ // only update non-workspace internal dependencies
158
+ if (!nextDeps[depName].startsWith('workspace:')) {
159
+ if (allPackageJsons.some((p) => p.name === depName)) {
160
+ nextDeps[depName] = nextVersion
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ await writeJSON(path, next, { spaces: 2 })
167
+ })
168
+ )
169
+ }
170
+
171
+ if (!finish && dryRun) {
172
+ console.info(`Dry run, exiting before publish`)
173
+ return
174
+ }
175
+
176
+ if (!finish && !rePublish) {
177
+ await run(`git diff`)
178
+ }
179
+
180
+ if (!finish) {
181
+ const tmpDir = `/tmp/one-publish`
182
+ await ensureDir(tmpDir)
183
+
184
+ await pMap(
185
+ packageJsons,
186
+ async ({ name, cwd }) => {
187
+ const publishOptions = [canary && `--tag canary`].filter(Boolean).join(' ')
188
+
189
+ // pack with workspace:* converted to versions
190
+ const tmpPackageDir = join(tmpDir, name.replace('/', '_'))
191
+ await fs.copy(cwd, tmpPackageDir, {
192
+ filter: (src) => {
193
+ // exclude node_modules to avoid symlink issues
194
+ return !src.includes('node_modules')
195
+ },
196
+ })
197
+
198
+ // replace workspace:* with version
199
+ const pkgJsonPath = join(tmpPackageDir, 'package.json')
200
+ const pkgJson = await fs.readJSON(pkgJsonPath)
201
+ for (const field of [
202
+ 'dependencies',
203
+ 'devDependencies',
204
+ 'optionalDependencies',
205
+ 'peerDependencies',
206
+ ]) {
207
+ if (!pkgJson[field]) continue
208
+ for (const depName in pkgJson[field]) {
209
+ if (pkgJson[field][depName].startsWith('workspace:')) {
210
+ pkgJson[field][depName] = nextVersion
211
+ }
212
+ }
213
+ }
214
+ await writeJSON(pkgJsonPath, pkgJson, { spaces: 2 })
215
+
216
+ const filename = `${name.replace('/', '_')}-package.tmp.tgz`
217
+ const absolutePath = `${tmpDir}/${filename}`
218
+ await run(`npm pack --pack-destination ${tmpDir}`, {
219
+ cwd: tmpPackageDir,
220
+ silent: true,
221
+ })
222
+
223
+ // rename npm's output to our expected filename
224
+ const npmFilename = `${name.replace('@', '').replace('/', '-')}-${nextVersion}.tgz`
225
+ await fs.rename(join(tmpDir, npmFilename), absolutePath)
226
+
227
+ const publishCommand = ['npm publish', absolutePath, publishOptions]
228
+ .filter(Boolean)
229
+ .join(' ')
230
+
231
+ console.info(`Publishing ${name}: ${publishCommand}`)
232
+
233
+ await run(publishCommand, {
234
+ cwd: tmpDir,
235
+ }).catch((err) => console.error(err))
236
+ },
237
+ {
238
+ concurrency: 15,
239
+ }
240
+ )
241
+
242
+ console.info(`✅ Published\n`)
243
+ }
244
+
245
+ if (!skipFinish) {
246
+ // then git tag, commit, push
247
+ if (!finish) {
248
+ await run(`bun install`)
249
+ }
250
+ const tagPrefix = canary ? 'canary' : 'v'
251
+ const gitTag = `${tagPrefix}${nextVersion}`
252
+
253
+ await finishAndCommit()
254
+
255
+ async function finishAndCommit(cwd = process.cwd()) {
256
+ if (!rePublish || reRun || finish) {
257
+ await run(`git add -A`, { cwd })
258
+
259
+ await run(`git commit -m ${gitTag}`, { cwd })
260
+
261
+ if (!canary) {
262
+ await run(`git tag ${gitTag}`, { cwd })
263
+ }
264
+
265
+ if (!dirty) {
266
+ // pull once more before pushing so if there was a push in interim we get it
267
+ await run(`git pull --rebase origin HEAD`, { cwd })
268
+ }
269
+
270
+ await run(`git push origin head`, { cwd })
271
+ if (!canary) {
272
+ await run(`git push origin ${gitTag}`, { cwd })
273
+ }
274
+
275
+ console.info(`✅ Pushed and versioned\n`)
276
+ }
277
+ }
278
+ }
279
+
280
+ console.info(`✅ Done\n`)
281
+ } catch (err) {
282
+ console.info('\nError:\n', err)
283
+ process.exit(1)
284
+ }
285
+ }
286
+
287
+ async function getWorkspacePackages() {
288
+ // read workspaces from root package.json
289
+ const rootPackageJson = await fs.readJSON(join(process.cwd(), 'package.json'))
290
+ const workspaceGlobs = rootPackageJson.workspaces || []
291
+
292
+ // resolve workspace paths
293
+ const packagePaths: { name: string; location: string }[] = []
294
+ for (const glob of workspaceGlobs) {
295
+ if (glob.includes('*')) {
296
+ // handle glob patterns like "./packages/*"
297
+ const baseDir = glob.replace('/*', '')
298
+ const fullPath = join(process.cwd(), baseDir)
299
+ if (await fs.pathExists(fullPath)) {
300
+ const dirs = await fs.readdir(fullPath)
301
+ for (const dir of dirs) {
302
+ const pkgPath = join(fullPath, dir, 'package.json')
303
+ if (await fs.pathExists(pkgPath)) {
304
+ const pkg = await fs.readJSON(pkgPath)
305
+ packagePaths.push({
306
+ name: pkg.name,
307
+ location: join(baseDir, dir),
308
+ })
309
+ }
310
+ }
311
+ }
312
+ } else {
313
+ // handle direct paths like "./src/start"
314
+ const pkgPath = join(process.cwd(), glob, 'package.json')
315
+ if (await fs.pathExists(pkgPath)) {
316
+ const pkg = await fs.readJSON(pkgPath)
317
+ packagePaths.push({
318
+ name: pkg.name,
319
+ location: glob,
320
+ })
321
+ }
322
+ }
323
+ }
324
+
325
+ return packagePaths
326
+ }
327
+
328
+ async function loadPackageJsons(packagePaths: { name: string; location: string }[]) {
329
+ const allPackageJsons = await Promise.all(
330
+ packagePaths
331
+ .filter((i) => i.location !== '.' && !i.name.startsWith('@takeout'))
332
+ .map(async ({ name, location }) => {
333
+ const cwd = path.join(process.cwd(), location)
334
+ const json = await fs.readJSON(path.join(cwd, 'package.json'))
335
+ return {
336
+ name,
337
+ cwd,
338
+ json,
339
+ path: path.join(cwd, 'package.json'),
340
+ directory: location,
341
+ }
342
+ })
343
+ )
344
+
345
+ const publishablePackages = allPackageJsons.filter(
346
+ (x) => !x.json.skipPublish && !x.json.private
347
+ )
348
+
349
+ return { allPackageJsons, publishablePackages }
350
+ }
351
+
352
+ main()
package/src/run.ts ADDED
@@ -0,0 +1,358 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * @description Run multiple scripts in parallel or sequence
5
+ */
6
+
7
+ import { handleProcessExit } from '@take-out/scripts/helpers/handleProcessExit'
8
+ import { spawn } from 'node:child_process'
9
+ import { promises as fs } from 'node:fs'
10
+ import { join, relative, resolve } from 'node:path'
11
+ import { getIsExiting } from './helpers/run'
12
+ import { checkNodeVersion } from './node-version-check'
13
+
14
+ const colors = [
15
+ '\x1b[36m', // Cyan
16
+ '\x1b[35m', // Magenta
17
+ '\x1b[33m', // Yellow
18
+ '\x1b[32m', // Green
19
+ '\x1b[34m', // Blue
20
+ '\x1b[31m', // Red
21
+ '\x1b[90m', // Gray
22
+ ]
23
+
24
+ const reset = '\x1b[0m'
25
+
26
+ // Verbose logging flag - set to false to reduce logs
27
+ const verbose = false
28
+
29
+ // Helper function to conditionally log based on verbosity
30
+ const log = {
31
+ info: (message: string) => {
32
+ if (verbose) console.info(message)
33
+ },
34
+ error: (message: string) => console.error(message),
35
+ output: (message: string) => console.info(message),
36
+ }
37
+
38
+ const MAX_RESTARTS = 3
39
+
40
+ // Separate command names from flags/arguments
41
+ const args = process.argv.slice(2)
42
+ const runCommands = args.filter((x) => !x.startsWith('--'))
43
+ const noRoot = args.includes('--no-root')
44
+ const runBun = args.includes('--bun')
45
+ const watch = args.includes('--watch') // just attempts to restart a failed process up to MAX_RESTARTS times
46
+
47
+ // Collect additional flags and arguments to forward to sub-commands
48
+ const forwardArgs = args.filter(
49
+ (arg) =>
50
+ arg.startsWith('--') && arg !== '--no-root' && arg !== '--bun' && arg !== '--watch'
51
+ )
52
+
53
+ // Get the list of scripts already being run by a parent process
54
+ const parentRunningScripts = process.env.BUN_RUN_SCRIPTS
55
+ ? process.env.BUN_RUN_SCRIPTS.split(',')
56
+ : []
57
+
58
+ const processes: ReturnType<typeof spawn>[] = []
59
+ const { addChildProcess, exit } = handleProcessExit()
60
+
61
+ if (runCommands.length === 0) {
62
+ log.error('Please provide at least one script name to run')
63
+ log.error('Example: bun run.ts watch lint test')
64
+ exit(1)
65
+ }
66
+
67
+ async function readPackageJson(directoryPath: string) {
68
+ try {
69
+ const packageJsonPath = join(directoryPath, 'package.json')
70
+ const content = await fs.readFile(packageJsonPath, 'utf8')
71
+ return JSON.parse(content)
72
+ } catch (_) {
73
+ return null
74
+ }
75
+ }
76
+
77
+ async function getWorkspacePatterns(): Promise<string[]> {
78
+ try {
79
+ const packageJson = await readPackageJson('.')
80
+ if (!packageJson || !packageJson.workspaces) return []
81
+
82
+ return Array.isArray(packageJson.workspaces)
83
+ ? packageJson.workspaces
84
+ : packageJson.workspaces.packages || []
85
+ } catch (_) {
86
+ log.error('Error reading workspace patterns')
87
+ return []
88
+ }
89
+ }
90
+
91
+ async function hasPackageJson(path: string): Promise<boolean> {
92
+ try {
93
+ await fs.access(join(path, 'package.json'))
94
+ return true
95
+ } catch {
96
+ return false
97
+ }
98
+ }
99
+
100
+ async function findPackageJsonDirs(basePath: string, maxDepth = 3): Promise<string[]> {
101
+ if (maxDepth <= 0) return []
102
+
103
+ try {
104
+ const entries = await fs.readdir(basePath, { withFileTypes: true })
105
+ const results: string[] = []
106
+
107
+ if (await hasPackageJson(basePath)) {
108
+ results.push(basePath)
109
+ }
110
+
111
+ const subDirPromises = entries
112
+ .filter(
113
+ (entry) =>
114
+ entry.isDirectory() &&
115
+ !entry.name.startsWith('.') &&
116
+ entry.name !== 'node_modules'
117
+ )
118
+ .map(async (dir) => {
119
+ const path = join(basePath, dir.name)
120
+ const subdirResults = await findPackageJsonDirs(path, maxDepth - 1)
121
+ return subdirResults
122
+ })
123
+
124
+ const subdirResults = await Promise.all(subDirPromises)
125
+ return [...results, ...subdirResults.flat()]
126
+ } catch (error) {
127
+ log.error(`Error scanning directory ${basePath}: ${error}`)
128
+ return []
129
+ }
130
+ }
131
+
132
+ async function findWorkspaceDirectories(): Promise<string[]> {
133
+ const patterns = await getWorkspacePatterns()
134
+ if (!patterns.length) return []
135
+
136
+ const allPackageDirs = await findPackageJsonDirs('.')
137
+
138
+ const normalizePattern = (pattern: string): string => {
139
+ return pattern.startsWith('./') ? pattern.substring(2) : pattern
140
+ }
141
+
142
+ const workspaceDirs = allPackageDirs.filter((dir) => {
143
+ if (dir === '.') return false
144
+
145
+ const relativePath = relative('.', dir)
146
+ return patterns.some((pattern) => {
147
+ const normalizedPattern = normalizePattern(pattern)
148
+ const normalizedPath = normalizePattern(relativePath)
149
+
150
+ if (normalizedPattern.endsWith('/*')) {
151
+ const prefix = normalizedPattern.slice(0, -1)
152
+ return normalizedPath.startsWith(prefix)
153
+ }
154
+ return (
155
+ normalizedPath === normalizedPattern ||
156
+ normalizedPath.startsWith(normalizedPattern + '/')
157
+ )
158
+ })
159
+ })
160
+
161
+ return workspaceDirs
162
+ }
163
+
164
+ async function findAvailableScripts(
165
+ directoryPath: string,
166
+ scriptNames: string[]
167
+ ): Promise<string[]> {
168
+ const packageJson = await readPackageJson(directoryPath)
169
+
170
+ if (!packageJson || !packageJson.scripts) {
171
+ return []
172
+ }
173
+
174
+ return scriptNames.filter(
175
+ (scriptName) => typeof packageJson.scripts?.[scriptName] === 'string'
176
+ )
177
+ }
178
+
179
+ async function mapWorkspacesToScripts(
180
+ scriptNames: string[]
181
+ ): Promise<Map<string, { scripts: string[]; packageName: string }>> {
182
+ const workspaceDirs = await findWorkspaceDirectories()
183
+ const workspaceScriptMap = new Map<string, { scripts: string[]; packageName: string }>()
184
+
185
+ for (const dir of workspaceDirs) {
186
+ const availableScripts = await findAvailableScripts(dir, scriptNames)
187
+
188
+ if (availableScripts.length > 0) {
189
+ const packageJson = await readPackageJson(dir)
190
+ const packageName = packageJson?.name || dir
191
+ workspaceScriptMap.set(dir, {
192
+ scripts: availableScripts,
193
+ packageName,
194
+ })
195
+ }
196
+ }
197
+
198
+ return workspaceScriptMap
199
+ }
200
+
201
+ const runScript = async (
202
+ name: string,
203
+ cwd = '.',
204
+ prefixLabel: string = name,
205
+ restarts = 0
206
+ ) => {
207
+ const colorIndex = processes.length % colors.length
208
+ const color = colors[colorIndex]
209
+
210
+ // Capture stderr for error reporting
211
+ let stderrBuffer = ''
212
+
213
+ // Construct command with arguments to forward
214
+ const runArgs = ['run', runBun ? '--bun' : '', name, ...forwardArgs].filter(Boolean)
215
+
216
+ // Log the exact command being run
217
+ const commandDisplay = `bun ${runArgs.join(' ')}`
218
+
219
+ log.info(
220
+ `${color}${prefixLabel}${reset} Running: ${commandDisplay} (in ${resolve(cwd)})`
221
+ )
222
+
223
+ // Combine parent running scripts with current scripts to prevent recursion
224
+ const allRunningScripts = [...parentRunningScripts, ...runCommands].join(',')
225
+
226
+ const proc = spawn('bun', runArgs, {
227
+ stdio: ['pipe', 'pipe', 'pipe'],
228
+ shell: false,
229
+ env: {
230
+ ...process.env,
231
+ FORCE_COLOR: '3',
232
+ BUN_RUN_PARENT_SCRIPT: name,
233
+ BUN_RUN_SCRIPTS: allRunningScripts,
234
+ } as any,
235
+ cwd: resolve(cwd),
236
+ detached: true,
237
+ })
238
+
239
+ log.info(`${color}${prefixLabel}${reset} Process started with PID: ${proc.pid}`)
240
+
241
+ processes.push(proc)
242
+ addChildProcess(proc)
243
+
244
+ proc.stdout.on('data', (data) => {
245
+ if (getIsExiting()) return // prevent output during cleanup
246
+ const lines = data.toString().split('\n')
247
+ for (const line of lines) {
248
+ if (line) log.output(`${color}${prefixLabel}${reset} ${line}`)
249
+ }
250
+ })
251
+
252
+ proc.stderr.on('data', (data) => {
253
+ const dataStr = data.toString()
254
+ stderrBuffer += dataStr
255
+
256
+ if (getIsExiting()) return // prevent output during cleanup
257
+ const lines = dataStr.split('\n')
258
+ for (const line of lines) {
259
+ if (line) log.error(`${color}${prefixLabel}${reset} ${line}`)
260
+ }
261
+ })
262
+
263
+ proc.on('error', (error) => {
264
+ log.error(`${color}${prefixLabel}${reset} Failed to start: ${error.message}`)
265
+ })
266
+
267
+ proc.on('close', (code) => {
268
+ if (getIsExiting()) {
269
+ // silently exit during cleanup
270
+ return
271
+ }
272
+
273
+ if (code && code !== 0) {
274
+ log.error(`${color}${prefixLabel}${reset} Process exited with code ${code}`)
275
+
276
+ if (code === 1) {
277
+ // Print a nicer error message with red header
278
+ console.error(
279
+ '\n\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'
280
+ )
281
+ console.error('\x1b[31m❌ Run Failed\x1b[0m')
282
+ console.error(
283
+ '\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'
284
+ )
285
+ console.error(
286
+ `\x1b[31mProcess "${prefixLabel}" failed with exit code ${code}\x1b[0m`
287
+ )
288
+
289
+ // Show the original error output without prefixes
290
+ if (stderrBuffer.trim()) {
291
+ console.error('\n\x1b[31mError output:\x1b[0m')
292
+ console.error('\x1b[90m' + '─'.repeat(80) + '\x1b[0m')
293
+ const cleanedStderr = stderrBuffer
294
+ console.error(cleanedStderr)
295
+ console.error('\x1b[90m' + '─'.repeat(80) + '\x1b[0m')
296
+ }
297
+
298
+ console.error('')
299
+
300
+ if (watch && restarts < MAX_RESTARTS) {
301
+ const newRestarts = restarts + 1
302
+ console.info(
303
+ `Restarting process ${name} (${newRestarts}/${MAX_RESTARTS} times)`
304
+ )
305
+ runScript(name, cwd, prefixLabel, newRestarts)
306
+ } else {
307
+ exit(1)
308
+ }
309
+ }
310
+ }
311
+ })
312
+
313
+ return proc
314
+ }
315
+
316
+ async function main() {
317
+ checkNodeVersion().catch((err) => {
318
+ log.error(err.message)
319
+ exit(1)
320
+ })
321
+
322
+ try {
323
+ if (runCommands.length > 0) {
324
+ // Root package.json scripts first, if not disabled
325
+ if (!noRoot) {
326
+ const scriptPromises = runCommands
327
+ .filter((name) => !parentRunningScripts.includes(name))
328
+ .map((name) => runScript(name))
329
+
330
+ await Promise.all(scriptPromises)
331
+ }
332
+
333
+ const workspaceScriptMap = await mapWorkspacesToScripts(runCommands)
334
+
335
+ for (const [workspace, { scripts, packageName }] of workspaceScriptMap.entries()) {
336
+ const workspaceScriptPromises = scripts
337
+ .filter((scriptName) => !parentRunningScripts.includes(scriptName))
338
+ .map((scriptName) =>
339
+ runScript(scriptName, workspace, `[${packageName}] [${scriptName}]`)
340
+ )
341
+
342
+ await Promise.all(workspaceScriptPromises)
343
+ }
344
+ }
345
+
346
+ if (processes.length === 0) {
347
+ exit(0)
348
+ }
349
+ } catch (error) {
350
+ log.error(`Error running scripts: ${error}`)
351
+ exit(1)
352
+ }
353
+ }
354
+
355
+ main().catch((error) => {
356
+ log.error(`Error running scripts: ${error}`)
357
+ exit(1)
358
+ })