@emeryld/manager 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/manager-cli.js +1 -3
- package/dist/git.js +68 -0
- package/dist/helper-cli.js +299 -0
- package/dist/menu.js +130 -0
- package/dist/packages.js +281 -0
- package/dist/preflight.js +26 -0
- package/dist/prompts.js +85 -0
- package/dist/publish.js +142 -0
- package/dist/release.js +292 -0
- package/dist/semver.js +17 -0
- package/dist/utils/colors.js +11 -0
- package/dist/utils/log.js +19 -0
- package/dist/utils/run.js +21 -0
- package/dist/workspace.js +246 -0
- package/package.json +5 -5
- package/tsconfig.base.json +0 -1
- package/src/git.ts +0 -74
- package/src/helper-cli.ts +0 -405
- package/src/menu.ts +0 -142
- package/src/packages.ts +0 -305
- package/src/preflight.ts +0 -26
- package/src/prompts.ts +0 -93
- package/src/publish.ts +0 -183
- package/src/release.ts +0 -410
- package/src/semver.ts +0 -27
- package/src/sync-version.mjs +0 -213
- package/src/utils/colors.ts +0 -11
- package/src/utils/log.ts +0 -42
- package/src/utils/run.ts +0 -30
- package/src/workspace.ts +0 -290
package/src/release.ts
DELETED
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
// src/release.js
|
|
2
|
-
import { readFile, writeFile } from 'node:fs/promises'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { stageCommitPush } from './git.js'
|
|
5
|
-
import { askLine, promptSingleKey, promptYesNoAll } from './prompts.js'
|
|
6
|
-
import { bumpVersion, isSemver, type BumpType } from './semver.js'
|
|
7
|
-
import type { LoadedPackage } from './utils/log.js'
|
|
8
|
-
import { colors, formatPkgName, logGlobal, logPkg } from './utils/log.js'
|
|
9
|
-
import { run } from './utils/run.js'
|
|
10
|
-
|
|
11
|
-
// in release.js
|
|
12
|
-
|
|
13
|
-
type VersionStrategy =
|
|
14
|
-
| { mode: 'bump'; bumpType: BumpType; preid?: string }
|
|
15
|
-
| { mode: 'sync'; version: string }
|
|
16
|
-
| { mode: 'noop' }
|
|
17
|
-
|
|
18
|
-
export interface PublishOptions {
|
|
19
|
-
nonInteractive?: boolean
|
|
20
|
-
bumpType?: BumpType // used when nonInteractive, now includes pre* types
|
|
21
|
-
preid?: string // e.g. "alpha" | "beta" | "rc"
|
|
22
|
-
syncVersion?: string // used when nonInteractive
|
|
23
|
-
tag?: string // e.g. "next" or "beta"
|
|
24
|
-
dryRun?: boolean
|
|
25
|
-
provenance?: boolean
|
|
26
|
-
// Optional: used only for git tag message, if you want:
|
|
27
|
-
releaseNotes?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface PublishOptions {
|
|
31
|
-
nonInteractive?: boolean
|
|
32
|
-
bumpType?: BumpType // used when nonInteractive
|
|
33
|
-
syncVersion?: string // used when nonInteractive
|
|
34
|
-
tag?: string // e.g. "next" or "beta"
|
|
35
|
-
dryRun?: boolean
|
|
36
|
-
provenance?: boolean
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const bumpMap: Record<string, BumpType> = {
|
|
40
|
-
'1': 'patch',
|
|
41
|
-
'2': 'minor',
|
|
42
|
-
'3': 'major',
|
|
43
|
-
// mapped from pre-release submenu later, but keep named keys for nonInteractive
|
|
44
|
-
patch: 'patch',
|
|
45
|
-
minor: 'minor',
|
|
46
|
-
major: 'major',
|
|
47
|
-
prepatch: 'prepatch',
|
|
48
|
-
preminor: 'preminor',
|
|
49
|
-
premajor: 'premajor',
|
|
50
|
-
prerelease: 'prerelease',
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function preBumpFromChoice(choice: string): BumpType | undefined {
|
|
54
|
-
switch (choice) {
|
|
55
|
-
case '1':
|
|
56
|
-
return 'prepatch'
|
|
57
|
-
case '2':
|
|
58
|
-
return 'preminor'
|
|
59
|
-
case '3':
|
|
60
|
-
return 'premajor'
|
|
61
|
-
case '4':
|
|
62
|
-
return 'prerelease'
|
|
63
|
-
default:
|
|
64
|
-
return undefined
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function writePackageJson(pkgPath: string, json: unknown) {
|
|
69
|
-
await writeFile(pkgPath, `${JSON.stringify(json, null, 2)}\n`)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function refreshPackagesFromDisk(pkgs: LoadedPackage[]) {
|
|
73
|
-
await Promise.all(
|
|
74
|
-
pkgs.map(async (pkg) => {
|
|
75
|
-
const raw = await readFile(pkg.packageJsonPath, 'utf8')
|
|
76
|
-
const json = JSON.parse(raw)
|
|
77
|
-
pkg.json = json
|
|
78
|
-
pkg.version = json.version ?? pkg.version
|
|
79
|
-
if (json.name) pkg.name = json.name
|
|
80
|
-
}),
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function ensureNpmAuth() {
|
|
85
|
-
try {
|
|
86
|
-
await run('pnpm', ['whoami'])
|
|
87
|
-
} catch {
|
|
88
|
-
throw new Error(
|
|
89
|
-
'Not authenticated to registry. Run "pnpm login" and retry.',
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function ensureNotPrivate(targets: LoadedPackage[]) {
|
|
95
|
-
const privates = targets.filter((p) => p.json?.private)
|
|
96
|
-
if (privates.length) {
|
|
97
|
-
const names = privates.map((p) => p.name).join(', ')
|
|
98
|
-
throw new Error(`Refusing to publish private packages: ${names}`)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async function publishPackage(pkg: LoadedPackage, opts: PublishOptions = {}) {
|
|
103
|
-
const args = ['publish', '--access', 'public']
|
|
104
|
-
|
|
105
|
-
let tag = opts.tag
|
|
106
|
-
if (!tag) {
|
|
107
|
-
tag = inferTagFromVersion(pkg.version || '')
|
|
108
|
-
}
|
|
109
|
-
if (tag) args.push('--tag', tag)
|
|
110
|
-
|
|
111
|
-
if (opts.dryRun) args.push('--dry-run')
|
|
112
|
-
if (opts.provenance) args.push('--provenance')
|
|
113
|
-
|
|
114
|
-
logPkg(pkg, `Publishing ${pkg.name}…`, colors.green)
|
|
115
|
-
try {
|
|
116
|
-
await run('pnpm', args, { cwd: pkg.path })
|
|
117
|
-
logPkg(pkg, `${pkg.name} published.`, colors.green)
|
|
118
|
-
|
|
119
|
-
const tagName = `${pkg.name}@${pkg.version}`
|
|
120
|
-
const tagMessage = opts.releaseNotes
|
|
121
|
-
? `release ${pkg.version}\n\n${opts.releaseNotes}`
|
|
122
|
-
: `release ${pkg.version}`
|
|
123
|
-
|
|
124
|
-
await run('git', ['tag', '-a', tagName, '-m', tagMessage])
|
|
125
|
-
await run('git', ['push', 'origin', tagName])
|
|
126
|
-
} catch (err) {
|
|
127
|
-
console.error(colors.red(`Publish failed for ${pkg.name}.`))
|
|
128
|
-
console.error(
|
|
129
|
-
colors.dim('To revert the version commit: git revert HEAD && git push'),
|
|
130
|
-
)
|
|
131
|
-
throw err
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function applyVersionStrategy(
|
|
136
|
-
strategy: VersionStrategy,
|
|
137
|
-
targetPkg: LoadedPackage,
|
|
138
|
-
packages: LoadedPackage[],
|
|
139
|
-
opts: PublishOptions = {},
|
|
140
|
-
): Promise<{ changedPaths: string[]; commitMessage: string }> {
|
|
141
|
-
if (strategy.mode === 'noop') {
|
|
142
|
-
console.log('\nSkipping version bump (no version change selected).')
|
|
143
|
-
return { changedPaths: [], commitMessage: '' }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (strategy.mode === 'sync') {
|
|
147
|
-
console.log()
|
|
148
|
-
logGlobal('Syncing all package versions…', colors.cyan)
|
|
149
|
-
await run('pnpm', ['sync', strategy.version])
|
|
150
|
-
packages.forEach((pkg) => {
|
|
151
|
-
pkg.version = strategy.version
|
|
152
|
-
pkg.json.version = strategy.version
|
|
153
|
-
})
|
|
154
|
-
return {
|
|
155
|
-
changedPaths: [
|
|
156
|
-
'package.json',
|
|
157
|
-
...packages.map((pkg) =>
|
|
158
|
-
path.relative(process.cwd(), pkg.packageJsonPath),
|
|
159
|
-
),
|
|
160
|
-
],
|
|
161
|
-
commitMessage: `chore(release): sync ${strategy.version}`,
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// mode === 'bump'
|
|
166
|
-
const nextVersion = bumpVersion(
|
|
167
|
-
targetPkg.version,
|
|
168
|
-
strategy.bumpType,
|
|
169
|
-
strategy.preid,
|
|
170
|
-
)
|
|
171
|
-
console.log(
|
|
172
|
-
`\nUpdating ${formatPkgName(targetPkg)} version to ${colors.green(nextVersion)}`,
|
|
173
|
-
)
|
|
174
|
-
targetPkg.json.version = nextVersion
|
|
175
|
-
targetPkg.version = nextVersion
|
|
176
|
-
await writePackageJson(targetPkg.packageJsonPath, targetPkg.json)
|
|
177
|
-
return {
|
|
178
|
-
changedPaths: [path.relative(process.cwd(), targetPkg.packageJsonPath)],
|
|
179
|
-
commitMessage: opts?.releaseNotes
|
|
180
|
-
? `chore(release): ${targetPkg.name}@${nextVersion} - ${opts.releaseNotes}`
|
|
181
|
-
: `chore(release): ${targetPkg.name}@${nextVersion}`,
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function strategyFromOptions(
|
|
186
|
-
targetPkg: LoadedPackage,
|
|
187
|
-
selection: LoadedPackage[],
|
|
188
|
-
opts?: PublishOptions,
|
|
189
|
-
): VersionStrategy | undefined {
|
|
190
|
-
if (!opts?.nonInteractive) return undefined
|
|
191
|
-
if (opts.syncVersion) {
|
|
192
|
-
if (!isSemver(opts.syncVersion))
|
|
193
|
-
throw new Error(`Invalid version "${opts.syncVersion}"`)
|
|
194
|
-
return { mode: 'sync', version: opts.syncVersion }
|
|
195
|
-
}
|
|
196
|
-
if (opts.bumpType)
|
|
197
|
-
return { mode: 'bump', bumpType: opts.bumpType, preid: opts.preid }
|
|
198
|
-
return { mode: 'noop' }
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function inferTagFromVersion(version: string): string | undefined {
|
|
202
|
-
const match = version.match(/-([0-9A-Za-z-]+)(?:\.\d+)?$/)
|
|
203
|
-
if (!match) return undefined
|
|
204
|
-
const preid = match[1].toLowerCase()
|
|
205
|
-
if (['alpha', 'beta', 'rc'].includes(preid)) return preid
|
|
206
|
-
return 'next'
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function inferPreidFromVersion(version: string): string | undefined {
|
|
210
|
-
const match = version.match(/-([0-9A-Za-z-]+)/)
|
|
211
|
-
return match?.[1]?.toLowerCase()
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export async function promptVersionStrategy(
|
|
215
|
-
targetPkg: LoadedPackage,
|
|
216
|
-
selection: LoadedPackage[],
|
|
217
|
-
opts?: PublishOptions,
|
|
218
|
-
): Promise<VersionStrategy> {
|
|
219
|
-
const viaOpts = strategyFromOptions(targetPkg, selection, opts)
|
|
220
|
-
if (viaOpts) return viaOpts
|
|
221
|
-
|
|
222
|
-
console.log('\nCurrent package versions:')
|
|
223
|
-
for (const pkg of selection) {
|
|
224
|
-
const marker =
|
|
225
|
-
selection.length === 1 && pkg.dirName === targetPkg.dirName ? '*' : '•'
|
|
226
|
-
console.log(
|
|
227
|
-
` ${marker} ${formatPkgName(pkg)} ${pkg.dirName} ${colors.yellow(pkg.version)}`,
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
console.log('\nSelect version strategy:')
|
|
232
|
-
console.log(' 1) Patch')
|
|
233
|
-
console.log(' 2) Minor')
|
|
234
|
-
console.log(' 3) Major')
|
|
235
|
-
console.log(' 4) Pre-release (alpha / beta / rc)')
|
|
236
|
-
console.log(' 5) Sync packages')
|
|
237
|
-
console.log(' 6) No version change')
|
|
238
|
-
|
|
239
|
-
const choice = await promptSingleKey('Choice: ', (key) => {
|
|
240
|
-
if (['1', '2', '3', '4', '5', '6'].includes(key)) return key
|
|
241
|
-
return undefined
|
|
242
|
-
})
|
|
243
|
-
console.log(choice)
|
|
244
|
-
|
|
245
|
-
// Sync
|
|
246
|
-
if (choice === '5') {
|
|
247
|
-
const desired = await askLine('Enter unified version: ')
|
|
248
|
-
if (!isSemver(desired)) throw new Error(`Invalid version "${desired}"`)
|
|
249
|
-
return { mode: 'sync', version: desired }
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// No version change
|
|
253
|
-
if (choice === '6') {
|
|
254
|
-
const ans = await promptYesNoAll('Publish without changing any versions?')
|
|
255
|
-
if (ans === 'no') {
|
|
256
|
-
console.log('Aborting.')
|
|
257
|
-
process.exit(0)
|
|
258
|
-
}
|
|
259
|
-
return { mode: 'noop' }
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Pre-release submenu
|
|
263
|
-
let preid: string | undefined
|
|
264
|
-
let bumpType: BumpType | undefined
|
|
265
|
-
|
|
266
|
-
if (choice === '4') {
|
|
267
|
-
const currentVersion = targetPkg.version || '?'
|
|
268
|
-
const defaultPreid = inferPreidFromVersion(currentVersion) || 'alpha'
|
|
269
|
-
const preview = (type: BumpType) => {
|
|
270
|
-
try {
|
|
271
|
-
return bumpVersion(currentVersion, type, defaultPreid)
|
|
272
|
-
} catch {
|
|
273
|
-
return '?'
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
console.log('\nSelect pre-release bump type:')
|
|
278
|
-
console.log(` 1) prepatch (${currentVersion} -> ${preview('prepatch')})`)
|
|
279
|
-
console.log(` 2) preminor (${currentVersion} -> ${preview('preminor')})`)
|
|
280
|
-
console.log(` 3) premajor (${currentVersion} -> ${preview('premajor')})`)
|
|
281
|
-
console.log(
|
|
282
|
-
` 4) prerelease (${currentVersion} -> ${preview('prerelease')})`,
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
const preChoice = await promptSingleKey('Choice: ', (key) => {
|
|
286
|
-
if (['1', '2', '3', '4'].includes(key)) return key
|
|
287
|
-
return undefined
|
|
288
|
-
})
|
|
289
|
-
console.log(preChoice)
|
|
290
|
-
|
|
291
|
-
bumpType = preBumpFromChoice(preChoice)
|
|
292
|
-
if (!bumpType) throw new Error('Invalid pre-release selection.')
|
|
293
|
-
|
|
294
|
-
const rawPreid = await askLine(
|
|
295
|
-
'Pre-release identifier (e.g. alpha, beta, rc) [alpha]: ',
|
|
296
|
-
)
|
|
297
|
-
preid = (rawPreid.trim() || 'alpha').toLowerCase()
|
|
298
|
-
} else {
|
|
299
|
-
// Regular patch / minor / major
|
|
300
|
-
bumpType = bumpMap[choice] as BumpType | undefined
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (!bumpType) throw new Error('Invalid selection.')
|
|
304
|
-
|
|
305
|
-
// Preview what will happen
|
|
306
|
-
if (selection.length === 1) {
|
|
307
|
-
const proposed = bumpVersion(targetPkg.version, bumpType, preid)
|
|
308
|
-
console.log(
|
|
309
|
-
`\nTarget package: ${formatPkgName(targetPkg)} -> ${colors.green(proposed)}`,
|
|
310
|
-
)
|
|
311
|
-
const others = selection.filter((pkg) => pkg.dirName !== targetPkg.dirName)
|
|
312
|
-
if (others.length) {
|
|
313
|
-
console.log('Other packages:')
|
|
314
|
-
others.forEach((pkg) => {
|
|
315
|
-
console.log(` - ${formatPkgName(pkg)} ${colors.yellow(pkg.version)}`)
|
|
316
|
-
})
|
|
317
|
-
}
|
|
318
|
-
} else {
|
|
319
|
-
console.log(
|
|
320
|
-
`\nApplying a ${bumpType}${
|
|
321
|
-
preid ? ` (${preid})` : ''
|
|
322
|
-
} bump to all selected packages:`,
|
|
323
|
-
)
|
|
324
|
-
selection.forEach((pkg) => {
|
|
325
|
-
const nextVersion = bumpVersion(pkg.version, bumpType!, preid)
|
|
326
|
-
const arrow = nextVersion === pkg.version ? '─' : '→'
|
|
327
|
-
console.log(
|
|
328
|
-
` - ${formatPkgName(pkg)} ${colors.yellow(pkg.version)} ${arrow} ${colors.green(
|
|
329
|
-
nextVersion,
|
|
330
|
-
)}`,
|
|
331
|
-
)
|
|
332
|
-
})
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const confirm = await promptYesNoAll(
|
|
336
|
-
selection.length === 1
|
|
337
|
-
? `Apply ${bumpType}${preid ? ` (${preid})` : ''} bump to ${targetPkg.name}?`
|
|
338
|
-
: `Apply ${bumpType}${preid ? ` (${preid})` : ''} bump to all selected packages?`,
|
|
339
|
-
)
|
|
340
|
-
if (confirm === 'no') {
|
|
341
|
-
console.log('Aborting.')
|
|
342
|
-
process.exit(0)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return { mode: 'bump', bumpType, preid }
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export async function releaseSingle(
|
|
349
|
-
targetPkg: LoadedPackage,
|
|
350
|
-
packages: LoadedPackage[],
|
|
351
|
-
opts: PublishOptions = {},
|
|
352
|
-
) {
|
|
353
|
-
await ensureNpmAuth()
|
|
354
|
-
await refreshPackagesFromDisk([targetPkg])
|
|
355
|
-
ensureNotPrivate([targetPkg])
|
|
356
|
-
|
|
357
|
-
console.log(`\n${formatPkgName(targetPkg)} ${colors.dim('(single release)')}`)
|
|
358
|
-
const strategy = await promptVersionStrategy(targetPkg, [targetPkg], opts)
|
|
359
|
-
const { changedPaths, commitMessage } = await applyVersionStrategy(
|
|
360
|
-
strategy,
|
|
361
|
-
targetPkg,
|
|
362
|
-
packages,
|
|
363
|
-
opts,
|
|
364
|
-
)
|
|
365
|
-
await stageCommitPush(changedPaths, commitMessage)
|
|
366
|
-
await publishPackage(targetPkg, opts)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
export async function releaseMultiple(
|
|
370
|
-
targets: LoadedPackage[],
|
|
371
|
-
packages: LoadedPackage[],
|
|
372
|
-
opts: PublishOptions = {},
|
|
373
|
-
) {
|
|
374
|
-
await ensureNpmAuth()
|
|
375
|
-
await refreshPackagesFromDisk(targets)
|
|
376
|
-
ensureNotPrivate(targets)
|
|
377
|
-
|
|
378
|
-
console.log(`\n🚀 ${'*'} Publishing all selected packages`)
|
|
379
|
-
const strategy = await promptVersionStrategy(targets[0], targets, opts)
|
|
380
|
-
let changedPaths: string[] = []
|
|
381
|
-
let commitMessage = ''
|
|
382
|
-
|
|
383
|
-
if (strategy.mode === 'sync') {
|
|
384
|
-
const result = await applyVersionStrategy(
|
|
385
|
-
strategy,
|
|
386
|
-
targets[0],
|
|
387
|
-
packages,
|
|
388
|
-
opts,
|
|
389
|
-
)
|
|
390
|
-
changedPaths = result.changedPaths
|
|
391
|
-
commitMessage = result.commitMessage
|
|
392
|
-
} else if (strategy.mode === 'bump') {
|
|
393
|
-
for (const pkg of targets) {
|
|
394
|
-
const result = await applyVersionStrategy(strategy, pkg, packages, opts)
|
|
395
|
-
changedPaths.push(...result.changedPaths)
|
|
396
|
-
}
|
|
397
|
-
commitMessage = `chore(release): ${targets.map((p) => `${p.name}@${p.version}`).join(', ')}`
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (changedPaths.length || commitMessage) {
|
|
401
|
-
await stageCommitPush(
|
|
402
|
-
changedPaths,
|
|
403
|
-
commitMessage || `chore(release): publish ${targets.length} package(s)`,
|
|
404
|
-
)
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
for (const pkg of targets) {
|
|
408
|
-
await publishPackage(pkg, opts)
|
|
409
|
-
}
|
|
410
|
-
}
|
package/src/semver.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// src/semver.js
|
|
2
|
-
import { inc, valid, } from 'semver'
|
|
3
|
-
|
|
4
|
-
export type BumpType =
|
|
5
|
-
| 'patch'
|
|
6
|
-
| 'minor'
|
|
7
|
-
| 'major'
|
|
8
|
-
| 'prepatch'
|
|
9
|
-
| 'preminor'
|
|
10
|
-
| 'premajor'
|
|
11
|
-
| 'prerelease'
|
|
12
|
-
|
|
13
|
-
export function bumpVersion(version: string, type: BumpType, preid?: string) {
|
|
14
|
-
let next
|
|
15
|
-
if (preid) {
|
|
16
|
-
next = inc(version, type as any, preid)
|
|
17
|
-
} else {
|
|
18
|
-
next = inc(version, type as any)
|
|
19
|
-
}
|
|
20
|
-
if (!next)
|
|
21
|
-
throw new Error(`Cannot bump invalid version "${version}" by "${type}"`)
|
|
22
|
-
return next
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function isSemver(version: string) {
|
|
26
|
-
return Boolean(valid(version))
|
|
27
|
-
}
|
package/src/sync-version.mjs
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readFile, writeFile, readdir } from 'node:fs/promises'
|
|
3
|
-
import { existsSync } from 'node:fs'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { pathToFileURL } from 'node:url'
|
|
6
|
-
|
|
7
|
-
const rootDir = process.cwd()
|
|
8
|
-
const ansi = (code) => (text) => `\x1b[${code}m${text}\x1b[0m`
|
|
9
|
-
const colors = {
|
|
10
|
-
cyan: ansi(36),
|
|
11
|
-
green: ansi(32),
|
|
12
|
-
yellow: ansi(33),
|
|
13
|
-
magenta: ansi(35),
|
|
14
|
-
red: ansi(31),
|
|
15
|
-
dim: ansi(2),
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const palette = ['cyan', 'green', 'yellow', 'magenta', 'red']
|
|
19
|
-
|
|
20
|
-
function manifestFilePath() {
|
|
21
|
-
return path.join(rootDir, 'scripts', 'packages.mjs')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function normalizeManifestPath(value) {
|
|
25
|
-
const absolute = path.resolve(rootDir, value || '')
|
|
26
|
-
let relative = path.relative(rootDir, absolute)
|
|
27
|
-
if (!relative) return ''
|
|
28
|
-
relative = relative.replace(/\\/g, '/')
|
|
29
|
-
return relative.replace(/^(?:\.\/)+/, '')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function colorFromSeed(seed) {
|
|
33
|
-
const normalized = `${seed}`.trim() || 'package'
|
|
34
|
-
let hash = 0
|
|
35
|
-
for (let i = 0; i < normalized.length; i++) {
|
|
36
|
-
hash = (hash * 31 + normalized.charCodeAt(i)) >>> 0
|
|
37
|
-
}
|
|
38
|
-
return palette[hash % palette.length]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isManifestMissing(error) {
|
|
42
|
-
if (typeof error !== 'object' || error === null) return false
|
|
43
|
-
const code = error.code
|
|
44
|
-
return code === 'ERR_MODULE_NOT_FOUND' || code === 'ENOENT'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function loadWorkspaceManifest() {
|
|
48
|
-
const manifestPath = manifestFilePath()
|
|
49
|
-
try {
|
|
50
|
-
const manifestModule = await import(pathToFileURL(manifestPath).href)
|
|
51
|
-
if (Array.isArray(manifestModule?.PACKAGE_MANIFEST)) {
|
|
52
|
-
return manifestModule.PACKAGE_MANIFEST
|
|
53
|
-
}
|
|
54
|
-
} catch (error) {
|
|
55
|
-
if (isManifestMissing(error)) return undefined
|
|
56
|
-
throw error
|
|
57
|
-
}
|
|
58
|
-
return undefined
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function inferManifestFromWorkspace() {
|
|
62
|
-
try {
|
|
63
|
-
const packagesDir = path.join(rootDir, 'packages')
|
|
64
|
-
const entries = await readdir(packagesDir, { withFileTypes: true })
|
|
65
|
-
const manifest = []
|
|
66
|
-
for (const entry of entries) {
|
|
67
|
-
if (!entry.isDirectory()) continue
|
|
68
|
-
const pkgJsonPath = path.join(packagesDir, entry.name, 'package.json')
|
|
69
|
-
try {
|
|
70
|
-
const raw = await readFile(pkgJsonPath, 'utf8')
|
|
71
|
-
const json = JSON.parse(raw)
|
|
72
|
-
const pkgName = (json.name || entry.name).trim() || entry.name
|
|
73
|
-
manifest.push({
|
|
74
|
-
name: pkgName,
|
|
75
|
-
path: normalizeManifestPath(path.relative(rootDir, path.join(packagesDir, entry.name))),
|
|
76
|
-
color: colorFromSeed(pkgName),
|
|
77
|
-
})
|
|
78
|
-
} catch {
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return manifest
|
|
83
|
-
} catch {
|
|
84
|
-
return []
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function mergeManifestEntries(inferred, overrides) {
|
|
89
|
-
const normalizedOverrides = new Map()
|
|
90
|
-
overrides?.forEach((entry) => {
|
|
91
|
-
const normalized = normalizeManifestPath(entry.path)
|
|
92
|
-
if (!normalized) return
|
|
93
|
-
normalizedOverrides.set(normalized, { ...entry, path: normalized })
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
const merged = []
|
|
97
|
-
for (const baseEntry of inferred) {
|
|
98
|
-
const normalized = normalizeManifestPath(baseEntry.path)
|
|
99
|
-
const override = normalizedOverrides.get(normalized)
|
|
100
|
-
if (override) {
|
|
101
|
-
normalizedOverrides.delete(normalized)
|
|
102
|
-
const name = override.name || baseEntry.name
|
|
103
|
-
const color = override.color ?? baseEntry.color ?? colorFromSeed(name)
|
|
104
|
-
merged.push({ name, path: normalized, color })
|
|
105
|
-
} else {
|
|
106
|
-
merged.push({ ...baseEntry, path: normalized })
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
normalizedOverrides.forEach((entry) => {
|
|
111
|
-
const name = entry.name || path.basename(entry.path) || 'package'
|
|
112
|
-
const color = entry.color ?? colorFromSeed(name)
|
|
113
|
-
merged.push({ name, path: entry.path, color })
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
return merged
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const registerPackageEntry = (map, pkgPath, entry) => {
|
|
120
|
-
if (!map.has(pkgPath)) {
|
|
121
|
-
map.set(pkgPath, entry)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function collectPackageEntries() {
|
|
126
|
-
const packagesDir = path.join(rootDir, 'packages')
|
|
127
|
-
const records = new Map()
|
|
128
|
-
|
|
129
|
-
registerPackageEntry(records, path.join(rootDir, 'package.json'), {
|
|
130
|
-
name: 'workspace root',
|
|
131
|
-
pkgPath: path.join(rootDir, 'package.json'),
|
|
132
|
-
color: 'magenta',
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const [workspaceManifest, inferred] = await Promise.all([
|
|
136
|
-
loadWorkspaceManifest(),
|
|
137
|
-
inferManifestFromWorkspace(),
|
|
138
|
-
])
|
|
139
|
-
const manifestEntries = mergeManifestEntries(inferred, workspaceManifest)
|
|
140
|
-
|
|
141
|
-
for (const manifestEntry of manifestEntries) {
|
|
142
|
-
if (!manifestEntry.path) continue
|
|
143
|
-
const pkgPath = path.join(rootDir, manifestEntry.path, 'package.json')
|
|
144
|
-
registerPackageEntry(records, pkgPath, {
|
|
145
|
-
name: manifestEntry.name,
|
|
146
|
-
pkgPath,
|
|
147
|
-
color: manifestEntry.color ?? colorFromSeed(manifestEntry.name),
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const entries = await readdir(packagesDir, { withFileTypes: true })
|
|
153
|
-
for (const entry of entries) {
|
|
154
|
-
if (!entry.isDirectory()) continue
|
|
155
|
-
const pkgPath = path.join(packagesDir, entry.name, 'package.json')
|
|
156
|
-
registerPackageEntry(records, pkgPath, {
|
|
157
|
-
name: entry.name,
|
|
158
|
-
pkgPath,
|
|
159
|
-
color: colorFromSeed(entry.name),
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error(`Failed to read packages directory: ${error}`)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return Array.from(records.values())
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function main() {
|
|
170
|
-
const version = process.argv[2]
|
|
171
|
-
|
|
172
|
-
if (!version) {
|
|
173
|
-
console.error('Usage: pnpm sync <version>')
|
|
174
|
-
process.exitCode = 1
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const semverPattern = /^\d+\.\d+\.\d+(?:[-+].*)?$/
|
|
179
|
-
if (!semverPattern.test(version)) {
|
|
180
|
-
console.error(`Invalid version "${version}". Expected semver (e.g. 1.6.0).`)
|
|
181
|
-
process.exitCode = 1
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const packageEntries = await collectPackageEntries()
|
|
186
|
-
|
|
187
|
-
await Promise.all(
|
|
188
|
-
packageEntries.map(async (entry) => {
|
|
189
|
-
if (!existsSync(entry.pkgPath)) {
|
|
190
|
-
console.warn(
|
|
191
|
-
`${colors[entry.color ?? 'cyan']('●')} ${colors.yellow(
|
|
192
|
-
`Skipping missing package file: ${path.relative(rootDir, entry.pkgPath)}`,
|
|
193
|
-
)}`,
|
|
194
|
-
)
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
const raw = await readFile(entry.pkgPath, 'utf8')
|
|
198
|
-
const json = JSON.parse(raw)
|
|
199
|
-
const previousVersion = json.version ?? 'unknown'
|
|
200
|
-
json.version = version
|
|
201
|
-
const formatted = `${JSON.stringify(json, null, 2)}\n`
|
|
202
|
-
await writeFile(entry.pkgPath, formatted)
|
|
203
|
-
console.log(
|
|
204
|
-
`${colors[entry.color ?? 'cyan']('●')} ${colors.cyan('[sync]')} ${path.relative(rootDir, entry.pkgPath)} ${colors.dim(`${previousVersion} -> ${version}`)}`,
|
|
205
|
-
)
|
|
206
|
-
}),
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
main().catch((error) => {
|
|
211
|
-
console.error(error)
|
|
212
|
-
process.exitCode = 1
|
|
213
|
-
})
|
package/src/utils/colors.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// src/utils/colors.js
|
|
2
|
-
const ansi = (code: number) => (text: string) => `\x1b[${code}m${text}\x1b[0m`
|
|
3
|
-
export const colors = {
|
|
4
|
-
cyan: ansi(36),
|
|
5
|
-
green: ansi(32),
|
|
6
|
-
yellow: ansi(33),
|
|
7
|
-
magenta: ansi(35),
|
|
8
|
-
red: ansi(31),
|
|
9
|
-
bold: ansi(1),
|
|
10
|
-
dim: ansi(2),
|
|
11
|
-
}
|
package/src/utils/log.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// src/utils/log.js
|
|
2
|
-
import { colors } from './colors.js'
|
|
3
|
-
|
|
4
|
-
export type PackageColor = 'cyan' | 'green' | 'yellow' | 'magenta' | 'red'
|
|
5
|
-
export const defaultPackageColor: PackageColor = 'cyan'
|
|
6
|
-
export const globalEmoji = '🚀'
|
|
7
|
-
|
|
8
|
-
export interface LoadedPackage {
|
|
9
|
-
dirName: string
|
|
10
|
-
path: string
|
|
11
|
-
packageJsonPath: string
|
|
12
|
-
json: Record<string, any>
|
|
13
|
-
version: string
|
|
14
|
-
name: string
|
|
15
|
-
color: PackageColor
|
|
16
|
-
substitute: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const formatPkgName = (pkg: LoadedPackage) => {
|
|
20
|
-
const primary = pkg.substitute
|
|
21
|
-
const displayName = pkg.name ?? pkg.dirName
|
|
22
|
-
const fileLabel = pkg.dirName
|
|
23
|
-
const colorizer = colors[pkg.color ?? defaultPackageColor]
|
|
24
|
-
return `${colorizer(colors.bold(primary))} ${colors.dim(
|
|
25
|
-
`(${displayName}) [${fileLabel}]`,
|
|
26
|
-
)}`
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const logPkg = (
|
|
30
|
-
pkg: LoadedPackage,
|
|
31
|
-
message: string,
|
|
32
|
-
colorizer = colors.cyan,
|
|
33
|
-
) => {
|
|
34
|
-
const label = colors[pkg.color ?? defaultPackageColor](pkg.substitute)
|
|
35
|
-
console.log(`${label} ${colorizer(message)}`)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const logGlobal = (message: string, colorizer = colors.magenta) => {
|
|
39
|
-
console.log(`${globalEmoji} ${colorizer(message)}`)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export { colors }
|