@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/LICENSE +21 -0
- package/package.json +27 -0
- package/src/bootstrap.ts +182 -0
- package/src/check-circular-deps.ts +113 -0
- package/src/clean.ts +15 -0
- package/src/dev-tunnel-if-exist.ts +166 -0
- package/src/dev-tunnel.ts +178 -0
- package/src/ensure-tunnel.ts +13 -0
- package/src/env-pull.ts +54 -0
- package/src/env-update.ts +126 -0
- package/src/exec-with-env.ts +57 -0
- package/src/helpers/check-port.ts +22 -0
- package/src/helpers/ensure-s3-bucket.ts +88 -0
- package/src/helpers/env-load.ts +26 -0
- package/src/helpers/get-docker-host.ts +37 -0
- package/src/helpers/get-test-env.ts +25 -0
- package/src/helpers/handleProcessExit.ts +254 -0
- package/src/helpers/run.ts +310 -0
- package/src/helpers/wait-for-port.ts +33 -0
- package/src/helpers/zero-get-version.ts +8 -0
- package/src/node-version-check.ts +49 -0
- package/src/release.ts +352 -0
- package/src/run.ts +358 -0
- package/src/sst-get-environment.ts +31 -0
- package/src/typescript.ts +16 -0
- package/src/update-deps.ts +336 -0
- package/src/wait-for-dev.ts +40 -0
- package/tsconfig.json +9 -0
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
|
+
})
|