@aifabrix/builder 2.31.0 → 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.
Files changed (119) hide show
  1. package/README.md +9 -9
  2. package/integration/hubspot/README.md +2 -2
  3. package/integration/hubspot/hubspot-deploy-company.json +17 -14
  4. package/integration/hubspot/hubspot-deploy-contact.json +19 -16
  5. package/integration/hubspot/hubspot-deploy-deal.json +21 -18
  6. package/lib/api/types/datasources.types.js +31 -5
  7. package/lib/api/types/wizard.types.js +142 -0
  8. package/lib/api/wizard.api.js +177 -0
  9. package/lib/{app-config.js → app/config.js} +4 -4
  10. package/lib/{app-deploy.js → app/deploy.js} +8 -8
  11. package/lib/app/display.js +90 -0
  12. package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
  13. package/lib/{app-down.js → app/down.js} +4 -4
  14. package/lib/app/helpers.js +218 -0
  15. package/lib/app/index.js +298 -0
  16. package/lib/{app-list.js → app/list.js} +6 -6
  17. package/lib/{app-push.js → app/push.js} +4 -4
  18. package/lib/{app-readme.js → app/readme.js} +34 -13
  19. package/lib/{app-register.js → app/register.js} +9 -9
  20. package/lib/{app-rotate-secret.js → app/rotate-secret.js} +123 -37
  21. package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
  22. package/lib/{app-run.js → app/run.js} +6 -6
  23. package/lib/{build.js → build/index.js} +59 -32
  24. package/lib/build/package.json +7 -0
  25. package/lib/cli.js +245 -179
  26. package/lib/commands/app.js +3 -3
  27. package/lib/commands/datasource.js +4 -4
  28. package/lib/commands/login-credentials.js +209 -0
  29. package/lib/commands/login-device.js +254 -0
  30. package/lib/commands/login.js +67 -378
  31. package/lib/commands/logout.js +1 -1
  32. package/lib/commands/secrets-set.js +1 -1
  33. package/lib/commands/secure.js +2 -2
  34. package/lib/commands/wizard.js +498 -0
  35. package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
  36. package/lib/{config.js → core/config.js} +28 -26
  37. package/lib/{diff.js → core/diff.js} +157 -72
  38. package/lib/{secrets.js → core/secrets.js} +86 -49
  39. package/lib/{templates.js → core/templates-env.js} +14 -222
  40. package/lib/core/templates.js +279 -0
  41. package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
  42. package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
  43. package/lib/datasource/list.js +223 -0
  44. package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
  45. package/lib/{deployer.js → deployment/deployer.js} +48 -18
  46. package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
  47. package/lib/{push.js → deployment/push.js} +1 -1
  48. package/lib/external-system/deploy-helpers.js +145 -0
  49. package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
  50. package/lib/external-system/download-helpers.js +114 -0
  51. package/lib/{external-system-download.js → external-system/download.js} +92 -135
  52. package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
  53. package/lib/external-system/test-auth.js +40 -0
  54. package/lib/external-system/test-execution.js +84 -0
  55. package/lib/external-system/test-helpers.js +109 -0
  56. package/lib/{external-system-test.js → external-system/test.js} +174 -192
  57. package/lib/{generator-builders.js → generator/builders.js} +87 -10
  58. package/lib/{generator-external.js → generator/external.js} +115 -52
  59. package/lib/{github-generator.js → generator/github.js} +116 -15
  60. package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
  61. package/lib/{generator.js → generator/index.js} +49 -22
  62. package/lib/{generator-split.js → generator/split.js} +108 -55
  63. package/lib/generator/wizard-prompts.js +357 -0
  64. package/lib/generator/wizard.js +490 -0
  65. package/lib/{infra.js → infrastructure/index.js} +49 -22
  66. package/lib/schema/external-datasource.schema.json +145 -133
  67. package/lib/schema/external-system.schema.json +42 -0
  68. package/lib/utils/api.js +9 -5
  69. package/lib/utils/app-register-api.js +60 -32
  70. package/lib/utils/app-register-auth.js +172 -47
  71. package/lib/utils/app-register-config.js +130 -59
  72. package/lib/utils/app-run-containers.js +29 -8
  73. package/lib/utils/build-helpers.js +1 -1
  74. package/lib/utils/cli-utils.js +78 -30
  75. package/lib/utils/compose-generator.js +145 -65
  76. package/lib/utils/config-paths.js +2 -0
  77. package/lib/utils/deployment-errors.js +1 -1
  78. package/lib/utils/device-code.js +99 -41
  79. package/lib/utils/env-config-loader.js +1 -1
  80. package/lib/utils/env-copy.js +21 -18
  81. package/lib/utils/env-endpoints.js +115 -67
  82. package/lib/utils/env-map.js +13 -14
  83. package/lib/utils/env-ports.js +45 -25
  84. package/lib/utils/env-template.js +84 -42
  85. package/lib/utils/error-formatter.js +26 -9
  86. package/lib/utils/error-formatters/error-parser.js +90 -4
  87. package/lib/utils/error-formatters/http-status-errors.js +54 -17
  88. package/lib/utils/error-formatters/network-errors.js +103 -26
  89. package/lib/utils/external-system-display.js +184 -90
  90. package/lib/utils/external-system-validators.js +164 -42
  91. package/lib/utils/file-upload.js +109 -0
  92. package/lib/utils/health-check.js +199 -83
  93. package/lib/utils/infra-containers.js +1 -1
  94. package/lib/utils/infra-status.js +66 -15
  95. package/lib/utils/local-secrets.js +45 -25
  96. package/lib/utils/paths.js +45 -33
  97. package/lib/utils/schema-loader.js +42 -25
  98. package/lib/utils/schema-resolver.js +123 -74
  99. package/lib/utils/secrets-encryption.js +62 -25
  100. package/lib/utils/secrets-helpers.js +126 -63
  101. package/lib/utils/secrets-path.js +1 -1
  102. package/lib/utils/secrets-url.js +1 -1
  103. package/lib/utils/token-manager-refresh.js +181 -0
  104. package/lib/utils/token-manager.js +76 -123
  105. package/lib/utils/variable-transformer.js +154 -77
  106. package/lib/utils/yaml-preserve.js +41 -47
  107. package/lib/{template-validator.js → validation/template.js} +54 -23
  108. package/lib/{validate.js → validation/validate.js} +205 -125
  109. package/lib/{validator.js → validation/validator.js} +58 -39
  110. package/package.json +34 -3
  111. package/scripts/install-local.js +210 -0
  112. package/templates/external-system/deploy.ps1.hbs +34 -0
  113. package/templates/external-system/deploy.sh.hbs +34 -0
  114. package/templates/external-system/external-datasource.json.hbs +31 -12
  115. package/lib/app.js +0 -467
  116. package/lib/datasource-list.js +0 -141
  117. /package/lib/{app-prompts.js → app/prompts.js} +0 -0
  118. /package/lib/{env-reader.js → core/env-reader.js} +0 -0
  119. /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('./utils/schema-loader');
13
- const { formatValidationErrors } = require('./utils/error-formatter');
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('./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');
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('./utils/token-manager');
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
- async function processDeploymentStatusResponse(response, attempt, maxAttempts, interval, deploymentId) {
246
- if (!response.success || !response.data) {
247
- if (response.status === 404) {
248
- throw new Error(`Deployment ${deploymentId || response.deploymentId || 'unknown'} not found`);
249
- }
250
- throw new Error(`Status check failed: ${response.formattedError || response.error || 'Unknown error'}`);
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
- const deploymentData = responseData.data || responseData;
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
- if (isTerminalStatus(status)) {
258
- return deploymentData;
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 progress = deploymentData.progress || 0;
262
- logger.log(chalk.blue(` Status: ${status} (${progress}%) (attempt ${attempt + 1}/${maxAttempts})`));
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('./utils/logger');
14
- const config = require('./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('./audit-logger');
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
- async function sendEnvironmentDeployment(controllerUrl, envKey, authConfig, options = {}) {
80
- const validatedUrl = validateControllerUrl(controllerUrl);
81
- const validatedEnvKey = validateEnvironmentKey(envKey);
82
-
83
- // Build environment deployment request
84
- const deploymentRequest = {
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: `${validatedEnvKey.charAt(0).toUpperCase() + validatedEnvKey.slice(1)} Environment`,
87
- description: `${validatedEnvKey.charAt(0).toUpperCase() + validatedEnvKey.slice(1)} environment for application deployments`
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
- // TODO: Load and parse config file if provided
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
- // Log deployment attempt for audit
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
- const error = new Error(response.formattedError || response.error || 'Environment deployment failed');
107
- error.status = response.status;
108
- error.data = response.errorData || response.data;
109
- throw error;
146
+ handleDeploymentApiError(response);
110
147
  }
111
148
 
112
- // Handle response structure
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
- async function deployEnvironment(envKey, options = {}) {
245
- try {
246
- // 1. Input validation
247
- if (!envKey || typeof envKey !== 'string' || envKey.trim().length === 0) {
248
- throw new Error('Environment key is required');
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
- const controllerUrl = options.controller || options['controller-url'];
252
- if (!controllerUrl) {
253
- throw new Error('Controller URL is required. Use --controller flag');
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
- // 2. Validate prerequisites
257
- if (!options.skipValidation) {
258
- validateEnvironmentPrerequisites(envKey, controllerUrl);
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
- // 3. Update root-level environment in config.yaml
262
- await config.setCurrentEnvironment(envKey);
307
+ // Update root-level environment in config.yaml
308
+ await config.setCurrentEnvironment(envKey);
263
309
 
264
- // 4. Get authentication (device token)
265
- logger.log(chalk.blue(`\n📋 Deploying environment '${envKey}' to ${controllerUrl}...`));
266
- const authConfig = await getEnvironmentAuth(controllerUrl);
267
- logger.log(chalk.green('✓ Environment validated'));
268
- logger.log(chalk.green('✓ Authentication successful'));
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
- // 5. Send environment deployment request
271
- logger.log(chalk.blue('\n🚀 Deploying environment infrastructure...'));
272
- const validatedControllerUrl = validateControllerUrl(authConfig.controller);
273
- const result = await sendEnvironmentDeployment(validatedControllerUrl, envKey, authConfig, options);
274
-
275
- logger.log(chalk.blue(`📤 Sending deployment request to ${validatedControllerUrl}/api/v1/environments/${envKey}/deploy...`));
276
-
277
- // 6. Poll for status if enabled
278
- const shouldPoll = options.poll !== false && !options.noPoll;
279
- if (shouldPoll && result.deploymentId) {
280
- const pollResult = await pollEnvironmentStatus(
281
- result.deploymentId,
282
- validatedControllerUrl,
283
- envKey,
284
- authConfig,
285
- {
286
- pollInterval: 5000,
287
- maxAttempts: 60
288
- }
289
- );
290
- result.status = pollResult.status;
291
- result.message = pollResult.message;
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
- // 7. Display results
295
- displayDeploymentResults(result);
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
- return result;
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
@@ -12,7 +12,7 @@
12
12
  const { exec } = require('child_process');
13
13
  const { promisify } = require('util');
14
14
  const chalk = require('chalk');
15
- const logger = require('./utils/logger');
15
+ const logger = require('../utils/logger');
16
16
 
17
17
  const execAsync = promisify(exec);
18
18