genbox 1.0.11 ā 1.0.13
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/config.js +273 -0
- package/dist/commands/create.js +139 -9
- package/dist/commands/init.js +154 -84
- package/dist/commands/migrate.js +246 -0
- package/dist/commands/resolve.js +243 -0
- package/dist/commands/scan.js +354 -0
- package/dist/commands/validate.js +529 -0
- package/dist/config-explainer.js +379 -0
- package/dist/detected-config.js +145 -0
- package/dist/index.js +12 -1
- package/dist/migration.js +334 -0
- package/dist/profile-resolver.js +8 -3
- package/dist/schema-v3.js +36 -0
- package/dist/schema-v4.js +54 -0
- package/dist/strict-mode.js +288 -0
- package/package.json +1 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve Command
|
|
4
|
+
*
|
|
5
|
+
* Computes the final configuration by:
|
|
6
|
+
* 1. Loading genbox.yaml
|
|
7
|
+
* 2. Applying profile settings
|
|
8
|
+
* 3. Resolving $detect markers from detected.yaml
|
|
9
|
+
* 4. Computing environment variables
|
|
10
|
+
* 5. Outputting to .genbox/resolved.yaml
|
|
11
|
+
*
|
|
12
|
+
* This provides transparency into what will actually be created.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* genbox resolve # Resolve with defaults
|
|
16
|
+
* genbox resolve --profile quick # Resolve with specific profile
|
|
17
|
+
* genbox resolve --stdout # Output to stdout
|
|
18
|
+
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
52
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
53
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
54
|
+
};
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.resolveCommand = void 0;
|
|
57
|
+
const commander_1 = require("commander");
|
|
58
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
59
|
+
const fs = __importStar(require("fs"));
|
|
60
|
+
const path = __importStar(require("path"));
|
|
61
|
+
const yaml = __importStar(require("js-yaml"));
|
|
62
|
+
const config_loader_1 = require("../config-loader");
|
|
63
|
+
const profile_resolver_1 = require("../profile-resolver");
|
|
64
|
+
const detected_config_1 = require("../detected-config");
|
|
65
|
+
exports.resolveCommand = new commander_1.Command('resolve')
|
|
66
|
+
.description('Compute final configuration from genbox.yaml and detected values')
|
|
67
|
+
.option('-p, --profile <profile>', 'Profile to use')
|
|
68
|
+
.option('-a, --apps <apps>', 'Comma-separated apps to include')
|
|
69
|
+
.option('--stdout', 'Output to stdout instead of file')
|
|
70
|
+
.option('--json', 'Output as JSON')
|
|
71
|
+
.option('-y, --yes', 'Skip interactive prompts')
|
|
72
|
+
.action(async (options) => {
|
|
73
|
+
const cwd = process.cwd();
|
|
74
|
+
console.log(chalk_1.default.cyan('\nš Resolving configuration...\n'));
|
|
75
|
+
try {
|
|
76
|
+
// Load configuration
|
|
77
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
78
|
+
const loadResult = await configLoader.load(cwd);
|
|
79
|
+
if (!loadResult.found || !loadResult.config) {
|
|
80
|
+
console.log(chalk_1.default.red('No genbox.yaml found. Run "genbox init" first.'));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
let config = loadResult.config;
|
|
84
|
+
// Load detected config if available
|
|
85
|
+
const detectedPath = path.join(loadResult.root, '.genbox', 'detected.yaml');
|
|
86
|
+
let detectedConfig = null;
|
|
87
|
+
let appliedDetections = [];
|
|
88
|
+
if (fs.existsSync(detectedPath)) {
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(detectedPath, 'utf8');
|
|
91
|
+
detectedConfig = yaml.load(content);
|
|
92
|
+
console.log(chalk_1.default.dim(`Loaded detected.yaml from ${detectedPath}`));
|
|
93
|
+
// Apply $detect markers
|
|
94
|
+
const result = (0, detected_config_1.applyDetectedValues)(config, detectedConfig);
|
|
95
|
+
config = result.config;
|
|
96
|
+
appliedDetections = result.applied;
|
|
97
|
+
if (appliedDetections.length > 0) {
|
|
98
|
+
console.log(chalk_1.default.dim(`Applied ${appliedDetections.length} $detect markers`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.log(chalk_1.default.yellow(`Warning: Could not parse detected.yaml: ${err}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Show resolution steps
|
|
106
|
+
console.log(chalk_1.default.bold('Resolution steps:'));
|
|
107
|
+
console.log(` 1. ${chalk_1.default.green('ā')} Loaded genbox.yaml`);
|
|
108
|
+
if (options.profile) {
|
|
109
|
+
const profile = configLoader.getProfile(config, options.profile);
|
|
110
|
+
if (profile) {
|
|
111
|
+
console.log(` 2. ${chalk_1.default.green('ā')} Applied profile '${options.profile}'`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log(` 2. ${chalk_1.default.yellow('ā ')} Profile '${options.profile}' not found`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (appliedDetections.length > 0) {
|
|
118
|
+
console.log(` 3. ${chalk_1.default.green('ā')} Resolved $detect markers:`);
|
|
119
|
+
for (const path of appliedDetections.slice(0, 5)) {
|
|
120
|
+
console.log(chalk_1.default.dim(` ā ${path}`));
|
|
121
|
+
}
|
|
122
|
+
if (appliedDetections.length > 5) {
|
|
123
|
+
console.log(chalk_1.default.dim(` ... and ${appliedDetections.length - 5} more`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Resolve using ProfileResolver
|
|
127
|
+
const profileResolver = new profile_resolver_1.ProfileResolver(configLoader);
|
|
128
|
+
const resolved = await profileResolver.resolve(config, {
|
|
129
|
+
name: 'resolved',
|
|
130
|
+
profile: options.profile,
|
|
131
|
+
apps: options.apps?.split(',').map(s => s.trim()),
|
|
132
|
+
yes: options.yes ?? true, // Default to non-interactive for resolve
|
|
133
|
+
});
|
|
134
|
+
// Add metadata
|
|
135
|
+
const resolvedWithMeta = {
|
|
136
|
+
_meta: {
|
|
137
|
+
resolved_at: new Date().toISOString(),
|
|
138
|
+
profile: options.profile || null,
|
|
139
|
+
detect_markers_applied: appliedDetections,
|
|
140
|
+
},
|
|
141
|
+
...resolved,
|
|
142
|
+
};
|
|
143
|
+
// Output
|
|
144
|
+
if (options.stdout) {
|
|
145
|
+
const output = options.json
|
|
146
|
+
? JSON.stringify(resolvedWithMeta, null, 2)
|
|
147
|
+
: yaml.dump(resolvedWithMeta, { lineWidth: 120, noRefs: true });
|
|
148
|
+
console.log('\n' + output);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
await writeResolvedConfig(resolvedWithMeta, loadResult.root, options.json);
|
|
152
|
+
showResolvedSummary(resolved, appliedDetections);
|
|
153
|
+
}
|
|
154
|
+
// Show warnings
|
|
155
|
+
if (resolved.warnings.length > 0) {
|
|
156
|
+
console.log(chalk_1.default.yellow('\nā ļø Warnings:'));
|
|
157
|
+
for (const warning of resolved.warnings) {
|
|
158
|
+
console.log(chalk_1.default.yellow(` ⢠${warning}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
console.log(chalk_1.default.bold('\nš Next steps:\n'));
|
|
162
|
+
console.log(' 1. Review the resolved configuration in .genbox/resolved.yaml');
|
|
163
|
+
console.log(' 2. Run ' + chalk_1.default.cyan('genbox validate') + ' to check for errors');
|
|
164
|
+
console.log(' 3. Run ' + chalk_1.default.cyan('genbox create <name>') + ' to provision');
|
|
165
|
+
console.log();
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error(chalk_1.default.red('Resolution failed:'), error);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
async function writeResolvedConfig(resolved, root, asJson) {
|
|
173
|
+
const genboxDir = path.join(root, '.genbox');
|
|
174
|
+
// Create .genbox directory if it doesn't exist
|
|
175
|
+
if (!fs.existsSync(genboxDir)) {
|
|
176
|
+
fs.mkdirSync(genboxDir, { recursive: true });
|
|
177
|
+
}
|
|
178
|
+
// Write resolved config
|
|
179
|
+
const filename = asJson ? 'resolved.json' : 'resolved.yaml';
|
|
180
|
+
const filePath = path.join(genboxDir, filename);
|
|
181
|
+
const content = asJson
|
|
182
|
+
? JSON.stringify(resolved, null, 2)
|
|
183
|
+
: yaml.dump(resolved, { lineWidth: 120, noRefs: true });
|
|
184
|
+
fs.writeFileSync(filePath, content);
|
|
185
|
+
console.log(chalk_1.default.green(`\nā Resolved configuration written to: ${filePath}`));
|
|
186
|
+
}
|
|
187
|
+
function showResolvedSummary(resolved, appliedDetections) {
|
|
188
|
+
console.log(chalk_1.default.bold('\nš Resolved Configuration:\n'));
|
|
189
|
+
// Basic info
|
|
190
|
+
console.log(` Name: ${chalk_1.default.cyan(resolved.name)}`);
|
|
191
|
+
console.log(` Size: ${chalk_1.default.cyan(resolved.size)}`);
|
|
192
|
+
console.log(` Project: ${resolved.project.name} (${resolved.project.structure})`);
|
|
193
|
+
// Apps
|
|
194
|
+
if (resolved.apps.length > 0) {
|
|
195
|
+
console.log(`\n Apps (${resolved.apps.length}):`);
|
|
196
|
+
for (const app of resolved.apps) {
|
|
197
|
+
const deps = Object.entries(app.dependencies)
|
|
198
|
+
.map(([name, config]) => `${name}:${config.mode}`)
|
|
199
|
+
.join(', ');
|
|
200
|
+
console.log(` ${chalk_1.default.cyan(app.name)}: ${app.type}${app.framework ? ` [${app.framework}]` : ''}`);
|
|
201
|
+
if (deps) {
|
|
202
|
+
console.log(chalk_1.default.dim(` Dependencies: ${deps}`));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Infrastructure
|
|
207
|
+
if (resolved.infrastructure.length > 0) {
|
|
208
|
+
console.log(`\n Infrastructure (${resolved.infrastructure.length}):`);
|
|
209
|
+
for (const infra of resolved.infrastructure) {
|
|
210
|
+
console.log(` ${chalk_1.default.cyan(infra.name)}: ${infra.type} (${infra.mode})`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Database
|
|
214
|
+
console.log(`\n Database: ${chalk_1.default.cyan(resolved.database.mode)}`);
|
|
215
|
+
if (resolved.database.source) {
|
|
216
|
+
console.log(chalk_1.default.dim(` Source: ${resolved.database.source}`));
|
|
217
|
+
}
|
|
218
|
+
// Repos
|
|
219
|
+
if (resolved.repos.length > 0) {
|
|
220
|
+
console.log(`\n Repositories (${resolved.repos.length}):`);
|
|
221
|
+
for (const repo of resolved.repos) {
|
|
222
|
+
console.log(` ${chalk_1.default.cyan(repo.name)}: ${repo.url}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Environment variables
|
|
226
|
+
const envCount = Object.keys(resolved.env).length;
|
|
227
|
+
if (envCount > 0) {
|
|
228
|
+
console.log(`\n Environment variables: ${envCount}`);
|
|
229
|
+
for (const [key, value] of Object.entries(resolved.env).slice(0, 5)) {
|
|
230
|
+
const displayValue = key.includes('SECRET') || key.includes('PASSWORD')
|
|
231
|
+
? '***'
|
|
232
|
+
: value.length > 50 ? value.slice(0, 47) + '...' : value;
|
|
233
|
+
console.log(chalk_1.default.dim(` ${key}=${displayValue}`));
|
|
234
|
+
}
|
|
235
|
+
if (envCount > 5) {
|
|
236
|
+
console.log(chalk_1.default.dim(` ... and ${envCount - 5} more`));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Detection info
|
|
240
|
+
if (appliedDetections.length > 0) {
|
|
241
|
+
console.log(chalk_1.default.yellow(`\n ā” ${appliedDetections.length} values from $detect markers`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Scan Command
|
|
4
|
+
*
|
|
5
|
+
* Analyzes the project structure and outputs detected configuration
|
|
6
|
+
* to .genbox/detected.yaml (or stdout with --stdout flag)
|
|
7
|
+
*
|
|
8
|
+
* This separates detection from configuration - users can review
|
|
9
|
+
* what was detected before using it.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* genbox scan # Output to .genbox/detected.yaml
|
|
13
|
+
* genbox scan --stdout # Output to stdout
|
|
14
|
+
* genbox scan --json # Output as JSON
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.scanCommand = void 0;
|
|
54
|
+
const commander_1 = require("commander");
|
|
55
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
56
|
+
const fs = __importStar(require("fs"));
|
|
57
|
+
const path = __importStar(require("path"));
|
|
58
|
+
const yaml = __importStar(require("js-yaml"));
|
|
59
|
+
const scanner_1 = require("../scanner");
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
61
|
+
const { version } = require('../../package.json');
|
|
62
|
+
exports.scanCommand = new commander_1.Command('scan')
|
|
63
|
+
.description('Analyze project structure and output detected configuration')
|
|
64
|
+
.option('--stdout', 'Output to stdout instead of .genbox/detected.yaml')
|
|
65
|
+
.option('--json', 'Output as JSON instead of YAML')
|
|
66
|
+
.option('--no-infra', 'Skip infrastructure detection (docker-compose)')
|
|
67
|
+
.option('--no-scripts', 'Skip script detection')
|
|
68
|
+
.option('-e, --exclude <patterns>', 'Comma-separated patterns to exclude', '')
|
|
69
|
+
.action(async (options) => {
|
|
70
|
+
const cwd = process.cwd();
|
|
71
|
+
console.log(chalk_1.default.cyan('\nš Scanning project...\n'));
|
|
72
|
+
try {
|
|
73
|
+
// Run the scanner
|
|
74
|
+
const scanner = new scanner_1.ProjectScanner();
|
|
75
|
+
const exclude = options.exclude ? options.exclude.split(',').map(s => s.trim()) : [];
|
|
76
|
+
const scan = await scanner.scan(cwd, {
|
|
77
|
+
exclude,
|
|
78
|
+
skipScripts: !options.scripts,
|
|
79
|
+
});
|
|
80
|
+
// Convert scan result to DetectedConfig format
|
|
81
|
+
const detected = convertScanToDetected(scan, cwd);
|
|
82
|
+
// Output
|
|
83
|
+
if (options.stdout) {
|
|
84
|
+
outputToStdout(detected, options.json);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
await outputToFile(detected, cwd, options.json);
|
|
88
|
+
showSummary(detected);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(chalk_1.default.red('Scan failed:'), error);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Convert ProjectScan to DetectedConfig
|
|
98
|
+
*/
|
|
99
|
+
function convertScanToDetected(scan, root) {
|
|
100
|
+
const detected = {
|
|
101
|
+
_meta: {
|
|
102
|
+
generated_at: new Date().toISOString(),
|
|
103
|
+
genbox_version: version,
|
|
104
|
+
scanned_root: root,
|
|
105
|
+
},
|
|
106
|
+
structure: {
|
|
107
|
+
type: mapStructureType(scan.structure.type),
|
|
108
|
+
confidence: scan.structure.confidence || 'medium',
|
|
109
|
+
indicators: scan.structure.indicators || [],
|
|
110
|
+
},
|
|
111
|
+
runtimes: scan.runtimes.map(r => ({
|
|
112
|
+
language: r.language,
|
|
113
|
+
version: r.version,
|
|
114
|
+
version_source: r.versionSource,
|
|
115
|
+
package_manager: r.packageManager,
|
|
116
|
+
lockfile: r.lockfile,
|
|
117
|
+
})),
|
|
118
|
+
apps: {},
|
|
119
|
+
};
|
|
120
|
+
// Convert apps
|
|
121
|
+
for (const app of scan.apps) {
|
|
122
|
+
// Map scanner AppType to DetectedApp type
|
|
123
|
+
const mappedType = mapAppType(app.type);
|
|
124
|
+
detected.apps[app.name] = {
|
|
125
|
+
path: app.path,
|
|
126
|
+
type: mappedType,
|
|
127
|
+
type_reason: inferTypeReason(app),
|
|
128
|
+
port: app.port,
|
|
129
|
+
port_source: app.port ? inferPortSource(app) : undefined,
|
|
130
|
+
framework: app.framework, // Framework is a string type
|
|
131
|
+
framework_source: app.framework ? inferFrameworkSource(app) : undefined,
|
|
132
|
+
commands: app.scripts ? {
|
|
133
|
+
dev: app.scripts.dev,
|
|
134
|
+
build: app.scripts.build,
|
|
135
|
+
start: app.scripts.start,
|
|
136
|
+
} : undefined,
|
|
137
|
+
dependencies: app.dependencies,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Convert infrastructure
|
|
141
|
+
if (scan.compose) {
|
|
142
|
+
detected.infrastructure = [];
|
|
143
|
+
for (const db of scan.compose.databases || []) {
|
|
144
|
+
detected.infrastructure.push({
|
|
145
|
+
name: db.name,
|
|
146
|
+
type: 'database',
|
|
147
|
+
image: db.image || 'unknown',
|
|
148
|
+
port: db.ports?.[0]?.host || 0,
|
|
149
|
+
source: 'docker-compose.yml',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
for (const cache of scan.compose.caches || []) {
|
|
153
|
+
detected.infrastructure.push({
|
|
154
|
+
name: cache.name,
|
|
155
|
+
type: 'cache',
|
|
156
|
+
image: cache.image || 'unknown',
|
|
157
|
+
port: cache.ports?.[0]?.host || 0,
|
|
158
|
+
source: 'docker-compose.yml',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
for (const queue of scan.compose.queues || []) {
|
|
162
|
+
detected.infrastructure.push({
|
|
163
|
+
name: queue.name,
|
|
164
|
+
type: 'queue',
|
|
165
|
+
image: queue.image || 'unknown',
|
|
166
|
+
port: queue.ports?.[0]?.host || 0,
|
|
167
|
+
source: 'docker-compose.yml',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Git info
|
|
172
|
+
if (scan.git) {
|
|
173
|
+
detected.git = {
|
|
174
|
+
remote: scan.git.remote,
|
|
175
|
+
type: scan.git.type,
|
|
176
|
+
provider: scan.git.provider,
|
|
177
|
+
branch: scan.git.branch,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Scripts
|
|
181
|
+
if (scan.scripts && scan.scripts.length > 0) {
|
|
182
|
+
detected.scripts = scan.scripts.map(s => ({
|
|
183
|
+
name: s.name,
|
|
184
|
+
path: s.path,
|
|
185
|
+
stage: s.stage,
|
|
186
|
+
stage_reason: inferStageReason(s),
|
|
187
|
+
executable: s.isExecutable,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
return detected;
|
|
191
|
+
}
|
|
192
|
+
function mapStructureType(type) {
|
|
193
|
+
if (type.startsWith('monorepo'))
|
|
194
|
+
return 'monorepo';
|
|
195
|
+
if (type === 'hybrid')
|
|
196
|
+
return 'workspace';
|
|
197
|
+
if (type === 'microservices')
|
|
198
|
+
return 'microservices';
|
|
199
|
+
return 'single-app';
|
|
200
|
+
}
|
|
201
|
+
function mapAppType(type) {
|
|
202
|
+
// Map scanner's AppType to DetectedApp type
|
|
203
|
+
switch (type) {
|
|
204
|
+
case 'frontend':
|
|
205
|
+
return 'frontend';
|
|
206
|
+
case 'backend':
|
|
207
|
+
case 'api': // Scanner has 'api', map to 'backend'
|
|
208
|
+
return 'backend';
|
|
209
|
+
case 'worker':
|
|
210
|
+
return 'worker';
|
|
211
|
+
case 'gateway':
|
|
212
|
+
return 'gateway';
|
|
213
|
+
case 'library':
|
|
214
|
+
return 'library';
|
|
215
|
+
default:
|
|
216
|
+
return undefined; // Unknown type
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function inferTypeReason(app) {
|
|
220
|
+
if (!app.type)
|
|
221
|
+
return 'unknown';
|
|
222
|
+
const name = (app.name || app.path || '').toLowerCase();
|
|
223
|
+
if (name.includes('web') || name.includes('frontend') || name.includes('ui') || name.includes('client')) {
|
|
224
|
+
return `naming convention ('${app.name}' contains frontend keyword)`;
|
|
225
|
+
}
|
|
226
|
+
if (name.includes('api') || name.includes('backend') || name.includes('server') || name.includes('gateway')) {
|
|
227
|
+
return `naming convention ('${app.name}' contains backend keyword)`;
|
|
228
|
+
}
|
|
229
|
+
if (name.includes('worker') || name.includes('queue') || name.includes('job')) {
|
|
230
|
+
return `naming convention ('${app.name}' contains worker keyword)`;
|
|
231
|
+
}
|
|
232
|
+
return 'dependency analysis';
|
|
233
|
+
}
|
|
234
|
+
function inferPortSource(app) {
|
|
235
|
+
if (app.scripts?.dev?.includes('--port')) {
|
|
236
|
+
return 'package.json scripts.dev (--port flag)';
|
|
237
|
+
}
|
|
238
|
+
if (app.scripts?.dev?.includes('PORT=')) {
|
|
239
|
+
return 'package.json scripts.dev (PORT= env)';
|
|
240
|
+
}
|
|
241
|
+
if (app.scripts?.start?.includes('--port')) {
|
|
242
|
+
return 'package.json scripts.start (--port flag)';
|
|
243
|
+
}
|
|
244
|
+
return 'framework default';
|
|
245
|
+
}
|
|
246
|
+
function inferFrameworkSource(app) {
|
|
247
|
+
const framework = app.framework;
|
|
248
|
+
if (!framework)
|
|
249
|
+
return 'unknown';
|
|
250
|
+
// Config file based detection
|
|
251
|
+
const configFrameworks = ['nextjs', 'nuxt', 'nestjs', 'astro', 'gatsby'];
|
|
252
|
+
if (configFrameworks.includes(framework)) {
|
|
253
|
+
return `config file (${framework}.config.* or similar)`;
|
|
254
|
+
}
|
|
255
|
+
return 'package.json dependencies';
|
|
256
|
+
}
|
|
257
|
+
function inferStageReason(script) {
|
|
258
|
+
const name = script.name.toLowerCase();
|
|
259
|
+
if (name.includes('setup') || name.includes('init') || name.includes('install')) {
|
|
260
|
+
return `filename contains '${name.match(/setup|init|install/)?.[0]}'`;
|
|
261
|
+
}
|
|
262
|
+
if (name.includes('build') || name.includes('compile')) {
|
|
263
|
+
return `filename contains '${name.match(/build|compile/)?.[0]}'`;
|
|
264
|
+
}
|
|
265
|
+
if (name.includes('start') || name.includes('run') || name.includes('serve')) {
|
|
266
|
+
return `filename contains '${name.match(/start|run|serve/)?.[0]}'`;
|
|
267
|
+
}
|
|
268
|
+
if (name.includes('deploy') || name.includes('release')) {
|
|
269
|
+
return `filename contains '${name.match(/deploy|release/)?.[0]}'`;
|
|
270
|
+
}
|
|
271
|
+
return 'default assignment';
|
|
272
|
+
}
|
|
273
|
+
function outputToStdout(detected, asJson) {
|
|
274
|
+
if (asJson) {
|
|
275
|
+
console.log(JSON.stringify(detected, null, 2));
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
console.log(yaml.dump(detected, { lineWidth: 120, noRefs: true }));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function outputToFile(detected, root, asJson) {
|
|
282
|
+
const genboxDir = path.join(root, '.genbox');
|
|
283
|
+
// Create .genbox directory if it doesn't exist
|
|
284
|
+
if (!fs.existsSync(genboxDir)) {
|
|
285
|
+
fs.mkdirSync(genboxDir, { recursive: true });
|
|
286
|
+
}
|
|
287
|
+
// Add .genbox to .gitignore if not already there
|
|
288
|
+
const gitignorePath = path.join(root, '.gitignore');
|
|
289
|
+
if (fs.existsSync(gitignorePath)) {
|
|
290
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf8');
|
|
291
|
+
if (!gitignore.includes('.genbox')) {
|
|
292
|
+
fs.appendFileSync(gitignorePath, '\n# Genbox generated files\n.genbox/\n');
|
|
293
|
+
console.log(chalk_1.default.dim('Added .genbox/ to .gitignore'));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Write detected config
|
|
297
|
+
const filename = asJson ? 'detected.json' : 'detected.yaml';
|
|
298
|
+
const filePath = path.join(genboxDir, filename);
|
|
299
|
+
const content = asJson
|
|
300
|
+
? JSON.stringify(detected, null, 2)
|
|
301
|
+
: yaml.dump(detected, { lineWidth: 120, noRefs: true });
|
|
302
|
+
fs.writeFileSync(filePath, content);
|
|
303
|
+
console.log(chalk_1.default.green(`\nā Detected configuration written to: ${filePath}`));
|
|
304
|
+
}
|
|
305
|
+
function showSummary(detected) {
|
|
306
|
+
console.log(chalk_1.default.bold('\nš Detection Summary:\n'));
|
|
307
|
+
// Structure
|
|
308
|
+
console.log(` Structure: ${chalk_1.default.cyan(detected.structure.type)} (${detected.structure.confidence} confidence)`);
|
|
309
|
+
if (detected.structure.indicators.length > 0) {
|
|
310
|
+
console.log(chalk_1.default.dim(` Indicators: ${detected.structure.indicators.join(', ')}`));
|
|
311
|
+
}
|
|
312
|
+
// Runtimes
|
|
313
|
+
if (detected.runtimes.length > 0) {
|
|
314
|
+
console.log(`\n Runtimes:`);
|
|
315
|
+
for (const runtime of detected.runtimes) {
|
|
316
|
+
console.log(` ${chalk_1.default.cyan(runtime.language)}${runtime.version ? ` ${runtime.version}` : ''}`);
|
|
317
|
+
if (runtime.package_manager) {
|
|
318
|
+
console.log(chalk_1.default.dim(` Package manager: ${runtime.package_manager}`));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Apps
|
|
323
|
+
const appNames = Object.keys(detected.apps);
|
|
324
|
+
if (appNames.length > 0) {
|
|
325
|
+
console.log(`\n Apps (${appNames.length}):`);
|
|
326
|
+
for (const name of appNames) {
|
|
327
|
+
const app = detected.apps[name];
|
|
328
|
+
const parts = [
|
|
329
|
+
chalk_1.default.cyan(name),
|
|
330
|
+
app.type ? `(${app.type})` : '',
|
|
331
|
+
app.framework ? `[${app.framework}]` : '',
|
|
332
|
+
app.port ? `port:${app.port}` : '',
|
|
333
|
+
].filter(Boolean);
|
|
334
|
+
console.log(` ${parts.join(' ')}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Infrastructure
|
|
338
|
+
if (detected.infrastructure && detected.infrastructure.length > 0) {
|
|
339
|
+
console.log(`\n Infrastructure (${detected.infrastructure.length}):`);
|
|
340
|
+
for (const infra of detected.infrastructure) {
|
|
341
|
+
console.log(` ${chalk_1.default.cyan(infra.name)}: ${infra.type} (${infra.image})`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Git
|
|
345
|
+
if (detected.git?.remote) {
|
|
346
|
+
console.log(`\n Git: ${detected.git.provider || 'git'} (${detected.git.type})`);
|
|
347
|
+
console.log(chalk_1.default.dim(` Branch: ${detected.git.branch || 'unknown'}`));
|
|
348
|
+
}
|
|
349
|
+
console.log(chalk_1.default.bold('\nš Next steps:\n'));
|
|
350
|
+
console.log(' 1. Review the detected configuration in .genbox/detected.yaml');
|
|
351
|
+
console.log(' 2. Run ' + chalk_1.default.cyan('genbox init --from-scan') + ' to create genbox.yaml');
|
|
352
|
+
console.log(' 3. Or manually create genbox.yaml using detected values');
|
|
353
|
+
console.log();
|
|
354
|
+
}
|