@tamyla/clodo-framework 3.0.15 → 3.1.0

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +215 -163
  2. package/README.md +133 -1
  3. package/bin/clodo-service.js +0 -0
  4. package/bin/security/security-cli.js +0 -0
  5. package/bin/service-management/create-service.js +0 -0
  6. package/bin/service-management/init-service.js +2 -1
  7. package/dist/service-management/GenerationEngine.js +298 -3025
  8. package/dist/service-management/ServiceCreator.js +19 -3
  9. package/dist/service-management/generators/BaseGenerator.js +233 -0
  10. package/dist/service-management/generators/GeneratorRegistry.js +254 -0
  11. package/dist/service-management/generators/cicd/CiWorkflowGenerator.js +87 -0
  12. package/dist/service-management/generators/cicd/DeployWorkflowGenerator.js +106 -0
  13. package/dist/service-management/generators/code/ServiceHandlersGenerator.js +235 -0
  14. package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +116 -0
  15. package/dist/service-management/generators/code/ServiceUtilsGenerator.js +246 -0
  16. package/dist/service-management/generators/code/WorkerIndexGenerator.js +143 -0
  17. package/dist/service-management/generators/config/DevelopmentEnvGenerator.js +101 -0
  18. package/dist/service-management/generators/config/DomainsConfigGenerator.js +175 -0
  19. package/dist/service-management/generators/config/EnvExampleGenerator.js +178 -0
  20. package/dist/service-management/generators/config/ProductionEnvGenerator.js +97 -0
  21. package/dist/service-management/generators/config/StagingEnvGenerator.js +97 -0
  22. package/dist/service-management/generators/config/WranglerTomlGenerator.js +238 -0
  23. package/dist/service-management/generators/core/PackageJsonGenerator.js +243 -0
  24. package/dist/service-management/generators/core/SiteConfigGenerator.js +115 -0
  25. package/dist/service-management/generators/documentation/ApiDocsGenerator.js +331 -0
  26. package/dist/service-management/generators/documentation/ConfigurationDocsGenerator.js +294 -0
  27. package/dist/service-management/generators/documentation/DeploymentDocsGenerator.js +244 -0
  28. package/dist/service-management/generators/documentation/ReadmeGenerator.js +196 -0
  29. package/dist/service-management/generators/schemas/ServiceSchemaGenerator.js +190 -0
  30. package/dist/service-management/generators/scripts/DeployScriptGenerator.js +123 -0
  31. package/dist/service-management/generators/scripts/HealthCheckScriptGenerator.js +101 -0
  32. package/dist/service-management/generators/scripts/SetupScriptGenerator.js +88 -0
  33. package/dist/service-management/generators/service-types/StaticSiteGenerator.js +342 -0
  34. package/dist/service-management/generators/testing/EslintConfigGenerator.js +85 -0
  35. package/dist/service-management/generators/testing/IntegrationTestsGenerator.js +237 -0
  36. package/dist/service-management/generators/testing/JestConfigGenerator.js +72 -0
  37. package/dist/service-management/generators/testing/UnitTestsGenerator.js +277 -0
  38. package/dist/service-management/generators/tooling/DockerComposeGenerator.js +71 -0
  39. package/dist/service-management/generators/tooling/GitignoreGenerator.js +143 -0
  40. package/dist/service-management/generators/utils/FileWriter.js +179 -0
  41. package/dist/service-management/generators/utils/PathResolver.js +157 -0
  42. package/dist/service-management/generators/utils/ServiceManifestGenerator.js +111 -0
  43. package/dist/service-management/generators/utils/TemplateEngine.js +185 -0
  44. package/dist/service-management/generators/utils/index.js +18 -0
  45. package/dist/service-management/routing/DomainRouteMapper.js +311 -0
  46. package/dist/service-management/routing/RouteGenerator.js +266 -0
  47. package/dist/service-management/routing/WranglerRoutesBuilder.js +273 -0
  48. package/dist/service-management/routing/index.js +14 -0
  49. package/dist/service-management/services/DirectoryStructureService.js +56 -0
  50. package/dist/service-management/services/GenerationCoordinator.js +208 -0
  51. package/dist/service-management/services/GeneratorRegistry.js +174 -0
  52. package/dist/services/GenericDataService.js +14 -1
  53. package/dist/utils/config/unified-config-manager.js +128 -12
  54. package/dist/utils/framework-config.js +74 -2
  55. package/dist/worker/integration.js +4 -1
  56. package/package.json +6 -1
  57. package/templates/generic/clodo-service-manifest.json +25 -0
  58. package/templates/static-site/.env.example +61 -0
  59. package/templates/static-site/README.md +176 -0
  60. package/templates/static-site/clodo-service-manifest.json +66 -0
  61. package/templates/static-site/package.json +28 -0
  62. package/templates/static-site/public/404.html +87 -0
  63. package/templates/static-site/public/app.js +100 -0
  64. package/templates/static-site/public/index.html +48 -0
  65. package/templates/static-site/public/styles.css +123 -0
  66. package/templates/static-site/scripts/deploy.ps1 +121 -0
  67. package/templates/static-site/scripts/setup.ps1 +179 -0
  68. package/templates/static-site/src/config/domains.js +35 -0
  69. package/templates/static-site/src/worker/index.js +153 -0
  70. package/templates/static-site/wrangler.toml +43 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * RouteGenerator - Main orchestrator for route generation
3
+ *
4
+ * Generates Cloudflare Workers [[routes]] configuration from domain metadata.
5
+ * This is a CORE IDENTITY feature that completes multi-domain orchestration.
6
+ *
7
+ * @module service-management/routing/RouteGenerator
8
+ */
9
+
10
+ import { DomainRouteMapper } from './DomainRouteMapper.js';
11
+ import { WranglerRoutesBuilder } from './WranglerRoutesBuilder.js';
12
+ import { frameworkConfig } from '../../utils/framework-config.js';
13
+ export class RouteGenerator {
14
+ constructor(options = {}) {
15
+ this.mapper = new DomainRouteMapper(options);
16
+ this.builder = new WranglerRoutesBuilder(options);
17
+
18
+ // Load routing configuration from validation-config.json
19
+ const routingConfig = frameworkConfig.getRoutingConfig();
20
+ const templateDefaults = frameworkConfig.config.templates?.defaults || {};
21
+ this.options = {
22
+ // Routing defaults from config
23
+ includeComments: routingConfig.defaults.includeComments,
24
+ includeZoneId: routingConfig.defaults.includeZoneId,
25
+ environment: routingConfig.defaults.targetEnvironment,
26
+ // Map targetEnvironment -> environment
27
+ orderStrategy: routingConfig.defaults.orderStrategy,
28
+ // Domain configuration
29
+ workersDomain: templateDefaults.WORKERS_DEV_DOMAIN || 'workers.dev',
30
+ skipPatterns: routingConfig.domains.skipPatterns,
31
+ // Allow runtime overrides
32
+ ...options
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Generate routes configuration for wrangler.toml
38
+ *
39
+ * @param {Object} coreInputs - Core service inputs (6 values)
40
+ * @param {string} coreInputs.serviceName - Service name
41
+ * @param {string} coreInputs.serviceType - Service type
42
+ * @param {string} coreInputs.domainName - Domain name
43
+ * @param {string} coreInputs.cloudflareAccountId - Cloudflare account ID
44
+ * @param {string} coreInputs.cloudflareZoneId - Cloudflare zone ID (32 hex chars)
45
+ * @param {string} coreInputs.environment - Environment (production|staging|development)
46
+ *
47
+ * @param {Object} confirmedValues - Confirmed configuration values (15 values)
48
+ * @param {string} confirmedValues.productionUrl - Production URL (e.g., api.example.com)
49
+ * @param {string} confirmedValues.stagingUrl - Staging URL (e.g., staging-api.example.com)
50
+ * @param {string} confirmedValues.developmentUrl - Development URL (optional, defaults to workers.dev)
51
+ * @param {string} confirmedValues.apiBasePath - API base path (e.g., /api)
52
+ * @param {string} confirmedValues.workerName - Worker name
53
+ *
54
+ * @param {Object} options - Generation options
55
+ * @param {boolean} options.includeComments - Include explanatory comments (default: true)
56
+ * @param {boolean} options.includeZoneId - Include zone_id in routes (default: true)
57
+ * @param {string} options.environment - Target environment filter (default: 'all')
58
+ *
59
+ * @returns {string} TOML-formatted routes configuration
60
+ *
61
+ * @throws {Error} If domain configuration is invalid or incomplete
62
+ *
63
+ * @example
64
+ * const routeGenerator = new RouteGenerator();
65
+ * const routesConfig = routeGenerator.generateRoutes(
66
+ * {
67
+ * serviceName: 'my-api',
68
+ * domainName: 'example.com',
69
+ * cloudflareZoneId: 'abc123...',
70
+ * // ... other core inputs
71
+ * },
72
+ * {
73
+ * productionUrl: 'api.example.com',
74
+ * stagingUrl: 'staging-api.example.com',
75
+ * apiBasePath: '/api',
76
+ * // ... other confirmed values
77
+ * }
78
+ * );
79
+ */
80
+ generateRoutes(coreInputs, confirmedValues, options = {}) {
81
+ // Merge options
82
+ const opts = {
83
+ ...this.options,
84
+ ...options
85
+ };
86
+
87
+ // Validate inputs
88
+ this.validateDomainConfig(coreInputs, confirmedValues);
89
+
90
+ // Build domain configuration
91
+ const domainConfig = {
92
+ domains: {
93
+ production: confirmedValues.productionUrl,
94
+ staging: confirmedValues.stagingUrl,
95
+ development: confirmedValues.developmentUrl
96
+ },
97
+ zoneId: coreInputs.cloudflareZoneId,
98
+ apiBasePath: confirmedValues.apiBasePath,
99
+ serviceName: coreInputs.serviceName,
100
+ workerName: confirmedValues.workerName
101
+ };
102
+
103
+ // Generate routes for each environment
104
+ const productionRoutes = this._generateEnvironmentRoutes(domainConfig, 'production', opts);
105
+ const stagingRoutes = this._generateEnvironmentRoutes(domainConfig, 'staging', opts);
106
+ const developmentRoutes = this._generateEnvironmentRoutes(domainConfig, 'development', opts);
107
+
108
+ // Build complete routes section
109
+ let routesSection = '';
110
+
111
+ // Add header comment
112
+ if (opts.includeComments) {
113
+ routesSection += `# Routes configuration for ${domainConfig.serviceName}\n`;
114
+ routesSection += `# Generated by Clodo Framework RouteGenerator\n`;
115
+ routesSection += `# Generated: ${new Date().toISOString()}\n\n`;
116
+ }
117
+
118
+ // Add production routes (top-level)
119
+ if (productionRoutes && (opts.environment === 'all' || opts.environment === 'production')) {
120
+ routesSection += productionRoutes;
121
+ }
122
+
123
+ // Add staging routes
124
+ if (stagingRoutes && (opts.environment === 'all' || opts.environment === 'staging')) {
125
+ if (routesSection && !routesSection.endsWith('\n\n')) {
126
+ routesSection += '\n';
127
+ }
128
+ routesSection += stagingRoutes;
129
+ }
130
+
131
+ // Add development routes (usually none - workers.dev)
132
+ if (developmentRoutes && (opts.environment === 'all' || opts.environment === 'development')) {
133
+ if (routesSection && !routesSection.endsWith('\n\n')) {
134
+ routesSection += '\n';
135
+ }
136
+ routesSection += developmentRoutes;
137
+ }
138
+ return routesSection.trim() + '\n';
139
+ }
140
+
141
+ /**
142
+ * Generate routes for a specific environment
143
+ * @private
144
+ */
145
+ _generateEnvironmentRoutes(domainConfig, environment, options) {
146
+ const domainUrl = domainConfig.domains[environment];
147
+
148
+ // Skip if no domain configured or matches skip patterns
149
+ if (!domainUrl || this._shouldSkipDomain(domainUrl)) {
150
+ // workers.dev subdomain - no routes needed
151
+ if (environment === 'development' && options.includeComments) {
152
+ return this.builder.generateDevComment(domainConfig.workerName);
153
+ }
154
+ return '';
155
+ }
156
+
157
+ // Map domain to route patterns
158
+ const routeMapping = this.mapper.mapDomainToRoutes(domainConfig, environment);
159
+
160
+ // Build TOML routes section
161
+ return this.builder.buildRoutesSection(routeMapping.patterns, environment, {
162
+ zoneId: options.includeZoneId ? routeMapping.zoneId : null,
163
+ includeComments: options.includeComments,
164
+ domain: domainUrl,
165
+ serviceName: domainConfig.serviceName
166
+ });
167
+ }
168
+
169
+ /**
170
+ * Validate domain configuration completeness
171
+ *
172
+ * @param {Object} coreInputs - Core service inputs
173
+ * @param {Object} confirmedValues - Confirmed configuration values
174
+ * @throws {Error} If validation fails
175
+ */
176
+ validateDomainConfig(coreInputs, confirmedValues) {
177
+ // Check required coreInputs
178
+ if (!coreInputs) {
179
+ throw new Error('coreInputs is required for route generation');
180
+ }
181
+ if (!coreInputs.serviceName) {
182
+ throw new Error('coreInputs.serviceName is required for route generation');
183
+ }
184
+ if (!coreInputs.cloudflareZoneId) {
185
+ throw new Error('coreInputs.cloudflareZoneId is required for route generation');
186
+ }
187
+
188
+ // Validate zone_id format (32 hex characters)
189
+ const zoneIdPattern = /^[a-f0-9]{32}$/;
190
+ if (!zoneIdPattern.test(coreInputs.cloudflareZoneId)) {
191
+ throw new Error('coreInputs.cloudflareZoneId must be a valid Cloudflare zone ID (32 hex characters). ' + `Received: "${coreInputs.cloudflareZoneId}"`);
192
+ }
193
+
194
+ // Check required confirmedValues
195
+ if (!confirmedValues) {
196
+ throw new Error('confirmedValues is required for route generation');
197
+ }
198
+ if (!confirmedValues.productionUrl) {
199
+ throw new Error('confirmedValues.productionUrl is required for route generation');
200
+ }
201
+
202
+ // Validate production URL format
203
+ if (!this._isValidDomain(confirmedValues.productionUrl)) {
204
+ throw new Error('confirmedValues.productionUrl must be a valid domain name. ' + `Received: "${confirmedValues.productionUrl}"`);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Check if a string is a valid domain name
210
+ * @private
211
+ */
212
+ _isValidDomain(domain) {
213
+ if (!domain || typeof domain !== 'string') {
214
+ return false;
215
+ }
216
+
217
+ // Basic domain validation
218
+ const domainPattern = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
219
+ return domainPattern.test(domain);
220
+ }
221
+
222
+ /**
223
+ * Get route patterns for a specific domain and environment
224
+ *
225
+ * @param {string} domain - Domain name (e.g., api.example.com)
226
+ * @param {string} environment - Environment (production|staging|development)
227
+ * @param {Object} options - Additional options
228
+ * @returns {Array<string>} Array of route patterns
229
+ *
230
+ * @example
231
+ * const patterns = routeGenerator.getRoutePatterns('api.example.com', 'production');
232
+ * // Returns: ['api.example.com/*', 'example.com/api/*']
233
+ */
234
+ getRoutePatterns(domain, environment, options = {}) {
235
+ const domainConfig = {
236
+ domains: {
237
+ [environment]: domain
238
+ },
239
+ zoneId: options.zoneId || 'placeholder',
240
+ apiBasePath: options.apiBasePath,
241
+ serviceName: options.serviceName || 'service'
242
+ };
243
+ const routeMapping = this.mapper.mapDomainToRoutes(domainConfig, environment);
244
+ return routeMapping.patterns;
245
+ }
246
+
247
+ /**
248
+ * Check if a domain should be skipped for route generation
249
+ * @private
250
+ *
251
+ * @param {string} domain - Domain to check
252
+ * @returns {boolean} True if domain should be skipped
253
+ */
254
+ _shouldSkipDomain(domain) {
255
+ if (!domain) return true;
256
+
257
+ // Check workers.dev domain
258
+ if (domain.includes(this.options.workersDomain)) {
259
+ return true;
260
+ }
261
+
262
+ // Check configured skip patterns
263
+ const skipPatterns = this.options.skipPatterns || [];
264
+ return skipPatterns.some(pattern => domain.includes(pattern));
265
+ }
266
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * WranglerRoutesBuilder - Builds TOML [[routes]] sections
3
+ *
4
+ * Formats route patterns as valid TOML with proper syntax,
5
+ * comments, and environment-specific sections.
6
+ *
7
+ * @module service-management/routing/WranglerRoutesBuilder
8
+ */
9
+
10
+ export class WranglerRoutesBuilder {
11
+ constructor(options = {}) {
12
+ this.options = options;
13
+ }
14
+
15
+ /**
16
+ * Build TOML [[routes]] section
17
+ *
18
+ * @param {Array<string>} patterns - Array of route patterns
19
+ * @param {string} environment - Target environment (production|staging|development)
20
+ * @param {Object} options - Build options
21
+ * @param {string} options.zoneId - Cloudflare zone ID (optional)
22
+ * @param {boolean} options.includeComments - Include comments (default: true)
23
+ * @param {string} options.domain - Domain name (for comments)
24
+ * @param {string} options.serviceName - Service name (for comments)
25
+ *
26
+ * @returns {string} TOML-formatted routes section
27
+ *
28
+ * @example
29
+ * const builder = new WranglerRoutesBuilder();
30
+ * const toml = builder.buildRoutesSection(
31
+ * ['api.example.com/*', 'example.com/api/*'],
32
+ * 'production',
33
+ * { zoneId: 'xyz789...', includeComments: true, domain: 'api.example.com' }
34
+ * );
35
+ */
36
+ buildRoutesSection(patterns, environment, options = {}) {
37
+ if (!patterns || patterns.length === 0) {
38
+ return '';
39
+ }
40
+ const opts = {
41
+ includeComments: options.includeComments !== false,
42
+ ...options
43
+ };
44
+ let section = '';
45
+
46
+ // Add environment comment
47
+ if (opts.includeComments) {
48
+ section += this.generateRouteComment(opts.domain || 'unknown', environment);
49
+ section += '\n';
50
+ }
51
+
52
+ // Determine if routes should be nested under environment
53
+ const isNested = environment === 'staging' || environment === 'development';
54
+ const prefix = isNested ? `env.${environment}.` : '';
55
+
56
+ // Build each route
57
+ patterns.forEach((pattern, index) => {
58
+ section += this.formatRoutePattern(pattern, opts.zoneId, prefix);
59
+
60
+ // Add spacing between routes (but not after last one)
61
+ if (index < patterns.length - 1) {
62
+ section += '\n';
63
+ }
64
+ });
65
+ return section;
66
+ }
67
+
68
+ /**
69
+ * Format a single route pattern as TOML
70
+ *
71
+ * @param {string} pattern - Route pattern (e.g., api.example.com/*)
72
+ * @param {string} zoneId - Cloudflare zone ID (optional)
73
+ * @param {string} prefix - Environment prefix (e.g., 'env.staging.')
74
+ * @returns {string} TOML-formatted route
75
+ *
76
+ * @example
77
+ * formatRoutePattern('api.example.com/*', 'xyz789...')
78
+ * // Returns:
79
+ * // [[routes]]
80
+ * // pattern = "api.example.com/*"
81
+ * // zone_id = "xyz789..."
82
+ */
83
+ formatRoutePattern(pattern, zoneId = null, prefix = '') {
84
+ let toml = `[[${prefix}routes]]\n`;
85
+ toml += `pattern = "${this._escapeTomlString(pattern)}"\n`;
86
+ if (zoneId) {
87
+ toml += `zone_id = "${this._escapeTomlString(zoneId)}"\n`;
88
+ }
89
+ return toml;
90
+ }
91
+
92
+ /**
93
+ * Generate descriptive comment for routes
94
+ *
95
+ * @param {string} domain - Domain name
96
+ * @param {string} environment - Environment name
97
+ * @returns {string} Comment text
98
+ *
99
+ * @example
100
+ * generateRouteComment('api.example.com', 'production')
101
+ * // Returns: "# Production environment routes\n# Domain: api.example.com"
102
+ */
103
+ generateRouteComment(domain, environment) {
104
+ const envName = environment.charAt(0).toUpperCase() + environment.slice(1);
105
+ let comment = `# ${envName} environment routes\n`;
106
+ comment += `# Domain: ${domain}`;
107
+ return comment;
108
+ }
109
+
110
+ /**
111
+ * Generate development environment comment
112
+ *
113
+ * Used when development uses workers.dev subdomain (no routes needed)
114
+ *
115
+ * @param {string} workerName - Worker name
116
+ * @returns {string} Comment explaining workers.dev usage
117
+ */
118
+ generateDevComment(workerName) {
119
+ return `# Development environment\n` + `# Uses workers.dev subdomain: https://${workerName}-dev.<account>.workers.dev\n` + `# No custom domain routes needed for development\n`;
120
+ }
121
+
122
+ /**
123
+ * Validate TOML syntax
124
+ *
125
+ * Basic validation to catch common TOML errors
126
+ *
127
+ * @param {string} tomlString - TOML string to validate
128
+ * @returns {Object} Validation result
129
+ * @returns {boolean} returns.valid - True if valid TOML syntax
130
+ * @returns {Array<string>} returns.errors - Array of error messages
131
+ *
132
+ * @example
133
+ * const result = builder.validateTomlSyntax('[[routes]]\npattern = "api.example.com/*"');
134
+ * // Returns: { valid: true, errors: [] }
135
+ */
136
+ validateTomlSyntax(tomlString) {
137
+ const errors = [];
138
+ if (!tomlString || typeof tomlString !== 'string') {
139
+ return {
140
+ valid: false,
141
+ errors: ['TOML string is required']
142
+ };
143
+ }
144
+ const lines = tomlString.split('\n');
145
+ lines.forEach((line, index) => {
146
+ const lineNum = index + 1;
147
+ const trimmed = line.trim();
148
+
149
+ // Skip empty lines and comments
150
+ if (!trimmed || trimmed.startsWith('#')) {
151
+ return;
152
+ }
153
+
154
+ // Check [[array]] syntax
155
+ if (trimmed.startsWith('[[')) {
156
+ if (!trimmed.endsWith(']]')) {
157
+ errors.push(`Line ${lineNum}: Invalid [[array]] syntax - missing closing ]]`);
158
+ }
159
+ const arrayName = trimmed.slice(2, -2);
160
+ if (!arrayName) {
161
+ errors.push(`Line ${lineNum}: Empty [[array]] name`);
162
+ }
163
+ }
164
+
165
+ // Check [section] syntax
166
+ else if (trimmed.startsWith('[') && !trimmed.startsWith('[[')) {
167
+ if (!trimmed.endsWith(']')) {
168
+ errors.push(`Line ${lineNum}: Invalid [section] syntax - missing closing ]`);
169
+ }
170
+ const sectionName = trimmed.slice(1, -1);
171
+ if (!sectionName) {
172
+ errors.push(`Line ${lineNum}: Empty [section] name`);
173
+ }
174
+ }
175
+
176
+ // Check key = value syntax
177
+ else if (trimmed.includes('=')) {
178
+ const parts = trimmed.split('=');
179
+ if (parts.length !== 2) {
180
+ errors.push(`Line ${lineNum}: Invalid key = value syntax - multiple equals signs`);
181
+ return;
182
+ }
183
+ const key = parts[0].trim();
184
+ const value = parts[1].trim();
185
+ if (!key) {
186
+ errors.push(`Line ${lineNum}: Missing key before =`);
187
+ }
188
+ if (!value) {
189
+ errors.push(`Line ${lineNum}: Missing value after =`);
190
+ }
191
+
192
+ // Check string values are quoted
193
+ if (value && !value.startsWith('"') && !value.match(/^[0-9]+$/)) {
194
+ // Not a number and not quoted
195
+ if (!value.startsWith('[') && !value.match(/^(true|false)$/)) {
196
+ errors.push(`Line ${lineNum}: String values must be quoted (key: ${key})`);
197
+ }
198
+ }
199
+
200
+ // Check for unescaped quotes
201
+ if (value.startsWith('"') && value.endsWith('"')) {
202
+ const innerValue = value.slice(1, -1);
203
+ if (innerValue.includes('"') && !innerValue.includes('\\"')) {
204
+ errors.push(`Line ${lineNum}: Unescaped quote in string value (key: ${key})`);
205
+ }
206
+ }
207
+ }
208
+ });
209
+ return {
210
+ valid: errors.length === 0,
211
+ errors
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Escape special characters for TOML strings
217
+ * @private
218
+ *
219
+ * @param {string} str - String to escape
220
+ * @returns {string} Escaped string
221
+ */
222
+ _escapeTomlString(str) {
223
+ if (!str) return '';
224
+ return str.replace(/\\/g, '\\\\') // Escape backslashes
225
+ .replace(/"/g, '\\"') // Escape quotes
226
+ .replace(/\n/g, '\\n') // Escape newlines
227
+ .replace(/\r/g, '\\r') // Escape carriage returns
228
+ .replace(/\t/g, '\\t'); // Escape tabs
229
+ }
230
+
231
+ /**
232
+ * Build complete wrangler.toml routes configuration
233
+ *
234
+ * High-level method that builds routes for all environments
235
+ *
236
+ * @param {Object} routesByEnvironment - Routes grouped by environment
237
+ * @param {Array<string>} routesByEnvironment.production - Production routes
238
+ * @param {Array<string>} routesByEnvironment.staging - Staging routes
239
+ * @param {Array<string>} routesByEnvironment.development - Development routes
240
+ * @param {Object} options - Build options
241
+ *
242
+ * @returns {string} Complete TOML routes configuration
243
+ */
244
+ buildCompleteRoutesConfig(routesByEnvironment, options = {}) {
245
+ let config = '';
246
+
247
+ // Add header
248
+ if (options.includeComments !== false) {
249
+ config += '# Cloudflare Workers Routes Configuration\n';
250
+ config += '# Generated by Clodo Framework\n';
251
+ config += `# Generated: ${new Date().toISOString()}\n\n`;
252
+ }
253
+
254
+ // Production routes (top-level)
255
+ if (routesByEnvironment.production && routesByEnvironment.production.length > 0) {
256
+ config += this.buildRoutesSection(routesByEnvironment.production, 'production', options);
257
+ config += '\n\n';
258
+ }
259
+
260
+ // Staging routes
261
+ if (routesByEnvironment.staging && routesByEnvironment.staging.length > 0) {
262
+ config += this.buildRoutesSection(routesByEnvironment.staging, 'staging', options);
263
+ config += '\n\n';
264
+ }
265
+
266
+ // Development routes
267
+ if (routesByEnvironment.development && routesByEnvironment.development.length > 0) {
268
+ config += this.buildRoutesSection(routesByEnvironment.development, 'development', options);
269
+ config += '\n\n';
270
+ }
271
+ return config.trim() + '\n';
272
+ }
273
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Routing module exports
3
+ *
4
+ * Provides automatic route generation for Cloudflare Workers
5
+ * from domain configuration metadata.
6
+ *
7
+ * This is a CORE IDENTITY feature that completes multi-domain orchestration.
8
+ *
9
+ * @module service-management/routing
10
+ */
11
+
12
+ export { RouteGenerator } from './RouteGenerator.js';
13
+ export { DomainRouteMapper } from './DomainRouteMapper.js';
14
+ export { WranglerRoutesBuilder } from './WranglerRoutesBuilder.js';
@@ -0,0 +1,56 @@
1
+ /**
2
+ * DirectoryStructureService - Handles creation of directory structures for services
3
+ *
4
+ * Provides standardized directory layout creation for generated services,
5
+ * ensuring consistent project structure across all service types.
6
+ */
7
+ import { existsSync, mkdirSync } from 'fs';
8
+ import { join } from 'path';
9
+ export class DirectoryStructureService {
10
+ /**
11
+ * Create the standard directory structure for a service
12
+ * @param {string} servicePath - Root path of the service
13
+ */
14
+ createStandardStructure(servicePath) {
15
+ const directories = ['src', 'src/config', 'src/worker', 'src/handlers', 'src/middleware', 'src/utils', 'src/schemas', 'scripts', 'test', 'test/unit', 'test/integration', 'docs', 'config', 'logs', '.github', '.github/workflows'];
16
+ for (const dir of directories) {
17
+ const fullPath = join(servicePath, dir);
18
+ if (!existsSync(fullPath)) {
19
+ mkdirSync(fullPath, {
20
+ recursive: true
21
+ });
22
+ }
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Create a custom directory structure
28
+ * @param {string} servicePath - Root path of the service
29
+ * @param {string[]} directories - Array of directory paths to create
30
+ */
31
+ createCustomStructure(servicePath, directories) {
32
+ for (const dir of directories) {
33
+ const fullPath = join(servicePath, dir);
34
+ if (!existsSync(fullPath)) {
35
+ mkdirSync(fullPath, {
36
+ recursive: true
37
+ });
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Ensure a single directory exists
44
+ * @param {string} directoryPath - Full path to the directory
45
+ * @returns {boolean} - True if directory was created, false if it already existed
46
+ */
47
+ ensureDirectory(directoryPath) {
48
+ if (!existsSync(directoryPath)) {
49
+ mkdirSync(directoryPath, {
50
+ recursive: true
51
+ });
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ }