genbox 1.0.47 → 1.0.49
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 +62 -0
- package/dist/commands/create.js +189 -9
- package/dist/commands/db-sync.js +1 -1
- package/dist/commands/help.js +15 -13
- package/dist/commands/init.js +103 -1
- package/dist/commands/migrate.js +18 -215
- package/dist/commands/profiles.js +5 -11
- package/dist/commands/push.js +70 -32
- package/dist/commands/rebuild.js +3 -8
- package/dist/commands/validate.js +13 -13
- package/dist/config-explainer.js +1 -1
- package/dist/config-loader.js +74 -150
- package/dist/db-utils.js +416 -0
- package/dist/index.js +1 -1
- package/dist/profile-resolver.js +35 -34
- package/dist/schema-v4.js +35 -35
- package/dist/strict-mode.js +57 -126
- package/package.json +3 -2
- package/dist/migration.js +0 -335
- package/dist/schema-v3.js +0 -48
package/dist/api.js
CHANGED
|
@@ -8,6 +8,14 @@ exports.handleApiError = handleApiError;
|
|
|
8
8
|
exports.isAuthError = isAuthError;
|
|
9
9
|
exports.fetchApi = fetchApi;
|
|
10
10
|
exports.checkNameAvailability = checkNameAvailability;
|
|
11
|
+
exports.syncProject = syncProject;
|
|
12
|
+
exports.getProjectByName = getProjectByName;
|
|
13
|
+
exports.initiateSnapshotUpload = initiateSnapshotUpload;
|
|
14
|
+
exports.completeSnapshotUpload = completeSnapshotUpload;
|
|
15
|
+
exports.failSnapshotUpload = failSnapshotUpload;
|
|
16
|
+
exports.listProjectSnapshots = listProjectSnapshots;
|
|
17
|
+
exports.getLatestSnapshot = getLatestSnapshot;
|
|
18
|
+
exports.getSnapshotDownloadUrl = getSnapshotDownloadUrl;
|
|
11
19
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
20
|
const config_store_1 = require("./config-store");
|
|
13
21
|
const API_URL = process.env.GENBOX_API_URL || 'https://api.genbox.dev';
|
|
@@ -97,3 +105,57 @@ async function fetchApi(endpoint, options = {}) {
|
|
|
97
105
|
async function checkNameAvailability(name, workspace) {
|
|
98
106
|
return fetchApi(`/genboxes/check-name?name=${encodeURIComponent(name)}&workspace=${encodeURIComponent(workspace)}`);
|
|
99
107
|
}
|
|
108
|
+
async function syncProject(payload) {
|
|
109
|
+
return fetchApi('/projects', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
body: JSON.stringify(payload),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async function getProjectByName(name) {
|
|
115
|
+
return fetchApi(`/projects/by-name/${encodeURIComponent(name)}`);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Initiate a snapshot upload - get pre-signed URL
|
|
119
|
+
*/
|
|
120
|
+
async function initiateSnapshotUpload(payload) {
|
|
121
|
+
return fetchApi('/database-snapshots/initiate-upload', {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: JSON.stringify(payload),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Complete a snapshot upload after file is uploaded to S3
|
|
128
|
+
*/
|
|
129
|
+
async function completeSnapshotUpload(snapshotId, payload) {
|
|
130
|
+
return fetchApi(`/database-snapshots/${snapshotId}/complete`, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
body: JSON.stringify(payload),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Mark snapshot upload as failed
|
|
137
|
+
*/
|
|
138
|
+
async function failSnapshotUpload(snapshotId, error) {
|
|
139
|
+
await fetchApi(`/database-snapshots/${snapshotId}/fail`, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
body: JSON.stringify({ error }),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* List snapshots for a project
|
|
146
|
+
*/
|
|
147
|
+
async function listProjectSnapshots(projectId) {
|
|
148
|
+
return fetchApi(`/database-snapshots/project/${projectId}`);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get latest snapshot for a project by source
|
|
152
|
+
*/
|
|
153
|
+
async function getLatestSnapshot(projectId, source) {
|
|
154
|
+
return fetchApi(`/database-snapshots/project/${projectId}/latest?source=${source}`);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get download URL for a snapshot
|
|
158
|
+
*/
|
|
159
|
+
async function getSnapshotDownloadUrl(snapshotId) {
|
|
160
|
+
return fetchApi(`/database-snapshots/${snapshotId}/download`);
|
|
161
|
+
}
|
package/dist/commands/create.js
CHANGED
|
@@ -51,6 +51,7 @@ const ssh_config_1 = require("../ssh-config");
|
|
|
51
51
|
const schema_v4_1 = require("../schema-v4");
|
|
52
52
|
const child_process_1 = require("child_process");
|
|
53
53
|
const random_name_1 = require("../random-name");
|
|
54
|
+
const db_utils_1 = require("../db-utils");
|
|
54
55
|
// Credits consumed per hour for each size (matches API billing.config.ts)
|
|
55
56
|
const CREDITS_PER_HOUR = {
|
|
56
57
|
cx22: 1,
|
|
@@ -80,6 +81,23 @@ function spawnSshConfigSetup(genboxId, name) {
|
|
|
80
81
|
// Allow parent to exit independently
|
|
81
82
|
child.unref();
|
|
82
83
|
}
|
|
84
|
+
const DETECTED_DIR = '.genbox';
|
|
85
|
+
const PROJECT_CACHE_FILENAME = 'project.json';
|
|
86
|
+
/**
|
|
87
|
+
* Load project cache from local storage
|
|
88
|
+
*/
|
|
89
|
+
function loadProjectCache(rootDir) {
|
|
90
|
+
const cachePath = path.join(rootDir, DETECTED_DIR, PROJECT_CACHE_FILENAME);
|
|
91
|
+
if (!fs.existsSync(cachePath))
|
|
92
|
+
return null;
|
|
93
|
+
try {
|
|
94
|
+
const content = fs.readFileSync(cachePath, 'utf8');
|
|
95
|
+
return JSON.parse(content);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
83
101
|
async function provisionGenbox(payload) {
|
|
84
102
|
return (0, api_1.fetchApi)('/genboxes', {
|
|
85
103
|
method: 'POST',
|
|
@@ -220,6 +238,8 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
220
238
|
.option('--api <mode>', 'API mode: local, staging, production')
|
|
221
239
|
.option('--db <mode>', 'Database mode: none, local, copy, remote')
|
|
222
240
|
.option('--db-source <source>', 'Database source: staging, production')
|
|
241
|
+
.option('--db-dump <path>', 'Use existing mongodump file instead of creating one')
|
|
242
|
+
.option('--db-copy-remote', 'Copy database on the server (requires publicly accessible DB)')
|
|
223
243
|
.option('-s, --size <size>', 'Server size: small, medium, large, xl')
|
|
224
244
|
.option('-b, --branch <branch>', 'Use existing git branch (skips new branch creation)')
|
|
225
245
|
.option('-n, --new-branch <name>', 'Create a new branch with this name (defaults to env name)')
|
|
@@ -238,7 +258,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
238
258
|
return;
|
|
239
259
|
}
|
|
240
260
|
const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
|
|
241
|
-
if (configVersion === '
|
|
261
|
+
if (configVersion === 'invalid') {
|
|
242
262
|
console.log(chalk_1.default.red('Unknown config version'));
|
|
243
263
|
console.log(chalk_1.default.dim('Run "genbox init" to create a new configuration.'));
|
|
244
264
|
return;
|
|
@@ -410,8 +430,102 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
410
430
|
}
|
|
411
431
|
}
|
|
412
432
|
}
|
|
433
|
+
// Handle database copy - dump locally and upload to S3
|
|
434
|
+
let snapshotId;
|
|
435
|
+
let snapshotS3Key;
|
|
436
|
+
let localDumpPath;
|
|
437
|
+
const projectCache = loadProjectCache(process.cwd());
|
|
438
|
+
const needsLocalDbCopy = resolved.database.mode === 'copy' &&
|
|
439
|
+
resolved.database.url &&
|
|
440
|
+
!options.dbCopyRemote;
|
|
441
|
+
if (needsLocalDbCopy) {
|
|
442
|
+
// Check for user-provided dump file
|
|
443
|
+
if (options.dbDump) {
|
|
444
|
+
if (!fs.existsSync(options.dbDump)) {
|
|
445
|
+
console.log(chalk_1.default.red(`Database dump file not found: ${options.dbDump}`));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
localDumpPath = options.dbDump;
|
|
449
|
+
console.log(chalk_1.default.dim(` Using provided dump file: ${options.dbDump}`));
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
// Need to run mongodump locally
|
|
453
|
+
if (!(0, db_utils_1.isMongoDumpAvailable)()) {
|
|
454
|
+
console.log(chalk_1.default.red('mongodump not found. Required for database copy.'));
|
|
455
|
+
console.log('');
|
|
456
|
+
console.log((0, db_utils_1.getMongoDumpInstallInstructions)());
|
|
457
|
+
console.log('');
|
|
458
|
+
console.log(chalk_1.default.dim('Alternatively:'));
|
|
459
|
+
console.log(chalk_1.default.dim(' • Use --db-dump <path> to provide an existing dump file'));
|
|
460
|
+
console.log(chalk_1.default.dim(' • Use --db-copy-remote if your database is publicly accessible'));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const dbUrl = resolved.database.url;
|
|
464
|
+
console.log('');
|
|
465
|
+
console.log(chalk_1.default.blue('=== Database Copy ==='));
|
|
466
|
+
console.log(chalk_1.default.dim(` Source: ${resolved.database.source}`));
|
|
467
|
+
console.log(chalk_1.default.dim(` URL: ${dbUrl.replace(/\/\/[^:]+:[^@]+@/, '//***:***@')}`));
|
|
468
|
+
const dumpSpinner = (0, ora_1.default)('Creating database dump...').start();
|
|
469
|
+
const dumpResult = await (0, db_utils_1.runLocalMongoDump)(dbUrl, {
|
|
470
|
+
onProgress: (msg) => dumpSpinner.text = msg,
|
|
471
|
+
});
|
|
472
|
+
if (!dumpResult.success) {
|
|
473
|
+
dumpSpinner.fail(chalk_1.default.red('Database dump failed'));
|
|
474
|
+
console.log(chalk_1.default.red(` ${dumpResult.error}`));
|
|
475
|
+
console.log('');
|
|
476
|
+
console.log(chalk_1.default.dim('You can:'));
|
|
477
|
+
console.log(chalk_1.default.dim(' • Fix the database connection and try again'));
|
|
478
|
+
console.log(chalk_1.default.dim(' • Use --db local to start with an empty database'));
|
|
479
|
+
console.log(chalk_1.default.dim(' • Use --db-dump <path> to provide an existing dump'));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
dumpSpinner.succeed(chalk_1.default.green(`Database dump created (${(0, db_utils_1.formatBytes)(dumpResult.sizeBytes || 0)})`));
|
|
483
|
+
localDumpPath = dumpResult.dumpPath;
|
|
484
|
+
}
|
|
485
|
+
// Upload to S3 if we have a project ID
|
|
486
|
+
if (localDumpPath && projectCache?._id) {
|
|
487
|
+
const uploadSpinner = (0, ora_1.default)('Uploading database snapshot...').start();
|
|
488
|
+
const snapshotSource = (resolved.database.source === 'staging' ? 'staging' :
|
|
489
|
+
resolved.database.source === 'production' ? 'production' : 'local');
|
|
490
|
+
const snapshotResult = await (0, db_utils_1.createAndUploadSnapshot)(localDumpPath, projectCache._id, snapshotSource, {
|
|
491
|
+
sourceUrl: resolved.database.url?.replace(/\/\/[^:]+:[^@]+@/, '//***:***@'),
|
|
492
|
+
onProgress: (msg) => uploadSpinner.text = msg,
|
|
493
|
+
});
|
|
494
|
+
if (snapshotResult.success) {
|
|
495
|
+
uploadSpinner.succeed(chalk_1.default.green('Database snapshot uploaded'));
|
|
496
|
+
snapshotId = snapshotResult.snapshotId;
|
|
497
|
+
snapshotS3Key = snapshotResult.s3Key;
|
|
498
|
+
// Cleanup local dump since it's now in S3
|
|
499
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
500
|
+
localDumpPath = undefined;
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
uploadSpinner.warn(chalk_1.default.yellow(`Snapshot upload failed: ${snapshotResult.error}`));
|
|
504
|
+
console.log(chalk_1.default.dim(' Will fall back to direct SCP upload after genbox creation'));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
413
508
|
// Build payload
|
|
414
|
-
const
|
|
509
|
+
const payloadResolved = { ...resolved };
|
|
510
|
+
if (snapshotId && snapshotS3Key) {
|
|
511
|
+
// Use S3 snapshot mode
|
|
512
|
+
payloadResolved.database = {
|
|
513
|
+
...resolved.database,
|
|
514
|
+
url: undefined,
|
|
515
|
+
mode: 'snapshot',
|
|
516
|
+
snapshotId,
|
|
517
|
+
s3Key: snapshotS3Key,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
else if (localDumpPath) {
|
|
521
|
+
// Fall back to SCP upload mode (no project or S3 upload failed)
|
|
522
|
+
payloadResolved.database = {
|
|
523
|
+
...resolved.database,
|
|
524
|
+
url: undefined,
|
|
525
|
+
mode: 'copy-local',
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
const payload = buildPayload(payloadResolved, config, publicKey, privateKeyContent, configLoader);
|
|
415
529
|
// Create genbox
|
|
416
530
|
const spinner = (0, ora_1.default)(`Creating Genbox '${name}'...`).start();
|
|
417
531
|
try {
|
|
@@ -431,12 +545,69 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
431
545
|
}
|
|
432
546
|
// Display results
|
|
433
547
|
displayGenboxInfo(genbox, resolved);
|
|
548
|
+
// Handle database upload if we have a local dump
|
|
549
|
+
if (localDumpPath && genbox.ipAddress) {
|
|
550
|
+
console.log('');
|
|
551
|
+
console.log(chalk_1.default.blue('=== Database Restore ==='));
|
|
552
|
+
// Wait for SSH access
|
|
553
|
+
const sshSpinner = (0, ora_1.default)('Waiting for SSH access...').start();
|
|
554
|
+
const sshReady = await (0, db_utils_1.waitForSshAccess)(genbox.ipAddress, 300, (msg) => {
|
|
555
|
+
sshSpinner.text = msg;
|
|
556
|
+
});
|
|
557
|
+
if (!sshReady) {
|
|
558
|
+
sshSpinner.fail(chalk_1.default.yellow('SSH not ready - database will need manual restore'));
|
|
559
|
+
console.log(chalk_1.default.dim(' Run `genbox db sync` after the server is ready'));
|
|
560
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
sshSpinner.succeed('SSH connected');
|
|
564
|
+
// Upload dump
|
|
565
|
+
const uploadSpinner = (0, ora_1.default)('Uploading database dump...').start();
|
|
566
|
+
const uploadResult = await (0, db_utils_1.uploadDumpToGenbox)(localDumpPath, genbox.ipAddress, {
|
|
567
|
+
onProgress: (msg) => uploadSpinner.text = msg,
|
|
568
|
+
});
|
|
569
|
+
if (!uploadResult.success) {
|
|
570
|
+
uploadSpinner.fail(chalk_1.default.yellow('Upload failed - database will need manual restore'));
|
|
571
|
+
console.log(chalk_1.default.dim(` ${uploadResult.error}`));
|
|
572
|
+
console.log(chalk_1.default.dim(' Run `genbox db sync` after the server is ready'));
|
|
573
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
uploadSpinner.succeed('Dump uploaded');
|
|
577
|
+
// Trigger restore
|
|
578
|
+
const restoreSpinner = (0, ora_1.default)('Restoring database...').start();
|
|
579
|
+
const restoreResult = await (0, db_utils_1.runRemoteMongoRestore)(genbox.ipAddress, config.project.name, { onProgress: (msg) => restoreSpinner.text = msg });
|
|
580
|
+
if (!restoreResult.success) {
|
|
581
|
+
restoreSpinner.fail(chalk_1.default.yellow('Restore failed'));
|
|
582
|
+
console.log(chalk_1.default.dim(` ${restoreResult.error}`));
|
|
583
|
+
console.log(chalk_1.default.dim(' The dump file is on the server - you can restore manually'));
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
restoreSpinner.succeed(chalk_1.default.green('Database restored successfully!'));
|
|
587
|
+
}
|
|
588
|
+
// Cleanup local dump
|
|
589
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else if (localDumpPath) {
|
|
594
|
+
// No IP yet - inform user to run db sync later
|
|
595
|
+
console.log('');
|
|
596
|
+
console.log(chalk_1.default.yellow('Database dump created but IP not yet assigned.'));
|
|
597
|
+
console.log(chalk_1.default.dim(' Run `genbox db sync` once the server is ready to restore the database.'));
|
|
598
|
+
// Keep the dump file - user might want to use it
|
|
599
|
+
console.log(chalk_1.default.dim(` Dump file: ${localDumpPath}`));
|
|
600
|
+
}
|
|
434
601
|
// Inform user about server provisioning
|
|
435
602
|
console.log('');
|
|
436
603
|
console.log(chalk_1.default.dim('Server is provisioning. Run `genbox connect` once ready.'));
|
|
437
604
|
}
|
|
438
605
|
catch (error) {
|
|
439
606
|
spinner.fail(chalk_1.default.red(`Failed to create Genbox: ${error.message}`));
|
|
607
|
+
// Cleanup dump on failure
|
|
608
|
+
if (localDumpPath) {
|
|
609
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
610
|
+
}
|
|
440
611
|
if (error instanceof api_1.AuthenticationError) {
|
|
441
612
|
console.log('');
|
|
442
613
|
console.log(chalk_1.default.yellow(' Please authenticate first:'));
|
|
@@ -509,6 +680,16 @@ function displayResolvedConfig(resolved) {
|
|
|
509
680
|
if (hasBackendApps && resolved.database.mode !== 'none') {
|
|
510
681
|
console.log('');
|
|
511
682
|
console.log(` ${chalk_1.default.bold('Database:')} ${resolved.database.mode}${resolved.database.source ? ` (from ${resolved.database.source})` : ''}`);
|
|
683
|
+
// Show info for database copy mode
|
|
684
|
+
if (resolved.database.mode === 'copy' && resolved.database.source) {
|
|
685
|
+
if (resolved.database.url) {
|
|
686
|
+
console.log(chalk_1.default.dim(` Source URL: ${resolved.database.url.replace(/\/\/[^:]+:[^@]+@/, '//***:***@')}`));
|
|
687
|
+
console.log(chalk_1.default.dim(` Database will be dumped locally and uploaded to the genbox`));
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
console.log(chalk_1.default.yellow(` ⚠ No database URL found in environments.${resolved.database.source}.urls.mongodb`));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
512
693
|
}
|
|
513
694
|
if (Object.keys(resolved.env).length > 0) {
|
|
514
695
|
console.log('');
|
|
@@ -735,13 +916,8 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
735
916
|
// Build services map
|
|
736
917
|
const services = {};
|
|
737
918
|
for (const app of resolved.apps) {
|
|
738
|
-
if (app.
|
|
739
|
-
|
|
740
|
-
services[name] = { port: svc.port, healthcheck: svc.healthcheck };
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
else if (app.port) {
|
|
744
|
-
services[app.name] = { port: app.port };
|
|
919
|
+
if (app.port) {
|
|
920
|
+
services[app.name] = { port: app.port, healthcheck: app.healthcheck };
|
|
745
921
|
}
|
|
746
922
|
}
|
|
747
923
|
// Build files bundle
|
|
@@ -848,6 +1024,8 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
848
1024
|
}
|
|
849
1025
|
// Get local git config for commits
|
|
850
1026
|
const gitConfig = getGitConfig();
|
|
1027
|
+
// Load project cache to get project ID
|
|
1028
|
+
const projectCache = loadProjectCache(process.cwd());
|
|
851
1029
|
return {
|
|
852
1030
|
name: resolved.name,
|
|
853
1031
|
size: resolved.size,
|
|
@@ -887,6 +1065,8 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
887
1065
|
branch: resolved.repos[0]?.branch,
|
|
888
1066
|
newBranch: resolved.repos[0]?.newBranch,
|
|
889
1067
|
sourceBranch: resolved.repos[0]?.sourceBranch,
|
|
1068
|
+
// Project reference
|
|
1069
|
+
project: projectCache?._id,
|
|
890
1070
|
};
|
|
891
1071
|
}
|
|
892
1072
|
/**
|
package/dist/commands/db-sync.js
CHANGED
|
@@ -78,7 +78,7 @@ exports.dbSyncCommand
|
|
|
78
78
|
const config = loadResult.config;
|
|
79
79
|
const source = options.source;
|
|
80
80
|
// Validate source environment exists
|
|
81
|
-
if (config.version ===
|
|
81
|
+
if (config.version === 4 && !config.environments?.[source]) {
|
|
82
82
|
console.log(chalk_1.default.yellow(`Environment '${source}' not configured in genbox.yaml`));
|
|
83
83
|
console.log(chalk_1.default.dim('Available environments:'));
|
|
84
84
|
for (const env of Object.keys(config.environments || {})) {
|
package/dist/commands/help.js
CHANGED
|
@@ -14,6 +14,8 @@ exports.helpCommand = new commander_1.Command('help')
|
|
|
14
14
|
console.log(chalk_1.default.bold('Genbox CLI - AI-Powered Development Environments'));
|
|
15
15
|
console.log(chalk_1.default.dim('Create isolated cloud development environments on demand'));
|
|
16
16
|
console.log('');
|
|
17
|
+
console.log(chalk_1.default.green('TIP:') + ' Use ' + chalk_1.default.cyan('gb') + ' as a shorthand for ' + chalk_1.default.cyan('genbox') + ' (e.g., ' + chalk_1.default.dim('gb list') + ')');
|
|
18
|
+
console.log('');
|
|
17
19
|
if (command) {
|
|
18
20
|
showCommandHelp(command);
|
|
19
21
|
}
|
|
@@ -45,34 +47,34 @@ function showGeneralHelp() {
|
|
|
45
47
|
console.log(chalk_1.default.bold('QUICK START'));
|
|
46
48
|
console.log('');
|
|
47
49
|
console.log(' 1. Initialize your project:');
|
|
48
|
-
console.log(chalk_1.default.dim(' $
|
|
50
|
+
console.log(chalk_1.default.dim(' $ gb init'));
|
|
49
51
|
console.log('');
|
|
50
52
|
console.log(' 2. Create a development environment:');
|
|
51
|
-
console.log(chalk_1.default.dim(' $
|
|
53
|
+
console.log(chalk_1.default.dim(' $ gb create feature-auth'));
|
|
52
54
|
console.log('');
|
|
53
55
|
console.log(' 3. Check progress (setup takes a few minutes):');
|
|
54
|
-
console.log(chalk_1.default.dim(' $
|
|
56
|
+
console.log(chalk_1.default.dim(' $ gb status feature-auth'));
|
|
55
57
|
console.log('');
|
|
56
58
|
console.log(' 4. Connect and start coding:');
|
|
57
|
-
console.log(chalk_1.default.dim(' $
|
|
59
|
+
console.log(chalk_1.default.dim(' $ gb connect feature-auth'));
|
|
58
60
|
console.log('');
|
|
59
61
|
console.log(' 5. Clean up when done:');
|
|
60
|
-
console.log(chalk_1.default.dim(' $
|
|
62
|
+
console.log(chalk_1.default.dim(' $ gb destroy feature-auth'));
|
|
61
63
|
console.log('');
|
|
62
64
|
console.log(chalk_1.default.bold('WORKFLOW EXAMPLES'));
|
|
63
65
|
console.log('');
|
|
64
66
|
console.log(chalk_1.default.yellow(' Feature Development:'));
|
|
65
|
-
console.log(chalk_1.default.dim(' $
|
|
66
|
-
console.log(chalk_1.default.dim(' $
|
|
67
|
-
console.log(chalk_1.default.dim(' $
|
|
68
|
-
console.log(chalk_1.default.dim(' $
|
|
67
|
+
console.log(chalk_1.default.dim(' $ gb create feat-new-checkout'));
|
|
68
|
+
console.log(chalk_1.default.dim(' $ gb status feat-new-checkout # Wait for setup'));
|
|
69
|
+
console.log(chalk_1.default.dim(' $ gb urls feat-new-checkout # Get service URLs'));
|
|
70
|
+
console.log(chalk_1.default.dim(' $ gb connect feat-new-checkout # SSH to code'));
|
|
69
71
|
console.log('');
|
|
70
72
|
console.log(chalk_1.default.yellow(' Local Debugging with Port Forwarding:'));
|
|
71
|
-
console.log(chalk_1.default.dim(' $
|
|
73
|
+
console.log(chalk_1.default.dim(' $ gb forward feat-new-checkout'));
|
|
72
74
|
console.log(chalk_1.default.dim(' # Now access remote services at localhost:3050, etc.'));
|
|
73
75
|
console.log('');
|
|
74
76
|
console.log(chalk_1.default.yellow(' Database Sync:'));
|
|
75
|
-
console.log(chalk_1.default.dim(' $
|
|
77
|
+
console.log(chalk_1.default.dim(' $ gb restore-db feat-new-checkout'));
|
|
76
78
|
console.log(chalk_1.default.dim(' # Copies your local MongoDB to the Genbox'));
|
|
77
79
|
console.log('');
|
|
78
80
|
console.log(chalk_1.default.bold('CONFIGURATION FILES'));
|
|
@@ -80,12 +82,12 @@ function showGeneralHelp() {
|
|
|
80
82
|
console.log(` ${chalk_1.default.cyan('genbox.yaml')} Project configuration (services, ports, repos)`);
|
|
81
83
|
console.log(` ${chalk_1.default.cyan('.env.genbox')} Environment variables (secrets, tokens)`);
|
|
82
84
|
console.log('');
|
|
83
|
-
console.log(chalk_1.default.dim(' Run `
|
|
85
|
+
console.log(chalk_1.default.dim(' Run `gb init` to create these files interactively.'));
|
|
84
86
|
console.log('');
|
|
85
87
|
console.log(chalk_1.default.bold('MORE HELP'));
|
|
86
88
|
console.log('');
|
|
87
89
|
console.log(' Get help for a specific command:');
|
|
88
|
-
console.log(chalk_1.default.dim(' $
|
|
90
|
+
console.log(chalk_1.default.dim(' $ gb help <command>'));
|
|
89
91
|
console.log('');
|
|
90
92
|
console.log(' Documentation: https://genbox.dev/docs');
|
|
91
93
|
console.log(' Issues: https://github.com/goodpass-co/genbox/issues');
|
package/dist/commands/init.js
CHANGED
|
@@ -59,12 +59,63 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
59
59
|
const yaml = __importStar(require("js-yaml"));
|
|
60
60
|
const process = __importStar(require("process"));
|
|
61
61
|
const scanner_1 = require("../scanner");
|
|
62
|
+
const api_1 = require("../api");
|
|
63
|
+
const config_store_1 = require("../config-store");
|
|
62
64
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
63
65
|
const { version } = require('../../package.json');
|
|
64
66
|
const CONFIG_FILENAME = 'genbox.yaml';
|
|
65
67
|
const ENV_FILENAME = '.env.genbox';
|
|
66
68
|
const DETECTED_DIR = '.genbox';
|
|
67
69
|
const DETECTED_FILENAME = 'detected.yaml';
|
|
70
|
+
const PROJECT_CACHE_FILENAME = 'project.json';
|
|
71
|
+
/**
|
|
72
|
+
* Save project ID and info to local cache
|
|
73
|
+
*/
|
|
74
|
+
function saveProjectCache(rootDir, cache) {
|
|
75
|
+
const genboxDir = path_1.default.join(rootDir, DETECTED_DIR);
|
|
76
|
+
if (!fs_1.default.existsSync(genboxDir)) {
|
|
77
|
+
fs_1.default.mkdirSync(genboxDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
const cachePath = path_1.default.join(genboxDir, PROJECT_CACHE_FILENAME);
|
|
80
|
+
fs_1.default.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Load project cache from local storage
|
|
84
|
+
*/
|
|
85
|
+
function loadProjectCache(rootDir) {
|
|
86
|
+
const cachePath = path_1.default.join(rootDir, DETECTED_DIR, PROJECT_CACHE_FILENAME);
|
|
87
|
+
if (!fs_1.default.existsSync(cachePath))
|
|
88
|
+
return null;
|
|
89
|
+
try {
|
|
90
|
+
const content = fs_1.default.readFileSync(cachePath, 'utf8');
|
|
91
|
+
return JSON.parse(content);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Convert GenboxConfig to ProjectSyncPayload
|
|
99
|
+
*/
|
|
100
|
+
function configToSyncPayload(config, envVars, privateKey) {
|
|
101
|
+
return {
|
|
102
|
+
name: config.project.name,
|
|
103
|
+
version: config.version,
|
|
104
|
+
project: config.project,
|
|
105
|
+
apps: config.apps,
|
|
106
|
+
provides: config.provides,
|
|
107
|
+
environments: config.environments,
|
|
108
|
+
profiles: config.profiles,
|
|
109
|
+
defaults: config.defaults,
|
|
110
|
+
repos: config.repos,
|
|
111
|
+
hooks: config.hooks,
|
|
112
|
+
scripts: config.scripts,
|
|
113
|
+
strict: config.strict,
|
|
114
|
+
git_auth: config.git_auth,
|
|
115
|
+
envVars,
|
|
116
|
+
privateKey,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
68
119
|
// =============================================================================
|
|
69
120
|
// Scan Phase
|
|
70
121
|
// =============================================================================
|
|
@@ -969,7 +1020,7 @@ function generateDefaultProfiles(detected, environments) {
|
|
|
969
1020
|
// Config Generation
|
|
970
1021
|
// =============================================================================
|
|
971
1022
|
/**
|
|
972
|
-
* Generate
|
|
1023
|
+
* Generate GenboxConfig from detected config and user inputs
|
|
973
1024
|
*/
|
|
974
1025
|
function generateConfig(detected, settings, repos, environments, profiles) {
|
|
975
1026
|
// Convert apps
|
|
@@ -1554,6 +1605,25 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
1554
1605
|
const envContent = generateEnvFile(settings.projectName, detected, { ...gitEnvVars, LOCAL_API_URL: 'http://localhost:3050' }, []);
|
|
1555
1606
|
fs_1.default.writeFileSync(path_1.default.join(rootDir, ENV_FILENAME), envContent);
|
|
1556
1607
|
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME}`));
|
|
1608
|
+
// Sync project to API if logged in
|
|
1609
|
+
if (config_store_1.ConfigStore.getToken()) {
|
|
1610
|
+
try {
|
|
1611
|
+
const payload = configToSyncPayload(config, { ...gitEnvVars, LOCAL_API_URL: 'http://localhost:3050' });
|
|
1612
|
+
const result = await (0, api_1.syncProject)(payload);
|
|
1613
|
+
saveProjectCache(rootDir, {
|
|
1614
|
+
_id: result._id,
|
|
1615
|
+
name: result.name,
|
|
1616
|
+
lastSyncedAt: new Date().toISOString(),
|
|
1617
|
+
action: result.action,
|
|
1618
|
+
});
|
|
1619
|
+
console.log(chalk_1.default.green(`✔ Project synced to Genbox (${result.action})`));
|
|
1620
|
+
}
|
|
1621
|
+
catch (error) {
|
|
1622
|
+
if (!(0, api_1.isAuthError)(error)) {
|
|
1623
|
+
console.log(chalk_1.default.yellow(` Warning: Could not sync project - ${error.message}`));
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1557
1627
|
return;
|
|
1558
1628
|
}
|
|
1559
1629
|
// =========================================
|
|
@@ -1604,6 +1674,38 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
1604
1674
|
}
|
|
1605
1675
|
}
|
|
1606
1676
|
// =========================================
|
|
1677
|
+
// PHASE 8: Sync Project to API
|
|
1678
|
+
// =========================================
|
|
1679
|
+
if (config_store_1.ConfigStore.getToken()) {
|
|
1680
|
+
const syncSpinner = (0, ora_1.default)('Syncing project to Genbox...').start();
|
|
1681
|
+
try {
|
|
1682
|
+
const payload = configToSyncPayload(config, allEnvVars);
|
|
1683
|
+
const result = await (0, api_1.syncProject)(payload);
|
|
1684
|
+
saveProjectCache(rootDir, {
|
|
1685
|
+
_id: result._id,
|
|
1686
|
+
name: result.name,
|
|
1687
|
+
lastSyncedAt: new Date().toISOString(),
|
|
1688
|
+
action: result.action,
|
|
1689
|
+
});
|
|
1690
|
+
syncSpinner.succeed(`Project synced to Genbox (${result.action})`);
|
|
1691
|
+
if (result.changes && result.changes.length > 0) {
|
|
1692
|
+
console.log(chalk_1.default.dim(` Changes: ${result.changes.join(', ')}`));
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
catch (error) {
|
|
1696
|
+
if ((0, api_1.isAuthError)(error)) {
|
|
1697
|
+
syncSpinner.warn('Not logged in - project not synced');
|
|
1698
|
+
console.log(chalk_1.default.dim(' Run `genbox login` to sync projects to your account'));
|
|
1699
|
+
}
|
|
1700
|
+
else {
|
|
1701
|
+
syncSpinner.warn(`Could not sync project - ${error.message}`);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
else {
|
|
1706
|
+
console.log(chalk_1.default.dim(' Project not synced (not logged in). Run `genbox login` to sync.'));
|
|
1707
|
+
}
|
|
1708
|
+
// =========================================
|
|
1607
1709
|
// Show Instructions
|
|
1608
1710
|
// =========================================
|
|
1609
1711
|
const hasBackend = Object.values(detected.apps).some(a => a.type === 'backend' || a.type === 'gateway');
|