@paulduvall/claude-dev-toolkit 0.0.1-alpha.1
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 +254 -0
- package/bin/claude-commands +132 -0
- package/lib/claude-code-compatibility.js +545 -0
- package/lib/command-selector.js +245 -0
- package/lib/config.js +182 -0
- package/lib/context-utils.js +80 -0
- package/lib/dependency-validator.js +354 -0
- package/lib/error-factory.js +394 -0
- package/lib/error-handler-utils.js +432 -0
- package/lib/error-recovery-system.js +563 -0
- package/lib/failure-recovery-installer.js +370 -0
- package/lib/hook-installer-core.js +330 -0
- package/lib/hook-installer.js +187 -0
- package/lib/hook-metadata-service.js +352 -0
- package/lib/hook-validator.js +358 -0
- package/lib/installation-configuration.js +380 -0
- package/lib/installation-instruction-generator.js +564 -0
- package/lib/installer.js +68 -0
- package/lib/package-manager-service.js +270 -0
- package/lib/permission-error-handler.js +543 -0
- package/lib/platform-utils.js +491 -0
- package/lib/setup-wizard-ui.js +245 -0
- package/lib/setup-wizard.js +355 -0
- package/lib/system-requirements-checker.js +558 -0
- package/lib/utils.js +15 -0
- package/lib/validation-utils.js +320 -0
- package/lib/version-validator-service.js +326 -0
- package/package.json +73 -0
- package/scripts/postinstall.js +182 -0
- package/scripts/validate.js +94 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared validation patterns and error handling utilities to eliminate duplication
|
|
5
|
+
* across DependencyValidator and PermissionErrorHandler classes.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Common validation patterns
|
|
9
|
+
* - Error result creation
|
|
10
|
+
* - Context validation
|
|
11
|
+
* - Version comparison utilities
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class ValidationUtils {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.config = {
|
|
17
|
+
errorCodes: {
|
|
18
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
19
|
+
NOT_FOUND: 'NOT_FOUND',
|
|
20
|
+
VERSION_MISMATCH: 'VERSION_MISMATCH',
|
|
21
|
+
INVALID_INPUT: 'INVALID_INPUT'
|
|
22
|
+
},
|
|
23
|
+
versionPatterns: [
|
|
24
|
+
/version\s+(\d+\.\d+\.\d+)/i,
|
|
25
|
+
/v?(\d+\.\d+\.\d+)/,
|
|
26
|
+
/(\d+\.\d+\.\d+)/
|
|
27
|
+
]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a standardized error result object
|
|
33
|
+
* @param {string} code - Error code
|
|
34
|
+
* @param {string} message - Error message
|
|
35
|
+
* @param {Object} additionalInfo - Additional error information
|
|
36
|
+
* @returns {Object} Standardized error result
|
|
37
|
+
*/
|
|
38
|
+
createErrorResult(code, message, additionalInfo = {}) {
|
|
39
|
+
return {
|
|
40
|
+
error: {
|
|
41
|
+
code,
|
|
42
|
+
message,
|
|
43
|
+
...additionalInfo
|
|
44
|
+
},
|
|
45
|
+
success: false,
|
|
46
|
+
timestamp: new Date().toISOString()
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a standardized success result object
|
|
52
|
+
* @param {Object} data - Success data
|
|
53
|
+
* @param {string} message - Success message (optional)
|
|
54
|
+
* @returns {Object} Standardized success result
|
|
55
|
+
*/
|
|
56
|
+
createSuccessResult(data = {}, message = null) {
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
data,
|
|
60
|
+
message,
|
|
61
|
+
timestamp: new Date().toISOString()
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate required properties on an object
|
|
67
|
+
* @param {Object} obj - Object to validate
|
|
68
|
+
* @param {Array<string>} requiredProps - Required property names
|
|
69
|
+
* @param {string} objectName - Name of object for error messages
|
|
70
|
+
* @returns {Object} Validation result
|
|
71
|
+
*/
|
|
72
|
+
validateRequiredProperties(obj, requiredProps, objectName = 'object') {
|
|
73
|
+
if (!obj || typeof obj !== 'object') {
|
|
74
|
+
return this.createErrorResult(
|
|
75
|
+
this.config.errorCodes.INVALID_INPUT,
|
|
76
|
+
`${objectName} must be a valid object`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const missingProps = requiredProps.filter(prop =>
|
|
81
|
+
!obj.hasOwnProperty(prop) || obj[prop] === undefined || obj[prop] === null
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (missingProps.length > 0) {
|
|
85
|
+
return this.createErrorResult(
|
|
86
|
+
this.config.errorCodes.INVALID_INPUT,
|
|
87
|
+
`${objectName} missing required properties: ${missingProps.join(', ')}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return this.createSuccessResult();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validate that a value is not null or undefined
|
|
96
|
+
* @param {*} value - Value to validate
|
|
97
|
+
* @param {string} name - Name for error messages
|
|
98
|
+
* @returns {Object} Validation result
|
|
99
|
+
*/
|
|
100
|
+
validateNotEmpty(value, name = 'value') {
|
|
101
|
+
if (value === null || value === undefined) {
|
|
102
|
+
return this.createErrorResult(
|
|
103
|
+
this.config.errorCodes.INVALID_INPUT,
|
|
104
|
+
`${name} cannot be null or undefined`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof value === 'string' && value.trim() === '') {
|
|
109
|
+
return this.createErrorResult(
|
|
110
|
+
this.config.errorCodes.INVALID_INPUT,
|
|
111
|
+
`${name} cannot be empty`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return this.createSuccessResult();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Normalize version string for comparison
|
|
120
|
+
* @param {string} version - Version string to normalize
|
|
121
|
+
* @returns {Array<number>} Normalized version parts
|
|
122
|
+
*/
|
|
123
|
+
normalizeVersion(version) {
|
|
124
|
+
if (!version || typeof version !== 'string') {
|
|
125
|
+
return [0, 0, 0];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return version.replace(/^v/, '').split('.').map(part => parseInt(part) || 0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Extract version from command output
|
|
133
|
+
* @param {string} output - Command output containing version
|
|
134
|
+
* @returns {string|null} Extracted version or null
|
|
135
|
+
*/
|
|
136
|
+
extractVersionFromOutput(output) {
|
|
137
|
+
if (!output || typeof output !== 'string') return null;
|
|
138
|
+
|
|
139
|
+
for (const pattern of this.config.versionPatterns) {
|
|
140
|
+
const match = output.match(pattern);
|
|
141
|
+
if (match && match[1]) {
|
|
142
|
+
return match[1];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Parse version requirement string
|
|
151
|
+
* @param {string} requirement - Version requirement (e.g., ">=18.0.0", "^2.1.0")
|
|
152
|
+
* @returns {Object} Parsed requirement with operator and version
|
|
153
|
+
*/
|
|
154
|
+
parseVersionRequirement(requirement) {
|
|
155
|
+
if (!requirement || typeof requirement !== 'string') {
|
|
156
|
+
return { operator: '=', version: [0, 0, 0] };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const operators = ['>=', '<=', '>', '<', '~', '^', '='];
|
|
160
|
+
let operator = '=';
|
|
161
|
+
let version = requirement;
|
|
162
|
+
|
|
163
|
+
for (const op of operators) {
|
|
164
|
+
if (requirement.startsWith(op)) {
|
|
165
|
+
operator = op;
|
|
166
|
+
version = requirement.slice(op.length).trim();
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
operator,
|
|
173
|
+
version: this.normalizeVersion(version)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Compare two version arrays
|
|
179
|
+
* @param {Array<number>} current - Current version parts
|
|
180
|
+
* @param {Array<number>} required - Required version parts
|
|
181
|
+
* @param {string} operator - Comparison operator
|
|
182
|
+
* @returns {boolean} Whether version satisfies requirement
|
|
183
|
+
*/
|
|
184
|
+
compareVersions(current, required, operator = '=') {
|
|
185
|
+
// Pad arrays to same length
|
|
186
|
+
while (current.length < required.length) current.push(0);
|
|
187
|
+
while (required.length < current.length) required.push(0);
|
|
188
|
+
|
|
189
|
+
// Handle semver operators
|
|
190
|
+
if (operator === '^') {
|
|
191
|
+
// Caret: compatible within same major version
|
|
192
|
+
if (current[0] !== required[0]) return false;
|
|
193
|
+
return this._isGreaterOrEqual(current, required);
|
|
194
|
+
} else if (operator === '~') {
|
|
195
|
+
// Tilde: compatible within same minor version
|
|
196
|
+
if (current[0] !== required[0] || current[1] !== required[1]) return false;
|
|
197
|
+
return this._isGreaterOrEqual(current, required);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Standard comparison operators
|
|
201
|
+
const comparison = this._compareVersionArrays(current, required);
|
|
202
|
+
|
|
203
|
+
switch (operator) {
|
|
204
|
+
case '>': return comparison > 0;
|
|
205
|
+
case '>=': return comparison >= 0;
|
|
206
|
+
case '<': return comparison < 0;
|
|
207
|
+
case '<=': return comparison <= 0;
|
|
208
|
+
case '!=': return comparison !== 0;
|
|
209
|
+
case '=':
|
|
210
|
+
default: return comparison === 0;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Compare version arrays and return -1, 0, or 1
|
|
216
|
+
* @param {Array<number>} a - First version
|
|
217
|
+
* @param {Array<number>} b - Second version
|
|
218
|
+
* @returns {number} Comparison result
|
|
219
|
+
* @private
|
|
220
|
+
*/
|
|
221
|
+
_compareVersionArrays(a, b) {
|
|
222
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
223
|
+
const aVal = a[i] || 0;
|
|
224
|
+
const bVal = b[i] || 0;
|
|
225
|
+
|
|
226
|
+
if (aVal > bVal) return 1;
|
|
227
|
+
if (aVal < bVal) return -1;
|
|
228
|
+
}
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Check if current version is greater or equal to required
|
|
234
|
+
* @param {Array<number>} current - Current version parts
|
|
235
|
+
* @param {Array<number>} required - Required version parts
|
|
236
|
+
* @returns {boolean} Whether current >= required
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
_isGreaterOrEqual(current, required) {
|
|
240
|
+
return this._compareVersionArrays(current, required) >= 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Create validation context with common properties
|
|
245
|
+
* @param {Object} input - Input to validate
|
|
246
|
+
* @param {Object} options - Validation options
|
|
247
|
+
* @returns {Object} Validation context
|
|
248
|
+
*/
|
|
249
|
+
createValidationContext(input, options = {}) {
|
|
250
|
+
return {
|
|
251
|
+
input,
|
|
252
|
+
options: {
|
|
253
|
+
strict: false,
|
|
254
|
+
allowEmpty: false,
|
|
255
|
+
...options
|
|
256
|
+
},
|
|
257
|
+
timestamp: new Date().toISOString(),
|
|
258
|
+
errors: [],
|
|
259
|
+
warnings: []
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Add error to validation context
|
|
265
|
+
* @param {Object} context - Validation context
|
|
266
|
+
* @param {string} code - Error code
|
|
267
|
+
* @param {string} message - Error message
|
|
268
|
+
* @param {Object} details - Additional error details
|
|
269
|
+
*/
|
|
270
|
+
addContextError(context, code, message, details = {}) {
|
|
271
|
+
context.errors.push({
|
|
272
|
+
code,
|
|
273
|
+
message,
|
|
274
|
+
details,
|
|
275
|
+
timestamp: new Date().toISOString()
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Add warning to validation context
|
|
281
|
+
* @param {Object} context - Validation context
|
|
282
|
+
* @param {string} message - Warning message
|
|
283
|
+
* @param {Object} details - Additional warning details
|
|
284
|
+
*/
|
|
285
|
+
addContextWarning(context, message, details = {}) {
|
|
286
|
+
context.warnings.push({
|
|
287
|
+
message,
|
|
288
|
+
details,
|
|
289
|
+
timestamp: new Date().toISOString()
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Check if validation context has errors
|
|
295
|
+
* @param {Object} context - Validation context
|
|
296
|
+
* @returns {boolean} True if context has errors
|
|
297
|
+
*/
|
|
298
|
+
hasContextErrors(context) {
|
|
299
|
+
return context.errors && context.errors.length > 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get validation result from context
|
|
304
|
+
* @param {Object} context - Validation context
|
|
305
|
+
* @returns {Object} Validation result
|
|
306
|
+
*/
|
|
307
|
+
getContextResult(context) {
|
|
308
|
+
const hasErrors = this.hasContextErrors(context);
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
success: !hasErrors,
|
|
312
|
+
errors: context.errors,
|
|
313
|
+
warnings: context.warnings,
|
|
314
|
+
data: hasErrors ? null : context.input,
|
|
315
|
+
timestamp: context.timestamp
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module.exports = ValidationUtils;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Validator Service
|
|
3
|
+
*
|
|
4
|
+
* Handles version validation, comparison, and requirement checking.
|
|
5
|
+
* Extracted from DependencyValidator as part of Phase 1 bloater refactoring.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Semantic version comparison
|
|
9
|
+
* - Version requirement parsing
|
|
10
|
+
* - Version extraction from command output
|
|
11
|
+
* - Cross-platform version validation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class VersionValidatorService {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.config = {
|
|
17
|
+
versionPatterns: this._createVersionPatterns()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create version detection patterns
|
|
23
|
+
* @returns {Array} Version extraction patterns
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
_createVersionPatterns() {
|
|
27
|
+
return [
|
|
28
|
+
/version\s+(\d+\.\d+\.\d+)/i,
|
|
29
|
+
/v?(\d+\.\d+\.\d+)/,
|
|
30
|
+
/(\d+\.\d+\.\d+)/
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate version requirement against current version
|
|
36
|
+
* @param {string} requirement - Version requirement (e.g., ">=18.0.0")
|
|
37
|
+
* @param {string} currentVersion - Current installed version
|
|
38
|
+
* @returns {Object} Version validation result
|
|
39
|
+
*/
|
|
40
|
+
validateVersionRequirement(requirement, currentVersion) {
|
|
41
|
+
const result = {
|
|
42
|
+
satisfies: false,
|
|
43
|
+
requirement: requirement,
|
|
44
|
+
current: currentVersion,
|
|
45
|
+
message: ''
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Simple version comparison implementation
|
|
50
|
+
const normalizedCurrent = this._normalizeVersion(currentVersion);
|
|
51
|
+
const parsedRequirement = this._parseVersionRequirement(requirement);
|
|
52
|
+
|
|
53
|
+
result.satisfies = this._compareVersions(normalizedCurrent, parsedRequirement);
|
|
54
|
+
result.message = result.satisfies ?
|
|
55
|
+
`Version ${currentVersion} satisfies requirement ${requirement}` :
|
|
56
|
+
`Version ${currentVersion} does not satisfy requirement ${requirement}`;
|
|
57
|
+
|
|
58
|
+
} catch (error) {
|
|
59
|
+
result.message = `Version validation error: ${error.message}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extract version number from command output
|
|
67
|
+
* @param {string} output - Command output
|
|
68
|
+
* @returns {string} Extracted version
|
|
69
|
+
*/
|
|
70
|
+
extractVersionFromOutput(output) {
|
|
71
|
+
for (const pattern of this.config.versionPatterns) {
|
|
72
|
+
const match = output.match(pattern);
|
|
73
|
+
if (match) {
|
|
74
|
+
return match[1];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return 'Unknown';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compare two version strings
|
|
82
|
+
* @param {string} version1 - First version
|
|
83
|
+
* @param {string} version2 - Second version
|
|
84
|
+
* @returns {number} Comparison result (-1, 0, 1)
|
|
85
|
+
*/
|
|
86
|
+
compareVersionStrings(version1, version2) {
|
|
87
|
+
const v1Parts = this._normalizeVersion(version1);
|
|
88
|
+
const v2Parts = this._normalizeVersion(version2);
|
|
89
|
+
|
|
90
|
+
// Pad arrays to same length
|
|
91
|
+
while (v1Parts.length < v2Parts.length) v1Parts.push(0);
|
|
92
|
+
while (v2Parts.length < v1Parts.length) v2Parts.push(0);
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
|
|
95
|
+
const v1Part = v1Parts[i] || 0;
|
|
96
|
+
const v2Part = v2Parts[i] || 0;
|
|
97
|
+
|
|
98
|
+
if (v1Part > v2Part) return 1;
|
|
99
|
+
if (v1Part < v2Part) return -1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if a version satisfies a requirement
|
|
107
|
+
* @param {string} version - Version to check
|
|
108
|
+
* @param {string} requirement - Version requirement
|
|
109
|
+
* @returns {boolean} Whether version satisfies requirement
|
|
110
|
+
*/
|
|
111
|
+
satisfiesRequirement(version, requirement) {
|
|
112
|
+
const normalizedVersion = this._normalizeVersion(version);
|
|
113
|
+
const parsedRequirement = this._parseVersionRequirement(requirement);
|
|
114
|
+
|
|
115
|
+
return this._compareVersions(normalizedVersion, parsedRequirement);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse a version requirement string
|
|
120
|
+
* @param {string} requirement - Version requirement string (e.g., ">=1.0.0")
|
|
121
|
+
* @returns {Object} Parsed requirement with operator and version
|
|
122
|
+
*/
|
|
123
|
+
parseVersionRequirement(requirement) {
|
|
124
|
+
return this._parseVersionRequirement(requirement);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Normalize a version string to version parts array
|
|
129
|
+
* @param {string} version - Version string
|
|
130
|
+
* @returns {Array<number>} Normalized version parts
|
|
131
|
+
*/
|
|
132
|
+
normalizeVersion(version) {
|
|
133
|
+
return this._normalizeVersion(version);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if version is greater or equal to another version
|
|
138
|
+
* @param {string} version - Version to check
|
|
139
|
+
* @param {string} minimumVersion - Minimum required version
|
|
140
|
+
* @returns {boolean} Whether version >= minimumVersion
|
|
141
|
+
*/
|
|
142
|
+
isGreaterOrEqual(version, minimumVersion) {
|
|
143
|
+
const current = this._normalizeVersion(version);
|
|
144
|
+
const required = this._normalizeVersion(minimumVersion);
|
|
145
|
+
|
|
146
|
+
return this._isGreaterOrEqual(current, required);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get the higher of two versions
|
|
151
|
+
* @param {string} version1 - First version
|
|
152
|
+
* @param {string} version2 - Second version
|
|
153
|
+
* @returns {string} Higher version
|
|
154
|
+
*/
|
|
155
|
+
getHigherVersion(version1, version2) {
|
|
156
|
+
const comparison = this.compareVersionStrings(version1, version2);
|
|
157
|
+
return comparison >= 0 ? version1 : version2;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validate multiple version requirements
|
|
162
|
+
* @param {string} version - Version to validate
|
|
163
|
+
* @param {Array<string>} requirements - Array of requirements
|
|
164
|
+
* @returns {Object} Validation result for all requirements
|
|
165
|
+
*/
|
|
166
|
+
validateMultipleRequirements(version, requirements) {
|
|
167
|
+
const results = [];
|
|
168
|
+
let allSatisfied = true;
|
|
169
|
+
|
|
170
|
+
for (const requirement of requirements) {
|
|
171
|
+
const result = this.validateVersionRequirement(requirement, version);
|
|
172
|
+
results.push(result);
|
|
173
|
+
if (!result.satisfies) {
|
|
174
|
+
allSatisfied = false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
version: version,
|
|
180
|
+
allSatisfied: allSatisfied,
|
|
181
|
+
results: results,
|
|
182
|
+
failedRequirements: results.filter(r => !r.satisfies).map(r => r.requirement)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Check if a version string is valid
|
|
188
|
+
* @param {string} version - Version string to validate
|
|
189
|
+
* @returns {boolean} Whether version string is valid
|
|
190
|
+
*/
|
|
191
|
+
isValidVersionString(version) {
|
|
192
|
+
try {
|
|
193
|
+
const normalized = this._normalizeVersion(version);
|
|
194
|
+
return normalized.length >= 1 && normalized.every(part => typeof part === 'number' && part >= 0);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Generate version requirement suggestions
|
|
202
|
+
* @param {string} currentVersion - Current version
|
|
203
|
+
* @param {string} targetVersion - Target version
|
|
204
|
+
* @returns {Object} Version requirement suggestions
|
|
205
|
+
*/
|
|
206
|
+
generateVersionRequirementSuggestions(currentVersion, targetVersion) {
|
|
207
|
+
const comparison = this.compareVersionStrings(currentVersion, targetVersion);
|
|
208
|
+
const suggestions = [];
|
|
209
|
+
|
|
210
|
+
if (comparison < 0) {
|
|
211
|
+
suggestions.push(`Upgrade from ${currentVersion} to ${targetVersion} or higher`);
|
|
212
|
+
suggestions.push(`Use version requirement: ">=${targetVersion}"`);
|
|
213
|
+
} else if (comparison === 0) {
|
|
214
|
+
suggestions.push(`Current version ${currentVersion} matches target ${targetVersion}`);
|
|
215
|
+
suggestions.push(`Use version requirement: "=${targetVersion}"`);
|
|
216
|
+
} else {
|
|
217
|
+
suggestions.push(`Current version ${currentVersion} is higher than target ${targetVersion}`);
|
|
218
|
+
suggestions.push(`Consider using: "^${targetVersion}" for compatibility`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
currentVersion: currentVersion,
|
|
223
|
+
targetVersion: targetVersion,
|
|
224
|
+
comparison: comparison,
|
|
225
|
+
suggestions: suggestions,
|
|
226
|
+
recommended: comparison < 0 ? `>=${targetVersion}` : `^${targetVersion}`
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Normalize version string for comparison (private)
|
|
232
|
+
* @param {string} version - Version string
|
|
233
|
+
* @returns {Array<number>} Normalized version parts
|
|
234
|
+
* @private
|
|
235
|
+
*/
|
|
236
|
+
_normalizeVersion(version) {
|
|
237
|
+
return version.replace(/^v/, '').split('.').map(part => parseInt(part) || 0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Parse version requirement (private)
|
|
242
|
+
* @param {string} requirement - Version requirement string
|
|
243
|
+
* @returns {Object} Parsed requirement
|
|
244
|
+
* @private
|
|
245
|
+
*/
|
|
246
|
+
_parseVersionRequirement(requirement) {
|
|
247
|
+
const operators = ['>=', '<=', '>', '<', '~', '^', '='];
|
|
248
|
+
let operator = '=';
|
|
249
|
+
let version = requirement;
|
|
250
|
+
|
|
251
|
+
for (const op of operators) {
|
|
252
|
+
if (requirement.startsWith(op)) {
|
|
253
|
+
operator = op;
|
|
254
|
+
version = requirement.slice(op.length);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
operator: operator,
|
|
261
|
+
version: this._normalizeVersion(version)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Compare versions according to requirement (private)
|
|
267
|
+
* @param {Array<number>} current - Current version parts
|
|
268
|
+
* @param {Object} requirement - Parsed requirement
|
|
269
|
+
* @returns {boolean} Whether version satisfies requirement
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
272
|
+
_compareVersions(current, requirement) {
|
|
273
|
+
const required = requirement.version;
|
|
274
|
+
const operator = requirement.operator;
|
|
275
|
+
|
|
276
|
+
// Pad arrays to same length
|
|
277
|
+
while (current.length < required.length) current.push(0);
|
|
278
|
+
while (required.length < current.length) required.push(0);
|
|
279
|
+
|
|
280
|
+
// Handle semver operators
|
|
281
|
+
if (operator === '^') {
|
|
282
|
+
// Caret: compatible within same major version
|
|
283
|
+
if (current[0] !== required[0]) return false;
|
|
284
|
+
return this._isGreaterOrEqual(current, required);
|
|
285
|
+
} else if (operator === '~') {
|
|
286
|
+
// Tilde: compatible within same minor version
|
|
287
|
+
if (current[0] !== required[0] || current[1] !== required[1]) return false;
|
|
288
|
+
return this._isGreaterOrEqual(current, required);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Compare version parts for other operators
|
|
292
|
+
for (let i = 0; i < Math.max(current.length, required.length); i++) {
|
|
293
|
+
const curr = current[i] || 0;
|
|
294
|
+
const req = required[i] || 0;
|
|
295
|
+
|
|
296
|
+
if (curr > req) {
|
|
297
|
+
return ['>', '>=', '!='].includes(operator);
|
|
298
|
+
} else if (curr < req) {
|
|
299
|
+
return ['<', '<=', '!='].includes(operator);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Versions are equal
|
|
304
|
+
return ['=', '>=', '<=', '~', '^'].includes(operator);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Check if current version is greater or equal to required (private)
|
|
309
|
+
* @param {Array<number>} current - Current version parts
|
|
310
|
+
* @param {Array<number>} required - Required version parts
|
|
311
|
+
* @returns {boolean} Whether current >= required
|
|
312
|
+
* @private
|
|
313
|
+
*/
|
|
314
|
+
_isGreaterOrEqual(current, required) {
|
|
315
|
+
for (let i = 0; i < Math.max(current.length, required.length); i++) {
|
|
316
|
+
const curr = current[i] || 0;
|
|
317
|
+
const req = required[i] || 0;
|
|
318
|
+
|
|
319
|
+
if (curr > req) return true;
|
|
320
|
+
if (curr < req) return false;
|
|
321
|
+
}
|
|
322
|
+
return true; // Equal
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
module.exports = VersionValidatorService;
|