@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.
- package/.cursor/rules/project-rules.mdc +6 -6
- package/README.md +3 -3
- 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/jest.config.manual.js +29 -0
- 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 +1 -1
- 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 +36 -29
- package/lib/cli/setup-auth.js +19 -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 +85 -31
- package/lib/commands/app-logs.js +28 -20
- package/lib/commands/app.js +30 -26
- package/lib/commands/auth-status.js +36 -3
- 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 +51 -70
- package/lib/schema/application-schema.json +4 -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-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 +45 -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 +34 -28
- package/lib/validation/validator.js +50 -30
- package/package.json +4 -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/templates/external-system/external-system.json.hbs +1 -16
- 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
|
@@ -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;
|
package/lib/utils/paths.js
CHANGED
|
@@ -178,6 +178,12 @@ function checkGlobalProjectRoot() {
|
|
|
178
178
|
return null;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// In test environment, allow temp dir as project root (Jest sets NODE_ENV=test / JEST_WORKER_ID)
|
|
182
|
+
const isTestEnv = process.env.NODE_ENV === 'test' || typeof process.env.JEST_WORKER_ID !== 'undefined';
|
|
183
|
+
if (isTestEnv) {
|
|
184
|
+
return globalRoot;
|
|
185
|
+
}
|
|
186
|
+
|
|
181
187
|
// Verify that __dirname is actually within globalRoot
|
|
182
188
|
const dirnameNormalized = path.resolve(__dirname);
|
|
183
189
|
const globalRootNormalized = path.resolve(globalRoot);
|
|
@@ -371,27 +377,11 @@ function getDeployJsonPath(appName, appType, preferNew = false) {
|
|
|
371
377
|
// If neither exists, return new naming (for generation)
|
|
372
378
|
return newPath;
|
|
373
379
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
* Reads and parses variables.yaml file
|
|
377
|
-
* @param {string} variablesPath - Path to variables.yaml file
|
|
378
|
-
* @returns {Object|null} Parsed variables object or null if error
|
|
379
|
-
*/
|
|
380
|
-
function readVariablesFile(variablesPath) {
|
|
381
|
-
try {
|
|
382
|
-
if (!fs.existsSync(variablesPath)) {
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
386
|
-
return yaml.load(content);
|
|
387
|
-
} catch {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
380
|
+
const { resolveApplicationConfigPath } = require('./app-config-resolver');
|
|
381
|
+
const { loadConfigFile } = require('./config-format');
|
|
392
382
|
/**
|
|
393
383
|
* Checks if app type is external from variables object
|
|
394
|
-
* @param {Object} variables - Parsed
|
|
384
|
+
* @param {Object} variables - Parsed application config object
|
|
395
385
|
* @returns {boolean} True if app type is external
|
|
396
386
|
*/
|
|
397
387
|
function isExternalAppType(variables) {
|
|
@@ -399,20 +389,25 @@ function isExternalAppType(variables) {
|
|
|
399
389
|
}
|
|
400
390
|
|
|
401
391
|
/**
|
|
402
|
-
* Checks integration folder for
|
|
392
|
+
* Checks integration folder for any valid application config
|
|
403
393
|
* @param {string} appName - Application name
|
|
404
|
-
* @returns {Object|null} App type info or null if
|
|
394
|
+
* @returns {Object|null} App type info or null if no config found
|
|
405
395
|
*/
|
|
406
396
|
function checkIntegrationFolder(appName) {
|
|
407
397
|
const integrationPath = getIntegrationPath(appName);
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
398
|
+
let variables;
|
|
399
|
+
try {
|
|
400
|
+
const configPath = resolveApplicationConfigPath(integrationPath);
|
|
401
|
+
variables = loadConfigFile(configPath);
|
|
402
|
+
} catch {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
if (variables) {
|
|
406
|
+
const isExternal = isExternalAppType(variables);
|
|
412
407
|
return {
|
|
413
|
-
isExternal
|
|
408
|
+
isExternal,
|
|
414
409
|
appPath: integrationPath,
|
|
415
|
-
appType: 'external',
|
|
410
|
+
appType: isExternal ? 'external' : 'regular',
|
|
416
411
|
baseDir: 'integration'
|
|
417
412
|
};
|
|
418
413
|
}
|
|
@@ -422,13 +417,20 @@ function checkIntegrationFolder(appName) {
|
|
|
422
417
|
/**
|
|
423
418
|
* Checks builder folder for app type
|
|
424
419
|
* @param {string} appName - Application name
|
|
425
|
-
* @returns {Object} App type info
|
|
420
|
+
* @returns {Object|null} App type info or null if no config found
|
|
426
421
|
*/
|
|
427
422
|
function checkBuilderFolder(appName) {
|
|
428
423
|
const builderPath = getBuilderPath(appName);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
424
|
+
if (!fs.existsSync(builderPath)) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
let variables;
|
|
428
|
+
try {
|
|
429
|
+
const configPath = resolveApplicationConfigPath(builderPath);
|
|
430
|
+
variables = loadConfigFile(configPath);
|
|
431
|
+
} catch {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
432
434
|
if (variables) {
|
|
433
435
|
const isExternal = isExternalAppType(variables);
|
|
434
436
|
return {
|
|
@@ -438,51 +440,27 @@ function checkBuilderFolder(appName) {
|
|
|
438
440
|
baseDir: 'builder'
|
|
439
441
|
};
|
|
440
442
|
}
|
|
441
|
-
|
|
442
|
-
// Default to regular app in builder folder
|
|
443
|
-
return {
|
|
444
|
-
isExternal: false,
|
|
445
|
-
appPath: builderPath,
|
|
446
|
-
appType: 'regular',
|
|
447
|
-
baseDir: 'builder'
|
|
448
|
-
};
|
|
443
|
+
return null;
|
|
449
444
|
}
|
|
450
|
-
|
|
451
445
|
/**
|
|
452
|
-
* Detects if an app is external type by checking
|
|
453
|
-
*
|
|
446
|
+
* Detects if an app is external type by checking application config.
|
|
447
|
+
* Resolution order: integration/ first, then builder/; if neither exists, throws.
|
|
448
|
+
* No CLI flag overrides this order.
|
|
454
449
|
*
|
|
455
450
|
* @param {string} appName - Application name
|
|
456
|
-
* @param {Object} [options] - Detection options
|
|
457
|
-
* @param {string} [options.type] - Forced application type (external)
|
|
451
|
+
* @param {Object} [options] - Detection options (reserved; options.type is ignored)
|
|
458
452
|
* @returns {Promise<{isExternal: boolean, appPath: string, appType: string, baseDir?: string}>}
|
|
453
|
+
* @throws {Error} When app not found in integration/ or builder/
|
|
459
454
|
*/
|
|
460
|
-
async function detectAppType(appName,
|
|
455
|
+
async function detectAppType(appName, _options = {}) {
|
|
461
456
|
if (!appName || typeof appName !== 'string') {
|
|
462
457
|
throw new Error('App name is required and must be a string');
|
|
463
458
|
}
|
|
464
|
-
|
|
465
|
-
if (options.type === 'external') {
|
|
466
|
-
const integrationPath = getIntegrationPath(appName);
|
|
467
|
-
if (!fs.existsSync(integrationPath)) {
|
|
468
|
-
throw new Error(`External system not found in integration/${appName}`);
|
|
469
|
-
}
|
|
470
|
-
return {
|
|
471
|
-
isExternal: true,
|
|
472
|
-
appPath: integrationPath,
|
|
473
|
-
appType: 'external',
|
|
474
|
-
baseDir: 'integration'
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Check integration folder first (new structure)
|
|
479
459
|
const integrationResult = checkIntegrationFolder(appName);
|
|
480
|
-
if (integrationResult)
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// Check builder folder (backward compatibility)
|
|
485
|
-
return checkBuilderFolder(appName);
|
|
460
|
+
if (integrationResult) return integrationResult;
|
|
461
|
+
const builderResult = checkBuilderFolder(appName);
|
|
462
|
+
if (builderResult) return builderResult;
|
|
463
|
+
throw new Error(`App '${appName}' not found in integration/${appName} or builder/${appName}`);
|
|
486
464
|
}
|
|
487
465
|
module.exports = {
|
|
488
466
|
getAifabrixHome,
|
|
@@ -494,6 +472,7 @@ module.exports = {
|
|
|
494
472
|
getIntegrationPath,
|
|
495
473
|
getBuilderPath,
|
|
496
474
|
getDeployJsonPath,
|
|
475
|
+
resolveApplicationConfigPath,
|
|
497
476
|
detectAppType,
|
|
498
477
|
clearProjectRootCache
|
|
499
478
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI Fabrix Builder - Centralized Port Resolution
|
|
3
3
|
*
|
|
4
|
-
* Single source of truth for resolving application port from
|
|
4
|
+
* Single source of truth for resolving application port from application config.
|
|
5
5
|
* Use getContainerPort for container/Docker/deployment/registration; use getLocalPort
|
|
6
6
|
* for local .env and dev-id–adjusted host port.
|
|
7
7
|
*
|
|
@@ -20,7 +20,7 @@ const yaml = require('js-yaml');
|
|
|
20
20
|
* Precedence: build.containerPort → port → defaultPort.
|
|
21
21
|
* Used for: Dockerfile, container .env PORT, compose, deployment, app register, variable-transformer, builders, secrets-utils.
|
|
22
22
|
*
|
|
23
|
-
* @param {Object} variables - Parsed
|
|
23
|
+
* @param {Object} variables - Parsed application config (or subset with build, port)
|
|
24
24
|
* @param {number} [defaultPort=3000] - Default when neither build.containerPort nor port is set
|
|
25
25
|
* @returns {number} Resolved container port
|
|
26
26
|
*/
|
|
@@ -34,7 +34,7 @@ function getContainerPort(variables, defaultPort = 3000) {
|
|
|
34
34
|
* Precedence: build.localPort (if number and > 0) → port → defaultPort.
|
|
35
35
|
* Used for: env-copy, env-ports, and as base for getLocalPortFromPath (secrets-helpers).
|
|
36
36
|
*
|
|
37
|
-
* @param {Object} variables - Parsed
|
|
37
|
+
* @param {Object} variables - Parsed application config
|
|
38
38
|
* @param {number} [defaultPort=3000] - Default when neither build.localPort nor port is set
|
|
39
39
|
* @returns {number} Resolved local port
|
|
40
40
|
*/
|
|
@@ -50,7 +50,7 @@ function getLocalPort(variables, defaultPort = 3000) {
|
|
|
50
50
|
/**
|
|
51
51
|
* Load variables from path. Returns null if path missing, not found, or parse error.
|
|
52
52
|
*
|
|
53
|
-
* @param {string} variablesPath - Path to
|
|
53
|
+
* @param {string} variablesPath - Path to application config
|
|
54
54
|
* @returns {Object|null} Parsed variables or null
|
|
55
55
|
*/
|
|
56
56
|
function loadVariablesFromPath(variablesPath) {
|
|
@@ -66,10 +66,10 @@ function loadVariablesFromPath(variablesPath) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* Resolve container port from
|
|
69
|
+
* Resolve container port from application config path.
|
|
70
70
|
* Returns null when file is missing or neither build.containerPort nor port is set (for chaining with other sources).
|
|
71
71
|
*
|
|
72
|
-
* @param {string} variablesPath - Path to
|
|
72
|
+
* @param {string} variablesPath - Path to application config
|
|
73
73
|
* @returns {number|null} Container port or null
|
|
74
74
|
*/
|
|
75
75
|
function getContainerPortFromPath(variablesPath) {
|
|
@@ -82,11 +82,11 @@ function getContainerPortFromPath(variablesPath) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Resolve local port from
|
|
85
|
+
* Resolve local port from application config path.
|
|
86
86
|
* Matches legacy getPortFromVariablesFile: build.localPort (if number and > 0) else variables.port or null.
|
|
87
87
|
* Returns null when file is missing or neither is set (for calculateAppPort chain).
|
|
88
88
|
*
|
|
89
|
-
* @param {string} variablesPath - Path to
|
|
89
|
+
* @param {string} variablesPath - Path to application config
|
|
90
90
|
* @returns {number|null} Local port or null
|
|
91
91
|
*/
|
|
92
92
|
function getLocalPortFromPath(variablesPath) {
|
|
@@ -290,6 +290,27 @@ function tryDetectionMethods(parsed, filePath) {
|
|
|
290
290
|
return null;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Detects schema type from already-parsed content (works with YAML or JSON).
|
|
295
|
+
* Use this when the file was parsed via loadConfigFile or similar.
|
|
296
|
+
*
|
|
297
|
+
* @function detectSchemaTypeFromParsed
|
|
298
|
+
* @param {Object} parsed - Parsed config object (from YAML or JSON)
|
|
299
|
+
* @param {string} filePath - File path (used for filename-based detection)
|
|
300
|
+
* @returns {string} 'application' | 'external-system' | 'external-datasource'
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* const parsed = yaml.load(content);
|
|
304
|
+
* const type = detectSchemaTypeFromParsed(parsed, '/path/to/application.yaml');
|
|
305
|
+
*/
|
|
306
|
+
function detectSchemaTypeFromParsed(parsed, filePath) {
|
|
307
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
308
|
+
return 'application';
|
|
309
|
+
}
|
|
310
|
+
const detected = tryDetectionMethods(parsed, filePath);
|
|
311
|
+
return detected || 'application';
|
|
312
|
+
}
|
|
313
|
+
|
|
293
314
|
function detectSchemaType(filePath, content) {
|
|
294
315
|
const parsed = readAndParseFileContent(filePath, content);
|
|
295
316
|
const detectedType = tryDetectionMethods(parsed, filePath);
|
|
@@ -300,6 +321,7 @@ module.exports = {
|
|
|
300
321
|
loadExternalSystemSchema,
|
|
301
322
|
loadExternalDataSourceSchema,
|
|
302
323
|
detectSchemaType,
|
|
324
|
+
detectSchemaTypeFromParsed,
|
|
303
325
|
resetValidators
|
|
304
326
|
};
|
|
305
327
|
|
|
@@ -11,43 +11,40 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
14
|
const { detectAppType } = require('./paths');
|
|
15
|
+
const { resolveApplicationConfigPath } = require('./app-config-resolver');
|
|
16
|
+
const { loadConfigFile } = require('./config-format');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* Resolves schemaBasePath from application
|
|
19
|
+
* Resolves schemaBasePath from application config
|
|
19
20
|
* Supports both absolute and relative paths
|
|
20
21
|
*
|
|
21
22
|
* @async
|
|
22
23
|
* @function resolveSchemaBasePath
|
|
23
24
|
* @param {string} appName - Application name
|
|
24
25
|
* @returns {Promise<string>} Resolved absolute path to schema base directory
|
|
25
|
-
* @throws {Error} If
|
|
26
|
+
* @throws {Error} If application config not found, externalIntegration missing, or path invalid
|
|
26
27
|
*
|
|
27
28
|
* @example
|
|
28
29
|
* const basePath = await resolveSchemaBasePath('myapp');
|
|
29
30
|
* // Returns: '/path/to/builder/myapp/schemas'
|
|
30
31
|
*/
|
|
31
32
|
/**
|
|
32
|
-
* Loads and validates
|
|
33
|
+
* Loads and validates application config for schema resolution
|
|
33
34
|
* @async
|
|
34
35
|
* @function loadAndValidateVariablesForSchema
|
|
35
36
|
* @param {string} appName - Application name
|
|
36
37
|
* @param {string} appPath - Application path
|
|
37
|
-
* @returns {Promise<Object>} Variables object
|
|
38
|
+
* @returns {Promise<{ variables: Object, configPath: string }>} Variables object and config path
|
|
38
39
|
* @throws {Error} If file not found or invalid
|
|
39
40
|
*/
|
|
40
41
|
async function loadAndValidateVariablesForSchema(appName, appPath) {
|
|
41
|
-
const
|
|
42
|
-
if (!fs.existsSync(variablesPath)) {
|
|
43
|
-
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
42
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
47
43
|
try {
|
|
48
|
-
|
|
44
|
+
const variables = loadConfigFile(configPath);
|
|
45
|
+
return { variables, configPath };
|
|
49
46
|
} catch (error) {
|
|
50
|
-
throw new Error(`
|
|
47
|
+
throw new Error(`Application config error: ${error.message}`);
|
|
51
48
|
}
|
|
52
49
|
}
|
|
53
50
|
|
|
@@ -61,7 +58,7 @@ async function loadAndValidateVariablesForSchema(appName, appPath) {
|
|
|
61
58
|
*/
|
|
62
59
|
function validateExternalIntegrationBlock(variables, appName) {
|
|
63
60
|
if (!variables.externalIntegration) {
|
|
64
|
-
throw new Error(`externalIntegration block not found in
|
|
61
|
+
throw new Error(`externalIntegration block not found in application config for app: ${appName}`);
|
|
65
62
|
}
|
|
66
63
|
if (!variables.externalIntegration.schemaBasePath) {
|
|
67
64
|
throw new Error(`schemaBasePath not found in externalIntegration block for app: ${appName}`);
|
|
@@ -73,7 +70,7 @@ function validateExternalIntegrationBlock(variables, appName) {
|
|
|
73
70
|
* Resolves and validates schema base path
|
|
74
71
|
* @function resolveAndValidateSchemaPath
|
|
75
72
|
* @param {string} schemaBasePath - Schema base path from config
|
|
76
|
-
* @param {string} variablesPath - Path to
|
|
73
|
+
* @param {string} variablesPath - Path to application config
|
|
77
74
|
* @returns {string} Resolved and validated path
|
|
78
75
|
* @throws {Error} If path is invalid
|
|
79
76
|
*/
|
|
@@ -95,17 +92,16 @@ function resolveAndValidateSchemaPath(schemaBasePath, variablesPath) {
|
|
|
95
92
|
return resolvedPath;
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
async function resolveSchemaBasePath(appName) {
|
|
95
|
+
async function resolveSchemaBasePath(appName, options = {}) {
|
|
99
96
|
if (!appName || typeof appName !== 'string') {
|
|
100
97
|
throw new Error('App name is required and must be a string');
|
|
101
98
|
}
|
|
102
99
|
|
|
103
|
-
const { appPath } = await detectAppType(appName);
|
|
104
|
-
const variables = await loadAndValidateVariablesForSchema(appName, appPath);
|
|
100
|
+
const { appPath } = await detectAppType(appName, options);
|
|
101
|
+
const { variables, configPath } = await loadAndValidateVariablesForSchema(appName, appPath);
|
|
105
102
|
const schemaBasePath = validateExternalIntegrationBlock(variables, appName);
|
|
106
|
-
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
107
103
|
|
|
108
|
-
return resolveAndValidateSchemaPath(schemaBasePath,
|
|
104
|
+
return resolveAndValidateSchemaPath(schemaBasePath, configPath);
|
|
109
105
|
}
|
|
110
106
|
|
|
111
107
|
/**
|
|
@@ -180,7 +176,7 @@ function resolveDatasourceFiles(schemaBasePath, datasourceFiles) {
|
|
|
180
176
|
}
|
|
181
177
|
|
|
182
178
|
/**
|
|
183
|
-
* Loads and validates
|
|
179
|
+
* Loads and validates application config
|
|
184
180
|
* @async
|
|
185
181
|
* @function loadAndValidateVariables
|
|
186
182
|
* @param {string} appPath - Application path
|
|
@@ -188,33 +184,27 @@ function resolveDatasourceFiles(schemaBasePath, datasourceFiles) {
|
|
|
188
184
|
* @throws {Error} If file not found or invalid
|
|
189
185
|
*/
|
|
190
186
|
async function loadAndValidateVariables(appPath) {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
if (!fs.existsSync(variablesPath)) {
|
|
194
|
-
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
187
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
198
188
|
try {
|
|
199
|
-
return
|
|
189
|
+
return loadConfigFile(configPath);
|
|
200
190
|
} catch (error) {
|
|
201
|
-
throw new Error(`
|
|
191
|
+
throw new Error(`Application config: ${error.message}`);
|
|
202
192
|
}
|
|
203
193
|
}
|
|
204
194
|
|
|
205
|
-
async function resolveExternalFiles(appName) {
|
|
195
|
+
async function resolveExternalFiles(appName, options = {}) {
|
|
206
196
|
if (!appName || typeof appName !== 'string') {
|
|
207
197
|
throw new Error('App name is required and must be a string');
|
|
208
198
|
}
|
|
209
199
|
|
|
210
|
-
const { appPath } = await detectAppType(appName);
|
|
200
|
+
const { appPath } = await detectAppType(appName, options);
|
|
211
201
|
const variables = await loadAndValidateVariables(appPath);
|
|
212
202
|
|
|
213
203
|
if (!variables.externalIntegration) {
|
|
214
204
|
return [];
|
|
215
205
|
}
|
|
216
206
|
|
|
217
|
-
const schemaBasePath = await resolveSchemaBasePath(appName);
|
|
207
|
+
const schemaBasePath = await resolveSchemaBasePath(appName, options);
|
|
218
208
|
const systemFiles = resolveSystemFiles(schemaBasePath, variables.externalIntegration.systems);
|
|
219
209
|
const datasourceFiles = resolveDatasourceFiles(schemaBasePath, variables.externalIntegration.dataSources);
|
|
220
210
|
|
|
@@ -166,9 +166,9 @@ function getPortFromLocalEnv(localEnv) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
* Gets port from
|
|
169
|
+
* Gets port from application config file (build.localPort if positive, else port). Uses port-resolver.
|
|
170
170
|
* @function getPortFromVariablesFile
|
|
171
|
-
* @param {string} variablesPath - Path to
|
|
171
|
+
* @param {string} variablesPath - Path to application config
|
|
172
172
|
* @returns {number|null} Port value or null
|
|
173
173
|
*/
|
|
174
174
|
function getPortFromVariablesFile(variablesPath) {
|
|
@@ -199,10 +199,10 @@ function applyDeveloperIdAdjustment(baseAppPort, devIdNum) {
|
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
201
|
* Calculate application port following override chain and developer-id adjustment
|
|
202
|
-
* Override chain: env-config.yaml → config.yaml →
|
|
202
|
+
* Override chain: env-config.yaml → config.yaml → application.yaml build.localPort → application.yaml port
|
|
203
203
|
* @async
|
|
204
204
|
* @function calculateAppPort
|
|
205
|
-
* @param {string} [variablesPath] - Path to
|
|
205
|
+
* @param {string} [variablesPath] - Path to application config
|
|
206
206
|
* @param {Object} localEnv - Local environment config from env-config.yaml and config.yaml
|
|
207
207
|
* @param {string} envContent - Environment content for fallback
|
|
208
208
|
* @param {number} devIdNum - Developer ID number
|
|
@@ -212,7 +212,7 @@ async function calculateAppPort(variablesPath, localEnv, envContent, devIdNum) {
|
|
|
212
212
|
// Start with env-config value
|
|
213
213
|
let baseAppPort = getPortFromLocalEnv(localEnv);
|
|
214
214
|
|
|
215
|
-
// Override with
|
|
215
|
+
// Override with application config → build.localPort (strongest)
|
|
216
216
|
const variablesPort = getPortFromVariablesFile(variablesPath);
|
|
217
217
|
if (variablesPort !== null) {
|
|
218
218
|
baseAppPort = variablesPort;
|
|
@@ -249,11 +249,11 @@ function updateLocalhostUrls(content, baseAppPort, appPort) {
|
|
|
249
249
|
/**
|
|
250
250
|
* Adjust infra-related ports in resolved .env content for local environment
|
|
251
251
|
* Only handles PORT variable (other ports handled by interpolation)
|
|
252
|
-
* Follows flow: getEnvHosts() → config.yaml override →
|
|
252
|
+
* Follows flow: getEnvHosts() → config.yaml override → application config override → developer-id adjustment
|
|
253
253
|
* @async
|
|
254
254
|
* @function adjustLocalEnvPortsInContent
|
|
255
255
|
* @param {string} envContent - Resolved .env content
|
|
256
|
-
* @param {string} [variablesPath] - Path to
|
|
256
|
+
* @param {string} [variablesPath] - Path to application config (to read build.localPort)
|
|
257
257
|
* @returns {Promise<string>} Updated content with local ports
|
|
258
258
|
*/
|
|
259
259
|
/**
|