@aifabrix/builder 2.33.0 → 2.33.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/integration/hubspot/README.md +7 -7
- package/lib/api/index.js +6 -2
- package/lib/app/deploy-config.js +161 -0
- package/lib/app/deploy.js +28 -153
- package/lib/app/register.js +6 -5
- package/lib/app/run-helpers.js +23 -17
- package/lib/cli.js +31 -1
- package/lib/commands/logout.js +3 -4
- package/lib/commands/up-common.js +72 -0
- package/lib/commands/up-dataplane.js +109 -0
- package/lib/commands/up-miso.js +134 -0
- package/lib/core/config.js +32 -9
- package/lib/core/secrets-docker-env.js +88 -0
- package/lib/core/secrets.js +142 -115
- package/lib/datasource/deploy.js +31 -3
- package/lib/datasource/list.js +102 -15
- package/lib/infrastructure/helpers.js +82 -1
- package/lib/infrastructure/index.js +2 -0
- package/lib/schema/env-config.yaml +7 -0
- package/lib/utils/api.js +70 -2
- package/lib/utils/compose-generator.js +13 -13
- package/lib/utils/config-paths.js +13 -0
- package/lib/utils/device-code.js +2 -2
- package/lib/utils/env-endpoints.js +2 -5
- package/lib/utils/env-map.js +4 -5
- package/lib/utils/error-formatters/network-errors.js +13 -3
- package/lib/utils/parse-image-ref.js +27 -0
- package/lib/utils/paths.js +28 -4
- package/lib/utils/secrets-generator.js +34 -12
- package/lib/utils/secrets-helpers.js +1 -2
- package/lib/utils/token-manager-refresh.js +5 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/Dockerfile +16 -0
- package/templates/applications/dataplane/README.md +205 -0
- package/templates/applications/dataplane/env.template +143 -0
- package/templates/applications/dataplane/rbac.yaml +283 -0
- package/templates/applications/dataplane/variables.yaml +143 -0
- package/templates/applications/keycloak/Dockerfile +1 -1
- package/templates/applications/keycloak/README.md +193 -0
- package/templates/applications/keycloak/variables.yaml +5 -6
- package/templates/applications/miso-controller/Dockerfile +8 -8
- package/templates/applications/miso-controller/README.md +369 -0
- package/templates/applications/miso-controller/env.template +114 -6
- package/templates/applications/miso-controller/rbac.yaml +74 -0
- package/templates/applications/miso-controller/variables.yaml +93 -5
- package/templates/github/ci.yaml.hbs +44 -1
- package/templates/github/release.yaml.hbs +44 -0
- package/templates/infra/compose.yaml.hbs +2 -1
- package/templates/applications/miso-controller/test.yaml +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - Up Commands Shared Helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for up-miso and up-dataplane (ensure app from template).
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Shared helpers for up-miso and up-dataplane commands
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
const pathsUtil = require('../utils/paths');
|
|
16
|
+
const { copyTemplateFiles } = require('../validation/template');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Copy template to a target path if variables.yaml is missing there.
|
|
20
|
+
* @param {string} appName - Application name
|
|
21
|
+
* @param {string} targetAppPath - Target directory (e.g. builder/keycloak)
|
|
22
|
+
* @returns {Promise<boolean>} True if template was copied, false if already present
|
|
23
|
+
*/
|
|
24
|
+
async function ensureTemplateAtPath(appName, targetAppPath) {
|
|
25
|
+
const variablesPath = path.join(targetAppPath, 'variables.yaml');
|
|
26
|
+
if (fs.existsSync(variablesPath)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
await copyTemplateFiles(appName, targetAppPath);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Ensures builder app directory exists from template if variables.yaml is missing.
|
|
35
|
+
* If builder/<appName>/variables.yaml does not exist, copies from templates/applications/<appName>.
|
|
36
|
+
* Uses AIFABRIX_BUILDER_DIR when set (e.g. by up-miso/up-dataplane from config aifabrix-env-config).
|
|
37
|
+
* When using a custom builder dir, also populates cwd/builder/<appName> so the repo's builder/ is not empty.
|
|
38
|
+
*
|
|
39
|
+
* @async
|
|
40
|
+
* @function ensureAppFromTemplate
|
|
41
|
+
* @param {string} appName - Application name (keycloak, miso-controller, dataplane)
|
|
42
|
+
* @returns {Promise<boolean>} True if template was copied (in either location), false if both already existed
|
|
43
|
+
* @throws {Error} If template copy fails
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* await ensureAppFromTemplate('keycloak');
|
|
47
|
+
*/
|
|
48
|
+
async function ensureAppFromTemplate(appName) {
|
|
49
|
+
if (!appName || typeof appName !== 'string') {
|
|
50
|
+
throw new Error('Application name is required and must be a string');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const appPath = pathsUtil.getBuilderPath(appName);
|
|
54
|
+
const primaryCopied = await ensureTemplateAtPath(appName, appPath);
|
|
55
|
+
if (primaryCopied) {
|
|
56
|
+
logger.log(chalk.blue(`Creating builder/${appName} from template...`));
|
|
57
|
+
logger.log(chalk.green(`✓ Copied template for ${appName}`));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
|
|
61
|
+
if (path.resolve(cwdBuilderPath) !== path.resolve(appPath)) {
|
|
62
|
+
const cwdCopied = await ensureTemplateAtPath(appName, cwdBuilderPath);
|
|
63
|
+
if (cwdCopied) {
|
|
64
|
+
logger.log(chalk.blue(`Creating builder/${appName} in project (from template)...`));
|
|
65
|
+
logger.log(chalk.green(`✓ Copied template for ${appName} into builder/`));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return primaryCopied;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { ensureAppFromTemplate };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - Up Dataplane Command
|
|
3
|
+
*
|
|
4
|
+
* Registers or rotates dataplane app in dev, then deploys. Miso-controller runs
|
|
5
|
+
* the dataplane container; this command does not run the image locally.
|
|
6
|
+
* If app is already registered, uses rotate-secret; otherwise registers.
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview up-dataplane command implementation
|
|
9
|
+
* @author AI Fabrix Team
|
|
10
|
+
* @version 2.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const yaml = require('js-yaml');
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
const logger = require('../utils/logger');
|
|
18
|
+
const config = require('../core/config');
|
|
19
|
+
const { checkAuthentication } = require('../utils/app-register-auth');
|
|
20
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
21
|
+
const { resolveEnvironment } = require('../core/config');
|
|
22
|
+
const { registerApplication } = require('../app/register');
|
|
23
|
+
const { rotateSecret } = require('../app/rotate-secret');
|
|
24
|
+
const { checkApplicationExists } = require('../utils/app-existence');
|
|
25
|
+
const app = require('../app');
|
|
26
|
+
const { ensureAppFromTemplate } = require('./up-common');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Register or rotate dataplane: if app exists in controller, rotate secret; otherwise register.
|
|
30
|
+
* @async
|
|
31
|
+
* @param {Object} options - Commander options
|
|
32
|
+
* @param {string} controllerUrl - Controller URL
|
|
33
|
+
* @param {string} environmentKey - Environment key
|
|
34
|
+
* @param {Object} authConfig - Auth config with token
|
|
35
|
+
*/
|
|
36
|
+
async function registerOrRotateDataplane(options, controllerUrl, environmentKey, authConfig) {
|
|
37
|
+
const appExists = await checkApplicationExists('dataplane', controllerUrl, environmentKey, authConfig);
|
|
38
|
+
if (appExists) {
|
|
39
|
+
logger.log(chalk.blue('Dataplane already registered; rotating secret...'));
|
|
40
|
+
await rotateSecret('dataplane', options);
|
|
41
|
+
} else {
|
|
42
|
+
const imageOverride = options.image || (options.registry ? buildDataplaneImageRef(options.registry) : undefined);
|
|
43
|
+
const registerOpts = { imageOverride, image: imageOverride, registryMode: options.registryMode };
|
|
44
|
+
await registerApplication('dataplane', registerOpts);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build full image ref from registry and dataplane variables (registry/name:tag)
|
|
50
|
+
* @param {string} registry - Registry URL
|
|
51
|
+
* @returns {string|undefined} Full image reference or undefined
|
|
52
|
+
*/
|
|
53
|
+
function buildDataplaneImageRef(registry) {
|
|
54
|
+
const pathsUtil = require('../utils/paths');
|
|
55
|
+
const variablesPath = path.join(pathsUtil.getBuilderPath('dataplane'), 'variables.yaml');
|
|
56
|
+
if (!fs.existsSync(variablesPath)) return undefined;
|
|
57
|
+
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
58
|
+
const variables = yaml.load(content);
|
|
59
|
+
const name = variables?.image?.name || variables?.app?.key || 'dataplane';
|
|
60
|
+
const tag = variables?.image?.tag || 'latest';
|
|
61
|
+
const base = (registry || '').replace(/\/+$/, '');
|
|
62
|
+
return base ? `${base}/${name}:${tag}` : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Handle up-dataplane command: ensure logged in, environment dev, ensure dataplane,
|
|
67
|
+
* register or rotate (if already registered), then deploy (miso-controller runs the container).
|
|
68
|
+
*
|
|
69
|
+
* @async
|
|
70
|
+
* @function handleUpDataplane
|
|
71
|
+
* @param {Object} options - Commander options
|
|
72
|
+
* @param {string} [options.registry] - Override registry for dataplane
|
|
73
|
+
* @param {string} [options.registryMode] - Override registry mode (acr|external)
|
|
74
|
+
* @param {string} [options.image] - Override image reference for dataplane
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
* @throws {Error} If not logged in, environment not dev, or any step fails
|
|
77
|
+
*/
|
|
78
|
+
async function handleUpDataplane(options = {}) {
|
|
79
|
+
const builderDir = await config.getAifabrixBuilderDir();
|
|
80
|
+
if (builderDir) {
|
|
81
|
+
process.env.AIFABRIX_BUILDER_DIR = builderDir;
|
|
82
|
+
}
|
|
83
|
+
logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy dataplane in dev)...\n'));
|
|
84
|
+
|
|
85
|
+
const [controllerUrl, environmentKey] = await Promise.all([resolveControllerUrl(), resolveEnvironment()]);
|
|
86
|
+
const authConfig = await checkAuthentication(controllerUrl, environmentKey);
|
|
87
|
+
|
|
88
|
+
const cfg = await config.getConfig();
|
|
89
|
+
const environment = (cfg && cfg.environment) ? cfg.environment : 'dev';
|
|
90
|
+
if (environment !== 'dev') {
|
|
91
|
+
throw new Error(
|
|
92
|
+
'Dataplane is only supported in dev environment. Set with: aifabrix auth config --set-environment dev.'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
logger.log(chalk.green('✓ Logged in and environment is dev'));
|
|
96
|
+
|
|
97
|
+
await ensureAppFromTemplate('dataplane');
|
|
98
|
+
|
|
99
|
+
await registerOrRotateDataplane(options, controllerUrl, environmentKey, authConfig);
|
|
100
|
+
|
|
101
|
+
const imageOverride = options.image || (options.registry ? buildDataplaneImageRef(options.registry) : undefined);
|
|
102
|
+
const deployOpts = { imageOverride, image: imageOverride, registryMode: options.registryMode };
|
|
103
|
+
|
|
104
|
+
await app.deployApp('dataplane', deployOpts);
|
|
105
|
+
|
|
106
|
+
logger.log(chalk.green('\n✓ up-dataplane complete. Dataplane is registered and deployed in dev (miso-controller runs the container).'));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { handleUpDataplane, buildDataplaneImageRef };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - Up Miso Command
|
|
3
|
+
*
|
|
4
|
+
* Installs miso-controller and keycloak from images (no build).
|
|
5
|
+
* Assumes infra is up; sets dev secrets and resolves (no force; existing .env values preserved).
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview up-miso command implementation
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
const chalk = require('chalk');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
|
+
const config = require('../core/config');
|
|
18
|
+
const secrets = require('../core/secrets');
|
|
19
|
+
const infra = require('../infrastructure');
|
|
20
|
+
const app = require('../app');
|
|
21
|
+
const { saveLocalSecret } = require('../utils/local-secrets');
|
|
22
|
+
const { ensureAppFromTemplate } = require('./up-common');
|
|
23
|
+
|
|
24
|
+
/** Keycloak base port (from templates/applications/keycloak/variables.yaml) */
|
|
25
|
+
const KEYCLOAK_BASE_PORT = 8082;
|
|
26
|
+
/** Miso controller base port (dev-config app base) */
|
|
27
|
+
const MISO_BASE_PORT = 3000;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse --image options array into map { keycloak?: string, 'miso-controller'?: string }
|
|
31
|
+
* @param {string[]|string} imageOpts - Option value(s) e.g. ['keycloak=reg/k:v1', 'miso-controller=reg/m:v1']
|
|
32
|
+
* @returns {{ keycloak?: string, 'miso-controller'?: string }}
|
|
33
|
+
*/
|
|
34
|
+
function parseImageOptions(imageOpts) {
|
|
35
|
+
const map = {};
|
|
36
|
+
const arr = Array.isArray(imageOpts) ? imageOpts : (imageOpts ? [imageOpts] : []);
|
|
37
|
+
for (const item of arr) {
|
|
38
|
+
if (typeof item !== 'string') continue;
|
|
39
|
+
const eq = item.indexOf('=');
|
|
40
|
+
if (eq > 0) {
|
|
41
|
+
const key = item.substring(0, eq).trim();
|
|
42
|
+
const value = item.substring(eq + 1).trim();
|
|
43
|
+
if (key && value) map[key] = value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return map;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build full image ref from registry and app variables (registry/name:tag)
|
|
51
|
+
* @param {string} appName - keycloak or miso-controller
|
|
52
|
+
* @param {string} registry - Registry URL
|
|
53
|
+
* @returns {string} Full image reference
|
|
54
|
+
*/
|
|
55
|
+
function buildImageRefFromRegistry(appName, registry) {
|
|
56
|
+
const pathsUtil = require('../utils/paths');
|
|
57
|
+
const variablesPath = path.join(pathsUtil.getBuilderPath(appName), 'variables.yaml');
|
|
58
|
+
if (!fs.existsSync(variablesPath)) return undefined;
|
|
59
|
+
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
60
|
+
const variables = yaml.load(content);
|
|
61
|
+
const name = variables?.image?.name || variables?.app?.key || appName;
|
|
62
|
+
const tag = variables?.image?.tag || 'latest';
|
|
63
|
+
const base = (registry || '').replace(/\/+$/, '');
|
|
64
|
+
return base ? `${base}/${name}:${tag}` : undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set URL secrets and resolve keycloak + miso-controller (no force; existing .env preserved)
|
|
69
|
+
* @async
|
|
70
|
+
* @param {number} devIdNum - Developer ID number
|
|
71
|
+
*/
|
|
72
|
+
async function setMisoSecretsAndResolve(devIdNum) {
|
|
73
|
+
const keycloakPort = KEYCLOAK_BASE_PORT + (devIdNum === 0 ? 0 : devIdNum * 100);
|
|
74
|
+
const misoPort = MISO_BASE_PORT + (devIdNum === 0 ? 0 : devIdNum * 100);
|
|
75
|
+
await saveLocalSecret('keycloak-public-server-urlKeyVault', `http://localhost:${keycloakPort}`);
|
|
76
|
+
await saveLocalSecret('miso-controller-web-server-url', `http://localhost:${misoPort}`);
|
|
77
|
+
logger.log(chalk.green('✓ Set keycloak and miso-controller URL secrets'));
|
|
78
|
+
await secrets.generateEnvFile('keycloak', undefined, 'docker', false, true);
|
|
79
|
+
await secrets.generateEnvFile('miso-controller', undefined, 'docker', false, true);
|
|
80
|
+
logger.log(chalk.green('✓ Resolved keycloak and miso-controller'));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build run options and run keycloak then miso-controller
|
|
85
|
+
* @async
|
|
86
|
+
* @param {Object} options - Commander options (image, registry, registryMode)
|
|
87
|
+
*/
|
|
88
|
+
async function runMisoApps(options) {
|
|
89
|
+
const imageMap = parseImageOptions(options.image);
|
|
90
|
+
const keycloakImage = imageMap.keycloak || (options.registry ? buildImageRefFromRegistry('keycloak', options.registry) : undefined);
|
|
91
|
+
const misoImage = imageMap['miso-controller'] || (options.registry ? buildImageRefFromRegistry('miso-controller', options.registry) : undefined);
|
|
92
|
+
const keycloakRunOpts = { image: keycloakImage, registry: options.registry, registryMode: options.registryMode, skipEnvOutputPath: true };
|
|
93
|
+
const misoRunOpts = { image: misoImage, registry: options.registry, registryMode: options.registryMode, skipEnvOutputPath: true };
|
|
94
|
+
logger.log(chalk.blue('Starting keycloak...'));
|
|
95
|
+
await app.runApp('keycloak', keycloakRunOpts);
|
|
96
|
+
logger.log(chalk.blue('Starting miso-controller...'));
|
|
97
|
+
await app.runApp('miso-controller', misoRunOpts);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Handle up-miso command: ensure infra, ensure app dirs, set secrets, resolve (preserve existing .env), run keycloak then miso-controller.
|
|
102
|
+
*
|
|
103
|
+
* @async
|
|
104
|
+
* @function handleUpMiso
|
|
105
|
+
* @param {Object} options - Commander options
|
|
106
|
+
* @param {string} [options.registry] - Override registry for both apps
|
|
107
|
+
* @param {string} [options.registryMode] - Override registry mode (acr|external)
|
|
108
|
+
* @param {string[]|string} [options.image] - Override images e.g. keycloak=reg/k:v1 or miso-controller=reg/m:v1
|
|
109
|
+
* @returns {Promise<void>}
|
|
110
|
+
* @throws {Error} If infra not up or any step fails
|
|
111
|
+
*/
|
|
112
|
+
async function handleUpMiso(options = {}) {
|
|
113
|
+
const builderDir = await config.getAifabrixBuilderDir();
|
|
114
|
+
if (builderDir) {
|
|
115
|
+
process.env.AIFABRIX_BUILDER_DIR = builderDir;
|
|
116
|
+
}
|
|
117
|
+
logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller from images)...\n'));
|
|
118
|
+
const health = await infra.checkInfraHealth();
|
|
119
|
+
const allHealthy = Object.values(health).every(status => status === 'healthy');
|
|
120
|
+
if (!allHealthy) {
|
|
121
|
+
throw new Error('Infrastructure is not up. Run \'aifabrix up\' first.');
|
|
122
|
+
}
|
|
123
|
+
logger.log(chalk.green('✓ Infrastructure is up'));
|
|
124
|
+
await ensureAppFromTemplate('keycloak');
|
|
125
|
+
await ensureAppFromTemplate('miso-controller');
|
|
126
|
+
const developerId = await config.getDeveloperId();
|
|
127
|
+
const devIdNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
128
|
+
await setMisoSecretsAndResolve(devIdNum);
|
|
129
|
+
await runMisoApps(options);
|
|
130
|
+
logger.log(chalk.green('\n✓ up-miso complete. Keycloak and miso-controller are running.'));
|
|
131
|
+
logger.log(chalk.gray(' Run onboarding and register Keycloak from the miso-controller repo if needed.'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { handleUpMiso, parseImageOptions };
|
package/lib/core/config.js
CHANGED
|
@@ -14,16 +14,31 @@ const yaml = require('js-yaml');
|
|
|
14
14
|
const os = require('os');
|
|
15
15
|
const { encryptToken, decryptToken, isTokenEncrypted } = require('../utils/token-encryption');
|
|
16
16
|
// Avoid importing paths here to prevent circular dependency.
|
|
17
|
-
// Config location
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
// Config location (first match wins):
|
|
18
|
+
// 1. AIFABRIX_CONFIG env = full path to config.yaml
|
|
19
|
+
// 2. AIFABRIX_HOME env = directory containing config.yaml
|
|
20
|
+
// 3. ~/.aifabrix
|
|
21
|
+
// Set AIFABRIX_HOME=/workspace/.aifabrix or AIFABRIX_CONFIG=/workspace/.aifabrix/config.yaml when config is not in default home.
|
|
22
|
+
|
|
23
|
+
function getConfigDir() {
|
|
24
|
+
const configFile = process.env.AIFABRIX_CONFIG && typeof process.env.AIFABRIX_CONFIG === 'string';
|
|
25
|
+
if (configFile) {
|
|
26
|
+
return path.dirname(path.resolve(process.env.AIFABRIX_CONFIG.trim()));
|
|
27
|
+
}
|
|
28
|
+
if (process.env.AIFABRIX_HOME && typeof process.env.AIFABRIX_HOME === 'string') {
|
|
29
|
+
return path.resolve(process.env.AIFABRIX_HOME.trim());
|
|
30
|
+
}
|
|
31
|
+
return path.join(os.homedir(), '.aifabrix');
|
|
32
|
+
}
|
|
22
33
|
|
|
23
|
-
// Runtime config directory
|
|
24
|
-
const RUNTIME_CONFIG_DIR =
|
|
34
|
+
// Runtime config directory and file (respect AIFABRIX_HOME)
|
|
35
|
+
const RUNTIME_CONFIG_DIR = getConfigDir();
|
|
25
36
|
const RUNTIME_CONFIG_FILE = path.join(RUNTIME_CONFIG_DIR, 'config.yaml');
|
|
26
37
|
|
|
38
|
+
// Legacy exports (same as runtime when module loads)
|
|
39
|
+
const CONFIG_DIR = RUNTIME_CONFIG_DIR;
|
|
40
|
+
const CONFIG_FILE = RUNTIME_CONFIG_FILE;
|
|
41
|
+
|
|
27
42
|
// Cache for developer ID - loaded when getConfig() is first called
|
|
28
43
|
let cachedDeveloperId = null;
|
|
29
44
|
|
|
@@ -325,9 +340,17 @@ async function decryptTokenValue(value) {
|
|
|
325
340
|
if (!isTokenEncrypted(value)) return value;
|
|
326
341
|
const decrypted = decryptToken(value, encryptionKey);
|
|
327
342
|
// Ensure we never return undefined for valid inputs
|
|
328
|
-
|
|
343
|
+
if (decrypted !== undefined && decrypted !== null) {
|
|
344
|
+
return decrypted;
|
|
345
|
+
}
|
|
346
|
+
// Encrypted value but decryption produced nothing - do not return encrypted string to callers (e.g. refresh API)
|
|
347
|
+
throw new Error('Could not decrypt stored token. If you changed the secrets-encryption key, run "aifabrix login" again.');
|
|
329
348
|
} catch (error) {
|
|
330
|
-
|
|
349
|
+
if (error.message && error.message.includes('Could not decrypt stored token')) {
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
// Decryption failed (wrong key, corrupted data, etc.) - do not pass encrypted value to callers
|
|
353
|
+
throw new Error('Could not decrypt stored token. If you changed the secrets-encryption key, run "aifabrix login" again.');
|
|
331
354
|
}
|
|
332
355
|
}
|
|
333
356
|
// Token management functions moved to lib/utils/config-tokens.js to reduce file size
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker environment helpers for secrets/env generation.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Base docker env, config overrides, and PORT handling for container env
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const yaml = require('js-yaml');
|
|
11
|
+
const config = require('./config');
|
|
12
|
+
const { getEnvHosts } = require('../utils/env-endpoints');
|
|
13
|
+
const { getContainerPortFromPath } = require('../utils/port-resolver');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Gets base docker environment config
|
|
17
|
+
* @async
|
|
18
|
+
* @function getBaseDockerEnv
|
|
19
|
+
* @returns {Promise<Object>} Docker environment config
|
|
20
|
+
*/
|
|
21
|
+
async function getBaseDockerEnv() {
|
|
22
|
+
return await getEnvHosts('docker');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Applies config.yaml override to docker environment
|
|
27
|
+
* @function applyDockerEnvOverride
|
|
28
|
+
* @param {Object} dockerEnv - Base docker environment config
|
|
29
|
+
* @returns {Object} Updated docker environment config
|
|
30
|
+
*/
|
|
31
|
+
function applyDockerEnvOverride(dockerEnv) {
|
|
32
|
+
try {
|
|
33
|
+
const cfgPath = config.CONFIG_FILE;
|
|
34
|
+
if (fs.existsSync(cfgPath)) {
|
|
35
|
+
const cfgContent = fs.readFileSync(cfgPath, 'utf8');
|
|
36
|
+
const cfg = yaml.load(cfgContent) || {};
|
|
37
|
+
if (cfg && cfg.environments && cfg.environments.docker) {
|
|
38
|
+
return { ...dockerEnv, ...cfg.environments.docker };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Ignore config.yaml read errors, continue with env-config values
|
|
43
|
+
}
|
|
44
|
+
return dockerEnv;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Gets container port from docker environment config
|
|
49
|
+
* @function getContainerPortFromDockerEnv
|
|
50
|
+
* @param {Object} dockerEnv - Docker environment config
|
|
51
|
+
* @returns {number} Container port (defaults to 3000)
|
|
52
|
+
*/
|
|
53
|
+
function getContainerPortFromDockerEnv(dockerEnv) {
|
|
54
|
+
if (dockerEnv.PORT === undefined || dockerEnv.PORT === null) {
|
|
55
|
+
return 3000;
|
|
56
|
+
}
|
|
57
|
+
const portVal = typeof dockerEnv.PORT === 'number' ? dockerEnv.PORT : parseInt(dockerEnv.PORT, 10);
|
|
58
|
+
return Number.isNaN(portVal) ? 3000 : portVal;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Updates PORT in resolved content for docker environment
|
|
63
|
+
* Sets PORT to container port (build.containerPort or port from variables.yaml)
|
|
64
|
+
* NOT the host port (which includes developer-id offset)
|
|
65
|
+
* @async
|
|
66
|
+
* @function updatePortForDocker
|
|
67
|
+
* @param {string} resolved - Resolved environment content
|
|
68
|
+
* @param {string} variablesPath - Path to variables.yaml file
|
|
69
|
+
* @returns {Promise<string>} Updated content with PORT set
|
|
70
|
+
*/
|
|
71
|
+
async function updatePortForDocker(resolved, variablesPath) {
|
|
72
|
+
let dockerEnv = await getBaseDockerEnv();
|
|
73
|
+
dockerEnv = applyDockerEnvOverride(dockerEnv);
|
|
74
|
+
|
|
75
|
+
let containerPort = getContainerPortFromPath(variablesPath);
|
|
76
|
+
if (containerPort === null) {
|
|
77
|
+
containerPort = getContainerPortFromDockerEnv(dockerEnv);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return resolved.replace(/^PORT\s*=\s*.*$/m, `PORT=${containerPort}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
getBaseDockerEnv,
|
|
85
|
+
applyDockerEnvOverride,
|
|
86
|
+
getContainerPortFromDockerEnv,
|
|
87
|
+
updatePortForDocker
|
|
88
|
+
};
|