@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
@@ -2,7 +2,7 @@
2
2
  * AI Fabrix Builder Deployment Key Generator
3
3
  *
4
4
  * This module generates SHA256-based deployment keys for controller authentication.
5
- * Keys are computed from variables.yaml content to ensure deployment integrity.
5
+ * Keys are computed from application config content to ensure deployment integrity.
6
6
  *
7
7
  * @fileoverview Deployment key generation for AI Fabrix Builder
8
8
  * @author AI Fabrix Team
@@ -14,14 +14,14 @@ const fs = require('fs');
14
14
  const path = require('path');
15
15
 
16
16
  /**
17
- * Generates deployment key from variables.yaml content
17
+ * Generates deployment key from application config content
18
18
  * Creates SHA256 hash for controller authentication and deployment integrity
19
19
  *
20
20
  * @async
21
21
  * @function generateDeploymentKey
22
22
  * @param {string} appName - Name of the application
23
- * @returns {Promise<string>} SHA256 hash of variables.yaml content
24
- * @throws {Error} If variables.yaml cannot be read
23
+ * @returns {Promise<string>} SHA256 hash of application config content
24
+ * @throws {Error} If application config cannot be read
25
25
  *
26
26
  * @example
27
27
  * const key = await generateDeploymentKey('myapp');
@@ -32,22 +32,19 @@ async function generateDeploymentKey(appName) {
32
32
  throw new Error('App name is required and must be a string');
33
33
  }
34
34
 
35
- const variablesPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
36
-
37
- if (!fs.existsSync(variablesPath)) {
38
- throw new Error(`variables.yaml not found: ${variablesPath}`);
39
- }
40
-
35
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
36
+ const builderPath = path.join(process.cwd(), 'builder', appName);
37
+ const variablesPath = resolveApplicationConfigPath(builderPath);
41
38
  const content = fs.readFileSync(variablesPath, 'utf8');
42
39
  return generateDeploymentKeyFromContent(content);
43
40
  }
44
41
 
45
42
  /**
46
- * Generates deployment key from raw variables.yaml content
43
+ * Generates deployment key from raw application config content
47
44
  * Useful for testing or when content is already loaded
48
45
  *
49
46
  * @function generateDeploymentKeyFromContent
50
- * @param {string} content - Raw variables.yaml content
47
+ * @param {string} content - Raw application config content
51
48
  * @returns {string} SHA256 hash of content
52
49
  *
53
50
  * @example
@@ -60,12 +60,12 @@ function getContainerPortFromDockerEnv(dockerEnv) {
60
60
 
61
61
  /**
62
62
  * Updates PORT in resolved content for docker environment
63
- * Sets PORT to container port (build.containerPort or port from variables.yaml)
63
+ * Sets PORT to container port (build.containerPort or port from application config)
64
64
  * NOT the host port (which includes developer-id offset)
65
65
  * @async
66
66
  * @function updatePortForDocker
67
67
  * @param {string} resolved - Resolved environment content
68
- * @param {string} variablesPath - Path to variables.yaml file
68
+ * @param {string} variablesPath - Path to application config file
69
69
  * @returns {Promise<string>} Updated content with PORT set
70
70
  */
71
71
  async function updatePortForDocker(resolved, variablesPath) {
@@ -12,6 +12,7 @@
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
14
  const logger = require('../utils/logger');
15
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
15
16
  const config = require('./config');
16
17
  const {
17
18
  interpolateEnvVars,
@@ -288,7 +289,7 @@ async function applyEnvironmentTransformations(resolved, environment, variablesP
288
289
  async function generateEnvContent(appName, secretsPath, environment = 'local', force = false) {
289
290
  const builderPath = pathsUtil.getBuilderPath(appName);
290
291
  const templatePath = path.join(builderPath, 'env.template');
291
- const variablesPath = path.join(builderPath, 'variables.yaml');
292
+ const variablesPath = resolveApplicationConfigPath(builderPath);
292
293
  const template = loadEnvTemplate(templatePath);
293
294
  const secretsPaths = await getActualSecretsPath(secretsPath, appName);
294
295
  if (force) {
@@ -391,7 +392,7 @@ function mergeEnvContentPreservingExisting(newContent, existingMap) {
391
392
  */
392
393
  async function generateEnvFile(appName, secretsPath, environment = 'local', force = false, skipOutputPath = false, preserveFromPath = null) {
393
394
  const builderPath = pathsUtil.getBuilderPath(appName);
394
- const variablesPath = path.join(builderPath, 'variables.yaml');
395
+ const variablesPath = resolveApplicationConfigPath(builderPath);
395
396
  const envPath = path.join(builderPath, '.env');
396
397
 
397
398
  const resolved = await generateEnvContent(appName, secretsPath, environment, force);
@@ -8,7 +8,7 @@
8
8
  const yaml = require('js-yaml');
9
9
 
10
10
  /**
11
- * Generate variables.yaml content for an application
11
+ * Generate application.yaml content for an application
12
12
  * Matches application-schema.json structure
13
13
  * @param {string} appName - Application name
14
14
  * @param {Object} config - Configuration options
@@ -166,7 +166,7 @@ function generateVariablesYaml(appName, config) {
166
166
  const displayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
167
167
  const appType = config.type || 'webapp';
168
168
 
169
- // For external type, create minimal variables.yaml
169
+ // For external type, create minimal application config
170
170
  if (appType === 'external') {
171
171
  const variables = generateExternalSystemVariables(appName, displayName, config);
172
172
  return dumpVariablesToYaml(variables);
@@ -11,7 +11,7 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const chalk = require('chalk');
14
- const { getDeploymentAuth } = require('../utils/token-manager');
14
+ const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
15
15
  const { getEnvironmentApplication } = require('../api/environments.api');
16
16
  const { publishDatasourceViaPipeline } = require('../api/pipeline.api');
17
17
  const { formatApiError } = require('../utils/api-error-handler');
@@ -152,6 +152,7 @@ async function setupDeploymentAuth(controllerUrl, environment, appKey) {
152
152
  * @throws {Error} If publish fails
153
153
  */
154
154
  async function publishDatasourceToDataplane(dataplaneUrl, systemKey, authConfig, datasourceConfig) {
155
+ requireBearerForDataplanePipeline(authConfig);
155
156
  logger.log(chalk.blue('\n🚀 Publishing datasource to dataplane...'));
156
157
 
157
158
  const publishResponse = await publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceConfig);
@@ -15,6 +15,7 @@ const logger = require('../utils/logger');
15
15
  const { validateControllerUrl, validateEnvironmentKey } = require('../utils/deployment-validation');
16
16
  const { handleDeploymentError, handleDeploymentErrors } = require('../utils/deployment-errors');
17
17
  const { validatePipeline, deployPipeline, getPipelineDeployment } = require('../api/pipeline.api');
18
+ const { getAuthUser } = require('../api/auth.api');
18
19
  const { handleValidationResponse } = require('../utils/deployment-validation-helpers');
19
20
  const {
20
21
  convertToPipelineAuthConfig,
@@ -36,73 +37,72 @@ function transformExternalManifestForPipeline(manifest) {
36
37
 
37
38
  /**
38
39
  * Build validation data for deployment
40
+ * When authenticated with bearer token only, clientId/clientSecret are not sent (controller uses token).
39
41
  * @async
40
42
  * @param {Object} manifest - Application manifest/config
41
43
  * @param {string} validatedEnvKey - Validated environment key
42
44
  * @param {Object} authConfig - Authentication configuration
43
45
  * @param {Object} options - Additional options
44
- * @returns {Promise<Object>} Object with validationData and pipelineAuthConfig
46
+ * @returns {Promise<Object>} Object with validationData, pipelineAuthConfig, and useBearerOnly
45
47
  */
46
48
  async function buildValidationData(manifest, validatedEnvKey, authConfig, options) {
47
- const tokenManager = require('../utils/token-manager');
48
- let clientId;
49
- let clientSecret;
50
- let pipelineAuthConfig;
49
+ const repositoryUrl = options.repositoryUrl || `https://github.com/aifabrix/${manifest.key}`;
51
50
 
52
- try {
53
- const credentials = await tokenManager.extractClientCredentials(
54
- authConfig,
55
- manifest.key,
56
- validatedEnvKey,
57
- options
58
- );
59
- clientId = credentials.clientId;
60
- clientSecret = credentials.clientSecret;
61
- pipelineAuthConfig = {
62
- type: 'client-credentials',
63
- clientId,
64
- clientSecret
51
+ if (authConfig.type === 'bearer' && authConfig.token && !authConfig.clientId) {
52
+ const pipelineAuthConfig = { type: 'bearer', token: authConfig.token };
53
+ const validationData = {
54
+ clientId: manifest.key,
55
+ repositoryUrl,
56
+ applicationConfig: manifest
65
57
  };
66
- } catch (credError) {
67
- if (authConfig.type === 'bearer' && authConfig.token) {
68
- pipelineAuthConfig = { type: 'bearer', token: authConfig.token };
69
- clientId = manifest.key;
70
- clientSecret = '';
71
- } else {
72
- throw credError;
73
- }
58
+ return { validationData, pipelineAuthConfig, useBearerOnly: true };
74
59
  }
75
60
 
76
- const repositoryUrl = options.repositoryUrl || `https://github.com/aifabrix/${manifest.key}`;
61
+ const tokenManager = require('../utils/token-manager');
62
+ const credentials = await tokenManager.extractClientCredentials(
63
+ authConfig,
64
+ manifest.key,
65
+ validatedEnvKey,
66
+ options
67
+ );
68
+ const pipelineAuthConfig = {
69
+ type: 'client-credentials',
70
+ clientId: credentials.clientId,
71
+ clientSecret: credentials.clientSecret
72
+ };
77
73
  const validationData = {
78
- clientId: clientId || '',
79
- clientSecret: clientSecret || '',
80
- repositoryUrl: repositoryUrl,
74
+ clientId: credentials.clientId || '',
75
+ clientSecret: credentials.clientSecret || '',
76
+ repositoryUrl,
81
77
  applicationConfig: manifest
82
78
  };
83
-
84
- return { validationData, pipelineAuthConfig };
79
+ return { validationData, pipelineAuthConfig, useBearerOnly: false };
85
80
  }
86
81
 
87
82
  /**
88
83
  * Handle authentication errors during validation
89
84
  * @param {Error} error - Error object
90
85
  * @param {string} appKey - Application key
86
+ * @param {boolean} [useBearerOnly] - True when auth was bearer token only (no client id/secret sent)
91
87
  * @throws {Error} Enhanced authentication error
92
88
  */
93
- function handleValidationAuthError(error, appKey) {
94
- if (error.status === 401 || (error.response && error.response.status === 401)) {
95
- const authError = new Error(
96
- `Authentication failed: Invalid or expired credentials for application '${appKey}'.\n` +
97
- 'The provided Client ID and Client Secret are incorrect or have been revoked.\n\n' +
98
- '💡 If the application already exists, rotate the secret:\n' +
99
- ` aifabrix app rotate-secret ${appKey}\n\n` +
100
- '💡 Otherwise, ensure credentials are correct in ~/.aifabrix/secrets.local.yaml or use --client-id and --client-secret flags.'
101
- );
102
- authError.status = 401;
103
- authError.originalError = error;
104
- throw authError;
89
+ function handleValidationAuthError(error, appKey, useBearerOnly) {
90
+ if (error.status !== 401 && (!error.response || error.response.status !== 401)) {
91
+ return;
105
92
  }
93
+ const authError = new Error(
94
+ useBearerOnly
95
+ ? 'Authentication failed: Your authentication token is invalid or expired.\n\n' +
96
+ 'To authenticate, run:\n aifabrix login --method device --controller <url>'
97
+ : `Authentication failed: Invalid or expired credentials for application '${appKey}'.\n` +
98
+ 'The provided Client ID and Client Secret are incorrect or have been revoked.\n\n' +
99
+ '💡 If the application already exists, rotate the secret:\n' +
100
+ ` aifabrix app rotate-secret ${appKey}\n\n` +
101
+ '💡 Otherwise, ensure credentials are correct in ~/.aifabrix/secrets.local.yaml or use --client-id and --client-secret flags.'
102
+ );
103
+ authError.status = 401;
104
+ authError.originalError = error;
105
+ throw authError;
106
106
  }
107
107
 
108
108
  /**
@@ -122,8 +122,8 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
122
122
  const validatedEnvKey = validateEnvironmentKey(envKey);
123
123
  const maxRetries = options.maxRetries || 3;
124
124
 
125
- // Build validation data
126
- const { validationData, pipelineAuthConfig } = await buildValidationData(manifest, validatedEnvKey, authConfig, options);
125
+ // Build validation data (bearer-only: no clientId/clientSecret sent)
126
+ const { validationData, pipelineAuthConfig, useBearerOnly } = await buildValidationData(manifest, validatedEnvKey, authConfig, options);
127
127
 
128
128
  let lastError;
129
129
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -133,8 +133,8 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
133
133
  } catch (error) {
134
134
  lastError = error;
135
135
 
136
- // Handle authentication errors (401) - credentials are invalid, not missing
137
- handleValidationAuthError(error, manifest.key);
136
+ // Handle authentication errors (401)
137
+ handleValidationAuthError(error, manifest.key, useBearerOnly);
138
138
 
139
139
  const shouldRetry = attempt < maxRetries && error.status && error.status >= 500;
140
140
  if (shouldRetry) {
@@ -310,6 +310,32 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
310
310
  throw new Error('Deployment timeout: Maximum polling attempts reached');
311
311
  }
312
312
 
313
+ /**
314
+ * When using bearer token only (no client credentials), verify token is valid before deploy.
315
+ * @async
316
+ * @param {string} controllerUrl - Controller URL
317
+ * @param {Object} authConfig - Authentication configuration
318
+ * @throws {Error} If token is invalid or expired
319
+ */
320
+ async function ensureBearerTokenValid(controllerUrl, authConfig) {
321
+ if (authConfig.type !== 'bearer' || !authConfig.token || authConfig.clientId) {
322
+ return;
323
+ }
324
+ try {
325
+ const response = await getAuthUser(controllerUrl, authConfig);
326
+ if (response && response.success && response.data && response.data.authenticated !== false) {
327
+ return;
328
+ }
329
+ } catch (_) {
330
+ // Fall through to throw below
331
+ }
332
+ throw new Error(
333
+ 'Your authentication token is invalid or expired.\n\n' +
334
+ 'Run: aifabrix login --method device --controller <url>\n\n' +
335
+ 'Then run deploy again.'
336
+ );
337
+ }
338
+
313
339
  /**
314
340
  * Validates and sends deployment request to controller
315
341
  * Implements two-step process: validate then deploy
@@ -322,6 +348,8 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
322
348
  * @returns {Promise<Object>} Deployment result
323
349
  */
324
350
  async function sendDeployment(url, validatedEnvKey, manifest, authConfig, options) {
351
+ await ensureBearerTokenValid(url, authConfig);
352
+
325
353
  // Step 1: Validate deployment
326
354
  logger.log(chalk.blue('🔍 Validating deployment configuration...'));
327
355
  const validateResult = await validateDeployment(url, validatedEnvKey, manifest, authConfig, {
@@ -50,7 +50,6 @@ async function getAuthAndDataplane(systemKey, _options) {
50
50
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
51
51
  logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
52
52
  const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
53
- logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
54
53
 
55
54
  return { authConfig, dataplaneUrl, environment, controllerUrl };
56
55
  }
@@ -11,11 +11,10 @@
11
11
  const fs = require('fs').promises;
12
12
  const fsSync = require('fs');
13
13
  const path = require('path');
14
- const yaml = require('js-yaml');
15
14
  const { detectAppType, getDeployJsonPath } = require('../utils/paths');
16
15
 
17
16
  /**
18
- * Loads variables.yaml for an application
17
+ * Loads application config for an application
19
18
  * @async
20
19
  * @function loadVariablesYaml
21
20
  * @param {string} appName - Application name
@@ -23,11 +22,11 @@ const { detectAppType, getDeployJsonPath } = require('../utils/paths');
23
22
  * @throws {Error} If file cannot be loaded
24
23
  */
25
24
  async function loadVariablesYaml(appName) {
26
- // Detect app type and get correct path (integration or builder)
27
25
  const { appPath } = await detectAppType(appName);
28
- const variablesPath = path.join(appPath, 'variables.yaml');
29
- const content = await fs.readFile(variablesPath, 'utf8');
30
- return yaml.load(content);
26
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
27
+ const { loadConfigFile } = require('../utils/config-format');
28
+ const configPath = resolveApplicationConfigPath(appPath);
29
+ return loadConfigFile(configPath);
31
30
  }
32
31
 
33
32
  /**
@@ -13,6 +13,8 @@ const chalk = require('chalk');
13
13
  const { getDeploymentAuth } = require('../utils/token-manager');
14
14
  const logger = require('../utils/logger');
15
15
  const { resolveControllerUrl } = require('../utils/controller-url');
16
+ const { detectAppType } = require('../utils/paths');
17
+ const { logOfflinePathWhenType } = require('../utils/cli-utils');
16
18
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
17
19
  const { getExternalSystem } = require('../api/external-systems.api');
18
20
  const { generateControllerManifest } = require('../generator/external-controller-manifest');
@@ -132,11 +134,14 @@ async function prepareDeploymentConfig(appName, _options) {
132
134
  */
133
135
  async function deployExternalSystem(appName, options = {}) {
134
136
  try {
137
+ const { appPath } = await detectAppType(appName);
138
+ logOfflinePathWhenType(appPath);
139
+
135
140
  logger.log(chalk.blue(`\n🚀 Deploying external system: ${appName}`));
136
141
 
137
142
  // Step 0: Validate before deployment (same as validate command)
138
143
  logger.log(chalk.blue('🔍 Validating external system before deployment...'));
139
- const validationResult = await validateExternalSystemComplete(appName);
144
+ const validationResult = await validateExternalSystemComplete(appName, options);
140
145
 
141
146
  if (!validationResult.valid) {
142
147
  displayValidationResults(validationResult);
@@ -146,7 +151,7 @@ async function deployExternalSystem(appName, options = {}) {
146
151
  logger.log(chalk.green('✓ Validation passed, proceeding with deployment...'));
147
152
 
148
153
  // Step 1: Generate controller manifest (validated, ready for deployment)
149
- const manifest = await generateControllerManifest(appName);
154
+ const manifest = await generateControllerManifest(appName, options);
150
155
 
151
156
  // Step 2: Get deployment configuration (auth, controller URL, etc.)
152
157
  const { environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
@@ -11,14 +11,14 @@
11
11
  const { generateExternalReadmeContent } = require('../utils/external-readme');
12
12
 
13
13
  /**
14
- * Generates variables.yaml content for downloaded system
14
+ * Generates application.yaml content for downloaded system
15
15
  * @param {string} systemKey - System key
16
16
  * @param {Object} application - External system configuration
17
17
  * @param {Array} dataSources - Array of datasource configurations
18
18
  * @returns {Object} Variables YAML object
19
19
  */
20
20
  function generateVariablesYaml(systemKey, application, dataSources) {
21
- const systemFileName = `${systemKey}-system.json`;
21
+ const systemFileName = `${systemKey}-system.yaml`;
22
22
  const datasourceFiles = dataSources.map(ds => {
23
23
  // Extract datasource key (remove system key prefix if present)
24
24
  const datasourceKey = ds.key || '';
@@ -29,7 +29,7 @@ function generateVariablesYaml(systemKey, application, dataSources) {
29
29
  const entityType = ds.entityType || ds.entityKey || datasourceKey.split('-').pop();
30
30
  datasourceKeyOnly = entityType;
31
31
  }
32
- return `${systemKey}-datasource-${datasourceKeyOnly}.json`;
32
+ return `${systemKey}-datasource-${datasourceKeyOnly}.yaml`;
33
33
  });
34
34
 
35
35
  return {
@@ -73,7 +73,7 @@ function generateReadme(systemKey, application, dataSources) {
73
73
  return {
74
74
  entityType: datasourceKeyOnly,
75
75
  displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
76
- fileName: `${systemKey}-datasource-${datasourceKeyOnly}.json`
76
+ fileName: `${systemKey}-datasource-${datasourceKeyOnly}.yaml`
77
77
  };
78
78
  });
79
79
 
@@ -19,6 +19,7 @@ const { getDeploymentAuth } = require('../utils/token-manager');
19
19
  const { getConfig } = require('../core/config');
20
20
  const { detectAppType } = require('../utils/paths');
21
21
  const logger = require('../utils/logger');
22
+ const { writeConfigFile } = require('../utils/config-format');
22
23
  const { generateEnvTemplate } = require('../utils/external-system-env-helpers');
23
24
  const { generateVariablesYaml, generateReadme } = require('./download-helpers');
24
25
  const { resolveControllerUrl } = require('../utils/controller-url');
@@ -201,9 +202,9 @@ async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig)
201
202
  * @returns {Promise<string>} System file path
202
203
  */
203
204
  async function generateSystemFile(tempDir, systemKey, application) {
204
- const systemFileName = `${systemKey}-system.json`;
205
+ const systemFileName = `${systemKey}-system.yaml`;
205
206
  const systemFilePath = path.join(tempDir, systemFileName);
206
- await fs.writeFile(systemFilePath, JSON.stringify(application, null, 2), 'utf8');
207
+ writeConfigFile(systemFilePath, application);
207
208
  return systemFilePath;
208
209
  }
209
210
 
@@ -230,9 +231,9 @@ async function generateDatasourceFiles(tempDir, systemKey, dataSources) {
230
231
  const entityType = datasource.entityType || datasource.entityKey || datasourceKey.split('-').pop();
231
232
  datasourceKeyOnly = entityType;
232
233
  }
233
- const datasourceFileName = `${systemKey}-datasource-${datasourceKeyOnly}.json`;
234
+ const datasourceFileName = `${systemKey}-datasource-${datasourceKeyOnly}.yaml`;
234
235
  const datasourceFilePath = path.join(tempDir, datasourceFileName);
235
- await fs.writeFile(datasourceFilePath, JSON.stringify(datasource, null, 2), 'utf8');
236
+ writeConfigFile(datasourceFilePath, datasource);
236
237
  datasourceFiles.push(datasourceFilePath);
237
238
  } catch (error) {
238
239
  datasourceErrors.push(new Error(`Failed to write datasource ${datasource.key}: ${error.message}`));
@@ -242,7 +243,7 @@ async function generateDatasourceFiles(tempDir, systemKey, dataSources) {
242
243
  }
243
244
 
244
245
  /**
245
- * Generates configuration files (variables.yaml, env.template, README.md)
246
+ * Generates configuration files (application.yaml, env.template, README.md)
246
247
  * @async
247
248
  * @function generateConfigFiles
248
249
  * @param {string} tempDir - Temporary directory
@@ -252,9 +253,9 @@ async function generateDatasourceFiles(tempDir, systemKey, dataSources) {
252
253
  * @returns {Promise<Object>} Object with file paths
253
254
  */
254
255
  async function generateConfigFiles(tempDir, systemKey, application, dataSources) {
255
- // Generate variables.yaml
256
+ // Generate application.yaml
256
257
  const variables = generateVariablesYaml(systemKey, application, dataSources);
257
- const variablesPath = path.join(tempDir, 'variables.yaml');
258
+ const variablesPath = path.join(tempDir, 'application.yaml');
258
259
  await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }), 'utf8');
259
260
 
260
261
  // Generate env.template
@@ -307,7 +308,7 @@ async function moveFilesToFinalLocation(tempDir, finalPath, systemKey, filePaths
307
308
  const systemFileName = `${systemKey}-system.json`;
308
309
  const filesToMove = [
309
310
  { from: filePaths.systemFilePath, to: path.join(finalPath, systemFileName) },
310
- { from: filePaths.variablesPath, to: path.join(finalPath, 'variables.yaml') },
311
+ { from: filePaths.variablesPath, to: path.join(finalPath, 'application.yaml') },
311
312
  { from: filePaths.envTemplatePath, to: path.join(finalPath, 'env.template') },
312
313
  { from: filePaths.readmePath, to: path.join(finalPath, 'README.md') }
313
314
  ];
@@ -347,8 +348,8 @@ function handleDryRun(systemKey, dataplaneUrl) {
347
348
  logger.log(chalk.gray(` ${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`));
348
349
  logger.log(chalk.yellow('\nWould create:'));
349
350
  logger.log(chalk.gray(` integration/${systemKey}/`));
350
- logger.log(chalk.gray(` integration/${systemKey}/variables.yaml`));
351
- logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-system.json`));
351
+ logger.log(chalk.gray(` integration/${systemKey}/application.yaml`));
352
+ logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-system.yaml`));
352
353
  logger.log(chalk.gray(` integration/${systemKey}/env.template`));
353
354
  logger.log(chalk.gray(` integration/${systemKey}/README.md`));
354
355
  }
@@ -12,9 +12,10 @@
12
12
  const fs = require('fs').promises;
13
13
  const path = require('path');
14
14
  const handlebars = require('handlebars');
15
- const yaml = require('js-yaml');
16
15
  const chalk = require('chalk');
17
16
  const logger = require('../utils/logger');
17
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
18
+ const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
18
19
 
19
20
  // Register Handlebars helper for equality check
20
21
  handlebars.registerHelper('eq', (a, b) => a === b);
@@ -52,11 +53,12 @@ async function generateExternalSystemTemplate(appPath, systemKey, config) {
52
53
  };
53
54
 
54
55
  const rendered = template(context);
56
+ const parsed = JSON.parse(rendered);
55
57
 
56
- // Generate in same folder as variables.yaml (new structure)
57
- // Use naming: <app-name>-system.json
58
- const outputPath = path.join(appPath, `${systemKey}-system.json`);
59
- await fs.writeFile(outputPath, rendered, 'utf8');
58
+ // Generate in same folder as application.yaml (new structure)
59
+ // Use naming: <app-name>-system.yaml
60
+ const outputPath = path.join(appPath, `${systemKey}-system.yaml`);
61
+ writeConfigFile(outputPath, parsed);
60
62
 
61
63
  return outputPath;
62
64
  } catch (error) {
@@ -98,15 +100,16 @@ async function generateExternalDataSourceTemplate(appPath, datasourceKey, config
98
100
  };
99
101
 
100
102
  const rendered = template(context);
103
+ const datasourceConfig = JSON.parse(rendered);
101
104
 
102
- // Generate in same folder as variables.yaml (new structure)
103
- // Use naming: <app-name>-datasource-<datasource-key>.json
105
+ // Generate in same folder as application.yaml (new structure)
106
+ // Use naming: <app-name>-datasource-<datasource-key>.yaml
104
107
  // Extract datasource key (remove system key prefix if present)
105
108
  const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${config.systemKey}-`)
106
109
  ? datasourceKey.substring(config.systemKey.length + 1)
107
110
  : datasourceKey;
108
- const outputPath = path.join(appPath, `${config.systemKey}-datasource-${datasourceKeyOnly}.json`);
109
- await fs.writeFile(outputPath, rendered, 'utf8');
111
+ const outputPath = path.join(appPath, `${config.systemKey}-datasource-${datasourceKeyOnly}.yaml`);
112
+ writeConfigFile(outputPath, datasourceConfig);
110
113
 
111
114
  return outputPath;
112
115
  } catch (error) {
@@ -160,7 +163,7 @@ async function generateExternalSystemFiles(appPath, appName, config) {
160
163
  logger.log(chalk.green(`✓ Generated datasource: ${path.basename(datasourcePath)}`));
161
164
  }
162
165
 
163
- // Update variables.yaml with externalIntegration block
166
+ // Update application.yaml with externalIntegration block
164
167
  await updateVariablesYamlWithExternalIntegration(appPath, systemKey, datasourcePaths);
165
168
 
166
169
  return {
@@ -173,7 +176,7 @@ async function generateExternalSystemFiles(appPath, appName, config) {
173
176
  }
174
177
 
175
178
  /**
176
- * Updates variables.yaml with externalIntegration block
179
+ * Updates application.yaml with externalIntegration block
177
180
  * @async
178
181
  * @function updateVariablesYamlWithExternalIntegration
179
182
  * @param {string} appPath - Application directory path
@@ -183,23 +186,22 @@ async function generateExternalSystemFiles(appPath, appName, config) {
183
186
  */
184
187
  async function updateVariablesYamlWithExternalIntegration(appPath, systemKey, datasourcePaths) {
185
188
  try {
186
- const variablesPath = path.join(appPath, 'variables.yaml');
187
- const variablesContent = await fs.readFile(variablesPath, 'utf8');
188
- const variables = yaml.load(variablesContent);
189
+ const configPath = resolveApplicationConfigPath(appPath);
190
+ const variables = loadConfigFile(configPath);
189
191
 
190
192
  // Add externalIntegration block
191
193
  // Files are in same folder, so schemaBasePath is './'
192
194
  variables.externalIntegration = {
193
195
  schemaBasePath: './',
194
- systems: [`${systemKey}-system.json`],
196
+ systems: [`${systemKey}-system.yaml`],
195
197
  dataSources: datasourcePaths.map(p => path.basename(p)),
196
198
  autopublish: true,
197
199
  version: '1.0.0'
198
200
  };
199
201
 
200
- await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }), 'utf8');
202
+ writeConfigFile(configPath, variables);
201
203
  } catch (error) {
202
- throw new Error(`Failed to update variables.yaml: ${error.message}`);
204
+ throw new Error(`Failed to update application config: ${error.message}`);
203
205
  }
204
206
  }
205
207
 
@@ -12,7 +12,6 @@
12
12
  const fs = require('fs').promises;
13
13
  const fsSync = require('fs');
14
14
  const path = require('path');
15
- const yaml = require('js-yaml');
16
15
  const chalk = require('chalk');
17
16
  const testHelpers = require('../utils/external-system-test-helpers');
18
17
  const { retryApiCall } = require('../utils/external-system-test-helpers');
@@ -39,30 +38,26 @@ const {
39
38
  } = require('./test-execution');
40
39
 
41
40
  /**
42
- * Loads and parses variables.yaml file
41
+ * Loads and parses application config file
43
42
  * @async
44
43
  * @function loadVariablesYamlFile
45
- * @param {string} variablesPath - Path to variables.yaml
44
+ * @param {string} variablesPath - Path to application config
46
45
  * @returns {Promise<Object>} Parsed variables
47
- * @throws {Error} If file not found or invalid YAML
46
+ * @throws {Error} If file not found or invalid
48
47
  */
49
48
  async function loadVariablesYamlFile(variablesPath) {
50
- if (!fsSync.existsSync(variablesPath)) {
51
- throw new Error(`variables.yaml not found: ${variablesPath}`);
52
- }
53
-
54
- const variablesContent = await fs.readFile(variablesPath, 'utf8');
49
+ const { loadConfigFile } = require('../utils/config-format');
55
50
  try {
56
- const variables = yaml.load(variablesContent);
51
+ const variables = loadConfigFile(variablesPath);
57
52
  if (!variables.externalIntegration) {
58
- throw new Error('externalIntegration block not found in variables.yaml');
53
+ throw new Error('externalIntegration block not found in application config');
59
54
  }
60
55
  return variables;
61
56
  } catch (error) {
62
57
  if (error.message.includes('externalIntegration')) {
63
58
  throw error;
64
59
  }
65
- throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
60
+ throw new Error(`Application config: ${error.message}`);
66
61
  }
67
62
  }
68
63
 
@@ -152,9 +147,9 @@ async function loadDatasourceFiles(datasourceFiles, schemaBasePath, appPath) {
152
147
  */
153
148
  async function loadExternalSystemFiles(appName) {
154
149
  const { appPath } = await detectAppType(appName);
155
- const variablesPath = path.join(appPath, 'variables.yaml');
156
-
157
- const variables = await loadVariablesYamlFile(variablesPath);
150
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
151
+ const configPath = resolveApplicationConfigPath(appPath);
152
+ const variables = await loadVariablesYamlFile(configPath);
158
153
 
159
154
  const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
160
155
  const systemFiles = variables.externalIntegration.systems || [];