@tamyla/clodo-framework 1.0.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 +564 -0
- package/LICENSE +21 -0
- package/README.md +1393 -0
- package/bin/README.md +71 -0
- package/bin/clodo-service.js +416 -0
- package/bin/security/security-cli.js +96 -0
- package/bin/service-management/README.md +74 -0
- package/bin/service-management/create-service.js +129 -0
- package/bin/service-management/init-service.js +102 -0
- package/bin/service-management/init-service.js.backup +889 -0
- package/bin/shared/config/customer-cli.js +293 -0
- package/dist/config/ConfigurationManager.js +159 -0
- package/dist/config/CustomerConfigCLI.js +220 -0
- package/dist/config/FeatureManager.js +426 -0
- package/dist/config/customers.js +441 -0
- package/dist/config/domains.js +180 -0
- package/dist/config/features.js +225 -0
- package/dist/config/index.js +6 -0
- package/dist/database/database-orchestrator.js +730 -0
- package/dist/database/index.js +4 -0
- package/dist/deployment/auditor.js +971 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/rollback-manager.js +523 -0
- package/dist/deployment/testers/api-tester.js +80 -0
- package/dist/deployment/testers/auth-tester.js +129 -0
- package/dist/deployment/testers/core.js +217 -0
- package/dist/deployment/testers/database-tester.js +105 -0
- package/dist/deployment/testers/index.js +74 -0
- package/dist/deployment/testers/load-tester.js +120 -0
- package/dist/deployment/testers/performance-tester.js +105 -0
- package/dist/deployment/validator.js +558 -0
- package/dist/deployment/wrangler-deployer.js +574 -0
- package/dist/handlers/GenericRouteHandler.js +532 -0
- package/dist/index.js +39 -0
- package/dist/migration/MigrationAdapters.js +562 -0
- package/dist/modules/ModuleManager.js +668 -0
- package/dist/modules/security.js +98 -0
- package/dist/orchestration/cross-domain-coordinator.js +1083 -0
- package/dist/orchestration/index.js +5 -0
- package/dist/orchestration/modules/DeploymentCoordinator.js +258 -0
- package/dist/orchestration/modules/DomainResolver.js +196 -0
- package/dist/orchestration/modules/StateManager.js +332 -0
- package/dist/orchestration/multi-domain-orchestrator.js +255 -0
- package/dist/routing/EnhancedRouter.js +158 -0
- package/dist/schema/SchemaManager.js +778 -0
- package/dist/security/ConfigurationValidator.js +490 -0
- package/dist/security/DeploymentManager.js +208 -0
- package/dist/security/SecretGenerator.js +142 -0
- package/dist/security/SecurityCLI.js +228 -0
- package/dist/security/index.js +51 -0
- package/dist/security/patterns/environment-rules.js +66 -0
- package/dist/security/patterns/insecure-patterns.js +21 -0
- package/dist/service-management/ConfirmationEngine.js +411 -0
- package/dist/service-management/ErrorTracker.js +294 -0
- package/dist/service-management/GenerationEngine.js +3109 -0
- package/dist/service-management/InputCollector.js +237 -0
- package/dist/service-management/ServiceCreator.js +229 -0
- package/dist/service-management/ServiceInitializer.js +448 -0
- package/dist/service-management/ServiceOrchestrator.js +638 -0
- package/dist/service-management/handlers/ConfigMutator.js +130 -0
- package/dist/service-management/handlers/ConfirmationHandler.js +71 -0
- package/dist/service-management/handlers/GenerationHandler.js +80 -0
- package/dist/service-management/handlers/InputHandler.js +59 -0
- package/dist/service-management/handlers/ValidationHandler.js +203 -0
- package/dist/service-management/index.js +7 -0
- package/dist/services/GenericDataService.js +488 -0
- package/dist/shared/cloudflare/domain-discovery.js +562 -0
- package/dist/shared/cloudflare/domain-manager.js +912 -0
- package/dist/shared/cloudflare/index.js +8 -0
- package/dist/shared/cloudflare/ops.js +387 -0
- package/dist/shared/config/cache.js +1167 -0
- package/dist/shared/config/command-config-manager.js +174 -0
- package/dist/shared/config/customer-cli.js +258 -0
- package/dist/shared/config/index.js +9 -0
- package/dist/shared/config/manager.js +289 -0
- package/dist/shared/database/connection-manager.js +338 -0
- package/dist/shared/database/index.js +7 -0
- package/dist/shared/database/orchestrator.js +632 -0
- package/dist/shared/deployment/auditor.js +971 -0
- package/dist/shared/deployment/index.js +10 -0
- package/dist/shared/deployment/rollback-manager.js +523 -0
- package/dist/shared/deployment/validator.js +558 -0
- package/dist/shared/index.js +32 -0
- package/dist/shared/monitoring/health-checker.js +250 -0
- package/dist/shared/monitoring/index.js +8 -0
- package/dist/shared/monitoring/memory-manager.js +382 -0
- package/dist/shared/monitoring/production-monitor.js +390 -0
- package/dist/shared/production-tester/api-tester.js +80 -0
- package/dist/shared/production-tester/auth-tester.js +129 -0
- package/dist/shared/production-tester/core.js +217 -0
- package/dist/shared/production-tester/database-tester.js +105 -0
- package/dist/shared/production-tester/index.js +74 -0
- package/dist/shared/production-tester/load-tester.js +120 -0
- package/dist/shared/production-tester/performance-tester.js +105 -0
- package/dist/shared/security/api-token-manager.js +296 -0
- package/dist/shared/security/index.js +8 -0
- package/dist/shared/security/secret-generator.js +918 -0
- package/dist/shared/security/secure-token-manager.js +379 -0
- package/dist/shared/utils/error-recovery.js +240 -0
- package/dist/shared/utils/graceful-shutdown-manager.js +380 -0
- package/dist/shared/utils/index.js +9 -0
- package/dist/shared/utils/interactive-prompts.js +134 -0
- package/dist/shared/utils/rate-limiter.js +249 -0
- package/dist/utils/ErrorHandler.js +173 -0
- package/dist/utils/deployment/config-cache.js +1160 -0
- package/dist/utils/deployment/index.js +6 -0
- package/dist/utils/deployment/interactive-prompts.js +97 -0
- package/dist/utils/deployment/secret-generator.js +896 -0
- package/dist/utils/dirname-helper.js +35 -0
- package/dist/utils/domain-config.js +159 -0
- package/dist/utils/error-recovery.js +240 -0
- package/dist/utils/esm-helper.js +52 -0
- package/dist/utils/framework-config.js +481 -0
- package/dist/utils/graceful-shutdown-manager.js +379 -0
- package/dist/utils/health-checker.js +114 -0
- package/dist/utils/index.js +36 -0
- package/dist/utils/prompt-handler.js +98 -0
- package/dist/utils/usage-tracker.js +252 -0
- package/dist/utils/validation.js +112 -0
- package/dist/version/VersionDetector.js +723 -0
- package/dist/worker/index.js +4 -0
- package/dist/worker/integration.js +332 -0
- package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +206 -0
- package/docs/INTEGRATION_GUIDE.md +2045 -0
- package/docs/README.md +82 -0
- package/docs/SECURITY.md +242 -0
- package/docs/deployment/deployment-guide.md +540 -0
- package/docs/overview.md +280 -0
- package/package.json +176 -0
- package/types/index.d.ts +575 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InputCollector - Tier 1: Core Input Collection
|
|
3
|
+
*
|
|
4
|
+
* Collects the 6 absolutely required inputs for service creation:
|
|
5
|
+
* 1. Service Name
|
|
6
|
+
* 2. Service Type
|
|
7
|
+
* 3. Domain Name
|
|
8
|
+
* 4. Cloudflare API Token
|
|
9
|
+
* 5. Cloudflare Account ID
|
|
10
|
+
* 6. Cloudflare Zone ID
|
|
11
|
+
* 7. Target Environment (derived but required)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createInterface } from 'readline';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import { validateServiceName, validateDomainName } from '../utils/validation.js';
|
|
17
|
+
export class InputCollector {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.interactive = options.interactive !== false;
|
|
20
|
+
this.rl = this.interactive ? createInterface({
|
|
21
|
+
input: process.stdin,
|
|
22
|
+
output: process.stdout
|
|
23
|
+
}) : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Collect all 6 core inputs interactively
|
|
28
|
+
*/
|
|
29
|
+
async collectCoreInputs() {
|
|
30
|
+
const inputs = {};
|
|
31
|
+
console.log(chalk.cyan('📝 Collecting Core Service Information'));
|
|
32
|
+
console.log(chalk.white('These 6 inputs are required to create your Clodo service.\n'));
|
|
33
|
+
|
|
34
|
+
// 1. Service Name
|
|
35
|
+
inputs.serviceName = await this.collectServiceName();
|
|
36
|
+
|
|
37
|
+
// 2. Service Type
|
|
38
|
+
inputs.serviceType = await this.collectServiceType();
|
|
39
|
+
|
|
40
|
+
// 3. Domain Name
|
|
41
|
+
inputs.domainName = await this.collectDomainName();
|
|
42
|
+
|
|
43
|
+
// 4. Cloudflare API Token
|
|
44
|
+
inputs.cloudflareToken = await this.collectCloudflareToken();
|
|
45
|
+
|
|
46
|
+
// 5. Cloudflare Account ID
|
|
47
|
+
inputs.cloudflareAccountId = await this.collectCloudflareAccountId();
|
|
48
|
+
|
|
49
|
+
// 6. Cloudflare Zone ID
|
|
50
|
+
inputs.cloudflareZoneId = await this.collectCloudflareZoneId();
|
|
51
|
+
|
|
52
|
+
// 7. Target Environment
|
|
53
|
+
inputs.environment = await this.collectEnvironment();
|
|
54
|
+
return inputs;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validate core inputs (for non-interactive mode)
|
|
59
|
+
*/
|
|
60
|
+
async validateCoreInputs(inputs) {
|
|
61
|
+
const required = ['serviceName', 'serviceType', 'domainName', 'cloudflareToken', 'cloudflareAccountId', 'cloudflareZoneId', 'environment'];
|
|
62
|
+
for (const field of required) {
|
|
63
|
+
if (!inputs[field]) {
|
|
64
|
+
throw new Error(`Missing required input: ${field}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Validate service name
|
|
69
|
+
if (!validateServiceName(inputs.serviceName)) {
|
|
70
|
+
throw new Error('Invalid service name format');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Validate domain name
|
|
74
|
+
if (!validateDomainName(inputs.domainName)) {
|
|
75
|
+
throw new Error('Invalid domain name format');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Validate service type
|
|
79
|
+
const validTypes = ['data-service', 'auth-service', 'content-service', 'api-gateway', 'generic'];
|
|
80
|
+
if (!validTypes.includes(inputs.serviceType)) {
|
|
81
|
+
throw new Error(`Invalid service type. Must be one of: ${validTypes.join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate environment
|
|
85
|
+
const validEnvs = ['development', 'staging', 'production'];
|
|
86
|
+
if (!validEnvs.includes(inputs.environment)) {
|
|
87
|
+
throw new Error(`Invalid environment. Must be one of: ${validEnvs.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Collect service name with validation
|
|
93
|
+
*/
|
|
94
|
+
async collectServiceName() {
|
|
95
|
+
for (;;) {
|
|
96
|
+
const name = await this.prompt('Service Name (lowercase, letters/numbers/hyphens only): ');
|
|
97
|
+
if (validateServiceName(name)) {
|
|
98
|
+
return name;
|
|
99
|
+
}
|
|
100
|
+
console.log(chalk.red('✗ Invalid service name. Use only lowercase letters, numbers, and hyphens.'));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Collect service type with menu
|
|
106
|
+
*/
|
|
107
|
+
async collectServiceType() {
|
|
108
|
+
const types = {
|
|
109
|
+
'data-service': 'Data service with CRUD operations, search, and filtering',
|
|
110
|
+
'auth-service': 'Authentication and authorization service',
|
|
111
|
+
'content-service': 'Content management with file storage and search',
|
|
112
|
+
'api-gateway': 'API gateway with rate limiting and routing',
|
|
113
|
+
'generic': 'Basic service with core Clodo Framework features'
|
|
114
|
+
};
|
|
115
|
+
console.log(chalk.cyan('Available Service Types:'));
|
|
116
|
+
Object.entries(types).forEach(([type, desc]) => {
|
|
117
|
+
console.log(chalk.white(` ${type}: ${desc}`));
|
|
118
|
+
});
|
|
119
|
+
console.log('');
|
|
120
|
+
for (;;) {
|
|
121
|
+
const type = await this.prompt('Service Type (default: generic): ', 'generic');
|
|
122
|
+
if (Object.keys(types).includes(type)) {
|
|
123
|
+
return type;
|
|
124
|
+
}
|
|
125
|
+
console.log(chalk.red('✗ Invalid service type. Choose from the list above.'));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Collect domain name with validation
|
|
131
|
+
*/
|
|
132
|
+
async collectDomainName() {
|
|
133
|
+
for (;;) {
|
|
134
|
+
const domain = await this.prompt('Domain Name (e.g., my-service.com): ');
|
|
135
|
+
if (validateDomainName(domain)) {
|
|
136
|
+
return domain;
|
|
137
|
+
}
|
|
138
|
+
console.log(chalk.red('✗ Invalid domain name format.'));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Collect Cloudflare API token
|
|
144
|
+
*/
|
|
145
|
+
async collectCloudflareToken() {
|
|
146
|
+
console.log(chalk.yellow('Cloudflare Configuration:'));
|
|
147
|
+
console.log(chalk.white('You can find your API token at: https://dash.cloudflare.com/profile/api-tokens'));
|
|
148
|
+
console.log('');
|
|
149
|
+
for (;;) {
|
|
150
|
+
const token = await this.prompt('Cloudflare API Token: ');
|
|
151
|
+
if (token && token.length > 20) {
|
|
152
|
+
// Basic length validation
|
|
153
|
+
return token;
|
|
154
|
+
}
|
|
155
|
+
console.log(chalk.red('✗ Invalid API token format.'));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Collect Cloudflare Account ID
|
|
161
|
+
*/
|
|
162
|
+
async collectCloudflareAccountId() {
|
|
163
|
+
console.log(chalk.white('Find your Account ID in the right sidebar of your Cloudflare dashboard.'));
|
|
164
|
+
console.log('');
|
|
165
|
+
for (;;) {
|
|
166
|
+
const accountId = await this.prompt('Cloudflare Account ID: ');
|
|
167
|
+
if (accountId && /^[a-f0-9]{32}$/i.test(accountId)) {
|
|
168
|
+
// 32 hex chars
|
|
169
|
+
return accountId;
|
|
170
|
+
}
|
|
171
|
+
console.log(chalk.red('✗ Invalid Account ID format (should be 32 hexadecimal characters).'));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Collect Cloudflare Zone ID
|
|
177
|
+
*/
|
|
178
|
+
async collectCloudflareZoneId() {
|
|
179
|
+
console.log(chalk.white('Find your Zone ID in the Overview tab of your domain in Cloudflare.'));
|
|
180
|
+
console.log('');
|
|
181
|
+
for (;;) {
|
|
182
|
+
const zoneId = await this.prompt('Cloudflare Zone ID: ');
|
|
183
|
+
if (zoneId && /^[a-f0-9]{32}$/i.test(zoneId)) {
|
|
184
|
+
// 32 hex chars
|
|
185
|
+
return zoneId;
|
|
186
|
+
}
|
|
187
|
+
console.log(chalk.red('✗ Invalid Zone ID format (should be 32 hexadecimal characters).'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Collect target environment
|
|
193
|
+
*/
|
|
194
|
+
async collectEnvironment() {
|
|
195
|
+
const environments = {
|
|
196
|
+
'development': 'Local development environment',
|
|
197
|
+
'staging': 'Staging environment for testing',
|
|
198
|
+
'production': 'Production environment for live services'
|
|
199
|
+
};
|
|
200
|
+
console.log(chalk.cyan('Target Environment:'));
|
|
201
|
+
Object.entries(environments).forEach(([env, desc]) => {
|
|
202
|
+
console.log(chalk.white(` ${env}: ${desc}`));
|
|
203
|
+
});
|
|
204
|
+
console.log('');
|
|
205
|
+
for (;;) {
|
|
206
|
+
const env = await this.prompt('Environment (default: development): ', 'development');
|
|
207
|
+
if (Object.keys(environments).includes(env)) {
|
|
208
|
+
return env;
|
|
209
|
+
}
|
|
210
|
+
console.log(chalk.red('✗ Invalid environment. Choose from development, staging, or production.'));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Prompt user for input (interactive mode)
|
|
216
|
+
*/
|
|
217
|
+
async prompt(question, defaultValue = '') {
|
|
218
|
+
if (!this.interactive) {
|
|
219
|
+
throw new Error('Cannot prompt in non-interactive mode');
|
|
220
|
+
}
|
|
221
|
+
return new Promise(resolve => {
|
|
222
|
+
const promptText = defaultValue ? `${question}(default: ${defaultValue}) ` : question;
|
|
223
|
+
this.rl.question(promptText, answer => {
|
|
224
|
+
resolve(answer || defaultValue);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Close readline interface
|
|
231
|
+
*/
|
|
232
|
+
close() {
|
|
233
|
+
if (this.rl) {
|
|
234
|
+
this.rl.close();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clodo Framework - Service Creator
|
|
3
|
+
* Programmatic API for creating services from templates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync, readdirSync, statSync } from 'fs';
|
|
7
|
+
import { join, dirname, resolve } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const TEMPLATES_DIR = join(__dirname, '..', '..', 'templates');
|
|
12
|
+
const SERVICE_TYPES = ['data-service', 'auth-service', 'content-service', 'api-gateway', 'generic'];
|
|
13
|
+
export class ServiceCreator {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.templatesDir = options.templatesDir || TEMPLATES_DIR;
|
|
16
|
+
this.serviceTypes = options.serviceTypes || SERVICE_TYPES;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate service name according to framework conventions
|
|
21
|
+
* @param {string} name - Service name to validate
|
|
22
|
+
* @throws {Error} If validation fails
|
|
23
|
+
*/
|
|
24
|
+
validateServiceName(name) {
|
|
25
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
26
|
+
throw new Error('Service name must contain only lowercase letters, numbers, and hyphens');
|
|
27
|
+
}
|
|
28
|
+
if (name.length < 3) {
|
|
29
|
+
throw new Error('Service name must be at least 3 characters long');
|
|
30
|
+
}
|
|
31
|
+
if (name.length > 50) {
|
|
32
|
+
throw new Error('Service name must be no more than 50 characters long');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validate service type
|
|
38
|
+
* @param {string} type - Service type to validate
|
|
39
|
+
* @throws {Error} If type is invalid
|
|
40
|
+
*/
|
|
41
|
+
validateServiceType(type) {
|
|
42
|
+
if (!this.serviceTypes.includes(type)) {
|
|
43
|
+
throw new Error(`Invalid service type. Must be one of: ${this.serviceTypes.join(', ')}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a service from a template
|
|
49
|
+
* @param {string} serviceName - Name of the service to create
|
|
50
|
+
* @param {Object} options - Creation options
|
|
51
|
+
* @param {string} options.type - Service type (default: 'generic')
|
|
52
|
+
* @param {string} options.output - Output directory (default: current directory)
|
|
53
|
+
* @param {boolean} options.force - Overwrite existing directory (default: false)
|
|
54
|
+
* @param {Object} options.variables - Additional template variables
|
|
55
|
+
* @returns {Object} Creation result with success status and metadata
|
|
56
|
+
*/
|
|
57
|
+
async createService(serviceName, options = {}) {
|
|
58
|
+
const config = {
|
|
59
|
+
type: 'generic',
|
|
60
|
+
output: process.cwd(),
|
|
61
|
+
force: false,
|
|
62
|
+
variables: {},
|
|
63
|
+
...options
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
// Validate inputs
|
|
67
|
+
this.validateServiceName(serviceName);
|
|
68
|
+
this.validateServiceType(config.type);
|
|
69
|
+
const templateDir = join(this.templatesDir, config.type);
|
|
70
|
+
const serviceDir = join(config.output, serviceName);
|
|
71
|
+
|
|
72
|
+
// Check if template exists
|
|
73
|
+
if (!existsSync(templateDir)) {
|
|
74
|
+
throw new Error(`Template not found: ${templateDir}. Available templates: ${this.serviceTypes.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if service directory already exists
|
|
78
|
+
if (existsSync(serviceDir) && !config.force) {
|
|
79
|
+
throw new Error(`Service directory already exists: ${serviceDir}. Use force option to overwrite.`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Copy template to service directory
|
|
83
|
+
cpSync(templateDir, serviceDir, {
|
|
84
|
+
recursive: true
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Prepare template variables
|
|
88
|
+
const defaultVariables = {
|
|
89
|
+
'{{SERVICE_NAME}}': serviceName,
|
|
90
|
+
'{{SERVICE_TYPE}}': config.type,
|
|
91
|
+
'{{SERVICE_DISPLAY_NAME}}': this.toTitleCase(serviceName.replace(/-/g, ' ')),
|
|
92
|
+
'{{CURRENT_DATE}}': new Date().toISOString().split('T')[0],
|
|
93
|
+
'{{CURRENT_YEAR}}': new Date().getFullYear().toString(),
|
|
94
|
+
'{{FRAMEWORK_VERSION}}': this.getFrameworkVersion()
|
|
95
|
+
};
|
|
96
|
+
const allVariables = {
|
|
97
|
+
...defaultVariables,
|
|
98
|
+
...config.variables
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Replace template variables
|
|
102
|
+
this.replaceTemplateVariables(serviceDir, allVariables);
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
serviceName,
|
|
106
|
+
serviceType: config.type,
|
|
107
|
+
serviceDir,
|
|
108
|
+
templateDir,
|
|
109
|
+
variables: allVariables
|
|
110
|
+
};
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
serviceName,
|
|
115
|
+
error: error.message
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Replace template variables in all files within a directory
|
|
122
|
+
* @param {string} dir - Directory to process
|
|
123
|
+
* @param {Object} variables - Variable mappings
|
|
124
|
+
*/
|
|
125
|
+
replaceTemplateVariables(dir, variables) {
|
|
126
|
+
const files = this.getAllFiles(dir);
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
try {
|
|
129
|
+
let content = readFileSync(file, 'utf8');
|
|
130
|
+
let modified = false;
|
|
131
|
+
for (const [placeholder, value] of Object.entries(variables)) {
|
|
132
|
+
if (content.includes(placeholder)) {
|
|
133
|
+
content = content.replace(new RegExp(this.escapeRegExp(placeholder), 'g'), value);
|
|
134
|
+
modified = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (modified) {
|
|
138
|
+
writeFileSync(file, content, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
// Skip binary files or files that can't be read
|
|
142
|
+
if (error.code !== 'EISDIR') {
|
|
143
|
+
// In programmatic usage, we might want to collect warnings
|
|
144
|
+
// For now, we'll silently skip problematic files
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get all files in a directory recursively
|
|
152
|
+
* @param {string} dir - Directory to traverse
|
|
153
|
+
* @returns {string[]} Array of file paths
|
|
154
|
+
*/
|
|
155
|
+
getAllFiles(dir) {
|
|
156
|
+
const files = [];
|
|
157
|
+
function traverse(currentDir) {
|
|
158
|
+
const items = readdirSync(currentDir);
|
|
159
|
+
for (const item of items) {
|
|
160
|
+
const fullPath = join(currentDir, item);
|
|
161
|
+
const stat = statSync(fullPath);
|
|
162
|
+
if (stat.isDirectory()) {
|
|
163
|
+
// Skip node_modules and other unwanted directories
|
|
164
|
+
if (!['node_modules', '.git', 'dist', 'build'].includes(item)) {
|
|
165
|
+
traverse(fullPath);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
files.push(fullPath);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
traverse(dir);
|
|
173
|
+
return files;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Escape special regex characters
|
|
178
|
+
* @param {string} string - String to escape
|
|
179
|
+
* @returns {string} Escaped string
|
|
180
|
+
*/
|
|
181
|
+
escapeRegExp(string) {
|
|
182
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Convert string to title case
|
|
187
|
+
* @param {string} str - String to convert
|
|
188
|
+
* @returns {string} Title case string
|
|
189
|
+
*/
|
|
190
|
+
toTitleCase(str) {
|
|
191
|
+
return str.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get framework version from package.json
|
|
196
|
+
* @returns {string} Framework version
|
|
197
|
+
*/
|
|
198
|
+
getFrameworkVersion() {
|
|
199
|
+
try {
|
|
200
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
201
|
+
return packageJson.version;
|
|
202
|
+
} catch {
|
|
203
|
+
return '1.0.0';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get available service types
|
|
209
|
+
* @returns {string[]} Array of available service types
|
|
210
|
+
*/
|
|
211
|
+
getServiceTypes() {
|
|
212
|
+
return [...this.serviceTypes];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Check if a service type is available
|
|
217
|
+
* @param {string} type - Service type to check
|
|
218
|
+
* @returns {boolean} True if type is available
|
|
219
|
+
*/
|
|
220
|
+
isServiceTypeAvailable(type) {
|
|
221
|
+
return this.serviceTypes.includes(type);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Convenience function for quick service creation
|
|
226
|
+
export async function createService(serviceName, options = {}) {
|
|
227
|
+
const creator = new ServiceCreator();
|
|
228
|
+
return await creator.createService(serviceName, options);
|
|
229
|
+
}
|