genbox 1.0.7 → 1.0.9

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.
@@ -233,6 +233,14 @@ function displayResolvedConfig(resolved) {
233
233
  console.log(chalk_1.default.dim(` deps: ${deps}`));
234
234
  }
235
235
  }
236
+ if (resolved.repos.length > 0) {
237
+ console.log('');
238
+ console.log(` ${chalk_1.default.bold('Repos:')}`);
239
+ for (const repo of resolved.repos) {
240
+ console.log(` • ${repo.name}: ${repo.url}`);
241
+ console.log(chalk_1.default.dim(` → ${repo.path}`));
242
+ }
243
+ }
236
244
  if (resolved.infrastructure.length > 0) {
237
245
  console.log('');
238
246
  console.log(` ${chalk_1.default.bold('Infrastructure:')}`);
@@ -12,6 +12,7 @@ const api_1 = require("../api");
12
12
  const genbox_selector_1 = require("../genbox-selector");
13
13
  const ssh_config_1 = require("../ssh-config");
14
14
  exports.destroyCommand = new commander_1.Command('destroy')
15
+ .alias('delete')
15
16
  .description('Destroy a Genbox')
16
17
  .argument('[name]', 'Name of the Genbox to destroy (optional - will prompt if not provided)')
17
18
  .option('-y, --yes', 'Skip confirmation')
@@ -153,6 +153,8 @@ exports.initCommand = new commander_1.Command('init')
153
153
  }
154
154
  console.log(chalk_1.default.blue('Initializing Genbox...'));
155
155
  console.log('');
156
+ // Track env vars to add to .env.genbox
157
+ const envVarsToAdd = {};
156
158
  // Get initial exclusions from CLI options only
157
159
  let exclude = [];
158
160
  if (options.exclude) {
@@ -291,6 +293,7 @@ exports.initCommand = new commander_1.Command('init')
291
293
  });
292
294
  if (selectedRepos.length > 0) {
293
295
  v3Config.repos = {};
296
+ let hasHttpsRepos = false;
294
297
  for (const repoName of selectedRepos) {
295
298
  const repo = appGitRepos.find(r => r.appName === repoName);
296
299
  v3Config.repos[repo.appName] = {
@@ -299,6 +302,24 @@ exports.initCommand = new commander_1.Command('init')
299
302
  branch: repo.branch !== 'main' && repo.branch !== 'master' ? repo.branch : undefined,
300
303
  auth: repo.type === 'ssh' ? 'ssh' : 'token',
301
304
  };
305
+ if (repo.type === 'https') {
306
+ hasHttpsRepos = true;
307
+ }
308
+ }
309
+ // Prompt for GIT_TOKEN if any HTTPS repos are selected
310
+ if (hasHttpsRepos) {
311
+ console.log('');
312
+ console.log(chalk_1.default.yellow('Private repositories require a GitHub token for cloning.'));
313
+ const gitToken = await prompts.password({
314
+ message: 'GitHub Personal Access Token (leave empty to skip):',
315
+ });
316
+ if (gitToken) {
317
+ envVarsToAdd['GIT_TOKEN'] = gitToken;
318
+ console.log(chalk_1.default.green('✓ GIT_TOKEN will be added to .env.genbox'));
319
+ }
320
+ else {
321
+ console.log(chalk_1.default.dim(' Skipped - add GIT_TOKEN to .env.genbox later if needed'));
322
+ }
302
323
  }
303
324
  }
304
325
  }
@@ -425,7 +446,7 @@ exports.initCommand = new commander_1.Command('init')
425
446
  fs_1.default.writeFileSync(configPath, yamlContent);
426
447
  console.log(chalk_1.default.green(`\n✔ Configuration saved to ${CONFIG_FILENAME}`));
427
448
  // Generate .env.genbox
428
- await setupEnvFile(projectName, v3Config, nonInteractive, scan, isMultiRepo);
449
+ await setupEnvFile(projectName, v3Config, nonInteractive, scan, isMultiRepo, envVarsToAdd);
429
450
  // Show warnings
430
451
  if (generated.warnings.length > 0) {
431
452
  console.log('');
@@ -718,7 +739,7 @@ async function setupEnvironments(scan, config, isMultiRepo = false) {
718
739
  /**
719
740
  * Setup .env.genbox file
720
741
  */
721
- async function setupEnvFile(projectName, config, nonInteractive = false, scan, isMultiRepo = false) {
742
+ async function setupEnvFile(projectName, config, nonInteractive = false, scan, isMultiRepo = false, extraEnvVars = {}) {
722
743
  const envPath = path_1.default.join(process.cwd(), ENV_FILENAME);
723
744
  if (fs_1.default.existsSync(envPath)) {
724
745
  console.log(chalk_1.default.dim(` ${ENV_FILENAME} already exists, skipping...`));
@@ -829,6 +850,18 @@ async function setupEnvFile(projectName, config, nonInteractive = false, scan, i
829
850
  }
830
851
  }
831
852
  }
853
+ // Append extra env vars (like GIT_TOKEN) to the file
854
+ if (Object.keys(extraEnvVars).length > 0 && fs_1.default.existsSync(envPath)) {
855
+ let content = fs_1.default.readFileSync(envPath, 'utf8');
856
+ // Add extra env vars section
857
+ let extraSection = '\n# === Added by genbox init ===\n';
858
+ for (const [key, value] of Object.entries(extraEnvVars)) {
859
+ // Remove any existing commented placeholder
860
+ content = content.replace(new RegExp(`^#\\s*${key}=.*$`, 'gm'), '');
861
+ extraSection += `${key}=${value}\n`;
862
+ }
863
+ fs_1.default.writeFileSync(envPath, content.trim() + '\n' + extraSection);
864
+ }
832
865
  // Add to .gitignore
833
866
  const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
834
867
  if (fs_1.default.existsSync(gitignorePath)) {
@@ -118,6 +118,21 @@ exports.statusCommand = new commander_1.Command('status')
118
118
  console.log(chalk_1.default.yellow('[WARN] Unable to connect to server. It may still be booting.'));
119
119
  return;
120
120
  }
121
+ // Check for SSH connection errors (timeout, refused, etc.)
122
+ const sshErrors = [
123
+ 'Operation timed out',
124
+ 'Connection refused',
125
+ 'Connection timed out',
126
+ 'No route to host',
127
+ 'Network is unreachable',
128
+ 'ssh: connect to host',
129
+ ];
130
+ if (sshErrors.some(err => status.includes(err))) {
131
+ console.log(chalk_1.default.yellow('[INFO] Server is still initializing - SSH is not yet available.'));
132
+ console.log(chalk_1.default.dim(' This is normal for newly created servers.'));
133
+ console.log(chalk_1.default.dim(' Please wait 1-2 minutes and try again.'));
134
+ return;
135
+ }
121
136
  if (status.includes('running')) {
122
137
  // Get elapsed time since server boot
123
138
  const uptime = sshExec(target.ipAddress, keyPath, "cat /proc/uptime 2>/dev/null | cut -d' ' -f1 | cut -d'.' -f1");
@@ -139,24 +154,20 @@ exports.statusCommand = new commander_1.Command('status')
139
154
  }
140
155
  }
141
156
  else if (status.includes('done')) {
142
- console.log(chalk_1.default.green('[SUCCESS] Cloud-init completed!'));
143
- console.log('');
144
- // Show timing summary
145
- console.log(chalk_1.default.blue('[INFO] === Timing Summary ==='));
146
- const setupTime = sshExec(target.ipAddress, keyPath, "sudo grep 'Setup completed in' /var/log/cloud-init-output.log 2>/dev/null | tail -1 | sed 's/=== //g; s/ ===//g'");
147
- if (setupTime) {
148
- console.log(` ${setupTime}`);
149
- }
150
- const cloudInitTotal = sshExec(target.ipAddress, keyPath, "sudo grep 'Cloud-init.*finished' /var/log/cloud-init-output.log 2>/dev/null | tail -1");
151
- if (cloudInitTotal) {
152
- console.log(` ${cloudInitTotal}`);
157
+ // Get uptime for timing display
158
+ const uptimeRaw = sshExec(target.ipAddress, keyPath, "cat /proc/uptime 2>/dev/null | cut -d' ' -f1");
159
+ let uptimeFormatted = '';
160
+ if (uptimeRaw) {
161
+ const uptimeSecs = Math.floor(parseFloat(uptimeRaw));
162
+ const mins = Math.floor(uptimeSecs / 60);
163
+ const secs = uptimeSecs % 60;
164
+ uptimeFormatted = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
153
165
  }
166
+ console.log(chalk_1.default.green(`[SUCCESS] Cloud-init completed!${uptimeFormatted ? ` (${uptimeFormatted})` : ''}`));
154
167
  console.log('');
155
- // Show Database Stats - from config
156
- console.log(chalk_1.default.blue('[INFO] === Database Stats ==='));
168
+ // Show Database Stats - only if configured
157
169
  let dbContainer = '';
158
170
  let dbName = '';
159
- // Try to load database settings from config
160
171
  if ((0, config_1.hasConfig)()) {
161
172
  try {
162
173
  const config = (0, config_1.loadConfig)();
@@ -164,17 +175,13 @@ exports.statusCommand = new commander_1.Command('status')
164
175
  dbName = config.database.name;
165
176
  dbContainer = config.database.container || `${config.project_name}-mongodb`;
166
177
  }
167
- else {
168
- // Default from project name
169
- dbName = config.project_name;
170
- dbContainer = `${config.project_name}-mongodb`;
171
- }
172
178
  }
173
179
  catch {
174
180
  // Config load failed
175
181
  }
176
182
  }
177
183
  if (dbContainer && dbName) {
184
+ console.log(chalk_1.default.blue('[INFO] === Database Stats ==='));
178
185
  const dbStats = sshExec(target.ipAddress, keyPath, `docker exec ${dbContainer} mongosh --quiet ${dbName} --eval 'print("Collections: " + db.getCollectionNames().length); db.getCollectionNames().slice(0, 5).forEach(c => print(" " + c + ": " + db[c].countDocuments()))' 2>/dev/null || echo "Unable to fetch stats"`, 15);
179
186
  if (dbStats) {
180
187
  const lines = dbStats.split('\n');
@@ -184,23 +191,17 @@ exports.statusCommand = new commander_1.Command('status')
184
191
  }
185
192
  }
186
193
  }
194
+ console.log('');
187
195
  }
188
- else {
189
- console.log(chalk_1.default.dim(' No database configured in genbox.yaml'));
190
- console.log(chalk_1.default.dim(' Run `genbox init` to configure database'));
191
- }
192
- console.log('');
193
- // Show Docker containers status
194
- console.log(chalk_1.default.blue('[INFO] === Docker Services ==='));
195
- const dockerStatus = sshExec(target.ipAddress, keyPath, 'docker ps --format "table {{.Names}}\\t{{.Status}}" 2>/dev/null || echo "No containers running"', 10);
196
- if (dockerStatus) {
196
+ // Show Docker containers status - only if containers are running
197
+ const dockerStatus = sshExec(target.ipAddress, keyPath, 'docker ps --format "{{.Names}}\\t{{.Status}}" 2>/dev/null', 10);
198
+ if (dockerStatus && dockerStatus.trim()) {
199
+ console.log(chalk_1.default.blue('[INFO] === Docker Services ==='));
200
+ console.log('NAMES\tSTATUS');
197
201
  console.log(dockerStatus);
202
+ console.log('');
198
203
  }
199
- console.log('');
200
- // Show Health Checks - from config services
201
- console.log(chalk_1.default.blue('[INFO] === Health Checks ==='));
202
204
  let healthChecks = [];
203
- // Try to load services from genbox.yaml config
204
205
  if ((0, config_1.hasConfig)()) {
205
206
  try {
206
207
  const config = (0, config_1.loadConfig)();
@@ -215,15 +216,11 @@ exports.statusCommand = new commander_1.Command('status')
215
216
  }
216
217
  }
217
218
  catch {
218
- // Config load failed, will use empty array
219
+ // Config load failed
219
220
  }
220
221
  }
221
- // If no services in config, show message
222
- if (healthChecks.length === 0) {
223
- console.log(chalk_1.default.dim(' No services configured in genbox.yaml'));
224
- console.log(chalk_1.default.dim(' Run `genbox init` to configure services'));
225
- }
226
- else {
222
+ if (healthChecks.length > 0) {
223
+ console.log(chalk_1.default.blue('[INFO] === Health Checks ==='));
227
224
  for (const check of healthChecks) {
228
225
  const result = sshExec(target.ipAddress, keyPath, `curl -s -o /dev/null -w '%{http_code}' "${check.url}" 2>/dev/null || echo "N/A"`, 5);
229
226
  const statusCode = result.trim();
@@ -234,8 +231,8 @@ exports.statusCommand = new commander_1.Command('status')
234
231
  console.log(` ${chalk_1.default.red('✗')} ${check.name}: ${statusCode}`);
235
232
  }
236
233
  }
234
+ console.log('');
237
235
  }
238
- console.log('');
239
236
  // Show URLs if available
240
237
  if (target.urls && Object.keys(target.urls).length > 0) {
241
238
  console.log(chalk_1.default.blue('[INFO] === Service URLs ==='));
@@ -410,7 +410,7 @@ class ProfileResolver {
410
410
  const seen = new Set();
411
411
  for (const app of apps) {
412
412
  const appConfig = config.apps[app.name];
413
- // Check if app has specific repo
413
+ // Check if app has specific repo field
414
414
  if (appConfig?.repo && config.repos?.[appConfig.repo]) {
415
415
  const repoConfig = config.repos[appConfig.repo];
416
416
  if (!seen.has(repoConfig.url)) {
@@ -423,6 +423,19 @@ class ProfileResolver {
423
423
  seen.add(repoConfig.url);
424
424
  }
425
425
  }
426
+ // Auto-match: if app name matches a repo name, use that repo
427
+ else if (config.repos?.[app.name]) {
428
+ const repoConfig = config.repos[app.name];
429
+ if (!seen.has(repoConfig.url)) {
430
+ repos.push({
431
+ name: app.name,
432
+ url: repoConfig.url,
433
+ path: repoConfig.path,
434
+ branch: repoConfig.branch,
435
+ });
436
+ seen.add(repoConfig.url);
437
+ }
438
+ }
426
439
  }
427
440
  // If no specific repos, use main project repo
428
441
  if (repos.length === 0 && config.repos) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {