@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,380 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Manages installation configuration and validation
|
|
6
|
+
* Extracted from InteractiveSetupWizard for better separation of concerns
|
|
7
|
+
*/
|
|
8
|
+
class InstallationConfiguration {
|
|
9
|
+
constructor(packageRoot) {
|
|
10
|
+
this.packageRoot = packageRoot;
|
|
11
|
+
this.configFile = path.join(packageRoot, 'setup-config.json');
|
|
12
|
+
|
|
13
|
+
this.installationTypes = [
|
|
14
|
+
{
|
|
15
|
+
id: 1,
|
|
16
|
+
name: 'Minimal Installation',
|
|
17
|
+
description: 'Essential commands only - lightweight setup',
|
|
18
|
+
commands: ['xhelp', 'xversion', 'xstatus']
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 2,
|
|
22
|
+
name: 'Standard Installation',
|
|
23
|
+
description: 'Recommended commands for most developers',
|
|
24
|
+
commands: ['xgit', 'xtest', 'xquality', 'xdocs', 'xsecurity']
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 3,
|
|
28
|
+
name: 'Full Installation',
|
|
29
|
+
description: 'All available commands - complete toolkit',
|
|
30
|
+
commands: ['all']
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
this.configurationTemplates = [
|
|
35
|
+
{
|
|
36
|
+
id: 1,
|
|
37
|
+
name: 'basic',
|
|
38
|
+
description: 'Minimal settings for basic usage',
|
|
39
|
+
filename: 'basic-settings.json'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 2,
|
|
43
|
+
name: 'comprehensive',
|
|
44
|
+
description: 'Optimized for active development',
|
|
45
|
+
filename: 'comprehensive-settings.json'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 3,
|
|
49
|
+
name: 'security-focused',
|
|
50
|
+
description: 'Enhanced security settings',
|
|
51
|
+
filename: 'security-focused-settings.json'
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get available installation types
|
|
58
|
+
* @returns {Array} Installation types
|
|
59
|
+
*/
|
|
60
|
+
getInstallationTypes() {
|
|
61
|
+
return this.installationTypes;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get installation type by ID
|
|
66
|
+
* @param {number} id - Installation type ID
|
|
67
|
+
* @returns {Object|null} Installation type or null if not found
|
|
68
|
+
*/
|
|
69
|
+
getInstallationTypeById(id) {
|
|
70
|
+
return this.installationTypes.find(type => type.id === id) || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get available configuration templates
|
|
75
|
+
* @returns {Array} Configuration templates
|
|
76
|
+
*/
|
|
77
|
+
getConfigurationTemplates() {
|
|
78
|
+
return this.configurationTemplates;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get configuration template by ID
|
|
83
|
+
* @param {number} id - Template ID
|
|
84
|
+
* @returns {Object|null} Template or null if not found
|
|
85
|
+
*/
|
|
86
|
+
getConfigurationTemplateById(id) {
|
|
87
|
+
return this.configurationTemplates.find(template => template.id === id) || null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validate installation configuration
|
|
92
|
+
* @param {Object} config - Configuration to validate
|
|
93
|
+
* @returns {Object} Validation result with errors if any
|
|
94
|
+
*/
|
|
95
|
+
validateConfiguration(config) {
|
|
96
|
+
const result = {
|
|
97
|
+
valid: true,
|
|
98
|
+
errors: []
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Validate installation type
|
|
102
|
+
if (!config.installationType) {
|
|
103
|
+
result.errors.push('Installation type is required');
|
|
104
|
+
result.valid = false;
|
|
105
|
+
} else {
|
|
106
|
+
// Handle both string names and numeric IDs
|
|
107
|
+
let type;
|
|
108
|
+
if (typeof config.installationType === 'number') {
|
|
109
|
+
type = this.getInstallationTypeById(config.installationType);
|
|
110
|
+
} else {
|
|
111
|
+
// Find by name for backward compatibility
|
|
112
|
+
const typeName = config.installationType.toLowerCase();
|
|
113
|
+
type = this.installationTypes.find(t =>
|
|
114
|
+
t.name.toLowerCase().includes(typeName)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (!type) {
|
|
118
|
+
result.errors.push('Invalid installation type');
|
|
119
|
+
result.valid = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Validate selected commands (handle both array and string formats)
|
|
124
|
+
if (config.selectedCommands) {
|
|
125
|
+
if (!Array.isArray(config.selectedCommands)) {
|
|
126
|
+
result.errors.push('Selected commands must be an array');
|
|
127
|
+
result.valid = false;
|
|
128
|
+
} else if (config.selectedCommands.length === 0) {
|
|
129
|
+
result.errors.push('At least one command must be selected');
|
|
130
|
+
result.valid = false;
|
|
131
|
+
}
|
|
132
|
+
} else if (config.commandSets && Array.isArray(config.commandSets)) {
|
|
133
|
+
// Accept commandSets as an alternative to selectedCommands
|
|
134
|
+
if (config.commandSets.length === 0) {
|
|
135
|
+
result.errors.push('At least one command set must be selected');
|
|
136
|
+
result.valid = false;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
result.errors.push('Selected commands or command sets must be provided');
|
|
140
|
+
result.valid = false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Validate configuration template if provided
|
|
144
|
+
if (config.configTemplate || config.template) {
|
|
145
|
+
const templateValue = config.configTemplate || config.template;
|
|
146
|
+
let template;
|
|
147
|
+
|
|
148
|
+
if (typeof templateValue === 'number') {
|
|
149
|
+
template = this.getConfigurationTemplateById(templateValue);
|
|
150
|
+
} else {
|
|
151
|
+
// Find by name for backward compatibility
|
|
152
|
+
const lowerTemplateValue = templateValue.toLowerCase();
|
|
153
|
+
template = this.configurationTemplates.find(t =>
|
|
154
|
+
t.name.toLowerCase().includes(lowerTemplateValue) ||
|
|
155
|
+
t.filename === templateValue ||
|
|
156
|
+
t.name.toLowerCase() === lowerTemplateValue
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!template) {
|
|
161
|
+
result.errors.push('Invalid configuration template');
|
|
162
|
+
result.valid = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Validate security hooks setting
|
|
167
|
+
if (config.securityHooks !== undefined && typeof config.securityHooks !== 'boolean') {
|
|
168
|
+
result.errors.push('Security hooks setting must be boolean');
|
|
169
|
+
result.valid = false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Create default configuration
|
|
177
|
+
* @returns {Object} Default configuration
|
|
178
|
+
*/
|
|
179
|
+
createDefaultConfiguration() {
|
|
180
|
+
return {
|
|
181
|
+
installationType: 2, // Standard installation
|
|
182
|
+
selectedCommands: this.installationTypes[1].commands,
|
|
183
|
+
securityHooks: true,
|
|
184
|
+
configTemplate: 2, // Development configuration
|
|
185
|
+
timestamp: new Date().toISOString(),
|
|
186
|
+
version: '1.0.0'
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Save configuration to file (supports both sync and async)
|
|
192
|
+
* @param {Object} config - Configuration to save
|
|
193
|
+
* @returns {boolean|Promise<boolean>} Success status
|
|
194
|
+
*/
|
|
195
|
+
saveConfiguration(config) {
|
|
196
|
+
try {
|
|
197
|
+
const validation = this.validateConfiguration(config);
|
|
198
|
+
if (!validation.valid) {
|
|
199
|
+
throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const configData = {
|
|
203
|
+
...config,
|
|
204
|
+
savedAt: new Date().toISOString(),
|
|
205
|
+
packageRoot: this.packageRoot
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Synchronous operation for backward compatibility
|
|
209
|
+
fs.writeFileSync(
|
|
210
|
+
this.configFile,
|
|
211
|
+
JSON.stringify(configData, null, 2),
|
|
212
|
+
'utf8'
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return true;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Failed to save configuration:', error.message);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Save configuration to file (async version)
|
|
224
|
+
* @param {Object} config - Configuration to save
|
|
225
|
+
* @returns {Promise<boolean>} Success status
|
|
226
|
+
*/
|
|
227
|
+
async saveConfigurationAsync(config) {
|
|
228
|
+
try {
|
|
229
|
+
const validation = this.validateConfiguration(config);
|
|
230
|
+
if (!validation.valid) {
|
|
231
|
+
throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const configData = {
|
|
235
|
+
...config,
|
|
236
|
+
savedAt: new Date().toISOString(),
|
|
237
|
+
packageRoot: this.packageRoot
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
await fs.promises.writeFile(
|
|
241
|
+
this.configFile,
|
|
242
|
+
JSON.stringify(configData, null, 2),
|
|
243
|
+
'utf8'
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
return true;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error('Failed to save configuration:', error.message);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Load configuration from file (supports both sync and async)
|
|
255
|
+
* @returns {Object|null} Loaded configuration or null if not found
|
|
256
|
+
*/
|
|
257
|
+
loadConfiguration() {
|
|
258
|
+
try {
|
|
259
|
+
if (!fs.existsSync(this.configFile)) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const data = fs.readFileSync(this.configFile, 'utf8');
|
|
264
|
+
const config = JSON.parse(data);
|
|
265
|
+
|
|
266
|
+
// Validate loaded configuration
|
|
267
|
+
const validation = this.validateConfiguration(config);
|
|
268
|
+
if (!validation.valid) {
|
|
269
|
+
console.warn('Loaded configuration is invalid:', validation.errors);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return config;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error('Failed to load configuration:', error.message);
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Load configuration from file (async version)
|
|
282
|
+
* @returns {Promise<Object|null>} Loaded configuration or null if not found
|
|
283
|
+
*/
|
|
284
|
+
async loadConfigurationAsync() {
|
|
285
|
+
try {
|
|
286
|
+
if (!fs.existsSync(this.configFile)) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const data = await fs.promises.readFile(this.configFile, 'utf8');
|
|
291
|
+
const config = JSON.parse(data);
|
|
292
|
+
|
|
293
|
+
// Validate loaded configuration
|
|
294
|
+
const validation = this.validateConfiguration(config);
|
|
295
|
+
if (!validation.valid) {
|
|
296
|
+
console.warn('Loaded configuration is invalid:', validation.errors);
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return config;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error('Failed to load configuration:', error.message);
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Check if configuration file exists
|
|
309
|
+
* @returns {boolean} True if configuration file exists
|
|
310
|
+
*/
|
|
311
|
+
hasExistingConfiguration() {
|
|
312
|
+
return fs.existsSync(this.configFile);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Delete configuration file
|
|
317
|
+
* @returns {Promise<boolean>} Success status
|
|
318
|
+
*/
|
|
319
|
+
async deleteConfiguration() {
|
|
320
|
+
try {
|
|
321
|
+
if (fs.existsSync(this.configFile)) {
|
|
322
|
+
await fs.promises.unlink(this.configFile);
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error('Failed to delete configuration:', error.message);
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get configuration file path
|
|
333
|
+
* @returns {string} Configuration file path
|
|
334
|
+
*/
|
|
335
|
+
getConfigurationPath() {
|
|
336
|
+
return this.configFile;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Create configuration backup
|
|
341
|
+
* @returns {Promise<string|null>} Backup file path or null if failed
|
|
342
|
+
*/
|
|
343
|
+
async createConfigurationBackup() {
|
|
344
|
+
try {
|
|
345
|
+
if (!fs.existsSync(this.configFile)) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
350
|
+
const backupPath = `${this.configFile}.backup-${timestamp}`;
|
|
351
|
+
|
|
352
|
+
await fs.promises.copyFile(this.configFile, backupPath);
|
|
353
|
+
return backupPath;
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error('Failed to create configuration backup:', error.message);
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Restore configuration from backup
|
|
362
|
+
* @param {string} backupPath - Path to backup file
|
|
363
|
+
* @returns {Promise<boolean>} Success status
|
|
364
|
+
*/
|
|
365
|
+
async restoreConfigurationFromBackup(backupPath) {
|
|
366
|
+
try {
|
|
367
|
+
if (!fs.existsSync(backupPath)) {
|
|
368
|
+
throw new Error('Backup file not found');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
await fs.promises.copyFile(backupPath, this.configFile);
|
|
372
|
+
return true;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error('Failed to restore configuration from backup:', error.message);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
module.exports = InstallationConfiguration;
|