@aifabrix/builder 2.22.1 → 2.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/jest.config.coverage.js +37 -0
- package/lib/api/pipeline.api.js +10 -9
- package/lib/app-deploy.js +36 -14
- package/lib/app-list.js +191 -71
- package/lib/app-prompts.js +77 -26
- package/lib/app-readme.js +123 -5
- package/lib/app-rotate-secret.js +101 -57
- package/lib/app-run-helpers.js +200 -172
- package/lib/app-run.js +137 -68
- package/lib/audit-logger.js +8 -7
- package/lib/build.js +161 -250
- package/lib/cli.js +73 -65
- package/lib/commands/login.js +45 -31
- package/lib/commands/logout.js +181 -0
- package/lib/commands/secrets-set.js +2 -2
- package/lib/commands/secure.js +61 -26
- package/lib/config.js +79 -45
- package/lib/datasource-deploy.js +89 -29
- package/lib/deployer.js +164 -129
- package/lib/diff.js +63 -21
- package/lib/environment-deploy.js +36 -19
- package/lib/external-system-deploy.js +134 -66
- package/lib/external-system-download.js +244 -171
- package/lib/external-system-test.js +199 -164
- package/lib/generator-external.js +145 -72
- package/lib/generator-helpers.js +49 -17
- package/lib/generator-split.js +105 -58
- package/lib/infra.js +101 -131
- package/lib/schema/application-schema.json +895 -896
- package/lib/schema/env-config.yaml +11 -4
- package/lib/template-validator.js +13 -4
- package/lib/utils/api.js +8 -8
- package/lib/utils/app-register-auth.js +36 -18
- package/lib/utils/app-run-containers.js +140 -0
- package/lib/utils/auth-headers.js +6 -6
- package/lib/utils/build-copy.js +60 -2
- package/lib/utils/build-helpers.js +94 -0
- package/lib/utils/cli-utils.js +177 -76
- package/lib/utils/compose-generator.js +12 -2
- package/lib/utils/config-tokens.js +151 -9
- package/lib/utils/deployment-errors.js +137 -69
- package/lib/utils/deployment-validation-helpers.js +103 -0
- package/lib/utils/docker-build.js +57 -0
- package/lib/utils/dockerfile-utils.js +13 -3
- package/lib/utils/env-copy.js +163 -94
- package/lib/utils/env-map.js +226 -86
- package/lib/utils/environment-checker.js +2 -2
- package/lib/utils/error-formatters/network-errors.js +0 -1
- package/lib/utils/external-system-display.js +14 -19
- package/lib/utils/external-system-env-helpers.js +107 -0
- package/lib/utils/external-system-test-helpers.js +144 -0
- package/lib/utils/health-check.js +10 -8
- package/lib/utils/infra-status.js +123 -0
- package/lib/utils/local-secrets.js +3 -2
- package/lib/utils/paths.js +228 -49
- package/lib/utils/schema-loader.js +125 -57
- package/lib/utils/token-manager.js +10 -7
- package/lib/utils/yaml-preserve.js +55 -16
- package/lib/validate.js +87 -89
- package/package.json +4 -4
- package/scripts/ci-fix.sh +19 -0
- package/scripts/ci-simulate.sh +19 -0
- package/templates/applications/miso-controller/test.yaml +1 -0
- package/templates/python/Dockerfile.hbs +8 -45
- package/templates/typescript/Dockerfile.hbs +8 -42
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Configuration for Coverage Reports
|
|
3
|
+
* Extends base config with coverage settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const baseConfig = require('./jest.config');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
...baseConfig,
|
|
10
|
+
collectCoverageFrom: [
|
|
11
|
+
'lib/**/*.js',
|
|
12
|
+
'bin/**/*.js',
|
|
13
|
+
'!**/node_modules/**',
|
|
14
|
+
'!**/tests/**',
|
|
15
|
+
'!lib/infra.js',
|
|
16
|
+
'!bin/aifabrix.js'
|
|
17
|
+
],
|
|
18
|
+
coverageDirectory: 'coverage',
|
|
19
|
+
coverageReporters: [
|
|
20
|
+
'text',
|
|
21
|
+
'lcov',
|
|
22
|
+
'html'
|
|
23
|
+
],
|
|
24
|
+
coverageProvider: 'v8',
|
|
25
|
+
coverageThreshold: {
|
|
26
|
+
global: {
|
|
27
|
+
branches: 80,
|
|
28
|
+
functions: 80,
|
|
29
|
+
lines: 80,
|
|
30
|
+
statements: 80
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
// Memory management for coverage runs
|
|
34
|
+
maxWorkers: 2,
|
|
35
|
+
workerIdleMemoryLimit: '500MB'
|
|
36
|
+
};
|
|
37
|
+
|
package/lib/api/pipeline.api.js
CHANGED
|
@@ -105,18 +105,19 @@ async function publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig,
|
|
|
105
105
|
* POST /api/v1/pipeline/{systemKey}/{datasourceKey}/test
|
|
106
106
|
* @async
|
|
107
107
|
* @function testDatasourceViaPipeline
|
|
108
|
-
* @param {
|
|
109
|
-
* @param {string}
|
|
110
|
-
* @param {string}
|
|
111
|
-
* @param {
|
|
112
|
-
* @param {Object}
|
|
113
|
-
* @param {Object} testData
|
|
114
|
-
* @param {Object}
|
|
115
|
-
* @param {
|
|
108
|
+
* @param {Object} params - Function parameters
|
|
109
|
+
* @param {string} params.dataplaneUrl - Dataplane base URL
|
|
110
|
+
* @param {string} params.systemKey - System key
|
|
111
|
+
* @param {string} params.datasourceKey - Datasource key
|
|
112
|
+
* @param {Object} params.authConfig - Authentication configuration
|
|
113
|
+
* @param {Object} params.testData - Test data
|
|
114
|
+
* @param {Object} params.testData.payloadTemplate - Test payload template
|
|
115
|
+
* @param {Object} [params.options] - Request options
|
|
116
|
+
* @param {number} [params.options.timeout] - Request timeout in milliseconds
|
|
116
117
|
* @returns {Promise<Object>} Test response
|
|
117
118
|
* @throws {Error} If test fails
|
|
118
119
|
*/
|
|
119
|
-
async function testDatasourceViaPipeline(dataplaneUrl, systemKey, datasourceKey, authConfig, testData, options = {}) {
|
|
120
|
+
async function testDatasourceViaPipeline({ dataplaneUrl, systemKey, datasourceKey, authConfig, testData, options = {} }) {
|
|
120
121
|
const client = new ApiClient(dataplaneUrl, authConfig);
|
|
121
122
|
const requestOptions = {
|
|
122
123
|
body: testData
|
package/lib/app-deploy.js
CHANGED
|
@@ -208,23 +208,13 @@ function validateDeploymentConfig(deploymentConfig) {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
*
|
|
211
|
+
* Configure deployment environment settings
|
|
212
212
|
* @async
|
|
213
|
-
* @param {string} appName - Application name
|
|
214
213
|
* @param {Object} options - CLI options
|
|
215
|
-
* @
|
|
216
|
-
* @
|
|
214
|
+
* @param {Object} deploymentConfig - Deployment configuration to update
|
|
215
|
+
* @returns {Promise<void>}
|
|
217
216
|
*/
|
|
218
|
-
async function
|
|
219
|
-
// Detect app type and get correct path (integration or builder)
|
|
220
|
-
const { appPath } = await detectAppType(appName);
|
|
221
|
-
await validateAppDirectory(appPath, appName);
|
|
222
|
-
|
|
223
|
-
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
224
|
-
const variables = await loadVariablesFile(variablesPath);
|
|
225
|
-
|
|
226
|
-
const deploymentConfig = extractDeploymentConfig(options, variables);
|
|
227
|
-
|
|
217
|
+
async function configureDeploymentEnvironment(options, deploymentConfig) {
|
|
228
218
|
// Update root-level environment if provided
|
|
229
219
|
if (options.environment) {
|
|
230
220
|
await config.setCurrentEnvironment(options.environment);
|
|
@@ -233,7 +223,17 @@ async function loadDeploymentConfig(appName, options) {
|
|
|
233
223
|
// Get current environment from root-level config
|
|
234
224
|
const currentEnvironment = await config.getCurrentEnvironment();
|
|
235
225
|
deploymentConfig.envKey = deploymentConfig.envKey || currentEnvironment;
|
|
226
|
+
}
|
|
236
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Refresh deployment token and configure authentication
|
|
230
|
+
* @async
|
|
231
|
+
* @param {string} appName - Application name
|
|
232
|
+
* @param {Object} deploymentConfig - Deployment configuration to update
|
|
233
|
+
* @returns {Promise<void>}
|
|
234
|
+
* @throws {Error} If authentication fails
|
|
235
|
+
*/
|
|
236
|
+
async function refreshDeploymentToken(appName, deploymentConfig) {
|
|
237
237
|
// Get controller URL
|
|
238
238
|
if (!deploymentConfig.controllerUrl) {
|
|
239
239
|
throw new Error('Controller URL is required. Set it in variables.yaml or use --controller flag');
|
|
@@ -257,6 +257,28 @@ async function loadDeploymentConfig(appName, options) {
|
|
|
257
257
|
} catch (error) {
|
|
258
258
|
throw new Error(`Failed to get authentication: ${error.message}`);
|
|
259
259
|
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Loads deployment configuration from variables.yaml and gets/refreshes token
|
|
264
|
+
* @async
|
|
265
|
+
* @param {string} appName - Application name
|
|
266
|
+
* @param {Object} options - CLI options
|
|
267
|
+
* @returns {Promise<Object>} Deployment configuration with token
|
|
268
|
+
* @throws {Error} If configuration is invalid
|
|
269
|
+
*/
|
|
270
|
+
async function loadDeploymentConfig(appName, options) {
|
|
271
|
+
// Detect app type and get correct path (integration or builder)
|
|
272
|
+
const { appPath } = await detectAppType(appName);
|
|
273
|
+
await validateAppDirectory(appPath, appName);
|
|
274
|
+
|
|
275
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
276
|
+
const variables = await loadVariablesFile(variablesPath);
|
|
277
|
+
|
|
278
|
+
const deploymentConfig = extractDeploymentConfig(options, variables);
|
|
279
|
+
|
|
280
|
+
await configureDeploymentEnvironment(options, deploymentConfig);
|
|
281
|
+
await refreshDeploymentToken(appName, deploymentConfig);
|
|
260
282
|
|
|
261
283
|
validateDeploymentConfig(deploymentConfig);
|
|
262
284
|
|
package/lib/app-list.js
CHANGED
|
@@ -16,6 +16,54 @@ const { formatApiError } = require('./utils/api-error-handler');
|
|
|
16
16
|
const { formatAuthenticationError } = require('./utils/error-formatters/http-status-errors');
|
|
17
17
|
const logger = require('./utils/logger');
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Extract wrapped array format: { success: true, data: { success: true, data: [...] } }
|
|
21
|
+
* @param {Object} apiResponse - API response data
|
|
22
|
+
* @returns {Array|null} Applications array or null if not this format
|
|
23
|
+
*/
|
|
24
|
+
function extractWrappedArray(apiResponse) {
|
|
25
|
+
if (apiResponse && apiResponse.data && Array.isArray(apiResponse.data)) {
|
|
26
|
+
return apiResponse.data;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extract direct array format: { success: true, data: [...] }
|
|
33
|
+
* @param {Object} apiResponse - API response data
|
|
34
|
+
* @returns {Array|null} Applications array or null if not this format
|
|
35
|
+
*/
|
|
36
|
+
function extractDirectArray(apiResponse) {
|
|
37
|
+
if (Array.isArray(apiResponse)) {
|
|
38
|
+
return apiResponse;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extract paginated items format: { success: true, data: { items: [...] } }
|
|
45
|
+
* @param {Object} apiResponse - API response data
|
|
46
|
+
* @returns {Array|null} Applications array or null if not this format
|
|
47
|
+
*/
|
|
48
|
+
function extractPaginatedItems(apiResponse) {
|
|
49
|
+
if (apiResponse && Array.isArray(apiResponse.items)) {
|
|
50
|
+
return apiResponse.items;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Extract wrapped paginated items format: { success: true, data: { success: true, data: { items: [...] } } }
|
|
57
|
+
* @param {Object} apiResponse - API response data
|
|
58
|
+
* @returns {Array|null} Applications array or null if not this format
|
|
59
|
+
*/
|
|
60
|
+
function extractWrappedPaginatedItems(apiResponse) {
|
|
61
|
+
if (apiResponse && apiResponse.data && apiResponse.data.items && Array.isArray(apiResponse.data.items)) {
|
|
62
|
+
return apiResponse.data.items;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
19
67
|
/**
|
|
20
68
|
* Extract applications array from API response
|
|
21
69
|
* Handles multiple response formats:
|
|
@@ -29,21 +77,14 @@ const logger = require('./utils/logger');
|
|
|
29
77
|
*/
|
|
30
78
|
function extractApplications(response) {
|
|
31
79
|
const apiResponse = response.data;
|
|
32
|
-
let applications;
|
|
33
80
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Check if apiResponse.items is an array (paginated format)
|
|
42
|
-
applications = apiResponse.items;
|
|
43
|
-
} else if (apiResponse && apiResponse.data && apiResponse.data.items && Array.isArray(apiResponse.data.items)) {
|
|
44
|
-
// Check if apiResponse.data.items is an array (wrapped paginated format)
|
|
45
|
-
applications = apiResponse.data.items;
|
|
46
|
-
} else {
|
|
81
|
+
// Try each format in sequence
|
|
82
|
+
const applications = extractWrappedArray(apiResponse) ||
|
|
83
|
+
extractDirectArray(apiResponse) ||
|
|
84
|
+
extractPaginatedItems(apiResponse) ||
|
|
85
|
+
extractWrappedPaginatedItems(apiResponse);
|
|
86
|
+
|
|
87
|
+
if (!applications) {
|
|
47
88
|
logger.error(chalk.red('❌ Invalid response: expected data array or items array'));
|
|
48
89
|
logger.error(chalk.gray('\nAPI response type:'), typeof apiResponse);
|
|
49
90
|
logger.error(chalk.gray('API response:'), JSON.stringify(apiResponse, null, 2));
|
|
@@ -55,6 +96,36 @@ function extractApplications(response) {
|
|
|
55
96
|
return applications;
|
|
56
97
|
}
|
|
57
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Find device token from config by trying each stored URL
|
|
101
|
+
* @async
|
|
102
|
+
* @param {Object} deviceConfig - Device configuration object
|
|
103
|
+
* @returns {Promise<Object|null>} Token result with token and controllerUrl, or null if not found
|
|
104
|
+
*/
|
|
105
|
+
async function findDeviceTokenFromConfig(deviceConfig) {
|
|
106
|
+
const deviceUrls = Object.keys(deviceConfig);
|
|
107
|
+
if (deviceUrls.length === 0) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const storedUrl of deviceUrls) {
|
|
112
|
+
try {
|
|
113
|
+
const normalizedStoredUrl = normalizeControllerUrl(storedUrl);
|
|
114
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedStoredUrl);
|
|
115
|
+
if (deviceToken && deviceToken.token) {
|
|
116
|
+
return {
|
|
117
|
+
token: deviceToken.token,
|
|
118
|
+
controllerUrl: deviceToken.controller || normalizedStoredUrl
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// Continue to next URL
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
58
129
|
/**
|
|
59
130
|
* Display applications list
|
|
60
131
|
* @param {Array} applications - Array of application objects
|
|
@@ -79,58 +150,57 @@ function displayApplications(applications, environment) {
|
|
|
79
150
|
}
|
|
80
151
|
|
|
81
152
|
/**
|
|
82
|
-
*
|
|
153
|
+
* Try to get device token from controller URL
|
|
83
154
|
* @async
|
|
84
|
-
* @param {
|
|
85
|
-
* @
|
|
86
|
-
* @param {string} [options.controller] - Controller URL (optional, uses configured controller if not provided)
|
|
87
|
-
* @throws {Error} If listing fails
|
|
155
|
+
* @param {string} controllerUrl - Controller URL
|
|
156
|
+
* @returns {Promise<Object|null>} Object with token and controllerUrl, or null
|
|
88
157
|
*/
|
|
89
|
-
async function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (controllerUrl) {
|
|
99
|
-
try {
|
|
100
|
-
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
101
|
-
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
102
|
-
if (deviceToken && deviceToken.token) {
|
|
103
|
-
token = deviceToken.token;
|
|
104
|
-
actualControllerUrl = deviceToken.controller || normalizedUrl;
|
|
105
|
-
}
|
|
106
|
-
} catch (error) {
|
|
107
|
-
// Show which controller URL failed
|
|
108
|
-
logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
|
|
109
|
-
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
110
|
-
process.exit(1);
|
|
158
|
+
async function tryGetTokenFromController(controllerUrl) {
|
|
159
|
+
try {
|
|
160
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
161
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
162
|
+
if (deviceToken && deviceToken.token) {
|
|
163
|
+
return {
|
|
164
|
+
token: deviceToken.token,
|
|
165
|
+
actualControllerUrl: deviceToken.controller || normalizedUrl
|
|
166
|
+
};
|
|
111
167
|
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
|
|
170
|
+
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
171
|
+
process.exit(1);
|
|
112
172
|
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
113
175
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
token = deviceToken.token;
|
|
124
|
-
actualControllerUrl = deviceToken.controller || normalizedStoredUrl;
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
} catch (error) {
|
|
128
|
-
// Continue to next URL
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
176
|
+
/**
|
|
177
|
+
* Try to find device token from config
|
|
178
|
+
* @async
|
|
179
|
+
* @param {Object} deviceConfig - Device configuration
|
|
180
|
+
* @returns {Promise<Object|null>} Object with token and controllerUrl, or null
|
|
181
|
+
*/
|
|
182
|
+
async function tryGetTokenFromConfig(deviceConfig) {
|
|
183
|
+
if (!deviceConfig) {
|
|
184
|
+
return null;
|
|
132
185
|
}
|
|
186
|
+
const tokenResult = await findDeviceTokenFromConfig(deviceConfig);
|
|
187
|
+
if (tokenResult) {
|
|
188
|
+
return {
|
|
189
|
+
token: tokenResult.token,
|
|
190
|
+
actualControllerUrl: tokenResult.controllerUrl
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
133
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Validate and return authentication token or exit
|
|
198
|
+
* @param {string|null} token - Authentication token
|
|
199
|
+
* @param {string|null} actualControllerUrl - Controller URL
|
|
200
|
+
* @param {string} controllerUrl - Original controller URL (for error message)
|
|
201
|
+
* @returns {Object} Object with token and actualControllerUrl
|
|
202
|
+
*/
|
|
203
|
+
function validateAuthToken(token, actualControllerUrl, controllerUrl) {
|
|
134
204
|
if (!token || !actualControllerUrl) {
|
|
135
205
|
const formattedError = formatAuthenticationError({
|
|
136
206
|
controllerUrl: controllerUrl || undefined,
|
|
@@ -139,23 +209,73 @@ async function listApplications(options) {
|
|
|
139
209
|
logger.error(formattedError);
|
|
140
210
|
process.exit(1);
|
|
141
211
|
}
|
|
212
|
+
return { token, actualControllerUrl };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get authentication token for listing applications
|
|
217
|
+
* @async
|
|
218
|
+
* @param {string} [controllerUrl] - Optional controller URL
|
|
219
|
+
* @param {Object} config - Configuration object
|
|
220
|
+
* @returns {Promise<Object>} Object with token and actualControllerUrl
|
|
221
|
+
* @throws {Error} If authentication fails
|
|
222
|
+
*/
|
|
223
|
+
async function getListAuthToken(controllerUrl, config) {
|
|
224
|
+
// Try to get token from controller URL first
|
|
225
|
+
let authResult = null;
|
|
226
|
+
if (controllerUrl) {
|
|
227
|
+
authResult = await tryGetTokenFromController(controllerUrl);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// If no token yet, try to find device token from config
|
|
231
|
+
if (!authResult && config.device) {
|
|
232
|
+
authResult = await tryGetTokenFromConfig(config.device);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Validate and return token or exit
|
|
236
|
+
return validateAuthToken(authResult?.token || null, authResult?.actualControllerUrl || null, controllerUrl);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Handle API response and extract applications
|
|
241
|
+
* @param {Object} response - API response
|
|
242
|
+
* @param {string} actualControllerUrl - Controller URL
|
|
243
|
+
* @returns {Array} Extracted applications
|
|
244
|
+
* @throws {Error} If response is invalid
|
|
245
|
+
*/
|
|
246
|
+
function handleListResponse(response, actualControllerUrl) {
|
|
247
|
+
if (!response.success || !response.data) {
|
|
248
|
+
const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
|
|
249
|
+
logger.error(formattedError);
|
|
250
|
+
logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
|
|
251
|
+
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
252
|
+
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return extractApplications(response);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* List applications in an environment
|
|
261
|
+
* @async
|
|
262
|
+
* @param {Object} options - Command options
|
|
263
|
+
* @param {string} options.environment - Environment ID or key
|
|
264
|
+
* @param {string} [options.controller] - Controller URL (optional, uses configured controller if not provided)
|
|
265
|
+
* @throws {Error} If listing fails
|
|
266
|
+
*/
|
|
267
|
+
async function listApplications(options) {
|
|
268
|
+
const config = await getConfig();
|
|
269
|
+
|
|
270
|
+
// Get authentication token
|
|
271
|
+
const controllerUrl = options.controller || null;
|
|
272
|
+
const { token, actualControllerUrl } = await getListAuthToken(controllerUrl, config);
|
|
142
273
|
|
|
143
274
|
// Use centralized API client
|
|
144
275
|
const authConfig = { type: 'bearer', token: token };
|
|
145
276
|
try {
|
|
146
277
|
const response = await listEnvironmentApplications(actualControllerUrl, options.environment, authConfig);
|
|
147
|
-
|
|
148
|
-
if (!response.success || !response.data) {
|
|
149
|
-
const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
|
|
150
|
-
logger.error(formattedError);
|
|
151
|
-
logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
|
|
152
|
-
// Log full response for debugging
|
|
153
|
-
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
154
|
-
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const applications = extractApplications(response);
|
|
278
|
+
const applications = handleListResponse(response, actualControllerUrl);
|
|
159
279
|
displayApplications(applications, options.environment);
|
|
160
280
|
} catch (error) {
|
|
161
281
|
logger.error(chalk.red(`❌ Failed to list applications from controller: ${actualControllerUrl}`));
|
package/lib/app-prompts.js
CHANGED
|
@@ -300,6 +300,75 @@ function resolveOptionalBoolean(optionValue, answerValue, defaultValue) {
|
|
|
300
300
|
return answerValue !== undefined ? answerValue : defaultValue;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Resolve a single config field from options or answers
|
|
305
|
+
* @function resolveConfigField
|
|
306
|
+
* @param {Object} options - Provided options
|
|
307
|
+
* @param {Object} answers - Prompt answers
|
|
308
|
+
* @param {string} fieldName - Field name to resolve
|
|
309
|
+
* @param {*} defaultValue - Default value
|
|
310
|
+
* @returns {*} Resolved value
|
|
311
|
+
*/
|
|
312
|
+
function resolveConfigField(options, answers, fieldName, defaultValue) {
|
|
313
|
+
return resolveField(options[fieldName], answers[fieldName], defaultValue);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Resolve a single external system field if present
|
|
318
|
+
* @function resolveExternalSystemField
|
|
319
|
+
* @param {Object} options - Provided options
|
|
320
|
+
* @param {Object} answers - Prompt answers
|
|
321
|
+
* @param {string} fieldName - Field name to resolve
|
|
322
|
+
* @param {*} defaultValue - Default value
|
|
323
|
+
* @returns {*|null} Resolved value or null if not present
|
|
324
|
+
*/
|
|
325
|
+
function resolveExternalSystemField(options, answers, fieldName, defaultValue) {
|
|
326
|
+
if (answers[fieldName] || options[fieldName]) {
|
|
327
|
+
return resolveField(options[fieldName], answers[fieldName], defaultValue);
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Resolve external system fields and add to config
|
|
334
|
+
* @function resolveExternalSystemFields
|
|
335
|
+
* @param {Object} options - Provided options
|
|
336
|
+
* @param {Object} answers - Prompt answers
|
|
337
|
+
* @param {Object} config - Configuration object to update
|
|
338
|
+
* @returns {void}
|
|
339
|
+
*/
|
|
340
|
+
function resolveExternalSystemFields(options, answers, config) {
|
|
341
|
+
const systemKey = resolveExternalSystemField(options, answers, 'systemKey', undefined);
|
|
342
|
+
if (systemKey !== null) {
|
|
343
|
+
config.systemKey = systemKey;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const systemDisplayName = resolveExternalSystemField(options, answers, 'systemDisplayName', undefined);
|
|
347
|
+
if (systemDisplayName !== null) {
|
|
348
|
+
config.systemDisplayName = systemDisplayName;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const systemDescription = resolveExternalSystemField(options, answers, 'systemDescription', undefined);
|
|
352
|
+
if (systemDescription !== null) {
|
|
353
|
+
config.systemDescription = systemDescription;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const systemType = resolveExternalSystemField(options, answers, 'systemType', 'openapi');
|
|
357
|
+
if (systemType !== null) {
|
|
358
|
+
config.systemType = systemType;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const authType = resolveExternalSystemField(options, answers, 'authType', 'apikey');
|
|
362
|
+
if (authType !== null) {
|
|
363
|
+
config.authType = authType;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const datasourceCount = resolveExternalSystemField(options, answers, 'datasourceCount', 1);
|
|
367
|
+
if (datasourceCount !== null) {
|
|
368
|
+
config.datasourceCount = parseInt(datasourceCount, 10);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
303
372
|
/**
|
|
304
373
|
* Resolves conflicts between options and answers
|
|
305
374
|
* @function resolveConflicts
|
|
@@ -309,36 +378,18 @@ function resolveOptionalBoolean(optionValue, answerValue, defaultValue) {
|
|
|
309
378
|
*/
|
|
310
379
|
function resolveConflicts(options, answers) {
|
|
311
380
|
const config = {
|
|
312
|
-
port: parseInt(
|
|
313
|
-
language:
|
|
314
|
-
database:
|
|
315
|
-
redis:
|
|
316
|
-
storage:
|
|
317
|
-
authentication:
|
|
381
|
+
port: parseInt(resolveConfigField(options, answers, 'port', 3000), 10),
|
|
382
|
+
language: resolveConfigField(options, answers, 'language', 'typescript'),
|
|
383
|
+
database: resolveConfigField(options, answers, 'database', false),
|
|
384
|
+
redis: resolveConfigField(options, answers, 'redis', false),
|
|
385
|
+
storage: resolveConfigField(options, answers, 'storage', false),
|
|
386
|
+
authentication: resolveConfigField(options, answers, 'authentication', false),
|
|
318
387
|
github: resolveOptionalBoolean(options.github, answers.github, false),
|
|
319
388
|
controller: resolveOptionalBoolean(options.controller, answers.controller, false),
|
|
320
|
-
controllerUrl:
|
|
389
|
+
controllerUrl: resolveConfigField(options, answers, 'controllerUrl', undefined)
|
|
321
390
|
};
|
|
322
391
|
|
|
323
|
-
|
|
324
|
-
if (answers.systemKey || options.systemKey) {
|
|
325
|
-
config.systemKey = resolveField(options.systemKey, answers.systemKey, undefined);
|
|
326
|
-
}
|
|
327
|
-
if (answers.systemDisplayName || options.systemDisplayName) {
|
|
328
|
-
config.systemDisplayName = resolveField(options.systemDisplayName, answers.systemDisplayName, undefined);
|
|
329
|
-
}
|
|
330
|
-
if (answers.systemDescription || options.systemDescription) {
|
|
331
|
-
config.systemDescription = resolveField(options.systemDescription, answers.systemDescription, undefined);
|
|
332
|
-
}
|
|
333
|
-
if (answers.systemType || options.systemType) {
|
|
334
|
-
config.systemType = resolveField(options.systemType, answers.systemType, 'openapi');
|
|
335
|
-
}
|
|
336
|
-
if (answers.authType || options.authType) {
|
|
337
|
-
config.authType = resolveField(options.authType, answers.authType, 'apikey');
|
|
338
|
-
}
|
|
339
|
-
if (answers.datasourceCount || options.datasourceCount) {
|
|
340
|
-
config.datasourceCount = parseInt(resolveField(options.datasourceCount, answers.datasourceCount, 1), 10);
|
|
341
|
-
}
|
|
392
|
+
resolveExternalSystemFields(options, answers, config);
|
|
342
393
|
|
|
343
394
|
return config;
|
|
344
395
|
}
|