tlc-claude-code 1.2.29 → 1.3.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/dashboard/dist/components/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +146 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- package/package.json +1 -1
- package/server/lib/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -0
- package/server/lib/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -0
- package/server/lib/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -0
- package/server/lib/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SharedKernel,
|
|
4
|
+
createSharedKernel,
|
|
5
|
+
generatePackageJson,
|
|
6
|
+
generateTypes,
|
|
7
|
+
generateContracts,
|
|
8
|
+
generateEvents,
|
|
9
|
+
generateUtils,
|
|
10
|
+
} from './shared-kernel.js';
|
|
11
|
+
|
|
12
|
+
describe('shared-kernel', () => {
|
|
13
|
+
describe('SharedKernel', () => {
|
|
14
|
+
describe('generate', () => {
|
|
15
|
+
it('creates shared directory structure', () => {
|
|
16
|
+
const kernel = new SharedKernel({ projectName: 'myapp' });
|
|
17
|
+
const result = kernel.generate({
|
|
18
|
+
services: ['user', 'order'],
|
|
19
|
+
events: ['UserCreated', 'OrderPlaced'],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(result.directories).toBeDefined();
|
|
23
|
+
expect(result.directories).toContain('shared');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('creates types/, contracts/, events/, utils/ subdirectories', () => {
|
|
27
|
+
const kernel = new SharedKernel({ projectName: 'myapp' });
|
|
28
|
+
const result = kernel.generate({
|
|
29
|
+
services: ['user'],
|
|
30
|
+
events: ['UserCreated'],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(result.directories).toContain('shared/types');
|
|
34
|
+
expect(result.directories).toContain('shared/contracts');
|
|
35
|
+
expect(result.directories).toContain('shared/events');
|
|
36
|
+
expect(result.directories).toContain('shared/utils');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('returns files array with generated content', () => {
|
|
40
|
+
const kernel = new SharedKernel({ projectName: 'myapp' });
|
|
41
|
+
const result = kernel.generate({
|
|
42
|
+
services: ['user'],
|
|
43
|
+
events: ['UserCreated'],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(result.files).toBeDefined();
|
|
47
|
+
expect(Array.isArray(result.files)).toBe(true);
|
|
48
|
+
expect(result.files.length).toBeGreaterThan(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('handles empty services array', () => {
|
|
52
|
+
const kernel = new SharedKernel({ projectName: 'myapp' });
|
|
53
|
+
const result = kernel.generate({
|
|
54
|
+
services: [],
|
|
55
|
+
events: ['SomeEvent'],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(result.directories).toContain('shared');
|
|
59
|
+
expect(result.files).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('handles empty events array', () => {
|
|
63
|
+
const kernel = new SharedKernel({ projectName: 'myapp' });
|
|
64
|
+
const result = kernel.generate({
|
|
65
|
+
services: ['user'],
|
|
66
|
+
events: [],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(result.directories).toContain('shared');
|
|
70
|
+
expect(result.files).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('generatePackageJson', () => {
|
|
76
|
+
it('has correct name format', () => {
|
|
77
|
+
const pkg = generatePackageJson({ projectName: 'myapp' });
|
|
78
|
+
|
|
79
|
+
expect(pkg.name).toBe('@myapp/shared');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('has exports field', () => {
|
|
83
|
+
const pkg = generatePackageJson({ projectName: 'myapp' });
|
|
84
|
+
|
|
85
|
+
expect(pkg.exports).toBeDefined();
|
|
86
|
+
expect(pkg.exports['./types']).toBeDefined();
|
|
87
|
+
expect(pkg.exports['./contracts']).toBeDefined();
|
|
88
|
+
expect(pkg.exports['./events']).toBeDefined();
|
|
89
|
+
expect(pkg.exports['./utils']).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('includes version and description', () => {
|
|
93
|
+
const pkg = generatePackageJson({ projectName: 'myapp' });
|
|
94
|
+
|
|
95
|
+
expect(pkg.version).toBeDefined();
|
|
96
|
+
expect(pkg.description).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('generateTypes', () => {
|
|
101
|
+
it('includes common types', () => {
|
|
102
|
+
const types = generateTypes({ services: ['user'] });
|
|
103
|
+
|
|
104
|
+
expect(types).toContain('ID');
|
|
105
|
+
expect(types).toContain('Timestamp');
|
|
106
|
+
expect(types).toContain('PaginatedResponse');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('includes service-specific types', () => {
|
|
110
|
+
const types = generateTypes({ services: ['user', 'order'] });
|
|
111
|
+
|
|
112
|
+
expect(types).toContain('User');
|
|
113
|
+
expect(types).toContain('Order');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('generates syntactically valid TypeScript', () => {
|
|
117
|
+
const types = generateTypes({ services: ['user'] });
|
|
118
|
+
|
|
119
|
+
// Should have proper export statements
|
|
120
|
+
expect(types).toContain('export');
|
|
121
|
+
// Should have type or interface declarations
|
|
122
|
+
expect(types).toMatch(/export\s+(type|interface)/);
|
|
123
|
+
// Should not have obvious syntax errors (unbalanced braces)
|
|
124
|
+
const openBraces = (types.match(/\{/g) || []).length;
|
|
125
|
+
const closeBraces = (types.match(/\}/g) || []).length;
|
|
126
|
+
expect(openBraces).toBe(closeBraces);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('handles empty services array', () => {
|
|
130
|
+
const types = generateTypes({ services: [] });
|
|
131
|
+
|
|
132
|
+
expect(types).toContain('ID');
|
|
133
|
+
expect(types).toContain('Timestamp');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('generateContracts', () => {
|
|
138
|
+
it('generates valid JSON Schema for services', () => {
|
|
139
|
+
const contracts = generateContracts(['user', 'order']);
|
|
140
|
+
|
|
141
|
+
expect(contracts.user).toBeDefined();
|
|
142
|
+
expect(contracts.order).toBeDefined();
|
|
143
|
+
expect(contracts.user.$schema).toContain('json-schema');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('has request/response schemas', () => {
|
|
147
|
+
const contracts = generateContracts(['user']);
|
|
148
|
+
|
|
149
|
+
expect(contracts.user.definitions).toBeDefined();
|
|
150
|
+
expect(contracts.user.definitions.Request).toBeDefined();
|
|
151
|
+
expect(contracts.user.definitions.Response).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('includes error response schema', () => {
|
|
155
|
+
const contracts = generateContracts(['user']);
|
|
156
|
+
|
|
157
|
+
expect(contracts.user.definitions.ErrorResponse).toBeDefined();
|
|
158
|
+
expect(contracts.user.definitions.ErrorResponse.properties.error).toBeDefined();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('handles empty services array', () => {
|
|
162
|
+
const contracts = generateContracts([]);
|
|
163
|
+
|
|
164
|
+
expect(contracts).toBeDefined();
|
|
165
|
+
expect(Object.keys(contracts).length).toBe(0);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('generateEvents', () => {
|
|
170
|
+
it('includes metadata fields', () => {
|
|
171
|
+
const events = generateEvents(['UserCreated', 'OrderPlaced']);
|
|
172
|
+
|
|
173
|
+
expect(events.base).toBeDefined();
|
|
174
|
+
expect(events.base).toContain('id');
|
|
175
|
+
expect(events.base).toContain('timestamp');
|
|
176
|
+
expect(events.base).toContain('source');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('generates per-event schema with payload types', () => {
|
|
180
|
+
const events = generateEvents(['UserCreated', 'OrderPlaced']);
|
|
181
|
+
|
|
182
|
+
expect(events.schemas.UserCreated).toBeDefined();
|
|
183
|
+
expect(events.schemas.OrderPlaced).toBeDefined();
|
|
184
|
+
expect(events.schemas.UserCreated).toContain('payload');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('generates event catalog listing all events', () => {
|
|
188
|
+
const events = generateEvents(['UserCreated', 'OrderPlaced']);
|
|
189
|
+
|
|
190
|
+
expect(events.catalog).toBeDefined();
|
|
191
|
+
expect(events.catalog).toContain('UserCreated');
|
|
192
|
+
expect(events.catalog).toContain('OrderPlaced');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('handles empty events array', () => {
|
|
196
|
+
const events = generateEvents([]);
|
|
197
|
+
|
|
198
|
+
expect(events.base).toBeDefined();
|
|
199
|
+
expect(events.catalog).toBeDefined();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('generateUtils', () => {
|
|
204
|
+
it('includes logger utility', () => {
|
|
205
|
+
const utils = generateUtils();
|
|
206
|
+
|
|
207
|
+
expect(utils.logger).toBeDefined();
|
|
208
|
+
expect(utils.logger).toContain('log');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('includes error classes', () => {
|
|
212
|
+
const utils = generateUtils();
|
|
213
|
+
|
|
214
|
+
expect(utils.errors).toBeDefined();
|
|
215
|
+
expect(utils.errors).toContain('AppError');
|
|
216
|
+
expect(utils.errors).toContain('ValidationError');
|
|
217
|
+
expect(utils.errors).toContain('NotFoundError');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('error classes extend base AppError', () => {
|
|
221
|
+
const utils = generateUtils();
|
|
222
|
+
|
|
223
|
+
expect(utils.errors).toContain('extends AppError');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('includes validation helpers', () => {
|
|
227
|
+
const utils = generateUtils();
|
|
228
|
+
|
|
229
|
+
expect(utils.validation).toBeDefined();
|
|
230
|
+
expect(utils.validation).toContain('validate');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('createSharedKernel', () => {
|
|
235
|
+
it('creates kernel instance with methods', () => {
|
|
236
|
+
const kernel = createSharedKernel({ projectName: 'myapp' });
|
|
237
|
+
|
|
238
|
+
expect(kernel.generate).toBeDefined();
|
|
239
|
+
expect(typeof kernel.generate).toBe('function');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('uses provided options', () => {
|
|
243
|
+
const kernel = createSharedKernel({ projectName: 'customapp' });
|
|
244
|
+
const result = kernel.generate({
|
|
245
|
+
services: ['user'],
|
|
246
|
+
events: [],
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Find the package.json file in result
|
|
250
|
+
const pkgFile = result.files.find(f => f.path === 'shared/package.json');
|
|
251
|
+
expect(pkgFile).toBeDefined();
|
|
252
|
+
expect(pkgFile.content).toContain('@customapp/shared');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Traefik API Gateway Configuration Generator
|
|
3
|
+
* Generates Traefik configuration files for microservice routing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class TraefikConfig {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate main traefik.yml static configuration
|
|
13
|
+
* @param {Object} config - Configuration options
|
|
14
|
+
* @param {string[]} config.services - List of service names
|
|
15
|
+
* @param {string} config.domain - Domain for routing
|
|
16
|
+
* @param {boolean} config.tls - Enable TLS/HTTPS
|
|
17
|
+
* @returns {string} YAML configuration
|
|
18
|
+
*/
|
|
19
|
+
generateTraefikYml(config) {
|
|
20
|
+
const { services = [], domain = 'localhost', tls = false } = config;
|
|
21
|
+
|
|
22
|
+
const traefikConfig = {
|
|
23
|
+
entryPoints: {
|
|
24
|
+
web: {
|
|
25
|
+
address: ':80',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
api: {
|
|
29
|
+
dashboard: true,
|
|
30
|
+
insecure: true,
|
|
31
|
+
},
|
|
32
|
+
providers: {
|
|
33
|
+
docker: {
|
|
34
|
+
exposedByDefault: false,
|
|
35
|
+
network: 'traefik-net',
|
|
36
|
+
},
|
|
37
|
+
file: {
|
|
38
|
+
directory: '/etc/traefik/dynamic',
|
|
39
|
+
watch: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Add websecure entrypoint if TLS enabled
|
|
45
|
+
if (tls) {
|
|
46
|
+
traefikConfig.entryPoints.websecure = {
|
|
47
|
+
address: ':443',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return this.toYaml(traefikConfig);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate dynamic routing configuration
|
|
56
|
+
* @param {Object} config - Configuration options
|
|
57
|
+
* @param {string[]} config.services - List of service names
|
|
58
|
+
* @param {string} config.domain - Domain for routing
|
|
59
|
+
* @param {boolean} config.tls - Enable TLS/HTTPS
|
|
60
|
+
* @returns {string} YAML configuration
|
|
61
|
+
*/
|
|
62
|
+
generateDynamicConfig(config) {
|
|
63
|
+
const { services = [], domain = 'localhost', tls = false } = config;
|
|
64
|
+
|
|
65
|
+
const dynamicConfig = {
|
|
66
|
+
http: {
|
|
67
|
+
routers: {},
|
|
68
|
+
services: {},
|
|
69
|
+
middlewares: {},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Generate routers and services for each service
|
|
74
|
+
for (const service of services) {
|
|
75
|
+
const serviceName = service.toLowerCase();
|
|
76
|
+
const defaultPort = 3000;
|
|
77
|
+
|
|
78
|
+
// Router
|
|
79
|
+
dynamicConfig.http.routers[`${serviceName}-router`] = {
|
|
80
|
+
rule: `PathPrefix(\`/api/${serviceName}\`)`,
|
|
81
|
+
service: `${serviceName}-service`,
|
|
82
|
+
entryPoints: ['web'],
|
|
83
|
+
middlewares: [`strip-${serviceName}`, 'rate-limit'],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Service
|
|
87
|
+
dynamicConfig.http.services[`${serviceName}-service`] = {
|
|
88
|
+
loadBalancer: {
|
|
89
|
+
servers: [
|
|
90
|
+
{ url: `http://${serviceName}-service:${defaultPort}` },
|
|
91
|
+
],
|
|
92
|
+
healthCheck: {
|
|
93
|
+
path: '/health',
|
|
94
|
+
interval: '10s',
|
|
95
|
+
timeout: '3s',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Strip prefix middleware for this service
|
|
101
|
+
dynamicConfig.http.middlewares[`strip-${serviceName}`] = {
|
|
102
|
+
stripPrefix: {
|
|
103
|
+
prefixes: [`/api/${serviceName}`],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Add common middlewares
|
|
109
|
+
dynamicConfig.http.middlewares['rate-limit'] = {
|
|
110
|
+
rateLimit: {
|
|
111
|
+
average: 100,
|
|
112
|
+
burst: 50,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
dynamicConfig.http.middlewares['secure-headers'] = {
|
|
117
|
+
headers: {
|
|
118
|
+
customResponseHeaders: {
|
|
119
|
+
'X-Frame-Options': 'DENY',
|
|
120
|
+
'X-Content-Type-Options': 'nosniff',
|
|
121
|
+
'X-XSS-Protection': '1; mode=block',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return this.toYaml(dynamicConfig);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate router configuration for a single service
|
|
131
|
+
* @param {string} serviceName - Service name
|
|
132
|
+
* @param {number} port - Service port
|
|
133
|
+
* @returns {string} YAML configuration
|
|
134
|
+
*/
|
|
135
|
+
generateServiceRouter(serviceName, port) {
|
|
136
|
+
const name = serviceName.toLowerCase();
|
|
137
|
+
|
|
138
|
+
const routerConfig = {
|
|
139
|
+
http: {
|
|
140
|
+
routers: {
|
|
141
|
+
[`${name}-router`]: {
|
|
142
|
+
rule: `PathPrefix(\`/api/${name}\`)`,
|
|
143
|
+
service: `${name}-service`,
|
|
144
|
+
entryPoints: ['web'],
|
|
145
|
+
middlewares: [`strip-${name}`],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
services: {
|
|
149
|
+
[`${name}-service`]: {
|
|
150
|
+
loadBalancer: {
|
|
151
|
+
servers: [
|
|
152
|
+
{ url: `http://${name}-service:${port}` },
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
middlewares: {
|
|
158
|
+
[`strip-${name}`]: {
|
|
159
|
+
stripPrefix: {
|
|
160
|
+
prefixes: [`/api/${name}`],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return this.toYaml(routerConfig);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate common middlewares configuration
|
|
172
|
+
* @returns {string} YAML configuration
|
|
173
|
+
*/
|
|
174
|
+
generateMiddlewares() {
|
|
175
|
+
const middlewaresConfig = {
|
|
176
|
+
http: {
|
|
177
|
+
middlewares: {
|
|
178
|
+
'rate-limit': {
|
|
179
|
+
rateLimit: {
|
|
180
|
+
average: 100,
|
|
181
|
+
burst: 50,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
'strip-api': {
|
|
185
|
+
stripPrefix: {
|
|
186
|
+
prefixes: ['/api'],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
'secure-headers': {
|
|
190
|
+
headers: {
|
|
191
|
+
customResponseHeaders: {
|
|
192
|
+
'X-Frame-Options': 'DENY',
|
|
193
|
+
'X-Content-Type-Options': 'nosniff',
|
|
194
|
+
'X-XSS-Protection': '1; mode=block',
|
|
195
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return this.toYaml(middlewaresConfig);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Generate TLS configuration with Let's Encrypt
|
|
208
|
+
* @param {string} domain - Domain for certificate
|
|
209
|
+
* @returns {string} YAML configuration
|
|
210
|
+
*/
|
|
211
|
+
generateTlsConfig(domain) {
|
|
212
|
+
const tlsConfig = {
|
|
213
|
+
certificatesResolvers: {
|
|
214
|
+
letsencrypt: {
|
|
215
|
+
acme: {
|
|
216
|
+
email: `admin@${domain}`,
|
|
217
|
+
storage: '/etc/traefik/acme.json',
|
|
218
|
+
httpChallenge: {
|
|
219
|
+
entryPoint: 'web',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
tls: {
|
|
225
|
+
options: {
|
|
226
|
+
default: {
|
|
227
|
+
minVersion: 'VersionTLS12',
|
|
228
|
+
sniStrict: true,
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
certificates: [
|
|
232
|
+
{
|
|
233
|
+
certFile: `/etc/traefik/certs/${domain}.crt`,
|
|
234
|
+
keyFile: `/etc/traefik/certs/${domain}.key`,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return this.toYaml(tlsConfig);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Simple YAML serializer
|
|
245
|
+
* @param {Object} obj - Object to serialize
|
|
246
|
+
* @param {number} indent - Current indentation level
|
|
247
|
+
* @returns {string} YAML string
|
|
248
|
+
*/
|
|
249
|
+
toYaml(obj, indent = 0) {
|
|
250
|
+
const spaces = ' '.repeat(indent);
|
|
251
|
+
let yaml = '';
|
|
252
|
+
|
|
253
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
254
|
+
if (value === null || value === undefined) continue;
|
|
255
|
+
|
|
256
|
+
if (Array.isArray(value)) {
|
|
257
|
+
yaml += `${spaces}${key}:\n`;
|
|
258
|
+
for (const item of value) {
|
|
259
|
+
if (typeof item === 'object') {
|
|
260
|
+
// For array of objects, use dash notation
|
|
261
|
+
const itemYaml = this.toYaml(item, 0).trim().split('\n');
|
|
262
|
+
yaml += `${spaces} - ${itemYaml[0]}\n`;
|
|
263
|
+
for (let i = 1; i < itemYaml.length; i++) {
|
|
264
|
+
yaml += `${spaces} ${itemYaml[i]}\n`;
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
yaml += `${spaces} - ${item}\n`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} else if (typeof value === 'object') {
|
|
271
|
+
yaml += `${spaces}${key}:\n`;
|
|
272
|
+
yaml += this.toYaml(value, indent + 1);
|
|
273
|
+
} else {
|
|
274
|
+
yaml += `${spaces}${key}: ${value}\n`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return yaml;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = { TraefikConfig };
|