pnpm-catalog-updates 1.0.2 → 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.
Files changed (51) hide show
  1. package/README.md +15 -0
  2. package/dist/index.js +22031 -10684
  3. package/dist/index.js.map +1 -1
  4. package/package.json +7 -2
  5. package/src/cli/__tests__/commandRegistrar.test.ts +248 -0
  6. package/src/cli/commandRegistrar.ts +785 -0
  7. package/src/cli/commands/__tests__/aiCommand.test.ts +161 -0
  8. package/src/cli/commands/__tests__/analyzeCommand.test.ts +283 -0
  9. package/src/cli/commands/__tests__/checkCommand.test.ts +435 -0
  10. package/src/cli/commands/__tests__/graphCommand.test.ts +312 -0
  11. package/src/cli/commands/__tests__/initCommand.test.ts +317 -0
  12. package/src/cli/commands/__tests__/rollbackCommand.test.ts +400 -0
  13. package/src/cli/commands/__tests__/securityCommand.test.ts +467 -0
  14. package/src/cli/commands/__tests__/themeCommand.test.ts +166 -0
  15. package/src/cli/commands/__tests__/updateCommand.test.ts +720 -0
  16. package/src/cli/commands/__tests__/workspaceCommand.test.ts +286 -0
  17. package/src/cli/commands/aiCommand.ts +163 -0
  18. package/src/cli/commands/analyzeCommand.ts +219 -0
  19. package/src/cli/commands/checkCommand.ts +91 -98
  20. package/src/cli/commands/graphCommand.ts +475 -0
  21. package/src/cli/commands/initCommand.ts +64 -54
  22. package/src/cli/commands/rollbackCommand.ts +334 -0
  23. package/src/cli/commands/securityCommand.ts +165 -100
  24. package/src/cli/commands/themeCommand.ts +148 -0
  25. package/src/cli/commands/updateCommand.ts +215 -263
  26. package/src/cli/commands/workspaceCommand.ts +73 -0
  27. package/src/cli/constants/cliChoices.ts +93 -0
  28. package/src/cli/formatters/__tests__/__snapshots__/outputFormatter.test.ts.snap +557 -0
  29. package/src/cli/formatters/__tests__/ciFormatter.test.ts +526 -0
  30. package/src/cli/formatters/__tests__/outputFormatter.test.ts +448 -0
  31. package/src/cli/formatters/__tests__/progressBar.test.ts +709 -0
  32. package/src/cli/formatters/ciFormatter.ts +964 -0
  33. package/src/cli/formatters/colorUtils.ts +145 -0
  34. package/src/cli/formatters/outputFormatter.ts +615 -332
  35. package/src/cli/formatters/progressBar.ts +43 -52
  36. package/src/cli/formatters/versionFormatter.ts +132 -0
  37. package/src/cli/handlers/aiAnalysisHandler.ts +205 -0
  38. package/src/cli/handlers/changelogHandler.ts +113 -0
  39. package/src/cli/handlers/index.ts +9 -0
  40. package/src/cli/handlers/installHandler.ts +130 -0
  41. package/src/cli/index.ts +175 -726
  42. package/src/cli/interactive/InteractiveOptionsCollector.ts +387 -0
  43. package/src/cli/interactive/interactivePrompts.ts +189 -83
  44. package/src/cli/interactive/optionUtils.ts +89 -0
  45. package/src/cli/themes/colorTheme.ts +43 -16
  46. package/src/cli/utils/cliOutput.ts +118 -0
  47. package/src/cli/utils/commandHelpers.ts +249 -0
  48. package/src/cli/validators/commandValidator.ts +321 -336
  49. package/src/cli/validators/index.ts +37 -2
  50. package/src/cli/options/globalOptions.ts +0 -437
  51. 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
- AIAnalysisService,
10
- type AnalysisType,
11
- type CatalogUpdateService,
12
- FileSystemService,
13
- FileWorkspaceRepository,
14
- type UpdateOptions,
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 { ConfigLoader } from '@pcu/utils'
20
- import chalk from 'chalk'
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, ThemeManager } from '../themes/colorTheme.js'
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
- constructor(updateService: CatalogUpdateService) {
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
- let progressBar: ProgressBar | undefined
84
+ const progressBar = this.createProgressBar()
59
85
 
60
86
  try {
61
- // Initialize theme
62
- ThemeManager.setTheme('default')
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
- // Step 1: Planning updates
99
- progressBar.update('正在检查包版本...', 1, 4)
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
- console.log(StyledText.iconSuccess('All dependencies are up to date!'))
95
+ progressBar.succeed(t('command.update.allUpToDate'))
96
+ cliOutput.print(StyledText.iconSuccess(t('command.update.allUpToDate')))
108
97
  return
109
98
  }
110
99
 
111
- console.log(
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
- 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
- }
102
+ // Process optional features and get final plan
103
+ const finalPlan = await this.processOptionalFeatures(plan, options)
104
+ if (!finalPlan) return
222
105
 
223
- // Step 3: Apply updates
224
- progressBar.update('正在准备应用更新...', 3, 4)
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
- console.log(StyledText.iconComplete('Update process completed!'))
109
+ cliOutput.print(StyledText.iconComplete(t('command.update.processComplete')))
247
110
  } catch (error) {
248
- if (progressBar) {
249
- progressBar.fail('Operation failed')
250
- }
111
+ this.handleExecuteError(error, progressBar, options)
112
+ }
113
+ }
251
114
 
252
- if (error instanceof Error) {
253
- console.error(StyledText.iconError(`Error: ${error.message}`))
254
- } else {
255
- console.error(StyledText.iconError('Unknown error occurred'))
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
- * Perform batch AI analysis for all packages in the update plan
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 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,
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
- // 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
264
+ return { config, updateOptions, formatter }
335
265
  }
336
266
 
337
267
  /**
338
- * Validate command options
268
+ * Execute updates or show dry-run preview
339
269
  */
340
- static validateOptions(options: UpdateCommandOptions): string[] {
341
- const errors: string[] = []
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
- // Validate format
344
- if (options.format && !['table', 'json', 'yaml', 'minimal'].includes(options.format)) {
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
- // Validate target
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
- // Interactive and dry-run conflict
357
- if (options.interactive && options.dryRun) {
358
- errors.push('Cannot use --interactive with --dry-run')
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
- return errors
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
+ }