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