@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
|
@@ -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);
|
|
@@ -15,6 +15,7 @@ const logger = require('../utils/logger');
|
|
|
15
15
|
const { validateControllerUrl, validateEnvironmentKey } = require('../utils/deployment-validation');
|
|
16
16
|
const { handleDeploymentError, handleDeploymentErrors } = require('../utils/deployment-errors');
|
|
17
17
|
const { validatePipeline, deployPipeline, getPipelineDeployment } = require('../api/pipeline.api');
|
|
18
|
+
const { getAuthUser } = require('../api/auth.api');
|
|
18
19
|
const { handleValidationResponse } = require('../utils/deployment-validation-helpers');
|
|
19
20
|
const {
|
|
20
21
|
convertToPipelineAuthConfig,
|
|
@@ -36,73 +37,72 @@ function transformExternalManifestForPipeline(manifest) {
|
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Build validation data for deployment
|
|
40
|
+
* When authenticated with bearer token only, clientId/clientSecret are not sent (controller uses token).
|
|
39
41
|
* @async
|
|
40
42
|
* @param {Object} manifest - Application manifest/config
|
|
41
43
|
* @param {string} validatedEnvKey - Validated environment key
|
|
42
44
|
* @param {Object} authConfig - Authentication configuration
|
|
43
45
|
* @param {Object} options - Additional options
|
|
44
|
-
* @returns {Promise<Object>} Object with validationData and
|
|
46
|
+
* @returns {Promise<Object>} Object with validationData, pipelineAuthConfig, and useBearerOnly
|
|
45
47
|
*/
|
|
46
48
|
async function buildValidationData(manifest, validatedEnvKey, authConfig, options) {
|
|
47
|
-
const
|
|
48
|
-
let clientId;
|
|
49
|
-
let clientSecret;
|
|
50
|
-
let pipelineAuthConfig;
|
|
49
|
+
const repositoryUrl = options.repositoryUrl || `https://github.com/aifabrix/${manifest.key}`;
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
manifest.key,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
59
|
-
clientId = credentials.clientId;
|
|
60
|
-
clientSecret = credentials.clientSecret;
|
|
61
|
-
pipelineAuthConfig = {
|
|
62
|
-
type: 'client-credentials',
|
|
63
|
-
clientId,
|
|
64
|
-
clientSecret
|
|
51
|
+
if (authConfig.type === 'bearer' && authConfig.token && !authConfig.clientId) {
|
|
52
|
+
const pipelineAuthConfig = { type: 'bearer', token: authConfig.token };
|
|
53
|
+
const validationData = {
|
|
54
|
+
clientId: manifest.key,
|
|
55
|
+
repositoryUrl,
|
|
56
|
+
applicationConfig: manifest
|
|
65
57
|
};
|
|
66
|
-
|
|
67
|
-
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
68
|
-
pipelineAuthConfig = { type: 'bearer', token: authConfig.token };
|
|
69
|
-
clientId = manifest.key;
|
|
70
|
-
clientSecret = '';
|
|
71
|
-
} else {
|
|
72
|
-
throw credError;
|
|
73
|
-
}
|
|
58
|
+
return { validationData, pipelineAuthConfig, useBearerOnly: true };
|
|
74
59
|
}
|
|
75
60
|
|
|
76
|
-
const
|
|
61
|
+
const tokenManager = require('../utils/token-manager');
|
|
62
|
+
const credentials = await tokenManager.extractClientCredentials(
|
|
63
|
+
authConfig,
|
|
64
|
+
manifest.key,
|
|
65
|
+
validatedEnvKey,
|
|
66
|
+
options
|
|
67
|
+
);
|
|
68
|
+
const pipelineAuthConfig = {
|
|
69
|
+
type: 'client-credentials',
|
|
70
|
+
clientId: credentials.clientId,
|
|
71
|
+
clientSecret: credentials.clientSecret
|
|
72
|
+
};
|
|
77
73
|
const validationData = {
|
|
78
|
-
clientId: clientId || '',
|
|
79
|
-
clientSecret: clientSecret || '',
|
|
80
|
-
repositoryUrl
|
|
74
|
+
clientId: credentials.clientId || '',
|
|
75
|
+
clientSecret: credentials.clientSecret || '',
|
|
76
|
+
repositoryUrl,
|
|
81
77
|
applicationConfig: manifest
|
|
82
78
|
};
|
|
83
|
-
|
|
84
|
-
return { validationData, pipelineAuthConfig };
|
|
79
|
+
return { validationData, pipelineAuthConfig, useBearerOnly: false };
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
/**
|
|
88
83
|
* Handle authentication errors during validation
|
|
89
84
|
* @param {Error} error - Error object
|
|
90
85
|
* @param {string} appKey - Application key
|
|
86
|
+
* @param {boolean} [useBearerOnly] - True when auth was bearer token only (no client id/secret sent)
|
|
91
87
|
* @throws {Error} Enhanced authentication error
|
|
92
88
|
*/
|
|
93
|
-
function handleValidationAuthError(error, appKey) {
|
|
94
|
-
if (error.status
|
|
95
|
-
|
|
96
|
-
`Authentication failed: Invalid or expired credentials for application '${appKey}'.\n` +
|
|
97
|
-
'The provided Client ID and Client Secret are incorrect or have been revoked.\n\n' +
|
|
98
|
-
'💡 If the application already exists, rotate the secret:\n' +
|
|
99
|
-
` aifabrix app rotate-secret ${appKey}\n\n` +
|
|
100
|
-
'💡 Otherwise, ensure credentials are correct in ~/.aifabrix/secrets.local.yaml or use --client-id and --client-secret flags.'
|
|
101
|
-
);
|
|
102
|
-
authError.status = 401;
|
|
103
|
-
authError.originalError = error;
|
|
104
|
-
throw authError;
|
|
89
|
+
function handleValidationAuthError(error, appKey, useBearerOnly) {
|
|
90
|
+
if (error.status !== 401 && (!error.response || error.response.status !== 401)) {
|
|
91
|
+
return;
|
|
105
92
|
}
|
|
93
|
+
const authError = new Error(
|
|
94
|
+
useBearerOnly
|
|
95
|
+
? 'Authentication failed: Your authentication token is invalid or expired.\n\n' +
|
|
96
|
+
'To authenticate, run:\n aifabrix login --method device --controller <url>'
|
|
97
|
+
: `Authentication failed: Invalid or expired credentials for application '${appKey}'.\n` +
|
|
98
|
+
'The provided Client ID and Client Secret are incorrect or have been revoked.\n\n' +
|
|
99
|
+
'💡 If the application already exists, rotate the secret:\n' +
|
|
100
|
+
` aifabrix app rotate-secret ${appKey}\n\n` +
|
|
101
|
+
'💡 Otherwise, ensure credentials are correct in ~/.aifabrix/secrets.local.yaml or use --client-id and --client-secret flags.'
|
|
102
|
+
);
|
|
103
|
+
authError.status = 401;
|
|
104
|
+
authError.originalError = error;
|
|
105
|
+
throw authError;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
@@ -122,8 +122,8 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
|
|
|
122
122
|
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
123
123
|
const maxRetries = options.maxRetries || 3;
|
|
124
124
|
|
|
125
|
-
// Build validation data
|
|
126
|
-
const { validationData, pipelineAuthConfig } = await buildValidationData(manifest, validatedEnvKey, authConfig, options);
|
|
125
|
+
// Build validation data (bearer-only: no clientId/clientSecret sent)
|
|
126
|
+
const { validationData, pipelineAuthConfig, useBearerOnly } = await buildValidationData(manifest, validatedEnvKey, authConfig, options);
|
|
127
127
|
|
|
128
128
|
let lastError;
|
|
129
129
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -133,8 +133,8 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
|
|
|
133
133
|
} catch (error) {
|
|
134
134
|
lastError = error;
|
|
135
135
|
|
|
136
|
-
// Handle authentication errors (401)
|
|
137
|
-
handleValidationAuthError(error, manifest.key);
|
|
136
|
+
// Handle authentication errors (401)
|
|
137
|
+
handleValidationAuthError(error, manifest.key, useBearerOnly);
|
|
138
138
|
|
|
139
139
|
const shouldRetry = attempt < maxRetries && error.status && error.status >= 500;
|
|
140
140
|
if (shouldRetry) {
|
|
@@ -310,6 +310,32 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
|
|
|
310
310
|
throw new Error('Deployment timeout: Maximum polling attempts reached');
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
/**
|
|
314
|
+
* When using bearer token only (no client credentials), verify token is valid before deploy.
|
|
315
|
+
* @async
|
|
316
|
+
* @param {string} controllerUrl - Controller URL
|
|
317
|
+
* @param {Object} authConfig - Authentication configuration
|
|
318
|
+
* @throws {Error} If token is invalid or expired
|
|
319
|
+
*/
|
|
320
|
+
async function ensureBearerTokenValid(controllerUrl, authConfig) {
|
|
321
|
+
if (authConfig.type !== 'bearer' || !authConfig.token || authConfig.clientId) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const response = await getAuthUser(controllerUrl, authConfig);
|
|
326
|
+
if (response && response.success && response.data && response.data.authenticated !== false) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
} catch (_) {
|
|
330
|
+
// Fall through to throw below
|
|
331
|
+
}
|
|
332
|
+
throw new Error(
|
|
333
|
+
'Your authentication token is invalid or expired.\n\n' +
|
|
334
|
+
'Run: aifabrix login --method device --controller <url>\n\n' +
|
|
335
|
+
'Then run deploy again.'
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
313
339
|
/**
|
|
314
340
|
* Validates and sends deployment request to controller
|
|
315
341
|
* Implements two-step process: validate then deploy
|
|
@@ -322,6 +348,8 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
|
|
|
322
348
|
* @returns {Promise<Object>} Deployment result
|
|
323
349
|
*/
|
|
324
350
|
async function sendDeployment(url, validatedEnvKey, manifest, authConfig, options) {
|
|
351
|
+
await ensureBearerTokenValid(url, authConfig);
|
|
352
|
+
|
|
325
353
|
// Step 1: Validate deployment
|
|
326
354
|
logger.log(chalk.blue('🔍 Validating deployment configuration...'));
|
|
327
355
|
const validateResult = await validateDeployment(url, validatedEnvKey, manifest, authConfig, {
|
|
@@ -50,7 +50,6 @@ async function getAuthAndDataplane(systemKey, _options) {
|
|
|
50
50
|
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
51
51
|
logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
|
|
52
52
|
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
53
|
-
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
54
53
|
|
|
55
54
|
return { authConfig, dataplaneUrl, environment, controllerUrl };
|
|
56
55
|
}
|
|
@@ -11,11 +11,10 @@
|
|
|
11
11
|
const fs = require('fs').promises;
|
|
12
12
|
const fsSync = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
14
|
const { detectAppType, getDeployJsonPath } = require('../utils/paths');
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
|
-
* Loads
|
|
17
|
+
* Loads application config for an application
|
|
19
18
|
* @async
|
|
20
19
|
* @function loadVariablesYaml
|
|
21
20
|
* @param {string} appName - Application name
|
|
@@ -23,11 +22,11 @@ const { detectAppType, getDeployJsonPath } = require('../utils/paths');
|
|
|
23
22
|
* @throws {Error} If file cannot be loaded
|
|
24
23
|
*/
|
|
25
24
|
async function loadVariablesYaml(appName) {
|
|
26
|
-
// Detect app type and get correct path (integration or builder)
|
|
27
25
|
const { appPath } = await detectAppType(appName);
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
26
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
27
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
28
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
29
|
+
return loadConfigFile(configPath);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -13,6 +13,8 @@ const chalk = require('chalk');
|
|
|
13
13
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
14
14
|
const logger = require('../utils/logger');
|
|
15
15
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
16
|
+
const { detectAppType } = require('../utils/paths');
|
|
17
|
+
const { logOfflinePathWhenType } = require('../utils/cli-utils');
|
|
16
18
|
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
17
19
|
const { getExternalSystem } = require('../api/external-systems.api');
|
|
18
20
|
const { generateControllerManifest } = require('../generator/external-controller-manifest');
|
|
@@ -132,11 +134,14 @@ async function prepareDeploymentConfig(appName, _options) {
|
|
|
132
134
|
*/
|
|
133
135
|
async function deployExternalSystem(appName, options = {}) {
|
|
134
136
|
try {
|
|
137
|
+
const { appPath } = await detectAppType(appName);
|
|
138
|
+
logOfflinePathWhenType(appPath);
|
|
139
|
+
|
|
135
140
|
logger.log(chalk.blue(`\n🚀 Deploying external system: ${appName}`));
|
|
136
141
|
|
|
137
142
|
// Step 0: Validate before deployment (same as validate command)
|
|
138
143
|
logger.log(chalk.blue('🔍 Validating external system before deployment...'));
|
|
139
|
-
const validationResult = await validateExternalSystemComplete(appName);
|
|
144
|
+
const validationResult = await validateExternalSystemComplete(appName, options);
|
|
140
145
|
|
|
141
146
|
if (!validationResult.valid) {
|
|
142
147
|
displayValidationResults(validationResult);
|
|
@@ -146,7 +151,7 @@ async function deployExternalSystem(appName, options = {}) {
|
|
|
146
151
|
logger.log(chalk.green('✓ Validation passed, proceeding with deployment...'));
|
|
147
152
|
|
|
148
153
|
// Step 1: Generate controller manifest (validated, ready for deployment)
|
|
149
|
-
const manifest = await generateControllerManifest(appName);
|
|
154
|
+
const manifest = await generateControllerManifest(appName, options);
|
|
150
155
|
|
|
151
156
|
// Step 2: Get deployment configuration (auth, controller URL, etc.)
|
|
152
157
|
const { environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
const { generateExternalReadmeContent } = require('../utils/external-readme');
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Generates
|
|
14
|
+
* Generates application.yaml content for downloaded system
|
|
15
15
|
* @param {string} systemKey - System key
|
|
16
16
|
* @param {Object} application - External system configuration
|
|
17
17
|
* @param {Array} dataSources - Array of datasource configurations
|
|
18
18
|
* @returns {Object} Variables YAML object
|
|
19
19
|
*/
|
|
20
20
|
function generateVariablesYaml(systemKey, application, dataSources) {
|
|
21
|
-
const systemFileName = `${systemKey}-system.
|
|
21
|
+
const systemFileName = `${systemKey}-system.yaml`;
|
|
22
22
|
const datasourceFiles = dataSources.map(ds => {
|
|
23
23
|
// Extract datasource key (remove system key prefix if present)
|
|
24
24
|
const datasourceKey = ds.key || '';
|
|
@@ -29,7 +29,7 @@ function generateVariablesYaml(systemKey, application, dataSources) {
|
|
|
29
29
|
const entityType = ds.entityType || ds.entityKey || datasourceKey.split('-').pop();
|
|
30
30
|
datasourceKeyOnly = entityType;
|
|
31
31
|
}
|
|
32
|
-
return `${systemKey}-datasource-${datasourceKeyOnly}.
|
|
32
|
+
return `${systemKey}-datasource-${datasourceKeyOnly}.yaml`;
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
return {
|
|
@@ -73,7 +73,7 @@ function generateReadme(systemKey, application, dataSources) {
|
|
|
73
73
|
return {
|
|
74
74
|
entityType: datasourceKeyOnly,
|
|
75
75
|
displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
|
|
76
|
-
fileName: `${systemKey}-datasource-${datasourceKeyOnly}.
|
|
76
|
+
fileName: `${systemKey}-datasource-${datasourceKeyOnly}.yaml`
|
|
77
77
|
};
|
|
78
78
|
});
|
|
79
79
|
|
|
@@ -19,6 +19,7 @@ const { getDeploymentAuth } = require('../utils/token-manager');
|
|
|
19
19
|
const { getConfig } = require('../core/config');
|
|
20
20
|
const { detectAppType } = require('../utils/paths');
|
|
21
21
|
const logger = require('../utils/logger');
|
|
22
|
+
const { writeConfigFile } = require('../utils/config-format');
|
|
22
23
|
const { generateEnvTemplate } = require('../utils/external-system-env-helpers');
|
|
23
24
|
const { generateVariablesYaml, generateReadme } = require('./download-helpers');
|
|
24
25
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
@@ -201,9 +202,9 @@ async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig)
|
|
|
201
202
|
* @returns {Promise<string>} System file path
|
|
202
203
|
*/
|
|
203
204
|
async function generateSystemFile(tempDir, systemKey, application) {
|
|
204
|
-
const systemFileName = `${systemKey}-system.
|
|
205
|
+
const systemFileName = `${systemKey}-system.yaml`;
|
|
205
206
|
const systemFilePath = path.join(tempDir, systemFileName);
|
|
206
|
-
|
|
207
|
+
writeConfigFile(systemFilePath, application);
|
|
207
208
|
return systemFilePath;
|
|
208
209
|
}
|
|
209
210
|
|
|
@@ -230,9 +231,9 @@ async function generateDatasourceFiles(tempDir, systemKey, dataSources) {
|
|
|
230
231
|
const entityType = datasource.entityType || datasource.entityKey || datasourceKey.split('-').pop();
|
|
231
232
|
datasourceKeyOnly = entityType;
|
|
232
233
|
}
|
|
233
|
-
const datasourceFileName = `${systemKey}-datasource-${datasourceKeyOnly}.
|
|
234
|
+
const datasourceFileName = `${systemKey}-datasource-${datasourceKeyOnly}.yaml`;
|
|
234
235
|
const datasourceFilePath = path.join(tempDir, datasourceFileName);
|
|
235
|
-
|
|
236
|
+
writeConfigFile(datasourceFilePath, datasource);
|
|
236
237
|
datasourceFiles.push(datasourceFilePath);
|
|
237
238
|
} catch (error) {
|
|
238
239
|
datasourceErrors.push(new Error(`Failed to write datasource ${datasource.key}: ${error.message}`));
|
|
@@ -242,7 +243,7 @@ async function generateDatasourceFiles(tempDir, systemKey, dataSources) {
|
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
/**
|
|
245
|
-
* Generates configuration files (
|
|
246
|
+
* Generates configuration files (application.yaml, env.template, README.md)
|
|
246
247
|
* @async
|
|
247
248
|
* @function generateConfigFiles
|
|
248
249
|
* @param {string} tempDir - Temporary directory
|
|
@@ -252,9 +253,9 @@ async function generateDatasourceFiles(tempDir, systemKey, dataSources) {
|
|
|
252
253
|
* @returns {Promise<Object>} Object with file paths
|
|
253
254
|
*/
|
|
254
255
|
async function generateConfigFiles(tempDir, systemKey, application, dataSources) {
|
|
255
|
-
// Generate
|
|
256
|
+
// Generate application.yaml
|
|
256
257
|
const variables = generateVariablesYaml(systemKey, application, dataSources);
|
|
257
|
-
const variablesPath = path.join(tempDir, '
|
|
258
|
+
const variablesPath = path.join(tempDir, 'application.yaml');
|
|
258
259
|
await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }), 'utf8');
|
|
259
260
|
|
|
260
261
|
// Generate env.template
|
|
@@ -307,7 +308,7 @@ async function moveFilesToFinalLocation(tempDir, finalPath, systemKey, filePaths
|
|
|
307
308
|
const systemFileName = `${systemKey}-system.json`;
|
|
308
309
|
const filesToMove = [
|
|
309
310
|
{ from: filePaths.systemFilePath, to: path.join(finalPath, systemFileName) },
|
|
310
|
-
{ from: filePaths.variablesPath, to: path.join(finalPath, '
|
|
311
|
+
{ from: filePaths.variablesPath, to: path.join(finalPath, 'application.yaml') },
|
|
311
312
|
{ from: filePaths.envTemplatePath, to: path.join(finalPath, 'env.template') },
|
|
312
313
|
{ from: filePaths.readmePath, to: path.join(finalPath, 'README.md') }
|
|
313
314
|
];
|
|
@@ -347,8 +348,8 @@ function handleDryRun(systemKey, dataplaneUrl) {
|
|
|
347
348
|
logger.log(chalk.gray(` ${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`));
|
|
348
349
|
logger.log(chalk.yellow('\nWould create:'));
|
|
349
350
|
logger.log(chalk.gray(` integration/${systemKey}/`));
|
|
350
|
-
logger.log(chalk.gray(` integration/${systemKey}/
|
|
351
|
-
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-system.
|
|
351
|
+
logger.log(chalk.gray(` integration/${systemKey}/application.yaml`));
|
|
352
|
+
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-system.yaml`));
|
|
352
353
|
logger.log(chalk.gray(` integration/${systemKey}/env.template`));
|
|
353
354
|
logger.log(chalk.gray(` integration/${systemKey}/README.md`));
|
|
354
355
|
}
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
const fs = require('fs').promises;
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const handlebars = require('handlebars');
|
|
15
|
-
const yaml = require('js-yaml');
|
|
16
15
|
const chalk = require('chalk');
|
|
17
16
|
const logger = require('../utils/logger');
|
|
17
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
18
|
+
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
18
19
|
|
|
19
20
|
// Register Handlebars helper for equality check
|
|
20
21
|
handlebars.registerHelper('eq', (a, b) => a === b);
|
|
@@ -52,11 +53,12 @@ async function generateExternalSystemTemplate(appPath, systemKey, config) {
|
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
const rendered = template(context);
|
|
56
|
+
const parsed = JSON.parse(rendered);
|
|
55
57
|
|
|
56
|
-
// Generate in same folder as
|
|
57
|
-
// Use naming: <app-name>-system.
|
|
58
|
-
const outputPath = path.join(appPath, `${systemKey}-system.
|
|
59
|
-
|
|
58
|
+
// Generate in same folder as application.yaml (new structure)
|
|
59
|
+
// Use naming: <app-name>-system.yaml
|
|
60
|
+
const outputPath = path.join(appPath, `${systemKey}-system.yaml`);
|
|
61
|
+
writeConfigFile(outputPath, parsed);
|
|
60
62
|
|
|
61
63
|
return outputPath;
|
|
62
64
|
} catch (error) {
|
|
@@ -98,15 +100,16 @@ async function generateExternalDataSourceTemplate(appPath, datasourceKey, config
|
|
|
98
100
|
};
|
|
99
101
|
|
|
100
102
|
const rendered = template(context);
|
|
103
|
+
const datasourceConfig = JSON.parse(rendered);
|
|
101
104
|
|
|
102
|
-
// Generate in same folder as
|
|
103
|
-
// Use naming: <app-name>-datasource-<datasource-key>.
|
|
105
|
+
// Generate in same folder as application.yaml (new structure)
|
|
106
|
+
// Use naming: <app-name>-datasource-<datasource-key>.yaml
|
|
104
107
|
// Extract datasource key (remove system key prefix if present)
|
|
105
108
|
const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${config.systemKey}-`)
|
|
106
109
|
? datasourceKey.substring(config.systemKey.length + 1)
|
|
107
110
|
: datasourceKey;
|
|
108
|
-
const outputPath = path.join(appPath, `${config.systemKey}-datasource-${datasourceKeyOnly}.
|
|
109
|
-
|
|
111
|
+
const outputPath = path.join(appPath, `${config.systemKey}-datasource-${datasourceKeyOnly}.yaml`);
|
|
112
|
+
writeConfigFile(outputPath, datasourceConfig);
|
|
110
113
|
|
|
111
114
|
return outputPath;
|
|
112
115
|
} catch (error) {
|
|
@@ -160,7 +163,7 @@ async function generateExternalSystemFiles(appPath, appName, config) {
|
|
|
160
163
|
logger.log(chalk.green(`✓ Generated datasource: ${path.basename(datasourcePath)}`));
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
// Update
|
|
166
|
+
// Update application.yaml with externalIntegration block
|
|
164
167
|
await updateVariablesYamlWithExternalIntegration(appPath, systemKey, datasourcePaths);
|
|
165
168
|
|
|
166
169
|
return {
|
|
@@ -173,7 +176,7 @@ async function generateExternalSystemFiles(appPath, appName, config) {
|
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
/**
|
|
176
|
-
* Updates
|
|
179
|
+
* Updates application.yaml with externalIntegration block
|
|
177
180
|
* @async
|
|
178
181
|
* @function updateVariablesYamlWithExternalIntegration
|
|
179
182
|
* @param {string} appPath - Application directory path
|
|
@@ -183,23 +186,22 @@ async function generateExternalSystemFiles(appPath, appName, config) {
|
|
|
183
186
|
*/
|
|
184
187
|
async function updateVariablesYamlWithExternalIntegration(appPath, systemKey, datasourcePaths) {
|
|
185
188
|
try {
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
const variables = yaml.load(variablesContent);
|
|
189
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
190
|
+
const variables = loadConfigFile(configPath);
|
|
189
191
|
|
|
190
192
|
// Add externalIntegration block
|
|
191
193
|
// Files are in same folder, so schemaBasePath is './'
|
|
192
194
|
variables.externalIntegration = {
|
|
193
195
|
schemaBasePath: './',
|
|
194
|
-
systems: [`${systemKey}-system.
|
|
196
|
+
systems: [`${systemKey}-system.yaml`],
|
|
195
197
|
dataSources: datasourcePaths.map(p => path.basename(p)),
|
|
196
198
|
autopublish: true,
|
|
197
199
|
version: '1.0.0'
|
|
198
200
|
};
|
|
199
201
|
|
|
200
|
-
|
|
202
|
+
writeConfigFile(configPath, variables);
|
|
201
203
|
} catch (error) {
|
|
202
|
-
throw new Error(`Failed to update
|
|
204
|
+
throw new Error(`Failed to update application config: ${error.message}`);
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
207
|
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
const fs = require('fs').promises;
|
|
13
13
|
const fsSync = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
-
const yaml = require('js-yaml');
|
|
16
15
|
const chalk = require('chalk');
|
|
17
16
|
const testHelpers = require('../utils/external-system-test-helpers');
|
|
18
17
|
const { retryApiCall } = require('../utils/external-system-test-helpers');
|
|
@@ -39,30 +38,26 @@ const {
|
|
|
39
38
|
} = require('./test-execution');
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
|
-
* Loads and parses
|
|
41
|
+
* Loads and parses application config file
|
|
43
42
|
* @async
|
|
44
43
|
* @function loadVariablesYamlFile
|
|
45
|
-
* @param {string} variablesPath - Path to
|
|
44
|
+
* @param {string} variablesPath - Path to application config
|
|
46
45
|
* @returns {Promise<Object>} Parsed variables
|
|
47
|
-
* @throws {Error} If file not found or invalid
|
|
46
|
+
* @throws {Error} If file not found or invalid
|
|
48
47
|
*/
|
|
49
48
|
async function loadVariablesYamlFile(variablesPath) {
|
|
50
|
-
|
|
51
|
-
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
49
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
55
50
|
try {
|
|
56
|
-
const variables =
|
|
51
|
+
const variables = loadConfigFile(variablesPath);
|
|
57
52
|
if (!variables.externalIntegration) {
|
|
58
|
-
throw new Error('externalIntegration block not found in
|
|
53
|
+
throw new Error('externalIntegration block not found in application config');
|
|
59
54
|
}
|
|
60
55
|
return variables;
|
|
61
56
|
} catch (error) {
|
|
62
57
|
if (error.message.includes('externalIntegration')) {
|
|
63
58
|
throw error;
|
|
64
59
|
}
|
|
65
|
-
throw new Error(`
|
|
60
|
+
throw new Error(`Application config: ${error.message}`);
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
63
|
|
|
@@ -152,9 +147,9 @@ async function loadDatasourceFiles(datasourceFiles, schemaBasePath, appPath) {
|
|
|
152
147
|
*/
|
|
153
148
|
async function loadExternalSystemFiles(appName) {
|
|
154
149
|
const { appPath } = await detectAppType(appName);
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
const variables = await loadVariablesYamlFile(
|
|
150
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
151
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
152
|
+
const variables = await loadVariablesYamlFile(configPath);
|
|
158
153
|
|
|
159
154
|
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
160
155
|
const systemFiles = variables.externalIntegration.systems || [];
|