pnpm-catalog-updates 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +15 -0
  2. package/dist/index.js +22031 -10684
  3. package/dist/index.js.map +1 -1
  4. package/package.json +7 -2
  5. package/src/cli/__tests__/commandRegistrar.test.ts +248 -0
  6. package/src/cli/commandRegistrar.ts +785 -0
  7. package/src/cli/commands/__tests__/aiCommand.test.ts +161 -0
  8. package/src/cli/commands/__tests__/analyzeCommand.test.ts +283 -0
  9. package/src/cli/commands/__tests__/checkCommand.test.ts +435 -0
  10. package/src/cli/commands/__tests__/graphCommand.test.ts +312 -0
  11. package/src/cli/commands/__tests__/initCommand.test.ts +317 -0
  12. package/src/cli/commands/__tests__/rollbackCommand.test.ts +400 -0
  13. package/src/cli/commands/__tests__/securityCommand.test.ts +467 -0
  14. package/src/cli/commands/__tests__/themeCommand.test.ts +166 -0
  15. package/src/cli/commands/__tests__/updateCommand.test.ts +720 -0
  16. package/src/cli/commands/__tests__/workspaceCommand.test.ts +286 -0
  17. package/src/cli/commands/aiCommand.ts +163 -0
  18. package/src/cli/commands/analyzeCommand.ts +219 -0
  19. package/src/cli/commands/checkCommand.ts +91 -98
  20. package/src/cli/commands/graphCommand.ts +475 -0
  21. package/src/cli/commands/initCommand.ts +64 -54
  22. package/src/cli/commands/rollbackCommand.ts +334 -0
  23. package/src/cli/commands/securityCommand.ts +165 -100
  24. package/src/cli/commands/themeCommand.ts +148 -0
  25. package/src/cli/commands/updateCommand.ts +215 -263
  26. package/src/cli/commands/workspaceCommand.ts +73 -0
  27. package/src/cli/constants/cliChoices.ts +93 -0
  28. package/src/cli/formatters/__tests__/__snapshots__/outputFormatter.test.ts.snap +557 -0
  29. package/src/cli/formatters/__tests__/ciFormatter.test.ts +526 -0
  30. package/src/cli/formatters/__tests__/outputFormatter.test.ts +448 -0
  31. package/src/cli/formatters/__tests__/progressBar.test.ts +709 -0
  32. package/src/cli/formatters/ciFormatter.ts +964 -0
  33. package/src/cli/formatters/colorUtils.ts +145 -0
  34. package/src/cli/formatters/outputFormatter.ts +615 -332
  35. package/src/cli/formatters/progressBar.ts +43 -52
  36. package/src/cli/formatters/versionFormatter.ts +132 -0
  37. package/src/cli/handlers/aiAnalysisHandler.ts +205 -0
  38. package/src/cli/handlers/changelogHandler.ts +113 -0
  39. package/src/cli/handlers/index.ts +9 -0
  40. package/src/cli/handlers/installHandler.ts +130 -0
  41. package/src/cli/index.ts +175 -726
  42. package/src/cli/interactive/InteractiveOptionsCollector.ts +387 -0
  43. package/src/cli/interactive/interactivePrompts.ts +189 -83
  44. package/src/cli/interactive/optionUtils.ts +89 -0
  45. package/src/cli/themes/colorTheme.ts +43 -16
  46. package/src/cli/utils/cliOutput.ts +118 -0
  47. package/src/cli/utils/commandHelpers.ts +249 -0
  48. package/src/cli/validators/commandValidator.ts +321 -336
  49. package/src/cli/validators/index.ts +37 -2
  50. package/src/cli/options/globalOptions.ts +0 -437
  51. package/src/cli/options/index.ts +0 -5
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Rollback Command Tests
3
+ */
4
+
5
+ import type { BackupInfo } from '@pcu/core'
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7
+
8
+ // Mock BackupService using vi.hoisted for proper class mocking
9
+ const backupServiceMocks = vi.hoisted(() => ({
10
+ listBackups: vi.fn(),
11
+ restoreFromBackup: vi.fn(),
12
+ deleteAllBackups: vi.fn(),
13
+ verifyRestoredFile: vi.fn(),
14
+ }))
15
+
16
+ vi.mock('@pcu/core', () => {
17
+ return {
18
+ BackupService: class MockBackupService {
19
+ listBackups = backupServiceMocks.listBackups
20
+ restoreFromBackup = backupServiceMocks.restoreFromBackup
21
+ deleteAllBackups = backupServiceMocks.deleteAllBackups
22
+ verifyRestoredFile = backupServiceMocks.verifyRestoredFile
23
+ },
24
+ }
25
+ })
26
+
27
+ // Mock @pcu/utils
28
+ vi.mock('@pcu/utils', () => ({
29
+ logger: {
30
+ error: vi.fn(),
31
+ warn: vi.fn(),
32
+ info: vi.fn(),
33
+ debug: vi.fn(),
34
+ },
35
+ t: (key: string, params?: Record<string, unknown>) => {
36
+ if (params) {
37
+ return `${key} ${JSON.stringify(params)}`
38
+ }
39
+ return key
40
+ },
41
+ }))
42
+
43
+ // Mock inquirer
44
+ const inquirerMocks = vi.hoisted(() => ({
45
+ prompt: vi.fn(),
46
+ }))
47
+
48
+ vi.mock('inquirer', () => ({
49
+ default: {
50
+ prompt: inquirerMocks.prompt,
51
+ },
52
+ }))
53
+
54
+ // Mock chalk
55
+ vi.mock('chalk', () => ({
56
+ default: {
57
+ blue: (text: string) => `[blue]${text}`,
58
+ gray: (text: string) => `[gray]${text}`,
59
+ cyan: (text: string) => `[cyan]${text}`,
60
+ green: (text: string) => `[green]${text}`,
61
+ yellow: (text: string) => `[yellow]${text}`,
62
+ },
63
+ }))
64
+
65
+ // Mock StyledText - QUAL-006/QUAL-016: Updated to include all methods used
66
+ vi.mock('../../themes/colorTheme.js', () => ({
67
+ StyledText: {
68
+ iconWarning: (text?: string) => `[warning]${text ?? ''}`,
69
+ iconSuccess: (text?: string) => `[success]${text ?? ''}`,
70
+ iconError: (text?: string) => `[error]${text ?? ''}`,
71
+ error: (text: string) => `[error]${text}`,
72
+ muted: (text: string) => `[muted]${text}`,
73
+ info: (text: string) => `[info]${text}`,
74
+ warning: (text: string) => `[warning]${text}`,
75
+ success: (text: string) => `[success]${text}`,
76
+ highlight: (text: string) => `[highlight]${text}`,
77
+ accent: (text: string) => `[accent]${text}`,
78
+ },
79
+ }))
80
+
81
+ // Mock cliOutput - QUAL-006: Added for unified output helpers
82
+ vi.mock('../../utils/cliOutput.js', () => ({
83
+ cliOutput: {
84
+ print: vi.fn((...args: unknown[]) => console.log(...args)),
85
+ error: vi.fn((...args: unknown[]) => console.error(...args)),
86
+ warn: vi.fn((...args: unknown[]) => console.warn(...args)),
87
+ },
88
+ }))
89
+
90
+ // Mock commandHelpers
91
+ vi.mock('../../utils/commandHelpers.js', () => ({
92
+ handleCommandError: vi.fn((error: unknown, _options?: unknown) => {
93
+ console.error(`[error]error.unknown`)
94
+ console.error(`[error]${String(error)}`)
95
+ return false
96
+ }),
97
+ }))
98
+
99
+ const { RollbackCommand } = await import('../rollbackCommand.js')
100
+
101
+ describe('RollbackCommand', () => {
102
+ let command: InstanceType<typeof RollbackCommand>
103
+ let consoleSpy: ReturnType<typeof vi.spyOn>
104
+ let consoleErrorSpy: ReturnType<typeof vi.spyOn>
105
+
106
+ const mockBackups: BackupInfo[] = [
107
+ {
108
+ path: '/workspace/pnpm-workspace.yaml.backup.2024-01-15T10-30-00-000Z',
109
+ timestamp: new Date('2024-01-15T10:30:00.000Z'),
110
+ size: 2048,
111
+ formattedTime: '01/15/2024, 10:30:00',
112
+ },
113
+ {
114
+ path: '/workspace/pnpm-workspace.yaml.backup.2024-01-14T09-00-00-000Z',
115
+ timestamp: new Date('2024-01-14T09:00:00.000Z'),
116
+ size: 1536,
117
+ formattedTime: '01/14/2024, 09:00:00',
118
+ },
119
+ {
120
+ path: '/workspace/pnpm-workspace.yaml.backup.2024-01-13T08-00-00-000Z',
121
+ timestamp: new Date('2024-01-13T08:00:00.000Z'),
122
+ size: 1024,
123
+ formattedTime: '01/13/2024, 08:00:00',
124
+ },
125
+ ]
126
+
127
+ beforeEach(() => {
128
+ vi.clearAllMocks()
129
+ command = new RollbackCommand()
130
+ consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
131
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
132
+
133
+ // Setup default mock for verifyRestoredFile
134
+ backupServiceMocks.verifyRestoredFile.mockResolvedValue({
135
+ success: true,
136
+ isValidYaml: true,
137
+ hasCatalogStructure: true,
138
+ catalogs: ['default'],
139
+ dependencyCount: 5,
140
+ })
141
+ })
142
+
143
+ afterEach(() => {
144
+ consoleSpy.mockRestore()
145
+ consoleErrorSpy.mockRestore()
146
+ })
147
+
148
+ describe('execute with --list option', () => {
149
+ it('should list available backups', async () => {
150
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
151
+
152
+ await command.execute({ list: true })
153
+
154
+ expect(backupServiceMocks.listBackups).toHaveBeenCalledWith(
155
+ expect.stringContaining('pnpm-workspace.yaml')
156
+ )
157
+ expect(consoleSpy).toHaveBeenCalledWith(
158
+ expect.stringContaining('command.rollback.availableBackups')
159
+ )
160
+ })
161
+
162
+ it('should show message when no backups exist', async () => {
163
+ backupServiceMocks.listBackups.mockResolvedValue([])
164
+
165
+ await command.execute({ list: true })
166
+
167
+ expect(consoleSpy).toHaveBeenCalledWith('[warning]command.rollback.noBackups')
168
+ })
169
+
170
+ it('should show verbose information when verbose option is set', async () => {
171
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
172
+
173
+ await command.execute({ list: true, verbose: true })
174
+
175
+ // Verbose mode shows path and size
176
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Path:'))
177
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Size:'))
178
+ })
179
+
180
+ it('should use custom workspace path', async () => {
181
+ backupServiceMocks.listBackups.mockResolvedValue([])
182
+
183
+ await command.execute({ list: true, workspace: '/custom/path' })
184
+
185
+ expect(backupServiceMocks.listBackups).toHaveBeenCalledWith(
186
+ '/custom/path/pnpm-workspace.yaml'
187
+ )
188
+ })
189
+ })
190
+
191
+ describe('execute with --latest option', () => {
192
+ it('should restore from latest backup when confirmed', async () => {
193
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
194
+ backupServiceMocks.restoreFromBackup.mockResolvedValue(
195
+ '/workspace/pnpm-workspace.yaml.backup.pre-restore'
196
+ )
197
+ inquirerMocks.prompt.mockResolvedValue({ confirmed: true })
198
+
199
+ await command.execute({ latest: true })
200
+
201
+ expect(backupServiceMocks.restoreFromBackup).toHaveBeenCalledWith(
202
+ expect.stringContaining('pnpm-workspace.yaml'),
203
+ mockBackups[0]?.path
204
+ )
205
+ expect(consoleSpy).toHaveBeenCalledWith('[success]command.rollback.success')
206
+ })
207
+
208
+ it('should display auto-backup note and pre-restore backup path', async () => {
209
+ const preRestoreBackupPath = '/workspace/pnpm-workspace.yaml.backup.2024-01-16T12-00-00-000Z'
210
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
211
+ backupServiceMocks.restoreFromBackup.mockResolvedValue(preRestoreBackupPath)
212
+ inquirerMocks.prompt.mockResolvedValue({ confirmed: true })
213
+
214
+ await command.execute({ latest: true })
215
+
216
+ // Should show auto-backup note before confirmation
217
+ expect(consoleSpy).toHaveBeenCalledWith(
218
+ expect.stringContaining('command.rollback.autoBackupNote')
219
+ )
220
+ // Should show pre-restore backup path after restore
221
+ expect(consoleSpy).toHaveBeenCalledWith(
222
+ expect.stringContaining('command.rollback.preRestoreBackupCreated')
223
+ )
224
+ // Should show safety note
225
+ expect(consoleSpy).toHaveBeenCalledWith(
226
+ expect.stringContaining('command.rollback.safetyNote')
227
+ )
228
+ })
229
+
230
+ it('should not restore when user cancels', async () => {
231
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
232
+ inquirerMocks.prompt.mockResolvedValue({ confirmed: false })
233
+
234
+ await command.execute({ latest: true })
235
+
236
+ expect(backupServiceMocks.restoreFromBackup).not.toHaveBeenCalled()
237
+ expect(consoleSpy).toHaveBeenCalledWith('[warning]command.rollback.cancelled')
238
+ })
239
+
240
+ it('should show message when no backups exist', async () => {
241
+ backupServiceMocks.listBackups.mockResolvedValue([])
242
+
243
+ await command.execute({ latest: true })
244
+
245
+ expect(backupServiceMocks.restoreFromBackup).not.toHaveBeenCalled()
246
+ expect(consoleSpy).toHaveBeenCalledWith('[warning]command.rollback.noBackups')
247
+ })
248
+ })
249
+
250
+ describe('execute with --delete-all option', () => {
251
+ it('should delete all backups when confirmed', async () => {
252
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
253
+ backupServiceMocks.deleteAllBackups.mockResolvedValue(3)
254
+ inquirerMocks.prompt.mockResolvedValue({ confirmed: true })
255
+
256
+ await command.execute({ deleteAll: true })
257
+
258
+ expect(backupServiceMocks.deleteAllBackups).toHaveBeenCalledWith(
259
+ expect.stringContaining('pnpm-workspace.yaml')
260
+ )
261
+ expect(consoleSpy).toHaveBeenCalledWith(
262
+ expect.stringContaining('[success]command.rollback.deletedBackups')
263
+ )
264
+ })
265
+
266
+ it('should not delete when user cancels', async () => {
267
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
268
+ inquirerMocks.prompt.mockResolvedValue({ confirmed: false })
269
+
270
+ await command.execute({ deleteAll: true })
271
+
272
+ expect(backupServiceMocks.deleteAllBackups).not.toHaveBeenCalled()
273
+ expect(consoleSpy).toHaveBeenCalledWith('[warning]command.rollback.cancelled')
274
+ })
275
+
276
+ it('should show message when no backups exist', async () => {
277
+ backupServiceMocks.listBackups.mockResolvedValue([])
278
+
279
+ await command.execute({ deleteAll: true })
280
+
281
+ expect(backupServiceMocks.deleteAllBackups).not.toHaveBeenCalled()
282
+ expect(consoleSpy).toHaveBeenCalledWith('[warning]command.rollback.noBackups')
283
+ })
284
+ })
285
+
286
+ describe('execute with interactive mode (default)', () => {
287
+ it('should restore selected backup when confirmed', async () => {
288
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
289
+ backupServiceMocks.restoreFromBackup.mockResolvedValue(
290
+ '/workspace/pnpm-workspace.yaml.backup.pre-restore'
291
+ )
292
+ inquirerMocks.prompt
293
+ .mockResolvedValueOnce({ selectedBackup: mockBackups[1] })
294
+ .mockResolvedValueOnce({ confirmed: true })
295
+
296
+ await command.execute({})
297
+
298
+ expect(backupServiceMocks.restoreFromBackup).toHaveBeenCalledWith(
299
+ expect.stringContaining('pnpm-workspace.yaml'),
300
+ mockBackups[1]?.path
301
+ )
302
+ expect(consoleSpy).toHaveBeenCalledWith('[success]command.rollback.success')
303
+ })
304
+
305
+ it('should display auto-backup note and pre-restore backup path in interactive mode', async () => {
306
+ const preRestoreBackupPath = '/workspace/pnpm-workspace.yaml.backup.2024-01-16T12-00-00-000Z'
307
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
308
+ backupServiceMocks.restoreFromBackup.mockResolvedValue(preRestoreBackupPath)
309
+ inquirerMocks.prompt
310
+ .mockResolvedValueOnce({ selectedBackup: mockBackups[0] })
311
+ .mockResolvedValueOnce({ confirmed: true })
312
+
313
+ await command.execute({})
314
+
315
+ // Should show auto-backup note before confirmation
316
+ expect(consoleSpy).toHaveBeenCalledWith(
317
+ expect.stringContaining('command.rollback.autoBackupNote')
318
+ )
319
+ // Should show pre-restore backup path after restore
320
+ expect(consoleSpy).toHaveBeenCalledWith(
321
+ expect.stringContaining('command.rollback.preRestoreBackupCreated')
322
+ )
323
+ // Should show safety note
324
+ expect(consoleSpy).toHaveBeenCalledWith(
325
+ expect.stringContaining('command.rollback.safetyNote')
326
+ )
327
+ })
328
+
329
+ it('should not restore when user cancels after selection', async () => {
330
+ backupServiceMocks.listBackups.mockResolvedValue(mockBackups)
331
+ inquirerMocks.prompt
332
+ .mockResolvedValueOnce({ selectedBackup: mockBackups[0] })
333
+ .mockResolvedValueOnce({ confirmed: false })
334
+
335
+ await command.execute({})
336
+
337
+ expect(backupServiceMocks.restoreFromBackup).not.toHaveBeenCalled()
338
+ expect(consoleSpy).toHaveBeenCalledWith('[warning]command.rollback.cancelled')
339
+ })
340
+
341
+ it('should show message when no backups exist', async () => {
342
+ backupServiceMocks.listBackups.mockResolvedValue([])
343
+
344
+ await command.execute({})
345
+
346
+ expect(inquirerMocks.prompt).not.toHaveBeenCalled()
347
+ expect(consoleSpy).toHaveBeenCalledWith('[warning]command.rollback.noBackups')
348
+ })
349
+ })
350
+
351
+ describe('error handling', () => {
352
+ it('should handle and rethrow errors', async () => {
353
+ const error = new Error('Backup service failed')
354
+ backupServiceMocks.listBackups.mockRejectedValue(error)
355
+
356
+ await expect(command.execute({ list: true })).rejects.toThrow('Backup service failed')
357
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('[error]'))
358
+ })
359
+
360
+ it('should handle non-Error objects', async () => {
361
+ backupServiceMocks.listBackups.mockRejectedValue('String error')
362
+
363
+ await expect(command.execute({ list: true })).rejects.toBe('String error')
364
+ expect(consoleErrorSpy).toHaveBeenCalledWith('[error]error.unknown')
365
+ })
366
+ })
367
+
368
+ describe('getHelpText', () => {
369
+ it('should return help text', () => {
370
+ const helpText = RollbackCommand.getHelpText()
371
+
372
+ expect(helpText).toContain('Rollback catalog updates')
373
+ expect(helpText).toContain('--list')
374
+ expect(helpText).toContain('--latest')
375
+ expect(helpText).toContain('--delete-all')
376
+ expect(helpText).toContain('Examples:')
377
+ })
378
+ })
379
+
380
+ describe('workspace path handling', () => {
381
+ it('should use process.cwd() when no workspace option provided', async () => {
382
+ backupServiceMocks.listBackups.mockResolvedValue([])
383
+ const originalCwd = process.cwd()
384
+
385
+ await command.execute({ list: true })
386
+
387
+ expect(backupServiceMocks.listBackups).toHaveBeenCalledWith(
388
+ expect.stringContaining(originalCwd)
389
+ )
390
+ })
391
+
392
+ it('should use provided workspace path', async () => {
393
+ backupServiceMocks.listBackups.mockResolvedValue([])
394
+
395
+ await command.execute({ list: true, workspace: '/my/project' })
396
+
397
+ expect(backupServiceMocks.listBackups).toHaveBeenCalledWith('/my/project/pnpm-workspace.yaml')
398
+ })
399
+ })
400
+ })