@aifabrix/builder 2.39.3 → 2.40.2

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.
Files changed (117) hide show
  1. package/.cursor/rules/project-rules.mdc +6 -6
  2. package/README.md +3 -3
  3. package/babel.config.js +6 -0
  4. package/integration/hubspot/README.md +53 -141
  5. package/integration/hubspot/application.yaml +37 -0
  6. package/integration/hubspot/env.template +2 -11
  7. package/integration/hubspot/hubspot-deploy.json +1 -0
  8. package/integration/hubspot/test.js +5 -5
  9. package/jest.config.manual.js +29 -0
  10. package/lib/api/credentials.api.js +5 -5
  11. package/lib/api/deployments.api.js +2 -2
  12. package/lib/api/pipeline.api.js +17 -17
  13. package/lib/api/wizard.api.js +2 -2
  14. package/lib/app/config.js +11 -6
  15. package/lib/app/deploy-config.js +13 -16
  16. package/lib/app/deploy.js +29 -22
  17. package/lib/app/display.js +1 -1
  18. package/lib/app/dockerfile.js +11 -12
  19. package/lib/app/helpers.js +51 -13
  20. package/lib/app/index.js +14 -2
  21. package/lib/app/prompts.js +37 -45
  22. package/lib/app/push.js +8 -11
  23. package/lib/app/readme.js +16 -12
  24. package/lib/app/register.js +1 -1
  25. package/lib/app/run-helpers.js +31 -22
  26. package/lib/app/run.js +44 -5
  27. package/lib/app/show-display.js +104 -44
  28. package/lib/app/show.js +123 -43
  29. package/lib/build/index.js +11 -18
  30. package/lib/cli/setup-app.js +36 -29
  31. package/lib/cli/setup-auth.js +19 -15
  32. package/lib/cli/setup-credential-deployment.js +3 -1
  33. package/lib/cli/setup-external-system.js +35 -16
  34. package/lib/cli/setup-infra.js +45 -23
  35. package/lib/cli/setup-utility.js +85 -31
  36. package/lib/commands/app-logs.js +28 -20
  37. package/lib/commands/app.js +30 -26
  38. package/lib/commands/auth-status.js +36 -3
  39. package/lib/commands/convert.js +202 -0
  40. package/lib/commands/credential-list.js +78 -17
  41. package/lib/commands/datasource.js +24 -24
  42. package/lib/commands/deployment-list.js +13 -6
  43. package/lib/commands/up-common.js +80 -42
  44. package/lib/commands/up-dataplane.js +15 -14
  45. package/lib/commands/up-miso.js +15 -14
  46. package/lib/commands/upload.js +163 -0
  47. package/lib/commands/wizard-core.js +5 -4
  48. package/lib/core/diff.js +84 -9
  49. package/lib/core/key-generator.js +9 -12
  50. package/lib/core/secrets-docker-env.js +2 -2
  51. package/lib/core/secrets.js +3 -2
  52. package/lib/core/templates.js +2 -2
  53. package/lib/datasource/deploy.js +2 -1
  54. package/lib/deployment/deployer.js +76 -48
  55. package/lib/external-system/delete.js +0 -1
  56. package/lib/external-system/deploy-helpers.js +5 -6
  57. package/lib/external-system/deploy.js +7 -2
  58. package/lib/external-system/download-helpers.js +4 -4
  59. package/lib/external-system/download.js +11 -10
  60. package/lib/external-system/generator.js +19 -17
  61. package/lib/external-system/test.js +10 -15
  62. package/lib/generator/builders.js +1 -1
  63. package/lib/generator/external-controller-manifest.js +26 -29
  64. package/lib/generator/external-schema-utils.js +6 -18
  65. package/lib/generator/external.js +32 -27
  66. package/lib/generator/github.js +1 -1
  67. package/lib/generator/helpers.js +12 -19
  68. package/lib/generator/index.js +15 -15
  69. package/lib/generator/parse-image.js +35 -0
  70. package/lib/generator/split-readme.js +105 -0
  71. package/lib/generator/split-variables.js +149 -0
  72. package/lib/generator/split.js +86 -246
  73. package/lib/generator/wizard.js +51 -70
  74. package/lib/schema/application-schema.json +4 -4
  75. package/lib/schema/external-datasource.schema.json +5 -0
  76. package/lib/schema/external-system.schema.json +10 -0
  77. package/lib/utils/app-config-resolver.js +52 -0
  78. package/lib/utils/app-register-api.js +1 -1
  79. package/lib/utils/app-register-auth.js +1 -1
  80. package/lib/utils/app-register-config.js +16 -23
  81. package/lib/utils/app-register-validator.js +2 -2
  82. package/lib/utils/cli-utils.js +47 -3
  83. package/lib/utils/config-format.js +154 -0
  84. package/lib/utils/config-paths.js +19 -52
  85. package/lib/utils/config-tokens.js +1 -0
  86. package/lib/utils/docker-build.js +71 -94
  87. package/lib/utils/dockerfile-utils.js +1 -1
  88. package/lib/utils/env-copy.js +4 -4
  89. package/lib/utils/env-ports.js +2 -2
  90. package/lib/utils/error-formatter.js +1 -1
  91. package/lib/utils/error-formatters/validation-errors.js +1 -1
  92. package/lib/utils/external-readme.js +12 -5
  93. package/lib/utils/external-system-test-helpers.js +2 -0
  94. package/lib/utils/health-check.js +55 -66
  95. package/lib/utils/image-version.js +12 -21
  96. package/lib/utils/paths.js +45 -66
  97. package/lib/utils/port-resolver.js +8 -8
  98. package/lib/utils/schema-loader.js +22 -0
  99. package/lib/utils/schema-resolver.js +23 -33
  100. package/lib/utils/secrets-helpers.js +7 -7
  101. package/lib/utils/secrets-utils.js +10 -12
  102. package/lib/utils/template-helpers.js +13 -13
  103. package/lib/utils/token-manager.js +20 -2
  104. package/lib/utils/variable-transformer.js +2 -2
  105. package/lib/validation/validate-display.js +3 -4
  106. package/lib/validation/validate.js +34 -28
  107. package/lib/validation/validator.js +50 -30
  108. package/package.json +4 -1
  109. package/templates/README.md +1 -1
  110. package/templates/applications/README.md.hbs +3 -3
  111. package/templates/applications/miso-controller/env.template +3 -1
  112. package/templates/external-system/README.md.hbs +4 -4
  113. package/templates/external-system/external-system.json.hbs +1 -16
  114. package/integration/hubspot/variables.yaml +0 -17
  115. /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
  116. /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
  117. /package/templates/applications/miso-controller/{variables.yaml → application.yaml} +0 -0
@@ -231,6 +231,60 @@ function parseHealthResponse(data, statusCode) {
231
231
  }
232
232
  }
233
233
 
234
+ function handleHealthResponse(res, data, debug, resolve) {
235
+ const isHealthy = parseHealthResponse(data, res.statusCode);
236
+ if (debug) {
237
+ const truncatedData = data.length > 200 ? data.substring(0, 200) + '...' : data;
238
+ logger.log(chalk.gray(`[DEBUG] Response body: ${truncatedData}`));
239
+ logger.log(chalk.gray(`[DEBUG] Health check result: ${isHealthy ? 'healthy' : 'unhealthy'}`));
240
+ }
241
+ resolve(isHealthy);
242
+ }
243
+
244
+ function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject) {
245
+ try {
246
+ const urlObj = new URL(healthCheckUrl);
247
+ const options = {
248
+ hostname: urlObj.hostname,
249
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
250
+ path: urlObj.pathname + urlObj.search,
251
+ method: 'GET'
252
+ };
253
+ if (debug) {
254
+ logger.log(chalk.gray(`[DEBUG] Health check request: ${healthCheckUrl}`));
255
+ logger.log(chalk.gray(`[DEBUG] Request options: ${JSON.stringify(options, null, 2)}`));
256
+ }
257
+ // eslint-disable-next-line prefer-const
258
+ let timeoutId;
259
+ const req = http.request(options, (res) => {
260
+ clearTimeout(timeoutId);
261
+ let data = '';
262
+ if (debug) {
263
+ logger.log(chalk.gray(`[DEBUG] Response status code: ${res.statusCode}`));
264
+ logger.log(chalk.gray(`[DEBUG] Response headers: ${JSON.stringify(res.headers, null, 2)}`));
265
+ }
266
+ res.on('data', (chunk) => {
267
+ data += chunk;
268
+ });
269
+ res.on('end', () => handleHealthResponse(res, data, debug, resolve));
270
+ });
271
+ timeoutId = setTimeout(() => {
272
+ if (debug) logger.log(chalk.gray('[DEBUG] Health check request timeout after 5 seconds'));
273
+ req.destroy();
274
+ resolve(false);
275
+ }, 5000);
276
+ req.on('error', (error) => {
277
+ clearTimeout(timeoutId);
278
+ if (debug) logger.log(chalk.gray(`[DEBUG] Health check request error: ${error.message}`));
279
+ resolve(false);
280
+ });
281
+ req.end();
282
+ } catch (error) {
283
+ if (debug) logger.log(chalk.gray(`[DEBUG] Health check exception: ${error.message}`));
284
+ reject(error);
285
+ }
286
+ }
287
+
234
288
  /**
235
289
  * Checks health endpoint
236
290
  * @async
@@ -242,72 +296,7 @@ function parseHealthResponse(data, statusCode) {
242
296
  */
243
297
  async function checkHealthEndpoint(healthCheckUrl, debug = false) {
244
298
  return new Promise((resolve, reject) => {
245
- try {
246
- const urlObj = new URL(healthCheckUrl);
247
- const options = {
248
- hostname: urlObj.hostname,
249
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
250
- path: urlObj.pathname + urlObj.search,
251
- method: 'GET'
252
- };
253
-
254
- if (debug) {
255
- logger.log(chalk.gray(`[DEBUG] Health check request: ${healthCheckUrl}`));
256
- logger.log(chalk.gray(`[DEBUG] Request options: ${JSON.stringify(options, null, 2)}`));
257
- }
258
-
259
- // Declare timeoutId before creating req so it can be used in callbacks
260
- // eslint-disable-next-line prefer-const
261
- let timeoutId;
262
-
263
- const req = http.request(options, (res) => {
264
- clearTimeout(timeoutId);
265
- let data = '';
266
- if (debug) {
267
- logger.log(chalk.gray(`[DEBUG] Response status code: ${res.statusCode}`));
268
- logger.log(chalk.gray(`[DEBUG] Response headers: ${JSON.stringify(res.headers, null, 2)}`));
269
- }
270
- res.on('data', (chunk) => {
271
- data += chunk;
272
- });
273
- res.on('end', () => {
274
- if (debug) {
275
- const truncatedData = data.length > 200 ? data.substring(0, 200) + '...' : data;
276
- logger.log(chalk.gray(`[DEBUG] Response body: ${truncatedData}`));
277
- }
278
- const isHealthy = parseHealthResponse(data, res.statusCode);
279
- if (debug) {
280
- logger.log(chalk.gray(`[DEBUG] Health check result: ${isHealthy ? 'healthy' : 'unhealthy'}`));
281
- }
282
- resolve(isHealthy);
283
- });
284
- });
285
-
286
- // Set timeout for the request using setTimeout
287
- timeoutId = setTimeout(() => {
288
- if (debug) {
289
- logger.log(chalk.gray('[DEBUG] Health check request timeout after 5 seconds'));
290
- }
291
- req.destroy();
292
- resolve(false);
293
- }, 5000);
294
-
295
- req.on('error', (error) => {
296
- clearTimeout(timeoutId);
297
- if (debug) {
298
- logger.log(chalk.gray(`[DEBUG] Health check request error: ${error.message}`));
299
- }
300
- resolve(false);
301
- });
302
-
303
- req.end();
304
- } catch (error) {
305
- if (debug) {
306
- logger.log(chalk.gray(`[DEBUG] Health check exception: ${error.message}`));
307
- }
308
- // Re-throw exceptions (not just network errors)
309
- reject(error);
310
- }
299
+ doHealthCheckRequest(healthCheckUrl, debug, resolve, reject);
311
300
  });
312
301
  }
313
302
 
@@ -11,11 +11,9 @@
11
11
 
12
12
  const { exec } = require('child_process');
13
13
  const { promisify } = require('util');
14
- const fs = require('fs').promises;
15
- const fsSync = require('fs');
16
- const path = require('path');
17
- const yaml = require('js-yaml');
18
14
  const { getBuilderPath } = require('./paths');
15
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
16
+ const { loadConfigFile, writeConfigFile } = require('./config-format');
19
17
  const composeGenerator = require('./compose-generator');
20
18
  const containerHelpers = require('./app-run-containers');
21
19
 
@@ -86,7 +84,7 @@ function compareSemver(a, b) {
86
84
 
87
85
  /**
88
86
  * Resolves version for external app (app.version or externalIntegration.version)
89
- * @param {Object} variables - Parsed variables.yaml
87
+ * @param {Object} variables - Parsed application config
90
88
  * @returns {string}
91
89
  */
92
90
  function resolveExternalVersion(variables) {
@@ -125,9 +123,9 @@ async function resolveRegularVersion(imageName, imageTag, templateVersion) {
125
123
  * Resolves version for an app: from image when image exists and template empty or smaller
126
124
  * @async
127
125
  * @param {string} appName - Application name
128
- * @param {Object} variables - Parsed variables.yaml
126
+ * @param {Object} variables - Parsed application config
129
127
  * @param {Object} [options] - Options
130
- * @param {boolean} [options.updateBuilder] - When true, update builder/variables.yaml if fromImage
128
+ * @param {boolean} [options.updateBuilder] - When true, update builder application config if fromImage
131
129
  * @param {string} [options.builderPath] - Builder path (defaults to getBuilderPath(appName))
132
130
  * @returns {Promise<{ version: string, fromImage: boolean, updated: boolean }>}
133
131
  */
@@ -164,37 +162,30 @@ async function resolveVersionForApp(appName, variables, options = {}) {
164
162
  let updated = false;
165
163
  if (fromImage && options.updateBuilder) {
166
164
  const builderPath = options.builderPath || getBuilderPath(appName);
167
- updated = await updateAppVersionInVariablesYaml(builderPath, version);
165
+ updated = updateAppVersionInVariablesYaml(builderPath, version);
168
166
  }
169
167
 
170
168
  return { version, fromImage, updated };
171
169
  }
172
170
 
173
171
  /**
174
- * Updates app.version in builder variables.yaml
175
- * @async
172
+ * Updates app.version in builder application config
176
173
  * @param {string} builderPath - Path to builder app directory
177
174
  * @param {string} version - Version to set
178
- * @returns {Promise<boolean>} True if file was updated
175
+ * @returns {boolean} True if file was updated
179
176
  */
180
- async function updateAppVersionInVariablesYaml(builderPath, version) {
177
+ function updateAppVersionInVariablesYaml(builderPath, version) {
181
178
  if (!builderPath || !version || typeof version !== 'string') {
182
179
  return false;
183
180
  }
184
- const variablesPath = path.join(builderPath, 'variables.yaml');
185
- if (!fsSync.existsSync(variablesPath)) {
186
- return false;
187
- }
188
-
189
181
  try {
190
- const content = await fs.readFile(variablesPath, 'utf8');
191
- const parsed = yaml.load(content) || {};
182
+ const configPath = resolveApplicationConfigPath(builderPath);
183
+ const parsed = loadConfigFile(configPath) || {};
192
184
  if (!parsed.app) {
193
185
  parsed.app = {};
194
186
  }
195
187
  parsed.app.version = version;
196
- const dumped = yaml.dump(parsed, { lineWidth: -1 });
197
- await fs.writeFile(variablesPath, dumped, { mode: 0o644, encoding: 'utf8' });
188
+ writeConfigFile(configPath, parsed);
198
189
  return true;
199
190
  } catch {
200
191
  return false;
@@ -178,6 +178,12 @@ function checkGlobalProjectRoot() {
178
178
  return null;
179
179
  }
180
180
 
181
+ // In test environment, allow temp dir as project root (Jest sets NODE_ENV=test / JEST_WORKER_ID)
182
+ const isTestEnv = process.env.NODE_ENV === 'test' || typeof process.env.JEST_WORKER_ID !== 'undefined';
183
+ if (isTestEnv) {
184
+ return globalRoot;
185
+ }
186
+
181
187
  // Verify that __dirname is actually within globalRoot
182
188
  const dirnameNormalized = path.resolve(__dirname);
183
189
  const globalRootNormalized = path.resolve(globalRoot);
@@ -371,27 +377,11 @@ function getDeployJsonPath(appName, appType, preferNew = false) {
371
377
  // If neither exists, return new naming (for generation)
372
378
  return newPath;
373
379
  }
374
-
375
- /**
376
- * Reads and parses variables.yaml file
377
- * @param {string} variablesPath - Path to variables.yaml file
378
- * @returns {Object|null} Parsed variables object or null if error
379
- */
380
- function readVariablesFile(variablesPath) {
381
- try {
382
- if (!fs.existsSync(variablesPath)) {
383
- return null;
384
- }
385
- const content = fs.readFileSync(variablesPath, 'utf8');
386
- return yaml.load(content);
387
- } catch {
388
- return null;
389
- }
390
- }
391
-
380
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
381
+ const { loadConfigFile } = require('./config-format');
392
382
  /**
393
383
  * Checks if app type is external from variables object
394
- * @param {Object} variables - Parsed variables.yaml object
384
+ * @param {Object} variables - Parsed application config object
395
385
  * @returns {boolean} True if app type is external
396
386
  */
397
387
  function isExternalAppType(variables) {
@@ -399,20 +389,25 @@ function isExternalAppType(variables) {
399
389
  }
400
390
 
401
391
  /**
402
- * Checks integration folder for external app type
392
+ * Checks integration folder for any valid application config
403
393
  * @param {string} appName - Application name
404
- * @returns {Object|null} App type info or null if not found
394
+ * @returns {Object|null} App type info or null if no config found
405
395
  */
406
396
  function checkIntegrationFolder(appName) {
407
397
  const integrationPath = getIntegrationPath(appName);
408
- const variablesPath = path.join(integrationPath, 'variables.yaml');
409
- const variables = readVariablesFile(variablesPath);
410
-
411
- if (variables && isExternalAppType(variables)) {
398
+ let variables;
399
+ try {
400
+ const configPath = resolveApplicationConfigPath(integrationPath);
401
+ variables = loadConfigFile(configPath);
402
+ } catch {
403
+ return null;
404
+ }
405
+ if (variables) {
406
+ const isExternal = isExternalAppType(variables);
412
407
  return {
413
- isExternal: true,
408
+ isExternal,
414
409
  appPath: integrationPath,
415
- appType: 'external',
410
+ appType: isExternal ? 'external' : 'regular',
416
411
  baseDir: 'integration'
417
412
  };
418
413
  }
@@ -422,13 +417,20 @@ function checkIntegrationFolder(appName) {
422
417
  /**
423
418
  * Checks builder folder for app type
424
419
  * @param {string} appName - Application name
425
- * @returns {Object} App type info
420
+ * @returns {Object|null} App type info or null if no config found
426
421
  */
427
422
  function checkBuilderFolder(appName) {
428
423
  const builderPath = getBuilderPath(appName);
429
- const variablesPath = path.join(builderPath, 'variables.yaml');
430
- const variables = readVariablesFile(variablesPath);
431
-
424
+ if (!fs.existsSync(builderPath)) {
425
+ return null;
426
+ }
427
+ let variables;
428
+ try {
429
+ const configPath = resolveApplicationConfigPath(builderPath);
430
+ variables = loadConfigFile(configPath);
431
+ } catch {
432
+ return null;
433
+ }
432
434
  if (variables) {
433
435
  const isExternal = isExternalAppType(variables);
434
436
  return {
@@ -438,51 +440,27 @@ function checkBuilderFolder(appName) {
438
440
  baseDir: 'builder'
439
441
  };
440
442
  }
441
-
442
- // Default to regular app in builder folder
443
- return {
444
- isExternal: false,
445
- appPath: builderPath,
446
- appType: 'regular',
447
- baseDir: 'builder'
448
- };
443
+ return null;
449
444
  }
450
-
451
445
  /**
452
- * Detects if an app is external type by checking variables.yaml
453
- * Checks both integration/ and builder/ folders for backward compatibility
446
+ * Detects if an app is external type by checking application config.
447
+ * Resolution order: integration/ first, then builder/; if neither exists, throws.
448
+ * No CLI flag overrides this order.
454
449
  *
455
450
  * @param {string} appName - Application name
456
- * @param {Object} [options] - Detection options
457
- * @param {string} [options.type] - Forced application type (external)
451
+ * @param {Object} [options] - Detection options (reserved; options.type is ignored)
458
452
  * @returns {Promise<{isExternal: boolean, appPath: string, appType: string, baseDir?: string}>}
453
+ * @throws {Error} When app not found in integration/ or builder/
459
454
  */
460
- async function detectAppType(appName, options = {}) {
455
+ async function detectAppType(appName, _options = {}) {
461
456
  if (!appName || typeof appName !== 'string') {
462
457
  throw new Error('App name is required and must be a string');
463
458
  }
464
-
465
- if (options.type === 'external') {
466
- const integrationPath = getIntegrationPath(appName);
467
- if (!fs.existsSync(integrationPath)) {
468
- throw new Error(`External system not found in integration/${appName}`);
469
- }
470
- return {
471
- isExternal: true,
472
- appPath: integrationPath,
473
- appType: 'external',
474
- baseDir: 'integration'
475
- };
476
- }
477
-
478
- // Check integration folder first (new structure)
479
459
  const integrationResult = checkIntegrationFolder(appName);
480
- if (integrationResult) {
481
- return integrationResult;
482
- }
483
-
484
- // Check builder folder (backward compatibility)
485
- return checkBuilderFolder(appName);
460
+ if (integrationResult) return integrationResult;
461
+ const builderResult = checkBuilderFolder(appName);
462
+ if (builderResult) return builderResult;
463
+ throw new Error(`App '${appName}' not found in integration/${appName} or builder/${appName}`);
486
464
  }
487
465
  module.exports = {
488
466
  getAifabrixHome,
@@ -494,6 +472,7 @@ module.exports = {
494
472
  getIntegrationPath,
495
473
  getBuilderPath,
496
474
  getDeployJsonPath,
475
+ resolveApplicationConfigPath,
497
476
  detectAppType,
498
477
  clearProjectRootCache
499
478
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * AI Fabrix Builder - Centralized Port Resolution
3
3
  *
4
- * Single source of truth for resolving application port from variables.yaml.
4
+ * Single source of truth for resolving application port from application config.
5
5
  * Use getContainerPort for container/Docker/deployment/registration; use getLocalPort
6
6
  * for local .env and dev-id–adjusted host port.
7
7
  *
@@ -20,7 +20,7 @@ const yaml = require('js-yaml');
20
20
  * Precedence: build.containerPort → port → defaultPort.
21
21
  * Used for: Dockerfile, container .env PORT, compose, deployment, app register, variable-transformer, builders, secrets-utils.
22
22
  *
23
- * @param {Object} variables - Parsed variables.yaml (or subset with build, port)
23
+ * @param {Object} variables - Parsed application config (or subset with build, port)
24
24
  * @param {number} [defaultPort=3000] - Default when neither build.containerPort nor port is set
25
25
  * @returns {number} Resolved container port
26
26
  */
@@ -34,7 +34,7 @@ function getContainerPort(variables, defaultPort = 3000) {
34
34
  * Precedence: build.localPort (if number and > 0) → port → defaultPort.
35
35
  * Used for: env-copy, env-ports, and as base for getLocalPortFromPath (secrets-helpers).
36
36
  *
37
- * @param {Object} variables - Parsed variables.yaml
37
+ * @param {Object} variables - Parsed application config
38
38
  * @param {number} [defaultPort=3000] - Default when neither build.localPort nor port is set
39
39
  * @returns {number} Resolved local port
40
40
  */
@@ -50,7 +50,7 @@ function getLocalPort(variables, defaultPort = 3000) {
50
50
  /**
51
51
  * Load variables from path. Returns null if path missing, not found, or parse error.
52
52
  *
53
- * @param {string} variablesPath - Path to variables.yaml
53
+ * @param {string} variablesPath - Path to application config
54
54
  * @returns {Object|null} Parsed variables or null
55
55
  */
56
56
  function loadVariablesFromPath(variablesPath) {
@@ -66,10 +66,10 @@ function loadVariablesFromPath(variablesPath) {
66
66
  }
67
67
 
68
68
  /**
69
- * Resolve container port from variables.yaml path.
69
+ * Resolve container port from application config path.
70
70
  * Returns null when file is missing or neither build.containerPort nor port is set (for chaining with other sources).
71
71
  *
72
- * @param {string} variablesPath - Path to variables.yaml
72
+ * @param {string} variablesPath - Path to application config
73
73
  * @returns {number|null} Container port or null
74
74
  */
75
75
  function getContainerPortFromPath(variablesPath) {
@@ -82,11 +82,11 @@ function getContainerPortFromPath(variablesPath) {
82
82
  }
83
83
 
84
84
  /**
85
- * Resolve local port from variables.yaml path.
85
+ * Resolve local port from application config path.
86
86
  * Matches legacy getPortFromVariablesFile: build.localPort (if number and > 0) else variables.port or null.
87
87
  * Returns null when file is missing or neither is set (for calculateAppPort chain).
88
88
  *
89
- * @param {string} variablesPath - Path to variables.yaml
89
+ * @param {string} variablesPath - Path to application config
90
90
  * @returns {number|null} Local port or null
91
91
  */
92
92
  function getLocalPortFromPath(variablesPath) {
@@ -290,6 +290,27 @@ function tryDetectionMethods(parsed, filePath) {
290
290
  return null;
291
291
  }
292
292
 
293
+ /**
294
+ * Detects schema type from already-parsed content (works with YAML or JSON).
295
+ * Use this when the file was parsed via loadConfigFile or similar.
296
+ *
297
+ * @function detectSchemaTypeFromParsed
298
+ * @param {Object} parsed - Parsed config object (from YAML or JSON)
299
+ * @param {string} filePath - File path (used for filename-based detection)
300
+ * @returns {string} 'application' | 'external-system' | 'external-datasource'
301
+ *
302
+ * @example
303
+ * const parsed = yaml.load(content);
304
+ * const type = detectSchemaTypeFromParsed(parsed, '/path/to/application.yaml');
305
+ */
306
+ function detectSchemaTypeFromParsed(parsed, filePath) {
307
+ if (!parsed || typeof parsed !== 'object') {
308
+ return 'application';
309
+ }
310
+ const detected = tryDetectionMethods(parsed, filePath);
311
+ return detected || 'application';
312
+ }
313
+
293
314
  function detectSchemaType(filePath, content) {
294
315
  const parsed = readAndParseFileContent(filePath, content);
295
316
  const detectedType = tryDetectionMethods(parsed, filePath);
@@ -300,6 +321,7 @@ module.exports = {
300
321
  loadExternalSystemSchema,
301
322
  loadExternalDataSourceSchema,
302
323
  detectSchemaType,
324
+ detectSchemaTypeFromParsed,
303
325
  resetValidators
304
326
  };
305
327
 
@@ -11,43 +11,40 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- const yaml = require('js-yaml');
15
14
  const { detectAppType } = require('./paths');
15
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
16
+ const { loadConfigFile } = require('./config-format');
16
17
 
17
18
  /**
18
- * Resolves schemaBasePath from application variables.yaml
19
+ * Resolves schemaBasePath from application config
19
20
  * Supports both absolute and relative paths
20
21
  *
21
22
  * @async
22
23
  * @function resolveSchemaBasePath
23
24
  * @param {string} appName - Application name
24
25
  * @returns {Promise<string>} Resolved absolute path to schema base directory
25
- * @throws {Error} If variables.yaml not found, externalIntegration missing, or path invalid
26
+ * @throws {Error} If application config not found, externalIntegration missing, or path invalid
26
27
  *
27
28
  * @example
28
29
  * const basePath = await resolveSchemaBasePath('myapp');
29
30
  * // Returns: '/path/to/builder/myapp/schemas'
30
31
  */
31
32
  /**
32
- * Loads and validates variables.yaml
33
+ * Loads and validates application config for schema resolution
33
34
  * @async
34
35
  * @function loadAndValidateVariablesForSchema
35
36
  * @param {string} appName - Application name
36
37
  * @param {string} appPath - Application path
37
- * @returns {Promise<Object>} Variables object
38
+ * @returns {Promise<{ variables: Object, configPath: string }>} Variables object and config path
38
39
  * @throws {Error} If file not found or invalid
39
40
  */
40
41
  async function loadAndValidateVariablesForSchema(appName, appPath) {
41
- const variablesPath = path.join(appPath, 'variables.yaml');
42
- if (!fs.existsSync(variablesPath)) {
43
- throw new Error(`variables.yaml not found: ${variablesPath}`);
44
- }
45
-
46
- const content = fs.readFileSync(variablesPath, 'utf8');
42
+ const configPath = resolveApplicationConfigPath(appPath);
47
43
  try {
48
- return yaml.load(content);
44
+ const variables = loadConfigFile(configPath);
45
+ return { variables, configPath };
49
46
  } catch (error) {
50
- throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
47
+ throw new Error(`Application config error: ${error.message}`);
51
48
  }
52
49
  }
53
50
 
@@ -61,7 +58,7 @@ async function loadAndValidateVariablesForSchema(appName, appPath) {
61
58
  */
62
59
  function validateExternalIntegrationBlock(variables, appName) {
63
60
  if (!variables.externalIntegration) {
64
- throw new Error(`externalIntegration block not found in variables.yaml for app: ${appName}`);
61
+ throw new Error(`externalIntegration block not found in application config for app: ${appName}`);
65
62
  }
66
63
  if (!variables.externalIntegration.schemaBasePath) {
67
64
  throw new Error(`schemaBasePath not found in externalIntegration block for app: ${appName}`);
@@ -73,7 +70,7 @@ function validateExternalIntegrationBlock(variables, appName) {
73
70
  * Resolves and validates schema base path
74
71
  * @function resolveAndValidateSchemaPath
75
72
  * @param {string} schemaBasePath - Schema base path from config
76
- * @param {string} variablesPath - Path to variables.yaml
73
+ * @param {string} variablesPath - Path to application config
77
74
  * @returns {string} Resolved and validated path
78
75
  * @throws {Error} If path is invalid
79
76
  */
@@ -95,17 +92,16 @@ function resolveAndValidateSchemaPath(schemaBasePath, variablesPath) {
95
92
  return resolvedPath;
96
93
  }
97
94
 
98
- async function resolveSchemaBasePath(appName) {
95
+ async function resolveSchemaBasePath(appName, options = {}) {
99
96
  if (!appName || typeof appName !== 'string') {
100
97
  throw new Error('App name is required and must be a string');
101
98
  }
102
99
 
103
- const { appPath } = await detectAppType(appName);
104
- const variables = await loadAndValidateVariablesForSchema(appName, appPath);
100
+ const { appPath } = await detectAppType(appName, options);
101
+ const { variables, configPath } = await loadAndValidateVariablesForSchema(appName, appPath);
105
102
  const schemaBasePath = validateExternalIntegrationBlock(variables, appName);
106
- const variablesPath = path.join(appPath, 'variables.yaml');
107
103
 
108
- return resolveAndValidateSchemaPath(schemaBasePath, variablesPath);
104
+ return resolveAndValidateSchemaPath(schemaBasePath, configPath);
109
105
  }
110
106
 
111
107
  /**
@@ -180,7 +176,7 @@ function resolveDatasourceFiles(schemaBasePath, datasourceFiles) {
180
176
  }
181
177
 
182
178
  /**
183
- * Loads and validates variables.yaml
179
+ * Loads and validates application config
184
180
  * @async
185
181
  * @function loadAndValidateVariables
186
182
  * @param {string} appPath - Application path
@@ -188,33 +184,27 @@ function resolveDatasourceFiles(schemaBasePath, datasourceFiles) {
188
184
  * @throws {Error} If file not found or invalid
189
185
  */
190
186
  async function loadAndValidateVariables(appPath) {
191
- const variablesPath = path.join(appPath, 'variables.yaml');
192
-
193
- if (!fs.existsSync(variablesPath)) {
194
- throw new Error(`variables.yaml not found: ${variablesPath}`);
195
- }
196
-
197
- const content = fs.readFileSync(variablesPath, 'utf8');
187
+ const configPath = resolveApplicationConfigPath(appPath);
198
188
  try {
199
- return yaml.load(content);
189
+ return loadConfigFile(configPath);
200
190
  } catch (error) {
201
- throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
191
+ throw new Error(`Application config: ${error.message}`);
202
192
  }
203
193
  }
204
194
 
205
- async function resolveExternalFiles(appName) {
195
+ async function resolveExternalFiles(appName, options = {}) {
206
196
  if (!appName || typeof appName !== 'string') {
207
197
  throw new Error('App name is required and must be a string');
208
198
  }
209
199
 
210
- const { appPath } = await detectAppType(appName);
200
+ const { appPath } = await detectAppType(appName, options);
211
201
  const variables = await loadAndValidateVariables(appPath);
212
202
 
213
203
  if (!variables.externalIntegration) {
214
204
  return [];
215
205
  }
216
206
 
217
- const schemaBasePath = await resolveSchemaBasePath(appName);
207
+ const schemaBasePath = await resolveSchemaBasePath(appName, options);
218
208
  const systemFiles = resolveSystemFiles(schemaBasePath, variables.externalIntegration.systems);
219
209
  const datasourceFiles = resolveDatasourceFiles(schemaBasePath, variables.externalIntegration.dataSources);
220
210
 
@@ -166,9 +166,9 @@ function getPortFromLocalEnv(localEnv) {
166
166
  }
167
167
 
168
168
  /**
169
- * Gets port from variables.yaml file (build.localPort if positive, else port). Uses port-resolver.
169
+ * Gets port from application config file (build.localPort if positive, else port). Uses port-resolver.
170
170
  * @function getPortFromVariablesFile
171
- * @param {string} variablesPath - Path to variables.yaml
171
+ * @param {string} variablesPath - Path to application config
172
172
  * @returns {number|null} Port value or null
173
173
  */
174
174
  function getPortFromVariablesFile(variablesPath) {
@@ -199,10 +199,10 @@ function applyDeveloperIdAdjustment(baseAppPort, devIdNum) {
199
199
 
200
200
  /**
201
201
  * Calculate application port following override chain and developer-id adjustment
202
- * Override chain: env-config.yaml → config.yaml → variables.yaml build.localPort → variables.yaml port
202
+ * Override chain: env-config.yaml → config.yaml → application.yaml build.localPort → application.yaml port
203
203
  * @async
204
204
  * @function calculateAppPort
205
- * @param {string} [variablesPath] - Path to variables.yaml
205
+ * @param {string} [variablesPath] - Path to application config
206
206
  * @param {Object} localEnv - Local environment config from env-config.yaml and config.yaml
207
207
  * @param {string} envContent - Environment content for fallback
208
208
  * @param {number} devIdNum - Developer ID number
@@ -212,7 +212,7 @@ async function calculateAppPort(variablesPath, localEnv, envContent, devIdNum) {
212
212
  // Start with env-config value
213
213
  let baseAppPort = getPortFromLocalEnv(localEnv);
214
214
 
215
- // Override with variables.yaml → build.localPort (strongest)
215
+ // Override with application config → build.localPort (strongest)
216
216
  const variablesPort = getPortFromVariablesFile(variablesPath);
217
217
  if (variablesPort !== null) {
218
218
  baseAppPort = variablesPort;
@@ -249,11 +249,11 @@ function updateLocalhostUrls(content, baseAppPort, appPort) {
249
249
  /**
250
250
  * Adjust infra-related ports in resolved .env content for local environment
251
251
  * Only handles PORT variable (other ports handled by interpolation)
252
- * Follows flow: getEnvHosts() → config.yaml override → variables.yaml override → developer-id adjustment
252
+ * Follows flow: getEnvHosts() → config.yaml override → application config override → developer-id adjustment
253
253
  * @async
254
254
  * @function adjustLocalEnvPortsInContent
255
255
  * @param {string} envContent - Resolved .env content
256
- * @param {string} [variablesPath] - Path to variables.yaml (to read build.localPort)
256
+ * @param {string} [variablesPath] - Path to application config (to read build.localPort)
257
257
  * @returns {Promise<string>} Updated content with local ports
258
258
  */
259
259
  /**