@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
@@ -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 || [];
@@ -290,7 +290,7 @@ function addHealthCheckToDeployment(deployment, variables) {
290
290
  * @param {Object|null} rbac - RBAC configuration
291
291
  */
292
292
  function addRolesAndPermissions(deployment, variables, rbac) {
293
- // Priority: variables.yaml > rbac.yaml
293
+ // Priority: application.yaml > rbac.yaml
294
294
  if (variables.roles) {
295
295
  deployment.roles = variables.roles;
296
296
  } else if (rbac && rbac.roles) {
@@ -11,6 +11,7 @@
11
11
 
12
12
  const path = require('path');
13
13
  const { detectAppType } = require('../utils/paths');
14
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
14
15
  const { loadSystemFile, loadDatasourceFiles } = require('./external');
15
16
  const { loadVariables, loadRbac } = require('./helpers');
16
17
 
@@ -42,18 +43,18 @@ function mergeRbacIntoSystemJson(systemJson, rbac) {
42
43
  * @param {Object} options - Options with optional appPath
43
44
  * @returns {Promise<string>} Application path
44
45
  */
45
- async function resolveAppPath(appName, options) {
46
- if (options.appPath) {
46
+ async function resolveAppPath(appName, options = {}) {
47
+ if (options && options.appPath) {
47
48
  return options.appPath;
48
49
  }
49
- const detected = await detectAppType(appName, { type: 'external' });
50
+ const detected = await detectAppType(appName);
50
51
  return detected.appPath;
51
52
  }
52
53
 
53
54
  /**
54
55
  * Extracts app metadata from variables
55
56
  * @function extractAppMetadata
56
- * @param {Object} variables - Parsed variables.yaml
57
+ * @param {Object} variables - Parsed application config
57
58
  * @param {string} appName - Application name
58
59
  * @returns {Object} App metadata { appKey, displayName, description }
59
60
  */
@@ -98,59 +99,55 @@ async function loadSystemWithRbac(appPath, schemaBasePath, systemFile) {
98
99
  * const manifest = await generateControllerManifest('my-hubspot');
99
100
  * // Returns: { key, displayName, description, type: "external", system: {...}, dataSources: [...] }
100
101
  */
102
+ function normalizeSchemaBasePath(schemaBasePath, appPath, appName) {
103
+ const base = path.normalize(schemaBasePath || './').replace(/[/\\]+$/, '');
104
+ return base === path.join('integration', appName) ? './' : (schemaBasePath || './');
105
+ }
106
+
101
107
  async function generateControllerManifest(appName, options = {}) {
102
108
  if (!appName || typeof appName !== 'string') {
103
109
  throw new Error('App name is required and must be a string');
104
110
  }
105
-
106
111
  const appPath = await resolveAppPath(appName, options);
107
- const variablesPath = path.join(appPath, 'variables.yaml');
108
- const { parsed: variables } = loadVariables(variablesPath);
109
-
112
+ const { parsed: variables } = loadVariables(resolveApplicationConfigPath(appPath));
110
113
  if (!variables.externalIntegration) {
111
- throw new Error('externalIntegration block not found in variables.yaml');
114
+ throw new Error('externalIntegration block not found in application.yaml');
112
115
  }
113
-
114
116
  const metadata = extractAppMetadata(variables, appName);
115
- const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
117
+ const schemaBasePath = normalizeSchemaBasePath(
118
+ variables.externalIntegration.schemaBasePath,
119
+ appPath,
120
+ appName
121
+ );
116
122
  const systemFiles = variables.externalIntegration.systems || [];
117
-
118
123
  if (systemFiles.length === 0) {
119
124
  throw new Error('No system files specified in externalIntegration.systems');
120
125
  }
121
-
122
- const systemJson = await loadSystemWithRbac(appPath, schemaBasePath, systemFiles[0]);
123
- const datasourceFiles = variables.externalIntegration.dataSources || [];
124
- const datasourceJsons = await loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles);
125
-
126
+ const [systemJson, datasourceJsons] = await Promise.all([
127
+ loadSystemWithRbac(appPath, schemaBasePath, systemFiles[0]),
128
+ loadDatasourceFiles(appPath, schemaBasePath, variables.externalIntegration.dataSources || [])
129
+ ]);
126
130
  const appVersion = variables.app?.version || variables.externalIntegration?.version || '1.0.0';
127
-
128
- // Build externalIntegration block (required by application schema for type: "external")
129
131
  const externalIntegration = {
130
- schemaBasePath: schemaBasePath,
132
+ schemaBasePath,
131
133
  systems: systemFiles,
132
- dataSources: datasourceFiles,
133
- autopublish: variables.externalIntegration.autopublish !== false, // default true
134
+ dataSources: variables.externalIntegration.dataSources || [],
135
+ autopublish: variables.externalIntegration.autopublish !== false,
134
136
  version: appVersion
135
137
  };
136
-
137
- const manifest = {
138
+ return {
138
139
  key: metadata.appKey,
139
140
  displayName: metadata.displayName,
140
141
  description: metadata.description,
141
142
  type: 'external',
142
143
  version: appVersion,
143
- externalIntegration: externalIntegration,
144
- // Inline system and dataSources for atomic deployment (optional but recommended)
144
+ externalIntegration,
145
145
  system: systemJson,
146
146
  dataSources: datasourceJsons,
147
- // Explicitly set to false to satisfy conditional schema requirements
148
147
  requiresDatabase: false,
149
148
  requiresRedis: false,
150
149
  requiresStorage: false
151
150
  };
152
-
153
- return manifest;
154
151
  }
155
152
 
156
153
  module.exports = {
@@ -56,18 +56,6 @@ function getSystemKey(application) {
56
56
  return systemKey;
57
57
  }
58
58
 
59
- /**
60
- * Writes JSON file with formatting
61
- * @async
62
- * @function writeJsonFile
63
- * @param {string} filePath - File path
64
- * @param {Object} data - JSON data
65
- * @returns {Promise<void>} Resolves when file is written
66
- */
67
- async function writeJsonFile(filePath, data) {
68
- await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
69
- }
70
-
71
59
  /**
72
60
  * Resolves datasource entity type
73
61
  * @function getDatasourceEntityType
@@ -104,7 +92,7 @@ function getDatasourceFileName(systemKey, datasource, index) {
104
92
  } else {
105
93
  datasourceKeyOnly = getDatasourceEntityType(datasource, index);
106
94
  }
107
- return `${systemKey}-datasource-${datasourceKeyOnly}.json`;
95
+ return `${systemKey}-datasource-${datasourceKeyOnly}.yaml`;
108
96
  }
109
97
 
110
98
  /**
@@ -122,14 +110,14 @@ async function writeDatasourceFiles(outputDir, systemKey, dataSources) {
122
110
  const datasource = dataSources[i];
123
111
  const datasourceFileName = getDatasourceFileName(systemKey, datasource, i);
124
112
  const datasourceFilePath = path.join(outputDir, datasourceFileName);
125
- await writeJsonFile(datasourceFilePath, datasource);
113
+ await writeYamlFile(datasourceFilePath, datasource, { indent: 2, lineWidth: 120, noRefs: true });
126
114
  datasourceFileNames.push(datasourceFileName);
127
115
  }
128
116
  return datasourceFileNames;
129
117
  }
130
118
 
131
119
  /**
132
- * Builds variables.yaml content for external integrations
120
+ * Builds application config content for external integrations
133
121
  * @function buildExternalVariables
134
122
  * @param {string} systemKey - System key
135
123
  * @param {Object} application - Application schema
@@ -187,14 +175,14 @@ async function writeYamlFile(filePath, data, options) {
187
175
  * @returns {Promise<Object>} Paths to generated files
188
176
  */
189
177
  async function writeSplitExternalSchemaFiles({ outputDir, systemKey, application, dataSources, version }) {
190
- const systemFileName = `${systemKey}-system.json`;
178
+ const systemFileName = `${systemKey}-system.yaml`;
191
179
  const systemFilePath = path.join(outputDir, systemFileName);
192
- await writeJsonFile(systemFilePath, application);
180
+ await writeYamlFile(systemFilePath, application, { indent: 2, lineWidth: 120, noRefs: true });
193
181
 
194
182
  const datasourceFileNames = await writeDatasourceFiles(outputDir, systemKey, dataSources);
195
183
  const variables = buildExternalVariables(systemKey, application, systemFileName, datasourceFileNames, version);
196
184
 
197
- const variablesPath = path.join(outputDir, 'variables.yaml');
185
+ const variablesPath = path.join(outputDir, 'application.yaml');
198
186
  await writeYamlFile(variablesPath, variables, { indent: 2, lineWidth: 120, noRefs: true });
199
187
 
200
188
  const envTemplatePath = path.join(outputDir, 'env.template');
@@ -12,6 +12,8 @@ const fs = require('fs');
12
12
  const path = require('path');
13
13
  const Ajv = require('ajv');
14
14
  const { detectAppType, getDeployJsonPath } = require('../utils/paths');
15
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
16
+ const { loadConfigFile } = require('../utils/config-format');
15
17
  const { loadVariables, loadRbac } = require('./helpers');
16
18
  const {
17
19
  parseApplicationSchema,
@@ -41,26 +43,29 @@ const {
41
43
  function resolveSystemFilePath(variables, appPath, appName) {
42
44
  const systemFileName = variables.externalIntegration.systems && variables.externalIntegration.systems.length > 0
43
45
  ? variables.externalIntegration.systems[0]
44
- : `${appName}-system.json`;
46
+ : `${appName}-system.yaml`;
45
47
 
46
- const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
48
+ let schemaBasePath = variables.externalIntegration.schemaBasePath || './';
49
+ const normalizedBase = path.normalize(schemaBasePath).replace(/[/\\]+$/, '');
50
+ if (normalizedBase === path.join('integration', appName)) {
51
+ schemaBasePath = './';
52
+ }
47
53
  const systemFilePath = path.isAbsolute(schemaBasePath)
48
54
  ? path.join(schemaBasePath, systemFileName)
49
55
  : path.join(appPath, schemaBasePath, systemFileName);
50
56
 
51
- // Support both old and new naming for backward compatibility
57
+ // Support both .yaml and legacy .json for backward compatibility
52
58
  if (!fs.existsSync(systemFilePath)) {
53
- // Try old naming format
54
- const oldSystemFileName = systemFileName.replace(/-system\.json$/, '-deploy.json');
55
- const oldSystemFilePath = path.isAbsolute(schemaBasePath)
56
- ? path.join(schemaBasePath, oldSystemFileName)
57
- : path.join(appPath, schemaBasePath, oldSystemFileName);
58
-
59
- if (fs.existsSync(oldSystemFilePath)) {
60
- return oldSystemFilePath;
59
+ const altFileName = systemFileName.replace(/-system\.yaml$/, '-system.json').replace(/-system\.yml$/, '-system.json');
60
+ const altSystemFilePath = path.isAbsolute(schemaBasePath)
61
+ ? path.join(schemaBasePath, altFileName)
62
+ : path.join(appPath, schemaBasePath, altFileName);
63
+
64
+ if (fs.existsSync(altSystemFilePath)) {
65
+ return altSystemFilePath;
61
66
  }
62
67
 
63
- throw new Error(`External system file not found: ${systemFilePath} (also checked: ${oldSystemFilePath}). Please create it first.`);
68
+ throw new Error(`External system file not found: ${systemFilePath} (also checked: ${altSystemFilePath}). Please create it first.`);
64
69
  }
65
70
 
66
71
  return systemFilePath;
@@ -87,15 +92,14 @@ function mergeRbacIntoSystemJson(systemJson, rbac) {
87
92
  }
88
93
 
89
94
  /**
90
- * Loads and parses system file
95
+ * Loads and parses system file (YAML or JSON)
91
96
  * @async
92
97
  * @function loadSystemFileContent
93
98
  * @param {string} systemFilePath - System file path
94
- * @returns {Promise<Object>} Parsed system JSON
99
+ * @returns {Promise<Object>} Parsed system config object
95
100
  */
96
101
  async function loadSystemFileContent(systemFilePath) {
97
- const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
98
- return JSON.parse(systemContent);
102
+ return loadConfigFile(systemFilePath);
99
103
  }
100
104
 
101
105
  async function generateExternalSystemDeployJson(appName, appPath) {
@@ -103,11 +107,11 @@ async function generateExternalSystemDeployJson(appName, appPath) {
103
107
  throw new Error('App name is required and must be a string');
104
108
  }
105
109
 
106
- const variablesPath = path.join(appPath, 'variables.yaml');
107
- const { parsed: variables } = loadVariables(variablesPath);
110
+ const configPath = resolveApplicationConfigPath(appPath);
111
+ const { parsed: variables } = loadVariables(configPath);
108
112
 
109
113
  if (!variables.externalIntegration) {
110
- throw new Error('externalIntegration block not found in variables.yaml');
114
+ throw new Error('externalIntegration block not found in application.yaml');
111
115
  }
112
116
 
113
117
  const systemFilePath = resolveSystemFilePath(variables, appPath, appName);
@@ -141,11 +145,13 @@ async function loadSystemFile(appPath, schemaBasePath, systemFileName) {
141
145
  : path.join(appPath, schemaBasePath, systemFileName);
142
146
 
143
147
  if (!fs.existsSync(systemFilePath)) {
144
- throw new Error(`System file not found: ${systemFilePath}`);
148
+ const hint = systemFileName.endsWith('-deploy.json')
149
+ ? ' Use the system definition file (e.g. <app>-system.yaml) in externalIntegration.systems, not the deploy manifest (-deploy.json).'
150
+ : '';
151
+ throw new Error(`System file not found: ${systemFilePath}.${hint}`);
145
152
  }
146
153
 
147
- const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
148
- const systemJson = JSON.parse(systemContent);
154
+ const systemJson = loadConfigFile(systemFilePath);
149
155
 
150
156
  // Load rbac.yaml from app directory and merge if present
151
157
  const rbacPath = path.join(appPath, 'rbac.yaml');
@@ -183,8 +189,7 @@ async function loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles) {
183
189
  throw new Error(`Datasource file not found: ${datasourcePath}`);
184
190
  }
185
191
 
186
- const datasourceContent = await fs.promises.readFile(datasourcePath, 'utf8');
187
- const datasourceJson = JSON.parse(datasourceContent);
192
+ const datasourceJson = loadConfigFile(datasourcePath);
188
193
  datasourceJsons.push(datasourceJson);
189
194
  }
190
195
 
@@ -297,11 +302,11 @@ function validateDatasourceSchemas(datasourceJsons, externalDatasourceSchema, aj
297
302
  * @throws {Error} If configuration is invalid
298
303
  */
299
304
  async function loadExternalIntegrationConfig(appPath) {
300
- const variablesPath = path.join(appPath, 'variables.yaml');
301
- const { parsed: variables } = loadVariables(variablesPath);
305
+ const configPath = resolveApplicationConfigPath(appPath);
306
+ const { parsed: variables } = loadVariables(configPath);
302
307
 
303
308
  if (!variables.externalIntegration) {
304
- throw new Error('externalIntegration block not found in variables.yaml');
309
+ throw new Error('externalIntegration block not found in application.yaml');
305
310
  }
306
311
 
307
312
  const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
@@ -45,7 +45,7 @@ async function loadStepTemplates(stepNames = []) {
45
45
  /**
46
46
  * Generate GitHub Actions workflow files from templates
47
47
  * @param {string} appPath - Path to application directory
48
- * @param {Object} config - Configuration from variables.yaml
48
+ * @param {Object} config - Configuration from application.yaml
49
49
  * @param {Object} options - Generation options
50
50
  * @returns {Promise<string[]>} Array of generated file paths
51
51
  */