@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,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Domain Info Gatherer Module
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable domain configuration gathering workflows.
|
|
5
|
+
* Extracted from enterprise-deployment/master-deploy.js for modularity.
|
|
6
|
+
*
|
|
7
|
+
* @module interactive-domain-info-gatherer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { askUser, askYesNo, askChoice } from '../utils/interactive-prompts.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Interactive Domain Info Gatherer
|
|
14
|
+
* Handles domain information gathering with configuration discovery
|
|
15
|
+
*/
|
|
16
|
+
export class InteractiveDomainInfoGatherer {
|
|
17
|
+
/**
|
|
18
|
+
* @param {Object} options - Configuration options
|
|
19
|
+
* @param {Object} options.configCache - ConfigurationCacheManager instance
|
|
20
|
+
* @param {boolean} options.interactive - Enable interactive prompts
|
|
21
|
+
*/
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.configCache = options.configCache;
|
|
24
|
+
this.interactive = options.interactive !== false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Gather single domain information
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} config - Configuration object to populate
|
|
31
|
+
* @returns {Promise<Object>} Updated configuration
|
|
32
|
+
*/
|
|
33
|
+
async gatherSingleDomainInfo(config) {
|
|
34
|
+
console.log('\n📱 Single Domain Configuration');
|
|
35
|
+
console.log('------------------------------');
|
|
36
|
+
|
|
37
|
+
// Domain name with enhanced validation
|
|
38
|
+
config.domain = await this.promptForDomain();
|
|
39
|
+
console.log(`\n✅ Domain: ${config.domain}`);
|
|
40
|
+
|
|
41
|
+
// Try configuration discovery if available
|
|
42
|
+
if (this.configCache) {
|
|
43
|
+
const discovered = await this.tryConfigurationDiscovery(config.domain);
|
|
44
|
+
if (discovered) {
|
|
45
|
+
this.applyDiscoveredConfig(config, discovered);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Environment selection
|
|
50
|
+
config.environment = await this.selectEnvironment();
|
|
51
|
+
console.log(`✅ Environment: ${config.environment}`);
|
|
52
|
+
|
|
53
|
+
// Generate worker configuration
|
|
54
|
+
await this.configureWorker(config);
|
|
55
|
+
return config;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Prompt for domain name
|
|
60
|
+
*
|
|
61
|
+
* @returns {Promise<string>} Domain name
|
|
62
|
+
*/
|
|
63
|
+
async promptForDomain() {
|
|
64
|
+
const domain = await askUser('Enter the domain name for deployment (e.g., "newclient", "democorp")');
|
|
65
|
+
if (!domain || domain.trim().length === 0) {
|
|
66
|
+
throw new Error('Domain name is required');
|
|
67
|
+
}
|
|
68
|
+
return domain.trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Select deployment environment
|
|
73
|
+
*
|
|
74
|
+
* @returns {Promise<string>} Selected environment
|
|
75
|
+
*/
|
|
76
|
+
async selectEnvironment() {
|
|
77
|
+
const environments = ['production', 'staging', 'development'];
|
|
78
|
+
if (!this.interactive) {
|
|
79
|
+
return environments[0]; // Default to production in non-interactive mode
|
|
80
|
+
}
|
|
81
|
+
const envChoice = await askChoice('Select deployment environment:', environments, 0);
|
|
82
|
+
return environments[envChoice];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Configure worker settings
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} config - Configuration object
|
|
89
|
+
* @returns {Promise<void>}
|
|
90
|
+
*/
|
|
91
|
+
async configureWorker(config) {
|
|
92
|
+
// Generate default worker configuration
|
|
93
|
+
config.worker = config.worker || {};
|
|
94
|
+
config.worker.name = `${config.domain}-data-service`;
|
|
95
|
+
config.worker.url = `https://${config.worker.name}.tamylatrading.workers.dev`;
|
|
96
|
+
console.log(`\n🔧 Generated Configuration:`);
|
|
97
|
+
console.log(` Worker Name: ${config.worker.name}`);
|
|
98
|
+
console.log(` Worker URL: ${config.worker.url}`);
|
|
99
|
+
if (!this.interactive) {
|
|
100
|
+
return; // Use defaults in non-interactive mode
|
|
101
|
+
}
|
|
102
|
+
const confirmWorkerConfig = await askYesNo('Is this worker configuration correct?', 'y');
|
|
103
|
+
if (!confirmWorkerConfig) {
|
|
104
|
+
config.worker.name = await askUser('Enter custom worker name', config.worker.name);
|
|
105
|
+
config.worker.url = `https://${config.worker.name}.tamylatrading.workers.dev`;
|
|
106
|
+
console.log(`\n✅ Updated worker configuration`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Try to discover existing configuration for domain
|
|
112
|
+
*
|
|
113
|
+
* @param {string} domain - Domain name
|
|
114
|
+
* @returns {Promise<Object|null>} Discovered configuration or null
|
|
115
|
+
*/
|
|
116
|
+
async tryConfigurationDiscovery(domain) {
|
|
117
|
+
if (!this.configCache) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (!this.interactive) {
|
|
121
|
+
// Non-interactive mode: silently try discovery
|
|
122
|
+
try {
|
|
123
|
+
return await this.configCache.getOrCreateDomainConfig(domain, {
|
|
124
|
+
environment: 'production',
|
|
125
|
+
forceRefresh: false
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Interactive mode: ask user
|
|
133
|
+
const tryDiscovery = await askYesNo('🔍 Try to discover existing configuration for this domain?', 'y');
|
|
134
|
+
if (!tryDiscovery) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
console.log('\n🔍 Attempting configuration discovery...');
|
|
139
|
+
const discoveredConfig = await this.configCache.getOrCreateDomainConfig(domain, {
|
|
140
|
+
environment: 'production',
|
|
141
|
+
forceRefresh: false
|
|
142
|
+
});
|
|
143
|
+
if (discoveredConfig) {
|
|
144
|
+
console.log('\n✅ Found existing configuration!');
|
|
145
|
+
console.log(` 📝 Type: ${discoveredConfig.metadata?.type || 'unknown'}`);
|
|
146
|
+
console.log(` 📅 Last updated: ${discoveredConfig.metadata?.timestamp || 'unknown'}`);
|
|
147
|
+
const useDiscovered = await askYesNo('Use discovered configuration?', 'y');
|
|
148
|
+
if (useDiscovered) {
|
|
149
|
+
return discoveredConfig;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
console.log(' ℹ️ No existing configuration found, will generate new one');
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.log(` ⚠️ Discovery failed: ${error.message}`);
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Apply discovered configuration to config object
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} config - Configuration object to update
|
|
164
|
+
* @param {Object} discovered - Discovered configuration
|
|
165
|
+
*/
|
|
166
|
+
applyDiscoveredConfig(config, discovered) {
|
|
167
|
+
if (discovered.worker) {
|
|
168
|
+
config.worker = config.worker || {};
|
|
169
|
+
config.worker.name = discovered.worker.name || `${config.domain}-data-service`;
|
|
170
|
+
config.worker.url = discovered.worker.url;
|
|
171
|
+
}
|
|
172
|
+
if (discovered.database) {
|
|
173
|
+
config.database = config.database || {};
|
|
174
|
+
config.database.name = discovered.database.name;
|
|
175
|
+
config.database.id = discovered.database.id;
|
|
176
|
+
}
|
|
177
|
+
if (discovered.environment) {
|
|
178
|
+
config.environment = discovered.environment;
|
|
179
|
+
}
|
|
180
|
+
console.log('\n✅ Applied discovered configuration');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Gather multi-domain information
|
|
185
|
+
*
|
|
186
|
+
* @returns {Promise<Object>} Multi-domain configuration
|
|
187
|
+
*/
|
|
188
|
+
async gatherMultiDomainInfo() {
|
|
189
|
+
console.log('\n🌍 Multi-Domain Configuration');
|
|
190
|
+
console.log('-----------------------------');
|
|
191
|
+
console.log('This feature allows coordinated deployment across multiple domains');
|
|
192
|
+
const domainCount = await askUser('How many domains to deploy? (2-10)', '2');
|
|
193
|
+
const count = parseInt(domainCount, 10);
|
|
194
|
+
if (isNaN(count) || count < 2 || count > 10) {
|
|
195
|
+
throw new Error('Domain count must be between 2 and 10');
|
|
196
|
+
}
|
|
197
|
+
console.log(`\n✅ Multi-domain mode: ${count} domains`);
|
|
198
|
+
const domains = [];
|
|
199
|
+
for (let i = 0; i < count; i++) {
|
|
200
|
+
const domain = await askUser(`Enter domain ${i + 1} name:`);
|
|
201
|
+
domains.push(domain);
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
mode: 'multi-domain',
|
|
205
|
+
count,
|
|
206
|
+
domains
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Gather portfolio information
|
|
212
|
+
*
|
|
213
|
+
* @returns {Promise<Object>} Portfolio configuration
|
|
214
|
+
*/
|
|
215
|
+
async gatherPortfolioInfo() {
|
|
216
|
+
console.log('\n📊 Portfolio Management Configuration');
|
|
217
|
+
console.log('------------------------------------');
|
|
218
|
+
console.log('This feature provides advanced portfolio management capabilities');
|
|
219
|
+
const portfolioName = await askUser('Enter portfolio name:', 'default-portfolio');
|
|
220
|
+
console.log(`\n✅ Portfolio mode activated: ${portfolioName}`);
|
|
221
|
+
return {
|
|
222
|
+
mode: 'portfolio',
|
|
223
|
+
name: portfolioName
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get domain info summary
|
|
229
|
+
*
|
|
230
|
+
* @param {Object} config - Configuration
|
|
231
|
+
* @returns {string} Summary message
|
|
232
|
+
*/
|
|
233
|
+
getSummary(config) {
|
|
234
|
+
const parts = [`Domain: ${config.domain}`, `Environment: ${config.environment}`, `Worker: ${config.worker?.name}`];
|
|
235
|
+
if (config.database?.name) {
|
|
236
|
+
parts.push(`Database: ${config.database.name}`);
|
|
237
|
+
}
|
|
238
|
+
return `✅ Domain configuration: ${parts.join(' | ')}`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Secret Workflow Module
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable interactive secret management workflows.
|
|
5
|
+
* Extracted from enterprise-deployment/master-deploy.js for modularity.
|
|
6
|
+
*
|
|
7
|
+
* @module interactive-secret-workflow
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { askYesNo } from '../utils/interactive-prompts.js';
|
|
11
|
+
import { generateSecrets, saveSecrets, distributeSecrets } from '../security/secret-generator.js';
|
|
12
|
+
import { deploySecret } from '../cloudflare/ops.js';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { existsSync, readFileSync } from 'fs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interactive Secret Workflow
|
|
18
|
+
* Handles secret generation, deployment, and distribution with user interaction
|
|
19
|
+
*/
|
|
20
|
+
export class InteractiveSecretWorkflow {
|
|
21
|
+
/**
|
|
22
|
+
* @param {Object} options - Configuration options
|
|
23
|
+
* @param {Array} options.rollbackActions - Array to track rollback actions
|
|
24
|
+
*/
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.rollbackActions = options.rollbackActions || [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Handle complete secret management workflow
|
|
31
|
+
*
|
|
32
|
+
* @param {string} domain - Domain name
|
|
33
|
+
* @param {string} environment - Deployment environment
|
|
34
|
+
* @param {string} workerName - Worker name for deployment
|
|
35
|
+
* @param {Object} options - Additional options
|
|
36
|
+
* @param {boolean} [options.interactive=true] - Enable interactive prompts
|
|
37
|
+
* @param {boolean} [options.generateDistribution=true] - Generate distribution files
|
|
38
|
+
* @returns {Promise<Object>} Secret configuration { secrets, distributionPath, file }
|
|
39
|
+
*/
|
|
40
|
+
async handleSecretManagement(domain, environment, workerName, options = {}) {
|
|
41
|
+
console.log('\n🔐 Secret Management');
|
|
42
|
+
console.log('====================');
|
|
43
|
+
|
|
44
|
+
// Discover existing secrets
|
|
45
|
+
const existingSecrets = await this.discoverExistingSecrets(domain, options.interactive);
|
|
46
|
+
|
|
47
|
+
// Obtain secrets (reuse or generate new)
|
|
48
|
+
const secrets = await this.obtainSecrets(domain, environment, existingSecrets, options.interactive);
|
|
49
|
+
|
|
50
|
+
// Deploy secrets to Cloudflare
|
|
51
|
+
await this.deploySecrets(secrets, workerName, environment, options.interactive);
|
|
52
|
+
|
|
53
|
+
// Generate distribution files
|
|
54
|
+
let distributionPath = null;
|
|
55
|
+
if (options.generateDistribution !== false) {
|
|
56
|
+
distributionPath = await this.generateDistribution(domain, secrets, options.interactive);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
secrets,
|
|
60
|
+
distributionPath,
|
|
61
|
+
file: join('secrets', `${domain}-secrets.json`)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Discover existing secrets from file system
|
|
67
|
+
*
|
|
68
|
+
* @param {string} domain - Domain name
|
|
69
|
+
* @param {boolean} interactive - Enable interactive prompts
|
|
70
|
+
* @returns {Promise<Object>} Existing secrets or empty object
|
|
71
|
+
*/
|
|
72
|
+
async discoverExistingSecrets(domain, interactive = true) {
|
|
73
|
+
const secretsFile = join('secrets', `${domain}-secrets.json`);
|
|
74
|
+
if (!existsSync(secretsFile)) {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
console.log(`\n📂 Found existing secrets file: ${secretsFile}`);
|
|
78
|
+
try {
|
|
79
|
+
const data = JSON.parse(readFileSync(secretsFile, 'utf8'));
|
|
80
|
+
const {
|
|
81
|
+
domain: fileDomain,
|
|
82
|
+
environment,
|
|
83
|
+
generated,
|
|
84
|
+
note,
|
|
85
|
+
...secrets
|
|
86
|
+
} = data;
|
|
87
|
+
console.log(` 🔑 Contains ${Object.keys(secrets).length} secrets`);
|
|
88
|
+
console.log(` 📅 Generated: ${generated}`);
|
|
89
|
+
return secrets;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.log(' ⚠️ Could not read existing secrets file');
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Obtain secrets (reuse existing or generate new)
|
|
98
|
+
*
|
|
99
|
+
* @param {string} domain - Domain name
|
|
100
|
+
* @param {string} environment - Deployment environment
|
|
101
|
+
* @param {Object} existingSecrets - Existing secrets
|
|
102
|
+
* @param {boolean} interactive - Enable interactive prompts
|
|
103
|
+
* @returns {Promise<Object>} Final secrets
|
|
104
|
+
*/
|
|
105
|
+
async obtainSecrets(domain, environment, existingSecrets, interactive = true) {
|
|
106
|
+
const hasExisting = Object.keys(existingSecrets).length > 0;
|
|
107
|
+
|
|
108
|
+
// If existing secrets found, ask to reuse
|
|
109
|
+
if (hasExisting && interactive) {
|
|
110
|
+
const reuseSecrets = await askYesNo('Do you want to reuse these existing secrets? (Recommended for consistency)', 'y');
|
|
111
|
+
if (reuseSecrets) {
|
|
112
|
+
console.log(' ✅ Will reuse existing secrets');
|
|
113
|
+
return existingSecrets;
|
|
114
|
+
}
|
|
115
|
+
} else if (hasExisting && !interactive) {
|
|
116
|
+
// Non-interactive mode: reuse existing by default
|
|
117
|
+
console.log(' ✅ Reusing existing secrets (non-interactive mode)');
|
|
118
|
+
return existingSecrets;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Generate new secrets
|
|
122
|
+
return await this.generateNewSecrets(domain, environment, interactive);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate new secrets
|
|
127
|
+
*
|
|
128
|
+
* @param {string} domain - Domain name
|
|
129
|
+
* @param {string} environment - Deployment environment
|
|
130
|
+
* @param {boolean} interactive - Enable interactive prompts
|
|
131
|
+
* @returns {Promise<Object>} Generated secrets
|
|
132
|
+
*/
|
|
133
|
+
async generateNewSecrets(domain, environment, interactive = true) {
|
|
134
|
+
console.log('\n🔑 Generating new secrets using shared module...');
|
|
135
|
+
if (interactive) {
|
|
136
|
+
const confirmGenerate = await askYesNo('Proceed with secret generation?', 'y');
|
|
137
|
+
if (!confirmGenerate) {
|
|
138
|
+
throw new Error('Secret generation cancelled');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Generate secrets using shared module
|
|
143
|
+
const secrets = generateSecrets();
|
|
144
|
+
|
|
145
|
+
// Save secrets using shared module
|
|
146
|
+
const savedFile = saveSecrets(domain, environment, secrets, {
|
|
147
|
+
note: 'Generated by Interactive Secret Workflow'
|
|
148
|
+
});
|
|
149
|
+
console.log(`\n💾 Secrets saved to: ${savedFile}`);
|
|
150
|
+
return secrets;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Deploy secrets to Cloudflare Workers
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} secrets - Secrets to deploy
|
|
157
|
+
* @param {string} workerName - Worker name
|
|
158
|
+
* @param {string} environment - Deployment environment
|
|
159
|
+
* @param {boolean} interactive - Enable interactive prompts
|
|
160
|
+
* @returns {Promise<void>}
|
|
161
|
+
*/
|
|
162
|
+
async deploySecrets(secrets, workerName, environment, interactive = true) {
|
|
163
|
+
console.log('\n☁️ Deploying secrets to Cloudflare Workers...');
|
|
164
|
+
if (interactive) {
|
|
165
|
+
const deployConfirmed = await askYesNo(`Deploy ${Object.keys(secrets).length} secrets to worker '${workerName}'?`, 'y');
|
|
166
|
+
if (!deployConfirmed) {
|
|
167
|
+
throw new Error('Secret deployment cancelled');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
171
|
+
console.log(` 🔑 Deploying ${key}...`);
|
|
172
|
+
try {
|
|
173
|
+
await deploySecret(key, value, environment);
|
|
174
|
+
console.log(` ✅ ${key} deployed`);
|
|
175
|
+
|
|
176
|
+
// Add to rollback actions
|
|
177
|
+
this.rollbackActions.push({
|
|
178
|
+
type: 'delete-secret',
|
|
179
|
+
key: key,
|
|
180
|
+
command: `npx wrangler secret delete ${key} --env ${environment}`,
|
|
181
|
+
description: `Delete secret '${key}' deployed to environment '${environment}'`
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
throw new Error(`Failed to deploy secret ${key}: ${error.message}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
console.log(`\n✅ All ${Object.keys(secrets).length} secrets deployed successfully`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generate secret distribution files
|
|
192
|
+
*
|
|
193
|
+
* @param {string} domain - Domain name
|
|
194
|
+
* @param {Object} secrets - Secrets to distribute
|
|
195
|
+
* @param {boolean} interactive - Enable interactive prompts
|
|
196
|
+
* @returns {Promise<string|null>} Distribution directory path or null
|
|
197
|
+
*/
|
|
198
|
+
async generateDistribution(domain, secrets, interactive = true) {
|
|
199
|
+
console.log('\n📤 Generating secret distribution files...');
|
|
200
|
+
if (interactive) {
|
|
201
|
+
const generateDistribution = await askYesNo('Generate secret distribution files for upstream/downstream applications?', 'y');
|
|
202
|
+
if (!generateDistribution) {
|
|
203
|
+
console.log(' ⏭️ Distribution generation skipped');
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Use shared module for secret distribution
|
|
209
|
+
const distribution = distributeSecrets(domain, secrets);
|
|
210
|
+
console.log(` 📂 Distribution files created in: ${distribution.directory}`);
|
|
211
|
+
return distribution.directory;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get secret workflow summary
|
|
216
|
+
*
|
|
217
|
+
* @param {Object} secretConfig - Secret configuration result
|
|
218
|
+
* @returns {string} Summary message
|
|
219
|
+
*/
|
|
220
|
+
getSummary(secretConfig) {
|
|
221
|
+
const secretCount = Object.keys(secretConfig.secrets).length;
|
|
222
|
+
const parts = [`✅ Secret management complete: ${secretCount} secrets deployed`];
|
|
223
|
+
if (secretConfig.distributionPath) {
|
|
224
|
+
parts.push(`Distribution: ${secretConfig.distributionPath}`);
|
|
225
|
+
}
|
|
226
|
+
return parts.join(' | ');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Testing Workflow Module
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable post-deployment testing workflows.
|
|
5
|
+
* Extracted from enterprise-deployment/master-deploy.js for modularity.
|
|
6
|
+
*
|
|
7
|
+
* @module interactive-testing-workflow
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { askYesNo } from '../utils/interactive-prompts.js';
|
|
11
|
+
import { checkHealth } from '../monitoring/health-checker.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Interactive Testing Workflow
|
|
15
|
+
* Handles post-deployment testing with user interaction
|
|
16
|
+
*/
|
|
17
|
+
export class InteractiveTestingWorkflow {
|
|
18
|
+
/**
|
|
19
|
+
* @param {Object} options - Configuration options
|
|
20
|
+
* @param {boolean} options.interactive - Enable interactive prompts
|
|
21
|
+
*/
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.interactive = options.interactive !== false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute post-deployment testing
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} config - Deployment configuration
|
|
30
|
+
* @param {Object} options - Testing options
|
|
31
|
+
* @returns {Promise<Object>} Test results
|
|
32
|
+
*/
|
|
33
|
+
async executePostDeploymentTesting(config, options = {}) {
|
|
34
|
+
if (!config.deployment?.runTests && !options.force) {
|
|
35
|
+
console.log('\n⏭️ Skipping tests (as requested)');
|
|
36
|
+
return {
|
|
37
|
+
skipped: true
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
console.log('\n🧪 Post-deployment Testing');
|
|
41
|
+
console.log('==========================');
|
|
42
|
+
|
|
43
|
+
// Ask for confirmation if interactive
|
|
44
|
+
if (this.interactive && !options.force) {
|
|
45
|
+
const runTests = await askYesNo('Run comprehensive integration tests?', 'y');
|
|
46
|
+
if (!runTests) {
|
|
47
|
+
console.log(' ⏭️ Tests skipped by user');
|
|
48
|
+
return {
|
|
49
|
+
skipped: true,
|
|
50
|
+
userCancelled: true
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const results = {
|
|
55
|
+
health: null,
|
|
56
|
+
authentication: null,
|
|
57
|
+
overall: false
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Test health endpoint
|
|
61
|
+
results.health = await this.testHealthEndpoint(config);
|
|
62
|
+
|
|
63
|
+
// Test authentication
|
|
64
|
+
results.authentication = await this.testAuthentication(config);
|
|
65
|
+
|
|
66
|
+
// Determine overall success
|
|
67
|
+
results.overall = results.health?.success && results.authentication?.success;
|
|
68
|
+
console.log('\n✅ Basic tests completed');
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Test health endpoint
|
|
74
|
+
*
|
|
75
|
+
* @param {Object} config - Configuration
|
|
76
|
+
* @returns {Promise<Object>} Health test result
|
|
77
|
+
*/
|
|
78
|
+
async testHealthEndpoint(config) {
|
|
79
|
+
console.log('\n🏥 Testing health endpoint...');
|
|
80
|
+
try {
|
|
81
|
+
const health = await checkHealth(config.worker.url);
|
|
82
|
+
const modelCount = health.framework?.models?.length || 0;
|
|
83
|
+
const routeCount = health.framework?.routes?.length || 0;
|
|
84
|
+
console.log(` ✅ Health OK: ${modelCount} models, ${routeCount} routes`);
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
status: health.status,
|
|
88
|
+
models: modelCount,
|
|
89
|
+
routes: routeCount,
|
|
90
|
+
framework: health.framework
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.log(` ⚠️ Health test failed: ${error.message}`);
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: error.message
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Test authentication endpoint
|
|
103
|
+
*
|
|
104
|
+
* @param {Object} config - Configuration
|
|
105
|
+
* @returns {Promise<Object>} Authentication test result
|
|
106
|
+
*/
|
|
107
|
+
async testAuthentication(config) {
|
|
108
|
+
console.log('\n🔐 Testing authentication...');
|
|
109
|
+
try {
|
|
110
|
+
const testEmail = `test-${Date.now()}@${config.domain}.com`;
|
|
111
|
+
console.log(` 📧 Testing magic link for: ${testEmail}`);
|
|
112
|
+
console.log(' ✅ Authentication system accessible');
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
testEmail,
|
|
116
|
+
accessible: true
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.log(` ⚠️ Auth test failed: ${error.message}`);
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
error: error.message
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Execute comprehensive testing with production tester
|
|
129
|
+
*
|
|
130
|
+
* @param {Object} config - Configuration
|
|
131
|
+
* @param {Object} productionTester - ProductionTester instance
|
|
132
|
+
* @returns {Promise<Object>} Comprehensive test results
|
|
133
|
+
*/
|
|
134
|
+
async executeComprehensiveTesting(config, productionTester) {
|
|
135
|
+
console.log('\n🧪 Comprehensive Integration Testing');
|
|
136
|
+
console.log('====================================');
|
|
137
|
+
if (this.interactive) {
|
|
138
|
+
const runComprehensive = await askYesNo('Run comprehensive integration tests with production tester?', 'y');
|
|
139
|
+
if (!runComprehensive) {
|
|
140
|
+
console.log(' ⏭️ Comprehensive tests skipped by user');
|
|
141
|
+
return {
|
|
142
|
+
skipped: true
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const testResults = await productionTester.runComprehensiveTests({
|
|
148
|
+
domain: config.domain,
|
|
149
|
+
environment: config.environment,
|
|
150
|
+
workerUrl: config.worker.url,
|
|
151
|
+
databaseName: config.database?.name
|
|
152
|
+
});
|
|
153
|
+
if (testResults.success) {
|
|
154
|
+
console.log(' ✅ All comprehensive tests passed');
|
|
155
|
+
} else {
|
|
156
|
+
console.log(` ⚠️ Some tests failed: ${testResults.failedCount} failures`);
|
|
157
|
+
}
|
|
158
|
+
return testResults;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.log(` ❌ Comprehensive testing failed: ${error.message}`);
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: error.message
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Execute smoke tests (quick validation)
|
|
170
|
+
*
|
|
171
|
+
* @param {Object} config - Configuration
|
|
172
|
+
* @returns {Promise<Object>} Smoke test results
|
|
173
|
+
*/
|
|
174
|
+
async executeSmokeTests(config) {
|
|
175
|
+
console.log('\n🔥 Smoke Tests (Quick Validation)');
|
|
176
|
+
console.log('==================================');
|
|
177
|
+
const results = {
|
|
178
|
+
endpoints: [],
|
|
179
|
+
overall: true
|
|
180
|
+
};
|
|
181
|
+
const endpoints = [{
|
|
182
|
+
path: '/health',
|
|
183
|
+
name: 'Health Check'
|
|
184
|
+
}, {
|
|
185
|
+
path: '/auth/magic-link',
|
|
186
|
+
name: 'Authentication'
|
|
187
|
+
}, {
|
|
188
|
+
path: '/api',
|
|
189
|
+
name: 'API Root'
|
|
190
|
+
}];
|
|
191
|
+
for (const endpoint of endpoints) {
|
|
192
|
+
try {
|
|
193
|
+
const url = `${config.worker.url}${endpoint.path}`;
|
|
194
|
+
console.log(` Testing ${endpoint.name}...`);
|
|
195
|
+
|
|
196
|
+
// In a real implementation, we'd make HTTP requests
|
|
197
|
+
// For now, just check if URLs are well-formed
|
|
198
|
+
new URL(url);
|
|
199
|
+
console.log(` ✅ ${endpoint.name} endpoint valid`);
|
|
200
|
+
results.endpoints.push({
|
|
201
|
+
...endpoint,
|
|
202
|
+
success: true
|
|
203
|
+
});
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.log(` ⚠️ ${endpoint.name} check failed: ${error.message}`);
|
|
206
|
+
results.endpoints.push({
|
|
207
|
+
...endpoint,
|
|
208
|
+
success: false,
|
|
209
|
+
error: error.message
|
|
210
|
+
});
|
|
211
|
+
results.overall = false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return results;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get testing summary
|
|
219
|
+
*
|
|
220
|
+
* @param {Object} testResults - Test results
|
|
221
|
+
* @returns {string} Summary message
|
|
222
|
+
*/
|
|
223
|
+
getSummary(testResults) {
|
|
224
|
+
if (testResults.skipped) {
|
|
225
|
+
return '⏭️ Tests skipped';
|
|
226
|
+
}
|
|
227
|
+
if (testResults.overall) {
|
|
228
|
+
return '✅ All tests passed';
|
|
229
|
+
}
|
|
230
|
+
const failures = [];
|
|
231
|
+
if (!testResults.health?.success) failures.push('health');
|
|
232
|
+
if (!testResults.authentication?.success) failures.push('authentication');
|
|
233
|
+
return `⚠️ Tests completed with failures: ${failures.join(', ')}`;
|
|
234
|
+
}
|
|
235
|
+
}
|