@aifabrix/builder 2.11.0 → 2.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.cursor/rules/project-rules.mdc +194 -0
  2. package/README.md +12 -0
  3. package/lib/api/applications.api.js +164 -0
  4. package/lib/api/auth.api.js +303 -0
  5. package/lib/api/datasources-core.api.js +87 -0
  6. package/lib/api/datasources-extended.api.js +117 -0
  7. package/lib/api/datasources.api.js +13 -0
  8. package/lib/api/deployments.api.js +126 -0
  9. package/lib/api/environments.api.js +245 -0
  10. package/lib/api/external-systems.api.js +251 -0
  11. package/lib/api/index.js +236 -0
  12. package/lib/api/pipeline.api.js +234 -0
  13. package/lib/api/types/applications.types.js +136 -0
  14. package/lib/api/types/auth.types.js +218 -0
  15. package/lib/api/types/datasources.types.js +272 -0
  16. package/lib/api/types/deployments.types.js +184 -0
  17. package/lib/api/types/environments.types.js +197 -0
  18. package/lib/api/types/external-systems.types.js +244 -0
  19. package/lib/api/types/pipeline.types.js +125 -0
  20. package/lib/app-list.js +5 -7
  21. package/lib/app-rotate-secret.js +4 -10
  22. package/lib/cli.js +30 -0
  23. package/lib/commands/login.js +41 -12
  24. package/lib/datasource-deploy.js +7 -30
  25. package/lib/datasource-list.js +9 -6
  26. package/lib/deployer.js +103 -135
  27. package/lib/environment-deploy.js +15 -26
  28. package/lib/external-system-deploy.js +12 -39
  29. package/lib/external-system-download.js +5 -13
  30. package/lib/external-system-test.js +9 -12
  31. package/lib/generator-split.js +342 -0
  32. package/lib/generator.js +94 -5
  33. package/lib/utils/app-register-api.js +5 -10
  34. package/lib/utils/deployment-errors.js +88 -6
  35. package/package.json +1 -1
  36. package/tatus +0 -181
@@ -13,7 +13,8 @@ const inquirer = require('inquirer');
13
13
  const chalk = require('chalk');
14
14
  const ora = require('ora');
15
15
  const { setCurrentEnvironment, saveDeviceToken, saveClientToken } = require('../config');
16
- const { makeApiCall, initiateDeviceCodeFlow, pollDeviceCodeToken, displayDeviceCodeInfo } = require('../utils/api');
16
+ const { getToken, initiateDeviceCodeFlow } = require('../api/auth.api');
17
+ const { pollDeviceCodeToken, displayDeviceCodeInfo } = require('../utils/api');
17
18
  const { formatApiError } = require('../utils/api-error-handler');
18
19
  const { loadClientCredentials } = require('../utils/token-manager');
19
20
  const logger = require('../utils/logger');
@@ -187,16 +188,8 @@ async function handleCredentialsLogin(controllerUrl, appName, clientId, clientSe
187
188
  credentials = await promptForCredentials(clientId, clientSecret);
188
189
  }
189
190
 
190
- // OpenAPI spec: POST /api/v1/auth/token with x-client-id and x-client-secret headers
191
- // Response: { success: boolean, token: string, expiresIn: number, expiresAt: string, timestamp: string }
192
- const response = await makeApiCall(`${controllerUrl}/api/v1/auth/token`, {
193
- method: 'POST',
194
- headers: {
195
- 'Content-Type': 'application/json',
196
- 'x-client-id': credentials.clientId,
197
- 'x-client-secret': credentials.clientSecret
198
- }
199
- });
191
+ // Use centralized API client for token generation
192
+ const response = await getToken(credentials.clientId, credentials.clientSecret, controllerUrl);
200
193
 
201
194
  if (!response.success) {
202
195
  const formattedError = response.formattedError || formatApiError(response);
@@ -346,7 +339,43 @@ async function handleDeviceCodeLogin(controllerUrl, environment, offline, scope)
346
339
  }
347
340
 
348
341
  try {
349
- const deviceCodeResponse = await initiateDeviceCodeFlow(controllerUrl, envKey, requestScope);
342
+ // Use centralized API client for device code flow initiation
343
+ const deviceCodeApiResponse = await initiateDeviceCodeFlow(controllerUrl, envKey, requestScope);
344
+
345
+ // Validate response structure
346
+ if (!deviceCodeApiResponse) {
347
+ throw new Error('Device code flow initiation returned no response');
348
+ }
349
+
350
+ // Check if API call was successful
351
+ if (!deviceCodeApiResponse.success) {
352
+ const errorMessage = deviceCodeApiResponse.formattedError ||
353
+ deviceCodeApiResponse.error ||
354
+ 'Device code flow initiation failed';
355
+ const error = new Error(errorMessage);
356
+ // Preserve formattedError for better error display
357
+ if (deviceCodeApiResponse.formattedError) {
358
+ error.formattedError = deviceCodeApiResponse.formattedError;
359
+ }
360
+ throw error;
361
+ }
362
+
363
+ // Handle API response format: { success: boolean, data: DeviceCodeResponse }
364
+ if (!deviceCodeApiResponse.data) {
365
+ throw new Error('Device code flow initiation returned no data');
366
+ }
367
+
368
+ const apiResponse = deviceCodeApiResponse.data;
369
+ const deviceCodeData = apiResponse.data || apiResponse;
370
+
371
+ // Convert camelCase from API to snake_case for compatibility with existing code
372
+ const deviceCodeResponse = {
373
+ device_code: deviceCodeData.deviceCode || deviceCodeData.device_code,
374
+ user_code: deviceCodeData.userCode || deviceCodeData.user_code,
375
+ verification_uri: deviceCodeData.verificationUri || deviceCodeData.verification_uri,
376
+ expires_in: deviceCodeData.expiresIn || deviceCodeData.expires_in || 600,
377
+ interval: deviceCodeData.interval || 5
378
+ };
350
379
 
351
380
  displayDeviceCodeInfo(deviceCodeResponse.user_code, deviceCodeResponse.verification_uri, logger, chalk);
352
381
 
@@ -12,7 +12,8 @@
12
12
  const fs = require('fs');
13
13
  const chalk = require('chalk');
14
14
  const { getDeploymentAuth } = require('./utils/token-manager');
15
- const { authenticatedApiCall } = require('./utils/api');
15
+ const { getEnvironmentApplication } = require('./api/environments.api');
16
+ const { publishDatasourceViaPipeline } = require('./api/pipeline.api');
16
17
  const { formatApiError } = require('./utils/api-error-handler');
17
18
  const logger = require('./utils/logger');
18
19
  const { validateDatasourceFile } = require('./datasource-validate');
@@ -30,18 +31,8 @@ const { validateDatasourceFile } = require('./datasource-validate');
30
31
  * @throws {Error} If dataplane URL cannot be retrieved
31
32
  */
32
33
  async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
33
- // Call controller API to get application details
34
- // Expected: GET /api/v1/environments/{env}/applications/{appKey}
35
- const endpoint = `${controllerUrl}/api/v1/environments/${environment}/applications/${appKey}`;
36
-
37
- let response;
38
- if (authConfig.type === 'bearer' && authConfig.token) {
39
- response = await authenticatedApiCall(endpoint, {}, authConfig.token);
40
- } else {
41
- // For credentials, we'd need to use a different API call method
42
- // For now, use bearer token approach
43
- throw new Error('Bearer token authentication required for getting dataplane URL');
44
- }
34
+ // Call controller API to get application details using centralized API client
35
+ const response = await getEnvironmentApplication(controllerUrl, environment, appKey, authConfig);
45
36
 
46
37
  if (!response.success || !response.data) {
47
38
  const formattedError = response.formattedError || formatApiError(response);
@@ -129,24 +120,10 @@ async function deployDatasource(appKey, filePath, options) {
129
120
  const dataplaneUrl = await getDataplaneUrl(options.controller, appKey, options.environment, authConfig);
130
121
  logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
131
122
 
132
- // Publish to dataplane (using publish endpoint)
123
+ // Publish to dataplane using pipeline workflow endpoint
133
124
  logger.log(chalk.blue('\n🚀 Publishing datasource to dataplane...'));
134
- const publishEndpoint = `${dataplaneUrl}/api/v1/pipeline/${systemKey}/publish`;
135
-
136
- // Prepare publish request - send datasource configuration directly
137
- let publishResponse;
138
- if (authConfig.type === 'bearer' && authConfig.token) {
139
- publishResponse = await authenticatedApiCall(
140
- publishEndpoint,
141
- {
142
- method: 'POST',
143
- body: JSON.stringify(datasourceConfig)
144
- },
145
- authConfig.token
146
- );
147
- } else {
148
- throw new Error('Bearer token authentication required for dataplane publish');
149
- }
125
+
126
+ const publishResponse = await publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceConfig);
150
127
 
151
128
  if (!publishResponse.success) {
152
129
  const formattedError = publishResponse.formattedError || formatApiError(publishResponse);
@@ -11,7 +11,7 @@
11
11
  const chalk = require('chalk');
12
12
  const { getConfig } = require('./config');
13
13
  const { getOrRefreshDeviceToken } = require('./utils/token-manager');
14
- const { authenticatedApiCall } = require('./utils/api');
14
+ const { listEnvironmentDatasources } = require('./api/environments.api');
15
15
  const { formatApiError } = require('./utils/api-error-handler');
16
16
  const logger = require('./utils/logger');
17
17
 
@@ -20,11 +20,15 @@ const logger = require('./utils/logger');
20
20
  * Handles multiple response formats similar to applications list
21
21
  *
22
22
  * @function extractDatasources
23
- * @param {Object} response - API response from authenticatedApiCall
23
+ * @param {Object} response - API response from centralized API client
24
24
  * @returns {Array} Array of datasources
25
25
  * @throws {Error} If response format is invalid
26
26
  */
27
27
  function extractDatasources(response) {
28
+ if (!response.success || !response.data) {
29
+ throw new Error('Invalid API response: missing success or data');
30
+ }
31
+
28
32
  const apiResponse = response.data;
29
33
  let datasources;
30
34
 
@@ -112,10 +116,9 @@ async function listDatasources(options) {
112
116
  process.exit(1);
113
117
  }
114
118
 
115
- // Call controller API - using placeholder endpoint until full specs available
116
- // Expected: GET /api/v1/environments/{env}/datasources
117
- const endpoint = `${controllerUrl}/api/v1/environments/${options.environment}/datasources`;
118
- const response = await authenticatedApiCall(endpoint, {}, token);
119
+ // Call controller API using centralized API client
120
+ const authConfig = { type: 'bearer', token };
121
+ const response = await listEnvironmentDatasources(controllerUrl, options.environment, authConfig);
119
122
 
120
123
  if (!response.success || !response.data) {
121
124
  const formattedError = response.formattedError || formatApiError(response);
package/lib/deployer.js CHANGED
@@ -9,84 +9,12 @@
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
- const axios = require('axios');
13
12
  const chalk = require('chalk');
14
13
  const auditLogger = require('./audit-logger');
15
14
  const logger = require('./utils/logger');
16
- const { createAuthHeaders } = require('./utils/auth-headers');
17
15
  const { validateControllerUrl, validateEnvironmentKey } = require('./utils/deployment-validation');
18
16
  const { handleDeploymentError, handleDeploymentErrors } = require('./utils/deployment-errors');
19
-
20
- /**
21
- * Handles deployment request retry logic
22
- * @async
23
- * @function handleDeploymentRetry
24
- * @param {string} endpoint - API endpoint
25
- * @param {Object} manifest - Deployment manifest
26
- * @param {Object} requestConfig - Request configuration
27
- * @param {number} maxRetries - Maximum retry attempts
28
- * @returns {Promise<Object>} Deployment result
29
- * @throws {Error} If deployment fails after retries
30
- */
31
- async function handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetries) {
32
- let lastError;
33
-
34
- // Validate manifest before sending
35
- if (!manifest || typeof manifest !== 'object') {
36
- throw new Error('Deployment manifest is required and must be an object');
37
- }
38
-
39
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
40
- try {
41
- // Axios automatically serializes objects to JSON when Content-Type is application/json
42
- const response = await axios.post(endpoint, manifest, requestConfig);
43
-
44
- if (response.status >= 400) {
45
- const error = new Error(`Controller returned error: ${response.status} ${response.statusText}`);
46
- error.status = response.status;
47
- error.response = {
48
- status: response.status,
49
- statusText: response.statusText,
50
- data: response.data
51
- };
52
- error.data = response.data;
53
- throw error;
54
- }
55
-
56
- // OpenAPI spec: Response 202 structure { success: boolean, deploymentId: string, status: string, deploymentUrl?: string, healthCheckUrl?: string, message?: string }
57
- // Handle both OpenAPI format and legacy format for backward compatibility
58
- const responseData = response.data;
59
- if (responseData && typeof responseData === 'object') {
60
- // OpenAPI format: { success, deploymentId, status, deploymentUrl, healthCheckUrl, message }
61
- return responseData;
62
- }
63
-
64
- return response.data;
65
- } catch (error) {
66
- lastError = error;
67
-
68
- if (attempt < maxRetries) {
69
- const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
70
- logger.log(chalk.yellow(`⚠️ Deployment attempt ${attempt} failed, retrying in ${delay}ms...`));
71
- await new Promise(resolve => setTimeout(resolve, delay));
72
- }
73
- }
74
- }
75
-
76
- // Preserve formatted error if available
77
- const errorMessage = lastError.formatted || lastError.message;
78
- const error = new Error(`Deployment failed after ${maxRetries} attempts: ${errorMessage}`);
79
- if (lastError.formatted) {
80
- error.formatted = lastError.formatted;
81
- }
82
- if (lastError.status) {
83
- error.status = lastError.status;
84
- }
85
- if (lastError.data) {
86
- error.data = lastError.data;
87
- }
88
- throw error;
89
- }
17
+ const { validatePipeline, deployPipeline, getPipelineDeployment } = require('./api/pipeline.api');
90
18
 
91
19
  /**
92
20
  * Validates deployment configuration via validate endpoint
@@ -103,8 +31,6 @@ async function handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetri
103
31
  */
104
32
  async function validateDeployment(url, envKey, manifest, authConfig, options = {}) {
105
33
  const validatedEnvKey = validateEnvironmentKey(envKey);
106
- const endpoint = `${url}/api/v1/pipeline/${validatedEnvKey}/validate`;
107
- const timeout = options.timeout || 30000;
108
34
  const maxRetries = options.maxRetries || 3;
109
35
 
110
36
  // Extract clientId and clientSecret
@@ -118,58 +44,68 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
118
44
 
119
45
  // Build validation request
120
46
  const repositoryUrl = options.repositoryUrl || `https://github.com/aifabrix/${manifest.key}`;
121
- const validationRequest = {
47
+ const validationData = {
122
48
  clientId: clientId,
123
49
  clientSecret: clientSecret,
124
50
  repositoryUrl: repositoryUrl,
125
51
  applicationConfig: manifest
126
52
  };
127
53
 
128
- // Create request config with client credentials headers
129
- const requestConfig = {
130
- headers: {
131
- 'Content-Type': 'application/json',
132
- 'User-Agent': 'aifabrix-builder/2.0.0',
133
- 'x-client-id': clientId,
134
- 'x-client-secret': clientSecret
135
- },
136
- timeout,
137
- validateStatus: (status) => status < 600 // Don't throw on any status
54
+ // Use centralized API client with retry logic
55
+ const pipelineAuthConfig = {
56
+ type: 'client-credentials',
57
+ clientId: clientId,
58
+ clientSecret: clientSecret
138
59
  };
139
60
 
140
61
  let lastError;
141
62
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
142
63
  try {
143
- const response = await axios.post(endpoint, validationRequest, requestConfig);
64
+ const response = await validatePipeline(url, validatedEnvKey, pipelineAuthConfig, validationData);
144
65
 
145
66
  // Handle successful validation (200 OK with valid: true)
146
- if (response.status === 200 && response.data.valid === true) {
147
- return {
148
- success: true,
149
- validateToken: response.data.validateToken,
150
- draftDeploymentId: response.data.draftDeploymentId,
151
- imageServer: response.data.imageServer,
152
- imageUsername: response.data.imageUsername,
153
- imagePassword: response.data.imagePassword,
154
- expiresAt: response.data.expiresAt
155
- };
67
+ if (response.success && response.data) {
68
+ const responseData = response.data.data || response.data;
69
+ if (responseData.valid === true) {
70
+ return {
71
+ success: true,
72
+ validateToken: responseData.validateToken,
73
+ draftDeploymentId: responseData.draftDeploymentId,
74
+ imageServer: responseData.imageServer,
75
+ imageUsername: responseData.imageUsername,
76
+ imagePassword: responseData.imagePassword,
77
+ expiresAt: responseData.expiresAt
78
+ };
79
+ }
80
+ // Handle validation failure (valid: false)
81
+ if (responseData.valid === false) {
82
+ const errorMessage = responseData.errors && responseData.errors.length > 0
83
+ ? `Validation failed: ${responseData.errors.join(', ')}`
84
+ : 'Validation failed: Invalid configuration';
85
+ const error = new Error(errorMessage);
86
+ error.status = 400;
87
+ error.data = responseData;
88
+ throw error;
89
+ }
156
90
  }
157
91
 
158
- // Handle validation errors
159
- if (response.status >= 400) {
160
- const error = new Error(`Validation request failed: ${response.status} ${response.statusText}`);
161
- error.status = response.status;
162
- error.response = {
163
- status: response.status,
164
- statusText: response.statusText,
165
- data: response.data
166
- };
92
+ // Handle validation errors (non-success responses)
93
+ if (!response.success) {
94
+ const error = new Error(`Validation request failed: ${response.formattedError || response.error || 'Unknown error'}`);
95
+ error.status = response.status || 400;
167
96
  error.data = response.data;
168
97
  throw error;
169
98
  }
99
+
100
+ // If we get here, response.success is true but valid is not true and not false
101
+ // This is an unexpected state, throw an error
102
+ const error = new Error('Validation response is in an unexpected state');
103
+ error.status = 400;
104
+ error.data = response.data;
105
+ throw error;
170
106
  } catch (error) {
171
107
  lastError = error;
172
- const shouldRetry = attempt < maxRetries && error.response && error.response.status >= 500;
108
+ const shouldRetry = attempt < maxRetries && error.status && error.status >= 500;
173
109
  if (shouldRetry) {
174
110
  const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
175
111
  logger.log(chalk.yellow(`⚠️ Validation attempt ${attempt} failed, retrying in ${delay}ms...`));
@@ -198,8 +134,6 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
198
134
  */
199
135
  async function sendDeploymentRequest(url, envKey, validateToken, authConfig, options = {}) {
200
136
  const validatedEnvKey = validateEnvironmentKey(envKey);
201
- const endpoint = `${url}/api/v1/pipeline/${validatedEnvKey}/deploy`;
202
- const timeout = options.timeout || 30000;
203
137
  const maxRetries = options.maxRetries || 3;
204
138
 
205
139
  // Extract clientId and clientSecret for deploy endpoint
@@ -212,24 +146,58 @@ async function sendDeploymentRequest(url, envKey, validateToken, authConfig, opt
212
146
 
213
147
  // Build deployment request
214
148
  const imageTag = options.imageTag || 'latest';
215
- const deployRequest = {
149
+ const deployData = {
216
150
  validateToken: validateToken,
217
151
  imageTag: imageTag
218
152
  };
219
153
 
220
- // Create request config with client credentials headers
221
- const requestConfig = {
222
- headers: {
223
- 'Content-Type': 'application/json',
224
- 'User-Agent': 'aifabrix-builder/2.0.0',
225
- 'x-client-id': clientId,
226
- 'x-client-secret': clientSecret
227
- },
228
- timeout,
229
- validateStatus: (status) => status < 500
154
+ // Use centralized API client with retry logic
155
+ const pipelineAuthConfig = {
156
+ type: 'client-credentials',
157
+ clientId: clientId,
158
+ clientSecret: clientSecret
230
159
  };
231
160
 
232
- return handleDeploymentRetry(endpoint, deployRequest, requestConfig, maxRetries);
161
+ // Wrap API call with retry logic
162
+ let lastError;
163
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
164
+ try {
165
+ const response = await deployPipeline(url, validatedEnvKey, pipelineAuthConfig, deployData);
166
+
167
+ if (response.success) {
168
+ return response.data.data || response.data;
169
+ }
170
+
171
+ // Handle deployment errors
172
+ if (response.status >= 400) {
173
+ const error = new Error(`Deployment request failed: ${response.formattedError || response.error || 'Unknown error'}`);
174
+ error.status = response.status;
175
+ error.data = response.data;
176
+ throw error;
177
+ }
178
+ } catch (error) {
179
+ lastError = error;
180
+ if (attempt < maxRetries) {
181
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
182
+ logger.log(chalk.yellow(`⚠️ Deployment attempt ${attempt} failed, retrying in ${delay}ms...`));
183
+ await new Promise(resolve => setTimeout(resolve, delay));
184
+ }
185
+ }
186
+ }
187
+
188
+ // Preserve formatted error if available
189
+ const errorMessage = lastError.formatted || lastError.message;
190
+ const error = new Error(`Deployment failed after ${maxRetries} attempts: ${errorMessage}`);
191
+ if (lastError.formatted) {
192
+ error.formatted = lastError.formatted;
193
+ }
194
+ if (lastError.status) {
195
+ error.status = lastError.status;
196
+ }
197
+ if (lastError.data) {
198
+ error.data = lastError.data;
199
+ }
200
+ throw error;
233
201
  }
234
202
 
235
203
  /**
@@ -259,20 +227,17 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
259
227
  const maxAttempts = options.maxAttempts || 60;
260
228
 
261
229
  const validatedEnvKey = validateEnvironmentKey(envKey);
262
- const statusEndpoint = `${controllerUrl}/api/v1/pipeline/${validatedEnvKey}/deployments/${deploymentId}`;
230
+
231
+ // Convert authConfig to format expected by API client
232
+ const pipelineAuthConfig = authConfig.type === 'bearer'
233
+ ? { type: 'bearer', token: authConfig.token }
234
+ : { type: 'client-credentials', clientId: authConfig.clientId, clientSecret: authConfig.clientSecret };
263
235
 
264
236
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
265
237
  try {
266
- const response = await axios.get(statusEndpoint, {
267
- headers: {
268
- 'Content-Type': 'application/json',
269
- ...createAuthHeaders(authConfig)
270
- },
271
- timeout: 10000,
272
- validateStatus: (status) => status < 500
273
- });
274
-
275
- if (response.status === 200) {
238
+ const response = await getPipelineDeployment(controllerUrl, validatedEnvKey, deploymentId, pipelineAuthConfig);
239
+
240
+ if (response.success && response.data) {
276
241
  // OpenAPI spec: Response structure { success: boolean, data: { status, progress, ... }, timestamp: string }
277
242
  const responseData = response.data;
278
243
  const deploymentData = responseData.data || responseData;
@@ -289,11 +254,14 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
289
254
  await new Promise(resolve => setTimeout(resolve, interval));
290
255
  }
291
256
  } else {
292
- throw new Error(`Status check failed: ${response.status}`);
257
+ if (response.status === 404) {
258
+ throw new Error(`Deployment ${deploymentId} not found`);
259
+ }
260
+ throw new Error(`Status check failed: ${response.formattedError || response.error || 'Unknown error'}`);
293
261
  }
294
262
  } catch (error) {
295
- if (error.response && error.response.status === 404) {
296
- throw new Error(`Deployment ${deploymentId} not found`);
263
+ if (error.message && error.message.includes('not found')) {
264
+ throw error;
297
265
  }
298
266
  throw error;
299
267
  }
@@ -14,7 +14,8 @@ const logger = require('./utils/logger');
14
14
  const config = require('./config');
15
15
  const { validateControllerUrl, validateEnvironmentKey } = require('./utils/deployment-validation');
16
16
  const { getOrRefreshDeviceToken } = require('./utils/token-manager');
17
- const { authenticatedApiCall } = require('./utils/api');
17
+ const { getEnvironmentStatus } = require('./api/environments.api');
18
+ const { deployEnvironment: deployEnvironmentInfra } = require('./api/deployments.api');
18
19
  const { handleDeploymentErrors } = require('./utils/deployment-errors');
19
20
  const auditLogger = require('./audit-logger');
20
21
 
@@ -93,32 +94,23 @@ async function sendEnvironmentDeployment(controllerUrl, envKey, authConfig, opti
93
94
  deploymentRequest.description += ` (config: ${options.config})`;
94
95
  }
95
96
 
96
- // API endpoint: POST /api/v1/environments/{env}/deploy
97
- // Alternative: POST /api/v1/environments/deploy with environment in body
98
- const endpoint = `${validatedUrl}/api/v1/environments/${validatedEnvKey}/deploy`;
99
-
100
97
  // Log deployment attempt for audit
101
98
  await auditLogger.logDeploymentAttempt(validatedEnvKey, validatedUrl, options);
102
99
 
103
100
  try {
104
- const response = await authenticatedApiCall(
105
- endpoint,
106
- {
107
- method: 'POST',
108
- body: JSON.stringify(deploymentRequest)
109
- },
110
- authConfig.token
111
- );
101
+ // Use centralized API client
102
+ const apiAuthConfig = { type: 'bearer', token: authConfig.token };
103
+ const response = await deployEnvironmentInfra(validatedUrl, validatedEnvKey, apiAuthConfig, deploymentRequest);
112
104
 
113
105
  if (!response.success) {
114
106
  const error = new Error(response.formattedError || response.error || 'Environment deployment failed');
115
107
  error.status = response.status;
116
- error.data = response.errorData;
108
+ error.data = response.errorData || response.data;
117
109
  throw error;
118
110
  }
119
111
 
120
112
  // Handle response structure
121
- const responseData = response.data || {};
113
+ const responseData = response.data.data || response.data || {};
122
114
  return {
123
115
  success: true,
124
116
  environment: validatedEnvKey,
@@ -150,25 +142,22 @@ async function pollEnvironmentStatus(deploymentId, controllerUrl, envKey, authCo
150
142
 
151
143
  const pollInterval = options.pollInterval || 5000;
152
144
  const maxAttempts = options.maxAttempts || 60;
153
- const statusEndpoint = `${validatedUrl}/api/v1/environments/${validatedEnvKey}/status`;
154
145
 
155
146
  logger.log(chalk.blue(`⏳ Polling environment status (${pollInterval}ms intervals)...`));
156
147
 
148
+ // Use centralized API client
149
+ const apiAuthConfig = { type: 'bearer', token: authConfig.token };
150
+
157
151
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
158
152
  try {
159
153
  await new Promise(resolve => setTimeout(resolve, pollInterval));
160
154
 
161
- const response = await authenticatedApiCall(
162
- statusEndpoint,
163
- {
164
- method: 'GET'
165
- },
166
- authConfig.token
167
- );
155
+ const response = await getEnvironmentStatus(validatedUrl, validatedEnvKey, apiAuthConfig);
168
156
 
169
157
  if (response.success && response.data) {
170
- const status = response.data.status || response.data.ready;
171
- const isReady = status === 'ready' || status === 'completed' || response.data.ready === true;
158
+ const responseData = response.data.data || response.data;
159
+ const status = responseData.status || responseData.ready;
160
+ const isReady = status === 'ready' || status === 'completed' || responseData.ready === true;
172
161
 
173
162
  if (isReady) {
174
163
  return {
@@ -181,7 +170,7 @@ async function pollEnvironmentStatus(deploymentId, controllerUrl, envKey, authCo
181
170
 
182
171
  // Check for terminal failure states
183
172
  if (status === 'failed' || status === 'error') {
184
- throw new Error(`Environment deployment failed: ${response.data.message || 'Unknown error'}`);
173
+ throw new Error(`Environment deployment failed: ${responseData.message || 'Unknown error'}`);
185
174
  }
186
175
  }
187
176
  } catch (error) {