@aifabrix/builder 2.32.3 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +8 -7
- package/integration/hubspot/companies.json +2048 -0
- package/integration/hubspot/create-hubspot.js +665 -0
- package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
- package/integration/hubspot/hubspot-deploy.json +832 -81
- package/integration/hubspot/hubspot-system.json +99 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
- package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
- package/integration/hubspot/test-dataplane-down-tests.js +419 -0
- package/integration/hubspot/test-dataplane-down.js +157 -0
- package/integration/hubspot/test.js +1517 -0
- package/integration/hubspot/variables.yaml +4 -4
- package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
- package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
- package/lib/api/applications.api.js +1 -0
- package/lib/api/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +161 -23
- package/lib/app/deploy.js +116 -54
- package/lib/app/display.js +6 -5
- package/lib/app/dockerfile.js +2 -1
- package/lib/app/list.js +17 -10
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +48 -31
- package/lib/cli.js +219 -70
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +7 -8
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +26 -17
- package/lib/commands/login.js +12 -10
- package/lib/commands/wizard-config-normalizer.js +92 -0
- package/lib/commands/wizard-core.js +515 -0
- package/lib/commands/wizard-dataplane.js +122 -0
- package/lib/commands/wizard-headless.js +115 -0
- package/lib/commands/wizard.js +110 -332
- package/lib/core/config.js +46 -0
- package/lib/core/secrets.js +3 -22
- package/lib/core/templates-env.js +1 -1
- package/lib/datasource/deploy.js +29 -21
- package/lib/datasource/list.js +8 -6
- package/lib/deployment/deployer.js +25 -0
- package/lib/deployment/environment.js +10 -13
- package/lib/external-system/delete.js +151 -0
- package/lib/external-system/deploy.js +53 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +33 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +4 -3
- package/lib/generator/builders.js +3 -1
- package/lib/generator/external-controller-manifest.js +157 -0
- package/lib/generator/external-schema-utils.js +236 -0
- package/lib/generator/external.js +55 -3
- package/lib/generator/index.js +22 -10
- package/lib/generator/wizard-prompts.js +33 -10
- package/lib/generator/wizard.js +69 -86
- package/lib/infrastructure/compose.js +100 -0
- package/lib/infrastructure/helpers.js +139 -0
- package/lib/infrastructure/index.js +52 -311
- package/lib/infrastructure/services.js +168 -0
- package/lib/schema/application-schema.json +23 -4
- package/lib/schema/external-datasource.schema.json +2 -2
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +32 -50
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +65 -17
- package/lib/utils/dataplane-health.js +115 -0
- package/lib/utils/dataplane-resolver.js +29 -0
- package/lib/utils/dev-config.js +6 -2
- package/lib/utils/env-copy.js +2 -1
- package/lib/utils/env-ports.js +2 -1
- package/lib/utils/env-template.js +1 -1
- package/lib/utils/error-formatter.js +49 -0
- package/lib/utils/external-readme.js +125 -0
- package/lib/utils/help-builder.js +190 -0
- package/lib/utils/infra-status.js +13 -3
- package/lib/utils/paths.js +17 -2
- package/lib/utils/port-resolver.js +111 -0
- package/lib/utils/secrets-helpers.js +3 -15
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/token-manager.js +9 -4
- package/lib/utils/variable-transformer.js +7 -2
- package/lib/validation/external-manifest-validator.js +202 -0
- package/lib/validation/validate-display.js +406 -0
- package/lib/validation/validate.js +159 -123
- package/lib/validation/validator.js +36 -3
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +18 -16
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +7 -7
- package/templates/external-system/README.md.hbs +99 -0
- package/templates/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- package/templates/typescript/docker-compose.hbs +26 -0
package/lib/core/secrets.js
CHANGED
|
@@ -44,6 +44,7 @@ const {
|
|
|
44
44
|
} = require('../utils/secrets-utils');
|
|
45
45
|
const { decryptSecret, isEncrypted } = require('../utils/secrets-encryption');
|
|
46
46
|
const pathsUtil = require('../utils/paths');
|
|
47
|
+
const { getContainerPortFromPath } = require('../utils/port-resolver');
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Generates a canonical secret name from an environment variable key.
|
|
@@ -253,26 +254,6 @@ function applyDockerEnvOverride(dockerEnv) {
|
|
|
253
254
|
return dockerEnv;
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
/**
|
|
257
|
-
* Gets container port from variables.yaml
|
|
258
|
-
* @function getContainerPortFromVariables
|
|
259
|
-
* @param {string} variablesPath - Path to variables.yaml
|
|
260
|
-
* @returns {number|null} Container port or null
|
|
261
|
-
*/
|
|
262
|
-
function getContainerPortFromVariables(variablesPath) {
|
|
263
|
-
if (!variablesPath || !fs.existsSync(variablesPath)) {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
const variablesContent = fs.readFileSync(variablesPath, 'utf8');
|
|
268
|
-
const variables = yaml.load(variablesContent);
|
|
269
|
-
// Use containerPort if specified, otherwise use base port (no developer-id offset)
|
|
270
|
-
return variables?.build?.containerPort || variables?.port || null;
|
|
271
|
-
} catch {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
257
|
/**
|
|
277
258
|
* Gets container port from docker environment config
|
|
278
259
|
* @function getContainerPortFromDockerEnv
|
|
@@ -305,7 +286,7 @@ async function updatePortForDocker(resolved, variablesPath) {
|
|
|
305
286
|
dockerEnv = applyDockerEnvOverride(dockerEnv);
|
|
306
287
|
|
|
307
288
|
// Step 3: Get PORT value for container (should be container port, NOT host port)
|
|
308
|
-
let containerPort =
|
|
289
|
+
let containerPort = getContainerPortFromPath(variablesPath);
|
|
309
290
|
if (containerPort === null) {
|
|
310
291
|
containerPort = getContainerPortFromDockerEnv(dockerEnv);
|
|
311
292
|
}
|
|
@@ -454,7 +435,7 @@ async function generateAdminSecretsEnv(secretsPath) {
|
|
|
454
435
|
|
|
455
436
|
const adminSecrets = `# Infrastructure Admin Credentials
|
|
456
437
|
POSTGRES_PASSWORD=${postgresPassword}
|
|
457
|
-
PGADMIN_DEFAULT_EMAIL=admin@aifabrix.
|
|
438
|
+
PGADMIN_DEFAULT_EMAIL=admin@aifabrix.dev
|
|
458
439
|
PGADMIN_DEFAULT_PASSWORD=${postgresPassword}
|
|
459
440
|
REDIS_HOST=local:redis:6379:0:
|
|
460
441
|
REDIS_COMMANDER_USER=admin
|
|
@@ -104,7 +104,7 @@ function buildMonitoringEnv(config) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
return {
|
|
107
|
-
'MISO_CONTROLLER_URL': config.controllerUrl || 'https://controller.aifabrix.
|
|
107
|
+
'MISO_CONTROLLER_URL': config.controllerUrl || 'https://controller.aifabrix.dev',
|
|
108
108
|
'MISO_ENVIRONMENT': 'dev',
|
|
109
109
|
'MISO_CLIENTID': 'kv://miso-controller-client-idKeyVault',
|
|
110
110
|
'MISO_CLIENTSECRET': 'kv://miso-controller-client-secretKeyVault',
|
package/lib/datasource/deploy.js
CHANGED
|
@@ -48,9 +48,10 @@ async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
|
|
|
48
48
|
application.configuration?.dataplaneUrl;
|
|
49
49
|
|
|
50
50
|
if (!dataplaneUrl) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const appType = application.configuration?.type || application.type;
|
|
52
|
+
if (appType === 'external') {
|
|
53
|
+
throw new Error('Dataplane URL not found for external system. Provide --dataplane <url>.');
|
|
54
|
+
}
|
|
54
55
|
throw new Error('Dataplane URL not found in application configuration');
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -64,19 +65,13 @@ async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
|
|
|
64
65
|
* @param {Object} options - Options
|
|
65
66
|
* @throws {Error} If validation fails
|
|
66
67
|
*/
|
|
67
|
-
function validateDeploymentInputs(appKey, filePath
|
|
68
|
+
function validateDeploymentInputs(appKey, filePath) {
|
|
68
69
|
if (!appKey || typeof appKey !== 'string') {
|
|
69
70
|
throw new Error('Application key is required');
|
|
70
71
|
}
|
|
71
72
|
if (!filePath || typeof filePath !== 'string') {
|
|
72
73
|
throw new Error('File path is required');
|
|
73
74
|
}
|
|
74
|
-
if (!options.controller) {
|
|
75
|
-
throw new Error('Controller URL is required (--controller)');
|
|
76
|
-
}
|
|
77
|
-
if (!options.environment) {
|
|
78
|
-
throw new Error('Environment is required (-e, --environment)');
|
|
79
|
-
}
|
|
80
75
|
}
|
|
81
76
|
|
|
82
77
|
/**
|
|
@@ -112,15 +107,18 @@ async function validateAndLoadDatasourceFile(filePath) {
|
|
|
112
107
|
* @param {string} controllerUrl - Controller URL
|
|
113
108
|
* @param {string} environment - Environment key
|
|
114
109
|
* @param {string} appKey - Application key
|
|
110
|
+
* @param {Object} [options] - Options
|
|
111
|
+
* @param {string} [options.dataplane] - Dataplane URL override
|
|
115
112
|
* @returns {Promise<Object>} Object with authConfig and dataplaneUrl
|
|
116
113
|
*/
|
|
117
114
|
async function setupDeploymentAuth(controllerUrl, environment, appKey) {
|
|
115
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
118
116
|
logger.log(chalk.blue('🔐 Getting authentication...'));
|
|
119
117
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, appKey);
|
|
120
118
|
logger.log(chalk.green('✓ Authentication successful'));
|
|
121
119
|
|
|
122
|
-
logger.log(chalk.blue('🌐
|
|
123
|
-
const dataplaneUrl = await
|
|
120
|
+
logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
|
|
121
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
124
122
|
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
125
123
|
|
|
126
124
|
return { authConfig, dataplaneUrl };
|
|
@@ -165,20 +163,30 @@ function displayDeploymentResults(datasourceConfig, systemKey, environment) {
|
|
|
165
163
|
}
|
|
166
164
|
|
|
167
165
|
/**
|
|
168
|
-
* Deploys datasource to dataplane
|
|
166
|
+
* Deploys datasource to dataplane.
|
|
167
|
+
* Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
|
|
169
168
|
*
|
|
170
169
|
* @async
|
|
171
170
|
* @function deployDatasource
|
|
172
171
|
* @param {string} appKey - Application key
|
|
173
172
|
* @param {string} filePath - Path to datasource JSON file
|
|
174
|
-
* @param {Object}
|
|
175
|
-
* @param {string} options.controller - Controller URL
|
|
176
|
-
* @param {string} options.environment - Environment key
|
|
173
|
+
* @param {Object} [_options] - Deployment options (reserved)
|
|
177
174
|
* @returns {Promise<Object>} Deployment result
|
|
178
175
|
* @throws {Error} If deployment fails
|
|
179
176
|
*/
|
|
180
|
-
async function deployDatasource(appKey, filePath,
|
|
181
|
-
|
|
177
|
+
async function deployDatasource(appKey, filePath, _options) {
|
|
178
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
179
|
+
const { resolveEnvironment } = require('../core/config');
|
|
180
|
+
const { displayCommandHeader } = require('../utils/command-header');
|
|
181
|
+
|
|
182
|
+
validateDeploymentInputs(appKey, filePath);
|
|
183
|
+
|
|
184
|
+
// Resolve controller and environment from config
|
|
185
|
+
const controllerUrl = await resolveControllerUrl();
|
|
186
|
+
const environment = await resolveEnvironment();
|
|
187
|
+
|
|
188
|
+
// Display command header
|
|
189
|
+
displayCommandHeader(controllerUrl, environment);
|
|
182
190
|
|
|
183
191
|
logger.log(chalk.blue('📋 Deploying datasource...\n'));
|
|
184
192
|
|
|
@@ -192,19 +200,19 @@ async function deployDatasource(appKey, filePath, options) {
|
|
|
192
200
|
}
|
|
193
201
|
|
|
194
202
|
// Setup authentication and get dataplane URL
|
|
195
|
-
const { authConfig, dataplaneUrl } = await setupDeploymentAuth(
|
|
203
|
+
const { authConfig, dataplaneUrl } = await setupDeploymentAuth(controllerUrl, environment, appKey);
|
|
196
204
|
|
|
197
205
|
// Publish to dataplane
|
|
198
206
|
await publishDatasourceToDataplane(dataplaneUrl, systemKey, authConfig, datasourceConfig);
|
|
199
207
|
|
|
200
208
|
// Display results
|
|
201
|
-
displayDeploymentResults(datasourceConfig, systemKey,
|
|
209
|
+
displayDeploymentResults(datasourceConfig, systemKey, environment);
|
|
202
210
|
|
|
203
211
|
return {
|
|
204
212
|
success: true,
|
|
205
213
|
datasourceKey: datasourceConfig.key,
|
|
206
214
|
systemKey: systemKey,
|
|
207
|
-
environment:
|
|
215
|
+
environment: environment,
|
|
208
216
|
dataplaneUrl: dataplaneUrl
|
|
209
217
|
};
|
|
210
218
|
}
|
package/lib/datasource/list.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const { getConfig } = require('../core/config');
|
|
12
|
+
const { getConfig, resolveEnvironment } = require('../core/config');
|
|
13
13
|
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
14
14
|
const { listEnvironmentDatasources } = require('../api/environments.api');
|
|
15
15
|
const { formatApiError } = require('../utils/api-error-handler');
|
|
@@ -130,8 +130,7 @@ function displayDatasources(datasources, environment) {
|
|
|
130
130
|
*
|
|
131
131
|
* @async
|
|
132
132
|
* @function listDatasources
|
|
133
|
-
* @param {Object}
|
|
134
|
-
* @param {string} options.environment - Environment ID or key
|
|
133
|
+
* @param {Object} _options - Command options (unused, kept for compatibility)
|
|
135
134
|
* @throws {Error} If listing fails
|
|
136
135
|
*/
|
|
137
136
|
/**
|
|
@@ -190,9 +189,12 @@ function handleDatasourceApiError(response) {
|
|
|
190
189
|
process.exit(1);
|
|
191
190
|
}
|
|
192
191
|
|
|
193
|
-
async function listDatasources(
|
|
192
|
+
async function listDatasources(_options) {
|
|
194
193
|
const config = await getConfig();
|
|
195
194
|
|
|
195
|
+
// Resolve environment from config.yaml (no flags)
|
|
196
|
+
const environment = await resolveEnvironment();
|
|
197
|
+
|
|
196
198
|
// Try to get device token
|
|
197
199
|
const authInfo = await getDeviceTokenFromConfig(config);
|
|
198
200
|
validateDatasourceListingAuth(authInfo?.token, authInfo?.controllerUrl);
|
|
@@ -204,7 +206,7 @@ async function listDatasources(options) {
|
|
|
204
206
|
return; // Never reached, but satisfies linter
|
|
205
207
|
}
|
|
206
208
|
const authConfig = { type: 'bearer', token: authInfo.token };
|
|
207
|
-
const response = await listEnvironmentDatasources(authInfo.controllerUrl,
|
|
209
|
+
const response = await listEnvironmentDatasources(authInfo.controllerUrl, environment, authConfig);
|
|
208
210
|
|
|
209
211
|
if (!response.success || !response.data) {
|
|
210
212
|
handleDatasourceApiError(response);
|
|
@@ -212,7 +214,7 @@ async function listDatasources(options) {
|
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
const datasources = extractDatasources(response);
|
|
215
|
-
displayDatasources(datasources,
|
|
217
|
+
displayDatasources(datasources, environment);
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
module.exports = {
|
|
@@ -52,6 +52,27 @@ async function buildValidationData(manifest, validatedEnvKey, authConfig, option
|
|
|
52
52
|
return { validationData, pipelineAuthConfig };
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Handle authentication errors during validation
|
|
57
|
+
* @param {Error} error - Error object
|
|
58
|
+
* @param {string} appKey - Application key
|
|
59
|
+
* @throws {Error} Enhanced authentication error
|
|
60
|
+
*/
|
|
61
|
+
function handleValidationAuthError(error, appKey) {
|
|
62
|
+
if (error.status === 401 || (error.response && error.response.status === 401)) {
|
|
63
|
+
const authError = new Error(
|
|
64
|
+
`Authentication failed: Invalid or expired credentials for application '${appKey}'.\n` +
|
|
65
|
+
'The provided Client ID and Client Secret are incorrect or have been revoked.\n\n' +
|
|
66
|
+
'💡 If the application already exists, rotate the secret:\n' +
|
|
67
|
+
` aifabrix app rotate-secret ${appKey}\n\n` +
|
|
68
|
+
'💡 Otherwise, ensure credentials are correct in ~/.aifabrix/secrets.local.yaml or use --client-id and --client-secret flags.'
|
|
69
|
+
);
|
|
70
|
+
authError.status = 401;
|
|
71
|
+
authError.originalError = error;
|
|
72
|
+
throw authError;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
55
76
|
/**
|
|
56
77
|
* Validates deployment configuration via validate endpoint
|
|
57
78
|
* This is the first step in the deployment process
|
|
@@ -79,6 +100,10 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
|
|
|
79
100
|
return handleValidationResponse(response);
|
|
80
101
|
} catch (error) {
|
|
81
102
|
lastError = error;
|
|
103
|
+
|
|
104
|
+
// Handle authentication errors (401) - credentials are invalid, not missing
|
|
105
|
+
handleValidationAuthError(error, manifest.key);
|
|
106
|
+
|
|
82
107
|
const shouldRetry = attempt < maxRetries && error.status && error.status >= 500;
|
|
83
108
|
if (shouldRetry) {
|
|
84
109
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
const logger = require('../utils/logger');
|
|
14
14
|
const config = require('../core/config');
|
|
15
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
15
16
|
const { validateControllerUrl, validateEnvironmentKey } = require('../utils/deployment-validation');
|
|
16
17
|
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
17
18
|
const { getEnvironmentStatus } = require('../api/environments.api');
|
|
@@ -266,27 +267,19 @@ function displayDeploymentResults(result) {
|
|
|
266
267
|
* @throws {Error} If deployment fails
|
|
267
268
|
*
|
|
268
269
|
* @example
|
|
269
|
-
* await deployEnvironment('dev', { controller: 'https://controller.aifabrix.
|
|
270
|
+
* await deployEnvironment('dev', { controller: 'https://controller.aifabrix.dev' });
|
|
270
271
|
*/
|
|
271
272
|
/**
|
|
272
|
-
* Validates deployment input parameters
|
|
273
|
+
* Validates deployment input parameters (environment key only).
|
|
274
|
+
* Controller URL is resolved from config.yaml via resolveControllerUrl().
|
|
273
275
|
* @function validateDeploymentInput
|
|
274
276
|
* @param {string} envKey - Environment key
|
|
275
|
-
* @param {Object} options - Deployment options
|
|
276
|
-
* @returns {string} Controller URL
|
|
277
277
|
* @throws {Error} If validation fails
|
|
278
278
|
*/
|
|
279
|
-
function validateDeploymentInput(envKey
|
|
279
|
+
function validateDeploymentInput(envKey) {
|
|
280
280
|
if (!envKey || typeof envKey !== 'string' || envKey.trim().length === 0) {
|
|
281
281
|
throw new Error('Environment key is required');
|
|
282
282
|
}
|
|
283
|
-
|
|
284
|
-
const controllerUrl = options.controller || options['controller-url'];
|
|
285
|
-
if (!controllerUrl) {
|
|
286
|
-
throw new Error('Controller URL is required. Use --controller flag');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return controllerUrl;
|
|
290
283
|
}
|
|
291
284
|
|
|
292
285
|
/**
|
|
@@ -365,7 +358,11 @@ async function pollDeploymentStatusIfEnabled(result, validatedControllerUrl, env
|
|
|
365
358
|
|
|
366
359
|
async function deployEnvironment(envKey, options = {}) {
|
|
367
360
|
try {
|
|
368
|
-
|
|
361
|
+
validateDeploymentInput(envKey);
|
|
362
|
+
const controllerUrl = await resolveControllerUrl();
|
|
363
|
+
if (!controllerUrl) {
|
|
364
|
+
throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
|
|
365
|
+
}
|
|
369
366
|
const authConfig = await prepareEnvironmentDeployment(envKey, controllerUrl, options);
|
|
370
367
|
|
|
371
368
|
const validatedControllerUrl = validateControllerUrl(authConfig.controller);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External System Delete Module
|
|
3
|
+
*
|
|
4
|
+
* Deletes external systems from dataplane and confirms before removal.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview External system delete functionality for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const inquirer = require('inquirer');
|
|
15
|
+
const logger = require('../utils/logger');
|
|
16
|
+
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
17
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
18
|
+
const { getExternalSystemConfig, deleteExternalSystem } = require('../api/external-systems.api');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validates system key format
|
|
22
|
+
* @param {string} systemKey - System key to validate
|
|
23
|
+
*/
|
|
24
|
+
function validateSystemKey(systemKey) {
|
|
25
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
26
|
+
throw new Error('System key is required and must be a string');
|
|
27
|
+
}
|
|
28
|
+
if (!/^[a-z0-9-_]+$/.test(systemKey)) {
|
|
29
|
+
throw new Error('System key must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Gets dataplane URL and authentication configuration
|
|
35
|
+
* @async
|
|
36
|
+
* @param {string} systemKey - System key
|
|
37
|
+
* @param {Object} options - Command options
|
|
38
|
+
* @returns {Promise<Object>} Auth and dataplane details
|
|
39
|
+
*/
|
|
40
|
+
async function getAuthAndDataplane(systemKey, _options) {
|
|
41
|
+
const { resolveEnvironment } = require('../core/config');
|
|
42
|
+
const environment = await resolveEnvironment();
|
|
43
|
+
const controllerUrl = await resolveControllerUrl();
|
|
44
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
45
|
+
|
|
46
|
+
if (!authConfig.token && !authConfig.clientId) {
|
|
47
|
+
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
51
|
+
logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
|
|
52
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
53
|
+
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
54
|
+
|
|
55
|
+
return { authConfig, dataplaneUrl, environment, controllerUrl };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Fetches external system configuration for warning display
|
|
60
|
+
* @async
|
|
61
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
62
|
+
* @param {string} systemKey - System key
|
|
63
|
+
* @param {Object} authConfig - Authentication configuration
|
|
64
|
+
* @returns {Promise<Object>} System config response data
|
|
65
|
+
*/
|
|
66
|
+
async function fetchExternalSystemConfig(dataplaneUrl, systemKey, authConfig) {
|
|
67
|
+
const response = await getExternalSystemConfig(dataplaneUrl, systemKey, authConfig);
|
|
68
|
+
if (!response || response.success === false) {
|
|
69
|
+
throw new Error(response?.error || response?.formattedError || `External system '${systemKey}' not found`);
|
|
70
|
+
}
|
|
71
|
+
return response.data?.data || response.data || {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Formats datasources for warning output
|
|
76
|
+
* @param {Array} dataSources - Datasource objects
|
|
77
|
+
* @returns {string[]} Datasource labels
|
|
78
|
+
*/
|
|
79
|
+
function formatDatasourceList(dataSources) {
|
|
80
|
+
if (!Array.isArray(dataSources)) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return dataSources
|
|
84
|
+
.map(ds => ds.key || ds.displayName || 'unknown-datasource')
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Prompts for delete confirmation if needed
|
|
90
|
+
* @async
|
|
91
|
+
* @param {string} systemKey - System key
|
|
92
|
+
* @param {string[]} datasources - Datasource keys
|
|
93
|
+
* @param {Object} options - Command options
|
|
94
|
+
* @returns {Promise<boolean>} True if confirmed
|
|
95
|
+
*/
|
|
96
|
+
async function confirmDeletion(systemKey, datasources, options) {
|
|
97
|
+
if (options.yes || options.force) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
logger.log(chalk.yellow(`\n⚠️ Warning: Deleting external system '${systemKey}' will also delete all associated datasources:`));
|
|
102
|
+
if (datasources.length > 0) {
|
|
103
|
+
datasources.forEach(ds => logger.log(chalk.yellow(` - ${ds}`)));
|
|
104
|
+
} else {
|
|
105
|
+
logger.log(chalk.yellow(' - (no datasources found)'));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const answer = await inquirer.prompt([{
|
|
109
|
+
type: 'input',
|
|
110
|
+
name: 'confirm',
|
|
111
|
+
message: `Are you sure you want to delete external system '${systemKey}'? (yes/no):`,
|
|
112
|
+
default: 'no'
|
|
113
|
+
}]);
|
|
114
|
+
|
|
115
|
+
return String(answer.confirm).trim().toLowerCase() === 'yes';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Deletes an external system from dataplane
|
|
120
|
+
* @async
|
|
121
|
+
* @function deleteExternalSystemCommand
|
|
122
|
+
* @param {string} systemKey - System key
|
|
123
|
+
* @param {Object} options - Command options
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
* @throws {Error} If deletion fails or is cancelled
|
|
126
|
+
*/
|
|
127
|
+
async function deleteExternalSystemCommand(systemKey, options = {}) {
|
|
128
|
+
validateSystemKey(systemKey);
|
|
129
|
+
|
|
130
|
+
const { authConfig, dataplaneUrl } = await getAuthAndDataplane(systemKey, options);
|
|
131
|
+
const configData = await fetchExternalSystemConfig(dataplaneUrl, systemKey, authConfig);
|
|
132
|
+
const dataSources = formatDatasourceList(configData.dataSources || []);
|
|
133
|
+
|
|
134
|
+
const confirmed = await confirmDeletion(systemKey, dataSources, options);
|
|
135
|
+
if (!confirmed) {
|
|
136
|
+
logger.log(chalk.yellow('Deletion cancelled.'));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const response = await deleteExternalSystem(dataplaneUrl, systemKey, authConfig);
|
|
141
|
+
if (!response || response.success === false) {
|
|
142
|
+
throw new Error(response?.error || response?.formattedError || `Failed to delete external system '${systemKey}'`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
logger.log(chalk.green(`✓ External system '${systemKey}' deleted successfully`));
|
|
146
|
+
logger.log(chalk.green('✓ All associated datasources have been removed'));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
deleteExternalSystem: deleteExternalSystemCommand
|
|
151
|
+
};
|