@tamyla/clodo-framework 4.0.7 → 4.0.10

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,17 @@
1
+ ## [4.0.10](https://github.com/tamylaa/clodo-framework/compare/v4.0.9...v4.0.10) (2025-12-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * clean up test artifacts and temporary files ([0eb6cda](https://github.com/tamylaa/clodo-framework/commit/0eb6cdac68bce6d07e567f4b7810f4d94752b7c0))
7
+
8
+ ## [4.0.8](https://github.com/tamylaa/clodo-framework/compare/v4.0.7...v4.0.8) (2025-12-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * respect user-provided worker and database names in deployment ([04ffc38](https://github.com/tamylaa/clodo-framework/commit/04ffc382ab910d4d260ba5c3f2e421763587948a))
14
+
1
15
  ## [4.0.7](https://github.com/tamylaa/clodo-framework/compare/v4.0.6...v4.0.7) (2025-12-09)
2
16
 
3
17
 
@@ -16,7 +16,11 @@
16
16
  * - list-types List available service types and their features
17
17
  */
18
18
  import { Command } from 'commander';
19
+ import { join, dirname } from 'path';
20
+ import { fileURLToPath, pathToFileURL } from 'url';
19
21
  import chalk from 'chalk';
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
20
24
 
21
25
  // Create program instance
22
26
  const program = new Command();
@@ -25,33 +29,35 @@ program.name('clodo-service').description('Unified conversational CLI for Clodo
25
29
  // Dynamically load available command modules
26
30
  // This makes the CLI resilient if some commands are excluded from the package
27
31
  async function registerAvailableCommands() {
32
+ // Use absolute paths to ensure commands are found regardless of working directory
33
+ const commandsDir = join(__dirname, 'commands');
28
34
  const commands = [{
29
35
  name: 'create',
30
- path: './commands/create.js',
36
+ path: pathToFileURL(join(commandsDir, 'create.js')).href,
31
37
  register: 'registerCreateCommand'
32
38
  }, {
33
39
  name: 'deploy',
34
- path: './commands/deploy.js',
40
+ path: pathToFileURL(join(commandsDir, 'deploy.js')).href,
35
41
  register: 'registerDeployCommand'
36
42
  }, {
37
43
  name: 'validate',
38
- path: './commands/validate.js',
44
+ path: pathToFileURL(join(commandsDir, 'validate.js')).href,
39
45
  register: 'registerValidateCommand'
40
46
  }, {
41
47
  name: 'update',
42
- path: './commands/update.js',
48
+ path: pathToFileURL(join(commandsDir, 'update.js')).href,
43
49
  register: 'registerUpdateCommand'
44
50
  }, {
45
51
  name: 'diagnose',
46
- path: './commands/diagnose.js',
52
+ path: pathToFileURL(join(commandsDir, 'diagnose.js')).href,
47
53
  register: 'registerDiagnoseCommand'
48
54
  }, {
49
55
  name: 'assess',
50
- path: './commands/assess.js',
56
+ path: pathToFileURL(join(commandsDir, 'assess.js')).href,
51
57
  register: 'registerAssessCommand'
52
58
  }, {
53
59
  name: 'init-config',
54
- path: './commands/init-config.js',
60
+ path: pathToFileURL(join(commandsDir, 'init-config.js')).href,
55
61
  register: 'registerInitConfigCommand'
56
62
  }];
57
63
  for (const cmd of commands) {
@@ -5,14 +5,46 @@
5
5
  * Copies the framework's validation-config.json to the service directory for customization
6
6
  */
7
7
  import { copyFile, access } from 'fs/promises';
8
- import { join, dirname } from 'path';
8
+ import { join, dirname, resolve } from 'path';
9
9
  import { fileURLToPath } from 'url';
10
10
  import chalk from 'chalk';
11
11
  const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = dirname(__filename);
13
13
 
14
- // Path to framework's bundled config
15
- const FRAMEWORK_CONFIG_PATH = join(__dirname, '../../config/validation-config.json');
14
+ /**
15
+ * Find the framework's config directory by walking up from the current working directory
16
+ * or from the command file location
17
+ */
18
+ export async function findFrameworkConfig() {
19
+ // First try relative to this command file
20
+ const relativeToCommand = join(__dirname, '../../config/validation-config.json');
21
+ try {
22
+ // Check if the file exists at the expected location
23
+ await access(relativeToCommand);
24
+ return relativeToCommand;
25
+ } catch {
26
+ // If not found, try to find it by walking up from current working directory
27
+ let currentDir = process.cwd();
28
+ const maxDepth = 10;
29
+ for (let i = 0; i < maxDepth; i++) {
30
+ const candidatePath = join(currentDir, 'config', 'validation-config.json');
31
+ try {
32
+ await access(candidatePath);
33
+ return candidatePath;
34
+ } catch {
35
+ const parentDir = dirname(currentDir);
36
+ if (parentDir === currentDir) {
37
+ // Reached root directory
38
+ break;
39
+ }
40
+ currentDir = parentDir;
41
+ }
42
+ }
43
+
44
+ // As fallback, try absolute path from command location
45
+ return resolve(__dirname, '../../config/validation-config.json');
46
+ }
47
+ }
16
48
 
17
49
  /**
18
50
  * Register the init-config command with the CLI
@@ -23,6 +55,9 @@ export function registerInitConfigCommand(program) {
23
55
  async function handler(options) {
24
56
  const targetPath = join(process.cwd(), 'validation-config.json');
25
57
  try {
58
+ // Find the framework config file
59
+ const frameworkConfigPath = await findFrameworkConfig();
60
+
26
61
  // Check if file already exists
27
62
  try {
28
63
  await access(targetPath);
@@ -38,7 +73,7 @@ async function handler(options) {
38
73
  }
39
74
 
40
75
  // Copy framework config to service directory
41
- await copyFile(FRAMEWORK_CONFIG_PATH, targetPath);
76
+ await copyFile(frameworkConfigPath, targetPath);
42
77
  console.log(chalk.green('✅ Successfully initialized validation-config.json'));
43
78
  console.log(chalk.gray('\n📝 Configuration file details:'));
44
79
  console.log(chalk.gray(` Location: ${targetPath}`));
@@ -38,9 +38,7 @@ export class CustomerConfigurationManager {
38
38
  const customerDir = resolve(this.configDir, 'customers', customerName);
39
39
 
40
40
  // Create customer directory structure
41
- console.log(`DEBUG: Checking if directory exists: ${customerDir}`);
42
41
  if (!fs.existsSync(customerDir)) {
43
- console.log(`DEBUG: Creating directory: ${customerDir}`);
44
42
  fs.mkdirSync(customerDir, {
45
43
  recursive: true
46
44
  });
@@ -598,9 +598,6 @@ export class DeploymentValidator {
598
598
  }
599
599
  async validateDomainEndpoints(domain) {
600
600
  console.log(` Validating endpoints for ${domain}...`);
601
- console.log(` 🔧 DEBUG: skipEndpointCheck = ${this.options?.skipEndpointCheck}`);
602
- console.log(` 🔧 DEBUG: deploymentType = ${this.options?.deploymentType}`);
603
- console.log(` 🔧 DEBUG: options = ${JSON.stringify(this.options)}`);
604
601
 
605
602
  // Skip endpoint validation for new deployments
606
603
  if (this.options?.skipEndpointCheck) {
@@ -214,6 +214,10 @@ export class InteractiveDeploymentCoordinator {
214
214
  environment: this.deploymentState.config.environment,
215
215
  domain: this.deploymentState.config.credentials.zoneName,
216
216
  serviceName: this.options.serviceName,
217
+ workerName: this.deploymentState.config.worker?.name,
218
+ // Pass the collected worker name
219
+ databaseName: this.deploymentState.resources.database?.name,
220
+ // Pass the collected database name
217
221
  dryRun: this.deploymentState.config.dryRun,
218
222
  credentials: this.deploymentState.config.credentials
219
223
  });
@@ -36,6 +36,8 @@ export class MultiDomainOrchestrator {
36
36
  this.parallelDeployments = options.parallelDeployments || 3;
37
37
  this.servicePath = options.servicePath || process.cwd();
38
38
  this.serviceName = options.serviceName || 'data-service'; // Service name for custom domain (e.g., 'data-service', 'auth-service')
39
+ this.workerName = options.workerName; // Specific worker name to use (optional)
40
+ this.databaseName = options.databaseName; // Specific database name to use (optional)
39
41
 
40
42
  // Wrangler config path - allows using customer-specific wrangler.toml files
41
43
  // If not specified, wrangler uses the default wrangler.toml in servicePath
@@ -293,13 +295,21 @@ export class MultiDomainOrchestrator {
293
295
  }
294
296
 
295
297
  /**
296
- * Setup domain database using DatabaseOrchestrator
298
+ * Get base service name from worker name (remove environment suffixes)
299
+ * @returns {string} Base service name for URL generation
297
300
  */
301
+ getBaseServiceName() {
302
+ if (this.workerName) {
303
+ // Remove environment suffixes from worker name to get base service name
304
+ return this.workerName.replace(/-development$|-staging$|-production$/, '');
305
+ }
306
+ return this.serviceName || 'data-service';
307
+ }
298
308
  async setupDomainDatabase(domain) {
299
309
  console.log(` 🗄️ Setting up database for ${domain}`);
300
310
  if (this.dryRun) {
301
311
  console.log(` � DRY RUN: Would create database for ${domain}`);
302
- const databaseName = `${domain.replace(/\./g, '-')}-${this.environment}-db`;
312
+ const databaseName = this.databaseName || `${domain.replace(/\./g, '-')}-${this.environment}-db`;
303
313
  return {
304
314
  databaseName,
305
315
  databaseId: 'dry-run-id',
@@ -308,7 +318,8 @@ export class MultiDomainOrchestrator {
308
318
  }
309
319
  try {
310
320
  // Create D1 database using Cloudflare ops
311
- const databaseName = `${domain.replace(/\./g, '-')}-${this.environment}-db`;
321
+ // Use provided database name, or generate one based on domain/environment
322
+ const databaseName = this.databaseName || `${domain.replace(/\./g, '-')}-${this.environment}-db`;
312
323
 
313
324
  // Check if database already exists
314
325
  console.log(` � Checking if database exists: ${databaseName}`);
@@ -526,7 +537,7 @@ export class MultiDomainOrchestrator {
526
537
  if (this.dryRun) {
527
538
  console.log(` 🔍 DRY RUN: Would deploy worker for ${domain}`);
528
539
  // Use centralized domain template from validation-config.json
529
- const customUrl = buildCustomDomain(this.serviceName, domain, this.environment);
540
+ const customUrl = buildCustomDomain(this.getBaseServiceName(), domain, this.environment);
530
541
  return {
531
542
  url: customUrl,
532
543
  deployed: false,
@@ -544,7 +555,8 @@ export class MultiDomainOrchestrator {
544
555
  // Generate or update customer config with current deployment parameters
545
556
  const customerConfigPath = await this.wranglerConfigManager.generateCustomerConfig(this.cloudflareZoneName, {
546
557
  accountId: this.cloudflareAccountId,
547
- environment: this.environment
558
+ environment: this.environment,
559
+ workerName: this.workerName // Pass specific worker name if provided
548
560
  });
549
561
 
550
562
  // Copy customer config to root (ephemeral working copy for this deployment)
@@ -609,7 +621,7 @@ export class MultiDomainOrchestrator {
609
621
 
610
622
  // Construct custom domain URL using centralized template from validation-config.json
611
623
  // Handles all environment patterns: production, staging, development
612
- const customUrl = buildCustomDomain(this.serviceName, domain, this.environment);
624
+ const customUrl = buildCustomDomain(this.getBaseServiceName(), domain, this.environment);
613
625
 
614
626
  // Store URLs in domain state
615
627
  const domainState = this.portfolioState.domainStates.get(domain);
@@ -621,6 +633,11 @@ export class MultiDomainOrchestrator {
621
633
  console.log(` ✅ Worker deployed successfully`);
622
634
  console.log(` 🔗 Worker URL: ${workerUrl}`);
623
635
  console.log(` 🔗 Custom URL: ${customUrl}`);
636
+
637
+ // Display custom domain setup instructions
638
+ if (customUrl && workerUrl !== customUrl) {
639
+ this.displayCustomDomainInstructions(customUrl, workerUrl, domain);
640
+ }
624
641
  } else {
625
642
  console.log(` ✅ Deployment completed (URL not detected in output)`);
626
643
  console.log(` 🔗 Expected URL: ${customUrl}`);
@@ -659,14 +676,22 @@ export class MultiDomainOrchestrator {
659
676
  return true;
660
677
  }
661
678
 
662
- // Get the deployment URL from domain state
679
+ // Get the deployment URL from domain state - prefer worker URL for immediate validation
663
680
  const domainState = this.portfolioState.domainStates.get(domain);
664
- const deploymentUrl = domainState?.deploymentUrl;
665
- if (!deploymentUrl) {
681
+ const workerUrl = domainState?.workerUrl;
682
+ const customUrl = domainState?.deploymentUrl;
683
+
684
+ // Use worker URL for health check since it's immediately available after deployment
685
+ // Custom domain may require DNS configuration and won't work immediately
686
+ const healthCheckUrl = workerUrl || customUrl;
687
+ if (!healthCheckUrl) {
666
688
  console.log(` ⚠️ No deployment URL found, skipping health check`);
667
689
  return true;
668
690
  }
669
- console.log(` 🔍 Running health check: ${deploymentUrl}/health`);
691
+ console.log(` 🔍 Running health check: ${healthCheckUrl}/health`);
692
+ if (workerUrl && customUrl && workerUrl !== customUrl) {
693
+ console.log(` ℹ️ Health check uses worker URL (custom domain requires DNS setup - see below)`);
694
+ }
670
695
 
671
696
  // Retry logic for health checks
672
697
  const maxRetries = 3;
@@ -678,7 +703,7 @@ export class MultiDomainOrchestrator {
678
703
  console.log(` Attempt ${attempt}/${maxRetries}...`);
679
704
 
680
705
  // Perform actual HTTP health check
681
- const response = await fetch(`${deploymentUrl}/health`, {
706
+ const response = await fetch(`${healthCheckUrl}/health`, {
682
707
  method: 'GET',
683
708
  headers: {
684
709
  'User-Agent': 'Clodo-Orchestrator/2.0'
@@ -692,7 +717,7 @@ export class MultiDomainOrchestrator {
692
717
 
693
718
  // Log successful health check
694
719
  this.stateManager.logAuditEvent('HEALTH_CHECK_PASSED', domain, {
695
- url: deploymentUrl,
720
+ url: healthCheckUrl,
696
721
  status,
697
722
  responseTime,
698
723
  attempt,
@@ -703,7 +728,7 @@ export class MultiDomainOrchestrator {
703
728
  const errorMsg = `Health check returned ${status} - deployment may have issues`;
704
729
  console.log(` ⚠️ ${errorMsg}`);
705
730
  this.stateManager.logAuditEvent('HEALTH_CHECK_WARNING', domain, {
706
- url: deploymentUrl,
731
+ url: healthCheckUrl,
707
732
  status,
708
733
  responseTime,
709
734
  attempt,
@@ -718,9 +743,9 @@ export class MultiDomainOrchestrator {
718
743
  const errorMsg = `Health check failed: ${error.message}`;
719
744
  if (isLastAttempt) {
720
745
  console.log(` ❌ ${errorMsg} (final attempt)`);
721
- console.log(` 💡 The service may still be deploying. Check manually: curl ${deploymentUrl}/health`);
746
+ console.log(` 💡 The service may still be deploying. Check manually: curl ${healthCheckUrl}/health`);
722
747
  this.stateManager.logAuditEvent('HEALTH_CHECK_FAILED', domain, {
723
- url: deploymentUrl,
748
+ url: healthCheckUrl,
724
749
  error: error.message,
725
750
  attempts: maxRetries,
726
751
  environment: this.environment
@@ -738,6 +763,43 @@ export class MultiDomainOrchestrator {
738
763
  return true;
739
764
  }
740
765
 
766
+ /**
767
+ * Display comprehensive custom domain setup instructions
768
+ * @param {string} customUrl - The custom domain URL
769
+ * @param {string} workerUrl - The worker URL to point DNS to
770
+ * @param {string} domain - The domain name
771
+ */
772
+ displayCustomDomainInstructions(customUrl, workerUrl, domain) {
773
+ console.log(`\n🌐 Custom Domain Setup Instructions`);
774
+ console.log(`═`.repeat(50));
775
+ console.log(`Your service is deployed and working at: ${workerUrl}`);
776
+ console.log(`To use your custom domain ${customUrl}, follow these steps:\n`);
777
+ console.log(`📋 Step 1: DNS Configuration`);
778
+ console.log(` Create a CNAME record in your DNS settings:`);
779
+ // Extract subdomain from custom URL (everything before the domain)
780
+ const urlObj = new URL(customUrl);
781
+ const subdomain = urlObj.hostname.replace(`.${domain}`, '');
782
+ console.log(` • Name: ${subdomain}`);
783
+ console.log(` • Type: CNAME`);
784
+ console.log(` • Target: ${workerUrl.replace('https://', '')}`);
785
+ console.log(` • TTL: 300 (5 minutes)\n`);
786
+ console.log(`🔧 Step 2: Cloudflare Workers Configuration`);
787
+ console.log(` 1. Go to Cloudflare Dashboard → Workers & Pages`);
788
+ console.log(` 2. Select your worker`);
789
+ console.log(` 3. Go to "Triggers" tab`);
790
+ console.log(` 4. Click "Add Custom Domain"`);
791
+ console.log(` 5. Enter: ${customUrl}\n`);
792
+ console.log(`⏱️ Step 3: Wait for Propagation`);
793
+ console.log(` • DNS changes: 5-30 minutes`);
794
+ console.log(` • SSL certificate: 5-10 minutes`);
795
+ console.log(` • Total setup time: 10-60 minutes\n`);
796
+ console.log(`✅ Step 4: Verify Setup`);
797
+ console.log(` Test your custom domain:`);
798
+ console.log(` curl -v ${customUrl}/health\n`);
799
+ console.log(`💡 Note: Your service is fully functional at the worker URL immediately.`);
800
+ console.log(` The custom domain is optional for branding/user experience.\n`);
801
+ }
802
+
741
803
  /**
742
804
  * Get rollback plan using state manager
743
805
  * @returns {Array} Rollback plan from state manager
@@ -763,12 +825,14 @@ export class MultiDomainOrchestrator {
763
825
  }
764
826
 
765
827
  /**
766
- * Simple API: Deploy a service with minimal configuration
767
- * @param {Object} options - Simple deployment options
828
+ * Deploy a single service to a specific domain/environment
829
+ * @param {Object} options - Deployment options
768
830
  * @param {string} options.servicePath - Path to service directory
769
831
  * @param {string} options.environment - Target environment
770
832
  * @param {string} options.domain - Specific domain to deploy to (used as zone name and domain suffix)
771
833
  * @param {string} options.serviceName - Service name for URL generation (e.g., 'data-service', 'auth-service')
834
+ * @param {string} options.workerName - Specific worker name to use
835
+ * @param {string} options.databaseName - Specific database name to use
772
836
  * @param {boolean} options.dryRun - Simulate deployment
773
837
  * @param {Object} options.credentials - Cloudflare credentials
774
838
  * @returns {Promise<Object>} Deployment result
@@ -779,6 +843,8 @@ export class MultiDomainOrchestrator {
779
843
  environment = 'production',
780
844
  domain,
781
845
  serviceName,
846
+ workerName,
847
+ databaseName,
782
848
  dryRun = false,
783
849
  credentials = {}
784
850
  } = options;
@@ -790,6 +856,10 @@ export class MultiDomainOrchestrator {
790
856
  servicePath,
791
857
  serviceName,
792
858
  // Pass through serviceName if provided
859
+ workerName,
860
+ // Pass through workerName if provided
861
+ databaseName,
862
+ // Pass through databaseName if provided
793
863
  cloudflareToken: credentials.token,
794
864
  cloudflareAccountId: credentials.accountId,
795
865
  cloudflareZoneId: credentials.zoneId,
@@ -34,6 +34,9 @@ export class Clodo {
34
34
  * @param {string} options.servicePath - Path to service directory
35
35
  * @param {string} options.environment - Target environment
36
36
  * @param {string} options.domain - Specific domain to deploy to
37
+ * @param {string} options.serviceName - Service name for URL generation
38
+ * @param {string} options.workerName - Specific worker name to use
39
+ * @param {string} options.databaseName - Specific database name to use
37
40
  * @param {boolean} options.dryRun - Simulate deployment
38
41
  * @returns {Promise<Object>} Deployment result
39
42
  */
@@ -127,12 +127,14 @@ export class WranglerConfigManager {
127
127
  * @param {Object} params - Deployment parameters
128
128
  * @param {string} params.accountId - Cloudflare account ID
129
129
  * @param {string} params.environment - Deployment environment (production, staging, development)
130
+ * @param {string} params.workerName - Specific worker name to use (optional, overrides automatic naming)
130
131
  * @returns {Promise<string>} Path to generated/updated customer config
131
132
  */
132
133
  async generateCustomerConfig(zoneName, params = {}) {
133
134
  const {
134
135
  accountId,
135
- environment = 'production'
136
+ environment = 'production',
137
+ workerName
136
138
  } = params;
137
139
  if (!zoneName) {
138
140
  throw new Error('Zone name is required to generate customer config');
@@ -186,16 +188,24 @@ export class WranglerConfigManager {
186
188
  console.log(` ✅ Set account_id to ${accountId} in customer config`);
187
189
  }
188
190
 
189
- // Update worker name based on zone
191
+ // Update worker name based on zone or provided name
190
192
  // Extract base service name and apply zone-based naming
191
193
  const customerPrefix = zoneName.split('.')[0]; // clodo.dev → clodo
192
194
 
193
195
  // Update root-level worker name
194
196
  if (config.name) {
195
- const baseName = config.name.replace(/^[^-]+-/, ''); // Remove existing prefix
196
- const newWorkerName = `${customerPrefix}-${baseName}`;
197
+ let newWorkerName;
198
+ if (workerName) {
199
+ // Use the provided worker name directly
200
+ newWorkerName = workerName;
201
+ console.log(` ✅ Set worker name to ${newWorkerName} in customer config (provided)`);
202
+ } else {
203
+ // Apply automatic zone-based naming
204
+ const baseName = config.name.replace(/^[^-]+-/, ''); // Remove existing prefix
205
+ newWorkerName = `${customerPrefix}-${baseName}`;
206
+ console.log(` ✅ Set worker name to ${newWorkerName} in customer config (auto-generated)`);
207
+ }
197
208
  config.name = newWorkerName;
198
- console.log(` ✅ Set worker name to ${newWorkerName} in customer config`);
199
209
  }
200
210
 
201
211
  // Update SERVICE_DOMAIN in environment variables (all environments)
@@ -227,9 +237,20 @@ export class WranglerConfigManager {
227
237
  if (config.env) {
228
238
  for (const [envName, envConfig] of Object.entries(config.env)) {
229
239
  if (envConfig.name) {
230
- const baseName = envConfig.name.replace(/^[^-]+-/, ''); // Remove existing prefix
231
- envConfig.name = `${customerPrefix}-${baseName}`;
232
- console.log(` ✅ Set [env.${envName}] worker name to ${envConfig.name}`);
240
+ let newEnvWorkerName;
241
+ if (workerName) {
242
+ // Derive environment-specific name from provided worker name
243
+ const baseName = workerName.replace(/-service$/, ''); // Remove -service suffix if present
244
+ const envSuffix = envName === 'production' ? '' : `-${envName}`;
245
+ newEnvWorkerName = `${baseName}${envSuffix}`;
246
+ console.log(` ✅ Set [env.${envName}] worker name to ${newEnvWorkerName} (derived from provided name)`);
247
+ } else {
248
+ // Apply automatic zone-based naming
249
+ const baseName = envConfig.name.replace(/^[^-]+-/, ''); // Remove existing prefix
250
+ newEnvWorkerName = `${customerPrefix}-${baseName}`;
251
+ console.log(` ✅ Set [env.${envName}] worker name to ${newEnvWorkerName} (auto-generated)`);
252
+ }
253
+ envConfig.name = newEnvWorkerName;
233
254
  }
234
255
  if (envConfig.vars) {
235
256
  updateServiceDomain(envConfig.vars);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamyla/clodo-framework",
3
- "version": "4.0.7",
3
+ "version": "4.0.10",
4
4
  "description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -172,7 +172,7 @@
172
172
  "bugs": {
173
173
  "url": "https://github.com/tamylaa/clodo-framework/issues"
174
174
  },
175
- "homepage": "https://clodo-framework.tamyla.com",
175
+ "homepage": "https://clodo.dev",
176
176
  "release": {
177
177
  "branches": [
178
178
  "main",