@tamyla/clodo-framework 2.0.19 → 3.0.2
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/CHANGELOG.md +206 -62
- package/bin/clodo-service.js +32 -56
- package/bin/database/README.md +33 -0
- package/bin/database/deployment-db-manager.js +527 -0
- package/bin/database/enterprise-db-manager.js +736 -0
- package/bin/database/wrangler-d1-manager.js +775 -0
- package/bin/shared/cloudflare/domain-discovery.js +636 -0
- package/bin/shared/cloudflare/domain-manager.js +952 -0
- package/bin/shared/cloudflare/index.js +8 -0
- package/bin/shared/cloudflare/ops.js +359 -0
- package/bin/shared/config/index.js +1 -1
- package/bin/shared/database/connection-manager.js +374 -0
- package/bin/shared/database/index.js +7 -0
- package/bin/shared/database/orchestrator.js +726 -0
- package/bin/shared/deployment/auditor.js +969 -0
- package/bin/shared/deployment/index.js +10 -0
- package/bin/shared/deployment/rollback-manager.js +570 -0
- package/bin/shared/deployment/validator.js +779 -0
- package/bin/shared/index.js +32 -0
- package/bin/shared/monitoring/health-checker.js +484 -0
- package/bin/shared/monitoring/index.js +8 -0
- package/bin/shared/monitoring/memory-manager.js +387 -0
- package/bin/shared/monitoring/production-monitor.js +391 -0
- package/bin/shared/production-tester/api-tester.js +82 -0
- package/bin/shared/production-tester/auth-tester.js +132 -0
- package/bin/shared/production-tester/core.js +197 -0
- package/bin/shared/production-tester/database-tester.js +109 -0
- package/bin/shared/production-tester/index.js +77 -0
- package/bin/shared/production-tester/load-tester.js +131 -0
- package/bin/shared/production-tester/performance-tester.js +103 -0
- package/bin/shared/security/api-token-manager.js +312 -0
- package/bin/shared/security/index.js +8 -0
- package/bin/shared/security/secret-generator.js +937 -0
- package/bin/shared/security/secure-token-manager.js +398 -0
- package/bin/shared/utils/error-recovery.js +225 -0
- package/bin/shared/utils/graceful-shutdown-manager.js +390 -0
- package/bin/shared/utils/index.js +9 -0
- package/bin/shared/utils/interactive-prompts.js +146 -0
- package/bin/shared/utils/interactive-utils.js +530 -0
- package/bin/shared/utils/rate-limiter.js +246 -0
- package/dist/database/database-orchestrator.js +34 -12
- package/dist/deployment/index.js +2 -2
- package/dist/orchestration/multi-domain-orchestrator.js +8 -6
- package/dist/service-management/GenerationEngine.js +76 -28
- package/dist/service-management/ServiceInitializer.js +5 -3
- package/dist/shared/cloudflare/domain-manager.js +1 -1
- package/dist/shared/cloudflare/ops.js +27 -12
- package/dist/shared/config/index.js +1 -1
- package/dist/shared/deployment/index.js +2 -2
- package/dist/shared/security/secret-generator.js +4 -2
- package/dist/shared/utils/error-recovery.js +1 -1
- package/dist/shared/utils/graceful-shutdown-manager.js +4 -3
- package/dist/utils/deployment/secret-generator.js +19 -6
- package/package.json +7 -6
- package/bin/shared/config/customer-cli.js +0 -182
- package/dist/shared/config/customer-cli.js +0 -175
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Interactive Utils Module
|
|
3
|
+
* Unified interactive utilities with advanced deployment capabilities
|
|
4
|
+
*
|
|
5
|
+
* Consolidates duplicate interactive-prompts.js modules and provides:
|
|
6
|
+
* - Enhanced user input validation
|
|
7
|
+
* - Progress tracking integration
|
|
8
|
+
* - Multi-step workflow support
|
|
9
|
+
* - Deployment-specific prompts
|
|
10
|
+
* - Rich formatting and colors
|
|
11
|
+
* - Error recovery and retry logic
|
|
12
|
+
*
|
|
13
|
+
* @version 2.0.0 - Enhanced Interactive Base
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import readline from 'readline';
|
|
17
|
+
import { existsSync, readFileSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
|
|
20
|
+
// Singleton readline interface
|
|
21
|
+
let rl = null;
|
|
22
|
+
let isInitialized = false;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Enhanced Interactive Utils Class
|
|
26
|
+
*/
|
|
27
|
+
export class InteractiveUtils {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.options = {
|
|
30
|
+
enableColors: options.enableColors !== false,
|
|
31
|
+
enableProgress: options.enableProgress !== false,
|
|
32
|
+
validateInputs: options.validateInputs !== false,
|
|
33
|
+
retryOnError: options.retryOnError !== false,
|
|
34
|
+
maxRetries: options.maxRetries || 3,
|
|
35
|
+
...options
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.progressState = {
|
|
39
|
+
currentStep: 0,
|
|
40
|
+
totalSteps: 0,
|
|
41
|
+
stepName: '',
|
|
42
|
+
startTime: null
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.validationRules = new Map();
|
|
46
|
+
this.inputHistory = [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize readline interface
|
|
51
|
+
*/
|
|
52
|
+
static getReadlineInterface() {
|
|
53
|
+
if (!rl) {
|
|
54
|
+
rl = readline.createInterface({
|
|
55
|
+
input: process.stdin,
|
|
56
|
+
output: process.stdout,
|
|
57
|
+
terminal: true
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Enhanced signal handling
|
|
61
|
+
rl.on('SIGINT', () => {
|
|
62
|
+
console.log('\\n\\n🚫 Operation cancelled by user');
|
|
63
|
+
process.exit(0);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
isInitialized = true;
|
|
67
|
+
}
|
|
68
|
+
return rl;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Enhanced user input with validation and retry logic
|
|
73
|
+
*/
|
|
74
|
+
static async askUser(question, defaultValue = null, options = {}) {
|
|
75
|
+
const utils = new InteractiveUtils(options);
|
|
76
|
+
return await utils.askUserWithValidation(question, defaultValue, options.validator);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Enhanced yes/no prompts with clear formatting
|
|
81
|
+
*/
|
|
82
|
+
static async askYesNo(question, defaultValue = 'n', options = {}) {
|
|
83
|
+
const utils = new InteractiveUtils(options);
|
|
84
|
+
|
|
85
|
+
let attempts = 0;
|
|
86
|
+
const maxAttempts = options.maxRetries || 3;
|
|
87
|
+
|
|
88
|
+
while (attempts < maxAttempts) {
|
|
89
|
+
try {
|
|
90
|
+
const defaultDisplay = defaultValue === 'y' ? 'Y/n' : 'y/N';
|
|
91
|
+
const coloredQuestion = utils.formatQuestion(question);
|
|
92
|
+
const prompt = `${coloredQuestion} [${defaultDisplay}]: `;
|
|
93
|
+
|
|
94
|
+
const answer = await utils.promptUser(prompt);
|
|
95
|
+
const response = answer.trim().toLowerCase() || defaultValue;
|
|
96
|
+
|
|
97
|
+
if (['y', 'yes', 'n', 'no'].includes(response)) {
|
|
98
|
+
const result = response === 'y' || response === 'yes';
|
|
99
|
+
utils.recordInput(question, result);
|
|
100
|
+
return result;
|
|
101
|
+
} else {
|
|
102
|
+
attempts++;
|
|
103
|
+
console.log(` ⚠️ Please answer 'y' or 'n'. (Attempt ${attempts}/${maxAttempts})`);
|
|
104
|
+
if (attempts >= maxAttempts) {
|
|
105
|
+
throw new Error('Maximum retry attempts exceeded');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (attempts >= maxAttempts - 1) throw error;
|
|
110
|
+
attempts++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Enhanced choice selection with better formatting
|
|
117
|
+
*/
|
|
118
|
+
static async askChoice(question, choices, defaultIndex = 0, options = {}) {
|
|
119
|
+
const utils = new InteractiveUtils(options);
|
|
120
|
+
|
|
121
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
122
|
+
throw new Error('Choices must be a non-empty array');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let attempts = 0;
|
|
126
|
+
const maxAttempts = options.maxRetries || 3;
|
|
127
|
+
|
|
128
|
+
while (attempts < maxAttempts) {
|
|
129
|
+
try {
|
|
130
|
+
const coloredQuestion = utils.formatQuestion(question);
|
|
131
|
+
console.log(`\\n${coloredQuestion}`);
|
|
132
|
+
|
|
133
|
+
// Enhanced choice formatting
|
|
134
|
+
choices.forEach((choice, index) => {
|
|
135
|
+
const marker = index === defaultIndex ? '▶' : ' ';
|
|
136
|
+
const color = index === defaultIndex ? utils.colors.cyan : utils.colors.white;
|
|
137
|
+
console.log(`${marker} ${color}${index + 1}. ${choice}${utils.colors.reset}`);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const prompt = `\\nSelect option [1-${choices.length}] (default: ${defaultIndex + 1}): `;
|
|
141
|
+
const answer = await utils.promptUser(prompt);
|
|
142
|
+
|
|
143
|
+
let choice;
|
|
144
|
+
if (answer.trim() === '') {
|
|
145
|
+
choice = defaultIndex;
|
|
146
|
+
} else {
|
|
147
|
+
choice = parseInt(answer) - 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (choice >= 0 && choice < choices.length) {
|
|
151
|
+
utils.recordInput(question, { choice, value: choices[choice] });
|
|
152
|
+
return choice;
|
|
153
|
+
} else {
|
|
154
|
+
attempts++;
|
|
155
|
+
console.log(` ⚠️ Please select a number between 1 and ${choices.length}. (Attempt ${attempts}/${maxAttempts})`);
|
|
156
|
+
if (attempts >= maxAttempts) {
|
|
157
|
+
throw new Error('Maximum retry attempts exceeded');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (attempts >= maxAttempts - 1) throw error;
|
|
162
|
+
attempts++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Enhanced password input (hidden)
|
|
169
|
+
*/
|
|
170
|
+
static async askPassword(question, options = {}) {
|
|
171
|
+
const utils = new InteractiveUtils(options);
|
|
172
|
+
const coloredQuestion = utils.formatQuestion(question);
|
|
173
|
+
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
const rl = InteractiveUtils.getReadlineInterface();
|
|
176
|
+
|
|
177
|
+
// Temporarily hide input
|
|
178
|
+
const stdin = process.stdin;
|
|
179
|
+
stdin.setRawMode(true);
|
|
180
|
+
stdin.resume();
|
|
181
|
+
|
|
182
|
+
let password = '';
|
|
183
|
+
console.log(`${coloredQuestion}: `);
|
|
184
|
+
|
|
185
|
+
stdin.on('data', function handler(char) {
|
|
186
|
+
char = char.toString();
|
|
187
|
+
|
|
188
|
+
switch (char) {
|
|
189
|
+
case '\\n':
|
|
190
|
+
case '\\r':
|
|
191
|
+
case '\\u0004': // Ctrl+D
|
|
192
|
+
stdin.setRawMode(false);
|
|
193
|
+
stdin.removeListener('data', handler);
|
|
194
|
+
console.log(''); // New line
|
|
195
|
+
utils.recordInput(question, '[HIDDEN]');
|
|
196
|
+
resolve(password);
|
|
197
|
+
break;
|
|
198
|
+
case '\\u0003': // Ctrl+C
|
|
199
|
+
console.log('\\n\\n🚫 Operation cancelled');
|
|
200
|
+
process.exit(0);
|
|
201
|
+
break;
|
|
202
|
+
case '\\u007f': // Backspace
|
|
203
|
+
if (password.length > 0) {
|
|
204
|
+
password = password.slice(0, -1);
|
|
205
|
+
process.stdout.write('\\b \\b');
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
default:
|
|
209
|
+
password += char;
|
|
210
|
+
process.stdout.write('*');
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Multi-step progress tracking
|
|
218
|
+
*/
|
|
219
|
+
static startProgress(totalSteps, initialStepName = '') {
|
|
220
|
+
const utils = new InteractiveUtils();
|
|
221
|
+
utils.progressState = {
|
|
222
|
+
currentStep: 0,
|
|
223
|
+
totalSteps,
|
|
224
|
+
stepName: initialStepName,
|
|
225
|
+
startTime: new Date()
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
if (initialStepName) {
|
|
229
|
+
utils.displayProgress();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return utils;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Update progress step
|
|
237
|
+
*/
|
|
238
|
+
nextStep(stepName) {
|
|
239
|
+
this.progressState.currentStep++;
|
|
240
|
+
this.progressState.stepName = stepName;
|
|
241
|
+
this.displayProgress();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Display progress with formatting
|
|
246
|
+
*/
|
|
247
|
+
displayProgress() {
|
|
248
|
+
if (!this.options.enableProgress) return;
|
|
249
|
+
|
|
250
|
+
const { currentStep, totalSteps, stepName, startTime } = this.progressState;
|
|
251
|
+
const percentage = Math.round((currentStep / totalSteps) * 100);
|
|
252
|
+
const elapsed = startTime ? Math.round((Date.now() - startTime.getTime()) / 1000) : 0;
|
|
253
|
+
|
|
254
|
+
const progressBar = this.generateProgressBar(percentage);
|
|
255
|
+
|
|
256
|
+
console.log(`\\n${this.colors.blue}[${currentStep}/${totalSteps}] ${progressBar} ${percentage}%${this.colors.reset}`);
|
|
257
|
+
console.log(`${this.colors.cyan}🔄 ${stepName}${this.colors.reset} ${elapsed > 0 ? `(${elapsed}s)` : ''}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Generate visual progress bar
|
|
262
|
+
*/
|
|
263
|
+
generateProgressBar(percentage, length = 20) {
|
|
264
|
+
const filled = Math.round((percentage / 100) * length);
|
|
265
|
+
const empty = length - filled;
|
|
266
|
+
return `${'█'.repeat(filled)}${'░'.repeat(empty)}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Internal method: Ask user with validation
|
|
271
|
+
*/
|
|
272
|
+
async askUserWithValidation(question, defaultValue = null, validator = null) {
|
|
273
|
+
let attempts = 0;
|
|
274
|
+
const maxAttempts = this.options.maxRetries;
|
|
275
|
+
|
|
276
|
+
while (attempts < maxAttempts) {
|
|
277
|
+
try {
|
|
278
|
+
const coloredQuestion = this.formatQuestion(question);
|
|
279
|
+
const prompt = defaultValue ? `${coloredQuestion} [${defaultValue}]: ` : `${coloredQuestion}: `;
|
|
280
|
+
|
|
281
|
+
const answer = await this.promptUser(prompt);
|
|
282
|
+
const finalAnswer = answer.trim() || defaultValue;
|
|
283
|
+
|
|
284
|
+
// Validation
|
|
285
|
+
if (validator) {
|
|
286
|
+
const validationResult = await this.validateInput(finalAnswer, validator);
|
|
287
|
+
if (!validationResult.valid) {
|
|
288
|
+
attempts++;
|
|
289
|
+
console.log(` ❌ ${validationResult.error} (Attempt ${attempts}/${maxAttempts})`);
|
|
290
|
+
if (attempts >= maxAttempts) {
|
|
291
|
+
throw new Error('Maximum validation attempts exceeded');
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.recordInput(question, finalAnswer);
|
|
298
|
+
return finalAnswer;
|
|
299
|
+
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (attempts >= maxAttempts - 1) throw error;
|
|
302
|
+
attempts++;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Internal method: Prompt user for input
|
|
309
|
+
*/
|
|
310
|
+
promptUser(prompt) {
|
|
311
|
+
return new Promise((resolve) => {
|
|
312
|
+
InteractiveUtils.getReadlineInterface().question(prompt, (answer) => {
|
|
313
|
+
resolve(answer);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Validate user input
|
|
320
|
+
*/
|
|
321
|
+
async validateInput(input, validator) {
|
|
322
|
+
if (typeof validator === 'function') {
|
|
323
|
+
try {
|
|
324
|
+
const result = await validator(input);
|
|
325
|
+
return { valid: result === true, error: typeof result === 'string' ? result : 'Invalid input' };
|
|
326
|
+
} catch (error) {
|
|
327
|
+
return { valid: false, error: error.message };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (typeof validator === 'object') {
|
|
332
|
+
// Built-in validation rules
|
|
333
|
+
if (validator.required && (!input || input.trim() === '')) {
|
|
334
|
+
return { valid: false, error: 'This field is required' };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (validator.minLength && input.length < validator.minLength) {
|
|
338
|
+
return { valid: false, error: `Minimum length is ${validator.minLength} characters` };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (validator.maxLength && input.length > validator.maxLength) {
|
|
342
|
+
return { valid: false, error: `Maximum length is ${validator.maxLength} characters` };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (validator.pattern && !validator.pattern.test(input)) {
|
|
346
|
+
return { valid: false, error: validator.message || 'Invalid format' };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (validator.custom) {
|
|
350
|
+
return await this.validateInput(input, validator.custom);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return { valid: true };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Format question with colors
|
|
359
|
+
*/
|
|
360
|
+
formatQuestion(question) {
|
|
361
|
+
if (!this.options.enableColors) return question;
|
|
362
|
+
return `${this.colors.yellow}❓ ${question}${this.colors.reset}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Record input for history/debugging
|
|
367
|
+
*/
|
|
368
|
+
recordInput(question, answer) {
|
|
369
|
+
this.inputHistory.push({
|
|
370
|
+
timestamp: new Date(),
|
|
371
|
+
question,
|
|
372
|
+
answer: typeof answer === 'object' ? JSON.stringify(answer) : answer
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Color codes for enhanced display
|
|
378
|
+
*/
|
|
379
|
+
get colors() {
|
|
380
|
+
if (!this.options.enableColors) {
|
|
381
|
+
return {
|
|
382
|
+
reset: '', red: '', green: '', yellow: '', blue: '', cyan: '', white: ''
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
reset: '\\x1b[0m',
|
|
388
|
+
red: '\\x1b[31m',
|
|
389
|
+
green: '\\x1b[32m',
|
|
390
|
+
yellow: '\\x1b[33m',
|
|
391
|
+
blue: '\\x1b[34m',
|
|
392
|
+
cyan: '\\x1b[36m',
|
|
393
|
+
white: '\\x1b[37m'
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Export input history
|
|
399
|
+
*/
|
|
400
|
+
getInputHistory() {
|
|
401
|
+
return this.inputHistory;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Clean up readline interface
|
|
406
|
+
*/
|
|
407
|
+
static closePrompts() {
|
|
408
|
+
if (rl && isInitialized) {
|
|
409
|
+
rl.close();
|
|
410
|
+
rl = null;
|
|
411
|
+
isInitialized = false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Export static convenience functions for backward compatibility
|
|
417
|
+
export const askUser = InteractiveUtils.askUser;
|
|
418
|
+
export const askYesNo = InteractiveUtils.askYesNo;
|
|
419
|
+
export const askChoice = InteractiveUtils.askChoice;
|
|
420
|
+
export const askPassword = InteractiveUtils.askPassword;
|
|
421
|
+
export const closePrompts = InteractiveUtils.closePrompts;
|
|
422
|
+
export const startProgress = InteractiveUtils.startProgress;
|
|
423
|
+
|
|
424
|
+
// Export class for advanced usage
|
|
425
|
+
export { InteractiveUtils as default };
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Deployment-specific interactive utilities
|
|
429
|
+
*/
|
|
430
|
+
export class DeploymentInteractiveUtils extends InteractiveUtils {
|
|
431
|
+
constructor(options = {}) {
|
|
432
|
+
super(options);
|
|
433
|
+
|
|
434
|
+
// Deployment-specific validation rules
|
|
435
|
+
this.validationRules.set('domain', {
|
|
436
|
+
pattern: /^[a-z0-9-]+$/,
|
|
437
|
+
message: 'Domain must contain only lowercase letters, numbers, and hyphens'
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
this.validationRules.set('email', {
|
|
441
|
+
pattern: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/,
|
|
442
|
+
message: 'Please enter a valid email address'
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
this.validationRules.set('url', {
|
|
446
|
+
pattern: /^https?:\/\/[^\s]+$/,
|
|
447
|
+
message: 'Please enter a valid URL starting with http:// or https://'
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Ask for domain name with validation
|
|
453
|
+
*/
|
|
454
|
+
async askDomain(question = 'Enter domain name', defaultValue = null) {
|
|
455
|
+
return await this.askUserWithValidation(
|
|
456
|
+
question,
|
|
457
|
+
defaultValue,
|
|
458
|
+
this.validationRules.get('domain')
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Ask for email with validation
|
|
464
|
+
*/
|
|
465
|
+
async askEmail(question = 'Enter email address', defaultValue = null) {
|
|
466
|
+
return await this.askUserWithValidation(
|
|
467
|
+
question,
|
|
468
|
+
defaultValue,
|
|
469
|
+
this.validationRules.get('email')
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Ask for URL with validation
|
|
475
|
+
*/
|
|
476
|
+
async askUrl(question = 'Enter URL', defaultValue = null) {
|
|
477
|
+
return await this.askUserWithValidation(
|
|
478
|
+
question,
|
|
479
|
+
defaultValue,
|
|
480
|
+
this.validationRules.get('url')
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Deployment mode selection
|
|
486
|
+
*/
|
|
487
|
+
async askDeploymentMode(defaultMode = 0) {
|
|
488
|
+
const modes = [
|
|
489
|
+
'Single Domain (Recommended for first-time users)',
|
|
490
|
+
'Multi-Domain (Deploy multiple domains together)',
|
|
491
|
+
'Portfolio Mode (Advanced: Full portfolio management)'
|
|
492
|
+
];
|
|
493
|
+
|
|
494
|
+
return await InteractiveUtils.askChoice(
|
|
495
|
+
'Select deployment mode:',
|
|
496
|
+
modes,
|
|
497
|
+
defaultMode,
|
|
498
|
+
{ enableColors: true }
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Environment selection with warnings
|
|
504
|
+
*/
|
|
505
|
+
async askEnvironment(defaultEnv = 0) {
|
|
506
|
+
const environments = ['production', 'staging', 'development', 'preview'];
|
|
507
|
+
|
|
508
|
+
const choice = await InteractiveUtils.askChoice(
|
|
509
|
+
'Select deployment environment:',
|
|
510
|
+
environments,
|
|
511
|
+
defaultEnv,
|
|
512
|
+
{ enableColors: true }
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
// Show environment-specific warnings
|
|
516
|
+
if (environments[choice] === 'production') {
|
|
517
|
+
console.log(`\\n${this.colors.yellow}⚠️ Production Environment Selected:${this.colors.reset}`);
|
|
518
|
+
console.log(' - Enhanced security validation will be performed');
|
|
519
|
+
console.log(' - Backup and recovery mechanisms will be enabled');
|
|
520
|
+
console.log(' - Performance monitoring will be activated');
|
|
521
|
+
|
|
522
|
+
const confirm = await InteractiveUtils.askYesNo('Continue with production deployment?', 'y');
|
|
523
|
+
if (!confirm) {
|
|
524
|
+
throw new Error('Production deployment cancelled by user');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return choice;
|
|
529
|
+
}
|
|
530
|
+
}
|