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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pnpm-catalog-updates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
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
|
+
})
|