@take-out/scripts 0.0.52 ā 0.0.58
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 +9 -6
- package/src/ensure-port.ts +5 -15
- package/src/helpers/args.ts +95 -0
- package/src/helpers/get-test-env.ts +30 -6
- package/src/helpers/run.ts +5 -3
- package/src/release.ts +25 -1
- package/src/run.ts +72 -47
- package/src/{update-deps.ts ā up.ts} +79 -18
- package/src/check-circular-deps.ts +0 -129
package/package.json
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@take-out/scripts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.58",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/run.ts",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./types/run.d.ts",
|
|
10
|
+
"default": "./src/run.ts"
|
|
11
|
+
},
|
|
8
12
|
"./package.json": "./package.json",
|
|
9
13
|
"./helpers/*": {
|
|
10
|
-
"types": "./
|
|
14
|
+
"types": "./types/helpers/*.d.ts",
|
|
11
15
|
"default": "./src/helpers/*.ts"
|
|
12
16
|
},
|
|
13
17
|
"./*": {
|
|
14
|
-
"types": "./
|
|
18
|
+
"types": "./types/*.d.ts",
|
|
15
19
|
"default": "./src/*.ts"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
@@ -24,10 +28,9 @@
|
|
|
24
28
|
"access": "public"
|
|
25
29
|
},
|
|
26
30
|
"dependencies": {
|
|
27
|
-
"@take-out/helpers": "0.0.
|
|
28
|
-
"glob": "^11.0.0"
|
|
31
|
+
"@take-out/helpers": "0.0.58"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
|
-
"vxrn": "*"
|
|
34
|
+
"vxrn": "*1.4.0"
|
|
32
35
|
}
|
|
33
36
|
}
|
package/src/ensure-port.ts
CHANGED
|
@@ -6,20 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { execSync } from 'node:child_process'
|
|
9
|
-
import { parseArgs } from 'node:util'
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
args: process.argv.slice(2),
|
|
13
|
-
options: {
|
|
14
|
-
'auto-kill': {
|
|
15
|
-
type: 'string',
|
|
16
|
-
short: 'k',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
allowPositionals: true,
|
|
20
|
-
})
|
|
10
|
+
import { args } from './helpers/args'
|
|
21
11
|
|
|
22
|
-
const
|
|
12
|
+
const { autoKill, _ } = args('--auto-kill string')
|
|
13
|
+
|
|
14
|
+
const port = _[0]
|
|
23
15
|
|
|
24
16
|
if (!port) {
|
|
25
17
|
console.error('usage: bun tko ensure-port <port> [--auto-kill <prefix>]')
|
|
@@ -71,10 +63,8 @@ function killProcess(pid: number): boolean {
|
|
|
71
63
|
const { pid, command } = getListeningProcess(portNum)
|
|
72
64
|
|
|
73
65
|
if (pid) {
|
|
74
|
-
const autoKillPrefix = values['auto-kill']
|
|
75
|
-
|
|
76
66
|
// check if we should auto-kill this process
|
|
77
|
-
if (
|
|
67
|
+
if (autoKill && command?.startsWith(autoKill)) {
|
|
78
68
|
console.info(`killing ${command} (pid ${pid}) on port ${portNum}`)
|
|
79
69
|
if (killProcess(pid)) {
|
|
80
70
|
// give it a moment to release the port
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ultra simple typed arg parsing
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const { port, verbose, _ } = args`--port number --verbose boolean`
|
|
6
|
+
* // port: number | undefined
|
|
7
|
+
* // verbose: boolean
|
|
8
|
+
* // _: string[]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type ParseType<T extends string> = T extends 'number'
|
|
12
|
+
? number | undefined
|
|
13
|
+
: T extends 'boolean'
|
|
14
|
+
? boolean
|
|
15
|
+
: T extends 'string'
|
|
16
|
+
? string | undefined
|
|
17
|
+
: never
|
|
18
|
+
|
|
19
|
+
type ParseFlag<S extends string> = S extends `--${infer Name} ${infer Type}`
|
|
20
|
+
? { [K in Name as KebabToCamel<K>]: ParseType<Type> }
|
|
21
|
+
: S extends `-${infer Name} ${infer Type}`
|
|
22
|
+
? { [K in Name as KebabToCamel<K>]: ParseType<Type> }
|
|
23
|
+
: object
|
|
24
|
+
|
|
25
|
+
type KebabToCamel<S extends string> = S extends `${infer A}-${infer B}`
|
|
26
|
+
? `${A}${Capitalize<KebabToCamel<B>>}`
|
|
27
|
+
: S
|
|
28
|
+
|
|
29
|
+
// trim leading/trailing whitespace from string type
|
|
30
|
+
type Trim<S extends string> = S extends ` ${infer R}`
|
|
31
|
+
? Trim<R>
|
|
32
|
+
: S extends `${infer R} `
|
|
33
|
+
? Trim<R>
|
|
34
|
+
: S extends `\n${infer R}`
|
|
35
|
+
? Trim<R>
|
|
36
|
+
: S extends `${infer R}\n`
|
|
37
|
+
? Trim<R>
|
|
38
|
+
: S
|
|
39
|
+
|
|
40
|
+
// split on -- allowing any whitespace between flags
|
|
41
|
+
type ParseFlags<S extends string> =
|
|
42
|
+
Trim<S> extends `${infer Flag} --${infer Rest}`
|
|
43
|
+
? ParseFlag<Trim<Flag>> & ParseFlags<`--${Rest}`>
|
|
44
|
+
: Trim<S> extends `${infer Flag}\n--${infer Rest}`
|
|
45
|
+
? ParseFlag<Trim<Flag>> & ParseFlags<`--${Rest}`>
|
|
46
|
+
: Trim<S> extends `${infer Flag} -${infer Rest}`
|
|
47
|
+
? ParseFlag<Trim<Flag>> & ParseFlags<`-${Rest}`>
|
|
48
|
+
: Trim<S> extends `${infer Flag}\n-${infer Rest}`
|
|
49
|
+
? ParseFlag<Trim<Flag>> & ParseFlags<`-${Rest}`>
|
|
50
|
+
: ParseFlag<Trim<S>>
|
|
51
|
+
|
|
52
|
+
// flatten intersection into single object for nice hover display
|
|
53
|
+
type Prettify<T> = { [K in keyof T]: T[K] } & {}
|
|
54
|
+
|
|
55
|
+
type Args<S extends string> = Prettify<ParseFlags<S> & { _: string[] }>
|
|
56
|
+
|
|
57
|
+
export function args<const S extends string>(spec: S): Args<S>
|
|
58
|
+
export function args<const S extends string>(strings: TemplateStringsArray | S): Args<S> {
|
|
59
|
+
const spec = typeof strings === 'string' ? strings : strings[0] || ''
|
|
60
|
+
const argv = process.argv.slice(2)
|
|
61
|
+
|
|
62
|
+
// parse spec: "--port number --verbose boolean"
|
|
63
|
+
const schema: Record<string, 'string' | 'number' | 'boolean'> = {}
|
|
64
|
+
const matches = spec.matchAll(/--?([a-z-]+)\s+(string|number|boolean)/gi)
|
|
65
|
+
|
|
66
|
+
for (const [, flag, type] of matches) {
|
|
67
|
+
const camel = flag!.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
68
|
+
schema[camel] = type as 'string' | 'number' | 'boolean'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result: Record<string, string | number | boolean | undefined> = {}
|
|
72
|
+
const rest: string[] = []
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < argv.length; i++) {
|
|
75
|
+
const arg = argv[i]!
|
|
76
|
+
|
|
77
|
+
if (arg.startsWith('-')) {
|
|
78
|
+
const key = arg.replace(/^--?/, '').replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
79
|
+
const type = schema[key]
|
|
80
|
+
|
|
81
|
+
if (type === 'boolean') {
|
|
82
|
+
result[key] = true
|
|
83
|
+
} else if (type === 'string' || type === 'number') {
|
|
84
|
+
const val = argv[++i]
|
|
85
|
+
result[key] = type === 'number' ? Number(val) : val
|
|
86
|
+
} else {
|
|
87
|
+
rest.push(arg)
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
rest.push(arg)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { ...result, _: rest } as Args<S>
|
|
95
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
1
2
|
import { join } from 'node:path'
|
|
2
3
|
|
|
3
4
|
import { loadEnv } from './env-load'
|
|
@@ -16,23 +17,44 @@ function getPortFromConnectionString(url: string | undefined): string | undefine
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
// read a specific env var directly from .env.development file
|
|
21
|
+
// (process.env may have production values if NODE_ENV=production)
|
|
22
|
+
function getDevEnvVar(key: string): string | undefined {
|
|
23
|
+
try {
|
|
24
|
+
const envPath = join(process.cwd(), '.env.development')
|
|
25
|
+
const content = readFileSync(envPath, 'utf-8')
|
|
26
|
+
const regex = new RegExp(`^${key}=["']?([^"'\\n]+)["']?`, 'm')
|
|
27
|
+
const match = content.match(regex)
|
|
28
|
+
return match?.[1]
|
|
29
|
+
} catch {
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getDevDbPort(): string | undefined {
|
|
35
|
+
const url = getDevEnvVar('ZERO_UPSTREAM_DB')
|
|
36
|
+
return url ? getPortFromConnectionString(url) : undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
export async function getTestEnv() {
|
|
20
40
|
const zeroVersion = getZeroVersion()
|
|
21
|
-
const devEnv = await loadEnv('development')
|
|
22
41
|
const dockerHost = getDockerHost()
|
|
42
|
+
const devEnv = await loadEnv('development')
|
|
23
43
|
const serverEnvFallback = await import(join(process.cwd(), 'src/server/env-server'))
|
|
24
44
|
|
|
25
45
|
// determine db port from (in order of priority):
|
|
26
46
|
// 1. DOCKER_DB_PORT env var
|
|
27
|
-
// 2. port from ZERO_UPSTREAM_DB in
|
|
47
|
+
// 2. port from ZERO_UPSTREAM_DB in .env.development file (read directly to avoid
|
|
48
|
+
// bun auto-loading .env.production when NODE_ENV=production)
|
|
28
49
|
// 3. default 5432
|
|
29
|
-
const dbPort =
|
|
30
|
-
process.env.DOCKER_DB_PORT ||
|
|
31
|
-
getPortFromConnectionString(devEnv.ZERO_UPSTREAM_DB as string) ||
|
|
32
|
-
'5432'
|
|
50
|
+
const dbPort = process.env.DOCKER_DB_PORT || getDevDbPort() || '5432'
|
|
33
51
|
|
|
34
52
|
const dockerDbBase = `postgresql://user:password@127.0.0.1:${dbPort}`
|
|
35
53
|
|
|
54
|
+
// use dev BETTER_AUTH_SECRET for CI/staging to match local dev database keys
|
|
55
|
+
// (better-auth encrypts JWKS private keys with this secret, so it must match)
|
|
56
|
+
const devAuthSecret = getDevEnvVar('BETTER_AUTH_SECRET')
|
|
57
|
+
|
|
36
58
|
return {
|
|
37
59
|
...serverEnvFallback,
|
|
38
60
|
...devEnv,
|
|
@@ -51,5 +73,7 @@ export async function getTestEnv() {
|
|
|
51
73
|
CLOUDFLARE_R2_PUBLIC_URL: 'http://127.0.0.1:9200',
|
|
52
74
|
CLOUDFLARE_R2_ACCESS_KEY: 'minio',
|
|
53
75
|
CLOUDFLARE_R2_SECRET_KEY: 'minio_password',
|
|
76
|
+
// ensure auth secret matches dev db keys
|
|
77
|
+
...(devAuthSecret && { BETTER_AUTH_SECRET: devAuthSecret }),
|
|
54
78
|
}
|
|
55
79
|
}
|
package/src/helpers/run.ts
CHANGED
|
@@ -84,7 +84,9 @@ export async function run(
|
|
|
84
84
|
return runInternal()
|
|
85
85
|
|
|
86
86
|
async function runInternal() {
|
|
87
|
-
|
|
87
|
+
// respect TKO_SILENT env var for quiet mode in watch scenarios
|
|
88
|
+
const effectiveSilent = silent || process.env.TKO_SILENT === '1'
|
|
89
|
+
if (!effectiveSilent) {
|
|
88
90
|
console.info(`$ ${command}${cwd ? ` (in ${cwd})` : ``}`)
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -117,7 +119,7 @@ export async function run(
|
|
|
117
119
|
const coloredPrefix = prefix ? `${color}[${prefix}]${reset}` : ''
|
|
118
120
|
|
|
119
121
|
const writeOutput = (text: string, isStderr: boolean) => {
|
|
120
|
-
if (!
|
|
122
|
+
if (!effectiveSilent) {
|
|
121
123
|
const output = prefix ? `${coloredPrefix} ${text}` : text
|
|
122
124
|
if (!prefix || !captureOutput) {
|
|
123
125
|
const stream = isStderr ? process.stderr : process.stdout
|
|
@@ -130,7 +132,7 @@ export async function run(
|
|
|
130
132
|
stream: ReadableStream<Uint8Array> | undefined,
|
|
131
133
|
isStderr: boolean
|
|
132
134
|
): Promise<string> => {
|
|
133
|
-
if (
|
|
135
|
+
if (effectiveSilent && !captureOutput) {
|
|
134
136
|
return ''
|
|
135
137
|
}
|
|
136
138
|
|
package/src/release.ts
CHANGED
|
@@ -127,7 +127,8 @@ async function main() {
|
|
|
127
127
|
|
|
128
128
|
if (!skipTest) {
|
|
129
129
|
await run(`bun lint`)
|
|
130
|
-
await run(`bun check`)
|
|
130
|
+
await run(`bun check:all`)
|
|
131
|
+
// only in packages
|
|
131
132
|
// await run(`bun test`)
|
|
132
133
|
}
|
|
133
134
|
}
|
|
@@ -180,6 +181,8 @@ async function main() {
|
|
|
180
181
|
|
|
181
182
|
if (!finish) {
|
|
182
183
|
const tmpDir = `/tmp/one-publish`
|
|
184
|
+
// clean up from previous runs
|
|
185
|
+
await fs.remove(tmpDir)
|
|
183
186
|
await ensureDir(tmpDir)
|
|
184
187
|
|
|
185
188
|
await pMap(
|
|
@@ -216,6 +219,27 @@ async function main() {
|
|
|
216
219
|
|
|
217
220
|
const filename = `${name.replace('/', '_')}-package.tmp.tgz`
|
|
218
221
|
const absolutePath = `${tmpDir}/${filename}`
|
|
222
|
+
|
|
223
|
+
// swap exports.types from ./src/*.ts to ./types/*.d.ts for publishing
|
|
224
|
+
if (pkgJson.exports) {
|
|
225
|
+
const swapTypes = (obj: any) => {
|
|
226
|
+
for (const key in obj) {
|
|
227
|
+
const val = obj[key]
|
|
228
|
+
if (typeof val === 'object' && val !== null) {
|
|
229
|
+
swapTypes(val)
|
|
230
|
+
} else if (
|
|
231
|
+
key === 'types' &&
|
|
232
|
+
typeof val === 'string' &&
|
|
233
|
+
val.includes('/src/')
|
|
234
|
+
) {
|
|
235
|
+
obj[key] = val.replace('/src/', '/types/').replace('.ts', '.d.ts')
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
swapTypes(pkgJson.exports)
|
|
240
|
+
await writeJSON(pkgJsonPath, pkgJson, { spaces: 2 })
|
|
241
|
+
}
|
|
242
|
+
|
|
219
243
|
await run(`npm pack --pack-destination ${tmpDir}`, {
|
|
220
244
|
cwd: tmpPackageDir,
|
|
221
245
|
silent: true,
|
package/src/run.ts
CHANGED
|
@@ -13,18 +13,22 @@ import { handleProcessExit } from '@take-out/scripts/helpers/handleProcessExit'
|
|
|
13
13
|
import { getIsExiting } from './helpers/run'
|
|
14
14
|
import { checkNodeVersion } from './node-version-check'
|
|
15
15
|
|
|
16
|
+
// 256-color grays for subtle differentiation (232=darkest, 255=lightest)
|
|
16
17
|
const colors = [
|
|
17
|
-
'\x1b[
|
|
18
|
-
'\x1b[
|
|
19
|
-
'\x1b[
|
|
20
|
-
'\x1b[
|
|
21
|
-
'\x1b[
|
|
22
|
-
'\x1b[
|
|
23
|
-
'\x1b[
|
|
18
|
+
'\x1b[38;5;245m', // medium gray
|
|
19
|
+
'\x1b[38;5;240m', // darker gray
|
|
20
|
+
'\x1b[38;5;250m', // lighter gray
|
|
21
|
+
'\x1b[38;5;243m', // medium-dark gray
|
|
22
|
+
'\x1b[38;5;248m', // medium-light gray
|
|
23
|
+
'\x1b[38;5;238m', // dark gray
|
|
24
|
+
'\x1b[38;5;252m', // light gray
|
|
24
25
|
]
|
|
25
26
|
|
|
26
27
|
const reset = '\x1b[0m'
|
|
27
28
|
|
|
29
|
+
// eslint-disable-next-line no-control-regex
|
|
30
|
+
const ansiPattern = /\x1b\[[0-9;]*m/g
|
|
31
|
+
|
|
28
32
|
// Verbose logging flag - set to false to reduce logs
|
|
29
33
|
const verbose = false
|
|
30
34
|
|
|
@@ -40,11 +44,38 @@ const log = {
|
|
|
40
44
|
const MAX_RESTARTS = 3
|
|
41
45
|
|
|
42
46
|
// Separate command names from flags/arguments
|
|
47
|
+
// Handles --flag=value and --flag value styles, excluding flag values from commands
|
|
43
48
|
const args = process.argv.slice(2)
|
|
44
|
-
const
|
|
49
|
+
const ownFlags = ['--no-root', '--bun', '--watch', '--flags=last']
|
|
50
|
+
const runCommands: string[] = []
|
|
51
|
+
const forwardArgs: string[] = []
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < args.length; i++) {
|
|
54
|
+
const arg = args[i]!
|
|
55
|
+
|
|
56
|
+
if (arg.startsWith('--')) {
|
|
57
|
+
// handle flags
|
|
58
|
+
if (ownFlags.includes(arg) || arg.startsWith('--stdin=')) {
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
forwardArgs.push(arg)
|
|
62
|
+
// if next arg exists and doesn't start with --, treat it as this flag's value
|
|
63
|
+
const nextArg = args[i + 1]
|
|
64
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
65
|
+
forwardArgs.push(nextArg)
|
|
66
|
+
i++ // skip the value in next iteration
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
// non-flag arg is a command name
|
|
70
|
+
runCommands.push(arg)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
45
74
|
const noRoot = args.includes('--no-root')
|
|
46
75
|
const runBun = args.includes('--bun')
|
|
47
76
|
const watch = args.includes('--watch') // just attempts to restart a failed process up to MAX_RESTARTS times
|
|
77
|
+
// --flags=last forwards args only to last script, default forwards to all
|
|
78
|
+
const flagsLast = args.includes('--flags=last')
|
|
48
79
|
|
|
49
80
|
// parse --stdin=<script-name> to specify which script receives keyboard input
|
|
50
81
|
// if not specified, defaults to the last script in the list
|
|
@@ -53,16 +84,6 @@ const stdinScript = stdinArg
|
|
|
53
84
|
? stdinArg.replace('--stdin=', '')
|
|
54
85
|
: (runCommands[runCommands.length - 1] ?? null)
|
|
55
86
|
|
|
56
|
-
// Collect additional flags and arguments to forward to sub-commands
|
|
57
|
-
const forwardArgs = args.filter(
|
|
58
|
-
(arg) =>
|
|
59
|
-
arg.startsWith('--') &&
|
|
60
|
-
arg !== '--no-root' &&
|
|
61
|
-
arg !== '--bun' &&
|
|
62
|
-
arg !== '--watch' &&
|
|
63
|
-
!arg.startsWith('--stdin=')
|
|
64
|
-
)
|
|
65
|
-
|
|
66
87
|
// Get the list of scripts already being run by a parent process
|
|
67
88
|
const parentRunningScripts = process.env.BUN_RUN_SCRIPTS
|
|
68
89
|
? process.env.BUN_RUN_SCRIPTS.split(',')
|
|
@@ -217,7 +238,8 @@ const runScript = async (
|
|
|
217
238
|
name: string,
|
|
218
239
|
cwd = '.',
|
|
219
240
|
prefixLabel: string = name,
|
|
220
|
-
restarts = 0
|
|
241
|
+
restarts = 0,
|
|
242
|
+
extraArgs: string[] = []
|
|
221
243
|
) => {
|
|
222
244
|
const colorIndex = processes.length % colors.length
|
|
223
245
|
const color = colors[colorIndex]
|
|
@@ -226,7 +248,10 @@ const runScript = async (
|
|
|
226
248
|
let stderrBuffer = ''
|
|
227
249
|
|
|
228
250
|
// Construct command with arguments to forward
|
|
229
|
-
|
|
251
|
+
// --silent suppresses bun's "$ command" output
|
|
252
|
+
const runArgs = ['run', '--silent', runBun ? '--bun' : '', name, ...extraArgs].filter(
|
|
253
|
+
Boolean
|
|
254
|
+
)
|
|
230
255
|
|
|
231
256
|
// Log the exact command being run
|
|
232
257
|
const commandDisplay = `bun ${runArgs.join(' ')}`
|
|
@@ -247,6 +272,8 @@ const runScript = async (
|
|
|
247
272
|
FORCE_COLOR: '3',
|
|
248
273
|
BUN_RUN_PARENT_SCRIPT: name,
|
|
249
274
|
BUN_RUN_SCRIPTS: allRunningScripts,
|
|
275
|
+
// propagate silent mode to child scripts
|
|
276
|
+
TKO_SILENT: '1',
|
|
250
277
|
} as any,
|
|
251
278
|
cwd: resolve(cwd),
|
|
252
279
|
detached: true,
|
|
@@ -261,6 +288,9 @@ const runScript = async (
|
|
|
261
288
|
if (getIsExiting()) return // prevent output during cleanup
|
|
262
289
|
const lines = data.toString().split('\n')
|
|
263
290
|
for (const line of lines) {
|
|
291
|
+
// filter out bun's "$ command" echo lines in nested scripts
|
|
292
|
+
const stripped = line.replace(ansiPattern, '')
|
|
293
|
+
if (stripped.startsWith('$ ')) continue
|
|
264
294
|
if (line) log.output(`${color}${prefixLabel}${reset} ${line}`)
|
|
265
295
|
}
|
|
266
296
|
})
|
|
@@ -272,6 +302,9 @@ const runScript = async (
|
|
|
272
302
|
if (getIsExiting()) return // prevent output during cleanup
|
|
273
303
|
const lines = dataStr.split('\n')
|
|
274
304
|
for (const line of lines) {
|
|
305
|
+
// filter out bun's "$ command" echo lines in nested scripts
|
|
306
|
+
const stripped = line.replace(ansiPattern, '')
|
|
307
|
+
if (stripped.startsWith('$ ')) continue
|
|
275
308
|
if (line) log.error(`${color}${prefixLabel}${reset} ${line}`)
|
|
276
309
|
}
|
|
277
310
|
})
|
|
@@ -290,35 +323,17 @@ const runScript = async (
|
|
|
290
323
|
log.error(`${color}${prefixLabel}${reset} Process exited with code ${code}`)
|
|
291
324
|
|
|
292
325
|
if (code === 1) {
|
|
293
|
-
// Print a nicer error message with red header
|
|
294
|
-
console.error(
|
|
295
|
-
'\n\x1b[31māāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\x1b[0m'
|
|
296
|
-
)
|
|
297
326
|
console.error('\x1b[31mā Run Failed\x1b[0m')
|
|
298
|
-
console.error(
|
|
299
|
-
'\x1b[31māāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\x1b[0m'
|
|
300
|
-
)
|
|
301
327
|
console.error(
|
|
302
328
|
`\x1b[31mProcess "${prefixLabel}" failed with exit code ${code}\x1b[0m`
|
|
303
329
|
)
|
|
304
330
|
|
|
305
|
-
// Show the original error output without prefixes
|
|
306
|
-
if (stderrBuffer.trim()) {
|
|
307
|
-
console.error('\n\x1b[31mError output:\x1b[0m')
|
|
308
|
-
console.error('\x1b[90m' + 'ā'.repeat(80) + '\x1b[0m')
|
|
309
|
-
const cleanedStderr = stderrBuffer
|
|
310
|
-
console.error(cleanedStderr)
|
|
311
|
-
console.error('\x1b[90m' + 'ā'.repeat(80) + '\x1b[0m')
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
console.error('')
|
|
315
|
-
|
|
316
331
|
if (watch && restarts < MAX_RESTARTS) {
|
|
317
332
|
const newRestarts = restarts + 1
|
|
318
333
|
console.info(
|
|
319
334
|
`Restarting process ${name} (${newRestarts}/${MAX_RESTARTS} times)`
|
|
320
335
|
)
|
|
321
|
-
runScript(name, cwd, prefixLabel, newRestarts)
|
|
336
|
+
runScript(name, cwd, prefixLabel, newRestarts, extraArgs)
|
|
322
337
|
} else {
|
|
323
338
|
exit(1)
|
|
324
339
|
}
|
|
@@ -337,11 +352,18 @@ async function main() {
|
|
|
337
352
|
|
|
338
353
|
try {
|
|
339
354
|
if (runCommands.length > 0) {
|
|
355
|
+
const lastScript = runCommands[runCommands.length - 1]
|
|
356
|
+
|
|
340
357
|
// Root package.json scripts first, if not disabled
|
|
341
358
|
if (!noRoot) {
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
359
|
+
const filteredCommands = runCommands.filter(
|
|
360
|
+
(name) => !parentRunningScripts.includes(name)
|
|
361
|
+
)
|
|
362
|
+
const scriptPromises = filteredCommands.map((name) => {
|
|
363
|
+
// --flags=last: only forward args to last script
|
|
364
|
+
const args = !flagsLast || name === lastScript ? forwardArgs : []
|
|
365
|
+
return runScript(name, '.', name, 0, args)
|
|
366
|
+
})
|
|
345
367
|
|
|
346
368
|
await Promise.all(scriptPromises)
|
|
347
369
|
}
|
|
@@ -349,11 +371,14 @@ async function main() {
|
|
|
349
371
|
const workspaceScriptMap = await mapWorkspacesToScripts(runCommands)
|
|
350
372
|
|
|
351
373
|
for (const [workspace, { scripts, packageName }] of workspaceScriptMap.entries()) {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
374
|
+
const filteredScripts = scripts.filter(
|
|
375
|
+
(scriptName) => !parentRunningScripts.includes(scriptName)
|
|
376
|
+
)
|
|
377
|
+
const workspaceScriptPromises = filteredScripts.map((scriptName) => {
|
|
378
|
+
// --flags=last: only forward args to last script
|
|
379
|
+
const args = !flagsLast || scriptName === lastScript ? forwardArgs : []
|
|
380
|
+
return runScript(scriptName, workspace, `${packageName} ${scriptName}`, 0, args)
|
|
381
|
+
})
|
|
357
382
|
|
|
358
383
|
await Promise.all(workspaceScriptPromises)
|
|
359
384
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @description
|
|
4
|
+
* @description Upgrade packages by name (takeout, tamagui, one, zero, better-auth)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
@@ -14,7 +14,13 @@ const packagePatterns: string[] = []
|
|
|
14
14
|
|
|
15
15
|
const args = process.argv.slice(2)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// check if first arg is a named upgrade set
|
|
18
|
+
const rootDir = process.cwd()
|
|
19
|
+
const rootPackageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8'))
|
|
20
|
+
const upgradeSets: Record<string, string[]> = rootPackageJson.upgradeSets || {}
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < args.length; i++) {
|
|
23
|
+
const arg = args[i]
|
|
18
24
|
if (arg.startsWith('--tag=')) {
|
|
19
25
|
const tagValue = arg.split('=')[1]
|
|
20
26
|
if (tagValue) {
|
|
@@ -26,21 +32,33 @@ for (const arg of args) {
|
|
|
26
32
|
} else if (arg === '--tag') {
|
|
27
33
|
console.error('Error: --tag option requires a value and must use = syntax.')
|
|
28
34
|
console.error('Correct usage: --tag=canary')
|
|
29
|
-
console.error('Example: bun
|
|
35
|
+
console.error('Example: bun tko up --tag=canary react react-dom')
|
|
30
36
|
process.exit(1)
|
|
31
37
|
} else if (arg === '--canary') {
|
|
32
38
|
globalTag = 'canary'
|
|
33
39
|
} else if (arg === '--rc') {
|
|
34
40
|
globalTag = 'rc'
|
|
41
|
+
} else if (arg in upgradeSets) {
|
|
42
|
+
// expand named upgrade set to its patterns
|
|
43
|
+
packagePatterns.push(...upgradeSets[arg])
|
|
35
44
|
} else {
|
|
36
45
|
packagePatterns.push(arg)
|
|
37
46
|
}
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
if (packagePatterns.length === 0) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
const setNames = Object.keys(upgradeSets)
|
|
51
|
+
if (setNames.length > 0) {
|
|
52
|
+
console.info('Usage: bun tko up <target|pattern> [options]')
|
|
53
|
+
console.info(`\nAvailable upgrade sets: ${setNames.join(', ')}`)
|
|
54
|
+
console.info('\nOr provide package patterns directly:')
|
|
55
|
+
console.info(' bun tko up @vxrn/* vxrn')
|
|
56
|
+
console.info(' bun tko up --tag=canary react react-dom')
|
|
57
|
+
} else {
|
|
58
|
+
console.error('Please provide at least one package pattern to update.')
|
|
59
|
+
console.error('Example: bun tko up @vxrn/* vxrn')
|
|
60
|
+
console.error('Or with a tag: bun tko up --tag=canary @vxrn/* vxrn')
|
|
61
|
+
}
|
|
44
62
|
process.exit(1)
|
|
45
63
|
}
|
|
46
64
|
|
|
@@ -58,7 +76,7 @@ function findPackageJsonFiles(dir: string): string[] {
|
|
|
58
76
|
results.push(join(dir, 'package.json'))
|
|
59
77
|
}
|
|
60
78
|
|
|
61
|
-
//
|
|
79
|
+
// check if it's a monorepo with workspaces
|
|
62
80
|
try {
|
|
63
81
|
const packageJson = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'))
|
|
64
82
|
if (packageJson.workspaces) {
|
|
@@ -71,22 +89,60 @@ function findPackageJsonFiles(dir: string): string[] {
|
|
|
71
89
|
}
|
|
72
90
|
|
|
73
91
|
for (const workspace of workspacePaths) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
// handle glob patterns like "packages/*", "code/**/*", "./code/ui/**/*"
|
|
93
|
+
const normalizedWorkspace = workspace.replace(/^\.\//, '')
|
|
94
|
+
|
|
95
|
+
if (normalizedWorkspace.includes('**')) {
|
|
96
|
+
// nested glob pattern - use glob to find all package.json files
|
|
97
|
+
const baseDir = normalizedWorkspace.split('**')[0].replace(/\/$/, '')
|
|
98
|
+
const basePath = join(dir, baseDir)
|
|
99
|
+
|
|
100
|
+
if (existsSync(basePath)) {
|
|
101
|
+
const findPackages = (searchDir: string) => {
|
|
102
|
+
try {
|
|
103
|
+
const entries = readdirSync(searchDir, { withFileTypes: true })
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
if (entry.isDirectory()) {
|
|
106
|
+
const subPath = join(searchDir, entry.name)
|
|
107
|
+
const pkgPath = join(subPath, 'package.json')
|
|
108
|
+
if (existsSync(pkgPath)) {
|
|
109
|
+
results.push(pkgPath)
|
|
110
|
+
}
|
|
111
|
+
// recurse into subdirectories
|
|
112
|
+
findPackages(subPath)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (_e) {
|
|
116
|
+
// ignore permission errors
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
findPackages(basePath)
|
|
120
|
+
}
|
|
121
|
+
} else if (normalizedWorkspace.includes('*')) {
|
|
122
|
+
// simple glob pattern like "packages/*"
|
|
123
|
+
const workspaceDir = normalizedWorkspace.replace(/\/\*$/, '')
|
|
124
|
+
if (existsSync(join(dir, workspaceDir))) {
|
|
125
|
+
const subdirs = readdirSync(join(dir, workspaceDir), { withFileTypes: true })
|
|
126
|
+
.filter((dirent) => dirent.isDirectory())
|
|
127
|
+
.map((dirent) => join(dir, workspaceDir, dirent.name))
|
|
128
|
+
|
|
129
|
+
for (const subdir of subdirs) {
|
|
130
|
+
if (existsSync(join(subdir, 'package.json'))) {
|
|
131
|
+
results.push(join(subdir, 'package.json'))
|
|
132
|
+
}
|
|
83
133
|
}
|
|
84
134
|
}
|
|
135
|
+
} else {
|
|
136
|
+
// exact path like "code/tamagui.dev" or "./code/sandbox"
|
|
137
|
+
const pkgPath = join(dir, normalizedWorkspace, 'package.json')
|
|
138
|
+
if (existsSync(pkgPath)) {
|
|
139
|
+
results.push(pkgPath)
|
|
140
|
+
}
|
|
85
141
|
}
|
|
86
142
|
}
|
|
87
143
|
}
|
|
88
144
|
} catch (_error) {
|
|
89
|
-
//
|
|
145
|
+
// ignore errors parsing package.json
|
|
90
146
|
}
|
|
91
147
|
|
|
92
148
|
return results
|
|
@@ -258,7 +314,6 @@ function getWorkspaceName(packageJsonPath: string, rootDir: string): string {
|
|
|
258
314
|
}
|
|
259
315
|
|
|
260
316
|
async function main() {
|
|
261
|
-
const rootDir = process.cwd()
|
|
262
317
|
const packageJsonFiles = findPackageJsonFiles(rootDir)
|
|
263
318
|
console.info(`Found ${packageJsonFiles.length} package.json files`)
|
|
264
319
|
|
|
@@ -326,6 +381,12 @@ async function main() {
|
|
|
326
381
|
|
|
327
382
|
await updatePackages(packagesByWorkspace, rootDir, packageJsonFiles)
|
|
328
383
|
|
|
384
|
+
// special handling for zero - update ZERO_VERSION in .env
|
|
385
|
+
if (packagePatterns.includes('@rocicorp/zero')) {
|
|
386
|
+
console.info('\nš Updating local env for Zero...')
|
|
387
|
+
await $`bun tko run update-local-env`
|
|
388
|
+
}
|
|
389
|
+
|
|
329
390
|
console.info('\nš Dependency update complete!')
|
|
330
391
|
}
|
|
331
392
|
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { basename } from 'node:path'
|
|
4
|
-
|
|
5
|
-
import { $ } from 'bun'
|
|
6
|
-
import { globSync } from 'glob'
|
|
7
|
-
|
|
8
|
-
const projectRoot = process.cwd()
|
|
9
|
-
|
|
10
|
-
// parse --ignore flag
|
|
11
|
-
const args = process.argv.slice(2)
|
|
12
|
-
const ignoreIndex = args.indexOf('--ignore')
|
|
13
|
-
const ignoredFiles: string[] = []
|
|
14
|
-
|
|
15
|
-
if (ignoreIndex !== -1 && args[ignoreIndex + 1]) {
|
|
16
|
-
// split comma-separated files
|
|
17
|
-
const ignoreArg = args[ignoreIndex + 1]
|
|
18
|
-
if (ignoreArg) {
|
|
19
|
-
const ignoreList = ignoreArg.split(',')
|
|
20
|
-
ignoredFiles.push(...ignoreList.map((f) => f.trim()))
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// glob all tsx files in app directory
|
|
25
|
-
const appFiles = globSync('app/**/*.tsx', {
|
|
26
|
-
cwd: projectRoot,
|
|
27
|
-
absolute: true,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
// glob all tsx files in src directory
|
|
31
|
-
const srcFiles = globSync('src/**/*.tsx', {
|
|
32
|
-
cwd: projectRoot,
|
|
33
|
-
absolute: true,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
// combine all files
|
|
37
|
-
const allFiles = [...appFiles, ...srcFiles]
|
|
38
|
-
|
|
39
|
-
if (allFiles.length === 0) {
|
|
40
|
-
console.error('No tsx files found')
|
|
41
|
-
process.exit(1)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// build the command with all -r flags
|
|
45
|
-
const commandArgs = [
|
|
46
|
-
'bunx',
|
|
47
|
-
'@glideapps/ts-helper',
|
|
48
|
-
'-c', // check circular dependencies
|
|
49
|
-
'-p',
|
|
50
|
-
projectRoot,
|
|
51
|
-
...allFiles.flatMap((file) => ['-r', file]),
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
console.info(`Checking circular dependencies for ${allFiles.length} files...`)
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
// run the command and capture output
|
|
58
|
-
await $`${commandArgs}`.quiet()
|
|
59
|
-
console.info('ā
No circular dependencies found')
|
|
60
|
-
} catch (error: any) {
|
|
61
|
-
// parse the output to check for cycles
|
|
62
|
-
const output = error.stderr?.toString() || error.stdout?.toString() || ''
|
|
63
|
-
|
|
64
|
-
// check if this is a tool crash (assertion error) rather than actual cycle detection
|
|
65
|
-
if (output.includes('Assertion failed') || output.includes('at panic')) {
|
|
66
|
-
const nodeVersion = process.versions.node
|
|
67
|
-
const majorVersion = parseInt(nodeVersion.split('.')[0] || '0', 10)
|
|
68
|
-
if (majorVersion >= 24) {
|
|
69
|
-
console.warn(`ā ļø @glideapps/ts-helper crashes on Node.js ${nodeVersion}`)
|
|
70
|
-
console.warn(' skipping locally - CI uses Node 20 where this check runs properly')
|
|
71
|
-
process.exit(0)
|
|
72
|
-
}
|
|
73
|
-
// unknown crash on older node, fail
|
|
74
|
-
console.error(output)
|
|
75
|
-
console.error('ā Circular dependency check crashed unexpectedly')
|
|
76
|
-
process.exit(1)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// parse cycle arrays from output
|
|
80
|
-
const cycleRegex = /\[([^\]]+)\]/g
|
|
81
|
-
const cycles: string[][] = []
|
|
82
|
-
let match
|
|
83
|
-
|
|
84
|
-
while ((match = cycleRegex.exec(output)) !== null) {
|
|
85
|
-
// parse the cycle array
|
|
86
|
-
const cycleStr = match[1]
|
|
87
|
-
if (cycleStr) {
|
|
88
|
-
const files = cycleStr.split(',').map((f) => f.trim().replace(/"/g, ''))
|
|
89
|
-
cycles.push(files)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// filter out cycles that contain ignored files
|
|
94
|
-
const remainingCycles = cycles.filter((cycle) => {
|
|
95
|
-
const containsIgnored = cycle.some((file) =>
|
|
96
|
-
ignoredFiles.some((ignored) => basename(file) === ignored || file.includes(ignored))
|
|
97
|
-
)
|
|
98
|
-
return !containsIgnored
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
if (remainingCycles.length > 0) {
|
|
102
|
-
// reconstruct output with filtered cycles
|
|
103
|
-
console.info(
|
|
104
|
-
output
|
|
105
|
-
.split('\n')
|
|
106
|
-
.filter((line: string) => !line.startsWith('['))
|
|
107
|
-
.join('\n')
|
|
108
|
-
)
|
|
109
|
-
console.info(`Found ${remainingCycles.length} dependency cycles`)
|
|
110
|
-
remainingCycles.forEach((cycle) => {
|
|
111
|
-
console.info(JSON.stringify(cycle))
|
|
112
|
-
})
|
|
113
|
-
console.error('ā Circular dependencies detected')
|
|
114
|
-
process.exit(1)
|
|
115
|
-
} else if (cycles.length > 0) {
|
|
116
|
-
// all cycles were ignored
|
|
117
|
-
console.info(
|
|
118
|
-
`ā
Found ${cycles.length} circular dependencies but all contain ignored files`
|
|
119
|
-
)
|
|
120
|
-
if (ignoredFiles.length > 0) {
|
|
121
|
-
console.info(` Ignored files: ${ignoredFiles.join(', ')}`)
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
// no cycles found, but command failed for another reason
|
|
125
|
-
console.error(output)
|
|
126
|
-
console.error('ā Error running circular dependency check')
|
|
127
|
-
process.exit(1)
|
|
128
|
-
}
|
|
129
|
-
}
|