@aifabrix/builder 2.38.0 → 2.39.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 +3 -0
- package/integration/hubspot/hubspot-deploy.json +0 -3
- package/integration/hubspot/hubspot-system.json +0 -3
- package/lib/api/applications.api.js +8 -2
- package/lib/api/auth.api.js +14 -0
- package/lib/api/credentials.api.js +1 -1
- package/lib/api/datasources-core.api.js +16 -1
- package/lib/api/datasources-extended.api.js +18 -1
- package/lib/api/deployments.api.js +6 -1
- package/lib/api/environments.api.js +11 -0
- package/lib/api/external-systems.api.js +16 -1
- package/lib/api/pipeline.api.js +12 -4
- package/lib/api/service-users.api.js +41 -0
- package/lib/api/types/service-users.types.js +24 -0
- package/lib/api/wizard.api.js +19 -0
- package/lib/app/deploy.js +86 -21
- package/lib/app/rotate-secret.js +3 -1
- package/lib/app/run-helpers.js +7 -2
- package/lib/app/show-display.js +30 -11
- package/lib/app/show.js +34 -8
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-app.js +8 -0
- package/lib/cli/setup-infra.js +3 -3
- package/lib/cli/setup-service-user.js +52 -0
- package/lib/commands/app.js +2 -1
- package/lib/commands/service-user.js +193 -0
- package/lib/commands/up-common.js +74 -5
- package/lib/commands/up-dataplane.js +13 -7
- package/lib/commands/up-miso.js +17 -24
- package/lib/core/templates.js +0 -1
- package/lib/external-system/deploy.js +79 -15
- package/lib/generator/builders.js +0 -24
- package/lib/schema/application-schema.json +0 -12
- package/lib/schema/external-system.schema.json +0 -16
- package/lib/utils/app-register-config.js +10 -12
- package/lib/utils/deployment-errors.js +10 -0
- package/lib/utils/environment-checker.js +25 -6
- package/lib/utils/variable-transformer.js +6 -14
- package/package.json +1 -1
- package/templates/applications/dataplane/README.md +23 -7
- package/templates/applications/dataplane/env.template +31 -2
- package/templates/applications/dataplane/rbac.yaml +1 -1
- package/templates/applications/dataplane/variables.yaml +2 -1
- package/templates/applications/keycloak/env.template +6 -3
- package/templates/applications/keycloak/variables.yaml +1 -0
- package/templates/applications/miso-controller/env.template +22 -15
- package/templates/applications/miso-controller/rbac.yaml +15 -0
- package/templates/applications/miso-controller/variables.yaml +24 -23
package/lib/commands/up-miso.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI Fabrix Builder - Up Miso Command
|
|
3
3
|
*
|
|
4
|
-
* Installs keycloak
|
|
4
|
+
* Installs keycloak and miso-controller from images (no build). For dataplane, use up-dataplane.
|
|
5
5
|
* Assumes infra is up; sets dev secrets and resolves (no force; existing .env values preserved).
|
|
6
6
|
*
|
|
7
7
|
* @fileoverview up-miso command implementation
|
|
@@ -19,19 +19,16 @@ const secrets = require('../core/secrets');
|
|
|
19
19
|
const infra = require('../infrastructure');
|
|
20
20
|
const app = require('../app');
|
|
21
21
|
const { saveLocalSecret } = require('../utils/local-secrets');
|
|
22
|
-
const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly } = require('./up-common');
|
|
22
|
+
const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly, validateEnvOutputPathFolderOrNull } = require('./up-common');
|
|
23
23
|
|
|
24
24
|
/** Keycloak base port (from templates/applications/keycloak/variables.yaml) */
|
|
25
25
|
const KEYCLOAK_BASE_PORT = 8082;
|
|
26
26
|
/** Miso controller base port (dev-config app base) */
|
|
27
27
|
const MISO_BASE_PORT = 3000;
|
|
28
|
-
/** Dataplane base port (from templates/applications/dataplane/variables.yaml) */
|
|
29
|
-
const _DATAPLANE_BASE_PORT = 3001;
|
|
30
|
-
|
|
31
28
|
/**
|
|
32
|
-
* Parse --image options array into map { keycloak?: string, 'miso-controller'?: string
|
|
33
|
-
* @param {string[]|string} imageOpts - Option value(s) e.g. ['keycloak=reg/k:v1', 'miso-controller=reg/m:v1'
|
|
34
|
-
* @returns {{ keycloak?: string, 'miso-controller'?: string
|
|
29
|
+
* Parse --image options array into map { keycloak?: string, 'miso-controller'?: string }
|
|
30
|
+
* @param {string[]|string} imageOpts - Option value(s) e.g. ['keycloak=reg/k:v1', 'miso-controller=reg/m:v1']
|
|
31
|
+
* @returns {{ keycloak?: string, 'miso-controller'?: string }}
|
|
35
32
|
*/
|
|
36
33
|
function parseImageOptions(imageOpts) {
|
|
37
34
|
const map = {};
|
|
@@ -50,7 +47,7 @@ function parseImageOptions(imageOpts) {
|
|
|
50
47
|
|
|
51
48
|
/**
|
|
52
49
|
* Build full image ref from registry and app variables (registry/name:tag)
|
|
53
|
-
* @param {string} appName - keycloak
|
|
50
|
+
* @param {string} appName - keycloak or miso-controller
|
|
54
51
|
* @param {string} registry - Registry URL
|
|
55
52
|
* @returns {string} Full image reference
|
|
56
53
|
*/
|
|
@@ -67,7 +64,7 @@ function buildImageRefFromRegistry(appName, registry) {
|
|
|
67
64
|
}
|
|
68
65
|
|
|
69
66
|
/**
|
|
70
|
-
* Set URL secrets and resolve keycloak + miso-controller
|
|
67
|
+
* Set URL secrets and resolve keycloak + miso-controller (no force; existing .env preserved)
|
|
71
68
|
* @async
|
|
72
69
|
* @param {number} devIdNum - Developer ID number
|
|
73
70
|
*/
|
|
@@ -79,12 +76,11 @@ async function setMisoSecretsAndResolve(devIdNum) {
|
|
|
79
76
|
logger.log(chalk.green('✓ Set keycloak and miso-controller URL secrets'));
|
|
80
77
|
await secrets.generateEnvFile('keycloak', undefined, 'docker', false, true);
|
|
81
78
|
await secrets.generateEnvFile('miso-controller', undefined, 'docker', false, true);
|
|
82
|
-
|
|
83
|
-
logger.log(chalk.green('✓ Resolved keycloak, miso-controller, and dataplane'));
|
|
79
|
+
logger.log(chalk.green('✓ Resolved keycloak and miso-controller'));
|
|
84
80
|
}
|
|
85
81
|
|
|
86
82
|
/**
|
|
87
|
-
* Build run options and run keycloak, miso-controller
|
|
83
|
+
* Build run options and run keycloak, then miso-controller
|
|
88
84
|
* @async
|
|
89
85
|
* @param {Object} options - Commander options (image, registry, registryMode)
|
|
90
86
|
*/
|
|
@@ -92,27 +88,23 @@ async function runMisoApps(options) {
|
|
|
92
88
|
const imageMap = parseImageOptions(options.image);
|
|
93
89
|
const keycloakImage = imageMap.keycloak || (options.registry ? buildImageRefFromRegistry('keycloak', options.registry) : undefined);
|
|
94
90
|
const misoImage = imageMap['miso-controller'] || (options.registry ? buildImageRefFromRegistry('miso-controller', options.registry) : undefined);
|
|
95
|
-
const dataplaneImage = imageMap.dataplane || (options.registry ? buildImageRefFromRegistry('dataplane', options.registry) : undefined);
|
|
96
91
|
const keycloakRunOpts = { image: keycloakImage, registry: options.registry, registryMode: options.registryMode, skipEnvOutputPath: true, skipInfraCheck: true };
|
|
97
92
|
const misoRunOpts = { image: misoImage, registry: options.registry, registryMode: options.registryMode, skipEnvOutputPath: true, skipInfraCheck: true };
|
|
98
|
-
const dataplaneRunOpts = { image: dataplaneImage, registry: options.registry, registryMode: options.registryMode, skipEnvOutputPath: true, skipInfraCheck: true };
|
|
99
93
|
logger.log(chalk.blue('Starting keycloak...'));
|
|
100
94
|
await app.runApp('keycloak', keycloakRunOpts);
|
|
101
95
|
logger.log(chalk.blue('Starting miso-controller...'));
|
|
102
96
|
await app.runApp('miso-controller', misoRunOpts);
|
|
103
|
-
logger.log(chalk.blue('Starting dataplane...'));
|
|
104
|
-
await app.runApp('dataplane', dataplaneRunOpts);
|
|
105
97
|
}
|
|
106
98
|
|
|
107
99
|
/**
|
|
108
|
-
* Handle up-miso command: ensure infra, ensure app dirs, set secrets, resolve (preserve existing .env), run keycloak
|
|
100
|
+
* Handle up-miso command: ensure infra, ensure app dirs, set secrets, resolve (preserve existing .env), run keycloak and miso-controller.
|
|
109
101
|
*
|
|
110
102
|
* @async
|
|
111
103
|
* @function handleUpMiso
|
|
112
104
|
* @param {Object} options - Commander options
|
|
113
105
|
* @param {string} [options.registry] - Override registry for all apps
|
|
114
106
|
* @param {string} [options.registryMode] - Override registry mode (acr|external)
|
|
115
|
-
* @param {string[]|string} [options.image] - Override images e.g. keycloak=reg/k:v1, miso-controller=reg/m:v1
|
|
107
|
+
* @param {string[]|string} [options.image] - Override images e.g. keycloak=reg/k:v1, miso-controller=reg/m:v1
|
|
116
108
|
* @returns {Promise<void>}
|
|
117
109
|
* @throws {Error} If infra not up or any step fails
|
|
118
110
|
*/
|
|
@@ -121,7 +113,7 @@ async function handleUpMiso(options = {}) {
|
|
|
121
113
|
if (builderDir) {
|
|
122
114
|
process.env.AIFABRIX_BUILDER_DIR = builderDir;
|
|
123
115
|
}
|
|
124
|
-
logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller
|
|
116
|
+
logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller from images)...\n'));
|
|
125
117
|
// Strict: only this developer's infra (same as status), so up-miso and status agree
|
|
126
118
|
const health = await infra.checkInfraHealth(undefined, { strict: true });
|
|
127
119
|
const allHealthy = Object.values(health).every(status => status === 'healthy');
|
|
@@ -131,17 +123,18 @@ async function handleUpMiso(options = {}) {
|
|
|
131
123
|
logger.log(chalk.green('✓ Infrastructure is up'));
|
|
132
124
|
await ensureAppFromTemplate('keycloak');
|
|
133
125
|
await ensureAppFromTemplate('miso-controller');
|
|
134
|
-
|
|
126
|
+
// If envOutputPath target folder does not exist, set envOutputPath to null
|
|
127
|
+
validateEnvOutputPathFolderOrNull('keycloak');
|
|
128
|
+
validateEnvOutputPathFolderOrNull('miso-controller');
|
|
135
129
|
// Deploy-only: do not copy .env to repo paths; patch variables so envOutputPath is null
|
|
136
130
|
patchEnvOutputPathForDeployOnly('keycloak');
|
|
137
131
|
patchEnvOutputPathForDeployOnly('miso-controller');
|
|
138
|
-
patchEnvOutputPathForDeployOnly('dataplane');
|
|
139
132
|
const developerId = await config.getDeveloperId();
|
|
140
133
|
const devIdNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
141
134
|
await setMisoSecretsAndResolve(devIdNum);
|
|
142
135
|
await runMisoApps(options);
|
|
143
|
-
logger.log(chalk.green('\n✓ up-miso complete. Keycloak
|
|
144
|
-
chalk.gray('\n Run onboarding and register Keycloak from the miso-controller repo if needed.'));
|
|
136
|
+
logger.log(chalk.green('\n✓ up-miso complete. Keycloak and miso-controller are running.') +
|
|
137
|
+
chalk.gray('\n Run onboarding and register Keycloak from the miso-controller repo if needed. Use \'aifabrix up-dataplane\' for dataplane.'));
|
|
145
138
|
}
|
|
146
139
|
|
|
147
140
|
module.exports = { handleUpMiso, parseImageOptions };
|
package/lib/core/templates.js
CHANGED
|
@@ -13,10 +13,87 @@ const chalk = require('chalk');
|
|
|
13
13
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
14
14
|
const logger = require('../utils/logger');
|
|
15
15
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
16
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
17
|
+
const { getExternalSystem } = require('../api/external-systems.api');
|
|
16
18
|
const { generateControllerManifest } = require('../generator/external-controller-manifest');
|
|
17
19
|
const { validateExternalSystemComplete } = require('../validation/validate');
|
|
18
20
|
const { displayValidationResults } = require('../validation/validate-display');
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Displays API and MCP documentation URLs from dataplane when available
|
|
24
|
+
* @async
|
|
25
|
+
* @function displayDeploymentDocs
|
|
26
|
+
* @param {string} controllerUrl - Controller base URL
|
|
27
|
+
* @param {string} environment - Environment key
|
|
28
|
+
* @param {Object} authConfig - Authentication configuration
|
|
29
|
+
* @param {string} systemKey - External system key
|
|
30
|
+
*/
|
|
31
|
+
async function displayDeploymentDocs(controllerUrl, environment, authConfig, systemKey) {
|
|
32
|
+
try {
|
|
33
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
34
|
+
const res = await getExternalSystem(dataplaneUrl, systemKey, authConfig);
|
|
35
|
+
const sys = res?.data || res;
|
|
36
|
+
if (!sys) return;
|
|
37
|
+
|
|
38
|
+
const apiDocumentUrl = sys.apiDocumentUrl;
|
|
39
|
+
const mcpServerUrl = sys.mcpServerUrl;
|
|
40
|
+
const openApiDocsPageUrl = sys.openApiDocsPageUrl;
|
|
41
|
+
|
|
42
|
+
const urls = [];
|
|
43
|
+
if (apiDocumentUrl && typeof apiDocumentUrl === 'string') {
|
|
44
|
+
urls.push({ label: 'API Docs', url: apiDocumentUrl });
|
|
45
|
+
}
|
|
46
|
+
if (mcpServerUrl && typeof mcpServerUrl === 'string') {
|
|
47
|
+
urls.push({ label: 'MCP Server', url: mcpServerUrl });
|
|
48
|
+
}
|
|
49
|
+
if (openApiDocsPageUrl && typeof openApiDocsPageUrl === 'string') {
|
|
50
|
+
urls.push({ label: 'OpenAPI Docs Page', url: openApiDocsPageUrl });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (urls.length > 0) {
|
|
54
|
+
logger.log(chalk.blue('\nDocumentation:'));
|
|
55
|
+
urls.forEach(({ label, url }) => {
|
|
56
|
+
logger.log(chalk.blue(` ${label}: ${url}`));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
} catch (_err) {
|
|
60
|
+
// Silently ignore: dataplane may be unreachable or docs not configured
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Deploys via controller and displays success summary with docs
|
|
66
|
+
* @async
|
|
67
|
+
* @function executeDeployAndDisplay
|
|
68
|
+
* @param {Object} manifest - Controller manifest
|
|
69
|
+
* @param {string} controllerUrl - Controller base URL
|
|
70
|
+
* @param {string} environment - Environment key
|
|
71
|
+
* @param {Object} authConfig - Authentication configuration
|
|
72
|
+
* @param {Object} options - Deployment options
|
|
73
|
+
* @returns {Promise<Object>} Deployment result
|
|
74
|
+
*/
|
|
75
|
+
async function executeDeployAndDisplay(manifest, controllerUrl, environment, authConfig, options) {
|
|
76
|
+
const deployer = require('../deployment/deployer');
|
|
77
|
+
const pollOpts = {
|
|
78
|
+
poll: options.poll,
|
|
79
|
+
pollInterval: options.pollInterval !== undefined ? options.pollInterval : 500,
|
|
80
|
+
pollMaxAttempts: options.pollMaxAttempts,
|
|
81
|
+
...options
|
|
82
|
+
};
|
|
83
|
+
const result = await deployer.deployToController(
|
|
84
|
+
manifest,
|
|
85
|
+
controllerUrl,
|
|
86
|
+
environment,
|
|
87
|
+
authConfig,
|
|
88
|
+
pollOpts
|
|
89
|
+
);
|
|
90
|
+
logger.log(chalk.green('\n✅ External system deployed successfully!'));
|
|
91
|
+
logger.log(chalk.blue(`System: ${manifest.key}`));
|
|
92
|
+
logger.log(chalk.blue(`Datasources: ${manifest.dataSources.length}`));
|
|
93
|
+
await displayDeploymentDocs(controllerUrl, environment, authConfig, manifest.key);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
20
97
|
/**
|
|
21
98
|
* Prepares deployment configuration (auth, controller URL, environment)
|
|
22
99
|
* @async
|
|
@@ -75,26 +152,13 @@ async function deployExternalSystem(appName, options = {}) {
|
|
|
75
152
|
const { environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
|
|
76
153
|
|
|
77
154
|
// Step 3: Deploy via controller pipeline (same as regular apps)
|
|
78
|
-
|
|
79
|
-
const deployer = require('../deployment/deployer');
|
|
80
|
-
const result = await deployer.deployToController(
|
|
155
|
+
const result = await executeDeployAndDisplay(
|
|
81
156
|
manifest,
|
|
82
157
|
controllerUrl,
|
|
83
158
|
environment,
|
|
84
159
|
authConfig,
|
|
85
|
-
|
|
86
|
-
poll: options.poll,
|
|
87
|
-
pollInterval: options.pollInterval !== undefined ? options.pollInterval : 500,
|
|
88
|
-
pollMaxAttempts: options.pollMaxAttempts,
|
|
89
|
-
...options
|
|
90
|
-
}
|
|
160
|
+
options
|
|
91
161
|
);
|
|
92
|
-
|
|
93
|
-
// Display success summary
|
|
94
|
-
logger.log(chalk.green('\n✅ External system deployed successfully!'));
|
|
95
|
-
logger.log(chalk.blue(`System: ${manifest.key}`));
|
|
96
|
-
logger.log(chalk.blue(`Datasources: ${manifest.dataSources.length}`));
|
|
97
|
-
|
|
98
162
|
return result;
|
|
99
163
|
} catch (error) {
|
|
100
164
|
let message = `Failed to deploy external system: ${error.message}`;
|
|
@@ -262,25 +262,6 @@ function validateBuildFields(build) {
|
|
|
262
262
|
return Object.keys(buildConfig).length > 0 ? buildConfig : null;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
/**
|
|
266
|
-
* Validates and transforms deployment fields
|
|
267
|
-
* @function validateDeploymentFields
|
|
268
|
-
* @param {Object} deployment - Deployment configuration
|
|
269
|
-
* @returns {Object|null} Validated deployment config or null
|
|
270
|
-
*/
|
|
271
|
-
function validateDeploymentFields(deployment) {
|
|
272
|
-
if (!deployment) {
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const deploymentConfig = {};
|
|
277
|
-
if (deployment.controllerUrl && deployment.controllerUrl.trim() && deployment.controllerUrl.startsWith('https://')) {
|
|
278
|
-
deploymentConfig.controllerUrl = deployment.controllerUrl;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return Object.keys(deploymentConfig).length > 0 ? deploymentConfig : null;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
265
|
/**
|
|
285
266
|
* Adds optional fields to deployment manifest
|
|
286
267
|
* @function buildOptionalFields
|
|
@@ -339,11 +320,6 @@ function addValidatedConfigSections(deployment, variables) {
|
|
|
339
320
|
if (build) {
|
|
340
321
|
deployment.build = build;
|
|
341
322
|
}
|
|
342
|
-
|
|
343
|
-
const deploymentConfig = validateDeploymentFields(variables.deployment);
|
|
344
|
-
if (deploymentConfig) {
|
|
345
|
-
deployment.deployment = deploymentConfig;
|
|
346
|
-
}
|
|
347
323
|
}
|
|
348
324
|
|
|
349
325
|
/**
|
|
@@ -772,18 +772,6 @@
|
|
|
772
772
|
},
|
|
773
773
|
"additionalProperties": false
|
|
774
774
|
},
|
|
775
|
-
"deployment": {
|
|
776
|
-
"type": "object",
|
|
777
|
-
"description": "Deployment configuration for pipeline API",
|
|
778
|
-
"properties": {
|
|
779
|
-
"controllerUrl": {
|
|
780
|
-
"type": "string",
|
|
781
|
-
"description": "Controller API URL for deployment",
|
|
782
|
-
"pattern": "^https://.*$"
|
|
783
|
-
}
|
|
784
|
-
},
|
|
785
|
-
"additionalProperties": false
|
|
786
|
-
},
|
|
787
775
|
"system": {
|
|
788
776
|
"type": "object",
|
|
789
777
|
"description": "Optional: Inline external system configuration for atomic deployment. Uses external-system.schema.json structure via $ref. Alternative to externalIntegration.systems file-based approach.",
|
|
@@ -102,22 +102,6 @@
|
|
|
102
102
|
"type": "boolean",
|
|
103
103
|
"default": true
|
|
104
104
|
},
|
|
105
|
-
"environment": {
|
|
106
|
-
"type": "object",
|
|
107
|
-
"description": "Environment-level configuration values used by dataplane and external data sources.",
|
|
108
|
-
"properties": {
|
|
109
|
-
"baseUrl": {
|
|
110
|
-
"type": "string",
|
|
111
|
-
"description": "Base API URL or MCP server URL",
|
|
112
|
-
"pattern": "^(http|https)://.*$"
|
|
113
|
-
},
|
|
114
|
-
"region": {
|
|
115
|
-
"type": "string",
|
|
116
|
-
"description": "Optional region setting for API routing"
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
"additionalProperties": true
|
|
120
|
-
},
|
|
121
105
|
"authentication": {
|
|
122
106
|
"type": "object",
|
|
123
107
|
"description": "Authentication configuration for external system",
|
|
@@ -112,19 +112,14 @@ function resolveSystemFilePath(appPath, schemaBasePath, systemFileName) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
115
|
+
* Base URL is no longer read from manifest; Controller resolves from configuration at deploy time.
|
|
116
116
|
* @function extractUrlFromSystemJson
|
|
117
|
-
* @param {Object}
|
|
118
|
-
* @param {string}
|
|
119
|
-
* @returns {
|
|
120
|
-
* @throws {Error} If URL is missing
|
|
117
|
+
* @param {Object} _systemJson - System JSON object (unused; kept for call-site compatibility)
|
|
118
|
+
* @param {string} _systemFileName - System file name (unused)
|
|
119
|
+
* @returns {undefined} URL is not supplied from manifest
|
|
121
120
|
*/
|
|
122
|
-
function extractUrlFromSystemJson(
|
|
123
|
-
|
|
124
|
-
if (!url) {
|
|
125
|
-
throw new Error(`Missing environment.baseUrl in ${systemFileName}`);
|
|
126
|
-
}
|
|
127
|
-
return url;
|
|
121
|
+
function extractUrlFromSystemJson(_systemJson, _systemFileName) {
|
|
122
|
+
return undefined;
|
|
128
123
|
}
|
|
129
124
|
|
|
130
125
|
/**
|
|
@@ -209,7 +204,10 @@ async function extractExternalIntegrationUrl(appKey, externalIntegration) {
|
|
|
209
204
|
*/
|
|
210
205
|
async function extractExternalAppConfiguration(appKey, variables, appKeyFromFile, displayName, description) {
|
|
211
206
|
const { url, apiKey } = await extractExternalIntegrationUrl(appKey, variables.externalIntegration);
|
|
212
|
-
const externalIntegration = {
|
|
207
|
+
const externalIntegration = {};
|
|
208
|
+
if (url !== undefined && url !== null) {
|
|
209
|
+
externalIntegration.url = url;
|
|
210
|
+
}
|
|
213
211
|
if (apiKey) {
|
|
214
212
|
externalIntegration.apiKey = apiKey;
|
|
215
213
|
}
|
|
@@ -206,6 +206,16 @@ async function handleDeploymentErrors(error, appName, url, alreadyLogged = false
|
|
|
206
206
|
// Extract error message
|
|
207
207
|
const errorMessage = extractErrorMessage(error);
|
|
208
208
|
|
|
209
|
+
// Deployment failed/cancelled (from deploy.js): show message as-is, do not treat as HTTP error
|
|
210
|
+
if (errorMessage.startsWith('Deployment failed:') || errorMessage.startsWith('Deployment cancelled:')) {
|
|
211
|
+
if (!alreadyLogged) {
|
|
212
|
+
await logDeploymentFailure(appName, url, error);
|
|
213
|
+
}
|
|
214
|
+
const err = new Error(errorMessage);
|
|
215
|
+
err.formatted = (error.formatted || errorMessage).trim();
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
|
|
209
219
|
// For validation errors (like URL validation), just re-throw them directly
|
|
210
220
|
if (isValidationError(errorMessage)) {
|
|
211
221
|
throwValidationError(error, errorMessage);
|
|
@@ -13,6 +13,8 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const dockerUtils = require('./docker');
|
|
15
15
|
const { getActualSecretsPath } = require('./secrets-path');
|
|
16
|
+
const config = require('../core/config');
|
|
17
|
+
const devConfig = require('./dev-config');
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Checks if Docker is installed and available
|
|
@@ -33,17 +35,28 @@ async function checkDocker() {
|
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Checks if required ports are available
|
|
38
|
+
* Uses developer-specific ports when developer-id greater than 0 (basePort + developerId * 100)
|
|
36
39
|
*
|
|
37
40
|
* @async
|
|
38
41
|
* @function checkPorts
|
|
42
|
+
* @param {number[]} [requiredPorts] - Ports to check. If omitted, uses ports from config developer-id
|
|
39
43
|
* @returns {Promise<string>} 'ok' if all ports are available, 'warning' otherwise
|
|
40
44
|
*/
|
|
41
|
-
async function checkPorts() {
|
|
42
|
-
const requiredPorts = [5432, 6379, 5050, 8081];
|
|
45
|
+
async function checkPorts(requiredPorts) {
|
|
43
46
|
const netstat = require('net');
|
|
47
|
+
let portsToCheck = requiredPorts;
|
|
48
|
+
|
|
49
|
+
if (!portsToCheck || portsToCheck.length === 0) {
|
|
50
|
+
const devId = await config.getDeveloperId();
|
|
51
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
52
|
+
const id = Number.isNaN(idNum) || idNum < 0 ? 0 : idNum;
|
|
53
|
+
const ports = devConfig.getDevPorts(id);
|
|
54
|
+
portsToCheck = [ports.postgres, ports.redis, ports.pgadmin, ports.redisCommander];
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
let portIssues = 0;
|
|
45
58
|
|
|
46
|
-
for (const port of
|
|
59
|
+
for (const port of portsToCheck) {
|
|
47
60
|
try {
|
|
48
61
|
await new Promise((resolve, reject) => {
|
|
49
62
|
const server = netstat.createServer();
|
|
@@ -128,10 +141,16 @@ async function checkEnvironment() {
|
|
|
128
141
|
result.recommendations.push('Install Docker and Docker Compose');
|
|
129
142
|
}
|
|
130
143
|
|
|
131
|
-
// Check ports
|
|
132
|
-
|
|
144
|
+
// Check ports (developer-specific: dev 0 = base ports, dev N = base + N*100)
|
|
145
|
+
const devId = await config.getDeveloperId();
|
|
146
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
147
|
+
const id = Number.isNaN(idNum) || idNum < 0 ? 0 : idNum;
|
|
148
|
+
const ports = devConfig.getDevPorts(id);
|
|
149
|
+
const requiredPorts = [ports.postgres, ports.redis, ports.pgadmin, ports.redisCommander];
|
|
150
|
+
|
|
151
|
+
result.ports = await checkPorts(requiredPorts);
|
|
133
152
|
if (result.ports === 'warning') {
|
|
134
|
-
result.recommendations.push(
|
|
153
|
+
result.recommendations.push(`Some required ports (${requiredPorts.join(', ')}) are in use`);
|
|
135
154
|
}
|
|
136
155
|
|
|
137
156
|
// Check secrets
|
|
@@ -198,22 +198,14 @@ function validateBuildConfig(build) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
|
-
* Validates and transforms deployment configuration
|
|
201
|
+
* Validates and transforms deployment configuration.
|
|
202
|
+
* Manifest is generic: deployment URL/env are resolved outside the manifest (user/config).
|
|
202
203
|
* @function validateDeploymentConfig
|
|
203
|
-
* @param {Object}
|
|
204
|
-
* @returns {
|
|
204
|
+
* @param {Object} _deployment - Deployment configuration (unused; manifest is generic)
|
|
205
|
+
* @returns {null} No deployment block emitted from variables
|
|
205
206
|
*/
|
|
206
|
-
function validateDeploymentConfig(
|
|
207
|
-
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const deploymentConfig = {};
|
|
212
|
-
if (deployment.controllerUrl && deployment.controllerUrl.trim() !== '' && /^https:\/\/.*$/.test(deployment.controllerUrl)) {
|
|
213
|
-
deploymentConfig.controllerUrl = deployment.controllerUrl;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return Object.keys(deploymentConfig).length > 0 ? deploymentConfig : null;
|
|
207
|
+
function validateDeploymentConfig(_deployment) {
|
|
208
|
+
return null;
|
|
217
209
|
}
|
|
218
210
|
|
|
219
211
|
/**
|
package/package.json
CHANGED
|
@@ -18,17 +18,27 @@ npm install -g @aifabrix/builder
|
|
|
18
18
|
# Check your environment
|
|
19
19
|
aifabrix doctor
|
|
20
20
|
|
|
21
|
-
# Login to controller (change your own port)
|
|
22
|
-
aifabrix login
|
|
21
|
+
# Login to controller (debug mode -c http://localhost:3010 - change your own port)
|
|
22
|
+
aifabrix login
|
|
23
23
|
|
|
24
24
|
# Register your application (gets you credentials automatically)
|
|
25
25
|
aifabrix app register dataplane
|
|
26
|
+
|
|
27
|
+
# Rotate credentials if needed:
|
|
28
|
+
aifabrix app rotate-secret dataplane
|
|
29
|
+
|
|
30
|
+
# Run locally
|
|
31
|
+
aifabrix run dataplane
|
|
32
|
+
|
|
33
|
+
# Deploy to miso-controller
|
|
34
|
+
aifabrix deploy dataplane
|
|
35
|
+
|
|
26
36
|
```
|
|
27
37
|
|
|
28
38
|
### 3. Build & Run Locally
|
|
29
39
|
|
|
30
40
|
```bash
|
|
31
|
-
# Build the Docker image
|
|
41
|
+
# Build the Docker image (latest)
|
|
32
42
|
aifabrix build dataplane
|
|
33
43
|
|
|
34
44
|
# Run locally
|
|
@@ -41,7 +51,7 @@ aifabrix run dataplane
|
|
|
41
51
|
|
|
42
52
|
## Testing dataplane (use DATAPLANE_TEST_GUIDE)
|
|
43
53
|
|
|
44
|
-
**Use the builder
|
|
54
|
+
**Use the builder’s Dataplane Test Guide** for auth, health, wizard, external systems, and pipeline checks:
|
|
45
55
|
|
|
46
56
|
- **In aifabrix-builder:** `integration/hubspot/DATAPLANE_TEST_GUIDE.md`
|
|
47
57
|
- **Dataplane base URL:** `http://localhost:3111`
|
|
@@ -53,13 +63,13 @@ Keep `build.localPort` in `variables.yaml` at **3111** so it matches that guide.
|
|
|
53
63
|
**View logs:**
|
|
54
64
|
|
|
55
65
|
```bash
|
|
56
|
-
docker logs aifabrix-dataplane -f
|
|
66
|
+
docker logs aifabrix-dev06-dataplane -f
|
|
57
67
|
```
|
|
58
68
|
|
|
59
69
|
**Stop:**
|
|
60
70
|
|
|
61
71
|
```bash
|
|
62
|
-
docker stop aifabrix-dataplane
|
|
72
|
+
docker stop aifabrix-dev06-dataplane
|
|
63
73
|
```
|
|
64
74
|
|
|
65
75
|
### 4. Deploy to Azure
|
|
@@ -73,6 +83,7 @@ aifabrix push dataplane --registry myacr.azurecr.io --tag "v1.0.0,latest"
|
|
|
73
83
|
|
|
74
84
|
# Deploy to miso-controller
|
|
75
85
|
aifabrix deploy dataplane
|
|
86
|
+
|
|
76
87
|
```
|
|
77
88
|
|
|
78
89
|
---
|
|
@@ -103,7 +114,8 @@ aifabrix dockerfile dataplane --force # Generate Dockerfile
|
|
|
103
114
|
aifabrix resolve dataplane # Generate .env file
|
|
104
115
|
|
|
105
116
|
# Deployment
|
|
106
|
-
aifabrix json dataplane #
|
|
117
|
+
aifabrix json dataplane # Preview deployment JSON
|
|
118
|
+
aifabrix genkey dataplane # Generate deployment key
|
|
107
119
|
aifabrix push dataplane --registry myacr.azurecr.io # Push to ACR
|
|
108
120
|
aifabrix deploy dataplane --controller <url> # Deploy to Azure
|
|
109
121
|
|
|
@@ -167,6 +179,8 @@ export AIFABRIX_HOME=/custom/path
|
|
|
167
179
|
export AIFABRIX_SECRETS=/path/to/secrets.yaml
|
|
168
180
|
```
|
|
169
181
|
|
|
182
|
+
**Default OAuth callback URL:** Set `DATAPLANE_WEB_SERVER_URL` (e.g. in `env.template` as `http://localhost:${PORT}`) so the dataplane can build the default OAuth2 callback URL when `redirectUri` is omitted. The callback URL is `{DATAPLANE_WEB_SERVER_URL}/auth/callback`. When you change the domain (e.g. from localhost to a production URL), update this single variable and register the same callback URL in your OAuth app (e.g. HubSpot).
|
|
183
|
+
|
|
170
184
|
---
|
|
171
185
|
|
|
172
186
|
## Troubleshooting
|
|
@@ -177,12 +191,14 @@ export AIFABRIX_SECRETS=/path/to/secrets.yaml
|
|
|
177
191
|
- **"Authentication failed"** → Run `aifabrix login` again
|
|
178
192
|
- **"Build fails"** → Check Docker is running and `variables.yaml` → `build.secrets` path is correct
|
|
179
193
|
- **"Can't connect"** → Verify infrastructure is running and PostgreSQL is accessible
|
|
194
|
+
- **Wizard / API 401 after `rotate-secret`** → The wizard may write `.env` to a different path (e.g. `../../.env`). Ensure the **project root** `.env` has the new `MISO_CLIENTID` and `MISO_CLIENTSECRET` (copy from the rotate-secret output or run `make resolve`), then **restart the backend** (`make dev` or restart the process) so it loads the new credentials.
|
|
180
195
|
|
|
181
196
|
**Regenerate files:**
|
|
182
197
|
|
|
183
198
|
```bash
|
|
184
199
|
aifabrix resolve dataplane --force
|
|
185
200
|
aifabrix json dataplane
|
|
201
|
+
aifabrix genkey dataplane
|
|
186
202
|
```
|
|
187
203
|
|
|
188
204
|
---
|