@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
@@ -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,
@@ -231,6 +231,60 @@ function parseHealthResponse(data, statusCode) {
231
231
  }
232
232
  }
233
233
 
234
+ function handleHealthResponse(res, data, debug, resolve) {
235
+ const isHealthy = parseHealthResponse(data, res.statusCode);
236
+ if (debug) {
237
+ const truncatedData = data.length > 200 ? data.substring(0, 200) + '...' : data;
238
+ logger.log(chalk.gray(`[DEBUG] Response body: ${truncatedData}`));
239
+ logger.log(chalk.gray(`[DEBUG] Health check result: ${isHealthy ? 'healthy' : 'unhealthy'}`));
240
+ }
241
+ resolve(isHealthy);
242
+ }
243
+
244
+ function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject) {
245
+ try {
246
+ const urlObj = new URL(healthCheckUrl);
247
+ const options = {
248
+ hostname: urlObj.hostname,
249
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
250
+ path: urlObj.pathname + urlObj.search,
251
+ method: 'GET'
252
+ };
253
+ if (debug) {
254
+ logger.log(chalk.gray(`[DEBUG] Health check request: ${healthCheckUrl}`));
255
+ logger.log(chalk.gray(`[DEBUG] Request options: ${JSON.stringify(options, null, 2)}`));
256
+ }
257
+ // eslint-disable-next-line prefer-const
258
+ let timeoutId;
259
+ const req = http.request(options, (res) => {
260
+ clearTimeout(timeoutId);
261
+ let data = '';
262
+ if (debug) {
263
+ logger.log(chalk.gray(`[DEBUG] Response status code: ${res.statusCode}`));
264
+ logger.log(chalk.gray(`[DEBUG] Response headers: ${JSON.stringify(res.headers, null, 2)}`));
265
+ }
266
+ res.on('data', (chunk) => {
267
+ data += chunk;
268
+ });
269
+ res.on('end', () => handleHealthResponse(res, data, debug, resolve));
270
+ });
271
+ timeoutId = setTimeout(() => {
272
+ if (debug) logger.log(chalk.gray('[DEBUG] Health check request timeout after 5 seconds'));
273
+ req.destroy();
274
+ resolve(false);
275
+ }, 5000);
276
+ req.on('error', (error) => {
277
+ clearTimeout(timeoutId);
278
+ if (debug) logger.log(chalk.gray(`[DEBUG] Health check request error: ${error.message}`));
279
+ resolve(false);
280
+ });
281
+ req.end();
282
+ } catch (error) {
283
+ if (debug) logger.log(chalk.gray(`[DEBUG] Health check exception: ${error.message}`));
284
+ reject(error);
285
+ }
286
+ }
287
+
234
288
  /**
235
289
  * Checks health endpoint
236
290
  * @async
@@ -242,72 +296,7 @@ function parseHealthResponse(data, statusCode) {
242
296
  */
243
297
  async function checkHealthEndpoint(healthCheckUrl, debug = false) {
244
298
  return new Promise((resolve, reject) => {
245
- try {
246
- const urlObj = new URL(healthCheckUrl);
247
- const options = {
248
- hostname: urlObj.hostname,
249
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
250
- path: urlObj.pathname + urlObj.search,
251
- method: 'GET'
252
- };
253
-
254
- if (debug) {
255
- logger.log(chalk.gray(`[DEBUG] Health check request: ${healthCheckUrl}`));
256
- logger.log(chalk.gray(`[DEBUG] Request options: ${JSON.stringify(options, null, 2)}`));
257
- }
258
-
259
- // Declare timeoutId before creating req so it can be used in callbacks
260
- // eslint-disable-next-line prefer-const
261
- let timeoutId;
262
-
263
- const req = http.request(options, (res) => {
264
- clearTimeout(timeoutId);
265
- let data = '';
266
- if (debug) {
267
- logger.log(chalk.gray(`[DEBUG] Response status code: ${res.statusCode}`));
268
- logger.log(chalk.gray(`[DEBUG] Response headers: ${JSON.stringify(res.headers, null, 2)}`));
269
- }
270
- res.on('data', (chunk) => {
271
- data += chunk;
272
- });
273
- res.on('end', () => {
274
- if (debug) {
275
- const truncatedData = data.length > 200 ? data.substring(0, 200) + '...' : data;
276
- logger.log(chalk.gray(`[DEBUG] Response body: ${truncatedData}`));
277
- }
278
- const isHealthy = parseHealthResponse(data, res.statusCode);
279
- if (debug) {
280
- logger.log(chalk.gray(`[DEBUG] Health check result: ${isHealthy ? 'healthy' : 'unhealthy'}`));
281
- }
282
- resolve(isHealthy);
283
- });
284
- });
285
-
286
- // Set timeout for the request using setTimeout
287
- timeoutId = setTimeout(() => {
288
- if (debug) {
289
- logger.log(chalk.gray('[DEBUG] Health check request timeout after 5 seconds'));
290
- }
291
- req.destroy();
292
- resolve(false);
293
- }, 5000);
294
-
295
- req.on('error', (error) => {
296
- clearTimeout(timeoutId);
297
- if (debug) {
298
- logger.log(chalk.gray(`[DEBUG] Health check request error: ${error.message}`));
299
- }
300
- resolve(false);
301
- });
302
-
303
- req.end();
304
- } catch (error) {
305
- if (debug) {
306
- logger.log(chalk.gray(`[DEBUG] Health check exception: ${error.message}`));
307
- }
308
- // Re-throw exceptions (not just network errors)
309
- reject(error);
310
- }
299
+ doHealthCheckRequest(healthCheckUrl, debug, resolve, reject);
311
300
  });
312
301
  }
313
302
 
@@ -11,11 +11,9 @@
11
11
 
12
12
  const { exec } = require('child_process');
13
13
  const { promisify } = require('util');
14
- const fs = require('fs').promises;
15
- const fsSync = require('fs');
16
- const path = require('path');
17
- const yaml = require('js-yaml');
18
14
  const { getBuilderPath } = require('./paths');
15
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
16
+ const { loadConfigFile, writeConfigFile } = require('./config-format');
19
17
  const composeGenerator = require('./compose-generator');
20
18
  const containerHelpers = require('./app-run-containers');
21
19
 
@@ -86,7 +84,7 @@ function compareSemver(a, b) {
86
84
 
87
85
  /**
88
86
  * Resolves version for external app (app.version or externalIntegration.version)
89
- * @param {Object} variables - Parsed variables.yaml
87
+ * @param {Object} variables - Parsed application config
90
88
  * @returns {string}
91
89
  */
92
90
  function resolveExternalVersion(variables) {
@@ -125,9 +123,9 @@ async function resolveRegularVersion(imageName, imageTag, templateVersion) {
125
123
  * Resolves version for an app: from image when image exists and template empty or smaller
126
124
  * @async
127
125
  * @param {string} appName - Application name
128
- * @param {Object} variables - Parsed variables.yaml
126
+ * @param {Object} variables - Parsed application config
129
127
  * @param {Object} [options] - Options
130
- * @param {boolean} [options.updateBuilder] - When true, update builder/variables.yaml if fromImage
128
+ * @param {boolean} [options.updateBuilder] - When true, update builder application config if fromImage
131
129
  * @param {string} [options.builderPath] - Builder path (defaults to getBuilderPath(appName))
132
130
  * @returns {Promise<{ version: string, fromImage: boolean, updated: boolean }>}
133
131
  */
@@ -164,37 +162,30 @@ async function resolveVersionForApp(appName, variables, options = {}) {
164
162
  let updated = false;
165
163
  if (fromImage && options.updateBuilder) {
166
164
  const builderPath = options.builderPath || getBuilderPath(appName);
167
- updated = await updateAppVersionInVariablesYaml(builderPath, version);
165
+ updated = updateAppVersionInVariablesYaml(builderPath, version);
168
166
  }
169
167
 
170
168
  return { version, fromImage, updated };
171
169
  }
172
170
 
173
171
  /**
174
- * Updates app.version in builder variables.yaml
175
- * @async
172
+ * Updates app.version in builder application config
176
173
  * @param {string} builderPath - Path to builder app directory
177
174
  * @param {string} version - Version to set
178
- * @returns {Promise<boolean>} True if file was updated
175
+ * @returns {boolean} True if file was updated
179
176
  */
180
- async function updateAppVersionInVariablesYaml(builderPath, version) {
177
+ function updateAppVersionInVariablesYaml(builderPath, version) {
181
178
  if (!builderPath || !version || typeof version !== 'string') {
182
179
  return false;
183
180
  }
184
- const variablesPath = path.join(builderPath, 'variables.yaml');
185
- if (!fsSync.existsSync(variablesPath)) {
186
- return false;
187
- }
188
-
189
181
  try {
190
- const content = await fs.readFile(variablesPath, 'utf8');
191
- const parsed = yaml.load(content) || {};
182
+ const configPath = resolveApplicationConfigPath(builderPath);
183
+ const parsed = loadConfigFile(configPath) || {};
192
184
  if (!parsed.app) {
193
185
  parsed.app = {};
194
186
  }
195
187
  parsed.app.version = version;
196
- const dumped = yaml.dump(parsed, { lineWidth: -1 });
197
- await fs.writeFile(variablesPath, dumped, { mode: 0o644, encoding: 'utf8' });
188
+ writeConfigFile(configPath, parsed);
198
189
  return true;
199
190
  } catch {
200
191
  return false;