genbox 1.0.14 ā 1.0.16
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/connect.js +7 -2
- package/dist/commands/create.js +101 -128
- package/dist/commands/init.js +636 -107
- package/dist/commands/profiles.js +49 -13
- package/dist/commands/scan.js +238 -3
- package/dist/commands/ssh-setup.js +34 -0
- package/dist/config-explainer.js +2 -1
- package/dist/config-loader.js +131 -41
- package/dist/index.js +3 -1
- package/dist/profile-resolver.js +35 -18
- package/package.json +1 -1
|
@@ -44,6 +44,7 @@ const yaml = __importStar(require("js-yaml"));
|
|
|
44
44
|
const fs = __importStar(require("fs"));
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
46
|
const config_loader_1 = require("../config-loader");
|
|
47
|
+
const schema_v4_1 = require("../schema-v4");
|
|
47
48
|
exports.profilesCommand = new commander_1.Command('profiles')
|
|
48
49
|
.description('List and manage profiles')
|
|
49
50
|
.option('--json', 'Output as JSON')
|
|
@@ -51,9 +52,15 @@ exports.profilesCommand = new commander_1.Command('profiles')
|
|
|
51
52
|
try {
|
|
52
53
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
53
54
|
const loadResult = await configLoader.load();
|
|
54
|
-
if (!loadResult.config
|
|
55
|
-
console.log(chalk_1.default.yellow('
|
|
56
|
-
console.log(chalk_1.default.dim('Run "genbox init" to create a
|
|
55
|
+
if (!loadResult.config) {
|
|
56
|
+
console.log(chalk_1.default.yellow('No genbox.yaml found'));
|
|
57
|
+
console.log(chalk_1.default.dim('Run "genbox init" to create a configuration'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const version = (0, schema_v4_1.getConfigVersion)(loadResult.config);
|
|
61
|
+
if (version === 'unknown') {
|
|
62
|
+
console.log(chalk_1.default.yellow('Unknown config version'));
|
|
63
|
+
console.log(chalk_1.default.dim('Run "genbox init" to create a v4 configuration'));
|
|
57
64
|
return;
|
|
58
65
|
}
|
|
59
66
|
const config = loadResult.config;
|
|
@@ -105,8 +112,13 @@ exports.profilesCommand
|
|
|
105
112
|
try {
|
|
106
113
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
107
114
|
const loadResult = await configLoader.load();
|
|
108
|
-
if (!loadResult.config
|
|
109
|
-
console.log(chalk_1.default.yellow('
|
|
115
|
+
if (!loadResult.config) {
|
|
116
|
+
console.log(chalk_1.default.yellow('No genbox.yaml found'));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
|
|
120
|
+
if (configVersion === 'unknown') {
|
|
121
|
+
console.log(chalk_1.default.yellow('Unknown config version'));
|
|
110
122
|
return;
|
|
111
123
|
}
|
|
112
124
|
const config = loadResult.config;
|
|
@@ -137,8 +149,10 @@ exports.profilesCommand
|
|
|
137
149
|
}
|
|
138
150
|
}
|
|
139
151
|
}
|
|
140
|
-
|
|
141
|
-
|
|
152
|
+
// Show connection (v4: default_connection, v3: connect_to)
|
|
153
|
+
const connection = (0, config_loader_1.getProfileConnection)(profile);
|
|
154
|
+
if (connection) {
|
|
155
|
+
console.log(` ${chalk_1.default.bold('Connect to:')} ${connection} environment`);
|
|
142
156
|
}
|
|
143
157
|
if (profile.database) {
|
|
144
158
|
console.log(` ${chalk_1.default.bold('Database:')}`);
|
|
@@ -147,9 +161,21 @@ exports.profilesCommand
|
|
|
147
161
|
console.log(` Source: ${profile.database.source}`);
|
|
148
162
|
}
|
|
149
163
|
}
|
|
150
|
-
|
|
164
|
+
// v4 uses connections, v3 uses infrastructure
|
|
165
|
+
const v4Profile = profile;
|
|
166
|
+
const v3Profile = profile;
|
|
167
|
+
if (v4Profile.connections) {
|
|
168
|
+
console.log(` ${chalk_1.default.bold('Connections:')}`);
|
|
169
|
+
for (const [appName, appConns] of Object.entries(v4Profile.connections)) {
|
|
170
|
+
console.log(` ${appName}:`);
|
|
171
|
+
for (const [targetName, mode] of Object.entries(appConns)) {
|
|
172
|
+
console.log(` ${targetName}: ${mode}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (v3Profile.infrastructure) {
|
|
151
177
|
console.log(` ${chalk_1.default.bold('Infrastructure:')}`);
|
|
152
|
-
for (const [name, mode] of Object.entries(
|
|
178
|
+
for (const [name, mode] of Object.entries(v3Profile.infrastructure)) {
|
|
153
179
|
console.log(` ${name}: ${mode}`);
|
|
154
180
|
}
|
|
155
181
|
}
|
|
@@ -169,8 +195,13 @@ exports.profilesCommand
|
|
|
169
195
|
try {
|
|
170
196
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
171
197
|
const loadResult = await configLoader.load();
|
|
172
|
-
if (!loadResult.config
|
|
173
|
-
console.log(chalk_1.default.yellow('
|
|
198
|
+
if (!loadResult.config) {
|
|
199
|
+
console.log(chalk_1.default.yellow('No genbox.yaml found'));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
|
|
203
|
+
if (configVersion === 'unknown') {
|
|
204
|
+
console.log(chalk_1.default.yellow('Unknown config version'));
|
|
174
205
|
return;
|
|
175
206
|
}
|
|
176
207
|
const config = loadResult.config;
|
|
@@ -286,8 +317,13 @@ exports.profilesCommand
|
|
|
286
317
|
try {
|
|
287
318
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
288
319
|
const loadResult = await configLoader.load();
|
|
289
|
-
if (!loadResult.config
|
|
290
|
-
console.log(chalk_1.default.yellow('
|
|
320
|
+
if (!loadResult.config) {
|
|
321
|
+
console.log(chalk_1.default.yellow('No genbox.yaml found'));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
|
|
325
|
+
if (configVersion === 'unknown') {
|
|
326
|
+
console.log(chalk_1.default.yellow('Unknown config version'));
|
|
291
327
|
return;
|
|
292
328
|
}
|
|
293
329
|
const config = loadResult.config;
|
package/dist/commands/scan.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* genbox scan # Output to .genbox/detected.yaml
|
|
13
13
|
* genbox scan --stdout # Output to stdout
|
|
14
14
|
* genbox scan --json # Output as JSON
|
|
15
|
+
* genbox scan -i # Interactive mode (select apps)
|
|
15
16
|
*/
|
|
16
17
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
18
|
if (k2 === undefined) k2 = k;
|
|
@@ -52,22 +53,69 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
52
53
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
54
|
exports.scanCommand = void 0;
|
|
54
55
|
const commander_1 = require("commander");
|
|
56
|
+
const prompts = __importStar(require("@inquirer/prompts"));
|
|
55
57
|
const chalk_1 = __importDefault(require("chalk"));
|
|
56
58
|
const fs = __importStar(require("fs"));
|
|
57
59
|
const path = __importStar(require("path"));
|
|
58
60
|
const yaml = __importStar(require("js-yaml"));
|
|
61
|
+
const child_process_1 = require("child_process");
|
|
59
62
|
const scanner_1 = require("../scanner");
|
|
60
63
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
61
64
|
const { version } = require('../../package.json');
|
|
65
|
+
/**
|
|
66
|
+
* Detect git repository info for a specific directory
|
|
67
|
+
*/
|
|
68
|
+
function detectGitForDirectory(dir) {
|
|
69
|
+
const gitDir = path.join(dir, '.git');
|
|
70
|
+
if (!fs.existsSync(gitDir))
|
|
71
|
+
return undefined;
|
|
72
|
+
try {
|
|
73
|
+
const remote = (0, child_process_1.execSync)('git remote get-url origin', {
|
|
74
|
+
cwd: dir,
|
|
75
|
+
stdio: 'pipe',
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
}).trim();
|
|
78
|
+
if (!remote)
|
|
79
|
+
return undefined;
|
|
80
|
+
const isSSH = remote.startsWith('git@') || remote.startsWith('ssh://');
|
|
81
|
+
let provider = 'other';
|
|
82
|
+
if (remote.includes('github.com'))
|
|
83
|
+
provider = 'github';
|
|
84
|
+
else if (remote.includes('gitlab.com'))
|
|
85
|
+
provider = 'gitlab';
|
|
86
|
+
else if (remote.includes('bitbucket.org'))
|
|
87
|
+
provider = 'bitbucket';
|
|
88
|
+
let branch = 'main';
|
|
89
|
+
try {
|
|
90
|
+
branch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
|
|
91
|
+
cwd: dir,
|
|
92
|
+
stdio: 'pipe',
|
|
93
|
+
encoding: 'utf8',
|
|
94
|
+
}).trim();
|
|
95
|
+
}
|
|
96
|
+
catch { }
|
|
97
|
+
return {
|
|
98
|
+
remote,
|
|
99
|
+
type: isSSH ? 'ssh' : 'https',
|
|
100
|
+
provider,
|
|
101
|
+
branch,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
62
108
|
exports.scanCommand = new commander_1.Command('scan')
|
|
63
109
|
.description('Analyze project structure and output detected configuration')
|
|
64
110
|
.option('--stdout', 'Output to stdout instead of .genbox/detected.yaml')
|
|
65
111
|
.option('--json', 'Output as JSON instead of YAML')
|
|
66
112
|
.option('--no-infra', 'Skip infrastructure detection (docker-compose)')
|
|
67
113
|
.option('--no-scripts', 'Skip script detection')
|
|
114
|
+
.option('-i, --interactive', 'Interactive mode - select apps before writing')
|
|
68
115
|
.option('-e, --exclude <patterns>', 'Comma-separated patterns to exclude', '')
|
|
69
116
|
.action(async (options) => {
|
|
70
117
|
const cwd = process.cwd();
|
|
118
|
+
const isInteractive = options.interactive && !options.stdout && process.stdin.isTTY;
|
|
71
119
|
console.log(chalk_1.default.cyan('\nš Scanning project...\n'));
|
|
72
120
|
try {
|
|
73
121
|
// Run the scanner
|
|
@@ -78,7 +126,21 @@ exports.scanCommand = new commander_1.Command('scan')
|
|
|
78
126
|
skipScripts: !options.scripts,
|
|
79
127
|
});
|
|
80
128
|
// Convert scan result to DetectedConfig format
|
|
81
|
-
|
|
129
|
+
let detected = convertScanToDetected(scan, cwd);
|
|
130
|
+
// Interactive mode: let user select apps
|
|
131
|
+
if (isInteractive && Object.keys(detected.apps).length > 0) {
|
|
132
|
+
detected = await interactiveAppSelection(detected);
|
|
133
|
+
}
|
|
134
|
+
// Scan env files for service URLs (only for selected frontend apps)
|
|
135
|
+
const frontendApps = Object.entries(detected.apps)
|
|
136
|
+
.filter(([, app]) => app.type === 'frontend')
|
|
137
|
+
.map(([name]) => name);
|
|
138
|
+
if (frontendApps.length > 0) {
|
|
139
|
+
const serviceUrls = scanEnvFilesForUrls(detected.apps, cwd);
|
|
140
|
+
if (serviceUrls.length > 0) {
|
|
141
|
+
detected.service_urls = serviceUrls;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
82
144
|
// Output
|
|
83
145
|
if (options.stdout) {
|
|
84
146
|
outputToStdout(detected, options.json);
|
|
@@ -89,10 +151,156 @@ exports.scanCommand = new commander_1.Command('scan')
|
|
|
89
151
|
}
|
|
90
152
|
}
|
|
91
153
|
catch (error) {
|
|
154
|
+
if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
|
|
155
|
+
console.log('\n' + chalk_1.default.dim('Cancelled.'));
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}
|
|
92
158
|
console.error(chalk_1.default.red('Scan failed:'), error);
|
|
93
159
|
process.exit(1);
|
|
94
160
|
}
|
|
95
161
|
});
|
|
162
|
+
/**
|
|
163
|
+
* Interactive app selection
|
|
164
|
+
*/
|
|
165
|
+
async function interactiveAppSelection(detected) {
|
|
166
|
+
const appEntries = Object.entries(detected.apps);
|
|
167
|
+
console.log(chalk_1.default.blue('=== Detected Apps ===\n'));
|
|
168
|
+
// Show detected apps
|
|
169
|
+
for (const [name, app] of appEntries) {
|
|
170
|
+
const parts = [
|
|
171
|
+
chalk_1.default.cyan(name),
|
|
172
|
+
app.type ? `(${app.type})` : '',
|
|
173
|
+
app.framework ? `[${app.framework}]` : '',
|
|
174
|
+
app.port ? `port:${app.port}` : '',
|
|
175
|
+
].filter(Boolean);
|
|
176
|
+
console.log(` ${parts.join(' ')}`);
|
|
177
|
+
if (app.git) {
|
|
178
|
+
console.log(chalk_1.default.dim(` āā ${app.git.remote}`));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
console.log();
|
|
182
|
+
// Let user select which apps to include
|
|
183
|
+
const choices = appEntries.map(([name, app]) => ({
|
|
184
|
+
name: `${name} (${app.type || 'unknown'}${app.framework ? `, ${app.framework}` : ''})`,
|
|
185
|
+
value: name,
|
|
186
|
+
checked: app.type !== 'library', // Default: include non-libraries
|
|
187
|
+
}));
|
|
188
|
+
const selectedApps = await prompts.checkbox({
|
|
189
|
+
message: 'Select apps to include in detected.yaml:',
|
|
190
|
+
choices,
|
|
191
|
+
});
|
|
192
|
+
// Filter apps to only selected ones
|
|
193
|
+
const filteredApps = {};
|
|
194
|
+
for (const appName of selectedApps) {
|
|
195
|
+
filteredApps[appName] = detected.apps[appName];
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
...detected,
|
|
199
|
+
apps: filteredApps,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Scan env files in app directories for service URLs
|
|
204
|
+
*/
|
|
205
|
+
function scanEnvFilesForUrls(apps, rootDir) {
|
|
206
|
+
const serviceUrls = new Map();
|
|
207
|
+
const envPatterns = ['.env', '.env.local', '.env.development'];
|
|
208
|
+
for (const [appName, app] of Object.entries(apps)) {
|
|
209
|
+
// Only scan frontend apps
|
|
210
|
+
if (app.type !== 'frontend')
|
|
211
|
+
continue;
|
|
212
|
+
const appDir = path.join(rootDir, app.path);
|
|
213
|
+
// Find env file
|
|
214
|
+
let envContent;
|
|
215
|
+
let envSource;
|
|
216
|
+
for (const pattern of envPatterns) {
|
|
217
|
+
const envPath = path.join(appDir, pattern);
|
|
218
|
+
if (fs.existsSync(envPath)) {
|
|
219
|
+
envContent = fs.readFileSync(envPath, 'utf8');
|
|
220
|
+
envSource = pattern;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!envContent)
|
|
225
|
+
continue;
|
|
226
|
+
// Find all HTTP URLs
|
|
227
|
+
const urlRegex = /^([A-Z_][A-Z0-9_]*)=["']?(https?:\/\/[a-zA-Z0-9_.-]+(?::\d+)?[^"'\s]*)["']?/gm;
|
|
228
|
+
let match;
|
|
229
|
+
while ((match = urlRegex.exec(envContent)) !== null) {
|
|
230
|
+
const varName = match[1];
|
|
231
|
+
const fullUrl = match[2];
|
|
232
|
+
// Extract hostname
|
|
233
|
+
const hostMatch = fullUrl.match(/^https?:\/\/([a-zA-Z0-9_.-]+)/);
|
|
234
|
+
if (!hostMatch)
|
|
235
|
+
continue;
|
|
236
|
+
const hostname = hostMatch[1];
|
|
237
|
+
// Only include local URLs (localhost, Docker internal names, IPs)
|
|
238
|
+
const isLocalUrl = hostname === 'localhost' ||
|
|
239
|
+
!hostname.includes('.') ||
|
|
240
|
+
/^\d+\.\d+\.\d+\.\d+$/.test(hostname);
|
|
241
|
+
if (!isLocalUrl)
|
|
242
|
+
continue;
|
|
243
|
+
// Extract base URL
|
|
244
|
+
const baseMatch = fullUrl.match(/^(https?:\/\/[a-zA-Z0-9_.-]+(?::\d+)?)/);
|
|
245
|
+
if (!baseMatch)
|
|
246
|
+
continue;
|
|
247
|
+
const baseUrl = baseMatch[1];
|
|
248
|
+
// Add to map
|
|
249
|
+
if (!serviceUrls.has(baseUrl)) {
|
|
250
|
+
serviceUrls.set(baseUrl, { vars: new Set(), apps: new Set() });
|
|
251
|
+
}
|
|
252
|
+
serviceUrls.get(baseUrl).vars.add(varName);
|
|
253
|
+
serviceUrls.get(baseUrl).apps.add(appName);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Convert to DetectedServiceUrl array
|
|
257
|
+
const result = [];
|
|
258
|
+
for (const [baseUrl, { vars, apps: appNames }] of serviceUrls) {
|
|
259
|
+
const serviceInfo = getServiceInfoFromUrl(baseUrl);
|
|
260
|
+
result.push({
|
|
261
|
+
base_url: baseUrl,
|
|
262
|
+
var_name: serviceInfo.varName,
|
|
263
|
+
description: serviceInfo.description,
|
|
264
|
+
used_by: Array.from(vars),
|
|
265
|
+
apps: Array.from(appNames),
|
|
266
|
+
source: 'env files',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// Sort by port for consistent output
|
|
270
|
+
result.sort((a, b) => {
|
|
271
|
+
const portA = parseInt(a.base_url.match(/:(\d+)/)?.[1] || '0');
|
|
272
|
+
const portB = parseInt(b.base_url.match(/:(\d+)/)?.[1] || '0');
|
|
273
|
+
return portA - portB;
|
|
274
|
+
});
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get service info from URL
|
|
279
|
+
*/
|
|
280
|
+
function getServiceInfoFromUrl(baseUrl) {
|
|
281
|
+
const urlMatch = baseUrl.match(/^https?:\/\/([a-zA-Z0-9_.-]+)(?::(\d+))?/);
|
|
282
|
+
if (!urlMatch) {
|
|
283
|
+
return { varName: 'UNKNOWN_URL', description: 'Unknown service' };
|
|
284
|
+
}
|
|
285
|
+
const hostname = urlMatch[1];
|
|
286
|
+
const port = urlMatch[2] ? parseInt(urlMatch[2]) : undefined;
|
|
287
|
+
// Generate from hostname if not localhost
|
|
288
|
+
if (hostname !== 'localhost') {
|
|
289
|
+
const varName = hostname.toUpperCase().replace(/-/g, '_') + '_URL';
|
|
290
|
+
return {
|
|
291
|
+
varName,
|
|
292
|
+
description: `${hostname} service`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Generate from port for localhost
|
|
296
|
+
if (port) {
|
|
297
|
+
return {
|
|
298
|
+
varName: `PORT_${port}_URL`,
|
|
299
|
+
description: `localhost:${port}`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return { varName: 'LOCALHOST_URL', description: 'localhost' };
|
|
303
|
+
}
|
|
96
304
|
/**
|
|
97
305
|
* Convert ProjectScan to DetectedConfig
|
|
98
306
|
*/
|
|
@@ -117,10 +325,17 @@ function convertScanToDetected(scan, root) {
|
|
|
117
325
|
})),
|
|
118
326
|
apps: {},
|
|
119
327
|
};
|
|
120
|
-
// Convert apps
|
|
328
|
+
// Convert apps (detect git info for each app in multi-repo workspaces)
|
|
329
|
+
const isMultiRepo = scan.structure.type === 'hybrid';
|
|
121
330
|
for (const app of scan.apps) {
|
|
122
331
|
// Map scanner AppType to DetectedApp type
|
|
123
332
|
const mappedType = mapAppType(app.type);
|
|
333
|
+
// Detect git info for this app (for multi-repo workspaces)
|
|
334
|
+
let appGit;
|
|
335
|
+
if (isMultiRepo) {
|
|
336
|
+
const appDir = path.join(root, app.path);
|
|
337
|
+
appGit = detectGitForDirectory(appDir);
|
|
338
|
+
}
|
|
124
339
|
detected.apps[app.name] = {
|
|
125
340
|
path: app.path,
|
|
126
341
|
type: mappedType,
|
|
@@ -135,6 +350,7 @@ function convertScanToDetected(scan, root) {
|
|
|
135
350
|
start: app.scripts.start,
|
|
136
351
|
} : undefined,
|
|
137
352
|
dependencies: app.dependencies,
|
|
353
|
+
git: appGit,
|
|
138
354
|
};
|
|
139
355
|
}
|
|
140
356
|
// Convert infrastructure
|
|
@@ -341,11 +557,30 @@ function showSummary(detected) {
|
|
|
341
557
|
console.log(` ${chalk_1.default.cyan(infra.name)}: ${infra.type} (${infra.image})`);
|
|
342
558
|
}
|
|
343
559
|
}
|
|
344
|
-
// Git
|
|
560
|
+
// Git (root level)
|
|
345
561
|
if (detected.git?.remote) {
|
|
346
562
|
console.log(`\n Git: ${detected.git.provider || 'git'} (${detected.git.type})`);
|
|
347
563
|
console.log(chalk_1.default.dim(` Branch: ${detected.git.branch || 'unknown'}`));
|
|
348
564
|
}
|
|
565
|
+
// Per-app git repos (for multi-repo workspaces)
|
|
566
|
+
const appsWithGit = Object.entries(detected.apps).filter(([, app]) => app.git);
|
|
567
|
+
if (appsWithGit.length > 0) {
|
|
568
|
+
console.log(`\n App Repositories (${appsWithGit.length}):`);
|
|
569
|
+
for (const [name, app] of appsWithGit) {
|
|
570
|
+
const git = app.git;
|
|
571
|
+
console.log(` ${chalk_1.default.cyan(name)}: ${git.provider} (${git.type})`);
|
|
572
|
+
console.log(chalk_1.default.dim(` ${git.remote}`));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// Service URLs (for staging URL configuration)
|
|
576
|
+
if (detected.service_urls && detected.service_urls.length > 0) {
|
|
577
|
+
console.log(`\n Service URLs (${detected.service_urls.length}):`);
|
|
578
|
+
for (const svc of detected.service_urls) {
|
|
579
|
+
console.log(` ${chalk_1.default.cyan(svc.var_name)}: ${svc.base_url}`);
|
|
580
|
+
console.log(chalk_1.default.dim(` Used by: ${svc.used_by.slice(0, 3).join(', ')}${svc.used_by.length > 3 ? ` +${svc.used_by.length - 3} more` : ''}`));
|
|
581
|
+
}
|
|
582
|
+
console.log(chalk_1.default.dim('\n These URLs will need staging equivalents in init.'));
|
|
583
|
+
}
|
|
349
584
|
console.log(chalk_1.default.bold('\nš Next steps:\n'));
|
|
350
585
|
console.log(' 1. Review the detected configuration in .genbox/detected.yaml');
|
|
351
586
|
console.log(' 2. Run ' + chalk_1.default.cyan('genbox init --from-scan') + ' to create genbox.yaml');
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sshSetupCommand = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const ssh_config_1 = require("../ssh-config");
|
|
7
|
+
/**
|
|
8
|
+
* Internal command to poll for IP and add SSH config in background
|
|
9
|
+
* Called by: genbox ssh-setup <genboxId> <name>
|
|
10
|
+
*/
|
|
11
|
+
exports.sshSetupCommand = new commander_1.Command('ssh-setup')
|
|
12
|
+
.argument('<genboxId>', 'Genbox ID')
|
|
13
|
+
.argument('<name>', 'Genbox name')
|
|
14
|
+
.option('--max-attempts <n>', 'Max polling attempts', '90')
|
|
15
|
+
.option('--delay <ms>', 'Delay between attempts in ms', '2000')
|
|
16
|
+
.action(async (genboxId, name, options) => {
|
|
17
|
+
const maxAttempts = parseInt(options.maxAttempts, 10);
|
|
18
|
+
const delayMs = parseInt(options.delay, 10);
|
|
19
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
20
|
+
try {
|
|
21
|
+
const genbox = await (0, api_1.fetchApi)(`/genboxes/${genboxId}`);
|
|
22
|
+
if (genbox.ipAddress) {
|
|
23
|
+
(0, ssh_config_1.addSshConfigEntry)({ name, ipAddress: genbox.ipAddress });
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Ignore errors during polling
|
|
29
|
+
}
|
|
30
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
31
|
+
}
|
|
32
|
+
// Timed out - exit silently
|
|
33
|
+
process.exit(0);
|
|
34
|
+
});
|
package/dist/config-explainer.js
CHANGED
|
@@ -200,7 +200,8 @@ class ConfigExplainer {
|
|
|
200
200
|
if (!config)
|
|
201
201
|
return warnings;
|
|
202
202
|
// Check each app for implicit type/port/framework
|
|
203
|
-
for (const [appName,
|
|
203
|
+
for (const [appName, rawAppConfig] of Object.entries(config.apps || {})) {
|
|
204
|
+
const appConfig = rawAppConfig;
|
|
204
205
|
// Check type - was it inferred?
|
|
205
206
|
if (appConfig.type) {
|
|
206
207
|
const typeExplanation = this.explain(`apps.${appName}.type`);
|