pnpm-catalog-updates 0.7.19 → 1.0.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.
@@ -5,24 +5,24 @@
5
5
  * Supports table, JSON, YAML, and minimal output formats.
6
6
  */
7
7
 
8
- import {
8
+ import type {
9
+ AnalysisResult,
9
10
  ImpactAnalysis,
10
11
  OutdatedReport,
11
12
  UpdateResult,
12
13
  WorkspaceStats,
13
14
  WorkspaceValidationReport,
14
- } from '@pcu/core';
15
- import { SecurityReport } from '../commands/securityCommand.js';
15
+ } from '@pcu/core'
16
+ import chalk from 'chalk'
17
+ import Table from 'cli-table3'
18
+ import YAML from 'yaml'
19
+ import type { SecurityReport } from '../commands/securityCommand.js'
16
20
 
17
- import chalk from 'chalk';
18
- import Table from 'cli-table3';
19
- import YAML from 'yaml';
20
-
21
- export type OutputFormat = 'table' | 'json' | 'yaml' | 'minimal';
21
+ export type OutputFormat = 'table' | 'json' | 'yaml' | 'minimal'
22
22
 
23
23
  // Build ANSI escape regex without literal control characters
24
- const ANSI_ESCAPE = String.fromCharCode(27);
25
- const ansiRegex: RegExp = new RegExp(`${ANSI_ESCAPE}\\[[0-9;]*m`, 'g');
24
+ const ANSI_ESCAPE = String.fromCharCode(27)
25
+ const ansiRegex: RegExp = new RegExp(`${ANSI_ESCAPE}\\[[0-9;]*m`, 'g')
26
26
 
27
27
  export class OutputFormatter {
28
28
  constructor(
@@ -36,14 +36,13 @@ export class OutputFormatter {
36
36
  formatOutdatedReport(report: OutdatedReport): string {
37
37
  switch (this.format) {
38
38
  case 'json':
39
- return JSON.stringify(report, null, 2);
39
+ return JSON.stringify(report, null, 2)
40
40
  case 'yaml':
41
- return YAML.stringify(report);
41
+ return YAML.stringify(report)
42
42
  case 'minimal':
43
- return this.formatOutdatedMinimal(report);
44
- case 'table':
43
+ return this.formatOutdatedMinimal(report)
45
44
  default:
46
- return this.formatOutdatedTable(report);
45
+ return this.formatOutdatedTable(report)
47
46
  }
48
47
  }
49
48
 
@@ -53,14 +52,13 @@ export class OutputFormatter {
53
52
  formatUpdateResult(result: UpdateResult): string {
54
53
  switch (this.format) {
55
54
  case 'json':
56
- return JSON.stringify(result, null, 2);
55
+ return JSON.stringify(result, null, 2)
57
56
  case 'yaml':
58
- return YAML.stringify(result);
57
+ return YAML.stringify(result)
59
58
  case 'minimal':
60
- return this.formatUpdateMinimal(result);
61
- case 'table':
59
+ return this.formatUpdateMinimal(result)
62
60
  default:
63
- return this.formatUpdateTable(result);
61
+ return this.formatUpdateTable(result)
64
62
  }
65
63
  }
66
64
 
@@ -70,14 +68,13 @@ export class OutputFormatter {
70
68
  formatImpactAnalysis(analysis: ImpactAnalysis): string {
71
69
  switch (this.format) {
72
70
  case 'json':
73
- return JSON.stringify(analysis, null, 2);
71
+ return JSON.stringify(analysis, null, 2)
74
72
  case 'yaml':
75
- return YAML.stringify(analysis);
73
+ return YAML.stringify(analysis)
76
74
  case 'minimal':
77
- return this.formatImpactMinimal(analysis);
78
- case 'table':
75
+ return this.formatImpactMinimal(analysis)
79
76
  default:
80
- return this.formatImpactTable(analysis);
77
+ return this.formatImpactTable(analysis)
81
78
  }
82
79
  }
83
80
 
@@ -87,14 +84,13 @@ export class OutputFormatter {
87
84
  formatValidationReport(report: WorkspaceValidationReport): string {
88
85
  switch (this.format) {
89
86
  case 'json':
90
- return JSON.stringify(report, null, 2);
87
+ return JSON.stringify(report, null, 2)
91
88
  case 'yaml':
92
- return YAML.stringify(report);
89
+ return YAML.stringify(report)
93
90
  case 'minimal':
94
- return this.formatValidationMinimal(report);
95
- case 'table':
91
+ return this.formatValidationMinimal(report)
96
92
  default:
97
- return this.formatValidationTable(report);
93
+ return this.formatValidationTable(report)
98
94
  }
99
95
  }
100
96
 
@@ -104,14 +100,13 @@ export class OutputFormatter {
104
100
  formatWorkspaceStats(stats: WorkspaceStats): string {
105
101
  switch (this.format) {
106
102
  case 'json':
107
- return JSON.stringify(stats, null, 2);
103
+ return JSON.stringify(stats, null, 2)
108
104
  case 'yaml':
109
- return YAML.stringify(stats);
105
+ return YAML.stringify(stats)
110
106
  case 'minimal':
111
- return this.formatStatsMinimal(stats);
112
- case 'table':
107
+ return this.formatStatsMinimal(stats)
113
108
  default:
114
- return this.formatStatsTable(stats);
109
+ return this.formatStatsTable(stats)
115
110
  }
116
111
  }
117
112
 
@@ -121,14 +116,13 @@ export class OutputFormatter {
121
116
  formatSecurityReport(report: SecurityReport): string {
122
117
  switch (this.format) {
123
118
  case 'json':
124
- return JSON.stringify(report, null, 2);
119
+ return JSON.stringify(report, null, 2)
125
120
  case 'yaml':
126
- return YAML.stringify(report);
121
+ return YAML.stringify(report)
127
122
  case 'minimal':
128
- return this.formatSecurityMinimal(report);
129
- case 'table':
123
+ return this.formatSecurityMinimal(report)
130
124
  default:
131
- return this.formatSecurityTable(report);
125
+ return this.formatSecurityTable(report)
132
126
  }
133
127
  }
134
128
 
@@ -137,19 +131,18 @@ export class OutputFormatter {
137
131
  */
138
132
  formatMessage(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): string {
139
133
  if (!this.useColor) {
140
- return message;
134
+ return message
141
135
  }
142
136
 
143
137
  switch (type) {
144
138
  case 'success':
145
- return chalk.green(message);
139
+ return chalk.green(message)
146
140
  case 'error':
147
- return chalk.red(message);
141
+ return chalk.red(message)
148
142
  case 'warning':
149
- return chalk.yellow(message);
150
- case 'info':
143
+ return chalk.yellow(message)
151
144
  default:
152
- return chalk.blue(message);
145
+ return chalk.blue(message)
153
146
  }
154
147
  }
155
148
 
@@ -157,42 +150,42 @@ export class OutputFormatter {
157
150
  * Format outdated dependencies as table
158
151
  */
159
152
  private formatOutdatedTable(report: OutdatedReport): string {
160
- const lines: string[] = [];
153
+ const lines: string[] = []
161
154
 
162
155
  // Header
163
- lines.push(this.colorize(chalk.bold, `\n📦 Workspace: ${report.workspace.name}`));
164
- lines.push(this.colorize(chalk.gray, `Path: ${report.workspace.path}`));
156
+ lines.push(this.colorize(chalk.bold, `\n📦 Workspace: ${report.workspace.name}`))
157
+ lines.push(this.colorize(chalk.gray, `Path: ${report.workspace.path}`))
165
158
 
166
159
  if (!report.hasUpdates) {
167
- lines.push(this.colorize(chalk.green, '\n✅ All catalog dependencies are up to date!'));
168
- return lines.join('\n');
160
+ lines.push(this.colorize(chalk.green, '\n✅ All catalog dependencies are up to date!'))
161
+ return lines.join('\n')
169
162
  }
170
163
 
171
164
  lines.push(
172
165
  this.colorize(chalk.yellow, `\n🔄 Found ${report.totalOutdated} outdated dependencies\n`)
173
- );
166
+ )
174
167
 
175
168
  for (const catalogInfo of report.catalogs) {
176
- if (catalogInfo.outdatedCount === 0) continue;
169
+ if (catalogInfo.outdatedCount === 0) continue
177
170
 
178
- lines.push(this.colorize(chalk.bold, `📋 Catalog: ${catalogInfo.catalogName}`));
171
+ lines.push(this.colorize(chalk.bold, `📋 Catalog: ${catalogInfo.catalogName}`))
179
172
 
180
173
  const table = new Table({
181
174
  head: this.colorizeHeaders(['Package', 'Current', 'Latest', 'Type', 'Packages']),
182
175
  style: { head: [], border: [] },
183
176
  colWidths: [25, 15, 15, 8, 20],
184
- });
177
+ })
185
178
 
186
179
  for (const dep of catalogInfo.outdatedDependencies) {
187
- const typeColor = this.getUpdateTypeColor(dep.updateType);
188
- const securityIcon = dep.isSecurityUpdate ? '🔒 ' : '';
180
+ const typeColor = this.getUpdateTypeColor(dep.updateType)
181
+ const securityIcon = dep.isSecurityUpdate ? '🔒 ' : ''
189
182
 
190
183
  // Colorize version differences
191
184
  const { currentColored, latestColored } = this.colorizeVersionDiff(
192
185
  dep.currentVersion,
193
186
  dep.latestVersion,
194
187
  dep.updateType
195
- );
188
+ )
196
189
 
197
190
  table.push([
198
191
  `${securityIcon}${dep.packageName}`,
@@ -200,14 +193,14 @@ export class OutputFormatter {
200
193
  latestColored,
201
194
  this.colorize(typeColor, dep.updateType),
202
195
  `${dep.affectedPackages.length} package(s)`,
203
- ]);
196
+ ])
204
197
  }
205
198
 
206
- lines.push(table.toString());
207
- lines.push('');
199
+ lines.push(table.toString())
200
+ lines.push('')
208
201
  }
209
202
 
210
- return lines.join('\n');
203
+ return lines.join('\n')
211
204
  }
212
205
 
213
206
  /**
@@ -215,96 +208,96 @@ export class OutputFormatter {
215
208
  */
216
209
  private formatOutdatedMinimal(report: OutdatedReport): string {
217
210
  if (!report.hasUpdates) {
218
- return 'All dependencies up to date';
211
+ return 'All dependencies up to date'
219
212
  }
220
213
 
221
214
  // Collect all dependencies first to calculate max package name width
222
215
  const allDeps: Array<{
223
- securityIcon: string;
224
- packageName: string;
225
- currentColored: string;
226
- latestColored: string;
227
- }> = [];
216
+ securityIcon: string
217
+ packageName: string
218
+ currentColored: string
219
+ latestColored: string
220
+ }> = []
228
221
 
229
222
  for (const catalogInfo of report.catalogs) {
230
223
  for (const dep of catalogInfo.outdatedDependencies) {
231
- const securityIcon = dep.isSecurityUpdate ? '🔒 ' : '';
224
+ const securityIcon = dep.isSecurityUpdate ? '🔒 ' : ''
232
225
  const { currentColored, latestColored } = this.colorizeVersionDiff(
233
226
  dep.currentVersion,
234
227
  dep.latestVersion,
235
228
  dep.updateType
236
- );
229
+ )
237
230
  allDeps.push({
238
231
  securityIcon,
239
232
  packageName: dep.packageName,
240
233
  currentColored,
241
234
  latestColored,
242
- });
235
+ })
243
236
  }
244
237
  }
245
238
 
246
239
  // Calculate max widths for alignment
247
240
  const maxNameWidth = Math.max(
248
241
  ...allDeps.map((dep) => (dep.securityIcon + dep.packageName).length)
249
- );
242
+ )
250
243
 
251
244
  // Calculate max version widths (need to strip color codes for accurate width calculation)
252
- const stripAnsi = (str: string) => str.replace(ansiRegex, '');
253
- const maxCurrentWidth = Math.max(...allDeps.map((dep) => stripAnsi(dep.currentColored).length));
245
+ const stripAnsi = (str: string) => str.replace(ansiRegex, '')
246
+ const maxCurrentWidth = Math.max(...allDeps.map((dep) => stripAnsi(dep.currentColored).length))
254
247
 
255
248
  // Format lines with proper alignment
256
- const lines: string[] = [];
249
+ const lines: string[] = []
257
250
  for (const dep of allDeps) {
258
- const nameWithIcon = dep.securityIcon + dep.packageName;
259
- const paddedName = nameWithIcon.padEnd(maxNameWidth);
251
+ const nameWithIcon = dep.securityIcon + dep.packageName
252
+ const paddedName = nameWithIcon.padEnd(maxNameWidth)
260
253
 
261
254
  // For current version alignment, we need to pad the visible text, not the colored version
262
- const currentVisible = stripAnsi(dep.currentColored);
263
- const currentPadding = maxCurrentWidth - currentVisible.length;
264
- const paddedCurrent = dep.currentColored + ' '.repeat(currentPadding);
255
+ const currentVisible = stripAnsi(dep.currentColored)
256
+ const currentPadding = maxCurrentWidth - currentVisible.length
257
+ const paddedCurrent = dep.currentColored + ' '.repeat(currentPadding)
265
258
 
266
- lines.push(`${paddedName} ${paddedCurrent} → ${dep.latestColored}`);
259
+ lines.push(`${paddedName} ${paddedCurrent} → ${dep.latestColored}`)
267
260
  }
268
261
 
269
- return lines.join('\n');
262
+ return lines.join('\n')
270
263
  }
271
264
 
272
265
  /**
273
266
  * Format update result as table
274
267
  */
275
268
  private formatUpdateTable(result: UpdateResult): string {
276
- const lines: string[] = [];
269
+ const lines: string[] = []
277
270
 
278
271
  // Header
279
- lines.push(this.colorize(chalk.bold, `\n📦 Workspace: ${result.workspace.name}`));
272
+ lines.push(this.colorize(chalk.bold, `\n📦 Workspace: ${result.workspace.name}`))
280
273
 
281
274
  if (result.success) {
282
- lines.push(this.colorize(chalk.green, '✅ Update completed successfully!'));
275
+ lines.push(this.colorize(chalk.green, '✅ Update completed successfully!'))
283
276
  } else {
284
- lines.push(this.colorize(chalk.red, '❌ Update completed with errors'));
277
+ lines.push(this.colorize(chalk.red, '❌ Update completed with errors'))
285
278
  }
286
279
 
287
- lines.push('');
280
+ lines.push('')
288
281
 
289
282
  // Updated dependencies
290
283
  if (result.updatedDependencies.length > 0) {
291
- lines.push(this.colorize(chalk.green, `🎉 Updated ${result.totalUpdated} dependencies:`));
284
+ lines.push(this.colorize(chalk.green, `🎉 Updated ${result.totalUpdated} dependencies:`))
292
285
 
293
286
  const table = new Table({
294
287
  head: this.colorizeHeaders(['Catalog', 'Package', 'From', 'To', 'Type']),
295
288
  style: { head: [], border: [] },
296
289
  colWidths: [15, 25, 15, 15, 8],
297
- });
290
+ })
298
291
 
299
292
  for (const dep of result.updatedDependencies) {
300
- const typeColor = this.getUpdateTypeColor(dep.updateType);
293
+ const typeColor = this.getUpdateTypeColor(dep.updateType)
301
294
 
302
295
  // Colorize version differences
303
296
  const { currentColored, latestColored } = this.colorizeVersionDiff(
304
297
  dep.fromVersion,
305
298
  dep.toVersion,
306
299
  dep.updateType
307
- );
300
+ )
308
301
 
309
302
  table.push([
310
303
  dep.catalogName,
@@ -312,46 +305,46 @@ export class OutputFormatter {
312
305
  currentColored,
313
306
  latestColored,
314
307
  this.colorize(typeColor, dep.updateType),
315
- ]);
308
+ ])
316
309
  }
317
310
 
318
- lines.push(table.toString());
319
- lines.push('');
311
+ lines.push(table.toString())
312
+ lines.push('')
320
313
  }
321
314
 
322
315
  // Skipped dependencies
323
316
  if (result.skippedDependencies.length > 0) {
324
- lines.push(this.colorize(chalk.yellow, `⚠️ Skipped ${result.totalSkipped} dependencies:`));
317
+ lines.push(this.colorize(chalk.yellow, `⚠️ Skipped ${result.totalSkipped} dependencies:`))
325
318
 
326
319
  for (const dep of result.skippedDependencies) {
327
- lines.push(` ${dep.catalogName}:${dep.packageName} - ${dep.reason}`);
320
+ lines.push(` ${dep.catalogName}:${dep.packageName} - ${dep.reason}`)
328
321
  }
329
- lines.push('');
322
+ lines.push('')
330
323
  }
331
324
 
332
325
  // Errors
333
326
  if (result.errors.length > 0) {
334
- lines.push(this.colorize(chalk.red, `❌ ${result.totalErrors} errors occurred:`));
327
+ lines.push(this.colorize(chalk.red, `❌ ${result.totalErrors} errors occurred:`))
335
328
 
336
329
  for (const error of result.errors) {
337
- const prefix = error.fatal ? '💥' : '⚠️ ';
338
- lines.push(` ${prefix} ${error.catalogName}:${error.packageName} - ${error.error}`);
330
+ const prefix = error.fatal ? '💥' : '⚠️ '
331
+ lines.push(` ${prefix} ${error.catalogName}:${error.packageName} - ${error.error}`)
339
332
  }
340
333
  }
341
334
 
342
- return lines.join('\n');
335
+ return lines.join('\n')
343
336
  }
344
337
 
345
338
  /**
346
339
  * Format update result minimally (npm-check-updates style)
347
340
  */
348
341
  private formatUpdateMinimal(result: UpdateResult): string {
349
- const lines: string[] = [];
342
+ const lines: string[] = []
350
343
 
351
344
  if (result.success) {
352
- lines.push(`Updated ${result.totalUpdated} dependencies`);
345
+ lines.push(`Updated ${result.totalUpdated} dependencies`)
353
346
  } else {
354
- lines.push(`Update failed with ${result.totalErrors} errors`);
347
+ lines.push(`Update failed with ${result.totalErrors} errors`)
355
348
  }
356
349
 
357
350
  if (result.updatedDependencies.length > 0) {
@@ -361,83 +354,83 @@ export class OutputFormatter {
361
354
  dep.fromVersion,
362
355
  dep.toVersion,
363
356
  dep.updateType
364
- );
357
+ )
365
358
  return {
366
359
  packageName: dep.packageName,
367
360
  currentColored,
368
361
  latestColored,
369
- };
370
- });
362
+ }
363
+ })
371
364
 
372
365
  // Calculate max widths for alignment
373
- const maxNameWidth = Math.max(...depsWithVersions.map((dep) => dep.packageName.length));
366
+ const maxNameWidth = Math.max(...depsWithVersions.map((dep) => dep.packageName.length))
374
367
 
375
- const stripAnsi = (str: string) => str.replace(ansiRegex, '');
368
+ const stripAnsi = (str: string) => str.replace(ansiRegex, '')
376
369
  const maxCurrentWidth = Math.max(
377
370
  ...depsWithVersions.map((dep) => stripAnsi(dep.currentColored).length)
378
- );
371
+ )
379
372
 
380
373
  for (const dep of depsWithVersions) {
381
- const paddedName = dep.packageName.padEnd(maxNameWidth);
374
+ const paddedName = dep.packageName.padEnd(maxNameWidth)
382
375
 
383
376
  // Pad current version for alignment
384
- const currentVisible = stripAnsi(dep.currentColored);
385
- const currentPadding = maxCurrentWidth - currentVisible.length;
386
- const paddedCurrent = dep.currentColored + ' '.repeat(currentPadding);
377
+ const currentVisible = stripAnsi(dep.currentColored)
378
+ const currentPadding = maxCurrentWidth - currentVisible.length
379
+ const paddedCurrent = dep.currentColored + ' '.repeat(currentPadding)
387
380
 
388
- lines.push(`${paddedName} ${paddedCurrent} → ${dep.latestColored}`);
381
+ lines.push(`${paddedName} ${paddedCurrent} → ${dep.latestColored}`)
389
382
  }
390
383
  }
391
384
 
392
- return lines.join('\n');
385
+ return lines.join('\n')
393
386
  }
394
387
 
395
388
  /**
396
389
  * Format impact analysis as table
397
390
  */
398
391
  private formatImpactTable(analysis: ImpactAnalysis): string {
399
- const lines: string[] = [];
392
+ const lines: string[] = []
400
393
 
401
394
  // Header
402
- lines.push(this.colorize(chalk.bold, `\n🔍 Impact Analysis: ${analysis.packageName}`));
403
- lines.push(this.colorize(chalk.gray, `Catalog: ${analysis.catalogName}`));
395
+ lines.push(this.colorize(chalk.bold, `\n🔍 Impact Analysis: ${analysis.packageName}`))
396
+ lines.push(this.colorize(chalk.gray, `Catalog: ${analysis.catalogName}`))
404
397
  lines.push(
405
398
  this.colorize(chalk.gray, `Update: ${analysis.currentVersion} → ${analysis.proposedVersion}`)
406
- );
407
- lines.push(this.colorize(chalk.gray, `Type: ${analysis.updateType}`));
399
+ )
400
+ lines.push(this.colorize(chalk.gray, `Type: ${analysis.updateType}`))
408
401
 
409
402
  // Risk level
410
- const riskColor = this.getRiskColor(analysis.riskLevel);
411
- lines.push(this.colorize(riskColor, `Risk Level: ${analysis.riskLevel.toUpperCase()}`));
412
- lines.push('');
403
+ const riskColor = this.getRiskColor(analysis.riskLevel)
404
+ lines.push(this.colorize(riskColor, `Risk Level: ${analysis.riskLevel.toUpperCase()}`))
405
+ lines.push('')
413
406
 
414
407
  // Affected packages
415
408
  if (analysis.affectedPackages.length > 0) {
416
- lines.push(this.colorize(chalk.bold, '📦 Affected Packages:'));
409
+ lines.push(this.colorize(chalk.bold, '📦 Affected Packages:'))
417
410
 
418
411
  const table = new Table({
419
412
  head: this.colorizeHeaders(['Package', 'Path', 'Dependency Type', 'Risk']),
420
413
  style: { head: [], border: [] },
421
414
  colWidths: [20, 30, 15, 10],
422
- });
415
+ })
423
416
 
424
417
  for (const pkg of analysis.affectedPackages) {
425
- const riskColor = this.getRiskColor(pkg.compatibilityRisk);
418
+ const riskColor = this.getRiskColor(pkg.compatibilityRisk)
426
419
  table.push([
427
420
  pkg.packageName,
428
421
  pkg.packagePath,
429
422
  pkg.dependencyType,
430
423
  this.colorize(riskColor, pkg.compatibilityRisk),
431
- ]);
424
+ ])
432
425
  }
433
426
 
434
- lines.push(table.toString());
435
- lines.push('');
427
+ lines.push(table.toString())
428
+ lines.push('')
436
429
  }
437
430
 
438
431
  // Security impact
439
432
  if (analysis.securityImpact.hasVulnerabilities) {
440
- lines.push(this.colorize(chalk.bold, '🔒 Security Impact:'));
433
+ lines.push(this.colorize(chalk.bold, '🔒 Security Impact:'))
441
434
 
442
435
  if (analysis.securityImpact.fixedVulnerabilities > 0) {
443
436
  lines.push(
@@ -445,7 +438,7 @@ export class OutputFormatter {
445
438
  chalk.green,
446
439
  ` ✅ Fixes ${analysis.securityImpact.fixedVulnerabilities} vulnerabilities`
447
440
  )
448
- );
441
+ )
449
442
  }
450
443
 
451
444
  if (analysis.securityImpact.newVulnerabilities > 0) {
@@ -454,21 +447,21 @@ export class OutputFormatter {
454
447
  chalk.red,
455
448
  ` ⚠️ Introduces ${analysis.securityImpact.newVulnerabilities} vulnerabilities`
456
449
  )
457
- );
450
+ )
458
451
  }
459
452
 
460
- lines.push('');
453
+ lines.push('')
461
454
  }
462
455
 
463
456
  // Recommendations
464
457
  if (analysis.recommendations.length > 0) {
465
- lines.push(this.colorize(chalk.bold, '💡 Recommendations:'));
458
+ lines.push(this.colorize(chalk.bold, '💡 Recommendations:'))
466
459
  for (const rec of analysis.recommendations) {
467
- lines.push(` ${rec}`);
460
+ lines.push(` ${rec}`)
468
461
  }
469
462
  }
470
463
 
471
- return lines.join('\n');
464
+ return lines.join('\n')
472
465
  }
473
466
 
474
467
  /**
@@ -479,104 +472,101 @@ export class OutputFormatter {
479
472
  `${analysis.packageName}: ${analysis.currentVersion} → ${analysis.proposedVersion}`,
480
473
  `Risk: ${analysis.riskLevel}`,
481
474
  `Affected: ${analysis.affectedPackages.length} packages`,
482
- ].join('\n');
475
+ ].join('\n')
483
476
  }
484
477
 
485
478
  /**
486
479
  * Format validation report as table
487
480
  */
488
481
  private formatValidationTable(report: WorkspaceValidationReport): string {
489
- const lines: string[] = [];
482
+ const lines: string[] = []
490
483
 
491
484
  // Header
492
- const statusIcon = report.isValid ? '✅' : '❌';
493
- const statusColor = report.isValid ? chalk.green : chalk.red;
485
+ const statusIcon = report.isValid ? '✅' : '❌'
486
+ const statusColor = report.isValid ? chalk.green : chalk.red
494
487
 
495
- lines.push(this.colorize(chalk.bold, `\n${statusIcon} Workspace Validation`));
496
- lines.push(this.colorize(statusColor, `Status: ${report.isValid ? 'VALID' : 'INVALID'}`));
497
- lines.push('');
488
+ lines.push(this.colorize(chalk.bold, `\n${statusIcon} Workspace Validation`))
489
+ lines.push(this.colorize(statusColor, `Status: ${report.isValid ? 'VALID' : 'INVALID'}`))
490
+ lines.push('')
498
491
 
499
492
  // Workspace info
500
- lines.push(this.colorize(chalk.bold, '📦 Workspace Information:'));
501
- lines.push(` Path: ${report.workspace.path}`);
502
- lines.push(` Name: ${report.workspace.name}`);
503
- lines.push(` Packages: ${report.workspace.packageCount}`);
504
- lines.push(` Catalogs: ${report.workspace.catalogCount}`);
505
- lines.push('');
493
+ lines.push(this.colorize(chalk.bold, '📦 Workspace Information:'))
494
+ lines.push(` Path: ${report.workspace.path}`)
495
+ lines.push(` Name: ${report.workspace.name}`)
496
+ lines.push(` Packages: ${report.workspace.packageCount}`)
497
+ lines.push(` Catalogs: ${report.workspace.catalogCount}`)
498
+ lines.push('')
506
499
 
507
500
  // Errors
508
501
  if (report.errors.length > 0) {
509
- lines.push(this.colorize(chalk.red, '❌ Errors:'));
502
+ lines.push(this.colorize(chalk.red, '❌ Errors:'))
510
503
  for (const error of report.errors) {
511
- lines.push(` • ${error}`);
504
+ lines.push(` • ${error}`)
512
505
  }
513
- lines.push('');
506
+ lines.push('')
514
507
  }
515
508
 
516
509
  // Warnings
517
510
  if (report.warnings.length > 0) {
518
- lines.push(this.colorize(chalk.yellow, '⚠️ Warnings:'));
511
+ lines.push(this.colorize(chalk.yellow, '⚠️ Warnings:'))
519
512
  for (const warning of report.warnings) {
520
- lines.push(` • ${warning}`);
513
+ lines.push(` • ${warning}`)
521
514
  }
522
- lines.push('');
515
+ lines.push('')
523
516
  }
524
517
 
525
518
  // Recommendations
526
519
  if (report.recommendations.length > 0) {
527
- lines.push(this.colorize(chalk.blue, '💡 Recommendations:'));
520
+ lines.push(this.colorize(chalk.blue, '💡 Recommendations:'))
528
521
  for (const rec of report.recommendations) {
529
- lines.push(` • ${rec}`);
522
+ lines.push(` • ${rec}`)
530
523
  }
531
524
  }
532
525
 
533
- return lines.join('\n');
526
+ return lines.join('\n')
534
527
  }
535
528
 
536
529
  /**
537
530
  * Format validation report minimally
538
531
  */
539
532
  private formatValidationMinimal(report: WorkspaceValidationReport): string {
540
- const status = report.isValid ? 'VALID' : 'INVALID';
541
- const errors = report.errors.length;
542
- const warnings = report.warnings.length;
533
+ const status = report.isValid ? 'VALID' : 'INVALID'
534
+ const errors = report.errors.length
535
+ const warnings = report.warnings.length
543
536
 
544
- return `${status} (${errors} errors, ${warnings} warnings)`;
537
+ return `${status} (${errors} errors, ${warnings} warnings)`
545
538
  }
546
539
 
547
540
  /**
548
541
  * Format workspace statistics as table
549
542
  */
550
543
  private formatStatsTable(stats: WorkspaceStats): string {
551
- const lines: string[] = [];
544
+ const lines: string[] = []
552
545
 
553
- lines.push(this.colorize(chalk.bold, `\n📊 Workspace Statistics`));
554
- lines.push(this.colorize(chalk.gray, `Workspace: ${stats.workspace.name}`));
555
- lines.push('');
546
+ lines.push(this.colorize(chalk.bold, `\n📊 Workspace Statistics`))
547
+ lines.push(this.colorize(chalk.gray, `Workspace: ${stats.workspace.name}`))
548
+ lines.push('')
556
549
 
557
550
  const table = new Table({
558
551
  head: this.colorizeHeaders(['Metric', 'Count']),
559
552
  style: { head: [], border: [] },
560
553
  colWidths: [30, 10],
561
- });
562
-
563
- table.push(['Total Packages', stats.packages.total.toString()]);
564
- table.push(['Packages with Catalog Refs', stats.packages.withCatalogReferences.toString()]);
565
- table.push(['Total Catalogs', stats.catalogs.total.toString()]);
566
- table.push(['Catalog Entries', stats.catalogs.totalEntries.toString()]);
567
- table.push(['Total Dependencies', stats.dependencies.total.toString()]);
568
- table.push(['Catalog References', stats.dependencies.catalogReferences.toString()]);
569
- table.push(['Dependencies', stats.dependencies.byType.dependencies.toString()]);
570
- table.push(['Dev Dependencies', stats.dependencies.byType.devDependencies.toString()]);
571
- table.push(['Peer Dependencies', stats.dependencies.byType.peerDependencies.toString()]);
572
- table.push([
573
- 'Optional Dependencies',
574
- stats.dependencies.byType.optionalDependencies.toString(),
575
- ]);
576
-
577
- lines.push(table.toString());
578
-
579
- return lines.join('\n');
554
+ })
555
+
556
+ table.push(['Total Packages', stats.packages.total.toString()])
557
+ table.push(['Packages with Catalog Refs', stats.packages.withCatalogReferences.toString()])
558
+ table.push(['Total Catalogs', stats.catalogs.total.toString()])
559
+ table.push(['Catalog Entries', stats.catalogs.totalEntries.toString()])
560
+ table.push(['Total Dependencies', stats.dependencies.total.toString()])
561
+ table.push(['Catalog References', stats.dependencies.catalogReferences.toString()])
562
+ table.push(['Dependencies', stats.dependencies.byType.dependencies.toString()])
563
+ table.push(['Dev Dependencies', stats.dependencies.byType.devDependencies.toString()])
564
+ table.push(['Peer Dependencies', stats.dependencies.byType.peerDependencies.toString()])
565
+ table.push(['Optional Dependencies', stats.dependencies.byType.optionalDependencies.toString()])
566
+
567
+ lines.push(table.toString())
568
+
569
+ return lines.join('\n')
580
570
  }
581
571
 
582
572
  /**
@@ -587,96 +577,96 @@ export class OutputFormatter {
587
577
  `Packages: ${stats.packages.total}`,
588
578
  `Catalogs: ${stats.catalogs.total}`,
589
579
  `Dependencies: ${stats.dependencies.total}`,
590
- ].join(', ');
580
+ ].join(', ')
591
581
  }
592
582
 
593
583
  /**
594
584
  * Format security report as table
595
585
  */
596
586
  private formatSecurityTable(report: SecurityReport): string {
597
- const lines: string[] = [];
587
+ const lines: string[] = []
598
588
 
599
589
  // Header
600
- lines.push(this.colorize(chalk.bold, '\n🔒 Security Report'));
601
- lines.push(this.colorize(chalk.gray, `Workspace: ${report.metadata.workspacePath}`));
590
+ lines.push(this.colorize(chalk.bold, '\n🔒 Security Report'))
591
+ lines.push(this.colorize(chalk.gray, `Workspace: ${report.metadata.workspacePath}`))
602
592
  lines.push(
603
593
  this.colorize(chalk.gray, `Scan Date: ${new Date(report.metadata.scanDate).toLocaleString()}`)
604
- );
605
- lines.push(this.colorize(chalk.gray, `Tools: ${report.metadata.scanTools.join(', ')}`));
594
+ )
595
+ lines.push(this.colorize(chalk.gray, `Tools: ${report.metadata.scanTools.join(', ')}`))
606
596
 
607
597
  // Summary
608
- lines.push('');
609
- lines.push(this.colorize(chalk.bold, '📊 Summary:'));
598
+ lines.push('')
599
+ lines.push(this.colorize(chalk.bold, '📊 Summary:'))
610
600
 
611
601
  const summaryTable = new Table({
612
602
  head: this.colorizeHeaders(['Severity', 'Count']),
613
603
  style: { head: [], border: [] },
614
604
  colWidths: [15, 10],
615
- });
605
+ })
616
606
 
617
- summaryTable.push(['Critical', this.colorize(chalk.red, report.summary.critical.toString())]);
618
- summaryTable.push(['High', this.colorize(chalk.yellow, report.summary.high.toString())]);
619
- summaryTable.push(['Moderate', this.colorize(chalk.blue, report.summary.moderate.toString())]);
620
- summaryTable.push(['Low', this.colorize(chalk.green, report.summary.low.toString())]);
621
- summaryTable.push(['Info', this.colorize(chalk.gray, report.summary.info.toString())]);
607
+ summaryTable.push(['Critical', this.colorize(chalk.red, report.summary.critical.toString())])
608
+ summaryTable.push(['High', this.colorize(chalk.yellow, report.summary.high.toString())])
609
+ summaryTable.push(['Moderate', this.colorize(chalk.blue, report.summary.moderate.toString())])
610
+ summaryTable.push(['Low', this.colorize(chalk.green, report.summary.low.toString())])
611
+ summaryTable.push(['Info', this.colorize(chalk.gray, report.summary.info.toString())])
622
612
  summaryTable.push([
623
613
  'Total',
624
614
  this.colorize(chalk.bold, report.summary.totalVulnerabilities.toString()),
625
- ]);
615
+ ])
626
616
 
627
- lines.push(summaryTable.toString());
617
+ lines.push(summaryTable.toString())
628
618
 
629
619
  // Vulnerabilities
630
620
  if (report.vulnerabilities.length > 0) {
631
- lines.push('');
632
- lines.push(this.colorize(chalk.bold, '🐛 Vulnerabilities:'));
621
+ lines.push('')
622
+ lines.push(this.colorize(chalk.bold, '🐛 Vulnerabilities:'))
633
623
 
634
624
  const vulnTable = new Table({
635
625
  head: this.colorizeHeaders(['Package', 'Severity', 'Title', 'Fix Available']),
636
626
  style: { head: [], border: [] },
637
627
  colWidths: [20, 12, 40, 15],
638
- });
628
+ })
639
629
 
640
630
  for (const vuln of report.vulnerabilities) {
641
- const severityColor = this.getSeverityColor(vuln.severity);
631
+ const severityColor = this.getSeverityColor(vuln.severity)
642
632
  const fixStatus = vuln.fixAvailable
643
633
  ? typeof vuln.fixAvailable === 'string'
644
634
  ? vuln.fixAvailable
645
635
  : 'Yes'
646
- : 'No';
636
+ : 'No'
647
637
 
648
638
  vulnTable.push([
649
639
  vuln.package,
650
640
  this.colorize(severityColor, vuln.severity.toUpperCase()),
651
- vuln.title.length > 35 ? vuln.title.substring(0, 35) + '...' : vuln.title,
641
+ vuln.title.length > 35 ? `${vuln.title.substring(0, 35)}...` : vuln.title,
652
642
  fixStatus,
653
- ]);
643
+ ])
654
644
  }
655
645
 
656
- lines.push(vulnTable.toString());
646
+ lines.push(vulnTable.toString())
657
647
  }
658
648
 
659
649
  // Recommendations
660
650
  if (report.recommendations.length > 0) {
661
- lines.push('');
662
- lines.push(this.colorize(chalk.bold, '💡 Recommendations:'));
651
+ lines.push('')
652
+ lines.push(this.colorize(chalk.bold, '💡 Recommendations:'))
663
653
 
664
654
  for (const rec of report.recommendations) {
665
- lines.push(` ${rec.package}: ${rec.currentVersion} → ${rec.recommendedVersion}`);
666
- lines.push(` ${rec.reason} (${rec.impact})`);
655
+ lines.push(` ${rec.package}: ${rec.currentVersion} → ${rec.recommendedVersion}`)
656
+ lines.push(` ${rec.reason} (${rec.impact})`)
667
657
  }
668
658
  }
669
659
 
670
- return lines.join('\n');
660
+ return lines.join('\n')
671
661
  }
672
662
 
673
663
  /**
674
664
  * Format security report minimally
675
665
  */
676
666
  private formatSecurityMinimal(report: SecurityReport): string {
677
- const vulnerabilities = report.summary.totalVulnerabilities;
667
+ const vulnerabilities = report.summary.totalVulnerabilities
678
668
  if (vulnerabilities === 0) {
679
- return 'No vulnerabilities found';
669
+ return 'No vulnerabilities found'
680
670
  }
681
671
 
682
672
  return [
@@ -685,7 +675,7 @@ export class OutputFormatter {
685
675
  ` High: ${report.summary.high}`,
686
676
  ` Moderate: ${report.summary.moderate}`,
687
677
  ` Low: ${report.summary.low}`,
688
- ].join('\n');
678
+ ].join('\n')
689
679
  }
690
680
 
691
681
  /**
@@ -694,15 +684,15 @@ export class OutputFormatter {
694
684
  private getSeverityColor(severity: string): typeof chalk {
695
685
  switch (severity.toLowerCase()) {
696
686
  case 'critical':
697
- return chalk.red;
687
+ return chalk.red
698
688
  case 'high':
699
- return chalk.yellow;
689
+ return chalk.yellow
700
690
  case 'moderate':
701
- return chalk.blue;
691
+ return chalk.blue
702
692
  case 'low':
703
- return chalk.green;
693
+ return chalk.green
704
694
  default:
705
- return chalk.gray;
695
+ return chalk.gray
706
696
  }
707
697
  }
708
698
 
@@ -710,14 +700,14 @@ export class OutputFormatter {
710
700
  * Apply color if color is enabled
711
701
  */
712
702
  private colorize(colorFn: typeof chalk, text: string): string {
713
- return this.useColor ? colorFn(text) : text;
703
+ return this.useColor ? colorFn(text) : text
714
704
  }
715
705
 
716
706
  /**
717
707
  * Colorize table headers
718
708
  */
719
709
  private colorizeHeaders(headers: string[]): string[] {
720
- return this.useColor ? headers.map((h) => chalk.bold.cyan(h)) : headers;
710
+ return this.useColor ? headers.map((h) => chalk.bold.cyan(h)) : headers
721
711
  }
722
712
 
723
713
  /**
@@ -726,29 +716,13 @@ export class OutputFormatter {
726
716
  private getUpdateTypeColor(updateType: string): typeof chalk {
727
717
  switch (updateType) {
728
718
  case 'major':
729
- return chalk.red;
719
+ return chalk.red
730
720
  case 'minor':
731
- return chalk.yellow;
721
+ return chalk.yellow
732
722
  case 'patch':
733
- return chalk.green;
734
- default:
735
- return chalk.gray;
736
- }
737
- }
738
-
739
- /**
740
- * Get color for risk level
741
- */
742
- private getRiskColor(riskLevel: string): typeof chalk {
743
- switch (riskLevel) {
744
- case 'high':
745
- return chalk.red;
746
- case 'medium':
747
- return chalk.yellow;
748
- case 'low':
749
- return chalk.green;
723
+ return chalk.green
750
724
  default:
751
- return chalk.gray;
725
+ return chalk.gray
752
726
  }
753
727
  }
754
728
 
@@ -760,76 +734,322 @@ export class OutputFormatter {
760
734
  latest: string,
761
735
  updateType: string
762
736
  ): {
763
- currentColored: string;
764
- latestColored: string;
737
+ currentColored: string
738
+ latestColored: string
765
739
  } {
766
740
  if (!this.useColor) {
767
- return { currentColored: current, latestColored: latest };
741
+ return { currentColored: current, latestColored: latest }
768
742
  }
769
743
 
770
744
  // Parse version numbers to identify different parts
771
745
  const parseVersion = (version: string) => {
772
746
  // Remove leading ^ or ~ or other prefix characters
773
- const cleanVersion = version.replace(/^[\^~>=<]+/, '');
774
- const parts = cleanVersion.split('.');
747
+ const cleanVersion = version.replace(/^[\^~>=<]+/, '')
748
+ const parts = cleanVersion.split('.')
775
749
  return {
776
750
  major: parts[0] || '0',
777
751
  minor: parts[1] || '0',
778
752
  patch: parts[2] || '0',
779
753
  extra: parts.slice(3).join('.'),
780
754
  prefix: version.substring(0, version.length - cleanVersion.length),
781
- };
782
- };
755
+ }
756
+ }
783
757
 
784
- const currentParts = parseVersion(current);
785
- const latestParts = parseVersion(latest);
758
+ const currentParts = parseVersion(current)
759
+ const latestParts = parseVersion(latest)
786
760
 
787
761
  // Determine color based on update type for highlighting differences
788
- const diffColor = this.getUpdateTypeColor(updateType);
762
+ const diffColor = this.getUpdateTypeColor(updateType)
789
763
 
790
764
  // Build colored version strings by comparing each part
791
765
  const colorCurrentPart = (part: string, latestPart: string, isChanged: boolean) => {
792
766
  if (isChanged && part !== latestPart) {
793
- return chalk.dim.white(part); // Dim white for old version part
767
+ return chalk.dim.white(part) // Dim white for old version part
794
768
  }
795
- return chalk.white(part); // Unchanged parts in white
796
- };
769
+ return chalk.white(part) // Unchanged parts in white
770
+ }
797
771
 
798
772
  const colorLatestPart = (part: string, currentPart: string, isChanged: boolean) => {
799
773
  if (isChanged && part !== currentPart) {
800
- return diffColor(part); // Highlight the new version part with update type color
774
+ return diffColor(part) // Highlight the new version part with update type color
801
775
  }
802
- return chalk.white(part); // Unchanged parts in white
803
- };
776
+ return chalk.white(part) // Unchanged parts in white
777
+ }
804
778
 
805
779
  // Check which parts are different
806
- const majorChanged = currentParts.major !== latestParts.major;
807
- const minorChanged = currentParts.minor !== latestParts.minor;
808
- const patchChanged = currentParts.patch !== latestParts.patch;
809
- const extraChanged = currentParts.extra !== latestParts.extra;
780
+ const majorChanged = currentParts.major !== latestParts.major
781
+ const minorChanged = currentParts.minor !== latestParts.minor
782
+ const patchChanged = currentParts.patch !== latestParts.patch
783
+ const extraChanged = currentParts.extra !== latestParts.extra
810
784
 
811
785
  // Build colored current version
812
- let currentColored = currentParts.prefix;
813
- currentColored += colorCurrentPart(currentParts.major, latestParts.major, majorChanged);
814
- currentColored += '.';
815
- currentColored += colorCurrentPart(currentParts.minor, latestParts.minor, minorChanged);
816
- currentColored += '.';
817
- currentColored += colorCurrentPart(currentParts.patch, latestParts.patch, patchChanged);
786
+ let currentColored = currentParts.prefix
787
+ currentColored += colorCurrentPart(currentParts.major, latestParts.major, majorChanged)
788
+ currentColored += '.'
789
+ currentColored += colorCurrentPart(currentParts.minor, latestParts.minor, minorChanged)
790
+ currentColored += '.'
791
+ currentColored += colorCurrentPart(currentParts.patch, latestParts.patch, patchChanged)
818
792
  if (currentParts.extra) {
819
- currentColored += '.' + colorCurrentPart(currentParts.extra, latestParts.extra, extraChanged);
793
+ currentColored += `.${colorCurrentPart(currentParts.extra, latestParts.extra, extraChanged)}`
820
794
  }
821
795
 
822
796
  // Build colored latest version
823
- let latestColored = latestParts.prefix;
824
- latestColored += colorLatestPart(latestParts.major, currentParts.major, majorChanged);
825
- latestColored += '.';
826
- latestColored += colorLatestPart(latestParts.minor, currentParts.minor, minorChanged);
827
- latestColored += '.';
828
- latestColored += colorLatestPart(latestParts.patch, currentParts.patch, patchChanged);
797
+ let latestColored = latestParts.prefix
798
+ latestColored += colorLatestPart(latestParts.major, currentParts.major, majorChanged)
799
+ latestColored += '.'
800
+ latestColored += colorLatestPart(latestParts.minor, currentParts.minor, minorChanged)
801
+ latestColored += '.'
802
+ latestColored += colorLatestPart(latestParts.patch, currentParts.patch, patchChanged)
829
803
  if (latestParts.extra) {
830
- latestColored += '.' + colorLatestPart(latestParts.extra, currentParts.extra, extraChanged);
804
+ latestColored += `.${colorLatestPart(latestParts.extra, currentParts.extra, extraChanged)}`
805
+ }
806
+
807
+ return { currentColored, latestColored }
808
+ }
809
+
810
+ /**
811
+ * Format AI analysis result
812
+ */
813
+ formatAIAnalysis(aiResult: AnalysisResult, basicAnalysis?: ImpactAnalysis): string {
814
+ switch (this.format) {
815
+ case 'json':
816
+ return JSON.stringify({ aiAnalysis: aiResult, basicAnalysis }, null, 2)
817
+ case 'yaml':
818
+ return YAML.stringify({ aiAnalysis: aiResult, basicAnalysis })
819
+ case 'minimal':
820
+ return this.formatAIAnalysisMinimal(aiResult)
821
+ default:
822
+ return this.formatAIAnalysisTable(aiResult, basicAnalysis)
823
+ }
824
+ }
825
+
826
+ /**
827
+ * Format AI analysis as table
828
+ */
829
+ private formatAIAnalysisTable(aiResult: AnalysisResult, basicAnalysis?: ImpactAnalysis): string {
830
+ const lines: string[] = []
831
+
832
+ // Header with provider info
833
+ const providerColor = this.useColor ? chalk.cyan : (s: string) => s
834
+ const headerColor = this.useColor ? chalk.bold.white : (s: string) => s
835
+ const successColor = this.useColor ? chalk.green : (s: string) => s
836
+ const warningColor = this.useColor ? chalk.yellow : (s: string) => s
837
+ const errorColor = this.useColor ? chalk.red : (s: string) => s
838
+ const infoColor = this.useColor ? chalk.blue : (s: string) => s
839
+ const mutedColor = this.useColor ? chalk.gray : (s: string) => s
840
+
841
+ lines.push('')
842
+ lines.push(headerColor('═══════════════════════════════════════════════════════════════'))
843
+ lines.push(headerColor(' 🤖 AI Analysis Report'))
844
+ lines.push(headerColor('═══════════════════════════════════════════════════════════════'))
845
+ lines.push('')
846
+
847
+ // Provider and analysis info
848
+ lines.push(`${infoColor('Provider:')} ${providerColor(aiResult.provider)}`)
849
+ lines.push(`${infoColor('Analysis Type:')} ${aiResult.analysisType}`)
850
+ lines.push(`${infoColor('Confidence:')} ${this.formatConfidence(aiResult.confidence)}`)
851
+ lines.push('')
852
+
853
+ // Summary
854
+ lines.push(headerColor('📋 Summary'))
855
+ lines.push(headerColor('───────────────────────────────────────────────────────────────'))
856
+ lines.push(aiResult.summary)
857
+ lines.push('')
858
+
859
+ // Recommendations table
860
+ if (aiResult.recommendations.length > 0) {
861
+ lines.push(headerColor('💡 Recommendations'))
862
+ lines.push(headerColor('───────────────────────────────────────────────────────────────'))
863
+
864
+ const table = new Table({
865
+ head: ['Package', 'Version Change', 'Action', 'Risk', 'Reason'],
866
+ style: { head: this.useColor ? ['cyan'] : [] },
867
+ colWidths: [20, 20, 10, 10, 35],
868
+ wordWrap: true,
869
+ })
870
+
871
+ for (const rec of aiResult.recommendations) {
872
+ const riskColor = this.getRiskColor(rec.riskLevel)
873
+ const actionColor = this.getActionColor(rec.action)
874
+
875
+ table.push([
876
+ rec.package,
877
+ `${rec.currentVersion} → ${rec.targetVersion}`,
878
+ actionColor(rec.action),
879
+ riskColor(rec.riskLevel),
880
+ rec.reason,
881
+ ])
882
+ }
883
+
884
+ lines.push(table.toString())
885
+ lines.push('')
886
+ }
887
+
888
+ // Breaking changes
889
+ const allBreakingChanges = aiResult.recommendations
890
+ .filter((r) => r.breakingChanges && r.breakingChanges.length > 0)
891
+ .flatMap((r) => r.breakingChanges || [])
892
+
893
+ if (allBreakingChanges.length > 0) {
894
+ lines.push(errorColor('⚠️ Breaking Changes'))
895
+ lines.push(headerColor('───────────────────────────────────────────────────────────────'))
896
+ for (const change of allBreakingChanges) {
897
+ lines.push(` ${warningColor('•')} ${change}`)
898
+ }
899
+ lines.push('')
900
+ }
901
+
902
+ // Security fixes
903
+ const allSecurityFixes = aiResult.recommendations
904
+ .filter((r) => r.securityFixes && r.securityFixes.length > 0)
905
+ .flatMap((r) => r.securityFixes || [])
906
+
907
+ if (allSecurityFixes.length > 0) {
908
+ lines.push(successColor('🔒 Security Fixes'))
909
+ lines.push(headerColor('───────────────────────────────────────────────────────────────'))
910
+ for (const fix of allSecurityFixes) {
911
+ lines.push(` ${successColor('•')} ${fix}`)
912
+ }
913
+ lines.push('')
831
914
  }
832
915
 
833
- return { currentColored, latestColored };
916
+ // Warnings
917
+ if (aiResult.warnings && aiResult.warnings.length > 0) {
918
+ lines.push(warningColor('⚡ Warnings'))
919
+ lines.push(headerColor('───────────────────────────────────────────────────────────────'))
920
+ for (const warning of aiResult.warnings) {
921
+ lines.push(` ${warningColor('•')} ${warning}`)
922
+ }
923
+ lines.push('')
924
+ }
925
+
926
+ // Details
927
+ if (aiResult.details) {
928
+ lines.push(infoColor('📝 Details'))
929
+ lines.push(headerColor('───────────────────────────────────────────────────────────────'))
930
+ lines.push(aiResult.details)
931
+ lines.push('')
932
+ }
933
+
934
+ // Basic analysis info (if provided)
935
+ if (basicAnalysis) {
936
+ lines.push(mutedColor('📦 Affected Packages'))
937
+ lines.push(headerColor('───────────────────────────────────────────────────────────────'))
938
+ if (basicAnalysis.affectedPackages.length > 0) {
939
+ for (const pkg of basicAnalysis.affectedPackages.slice(0, 10)) {
940
+ // Handle both string and PackageImpact object types
941
+ const pkgName = typeof pkg === 'string' ? pkg : pkg.packageName
942
+ const pkgType =
943
+ typeof pkg === 'object' && pkg.dependencyType ? ` (${pkg.dependencyType})` : ''
944
+ lines.push(` ${mutedColor('•')} ${pkgName}${mutedColor(pkgType)}`)
945
+ }
946
+ if (basicAnalysis.affectedPackages.length > 10) {
947
+ lines.push(
948
+ ` ${mutedColor(` ... and ${basicAnalysis.affectedPackages.length - 10} more`)}`
949
+ )
950
+ }
951
+ } else {
952
+ lines.push(` ${mutedColor('No packages directly affected')}`)
953
+ }
954
+ lines.push('')
955
+ }
956
+
957
+ // Footer with metadata
958
+ lines.push(mutedColor('───────────────────────────────────────────────────────────────'))
959
+ const timestamp =
960
+ aiResult.timestamp instanceof Date
961
+ ? aiResult.timestamp.toISOString()
962
+ : String(aiResult.timestamp)
963
+ lines.push(mutedColor(`Generated at: ${timestamp}`))
964
+ if (aiResult.processingTimeMs) {
965
+ lines.push(mutedColor(`Processing time: ${aiResult.processingTimeMs}ms`))
966
+ }
967
+ if (aiResult.tokensUsed) {
968
+ lines.push(mutedColor(`Tokens used: ${aiResult.tokensUsed}`))
969
+ }
970
+ lines.push('')
971
+
972
+ return lines.join('\n')
973
+ }
974
+
975
+ /**
976
+ * Format AI analysis as minimal output
977
+ */
978
+ private formatAIAnalysisMinimal(aiResult: AnalysisResult): string {
979
+ const lines: string[] = []
980
+
981
+ lines.push(
982
+ `[${aiResult.provider}] ${aiResult.analysisType} (${Math.round(aiResult.confidence * 100)}% confidence)`
983
+ )
984
+ lines.push(aiResult.summary)
985
+
986
+ for (const rec of aiResult.recommendations) {
987
+ lines.push(
988
+ `${rec.action}: ${rec.package} ${rec.currentVersion} → ${rec.targetVersion} [${rec.riskLevel}]`
989
+ )
990
+ }
991
+
992
+ return lines.join('\n')
993
+ }
994
+
995
+ /**
996
+ * Format confidence score with color
997
+ */
998
+ private formatConfidence(confidence: number): string {
999
+ const percentage = Math.round(confidence * 100)
1000
+ const bar =
1001
+ '█'.repeat(Math.floor(percentage / 10)) + '░'.repeat(10 - Math.floor(percentage / 10))
1002
+
1003
+ if (!this.useColor) {
1004
+ return `${bar} ${percentage}%`
1005
+ }
1006
+
1007
+ if (confidence >= 0.8) {
1008
+ return chalk.green(`${bar} ${percentage}%`)
1009
+ } else if (confidence >= 0.5) {
1010
+ return chalk.yellow(`${bar} ${percentage}%`)
1011
+ } else {
1012
+ return chalk.red(`${bar} ${percentage}%`)
1013
+ }
1014
+ }
1015
+
1016
+ /**
1017
+ * Get color for risk level
1018
+ */
1019
+ private getRiskColor(riskLevel: string): typeof chalk {
1020
+ if (!this.useColor) return chalk
1021
+
1022
+ switch (riskLevel) {
1023
+ case 'critical':
1024
+ return chalk.red.bold
1025
+ case 'high':
1026
+ return chalk.red
1027
+ case 'medium':
1028
+ return chalk.yellow
1029
+ case 'low':
1030
+ return chalk.green
1031
+ default:
1032
+ return chalk.gray
1033
+ }
1034
+ }
1035
+
1036
+ /**
1037
+ * Get color for action
1038
+ */
1039
+ private getActionColor(action: string): typeof chalk {
1040
+ if (!this.useColor) return chalk
1041
+
1042
+ switch (action) {
1043
+ case 'update':
1044
+ return chalk.green
1045
+ case 'wait':
1046
+ return chalk.yellow
1047
+ case 'skip':
1048
+ return chalk.red
1049
+ case 'review':
1050
+ return chalk.cyan
1051
+ default:
1052
+ return chalk.white
1053
+ }
834
1054
  }
835
1055
  }