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,23 +5,32 @@
|
|
|
5
5
|
* Supports interactive mode, dry-run, and various update strategies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
type UpdatePlan,
|
|
16
|
-
type UpdateTarget,
|
|
8
|
+
import type {
|
|
9
|
+
AnalysisType,
|
|
10
|
+
CatalogUpdateService,
|
|
11
|
+
IPackageManagerService,
|
|
12
|
+
UpdateOptions,
|
|
13
|
+
UpdatePlan,
|
|
14
|
+
UpdateTarget,
|
|
17
15
|
WorkspaceService,
|
|
18
16
|
} from '@pcu/core'
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
import { type OutputFormat, OutputFormatter } from '../formatters/outputFormatter.js'
|
|
17
|
+
import { type PackageFilterConfig, t } from '@pcu/utils'
|
|
18
|
+
import type { OutputFormat, OutputFormatter } from '../formatters/outputFormatter.js'
|
|
22
19
|
import { ProgressBar } from '../formatters/progressBar.js'
|
|
20
|
+
import { AIAnalysisHandler, ChangelogHandler, InstallHandler } from '../handlers/index.js'
|
|
23
21
|
import { InteractivePrompts } from '../interactive/interactivePrompts.js'
|
|
24
|
-
import { StyledText
|
|
22
|
+
import { StyledText } from '../themes/colorTheme.js'
|
|
23
|
+
import { cliOutput } from '../utils/cliOutput.js'
|
|
24
|
+
import {
|
|
25
|
+
createFormatter,
|
|
26
|
+
getEffectivePatterns,
|
|
27
|
+
getEffectiveTarget,
|
|
28
|
+
handleCommandError,
|
|
29
|
+
initializeTheme,
|
|
30
|
+
loadConfiguration,
|
|
31
|
+
mergeWithConfig,
|
|
32
|
+
} from '../utils/commandHelpers.js'
|
|
33
|
+
import { errorsOnly, validateUpdateOptions } from '../validators/index.js'
|
|
25
34
|
|
|
26
35
|
export interface UpdateCommandOptions {
|
|
27
36
|
workspace?: string
|
|
@@ -42,220 +51,153 @@ export interface UpdateCommandOptions {
|
|
|
42
51
|
provider?: string
|
|
43
52
|
analysisType?: AnalysisType
|
|
44
53
|
skipCache?: boolean
|
|
54
|
+
// Auto install after update
|
|
55
|
+
install?: boolean
|
|
56
|
+
// Show changelog for updates
|
|
57
|
+
changelog?: boolean
|
|
58
|
+
// Skip security vulnerability checks
|
|
59
|
+
noSecurity?: boolean
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
export class UpdateCommand {
|
|
48
63
|
private readonly updateService: CatalogUpdateService
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
private readonly aiAnalysisHandler: AIAnalysisHandler
|
|
65
|
+
private readonly changelogHandler: ChangelogHandler
|
|
66
|
+
private readonly installHandler: InstallHandler
|
|
67
|
+
|
|
68
|
+
constructor(
|
|
69
|
+
updateService: CatalogUpdateService,
|
|
70
|
+
workspaceService: WorkspaceService,
|
|
71
|
+
packageManagerService: IPackageManagerService
|
|
72
|
+
) {
|
|
51
73
|
this.updateService = updateService
|
|
74
|
+
this.aiAnalysisHandler = new AIAnalysisHandler(workspaceService)
|
|
75
|
+
this.changelogHandler = new ChangelogHandler()
|
|
76
|
+
// ARCH-002: Extracted install logic to dedicated handler
|
|
77
|
+
this.installHandler = new InstallHandler(packageManagerService)
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
/**
|
|
55
81
|
* Execute the update command
|
|
56
82
|
*/
|
|
57
83
|
async execute(options: UpdateCommandOptions = {}): Promise<void> {
|
|
58
|
-
|
|
84
|
+
const progressBar = this.createProgressBar()
|
|
59
85
|
|
|
60
86
|
try {
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Create progress bar for the update process
|
|
65
|
-
progressBar = new ProgressBar({
|
|
66
|
-
text: '正在规划更新...',
|
|
67
|
-
total: 4, // 4 main steps
|
|
68
|
-
})
|
|
69
|
-
progressBar.start('正在加载工作区配置...')
|
|
70
|
-
|
|
71
|
-
// Load configuration file first
|
|
72
|
-
const config = ConfigLoader.loadConfig(options.workspace || process.cwd())
|
|
73
|
-
|
|
74
|
-
// Use format from CLI options first, then config file, then default
|
|
75
|
-
const effectiveFormat = options.format || config.defaults?.format || 'table'
|
|
76
|
-
|
|
77
|
-
// Create output formatter with effective format
|
|
78
|
-
const formatter = new OutputFormatter(
|
|
79
|
-
effectiveFormat as OutputFormat,
|
|
80
|
-
options.color !== false
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
// Merge CLI options with configuration file settings
|
|
84
|
-
const updateOptions: UpdateOptions = {
|
|
85
|
-
workspacePath: options.workspace,
|
|
86
|
-
catalogName: options.catalog,
|
|
87
|
-
target: options.target || config.defaults?.target,
|
|
88
|
-
includePrerelease: options.prerelease ?? config.defaults?.includePrerelease ?? false,
|
|
89
|
-
// CLI include/exclude options take priority over config file
|
|
90
|
-
include: options.include?.length ? options.include : config.include,
|
|
91
|
-
exclude: options.exclude?.length ? options.exclude : config.exclude,
|
|
92
|
-
interactive: options.interactive ?? config.defaults?.interactive ?? false,
|
|
93
|
-
dryRun: options.dryRun ?? config.defaults?.dryRun ?? false,
|
|
94
|
-
force: options.force ?? false,
|
|
95
|
-
createBackup: options.createBackup ?? config.defaults?.createBackup ?? false,
|
|
96
|
-
}
|
|
87
|
+
// Load configuration and merge with CLI options
|
|
88
|
+
const { updateOptions, formatter } = await this.prepareUpdateContext(options)
|
|
97
89
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const plan = await this.updateService.planUpdates(updateOptions)
|
|
101
|
-
|
|
102
|
-
// Step 2: Check if any updates found
|
|
103
|
-
progressBar.update('正在分析更新...', 2, 4)
|
|
90
|
+
// Plan updates
|
|
91
|
+
const plan = await this.planUpdates(progressBar, updateOptions)
|
|
104
92
|
|
|
93
|
+
// Handle empty plan
|
|
105
94
|
if (!plan.updates.length) {
|
|
106
|
-
progressBar.succeed('
|
|
107
|
-
|
|
95
|
+
progressBar.succeed(t('command.update.allUpToDate'))
|
|
96
|
+
cliOutput.print(StyledText.iconSuccess(t('command.update.allUpToDate')))
|
|
108
97
|
return
|
|
109
98
|
}
|
|
110
99
|
|
|
111
|
-
|
|
112
|
-
StyledText.iconPackage(
|
|
113
|
-
`Found ${plan.totalUpdates} update${plan.totalUpdates === 1 ? '' : 's'} available`
|
|
114
|
-
)
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
// Interactive selection if enabled
|
|
118
|
-
let finalPlan = plan
|
|
119
|
-
if (options.interactive) {
|
|
120
|
-
finalPlan = await this.interactiveSelection(plan)
|
|
121
|
-
if (!finalPlan.updates.length) {
|
|
122
|
-
progressBar.warn('未选择任何更新')
|
|
123
|
-
console.log(StyledText.iconWarning('No updates selected'))
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
}
|
|
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()
|
|
100
|
+
progressBar.succeed(t('command.update.foundUpdates', { count: plan.totalUpdates }))
|
|
132
101
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
}
|
|
102
|
+
// Process optional features and get final plan
|
|
103
|
+
const finalPlan = await this.processOptionalFeatures(plan, options)
|
|
104
|
+
if (!finalPlan) return
|
|
222
105
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (!options.dryRun) {
|
|
227
|
-
// Replace the progress bar with one for applying updates
|
|
228
|
-
progressBar.stop()
|
|
229
|
-
progressBar = new ProgressBar({
|
|
230
|
-
text: 'Applying updates...',
|
|
231
|
-
total: finalPlan.updates.length,
|
|
232
|
-
})
|
|
233
|
-
progressBar.start('正在应用更新...')
|
|
234
|
-
|
|
235
|
-
const result = await this.updateService.executeUpdates(finalPlan, updateOptions)
|
|
236
|
-
progressBar.succeed(`Applied ${finalPlan.updates.length} updates`)
|
|
237
|
-
|
|
238
|
-
console.log(formatter.formatUpdateResult(result))
|
|
239
|
-
} else {
|
|
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))
|
|
244
|
-
}
|
|
106
|
+
// Execute updates or show dry-run preview
|
|
107
|
+
await this.executeUpdatesOrDryRun(finalPlan, updateOptions, options, formatter)
|
|
245
108
|
|
|
246
|
-
|
|
109
|
+
cliOutput.print(StyledText.iconComplete(t('command.update.processComplete')))
|
|
247
110
|
} catch (error) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
111
|
+
this.handleExecuteError(error, progressBar, options)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
251
114
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Create and initialize progress bar
|
|
117
|
+
*/
|
|
118
|
+
private createProgressBar(): ProgressBar {
|
|
119
|
+
initializeTheme('default')
|
|
120
|
+
const progressBar = new ProgressBar({
|
|
121
|
+
text: t('command.update.planningUpdates'),
|
|
122
|
+
total: 4,
|
|
123
|
+
})
|
|
124
|
+
progressBar.start(t('command.update.loadingConfig'))
|
|
125
|
+
return progressBar
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Plan updates with progress tracking
|
|
130
|
+
*/
|
|
131
|
+
private async planUpdates(
|
|
132
|
+
progressBar: ProgressBar,
|
|
133
|
+
updateOptions: UpdateOptions
|
|
134
|
+
): Promise<UpdatePlan> {
|
|
135
|
+
progressBar.update(t('command.update.checkingVersions'), 1, 4)
|
|
136
|
+
const plan = await this.updateService.planUpdates(updateOptions)
|
|
137
|
+
progressBar.update(t('command.update.analyzingUpdates'), 2, 4)
|
|
138
|
+
return plan
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Process optional features (changelog, interactive selection, AI analysis)
|
|
143
|
+
* @returns Final plan or null if user cancelled
|
|
144
|
+
*/
|
|
145
|
+
private async processOptionalFeatures(
|
|
146
|
+
plan: UpdatePlan,
|
|
147
|
+
options: UpdateCommandOptions
|
|
148
|
+
): Promise<UpdatePlan | null> {
|
|
149
|
+
// Display changelog if enabled (uses ChangelogHandler)
|
|
150
|
+
if (options.changelog) {
|
|
151
|
+
await this.changelogHandler.displayChangelogs(plan, options.verbose)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Interactive selection if enabled
|
|
155
|
+
let finalPlan = plan
|
|
156
|
+
if (options.interactive) {
|
|
157
|
+
finalPlan = await this.interactiveSelection(plan)
|
|
158
|
+
if (!finalPlan.updates.length) {
|
|
159
|
+
cliOutput.print(StyledText.iconWarning(t('command.update.noUpdatesSelected')))
|
|
160
|
+
return null
|
|
256
161
|
}
|
|
257
|
-
throw error
|
|
258
162
|
}
|
|
163
|
+
|
|
164
|
+
// AI batch analysis if enabled (uses AIAnalysisHandler)
|
|
165
|
+
if (options.ai) {
|
|
166
|
+
await this.aiAnalysisHandler.analyzeAndDisplay(finalPlan, {
|
|
167
|
+
workspace: options.workspace,
|
|
168
|
+
provider: options.provider,
|
|
169
|
+
analysisType: options.analysisType,
|
|
170
|
+
skipCache: options.skipCache,
|
|
171
|
+
verbose: options.verbose,
|
|
172
|
+
force: options.force,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return finalPlan
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Handle errors during execute()
|
|
181
|
+
* QUAL-007: Uses unified error handling from commandHelpers
|
|
182
|
+
*/
|
|
183
|
+
private handleExecuteError(
|
|
184
|
+
error: unknown,
|
|
185
|
+
progressBar: ProgressBar,
|
|
186
|
+
options: UpdateCommandOptions
|
|
187
|
+
): never | undefined {
|
|
188
|
+
// Use unified error handling - returns true if user cancellation was handled
|
|
189
|
+
const wasCancelled = handleCommandError(error, {
|
|
190
|
+
verbose: options.verbose,
|
|
191
|
+
progressBar,
|
|
192
|
+
errorMessage: 'Update command failed',
|
|
193
|
+
context: { options },
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
if (wasCancelled) {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
throw error
|
|
259
201
|
}
|
|
260
202
|
|
|
261
203
|
/**
|
|
@@ -287,78 +229,83 @@ export class UpdateCommand {
|
|
|
287
229
|
}
|
|
288
230
|
|
|
289
231
|
/**
|
|
290
|
-
*
|
|
291
|
-
* This analyzes ALL packages in a single AI request for efficiency
|
|
232
|
+
* Prepare update context by loading config and merging with CLI options
|
|
292
233
|
*/
|
|
293
|
-
private async
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const wsInfo = {
|
|
322
|
-
name: workspaceInfo.name,
|
|
323
|
-
path: workspaceInfo.path,
|
|
324
|
-
packageCount: workspaceInfo.packageCount,
|
|
325
|
-
catalogCount: workspaceInfo.catalogCount,
|
|
234
|
+
private async prepareUpdateContext(options: UpdateCommandOptions): Promise<{
|
|
235
|
+
config: PackageFilterConfig
|
|
236
|
+
updateOptions: UpdateOptions
|
|
237
|
+
formatter: OutputFormatter
|
|
238
|
+
}> {
|
|
239
|
+
// Load configuration using shared helper
|
|
240
|
+
const config = await loadConfiguration(options.workspace)
|
|
241
|
+
|
|
242
|
+
// Create output formatter using shared helper
|
|
243
|
+
const formatter = createFormatter(options.format, config, options.color !== false)
|
|
244
|
+
|
|
245
|
+
// Merge CLI options with configuration file settings using shared helpers
|
|
246
|
+
const updateOptions: UpdateOptions = {
|
|
247
|
+
workspacePath: options.workspace,
|
|
248
|
+
catalogName: options.catalog,
|
|
249
|
+
target: getEffectiveTarget(options.target, config.defaults?.target),
|
|
250
|
+
includePrerelease: mergeWithConfig(
|
|
251
|
+
options.prerelease,
|
|
252
|
+
config.defaults?.includePrerelease,
|
|
253
|
+
false
|
|
254
|
+
),
|
|
255
|
+
include: getEffectivePatterns(options.include, config.include),
|
|
256
|
+
exclude: getEffectivePatterns(options.exclude, config.exclude),
|
|
257
|
+
interactive: mergeWithConfig(options.interactive, config.defaults?.interactive, false),
|
|
258
|
+
dryRun: mergeWithConfig(options.dryRun, config.defaults?.dryRun, false),
|
|
259
|
+
force: options.force ?? false,
|
|
260
|
+
createBackup: mergeWithConfig(options.createBackup, config.defaults?.createBackup, true),
|
|
261
|
+
noSecurity: options.noSecurity,
|
|
326
262
|
}
|
|
327
263
|
|
|
328
|
-
|
|
329
|
-
const result = await aiService.analyzeUpdates(packages, wsInfo, {
|
|
330
|
-
analysisType: options.analysisType || 'impact',
|
|
331
|
-
skipCache: options.skipCache,
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
return result
|
|
264
|
+
return { config, updateOptions, formatter }
|
|
335
265
|
}
|
|
336
266
|
|
|
337
267
|
/**
|
|
338
|
-
*
|
|
268
|
+
* Execute updates or show dry-run preview
|
|
339
269
|
*/
|
|
340
|
-
|
|
341
|
-
|
|
270
|
+
private async executeUpdatesOrDryRun(
|
|
271
|
+
finalPlan: UpdatePlan,
|
|
272
|
+
updateOptions: UpdateOptions,
|
|
273
|
+
options: UpdateCommandOptions,
|
|
274
|
+
formatter: OutputFormatter
|
|
275
|
+
): Promise<void> {
|
|
276
|
+
if (!options.dryRun) {
|
|
277
|
+
// Create new progress bar for applying updates
|
|
278
|
+
const progressBar = new ProgressBar({
|
|
279
|
+
text: t('command.update.applyingUpdates'),
|
|
280
|
+
total: finalPlan.updates.length,
|
|
281
|
+
})
|
|
282
|
+
progressBar.start(t('command.update.applyingUpdates'))
|
|
342
283
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
errors.push('Invalid format. Must be one of: table, json, yaml, minimal')
|
|
346
|
-
}
|
|
284
|
+
const result = await this.updateService.executeUpdates(finalPlan, updateOptions)
|
|
285
|
+
progressBar.succeed(t('command.update.appliedUpdates', { count: finalPlan.updates.length }))
|
|
347
286
|
|
|
348
|
-
|
|
349
|
-
if (
|
|
350
|
-
options.target &&
|
|
351
|
-
!['latest', 'greatest', 'minor', 'patch', 'newest'].includes(options.target)
|
|
352
|
-
) {
|
|
353
|
-
errors.push('Invalid target. Must be one of: latest, greatest, minor, patch, newest')
|
|
354
|
-
}
|
|
287
|
+
cliOutput.print(formatter.formatUpdateResult(result))
|
|
355
288
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
289
|
+
// Run pnpm install if enabled (default: true)
|
|
290
|
+
// ARCH-002: Delegated to InstallHandler
|
|
291
|
+
if (options.install !== false) {
|
|
292
|
+
await this.installHandler.runInstall(
|
|
293
|
+
updateOptions.workspacePath || process.cwd(),
|
|
294
|
+
options.verbose
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
cliOutput.print(StyledText.iconInfo(t('command.update.dryRunHint')))
|
|
299
|
+
cliOutput.print(formatter.formatUpdatePlan(finalPlan))
|
|
359
300
|
}
|
|
301
|
+
}
|
|
360
302
|
|
|
361
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Validate command options
|
|
305
|
+
* QUAL-002: Uses unified validator from validators/
|
|
306
|
+
*/
|
|
307
|
+
static validateOptions(options: UpdateCommandOptions): string[] {
|
|
308
|
+
return errorsOnly(validateUpdateOptions)(options)
|
|
362
309
|
}
|
|
363
310
|
|
|
364
311
|
/**
|
|
@@ -382,14 +329,19 @@ Options:
|
|
|
382
329
|
--prerelease Include prerelease versions
|
|
383
330
|
--include <pattern> Include packages matching pattern (can be used multiple times)
|
|
384
331
|
--exclude <pattern> Exclude packages matching pattern (can be used multiple times)
|
|
385
|
-
--create-backup Create backup files before updating
|
|
332
|
+
--create-backup Create backup files before updating (default: true)
|
|
333
|
+
--no-backup Skip creating backup before updating
|
|
334
|
+
--install Run pnpm install after update (default: true)
|
|
335
|
+
--no-install Skip pnpm install after update
|
|
386
336
|
--verbose Show detailed information
|
|
387
337
|
--no-color Disable colored output
|
|
388
338
|
|
|
389
339
|
Examples:
|
|
390
|
-
pcu update # Update all catalogs
|
|
340
|
+
pcu update # Update all catalogs (with backup and pnpm install)
|
|
391
341
|
pcu update --interactive # Interactive update selection
|
|
392
342
|
pcu update --dry-run # Preview updates without applying
|
|
343
|
+
pcu update --no-backup # Update without creating backup
|
|
344
|
+
pcu update --no-install # Update catalogs without running pnpm install
|
|
393
345
|
pcu update --catalog react17 # Update specific catalog
|
|
394
346
|
pcu update --target minor # Update to latest minor versions only
|
|
395
347
|
pcu update --force # Force updates despite conflicts
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Command
|
|
3
|
+
*
|
|
4
|
+
* CLI command to display workspace information and validation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { WorkspaceService } from '@pcu/core'
|
|
8
|
+
import { CommandExitError } from '@pcu/utils'
|
|
9
|
+
import { type OutputFormat, OutputFormatter } from '../formatters/outputFormatter.js'
|
|
10
|
+
import { cliOutput } from '../utils/cliOutput.js'
|
|
11
|
+
|
|
12
|
+
export interface WorkspaceCommandOptions {
|
|
13
|
+
workspace?: string
|
|
14
|
+
validate?: boolean
|
|
15
|
+
stats?: boolean
|
|
16
|
+
format?: OutputFormat
|
|
17
|
+
verbose?: boolean
|
|
18
|
+
color?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class WorkspaceCommand {
|
|
22
|
+
constructor(private readonly workspaceService: WorkspaceService) {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Execute the workspace command
|
|
26
|
+
*/
|
|
27
|
+
async execute(options: WorkspaceCommandOptions = {}): Promise<void> {
|
|
28
|
+
const formatter = new OutputFormatter(options.format || 'table', options.color !== false)
|
|
29
|
+
|
|
30
|
+
if (options.validate) {
|
|
31
|
+
const report = await this.workspaceService.validateWorkspace(options.workspace)
|
|
32
|
+
const formattedOutput = formatter.formatValidationReport(report)
|
|
33
|
+
cliOutput.print(formattedOutput)
|
|
34
|
+
throw report.isValid
|
|
35
|
+
? CommandExitError.success()
|
|
36
|
+
: CommandExitError.failure('Validation failed')
|
|
37
|
+
} else if (options.stats) {
|
|
38
|
+
const stats = await this.workspaceService.getWorkspaceStats(options.workspace)
|
|
39
|
+
const formattedOutput = formatter.formatWorkspaceStats(stats)
|
|
40
|
+
cliOutput.print(formattedOutput)
|
|
41
|
+
throw CommandExitError.success()
|
|
42
|
+
} else {
|
|
43
|
+
const info = await this.workspaceService.getWorkspaceInfo(options.workspace)
|
|
44
|
+
const formattedOutput = formatter.formatWorkspaceInfo(info)
|
|
45
|
+
cliOutput.print(formattedOutput)
|
|
46
|
+
throw CommandExitError.success()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get command help text
|
|
52
|
+
*/
|
|
53
|
+
static getHelpText(): string {
|
|
54
|
+
return `
|
|
55
|
+
Workspace information and validation
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
pcu workspace [options]
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
--validate Validate workspace configuration
|
|
62
|
+
-s, --stats Show workspace statistics
|
|
63
|
+
-f, --format <type> Output format: table, json, yaml, minimal (default: table)
|
|
64
|
+
--verbose Show detailed information
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
pcu workspace # Show basic workspace info
|
|
68
|
+
pcu workspace --validate # Validate workspace configuration
|
|
69
|
+
pcu workspace --stats # Show workspace statistics
|
|
70
|
+
pcu workspace --format json # Output as JSON
|
|
71
|
+
`
|
|
72
|
+
}
|
|
73
|
+
}
|