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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pnpm-catalog-updates",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "CLI application for pnpm-catalog-updates",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,12 +25,18 @@
25
25
  "test": "vitest --run --passWithNoTests",
26
26
  "test:watch": "vitest --watch",
27
27
  "test:coverage": "vitest --run --coverage --passWithNoTests",
28
+ "test:e2e": "vitest --run tests/e2e",
28
29
  "check": "biome check src",
29
30
  "check:fix": "biome check --write src",
30
31
  "typecheck": "tsc --noEmit",
31
32
  "clean": "rimraf dist bin/*.js"
32
33
  },
33
34
  "dependencies": {
35
+ "@clack/prompts": "^0.11.0",
36
+ "@inquirer/core": "^10.3.2",
37
+ "@pcu/core": "workspace:*",
38
+ "@pcu/utils": "workspace:*",
39
+ "boxen": "^8.0.1",
34
40
  "chalk": "^5.6.2",
35
41
  "cli-table3": "^0.6.5",
36
42
  "commander": "^14.0.2",
@@ -50,7 +56,6 @@
50
56
  "@types/node": "catalog:node20",
51
57
  "@vitest/coverage-v8": "catalog:",
52
58
  "@vitest/ui": "catalog:",
53
- "eslint": "catalog:",
54
59
  "fs-extra": "catalog:",
55
60
  "msw": "catalog:",
56
61
  "prettier": "catalog:",
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Command Registrar Tests
3
+ */
4
+
5
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
6
+
7
+ // Use vi.hoisted for mock values
8
+ const mocks = vi.hoisted(() => ({
9
+ exitProcess: vi.fn(),
10
+ isCommandExitError: vi.fn(),
11
+ loggerError: vi.fn(),
12
+ }))
13
+
14
+ // Mock @inquirer/core
15
+ vi.mock('@inquirer/core', () => ({
16
+ ExitPromptError: class ExitPromptError extends Error {
17
+ constructor() {
18
+ super('User cancelled')
19
+ this.name = 'ExitPromptError'
20
+ }
21
+ },
22
+ }))
23
+
24
+ // Mock @pcu/utils
25
+ vi.mock('@pcu/utils', () => ({
26
+ exitProcess: mocks.exitProcess,
27
+ isCommandExitError: mocks.isCommandExitError,
28
+ Logger: {
29
+ setGlobalLevel: vi.fn(),
30
+ },
31
+ logger: {
32
+ error: mocks.loggerError,
33
+ warn: vi.fn(),
34
+ info: vi.fn(),
35
+ debug: vi.fn(),
36
+ },
37
+ parseBooleanFlag: (val: unknown) => Boolean(val),
38
+ t: (key: string) => key,
39
+ VersionChecker: {
40
+ checkVersion: vi.fn().mockResolvedValue({ isLatest: true, currentVersion: '1.0.0' }),
41
+ performUpdateAction: vi.fn().mockResolvedValue(true),
42
+ },
43
+ I18n: {
44
+ getLocale: vi.fn().mockReturnValue('en'),
45
+ },
46
+ }))
47
+
48
+ // Mock @pcu/core
49
+ vi.mock('@pcu/core', () => ({
50
+ CatalogUpdateService: {
51
+ createWithConfig: vi.fn().mockResolvedValue({}),
52
+ },
53
+ FileSystemService: class MockFileSystemService {},
54
+ FileWorkspaceRepository: class MockFileWorkspaceRepository {},
55
+ WorkspaceService: class MockWorkspaceService {},
56
+ PnpmPackageManagerService: class MockPnpmPackageManagerService {},
57
+ }))
58
+
59
+ // Mock CLI output
60
+ vi.mock('../utils/cliOutput.js', () => ({
61
+ cliOutput: {
62
+ print: vi.fn(),
63
+ error: vi.fn(),
64
+ },
65
+ }))
66
+
67
+ // Import type for proper typing
68
+ import type { Services } from '../commandRegistrar.js'
69
+
70
+ // Import after mock setup
71
+ const { LazyServiceFactory, isExitPromptError, handleCommandError } = await import(
72
+ '../commandRegistrar.js'
73
+ )
74
+
75
+ describe('commandRegistrar', () => {
76
+ beforeEach(() => {
77
+ vi.clearAllMocks()
78
+ })
79
+
80
+ describe('LazyServiceFactory', () => {
81
+ it('should create services lazily on first get() call', async () => {
82
+ const factory = new LazyServiceFactory('/test/path')
83
+
84
+ expect(factory.isInitialized()).toBe(false)
85
+
86
+ const services = await factory.get()
87
+
88
+ expect(factory.isInitialized()).toBe(true)
89
+ expect(services).toBeDefined()
90
+ expect(services.fileSystemService).toBeDefined()
91
+ expect(services.workspaceRepository).toBeDefined()
92
+ expect(services.catalogUpdateService).toBeDefined()
93
+ expect(services.workspaceService).toBeDefined()
94
+ expect(services.packageManagerService).toBeDefined()
95
+ })
96
+
97
+ it('should return same services on subsequent get() calls', async () => {
98
+ const factory = new LazyServiceFactory()
99
+
100
+ const services1 = await factory.get()
101
+ const services2 = await factory.get()
102
+
103
+ expect(services1).toBe(services2)
104
+ })
105
+
106
+ it('should reset cached services', async () => {
107
+ const factory = new LazyServiceFactory()
108
+
109
+ await factory.get()
110
+ expect(factory.isInitialized()).toBe(true)
111
+
112
+ factory.reset()
113
+ expect(factory.isInitialized()).toBe(false)
114
+ })
115
+
116
+ it('should allow injecting services via withServices', async () => {
117
+ const mockServices = {
118
+ fileSystemService: { mock: true },
119
+ workspaceRepository: { mock: true },
120
+ catalogUpdateService: { mock: true },
121
+ workspaceService: { mock: true },
122
+ packageManagerService: { mock: true },
123
+ } as Services
124
+
125
+ const factory = LazyServiceFactory.withServices(mockServices)
126
+
127
+ const services = await factory.get()
128
+
129
+ expect(services).toBe(mockServices)
130
+ expect(factory.isInitialized()).toBe(true)
131
+ })
132
+
133
+ it('should work without workspace path', async () => {
134
+ const factory = new LazyServiceFactory()
135
+
136
+ const services = await factory.get()
137
+
138
+ expect(services).toBeDefined()
139
+ })
140
+ })
141
+
142
+ describe('isExitPromptError', () => {
143
+ it('should return false for null/undefined', () => {
144
+ expect(isExitPromptError(null)).toBe(false)
145
+ expect(isExitPromptError(undefined)).toBe(false)
146
+ })
147
+
148
+ it('should return false for non-objects', () => {
149
+ expect(isExitPromptError('error')).toBe(false)
150
+ expect(isExitPromptError(123)).toBe(false)
151
+ expect(isExitPromptError(true)).toBe(false)
152
+ })
153
+
154
+ it('should return true for ExitPromptError instance', async () => {
155
+ const { ExitPromptError } = await import('@inquirer/core')
156
+ const error = new ExitPromptError()
157
+
158
+ expect(isExitPromptError(error)).toBe(true)
159
+ })
160
+
161
+ it('should return true for error with name ExitPromptError', () => {
162
+ const error = { name: 'ExitPromptError', message: 'cancelled' }
163
+
164
+ expect(isExitPromptError(error)).toBe(true)
165
+ })
166
+
167
+ it('should return true for error with constructor name ExitPromptError', () => {
168
+ const error = {
169
+ constructor: { name: 'ExitPromptError' },
170
+ message: 'cancelled',
171
+ }
172
+
173
+ expect(isExitPromptError(error)).toBe(true)
174
+ })
175
+
176
+ it('should return false for regular errors', () => {
177
+ const error = new Error('regular error')
178
+
179
+ expect(isExitPromptError(error)).toBe(false)
180
+ })
181
+ })
182
+
183
+ describe('handleCommandError', () => {
184
+ it('should handle CommandExitError and exit with its code', () => {
185
+ const error = { exitCode: 5 }
186
+ mocks.isCommandExitError.mockReturnValue(true)
187
+
188
+ handleCommandError(error, 'test')
189
+
190
+ expect(mocks.exitProcess).toHaveBeenCalledWith(5)
191
+ })
192
+
193
+ it('should handle ExitPromptError and exit with 0', () => {
194
+ const error = { name: 'ExitPromptError', message: 'cancelled' }
195
+ mocks.isCommandExitError.mockReturnValue(false)
196
+
197
+ handleCommandError(error, 'test')
198
+
199
+ expect(mocks.exitProcess).toHaveBeenCalledWith(0)
200
+ })
201
+
202
+ it('should log and exit with 1 for general errors', () => {
203
+ const error = new Error('general error')
204
+ mocks.isCommandExitError.mockReturnValue(false)
205
+
206
+ handleCommandError(error, 'test')
207
+
208
+ expect(mocks.loggerError).toHaveBeenCalled()
209
+ expect(mocks.exitProcess).toHaveBeenCalledWith(1)
210
+ })
211
+
212
+ it('should log command name in error', () => {
213
+ const error = new Error('test error')
214
+ mocks.isCommandExitError.mockReturnValue(false)
215
+
216
+ handleCommandError(error, 'mycommand')
217
+
218
+ expect(mocks.loggerError).toHaveBeenCalledWith(
219
+ 'mycommand command failed',
220
+ expect.any(Error),
221
+ expect.objectContaining({ command: 'mycommand' })
222
+ )
223
+ })
224
+ })
225
+ })
226
+
227
+ describe('LazyServiceFactory isolation', () => {
228
+ it('should provide isolated services for different instances', async () => {
229
+ const factory1 = new LazyServiceFactory('/path1')
230
+ const factory2 = new LazyServiceFactory('/path2')
231
+
232
+ const services1 = await factory1.get()
233
+ const services2 = await factory2.get()
234
+
235
+ // Each factory creates its own services
236
+ expect(services1).not.toBe(services2)
237
+ })
238
+
239
+ it('should allow multiple resets', () => {
240
+ const factory = new LazyServiceFactory()
241
+
242
+ factory.reset()
243
+ factory.reset()
244
+ factory.reset()
245
+
246
+ expect(factory.isInitialized()).toBe(false)
247
+ })
248
+ })