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.
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+ /**
3
+ * Database Utilities
4
+ *
5
+ * Handles local mongodump/mongorestore operations and
6
+ * uploading database dumps to genboxes.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.isMongoDumpAvailable = isMongoDumpAvailable;
46
+ exports.getMongoDumpInstallInstructions = getMongoDumpInstallInstructions;
47
+ exports.runLocalMongoDump = runLocalMongoDump;
48
+ exports.uploadDumpToGenbox = uploadDumpToGenbox;
49
+ exports.runRemoteMongoRestore = runRemoteMongoRestore;
50
+ exports.cleanupDump = cleanupDump;
51
+ exports.formatBytes = formatBytes;
52
+ exports.waitForSshAccess = waitForSshAccess;
53
+ exports.uploadDumpToS3 = uploadDumpToS3;
54
+ exports.createAndUploadSnapshot = createAndUploadSnapshot;
55
+ const child_process_1 = require("child_process");
56
+ const fs = __importStar(require("fs"));
57
+ const path = __importStar(require("path"));
58
+ const os = __importStar(require("os"));
59
+ const chalk_1 = __importDefault(require("chalk"));
60
+ /**
61
+ * Check if mongodump is available locally
62
+ */
63
+ function isMongoDumpAvailable() {
64
+ try {
65
+ (0, child_process_1.execSync)('mongodump --version', { stdio: 'ignore' });
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ /**
73
+ * Get instructions for installing mongodb-database-tools
74
+ */
75
+ function getMongoDumpInstallInstructions() {
76
+ const platform = process.platform;
77
+ if (platform === 'darwin') {
78
+ return `Install MongoDB Database Tools:
79
+ ${chalk_1.default.cyan('brew tap mongodb/brew')}
80
+ ${chalk_1.default.cyan('brew install mongodb-database-tools')}`;
81
+ }
82
+ else if (platform === 'linux') {
83
+ return `Install MongoDB Database Tools:
84
+ ${chalk_1.default.cyan('# For Ubuntu/Debian:')}
85
+ ${chalk_1.default.cyan('wget -qO- https://www.mongodb.org/static/pgp/server-7.0.asc | sudo gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg')}
86
+ ${chalk_1.default.cyan('echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list')}
87
+ ${chalk_1.default.cyan('sudo apt-get update && sudo apt-get install -y mongodb-database-tools')}`;
88
+ }
89
+ else {
90
+ return `Install MongoDB Database Tools from:
91
+ ${chalk_1.default.cyan('https://www.mongodb.com/try/download/database-tools')}`;
92
+ }
93
+ }
94
+ /**
95
+ * Run mongodump locally
96
+ */
97
+ async function runLocalMongoDump(sourceUrl, options = {}) {
98
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'genbox-dbdump-'));
99
+ const dumpPath = path.join(tempDir, 'dump.gz');
100
+ return new Promise((resolve) => {
101
+ const args = [
102
+ `--uri=${sourceUrl}`,
103
+ `--archive=${dumpPath}`,
104
+ '--gzip',
105
+ ];
106
+ // Add collection filters
107
+ if (options.collections && options.collections.length > 0) {
108
+ for (const col of options.collections) {
109
+ args.push(`--collection=${col}`);
110
+ }
111
+ }
112
+ if (options.excludeCollections && options.excludeCollections.length > 0) {
113
+ for (const col of options.excludeCollections) {
114
+ args.push(`--excludeCollection=${col}`);
115
+ }
116
+ }
117
+ options.onProgress?.('Starting mongodump...');
118
+ const proc = (0, child_process_1.spawn)('mongodump', args, {
119
+ stdio: ['ignore', 'pipe', 'pipe'],
120
+ });
121
+ let stderr = '';
122
+ proc.stderr?.on('data', (data) => {
123
+ const line = data.toString();
124
+ stderr += line;
125
+ // Parse progress from mongodump output
126
+ if (line.includes('done dumping')) {
127
+ options.onProgress?.(line.trim());
128
+ }
129
+ });
130
+ proc.on('close', (code) => {
131
+ if (code === 0 && fs.existsSync(dumpPath)) {
132
+ const stats = fs.statSync(dumpPath);
133
+ resolve({
134
+ success: true,
135
+ dumpPath,
136
+ sizeBytes: stats.size,
137
+ });
138
+ }
139
+ else {
140
+ // Cleanup on failure
141
+ fs.rmSync(tempDir, { recursive: true, force: true });
142
+ // Parse common errors
143
+ let errorMessage = 'mongodump failed';
144
+ if (stderr.includes('authentication failed')) {
145
+ errorMessage = 'Authentication failed - check your database credentials';
146
+ }
147
+ else if (stderr.includes('connection refused') || stderr.includes('no reachable servers')) {
148
+ errorMessage = 'Connection failed - check if the database URL is correct and accessible';
149
+ }
150
+ else if (stderr.includes('timed out')) {
151
+ errorMessage = 'Connection timed out - the database may not be accessible from your network';
152
+ }
153
+ else if (stderr) {
154
+ errorMessage = stderr.split('\n')[0] || errorMessage;
155
+ }
156
+ resolve({
157
+ success: false,
158
+ error: errorMessage,
159
+ });
160
+ }
161
+ });
162
+ proc.on('error', (err) => {
163
+ fs.rmSync(tempDir, { recursive: true, force: true });
164
+ resolve({
165
+ success: false,
166
+ error: `Failed to run mongodump: ${err.message}`,
167
+ });
168
+ });
169
+ });
170
+ }
171
+ /**
172
+ * Upload dump file to genbox via SCP
173
+ */
174
+ async function uploadDumpToGenbox(dumpPath, ipAddress, options = {}) {
175
+ return new Promise((resolve) => {
176
+ const remotePath = '/home/dev/.db-dump.gz';
177
+ options.onProgress?.(`Uploading dump to genbox (${formatBytes(fs.statSync(dumpPath).size)})...`);
178
+ const proc = (0, child_process_1.spawn)('scp', [
179
+ '-o', 'StrictHostKeyChecking=no',
180
+ '-o', 'UserKnownHostsFile=/dev/null',
181
+ '-o', 'ConnectTimeout=30',
182
+ dumpPath,
183
+ `dev@${ipAddress}:${remotePath}`,
184
+ ], {
185
+ stdio: ['ignore', 'pipe', 'pipe'],
186
+ });
187
+ let stderr = '';
188
+ proc.stderr?.on('data', (data) => {
189
+ stderr += data.toString();
190
+ });
191
+ proc.on('close', (code) => {
192
+ if (code === 0) {
193
+ resolve({ success: true });
194
+ }
195
+ else {
196
+ let errorMessage = 'SCP upload failed';
197
+ if (stderr.includes('Connection refused') || stderr.includes('Connection timed out')) {
198
+ errorMessage = 'Cannot connect to genbox - it may still be provisioning';
199
+ }
200
+ else if (stderr.includes('Permission denied')) {
201
+ errorMessage = 'Permission denied - SSH key may not be configured';
202
+ }
203
+ else if (stderr) {
204
+ errorMessage = stderr.split('\n')[0] || errorMessage;
205
+ }
206
+ resolve({ success: false, error: errorMessage });
207
+ }
208
+ });
209
+ proc.on('error', (err) => {
210
+ resolve({ success: false, error: `Failed to run scp: ${err.message}` });
211
+ });
212
+ });
213
+ }
214
+ /**
215
+ * Run mongorestore on genbox via SSH
216
+ */
217
+ async function runRemoteMongoRestore(ipAddress, dbName, options = {}) {
218
+ return new Promise((resolve) => {
219
+ options.onProgress?.('Restoring database on genbox...');
220
+ // The restore command - uses the uploaded dump file
221
+ const restoreCmd = `
222
+ # Wait for MongoDB to be ready
223
+ for i in {1..30}; do
224
+ if docker exec goodpass-mongodb mongosh --quiet --eval "db.runCommand({ping:1})" 2>/dev/null; then
225
+ break
226
+ fi
227
+ echo "Waiting for MongoDB... ($i/30)"
228
+ sleep 2
229
+ done
230
+
231
+ # Restore the database
232
+ if [ -f /home/dev/.db-dump.gz ]; then
233
+ echo "Restoring database..."
234
+ mongorestore --uri="mongodb://localhost:27017" --db="${dbName}" --archive=/home/dev/.db-dump.gz --gzip --drop
235
+ rm -f /home/dev/.db-dump.gz
236
+ echo "Database restored successfully"
237
+ else
238
+ echo "Error: Dump file not found"
239
+ exit 1
240
+ fi
241
+ `;
242
+ const proc = (0, child_process_1.spawn)('ssh', [
243
+ '-o', 'StrictHostKeyChecking=no',
244
+ '-o', 'UserKnownHostsFile=/dev/null',
245
+ '-o', 'ConnectTimeout=30',
246
+ `dev@${ipAddress}`,
247
+ 'bash', '-c', `'${restoreCmd.replace(/'/g, "'\\''")}'`,
248
+ ], {
249
+ stdio: ['ignore', 'pipe', 'pipe'],
250
+ });
251
+ let stdout = '';
252
+ let stderr = '';
253
+ proc.stdout?.on('data', (data) => {
254
+ const line = data.toString();
255
+ stdout += line;
256
+ if (line.includes('Restoring') || line.includes('restored')) {
257
+ options.onProgress?.(line.trim());
258
+ }
259
+ });
260
+ proc.stderr?.on('data', (data) => {
261
+ stderr += data.toString();
262
+ });
263
+ proc.on('close', (code) => {
264
+ if (code === 0) {
265
+ resolve({ success: true });
266
+ }
267
+ else {
268
+ resolve({
269
+ success: false,
270
+ error: stderr || stdout || 'Restore failed',
271
+ });
272
+ }
273
+ });
274
+ proc.on('error', (err) => {
275
+ resolve({ success: false, error: `Failed to run ssh: ${err.message}` });
276
+ });
277
+ });
278
+ }
279
+ /**
280
+ * Clean up temporary dump files
281
+ */
282
+ function cleanupDump(dumpPath) {
283
+ try {
284
+ const dir = path.dirname(dumpPath);
285
+ if (dir.includes('genbox-dbdump-')) {
286
+ fs.rmSync(dir, { recursive: true, force: true });
287
+ }
288
+ else {
289
+ fs.rmSync(dumpPath, { force: true });
290
+ }
291
+ }
292
+ catch {
293
+ // Ignore cleanup errors
294
+ }
295
+ }
296
+ /**
297
+ * Format bytes to human readable
298
+ */
299
+ function formatBytes(bytes) {
300
+ if (bytes < 1024)
301
+ return `${bytes} B`;
302
+ if (bytes < 1024 * 1024)
303
+ return `${(bytes / 1024).toFixed(1)} KB`;
304
+ if (bytes < 1024 * 1024 * 1024)
305
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
306
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
307
+ }
308
+ /**
309
+ * Wait for genbox to be accessible via SSH
310
+ */
311
+ async function waitForSshAccess(ipAddress, maxWaitSeconds = 300, onProgress) {
312
+ const startTime = Date.now();
313
+ const checkInterval = 5000; // 5 seconds
314
+ while (Date.now() - startTime < maxWaitSeconds * 1000) {
315
+ try {
316
+ (0, child_process_1.execSync)(`ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 dev@${ipAddress} echo ok`, { stdio: 'ignore' });
317
+ return true;
318
+ }
319
+ catch {
320
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
321
+ onProgress?.(`Waiting for genbox to be ready (${elapsed}s)...`);
322
+ await new Promise((r) => setTimeout(r, checkInterval));
323
+ }
324
+ }
325
+ return false;
326
+ }
327
+ /**
328
+ * Upload dump file to S3 using pre-signed URL
329
+ */
330
+ async function uploadDumpToS3(dumpPath, uploadUrl, options = {}) {
331
+ const fileSize = fs.statSync(dumpPath).size;
332
+ options.onProgress?.(`Uploading snapshot to cloud (${formatBytes(fileSize)})...`, 0);
333
+ try {
334
+ // Read file as buffer
335
+ const fileBuffer = fs.readFileSync(dumpPath);
336
+ // Upload to S3 using pre-signed URL
337
+ const response = await fetch(uploadUrl, {
338
+ method: 'PUT',
339
+ headers: {
340
+ 'Content-Type': 'application/gzip',
341
+ 'Content-Length': fileSize.toString(),
342
+ },
343
+ body: fileBuffer,
344
+ });
345
+ if (!response.ok) {
346
+ const errorText = await response.text();
347
+ return {
348
+ success: false,
349
+ error: `Upload failed: ${response.status} ${response.statusText} - ${errorText}`,
350
+ };
351
+ }
352
+ options.onProgress?.('Upload complete', 100);
353
+ return { success: true };
354
+ }
355
+ catch (error) {
356
+ return {
357
+ success: false,
358
+ error: `Upload failed: ${error.message}`,
359
+ };
360
+ }
361
+ }
362
+ async function createAndUploadSnapshot(dumpPath, projectId, source, options = {}) {
363
+ // Import API functions dynamically to avoid circular deps
364
+ const { initiateSnapshotUpload, completeSnapshotUpload, failSnapshotUpload, } = await Promise.resolve().then(() => __importStar(require('./api')));
365
+ const fileSize = fs.statSync(dumpPath).size;
366
+ const snapshotName = options.name || `${source}-${new Date().toISOString().split('T')[0]}`;
367
+ let snapshotId;
368
+ try {
369
+ // Step 1: Initiate upload to get pre-signed URL
370
+ options.onProgress?.('Preparing snapshot upload...');
371
+ const initResult = await initiateSnapshotUpload({
372
+ name: snapshotName,
373
+ projectId,
374
+ source,
375
+ sizeBytes: fileSize,
376
+ contentType: 'application/gzip',
377
+ });
378
+ snapshotId = initResult.snapshotId;
379
+ // Step 2: Upload to S3
380
+ const uploadResult = await uploadDumpToS3(dumpPath, initResult.uploadUrl, {
381
+ onProgress: options.onProgress,
382
+ });
383
+ if (!uploadResult.success) {
384
+ // Mark as failed
385
+ await failSnapshotUpload(snapshotId, uploadResult.error || 'Upload failed');
386
+ return { success: false, error: uploadResult.error };
387
+ }
388
+ // Step 3: Complete the upload
389
+ options.onProgress?.('Finalizing snapshot...');
390
+ await completeSnapshotUpload(snapshotId, {
391
+ sizeBytes: fileSize,
392
+ compressedSizeBytes: fileSize, // Already gzipped
393
+ sourceUrl: options.sourceUrl,
394
+ });
395
+ return {
396
+ success: true,
397
+ snapshotId,
398
+ s3Key: initResult.s3Key,
399
+ };
400
+ }
401
+ catch (error) {
402
+ // Try to mark as failed if we have a snapshot ID
403
+ if (snapshotId) {
404
+ try {
405
+ await failSnapshotUpload(snapshotId, error.message);
406
+ }
407
+ catch {
408
+ // Ignore cleanup error
409
+ }
410
+ }
411
+ return {
412
+ success: false,
413
+ error: error.message,
414
+ };
415
+ }
416
+ }
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ const program = new commander_1.Command();
8
8
  const { version } = require('../package.json');
9
9
  program
10
10
  .name('genbox')
11
- .description('Genbox CLI - AI-Powered Development Environments')
11
+ .description('Genbox CLI - AI-Powered Development Environments\n\nTIP: Use "gb" as a shorthand (e.g., gb list, gb create)')
12
12
  .version(version, '-v, --version', 'Output the current version');
13
13
  const create_1 = require("./commands/create");
14
14
  const list_1 = require("./commands/list");
@@ -60,7 +60,6 @@ class ProfileResolver {
60
60
  */
61
61
  async resolve(config, options) {
62
62
  const warnings = [];
63
- const isV4Config = (0, config_loader_1.isV4)(config);
64
63
  // Step 1: Get base profile (if specified)
65
64
  let profile = {};
66
65
  if (options.profile) {
@@ -193,12 +192,13 @@ class ProfileResolver {
193
192
  const dependencies = {};
194
193
  const infrastructure = (0, config_loader_1.getInfrastructure)(config);
195
194
  // Process each dependency (v3: requires, v4: connects_to)
196
- const appDeps = (0, config_loader_1.isV4)(config)
195
+ const appDeps = true
197
196
  ? Object.entries(appConfig.connects_to || {})
198
- : Object.entries(appConfig.requires || {});
197
+ : Object.entries(appConfig.connects_to || {});
199
198
  for (const [depName, requirement] of appDeps) {
200
- // v3 uses 'none', v4 uses { required: false }
201
- if (requirement === 'none' || (typeof requirement === 'object' && !requirement.required))
199
+ // Skip if not required
200
+ const reqObj = requirement;
201
+ if (!reqObj.required)
202
202
  continue;
203
203
  // Check if dependency is already in selected apps
204
204
  const isLocalApp = selectedApps.includes(depName);
@@ -218,12 +218,12 @@ class ProfileResolver {
218
218
  url,
219
219
  };
220
220
  }
221
- else if ((requirement === 'required' || (typeof requirement === 'object' && requirement.required)) && !options.yes) {
221
+ else if (reqObj.required && !options.yes) {
222
222
  // Prompt user for resolution
223
223
  const resolution = await this.promptDependencyResolution(config, appName, depName, isInfra ? 'infrastructure' : 'app');
224
224
  dependencies[depName] = resolution;
225
225
  }
226
- else if (requirement === 'optional' || (typeof requirement === 'object' && !requirement.required)) {
226
+ else if (!reqObj.required) {
227
227
  // Skip optional dependencies if not explicitly included
228
228
  continue;
229
229
  }
@@ -250,13 +250,13 @@ class ProfileResolver {
250
250
  type,
251
251
  framework,
252
252
  port,
253
- services: appConfig.services,
254
253
  commands: appConfig.commands,
255
254
  env: appConfig.env,
256
255
  runner,
257
256
  docker,
258
257
  healthcheck,
259
258
  dependsOn,
259
+ connections: [], // Will be populated later if needed
260
260
  dependencies,
261
261
  };
262
262
  }
@@ -314,26 +314,25 @@ class ProfileResolver {
314
314
  * Get URL for a dependency from environment config
315
315
  */
316
316
  getUrlForDependency(depName, envConfig) {
317
- // Check if it's an API dependency
318
- if (depName === 'api' && envConfig.api) {
319
- // Check common fields: url, gateway, api, or first string value
320
- const apiConfig = envConfig.api;
321
- return apiConfig.url || apiConfig.gateway || apiConfig.api ||
322
- Object.values(apiConfig).find(v => typeof v === 'string' && v.startsWith('http'));
323
- }
324
- // Check infrastructure
325
- const infraConfig = envConfig[depName];
326
- return infraConfig?.url;
317
+ // Check the urls map in the environment config
318
+ return envConfig.urls?.[depName];
327
319
  }
328
320
  /**
329
321
  * Resolve database mode
330
322
  */
331
323
  async resolveDatabaseMode(config, options, profile) {
324
+ // Helper to get MongoDB URL for an environment
325
+ const getMongoUrl = (source) => {
326
+ const envConfig = config.environments?.[source];
327
+ return envConfig?.urls?.mongodb;
328
+ };
332
329
  // CLI flag takes precedence
333
330
  if (options.db) {
331
+ const source = options.dbSource || profile.database?.source || 'staging';
334
332
  return {
335
333
  mode: options.db,
336
- source: options.dbSource || profile.database?.source || 'staging',
334
+ source,
335
+ url: options.db === 'copy' ? getMongoUrl(source) : undefined,
337
336
  };
338
337
  }
339
338
  // Profile setting
@@ -341,16 +340,16 @@ class ProfileResolver {
341
340
  return {
342
341
  mode: profile.database.mode,
343
342
  source: profile.database.source,
343
+ url: profile.database.mode === 'copy' && profile.database.source
344
+ ? getMongoUrl(profile.database.source)
345
+ : undefined,
344
346
  };
345
347
  }
346
- // If connect_to (v3) or default_connection (v4) is set, use remote
348
+ // If default_connection is set, use remote
347
349
  const profileConnection = (0, config_loader_1.getProfileConnection)(profile);
348
350
  if (profileConnection) {
349
351
  const envConfig = config.environments?.[profileConnection];
350
- // v4 uses urls.mongodb, v3 uses mongodb.url
351
- const mongoUrl = (0, config_loader_1.isV4)(config)
352
- ? envConfig?.urls?.mongodb
353
- : envConfig?.mongodb?.url;
352
+ const mongoUrl = envConfig?.urls?.mongodb;
354
353
  return {
355
354
  mode: 'remote',
356
355
  source: profileConnection,
@@ -362,9 +361,12 @@ class ProfileResolver {
362
361
  return await this.selectDatabaseModeInteractive(config);
363
362
  }
364
363
  // Default
364
+ const defaultMode = config.defaults?.database?.mode || 'local';
365
+ const defaultSource = config.defaults?.database?.source;
365
366
  return {
366
- mode: config.defaults?.database?.mode || 'local',
367
- source: config.defaults?.database?.source,
367
+ mode: defaultMode,
368
+ source: defaultSource,
369
+ url: defaultMode === 'copy' && defaultSource ? getMongoUrl(defaultSource) : undefined,
368
370
  };
369
371
  }
370
372
  /**
@@ -403,15 +405,14 @@ class ProfileResolver {
403
405
  }
404
406
  else if (answer.startsWith('copy-')) {
405
407
  const source = answer.replace('copy-', '');
406
- return { mode: 'copy', source };
408
+ const envConfig = config.environments?.[source];
409
+ const mongoUrl = envConfig?.urls?.mongodb;
410
+ return { mode: 'copy', source, url: mongoUrl };
407
411
  }
408
412
  else if (answer.startsWith('remote-')) {
409
413
  const source = answer.replace('remote-', '');
410
414
  const envConfig = config.environments?.[source];
411
- // v4 uses urls.mongodb, v3 uses mongodb.url
412
- const mongoUrl = (0, config_loader_1.isV4)(config)
413
- ? envConfig?.urls?.mongodb
414
- : envConfig?.mongodb?.url;
415
+ const mongoUrl = envConfig?.urls?.mongodb;
415
416
  return {
416
417
  mode: 'remote',
417
418
  source,
@@ -427,9 +428,9 @@ class ProfileResolver {
427
428
  const localApps = apps.length;
428
429
  const localInfra = infrastructure.filter(i => i.mode === 'local').length;
429
430
  const total = localApps + localInfra;
430
- // Check for heavy services
431
- const hasHeavyService = apps.some(a => a.services && Object.keys(a.services).length > 3);
432
- if (total > 8 || hasHeavyService)
431
+ // Check for heavy apps (many dependencies)
432
+ const hasHeavyApp = apps.some(a => Object.keys(a.dependencies).length > 3);
433
+ if (total > 8 || hasHeavyApp)
433
434
  return 'xl';
434
435
  if (total > 5)
435
436
  return 'large';
package/dist/schema-v4.js CHANGED
@@ -2,53 +2,53 @@
2
2
  /**
3
3
  * Genbox Configuration Schema v4
4
4
  *
5
- * Key changes from v3:
6
- * - Explicit `connects_to` replacing implicit dependency resolution
7
- * - `provides` section for infrastructure definitions
8
- * - `$detect` markers for opt-in auto-detection
9
- * - Strict mode by default (no implicit inference)
10
- * - Better separation of concerns
5
+ * This is the ONLY supported schema version.
6
+ * All legacy v2/v3 support has been removed.
11
7
  */
12
8
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.DEPRECATED_V3_FIELDS = void 0;
14
- exports.isV4Config = isV4Config;
9
+ exports.DETECTABLE_APP_FIELDS = exports.DETECT_MARKER = void 0;
10
+ exports.isDetectMarker = isDetectMarker;
11
+ exports.isValidConfig = isValidConfig;
15
12
  exports.getConfigVersion = getConfigVersion;
16
13
  // ============================================
17
- // Migration Helpers
14
+ // $detect Marker Support
18
15
  // ============================================
19
16
  /**
20
- * Check if a config is v4
17
+ * Special marker indicating a value should be auto-detected.
21
18
  */
22
- function isV4Config(config) {
19
+ exports.DETECT_MARKER = '$detect';
20
+ /**
21
+ * List of fields that support $detect markers in AppConfig
22
+ */
23
+ exports.DETECTABLE_APP_FIELDS = [
24
+ 'type',
25
+ 'framework',
26
+ 'port',
27
+ 'commands.install',
28
+ 'commands.dev',
29
+ 'commands.build',
30
+ 'commands.start',
31
+ ];
32
+ /**
33
+ * Check if a value is a $detect marker
34
+ */
35
+ function isDetectMarker(value) {
36
+ return value === exports.DETECT_MARKER;
37
+ }
38
+ // ============================================
39
+ // Helper Functions
40
+ // ============================================
41
+ /**
42
+ * Check if a config is valid v4
43
+ */
44
+ function isValidConfig(config) {
23
45
  return config.version === 4;
24
46
  }
25
47
  /**
26
- * Get the version of a config
48
+ * Get the version of a config (only v4 is valid now)
27
49
  */
28
50
  function getConfigVersion(config) {
29
51
  if (config.version === 4)
30
52
  return 4;
31
- if (config.version === 3 || config.version === '3.0')
32
- return 3;
33
- return 'unknown';
53
+ return 'invalid';
34
54
  }
35
- /**
36
- * List of deprecated fields from v3
37
- */
38
- exports.DEPRECATED_V3_FIELDS = [
39
- {
40
- path: 'apps.*.requires',
41
- replacement: 'apps.*.connects_to',
42
- message: 'Use connects_to with explicit connection modes',
43
- },
44
- {
45
- path: 'infrastructure',
46
- replacement: 'provides',
47
- message: 'Renamed to provides for clarity',
48
- },
49
- {
50
- path: 'profiles.*.connect_to',
51
- replacement: 'profiles.*.default_connection',
52
- message: 'Use default_connection or per-app connections override',
53
- },
54
- ];