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.
- 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
|
@@ -1,390 +1,375 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI Command Validation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Provides
|
|
4
|
+
* QUAL-002: Unified validation logic for CLI commands.
|
|
5
|
+
* Provides composable validators that can be combined per command.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { existsSync } from 'node:fs'
|
|
9
|
+
import { createValidationResult, t, type ValidationResult } from '@pcu/utils'
|
|
10
|
+
import {
|
|
11
|
+
FORMAT_CHOICES,
|
|
12
|
+
isValidAnalysisType,
|
|
13
|
+
isValidFormat,
|
|
14
|
+
isValidProvider,
|
|
15
|
+
isValidSeverity,
|
|
16
|
+
isValidTarget,
|
|
17
|
+
SEVERITY_CHOICES,
|
|
18
|
+
TARGET_CHOICES,
|
|
19
|
+
} from '../constants/cliChoices.js'
|
|
9
20
|
|
|
10
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Validation rule function signature
|
|
23
|
+
*/
|
|
24
|
+
type ValidationRule<T> = (options: T) => { errors: string[]; warnings: string[] }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Base options that many commands share
|
|
28
|
+
*/
|
|
29
|
+
export interface BaseCommandOptions {
|
|
11
30
|
workspace?: string
|
|
12
31
|
catalog?: string
|
|
13
32
|
format?: string
|
|
33
|
+
verbose?: boolean
|
|
34
|
+
color?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options that include update target
|
|
39
|
+
*/
|
|
40
|
+
export interface TargetOptions {
|
|
14
41
|
target?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Options for interactive mode
|
|
46
|
+
*/
|
|
47
|
+
export interface InteractiveOptions {
|
|
15
48
|
interactive?: boolean
|
|
16
49
|
dryRun?: boolean
|
|
17
|
-
force?: boolean
|
|
18
|
-
prerelease?: boolean
|
|
19
|
-
include?: string[]
|
|
20
|
-
exclude?: string[]
|
|
21
|
-
createBackup?: boolean
|
|
22
|
-
verbose?: boolean
|
|
23
|
-
color?: boolean
|
|
24
|
-
registry?: string
|
|
25
|
-
timeout?: number
|
|
26
50
|
}
|
|
27
51
|
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Options for AI analysis
|
|
54
|
+
*/
|
|
55
|
+
export interface AIOptions {
|
|
56
|
+
ai?: boolean
|
|
57
|
+
provider?: string
|
|
58
|
+
analysisType?: string
|
|
59
|
+
skipCache?: boolean
|
|
60
|
+
}
|
|
30
61
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Options for security command
|
|
64
|
+
*/
|
|
65
|
+
export interface SecurityOptions {
|
|
66
|
+
severity?: string
|
|
67
|
+
}
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Graph command specific options
|
|
71
|
+
*/
|
|
72
|
+
export interface GraphOptions {
|
|
73
|
+
type?: string
|
|
74
|
+
}
|
|
42
75
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Create validation result from errors and warnings
|
|
78
|
+
*/
|
|
79
|
+
function toValidationResult(errors: string[], warnings: string[]): ValidationResult {
|
|
80
|
+
return createValidationResult(errors.length === 0, errors, warnings)
|
|
81
|
+
}
|
|
47
82
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Compose multiple validation rules into a single validator
|
|
85
|
+
*/
|
|
86
|
+
export function composeValidators<T>(
|
|
87
|
+
...rules: ValidationRule<T>[]
|
|
88
|
+
): (options: T) => ValidationResult {
|
|
89
|
+
return (options: T) => {
|
|
90
|
+
const allErrors: string[] = []
|
|
91
|
+
const allWarnings: string[] = []
|
|
52
92
|
|
|
53
|
-
|
|
54
|
-
|
|
93
|
+
for (const rule of rules) {
|
|
94
|
+
const { errors, warnings } = rule(options)
|
|
95
|
+
allErrors.push(...errors)
|
|
96
|
+
allWarnings.push(...warnings)
|
|
55
97
|
}
|
|
56
98
|
|
|
57
|
-
return
|
|
58
|
-
isValid: errors.length === 0,
|
|
59
|
-
errors,
|
|
60
|
-
warnings,
|
|
61
|
-
}
|
|
99
|
+
return toValidationResult(allErrors, allWarnings)
|
|
62
100
|
}
|
|
101
|
+
}
|
|
63
102
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
validateUpdateOptions(options: any): ValidationResult {
|
|
68
|
-
const errors: string[] = []
|
|
69
|
-
const warnings: string[] = []
|
|
70
|
-
|
|
71
|
-
// Basic validation
|
|
72
|
-
const basicValidation = validateCliOptions(options)
|
|
73
|
-
errors.push(...basicValidation.errors)
|
|
74
|
-
warnings.push(...basicValidation.warnings)
|
|
75
|
-
|
|
76
|
-
// Update-specific validations
|
|
77
|
-
if (options.interactive && options.dryRun) {
|
|
78
|
-
errors.push('Cannot use --interactive with --dry-run')
|
|
79
|
-
}
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Individual Validation Rules
|
|
105
|
+
// ============================================================================
|
|
80
106
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Validate output format
|
|
109
|
+
*/
|
|
110
|
+
export function validateFormat<T extends { format?: string }>(
|
|
111
|
+
options: T
|
|
112
|
+
): {
|
|
113
|
+
errors: string[]
|
|
114
|
+
warnings: string[]
|
|
115
|
+
} {
|
|
116
|
+
const errors: string[] = []
|
|
117
|
+
const warnings: string[] = []
|
|
118
|
+
|
|
119
|
+
if (options.format && !isValidFormat(options.format)) {
|
|
120
|
+
errors.push(t('validation.invalidFormat'))
|
|
121
|
+
}
|
|
84
122
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
'Major updates may contain breaking changes. Consider using --interactive or --force'
|
|
88
|
-
)
|
|
89
|
-
}
|
|
123
|
+
return { errors, warnings }
|
|
124
|
+
}
|
|
90
125
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Validate update target
|
|
128
|
+
*/
|
|
129
|
+
export function validateTarget<T extends TargetOptions>(
|
|
130
|
+
options: T
|
|
131
|
+
): {
|
|
132
|
+
errors: string[]
|
|
133
|
+
warnings: string[]
|
|
134
|
+
} {
|
|
135
|
+
const errors: string[] = []
|
|
136
|
+
const warnings: string[] = []
|
|
137
|
+
|
|
138
|
+
if (options.target && !isValidTarget(options.target)) {
|
|
139
|
+
errors.push(t('validation.invalidTarget'))
|
|
140
|
+
}
|
|
100
141
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
142
|
+
return { errors, warnings }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validate workspace path exists
|
|
147
|
+
*/
|
|
148
|
+
export function validateWorkspacePath<T extends { workspace?: string }>(
|
|
149
|
+
options: T
|
|
150
|
+
): {
|
|
151
|
+
errors: string[]
|
|
152
|
+
warnings: string[]
|
|
153
|
+
} {
|
|
154
|
+
const errors: string[] = []
|
|
155
|
+
const warnings: string[] = []
|
|
156
|
+
|
|
157
|
+
if (options.workspace && !existsSync(options.workspace)) {
|
|
158
|
+
errors.push(t('validation.workspaceDirNotExist', { path: options.workspace }))
|
|
106
159
|
}
|
|
107
160
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
*/
|
|
111
|
-
validateAnalyzeArgs(catalog: string, packageName: string, version?: string): ValidationResult {
|
|
112
|
-
const errors: string[] = []
|
|
113
|
-
const warnings: string[] = []
|
|
114
|
-
|
|
115
|
-
// Validate catalog name
|
|
116
|
-
if (!catalog || catalog.trim() === '') {
|
|
117
|
-
errors.push('Catalog name is required')
|
|
118
|
-
} else if (catalog.includes('/') || catalog.includes('\\')) {
|
|
119
|
-
errors.push('Catalog name cannot contain path separators')
|
|
120
|
-
}
|
|
161
|
+
return { errors, warnings }
|
|
162
|
+
}
|
|
121
163
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Validate interactive mode conflicts
|
|
166
|
+
*/
|
|
167
|
+
export function validateInteractiveConflicts<T extends InteractiveOptions>(
|
|
168
|
+
options: T
|
|
169
|
+
): {
|
|
170
|
+
errors: string[]
|
|
171
|
+
warnings: string[]
|
|
172
|
+
} {
|
|
173
|
+
const errors: string[] = []
|
|
174
|
+
const warnings: string[] = []
|
|
175
|
+
|
|
176
|
+
if (options.interactive && options.dryRun) {
|
|
177
|
+
errors.push(t('validation.interactiveWithDryRun'))
|
|
178
|
+
}
|
|
132
179
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const semverRegex =
|
|
136
|
-
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
|
137
|
-
if (!semverRegex.test(version)) {
|
|
138
|
-
errors.push('Invalid version format. Use semantic versioning (e.g., 1.2.3)')
|
|
139
|
-
}
|
|
140
|
-
}
|
|
180
|
+
return { errors, warnings }
|
|
181
|
+
}
|
|
141
182
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Validate AI provider options
|
|
185
|
+
*/
|
|
186
|
+
export function validateAIOptions<T extends AIOptions>(
|
|
187
|
+
options: T
|
|
188
|
+
): {
|
|
189
|
+
errors: string[]
|
|
190
|
+
warnings: string[]
|
|
191
|
+
} {
|
|
192
|
+
const errors: string[] = []
|
|
193
|
+
const warnings: string[] = []
|
|
194
|
+
|
|
195
|
+
if (options.provider && !isValidProvider(options.provider)) {
|
|
196
|
+
errors.push(t('validation.invalidProvider'))
|
|
147
197
|
}
|
|
148
198
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
validateWorkspaceOptions(options: any): ValidationResult {
|
|
153
|
-
const errors: string[] = []
|
|
154
|
-
const warnings: string[] = []
|
|
155
|
-
|
|
156
|
-
// Basic validation
|
|
157
|
-
const basicValidation = validateCliOptions(options)
|
|
158
|
-
errors.push(...basicValidation.errors)
|
|
159
|
-
warnings.push(...basicValidation.warnings)
|
|
160
|
-
|
|
161
|
-
// Workspace-specific validations
|
|
162
|
-
const actionCount = [options.validate, options.stats, options.info].filter(Boolean).length
|
|
163
|
-
if (actionCount > 1) {
|
|
164
|
-
errors.push('Cannot use multiple workspace actions simultaneously')
|
|
165
|
-
}
|
|
199
|
+
if (options.analysisType && !isValidAnalysisType(options.analysisType)) {
|
|
200
|
+
errors.push(t('validation.invalidAnalysisType'))
|
|
201
|
+
}
|
|
166
202
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
203
|
+
return { errors, warnings }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Validate severity level
|
|
208
|
+
*/
|
|
209
|
+
export function validateSeverity<T extends SecurityOptions>(
|
|
210
|
+
options: T
|
|
211
|
+
): {
|
|
212
|
+
errors: string[]
|
|
213
|
+
warnings: string[]
|
|
214
|
+
} {
|
|
215
|
+
const errors: string[] = []
|
|
216
|
+
const warnings: string[] = []
|
|
217
|
+
|
|
218
|
+
if (options.severity && !isValidSeverity(options.severity)) {
|
|
219
|
+
errors.push(t('validation.invalidSeverity'))
|
|
172
220
|
}
|
|
173
221
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*/
|
|
177
|
-
validateGlobalOptions(options: any): ValidationResult {
|
|
178
|
-
const errors: string[] = []
|
|
179
|
-
const warnings: string[] = []
|
|
180
|
-
|
|
181
|
-
// Validate workspace path
|
|
182
|
-
if (options.workspace) {
|
|
183
|
-
// Future: Add path validation logic here
|
|
184
|
-
// Currently skipped to avoid TypeScript errors
|
|
185
|
-
}
|
|
222
|
+
return { errors, warnings }
|
|
223
|
+
}
|
|
186
224
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Validate graph type
|
|
227
|
+
*/
|
|
228
|
+
export function validateGraphType<T extends GraphOptions>(
|
|
229
|
+
options: T
|
|
230
|
+
): {
|
|
231
|
+
errors: string[]
|
|
232
|
+
warnings: string[]
|
|
233
|
+
} {
|
|
234
|
+
const errors: string[] = []
|
|
235
|
+
const warnings: string[] = []
|
|
236
|
+
const validTypes = ['catalog', 'package', 'full']
|
|
237
|
+
|
|
238
|
+
if (options.type && !validTypes.includes(options.type)) {
|
|
239
|
+
errors.push(t('validation.invalidGraphType', { validTypes: validTypes.join(', ') }))
|
|
240
|
+
}
|
|
191
241
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
for (const deprecated of deprecatedOptions) {
|
|
195
|
-
if (options[deprecated]) {
|
|
196
|
-
warnings.push(`Option --${deprecated} is deprecated and will be removed in future versions`)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
242
|
+
return { errors, warnings }
|
|
243
|
+
}
|
|
199
244
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Validate graph format (subset of formats)
|
|
247
|
+
*/
|
|
248
|
+
export function validateGraphFormat<T extends { format?: string }>(
|
|
249
|
+
options: T
|
|
250
|
+
): {
|
|
251
|
+
errors: string[]
|
|
252
|
+
warnings: string[]
|
|
253
|
+
} {
|
|
254
|
+
const errors: string[] = []
|
|
255
|
+
const warnings: string[] = []
|
|
256
|
+
const validFormats = ['text', 'mermaid', 'dot', 'json']
|
|
257
|
+
|
|
258
|
+
if (options.format && !validFormats.includes(options.format)) {
|
|
259
|
+
errors.push(t('validation.invalidGraphFormat', { validFormats: validFormats.join(', ') }))
|
|
205
260
|
}
|
|
206
261
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
*/
|
|
210
|
-
validateConfigFile(configPath: string): ValidationResult {
|
|
211
|
-
const errors: string[] = []
|
|
212
|
-
const warnings: string[] = []
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
const fs = require('node:fs')
|
|
216
|
-
// const path = require('path'); // Reserved for future use
|
|
217
|
-
|
|
218
|
-
if (!fs.existsSync(configPath)) {
|
|
219
|
-
errors.push(`Configuration file not found: ${configPath}`)
|
|
220
|
-
return { isValid: false, errors, warnings }
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
let config: any
|
|
224
|
-
|
|
225
|
-
if (configPath.endsWith('.js')) {
|
|
226
|
-
// JavaScript config file
|
|
227
|
-
try {
|
|
228
|
-
delete require.cache[require.resolve(configPath)]
|
|
229
|
-
config = require(configPath)
|
|
230
|
-
} catch (error) {
|
|
231
|
-
errors.push(`Failed to load JavaScript config: ${error}`)
|
|
232
|
-
return { isValid: false, errors, warnings }
|
|
233
|
-
}
|
|
234
|
-
} else {
|
|
235
|
-
// JSON config file
|
|
236
|
-
try {
|
|
237
|
-
const content = fs.readFileSync(configPath, 'utf-8')
|
|
238
|
-
config = JSON.parse(content)
|
|
239
|
-
} catch (error) {
|
|
240
|
-
errors.push(`Failed to parse JSON config: ${error}`)
|
|
241
|
-
return { isValid: false, errors, warnings }
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Validate config structure
|
|
246
|
-
if (typeof config !== 'object' || config === null) {
|
|
247
|
-
errors.push('Configuration must be an object')
|
|
248
|
-
return { isValid: false, errors, warnings }
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Validate known configuration sections
|
|
252
|
-
if (config.registry && typeof config.registry !== 'object') {
|
|
253
|
-
errors.push('registry configuration must be an object')
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (config.update && typeof config.update !== 'object') {
|
|
257
|
-
errors.push('update configuration must be an object')
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (config.output && typeof config.output !== 'object') {
|
|
261
|
-
errors.push('output configuration must be an object')
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Check for unknown top-level keys
|
|
265
|
-
const knownKeys = ['registry', 'update', 'output', 'workspace', 'notification', 'logging']
|
|
266
|
-
const unknownKeys = Object.keys(config).filter((key) => !knownKeys.includes(key))
|
|
267
|
-
|
|
268
|
-
if (unknownKeys.length > 0) {
|
|
269
|
-
warnings.push(`Unknown configuration keys: ${unknownKeys.join(', ')}`)
|
|
270
|
-
}
|
|
271
|
-
} catch (error) {
|
|
272
|
-
errors.push(`Failed to validate configuration file: ${error}`)
|
|
273
|
-
}
|
|
262
|
+
return { errors, warnings }
|
|
263
|
+
}
|
|
274
264
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Options for pattern validation
|
|
267
|
+
*/
|
|
268
|
+
export interface PatternOptions {
|
|
269
|
+
include?: string[]
|
|
270
|
+
exclude?: string[]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Validate include/exclude patterns
|
|
275
|
+
*/
|
|
276
|
+
export function validatePatterns<T extends PatternOptions>(
|
|
277
|
+
options: T
|
|
278
|
+
): {
|
|
279
|
+
errors: string[]
|
|
280
|
+
warnings: string[]
|
|
281
|
+
} {
|
|
282
|
+
const errors: string[] = []
|
|
283
|
+
const warnings: string[] = []
|
|
284
|
+
|
|
285
|
+
if (options.include?.some((pattern) => !pattern.trim())) {
|
|
286
|
+
errors.push(t('validation.includePatternsEmpty'))
|
|
280
287
|
}
|
|
281
288
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
sanitizeOptions(options: any): ValidatedOptions {
|
|
286
|
-
const sanitized: ValidatedOptions = {}
|
|
289
|
+
if (options.exclude?.some((pattern) => !pattern.trim())) {
|
|
290
|
+
errors.push(t('validation.excludePatternsEmpty'))
|
|
291
|
+
}
|
|
287
292
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
sanitized.workspace = String(options.workspace).trim()
|
|
291
|
-
}
|
|
292
|
-
if (options.catalog) {
|
|
293
|
-
sanitized.catalog = String(options.catalog).trim()
|
|
294
|
-
}
|
|
295
|
-
if (options.format) {
|
|
296
|
-
sanitized.format = String(options.format).toLowerCase().trim()
|
|
297
|
-
}
|
|
298
|
-
if (options.target) {
|
|
299
|
-
sanitized.target = String(options.target).toLowerCase().trim()
|
|
300
|
-
}
|
|
301
|
-
if (options.registry) {
|
|
302
|
-
sanitized.registry = String(options.registry).trim()
|
|
303
|
-
}
|
|
293
|
+
return { errors, warnings }
|
|
294
|
+
}
|
|
304
295
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
sanitized.force = Boolean(options.force)
|
|
309
|
-
sanitized.prerelease = Boolean(options.prerelease)
|
|
310
|
-
sanitized.createBackup = Boolean(options.createBackup)
|
|
311
|
-
sanitized.verbose = Boolean(options.verbose)
|
|
312
|
-
|
|
313
|
-
// Handle color option (tri-state: true, false, or undefined)
|
|
314
|
-
if (options.color !== undefined) {
|
|
315
|
-
sanitized.color = Boolean(options.color)
|
|
316
|
-
} else if (options.noColor) {
|
|
317
|
-
sanitized.color = false
|
|
318
|
-
}
|
|
296
|
+
// ============================================================================
|
|
297
|
+
// Pre-composed Validators for Common Command Types
|
|
298
|
+
// ============================================================================
|
|
319
299
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
300
|
+
/**
|
|
301
|
+
* Validator for check command
|
|
302
|
+
*/
|
|
303
|
+
export const validateCheckOptions = composeValidators<
|
|
304
|
+
BaseCommandOptions & TargetOptions & PatternOptions
|
|
305
|
+
>(validateFormat, validateTarget, validatePatterns)
|
|
327
306
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (options.exclude) {
|
|
335
|
-
sanitized.exclude = Array.isArray(options.exclude)
|
|
336
|
-
? options.exclude.map((p: any) => String(p).trim()).filter(Boolean)
|
|
337
|
-
: [String(options.exclude).trim()].filter(Boolean)
|
|
338
|
-
}
|
|
307
|
+
/**
|
|
308
|
+
* Validator for update command
|
|
309
|
+
*/
|
|
310
|
+
export const validateUpdateOptions = composeValidators<
|
|
311
|
+
BaseCommandOptions & TargetOptions & InteractiveOptions & AIOptions
|
|
312
|
+
>(validateFormat, validateTarget, validateInteractiveConflicts, validateAIOptions)
|
|
339
313
|
|
|
340
|
-
|
|
341
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Validator for security command
|
|
316
|
+
*/
|
|
317
|
+
export const validateSecurityOptions = composeValidators<BaseCommandOptions & SecurityOptions>(
|
|
318
|
+
validateFormat,
|
|
319
|
+
validateSeverity
|
|
320
|
+
)
|
|
342
321
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
case 'check':
|
|
351
|
-
if (!options.workspace) {
|
|
352
|
-
suggestions.push('Consider specifying --workspace for non-standard directory structures')
|
|
353
|
-
}
|
|
354
|
-
if (options.format === 'json' && options.verbose) {
|
|
355
|
-
suggestions.push('JSON output already includes detailed info, --verbose may be redundant')
|
|
356
|
-
}
|
|
357
|
-
break
|
|
358
|
-
|
|
359
|
-
case 'update':
|
|
360
|
-
if (!options.dryRun && !options.createBackup && !options.force) {
|
|
361
|
-
suggestions.push('Consider using --dry-run first to preview changes')
|
|
362
|
-
}
|
|
363
|
-
if (options.target === 'greatest' && !options.prerelease) {
|
|
364
|
-
suggestions.push('Add --prerelease to include pre-release versions with greatest target')
|
|
365
|
-
}
|
|
366
|
-
break
|
|
367
|
-
|
|
368
|
-
case 'analyze':
|
|
369
|
-
if (!options.format) {
|
|
370
|
-
suggestions.push('Use --format json for programmatic consumption of analysis data')
|
|
371
|
-
}
|
|
372
|
-
break
|
|
373
|
-
|
|
374
|
-
case 'workspace':
|
|
375
|
-
if (!options.validate && !options.stats) {
|
|
376
|
-
suggestions.push(
|
|
377
|
-
'Use --validate to check workspace integrity or --stats for detailed information'
|
|
378
|
-
)
|
|
379
|
-
}
|
|
380
|
-
break
|
|
381
|
-
}
|
|
322
|
+
/**
|
|
323
|
+
* Validator for graph command
|
|
324
|
+
*/
|
|
325
|
+
export const validateGraphOptions = composeValidators<BaseCommandOptions & GraphOptions>(
|
|
326
|
+
validateGraphFormat,
|
|
327
|
+
validateGraphType
|
|
328
|
+
)
|
|
382
329
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Validator for analyze command
|
|
332
|
+
*/
|
|
333
|
+
export const validateAnalyzeOptions = composeValidators<BaseCommandOptions & AIOptions>(
|
|
334
|
+
validateFormat,
|
|
335
|
+
validateAIOptions
|
|
336
|
+
)
|
|
387
337
|
|
|
388
|
-
|
|
389
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Validator for init command
|
|
340
|
+
*/
|
|
341
|
+
export const validateInitOptions = composeValidators<BaseCommandOptions>(validateWorkspacePath)
|
|
342
|
+
|
|
343
|
+
// ============================================================================
|
|
344
|
+
// Legacy Compatibility - Returns string[] for existing command signatures
|
|
345
|
+
// ============================================================================
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Wrap validator to return only errors array (legacy compatibility)
|
|
349
|
+
*/
|
|
350
|
+
export function errorsOnly<T>(
|
|
351
|
+
validator: (options: T) => ValidationResult
|
|
352
|
+
): (options: T) => string[] {
|
|
353
|
+
return (options: T) => validator(options).errors
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Format choices for help text
|
|
358
|
+
*/
|
|
359
|
+
export function formatChoicesHelp(): string {
|
|
360
|
+
return FORMAT_CHOICES.join(', ')
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Target choices for help text
|
|
365
|
+
*/
|
|
366
|
+
export function targetChoicesHelp(): string {
|
|
367
|
+
return TARGET_CHOICES.join(', ')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Severity choices for help text
|
|
372
|
+
*/
|
|
373
|
+
export function severityChoicesHelp(): string {
|
|
374
|
+
return SEVERITY_CHOICES.join(', ')
|
|
390
375
|
}
|