@aifabrix/builder 2.39.3 → 2.40.2
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 +6 -6
- package/README.md +3 -3
- package/babel.config.js +6 -0
- package/integration/hubspot/README.md +53 -141
- package/integration/hubspot/application.yaml +37 -0
- package/integration/hubspot/env.template +2 -11
- package/integration/hubspot/hubspot-deploy.json +1 -0
- package/integration/hubspot/test.js +5 -5
- package/jest.config.manual.js +29 -0
- package/lib/api/credentials.api.js +5 -5
- package/lib/api/deployments.api.js +2 -2
- package/lib/api/pipeline.api.js +17 -17
- package/lib/api/wizard.api.js +2 -2
- package/lib/app/config.js +11 -6
- package/lib/app/deploy-config.js +13 -16
- package/lib/app/deploy.js +29 -22
- package/lib/app/display.js +1 -1
- package/lib/app/dockerfile.js +11 -12
- package/lib/app/helpers.js +51 -13
- package/lib/app/index.js +14 -2
- package/lib/app/prompts.js +37 -45
- package/lib/app/push.js +8 -11
- package/lib/app/readme.js +16 -12
- package/lib/app/register.js +1 -1
- package/lib/app/run-helpers.js +31 -22
- package/lib/app/run.js +44 -5
- package/lib/app/show-display.js +104 -44
- package/lib/app/show.js +123 -43
- package/lib/build/index.js +11 -18
- package/lib/cli/setup-app.js +36 -29
- package/lib/cli/setup-auth.js +19 -15
- package/lib/cli/setup-credential-deployment.js +3 -1
- package/lib/cli/setup-external-system.js +35 -16
- package/lib/cli/setup-infra.js +45 -23
- package/lib/cli/setup-utility.js +85 -31
- package/lib/commands/app-logs.js +28 -20
- package/lib/commands/app.js +30 -26
- package/lib/commands/auth-status.js +36 -3
- package/lib/commands/convert.js +202 -0
- package/lib/commands/credential-list.js +78 -17
- package/lib/commands/datasource.js +24 -24
- package/lib/commands/deployment-list.js +13 -6
- package/lib/commands/up-common.js +80 -42
- package/lib/commands/up-dataplane.js +15 -14
- package/lib/commands/up-miso.js +15 -14
- package/lib/commands/upload.js +163 -0
- package/lib/commands/wizard-core.js +5 -4
- package/lib/core/diff.js +84 -9
- package/lib/core/key-generator.js +9 -12
- package/lib/core/secrets-docker-env.js +2 -2
- package/lib/core/secrets.js +3 -2
- package/lib/core/templates.js +2 -2
- package/lib/datasource/deploy.js +2 -1
- package/lib/deployment/deployer.js +76 -48
- package/lib/external-system/delete.js +0 -1
- package/lib/external-system/deploy-helpers.js +5 -6
- package/lib/external-system/deploy.js +7 -2
- package/lib/external-system/download-helpers.js +4 -4
- package/lib/external-system/download.js +11 -10
- package/lib/external-system/generator.js +19 -17
- package/lib/external-system/test.js +10 -15
- package/lib/generator/builders.js +1 -1
- package/lib/generator/external-controller-manifest.js +26 -29
- package/lib/generator/external-schema-utils.js +6 -18
- package/lib/generator/external.js +32 -27
- package/lib/generator/github.js +1 -1
- package/lib/generator/helpers.js +12 -19
- package/lib/generator/index.js +15 -15
- package/lib/generator/parse-image.js +35 -0
- package/lib/generator/split-readme.js +105 -0
- package/lib/generator/split-variables.js +149 -0
- package/lib/generator/split.js +86 -246
- package/lib/generator/wizard.js +51 -70
- package/lib/schema/application-schema.json +4 -4
- package/lib/schema/external-datasource.schema.json +5 -0
- package/lib/schema/external-system.schema.json +10 -0
- package/lib/utils/app-config-resolver.js +52 -0
- package/lib/utils/app-register-api.js +1 -1
- package/lib/utils/app-register-auth.js +1 -1
- package/lib/utils/app-register-config.js +16 -23
- package/lib/utils/app-register-validator.js +2 -2
- package/lib/utils/cli-utils.js +47 -3
- package/lib/utils/config-format.js +154 -0
- package/lib/utils/config-paths.js +19 -52
- package/lib/utils/config-tokens.js +1 -0
- package/lib/utils/docker-build.js +71 -94
- package/lib/utils/dockerfile-utils.js +1 -1
- package/lib/utils/env-copy.js +4 -4
- package/lib/utils/env-ports.js +2 -2
- package/lib/utils/error-formatter.js +1 -1
- package/lib/utils/error-formatters/validation-errors.js +1 -1
- package/lib/utils/external-readme.js +12 -5
- package/lib/utils/external-system-test-helpers.js +2 -0
- package/lib/utils/health-check.js +55 -66
- package/lib/utils/image-version.js +12 -21
- package/lib/utils/paths.js +45 -66
- package/lib/utils/port-resolver.js +8 -8
- package/lib/utils/schema-loader.js +22 -0
- package/lib/utils/schema-resolver.js +23 -33
- package/lib/utils/secrets-helpers.js +7 -7
- package/lib/utils/secrets-utils.js +10 -12
- package/lib/utils/template-helpers.js +13 -13
- package/lib/utils/token-manager.js +20 -2
- package/lib/utils/variable-transformer.js +2 -2
- package/lib/validation/validate-display.js +3 -4
- package/lib/validation/validate.js +34 -28
- package/lib/validation/validator.js +50 -30
- package/package.json +4 -1
- package/templates/README.md +1 -1
- package/templates/applications/README.md.hbs +3 -3
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/external-system/README.md.hbs +4 -4
- package/templates/external-system/external-system.json.hbs +1 -16
- package/integration/hubspot/variables.yaml +0 -17
- /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
- /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
- /package/templates/applications/miso-controller/{variables.yaml → application.yaml} +0 -0
package/lib/app/deploy-config.js
CHANGED
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const fs = require('fs').promises;
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const yaml = require('js-yaml');
|
|
13
11
|
const config = require('../core/config');
|
|
14
12
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
15
|
-
const { detectAppType } = require('../utils/paths');
|
|
13
|
+
const { detectAppType, resolveApplicationConfigPath } = require('../utils/paths');
|
|
14
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
16
15
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -34,18 +33,17 @@ async function validateAppDirectory(builderPath, appName) {
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
|
-
* Loads
|
|
38
|
-
* @
|
|
39
|
-
* @
|
|
40
|
-
* @returns {Promise<Object>} Variables object
|
|
36
|
+
* Loads application config (application.yaml or application.json) via resolver + converter.
|
|
37
|
+
* @param {string} appPath - Path to application directory
|
|
38
|
+
* @returns {Object} Variables object
|
|
41
39
|
* @throws {Error} If file cannot be loaded
|
|
42
40
|
*/
|
|
43
|
-
|
|
41
|
+
function loadVariablesFile(appPath) {
|
|
44
42
|
try {
|
|
45
|
-
const
|
|
46
|
-
return
|
|
43
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
44
|
+
return loadConfigFile(configPath);
|
|
47
45
|
} catch (error) {
|
|
48
|
-
throw new Error(
|
|
46
|
+
throw new Error('Failed to load configuration: ' + error.message);
|
|
49
47
|
}
|
|
50
48
|
}
|
|
51
49
|
|
|
@@ -55,7 +53,7 @@ async function loadVariablesFile(variablesPath) {
|
|
|
55
53
|
* Resolves environment using fallback chain: config.environment → default 'dev'
|
|
56
54
|
* @async
|
|
57
55
|
* @param {Object} options - CLI options (for poll settings only)
|
|
58
|
-
* @param {Object} _variables - Variables from
|
|
56
|
+
* @param {Object} _variables - Variables from application config (unused, kept for compatibility)
|
|
59
57
|
* @returns {Promise<Object>} Extracted configuration with resolved controller URL
|
|
60
58
|
*/
|
|
61
59
|
async function extractDeploymentConfig(options, _variables) {
|
|
@@ -132,7 +130,7 @@ async function refreshDeploymentToken(appName, deploymentConfig) {
|
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
/**
|
|
135
|
-
* Loads deployment configuration from
|
|
133
|
+
* Loads deployment configuration from application.yaml and gets/refreshes token
|
|
136
134
|
* @async
|
|
137
135
|
* @param {string} appName - Application name
|
|
138
136
|
* @param {Object} options - CLI options
|
|
@@ -143,8 +141,7 @@ async function loadDeploymentConfig(appName, options) {
|
|
|
143
141
|
const { appPath } = await detectAppType(appName);
|
|
144
142
|
await validateAppDirectory(appPath, appName);
|
|
145
143
|
|
|
146
|
-
const
|
|
147
|
-
const variables = await loadVariablesFile(variablesPath);
|
|
144
|
+
const variables = loadVariablesFile(appPath);
|
|
148
145
|
|
|
149
146
|
const deploymentConfig = await extractDeploymentConfig(options, variables);
|
|
150
147
|
|
|
@@ -153,7 +150,7 @@ async function loadDeploymentConfig(appName, options) {
|
|
|
153
150
|
|
|
154
151
|
validateDeploymentConfig(deploymentConfig);
|
|
155
152
|
|
|
156
|
-
return deploymentConfig;
|
|
153
|
+
return { ...deploymentConfig, appPath };
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
module.exports = {
|
package/lib/app/deploy.js
CHANGED
|
@@ -10,14 +10,13 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const fs = require('fs').promises;
|
|
13
|
-
const path = require('path');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
13
|
const chalk = require('chalk');
|
|
16
14
|
const pushUtils = require('../deployment/push');
|
|
17
15
|
const logger = require('../utils/logger');
|
|
18
16
|
const { detectAppType, getBuilderPath, getIntegrationPath } = require('../utils/paths');
|
|
19
17
|
const { checkApplicationExists } = require('../utils/app-existence');
|
|
20
18
|
const { loadDeploymentConfig } = require('./deploy-config');
|
|
19
|
+
const { logOfflinePathWhenType } = require('../utils/cli-utils');
|
|
21
20
|
const { displayAppUrlFromController } = require('./deploy-status-display');
|
|
22
21
|
|
|
23
22
|
/**
|
|
@@ -116,17 +115,20 @@ async function pushApp(appName, options = {}) {
|
|
|
116
115
|
try {
|
|
117
116
|
validateAppName(appName);
|
|
118
117
|
|
|
119
|
-
const
|
|
118
|
+
const { getBuilderPath, resolveApplicationConfigPath } = require('../utils/paths');
|
|
119
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
120
|
+
const builderPath = getBuilderPath(appName);
|
|
120
121
|
let config;
|
|
121
122
|
try {
|
|
122
|
-
|
|
123
|
+
const configPath = resolveApplicationConfigPath(builderPath);
|
|
124
|
+
config = loadConfigFile(configPath);
|
|
123
125
|
} catch (error) {
|
|
124
|
-
throw new Error(`Failed to load configuration: ${
|
|
126
|
+
throw new Error(`Failed to load configuration: ${error.message}\nRun 'aifabrix create ${appName}' first`);
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
const registry = options.registry || config.image?.registry;
|
|
128
130
|
if (!registry) {
|
|
129
|
-
throw new Error('Registry URL is required. Provide via --registry flag or configure in
|
|
131
|
+
throw new Error('Registry URL is required. Provide via --registry flag or configure in application.yaml under image.registry');
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
if (!/^[^.]+\.azurecr\.io$/.test(registry)) {
|
|
@@ -148,15 +150,16 @@ async function pushApp(appName, options = {}) {
|
|
|
148
150
|
* Generates and validates deployment manifest
|
|
149
151
|
* @async
|
|
150
152
|
* @param {string} appName - Application name
|
|
153
|
+
* @param {Object} [options] - Deployment options (type: 'app' | 'external' for path resolution)
|
|
151
154
|
* @returns {Promise<Object>} Deployment manifest
|
|
152
155
|
* @throws {Error} If generation or validation fails
|
|
153
156
|
*/
|
|
154
|
-
async function generateAndValidateManifest(appName) {
|
|
157
|
+
async function generateAndValidateManifest(appName, options = {}) {
|
|
155
158
|
logger.log(chalk.blue(`\n📋 Generating deployment manifest for ${appName}...`));
|
|
156
159
|
const generator = require('../generator');
|
|
157
160
|
|
|
158
161
|
// generateDeployJson already validates against schema and throws on error
|
|
159
|
-
const manifestPath = await generator.generateDeployJson(appName);
|
|
162
|
+
const manifestPath = await generator.generateDeployJson(appName, options);
|
|
160
163
|
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
161
164
|
|
|
162
165
|
// Additional validation for warnings (schema validation already passed)
|
|
@@ -220,16 +223,16 @@ function displayDeploymentResults(result) {
|
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
/**
|
|
223
|
-
* Check if app is external and handle external deployment.
|
|
224
|
-
*
|
|
226
|
+
* Check if app is external (resolved from integration/) and handle external deployment.
|
|
227
|
+
* Path resolution order: integration first, then builder; no flag overrides.
|
|
225
228
|
* @async
|
|
226
229
|
* @function handleExternalDeployment
|
|
227
230
|
* @param {string} appName - Application name
|
|
228
|
-
* @param {Object} options - Deployment options (
|
|
231
|
+
* @param {Object} options - Deployment options (poll, etc.)
|
|
229
232
|
* @returns {Promise<Object|null>} Deployment result if external, null otherwise
|
|
230
233
|
*/
|
|
231
234
|
async function handleExternalDeployment(appName, options) {
|
|
232
|
-
const { isExternal } = await detectAppType(appName
|
|
235
|
+
const { isExternal } = await detectAppType(appName);
|
|
233
236
|
if (isExternal) {
|
|
234
237
|
const externalDeploy = require('../external-system/deploy');
|
|
235
238
|
await externalDeploy.deployExternalSystem(appName, options);
|
|
@@ -266,7 +269,7 @@ async function handleDeploymentError(error, appName, controllerUrl, usedExternal
|
|
|
266
269
|
*/
|
|
267
270
|
function validateImageIsPullable(imageRef, appName) {
|
|
268
271
|
if (!imageRef || !imageRef.includes('/')) {
|
|
269
|
-
const hint = `Set image.registry and image.tag in builder/${appName}/
|
|
272
|
+
const hint = `Set image.registry and image.tag in builder/${appName}/application.yaml, or pass a full image ref (e.g. --image <registry>/${appName}:<tag>) when deploying`;
|
|
270
273
|
throw new Error(
|
|
271
274
|
`Deployed image must be pullable (include a registry). Current image: "${imageRef || 'none'}". ${hint}`
|
|
272
275
|
);
|
|
@@ -336,10 +339,11 @@ function applyManifestOverrides(manifest, options) {
|
|
|
336
339
|
*/
|
|
337
340
|
async function executeStandardDeployment(appName, options) {
|
|
338
341
|
const config = await loadDeploymentConfig(appName, options);
|
|
342
|
+
logOfflinePathWhenType(config.appPath, options);
|
|
339
343
|
const controllerUrl = config.controllerUrl || 'unknown';
|
|
340
344
|
const appExists = await checkApplicationExists(appName, controllerUrl, config.envKey, config.auth);
|
|
341
345
|
|
|
342
|
-
const { manifest, manifestPath } = await generateAndValidateManifest(appName);
|
|
346
|
+
const { manifest, manifestPath } = await generateAndValidateManifest(appName, options);
|
|
343
347
|
applyManifestOverrides(manifest, options);
|
|
344
348
|
validateImageIsPullable(manifest.image, appName);
|
|
345
349
|
displayDeploymentInfo(manifest, manifestPath);
|
|
@@ -388,7 +392,7 @@ async function tryExternalDeployFallback(appName, options) {
|
|
|
388
392
|
if (e.code !== 'ENOENT') throw e;
|
|
389
393
|
}
|
|
390
394
|
if (!builderExists && integrationExists) {
|
|
391
|
-
const fallbackResult = await handleExternalDeployment(appName,
|
|
395
|
+
const fallbackResult = await handleExternalDeployment(appName, options);
|
|
392
396
|
if (fallbackResult) return { usedExternalDeploy: true, result: fallbackResult };
|
|
393
397
|
}
|
|
394
398
|
return { usedExternalDeploy: false, result: null };
|
|
@@ -402,20 +406,21 @@ async function tryExternalDeployFallback(appName, options) {
|
|
|
402
406
|
* @function deployApp
|
|
403
407
|
* @param {string} appName - Name of the application to deploy
|
|
404
408
|
* @param {Object} options - Deployment options
|
|
409
|
+
* @param {boolean} [options.local] - If true, caller may run app locally or restart dataplane (for external)
|
|
405
410
|
* @param {boolean} [options.poll] - Poll for deployment status
|
|
406
411
|
* @param {number} [options.pollInterval] - Polling interval in milliseconds
|
|
407
412
|
* @param {number} [options.pollMaxAttempts] - Max polling attempts
|
|
408
|
-
* @returns {Promise<Object>} Deployment result
|
|
413
|
+
* @returns {Promise<{ result: Object, usedExternalDeploy: boolean }>} Deployment result and whether external deploy was used
|
|
409
414
|
* @throws {Error} If deployment fails
|
|
410
415
|
*
|
|
411
416
|
* Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
|
|
412
417
|
*
|
|
413
418
|
* @example
|
|
414
|
-
* await deployApp('myapp', { poll: true });
|
|
419
|
+
* const { result, usedExternalDeploy } = await deployApp('myapp', { poll: true });
|
|
415
420
|
*/
|
|
416
421
|
async function deployApp(appName, options = {}) {
|
|
417
422
|
let controllerUrl = null;
|
|
418
|
-
let usedExternalDeploy =
|
|
423
|
+
let usedExternalDeploy = false;
|
|
419
424
|
|
|
420
425
|
try {
|
|
421
426
|
if (!appName || typeof appName !== 'string' || appName.trim().length === 0) {
|
|
@@ -424,18 +429,20 @@ async function deployApp(appName, options = {}) {
|
|
|
424
429
|
validateAppName(appName);
|
|
425
430
|
|
|
426
431
|
const externalResult = await handleExternalDeployment(appName, options);
|
|
427
|
-
if (externalResult)
|
|
432
|
+
if (externalResult) {
|
|
433
|
+
usedExternalDeploy = true;
|
|
434
|
+
return { result: externalResult, usedExternalDeploy: true };
|
|
435
|
+
}
|
|
428
436
|
|
|
429
437
|
const fallback = await tryExternalDeployFallback(appName, options);
|
|
430
438
|
if (fallback.result) {
|
|
431
439
|
usedExternalDeploy = fallback.usedExternalDeploy;
|
|
432
|
-
return fallback.result;
|
|
440
|
+
return { result: fallback.result, usedExternalDeploy };
|
|
433
441
|
}
|
|
434
|
-
usedExternalDeploy = false;
|
|
435
442
|
|
|
436
443
|
const { result, controllerUrl: url } = await executeStandardDeployment(appName, options);
|
|
437
444
|
controllerUrl = url;
|
|
438
|
-
return result;
|
|
445
|
+
return { result, usedExternalDeploy: false };
|
|
439
446
|
} catch (error) {
|
|
440
447
|
await handleDeploymentError(error, appName, controllerUrl, usedExternalDeploy);
|
|
441
448
|
}
|
package/lib/app/display.js
CHANGED
|
@@ -25,7 +25,7 @@ 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 validate ' + appName
|
|
28
|
+
logger.log(chalk.white('2. Run: aifabrix validate ' + appName));
|
|
29
29
|
logger.log(chalk.white('3. Run: aifabrix login'));
|
|
30
30
|
logger.log(chalk.white('4. Run: aifabrix deploy ' + appName));
|
|
31
31
|
}
|
package/lib/app/dockerfile.js
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
const fs = require('fs').promises;
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
14
|
const build = require('../build');
|
|
16
15
|
const { validateAppName } = require('./push');
|
|
17
16
|
const logger = require('../utils/logger');
|
|
@@ -40,24 +39,25 @@ async function checkDockerfileExists(dockerfilePath, options) {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
* Loads application configuration from
|
|
44
|
-
* @
|
|
45
|
-
* @param {string} configPath - Path to variables.yaml
|
|
42
|
+
* Loads application configuration from application config (application.yaml or application.json)
|
|
43
|
+
* @param {string} appPath - Application directory path
|
|
46
44
|
* @param {Object} options - Generation options
|
|
47
|
-
* @returns {
|
|
45
|
+
* @returns {Object} Application configuration
|
|
48
46
|
* @throws {Error} If configuration cannot be loaded
|
|
49
47
|
*/
|
|
50
|
-
|
|
48
|
+
function loadAppConfig(appPath, options) {
|
|
49
|
+
const { resolveApplicationConfigPath } = require('../utils/paths');
|
|
50
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
51
51
|
try {
|
|
52
|
-
const
|
|
53
|
-
const variables =
|
|
52
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
53
|
+
const variables = loadConfigFile(configPath);
|
|
54
54
|
return {
|
|
55
55
|
language: options.language || variables.build?.language || 'typescript',
|
|
56
56
|
port: getContainerPort(variables, 3000),
|
|
57
57
|
...variables
|
|
58
58
|
};
|
|
59
|
-
} catch {
|
|
60
|
-
throw new Error(`Failed to load configuration
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(`Failed to load configuration: ${error.message}`);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -109,8 +109,7 @@ async function generateDockerfileForApp(appName, options = {}) {
|
|
|
109
109
|
await checkDockerfileExists(dockerfilePath, options);
|
|
110
110
|
|
|
111
111
|
// Load configuration
|
|
112
|
-
const
|
|
113
|
-
const config = await loadAppConfig(configPath, options);
|
|
112
|
+
const config = loadAppConfig(appPath, options);
|
|
114
113
|
|
|
115
114
|
// Generate and copy Dockerfile
|
|
116
115
|
return await generateAndCopyDockerfile(appPath, dockerfilePath, config);
|
package/lib/app/helpers.js
CHANGED
|
@@ -13,6 +13,40 @@ const path = require('path');
|
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const { validateTemplate, copyTemplateFiles, copyAppFiles } = require('../validation/template');
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
|
+
const { getIntegrationPath, getBuilderPath } = require('../utils/paths');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates that no app or external system with this name exists in integration/ or builder/.
|
|
20
|
+
* Call before create so we do not overwrite existing directories.
|
|
21
|
+
*
|
|
22
|
+
* @async
|
|
23
|
+
* @param {string} appName - Application or external system name
|
|
24
|
+
* @throws {Error} If integration/<appName> or builder/<appName> already exists
|
|
25
|
+
*/
|
|
26
|
+
async function validateAppOrExternalNameNotExists(appName) {
|
|
27
|
+
const integrationPath = getIntegrationPath(appName);
|
|
28
|
+
const builderPath = getBuilderPath(appName);
|
|
29
|
+
try {
|
|
30
|
+
await fs.access(integrationPath);
|
|
31
|
+
throw new Error(
|
|
32
|
+
`App or external system '${appName}' already exists in integration/. ` +
|
|
33
|
+
`Use a different name or remove integration/${appName} if you intend to replace it.`
|
|
34
|
+
);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err.code !== 'ENOENT' && err.message.includes('already exists')) throw err;
|
|
37
|
+
if (err.code !== 'ENOENT') throw err;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await fs.access(builderPath);
|
|
41
|
+
throw new Error(
|
|
42
|
+
`App or external system '${appName}' already exists in builder/. ` +
|
|
43
|
+
`Use a different name or remove builder/${appName} if you intend to replace it.`
|
|
44
|
+
);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (err.code !== 'ENOENT' && err.message.includes('already exists')) throw err;
|
|
47
|
+
if (err.code !== 'ENOENT') throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
16
50
|
|
|
17
51
|
/**
|
|
18
52
|
* Validates that app directory doesn't already exist
|
|
@@ -87,6 +121,7 @@ async function handleGitHubWorkflows(options, config) {
|
|
|
87
121
|
async function validateAppCreation(appName, options, appPath, baseDir = 'builder') {
|
|
88
122
|
const { validateAppName } = require('./push');
|
|
89
123
|
validateAppName(appName);
|
|
124
|
+
await validateAppOrExternalNameNotExists(appName);
|
|
90
125
|
await validateAppDirectoryNotExists(appPath, appName, baseDir);
|
|
91
126
|
|
|
92
127
|
if (!options.app) {
|
|
@@ -128,7 +163,7 @@ async function processTemplateFiles(template, appPath, appName, options, config)
|
|
|
128
163
|
}
|
|
129
164
|
|
|
130
165
|
/**
|
|
131
|
-
* Updates
|
|
166
|
+
* Updates application config for --app flag
|
|
132
167
|
* @async
|
|
133
168
|
* @function updateVariablesForAppFlag
|
|
134
169
|
* @param {string} appPath - Application directory path
|
|
@@ -136,11 +171,11 @@ async function processTemplateFiles(template, appPath, appName, options, config)
|
|
|
136
171
|
* @throws {Error} If update fails
|
|
137
172
|
*/
|
|
138
173
|
async function updateVariablesForAppFlag(appPath, appName) {
|
|
174
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
175
|
+
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
139
176
|
try {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
143
|
-
const variables = yaml.load(variablesContent);
|
|
177
|
+
const variablesPath = resolveApplicationConfigPath(appPath);
|
|
178
|
+
const variables = loadConfigFile(variablesPath) || {};
|
|
144
179
|
|
|
145
180
|
if (variables.build) {
|
|
146
181
|
variables.build.context = '../..';
|
|
@@ -152,14 +187,16 @@ async function updateVariablesForAppFlag(appPath, appName) {
|
|
|
152
187
|
};
|
|
153
188
|
}
|
|
154
189
|
|
|
155
|
-
|
|
190
|
+
writeConfigFile(variablesPath, variables);
|
|
156
191
|
} catch (error) {
|
|
157
|
-
|
|
192
|
+
if (!error.message.includes('not found')) {
|
|
193
|
+
logger.warn(chalk.yellow(`⚠️ Warning: Could not update application config: ${error.message}`));
|
|
194
|
+
}
|
|
158
195
|
}
|
|
159
196
|
}
|
|
160
197
|
|
|
161
198
|
/**
|
|
162
|
-
* Gets language from config or
|
|
199
|
+
* Gets language from config or application.yaml
|
|
163
200
|
* @async
|
|
164
201
|
* @function getLanguageForAppFiles
|
|
165
202
|
* @param {string} language - Language from config
|
|
@@ -172,14 +209,14 @@ async function getLanguageForAppFiles(language, appPath) {
|
|
|
172
209
|
return language;
|
|
173
210
|
}
|
|
174
211
|
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const variables =
|
|
212
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
213
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
214
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
215
|
+
const variables = loadConfigFile(configPath) || {};
|
|
179
216
|
const languageFromYaml = variables?.build?.language;
|
|
180
217
|
|
|
181
218
|
if (!languageFromYaml) {
|
|
182
|
-
throw new Error('Language not specified and could not be determined from
|
|
219
|
+
throw new Error('Language not specified and could not be determined from application.yaml. Use --language flag or ensure application.yaml contains build.language');
|
|
183
220
|
}
|
|
184
221
|
|
|
185
222
|
return languageFromYaml;
|
|
@@ -207,6 +244,7 @@ async function setupAppFiles(appName, appPath, config, options) {
|
|
|
207
244
|
|
|
208
245
|
module.exports = {
|
|
209
246
|
validateAppDirectoryNotExists,
|
|
247
|
+
validateAppOrExternalNameNotExists,
|
|
210
248
|
getBaseDirForAppType,
|
|
211
249
|
handleGitHubWorkflows,
|
|
212
250
|
validateAppCreation,
|
package/lib/app/index.js
CHANGED
|
@@ -52,7 +52,7 @@ const {
|
|
|
52
52
|
*
|
|
53
53
|
* @example
|
|
54
54
|
* await createApp('myapp', { port: 3000, database: true, language: 'typescript' });
|
|
55
|
-
* // Creates builder/ with
|
|
55
|
+
* // Creates builder/ with application.yaml, env.template, rbac.yaml
|
|
56
56
|
*/
|
|
57
57
|
/**
|
|
58
58
|
* Validates app name and initial setup
|
|
@@ -230,7 +230,7 @@ function detectLanguage(appPath) {
|
|
|
230
230
|
* @function generateDockerfile
|
|
231
231
|
* @param {string} appPath - Path to application directory
|
|
232
232
|
* @param {string} language - Target language ('typescript', 'python')
|
|
233
|
-
* @param {Object} config - Application configuration from
|
|
233
|
+
* @param {Object} config - Application configuration from application.yaml
|
|
234
234
|
* @returns {Promise<string>} Path to generated Dockerfile
|
|
235
235
|
* @throws {Error} If template generation fails
|
|
236
236
|
*
|
|
@@ -263,6 +263,17 @@ async function runApp(appName, options = {}) {
|
|
|
263
263
|
return appRun.runApp(appName, options);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Restart a running application container (Docker restart).
|
|
268
|
+
* @async
|
|
269
|
+
* @function restartApp
|
|
270
|
+
* @param {string} appName - Application name (must be running)
|
|
271
|
+
* @returns {Promise<void>} Resolves when container is restarted
|
|
272
|
+
*/
|
|
273
|
+
async function restartApp(appName) {
|
|
274
|
+
return appRun.restartApp(appName);
|
|
275
|
+
}
|
|
276
|
+
|
|
266
277
|
/**
|
|
267
278
|
* Deploys application to controller
|
|
268
279
|
* @async
|
|
@@ -280,6 +291,7 @@ module.exports = {
|
|
|
280
291
|
createApp,
|
|
281
292
|
buildApp,
|
|
282
293
|
runApp,
|
|
294
|
+
restartApp,
|
|
283
295
|
downApp,
|
|
284
296
|
detectLanguage,
|
|
285
297
|
generateDockerfile,
|
package/lib/app/prompts.js
CHANGED
|
@@ -112,16 +112,8 @@ function buildServiceQuestions(options, appType) {
|
|
|
112
112
|
return questions;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
* Builds external system configuration questions
|
|
117
|
-
* @param {Object} options - Provided options
|
|
118
|
-
* @param {string} appName - Application name
|
|
119
|
-
* @returns {Array} Array of question objects
|
|
120
|
-
*/
|
|
121
|
-
function buildExternalSystemQuestions(options, appName) {
|
|
115
|
+
function buildExternalSystemIdentityQuestions(options, appName) {
|
|
122
116
|
const questions = [];
|
|
123
|
-
|
|
124
|
-
// System key (defaults to app name)
|
|
125
117
|
if (!options.systemKey) {
|
|
126
118
|
questions.push({
|
|
127
119
|
type: 'input',
|
|
@@ -129,18 +121,12 @@ function buildExternalSystemQuestions(options, appName) {
|
|
|
129
121
|
message: 'What is the system key?',
|
|
130
122
|
default: appName,
|
|
131
123
|
validate: (input) => {
|
|
132
|
-
if (!input || input.trim().length === 0)
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
if (!/^[a-z0-9-]+$/.test(input)) {
|
|
136
|
-
return 'System key must contain only lowercase letters, numbers, and hyphens';
|
|
137
|
-
}
|
|
124
|
+
if (!input || input.trim().length === 0) return 'System key is required';
|
|
125
|
+
if (!/^[a-z0-9-]+$/.test(input)) return 'System key must contain only lowercase letters, numbers, and hyphens';
|
|
138
126
|
return true;
|
|
139
127
|
}
|
|
140
128
|
});
|
|
141
129
|
}
|
|
142
|
-
|
|
143
|
-
// System display name
|
|
144
130
|
if (!options.systemDisplayName) {
|
|
145
131
|
questions.push({
|
|
146
132
|
type: 'input',
|
|
@@ -148,15 +134,11 @@ function buildExternalSystemQuestions(options, appName) {
|
|
|
148
134
|
message: 'What is the system display name?',
|
|
149
135
|
default: appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
|
150
136
|
validate: (input) => {
|
|
151
|
-
if (!input || input.trim().length === 0)
|
|
152
|
-
return 'System display name is required';
|
|
153
|
-
}
|
|
137
|
+
if (!input || input.trim().length === 0) return 'System display name is required';
|
|
154
138
|
return true;
|
|
155
139
|
}
|
|
156
140
|
});
|
|
157
141
|
}
|
|
158
|
-
|
|
159
|
-
// System description
|
|
160
142
|
if (!options.systemDescription) {
|
|
161
143
|
questions.push({
|
|
162
144
|
type: 'input',
|
|
@@ -164,15 +146,16 @@ function buildExternalSystemQuestions(options, appName) {
|
|
|
164
146
|
message: 'What is the system description?',
|
|
165
147
|
default: `External system integration for ${appName}`,
|
|
166
148
|
validate: (input) => {
|
|
167
|
-
if (!input || input.trim().length === 0)
|
|
168
|
-
return 'System description is required';
|
|
169
|
-
}
|
|
149
|
+
if (!input || input.trim().length === 0) return 'System description is required';
|
|
170
150
|
return true;
|
|
171
151
|
}
|
|
172
152
|
});
|
|
173
153
|
}
|
|
154
|
+
return questions;
|
|
155
|
+
}
|
|
174
156
|
|
|
175
|
-
|
|
157
|
+
function buildExternalSystemTypeQuestions(options) {
|
|
158
|
+
const questions = [];
|
|
176
159
|
if (!options.systemType) {
|
|
177
160
|
questions.push({
|
|
178
161
|
type: 'list',
|
|
@@ -186,8 +169,6 @@ function buildExternalSystemQuestions(options, appName) {
|
|
|
186
169
|
default: 'openapi'
|
|
187
170
|
});
|
|
188
171
|
}
|
|
189
|
-
|
|
190
|
-
// Authentication type
|
|
191
172
|
if (!options.authType) {
|
|
192
173
|
questions.push({
|
|
193
174
|
type: 'list',
|
|
@@ -201,25 +182,36 @@ function buildExternalSystemQuestions(options, appName) {
|
|
|
201
182
|
default: 'apikey'
|
|
202
183
|
});
|
|
203
184
|
}
|
|
185
|
+
return questions;
|
|
186
|
+
}
|
|
204
187
|
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
});
|
|
220
|
-
}
|
|
188
|
+
function buildExternalSystemDatasourceQuestion(options) {
|
|
189
|
+
if (options.datasourceCount) return [];
|
|
190
|
+
return [{
|
|
191
|
+
type: 'input',
|
|
192
|
+
name: 'datasourceCount',
|
|
193
|
+
message: 'How many datasources do you want to create?',
|
|
194
|
+
default: '1',
|
|
195
|
+
validate: (input) => {
|
|
196
|
+
const count = parseInt(input, 10);
|
|
197
|
+
if (isNaN(count) || count < 1 || count > 10) return 'Datasource count must be a number between 1 and 10';
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}];
|
|
201
|
+
}
|
|
221
202
|
|
|
222
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Builds external system configuration questions
|
|
205
|
+
* @param {Object} options - Provided options
|
|
206
|
+
* @param {string} appName - Application name
|
|
207
|
+
* @returns {Array} Array of question objects
|
|
208
|
+
*/
|
|
209
|
+
function buildExternalSystemQuestions(options, appName) {
|
|
210
|
+
return [
|
|
211
|
+
...buildExternalSystemIdentityQuestions(options, appName),
|
|
212
|
+
...buildExternalSystemTypeQuestions(options),
|
|
213
|
+
...buildExternalSystemDatasourceQuestion(options)
|
|
214
|
+
];
|
|
223
215
|
}
|
|
224
216
|
|
|
225
217
|
/**
|
package/lib/app/push.js
CHANGED
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const fs = require('fs').promises;
|
|
12
|
-
const path = require('path');
|
|
13
11
|
const chalk = require('chalk');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
12
|
const pushUtils = require('../deployment/push');
|
|
16
13
|
const logger = require('../utils/logger');
|
|
17
14
|
|
|
@@ -44,7 +41,7 @@ function validateAppName(appName) {
|
|
|
44
41
|
|
|
45
42
|
/**
|
|
46
43
|
* Extracts image name from configuration using the same logic as build command
|
|
47
|
-
* @param {Object} config - Configuration object from
|
|
44
|
+
* @param {Object} config - Configuration object from application.yaml
|
|
48
45
|
* @param {string} appName - Application name (fallback)
|
|
49
46
|
* @returns {string} Image name
|
|
50
47
|
*/
|
|
@@ -61,7 +58,7 @@ function extractImageName(config, appName) {
|
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
/**
|
|
64
|
-
* Loads push configuration from
|
|
61
|
+
* Loads push configuration from application config (application.yaml or application.json)
|
|
65
62
|
* @async
|
|
66
63
|
* @param {string} appName - Application name
|
|
67
64
|
* @param {Object} options - Push options
|
|
@@ -69,15 +66,15 @@ function extractImageName(config, appName) {
|
|
|
69
66
|
* @throws {Error} If configuration cannot be loaded
|
|
70
67
|
*/
|
|
71
68
|
async function loadPushConfig(appName, options) {
|
|
72
|
-
|
|
73
|
-
const {
|
|
69
|
+
const { detectAppType, resolveApplicationConfigPath } = require('../utils/paths');
|
|
70
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
74
71
|
const { appPath } = await detectAppType(appName);
|
|
75
|
-
const configPath = path.join(appPath, 'variables.yaml');
|
|
76
72
|
try {
|
|
77
|
-
const
|
|
73
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
74
|
+
const config = loadConfigFile(configPath);
|
|
78
75
|
const registry = options.registry || config.image?.registry;
|
|
79
76
|
if (!registry) {
|
|
80
|
-
throw new Error('Registry URL is required. Provide via --registry flag or configure in
|
|
77
|
+
throw new Error('Registry URL is required. Provide via --registry flag or configure in application config under image.registry');
|
|
81
78
|
}
|
|
82
79
|
const imageName = extractImageName(config, appName);
|
|
83
80
|
return { registry, imageName };
|
|
@@ -85,7 +82,7 @@ async function loadPushConfig(appName, options) {
|
|
|
85
82
|
if (error.message.includes('Registry URL')) {
|
|
86
83
|
throw error;
|
|
87
84
|
}
|
|
88
|
-
throw new Error(`Failed to load configuration: ${
|
|
85
|
+
throw new Error(`Failed to load configuration: ${error.message}\nRun 'aifabrix create ${appName}' first`);
|
|
89
86
|
}
|
|
90
87
|
}
|
|
91
88
|
|