@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
@@ -8,11 +8,10 @@
8
8
  */
9
9
 
10
10
  const fs = require('fs').promises;
11
- const path = require('path');
12
- const yaml = require('js-yaml');
13
11
  const config = require('../core/config');
14
12
  const { getDeploymentAuth } = require('../utils/token-manager');
15
- const { detectAppType } = require('../utils/paths');
13
+ const { detectAppType, resolveApplicationConfigPath } = require('../utils/paths');
14
+ const { loadConfigFile } = require('../utils/config-format');
16
15
  const { resolveControllerUrl } = require('../utils/controller-url');
17
16
 
18
17
  /**
@@ -34,18 +33,17 @@ async function validateAppDirectory(builderPath, appName) {
34
33
  }
35
34
 
36
35
  /**
37
- * Loads variables.yaml file
38
- * @async
39
- * @param {string} variablesPath - Path to variables.yaml
40
- * @returns {Promise<Object>} Variables object
36
+ * Loads application config (application.yaml or application.json) via resolver + converter.
37
+ * @param {string} appPath - Path to application directory
38
+ * @returns {Object} Variables object
41
39
  * @throws {Error} If file cannot be loaded
42
40
  */
43
- async function loadVariablesFile(variablesPath) {
41
+ function loadVariablesFile(appPath) {
44
42
  try {
45
- const variablesContent = await fs.readFile(variablesPath, 'utf8');
46
- return yaml.load(variablesContent);
43
+ const configPath = resolveApplicationConfigPath(appPath);
44
+ return loadConfigFile(configPath);
47
45
  } catch (error) {
48
- throw new Error(`Failed to load configuration from variables.yaml: ${error.message}`);
46
+ throw new Error('Failed to load configuration: ' + error.message);
49
47
  }
50
48
  }
51
49
 
@@ -55,7 +53,7 @@ async function loadVariablesFile(variablesPath) {
55
53
  * Resolves environment using fallback chain: config.environment → default 'dev'
56
54
  * @async
57
55
  * @param {Object} options - CLI options (for poll settings only)
58
- * @param {Object} _variables - Variables from variables.yaml (unused, kept for compatibility)
56
+ * @param {Object} _variables - Variables from application config (unused, kept for compatibility)
59
57
  * @returns {Promise<Object>} Extracted configuration with resolved controller URL
60
58
  */
61
59
  async function extractDeploymentConfig(options, _variables) {
@@ -132,7 +130,7 @@ async function refreshDeploymentToken(appName, deploymentConfig) {
132
130
  }
133
131
 
134
132
  /**
135
- * Loads deployment configuration from variables.yaml and gets/refreshes token
133
+ * Loads deployment configuration from application.yaml and gets/refreshes token
136
134
  * @async
137
135
  * @param {string} appName - Application name
138
136
  * @param {Object} options - CLI options
@@ -143,8 +141,7 @@ async function loadDeploymentConfig(appName, options) {
143
141
  const { appPath } = await detectAppType(appName);
144
142
  await validateAppDirectory(appPath, appName);
145
143
 
146
- const variablesPath = path.join(appPath, 'variables.yaml');
147
- const variables = await loadVariablesFile(variablesPath);
144
+ const variables = loadVariablesFile(appPath);
148
145
 
149
146
  const deploymentConfig = await extractDeploymentConfig(options, variables);
150
147
 
@@ -153,7 +150,7 @@ async function loadDeploymentConfig(appName, options) {
153
150
 
154
151
  validateDeploymentConfig(deploymentConfig);
155
152
 
156
- return deploymentConfig;
153
+ return { ...deploymentConfig, appPath };
157
154
  }
158
155
 
159
156
  module.exports = {
package/lib/app/deploy.js CHANGED
@@ -10,14 +10,13 @@
10
10
  */
11
11
 
12
12
  const fs = require('fs').promises;
13
- const path = require('path');
14
- const yaml = require('js-yaml');
15
13
  const chalk = require('chalk');
16
14
  const pushUtils = require('../deployment/push');
17
15
  const logger = require('../utils/logger');
18
16
  const { detectAppType, getBuilderPath, getIntegrationPath } = require('../utils/paths');
19
17
  const { checkApplicationExists } = require('../utils/app-existence');
20
18
  const { loadDeploymentConfig } = require('./deploy-config');
19
+ const { logOfflinePathWhenType } = require('../utils/cli-utils');
21
20
  const { displayAppUrlFromController } = require('./deploy-status-display');
22
21
 
23
22
  /**
@@ -116,17 +115,20 @@ async function pushApp(appName, options = {}) {
116
115
  try {
117
116
  validateAppName(appName);
118
117
 
119
- const configPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
118
+ const { getBuilderPath, resolveApplicationConfigPath } = require('../utils/paths');
119
+ const { loadConfigFile } = require('../utils/config-format');
120
+ const builderPath = getBuilderPath(appName);
120
121
  let config;
121
122
  try {
122
- config = yaml.load(await fs.readFile(configPath, 'utf8'));
123
+ const configPath = resolveApplicationConfigPath(builderPath);
124
+ config = loadConfigFile(configPath);
123
125
  } catch (error) {
124
- throw new Error(`Failed to load configuration: ${configPath}\nRun 'aifabrix create ${appName}' first`);
126
+ throw new Error(`Failed to load configuration: ${error.message}\nRun 'aifabrix create ${appName}' first`);
125
127
  }
126
128
 
127
129
  const registry = options.registry || config.image?.registry;
128
130
  if (!registry) {
129
- throw new Error('Registry URL is required. Provide via --registry flag or configure in variables.yaml under image.registry');
131
+ throw new Error('Registry URL is required. Provide via --registry flag or configure in application.yaml under image.registry');
130
132
  }
131
133
 
132
134
  if (!/^[^.]+\.azurecr\.io$/.test(registry)) {
@@ -148,15 +150,16 @@ async function pushApp(appName, options = {}) {
148
150
  * Generates and validates deployment manifest
149
151
  * @async
150
152
  * @param {string} appName - Application name
153
+ * @param {Object} [options] - Deployment options (type: 'app' | 'external' for path resolution)
151
154
  * @returns {Promise<Object>} Deployment manifest
152
155
  * @throws {Error} If generation or validation fails
153
156
  */
154
- async function generateAndValidateManifest(appName) {
157
+ async function generateAndValidateManifest(appName, options = {}) {
155
158
  logger.log(chalk.blue(`\n📋 Generating deployment manifest for ${appName}...`));
156
159
  const generator = require('../generator');
157
160
 
158
161
  // generateDeployJson already validates against schema and throws on error
159
- const manifestPath = await generator.generateDeployJson(appName);
162
+ const manifestPath = await generator.generateDeployJson(appName, options);
160
163
  const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
161
164
 
162
165
  // Additional validation for warnings (schema validation already passed)
@@ -220,16 +223,16 @@ function displayDeploymentResults(result) {
220
223
  }
221
224
 
222
225
  /**
223
- * Check if app is external and handle external deployment.
224
- * When options.type === 'external', forces deployment from integration/<app> (no app register needed).
226
+ * Check if app is external (resolved from integration/) and handle external deployment.
227
+ * Path resolution order: integration first, then builder; no flag overrides.
225
228
  * @async
226
229
  * @function handleExternalDeployment
227
230
  * @param {string} appName - Application name
228
- * @param {Object} options - Deployment options (type: 'external' to force integration/<app>)
231
+ * @param {Object} options - Deployment options (poll, etc.)
229
232
  * @returns {Promise<Object|null>} Deployment result if external, null otherwise
230
233
  */
231
234
  async function handleExternalDeployment(appName, options) {
232
- const { isExternal } = await detectAppType(appName, options);
235
+ const { isExternal } = await detectAppType(appName);
233
236
  if (isExternal) {
234
237
  const externalDeploy = require('../external-system/deploy');
235
238
  await externalDeploy.deployExternalSystem(appName, options);
@@ -266,7 +269,7 @@ async function handleDeploymentError(error, appName, controllerUrl, usedExternal
266
269
  */
267
270
  function validateImageIsPullable(imageRef, appName) {
268
271
  if (!imageRef || !imageRef.includes('/')) {
269
- const hint = `Set image.registry and image.tag in builder/${appName}/variables.yaml, or pass a full image ref (e.g. --image <registry>/${appName}:<tag>) when deploying`;
272
+ const hint = `Set image.registry and image.tag in builder/${appName}/application.yaml, or pass a full image ref (e.g. --image <registry>/${appName}:<tag>) when deploying`;
270
273
  throw new Error(
271
274
  `Deployed image must be pullable (include a registry). Current image: "${imageRef || 'none'}". ${hint}`
272
275
  );
@@ -336,10 +339,11 @@ function applyManifestOverrides(manifest, options) {
336
339
  */
337
340
  async function executeStandardDeployment(appName, options) {
338
341
  const config = await loadDeploymentConfig(appName, options);
342
+ logOfflinePathWhenType(config.appPath, options);
339
343
  const controllerUrl = config.controllerUrl || 'unknown';
340
344
  const appExists = await checkApplicationExists(appName, controllerUrl, config.envKey, config.auth);
341
345
 
342
- const { manifest, manifestPath } = await generateAndValidateManifest(appName);
346
+ const { manifest, manifestPath } = await generateAndValidateManifest(appName, options);
343
347
  applyManifestOverrides(manifest, options);
344
348
  validateImageIsPullable(manifest.image, appName);
345
349
  displayDeploymentInfo(manifest, manifestPath);
@@ -388,7 +392,7 @@ async function tryExternalDeployFallback(appName, options) {
388
392
  if (e.code !== 'ENOENT') throw e;
389
393
  }
390
394
  if (!builderExists && integrationExists) {
391
- const fallbackResult = await handleExternalDeployment(appName, { ...options, type: 'external' });
395
+ const fallbackResult = await handleExternalDeployment(appName, options);
392
396
  if (fallbackResult) return { usedExternalDeploy: true, result: fallbackResult };
393
397
  }
394
398
  return { usedExternalDeploy: false, result: null };
@@ -402,20 +406,21 @@ async function tryExternalDeployFallback(appName, options) {
402
406
  * @function deployApp
403
407
  * @param {string} appName - Name of the application to deploy
404
408
  * @param {Object} options - Deployment options
409
+ * @param {boolean} [options.local] - If true, caller may run app locally or restart dataplane (for external)
405
410
  * @param {boolean} [options.poll] - Poll for deployment status
406
411
  * @param {number} [options.pollInterval] - Polling interval in milliseconds
407
412
  * @param {number} [options.pollMaxAttempts] - Max polling attempts
408
- * @returns {Promise<Object>} Deployment result
413
+ * @returns {Promise<{ result: Object, usedExternalDeploy: boolean }>} Deployment result and whether external deploy was used
409
414
  * @throws {Error} If deployment fails
410
415
  *
411
416
  * Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
412
417
  *
413
418
  * @example
414
- * await deployApp('myapp', { poll: true });
419
+ * const { result, usedExternalDeploy } = await deployApp('myapp', { poll: true });
415
420
  */
416
421
  async function deployApp(appName, options = {}) {
417
422
  let controllerUrl = null;
418
- let usedExternalDeploy = options.type === 'external';
423
+ let usedExternalDeploy = false;
419
424
 
420
425
  try {
421
426
  if (!appName || typeof appName !== 'string' || appName.trim().length === 0) {
@@ -424,18 +429,20 @@ async function deployApp(appName, options = {}) {
424
429
  validateAppName(appName);
425
430
 
426
431
  const externalResult = await handleExternalDeployment(appName, options);
427
- if (externalResult) return externalResult;
432
+ if (externalResult) {
433
+ usedExternalDeploy = true;
434
+ return { result: externalResult, usedExternalDeploy: true };
435
+ }
428
436
 
429
437
  const fallback = await tryExternalDeployFallback(appName, options);
430
438
  if (fallback.result) {
431
439
  usedExternalDeploy = fallback.usedExternalDeploy;
432
- return fallback.result;
440
+ return { result: fallback.result, usedExternalDeploy };
433
441
  }
434
- usedExternalDeploy = false;
435
442
 
436
443
  const { result, controllerUrl: url } = await executeStandardDeployment(appName, options);
437
444
  controllerUrl = url;
438
- return result;
445
+ return { result, usedExternalDeploy: false };
439
446
  } catch (error) {
440
447
  await handleDeploymentError(error, appName, controllerUrl, usedExternalDeploy);
441
448
  }
@@ -25,7 +25,7 @@ function displayExternalSystemSuccess(appName, config, location) {
25
25
  logger.log(chalk.blue(`System Key: ${config.systemKey || appName}`));
26
26
  logger.log(chalk.green('\nNext steps:'));
27
27
  logger.log(chalk.white('1. Edit external system JSON files in ' + location));
28
- logger.log(chalk.white('2. Run: aifabrix validate ' + appName + ' --type external'));
28
+ logger.log(chalk.white('2. Run: aifabrix validate ' + appName));
29
29
  logger.log(chalk.white('3. Run: aifabrix login'));
30
30
  logger.log(chalk.white('4. Run: aifabrix deploy ' + appName));
31
31
  }
@@ -11,7 +11,6 @@
11
11
  const fs = require('fs').promises;
12
12
  const path = require('path');
13
13
  const chalk = require('chalk');
14
- const yaml = require('js-yaml');
15
14
  const build = require('../build');
16
15
  const { validateAppName } = require('./push');
17
16
  const logger = require('../utils/logger');
@@ -40,24 +39,25 @@ async function checkDockerfileExists(dockerfilePath, options) {
40
39
  }
41
40
 
42
41
  /**
43
- * Loads application configuration from variables.yaml
44
- * @async
45
- * @param {string} configPath - Path to variables.yaml
42
+ * Loads application configuration from application config (application.yaml or application.json)
43
+ * @param {string} appPath - Application directory path
46
44
  * @param {Object} options - Generation options
47
- * @returns {Promise<Object>} Application configuration
45
+ * @returns {Object} Application configuration
48
46
  * @throws {Error} If configuration cannot be loaded
49
47
  */
50
- async function loadAppConfig(configPath, options) {
48
+ function loadAppConfig(appPath, options) {
49
+ const { resolveApplicationConfigPath } = require('../utils/paths');
50
+ const { loadConfigFile } = require('../utils/config-format');
51
51
  try {
52
- const yamlContent = await fs.readFile(configPath, 'utf8');
53
- const variables = yaml.load(yamlContent);
52
+ const configPath = resolveApplicationConfigPath(appPath);
53
+ const variables = loadConfigFile(configPath);
54
54
  return {
55
55
  language: options.language || variables.build?.language || 'typescript',
56
56
  port: getContainerPort(variables, 3000),
57
57
  ...variables
58
58
  };
59
- } catch {
60
- throw new Error(`Failed to load configuration from ${configPath}`);
59
+ } catch (error) {
60
+ throw new Error(`Failed to load configuration: ${error.message}`);
61
61
  }
62
62
  }
63
63
 
@@ -109,8 +109,7 @@ async function generateDockerfileForApp(appName, options = {}) {
109
109
  await checkDockerfileExists(dockerfilePath, options);
110
110
 
111
111
  // Load configuration
112
- const configPath = path.join(appPath, 'variables.yaml');
113
- const config = await loadAppConfig(configPath, options);
112
+ const config = loadAppConfig(appPath, options);
114
113
 
115
114
  // Generate and copy Dockerfile
116
115
  return await generateAndCopyDockerfile(appPath, dockerfilePath, config);
@@ -13,6 +13,40 @@ const path = require('path');
13
13
  const chalk = require('chalk');
14
14
  const { validateTemplate, copyTemplateFiles, copyAppFiles } = require('../validation/template');
15
15
  const logger = require('../utils/logger');
16
+ const { getIntegrationPath, getBuilderPath } = require('../utils/paths');
17
+
18
+ /**
19
+ * Validates that no app or external system with this name exists in integration/ or builder/.
20
+ * Call before create so we do not overwrite existing directories.
21
+ *
22
+ * @async
23
+ * @param {string} appName - Application or external system name
24
+ * @throws {Error} If integration/<appName> or builder/<appName> already exists
25
+ */
26
+ async function validateAppOrExternalNameNotExists(appName) {
27
+ const integrationPath = getIntegrationPath(appName);
28
+ const builderPath = getBuilderPath(appName);
29
+ try {
30
+ await fs.access(integrationPath);
31
+ throw new Error(
32
+ `App or external system '${appName}' already exists in integration/. ` +
33
+ `Use a different name or remove integration/${appName} if you intend to replace it.`
34
+ );
35
+ } catch (err) {
36
+ if (err.code !== 'ENOENT' && err.message.includes('already exists')) throw err;
37
+ if (err.code !== 'ENOENT') throw err;
38
+ }
39
+ try {
40
+ await fs.access(builderPath);
41
+ throw new Error(
42
+ `App or external system '${appName}' already exists in builder/. ` +
43
+ `Use a different name or remove builder/${appName} if you intend to replace it.`
44
+ );
45
+ } catch (err) {
46
+ if (err.code !== 'ENOENT' && err.message.includes('already exists')) throw err;
47
+ if (err.code !== 'ENOENT') throw err;
48
+ }
49
+ }
16
50
 
17
51
  /**
18
52
  * Validates that app directory doesn't already exist
@@ -87,6 +121,7 @@ async function handleGitHubWorkflows(options, config) {
87
121
  async function validateAppCreation(appName, options, appPath, baseDir = 'builder') {
88
122
  const { validateAppName } = require('./push');
89
123
  validateAppName(appName);
124
+ await validateAppOrExternalNameNotExists(appName);
90
125
  await validateAppDirectoryNotExists(appPath, appName, baseDir);
91
126
 
92
127
  if (!options.app) {
@@ -128,7 +163,7 @@ async function processTemplateFiles(template, appPath, appName, options, config)
128
163
  }
129
164
 
130
165
  /**
131
- * Updates variables.yaml for --app flag
166
+ * Updates application config for --app flag
132
167
  * @async
133
168
  * @function updateVariablesForAppFlag
134
169
  * @param {string} appPath - Application directory path
@@ -136,11 +171,11 @@ async function processTemplateFiles(template, appPath, appName, options, config)
136
171
  * @throws {Error} If update fails
137
172
  */
138
173
  async function updateVariablesForAppFlag(appPath, appName) {
174
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
175
+ const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
139
176
  try {
140
- const yaml = require('js-yaml');
141
- const variablesPath = path.join(appPath, 'variables.yaml');
142
- const variablesContent = await fs.readFile(variablesPath, 'utf8');
143
- const variables = yaml.load(variablesContent);
177
+ const variablesPath = resolveApplicationConfigPath(appPath);
178
+ const variables = loadConfigFile(variablesPath) || {};
144
179
 
145
180
  if (variables.build) {
146
181
  variables.build.context = '../..';
@@ -152,14 +187,16 @@ async function updateVariablesForAppFlag(appPath, appName) {
152
187
  };
153
188
  }
154
189
 
155
- await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }));
190
+ writeConfigFile(variablesPath, variables);
156
191
  } catch (error) {
157
- logger.warn(chalk.yellow(`⚠️ Warning: Could not update variables.yaml: ${error.message}`));
192
+ if (!error.message.includes('not found')) {
193
+ logger.warn(chalk.yellow(`⚠️ Warning: Could not update application config: ${error.message}`));
194
+ }
158
195
  }
159
196
  }
160
197
 
161
198
  /**
162
- * Gets language from config or variables.yaml
199
+ * Gets language from config or application.yaml
163
200
  * @async
164
201
  * @function getLanguageForAppFiles
165
202
  * @param {string} language - Language from config
@@ -172,14 +209,14 @@ async function getLanguageForAppFiles(language, appPath) {
172
209
  return language;
173
210
  }
174
211
 
175
- const yaml = require('js-yaml');
176
- const variablesPath = path.join(appPath, 'variables.yaml');
177
- const variablesContent = await fs.readFile(variablesPath, 'utf8');
178
- const variables = yaml.load(variablesContent);
212
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
213
+ const { loadConfigFile } = require('../utils/config-format');
214
+ const configPath = resolveApplicationConfigPath(appPath);
215
+ const variables = loadConfigFile(configPath) || {};
179
216
  const languageFromYaml = variables?.build?.language;
180
217
 
181
218
  if (!languageFromYaml) {
182
- throw new Error('Language not specified and could not be determined from variables.yaml. Use --language flag or ensure variables.yaml contains build.language');
219
+ throw new Error('Language not specified and could not be determined from application.yaml. Use --language flag or ensure application.yaml contains build.language');
183
220
  }
184
221
 
185
222
  return languageFromYaml;
@@ -207,6 +244,7 @@ async function setupAppFiles(appName, appPath, config, options) {
207
244
 
208
245
  module.exports = {
209
246
  validateAppDirectoryNotExists,
247
+ validateAppOrExternalNameNotExists,
210
248
  getBaseDirForAppType,
211
249
  handleGitHubWorkflows,
212
250
  validateAppCreation,
package/lib/app/index.js CHANGED
@@ -52,7 +52,7 @@ const {
52
52
  *
53
53
  * @example
54
54
  * await createApp('myapp', { port: 3000, database: true, language: 'typescript' });
55
- * // Creates builder/ with variables.yaml, env.template, rbac.yaml
55
+ * // Creates builder/ with application.yaml, env.template, rbac.yaml
56
56
  */
57
57
  /**
58
58
  * Validates app name and initial setup
@@ -230,7 +230,7 @@ function detectLanguage(appPath) {
230
230
  * @function generateDockerfile
231
231
  * @param {string} appPath - Path to application directory
232
232
  * @param {string} language - Target language ('typescript', 'python')
233
- * @param {Object} config - Application configuration from variables.yaml
233
+ * @param {Object} config - Application configuration from application.yaml
234
234
  * @returns {Promise<string>} Path to generated Dockerfile
235
235
  * @throws {Error} If template generation fails
236
236
  *
@@ -263,6 +263,17 @@ async function runApp(appName, options = {}) {
263
263
  return appRun.runApp(appName, options);
264
264
  }
265
265
 
266
+ /**
267
+ * Restart a running application container (Docker restart).
268
+ * @async
269
+ * @function restartApp
270
+ * @param {string} appName - Application name (must be running)
271
+ * @returns {Promise<void>} Resolves when container is restarted
272
+ */
273
+ async function restartApp(appName) {
274
+ return appRun.restartApp(appName);
275
+ }
276
+
266
277
  /**
267
278
  * Deploys application to controller
268
279
  * @async
@@ -280,6 +291,7 @@ module.exports = {
280
291
  createApp,
281
292
  buildApp,
282
293
  runApp,
294
+ restartApp,
283
295
  downApp,
284
296
  detectLanguage,
285
297
  generateDockerfile,
@@ -112,16 +112,8 @@ function buildServiceQuestions(options, appType) {
112
112
  return questions;
113
113
  }
114
114
 
115
- /**
116
- * Builds external system configuration questions
117
- * @param {Object} options - Provided options
118
- * @param {string} appName - Application name
119
- * @returns {Array} Array of question objects
120
- */
121
- function buildExternalSystemQuestions(options, appName) {
115
+ function buildExternalSystemIdentityQuestions(options, appName) {
122
116
  const questions = [];
123
-
124
- // System key (defaults to app name)
125
117
  if (!options.systemKey) {
126
118
  questions.push({
127
119
  type: 'input',
@@ -129,18 +121,12 @@ function buildExternalSystemQuestions(options, appName) {
129
121
  message: 'What is the system key?',
130
122
  default: appName,
131
123
  validate: (input) => {
132
- if (!input || input.trim().length === 0) {
133
- return 'System key is required';
134
- }
135
- if (!/^[a-z0-9-]+$/.test(input)) {
136
- return 'System key must contain only lowercase letters, numbers, and hyphens';
137
- }
124
+ if (!input || input.trim().length === 0) return 'System key is required';
125
+ if (!/^[a-z0-9-]+$/.test(input)) return 'System key must contain only lowercase letters, numbers, and hyphens';
138
126
  return true;
139
127
  }
140
128
  });
141
129
  }
142
-
143
- // System display name
144
130
  if (!options.systemDisplayName) {
145
131
  questions.push({
146
132
  type: 'input',
@@ -148,15 +134,11 @@ function buildExternalSystemQuestions(options, appName) {
148
134
  message: 'What is the system display name?',
149
135
  default: appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
150
136
  validate: (input) => {
151
- if (!input || input.trim().length === 0) {
152
- return 'System display name is required';
153
- }
137
+ if (!input || input.trim().length === 0) return 'System display name is required';
154
138
  return true;
155
139
  }
156
140
  });
157
141
  }
158
-
159
- // System description
160
142
  if (!options.systemDescription) {
161
143
  questions.push({
162
144
  type: 'input',
@@ -164,15 +146,16 @@ function buildExternalSystemQuestions(options, appName) {
164
146
  message: 'What is the system description?',
165
147
  default: `External system integration for ${appName}`,
166
148
  validate: (input) => {
167
- if (!input || input.trim().length === 0) {
168
- return 'System description is required';
169
- }
149
+ if (!input || input.trim().length === 0) return 'System description is required';
170
150
  return true;
171
151
  }
172
152
  });
173
153
  }
154
+ return questions;
155
+ }
174
156
 
175
- // System type
157
+ function buildExternalSystemTypeQuestions(options) {
158
+ const questions = [];
176
159
  if (!options.systemType) {
177
160
  questions.push({
178
161
  type: 'list',
@@ -186,8 +169,6 @@ function buildExternalSystemQuestions(options, appName) {
186
169
  default: 'openapi'
187
170
  });
188
171
  }
189
-
190
- // Authentication type
191
172
  if (!options.authType) {
192
173
  questions.push({
193
174
  type: 'list',
@@ -201,25 +182,36 @@ function buildExternalSystemQuestions(options, appName) {
201
182
  default: 'apikey'
202
183
  });
203
184
  }
185
+ return questions;
186
+ }
204
187
 
205
- // Number of datasources
206
- if (!options.datasourceCount) {
207
- questions.push({
208
- type: 'input',
209
- name: 'datasourceCount',
210
- message: 'How many datasources do you want to create?',
211
- default: '1',
212
- validate: (input) => {
213
- const count = parseInt(input, 10);
214
- if (isNaN(count) || count < 1 || count > 10) {
215
- return 'Datasource count must be a number between 1 and 10';
216
- }
217
- return true;
218
- }
219
- });
220
- }
188
+ function buildExternalSystemDatasourceQuestion(options) {
189
+ if (options.datasourceCount) return [];
190
+ return [{
191
+ type: 'input',
192
+ name: 'datasourceCount',
193
+ message: 'How many datasources do you want to create?',
194
+ default: '1',
195
+ validate: (input) => {
196
+ const count = parseInt(input, 10);
197
+ if (isNaN(count) || count < 1 || count > 10) return 'Datasource count must be a number between 1 and 10';
198
+ return true;
199
+ }
200
+ }];
201
+ }
221
202
 
222
- return questions;
203
+ /**
204
+ * Builds external system configuration questions
205
+ * @param {Object} options - Provided options
206
+ * @param {string} appName - Application name
207
+ * @returns {Array} Array of question objects
208
+ */
209
+ function buildExternalSystemQuestions(options, appName) {
210
+ return [
211
+ ...buildExternalSystemIdentityQuestions(options, appName),
212
+ ...buildExternalSystemTypeQuestions(options),
213
+ ...buildExternalSystemDatasourceQuestion(options)
214
+ ];
223
215
  }
224
216
 
225
217
  /**
package/lib/app/push.js CHANGED
@@ -8,10 +8,7 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
- const fs = require('fs').promises;
12
- const path = require('path');
13
11
  const chalk = require('chalk');
14
- const yaml = require('js-yaml');
15
12
  const pushUtils = require('../deployment/push');
16
13
  const logger = require('../utils/logger');
17
14
 
@@ -44,7 +41,7 @@ function validateAppName(appName) {
44
41
 
45
42
  /**
46
43
  * Extracts image name from configuration using the same logic as build command
47
- * @param {Object} config - Configuration object from variables.yaml
44
+ * @param {Object} config - Configuration object from application.yaml
48
45
  * @param {string} appName - Application name (fallback)
49
46
  * @returns {string} Image name
50
47
  */
@@ -61,7 +58,7 @@ function extractImageName(config, appName) {
61
58
  }
62
59
 
63
60
  /**
64
- * Loads push configuration from variables.yaml
61
+ * Loads push configuration from application config (application.yaml or application.json)
65
62
  * @async
66
63
  * @param {string} appName - Application name
67
64
  * @param {Object} options - Push options
@@ -69,15 +66,15 @@ function extractImageName(config, appName) {
69
66
  * @throws {Error} If configuration cannot be loaded
70
67
  */
71
68
  async function loadPushConfig(appName, options) {
72
- // Detect app type and get correct path (integration or builder)
73
- const { detectAppType } = require('../utils/paths');
69
+ const { detectAppType, resolveApplicationConfigPath } = require('../utils/paths');
70
+ const { loadConfigFile } = require('../utils/config-format');
74
71
  const { appPath } = await detectAppType(appName);
75
- const configPath = path.join(appPath, 'variables.yaml');
76
72
  try {
77
- const config = yaml.load(await fs.readFile(configPath, 'utf8'));
73
+ const configPath = resolveApplicationConfigPath(appPath);
74
+ const config = loadConfigFile(configPath);
78
75
  const registry = options.registry || config.image?.registry;
79
76
  if (!registry) {
80
- throw new Error('Registry URL is required. Provide via --registry flag or configure in variables.yaml under image.registry');
77
+ throw new Error('Registry URL is required. Provide via --registry flag or configure in application config under image.registry');
81
78
  }
82
79
  const imageName = extractImageName(config, appName);
83
80
  return { registry, imageName };
@@ -85,7 +82,7 @@ async function loadPushConfig(appName, options) {
85
82
  if (error.message.includes('Registry URL')) {
86
83
  throw error;
87
84
  }
88
- throw new Error(`Failed to load configuration: ${configPath}\nRun 'aifabrix create ${appName}' first`);
85
+ throw new Error(`Failed to load configuration: ${error.message}\nRun 'aifabrix create ${appName}' first`);
89
86
  }
90
87
  }
91
88