@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
package/lib/utils/cli-utils.js
CHANGED
|
@@ -38,31 +38,76 @@ function formatFormattedError(formatted) {
|
|
|
38
38
|
return messages;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Checks if error is about Docker image not found
|
|
43
|
+
* @function isDockerImageNotFoundError
|
|
44
|
+
* @param {string} errorMsg - Error message
|
|
45
|
+
* @returns {boolean} True if Docker image not found error
|
|
46
|
+
*/
|
|
47
|
+
function isDockerImageNotFoundError(errorMsg) {
|
|
48
|
+
return errorMsg.includes('not found locally') ||
|
|
49
|
+
(errorMsg.includes('Docker image') && errorMsg.includes('not found'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if error is about Docker not running/installed
|
|
54
|
+
* @function isDockerNotRunningError
|
|
55
|
+
* @param {string} errorMsg - Error message
|
|
56
|
+
* @returns {boolean} True if Docker not running error
|
|
57
|
+
*/
|
|
58
|
+
function isDockerNotRunningError(errorMsg) {
|
|
59
|
+
return errorMsg.includes('Docker') &&
|
|
60
|
+
(errorMsg.includes('not running') || errorMsg.includes('not installed') || errorMsg.includes('Cannot connect'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Checks if error is about port conflict
|
|
65
|
+
* @function isPortConflictError
|
|
66
|
+
* @param {string} errorMsg - Error message
|
|
67
|
+
* @returns {boolean} True if port conflict error
|
|
68
|
+
*/
|
|
69
|
+
function isPortConflictError(errorMsg) {
|
|
70
|
+
return errorMsg.toLowerCase().includes('port') &&
|
|
71
|
+
(errorMsg.includes('already in use') || errorMsg.includes('in use') || errorMsg.includes('conflict'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks if error is about permission denied (excluding permissions field)
|
|
76
|
+
* @function isPermissionDeniedError
|
|
77
|
+
* @param {string} errorMsg - Error message
|
|
78
|
+
* @returns {boolean} True if permission denied error
|
|
79
|
+
*/
|
|
80
|
+
function isPermissionDeniedError(errorMsg) {
|
|
81
|
+
return (errorMsg.includes('permission denied') || errorMsg.includes('EACCES') || errorMsg.includes('Permission denied')) &&
|
|
82
|
+
!errorMsg.includes('permissions/') &&
|
|
83
|
+
!errorMsg.includes('Field "permissions');
|
|
84
|
+
}
|
|
85
|
+
|
|
41
86
|
/**
|
|
42
87
|
* Format Docker-related errors
|
|
43
88
|
* @param {string} errorMsg - Error message
|
|
44
89
|
* @returns {string[]|null} Array of error message lines or null if not a Docker error
|
|
45
90
|
*/
|
|
46
91
|
function formatDockerError(errorMsg) {
|
|
47
|
-
if (
|
|
92
|
+
if (isDockerImageNotFoundError(errorMsg)) {
|
|
48
93
|
return [
|
|
49
94
|
' Docker image not found.',
|
|
50
95
|
' Run: aifabrix build <app> first'
|
|
51
96
|
];
|
|
52
97
|
}
|
|
53
|
-
if (
|
|
98
|
+
if (isDockerNotRunningError(errorMsg)) {
|
|
54
99
|
return [
|
|
55
100
|
' Docker is not running or not installed.',
|
|
56
101
|
' Please start Docker Desktop and try again.'
|
|
57
102
|
];
|
|
58
103
|
}
|
|
59
|
-
if (
|
|
104
|
+
if (isPortConflictError(errorMsg)) {
|
|
60
105
|
return [
|
|
61
106
|
' Port conflict detected.',
|
|
62
107
|
' Run "aifabrix doctor" to check which ports are in use.'
|
|
63
108
|
];
|
|
64
109
|
}
|
|
65
|
-
if ((errorMsg
|
|
110
|
+
if (isPermissionDeniedError(errorMsg)) {
|
|
66
111
|
return [
|
|
67
112
|
' Permission denied.',
|
|
68
113
|
' Make sure you have the necessary permissions to run Docker commands.'
|
|
@@ -179,6 +224,31 @@ function formatValidationError(errorMsg) {
|
|
|
179
224
|
* @param {Error} error - The error that occurred
|
|
180
225
|
* @returns {string[]} Array of error message lines
|
|
181
226
|
*/
|
|
227
|
+
/**
|
|
228
|
+
* Tries to format error using specific formatters
|
|
229
|
+
* @function tryFormatErrorWithFormatters
|
|
230
|
+
* @param {string} errorMsg - Error message
|
|
231
|
+
* @returns {string[]|null} Formatted error messages or null if no formatter matched
|
|
232
|
+
*/
|
|
233
|
+
function tryFormatErrorWithFormatters(errorMsg) {
|
|
234
|
+
const formatters = [
|
|
235
|
+
formatDockerError,
|
|
236
|
+
formatAzureError,
|
|
237
|
+
formatSecretsError,
|
|
238
|
+
formatDeploymentError,
|
|
239
|
+
formatValidationError
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
for (const formatter of formatters) {
|
|
243
|
+
const formatted = formatter(errorMsg);
|
|
244
|
+
if (formatted) {
|
|
245
|
+
return formatted;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
182
252
|
function formatError(error) {
|
|
183
253
|
// If error has formatted message (from API error handler), use it directly
|
|
184
254
|
if (error.formatted) {
|
|
@@ -186,37 +256,15 @@ function formatError(error) {
|
|
|
186
256
|
}
|
|
187
257
|
|
|
188
258
|
const errorMsg = error.message || '';
|
|
189
|
-
const messages = [];
|
|
190
259
|
|
|
191
260
|
// Try different error formatters in order of specificity
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
return
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const azureError = formatAzureError(errorMsg);
|
|
198
|
-
if (azureError) {
|
|
199
|
-
return azureError;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const secretsError = formatSecretsError(errorMsg);
|
|
203
|
-
if (secretsError) {
|
|
204
|
-
return secretsError;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const deploymentError = formatDeploymentError(errorMsg);
|
|
208
|
-
if (deploymentError) {
|
|
209
|
-
return deploymentError;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const validationError = formatValidationError(errorMsg);
|
|
213
|
-
if (validationError) {
|
|
214
|
-
return validationError;
|
|
261
|
+
const formatted = tryFormatErrorWithFormatters(errorMsg);
|
|
262
|
+
if (formatted) {
|
|
263
|
+
return formatted;
|
|
215
264
|
}
|
|
216
265
|
|
|
217
266
|
// Default: return generic error message
|
|
218
|
-
|
|
219
|
-
return messages;
|
|
267
|
+
return [` ${errorMsg}`];
|
|
220
268
|
}
|
|
221
269
|
|
|
222
270
|
/**
|
|
@@ -13,7 +13,7 @@ const fsSync = require('fs');
|
|
|
13
13
|
const fs = require('fs').promises;
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const handlebars = require('handlebars');
|
|
16
|
-
const config = require('../config');
|
|
16
|
+
const config = require('../core/config');
|
|
17
17
|
const buildCopy = require('./build-copy');
|
|
18
18
|
|
|
19
19
|
// Register commonly used helpers
|
|
@@ -230,19 +230,26 @@ function buildNetworksConfig(config) {
|
|
|
230
230
|
* @returns {Promise<Object>} Object with passwords array and lookup map
|
|
231
231
|
* @throws {Error} If required password variables are missing
|
|
232
232
|
*/
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Reads and parses .env file
|
|
235
|
+
* @async
|
|
236
|
+
* @function readEnvFile
|
|
237
|
+
* @param {string} envPath - Path to .env file
|
|
238
|
+
* @returns {Promise<Object>} Object with environment variables
|
|
239
|
+
* @throws {Error} If file not found or read fails
|
|
240
|
+
*/
|
|
241
|
+
async function readEnvFile(envPath) {
|
|
239
242
|
if (!fsSync.existsSync(envPath)) {
|
|
240
243
|
throw new Error(`.env file not found: ${envPath}`);
|
|
241
244
|
}
|
|
242
245
|
|
|
243
246
|
try {
|
|
244
247
|
const envContent = await fs.readFile(envPath, 'utf8');
|
|
248
|
+
if (envContent === undefined || envContent === null) {
|
|
249
|
+
throw new Error('Failed to read .env file: file content is empty or undefined');
|
|
250
|
+
}
|
|
245
251
|
const lines = envContent.split('\n');
|
|
252
|
+
const envVars = {};
|
|
246
253
|
|
|
247
254
|
for (const line of lines) {
|
|
248
255
|
const trimmed = line.trim();
|
|
@@ -257,46 +264,97 @@ async function readDatabasePasswords(envPath, databases, appKey) {
|
|
|
257
264
|
envVars[key] = value;
|
|
258
265
|
}
|
|
259
266
|
}
|
|
267
|
+
|
|
268
|
+
return envVars;
|
|
260
269
|
} catch (error) {
|
|
261
270
|
throw new Error(`Failed to read .env file: ${error.message}`);
|
|
262
271
|
}
|
|
272
|
+
}
|
|
263
273
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Validates and extracts password from environment variables
|
|
276
|
+
* @function extractPassword
|
|
277
|
+
* @param {Object} envVars - Environment variables
|
|
278
|
+
* @param {string} passwordKey - Password key to look up
|
|
279
|
+
* @returns {string} Password value
|
|
280
|
+
* @throws {Error} If password is missing or empty
|
|
281
|
+
*/
|
|
282
|
+
function extractPassword(envVars, passwordKey) {
|
|
283
|
+
if (!(passwordKey in envVars)) {
|
|
284
|
+
throw new Error(`Missing required password variable ${passwordKey} in .env file`);
|
|
285
|
+
}
|
|
274
286
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
287
|
+
const password = envVars[passwordKey].trim();
|
|
288
|
+
if (!password || password.length === 0) {
|
|
289
|
+
throw new Error(`Password variable ${passwordKey} is empty in .env file`);
|
|
290
|
+
}
|
|
279
291
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
} else {
|
|
284
|
-
// Single database case - use DB_0_PASSWORD or DB_PASSWORD
|
|
285
|
-
const passwordKey = ('DB_0_PASSWORD' in envVars) ? 'DB_0_PASSWORD' : 'DB_PASSWORD';
|
|
292
|
+
return password;
|
|
293
|
+
}
|
|
286
294
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Processes multiple databases
|
|
297
|
+
* @function processMultipleDatabases
|
|
298
|
+
* @param {Array} databases - Array of database configurations
|
|
299
|
+
* @param {Object} envVars - Environment variables
|
|
300
|
+
* @param {string} appKey - Application key
|
|
301
|
+
* @returns {Object} Object with passwords map and array
|
|
302
|
+
*/
|
|
303
|
+
function processMultipleDatabases(databases, envVars, appKey) {
|
|
304
|
+
const passwords = {};
|
|
305
|
+
const passwordsArray = [];
|
|
290
306
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
307
|
+
for (let i = 0; i < databases.length; i++) {
|
|
308
|
+
const db = databases[i];
|
|
309
|
+
const dbName = db.name || appKey;
|
|
310
|
+
const passwordKey = `DB_${i}_PASSWORD`;
|
|
311
|
+
const password = extractPassword(envVars, passwordKey);
|
|
295
312
|
|
|
296
|
-
passwords[
|
|
313
|
+
passwords[dbName] = password;
|
|
297
314
|
passwordsArray.push(password);
|
|
298
315
|
}
|
|
299
316
|
|
|
317
|
+
return { passwords, passwordsArray };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Processes single database case
|
|
322
|
+
* @function processSingleDatabase
|
|
323
|
+
* @param {Object} envVars - Environment variables
|
|
324
|
+
* @param {string} appKey - Application key
|
|
325
|
+
* @returns {Object} Object with passwords map and array
|
|
326
|
+
*/
|
|
327
|
+
function processSingleDatabase(envVars, appKey) {
|
|
328
|
+
const passwords = {};
|
|
329
|
+
const passwordsArray = [];
|
|
330
|
+
|
|
331
|
+
// Single database case - use DB_0_PASSWORD or DB_PASSWORD
|
|
332
|
+
const passwordKey = ('DB_0_PASSWORD' in envVars) ? 'DB_0_PASSWORD' : 'DB_PASSWORD';
|
|
333
|
+
|
|
334
|
+
if (!(passwordKey in envVars)) {
|
|
335
|
+
throw new Error(`Missing required password variable ${passwordKey} in .env file. Add DB_0_PASSWORD or DB_PASSWORD to your .env file.`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const password = extractPassword(envVars, passwordKey);
|
|
339
|
+
passwords[appKey] = password;
|
|
340
|
+
passwordsArray.push(password);
|
|
341
|
+
|
|
342
|
+
return { passwords, passwordsArray };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function readDatabasePasswords(envPath, databases, appKey) {
|
|
346
|
+
const envVars = await readEnvFile(envPath);
|
|
347
|
+
|
|
348
|
+
// Process each database
|
|
349
|
+
if (databases && databases.length > 0) {
|
|
350
|
+
const { passwords, passwordsArray } = processMultipleDatabases(databases, envVars, appKey);
|
|
351
|
+
return {
|
|
352
|
+
map: passwords,
|
|
353
|
+
array: passwordsArray
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const { passwords, passwordsArray } = processSingleDatabase(envVars, appKey);
|
|
300
358
|
return {
|
|
301
359
|
map: passwords,
|
|
302
360
|
array: passwordsArray
|
|
@@ -310,48 +368,71 @@ async function readDatabasePasswords(envPath, databases, appKey) {
|
|
|
310
368
|
* @param {Object} options - Run options
|
|
311
369
|
* @returns {Promise<string>} Generated compose content
|
|
312
370
|
*/
|
|
371
|
+
/**
|
|
372
|
+
* Gets developer ID and calculates numeric ID
|
|
373
|
+
* @async
|
|
374
|
+
* @function getDeveloperIdAndNumeric
|
|
375
|
+
* @returns {Promise<Object>} Object with devId and idNum
|
|
376
|
+
*/
|
|
377
|
+
async function getDeveloperIdAndNumeric() {
|
|
378
|
+
const devId = await config.getDeveloperId();
|
|
379
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
380
|
+
return { devId, idNum };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Builds network and container names
|
|
385
|
+
* @function buildNetworkAndContainerNames
|
|
386
|
+
* @param {string} appName - Application name
|
|
387
|
+
* @param {string|number} devId - Developer ID
|
|
388
|
+
* @param {number} idNum - Numeric developer ID
|
|
389
|
+
* @returns {Object} Object with networkName and containerName
|
|
390
|
+
*/
|
|
391
|
+
function buildNetworkAndContainerNames(appName, devId, idNum) {
|
|
392
|
+
const networkName = idNum === 0 ? 'infra-aifabrix-network' : `infra-dev${devId}-aifabrix-network`;
|
|
393
|
+
const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${devId}-${appName}`;
|
|
394
|
+
return { networkName, containerName };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Reads database passwords if needed
|
|
399
|
+
* @async
|
|
400
|
+
* @function readDatabasePasswordsIfNeeded
|
|
401
|
+
* @param {boolean} requiresDatabase - Whether database is required
|
|
402
|
+
* @param {Array} databases - Array of databases
|
|
403
|
+
* @param {string} envFilePath - Environment file path
|
|
404
|
+
* @param {string} appName - Application name
|
|
405
|
+
* @returns {Promise<Object>} Database passwords object
|
|
406
|
+
*/
|
|
407
|
+
async function readDatabasePasswordsIfNeeded(requiresDatabase, databases, envFilePath, appName) {
|
|
408
|
+
if (requiresDatabase || databases.length > 0) {
|
|
409
|
+
return await readDatabasePasswords(envFilePath, databases, appName);
|
|
410
|
+
}
|
|
411
|
+
return { map: {}, array: [] };
|
|
412
|
+
}
|
|
413
|
+
|
|
313
414
|
async function generateDockerCompose(appName, appConfig, options) {
|
|
314
415
|
const language = appConfig.build?.language || appConfig.language || 'typescript';
|
|
315
416
|
const template = loadDockerComposeTemplate(language);
|
|
316
|
-
|
|
317
|
-
// Use options.port if provided, otherwise use config.port
|
|
318
|
-
// (localPort will be handled in buildServiceConfig)
|
|
319
417
|
const port = options.port || appConfig.port || 3000;
|
|
320
418
|
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
324
|
-
// Dev 0: infra-aifabrix-network (no dev-0 suffix)
|
|
325
|
-
// Dev > 0: infra-dev{id}-aifabrix-network
|
|
326
|
-
const networkName = idNum === 0 ? 'infra-aifabrix-network' : `infra-dev${devId}-aifabrix-network`;
|
|
327
|
-
// Dev 0: aifabrix-{appName} (no dev-0 suffix)
|
|
328
|
-
// Dev > 0: aifabrix-dev{id}-{appName}
|
|
329
|
-
const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${devId}-${appName}`;
|
|
419
|
+
const { devId, idNum } = await getDeveloperIdAndNumeric();
|
|
420
|
+
const { networkName, containerName } = buildNetworkAndContainerNames(appName, devId, idNum);
|
|
330
421
|
|
|
331
422
|
const serviceConfig = buildServiceConfig(appName, appConfig, port);
|
|
332
423
|
const volumesConfig = buildVolumesConfig(appName);
|
|
333
424
|
const networksConfig = buildNetworksConfig(appConfig);
|
|
334
425
|
|
|
335
|
-
// Get absolute path to .env file for docker-compose (use dev-specific directory)
|
|
336
426
|
const devDir = buildCopy.getDevDirectory(appName, devId);
|
|
337
427
|
const envFilePath = path.join(devDir, '.env');
|
|
338
|
-
const envFileAbsolutePath = envFilePath.replace(/\\/g, '/');
|
|
428
|
+
const envFileAbsolutePath = envFilePath.replace(/\\/g, '/');
|
|
339
429
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
// Only read passwords if database is actually required
|
|
347
|
-
databasePasswords = await readDatabasePasswords(envFilePath, databases, appName);
|
|
348
|
-
} else {
|
|
349
|
-
// Return empty passwords when database is not required
|
|
350
|
-
databasePasswords = {
|
|
351
|
-
map: {},
|
|
352
|
-
array: []
|
|
353
|
-
};
|
|
354
|
-
}
|
|
430
|
+
const databasePasswords = await readDatabasePasswordsIfNeeded(
|
|
431
|
+
serviceConfig.requiresDatabase || false,
|
|
432
|
+
networksConfig.databases || [],
|
|
433
|
+
envFilePath,
|
|
434
|
+
appName
|
|
435
|
+
);
|
|
355
436
|
|
|
356
437
|
const templateData = {
|
|
357
438
|
...serviceConfig,
|
|
@@ -359,7 +440,6 @@ async function generateDockerCompose(appName, appConfig, options) {
|
|
|
359
440
|
...networksConfig,
|
|
360
441
|
envFile: envFileAbsolutePath,
|
|
361
442
|
databasePasswords: databasePasswords,
|
|
362
|
-
// IMPORTANT: pass numeric devId to templates so (eq devId 0) works correctly
|
|
363
443
|
devId: idNum,
|
|
364
444
|
networkName: networkName,
|
|
365
445
|
containerName: containerName
|
package/lib/utils/device-code.js
CHANGED
|
@@ -146,41 +146,67 @@ function parseTokenResponse(response) {
|
|
|
146
146
|
* @param {Object} response - Full API response object
|
|
147
147
|
* @returns {Error} Validation error with formattedError and errorData attached
|
|
148
148
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Attaches formatted error to validation error
|
|
151
|
+
* @function attachFormattedError
|
|
152
|
+
* @param {Error} validationError - Validation error object
|
|
153
|
+
* @param {Object} response - API response
|
|
154
|
+
*/
|
|
155
|
+
function attachFormattedError(validationError, response) {
|
|
153
156
|
if (response && response.formattedError) {
|
|
154
157
|
validationError.formattedError = response.formattedError;
|
|
155
158
|
validationError.message = `Token polling failed:\n${response.formattedError}`;
|
|
156
159
|
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Builds detailed error message from error data
|
|
164
|
+
* @function buildDetailedErrorMessage
|
|
165
|
+
* @param {Object} errorData - Error data object
|
|
166
|
+
* @returns {string} Detailed error message
|
|
167
|
+
*/
|
|
168
|
+
function buildDetailedErrorMessage(errorData) {
|
|
169
|
+
const detail = errorData.detail || errorData.title || errorData.message || 'Validation error';
|
|
170
|
+
let errorMsg = `Token polling failed: ${detail}`;
|
|
171
|
+
|
|
172
|
+
if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
|
|
173
|
+
errorMsg += '\n\nValidation errors:';
|
|
174
|
+
errorData.errors.forEach(err => {
|
|
175
|
+
const field = err.field || err.path || 'validation';
|
|
176
|
+
const message = err.message || 'Invalid value';
|
|
177
|
+
if (field === 'validation' || field === 'unknown') {
|
|
178
|
+
errorMsg += `\n • ${message}`;
|
|
179
|
+
} else {
|
|
180
|
+
errorMsg += `\n • ${field}: ${message}`;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return errorMsg;
|
|
186
|
+
}
|
|
157
187
|
|
|
158
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Attaches error data to validation error
|
|
190
|
+
* @function attachErrorData
|
|
191
|
+
* @param {Error} validationError - Validation error object
|
|
192
|
+
* @param {Object} response - API response
|
|
193
|
+
*/
|
|
194
|
+
function attachErrorData(validationError, response) {
|
|
159
195
|
if (response && response.errorData) {
|
|
160
196
|
validationError.errorData = response.errorData;
|
|
161
197
|
validationError.errorType = response.errorType || 'validation';
|
|
162
198
|
|
|
163
|
-
// Build detailed message if formattedError not available
|
|
164
199
|
if (!validationError.formattedError) {
|
|
165
|
-
|
|
166
|
-
const detail = errorData.detail || errorData.title || errorData.message || 'Validation error';
|
|
167
|
-
let errorMsg = `Token polling failed: ${detail}`;
|
|
168
|
-
// Add validation errors if available
|
|
169
|
-
if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
|
|
170
|
-
errorMsg += '\n\nValidation errors:';
|
|
171
|
-
errorData.errors.forEach(err => {
|
|
172
|
-
const field = err.field || err.path || 'validation';
|
|
173
|
-
const message = err.message || 'Invalid value';
|
|
174
|
-
if (field === 'validation' || field === 'unknown') {
|
|
175
|
-
errorMsg += `\n • ${message}`;
|
|
176
|
-
} else {
|
|
177
|
-
errorMsg += `\n • ${field}: ${message}`;
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
validationError.message = errorMsg;
|
|
200
|
+
validationError.message = buildDetailedErrorMessage(response.errorData);
|
|
182
201
|
}
|
|
183
202
|
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function createValidationError(response) {
|
|
206
|
+
const validationError = new Error('Token polling failed: Validation error');
|
|
207
|
+
|
|
208
|
+
attachFormattedError(validationError, response);
|
|
209
|
+
attachErrorData(validationError, response);
|
|
184
210
|
|
|
185
211
|
return validationError;
|
|
186
212
|
}
|
|
@@ -239,36 +265,69 @@ async function waitForNextPoll(interval, slowDown) {
|
|
|
239
265
|
* @param {Object} response - API response object
|
|
240
266
|
* @returns {string} Error code or 'Unknown error'
|
|
241
267
|
*/
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Checks if error code indicates validation error
|
|
270
|
+
* @function isValidationErrorCode
|
|
271
|
+
* @param {string} errorCode - Error code to check
|
|
272
|
+
* @returns {boolean} True if validation error
|
|
273
|
+
*/
|
|
274
|
+
function isValidationErrorCode(errorCode) {
|
|
275
|
+
return errorCode === 'INVALID_TOKEN' || errorCode === 'INVALID_ACCESS_TOKEN';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Extracts error from structured error data
|
|
280
|
+
* @function extractStructuredError
|
|
281
|
+
* @param {Object} response - API response with errorData
|
|
282
|
+
* @returns {string} Error message or code
|
|
283
|
+
*/
|
|
284
|
+
function extractStructuredError(response) {
|
|
285
|
+
const errorData = response.errorData;
|
|
286
|
+
|
|
287
|
+
// For validation errors, return the error type so we can handle it specially
|
|
288
|
+
if (response.errorType === 'validation') {
|
|
289
|
+
return 'validation_error';
|
|
257
290
|
}
|
|
258
291
|
|
|
259
|
-
//
|
|
292
|
+
// Check if error code indicates validation error (e.g., INVALID_TOKEN)
|
|
293
|
+
const errorCode = errorData.error || errorData.code || response.error;
|
|
294
|
+
if (isValidationErrorCode(errorCode)) {
|
|
295
|
+
return 'validation_error';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Return the error message from structured error
|
|
299
|
+
return errorData.detail || errorData.title || errorData.message || errorCode || response.error || 'Unknown error';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Extracts error from fallback response structure
|
|
304
|
+
* @function extractFallbackError
|
|
305
|
+
* @param {Object} response - API response
|
|
306
|
+
* @returns {string} Error code
|
|
307
|
+
*/
|
|
308
|
+
function extractFallbackError(response) {
|
|
260
309
|
const apiResponse = response.data || {};
|
|
261
310
|
const errorData = typeof apiResponse === 'object' ? apiResponse : {};
|
|
262
311
|
const errorCode = errorData.error || response.error || 'Unknown error';
|
|
263
312
|
|
|
264
313
|
// Check if error code indicates validation error (e.g., INVALID_TOKEN)
|
|
265
|
-
if (errorCode
|
|
314
|
+
if (isValidationErrorCode(errorCode)) {
|
|
266
315
|
return 'validation_error';
|
|
267
316
|
}
|
|
268
317
|
|
|
269
318
|
return errorCode;
|
|
270
319
|
}
|
|
271
320
|
|
|
321
|
+
function extractPollingError(response) {
|
|
322
|
+
// Check for structured error data first (from api-error-handler)
|
|
323
|
+
if (response.errorData) {
|
|
324
|
+
return extractStructuredError(response);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Fallback to original extraction logic
|
|
328
|
+
return extractFallbackError(response);
|
|
329
|
+
}
|
|
330
|
+
|
|
272
331
|
/**
|
|
273
332
|
* Handles successful polling response
|
|
274
333
|
* @param {Object} response - API response object
|
|
@@ -439,4 +498,3 @@ module.exports = {
|
|
|
439
498
|
refreshDeviceToken,
|
|
440
499
|
parseTokenResponse
|
|
441
500
|
};
|
|
442
|
-
|