@aifabrix/builder 2.32.3 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +8 -7
- 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/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 +29 -21
- package/lib/datasource/list.js +8 -6
- 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 +32 -50
- 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/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/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- package/templates/typescript/docker-compose.hbs +26 -0
package/lib/app/deploy.js
CHANGED
|
@@ -18,6 +18,8 @@ const logger = require('../utils/logger');
|
|
|
18
18
|
const config = require('../core/config');
|
|
19
19
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
20
20
|
const { detectAppType } = require('../utils/paths');
|
|
21
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
22
|
+
const { checkApplicationExists } = require('../utils/app-existence');
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Validate application name format
|
|
@@ -178,15 +180,26 @@ async function loadVariablesFile(variablesPath) {
|
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
/**
|
|
181
|
-
* Extracts deployment configuration from
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* @
|
|
183
|
+
* Extracts deployment configuration from config.yaml
|
|
184
|
+
* Resolves controller URL using fallback chain: config.controller → logged-in user → developer ID default
|
|
185
|
+
* Resolves environment using fallback chain: config.environment → default 'dev'
|
|
186
|
+
* @async
|
|
187
|
+
* @param {Object} options - CLI options (for poll settings only)
|
|
188
|
+
* @param {Object} _variables - Variables from variables.yaml (unused, kept for compatibility)
|
|
189
|
+
* @returns {Promise<Object>} Extracted configuration with resolved controller URL
|
|
185
190
|
*/
|
|
186
|
-
function extractDeploymentConfig(options,
|
|
191
|
+
async function extractDeploymentConfig(options, _variables) {
|
|
192
|
+
const { resolveEnvironment } = require('../core/config');
|
|
193
|
+
|
|
194
|
+
// Resolve controller URL from config.yaml (no flags, no options)
|
|
195
|
+
const controllerUrl = await resolveControllerUrl();
|
|
196
|
+
|
|
197
|
+
// Resolve environment from config.yaml (no flags, no options)
|
|
198
|
+
const envKey = await resolveEnvironment();
|
|
199
|
+
|
|
187
200
|
return {
|
|
188
|
-
controllerUrl
|
|
189
|
-
envKey
|
|
201
|
+
controllerUrl,
|
|
202
|
+
envKey,
|
|
190
203
|
poll: options.poll !== false,
|
|
191
204
|
pollInterval: options.pollInterval || 5000,
|
|
192
205
|
pollMaxAttempts: options.pollMaxAttempts || 60
|
|
@@ -200,7 +213,7 @@ function extractDeploymentConfig(options, variables) {
|
|
|
200
213
|
*/
|
|
201
214
|
function validateDeploymentConfig(deploymentConfig) {
|
|
202
215
|
if (!deploymentConfig.controllerUrl) {
|
|
203
|
-
throw new Error('Controller URL is required.
|
|
216
|
+
throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
|
|
204
217
|
}
|
|
205
218
|
if (!deploymentConfig.auth) {
|
|
206
219
|
throw new Error('Authentication is required. Run "aifabrix login" first or ensure credentials are in secrets.local.yaml');
|
|
@@ -208,19 +221,15 @@ function validateDeploymentConfig(deploymentConfig) {
|
|
|
208
221
|
}
|
|
209
222
|
|
|
210
223
|
/**
|
|
211
|
-
* Configure deployment environment settings
|
|
224
|
+
* Configure deployment environment settings from config.yaml
|
|
212
225
|
* @async
|
|
213
|
-
* @param {Object}
|
|
226
|
+
* @param {Object} _options - CLI options (unused, kept for compatibility)
|
|
214
227
|
* @param {Object} deploymentConfig - Deployment configuration to update
|
|
215
228
|
* @returns {Promise<void>}
|
|
216
229
|
*/
|
|
217
|
-
async function configureDeploymentEnvironment(
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
await config.setCurrentEnvironment(options.environment);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Get current environment from root-level config
|
|
230
|
+
async function configureDeploymentEnvironment(_options, deploymentConfig) {
|
|
231
|
+
// Get current environment from root-level config (already resolved in extractDeploymentConfig)
|
|
232
|
+
// This function is kept for compatibility but no longer updates environment from options
|
|
224
233
|
const currentEnvironment = await config.getCurrentEnvironment();
|
|
225
234
|
deploymentConfig.envKey = deploymentConfig.envKey || currentEnvironment;
|
|
226
235
|
}
|
|
@@ -234,9 +243,9 @@ async function configureDeploymentEnvironment(options, deploymentConfig) {
|
|
|
234
243
|
* @throws {Error} If authentication fails
|
|
235
244
|
*/
|
|
236
245
|
async function refreshDeploymentToken(appName, deploymentConfig) {
|
|
237
|
-
// Get controller URL
|
|
246
|
+
// Get controller URL (should already be resolved by extractDeploymentConfig)
|
|
238
247
|
if (!deploymentConfig.controllerUrl) {
|
|
239
|
-
throw new Error('Controller URL is required.
|
|
248
|
+
throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
|
|
240
249
|
}
|
|
241
250
|
|
|
242
251
|
// Get deployment authentication (device token → client token → credentials)
|
|
@@ -275,7 +284,7 @@ async function loadDeploymentConfig(appName, options) {
|
|
|
275
284
|
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
276
285
|
const variables = await loadVariablesFile(variablesPath);
|
|
277
286
|
|
|
278
|
-
const deploymentConfig = extractDeploymentConfig(options, variables);
|
|
287
|
+
const deploymentConfig = await extractDeploymentConfig(options, variables);
|
|
279
288
|
|
|
280
289
|
await configureDeploymentEnvironment(options, deploymentConfig);
|
|
281
290
|
await refreshDeploymentToken(appName, deploymentConfig);
|
|
@@ -360,6 +369,80 @@ function displayDeploymentResults(result) {
|
|
|
360
369
|
}
|
|
361
370
|
}
|
|
362
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Check if app is external and handle external deployment
|
|
374
|
+
* @async
|
|
375
|
+
* @function handleExternalDeployment
|
|
376
|
+
* @param {string} appName - Application name
|
|
377
|
+
* @param {Object} options - Deployment options
|
|
378
|
+
* @returns {Promise<Object|null>} Deployment result if external, null otherwise
|
|
379
|
+
*/
|
|
380
|
+
async function handleExternalDeployment(appName, options) {
|
|
381
|
+
const { isExternal } = await detectAppType(appName);
|
|
382
|
+
if (isExternal) {
|
|
383
|
+
const externalDeploy = require('../external-system/deploy');
|
|
384
|
+
await externalDeploy.deployExternalSystem(appName, options);
|
|
385
|
+
return { success: true, type: 'external' };
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Handle deployment errors
|
|
392
|
+
* @async
|
|
393
|
+
* @function handleDeploymentError
|
|
394
|
+
* @param {Error} error - Error that occurred
|
|
395
|
+
* @param {string} appName - Application name
|
|
396
|
+
* @param {string} controllerUrl - Controller URL (from config, or null if not yet resolved)
|
|
397
|
+
* @param {boolean} usedExternalDeploy - Whether external deployment was used
|
|
398
|
+
*/
|
|
399
|
+
async function handleDeploymentError(error, appName, controllerUrl, usedExternalDeploy) {
|
|
400
|
+
if (usedExternalDeploy) {
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
const alreadyLogged = error._logged === true;
|
|
404
|
+
const url = controllerUrl || 'unknown';
|
|
405
|
+
const deployer = require('../deployment/deployer');
|
|
406
|
+
await deployer.handleDeploymentErrors(error, appName, url, alreadyLogged);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Execute standard application deployment flow
|
|
411
|
+
* @async
|
|
412
|
+
* @function executeStandardDeployment
|
|
413
|
+
* @param {string} appName - Application name
|
|
414
|
+
* @param {Object} options - Deployment options
|
|
415
|
+
* @returns {Promise<Object>} Deployment result
|
|
416
|
+
*/
|
|
417
|
+
async function executeStandardDeployment(appName, options) {
|
|
418
|
+
const config = await loadDeploymentConfig(appName, options);
|
|
419
|
+
const controllerUrl = config.controllerUrl || 'unknown';
|
|
420
|
+
|
|
421
|
+
// Check if application exists before deployment
|
|
422
|
+
const appExists = await checkApplicationExists(appName, controllerUrl, config.envKey, config.auth);
|
|
423
|
+
|
|
424
|
+
const { manifest, manifestPath } = await generateAndValidateManifest(appName);
|
|
425
|
+
displayDeploymentInfo(manifest, manifestPath);
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const result = await executeDeployment(manifest, config);
|
|
429
|
+
displayDeploymentResults(result);
|
|
430
|
+
return { result, controllerUrl, appExists };
|
|
431
|
+
} catch (error) {
|
|
432
|
+
// Enhance error if app exists and credentials are invalid
|
|
433
|
+
if (appExists && error.status === 401 && !error.message.includes('rotate-secret')) {
|
|
434
|
+
const enhancedError = new Error(
|
|
435
|
+
`${error.message}\n\n💡 The application '${appName}' exists in environment '${config.envKey}'. ` +
|
|
436
|
+
`To fix invalid credentials, rotate the application secret:\n aifabrix app rotate-secret ${appName}`
|
|
437
|
+
);
|
|
438
|
+
enhancedError.status = 401;
|
|
439
|
+
enhancedError.formatted = error.formatted || enhancedError.message;
|
|
440
|
+
throw enhancedError;
|
|
441
|
+
}
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
363
446
|
/**
|
|
364
447
|
* Deploys application to Miso Controller
|
|
365
448
|
* Orchestrates manifest generation, key creation, and deployment
|
|
@@ -368,59 +451,38 @@ function displayDeploymentResults(result) {
|
|
|
368
451
|
* @function deployApp
|
|
369
452
|
* @param {string} appName - Name of the application to deploy
|
|
370
453
|
* @param {Object} options - Deployment options
|
|
371
|
-
* @param {string} options.controller - Controller URL (required)
|
|
372
|
-
* @param {string} [options.environment] - Target environment (miso/dev/tst/pro)
|
|
373
454
|
* @param {boolean} [options.poll] - Poll for deployment status
|
|
374
455
|
* @param {number} [options.pollInterval] - Polling interval in milliseconds
|
|
456
|
+
* @param {number} [options.pollMaxAttempts] - Max polling attempts
|
|
375
457
|
* @returns {Promise<Object>} Deployment result
|
|
376
458
|
* @throws {Error} If deployment fails
|
|
377
459
|
*
|
|
460
|
+
* Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
|
|
461
|
+
*
|
|
378
462
|
* @example
|
|
379
|
-
* await deployApp('myapp', {
|
|
463
|
+
* await deployApp('myapp', { poll: true });
|
|
380
464
|
*/
|
|
381
465
|
async function deployApp(appName, options = {}) {
|
|
382
466
|
let controllerUrl = null;
|
|
383
|
-
let
|
|
467
|
+
let usedExternalDeploy = false;
|
|
384
468
|
|
|
385
469
|
try {
|
|
386
|
-
// 1. Input validation
|
|
387
470
|
if (!appName || typeof appName !== 'string' || appName.trim().length === 0) {
|
|
388
471
|
throw new Error('App name is required');
|
|
389
472
|
}
|
|
390
|
-
|
|
391
473
|
validateAppName(appName);
|
|
392
474
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
config = await loadDeploymentConfig(appName, options);
|
|
399
|
-
controllerUrl = config.controllerUrl || options.controller || 'unknown';
|
|
400
|
-
|
|
401
|
-
// 3. Generate and validate manifest
|
|
402
|
-
const { manifest, manifestPath } = await generateAndValidateManifest(appName);
|
|
403
|
-
|
|
404
|
-
// 4. Display deployment info
|
|
405
|
-
displayDeploymentInfo(manifest, manifestPath);
|
|
406
|
-
|
|
407
|
-
// 5. Execute deployment
|
|
408
|
-
const result = await executeDeployment(manifest, config);
|
|
409
|
-
|
|
410
|
-
// 6. Display results
|
|
411
|
-
displayDeploymentResults(result);
|
|
475
|
+
const externalResult = await handleExternalDeployment(appName, options);
|
|
476
|
+
if (externalResult) {
|
|
477
|
+
return externalResult;
|
|
478
|
+
}
|
|
479
|
+
usedExternalDeploy = false;
|
|
412
480
|
|
|
481
|
+
const { result, controllerUrl: url } = await executeStandardDeployment(appName, options);
|
|
482
|
+
controllerUrl = url;
|
|
413
483
|
return result;
|
|
414
|
-
|
|
415
484
|
} catch (error) {
|
|
416
|
-
|
|
417
|
-
// Check if error was already logged (from deployer.js)
|
|
418
|
-
const alreadyLogged = error._logged === true;
|
|
419
|
-
const url = controllerUrl || options.controller || 'unknown';
|
|
420
|
-
|
|
421
|
-
const deployer = require('../deployment/deployer');
|
|
422
|
-
// handleDeploymentErrors will log, format, and throw the error
|
|
423
|
-
await deployer.handleDeploymentErrors(error, appName, url, alreadyLogged);
|
|
485
|
+
await handleDeploymentError(error, appName, controllerUrl, usedExternalDeploy);
|
|
424
486
|
}
|
|
425
487
|
}
|
|
426
488
|
|
package/lib/app/display.js
CHANGED
|
@@ -25,9 +25,9 @@ function displayExternalSystemSuccess(appName, config, location) {
|
|
|
25
25
|
logger.log(chalk.blue(`System Key: ${config.systemKey || appName}`));
|
|
26
26
|
logger.log(chalk.green('\nNext steps:'));
|
|
27
27
|
logger.log(chalk.white('1. Edit external system JSON files in ' + location));
|
|
28
|
-
logger.log(chalk.white('2. Run: aifabrix
|
|
29
|
-
logger.log(chalk.white('3. Run: aifabrix
|
|
30
|
-
logger.log(chalk.white('4. Run: aifabrix deploy ' + appName
|
|
28
|
+
logger.log(chalk.white('2. Run: aifabrix validate ' + appName + ' --type external'));
|
|
29
|
+
logger.log(chalk.white('3. Run: aifabrix login'));
|
|
30
|
+
logger.log(chalk.white('4. Run: aifabrix deploy ' + appName));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -50,8 +50,9 @@ function displayWebappSuccess(appName, config, envConversionMessage) {
|
|
|
50
50
|
|
|
51
51
|
logger.log(chalk.green('\nNext steps:'));
|
|
52
52
|
logger.log(chalk.white('1. Copy env.template to .env and fill in your values'));
|
|
53
|
-
logger.log(chalk.white('2. Run: aifabrix
|
|
54
|
-
logger.log(chalk.white('3. Run: aifabrix
|
|
53
|
+
logger.log(chalk.white('2. Run: aifabrix up'));
|
|
54
|
+
logger.log(chalk.white('3. Run: aifabrix build ' + appName));
|
|
55
|
+
logger.log(chalk.white('4. Run: aifabrix run ' + appName));
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
package/lib/app/dockerfile.js
CHANGED
|
@@ -15,6 +15,7 @@ const yaml = require('js-yaml');
|
|
|
15
15
|
const build = require('../build');
|
|
16
16
|
const { validateAppName } = require('./push');
|
|
17
17
|
const logger = require('../utils/logger');
|
|
18
|
+
const { getContainerPort } = require('../utils/port-resolver');
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Checks if Dockerfile exists and validates overwrite permission
|
|
@@ -52,7 +53,7 @@ async function loadAppConfig(configPath, options) {
|
|
|
52
53
|
const variables = yaml.load(yamlContent);
|
|
53
54
|
return {
|
|
54
55
|
language: options.language || variables.build?.language || 'typescript',
|
|
55
|
-
port: variables
|
|
56
|
+
port: getContainerPort(variables, 3000),
|
|
56
57
|
...variables
|
|
57
58
|
};
|
|
58
59
|
} catch {
|
package/lib/app/list.js
CHANGED
|
@@ -286,18 +286,25 @@ function handleListResponse(response, actualControllerUrl) {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
/**
|
|
289
|
-
* List applications in an environment
|
|
289
|
+
* List applications in an environment.
|
|
290
|
+
* Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
|
|
290
291
|
* @async
|
|
291
|
-
* @param {Object}
|
|
292
|
-
* @param {string} options.environment - Environment ID or key
|
|
293
|
-
* @param {string} [options.controller] - Controller URL (optional, uses configured controller if not provided)
|
|
292
|
+
* @param {Object} [_options] - Command options (reserved)
|
|
294
293
|
* @throws {Error} If listing fails
|
|
295
294
|
*/
|
|
296
|
-
async function listApplications(options) {
|
|
297
|
-
const
|
|
295
|
+
async function listApplications(options = {}) {
|
|
296
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
297
|
+
const { resolveEnvironment } = require('../core/config');
|
|
298
|
+
|
|
299
|
+
const controllerUrl = options.controller || (await resolveControllerUrl());
|
|
300
|
+
if (!controllerUrl) {
|
|
301
|
+
logger.error(chalk.red('❌ Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml'));
|
|
302
|
+
process.exit(1);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
298
305
|
|
|
299
|
-
|
|
300
|
-
const
|
|
306
|
+
const environment = options.environment || (await resolveEnvironment());
|
|
307
|
+
const config = await getConfig();
|
|
301
308
|
const { token, actualControllerUrl } = await getListAuthToken(controllerUrl, config);
|
|
302
309
|
|
|
303
310
|
// Check if authentication succeeded (may be null after process.exit in tests)
|
|
@@ -308,9 +315,9 @@ async function listApplications(options) {
|
|
|
308
315
|
// Use centralized API client
|
|
309
316
|
const authConfig = { type: 'bearer', token: token };
|
|
310
317
|
try {
|
|
311
|
-
const response = await listEnvironmentApplications(actualControllerUrl,
|
|
318
|
+
const response = await listEnvironmentApplications(actualControllerUrl, environment, authConfig);
|
|
312
319
|
const applications = handleListResponse(response, actualControllerUrl);
|
|
313
|
-
displayApplications(applications,
|
|
320
|
+
displayApplications(applications, environment, actualControllerUrl);
|
|
314
321
|
} catch (error) {
|
|
315
322
|
logger.error(chalk.red(`❌ Failed to list applications from controller: ${actualControllerUrl}`));
|
|
316
323
|
logger.error(chalk.gray(`Error: ${error.message}`));
|
package/lib/app/readme.js
CHANGED
|
@@ -12,6 +12,7 @@ const fs = require('fs').promises;
|
|
|
12
12
|
const fsSync = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const handlebars = require('handlebars');
|
|
15
|
+
const { generateExternalReadmeContent } = require('../utils/external-readme');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Checks if a file exists
|
|
@@ -86,6 +87,27 @@ function extractServiceFlags(config) {
|
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Builds placeholder datasources for external README generation
|
|
92
|
+
* @function buildExternalDatasourcePlaceholders
|
|
93
|
+
* @param {number} datasourceCount - Datasource count
|
|
94
|
+
* @returns {Array<Object>} Datasource placeholders
|
|
95
|
+
*/
|
|
96
|
+
function buildExternalDatasourcePlaceholders(systemKey, datasourceCount) {
|
|
97
|
+
const normalizedCount = Number.isInteger(datasourceCount)
|
|
98
|
+
? datasourceCount
|
|
99
|
+
: parseInt(datasourceCount, 10);
|
|
100
|
+
const total = Number.isFinite(normalizedCount) && normalizedCount > 0 ? normalizedCount : 0;
|
|
101
|
+
return Array.from({ length: total }, (_value, index) => {
|
|
102
|
+
const entityType = `entity${index + 1}`;
|
|
103
|
+
return {
|
|
104
|
+
entityType,
|
|
105
|
+
displayName: `Datasource ${index + 1}`,
|
|
106
|
+
fileName: `${systemKey}-datasource-${entityType}.json`
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
89
111
|
/**
|
|
90
112
|
* Builds template context for README generation
|
|
91
113
|
* @function buildReadmeContext
|
|
@@ -95,8 +117,11 @@ function extractServiceFlags(config) {
|
|
|
95
117
|
*/
|
|
96
118
|
function buildReadmeContext(appName, config) {
|
|
97
119
|
const displayName = formatAppDisplayName(appName);
|
|
98
|
-
const
|
|
99
|
-
const
|
|
120
|
+
const port = config.port ?? 3000;
|
|
121
|
+
const localPort = (typeof config.build?.localPort === 'number' && config.build.localPort > 0)
|
|
122
|
+
? config.build.localPort
|
|
123
|
+
: port;
|
|
124
|
+
const imageName = config.image?.name || `aifabrix/${appName}`;
|
|
100
125
|
// Extract registry from nested structure (config.image.registry) or flattened (config.registry)
|
|
101
126
|
const registry = config.image?.registry || config.registry || 'myacr.azurecr.io';
|
|
102
127
|
|
|
@@ -108,6 +133,7 @@ function buildReadmeContext(appName, config) {
|
|
|
108
133
|
displayName,
|
|
109
134
|
imageName,
|
|
110
135
|
port,
|
|
136
|
+
localPort,
|
|
111
137
|
registry,
|
|
112
138
|
...serviceFlags,
|
|
113
139
|
hasAnyService
|
|
@@ -115,117 +141,20 @@ function buildReadmeContext(appName, config) {
|
|
|
115
141
|
}
|
|
116
142
|
|
|
117
143
|
function generateReadmeMd(appName, config) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
function generateComprehensiveReadme(context) {
|
|
130
|
-
const { appName, displayName, imageName, port, registry, hasDatabase, hasRedis, hasStorage, hasAuthentication, hasAnyService } = context;
|
|
131
|
-
|
|
132
|
-
let prerequisites = 'Before running this application, ensure the following prerequisites are met:\n';
|
|
133
|
-
prerequisites += '- `@aifabrix/builder` installed globally\n';
|
|
134
|
-
prerequisites += '- Docker Desktop running\n';
|
|
135
|
-
|
|
136
|
-
if (hasAnyService) {
|
|
137
|
-
if (hasDatabase) {
|
|
138
|
-
prerequisites += '- PostgreSQL database\n';
|
|
139
|
-
}
|
|
140
|
-
if (hasRedis) {
|
|
141
|
-
prerequisites += '- Redis\n';
|
|
142
|
-
}
|
|
143
|
-
if (hasStorage) {
|
|
144
|
-
prerequisites += '- File storage configured\n';
|
|
145
|
-
}
|
|
146
|
-
if (hasAuthentication) {
|
|
147
|
-
prerequisites += '- Authentication/RBAC configured\n';
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
prerequisites += '- Infrastructure running\n';
|
|
144
|
+
if (config.type === 'external') {
|
|
145
|
+
const systemKey = config.systemKey || appName;
|
|
146
|
+
const datasources = buildExternalDatasourcePlaceholders(systemKey, config.datasourceCount);
|
|
147
|
+
return generateExternalReadmeContent({
|
|
148
|
+
appName,
|
|
149
|
+
systemKey,
|
|
150
|
+
systemType: config.systemType,
|
|
151
|
+
displayName: config.systemDisplayName,
|
|
152
|
+
description: config.systemDescription,
|
|
153
|
+
datasources
|
|
154
|
+
});
|
|
151
155
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (hasDatabase) {
|
|
155
|
-
troubleshooting = `### Database Connection Issues
|
|
156
|
-
|
|
157
|
-
If you encounter database connection errors, ensure:
|
|
158
|
-
- PostgreSQL is running and accessible
|
|
159
|
-
- Database credentials are correctly configured in your \`.env\` file
|
|
160
|
-
- The database name matches your configuration
|
|
161
|
-
- Verify infrastructure is running and PostgreSQL is accessible`;
|
|
162
|
-
} else {
|
|
163
|
-
troubleshooting = 'Verify infrastructure is running.';
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return `# ${displayName} Builder
|
|
167
|
-
|
|
168
|
-
Build, run, and deploy ${displayName}.
|
|
169
|
-
|
|
170
|
-
## Prerequisites
|
|
171
|
-
|
|
172
|
-
${prerequisites}
|
|
173
|
-
|
|
174
|
-
## Quick Start
|
|
175
|
-
|
|
176
|
-
### 1. Install
|
|
177
|
-
|
|
178
|
-
Install the AI Fabrix Builder CLI if you haven't already.
|
|
179
|
-
|
|
180
|
-
### 2. Configure
|
|
181
|
-
|
|
182
|
-
Configure your application settings in \`variables.yaml\`.
|
|
183
|
-
|
|
184
|
-
### 3. Build & Run Locally
|
|
185
|
-
|
|
186
|
-
Build the application:
|
|
187
|
-
\`\`\`bash
|
|
188
|
-
aifabrix build ${appName}
|
|
189
|
-
\`\`\`
|
|
190
|
-
|
|
191
|
-
Run the application:
|
|
192
|
-
\`\`\`bash
|
|
193
|
-
aifabrix run ${appName}
|
|
194
|
-
\`\`\`
|
|
195
|
-
|
|
196
|
-
The application will be available at http://localhost:${port} (default: ${port}).
|
|
197
|
-
|
|
198
|
-
### 4. Deploy to Azure
|
|
199
|
-
|
|
200
|
-
Push to registry:
|
|
201
|
-
\`\`\`bash
|
|
202
|
-
aifabrix push ${appName} --registry ${registry} --tag "v1.0.0,latest"
|
|
203
|
-
\`\`\`
|
|
204
|
-
|
|
205
|
-
## Configuration
|
|
206
|
-
|
|
207
|
-
- **Port**: ${port} (default: 3000)
|
|
208
|
-
- **Image**: ${imageName}:latest
|
|
209
|
-
- **Registry**: ${registry}
|
|
210
|
-
|
|
211
|
-
## Docker Commands
|
|
212
|
-
|
|
213
|
-
View logs:
|
|
214
|
-
\`\`\`bash
|
|
215
|
-
docker logs aifabrix-${appName} -f
|
|
216
|
-
\`\`\`
|
|
217
|
-
|
|
218
|
-
Stop the application:
|
|
219
|
-
\`\`\`bash
|
|
220
|
-
aifabrix down ${appName}
|
|
221
|
-
\`\`\`
|
|
222
|
-
|
|
223
|
-
## Troubleshooting
|
|
224
|
-
|
|
225
|
-
${troubleshooting}
|
|
226
|
-
|
|
227
|
-
For more information, see the [AI Fabrix Builder documentation](https://docs.aifabrix.com).
|
|
228
|
-
`;
|
|
156
|
+
const context = buildReadmeContext(appName, config);
|
|
157
|
+
return _loadReadmeTemplate()(context);
|
|
229
158
|
}
|
|
230
159
|
|
|
231
160
|
/**
|
package/lib/app/register.js
CHANGED
|
@@ -43,7 +43,7 @@ function buildRegistrationData(appConfig, options) {
|
|
|
43
43
|
|
|
44
44
|
// Handle external type vs non-external types differently
|
|
45
45
|
if (appConfig.appType === 'external') {
|
|
46
|
-
// For external type: include externalIntegration, exclude registryMode/port/image
|
|
46
|
+
// For external type: include externalIntegration, exclude registryMode/port/image/url
|
|
47
47
|
if (appConfig.externalIntegration) {
|
|
48
48
|
registrationData.externalIntegration = appConfig.externalIntegration;
|
|
49
49
|
}
|
|
@@ -60,6 +60,12 @@ function buildRegistrationData(appConfig, options) {
|
|
|
60
60
|
if (appConfig.image) {
|
|
61
61
|
registrationData.image = appConfig.image;
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
// URL: always set when we have port so controller DB has it. Precedence: --url, variables (app.url, deployment.dataplaneUrl, deployment.appUrl), else http://localhost:{localPort|port}
|
|
65
|
+
const portForUrl = appConfig.localPort ?? appConfig.port;
|
|
66
|
+
if (portForUrl) {
|
|
67
|
+
registrationData.url = options.url || appConfig.url || `http://localhost:${portForUrl}`;
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
return registrationData;
|
|
@@ -103,20 +109,48 @@ async function saveLocalCredentials(responseData, apiUrl) {
|
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
/**
|
|
106
|
-
*
|
|
112
|
+
* For localhost controller: apply developer-id offset to port and URL fallback so the
|
|
113
|
+
* controller can reach the app on the correct Docker/exposed host port.
|
|
114
|
+
* @async
|
|
115
|
+
* @param {Object} appConfig - App config (mutated: port, url)
|
|
116
|
+
* @param {string} apiUrl - Controller API URL
|
|
117
|
+
* @param {Object} options - CLI options (url override)
|
|
118
|
+
*/
|
|
119
|
+
async function applyLocalhostPortAdjustment(appConfig, apiUrl, options) {
|
|
120
|
+
if (!isLocalhost(apiUrl) || appConfig.port === null || appConfig.port === undefined) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const { getDeveloperId } = require('../core/config');
|
|
124
|
+
const devId = await getDeveloperId();
|
|
125
|
+
const devIdNum = (devId !== null && devId !== undefined && devId !== '') ? parseInt(devId, 10) : 0;
|
|
126
|
+
if (Number.isNaN(devIdNum) || devIdNum <= 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const adjusted = appConfig.port + devIdNum * 100;
|
|
130
|
+
appConfig.port = adjusted;
|
|
131
|
+
if (!options.url && !appConfig.url) {
|
|
132
|
+
appConfig.url = `http://localhost:${adjusted}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register an application.
|
|
138
|
+
* Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
|
|
107
139
|
* @async
|
|
108
140
|
* @param {string} appKey - Application key
|
|
109
141
|
* @param {Object} options - Registration options
|
|
110
|
-
* @param {string} options.environment - Environment ID or key
|
|
111
|
-
* @param {string} [options.controller] - Controller URL (overrides variables.yaml)
|
|
112
142
|
* @param {number} [options.port] - Application port
|
|
143
|
+
* @param {string} [options.url] - Application URL (overrides variables; see app register --help for fallback when omitted)
|
|
113
144
|
* @param {string} [options.name] - Override display name
|
|
114
145
|
* @param {string} [options.description] - Override description
|
|
115
146
|
* @throws {Error} If registration fails
|
|
116
147
|
*/
|
|
117
|
-
async function registerApplication(appKey, options) {
|
|
148
|
+
async function registerApplication(appKey, options = {}) {
|
|
118
149
|
logger.log(chalk.blue('📋 Registering application...\n'));
|
|
119
150
|
|
|
151
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
152
|
+
const { resolveEnvironment } = require('../core/config');
|
|
153
|
+
|
|
120
154
|
// Load variables.yaml
|
|
121
155
|
const { variables, created } = await loadVariablesYaml(appKey);
|
|
122
156
|
const finalVariables = created
|
|
@@ -127,10 +161,11 @@ async function registerApplication(appKey, options) {
|
|
|
127
161
|
const appConfig = await extractAppConfiguration(finalVariables, appKey, options);
|
|
128
162
|
await validateAppRegistrationData(appConfig, appKey);
|
|
129
163
|
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
164
|
+
const [controllerUrl, environmentKey] = await Promise.all([resolveControllerUrl(), resolveEnvironment()]);
|
|
165
|
+
const authConfig = await checkAuthentication(controllerUrl, environmentKey);
|
|
166
|
+
const environment = registerApplicationSchema.environmentId(environmentKey);
|
|
167
|
+
|
|
168
|
+
await applyLocalhostPortAdjustment(appConfig, authConfig.apiUrl, options);
|
|
134
169
|
|
|
135
170
|
// Register application
|
|
136
171
|
const registrationData = buildRegistrationData(appConfig, options);
|