@aifabrix/builder 2.39.2 → 2.40.0

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 (116) hide show
  1. package/.cursor/rules/project-rules.mdc +6 -6
  2. package/README.md +2 -2
  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/lib/api/credentials.api.js +5 -5
  10. package/lib/api/deployments.api.js +2 -2
  11. package/lib/api/pipeline.api.js +17 -17
  12. package/lib/api/wizard.api.js +2 -2
  13. package/lib/app/config.js +11 -6
  14. package/lib/app/deploy-config.js +13 -16
  15. package/lib/app/deploy.js +29 -22
  16. package/lib/app/display.js +1 -1
  17. package/lib/app/dockerfile.js +11 -12
  18. package/lib/app/helpers.js +51 -13
  19. package/lib/app/index.js +14 -2
  20. package/lib/app/prompts.js +37 -45
  21. package/lib/app/push.js +8 -11
  22. package/lib/app/readme.js +16 -12
  23. package/lib/app/register.js +3 -3
  24. package/lib/app/run-helpers.js +31 -22
  25. package/lib/app/run.js +44 -5
  26. package/lib/app/show-display.js +104 -44
  27. package/lib/app/show.js +123 -43
  28. package/lib/build/index.js +11 -18
  29. package/lib/cli/setup-app.js +38 -28
  30. package/lib/cli/setup-auth.js +18 -15
  31. package/lib/cli/setup-credential-deployment.js +3 -1
  32. package/lib/cli/setup-external-system.js +35 -16
  33. package/lib/cli/setup-infra.js +45 -23
  34. package/lib/cli/setup-utility.js +79 -31
  35. package/lib/commands/app-logs.js +165 -10
  36. package/lib/commands/app.js +30 -26
  37. package/lib/commands/convert.js +202 -0
  38. package/lib/commands/credential-list.js +78 -17
  39. package/lib/commands/datasource.js +24 -24
  40. package/lib/commands/deployment-list.js +13 -6
  41. package/lib/commands/up-common.js +80 -42
  42. package/lib/commands/up-dataplane.js +15 -14
  43. package/lib/commands/up-miso.js +15 -14
  44. package/lib/commands/upload.js +163 -0
  45. package/lib/commands/wizard-core.js +5 -4
  46. package/lib/core/diff.js +84 -9
  47. package/lib/core/key-generator.js +9 -12
  48. package/lib/core/secrets-docker-env.js +2 -2
  49. package/lib/core/secrets.js +3 -2
  50. package/lib/core/templates.js +2 -2
  51. package/lib/datasource/deploy.js +2 -1
  52. package/lib/deployment/deployer.js +76 -48
  53. package/lib/external-system/delete.js +0 -1
  54. package/lib/external-system/deploy-helpers.js +5 -6
  55. package/lib/external-system/deploy.js +7 -2
  56. package/lib/external-system/download-helpers.js +4 -4
  57. package/lib/external-system/download.js +11 -10
  58. package/lib/external-system/generator.js +19 -17
  59. package/lib/external-system/test.js +10 -15
  60. package/lib/generator/builders.js +1 -1
  61. package/lib/generator/external-controller-manifest.js +26 -29
  62. package/lib/generator/external-schema-utils.js +6 -18
  63. package/lib/generator/external.js +32 -27
  64. package/lib/generator/github.js +1 -1
  65. package/lib/generator/helpers.js +12 -19
  66. package/lib/generator/index.js +15 -15
  67. package/lib/generator/parse-image.js +35 -0
  68. package/lib/generator/split-readme.js +105 -0
  69. package/lib/generator/split-variables.js +149 -0
  70. package/lib/generator/split.js +86 -246
  71. package/lib/generator/wizard.js +46 -69
  72. package/lib/schema/application-schema.json +4 -4
  73. package/lib/schema/deployment-rules.yaml +0 -4
  74. package/lib/schema/external-datasource.schema.json +5 -0
  75. package/lib/schema/external-system.schema.json +10 -0
  76. package/lib/utils/app-config-resolver.js +52 -0
  77. package/lib/utils/app-register-api.js +1 -1
  78. package/lib/utils/app-register-auth.js +1 -1
  79. package/lib/utils/app-register-config.js +16 -23
  80. package/lib/utils/app-register-display.js +22 -3
  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 +39 -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 +33 -27
  107. package/lib/validation/validator.js +50 -30
  108. package/package.json +2 -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/integration/hubspot/variables.yaml +0 -17
  114. /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
  115. /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
  116. /package/templates/applications/miso-controller/{variables.yaml → application.yaml} +0 -0
@@ -371,27 +371,11 @@ function getDeployJsonPath(appName, appType, preferNew = false) {
371
371
  // If neither exists, return new naming (for generation)
372
372
  return newPath;
373
373
  }
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
-
374
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
375
+ const { loadConfigFile } = require('./config-format');
392
376
  /**
393
377
  * Checks if app type is external from variables object
394
- * @param {Object} variables - Parsed variables.yaml object
378
+ * @param {Object} variables - Parsed application config object
395
379
  * @returns {boolean} True if app type is external
396
380
  */
397
381
  function isExternalAppType(variables) {
@@ -399,20 +383,25 @@ function isExternalAppType(variables) {
399
383
  }
400
384
 
401
385
  /**
402
- * Checks integration folder for external app type
386
+ * Checks integration folder for any valid application config
403
387
  * @param {string} appName - Application name
404
- * @returns {Object|null} App type info or null if not found
388
+ * @returns {Object|null} App type info or null if no config found
405
389
  */
406
390
  function checkIntegrationFolder(appName) {
407
391
  const integrationPath = getIntegrationPath(appName);
408
- const variablesPath = path.join(integrationPath, 'variables.yaml');
409
- const variables = readVariablesFile(variablesPath);
410
-
411
- if (variables && isExternalAppType(variables)) {
392
+ let variables;
393
+ try {
394
+ const configPath = resolveApplicationConfigPath(integrationPath);
395
+ variables = loadConfigFile(configPath);
396
+ } catch {
397
+ return null;
398
+ }
399
+ if (variables) {
400
+ const isExternal = isExternalAppType(variables);
412
401
  return {
413
- isExternal: true,
402
+ isExternal,
414
403
  appPath: integrationPath,
415
- appType: 'external',
404
+ appType: isExternal ? 'external' : 'regular',
416
405
  baseDir: 'integration'
417
406
  };
418
407
  }
@@ -422,13 +411,20 @@ function checkIntegrationFolder(appName) {
422
411
  /**
423
412
  * Checks builder folder for app type
424
413
  * @param {string} appName - Application name
425
- * @returns {Object} App type info
414
+ * @returns {Object|null} App type info or null if no config found
426
415
  */
427
416
  function checkBuilderFolder(appName) {
428
417
  const builderPath = getBuilderPath(appName);
429
- const variablesPath = path.join(builderPath, 'variables.yaml');
430
- const variables = readVariablesFile(variablesPath);
431
-
418
+ if (!fs.existsSync(builderPath)) {
419
+ return null;
420
+ }
421
+ let variables;
422
+ try {
423
+ const configPath = resolveApplicationConfigPath(builderPath);
424
+ variables = loadConfigFile(configPath);
425
+ } catch {
426
+ return null;
427
+ }
432
428
  if (variables) {
433
429
  const isExternal = isExternalAppType(variables);
434
430
  return {
@@ -438,51 +434,27 @@ function checkBuilderFolder(appName) {
438
434
  baseDir: 'builder'
439
435
  };
440
436
  }
441
-
442
- // Default to regular app in builder folder
443
- return {
444
- isExternal: false,
445
- appPath: builderPath,
446
- appType: 'regular',
447
- baseDir: 'builder'
448
- };
437
+ return null;
449
438
  }
450
-
451
439
  /**
452
- * Detects if an app is external type by checking variables.yaml
453
- * Checks both integration/ and builder/ folders for backward compatibility
440
+ * Detects if an app is external type by checking application config.
441
+ * Resolution order: integration/ first, then builder/; if neither exists, throws.
442
+ * No CLI flag overrides this order.
454
443
  *
455
444
  * @param {string} appName - Application name
456
- * @param {Object} [options] - Detection options
457
- * @param {string} [options.type] - Forced application type (external)
445
+ * @param {Object} [options] - Detection options (reserved; options.type is ignored)
458
446
  * @returns {Promise<{isExternal: boolean, appPath: string, appType: string, baseDir?: string}>}
447
+ * @throws {Error} When app not found in integration/ or builder/
459
448
  */
460
- async function detectAppType(appName, options = {}) {
449
+ async function detectAppType(appName, _options = {}) {
461
450
  if (!appName || typeof appName !== 'string') {
462
451
  throw new Error('App name is required and must be a string');
463
452
  }
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
453
  const integrationResult = checkIntegrationFolder(appName);
480
- if (integrationResult) {
481
- return integrationResult;
482
- }
483
-
484
- // Check builder folder (backward compatibility)
485
- return checkBuilderFolder(appName);
454
+ if (integrationResult) return integrationResult;
455
+ const builderResult = checkBuilderFolder(appName);
456
+ if (builderResult) return builderResult;
457
+ throw new Error(`App '${appName}' not found in integration/${appName} or builder/${appName}`);
486
458
  }
487
459
  module.exports = {
488
460
  getAifabrixHome,
@@ -494,6 +466,7 @@ module.exports = {
494
466
  getIntegrationPath,
495
467
  getBuilderPath,
496
468
  getDeployJsonPath,
469
+ resolveApplicationConfigPath,
497
470
  detectAppType,
498
471
  clearProjectRootCache
499
472
  };
@@ -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
  /**
@@ -119,7 +119,7 @@ function buildHostnameToServiceMap(dockerHosts) {
119
119
  }
120
120
 
121
121
  /**
122
- * Resolves port for a single URL by looking up service's variables.yaml
122
+ * Resolves port for a single URL by looking up service's application config
123
123
  * @function resolveUrlPort
124
124
  * @param {string} protocol - URL protocol (http:// or https://)
125
125
  * @param {string} hostname - Service hostname
@@ -135,24 +135,22 @@ function resolveUrlPort(protocol, hostname, port, urlPath, hostnameToService) {
135
135
  return `${protocol}${hostname}:${port}${urlPath}`;
136
136
  }
137
137
 
138
- // Try to load service's variables.yaml
139
- const serviceVariablesPath = path.join(process.cwd(), 'builder', serviceName, 'variables.yaml');
140
- if (!fs.existsSync(serviceVariablesPath)) {
141
- // Service variables.yaml not found, keep original port
138
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
139
+ const { loadConfigFile } = require('./config-format');
140
+ const builderPath = path.join(process.cwd(), 'builder', serviceName);
141
+ let serviceVariablesPath;
142
+ try {
143
+ serviceVariablesPath = resolveApplicationConfigPath(builderPath);
144
+ } catch {
142
145
  return `${protocol}${hostname}:${port}${urlPath}`;
143
146
  }
144
147
 
145
148
  try {
146
- const variablesContent = fs.readFileSync(serviceVariablesPath, 'utf8');
147
- const variables = yaml.load(variablesContent);
148
-
149
+ const variables = loadConfigFile(serviceVariablesPath);
149
150
  const containerPort = getContainerPort(variables, port);
150
-
151
- // Replace port in URL
152
151
  return `${protocol}${hostname}:${containerPort}${urlPath}`;
153
152
  } catch (error) {
154
- // Error loading variables.yaml, keep original port
155
- logger.warn(`Warning: Could not load variables.yaml for service ${serviceName}: ${error.message}`);
153
+ logger.warn(`Warning: Could not load application config for service ${serviceName}: ${error.message}`);
156
154
  return `${protocol}${hostname}:${port}${urlPath}`;
157
155
  }
158
156
  }
@@ -14,7 +14,7 @@ const chalk = require('chalk');
14
14
  const logger = require('./logger');
15
15
 
16
16
  /**
17
- * Loads template variables from template's variables.yaml file
17
+ * Loads template variables from template's application.yaml file
18
18
  * @async
19
19
  * @function loadTemplateVariables
20
20
  * @param {string} templateName - Template name
@@ -27,15 +27,15 @@ async function loadTemplateVariables(templateName) {
27
27
 
28
28
  const yaml = require('js-yaml');
29
29
  const templatePath = path.join(__dirname, '..', '..', 'templates', 'applications', templateName);
30
- const templateVariablesPath = path.join(templatePath, 'variables.yaml');
30
+ const templateVariablesPath = path.join(templatePath, 'application.yaml');
31
31
 
32
32
  try {
33
33
  const templateContent = await fs.readFile(templateVariablesPath, 'utf8');
34
34
  return yaml.load(templateContent);
35
35
  } catch (error) {
36
- // Template variables.yaml not found or invalid, continue without it
36
+ // Template application.yaml not found or invalid, continue without it
37
37
  if (error.code !== 'ENOENT') {
38
- logger.warn(chalk.yellow(`⚠️ Warning: Could not load template variables.yaml: ${error.message}`));
38
+ logger.warn(chalk.yellow(`⚠️ Warning: Could not load template application.yaml: ${error.message}`));
39
39
  }
40
40
  return null;
41
41
  }
@@ -101,7 +101,7 @@ function updateDatabaseConfig(variables, options, appName) {
101
101
  }
102
102
 
103
103
  /**
104
- * Updates variables.yaml file after copying from template
104
+ * Updates application config file after copying from template
105
105
  * Updates app.key, displayName, and port with actual values
106
106
  * @async
107
107
  * @function updateTemplateVariables
@@ -111,21 +111,21 @@ function updateDatabaseConfig(variables, options, appName) {
111
111
  * @param {Object} config - Final configuration
112
112
  */
113
113
  async function updateTemplateVariables(appPath, appName, options, config) {
114
- const variablesPath = path.join(appPath, 'variables.yaml');
114
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
115
+ const { loadConfigFile, writeConfigFile } = require('./config-format');
115
116
  try {
116
- const yaml = require('js-yaml');
117
- const variablesContent = await fs.readFile(variablesPath, 'utf8');
118
- const variables = yaml.load(variablesContent);
117
+ const variablesPath = resolveApplicationConfigPath(appPath);
118
+ const variables = loadConfigFile(variablesPath) || {};
119
119
 
120
120
  updateAppMetadata(variables, appName);
121
121
  updatePort(variables, options, config);
122
122
  updateBuildConfig(variables);
123
123
  updateDatabaseConfig(variables, options, appName);
124
124
 
125
- await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }));
125
+ writeConfigFile(variablesPath, variables);
126
126
  } catch (error) {
127
- if (error.code !== 'ENOENT') {
128
- logger.warn(chalk.yellow(`⚠️ Warning: Could not update variables.yaml: ${error.message}`));
127
+ if (error.message && !error.message.includes('not found')) {
128
+ logger.warn(chalk.yellow(`⚠️ Warning: Could not update application config: ${error.message}`));
129
129
  }
130
130
  }
131
131
  }
@@ -197,7 +197,7 @@ function mergeAuthentication(merged, templateVariables) {
197
197
  * Merges template variables into options
198
198
  * @function mergeTemplateVariables
199
199
  * @param {Object} options - User-provided options
200
- * @param {Object} templateVariables - Template variables from variables.yaml
200
+ * @param {Object} templateVariables - Template variables from application.yaml
201
201
  * @returns {Object} Merged options object
202
202
  */
203
203
  function mergeTemplateVariables(options, templateVariables) {
@@ -427,6 +427,24 @@ async function getDeviceOnlyAuth(controllerUrl) {
427
427
  throw new Error('Device token authentication required. Run "aifabrix login" to authenticate.');
428
428
  }
429
429
 
430
+ /**
431
+ * Ensures auth config has a Bearer token for Dataplane pipeline endpoints.
432
+ * Dataplane pipeline (upload, validate, publish, test) accepts OAuth2 (Bearer) or API_KEY only;
433
+ * client id/secret are rejected. Call this before invoking pipeline API functions.
434
+ * @param {Object} authConfig - Authentication configuration (may have token, clientId, clientSecret)
435
+ * @throws {Error} If authConfig has only client id/secret (no token)
436
+ */
437
+ function requireBearerForDataplanePipeline(authConfig) {
438
+ if (authConfig.token) {
439
+ return;
440
+ }
441
+ if (authConfig.clientId || authConfig.clientSecret) {
442
+ throw new Error(
443
+ 'Dataplane pipeline endpoints require OAuth2 (Bearer token). Client id/secret are not accepted. Run "aifabrix login" to authenticate, then retry.'
444
+ );
445
+ }
446
+ }
447
+
430
448
  module.exports = {
431
449
  getDeviceToken,
432
450
  getClientToken,
@@ -440,6 +458,6 @@ module.exports = {
440
458
  forceRefreshDeviceToken,
441
459
  getDeploymentAuth,
442
460
  getDeviceOnlyAuth,
443
- extractClientCredentials
461
+ extractClientCredentials,
462
+ requireBearerForDataplanePipeline
444
463
  };
445
-
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Variable Transformation Utilities
3
3
  *
4
- * Transforms nested variables.yaml structure to flat schema format
4
+ * Transforms nested application config structure to flat schema format
5
5
  * Converts app.*, image.*, requires.* to schema-compatible structure
6
6
  *
7
7
  * @fileoverview Variable transformation utilities for AI Fabrix Builder
@@ -309,7 +309,7 @@ function transformOptionalFields(variables, transformed) {
309
309
  }
310
310
 
311
311
  /**
312
- * Transforms nested variables.yaml structure to flat schema format
312
+ * Transforms nested application config structure to flat schema format
313
313
  * Converts app.*, image.*, requires.* to schema-compatible structure
314
314
  * Handles both flat and nested structures
315
315
  *
@@ -8,9 +8,9 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
- const fs = require('fs');
12
11
  const chalk = require('chalk');
13
12
  const logger = require('../utils/logger');
13
+ const { loadConfigFile } = require('../utils/config-format');
14
14
 
15
15
  /**
16
16
  * Displays application validation results
@@ -44,12 +44,11 @@ function displayApplicationValidation(application) {
44
44
  * Extracts dimensions from a datasource file
45
45
  * @function extractDimensionsFromDatasource
46
46
  * @param {string} filePath - Path to datasource file
47
- * @returns {Object} Dimensions info { dimensions: Object, hasDimensions: boolean }
47
+ * @returns {Object} Dimensions info { dimensions: Object, dimensionKeys: string[], hasDimensions: boolean }
48
48
  */
49
49
  function extractDimensionsFromDatasource(filePath) {
50
50
  try {
51
- const content = fs.readFileSync(filePath, 'utf8');
52
- const parsed = JSON.parse(content);
51
+ const parsed = loadConfigFile(filePath);
53
52
 
54
53
  // Check fieldMappings.dimensions (primary location)
55
54
  const dimensions = parsed.fieldMappings?.dimensions || {};