@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
@@ -1,79 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Clodo Framework - Service Initializer
5
- *
6
- * Initializes a new service with auto-generated configurations,
7
- * eliminating the need for manual wrangler.toml and domains.js setup.
8
- *
9
- * This solves the workflow order problem by making configuration
10
- * generation the first step, not a prerequisite.
11
- */
12
- import { program } from 'commander';
13
- import { ServiceInitializer } from "../../service-management/ServiceInitializer.js";
14
- const SERVICE_TYPES = ['generic', 'data-service', 'auth-service', 'content-service', 'api-gateway'];
15
- class ServiceInitializerCLI {
16
- constructor() {
17
- this.setupCLI();
18
- }
19
- setupCLI() {
20
- program.name('clodo-init-service').description('Initialize a Clodo Framework service with auto-generated configurations').version('1.0.0').argument('<service-name>', 'Name of the service to initialize').option('-t, --type <type>', 'Service type', 'generic').option('-d, --domains <domains>', 'Comma-separated list of domains (can include account info)').option('-e, --env <environment>', 'Target environment', 'development').option('--api-token <token>', 'Cloudflare API token for domain discovery').option('--account-id <id>', 'Default Cloudflare account ID').option('--zone-id <id>', 'Default Cloudflare zone ID').option('-o, --output <path>', 'Output directory (services will be created in a services/ subdirectory)', process.cwd()).option('-f, --force', 'Overwrite existing service directory').option('--dry-run', 'Show what would be created without creating files').option('--multi-domain', 'Generate configurations for multiple domains').action((serviceName, options) => {
21
- this.initializeService(serviceName, options);
22
- });
23
- program.on('--help', () => {
24
- console.log('\nExamples:');
25
- console.log(' clodo-init-service my-api --type api-gateway --domains "api.example.com,staging.example.com"');
26
- console.log(' clodo-init-service data-service --type data-service --api-token $CF_TOKEN');
27
- console.log(' clodo-init-service my-service --env production --account-id $CF_ACCOUNT_ID');
28
- console.log('\nServices are created in: ./services/{service-name}/');
29
- console.log('\nEnvironment Variables:');
30
- console.log(' CLOUDFLARE_API_TOKEN - API token for domain discovery');
31
- console.log(' CLOUDFLARE_ACCOUNT_ID - Account ID for configurations');
32
- console.log(' CLOUDFLARE_ZONE_ID - Zone ID for domain configurations');
33
- });
34
- }
35
- async initializeService(serviceName, options) {
36
- try {
37
- console.log('🚀 Clodo Framework - Service Initializer');
38
- console.log('='.repeat(50));
39
- console.log('📦 Using Clodo Framework ServiceInitializer module');
40
- console.log('');
41
- const initializer = new ServiceInitializer();
42
- const result = await initializer.initializeService(serviceName, {
43
- type: options.type,
44
- domains: options.domains,
45
- env: options.env,
46
- apiToken: options.apiToken,
47
- accountId: options.accountId,
48
- zoneId: options.zoneId,
49
- output: options.output,
50
- force: options.force,
51
- dryRun: options.dryRun
52
- });
53
- if (result.success) {
54
- if (result.dryRun) {
55
- console.log('📋 Dry run - would create the following:');
56
- const configs = Array.isArray(result.configs) ? result.configs : [result.configs];
57
- console.log('Files:', configs.filter(Boolean).join(', '));
58
- return;
59
- }
60
- console.log('\n✅ Service initialized successfully!');
61
- console.log('\n📝 Next steps:');
62
- console.log(` cd services/${serviceName}`);
63
- console.log(' npm install');
64
- console.log(' npm run dev # Start development server');
65
- console.log(' npm run deploy # Deploy to Cloudflare');
66
- } else {
67
- console.error('\n❌ Initialization failed:', result.error);
68
- process.exit(1);
69
- }
70
- } catch (error) {
71
- console.error('\n❌ Unexpected error:', error.message);
72
- process.exit(1);
73
- }
74
- }
75
- }
76
-
77
- // Run the CLI
78
- const cli = new ServiceInitializerCLI();
79
- program.parse();
@@ -1,623 +0,0 @@
1
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
2
- // eslint-disable-next-line no-unused-vars
3
- import { resolve, join } from 'path';
4
- import toml from '@iarna/toml';
5
- import { createDomainConfigSchema, validateDomainConfig, createDomainRegistry } from './domains.js';
6
- import { getDirname } from '../utils/esm-helper.js';
7
- const __dirname = getDirname(import.meta.url, 'src/config');
8
-
9
- // Simple inline logger to avoid circular dependency with index.js
10
- const logger = {
11
- info: (message, ...args) => console.log(`[CustomerConfig] ${message}`, ...args),
12
- error: (message, ...args) => console.error(`[CustomerConfig] ${message}`, ...args),
13
- warn: (message, ...args) => console.warn(`[CustomerConfig] ${message}`, ...args),
14
- debug: (message, ...args) => console.debug(`[CustomerConfig] ${message}`, ...args)
15
- };
16
-
17
- /**
18
- * Customer Configuration Manager
19
- * Manages multi-environment, multi-customer configuration structure
20
- * Integrates with existing domain and feature flag systems
21
- */
22
- export class CustomerConfigurationManager {
23
- constructor(options = {}) {
24
- this.configDir = options.configDir || resolve(__dirname, '..', '..', 'config');
25
- this.environments = options.environments || ['development', 'staging', 'production'];
26
- this.domainRegistry = createDomainRegistry({});
27
- this.customers = new Map();
28
- }
29
-
30
- /**
31
- * Create a new customer configuration from template
32
- * @param {string} customerName - Customer identifier
33
- * @param {string} domain - Customer domain (optional)
34
- * @param {Object} options - Additional customer options
35
- */
36
- async createCustomer(customerName, domain = null, options = {}) {
37
- logger.info(`Creating customer configuration: ${customerName}`);
38
- const customerDir = resolve(this.configDir, 'customers', customerName);
39
-
40
- // Create customer directory structure
41
- if (!existsSync(customerDir)) {
42
- mkdirSync(customerDir, {
43
- recursive: true
44
- });
45
- logger.info(`Created customer directory: ${customerDir}`);
46
- }
47
-
48
- // Create domain configuration for customer
49
- const domainConfig = this.createCustomerDomainConfig(customerName, domain, options);
50
- this.domainRegistry.add(customerName, domainConfig);
51
-
52
- // Create environment-specific configs from templates
53
- for (const env of this.environments) {
54
- await this.createCustomerEnvironment(customerName, env, domain, options);
55
- }
56
-
57
- // Store customer metadata
58
- this.customers.set(customerName, {
59
- name: customerName,
60
- domain: domain,
61
- createdAt: new Date().toISOString(),
62
- environments: this.environments,
63
- ...options
64
- });
65
- logger.info(`Customer ${customerName} configuration created successfully`);
66
- return this.getCustomerInfo(customerName);
67
- }
68
-
69
- /**
70
- * Create domain configuration for customer
71
- */
72
- createCustomerDomainConfig(customerName, domain, options) {
73
- const baseConfig = createDomainConfigSchema();
74
-
75
- // Generate customer-specific domain config
76
- const domainConfig = {
77
- ...baseConfig,
78
- name: customerName,
79
- displayName: options.displayName || this.capitalizeFirst(customerName),
80
- accountId: options.accountId || '00000000000000000000000000000000',
81
- // Placeholder for onboarding
82
- zoneId: options.zoneId || '00000000000000000000000000000000',
83
- // Placeholder for onboarding
84
-
85
- domains: {
86
- production: domain || `${customerName}.com`,
87
- staging: `staging.${domain || `${customerName}.com`}`,
88
- development: `dev.${domain || `${customerName}.com`}`
89
- },
90
- features: {
91
- // Customer-specific feature flags
92
- customerIsolation: true,
93
- customDomain: !!domain,
94
- multiEnvironment: true,
95
- ...options.features
96
- },
97
- settings: {
98
- ...baseConfig.settings,
99
- customerName: customerName,
100
- customerDomain: domain,
101
- ...options.settings
102
- }
103
- };
104
-
105
- // Skip validation during initial creation if using placeholders
106
- if (options.skipValidation || !options.accountId || !options.zoneId) {
107
- logger.info(`Created customer domain config for ${customerName} (framework mode - placeholders used)`);
108
- return domainConfig;
109
- }
110
- validateDomainConfig(domainConfig);
111
- return domainConfig;
112
- }
113
-
114
- /**
115
- * Create customer environment config from template
116
- */
117
- // eslint-disable-next-line no-unused-vars
118
- async createCustomerEnvironment(customerName, environment, domain, options) {
119
- const templatePath = resolve(this.configDir, 'customers', 'template', `${environment}.env.template`);
120
- const outputPath = resolve(this.configDir, 'customers', customerName, `${environment}.env`);
121
-
122
- // Check if template exists
123
- if (!existsSync(templatePath)) {
124
- logger.warn(`Template not found: ${templatePath}, skipping ${environment} config`);
125
- return;
126
- }
127
-
128
- // Skip if config already exists
129
- if (existsSync(outputPath)) {
130
- logger.info(`Config already exists: ${outputPath}, skipping`);
131
- return;
132
- }
133
- try {
134
- // Read template and replace placeholders
135
- let template = readFileSync(templatePath, 'utf8');
136
-
137
- // Replace customer-specific placeholders
138
- template = template.replace(/\{\{CUSTOMER_NAME\}\}/g, customerName);
139
- template = template.replace(/\{\{CUSTOMER_DOMAIN\}\}/g, domain || `${customerName}.com`);
140
- template = template.replace(/\{\{ENVIRONMENT\}\}/g, environment);
141
-
142
- // Environment-specific domain replacements
143
- const envDomains = this.getEnvironmentDomains(customerName, domain);
144
- template = template.replace(/\{\{DOMAIN\}\}/g, envDomains[environment]);
145
-
146
- // Write customer config
147
- writeFileSync(outputPath, template);
148
- logger.info(`Created customer config: ${environment}.env for ${customerName}`);
149
- } catch (error) {
150
- logger.error(`Failed to create ${environment} config for ${customerName}:`, error.message);
151
- throw error;
152
- }
153
- }
154
-
155
- /**
156
- * Get environment-specific domains for customer
157
- */
158
- getEnvironmentDomains(customerName, domain) {
159
- const baseDomain = domain || `${customerName}.com`;
160
- return {
161
- production: baseDomain,
162
- staging: `staging.${baseDomain}`,
163
- development: `dev.${baseDomain}`
164
- };
165
- }
166
-
167
- /**
168
- * Validate customer configurations
169
- */
170
- async validateConfigs() {
171
- logger.info('Validating customer configuration structure...');
172
- let valid = true;
173
- const errors = [];
174
-
175
- // Validate base configuration files
176
- const baseFiles = ['wrangler.base.toml', 'variables.base.env'];
177
- for (const file of baseFiles) {
178
- const path = resolve(this.configDir, 'base', file);
179
- if (!existsSync(path)) {
180
- errors.push(`Missing base config: ${file}`);
181
- valid = false;
182
- }
183
- }
184
-
185
- // Validate environment configs
186
- for (const env of this.environments) {
187
- const envFile = `${env}.toml`;
188
- const path = resolve(this.configDir, 'environments', envFile);
189
- if (!existsSync(path)) {
190
- errors.push(`Missing environment config: ${envFile}`);
191
- valid = false;
192
- }
193
- }
194
-
195
- // Validate customer configs
196
- const customersDir = resolve(this.configDir, 'customers');
197
- if (existsSync(customersDir)) {
198
- const customerDirs = this.getCustomerDirectories();
199
- for (const customerName of customerDirs) {
200
- const customerValid = await this.validateCustomerConfig(customerName);
201
- if (!customerValid) {
202
- valid = false;
203
- }
204
- }
205
- }
206
- if (errors.length > 0) {
207
- logger.error('Configuration validation errors:', errors);
208
- }
209
- logger.info(`Configuration validation ${valid ? 'passed' : 'failed'}`);
210
- return {
211
- valid,
212
- errors
213
- };
214
- }
215
-
216
- /**
217
- * Validate individual customer configuration
218
- */
219
- async validateCustomerConfig(customerName) {
220
- const customerDir = resolve(this.configDir, 'customers', customerName);
221
- let valid = true;
222
-
223
- // Check environment files exist
224
- for (const env of this.environments) {
225
- const envFile = resolve(customerDir, `${env}.env`);
226
- if (!existsSync(envFile)) {
227
- logger.error(`Missing ${env}.env for customer ${customerName}`);
228
- valid = false;
229
- }
230
- }
231
-
232
- // Validate domain configuration (skip in framework mode)
233
- try {
234
- const domainConfig = this.domainRegistry.get(customerName);
235
- if (!domainConfig.settings?.isFrameworkMode) {
236
- validateDomainConfig(domainConfig);
237
- } else {
238
- logger.info(`Skipping domain validation for ${customerName} (framework mode)`);
239
- }
240
- } catch (error) {
241
- logger.error(`Invalid domain config for ${customerName}:`, error.message);
242
- valid = false;
243
- }
244
- return valid;
245
- }
246
-
247
- /**
248
- * Show effective configuration for customer/environment
249
- */
250
- showConfig(customerName, environment) {
251
- logger.info(`Showing effective configuration: ${customerName}/${environment}`);
252
-
253
- // Get domain config from registry
254
- let domainConfig;
255
- try {
256
- domainConfig = this.domainRegistry.get(customerName);
257
- } catch (error) {
258
- // If not in registry, try to find customer metadata
259
- const customerMeta = this.customers.get(customerName);
260
- if (customerMeta) {
261
- // Recreate domain config from metadata
262
- domainConfig = this.createCustomerDomainConfig(customerName, customerMeta.domain, customerMeta);
263
- } else {
264
- throw new Error(`Customer not found: ${customerName}`);
265
- }
266
- }
267
- const config = {
268
- customer: customerName,
269
- environment: environment,
270
- domain: domainConfig,
271
- variables: {},
272
- features: {}
273
- };
274
-
275
- // Load base variables
276
- const baseVarsPath = resolve(this.configDir, 'base', 'variables.base.env');
277
- if (existsSync(baseVarsPath)) {
278
- config.variables.base = this.parseEnvFile(baseVarsPath);
279
- }
280
-
281
- // Load customer environment variables
282
- const customerConfigPath = resolve(this.configDir, 'customers', customerName, `${environment}.env`);
283
- if (existsSync(customerConfigPath)) {
284
- config.variables.customer = this.parseEnvFile(customerConfigPath);
285
- }
286
-
287
- // Get domain features
288
- config.features = domainConfig.features || {};
289
- return config;
290
- }
291
-
292
- /**
293
- * Generate deployment command for customer/environment
294
- */
295
- getDeployCommand(customerName, environment) {
296
- const commands = {
297
- development: `wrangler dev --config config/environments/development.toml`,
298
- staging: `wrangler deploy --config config/environments/staging.toml --env staging`,
299
- production: `wrangler deploy --env production`
300
- };
301
- const command = commands[environment];
302
- if (!command) {
303
- throw new Error(`Unknown environment: ${environment}`);
304
- }
305
- logger.info(`Generated deploy command for ${customerName}/${environment}`);
306
- return {
307
- command: command,
308
- customer: customerName,
309
- environment: environment,
310
- configPath: `config/customers/${customerName}/${environment}.env`
311
- };
312
- }
313
-
314
- /**
315
- * Get customer information
316
- */
317
- getCustomerInfo(customerName) {
318
- const domainConfig = this.domainRegistry.get(customerName);
319
- const customerMeta = this.customers.get(customerName);
320
- return {
321
- ...customerMeta,
322
- domainConfig: domainConfig,
323
- configPath: resolve(this.configDir, 'customers', customerName)
324
- };
325
- }
326
-
327
- /**
328
- * List all customers
329
- */
330
- listCustomers() {
331
- return Array.from(this.customers.keys()).map(name => ({
332
- name: name,
333
- ...this.getCustomerInfo(name)
334
- }));
335
- }
336
-
337
- /**
338
- * Load existing customers from filesystem
339
- */
340
- async loadExistingCustomers() {
341
- const customersDir = resolve(this.configDir, 'customers');
342
- if (!existsSync(customersDir)) {
343
- return;
344
- }
345
- try {
346
- // First, try to read from root wrangler.toml (real deployment config)
347
- const rootWranglerPath = resolve(this.configDir, '..', 'wrangler.toml');
348
- let wranglerConfig = null;
349
- let globalAccountId = null;
350
- if (existsSync(rootWranglerPath)) {
351
- try {
352
- const wranglerContent = readFileSync(rootWranglerPath, 'utf8');
353
- wranglerConfig = toml.parse(wranglerContent);
354
- globalAccountId = wranglerConfig.account_id;
355
- logger.info(`Loaded wrangler.toml with account_id: ${globalAccountId ? globalAccountId.substring(0, 8) + '...' : 'not found'}`);
356
- } catch (error) {
357
- logger.warn('Could not parse root wrangler.toml:', error.message);
358
- }
359
- }
360
-
361
- // Read customer directories
362
- const customerDirs = this.getCustomerDirectories();
363
- for (const customerName of customerDirs) {
364
- // Skip template directory
365
- if (customerName === 'template') continue;
366
- const customerDir = resolve(customersDir, customerName);
367
-
368
- // Initialize customer metadata
369
- const metadata = {
370
- name: customerName,
371
- createdAt: new Date().toISOString(),
372
- environments: this.environments,
373
- accountId: globalAccountId // Start with global account ID
374
- };
375
-
376
- // Try to load from wrangler.toml [env.production] section
377
- if (wranglerConfig && wranglerConfig.env && wranglerConfig.env.production) {
378
- const prodEnv = wranglerConfig.env.production;
379
-
380
- // Extract SERVICE_DOMAIN (maps to customer name usually)
381
- if (prodEnv.vars && prodEnv.vars.SERVICE_DOMAIN) {
382
- metadata.serviceDomain = prodEnv.vars.SERVICE_DOMAIN;
383
-
384
- // If SERVICE_DOMAIN matches customer name, this is their config
385
- if (prodEnv.vars.SERVICE_DOMAIN === customerName) {
386
- // Extract database info
387
- if (prodEnv.d1_databases && prodEnv.d1_databases.length > 0) {
388
- const db = prodEnv.d1_databases[0];
389
- metadata.databaseId = db.database_id;
390
- metadata.databaseName = db.database_name;
391
- }
392
-
393
- // Extract zone_id if present
394
- if (prodEnv.zone_id) {
395
- metadata.zoneId = prodEnv.zone_id;
396
- }
397
-
398
- // Extract route to infer domain
399
- if (prodEnv.route) {
400
- // Route format: "example.com/*" or "*.example.com/*"
401
- const domain = prodEnv.route.replace(/\/\*$/, '').replace(/^\*\./, '');
402
- metadata.domain = domain;
403
- }
404
- }
405
- }
406
- }
407
-
408
- // Read customer-specific env file to get CUSTOMER_DOMAIN and other info
409
- const prodConfigPath = resolve(customerDir, 'production.env');
410
- if (existsSync(prodConfigPath)) {
411
- try {
412
- const prodConfig = this.parseEnvFile(prodConfigPath);
413
-
414
- // Use CUSTOMER_DOMAIN (correct field name in real configs)
415
- if (prodConfig.CUSTOMER_DOMAIN) {
416
- metadata.customerDomain = prodConfig.CUSTOMER_DOMAIN.replace(/^https?:\/\//, '');
417
- // If we didn't get domain from wrangler.toml, use this
418
- if (!metadata.domain) {
419
- metadata.domain = metadata.customerDomain;
420
- }
421
- }
422
-
423
- // Also check old DOMAIN field for backward compatibility
424
- if (!metadata.domain && prodConfig.DOMAIN) {
425
- metadata.domain = prodConfig.DOMAIN.replace(/^https?:\/\//, '');
426
- }
427
-
428
- // Extract customer ID
429
- if (prodConfig.CUSTOMER_ID) {
430
- metadata.customerId = prodConfig.CUSTOMER_ID;
431
- }
432
- } catch (error) {
433
- logger.warn(`Could not parse customer env for ${customerName}:`, error.message);
434
- }
435
- }
436
-
437
- // Check if secrets exist (we can't read them, but can note they should be set)
438
- // In a real system, you'd run: wrangler secret list --env production
439
- // For now, we just note that secrets should be managed separately
440
- metadata.hasSecrets = true; // Assume secrets are managed via wrangler secret commands
441
-
442
- // Register customer
443
- this.customers.set(customerName, metadata);
444
-
445
- // Try to register domain config
446
- try {
447
- const domainConfig = this.createCustomerDomainConfig(customerName, metadata.domain, {
448
- skipValidation: true,
449
- isFrameworkMode: true,
450
- accountId: metadata.accountId,
451
- zoneId: metadata.zoneId
452
- });
453
- this.domainRegistry.add(customerName, domainConfig);
454
- } catch (error) {
455
- logger.warn(`Could not register domain for existing customer ${customerName}:`, error.message);
456
- }
457
- logger.info(`Loaded existing customer: ${customerName}`);
458
- }
459
- } catch (error) {
460
- logger.error('Error loading existing customers:', error.message);
461
- }
462
- }
463
-
464
- /**
465
- * Get customer directories from filesystem
466
- */
467
- getCustomerDirectories() {
468
- const customersDir = resolve(this.configDir, 'customers');
469
- if (!existsSync(customersDir)) {
470
- return [];
471
- }
472
- try {
473
- // Read directory contents
474
- const items = readdirSync(customersDir);
475
- return items.filter(item => {
476
- const itemPath = resolve(customersDir, item);
477
- return statSync(itemPath).isDirectory() && item !== 'template';
478
- });
479
- } catch (error) {
480
- logger.error('Error reading customer directories:', error.message);
481
- return [];
482
- }
483
- }
484
-
485
- /**
486
- * Parse environment file
487
- */
488
- parseEnvFile(filePath) {
489
- const content = readFileSync(filePath, 'utf8');
490
- const variables = {};
491
- content.split('\n').forEach(line => {
492
- const trimmed = line.trim();
493
- if (trimmed && !trimmed.startsWith('#')) {
494
- const [key, ...valueParts] = trimmed.split('=');
495
- if (key && valueParts.length > 0) {
496
- variables[key.trim()] = valueParts.join('=').trim();
497
- }
498
- }
499
- });
500
- return variables;
501
- }
502
-
503
- /**
504
- * Update wrangler.toml with new configuration
505
- * @param {string} wranglerPath - Path to wrangler.toml
506
- * @param {Object} updates - Configuration updates to merge
507
- * @returns {boolean} Success status
508
- */
509
- updateWranglerToml(wranglerPath, updates) {
510
- try {
511
- let existingConfig = {};
512
-
513
- // Read existing config if file exists
514
- if (existsSync(wranglerPath)) {
515
- const content = readFileSync(wranglerPath, 'utf8');
516
- existingConfig = toml.parse(content);
517
- }
518
-
519
- // Deep merge updates into existing config
520
- const mergedConfig = this.deepMergeConfig(existingConfig, updates);
521
-
522
- // Write back to file
523
- const tomlContent = toml.stringify(mergedConfig);
524
- writeFileSync(wranglerPath, tomlContent, 'utf8');
525
- logger.info(`Updated wrangler.toml: ${wranglerPath}`);
526
- return true;
527
- } catch (error) {
528
- logger.error(`Failed to update wrangler.toml: ${error.message}`);
529
- return false;
530
- }
531
- }
532
-
533
- /**
534
- * Deep merge two configuration objects
535
- * @private
536
- */
537
- deepMergeConfig(target, source) {
538
- const output = {
539
- ...target
540
- };
541
- for (const key in source) {
542
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
543
- // Recursively merge objects
544
- output[key] = this.deepMergeConfig(target[key] || {}, source[key]);
545
- } else {
546
- // Overwrite arrays and primitives
547
- output[key] = source[key];
548
- }
549
- }
550
- return output;
551
- }
552
-
553
- /**
554
- * Create or update environment section in wrangler.toml
555
- * @param {string} wranglerPath - Path to wrangler.toml
556
- * @param {string} environment - Environment name (production, staging, development)
557
- * @param {Object} envConfig - Environment configuration
558
- * @returns {boolean} Success status
559
- */
560
- updateEnvironmentConfig(wranglerPath, environment, envConfig) {
561
- const updates = {
562
- env: {
563
- [environment]: envConfig
564
- }
565
- };
566
- return this.updateWranglerToml(wranglerPath, updates);
567
- }
568
-
569
- /**
570
- * Add D1 database binding to environment
571
- * @param {string} wranglerPath - Path to wrangler.toml
572
- * @param {string} environment - Environment name
573
- * @param {Object} databaseConfig - Database configuration
574
- * @returns {boolean} Success status
575
- */
576
- addD1Database(wranglerPath, environment, databaseConfig) {
577
- try {
578
- const content = readFileSync(wranglerPath, 'utf8');
579
- const config = toml.parse(content);
580
-
581
- // Ensure env section exists
582
- if (!config.env) config.env = {};
583
- if (!config.env[environment]) config.env[environment] = {};
584
-
585
- // Ensure d1_databases array exists
586
- if (!config.env[environment].d1_databases) {
587
- config.env[environment].d1_databases = [];
588
- }
589
-
590
- // Add or update database
591
- const existingIndex = config.env[environment].d1_databases.findIndex(db => db.binding === databaseConfig.binding);
592
- if (existingIndex >= 0) {
593
- config.env[environment].d1_databases[existingIndex] = databaseConfig;
594
- } else {
595
- config.env[environment].d1_databases.push(databaseConfig);
596
- }
597
-
598
- // Write back
599
- writeFileSync(wranglerPath, toml.stringify(config), 'utf8');
600
- logger.info(`Added D1 database to ${environment} environment`);
601
- return true;
602
- } catch (error) {
603
- logger.error(`Failed to add D1 database: ${error.message}`);
604
- return false;
605
- }
606
- }
607
-
608
- /**
609
- * Capitalize first letter
610
- */
611
- capitalizeFirst(str) {
612
- return str.charAt(0).toUpperCase() + str.slice(1);
613
- }
614
- }
615
-
616
- // Default instance
617
- export const customerConfigManager = new CustomerConfigurationManager();
618
-
619
- // Convenience functions
620
- export const createCustomer = (name, domain, options) => customerConfigManager.createCustomer(name, domain, options);
621
- export const validateCustomerConfigs = () => customerConfigManager.validateConfigs();
622
- export const showCustomerConfig = (customer, env) => customerConfigManager.showConfig(customer, env);
623
- export const getCustomerDeployCommand = (customer, env) => customerConfigManager.getDeployCommand(customer, env);