@tamyla/clodo-framework 3.1.5 → 3.1.8

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/clodo-service.js +29 -947
  3. package/bin/database/enterprise-db-manager.js +7 -5
  4. package/bin/security/security-cli.js +0 -0
  5. package/bin/service-management/create-service.js +0 -0
  6. package/bin/service-management/init-service.js +0 -0
  7. package/bin/shared/cloudflare/domain-discovery.js +11 -10
  8. package/bin/shared/cloudflare/ops.js +1 -1
  9. package/bin/shared/config/ConfigurationManager.js +539 -0
  10. package/bin/shared/config/index.js +13 -1
  11. package/bin/shared/database/connection-manager.js +2 -2
  12. package/bin/shared/database/orchestrator.js +5 -4
  13. package/bin/shared/deployment/auditor.js +9 -8
  14. package/bin/shared/logging/Logger.js +214 -0
  15. package/bin/shared/monitoring/production-monitor.js +21 -9
  16. package/bin/shared/utils/ErrorHandler.js +675 -0
  17. package/bin/shared/utils/error-recovery.js +33 -13
  18. package/bin/shared/utils/file-manager.js +162 -0
  19. package/bin/shared/utils/formatters.js +247 -0
  20. package/bin/shared/utils/index.js +14 -4
  21. package/bin/shared/validation/ValidationRegistry.js +143 -0
  22. package/dist/deployment/auditor.js +23 -8
  23. package/dist/deployment/orchestration/BaseDeploymentOrchestrator.js +426 -0
  24. package/dist/deployment/orchestration/EnterpriseOrchestrator.js +401 -0
  25. package/dist/deployment/orchestration/PortfolioOrchestrator.js +273 -0
  26. package/dist/deployment/orchestration/SingleServiceOrchestrator.js +231 -0
  27. package/dist/deployment/orchestration/UnifiedDeploymentOrchestrator.js +662 -0
  28. package/dist/deployment/orchestration/index.js +17 -0
  29. package/dist/index.js +12 -0
  30. package/dist/orchestration/modules/DomainResolver.js +8 -6
  31. package/dist/orchestration/multi-domain-orchestrator.js +13 -1
  32. package/dist/security/index.js +2 -2
  33. package/dist/service-management/ConfirmationEngine.js +8 -7
  34. package/dist/service-management/ErrorTracker.js +7 -2
  35. package/dist/service-management/InputCollector.js +18 -12
  36. package/dist/service-management/ServiceCreator.js +22 -7
  37. package/dist/service-management/ServiceInitializer.js +12 -18
  38. package/dist/shared/cloudflare/domain-discovery.js +11 -10
  39. package/dist/shared/cloudflare/ops.js +1 -1
  40. package/dist/shared/config/ConfigurationManager.js +519 -0
  41. package/dist/shared/config/index.js +5 -1
  42. package/dist/shared/database/connection-manager.js +2 -2
  43. package/dist/shared/database/orchestrator.js +13 -4
  44. package/dist/shared/deployment/auditor.js +23 -8
  45. package/dist/shared/logging/Logger.js +209 -0
  46. package/dist/shared/monitoring/production-monitor.js +24 -8
  47. package/dist/{utils → shared/utils}/ErrorHandler.js +306 -28
  48. package/dist/shared/utils/error-recovery.js +33 -13
  49. package/dist/shared/utils/file-manager.js +155 -0
  50. package/dist/shared/utils/formatters.js +215 -0
  51. package/dist/shared/utils/index.js +14 -4
  52. package/dist/shared/validation/ValidationRegistry.js +126 -0
  53. package/dist/utils/config/unified-config-manager.js +14 -12
  54. package/dist/utils/deployment/config-cache.js +3 -1
  55. package/dist/utils/deployment/secret-generator.js +32 -29
  56. package/dist/utils/framework-config.js +6 -3
  57. package/dist/utils/ui-structures-loader.js +3 -0
  58. package/dist/worker/integration.js +11 -1
  59. package/package.json +31 -3
  60. package/dist/config/FeatureManager.js +0 -426
  61. package/dist/config/features.js +0 -230
  62. package/dist/utils/error-recovery.js +0 -240
@@ -1,26 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Clodo Framework - Unified Three-Tier Service Management CLI
4
+ * Clodo Framework - Unified Service Management CLI
5
5
  *
6
- * This tool provides a conversational interface for complete service lifecycle management
7
- * combining service creation, initialization, and deployment preparation.
6
+ * Main entry point that registers and orchestrates all service management commands.
7
+ * Each command is in its own module in bin/commands/ for clean separation of concerns.
8
8
  *
9
- * Three-Tier Architecture:
10
- * 1. Core Input Collection (6 required inputs)
11
- * 2. Smart Confirmations (15 derived values)
12
- * 3. Automated Generation (67 configurations + service manifest)
9
+ * Commands:
10
+ * - create Create a new Clodo service with conversational setup
11
+ * - deploy Deploy a Clodo service with smart credential handling
12
+ * - validate Validate an existing service configuration
13
+ * - update Update an existing service configuration
14
+ * - diagnose Diagnose and report issues with an existing service
15
+ * - assess Run intelligent capability assessment
16
+ * - list-types List available service types and their features
13
17
  */
14
18
 
15
19
  import { Command } from 'commander';
16
- import { createInterface } from 'readline';
17
20
  import chalk from 'chalk';
18
- import { join } from 'path';
19
- import { ServiceOrchestrator } from '../dist/service-management/ServiceOrchestrator.js';
20
- import { InputCollector } from '../dist/service-management/InputCollector.js';
21
- import { readFileSync, existsSync } from 'fs';
22
- import { resolve } from 'path';
23
21
 
22
+ // Import command registration functions
23
+ import { registerCreateCommand } from './commands/create.js';
24
+ import { registerDeployCommand } from './commands/deploy.js';
25
+ import { registerValidateCommand } from './commands/validate.js';
26
+ import { registerUpdateCommand } from './commands/update.js';
27
+ import { registerDiagnoseCommand } from './commands/diagnose.js';
28
+ import { registerAssessCommand } from './commands/assess.js';
29
+
30
+ // Create program instance
24
31
  const program = new Command();
25
32
 
26
33
  program
@@ -28,204 +35,13 @@ program
28
35
  .description('Unified conversational CLI for Clodo Framework service lifecycle management')
29
36
  .version('1.0.0');
30
37
 
31
- // Helper function to load JSON configuration file
32
- function loadJsonConfig(configPath) {
33
- try {
34
- const fullPath = resolve(configPath);
35
- if (!existsSync(fullPath)) {
36
- throw new Error(`Configuration file not found: ${fullPath}`);
37
- }
38
-
39
- const content = readFileSync(fullPath, 'utf8');
40
- const config = JSON.parse(content);
41
-
42
- // Validate required fields
43
- const required = ['customer', 'environment', 'domainName', 'cloudflareToken'];
44
- const missing = required.filter(field => !config[field]);
45
-
46
- if (missing.length > 0) {
47
- throw new Error(`Missing required configuration fields: ${missing.join(', ')}`);
48
- }
49
-
50
- console.log(chalk.green(`āœ… Loaded configuration from: ${fullPath}`));
51
- return config;
52
- } catch (error) {
53
- if (error instanceof SyntaxError) {
54
- throw new Error(`Invalid JSON in configuration file: ${error.message}`);
55
- }
56
- throw error;
57
- }
58
- }
59
-
60
- // Simple progress indicator for deployment steps
61
- function showProgress(message, duration = 2000) {
62
- return new Promise(resolve => {
63
- process.stdout.write(chalk.cyan(`ā³ ${message}...`));
64
-
65
- const spinner = ['ā ‹', 'ā ™', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ‡', 'ā '];
66
- let i = 0;
67
-
68
- const interval = setInterval(() => {
69
- process.stdout.write(`\r${chalk.cyan(spinner[i])} ${message}...`);
70
- i = (i + 1) % spinner.length;
71
- }, 100);
72
-
73
- setTimeout(() => {
74
- clearInterval(interval);
75
- process.stdout.write(`\r${chalk.green('āœ…')} ${message}... Done!\n`);
76
- resolve();
77
- }, duration);
78
- });
79
- }
80
-
81
- // Early validation function to check prerequisites before deployment
82
- async function validateDeploymentPrerequisites(coreInputs, options) {
83
- const issues = [];
84
-
85
- console.log(chalk.cyan('\nšŸ” Pre-deployment Validation'));
86
- console.log(chalk.gray('─'.repeat(40)));
87
-
88
- // Check required fields
89
- if (!coreInputs.customer) {
90
- issues.push('Customer name is required');
91
- }
92
- if (!coreInputs.environment) {
93
- issues.push('Environment is required');
94
- }
95
- if (!coreInputs.domainName) {
96
- issues.push('Domain name is required');
97
- }
98
- if (!coreInputs.cloudflareToken) {
99
- issues.push('Cloudflare API token is required');
100
- }
101
-
102
- // Check Cloudflare token format (basic validation)
103
- if (coreInputs.cloudflareToken && !coreInputs.cloudflareToken.startsWith('CLOUDFLARE_API_TOKEN=')) {
104
- if (coreInputs.cloudflareToken.length < 40) {
105
- issues.push('Cloudflare API token appears to be invalid (too short)');
106
- }
107
- }
108
-
109
- // Check if service path exists
110
- if (options.servicePath && options.servicePath !== '.') {
111
- const { existsSync } = await import('fs');
112
- if (!existsSync(options.servicePath)) {
113
- issues.push(`Service path does not exist: ${options.servicePath}`);
114
- }
115
- }
116
-
117
- // Check for wrangler.toml if not dry run
118
- if (!options.dryRun) {
119
- const { existsSync } = await import('fs');
120
- const { join } = await import('path');
121
- const wranglerPath = join(options.servicePath || '.', 'wrangler.toml');
122
- if (!existsSync(wranglerPath)) {
123
- issues.push('wrangler.toml not found in service directory');
124
- }
125
- }
126
-
127
- // Report issues
128
- if (issues.length > 0) {
129
- console.log(chalk.red('\nāŒ Validation Failed:'));
130
- issues.forEach(issue => {
131
- console.log(chalk.red(` • ${issue}`));
132
- });
133
- console.log(chalk.gray('\n─'.repeat(40)));
134
- return false;
135
- }
136
-
137
- console.log(chalk.green('āœ… All prerequisites validated'));
138
- console.log(chalk.gray('─'.repeat(40)));
139
- return true;
140
- }
141
-
142
- // Main interactive command
143
- program
144
- .command('create')
145
- .description('Create a new Clodo service with conversational setup')
146
- .option('-n, --non-interactive', 'Run in non-interactive mode with all required parameters')
147
- .option('--service-name <name>', 'Service name (required in non-interactive mode)')
148
- .option('--service-type <type>', 'Service type: data-service, auth-service, content-service, api-gateway, generic', 'generic')
149
- .option('--domain-name <domain>', 'Domain name (required in non-interactive mode)')
150
- .option('--cloudflare-token <token>', 'Cloudflare API token (required in non-interactive mode)')
151
- .option('--cloudflare-account-id <id>', 'Cloudflare account ID (required in non-interactive mode)')
152
- .option('--cloudflare-zone-id <id>', 'Cloudflare zone ID (required in non-interactive mode)')
153
- .option('--environment <env>', 'Target environment: development, staging, production', 'development')
154
- .option('--output-path <path>', 'Output directory for generated service', '.')
155
- .option('--template-path <path>', 'Path to service templates', './templates')
156
- .action(async (options) => {
157
- try {
158
- const orchestrator = new ServiceOrchestrator({
159
- interactive: !options.nonInteractive,
160
- outputPath: options.outputPath,
161
- templatePath: options.templatePath
162
- });
163
-
164
- if (options.nonInteractive) {
165
- // Validate required parameters for non-interactive mode
166
- const required = ['serviceName', 'domainName', 'cloudflareToken', 'cloudflareAccountId', 'cloudflareZoneId'];
167
- const missing = required.filter(key => !options[key]);
168
-
169
- if (missing.length > 0) {
170
- console.error(chalk.red(`Missing required parameters: ${missing.join(', ')}`));
171
- console.error(chalk.yellow('Use --help for parameter details'));
172
- process.exit(1);
173
- }
174
-
175
- // Convert CLI options to core inputs
176
- const coreInputs = {
177
- serviceName: options.serviceName,
178
- serviceType: options.serviceType,
179
- domainName: options.domainName,
180
- cloudflareToken: options.cloudflareToken,
181
- cloudflareAccountId: options.cloudflareAccountId,
182
- cloudflareZoneId: options.cloudflareZoneId,
183
- environment: options.environment
184
- };
185
-
186
- await orchestrator.runNonInteractive(coreInputs);
187
- } else {
188
- await orchestrator.runInteractive();
189
- }
190
-
191
- console.log(chalk.green('\nāœ“ Service creation completed successfully!'));
192
- console.log(chalk.cyan('Next steps:'));
193
- console.log(chalk.white(' 1. cd into your new service directory'));
194
- console.log(chalk.white(' 2. Run npm install'));
195
- console.log(chalk.white(' 3. Configure additional settings in src/config/domains.js'));
196
- console.log(chalk.white(' 4. Run npm run deploy to deploy to Cloudflare'));
197
-
198
- } catch (error) {
199
- console.error(chalk.red(`\nāœ— Service creation failed: ${error.message}`));
200
- if (error.details) {
201
- console.error(chalk.yellow(`Details: ${error.details}`));
202
- }
203
- process.exit(1);
204
- }
205
- });
206
-
207
- // Legacy command aliases for backward compatibility
208
- program
209
- .command('create-service')
210
- .description('Legacy alias for create command')
211
- .action(async (options) => {
212
- console.log(chalk.yellow('This command is deprecated. Use "clodo-service create" instead.'));
213
- const createCommand = program.commands.find(cmd => cmd.name() === 'create');
214
- if (createCommand && createCommand._actionHandler) {
215
- await createCommand._actionHandler(options || {});
216
- }
217
- });
218
-
219
- program
220
- .command('init-service')
221
- .description('Legacy alias for create command')
222
- .action(async (options) => {
223
- console.log(chalk.yellow('This command is deprecated. Use "clodo-service create" instead.'));
224
- const createCommand = program.commands.find(cmd => cmd.name() === 'create');
225
- if (createCommand && createCommand._actionHandler) {
226
- await createCommand._actionHandler(options || {});
227
- }
228
- });
38
+ // Register all command modules
39
+ registerCreateCommand(program);
40
+ registerDeployCommand(program);
41
+ registerValidateCommand(program);
42
+ registerUpdateCommand(program);
43
+ registerDiagnoseCommand(program);
44
+ registerAssessCommand(program);
229
45
 
230
46
  // List available service types
231
47
  program
@@ -252,739 +68,5 @@ program
252
68
  });
253
69
  });
254
70
 
255
- // Show usage statistics and plan information
256
- /*
257
- program
258
- .command('usage')
259
- .description('Show usage statistics and subscription information')
260
- .action(() => {
261
- const stats = usageTracker.getUsageStats();
262
- const license = usageTracker.getLicense();
263
- const plan = usageTracker.isPaidUser() ? 'Paid' : 'Free';
264
-
265
- console.log(chalk.cyan('\nšŸ“Š Clodo Framework Usage Statistics'));
266
- console.log('='.repeat(50));
267
-
268
- if (license && (license.plan === 'founder' || license.plan === 'admin')) {
269
- console.log(chalk.magenta(`šŸ‘‘ Plan: ${license.plan.charAt(0).toUpperCase() + license.plan.slice(1)} (Unlimited)`));
270
- console.log(chalk.magenta(`šŸ‘¤ User: ${license.userName} <${license.userEmail}>`));
271
- } else {
272
- console.log(chalk.white(`Plan: ${plan}`));
273
- }
274
-
275
- console.log(chalk.white(`Services Created: ${stats.currentUsage}${stats.limit !== -1 ? `/${stats.limit}` : ''}`));
276
- console.log(chalk.white(`Environments: ${stats.environments.join(', ')}`));
277
- console.log(chalk.white(`Premium Features: ${stats.premiumFeatures ? 'āœ…' : 'āŒ'}`));
278
-
279
- if (stats.daysUntilExpiry && stats.daysUntilExpiry < 36500) { // Not showing expiry for founder licenses
280
- console.log(chalk.white(`Days Until Expiry: ${stats.daysUntilExpiry}`));
281
- } else if (license && (license.plan === 'founder' || license.plan === 'admin')) {
282
- console.log(chalk.magenta(`ā° Status: Never Expires`));
283
- }
284
-
285
- if (!usageTracker.isPaidUser()) {
286
- console.log(chalk.yellow('\nšŸš€ Upgrade to unlock unlimited usage!'));
287
- console.log(chalk.white('Visit: https://clodo-framework.com/pricing'));
288
- } else if (license && (license.plan === 'founder' || license.plan === 'admin')) {
289
- console.log(chalk.magenta('\nšŸŽ‰ You have unlimited founder access!'));
290
- console.log(chalk.white('Thank you for building Clodo Framework!'));
291
- }
292
-
293
- console.log('');
294
- });
295
- */
296
-
297
- // Upgrade to paid plan (simulated for now)
298
- /*
299
- program
300
- .command('upgrade')
301
- .description('Upgrade to a paid plan')
302
- .option('--plan <plan>', 'Plan type: monthly, annual, lifetime', 'monthly')
303
- .option('--simulate', 'Simulate upgrade without actual payment')
304
- .action((options) => {
305
- if (options.simulate) {
306
- // Simulate license activation
307
- const license = usageTracker.activateLicense(options.plan, 'simulated');
308
- console.log(chalk.green('\nšŸŽ‰ Successfully upgraded to Clodo Framework!'));
309
- console.log('='.repeat(50));
310
- console.log(chalk.white(`Plan: ${options.plan.charAt(0).toUpperCase() + options.plan.slice(1)}`));
311
- console.log(chalk.white(`License ID: ${license.id}`));
312
- console.log(chalk.white(`Expires: ${new Date(license.expiry).toLocaleDateString()}`));
313
- console.log(chalk.green('\nāœ… You now have unlimited access to all features!'));
314
- console.log(chalk.cyan('Run "clodo-service usage" to see your new limits.'));
315
- } else {
316
- console.log(chalk.cyan('\nšŸš€ Ready to upgrade to Clodo Framework?'));
317
- console.log('='.repeat(50));
318
- console.log(chalk.white('Choose your plan:'));
319
- console.log(chalk.white('• Monthly: $19/month'));
320
- console.log(chalk.white('• Annual: $189/year (save 17%)'));
321
- console.log(chalk.white('• Lifetime: $999 (one-time payment)'));
322
- console.log('');
323
- console.log(chalk.yellow('For testing, use: clodo-service upgrade --simulate --plan annual'));
324
- console.log(chalk.cyan('Real payments coming soon at: https://clodo-framework.com/pricing'));
325
- }
326
- console.log('');
327
- });
328
- */
329
-
330
- // Generate founder license (for framework builder and selected team)
331
- /*
332
- program
333
- .command('generate-founder-license')
334
- .description('Generate unlimited founder license (admin only)')
335
- .option('--email <email>', 'User email address', 'founder@clodo-framework.com')
336
- .option('--name <name>', 'User display name', 'Framework Builder')
337
- .option('--admin', 'Generate admin license instead of founder')
338
- .action((options) => {
339
- try {
340
- let license;
341
- if (options.admin) {
342
- license = usageTracker.generateAdminLicense(options.email, options.name);
343
- console.log(chalk.green('\nšŸ”‘ Admin License Generated Successfully!'));
344
- console.log('='.repeat(50));
345
- console.log(chalk.white(`License Type: Admin (Unlimited Access)`));
346
- } else {
347
- license = usageTracker.generateFounderLicense(options.email, options.name);
348
- console.log(chalk.green('\nšŸ‘‘ Founder License Generated Successfully!'));
349
- console.log('='.repeat(50));
350
- console.log(chalk.white(`License Type: Founder (Unlimited Access)`));
351
- }
352
-
353
- console.log(chalk.white(`License ID: ${license.id}`));
354
- console.log(chalk.white(`User: ${license.userName} <${license.userEmail}>`));
355
- console.log(chalk.white(`Generated: ${new Date(license.generated).toLocaleString()}`));
356
- console.log(chalk.green('\nāœ… Unlimited access granted - never expires!'));
357
- console.log(chalk.cyan('Run "clodo-service usage" to verify your access.'));
358
-
359
- } catch (error) {
360
- console.error(chalk.red(`\nāŒ Error generating license: ${error.message}`));
361
- process.exit(1);
362
- }
363
- console.log('');
364
- });
365
- */
366
-
367
- // Validate service configuration
368
- program
369
- .command('validate <service-path>')
370
- .description('Validate an existing service configuration')
371
- .action(async (servicePath) => {
372
- try {
373
- const orchestrator = new ServiceOrchestrator();
374
- const result = await orchestrator.validateService(servicePath);
375
-
376
- if (result.valid) {
377
- console.log(chalk.green('āœ“ Service configuration is valid'));
378
- } else {
379
- console.log(chalk.red('āœ— Service configuration has issues:'));
380
- result.issues.forEach(issue => {
381
- console.log(chalk.yellow(` • ${issue}`));
382
- });
383
- process.exit(1);
384
- }
385
- } catch (error) {
386
- console.error(chalk.red(`Validation failed: ${error.message}`));
387
- process.exit(1);
388
- }
389
- });
390
-
391
- // Update existing service
392
- program
393
- .command('update [service-path]')
394
- .description('Update an existing service configuration')
395
- .option('-i, --interactive', 'Run in interactive mode to select what to update')
396
- .option('--domain-name <domain>', 'Update domain name')
397
- .option('--cloudflare-token <token>', 'Update Cloudflare API token')
398
- .option('--cloudflare-account-id <id>', 'Update Cloudflare account ID')
399
- .option('--cloudflare-zone-id <id>', 'Update Cloudflare zone ID')
400
- .option('--environment <env>', 'Update target environment: development, staging, production')
401
- .option('--add-feature <feature>', 'Add a feature flag')
402
- .option('--remove-feature <feature>', 'Remove a feature flag')
403
- .option('--regenerate-configs', 'Regenerate all configuration files')
404
- .option('--fix-errors', 'Attempt to fix common configuration errors')
405
- .action(async (servicePath, options) => {
406
- try {
407
- const orchestrator = new ServiceOrchestrator();
408
-
409
- // Auto-detect service path if not provided
410
- if (!servicePath) {
411
- servicePath = await orchestrator.detectServicePath();
412
- if (!servicePath) {
413
- console.error(chalk.red('No service path provided and could not auto-detect service directory'));
414
- console.log(chalk.white('Please run this command from within a service directory or specify the path'));
415
- process.exit(1);
416
- }
417
- console.log(chalk.cyan(`Auto-detected service at: ${servicePath}`));
418
- }
419
-
420
- // Validate it's a service directory
421
- const isValid = await orchestrator.validateService(servicePath);
422
- if (!isValid.valid) {
423
- console.log(chalk.yellow('āš ļø Service has configuration issues. Use --fix-errors to attempt automatic fixes.'));
424
- if (!options.fixErrors) {
425
- console.log(chalk.white('Issues found:'));
426
- isValid.issues.forEach(issue => {
427
- console.log(chalk.yellow(` • ${issue}`));
428
- });
429
- process.exit(1);
430
- }
431
- }
432
-
433
- if (options.interactive) {
434
- await orchestrator.runInteractiveUpdate(servicePath);
435
- } else {
436
- await orchestrator.runNonInteractiveUpdate(servicePath, options);
437
- }
438
-
439
- console.log(chalk.green('\nāœ“ Service update completed successfully!'));
440
-
441
- } catch (error) {
442
- console.error(chalk.red(`\nāœ— Service update failed: ${error.message}`));
443
- if (error.details) {
444
- console.error(chalk.yellow(`Details: ${error.details}`));
445
- }
446
- if (error.recovery) {
447
- console.log(chalk.cyan('\nšŸ’” Recovery suggestions:'));
448
- error.recovery.forEach(suggestion => {
449
- console.log(chalk.white(` • ${suggestion}`));
450
- });
451
- }
452
- process.exit(1);
453
- }
454
- });
455
-
456
- // Diagnose service issues
457
- program
458
- .command('diagnose [service-path]')
459
- .description('Diagnose and report issues with an existing service')
460
- .option('--deep-scan', 'Perform deep analysis including dependencies and deployment readiness')
461
- .option('--export-report <file>', 'Export diagnostic report to file')
462
- .action(async (servicePath, options) => {
463
- try {
464
- const orchestrator = new ServiceOrchestrator();
465
-
466
- // Auto-detect service path if not provided
467
- if (!servicePath) {
468
- servicePath = await orchestrator.detectServicePath();
469
- if (!servicePath) {
470
- console.error(chalk.red('No service path provided and could not auto-detect service directory'));
471
- process.exit(1);
472
- }
473
- }
474
-
475
- console.log(chalk.cyan('šŸ” Diagnosing service...'));
476
- const diagnosis = await orchestrator.diagnoseService(servicePath, options);
477
-
478
- // Display results
479
- console.log(chalk.cyan('\nšŸ“‹ Diagnostic Report'));
480
- console.log(chalk.white(`Service: ${diagnosis.serviceName || 'Unknown'}`));
481
- console.log(chalk.white(`Path: ${servicePath}`));
482
-
483
- if (diagnosis.errors.length > 0) {
484
- console.log(chalk.red('\nāŒ Critical Errors:'));
485
- diagnosis.errors.forEach(error => {
486
- console.log(chalk.red(` • ${error.message}`));
487
- if (error.location) {
488
- console.log(chalk.gray(` Location: ${error.location}`));
489
- }
490
- if (error.suggestion) {
491
- console.log(chalk.cyan(` šŸ’” ${error.suggestion}`));
492
- }
493
- });
494
- }
495
-
496
- if (diagnosis.warnings.length > 0) {
497
- console.log(chalk.yellow('\nāš ļø Warnings:'));
498
- diagnosis.warnings.forEach(warning => {
499
- console.log(chalk.yellow(` • ${warning.message}`));
500
- if (warning.suggestion) {
501
- console.log(chalk.cyan(` šŸ’” ${warning.suggestion}`));
502
- }
503
- });
504
- }
505
-
506
- if (diagnosis.recommendations.length > 0) {
507
- console.log(chalk.cyan('\nšŸ’” Recommendations:'));
508
- diagnosis.recommendations.forEach(rec => {
509
- console.log(chalk.white(` • ${rec}`));
510
- });
511
- }
512
-
513
- // Export report if requested
514
- if (options.exportReport) {
515
- await orchestrator.exportDiagnosticReport(diagnosis, options.exportReport);
516
- console.log(chalk.green(`\nšŸ“„ Report exported to: ${options.exportReport}`));
517
- }
518
-
519
- // Exit with error code if critical issues found
520
- if (diagnosis.errors.length > 0) {
521
- process.exit(1);
522
- }
523
-
524
- } catch (error) {
525
- console.error(chalk.red(`Diagnosis failed: ${error.message}`));
526
- process.exit(1);
527
- }
528
- });
529
-
530
- // Professional Edition: assess command (from clodo-orchestration)
531
- program
532
- .command('assess [service-path]')
533
- .description('Run intelligent capability assessment (requires @tamyla/clodo-orchestration)')
534
- .option('--export <file>', 'Export assessment results to JSON file')
535
- .option('--domain <domain>', 'Domain name for assessment')
536
- .option('--service-type <type>', 'Service type for assessment')
537
- .option('--token <token>', 'Cloudflare API token')
538
- .action(async (servicePath, options) => {
539
- try {
540
- // Try to load professional orchestration package
541
- let orchestrationModule;
542
- try {
543
- orchestrationModule = await import('@tamyla/clodo-orchestration');
544
- } catch (err) {
545
- console.error(chalk.red('āŒ clodo-orchestration package not found'));
546
- console.error(chalk.yellow('šŸ’” Install with: npm install @tamyla/clodo-orchestration'));
547
- process.exit(1);
548
- }
549
-
550
- const {
551
- CapabilityAssessmentEngine,
552
- ServiceAutoDiscovery,
553
- runAssessmentWorkflow
554
- } = orchestrationModule;
555
-
556
- const targetPath = servicePath || process.cwd();
557
- console.log(chalk.cyan('\n🧠 Professional Capability Assessment'));
558
- console.log(chalk.gray('─'.repeat(60)));
559
- console.log(chalk.white(`Service Path: ${targetPath}`));
560
-
561
- if (options.domain) {
562
- console.log(chalk.white(`Domain: ${options.domain}`));
563
- }
564
- if (options.serviceType) {
565
- console.log(chalk.white(`Service Type: ${options.serviceType}`));
566
- }
567
- console.log(chalk.gray('─'.repeat(60)));
568
-
569
- // Use the assessment workflow
570
- const assessment = await runAssessmentWorkflow({
571
- servicePath: targetPath,
572
- domain: options.domain,
573
- serviceType: options.serviceType,
574
- token: options.token || process.env.CLOUDFLARE_API_TOKEN
575
- });
576
-
577
- // Display results
578
- console.log(chalk.cyan('\nāœ… Assessment Results'));
579
- console.log(chalk.gray('─'.repeat(60)));
580
- console.log(chalk.white(`Service Type: ${assessment.mergedInputs?.serviceType || assessment.serviceType || 'Not determined'}`));
581
- console.log(chalk.white(`Confidence: ${assessment.confidence}%`));
582
-
583
- if (assessment.gapAnalysis?.missing) {
584
- console.log(chalk.white(`Missing Capabilities: ${assessment.gapAnalysis.missing.length}`));
585
- if (assessment.gapAnalysis.missing.length > 0) {
586
- console.log(chalk.yellow('\nāš ļø Missing:'));
587
- assessment.gapAnalysis.missing.forEach(gap => {
588
- console.log(chalk.yellow(` • ${gap.capability}: ${gap.reason || 'Not available'}`));
589
- });
590
- }
591
- }
592
-
593
- console.log(chalk.gray('─'.repeat(60)));
594
-
595
- // Export results if requested
596
- if (options.export) {
597
- require('fs').writeFileSync(options.export, JSON.stringify(assessment, null, 2));
598
- console.log(chalk.green(`\nšŸ“„ Results exported to: ${options.export}`));
599
- }
600
-
601
- } catch (error) {
602
- console.error(chalk.red(`Assessment failed: ${error.message}`));
603
- if (process.env.DEBUG) {
604
- console.error(chalk.gray(error.stack));
605
- }
606
- process.exit(1);
607
- }
608
- });
609
-
610
- // Deploy command - using existing InputCollector + CustomerConfigLoader (reusable pattern)
611
- program
612
- .command('deploy')
613
- .description('Deploy service using three-tier input architecture')
614
- .option('-c, --customer <name>', 'Customer name')
615
- .option('-e, --env <environment>', 'Target environment (development, staging, production)')
616
- .option('-i, --interactive', 'Interactive mode (review confirmations)', true)
617
- .option('--non-interactive', 'Non-interactive mode (use stored config)')
618
- .option('--config-file <file>', 'Load configuration from JSON file')
619
- .option('--defaults', 'Use default values where possible (non-interactive)')
620
- .option('--quiet', 'Quiet mode - minimal output for CI/CD')
621
- .option('--json-output', 'Output results as JSON for scripting')
622
- .option('--dry-run', 'Simulate deployment without making changes')
623
- .option('--domain <domain>', 'Domain name (overrides stored config)')
624
- .option('--service-path <path>', 'Path to service directory', '.')
625
- .action(async (options) => {
626
- try {
627
- // Use existing reusable components
628
- const { InputCollector } = await import('../dist/service-management/InputCollector.js');
629
- const { UnifiedConfigManager } = await import('../dist/utils/config/unified-config-manager.js');
630
- const { ConfirmationHandler } = await import('../dist/service-management/handlers/ConfirmationHandler.js');
631
- const { MultiDomainOrchestrator } = await import('../dist/orchestration/multi-domain-orchestrator.js');
632
-
633
- console.log(chalk.cyan('\nšŸš€ Clodo Framework Deployment'));
634
- console.log(chalk.white('Using Three-Tier Input Architecture\n'));
635
-
636
- const isInteractive = options.interactive && !options.nonInteractive && !options.configFile && !options.defaults;
637
- const isQuiet = options.quiet;
638
- const jsonOutput = options.jsonOutput;
639
- const configManager = new UnifiedConfigManager({
640
- configDir: join(process.cwd(), 'config', 'customers')
641
- });
642
- const inputCollector = new InputCollector({ interactive: isInteractive });
643
- const confirmationHandler = new ConfirmationHandler({ interactive: isInteractive });
644
-
645
- let coreInputs = {};
646
- let source = 'interactive';
647
-
648
- // Interactive mode: run full three-tier input collection
649
- if (isInteractive) {
650
- const collectionResult = await inputCollector.collect();
651
- coreInputs = collectionResult.flatInputs;
652
-
653
- // The three-tier collection already included confirmations
654
- // Skip the separate confirmation step since it was done in collect()
655
- } else {
656
- // Non-interactive mode: load from config file or stored config
657
-
658
- // Load from JSON config file if specified
659
- if (options.configFile) {
660
- coreInputs = loadJsonConfig(options.configFile);
661
- source = 'config-file';
662
- } else if (options.customer && options.env) {
663
- // Check UnifiedConfigManager for existing config
664
- if (configManager.configExists(options.customer, options.env)) {
665
- console.log(chalk.green(`āœ… Found existing configuration for ${options.customer}/${options.env}\n`));
666
- configManager.displayCustomerConfig(options.customer, options.env);
667
-
668
- // Non-interactive: auto-load the config
669
- const storedConfig = configManager.loadCustomerConfig(options.customer, options.env);
670
- if (storedConfig) {
671
- coreInputs = storedConfig;
672
- source = 'stored-config';
673
- }
674
- } else {
675
- console.log(chalk.yellow(`āš ļø No configuration found for ${options.customer}/${options.env}`));
676
- console.log(chalk.white('Collecting inputs interactively...\n'));
677
- }
678
- } else {
679
- console.log(chalk.yellow('āš ļø Customer and environment not specified'));
680
- console.log(chalk.white('Use --customer and --env options, or run in interactive mode'));
681
- process.exit(1);
682
- }
683
-
684
- // Collect inputs if we don't have them from stored config
685
- if (!coreInputs.cloudflareAccountId) {
686
- console.log(chalk.cyan('šŸ“Š Tier 1: Core Input Collection\n'));
687
-
688
- // Collect basic info with smart customer selection
689
- let customer = options.customer;
690
- if (!customer) {
691
- const customers = configManager.listCustomers();
692
- if (customers.length > 0) {
693
- const selection = await inputCollector.question('Select customer (enter number or name): ');
694
-
695
- // Try to parse as number first
696
- const num = parseInt(selection);
697
- if (!isNaN(num) && num >= 1 && num <= customers.length) {
698
- customer = customers[num - 1];
699
- console.log(chalk.green(`āœ“ Selected: ${customer}\n`));
700
- } else if (customers.includes(selection)) {
701
- customer = selection;
702
- console.log(chalk.green(`āœ“ Selected: ${customer}\n`));
703
- } else {
704
- // New customer name
705
- customer = selection;
706
- console.log(chalk.yellow(`āš ļø Creating new customer: ${customer}\n`));
707
- }
708
- } else {
709
- customer = await inputCollector.question('Customer name: ');
710
- }
711
- }
712
- const environment = options.env || await inputCollector.collectEnvironment();
713
- const serviceName = await inputCollector.collectServiceName();
714
- const serviceType = await inputCollector.collectServiceType();
715
-
716
- // Collect Cloudflare token
717
- const cloudflareToken = process.env.CLOUDFLARE_API_TOKEN || await inputCollector.collectCloudflareToken();
718
-
719
- // Use CloudflareAPI for automatic domain discovery
720
- console.log(chalk.cyan('ā³ Fetching Cloudflare configuration...'));
721
- const cloudflareConfig = await inputCollector.collectCloudflareConfigWithDiscovery(
722
- cloudflareToken,
723
- options.domain
724
- );
725
-
726
- // Combine all inputs
727
- coreInputs = {
728
- customer,
729
- environment,
730
- serviceName,
731
- serviceType,
732
- domainName: cloudflareConfig.domainName,
733
- cloudflareToken,
734
- cloudflareAccountId: cloudflareConfig.accountId,
735
- cloudflareZoneId: cloudflareConfig.zoneId
736
- };
737
-
738
- source = 'interactive';
739
- }
740
-
741
- // Allow domain override
742
- if (options.domain) {
743
- coreInputs.domainName = options.domain;
744
- }
745
-
746
- // Early validation before proceeding
747
- const isValid = await validateDeploymentPrerequisites(coreInputs, options);
748
- if (!isValid) {
749
- console.log(chalk.red('\nāŒ Deployment cancelled due to validation errors.'));
750
- console.log(chalk.cyan('šŸ’” Fix the issues above and try again.'));
751
- process.exit(1);
752
- }
753
-
754
- // Tier 2: Generate smart confirmations using existing ConfirmationHandler
755
- // Skip if interactive mode (confirmations already done in three-tier collection)
756
- const confirmations = isInteractive
757
- ? {} // Confirmations already collected in interactive mode
758
- : await confirmationHandler.generateAndConfirm(coreInputs);
759
-
760
- // Show deployment summary
761
- console.log(chalk.cyan('\nšŸ“Š Deployment Summary'));
762
- console.log(chalk.gray('─'.repeat(60)));
763
-
764
- console.log(chalk.white(`Source: ${source}`));
765
- console.log(chalk.white(`Customer: ${coreInputs.customer}`));
766
- console.log(chalk.white(`Environment: ${coreInputs.environment}`));
767
- console.log(chalk.white(`Domain: ${coreInputs.domainName}`));
768
- console.log(chalk.white(`Account ID: ${coreInputs.cloudflareAccountId ? coreInputs.cloudflareAccountId.substring(0, 8) + '...' : 'N/A'}`));
769
- console.log(chalk.white(`Zone ID: ${coreInputs.cloudflareZoneId ? coreInputs.cloudflareZoneId.substring(0, 8) + '...' : 'N/A'}`));
770
-
771
- if (confirmations.workerName) {
772
- console.log(chalk.white(`Worker: ${confirmations.workerName}`));
773
- }
774
- if (confirmations.databaseName) {
775
- console.log(chalk.white(`Database: ${confirmations.databaseName}`));
776
- }
777
-
778
- console.log(chalk.gray('─'.repeat(60)));
779
-
780
- if (options.dryRun) {
781
- console.log(chalk.yellow('\nšŸ” DRY RUN MODE - No changes will be made\n'));
782
- }
783
-
784
- // Tier 3: Execute deployment
785
- console.log(chalk.cyan('\nāš™ļø Tier 3: Automated Deployment\n'));
786
-
787
- // OPTIONAL: Try to load professional orchestration package if available
788
- let professionalOrchestration = null;
789
- try {
790
- const { runAssessmentWorkflow } = await import('@tamyla/clodo-orchestration');
791
- professionalOrchestration = { runAssessmentWorkflow };
792
- console.log(chalk.cyan('šŸ“Š Professional Edition (clodo-orchestration) detected'));
793
- } catch (err) {
794
- // clodo-orchestration not installed, continue with standard assessment
795
- if (process.env.DEBUG) {
796
- console.log(chalk.gray(` ā„¹ļø clodo-orchestration not available: ${err.message}`));
797
- }
798
- }
799
-
800
- // INTEGRATION: Add intelligent service discovery and assessment
801
- console.log(chalk.cyan('🧠 Performing Intelligent Service Assessment...'));
802
- const { CapabilityAssessmentEngine } = await import('../dist/service-management/CapabilityAssessmentEngine.js');
803
- const assessor = new CapabilityAssessmentEngine(options.servicePath || process.cwd());
804
-
805
- try {
806
- const assessment = await assessor.assessCapabilities({
807
- serviceName: coreInputs.serviceName,
808
- serviceType: coreInputs.serviceType,
809
- environment: coreInputs.environment,
810
- domainName: coreInputs.domainName
811
- });
812
-
813
- // Display assessment results
814
- console.log(chalk.green('āœ… Assessment Complete'));
815
- console.log(chalk.white(` Service Type: ${assessment.mergedInputs.serviceType || 'Not determined'}`));
816
- console.log(chalk.white(` Confidence: ${assessment.confidence}%`));
817
- console.log(chalk.white(` Missing Capabilities: ${assessment.gapAnalysis.missing.length}`));
818
-
819
- // Show any blocking issues
820
- const blockingIssues = assessment.gapAnalysis.missing.filter(gap => gap.priority === 'blocked');
821
- if (blockingIssues.length > 0) {
822
- console.log(chalk.yellow('\nāš ļø Permission-limited capabilities detected:'));
823
- blockingIssues.forEach(issue => {
824
- console.log(chalk.yellow(` - ${issue.capability}: ${issue.reason}`));
825
- });
826
- }
827
-
828
- console.log('');
829
-
830
- } catch (assessmentError) {
831
- console.log(chalk.yellow('āš ļø Assessment failed, proceeding with deployment:'), assessmentError.message);
832
- console.log('');
833
- }
834
-
835
- const orchestrator = new MultiDomainOrchestrator({
836
- domains: [coreInputs.domainName],
837
- environment: coreInputs.environment,
838
- dryRun: options.dryRun,
839
- servicePath: options.servicePath,
840
- cloudflareToken: coreInputs.cloudflareToken,
841
- cloudflareAccountId: coreInputs.cloudflareAccountId
842
- });
843
-
844
- // Show progress for initialization
845
- await showProgress('Initializing deployment orchestrator', 1500);
846
- await orchestrator.initialize();
847
-
848
- console.log(chalk.cyan('šŸš€ Starting deployment...\n'));
849
-
850
- // Show progress during deployment
851
- const deployPromise = orchestrator.deploySingleDomain(coreInputs.domainName, {
852
- ...coreInputs,
853
- ...confirmations,
854
- servicePath: options.servicePath
855
- });
856
-
857
- // Show deployment progress
858
- const progressMessages = [
859
- 'Preparing deployment configuration',
860
- 'Setting up Cloudflare resources',
861
- 'Deploying worker script',
862
- 'Configuring database connections',
863
- 'Setting up domain routing',
864
- 'Running final health checks'
865
- ];
866
-
867
- let progressIndex = 0;
868
- const progressInterval = setInterval(() => {
869
- if (progressIndex < progressMessages.length) {
870
- console.log(chalk.gray(` ${progressMessages[progressIndex]}`));
871
- progressIndex++;
872
- }
873
- }, 2000);
874
-
875
- const result = await deployPromise;
876
- clearInterval(progressInterval);
877
-
878
- // Fill in remaining progress messages quickly
879
- while (progressIndex < progressMessages.length) {
880
- console.log(chalk.gray(` ${progressMessages[progressIndex]}`));
881
- progressIndex++;
882
- }
883
-
884
- // Display results with cleaner formatting
885
- console.log(chalk.green('\nāœ… Deployment Completed Successfully!'));
886
- console.log(chalk.gray('─'.repeat(60)));
887
-
888
- // Show key information prominently
889
- if (result.url || confirmations.deploymentUrl) {
890
- console.log(chalk.white(`🌐 Service URL: ${chalk.bold(result.url || confirmations.deploymentUrl)}`));
891
- }
892
-
893
- console.log(chalk.white(`šŸ‘¤ Customer: ${coreInputs.customer}`));
894
- console.log(chalk.white(`šŸ­ Environment: ${coreInputs.environment}`));
895
- console.log(chalk.white(`šŸ”§ Worker: ${confirmations.workerName || 'N/A'}`));
896
-
897
- if (result.health) {
898
- const healthStatus = result.health.toLowerCase().includes('ok') || result.health.toLowerCase().includes('healthy')
899
- ? chalk.green('āœ… Healthy')
900
- : chalk.yellow('āš ļø Check required');
901
- console.log(chalk.white(`šŸ’š Health: ${healthStatus}`));
902
- }
903
-
904
- console.log(chalk.gray('─'.repeat(60)));
905
-
906
- // Save deployment configuration for future reuse
907
- try {
908
- console.log(chalk.cyan('\nšŸ’¾ Saving deployment configuration...'));
909
-
910
- const configFile = await configManager.saveCustomerConfig(
911
- coreInputs.customer,
912
- coreInputs.environment,
913
- {
914
- coreInputs,
915
- confirmations,
916
- result
917
- }
918
- );
919
-
920
- console.log(chalk.green(' āœ… Configuration saved for future deployments'));
921
- console.log(chalk.gray(` šŸ“„ File: ${configFile}`));
922
- } catch (saveError) {
923
- console.log(chalk.yellow(` āš ļø Could not save configuration: ${saveError.message}`));
924
- console.log(chalk.gray(' Deployment succeeded, but you may need to re-enter values next time.'));
925
- }
926
-
927
- console.log(chalk.cyan('\nšŸ“‹ Next Steps:'));
928
- console.log(chalk.white(` • Test deployment: curl ${result.url || confirmations.deploymentUrl}/health`));
929
- console.log(chalk.white(` • Monitor logs: wrangler tail ${confirmations.workerName}`));
930
- console.log(chalk.white(` • View dashboard: https://dash.cloudflare.com`));
931
- }
932
-
933
- } catch (error) {
934
- console.error(chalk.red(`\nāŒ Deployment failed: ${error.message}`));
935
-
936
- // Show helpful context based on error type
937
- if (error.message.includes('timeout')) {
938
- console.log(chalk.yellow('\nšŸ’” Troubleshooting Tips:'));
939
- console.log(chalk.white(' • Use non-interactive mode: npx clodo-service deploy --customer=NAME --env=ENV --non-interactive'));
940
- console.log(chalk.white(' • Set DEBUG=1 for detailed logs: DEBUG=1 npx clodo-service deploy'));
941
- console.log(chalk.white(' • Check your terminal supports readline'));
942
- } else if (error.message.includes('domain')) {
943
- console.log(chalk.yellow('\nšŸ’” Domain Issues:'));
944
- console.log(chalk.white(' • Verify domain exists in Cloudflare dashboard'));
945
- console.log(chalk.white(' • Check API token has zone:read permissions'));
946
- console.log(chalk.white(' • Try specifying domain: --domain=example.com'));
947
- } else if (error.message.includes('readline')) {
948
- console.log(chalk.yellow('\nšŸ’” Terminal Issues:'));
949
- console.log(chalk.white(' • Try a different terminal (cmd, bash, powershell)'));
950
- console.log(chalk.white(' • Use --non-interactive with config file'));
951
- }
952
-
953
- if (process.env.DEBUG) {
954
- console.error(chalk.gray('\nFull Stack Trace:'));
955
- console.error(chalk.gray(error.stack));
956
- } else {
957
- console.log(chalk.gray('\nRun with DEBUG=1 for full stack trace'));
958
- }
959
-
960
- process.exit(1);
961
- }
962
- });
963
-
964
- // Utility function to redact sensitive information from logs
965
- function redactSensitiveInfo(text) {
966
- if (typeof text !== 'string') return text;
967
-
968
- // Patterns to redact
969
- const patterns = [
970
- // Cloudflare API tokens
971
- [/(CLOUDFLARE_API_TOKEN=?)(\w{20,})/gi, '$1[REDACTED]'],
972
- // Generic API tokens/keys
973
- [/(api[_-]?token|api[_-]?key|auth[_-]?token)["']?[:=]\s*["']?([a-zA-Z0-9_-]{20,})["']?/gi, '$1: [REDACTED]'],
974
- // Passwords
975
- [/(password|passwd|pwd)["']?[:=]\s*["']?([^"'\s]{3,})["']?/gi, '$1: [REDACTED]'],
976
- // Secrets
977
- [/(secret|key)["']?[:=]\s*["']?([a-zA-Z0-9_-]{10,})["']?/gi, '$1: [REDACTED]'],
978
- // Account IDs (partial redaction)
979
- [/(account[_-]?id|zone[_-]?id)["']?[:=]\s*["']?([a-zA-Z0-9]{8})([a-zA-Z0-9]{24,})["']?/gi, '$1: $2[REDACTED]']
980
- ];
981
-
982
- let redacted = text;
983
- patterns.forEach(([pattern, replacement]) => {
984
- redacted = redacted.replace(pattern, replacement);
985
- });
986
-
987
- return redacted;
988
- }
989
-
990
- program.parse();
71
+ // Parse command line arguments
72
+ program.parse();