@tamyla/clodo-framework 3.1.23 → 3.1.25

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 (49) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cli/clodo-simple.js +111 -0
  3. package/dist/cli/commands/assess.js +29 -20
  4. package/dist/cli/commands/create.js +22 -31
  5. package/dist/cli/commands/deploy.js +20 -333
  6. package/dist/cli/commands/diagnose.js +26 -26
  7. package/dist/cli/commands/helpers/deployment-verification.js +2 -2
  8. package/dist/cli/commands/helpers/error-recovery.js +1 -1
  9. package/dist/cli/commands/helpers/resource-detection.js +1 -1
  10. package/dist/cli/commands/init-config.js +1 -1
  11. package/dist/cli/commands/update.js +46 -19
  12. package/dist/cli/commands/validate.js +24 -34
  13. package/dist/cli/security-cli.js +1 -1
  14. package/dist/deployment/wrangler-deployer.js +1 -1
  15. package/dist/index.js +5 -2
  16. package/dist/lib/deployment/modules/DeploymentOrchestrator.js +2 -2
  17. package/dist/lib/deployment/modules/EnvironmentManager.js +2 -2
  18. package/dist/lib/shared/cloudflare/domain-manager.js +1 -1
  19. package/dist/lib/shared/cloudflare/ops.js +4 -4
  20. package/dist/lib/shared/config/command-config-manager.js +1 -1
  21. package/dist/lib/shared/config/index.js +1 -1
  22. package/dist/lib/shared/config/manifest-loader.js +3 -3
  23. package/dist/lib/shared/deployment/credential-collector.js +1 -1
  24. package/dist/lib/shared/deployment/index.js +2 -2
  25. package/dist/lib/shared/deployment/rollback-manager.js +1 -1
  26. package/dist/lib/shared/deployment/utilities/d1-error-recovery.js +1 -1
  27. package/dist/lib/shared/deployment/validator.js +1 -1
  28. package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +1 -1
  29. package/dist/lib/shared/monitoring/health-checker.js +2 -2
  30. package/dist/lib/shared/routing/domain-router.js +1 -1
  31. package/dist/lib/shared/utils/config-loader.js +47 -11
  32. package/dist/lib/shared/utils/service-config-manager.js +227 -0
  33. package/dist/lib/shared/validation/ValidationRegistry.js +1 -1
  34. package/dist/orchestration/cross-domain-coordinator.js +5 -5
  35. package/dist/orchestration/multi-domain-orchestrator.js +51 -0
  36. package/dist/security/index.js +2 -2
  37. package/dist/service-management/ConfirmationEngine.js +1 -1
  38. package/dist/service-management/ErrorTracker.js +1 -1
  39. package/dist/service-management/InputCollector.js +1 -1
  40. package/dist/service-management/ServiceOrchestrator.js +120 -2
  41. package/dist/service-management/generators/testing/UnitTestsGenerator.js +4 -4
  42. package/dist/service-management/handlers/ValidationHandler.js +22 -9
  43. package/dist/simple-api.js +94 -0
  44. package/dist/utils/cloudflare/ops.js +1 -1
  45. package/dist/utils/file-manager.js +1 -1
  46. package/dist/utils/formatters.js +1 -1
  47. package/dist/utils/logger.js +1 -1
  48. package/dist/worker/integration.js +1 -1
  49. package/package.json +2 -1
@@ -1,41 +1,16 @@
1
- /**
2
- * Deploy Command - Smart minimal input deployment with service detection
3
- *
4
- * Input Strategy: SMART MINIMAL WITH DOMAIN ROUTING
5
- * - Detects Clodo services OR legacy services (wrangler.toml)
6
- * - Supports multiple manifest locations
7
- * - Uses DomainRouter for intelligent domain selection
8
- * - Gathers credentials smartly: env vars → flags → interactive collection with auto-fetch
9
- * - Validates token and fetches account ID & zone ID from Cloudflare
10
- * - REFACTORED (Task 3.2): Integrates with MultiDomainOrchestrator for full deployment orchestration
11
- * - REFACTORED (UX): Modularized with helper functions for better maintainability
12
- */
13
-
14
1
  import chalk from 'chalk';
15
- import { resolve, join } from 'path';
16
- import { existsSync } from 'fs';
17
- import { ManifestLoader } from '../lib/shared/config/manifest-loader.js';
18
- import { CloudflareServiceValidator } from '../lib/shared/config/cloudflare-service-validator.js';
19
- import { DeploymentCredentialCollector } from '../lib/shared/deployment/credential-collector.js';
20
- import { StandardOptions } from '../lib/shared/utils/cli-options.js';
21
- import { ConfigLoader } from '../lib/shared/utils/config-loader.js';
22
- import { DomainRouter } from '../lib/shared/routing/domain-router.js';
23
- import { MultiDomainOrchestrator } from '../orchestration/multi-domain-orchestrator.js';
24
-
25
- // Import modular helpers
26
- import { detectExistingResources, displayDeploymentPlan } from './helpers/resource-detection.js';
27
- import { confirmDeployment, displayDeploymentResults, displayNextSteps } from './helpers/deployment-ui.js';
28
- import { runPostDeploymentVerification } from './helpers/deployment-verification.js';
29
- import { handleDeploymentError } from './helpers/error-recovery.js';
2
+ import { Clodo } from '../../src/simple-api.js';
3
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
4
+ import { ConfigLoader } from '../../lib/shared/utils/config-loader.js';
30
5
  export function registerDeployCommand(program) {
31
6
  const command = program.command('deploy').description('Deploy a Clodo service with smart credential handling and domain selection')
32
7
  // Cloudflare-specific options
33
- .option('--token <token>', 'Cloudflare API token').option('--account-id <id>', 'Cloudflare account ID').option('--zone-id <id>', 'Cloudflare zone ID').option('--domain <domain>', 'Specific domain to deploy to (otherwise prompted if multiple exist)').option('--environment <env>', 'Target environment (development, staging, production)', 'production').option('--development', 'Deploy to development environment (shorthand for --environment development)').option('--staging', 'Deploy to staging environment (shorthand for --environment staging)').option('--production', 'Deploy to production environment (shorthand for --environment production)').option('--all-domains', 'Deploy to all configured domains (ignores --domain flag)').option('--dry-run', 'Simulate deployment without making changes').option('-y, --yes', 'Skip confirmation prompts (for CI/CD)').option('--service-path <path>', 'Path to service directory', '.');
8
+ .option('--token <token>', 'Cloudflare API token').option('--account-id <id>', 'Cloudflare account ID').option('--zone-id <id>', 'Cloudflare zone ID').option('--domain <domain>', 'Specific domain to deploy to (otherwise prompted if multiple exist)').option('--environment <env>', 'Target environment (development, staging, production)', 'production').option('--development', 'Deploy to development environment (shorthand for --environment development)').option('--staging', 'Deploy to staging environment (shorthand for --environment staging)').option('--production', 'Deploy to production environment (shorthand for --environment production)').option('--dry-run', 'Simulate deployment without making changes').option('-y, --yes', 'Skip confirmation prompts (for CI/CD)').option('--service-path <path>', 'Path to service directory', '.');
34
9
 
35
10
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
36
11
  StandardOptions.define(command).action(async options => {
37
12
  try {
38
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
13
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
39
14
  const configLoader = new ConfigLoader({
40
15
  verbose: options.verbose,
41
16
  quiet: options.quiet,
@@ -60,321 +35,33 @@ export function registerDeployCommand(program) {
60
35
  }
61
36
  }
62
37
 
63
- // Substitute environment variables in config
64
- configFileData = configLoader.substituteEnvironmentVariables(configFileData);
65
-
66
38
  // Merge config file defaults with CLI options (CLI takes precedence)
67
39
  const mergedOptions = configLoader.merge(configFileData, options);
68
- output.info('🚀 Clodo Service Deployment');
69
-
70
- // Step 1: Load and validate service configuration
71
- const servicePath = resolve(mergedOptions.servicePath || '.');
72
- const serviceConfig = await ManifestLoader.loadAndValidateCloudflareService(servicePath);
73
- if (!serviceConfig.manifest) {
74
- if (serviceConfig.error === 'NOT_A_CLOUDFLARE_SERVICE') {
75
- ManifestLoader.printNotCloudflareServiceError(servicePath);
76
- } else if (serviceConfig.error === 'CLOUDFLARE_SERVICE_INVALID') {
77
- // Pass false because we're validating a detected service, not a Clodo manifest
78
- ManifestLoader.printValidationErrors(serviceConfig.validationResult, false);
79
- }
80
- process.exit(1);
81
- }
82
-
83
- // Print service info and validation results
84
- if (serviceConfig.validationResult) {
85
- CloudflareServiceValidator.printValidationReport(serviceConfig.validationResult.validation);
86
- }
87
- ManifestLoader.printManifestInfo(serviceConfig.manifest);
88
- console.log(chalk.blue(`ℹ️ Configuration loaded from: ${serviceConfig.foundAt}`));
89
40
 
90
- // Step 2: Smart credential gathering with interactive collection
91
- // Uses DeploymentCredentialCollector which:
92
- // - Checks flags and env vars first
93
- // - Prompts for API token if needed
94
- // - Validates token and auto-fetches account ID & zone ID
95
- // - Caches credentials for future use
96
- const credentialCollector = new DeploymentCredentialCollector({
97
- servicePath: servicePath,
98
- quiet: mergedOptions.quiet
99
- });
100
- let credentials;
101
- try {
102
- credentials = await credentialCollector.collectCredentials({
41
+ // Use simple API for deployment
42
+ const result = await Clodo.deploy({
43
+ servicePath: mergedOptions.servicePath || '.',
44
+ environment: mergedOptions.environment || 'production',
45
+ domain: mergedOptions.domain,
46
+ dryRun: mergedOptions.dryRun || false,
47
+ credentials: {
103
48
  token: mergedOptions.token,
104
49
  accountId: mergedOptions.accountId,
105
50
  zoneId: mergedOptions.zoneId
106
- });
107
- } finally {
108
- credentialCollector.cleanup();
109
- }
110
-
111
- // Step 3: Initialize DomainRouter for intelligent domain selection
112
- // REFACTORED (Task 3.2): Use DomainRouter for domain detection and selection
113
- console.log(chalk.cyan('\n🗺️ Detecting available domains...\n'));
114
- const router = new DomainRouter({
115
- environment: mergedOptions.environment || 'production',
116
- verbose: options.verbose,
117
- configPath: options.configPath,
118
- disableOrchestrator: false,
119
- // We'll use the orchestrator for deployment
120
- orchestratorOptions: {
121
- dryRun: options.dryRun || false,
122
- cloudflareToken: credentials.token,
123
- cloudflareAccountId: credentials.accountId
124
51
  }
125
52
  });
126
-
127
- // Detect domains from manifest
128
- let detectedDomains = [];
129
- const manifest = serviceConfig.manifest;
130
- const config = manifest.deployment || manifest.configuration || {};
131
- const domainsConfig = config.domains || [];
132
- if (Array.isArray(domainsConfig) && domainsConfig.length > 0) {
133
- // Handle both array of strings and array of objects
134
- detectedDomains = domainsConfig.map(d => {
135
- if (typeof d === 'string') return d;
136
- if (typeof d === 'object' && d.name) return d.name;
137
- return null;
138
- }).filter(d => d !== null);
139
- }
140
-
141
- // If no domains in manifest but we have a selected zone from credentials, use that
142
- if (detectedDomains.length === 0 && credentials.zoneName) {
143
- detectedDomains = [credentials.zoneName];
144
- if (!options.quiet) {
145
- console.log(chalk.blue(`ℹ️ Using selected Cloudflare domain: ${credentials.zoneName}`));
146
- }
147
- }
148
-
149
- // If still no domains and it's a detected CF service, use default
150
- if (detectedDomains.length === 0 && manifest._source === 'cloudflare-service-detected') {
151
- // For detected CF services, use default
152
- detectedDomains = ['workers.cloudflare.com'];
153
- if (!options.quiet) {
154
- console.log(chalk.blue(`ℹ️ No custom domains configured, using default: workers.cloudflare.com`));
155
- }
156
- } else if (detectedDomains.length > 0 && !options.quiet && !credentials.zoneName) {
157
- console.log(chalk.blue(`ℹ️ Found ${detectedDomains.length} configured domain(s) in manifest`));
158
- }
159
-
160
- // Domain selection: check CLI flag first, then prompt user
161
- let selectedDomain = mergedOptions.domain;
162
- if (selectedDomain && !options.quiet) {
163
- console.log(chalk.blue(`ℹ️ Using domain from --domain flag: ${selectedDomain}`));
164
- }
165
- if (!selectedDomain && detectedDomains.length > 0) {
166
- if (detectedDomains.length === 1) {
167
- // Only one domain, use it directly
168
- selectedDomain = detectedDomains[0];
169
- if (!options.quiet) {
170
- console.log(chalk.green(`✓ Auto-selected only available domain: ${selectedDomain}`));
171
- }
172
- } else {
173
- // Multiple domains available - let user choose
174
- console.log(chalk.cyan('📍 Available domains:'));
175
- detectedDomains.forEach((d, i) => {
176
- console.log(chalk.white(` ${i + 1}) ${d}`));
177
- });
178
-
179
- // If running interactively, prompt user
180
- if (process.stdin.isTTY) {
181
- const {
182
- createPromptModule
183
- } = await import('inquirer');
184
- const prompt = createPromptModule();
185
- const response = await prompt([{
186
- type: 'list',
187
- name: 'selectedDomain',
188
- message: 'Select domain to deploy to:',
189
- choices: detectedDomains,
190
- default: detectedDomains[0]
191
- }]);
192
- selectedDomain = response.selectedDomain;
193
- } else {
194
- // Non-interactive mode: use first domain
195
- selectedDomain = detectedDomains[0];
196
- console.log(chalk.yellow(`⚠️ Non-interactive mode: using first domain: ${selectedDomain}`));
197
- }
198
- }
199
- }
200
- if (!selectedDomain) {
201
- if (!options.quiet) {
202
- console.error(chalk.yellow('⚠️ No domain configured for deployment'));
203
- console.error(chalk.gray('For Clodo services: add deployment.domains in clodo-service-manifest.json'));
204
- console.error(chalk.gray('For detected CF services: define routes in wrangler.toml'));
205
- }
206
- process.exit(1);
207
- }
208
-
209
- // Step 4: Validate domain configuration
210
- console.log(chalk.cyan('\n🔍 Validating domain configuration...\n'));
211
- const validation = router.validateConfiguration({
212
- domains: [selectedDomain],
213
- environment: mergedOptions.environment || 'production'
214
- });
215
- if (!validation.valid) {
216
- console.error(chalk.red('❌ Configuration validation failed:'));
217
- validation.errors.forEach(err => {
218
- console.error(chalk.yellow(` • ${err}`));
219
- });
220
- process.exit(1);
221
- }
222
- if (validation.warnings && validation.warnings.length > 0) {
223
- console.log(chalk.yellow('⚠️ Configuration warnings:'));
224
- validation.warnings.forEach(warn => {
225
- console.log(chalk.gray(` • ${warn}`));
226
- });
227
- }
228
-
229
- // Extract service metadata
230
- const serviceName = manifest.serviceName || 'unknown-service';
231
- const serviceType = manifest.serviceType || 'generic';
232
-
233
- // Step 5: Detect existing resources and display deployment plan
234
- const {
235
- existingWorker,
236
- existingDatabases,
237
- resourceDetectionFailed
238
- } = await detectExistingResources(serviceName, manifest, credentials, output, options.verbose);
239
- displayDeploymentPlan({
240
- serviceName,
241
- serviceType,
242
- selectedDomain,
243
- environment: mergedOptions.environment,
244
- credentials,
245
- dryRun: options.dryRun,
246
- existingWorker,
247
- existingDatabases,
248
- resourceDetectionFailed,
249
- manifest
250
- });
251
-
252
- // Step 6: Get user confirmation
253
- const confirmed = await confirmDeployment(options);
254
- if (!confirmed) {
255
- process.exit(0);
256
- }
257
-
258
- // Step 5: Initialize MultiDomainOrchestrator for deployment
259
- // REFACTORED (Task 3.2): Direct orchestrator integration instead of external deployer
260
- console.log(chalk.cyan('\n⚙️ Initializing orchestration system...\n'));
261
-
262
- // Determine wrangler config path based on selected domain/zone
263
- // For multi-customer deployments, use customer-specific config if available
264
- let wranglerConfigPath;
265
- const configDir = join(servicePath, 'config');
266
-
267
- // Map domain to config file (customize this mapping for your setup)
268
- if (selectedDomain === 'clodo.dev' || credentials.cloudflareSettings?.zoneName === 'clodo.dev') {
269
- // Use config/wrangler.toml for clodo.dev domain
270
- const clodoConfigPath = join(configDir, 'wrangler.toml');
271
- if (existsSync(clodoConfigPath)) {
272
- wranglerConfigPath = clodoConfigPath;
273
- console.log(chalk.green(`✓ Using clodo.dev config: ${clodoConfigPath}`));
274
- }
275
- }
276
- // Add more domain mappings as needed:
277
- // else if (selectedDomain === 'customer2.com') {
278
- // wranglerConfigPath = path.join(configDir, 'customers', 'customer2-wrangler.toml');
279
- // }
280
-
281
- // If no specific config found, use default (root wrangler.toml)
282
- if (!wranglerConfigPath) {
283
- console.log(chalk.yellow(`ℹ Using default wrangler.toml from service root`));
284
- }
285
- const orchestrator = new MultiDomainOrchestrator({
286
- domains: [selectedDomain],
287
- environment: mergedOptions.environment || 'production',
288
- dryRun: options.dryRun || false,
289
- skipTests: false,
290
- parallelDeployments: 1,
291
- // Single domain in this flow
292
- servicePath: servicePath,
293
- serviceName: serviceName,
294
- // Pass service name for custom domain construction
295
- wranglerConfigPath: wranglerConfigPath,
296
- // Pass custom config path if found
297
- // Use cloudflareSettings object for complete zone-specific configuration
298
- cloudflareSettings: credentials.cloudflareSettings,
299
- enablePersistence: !options.dryRun,
300
- rollbackEnabled: !options.dryRun,
301
- verbose: options.verbose
302
- });
303
- try {
304
- await orchestrator.initialize();
305
- } catch (err) {
306
- console.error(chalk.red('❌ Failed to initialize orchestrator'));
307
- console.error(chalk.yellow(`Error: ${err.message}`));
308
- if (process.env.DEBUG) {
309
- console.error(chalk.gray(err.stack));
53
+ if (result.success) {
54
+ output.success(result.message);
55
+ if (result.deployedDomains && result.deployedDomains.length > 0) {
56
+ output.info(`Deployed to domains: ${result.deployedDomains.join(', ')}`);
310
57
  }
58
+ } else {
59
+ output.error('Deployment failed');
311
60
  process.exit(1);
312
61
  }
313
-
314
- // Step 7: Execute deployment via orchestrator
315
- console.log(chalk.cyan('🚀 Starting deployment via orchestrator...\n'));
316
- let result;
317
- try {
318
- result = await orchestrator.deploySingleDomain(selectedDomain, {
319
- manifest: manifest,
320
- credentials: credentials,
321
- dryRun: options.dryRun,
322
- environment: mergedOptions.environment || 'production'
323
- });
324
- } catch (deployError) {
325
- await handleDeploymentError(deployError, command, options);
326
- }
327
-
328
- // Step 8: Display deployment results
329
- displayDeploymentResults({
330
- result,
331
- serviceName,
332
- serviceType,
333
- selectedDomain,
334
- environment: mergedOptions.environment
335
- });
336
-
337
- // Step 9: Run post-deployment verification and health check
338
- await runPostDeploymentVerification(serviceName, result, credentials, {
339
- ...options,
340
- serviceName,
341
- customDomain: selectedDomain !== 'workers.cloudflare.com' ? selectedDomain : null
342
- });
343
-
344
- // Step 10: Display next steps
345
- displayNextSteps({
346
- result,
347
- selectedDomain,
348
- serviceName,
349
- dryRun: options.dryRun
350
- });
351
- if (process.env.DEBUG && result.details) {
352
- console.log(chalk.gray('📋 Full Result:'));
353
- console.log(chalk.gray(JSON.stringify(result, null, 2)));
354
- }
355
62
  } catch (error) {
356
- console.error(chalk.red(`\n❌ Deployment failed: ${error.message}`));
357
- if (error.message.includes('credentials') || error.message.includes('auth')) {
358
- console.error(chalk.yellow('\n💡 Credential Issue:'));
359
- console.error(chalk.white(' Check your API token, account ID, and zone ID'));
360
- console.error(chalk.white(' Visit: https://dash.cloudflare.com/profile/api-tokens'));
361
- }
362
- if (error.message.includes('domain') || error.message.includes('zone')) {
363
- console.error(chalk.yellow('\n💡 Domain Issue:'));
364
- console.error(chalk.white(' Verify domain exists in Cloudflare'));
365
- console.error(chalk.white(' Check API token has zone:read permissions'));
366
- }
367
- if (error.message.includes('orchestration') || error.message.includes('initialization')) {
368
- console.error(chalk.yellow('\n💡 Orchestration Issue:'));
369
- console.error(chalk.white(' Check MultiDomainOrchestrator configuration'));
370
- console.error(chalk.white(' Verify all modular components loaded correctly'));
371
- }
372
- if (process.env.DEBUG) {
373
- console.error(chalk.gray('\nFull Stack Trace:'));
374
- console.error(chalk.gray(error.stack));
375
- } else {
376
- console.error(chalk.gray('Run with DEBUG=1 for full stack trace'));
377
- }
63
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
64
+ output.error(`Deployment failed: ${error.message}`);
378
65
  process.exit(1);
379
66
  }
380
67
  });
@@ -1,41 +1,41 @@
1
1
  import chalk from 'chalk';
2
- import { ServiceOrchestrator } from '../service-management/ServiceOrchestrator.js';
3
- import { StandardOptions } from '../lib/shared/utils/cli-options.js';
4
- import { ConfigLoader } from '../lib/shared/utils/config-loader.js';
2
+ import path from 'path';
3
+ import { ServiceOrchestrator } from '../../src/service-management/ServiceOrchestrator.js';
4
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
5
+ import { ServiceConfigManager } from '../../lib/shared/utils/service-config-manager.js';
5
6
  export function registerDiagnoseCommand(program) {
6
- const command = program.command('diagnose [service-path]').description('Diagnose and report issues with an existing service').option('--deep-scan', 'Perform deep analysis including dependencies and deployment readiness').option('--export-report <file>', 'Export diagnostic report to file').option('--fix-suggestions', 'Include suggested fixes for issues');
7
+ const command = program.command('diagnose [service-path]').description('Diagnose and report issues with an existing service').option('--deep-scan', 'Perform deep analysis including dependencies and deployment readiness').option('--export-report <file>', 'Export diagnostic report to file').option('--fix-suggestions', 'Include suggested fixes for issues').option('--show-config-sources', 'Display all configuration sources and merged result');
7
8
 
8
9
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
9
10
  StandardOptions.define(command).action(async (servicePath, options) => {
10
11
  try {
11
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
12
- const configLoader = new ConfigLoader({
12
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
13
+ const configManager = new ServiceConfigManager({
13
14
  verbose: options.verbose,
14
15
  quiet: options.quiet,
15
- json: options.json
16
+ json: options.json,
17
+ showSources: options.showConfigSources
16
18
  });
17
-
18
- // Load config from file if specified
19
- let configFileData = {};
20
- if (options.configFile) {
21
- configFileData = configLoader.loadSafe(options.configFile, {});
22
- if (options.verbose && !options.quiet) {
23
- output.info(`Loaded configuration from: ${options.configFile}`);
24
- }
25
- }
26
-
27
- // Merge config file defaults with CLI options (CLI takes precedence)
28
- const mergedOptions = configLoader.merge(configFileData, options);
29
19
  const orchestrator = new ServiceOrchestrator();
30
20
 
31
- // Auto-detect service path if not provided
32
- if (!servicePath) {
33
- servicePath = await orchestrator.detectServicePath();
34
- if (!servicePath) {
35
- output.error('No service path provided and could not auto-detect service directory');
36
- process.exit(1);
21
+ // Validate service path with better error handling
22
+ try {
23
+ servicePath = await configManager.validateServicePath(servicePath, orchestrator);
24
+ } catch (error) {
25
+ output.error(error.message);
26
+ if (error.suggestions) {
27
+ output.info('Suggestions:');
28
+ output.list(error.suggestions);
37
29
  }
30
+ process.exit(1);
38
31
  }
32
+
33
+ // Load and merge all configurations
34
+ const mergedOptions = await configManager.loadServiceConfig(servicePath, options, {
35
+ deepScan: false,
36
+ exportReport: null,
37
+ fixSuggestions: false
38
+ });
39
39
  output.info('🔍 Diagnosing service...');
40
40
  const diagnosis = await orchestrator.diagnoseService(servicePath, mergedOptions);
41
41
 
@@ -75,7 +75,7 @@ export function registerDiagnoseCommand(program) {
75
75
  process.exit(1);
76
76
  }
77
77
  } catch (error) {
78
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
78
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
79
79
  output.error(`Diagnosis failed: ${error.message}`);
80
80
  process.exit(1);
81
81
  }
@@ -3,7 +3,7 @@
3
3
  * Provides UI-specific deployment verification by delegating to shared infrastructure
4
4
  */
5
5
 
6
- import { verifyWorkerDeployment, healthCheckWithBackoff, checkHealth } from '../../lib/shared/monitoring/health-checker.js';
6
+ import { verifyWorkerDeployment, healthCheckWithBackoff, checkHealth } from '../../../lib/shared/monitoring/health-checker.js';
7
7
  import chalk from 'chalk';
8
8
  import readline from 'readline';
9
9
 
@@ -157,7 +157,7 @@ async function discoverUrlFromCloudflare(cloudflareSettings, options = {}) {
157
157
  try {
158
158
  const {
159
159
  CloudflareAPI
160
- } = await import('../../utils/cloudflare/api.js');
160
+ } = await import('../../../utils/cloudflare/api.js');
161
161
  const api = new CloudflareAPI(cloudflareSettings.token);
162
162
 
163
163
  // Get worker routes for the zone
@@ -3,7 +3,7 @@
3
3
  * Provides interactive error recovery by delegating to shared error classification
4
4
  */
5
5
 
6
- import { classifyError, getRecoverySuggestions } from '../../lib/shared/error-handling/error-classifier.js';
6
+ import { classifyError, getRecoverySuggestions } from '../../../lib/shared/error-handling/error-classifier.js';
7
7
  import chalk from 'chalk';
8
8
  import readline from 'readline';
9
9
 
@@ -22,7 +22,7 @@ export async function detectExistingResources(serviceName, manifest, credentials
22
22
  // Import CloudflareAPI to check existing resources
23
23
  const {
24
24
  CloudflareAPI
25
- } = await import('../../utils/cloudflare/api.js');
25
+ } = await import('../../../src/utils/cloudflare/api.js');
26
26
  const cfApi = new CloudflareAPI(credentials.token);
27
27
 
28
28
  // Check if worker already exists
@@ -12,7 +12,7 @@ const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = dirname(__filename);
13
13
 
14
14
  // Path to framework's bundled config
15
- const FRAMEWORK_CONFIG_PATH = join(__dirname, '../config/validation-config.json');
15
+ const FRAMEWORK_CONFIG_PATH = join(__dirname, '../../config/validation-config.json');
16
16
 
17
17
  /**
18
18
  * Register the init-config command with the CLI
@@ -3,36 +3,61 @@
3
3
  */
4
4
 
5
5
  import chalk from 'chalk';
6
- import { ServiceOrchestrator } from '../service-management/ServiceOrchestrator.js';
7
- import { StandardOptions } from '../lib/shared/utils/cli-options.js';
8
- import { ConfigLoader } from '../lib/shared/utils/config-loader.js';
6
+ import path from 'path';
7
+ import { ServiceOrchestrator } from '../../src/service-management/ServiceOrchestrator.js';
8
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
9
+ import { ServiceConfigManager } from '../../lib/shared/utils/service-config-manager.js';
9
10
  export function registerUpdateCommand(program) {
10
- const command = program.command('update [service-path]').description('Update an existing service configuration').option('-i, --interactive', 'Run in interactive mode to select what to update').option('--domain-name <domain>', 'Update domain name').option('--cloudflare-token <token>', 'Update Cloudflare API token').option('--cloudflare-account-id <id>', 'Update Cloudflare account ID').option('--cloudflare-zone-id <id>', 'Update Cloudflare zone ID').option('--environment <env>', 'Update target environment: development, staging, production').option('--add-feature <feature>', 'Add a feature flag').option('--remove-feature <feature>', 'Remove a feature flag').option('--regenerate-configs', 'Regenerate all configuration files').option('--fix-errors', 'Attempt to fix common configuration errors').option('--preview', 'Show what would be changed without applying').option('--force', 'Skip confirmation prompts');
11
+ const command = program.command('update [service-path]').description('Update an existing service configuration').option('-i, --interactive', 'Run in interactive mode to select what to update').option('--domain-name <domain>', 'Update domain name').option('--cloudflare-token <token>', 'Update Cloudflare API token').option('--cloudflare-account-id <id>', 'Update Cloudflare account ID').option('--cloudflare-zone-id <id>', 'Update Cloudflare zone ID').option('--environment <env>', 'Update target environment: development, staging, production').option('--add-feature <feature>', 'Add a feature flag').option('--remove-feature <feature>', 'Remove a feature flag').option('--regenerate-configs', 'Regenerate all configuration files').option('--fix-errors', 'Attempt to fix common configuration errors').option('--preview', 'Show what would be changed without applying').option('--show-config-sources', 'Display all configuration sources and merged result').option('--force', 'Skip confirmation prompts');
11
12
 
12
13
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
13
14
  StandardOptions.define(command).action(async (servicePath, options) => {
14
15
  try {
15
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
16
- const configLoader = new ConfigLoader({
16
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
17
+ const configManager = new ServiceConfigManager({
17
18
  verbose: options.verbose,
18
19
  quiet: options.quiet,
19
- json: options.json
20
+ json: options.json,
21
+ showSources: options.showConfigSources
20
22
  });
23
+ const orchestrator = new ServiceOrchestrator();
21
24
 
22
- // Load config from file if specified
23
- let configFileData = {};
24
- if (options.configFile) {
25
- configFileData = configLoader.loadSafe(options.configFile, {});
26
- if (options.verbose && !options.quiet) {
27
- output.info(`Loaded configuration from: ${options.configFile}`);
25
+ // Auto-detect service path if not provided
26
+ if (!servicePath) {
27
+ servicePath = await orchestrator.detectServicePath();
28
+ if (!servicePath) {
29
+ output.error('No service path provided and could not auto-detect service directory');
30
+ process.exit(1);
28
31
  }
29
32
  }
30
33
 
31
- // Merge config file defaults with CLI options (CLI takes precedence)
32
- const mergedOptions = configLoader.merge(configFileData, options);
33
- const orchestrator = new ServiceOrchestrator();
34
+ // Validate service path with better error handling
35
+ try {
36
+ servicePath = await configManager.validateServicePath(servicePath, orchestrator);
37
+ } catch (error) {
38
+ output.error(error.message);
39
+ if (error.suggestions) {
40
+ output.info('Suggestions:');
41
+ output.list(error.suggestions);
42
+ }
43
+ process.exit(1);
44
+ }
34
45
 
35
- // Auto-detect service path if not provided
46
+ // Load and merge all configurations
47
+ const mergedOptions = await configManager.loadServiceConfig(servicePath, options, {
48
+ interactive: false,
49
+ domainName: null,
50
+ cloudflareToken: null,
51
+ cloudflareAccountId: null,
52
+ cloudflareZoneId: null,
53
+ environment: null,
54
+ addFeature: null,
55
+ removeFeature: null,
56
+ regenerateConfigs: false,
57
+ fixErrors: false,
58
+ preview: false,
59
+ force: false
60
+ });
36
61
  if (!servicePath) {
37
62
  servicePath = await orchestrator.detectServicePath();
38
63
  if (!servicePath) {
@@ -44,7 +69,9 @@ export function registerUpdateCommand(program) {
44
69
  }
45
70
 
46
71
  // Validate it's a service directory
47
- const isValid = await orchestrator.validateService(servicePath);
72
+ const isValid = await orchestrator.validateService(servicePath, {
73
+ customConfig: mergedOptions
74
+ });
48
75
  if (!isValid.valid) {
49
76
  output.warning('Service has configuration issues. Use --fix-errors to attempt automatic fixes.');
50
77
  if (!mergedOptions.fixErrors) {
@@ -60,7 +87,7 @@ export function registerUpdateCommand(program) {
60
87
  }
61
88
  output.success('Service update completed successfully!');
62
89
  } catch (error) {
63
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
90
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
64
91
  output.error(`Service update failed: ${error.message}`);
65
92
  if (error.details) {
66
93
  output.warning(`Details: ${error.details}`);