@o/scripts 0.16.19
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 +57 -0
- package/src/build-initial.ts +93 -0
- package/src/clean.ts +29 -0
- package/src/cmd.ts +105 -0
- package/src/dev-tunnel.ts +162 -0
- package/src/ensure-port.ts +79 -0
- package/src/ensure-tunnel.ts +19 -0
- package/src/ensure-zero-sqlite.ts +31 -0
- package/src/env-pull.ts +58 -0
- package/src/env-update.ts +342 -0
- package/src/exec-with-env.ts +61 -0
- package/src/helpers/args.ts +97 -0
- package/src/helpers/check-port.ts +22 -0
- package/src/helpers/deploy-health.ts +227 -0
- package/src/helpers/deploy-lock.ts +231 -0
- package/src/helpers/ensure-s3-bucket.ts +88 -0
- package/src/helpers/env-load.ts +43 -0
- package/src/helpers/get-docker-host.ts +37 -0
- package/src/helpers/get-test-env.ts +46 -0
- package/src/helpers/github-tail.ts +249 -0
- package/src/helpers/multipass.ts +174 -0
- package/src/helpers/parse-env-file.ts +71 -0
- package/src/helpers/process-compose-env.ts +57 -0
- package/src/helpers/resolve-dep-version.ts +11 -0
- package/src/helpers/run.ts +340 -0
- package/src/helpers/ssh.ts +50 -0
- package/src/helpers/uncloud-deploy.ts +162 -0
- package/src/helpers/uncloud.ts +197 -0
- package/src/helpers/wait-for-port.ts +33 -0
- package/src/helpers/zero-get-version.ts +8 -0
- package/src/release.ts +544 -0
- package/src/sst-get-environment.ts +38 -0
- package/src/typecheck.ts +21 -0
- package/src/up.ts +440 -0
- package/src/update-changelog.ts +92 -0
- package/src/wait-for-dev.ts +41 -0
package/src/up.ts
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { cmd } from './cmd'
|
|
4
|
+
|
|
5
|
+
interface PackageJson {
|
|
6
|
+
dependencies?: Record<string, string>
|
|
7
|
+
devDependencies?: Record<string, string>
|
|
8
|
+
peerDependencies?: Record<string, string>
|
|
9
|
+
optionalDependencies?: Record<string, string>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
await cmd`upgrade packages by name or pattern`
|
|
13
|
+
.args('--tag string --canary boolean --rc boolean')
|
|
14
|
+
.run(async ({ args, $, path, fs }) => {
|
|
15
|
+
let globalTag: string | undefined = args.tag
|
|
16
|
+
if (args.canary) globalTag = 'canary'
|
|
17
|
+
if (args.rc) globalTag = 'rc'
|
|
18
|
+
|
|
19
|
+
const packagePatterns: string[] = []
|
|
20
|
+
const rootDir = process.cwd()
|
|
21
|
+
const rootPackageJson = JSON.parse(
|
|
22
|
+
fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8')
|
|
23
|
+
)
|
|
24
|
+
const upgradeSets: Record<string, string[]> = rootPackageJson.upgradeSets || {}
|
|
25
|
+
|
|
26
|
+
for (const arg of args.rest) {
|
|
27
|
+
if (arg in upgradeSets) {
|
|
28
|
+
// expand named upgrade set to its patterns
|
|
29
|
+
packagePatterns.push(...upgradeSets[arg]!)
|
|
30
|
+
} else {
|
|
31
|
+
packagePatterns.push(arg)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (packagePatterns.length === 0) {
|
|
36
|
+
const setNames = Object.keys(upgradeSets)
|
|
37
|
+
if (setNames.length > 0) {
|
|
38
|
+
console.info('Usage: bun tko up <target|pattern> [options]')
|
|
39
|
+
console.info(`\nAvailable upgrade sets: ${setNames.join(', ')}`)
|
|
40
|
+
console.info('\nOr provide package patterns directly:')
|
|
41
|
+
console.info(' bun tko up @vxrn/* vxrn')
|
|
42
|
+
console.info(' bun tko up --tag canary react react-dom')
|
|
43
|
+
} else {
|
|
44
|
+
console.error('Please provide at least one package pattern to update.')
|
|
45
|
+
console.error('Example: bun tko up @vxrn/* vxrn')
|
|
46
|
+
console.error('Or with a tag: bun tko up --tag canary @vxrn/* vxrn')
|
|
47
|
+
}
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findPackageJsonFiles(dir: string): string[] {
|
|
52
|
+
const results: string[] = []
|
|
53
|
+
|
|
54
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
55
|
+
results.push(path.join(dir, 'package.json'))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// check if it's a monorepo with workspaces
|
|
59
|
+
try {
|
|
60
|
+
const packageJson = JSON.parse(
|
|
61
|
+
fs.readFileSync(path.join(dir, 'package.json'), 'utf-8')
|
|
62
|
+
)
|
|
63
|
+
if (packageJson.workspaces) {
|
|
64
|
+
let workspacePaths: string[] = []
|
|
65
|
+
|
|
66
|
+
if (Array.isArray(packageJson.workspaces)) {
|
|
67
|
+
workspacePaths = packageJson.workspaces
|
|
68
|
+
} else if (packageJson.workspaces.packages) {
|
|
69
|
+
workspacePaths = packageJson.workspaces.packages
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const workspace of workspacePaths) {
|
|
73
|
+
// handle glob patterns like "packages/*", "code/**/*", "./code/ui/**/*"
|
|
74
|
+
const normalizedWorkspace = workspace.replace(/^\.\//, '')
|
|
75
|
+
|
|
76
|
+
if (normalizedWorkspace.includes('**')) {
|
|
77
|
+
// nested glob pattern - use glob to find all package.json files
|
|
78
|
+
const baseDir = normalizedWorkspace.split('**')[0]!.replace(/\/$/, '')
|
|
79
|
+
const basePath = path.join(dir, baseDir)
|
|
80
|
+
|
|
81
|
+
if (fs.existsSync(basePath)) {
|
|
82
|
+
const findPackages = (searchDir: string) => {
|
|
83
|
+
try {
|
|
84
|
+
const entries = fs.readdirSync(searchDir, { withFileTypes: true })
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
87
|
+
const subPath = path.join(searchDir, entry.name)
|
|
88
|
+
const pkgPath = path.join(subPath, 'package.json')
|
|
89
|
+
if (fs.existsSync(pkgPath)) {
|
|
90
|
+
results.push(pkgPath)
|
|
91
|
+
}
|
|
92
|
+
// recurse into subdirectories
|
|
93
|
+
findPackages(subPath)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (_e) {
|
|
97
|
+
// ignore permission errors
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
findPackages(basePath)
|
|
101
|
+
}
|
|
102
|
+
} else if (normalizedWorkspace.includes('*')) {
|
|
103
|
+
// simple glob pattern like "packages/*"
|
|
104
|
+
const workspaceDir = normalizedWorkspace.replace(/\/\*$/, '')
|
|
105
|
+
if (fs.existsSync(path.join(dir, workspaceDir))) {
|
|
106
|
+
const subdirs = fs
|
|
107
|
+
.readdirSync(path.join(dir, workspaceDir), {
|
|
108
|
+
withFileTypes: true,
|
|
109
|
+
})
|
|
110
|
+
.filter((dirent) => dirent.isDirectory())
|
|
111
|
+
.map((dirent) => path.join(dir, workspaceDir, dirent.name))
|
|
112
|
+
|
|
113
|
+
for (const subdir of subdirs) {
|
|
114
|
+
if (fs.existsSync(path.join(subdir, 'package.json'))) {
|
|
115
|
+
results.push(path.join(subdir, 'package.json'))
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
// exact path like "code/tamagui.dev" or "./code/sandbox"
|
|
121
|
+
const pkgPath = path.join(dir, normalizedWorkspace, 'package.json')
|
|
122
|
+
if (fs.existsSync(pkgPath)) {
|
|
123
|
+
results.push(pkgPath)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (_error) {
|
|
129
|
+
// ignore errors parsing package.json
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return results
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function extractDependencies(packageJsonPath: string): string[] {
|
|
136
|
+
try {
|
|
137
|
+
const content = fs.readFileSync(packageJsonPath, 'utf-8')
|
|
138
|
+
const packageJson = JSON.parse(content) as PackageJson
|
|
139
|
+
|
|
140
|
+
const deps: string[] = []
|
|
141
|
+
|
|
142
|
+
const addNonWorkspaceDeps = (depsObject: Record<string, string> | undefined) => {
|
|
143
|
+
if (!depsObject) return
|
|
144
|
+
for (const [name, version] of Object.entries(depsObject)) {
|
|
145
|
+
// skip workspace dependencies
|
|
146
|
+
if (!version.startsWith('workspace:')) {
|
|
147
|
+
deps.push(name)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
addNonWorkspaceDeps(packageJson.dependencies)
|
|
153
|
+
addNonWorkspaceDeps(packageJson.devDependencies)
|
|
154
|
+
addNonWorkspaceDeps(packageJson.peerDependencies)
|
|
155
|
+
addNonWorkspaceDeps(packageJson.optionalDependencies)
|
|
156
|
+
|
|
157
|
+
return deps
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(`Error parsing ${packageJsonPath}:`, error)
|
|
160
|
+
return []
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function doesPackageMatchPattern(packageName: string, pattern: string): boolean {
|
|
165
|
+
if (pattern.includes('*')) {
|
|
166
|
+
const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`)
|
|
167
|
+
return regex.test(packageName)
|
|
168
|
+
}
|
|
169
|
+
return packageName === pattern
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function updatePackageJsonVersions(
|
|
173
|
+
packageJsonPath: string,
|
|
174
|
+
packagesToUpdate: string[],
|
|
175
|
+
versionMap: Map<string, string>
|
|
176
|
+
): number {
|
|
177
|
+
const content = fs.readFileSync(packageJsonPath, 'utf-8')
|
|
178
|
+
const packageJson = JSON.parse(content) as PackageJson
|
|
179
|
+
let updatedCount = 0
|
|
180
|
+
|
|
181
|
+
const updateDeps = (depsObject: Record<string, string> | undefined) => {
|
|
182
|
+
if (!depsObject) return
|
|
183
|
+
for (const pkg of packagesToUpdate) {
|
|
184
|
+
const current = depsObject[pkg]
|
|
185
|
+
if (current && !current.startsWith('workspace:')) {
|
|
186
|
+
const newVersion = versionMap.get(pkg)
|
|
187
|
+
if (newVersion) {
|
|
188
|
+
// for tagged versions (canary, rc, etc), use exact version (no prefix)
|
|
189
|
+
// otherwise preserve version prefix (^, ~, >=, etc)
|
|
190
|
+
if (globalTag) {
|
|
191
|
+
depsObject[pkg] = newVersion
|
|
192
|
+
} else {
|
|
193
|
+
// wildcard "*" means newly added placeholder, use ^ prefix
|
|
194
|
+
if (current === '*') {
|
|
195
|
+
depsObject[pkg] = `^${newVersion}`
|
|
196
|
+
updatedCount++
|
|
197
|
+
continue
|
|
198
|
+
}
|
|
199
|
+
const prefixMatch = current.match(/^([^\d]*)/)
|
|
200
|
+
const prefix = prefixMatch?.[1] || ''
|
|
201
|
+
depsObject[pkg] = `${prefix}${newVersion}`
|
|
202
|
+
}
|
|
203
|
+
updatedCount++
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
updateDeps(packageJson.dependencies)
|
|
210
|
+
updateDeps(packageJson.devDependencies)
|
|
211
|
+
updateDeps(packageJson.peerDependencies)
|
|
212
|
+
updateDeps(packageJson.optionalDependencies)
|
|
213
|
+
|
|
214
|
+
if (updatedCount > 0) {
|
|
215
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return updatedCount
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function updatePackages(
|
|
222
|
+
packagesByWorkspace: Map<string, { dir: string; packages: string[] }>,
|
|
223
|
+
rootDir: string,
|
|
224
|
+
packageJsonFiles: string[]
|
|
225
|
+
) {
|
|
226
|
+
try {
|
|
227
|
+
fs.rmSync(`node_modules/vite`, {
|
|
228
|
+
recursive: true,
|
|
229
|
+
force: true,
|
|
230
|
+
})
|
|
231
|
+
} catch (_e) {
|
|
232
|
+
// ignore if vite is not there
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// collect all unique packages to update
|
|
236
|
+
const allPackages = new Set<string>()
|
|
237
|
+
for (const { packages } of packagesByWorkspace.values()) {
|
|
238
|
+
packages.forEach((pkg) => allPackages.add(pkg))
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// fetch versions for all packages (with tag if specified)
|
|
242
|
+
const versionMap = new Map<string, string>()
|
|
243
|
+
console.info(`\nš Fetching versions for ${allPackages.size} package(s)...`)
|
|
244
|
+
|
|
245
|
+
await Promise.all(
|
|
246
|
+
[...allPackages].map(async (pkg) => {
|
|
247
|
+
try {
|
|
248
|
+
const tag = globalTag || 'latest'
|
|
249
|
+
const result = await $`npm view ${pkg}@${tag} version`.quiet()
|
|
250
|
+
const version = result.text().trim()
|
|
251
|
+
if (version) {
|
|
252
|
+
versionMap.set(pkg, version)
|
|
253
|
+
console.info(` ā ${pkg}@${tag} ā ${version}`)
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
if (globalTag) {
|
|
257
|
+
console.info(` ā ${pkg}@${globalTag} not found, skipping`)
|
|
258
|
+
} else {
|
|
259
|
+
console.info(` ā ${pkg} not found, skipping`)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if (versionMap.size === 0) {
|
|
266
|
+
console.info(`\nā ļø No packages found to update`)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// update all package.json files directly
|
|
271
|
+
console.info(`\nš¦ Updating ${packageJsonFiles.length} package.json file(s)...`)
|
|
272
|
+
let totalUpdates = 0
|
|
273
|
+
|
|
274
|
+
for (const packageJsonPath of packageJsonFiles) {
|
|
275
|
+
const packagesInWorkspace =
|
|
276
|
+
packagesByWorkspace.get(getWorkspaceName(packageJsonPath, rootDir))?.packages ||
|
|
277
|
+
[]
|
|
278
|
+
|
|
279
|
+
if (packagesInWorkspace.length > 0) {
|
|
280
|
+
const updates = updatePackageJsonVersions(
|
|
281
|
+
packageJsonPath,
|
|
282
|
+
packagesInWorkspace,
|
|
283
|
+
versionMap
|
|
284
|
+
)
|
|
285
|
+
if (updates > 0) {
|
|
286
|
+
const name = getWorkspaceName(packageJsonPath, rootDir)
|
|
287
|
+
console.info(` ā ${name}: ${updates} package(s)`)
|
|
288
|
+
totalUpdates += updates
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.info(`\nš Updated ${totalUpdates} dependency version(s)`)
|
|
294
|
+
|
|
295
|
+
console.info(`\nāļø Running 'bun install'...`)
|
|
296
|
+
$.cwd(rootDir)
|
|
297
|
+
try {
|
|
298
|
+
await $`bun install`
|
|
299
|
+
console.info('ā
Done!')
|
|
300
|
+
} catch (error: any) {
|
|
301
|
+
const stderr = error.stderr?.toString() || error.message || ''
|
|
302
|
+
// check if it's a version resolution error (common after new publish)
|
|
303
|
+
if (
|
|
304
|
+
stderr.includes('No version matching') ||
|
|
305
|
+
stderr.includes('failed to resolve')
|
|
306
|
+
) {
|
|
307
|
+
console.info(`ā ļø Version not in cache, clearing cache and retrying...`)
|
|
308
|
+
try {
|
|
309
|
+
await $`bun pm cache rm`.quiet()
|
|
310
|
+
await $`bun install`
|
|
311
|
+
console.info('ā
Done!')
|
|
312
|
+
} catch (retryError: any) {
|
|
313
|
+
const retryStderr = retryError.stderr?.toString() || retryError.message || ''
|
|
314
|
+
console.error(
|
|
315
|
+
`šØ 'bun install' failed after cache clear: ${retryStderr.split('\n')[0]}`
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
console.error(`šØ 'bun install' failed: ${stderr.split('\n')[0]}`)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function getWorkspaceName(packageJsonPath: string, rootDir: string): string {
|
|
325
|
+
const dir = packageJsonPath.replace('/package.json', '')
|
|
326
|
+
if (dir === rootDir) return 'root'
|
|
327
|
+
return dir.replace(rootDir + '/', '')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const packageJsonFiles = findPackageJsonFiles(rootDir)
|
|
331
|
+
console.info(`Found ${packageJsonFiles.length} package.json files`)
|
|
332
|
+
|
|
333
|
+
// get workspace package names to exclude from updates
|
|
334
|
+
const workspacePackageNames = new Set<string>()
|
|
335
|
+
for (const packageJsonPath of packageJsonFiles) {
|
|
336
|
+
if (packageJsonPath === path.join(rootDir, 'package.json')) continue
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const content = fs.readFileSync(packageJsonPath, 'utf-8')
|
|
340
|
+
const packageJson = JSON.parse(content)
|
|
341
|
+
if (packageJson.name) {
|
|
342
|
+
workspacePackageNames.add(packageJson.name)
|
|
343
|
+
}
|
|
344
|
+
} catch (_error) {
|
|
345
|
+
// ignore errors
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
console.info(
|
|
350
|
+
`Found ${workspacePackageNames.size} workspace packages to exclude from updates`
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
// build map of packages to update per workspace
|
|
354
|
+
const packagesByWorkspace = new Map<string, { dir: string; packages: string[] }>()
|
|
355
|
+
const allMatchingDeps = new Set<string>()
|
|
356
|
+
|
|
357
|
+
for (const packageJsonPath of packageJsonFiles) {
|
|
358
|
+
const deps = extractDependencies(packageJsonPath)
|
|
359
|
+
const matchingDeps: string[] = []
|
|
360
|
+
|
|
361
|
+
for (const dep of deps) {
|
|
362
|
+
// skip workspace packages
|
|
363
|
+
if (workspacePackageNames.has(dep)) continue
|
|
364
|
+
|
|
365
|
+
for (const pattern of packagePatterns) {
|
|
366
|
+
if (doesPackageMatchPattern(dep, pattern)) {
|
|
367
|
+
matchingDeps.push(dep)
|
|
368
|
+
allMatchingDeps.add(dep)
|
|
369
|
+
break
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (matchingDeps.length > 0) {
|
|
375
|
+
const dir = packageJsonPath.replace('/package.json', '')
|
|
376
|
+
const name = getWorkspaceName(packageJsonPath, rootDir)
|
|
377
|
+
packagesByWorkspace.set(name, { dir, packages: matchingDeps })
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (allMatchingDeps.size === 0) {
|
|
382
|
+
// no existing deps matched, but exact patterns (no wildcards) can be added fresh
|
|
383
|
+
const exactPatterns = packagePatterns.filter((p) => !p.includes('*'))
|
|
384
|
+
if (exactPatterns.length === 0) {
|
|
385
|
+
console.info(
|
|
386
|
+
`Found 0 dependencies matching patterns: ${packagePatterns.join(', ')}`
|
|
387
|
+
)
|
|
388
|
+
console.info('No matching packages found to update.')
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// add as new dependencies to the root package.json
|
|
393
|
+
console.info(`No existing deps found, adding to root: ${exactPatterns.join(', ')}`)
|
|
394
|
+
const rootPkgPath = path.join(rootDir, 'package.json')
|
|
395
|
+
|
|
396
|
+
for (const pkg of exactPatterns) {
|
|
397
|
+
allMatchingDeps.add(pkg)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
packagesByWorkspace.set('root', {
|
|
401
|
+
dir: rootDir,
|
|
402
|
+
packages: exactPatterns,
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
// insert placeholder so updatePackageJsonVersions can set the real version
|
|
406
|
+
const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf-8'))
|
|
407
|
+
if (!rootPkg.dependencies) {
|
|
408
|
+
rootPkg.dependencies = {}
|
|
409
|
+
}
|
|
410
|
+
for (const pkg of exactPatterns) {
|
|
411
|
+
if (!rootPkg.dependencies[pkg] && !rootPkg.devDependencies?.[pkg]) {
|
|
412
|
+
rootPkg.dependencies[pkg] = '*'
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2) + '\n')
|
|
416
|
+
} else {
|
|
417
|
+
console.info(
|
|
418
|
+
`Found ${allMatchingDeps.size} dependencies matching patterns: ${packagePatterns.join(', ')}`
|
|
419
|
+
)
|
|
420
|
+
console.info(`Found matches in ${packagesByWorkspace.size} workspace(s)`)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (globalTag) {
|
|
424
|
+
console.info(`š·ļø Using tag '${globalTag}'`)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
await updatePackages(packagesByWorkspace, rootDir, packageJsonFiles)
|
|
428
|
+
|
|
429
|
+
// sync resolved $dep: values (like ZERO_VERSION) to all env targets
|
|
430
|
+
if (
|
|
431
|
+
Object.values(rootPackageJson.env || {}).some(
|
|
432
|
+
(v: any) => typeof v === 'string' && v.startsWith('$dep:')
|
|
433
|
+
)
|
|
434
|
+
) {
|
|
435
|
+
console.info('\nš Syncing env variables...')
|
|
436
|
+
await $`bun tko run env-update`
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
console.info('\nš Dependency update complete!')
|
|
440
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
|
|
5
|
+
import { cmd } from './cmd'
|
|
6
|
+
|
|
7
|
+
await cmd`update changelog with recent git commits`.run(async ({ path }) => {
|
|
8
|
+
const { execSync } = await import('node:child_process')
|
|
9
|
+
|
|
10
|
+
const CHANGELOG_PATH = path.join(process.cwd(), 'src/features/site/docs/changelog.mdx')
|
|
11
|
+
|
|
12
|
+
function getLastSha(): string | null {
|
|
13
|
+
if (!fs.existsSync(CHANGELOG_PATH)) return null
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const content = fs.readFileSync(CHANGELOG_PATH, 'utf-8')
|
|
17
|
+
const match = content.match(/\{\/\* last updated: ([a-f0-9]+) \*\/\}/)
|
|
18
|
+
return match?.[1] || null
|
|
19
|
+
} catch {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getLatestSha(): string {
|
|
25
|
+
return execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getCommitCount(fromSha: string, toSha: string): number {
|
|
29
|
+
try {
|
|
30
|
+
const result = execSync(`git rev-list --count ${fromSha}..${toSha} --no-merges`, {
|
|
31
|
+
encoding: 'utf-8',
|
|
32
|
+
})
|
|
33
|
+
return parseInt(result.trim(), 10)
|
|
34
|
+
} catch {
|
|
35
|
+
return 0
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const lastSha = getLastSha()
|
|
40
|
+
const latestSha = getLatestSha()
|
|
41
|
+
|
|
42
|
+
if (!lastSha) {
|
|
43
|
+
console.info(`no last sha found in changelog, defaulting to 4 weeks ago`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const fromRef = lastSha || '$(git log --since="4 weeks ago" --format="%H" | tail -1)'
|
|
47
|
+
const commitCount = lastSha ? getCommitCount(lastSha, latestSha) : '~30'
|
|
48
|
+
|
|
49
|
+
if (commitCount === 0) {
|
|
50
|
+
console.info('no new commits since last update')
|
|
51
|
+
process.exit(0)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.info(`
|
|
55
|
+
Update the changelog at: ${CHANGELOG_PATH}
|
|
56
|
+
|
|
57
|
+
Commit range: ${fromRef}..${latestSha} (${commitCount} commits)
|
|
58
|
+
|
|
59
|
+
Instructions:
|
|
60
|
+
- investigate commits with git log, git show, git diff as needed
|
|
61
|
+
- for vague commits like "cleanups" or "fix Component.tsx", look at the actual diff to understand what changed
|
|
62
|
+
- update the "last updated" comment to: ${latestSha}
|
|
63
|
+
- create new week sections if needed (weeks start monday)
|
|
64
|
+
- format: "## Week of January 20, 2026" with sha in backticks below
|
|
65
|
+
- no emoji, plain text only
|
|
66
|
+
- group small fixes/chores into "Bug fixes and chores (N)"
|
|
67
|
+
- for tamagui/one upgrades, check ~/tamagui or ~/one repos or github for what actually changed
|
|
68
|
+
- write useful descriptions - explain WHY and WHAT improved, not just "fixed X"
|
|
69
|
+
- stop when you reach commits already in the changelog
|
|
70
|
+
|
|
71
|
+
Archiving old entries:
|
|
72
|
+
- keep only 10 weeks in changelog.mdx
|
|
73
|
+
- when over 10 weeks, move the oldest entries into an archive file named
|
|
74
|
+
changelog-YYYY.mdx, where YYYY is the year the archived WEEK is from
|
|
75
|
+
(not the current year). e.g. a "Week of January 27, 2026" entry belongs
|
|
76
|
+
in changelog-2026.mdx, not changelog-2025.mdx ā even if 2025 already
|
|
77
|
+
exists as an archive. never mix years in the same archive file.
|
|
78
|
+
- if the target archive file doesn't exist yet, create it with frontmatter:
|
|
79
|
+
---
|
|
80
|
+
title: Changelog Archive ā YYYY
|
|
81
|
+
description: Archived Takeout updates from YYYY
|
|
82
|
+
---
|
|
83
|
+
and end the file with a link chain to the previous year's archive if
|
|
84
|
+
one exists, e.g. "See [2025 changes](/docs/changelog-2025) for earlier updates."
|
|
85
|
+
- if the archive already exists, prepend the new weeks to the top
|
|
86
|
+
(after frontmatter), so it stays in reverse-chronological order
|
|
87
|
+
- at the bottom of changelog.mdx, list links to every archive file that
|
|
88
|
+
exists, newest first, e.g.
|
|
89
|
+
"See [early 2026 changes](/docs/changelog-2026) and [2025 changes](/docs/changelog-2025) for previous updates."
|
|
90
|
+
(use absolute paths, update the list whenever a new archive file is created)
|
|
91
|
+
`)
|
|
92
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { cmd } from './cmd'
|
|
4
|
+
|
|
5
|
+
await cmd`wait for dev server to be available`.run(async () => {
|
|
6
|
+
const { sleep } = await import('@o/helpers')
|
|
7
|
+
|
|
8
|
+
const ONE_SERVER_URL = process.env.ONE_SERVER_URL || 'http://localhost:8081'
|
|
9
|
+
const CHECK_INTERVAL = 2000
|
|
10
|
+
|
|
11
|
+
async function checkServer(): Promise<boolean> {
|
|
12
|
+
try {
|
|
13
|
+
await fetch(ONE_SERVER_URL, {
|
|
14
|
+
signal: AbortSignal.timeout(5000),
|
|
15
|
+
}).then((res) => res.text())
|
|
16
|
+
// give it a couple seconds to build initial route
|
|
17
|
+
await sleep(2000)
|
|
18
|
+
return true
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
process.stdout.write(`Waiting for server at ${ONE_SERVER_URL}...\n`)
|
|
25
|
+
|
|
26
|
+
while (true) {
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, CHECK_INTERVAL))
|
|
28
|
+
const isReady = await checkServer()
|
|
29
|
+
|
|
30
|
+
if (isReady) {
|
|
31
|
+
process.stdout.write(`ā Server at ${ONE_SERVER_URL} is ready!\n`)
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
`Waiting to start dev watch until after dev server (wait ${CHECK_INTERVAL / 1000}s)...\n`
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
process.exit(0)
|
|
41
|
+
})
|