@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.
@@ -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;