@aifabrix/builder 2.39.3 ā 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 +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 +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 +28 -20
- 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/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 +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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Always local deployment: registers or rotates dataplane app in dev, sends
|
|
5
5
|
* deployment manifest to Miso Controller, then runs the dataplane app locally
|
|
6
|
-
* (same as aifabrix deploy dataplane --
|
|
6
|
+
* (same as aifabrix deploy dataplane --local). If app is already
|
|
7
7
|
* registered, uses rotate-secret; otherwise registers.
|
|
8
8
|
*
|
|
9
9
|
* @fileoverview up-dataplane command implementation
|
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
* @version 2.0.0
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const yaml = require('js-yaml');
|
|
17
14
|
const chalk = require('chalk');
|
|
15
|
+
const pathsUtil = require('../utils/paths');
|
|
16
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
18
17
|
const logger = require('../utils/logger');
|
|
19
18
|
const config = require('../core/config');
|
|
20
19
|
const { checkAuthentication } = require('../utils/app-register-auth');
|
|
@@ -47,20 +46,22 @@ async function registerOrRotateDataplane(options, controllerUrl, environmentKey,
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
|
-
* Build full image ref from registry and dataplane
|
|
49
|
+
* Build full image ref from registry and dataplane config (registry/name:tag)
|
|
51
50
|
* @param {string} registry - Registry URL
|
|
52
51
|
* @returns {string|undefined} Full image reference or undefined
|
|
53
52
|
*/
|
|
54
53
|
function buildDataplaneImageRef(registry) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
try {
|
|
55
|
+
const builderPath = pathsUtil.getBuilderPath('dataplane');
|
|
56
|
+
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
57
|
+
const variables = loadConfigFile(configPath);
|
|
58
|
+
const name = variables?.image?.name || variables?.app?.key || 'dataplane';
|
|
59
|
+
const tag = variables?.image?.tag || 'latest';
|
|
60
|
+
const base = (registry || '').replace(/\/+$/, '');
|
|
61
|
+
return base ? `${base}/${name}:${tag}` : undefined;
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
/**
|
package/lib/commands/up-miso.js
CHANGED
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
* @version 2.0.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
12
|
const chalk = require('chalk');
|
|
13
|
+
const pathsUtil = require('../utils/paths');
|
|
14
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
16
15
|
const logger = require('../utils/logger');
|
|
17
16
|
const config = require('../core/config');
|
|
18
17
|
const secrets = require('../core/secrets');
|
|
@@ -21,7 +20,7 @@ const app = require('../app');
|
|
|
21
20
|
const { saveLocalSecret } = require('../utils/local-secrets');
|
|
22
21
|
const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly, validateEnvOutputPathFolderOrNull } = require('./up-common');
|
|
23
22
|
|
|
24
|
-
/** Keycloak base port (from templates/applications/keycloak
|
|
23
|
+
/** Keycloak base port (from templates/applications/keycloak application config) */
|
|
25
24
|
const KEYCLOAK_BASE_PORT = 8082;
|
|
26
25
|
/** Miso controller base port (dev-config app base) */
|
|
27
26
|
const MISO_BASE_PORT = 3000;
|
|
@@ -46,21 +45,23 @@ function parseImageOptions(imageOpts) {
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
/**
|
|
49
|
-
* Build full image ref from registry and app
|
|
48
|
+
* Build full image ref from registry and app config (registry/name:tag)
|
|
50
49
|
* @param {string} appName - keycloak or miso-controller
|
|
51
50
|
* @param {string} registry - Registry URL
|
|
52
51
|
* @returns {string} Full image reference
|
|
53
52
|
*/
|
|
54
53
|
function buildImageRefFromRegistry(appName, registry) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
try {
|
|
55
|
+
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
56
|
+
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
57
|
+
const variables = loadConfigFile(configPath);
|
|
58
|
+
const name = variables?.image?.name || variables?.app?.key || appName;
|
|
59
|
+
const tag = variables?.image?.tag || 'latest';
|
|
60
|
+
const base = (registry || '').replace(/\/+$/, '');
|
|
61
|
+
return base ? `${base}/${name}:${tag}` : undefined;
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
/**
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload external system to dataplane (upload ā validate ā publish).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Upload command handler for aifabrix upload <system-key>
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
12
|
+
const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
|
|
13
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
14
|
+
const { validateExternalSystemComplete } = require('../validation/validate');
|
|
15
|
+
const { displayValidationResults } = require('../validation/validate-display');
|
|
16
|
+
const { generateControllerManifest } = require('../generator/external-controller-manifest');
|
|
17
|
+
const {
|
|
18
|
+
uploadApplicationViaPipeline,
|
|
19
|
+
validateUploadViaPipeline,
|
|
20
|
+
publishUploadViaPipeline
|
|
21
|
+
} = require('../api/pipeline.api');
|
|
22
|
+
const { formatApiError } = require('../utils/api-error-handler');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validates system-key format (same as download).
|
|
26
|
+
* @param {string} systemKey - System key
|
|
27
|
+
* @throws {Error} If invalid
|
|
28
|
+
*/
|
|
29
|
+
function validateSystemKeyFormat(systemKey) {
|
|
30
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
31
|
+
throw new Error('System key is required and must be a string');
|
|
32
|
+
}
|
|
33
|
+
if (!/^[a-z0-9-_]+$/.test(systemKey)) {
|
|
34
|
+
throw new Error('System key must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds pipeline upload payload from controller manifest.
|
|
40
|
+
* Payload: { version, application, dataSources }; application = system with RBAC.
|
|
41
|
+
* @param {Object} manifest - Controller manifest from generateControllerManifest
|
|
42
|
+
* @returns {Object} { version, application, dataSources }
|
|
43
|
+
*/
|
|
44
|
+
function buildUploadPayload(manifest) {
|
|
45
|
+
return {
|
|
46
|
+
version: manifest.version || '1.0.0',
|
|
47
|
+
application: manifest.system,
|
|
48
|
+
dataSources: manifest.dataSources || []
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolves dataplane URL and auth (same pattern as download).
|
|
54
|
+
* @param {string} systemKey - System key
|
|
55
|
+
* @param {Object} options - Options with optional dataplane override
|
|
56
|
+
* @returns {Promise<{ dataplaneUrl: string, authConfig: Object, environment: string }>}
|
|
57
|
+
*/
|
|
58
|
+
async function resolveDataplaneAndAuth(systemKey, options) {
|
|
59
|
+
const { resolveEnvironment } = require('../core/config');
|
|
60
|
+
const environment = await resolveEnvironment();
|
|
61
|
+
const controllerUrl = await resolveControllerUrl();
|
|
62
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
63
|
+
|
|
64
|
+
if (!authConfig.token && !authConfig.clientId) {
|
|
65
|
+
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <system-key>" first.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let dataplaneUrl;
|
|
69
|
+
if (options.dataplane) {
|
|
70
|
+
dataplaneUrl = options.dataplane.replace(/\/$/, '');
|
|
71
|
+
} else {
|
|
72
|
+
logger.log(chalk.blue('Resolving dataplane URL...'));
|
|
73
|
+
dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { dataplaneUrl, authConfig, environment };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Runs upload ā validate ā publish on the dataplane.
|
|
81
|
+
* @param {string} dataplaneUrl - Dataplane base URL
|
|
82
|
+
* @param {Object} authConfig - Auth config
|
|
83
|
+
* @param {Object} payload - { version, application, dataSources }
|
|
84
|
+
* @returns {Promise<{ uploadId: string }>}
|
|
85
|
+
*/
|
|
86
|
+
async function runUploadValidatePublish(dataplaneUrl, authConfig, payload) {
|
|
87
|
+
const uploadRes = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, payload);
|
|
88
|
+
const uploadId = uploadRes?.data?.uploadId ?? uploadRes?.data?.id ?? uploadRes?.uploadId;
|
|
89
|
+
if (!uploadId) {
|
|
90
|
+
const msg = uploadRes?.success === false
|
|
91
|
+
? formatApiError(uploadRes, dataplaneUrl)
|
|
92
|
+
: 'Upload did not return an upload ID';
|
|
93
|
+
throw new Error(msg);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const validateRes = await validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
|
|
97
|
+
if (validateRes?.success === false) {
|
|
98
|
+
const msg = formatApiError(validateRes, dataplaneUrl);
|
|
99
|
+
throw new Error(`Upload validation failed: ${msg}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
|
|
103
|
+
return { uploadId };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Throws if validation result is invalid (displays results first).
|
|
108
|
+
* @param {Object} validationResult - Result from validateExternalSystemComplete
|
|
109
|
+
* @throws {Error} If validationResult.valid is false
|
|
110
|
+
*/
|
|
111
|
+
function throwIfValidationFailed(validationResult) {
|
|
112
|
+
if (!validationResult.valid) {
|
|
113
|
+
displayValidationResults(validationResult);
|
|
114
|
+
throw new Error('Validation failed. Fix errors before uploading.');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Uploads external system to dataplane (upload ā validate ā publish). No controller deploy.
|
|
120
|
+
* @param {string} systemKey - External system key (integration/<system-key>/)
|
|
121
|
+
* @param {Object} [options] - Options
|
|
122
|
+
* @param {boolean} [options.dryRun] - Validate and build payload only; no API calls
|
|
123
|
+
* @param {string} [options.dataplane] - Override dataplane URL
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
* @throws {Error} If validation or API calls fail
|
|
126
|
+
*/
|
|
127
|
+
async function uploadExternalSystem(systemKey, options = {}) {
|
|
128
|
+
validateSystemKeyFormat(systemKey);
|
|
129
|
+
|
|
130
|
+
logger.log(chalk.blue(`\nUploading external system to dataplane: ${systemKey}`));
|
|
131
|
+
|
|
132
|
+
const validationResult = await validateExternalSystemComplete(systemKey, { type: 'external' });
|
|
133
|
+
throwIfValidationFailed(validationResult);
|
|
134
|
+
logger.log(chalk.green('Validation passed.'));
|
|
135
|
+
|
|
136
|
+
const manifest = await generateControllerManifest(systemKey, { type: 'external' });
|
|
137
|
+
const payload = buildUploadPayload(manifest);
|
|
138
|
+
|
|
139
|
+
if (options.dryRun) {
|
|
140
|
+
logger.log(chalk.yellow('Dry run: would upload payload (no API calls).'));
|
|
141
|
+
logger.log(chalk.gray(` System: ${manifest.key}, version: ${payload.version}, datasources: ${payload.dataSources.length}`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { dataplaneUrl, authConfig, environment } = await resolveDataplaneAndAuth(systemKey, options);
|
|
146
|
+
requireBearerForDataplanePipeline(authConfig);
|
|
147
|
+
logger.log(chalk.blue(`Dataplane: ${dataplaneUrl}`));
|
|
148
|
+
|
|
149
|
+
await runUploadValidatePublish(dataplaneUrl, authConfig, payload);
|
|
150
|
+
|
|
151
|
+
logger.log(chalk.green('\nUpload validated and published to dataplane.'));
|
|
152
|
+
logger.log(chalk.blue(`Environment: ${environment}`));
|
|
153
|
+
logger.log(chalk.blue(`System: ${systemKey}`));
|
|
154
|
+
logger.log(chalk.blue(`Dataplane: ${dataplaneUrl}`));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = {
|
|
158
|
+
uploadExternalSystem,
|
|
159
|
+
buildUploadPayload,
|
|
160
|
+
resolveDataplaneAndAuth,
|
|
161
|
+
runUploadValidatePublish,
|
|
162
|
+
validateSystemKeyFormat
|
|
163
|
+
};
|
|
@@ -339,7 +339,7 @@ async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfi
|
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
/**
|
|
342
|
-
* Fetches deployment docs and writes README.md when
|
|
342
|
+
* Fetches deployment docs and writes README.md when application config and deploy JSON are available.
|
|
343
343
|
* @async
|
|
344
344
|
* @param {string} appPath - Application path
|
|
345
345
|
* @param {string} appName - Application name
|
|
@@ -348,12 +348,13 @@ async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfi
|
|
|
348
348
|
* @param {string} systemKey - System key
|
|
349
349
|
*/
|
|
350
350
|
async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl, authConfig, systemKey) {
|
|
351
|
-
const
|
|
351
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
352
352
|
const deployPath = path.join(appPath, `${appName}-deploy.json`);
|
|
353
353
|
let variablesYaml = null;
|
|
354
354
|
let deployJson = null;
|
|
355
355
|
try {
|
|
356
|
-
|
|
356
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
357
|
+
variablesYaml = await fs.readFile(configPath, 'utf8');
|
|
357
358
|
} catch {
|
|
358
359
|
// optional
|
|
359
360
|
}
|
|
@@ -372,7 +373,7 @@ async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl,
|
|
|
372
373
|
if (content && typeof content === 'string') {
|
|
373
374
|
const readmePath = path.join(appPath, 'README.md');
|
|
374
375
|
await fs.writeFile(readmePath, content, 'utf8');
|
|
375
|
-
logger.log(chalk.gray(' Updated README.md from deployment-docs API (
|
|
376
|
+
logger.log(chalk.gray(' Updated README.md from deployment-docs API (application config + deploy JSON).'));
|
|
376
377
|
}
|
|
377
378
|
}
|
|
378
379
|
|
package/lib/core/diff.js
CHANGED
|
@@ -13,6 +13,10 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const chalk = require('chalk');
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
17
|
+
const { detectSchemaTypeFromParsed, loadExternalSystemSchema, loadExternalDataSourceSchema } = require('../utils/schema-loader');
|
|
18
|
+
const { validateObjectAgainstApplicationSchema } = require('../validation/validator');
|
|
19
|
+
const { formatValidationErrors } = require('../utils/error-formatter');
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Handle added field in comparison
|
|
@@ -169,19 +173,22 @@ function identifyBreakingChanges(comparison) {
|
|
|
169
173
|
}
|
|
170
174
|
|
|
171
175
|
/**
|
|
172
|
-
* Compares two configuration files
|
|
173
|
-
*
|
|
176
|
+
* Compares two configuration files.
|
|
177
|
+
* Both files must be the same config type (app, system, or datasource).
|
|
178
|
+
* By default validates both against their schema; pass { validate: false } to skip.
|
|
174
179
|
*
|
|
175
180
|
* @async
|
|
176
181
|
* @function compareFiles
|
|
177
182
|
* @param {string} file1 - Path to first file
|
|
178
183
|
* @param {string} file2 - Path to second file
|
|
184
|
+
* @param {Object} [options] - Options
|
|
185
|
+
* @param {boolean} [options.validate=true] - If true, validate both files against their schema
|
|
179
186
|
* @returns {Promise<Object>} Comparison result with differences
|
|
180
|
-
* @throws {Error} If files cannot be read or
|
|
187
|
+
* @throws {Error} If files cannot be read, parsed, types differ, or validation fails
|
|
181
188
|
*
|
|
182
189
|
* @example
|
|
183
190
|
* const result = await compareFiles('./old.json', './new.json');
|
|
184
|
-
*
|
|
191
|
+
* const resultNoValidate = await compareFiles('./a.yaml', './b.yaml', { validate: false });
|
|
185
192
|
*/
|
|
186
193
|
/**
|
|
187
194
|
* Validates file paths
|
|
@@ -206,21 +213,70 @@ function validateFilePaths(file1, file2) {
|
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
/**
|
|
209
|
-
* Reads and parses a JSON
|
|
216
|
+
* Reads and parses a config file (JSON or YAML by extension: .json, .yaml, .yml).
|
|
210
217
|
* @function readAndParseFile
|
|
211
218
|
* @param {string} filePath - File path
|
|
212
|
-
* @returns {Object} Parsed
|
|
219
|
+
* @returns {Object} Parsed object
|
|
213
220
|
* @throws {Error} If file cannot be read or parsed
|
|
214
221
|
*/
|
|
215
222
|
function readAndParseFile(filePath) {
|
|
216
223
|
try {
|
|
217
|
-
|
|
218
|
-
return JSON.parse(content);
|
|
224
|
+
return loadConfigFile(filePath);
|
|
219
225
|
} catch (error) {
|
|
220
226
|
throw new Error(`Failed to parse ${filePath}: ${error.message}`);
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Maps schema type to user-facing label (app | system | datasource).
|
|
232
|
+
* @param {string} schemaType - 'application' | 'external-system' | 'external-datasource'
|
|
233
|
+
* @returns {string} 'app' | 'system' | 'datasource'
|
|
234
|
+
*/
|
|
235
|
+
function toUserFacingType(schemaType) {
|
|
236
|
+
const map = {
|
|
237
|
+
application: 'app',
|
|
238
|
+
'external-system': 'system',
|
|
239
|
+
'external-datasource': 'datasource'
|
|
240
|
+
};
|
|
241
|
+
return map[schemaType] || 'app';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Runs external schema validator and returns error messages or null.
|
|
246
|
+
* @param {Function} validateFn - AJV validate function
|
|
247
|
+
* @param {Object} parsed - Parsed config object
|
|
248
|
+
* @returns {string[]|null} Error messages or null if valid
|
|
249
|
+
*/
|
|
250
|
+
function getValidationErrors(validateFn, parsed) {
|
|
251
|
+
const valid = validateFn(parsed);
|
|
252
|
+
if (valid) return null;
|
|
253
|
+
return formatValidationErrors(validateFn.errors);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Validates parsed object against the schema for the given type.
|
|
258
|
+
* @param {Object} parsed - Parsed config object
|
|
259
|
+
* @param {string} schemaType - 'application' | 'external-system' | 'external-datasource'
|
|
260
|
+
* @param {string} filePath - File path (for error messages)
|
|
261
|
+
* @throws {Error} If validation fails
|
|
262
|
+
*/
|
|
263
|
+
function validateParsedForType(parsed, schemaType, filePath) {
|
|
264
|
+
let messages = [];
|
|
265
|
+
if (schemaType === 'application') {
|
|
266
|
+
const result = validateObjectAgainstApplicationSchema(parsed);
|
|
267
|
+
if (!result.valid) messages = result.errors;
|
|
268
|
+
} else if (schemaType === 'external-system') {
|
|
269
|
+
messages = getValidationErrors(loadExternalSystemSchema(), parsed) || [];
|
|
270
|
+
} else if (schemaType === 'external-datasource') {
|
|
271
|
+
messages = getValidationErrors(loadExternalDataSourceSchema(), parsed) || [];
|
|
272
|
+
} else {
|
|
273
|
+
throw new Error(`Unknown schema type: ${schemaType}`);
|
|
274
|
+
}
|
|
275
|
+
if (messages.length > 0) {
|
|
276
|
+
throw new Error(`Validation failed for ${filePath}: ${messages.join('; ')}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
224
280
|
/**
|
|
225
281
|
* Extracts version from parsed object
|
|
226
282
|
* @function extractVersion
|
|
@@ -267,12 +323,31 @@ function buildComparisonResult(comparison, parsed1, parsed2, file1, file2) {
|
|
|
267
323
|
};
|
|
268
324
|
}
|
|
269
325
|
|
|
270
|
-
async function compareFiles(file1, file2) {
|
|
326
|
+
async function compareFiles(file1, file2, options = {}) {
|
|
327
|
+
const shouldValidate = options.validate !== false;
|
|
328
|
+
|
|
271
329
|
validateFilePaths(file1, file2);
|
|
272
330
|
|
|
273
331
|
const parsed1 = readAndParseFile(file1);
|
|
274
332
|
const parsed2 = readAndParseFile(file2);
|
|
275
333
|
|
|
334
|
+
const type1 = detectSchemaTypeFromParsed(parsed1, file1);
|
|
335
|
+
const type2 = detectSchemaTypeFromParsed(parsed2, file2);
|
|
336
|
+
const userType1 = toUserFacingType(type1);
|
|
337
|
+
const userType2 = toUserFacingType(type2);
|
|
338
|
+
|
|
339
|
+
if (userType1 !== userType2) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`Type mismatch: ${file1} is ${userType1} config and ${file2} is ${userType2} config. ` +
|
|
342
|
+
'Both files must be the same type (app, system, or datasource).'
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (shouldValidate) {
|
|
347
|
+
validateParsedForType(parsed1, type1, file1);
|
|
348
|
+
validateParsedForType(parsed2, type2, file2);
|
|
349
|
+
}
|
|
350
|
+
|
|
276
351
|
const comparison = compareObjects(parsed1, parsed2);
|
|
277
352
|
return buildComparisonResult(comparison, parsed1, parsed2, file1, file2);
|
|
278
353
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* AI Fabrix Builder Deployment Key Generator
|
|
3
3
|
*
|
|
4
4
|
* This module generates SHA256-based deployment keys for controller authentication.
|
|
5
|
-
* Keys are computed from
|
|
5
|
+
* Keys are computed from application config content to ensure deployment integrity.
|
|
6
6
|
*
|
|
7
7
|
* @fileoverview Deployment key generation for AI Fabrix Builder
|
|
8
8
|
* @author AI Fabrix Team
|
|
@@ -14,14 +14,14 @@ const fs = require('fs');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Generates deployment key from
|
|
17
|
+
* Generates deployment key from application config content
|
|
18
18
|
* Creates SHA256 hash for controller authentication and deployment integrity
|
|
19
19
|
*
|
|
20
20
|
* @async
|
|
21
21
|
* @function generateDeploymentKey
|
|
22
22
|
* @param {string} appName - Name of the application
|
|
23
|
-
* @returns {Promise<string>} SHA256 hash of
|
|
24
|
-
* @throws {Error} If
|
|
23
|
+
* @returns {Promise<string>} SHA256 hash of application config content
|
|
24
|
+
* @throws {Error} If application config cannot be read
|
|
25
25
|
*
|
|
26
26
|
* @example
|
|
27
27
|
* const key = await generateDeploymentKey('myapp');
|
|
@@ -32,22 +32,19 @@ async function generateDeploymentKey(appName) {
|
|
|
32
32
|
throw new Error('App name is required and must be a string');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
35
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
36
|
+
const builderPath = path.join(process.cwd(), 'builder', appName);
|
|
37
|
+
const variablesPath = resolveApplicationConfigPath(builderPath);
|
|
41
38
|
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
42
39
|
return generateDeploymentKeyFromContent(content);
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
/**
|
|
46
|
-
* Generates deployment key from raw
|
|
43
|
+
* Generates deployment key from raw application config content
|
|
47
44
|
* Useful for testing or when content is already loaded
|
|
48
45
|
*
|
|
49
46
|
* @function generateDeploymentKeyFromContent
|
|
50
|
-
* @param {string} content - Raw
|
|
47
|
+
* @param {string} content - Raw application config content
|
|
51
48
|
* @returns {string} SHA256 hash of content
|
|
52
49
|
*
|
|
53
50
|
* @example
|
|
@@ -60,12 +60,12 @@ function getContainerPortFromDockerEnv(dockerEnv) {
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Updates PORT in resolved content for docker environment
|
|
63
|
-
* Sets PORT to container port (build.containerPort or port from
|
|
63
|
+
* Sets PORT to container port (build.containerPort or port from application config)
|
|
64
64
|
* NOT the host port (which includes developer-id offset)
|
|
65
65
|
* @async
|
|
66
66
|
* @function updatePortForDocker
|
|
67
67
|
* @param {string} resolved - Resolved environment content
|
|
68
|
-
* @param {string} variablesPath - Path to
|
|
68
|
+
* @param {string} variablesPath - Path to application config file
|
|
69
69
|
* @returns {Promise<string>} Updated content with PORT set
|
|
70
70
|
*/
|
|
71
71
|
async function updatePortForDocker(resolved, variablesPath) {
|
package/lib/core/secrets.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const logger = require('../utils/logger');
|
|
15
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
15
16
|
const config = require('./config');
|
|
16
17
|
const {
|
|
17
18
|
interpolateEnvVars,
|
|
@@ -288,7 +289,7 @@ async function applyEnvironmentTransformations(resolved, environment, variablesP
|
|
|
288
289
|
async function generateEnvContent(appName, secretsPath, environment = 'local', force = false) {
|
|
289
290
|
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
290
291
|
const templatePath = path.join(builderPath, 'env.template');
|
|
291
|
-
const variablesPath =
|
|
292
|
+
const variablesPath = resolveApplicationConfigPath(builderPath);
|
|
292
293
|
const template = loadEnvTemplate(templatePath);
|
|
293
294
|
const secretsPaths = await getActualSecretsPath(secretsPath, appName);
|
|
294
295
|
if (force) {
|
|
@@ -391,7 +392,7 @@ function mergeEnvContentPreservingExisting(newContent, existingMap) {
|
|
|
391
392
|
*/
|
|
392
393
|
async function generateEnvFile(appName, secretsPath, environment = 'local', force = false, skipOutputPath = false, preserveFromPath = null) {
|
|
393
394
|
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
394
|
-
const variablesPath =
|
|
395
|
+
const variablesPath = resolveApplicationConfigPath(builderPath);
|
|
395
396
|
const envPath = path.join(builderPath, '.env');
|
|
396
397
|
|
|
397
398
|
const resolved = await generateEnvContent(appName, secretsPath, environment, force);
|
package/lib/core/templates.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const yaml = require('js-yaml');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Generate
|
|
11
|
+
* Generate application.yaml content for an application
|
|
12
12
|
* Matches application-schema.json structure
|
|
13
13
|
* @param {string} appName - Application name
|
|
14
14
|
* @param {Object} config - Configuration options
|
|
@@ -166,7 +166,7 @@ function generateVariablesYaml(appName, config) {
|
|
|
166
166
|
const displayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
167
167
|
const appType = config.type || 'webapp';
|
|
168
168
|
|
|
169
|
-
// For external type, create minimal
|
|
169
|
+
// For external type, create minimal application config
|
|
170
170
|
if (appType === 'external') {
|
|
171
171
|
const variables = generateExternalSystemVariables(appName, displayName, config);
|
|
172
172
|
return dumpVariablesToYaml(variables);
|
package/lib/datasource/deploy.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
|
-
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
14
|
+
const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
|
|
15
15
|
const { getEnvironmentApplication } = require('../api/environments.api');
|
|
16
16
|
const { publishDatasourceViaPipeline } = require('../api/pipeline.api');
|
|
17
17
|
const { formatApiError } = require('../utils/api-error-handler');
|
|
@@ -152,6 +152,7 @@ async function setupDeploymentAuth(controllerUrl, environment, appKey) {
|
|
|
152
152
|
* @throws {Error} If publish fails
|
|
153
153
|
*/
|
|
154
154
|
async function publishDatasourceToDataplane(dataplaneUrl, systemKey, authConfig, datasourceConfig) {
|
|
155
|
+
requireBearerForDataplanePipeline(authConfig);
|
|
155
156
|
logger.log(chalk.blue('\nš Publishing datasource to dataplane...'));
|
|
156
157
|
|
|
157
158
|
const publishResponse = await publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceConfig);
|