@tamyla/clodo-framework 2.0.17 → 2.0.18

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 CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.0.18](https://github.com/tamylaa/clodo-framework/compare/v2.0.17...v2.0.18) (2025-10-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deployment:** Critical deployment fixes - migrations, worker deployment, status tracking ([23c07e9](https://github.com/tamylaa/clodo-framework/commit/23c07e9a68fa4eb52ccd0df9421eff7d91919cb1)), closes [#2](https://github.com/tamylaa/clodo-framework/issues/2) [#3](https://github.com/tamylaa/clodo-framework/issues/3) [#4](https://github.com/tamylaa/clodo-framework/issues/4)
7
+
1
8
  ## [2.0.17](https://github.com/tamylaa/clodo-framework/compare/v2.0.16...v2.0.17) (2025-10-12)
2
9
 
3
10
 
@@ -628,8 +628,16 @@ export class DatabaseOrchestrator {
628
628
  // Command builders and utility methods
629
629
 
630
630
  buildMigrationCommand(databaseName, environment, isRemote) {
631
- const remoteFlag = isRemote ? '--remote' : '--local';
632
- return `npx wrangler d1 migrations apply ${databaseName} --env ${environment} ${remoteFlag}`;
631
+ let command = `npx wrangler d1 migrations apply ${databaseName}`;
632
+
633
+ // For remote environments, add --env flag
634
+ // For local development, use --local WITHOUT --env (wrangler requirement)
635
+ if (isRemote) {
636
+ command += ` --env ${environment} --remote`;
637
+ } else {
638
+ command += ` --local`;
639
+ }
640
+ return command;
633
641
  }
634
642
  buildBackupCommand(databaseName, environment, backupFile, isRemote) {
635
643
  const remoteFlag = isRemote ? '--remote' : '--local';
@@ -22,6 +22,38 @@ export class DeploymentCoordinator {
22
22
  * @returns {Promise<Object>} Deployment result
23
23
  */
24
24
  async deploySingleDomain(domain, domainState, handlers) {
25
+ const phaseResults = {
26
+ validation: {
27
+ success: false,
28
+ errors: [],
29
+ warnings: []
30
+ },
31
+ initialization: {
32
+ success: false,
33
+ errors: [],
34
+ warnings: []
35
+ },
36
+ database: {
37
+ success: false,
38
+ errors: [],
39
+ warnings: []
40
+ },
41
+ secrets: {
42
+ success: false,
43
+ errors: [],
44
+ warnings: []
45
+ },
46
+ deployment: {
47
+ success: false,
48
+ errors: [],
49
+ warnings: []
50
+ },
51
+ 'post-validation': {
52
+ success: false,
53
+ errors: [],
54
+ warnings: []
55
+ }
56
+ };
25
57
  try {
26
58
  domainState.startTime = new Date();
27
59
  domainState.status = 'deploying';
@@ -29,38 +61,105 @@ export class DeploymentCoordinator {
29
61
  console.log(` Deployment ID: ${domainState.deploymentId}`);
30
62
  console.log(` Environment: ${this.environment}`);
31
63
  let deploymentUrl = null;
64
+ let hasCriticalErrors = false;
32
65
 
33
66
  // Execute deployment phases
34
67
  for (const phase of this.deploymentPhases) {
35
- const phaseResult = await this.executeDeploymentPhase(domain, phase, domainState, handlers);
36
- domainState.phase = `${phase}-complete`;
68
+ try {
69
+ const phaseResult = await this.executeDeploymentPhase(domain, phase, domainState, handlers);
70
+ domainState.phase = `${phase}-complete`;
71
+
72
+ // Mark phase as successful
73
+ phaseResults[phase].success = true;
74
+
75
+ // Capture deployment URL from deployment phase
76
+ if (phase === 'deployment' && phaseResult) {
77
+ if (phaseResult.url) {
78
+ deploymentUrl = phaseResult.url;
79
+ }
80
+ if (phaseResult.deployed === false) {
81
+ phaseResults[phase].warnings.push('Worker deployed in dry-run mode');
82
+ }
83
+ }
37
84
 
38
- // Capture deployment URL from deployment phase
39
- if (phase === 'deployment' && phaseResult && phaseResult.url) {
40
- deploymentUrl = phaseResult.url;
85
+ // Capture database warnings
86
+ if (phase === 'database' && phaseResult) {
87
+ if (phaseResult.error) {
88
+ phaseResults[phase].warnings.push(phaseResult.error);
89
+ }
90
+ }
91
+ } catch (phaseError) {
92
+ phaseResults[phase].success = false;
93
+ phaseResults[phase].errors.push(phaseError.message);
94
+
95
+ // Determine if error is critical (stops deployment)
96
+ const criticalPhases = ['validation', 'initialization', 'deployment'];
97
+ if (criticalPhases.includes(phase)) {
98
+ console.error(` ❌ Critical error in ${phase} phase: ${phaseError.message}`);
99
+ hasCriticalErrors = true;
100
+ throw phaseError; // Stop deployment on critical errors
101
+ } else {
102
+ // Non-critical errors (database migrations, health checks) - log but continue
103
+ console.warn(` ⚠️ ${phase} phase warning: ${phaseError.message}`);
104
+ console.warn(` 💡 Deployment will continue - this can be fixed manually`);
105
+ }
41
106
  }
42
107
  }
43
- domainState.status = 'completed';
108
+
109
+ // Determine overall success based on phase results
110
+ const allPhasesSuccessful = Object.values(phaseResults).every(result => result.success);
111
+ const hasWarnings = Object.values(phaseResults).some(result => result.warnings.length > 0 || result.success === false && result.errors.length === 0);
112
+ domainState.status = hasCriticalErrors ? 'failed' : allPhasesSuccessful ? 'completed' : 'completed-with-warnings';
44
113
  domainState.endTime = new Date();
45
- console.log(` ✅ ${domain} deployed successfully`);
114
+ domainState.phaseResults = phaseResults;
115
+
116
+ // Show appropriate completion message
117
+ if (allPhasesSuccessful) {
118
+ console.log(` ✅ ${domain} deployed successfully`);
119
+ } else if (!hasCriticalErrors) {
120
+ console.log(` ⚠️ ${domain} deployed with warnings`);
121
+ this.displayPhaseWarnings(phaseResults);
122
+ }
46
123
  return {
47
124
  domain,
48
- success: true,
125
+ success: !hasCriticalErrors,
126
+ allPhasesSuccessful,
49
127
  deploymentId: domainState.deploymentId,
50
128
  duration: domainState.endTime - domainState.startTime,
51
129
  phases: this.deploymentPhases.length,
52
130
  url: deploymentUrl || domainState.deploymentUrl,
53
- status: 'deployed'
131
+ status: domainState.status,
132
+ phaseResults
54
133
  };
55
134
  } catch (error) {
56
135
  domainState.status = 'failed';
57
136
  domainState.error = error.message;
58
137
  domainState.endTime = new Date();
138
+ domainState.phaseResults = phaseResults;
59
139
  console.error(` ❌ ${domain} deployment failed: ${error.message}`);
60
140
  throw error;
61
141
  }
62
142
  }
63
143
 
144
+ /**
145
+ * Display warnings for phases that had issues
146
+ * @param {Object} phaseResults - Phase results object
147
+ */
148
+ displayPhaseWarnings(phaseResults) {
149
+ console.log(`\n 📊 Phase Status Summary:`);
150
+ for (const [phase, result] of Object.entries(phaseResults)) {
151
+ if (result.success && result.warnings.length === 0) {
152
+ console.log(` ✅ ${phase}: Success`);
153
+ } else if (result.success && result.warnings.length > 0) {
154
+ console.log(` ⚠️ ${phase}: Success with warnings`);
155
+ result.warnings.forEach(warn => console.log(` • ${warn}`));
156
+ } else {
157
+ console.log(` ❌ ${phase}: Failed`);
158
+ result.errors.forEach(err => console.log(` • ${err}`));
159
+ }
160
+ }
161
+ }
162
+
64
163
  /**
65
164
  * Execute specific deployment phase
66
165
  * @param {string} domain - Domain being deployed
@@ -13,6 +13,10 @@ import { StateManager } from './modules/StateManager.js';
13
13
  import { DatabaseOrchestrator } from '../database/database-orchestrator.js';
14
14
  import { EnhancedSecretManager } from '../utils/deployment/secret-generator.js';
15
15
  import { ConfigurationValidator } from '../security/ConfigurationValidator.js';
16
+ import { exec } from 'child_process';
17
+ import { promisify } from 'util';
18
+ import { join } from 'path';
19
+ const execAsync = promisify(exec);
16
20
 
17
21
  /**
18
22
  * Multi-Domain Deployment Orchestrator
@@ -359,26 +363,100 @@ export class MultiDomainOrchestrator {
359
363
  }
360
364
 
361
365
  /**
362
- * Deploy domain worker (returns worker URL)
366
+ * Deploy domain worker (executes actual wrangler deploy)
363
367
  */
364
368
  async deployDomainWorker(domain) {
365
- // Placeholder: Add actual worker deployment logic here
366
369
  console.log(` 🚀 Deploying worker for ${domain}`);
370
+ if (this.dryRun) {
371
+ console.log(` 🔍 DRY RUN: Would deploy worker for ${domain}`);
372
+ const subdomain = this.environment === 'production' ? 'api' : `${this.environment}-api`;
373
+ return {
374
+ url: `https://${subdomain}.${domain}`,
375
+ deployed: false,
376
+ dryRun: true
377
+ };
378
+ }
379
+ try {
380
+ // Find wrangler.toml in service path
381
+ const wranglerConfigPath = join(this.servicePath, 'wrangler.toml');
367
382
 
368
- // TODO: Execute actual wrangler deploy command here
369
- // For now, construct the expected URL from domain and environment
370
- const subdomain = this.environment === 'production' ? 'api' : `${this.environment}-api`;
371
- const workerUrl = `https://${subdomain}.${domain}`;
383
+ // Build deploy command with environment
384
+ let deployCommand = `npx wrangler deploy`;
372
385
 
373
- // Store URL in domain state for later retrieval
374
- const domainState = this.portfolioState.domainStates.get(domain);
375
- if (domainState) {
376
- domainState.deploymentUrl = workerUrl;
386
+ // Add environment flag for non-production
387
+ if (this.environment !== 'production') {
388
+ deployCommand += ` --env ${this.environment}`;
389
+ }
390
+ console.log(` � Executing: ${deployCommand}`);
391
+ console.log(` 📁 Working directory: ${this.servicePath}`);
392
+
393
+ // Execute deployment with timeout
394
+ const {
395
+ stdout,
396
+ stderr
397
+ } = await execAsync(deployCommand, {
398
+ cwd: this.servicePath,
399
+ timeout: 120000,
400
+ // 2 minute timeout
401
+ maxBuffer: 1024 * 1024 * 10 // 10MB buffer for large outputs
402
+ });
403
+
404
+ // Log output for debugging
405
+ if (stdout) {
406
+ console.log(` 📄 Deployment output:`);
407
+ stdout.split('\n').filter(line => line.trim()).forEach(line => {
408
+ console.log(` ${line}`);
409
+ });
410
+ }
411
+ if (stderr && !stderr.includes('deprecated')) {
412
+ console.warn(` ⚠️ Deployment warnings: ${stderr}`);
413
+ }
414
+
415
+ // Parse worker URL from wrangler output
416
+ // Wrangler outputs: "Published service-name (version) to https://worker-url"
417
+ const urlMatch = stdout.match(/https:\/\/[^\s]+/);
418
+ const workerUrl = urlMatch ? urlMatch[0] : null;
419
+
420
+ // Also construct custom domain URL
421
+ const subdomain = this.environment === 'production' ? 'api' : `${this.environment}-api`;
422
+ const customUrl = `https://${subdomain}.${domain}`;
423
+
424
+ // Store URLs in domain state
425
+ const domainState = this.portfolioState.domainStates.get(domain);
426
+ if (domainState) {
427
+ domainState.workerUrl = workerUrl;
428
+ domainState.deploymentUrl = customUrl;
429
+ }
430
+ if (workerUrl) {
431
+ console.log(` ✅ Worker deployed successfully`);
432
+ console.log(` 🔗 Worker URL: ${workerUrl}`);
433
+ console.log(` 🔗 Custom URL: ${customUrl}`);
434
+ } else {
435
+ console.log(` ✅ Deployment completed (URL not detected in output)`);
436
+ console.log(` 🔗 Expected URL: ${customUrl}`);
437
+ }
438
+ return {
439
+ url: customUrl,
440
+ workerUrl: workerUrl,
441
+ deployed: true,
442
+ stdout,
443
+ stderr
444
+ };
445
+ } catch (error) {
446
+ console.error(` ❌ Worker deployment failed: ${error.message}`);
447
+
448
+ // Parse error for helpful diagnostics
449
+ if (error.message.includes('wrangler.toml')) {
450
+ console.error(` 💡 Ensure wrangler.toml exists in ${this.servicePath}`);
451
+ }
452
+ if (error.message.includes('No environment found')) {
453
+ console.error(` 💡 Add [env.${this.environment}] section to wrangler.toml`);
454
+ }
455
+ if (error.stderr) {
456
+ console.error(` 📄 Error details: ${error.stderr}`);
457
+ }
458
+ throw new Error(`Worker deployment failed for ${domain}: ${error.message}`);
377
459
  }
378
- return {
379
- url: workerUrl,
380
- deployed: true
381
- };
382
460
  }
383
461
 
384
462
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamyla/clodo-framework",
3
- "version": "2.0.17",
3
+ "version": "2.0.18",
4
4
  "description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
5
5
  "type": "module",
6
6
  "sideEffects": [