genbox 1.0.2 → 1.0.4

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.
@@ -0,0 +1,441 @@
1
+ "use strict";
2
+ /**
3
+ * Docker Compose Parser
4
+ *
5
+ * Parses docker-compose files to extract:
6
+ * - Services (applications, databases, infrastructure)
7
+ * - Port mappings
8
+ * - Environment variables
9
+ * - Dependencies between services
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.ComposeParser = void 0;
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const yaml = __importStar(require("js-yaml"));
49
+ // Known database images
50
+ const DATABASE_PATTERNS = [
51
+ /^mongo(db)?/i,
52
+ /^postgres/i,
53
+ /^mysql/i,
54
+ /^mariadb/i,
55
+ /^cockroach/i,
56
+ /^cassandra/i,
57
+ /^dynamodb/i,
58
+ /^couchdb/i,
59
+ /^neo4j/i,
60
+ /^arangodb/i,
61
+ ];
62
+ // Known cache images
63
+ const CACHE_PATTERNS = [
64
+ /^redis/i,
65
+ /^memcache/i,
66
+ /^hazelcast/i,
67
+ /^keydb/i,
68
+ ];
69
+ // Known queue/message broker images
70
+ const QUEUE_PATTERNS = [
71
+ /^rabbitmq/i,
72
+ /^kafka/i,
73
+ /^nats/i,
74
+ /^pulsar/i,
75
+ /^activemq/i,
76
+ /^zeromq/i,
77
+ ];
78
+ // Known infrastructure images
79
+ const INFRA_PATTERNS = [
80
+ /^nginx/i,
81
+ /^traefik/i,
82
+ /^caddy/i,
83
+ /^haproxy/i,
84
+ /^envoy/i,
85
+ /^mailhog/i,
86
+ /^localstack/i,
87
+ /^minio/i,
88
+ /^elasticsearch/i,
89
+ /^kibana/i,
90
+ /^grafana/i,
91
+ /^prometheus/i,
92
+ /^jaeger/i,
93
+ /^zipkin/i,
94
+ /^vault/i,
95
+ /^consul/i,
96
+ ];
97
+ class ComposeParser {
98
+ /**
99
+ * Parse docker-compose files in the project
100
+ */
101
+ async parse(root) {
102
+ const composeFiles = this.findComposeFiles(root);
103
+ if (composeFiles.length === 0) {
104
+ return null;
105
+ }
106
+ const allServices = [];
107
+ for (const file of composeFiles) {
108
+ try {
109
+ const content = yaml.load(fs.readFileSync(file, 'utf8'));
110
+ if (content && content.services) {
111
+ for (const [name, config] of Object.entries(content.services)) {
112
+ const service = this.normalizeService(name, config, root);
113
+ allServices.push(service);
114
+ }
115
+ }
116
+ }
117
+ catch (err) {
118
+ console.warn(`Warning: Could not parse ${file}:`, err);
119
+ }
120
+ }
121
+ // Categorize services
122
+ const categorized = this.categorizeServices(allServices);
123
+ // Build port map
124
+ const portMap = new Map();
125
+ for (const service of allServices) {
126
+ for (const port of service.ports) {
127
+ portMap.set(port.host, service.name);
128
+ }
129
+ }
130
+ // Build dependency graph
131
+ const dependencyGraph = new Map();
132
+ for (const service of allServices) {
133
+ dependencyGraph.set(service.name, service.dependsOn);
134
+ }
135
+ return {
136
+ files: composeFiles,
137
+ ...categorized,
138
+ portMap,
139
+ dependencyGraph,
140
+ };
141
+ }
142
+ findComposeFiles(root) {
143
+ const files = [];
144
+ const patterns = [
145
+ 'docker-compose.yml',
146
+ 'docker-compose.yaml',
147
+ 'docker-compose.dev.yml',
148
+ 'docker-compose.dev.yaml',
149
+ 'docker-compose.local.yml',
150
+ 'docker-compose.local.yaml',
151
+ 'docker-compose.override.yml',
152
+ 'docker-compose.override.yaml',
153
+ 'docker-compose.secure.yml',
154
+ 'docker-compose.secure.yaml',
155
+ 'compose.yml',
156
+ 'compose.yaml',
157
+ ];
158
+ for (const pattern of patterns) {
159
+ const filePath = path.join(root, pattern);
160
+ if (fs.existsSync(filePath)) {
161
+ files.push(filePath);
162
+ }
163
+ }
164
+ return files;
165
+ }
166
+ normalizeService(name, config, root) {
167
+ return {
168
+ name,
169
+ image: config.image,
170
+ build: this.normalizeBuild(config.build, root),
171
+ ports: this.normalizePorts(config.ports),
172
+ environment: this.normalizeEnvironment(config.environment),
173
+ envFile: this.normalizeEnvFile(config.env_file),
174
+ dependsOn: this.normalizeDependsOn(config.depends_on),
175
+ volumes: this.normalizeVolumes(config.volumes),
176
+ healthcheck: this.normalizeHealthcheck(config.healthcheck),
177
+ command: this.normalizeCommand(config.command),
178
+ labels: this.normalizeLabels(config.labels),
179
+ };
180
+ }
181
+ normalizeBuild(build, root) {
182
+ if (!build)
183
+ return undefined;
184
+ if (typeof build === 'string') {
185
+ return {
186
+ context: path.resolve(root, build),
187
+ };
188
+ }
189
+ if (typeof build === 'object') {
190
+ const b = build;
191
+ return {
192
+ context: path.resolve(root, b.context || '.'),
193
+ dockerfile: b.dockerfile,
194
+ target: b.target,
195
+ };
196
+ }
197
+ return undefined;
198
+ }
199
+ normalizePorts(ports) {
200
+ if (!ports || !Array.isArray(ports))
201
+ return [];
202
+ const mappings = [];
203
+ for (const port of ports) {
204
+ if (typeof port === 'string') {
205
+ // Parse "3000:3000" or "3000:3000/tcp" or "3000"
206
+ const match = port.match(/^(\d+)(?::(\d+))?(?:\/(\w+))?$/);
207
+ if (match) {
208
+ const host = parseInt(match[1], 10);
209
+ const container = match[2] ? parseInt(match[2], 10) : host;
210
+ mappings.push({
211
+ host,
212
+ container,
213
+ protocol: match[3] || 'tcp',
214
+ });
215
+ }
216
+ }
217
+ else if (typeof port === 'object') {
218
+ const p = port;
219
+ if (p.published && p.target) {
220
+ mappings.push({
221
+ host: p.published,
222
+ container: p.target,
223
+ protocol: p.protocol || 'tcp',
224
+ });
225
+ }
226
+ }
227
+ else if (typeof port === 'number') {
228
+ mappings.push({ host: port, container: port });
229
+ }
230
+ }
231
+ return mappings;
232
+ }
233
+ normalizeEnvironment(env) {
234
+ if (!env)
235
+ return {};
236
+ if (Array.isArray(env)) {
237
+ // ["KEY=value", "KEY2=value2"]
238
+ const result = {};
239
+ for (const item of env) {
240
+ if (typeof item === 'string') {
241
+ const [key, ...valueParts] = item.split('=');
242
+ result[key] = valueParts.join('=');
243
+ }
244
+ }
245
+ return result;
246
+ }
247
+ if (typeof env === 'object') {
248
+ // { KEY: "value" }
249
+ return env;
250
+ }
251
+ return {};
252
+ }
253
+ normalizeEnvFile(envFile) {
254
+ if (!envFile)
255
+ return undefined;
256
+ if (typeof envFile === 'string') {
257
+ return [envFile];
258
+ }
259
+ if (Array.isArray(envFile)) {
260
+ return envFile.filter(f => typeof f === 'string');
261
+ }
262
+ return undefined;
263
+ }
264
+ normalizeDependsOn(dependsOn) {
265
+ if (!dependsOn)
266
+ return [];
267
+ if (Array.isArray(dependsOn)) {
268
+ return dependsOn.filter(d => typeof d === 'string');
269
+ }
270
+ if (typeof dependsOn === 'object') {
271
+ // { service_name: { condition: "service_healthy" } }
272
+ return Object.keys(dependsOn);
273
+ }
274
+ return [];
275
+ }
276
+ normalizeVolumes(volumes) {
277
+ if (!volumes || !Array.isArray(volumes))
278
+ return [];
279
+ const mappings = [];
280
+ for (const vol of volumes) {
281
+ if (typeof vol === 'string') {
282
+ // Parse "./data:/data:ro" or "./data:/data"
283
+ const parts = vol.split(':');
284
+ if (parts.length >= 2) {
285
+ mappings.push({
286
+ source: parts[0],
287
+ target: parts[1],
288
+ readOnly: parts[2] === 'ro',
289
+ });
290
+ }
291
+ }
292
+ else if (typeof vol === 'object') {
293
+ const v = vol;
294
+ if (v.source && v.target) {
295
+ mappings.push({
296
+ source: v.source,
297
+ target: v.target,
298
+ readOnly: v.read_only,
299
+ });
300
+ }
301
+ }
302
+ }
303
+ return mappings;
304
+ }
305
+ normalizeHealthcheck(healthcheck) {
306
+ if (!healthcheck || typeof healthcheck !== 'object')
307
+ return undefined;
308
+ const hc = healthcheck;
309
+ let test = '';
310
+ if (Array.isArray(hc.test)) {
311
+ test = hc.test.join(' ');
312
+ }
313
+ else if (typeof hc.test === 'string') {
314
+ test = hc.test;
315
+ }
316
+ return {
317
+ test,
318
+ interval: hc.interval,
319
+ timeout: hc.timeout,
320
+ retries: hc.retries,
321
+ };
322
+ }
323
+ normalizeCommand(command) {
324
+ if (!command)
325
+ return undefined;
326
+ if (typeof command === 'string') {
327
+ return command;
328
+ }
329
+ if (Array.isArray(command)) {
330
+ return command.join(' ');
331
+ }
332
+ return undefined;
333
+ }
334
+ normalizeLabels(labels) {
335
+ if (!labels)
336
+ return undefined;
337
+ if (Array.isArray(labels)) {
338
+ // ["key=value"]
339
+ const result = {};
340
+ for (const item of labels) {
341
+ if (typeof item === 'string') {
342
+ const [key, ...valueParts] = item.split('=');
343
+ result[key] = valueParts.join('=');
344
+ }
345
+ }
346
+ return result;
347
+ }
348
+ if (typeof labels === 'object') {
349
+ return labels;
350
+ }
351
+ return undefined;
352
+ }
353
+ categorizeServices(services) {
354
+ const applications = [];
355
+ const databases = [];
356
+ const caches = [];
357
+ const queues = [];
358
+ const infrastructure = [];
359
+ for (const service of services) {
360
+ const category = this.categorizeService(service);
361
+ switch (category) {
362
+ case 'application':
363
+ applications.push(service);
364
+ break;
365
+ case 'database':
366
+ databases.push(service);
367
+ break;
368
+ case 'cache':
369
+ caches.push(service);
370
+ break;
371
+ case 'queue':
372
+ queues.push(service);
373
+ break;
374
+ case 'infra':
375
+ infrastructure.push(service);
376
+ break;
377
+ }
378
+ }
379
+ return { applications, databases, caches, queues, infrastructure };
380
+ }
381
+ categorizeService(service) {
382
+ // Check labels first (explicit declaration)
383
+ if (service.labels?.['genbox.type']) {
384
+ return service.labels['genbox.type'];
385
+ }
386
+ const image = service.image?.toLowerCase() || '';
387
+ // Check against known patterns
388
+ if (DATABASE_PATTERNS.some(p => p.test(image))) {
389
+ return 'database';
390
+ }
391
+ if (CACHE_PATTERNS.some(p => p.test(image))) {
392
+ return 'cache';
393
+ }
394
+ if (QUEUE_PATTERNS.some(p => p.test(image))) {
395
+ return 'queue';
396
+ }
397
+ if (INFRA_PATTERNS.some(p => p.test(image))) {
398
+ return 'infra';
399
+ }
400
+ // If it has a build context, it's likely an application
401
+ if (service.build) {
402
+ return 'application';
403
+ }
404
+ // Default to infrastructure for other pre-built images
405
+ if (service.image) {
406
+ return 'infra';
407
+ }
408
+ return 'application';
409
+ }
410
+ /**
411
+ * Get suggested genbox service name from compose service
412
+ */
413
+ getGenboxServiceName(service) {
414
+ // Use label if available
415
+ if (service.labels?.['genbox.name']) {
416
+ return service.labels['genbox.name'];
417
+ }
418
+ // Clean up the name
419
+ return service.name
420
+ .replace(/[-_]service$/i, '')
421
+ .replace(/[-_]app$/i, '')
422
+ .replace(/[-_]api$/i, '-api')
423
+ .toLowerCase();
424
+ }
425
+ /**
426
+ * Get the primary port for a service
427
+ */
428
+ getPrimaryPort(service) {
429
+ if (service.ports.length === 0)
430
+ return undefined;
431
+ // Prefer certain ports based on service category
432
+ const category = this.categorizeService(service);
433
+ // For applications, prefer the first exposed port
434
+ if (category === 'application') {
435
+ return service.ports[0].host;
436
+ }
437
+ // For databases/caches, use the container port
438
+ return service.ports[0].container;
439
+ }
440
+ }
441
+ exports.ComposeParser = ComposeParser;