@tamyla/clodo-framework 2.0.1 → 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.
@@ -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 '../../../src/config/CustomerConfigCLI.js';
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
+ }
@@ -5,7 +5,7 @@
5
5
  * Manages multi-environment, multi-customer configuration structure
6
6
  * Integrates with Clodo Framework domain and feature flag systems
7
7
  */
8
- import { CustomerConfigCLI } from '../../../src/config/CustomerConfigCLI.js';
8
+ import { CustomerConfigCLI } from '../../../dist/config/CustomerConfigCLI.js';
9
9
  const command = process.argv[2];
10
10
  const args = process.argv.slice(3);
11
11
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamyla/clodo-framework",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -53,8 +53,10 @@
53
53
  "files": [
54
54
  "dist",
55
55
  "types",
56
+ "bin/clodo-service.js",
56
57
  "bin/service-management",
57
58
  "bin/security",
59
+ "bin/shared/config",
58
60
  "docs/README.md",
59
61
  "docs/overview.md",
60
62
  "docs/SECURITY.md",
@@ -108,7 +110,9 @@
108
110
  "author": "Tamyla",
109
111
  "license": "GPL-3.0-or-later",
110
112
  "dependencies": {
111
- "wrangler": ">=3.0.0"
113
+ "wrangler": ">=3.0.0",
114
+ "chalk": "^5.3.0",
115
+ "commander": "^11.0.0"
112
116
  },
113
117
  "devDependencies": {
114
118
  "@babel/cli": "^7.23.0",
@@ -119,8 +123,6 @@
119
123
  "@semantic-release/git": "^10.0.1",
120
124
  "@types/node": "^20.19.19",
121
125
  "babel-plugin-transform-import-meta": "^2.3.3",
122
- "chalk": "^5.3.0",
123
- "commander": "^11.0.0",
124
126
  "eslint": "^8.54.0",
125
127
  "jest": "^29.7.0",
126
128
  "rimraf": "^5.0.10",