@aifabrix/builder 2.32.3 → 2.33.1
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 +12 -11
- 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 +6 -2
- 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 +59 -23
- package/lib/datasource/list.js +108 -19
- 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 +102 -52
- 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/error-formatters/network-errors.js +13 -3
- 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/github/ci.yaml.hbs +44 -1
- package/templates/github/release.yaml.hbs +44 -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/app/rotate-secret.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const { getConfig, normalizeControllerUrl } = require('../core/config');
|
|
12
|
+
const { getConfig, normalizeControllerUrl, resolveEnvironment } = require('../core/config');
|
|
13
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
13
14
|
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
14
15
|
const { rotateApplicationSecret } = require('../api/applications.api');
|
|
15
16
|
const { formatApiError } = require('../utils/api-error-handler');
|
|
@@ -61,6 +62,22 @@ function validateEnvironment(environment) {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Resolve controller URL and environment from config; exit if controller is missing.
|
|
67
|
+
* @async
|
|
68
|
+
* @returns {Promise<{controllerUrl: string, environment: string}>}
|
|
69
|
+
*/
|
|
70
|
+
async function resolveControllerAndEnvironment() {
|
|
71
|
+
const controllerUrl = await resolveControllerUrl();
|
|
72
|
+
if (!controllerUrl) {
|
|
73
|
+
logger.error(chalk.red('❌ Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml'));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const environment = await resolveEnvironment();
|
|
77
|
+
validateEnvironment(environment);
|
|
78
|
+
return { controllerUrl, environment };
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
/**
|
|
65
82
|
* Validate credentials object structure
|
|
66
83
|
* @param {Object} credentials - Credentials object to validate
|
|
@@ -286,46 +303,46 @@ async function saveCredentialsLocally(appKey, credentials, actualControllerUrl)
|
|
|
286
303
|
}
|
|
287
304
|
|
|
288
305
|
/**
|
|
289
|
-
*
|
|
306
|
+
* Call rotate API, validate response, save locally, and display results.
|
|
307
|
+
* @async
|
|
308
|
+
* @param {string} appKey - Application key
|
|
309
|
+
* @param {string} actualControllerUrl - Resolved controller URL
|
|
310
|
+
* @param {string} environment - Environment ID or key
|
|
311
|
+
* @param {string} token - Bearer token
|
|
312
|
+
*/
|
|
313
|
+
async function executeRotation(appKey, actualControllerUrl, environment, token) {
|
|
314
|
+
const authConfig = { type: 'bearer', token };
|
|
315
|
+
const response = await rotateApplicationSecret(actualControllerUrl, environment, appKey, authConfig);
|
|
316
|
+
|
|
317
|
+
if (!response.success) {
|
|
318
|
+
const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
|
|
319
|
+
logger.error(formattedError);
|
|
320
|
+
logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const { credentials, message } = validateResponse(response);
|
|
325
|
+
await saveCredentialsLocally(appKey, credentials, actualControllerUrl);
|
|
326
|
+
displayRotationResults(appKey, environment, credentials, actualControllerUrl, message);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Rotate secret for an application.
|
|
331
|
+
* Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
|
|
290
332
|
* @async
|
|
291
333
|
* @param {string} appKey - Application key
|
|
292
|
-
* @param {Object}
|
|
293
|
-
* @param {string} options.environment - Environment ID or key
|
|
294
|
-
* @param {string} [options.controller] - Controller URL (optional, uses configured controller if not provided)
|
|
334
|
+
* @param {Object} [_options] - Command options (reserved)
|
|
295
335
|
* @throws {Error} If rotation fails
|
|
296
336
|
*/
|
|
297
|
-
async function rotateSecret(appKey,
|
|
337
|
+
async function rotateSecret(appKey, _options = {}) {
|
|
298
338
|
logger.log(chalk.yellow('⚠️ This will invalidate the old ClientSecret!\n'));
|
|
299
339
|
|
|
340
|
+
const { controllerUrl, environment } = await resolveControllerAndEnvironment();
|
|
300
341
|
const config = await getConfig();
|
|
301
|
-
|
|
302
|
-
// Get authentication token
|
|
303
|
-
const controllerUrl = options.controller || null;
|
|
304
342
|
const { token, actualControllerUrl } = await getRotationAuthToken(controllerUrl, config);
|
|
305
343
|
|
|
306
|
-
// Validate environment
|
|
307
|
-
validateEnvironment(options.environment);
|
|
308
|
-
|
|
309
|
-
// Use centralized API client
|
|
310
|
-
const authConfig = { type: 'bearer', token: token };
|
|
311
344
|
try {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (!response.success) {
|
|
315
|
-
const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
|
|
316
|
-
logger.error(formattedError);
|
|
317
|
-
logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
|
|
318
|
-
process.exit(1);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Validate response structure and extract credentials
|
|
322
|
-
const { credentials, message } = validateResponse(response);
|
|
323
|
-
|
|
324
|
-
// Save credentials locally
|
|
325
|
-
await saveCredentialsLocally(appKey, credentials, actualControllerUrl);
|
|
326
|
-
|
|
327
|
-
// Display results
|
|
328
|
-
displayRotationResults(appKey, options.environment, credentials, actualControllerUrl, message);
|
|
345
|
+
await executeRotation(appKey, actualControllerUrl, environment, token);
|
|
329
346
|
} catch (error) {
|
|
330
347
|
logger.error(chalk.red(`❌ Failed to rotate secret via controller: ${actualControllerUrl}`));
|
|
331
348
|
logger.error(chalk.gray(`Error: ${error.message}`));
|
package/lib/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ const validator = require('./validation/validator');
|
|
|
18
18
|
const config = require('./core/config');
|
|
19
19
|
const devConfig = require('./utils/dev-config');
|
|
20
20
|
const chalk = require('chalk');
|
|
21
|
+
const path = require('path');
|
|
21
22
|
const logger = require('./utils/logger');
|
|
22
23
|
const { validateCommand, handleCommandError } = require('./utils/cli-utils');
|
|
23
24
|
const { handleLogin } = require('./commands/login');
|
|
@@ -25,6 +26,9 @@ const { handleLogout } = require('./commands/logout');
|
|
|
25
26
|
const { handleAuthStatus } = require('./commands/auth-status');
|
|
26
27
|
const { handleSecure } = require('./commands/secure');
|
|
27
28
|
const { handleSecretsSet } = require('./commands/secrets-set');
|
|
29
|
+
const { handleAuthConfig } = require('./commands/auth-config');
|
|
30
|
+
const { setupAppCommands: setupAppManagementCommands } = require('./commands/app');
|
|
31
|
+
const { setupDatasourceCommands } = require('./commands/datasource');
|
|
28
32
|
|
|
29
33
|
/**
|
|
30
34
|
* Sets up authentication commands
|
|
@@ -33,7 +37,7 @@ const { handleSecretsSet } = require('./commands/secrets-set');
|
|
|
33
37
|
function setupAuthCommands(program) {
|
|
34
38
|
program.command('login')
|
|
35
39
|
.description('Authenticate with Miso Controller')
|
|
36
|
-
.option('-c, --controller <url>', 'Controller URL (default:
|
|
40
|
+
.option('-c, --controller <url>', 'Controller URL (default: from config or developer ID, e.g. http://localhost:3000)')
|
|
37
41
|
.option('-m, --method <method>', 'Authentication method (device|credentials)', 'device')
|
|
38
42
|
.option('-a, --app <app>', 'Application name (required for credentials method, reads from secrets.local.yaml)')
|
|
39
43
|
.option('--client-id <id>', 'Client ID (for credentials method, overrides secrets.local.yaml)')
|
|
@@ -81,16 +85,52 @@ function setupAuthCommands(program) {
|
|
|
81
85
|
auth
|
|
82
86
|
.command('status')
|
|
83
87
|
.description('Display authentication status for current controller and environment')
|
|
84
|
-
.option('-c, --controller <url>', 'Check status for specific controller (uses developer ID-based default if not provided)')
|
|
85
|
-
.option('-e, --environment <env>', 'Check status for specific environment')
|
|
86
88
|
.action(authStatusHandler);
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.description('
|
|
91
|
-
.option('-
|
|
92
|
-
.option('-
|
|
93
|
-
.action(
|
|
90
|
+
auth
|
|
91
|
+
.command('config')
|
|
92
|
+
.description('Configure authentication settings (controller, environment)')
|
|
93
|
+
.option('--set-controller <url>', 'Set default controller URL')
|
|
94
|
+
.option('--set-environment <env>', 'Set default environment')
|
|
95
|
+
.action(async(options) => {
|
|
96
|
+
try {
|
|
97
|
+
await handleAuthConfig(options);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
handleCommandError(error, 'auth config');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Runs the up command: resolves developer ID, traefik, and starts infra.
|
|
107
|
+
* @param {Object} options - Commander options (developer, traefik)
|
|
108
|
+
* @returns {Promise<void>}
|
|
109
|
+
*/
|
|
110
|
+
async function runUpCommand(options) {
|
|
111
|
+
let developerId = null;
|
|
112
|
+
if (options.developer) {
|
|
113
|
+
const id = parseInt(options.developer, 10);
|
|
114
|
+
if (isNaN(id) || id < 0) {
|
|
115
|
+
throw new Error('Developer ID must be a non-negative number (0 = default infra, > 0 = developer-specific)');
|
|
116
|
+
}
|
|
117
|
+
await config.setDeveloperId(id);
|
|
118
|
+
process.env.AIFABRIX_DEVELOPERID = id.toString();
|
|
119
|
+
developerId = id;
|
|
120
|
+
logger.log(chalk.green(`✓ Developer ID set to ${id}`));
|
|
121
|
+
}
|
|
122
|
+
const cfg = await config.getConfig();
|
|
123
|
+
if (options.traefik === true) {
|
|
124
|
+
cfg.traefik = true;
|
|
125
|
+
await config.saveConfig(cfg);
|
|
126
|
+
logger.log(chalk.green('✓ Traefik enabled and saved to config'));
|
|
127
|
+
} else if (options.traefik === false) {
|
|
128
|
+
cfg.traefik = false;
|
|
129
|
+
await config.saveConfig(cfg);
|
|
130
|
+
logger.log(chalk.green('✓ Traefik disabled and saved to config'));
|
|
131
|
+
}
|
|
132
|
+
const useTraefik = options.traefik === true ? true : (options.traefik === false ? false : !!(cfg.traefik));
|
|
133
|
+
await infra.startInfra(developerId, { traefik: useTraefik });
|
|
94
134
|
}
|
|
95
135
|
|
|
96
136
|
/**
|
|
@@ -101,20 +141,11 @@ function setupInfraCommands(program) {
|
|
|
101
141
|
program.command('up')
|
|
102
142
|
.description('Start local infrastructure services (Postgres, Redis, pgAdmin, Redis Commander)')
|
|
103
143
|
.option('-d, --developer <id>', 'Set developer ID and start infrastructure')
|
|
144
|
+
.option('--traefik', 'Include Traefik reverse proxy and save to config')
|
|
145
|
+
.option('--no-traefik', 'Exclude Traefik and save to config')
|
|
104
146
|
.action(async(options) => {
|
|
105
147
|
try {
|
|
106
|
-
|
|
107
|
-
if (options.developer) {
|
|
108
|
-
const id = parseInt(options.developer, 10);
|
|
109
|
-
if (isNaN(id) || id < 0) {
|
|
110
|
-
throw new Error('Developer ID must be a non-negative number (0 = default infra, > 0 = developer-specific)');
|
|
111
|
-
}
|
|
112
|
-
await config.setDeveloperId(id);
|
|
113
|
-
process.env.AIFABRIX_DEVELOPERID = id.toString();
|
|
114
|
-
developerId = id;
|
|
115
|
-
logger.log(chalk.green(`✓ Developer ID set to ${id}`));
|
|
116
|
-
}
|
|
117
|
-
await infra.startInfra(developerId);
|
|
148
|
+
await runUpCommand(options);
|
|
118
149
|
} catch (error) {
|
|
119
150
|
handleCommandError(error, 'up');
|
|
120
151
|
process.exit(1);
|
|
@@ -232,6 +263,88 @@ function setupInfraCommands(program) {
|
|
|
232
263
|
});
|
|
233
264
|
}
|
|
234
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Normalize options for external system creation
|
|
268
|
+
* @function normalizeExternalOptions
|
|
269
|
+
* @param {Object} options - Raw CLI options
|
|
270
|
+
* @returns {Object} Normalized options
|
|
271
|
+
*/
|
|
272
|
+
function normalizeExternalOptions(options) {
|
|
273
|
+
const normalized = { ...options };
|
|
274
|
+
if (options.displayName) normalized.systemDisplayName = options.displayName;
|
|
275
|
+
if (options.description) normalized.systemDescription = options.description;
|
|
276
|
+
if (options.systemType) normalized.systemType = options.systemType;
|
|
277
|
+
if (options.authType) normalized.authType = options.authType;
|
|
278
|
+
if (options.datasources !== undefined) {
|
|
279
|
+
const parsedCount = parseInt(options.datasources, 10);
|
|
280
|
+
if (Number.isNaN(parsedCount) || parsedCount < 1 || parsedCount > 10) {
|
|
281
|
+
throw new Error('Datasources count must be a number between 1 and 10');
|
|
282
|
+
}
|
|
283
|
+
normalized.datasourceCount = parsedCount;
|
|
284
|
+
}
|
|
285
|
+
if (options.controller) {
|
|
286
|
+
normalized.controller = true;
|
|
287
|
+
normalized.controllerUrl = options.controller;
|
|
288
|
+
}
|
|
289
|
+
return normalized;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Validate required options for non-interactive external creation
|
|
294
|
+
* @function validateNonInteractiveExternalOptions
|
|
295
|
+
* @param {Object} normalizedOptions - Normalized options
|
|
296
|
+
* @throws {Error} If required options are missing
|
|
297
|
+
*/
|
|
298
|
+
function validateNonInteractiveExternalOptions(normalizedOptions) {
|
|
299
|
+
const missing = [];
|
|
300
|
+
if (!normalizedOptions.systemDisplayName) missing.push('--display-name');
|
|
301
|
+
if (!normalizedOptions.systemDescription) missing.push('--description');
|
|
302
|
+
if (!normalizedOptions.systemType) missing.push('--system-type');
|
|
303
|
+
if (!normalizedOptions.authType) missing.push('--auth-type');
|
|
304
|
+
if (!normalizedOptions.datasourceCount) missing.push('--datasources');
|
|
305
|
+
if (missing.length > 0) {
|
|
306
|
+
throw new Error(`Missing required options for non-interactive external create: ${missing.join(', ')}`);
|
|
307
|
+
}
|
|
308
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedOptions, 'github')) {
|
|
309
|
+
normalizedOptions.github = false;
|
|
310
|
+
}
|
|
311
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedOptions, 'controller')) {
|
|
312
|
+
normalizedOptions.controller = false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Handle create command execution
|
|
318
|
+
* @async
|
|
319
|
+
* @function handleCreateCommand
|
|
320
|
+
* @param {string} appName - Application name
|
|
321
|
+
* @param {Object} options - CLI options
|
|
322
|
+
*/
|
|
323
|
+
async function handleCreateCommand(appName, options) {
|
|
324
|
+
const validTypes = ['webapp', 'api', 'service', 'functionapp', 'external'];
|
|
325
|
+
if (options.type && !validTypes.includes(options.type)) {
|
|
326
|
+
throw new Error(`Invalid type: ${options.type}. Must be one of: ${validTypes.join(', ')}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const wizardOptions = { app: appName, ...options };
|
|
330
|
+
const normalizedOptions = normalizeExternalOptions(options);
|
|
331
|
+
|
|
332
|
+
const isExternalType = options.type === 'external';
|
|
333
|
+
const isNonInteractive = process.stdin && process.stdin.isTTY === false;
|
|
334
|
+
|
|
335
|
+
if (isExternalType && !options.wizard && isNonInteractive) {
|
|
336
|
+
validateNonInteractiveExternalOptions(normalizedOptions);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const shouldUseWizard = options.wizard && (options.type === 'external' || (!options.type && validTypes.includes('external')));
|
|
340
|
+
if (shouldUseWizard) {
|
|
341
|
+
const { handleWizard } = require('./commands/wizard');
|
|
342
|
+
await handleWizard(wizardOptions);
|
|
343
|
+
} else {
|
|
344
|
+
await app.createApp(appName, normalizedOptions);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
235
348
|
/**
|
|
236
349
|
* Sets up application lifecycle commands
|
|
237
350
|
* @param {Command} program - Commander program instance
|
|
@@ -252,20 +365,14 @@ function setupAppCommands(program) {
|
|
|
252
365
|
.option('--github-steps <steps>', 'Extra GitHub workflow steps (comma-separated, e.g., npm,test)')
|
|
253
366
|
.option('--main-branch <branch>', 'Main branch name for workflows', 'main')
|
|
254
367
|
.option('--wizard', 'Use interactive wizard for external system creation')
|
|
368
|
+
.option('--display-name <name>', 'External system display name')
|
|
369
|
+
.option('--description <desc>', 'External system description')
|
|
370
|
+
.option('--system-type <type>', 'External system type (openapi, mcp, custom)')
|
|
371
|
+
.option('--auth-type <type>', 'External system auth type (oauth2, apikey, basic)')
|
|
372
|
+
.option('--datasources <count>', 'Number of datasources to create')
|
|
255
373
|
.action(async(appName, options) => {
|
|
256
374
|
try {
|
|
257
|
-
|
|
258
|
-
const validTypes = ['webapp', 'api', 'service', 'functionapp', 'external'];
|
|
259
|
-
if (options.type && !validTypes.includes(options.type)) {
|
|
260
|
-
throw new Error(`Invalid type: ${options.type}. Must be one of: ${validTypes.join(', ')}`);
|
|
261
|
-
}
|
|
262
|
-
// If wizard flag is set and type is external, use wizard instead
|
|
263
|
-
if (options.wizard && (options.type === 'external' || (!options.type && validTypes.includes('external')))) {
|
|
264
|
-
const { handleWizard } = require('./commands/wizard');
|
|
265
|
-
await handleWizard({ app: appName, ...options });
|
|
266
|
-
} else {
|
|
267
|
-
await app.createApp(appName, options);
|
|
268
|
-
}
|
|
375
|
+
await handleCreateCommand(appName, options);
|
|
269
376
|
} catch (error) {
|
|
270
377
|
handleCommandError(error, 'create');
|
|
271
378
|
process.exit(1);
|
|
@@ -273,11 +380,9 @@ function setupAppCommands(program) {
|
|
|
273
380
|
});
|
|
274
381
|
|
|
275
382
|
program.command('wizard')
|
|
276
|
-
.description('Interactive wizard for creating external systems')
|
|
383
|
+
.description('Interactive wizard for creating external systems (or headless with --config)')
|
|
277
384
|
.option('-a, --app <app>', 'Application name (if not provided, will prompt)')
|
|
278
|
-
.option('
|
|
279
|
-
.option('-e, --environment <env>', 'Environment (dev, tst, pro)', 'dev')
|
|
280
|
-
.option('--dataplane <url>', 'Dataplane URL (overrides controller lookup)')
|
|
385
|
+
.option('--config <file>', 'Path to wizard.yaml config file for headless mode')
|
|
281
386
|
.action(async(options) => {
|
|
282
387
|
try {
|
|
283
388
|
const { handleWizard } = require('./commands/wizard');
|
|
@@ -331,8 +436,6 @@ function setupAppCommands(program) {
|
|
|
331
436
|
|
|
332
437
|
program.command('deploy <app>')
|
|
333
438
|
.description('Deploy to Azure via Miso Controller')
|
|
334
|
-
.option('-c, --controller <url>', 'Controller URL')
|
|
335
|
-
.option('-e, --environment <env>', 'Environment (miso, dev, tst, pro)', 'dev')
|
|
336
439
|
.option('--client-id <id>', 'Client ID (overrides config)')
|
|
337
440
|
.option('--client-secret <secret>', 'Client Secret (overrides config)')
|
|
338
441
|
.option('--poll', 'Poll for deployment status', true)
|
|
@@ -384,7 +487,6 @@ function setupEnvironmentCommands(program) {
|
|
|
384
487
|
environment
|
|
385
488
|
.command('deploy <env>')
|
|
386
489
|
.description('Deploy/setup environment in Miso Controller')
|
|
387
|
-
.option('-c, --controller <url>', 'Controller URL (required)')
|
|
388
490
|
.option('--config <file>', 'Environment configuration file')
|
|
389
491
|
.option('--skip-validation', 'Skip environment validation')
|
|
390
492
|
.option('--poll', 'Poll for deployment status', true)
|
|
@@ -399,7 +501,6 @@ function setupEnvironmentCommands(program) {
|
|
|
399
501
|
env
|
|
400
502
|
.command('deploy <env>')
|
|
401
503
|
.description('Deploy/setup environment in Miso Controller')
|
|
402
|
-
.option('-c, --controller <url>', 'Controller URL (required)')
|
|
403
504
|
.option('--config <file>', 'Environment configuration file')
|
|
404
505
|
.option('--skip-validation', 'Skip environment validation')
|
|
405
506
|
.option('--poll', 'Poll for deployment status', true)
|
|
@@ -407,6 +508,52 @@ function setupEnvironmentCommands(program) {
|
|
|
407
508
|
.action(deployEnvHandler);
|
|
408
509
|
}
|
|
409
510
|
|
|
511
|
+
/**
|
|
512
|
+
* Handles split-json command logic
|
|
513
|
+
* @async
|
|
514
|
+
* @function handleSplitJsonCommand
|
|
515
|
+
* @param {string} appName - Application name
|
|
516
|
+
* @param {Object} options - Command options
|
|
517
|
+
* @returns {Promise<Object>} Paths to generated files
|
|
518
|
+
*/
|
|
519
|
+
async function handleSplitJsonCommand(appName, options) {
|
|
520
|
+
const fs = require('fs');
|
|
521
|
+
const { detectAppType, getDeployJsonPath } = require('./utils/paths');
|
|
522
|
+
const { appPath, appType } = await detectAppType(appName, options);
|
|
523
|
+
|
|
524
|
+
const outputDir = options.output || appPath;
|
|
525
|
+
if (appType === 'external') {
|
|
526
|
+
const schemaPath = path.join(appPath, 'application-schema.json');
|
|
527
|
+
if (!fs.existsSync(schemaPath)) {
|
|
528
|
+
throw new Error(`application-schema.json not found: ${schemaPath}`);
|
|
529
|
+
}
|
|
530
|
+
return generator.splitExternalApplicationSchema(schemaPath, outputDir);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const deployJsonPath = getDeployJsonPath(appName, appType, true);
|
|
534
|
+
if (!fs.existsSync(deployJsonPath)) {
|
|
535
|
+
throw new Error(`Deployment JSON file not found: ${deployJsonPath}`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return generator.splitDeployJson(deployJsonPath, outputDir);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Logs split-json results
|
|
543
|
+
* @function logSplitJsonResult
|
|
544
|
+
* @param {Object} result - Generated file paths
|
|
545
|
+
* @returns {void}
|
|
546
|
+
*/
|
|
547
|
+
function logSplitJsonResult(result) {
|
|
548
|
+
logger.log(chalk.green('\n✓ Successfully split deployment JSON into component files:'));
|
|
549
|
+
logger.log(` • env.template: ${result.envTemplate}`);
|
|
550
|
+
logger.log(` • variables.yaml: ${result.variables}`);
|
|
551
|
+
if (result.rbac) {
|
|
552
|
+
logger.log(` • rbac.yml: ${result.rbac}`);
|
|
553
|
+
}
|
|
554
|
+
logger.log(` • README.md: ${result.readme}`);
|
|
555
|
+
}
|
|
556
|
+
|
|
410
557
|
/**
|
|
411
558
|
* Sets up utility commands
|
|
412
559
|
* @param {Command} program - Commander program instance
|
|
@@ -441,9 +588,10 @@ function setupUtilityCommands(program) {
|
|
|
441
588
|
|
|
442
589
|
program.command('json <app>')
|
|
443
590
|
.description('Generate deployment JSON (aifabrix-deploy.json for normal apps, application-schema.json for external systems)')
|
|
444
|
-
.
|
|
591
|
+
.option('--type <type>', 'Application type (external) - if set, only checks integration folder')
|
|
592
|
+
.action(async(appName, options) => {
|
|
445
593
|
try {
|
|
446
|
-
const result = await generator.generateDeployJsonWithValidation(appName);
|
|
594
|
+
const result = await generator.generateDeployJsonWithValidation(appName, options);
|
|
447
595
|
if (result.success) {
|
|
448
596
|
const fileName = result.path.includes('application-schema.json') ? 'application-schema.json' : 'deployment JSON';
|
|
449
597
|
logger.log(`✓ Generated ${fileName}: ${result.path}`);
|
|
@@ -468,27 +616,11 @@ function setupUtilityCommands(program) {
|
|
|
468
616
|
program.command('split-json <app>')
|
|
469
617
|
.description('Split deployment JSON into component files (env.template, variables.yaml, rbac.yml, README.md)')
|
|
470
618
|
.option('-o, --output <dir>', 'Output directory for component files (defaults to same directory as JSON)')
|
|
619
|
+
.option('--type <type>', 'Application type (external) - if set, only checks integration folder')
|
|
471
620
|
.action(async(appName, options) => {
|
|
472
621
|
try {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
const { appPath, appType } = await detectAppType(appName);
|
|
476
|
-
const deployJsonPath = getDeployJsonPath(appName, appType, true);
|
|
477
|
-
|
|
478
|
-
if (!fs.existsSync(deployJsonPath)) {
|
|
479
|
-
throw new Error(`Deployment JSON file not found: ${deployJsonPath}`);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const outputDir = options.output || appPath;
|
|
483
|
-
const result = await generator.splitDeployJson(deployJsonPath, outputDir);
|
|
484
|
-
|
|
485
|
-
logger.log(chalk.green('\n✓ Successfully split deployment JSON into component files:'));
|
|
486
|
-
logger.log(` • env.template: ${result.envTemplate}`);
|
|
487
|
-
logger.log(` • variables.yaml: ${result.variables}`);
|
|
488
|
-
if (result.rbac) {
|
|
489
|
-
logger.log(` • rbac.yml: ${result.rbac}`);
|
|
490
|
-
}
|
|
491
|
-
logger.log(` • README.md: ${result.readme}`);
|
|
622
|
+
const result = await handleSplitJsonCommand(appName, options);
|
|
623
|
+
logSplitJsonResult(result);
|
|
492
624
|
} catch (error) {
|
|
493
625
|
handleCommandError(error, 'split-json');
|
|
494
626
|
process.exit(1);
|
|
@@ -525,10 +657,11 @@ function setupUtilityCommands(program) {
|
|
|
525
657
|
|
|
526
658
|
program.command('validate <appOrFile>')
|
|
527
659
|
.description('Validate application or external integration file')
|
|
528
|
-
.
|
|
660
|
+
.option('--type <type>', 'Application type (external) - if set, only checks integration folder')
|
|
661
|
+
.action(async(appOrFile, options) => {
|
|
529
662
|
try {
|
|
530
663
|
const validate = require('./validation/validate');
|
|
531
|
-
const result = await validate.validateAppOrFile(appOrFile);
|
|
664
|
+
const result = await validate.validateAppOrFile(appOrFile, options);
|
|
532
665
|
validate.displayValidationResults(result);
|
|
533
666
|
if (!result.valid) {
|
|
534
667
|
process.exit(1);
|
|
@@ -688,8 +821,6 @@ function setupSecretsCommands(program) {
|
|
|
688
821
|
function setupExternalSystemCommands(program) {
|
|
689
822
|
program.command('download <system-key>')
|
|
690
823
|
.description('Download external system from dataplane to local development structure')
|
|
691
|
-
.option('-e, --environment <env>', 'Environment (dev, tst, pro)', 'dev')
|
|
692
|
-
.option('-c, --controller <url>', 'Controller URL')
|
|
693
824
|
.option('--dry-run', 'Show what would be downloaded without actually downloading')
|
|
694
825
|
.action(async(systemKey, options) => {
|
|
695
826
|
try {
|
|
@@ -701,6 +832,24 @@ function setupExternalSystemCommands(program) {
|
|
|
701
832
|
}
|
|
702
833
|
});
|
|
703
834
|
|
|
835
|
+
program.command('delete <system-key>')
|
|
836
|
+
.description('Delete external system from dataplane (also deletes all associated datasources)')
|
|
837
|
+
.option('--type <type>', 'Application type (external) - required for external systems')
|
|
838
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
839
|
+
.option('--force', 'Skip confirmation prompt (alias for --yes)')
|
|
840
|
+
.action(async(systemKey, options) => {
|
|
841
|
+
try {
|
|
842
|
+
if (options.type !== 'external') {
|
|
843
|
+
throw new Error('Delete command for external systems requires --type external');
|
|
844
|
+
}
|
|
845
|
+
const externalDelete = require('./external-system/delete');
|
|
846
|
+
await externalDelete.deleteExternalSystem(systemKey, options);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
handleCommandError(error, 'delete');
|
|
849
|
+
process.exit(1);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
|
|
704
853
|
program.command('test <app>')
|
|
705
854
|
.description('Run unit tests for external system (local validation, no API calls)')
|
|
706
855
|
.option('-d, --datasource <key>', 'Test specific datasource only')
|
|
@@ -723,8 +872,6 @@ function setupExternalSystemCommands(program) {
|
|
|
723
872
|
.description('Run integration tests via dataplane pipeline API')
|
|
724
873
|
.option('-d, --datasource <key>', 'Test specific datasource only')
|
|
725
874
|
.option('-p, --payload <file>', 'Path to custom test payload file')
|
|
726
|
-
.option('-e, --environment <env>', 'Environment (dev, tst, pro)', 'dev')
|
|
727
|
-
.option('-c, --controller <url>', 'Controller URL')
|
|
728
875
|
.option('-v, --verbose', 'Show detailed test output')
|
|
729
876
|
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
730
877
|
.action(async(appName, options) => {
|
|
@@ -747,14 +894,16 @@ function setupExternalSystemCommands(program) {
|
|
|
747
894
|
* @param {Command} program - Commander program instance
|
|
748
895
|
*/
|
|
749
896
|
function setupCommands(program) {
|
|
750
|
-
setupAuthCommands(program);
|
|
751
897
|
setupInfraCommands(program);
|
|
898
|
+
setupAuthCommands(program);
|
|
752
899
|
setupAppCommands(program);
|
|
753
900
|
setupEnvironmentCommands(program);
|
|
901
|
+
setupAppManagementCommands(program);
|
|
902
|
+
setupDatasourceCommands(program);
|
|
754
903
|
setupUtilityCommands(program);
|
|
904
|
+
setupExternalSystemCommands(program);
|
|
755
905
|
setupDevCommands(program);
|
|
756
906
|
setupSecretsCommands(program);
|
|
757
|
-
setupExternalSystemCommands(program);
|
|
758
907
|
}
|
|
759
908
|
|
|
760
909
|
module.exports = {
|
package/lib/commands/app.js
CHANGED
|
@@ -24,13 +24,12 @@ function setupAppCommands(program) {
|
|
|
24
24
|
.command('app')
|
|
25
25
|
.description('Manage applications');
|
|
26
26
|
|
|
27
|
-
// Register command
|
|
27
|
+
// Register command (controller and environment from config.yaml)
|
|
28
28
|
app
|
|
29
29
|
.command('register <appKey>')
|
|
30
30
|
.description('Register application and get pipeline credentials')
|
|
31
|
-
.requiredOption('-e, --environment <env>', 'Environment ID or key')
|
|
32
|
-
.option('-c, --controller <url>', 'Controller URL (overrides variables.yaml)')
|
|
33
31
|
.option('-p, --port <port>', 'Application port (default: from variables.yaml)')
|
|
32
|
+
.option('-u, --url <url>', 'Application URL. If omitted: app.url, deployment.dataplaneUrl or deployment.appUrl in variables.yaml; else http://localhost:{build.localPort or port}')
|
|
34
33
|
.option('-n, --name <name>', 'Override display name')
|
|
35
34
|
.option('-d, --description <desc>', 'Override description')
|
|
36
35
|
.action(async(appKey, options) => {
|
|
@@ -42,12 +41,10 @@ function setupAppCommands(program) {
|
|
|
42
41
|
}
|
|
43
42
|
});
|
|
44
43
|
|
|
45
|
-
// List command
|
|
44
|
+
// List command (controller and environment from config.yaml)
|
|
46
45
|
app
|
|
47
46
|
.command('list')
|
|
48
47
|
.description('List applications')
|
|
49
|
-
.requiredOption('-e, --environment <env>', 'Environment ID or key')
|
|
50
|
-
.option('-c, --controller <url>', 'Controller URL (optional, uses configured controller if not provided)')
|
|
51
48
|
.action(async(options) => {
|
|
52
49
|
try {
|
|
53
50
|
await listApplications(options);
|
|
@@ -57,12 +54,10 @@ function setupAppCommands(program) {
|
|
|
57
54
|
}
|
|
58
55
|
});
|
|
59
56
|
|
|
60
|
-
// Rotate secret command
|
|
57
|
+
// Rotate secret command (controller and environment from config.yaml)
|
|
61
58
|
app
|
|
62
59
|
.command('rotate-secret <appKey>')
|
|
63
60
|
.description('Rotate pipeline ClientSecret for an application')
|
|
64
|
-
.requiredOption('-e, --environment <env>', 'Environment ID or key')
|
|
65
|
-
.option('-c, --controller <url>', 'Controller URL (optional, uses configured controller if not provided)')
|
|
66
61
|
.action(async(appKey, options) => {
|
|
67
62
|
try {
|
|
68
63
|
await rotateSecret(appKey, options);
|