@tamyla/clodo-framework 4.0.13 → 4.0.15
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 +18 -0
- package/README.md +7 -0
- package/dist/cli/commands/create.js +2 -1
- package/dist/index.js +5 -0
- package/dist/middleware/Composer.js +38 -0
- package/dist/middleware/Registry.js +14 -0
- package/dist/middleware/index.js +3 -0
- package/dist/middleware/shared/basicAuth.js +21 -0
- package/dist/middleware/shared/cors.js +28 -0
- package/dist/middleware/shared/index.js +3 -0
- package/dist/middleware/shared/logging.js +14 -0
- package/dist/monitoring/HealthChecker.js +286 -0
- package/dist/orchestration/modules/StateManager.js +11 -3
- package/dist/security/ConfigurationValidator.js +216 -0
- package/dist/service-management/GenerationEngine.js +13 -2
- package/dist/service-management/ServiceOrchestrator.js +6 -2
- package/dist/service-management/generators/BaseGenerator.js +31 -6
- package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +156 -10
- package/dist/service-management/generators/code/WorkerIndexGenerator.js +75 -9
- package/dist/service-management/generators/utils/FileWriter.js +13 -1
- package/dist/services/ServiceClient.js +239 -0
- package/dist/simple-api.js +32 -1
- package/dist/utils/CircuitBreaker.js +192 -0
- package/dist/utils/EnvironmentValidator.js +147 -0
- package/dist/utils/TemplateRuntime.js +291 -0
- package/dist/utils/deployment/secret-generator.js +37 -26
- package/dist/version/FrameworkInfo.js +104 -0
- package/dist/worker/integration.js +13 -1
- package/docs/MIDDLEWARE_MIGRATION_SUMMARY.md +121 -0
- package/package.json +8 -1
- package/scripts/DEPLOY_COMMAND_NEW.js +128 -0
- package/scripts/README-automated-testing-suite.md +356 -0
- package/scripts/README-test-clodo-deployment.md +157 -0
- package/scripts/README.md +50 -0
- package/scripts/analyze-imports.ps1 +104 -0
- package/scripts/analyze-mixed-code.js +163 -0
- package/scripts/analyze-mixed-rationale.js +149 -0
- package/scripts/automated-testing-suite.js +776 -0
- package/scripts/check-templates.js +105 -0
- package/scripts/debug-generate-worker.js +58 -0
- package/scripts/deployment/README.md +31 -0
- package/scripts/deployment/deploy-domain.ps1 +449 -0
- package/scripts/deployment/deploy-staging.js +120 -0
- package/scripts/deployment/validate-staging.js +166 -0
- package/scripts/diagnose-imports.js +362 -0
- package/scripts/framework-diagnostic.js +368 -0
- package/scripts/migration/migrate-middleware-legacy-to-contract.js +64 -0
- package/scripts/post-publish-test.js +663 -0
- package/scripts/scan-worker-issues.js +52 -0
- package/scripts/service-management/README.md +27 -0
- package/scripts/service-management/setup-interactive.ps1 +693 -0
- package/scripts/test-clodo-deployment.js +588 -0
- package/scripts/test-downstream-install.js +237 -0
- package/scripts/test-local-package.ps1 +126 -0
- package/scripts/test-local-package.sh +166 -0
- package/scripts/test-package.js +339 -0
- package/scripts/testing/README.md +49 -0
- package/scripts/testing/test-first.ps1 +0 -0
- package/scripts/testing/test-first50.ps1 +0 -0
- package/scripts/testing/test.ps1 +0 -0
- package/scripts/utilities/README.md +61 -0
- package/scripts/utilities/check-bin.js +8 -0
- package/scripts/utilities/check-bundle.js +23 -0
- package/scripts/utilities/check-dist-imports.js +65 -0
- package/scripts/utilities/check-import-paths.js +191 -0
- package/scripts/utilities/cleanup-cli.js +159 -0
- package/scripts/utilities/deployment-helpers.ps1 +199 -0
- package/scripts/utilities/fix-dist-imports.js +135 -0
- package/scripts/utilities/generate-secrets.js +159 -0
- package/scripts/utilities/safe-push.ps1 +51 -0
- package/scripts/utilities/setup-helpers.ps1 +206 -0
- package/scripts/utilities/test-packaged-artifact.js +92 -0
- package/scripts/utilities/validate-dist-imports.js +189 -0
- package/scripts/utilities/validate-schema.js +102 -0
- package/scripts/verify-exports.js +193 -0
- package/scripts/verify-persistence-config.js +45 -0
- package/scripts/verify-worker-safety.js +73 -0
- package/templates/generic/src/config/.config-is-sample +1 -0
- package/templates/static-site/.env.example +1 -1
- package/templates/static-site/src/config/.config-is-sample +1 -0
- package/templates/static-site/src/config/domains.js +3 -0
- package/types/middleware.d.ts +1 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Runtime Coordination
|
|
3
|
+
* Provides consistent code generation helpers for templates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Template Runtime for consistent code generation
|
|
8
|
+
*/
|
|
9
|
+
export class TemplateRuntime {
|
|
10
|
+
/**
|
|
11
|
+
* Generate consistent binding objects for templates
|
|
12
|
+
* @param {Object} features - Enabled features configuration
|
|
13
|
+
* @returns {Object} Binding configuration
|
|
14
|
+
*/
|
|
15
|
+
static generateBindings(features) {
|
|
16
|
+
const bindings = {};
|
|
17
|
+
|
|
18
|
+
// D1 Database bindings
|
|
19
|
+
if (features.d1) {
|
|
20
|
+
bindings.DATABASE = {
|
|
21
|
+
type: 'd1',
|
|
22
|
+
id: 'DB'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// KV Namespace bindings
|
|
27
|
+
if (features.kv) {
|
|
28
|
+
bindings.KV_CACHE = {
|
|
29
|
+
type: 'kv_namespace',
|
|
30
|
+
id: 'CACHE'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// R2 Bucket bindings
|
|
35
|
+
if (features.r2) {
|
|
36
|
+
bindings.R2_STORAGE = {
|
|
37
|
+
type: 'r2_bucket',
|
|
38
|
+
id: 'STORAGE'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Durable Object bindings
|
|
43
|
+
if (features.durableObjects) {
|
|
44
|
+
bindings.DURABLE_OBJECT = {
|
|
45
|
+
type: 'durable_object',
|
|
46
|
+
className: 'MyDurableObject'
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Queue bindings
|
|
51
|
+
if (features.queue) {
|
|
52
|
+
bindings.QUEUE = {
|
|
53
|
+
type: 'queue',
|
|
54
|
+
queueName: 'my-queue'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Email bindings
|
|
59
|
+
if (features.email) {
|
|
60
|
+
bindings.EMAIL = {
|
|
61
|
+
type: 'email'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return bindings;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generate consistent import statements for templates
|
|
69
|
+
* @param {Object} features - Enabled features configuration
|
|
70
|
+
* @returns {string[]} Array of import statements
|
|
71
|
+
*/
|
|
72
|
+
static generateImports(features) {
|
|
73
|
+
const imports = [];
|
|
74
|
+
|
|
75
|
+
// Framework imports
|
|
76
|
+
imports.push("import { initializeService } from '@tamyla/clodo-framework/worker';");
|
|
77
|
+
|
|
78
|
+
// Feature-specific imports
|
|
79
|
+
if (features.d1) {
|
|
80
|
+
imports.push("import { D1Database } from '@cloudflare/workers-types';");
|
|
81
|
+
}
|
|
82
|
+
if (features.kv) {
|
|
83
|
+
imports.push("import { KVNamespace } from '@cloudflare/workers-types';");
|
|
84
|
+
}
|
|
85
|
+
if (features.r2) {
|
|
86
|
+
imports.push("import { R2Bucket } from '@cloudflare/workers-types';");
|
|
87
|
+
}
|
|
88
|
+
if (features.durableObjects) {
|
|
89
|
+
imports.push("import { DurableObject } from '@cloudflare/workers-types';");
|
|
90
|
+
}
|
|
91
|
+
if (features.queue) {
|
|
92
|
+
imports.push("import { Queue } from '@cloudflare/workers-types';");
|
|
93
|
+
}
|
|
94
|
+
if (features.email) {
|
|
95
|
+
imports.push("import { EmailMessage } from '@cloudflare/workers-types';");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Utility imports
|
|
99
|
+
if (features.logging) {
|
|
100
|
+
imports.push("import { Logger } from '@tamyla/clodo-framework/utils';");
|
|
101
|
+
}
|
|
102
|
+
if (features.validation) {
|
|
103
|
+
imports.push("import { validateRequest } from '@tamyla/clodo-framework/utils';");
|
|
104
|
+
}
|
|
105
|
+
return imports;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generate consistent route handlers for templates
|
|
110
|
+
* @param {Object} routes - Route configuration
|
|
111
|
+
* @param {Object} features - Enabled features
|
|
112
|
+
* @returns {string} Generated route handler code
|
|
113
|
+
*/
|
|
114
|
+
static generateRouteHandlers(routes, features) {
|
|
115
|
+
let code = '';
|
|
116
|
+
|
|
117
|
+
// Generate route handling logic
|
|
118
|
+
code += 'export async function handleRequest(request, env, ctx) {\n';
|
|
119
|
+
code += ' const url = new URL(request.url);\n';
|
|
120
|
+
code += ' const method = request.method;\n\n';
|
|
121
|
+
|
|
122
|
+
// Add route matching
|
|
123
|
+
for (const [path, config] of Object.entries(routes)) {
|
|
124
|
+
code += ` if (url.pathname === '${path}') {\n`;
|
|
125
|
+
|
|
126
|
+
// Method-specific handling
|
|
127
|
+
if (config.methods && Array.isArray(config.methods)) {
|
|
128
|
+
code += ` if (${config.methods.map(m => `method === '${m}'`).join(' || ')}) {\n`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Feature guards
|
|
132
|
+
if (config.features) {
|
|
133
|
+
for (const feature of config.features) {
|
|
134
|
+
code += ` // Check ${feature} feature\n`;
|
|
135
|
+
code += ` if (!env.FEATURES?.${feature}) {\n`;
|
|
136
|
+
code += ` return new Response('Feature not enabled', { status: 403 });\n`;
|
|
137
|
+
code += ` }\n`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Handler logic
|
|
142
|
+
const handlerName = this.capitalize(path.split('/').filter(Boolean).pop());
|
|
143
|
+
code += ` return await handle${handlerName}(request, env, ctx);\n`;
|
|
144
|
+
if (config.methods && Array.isArray(config.methods)) {
|
|
145
|
+
code += ` }\n`;
|
|
146
|
+
}
|
|
147
|
+
code += ` }\n\n`;
|
|
148
|
+
}
|
|
149
|
+
code += ' return new Response(\'Not Found\', { status: 404 });\n';
|
|
150
|
+
code += '}\n\n';
|
|
151
|
+
|
|
152
|
+
// Generate individual handlers
|
|
153
|
+
for (const [path] of Object.entries(routes)) {
|
|
154
|
+
const handlerName = this.capitalize(path.split('/').filter(Boolean).pop());
|
|
155
|
+
code += `async function handle${handlerName}(request, env, ctx) {\n`;
|
|
156
|
+
code += ` // TODO: Implement ${handlerName} handler\n`;
|
|
157
|
+
code += ` return new Response('Hello from ${handlerName}');\n`;
|
|
158
|
+
code += `}\n\n`;
|
|
159
|
+
}
|
|
160
|
+
return code;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generate consistent middleware chain for templates
|
|
165
|
+
* @param {string[]} middleware - List of middleware names
|
|
166
|
+
* @returns {string} Generated middleware chain code
|
|
167
|
+
*/
|
|
168
|
+
static generateMiddlewareChain(middleware) {
|
|
169
|
+
let code = '';
|
|
170
|
+
code += 'export function createMiddlewareChain() {\n';
|
|
171
|
+
code += ' const middlewares = [\n';
|
|
172
|
+
for (const mw of middleware) {
|
|
173
|
+
code += ` ${mw},\n`;
|
|
174
|
+
}
|
|
175
|
+
code += ' ];\n\n';
|
|
176
|
+
code += ' return (handler) => {\n';
|
|
177
|
+
code += ' return middlewares.reduceRight((acc, mw) => mw(acc), handler);\n';
|
|
178
|
+
code += ' };\n';
|
|
179
|
+
code += '}\n\n';
|
|
180
|
+
return code;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Generate consistent error handling for templates
|
|
185
|
+
* @param {Object} features - Enabled features
|
|
186
|
+
* @returns {string} Generated error handling code
|
|
187
|
+
*/
|
|
188
|
+
static generateErrorHandling(features) {
|
|
189
|
+
let code = '';
|
|
190
|
+
code += 'export function createErrorHandler() {\n';
|
|
191
|
+
code += ' return async (error, request, env, ctx) => {\n';
|
|
192
|
+
code += ' console.error(\'Request error:\', error);\n\n';
|
|
193
|
+
if (features.logging) {
|
|
194
|
+
code += ' // Log error details\n';
|
|
195
|
+
code += ' await logError(error, request);\n\n';
|
|
196
|
+
}
|
|
197
|
+
code += ' // Return appropriate error response\n';
|
|
198
|
+
code += ' if (error.status) {\n';
|
|
199
|
+
code += ' return new Response(JSON.stringify({\n';
|
|
200
|
+
code += ' error: error.message,\n';
|
|
201
|
+
code += ' status: error.status\n';
|
|
202
|
+
code += ' }), {\n';
|
|
203
|
+
code += ' status: error.status,\n';
|
|
204
|
+
code += ' headers: { \'Content-Type\': \'application/json\' }\n';
|
|
205
|
+
code += ' });\n';
|
|
206
|
+
code += ' }\n\n';
|
|
207
|
+
code += ' return new Response(JSON.stringify({\n';
|
|
208
|
+
code += ' error: \'Internal Server Error\',\n';
|
|
209
|
+
code += ' status: 500\n';
|
|
210
|
+
code += ' }), {\n';
|
|
211
|
+
code += ' status: 500,\n';
|
|
212
|
+
code += ' headers: { \'Content-Type\': \'application/json\' }\n';
|
|
213
|
+
code += ' });\n';
|
|
214
|
+
code += ' };\n';
|
|
215
|
+
code += '}\n\n';
|
|
216
|
+
return code;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Generate consistent health check endpoint for templates
|
|
221
|
+
* @param {Object} serviceInfo - Service information
|
|
222
|
+
* @returns {string} Generated health check code
|
|
223
|
+
*/
|
|
224
|
+
static generateHealthCheck(serviceInfo) {
|
|
225
|
+
let code = '';
|
|
226
|
+
code += 'export async function handleHealthCheck(request, env, ctx) {\n';
|
|
227
|
+
code += ' const health = {\n';
|
|
228
|
+
code += ' status: \'healthy\',\n';
|
|
229
|
+
code += ` service: '${serviceInfo.name || 'unknown'}',\n`;
|
|
230
|
+
code += ` version: '${serviceInfo.version || '1.0.0'}',\n`;
|
|
231
|
+
code += ` type: '${serviceInfo.type || 'generic'}',\n`;
|
|
232
|
+
code += ' timestamp: new Date().toISOString(),\n';
|
|
233
|
+
code += ' checks: {}\n';
|
|
234
|
+
code += ' };\n\n';
|
|
235
|
+
|
|
236
|
+
// Add database health checks
|
|
237
|
+
if (serviceInfo.features?.d1) {
|
|
238
|
+
code += ' try {\n';
|
|
239
|
+
code += ' // Check D1 database connectivity\n';
|
|
240
|
+
code += ' await env.DATABASE.prepare(\'SELECT 1\').first();\n';
|
|
241
|
+
code += ' health.checks.database = { status: \'healthy\' };\n';
|
|
242
|
+
code += ' } catch (error) {\n';
|
|
243
|
+
code += ' health.checks.database = { status: \'unhealthy\', error: error.message };\n';
|
|
244
|
+
code += ' health.status = \'unhealthy\';\n';
|
|
245
|
+
code += ' }\n\n';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Add KV health checks
|
|
249
|
+
if (serviceInfo.features?.kv) {
|
|
250
|
+
code += ' try {\n';
|
|
251
|
+
code += ' // Check KV connectivity\n';
|
|
252
|
+
code += ' await env.KV_CACHE.put(\'health-check\', \'ok\', { expirationTtl: 60 });\n';
|
|
253
|
+
code += ' health.checks.kv = { status: \'healthy\' };\n';
|
|
254
|
+
code += ' } catch (error) {\n';
|
|
255
|
+
code += ' health.checks.kv = { status: \'unhealthy\', error: error.message };\n';
|
|
256
|
+
code += ' health.status = \'unhealthy\';\n';
|
|
257
|
+
code += ' }\n\n';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Add R2 health checks
|
|
261
|
+
if (serviceInfo.features?.r2) {
|
|
262
|
+
code += ' try {\n';
|
|
263
|
+
code += ' // Check R2 bucket connectivity\n';
|
|
264
|
+
code += ' await env.R2_STORAGE.head(\'health-check.txt\');\n';
|
|
265
|
+
code += ' health.checks.r2 = { status: \'healthy\' };\n';
|
|
266
|
+
code += ' } catch (error) {\n';
|
|
267
|
+
code += ' health.checks.r2 = { status: \'unhealthy\', error: error.message };\n';
|
|
268
|
+
code += ' health.status = \'unhealthy\';\n';
|
|
269
|
+
code += ' }\n\n';
|
|
270
|
+
}
|
|
271
|
+
code += ' const statusCode = health.status === \'healthy\' ? 200 : 503;\n';
|
|
272
|
+
code += ' return new Response(JSON.stringify(health), {\n';
|
|
273
|
+
code += ' status: statusCode,\n';
|
|
274
|
+
code += ' headers: {\n';
|
|
275
|
+
code += ' \'Content-Type\': \'application/json\',\n';
|
|
276
|
+
code += ' \'Cache-Control\': \'no-cache\'\n';
|
|
277
|
+
code += ' }\n';
|
|
278
|
+
code += ' });\n';
|
|
279
|
+
code += '}\n\n';
|
|
280
|
+
return code;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Utility function to capitalize strings
|
|
285
|
+
* @param {string} str - String to capitalize
|
|
286
|
+
* @returns {string} Capitalized string
|
|
287
|
+
*/
|
|
288
|
+
static capitalize(str) {
|
|
289
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -23,15 +23,9 @@ const moduleFileManager = new FileManager({
|
|
|
23
23
|
enableCache: true
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
__dirname = dirname(__filename);
|
|
30
|
-
} catch (error) {
|
|
31
|
-
// Fallback for test environment
|
|
32
|
-
__dirname = join(process.cwd(), 'src', 'utils', 'deployment');
|
|
33
|
-
__filename = join(__dirname, 'secret-generator.js');
|
|
34
|
-
}
|
|
26
|
+
// Set __dirname relative to current working directory for compatibility
|
|
27
|
+
__dirname = join(process.cwd(), 'src', 'utils', 'deployment');
|
|
28
|
+
__filename = join(__dirname, 'secret-generator.js');
|
|
35
29
|
const SECRET_CONFIGS = {
|
|
36
30
|
'AUTH_JWT_SECRET': {
|
|
37
31
|
length: 64,
|
|
@@ -104,13 +98,15 @@ export class EnhancedSecretManager {
|
|
|
104
98
|
this.environmentConfigs = new Map();
|
|
105
99
|
this.rotationSchedule = new Map();
|
|
106
100
|
|
|
107
|
-
//
|
|
101
|
+
// Persistence opt-in: secrets are sensitive. Default storage is inside .clodo-cache and persistence is disabled
|
|
102
|
+
this.enablePersistence = options.enablePersistence ?? process.env.CLODO_ENABLE_PERSISTENCE === '1';
|
|
103
|
+
// Paths for secret management (default to cache to avoid polluting workspace root)
|
|
108
104
|
this.secretPaths = {
|
|
109
|
-
root: join(this.projectRoot, 'secrets'),
|
|
110
|
-
distribution: join(this.projectRoot, 'secrets', 'distribution'),
|
|
111
|
-
backups: join(this.projectRoot, 'secrets', 'backups'),
|
|
105
|
+
root: join(this.projectRoot, '.clodo-cache', 'secrets'),
|
|
106
|
+
distribution: join(this.projectRoot, '.clodo-cache', 'secrets', 'distribution'),
|
|
107
|
+
backups: join(this.projectRoot, '.clodo-cache', 'secrets', 'backups'),
|
|
112
108
|
audit: join(this.projectRoot, 'logs', 'secrets-audit.log'),
|
|
113
|
-
templates: join(this.projectRoot, 'secrets', 'templates')
|
|
109
|
+
templates: join(this.projectRoot, '.clodo-cache', 'secrets', 'templates')
|
|
114
110
|
};
|
|
115
111
|
|
|
116
112
|
// Supported output formats
|
|
@@ -158,16 +154,21 @@ export class EnhancedSecretManager {
|
|
|
158
154
|
console.log('');
|
|
159
155
|
}
|
|
160
156
|
|
|
161
|
-
// Create directories
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
157
|
+
// Create directories only when persistence is explicitly enabled (prevents accidental creation of secrets in workspaces)
|
|
158
|
+
const shouldCreateDirs = this.enablePersistence && !isTestEnv && !this.dryRun;
|
|
159
|
+
if (shouldCreateDirs) {
|
|
160
|
+
Object.values(this.secretPaths).forEach(path => {
|
|
161
|
+
if (!path.endsWith('.log')) {
|
|
162
|
+
this.ensureDirectory(path);
|
|
163
|
+
} else {
|
|
164
|
+
// For log files, ensure parent directory exists
|
|
165
|
+
const logDir = dirname(path);
|
|
166
|
+
this.ensureDirectory(logDir);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
console.log(' ℹ️ Secrets persistence disabled or dry-run/test env: skipping secrets directory creation');
|
|
171
|
+
}
|
|
171
172
|
this.logSecretEvent('MANAGER_INITIALIZED', 'SYSTEM', {
|
|
172
173
|
formats: Object.keys(this.outputFormats),
|
|
173
174
|
mode: this.dryRun ? 'DRY_RUN' : 'LIVE'
|
|
@@ -698,6 +699,10 @@ ${Object.entries(SECRET_CONFIGS).map(([key, config]) => `- **${key}**: ${config.
|
|
|
698
699
|
}
|
|
699
700
|
async saveDomainSecrets(domain, environment, secretData) {
|
|
700
701
|
const filename = join(this.secretPaths.root, `${domain}-${environment}-secrets.json`);
|
|
702
|
+
if (!this.enablePersistence) {
|
|
703
|
+
console.log(` ℹ️ Persistence disabled: not saving secrets to disk for ${domain} (${environment})`);
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
701
706
|
if (this.dryRun) {
|
|
702
707
|
console.log(` 🔍 DRY RUN: Would save secrets to ${filename}`);
|
|
703
708
|
return filename;
|
|
@@ -753,7 +758,12 @@ export function generateSingleSecret(length = 32) {
|
|
|
753
758
|
return randomBytes(length / 2).toString('hex');
|
|
754
759
|
}
|
|
755
760
|
export function saveSecrets(domain, environment, secrets, additionalData = {}) {
|
|
756
|
-
const secretsDir = 'secrets';
|
|
761
|
+
const secretsDir = process.env.CLODO_SECRETS_DIR || '.clodo-cache/secrets';
|
|
762
|
+
const persistenceEnabled = process.env.CLODO_ENABLE_PERSISTENCE === '1';
|
|
763
|
+
if (!persistenceEnabled) {
|
|
764
|
+
console.log(' ℹ️ Secrets persistence disabled: saveSecrets will not write files unless CLODO_ENABLE_PERSISTENCE=1 is set.');
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
757
767
|
if (!moduleFileManager.exists(secretsDir)) {
|
|
758
768
|
moduleFileManager.ensureDir(secretsDir);
|
|
759
769
|
}
|
|
@@ -798,7 +808,8 @@ export function loadSecrets(domain) {
|
|
|
798
808
|
}
|
|
799
809
|
}
|
|
800
810
|
export function distributeSecrets(domain, secrets) {
|
|
801
|
-
const
|
|
811
|
+
const distBase = process.env.CLODO_SECRETS_DIR || '.clodo-cache/secrets';
|
|
812
|
+
const distDir = join(distBase, 'distribution', domain);
|
|
802
813
|
moduleFileManager.ensureDir(distDir);
|
|
803
814
|
const files = {};
|
|
804
815
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Info Module
|
|
3
|
+
* Provides version detection and framework information
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
|
+
import { resolve, dirname } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Framework Information and Version Detection
|
|
14
|
+
*/
|
|
15
|
+
export class FrameworkInfo {
|
|
16
|
+
/**
|
|
17
|
+
* Get the current framework version from the consuming service's package.json
|
|
18
|
+
* @param {string} serviceRoot - Root directory of the consuming service (optional)
|
|
19
|
+
* @returns {string} Framework version or 'unknown' if not found
|
|
20
|
+
*/
|
|
21
|
+
static getVersion(serviceRoot = null) {
|
|
22
|
+
try {
|
|
23
|
+
// Try to find package.json in the consuming service
|
|
24
|
+
const searchPaths = [serviceRoot, process.cwd(), resolve(process.cwd(), '..'), resolve(process.cwd(), '../..')].filter(Boolean);
|
|
25
|
+
for (const searchPath of searchPaths) {
|
|
26
|
+
try {
|
|
27
|
+
const packagePath = resolve(searchPath, 'package.json');
|
|
28
|
+
const packageContent = readFileSync(packagePath, 'utf8');
|
|
29
|
+
const packageJson = JSON.parse(packageContent);
|
|
30
|
+
|
|
31
|
+
// Check for the framework dependency
|
|
32
|
+
const frameworkDep = packageJson.dependencies?.['@tamyla/clodo-framework'] || packageJson.devDependencies?.['@tamyla/clodo-framework'];
|
|
33
|
+
if (frameworkDep) {
|
|
34
|
+
// Extract version from semver range (e.g., "^4.0.11" -> "4.0.11", ">=4.0.0" -> "4.0.0")
|
|
35
|
+
const version = frameworkDep.replace(/^[^\d]*/, '').split(/[\s-]/)[0];
|
|
36
|
+
return version || frameworkDep;
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// Continue searching other paths
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Fallback: try to get version from the framework's own package.json
|
|
45
|
+
try {
|
|
46
|
+
const frameworkPackagePath = resolve(__dirname, '../../package.json');
|
|
47
|
+
const frameworkPackage = JSON.parse(readFileSync(frameworkPackagePath, 'utf8'));
|
|
48
|
+
return frameworkPackage.version || 'unknown';
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return 'unknown';
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn('Framework version detection failed:', error.message);
|
|
54
|
+
return 'unknown';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get detailed framework information
|
|
60
|
+
* @param {string} serviceRoot - Root directory of the consuming service (optional)
|
|
61
|
+
* @returns {Object} Framework information object
|
|
62
|
+
*/
|
|
63
|
+
static getInfo(serviceRoot = null) {
|
|
64
|
+
const version = this.getVersion(serviceRoot);
|
|
65
|
+
return {
|
|
66
|
+
name: '@tamyla/clodo-framework',
|
|
67
|
+
version: version,
|
|
68
|
+
detected: version !== 'unknown',
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
environment: {
|
|
71
|
+
node: process.version,
|
|
72
|
+
platform: process.platform,
|
|
73
|
+
arch: process.arch
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if the current version meets minimum requirements
|
|
80
|
+
* @param {string} minVersion - Minimum required version
|
|
81
|
+
* @param {string} serviceRoot - Root directory of the consuming service (optional)
|
|
82
|
+
* @returns {boolean} True if version meets requirements
|
|
83
|
+
*/
|
|
84
|
+
static meetsMinimumVersion(minVersion, serviceRoot = null) {
|
|
85
|
+
const currentVersion = this.getVersion(serviceRoot);
|
|
86
|
+
if (currentVersion === 'unknown') {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const current = currentVersion.split('.').map(Number);
|
|
91
|
+
const minimum = minVersion.split('.').map(Number);
|
|
92
|
+
for (let i = 0; i < Math.max(current.length, minimum.length); i++) {
|
|
93
|
+
const currentPart = current[i] || 0;
|
|
94
|
+
const minimumPart = minimum[i] || 0;
|
|
95
|
+
if (currentPart > minimumPart) return true;
|
|
96
|
+
if (currentPart < minimumPart) return false;
|
|
97
|
+
}
|
|
98
|
+
return true; // versions are equal
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.warn('Version comparison failed:', error.message);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -3,6 +3,13 @@ import { getDomainFromEnv, createEnvironmentConfig } from '../config/domains.js'
|
|
|
3
3
|
// Import COMMON_FEATURES from worker-safe constants (no Node.js dependencies)
|
|
4
4
|
import { COMMON_FEATURES } from './features.js';
|
|
5
5
|
|
|
6
|
+
// Import new framework components (worker-safe only)
|
|
7
|
+
import { FrameworkInfo } from '../version/FrameworkInfo.js';
|
|
8
|
+
import { EnvironmentValidator } from '../utils/EnvironmentValidator.js';
|
|
9
|
+
import { ServiceClient } from '../services/ServiceClient.js';
|
|
10
|
+
import { HealthChecker } from '../monitoring/HealthChecker.js';
|
|
11
|
+
// Note: ConfigurationValidator requires Node.js APIs, so it's not included in worker runtime
|
|
12
|
+
|
|
6
13
|
// Re-export COMMON_FEATURES for use in worker templates
|
|
7
14
|
export { COMMON_FEATURES };
|
|
8
15
|
|
|
@@ -58,7 +65,12 @@ export const initializeService = (env, domainConfigs = {}) => {
|
|
|
58
65
|
env,
|
|
59
66
|
isProduction: environment === 'production',
|
|
60
67
|
isStaging: environment === 'staging',
|
|
61
|
-
isDevelopment: environment === 'development'
|
|
68
|
+
isDevelopment: environment === 'development',
|
|
69
|
+
// Enhanced framework features (worker-safe only)
|
|
70
|
+
serviceClient: new ServiceClient(),
|
|
71
|
+
healthChecker: new HealthChecker(),
|
|
72
|
+
environmentValidator: EnvironmentValidator,
|
|
73
|
+
frameworkInfo: FrameworkInfo.getInfo()
|
|
62
74
|
};
|
|
63
75
|
logger.info(`Service initialized: ${domainConfig.name} (${environment})`);
|
|
64
76
|
logger.debug(`Enabled features: ${serviceContext.features.join(', ')}`);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Middleware Architecture Migration Summary (v4.1)
|
|
2
|
+
|
|
3
|
+
## 📌 Purpose
|
|
4
|
+
This document captures the design decisions, implementation details, assumptions, tests, migration steps, and next actions for the contract-first middleware migration (v4.1): moving from per-service concrete middleware implementations (`createServiceMiddleware`) to lightweight middleware contracts with a shared runtime (Registry + Composer).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🔧 What was implemented
|
|
9
|
+
- Default approach: **contract-first** middleware generation with opt-in `legacy` strategy.
|
|
10
|
+
- New runtime primitives (framework-level):
|
|
11
|
+
- `src/middleware/Registry.js` - central registry API used in tests and tooling.
|
|
12
|
+
- `src/middleware/Composer.js` - middleware composer that executes middleware chains with short-circuiting and post-processing.
|
|
13
|
+
- `src/middleware/types.d.ts` - TypeScript types describing `IServiceMiddleware` and `IMiddlewareChain`.
|
|
14
|
+
- `src/middleware/shared/` - framework shared middleware: `cors`, `logging`, `basicAuth` (exports in `index.js`).
|
|
15
|
+
- Generator changes:
|
|
16
|
+
- `ServiceMiddlewareGenerator` now emits either:
|
|
17
|
+
- A minimal contract class + `registerMiddleware` helper (contract strategy), or
|
|
18
|
+
- A legacy `createServiceMiddleware` factory (legacy strategy).
|
|
19
|
+
- Generator also emits a self-contained `src/middleware/runtime.js` and a `src/middleware/shared/` folder when generating services (for self-containment).
|
|
20
|
+
- Worker entry updates:
|
|
21
|
+
- `WorkerIndexGenerator` updated to import middleware runtime and compose pipeline using `MiddlewareComposer` + `MiddlewareRegistry`.
|
|
22
|
+
- Worker fetch runtime supports both contract registration and legacy factory via an adapter.
|
|
23
|
+
- Migration tools & tests:
|
|
24
|
+
- `scripts/migration/migrate-middleware-legacy-to-contract.js` (basic codemod to convert legacy factories to contract skeletons + `registerMiddleware`).
|
|
25
|
+
- Unit tests added for `Registry`, `Composer`, `shared/cors`, generator outputs, and migration script.
|
|
26
|
+
- CLI:
|
|
27
|
+
- `cli/commands/create.js` adds `--middleware-strategy <strategy>` CLI flag (default `contract`).
|
|
28
|
+
- Docs & changelog updated:
|
|
29
|
+
- `docs/HOWTO_CONSUME_CLODO_FRAMEWORK.md` updated to describe the new approach.
|
|
30
|
+
- `CHANGELOG.md` mentions the v4.1 middleware architecture change.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ✅ Files added / modified (high-level)
|
|
35
|
+
- Added:
|
|
36
|
+
- `src/middleware/Registry.js`
|
|
37
|
+
- `src/middleware/Composer.js`
|
|
38
|
+
- `src/middleware/types.d.ts`
|
|
39
|
+
- `src/middleware/shared/{cors.js,logging.js,basicAuth.js,index.js}`
|
|
40
|
+
- `scripts/migration/migrate-middleware-legacy-to-contract.js`
|
|
41
|
+
- `docs/MIDDLEWARE_MIGRATION_SUMMARY.md` (this file)
|
|
42
|
+
- Tests: `test/utils/middleware/{Registry,Composer,cors}.test.js`, generator tests and migration test
|
|
43
|
+
- Modified (generators & templates):
|
|
44
|
+
- `src/service-management/generators/code/ServiceMiddlewareGenerator.js` (now emits contract or legacy output and service-local runtime/shared stubs)
|
|
45
|
+
- `src/service-management/generators/code/WorkerIndexGenerator.js` (loads middleware using dynamic import and composes pipeline; includes legacy adapter)
|
|
46
|
+
- `src/service-management/GenerationEngine.js` (passes `middlewareStrategy` through context)
|
|
47
|
+
- `src/service-management/ServiceOrchestrator.js` & `src/simple-api.js` (accept middleware strategy)
|
|
48
|
+
- `cli/commands/create.js` (new `--middleware-strategy` option)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🧭 Key assumptions made
|
|
53
|
+
1. Default strategy is `contract` (opt-in `legacy`).
|
|
54
|
+
2. Generated services are self-contained by default (runtime + shared stubs added into generated service) to work standalone.
|
|
55
|
+
3. Legacy factory shape expected to return object with `processRequest(request)` and `processResponse(response)` methods.
|
|
56
|
+
4. Runtime dynamic import is acceptable in Worker code (`await import('../middleware/service-middleware.js')`).
|
|
57
|
+
5. TypeScript users will later add TS skeletons or `.d.ts` files if they require compile-time checks.
|
|
58
|
+
|
|
59
|
+
If any of these assumptions are not desirable for your environment, they require small changes (see "Nuances and review points").
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## ⚠️ Nuances & review points (actionable)
|
|
64
|
+
- Registry duplication:
|
|
65
|
+
- There are both framework-level `Registry.js` and a service-local `runtime.js` (generated by the generator). Decide whether you want a single shared runtime or per-service runtime. If you prefer a single runtime, update the generator to import the framework runtime rather than generate a local copy.
|
|
66
|
+
- Bundler behavior & dynamic imports:
|
|
67
|
+
- The dynamic import pattern must be tested with your bundler (esbuild/wrangler). Ensure bundler includes generated middleware files in the built artifact.
|
|
68
|
+
- Adapter coverage:
|
|
69
|
+
- The legacy adapter covers the common `createServiceMiddleware` factory shape. If your legacy services used other shapes, extend the adapter logic accordingly.
|
|
70
|
+
- TypeScript support:
|
|
71
|
+
- Generated skeletons are JavaScript; if you primarily use TypeScript, consider adding `.ts` templates and `.d.ts` exports for better DX.
|
|
72
|
+
- Migration tool limitations:
|
|
73
|
+
- The codemod is intentionally conservative and may require manual editing for complex legacy middleware logic.
|
|
74
|
+
- Security/logging:
|
|
75
|
+
- The example `logging` middleware uses `console.log` directly—avoid logging sensitive headers. Consider adding redaction configuration.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 🔁 Migration steps (recommended)
|
|
80
|
+
1. Try generating a new service with default (contract) strategy to validate the new flow:
|
|
81
|
+
- `npx clodo-service create --middleware-strategy contract` (or omit flag — default is `contract`).
|
|
82
|
+
2. To generate an old-style output for a single new service for comparison: `npx clodo-service create --middleware-strategy legacy`.
|
|
83
|
+
3. For existing services using legacy middleware:
|
|
84
|
+
- Run `node scripts/migration/migrate-middleware-legacy-to-contract.js <servicePath> [serviceName]`.
|
|
85
|
+
- Review the converted skeleton and verify imports and custom logic—make adjustments as required.
|
|
86
|
+
4. Update tests and CI to include a smoke test that deploys/loads the generated service (both contract and legacy flows).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## ✅ Tests & QA to add (if not already present)
|
|
91
|
+
- Integration tests that:
|
|
92
|
+
- Generate a service with `contract` strategy and verify `registerMiddleware` is invoked and runtime pipeline executes correctly.
|
|
93
|
+
- Generate a service with `legacy` strategy and verify the legacy adapter path executes `createServiceMiddleware`.
|
|
94
|
+
- Bundle-size smoke test that measures the generated artifact size between legacy and contract strategies.
|
|
95
|
+
- Migration tests on representative real-world legacy middleware shapes.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 📋 Next steps & open todos
|
|
100
|
+
- Complete migration of tests and e2e fixtures that currently rely on legacy middleware.
|
|
101
|
+
- Add TypeScript skeleton templates (optional, recommended for TS-first projects).
|
|
102
|
+
- Decide on runtime model: service-local runtime (current) vs centralized runtime (change generator behavior if you prefer central runtime).
|
|
103
|
+
- Add more robust adapter patterns if you encounter edge-case legacy middleware shapes in the wild.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 📎 Helpful quick references
|
|
108
|
+
- Generator: `src/service-management/generators/code/ServiceMiddlewareGenerator.js`
|
|
109
|
+
- Worker: `src/service-management/generators/code/WorkerIndexGenerator.js`
|
|
110
|
+
- Migration: `scripts/migration/migrate-middleware-legacy-to-contract.js`
|
|
111
|
+
- Runtime: `src/middleware/{Registry.js,Composer.js}`
|
|
112
|
+
- Tests: `test/utils/middleware/*`, `test/service-management/generators/*`, `test/scripts/migration/*`
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
If you'd like, I can now:
|
|
117
|
+
- Finish updating tests/fixtures and add the bundle-size smoke test, or
|
|
118
|
+
- Add TypeScript templates for generated services, or
|
|
119
|
+
- Replace per-service runtime with a centralized runtime (if you prefer that model).
|
|
120
|
+
|
|
121
|
+
Pick one of the bullets above and I’ll proceed. If you'd like further edits to this summary, I can fold them into this document.
|