@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
@@ -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
- async function checkAuthentication(controllerUrl, environment) {
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 config = await getConfig();
92
-
93
- // Try to get controller URL from parameter, config, or device tokens
94
- // Handle empty string as falsy (treat same as undefined/null)
95
- const normalizedControllerUrl = (controllerUrl && controllerUrl.trim()) ? normalizeControllerUrl(controllerUrl) : null;
96
- let finalControllerUrl = normalizedControllerUrl;
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
- // If no token yet, try to find any device token in config
117
- if (!token && config.device) {
118
- const tokenResult = await findDeviceTokenFromConfig(config.device, attemptedUrls);
119
- if (tokenResult) {
120
- token = tokenResult.token;
121
- finalControllerUrl = tokenResult.controllerUrl;
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
- // If still no token, check for client token (requires environment and app)
126
- if (!token && environment) {
127
- // For app register, we don't have an app yet, so client tokens won't work
128
- // This is expected - device tokens should be used for registration
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
- if (!token || !finalControllerUrl) {
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 // Return the actual URL used
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
- // Import createApp to auto-generate config if missing
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
- if (!createApp) {
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 createApp(appKey, {
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 { appPath } = await detectAppType(appKey);
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 = path.isAbsolute(schemaBasePath)
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
- // Extract URL from environment.baseUrl
119
- const url = systemJson.environment?.baseUrl;
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
- if (error.code === 'ENOENT') {
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
- async function extractAppConfiguration(variables, appKey, options) {
153
- const appKeyFromFile = variables.app?.key || appKey;
154
- const displayName = variables.app?.name || options.name || appKey;
155
- const description = variables.app?.description || '';
156
-
157
- // Handle external type
158
- if (variables.app?.type === 'external') {
159
- // Extract URL from external system JSON file
160
- const { url, apiKey } = await extractExternalIntegrationUrl(appKey, variables.externalIntegration);
161
-
162
- // Build simplified externalIntegration object for registration API
163
- const externalIntegration = { url };
164
- if (apiKey) {
165
- externalIntegration.apiKey = apiKey;
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 idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
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
- const statusCmd = `docker ps --filter "name=${containerName}" --format "{{.Status}}"`;
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