@tamyla/clodo-framework 4.0.10 → 4.0.12

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,54 @@
1
+ ## [4.0.12](https://github.com/tamylaa/clodo-framework/compare/v4.0.11...v4.0.12) (2025-12-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * resolve version sync for 4.0.12 release ([6375c5f](https://github.com/tamylaa/clodo-framework/commit/6375c5f71516cf2a7a96edcf5878106063037ba8))
7
+ * trigger 4.0.12 release after tag sync ([3dafb25](https://github.com/tamylaa/clodo-framework/commit/3dafb253cbbae6e7eac27dd5170e9f391c3deda0))
8
+
9
+ ## [4.0.11](https://github.com/tamylaa/clodo-framework/compare/v4.0.10...v4.0.11) (2025-12-12)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * add wrangler compatibility and config validation utilities ([98c745b](https://github.com/tamylaa/clodo-framework/commit/98c745bb72608a4d5808c947cc9c7a39b868243f))
15
+ * correct import path in file-manager.js for proper dist compilation ([166698e](https://github.com/tamylaa/clodo-framework/commit/166698ebb9473ba5d889739831a1d4e1040c12a5))
16
+ * correct NPM_TOKEN environment variable name in GitHub Actions workflow ([938c953](https://github.com/tamylaa/clodo-framework/commit/938c953d7e2e9f1a679ee566f1a4c7a070e629e6))
17
+ * resolve version sync for 4.0.12 release ([6375c5f](https://github.com/tamylaa/clodo-framework/commit/6375c5f71516cf2a7a96edcf5878106063037ba8))
18
+ * trigger patch release for latest changes ([6161573](https://github.com/tamylaa/clodo-framework/commit/6161573ead8271eaea2766bf7c85f63ef7378a7d))
19
+ * update .gitignore to prevent test artifacts from being tracked ([83457d5](https://github.com/tamylaa/clodo-framework/commit/83457d58b1e8d44865a593b2b75e39f4509a75bf))
20
+
21
+ ## [4.0.11](https://github.com/tamylaa/clodo-framework/compare/v4.0.10...v4.0.11) (2025-12-12)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * add wrangler compatibility and config validation utilities ([98c745b](https://github.com/tamylaa/clodo-framework/commit/98c745bb72608a4d5808c947cc9c7a39b868243f))
27
+ * correct import path in file-manager.js for proper dist compilation ([166698e](https://github.com/tamylaa/clodo-framework/commit/166698ebb9473ba5d889739831a1d4e1040c12a5))
28
+ * correct NPM_TOKEN environment variable name in GitHub Actions workflow ([938c953](https://github.com/tamylaa/clodo-framework/commit/938c953d7e2e9f1a679ee566f1a4c7a070e629e6))
29
+ * trigger patch release for latest changes ([6161573](https://github.com/tamylaa/clodo-framework/commit/6161573ead8271eaea2766bf7c85f63ef7378a7d))
30
+ * update .gitignore to prevent test artifacts from being tracked ([83457d5](https://github.com/tamylaa/clodo-framework/commit/83457d58b1e8d44865a593b2b75e39f4509a75bf))
31
+
32
+ ## [4.0.11](https://github.com/tamylaa/clodo-framework/compare/v4.0.10...v4.0.11) (2025-12-12)
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * add wrangler compatibility and config validation utilities ([98c745b](https://github.com/tamylaa/clodo-framework/commit/98c745bb72608a4d5808c947cc9c7a39b868243f))
38
+ * correct import path in file-manager.js for proper dist compilation ([166698e](https://github.com/tamylaa/clodo-framework/commit/166698ebb9473ba5d889739831a1d4e1040c12a5))
39
+ * correct NPM_TOKEN environment variable name in GitHub Actions workflow ([938c953](https://github.com/tamylaa/clodo-framework/commit/938c953d7e2e9f1a679ee566f1a4c7a070e629e6))
40
+ * update .gitignore to prevent test artifacts from being tracked ([83457d5](https://github.com/tamylaa/clodo-framework/commit/83457d58b1e8d44865a593b2b75e39f4509a75bf))
41
+
42
+ ## [4.0.11](https://github.com/tamylaa/clodo-framework/compare/v4.0.10...v4.0.11) (2025-12-12)
43
+
44
+
45
+ ### Bug Fixes
46
+
47
+ * add wrangler compatibility and config validation utilities ([98c745b](https://github.com/tamylaa/clodo-framework/commit/98c745bb72608a4d5808c947cc9c7a39b868243f))
48
+ * correct import path in file-manager.js for proper dist compilation ([166698e](https://github.com/tamylaa/clodo-framework/commit/166698ebb9473ba5d889739831a1d4e1040c12a5))
49
+ * correct NPM_TOKEN environment variable name in GitHub Actions workflow ([938c953](https://github.com/tamylaa/clodo-framework/commit/938c953d7e2e9f1a679ee566f1a4c7a070e629e6))
50
+ * update .gitignore to prevent test artifacts from being tracked ([83457d5](https://github.com/tamylaa/clodo-framework/commit/83457d58b1e8d44865a593b2b75e39f4509a75bf))
51
+
1
52
  ## [4.0.10](https://github.com/tamylaa/clodo-framework/compare/v4.0.9...v4.0.10) (2025-12-10)
2
53
 
3
54
 
@@ -5,7 +5,8 @@
5
5
 
6
6
  import chalk from 'chalk';
7
7
  import path from 'path';
8
- import { ServiceOrchestrator, StandardOptions, ServiceConfigManager } from '@tamyla/clodo-framework';
8
+ import { ServiceOrchestrator, ServiceConfigManager } from '@tamyla/clodo-framework';
9
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
9
10
  export function registerAssessCommand(program) {
10
11
  const command = program.command('assess [service-path]').description('Run intelligent capability assessment (requires @tamyla/clodo-orchestration)').option('--export <file>', 'Export assessment results to JSON file').option('--domain <domain>', 'Domain name for assessment').option('--service-type <type>', 'Service type for assessment').option('--token <token>', 'Cloudflare API token').option('--show-config-sources', 'Display all configuration sources and merged result');
11
12
 
@@ -6,7 +6,8 @@
6
6
  */
7
7
 
8
8
  import chalk from 'chalk';
9
- import { Clodo, StandardOptions, ConfigLoader } from '@tamyla/clodo-framework';
9
+ import { Clodo, ConfigLoader } from '@tamyla/clodo-framework';
10
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
10
11
  export function registerCreateCommand(program) {
11
12
  const command = program.command('create').description('Create a new Clodo service with conversational setup').option('-n, --non-interactive', 'Run in non-interactive mode with all required parameters').option('--service-name <name>', 'Service name (required in non-interactive mode)').option('--service-type <type>', 'Service type: data-service, auth-service, content-service, api-gateway, generic', 'generic').option('--domain-name <domain>', 'Domain name (required in non-interactive mode)').option('--cloudflare-token <token>', 'Cloudflare API token (required in non-interactive mode)').option('--cloudflare-account-id <id>', 'Cloudflare account ID (required in non-interactive mode)').option('--cloudflare-zone-id <id>', 'Cloudflare zone ID (required in non-interactive mode)').option('--environment <env>', 'Target environment: development, staging, production', 'development').option('--output-path <path>', 'Output directory for generated service', '.').option('--template-path <path>', 'Path to service templates', './templates').option('--force', 'Skip confirmation prompts').option('--validate', 'Validate service after creation');
12
13
 
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
- import { Clodo, StandardOptions, ConfigLoader, InteractiveDeploymentCoordinator, OutputFormatter } from '@tamyla/clodo-framework';
2
+ import { Clodo, ConfigLoader, InteractiveDeploymentCoordinator, OutputFormatter } from '@tamyla/clodo-framework';
3
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
3
4
  export function registerDeployCommand(program) {
4
5
  const command = program.command('deploy').description('Deploy a Clodo service with interactive configuration and validation')
5
6
  // Cloudflare-specific options
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import path from 'path';
3
- import { ServiceOrchestrator, StandardOptions, ServiceConfigManager } from '@tamyla/clodo-framework';
3
+ import { ServiceOrchestrator, ServiceConfigManager } from '@tamyla/clodo-framework';
4
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
4
5
  export function registerDiagnoseCommand(program) {
5
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').option('--show-config-sources', 'Display all configuration sources and merged result');
6
7
 
@@ -4,7 +4,8 @@
4
4
 
5
5
  import chalk from 'chalk';
6
6
  import path from 'path';
7
- import { ServiceOrchestrator, StandardOptions, ServiceConfigManager } from '@tamyla/clodo-framework';
7
+ import { ServiceOrchestrator, ServiceConfigManager } from '@tamyla/clodo-framework';
8
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
8
9
  export function registerUpdateCommand(program) {
9
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('--show-config-sources', 'Display all configuration sources and merged result').option('--force', 'Skip confirmation prompts');
10
11
 
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
- import { Clodo, StandardOptions } from '@tamyla/clodo-framework';
2
+ import { Clodo } from '@tamyla/clodo-framework';
3
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
3
4
  export function registerValidateCommand(program) {
4
5
  const command = program.command('validate <service-path>').description('Validate an existing service configuration').option('--export-report <file>', 'Export validation report to JSON file');
5
6
 
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Configuration Validator
3
+ *
4
+ * Validates service configurations before deployment to catch issues early
5
+ * and provide actionable error messages.
6
+ */
7
+
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { join, dirname } from 'path';
10
+ import { WranglerCompatibilityDetector } from './wrangler-compatibility.js';
11
+ export class ConfigurationValidator {
12
+ /**
13
+ * Validate a service configuration
14
+ * @param {string} servicePath - Path to the service directory
15
+ * @returns {Promise<ValidationResult>} Validation result
16
+ */
17
+ static async validateServiceConfig(servicePath) {
18
+ const result = {
19
+ isValid: true,
20
+ errors: [],
21
+ warnings: [],
22
+ suggestions: []
23
+ };
24
+ try {
25
+ // Check if wrangler.toml exists
26
+ const wranglerPath = join(servicePath, 'wrangler.toml');
27
+ if (!existsSync(wranglerPath)) {
28
+ result.isValid = false;
29
+ result.errors.push('Missing wrangler.toml file');
30
+ result.suggestions.push('Run: npx clodo-service create --service-name <name>');
31
+ return result;
32
+ }
33
+
34
+ // Load and parse wrangler.toml
35
+ const wranglerConfig = ConfigurationValidator.parseWranglerToml(wranglerPath);
36
+ if (!wranglerConfig) {
37
+ result.isValid = false;
38
+ result.errors.push('Invalid wrangler.toml format');
39
+ return result;
40
+ }
41
+
42
+ // Validate required fields
43
+ ConfigurationValidator.validateRequiredFields(wranglerConfig, result);
44
+
45
+ // Validate compatibility configuration
46
+ await ConfigurationValidator.validateCompatibilityConfig(wranglerConfig, result);
47
+
48
+ // Validate build configuration
49
+ ConfigurationValidator.validateBuildConfig(wranglerConfig, result);
50
+
51
+ // Check for common issues
52
+ ConfigurationValidator.checkCommonIssues(servicePath, wranglerConfig, result);
53
+ } catch (error) {
54
+ result.isValid = false;
55
+ result.errors.push(`Configuration validation failed: ${error.message}`);
56
+ }
57
+ return result;
58
+ }
59
+
60
+ /**
61
+ * Parse wrangler.toml file
62
+ * @param {string} filePath - Path to wrangler.toml
63
+ * @returns {Object|null} Parsed configuration or null if invalid
64
+ */
65
+ static parseWranglerToml(filePath) {
66
+ try {
67
+ const content = readFileSync(filePath, 'utf8');
68
+ // Simple TOML-like parsing (basic implementation)
69
+ const config = {};
70
+ const lines = content.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith('#'));
71
+ let currentSection = null;
72
+ for (const line of lines) {
73
+ if (line.startsWith('[') && line.endsWith(']')) {
74
+ currentSection = line.slice(1, -1);
75
+ config[currentSection] = {};
76
+ } else if (line.includes('=')) {
77
+ const [key, ...valueParts] = line.split('=');
78
+ const value = valueParts.join('=').trim();
79
+ if (currentSection) {
80
+ config[currentSection][key.trim()] = this.parseValue(value);
81
+ } else {
82
+ config[key.trim()] = this.parseValue(value);
83
+ }
84
+ }
85
+ }
86
+ return config;
87
+ } catch (error) {
88
+ console.warn(`Failed to parse wrangler.toml: ${error.message}`);
89
+ return null;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Parse TOML value
95
+ * @param {string} value - Raw value string
96
+ * @returns {any} Parsed value
97
+ */
98
+ static parseValue(value) {
99
+ // Remove quotes
100
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
101
+ return value.slice(1, -1);
102
+ }
103
+
104
+ // Parse booleans
105
+ if (value === 'true') return true;
106
+ if (value === 'false') return false;
107
+
108
+ // Parse arrays (basic)
109
+ if (value.startsWith('[') && value.endsWith(']')) {
110
+ try {
111
+ return JSON.parse(value);
112
+ } catch {
113
+ return value;
114
+ }
115
+ }
116
+ return value;
117
+ }
118
+
119
+ /**
120
+ * Validate required fields
121
+ * @param {Object} config - Wrangler configuration
122
+ * @param {ValidationResult} result - Validation result to update
123
+ */
124
+ static validateRequiredFields(config, result) {
125
+ const required = ['name', 'main', 'compatibility_date', 'account_id'];
126
+ for (const field of required) {
127
+ if (!config[field]) {
128
+ result.isValid = false;
129
+ result.errors.push(`Missing required field: ${field}`);
130
+ }
131
+ }
132
+
133
+ // Check for D1 databases
134
+ if (config['d1_databases'] || config['[[d1_databases]]']) {
135
+ if (!config['d1_databases']?.binding || !config['[[d1_databases]]']?.binding) {
136
+ result.warnings.push('D1 database configured but binding not specified');
137
+ }
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Validate compatibility configuration
143
+ * @param {Object} config - Wrangler configuration
144
+ * @param {ValidationResult} result - Validation result to update
145
+ */
146
+ static async validateCompatibilityConfig(config, result) {
147
+ try {
148
+ const detector = new WranglerCompatibilityDetector();
149
+ const wranglerVersion = await Promise.race([detector.detectVersion(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000))]);
150
+ const optimalConfig = detector.getOptimalConfig(wranglerVersion);
151
+
152
+ // Check if current config matches optimal
153
+ if (optimalConfig.nodejs_compat !== undefined) {
154
+ if (config.nodejs_compat !== optimalConfig.nodejs_compat) {
155
+ result.warnings.push(`nodejs_compat should be ${optimalConfig.nodejs_compat} for Wrangler ${wranglerVersion}`);
156
+ result.suggestions.push('Consider updating compatibility configuration');
157
+ }
158
+ } else if (optimalConfig.compatibility_flags) {
159
+ const currentFlags = config.compatibility_flags;
160
+ if (!currentFlags || !Array.isArray(currentFlags) || !ConfigurationValidator.arraysEqual(currentFlags, optimalConfig.compatibility_flags)) {
161
+ result.warnings.push(`compatibility_flags should be ${JSON.stringify(optimalConfig.compatibility_flags)} for Wrangler ${wranglerVersion}`);
162
+ result.suggestions.push('Consider updating compatibility configuration');
163
+ }
164
+ }
165
+ } catch (error) {
166
+ result.warnings.push(`Could not validate compatibility configuration: ${error.message}`);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Validate build configuration
172
+ * @param {Object} config - Wrangler configuration
173
+ * @param {ValidationResult} result - Validation result to update
174
+ */
175
+ static validateBuildConfig(config, result) {
176
+ const build = config.build || {};
177
+
178
+ // Check for build command
179
+ if (!build.command) {
180
+ result.warnings.push('No build command specified - deployment may fail if code needs compilation');
181
+ result.suggestions.push('Add: [build] command = "npm run build"');
182
+ }
183
+
184
+ // Check for upload format
185
+ const upload = build.upload || {};
186
+ if (!upload.format) {
187
+ result.warnings.push('No upload format specified - using default may cause issues');
188
+ result.suggestions.push('Add: [build.upload] format = "modules"');
189
+ }
190
+
191
+ // Check for external includes (important for Node.js modules)
192
+ const external = upload.external || {};
193
+ if (!external.include || !external.include.includes('node:*')) {
194
+ result.warnings.push('Node.js modules may not be properly externalized');
195
+ result.suggestions.push('Add: [build.upload.external] include = ["node:*"]');
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Check for common configuration issues
201
+ * @param {string} servicePath - Service directory path
202
+ * @param {Object} config - Wrangler configuration
203
+ * @param {ValidationResult} result - Validation result to update
204
+ */
205
+ static checkCommonIssues(servicePath, config, result) {
206
+ // Check if main file exists
207
+ if (config.main) {
208
+ const mainPath = join(servicePath, config.main);
209
+ if (!existsSync(mainPath)) {
210
+ result.isValid = false;
211
+ result.errors.push(`Main file does not exist: ${config.main}`);
212
+ result.suggestions.push('Ensure the main file exists or update the main field in wrangler.toml');
213
+ }
214
+ }
215
+
216
+ // Check for environment variables without values
217
+ const vars = config.vars || {};
218
+ for (const [key, value] of Object.entries(vars)) {
219
+ if (value === '' || value === '""') {
220
+ result.warnings.push(`Environment variable ${key} has empty value`);
221
+ }
222
+ }
223
+
224
+ // Check for database_id placeholders
225
+ if (config['d1_databases']?.database_id === '') {
226
+ result.warnings.push('D1 database_id is empty - needs to be configured before deployment');
227
+ result.suggestions.push('Run: wrangler d1 create <database-name>');
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Check if two arrays are equal
233
+ * @param {Array} a - First array
234
+ * @param {Array} b - Second array
235
+ * @returns {boolean} True if arrays are equal
236
+ */
237
+ static arraysEqual(a, b) {
238
+ if (a.length !== b.length) return false;
239
+ return a.every((val, index) => val === b[index]);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Validation result interface
245
+ * @typedef {Object} ValidationResult
246
+ * @property {boolean} isValid - Whether configuration is valid
247
+ * @property {string[]} errors - Critical errors that prevent deployment
248
+ * @property {string[]} warnings - Non-critical issues that should be addressed
249
+ * @property {string[]} suggestions - Actionable suggestions for improvement
250
+ */
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Wrangler Compatibility Detector
3
+ *
4
+ * Automatically detects Wrangler version and provides optimal configuration
5
+ * to prevent deployment failures due to compatibility issues.
6
+ */
7
+
8
+ import { exec } from 'child_process';
9
+ import { promisify } from 'util';
10
+ const execAsync = promisify(exec);
11
+
12
+ /**
13
+ * Wrangler Compatibility Detector
14
+ * Handles version detection and optimal configuration generation
15
+ */
16
+ export class WranglerCompatibilityDetector {
17
+ /**
18
+ * Detects the installed Wrangler version
19
+ * @returns {Promise<string>} Wrangler version string
20
+ */
21
+ async detectVersion() {
22
+ try {
23
+ const {
24
+ stdout
25
+ } = await execAsync('npx wrangler --version');
26
+ const version = stdout.trim();
27
+
28
+ // Validate version format (e.g., "3.15.0", "4.0.0")
29
+ if (!this.isValidVersion(version)) {
30
+ console.warn(`⚠️ Detected Wrangler version '${version}' may not be valid. Using fallback detection.`);
31
+ return this.fallbackDetection();
32
+ }
33
+ return version;
34
+ } catch (error) {
35
+ console.warn(`⚠️ Failed to detect Wrangler version: ${error.message}`);
36
+ return this.fallbackDetection();
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Validates version string format
42
+ * @param {string} version - Version string to validate
43
+ * @returns {boolean} True if version format is valid
44
+ */
45
+ isValidVersion(version) {
46
+ // Match semantic versioning (e.g., 3.15.0, 4.0.0-beta.1)
47
+ const versionRegex = /^\d+\.\d+\.\d+(-[\w\.\-]+)?$/;
48
+ return versionRegex.test(version);
49
+ }
50
+
51
+ /**
52
+ * Fallback version detection when direct detection fails
53
+ * @returns {string} Fallback version string
54
+ */
55
+ fallbackDetection() {
56
+ // Check for wrangler in package.json
57
+ try {
58
+ const fs = require('fs');
59
+ const path = require('path');
60
+
61
+ // Look for wrangler in package.json
62
+ const packagePath = path.join(process.cwd(), 'package.json');
63
+ if (fs.existsSync(packagePath)) {
64
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
65
+ const wranglerVersion = packageJson.dependencies?.wrangler || packageJson.devDependencies?.wrangler;
66
+ if (wranglerVersion) {
67
+ // Extract version from semver range (e.g., "^3.15.0" -> "3.15.0")
68
+ const cleanVersion = wranglerVersion.replace(/[^\d.]/g, '');
69
+ if (cleanVersion) {
70
+ console.log(`📋 Using Wrangler version from package.json: ${cleanVersion}`);
71
+ return cleanVersion;
72
+ }
73
+ }
74
+ }
75
+ } catch (error) {
76
+ console.warn(`⚠️ Fallback detection failed: ${error.message}`);
77
+ }
78
+
79
+ // Default to latest stable version
80
+ console.log('📋 Using default Wrangler version: 4.0.0');
81
+ return '4.0.0';
82
+ }
83
+
84
+ /**
85
+ * Gets optimal Wrangler configuration for the detected version
86
+ * @param {string} version - Wrangler version
87
+ * @returns {Object} Optimal configuration object
88
+ */
89
+ getOptimalConfig(version) {
90
+ const majorVersion = parseInt(version.split('.')[0]);
91
+ if (majorVersion >= 4) {
92
+ // Wrangler v4+ uses nodejs_compat flag
93
+ return {
94
+ nodejs_compat: true
95
+ };
96
+ } else if (majorVersion >= 3) {
97
+ // Wrangler v3 uses compatibility_flags array
98
+ return {
99
+ compatibility_flags: ["nodejs_compat"]
100
+ };
101
+ } else {
102
+ // Wrangler v2 and below - minimal compatibility
103
+ return {
104
+ compatibility_flags: []
105
+ };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Gets build configuration optimized for the Wrangler version
111
+ * @param {string} version - Wrangler version
112
+ * @returns {Object} Build configuration object
113
+ */
114
+ getBuildConfig(version) {
115
+ const majorVersion = parseInt(version.split('.')[0]);
116
+ const baseConfig = {
117
+ command: "npm run build",
118
+ upload: {
119
+ format: "modules"
120
+ }
121
+ };
122
+
123
+ // Add Node.js module externalization for v3+
124
+ if (majorVersion >= 3) {
125
+ baseConfig.upload.external = {
126
+ include: ["node:*"]
127
+ };
128
+ }
129
+ return baseConfig;
130
+ }
131
+
132
+ /**
133
+ * Validates if current configuration is optimal for the Wrangler version
134
+ * @param {Object} config - Current Wrangler configuration
135
+ * @param {string} version - Wrangler version
136
+ * @returns {Object} Validation result with issues and suggestions
137
+ */
138
+ validateConfig(config, version) {
139
+ const issues = [];
140
+ const optimalConfig = this.getOptimalConfig(version);
141
+
142
+ // Check Node.js compatibility settings
143
+ if (optimalConfig.nodejs_compat !== undefined) {
144
+ if (config.nodejs_compat !== optimalConfig.nodejs_compat) {
145
+ issues.push({
146
+ type: 'COMPATIBILITY_CONFIG',
147
+ severity: 'ERROR',
148
+ message: `nodejs_compat should be ${optimalConfig.nodejs_compat} for Wrangler ${version}`,
149
+ fix: `Set nodejs_compat = ${optimalConfig.nodejs_compat} in wrangler.toml`
150
+ });
151
+ }
152
+ } else if (optimalConfig.compatibility_flags) {
153
+ const hasNodejsCompat = config.compatibility_flags?.includes('nodejs_compat');
154
+ const shouldHaveNodejsCompat = optimalConfig.compatibility_flags.includes('nodejs_compat');
155
+ if (hasNodejsCompat !== shouldHaveNodejsCompat) {
156
+ issues.push({
157
+ type: 'COMPATIBILITY_FLAGS',
158
+ severity: 'ERROR',
159
+ message: `compatibility_flags should ${shouldHaveNodejsCompat ? 'include' : 'exclude'} "nodejs_compat" for Wrangler ${version}`,
160
+ fix: shouldHaveNodejsCompat ? 'Add "nodejs_compat" to compatibility_flags array' : 'Remove "nodejs_compat" from compatibility_flags array'
161
+ });
162
+ }
163
+ }
164
+ return {
165
+ isValid: issues.filter(issue => issue.severity === 'ERROR').length === 0,
166
+ issues
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Generates a complete optimized Wrangler configuration
172
+ * @param {string} version - Wrangler version
173
+ * @param {Object} baseConfig - Base configuration to extend
174
+ * @returns {Object} Complete optimized configuration
175
+ */
176
+ generateOptimalConfig(version, baseConfig = {}) {
177
+ const optimalConfig = this.getOptimalConfig(version);
178
+ const buildConfig = this.getBuildConfig(version);
179
+ return {
180
+ ...baseConfig,
181
+ ...optimalConfig,
182
+ build: {
183
+ ...buildConfig,
184
+ ...(baseConfig.build || {})
185
+ }
186
+ };
187
+ }
188
+ }
189
+
190
+ // Export singleton instance for convenience
191
+ export const wranglerCompatibility = new WranglerCompatibilityDetector();
@@ -0,0 +1,383 @@
1
+ /**
2
+ * Configuration Validator
3
+ *
4
+ * Validates Wrangler and service configurations before deployment
5
+ * to prevent common deployment failures and provide actionable feedback.
6
+ */
7
+
8
+ import { WranglerCompatibilityDetector } from '../utils/wrangler-compatibility.js';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+
12
+ /**
13
+ * Configuration Validation Result
14
+ * @typedef {Object} ValidationResult
15
+ * @property {boolean} isValid - Whether configuration is valid
16
+ * @property {ValidationIssue[]} issues - List of validation issues
17
+ * @property {string[]} warnings - Non-blocking warnings
18
+ * @property {string[]} suggestions - Improvement suggestions
19
+ */
20
+
21
+ /**
22
+ * Validation Issue
23
+ * @typedef {Object} ValidationIssue
24
+ * @property {string} type - Issue type identifier
25
+ * @property {string} severity - ERROR, WARNING, or INFO
26
+ * @property {string} message - Human-readable message
27
+ * @property {string} fix - Suggested fix
28
+ * @property {string} [file] - File where issue occurs
29
+ * @property {number} [line] - Line number if applicable
30
+ */
31
+
32
+ /**
33
+ * Configuration Validator
34
+ * Handles pre-deployment configuration validation
35
+ */
36
+ export class ConfigurationValidator {
37
+ constructor() {
38
+ this.compatibilityDetector = new WranglerCompatibilityDetector();
39
+ }
40
+
41
+ /**
42
+ * Validates a complete service configuration
43
+ * @param {string} servicePath - Path to service directory
44
+ * @param {Object} options - Validation options
45
+ * @returns {Promise<ValidationResult>} Validation result
46
+ */
47
+ async validateServiceConfig(servicePath, options = {}) {
48
+ const issues = [];
49
+ const warnings = [];
50
+ const suggestions = [];
51
+ try {
52
+ // Load wrangler.toml
53
+ const wranglerConfig = await this.loadWranglerConfig(servicePath);
54
+ if (!wranglerConfig) {
55
+ issues.push({
56
+ type: 'MISSING_CONFIG',
57
+ severity: 'ERROR',
58
+ message: 'wrangler.toml not found in service directory',
59
+ fix: 'Create wrangler.toml file or run service generation'
60
+ });
61
+ return {
62
+ isValid: false,
63
+ issues,
64
+ warnings,
65
+ suggestions
66
+ };
67
+ }
68
+
69
+ // Detect Wrangler version
70
+ const wranglerVersion = await this.compatibilityDetector.detectVersion();
71
+
72
+ // Validate compatibility settings
73
+ const compatibilityResult = this.compatibilityDetector.validateConfig(wranglerConfig, wranglerVersion);
74
+ issues.push(...compatibilityResult.issues);
75
+
76
+ // Validate build configuration
77
+ const buildIssues = this.validateBuildConfig(wranglerConfig);
78
+ issues.push(...buildIssues);
79
+
80
+ // Validate service structure
81
+ const structureIssues = await this.validateServiceStructure(servicePath);
82
+ issues.push(...structureIssues);
83
+
84
+ // Check for common issues
85
+ const commonIssues = await this.checkCommonIssues(servicePath, wranglerConfig);
86
+ warnings.push(...commonIssues.warnings);
87
+ suggestions.push(...commonIssues.suggestions);
88
+
89
+ // Generate improvement suggestions
90
+ if (options.verbose) {
91
+ suggestions.push(...this.generateOptimizationSuggestions(wranglerConfig, wranglerVersion));
92
+ }
93
+ const isValid = issues.filter(issue => issue.severity === 'ERROR').length === 0;
94
+ return {
95
+ isValid,
96
+ issues,
97
+ warnings,
98
+ suggestions
99
+ };
100
+ } catch (error) {
101
+ issues.push({
102
+ type: 'VALIDATION_ERROR',
103
+ severity: 'ERROR',
104
+ message: `Configuration validation failed: ${error.message}`,
105
+ fix: 'Check service directory structure and configuration files'
106
+ });
107
+ return {
108
+ isValid: false,
109
+ issues,
110
+ warnings,
111
+ suggestions
112
+ };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Loads and parses wrangler.toml configuration
118
+ * @param {string} servicePath - Path to service directory
119
+ * @returns {Promise<Object|null>} Parsed configuration or null if not found
120
+ */
121
+ async loadWranglerConfig(servicePath) {
122
+ const configPath = path.join(servicePath, 'wrangler.toml');
123
+ try {
124
+ if (!fs.existsSync(configPath)) {
125
+ return null;
126
+ }
127
+
128
+ // For now, return a basic parsed config
129
+ // In a real implementation, you'd use a TOML parser
130
+ const configContent = fs.readFileSync(configPath, 'utf8');
131
+
132
+ // Basic TOML parsing (simplified - would use proper TOML parser in production)
133
+ return this.parseTomlConfig(configContent);
134
+ } catch (error) {
135
+ throw new Error(`Failed to load wrangler.toml: ${error.message}`);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Basic TOML config parser (simplified)
141
+ * @param {string} content - TOML content
142
+ * @returns {Object} Parsed configuration
143
+ */
144
+ parseTomlConfig(content) {
145
+ const config = {};
146
+
147
+ // Very basic parsing - in production, use a proper TOML library
148
+ const lines = content.split('\n');
149
+ for (const line of lines) {
150
+ const trimmed = line.trim();
151
+
152
+ // Skip comments and empty lines
153
+ if (!trimmed || trimmed.startsWith('#')) continue;
154
+
155
+ // Parse key = value
156
+ const equalsIndex = trimmed.indexOf('=');
157
+ if (equalsIndex > 0) {
158
+ const key = trimmed.substring(0, equalsIndex).trim();
159
+ const value = trimmed.substring(equalsIndex + 1).trim();
160
+
161
+ // Handle different value types
162
+ if (value === 'true') {
163
+ config[key] = true;
164
+ } else if (value === 'false') {
165
+ config[key] = false;
166
+ } else if (value.startsWith('"') && value.endsWith('"')) {
167
+ config[key] = value.slice(1, -1);
168
+ } else if (value.startsWith('[') && value.endsWith(']')) {
169
+ // Basic array parsing
170
+ const arrayContent = value.slice(1, -1);
171
+ config[key] = arrayContent.split(',').map(item => item.trim().replace(/"/g, ''));
172
+ } else {
173
+ config[key] = value;
174
+ }
175
+ }
176
+ }
177
+ return config;
178
+ }
179
+
180
+ /**
181
+ * Validates build configuration
182
+ * @param {Object} config - Wrangler configuration
183
+ * @returns {ValidationIssue[]} Build-related issues
184
+ */
185
+ validateBuildConfig(config) {
186
+ const issues = [];
187
+
188
+ // Check build command
189
+ if (!config.build?.command) {
190
+ issues.push({
191
+ type: 'MISSING_BUILD_COMMAND',
192
+ severity: 'WARNING',
193
+ message: 'Build command not specified',
194
+ fix: 'Add build.command = "npm run build" to wrangler.toml'
195
+ });
196
+ }
197
+
198
+ // Check upload format
199
+ if (config.build?.upload?.format !== 'modules') {
200
+ issues.push({
201
+ type: 'BUILD_FORMAT',
202
+ severity: 'INFO',
203
+ message: 'Consider using modules format for better performance',
204
+ fix: 'Set build.upload.format = "modules"'
205
+ });
206
+ }
207
+ return issues;
208
+ }
209
+
210
+ /**
211
+ * Validates service directory structure
212
+ * @param {string} servicePath - Path to service directory
213
+ * @returns {Promise<ValidationIssue[]>} Structure-related issues
214
+ */
215
+ async validateServiceStructure(servicePath) {
216
+ const issues = [];
217
+
218
+ // Check for package.json
219
+ const packagePath = path.join(servicePath, 'package.json');
220
+ if (!fs.existsSync(packagePath)) {
221
+ issues.push({
222
+ type: 'MISSING_PACKAGE_JSON',
223
+ severity: 'ERROR',
224
+ message: 'package.json not found',
225
+ fix: 'Create package.json with service dependencies'
226
+ });
227
+ }
228
+
229
+ // Check for src directory
230
+ const srcPath = path.join(servicePath, 'src');
231
+ if (!fs.existsSync(srcPath)) {
232
+ issues.push({
233
+ type: 'MISSING_SRC_DIR',
234
+ severity: 'WARNING',
235
+ message: 'src directory not found',
236
+ fix: 'Create src directory with service code'
237
+ });
238
+ }
239
+ return issues;
240
+ }
241
+
242
+ /**
243
+ * Checks for common configuration issues
244
+ * @param {string} servicePath - Path to service directory
245
+ * @param {Object} config - Wrangler configuration
246
+ * @returns {Object} Common issues with warnings and suggestions
247
+ */
248
+ async checkCommonIssues(servicePath, config) {
249
+ const warnings = [];
250
+ const suggestions = [];
251
+
252
+ // Check for large bundle potential
253
+ if (!config.build?.upload?.external?.include?.includes('node:*')) {
254
+ warnings.push('Consider externalizing Node.js modules to reduce bundle size');
255
+ }
256
+
257
+ // Check for missing environment variables
258
+ if (config.vars) {
259
+ const varKeys = Object.keys(config.vars);
260
+ suggestions.push(`Consider moving ${varKeys.length} environment variables to .env file for security`);
261
+ }
262
+ return {
263
+ warnings,
264
+ suggestions
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Generates optimization suggestions
270
+ * @param {Object} config - Current configuration
271
+ * @param {string} wranglerVersion - Wrangler version
272
+ * @returns {string[]} Optimization suggestions
273
+ */
274
+ generateOptimizationSuggestions(config, wranglerVersion) {
275
+ const suggestions = [];
276
+
277
+ // Performance suggestions
278
+ if (!config.build?.minify) {
279
+ suggestions.push('Enable minification: build.minify = true');
280
+ }
281
+
282
+ // Security suggestions
283
+ if (!config.build?.upload?.external) {
284
+ suggestions.push('Externalize dependencies to reduce bundle size and improve cold start performance');
285
+ }
286
+ return suggestions;
287
+ }
288
+
289
+ /**
290
+ * Fixes common configuration issues automatically
291
+ * @param {string} servicePath - Path to service directory
292
+ * @param {ValidationResult} validationResult - Validation result
293
+ * @returns {Promise<Object>} Fix result
294
+ */
295
+ async autoFix(servicePath, validationResult) {
296
+ const fixes = [];
297
+ const errors = [];
298
+ try {
299
+ const wranglerVersion = await this.compatibilityDetector.detectVersion();
300
+ const currentConfig = await this.loadWranglerConfig(servicePath);
301
+ if (!currentConfig) {
302
+ errors.push('Cannot auto-fix: wrangler.toml not found');
303
+ return {
304
+ success: false,
305
+ fixes,
306
+ errors
307
+ };
308
+ }
309
+
310
+ // Generate optimal config
311
+ const optimalConfig = this.compatibilityDetector.generateOptimalConfig(wranglerVersion, currentConfig);
312
+
313
+ // Apply fixes for auto-fixable issues
314
+ for (const issue of validationResult.issues) {
315
+ if (issue.type === 'COMPATIBILITY_CONFIG' && optimalConfig.nodejs_compat !== undefined) {
316
+ optimalConfig.nodejs_compat = optimalConfig.nodejs_compat;
317
+ fixes.push('Fixed nodejs_compat setting');
318
+ } else if (issue.type === 'COMPATIBILITY_FLAGS') {
319
+ optimalConfig.compatibility_flags = optimalConfig.compatibility_flags;
320
+ fixes.push('Fixed compatibility_flags setting');
321
+ }
322
+ }
323
+
324
+ // Save updated config
325
+ await this.saveWranglerConfig(servicePath, optimalConfig);
326
+ fixes.push('Updated wrangler.toml with optimal configuration');
327
+ return {
328
+ success: true,
329
+ fixes,
330
+ errors
331
+ };
332
+ } catch (error) {
333
+ errors.push(`Auto-fix failed: ${error.message}`);
334
+ return {
335
+ success: false,
336
+ fixes,
337
+ errors
338
+ };
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Saves wrangler configuration to file
344
+ * @param {string} servicePath - Path to service directory
345
+ * @param {Object} config - Configuration to save
346
+ */
347
+ async saveWranglerConfig(servicePath, config) {
348
+ const configPath = path.join(servicePath, 'wrangler.toml');
349
+
350
+ // Basic TOML generation (simplified - would use proper TOML library in production)
351
+ let tomlContent = '# Auto-generated by Clodo Framework\n\n';
352
+
353
+ // Add main config
354
+ if (config.name) tomlContent += `name = "${config.name}"\n`;
355
+ if (config.main) tomlContent += `main = "${config.main}"\n`;
356
+ if (config.compatibility_flags) {
357
+ tomlContent += `compatibility_flags = ${JSON.stringify(config.compatibility_flags)}\n`;
358
+ }
359
+ if (config.nodejs_compat !== undefined) {
360
+ tomlContent += `nodejs_compat = ${config.nodejs_compat}\n`;
361
+ }
362
+
363
+ // Add build config
364
+ if (config.build) {
365
+ tomlContent += '\n[build]\n';
366
+ if (config.build.command) tomlContent += `command = "${config.build.command}"\n`;
367
+ if (config.build.upload) {
368
+ tomlContent += '\n[build.upload]\n';
369
+ if (config.build.upload.format) tomlContent += `format = "${config.build.upload.format}"\n`;
370
+ if (config.build.upload.external) {
371
+ tomlContent += '\n[build.upload.external]\n';
372
+ if (config.build.upload.external.include) {
373
+ tomlContent += `include = ${JSON.stringify(config.build.upload.external.include)}\n`;
374
+ }
375
+ }
376
+ }
377
+ }
378
+ fs.writeFileSync(configPath, tomlContent, 'utf8');
379
+ }
380
+ }
381
+
382
+ // Export singleton instance
383
+ export const configValidator = new ConfigurationValidator();
@@ -1,4 +1,5 @@
1
1
  import { BaseGenerator } from '../BaseGenerator.js';
2
+ import { WranglerCompatibilityDetector } from '../../../../lib/shared/utils/wrangler-compatibility.js';
2
3
  import { join } from 'path';
3
4
 
4
5
  /**
@@ -58,7 +59,7 @@ export class WranglerTomlGenerator extends BaseGenerator {
58
59
  const siteConfig = await this._generateSiteConfig(coreInputs);
59
60
 
60
61
  // Build wrangler.toml content
61
- const content = this._buildWranglerToml(coreInputs, confirmedValues, routesConfig, siteConfig);
62
+ const content = await this._buildWranglerToml(coreInputs, confirmedValues, routesConfig, siteConfig);
62
63
 
63
64
  // Write file
64
65
  await this.writeFile('wrangler.toml', content);
@@ -115,13 +116,42 @@ export class WranglerTomlGenerator extends BaseGenerator {
115
116
  /**
116
117
  * Build the complete wrangler.toml content
117
118
  */
118
- _buildWranglerToml(coreInputs, confirmedValues, routesConfig, siteConfig) {
119
+ async _buildWranglerToml(coreInputs, confirmedValues, routesConfig, siteConfig) {
119
120
  const compatDate = new Date().toISOString().split('T')[0];
121
+
122
+ // Detect Wrangler version and get optimal compatibility config
123
+ let compatibilityString = 'compatibility_flags = ["nodejs_compat"]';
124
+ let buildString = '\n\n[build]\ncommand = "npm run build"\n\n[build.upload]\nformat = "modules"\n\n[build.upload.external]\ninclude = ["node:*"]';
125
+ try {
126
+ const compatibilityDetector = new WranglerCompatibilityDetector();
127
+ const wranglerVersion = await Promise.race([compatibilityDetector.detectVersion(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000)) // 2 second timeout
128
+ ]);
129
+ const compatibilityConfig = compatibilityDetector.getOptimalConfig(wranglerVersion);
130
+ const buildConfig = compatibilityDetector.getBuildConfig(wranglerVersion);
131
+
132
+ // Build compatibility flags string
133
+ if (compatibilityConfig.nodejs_compat !== undefined) {
134
+ compatibilityString = `nodejs_compat = ${compatibilityConfig.nodejs_compat}`;
135
+ } else if (compatibilityConfig.compatibility_flags) {
136
+ compatibilityString = `compatibility_flags = ${JSON.stringify(compatibilityConfig.compatibility_flags)}`;
137
+ }
138
+
139
+ // Add any additional build config from detector (if needed)
140
+ if (buildConfig.command && buildConfig.command !== "npm run build") {
141
+ buildString += `\n\n# Additional build config from Wrangler ${wranglerVersion}\n[build.v${wranglerVersion.split('.')[0]}]\ncommand = "${buildConfig.command}"`;
142
+ }
143
+ if (buildConfig.upload && buildConfig.upload.format && buildConfig.upload.format !== "modules") {
144
+ buildString += `\nformat_override = "${buildConfig.upload.format}"`;
145
+ }
146
+ } catch (error) {
147
+ // Fall back to default configuration if detection fails
148
+ console.warn(`⚠️ Wrangler compatibility detection failed, using defaults: ${error.message}`);
149
+ }
120
150
  return `# Cloudflare Workers Configuration for ${confirmedValues.displayName}
121
151
  name = "${confirmedValues.workerName}"
122
152
  main = "src/worker/index.js"
123
153
  compatibility_date = "${compatDate}"
124
- compatibility_flags = ["nodejs_compat"]
154
+ ${compatibilityString}${buildString}
125
155
 
126
156
  # Account configuration
127
157
  account_id = "${coreInputs.cloudflareAccountId}"
@@ -6,6 +6,8 @@
6
6
  import fs from 'fs/promises';
7
7
  import path from 'path';
8
8
  import { FrameworkConfig } from '../../utils/framework-config.js';
9
+ // import { ConfigurationValidator } from '../../lib/shared/utils/configuration-validator.js';
10
+
9
11
  export class ValidationHandler {
10
12
  constructor(options = {}) {
11
13
  this.strict = options.strict || false;
@@ -90,6 +92,19 @@ export class ValidationHandler {
90
92
  // Validate wrangler configuration
91
93
  const wranglerValidation = await this.validateWranglerConfig(servicePath);
92
94
  issues.push(...wranglerValidation.issues);
95
+
96
+ // Run comprehensive configuration validation using ConfigurationValidator
97
+ // Temporarily disabled due to import issues
98
+ // try {
99
+ // const configValidation = await ConfigurationValidator.validateServiceConfig(servicePath);
100
+ // if (!configValidation.isValid) {
101
+ // issues.push(...configValidation.errors);
102
+ // issues.push(...configValidation.warnings.map(w => `Warning: ${w}`));
103
+ // }
104
+ // } catch (error) {
105
+ // issues.push(`Configuration validation failed: ${error.message}`);
106
+ // }
107
+
93
108
  return {
94
109
  valid: issues.length === 0,
95
110
  issues,
@@ -3,18 +3,11 @@
3
3
  *
4
4
  * Re-exports FileManager from lib/ for library use.
5
5
  *
6
- * IMPORTANT PATH CALCULATION FOR NPM PACKAGE:
7
- * - Source: src/utils/file-manager.js (2 levels from root)
8
- * - Compiled: dist/utils/file-manager.js (2 levels from root)
9
- *
10
- * When installed via npm, structure is:
11
- * node_modules/@tamyla/clodo-framework/
12
- * dist/
13
- * utils/file-manager.js
14
- * lib/shared/utils/file-manager.js
15
- *
16
- * From dist/utils/, need to go UP one level (../) to reach lib/
17
- * Source path must account for compilation depth adjustment
6
+ * IMPORTANT PATH CALCULATION:
7
+ * - Source: src/utils/file-manager.js
8
+ * - Compiled: dist/utils/file-manager.js
9
+ * - Target: dist/lib/shared/utils/file-manager.js
10
+ * - From dist/utils/, path to dist/lib/ is ../lib/
18
11
  */
19
12
 
20
13
  export { FileManager } from '../lib/shared/utils/file-manager.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamyla/clodo-framework",
3
- "version": "4.0.10",
3
+ "version": "4.0.12",
4
4
  "description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
5
5
  "type": "module",
6
6
  "sideEffects": [