pnpm-catalog-updates 1.0.3 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/index.js +22031 -10684
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/src/cli/__tests__/commandRegistrar.test.ts +248 -0
- package/src/cli/commandRegistrar.ts +785 -0
- package/src/cli/commands/__tests__/aiCommand.test.ts +161 -0
- package/src/cli/commands/__tests__/analyzeCommand.test.ts +283 -0
- package/src/cli/commands/__tests__/checkCommand.test.ts +435 -0
- package/src/cli/commands/__tests__/graphCommand.test.ts +312 -0
- package/src/cli/commands/__tests__/initCommand.test.ts +317 -0
- package/src/cli/commands/__tests__/rollbackCommand.test.ts +400 -0
- package/src/cli/commands/__tests__/securityCommand.test.ts +467 -0
- package/src/cli/commands/__tests__/themeCommand.test.ts +166 -0
- package/src/cli/commands/__tests__/updateCommand.test.ts +720 -0
- package/src/cli/commands/__tests__/workspaceCommand.test.ts +286 -0
- package/src/cli/commands/aiCommand.ts +163 -0
- package/src/cli/commands/analyzeCommand.ts +219 -0
- package/src/cli/commands/checkCommand.ts +91 -98
- package/src/cli/commands/graphCommand.ts +475 -0
- package/src/cli/commands/initCommand.ts +64 -54
- package/src/cli/commands/rollbackCommand.ts +334 -0
- package/src/cli/commands/securityCommand.ts +165 -100
- package/src/cli/commands/themeCommand.ts +148 -0
- package/src/cli/commands/updateCommand.ts +215 -263
- package/src/cli/commands/workspaceCommand.ts +73 -0
- package/src/cli/constants/cliChoices.ts +93 -0
- package/src/cli/formatters/__tests__/__snapshots__/outputFormatter.test.ts.snap +557 -0
- package/src/cli/formatters/__tests__/ciFormatter.test.ts +526 -0
- package/src/cli/formatters/__tests__/outputFormatter.test.ts +448 -0
- package/src/cli/formatters/__tests__/progressBar.test.ts +709 -0
- package/src/cli/formatters/ciFormatter.ts +964 -0
- package/src/cli/formatters/colorUtils.ts +145 -0
- package/src/cli/formatters/outputFormatter.ts +615 -332
- package/src/cli/formatters/progressBar.ts +43 -52
- package/src/cli/formatters/versionFormatter.ts +132 -0
- package/src/cli/handlers/aiAnalysisHandler.ts +205 -0
- package/src/cli/handlers/changelogHandler.ts +113 -0
- package/src/cli/handlers/index.ts +9 -0
- package/src/cli/handlers/installHandler.ts +130 -0
- package/src/cli/index.ts +175 -726
- package/src/cli/interactive/InteractiveOptionsCollector.ts +387 -0
- package/src/cli/interactive/interactivePrompts.ts +189 -83
- package/src/cli/interactive/optionUtils.ts +89 -0
- package/src/cli/themes/colorTheme.ts +43 -16
- package/src/cli/utils/cliOutput.ts +118 -0
- package/src/cli/utils/commandHelpers.ts +249 -0
- package/src/cli/validators/commandValidator.ts +321 -336
- package/src/cli/validators/index.ts +37 -2
- package/src/cli/options/globalOptions.ts +0 -437
- package/src/cli/options/index.ts +0 -5
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollback Command
|
|
3
|
+
*
|
|
4
|
+
* CLI command to rollback catalog updates to a previous state.
|
|
5
|
+
* Supports listing backups and restoring from a specific backup.
|
|
6
|
+
*
|
|
7
|
+
* QUAL-006/QUAL-016: Refactored to use unified output helpers (cliOutput, StyledText).
|
|
8
|
+
* QUAL-007: Extracted common restore logic to reduce code duplication.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import path from 'node:path'
|
|
12
|
+
import { type BackupInfo, BackupService } from '@pcu/core'
|
|
13
|
+
import { t } from '@pcu/utils'
|
|
14
|
+
import inquirer from 'inquirer'
|
|
15
|
+
import { StyledText } from '../themes/colorTheme.js'
|
|
16
|
+
import { cliOutput } from '../utils/cliOutput.js'
|
|
17
|
+
import { handleCommandError } from '../utils/commandHelpers.js'
|
|
18
|
+
|
|
19
|
+
export interface RollbackCommandOptions {
|
|
20
|
+
workspace?: string
|
|
21
|
+
list?: boolean
|
|
22
|
+
latest?: boolean
|
|
23
|
+
select?: boolean
|
|
24
|
+
deleteAll?: boolean
|
|
25
|
+
verbose?: boolean
|
|
26
|
+
color?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class RollbackCommand {
|
|
30
|
+
private readonly backupService: BackupService
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
this.backupService = new BackupService({ maxBackups: 10 })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Execute the rollback command
|
|
38
|
+
*/
|
|
39
|
+
async execute(options: RollbackCommandOptions = {}): Promise<void> {
|
|
40
|
+
try {
|
|
41
|
+
const workspacePath = options.workspace || process.cwd()
|
|
42
|
+
const workspaceConfigPath = path.join(workspacePath, 'pnpm-workspace.yaml')
|
|
43
|
+
|
|
44
|
+
// List backups
|
|
45
|
+
if (options.list) {
|
|
46
|
+
await this.listBackups(workspaceConfigPath, options.verbose)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Delete all backups
|
|
51
|
+
if (options.deleteAll) {
|
|
52
|
+
await this.deleteAllBackups(workspaceConfigPath)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Restore from latest backup
|
|
57
|
+
if (options.latest) {
|
|
58
|
+
await this.restoreLatest(workspaceConfigPath)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Interactive selection (default behavior if no flags)
|
|
63
|
+
await this.interactiveRestore(workspaceConfigPath)
|
|
64
|
+
} catch (error) {
|
|
65
|
+
handleCommandError(error, {
|
|
66
|
+
verbose: options.verbose,
|
|
67
|
+
errorMessage: 'Rollback command failed',
|
|
68
|
+
context: { options },
|
|
69
|
+
})
|
|
70
|
+
throw error
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* List all available backups
|
|
76
|
+
*/
|
|
77
|
+
private async listBackups(workspaceConfigPath: string, verbose?: boolean): Promise<void> {
|
|
78
|
+
const backups = await this.backupService.listBackups(workspaceConfigPath)
|
|
79
|
+
|
|
80
|
+
if (backups.length === 0) {
|
|
81
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.noBackups')))
|
|
82
|
+
cliOutput.print(StyledText.muted(t('command.rollback.createBackupHint')))
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
cliOutput.print(
|
|
87
|
+
StyledText.info(`\n📋 ${t('command.rollback.availableBackups', { count: backups.length })}`)
|
|
88
|
+
)
|
|
89
|
+
cliOutput.print(StyledText.muted('─'.repeat(60)))
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < backups.length; i++) {
|
|
92
|
+
const backup = backups[i]
|
|
93
|
+
if (!backup) continue
|
|
94
|
+
|
|
95
|
+
const sizeKB = (backup.size / 1024).toFixed(2)
|
|
96
|
+
const isLatest = i === 0 ? StyledText.success(' (latest)') : ''
|
|
97
|
+
|
|
98
|
+
cliOutput.print(` ${StyledText.accent(`[${i + 1}]`)} ${backup.formattedTime}${isLatest}`)
|
|
99
|
+
|
|
100
|
+
if (verbose) {
|
|
101
|
+
cliOutput.print(StyledText.muted(` Path: ${backup.path}`))
|
|
102
|
+
cliOutput.print(StyledText.muted(` Size: ${sizeKB} KB`))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
cliOutput.print(StyledText.muted('─'.repeat(60)))
|
|
107
|
+
cliOutput.print(StyledText.muted(t('command.rollback.restoreHint')))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* QUAL-007: Common restore execution logic
|
|
112
|
+
* Performs the actual restore operation after user confirmation
|
|
113
|
+
*/
|
|
114
|
+
private async executeRestore(
|
|
115
|
+
workspaceConfigPath: string,
|
|
116
|
+
backup: BackupInfo,
|
|
117
|
+
promptMessage: string
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
cliOutput.print(StyledText.success(` ✓ ${t('command.rollback.autoBackupNote')}`))
|
|
120
|
+
|
|
121
|
+
const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>({
|
|
122
|
+
type: 'confirm',
|
|
123
|
+
name: 'confirmed',
|
|
124
|
+
message: promptMessage,
|
|
125
|
+
default: false,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (!confirmed) {
|
|
129
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.cancelled')))
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const preRestoreBackupPath = await this.backupService.restoreFromBackup(
|
|
134
|
+
workspaceConfigPath,
|
|
135
|
+
backup.path
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// Verify the restored file
|
|
139
|
+
const verification = await this.backupService.verifyRestoredFile(workspaceConfigPath)
|
|
140
|
+
await this.displayVerificationResult(verification, preRestoreBackupPath)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Restore from the latest backup
|
|
145
|
+
*/
|
|
146
|
+
private async restoreLatest(workspaceConfigPath: string): Promise<void> {
|
|
147
|
+
const backups = await this.backupService.listBackups(workspaceConfigPath)
|
|
148
|
+
|
|
149
|
+
if (backups.length === 0) {
|
|
150
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.noBackups')))
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const latestBackup = backups[0]
|
|
155
|
+
if (!latestBackup) {
|
|
156
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.noBackups')))
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
cliOutput.print(StyledText.info(`\n🔄 ${t('command.rollback.restoringLatest')}`))
|
|
161
|
+
cliOutput.print(
|
|
162
|
+
StyledText.muted(` ${t('command.rollback.from')}: ${latestBackup.formattedTime}`)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
await this.executeRestore(
|
|
166
|
+
workspaceConfigPath,
|
|
167
|
+
latestBackup,
|
|
168
|
+
t('command.rollback.confirmRestore')
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Display rollback verification results
|
|
174
|
+
*/
|
|
175
|
+
private async displayVerificationResult(
|
|
176
|
+
verification: Awaited<ReturnType<BackupService['verifyRestoredFile']>> | undefined,
|
|
177
|
+
preRestoreBackupPath: string
|
|
178
|
+
): Promise<void> {
|
|
179
|
+
if (!verification) {
|
|
180
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.verification.skipped')))
|
|
181
|
+
cliOutput.print(
|
|
182
|
+
StyledText.muted(
|
|
183
|
+
t('command.rollback.preRestoreBackupCreated', { path: preRestoreBackupPath })
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
cliOutput.print(StyledText.muted(t('command.rollback.runPnpmInstall')))
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (verification.success) {
|
|
191
|
+
cliOutput.print(StyledText.iconSuccess(t('command.rollback.success')))
|
|
192
|
+
cliOutput.print(StyledText.success(` ✓ ${t('command.rollback.verification.validYaml')}`))
|
|
193
|
+
cliOutput.print(
|
|
194
|
+
StyledText.success(
|
|
195
|
+
` ✓ ${t('command.rollback.verification.catalogsFound', { count: verification.catalogs.length })}`
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
if (verification.catalogs.length > 0) {
|
|
199
|
+
cliOutput.print(
|
|
200
|
+
StyledText.muted(
|
|
201
|
+
` ${t('command.rollback.verification.catalogs')}: ${verification.catalogs.join(', ')}`
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
cliOutput.print(
|
|
206
|
+
StyledText.muted(
|
|
207
|
+
` ${t('command.rollback.verification.dependencies', { count: verification.dependencyCount })}`
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
} else {
|
|
211
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.verification.warning')))
|
|
212
|
+
if (!verification.isValidYaml) {
|
|
213
|
+
cliOutput.print(StyledText.error(` ✗ ${t('command.rollback.verification.invalidYaml')}`))
|
|
214
|
+
}
|
|
215
|
+
if (!verification.hasCatalogStructure) {
|
|
216
|
+
cliOutput.print(StyledText.warning(` ⚠ ${t('command.rollback.verification.noCatalogs')}`))
|
|
217
|
+
}
|
|
218
|
+
if (verification.errorMessage) {
|
|
219
|
+
cliOutput.print(StyledText.muted(` ${verification.errorMessage}`))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
cliOutput.print(
|
|
224
|
+
StyledText.muted(
|
|
225
|
+
t('command.rollback.preRestoreBackupCreated', { path: preRestoreBackupPath })
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
cliOutput.print(StyledText.muted(t('command.rollback.safetyNote')))
|
|
229
|
+
cliOutput.print(StyledText.muted(t('command.rollback.runPnpmInstall')))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Interactive backup selection and restore
|
|
234
|
+
*/
|
|
235
|
+
private async interactiveRestore(workspaceConfigPath: string): Promise<void> {
|
|
236
|
+
const backups = await this.backupService.listBackups(workspaceConfigPath)
|
|
237
|
+
|
|
238
|
+
if (backups.length === 0) {
|
|
239
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.noBackups')))
|
|
240
|
+
cliOutput.print(StyledText.muted(t('command.rollback.createBackupHint')))
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
cliOutput.print(StyledText.info(`\n🔄 ${t('command.rollback.selectBackup')}`))
|
|
245
|
+
|
|
246
|
+
const choices = backups.map((backup, index) => ({
|
|
247
|
+
name: `${backup.formattedTime}${index === 0 ? ' (latest)' : ''} - ${(backup.size / 1024).toFixed(2)} KB`,
|
|
248
|
+
value: backup,
|
|
249
|
+
}))
|
|
250
|
+
|
|
251
|
+
const { selectedBackup } = await inquirer.prompt<{ selectedBackup: BackupInfo }>({
|
|
252
|
+
type: 'list',
|
|
253
|
+
name: 'selectedBackup',
|
|
254
|
+
message: t('command.rollback.chooseBackup'),
|
|
255
|
+
choices,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
cliOutput.print(StyledText.warning(`\n⚠️ ${t('command.rollback.warning')}`))
|
|
259
|
+
cliOutput.print(
|
|
260
|
+
StyledText.muted(
|
|
261
|
+
` ${t('command.rollback.willRestore', { time: selectedBackup.formattedTime })}`
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
await this.executeRestore(
|
|
266
|
+
workspaceConfigPath,
|
|
267
|
+
selectedBackup,
|
|
268
|
+
t('command.rollback.confirmRestore')
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Delete all backups
|
|
274
|
+
*/
|
|
275
|
+
private async deleteAllBackups(workspaceConfigPath: string): Promise<void> {
|
|
276
|
+
const backups = await this.backupService.listBackups(workspaceConfigPath)
|
|
277
|
+
|
|
278
|
+
if (backups.length === 0) {
|
|
279
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.noBackups')))
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
cliOutput.print(
|
|
284
|
+
StyledText.warning(`\n⚠️ ${t('command.rollback.deleteWarning', { count: backups.length })}`)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>({
|
|
288
|
+
type: 'confirm',
|
|
289
|
+
name: 'confirmed',
|
|
290
|
+
message: t('command.rollback.confirmDelete'),
|
|
291
|
+
default: false,
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
if (!confirmed) {
|
|
295
|
+
cliOutput.print(StyledText.iconWarning(t('command.rollback.cancelled')))
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const deleted = await this.backupService.deleteAllBackups(workspaceConfigPath)
|
|
300
|
+
cliOutput.print(
|
|
301
|
+
StyledText.iconSuccess(t('command.rollback.deletedBackups', { count: deleted }))
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get command help text
|
|
307
|
+
*/
|
|
308
|
+
static getHelpText(): string {
|
|
309
|
+
return `
|
|
310
|
+
Rollback catalog updates to a previous state
|
|
311
|
+
|
|
312
|
+
Usage:
|
|
313
|
+
pcu rollback [options]
|
|
314
|
+
|
|
315
|
+
Options:
|
|
316
|
+
--workspace <path> Workspace directory (default: current directory)
|
|
317
|
+
-l, --list List available backups
|
|
318
|
+
--latest Restore from the most recent backup
|
|
319
|
+
--delete-all Delete all backups
|
|
320
|
+
--verbose Show detailed information
|
|
321
|
+
|
|
322
|
+
Examples:
|
|
323
|
+
pcu rollback # Interactive backup selection
|
|
324
|
+
pcu rollback --list # List all available backups
|
|
325
|
+
pcu rollback --latest # Restore from the most recent backup
|
|
326
|
+
pcu rollback --delete-all # Delete all backups
|
|
327
|
+
|
|
328
|
+
Notes:
|
|
329
|
+
- Backups are automatically created when using 'pcu update -b'
|
|
330
|
+
- Before restoring, a new backup of the current state is created
|
|
331
|
+
- After rollback, run 'pnpm install' to sync the lock file
|
|
332
|
+
`
|
|
333
|
+
}
|
|
334
|
+
}
|