pnpm-catalog-updates 0.7.19 → 1.0.1
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 +27 -0
- package/dist/index.js +10212 -6761
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
- package/src/cli/commands/checkCommand.ts +62 -62
- package/src/cli/commands/initCommand.ts +90 -90
- package/src/cli/commands/securityCommand.ts +172 -172
- package/src/cli/commands/updateCommand.ts +227 -68
- package/src/cli/formatters/outputFormatter.ts +500 -280
- package/src/cli/formatters/progressBar.ts +228 -228
- package/src/cli/index.ts +407 -167
- package/src/cli/interactive/interactivePrompts.ts +100 -98
- package/src/cli/options/globalOptions.ts +143 -86
- package/src/cli/options/index.ts +1 -1
- package/src/cli/themes/colorTheme.ts +70 -70
- package/src/cli/validators/commandValidator.ts +118 -122
- package/src/cli/validators/index.ts +1 -1
- package/src/index.ts +1 -1
|
@@ -5,64 +5,80 @@
|
|
|
5
5
|
* Supports interactive mode, dry-run, and various update strategies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
import {
|
|
9
|
+
AIAnalysisService,
|
|
10
|
+
type AnalysisType,
|
|
11
|
+
type CatalogUpdateService,
|
|
12
|
+
FileSystemService,
|
|
13
|
+
FileWorkspaceRepository,
|
|
14
|
+
type UpdateOptions,
|
|
15
|
+
type UpdatePlan,
|
|
16
|
+
type UpdateTarget,
|
|
17
|
+
WorkspaceService,
|
|
18
|
+
} from '@pcu/core'
|
|
19
|
+
import { ConfigLoader } from '@pcu/utils'
|
|
20
|
+
import chalk from 'chalk'
|
|
21
|
+
import { type OutputFormat, OutputFormatter } from '../formatters/outputFormatter.js'
|
|
22
|
+
import { ProgressBar } from '../formatters/progressBar.js'
|
|
23
|
+
import { InteractivePrompts } from '../interactive/interactivePrompts.js'
|
|
24
|
+
import { StyledText, ThemeManager } from '../themes/colorTheme.js'
|
|
14
25
|
|
|
15
26
|
export interface UpdateCommandOptions {
|
|
16
|
-
workspace?: string
|
|
17
|
-
catalog?: string
|
|
18
|
-
format?: OutputFormat
|
|
19
|
-
target?: UpdateTarget
|
|
20
|
-
interactive?: boolean
|
|
21
|
-
dryRun?: boolean
|
|
22
|
-
force?: boolean
|
|
23
|
-
prerelease?: boolean
|
|
24
|
-
include?: string[]
|
|
25
|
-
exclude?: string[]
|
|
26
|
-
createBackup?: boolean
|
|
27
|
-
verbose?: boolean
|
|
28
|
-
color?: boolean
|
|
27
|
+
workspace?: string
|
|
28
|
+
catalog?: string
|
|
29
|
+
format?: OutputFormat
|
|
30
|
+
target?: UpdateTarget
|
|
31
|
+
interactive?: boolean
|
|
32
|
+
dryRun?: boolean
|
|
33
|
+
force?: boolean
|
|
34
|
+
prerelease?: boolean
|
|
35
|
+
include?: string[]
|
|
36
|
+
exclude?: string[]
|
|
37
|
+
createBackup?: boolean
|
|
38
|
+
verbose?: boolean
|
|
39
|
+
color?: boolean
|
|
40
|
+
// AI batch analysis options
|
|
41
|
+
ai?: boolean
|
|
42
|
+
provider?: string
|
|
43
|
+
analysisType?: AnalysisType
|
|
44
|
+
skipCache?: boolean
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
export class UpdateCommand {
|
|
32
|
-
private readonly updateService: CatalogUpdateService
|
|
48
|
+
private readonly updateService: CatalogUpdateService
|
|
33
49
|
|
|
34
50
|
constructor(updateService: CatalogUpdateService) {
|
|
35
|
-
this.updateService = updateService
|
|
51
|
+
this.updateService = updateService
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
/**
|
|
39
55
|
* Execute the update command
|
|
40
56
|
*/
|
|
41
57
|
async execute(options: UpdateCommandOptions = {}): Promise<void> {
|
|
42
|
-
let progressBar: ProgressBar | undefined
|
|
58
|
+
let progressBar: ProgressBar | undefined
|
|
43
59
|
|
|
44
60
|
try {
|
|
45
61
|
// Initialize theme
|
|
46
|
-
ThemeManager.setTheme('default')
|
|
62
|
+
ThemeManager.setTheme('default')
|
|
47
63
|
|
|
48
64
|
// Create progress bar for the update process
|
|
49
65
|
progressBar = new ProgressBar({
|
|
50
66
|
text: '正在规划更新...',
|
|
51
67
|
total: 4, // 4 main steps
|
|
52
|
-
})
|
|
53
|
-
progressBar.start('正在加载工作区配置...')
|
|
68
|
+
})
|
|
69
|
+
progressBar.start('正在加载工作区配置...')
|
|
54
70
|
|
|
55
71
|
// Load configuration file first
|
|
56
|
-
const config = ConfigLoader.loadConfig(options.workspace || process.cwd())
|
|
72
|
+
const config = ConfigLoader.loadConfig(options.workspace || process.cwd())
|
|
57
73
|
|
|
58
74
|
// Use format from CLI options first, then config file, then default
|
|
59
|
-
const effectiveFormat = options.format || config.defaults?.format || 'table'
|
|
75
|
+
const effectiveFormat = options.format || config.defaults?.format || 'table'
|
|
60
76
|
|
|
61
77
|
// Create output formatter with effective format
|
|
62
78
|
const formatter = new OutputFormatter(
|
|
63
79
|
effectiveFormat as OutputFormat,
|
|
64
80
|
options.color !== false
|
|
65
|
-
)
|
|
81
|
+
)
|
|
66
82
|
|
|
67
83
|
// Merge CLI options with configuration file settings
|
|
68
84
|
const updateOptions: UpdateOptions = {
|
|
@@ -77,73 +93,168 @@ export class UpdateCommand {
|
|
|
77
93
|
dryRun: options.dryRun ?? config.defaults?.dryRun ?? false,
|
|
78
94
|
force: options.force ?? false,
|
|
79
95
|
createBackup: options.createBackup ?? config.defaults?.createBackup ?? false,
|
|
80
|
-
}
|
|
96
|
+
}
|
|
81
97
|
|
|
82
98
|
// Step 1: Planning updates
|
|
83
|
-
progressBar.update('正在检查包版本...', 1, 4)
|
|
84
|
-
const plan = await this.updateService.planUpdates(updateOptions)
|
|
99
|
+
progressBar.update('正在检查包版本...', 1, 4)
|
|
100
|
+
const plan = await this.updateService.planUpdates(updateOptions)
|
|
85
101
|
|
|
86
102
|
// Step 2: Check if any updates found
|
|
87
|
-
progressBar.update('正在分析更新...', 2, 4)
|
|
103
|
+
progressBar.update('正在分析更新...', 2, 4)
|
|
88
104
|
|
|
89
105
|
if (!plan.updates.length) {
|
|
90
|
-
progressBar.succeed('所有依赖包都是最新的')
|
|
91
|
-
console.log(StyledText.iconSuccess('All dependencies are up to date!'))
|
|
92
|
-
return
|
|
106
|
+
progressBar.succeed('所有依赖包都是最新的')
|
|
107
|
+
console.log(StyledText.iconSuccess('All dependencies are up to date!'))
|
|
108
|
+
return
|
|
93
109
|
}
|
|
94
110
|
|
|
95
111
|
console.log(
|
|
96
112
|
StyledText.iconPackage(
|
|
97
113
|
`Found ${plan.totalUpdates} update${plan.totalUpdates === 1 ? '' : 's'} available`
|
|
98
114
|
)
|
|
99
|
-
)
|
|
115
|
+
)
|
|
100
116
|
|
|
101
117
|
// Interactive selection if enabled
|
|
102
|
-
let finalPlan = plan
|
|
118
|
+
let finalPlan = plan
|
|
103
119
|
if (options.interactive) {
|
|
104
|
-
finalPlan = await this.interactiveSelection(plan)
|
|
120
|
+
finalPlan = await this.interactiveSelection(plan)
|
|
105
121
|
if (!finalPlan.updates.length) {
|
|
106
|
-
progressBar.warn('未选择任何更新')
|
|
107
|
-
console.log(StyledText.iconWarning('No updates selected'))
|
|
108
|
-
return
|
|
122
|
+
progressBar.warn('未选择任何更新')
|
|
123
|
+
console.log(StyledText.iconWarning('No updates selected'))
|
|
124
|
+
return
|
|
109
125
|
}
|
|
110
126
|
}
|
|
111
127
|
|
|
128
|
+
// AI batch analysis if enabled - analyze ALL packages in one request
|
|
129
|
+
if (options.ai) {
|
|
130
|
+
progressBar.update('🤖 正在进行 AI 批量分析...', 2.5, 4)
|
|
131
|
+
progressBar.stop()
|
|
132
|
+
|
|
133
|
+
console.log(
|
|
134
|
+
chalk.blue(
|
|
135
|
+
`\n🤖 Running AI-powered batch analysis for ${finalPlan.updates.length} packages...`
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
console.log(chalk.gray('This analyzes all packages in a single request for efficiency.\n'))
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const aiResult = await this.performBatchAIAnalysis(finalPlan, options)
|
|
142
|
+
|
|
143
|
+
// Display AI analysis results
|
|
144
|
+
console.log(chalk.blue('\n📊 AI Analysis Results:'))
|
|
145
|
+
console.log(chalk.gray('─'.repeat(60)))
|
|
146
|
+
console.log(chalk.cyan(`Provider: ${aiResult.provider}`))
|
|
147
|
+
console.log(chalk.cyan(`Confidence: ${(aiResult.confidence * 100).toFixed(0)}%`))
|
|
148
|
+
console.log(chalk.cyan(`Processing time: ${aiResult.processingTimeMs}ms`))
|
|
149
|
+
console.log(chalk.gray('─'.repeat(60)))
|
|
150
|
+
console.log(chalk.yellow('\n📝 Summary:'))
|
|
151
|
+
console.log(aiResult.summary)
|
|
152
|
+
|
|
153
|
+
// Display recommendations for each package
|
|
154
|
+
if (aiResult.recommendations.length > 0) {
|
|
155
|
+
console.log(chalk.yellow('\n📦 Package Recommendations:'))
|
|
156
|
+
for (const rec of aiResult.recommendations) {
|
|
157
|
+
const actionIcon = rec.action === 'update' ? '✅' : rec.action === 'skip' ? '❌' : '⚠️'
|
|
158
|
+
const riskColor =
|
|
159
|
+
rec.riskLevel === 'critical'
|
|
160
|
+
? chalk.red
|
|
161
|
+
: rec.riskLevel === 'high'
|
|
162
|
+
? chalk.yellow
|
|
163
|
+
: rec.riskLevel === 'medium'
|
|
164
|
+
? chalk.cyan
|
|
165
|
+
: chalk.green
|
|
166
|
+
|
|
167
|
+
console.log(
|
|
168
|
+
`\n ${actionIcon} ${chalk.bold(rec.package)}: ${rec.currentVersion} → ${rec.targetVersion}`
|
|
169
|
+
)
|
|
170
|
+
console.log(
|
|
171
|
+
` Action: ${chalk.bold(rec.action.toUpperCase())} | Risk: ${riskColor(rec.riskLevel)}`
|
|
172
|
+
)
|
|
173
|
+
console.log(` ${rec.reason}`)
|
|
174
|
+
|
|
175
|
+
if (rec.breakingChanges && rec.breakingChanges.length > 0) {
|
|
176
|
+
console.log(
|
|
177
|
+
chalk.red(` ⚠️ Breaking changes: ${rec.breakingChanges.join(', ')}`)
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
if (rec.securityFixes && rec.securityFixes.length > 0) {
|
|
181
|
+
console.log(chalk.green(` 🔒 Security fixes: ${rec.securityFixes.join(', ')}`))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Display warnings
|
|
187
|
+
if (aiResult.warnings && aiResult.warnings.length > 0) {
|
|
188
|
+
console.log(chalk.yellow('\n⚠️ Warnings:'))
|
|
189
|
+
for (const warning of aiResult.warnings) {
|
|
190
|
+
console.log(chalk.yellow(` - ${warning}`))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(chalk.gray(`\n${'─'.repeat(60)}`))
|
|
195
|
+
|
|
196
|
+
// If there are critical/skip recommendations, warn the user
|
|
197
|
+
const skipRecommendations = aiResult.recommendations.filter((r) => r.action === 'skip')
|
|
198
|
+
if (skipRecommendations.length > 0 && !options.force) {
|
|
199
|
+
console.log(
|
|
200
|
+
chalk.red(
|
|
201
|
+
`\n⛔ AI recommends skipping ${skipRecommendations.length} package(s) due to risks.`
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
console.log(chalk.yellow('Use --force to override AI recommendations.\n'))
|
|
205
|
+
}
|
|
206
|
+
} catch (aiError) {
|
|
207
|
+
console.warn(
|
|
208
|
+
chalk.yellow('\n⚠️ AI batch analysis failed, continuing without AI insights:')
|
|
209
|
+
)
|
|
210
|
+
if (options.verbose) {
|
|
211
|
+
console.warn(chalk.gray(String(aiError)))
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Restart progress bar
|
|
216
|
+
progressBar = new ProgressBar({
|
|
217
|
+
text: '准备应用更新...',
|
|
218
|
+
total: 4,
|
|
219
|
+
})
|
|
220
|
+
progressBar.start('正在准备应用更新...')
|
|
221
|
+
}
|
|
222
|
+
|
|
112
223
|
// Step 3: Apply updates
|
|
113
|
-
progressBar.update('正在准备应用更新...', 3, 4)
|
|
224
|
+
progressBar.update('正在准备应用更新...', 3, 4)
|
|
114
225
|
|
|
115
226
|
if (!options.dryRun) {
|
|
116
227
|
// Replace the progress bar with one for applying updates
|
|
117
|
-
progressBar.stop()
|
|
228
|
+
progressBar.stop()
|
|
118
229
|
progressBar = new ProgressBar({
|
|
119
230
|
text: 'Applying updates...',
|
|
120
231
|
total: finalPlan.updates.length,
|
|
121
|
-
})
|
|
122
|
-
progressBar.start('正在应用更新...')
|
|
232
|
+
})
|
|
233
|
+
progressBar.start('正在应用更新...')
|
|
123
234
|
|
|
124
|
-
const result = await this.updateService.executeUpdates(finalPlan, updateOptions)
|
|
125
|
-
progressBar.succeed(`Applied ${finalPlan.updates.length} updates`)
|
|
235
|
+
const result = await this.updateService.executeUpdates(finalPlan, updateOptions)
|
|
236
|
+
progressBar.succeed(`Applied ${finalPlan.updates.length} updates`)
|
|
126
237
|
|
|
127
|
-
console.log(formatter.formatUpdateResult(result))
|
|
238
|
+
console.log(formatter.formatUpdateResult(result))
|
|
128
239
|
} else {
|
|
129
|
-
progressBar.update('正在生成预览...', 4, 4)
|
|
130
|
-
progressBar.succeed('更新预览完成')
|
|
131
|
-
console.log(StyledText.iconInfo('Dry run - no changes made'))
|
|
132
|
-
console.log(JSON.stringify(finalPlan, null, 2))
|
|
240
|
+
progressBar.update('正在生成预览...', 4, 4)
|
|
241
|
+
progressBar.succeed('更新预览完成')
|
|
242
|
+
console.log(StyledText.iconInfo('Dry run - no changes made'))
|
|
243
|
+
console.log(JSON.stringify(finalPlan, null, 2))
|
|
133
244
|
}
|
|
134
245
|
|
|
135
|
-
console.log(StyledText.iconComplete('Update process completed!'))
|
|
246
|
+
console.log(StyledText.iconComplete('Update process completed!'))
|
|
136
247
|
} catch (error) {
|
|
137
248
|
if (progressBar) {
|
|
138
|
-
progressBar.fail('Operation failed')
|
|
249
|
+
progressBar.fail('Operation failed')
|
|
139
250
|
}
|
|
140
251
|
|
|
141
252
|
if (error instanceof Error) {
|
|
142
|
-
console.error(StyledText.iconError(`Error: ${error.message}`))
|
|
253
|
+
console.error(StyledText.iconError(`Error: ${error.message}`))
|
|
143
254
|
} else {
|
|
144
|
-
console.error(StyledText.iconError('Unknown error occurred'))
|
|
255
|
+
console.error(StyledText.iconError('Unknown error occurred'))
|
|
145
256
|
}
|
|
146
|
-
throw error
|
|
257
|
+
throw error
|
|
147
258
|
}
|
|
148
259
|
}
|
|
149
260
|
|
|
@@ -151,7 +262,7 @@ export class UpdateCommand {
|
|
|
151
262
|
* Interactive update selection
|
|
152
263
|
*/
|
|
153
264
|
private async interactiveSelection(plan: UpdatePlan): Promise<UpdatePlan> {
|
|
154
|
-
const interactivePrompts = new InteractivePrompts()
|
|
265
|
+
const interactivePrompts = new InteractivePrompts()
|
|
155
266
|
|
|
156
267
|
// Transform PlannedUpdate to the format expected by InteractivePrompts
|
|
157
268
|
const packages = plan.updates.map((update) => ({
|
|
@@ -159,31 +270,79 @@ export class UpdateCommand {
|
|
|
159
270
|
current: update.currentVersion,
|
|
160
271
|
latest: update.newVersion,
|
|
161
272
|
type: update.updateType,
|
|
162
|
-
}))
|
|
273
|
+
}))
|
|
163
274
|
|
|
164
|
-
const selectedPackageNames = await interactivePrompts.selectPackages(packages)
|
|
275
|
+
const selectedPackageNames = await interactivePrompts.selectPackages(packages)
|
|
165
276
|
|
|
166
277
|
// Filter the plan to only include selected packages
|
|
167
278
|
const selectedUpdates = plan.updates.filter((update) =>
|
|
168
279
|
selectedPackageNames.includes(update.packageName)
|
|
169
|
-
)
|
|
280
|
+
)
|
|
170
281
|
|
|
171
282
|
return {
|
|
172
283
|
...plan,
|
|
173
284
|
updates: selectedUpdates,
|
|
174
285
|
totalUpdates: selectedUpdates.length,
|
|
175
|
-
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Perform batch AI analysis for all packages in the update plan
|
|
291
|
+
* This analyzes ALL packages in a single AI request for efficiency
|
|
292
|
+
*/
|
|
293
|
+
private async performBatchAIAnalysis(plan: UpdatePlan, options: UpdateCommandOptions) {
|
|
294
|
+
const workspacePath = options.workspace || process.cwd()
|
|
295
|
+
|
|
296
|
+
// Create workspace service to get workspace info
|
|
297
|
+
const fileSystemService = new FileSystemService()
|
|
298
|
+
const workspaceRepository = new FileWorkspaceRepository(fileSystemService)
|
|
299
|
+
const workspaceService = new WorkspaceService(workspaceRepository)
|
|
300
|
+
const workspaceInfo = await workspaceService.getWorkspaceInfo(workspacePath)
|
|
301
|
+
|
|
302
|
+
// Create AI service
|
|
303
|
+
const aiService = new AIAnalysisService({
|
|
304
|
+
config: {
|
|
305
|
+
preferredProvider: options.provider === 'auto' ? 'auto' : options.provider,
|
|
306
|
+
cache: { enabled: !options.skipCache, ttl: 3600 },
|
|
307
|
+
fallback: { enabled: true, useRuleEngine: true },
|
|
308
|
+
},
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// Convert all planned updates to PackageUpdateInfo format for batch analysis
|
|
312
|
+
const packages = plan.updates.map((update) => ({
|
|
313
|
+
name: update.packageName,
|
|
314
|
+
currentVersion: update.currentVersion,
|
|
315
|
+
targetVersion: update.newVersion,
|
|
316
|
+
updateType: update.updateType,
|
|
317
|
+
catalogName: update.catalogName,
|
|
318
|
+
}))
|
|
319
|
+
|
|
320
|
+
// Build workspace info for AI
|
|
321
|
+
const wsInfo = {
|
|
322
|
+
name: workspaceInfo.name,
|
|
323
|
+
path: workspaceInfo.path,
|
|
324
|
+
packageCount: workspaceInfo.packageCount,
|
|
325
|
+
catalogCount: workspaceInfo.catalogCount,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Perform single batch analysis for ALL packages
|
|
329
|
+
const result = await aiService.analyzeUpdates(packages, wsInfo, {
|
|
330
|
+
analysisType: options.analysisType || 'impact',
|
|
331
|
+
skipCache: options.skipCache,
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
return result
|
|
176
335
|
}
|
|
177
336
|
|
|
178
337
|
/**
|
|
179
338
|
* Validate command options
|
|
180
339
|
*/
|
|
181
340
|
static validateOptions(options: UpdateCommandOptions): string[] {
|
|
182
|
-
const errors: string[] = []
|
|
341
|
+
const errors: string[] = []
|
|
183
342
|
|
|
184
343
|
// Validate format
|
|
185
344
|
if (options.format && !['table', 'json', 'yaml', 'minimal'].includes(options.format)) {
|
|
186
|
-
errors.push('Invalid format. Must be one of: table, json, yaml, minimal')
|
|
345
|
+
errors.push('Invalid format. Must be one of: table, json, yaml, minimal')
|
|
187
346
|
}
|
|
188
347
|
|
|
189
348
|
// Validate target
|
|
@@ -191,15 +350,15 @@ export class UpdateCommand {
|
|
|
191
350
|
options.target &&
|
|
192
351
|
!['latest', 'greatest', 'minor', 'patch', 'newest'].includes(options.target)
|
|
193
352
|
) {
|
|
194
|
-
errors.push('Invalid target. Must be one of: latest, greatest, minor, patch, newest')
|
|
353
|
+
errors.push('Invalid target. Must be one of: latest, greatest, minor, patch, newest')
|
|
195
354
|
}
|
|
196
355
|
|
|
197
356
|
// Interactive and dry-run conflict
|
|
198
357
|
if (options.interactive && options.dryRun) {
|
|
199
|
-
errors.push('Cannot use --interactive with --dry-run')
|
|
358
|
+
errors.push('Cannot use --interactive with --dry-run')
|
|
200
359
|
}
|
|
201
360
|
|
|
202
|
-
return errors
|
|
361
|
+
return errors
|
|
203
362
|
}
|
|
204
363
|
|
|
205
364
|
/**
|
|
@@ -240,6 +399,6 @@ Exit Codes:
|
|
|
240
399
|
0 Updates completed successfully
|
|
241
400
|
1 Updates failed or were cancelled
|
|
242
401
|
2 Error occurred
|
|
243
|
-
|
|
402
|
+
`
|
|
244
403
|
}
|
|
245
404
|
}
|