@aifabrix/builder 2.32.2 → 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/index.js +10 -5
- package/lib/api/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +207 -38
- 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 +78 -37
- package/lib/app/prompts.js +9 -5
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +50 -32
- package/lib/cli.js +243 -65
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +261 -0
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +43 -29
- package/lib/commands/login.js +22 -13
- 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 +129 -357
- 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 +34 -23
- 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 +54 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +34 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +5 -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 +24 -5
- package/lib/schema/external-datasource.schema.json +303 -17
- package/lib/schema/external-system.schema.json +1 -1
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +37 -42
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/app-register-display.js +2 -1
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/cli-utils.js +3 -1
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +115 -0
- 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-map.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 +149 -28
- 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 +69 -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 +38 -4
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +19 -17
- 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/external-system/external-system.json.hbs +1 -1
- 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/config.js
CHANGED
|
@@ -95,6 +95,8 @@ function applyConfigDefaults(config) {
|
|
|
95
95
|
if (typeof config.device !== 'object' || config.device === null) {
|
|
96
96
|
config.device = {};
|
|
97
97
|
}
|
|
98
|
+
// Ensure controller field exists (but don't set defaults)
|
|
99
|
+
// It will be set by login or auth config commands
|
|
98
100
|
return config;
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -107,6 +109,7 @@ function getDefaultConfig() {
|
|
|
107
109
|
return {
|
|
108
110
|
'developer-id': '0',
|
|
109
111
|
environment: 'dev',
|
|
112
|
+
controller: undefined,
|
|
110
113
|
environments: {},
|
|
111
114
|
device: {}
|
|
112
115
|
};
|
|
@@ -239,6 +242,17 @@ async function getCurrentEnvironment() {
|
|
|
239
242
|
return config.environment || 'dev';
|
|
240
243
|
}
|
|
241
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Resolve environment from configuration
|
|
247
|
+
* Uses config.environment, defaults to 'dev'
|
|
248
|
+
* @async
|
|
249
|
+
* @function resolveEnvironment
|
|
250
|
+
* @returns {Promise<string>} Environment key
|
|
251
|
+
*/
|
|
252
|
+
async function resolveEnvironment() {
|
|
253
|
+
return await getCurrentEnvironment();
|
|
254
|
+
}
|
|
255
|
+
|
|
242
256
|
async function setCurrentEnvironment(environment) {
|
|
243
257
|
if (!environment || typeof environment !== 'string') {
|
|
244
258
|
throw new Error('Environment must be a non-empty string');
|
|
@@ -248,6 +262,35 @@ async function setCurrentEnvironment(environment) {
|
|
|
248
262
|
await saveConfig(config);
|
|
249
263
|
}
|
|
250
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Set controller URL in configuration
|
|
267
|
+
* @async
|
|
268
|
+
* @function setControllerUrl
|
|
269
|
+
* @param {string} controllerUrl - Controller URL to save
|
|
270
|
+
* @returns {Promise<void>}
|
|
271
|
+
* @throws {Error} If controller URL is invalid
|
|
272
|
+
*/
|
|
273
|
+
async function setControllerUrl(controllerUrl) {
|
|
274
|
+
if (!controllerUrl || typeof controllerUrl !== 'string') {
|
|
275
|
+
throw new Error('Controller URL is required and must be a string');
|
|
276
|
+
}
|
|
277
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
278
|
+
const config = await getConfig();
|
|
279
|
+
config.controller = normalizedUrl;
|
|
280
|
+
await saveConfig(config);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get controller URL from configuration
|
|
285
|
+
* @async
|
|
286
|
+
* @function getControllerUrl
|
|
287
|
+
* @returns {Promise<string|null>} Controller URL or null if not set
|
|
288
|
+
*/
|
|
289
|
+
async function getControllerUrl() {
|
|
290
|
+
const config = await getConfig();
|
|
291
|
+
return config.controller || null;
|
|
292
|
+
}
|
|
293
|
+
|
|
251
294
|
function isTokenExpired(expiresAt) {
|
|
252
295
|
if (!expiresAt) return true;
|
|
253
296
|
const expirationTime = new Date(expiresAt).getTime();
|
|
@@ -354,6 +397,7 @@ const exportsObj = {
|
|
|
354
397
|
loadDeveloperId,
|
|
355
398
|
getCurrentEnvironment,
|
|
356
399
|
setCurrentEnvironment,
|
|
400
|
+
resolveEnvironment,
|
|
357
401
|
isTokenExpired,
|
|
358
402
|
shouldRefreshToken,
|
|
359
403
|
encryptTokenValue,
|
|
@@ -363,6 +407,8 @@ const exportsObj = {
|
|
|
363
407
|
getSecretsPath,
|
|
364
408
|
setSecretsPath,
|
|
365
409
|
normalizeControllerUrl,
|
|
410
|
+
setControllerUrl,
|
|
411
|
+
getControllerUrl,
|
|
366
412
|
CONFIG_DIR,
|
|
367
413
|
CONFIG_FILE
|
|
368
414
|
};
|
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
|
@@ -40,14 +40,18 @@ async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Extract dataplane URL from application response
|
|
43
|
-
//
|
|
43
|
+
// Try multiple possible locations for the URL
|
|
44
44
|
const application = response.data.data || response.data;
|
|
45
|
-
const dataplaneUrl = application.
|
|
45
|
+
const dataplaneUrl = application.url ||
|
|
46
|
+
application.dataplaneUrl ||
|
|
47
|
+
application.dataplane?.url ||
|
|
48
|
+
application.configuration?.dataplaneUrl;
|
|
46
49
|
|
|
47
50
|
if (!dataplaneUrl) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
}
|
|
51
55
|
throw new Error('Dataplane URL not found in application configuration');
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -61,19 +65,13 @@ async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
|
|
|
61
65
|
* @param {Object} options - Options
|
|
62
66
|
* @throws {Error} If validation fails
|
|
63
67
|
*/
|
|
64
|
-
function validateDeploymentInputs(appKey, filePath
|
|
68
|
+
function validateDeploymentInputs(appKey, filePath) {
|
|
65
69
|
if (!appKey || typeof appKey !== 'string') {
|
|
66
70
|
throw new Error('Application key is required');
|
|
67
71
|
}
|
|
68
72
|
if (!filePath || typeof filePath !== 'string') {
|
|
69
73
|
throw new Error('File path is required');
|
|
70
74
|
}
|
|
71
|
-
if (!options.controller) {
|
|
72
|
-
throw new Error('Controller URL is required (--controller)');
|
|
73
|
-
}
|
|
74
|
-
if (!options.environment) {
|
|
75
|
-
throw new Error('Environment is required (-e, --environment)');
|
|
76
|
-
}
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
/**
|
|
@@ -109,15 +107,18 @@ async function validateAndLoadDatasourceFile(filePath) {
|
|
|
109
107
|
* @param {string} controllerUrl - Controller URL
|
|
110
108
|
* @param {string} environment - Environment key
|
|
111
109
|
* @param {string} appKey - Application key
|
|
110
|
+
* @param {Object} [options] - Options
|
|
111
|
+
* @param {string} [options.dataplane] - Dataplane URL override
|
|
112
112
|
* @returns {Promise<Object>} Object with authConfig and dataplaneUrl
|
|
113
113
|
*/
|
|
114
114
|
async function setupDeploymentAuth(controllerUrl, environment, appKey) {
|
|
115
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
115
116
|
logger.log(chalk.blue('🔐 Getting authentication...'));
|
|
116
117
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, appKey);
|
|
117
118
|
logger.log(chalk.green('✓ Authentication successful'));
|
|
118
119
|
|
|
119
|
-
logger.log(chalk.blue('🌐
|
|
120
|
-
const dataplaneUrl = await
|
|
120
|
+
logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
|
|
121
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
121
122
|
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
122
123
|
|
|
123
124
|
return { authConfig, dataplaneUrl };
|
|
@@ -162,20 +163,30 @@ function displayDeploymentResults(datasourceConfig, systemKey, environment) {
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
/**
|
|
165
|
-
* 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).
|
|
166
168
|
*
|
|
167
169
|
* @async
|
|
168
170
|
* @function deployDatasource
|
|
169
171
|
* @param {string} appKey - Application key
|
|
170
172
|
* @param {string} filePath - Path to datasource JSON file
|
|
171
|
-
* @param {Object}
|
|
172
|
-
* @param {string} options.controller - Controller URL
|
|
173
|
-
* @param {string} options.environment - Environment key
|
|
173
|
+
* @param {Object} [_options] - Deployment options (reserved)
|
|
174
174
|
* @returns {Promise<Object>} Deployment result
|
|
175
175
|
* @throws {Error} If deployment fails
|
|
176
176
|
*/
|
|
177
|
-
async function deployDatasource(appKey, filePath,
|
|
178
|
-
|
|
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);
|
|
179
190
|
|
|
180
191
|
logger.log(chalk.blue('📋 Deploying datasource...\n'));
|
|
181
192
|
|
|
@@ -189,19 +200,19 @@ async function deployDatasource(appKey, filePath, options) {
|
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
// Setup authentication and get dataplane URL
|
|
192
|
-
const { authConfig, dataplaneUrl } = await setupDeploymentAuth(
|
|
203
|
+
const { authConfig, dataplaneUrl } = await setupDeploymentAuth(controllerUrl, environment, appKey);
|
|
193
204
|
|
|
194
205
|
// Publish to dataplane
|
|
195
206
|
await publishDatasourceToDataplane(dataplaneUrl, systemKey, authConfig, datasourceConfig);
|
|
196
207
|
|
|
197
208
|
// Display results
|
|
198
|
-
displayDeploymentResults(datasourceConfig, systemKey,
|
|
209
|
+
displayDeploymentResults(datasourceConfig, systemKey, environment);
|
|
199
210
|
|
|
200
211
|
return {
|
|
201
212
|
success: true,
|
|
202
213
|
datasourceKey: datasourceConfig.key,
|
|
203
214
|
systemKey: systemKey,
|
|
204
|
-
environment:
|
|
215
|
+
environment: environment,
|
|
205
216
|
dataplaneUrl: dataplaneUrl
|
|
206
217
|
};
|
|
207
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
|
+
};
|