@tamyla/clodo-framework 2.0.0 ā 2.0.2
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 +14 -0
- package/bin/clodo-service.js +2 -2
- package/bin/security/security-cli.js +1 -1
- package/bin/service-management/create-service.js +1 -1
- package/bin/service-management/init-service.js +1 -1
- package/bin/shared/config/cache.js +1230 -0
- package/bin/shared/config/command-config-manager.js +184 -0
- package/bin/shared/config/customer-cli.js +1 -1
- package/bin/shared/config/index.js +9 -0
- package/bin/shared/config/manager.js +315 -0
- package/dist/deployment/validator.js +112 -0
- package/dist/deployment/wrangler-deployer.js +47 -2
- package/dist/service-management/InputCollector.js +208 -20
- package/dist/shared/config/customer-cli.js +1 -1
- package/dist/shared/deployment/validator.js +112 -0
- package/dist/shared/monitoring/health-checker.js +200 -0
- package/dist/shared/utils/interactive-utils.js +506 -0
- package/dist/utils/ErrorHandler.js +113 -0
- package/dist/utils/scripts-manager.js +167 -0
- package/dist/utils/ui-structures-loader.js +110 -0
- package/docs/README.md +20 -67
- package/docs/api-reference.md +928 -0
- package/package.json +7 -7
- package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +0 -206
- package/docs/INTEGRATION_GUIDE.md +0 -2045
- package/docs/deployment/deployment-guide.md +0 -540
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Configuration Manager
|
|
3
|
+
* Loads and manages configurable system commands from validation-config.json
|
|
4
|
+
*
|
|
5
|
+
* Ensures all commands are configurable and platform-specific
|
|
6
|
+
* No hardcoded commands in the codebase
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
|
|
12
|
+
export class CommandConfigManager {
|
|
13
|
+
constructor(configPath = null) {
|
|
14
|
+
this.configPath = configPath || join(process.cwd(), 'validation-config.json');
|
|
15
|
+
this.config = null;
|
|
16
|
+
this.loadConfig();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load configuration from JSON file
|
|
21
|
+
*/
|
|
22
|
+
loadConfig() {
|
|
23
|
+
try {
|
|
24
|
+
const configData = readFileSync(this.configPath, 'utf-8');
|
|
25
|
+
this.config = JSON.parse(configData);
|
|
26
|
+
console.log('š Loaded command configuration from validation-config.json');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.log('ā ļø Could not load command config, using defaults');
|
|
29
|
+
this.config = this.getDefaultConfig();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get default configuration fallback
|
|
35
|
+
*/
|
|
36
|
+
getDefaultConfig() {
|
|
37
|
+
return {
|
|
38
|
+
requiredCommands: {
|
|
39
|
+
npx: 'npx',
|
|
40
|
+
node: 'node',
|
|
41
|
+
npm: 'npm',
|
|
42
|
+
wrangler: 'npx wrangler'
|
|
43
|
+
},
|
|
44
|
+
cloudflareCommands: {
|
|
45
|
+
whoami: 'npx wrangler whoami',
|
|
46
|
+
auth_login: 'npx wrangler auth login',
|
|
47
|
+
deployments_list: 'npx wrangler deployments list',
|
|
48
|
+
list_workers: 'npx wrangler dev --help',
|
|
49
|
+
deploy: 'npx wrangler deploy',
|
|
50
|
+
list_zones: 'npx wrangler zone list',
|
|
51
|
+
worker_status: 'npx wrangler status'
|
|
52
|
+
},
|
|
53
|
+
systemCommands: {
|
|
54
|
+
powershell_web_request: 'powershell -Command "try { Invoke-WebRequest -Uri \'{url}\' -TimeoutSec 10 -UseBasicParsing | Out-Null } catch { exit 1 }"',
|
|
55
|
+
curl_test: 'curl -s --connect-timeout 10 {url} -o /dev/null'
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get a Cloudflare command
|
|
62
|
+
*/
|
|
63
|
+
getCloudflareCommand(commandName, params = {}) {
|
|
64
|
+
const command = this.config?.cloudflareCommands?.[commandName];
|
|
65
|
+
if (!command) {
|
|
66
|
+
throw new Error(`Cloudflare command '${commandName}' not found in configuration`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return this.interpolateParams(command, params);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get a system command
|
|
74
|
+
*/
|
|
75
|
+
getSystemCommand(commandName, params = {}) {
|
|
76
|
+
const command = this.config?.systemCommands?.[commandName];
|
|
77
|
+
if (!command) {
|
|
78
|
+
throw new Error(`System command '${commandName}' not found in configuration`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this.interpolateParams(command, params);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get a required command
|
|
86
|
+
*/
|
|
87
|
+
getRequiredCommand(commandName) {
|
|
88
|
+
const command = this.config?.requiredCommands?.[commandName];
|
|
89
|
+
if (!command) {
|
|
90
|
+
throw new Error(`Required command '${commandName}' not found in configuration`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return command;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get network test command for current platform
|
|
98
|
+
*/
|
|
99
|
+
getNetworkTestCommand(url) {
|
|
100
|
+
const isWindows = process.platform === 'win32';
|
|
101
|
+
const commandName = isWindows ? 'powershell_web_request' : 'curl_test';
|
|
102
|
+
|
|
103
|
+
return this.getSystemCommand(commandName, { url });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Interpolate parameters in command strings
|
|
108
|
+
*/
|
|
109
|
+
interpolateParams(command, params) {
|
|
110
|
+
let result = command;
|
|
111
|
+
|
|
112
|
+
for (const [key, value] of Object.entries(params)) {
|
|
113
|
+
const placeholder = `{${key}}`;
|
|
114
|
+
result = result.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'), value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get all available commands for debugging
|
|
122
|
+
*/
|
|
123
|
+
getAllCommands() {
|
|
124
|
+
return {
|
|
125
|
+
requiredCommands: this.config?.requiredCommands || {},
|
|
126
|
+
cloudflareCommands: this.config?.cloudflareCommands || {},
|
|
127
|
+
systemCommands: this.config?.systemCommands || {}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Validate command configuration
|
|
133
|
+
*/
|
|
134
|
+
validateConfig() {
|
|
135
|
+
const errors = [];
|
|
136
|
+
|
|
137
|
+
// Check required sections
|
|
138
|
+
const requiredSections = ['requiredCommands', 'cloudflareCommands', 'systemCommands'];
|
|
139
|
+
|
|
140
|
+
for (const section of requiredSections) {
|
|
141
|
+
if (!this.config[section] || typeof this.config[section] !== 'object') {
|
|
142
|
+
errors.push(`Missing or invalid section: ${section}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check essential Cloudflare commands
|
|
147
|
+
const essentialCfCommands = ['whoami', 'auth_login', 'deployments_list'];
|
|
148
|
+
|
|
149
|
+
for (const cmd of essentialCfCommands) {
|
|
150
|
+
if (!this.config?.cloudflareCommands?.[cmd]) {
|
|
151
|
+
errors.push(`Missing essential Cloudflare command: ${cmd}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (errors.length > 0) {
|
|
156
|
+
throw new Error(`Command configuration validation failed:\\n${errors.join('\\n')}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Reload configuration
|
|
164
|
+
*/
|
|
165
|
+
reload() {
|
|
166
|
+
this.loadConfig();
|
|
167
|
+
this.validateConfig();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Singleton instance
|
|
172
|
+
let commandConfigInstance = null;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get singleton instance of command config manager
|
|
176
|
+
*/
|
|
177
|
+
export function getCommandConfig(configPath = null) {
|
|
178
|
+
if (!commandConfigInstance || configPath) {
|
|
179
|
+
commandConfigInstance = new CommandConfigManager(configPath);
|
|
180
|
+
}
|
|
181
|
+
return commandConfigInstance;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export default CommandConfigManager;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Integrates with Clodo Framework domain and feature flag systems
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { CustomerConfigCLI } from '../../../
|
|
9
|
+
import { CustomerConfigCLI } from '../../../dist/config/CustomerConfigCLI.js';
|
|
10
10
|
|
|
11
11
|
const command = process.argv[2];
|
|
12
12
|
const args = process.argv.slice(3);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Module
|
|
3
|
+
* Exports all configuration management utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { ConfigCache } from './cache.js';
|
|
7
|
+
export { ConfigManager } from './manager.js';
|
|
8
|
+
export { CommandConfigManager } from './command-config-manager.js';
|
|
9
|
+
export { CustomerConfigurationManager } from '../../../src/config/customers.js';
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager Module
|
|
3
|
+
* Configuration file management and validation
|
|
4
|
+
*
|
|
5
|
+
* Consolidates config management across 34+ scripts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, copyFileSync, readdirSync, statSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
export function updateWranglerConfig(updates, configPath = 'wrangler.toml') {
|
|
12
|
+
if (!existsSync(configPath)) {
|
|
13
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let config = readFileSync(configPath, 'utf8');
|
|
17
|
+
let changesMade = [];
|
|
18
|
+
|
|
19
|
+
// Apply updates systematically
|
|
20
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
21
|
+
switch (key) {
|
|
22
|
+
case 'workerName':
|
|
23
|
+
// Update main worker name
|
|
24
|
+
if (config.match(/^name = "[^"]*"/m)) {
|
|
25
|
+
config = config.replace(/^name = "[^"]*"/m, `name = "${value}"`);
|
|
26
|
+
changesMade.push(`Updated main worker name to: ${value}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Update production environment name
|
|
30
|
+
if (config.match(/^\[env\.production\]\s*\nname = "[^"]*"/m)) {
|
|
31
|
+
config = config.replace(/^\[env\.production\]\s*\nname = "[^"]*"/m, `[env.production]\nname = "${value}"`);
|
|
32
|
+
changesMade.push(`Updated production worker name to: ${value}`);
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case 'databaseName': {
|
|
37
|
+
const dbNameRegex = /database_name = "[^"]*"/g;
|
|
38
|
+
if (config.match(dbNameRegex)) {
|
|
39
|
+
config = config.replace(dbNameRegex, `database_name = "${value}"`);
|
|
40
|
+
changesMade.push(`Updated database name to: ${value}`);
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case 'databaseId': {
|
|
46
|
+
const dbIdRegex = /database_id = "[^"]*"/g;
|
|
47
|
+
if (config.match(dbIdRegex)) {
|
|
48
|
+
config = config.replace(dbIdRegex, `database_id = "${value}"`);
|
|
49
|
+
changesMade.push(`Updated database ID to: ${value}`);
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case 'serviceDomain': {
|
|
55
|
+
const domainRegex = /SERVICE_DOMAIN = "[^"]*"/g;
|
|
56
|
+
if (config.match(domainRegex)) {
|
|
57
|
+
config = config.replace(domainRegex, `SERVICE_DOMAIN = "${value}"`);
|
|
58
|
+
changesMade.push(`Updated service domain to: ${value}`);
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'compatibilityDate': {
|
|
64
|
+
const dateRegex = /^compatibility_date = "[^"]*"/m;
|
|
65
|
+
if (config.match(dateRegex)) {
|
|
66
|
+
config = config.replace(dateRegex, `compatibility_date = "${value}"`);
|
|
67
|
+
changesMade.push(`Updated compatibility date to: ${value}`);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
default:
|
|
73
|
+
console.warn(`Unknown config update key: ${key}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
writeFileSync(configPath, config);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
configPath,
|
|
81
|
+
changesMade,
|
|
82
|
+
success: changesMade.length > 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function backupConfig(configPath = 'wrangler.toml', suffix = null) {
|
|
87
|
+
if (!existsSync(configPath)) {
|
|
88
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const timestamp = suffix || new Date().toISOString().replace(/[:.]/g, '-');
|
|
92
|
+
const backupPath = `${configPath}.backup.${timestamp}`;
|
|
93
|
+
|
|
94
|
+
copyFileSync(configPath, backupPath);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
originalPath: configPath,
|
|
98
|
+
backupPath,
|
|
99
|
+
timestamp: new Date().toISOString()
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function validateConfig(configPath = 'wrangler.toml') {
|
|
104
|
+
if (!existsSync(configPath)) {
|
|
105
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const config = readFileSync(configPath, 'utf8');
|
|
109
|
+
const issues = [];
|
|
110
|
+
const warnings = [];
|
|
111
|
+
|
|
112
|
+
// Required fields validation
|
|
113
|
+
const requiredFields = [
|
|
114
|
+
{ pattern: /^name = "/m, description: 'Worker name' },
|
|
115
|
+
{ pattern: /^compatibility_date = "/m, description: 'Compatibility date' },
|
|
116
|
+
{ pattern: /^main = "/m, description: 'Main entry point' }
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
requiredFields.forEach(field => {
|
|
120
|
+
if (!config.match(field.pattern)) {
|
|
121
|
+
issues.push(`Missing required field: ${field.description}`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Database configuration validation
|
|
126
|
+
const hasDatabaseBinding = config.includes('[[d1_databases]]');
|
|
127
|
+
if (hasDatabaseBinding) {
|
|
128
|
+
if (!config.includes('database_name =')) {
|
|
129
|
+
issues.push('Database binding found but missing database_name');
|
|
130
|
+
}
|
|
131
|
+
if (!config.includes('database_id =')) {
|
|
132
|
+
issues.push('Database binding found but missing database_id');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Environment validation
|
|
137
|
+
const hasProductionEnv = config.includes('[env.production]');
|
|
138
|
+
if (hasProductionEnv) {
|
|
139
|
+
const prodSection = config.split('[env.production]')[1];
|
|
140
|
+
if (prodSection && !prodSection.includes('name =')) {
|
|
141
|
+
warnings.push('Production environment missing explicit name');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Compatibility date validation
|
|
146
|
+
const compatMatch = config.match(/compatibility_date = "([^"]+)"/);
|
|
147
|
+
if (compatMatch) {
|
|
148
|
+
const date = new Date(compatMatch[1]);
|
|
149
|
+
const now = new Date();
|
|
150
|
+
const daysDiff = (now - date) / (1000 * 60 * 60 * 24);
|
|
151
|
+
|
|
152
|
+
if (daysDiff > 365) {
|
|
153
|
+
warnings.push(`Compatibility date is ${Math.floor(daysDiff)} days old`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
isValid: issues.length === 0,
|
|
159
|
+
issues,
|
|
160
|
+
warnings,
|
|
161
|
+
summary: {
|
|
162
|
+
errors: issues.length,
|
|
163
|
+
warnings: warnings.length,
|
|
164
|
+
status: issues.length === 0 ? 'valid' : 'invalid'
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function parseWranglerConfig(configPath = 'wrangler.toml') {
|
|
170
|
+
if (!existsSync(configPath)) {
|
|
171
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const config = readFileSync(configPath, 'utf8');
|
|
175
|
+
const parsed = {};
|
|
176
|
+
|
|
177
|
+
// Extract main fields
|
|
178
|
+
const extractField = (pattern, key) => {
|
|
179
|
+
const match = config.match(pattern);
|
|
180
|
+
if (match) parsed[key] = match[1];
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
extractField(/^name = "([^"]+)"/m, 'name');
|
|
184
|
+
extractField(/^compatibility_date = "([^"]+)"/m, 'compatibilityDate');
|
|
185
|
+
extractField(/^main = "([^"]+)"/m, 'main');
|
|
186
|
+
|
|
187
|
+
// Extract database configuration
|
|
188
|
+
if (config.includes('[[d1_databases]]')) {
|
|
189
|
+
parsed.database = {};
|
|
190
|
+
extractField(/database_name = "([^"]+)"/m, 'name');
|
|
191
|
+
extractField(/database_id = "([^"]+)"/m, 'id');
|
|
192
|
+
|
|
193
|
+
if (parsed.name) parsed.database.name = parsed.name;
|
|
194
|
+
if (parsed.id) parsed.database.id = parsed.id;
|
|
195
|
+
delete parsed.name;
|
|
196
|
+
delete parsed.id;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Extract environment configurations
|
|
200
|
+
parsed.environments = {};
|
|
201
|
+
const envMatches = config.matchAll(/\[env\.([^\]]+)\]/g);
|
|
202
|
+
for (const match of envMatches) {
|
|
203
|
+
const envName = match[1];
|
|
204
|
+
parsed.environments[envName] = {};
|
|
205
|
+
|
|
206
|
+
// Extract env-specific settings (simplified parsing)
|
|
207
|
+
const envSection = config.split(`[env.${envName}]`)[1];
|
|
208
|
+
if (envSection) {
|
|
209
|
+
const nextEnv = envSection.indexOf('[env.');
|
|
210
|
+
const sectionContent = nextEnv > 0 ? envSection.substring(0, nextEnv) : envSection;
|
|
211
|
+
|
|
212
|
+
const nameMatch = sectionContent.match(/name = "([^"]+)"/);
|
|
213
|
+
if (nameMatch) parsed.environments[envName].name = nameMatch[1];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return parsed;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function generateWranglerConfig(options) {
|
|
221
|
+
const {
|
|
222
|
+
name,
|
|
223
|
+
main = 'src/worker/index.js',
|
|
224
|
+
compatibilityDate = new Date().toISOString().split('T')[0],
|
|
225
|
+
database = null,
|
|
226
|
+
environments = {},
|
|
227
|
+
vars = {},
|
|
228
|
+
secrets = []
|
|
229
|
+
} = options;
|
|
230
|
+
|
|
231
|
+
let config = `# Generated Wrangler Configuration
|
|
232
|
+
# Timestamp: ${new Date().toISOString()}
|
|
233
|
+
|
|
234
|
+
name = "${name}"
|
|
235
|
+
main = "${main}"
|
|
236
|
+
compatibility_date = "${compatibilityDate}"
|
|
237
|
+
`;
|
|
238
|
+
|
|
239
|
+
// Add variables
|
|
240
|
+
if (Object.keys(vars).length > 0) {
|
|
241
|
+
config += '\n[vars]\n';
|
|
242
|
+
Object.entries(vars).forEach(([key, value]) => {
|
|
243
|
+
config += `${key} = "${value}"\n`;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add database configuration
|
|
248
|
+
if (database) {
|
|
249
|
+
config += `
|
|
250
|
+
[[d1_databases]]
|
|
251
|
+
binding = "DB"
|
|
252
|
+
database_name = "${database.name}"
|
|
253
|
+
database_id = "${database.id}"
|
|
254
|
+
`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Add environments
|
|
258
|
+
Object.entries(environments).forEach(([envName, envConfig]) => {
|
|
259
|
+
config += `\n[env.${envName}]\n`;
|
|
260
|
+
if (envConfig.name) config += `name = "${envConfig.name}"\n`;
|
|
261
|
+
|
|
262
|
+
if (envConfig.vars) {
|
|
263
|
+
Object.entries(envConfig.vars).forEach(([key, value]) => {
|
|
264
|
+
config += `${key} = "${value}"\n`;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (envConfig.database) {
|
|
269
|
+
config += `
|
|
270
|
+
[[env.${envName}.d1_databases]]
|
|
271
|
+
binding = "DB"
|
|
272
|
+
database_name = "${envConfig.database.name}"
|
|
273
|
+
database_id = "${envConfig.database.id}"
|
|
274
|
+
`;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return config;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function listConfigFiles(directory = '.') {
|
|
282
|
+
const configFiles = [];
|
|
283
|
+
const patterns = ['wrangler.toml', 'package.json', '.env*'];
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const items = readdirSync(directory);
|
|
287
|
+
|
|
288
|
+
items.forEach(item => {
|
|
289
|
+
const path = join(directory, item);
|
|
290
|
+
const stat = statSync(path);
|
|
291
|
+
|
|
292
|
+
if (stat.isFile()) {
|
|
293
|
+
const matches = patterns.some(pattern => {
|
|
294
|
+
if (pattern.includes('*')) {
|
|
295
|
+
return item.startsWith(pattern.replace('*', ''));
|
|
296
|
+
}
|
|
297
|
+
return item === pattern;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (matches) {
|
|
301
|
+
configFiles.push({
|
|
302
|
+
name: item,
|
|
303
|
+
path,
|
|
304
|
+
size: stat.size,
|
|
305
|
+
modified: stat.mtime
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return configFiles.sort((a, b) => a.name.localeCompare(b.name));
|
|
312
|
+
} catch (error) {
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -270,6 +270,9 @@ export class DeploymentValidator {
|
|
|
270
270
|
|
|
271
271
|
// Validate environment configuration
|
|
272
272
|
await this.validateEnvironmentConfig();
|
|
273
|
+
|
|
274
|
+
// Validate D1 database bindings
|
|
275
|
+
await this.validateD1Configuration();
|
|
273
276
|
this.results.categories.configuration = 'passed';
|
|
274
277
|
this.addResult('configuration', 'Configuration files validated', 'success');
|
|
275
278
|
} catch (error) {
|
|
@@ -457,6 +460,112 @@ export class DeploymentValidator {
|
|
|
457
460
|
console.log(` ā
Environment: ${this.environment} configuration valid`);
|
|
458
461
|
this.addResult('environment', `${this.environment} environment validated`, 'info');
|
|
459
462
|
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Validate D1 database configuration across all services
|
|
466
|
+
*/
|
|
467
|
+
async validateD1Configuration() {
|
|
468
|
+
console.log(' šļø Validating D1 database configuration...');
|
|
469
|
+
try {
|
|
470
|
+
// Import WranglerDeployer for D1 validation capabilities
|
|
471
|
+
const {
|
|
472
|
+
WranglerDeployer
|
|
473
|
+
} = await import('../../../src/deployment/wrangler-deployer.js');
|
|
474
|
+
|
|
475
|
+
// Check if this is a framework-level validation (no specific service)
|
|
476
|
+
if (!this.options?.servicePath) {
|
|
477
|
+
console.log(' āļø Skipping D1 validation (framework-level validation)');
|
|
478
|
+
this.addResult('d1-config', 'D1 validation skipped for framework-level validation', 'info');
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Create deployer instance for the specific service
|
|
483
|
+
const deployer = new WranglerDeployer({
|
|
484
|
+
cwd: this.options.servicePath,
|
|
485
|
+
environment: this.environment
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Discover deployment configuration
|
|
489
|
+
const deployConfig = await deployer.discoverDeploymentConfig(this.environment);
|
|
490
|
+
if (!deployConfig.configPath) {
|
|
491
|
+
console.log(' āļø No wrangler.toml found, skipping D1 validation');
|
|
492
|
+
this.addResult('d1-config', 'No wrangler.toml found', 'info');
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Validate D1 bindings
|
|
497
|
+
const d1Validation = await deployer.validateD1Bindings(deployConfig);
|
|
498
|
+
if (d1Validation.valid) {
|
|
499
|
+
if (d1Validation.bindings.length === 0) {
|
|
500
|
+
console.log(' ā
No D1 databases configured');
|
|
501
|
+
this.addResult('d1-config', 'No D1 databases configured', 'info');
|
|
502
|
+
} else {
|
|
503
|
+
console.log(` ā
All ${d1Validation.summary.total} D1 database bindings valid`);
|
|
504
|
+
this.addResult('d1-config', `${d1Validation.summary.total} D1 bindings validated`, 'success');
|
|
505
|
+
|
|
506
|
+
// Log details about each binding
|
|
507
|
+
d1Validation.bindings.forEach(binding => {
|
|
508
|
+
if (binding.valid) {
|
|
509
|
+
console.log(` ā
${binding.binding}: ${binding.database_name}`);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
console.log(` ā D1 validation failed: ${d1Validation.summary.invalid} invalid bindings`);
|
|
515
|
+
|
|
516
|
+
// Log details about invalid bindings
|
|
517
|
+
d1Validation.invalidBindings.forEach(binding => {
|
|
518
|
+
console.log(` ā ${binding.binding || 'unnamed'}: ${binding.issues.join(', ')}`);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Add warning with suggestions
|
|
522
|
+
const suggestions = this.generateD1Suggestions(d1Validation);
|
|
523
|
+
this.addWarning('d1-config', `D1 configuration issues found. ${suggestions}`);
|
|
524
|
+
|
|
525
|
+
// Don't fail validation entirely for D1 issues if not in strict mode
|
|
526
|
+
if (!this.strictMode) {
|
|
527
|
+
console.log(' ā ļø Continuing with warnings (non-strict mode)');
|
|
528
|
+
this.addResult('d1-config', 'D1 issues found but continuing', 'warning');
|
|
529
|
+
} else {
|
|
530
|
+
throw new Error(`D1 validation failed: ${d1Validation.summary.invalid} invalid bindings`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (error.message.includes('D1 validation failed')) {
|
|
535
|
+
throw error; // Re-throw D1 validation errors
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Handle other errors (import errors, etc.)
|
|
539
|
+
console.log(` ā ļø D1 validation error: ${error.message}`);
|
|
540
|
+
this.addWarning('d1-config', `D1 validation error: ${error.message}`);
|
|
541
|
+
if (this.strictMode) {
|
|
542
|
+
throw new Error(`D1 validation failed: ${error.message}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Generate helpful suggestions for D1 configuration issues
|
|
549
|
+
* @param {Object} d1Validation - D1 validation results
|
|
550
|
+
* @returns {string} Suggestions text
|
|
551
|
+
*/
|
|
552
|
+
generateD1Suggestions(d1Validation) {
|
|
553
|
+
const suggestions = [];
|
|
554
|
+
d1Validation.invalidBindings.forEach(binding => {
|
|
555
|
+
binding.issues.forEach(issue => {
|
|
556
|
+
if (issue.includes('not found')) {
|
|
557
|
+
suggestions.push('Run "wrangler d1 list" to see available databases');
|
|
558
|
+
suggestions.push('Create missing database with "wrangler d1 create <name>"');
|
|
559
|
+
} else if (issue.includes('Missing')) {
|
|
560
|
+
suggestions.push('Check wrangler.toml [[d1_databases]] section completeness');
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Remove duplicates
|
|
566
|
+
const uniqueSuggestions = [...new Set(suggestions)];
|
|
567
|
+
return uniqueSuggestions.length > 0 ? `Suggestions: ${uniqueSuggestions.slice(0, 2).join('; ')}` : 'Check D1 database configuration in wrangler.toml';
|
|
568
|
+
}
|
|
460
569
|
async validateDomainEndpoints(domain) {
|
|
461
570
|
console.log(` Validating endpoints for ${domain}...`);
|
|
462
571
|
console.log(` š§ DEBUG: skipEndpointCheck = ${this.options?.skipEndpointCheck}`);
|
|
@@ -538,6 +647,9 @@ export class DeploymentValidator {
|
|
|
538
647
|
this.results.errors.push(message);
|
|
539
648
|
}
|
|
540
649
|
}
|
|
650
|
+
addWarning(category, message) {
|
|
651
|
+
this.addResult(category, message, 'warning');
|
|
652
|
+
}
|
|
541
653
|
printValidationSummary() {
|
|
542
654
|
console.log('\nš VALIDATION SUMMARY');
|
|
543
655
|
console.log('====================');
|