genbox 1.0.3 → 1.0.5

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,412 @@
1
+ "use strict";
2
+ /**
3
+ * Structure Detector
4
+ *
5
+ * Detects project structure type:
6
+ * - Single app
7
+ * - Monorepo (npm/pnpm/yarn/nx/turborepo/lerna)
8
+ * - Microservices (docker-compose based)
9
+ * - Go workspaces
10
+ * - Python monorepos
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.StructureDetector = void 0;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ class StructureDetector {
50
+ /**
51
+ * Detect project structure
52
+ */
53
+ async detect(root, exclude) {
54
+ // Run detectors in order of specificity (most specific first)
55
+ const detectors = [
56
+ () => this.detectNx(root),
57
+ () => this.detectTurborepo(root),
58
+ () => this.detectLerna(root),
59
+ () => this.detectPnpmWorkspace(root),
60
+ () => this.detectNpmWorkspace(root),
61
+ () => this.detectGoWorkspace(root),
62
+ () => this.detectPythonWorkspace(root),
63
+ () => this.detectMultiRepoWorkspace(root, exclude), // Multi-repo before microservices
64
+ () => this.detectMicroservices(root),
65
+ () => this.detectSingleApp(root),
66
+ ];
67
+ for (const detector of detectors) {
68
+ const result = detector();
69
+ if (result) {
70
+ return {
71
+ type: result.type,
72
+ confidence: result.confidence,
73
+ indicators: result.indicators,
74
+ workspaceRoot: root,
75
+ };
76
+ }
77
+ }
78
+ // Fallback
79
+ return {
80
+ type: 'single-app',
81
+ confidence: 'low',
82
+ indicators: ['No specific structure detected'],
83
+ workspaceRoot: root,
84
+ };
85
+ }
86
+ detectNx(root) {
87
+ const nxJsonPath = path.join(root, 'nx.json');
88
+ if (!fs.existsSync(nxJsonPath))
89
+ return null;
90
+ const indicators = ['nx.json'];
91
+ // Check for additional Nx files
92
+ const workspaceJson = path.join(root, 'workspace.json');
93
+ if (fs.existsSync(workspaceJson)) {
94
+ indicators.push('workspace.json');
95
+ }
96
+ // Check for project.json files in subdirectories
97
+ const projectDirs = ['apps', 'libs', 'packages'];
98
+ for (const dir of projectDirs) {
99
+ const dirPath = path.join(root, dir);
100
+ if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
101
+ indicators.push(`${dir}/ directory`);
102
+ }
103
+ }
104
+ return {
105
+ type: 'monorepo-nx',
106
+ confidence: 'high',
107
+ indicators,
108
+ };
109
+ }
110
+ detectTurborepo(root) {
111
+ const turboJsonPath = path.join(root, 'turbo.json');
112
+ if (!fs.existsSync(turboJsonPath))
113
+ return null;
114
+ const indicators = ['turbo.json'];
115
+ // Verify it has pipeline configuration
116
+ try {
117
+ const turboConfig = JSON.parse(fs.readFileSync(turboJsonPath, 'utf8'));
118
+ if (turboConfig.pipeline || turboConfig.tasks) {
119
+ indicators.push('Has pipeline/tasks configuration');
120
+ }
121
+ }
122
+ catch { }
123
+ return {
124
+ type: 'monorepo-turborepo',
125
+ confidence: 'high',
126
+ indicators,
127
+ };
128
+ }
129
+ detectLerna(root) {
130
+ const lernaJsonPath = path.join(root, 'lerna.json');
131
+ if (!fs.existsSync(lernaJsonPath))
132
+ return null;
133
+ const indicators = ['lerna.json'];
134
+ try {
135
+ const lernaConfig = JSON.parse(fs.readFileSync(lernaJsonPath, 'utf8'));
136
+ if (lernaConfig.packages) {
137
+ indicators.push(`Packages: ${lernaConfig.packages.join(', ')}`);
138
+ }
139
+ }
140
+ catch { }
141
+ return {
142
+ type: 'monorepo-lerna',
143
+ confidence: 'high',
144
+ indicators,
145
+ };
146
+ }
147
+ detectPnpmWorkspace(root) {
148
+ const pnpmWorkspacePath = path.join(root, 'pnpm-workspace.yaml');
149
+ if (!fs.existsSync(pnpmWorkspacePath))
150
+ return null;
151
+ const indicators = ['pnpm-workspace.yaml'];
152
+ // Check for pnpm-lock.yaml
153
+ if (fs.existsSync(path.join(root, 'pnpm-lock.yaml'))) {
154
+ indicators.push('pnpm-lock.yaml');
155
+ }
156
+ return {
157
+ type: 'monorepo-pnpm',
158
+ confidence: 'high',
159
+ indicators,
160
+ };
161
+ }
162
+ detectNpmWorkspace(root) {
163
+ const packageJsonPath = path.join(root, 'package.json');
164
+ if (!fs.existsSync(packageJsonPath))
165
+ return null;
166
+ try {
167
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
168
+ // Check for workspaces field
169
+ if (!pkg.workspaces)
170
+ return null;
171
+ const workspaces = Array.isArray(pkg.workspaces)
172
+ ? pkg.workspaces
173
+ : pkg.workspaces.packages || [];
174
+ if (workspaces.length === 0)
175
+ return null;
176
+ const indicators = [`package.json workspaces: ${workspaces.join(', ')}`];
177
+ // Determine if it's yarn or npm based on lockfile
178
+ const hasYarnLock = fs.existsSync(path.join(root, 'yarn.lock'));
179
+ return {
180
+ type: hasYarnLock ? 'monorepo-yarn' : 'monorepo-npm',
181
+ confidence: 'high',
182
+ indicators,
183
+ };
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ detectGoWorkspace(root) {
190
+ const goWorkPath = path.join(root, 'go.work');
191
+ if (!fs.existsSync(goWorkPath))
192
+ return null;
193
+ const indicators = ['go.work'];
194
+ try {
195
+ const content = fs.readFileSync(goWorkPath, 'utf8');
196
+ const useMatches = content.match(/use\s+\./g);
197
+ if (useMatches) {
198
+ indicators.push(`${useMatches.length} use directives`);
199
+ }
200
+ }
201
+ catch { }
202
+ return {
203
+ type: 'monorepo-go',
204
+ confidence: 'high',
205
+ indicators,
206
+ };
207
+ }
208
+ detectPythonWorkspace(root) {
209
+ const pyprojectPath = path.join(root, 'pyproject.toml');
210
+ if (!fs.existsSync(pyprojectPath))
211
+ return null;
212
+ try {
213
+ const content = fs.readFileSync(pyprojectPath, 'utf8');
214
+ // Check for Poetry workspace or PDM workspace
215
+ if (content.includes('[tool.poetry.packages]') ||
216
+ content.includes('[tool.pdm.packages]')) {
217
+ return {
218
+ type: 'monorepo-python',
219
+ confidence: 'high',
220
+ indicators: ['pyproject.toml with workspace packages'],
221
+ };
222
+ }
223
+ // Check for multiple Python projects in subdirectories
224
+ const projectDirs = ['packages', 'libs', 'services'];
225
+ const foundDirs = [];
226
+ for (const dir of projectDirs) {
227
+ const dirPath = path.join(root, dir);
228
+ if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
229
+ const subDirs = fs.readdirSync(dirPath);
230
+ const pythonProjects = subDirs.filter(sub => {
231
+ const subPath = path.join(dirPath, sub);
232
+ return fs.existsSync(path.join(subPath, 'pyproject.toml')) ||
233
+ fs.existsSync(path.join(subPath, 'setup.py'));
234
+ });
235
+ if (pythonProjects.length > 0) {
236
+ foundDirs.push(...pythonProjects.map(p => `${dir}/${p}`));
237
+ }
238
+ }
239
+ }
240
+ if (foundDirs.length > 1) {
241
+ return {
242
+ type: 'monorepo-python',
243
+ confidence: 'medium',
244
+ indicators: foundDirs,
245
+ };
246
+ }
247
+ }
248
+ catch { }
249
+ return null;
250
+ }
251
+ /**
252
+ * Detect multi-repo workspace (multiple separate projects/repos in subdirectories)
253
+ * This is common when a directory contains multiple related but independent projects
254
+ */
255
+ detectMultiRepoWorkspace(root, exclude) {
256
+ // Skip if there's a root package.json (would be detected as npm/yarn workspace)
257
+ if (fs.existsSync(path.join(root, 'package.json')))
258
+ return null;
259
+ if (fs.existsSync(path.join(root, 'pnpm-workspace.yaml')))
260
+ return null;
261
+ const indicators = [];
262
+ const projectDirs = [];
263
+ // Default exclusions
264
+ const defaultExclusions = ['node_modules', 'dist', 'build', '.git', 'backup', 'backups', 'logs'];
265
+ const exclusions = new Set([...defaultExclusions, ...(exclude || [])]);
266
+ // Look for subdirectories that are independent projects
267
+ try {
268
+ const entries = fs.readdirSync(root, { withFileTypes: true });
269
+ for (const entry of entries) {
270
+ if (!entry.isDirectory())
271
+ continue;
272
+ if (entry.name.startsWith('.'))
273
+ continue; // Skip hidden dirs
274
+ if (exclusions.has(entry.name))
275
+ continue; // Skip excluded dirs
276
+ const subPath = path.join(root, entry.name);
277
+ // Check if this subdir is a project (has package.json, go.mod, etc.)
278
+ const isProject = fs.existsSync(path.join(subPath, 'package.json')) ||
279
+ fs.existsSync(path.join(subPath, 'go.mod')) ||
280
+ fs.existsSync(path.join(subPath, 'pyproject.toml')) ||
281
+ fs.existsSync(path.join(subPath, 'Cargo.toml')) ||
282
+ fs.existsSync(path.join(subPath, 'Gemfile'));
283
+ if (isProject) {
284
+ // Check if it's a separate git repo
285
+ const hasOwnGit = fs.existsSync(path.join(subPath, '.git'));
286
+ projectDirs.push(entry.name);
287
+ indicators.push(`${entry.name}/${hasOwnGit ? ' (git repo)' : ''}`);
288
+ }
289
+ }
290
+ }
291
+ catch {
292
+ return null;
293
+ }
294
+ // Need at least 2 project subdirectories to be a workspace
295
+ if (projectDirs.length >= 2) {
296
+ return {
297
+ type: 'hybrid', // Use 'hybrid' for multi-repo workspaces
298
+ confidence: projectDirs.length >= 3 ? 'high' : 'medium',
299
+ indicators: [
300
+ `${projectDirs.length} independent projects`,
301
+ ...indicators.slice(0, 8), // Limit indicator list
302
+ ],
303
+ };
304
+ }
305
+ return null;
306
+ }
307
+ detectMicroservices(root) {
308
+ // Look for docker-compose files
309
+ const composeFiles = this.findComposeFiles(root);
310
+ if (composeFiles.length === 0)
311
+ return null;
312
+ // Parse docker-compose to count services
313
+ const indicators = [];
314
+ let totalServices = 0;
315
+ let buildServices = 0;
316
+ for (const file of composeFiles) {
317
+ try {
318
+ const yaml = require('js-yaml');
319
+ const content = yaml.load(fs.readFileSync(file, 'utf8'));
320
+ if (content && content.services) {
321
+ const services = Object.entries(content.services);
322
+ totalServices += services.length;
323
+ // Count services with build context (actual application code)
324
+ for (const [name, config] of services) {
325
+ const svc = config;
326
+ if (svc.build) {
327
+ buildServices++;
328
+ indicators.push(`${name} (build)`);
329
+ }
330
+ }
331
+ }
332
+ }
333
+ catch { }
334
+ }
335
+ // Consider it microservices if there are multiple buildable services
336
+ if (buildServices >= 2) {
337
+ return {
338
+ type: 'microservices',
339
+ confidence: 'high',
340
+ indicators: [
341
+ `${composeFiles.length} docker-compose file(s)`,
342
+ `${totalServices} total services`,
343
+ `${buildServices} buildable services`,
344
+ ...indicators.slice(0, 5), // Limit indicators
345
+ ],
346
+ };
347
+ }
348
+ return null;
349
+ }
350
+ detectSingleApp(root) {
351
+ // Check for common single-app indicators
352
+ const indicators = [];
353
+ // Node.js
354
+ if (fs.existsSync(path.join(root, 'package.json'))) {
355
+ indicators.push('package.json');
356
+ }
357
+ // Python
358
+ if (fs.existsSync(path.join(root, 'pyproject.toml'))) {
359
+ indicators.push('pyproject.toml');
360
+ }
361
+ else if (fs.existsSync(path.join(root, 'requirements.txt'))) {
362
+ indicators.push('requirements.txt');
363
+ }
364
+ // Go
365
+ if (fs.existsSync(path.join(root, 'go.mod'))) {
366
+ indicators.push('go.mod');
367
+ }
368
+ // Ruby
369
+ if (fs.existsSync(path.join(root, 'Gemfile'))) {
370
+ indicators.push('Gemfile');
371
+ }
372
+ // Rust
373
+ if (fs.existsSync(path.join(root, 'Cargo.toml'))) {
374
+ indicators.push('Cargo.toml');
375
+ }
376
+ if (indicators.length > 0) {
377
+ return {
378
+ type: 'single-app',
379
+ confidence: indicators.length >= 2 ? 'high' : 'medium',
380
+ indicators,
381
+ };
382
+ }
383
+ return null;
384
+ }
385
+ findComposeFiles(root) {
386
+ const files = [];
387
+ const patterns = [
388
+ 'docker-compose.yml',
389
+ 'docker-compose.yaml',
390
+ 'docker-compose.dev.yml',
391
+ 'docker-compose.dev.yaml',
392
+ 'docker-compose.local.yml',
393
+ 'docker-compose.local.yaml',
394
+ 'docker-compose.override.yml',
395
+ 'docker-compose.override.yaml',
396
+ 'docker-compose.prod.yml',
397
+ 'docker-compose.prod.yaml',
398
+ 'docker-compose.production.yml',
399
+ 'docker-compose.production.yaml',
400
+ 'compose.yml',
401
+ 'compose.yaml',
402
+ ];
403
+ for (const pattern of patterns) {
404
+ const filePath = path.join(root, pattern);
405
+ if (fs.existsSync(filePath)) {
406
+ files.push(filePath);
407
+ }
408
+ }
409
+ return files;
410
+ }
411
+ }
412
+ exports.StructureDetector = StructureDetector;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Scanner Type Definitions
4
+ *
5
+ * Comprehensive types for multi-layer project scanning
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * Genbox Configuration Schema v3
4
+ *
5
+ * Supports:
6
+ * - Multi-level configs (workspace/project/user)
7
+ * - Flexible app selection (profiles, CLI, interactive)
8
+ * - External environment connections
9
+ * - Database modes (none/local/copy/remote)
10
+ * - Dependency resolution
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -54,6 +54,7 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@inquirer/confirm": "^6.0.2",
57
+ "@inquirer/prompts": "^8.0.2",
57
58
  "@inquirer/select": "^5.0.2",
58
59
  "chalk": "^5.6.2",
59
60
  "commander": "^14.0.2",