@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.
- package/CHANGELOG.md +215 -163
- package/README.md +133 -1
- package/bin/clodo-service.js +0 -0
- package/bin/security/security-cli.js +0 -0
- package/bin/service-management/create-service.js +0 -0
- package/bin/service-management/init-service.js +2 -1
- package/dist/service-management/GenerationEngine.js +298 -3025
- package/dist/service-management/ServiceCreator.js +19 -3
- package/dist/service-management/generators/BaseGenerator.js +233 -0
- package/dist/service-management/generators/GeneratorRegistry.js +254 -0
- package/dist/service-management/generators/cicd/CiWorkflowGenerator.js +87 -0
- package/dist/service-management/generators/cicd/DeployWorkflowGenerator.js +106 -0
- package/dist/service-management/generators/code/ServiceHandlersGenerator.js +235 -0
- package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +116 -0
- package/dist/service-management/generators/code/ServiceUtilsGenerator.js +246 -0
- package/dist/service-management/generators/code/WorkerIndexGenerator.js +143 -0
- package/dist/service-management/generators/config/DevelopmentEnvGenerator.js +101 -0
- package/dist/service-management/generators/config/DomainsConfigGenerator.js +175 -0
- package/dist/service-management/generators/config/EnvExampleGenerator.js +178 -0
- package/dist/service-management/generators/config/ProductionEnvGenerator.js +97 -0
- package/dist/service-management/generators/config/StagingEnvGenerator.js +97 -0
- package/dist/service-management/generators/config/WranglerTomlGenerator.js +238 -0
- package/dist/service-management/generators/core/PackageJsonGenerator.js +243 -0
- package/dist/service-management/generators/core/SiteConfigGenerator.js +115 -0
- package/dist/service-management/generators/documentation/ApiDocsGenerator.js +331 -0
- package/dist/service-management/generators/documentation/ConfigurationDocsGenerator.js +294 -0
- package/dist/service-management/generators/documentation/DeploymentDocsGenerator.js +244 -0
- package/dist/service-management/generators/documentation/ReadmeGenerator.js +196 -0
- package/dist/service-management/generators/schemas/ServiceSchemaGenerator.js +190 -0
- package/dist/service-management/generators/scripts/DeployScriptGenerator.js +123 -0
- package/dist/service-management/generators/scripts/HealthCheckScriptGenerator.js +101 -0
- package/dist/service-management/generators/scripts/SetupScriptGenerator.js +88 -0
- package/dist/service-management/generators/service-types/StaticSiteGenerator.js +342 -0
- package/dist/service-management/generators/testing/EslintConfigGenerator.js +85 -0
- package/dist/service-management/generators/testing/IntegrationTestsGenerator.js +237 -0
- package/dist/service-management/generators/testing/JestConfigGenerator.js +72 -0
- package/dist/service-management/generators/testing/UnitTestsGenerator.js +277 -0
- package/dist/service-management/generators/tooling/DockerComposeGenerator.js +71 -0
- package/dist/service-management/generators/tooling/GitignoreGenerator.js +143 -0
- package/dist/service-management/generators/utils/FileWriter.js +179 -0
- package/dist/service-management/generators/utils/PathResolver.js +157 -0
- package/dist/service-management/generators/utils/ServiceManifestGenerator.js +111 -0
- package/dist/service-management/generators/utils/TemplateEngine.js +185 -0
- package/dist/service-management/generators/utils/index.js +18 -0
- package/dist/service-management/routing/DomainRouteMapper.js +311 -0
- package/dist/service-management/routing/RouteGenerator.js +266 -0
- package/dist/service-management/routing/WranglerRoutesBuilder.js +273 -0
- package/dist/service-management/routing/index.js +14 -0
- package/dist/service-management/services/DirectoryStructureService.js +56 -0
- package/dist/service-management/services/GenerationCoordinator.js +208 -0
- package/dist/service-management/services/GeneratorRegistry.js +174 -0
- package/dist/services/GenericDataService.js +14 -1
- package/dist/utils/config/unified-config-manager.js +128 -12
- package/dist/utils/framework-config.js +74 -2
- package/dist/worker/integration.js +4 -1
- package/package.json +6 -1
- package/templates/generic/clodo-service-manifest.json +25 -0
- package/templates/static-site/.env.example +61 -0
- package/templates/static-site/README.md +176 -0
- package/templates/static-site/clodo-service-manifest.json +66 -0
- package/templates/static-site/package.json +28 -0
- package/templates/static-site/public/404.html +87 -0
- package/templates/static-site/public/app.js +100 -0
- package/templates/static-site/public/index.html +48 -0
- package/templates/static-site/public/styles.css +123 -0
- package/templates/static-site/scripts/deploy.ps1 +121 -0
- package/templates/static-site/scripts/setup.ps1 +179 -0
- package/templates/static-site/src/config/domains.js +35 -0
- package/templates/static-site/src/worker/index.js +153 -0
- 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
|
+
}
|