genbox 1.0.177 → 1.0.179
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/api.js +13 -1
- package/dist/commands/attach.js +16 -16
- package/dist/commands/connect.js +24 -1
- package/dist/commands/create.js +262 -17
- package/dist/commands/destroy.js +95 -27
- package/dist/commands/init.js +9 -3
- package/dist/commands/list.js +260 -201
- package/dist/commands/logs.js +540 -0
- package/dist/commands/ps.js +73 -0
- package/dist/commands/session/index.js +1168 -81
- package/dist/commands/session/list.js +135 -17
- package/dist/commands/session/migrate.js +392 -0
- package/dist/commands/session/start.js +1 -1
- package/dist/commands/setup-local.js +266 -0
- package/dist/commands/status.js +175 -2
- package/dist/commands/urls.js +15 -2
- package/dist/genbox-selector.js +64 -20
- package/dist/index.js +9 -0
- package/dist/lib/context-exporter.js +314 -0
- package/dist/lib/context-importer.js +331 -0
- package/dist/lib/db-migrator.js +275 -0
- package/dist/lib/env-migrator.js +392 -0
- package/dist/lib/hooks-configurator.js +420 -0
- package/dist/lib/local-app-runner.js +507 -0
- package/dist/lib/local-db-seeder.js +368 -0
- package/dist/lib/local-genbox-provisioner.js +746 -0
- package/dist/lib/local-infra-provisioner.js +518 -0
- package/dist/lib/local-proxy-manager.js +526 -0
- package/dist/lib/local-session-manager.js +394 -1
- package/dist/lib/local-vm-provisioner.js +894 -0
- package/dist/lib/native-session-manager.js +468 -0
- package/dist/lib/session-migrator.js +501 -0
- package/dist/profile-resolver.js +1 -0
- package/dist/utils/env-parser.js +54 -1
- package/package.json +3 -2
package/dist/api.js
CHANGED
|
@@ -58,6 +58,7 @@ async function fetchApi(endpoint, options = {}) {
|
|
|
58
58
|
const token = config_store_1.ConfigStore.getToken();
|
|
59
59
|
const headers = {
|
|
60
60
|
'Content-Type': 'application/json',
|
|
61
|
+
'Connection': 'close', // Ensure connection closes after request (prevents node from hanging)
|
|
61
62
|
...options.headers,
|
|
62
63
|
};
|
|
63
64
|
if (token) {
|
|
@@ -69,14 +70,25 @@ async function fetchApi(endpoint, options = {}) {
|
|
|
69
70
|
console.log(chalk_1.default.dim(`[API DEBUG] POST ${endpoint}`));
|
|
70
71
|
console.log(chalk_1.default.dim(`[API DEBUG] repos: ${JSON.stringify(bodyObj.repos, null, 2)}`));
|
|
71
72
|
}
|
|
73
|
+
// Extract custom timeout, default to 15s for mutations, can be lower for reads
|
|
74
|
+
const { timeout = 15000, ...fetchOptions } = options;
|
|
72
75
|
let response;
|
|
73
76
|
try {
|
|
77
|
+
// Add timeout to prevent hanging on network issues
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
74
80
|
response = await fetch(url, {
|
|
75
|
-
...
|
|
81
|
+
...fetchOptions,
|
|
76
82
|
headers,
|
|
83
|
+
signal: controller.signal,
|
|
77
84
|
});
|
|
85
|
+
clearTimeout(timeoutId);
|
|
78
86
|
}
|
|
79
87
|
catch (error) {
|
|
88
|
+
// Handle timeout
|
|
89
|
+
if (error.name === 'AbortError') {
|
|
90
|
+
throw new Error(`Request to Genbox API timed out. Check your internet connection.`);
|
|
91
|
+
}
|
|
80
92
|
// Handle network-level errors (DNS, connection refused, timeout, etc.)
|
|
81
93
|
if (error.cause?.code === 'ECONNREFUSED') {
|
|
82
94
|
throw new Error(`Cannot connect to Genbox API at ${API_URL}. Is the server running?`);
|
package/dist/commands/attach.js
CHANGED
|
@@ -377,30 +377,30 @@ function getPrivateSshKey() {
|
|
|
377
377
|
}
|
|
378
378
|
throw new Error('No SSH private key found in ~/.ssh/');
|
|
379
379
|
}
|
|
380
|
-
//
|
|
380
|
+
// Minimal tmux config - session management only, no extra features
|
|
381
381
|
const TMUX_CONFIG = `
|
|
382
|
-
#
|
|
383
|
-
set -
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
set -
|
|
382
|
+
# Extended keys (REQUIRED for Shift+Enter in Claude Code)
|
|
383
|
+
set -s extended-keys on
|
|
384
|
+
set -s extended-keys-format csi-u
|
|
385
|
+
set -g extended-keys always
|
|
386
|
+
set -as terminal-features 'xterm*:extkeys'
|
|
387
387
|
|
|
388
|
-
# No delay for
|
|
388
|
+
# No escape delay (for vim mode)
|
|
389
389
|
set -g escape-time 0
|
|
390
390
|
|
|
391
|
-
#
|
|
391
|
+
# Mouse scroll: pass to app if in alternate screen (Claude), else copy-mode
|
|
392
|
+
set -g mouse on
|
|
393
|
+
bind -n WheelUpPane if-shell -F "#{alternate_on}" "send-keys -M" "copy-mode -e; send-keys -M"
|
|
394
|
+
bind -n WheelDownPane send-keys -M
|
|
395
|
+
|
|
396
|
+
# Status bar for Genbox branding and image upload messages
|
|
392
397
|
set -g status on
|
|
393
398
|
set -g status-position bottom
|
|
394
|
-
set -g status-style 'bg=#
|
|
395
|
-
set -g status-left '#[
|
|
399
|
+
set -g status-style 'bg=#2d2d2d fg=#888888'
|
|
400
|
+
set -g status-left '#[fg=#888888,bold] GENBOX #[default] #H '
|
|
396
401
|
set -g status-left-length 30
|
|
397
|
-
set -g status-right '#[fg=#
|
|
402
|
+
set -g status-right '#[fg=#666666] %H:%M '
|
|
398
403
|
set -g status-right-length 20
|
|
399
|
-
|
|
400
|
-
# Window styling
|
|
401
|
-
set -g window-status-current-style 'bg=#000000,fg=#f59e0b,bold'
|
|
402
|
-
set -g window-status-current-format ' #W '
|
|
403
|
-
set -g window-status-format ' #W '
|
|
404
404
|
`.trim();
|
|
405
405
|
async function listTmuxSessions(ipAddress, keyPath) {
|
|
406
406
|
try {
|
package/dist/commands/connect.js
CHANGED
|
@@ -78,7 +78,30 @@ exports.connectCommand = new commander_1.Command('connect')
|
|
|
78
78
|
if (!target) {
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
|
-
// Handle local genbox -
|
|
81
|
+
// Handle local genbox with VM (multipass isolation) - SSH into VM
|
|
82
|
+
if (isLocal && localSession && localSession.isolation === 'multipass' && localSession.ipAddress) {
|
|
83
|
+
const keyPath = getPrivateSshKey();
|
|
84
|
+
// Add SSH config for easy access (addSshConfigEntry adds genbox- prefix)
|
|
85
|
+
if (!(0, ssh_config_1.hasSshConfigEntry)(localSession.name)) {
|
|
86
|
+
(0, ssh_config_1.addSshConfigEntry)({ name: localSession.name, ipAddress: localSession.ipAddress });
|
|
87
|
+
}
|
|
88
|
+
console.log(chalk_1.default.dim(`Connecting to local VM ${chalk_1.default.bold(localSession.name)} (${localSession.ipAddress})...`));
|
|
89
|
+
console.log(chalk_1.default.dim(` You can also use: ssh genbox-${localSession.name}`));
|
|
90
|
+
const sshArgs = [
|
|
91
|
+
'-i', keyPath,
|
|
92
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
93
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
94
|
+
`dev@${localSession.ipAddress}`
|
|
95
|
+
];
|
|
96
|
+
const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
|
|
97
|
+
ssh.on('close', (code) => {
|
|
98
|
+
if (code !== 0) {
|
|
99
|
+
console.log(chalk_1.default.dim(`Connection closed with code ${code}`));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Handle local genbox without VM - attach to session
|
|
82
105
|
if (isLocal && localSession) {
|
|
83
106
|
console.log(chalk_1.default.dim(`Attaching to local genbox ${chalk_1.default.bold(localSession.name)}...`));
|
|
84
107
|
const manager = (0, local_session_manager_1.getLocalSessionManager)();
|
package/dist/commands/create.js
CHANGED
|
@@ -46,6 +46,7 @@ const path = __importStar(require("path"));
|
|
|
46
46
|
const child_process_1 = require("child_process");
|
|
47
47
|
const config_loader_1 = require("../config-loader");
|
|
48
48
|
const local_session_manager_1 = require("../lib/local-session-manager");
|
|
49
|
+
const local_genbox_provisioner_1 = require("../lib/local-genbox-provisioner");
|
|
49
50
|
const profile_resolver_1 = require("../profile-resolver");
|
|
50
51
|
const api_1 = require("../api");
|
|
51
52
|
const ssh_config_1 = require("../ssh-config");
|
|
@@ -329,28 +330,243 @@ async function promptForProfile(profiles) {
|
|
|
329
330
|
}
|
|
330
331
|
/**
|
|
331
332
|
* Create a local genbox (Docker container or VM)
|
|
332
|
-
*
|
|
333
|
+
* Supports two modes:
|
|
334
|
+
* 1. Full replica mode (if genbox.yaml exists): Uses same config as cloud
|
|
335
|
+
* 2. Simple mode: Just container/VM with mounted folder
|
|
333
336
|
*/
|
|
334
337
|
async function createLocalGenbox(nameArg, options) {
|
|
338
|
+
// Check if genbox.yaml exists for full replica mode
|
|
339
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
340
|
+
const loadResult = await configLoader.load();
|
|
341
|
+
if (loadResult.config) {
|
|
342
|
+
// Full replica mode - use genbox.yaml configuration
|
|
343
|
+
await createLocalGenboxFullReplica(nameArg, options, loadResult.config, configLoader);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// Simple mode - no genbox.yaml
|
|
347
|
+
await createLocalGenboxSimple(nameArg, options);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Create a full replica local genbox using genbox.yaml configuration
|
|
352
|
+
*/
|
|
353
|
+
async function createLocalGenboxFullReplica(nameArg, options, config, configLoader) {
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(chalk_1.default.blue('=== Create Local Genbox (Full Replica) ==='));
|
|
356
|
+
console.log(chalk_1.default.dim('Using genbox.yaml configuration'));
|
|
357
|
+
console.log('');
|
|
358
|
+
// Interactive name prompt if not provided
|
|
359
|
+
let name = nameArg;
|
|
360
|
+
if (!name && !options.yes) {
|
|
361
|
+
name = await promptForName(false);
|
|
362
|
+
}
|
|
363
|
+
else if (!name && options.yes) {
|
|
364
|
+
name = (0, random_name_1.generateRandomName)();
|
|
365
|
+
}
|
|
366
|
+
// Prompt for profile if available and not specified
|
|
367
|
+
let selectedProfile = options.profile;
|
|
368
|
+
if (!selectedProfile && !options.yes && config.profiles && Object.keys(config.profiles).length > 0) {
|
|
369
|
+
selectedProfile = await promptForProfile(config.profiles);
|
|
370
|
+
}
|
|
371
|
+
// Resolve profile and configuration
|
|
372
|
+
const profileResolver = new profile_resolver_1.ProfileResolver(configLoader);
|
|
373
|
+
const createOptions = {
|
|
374
|
+
name: name,
|
|
375
|
+
profile: selectedProfile,
|
|
376
|
+
apps: options.apps ? options.apps.split(',').map((a) => a.trim()) : undefined,
|
|
377
|
+
addApps: options.addApps ? options.addApps.split(',').map((a) => a.trim()) : undefined,
|
|
378
|
+
api: options.api,
|
|
379
|
+
db: options.db,
|
|
380
|
+
dbSource: options.dbSource,
|
|
381
|
+
size: options.size,
|
|
382
|
+
branch: options.branch,
|
|
383
|
+
newBranch: options.newBranch,
|
|
384
|
+
sourceBranch: options.fromBranch,
|
|
385
|
+
yes: options.yes,
|
|
386
|
+
};
|
|
387
|
+
const resolved = await profileResolver.resolve(config, createOptions);
|
|
388
|
+
// Check system resources
|
|
389
|
+
const provisioner = (0, local_genbox_provisioner_1.getLocalGenboxProvisioner)();
|
|
390
|
+
const requirements = provisioner.calculateRequirements(resolved);
|
|
391
|
+
const resourceCheck = provisioner.checkResourceAvailability(requirements);
|
|
392
|
+
// Display configuration
|
|
393
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
394
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
|
|
395
|
+
console.log(` ${chalk_1.default.bold('Project:')} ${resolved.project.name}`);
|
|
396
|
+
console.log(` ${chalk_1.default.bold('Size:')} ${resolved.size}`);
|
|
397
|
+
console.log(` ${chalk_1.default.bold('Apps:')} ${resolved.apps.map(a => a.name).join(', ') || 'none'}`);
|
|
398
|
+
// Show repos that will be cloned (like cloud)
|
|
399
|
+
if (resolved.repos && resolved.repos.length > 0) {
|
|
400
|
+
console.log(` ${chalk_1.default.bold('Repos:')} ${resolved.repos.map(r => r.name).join(', ')}`);
|
|
401
|
+
}
|
|
402
|
+
const localInfra = resolved.infrastructure.filter(i => i.mode === 'local');
|
|
403
|
+
if (localInfra.length > 0) {
|
|
404
|
+
console.log(` ${chalk_1.default.bold('Infra:')} ${localInfra.map(i => i.name).join(', ')}`);
|
|
405
|
+
}
|
|
406
|
+
console.log(` ${chalk_1.default.bold('Database:')} ${resolved.database.mode}${resolved.database.source ? ` (${resolved.database.source})` : ''}`);
|
|
407
|
+
console.log(` ${chalk_1.default.bold('Directory:')} ${process.cwd()}`);
|
|
408
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
409
|
+
// Show resource warnings
|
|
410
|
+
if (resourceCheck.warnings.length > 0) {
|
|
411
|
+
console.log('');
|
|
412
|
+
console.log(chalk_1.default.yellow('Resource warnings:'));
|
|
413
|
+
for (const warning of resourceCheck.warnings) {
|
|
414
|
+
console.log(chalk_1.default.yellow(` - ${warning}`));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Confirm
|
|
418
|
+
if (!options.yes) {
|
|
419
|
+
console.log('');
|
|
420
|
+
const confirm = await prompts.confirm({
|
|
421
|
+
message: `Create local genbox '${name}'?`,
|
|
422
|
+
default: true,
|
|
423
|
+
});
|
|
424
|
+
if (!confirm) {
|
|
425
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
console.log('');
|
|
430
|
+
// Load GIT_TOKEN for git clone (like cloud)
|
|
431
|
+
const envVars = configLoader.loadEnvVars(process.cwd());
|
|
432
|
+
// Provision
|
|
433
|
+
const result = await provisioner.provision({
|
|
434
|
+
name: name,
|
|
435
|
+
workdir: process.cwd(),
|
|
436
|
+
resolvedConfig: resolved,
|
|
437
|
+
isolation: options.vm ? 'multipass' : options.native ? 'native' : 'docker',
|
|
438
|
+
skipInstall: options.skipInstall,
|
|
439
|
+
skipApps: false,
|
|
440
|
+
skipDatabase: false,
|
|
441
|
+
gitToken: envVars.GIT_TOKEN, // For git clone in VM
|
|
442
|
+
onProgress: (msg) => console.log(msg),
|
|
443
|
+
});
|
|
444
|
+
if (!result.success) {
|
|
445
|
+
console.error(chalk_1.default.red(`\nFailed: ${result.message}`));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const session = result.session;
|
|
449
|
+
// Display result
|
|
450
|
+
console.log('');
|
|
451
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
452
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${session.name}`);
|
|
453
|
+
console.log(` ${chalk_1.default.bold('ID:')} ${session.id}`);
|
|
454
|
+
console.log(` ${chalk_1.default.bold('Size:')} ${session.size}`);
|
|
455
|
+
if (session.infrastructure.length > 0) {
|
|
456
|
+
console.log(` ${chalk_1.default.bold('Infra:')} ${session.infrastructure.map(i => `${i.name}:${i.port}`).join(', ')}`);
|
|
457
|
+
}
|
|
458
|
+
if (session.apps.length > 0) {
|
|
459
|
+
const running = session.apps.filter(a => a.status === 'running');
|
|
460
|
+
console.log(` ${chalk_1.default.bold('Apps:')} ${running.length}/${session.apps.length} running`);
|
|
461
|
+
}
|
|
462
|
+
if (session.domain) {
|
|
463
|
+
console.log(` ${chalk_1.default.bold('Domain:')} ${chalk_1.default.cyan(session.domain)}`);
|
|
464
|
+
}
|
|
465
|
+
if (session.database.seeded) {
|
|
466
|
+
console.log(` ${chalk_1.default.bold('Database:')} ${chalk_1.default.green('seeded')} (${session.database.mode})`);
|
|
467
|
+
}
|
|
468
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
469
|
+
console.log('');
|
|
470
|
+
console.log(chalk_1.default.bold('Next steps:'));
|
|
471
|
+
console.log(` Status: ${chalk_1.default.cyan(`gb status ${session.name}`)}`);
|
|
472
|
+
console.log(` Destroy: ${chalk_1.default.cyan(`gb destroy ${session.name}`)}`);
|
|
473
|
+
if (session.previewUrl) {
|
|
474
|
+
console.log('');
|
|
475
|
+
console.log(chalk_1.default.dim(` Preview: ${chalk_1.default.cyan(session.previewUrl)}`));
|
|
476
|
+
}
|
|
477
|
+
// Show infrastructure URLs
|
|
478
|
+
if (session.infrastructure.length > 0) {
|
|
479
|
+
console.log('');
|
|
480
|
+
console.log(chalk_1.default.bold('Infrastructure:'));
|
|
481
|
+
for (const infra of session.infrastructure) {
|
|
482
|
+
console.log(` ${infra.name}: ${chalk_1.default.dim(infra.url)}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
console.log('');
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Create a simple local genbox (no genbox.yaml)
|
|
489
|
+
* By default creates an isolated session with git worktree
|
|
490
|
+
*/
|
|
491
|
+
async function createLocalGenboxSimple(nameArg, options) {
|
|
335
492
|
const manager = (0, local_session_manager_1.getLocalSessionManager)();
|
|
336
493
|
const provider = options.gemini ? 'gemini' : 'claude';
|
|
337
494
|
const isolation = options.vm ? 'multipass' : options.native ? 'native' : 'docker';
|
|
495
|
+
const useWorktree = !options.noWorktree && isolation !== 'native';
|
|
496
|
+
// Interactive name prompt if not provided (same as cloud flow)
|
|
497
|
+
let name = nameArg;
|
|
498
|
+
if (!name && !options.yes) {
|
|
499
|
+
console.log('');
|
|
500
|
+
console.log(chalk_1.default.blue('=== Create Local Genbox ==='));
|
|
501
|
+
console.log(chalk_1.default.dim('No genbox.yaml found - using simple mode'));
|
|
502
|
+
console.log('');
|
|
503
|
+
name = await promptForName(false);
|
|
504
|
+
}
|
|
505
|
+
else if (!name && options.yes) {
|
|
506
|
+
// Auto-generate name in non-interactive mode
|
|
507
|
+
name = (0, random_name_1.generateRandomName)();
|
|
508
|
+
}
|
|
509
|
+
// Native mode doesn't support worktrees
|
|
510
|
+
if (options.native && !options.noWorktree) {
|
|
511
|
+
console.log(chalk_1.default.yellow('Note: Native mode does not support isolated worktrees.'));
|
|
512
|
+
console.log(chalk_1.default.dim('Use --vm or default Docker mode for isolated development.'));
|
|
513
|
+
console.log('');
|
|
514
|
+
}
|
|
338
515
|
console.log('');
|
|
339
|
-
console.log(chalk_1.default.
|
|
340
|
-
console.log('');
|
|
516
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
517
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
|
|
341
518
|
console.log(` ${chalk_1.default.bold('Type:')} ${isolation}`);
|
|
342
519
|
console.log(` ${chalk_1.default.bold('Provider:')} ${provider}`);
|
|
343
520
|
console.log(` ${chalk_1.default.bold('Directory:')} ${process.cwd()}`);
|
|
521
|
+
if (useWorktree && manager.isGitRepo(process.cwd())) {
|
|
522
|
+
const branchName = options.branch || name;
|
|
523
|
+
console.log(` ${chalk_1.default.bold('Isolation:')} Git worktree (branch: ${branchName})`);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
console.log(` ${chalk_1.default.bold('Isolation:')} Mounted folder (shared)`);
|
|
527
|
+
}
|
|
528
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
344
529
|
console.log('');
|
|
345
|
-
const spinner = (0, ora_1.default)(`Creating ${isolation} environment...`).start();
|
|
346
530
|
try {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
531
|
+
let session;
|
|
532
|
+
if (useWorktree && manager.isGitRepo(process.cwd())) {
|
|
533
|
+
// Create isolated session with worktree
|
|
534
|
+
console.log(chalk_1.default.dim('Creating isolated development environment...'));
|
|
535
|
+
console.log('');
|
|
536
|
+
session = await manager.createIsolatedSession({
|
|
537
|
+
provider,
|
|
538
|
+
isolation: isolation,
|
|
539
|
+
workdir: process.cwd(),
|
|
540
|
+
name,
|
|
541
|
+
branch: options.branch || name,
|
|
542
|
+
sourceBranch: options.fromBranch,
|
|
543
|
+
appPort: options.port ? parseInt(options.port, 10) : 3000,
|
|
544
|
+
appName: options.appName || 'web',
|
|
545
|
+
installDeps: !options.skipInstall,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
// Create simple session (mounted folder, no worktree)
|
|
550
|
+
if (useWorktree && !manager.isGitRepo(process.cwd())) {
|
|
551
|
+
console.log(chalk_1.default.yellow('Note: Not a git repository. Using mounted folder instead of worktree.'));
|
|
552
|
+
console.log(chalk_1.default.dim('Initialize git to enable isolated development with worktrees.'));
|
|
553
|
+
console.log('');
|
|
554
|
+
}
|
|
555
|
+
const spinner = (0, ora_1.default)(`Creating ${isolation} environment...`).start();
|
|
556
|
+
try {
|
|
557
|
+
session = await manager.createSession({
|
|
558
|
+
provider,
|
|
559
|
+
isolation,
|
|
560
|
+
workdir: process.cwd(),
|
|
561
|
+
name,
|
|
562
|
+
});
|
|
563
|
+
spinner.succeed(chalk_1.default.green('Local genbox created!'));
|
|
564
|
+
}
|
|
565
|
+
catch (err) {
|
|
566
|
+
spinner.fail(chalk_1.default.red('Failed to create environment'));
|
|
567
|
+
throw err;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
354
570
|
console.log('');
|
|
355
571
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
356
572
|
console.log(` ${chalk_1.default.bold('Name:')} ${session.name}`);
|
|
@@ -358,6 +574,15 @@ async function createLocalGenbox(nameArg, options) {
|
|
|
358
574
|
console.log(` ${chalk_1.default.bold('Provider:')} ${session.provider}`);
|
|
359
575
|
console.log(` ${chalk_1.default.bold('Isolation:')} ${session.isolation}`);
|
|
360
576
|
console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.green(session.status)}`);
|
|
577
|
+
if (session.worktreeBranch) {
|
|
578
|
+
console.log(` ${chalk_1.default.bold('Branch:')} ${session.worktreeBranch}`);
|
|
579
|
+
}
|
|
580
|
+
if (session.worktreePath) {
|
|
581
|
+
console.log(` ${chalk_1.default.bold('Worktree:')} ${chalk_1.default.dim(session.worktreePath)}`);
|
|
582
|
+
}
|
|
583
|
+
if (session.domain) {
|
|
584
|
+
console.log(` ${chalk_1.default.bold('Domain:')} ${chalk_1.default.cyan(session.domain)}`);
|
|
585
|
+
}
|
|
361
586
|
if (session.containerId) {
|
|
362
587
|
console.log(` ${chalk_1.default.bold('Container:')} ${session.containerId.slice(0, 12)}`);
|
|
363
588
|
}
|
|
@@ -366,22 +591,38 @@ async function createLocalGenbox(nameArg, options) {
|
|
|
366
591
|
}
|
|
367
592
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
368
593
|
console.log('');
|
|
594
|
+
// Show domain setup hint if using worktree
|
|
595
|
+
if (session.domain) {
|
|
596
|
+
console.log(chalk_1.default.yellow('DNS Setup Required:'));
|
|
597
|
+
console.log(chalk_1.default.dim(` Add to /etc/hosts: ${chalk_1.default.cyan(`127.0.0.1 ${session.domain}`)}`));
|
|
598
|
+
console.log(chalk_1.default.dim(` Or run: ${chalk_1.default.cyan('gb setup-local')} for automatic proxy setup`));
|
|
599
|
+
console.log('');
|
|
600
|
+
}
|
|
369
601
|
console.log(chalk_1.default.bold('Next steps:'));
|
|
370
602
|
console.log(` Attach: ${chalk_1.default.cyan(`gb session ${session.name}`)}`);
|
|
371
|
-
console.log(`
|
|
372
|
-
console.log(`
|
|
373
|
-
console.log(` Destroy: ${chalk_1.default.cyan(`gb
|
|
603
|
+
console.log(` Connect: ${chalk_1.default.cyan(`gb connect ${session.name}`)}`);
|
|
604
|
+
console.log(` Status: ${chalk_1.default.cyan(`gb status ${session.name}`)}`);
|
|
605
|
+
console.log(` Destroy: ${chalk_1.default.cyan(`gb destroy ${session.name}`)}`);
|
|
606
|
+
if (session.domain) {
|
|
607
|
+
console.log('');
|
|
608
|
+
console.log(chalk_1.default.dim(` Preview: ${chalk_1.default.cyan(`https://${session.domain}`)}`));
|
|
609
|
+
console.log(chalk_1.default.dim(` or ${chalk_1.default.cyan(`http://localhost:${session.ports.preview}`)}`));
|
|
610
|
+
}
|
|
374
611
|
console.log('');
|
|
375
612
|
}
|
|
376
613
|
catch (error) {
|
|
377
|
-
|
|
614
|
+
console.error(chalk_1.default.red(`Failed to create local genbox: ${error.message}`));
|
|
378
615
|
if (error.message.includes('Docker')) {
|
|
379
616
|
console.log('');
|
|
380
617
|
console.log(chalk_1.default.yellow('Tip: Make sure Docker Desktop is running.'));
|
|
381
618
|
}
|
|
382
619
|
if (error.message.includes('multipass')) {
|
|
383
620
|
console.log('');
|
|
384
|
-
console.log(chalk_1.default.yellow('Tip: Install Multipass from https://multipass
|
|
621
|
+
console.log(chalk_1.default.yellow('Tip: Install Multipass from https://canonical.com/multipass/install'));
|
|
622
|
+
}
|
|
623
|
+
if (error.message.includes('worktree')) {
|
|
624
|
+
console.log('');
|
|
625
|
+
console.log(chalk_1.default.yellow('Tip: Use --no-worktree to mount the folder directly instead.'));
|
|
385
626
|
}
|
|
386
627
|
}
|
|
387
628
|
}
|
|
@@ -391,6 +632,10 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
391
632
|
.option('-l, --local', 'Create local genbox (Docker container or VM)')
|
|
392
633
|
.option('--vm', 'Use Multipass VM for local genbox')
|
|
393
634
|
.option('--native', 'Use native mode (no isolation) for local genbox')
|
|
635
|
+
.option('--no-worktree', 'Mount existing folder instead of creating git worktree')
|
|
636
|
+
.option('--skip-install', 'Skip dependency installation for worktree')
|
|
637
|
+
.option('--port <port>', 'App port for local genbox (default: 3000)')
|
|
638
|
+
.option('--app-name <name>', 'App name for domain routing (default: web)')
|
|
394
639
|
.option('--claude', 'Use Claude provider (default)')
|
|
395
640
|
.option('--gemini', 'Use Gemini provider')
|
|
396
641
|
.option('-p, --profile <profile>', 'Use a predefined profile')
|
|
@@ -401,7 +646,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
401
646
|
.option('--db-source <source>', 'Database source: staging, production')
|
|
402
647
|
.option('--db-dump <path>', 'Use existing mongodump file instead of creating one')
|
|
403
648
|
.option('--db-copy-remote', 'Copy database on the server (requires publicly accessible DB)')
|
|
404
|
-
.option('-s, --size <size>', 'Server size: small, medium, large, xl')
|
|
649
|
+
.option('-s, --size <size>', 'Server size: tiny, mini (local only), small, medium, large, xl')
|
|
405
650
|
.option('-b, --branch <branch>', 'Use existing git branch (skips new branch creation)')
|
|
406
651
|
.option('-n, --new-branch <name>', 'Create a new branch with this name (defaults to env name)')
|
|
407
652
|
.option('-f, --from-branch <branch>', 'Source branch to create new branch from (defaults to config default or main)')
|