pnpm-catalog-updates 0.7.18 → 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.
- package/README.md +1 -1
- package/bin/pcu.js +3 -1
- package/dist/index.js +10212 -6761
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
- package/src/cli/commands/checkCommand.ts +62 -62
- package/src/cli/commands/initCommand.ts +90 -90
- package/src/cli/commands/securityCommand.ts +172 -172
- package/src/cli/commands/updateCommand.ts +227 -68
- package/src/cli/formatters/outputFormatter.ts +500 -280
- package/src/cli/formatters/progressBar.ts +228 -228
- package/src/cli/index.ts +407 -167
- package/src/cli/interactive/interactivePrompts.ts +100 -98
- package/src/cli/options/globalOptions.ts +143 -86
- package/src/cli/options/index.ts +1 -1
- package/src/cli/themes/colorTheme.ts +70 -70
- package/src/cli/validators/commandValidator.ts +118 -122
- package/src/cli/validators/index.ts +1 -1
- package/src/index.ts +1 -1
|
@@ -5,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
|
|
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
|
-
|
|
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
|
-
|
|
574
|
-
|
|
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)
|
|
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)
|
|
767
|
+
return chalk.dim.white(part) // Dim white for old version part
|
|
794
768
|
}
|
|
795
|
-
return chalk.white(part)
|
|
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)
|
|
774
|
+
return diffColor(part) // Highlight the new version part with update type color
|
|
801
775
|
}
|
|
802
|
-
return chalk.white(part)
|
|
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 +=
|
|
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 +=
|
|
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
|
-
|
|
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
|
}
|