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.
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,448 @@
1
+ /**
2
+ * OutputFormatter Snapshot Tests
3
+ *
4
+ * Tests for the OutputFormatter class using snapshot testing
5
+ * to ensure consistent output formatting across different formats.
6
+ */
7
+
8
+ import type {
9
+ OutdatedReport,
10
+ UpdatePlan,
11
+ UpdateResult,
12
+ WorkspaceInfo,
13
+ WorkspaceStats,
14
+ WorkspaceValidationReport,
15
+ } from '@pcu/core'
16
+ import { describe, expect, it, vi } from 'vitest'
17
+ import { OutputFormatter } from '../outputFormatter.js'
18
+
19
+ // Mock @pcu/utils functions
20
+ vi.mock('@pcu/utils', () => ({
21
+ t: (key: string, params?: Record<string, unknown>) => {
22
+ if (params) {
23
+ let result = key
24
+ for (const [k, v] of Object.entries(params)) {
25
+ result = result.replace(`{{${k}}}`, String(v))
26
+ }
27
+ return result
28
+ }
29
+ return key
30
+ },
31
+ // Include async utilities that may be used by the code
32
+ timeout: vi.fn().mockImplementation((promise: Promise<unknown>) => promise),
33
+ delay: vi.fn().mockResolvedValue(undefined),
34
+ retry: vi.fn().mockImplementation((fn: () => Promise<unknown>) => fn()),
35
+ }))
36
+
37
+ // Test fixtures
38
+ const mockWorkspace = {
39
+ name: 'test-workspace',
40
+ path: '/path/to/workspace',
41
+ }
42
+
43
+ const mockOutdatedReport: OutdatedReport = {
44
+ workspace: mockWorkspace,
45
+ hasUpdates: true,
46
+ totalOutdated: 3,
47
+ catalogs: [
48
+ {
49
+ catalogName: 'default',
50
+ totalDependencies: 10,
51
+ outdatedCount: 3,
52
+ outdatedDependencies: [
53
+ {
54
+ packageName: 'lodash',
55
+ currentVersion: '^4.17.0',
56
+ latestVersion: '^4.17.21',
57
+ updateType: 'patch',
58
+ isSecurityUpdate: false,
59
+ affectedPackages: ['app1', 'app2'],
60
+ },
61
+ {
62
+ packageName: 'react',
63
+ currentVersion: '^17.0.0',
64
+ latestVersion: '^18.2.0',
65
+ updateType: 'major',
66
+ isSecurityUpdate: false,
67
+ affectedPackages: ['app1'],
68
+ },
69
+ {
70
+ packageName: 'axios',
71
+ currentVersion: '^0.21.0',
72
+ latestVersion: '^1.6.0',
73
+ updateType: 'major',
74
+ isSecurityUpdate: true,
75
+ affectedPackages: ['app2', 'lib1'],
76
+ },
77
+ ],
78
+ },
79
+ ],
80
+ }
81
+
82
+ const mockEmptyOutdatedReport: OutdatedReport = {
83
+ workspace: mockWorkspace,
84
+ hasUpdates: false,
85
+ totalOutdated: 0,
86
+ catalogs: [
87
+ {
88
+ catalogName: 'default',
89
+ totalDependencies: 5,
90
+ outdatedCount: 0,
91
+ outdatedDependencies: [],
92
+ },
93
+ ],
94
+ }
95
+
96
+ const mockUpdatePlan: UpdatePlan = {
97
+ workspace: mockWorkspace,
98
+ totalUpdates: 2,
99
+ hasConflicts: false,
100
+ conflicts: [],
101
+ updates: [
102
+ {
103
+ catalogName: 'default',
104
+ packageName: 'lodash',
105
+ currentVersion: '^4.17.0',
106
+ newVersion: '^4.17.21',
107
+ updateType: 'patch',
108
+ },
109
+ {
110
+ catalogName: 'default',
111
+ packageName: 'express',
112
+ currentVersion: '^4.17.0',
113
+ newVersion: '^4.18.2',
114
+ updateType: 'minor',
115
+ },
116
+ ],
117
+ }
118
+
119
+ const mockUpdatePlanWithConflicts: UpdatePlan = {
120
+ workspace: mockWorkspace,
121
+ totalUpdates: 1,
122
+ hasConflicts: true,
123
+ conflicts: [
124
+ {
125
+ packageName: 'typescript',
126
+ catalogs: [
127
+ { catalogName: 'default', currentVersion: '4.9.0', proposedVersion: '5.0.0' },
128
+ { catalogName: 'legacy', currentVersion: '4.5.0', proposedVersion: '4.9.0' },
129
+ ],
130
+ recommendation: 'Consider aligning TypeScript versions across catalogs',
131
+ },
132
+ ],
133
+ updates: [
134
+ {
135
+ catalogName: 'default',
136
+ packageName: 'lodash',
137
+ currentVersion: '^4.17.0',
138
+ newVersion: '^4.17.21',
139
+ updateType: 'patch',
140
+ },
141
+ ],
142
+ }
143
+
144
+ const mockUpdateResult: UpdateResult = {
145
+ workspace: mockWorkspace,
146
+ success: true,
147
+ totalUpdated: 2,
148
+ totalSkipped: 1,
149
+ totalErrors: 0,
150
+ updatedDependencies: [
151
+ {
152
+ catalogName: 'default',
153
+ packageName: 'lodash',
154
+ fromVersion: '^4.17.0',
155
+ toVersion: '^4.17.21',
156
+ updateType: 'patch',
157
+ },
158
+ {
159
+ catalogName: 'default',
160
+ packageName: 'express',
161
+ fromVersion: '^4.17.0',
162
+ toVersion: '^4.18.2',
163
+ updateType: 'minor',
164
+ },
165
+ ],
166
+ skippedDependencies: [
167
+ {
168
+ catalogName: 'default',
169
+ packageName: 'react',
170
+ reason: 'Major version update requires --force flag',
171
+ },
172
+ ],
173
+ errors: [],
174
+ }
175
+
176
+ const mockFailedUpdateResult: UpdateResult = {
177
+ workspace: mockWorkspace,
178
+ success: false,
179
+ totalUpdated: 0,
180
+ totalSkipped: 0,
181
+ totalErrors: 1,
182
+ updatedDependencies: [],
183
+ skippedDependencies: [],
184
+ errors: [
185
+ {
186
+ catalogName: 'default',
187
+ packageName: 'broken-pkg',
188
+ error: 'Network error while fetching version',
189
+ fatal: true,
190
+ },
191
+ ],
192
+ }
193
+
194
+ const mockWorkspaceInfo: WorkspaceInfo = {
195
+ name: 'test-workspace',
196
+ path: '/path/to/workspace',
197
+ packageCount: 5,
198
+ catalogCount: 2,
199
+ catalogNames: ['default', 'legacy'],
200
+ isValid: true,
201
+ }
202
+
203
+ const mockWorkspaceStats: WorkspaceStats = {
204
+ workspace: mockWorkspace,
205
+ packages: {
206
+ total: 10,
207
+ withCatalogReferences: 8,
208
+ },
209
+ catalogs: {
210
+ total: 2,
211
+ totalEntries: 25,
212
+ },
213
+ dependencies: {
214
+ total: 50,
215
+ catalogReferences: 40,
216
+ byType: {
217
+ dependencies: 30,
218
+ devDependencies: 15,
219
+ peerDependencies: 3,
220
+ optionalDependencies: 2,
221
+ },
222
+ },
223
+ }
224
+
225
+ const mockValidationReport: WorkspaceValidationReport = {
226
+ workspace: {
227
+ path: '/path/to/workspace',
228
+ name: 'test-workspace',
229
+ packageCount: 5,
230
+ catalogCount: 2,
231
+ },
232
+ isValid: true,
233
+ errors: [],
234
+ warnings: ['Consider updating deprecated package "old-package"'],
235
+ recommendations: [
236
+ 'Add catalog entries for commonly used packages',
237
+ 'Consider using stricter version ranges',
238
+ ],
239
+ }
240
+
241
+ const mockInvalidValidationReport: WorkspaceValidationReport = {
242
+ workspace: {
243
+ path: '/path/to/workspace',
244
+ name: 'test-workspace',
245
+ packageCount: 5,
246
+ catalogCount: 2,
247
+ },
248
+ isValid: false,
249
+ errors: ['Missing required catalog "default"', 'Invalid version range for package "broken-pkg"'],
250
+ warnings: ['Consider updating deprecated package "old-package"'],
251
+ recommendations: ['Fix the errors above before proceeding'],
252
+ }
253
+
254
+ describe('OutputFormatter', () => {
255
+ // Use consistent settings for snapshot tests: no color for readable snapshots
256
+ const formats = ['table', 'json', 'yaml', 'minimal'] as const
257
+
258
+ describe('formatOutdatedReport', () => {
259
+ formats.forEach((format) => {
260
+ it(`should format outdated report as ${format}`, () => {
261
+ const formatter = new OutputFormatter(format, false)
262
+ const result = formatter.formatOutdatedReport(mockOutdatedReport)
263
+ expect(result).toMatchSnapshot()
264
+ })
265
+ })
266
+
267
+ it('should format empty outdated report', () => {
268
+ const formatter = new OutputFormatter('table', false)
269
+ const result = formatter.formatOutdatedReport(mockEmptyOutdatedReport)
270
+ expect(result).toMatchSnapshot()
271
+ })
272
+
273
+ it('should format outdated report minimal with aligned columns', () => {
274
+ const formatter = new OutputFormatter('minimal', false)
275
+ const result = formatter.formatOutdatedReport(mockOutdatedReport)
276
+ // Verify alignment by checking that all lines have similar structure
277
+ const lines = result.split('\n')
278
+ expect(lines.length).toBe(3)
279
+ lines.forEach((line) => {
280
+ expect(line).toContain('→')
281
+ })
282
+ })
283
+ })
284
+
285
+ describe('formatUpdatePlan', () => {
286
+ formats.forEach((format) => {
287
+ it(`should format update plan as ${format}`, () => {
288
+ const formatter = new OutputFormatter(format, false)
289
+ const result = formatter.formatUpdatePlan(mockUpdatePlan)
290
+ expect(result).toMatchSnapshot()
291
+ })
292
+ })
293
+
294
+ it('should format update plan with conflicts', () => {
295
+ const formatter = new OutputFormatter('table', false)
296
+ const result = formatter.formatUpdatePlan(mockUpdatePlanWithConflicts)
297
+ expect(result).toMatchSnapshot()
298
+ })
299
+
300
+ it('should handle empty update plan', () => {
301
+ const emptyPlan: UpdatePlan = {
302
+ workspace: mockWorkspace,
303
+ totalUpdates: 0,
304
+ hasConflicts: false,
305
+ conflicts: [],
306
+ updates: [],
307
+ }
308
+ const formatter = new OutputFormatter('table', false)
309
+ const result = formatter.formatUpdatePlan(emptyPlan)
310
+ expect(result).toMatchSnapshot()
311
+ })
312
+ })
313
+
314
+ describe('formatUpdateResult', () => {
315
+ formats.forEach((format) => {
316
+ it(`should format update result as ${format}`, () => {
317
+ const formatter = new OutputFormatter(format, false)
318
+ const result = formatter.formatUpdateResult(mockUpdateResult)
319
+ expect(result).toMatchSnapshot()
320
+ })
321
+ })
322
+
323
+ it('should format failed update result', () => {
324
+ const formatter = new OutputFormatter('table', false)
325
+ const result = formatter.formatUpdateResult(mockFailedUpdateResult)
326
+ expect(result).toMatchSnapshot()
327
+ })
328
+ })
329
+
330
+ describe('formatWorkspaceInfo', () => {
331
+ formats.forEach((format) => {
332
+ it(`should format workspace info as ${format}`, () => {
333
+ const formatter = new OutputFormatter(format, false)
334
+ const result = formatter.formatWorkspaceInfo(mockWorkspaceInfo)
335
+ expect(result).toMatchSnapshot()
336
+ })
337
+ })
338
+
339
+ it('should format workspace info without catalog names', () => {
340
+ const infoWithoutCatalogs: WorkspaceInfo = {
341
+ ...mockWorkspaceInfo,
342
+ catalogNames: [],
343
+ }
344
+ const formatter = new OutputFormatter('table', false)
345
+ const result = formatter.formatWorkspaceInfo(infoWithoutCatalogs)
346
+ expect(result).toMatchSnapshot()
347
+ })
348
+ })
349
+
350
+ describe('formatWorkspaceStats', () => {
351
+ formats.forEach((format) => {
352
+ it(`should format workspace stats as ${format}`, () => {
353
+ const formatter = new OutputFormatter(format, false)
354
+ const result = formatter.formatWorkspaceStats(mockWorkspaceStats)
355
+ expect(result).toMatchSnapshot()
356
+ })
357
+ })
358
+ })
359
+
360
+ describe('formatValidationReport', () => {
361
+ formats.forEach((format) => {
362
+ it(`should format valid report as ${format}`, () => {
363
+ const formatter = new OutputFormatter(format, false)
364
+ const result = formatter.formatValidationReport(mockValidationReport)
365
+ expect(result).toMatchSnapshot()
366
+ })
367
+ })
368
+
369
+ it('should format invalid validation report', () => {
370
+ const formatter = new OutputFormatter('table', false)
371
+ const result = formatter.formatValidationReport(mockInvalidValidationReport)
372
+ expect(result).toMatchSnapshot()
373
+ })
374
+ })
375
+
376
+ describe('formatMessage', () => {
377
+ it('should format success message', () => {
378
+ const formatter = new OutputFormatter('table', false)
379
+ const result = formatter.formatMessage('Operation completed', 'success')
380
+ expect(result).toBe('Operation completed')
381
+ })
382
+
383
+ it('should format error message', () => {
384
+ const formatter = new OutputFormatter('table', false)
385
+ const result = formatter.formatMessage('Something went wrong', 'error')
386
+ expect(result).toBe('Something went wrong')
387
+ })
388
+
389
+ it('should format warning message', () => {
390
+ const formatter = new OutputFormatter('table', false)
391
+ const result = formatter.formatMessage('Please review', 'warning')
392
+ expect(result).toBe('Please review')
393
+ })
394
+
395
+ it('should format info message', () => {
396
+ const formatter = new OutputFormatter('table', false)
397
+ const result = formatter.formatMessage('FYI', 'info')
398
+ expect(result).toBe('FYI')
399
+ })
400
+ })
401
+
402
+ describe('constructor defaults', () => {
403
+ it('should use table format by default', () => {
404
+ const formatter = new OutputFormatter()
405
+ const result = formatter.formatWorkspaceInfo(mockWorkspaceInfo)
406
+ // Table format includes box-drawing characters
407
+ expect(result).toContain('│')
408
+ })
409
+
410
+ it('should accept color option', () => {
411
+ // Note: Color output depends on terminal capabilities and environment
412
+ // We test that the color option is accepted without error
413
+ const formatterWithColor = new OutputFormatter('table', true)
414
+ const formatterWithoutColor = new OutputFormatter('table', false)
415
+ const resultWithColor = formatterWithColor.formatMessage('test', 'success')
416
+ const resultWithoutColor = formatterWithoutColor.formatMessage('test', 'success')
417
+ // Both should produce valid output
418
+ expect(resultWithColor).toBe('test')
419
+ expect(resultWithoutColor).toBe('test')
420
+ })
421
+ })
422
+
423
+ describe('JSON output', () => {
424
+ it('should produce valid JSON', () => {
425
+ const formatter = new OutputFormatter('json', false)
426
+ const result = formatter.formatOutdatedReport(mockOutdatedReport)
427
+ expect(() => JSON.parse(result)).not.toThrow()
428
+ })
429
+
430
+ it('should include all data in JSON', () => {
431
+ const formatter = new OutputFormatter('json', false)
432
+ const result = formatter.formatOutdatedReport(mockOutdatedReport)
433
+ const parsed = JSON.parse(result)
434
+ expect(parsed.workspace.name).toBe('test-workspace')
435
+ expect(parsed.totalOutdated).toBe(3)
436
+ expect(parsed.catalogs).toHaveLength(1)
437
+ })
438
+ })
439
+
440
+ describe('YAML output', () => {
441
+ it('should produce valid YAML structure', () => {
442
+ const formatter = new OutputFormatter('yaml', false)
443
+ const result = formatter.formatOutdatedReport(mockOutdatedReport)
444
+ expect(result).toContain('workspace:')
445
+ expect(result).toContain('name: test-workspace')
446
+ })
447
+ })
448
+ })