genbox 1.0.40 → 1.0.42
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/dist/commands/create.js +2 -0
- package/dist/commands/init.js +27 -16
- package/dist/commands/scan.js +141 -11
- package/dist/profile-resolver.js +4 -0
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -771,6 +771,8 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
771
771
|
framework: a.framework,
|
|
772
772
|
runner: a.runner,
|
|
773
773
|
docker: a.docker,
|
|
774
|
+
healthcheck: a.healthcheck,
|
|
775
|
+
dependsOn: a.dependsOn,
|
|
774
776
|
commands: a.commands,
|
|
775
777
|
})),
|
|
776
778
|
infrastructure: resolved.infrastructure.map(i => ({
|
package/dist/commands/init.js
CHANGED
|
@@ -583,8 +583,15 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
583
583
|
console.log(` ... and ${scan.apps.length - 5} more`);
|
|
584
584
|
}
|
|
585
585
|
}
|
|
586
|
-
|
|
587
|
-
|
|
586
|
+
// Show Docker app count (apps with runner: 'docker')
|
|
587
|
+
const dockerApps = scan.apps.filter(a => a.runner === 'docker');
|
|
588
|
+
if (dockerApps.length > 0) {
|
|
589
|
+
console.log(` ${chalk_1.default.dim('Docker:')} ${dockerApps.length} app(s) with docker runner`);
|
|
590
|
+
}
|
|
591
|
+
// Show infrastructure services from docker-compose (databases, caches, etc.)
|
|
592
|
+
if (scan.compose && scan.compose.databases.length + scan.compose.caches.length + scan.compose.queues.length > 0) {
|
|
593
|
+
const infraCount = scan.compose.databases.length + scan.compose.caches.length + scan.compose.queues.length;
|
|
594
|
+
console.log(` ${chalk_1.default.dim('Infra:')} ${infraCount} infrastructure service(s)`);
|
|
588
595
|
}
|
|
589
596
|
if (scan.git) {
|
|
590
597
|
console.log(` ${chalk_1.default.dim('Git:')} ${scan.git.remote} (${scan.git.type})`);
|
|
@@ -1886,24 +1893,28 @@ function convertDetectedToScan(detected) {
|
|
|
1886
1893
|
lockfile: r.lockfile,
|
|
1887
1894
|
}));
|
|
1888
1895
|
// Convert infrastructure to compose analysis
|
|
1896
|
+
// Note: docker_services is deprecated - apps with runner: 'docker' are now tracked per-app
|
|
1889
1897
|
let compose = null;
|
|
1890
1898
|
const hasInfra = detected.infrastructure && detected.infrastructure.length > 0;
|
|
1891
|
-
|
|
1892
|
-
|
|
1899
|
+
// Get docker apps from apps with runner: 'docker' (new approach)
|
|
1900
|
+
const dockerApps = Object.entries(detected.apps || {})
|
|
1901
|
+
.filter(([, app]) => app.runner === 'docker')
|
|
1902
|
+
.map(([name, app]) => ({
|
|
1903
|
+
name,
|
|
1904
|
+
image: app.docker?.service || name,
|
|
1905
|
+
build: app.docker?.build_context ? {
|
|
1906
|
+
context: app.docker.build_context,
|
|
1907
|
+
dockerfile: app.docker.dockerfile,
|
|
1908
|
+
} : undefined,
|
|
1909
|
+
ports: app.port ? [{ host: app.port, container: app.port }] : [],
|
|
1910
|
+
environment: {},
|
|
1911
|
+
dependsOn: app.depends_on || [],
|
|
1912
|
+
volumes: [],
|
|
1913
|
+
}));
|
|
1914
|
+
if (hasInfra || dockerApps.length > 0) {
|
|
1893
1915
|
compose = {
|
|
1894
1916
|
files: ['docker-compose.yml'],
|
|
1895
|
-
applications:
|
|
1896
|
-
name: svc.name,
|
|
1897
|
-
image: svc.image,
|
|
1898
|
-
build: svc.build_context ? {
|
|
1899
|
-
context: svc.build_context,
|
|
1900
|
-
dockerfile: svc.dockerfile,
|
|
1901
|
-
} : undefined,
|
|
1902
|
-
ports: svc.port ? [{ host: svc.port, container: svc.port }] : [],
|
|
1903
|
-
environment: {},
|
|
1904
|
-
dependsOn: svc.depends_on || [],
|
|
1905
|
-
volumes: [],
|
|
1906
|
-
})),
|
|
1917
|
+
applications: dockerApps,
|
|
1907
1918
|
databases: (detected.infrastructure || [])
|
|
1908
1919
|
.filter(i => i.type === 'database')
|
|
1909
1920
|
.map(i => ({
|
package/dist/commands/scan.js
CHANGED
|
@@ -330,15 +330,105 @@ async function interactiveEditMode(detected) {
|
|
|
330
330
|
choices: appChoices,
|
|
331
331
|
});
|
|
332
332
|
// Edit each selected app
|
|
333
|
+
// Pass the scanned root dir for docker-compose lookup
|
|
334
|
+
const rootDir = detected._meta.scanned_root;
|
|
333
335
|
for (const appName of appsToEdit) {
|
|
334
|
-
result.apps[appName] = await editAppConfig(appName, result.apps[appName]);
|
|
336
|
+
result.apps[appName] = await editAppConfig(appName, result.apps[appName], rootDir);
|
|
335
337
|
}
|
|
336
338
|
return result;
|
|
337
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Look up service info from docker-compose.yml
|
|
342
|
+
*/
|
|
343
|
+
function getDockerComposeServiceInfo(rootDir, serviceName) {
|
|
344
|
+
const composeFiles = ['docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml'];
|
|
345
|
+
for (const filename of composeFiles) {
|
|
346
|
+
const composePath = path.join(rootDir, filename);
|
|
347
|
+
if (fs.existsSync(composePath)) {
|
|
348
|
+
try {
|
|
349
|
+
const content = fs.readFileSync(composePath, 'utf8');
|
|
350
|
+
const compose = yaml.load(content);
|
|
351
|
+
if (compose?.services?.[serviceName]) {
|
|
352
|
+
const service = compose.services[serviceName];
|
|
353
|
+
const info = {};
|
|
354
|
+
// Parse port
|
|
355
|
+
if (service.ports && service.ports.length > 0) {
|
|
356
|
+
const portDef = service.ports[0];
|
|
357
|
+
if (typeof portDef === 'string') {
|
|
358
|
+
const parts = portDef.split(':');
|
|
359
|
+
info.port = parseInt(parts[0], 10);
|
|
360
|
+
}
|
|
361
|
+
else if (typeof portDef === 'number') {
|
|
362
|
+
info.port = portDef;
|
|
363
|
+
}
|
|
364
|
+
else if (portDef.published) {
|
|
365
|
+
info.port = portDef.published;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Parse build context and dockerfile
|
|
369
|
+
if (service.build) {
|
|
370
|
+
if (typeof service.build === 'string') {
|
|
371
|
+
info.buildContext = service.build;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
info.buildContext = service.build.context;
|
|
375
|
+
info.dockerfile = service.build.dockerfile;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Parse healthcheck
|
|
379
|
+
if (service.healthcheck?.test) {
|
|
380
|
+
const test = service.healthcheck.test;
|
|
381
|
+
if (Array.isArray(test)) {
|
|
382
|
+
// ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
|
383
|
+
const cmdIndex = test.indexOf('CMD') + 1 || test.indexOf('CMD-SHELL') + 1;
|
|
384
|
+
if (cmdIndex > 0) {
|
|
385
|
+
const healthCmd = test.slice(cmdIndex).join(' ');
|
|
386
|
+
// Extract health endpoint from curl command
|
|
387
|
+
const urlMatch = healthCmd.match(/https?:\/\/[^/]+(\S+)/);
|
|
388
|
+
if (urlMatch) {
|
|
389
|
+
info.healthcheck = urlMatch[1];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (typeof test === 'string') {
|
|
394
|
+
const urlMatch = test.match(/https?:\/\/[^/]+(\S+)/);
|
|
395
|
+
if (urlMatch) {
|
|
396
|
+
info.healthcheck = urlMatch[1];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Parse depends_on
|
|
401
|
+
if (service.depends_on) {
|
|
402
|
+
if (Array.isArray(service.depends_on)) {
|
|
403
|
+
info.dependsOn = service.depends_on;
|
|
404
|
+
}
|
|
405
|
+
else if (typeof service.depends_on === 'object') {
|
|
406
|
+
info.dependsOn = Object.keys(service.depends_on);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// Parse environment variables (names only)
|
|
410
|
+
if (service.environment) {
|
|
411
|
+
if (Array.isArray(service.environment)) {
|
|
412
|
+
info.envVars = service.environment.map((e) => e.split('=')[0]);
|
|
413
|
+
}
|
|
414
|
+
else if (typeof service.environment === 'object') {
|
|
415
|
+
info.envVars = Object.keys(service.environment);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return info;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
// Ignore parse errors
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
338
428
|
/**
|
|
339
429
|
* Edit a single app's configuration
|
|
340
430
|
*/
|
|
341
|
-
async function editAppConfig(name, app) {
|
|
431
|
+
async function editAppConfig(name, app, rootDir) {
|
|
342
432
|
console.log('');
|
|
343
433
|
console.log(chalk_1.default.blue(`=== Editing: ${name} ===`));
|
|
344
434
|
// Show current values
|
|
@@ -383,38 +473,78 @@ async function editAppConfig(name, app) {
|
|
|
383
473
|
result.runner = newRunner;
|
|
384
474
|
result.runner_reason = 'manually set';
|
|
385
475
|
}
|
|
386
|
-
// If runner is docker, ask for docker config
|
|
476
|
+
// If runner is docker, ask for docker config and auto-detect from docker-compose
|
|
387
477
|
if (newRunner === 'docker') {
|
|
478
|
+
// First ask for service name to look up in docker-compose
|
|
388
479
|
const dockerService = await prompts.input({
|
|
389
480
|
message: 'Docker service name:',
|
|
390
481
|
default: app.docker?.service || name,
|
|
391
482
|
});
|
|
483
|
+
// Look up service info from docker-compose.yml
|
|
484
|
+
const composeInfo = getDockerComposeServiceInfo(rootDir, dockerService);
|
|
485
|
+
if (composeInfo) {
|
|
486
|
+
console.log(chalk_1.default.blue('\n Detected from docker-compose.yml:'));
|
|
487
|
+
if (composeInfo.buildContext)
|
|
488
|
+
console.log(chalk_1.default.dim(` build context: ${composeInfo.buildContext}`));
|
|
489
|
+
if (composeInfo.dockerfile)
|
|
490
|
+
console.log(chalk_1.default.dim(` dockerfile: ${composeInfo.dockerfile}`));
|
|
491
|
+
if (composeInfo.port)
|
|
492
|
+
console.log(chalk_1.default.dim(` port: ${composeInfo.port}`));
|
|
493
|
+
if (composeInfo.healthcheck)
|
|
494
|
+
console.log(chalk_1.default.dim(` healthcheck: ${composeInfo.healthcheck}`));
|
|
495
|
+
if (composeInfo.dependsOn?.length)
|
|
496
|
+
console.log(chalk_1.default.dim(` depends_on: ${composeInfo.dependsOn.join(', ')}`));
|
|
497
|
+
if (composeInfo.envVars?.length)
|
|
498
|
+
console.log(chalk_1.default.dim(` env vars: ${composeInfo.envVars.slice(0, 5).join(', ')}${composeInfo.envVars.length > 5 ? ` +${composeInfo.envVars.length - 5} more` : ''}`));
|
|
499
|
+
console.log('');
|
|
500
|
+
}
|
|
501
|
+
// Use detected values as defaults
|
|
502
|
+
const defaultContext = composeInfo?.buildContext || app.docker?.build_context || app.path || '.';
|
|
392
503
|
const dockerContext = await prompts.input({
|
|
393
504
|
message: 'Docker build context:',
|
|
394
|
-
default:
|
|
505
|
+
default: defaultContext,
|
|
395
506
|
});
|
|
507
|
+
const defaultDockerfile = composeInfo?.dockerfile || app.docker?.dockerfile;
|
|
508
|
+
const dockerfile = defaultDockerfile ? await prompts.input({
|
|
509
|
+
message: 'Dockerfile:',
|
|
510
|
+
default: defaultDockerfile,
|
|
511
|
+
}) : undefined;
|
|
396
512
|
result.docker = {
|
|
397
513
|
service: dockerService,
|
|
398
514
|
build_context: dockerContext,
|
|
399
|
-
dockerfile: app.docker?.dockerfile,
|
|
515
|
+
dockerfile: dockerfile || app.docker?.dockerfile,
|
|
400
516
|
};
|
|
517
|
+
// Auto-set values from docker-compose
|
|
518
|
+
if (composeInfo?.port) {
|
|
519
|
+
result.port = composeInfo.port;
|
|
520
|
+
result.port_source = 'docker-compose.yml';
|
|
521
|
+
}
|
|
522
|
+
if (composeInfo?.healthcheck) {
|
|
523
|
+
result.healthcheck = composeInfo.healthcheck;
|
|
524
|
+
}
|
|
525
|
+
if (composeInfo?.dependsOn?.length) {
|
|
526
|
+
result.depends_on = composeInfo.dependsOn;
|
|
527
|
+
}
|
|
401
528
|
}
|
|
402
|
-
// Edit port (only if not a library)
|
|
529
|
+
// Edit port (only if not a library and not already set by docker-compose)
|
|
403
530
|
if (newRunner !== 'none' && result.type !== 'library') {
|
|
531
|
+
// For docker runner, only ask if port wasn't auto-detected
|
|
532
|
+
const currentPort = result.port;
|
|
533
|
+
const portDefault = currentPort ? String(currentPort) : '';
|
|
404
534
|
const portInput = await prompts.input({
|
|
405
|
-
message: 'Port (leave empty to skip):',
|
|
406
|
-
default:
|
|
535
|
+
message: currentPort ? `Port (detected: ${currentPort}, press Enter to keep):` : 'Port (leave empty to skip):',
|
|
536
|
+
default: portDefault,
|
|
407
537
|
});
|
|
408
538
|
if (portInput) {
|
|
409
539
|
const portNum = parseInt(portInput, 10);
|
|
410
540
|
if (!isNaN(portNum) && portNum > 0 && portNum < 65536) {
|
|
411
|
-
|
|
412
|
-
|
|
541
|
+
if (portNum !== currentPort) {
|
|
542
|
+
result.port = portNum;
|
|
413
543
|
result.port_source = 'manually set';
|
|
414
544
|
}
|
|
415
545
|
}
|
|
416
546
|
}
|
|
417
|
-
else {
|
|
547
|
+
else if (!currentPort) {
|
|
418
548
|
result.port = undefined;
|
|
419
549
|
result.port_source = undefined;
|
|
420
550
|
}
|
package/dist/profile-resolver.js
CHANGED
|
@@ -242,6 +242,8 @@ class ProfileResolver {
|
|
|
242
242
|
const v4Config = appConfig;
|
|
243
243
|
const runner = v4Config.runner;
|
|
244
244
|
const docker = v4Config.docker;
|
|
245
|
+
const healthcheck = v4Config.healthcheck;
|
|
246
|
+
const dependsOn = v4Config.depends_on;
|
|
245
247
|
return {
|
|
246
248
|
name: appName,
|
|
247
249
|
path: appConfig.path,
|
|
@@ -253,6 +255,8 @@ class ProfileResolver {
|
|
|
253
255
|
env: appConfig.env,
|
|
254
256
|
runner,
|
|
255
257
|
docker,
|
|
258
|
+
healthcheck,
|
|
259
|
+
dependsOn,
|
|
256
260
|
dependencies,
|
|
257
261
|
};
|
|
258
262
|
}
|