@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.
- 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} +123 -37
- 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 +34 -3
- package/scripts/install-local.js +210 -0
- 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
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
12
|
const logger = require('./logger');
|
|
13
|
-
const { getConfig, normalizeControllerUrl } = require('../config');
|
|
13
|
+
const { getConfig, normalizeControllerUrl } = require('../core/config');
|
|
14
14
|
const { getOrRefreshDeviceToken } = require('./token-manager');
|
|
15
15
|
const { formatAuthenticationError } = require('./error-formatters/http-status-errors');
|
|
16
16
|
|
|
@@ -86,66 +86,191 @@ async function findDeviceTokenFromConfig(deviceConfig, attemptedUrls) {
|
|
|
86
86
|
* @param {string} [environment] - Optional environment key
|
|
87
87
|
* @returns {Promise<{apiUrl: string, token: string, controllerUrl: string}>} Configuration with API URL, token, and controller URL
|
|
88
88
|
*/
|
|
89
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Tries to get device token for provided controller URL
|
|
91
|
+
* @async
|
|
92
|
+
* @function tryGetDeviceTokenForController
|
|
93
|
+
* @param {string} controllerUrl - Controller URL
|
|
94
|
+
* @param {Array<string>} attemptedUrls - Array to track attempted URLs
|
|
95
|
+
* @returns {Promise<Object|null>} Token result or null
|
|
96
|
+
*/
|
|
97
|
+
async function tryGetDeviceTokenForController(controllerUrl, attemptedUrls) {
|
|
98
|
+
if (!controllerUrl) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
attemptedUrls.push(controllerUrl);
|
|
90
102
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
let token = null;
|
|
98
|
-
let lastError = null;
|
|
99
|
-
const attemptedUrls = []; // Track all attempted URLs
|
|
100
|
-
|
|
101
|
-
// If controller URL provided, try to get device token
|
|
102
|
-
if (finalControllerUrl) {
|
|
103
|
-
attemptedUrls.push(finalControllerUrl);
|
|
104
|
-
try {
|
|
105
|
-
const deviceToken = await getOrRefreshDeviceToken(finalControllerUrl);
|
|
106
|
-
if (deviceToken && deviceToken.token) {
|
|
107
|
-
token = deviceToken.token;
|
|
108
|
-
finalControllerUrl = deviceToken.controller || finalControllerUrl;
|
|
109
|
-
}
|
|
110
|
-
} catch (error) {
|
|
111
|
-
lastError = error;
|
|
112
|
-
logger.warn(chalk.yellow(`⚠️ Failed to get token for controller ${finalControllerUrl}: ${error.message}`));
|
|
113
|
-
}
|
|
103
|
+
const deviceToken = await getOrRefreshDeviceToken(controllerUrl);
|
|
104
|
+
if (deviceToken && deviceToken.token) {
|
|
105
|
+
return {
|
|
106
|
+
token: deviceToken.token,
|
|
107
|
+
controllerUrl: deviceToken.controller || controllerUrl
|
|
108
|
+
};
|
|
114
109
|
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
logger.warn(chalk.yellow(`⚠️ Failed to get token for controller ${controllerUrl}: ${error.message}`));
|
|
112
|
+
return { error };
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Tries to find device token from config
|
|
119
|
+
* @async
|
|
120
|
+
* @function tryFindDeviceTokenFromConfig
|
|
121
|
+
* @param {Object} deviceConfig - Device config from main config
|
|
122
|
+
* @param {Array<string>} attemptedUrls - Array to track attempted URLs
|
|
123
|
+
* @returns {Promise<Object|null>} Token result or null
|
|
124
|
+
*/
|
|
125
|
+
async function tryFindDeviceTokenFromConfig(deviceConfig, attemptedUrls) {
|
|
126
|
+
if (!deviceConfig) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const tokenResult = await findDeviceTokenFromConfig(deviceConfig, attemptedUrls);
|
|
130
|
+
if (tokenResult) {
|
|
131
|
+
return {
|
|
132
|
+
token: tokenResult.token,
|
|
133
|
+
controllerUrl: tokenResult.controllerUrl
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Creates error data for authentication failure
|
|
141
|
+
* @function createAuthErrorData
|
|
142
|
+
* @param {Error|null} lastError - Last error encountered
|
|
143
|
+
* @param {string|null} controllerUrl - Original controller URL
|
|
144
|
+
* @param {Array<string>} attemptedUrls - Attempted URLs
|
|
145
|
+
* @returns {Object} Error data object
|
|
146
|
+
*/
|
|
147
|
+
function createAuthErrorData(lastError, controllerUrl, attemptedUrls) {
|
|
148
|
+
return {
|
|
149
|
+
message: lastError ? lastError.message : 'No valid authentication found',
|
|
150
|
+
controllerUrl: controllerUrl || (attemptedUrls.length > 0 ? attemptedUrls[0] : undefined),
|
|
151
|
+
attemptedUrls: attemptedUrls.length > 1 ? attemptedUrls : undefined,
|
|
152
|
+
correlationId: undefined
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Normalizes controller URL
|
|
158
|
+
* @function normalizeControllerUrlIfProvided
|
|
159
|
+
* @param {string|null} controllerUrl - Controller URL
|
|
160
|
+
* @returns {string|null} Normalized controller URL or null
|
|
161
|
+
*/
|
|
162
|
+
function normalizeControllerUrlIfProvided(controllerUrl) {
|
|
163
|
+
// Handle empty string as falsy (treat same as undefined/null)
|
|
164
|
+
return (controllerUrl && controllerUrl.trim()) ? normalizeControllerUrl(controllerUrl) : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Attempts to get device token for provided controller URL
|
|
169
|
+
* @async
|
|
170
|
+
* @function attemptDeviceTokenForController
|
|
171
|
+
* @param {string|null} finalControllerUrl - Final controller URL
|
|
172
|
+
* @param {string[]} attemptedUrls - Array of attempted URLs
|
|
173
|
+
* @returns {Promise<Object|null>} Device token result or null
|
|
174
|
+
*/
|
|
175
|
+
async function attemptDeviceTokenForController(finalControllerUrl, attemptedUrls) {
|
|
176
|
+
if (!finalControllerUrl) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const deviceTokenResult = await tryGetDeviceTokenForController(finalControllerUrl, attemptedUrls);
|
|
181
|
+
return deviceTokenResult;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Attempts to find device token from config
|
|
186
|
+
* @async
|
|
187
|
+
* @function attemptDeviceTokenFromConfig
|
|
188
|
+
* @param {Object} deviceConfig - Device configuration
|
|
189
|
+
* @param {string[]} attemptedUrls - Array of attempted URLs
|
|
190
|
+
* @returns {Promise<Object|null>} Device token result or null
|
|
191
|
+
*/
|
|
192
|
+
async function attemptDeviceTokenFromConfig(deviceConfig, attemptedUrls) {
|
|
193
|
+
if (!deviceConfig) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return await tryFindDeviceTokenFromConfig(deviceConfig, attemptedUrls);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Validates authentication result
|
|
202
|
+
* @function validateAuthenticationResult
|
|
203
|
+
* @param {string|null} token - Authentication token
|
|
204
|
+
* @param {string|null} finalControllerUrl - Final controller URL
|
|
205
|
+
* @param {Error|null} lastError - Last error encountered
|
|
206
|
+
* @param {string|null} originalControllerUrl - Original controller URL
|
|
207
|
+
* @param {string[]} attemptedUrls - Array of attempted URLs
|
|
208
|
+
*/
|
|
209
|
+
function validateAuthenticationResult(token, finalControllerUrl, lastError, originalControllerUrl, attemptedUrls) {
|
|
210
|
+
if (!token || !finalControllerUrl) {
|
|
211
|
+
const errorData = createAuthErrorData(lastError, originalControllerUrl, attemptedUrls);
|
|
212
|
+
displayAuthenticationError(lastError, errorData);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Attempts to get authentication token
|
|
218
|
+
* @async
|
|
219
|
+
* @function attemptGetAuthenticationToken
|
|
220
|
+
* @param {string|null} normalizedControllerUrl - Normalized controller URL
|
|
221
|
+
* @param {Object} config - Configuration object
|
|
222
|
+
* @param {string[]} attemptedUrls - Array of attempted URLs
|
|
223
|
+
* @returns {Promise<Object>} Object with token, finalControllerUrl, and lastError
|
|
224
|
+
*/
|
|
225
|
+
async function attemptGetAuthenticationToken(normalizedControllerUrl, config, attemptedUrls) {
|
|
226
|
+
let finalControllerUrl = normalizedControllerUrl;
|
|
227
|
+
let token = null;
|
|
228
|
+
let lastError = null;
|
|
229
|
+
|
|
230
|
+
// If controller URL provided, try to get device token
|
|
231
|
+
const deviceTokenResult = await attemptDeviceTokenForController(finalControllerUrl, attemptedUrls);
|
|
232
|
+
if (deviceTokenResult) {
|
|
233
|
+
if (deviceTokenResult.error) {
|
|
234
|
+
lastError = deviceTokenResult.error;
|
|
235
|
+
} else {
|
|
236
|
+
token = deviceTokenResult.token;
|
|
237
|
+
finalControllerUrl = deviceTokenResult.controllerUrl;
|
|
123
238
|
}
|
|
239
|
+
}
|
|
124
240
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
241
|
+
// If no token yet, try to find any device token in config
|
|
242
|
+
if (!token) {
|
|
243
|
+
const configTokenResult = await attemptDeviceTokenFromConfig(config.device, attemptedUrls);
|
|
244
|
+
if (configTokenResult) {
|
|
245
|
+
token = configTokenResult.token;
|
|
246
|
+
finalControllerUrl = configTokenResult.controllerUrl;
|
|
129
247
|
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { token, finalControllerUrl, lastError };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function checkAuthentication(controllerUrl, _environment) {
|
|
254
|
+
try {
|
|
255
|
+
const config = await getConfig();
|
|
256
|
+
const normalizedControllerUrl = normalizeControllerUrlIfProvided(controllerUrl);
|
|
257
|
+
const attemptedUrls = [];
|
|
258
|
+
|
|
259
|
+
const { token, finalControllerUrl, lastError } = await attemptGetAuthenticationToken(
|
|
260
|
+
normalizedControllerUrl,
|
|
261
|
+
config,
|
|
262
|
+
attemptedUrls
|
|
263
|
+
);
|
|
130
264
|
|
|
131
265
|
// If no token found, display error with attempted URLs
|
|
132
|
-
|
|
133
|
-
const errorData = {
|
|
134
|
-
message: lastError ? lastError.message : 'No valid authentication found',
|
|
135
|
-
controllerUrl: controllerUrl || (attemptedUrls.length > 0 ? attemptedUrls[0] : undefined),
|
|
136
|
-
attemptedUrls: attemptedUrls.length > 1 ? attemptedUrls : undefined,
|
|
137
|
-
correlationId: undefined
|
|
138
|
-
};
|
|
139
|
-
displayAuthenticationError(lastError, errorData);
|
|
140
|
-
}
|
|
266
|
+
validateAuthenticationResult(token, finalControllerUrl, lastError, controllerUrl, attemptedUrls);
|
|
141
267
|
|
|
142
268
|
return {
|
|
143
269
|
apiUrl: finalControllerUrl,
|
|
144
270
|
token: token,
|
|
145
|
-
controllerUrl: finalControllerUrl
|
|
271
|
+
controllerUrl: finalControllerUrl
|
|
146
272
|
};
|
|
147
273
|
} catch (error) {
|
|
148
|
-
// Handle any unexpected errors during authentication check
|
|
149
274
|
displayAuthenticationError(error, { controllerUrl: controllerUrl });
|
|
150
275
|
}
|
|
151
276
|
}
|
|
@@ -15,13 +15,7 @@ const yaml = require('js-yaml');
|
|
|
15
15
|
const logger = require('./logger');
|
|
16
16
|
const { detectAppType } = require('./paths');
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
let createApp;
|
|
20
|
-
try {
|
|
21
|
-
createApp = require('../app').createApp;
|
|
22
|
-
} catch {
|
|
23
|
-
createApp = null;
|
|
24
|
-
}
|
|
18
|
+
// createApp is imported dynamically in createMinimalAppIfNeeded to handle test mocking
|
|
25
19
|
|
|
26
20
|
/**
|
|
27
21
|
* Load variables.yaml file for an application
|
|
@@ -55,11 +49,13 @@ async function loadVariablesYaml(appKey) {
|
|
|
55
49
|
* @returns {Promise<Object>} Variables after creation
|
|
56
50
|
*/
|
|
57
51
|
async function createMinimalAppIfNeeded(appKey, options) {
|
|
58
|
-
|
|
52
|
+
// Re-import createApp to check current availability (handles dynamic mocking in tests)
|
|
53
|
+
const { createApp: currentCreateApp } = require('../app');
|
|
54
|
+
if (!currentCreateApp) {
|
|
59
55
|
throw new Error('Cannot auto-create application: createApp function not available');
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
await
|
|
58
|
+
await currentCreateApp(appKey, {
|
|
63
59
|
port: options.port,
|
|
64
60
|
language: 'typescript',
|
|
65
61
|
database: false,
|
|
@@ -69,7 +65,11 @@ async function createMinimalAppIfNeeded(appKey, options) {
|
|
|
69
65
|
});
|
|
70
66
|
|
|
71
67
|
// Detect app type and get correct path (integration or builder)
|
|
72
|
-
const
|
|
68
|
+
const appTypeResult = await detectAppType(appKey);
|
|
69
|
+
if (!appTypeResult || !appTypeResult.appPath) {
|
|
70
|
+
throw new Error('Failed to detect app type after creation');
|
|
71
|
+
}
|
|
72
|
+
const { appPath } = appTypeResult;
|
|
73
73
|
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
74
74
|
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
75
75
|
return yaml.load(variablesContent);
|
|
@@ -96,6 +96,71 @@ function buildImageReference(variables, appKey) {
|
|
|
96
96
|
* @param {Object} externalIntegration - External integration config from variables.yaml
|
|
97
97
|
* @returns {Promise<{url: string, apiKey?: string}>} URL and optional API key
|
|
98
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* Resolves system file path
|
|
101
|
+
* @function resolveSystemFilePath
|
|
102
|
+
* @param {string} appPath - Application path
|
|
103
|
+
* @param {string} schemaBasePath - Schema base path
|
|
104
|
+
* @param {string} systemFileName - System file name
|
|
105
|
+
* @returns {string} Resolved system file path
|
|
106
|
+
*/
|
|
107
|
+
function resolveSystemFilePath(appPath, schemaBasePath, systemFileName) {
|
|
108
|
+
return path.isAbsolute(schemaBasePath)
|
|
109
|
+
? path.join(schemaBasePath, systemFileName)
|
|
110
|
+
: path.join(appPath, schemaBasePath, systemFileName);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Extracts URL from system JSON
|
|
115
|
+
* @function extractUrlFromSystemJson
|
|
116
|
+
* @param {Object} systemJson - System JSON object
|
|
117
|
+
* @param {string} systemFileName - System file name
|
|
118
|
+
* @returns {string} Base URL
|
|
119
|
+
* @throws {Error} If URL is missing
|
|
120
|
+
*/
|
|
121
|
+
function extractUrlFromSystemJson(systemJson, systemFileName) {
|
|
122
|
+
const url = systemJson.environment?.baseUrl;
|
|
123
|
+
if (!url) {
|
|
124
|
+
throw new Error(`Missing environment.baseUrl in ${systemFileName}`);
|
|
125
|
+
}
|
|
126
|
+
return url;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Extracts API key from system JSON if present
|
|
131
|
+
* @function extractApiKeyFromSystemJson
|
|
132
|
+
* @param {Object} systemJson - System JSON object
|
|
133
|
+
* @returns {string|undefined} API key or undefined
|
|
134
|
+
*/
|
|
135
|
+
function extractApiKeyFromSystemJson(systemJson) {
|
|
136
|
+
if (!systemJson.authentication?.apikey?.key) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// If it's a kv:// reference, we can't resolve it here, so leave it undefined
|
|
141
|
+
// The API will handle kv:// references
|
|
142
|
+
const keyValue = systemJson.authentication.apikey.key;
|
|
143
|
+
if (keyValue.startsWith('kv://') || keyValue.startsWith('{{')) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return keyValue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Handles file read errors
|
|
152
|
+
* @function handleFileReadError
|
|
153
|
+
* @param {Error} error - Error object
|
|
154
|
+
* @param {string} systemFilePath - System file path
|
|
155
|
+
* @throws {Error} Formatted error
|
|
156
|
+
*/
|
|
157
|
+
function handleFileReadError(error, systemFilePath) {
|
|
158
|
+
if (error.code === 'ENOENT') {
|
|
159
|
+
throw new Error(`External system file not found: ${systemFilePath}`);
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`Failed to read external system file: ${error.message}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
99
164
|
async function extractExternalIntegrationUrl(appKey, externalIntegration) {
|
|
100
165
|
if (!externalIntegration || !externalIntegration.systems || externalIntegration.systems.length === 0) {
|
|
101
166
|
throw new Error('externalIntegration.systems is required for external type applications');
|
|
@@ -107,37 +172,18 @@ async function extractExternalIntegrationUrl(appKey, externalIntegration) {
|
|
|
107
172
|
const systemFileName = externalIntegration.systems[0];
|
|
108
173
|
|
|
109
174
|
// Resolve system file path (handle both relative and absolute paths)
|
|
110
|
-
const systemFilePath =
|
|
111
|
-
? path.join(schemaBasePath, systemFileName)
|
|
112
|
-
: path.join(appPath, schemaBasePath, systemFileName);
|
|
175
|
+
const systemFilePath = resolveSystemFilePath(appPath, schemaBasePath, systemFileName);
|
|
113
176
|
|
|
114
177
|
try {
|
|
115
178
|
const systemContent = await fs.readFile(systemFilePath, 'utf-8');
|
|
116
179
|
const systemJson = JSON.parse(systemContent);
|
|
117
180
|
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
if (!url) {
|
|
121
|
-
throw new Error(`Missing environment.baseUrl in ${systemFileName}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Extract optional API key from authentication if present
|
|
125
|
-
let apiKey;
|
|
126
|
-
if (systemJson.authentication?.apikey?.key) {
|
|
127
|
-
// If it's a kv:// reference, we can't resolve it here, so leave it undefined
|
|
128
|
-
// The API will handle kv:// references
|
|
129
|
-
const keyValue = systemJson.authentication.apikey.key;
|
|
130
|
-
if (!keyValue.startsWith('kv://') && !keyValue.startsWith('{{')) {
|
|
131
|
-
apiKey = keyValue;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
181
|
+
const url = extractUrlFromSystemJson(systemJson, systemFileName);
|
|
182
|
+
const apiKey = extractApiKeyFromSystemJson(systemJson);
|
|
134
183
|
|
|
135
184
|
return { url, apiKey };
|
|
136
185
|
} catch (error) {
|
|
137
|
-
|
|
138
|
-
throw new Error(`External system file not found: ${systemFilePath}`);
|
|
139
|
-
}
|
|
140
|
-
throw new Error(`Failed to read external system file: ${error.message}`);
|
|
186
|
+
handleFileReadError(error, systemFilePath);
|
|
141
187
|
}
|
|
142
188
|
}
|
|
143
189
|
|
|
@@ -149,34 +195,47 @@ async function extractExternalIntegrationUrl(appKey, externalIntegration) {
|
|
|
149
195
|
* @param {Object} options - Registration options
|
|
150
196
|
* @returns {Promise<Object>} Extracted configuration
|
|
151
197
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
appKey: appKeyFromFile,
|
|
170
|
-
displayName,
|
|
171
|
-
description,
|
|
172
|
-
appType: 'external',
|
|
173
|
-
externalIntegration,
|
|
174
|
-
port: null, // External systems don't need ports
|
|
175
|
-
image: null, // External systems don't need images
|
|
176
|
-
language: null // External systems don't need language
|
|
177
|
-
};
|
|
198
|
+
/**
|
|
199
|
+
* Extracts external app configuration
|
|
200
|
+
* @async
|
|
201
|
+
* @function extractExternalAppConfiguration
|
|
202
|
+
* @param {string} appKey - Application key
|
|
203
|
+
* @param {Object} variables - Variables object
|
|
204
|
+
* @param {string} appKeyFromFile - App key from file
|
|
205
|
+
* @param {string} displayName - Display name
|
|
206
|
+
* @param {string} description - Description
|
|
207
|
+
* @returns {Promise<Object>} External app configuration
|
|
208
|
+
*/
|
|
209
|
+
async function extractExternalAppConfiguration(appKey, variables, appKeyFromFile, displayName, description) {
|
|
210
|
+
const { url, apiKey } = await extractExternalIntegrationUrl(appKey, variables.externalIntegration);
|
|
211
|
+
const externalIntegration = { url };
|
|
212
|
+
if (apiKey) {
|
|
213
|
+
externalIntegration.apiKey = apiKey;
|
|
178
214
|
}
|
|
179
215
|
|
|
216
|
+
return {
|
|
217
|
+
appKey: appKeyFromFile,
|
|
218
|
+
displayName,
|
|
219
|
+
description,
|
|
220
|
+
appType: 'external',
|
|
221
|
+
externalIntegration,
|
|
222
|
+
port: null,
|
|
223
|
+
image: null,
|
|
224
|
+
language: null
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Extracts webapp/service configuration
|
|
230
|
+
* @function extractWebappConfiguration
|
|
231
|
+
* @param {Object} variables - Variables object
|
|
232
|
+
* @param {string} appKeyFromFile - App key from file
|
|
233
|
+
* @param {string} displayName - Display name
|
|
234
|
+
* @param {string} description - Description
|
|
235
|
+
* @param {Object} options - Options object
|
|
236
|
+
* @returns {Object} Webapp configuration
|
|
237
|
+
*/
|
|
238
|
+
function extractWebappConfiguration(variables, appKeyFromFile, displayName, description, options) {
|
|
180
239
|
const appType = variables.build?.language === 'typescript' ? 'webapp' : 'service';
|
|
181
240
|
const registryMode = variables.image?.registryMode || 'external';
|
|
182
241
|
const port = variables.build?.port || options.port || 3000;
|
|
@@ -195,6 +254,18 @@ async function extractAppConfiguration(variables, appKey, options) {
|
|
|
195
254
|
};
|
|
196
255
|
}
|
|
197
256
|
|
|
257
|
+
async function extractAppConfiguration(variables, appKey, options) {
|
|
258
|
+
const appKeyFromFile = variables.app?.key || appKey;
|
|
259
|
+
const displayName = variables.app?.name || options.name || appKey;
|
|
260
|
+
const description = variables.app?.description || '';
|
|
261
|
+
|
|
262
|
+
if (variables.app?.type === 'external') {
|
|
263
|
+
return await extractExternalAppConfiguration(appKey, variables, appKeyFromFile, displayName, description);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return extractWebappConfiguration(variables, appKeyFromFile, displayName, description, options);
|
|
267
|
+
}
|
|
268
|
+
|
|
198
269
|
module.exports = {
|
|
199
270
|
loadVariablesYaml,
|
|
200
271
|
createMinimalAppIfNeeded,
|
|
@@ -52,10 +52,36 @@ async function checkImageExists(imageName, tag = 'latest', debug = false) {
|
|
|
52
52
|
* @param {boolean} [debug=false] - Enable debug logging
|
|
53
53
|
* @returns {Promise<boolean>} True if container is running
|
|
54
54
|
*/
|
|
55
|
+
/**
|
|
56
|
+
* Gets container name from app name and developer ID
|
|
57
|
+
* @function getContainerName
|
|
58
|
+
* @param {string} appName - Application name
|
|
59
|
+
* @param {number|string} developerId - Developer ID
|
|
60
|
+
* @returns {string} Container name
|
|
61
|
+
*/
|
|
62
|
+
function getContainerName(appName, developerId) {
|
|
63
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
64
|
+
return idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Logs debug information about container
|
|
69
|
+
* @async
|
|
70
|
+
* @function logContainerDebugInfo
|
|
71
|
+
* @param {string} containerName - Container name
|
|
72
|
+
*/
|
|
73
|
+
async function logContainerDebugInfo(containerName) {
|
|
74
|
+
const statusCmd = `docker ps --filter "name=${containerName}" --format "{{.Status}}"`;
|
|
75
|
+
const { stdout: status } = await execAsync(statusCmd);
|
|
76
|
+
const portsCmd = `docker ps --filter "name=${containerName}" --format "{{.Ports}}"`;
|
|
77
|
+
const { stdout: ports } = await execAsync(portsCmd);
|
|
78
|
+
logger.log(chalk.gray(`[DEBUG] Container status: ${status.trim()}`));
|
|
79
|
+
logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
|
|
80
|
+
}
|
|
81
|
+
|
|
55
82
|
async function checkContainerRunning(appName, developerId, debug = false) {
|
|
56
83
|
try {
|
|
57
|
-
const
|
|
58
|
-
const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
|
|
84
|
+
const containerName = getContainerName(appName, developerId);
|
|
59
85
|
const cmd = `docker ps --filter "name=${containerName}" --format "{{.Names}}"`;
|
|
60
86
|
if (debug) {
|
|
61
87
|
logger.log(chalk.gray(`[DEBUG] Executing: ${cmd}`));
|
|
@@ -65,12 +91,7 @@ async function checkContainerRunning(appName, developerId, debug = false) {
|
|
|
65
91
|
if (debug) {
|
|
66
92
|
logger.log(chalk.gray(`[DEBUG] Container ${containerName} running: ${isRunning}`));
|
|
67
93
|
if (isRunning) {
|
|
68
|
-
|
|
69
|
-
const { stdout: status } = await execAsync(statusCmd);
|
|
70
|
-
const portsCmd = `docker ps --filter "name=${containerName}" --format "{{.Ports}}"`;
|
|
71
|
-
const { stdout: ports } = await execAsync(portsCmd);
|
|
72
|
-
logger.log(chalk.gray(`[DEBUG] Container status: ${status.trim()}`));
|
|
73
|
-
logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
|
|
94
|
+
await logContainerDebugInfo(containerName);
|
|
74
95
|
}
|
|
75
96
|
}
|
|
76
97
|
return isRunning;
|
|
@@ -55,7 +55,7 @@ async function determineDockerfile(appName, options, generateDockerfileFn) {
|
|
|
55
55
|
*/
|
|
56
56
|
async function loadAndValidateConfig(appName) {
|
|
57
57
|
const { loadVariablesYaml } = require('../build');
|
|
58
|
-
const validator = require('../validator');
|
|
58
|
+
const validator = require('../validation/validator');
|
|
59
59
|
|
|
60
60
|
const variables = await loadVariablesYaml(appName);
|
|
61
61
|
|