@tamyla/clodo-framework 3.1.21 → 3.1.23
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 +17 -0
- package/README.md +283 -1
- package/dist/{bin → cli}/clodo-service.js +47 -15
- package/dist/cli/commands/assess.js +183 -0
- package/dist/{bin → cli}/commands/create.js +5 -5
- package/dist/{bin → cli}/commands/deploy.js +122 -90
- package/dist/{bin → cli}/commands/diagnose.js +5 -5
- package/dist/cli/commands/helpers/deployment-ui.js +138 -0
- package/dist/cli/commands/helpers/deployment-verification.js +250 -0
- package/dist/cli/commands/helpers/error-recovery.js +80 -0
- package/dist/cli/commands/helpers/resource-detection.js +113 -0
- package/dist/{bin → cli}/commands/helpers.js +0 -28
- package/dist/cli/commands/init-config.js +57 -0
- package/dist/{bin → cli}/commands/update.js +5 -5
- package/dist/{bin → cli}/commands/validate.js +5 -5
- package/dist/cli/security-cli.js +118 -0
- package/dist/config/FeatureManager.js +6 -0
- package/dist/config/clodo-create.example.json +26 -0
- package/dist/config/clodo-deploy.example.json +41 -0
- package/dist/config/clodo-update.example.json +46 -0
- package/dist/config/clodo-validate.example.json +41 -0
- package/dist/config/customers/template/development.env.template +37 -0
- package/dist/config/customers/template/production.env.template +39 -0
- package/dist/config/customers/template/staging.env.template +37 -0
- package/dist/config/customers.js +28 -26
- package/dist/config/domain-examples/README.md +464 -0
- package/dist/config/domain-examples/environment-mapped.json +168 -0
- package/dist/config/domain-examples/multi-domain.json +144 -0
- package/dist/config/domain-examples/single-domain.json +50 -0
- package/dist/config/examples +12 -0
- package/dist/config/features.js +61 -0
- package/dist/config/staging-deployment.json +60 -0
- package/dist/config/validation-config.json +347 -0
- package/dist/deployment/wrangler-deployer.js +1 -1
- package/dist/{bin → lib}/deployment/modules/DeploymentOrchestrator.js +2 -2
- package/dist/{bin → lib}/deployment/modules/EnvironmentManager.js +2 -2
- package/dist/lib/deployment/orchestration/EnterpriseOrchestrator.js +21 -0
- package/dist/lib/shared/cache/configuration-cache.js +82 -0
- package/dist/{bin → lib}/shared/cloudflare/domain-discovery.js +1 -1
- package/dist/{bin → lib}/shared/cloudflare/domain-manager.js +1 -1
- package/dist/{bin → lib}/shared/cloudflare/index.js +1 -1
- package/dist/{bin → lib}/shared/cloudflare/ops.js +10 -8
- package/dist/{bin → lib}/shared/config/ConfigurationManager.js +23 -1
- package/dist/{bin → lib}/shared/config/command-config-manager.js +19 -3
- package/dist/{bin → lib}/shared/config/index.js +1 -1
- package/dist/{bin → lib}/shared/deployment/credential-collector.js +30 -7
- package/dist/lib/shared/deployment/index.js +10 -0
- package/dist/lib/shared/deployment/rollback-manager.js +7 -0
- package/dist/lib/shared/deployment/utilities/d1-error-recovery.js +177 -0
- package/dist/{bin → lib}/shared/deployment/validator.js +40 -10
- package/dist/lib/shared/deployment/workflows/deployment-summary.js +214 -0
- package/dist/lib/shared/deployment/workflows/interactive-confirmation.js +188 -0
- package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +234 -0
- package/dist/lib/shared/deployment/workflows/interactive-domain-info-gatherer.js +240 -0
- package/dist/lib/shared/deployment/workflows/interactive-secret-workflow.js +228 -0
- package/dist/lib/shared/deployment/workflows/interactive-testing-workflow.js +235 -0
- package/dist/lib/shared/deployment/workflows/interactive-validation.js +218 -0
- package/dist/lib/shared/error-handling/error-classifier.js +46 -0
- package/dist/{bin → lib}/shared/monitoring/health-checker.js +129 -1
- package/dist/{bin → lib}/shared/monitoring/memory-manager.js +17 -6
- package/dist/{bin → lib}/shared/routing/domain-router.js +1 -1
- package/dist/lib/shared/utils/deployment-validator.js +97 -0
- package/dist/{bin → lib}/shared/utils/formatters.js +10 -0
- package/dist/{bin → lib}/shared/utils/index.js +13 -1
- package/dist/{bin → lib}/shared/utils/interactive-prompts.js +34 -18
- package/dist/{bin → lib}/shared/utils/progress-manager.js +2 -2
- package/dist/lib/shared/utils/progress-spinner.js +53 -0
- package/dist/lib/shared/utils/sensitive-redactor.js +91 -0
- package/dist/{bin → lib}/shared/validation/ValidationRegistry.js +1 -1
- package/dist/migration/MigrationAdapters.js +50 -4
- package/dist/orchestration/cross-domain-coordinator.js +5 -5
- package/dist/orchestration/multi-domain-orchestrator.js +63 -22
- package/dist/security/index.js +2 -2
- package/dist/security/patterns/insecure-patterns.js +1 -1
- package/dist/service-management/ConfirmationEngine.js +1 -1
- package/dist/service-management/ErrorTracker.js +1 -1
- package/dist/service-management/InputCollector.js +1 -1
- package/dist/service-management/ServiceCreator.js +11 -255
- package/dist/service-management/ServiceOrchestrator.js +0 -2
- package/dist/service-management/generators/testing/UnitTestsGenerator.js +4 -4
- package/dist/service-management/index.js +1 -1
- package/dist/utils/cloudflare/ops.js +1 -1
- package/dist/utils/constants.js +102 -0
- package/dist/utils/deployment/wrangler-config-manager.js +215 -48
- package/dist/utils/file-manager.js +1 -1
- package/dist/utils/formatters.js +1 -1
- package/dist/utils/framework-config.js +2 -2
- package/dist/utils/interactive-prompts.js +10 -59
- package/dist/utils/logger.js +1 -1
- package/dist/version/VersionDetector.js +99 -9
- package/dist/worker/integration.js +1 -1
- package/package.json +10 -10
- package/dist/bin/clodo-service-old.js +0 -868
- package/dist/bin/clodo-service-test.js +0 -10
- package/dist/bin/commands/assess.js +0 -91
- package/dist/bin/database/enterprise-db-manager.js +0 -457
- package/dist/bin/deployment/enterprise-deploy.js +0 -877
- package/dist/bin/deployment/master-deploy.js +0 -1376
- package/dist/bin/deployment/modular-enterprise-deploy.js +0 -466
- package/dist/bin/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/bin/deployment/test-interactive-utils.js +0 -66
- package/dist/bin/portfolio/portfolio-manager.js +0 -487
- package/dist/bin/security/security-cli.js +0 -108
- package/dist/bin/service-management/create-service.js +0 -122
- package/dist/bin/service-management/init-service.js +0 -79
- package/dist/bin/shared/deployment/index.js +0 -10
- package/dist/bin/shared/deployment/rollback-manager.js +0 -523
- package/dist/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/service-management/ServiceInitializer.js +0 -453
- /package/dist/{bin → lib}/database/deployment-db-manager.js +0 -0
- /package/dist/{bin → lib}/database/wrangler-d1-manager.js +0 -0
- /package/dist/{bin → lib}/deployment/modules/DeploymentConfiguration.js +0 -0
- /package/dist/{bin → lib}/deployment/modules/MonitoringIntegration.js +0 -0
- /package/dist/{bin → lib}/deployment/modules/ValidationManager.js +0 -0
- /package/dist/{bin → lib}/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -0
- /package/dist/{bin → lib}/deployment/orchestration/PortfolioOrchestrator.js +0 -0
- /package/dist/{bin → lib}/deployment/orchestration/SingleServiceOrchestrator.js +0 -0
- /package/dist/{bin → lib}/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -0
- /package/dist/{bin → lib}/shared/config/cache.js +0 -0
- /package/dist/{bin → lib}/shared/config/cloudflare-service-validator.js +0 -0
- /package/dist/{bin → lib}/shared/config/manager.js +0 -0
- /package/dist/{bin → lib}/shared/config/manifest-loader.js +0 -0
- /package/dist/{bin → lib}/shared/database/connection-manager.js +0 -0
- /package/dist/{bin → lib}/shared/database/index.js +0 -0
- /package/dist/{bin → lib}/shared/database/orchestrator.js +0 -0
- /package/dist/{bin → lib}/shared/deployment/auditor.js +0 -0
- /package/dist/{bin → lib}/shared/index.js +0 -0
- /package/dist/{bin → lib}/shared/logging/Logger.js +0 -0
- /package/dist/{bin → lib}/shared/monitoring/index.js +0 -0
- /package/dist/{bin → lib}/shared/monitoring/production-monitor.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/api-tester.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/auth-tester.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/core.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/database-tester.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/index.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/load-tester.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/performance-tester.js +0 -0
- /package/dist/{bin → lib}/shared/security/api-token-manager.js +0 -0
- /package/dist/{bin → lib}/shared/security/index.js +0 -0
- /package/dist/{bin → lib}/shared/security/secret-generator.js +0 -0
- /package/dist/{bin → lib}/shared/security/secure-token-manager.js +0 -0
- /package/dist/{bin → lib}/shared/utils/ErrorHandler.js +0 -0
- /package/dist/{bin → lib}/shared/utils/cli-options.js +0 -0
- /package/dist/{bin → lib}/shared/utils/config-loader.js +0 -0
- /package/dist/{bin → lib}/shared/utils/error-recovery.js +0 -0
- /package/dist/{bin → lib}/shared/utils/file-manager.js +0 -0
- /package/dist/{bin → lib}/shared/utils/graceful-shutdown-manager.js +0 -0
- /package/dist/{bin → lib}/shared/utils/interactive-utils.js +0 -0
- /package/dist/{bin → lib}/shared/utils/output-formatter.js +0 -0
- /package/dist/{bin → lib}/shared/utils/rate-limiter.js +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Validation Workflow Module
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable interactive validation workflows.
|
|
5
|
+
* Extracted from enterprise-deployment/master-deploy.js for modularity.
|
|
6
|
+
*
|
|
7
|
+
* @module interactive-validation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { askYesNo } from '../utils/interactive-prompts.js';
|
|
11
|
+
import { validatePrerequisites, checkAuth, authenticate, workerExists } from '../cloudflare/ops.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Interactive Validation Workflow
|
|
15
|
+
* Handles pre-deployment checks and comprehensive validation
|
|
16
|
+
*/
|
|
17
|
+
export class InteractiveValidation {
|
|
18
|
+
/**
|
|
19
|
+
* @param {Object} options - Configuration options
|
|
20
|
+
*/
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.interactive = options.interactive !== false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Execute pre-deployment checks
|
|
27
|
+
*
|
|
28
|
+
* @param {Object} config - Deployment configuration
|
|
29
|
+
* @returns {Promise<Object>} Validation results
|
|
30
|
+
*/
|
|
31
|
+
async executePreDeploymentChecks(config) {
|
|
32
|
+
console.log('\n✅ Pre-deployment Validation');
|
|
33
|
+
console.log('============================');
|
|
34
|
+
|
|
35
|
+
// Check prerequisites
|
|
36
|
+
await this.validatePrerequisites();
|
|
37
|
+
|
|
38
|
+
// Check authentication
|
|
39
|
+
await this.validateAuthentication();
|
|
40
|
+
|
|
41
|
+
// Check for existing deployments
|
|
42
|
+
await this.checkExistingDeployments(config);
|
|
43
|
+
console.log('\n✅ All pre-deployment checks passed');
|
|
44
|
+
return {
|
|
45
|
+
prerequisites: true,
|
|
46
|
+
authentication: true,
|
|
47
|
+
existingDeployments: false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate system prerequisites
|
|
53
|
+
*
|
|
54
|
+
* @returns {Promise<void>}
|
|
55
|
+
*/
|
|
56
|
+
async validatePrerequisites() {
|
|
57
|
+
console.log('\n🔍 Checking prerequisites...');
|
|
58
|
+
const prereqs = await validatePrerequisites();
|
|
59
|
+
for (const prereq of prereqs) {
|
|
60
|
+
if (prereq.status === 'ok') {
|
|
61
|
+
console.log(` ✅ ${prereq.name}: ${prereq.version}`);
|
|
62
|
+
} else {
|
|
63
|
+
throw new Error(`${prereq.name} is not available: ${prereq.error}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate Cloudflare authentication
|
|
70
|
+
*
|
|
71
|
+
* @returns {Promise<void>}
|
|
72
|
+
*/
|
|
73
|
+
async validateAuthentication() {
|
|
74
|
+
console.log('\n🔐 Checking Cloudflare authentication...');
|
|
75
|
+
const isAuthenticated = await checkAuth();
|
|
76
|
+
if (!isAuthenticated) {
|
|
77
|
+
if (!this.interactive) {
|
|
78
|
+
throw new Error('Cloudflare authentication required but running in non-interactive mode');
|
|
79
|
+
}
|
|
80
|
+
const shouldAuthenticate = await askYesNo('Cloudflare authentication required. Run authentication now?', 'y');
|
|
81
|
+
if (shouldAuthenticate) {
|
|
82
|
+
console.log('\n🔑 Please complete Cloudflare authentication...');
|
|
83
|
+
await authenticate();
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error('Cloudflare authentication is required for deployment');
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
console.log(' ✅ Cloudflare: Authenticated');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check for existing deployments
|
|
94
|
+
*
|
|
95
|
+
* @param {Object} config - Deployment configuration
|
|
96
|
+
* @returns {Promise<boolean>} True if existing deployment found and should continue
|
|
97
|
+
*/
|
|
98
|
+
async checkExistingDeployments(config) {
|
|
99
|
+
console.log('\n🔍 Checking for existing deployments...');
|
|
100
|
+
const workerExistsAlready = await workerExists(config.worker.name);
|
|
101
|
+
if (workerExistsAlready) {
|
|
102
|
+
console.log(` ⚠️ Worker '${config.worker.name}' already exists`);
|
|
103
|
+
if (!this.interactive) {
|
|
104
|
+
console.log(' ℹ️ Non-interactive mode: will overwrite existing worker');
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const shouldOverwrite = await askYesNo('Do you want to overwrite the existing worker?', 'n');
|
|
108
|
+
if (!shouldOverwrite) {
|
|
109
|
+
throw new Error('Deployment cancelled - worker already exists');
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
} else {
|
|
113
|
+
console.log(` ✅ Worker name '${config.worker.name}' is available`);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Execute comprehensive validation
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} config - Deployment configuration
|
|
122
|
+
* @param {Object} validationManager - ValidationManager instance
|
|
123
|
+
* @returns {Promise<Object>} Validation results
|
|
124
|
+
*/
|
|
125
|
+
async executeComprehensiveValidation(config, validationManager) {
|
|
126
|
+
console.log('\n🔍 Comprehensive Validation');
|
|
127
|
+
console.log('==========================');
|
|
128
|
+
try {
|
|
129
|
+
const validationResult = await validationManager.validateDeploymentConfiguration({
|
|
130
|
+
domain: config.domain,
|
|
131
|
+
environment: config.environment,
|
|
132
|
+
worker: config.worker,
|
|
133
|
+
database: config.database,
|
|
134
|
+
secrets: config.secrets,
|
|
135
|
+
comprehensive: true
|
|
136
|
+
});
|
|
137
|
+
if (validationResult.valid) {
|
|
138
|
+
console.log(' ✅ Configuration validation passed');
|
|
139
|
+
if (validationResult.warnings?.length > 0) {
|
|
140
|
+
console.log(` ⚠️ ${validationResult.warnings.length} warnings found`);
|
|
141
|
+
for (const warning of validationResult.warnings) {
|
|
142
|
+
console.log(` - ${warning}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
console.log(' ❌ Configuration validation failed');
|
|
147
|
+
for (const error of validationResult.errors) {
|
|
148
|
+
console.log(` - ${error}`);
|
|
149
|
+
}
|
|
150
|
+
throw new Error('Configuration validation failed');
|
|
151
|
+
}
|
|
152
|
+
return validationResult;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.log(` ❌ Validation error: ${error.message}`);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Validate configuration object
|
|
161
|
+
*
|
|
162
|
+
* @param {Object} config - Configuration to validate
|
|
163
|
+
* @returns {Promise<Object>} Validation results
|
|
164
|
+
*/
|
|
165
|
+
async validateConfiguration(config) {
|
|
166
|
+
const errors = [];
|
|
167
|
+
const warnings = [];
|
|
168
|
+
|
|
169
|
+
// Domain validation
|
|
170
|
+
if (!config.domain || config.domain.length === 0) {
|
|
171
|
+
errors.push('Domain name is required');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Environment validation
|
|
175
|
+
if (!config.environment) {
|
|
176
|
+
errors.push('Environment is required');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Worker validation
|
|
180
|
+
if (!config.worker?.name) {
|
|
181
|
+
errors.push('Worker name is required');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Database validation
|
|
185
|
+
if (config.database?.name && !config.database?.id) {
|
|
186
|
+
warnings.push('Database name specified but no database ID');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Secrets validation
|
|
190
|
+
if (Object.keys(config.secrets?.keys || {}).length === 0) {
|
|
191
|
+
warnings.push('No secrets configured');
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
valid: errors.length === 0,
|
|
195
|
+
errors,
|
|
196
|
+
warnings
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get validation summary
|
|
202
|
+
*
|
|
203
|
+
* @param {Object} validationResult - Validation result
|
|
204
|
+
* @returns {string} Summary message
|
|
205
|
+
*/
|
|
206
|
+
getValidationSummary(validationResult) {
|
|
207
|
+
if (validationResult.valid) {
|
|
208
|
+
const warningCount = validationResult.warnings?.length || 0;
|
|
209
|
+
if (warningCount > 0) {
|
|
210
|
+
return `✅ Validation passed with ${warningCount} warning(s)`;
|
|
211
|
+
}
|
|
212
|
+
return '✅ Validation passed';
|
|
213
|
+
} else {
|
|
214
|
+
const errorCount = validationResult.errors?.length || 0;
|
|
215
|
+
return `❌ Validation failed with ${errorCount} error(s)`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Classification and Recovery Module
|
|
3
|
+
* Provides intelligent error classification for contextual recovery suggestions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Classify error type for contextual recovery suggestions
|
|
8
|
+
* @param {Error} error - The error to classify
|
|
9
|
+
* @returns {string} Error type classification
|
|
10
|
+
*/
|
|
11
|
+
export function classifyError(error) {
|
|
12
|
+
const msg = error.message.toLowerCase();
|
|
13
|
+
if (msg.includes('credential') || msg.includes('auth') || msg.includes('token') || msg.includes('unauthorized') || msg.includes('forbidden')) {
|
|
14
|
+
return 'credentials';
|
|
15
|
+
}
|
|
16
|
+
if (msg.includes('domain') || msg.includes('zone') || msg.includes('dns')) {
|
|
17
|
+
return 'domain';
|
|
18
|
+
}
|
|
19
|
+
if (msg.includes('network') || msg.includes('timeout') || msg.includes('econnrefused') || msg.includes('enotfound') || msg.includes('fetch failed')) {
|
|
20
|
+
return 'network';
|
|
21
|
+
}
|
|
22
|
+
if (msg.includes('bundle') || msg.includes('syntax') || msg.includes('compile') || msg.includes('build') || msg.includes('module not found')) {
|
|
23
|
+
return 'bundle';
|
|
24
|
+
}
|
|
25
|
+
if (msg.includes('database') || msg.includes('d1') || msg.includes('migration') || msg.includes('sql')) {
|
|
26
|
+
return 'database';
|
|
27
|
+
}
|
|
28
|
+
return 'unknown';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get recovery suggestions for an error type
|
|
33
|
+
* @param {string} errorType - The classified error type
|
|
34
|
+
* @returns {Array<string>} Array of suggestion strings
|
|
35
|
+
*/
|
|
36
|
+
export function getRecoverySuggestions(errorType) {
|
|
37
|
+
const suggestions = {
|
|
38
|
+
credentials: ['Check your API token, account ID, and zone ID', 'Verify token has not expired', 'Visit: https://dash.cloudflare.com/profile/api-tokens'],
|
|
39
|
+
domain: ['Verify domain exists in Cloudflare dashboard', 'Check API token has zone:read permissions', 'Ensure zone ID matches the domain'],
|
|
40
|
+
network: ['Check internet connectivity', 'Verify Cloudflare API is accessible', 'Check for firewall/proxy issues'],
|
|
41
|
+
bundle: ['Check for syntax errors in your code', 'Verify all dependencies are installed', 'Run: npm install or yarn install'],
|
|
42
|
+
database: ['Verify database exists in Cloudflare', 'Check migrations are valid SQL', 'Ensure D1 binding name matches wrangler.toml'],
|
|
43
|
+
unknown: ['Check the error message for details', 'Run with DEBUG=1 for full stack trace', 'Review deployment logs']
|
|
44
|
+
};
|
|
45
|
+
return suggestions[errorType] || suggestions.unknown;
|
|
46
|
+
}
|
|
@@ -14,7 +14,7 @@ const execAsync = promisify(exec);
|
|
|
14
14
|
// Load framework configuration
|
|
15
15
|
const {
|
|
16
16
|
frameworkConfig
|
|
17
|
-
} = await import('
|
|
17
|
+
} = await import('../../utils/framework-config.js');
|
|
18
18
|
const timing = frameworkConfig.getTiming();
|
|
19
19
|
function makeHttpRequest(url, method = 'GET', timeout = 5000) {
|
|
20
20
|
return new Promise((resolve, reject) => {
|
|
@@ -447,4 +447,132 @@ export function formatD1HealthResults(d1Results) {
|
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
449
|
return lines.join('\n');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Modern fetch-based health check with exponential backoff
|
|
454
|
+
* @param {string} baseUrl - Base URL of the service
|
|
455
|
+
* @param {Object} options - Health check options
|
|
456
|
+
* @returns {Promise<Object>} Health check result
|
|
457
|
+
*/
|
|
458
|
+
export async function healthCheckWithBackoff(baseUrl, options = {}) {
|
|
459
|
+
const {
|
|
460
|
+
maxAttempts = 10,
|
|
461
|
+
initialDelay = 1000,
|
|
462
|
+
maxDelay = 8000,
|
|
463
|
+
timeout = 5000,
|
|
464
|
+
silent = false
|
|
465
|
+
} = options;
|
|
466
|
+
let attempt = 0;
|
|
467
|
+
let delay = initialDelay;
|
|
468
|
+
while (attempt < maxAttempts) {
|
|
469
|
+
attempt++;
|
|
470
|
+
try {
|
|
471
|
+
if (!silent) {
|
|
472
|
+
process.stdout.write(` Attempt ${attempt}/${maxAttempts}... `);
|
|
473
|
+
}
|
|
474
|
+
const controller = new AbortController();
|
|
475
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
476
|
+
const startTime = Date.now();
|
|
477
|
+
const response = await fetch(`${baseUrl}/health`, {
|
|
478
|
+
signal: controller.signal,
|
|
479
|
+
headers: {
|
|
480
|
+
'User-Agent': 'clodo-service-health-check'
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
clearTimeout(timeoutId);
|
|
484
|
+
const responseTime = Date.now() - startTime;
|
|
485
|
+
if (response.ok) {
|
|
486
|
+
// Success!
|
|
487
|
+
let status = null;
|
|
488
|
+
try {
|
|
489
|
+
const data = await response.json();
|
|
490
|
+
status = data.status || 'healthy';
|
|
491
|
+
} catch {
|
|
492
|
+
// Not JSON, that's ok
|
|
493
|
+
}
|
|
494
|
+
if (!silent) console.log('✓');
|
|
495
|
+
return {
|
|
496
|
+
healthy: true,
|
|
497
|
+
responseTime,
|
|
498
|
+
attempts: attempt,
|
|
499
|
+
status
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Non-200 but got response
|
|
504
|
+
if (!silent) console.log(`✗ (HTTP ${response.status})`);
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (!silent) {
|
|
507
|
+
if (error.name === 'AbortError') {
|
|
508
|
+
console.log('✗ (timeout)');
|
|
509
|
+
} else {
|
|
510
|
+
console.log(`✗ (${error.message})`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Wait before retry (exponential backoff)
|
|
516
|
+
if (attempt < maxAttempts) {
|
|
517
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
518
|
+
delay = Math.min(delay * 2, maxDelay); // Exponential backoff, capped
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
healthy: false,
|
|
523
|
+
attempts: maxAttempts
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Verify worker deployment via Cloudflare API
|
|
529
|
+
* @param {string} workerName - Name of the worker to verify
|
|
530
|
+
* @param {Object} credentials - Cloudflare credentials
|
|
531
|
+
* @param {Object} options - Verification options
|
|
532
|
+
* @returns {Promise<Object>} Verification result
|
|
533
|
+
*/
|
|
534
|
+
export async function verifyWorkerDeployment(workerName, credentials, options = {}) {
|
|
535
|
+
const {
|
|
536
|
+
maxAttempts = 5,
|
|
537
|
+
delay = 2000,
|
|
538
|
+
silent = false
|
|
539
|
+
} = options;
|
|
540
|
+
let attempt = 0;
|
|
541
|
+
while (attempt < maxAttempts) {
|
|
542
|
+
attempt++;
|
|
543
|
+
try {
|
|
544
|
+
if (!silent) {
|
|
545
|
+
process.stdout.write(` Checking deployment status (attempt ${attempt}/${maxAttempts})... `);
|
|
546
|
+
}
|
|
547
|
+
const {
|
|
548
|
+
CloudflareAPI
|
|
549
|
+
} = await import('../../utils/cloudflare/api.js');
|
|
550
|
+
const cfApi = new CloudflareAPI(credentials.token);
|
|
551
|
+
|
|
552
|
+
// List all workers to find ours
|
|
553
|
+
const workers = await cfApi.listWorkers(credentials.accountId);
|
|
554
|
+
const deployedWorker = workers.find(w => w.name === workerName);
|
|
555
|
+
if (deployedWorker) {
|
|
556
|
+
if (!silent) console.log('✓');
|
|
557
|
+
return {
|
|
558
|
+
success: true,
|
|
559
|
+
status: 'Active',
|
|
560
|
+
modifiedOn: deployedWorker.modifiedOn ? new Date(deployedWorker.modifiedOn).toLocaleString() : null,
|
|
561
|
+
etag: deployedWorker.etag
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
if (!silent) console.log('Not found yet');
|
|
565
|
+
} catch (error) {
|
|
566
|
+
if (!silent) console.log(`✗ (${error.message})`);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Wait before retry
|
|
570
|
+
if (attempt < maxAttempts) {
|
|
571
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
success: false,
|
|
576
|
+
error: 'Worker not found in Cloudflare API after deployment'
|
|
577
|
+
};
|
|
450
578
|
}
|
|
@@ -46,7 +46,11 @@ export class MemoryManager {
|
|
|
46
46
|
|
|
47
47
|
// Register process cleanup handlers
|
|
48
48
|
this.registerProcessHandlers();
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
// Only log if verbose mode or DEBUG is enabled
|
|
51
|
+
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
52
|
+
console.log('🧠 Memory monitoring started');
|
|
53
|
+
}
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
/**
|
|
@@ -87,16 +91,23 @@ export class MemoryManager {
|
|
|
87
91
|
// Check memory thresholds
|
|
88
92
|
const heapUsagePercent = memUsage.heapUsed / memUsage.heapTotal;
|
|
89
93
|
if (heapUsagePercent > this.config.memoryThreshold) {
|
|
90
|
-
|
|
94
|
+
// Only warn if verbose/debug mode or if usage is critically high (>95%)
|
|
95
|
+
if (process.env.DEBUG || process.env.VERBOSE || heapUsagePercent > 0.95) {
|
|
96
|
+
console.warn(`⚠️ High memory usage detected: ${(heapUsagePercent * 100).toFixed(1)}%`);
|
|
97
|
+
}
|
|
91
98
|
|
|
92
99
|
// Force garbage collection if available
|
|
93
|
-
if (global.gc) {
|
|
94
|
-
|
|
100
|
+
if (global.gc && heapUsagePercent > 0.90) {
|
|
101
|
+
if (process.env.DEBUG) {
|
|
102
|
+
console.log('🗑️ Running forced garbage collection');
|
|
103
|
+
}
|
|
95
104
|
global.gc();
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
// Run cleanup routines
|
|
99
|
-
|
|
107
|
+
// Run cleanup routines only if critically high
|
|
108
|
+
if (heapUsagePercent > 0.90) {
|
|
109
|
+
this.runCleanupRoutines();
|
|
110
|
+
}
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
// Check for memory leaks
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { existsSync, readFileSync } from 'fs';
|
|
16
16
|
import { resolve } from 'path';
|
|
17
|
-
import { MultiDomainOrchestrator } from
|
|
17
|
+
import { MultiDomainOrchestrator } from '../../orchestration/multi-domain-orchestrator.js';
|
|
18
18
|
export class DomainRouter {
|
|
19
19
|
constructor(options = {}) {
|
|
20
20
|
this.configPath = options.configPath || './config/domains.json';
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment Validator Utility
|
|
3
|
+
* Pre-deployment validation and prerequisite checking
|
|
4
|
+
* Extracted from clodo-service-old.js for modular reuse
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate deployment prerequisites before proceeding
|
|
13
|
+
* @param {Object} coreInputs - Core deployment inputs
|
|
14
|
+
* @param {Object} options - Deployment options
|
|
15
|
+
* @returns {Promise<boolean>} True if all prerequisites are valid
|
|
16
|
+
*/
|
|
17
|
+
export async function validateDeploymentPrerequisites(coreInputs, options) {
|
|
18
|
+
const issues = [];
|
|
19
|
+
console.log(chalk.cyan('\n🔍 Pre-deployment Validation'));
|
|
20
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
21
|
+
|
|
22
|
+
// Check required fields
|
|
23
|
+
if (!coreInputs.customer) {
|
|
24
|
+
issues.push('Customer name is required');
|
|
25
|
+
}
|
|
26
|
+
if (!coreInputs.environment) {
|
|
27
|
+
issues.push('Environment is required');
|
|
28
|
+
}
|
|
29
|
+
if (!coreInputs.domainName) {
|
|
30
|
+
issues.push('Domain name is required');
|
|
31
|
+
}
|
|
32
|
+
if (!coreInputs.cloudflareToken) {
|
|
33
|
+
issues.push('Cloudflare API token is required');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check Cloudflare token format (basic validation)
|
|
37
|
+
if (coreInputs.cloudflareToken && !coreInputs.cloudflareToken.startsWith('CLOUDFLARE_API_TOKEN=')) {
|
|
38
|
+
if (coreInputs.cloudflareToken.length < 40) {
|
|
39
|
+
issues.push('Cloudflare API token appears to be invalid (too short)');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if service path exists
|
|
44
|
+
if (options.servicePath && options.servicePath !== '.') {
|
|
45
|
+
if (!existsSync(options.servicePath)) {
|
|
46
|
+
issues.push(`Service path does not exist: ${options.servicePath}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check for wrangler.toml if not dry run
|
|
51
|
+
if (!options.dryRun) {
|
|
52
|
+
const wranglerPath = join(options.servicePath || '.', 'wrangler.toml');
|
|
53
|
+
if (!existsSync(wranglerPath)) {
|
|
54
|
+
issues.push('wrangler.toml not found in service directory');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Report issues
|
|
59
|
+
if (issues.length > 0) {
|
|
60
|
+
console.log(chalk.red('\n❌ Validation Failed:'));
|
|
61
|
+
issues.forEach(issue => {
|
|
62
|
+
console.log(chalk.red(` • ${issue}`));
|
|
63
|
+
});
|
|
64
|
+
console.log(chalk.gray('\n─'.repeat(40)));
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.green('✅ All prerequisites validated'));
|
|
68
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate basic deployment inputs
|
|
74
|
+
* @param {Object} inputs - Input object to validate
|
|
75
|
+
* @param {Array<string>} requiredFields - Required field names
|
|
76
|
+
* @returns {Object} Validation result { valid, errors }
|
|
77
|
+
*/
|
|
78
|
+
export function validateDeploymentInputs(inputs, requiredFields = ['customer', 'environment', 'domainName', 'cloudflareToken']) {
|
|
79
|
+
const result = {
|
|
80
|
+
valid: true,
|
|
81
|
+
errors: []
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Handle null/undefined inputs
|
|
85
|
+
if (!inputs) {
|
|
86
|
+
result.errors.push('Input object is required');
|
|
87
|
+
result.valid = false;
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
requiredFields.forEach(field => {
|
|
91
|
+
if (!inputs[field]) {
|
|
92
|
+
result.errors.push(`${field} is required`);
|
|
93
|
+
result.valid = false;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
@@ -118,6 +118,16 @@ export const ResourceFormatters = {
|
|
|
118
118
|
*/
|
|
119
119
|
configKey(camelCase) {
|
|
120
120
|
return NameFormatters.toKebabCase(camelCase);
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* Format package name for NPM
|
|
124
|
+
* Example: 'my-service' → 'my-service'
|
|
125
|
+
*/
|
|
126
|
+
packageName(serviceName) {
|
|
127
|
+
if (!serviceName) return '';
|
|
128
|
+
// For NPM packages, just use the service name as-is
|
|
129
|
+
// Scoped packages would be handled separately (e.g., @company/my-service)
|
|
130
|
+
return serviceName;
|
|
121
131
|
}
|
|
122
132
|
};
|
|
123
133
|
|
|
@@ -16,4 +16,16 @@ export { GracefulShutdownManager, getShutdownManager, initializeGracefulShutdown
|
|
|
16
16
|
export { executeWithRateLimit, queueRequest, getRateLimitStatus, clearQueues, RATE_LIMITS } from './rate-limiter.js';
|
|
17
17
|
|
|
18
18
|
// Unified error handling module (Phase 3.2.3d - consolidated from 5 sources)
|
|
19
|
-
export { default as ErrorHandler, createErrorResponse, createContextualError, createErrorHandler } from './ErrorHandler.js';
|
|
19
|
+
export { default as ErrorHandler, createErrorResponse, createContextualError, createErrorHandler } from './ErrorHandler.js';
|
|
20
|
+
|
|
21
|
+
// Progress display utilities
|
|
22
|
+
export { showProgressSpinner, showProgressWithSpinner } from './progress-spinner.js';
|
|
23
|
+
|
|
24
|
+
// Deployment validation
|
|
25
|
+
export { validateDeploymentPrerequisites, validateDeploymentInputs } from './deployment-validator.js';
|
|
26
|
+
|
|
27
|
+
// Sensitive information redaction
|
|
28
|
+
export { redactSensitiveInfo, redactSensitiveObject } from './sensitive-redactor.js';
|
|
29
|
+
|
|
30
|
+
// Configuration loading
|
|
31
|
+
export { ConfigLoader } from './config-loader.js';
|
|
@@ -98,6 +98,7 @@ export function showProgress(message, steps = ['⏳', '⚡', '✅']) {
|
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
100
|
* Ask for sensitive input (like API tokens) with hidden input
|
|
101
|
+
* Supports pasting with Ctrl+V or right-click paste
|
|
101
102
|
*/
|
|
102
103
|
export function askPassword(question) {
|
|
103
104
|
return new Promise(resolve => {
|
|
@@ -107,26 +108,41 @@ export function askPassword(question) {
|
|
|
107
108
|
// Hide input for sensitive data
|
|
108
109
|
process.stdin.setRawMode(true);
|
|
109
110
|
process.stdin.resume();
|
|
111
|
+
process.stdin.setEncoding('utf8');
|
|
110
112
|
let password = '';
|
|
111
|
-
const onData =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
const onData = chunk => {
|
|
114
|
+
// Handle multi-character input (paste operations)
|
|
115
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
116
|
+
const char = chunk[i];
|
|
117
|
+
const charCode = char.charCodeAt(0);
|
|
118
|
+
if (charCode === 13 || charCode === 10) {
|
|
119
|
+
// Enter key (CR or LF)
|
|
120
|
+
process.stdin.setRawMode(false);
|
|
121
|
+
process.stdin.pause();
|
|
122
|
+
process.stdin.removeListener('data', onData);
|
|
123
|
+
process.stdout.write('\n');
|
|
124
|
+
resolve(password);
|
|
125
|
+
return;
|
|
126
|
+
} else if (charCode === 127 || charCode === 8) {
|
|
127
|
+
// Backspace
|
|
128
|
+
if (password.length > 0) {
|
|
129
|
+
password = password.slice(0, -1);
|
|
130
|
+
process.stdout.write('\b \b');
|
|
131
|
+
}
|
|
132
|
+
} else if (charCode === 3) {
|
|
133
|
+
// Ctrl+C
|
|
134
|
+
process.stdin.setRawMode(false);
|
|
135
|
+
process.stdin.pause();
|
|
136
|
+
process.stdin.removeListener('data', onData);
|
|
137
|
+
process.stdout.write('\n');
|
|
138
|
+
console.log('\nOperation cancelled');
|
|
139
|
+
process.exit(0);
|
|
140
|
+
} else if (charCode >= 32 && charCode <= 126) {
|
|
141
|
+
// Printable characters
|
|
142
|
+
password += char;
|
|
143
|
+
process.stdout.write('*');
|
|
125
144
|
}
|
|
126
|
-
|
|
127
|
-
// Printable characters
|
|
128
|
-
password += char.toString();
|
|
129
|
-
process.stdout.write('*');
|
|
145
|
+
// Ignore other control characters (allows paste to work)
|
|
130
146
|
}
|
|
131
147
|
};
|
|
132
148
|
process.stdin.on('data', onData);
|
|
@@ -71,11 +71,11 @@ export class ProgressManager {
|
|
|
71
71
|
* @returns {ProgressManager} This instance for chaining
|
|
72
72
|
*/
|
|
73
73
|
nextStep(stepName = null) {
|
|
74
|
+
this.currentStep++;
|
|
75
|
+
this.stepStartTime = Date.now();
|
|
74
76
|
if (this.quiet) {
|
|
75
77
|
return this;
|
|
76
78
|
}
|
|
77
|
-
this.currentStep++;
|
|
78
|
-
this.stepStartTime = Date.now();
|
|
79
79
|
const name = stepName || this.steps[this.currentStep - 1] || `Step ${this.currentStep}`;
|
|
80
80
|
const progress = this.totalSteps > 0 ? Math.round(this.currentStep / this.totalSteps * 100) : 0;
|
|
81
81
|
if (this.output) {
|