@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.
- package/.cursor/rules/project-rules.mdc +6 -6
- package/README.md +2 -2
- package/babel.config.js +6 -0
- package/integration/hubspot/README.md +53 -141
- package/integration/hubspot/application.yaml +37 -0
- package/integration/hubspot/env.template +2 -11
- package/integration/hubspot/hubspot-deploy.json +1 -0
- package/integration/hubspot/test.js +5 -5
- package/lib/api/credentials.api.js +5 -5
- package/lib/api/deployments.api.js +2 -2
- package/lib/api/pipeline.api.js +17 -17
- package/lib/api/wizard.api.js +2 -2
- package/lib/app/config.js +11 -6
- package/lib/app/deploy-config.js +13 -16
- package/lib/app/deploy.js +29 -22
- package/lib/app/display.js +1 -1
- package/lib/app/dockerfile.js +11 -12
- package/lib/app/helpers.js +51 -13
- package/lib/app/index.js +14 -2
- package/lib/app/prompts.js +37 -45
- package/lib/app/push.js +8 -11
- package/lib/app/readme.js +16 -12
- package/lib/app/register.js +3 -3
- package/lib/app/run-helpers.js +31 -22
- package/lib/app/run.js +44 -5
- package/lib/app/show-display.js +104 -44
- package/lib/app/show.js +123 -43
- package/lib/build/index.js +11 -18
- package/lib/cli/setup-app.js +38 -28
- package/lib/cli/setup-auth.js +18 -15
- package/lib/cli/setup-credential-deployment.js +3 -1
- package/lib/cli/setup-external-system.js +35 -16
- package/lib/cli/setup-infra.js +45 -23
- package/lib/cli/setup-utility.js +79 -31
- package/lib/commands/app-logs.js +165 -10
- package/lib/commands/app.js +30 -26
- package/lib/commands/convert.js +202 -0
- package/lib/commands/credential-list.js +78 -17
- package/lib/commands/datasource.js +24 -24
- package/lib/commands/deployment-list.js +13 -6
- package/lib/commands/up-common.js +80 -42
- package/lib/commands/up-dataplane.js +15 -14
- package/lib/commands/up-miso.js +15 -14
- package/lib/commands/upload.js +163 -0
- package/lib/commands/wizard-core.js +5 -4
- package/lib/core/diff.js +84 -9
- package/lib/core/key-generator.js +9 -12
- package/lib/core/secrets-docker-env.js +2 -2
- package/lib/core/secrets.js +3 -2
- package/lib/core/templates.js +2 -2
- package/lib/datasource/deploy.js +2 -1
- package/lib/deployment/deployer.js +76 -48
- package/lib/external-system/delete.js +0 -1
- package/lib/external-system/deploy-helpers.js +5 -6
- package/lib/external-system/deploy.js +7 -2
- package/lib/external-system/download-helpers.js +4 -4
- package/lib/external-system/download.js +11 -10
- package/lib/external-system/generator.js +19 -17
- package/lib/external-system/test.js +10 -15
- package/lib/generator/builders.js +1 -1
- package/lib/generator/external-controller-manifest.js +26 -29
- package/lib/generator/external-schema-utils.js +6 -18
- package/lib/generator/external.js +32 -27
- package/lib/generator/github.js +1 -1
- package/lib/generator/helpers.js +12 -19
- package/lib/generator/index.js +15 -15
- package/lib/generator/parse-image.js +35 -0
- package/lib/generator/split-readme.js +105 -0
- package/lib/generator/split-variables.js +149 -0
- package/lib/generator/split.js +86 -246
- package/lib/generator/wizard.js +46 -69
- package/lib/schema/application-schema.json +4 -4
- package/lib/schema/deployment-rules.yaml +0 -4
- package/lib/schema/external-datasource.schema.json +5 -0
- package/lib/schema/external-system.schema.json +10 -0
- package/lib/utils/app-config-resolver.js +52 -0
- package/lib/utils/app-register-api.js +1 -1
- package/lib/utils/app-register-auth.js +1 -1
- package/lib/utils/app-register-config.js +16 -23
- package/lib/utils/app-register-display.js +22 -3
- package/lib/utils/app-register-validator.js +2 -2
- package/lib/utils/cli-utils.js +47 -3
- package/lib/utils/config-format.js +154 -0
- package/lib/utils/config-paths.js +19 -52
- package/lib/utils/config-tokens.js +1 -0
- package/lib/utils/docker-build.js +71 -94
- package/lib/utils/dockerfile-utils.js +1 -1
- package/lib/utils/env-copy.js +4 -4
- package/lib/utils/env-ports.js +2 -2
- package/lib/utils/error-formatter.js +1 -1
- package/lib/utils/error-formatters/validation-errors.js +1 -1
- package/lib/utils/external-readme.js +12 -5
- package/lib/utils/external-system-test-helpers.js +2 -0
- package/lib/utils/health-check.js +55 -66
- package/lib/utils/image-version.js +12 -21
- package/lib/utils/paths.js +39 -66
- package/lib/utils/port-resolver.js +8 -8
- package/lib/utils/schema-loader.js +22 -0
- package/lib/utils/schema-resolver.js +23 -33
- package/lib/utils/secrets-helpers.js +7 -7
- package/lib/utils/secrets-utils.js +10 -12
- package/lib/utils/template-helpers.js +13 -13
- package/lib/utils/token-manager.js +20 -2
- package/lib/utils/variable-transformer.js +2 -2
- package/lib/validation/validate-display.js +3 -4
- package/lib/validation/validate.js +33 -27
- package/lib/validation/validator.js +50 -30
- package/package.json +2 -1
- package/templates/README.md +1 -1
- package/templates/applications/README.md.hbs +3 -3
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/external-system/README.md.hbs +4 -4
- package/integration/hubspot/variables.yaml +0 -17
- /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
- /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
- /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
|
-
|
|
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
|
|
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
|
package/lib/utils/env-copy.js
CHANGED
|
@@ -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
|
|
54
|
-
* @param {string} variablesPath - Path to
|
|
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
|
|
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
|
|
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
|
*/
|
package/lib/utils/env-ports.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Environment port utilities
|
|
3
3
|
*
|
|
4
|
-
* @fileoverview Update container PORT based on
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
126
|
+
* @param {Object} variables - Parsed application config
|
|
129
127
|
* @param {Object} [options] - Options
|
|
130
|
-
* @param {boolean} [options.updateBuilder] - When true, update builder
|
|
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 =
|
|
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
|
|
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 {
|
|
175
|
+
* @returns {boolean} True if file was updated
|
|
179
176
|
*/
|
|
180
|
-
|
|
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
|
|
191
|
-
const parsed =
|
|
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
|
-
|
|
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;
|