@aifabrix/builder 2.31.1 → 2.32.1
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/README.md +9 -9
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy-company.json +17 -14
- package/integration/hubspot/hubspot-deploy-contact.json +19 -16
- package/integration/hubspot/hubspot-deploy-deal.json +21 -18
- package/lib/api/types/datasources.types.js +31 -5
- package/lib/api/types/wizard.types.js +142 -0
- package/lib/api/wizard.api.js +177 -0
- package/lib/{app-config.js → app/config.js} +4 -4
- package/lib/{app-deploy.js → app/deploy.js} +8 -8
- package/lib/app/display.js +90 -0
- package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
- package/lib/{app-down.js → app/down.js} +4 -4
- package/lib/app/helpers.js +218 -0
- package/lib/app/index.js +298 -0
- package/lib/{app-list.js → app/list.js} +6 -6
- package/lib/{app-push.js → app/push.js} +4 -4
- package/lib/{app-readme.js → app/readme.js} +34 -13
- package/lib/{app-register.js → app/register.js} +9 -9
- package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
- package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
- package/lib/{app-run.js → app/run.js} +6 -6
- package/lib/{build.js → build/index.js} +59 -32
- package/lib/build/package.json +7 -0
- package/lib/cli.js +245 -179
- package/lib/commands/app.js +3 -3
- package/lib/commands/datasource.js +4 -4
- package/lib/commands/login-credentials.js +209 -0
- package/lib/commands/login-device.js +254 -0
- package/lib/commands/login.js +67 -378
- package/lib/commands/logout.js +1 -1
- package/lib/commands/secrets-set.js +1 -1
- package/lib/commands/secure.js +2 -2
- package/lib/commands/wizard.js +498 -0
- package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
- package/lib/{config.js → core/config.js} +28 -26
- package/lib/{diff.js → core/diff.js} +157 -72
- package/lib/{secrets.js → core/secrets.js} +86 -49
- package/lib/{templates.js → core/templates-env.js} +14 -222
- package/lib/core/templates.js +279 -0
- package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
- package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
- package/lib/datasource/list.js +223 -0
- package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
- package/lib/{deployer.js → deployment/deployer.js} +48 -18
- package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
- package/lib/{push.js → deployment/push.js} +1 -1
- package/lib/external-system/deploy-helpers.js +145 -0
- package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
- package/lib/external-system/download-helpers.js +114 -0
- package/lib/{external-system-download.js → external-system/download.js} +92 -135
- package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
- package/lib/external-system/test-auth.js +40 -0
- package/lib/external-system/test-execution.js +84 -0
- package/lib/external-system/test-helpers.js +109 -0
- package/lib/{external-system-test.js → external-system/test.js} +174 -192
- package/lib/{generator-builders.js → generator/builders.js} +87 -10
- package/lib/{generator-external.js → generator/external.js} +115 -52
- package/lib/{github-generator.js → generator/github.js} +116 -15
- package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
- package/lib/{generator.js → generator/index.js} +49 -22
- package/lib/{generator-split.js → generator/split.js} +108 -55
- package/lib/generator/wizard-prompts.js +357 -0
- package/lib/generator/wizard.js +490 -0
- package/lib/{infra.js → infrastructure/index.js} +49 -22
- package/lib/schema/external-datasource.schema.json +145 -133
- package/lib/schema/external-system.schema.json +42 -0
- package/lib/utils/api.js +9 -5
- package/lib/utils/app-register-api.js +60 -32
- package/lib/utils/app-register-auth.js +172 -47
- package/lib/utils/app-register-config.js +130 -59
- package/lib/utils/app-run-containers.js +29 -8
- package/lib/utils/build-helpers.js +1 -1
- package/lib/utils/cli-utils.js +78 -30
- package/lib/utils/compose-generator.js +145 -65
- package/lib/utils/config-paths.js +2 -0
- package/lib/utils/deployment-errors.js +1 -1
- package/lib/utils/device-code.js +99 -41
- package/lib/utils/env-config-loader.js +1 -1
- package/lib/utils/env-copy.js +21 -18
- package/lib/utils/env-endpoints.js +115 -67
- package/lib/utils/env-map.js +13 -14
- package/lib/utils/env-ports.js +45 -25
- package/lib/utils/env-template.js +84 -42
- package/lib/utils/error-formatter.js +26 -9
- package/lib/utils/error-formatters/error-parser.js +90 -4
- package/lib/utils/error-formatters/http-status-errors.js +54 -17
- package/lib/utils/error-formatters/network-errors.js +103 -26
- package/lib/utils/external-system-display.js +184 -90
- package/lib/utils/external-system-validators.js +164 -42
- package/lib/utils/file-upload.js +109 -0
- package/lib/utils/health-check.js +199 -83
- package/lib/utils/infra-containers.js +1 -1
- package/lib/utils/infra-status.js +66 -15
- package/lib/utils/local-secrets.js +45 -25
- package/lib/utils/paths.js +45 -33
- package/lib/utils/schema-loader.js +42 -25
- package/lib/utils/schema-resolver.js +123 -74
- package/lib/utils/secrets-encryption.js +62 -25
- package/lib/utils/secrets-helpers.js +126 -63
- package/lib/utils/secrets-path.js +1 -1
- package/lib/utils/secrets-url.js +1 -1
- package/lib/utils/token-manager-refresh.js +181 -0
- package/lib/utils/token-manager.js +76 -123
- package/lib/utils/variable-transformer.js +154 -77
- package/lib/utils/yaml-preserve.js +41 -47
- package/lib/{template-validator.js → validation/template.js} +54 -23
- package/lib/{validate.js → validation/validate.js} +205 -125
- package/lib/{validator.js → validation/validator.js} +58 -39
- package/package.json +31 -2
- package/templates/external-system/deploy.ps1.hbs +34 -0
- package/templates/external-system/deploy.sh.hbs +34 -0
- package/templates/external-system/external-datasource.json.hbs +31 -12
- package/lib/app.js +0 -467
- package/lib/datasource-list.js +0 -141
- /package/lib/{app-prompts.js → app/prompts.js} +0 -0
- /package/lib/{env-reader.js → core/env-reader.js} +0 -0
- /package/lib/{key-generator.js → core/key-generator.js} +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource List Command
|
|
3
|
+
*
|
|
4
|
+
* Lists datasources from an environment via controller API.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Datasource listing for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const { getConfig } = require('../core/config');
|
|
13
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
14
|
+
const { listEnvironmentDatasources } = require('../api/environments.api');
|
|
15
|
+
const { formatApiError } = require('../utils/api-error-handler');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extracts datasources array from API response
|
|
20
|
+
* Handles multiple response formats similar to applications list
|
|
21
|
+
*
|
|
22
|
+
* @function extractDatasources
|
|
23
|
+
* @param {Object} response - API response from centralized API client
|
|
24
|
+
* @returns {Array} Array of datasources
|
|
25
|
+
* @throws {Error} If response format is invalid
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Extracts datasources from wrapped format
|
|
29
|
+
* @function extractFromWrappedFormat
|
|
30
|
+
* @param {Object} apiResponse - API response object
|
|
31
|
+
* @returns {Array|null} Datasources array or null
|
|
32
|
+
*/
|
|
33
|
+
function extractFromWrappedFormat(apiResponse) {
|
|
34
|
+
if (apiResponse && apiResponse.data && Array.isArray(apiResponse.data)) {
|
|
35
|
+
return apiResponse.data;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extracts datasources from direct array format
|
|
42
|
+
* @function extractFromDirectArray
|
|
43
|
+
* @param {Object} apiResponse - API response object
|
|
44
|
+
* @returns {Array|null} Datasources array or null
|
|
45
|
+
*/
|
|
46
|
+
function extractFromDirectArray(apiResponse) {
|
|
47
|
+
if (Array.isArray(apiResponse)) {
|
|
48
|
+
return apiResponse;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extracts datasources from paginated format
|
|
55
|
+
* @function extractFromPaginatedFormat
|
|
56
|
+
* @param {Object} apiResponse - API response object
|
|
57
|
+
* @returns {Array|null} Datasources array or null
|
|
58
|
+
*/
|
|
59
|
+
function extractFromPaginatedFormat(apiResponse) {
|
|
60
|
+
if (apiResponse && Array.isArray(apiResponse.items)) {
|
|
61
|
+
return apiResponse.items;
|
|
62
|
+
}
|
|
63
|
+
if (apiResponse && apiResponse.data && apiResponse.data.items && Array.isArray(apiResponse.data.items)) {
|
|
64
|
+
return apiResponse.data.items;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Logs error for invalid response format
|
|
71
|
+
* @function logInvalidResponseError
|
|
72
|
+
* @param {Object} apiResponse - API response object
|
|
73
|
+
*/
|
|
74
|
+
function logInvalidResponseError(apiResponse) {
|
|
75
|
+
logger.error(chalk.red('❌ Invalid response: expected data array or items array'));
|
|
76
|
+
logger.error(chalk.gray('\nAPI response type:'), typeof apiResponse);
|
|
77
|
+
logger.error(chalk.gray('API response:'), JSON.stringify(apiResponse, null, 2));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractDatasources(response) {
|
|
81
|
+
if (!response.success || !response.data) {
|
|
82
|
+
throw new Error('Invalid API response: missing success or data');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const apiResponse = response.data;
|
|
86
|
+
|
|
87
|
+
// Try different response formats
|
|
88
|
+
const datasources = extractFromWrappedFormat(apiResponse) ||
|
|
89
|
+
extractFromDirectArray(apiResponse) ||
|
|
90
|
+
extractFromPaginatedFormat(apiResponse);
|
|
91
|
+
|
|
92
|
+
if (!datasources) {
|
|
93
|
+
logInvalidResponseError(apiResponse);
|
|
94
|
+
throw new Error('Invalid API response format: expected array of datasources');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return datasources;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Displays datasources in a formatted table
|
|
102
|
+
*
|
|
103
|
+
* @function displayDatasources
|
|
104
|
+
* @param {Array} datasources - Array of datasource objects
|
|
105
|
+
* @param {string} environment - Environment key
|
|
106
|
+
*/
|
|
107
|
+
function displayDatasources(datasources, environment) {
|
|
108
|
+
if (datasources.length === 0) {
|
|
109
|
+
logger.log(chalk.yellow(`\nNo datasources found in environment: ${environment}`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
logger.log(chalk.blue(`\n📋 Datasources in environment: ${environment}\n`));
|
|
114
|
+
logger.log(chalk.gray('Key'.padEnd(30) + 'Display Name'.padEnd(30) + 'System Key'.padEnd(20) + 'Version'.padEnd(15) + 'Status'));
|
|
115
|
+
logger.log(chalk.gray('-'.repeat(120)));
|
|
116
|
+
|
|
117
|
+
datasources.forEach((ds) => {
|
|
118
|
+
const key = (ds.key || 'N/A').padEnd(30);
|
|
119
|
+
const displayName = (ds.displayName || 'N/A').padEnd(30);
|
|
120
|
+
const systemKey = (ds.systemKey || 'N/A').padEnd(20);
|
|
121
|
+
const version = (ds.version || 'N/A').padEnd(15);
|
|
122
|
+
const status = ds.enabled !== false ? chalk.green('enabled') : chalk.red('disabled');
|
|
123
|
+
logger.log(`${key}${displayName}${systemKey}${version}${status}`);
|
|
124
|
+
});
|
|
125
|
+
logger.log('');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Lists datasources from an environment
|
|
130
|
+
*
|
|
131
|
+
* @async
|
|
132
|
+
* @function listDatasources
|
|
133
|
+
* @param {Object} options - Command options
|
|
134
|
+
* @param {string} options.environment - Environment ID or key
|
|
135
|
+
* @throws {Error} If listing fails
|
|
136
|
+
*/
|
|
137
|
+
/**
|
|
138
|
+
* Gets device token from config
|
|
139
|
+
* @async
|
|
140
|
+
* @function getDeviceTokenFromConfig
|
|
141
|
+
* @param {Object} config - Configuration object
|
|
142
|
+
* @returns {Promise<Object|null>} Object with token and controllerUrl or null
|
|
143
|
+
*/
|
|
144
|
+
async function getDeviceTokenFromConfig(config) {
|
|
145
|
+
if (!config.device) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const deviceUrls = Object.keys(config.device);
|
|
150
|
+
if (deviceUrls.length === 0) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const controllerUrl = deviceUrls[0];
|
|
155
|
+
const deviceToken = await getOrRefreshDeviceToken(controllerUrl);
|
|
156
|
+
if (deviceToken && deviceToken.token) {
|
|
157
|
+
return {
|
|
158
|
+
token: deviceToken.token,
|
|
159
|
+
controllerUrl: deviceToken.controller
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Validates authentication for datasource listing
|
|
168
|
+
* @function validateDatasourceListingAuth
|
|
169
|
+
* @param {string|null} token - Authentication token
|
|
170
|
+
* @param {string|null} controllerUrl - Controller URL
|
|
171
|
+
*/
|
|
172
|
+
function validateDatasourceListingAuth(token, controllerUrl) {
|
|
173
|
+
if (!token || !controllerUrl) {
|
|
174
|
+
logger.error(chalk.red('❌ Not logged in. Run: aifabrix login'));
|
|
175
|
+
logger.error(chalk.gray(' Use device code flow: aifabrix login --method device --controller <url>'));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Handles API response errors
|
|
182
|
+
* @function handleDatasourceApiError
|
|
183
|
+
* @param {Object} response - API response
|
|
184
|
+
*/
|
|
185
|
+
function handleDatasourceApiError(response) {
|
|
186
|
+
const formattedError = response.formattedError || formatApiError(response);
|
|
187
|
+
logger.error(formattedError);
|
|
188
|
+
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
189
|
+
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function listDatasources(options) {
|
|
194
|
+
const config = await getConfig();
|
|
195
|
+
|
|
196
|
+
// Try to get device token
|
|
197
|
+
const authInfo = await getDeviceTokenFromConfig(config);
|
|
198
|
+
validateDatasourceListingAuth(authInfo?.token, authInfo?.controllerUrl);
|
|
199
|
+
|
|
200
|
+
// Call controller API using centralized API client
|
|
201
|
+
// Note: validateDatasourceListingAuth will exit if auth is missing, so this check is defensive
|
|
202
|
+
if (!authInfo || !authInfo.token || !authInfo.controllerUrl) {
|
|
203
|
+
validateDatasourceListingAuth(null, null); // This will exit
|
|
204
|
+
return; // Never reached, but satisfies linter
|
|
205
|
+
}
|
|
206
|
+
const authConfig = { type: 'bearer', token: authInfo.token };
|
|
207
|
+
const response = await listEnvironmentDatasources(authInfo.controllerUrl, options.environment, authConfig);
|
|
208
|
+
|
|
209
|
+
if (!response.success || !response.data) {
|
|
210
|
+
handleDatasourceApiError(response);
|
|
211
|
+
return; // Ensure we don't continue after exit
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const datasources = extractDatasources(response);
|
|
215
|
+
displayDatasources(datasources, options.environment);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
listDatasources,
|
|
220
|
+
displayDatasources,
|
|
221
|
+
extractDatasources
|
|
222
|
+
};
|
|
223
|
+
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
|
-
const { loadExternalDataSourceSchema } = require('
|
|
13
|
-
const { formatValidationErrors } = require('
|
|
12
|
+
const { loadExternalDataSourceSchema } = require('../utils/schema-loader');
|
|
13
|
+
const { formatValidationErrors } = require('../utils/error-formatter');
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Validates a datasource file against external-datasource schema
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
|
-
const auditLogger = require('
|
|
14
|
-
const logger = require('
|
|
15
|
-
const { validateControllerUrl, validateEnvironmentKey } = require('
|
|
16
|
-
const { handleDeploymentError, handleDeploymentErrors } = require('
|
|
17
|
-
const { validatePipeline, deployPipeline, getPipelineDeployment } = require('
|
|
18
|
-
const { handleValidationResponse } = require('
|
|
13
|
+
const auditLogger = require('../core/audit-logger');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
const { validateControllerUrl, validateEnvironmentKey } = require('../utils/deployment-validation');
|
|
16
|
+
const { handleDeploymentError, handleDeploymentErrors } = require('../utils/deployment-errors');
|
|
17
|
+
const { validatePipeline, deployPipeline, getPipelineDeployment } = require('../api/pipeline.api');
|
|
18
|
+
const { handleValidationResponse } = require('../utils/deployment-validation-helpers');
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Build validation data for deployment
|
|
@@ -27,7 +27,7 @@ const { handleValidationResponse } = require('./utils/deployment-validation-help
|
|
|
27
27
|
* @returns {Promise<Object>} Object with validationData and pipelineAuthConfig
|
|
28
28
|
*/
|
|
29
29
|
async function buildValidationData(manifest, validatedEnvKey, authConfig, options) {
|
|
30
|
-
const tokenManager = require('
|
|
30
|
+
const tokenManager = require('../utils/token-manager');
|
|
31
31
|
const { clientId, clientSecret } = await tokenManager.extractClientCredentials(
|
|
32
32
|
authConfig,
|
|
33
33
|
manifest.key,
|
|
@@ -242,25 +242,55 @@ function convertToPipelineAuthConfig(authConfig) {
|
|
|
242
242
|
* @param {string} deploymentId - Deployment ID for error messages
|
|
243
243
|
* @returns {Object|null} Deployment data if terminal, null if needs to continue polling
|
|
244
244
|
*/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Handles error response from deployment status check
|
|
247
|
+
* @function handleDeploymentStatusError
|
|
248
|
+
* @param {Object} response - API response
|
|
249
|
+
* @param {string} deploymentId - Deployment ID
|
|
250
|
+
* @throws {Error} Appropriate error message
|
|
251
|
+
*/
|
|
252
|
+
function handleDeploymentStatusError(response, deploymentId) {
|
|
253
|
+
if (response.status === 404) {
|
|
254
|
+
throw new Error(`Deployment ${deploymentId || response.deploymentId || 'unknown'} not found`);
|
|
251
255
|
}
|
|
256
|
+
throw new Error(`Status check failed: ${response.formattedError || response.error || 'Unknown error'}`);
|
|
257
|
+
}
|
|
252
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Extracts deployment data from response
|
|
261
|
+
* @function extractDeploymentData
|
|
262
|
+
* @param {Object} response - API response
|
|
263
|
+
* @returns {Object} Deployment data
|
|
264
|
+
*/
|
|
265
|
+
function extractDeploymentData(response) {
|
|
253
266
|
const responseData = response.data;
|
|
254
|
-
|
|
267
|
+
return responseData.data || responseData;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Logs deployment progress
|
|
272
|
+
* @function logDeploymentProgress
|
|
273
|
+
* @param {Object} deploymentData - Deployment data
|
|
274
|
+
* @param {number} attempt - Current attempt
|
|
275
|
+
* @param {number} maxAttempts - Maximum attempts
|
|
276
|
+
*/
|
|
277
|
+
function logDeploymentProgress(deploymentData, attempt, maxAttempts) {
|
|
255
278
|
const status = deploymentData.status;
|
|
279
|
+
const progress = deploymentData.progress || 0;
|
|
280
|
+
logger.log(chalk.blue(` Status: ${status} (${progress}%) (attempt ${attempt + 1}/${maxAttempts})`));
|
|
281
|
+
}
|
|
256
282
|
|
|
257
|
-
|
|
258
|
-
|
|
283
|
+
async function processDeploymentStatusResponse(response, attempt, maxAttempts, interval, deploymentId) {
|
|
284
|
+
if (!response.success || !response.data) {
|
|
285
|
+
handleDeploymentStatusError(response, deploymentId);
|
|
259
286
|
}
|
|
260
287
|
|
|
261
|
-
const
|
|
262
|
-
|
|
288
|
+
const deploymentData = extractDeploymentData(response);
|
|
289
|
+
if (isTerminalStatus(deploymentData.status)) {
|
|
290
|
+
return deploymentData;
|
|
291
|
+
}
|
|
263
292
|
|
|
293
|
+
logDeploymentProgress(deploymentData, attempt, maxAttempts);
|
|
264
294
|
if (attempt < maxAttempts - 1) {
|
|
265
295
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
266
296
|
}
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
|
-
const logger = require('
|
|
14
|
-
const config = require('
|
|
15
|
-
const { validateControllerUrl, validateEnvironmentKey } = require('
|
|
16
|
-
const { getOrRefreshDeviceToken } = require('
|
|
17
|
-
const { getEnvironmentStatus } = require('
|
|
18
|
-
const { deployEnvironment: deployEnvironmentInfra } = require('
|
|
19
|
-
const { handleDeploymentErrors } = require('
|
|
20
|
-
const auditLogger = require('
|
|
13
|
+
const logger = require('../utils/logger');
|
|
14
|
+
const config = require('../core/config');
|
|
15
|
+
const { validateControllerUrl, validateEnvironmentKey } = require('../utils/deployment-validation');
|
|
16
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
17
|
+
const { getEnvironmentStatus } = require('../api/environments.api');
|
|
18
|
+
const { deployEnvironment: deployEnvironmentInfra } = require('../api/deployments.api');
|
|
19
|
+
const { handleDeploymentErrors } = require('../utils/deployment-errors');
|
|
20
|
+
const auditLogger = require('../core/audit-logger');
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Validates environment deployment prerequisites
|
|
@@ -76,51 +76,78 @@ async function getEnvironmentAuth(controllerUrl) {
|
|
|
76
76
|
* @returns {Promise<Object>} Deployment result
|
|
77
77
|
* @throws {Error} If deployment fails
|
|
78
78
|
*/
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Builds environment deployment request
|
|
81
|
+
* @function buildEnvironmentDeploymentRequest
|
|
82
|
+
* @param {string} validatedEnvKey - Validated environment key
|
|
83
|
+
* @param {Object} options - Deployment options
|
|
84
|
+
* @returns {Object} Deployment request object
|
|
85
|
+
*/
|
|
86
|
+
function buildEnvironmentDeploymentRequest(validatedEnvKey, options) {
|
|
87
|
+
const capitalized = validatedEnvKey.charAt(0).toUpperCase() + validatedEnvKey.slice(1);
|
|
88
|
+
const request = {
|
|
85
89
|
key: validatedEnvKey,
|
|
86
|
-
displayName: `${
|
|
87
|
-
description: `${
|
|
90
|
+
displayName: `${capitalized} Environment`,
|
|
91
|
+
description: `${capitalized} environment for application deployments`
|
|
88
92
|
};
|
|
89
93
|
|
|
90
|
-
// Add configuration if provided
|
|
91
94
|
if (options.config) {
|
|
92
|
-
|
|
93
|
-
// For now, just include the config path in description
|
|
94
|
-
deploymentRequest.description += ` (config: ${options.config})`;
|
|
95
|
+
request.description += ` (config: ${options.config})`;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
return request;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Handles deployment API error response
|
|
103
|
+
* @function handleDeploymentApiError
|
|
104
|
+
* @param {Object} response - API response
|
|
105
|
+
* @throws {Error} Deployment error
|
|
106
|
+
*/
|
|
107
|
+
function handleDeploymentApiError(response) {
|
|
108
|
+
const error = new Error(response.formattedError || response.error || 'Environment deployment failed');
|
|
109
|
+
error.status = response.status;
|
|
110
|
+
error.data = response.errorData || response.data;
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Builds deployment result from API response
|
|
116
|
+
* @function buildDeploymentResult
|
|
117
|
+
* @param {Object} response - API response
|
|
118
|
+
* @param {string} validatedEnvKey - Validated environment key
|
|
119
|
+
* @param {string} validatedUrl - Validated controller URL
|
|
120
|
+
* @returns {Object} Deployment result
|
|
121
|
+
*/
|
|
122
|
+
function buildDeploymentResult(response, validatedEnvKey, validatedUrl) {
|
|
123
|
+
const responseData = response.data.data || response.data || {};
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
environment: validatedEnvKey,
|
|
127
|
+
deploymentId: responseData.deploymentId || responseData.id,
|
|
128
|
+
status: responseData.status || 'initiated',
|
|
129
|
+
url: responseData.url || `${validatedUrl}/environments/${validatedEnvKey}`,
|
|
130
|
+
message: responseData.message
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function sendEnvironmentDeployment(controllerUrl, envKey, authConfig, options = {}) {
|
|
135
|
+
const validatedUrl = validateControllerUrl(controllerUrl);
|
|
136
|
+
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
137
|
+
const deploymentRequest = buildEnvironmentDeploymentRequest(validatedEnvKey, options);
|
|
138
|
+
|
|
98
139
|
await auditLogger.logDeploymentAttempt(validatedEnvKey, validatedUrl, options);
|
|
99
140
|
|
|
100
141
|
try {
|
|
101
|
-
// Use centralized API client
|
|
102
142
|
const apiAuthConfig = { type: 'bearer', token: authConfig.token };
|
|
103
143
|
const response = await deployEnvironmentInfra(validatedUrl, validatedEnvKey, apiAuthConfig, deploymentRequest);
|
|
104
144
|
|
|
105
145
|
if (!response.success) {
|
|
106
|
-
|
|
107
|
-
error.status = response.status;
|
|
108
|
-
error.data = response.errorData || response.data;
|
|
109
|
-
throw error;
|
|
146
|
+
handleDeploymentApiError(response);
|
|
110
147
|
}
|
|
111
148
|
|
|
112
|
-
|
|
113
|
-
const responseData = response.data.data || response.data || {};
|
|
114
|
-
return {
|
|
115
|
-
success: true,
|
|
116
|
-
environment: validatedEnvKey,
|
|
117
|
-
deploymentId: responseData.deploymentId || responseData.id,
|
|
118
|
-
status: responseData.status || 'initiated',
|
|
119
|
-
url: responseData.url || `${validatedUrl}/environments/${validatedEnvKey}`,
|
|
120
|
-
message: responseData.message
|
|
121
|
-
};
|
|
149
|
+
return buildDeploymentResult(response, validatedEnvKey, validatedUrl);
|
|
122
150
|
} catch (error) {
|
|
123
|
-
// Use unified error handler
|
|
124
151
|
await handleDeploymentErrors(error, validatedEnvKey, validatedUrl, false);
|
|
125
152
|
throw error;
|
|
126
153
|
}
|
|
@@ -241,60 +268,112 @@ function displayDeploymentResults(result) {
|
|
|
241
268
|
* @example
|
|
242
269
|
* await deployEnvironment('dev', { controller: 'https://controller.aifabrix.ai' });
|
|
243
270
|
*/
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Validates deployment input parameters
|
|
273
|
+
* @function validateDeploymentInput
|
|
274
|
+
* @param {string} envKey - Environment key
|
|
275
|
+
* @param {Object} options - Deployment options
|
|
276
|
+
* @returns {string} Controller URL
|
|
277
|
+
* @throws {Error} If validation fails
|
|
278
|
+
*/
|
|
279
|
+
function validateDeploymentInput(envKey, options) {
|
|
280
|
+
if (!envKey || typeof envKey !== 'string' || envKey.trim().length === 0) {
|
|
281
|
+
throw new Error('Environment key is required');
|
|
282
|
+
}
|
|
250
283
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
284
|
+
const controllerUrl = options.controller || options['controller-url'];
|
|
285
|
+
if (!controllerUrl) {
|
|
286
|
+
throw new Error('Controller URL is required. Use --controller flag');
|
|
287
|
+
}
|
|
255
288
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
289
|
+
return controllerUrl;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Prepares environment deployment
|
|
294
|
+
* @async
|
|
295
|
+
* @function prepareEnvironmentDeployment
|
|
296
|
+
* @param {string} envKey - Environment key
|
|
297
|
+
* @param {string} controllerUrl - Controller URL
|
|
298
|
+
* @param {Object} options - Deployment options
|
|
299
|
+
* @returns {Promise<Object>} Authentication configuration
|
|
300
|
+
*/
|
|
301
|
+
async function prepareEnvironmentDeployment(envKey, controllerUrl, options) {
|
|
302
|
+
// Validate prerequisites
|
|
303
|
+
if (!options.skipValidation) {
|
|
304
|
+
validateEnvironmentPrerequisites(envKey, controllerUrl);
|
|
305
|
+
}
|
|
260
306
|
|
|
261
|
-
|
|
262
|
-
|
|
307
|
+
// Update root-level environment in config.yaml
|
|
308
|
+
await config.setCurrentEnvironment(envKey);
|
|
263
309
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
310
|
+
// Get authentication (device token)
|
|
311
|
+
logger.log(chalk.blue(`\n📋 Deploying environment '${envKey}' to ${controllerUrl}...`));
|
|
312
|
+
const authConfig = await getEnvironmentAuth(controllerUrl);
|
|
313
|
+
logger.log(chalk.green('✓ Environment validated'));
|
|
314
|
+
logger.log(chalk.green('✓ Authentication successful'));
|
|
269
315
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
316
|
+
return authConfig;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Executes environment deployment
|
|
321
|
+
* @async
|
|
322
|
+
* @function executeEnvironmentDeployment
|
|
323
|
+
* @param {string} validatedControllerUrl - Validated controller URL
|
|
324
|
+
* @param {string} envKey - Environment key
|
|
325
|
+
* @param {Object} authConfig - Authentication configuration
|
|
326
|
+
* @param {Object} options - Deployment options
|
|
327
|
+
* @returns {Promise<Object>} Deployment result
|
|
328
|
+
*/
|
|
329
|
+
async function executeEnvironmentDeployment(validatedControllerUrl, envKey, authConfig, options) {
|
|
330
|
+
logger.log(chalk.blue('\n🚀 Deploying environment infrastructure...'));
|
|
331
|
+
const result = await sendEnvironmentDeployment(validatedControllerUrl, envKey, authConfig, options);
|
|
332
|
+
logger.log(chalk.blue(`📤 Sending deployment request to ${validatedControllerUrl}/api/v1/environments/${envKey}/deploy...`));
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Polls deployment status if enabled
|
|
338
|
+
* @async
|
|
339
|
+
* @function pollDeploymentStatusIfEnabled
|
|
340
|
+
* @param {Object} result - Deployment result
|
|
341
|
+
* @param {string} validatedControllerUrl - Validated controller URL
|
|
342
|
+
* @param {string} envKey - Environment key
|
|
343
|
+
* @param {Object} authConfig - Authentication configuration
|
|
344
|
+
* @param {Object} options - Deployment options
|
|
345
|
+
* @returns {Promise<Object>} Updated result with status
|
|
346
|
+
*/
|
|
347
|
+
async function pollDeploymentStatusIfEnabled(result, validatedControllerUrl, envKey, authConfig, options) {
|
|
348
|
+
const shouldPoll = options.poll !== false && !options.noPoll;
|
|
349
|
+
if (shouldPoll && result.deploymentId) {
|
|
350
|
+
const pollResult = await pollEnvironmentStatus(
|
|
351
|
+
result.deploymentId,
|
|
352
|
+
validatedControllerUrl,
|
|
353
|
+
envKey,
|
|
354
|
+
authConfig,
|
|
355
|
+
{
|
|
356
|
+
pollInterval: 5000,
|
|
357
|
+
maxAttempts: 60
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
result.status = pollResult.status;
|
|
361
|
+
result.message = pollResult.message;
|
|
362
|
+
}
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
293
365
|
|
|
294
|
-
|
|
295
|
-
|
|
366
|
+
async function deployEnvironment(envKey, options = {}) {
|
|
367
|
+
try {
|
|
368
|
+
const controllerUrl = validateDeploymentInput(envKey, options);
|
|
369
|
+
const authConfig = await prepareEnvironmentDeployment(envKey, controllerUrl, options);
|
|
370
|
+
|
|
371
|
+
const validatedControllerUrl = validateControllerUrl(authConfig.controller);
|
|
372
|
+
const result = await executeEnvironmentDeployment(validatedControllerUrl, envKey, authConfig, options);
|
|
373
|
+
const finalResult = await pollDeploymentStatusIfEnabled(result, validatedControllerUrl, envKey, authConfig, options);
|
|
296
374
|
|
|
297
|
-
|
|
375
|
+
displayDeploymentResults(finalResult);
|
|
376
|
+
return finalResult;
|
|
298
377
|
} catch (error) {
|
|
299
378
|
// Error handling is done in sendEnvironmentDeployment and pollEnvironmentStatus
|
|
300
379
|
// Re-throw with context
|