genbox 1.0.100 → 1.0.102
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 +24 -0
- package/dist/commands/backup.js +410 -0
- package/dist/commands/backups.js +193 -0
- package/dist/commands/create.js +254 -4
- package/dist/commands/extend.js +10 -5
- package/dist/commands/profiles.js +133 -0
- package/dist/commands/restart.js +424 -0
- package/dist/commands/status.js +89 -9
- package/dist/index.js +7 -1
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -81,6 +81,151 @@ function spawnSshConfigSetup(genboxId, name) {
|
|
|
81
81
|
// Allow parent to exit independently
|
|
82
82
|
child.unref();
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Wait for genbox to reach a specific status
|
|
86
|
+
*/
|
|
87
|
+
async function waitForGenboxStatus(genboxId, targetStatus, maxAttempts = 120, // 10 minutes with 5s interval
|
|
88
|
+
intervalMs = 5000) {
|
|
89
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
90
|
+
try {
|
|
91
|
+
const genbox = await (0, api_1.fetchApi)(`/genboxes/${genboxId}`);
|
|
92
|
+
if (targetStatus.includes(genbox.status)) {
|
|
93
|
+
return { success: true, genbox };
|
|
94
|
+
}
|
|
95
|
+
if (genbox.status === 'failed' || genbox.status === 'error') {
|
|
96
|
+
return { success: false, error: `Genbox provisioning failed: ${genbox.statusMessage || 'unknown error'}` };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// Ignore fetch errors and continue polling
|
|
101
|
+
}
|
|
102
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
103
|
+
}
|
|
104
|
+
return { success: false, error: 'Timeout waiting for genbox to be ready' };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Run restore script on genbox via SSH
|
|
108
|
+
*/
|
|
109
|
+
async function runRestore(ipAddress, downloadUrl, backup, onProgress) {
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
// Build restore script
|
|
112
|
+
const restoreScript = `
|
|
113
|
+
set -e
|
|
114
|
+
|
|
115
|
+
BACKUP_DIR="/tmp/backup-restore"
|
|
116
|
+
DOWNLOAD_URL="${downloadUrl}"
|
|
117
|
+
|
|
118
|
+
echo "=== Starting Restore ==="
|
|
119
|
+
echo "Backup: ${backup.name}"
|
|
120
|
+
|
|
121
|
+
# Create temp directory
|
|
122
|
+
rm -rf "$BACKUP_DIR"
|
|
123
|
+
mkdir -p "$BACKUP_DIR"
|
|
124
|
+
cd "$BACKUP_DIR"
|
|
125
|
+
|
|
126
|
+
# Download backup
|
|
127
|
+
echo "Downloading backup..."
|
|
128
|
+
curl -sS -L -o backup.tar.gz "$DOWNLOAD_URL"
|
|
129
|
+
if [ ! -f backup.tar.gz ]; then
|
|
130
|
+
echo "ERROR: Failed to download backup"
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# Extract
|
|
135
|
+
echo "Extracting backup..."
|
|
136
|
+
tar -xzf backup.tar.gz
|
|
137
|
+
rm backup.tar.gz
|
|
138
|
+
|
|
139
|
+
# Restore git patches (uncommitted changes)
|
|
140
|
+
if [ -d "repos" ]; then
|
|
141
|
+
echo "Restoring git changes..."
|
|
142
|
+
for patch in repos/*.patch; do
|
|
143
|
+
if [ -f "$patch" ]; then
|
|
144
|
+
# Extract repo path from patch filename (format: repo-path.patch)
|
|
145
|
+
repo_name=$(basename "$patch" .patch)
|
|
146
|
+
# Try to find the repo
|
|
147
|
+
if [ -d "/home/dev/$repo_name" ]; then
|
|
148
|
+
cd "/home/dev/$repo_name"
|
|
149
|
+
git apply "$BACKUP_DIR/repos/$repo_name.patch" 2>/dev/null || echo " Warning: Could not apply patch for $repo_name"
|
|
150
|
+
cd "$BACKUP_DIR"
|
|
151
|
+
echo " Applied patch: $repo_name"
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
done
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Restore database
|
|
158
|
+
if [ -f "database.archive.gz" ]; then
|
|
159
|
+
echo "Restoring database..."
|
|
160
|
+
DB_NAME="${backup.database?.name || 'genbox_db'}"
|
|
161
|
+
mongorestore --gzip --archive="database.archive.gz" --nsFrom="\${DB_NAME}.*" --nsTo="\${DB_NAME}.*" --drop 2>/dev/null || {
|
|
162
|
+
# Try without namespace mapping
|
|
163
|
+
mongorestore --gzip --archive="database.archive.gz" --drop 2>/dev/null || echo " Warning: Database restore had issues"
|
|
164
|
+
}
|
|
165
|
+
echo " Database restored"
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# Restore Claude history
|
|
169
|
+
if [ -f "claude-history.tar.gz" ]; then
|
|
170
|
+
echo "Restoring Claude history..."
|
|
171
|
+
mkdir -p /home/dev/.claude
|
|
172
|
+
tar -xzf claude-history.tar.gz -C /home/dev/.claude 2>/dev/null || true
|
|
173
|
+
echo " Claude history restored"
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
# Restore config files
|
|
177
|
+
if [ -f "configs.tar.gz" ]; then
|
|
178
|
+
echo "Restoring config files..."
|
|
179
|
+
tar -xzf configs.tar.gz -C /home/dev 2>/dev/null || true
|
|
180
|
+
echo " Config files restored"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Cleanup
|
|
184
|
+
rm -rf "$BACKUP_DIR"
|
|
185
|
+
|
|
186
|
+
echo ""
|
|
187
|
+
echo "=== Restore Complete ==="
|
|
188
|
+
echo "Run 'gb restart' to start services"
|
|
189
|
+
`;
|
|
190
|
+
const ssh = (0, child_process_1.spawn)('ssh', [
|
|
191
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
192
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
193
|
+
'-o', 'ConnectTimeout=30',
|
|
194
|
+
`dev@${ipAddress}`,
|
|
195
|
+
restoreScript
|
|
196
|
+
]);
|
|
197
|
+
let stdout = '';
|
|
198
|
+
let stderr = '';
|
|
199
|
+
ssh.stdout.on('data', (data) => {
|
|
200
|
+
const line = data.toString();
|
|
201
|
+
stdout += line;
|
|
202
|
+
if (onProgress) {
|
|
203
|
+
// Extract meaningful progress messages
|
|
204
|
+
const lines = line.split('\n').filter((l) => l.trim());
|
|
205
|
+
for (const l of lines) {
|
|
206
|
+
if (l.startsWith('===') || l.startsWith('Downloading') || l.startsWith('Extracting') ||
|
|
207
|
+
l.startsWith('Restoring') || l.startsWith(' ')) {
|
|
208
|
+
onProgress(l.trim());
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
ssh.stderr.on('data', (data) => {
|
|
214
|
+
stderr += data.toString();
|
|
215
|
+
});
|
|
216
|
+
ssh.on('close', (code) => {
|
|
217
|
+
if (code === 0) {
|
|
218
|
+
resolve({ success: true });
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
resolve({ success: false, error: stderr || `SSH exited with code ${code}` });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
ssh.on('error', (error) => {
|
|
225
|
+
resolve({ success: false, error: error.message });
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|
|
84
229
|
const DETECTED_DIR = '.genbox';
|
|
85
230
|
const PROJECT_CACHE_FILENAME = 'project.json';
|
|
86
231
|
/**
|
|
@@ -197,8 +342,12 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
197
342
|
.option('-f, --from-branch <branch>', 'Source branch to create new branch from (defaults to config default or main)')
|
|
198
343
|
.option('-y, --yes', 'Skip interactive prompts')
|
|
199
344
|
.option('--dry-run', 'Show what would be created without actually creating')
|
|
345
|
+
.option('-r, --restore', 'Restore from backup (uses genbox name to find backup)')
|
|
200
346
|
.action(async (nameArg, options) => {
|
|
201
347
|
try {
|
|
348
|
+
// Handle restore mode
|
|
349
|
+
let restoreBackup = null;
|
|
350
|
+
let restoreDownloadUrl = null;
|
|
202
351
|
// Load configuration
|
|
203
352
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
204
353
|
const loadResult = await configLoader.load();
|
|
@@ -217,6 +366,66 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
217
366
|
// Support both v3 and v4 configs
|
|
218
367
|
const config = loadResult.config;
|
|
219
368
|
const profileResolver = new profile_resolver_1.ProfileResolver(configLoader);
|
|
369
|
+
// Get workspace early for backup resolution
|
|
370
|
+
const workspace = config.project?.name || 'default';
|
|
371
|
+
// Handle --restore option: fetch backup info by genbox name
|
|
372
|
+
if (options.restore) {
|
|
373
|
+
// Require genbox name for restore
|
|
374
|
+
if (!nameArg) {
|
|
375
|
+
console.log(chalk_1.default.red('Name required for restore'));
|
|
376
|
+
console.log(chalk_1.default.dim(' Usage: gb create <genbox-name> --restore'));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
console.log(chalk_1.default.blue('=== Restore Mode ==='));
|
|
380
|
+
console.log('');
|
|
381
|
+
const restoreSpinner = (0, ora_1.default)(`Looking for backup of '${nameArg}'...`).start();
|
|
382
|
+
try {
|
|
383
|
+
// Find backup by genbox name
|
|
384
|
+
restoreBackup = await (0, api_1.getLatestBackup)(nameArg, workspace);
|
|
385
|
+
if (!restoreBackup) {
|
|
386
|
+
restoreSpinner.fail(chalk_1.default.red(`No backup found for '${nameArg}'`));
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log(chalk_1.default.dim(' Create a backup first with: gb backup ' + nameArg));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
// Verify backup is completed
|
|
392
|
+
if (restoreBackup.status !== 'completed') {
|
|
393
|
+
restoreSpinner.fail(chalk_1.default.red(`Backup is not ready (status: ${restoreBackup.status})`));
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
// Get download URL
|
|
397
|
+
const downloadResult = await (0, api_1.getBackupDownloadUrl)(restoreBackup._id);
|
|
398
|
+
restoreDownloadUrl = downloadResult.downloadUrl;
|
|
399
|
+
restoreSpinner.succeed(chalk_1.default.green('Backup found'));
|
|
400
|
+
// Display backup details
|
|
401
|
+
console.log('');
|
|
402
|
+
console.log(` ${chalk_1.default.bold('Size:')} ${restoreBackup.sourceSize}`);
|
|
403
|
+
console.log(` ${chalk_1.default.bold('Backed up:')} ${new Date(restoreBackup.createdAt).toLocaleString()}`);
|
|
404
|
+
// Show contents
|
|
405
|
+
const contents = [];
|
|
406
|
+
if (restoreBackup.repos && restoreBackup.repos.some(r => r.hadUncommittedChanges)) {
|
|
407
|
+
contents.push('uncommitted changes');
|
|
408
|
+
}
|
|
409
|
+
if (restoreBackup.database) {
|
|
410
|
+
contents.push('database');
|
|
411
|
+
}
|
|
412
|
+
if (restoreBackup.claudeHistoryIncluded) {
|
|
413
|
+
contents.push('Claude history');
|
|
414
|
+
}
|
|
415
|
+
if (contents.length > 0) {
|
|
416
|
+
console.log(` ${chalk_1.default.bold('Contains:')} ${contents.join(', ')}`);
|
|
417
|
+
}
|
|
418
|
+
console.log('');
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
restoreSpinner.fail(chalk_1.default.red(`Failed to fetch backup: ${error.message}`));
|
|
422
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
423
|
+
console.log(chalk_1.default.yellow(' Please authenticate first:'));
|
|
424
|
+
console.log(chalk_1.default.cyan(' $ genbox login'));
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
220
429
|
// Interactive name prompt if not provided
|
|
221
430
|
let name = nameArg;
|
|
222
431
|
if (!name && !options.yes) {
|
|
@@ -233,7 +442,6 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
233
442
|
selectedProfile = await promptForProfile(config.profiles);
|
|
234
443
|
}
|
|
235
444
|
// Check if name is available in workspace, add suffix if taken
|
|
236
|
-
const workspace = config.project?.name || 'default';
|
|
237
445
|
try {
|
|
238
446
|
let { available } = await (0, api_1.checkNameAvailability)(name, workspace);
|
|
239
447
|
if (!available) {
|
|
@@ -601,9 +809,51 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
601
809
|
}
|
|
602
810
|
// Display results
|
|
603
811
|
displayGenboxInfo(genbox, resolved);
|
|
604
|
-
//
|
|
605
|
-
|
|
606
|
-
|
|
812
|
+
// Handle restore if backup was specified
|
|
813
|
+
if (restoreBackup && restoreDownloadUrl && genbox._id) {
|
|
814
|
+
console.log('');
|
|
815
|
+
console.log(chalk_1.default.blue('=== Restoring from Backup ==='));
|
|
816
|
+
// Wait for genbox to be ready
|
|
817
|
+
const waitSpinner = (0, ora_1.default)('Waiting for genbox to be ready...').start();
|
|
818
|
+
const waitResult = await waitForGenboxStatus(genbox._id, ['running']);
|
|
819
|
+
if (!waitResult.success) {
|
|
820
|
+
waitSpinner.fail(chalk_1.default.red(waitResult.error || 'Failed to wait for genbox'));
|
|
821
|
+
console.log(chalk_1.default.dim(' You can manually restore later: gb connect, then run the restore script'));
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
waitSpinner.succeed(chalk_1.default.green('Genbox is ready'));
|
|
825
|
+
// Get IP address from the ready genbox
|
|
826
|
+
const readyGenbox = waitResult.genbox;
|
|
827
|
+
const ipAddress = readyGenbox?.ipAddress || genbox.ipAddress;
|
|
828
|
+
if (!ipAddress) {
|
|
829
|
+
console.log(chalk_1.default.red('Could not get genbox IP address'));
|
|
830
|
+
console.log(chalk_1.default.dim(' You can manually restore by connecting and downloading the backup'));
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
// Run restore
|
|
834
|
+
const restoreSpinner = (0, ora_1.default)('Restoring backup...').start();
|
|
835
|
+
const restoreResult = await runRestore(ipAddress, restoreDownloadUrl, restoreBackup, (msg) => {
|
|
836
|
+
restoreSpinner.text = msg;
|
|
837
|
+
});
|
|
838
|
+
if (restoreResult.success) {
|
|
839
|
+
restoreSpinner.succeed(chalk_1.default.green('Backup restored successfully!'));
|
|
840
|
+
console.log('');
|
|
841
|
+
console.log(chalk_1.default.green('✓ Restore complete'));
|
|
842
|
+
console.log('');
|
|
843
|
+
console.log(chalk_1.default.bold('Next steps:'));
|
|
844
|
+
console.log(` 1. Connect: ${chalk_1.default.cyan(`gb connect ${name}`)}`);
|
|
845
|
+
console.log(` 2. Start services: ${chalk_1.default.cyan('gb restart')}`);
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
restoreSpinner.fail(chalk_1.default.red(`Restore failed: ${restoreResult.error}`));
|
|
849
|
+
console.log(chalk_1.default.dim(' You can manually restore by connecting and running the restore script'));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
// Inform user about server provisioning
|
|
854
|
+
console.log('');
|
|
855
|
+
console.log(chalk_1.default.dim('Server is provisioning. Run `genbox connect` once ready.'));
|
|
856
|
+
}
|
|
607
857
|
}
|
|
608
858
|
catch (error) {
|
|
609
859
|
spinner.fail(chalk_1.default.red(`Failed to create Genbox: ${error.message}`));
|
package/dist/commands/extend.js
CHANGED
|
@@ -92,13 +92,18 @@ exports.extendCommand = new commander_1.Command('extend')
|
|
|
92
92
|
spinner.stop();
|
|
93
93
|
if (result.success) {
|
|
94
94
|
console.log(chalk_1.default.green(`✓ ${result.message}`));
|
|
95
|
-
if (result.
|
|
96
|
-
const
|
|
97
|
-
const minutesUntil = Math.ceil((
|
|
98
|
-
|
|
95
|
+
if (result.protectedUntil && result.autoDestroyEnabled) {
|
|
96
|
+
const protectedTime = new Date(result.protectedUntil);
|
|
97
|
+
const minutesUntil = Math.ceil((protectedTime.getTime() - Date.now()) / (60 * 1000));
|
|
98
|
+
const hoursUntil = Math.floor(minutesUntil / 60);
|
|
99
|
+
const remainingMins = minutesUntil % 60;
|
|
100
|
+
const timeStr = hoursUntil > 0
|
|
101
|
+
? `${hoursUntil}h ${remainingMins}m`
|
|
102
|
+
: `${minutesUntil} min`;
|
|
103
|
+
console.log(chalk_1.default.dim(` Protected from auto-destroy for: ${timeStr}`));
|
|
99
104
|
}
|
|
100
105
|
console.log(chalk_1.default.dim(` Auto-destroy: ${result.autoDestroyEnabled ? 'enabled' : 'disabled'}`));
|
|
101
|
-
console.log(chalk_1.default.dim(` Note: Credits charged at the end of each hour`));
|
|
106
|
+
console.log(chalk_1.default.dim(` Note: Credits charged at the end of each billing hour`));
|
|
102
107
|
}
|
|
103
108
|
else {
|
|
104
109
|
console.error(chalk_1.default.red(`Error: ${result.message}`));
|
|
@@ -337,3 +337,136 @@ exports.profilesCommand
|
|
|
337
337
|
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
338
338
|
}
|
|
339
339
|
});
|
|
340
|
+
// Subcommand: profiles edit [name]
|
|
341
|
+
exports.profilesCommand
|
|
342
|
+
.command('edit [name]')
|
|
343
|
+
.description('Interactively edit a profile')
|
|
344
|
+
.action(async (name) => {
|
|
345
|
+
try {
|
|
346
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
347
|
+
const loadResult = await configLoader.load();
|
|
348
|
+
if (!loadResult.config) {
|
|
349
|
+
console.log(chalk_1.default.red('Not a genbox project'));
|
|
350
|
+
console.log(chalk_1.default.dim('No genbox.yaml found in current directory.'));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
|
|
354
|
+
if (configVersion === 'invalid') {
|
|
355
|
+
console.log(chalk_1.default.red('Unknown config version'));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const config = loadResult.config;
|
|
359
|
+
const profiles = configLoader.listProfiles(config);
|
|
360
|
+
if (profiles.length === 0) {
|
|
361
|
+
console.log(chalk_1.default.yellow('No profiles defined'));
|
|
362
|
+
console.log(chalk_1.default.dim('Run "genbox profiles create" to create a profile first'));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// If no name provided, let user select from list
|
|
366
|
+
let profileName = name;
|
|
367
|
+
if (!profileName) {
|
|
368
|
+
const profileChoices = profiles.map(p => ({
|
|
369
|
+
name: `${p.name}${p.description ? ` - ${p.description}` : ''}`,
|
|
370
|
+
value: p.name,
|
|
371
|
+
}));
|
|
372
|
+
profileName = await prompts.select({
|
|
373
|
+
message: 'Select profile to edit:',
|
|
374
|
+
choices: profileChoices,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
// Check if profile exists
|
|
378
|
+
if (!config.profiles?.[profileName]) {
|
|
379
|
+
console.log(chalk_1.default.red(`Profile '${profileName}' not found in genbox.yaml`));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const existingProfile = config.profiles[profileName];
|
|
383
|
+
console.log(chalk_1.default.blue(`\nEditing Profile: ${profileName}\n`));
|
|
384
|
+
// Description
|
|
385
|
+
const description = await prompts.input({
|
|
386
|
+
message: 'Description:',
|
|
387
|
+
default: existingProfile.description || '',
|
|
388
|
+
});
|
|
389
|
+
// Apps selection
|
|
390
|
+
const appChoices = Object.entries(config.apps)
|
|
391
|
+
.filter(([_, app]) => app.type !== 'library')
|
|
392
|
+
.map(([appName, app]) => ({
|
|
393
|
+
name: `${appName} (${app.type})`,
|
|
394
|
+
value: appName,
|
|
395
|
+
checked: existingProfile.apps?.includes(appName) ?? false,
|
|
396
|
+
}));
|
|
397
|
+
const selectedApps = await prompts.checkbox({
|
|
398
|
+
message: 'Select apps to include:',
|
|
399
|
+
choices: appChoices,
|
|
400
|
+
});
|
|
401
|
+
// Size
|
|
402
|
+
const currentSize = existingProfile.size || 'medium';
|
|
403
|
+
const size = await prompts.select({
|
|
404
|
+
message: 'Server size:',
|
|
405
|
+
choices: [
|
|
406
|
+
{ name: 'Small', value: 'small' },
|
|
407
|
+
{ name: 'Medium', value: 'medium' },
|
|
408
|
+
{ name: 'Large', value: 'large' },
|
|
409
|
+
{ name: 'XL', value: 'xl' },
|
|
410
|
+
],
|
|
411
|
+
default: currentSize,
|
|
412
|
+
});
|
|
413
|
+
// Connect to environment
|
|
414
|
+
const currentConnection = existingProfile.default_connection || 'local';
|
|
415
|
+
const connectTo = await prompts.select({
|
|
416
|
+
message: 'How should dependencies be resolved?',
|
|
417
|
+
choices: [
|
|
418
|
+
{ name: 'Include locally', value: 'local' },
|
|
419
|
+
{ name: 'Connect to staging', value: 'staging' },
|
|
420
|
+
{ name: 'Connect to production', value: 'production' },
|
|
421
|
+
],
|
|
422
|
+
default: currentConnection,
|
|
423
|
+
});
|
|
424
|
+
// Database mode - determine current setting
|
|
425
|
+
let currentDbMode = 'none';
|
|
426
|
+
if (existingProfile.database) {
|
|
427
|
+
if (existingProfile.database.mode === 'local') {
|
|
428
|
+
currentDbMode = 'local';
|
|
429
|
+
}
|
|
430
|
+
else if (existingProfile.database.mode === 'copy') {
|
|
431
|
+
currentDbMode = `copy-${existingProfile.database.source || 'staging'}`;
|
|
432
|
+
}
|
|
433
|
+
else if (existingProfile.database.mode === 'remote') {
|
|
434
|
+
currentDbMode = `remote-${existingProfile.database.source || 'staging'}`;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const dbMode = await prompts.select({
|
|
438
|
+
message: 'Database mode:',
|
|
439
|
+
choices: [
|
|
440
|
+
{ name: 'None', value: 'none' },
|
|
441
|
+
{ name: 'Fresh (empty)', value: 'local' },
|
|
442
|
+
{ name: 'Copy from staging', value: 'copy-staging' },
|
|
443
|
+
{ name: 'Connect to staging', value: 'remote-staging' },
|
|
444
|
+
],
|
|
445
|
+
default: currentDbMode,
|
|
446
|
+
});
|
|
447
|
+
// Build updated profile
|
|
448
|
+
const updatedProfile = {
|
|
449
|
+
description: description || undefined,
|
|
450
|
+
size: size,
|
|
451
|
+
apps: selectedApps,
|
|
452
|
+
default_connection: connectTo !== 'local' ? connectTo : undefined,
|
|
453
|
+
database: dbMode !== 'none' ? {
|
|
454
|
+
mode: dbMode.startsWith('copy') ? 'copy' : dbMode.startsWith('remote') ? 'remote' : 'local',
|
|
455
|
+
source: dbMode.includes('staging') ? 'staging' : dbMode.includes('production') ? 'production' : undefined,
|
|
456
|
+
} : undefined,
|
|
457
|
+
};
|
|
458
|
+
// Update and save
|
|
459
|
+
config.profiles[profileName] = updatedProfile;
|
|
460
|
+
const configPath = configLoader.getConfigPath();
|
|
461
|
+
const yamlContent = yaml.dump(config, { lineWidth: 120, noRefs: true });
|
|
462
|
+
fs.writeFileSync(configPath, yamlContent);
|
|
463
|
+
console.log(chalk_1.default.green(`\n✔ Profile '${profileName}' updated in genbox.yaml`));
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
if (error.name === 'ExitPromptError') {
|
|
467
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
471
|
+
}
|
|
472
|
+
});
|