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.
- package/dist/commands/create.js +8 -0
- package/dist/commands/destroy.js +1 -0
- package/dist/commands/init.js +35 -2
- package/dist/commands/status.js +37 -40
- package/dist/profile-resolver.js +14 -1
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -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:')}`);
|
package/dist/commands/destroy.js
CHANGED
|
@@ -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')
|
package/dist/commands/init.js
CHANGED
|
@@ -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)) {
|
package/dist/commands/status.js
CHANGED
|
@@ -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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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 -
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
219
|
+
// Config load failed
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
|
-
|
|
222
|
-
|
|
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 ==='));
|
package/dist/profile-resolver.js
CHANGED
|
@@ -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) {
|