genbox 1.0.97 → 1.0.99
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/cleanup-ssh.js +91 -0
- package/dist/commands/destroy.js +4 -0
- package/dist/index.js +27 -1
- package/dist/scanner/index.js +93 -11
- package/dist/ssh-config.js +21 -0
- package/package.json +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cleanupSshCommand = void 0;
|
|
7
|
+
exports.cleanupOrphanedSshConfigs = cleanupOrphanedSshConfigs;
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const api_1 = require("../api");
|
|
12
|
+
const ssh_config_1 = require("../ssh-config");
|
|
13
|
+
/**
|
|
14
|
+
* Clean up orphaned SSH config entries for genboxes that no longer exist
|
|
15
|
+
* Returns the list of removed entries
|
|
16
|
+
*/
|
|
17
|
+
async function cleanupOrphanedSshConfigs(options = {}) {
|
|
18
|
+
const { silent = false } = options;
|
|
19
|
+
// Get all SSH config entries for genboxes
|
|
20
|
+
const sshEntries = (0, ssh_config_1.getAllSshConfigEntries)();
|
|
21
|
+
if (sshEntries.length === 0) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
// Fetch all user's genboxes from API
|
|
25
|
+
let existingGenboxes = [];
|
|
26
|
+
try {
|
|
27
|
+
existingGenboxes = await (0, api_1.fetchApi)('/genboxes');
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// If not authenticated, we can't check - skip silently in auto mode
|
|
31
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
32
|
+
if (!silent) {
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
// Get names of existing genboxes (both running and terminated but recent)
|
|
40
|
+
const existingNames = new Set(existingGenboxes.map(g => g.name));
|
|
41
|
+
// Find orphaned entries (SSH configs for genboxes that don't exist anymore)
|
|
42
|
+
const orphaned = sshEntries.filter(name => !existingNames.has(name));
|
|
43
|
+
if (orphaned.length === 0) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
// Remove orphaned entries
|
|
47
|
+
const removed = [];
|
|
48
|
+
for (const name of orphaned) {
|
|
49
|
+
if ((0, ssh_config_1.removeSshConfigEntry)(name)) {
|
|
50
|
+
removed.push(name);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return removed;
|
|
54
|
+
}
|
|
55
|
+
exports.cleanupSshCommand = new commander_1.Command('cleanup-ssh')
|
|
56
|
+
.description('Remove SSH config entries for genboxes that no longer exist')
|
|
57
|
+
.option('-q, --quiet', 'Only output if entries were removed')
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
try {
|
|
60
|
+
const spinner = (0, ora_1.default)('Checking for orphaned SSH configs...').start();
|
|
61
|
+
const sshEntries = (0, ssh_config_1.getAllSshConfigEntries)();
|
|
62
|
+
if (sshEntries.length === 0) {
|
|
63
|
+
spinner.info(chalk_1.default.dim('No genbox SSH config entries found'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
spinner.text = `Found ${sshEntries.length} SSH config entries, checking against API...`;
|
|
67
|
+
const removed = await cleanupOrphanedSshConfigs();
|
|
68
|
+
if (removed.length === 0) {
|
|
69
|
+
spinner.succeed(chalk_1.default.dim('No orphaned SSH configs found'));
|
|
70
|
+
if (!options.quiet) {
|
|
71
|
+
console.log(chalk_1.default.dim(` ${sshEntries.length} SSH config entries are all valid`));
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
spinner.succeed(chalk_1.default.green(`Cleaned up ${removed.length} orphaned SSH config${removed.length === 1 ? '' : 's'}`));
|
|
76
|
+
if (!options.quiet) {
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(chalk_1.default.dim('Removed entries for:'));
|
|
79
|
+
for (const name of removed) {
|
|
80
|
+
console.log(chalk_1.default.dim(` - ${name}`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
86
|
+
(0, api_1.handleApiError)(error);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
90
|
+
}
|
|
91
|
+
});
|
package/dist/commands/destroy.js
CHANGED
|
@@ -234,6 +234,10 @@ async function triggerSaveWork(genbox) {
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
+
// Display Claude history backup status
|
|
238
|
+
if (result.claudeHistoryBackedUp) {
|
|
239
|
+
console.log(` ${chalk_1.default.green('✓')} Claude history backed up to database`);
|
|
240
|
+
}
|
|
237
241
|
return result;
|
|
238
242
|
}
|
|
239
243
|
catch (error) {
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
7
|
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
5
9
|
const init_1 = require("./commands/init");
|
|
10
|
+
const cleanup_ssh_1 = require("./commands/cleanup-ssh");
|
|
11
|
+
/**
|
|
12
|
+
* Auto-cleanup orphaned SSH configs on startup (non-blocking, silent)
|
|
13
|
+
* This runs in the background without affecting CLI startup time
|
|
14
|
+
*/
|
|
15
|
+
function autoCleanupSshConfigs() {
|
|
16
|
+
// Run cleanup in background - don't await, don't block startup
|
|
17
|
+
(0, cleanup_ssh_1.cleanupOrphanedSshConfigs)({ silent: true })
|
|
18
|
+
.then(removed => {
|
|
19
|
+
// Optionally log if GENBOX_DEBUG is set
|
|
20
|
+
if (process.env.GENBOX_DEBUG && removed.length > 0) {
|
|
21
|
+
console.error(chalk_1.default.dim(`[Auto-cleanup] Removed ${removed.length} orphaned SSH config(s): ${removed.join(', ')}`));
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
.catch(() => {
|
|
25
|
+
// Silently ignore errors - this is a background cleanup
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// Run auto-cleanup on startup (non-blocking)
|
|
29
|
+
autoCleanupSshConfigs();
|
|
6
30
|
const program = new commander_1.Command();
|
|
7
31
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
8
32
|
const { version } = require('../package.json');
|
|
@@ -31,6 +55,7 @@ const migrate_1 = require("./commands/migrate");
|
|
|
31
55
|
const ssh_setup_1 = require("./commands/ssh-setup");
|
|
32
56
|
const rebuild_1 = require("./commands/rebuild");
|
|
33
57
|
const extend_1 = require("./commands/extend");
|
|
58
|
+
const cleanup_ssh_2 = require("./commands/cleanup-ssh");
|
|
34
59
|
program
|
|
35
60
|
.addCommand(init_1.initCommand)
|
|
36
61
|
.addCommand(create_1.createCommand)
|
|
@@ -54,5 +79,6 @@ program
|
|
|
54
79
|
.addCommand(migrate_1.deprecationsCommand)
|
|
55
80
|
.addCommand(ssh_setup_1.sshSetupCommand)
|
|
56
81
|
.addCommand(rebuild_1.rebuildCommand)
|
|
57
|
-
.addCommand(extend_1.extendCommand)
|
|
82
|
+
.addCommand(extend_1.extendCommand)
|
|
83
|
+
.addCommand(cleanup_ssh_2.cleanupSshCommand);
|
|
58
84
|
program.parse(process.argv);
|
package/dist/scanner/index.js
CHANGED
|
@@ -170,20 +170,41 @@ class ProjectScanner {
|
|
|
170
170
|
const hasStartScript = !!(pkg.scripts?.start || pkg.scripts?.dev || pkg.scripts?.serve);
|
|
171
171
|
const type = this.inferAppTypeFromPackage(entry.name, pkg, hasStartScript);
|
|
172
172
|
// Try to detect framework
|
|
173
|
+
// Priority: fullstack frameworks > meta-frameworks > dev servers > UI libraries
|
|
173
174
|
let framework;
|
|
174
175
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
176
|
+
// 1. Fullstack/meta-frameworks (most specific)
|
|
175
177
|
if (deps['next'])
|
|
176
178
|
framework = 'nextjs';
|
|
179
|
+
else if (deps['nuxt'])
|
|
180
|
+
framework = 'nuxt';
|
|
181
|
+
else if (deps['@remix-run/react'])
|
|
182
|
+
framework = 'remix';
|
|
183
|
+
else if (deps['astro'])
|
|
184
|
+
framework = 'astro';
|
|
185
|
+
else if (deps['@sveltejs/kit'])
|
|
186
|
+
framework = 'sveltekit';
|
|
187
|
+
// 2. Backend frameworks
|
|
177
188
|
else if (deps['@nestjs/core'])
|
|
178
189
|
framework = 'nestjs';
|
|
190
|
+
else if (deps['fastify'])
|
|
191
|
+
framework = 'fastify';
|
|
192
|
+
else if (deps['express'])
|
|
193
|
+
framework = 'express';
|
|
194
|
+
else if (deps['hono'])
|
|
195
|
+
framework = 'hono';
|
|
196
|
+
// 3. Dev servers (determines port)
|
|
197
|
+
else if (deps['vite'])
|
|
198
|
+
framework = 'vite';
|
|
199
|
+
// 4. UI libraries (least specific - use dev server port if available)
|
|
179
200
|
else if (deps['react-admin'])
|
|
180
|
-
framework = 'react-admin
|
|
201
|
+
framework = 'vite'; // react-admin uses vite
|
|
181
202
|
else if (deps['react'])
|
|
182
203
|
framework = 'react';
|
|
183
204
|
else if (deps['vue'])
|
|
184
205
|
framework = 'vue';
|
|
185
|
-
else if (deps['
|
|
186
|
-
framework = '
|
|
206
|
+
else if (deps['svelte'])
|
|
207
|
+
framework = 'svelte';
|
|
187
208
|
// Try to detect port from package.json scripts
|
|
188
209
|
let port;
|
|
189
210
|
const devScript = pkg.scripts?.dev || pkg.scripts?.start || '';
|
|
@@ -370,19 +391,80 @@ class ProjectScanner {
|
|
|
370
391
|
if (!app.framework) {
|
|
371
392
|
app.framework = appFramework.name;
|
|
372
393
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
394
|
+
}
|
|
395
|
+
// Priority 1: Detect port from config files (vite.config.ts, etc.)
|
|
396
|
+
if (!app.port) {
|
|
397
|
+
const configPort = this.detectPortFromConfig(appPath);
|
|
398
|
+
if (configPort) {
|
|
399
|
+
app.port = configPort;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Priority 2: Use framework default from detector
|
|
403
|
+
if (!app.port && appFramework?.defaultPort) {
|
|
404
|
+
app.port = appFramework.defaultPort;
|
|
405
|
+
}
|
|
406
|
+
// Priority 3: Use hardcoded framework default as last resort
|
|
407
|
+
if (!app.port && app.framework) {
|
|
408
|
+
app.port = this.getDefaultPortForFramework(app.framework);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Detect port from config files (vite.config.ts, next.config.js, etc.)
|
|
414
|
+
*/
|
|
415
|
+
detectPortFromConfig(appPath) {
|
|
416
|
+
const fs = require('fs');
|
|
417
|
+
const pathModule = require('path');
|
|
418
|
+
// Try vite.config.ts/js
|
|
419
|
+
for (const configFile of ['vite.config.ts', 'vite.config.js', 'vite.config.mts', 'vite.config.mjs']) {
|
|
420
|
+
const configPath = pathModule.join(appPath, configFile);
|
|
421
|
+
if (fs.existsSync(configPath)) {
|
|
422
|
+
try {
|
|
423
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
424
|
+
// Match server.port or server: { port: XXXX }
|
|
425
|
+
const portMatch = content.match(/server\s*:\s*\{[^}]*port\s*:\s*(\d+)/s) ||
|
|
426
|
+
content.match(/port\s*:\s*(\d+)/);
|
|
427
|
+
if (portMatch) {
|
|
428
|
+
return parseInt(portMatch[1], 10);
|
|
429
|
+
}
|
|
376
430
|
}
|
|
431
|
+
catch { }
|
|
377
432
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
433
|
+
}
|
|
434
|
+
// Try next.config.js/mjs (less common to have port there, but check anyway)
|
|
435
|
+
// Next.js port is usually in package.json scripts or CLI args
|
|
436
|
+
// Try nuxt.config.ts/js
|
|
437
|
+
for (const configFile of ['nuxt.config.ts', 'nuxt.config.js']) {
|
|
438
|
+
const configPath = pathModule.join(appPath, configFile);
|
|
439
|
+
if (fs.existsSync(configPath)) {
|
|
440
|
+
try {
|
|
441
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
442
|
+
const portMatch = content.match(/devServer\s*:\s*\{[^}]*port\s*:\s*(\d+)/s) ||
|
|
443
|
+
content.match(/port\s*:\s*(\d+)/);
|
|
444
|
+
if (portMatch) {
|
|
445
|
+
return parseInt(portMatch[1], 10);
|
|
446
|
+
}
|
|
383
447
|
}
|
|
448
|
+
catch { }
|
|
384
449
|
}
|
|
385
450
|
}
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Get default port for a framework (fallback if config file doesn't specify)
|
|
455
|
+
*/
|
|
456
|
+
getDefaultPortForFramework(framework) {
|
|
457
|
+
const portMap = {
|
|
458
|
+
nextjs: 3000,
|
|
459
|
+
nuxt: 3000,
|
|
460
|
+
vite: 5173,
|
|
461
|
+
react: 3000,
|
|
462
|
+
vue: 5173,
|
|
463
|
+
angular: 4200,
|
|
464
|
+
nestjs: 3000,
|
|
465
|
+
express: 3000,
|
|
466
|
+
};
|
|
467
|
+
return portMap[framework];
|
|
386
468
|
}
|
|
387
469
|
/**
|
|
388
470
|
* Ensure all apps have unique ports.
|
package/dist/ssh-config.js
CHANGED
|
@@ -38,6 +38,7 @@ exports.removeSshConfigEntry = removeSshConfigEntry;
|
|
|
38
38
|
exports.updateSshConfigEntry = updateSshConfigEntry;
|
|
39
39
|
exports.hasSshConfigEntry = hasSshConfigEntry;
|
|
40
40
|
exports.getSshAlias = getSshAlias;
|
|
41
|
+
exports.getAllSshConfigEntries = getAllSshConfigEntries;
|
|
41
42
|
const fs = __importStar(require("fs"));
|
|
42
43
|
const path = __importStar(require("path"));
|
|
43
44
|
const os = __importStar(require("os"));
|
|
@@ -214,3 +215,23 @@ function hasSshConfigEntry(name) {
|
|
|
214
215
|
function getSshAlias(name) {
|
|
215
216
|
return `genbox-${name}`;
|
|
216
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Get all Genbox SSH config entries from ~/.ssh/config
|
|
220
|
+
* Returns array of genbox names that have SSH config entries
|
|
221
|
+
*/
|
|
222
|
+
function getAllSshConfigEntries() {
|
|
223
|
+
try {
|
|
224
|
+
const config = readSshConfig();
|
|
225
|
+
const entries = [];
|
|
226
|
+
// Find all genbox markers
|
|
227
|
+
const regex = new RegExp(`${GENBOX_MARKER_START} (.+)`, 'g');
|
|
228
|
+
let match;
|
|
229
|
+
while ((match = regex.exec(config)) !== null) {
|
|
230
|
+
entries.push(match[1]);
|
|
231
|
+
}
|
|
232
|
+
return entries;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
}
|