genbox 1.0.12 → 1.0.14

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,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
+ }