@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
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Config Format Converter Layer
3
+ *
4
+ * Single place for YAML/JSON config I/O. All config loaders and writers use this
5
+ * layer; internal code works with plain JS objects and JSON Schema only.
6
+ *
7
+ * @fileoverview Config format conversion (YAML/JSON) at I/O boundary
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const yaml = require('js-yaml');
17
+
18
+ const YAML_EXTENSIONS = ['.yaml', '.yml'];
19
+ const JSON_EXTENSIONS = ['.json'];
20
+
21
+ const DEFAULT_YAML_OPTIONS = { indent: 2, lineWidth: 120, noRefs: true };
22
+
23
+ /**
24
+ * Parses YAML string to plain JS object (same shape as JSON).
25
+ * Used when reading .yaml / .yml files.
26
+ *
27
+ * @param {string} content - YAML string content
28
+ * @returns {Object} Plain JS object
29
+ * @throws {Error} If YAML syntax is invalid
30
+ */
31
+ function yamlToJson(content) {
32
+ if (typeof content !== 'string') {
33
+ throw new Error('yamlToJson expects a string');
34
+ }
35
+ try {
36
+ const parsed = yaml.load(content);
37
+ return parsed === undefined || parsed === null ? {} : parsed;
38
+ } catch (error) {
39
+ throw new Error(`Invalid YAML syntax: ${error.message}`);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Serializes JS object to YAML string.
45
+ * Used when writing human-editable config as YAML.
46
+ *
47
+ * @param {Object} object - Plain JS object (config)
48
+ * @param {Object} [options] - js-yaml dump options
49
+ * @returns {string} YAML string
50
+ */
51
+ function jsonToYaml(object, options = {}) {
52
+ if (object === undefined || object === null) {
53
+ return '';
54
+ }
55
+ const opts = { ...DEFAULT_YAML_OPTIONS, ...options };
56
+ return yaml.dump(object, opts);
57
+ }
58
+
59
+ /**
60
+ * Returns whether the file path is treated as YAML by extension.
61
+ *
62
+ * @param {string} filePath - File path
63
+ * @returns {boolean} True if .yaml or .yml
64
+ */
65
+ function isYamlPath(filePath) {
66
+ const ext = path.extname(filePath).toLowerCase();
67
+ return YAML_EXTENSIONS.includes(ext);
68
+ }
69
+
70
+ /**
71
+ * Returns whether the file path is treated as JSON by extension.
72
+ *
73
+ * @param {string} filePath - File path
74
+ * @returns {boolean} True if .json
75
+ */
76
+ function isJsonPath(filePath) {
77
+ const ext = path.extname(filePath).toLowerCase();
78
+ return JSON_EXTENSIONS.includes(ext);
79
+ }
80
+
81
+ /**
82
+ * Reads config file at path; by extension uses yamlToJson or JSON.parse.
83
+ * Single entry point for "read config file regardless of format".
84
+ *
85
+ * @param {string} filePath - Absolute path to config file
86
+ * @returns {Object} Parsed config object
87
+ * @throws {Error} If file not found, unreadable, or invalid format
88
+ */
89
+ function loadConfigFile(filePath) {
90
+ if (!filePath || typeof filePath !== 'string') {
91
+ throw new Error('loadConfigFile requires a non-empty file path');
92
+ }
93
+ if (!fs.existsSync(filePath)) {
94
+ throw new Error(`Config file not found: ${filePath}`);
95
+ }
96
+ const content = fs.readFileSync(filePath, 'utf8');
97
+ const ext = path.extname(filePath).toLowerCase();
98
+ if (YAML_EXTENSIONS.includes(ext)) {
99
+ return yamlToJson(content);
100
+ }
101
+ if (JSON_EXTENSIONS.includes(ext)) {
102
+ try {
103
+ const parsed = JSON.parse(content);
104
+ return parsed === undefined || parsed === null ? {} : parsed;
105
+ } catch (error) {
106
+ throw new Error(`Invalid JSON syntax in ${path.basename(filePath)}: ${error.message}`);
107
+ }
108
+ }
109
+ throw new Error(`Unsupported config file extension: ${ext}. Use .yaml, .yml, or .json`);
110
+ }
111
+
112
+ /**
113
+ * Writes config object to path as YAML or JSON based on format or path extension.
114
+ *
115
+ * @param {string} filePath - Absolute path to write (extension determines format if format omitted)
116
+ * @param {Object} object - Config object to write
117
+ * @param {string} [format] - 'yaml' or 'json'; if omitted, inferred from filePath extension
118
+ * @throws {Error} If format is invalid or write fails
119
+ */
120
+ function writeConfigFile(filePath, object, format) {
121
+ if (!filePath || typeof filePath !== 'string') {
122
+ throw new Error('writeConfigFile requires a non-empty file path');
123
+ }
124
+ let targetFormat = format;
125
+ if (!targetFormat) {
126
+ const ext = path.extname(filePath).toLowerCase();
127
+ if (YAML_EXTENSIONS.includes(ext)) {
128
+ targetFormat = 'yaml';
129
+ } else if (JSON_EXTENSIONS.includes(ext)) {
130
+ targetFormat = 'json';
131
+ } else {
132
+ throw new Error(`Cannot infer format from path ${filePath}. Use .yaml, .yml, or .json, or pass format.`);
133
+ }
134
+ }
135
+ const normalized = targetFormat.toLowerCase();
136
+ let content;
137
+ if (normalized === 'yaml' || normalized === 'yml') {
138
+ content = jsonToYaml(object);
139
+ } else if (normalized === 'json') {
140
+ content = JSON.stringify(object, null, 2);
141
+ } else {
142
+ throw new Error(`Invalid format: ${format}. Use 'yaml' or 'json'.`);
143
+ }
144
+ fs.writeFileSync(filePath, content, 'utf8');
145
+ }
146
+
147
+ module.exports = {
148
+ yamlToJson,
149
+ jsonToYaml,
150
+ loadConfigFile,
151
+ writeConfigFile,
152
+ isYamlPath,
153
+ isJsonPath
154
+ };
@@ -41,77 +41,31 @@ async function setPathConfig(getConfigFn, saveConfigFn, key, value, errorMsg) {
41
41
  await saveConfigFn(config);
42
42
  }
43
43
 
44
- /**
45
- * Create path configuration functions with config access
46
- * @param {Function} getConfigFn - Function to get config
47
- * @param {Function} saveConfigFn - Function to save config
48
- * @returns {Object} Path configuration functions
49
- */
50
- function createPathConfigFunctions(getConfigFn, saveConfigFn) {
44
+ function createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn) {
51
45
  return {
52
- /**
53
- * Get aifabrix-home override path
54
- * @async
55
- * @returns {Promise<string|null>} Home path or null
56
- */
57
46
  async getAifabrixHomeOverride() {
58
47
  return getPathConfig(getConfigFn, 'aifabrix-home');
59
48
  },
60
-
61
- /**
62
- * Set aifabrix-home override path
63
- * @async
64
- * @param {string} homePath - Home path
65
- * @returns {Promise<void>}
66
- */
67
49
  async setAifabrixHomeOverride(homePath) {
68
50
  await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-home', homePath, 'Home path is required and must be a string');
69
51
  },
70
-
71
- /**
72
- * Get aifabrix-secrets path
73
- * @async
74
- * @returns {Promise<string|null>} Secrets path or null
75
- */
76
52
  async getAifabrixSecretsPath() {
77
53
  return getPathConfig(getConfigFn, 'aifabrix-secrets');
78
54
  },
79
-
80
- /**
81
- * Set aifabrix-secrets path
82
- * @async
83
- * @param {string} secretsPath - Secrets path
84
- * @returns {Promise<void>}
85
- */
86
55
  async setAifabrixSecretsPath(secretsPath) {
87
56
  await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-secrets', secretsPath, 'Secrets path is required and must be a string');
88
- },
57
+ }
58
+ };
59
+ }
89
60
 
90
- /**
91
- * Get aifabrix-env-config path
92
- * @async
93
- * @returns {Promise<string|null>} Env config path or null
94
- */
61
+ function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
62
+ return {
95
63
  async getAifabrixEnvConfigPath() {
96
64
  return getPathConfig(getConfigFn, 'aifabrix-env-config');
97
65
  },
98
-
99
- /**
100
- * Set aifabrix-env-config path
101
- * @async
102
- * @param {string} envConfigPath - Env config path
103
- * @returns {Promise<void>}
104
- */
105
66
  async setAifabrixEnvConfigPath(envConfigPath) {
106
67
  await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-env-config', envConfigPath, 'Env config path is required and must be a string');
107
68
  },
108
-
109
- /**
110
- * Get builder root directory (dirname of aifabrix-env-config when set).
111
- * When set, app dirs and generated .env use this instead of cwd/builder.
112
- * @async
113
- * @returns {Promise<string|null>} Builder root path or null to use cwd/builder
114
- */
115
69
  async getAifabrixBuilderDir() {
116
70
  const envConfigPath = await getPathConfig(getConfigFn, 'aifabrix-env-config');
117
71
  return envConfigPath && typeof envConfigPath === 'string' ? path.dirname(envConfigPath) : null;
@@ -119,6 +73,19 @@ function createPathConfigFunctions(getConfigFn, saveConfigFn) {
119
73
  };
120
74
  }
121
75
 
76
+ /**
77
+ * Create path configuration functions with config access
78
+ * @param {Function} getConfigFn - Function to get config
79
+ * @param {Function} saveConfigFn - Function to save config
80
+ * @returns {Object} Path configuration functions
81
+ */
82
+ function createPathConfigFunctions(getConfigFn, saveConfigFn) {
83
+ return {
84
+ ...createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn),
85
+ ...createEnvConfigPathFunctions(getConfigFn, saveConfigFn)
86
+ };
87
+ }
88
+
122
89
  module.exports = {
123
90
  getPathConfig,
124
91
  setPathConfig,
@@ -39,6 +39,7 @@ function normalizeControllerUrl(url) {
39
39
  * @param {Function} params.isTokenEncryptedFn - Function to check if token is encrypted
40
40
  * @returns {Object} Token management functions
41
41
  */
42
+ /* eslint-disable max-lines-per-function -- factory returns many bound helpers; splitting would break encapsulation */
42
43
  function createTokenManagementFunctions({
43
44
  getConfigFn,
44
45
  saveConfigFn,
@@ -63,6 +63,75 @@ function parseDockerBuildProgress(line) {
63
63
  return null;
64
64
  }
65
65
 
66
+ function updateSpinnerFromOutput(output, spinner, lastProgressUpdateRef) {
67
+ const lines = output.split('\n');
68
+ for (const line of lines) {
69
+ const progress = parseDockerBuildProgress(line.trim());
70
+ if (progress) {
71
+ const now = Date.now();
72
+ if (now - lastProgressUpdateRef.current > 200) {
73
+ spinner.text = `Building image... ${progress}`;
74
+ lastProgressUpdateRef.current = now;
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ function handleDockerClose(code, ctx) {
81
+ const { imageName, tag, stderrBuffer, stdoutBuffer, spinner, resolve, reject } = ctx;
82
+ if (code === 0) {
83
+ spinner.succeed(`Image built: ${imageName}:${tag}`);
84
+ resolve();
85
+ } else {
86
+ spinner.fail('Build failed');
87
+ const errorMessage = stderrBuffer || stdoutBuffer || 'Docker build failed';
88
+ if (isDockerNotAvailableError(errorMessage)) {
89
+ reject(new Error('Docker is not running or not installed. Please start Docker Desktop and try again.'));
90
+ } else {
91
+ const errorLines = errorMessage.split('\n').filter(line => line.trim());
92
+ reject(new Error(`Docker build failed: ${errorLines.slice(-5).join('\n')}`));
93
+ }
94
+ }
95
+ }
96
+
97
+ function runDockerBuildProcess(buildOpts) {
98
+ const { imageName, tag, dockerfilePath, contextPath, spinner, resolve, reject } = buildOpts;
99
+ const dockerProcess = spawn('docker', ['build', '-t', `${imageName}:${tag}`, '-f', dockerfilePath, contextPath], {
100
+ shell: process.platform === 'win32'
101
+ });
102
+ let stdoutBuffer = '';
103
+ let stderrBuffer = '';
104
+ const lastProgressUpdateRef = { current: Date.now() };
105
+
106
+ dockerProcess.stdout.on('data', (data) => {
107
+ const output = data.toString();
108
+ stdoutBuffer += output;
109
+ updateSpinnerFromOutput(output, spinner, lastProgressUpdateRef);
110
+ });
111
+
112
+ dockerProcess.stderr.on('data', (data) => {
113
+ const output = data.toString();
114
+ stderrBuffer += output;
115
+ if (!output.toLowerCase().includes('warning')) {
116
+ updateSpinnerFromOutput(output, spinner, lastProgressUpdateRef);
117
+ }
118
+ });
119
+
120
+ dockerProcess.on('close', (code) => {
121
+ handleDockerClose(code, { imageName, tag, stderrBuffer, stdoutBuffer, spinner, resolve, reject });
122
+ });
123
+
124
+ dockerProcess.on('error', (error) => {
125
+ spinner.fail('Build failed');
126
+ const msg = error.message || String(error);
127
+ if (isDockerNotAvailableError(msg)) {
128
+ reject(new Error('Docker is not running or not installed. Please start Docker Desktop and try again.'));
129
+ } else {
130
+ reject(new Error(`Docker build failed: ${msg}`));
131
+ }
132
+ });
133
+ }
134
+
66
135
  /**
67
136
  * Executes Docker build command with progress indicator
68
137
  * @param {string} imageName - Image name to build
@@ -73,29 +142,20 @@ function parseDockerBuildProgress(line) {
73
142
  * @throws {Error} If build fails
74
143
  */
75
144
  async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag) {
76
- const spinner = ora({
77
- text: 'Starting Docker build...',
78
- spinner: 'dots'
79
- }).start();
80
-
81
- // Ensure paths are absolute and normalized
145
+ const spinner = ora({ text: 'Starting Docker build...', spinner: 'dots' }).start();
82
146
  const fsSync = require('fs');
83
147
  const path = require('path');
84
-
85
148
  dockerfilePath = path.resolve(dockerfilePath);
86
149
  contextPath = path.resolve(contextPath);
87
150
 
88
- // Validate paths exist (skip in test environments)
89
151
  const isTestEnv = process.env.NODE_ENV === 'test' ||
90
152
  process.env.JEST_WORKER_ID !== undefined ||
91
153
  typeof jest !== 'undefined';
92
-
93
154
  if (!isTestEnv) {
94
155
  if (!fsSync.existsSync(dockerfilePath)) {
95
156
  spinner.fail('Build failed');
96
157
  throw new Error(`Dockerfile not found: ${dockerfilePath}`);
97
158
  }
98
-
99
159
  if (!fsSync.existsSync(contextPath)) {
100
160
  spinner.fail('Build failed');
101
161
  throw new Error(`Build context path does not exist: ${contextPath}`);
@@ -103,90 +163,7 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag) {
103
163
  }
104
164
 
105
165
  return new Promise((resolve, reject) => {
106
- // Use spawn for streaming output
107
- const dockerProcess = spawn('docker', [
108
- 'build',
109
- '-t', `${imageName}:${tag}`,
110
- '-f', dockerfilePath,
111
- contextPath
112
- ], {
113
- shell: process.platform === 'win32'
114
- });
115
-
116
- let stdoutBuffer = '';
117
- let stderrBuffer = '';
118
- let lastProgressUpdate = Date.now();
119
-
120
- dockerProcess.stdout.on('data', (data) => {
121
- const output = data.toString();
122
- stdoutBuffer += output;
123
-
124
- // Parse progress from output lines
125
- const lines = output.split('\n');
126
- for (const line of lines) {
127
- const progress = parseDockerBuildProgress(line.trim());
128
- if (progress) {
129
- // Update spinner text with progress (throttle updates)
130
- const now = Date.now();
131
- if (now - lastProgressUpdate > 200) {
132
- spinner.text = `Building image... ${progress}`;
133
- lastProgressUpdate = now;
134
- }
135
- }
136
- }
137
- });
138
-
139
- dockerProcess.stderr.on('data', (data) => {
140
- const output = data.toString();
141
- stderrBuffer += output;
142
-
143
- // Check for warnings vs errors
144
- if (!output.toLowerCase().includes('warning')) {
145
- // Parse progress from stderr too (Docker outputs progress to stderr)
146
- const lines = output.split('\n');
147
- for (const line of lines) {
148
- const progress = parseDockerBuildProgress(line.trim());
149
- if (progress) {
150
- const now = Date.now();
151
- if (now - lastProgressUpdate > 200) {
152
- spinner.text = `Building image... ${progress}`;
153
- lastProgressUpdate = now;
154
- }
155
- }
156
- }
157
- }
158
- });
159
-
160
- dockerProcess.on('close', (code) => {
161
- if (code === 0) {
162
- spinner.succeed(`Image built: ${imageName}:${tag}`);
163
- resolve();
164
- } else {
165
- spinner.fail('Build failed');
166
-
167
- const errorMessage = stderrBuffer || stdoutBuffer || 'Docker build failed';
168
-
169
- if (isDockerNotAvailableError(errorMessage)) {
170
- reject(new Error('Docker is not running or not installed. Please start Docker Desktop and try again.'));
171
- } else {
172
- // Show last few lines of error output
173
- const errorLines = errorMessage.split('\n').filter(line => line.trim());
174
- const lastError = errorLines.slice(-5).join('\n');
175
- reject(new Error(`Docker build failed: ${lastError}`));
176
- }
177
- }
178
- });
179
-
180
- dockerProcess.on('error', (error) => {
181
- spinner.fail('Build failed');
182
- const errorMessage = error.message || String(error);
183
-
184
- if (isDockerNotAvailableError(errorMessage)) {
185
- reject(new Error('Docker is not running or not installed. Please start Docker Desktop and try again.'));
186
- } else {
187
- reject(new Error(`Docker build failed: ${errorMessage}`));
188
- }
189
- });
166
+ runDockerBuildProcess({ imageName, tag, dockerfilePath, contextPath, spinner, resolve, reject });
190
167
  });
191
168
  }
192
169
 
@@ -103,7 +103,7 @@ function checkTemplateDockerfile(builderPath, appName, forceTemplate) {
103
103
  }
104
104
 
105
105
  /**
106
- * Checks for custom Dockerfile from variables.yaml
106
+ * Checks for custom Dockerfile from application config
107
107
  * @function checkProjectDockerfile
108
108
  * @param {string} builderPath - Builder directory path
109
109
  * @param {string} appName - Application name
@@ -50,8 +50,8 @@ function readDeveloperIdFromConfig(config) {
50
50
 
51
51
  /**
52
52
  * Resolve output path for env file
53
- * @param {string} rawOutputPath - Raw output path from variables.yaml
54
- * @param {string} variablesPath - Path to variables.yaml
53
+ * @param {string} rawOutputPath - Raw output path from application config
54
+ * @param {string} variablesPath - Path to application config
55
55
  * @returns {string} Resolved output path
56
56
  */
57
57
  function resolveEnvOutputPath(rawOutputPath, variablesPath) {
@@ -154,7 +154,7 @@ function extractEnvVarsFromContent(envContent, envVars) {
154
154
  * Patch env content for local development
155
155
  * @async
156
156
  * @param {string} envContent - Original env content
157
- * @param {Object} variables - Variables from variables.yaml
157
+ * @param {Object} variables - Variables from application config
158
158
  * @returns {Promise<string>} Patched env content
159
159
  */
160
160
  async function patchEnvContentForLocal(envContent, variables) {
@@ -186,7 +186,7 @@ async function patchEnvContentForLocal(envContent, variables) {
186
186
  * @async
187
187
  * @function processEnvVariables
188
188
  * @param {string} envPath - Path to generated .env file
189
- * @param {string} variablesPath - Path to variables.yaml
189
+ * @param {string} variablesPath - Path to application config
190
190
  * @param {string} appName - Application name (for regenerating with local env)
191
191
  * @param {string} [secretsPath] - Path to secrets file (optional, for regenerating)
192
192
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Environment port utilities
3
3
  *
4
- * @fileoverview Update container PORT based on variables.yaml and developer-id offset
4
+ * @fileoverview Update container PORT based on application config and developer-id offset
5
5
  * @author AI Fabrix Team
6
6
  * @version 2.0.0
7
7
  */
@@ -17,7 +17,7 @@ const { getLocalPort } = require('./port-resolver');
17
17
  * Update PORT in the container's .env file to use variables.port (+offset)
18
18
  * @function updateContainerPortInEnvFile
19
19
  * @param {string} envPath - Path to .env
20
- * @param {string} variablesPath - Path to variables.yaml
20
+ * @param {string} variablesPath - Path to application config
21
21
  */
22
22
  /**
23
23
  * Gets developer ID from environment variable or config file
@@ -187,7 +187,7 @@ function formatMissingDbPasswordError(appKey, opts = {}) {
187
187
  'Add ' + passwordKey + '=your_secret to your .env file. For multiple databases you need DB_0_PASSWORD, DB_1_PASSWORD, etc.';
188
188
  }
189
189
  return 'Missing required password variable DB_0_PASSWORD or DB_PASSWORD in .env file for application \'' + appKey + '\'. ' +
190
- 'This app has requires.database or databases in variables.yaml. Add DB_0_PASSWORD=your_secret or DB_PASSWORD=your_secret to builder/' + appKey + '/.env (or run \'aifabrix resolve ' + appKey + '\'), or set requires.database: false in variables.yaml if not needed.';
190
+ 'This app has requires.database or databases in application.yaml. Add DB_0_PASSWORD=your_secret or DB_PASSWORD=your_secret to builder/' + appKey + '/.env (or run \'aifabrix resolve ' + appKey + '\'), or set requires.database: false in application.yaml if not needed.';
191
191
  }
192
192
 
193
193
  module.exports = {
@@ -97,7 +97,7 @@ function addValidationGuidance(lines, hasErrors) {
97
97
  return;
98
98
  }
99
99
  lines.push(chalk.gray('Tips:'));
100
- lines.push(chalk.gray(' • Check your variables.yaml file for the correct field values'));
100
+ lines.push(chalk.gray(' • Check your application.yaml file for the correct field values'));
101
101
  lines.push(chalk.gray(' • Verify field names match the expected schema'));
102
102
  lines.push(chalk.gray(' • Ensure required fields are present and valid'));
103
103
  lines.push('');
@@ -51,11 +51,18 @@ function normalizeDatasources(datasources, systemKey) {
51
51
  let fileName = datasource.fileName || datasource.file;
52
52
  if (!fileName) {
53
53
  const key = datasource.key || '';
54
- // Extract entity from keys like "hubspot-deploy-company" -> "company"
55
- const entity = (systemKey && key.startsWith(`${systemKey}-deploy-`))
56
- ? key.slice(`${systemKey}-deploy-`.length)
57
- : entityType;
58
- fileName = systemKey ? `${systemKey}-datasource-${entity}.json` : `${entity}.json`;
54
+ // Suffix matches split getExternalDatasourceFileName for consistent README and file names
55
+ let suffix;
56
+ if (key.startsWith(`${systemKey}-deploy-`)) {
57
+ suffix = key.slice(`${systemKey}-deploy-`.length);
58
+ } else if (systemKey && key.startsWith(`${systemKey}-`)) {
59
+ suffix = key.slice(systemKey.length + 1);
60
+ } else if (key) {
61
+ suffix = key;
62
+ } else {
63
+ suffix = entityType;
64
+ }
65
+ fileName = systemKey ? `${systemKey}-datasource-${suffix}.yaml` : `${suffix}.yaml`;
59
66
  }
60
67
  return { entityType, displayName, fileName };
61
68
  });
@@ -12,6 +12,7 @@
12
12
  const fs = require('fs').promises;
13
13
  const path = require('path');
14
14
  const { testDatasourceViaPipeline } = require('../api/pipeline.api');
15
+ const { requireBearerForDataplanePipeline } = require('./token-manager');
15
16
 
16
17
  /**
17
18
  * Retry API call with exponential backoff
@@ -51,6 +52,7 @@ async function retryApiCall(fn, maxRetries = 3, backoffMs = 1000) {
51
52
  * @returns {Promise<Object>} Test response
52
53
  */
53
54
  async function callPipelineTestEndpoint({ systemKey, datasourceKey, payloadTemplate, dataplaneUrl, authConfig, timeout = 30000 }) {
55
+ requireBearerForDataplanePipeline(authConfig);
54
56
  const response = await retryApiCall(async() => {
55
57
  return await testDatasourceViaPipeline({
56
58
  dataplaneUrl,