@tamyla/clodo-framework 4.0.11 → 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 +44 -1
- package/dist/cli/commands/assess.js +2 -1
- package/dist/cli/commands/create.js +2 -1
- package/dist/cli/commands/deploy.js +2 -1
- package/dist/cli/commands/diagnose.js +2 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/commands/validate.js +2 -1
- package/dist/lib/shared/utils/configuration-validator.js +250 -0
- package/dist/lib/shared/utils/wrangler-compatibility.js +191 -0
- package/dist/lib/shared/validation/config-validator.js +383 -0
- package/dist/service-management/generators/config/WranglerTomlGenerator.js +33 -3
- package/dist/service-management/handlers/ValidationHandler.js +15 -0
- package/dist/utils/file-manager.js +5 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,51 @@
|
|
|
1
|
-
## [4.0.
|
|
1
|
+
## [4.0.12](https://github.com/tamylaa/clodo-framework/compare/v4.0.11...v4.0.12) (2025-12-12)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
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))
|
|
6
49
|
* correct NPM_TOKEN environment variable name in GitHub Actions workflow ([938c953](https://github.com/tamylaa/clodo-framework/commit/938c953d7e2e9f1a679ee566f1a4c7a070e629e6))
|
|
7
50
|
* update .gitignore to prevent test artifacts from being tracked ([83457d5](https://github.com/tamylaa/clodo-framework/commit/83457d58b1e8d44865a593b2b75e39f4509a75bf))
|
|
8
51
|
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import path from 'path';
|
|
8
|
-
import { ServiceOrchestrator,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
7
|
-
* - Source: src/utils/file-manager.js
|
|
8
|
-
* - Compiled: dist/utils/file-manager.js
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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';
|