@tamyla/clodo-framework 3.1.27 → 3.2.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cli/clodo-simple.js +1 -1
  3. package/dist/cli/commands/assess.js +1 -1
  4. package/dist/cli/commands/create.js +1 -1
  5. package/dist/cli/commands/deploy.js +59 -21
  6. package/dist/cli/commands/diagnose.js +1 -1
  7. package/dist/cli/commands/update.js +1 -1
  8. package/dist/cli/commands/validate.js +1 -1
  9. package/dist/cli/security-cli.js +1 -1
  10. package/dist/config/.env.staging.example +25 -0
  11. package/dist/deployment/index.js +2 -2
  12. package/dist/deployment/orchestration/BaseDeploymentOrchestrator.js +1 -1
  13. package/dist/deployment/orchestration/UnifiedDeploymentOrchestrator.js +2 -4
  14. package/dist/index.js +11 -12
  15. package/dist/lib/deployment/orchestration/BaseDeploymentOrchestrator.js +1 -1
  16. package/dist/lib/shared/cloudflare/ops.js +103 -17
  17. package/dist/lib/shared/deployment/credential-collector.js +134 -1
  18. package/dist/lib/shared/deployment/index.js +7 -3
  19. package/dist/lib/shared/deployment/rollback-manager.js +1 -1
  20. package/dist/lib/shared/deployment/utilities/d1-error-recovery.js +1 -1
  21. package/dist/lib/shared/deployment/workflows/index.js +13 -0
  22. package/dist/lib/shared/deployment/workflows/interactive-confirmation.js +1 -1
  23. package/dist/lib/shared/deployment/workflows/interactive-deployment-coordinator.js +229 -0
  24. package/dist/lib/shared/deployment/workflows/interactive-domain-info-gatherer.js +1 -1
  25. package/dist/lib/shared/deployment/workflows/interactive-secret-workflow.js +3 -3
  26. package/dist/lib/shared/deployment/workflows/interactive-testing-workflow.js +2 -2
  27. package/dist/lib/shared/deployment/workflows/interactive-validation.js +2 -2
  28. package/dist/lib/shared/monitoring/health-checker.js +11 -5
  29. package/dist/lib/shared/validation/ValidationRegistry.js +1 -1
  30. package/dist/utils/deployment/index.js +2 -1
  31. package/dist/utils/health-checker.js +53 -76
  32. package/dist/utils/index.js +3 -0
  33. package/package.json +4 -11
  34. package/templates/generic/src/worker/index.js +1 -1
  35. package/templates/static-site/src/worker/index.js +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [3.2.1](https://github.com/tamylaa/clodo-framework/compare/v3.2.0...v3.2.1) (2025-12-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * enhance deploy command with prerequisite checking and organize project structure ([660c5ab](https://github.com/tamylaa/clodo-framework/commit/660c5ab82546ca1a80b42c1d4cbc6d3972cc6203))
7
+
8
+ # [3.2.0](https://github.com/tamylaa/clodo-framework/compare/v3.1.27...v3.2.0) (2025-12-03)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * add pre-publish import validation and remove non-existent exports ([a41fa7d](https://github.com/tamylaa/clodo-framework/commit/a41fa7df5c1ce05d34efe7e62e68d1c2fa8ea89c))
14
+ * correct CLI import paths for npm distribution ([ba68e0a](https://github.com/tamylaa/clodo-framework/commit/ba68e0adfa4c3bf36584b8bd37310ef616f79722))
15
+ * enable missing exports and remove lib-dependent modules from npm ([1645a58](https://github.com/tamylaa/clodo-framework/commit/1645a58d1e1f5d7126fd02766e9b4d006fa45be7))
16
+
17
+
18
+ ### Features
19
+
20
+ * add comprehensive pre-publish and downstream install tests ([b1e8a25](https://github.com/tamylaa/clodo-framework/commit/b1e8a25b3acae840b66f728aa55dd5c24af0914f))
21
+ * add proper CLI regression tests to downstream install test ([4cde4bc](https://github.com/tamylaa/clodo-framework/commit/4cde4bc08f0290f0432815b2c8cfa60247bc8dfd))
22
+
1
23
  ## [3.1.27](https://github.com/tamylaa/clodo-framework/compare/v3.1.26...v3.1.27) (2025-12-02)
2
24
 
3
25
 
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command } from 'commander';
10
10
  import chalk from 'chalk';
11
- import { Clodo } from '../src/simple-api.js';
11
+ import { Clodo } from '@tamyla/clodo-framework';
12
12
 
13
13
  // Create program instance
14
14
  const program = new Command();
@@ -5,7 +5,7 @@
5
5
 
6
6
  import chalk from 'chalk';
7
7
  import path from 'path';
8
- import { ServiceOrchestrator } from '../../src/service-management/ServiceOrchestrator.js';
8
+ import { ServiceOrchestrator } from '@tamyla/clodo-framework';
9
9
  import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
10
10
  import { ServiceConfigManager } from '../../lib/shared/utils/service-config-manager.js';
11
11
  export function registerAssessCommand(program) {
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import chalk from 'chalk';
9
- import { Clodo } from '../../src/simple-api.js';
9
+ import { Clodo } from '@tamyla/clodo-framework';
10
10
  import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
11
11
  import { ConfigLoader } from '../../lib/shared/utils/config-loader.js';
12
12
  export function registerCreateCommand(program) {
@@ -1,11 +1,12 @@
1
1
  import chalk from 'chalk';
2
- import { Clodo } from '../../src/simple-api.js';
2
+ import { Clodo } from '@tamyla/clodo-framework';
3
3
  import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
4
4
  import { ConfigLoader } from '../../lib/shared/utils/config-loader.js';
5
+ import { InteractiveDeploymentCoordinator } from '../../lib/shared/deployment/workflows/interactive-deployment-coordinator.js';
5
6
  export function registerDeployCommand(program) {
6
- const command = program.command('deploy').description('Deploy a Clodo service with smart credential handling and domain selection')
7
+ const command = program.command('deploy').description('Deploy a Clodo service with interactive configuration and validation')
7
8
  // Cloudflare-specific options
8
- .option('--token <token>', 'Cloudflare API token').option('--account-id <id>', 'Cloudflare account ID').option('--zone-id <id>', 'Cloudflare zone ID').option('--domain <domain>', 'Specific domain to deploy to (otherwise prompted if multiple exist)').option('--environment <env>', 'Target environment (development, staging, production)', 'production').option('--development', 'Deploy to development environment (shorthand for --environment development)').option('--staging', 'Deploy to staging environment (shorthand for --environment staging)').option('--production', 'Deploy to production environment (shorthand for --environment production)').option('--dry-run', 'Simulate deployment without making changes').option('-y, --yes', 'Skip confirmation prompts (for CI/CD)').option('--service-path <path>', 'Path to service directory', '.');
9
+ .option('--token <token>', 'Cloudflare API token (or set CLOUDFLARE_API_TOKEN env var)').option('--account-id <id>', 'Cloudflare account ID (or set CLOUDFLARE_ACCOUNT_ID env var)').option('--zone-id <id>', 'Cloudflare zone ID (or set CLOUDFLARE_ZONE_ID env var)').option('--domain <domain>', 'Specific domain to deploy to').option('--environment <env>', 'Target environment (development, staging, production)', 'production').option('--development', 'Deploy to development environment (shorthand for --environment development)').option('--staging', 'Deploy to staging environment (shorthand for --environment staging)').option('--production', 'Deploy to production environment (shorthand for --environment production)').option('--dry-run', 'Simulate deployment without making changes').option('-y, --yes', 'Skip confirmation prompts (for CI/CD)').option('--service-path <path>', 'Path to service directory', '.').option('--check-prereqs', 'Check deployment prerequisites before starting').option('--check-auth', 'Check Wrangler authentication status').option('--check-network', 'Check network connectivity to Cloudflare');
9
10
 
10
11
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
11
12
  StandardOptions.define(command).action(async options => {
@@ -38,26 +39,63 @@ export function registerDeployCommand(program) {
38
39
  // Merge config file defaults with CLI options (CLI takes precedence)
39
40
  const mergedOptions = configLoader.merge(configFileData, options);
40
41
 
41
- // Use simple API for deployment
42
- const result = await Clodo.deploy({
43
- servicePath: mergedOptions.servicePath || '.',
44
- environment: mergedOptions.environment || 'production',
45
- domain: mergedOptions.domain,
46
- dryRun: mergedOptions.dryRun || false,
47
- credentials: {
48
- token: mergedOptions.token,
49
- accountId: mergedOptions.accountId,
50
- zoneId: mergedOptions.zoneId
51
- }
52
- });
53
- if (result.success) {
54
- output.success(result.message);
55
- if (result.deployedDomains && result.deployedDomains.length > 0) {
56
- output.info(`Deployed to domains: ${result.deployedDomains.join(', ')}`);
42
+ // Determine if interactive mode should be enabled
43
+ const interactive = !mergedOptions.nonInteractive && !mergedOptions.yes;
44
+ if (interactive) {
45
+ console.log(chalk.cyan('\nšŸš€ Interactive Clodo Service Deployment'));
46
+ console.log(chalk.gray('═'.repeat(60)));
47
+ console.log(chalk.white('Welcome to the interactive deployment wizard!\n'));
48
+
49
+ // Use the interactive deployment coordinator
50
+ const coordinator = new InteractiveDeploymentCoordinator({
51
+ servicePath: mergedOptions.servicePath || '.',
52
+ environment: mergedOptions.environment || 'production',
53
+ domain: mergedOptions.domain,
54
+ dryRun: mergedOptions.dryRun || false,
55
+ credentials: {
56
+ token: mergedOptions.token,
57
+ accountId: mergedOptions.accountId,
58
+ zoneId: mergedOptions.zoneId
59
+ },
60
+ checkPrereqs: mergedOptions.checkPrereqs,
61
+ checkAuth: mergedOptions.checkAuth,
62
+ checkNetwork: mergedOptions.checkNetwork,
63
+ verbose: mergedOptions.verbose,
64
+ quiet: mergedOptions.quiet,
65
+ json: mergedOptions.json
66
+ });
67
+ const result = await coordinator.runInteractiveDeployment();
68
+ if (result.success) {
69
+ output.success(result.message);
70
+ if (result.deployedDomains && result.deployedDomains.length > 0) {
71
+ output.info(`Deployed to domains: ${result.deployedDomains.join(', ')}`);
72
+ }
73
+ } else {
74
+ output.error('Interactive deployment failed');
75
+ process.exit(1);
57
76
  }
58
77
  } else {
59
- output.error('Deployment failed');
60
- process.exit(1);
78
+ // Use simple API for deployment (non-interactive/CI mode)
79
+ const result = await Clodo.deploy({
80
+ servicePath: mergedOptions.servicePath || '.',
81
+ environment: mergedOptions.environment || 'production',
82
+ domain: mergedOptions.domain,
83
+ dryRun: mergedOptions.dryRun || false,
84
+ credentials: {
85
+ token: mergedOptions.token,
86
+ accountId: mergedOptions.accountId,
87
+ zoneId: mergedOptions.zoneId
88
+ }
89
+ });
90
+ if (result.success) {
91
+ output.success(result.message);
92
+ if (result.deployedDomains && result.deployedDomains.length > 0) {
93
+ output.info(`Deployed to domains: ${result.deployedDomains.join(', ')}`);
94
+ }
95
+ } else {
96
+ output.error('Deployment failed');
97
+ process.exit(1);
98
+ }
61
99
  }
62
100
  } catch (error) {
63
101
  const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import path from 'path';
3
- import { ServiceOrchestrator } from '../../src/service-management/ServiceOrchestrator.js';
3
+ import { ServiceOrchestrator } from '@tamyla/clodo-framework';
4
4
  import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
5
5
  import { ServiceConfigManager } from '../../lib/shared/utils/service-config-manager.js';
6
6
  export function registerDiagnoseCommand(program) {
@@ -4,7 +4,7 @@
4
4
 
5
5
  import chalk from 'chalk';
6
6
  import path from 'path';
7
- import { ServiceOrchestrator } from '../../src/service-management/ServiceOrchestrator.js';
7
+ import { ServiceOrchestrator } from '@tamyla/clodo-framework';
8
8
  import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
9
9
  import { ServiceConfigManager } from '../../lib/shared/utils/service-config-manager.js';
10
10
  export function registerUpdateCommand(program) {
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { Clodo } from '../../src/simple-api.js';
2
+ import { Clodo } from '@tamyla/clodo-framework';
3
3
  import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
4
4
  export function registerValidateCommand(program) {
5
5
  const command = program.command('validate <service-path>').description('Validate an existing service configuration').option('--export-report <file>', 'Export validation report to JSON file');
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { SecurityCLI } from '../src/security/SecurityCLI.js';
2
+ import { SecurityCLI } from '../security/SecurityCLI.js';
3
3
  const command = process.argv[2];
4
4
  const args = process.argv.slice(3);
5
5
  async function main() {
@@ -0,0 +1,25 @@
1
+ # Staging Environment Configuration
2
+ # Copy this file to .env.staging and fill in your actual Cloudflare credentials
3
+ # DO NOT commit this file to version control
4
+
5
+ # Cloudflare API Credentials for Staging Environment
6
+ CLOUDFLARE_API_TOKEN=your_staging_cloudflare_api_token_here
7
+ CLOUDFLARE_ACCOUNT_ID=your_staging_cloudflare_account_id_here
8
+ CLOUDFLARE_ZONE_ID=your_staging_cloudflare_zone_id_here
9
+
10
+ # Staging Domain Configuration
11
+ STAGING_DOMAIN=staging.example.com
12
+ STAGING_ZONE_ID=${CLOUDFLARE_ZONE_ID}
13
+
14
+ # Database Configuration (if different from development)
15
+ STAGING_DATABASE_ID=your_staging_database_id_here
16
+ STAGING_DATABASE_NAME=api2-com-staging-db
17
+
18
+ # Monitoring and Alerting
19
+ STAGING_METRICS_ENDPOINT=https://metrics-staging.example.com
20
+ STAGING_ALERT_EMAIL=staging-alerts@example.com
21
+
22
+ # Security Settings
23
+ STAGING_WAF_ENABLED=true
24
+ STAGING_RATE_LIMIT=500
25
+ STAGING_CORS_ORIGINS=https://staging.example.com,https://app-staging.example.com
@@ -1,11 +1,11 @@
1
1
  // Deployment Module
2
2
  // Core deployment components for the Clodo Framework
3
3
 
4
- export { WranglerDeployer } from './wrangler-deployer.js';
4
+ // NOTE: WranglerDeployer has lib/ dependencies not available in npm distribution
5
+ // export { WranglerDeployer } from './wrangler-deployer.js';
5
6
 
6
7
  // Orchestration Framework - Public API for downstream consumers
7
8
  export { BaseDeploymentOrchestrator } from './orchestration/BaseDeploymentOrchestrator.js';
8
9
  export { SingleServiceOrchestrator } from './orchestration/SingleServiceOrchestrator.js';
9
10
  export { PortfolioOrchestrator } from './orchestration/PortfolioOrchestrator.js';
10
- export { EnterpriseOrchestrator } from './orchestration/EnterpriseOrchestrator.js';
11
11
  export { UnifiedDeploymentOrchestrator } from './orchestration/UnifiedDeploymentOrchestrator.js';
@@ -19,7 +19,7 @@
19
19
  * @abstract
20
20
  */
21
21
 
22
- import { ErrorHandler } from '../../shared/utils/ErrorHandler.js';
22
+ import { ErrorHandler } from '../../../lib/shared/utils/ErrorHandler.js';
23
23
 
24
24
  /**
25
25
  * Phase state and execution order
@@ -36,8 +36,7 @@
36
36
  import { BaseDeploymentOrchestrator } from './BaseDeploymentOrchestrator.js';
37
37
  import { SingleServiceOrchestrator } from './SingleServiceOrchestrator.js';
38
38
  import { PortfolioOrchestrator } from './PortfolioOrchestrator.js';
39
- import { EnterpriseOrchestrator } from './EnterpriseOrchestrator.js';
40
- import { ErrorHandler } from '../../shared/utils/ErrorHandler.js';
39
+ import { ErrorHandler } from '../../../lib/shared/utils/ErrorHandler.js';
41
40
 
42
41
  /**
43
42
  * Capability descriptors - Defines what each capability provides
@@ -204,8 +203,7 @@ export class UnifiedDeploymentOrchestrator extends BaseDeploymentOrchestrator {
204
203
  // System adapters for backward compatibility
205
204
  this.systemAdapters = {
206
205
  single: SingleServiceOrchestrator,
207
- portfolio: PortfolioOrchestrator,
208
- enterprise: EnterpriseOrchestrator
206
+ portfolio: PortfolioOrchestrator
209
207
  };
210
208
 
211
209
  // Deployment context
package/dist/index.js CHANGED
@@ -21,22 +21,21 @@ export * from './modules/ModuleManager.js';
21
21
  export * from './routing/EnhancedRouter.js';
22
22
  export * from './handlers/GenericRouteHandler.js';
23
23
 
24
- // Deployment components (build-time only)
25
- // NOTE: Temporarily disabled - has lib/ dependencies that don't exist in npm distribution
24
+ // Deployment components
25
+ export { DeploymentValidator } from './deployment/validator.js';
26
+ export { DeploymentAuditor } from './deployment/auditor.js';
27
+ // NOTE: WranglerDeployer has lib/ dependencies not available in npm distribution
26
28
  // export { WranglerDeployer } from './deployment/wrangler-deployer.js';
27
29
 
28
30
  // Security components
29
- // NOTE: Temporarily disabled - has lib/ dependencies
30
- // export * from './security/index.js';
31
+ export { SecurityCLI } from './security/SecurityCLI.js';
32
+ export { ConfigurationValidator } from './security/ConfigurationValidator.js';
33
+ export { SecretGenerator } from './security/SecretGenerator.js';
31
34
 
32
- // Service management components (build-time only - has lib dependencies)
33
- // NOTE: Temporarily disabled - ServiceCreator imports from lib/
34
- // export { ServiceCreator } from './service-management/ServiceCreator.js';
35
- // export { ServiceOrchestrator } from './service-management/ServiceOrchestrator.js';
36
- // export { InputHandler } from './service-management/handlers/InputHandler.js';
37
- // export { ConfirmationHandler } from './service-management/handlers/ConfirmationHandler.js';
38
- // export { GenerationHandler } from './service-management/handlers/GenerationHandler.js';
39
- // export { ValidationHandler } from './service-management/handlers/ValidationHandler.js';
35
+ // Service management components
36
+ export { ServiceCreator } from './service-management/ServiceCreator.js';
37
+ export { ServiceOrchestrator } from './service-management/ServiceOrchestrator.js';
38
+ export { InputCollector } from './service-management/InputCollector.js';
40
39
 
41
40
  // Framework version info
42
41
  export const FRAMEWORK_VERSION = '1.0.0';
@@ -19,7 +19,7 @@
19
19
  * @abstract
20
20
  */
21
21
 
22
- import { ErrorHandler } from '../../shared/utils/ErrorHandler.js';
22
+ import { ErrorHandler } from '../../../shared/utils/ErrorHandler.js';
23
23
 
24
24
  /**
25
25
  * Phase state and execution order
@@ -242,7 +242,7 @@ export async function listDatabases(options = {}) {
242
242
  if (apiToken && accountId) {
243
243
  const {
244
244
  CloudflareAPI
245
- } = await import('../../../src/utils/cloudflare/api.js');
245
+ } = await import('@tamyla/clodo-framework/utils/cloudflare');
246
246
  const cf = new CloudflareAPI(apiToken);
247
247
  return await cf.listD1Databases(accountId);
248
248
  }
@@ -267,7 +267,7 @@ export async function databaseExists(databaseName, options = {}) {
267
267
  if (apiToken && accountId) {
268
268
  const {
269
269
  CloudflareAPI
270
- } = await import('../../../src/utils/cloudflare/api.js');
270
+ } = await import('@tamyla/clodo-framework/utils/cloudflare');
271
271
  const cf = new CloudflareAPI(apiToken);
272
272
  return await cf.d1DatabaseExists(accountId, databaseName);
273
273
  }
@@ -290,7 +290,7 @@ export async function createDatabase(name, options = {}) {
290
290
  if (apiToken && accountId) {
291
291
  const {
292
292
  CloudflareAPI
293
- } = await import('../../../src/utils/cloudflare/api.js');
293
+ } = await import('@tamyla/clodo-framework/utils/cloudflare');
294
294
  const cf = new CloudflareAPI(apiToken);
295
295
  const result = await cf.createD1Database(accountId, name);
296
296
  return result.uuid; // Return UUID to match CLI behavior
@@ -405,7 +405,7 @@ export async function getDatabaseId(databaseName, options = {}) {
405
405
  if (apiToken && accountId) {
406
406
  const {
407
407
  CloudflareAPI
408
- } = await import('../../../src/utils/cloudflare/api.js');
408
+ } = await import('@tamyla/clodo-framework/utils/cloudflare');
409
409
  const cf = new CloudflareAPI(apiToken);
410
410
  const db = await cf.getD1Database(accountId, databaseName);
411
411
  return db?.uuid || null;
@@ -430,33 +430,119 @@ export async function getDatabaseId(databaseName, options = {}) {
430
430
  }
431
431
 
432
432
  // Validate prerequisites
433
- export async function validatePrerequisites() {
433
+ export async function validatePrerequisites(options = {}) {
434
+ const {
435
+ checkAuth = false,
436
+ checkNetwork = false,
437
+ verbose = false
438
+ } = options;
434
439
  const checks = [{
435
440
  name: 'Node.js',
436
- command: 'node --version'
441
+ command: 'node --version',
442
+ required: true,
443
+ description: 'Required for running the framework and build processes',
444
+ minVersion: '18.0.0'
437
445
  }, {
438
446
  name: 'NPM',
439
- command: 'npm --version'
447
+ command: 'npm --version',
448
+ required: true,
449
+ description: 'Required for package management and scripts',
450
+ minVersion: '8.0.0'
440
451
  }, {
441
452
  name: 'Wrangler',
442
- command: 'npx wrangler --version'
453
+ command: 'npx wrangler --version',
454
+ required: true,
455
+ description: 'Cloudflare CLI for Workers and D1 database management',
456
+ minVersion: '3.0.0'
443
457
  }];
458
+
459
+ // Add optional checks
460
+ if (checkAuth) {
461
+ checks.push({
462
+ name: 'Wrangler Auth',
463
+ command: 'npx wrangler whoami',
464
+ required: false,
465
+ description: 'Cloudflare authentication (optional - can authenticate during deployment)',
466
+ async: true
467
+ });
468
+ }
469
+ if (checkNetwork) {
470
+ checks.push({
471
+ name: 'Network',
472
+ command: null,
473
+ required: false,
474
+ description: 'Internet connectivity to Cloudflare API',
475
+ async: true,
476
+ checkFunction: async () => {
477
+ try {
478
+ const response = await fetch('https://api.cloudflare.com/client/v4/user/tokens/verify', {
479
+ method: 'GET',
480
+ headers: {
481
+ 'Authorization': 'Bearer test'
482
+ },
483
+ signal: AbortSignal.timeout(5000)
484
+ });
485
+ return {
486
+ status: 'ok',
487
+ version: 'Connected'
488
+ };
489
+ } catch (error) {
490
+ if (error.name === 'AbortError') {
491
+ return {
492
+ status: 'failed',
493
+ error: 'Connection timeout'
494
+ };
495
+ }
496
+ return {
497
+ status: 'ok',
498
+ version: 'Connected (API responded)'
499
+ };
500
+ }
501
+ }
502
+ });
503
+ }
444
504
  const results = [];
445
505
  for (const check of checks) {
446
506
  try {
447
- const {
448
- stdout: version
449
- } = await execAsync(check.command, {
450
- encoding: 'utf8'
451
- });
507
+ let result;
508
+ if (check.async && check.checkFunction) {
509
+ result = await check.checkFunction();
510
+ } else if (check.command) {
511
+ const {
512
+ stdout: version
513
+ } = await execAsync(check.command, {
514
+ encoding: 'utf8',
515
+ timeout: 10000
516
+ });
517
+ result = {
518
+ status: 'ok',
519
+ version: version.trim()
520
+ };
521
+
522
+ // Version checking for critical components
523
+ if (check.minVersion && check.name === 'Node.js') {
524
+ const versionNum = version.trim().replace('v', '');
525
+ if (versionNum < check.minVersion) {
526
+ result = {
527
+ status: 'warning',
528
+ version: version.trim(),
529
+ warning: `Node.js ${check.minVersion}+ recommended, found ${versionNum}`
530
+ };
531
+ }
532
+ }
533
+ } else {
534
+ result = {
535
+ status: 'skipped',
536
+ version: 'N/A'
537
+ };
538
+ }
452
539
  results.push({
453
- name: check.name,
454
- status: 'ok',
455
- version: version.trim()
540
+ ...check,
541
+ ...result
456
542
  });
457
543
  } catch (error) {
458
544
  results.push({
459
- name: check.name,
545
+ ...check,
460
546
  status: 'failed',
461
547
  error: error.message
462
548
  });
@@ -11,7 +11,8 @@
11
11
  import chalk from 'chalk';
12
12
  import { askUser, askPassword, askChoice, closePrompts } from '../utils/interactive-prompts.js';
13
13
  import { ApiTokenManager } from '../security/api-token-manager.js';
14
- import { CloudflareAPI } from '../../../src/utils/cloudflare/api.js';
14
+ import { CloudflareAPI } from '@tamyla/clodo-framework/utils/cloudflare';
15
+ import { validatePrerequisites } from '../cloudflare/ops.js';
15
16
  export class DeploymentCredentialCollector {
16
17
  constructor(options = {}) {
17
18
  this.servicePath = options.servicePath || '.';
@@ -29,6 +30,10 @@ export class DeploymentCredentialCollector {
29
30
  * 4. Return complete credential set
30
31
  */
31
32
  async collectCredentials(options = {}) {
33
+ // First, check prerequisites
34
+ if (!this.quiet) {
35
+ await this.displayPrerequisites(options);
36
+ }
32
37
  const startCredentials = {
33
38
  token: options.token || process.env.CLOUDFLARE_API_TOKEN || null,
34
39
  accountId: options.accountId || process.env.CLOUDFLARE_ACCOUNT_ID || null,
@@ -36,6 +41,11 @@ export class DeploymentCredentialCollector {
36
41
  zoneName: null // Will be populated when fetching zone
37
42
  };
38
43
 
44
+ // Show credential status if not quiet
45
+ if (!this.quiet) {
46
+ this.displayCredentialStatus(startCredentials, options);
47
+ }
48
+
39
49
  // All credentials provided - quick path
40
50
  if (startCredentials.token && startCredentials.accountId && startCredentials.zoneId) {
41
51
  if (!this.quiet) {
@@ -247,5 +257,128 @@ export class DeploymentCredentialCollector {
247
257
  cleanup() {
248
258
  closePrompts();
249
259
  }
260
+
261
+ /**
262
+ * Display prerequisite check results
263
+ * @param {Object} options - Options for prerequisite checking
264
+ */
265
+ async displayPrerequisites(options = {}) {
266
+ console.log(chalk.cyan('\nšŸ”§ Deployment Prerequisites\n'));
267
+ try {
268
+ const results = await validatePrerequisites({
269
+ checkAuth: options.checkAuth || false,
270
+ checkNetwork: options.checkNetwork || false,
271
+ verbose: !this.quiet
272
+ });
273
+ let allOk = true;
274
+ let hasWarnings = false;
275
+ results.forEach(result => {
276
+ const icon = result.status === 'ok' ? 'āœ…' : result.status === 'warning' ? 'āš ļø' : result.status === 'failed' ? 'āŒ' : 'ā­ļø';
277
+ const color = result.status === 'ok' ? chalk.green : result.status === 'warning' ? chalk.yellow : result.status === 'failed' ? chalk.red : chalk.gray;
278
+ console.log(`${icon} ${color(result.name)}: ${result.version || 'N/A'}`);
279
+ if (result.description) {
280
+ console.log(` ${chalk.gray(result.description)}`);
281
+ }
282
+ if (result.warning) {
283
+ console.log(` ${chalk.yellow('āš ļø ' + result.warning)}`);
284
+ hasWarnings = true;
285
+ }
286
+ if (result.error) {
287
+ console.log(` ${chalk.red('āŒ ' + result.error)}`);
288
+ allOk = false;
289
+ }
290
+ if (result.status === 'failed' && result.required) {
291
+ console.log(` ${chalk.red('šŸ’” Required for deployment - please install/update ' + result.name.toLowerCase())}`);
292
+ }
293
+ });
294
+ console.log('');
295
+
296
+ // Summary
297
+ if (allOk && !hasWarnings) {
298
+ console.log(chalk.green('šŸŽ‰ All prerequisites met - ready for deployment!\n'));
299
+ } else if (allOk && hasWarnings) {
300
+ console.log(chalk.yellow('āš ļø Prerequisites OK but with warnings - deployment may proceed\n'));
301
+ } else {
302
+ console.log(chalk.red('āŒ Some required prerequisites are missing - please resolve before deploying\n'));
303
+ }
304
+ } catch (error) {
305
+ console.log(chalk.yellow(`āš ļø Could not check prerequisites: ${error.message}\n`));
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Display credential status showing what's available vs missing
311
+ * @param {Object} credentials - Current credential state
312
+ * @param {Object} options - CLI options
313
+ */
314
+ displayCredentialStatus(credentials, options) {
315
+ console.log(chalk.cyan('\nšŸ” Credential Status\n'));
316
+ const status = {
317
+ token: {
318
+ available: !!credentials.token,
319
+ source: this.getCredentialSource('token', credentials.token, options.token),
320
+ required: true
321
+ },
322
+ accountId: {
323
+ available: !!credentials.accountId,
324
+ source: this.getCredentialSource('accountId', credentials.accountId, options.accountId),
325
+ required: false
326
+ },
327
+ zoneId: {
328
+ available: !!credentials.zoneId,
329
+ source: this.getCredentialSource('zoneId', credentials.zoneId, options.zoneId),
330
+ required: false
331
+ }
332
+ };
333
+
334
+ // Display each credential
335
+ Object.entries(status).forEach(([key, info]) => {
336
+ const icon = info.available ? 'āœ…' : 'āŒ';
337
+ const color = info.available ? chalk.green : chalk.red;
338
+ const label = key === 'token' ? 'API Token' : key === 'accountId' ? 'Account ID' : 'Zone ID';
339
+ const envVar = key === 'token' ? 'CLOUDFLARE_API_TOKEN' : key === 'accountId' ? 'CLOUDFLARE_ACCOUNT_ID' : 'CLOUDFLARE_ZONE_ID';
340
+ console.log(`${icon} ${color(label)}: ${info.available ? info.source : 'Missing'}`);
341
+ if (!info.available) {
342
+ console.log(` ${chalk.gray(`Set via --${key.replace('Id', '-id')} flag or ${envVar} environment variable`)}`);
343
+ console.log(` ${chalk.gray('To set environment variable:')}`);
344
+ console.log(` ${chalk.gray(` Windows PowerShell: $env:${envVar}="your-value-here"`)}`);
345
+ console.log(` ${chalk.gray(` Windows CMD: set ${envVar}=your-value-here`)}`);
346
+ console.log(` ${chalk.gray(` Linux/macOS: export ${envVar}="your-value-here"`)}`);
347
+ console.log(` ${chalk.gray(` Or add to .env file: ${envVar}=your-value-here`)}`);
348
+ console.log('');
349
+ }
350
+ });
351
+ console.log('');
352
+
353
+ // Summary
354
+ const availableCount = Object.values(status).filter(s => s.available).length;
355
+ const totalCount = Object.keys(status).length;
356
+ if (availableCount === totalCount) {
357
+ console.log(chalk.green('šŸŽ‰ All credentials available - ready for deployment!\n'));
358
+ } else if (availableCount >= 1) {
359
+ console.log(chalk.yellow(`āš ļø ${availableCount}/${totalCount} credentials available. Missing credentials will be fetched interactively.\n`));
360
+ } else {
361
+ console.log(chalk.red('āŒ No credentials found. Interactive collection required.\n'));
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Get the source of a credential (flag, env var, or none)
367
+ * @param {string} key - Credential key
368
+ * @param {*} value - Credential value
369
+ * @param {*} flagValue - CLI flag value
370
+ * @returns {string} Source description
371
+ */
372
+ getCredentialSource(key, value, flagValue) {
373
+ if (!value) return 'Not set';
374
+
375
+ // Check if it came from CLI flag
376
+ if (flagValue) return 'CLI flag';
377
+
378
+ // Check if it came from environment variable
379
+ const envVar = key === 'token' ? 'CLOUDFLARE_API_TOKEN' : key === 'accountId' ? 'CLOUDFLARE_ACCOUNT_ID' : 'CLOUDFLARE_ZONE_ID';
380
+ if (process.env[envVar]) return 'Environment variable';
381
+ return 'Unknown source';
382
+ }
250
383
  }
251
384
  export default DeploymentCredentialCollector;