pnpm-catalog-updates 1.0.3 → 1.1.0
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/README.md +15 -0
- package/dist/index.js +22031 -10684
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/src/cli/__tests__/commandRegistrar.test.ts +248 -0
- package/src/cli/commandRegistrar.ts +785 -0
- package/src/cli/commands/__tests__/aiCommand.test.ts +161 -0
- package/src/cli/commands/__tests__/analyzeCommand.test.ts +283 -0
- package/src/cli/commands/__tests__/checkCommand.test.ts +435 -0
- package/src/cli/commands/__tests__/graphCommand.test.ts +312 -0
- package/src/cli/commands/__tests__/initCommand.test.ts +317 -0
- package/src/cli/commands/__tests__/rollbackCommand.test.ts +400 -0
- package/src/cli/commands/__tests__/securityCommand.test.ts +467 -0
- package/src/cli/commands/__tests__/themeCommand.test.ts +166 -0
- package/src/cli/commands/__tests__/updateCommand.test.ts +720 -0
- package/src/cli/commands/__tests__/workspaceCommand.test.ts +286 -0
- package/src/cli/commands/aiCommand.ts +163 -0
- package/src/cli/commands/analyzeCommand.ts +219 -0
- package/src/cli/commands/checkCommand.ts +91 -98
- package/src/cli/commands/graphCommand.ts +475 -0
- package/src/cli/commands/initCommand.ts +64 -54
- package/src/cli/commands/rollbackCommand.ts +334 -0
- package/src/cli/commands/securityCommand.ts +165 -100
- package/src/cli/commands/themeCommand.ts +148 -0
- package/src/cli/commands/updateCommand.ts +215 -263
- package/src/cli/commands/workspaceCommand.ts +73 -0
- package/src/cli/constants/cliChoices.ts +93 -0
- package/src/cli/formatters/__tests__/__snapshots__/outputFormatter.test.ts.snap +557 -0
- package/src/cli/formatters/__tests__/ciFormatter.test.ts +526 -0
- package/src/cli/formatters/__tests__/outputFormatter.test.ts +448 -0
- package/src/cli/formatters/__tests__/progressBar.test.ts +709 -0
- package/src/cli/formatters/ciFormatter.ts +964 -0
- package/src/cli/formatters/colorUtils.ts +145 -0
- package/src/cli/formatters/outputFormatter.ts +615 -332
- package/src/cli/formatters/progressBar.ts +43 -52
- package/src/cli/formatters/versionFormatter.ts +132 -0
- package/src/cli/handlers/aiAnalysisHandler.ts +205 -0
- package/src/cli/handlers/changelogHandler.ts +113 -0
- package/src/cli/handlers/index.ts +9 -0
- package/src/cli/handlers/installHandler.ts +130 -0
- package/src/cli/index.ts +175 -726
- package/src/cli/interactive/InteractiveOptionsCollector.ts +387 -0
- package/src/cli/interactive/interactivePrompts.ts +189 -83
- package/src/cli/interactive/optionUtils.ts +89 -0
- package/src/cli/themes/colorTheme.ts +43 -16
- package/src/cli/utils/cliOutput.ts +118 -0
- package/src/cli/utils/commandHelpers.ts +249 -0
- package/src/cli/validators/commandValidator.ts +321 -336
- package/src/cli/validators/index.ts +37 -2
- package/src/cli/options/globalOptions.ts +0 -437
- package/src/cli/options/index.ts +0 -5
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* with multiple styles and themes.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { t } from '@pcu/utils'
|
|
8
9
|
import chalk from 'chalk'
|
|
9
10
|
|
|
10
11
|
export interface ProgressBarOptions {
|
|
@@ -24,7 +25,7 @@ export class ProgressBar {
|
|
|
24
25
|
private showSpeed: boolean
|
|
25
26
|
|
|
26
27
|
constructor(options: ProgressBarOptions = {}) {
|
|
27
|
-
this.text = options.text || '
|
|
28
|
+
this.text = options.text || t('progress.processing')
|
|
28
29
|
this.total = options.total || 0
|
|
29
30
|
this.style = options.style || 'default'
|
|
30
31
|
this.showSpeed = options.showSpeed ?? true
|
|
@@ -37,9 +38,6 @@ export class ProgressBar {
|
|
|
37
38
|
this.text = text || this.text
|
|
38
39
|
this.startTime = Date.now()
|
|
39
40
|
|
|
40
|
-
// 在开始新进度条前,彻底清理可能的残留内容
|
|
41
|
-
this.clearPreviousOutput()
|
|
42
|
-
|
|
43
41
|
// 强制使用percentageBar,即使没有total也要创建
|
|
44
42
|
// 这样可以避免spinner导致的冲突问题
|
|
45
43
|
const effectiveTotal = this.total > 0 ? this.total : 1 // 如果没有total,设为1避免除零错误
|
|
@@ -86,7 +84,9 @@ export class ProgressBar {
|
|
|
86
84
|
// 只使用percentageBar,不使用spinner
|
|
87
85
|
if (this.percentageBar) {
|
|
88
86
|
const successText = text || this.getCompletionText()
|
|
87
|
+
// complete() will clear the progress bar output
|
|
89
88
|
this.percentageBar.complete(successText)
|
|
89
|
+
// Now print the success message on a clean line
|
|
90
90
|
console.log(this.getSuccessMessage(successText))
|
|
91
91
|
this.percentageBar = null
|
|
92
92
|
}
|
|
@@ -99,6 +99,9 @@ export class ProgressBar {
|
|
|
99
99
|
// 只使用percentageBar,不使用spinner
|
|
100
100
|
if (this.percentageBar) {
|
|
101
101
|
const failText = text || this.getFailureText()
|
|
102
|
+
// Clear the progress bar first
|
|
103
|
+
this.percentageBar.complete(failText)
|
|
104
|
+
// Now print the failure message on a clean line
|
|
102
105
|
console.log(this.getFailureMessage(failText))
|
|
103
106
|
this.percentageBar = null
|
|
104
107
|
}
|
|
@@ -111,15 +114,15 @@ export class ProgressBar {
|
|
|
111
114
|
const elapsed = this.getElapsedTime()
|
|
112
115
|
switch (this.style) {
|
|
113
116
|
case 'gradient':
|
|
114
|
-
return `${chalk.magenta.bold('
|
|
117
|
+
return `${chalk.magenta.bold('*')} ${chalk.green(text)} ${chalk.gray(elapsed)}`
|
|
115
118
|
case 'fancy':
|
|
116
|
-
return `${chalk.cyan('
|
|
119
|
+
return `${chalk.cyan('*')} ${chalk.green.bold(text)} ${chalk.cyan('*')} ${chalk.gray(elapsed)}`
|
|
117
120
|
case 'minimal':
|
|
118
121
|
return chalk.green(text)
|
|
119
122
|
case 'rainbow':
|
|
120
|
-
return `${chalk.magenta('
|
|
123
|
+
return `${chalk.magenta('~')} ${chalk.green(text)} ${chalk.gray(elapsed)}`
|
|
121
124
|
case 'neon':
|
|
122
|
-
return `${chalk.green.bold(
|
|
125
|
+
return `${chalk.green.bold(`⚡ ${t('progress.success').toUpperCase()}`)} ${chalk.green(text)} ${chalk.gray(elapsed)}`
|
|
123
126
|
default:
|
|
124
127
|
return `${chalk.green('✅')} ${chalk.green(text)} ${chalk.gray(elapsed)}`
|
|
125
128
|
}
|
|
@@ -132,15 +135,15 @@ export class ProgressBar {
|
|
|
132
135
|
const elapsed = this.getElapsedTime()
|
|
133
136
|
switch (this.style) {
|
|
134
137
|
case 'gradient':
|
|
135
|
-
return `${chalk.red.bold('
|
|
138
|
+
return `${chalk.red.bold('!!')} ${chalk.red(text)} ${chalk.gray(elapsed)}`
|
|
136
139
|
case 'fancy':
|
|
137
|
-
return `${chalk.red('
|
|
140
|
+
return `${chalk.red('!!')} ${chalk.red.bold(text)} ${chalk.red('!!')} ${chalk.gray(elapsed)}`
|
|
138
141
|
case 'minimal':
|
|
139
142
|
return chalk.red(text)
|
|
140
143
|
case 'rainbow':
|
|
141
144
|
return `${chalk.red('⚠️')} ${chalk.red(text)} ${chalk.gray(elapsed)}`
|
|
142
145
|
case 'neon':
|
|
143
|
-
return `${chalk.red.bold(
|
|
146
|
+
return `${chalk.red.bold(`⚡ ${t('progress.error').toUpperCase()}`)} ${chalk.red(text)} ${chalk.gray(elapsed)}`
|
|
144
147
|
default:
|
|
145
148
|
return `${chalk.red('❌')} ${chalk.red(text)} ${chalk.gray(elapsed)}`
|
|
146
149
|
}
|
|
@@ -152,14 +155,14 @@ export class ProgressBar {
|
|
|
152
155
|
private getCompletionText(): string {
|
|
153
156
|
const elapsed = this.getElapsedTime()
|
|
154
157
|
const speed = this.getAverageSpeed()
|
|
155
|
-
return `${this.text} completed ${speed} ${elapsed}`
|
|
158
|
+
return `${this.text} ${t('progress.completed')} ${speed} ${elapsed}`
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
/**
|
|
159
162
|
* Get failure text
|
|
160
163
|
*/
|
|
161
164
|
private getFailureText(): string {
|
|
162
|
-
return `${this.text} failed`
|
|
165
|
+
return `${this.text} ${t('progress.failed')}`
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
/**
|
|
@@ -189,6 +192,9 @@ export class ProgressBar {
|
|
|
189
192
|
// 只使用percentageBar,不使用spinner
|
|
190
193
|
if (this.percentageBar) {
|
|
191
194
|
const warnText = text || this.text
|
|
195
|
+
// Clear the progress bar first
|
|
196
|
+
this.percentageBar.complete(warnText)
|
|
197
|
+
// Now print the warning message on a clean line
|
|
192
198
|
console.log(this.getWarningMessage(warnText))
|
|
193
199
|
this.percentageBar = null
|
|
194
200
|
}
|
|
@@ -201,6 +207,9 @@ export class ProgressBar {
|
|
|
201
207
|
// 只使用percentageBar,不使用spinner
|
|
202
208
|
if (this.percentageBar) {
|
|
203
209
|
const infoText = text || this.text
|
|
210
|
+
// Clear the progress bar first
|
|
211
|
+
this.percentageBar.complete(infoText)
|
|
212
|
+
// Now print the info message on a clean line
|
|
204
213
|
console.log(this.getInfoMessage(infoText))
|
|
205
214
|
this.percentageBar = null
|
|
206
215
|
}
|
|
@@ -213,7 +222,7 @@ export class ProgressBar {
|
|
|
213
222
|
const elapsed = this.getElapsedTime()
|
|
214
223
|
switch (this.style) {
|
|
215
224
|
case 'gradient':
|
|
216
|
-
return `${chalk.yellow.bold('
|
|
225
|
+
return `${chalk.yellow.bold('!')} ${chalk.yellow(text)} ${chalk.gray(elapsed)}`
|
|
217
226
|
case 'fancy':
|
|
218
227
|
return `${chalk.yellow('⚠️')} ${chalk.yellow.bold(text)} ${chalk.yellow('⚠️')} ${chalk.gray(elapsed)}`
|
|
219
228
|
case 'minimal':
|
|
@@ -221,7 +230,7 @@ export class ProgressBar {
|
|
|
221
230
|
case 'rainbow':
|
|
222
231
|
return `${chalk.yellow('⚠️')} ${chalk.yellow(text)} ${chalk.gray(elapsed)}`
|
|
223
232
|
case 'neon':
|
|
224
|
-
return `${chalk.yellow.bold(
|
|
233
|
+
return `${chalk.yellow.bold(`⚡ ${t('progress.warning').toUpperCase()}`)} ${chalk.yellow(text)} ${chalk.gray(elapsed)}`
|
|
225
234
|
default:
|
|
226
235
|
return `${chalk.yellow('⚠️')} ${chalk.yellow(text)} ${chalk.gray(elapsed)}`
|
|
227
236
|
}
|
|
@@ -234,17 +243,17 @@ export class ProgressBar {
|
|
|
234
243
|
const elapsed = this.getElapsedTime()
|
|
235
244
|
switch (this.style) {
|
|
236
245
|
case 'gradient':
|
|
237
|
-
return `${chalk.blue.bold('
|
|
246
|
+
return `${chalk.blue.bold('[i]')} ${chalk.blue(text)} ${chalk.gray(elapsed)}`
|
|
238
247
|
case 'fancy':
|
|
239
|
-
return `${chalk.blue('
|
|
248
|
+
return `${chalk.blue('[i]')} ${chalk.blue.bold(text)} ${chalk.blue('[i]')} ${chalk.gray(elapsed)}`
|
|
240
249
|
case 'minimal':
|
|
241
250
|
return chalk.blue(text)
|
|
242
251
|
case 'rainbow':
|
|
243
|
-
return `${chalk.blue('
|
|
252
|
+
return `${chalk.blue('[i]')} ${chalk.blue(text)} ${chalk.gray(elapsed)}`
|
|
244
253
|
case 'neon':
|
|
245
|
-
return `${chalk.blue.bold(
|
|
254
|
+
return `${chalk.blue.bold(`[i] ${t('progress.info').toUpperCase()}`)} ${chalk.blue(text)} ${chalk.gray(elapsed)}`
|
|
246
255
|
default:
|
|
247
|
-
return `${chalk.blue('
|
|
256
|
+
return `${chalk.blue('[i]')} ${chalk.blue(text)} ${chalk.gray(elapsed)}`
|
|
248
257
|
}
|
|
249
258
|
}
|
|
250
259
|
|
|
@@ -254,22 +263,12 @@ export class ProgressBar {
|
|
|
254
263
|
stop(): void {
|
|
255
264
|
// 只使用percentageBar,不使用spinner
|
|
256
265
|
if (this.percentageBar) {
|
|
266
|
+
// Clear the progress bar before stopping
|
|
267
|
+
this.percentageBar.complete('')
|
|
257
268
|
this.percentageBar = null
|
|
258
269
|
}
|
|
259
270
|
}
|
|
260
271
|
|
|
261
|
-
/**
|
|
262
|
-
* Clear previous output to prevent residual progress bars
|
|
263
|
-
*/
|
|
264
|
-
private clearPreviousOutput(): void {
|
|
265
|
-
// 清理可能的残留进度条显示(最多清理5行,应该足够了)
|
|
266
|
-
for (let i = 0; i < 5; i++) {
|
|
267
|
-
process.stdout.write('\x1b[1A\r\x1b[K') // 上移一行并清除
|
|
268
|
-
}
|
|
269
|
-
// 确保光标在正确位置
|
|
270
|
-
process.stdout.write('\r')
|
|
271
|
-
}
|
|
272
|
-
|
|
273
272
|
/**
|
|
274
273
|
* Create a multi-step progress indicator
|
|
275
274
|
*/
|
|
@@ -345,7 +344,7 @@ export class MultiStepProgress {
|
|
|
345
344
|
}
|
|
346
345
|
|
|
347
346
|
start(): void {
|
|
348
|
-
console.log(chalk.bold(
|
|
347
|
+
console.log(chalk.bold(`\n📋 ${t('progress.steps')}:\n`))
|
|
349
348
|
this.renderSteps()
|
|
350
349
|
}
|
|
351
350
|
|
|
@@ -362,7 +361,7 @@ export class MultiStepProgress {
|
|
|
362
361
|
}
|
|
363
362
|
|
|
364
363
|
complete(): void {
|
|
365
|
-
console.log(chalk.green(
|
|
364
|
+
console.log(chalk.green(`\n🎉 ${t('progress.allStepsCompleted')}\n`))
|
|
366
365
|
}
|
|
367
366
|
|
|
368
367
|
private renderSteps(): void {
|
|
@@ -403,24 +402,9 @@ export class PercentageProgressBar {
|
|
|
403
402
|
this.startTime = Date.now()
|
|
404
403
|
this.isFirstRender = true // 重置首次渲染标记
|
|
405
404
|
|
|
406
|
-
// 清理可能的残留输出
|
|
407
|
-
this.clearPreviousLines()
|
|
408
|
-
|
|
409
405
|
this.render()
|
|
410
406
|
}
|
|
411
407
|
|
|
412
|
-
/**
|
|
413
|
-
* Clear any previous output lines to prevent conflicts
|
|
414
|
-
*/
|
|
415
|
-
private clearPreviousLines(): void {
|
|
416
|
-
// 更强力的清理:清理多行可能的残留内容
|
|
417
|
-
for (let i = 0; i < 6; i++) {
|
|
418
|
-
process.stdout.write('\x1b[1A\r\x1b[2K') // 上移一行并完全清除该行
|
|
419
|
-
}
|
|
420
|
-
// 回到起始位置
|
|
421
|
-
process.stdout.write('\r')
|
|
422
|
-
}
|
|
423
|
-
|
|
424
408
|
update(current: number, text?: string): void {
|
|
425
409
|
this.current = current
|
|
426
410
|
if (text) this.text = text
|
|
@@ -436,8 +420,15 @@ export class PercentageProgressBar {
|
|
|
436
420
|
complete(text?: string): void {
|
|
437
421
|
this.current = this.total
|
|
438
422
|
if (text) this.text = text
|
|
439
|
-
|
|
440
|
-
|
|
423
|
+
|
|
424
|
+
// Clear the multi-line progress bar output completely before final state
|
|
425
|
+
if (this.useMultiLine && !this.isFirstRender) {
|
|
426
|
+
// Move up 2 lines (text line + progress bar line) and clear them
|
|
427
|
+
process.stdout.write('\x1b[2A\r\x1b[2K\x1b[1B\x1b[2K\x1b[1A\r')
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Don't render anymore - just let the caller handle the final message
|
|
431
|
+
this.lastRender = ''
|
|
441
432
|
}
|
|
442
433
|
|
|
443
434
|
private render(): void {
|
|
@@ -678,7 +669,7 @@ export class BatchProgressManager {
|
|
|
678
669
|
|
|
679
670
|
console.log(
|
|
680
671
|
chalk.cyan(
|
|
681
|
-
`📊
|
|
672
|
+
`📊 ${t('progress.overallProgress')}: ${percentage}% (${this.completedOperations}/${this.totalOperations})`
|
|
682
673
|
)
|
|
683
674
|
)
|
|
684
675
|
if (text) {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Formatter Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides version parsing and colorization utilities for CLI output.
|
|
5
|
+
* Extracted from OutputFormatter for better maintainability.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk'
|
|
9
|
+
import type { ColorUtils } from './colorUtils.js'
|
|
10
|
+
|
|
11
|
+
export interface VersionParts {
|
|
12
|
+
major: string
|
|
13
|
+
minor: string
|
|
14
|
+
patch: string
|
|
15
|
+
extra: string
|
|
16
|
+
prefix: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ColorizedVersions {
|
|
20
|
+
currentColored: string
|
|
21
|
+
latestColored: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Version formatting utilities for CLI output
|
|
26
|
+
*/
|
|
27
|
+
export class VersionFormatter {
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly colorUtils: ColorUtils,
|
|
30
|
+
private readonly useColor: boolean = true
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse a version string into its component parts
|
|
35
|
+
*/
|
|
36
|
+
parseVersion(version: string): VersionParts {
|
|
37
|
+
// Remove leading ^ or ~ or other prefix characters
|
|
38
|
+
const cleanVersion = version.replace(/^[\^~>=<]+/, '')
|
|
39
|
+
const parts = cleanVersion.split('.')
|
|
40
|
+
return {
|
|
41
|
+
major: parts[0] || '0',
|
|
42
|
+
minor: parts[1] || '0',
|
|
43
|
+
patch: parts[2] || '0',
|
|
44
|
+
extra: parts.slice(3).join('.'),
|
|
45
|
+
prefix: version.substring(0, version.length - cleanVersion.length),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Colorize version differences between current and latest
|
|
51
|
+
*/
|
|
52
|
+
colorizeVersionDiff(current: string, latest: string, updateType: string): ColorizedVersions {
|
|
53
|
+
if (!this.useColor) {
|
|
54
|
+
return { currentColored: current, latestColored: latest }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const currentParts = this.parseVersion(current)
|
|
58
|
+
const latestParts = this.parseVersion(latest)
|
|
59
|
+
|
|
60
|
+
// Determine color based on update type for highlighting differences
|
|
61
|
+
const diffColor = this.colorUtils.getUpdateTypeColor(updateType)
|
|
62
|
+
|
|
63
|
+
// Check which parts are different
|
|
64
|
+
const majorChanged = currentParts.major !== latestParts.major
|
|
65
|
+
const minorChanged = currentParts.minor !== latestParts.minor
|
|
66
|
+
const patchChanged = currentParts.patch !== latestParts.patch
|
|
67
|
+
const extraChanged = currentParts.extra !== latestParts.extra
|
|
68
|
+
|
|
69
|
+
// Build colored version strings
|
|
70
|
+
const currentColored = this.buildColoredVersion(
|
|
71
|
+
currentParts,
|
|
72
|
+
latestParts,
|
|
73
|
+
{ majorChanged, minorChanged, patchChanged, extraChanged },
|
|
74
|
+
true
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const latestColored = this.buildColoredVersion(
|
|
78
|
+
latestParts,
|
|
79
|
+
currentParts,
|
|
80
|
+
{ majorChanged, minorChanged, patchChanged, extraChanged },
|
|
81
|
+
false,
|
|
82
|
+
diffColor
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return { currentColored, latestColored }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build a colored version string
|
|
90
|
+
*/
|
|
91
|
+
private buildColoredVersion(
|
|
92
|
+
parts: VersionParts,
|
|
93
|
+
compareParts: VersionParts,
|
|
94
|
+
changes: {
|
|
95
|
+
majorChanged: boolean
|
|
96
|
+
minorChanged: boolean
|
|
97
|
+
patchChanged: boolean
|
|
98
|
+
extraChanged: boolean
|
|
99
|
+
},
|
|
100
|
+
isCurrent: boolean,
|
|
101
|
+
diffColor?: typeof chalk
|
|
102
|
+
): string {
|
|
103
|
+
const colorPart = (part: string, comparePart: string, isChanged: boolean) => {
|
|
104
|
+
if (isCurrent) {
|
|
105
|
+
// For current version: dim white for changed parts
|
|
106
|
+
if (isChanged && part !== comparePart) {
|
|
107
|
+
return chalk.dim.white(part)
|
|
108
|
+
}
|
|
109
|
+
return chalk.white(part)
|
|
110
|
+
} else {
|
|
111
|
+
// For latest version: highlight changed parts with update type color
|
|
112
|
+
if (isChanged && part !== comparePart && diffColor) {
|
|
113
|
+
return diffColor(part)
|
|
114
|
+
}
|
|
115
|
+
return chalk.white(part)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let result = parts.prefix
|
|
120
|
+
result += colorPart(parts.major, compareParts.major, changes.majorChanged)
|
|
121
|
+
result += '.'
|
|
122
|
+
result += colorPart(parts.minor, compareParts.minor, changes.minorChanged)
|
|
123
|
+
result += '.'
|
|
124
|
+
result += colorPart(parts.patch, compareParts.patch, changes.patchChanged)
|
|
125
|
+
|
|
126
|
+
if (parts.extra) {
|
|
127
|
+
result += `.${colorPart(parts.extra, compareParts.extra, changes.extraChanged)}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Analysis Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles AI analysis display and formatting for the update command.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
AIAnalysisService,
|
|
9
|
+
type AnalysisResult,
|
|
10
|
+
type AnalysisType,
|
|
11
|
+
type UpdatePlan,
|
|
12
|
+
type WorkspaceService,
|
|
13
|
+
} from '@pcu/core'
|
|
14
|
+
import { logger, t } from '@pcu/utils'
|
|
15
|
+
import chalk from 'chalk'
|
|
16
|
+
import { cliOutput } from '../utils/cliOutput.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options for AI analysis
|
|
20
|
+
*/
|
|
21
|
+
export interface AIAnalysisOptions {
|
|
22
|
+
workspace?: string
|
|
23
|
+
provider?: string
|
|
24
|
+
analysisType?: AnalysisType
|
|
25
|
+
skipCache?: boolean
|
|
26
|
+
verbose?: boolean
|
|
27
|
+
force?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handles AI analysis for package updates
|
|
32
|
+
*/
|
|
33
|
+
export class AIAnalysisHandler {
|
|
34
|
+
private readonly workspaceService: WorkspaceService
|
|
35
|
+
|
|
36
|
+
constructor(workspaceService: WorkspaceService) {
|
|
37
|
+
this.workspaceService = workspaceService
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Perform AI analysis and display results
|
|
42
|
+
*/
|
|
43
|
+
async analyzeAndDisplay(plan: UpdatePlan, options: AIAnalysisOptions): Promise<void> {
|
|
44
|
+
cliOutput.print(
|
|
45
|
+
chalk.blue(`\n🤖 ${t('command.update.runningBatchAI', { count: plan.updates.length })}`)
|
|
46
|
+
)
|
|
47
|
+
cliOutput.print(chalk.gray(`${t('command.update.batchAIHint')}\n`))
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const aiResult = await this.performBatchAnalysis(plan, options)
|
|
51
|
+
this.displayResults(aiResult, options.force)
|
|
52
|
+
} catch (aiError) {
|
|
53
|
+
logger.warn('AI batch analysis failed', {
|
|
54
|
+
error: aiError instanceof Error ? aiError.message : String(aiError),
|
|
55
|
+
packageCount: plan.updates.length,
|
|
56
|
+
provider: options.provider,
|
|
57
|
+
})
|
|
58
|
+
cliOutput.warn(chalk.yellow(`\n⚠️ ${t('command.update.aiBatchFailed')}`))
|
|
59
|
+
if (options.verbose) {
|
|
60
|
+
cliOutput.warn(chalk.gray(String(aiError)))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Perform batch AI analysis for all packages in the update plan
|
|
67
|
+
*/
|
|
68
|
+
private async performBatchAnalysis(
|
|
69
|
+
plan: UpdatePlan,
|
|
70
|
+
options: AIAnalysisOptions
|
|
71
|
+
): Promise<AnalysisResult> {
|
|
72
|
+
const workspacePath = options.workspace || process.cwd()
|
|
73
|
+
const workspaceInfo = await this.workspaceService.getWorkspaceInfo(workspacePath)
|
|
74
|
+
|
|
75
|
+
const aiService = new AIAnalysisService({
|
|
76
|
+
config: {
|
|
77
|
+
preferredProvider: options.provider === 'auto' ? 'auto' : options.provider,
|
|
78
|
+
cache: { enabled: !options.skipCache, ttl: 3600 },
|
|
79
|
+
fallback: { enabled: true, useRuleEngine: true },
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Convert planned updates to PackageUpdateInfo format
|
|
84
|
+
const packages = plan.updates.map((update) => ({
|
|
85
|
+
name: update.packageName,
|
|
86
|
+
currentVersion: update.currentVersion,
|
|
87
|
+
targetVersion: update.newVersion,
|
|
88
|
+
updateType: update.updateType,
|
|
89
|
+
catalogName: update.catalogName,
|
|
90
|
+
}))
|
|
91
|
+
|
|
92
|
+
const wsInfo = {
|
|
93
|
+
name: workspaceInfo.name,
|
|
94
|
+
path: workspaceInfo.path,
|
|
95
|
+
packageCount: workspaceInfo.packageCount,
|
|
96
|
+
catalogCount: workspaceInfo.catalogCount,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return aiService.analyzeWithChunking(packages, wsInfo, {
|
|
100
|
+
analysisType: options.analysisType || 'impact',
|
|
101
|
+
skipCache: options.skipCache,
|
|
102
|
+
chunking: {
|
|
103
|
+
enabled: true,
|
|
104
|
+
threshold: 15,
|
|
105
|
+
chunkSize: 10,
|
|
106
|
+
onProgress: options.verbose
|
|
107
|
+
? (progress) => {
|
|
108
|
+
cliOutput.print(
|
|
109
|
+
chalk.gray(
|
|
110
|
+
` ${t('command.update.processingChunks', {
|
|
111
|
+
current: progress.currentChunk,
|
|
112
|
+
total: progress.totalChunks,
|
|
113
|
+
})} (${progress.percentComplete}%)`
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
: undefined,
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Display AI analysis results
|
|
124
|
+
*/
|
|
125
|
+
private displayResults(aiResult: AnalysisResult, force?: boolean): void {
|
|
126
|
+
cliOutput.print(chalk.blue(`\n📊 ${t('command.update.aiResults')}`))
|
|
127
|
+
cliOutput.print(chalk.gray('─'.repeat(60)))
|
|
128
|
+
cliOutput.print(chalk.cyan(t('command.update.provider', { provider: aiResult.provider })))
|
|
129
|
+
cliOutput.print(
|
|
130
|
+
chalk.cyan(
|
|
131
|
+
t('command.update.confidence', { confidence: (aiResult.confidence * 100).toFixed(0) })
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
cliOutput.print(
|
|
135
|
+
chalk.cyan(t('command.update.processingTime', { time: aiResult.processingTimeMs }))
|
|
136
|
+
)
|
|
137
|
+
cliOutput.print(chalk.gray('─'.repeat(60)))
|
|
138
|
+
cliOutput.print(chalk.yellow(`\n📝 ${t('command.update.summary')}`))
|
|
139
|
+
cliOutput.print(aiResult.summary)
|
|
140
|
+
|
|
141
|
+
if (aiResult.recommendations.length > 0) {
|
|
142
|
+
cliOutput.print(chalk.yellow(`\n📦 ${t('command.update.packageRecommendations')}`))
|
|
143
|
+
for (const rec of aiResult.recommendations) {
|
|
144
|
+
this.displayRecommendation(rec)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (aiResult.warnings && aiResult.warnings.length > 0) {
|
|
149
|
+
cliOutput.print(chalk.yellow(`\n⚠️ ${t('command.update.warnings')}`))
|
|
150
|
+
for (const warning of aiResult.warnings) {
|
|
151
|
+
cliOutput.print(chalk.yellow(` - ${warning}`))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
cliOutput.print(chalk.gray(`\n${'─'.repeat(60)}`))
|
|
156
|
+
|
|
157
|
+
const skipRecommendations = aiResult.recommendations.filter((r) => r.action === 'skip')
|
|
158
|
+
if (skipRecommendations.length > 0 && !force) {
|
|
159
|
+
cliOutput.print(
|
|
160
|
+
chalk.red(
|
|
161
|
+
`\n⛔ ${t('command.update.aiSkipRecommend', { count: skipRecommendations.length })}`
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
cliOutput.print(chalk.yellow(t('command.update.useForce')))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Display a single package recommendation
|
|
170
|
+
*/
|
|
171
|
+
private displayRecommendation(rec: AnalysisResult['recommendations'][0]): void {
|
|
172
|
+
const actionIcon = rec.action === 'update' ? '✅' : rec.action === 'skip' ? '❌' : '⚠️'
|
|
173
|
+
const riskColor =
|
|
174
|
+
rec.riskLevel === 'critical'
|
|
175
|
+
? chalk.red
|
|
176
|
+
: rec.riskLevel === 'high'
|
|
177
|
+
? chalk.yellow
|
|
178
|
+
: rec.riskLevel === 'medium'
|
|
179
|
+
? chalk.cyan
|
|
180
|
+
: chalk.green
|
|
181
|
+
|
|
182
|
+
cliOutput.print(
|
|
183
|
+
`\n ${actionIcon} ${chalk.bold(rec.package)}: ${rec.currentVersion} → ${rec.targetVersion}`
|
|
184
|
+
)
|
|
185
|
+
cliOutput.print(
|
|
186
|
+
` Action: ${chalk.bold(rec.action.toUpperCase())} | Risk: ${riskColor(rec.riskLevel)}`
|
|
187
|
+
)
|
|
188
|
+
cliOutput.print(` ${rec.reason}`)
|
|
189
|
+
|
|
190
|
+
if (rec.breakingChanges && rec.breakingChanges.length > 0) {
|
|
191
|
+
cliOutput.print(
|
|
192
|
+
chalk.red(
|
|
193
|
+
` ⚠️ ${t('command.update.breakingChanges', { changes: rec.breakingChanges.join(', ') })}`
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
if (rec.securityFixes && rec.securityFixes.length > 0) {
|
|
198
|
+
cliOutput.print(
|
|
199
|
+
chalk.green(
|
|
200
|
+
` 🔒 ${t('command.update.securityFixes', { fixes: rec.securityFixes.join(', ') })}`
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Changelog Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles changelog fetching and display for the update command.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ChangelogService, NpmRegistryService, type UpdatePlan } from '@pcu/core'
|
|
8
|
+
import { logger, t } from '@pcu/utils'
|
|
9
|
+
import chalk from 'chalk'
|
|
10
|
+
import { cliOutput } from '../utils/cliOutput.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handles changelog display for package updates
|
|
14
|
+
*/
|
|
15
|
+
export class ChangelogHandler {
|
|
16
|
+
private readonly changelogService: ChangelogService
|
|
17
|
+
private readonly npmRegistry: NpmRegistryService
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
this.changelogService = new ChangelogService({ cacheMinutes: 30 })
|
|
21
|
+
this.npmRegistry = new NpmRegistryService()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Display changelogs for all planned updates
|
|
26
|
+
*/
|
|
27
|
+
async displayChangelogs(plan: UpdatePlan, verbose?: boolean): Promise<void> {
|
|
28
|
+
cliOutput.print(chalk.blue(`\n📋 ${t('command.update.fetchingChangelogs')}`))
|
|
29
|
+
|
|
30
|
+
for (const update of plan.updates) {
|
|
31
|
+
await this.displayPackageChangelog(
|
|
32
|
+
update.packageName,
|
|
33
|
+
update.currentVersion,
|
|
34
|
+
update.newVersion,
|
|
35
|
+
verbose
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cliOutput.print(chalk.gray(`\n${'─'.repeat(60)}`))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Display changelog for a single package
|
|
44
|
+
*/
|
|
45
|
+
private async displayPackageChangelog(
|
|
46
|
+
packageName: string,
|
|
47
|
+
currentVersion: string,
|
|
48
|
+
newVersion: string,
|
|
49
|
+
verbose?: boolean
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
try {
|
|
52
|
+
// Get package info to find repository URL
|
|
53
|
+
const packageInfo = await this.npmRegistry.getPackageInfo(packageName)
|
|
54
|
+
const repository = this.extractRepository(packageInfo.repository)
|
|
55
|
+
|
|
56
|
+
// Fetch changelog between versions
|
|
57
|
+
const changelog = await this.changelogService.getChangelog(
|
|
58
|
+
packageName,
|
|
59
|
+
currentVersion,
|
|
60
|
+
newVersion,
|
|
61
|
+
repository
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// Display formatted changelog
|
|
65
|
+
cliOutput.print(chalk.yellow(`\n📦 ${packageName}`))
|
|
66
|
+
cliOutput.print(chalk.gray(` ${currentVersion} → ${newVersion}`))
|
|
67
|
+
cliOutput.print(this.changelogService.formatChangelog(changelog, verbose))
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.debug('Failed to fetch changelog', {
|
|
70
|
+
package: packageName,
|
|
71
|
+
error: error instanceof Error ? error.message : String(error),
|
|
72
|
+
})
|
|
73
|
+
this.displayFallbackInfo(packageName, currentVersion, newVersion)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Extract repository info from package.json repository field
|
|
79
|
+
*/
|
|
80
|
+
private extractRepository(repository: unknown): { type: string; url: string } | undefined {
|
|
81
|
+
if (!repository) return undefined
|
|
82
|
+
|
|
83
|
+
if (typeof repository === 'string') {
|
|
84
|
+
return { type: 'git', url: repository }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof repository === 'object' && repository !== null) {
|
|
88
|
+
const repo = repository as { type?: string; url?: string }
|
|
89
|
+
if (repo.url) {
|
|
90
|
+
return { type: repo.type || 'git', url: repo.url }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return undefined
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Display fallback info when changelog is not available
|
|
99
|
+
*/
|
|
100
|
+
private displayFallbackInfo(
|
|
101
|
+
packageName: string,
|
|
102
|
+
currentVersion: string,
|
|
103
|
+
newVersion: string
|
|
104
|
+
): void {
|
|
105
|
+
cliOutput.print(chalk.yellow(`\n📦 ${packageName}`))
|
|
106
|
+
cliOutput.print(chalk.gray(` ${currentVersion} → ${newVersion}`))
|
|
107
|
+
cliOutput.print(
|
|
108
|
+
chalk.gray(
|
|
109
|
+
` 📋 ${t('command.update.changelogUnavailable')}: https://www.npmjs.com/package/${packageName}?activeTab=versions`
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
}
|