@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.
- package/CHANGELOG.md +15 -0
- package/dist/cli/clodo-simple.js +111 -0
- package/dist/cli/commands/assess.js +29 -20
- package/dist/cli/commands/create.js +22 -31
- package/dist/cli/commands/deploy.js +20 -333
- package/dist/cli/commands/diagnose.js +26 -26
- package/dist/cli/commands/helpers/deployment-verification.js +2 -2
- package/dist/cli/commands/helpers/error-recovery.js +1 -1
- package/dist/cli/commands/helpers/resource-detection.js +1 -1
- package/dist/cli/commands/init-config.js +1 -1
- package/dist/cli/commands/update.js +46 -19
- package/dist/cli/commands/validate.js +24 -34
- package/dist/cli/security-cli.js +1 -1
- package/dist/deployment/wrangler-deployer.js +1 -1
- package/dist/index.js +5 -2
- package/dist/lib/deployment/modules/DeploymentOrchestrator.js +2 -2
- package/dist/lib/deployment/modules/EnvironmentManager.js +2 -2
- package/dist/lib/shared/cloudflare/domain-manager.js +1 -1
- package/dist/lib/shared/cloudflare/ops.js +4 -4
- package/dist/lib/shared/config/command-config-manager.js +1 -1
- package/dist/lib/shared/config/index.js +1 -1
- package/dist/lib/shared/config/manifest-loader.js +3 -3
- package/dist/lib/shared/deployment/credential-collector.js +1 -1
- package/dist/lib/shared/deployment/index.js +2 -2
- package/dist/lib/shared/deployment/rollback-manager.js +1 -1
- package/dist/lib/shared/deployment/utilities/d1-error-recovery.js +1 -1
- package/dist/lib/shared/deployment/validator.js +1 -1
- package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +1 -1
- package/dist/lib/shared/monitoring/health-checker.js +2 -2
- package/dist/lib/shared/routing/domain-router.js +1 -1
- package/dist/lib/shared/utils/config-loader.js +47 -11
- package/dist/lib/shared/utils/service-config-manager.js +227 -0
- package/dist/lib/shared/validation/ValidationRegistry.js +1 -1
- package/dist/orchestration/cross-domain-coordinator.js +5 -5
- package/dist/orchestration/multi-domain-orchestrator.js +51 -0
- package/dist/security/index.js +2 -2
- package/dist/service-management/ConfirmationEngine.js +1 -1
- package/dist/service-management/ErrorTracker.js +1 -1
- package/dist/service-management/InputCollector.js +1 -1
- package/dist/service-management/ServiceOrchestrator.js +120 -2
- package/dist/service-management/generators/testing/UnitTestsGenerator.js +4 -4
- package/dist/service-management/handlers/ValidationHandler.js +22 -9
- package/dist/simple-api.js +94 -0
- package/dist/utils/cloudflare/ops.js +1 -1
- package/dist/utils/file-manager.js +1 -1
- package/dist/utils/formatters.js +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/worker/integration.js +1 -1
- 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 {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
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('--
|
|
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('
|
|
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
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
357
|
-
|
|
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
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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('
|
|
12
|
-
const
|
|
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
|
-
//
|
|
32
|
-
|
|
33
|
-
servicePath = await
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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('
|
|
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 '
|
|
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('
|
|
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 '
|
|
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('
|
|
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, '
|
|
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
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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('
|
|
16
|
-
const
|
|
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
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
//
|
|
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('
|
|
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}`);
|