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
package/dist/commands/connect.js
CHANGED
|
@@ -41,6 +41,7 @@ const commander_1 = require("commander");
|
|
|
41
41
|
const chalk_1 = __importDefault(require("chalk"));
|
|
42
42
|
const api_1 = require("../api");
|
|
43
43
|
const genbox_selector_1 = require("../genbox-selector");
|
|
44
|
+
const ssh_config_1 = require("../ssh-config");
|
|
44
45
|
const os = __importStar(require("os"));
|
|
45
46
|
const path = __importStar(require("path"));
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
@@ -80,9 +81,13 @@ exports.connectCommand = new commander_1.Command('connect')
|
|
|
80
81
|
console.error(chalk_1.default.yellow(`Genbox '${target.name}' is still provisioning (no IP). Please wait.`));
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
83
|
-
// 2.
|
|
84
|
+
// 2. Ensure SSH config exists (in case background setup hasn't finished)
|
|
85
|
+
if (!(0, ssh_config_1.hasSshConfigEntry)(target.name)) {
|
|
86
|
+
(0, ssh_config_1.addSshConfigEntry)({ name: target.name, ipAddress: target.ipAddress });
|
|
87
|
+
}
|
|
88
|
+
// 3. Get Key
|
|
84
89
|
const keyPath = getPrivateSshKey();
|
|
85
|
-
//
|
|
90
|
+
// 4. Connect
|
|
86
91
|
console.log(chalk_1.default.dim(`Connecting to ${chalk_1.default.bold(target.name)} (${target.ipAddress})...`));
|
|
87
92
|
const sshArgs = [
|
|
88
93
|
'-i', keyPath,
|
package/dist/commands/create.js
CHANGED
|
@@ -48,64 +48,22 @@ const config_loader_1 = require("../config-loader");
|
|
|
48
48
|
const profile_resolver_1 = require("../profile-resolver");
|
|
49
49
|
const api_1 = require("../api");
|
|
50
50
|
const ssh_config_1 = require("../ssh-config");
|
|
51
|
+
const schema_v4_1 = require("../schema-v4");
|
|
51
52
|
const child_process_1 = require("child_process");
|
|
52
53
|
/**
|
|
53
|
-
*
|
|
54
|
+
* Spawn a background process to poll for IP and add SSH config
|
|
55
|
+
* This runs detached so the main process can exit immediately
|
|
54
56
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Find SSH private key
|
|
72
|
-
*/
|
|
73
|
-
function findSshKeyPath() {
|
|
74
|
-
const home = os.homedir();
|
|
75
|
-
const keyPaths = [
|
|
76
|
-
path.join(home, '.ssh', 'id_ed25519'),
|
|
77
|
-
path.join(home, '.ssh', 'id_rsa'),
|
|
78
|
-
];
|
|
79
|
-
for (const keyPath of keyPaths) {
|
|
80
|
-
if (fs.existsSync(keyPath)) {
|
|
81
|
-
return keyPath;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Wait for SSH to be available on the server
|
|
88
|
-
*/
|
|
89
|
-
async function waitForSsh(ipAddress, maxAttempts = 30, delayMs = 5000) {
|
|
90
|
-
const keyPath = findSshKeyPath();
|
|
91
|
-
if (!keyPath)
|
|
92
|
-
return false;
|
|
93
|
-
const sshOpts = `-i ${keyPath} -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=5`;
|
|
94
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
95
|
-
try {
|
|
96
|
-
(0, child_process_1.execSync)(`ssh ${sshOpts} dev@${ipAddress} "echo 'SSH ready'"`, {
|
|
97
|
-
encoding: 'utf8',
|
|
98
|
-
timeout: 10000,
|
|
99
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
100
|
-
});
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// SSH not ready yet
|
|
105
|
-
}
|
|
106
|
-
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
107
|
-
}
|
|
108
|
-
return false;
|
|
57
|
+
function spawnSshConfigSetup(genboxId, name) {
|
|
58
|
+
// Get the path to the CLI executable
|
|
59
|
+
const cliPath = process.argv[1];
|
|
60
|
+
// Spawn genbox ssh-setup in background
|
|
61
|
+
const child = (0, child_process_1.spawn)(process.execPath, [cliPath, 'ssh-setup', genboxId, name], {
|
|
62
|
+
detached: true,
|
|
63
|
+
stdio: 'ignore',
|
|
64
|
+
});
|
|
65
|
+
// Allow parent to exit independently
|
|
66
|
+
child.unref();
|
|
109
67
|
}
|
|
110
68
|
async function provisionGenbox(payload) {
|
|
111
69
|
return (0, api_1.fetchApi)('/genboxes', {
|
|
@@ -159,11 +117,13 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
159
117
|
// Load configuration
|
|
160
118
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
161
119
|
const loadResult = await configLoader.load();
|
|
162
|
-
|
|
120
|
+
const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
|
|
121
|
+
if (!loadResult.config || configVersion === 'unknown') {
|
|
163
122
|
// Fall back to legacy v1/v2 handling
|
|
164
123
|
await createLegacy(name, options);
|
|
165
124
|
return;
|
|
166
125
|
}
|
|
126
|
+
// Support both v3 and v4 configs
|
|
167
127
|
const config = loadResult.config;
|
|
168
128
|
const profileResolver = new profile_resolver_1.ProfileResolver(configLoader);
|
|
169
129
|
// Build create options
|
|
@@ -210,7 +170,8 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
210
170
|
const publicKey = getPublicSshKey();
|
|
211
171
|
// Check if SSH auth is needed for git
|
|
212
172
|
let privateKeyContent;
|
|
213
|
-
const
|
|
173
|
+
const v3Config = config;
|
|
174
|
+
const usesSSH = v3Config.git_auth?.method === 'ssh' ||
|
|
214
175
|
Object.values(config.repos || {}).some(r => r.auth === 'ssh');
|
|
215
176
|
if (usesSSH && !options.yes) {
|
|
216
177
|
const injectKey = await prompts.confirm({
|
|
@@ -231,40 +192,23 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
231
192
|
try {
|
|
232
193
|
const genbox = await provisionGenbox(payload);
|
|
233
194
|
spinner.succeed(chalk_1.default.green(`Genbox '${name}' created!`));
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (ipAddress) {
|
|
240
|
-
spinner.succeed(`IP address assigned: ${ipAddress}`);
|
|
241
|
-
genbox.ipAddress = ipAddress;
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
spinner.fail('Timed out waiting for IP. Run `genbox status` to check later.');
|
|
245
|
-
displayGenboxInfo(genbox, resolved);
|
|
246
|
-
return;
|
|
195
|
+
// Add SSH config immediately if IP available, otherwise spawn background process
|
|
196
|
+
if (genbox.ipAddress) {
|
|
197
|
+
const sshAdded = (0, ssh_config_1.addSshConfigEntry)({ name, ipAddress: genbox.ipAddress });
|
|
198
|
+
if (sshAdded) {
|
|
199
|
+
console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
|
|
247
200
|
}
|
|
248
201
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
spinner.succeed(chalk_1.default.green('SSH is ready!'));
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
spinner.warn('SSH not ready yet. Server may still be booting.');
|
|
257
|
-
}
|
|
258
|
-
// Add SSH config
|
|
259
|
-
const sshAdded = (0, ssh_config_1.addSshConfigEntry)({
|
|
260
|
-
name,
|
|
261
|
-
ipAddress,
|
|
262
|
-
});
|
|
263
|
-
if (sshAdded) {
|
|
264
|
-
console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
|
|
202
|
+
else if (genbox._id) {
|
|
203
|
+
// Spawn background process to poll for IP and add SSH config
|
|
204
|
+
spawnSshConfigSetup(genbox._id, name);
|
|
205
|
+
console.log(chalk_1.default.dim(' SSH config will be added once IP is assigned.'));
|
|
265
206
|
}
|
|
266
207
|
// Display results
|
|
267
208
|
displayGenboxInfo(genbox, resolved);
|
|
209
|
+
// Inform user about server provisioning
|
|
210
|
+
console.log('');
|
|
211
|
+
console.log(chalk_1.default.dim('Server is provisioning. Run `genbox connect` once ready.'));
|
|
268
212
|
}
|
|
269
213
|
catch (error) {
|
|
270
214
|
spinner.fail(chalk_1.default.red(`Failed to create Genbox: ${error.message}`));
|
|
@@ -375,10 +319,46 @@ function parseEnvGenboxSections(content) {
|
|
|
375
319
|
}
|
|
376
320
|
return sections;
|
|
377
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Build a map of service URL variables based on connection type
|
|
324
|
+
* e.g., if connectTo=staging: GATEWAY_URL → STAGING_GATEWAY_URL value
|
|
325
|
+
*/
|
|
326
|
+
function buildServiceUrlMap(envVarsFromFile, connectTo) {
|
|
327
|
+
const urlMap = {};
|
|
328
|
+
const prefix = connectTo ? `${connectTo.toUpperCase()}_` : 'LOCAL_';
|
|
329
|
+
// Find all service URL variables (LOCAL_*_URL and STAGING_*_URL patterns)
|
|
330
|
+
const serviceNames = new Set();
|
|
331
|
+
for (const key of Object.keys(envVarsFromFile)) {
|
|
332
|
+
const match = key.match(/^(LOCAL|STAGING|PRODUCTION)_(.+_URL)$/);
|
|
333
|
+
if (match) {
|
|
334
|
+
serviceNames.add(match[2]);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Build mapping: VARNAME → value from appropriate prefix
|
|
338
|
+
for (const serviceName of serviceNames) {
|
|
339
|
+
const prefixedKey = `${prefix}${serviceName}`;
|
|
340
|
+
const localKey = `LOCAL_${serviceName}`;
|
|
341
|
+
// Use prefixed value if available, otherwise fall back to local
|
|
342
|
+
const value = envVarsFromFile[prefixedKey] || envVarsFromFile[localKey];
|
|
343
|
+
if (value) {
|
|
344
|
+
urlMap[serviceName] = value;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Also handle legacy API_URL for backwards compatibility
|
|
348
|
+
if (!urlMap['API_URL']) {
|
|
349
|
+
const apiUrl = envVarsFromFile[`${prefix}API_URL`] ||
|
|
350
|
+
envVarsFromFile['LOCAL_API_URL'] ||
|
|
351
|
+
envVarsFromFile['STAGING_API_URL'];
|
|
352
|
+
if (apiUrl) {
|
|
353
|
+
urlMap['API_URL'] = apiUrl;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return urlMap;
|
|
357
|
+
}
|
|
378
358
|
/**
|
|
379
359
|
* Build env content for a specific app by combining GLOBAL + app-specific sections
|
|
380
360
|
*/
|
|
381
|
-
function buildAppEnvContent(sections, appName,
|
|
361
|
+
function buildAppEnvContent(sections, appName, serviceUrlMap) {
|
|
382
362
|
const parts = [];
|
|
383
363
|
// Always include GLOBAL section
|
|
384
364
|
const globalSection = sections.get('GLOBAL');
|
|
@@ -391,8 +371,11 @@ function buildAppEnvContent(sections, appName, apiUrl) {
|
|
|
391
371
|
parts.push(appSection);
|
|
392
372
|
}
|
|
393
373
|
let envContent = parts.join('\n\n');
|
|
394
|
-
// Expand ${
|
|
395
|
-
|
|
374
|
+
// Expand all ${VARNAME} references using the service URL map
|
|
375
|
+
for (const [varName, value] of Object.entries(serviceUrlMap)) {
|
|
376
|
+
const pattern = new RegExp(`\\$\\{${varName}\\}`, 'g');
|
|
377
|
+
envContent = envContent.replace(pattern, value);
|
|
378
|
+
}
|
|
396
379
|
// Keep only actual env vars (filter out pure comment lines but keep var definitions)
|
|
397
380
|
envContent = envContent
|
|
398
381
|
.split('\n')
|
|
@@ -449,18 +432,19 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
449
432
|
envVarsFromFile[match[1]] = value;
|
|
450
433
|
}
|
|
451
434
|
}
|
|
452
|
-
// Determine
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
// Use the environment-specific API URL (e.g., STAGING_API_URL)
|
|
458
|
-
const envApiVarName = `${connectTo.toUpperCase()}_API_URL`;
|
|
459
|
-
apiUrl = envVarsFromFile[envApiVarName] || resolved.env['API_URL'] || 'http://localhost:3050';
|
|
435
|
+
// Determine connection type from profile's connect_to (v3) or default_connection (v4)
|
|
436
|
+
let connectTo;
|
|
437
|
+
if (resolved.profile && config.profiles?.[resolved.profile]) {
|
|
438
|
+
const profile = config.profiles[resolved.profile];
|
|
439
|
+
connectTo = (0, config_loader_1.getProfileConnection)(profile);
|
|
460
440
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
441
|
+
// Build service URL map for variable expansion
|
|
442
|
+
// This maps GATEWAY_URL → STAGING_GATEWAY_URL value (if connectTo=staging)
|
|
443
|
+
// or GATEWAY_URL → LOCAL_GATEWAY_URL value (if local)
|
|
444
|
+
const serviceUrlMap = buildServiceUrlMap(envVarsFromFile, connectTo);
|
|
445
|
+
// Log what's being expanded for debugging
|
|
446
|
+
if (connectTo && Object.keys(serviceUrlMap).length > 0) {
|
|
447
|
+
console.log(chalk_1.default.dim(` Using ${connectTo} URLs for variable expansion`));
|
|
464
448
|
}
|
|
465
449
|
// Add env file for each app - filtered by selected apps only
|
|
466
450
|
for (const app of resolved.apps) {
|
|
@@ -474,7 +458,7 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
474
458
|
for (const serviceSectionName of servicesSections) {
|
|
475
459
|
const serviceName = serviceSectionName.split('/')[1];
|
|
476
460
|
// Build service-specific env content (GLOBAL + service section)
|
|
477
|
-
const serviceEnvContent = buildAppEnvContent(sections, serviceSectionName,
|
|
461
|
+
const serviceEnvContent = buildAppEnvContent(sections, serviceSectionName, serviceUrlMap);
|
|
478
462
|
const stagingName = `${app.name}-${serviceName}.env`;
|
|
479
463
|
const targetPath = `${repoPath}/apps/${serviceName}/.env`;
|
|
480
464
|
files.push({
|
|
@@ -487,7 +471,7 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
487
471
|
}
|
|
488
472
|
else {
|
|
489
473
|
// Regular app - build app-specific env content (GLOBAL + app section)
|
|
490
|
-
const appEnvContent = buildAppEnvContent(sections, app.name,
|
|
474
|
+
const appEnvContent = buildAppEnvContent(sections, app.name, serviceUrlMap);
|
|
491
475
|
files.push({
|
|
492
476
|
path: `/home/dev/.env-staging/${app.name}.env`,
|
|
493
477
|
content: appEnvContent,
|
|
@@ -699,39 +683,28 @@ async function createLegacy(name, options) {
|
|
|
699
683
|
gitToken: envVars.GIT_TOKEN,
|
|
700
684
|
});
|
|
701
685
|
spinner.succeed(chalk_1.default.green(`Genbox '${name}' created!`));
|
|
702
|
-
//
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
if (ipAddress) {
|
|
708
|
-
spinner.succeed(`IP address assigned: ${ipAddress}`);
|
|
709
|
-
genbox.ipAddress = ipAddress;
|
|
710
|
-
}
|
|
711
|
-
else {
|
|
712
|
-
spinner.fail('Timed out waiting for IP. Run `genbox status` to check later.');
|
|
713
|
-
return;
|
|
686
|
+
// Add SSH config immediately if IP available, otherwise spawn background process
|
|
687
|
+
if (genbox.ipAddress) {
|
|
688
|
+
const sshAdded = (0, ssh_config_1.addSshConfigEntry)({ name, ipAddress: genbox.ipAddress });
|
|
689
|
+
if (sshAdded) {
|
|
690
|
+
console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
|
|
714
691
|
}
|
|
715
692
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
spinner.succeed(chalk_1.default.green('SSH is ready!'));
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
spinner.warn('SSH not ready yet. Server may still be booting.');
|
|
724
|
-
}
|
|
725
|
-
// Add SSH config
|
|
726
|
-
const sshAdded = (0, ssh_config_1.addSshConfigEntry)({ name, ipAddress });
|
|
727
|
-
if (sshAdded) {
|
|
728
|
-
console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
|
|
693
|
+
else if (genbox._id) {
|
|
694
|
+
// Spawn background process to poll for IP and add SSH config
|
|
695
|
+
spawnSshConfigSetup(genbox._id, name);
|
|
696
|
+
console.log(chalk_1.default.dim(' SSH config will be added once IP is assigned.'));
|
|
729
697
|
}
|
|
730
698
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
731
699
|
console.log(` ${chalk_1.default.bold('Environment:')} ${name}`);
|
|
732
700
|
console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.yellow(genbox.status)}`);
|
|
733
|
-
|
|
701
|
+
if (genbox.ipAddress) {
|
|
702
|
+
console.log(` ${chalk_1.default.bold('IP:')} ${genbox.ipAddress}`);
|
|
703
|
+
}
|
|
734
704
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
705
|
+
// Inform user about server provisioning
|
|
706
|
+
console.log('');
|
|
707
|
+
console.log(chalk_1.default.dim('Server is provisioning. Run `genbox connect` once ready.'));
|
|
735
708
|
}
|
|
736
709
|
catch (error) {
|
|
737
710
|
spinner.fail(chalk_1.default.red(`Failed: ${error.message}`));
|