@tamyla/clodo-framework 3.1.21 ā 3.1.22
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 +9 -0
- package/README.md +53 -0
- package/dist/bin/clodo-service.js +47 -15
- package/dist/bin/commands/deploy.js +115 -83
- package/dist/bin/commands/helpers/deployment-ui.js +138 -0
- package/dist/bin/commands/helpers/deployment-verification.js +251 -0
- package/dist/bin/commands/helpers/error-recovery.js +80 -0
- package/dist/bin/commands/helpers/resource-detection.js +113 -0
- package/dist/bin/commands/validate.js +1 -1
- package/dist/bin/security/security-cli.js +1 -1
- package/dist/bin/shared/cache/configuration-cache.js +82 -0
- package/dist/bin/shared/cloudflare/domain-manager.js +1 -1
- package/dist/bin/shared/cloudflare/index.js +1 -1
- package/dist/bin/shared/cloudflare/ops.js +6 -4
- package/dist/bin/shared/config/ConfigurationManager.js +23 -1
- package/dist/bin/shared/config/command-config-manager.js +19 -3
- package/dist/bin/shared/config/index.js +1 -1
- package/dist/bin/shared/deployment/credential-collector.js +30 -7
- package/dist/bin/shared/deployment/index.js +2 -2
- package/dist/bin/shared/deployment/rollback-manager.js +4 -520
- package/dist/bin/shared/deployment/utilities/d1-error-recovery.js +177 -0
- package/dist/bin/shared/deployment/validator.js +40 -10
- package/dist/bin/shared/deployment/workflows/deployment-summary.js +214 -0
- package/dist/bin/shared/deployment/workflows/interactive-confirmation.js +188 -0
- package/dist/bin/shared/deployment/workflows/interactive-database-workflow.js +234 -0
- package/dist/bin/shared/deployment/workflows/interactive-domain-info-gatherer.js +240 -0
- package/dist/bin/shared/deployment/workflows/interactive-secret-workflow.js +228 -0
- package/dist/bin/shared/deployment/workflows/interactive-testing-workflow.js +235 -0
- package/dist/bin/shared/deployment/workflows/interactive-validation.js +218 -0
- package/dist/bin/shared/error-handling/error-classifier.js +46 -0
- package/dist/bin/shared/monitoring/health-checker.js +129 -1
- package/dist/bin/shared/monitoring/memory-manager.js +17 -6
- package/dist/bin/shared/routing/domain-router.js +1 -1
- package/dist/bin/shared/utils/deployment-validator.js +97 -0
- package/dist/bin/shared/utils/formatters.js +10 -0
- package/dist/bin/shared/utils/index.js +13 -1
- package/dist/bin/shared/utils/interactive-prompts.js +34 -18
- package/dist/bin/shared/utils/progress-manager.js +2 -2
- package/dist/bin/shared/utils/progress-spinner.js +53 -0
- package/dist/bin/shared/utils/sensitive-redactor.js +91 -0
- package/dist/bin/shared/validation/ValidationRegistry.js +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/patterns/insecure-patterns.js +1 -1
- package/dist/utils/constants.js +102 -0
- package/dist/utils/deployment/wrangler-config-manager.js +215 -48
- package/dist/utils/framework-config.js +2 -2
- package/dist/utils/interactive-prompts.js +10 -59
- package/package.json +16 -8
- 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/commands/create.js +0 -77
- package/dist/bin/commands/diagnose.js +0 -83
- package/dist/bin/commands/helpers.js +0 -138
- package/dist/bin/commands/update.js +0 -75
- package/dist/bin/database/deployment-db-manager.js +0 -423
- package/dist/bin/database/enterprise-db-manager.js +0 -457
- package/dist/bin/database/wrangler-d1-manager.js +0 -685
- 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/modules/DeploymentConfiguration.js +0 -395
- package/dist/bin/deployment/modules/DeploymentOrchestrator.js +0 -492
- package/dist/bin/deployment/modules/EnvironmentManager.js +0 -517
- package/dist/bin/deployment/modules/MonitoringIntegration.js +0 -560
- package/dist/bin/deployment/modules/ValidationManager.js +0 -342
- package/dist/bin/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
- package/dist/bin/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/bin/deployment/orchestration/PortfolioOrchestrator.js +0 -273
- package/dist/bin/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
- package/dist/bin/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
- package/dist/bin/deployment/test-interactive-utils.js +0 -66
- package/dist/bin/portfolio/portfolio-manager.js +0 -487
- package/dist/bin/service-management/create-service.js +0 -122
- package/dist/bin/service-management/init-service.js +0 -79
- package/dist/config/customers.js +0 -623
- package/dist/config/domains.js +0 -186
- package/dist/config/index.js +0 -6
- package/dist/database/database-orchestrator.js +0 -795
- package/dist/database/index.js +0 -4
- package/dist/deployment/index.js +0 -11
- package/dist/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
- package/dist/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/deployment/orchestration/PortfolioOrchestrator.js +0 -273
- package/dist/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
- package/dist/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
- package/dist/deployment/orchestration/index.js +0 -17
- package/dist/deployment/rollback-manager.js +0 -36
- package/dist/deployment/wrangler-deployer.js +0 -640
- package/dist/handlers/GenericRouteHandler.js +0 -532
- package/dist/migration/MigrationAdapters.js +0 -562
- package/dist/modules/ModuleManager.js +0 -668
- package/dist/modules/security.js +0 -96
- package/dist/orchestration/cross-domain-coordinator.js +0 -1083
- package/dist/orchestration/index.js +0 -5
- package/dist/orchestration/modules/DeploymentCoordinator.js +0 -368
- package/dist/orchestration/modules/DomainResolver.js +0 -198
- package/dist/orchestration/modules/StateManager.js +0 -332
- package/dist/orchestration/multi-domain-orchestrator.js +0 -724
- package/dist/routing/EnhancedRouter.js +0 -158
- package/dist/schema/SchemaManager.js +0 -778
- package/dist/service-management/ConfirmationEngine.js +0 -412
- package/dist/service-management/ErrorTracker.js +0 -299
- package/dist/service-management/GenerationEngine.js +0 -447
- package/dist/service-management/InputCollector.js +0 -619
- package/dist/service-management/ServiceCreator.js +0 -265
- package/dist/service-management/ServiceInitializer.js +0 -453
- package/dist/service-management/ServiceOrchestrator.js +0 -633
- package/dist/service-management/generators/BaseGenerator.js +0 -233
- package/dist/service-management/generators/GeneratorRegistry.js +0 -254
- package/dist/service-management/generators/cicd/CiWorkflowGenerator.js +0 -87
- package/dist/service-management/generators/cicd/DeployWorkflowGenerator.js +0 -106
- package/dist/service-management/generators/code/ServiceHandlersGenerator.js +0 -235
- package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +0 -116
- package/dist/service-management/generators/code/ServiceUtilsGenerator.js +0 -246
- package/dist/service-management/generators/code/WorkerIndexGenerator.js +0 -143
- package/dist/service-management/generators/config/DevelopmentEnvGenerator.js +0 -101
- package/dist/service-management/generators/config/DomainsConfigGenerator.js +0 -175
- package/dist/service-management/generators/config/EnvExampleGenerator.js +0 -178
- package/dist/service-management/generators/config/ProductionEnvGenerator.js +0 -97
- package/dist/service-management/generators/config/StagingEnvGenerator.js +0 -97
- package/dist/service-management/generators/config/WranglerTomlGenerator.js +0 -238
- package/dist/service-management/generators/core/PackageJsonGenerator.js +0 -243
- package/dist/service-management/generators/core/SiteConfigGenerator.js +0 -115
- package/dist/service-management/generators/documentation/ApiDocsGenerator.js +0 -331
- package/dist/service-management/generators/documentation/ConfigurationDocsGenerator.js +0 -294
- package/dist/service-management/generators/documentation/DeploymentDocsGenerator.js +0 -244
- package/dist/service-management/generators/documentation/ReadmeGenerator.js +0 -196
- package/dist/service-management/generators/schemas/ServiceSchemaGenerator.js +0 -190
- package/dist/service-management/generators/scripts/DeployScriptGenerator.js +0 -123
- package/dist/service-management/generators/scripts/HealthCheckScriptGenerator.js +0 -101
- package/dist/service-management/generators/scripts/SetupScriptGenerator.js +0 -88
- package/dist/service-management/generators/service-types/StaticSiteGenerator.js +0 -342
- package/dist/service-management/generators/testing/EslintConfigGenerator.js +0 -85
- package/dist/service-management/generators/testing/IntegrationTestsGenerator.js +0 -237
- package/dist/service-management/generators/testing/JestConfigGenerator.js +0 -72
- package/dist/service-management/generators/testing/UnitTestsGenerator.js +0 -277
- package/dist/service-management/generators/tooling/DockerComposeGenerator.js +0 -71
- package/dist/service-management/generators/tooling/GitignoreGenerator.js +0 -143
- package/dist/service-management/generators/utils/FileWriter.js +0 -179
- package/dist/service-management/generators/utils/PathResolver.js +0 -157
- package/dist/service-management/generators/utils/ServiceManifestGenerator.js +0 -111
- package/dist/service-management/generators/utils/TemplateEngine.js +0 -185
- package/dist/service-management/generators/utils/index.js +0 -18
- package/dist/service-management/handlers/ConfirmationHandler.js +0 -71
- package/dist/service-management/handlers/GenerationHandler.js +0 -80
- package/dist/service-management/handlers/InputHandler.js +0 -59
- package/dist/service-management/handlers/ValidationHandler.js +0 -203
- package/dist/service-management/index.js +0 -14
- package/dist/service-management/routing/DomainRouteMapper.js +0 -311
- package/dist/service-management/routing/RouteGenerator.js +0 -266
- package/dist/service-management/routing/WranglerRoutesBuilder.js +0 -273
- package/dist/service-management/routing/index.js +0 -14
- package/dist/service-management/services/DirectoryStructureService.js +0 -56
- package/dist/service-management/services/GenerationCoordinator.js +0 -208
- package/dist/service-management/services/GeneratorRegistry.js +0 -174
- package/dist/services/GenericDataService.js +0 -501
- package/dist/ui-structures/concepts/second-order-acquisition-strategy.md +0 -286
- package/dist/ui-structures/concepts/service-lifecycle-management.md +0 -150
- package/dist/ui-structures/concepts/service-manifest-guide.md +0 -309
- package/dist/ui-structures/concepts/three-tier-categorization-strategy.md +0 -231
- package/dist/ui-structures/creation/automated-generation-ui.json +0 -246
- package/dist/ui-structures/creation/core-inputs-ui.json +0 -217
- package/dist/ui-structures/creation/smart-confirmable-ui.json +0 -451
- package/dist/ui-structures/reference/absolutely-required-inputs.json +0 -315
- package/dist/ui-structures/reference/service-manifest-template.json +0 -342
- package/dist/version/VersionDetector.js +0 -723
- package/dist/worker/index.js +0 -4
- package/dist/worker/integration.js +0 -351
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Database Workflow Module
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable interactive database creation and configuration workflows.
|
|
5
|
+
* Extracted from enterprise-deployment/master-deploy.js for modularity.
|
|
6
|
+
*
|
|
7
|
+
* @module interactive-database-workflow
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { askUser, askYesNo, askChoice } from '../../utils/interactive-prompts.js';
|
|
11
|
+
import { databaseExists, createDatabase, deleteDatabase } from '../../cloudflare/ops.js';
|
|
12
|
+
import { exec } from 'child_process';
|
|
13
|
+
import { promisify } from 'util';
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interactive Database Workflow
|
|
18
|
+
* Handles database naming, existence checking, and creation with user interaction
|
|
19
|
+
*/
|
|
20
|
+
export class InteractiveDatabaseWorkflow {
|
|
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 database setup workflow
|
|
31
|
+
*
|
|
32
|
+
* @param {string} domain - Domain name for database
|
|
33
|
+
* @param {string} environment - Deployment environment
|
|
34
|
+
* @param {Object} options - Additional options
|
|
35
|
+
* @param {string} [options.suggestedName] - Suggested database name
|
|
36
|
+
* @param {boolean} [options.interactive=true] - Enable interactive prompts
|
|
37
|
+
* @returns {Promise<Object>} Database configuration { name, id, created, reused }
|
|
38
|
+
*/
|
|
39
|
+
async handleDatabaseSetup(domain, environment, options = {}) {
|
|
40
|
+
console.log('\nšļø Database Configuration');
|
|
41
|
+
console.log('=========================');
|
|
42
|
+
|
|
43
|
+
// Generate default database name
|
|
44
|
+
const suggestedName = options.suggestedName || `${domain}-auth-db`;
|
|
45
|
+
|
|
46
|
+
// Get final database name
|
|
47
|
+
const databaseName = await this.promptForDatabaseName(suggestedName, options.interactive);
|
|
48
|
+
|
|
49
|
+
// Check if database exists
|
|
50
|
+
const existingInfo = await this.checkExistingDatabase(databaseName);
|
|
51
|
+
if (existingInfo.exists) {
|
|
52
|
+
return await this.handleExistingDatabase(databaseName, existingInfo, options.interactive);
|
|
53
|
+
} else {
|
|
54
|
+
return await this.createNewDatabase(databaseName, options.interactive);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Prompt user for database name
|
|
60
|
+
*
|
|
61
|
+
* @param {string} suggested - Suggested database name
|
|
62
|
+
* @param {boolean} interactive - Enable interactive prompts
|
|
63
|
+
* @returns {Promise<string>} Final database name
|
|
64
|
+
*/
|
|
65
|
+
async promptForDatabaseName(suggested, interactive = true) {
|
|
66
|
+
console.log(`\nš Generated database name: ${suggested}`);
|
|
67
|
+
if (!interactive) {
|
|
68
|
+
return suggested;
|
|
69
|
+
}
|
|
70
|
+
const useGeneratedName = await askYesNo('Use this database name?', 'y');
|
|
71
|
+
if (!useGeneratedName) {
|
|
72
|
+
return await askUser('Enter custom database name', suggested);
|
|
73
|
+
}
|
|
74
|
+
return suggested;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if database exists and get its details
|
|
79
|
+
*
|
|
80
|
+
* @param {string} name - Database name
|
|
81
|
+
* @returns {Promise<Object>} { exists: boolean, id?: string, error?: string }
|
|
82
|
+
*/
|
|
83
|
+
async checkExistingDatabase(name) {
|
|
84
|
+
console.log('\nš Checking for existing database...');
|
|
85
|
+
try {
|
|
86
|
+
const exists = await databaseExists(name);
|
|
87
|
+
if (exists) {
|
|
88
|
+
console.log(` š Database '${name}' already exists`);
|
|
89
|
+
|
|
90
|
+
// Extract database ID from the list command
|
|
91
|
+
try {
|
|
92
|
+
const dbListResult = await execAsync('npx wrangler d1 list');
|
|
93
|
+
const lines = dbListResult.stdout.split('\n');
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (line.includes(name)) {
|
|
96
|
+
const match = line.match(/([a-f0-9-]{36})/);
|
|
97
|
+
if (match) {
|
|
98
|
+
return {
|
|
99
|
+
exists: true,
|
|
100
|
+
id: match[1]
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.log(' ā ļø Could not extract database ID');
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
exists: true
|
|
110
|
+
};
|
|
111
|
+
} else {
|
|
112
|
+
console.log(` ā
Database '${name}' does not exist`);
|
|
113
|
+
return {
|
|
114
|
+
exists: false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.log(' ā ļø Could not check existing databases');
|
|
119
|
+
return {
|
|
120
|
+
exists: false,
|
|
121
|
+
error: error.message
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Handle workflow for existing database
|
|
128
|
+
*
|
|
129
|
+
* @param {string} name - Database name
|
|
130
|
+
* @param {Object} existingInfo - Information about existing database
|
|
131
|
+
* @param {boolean} interactive - Enable interactive prompts
|
|
132
|
+
* @returns {Promise<Object>} Database configuration
|
|
133
|
+
*/
|
|
134
|
+
async handleExistingDatabase(name, existingInfo, interactive = true) {
|
|
135
|
+
if (!interactive) {
|
|
136
|
+
// Non-interactive mode: reuse existing
|
|
137
|
+
console.log(` ā
Using existing database: ${name}`);
|
|
138
|
+
return {
|
|
139
|
+
name,
|
|
140
|
+
id: existingInfo.id,
|
|
141
|
+
created: false,
|
|
142
|
+
reused: true
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const databaseChoice = await askChoice('What would you like to do with the existing database?', ['Use the existing database (recommended)', 'Create a new database with different name', 'Delete existing and create new (DANGER: DATA LOSS)'], 0);
|
|
146
|
+
switch (databaseChoice) {
|
|
147
|
+
case 0:
|
|
148
|
+
{
|
|
149
|
+
// Use existing database
|
|
150
|
+
if (existingInfo.id) {
|
|
151
|
+
console.log(` ā
Using existing database ID: ${existingInfo.id}`);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
name,
|
|
155
|
+
id: existingInfo.id,
|
|
156
|
+
created: false,
|
|
157
|
+
reused: true
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
case 1:
|
|
161
|
+
{
|
|
162
|
+
// Create new database with different name
|
|
163
|
+
const newName = await askUser('Enter new database name');
|
|
164
|
+
return await this.createNewDatabase(newName, interactive);
|
|
165
|
+
}
|
|
166
|
+
case 2:
|
|
167
|
+
{
|
|
168
|
+
// Delete and recreate
|
|
169
|
+
const confirmDelete = await askYesNo('ā ļø ARE YOU SURE? This will DELETE all data in the existing database!', 'n');
|
|
170
|
+
if (!confirmDelete) {
|
|
171
|
+
throw new Error('Database deletion cancelled');
|
|
172
|
+
}
|
|
173
|
+
console.log(`\nšļø Deleting existing database...`);
|
|
174
|
+
await deleteDatabase(name);
|
|
175
|
+
return await this.createNewDatabase(name, interactive);
|
|
176
|
+
}
|
|
177
|
+
default:
|
|
178
|
+
throw new Error('Invalid database choice');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create a new database
|
|
184
|
+
*
|
|
185
|
+
* @param {string} name - Database name
|
|
186
|
+
* @param {boolean} interactive - Enable interactive prompts
|
|
187
|
+
* @returns {Promise<Object>} Database configuration
|
|
188
|
+
*/
|
|
189
|
+
async createNewDatabase(name, interactive = true) {
|
|
190
|
+
console.log(`\nš Creating new database: ${name}`);
|
|
191
|
+
if (interactive) {
|
|
192
|
+
const confirmCreate = await askYesNo('Proceed with database creation?', 'y');
|
|
193
|
+
if (!confirmCreate) {
|
|
194
|
+
throw new Error('Database creation cancelled');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const databaseId = await createDatabase(name);
|
|
199
|
+
console.log(` ā
Database created with ID: ${databaseId}`);
|
|
200
|
+
|
|
201
|
+
// Add to rollback actions
|
|
202
|
+
this.rollbackActions.push({
|
|
203
|
+
type: 'delete-database',
|
|
204
|
+
name: name,
|
|
205
|
+
command: `npx wrangler d1 delete ${name} --skip-confirmation`,
|
|
206
|
+
description: `Delete database '${name}' created during deployment`
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
name,
|
|
210
|
+
id: databaseId,
|
|
211
|
+
created: true,
|
|
212
|
+
reused: false
|
|
213
|
+
};
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw new Error(`Database creation failed: ${error.message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get database configuration summary
|
|
221
|
+
*
|
|
222
|
+
* @param {Object} dbConfig - Database configuration
|
|
223
|
+
* @returns {string} Summary message
|
|
224
|
+
*/
|
|
225
|
+
getSummary(dbConfig) {
|
|
226
|
+
if (dbConfig.created) {
|
|
227
|
+
return `ā
Database configured: ${dbConfig.name} (${dbConfig.id}) - CREATED`;
|
|
228
|
+
} else if (dbConfig.reused) {
|
|
229
|
+
return `ā
Database configured: ${dbConfig.name} (${dbConfig.id}) - REUSED`;
|
|
230
|
+
} else {
|
|
231
|
+
return `ā
Database configured: ${dbConfig.name} (${dbConfig.id})`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -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
|
+
}
|