@aifabrix/builder 2.32.3 → 2.33.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/.cursor/rules/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +12 -11
- package/integration/hubspot/companies.json +2048 -0
- package/integration/hubspot/create-hubspot.js +665 -0
- package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
- package/integration/hubspot/hubspot-deploy.json +832 -81
- package/integration/hubspot/hubspot-system.json +99 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
- package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
- package/integration/hubspot/test-dataplane-down-tests.js +419 -0
- package/integration/hubspot/test-dataplane-down.js +157 -0
- package/integration/hubspot/test.js +1517 -0
- package/integration/hubspot/variables.yaml +4 -4
- package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
- package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
- package/lib/api/applications.api.js +1 -0
- package/lib/api/index.js +6 -2
- package/lib/api/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +161 -23
- package/lib/app/deploy.js +116 -54
- package/lib/app/display.js +6 -5
- package/lib/app/dockerfile.js +2 -1
- package/lib/app/list.js +17 -10
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +48 -31
- package/lib/cli.js +219 -70
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +7 -8
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +26 -17
- package/lib/commands/login.js +12 -10
- package/lib/commands/wizard-config-normalizer.js +92 -0
- package/lib/commands/wizard-core.js +515 -0
- package/lib/commands/wizard-dataplane.js +122 -0
- package/lib/commands/wizard-headless.js +115 -0
- package/lib/commands/wizard.js +110 -332
- package/lib/core/config.js +46 -0
- package/lib/core/secrets.js +3 -22
- package/lib/core/templates-env.js +1 -1
- package/lib/datasource/deploy.js +59 -23
- package/lib/datasource/list.js +108 -19
- package/lib/deployment/deployer.js +25 -0
- package/lib/deployment/environment.js +10 -13
- package/lib/external-system/delete.js +151 -0
- package/lib/external-system/deploy.js +53 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +33 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +4 -3
- package/lib/generator/builders.js +3 -1
- package/lib/generator/external-controller-manifest.js +157 -0
- package/lib/generator/external-schema-utils.js +236 -0
- package/lib/generator/external.js +55 -3
- package/lib/generator/index.js +22 -10
- package/lib/generator/wizard-prompts.js +33 -10
- package/lib/generator/wizard.js +69 -86
- package/lib/infrastructure/compose.js +100 -0
- package/lib/infrastructure/helpers.js +139 -0
- package/lib/infrastructure/index.js +52 -311
- package/lib/infrastructure/services.js +168 -0
- package/lib/schema/application-schema.json +23 -4
- package/lib/schema/external-datasource.schema.json +2 -2
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +102 -52
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +65 -17
- package/lib/utils/dataplane-health.js +115 -0
- package/lib/utils/dataplane-resolver.js +29 -0
- package/lib/utils/dev-config.js +6 -2
- package/lib/utils/env-copy.js +2 -1
- package/lib/utils/env-ports.js +2 -1
- package/lib/utils/env-template.js +1 -1
- package/lib/utils/error-formatter.js +49 -0
- package/lib/utils/error-formatters/network-errors.js +13 -3
- package/lib/utils/external-readme.js +125 -0
- package/lib/utils/help-builder.js +190 -0
- package/lib/utils/infra-status.js +13 -3
- package/lib/utils/paths.js +17 -2
- package/lib/utils/port-resolver.js +111 -0
- package/lib/utils/secrets-helpers.js +3 -15
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/token-manager.js +9 -4
- package/lib/utils/variable-transformer.js +7 -2
- package/lib/validation/external-manifest-validator.js +202 -0
- package/lib/validation/validate-display.js +406 -0
- package/lib/validation/validate.js +159 -123
- package/lib/validation/validator.js +36 -3
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +18 -16
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +7 -7
- package/templates/external-system/README.md.hbs +99 -0
- package/templates/github/ci.yaml.hbs +44 -1
- package/templates/github/release.yaml.hbs +44 -0
- package/templates/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- package/templates/typescript/docker-compose.hbs +26 -0
|
@@ -15,6 +15,8 @@ const path = require('path');
|
|
|
15
15
|
const handlebars = require('handlebars');
|
|
16
16
|
const config = require('../core/config');
|
|
17
17
|
const buildCopy = require('./build-copy');
|
|
18
|
+
const { formatMissingDbPasswordError } = require('./error-formatter');
|
|
19
|
+
const { getContainerPort } = require('./port-resolver');
|
|
18
20
|
|
|
19
21
|
// Register commonly used helpers
|
|
20
22
|
handlebars.registerHelper('eq', (a, b) => a === b);
|
|
@@ -150,6 +152,71 @@ function buildHealthCheckConfig(config) {
|
|
|
150
152
|
};
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Derives base path from routing pattern by removing trailing wildcards
|
|
157
|
+
* @param {string} pattern - URL pattern (e.g., '/app/*', '/api/v1/*')
|
|
158
|
+
* @returns {string} Base path for routing
|
|
159
|
+
*/
|
|
160
|
+
function derivePathFromPattern(pattern) {
|
|
161
|
+
if (!pattern || typeof pattern !== 'string') {
|
|
162
|
+
return '/';
|
|
163
|
+
}
|
|
164
|
+
const trimmed = pattern.trim();
|
|
165
|
+
if (trimmed === '/' || trimmed === '') {
|
|
166
|
+
return '/';
|
|
167
|
+
}
|
|
168
|
+
const withoutWildcards = trimmed.replace(/\*+$/g, '');
|
|
169
|
+
const withoutTrailingSlashes = withoutWildcards.replace(/\/+$/g, '');
|
|
170
|
+
return withoutTrailingSlashes || '/';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Builds developer username from developer ID
|
|
175
|
+
* @param {string|number} devId - Developer ID
|
|
176
|
+
* @returns {string} Developer username (dev, dev01, dev02, ...)
|
|
177
|
+
*/
|
|
178
|
+
function buildDevUsername(devId) {
|
|
179
|
+
if (devId === undefined || devId === null) {
|
|
180
|
+
return 'dev';
|
|
181
|
+
}
|
|
182
|
+
const devIdString = String(devId);
|
|
183
|
+
if (devIdString === '0') {
|
|
184
|
+
return 'dev';
|
|
185
|
+
}
|
|
186
|
+
const paddedId = devIdString.length === 1 ? devIdString.padStart(2, '0') : devIdString;
|
|
187
|
+
return `dev${paddedId}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Builds Traefik ingress configuration from frontDoorRouting
|
|
192
|
+
* Resolves ${DEV_USERNAME} variable interpolation in host field
|
|
193
|
+
* @param {Object} config - Application configuration
|
|
194
|
+
* @param {string|number} devId - Developer ID
|
|
195
|
+
* @returns {Object} Traefik configuration object
|
|
196
|
+
*/
|
|
197
|
+
function buildTraefikConfig(config, devId) {
|
|
198
|
+
const frontDoor = config.frontDoorRouting;
|
|
199
|
+
if (!frontDoor || frontDoor.enabled !== true) {
|
|
200
|
+
return { enabled: false };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!frontDoor.host || typeof frontDoor.host !== 'string') {
|
|
204
|
+
throw new Error('frontDoorRouting.host is required when frontDoorRouting.enabled is true');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const devUsername = buildDevUsername(devId);
|
|
208
|
+
const host = frontDoor.host.replace(/\$\{DEV_USERNAME\}/g, devUsername);
|
|
209
|
+
const path = derivePathFromPattern(frontDoor.pattern);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
enabled: true,
|
|
213
|
+
host,
|
|
214
|
+
path,
|
|
215
|
+
tls: frontDoor.tls !== false,
|
|
216
|
+
certStore: frontDoor.certStore || null
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
153
220
|
/**
|
|
154
221
|
* Builds requires configuration section
|
|
155
222
|
* @param {Object} config - Application configuration
|
|
@@ -169,17 +236,12 @@ function buildRequiresConfig(config) {
|
|
|
169
236
|
* @param {string} appName - Application name
|
|
170
237
|
* @param {Object} config - Application configuration
|
|
171
238
|
* @param {number} port - Application port
|
|
239
|
+
* @param {string|number} devId - Developer ID
|
|
172
240
|
* @returns {Object} Service configuration
|
|
173
241
|
*/
|
|
174
|
-
function buildServiceConfig(appName, config, port) {
|
|
175
|
-
|
|
176
|
-
// Container port should remain unchanged regardless of developer ID
|
|
177
|
-
const containerPortValue = config.build?.containerPort || config.port || 3000;
|
|
178
|
-
|
|
179
|
-
// Host port: use port parameter (already calculated from CLI --port or config.port in generateDockerCompose)
|
|
180
|
-
// Note: build.localPort is ONLY used for .env file PORT variable (for local PC dev), NOT for Docker Compose
|
|
242
|
+
function buildServiceConfig(appName, config, port, devId) {
|
|
243
|
+
const containerPortValue = getContainerPort(config, 3000);
|
|
181
244
|
const hostPort = port;
|
|
182
|
-
|
|
183
245
|
return {
|
|
184
246
|
app: buildAppConfig(appName, config),
|
|
185
247
|
image: buildImageConfig(config, appName),
|
|
@@ -190,6 +252,7 @@ function buildServiceConfig(appName, config, port) {
|
|
|
190
252
|
localPort: config.build?.localPort || null // Only used for .env file PORT variable, not for Docker Compose
|
|
191
253
|
},
|
|
192
254
|
healthCheck: buildHealthCheckConfig(config),
|
|
255
|
+
traefik: buildTraefikConfig(config, devId),
|
|
193
256
|
...buildRequiresConfig(config)
|
|
194
257
|
};
|
|
195
258
|
}
|
|
@@ -200,11 +263,7 @@ function buildServiceConfig(appName, config, port) {
|
|
|
200
263
|
* @returns {Object} Volumes configuration
|
|
201
264
|
*/
|
|
202
265
|
function buildVolumesConfig(appName) {
|
|
203
|
-
|
|
204
|
-
const volumePath = path.join(process.cwd(), 'data', appName);
|
|
205
|
-
return {
|
|
206
|
-
mountVolume: volumePath.replace(/\\/g, '/')
|
|
207
|
-
};
|
|
266
|
+
return { mountVolume: path.join(process.cwd(), 'data', appName).replace(/\\/g, '/') };
|
|
208
267
|
}
|
|
209
268
|
|
|
210
269
|
/**
|
|
@@ -213,23 +272,9 @@ function buildVolumesConfig(appName) {
|
|
|
213
272
|
* @returns {Object} Networks configuration
|
|
214
273
|
*/
|
|
215
274
|
function buildNetworksConfig(config) {
|
|
216
|
-
|
|
217
|
-
const databases = config.requires?.databases || config.databases || [];
|
|
218
|
-
return {
|
|
219
|
-
databases: databases
|
|
220
|
-
};
|
|
275
|
+
return { databases: config.requires?.databases || config.databases || [] };
|
|
221
276
|
}
|
|
222
277
|
|
|
223
|
-
/**
|
|
224
|
-
* Reads database passwords from .env file
|
|
225
|
-
* Requires DB_0_PASSWORD, DB_1_PASSWORD, etc. to be set in .env file
|
|
226
|
-
* @async
|
|
227
|
-
* @param {string} envPath - Path to .env file
|
|
228
|
-
* @param {Array<Object>} databases - Array of database configurations
|
|
229
|
-
* @param {string} appKey - Application key (fallback for single database)
|
|
230
|
-
* @returns {Promise<Object>} Object with passwords array and lookup map
|
|
231
|
-
* @throws {Error} If required password variables are missing
|
|
232
|
-
*/
|
|
233
278
|
/**
|
|
234
279
|
* Reads and parses .env file
|
|
235
280
|
* @async
|
|
@@ -276,17 +321,21 @@ async function readEnvFile(envPath) {
|
|
|
276
321
|
* @function extractPassword
|
|
277
322
|
* @param {Object} envVars - Environment variables
|
|
278
323
|
* @param {string} passwordKey - Password key to look up
|
|
324
|
+
* @param {Object} [context] - Optional: { appKey, multi } for clearer error messages
|
|
279
325
|
* @returns {string} Password value
|
|
280
326
|
* @throws {Error} If password is missing or empty
|
|
281
327
|
*/
|
|
282
|
-
function extractPassword(envVars, passwordKey) {
|
|
328
|
+
function extractPassword(envVars, passwordKey, context = {}) {
|
|
329
|
+
const { appKey, multi } = context;
|
|
330
|
+
const appSuffix = appKey ? ` for application '${appKey}'` : '';
|
|
331
|
+
|
|
283
332
|
if (!(passwordKey in envVars)) {
|
|
284
|
-
throw new Error(
|
|
333
|
+
throw new Error(multi && appKey ? formatMissingDbPasswordError(appKey, { multiDb: true, passwordKey }) : 'Missing required password variable ' + passwordKey + ' in .env file' + appSuffix + '. Add ' + passwordKey + '=your_secret to your .env file.');
|
|
285
334
|
}
|
|
286
335
|
|
|
287
336
|
const password = envVars[passwordKey].trim();
|
|
288
337
|
if (!password || password.length === 0) {
|
|
289
|
-
throw new Error(
|
|
338
|
+
throw new Error('Password variable ' + passwordKey + ' is empty in .env file' + appSuffix + '. Set a non-empty value.');
|
|
290
339
|
}
|
|
291
340
|
|
|
292
341
|
return password;
|
|
@@ -303,17 +352,14 @@ function extractPassword(envVars, passwordKey) {
|
|
|
303
352
|
function processMultipleDatabases(databases, envVars, appKey) {
|
|
304
353
|
const passwords = {};
|
|
305
354
|
const passwordsArray = [];
|
|
306
|
-
|
|
307
355
|
for (let i = 0; i < databases.length; i++) {
|
|
308
356
|
const db = databases[i];
|
|
309
357
|
const dbName = db.name || appKey;
|
|
310
358
|
const passwordKey = `DB_${i}_PASSWORD`;
|
|
311
|
-
const password = extractPassword(envVars, passwordKey);
|
|
312
|
-
|
|
359
|
+
const password = extractPassword(envVars, passwordKey, { appKey, multi: true });
|
|
313
360
|
passwords[dbName] = password;
|
|
314
361
|
passwordsArray.push(password);
|
|
315
362
|
}
|
|
316
|
-
|
|
317
363
|
return { passwords, passwordsArray };
|
|
318
364
|
}
|
|
319
365
|
|
|
@@ -327,47 +373,36 @@ function processMultipleDatabases(databases, envVars, appKey) {
|
|
|
327
373
|
function processSingleDatabase(envVars, appKey) {
|
|
328
374
|
const passwords = {};
|
|
329
375
|
const passwordsArray = [];
|
|
330
|
-
|
|
331
|
-
// Single database case - use DB_0_PASSWORD or DB_PASSWORD
|
|
332
376
|
const passwordKey = ('DB_0_PASSWORD' in envVars) ? 'DB_0_PASSWORD' : 'DB_PASSWORD';
|
|
333
|
-
|
|
334
377
|
if (!(passwordKey in envVars)) {
|
|
335
|
-
throw new Error(
|
|
378
|
+
throw new Error(formatMissingDbPasswordError(appKey));
|
|
336
379
|
}
|
|
337
|
-
|
|
338
|
-
const password = extractPassword(envVars, passwordKey);
|
|
380
|
+
const password = extractPassword(envVars, passwordKey, { appKey });
|
|
339
381
|
passwords[appKey] = password;
|
|
340
382
|
passwordsArray.push(password);
|
|
341
|
-
|
|
342
383
|
return { passwords, passwordsArray };
|
|
343
384
|
}
|
|
344
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Reads database passwords from .env file
|
|
388
|
+
* @async
|
|
389
|
+
* @function readDatabasePasswords
|
|
390
|
+
* @param {string} envPath - Path to .env file
|
|
391
|
+
* @param {Array<Object>} databases - Array of database configurations
|
|
392
|
+
* @param {string} appKey - Application key (fallback for single database)
|
|
393
|
+
* @returns {Promise<Object>} Object with passwords map and array
|
|
394
|
+
* @throws {Error} If required password variables are missing
|
|
395
|
+
*/
|
|
345
396
|
async function readDatabasePasswords(envPath, databases, appKey) {
|
|
346
397
|
const envVars = await readEnvFile(envPath);
|
|
347
|
-
|
|
348
|
-
// Process each database
|
|
349
398
|
if (databases && databases.length > 0) {
|
|
350
399
|
const { passwords, passwordsArray } = processMultipleDatabases(databases, envVars, appKey);
|
|
351
|
-
return {
|
|
352
|
-
map: passwords,
|
|
353
|
-
array: passwordsArray
|
|
354
|
-
};
|
|
400
|
+
return { map: passwords, array: passwordsArray };
|
|
355
401
|
}
|
|
356
|
-
|
|
357
402
|
const { passwords, passwordsArray } = processSingleDatabase(envVars, appKey);
|
|
358
|
-
return {
|
|
359
|
-
map: passwords,
|
|
360
|
-
array: passwordsArray
|
|
361
|
-
};
|
|
403
|
+
return { map: passwords, array: passwordsArray };
|
|
362
404
|
}
|
|
363
405
|
|
|
364
|
-
/**
|
|
365
|
-
* Generates Docker Compose configuration from template
|
|
366
|
-
* @param {string} appName - Application name
|
|
367
|
-
* @param {Object} appConfig - Application configuration
|
|
368
|
-
* @param {Object} options - Run options
|
|
369
|
-
* @returns {Promise<string>} Generated compose content
|
|
370
|
-
*/
|
|
371
406
|
/**
|
|
372
407
|
* Gets developer ID and calculates numeric ID
|
|
373
408
|
* @async
|
|
@@ -376,8 +411,7 @@ async function readDatabasePasswords(envPath, databases, appKey) {
|
|
|
376
411
|
*/
|
|
377
412
|
async function getDeveloperIdAndNumeric() {
|
|
378
413
|
const devId = await config.getDeveloperId();
|
|
379
|
-
|
|
380
|
-
return { devId, idNum };
|
|
414
|
+
return { devId, idNum: typeof devId === 'string' ? parseInt(devId, 10) : devId };
|
|
381
415
|
}
|
|
382
416
|
|
|
383
417
|
/**
|
|
@@ -411,15 +445,22 @@ async function readDatabasePasswordsIfNeeded(requiresDatabase, databases, envFil
|
|
|
411
445
|
return { map: {}, array: [] };
|
|
412
446
|
}
|
|
413
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Generates Docker Compose configuration from template
|
|
450
|
+
* @async
|
|
451
|
+
* @function generateDockerCompose
|
|
452
|
+
* @param {string} appName - Application name
|
|
453
|
+
* @param {Object} appConfig - Application configuration
|
|
454
|
+
* @param {Object} options - Run options
|
|
455
|
+
* @returns {Promise<string>} Generated compose content
|
|
456
|
+
*/
|
|
414
457
|
async function generateDockerCompose(appName, appConfig, options) {
|
|
415
458
|
const language = appConfig.build?.language || appConfig.language || 'typescript';
|
|
416
459
|
const template = loadDockerComposeTemplate(language);
|
|
417
460
|
const port = options.port || appConfig.port || 3000;
|
|
418
|
-
|
|
419
461
|
const { devId, idNum } = await getDeveloperIdAndNumeric();
|
|
420
462
|
const { networkName, containerName } = buildNetworkAndContainerNames(appName, devId, idNum);
|
|
421
|
-
|
|
422
|
-
const serviceConfig = buildServiceConfig(appName, appConfig, port);
|
|
463
|
+
const serviceConfig = buildServiceConfig(appName, appConfig, port, devId);
|
|
423
464
|
const volumesConfig = buildVolumesConfig(appName);
|
|
424
465
|
const networksConfig = buildNetworksConfig(appConfig);
|
|
425
466
|
|
|
@@ -439,17 +480,19 @@ async function generateDockerCompose(appName, appConfig, options) {
|
|
|
439
480
|
...volumesConfig,
|
|
440
481
|
...networksConfig,
|
|
441
482
|
envFile: envFileAbsolutePath,
|
|
442
|
-
databasePasswords
|
|
483
|
+
databasePasswords,
|
|
443
484
|
devId: idNum,
|
|
444
|
-
networkName
|
|
445
|
-
containerName
|
|
485
|
+
networkName,
|
|
486
|
+
containerName
|
|
446
487
|
};
|
|
447
|
-
|
|
448
488
|
return template(templateData);
|
|
449
489
|
}
|
|
450
490
|
|
|
451
491
|
module.exports = {
|
|
452
492
|
generateDockerCompose,
|
|
453
|
-
getImageName
|
|
493
|
+
getImageName,
|
|
494
|
+
derivePathFromPattern,
|
|
495
|
+
buildTraefikConfig,
|
|
496
|
+
buildDevUsername
|
|
454
497
|
};
|
|
455
498
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const { getDeveloperIdNumber } = require('./env-map');
|
|
13
13
|
const devConfig = require('./dev-config');
|
|
14
|
+
const config = require('../core/config');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Calculate default controller URL based on developer ID
|
|
@@ -28,33 +29,78 @@ async function getDefaultControllerUrl() {
|
|
|
28
29
|
return `http://localhost:${ports.app}`;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Normalize controller URL (remove trailing slashes)
|
|
34
|
+
* @param {string} url - Controller URL to normalize
|
|
35
|
+
* @returns {string} Normalized controller URL
|
|
36
|
+
*/
|
|
37
|
+
function normalizeUrl(url) {
|
|
38
|
+
if (!url || typeof url !== 'string') {
|
|
39
|
+
return url;
|
|
40
|
+
}
|
|
41
|
+
return url.trim().replace(/\/+$/, '');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get controller URL from logged-in user's device tokens
|
|
46
|
+
* Returns the first available controller URL from device tokens stored in config
|
|
47
|
+
* @async
|
|
48
|
+
* @function getControllerUrlFromLoggedInUser
|
|
49
|
+
* @returns {Promise<string|null>} Controller URL from logged-in user, or null if not found
|
|
50
|
+
*/
|
|
51
|
+
async function getControllerUrlFromLoggedInUser() {
|
|
52
|
+
try {
|
|
53
|
+
const userConfig = await config.getConfig();
|
|
54
|
+
if (!userConfig.device || typeof userConfig.device !== 'object') {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const deviceUrls = Object.keys(userConfig.device);
|
|
59
|
+
if (deviceUrls.length === 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Return the first available controller URL (normalized)
|
|
64
|
+
const firstControllerUrl = deviceUrls[0];
|
|
65
|
+
return normalizeUrl(firstControllerUrl);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// If config doesn't exist or can't be read, return null
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get controller URL from config.yaml
|
|
74
|
+
* @async
|
|
75
|
+
* @function getControllerFromConfig
|
|
76
|
+
* @returns {Promise<string|null>} Controller URL from config or null
|
|
77
|
+
*/
|
|
78
|
+
async function getControllerFromConfig() {
|
|
79
|
+
const { getControllerUrl } = require('../core/config');
|
|
80
|
+
return await getControllerUrl();
|
|
81
|
+
}
|
|
82
|
+
|
|
31
83
|
/**
|
|
32
84
|
* Resolve controller URL with fallback chain
|
|
33
85
|
* Priority:
|
|
34
|
-
* 1.
|
|
35
|
-
* 2.
|
|
86
|
+
* 1. config.controller (from config.yaml)
|
|
87
|
+
* 2. getControllerUrlFromLoggedInUser() (from logged-in device tokens)
|
|
36
88
|
* 3. getDefaultControllerUrl() (developer ID-based default)
|
|
37
89
|
* @async
|
|
38
90
|
* @function resolveControllerUrl
|
|
39
|
-
* @param {Object} options - Command options
|
|
40
|
-
* @param {string} [options.controller] - Explicit controller URL option
|
|
41
|
-
* @param {Object} config - Configuration object
|
|
42
|
-
* @param {Object} [config.deployment] - Deployment configuration
|
|
43
|
-
* @param {string} [config.deployment.controllerUrl] - Controller URL from config
|
|
44
91
|
* @returns {Promise<string>} Resolved controller URL
|
|
45
92
|
*/
|
|
46
|
-
async function resolveControllerUrl(
|
|
47
|
-
// Priority 1:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return explicitUrl.replace(/\/$/, '');
|
|
52
|
-
}
|
|
93
|
+
async function resolveControllerUrl() {
|
|
94
|
+
// Priority 1: config.controller (from config.yaml)
|
|
95
|
+
const configController = await getControllerFromConfig();
|
|
96
|
+
if (configController) {
|
|
97
|
+
return configController.replace(/\/+$/, '');
|
|
53
98
|
}
|
|
54
99
|
|
|
55
|
-
// Priority 2:
|
|
56
|
-
|
|
57
|
-
|
|
100
|
+
// Priority 2: Logged-in user's device tokens
|
|
101
|
+
const loggedInControllerUrl = await getControllerUrlFromLoggedInUser();
|
|
102
|
+
if (loggedInControllerUrl) {
|
|
103
|
+
return loggedInControllerUrl;
|
|
58
104
|
}
|
|
59
105
|
|
|
60
106
|
// Priority 3: Developer ID-based default
|
|
@@ -63,5 +109,7 @@ async function resolveControllerUrl(options, config) {
|
|
|
63
109
|
|
|
64
110
|
module.exports = {
|
|
65
111
|
getDefaultControllerUrl,
|
|
112
|
+
getControllerUrlFromLoggedInUser,
|
|
113
|
+
getControllerFromConfig,
|
|
66
114
|
resolveControllerUrl
|
|
67
115
|
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dataplane Health Check Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for checking dataplane URL health and reachability
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Dataplane health check utilities
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tests a single endpoint for reachability
|
|
13
|
+
* @async
|
|
14
|
+
* @function testEndpoint
|
|
15
|
+
* @param {string} testUrl - URL to test
|
|
16
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
17
|
+
* @returns {Promise<boolean>} True if endpoint is reachable
|
|
18
|
+
*/
|
|
19
|
+
async function testEndpoint(testUrl, timeoutMs) {
|
|
20
|
+
try {
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const abortTimeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
23
|
+
let raceTimeoutId;
|
|
24
|
+
|
|
25
|
+
const response = await Promise.race([
|
|
26
|
+
fetch(testUrl, {
|
|
27
|
+
method: 'GET',
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
headers: { 'Accept': 'application/json' }
|
|
30
|
+
}),
|
|
31
|
+
new Promise((_, reject) => {
|
|
32
|
+
raceTimeoutId = setTimeout(() => reject(new Error('Timeout')), timeoutMs);
|
|
33
|
+
})
|
|
34
|
+
]).catch(() => null);
|
|
35
|
+
|
|
36
|
+
clearTimeout(abortTimeoutId);
|
|
37
|
+
if (raceTimeoutId) {
|
|
38
|
+
clearTimeout(raceTimeoutId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// If we get any response (even 404 or 401), the service is reachable
|
|
42
|
+
// 401 is OK because it means the API is working, just needs auth
|
|
43
|
+
if (response && (response.ok || response.status === 404 || response.status === 401)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If we get a 500 or other server error, the service is up but broken
|
|
48
|
+
// Still consider it "reachable" - the wizard will handle the actual error
|
|
49
|
+
if (response && response.status >= 500) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If response is null (timeout/error), endpoint is not reachable
|
|
54
|
+
return false;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// Timeout or abort means endpoint is not reachable
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Checks if dataplane URL is reachable and API is functional
|
|
63
|
+
* @async
|
|
64
|
+
* @function checkDataplaneHealth
|
|
65
|
+
* @param {string} dataplaneUrl - Dataplane URL to check
|
|
66
|
+
* @param {number} timeoutMs - Timeout in milliseconds (default: 5000)
|
|
67
|
+
* @returns {Promise<boolean>} True if dataplane is reachable and functional
|
|
68
|
+
*/
|
|
69
|
+
async function checkDataplaneHealth(dataplaneUrl, timeoutMs = 5000) {
|
|
70
|
+
if (!dataplaneUrl) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const baseUrl = dataplaneUrl.replace(/\/+$/, '');
|
|
75
|
+
const endpointsToTest = ['/health', '/api/v1/health', '/api/health', ''];
|
|
76
|
+
|
|
77
|
+
for (const endpoint of endpointsToTest) {
|
|
78
|
+
const testUrl = endpoint ? `${baseUrl}${endpoint}` : baseUrl;
|
|
79
|
+
const isReachable = await testEndpoint(testUrl, timeoutMs);
|
|
80
|
+
if (isReachable) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validates dataplane health before running wizard
|
|
90
|
+
* @async
|
|
91
|
+
* @function validateDataplaneHealth
|
|
92
|
+
* @param {string} dataplaneUrl - Dataplane URL to check
|
|
93
|
+
* @returns {Promise<Error|null>} Error if unhealthy, null if healthy
|
|
94
|
+
*/
|
|
95
|
+
async function validateDataplaneHealth(dataplaneUrl) {
|
|
96
|
+
try {
|
|
97
|
+
const isHealthy = await checkDataplaneHealth(dataplaneUrl, 5000);
|
|
98
|
+
if (!isHealthy) {
|
|
99
|
+
return new Error(
|
|
100
|
+
`Dataplane is not reachable at ${dataplaneUrl}.\n\n` +
|
|
101
|
+
'Please ensure the dataplane service is running and accessible, then try again.\n' +
|
|
102
|
+
`You can check dataplane status with: curl ${dataplaneUrl}/health`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return new Error(`Failed to check dataplane health: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
checkDataplaneHealth,
|
|
113
|
+
validateDataplaneHealth,
|
|
114
|
+
testEndpoint
|
|
115
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dataplane URL Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves dataplane URL by discovering from controller
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Dataplane URL resolution utilities
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { discoverDataplaneUrl } = require('../commands/wizard-dataplane');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve dataplane URL by discovering from controller
|
|
15
|
+
* @async
|
|
16
|
+
* @function resolveDataplaneUrl
|
|
17
|
+
* @param {string} controllerUrl - Controller URL
|
|
18
|
+
* @param {string} environment - Environment key
|
|
19
|
+
* @param {Object} authConfig - Authentication configuration
|
|
20
|
+
* @returns {Promise<string>} Resolved dataplane URL
|
|
21
|
+
* @throws {Error} If dataplane URL cannot be resolved
|
|
22
|
+
*/
|
|
23
|
+
async function resolveDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
24
|
+
return await discoverDataplaneUrl(controllerUrl, environment, authConfig);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
resolveDataplaneUrl
|
|
29
|
+
};
|
package/lib/utils/dev-config.js
CHANGED
|
@@ -18,7 +18,9 @@ const BASE_PORTS = {
|
|
|
18
18
|
postgres: 5432,
|
|
19
19
|
redis: 6379,
|
|
20
20
|
pgadmin: 5050,
|
|
21
|
-
redisCommander: 8081
|
|
21
|
+
redisCommander: 8081,
|
|
22
|
+
traefikHttp: 80,
|
|
23
|
+
traefikHttps: 443
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -65,7 +67,9 @@ function getDevPorts(developerId) {
|
|
|
65
67
|
postgres: BASE_PORTS.postgres + offset,
|
|
66
68
|
redis: BASE_PORTS.redis + offset,
|
|
67
69
|
pgadmin: BASE_PORTS.pgadmin + offset,
|
|
68
|
-
redisCommander: BASE_PORTS.redisCommander + offset
|
|
70
|
+
redisCommander: BASE_PORTS.redisCommander + offset,
|
|
71
|
+
traefikHttp: BASE_PORTS.traefikHttp + offset,
|
|
72
|
+
traefikHttps: BASE_PORTS.traefikHttps + offset
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
75
|
|
package/lib/utils/env-copy.js
CHANGED
|
@@ -18,6 +18,7 @@ const devConfig = require('../utils/dev-config');
|
|
|
18
18
|
const { rewriteInfraEndpoints } = require('./env-endpoints');
|
|
19
19
|
const { buildEnvVarMap } = require('./env-map');
|
|
20
20
|
const { interpolateEnvVars } = require('./secrets-helpers');
|
|
21
|
+
const { getLocalPort } = require('./port-resolver');
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Read developer ID from config file synchronously
|
|
@@ -157,7 +158,7 @@ function extractEnvVarsFromContent(envContent, envVars) {
|
|
|
157
158
|
* @returns {Promise<string>} Patched env content
|
|
158
159
|
*/
|
|
159
160
|
async function patchEnvContentForLocal(envContent, variables) {
|
|
160
|
-
const baseAppPort = variables
|
|
161
|
+
const baseAppPort = getLocalPort(variables, 3000);
|
|
161
162
|
const appPort = calculateDevAppPort(baseAppPort);
|
|
162
163
|
const devIdNum = readDeveloperIdFromConfig(config) || 0;
|
|
163
164
|
const infraPorts = devConfig.getDevPorts(devIdNum);
|
package/lib/utils/env-ports.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const yaml = require('js-yaml');
|
|
13
13
|
const config = require('../core/config');
|
|
14
|
+
const { getLocalPort } = require('./port-resolver');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Update PORT in the container's .env file to use variables.port (+offset)
|
|
@@ -66,7 +67,7 @@ function updateContainerPortInEnvFile(envPath, variablesPath) {
|
|
|
66
67
|
}
|
|
67
68
|
const variablesContent = fs.readFileSync(variablesPath, 'utf8');
|
|
68
69
|
const variables = yaml.load(variablesContent);
|
|
69
|
-
const basePort = variables
|
|
70
|
+
const basePort = getLocalPort(variables, 3000);
|
|
70
71
|
const devIdNum = getDeveloperIdFromEnvOrConfig();
|
|
71
72
|
const port = calculatePortWithDevId(basePort, devIdNum);
|
|
72
73
|
let envContent = fs.readFileSync(envPath, 'utf8');
|
|
@@ -20,7 +20,7 @@ const logger = require('../utils/logger');
|
|
|
20
20
|
* @param {string} appKey - Application key
|
|
21
21
|
* @param {string} clientIdKey - Secret key for client ID (e.g., 'myapp-client-idKeyVault')
|
|
22
22
|
* @param {string} clientSecretKey - Secret key for client secret (e.g., 'myapp-client-secretKeyVault')
|
|
23
|
-
* @param {string} _controllerUrl - Controller URL (e.g., 'http://localhost:3010' or 'https://controller.aifabrix.
|
|
23
|
+
* @param {string} _controllerUrl - Controller URL (e.g., 'http://localhost:3010' or 'https://controller.aifabrix.dev')
|
|
24
24
|
* Note: This parameter is accepted for compatibility but the template format http://${MISO_HOST}:${MISO_PORT} is used instead
|
|
25
25
|
* @returns {Promise<void>} Resolves when template is updated
|
|
26
26
|
*/
|