genbox 1.0.47 → 1.0.48

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.
@@ -4,10 +4,7 @@
4
4
  *
5
5
  * Loads genbox.yaml from the CURRENT directory only.
6
6
  * No parent directory lookup - each project is self-contained.
7
- *
8
- * Additional sources (merged with project config):
9
- * - User profiles (~/.genbox/profiles.yaml)
10
- * - User defaults (~/.genbox/config.json)
7
+ * Only supports v4 configuration format.
11
8
  */
12
9
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
10
  if (k2 === undefined) k2 = k;
@@ -44,7 +41,6 @@ var __importStar = (this && this.__importStar) || (function () {
44
41
  })();
45
42
  Object.defineProperty(exports, "__esModule", { value: true });
46
43
  exports.configLoader = exports.ConfigLoader = void 0;
47
- exports.isV4 = isV4;
48
44
  exports.getProfileConnection = getProfileConnection;
49
45
  exports.getAppDependencies = getAppDependencies;
50
46
  exports.getInfrastructure = getInfrastructure;
@@ -52,33 +48,20 @@ const fs = __importStar(require("fs"));
52
48
  const path = __importStar(require("path"));
53
49
  const yaml = __importStar(require("js-yaml"));
54
50
  const os = __importStar(require("os"));
55
- const schema_v4_1 = require("./schema-v4");
56
- // Helper to check if config is v4
57
- function isV4(config) {
58
- return (0, schema_v4_1.getConfigVersion)(config) === 4;
59
- }
60
- // Helper to get profile connection (handles both v3 and v4)
51
+ // Helper to get profile connection
61
52
  function getProfileConnection(profile) {
62
- // v4 uses default_connection, v3 uses connect_to
63
- return profile.default_connection || profile.connect_to;
53
+ return profile.default_connection;
64
54
  }
65
- // Helper to get app dependencies (handles both v3 and v4)
55
+ // Helper to get app dependencies
66
56
  function getAppDependencies(app) {
67
- // v4 uses connects_to, v3 uses requires
68
57
  if (app.connects_to) {
69
58
  return Object.keys(app.connects_to);
70
59
  }
71
- if (app.requires) {
72
- return Object.keys(app.requires);
73
- }
74
60
  return [];
75
61
  }
76
- // Helper to get infrastructure/provides (handles both v3 and v4)
62
+ // Helper to get infrastructure/provides
77
63
  function getInfrastructure(config) {
78
- if (isV4(config)) {
79
- return config.provides;
80
- }
81
- return config.infrastructure;
64
+ return config.provides;
82
65
  }
83
66
  const CONFIG_FILENAME = 'genbox.yaml';
84
67
  const ENV_FILENAME = '.env.genbox';
@@ -102,16 +85,18 @@ class ConfigLoader {
102
85
  // Merge configurations
103
86
  const mergedConfig = this.mergeConfigs(sources, userConfig);
104
87
  // Validate
105
- const validation = this.validate(mergedConfig);
106
- if (!validation.valid) {
107
- warnings.push(...validation.errors.map(e => e.message));
88
+ if (mergedConfig) {
89
+ const validation = this.validate(mergedConfig);
90
+ if (!validation.valid) {
91
+ warnings.push(...validation.errors.map(e => e.message));
92
+ }
93
+ warnings.push(...validation.warnings.map(w => w.message));
108
94
  }
109
- warnings.push(...validation.warnings.map(w => w.message));
110
95
  // Root is current directory
111
96
  const root = projectConfig?.path ? path.dirname(projectConfig.path) : cwd;
112
97
  return {
113
98
  found: sources.length > 0,
114
- config: sources.length > 0 ? mergedConfig : null,
99
+ config: mergedConfig,
115
100
  sources,
116
101
  warnings,
117
102
  isWorkspace: false,
@@ -129,6 +114,12 @@ class ConfigLoader {
129
114
  try {
130
115
  const content = fs.readFileSync(configPath, 'utf8');
131
116
  const config = yaml.load(content);
117
+ // Validate it's v4
118
+ if (config.version !== 4) {
119
+ console.warn(`Warning: Config version ${config.version} is not supported. Only v4 is supported.`);
120
+ console.warn('Run "genbox init" to create a new v4 configuration.');
121
+ return null;
122
+ }
132
123
  return {
133
124
  type: 'project',
134
125
  path: configPath,
@@ -173,43 +164,27 @@ class ConfigLoader {
173
164
  }
174
165
  }
175
166
  /**
176
- * Merge configurations from all sources
177
- * Supports both v3 and v4 config formats
167
+ * Merge configurations from all sources (v4 only)
178
168
  */
179
169
  mergeConfigs(sources, userConfig) {
180
- // Detect version from first source that has it
181
- let isVersion4 = false;
182
- for (const source of sources) {
183
- // Version can be number (v4) or string (v3)
184
- if (source.config.version === 4) {
185
- isVersion4 = true;
186
- break;
187
- }
170
+ if (sources.length === 0) {
171
+ return null;
188
172
  }
189
- // Start with appropriate default structure
190
- const merged = isVersion4
191
- ? {
192
- version: 4,
193
- project: {
194
- name: 'unnamed',
195
- structure: 'single-app',
196
- },
197
- apps: {},
198
- strict: {
199
- enabled: true,
200
- allow_detect: true,
201
- warnings_as_errors: false,
202
- },
203
- }
204
- : {
205
- version: '3.0',
206
- project: {
207
- name: 'unnamed',
208
- structure: 'single-app',
209
- },
210
- apps: {},
211
- };
212
- // Merge sources in order (workspace first, then project)
173
+ // Start with v4 default structure
174
+ const merged = {
175
+ version: 4,
176
+ project: {
177
+ name: 'unnamed',
178
+ structure: 'single-app',
179
+ },
180
+ apps: {},
181
+ strict: {
182
+ enabled: true,
183
+ allow_detect: true,
184
+ warnings_as_errors: false,
185
+ },
186
+ };
187
+ // Merge sources in order
213
188
  for (const source of sources) {
214
189
  this.deepMerge(merged, source.config);
215
190
  }
@@ -232,10 +207,6 @@ class ConfigLoader {
232
207
  }
233
208
  /**
234
209
  * Deep merge objects
235
- *
236
- * Special handling:
237
- * - 'version' field is never overwritten (prevents v4 -> v1 downgrades)
238
- * - Empty objects don't overwrite non-empty objects
239
210
  */
240
211
  deepMerge(target, source) {
241
212
  for (const key of Object.keys(source)) {
@@ -243,7 +214,7 @@ class ConfigLoader {
243
214
  const targetValue = target[key];
244
215
  if (sourceValue === undefined)
245
216
  continue;
246
- // Never overwrite version field - workspace config takes precedence
217
+ // Never overwrite version field
247
218
  if (key === 'version' && targetValue !== undefined)
248
219
  continue;
249
220
  // Don't overwrite non-empty objects with empty objects
@@ -255,7 +226,6 @@ class ConfigLoader {
255
226
  targetValue !== null &&
256
227
  !Array.isArray(targetValue) &&
257
228
  Object.keys(targetValue).length > 0) {
258
- // Skip - don't let empty {} overwrite populated object
259
229
  continue;
260
230
  }
261
231
  if (typeof sourceValue === 'object' &&
@@ -272,12 +242,19 @@ class ConfigLoader {
272
242
  }
273
243
  }
274
244
  /**
275
- * Validate configuration (supports both v3 and v4)
245
+ * Validate configuration (v4 only)
276
246
  */
277
247
  validate(config) {
278
248
  const errors = [];
279
249
  const warnings = [];
280
- const configIsV4 = isV4(config);
250
+ // Check version
251
+ if (config.version !== 4) {
252
+ errors.push({
253
+ path: 'version',
254
+ message: `Config version ${config.version} is not supported. Only v4 is supported.`,
255
+ severity: 'error',
256
+ });
257
+ }
281
258
  // Required fields
282
259
  if (!config.project?.name) {
283
260
  errors.push({
@@ -293,8 +270,7 @@ class ConfigLoader {
293
270
  severity: 'warning',
294
271
  });
295
272
  }
296
- // Get infrastructure (v4: provides, v3: infrastructure)
297
- const infra = getInfrastructure(config);
273
+ const infra = config.provides;
298
274
  // Validate apps
299
275
  for (const [name, app] of Object.entries(config.apps || {})) {
300
276
  if (!app.path) {
@@ -311,16 +287,15 @@ class ConfigLoader {
311
287
  severity: 'warning',
312
288
  });
313
289
  }
314
- // Validate dependencies (v4: connects_to, v3: requires)
290
+ // Validate dependencies
315
291
  const deps = getAppDependencies(app);
316
- const depField = configIsV4 ? 'connects_to' : 'requires';
317
292
  for (const dep of deps) {
318
293
  const isAppDep = config.apps?.[dep];
319
294
  const isInfraDep = infra?.[dep];
320
295
  if (!isAppDep && !isInfraDep) {
321
296
  warnings.push({
322
- path: `apps.${name}.${depField}.${dep}`,
323
- message: `App '${name}' ${configIsV4 ? 'connects to' : 'requires'} '${dep}' which is not defined`,
297
+ path: `apps.${name}.connects_to.${dep}`,
298
+ message: `App '${name}' connects to '${dep}' which is not defined`,
324
299
  severity: 'warning',
325
300
  });
326
301
  }
@@ -328,7 +303,6 @@ class ConfigLoader {
328
303
  }
329
304
  // Validate profiles
330
305
  for (const [name, profile] of Object.entries(config.profiles || {})) {
331
- // Check apps exist
332
306
  for (const appName of profile.apps || []) {
333
307
  if (!config.apps?.[appName]) {
334
308
  errors.push({
@@ -338,7 +312,6 @@ class ConfigLoader {
338
312
  });
339
313
  }
340
314
  }
341
- // Check extends
342
315
  if (profile.extends && !config.profiles?.[profile.extends]) {
343
316
  errors.push({
344
317
  path: `profiles.${name}.extends`,
@@ -346,12 +319,10 @@ class ConfigLoader {
346
319
  severity: 'error',
347
320
  });
348
321
  }
349
- // Check connection (v4: default_connection, v3: connect_to)
350
- const connection = getProfileConnection(profile);
322
+ const connection = profile.default_connection;
351
323
  if (connection && !config.environments?.[connection]) {
352
- const connField = configIsV4 ? 'default_connection' : 'connect_to';
353
324
  warnings.push({
354
- path: `profiles.${name}.${connField}`,
325
+ path: `profiles.${name}.default_connection`,
355
326
  message: `Profile '${name}' connects to undefined environment '${connection}'`,
356
327
  severity: 'warning',
357
328
  });
@@ -359,37 +330,20 @@ class ConfigLoader {
359
330
  }
360
331
  // Validate environments
361
332
  for (const [name, env] of Object.entries(config.environments || {})) {
362
- // Check for unresolved variable references
363
- const checkValue = (value, path) => {
364
- if (typeof value === 'string' && value.includes('${')) {
365
- const match = value.match(/\$\{([^}]+)\}/);
366
- if (match) {
367
- warnings.push({
368
- path,
369
- message: `Environment '${name}' has variable reference '${match[1]}' - ensure it's defined in .env.genbox`,
370
- severity: 'warning',
371
- });
372
- }
373
- }
374
- };
375
- // v4 uses urls object, v3 uses separate mongodb/redis/rabbitmq objects
376
- if (configIsV4) {
377
- const v4Env = env;
378
- if (v4Env.urls) {
379
- for (const [key, url] of Object.entries(v4Env.urls)) {
380
- checkValue(url, `environments.${name}.urls.${key}`);
333
+ if (env.urls) {
334
+ for (const [key, url] of Object.entries(env.urls)) {
335
+ if (typeof url === 'string' && url.includes('${')) {
336
+ const match = url.match(/\$\{([^}]+)\}/);
337
+ if (match) {
338
+ warnings.push({
339
+ path: `environments.${name}.urls.${key}`,
340
+ message: `Environment '${name}' has variable reference '${match[1]}' - ensure it's defined in .env.genbox`,
341
+ severity: 'warning',
342
+ });
343
+ }
381
344
  }
382
345
  }
383
346
  }
384
- else {
385
- const v3Env = env;
386
- if (v3Env.mongodb?.url)
387
- checkValue(v3Env.mongodb.url, `environments.${name}.mongodb.url`);
388
- if (v3Env.redis?.url)
389
- checkValue(v3Env.redis.url, `environments.${name}.redis.url`);
390
- if (v3Env.rabbitmq?.url)
391
- checkValue(v3Env.rabbitmq.url, `environments.${name}.rabbitmq.url`);
392
- }
393
347
  }
394
348
  return {
395
349
  valid: errors.length === 0,
@@ -398,12 +352,11 @@ class ConfigLoader {
398
352
  };
399
353
  }
400
354
  /**
401
- * Get a specific profile, resolving extends (supports both v3 and v4)
355
+ * Get a specific profile, resolving extends
402
356
  */
403
357
  getProfile(config, profileName) {
404
358
  const profile = config.profiles?.[profileName];
405
359
  if (!profile) {
406
- // Check user profiles
407
360
  const userProfiles = this.loadUserProfiles();
408
361
  if (userProfiles?.profiles?.[profileName]) {
409
362
  return this.resolveProfile(config, userProfiles.profiles[profileName], userProfiles);
@@ -413,13 +366,12 @@ class ConfigLoader {
413
366
  return this.resolveProfile(config, profile);
414
367
  }
415
368
  /**
416
- * Resolve profile inheritance (extends) - supports both v3 and v4
369
+ * Resolve profile inheritance (extends)
417
370
  */
418
371
  resolveProfile(config, profile, userProfiles) {
419
372
  if (!profile.extends) {
420
373
  return profile;
421
374
  }
422
- // Find parent profile
423
375
  let parent = config.profiles?.[profile.extends];
424
376
  if (!parent && userProfiles) {
425
377
  parent = userProfiles.profiles?.[profile.extends];
@@ -428,47 +380,26 @@ class ConfigLoader {
428
380
  console.warn(`Warning: Profile extends '${profile.extends}' which doesn't exist`);
429
381
  return profile;
430
382
  }
431
- // Resolve parent first (recursive)
432
383
  const resolvedParent = this.resolveProfile(config, parent, userProfiles);
433
- // Merge parent into child - handle both v3 and v4
434
- const merged = {
384
+ return {
435
385
  ...resolvedParent,
436
386
  ...profile,
437
- // Arrays are replaced, not merged
438
387
  apps: profile.apps || resolvedParent.apps,
439
- // Objects are merged
440
388
  env: {
441
389
  ...resolvedParent.env,
442
390
  ...profile.env,
443
391
  },
392
+ connections: {
393
+ ...resolvedParent.connections,
394
+ ...profile.connections,
395
+ },
444
396
  };
445
- // Handle v4 connections override
446
- if (isV4(config)) {
447
- const v4Profile = profile;
448
- const v4Parent = resolvedParent;
449
- merged.connections = {
450
- ...v4Parent.connections,
451
- ...v4Profile.connections,
452
- };
453
- }
454
- else {
455
- // v3 infrastructure merge
456
- const v3Profile = profile;
457
- const v3Parent = resolvedParent;
458
- merged.infrastructure = {
459
- ...v3Parent.infrastructure,
460
- ...v3Profile.infrastructure,
461
- };
462
- }
463
- return merged;
464
397
  }
465
398
  /**
466
- * List all available profiles (supports both v3 and v4)
399
+ * List all available profiles
467
400
  */
468
401
  listProfiles(config) {
469
402
  const profiles = [];
470
- // Project profiles only (user/global profiles removed - they don't make sense
471
- // because profiles reference project-specific app names)
472
403
  for (const [name, profile] of Object.entries(config.profiles || {})) {
473
404
  const resolved = this.getProfile(config, name);
474
405
  profiles.push({
@@ -477,7 +408,7 @@ class ConfigLoader {
477
408
  source: 'project',
478
409
  apps: resolved?.apps || [],
479
410
  size: resolved?.size,
480
- connection: resolved ? getProfileConnection(resolved) : undefined,
411
+ connection: resolved?.default_connection,
481
412
  });
482
413
  }
483
414
  return profiles;
@@ -486,11 +417,9 @@ class ConfigLoader {
486
417
  * Save user profile
487
418
  */
488
419
  saveUserProfile(name, profile) {
489
- // Ensure directory exists
490
420
  if (!fs.existsSync(USER_CONFIG_DIR)) {
491
421
  fs.mkdirSync(USER_CONFIG_DIR, { recursive: true });
492
422
  }
493
- // Load existing profiles
494
423
  let profiles = { profiles: {} };
495
424
  if (fs.existsSync(USER_PROFILES_FILE)) {
496
425
  try {
@@ -499,9 +428,7 @@ class ConfigLoader {
499
428
  }
500
429
  catch { }
501
430
  }
502
- // Add/update profile
503
431
  profiles.profiles[name] = profile;
504
- // Save
505
432
  const content = yaml.dump(profiles, { lineWidth: 120 });
506
433
  fs.writeFileSync(USER_PROFILES_FILE, content);
507
434
  }
@@ -510,10 +437,9 @@ class ConfigLoader {
510
437
  */
511
438
  loadEnvVars(root) {
512
439
  const vars = {};
513
- // Load from multiple locations
514
440
  const envPaths = [
515
441
  path.join(root, ENV_FILENAME),
516
- path.join(path.dirname(root), ENV_FILENAME), // Workspace level
442
+ path.join(path.dirname(root), ENV_FILENAME),
517
443
  ];
518
444
  for (const envPath of envPaths) {
519
445
  if (fs.existsSync(envPath)) {
@@ -532,13 +458,11 @@ class ConfigLoader {
532
458
  const lines = content.split('\n');
533
459
  for (const line of lines) {
534
460
  const trimmed = line.trim();
535
- // Skip comments and empty lines
536
461
  if (!trimmed || trimmed.startsWith('#'))
537
462
  continue;
538
463
  const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
539
464
  if (match) {
540
465
  let [, key, value] = match;
541
- // Remove quotes
542
466
  if ((value.startsWith('"') && value.endsWith('"')) ||
543
467
  (value.startsWith("'") && value.endsWith("'"))) {
544
468
  value = value.slice(1, -1);
@@ -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,16 +314,8 @@ 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
@@ -348,7 +340,7 @@ class ProfileResolver {
348
340
  if (profileConnection) {
349
341
  const envConfig = config.environments?.[profileConnection];
350
342
  // v4 uses urls.mongodb, v3 uses mongodb.url
351
- const mongoUrl = (0, config_loader_1.isV4)(config)
343
+ const mongoUrl = true
352
344
  ? envConfig?.urls?.mongodb
353
345
  : envConfig?.mongodb?.url;
354
346
  return {
@@ -409,7 +401,7 @@ class ProfileResolver {
409
401
  const source = answer.replace('remote-', '');
410
402
  const envConfig = config.environments?.[source];
411
403
  // v4 uses urls.mongodb, v3 uses mongodb.url
412
- const mongoUrl = (0, config_loader_1.isV4)(config)
404
+ const mongoUrl = true
413
405
  ? envConfig?.urls?.mongodb
414
406
  : envConfig?.mongodb?.url;
415
407
  return {
@@ -427,9 +419,9 @@ class ProfileResolver {
427
419
  const localApps = apps.length;
428
420
  const localInfra = infrastructure.filter(i => i.mode === 'local').length;
429
421
  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)
422
+ // Check for heavy apps (many dependencies)
423
+ const hasHeavyApp = apps.some(a => Object.keys(a.dependencies).length > 3);
424
+ if (total > 8 || hasHeavyApp)
433
425
  return 'xl';
434
426
  if (total > 5)
435
427
  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
- ];