@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.
Files changed (169) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +53 -0
  3. package/dist/bin/clodo-service.js +47 -15
  4. package/dist/bin/commands/deploy.js +115 -83
  5. package/dist/bin/commands/helpers/deployment-ui.js +138 -0
  6. package/dist/bin/commands/helpers/deployment-verification.js +251 -0
  7. package/dist/bin/commands/helpers/error-recovery.js +80 -0
  8. package/dist/bin/commands/helpers/resource-detection.js +113 -0
  9. package/dist/bin/commands/validate.js +1 -1
  10. package/dist/bin/security/security-cli.js +1 -1
  11. package/dist/bin/shared/cache/configuration-cache.js +82 -0
  12. package/dist/bin/shared/cloudflare/domain-manager.js +1 -1
  13. package/dist/bin/shared/cloudflare/index.js +1 -1
  14. package/dist/bin/shared/cloudflare/ops.js +6 -4
  15. package/dist/bin/shared/config/ConfigurationManager.js +23 -1
  16. package/dist/bin/shared/config/command-config-manager.js +19 -3
  17. package/dist/bin/shared/config/index.js +1 -1
  18. package/dist/bin/shared/deployment/credential-collector.js +30 -7
  19. package/dist/bin/shared/deployment/index.js +2 -2
  20. package/dist/bin/shared/deployment/rollback-manager.js +4 -520
  21. package/dist/bin/shared/deployment/utilities/d1-error-recovery.js +177 -0
  22. package/dist/bin/shared/deployment/validator.js +40 -10
  23. package/dist/bin/shared/deployment/workflows/deployment-summary.js +214 -0
  24. package/dist/bin/shared/deployment/workflows/interactive-confirmation.js +188 -0
  25. package/dist/bin/shared/deployment/workflows/interactive-database-workflow.js +234 -0
  26. package/dist/bin/shared/deployment/workflows/interactive-domain-info-gatherer.js +240 -0
  27. package/dist/bin/shared/deployment/workflows/interactive-secret-workflow.js +228 -0
  28. package/dist/bin/shared/deployment/workflows/interactive-testing-workflow.js +235 -0
  29. package/dist/bin/shared/deployment/workflows/interactive-validation.js +218 -0
  30. package/dist/bin/shared/error-handling/error-classifier.js +46 -0
  31. package/dist/bin/shared/monitoring/health-checker.js +129 -1
  32. package/dist/bin/shared/monitoring/memory-manager.js +17 -6
  33. package/dist/bin/shared/routing/domain-router.js +1 -1
  34. package/dist/bin/shared/utils/deployment-validator.js +97 -0
  35. package/dist/bin/shared/utils/formatters.js +10 -0
  36. package/dist/bin/shared/utils/index.js +13 -1
  37. package/dist/bin/shared/utils/interactive-prompts.js +34 -18
  38. package/dist/bin/shared/utils/progress-manager.js +2 -2
  39. package/dist/bin/shared/utils/progress-spinner.js +53 -0
  40. package/dist/bin/shared/utils/sensitive-redactor.js +91 -0
  41. package/dist/bin/shared/validation/ValidationRegistry.js +1 -1
  42. package/dist/security/index.js +1 -1
  43. package/dist/security/patterns/insecure-patterns.js +1 -1
  44. package/dist/utils/constants.js +102 -0
  45. package/dist/utils/deployment/wrangler-config-manager.js +215 -48
  46. package/dist/utils/framework-config.js +2 -2
  47. package/dist/utils/interactive-prompts.js +10 -59
  48. package/package.json +16 -8
  49. package/dist/bin/clodo-service-old.js +0 -868
  50. package/dist/bin/clodo-service-test.js +0 -10
  51. package/dist/bin/commands/assess.js +0 -91
  52. package/dist/bin/commands/create.js +0 -77
  53. package/dist/bin/commands/diagnose.js +0 -83
  54. package/dist/bin/commands/helpers.js +0 -138
  55. package/dist/bin/commands/update.js +0 -75
  56. package/dist/bin/database/deployment-db-manager.js +0 -423
  57. package/dist/bin/database/enterprise-db-manager.js +0 -457
  58. package/dist/bin/database/wrangler-d1-manager.js +0 -685
  59. package/dist/bin/deployment/enterprise-deploy.js +0 -877
  60. package/dist/bin/deployment/master-deploy.js +0 -1376
  61. package/dist/bin/deployment/modular-enterprise-deploy.js +0 -466
  62. package/dist/bin/deployment/modules/DeploymentConfiguration.js +0 -395
  63. package/dist/bin/deployment/modules/DeploymentOrchestrator.js +0 -492
  64. package/dist/bin/deployment/modules/EnvironmentManager.js +0 -517
  65. package/dist/bin/deployment/modules/MonitoringIntegration.js +0 -560
  66. package/dist/bin/deployment/modules/ValidationManager.js +0 -342
  67. package/dist/bin/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
  68. package/dist/bin/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
  69. package/dist/bin/deployment/orchestration/PortfolioOrchestrator.js +0 -273
  70. package/dist/bin/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
  71. package/dist/bin/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
  72. package/dist/bin/deployment/test-interactive-utils.js +0 -66
  73. package/dist/bin/portfolio/portfolio-manager.js +0 -487
  74. package/dist/bin/service-management/create-service.js +0 -122
  75. package/dist/bin/service-management/init-service.js +0 -79
  76. package/dist/config/customers.js +0 -623
  77. package/dist/config/domains.js +0 -186
  78. package/dist/config/index.js +0 -6
  79. package/dist/database/database-orchestrator.js +0 -795
  80. package/dist/database/index.js +0 -4
  81. package/dist/deployment/index.js +0 -11
  82. package/dist/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
  83. package/dist/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
  84. package/dist/deployment/orchestration/PortfolioOrchestrator.js +0 -273
  85. package/dist/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
  86. package/dist/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
  87. package/dist/deployment/orchestration/index.js +0 -17
  88. package/dist/deployment/rollback-manager.js +0 -36
  89. package/dist/deployment/wrangler-deployer.js +0 -640
  90. package/dist/handlers/GenericRouteHandler.js +0 -532
  91. package/dist/migration/MigrationAdapters.js +0 -562
  92. package/dist/modules/ModuleManager.js +0 -668
  93. package/dist/modules/security.js +0 -96
  94. package/dist/orchestration/cross-domain-coordinator.js +0 -1083
  95. package/dist/orchestration/index.js +0 -5
  96. package/dist/orchestration/modules/DeploymentCoordinator.js +0 -368
  97. package/dist/orchestration/modules/DomainResolver.js +0 -198
  98. package/dist/orchestration/modules/StateManager.js +0 -332
  99. package/dist/orchestration/multi-domain-orchestrator.js +0 -724
  100. package/dist/routing/EnhancedRouter.js +0 -158
  101. package/dist/schema/SchemaManager.js +0 -778
  102. package/dist/service-management/ConfirmationEngine.js +0 -412
  103. package/dist/service-management/ErrorTracker.js +0 -299
  104. package/dist/service-management/GenerationEngine.js +0 -447
  105. package/dist/service-management/InputCollector.js +0 -619
  106. package/dist/service-management/ServiceCreator.js +0 -265
  107. package/dist/service-management/ServiceInitializer.js +0 -453
  108. package/dist/service-management/ServiceOrchestrator.js +0 -633
  109. package/dist/service-management/generators/BaseGenerator.js +0 -233
  110. package/dist/service-management/generators/GeneratorRegistry.js +0 -254
  111. package/dist/service-management/generators/cicd/CiWorkflowGenerator.js +0 -87
  112. package/dist/service-management/generators/cicd/DeployWorkflowGenerator.js +0 -106
  113. package/dist/service-management/generators/code/ServiceHandlersGenerator.js +0 -235
  114. package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +0 -116
  115. package/dist/service-management/generators/code/ServiceUtilsGenerator.js +0 -246
  116. package/dist/service-management/generators/code/WorkerIndexGenerator.js +0 -143
  117. package/dist/service-management/generators/config/DevelopmentEnvGenerator.js +0 -101
  118. package/dist/service-management/generators/config/DomainsConfigGenerator.js +0 -175
  119. package/dist/service-management/generators/config/EnvExampleGenerator.js +0 -178
  120. package/dist/service-management/generators/config/ProductionEnvGenerator.js +0 -97
  121. package/dist/service-management/generators/config/StagingEnvGenerator.js +0 -97
  122. package/dist/service-management/generators/config/WranglerTomlGenerator.js +0 -238
  123. package/dist/service-management/generators/core/PackageJsonGenerator.js +0 -243
  124. package/dist/service-management/generators/core/SiteConfigGenerator.js +0 -115
  125. package/dist/service-management/generators/documentation/ApiDocsGenerator.js +0 -331
  126. package/dist/service-management/generators/documentation/ConfigurationDocsGenerator.js +0 -294
  127. package/dist/service-management/generators/documentation/DeploymentDocsGenerator.js +0 -244
  128. package/dist/service-management/generators/documentation/ReadmeGenerator.js +0 -196
  129. package/dist/service-management/generators/schemas/ServiceSchemaGenerator.js +0 -190
  130. package/dist/service-management/generators/scripts/DeployScriptGenerator.js +0 -123
  131. package/dist/service-management/generators/scripts/HealthCheckScriptGenerator.js +0 -101
  132. package/dist/service-management/generators/scripts/SetupScriptGenerator.js +0 -88
  133. package/dist/service-management/generators/service-types/StaticSiteGenerator.js +0 -342
  134. package/dist/service-management/generators/testing/EslintConfigGenerator.js +0 -85
  135. package/dist/service-management/generators/testing/IntegrationTestsGenerator.js +0 -237
  136. package/dist/service-management/generators/testing/JestConfigGenerator.js +0 -72
  137. package/dist/service-management/generators/testing/UnitTestsGenerator.js +0 -277
  138. package/dist/service-management/generators/tooling/DockerComposeGenerator.js +0 -71
  139. package/dist/service-management/generators/tooling/GitignoreGenerator.js +0 -143
  140. package/dist/service-management/generators/utils/FileWriter.js +0 -179
  141. package/dist/service-management/generators/utils/PathResolver.js +0 -157
  142. package/dist/service-management/generators/utils/ServiceManifestGenerator.js +0 -111
  143. package/dist/service-management/generators/utils/TemplateEngine.js +0 -185
  144. package/dist/service-management/generators/utils/index.js +0 -18
  145. package/dist/service-management/handlers/ConfirmationHandler.js +0 -71
  146. package/dist/service-management/handlers/GenerationHandler.js +0 -80
  147. package/dist/service-management/handlers/InputHandler.js +0 -59
  148. package/dist/service-management/handlers/ValidationHandler.js +0 -203
  149. package/dist/service-management/index.js +0 -14
  150. package/dist/service-management/routing/DomainRouteMapper.js +0 -311
  151. package/dist/service-management/routing/RouteGenerator.js +0 -266
  152. package/dist/service-management/routing/WranglerRoutesBuilder.js +0 -273
  153. package/dist/service-management/routing/index.js +0 -14
  154. package/dist/service-management/services/DirectoryStructureService.js +0 -56
  155. package/dist/service-management/services/GenerationCoordinator.js +0 -208
  156. package/dist/service-management/services/GeneratorRegistry.js +0 -174
  157. package/dist/services/GenericDataService.js +0 -501
  158. package/dist/ui-structures/concepts/second-order-acquisition-strategy.md +0 -286
  159. package/dist/ui-structures/concepts/service-lifecycle-management.md +0 -150
  160. package/dist/ui-structures/concepts/service-manifest-guide.md +0 -309
  161. package/dist/ui-structures/concepts/three-tier-categorization-strategy.md +0 -231
  162. package/dist/ui-structures/creation/automated-generation-ui.json +0 -246
  163. package/dist/ui-structures/creation/core-inputs-ui.json +0 -217
  164. package/dist/ui-structures/creation/smart-confirmable-ui.json +0 -451
  165. package/dist/ui-structures/reference/absolutely-required-inputs.json +0 -315
  166. package/dist/ui-structures/reference/service-manifest-template.json +0 -342
  167. package/dist/version/VersionDetector.js +0 -723
  168. package/dist/worker/index.js +0 -4
  169. 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
+ }