@aifabrix/builder 2.41.0 → 2.42.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/docs-rules.mdc +30 -0
- package/README.md +2 -2
- package/integration/hubspot/README.md +11 -5
- package/integration/hubspot/application.json +54 -0
- package/integration/hubspot/create-hubspot.js +9 -136
- package/integration/hubspot/env.template +3 -4
- package/integration/hubspot/hubspot-datasource-company.json +343 -5
- package/integration/hubspot/hubspot-datasource-contact.json +413 -5
- package/integration/hubspot/hubspot-datasource-deal.json +341 -4
- package/integration/hubspot/hubspot-datasource-users.json +116 -0
- package/integration/hubspot/hubspot-deploy.json +1250 -108
- package/integration/hubspot/hubspot-system.json +15 -32
- package/integration/hubspot/test-dataplane-down-tests.js +17 -16
- package/integration/hubspot/test-dataplane-down.js +2 -2
- package/jest.config.manual.js +2 -1
- package/lib/api/external-test.api.js +111 -0
- package/lib/api/index.js +42 -19
- package/lib/api/pipeline.api.js +66 -120
- package/lib/api/types/pipeline.types.js +37 -0
- package/lib/api/wizard-platform.api.js +61 -0
- package/lib/api/wizard.api.js +36 -2
- package/lib/app/config.js +23 -11
- package/lib/app/index.js +5 -3
- package/lib/app/prompts.js +46 -31
- package/lib/app/readme.js +11 -4
- package/lib/app/run-env-compose.js +64 -1
- package/lib/app/run-helpers.js +1 -1
- package/lib/app/show-display.js +1 -1
- package/lib/cli/setup-app.js +45 -14
- package/lib/cli/setup-credential-deployment.js +31 -6
- package/lib/cli/setup-dev.js +27 -0
- package/lib/cli/setup-environment.js +12 -4
- package/lib/cli/setup-external-system.js +19 -4
- package/lib/cli/setup-infra.js +54 -14
- package/lib/cli/setup-utility.js +117 -21
- package/lib/commands/auth-config.js +22 -12
- package/lib/commands/credential-env.js +162 -0
- package/lib/commands/credential-list.js +17 -22
- package/lib/commands/credential-push.js +96 -0
- package/lib/commands/datasource.js +77 -6
- package/lib/commands/dev-init.js +39 -1
- package/lib/commands/repair-auth-config.js +99 -0
- package/lib/commands/repair-datasource-keys.js +208 -0
- package/lib/commands/repair-datasource.js +235 -0
- package/lib/commands/repair-env-template.js +348 -0
- package/lib/commands/repair-internal.js +85 -0
- package/lib/commands/repair-rbac.js +158 -0
- package/lib/commands/repair.js +518 -0
- package/lib/commands/secrets-set.js +6 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/up-dataplane.js +90 -6
- package/lib/commands/upload.js +71 -40
- package/lib/commands/wizard-core-helpers.js +230 -5
- package/lib/commands/wizard-core.js +68 -29
- package/lib/commands/wizard-dataplane.js +1 -1
- package/lib/commands/wizard-entity-selection.js +43 -0
- package/lib/commands/wizard-headless.js +49 -5
- package/lib/commands/wizard-helpers.js +7 -3
- package/lib/commands/wizard.js +93 -64
- package/lib/core/config.js +7 -1
- package/lib/core/secrets.js +33 -12
- package/lib/datasource/deploy.js +12 -3
- package/lib/datasource/test-e2e.js +219 -0
- package/lib/datasource/test-integration.js +154 -0
- package/lib/deployment/deployer.js +7 -5
- package/lib/external-system/download-helpers.js +3 -1
- package/lib/external-system/download.js +182 -204
- package/lib/external-system/generator.js +204 -56
- package/lib/external-system/test-execution.js +2 -1
- package/lib/external-system/test-system-level.js +73 -0
- package/lib/external-system/test.js +51 -18
- package/lib/generator/external-controller-manifest.js +29 -2
- package/lib/generator/external-schema-utils.js +4 -2
- package/lib/generator/external.js +10 -3
- package/lib/generator/index.js +4 -1
- package/lib/generator/split-readme.js +1 -0
- package/lib/generator/split-variables.js +7 -1
- package/lib/generator/split.js +194 -54
- package/lib/generator/wizard-prompts-secondary.js +326 -0
- package/lib/generator/wizard-prompts.js +105 -106
- package/lib/generator/wizard-readme.js +91 -0
- package/lib/generator/wizard.js +180 -179
- package/lib/infrastructure/compose.js +11 -1
- package/lib/infrastructure/index.js +11 -3
- package/lib/infrastructure/services.js +22 -11
- package/lib/schema/application-schema.json +8 -5
- package/lib/schema/external-datasource.schema.json +49 -26
- package/lib/schema/external-system.schema.json +82 -6
- package/lib/schema/wizard-config.schema.json +23 -1
- package/lib/utils/api.js +38 -10
- package/lib/utils/auth-headers.js +8 -7
- package/lib/utils/compose-generator.js +1 -1
- package/lib/utils/compose-handlebars-helpers.js +11 -0
- package/lib/utils/config-format-preference.js +51 -0
- package/lib/utils/config-format.js +36 -0
- package/lib/utils/configuration-env-resolver.js +179 -0
- package/lib/utils/credential-display.js +83 -0
- package/lib/utils/credential-secrets-env.js +115 -25
- package/lib/utils/dataplane-pipeline-warning.js +28 -0
- package/lib/utils/deployment-validation-helpers.js +4 -4
- package/lib/utils/dev-ca-install.js +139 -0
- package/lib/utils/env-copy.js +23 -3
- package/lib/utils/error-formatters/http-status-errors.js +0 -1
- package/lib/utils/error-formatters/permission-errors.js +0 -1
- package/lib/utils/error-formatters/validation-errors.js +0 -1
- package/lib/utils/external-readme.js +89 -30
- package/lib/utils/external-system-display.js +59 -1
- package/lib/utils/external-system-test-helpers.js +21 -8
- package/lib/utils/external-system-validators.js +3 -0
- package/lib/utils/file-upload.js +20 -50
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/infra-status.js +50 -44
- package/lib/utils/local-secrets.js +5 -5
- package/lib/utils/paths.js +85 -4
- package/lib/utils/secrets-canonical.js +93 -0
- package/lib/utils/secrets-generator.js +20 -0
- package/lib/utils/secrets-helpers.js +75 -89
- package/lib/utils/test-log-writer.js +56 -0
- package/lib/utils/token-manager.js +24 -32
- package/lib/validation/env-template-auth.js +157 -0
- package/lib/validation/env-template-kv.js +41 -0
- package/lib/validation/external-manifest-validator.js +25 -0
- package/lib/validation/external-system-auth-rules.js +86 -0
- package/lib/validation/validate-batch.js +149 -0
- package/lib/validation/validate-datasource-keys-api.js +33 -0
- package/lib/validation/validate-display.js +94 -16
- package/lib/validation/validate.js +25 -12
- package/lib/validation/validator.js +7 -9
- package/lib/validation/wizard-datasource-validation.js +50 -0
- package/package.json +7 -2
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +5 -5
- package/templates/applications/dataplane/rbac.yaml +2 -2
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/external-system/README.md.hbs +75 -22
- package/templates/external-system/deploy.js.hbs +4 -2
- package/templates/external-system/external-datasource.yaml.hbs +217 -0
- package/templates/external-system/external-system.json.hbs +1 -18
- package/templates/infra/compose.yaml.hbs +6 -0
- package/templates/python/docker-compose.hbs +4 -4
- package/templates/typescript/docker-compose.hbs +4 -4
- package/integration/hubspot/application.yaml +0 -37
package/lib/cli/setup-app.js
CHANGED
|
@@ -17,12 +17,20 @@ const { handleCommandError } = require('../utils/cli-utils');
|
|
|
17
17
|
* @param {Object} options - Raw CLI options
|
|
18
18
|
* @returns {Object} Normalized options
|
|
19
19
|
*/
|
|
20
|
+
const VALID_ENTITY_TYPES = ['recordStorage', 'documentStorage', 'vectorStore', 'messageService', 'none'];
|
|
21
|
+
|
|
20
22
|
function normalizeExternalOptions(options) {
|
|
21
23
|
const normalized = { ...options };
|
|
22
24
|
if (options.displayName) normalized.systemDisplayName = options.displayName;
|
|
23
25
|
if (options.description) normalized.systemDescription = options.description;
|
|
24
26
|
if (options.systemType) normalized.systemType = options.systemType;
|
|
25
27
|
if (options.authType) normalized.authType = options.authType;
|
|
28
|
+
if (options.entityType) {
|
|
29
|
+
if (!VALID_ENTITY_TYPES.includes(options.entityType)) {
|
|
30
|
+
throw new Error(`Invalid --entity-type. Must be one of: ${VALID_ENTITY_TYPES.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
normalized.entityType = options.entityType;
|
|
33
|
+
}
|
|
26
34
|
if (options.datasources !== undefined) {
|
|
27
35
|
const parsedCount = parseInt(options.datasources, 10);
|
|
28
36
|
if (Number.isNaN(parsedCount) || parsedCount < 1 || parsedCount > 10) {
|
|
@@ -48,6 +56,7 @@ function validateNonInteractiveExternalOptions(normalizedOptions) {
|
|
|
48
56
|
if (!normalizedOptions.systemDescription) missing.push('--description');
|
|
49
57
|
if (!normalizedOptions.systemType) missing.push('--system-type');
|
|
50
58
|
if (!normalizedOptions.authType) missing.push('--auth-type');
|
|
59
|
+
if (!normalizedOptions.entityType) missing.push('--entity-type');
|
|
51
60
|
if (!normalizedOptions.datasourceCount) missing.push('--datasources');
|
|
52
61
|
if (missing.length > 0) {
|
|
53
62
|
throw new Error(`Missing required options for non-interactive external create: ${missing.join(', ')}`);
|
|
@@ -75,14 +84,14 @@ async function handleCreateCommand(appName, options) {
|
|
|
75
84
|
const wizardOptions = { app: appName, ...options };
|
|
76
85
|
const normalizedOptions = normalizeExternalOptions(options);
|
|
77
86
|
|
|
78
|
-
const isExternalType = options.type === 'external';
|
|
87
|
+
const isExternalType = options.type === 'external' || !options.type;
|
|
79
88
|
const isNonInteractive = process.stdin && process.stdin.isTTY === false;
|
|
80
89
|
|
|
81
90
|
if (isExternalType && !options.wizard && isNonInteractive) {
|
|
82
91
|
validateNonInteractiveExternalOptions(normalizedOptions);
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
const shouldUseWizard = options.wizard && (options.type === 'external' ||
|
|
94
|
+
const shouldUseWizard = options.wizard && (options.type === 'external' || !options.type);
|
|
86
95
|
if (shouldUseWizard) {
|
|
87
96
|
const { handleWizard } = require('../commands/wizard');
|
|
88
97
|
await handleWizard(wizardOptions);
|
|
@@ -101,7 +110,7 @@ function setupCreateCommand(program) {
|
|
|
101
110
|
.option('-a, --authentication', 'Requires authentication/RBAC')
|
|
102
111
|
.option('-l, --language <lang>', 'Runtime language (typescript/python)')
|
|
103
112
|
.option('-t, --template <name>', 'Template to use (e.g., miso-controller, keycloak)')
|
|
104
|
-
.option('--type <type>', 'Application type (webapp, api, service, functionapp, external)', '
|
|
113
|
+
.option('--type <type>', 'Application type (webapp, api, service, functionapp, external)', 'external')
|
|
105
114
|
.option('--app', 'Generate minimal application files (package.json, index.ts or requirements.txt, main.py)')
|
|
106
115
|
.option('-g, --github', 'Generate GitHub Actions workflows')
|
|
107
116
|
.option('--github-steps <steps>', 'Extra GitHub workflow steps (comma-separated, e.g., npm,test)')
|
|
@@ -110,7 +119,8 @@ function setupCreateCommand(program) {
|
|
|
110
119
|
.option('--display-name <name>', 'External system display name')
|
|
111
120
|
.option('--description <desc>', 'External system description')
|
|
112
121
|
.option('--system-type <type>', 'External system type (openapi, mcp, custom)')
|
|
113
|
-
.option('--auth-type <type>', 'External system auth type (oauth2, apikey, basic)')
|
|
122
|
+
.option('--auth-type <type>', 'External system auth type (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none)')
|
|
123
|
+
.option('--entity-type <type>', 'Entity type for datasources (recordStorage, documentStorage, vectorStore, messageService, none)')
|
|
114
124
|
.option('--datasources <count>', 'Number of datasources to create')
|
|
115
125
|
.action(async(appName, options) => {
|
|
116
126
|
try {
|
|
@@ -130,6 +140,7 @@ Examples:
|
|
|
130
140
|
$ aifabrix wizard my-integration --silent Run headless with integration/my-integration/wizard.yaml (no prompts)
|
|
131
141
|
$ aifabrix wizard -a my-integration Same as above (app name set)
|
|
132
142
|
$ aifabrix wizard --config wizard.yaml Run headless from a wizard config file
|
|
143
|
+
$ aifabrix wizard hubspot-test-v2 --debug Enable debug output and save debug manifests on validation failure
|
|
133
144
|
|
|
134
145
|
Config path: When appName is provided, integration/<appName>/wizard.yaml is used for load/save and error.log.
|
|
135
146
|
To change settings after a run, edit that file and run "aifabrix wizard <app>" again.
|
|
@@ -140,6 +151,7 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`;
|
|
|
140
151
|
.option('-a, --app <app>', 'Application name (synonym for positional appName)')
|
|
141
152
|
.option('--config <file>', 'Run headless using a wizard.yaml file (appName, mode, source, credential, preferences)')
|
|
142
153
|
.option('--silent', 'Run with saved integration/<app>/wizard.yaml only; no prompts (requires app name and existing wizard.yaml)')
|
|
154
|
+
.option('--debug', 'Enable debug output and save debug manifests on validation failure')
|
|
143
155
|
.addHelpText('after', wizardHelp)
|
|
144
156
|
.action(async(positionalAppName, options) => {
|
|
145
157
|
try {
|
|
@@ -280,6 +292,29 @@ function setupShellTestStopCommands(program) {
|
|
|
280
292
|
});
|
|
281
293
|
}
|
|
282
294
|
|
|
295
|
+
async function runTestE2ECommand(appName, options) {
|
|
296
|
+
const pathsUtil = require('../utils/paths');
|
|
297
|
+
const appType = await pathsUtil.detectAppType(appName).catch(() => null);
|
|
298
|
+
if (appType && appType.baseDir === 'integration') {
|
|
299
|
+
const { runTestE2EForExternalSystem } = require('../commands/test-e2e-external');
|
|
300
|
+
const { success, results } = await runTestE2EForExternalSystem(appName, {
|
|
301
|
+
env: options.env,
|
|
302
|
+
debug: options.debug,
|
|
303
|
+
verbose: options.verbose,
|
|
304
|
+
async: options.async !== false
|
|
305
|
+
});
|
|
306
|
+
results.forEach(r => {
|
|
307
|
+
const icon = r.success ? chalk.green('✓') : chalk.red('✗');
|
|
308
|
+
const msg = r.error ? `${r.key}: ${r.error}` : r.key;
|
|
309
|
+
logger.log(` ${icon} ${msg}`);
|
|
310
|
+
});
|
|
311
|
+
if (!success) process.exit(1);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const { runAppTestE2e } = require('../commands/app-test');
|
|
315
|
+
await runAppTestE2e(appName, { env: options.env });
|
|
316
|
+
}
|
|
317
|
+
|
|
283
318
|
function setupInstallTestE2eLintCommands(program) {
|
|
284
319
|
program.command('install <app>')
|
|
285
320
|
.description('Install dependencies in container (builder apps only)')
|
|
@@ -301,18 +336,14 @@ function setupInstallTestE2eLintCommands(program) {
|
|
|
301
336
|
});
|
|
302
337
|
|
|
303
338
|
program.command('test-e2e <app>')
|
|
304
|
-
.description('Run e2e tests in container
|
|
305
|
-
.option('--env <env>', 'dev
|
|
339
|
+
.description('Run e2e tests (builder: in container; external system: all datasources via dataplane)')
|
|
340
|
+
.option('-e, --env <env>', 'Environment: dev, tst, or pro (builder: dev/tst for container)')
|
|
341
|
+
.option('-v, --verbose', 'Show detailed step output and poll progress')
|
|
342
|
+
.option('--debug', 'Include debug output and write log to integration/<app>/logs/')
|
|
343
|
+
.option('--no-async', 'Use sync mode (no polling); single POST per datasource')
|
|
306
344
|
.action(async(appName, options) => {
|
|
307
345
|
try {
|
|
308
|
-
|
|
309
|
-
const appType = await pathsUtil.detectAppType(appName).catch(() => null);
|
|
310
|
-
if (appType && appType.baseDir === 'integration') {
|
|
311
|
-
logger.log(chalk.gray('test-e2e is for builder applications only. Use aifabrix shell <app> then make test:e2e or pnpm test:e2e.'));
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const { runAppTestE2e } = require('../commands/app-test');
|
|
315
|
-
await runAppTestE2e(appName, { env: options.env });
|
|
346
|
+
await runTestE2ECommand(appName, options);
|
|
316
347
|
} catch (error) {
|
|
317
348
|
handleCommandError(error, 'test-e2e');
|
|
318
349
|
process.exit(1);
|
|
@@ -11,8 +11,37 @@ const chalk = require('chalk');
|
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
12
|
const { handleCommandError } = require('../utils/cli-utils');
|
|
13
13
|
const { runCredentialList } = require('../commands/credential-list');
|
|
14
|
+
const { runCredentialEnv } = require('../commands/credential-env');
|
|
15
|
+
const { runCredentialPush } = require('../commands/credential-push');
|
|
14
16
|
const { runDeploymentList } = require('../commands/deployment-list');
|
|
15
17
|
|
|
18
|
+
function setupCredentialEnvAndPush(credential) {
|
|
19
|
+
credential
|
|
20
|
+
.command('env <system-key>')
|
|
21
|
+
.description('Prompt for KV_* credential values and write integration/<system-key>/.env')
|
|
22
|
+
.action(async(systemKey, _options) => {
|
|
23
|
+
try {
|
|
24
|
+
await runCredentialEnv(systemKey);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
27
|
+
handleCommandError(error, 'credential env');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
credential
|
|
32
|
+
.command('push <system-key>')
|
|
33
|
+
.description('Push credential secrets from .env to Dataplane (KV_* vars)')
|
|
34
|
+
.action(async(systemKey, _options) => {
|
|
35
|
+
try {
|
|
36
|
+
await runCredentialPush(systemKey);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
39
|
+
handleCommandError(error, 'credential push');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
16
45
|
/**
|
|
17
46
|
* Sets up credential and deployment list commands
|
|
18
47
|
* @param {Command} program - Commander program instance
|
|
@@ -21,19 +50,15 @@ function setupCredentialDeploymentCommands(program) {
|
|
|
21
50
|
const credential = program
|
|
22
51
|
.command('credential')
|
|
23
52
|
.description('Manage credentials');
|
|
24
|
-
|
|
53
|
+
setupCredentialEnvAndPush(credential);
|
|
25
54
|
credential
|
|
26
55
|
.command('list')
|
|
27
|
-
.description('
|
|
28
|
-
.option('--controller <url>', 'Controller URL (default: from config)')
|
|
29
|
-
.option('--dataplane <url>', 'Dataplane URL (default: resolved from controller + environment)')
|
|
56
|
+
.description('Get credentials from Dataplane')
|
|
30
57
|
.option('--active-only', 'List only active credentials')
|
|
31
58
|
.option('--page-size <n>', 'Items per page', '50')
|
|
32
59
|
.action(async(options) => {
|
|
33
60
|
try {
|
|
34
61
|
const opts = {
|
|
35
|
-
controller: options.controller,
|
|
36
|
-
dataplane: options.dataplane,
|
|
37
62
|
activeOnly: options.activeOnly,
|
|
38
63
|
pageSize: parseInt(options.pageSize, 10) || 50
|
|
39
64
|
};
|
package/lib/cli/setup-dev.js
CHANGED
|
@@ -33,6 +33,7 @@ async function displayDevConfig(devId) {
|
|
|
33
33
|
const controller = await config.getControllerUrl();
|
|
34
34
|
|
|
35
35
|
const optionalConfigVars = [
|
|
36
|
+
{ key: 'format', value: (await config.getFormat()) ?? '(not set)' },
|
|
36
37
|
{ key: 'aifabrix-home', value: await config.getAifabrixHomeOverride() },
|
|
37
38
|
{ key: 'aifabrix-secrets', value: await config.getAifabrixSecretsPath() },
|
|
38
39
|
{ key: 'aifabrix-env-config', value: await config.getAifabrixEnvConfigPath() },
|
|
@@ -59,6 +60,18 @@ async function displayDevConfig(devId) {
|
|
|
59
60
|
logger.log('');
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Handle dev set-format command
|
|
65
|
+
* @param {string} format - Format value (json | yaml)
|
|
66
|
+
* @returns {Promise<void>}
|
|
67
|
+
*/
|
|
68
|
+
async function handleSetFormat(format) {
|
|
69
|
+
await config.setFormat(format);
|
|
70
|
+
logger.log(chalk.green(`✓ Format set to ${format.toLowerCase()}`));
|
|
71
|
+
const devId = await config.getDeveloperId();
|
|
72
|
+
await displayDevConfig(devId);
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
/**
|
|
63
76
|
* Register dev config and set-id commands.
|
|
64
77
|
* @param {Command} dev - dev subcommand group
|
|
@@ -90,6 +103,18 @@ function setupDevConfigCommands(dev) {
|
|
|
90
103
|
}
|
|
91
104
|
});
|
|
92
105
|
|
|
106
|
+
dev
|
|
107
|
+
.command('set-format <format>')
|
|
108
|
+
.description('Set default output format for download/convert (json | yaml); used when --format not passed')
|
|
109
|
+
.action(async(format) => {
|
|
110
|
+
try {
|
|
111
|
+
await handleSetFormat(format);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
handleCommandError(error, 'dev set-format');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
93
118
|
dev
|
|
94
119
|
.command('set-id <id>')
|
|
95
120
|
.description('Set developer ID (convenience alias for "dev config --set-id")')
|
|
@@ -121,6 +146,8 @@ function setupDevInitCommand(dev) {
|
|
|
121
146
|
.requiredOption('--developer-id <id>', 'Developer ID (same as dev add; e.g. 01)')
|
|
122
147
|
.requiredOption('--server <url>', 'Builder Server base URL (e.g. https://dev.aifabrix.dev)')
|
|
123
148
|
.requiredOption('--pin <pin>', 'One-time PIN from your admin')
|
|
149
|
+
.option('-y, --yes', 'Auto-install development CA without prompt when certificate is untrusted')
|
|
150
|
+
.option('--no-install-ca', 'Do not offer CA install; fail with manual instructions on untrusted certificate')
|
|
124
151
|
.action(async(options) => {
|
|
125
152
|
try {
|
|
126
153
|
await runDevInit(options);
|
|
@@ -25,30 +25,38 @@ function setupEnvironmentCommands(program) {
|
|
|
25
25
|
|
|
26
26
|
const environment = program
|
|
27
27
|
.command('environment')
|
|
28
|
-
.description('
|
|
28
|
+
.description('Deploy and manage Miso Controller environments (dev, tst, pro, miso)');
|
|
29
|
+
|
|
30
|
+
const deployExamples = `
|
|
31
|
+
Examples:
|
|
32
|
+
$ aifabrix environment deploy dev
|
|
33
|
+
$ aifabrix environment deploy tst --preset m
|
|
34
|
+
$ aifabrix environment deploy dev --config ./env-config.json --no-poll`;
|
|
29
35
|
|
|
30
36
|
environment
|
|
31
37
|
.command('deploy <env>')
|
|
32
|
-
.description('Deploy
|
|
38
|
+
.description('Deploy environment infrastructure in Miso Controller (run before deploy <app>)')
|
|
33
39
|
.option('--config <file>', 'Environment configuration file (optional if --preset is used)')
|
|
34
40
|
.option('--preset <size>', 'Environment size preset: s, m, l, xl (default: s)', 's')
|
|
35
41
|
.option('--skip-validation', 'Skip environment validation')
|
|
36
42
|
.option('--poll', 'Poll for deployment status', true)
|
|
37
43
|
.option('--no-poll', 'Do not poll for status')
|
|
44
|
+
.addHelpText('after', deployExamples)
|
|
38
45
|
.action(deployEnvHandler);
|
|
39
46
|
|
|
40
47
|
const env = program
|
|
41
48
|
.command('env')
|
|
42
|
-
.description('
|
|
49
|
+
.description('Deploy and manage Miso Controller environments (alias for environment)');
|
|
43
50
|
|
|
44
51
|
env
|
|
45
52
|
.command('deploy <env>')
|
|
46
|
-
.description('Deploy
|
|
53
|
+
.description('Deploy environment infrastructure in Miso Controller (run before deploy <app>)')
|
|
47
54
|
.option('--config <file>', 'Environment configuration file (optional if --preset is used)')
|
|
48
55
|
.option('--preset <size>', 'Environment size preset: s, m, l, xl (default: s)', 's')
|
|
49
56
|
.option('--skip-validation', 'Skip environment validation')
|
|
50
57
|
.option('--poll', 'Poll for deployment status', true)
|
|
51
58
|
.option('--no-poll', 'Do not poll for status')
|
|
59
|
+
.addHelpText('after', deployExamples)
|
|
52
60
|
.action(deployEnvHandler);
|
|
53
61
|
}
|
|
54
62
|
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI external system command setup (download,
|
|
2
|
+
* CLI external system command setup (download, upload, delete, test-integration).
|
|
3
|
+
*
|
|
4
|
+
* Registers these commands on the Commander program:
|
|
5
|
+
* - download <system-key> – Download external system from dataplane to integration/<system-key>/
|
|
6
|
+
* - upload <system-key> – Upload to dataplane (validate → publish; no controller deploy)
|
|
7
|
+
* - delete <system-key> – Delete external system and associated datasources from dataplane
|
|
8
|
+
* - test-integration <app> – Run integration tests (builder: in container; external: via dataplane pipeline)
|
|
3
9
|
*
|
|
4
10
|
* @fileoverview External system command definitions for AI Fabrix Builder CLI
|
|
5
11
|
* @author AI Fabrix Team
|
|
6
12
|
* @version 2.0.0
|
|
13
|
+
* @see docs/commands/external-integration.md - User-facing command reference
|
|
14
|
+
* @see docs/external-systems.md - External systems guide and workflow
|
|
7
15
|
*/
|
|
8
16
|
|
|
9
17
|
const { handleCommandError } = require('../utils/cli-utils');
|
|
@@ -11,11 +19,18 @@ const { handleCommandError } = require('../utils/cli-utils');
|
|
|
11
19
|
function setupDownloadCommand(program) {
|
|
12
20
|
program.command('download <system-key>')
|
|
13
21
|
.description('Download external system from dataplane to local development structure')
|
|
22
|
+
.option('--format <format>', 'Output format: json | yaml (default: yaml or config format)')
|
|
14
23
|
.option('--dry-run', 'Show what would be downloaded without actually downloading')
|
|
24
|
+
.option('--force', 'Overwrite existing README.md without prompting')
|
|
15
25
|
.action(async(systemKey, options) => {
|
|
16
26
|
try {
|
|
27
|
+
const config = require('../core/config');
|
|
28
|
+
const effectiveFormat = (options.format || (await config.getFormat()) || 'yaml').trim().toLowerCase();
|
|
29
|
+
if (effectiveFormat !== 'json' && effectiveFormat !== 'yaml') {
|
|
30
|
+
throw new Error('Option --format must be \'json\' or \'yaml\'');
|
|
31
|
+
}
|
|
17
32
|
const download = require('../external-system/download');
|
|
18
|
-
await download.downloadExternalSystem(systemKey, options);
|
|
33
|
+
await download.downloadExternalSystem(systemKey, { ...options, format: effectiveFormat });
|
|
19
34
|
} catch (error) {
|
|
20
35
|
handleCommandError(error, 'download');
|
|
21
36
|
process.exit(1);
|
|
@@ -27,7 +42,6 @@ function setupUploadCommand(program) {
|
|
|
27
42
|
program.command('upload <system-key>')
|
|
28
43
|
.description('Upload external system to dataplane (upload → validate → publish; no controller deploy)')
|
|
29
44
|
.option('--dry-run', 'Validate and build payload only; no API calls')
|
|
30
|
-
.option('--dataplane <url>', 'Dataplane URL (default: discovered from controller)')
|
|
31
45
|
.action(async(systemKey, options) => {
|
|
32
46
|
try {
|
|
33
47
|
const upload = require('../commands/upload');
|
|
@@ -97,7 +111,7 @@ async function tryBuilderTestIntegration(appName, options) {
|
|
|
97
111
|
*/
|
|
98
112
|
async function runExternalSystemTestIntegration(appName, options) {
|
|
99
113
|
const test = require('../external-system/test');
|
|
100
|
-
const opts = { ...options, environment: options.env || options.environment };
|
|
114
|
+
const opts = { ...options, environment: options.env || options.environment, debug: options.debug };
|
|
101
115
|
const results = await test.testExternalSystemIntegration(appName, opts);
|
|
102
116
|
test.displayIntegrationTestResults(results, options.verbose);
|
|
103
117
|
if (!results.success) process.exit(1);
|
|
@@ -131,6 +145,7 @@ function setupExternalSystemTestCommands(program) {
|
|
|
131
145
|
.option('-p, --payload <file>', 'Path to custom test payload file')
|
|
132
146
|
.option('-e, --env <env>', 'Environment: dev, tst, or pro (default: from aifabrix auth config)')
|
|
133
147
|
.option('-v, --verbose', 'Show detailed test output')
|
|
148
|
+
.option('--debug', 'Include debug output and write log to integration/<app>/logs/')
|
|
134
149
|
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
135
150
|
.action(async(appName, options) => {
|
|
136
151
|
try {
|
package/lib/cli/setup-infra.js
CHANGED
|
@@ -19,8 +19,34 @@ const { handleUpMiso } = require('../commands/up-miso');
|
|
|
19
19
|
const { handleUpDataplane } = require('../commands/up-dataplane');
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param {Object}
|
|
22
|
+
* Persists optional service flag to config when explicitly set.
|
|
23
|
+
* @param {Object} cfg - Config object (mutated)
|
|
24
|
+
* @param {string} key - Config key (traefik, pgadmin, redisCommander)
|
|
25
|
+
* @param {boolean} value - Value to set
|
|
26
|
+
* @param {string} label - Label for log message
|
|
27
|
+
*/
|
|
28
|
+
async function persistOptionalServiceFlag(cfg, key, value, label) {
|
|
29
|
+
cfg[key] = value;
|
|
30
|
+
await config.saveConfig(cfg);
|
|
31
|
+
logger.log(chalk.green(`✓ ${label} ${value ? 'enabled' : 'disabled'} and saved to config`));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolves effective boolean from option vs config.
|
|
36
|
+
* @param {*} optValue - options.traefik | options.pgAdmin | options.redisAdmin
|
|
37
|
+
* @param {*} cfgValue - cfg.traefik | cfg.pgadmin | cfg.redisCommander
|
|
38
|
+
* @param {boolean} defaultWhenUndef - Default when config value is undefined
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
function resolveFlag(optValue, cfgValue, defaultWhenUndef = true) {
|
|
42
|
+
if (optValue === true) return true;
|
|
43
|
+
if (optValue === false) return false;
|
|
44
|
+
return cfgValue !== false && (cfgValue === true || defaultWhenUndef);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Runs the up-infra command: resolves developer ID, traefik, pgAdmin, redisAdmin, and starts infra.
|
|
49
|
+
* @param {Object} options - Commander options (developer, traefik, pgAdmin, redisAdmin)
|
|
24
50
|
* @returns {Promise<void>}
|
|
25
51
|
*/
|
|
26
52
|
async function runUpInfraCommand(options) {
|
|
@@ -37,24 +63,33 @@ async function runUpInfraCommand(options) {
|
|
|
37
63
|
logger.log(chalk.green(`✓ Developer ID set to ${id}`));
|
|
38
64
|
}
|
|
39
65
|
const cfg = await config.getConfig();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
const flagSpecs = [
|
|
67
|
+
{ opt: options.traefik, key: 'traefik', label: 'Traefik' },
|
|
68
|
+
{ opt: options.pgAdmin, key: 'pgadmin', label: 'pgAdmin' },
|
|
69
|
+
{ opt: options.redisAdmin, key: 'redisCommander', label: 'Redis Commander' }
|
|
70
|
+
];
|
|
71
|
+
for (const { opt, key, label } of flagSpecs) {
|
|
72
|
+
if (opt === true || opt === false) {
|
|
73
|
+
await persistOptionalServiceFlag(cfg, key, opt, label);
|
|
74
|
+
}
|
|
48
75
|
}
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
await infra.startInfra(developerId, {
|
|
77
|
+
traefik: resolveFlag(options.traefik, cfg.traefik, false),
|
|
78
|
+
pgadmin: resolveFlag(options.pgAdmin, cfg.pgadmin, true),
|
|
79
|
+
redisCommander: resolveFlag(options.redisAdmin, cfg.redisCommander, true),
|
|
80
|
+
adminPwd: options.adminPwd
|
|
81
|
+
});
|
|
51
82
|
}
|
|
52
83
|
|
|
53
84
|
function setupUpInfraCommand(program) {
|
|
54
85
|
program.command('up-infra')
|
|
55
|
-
.description('Start local infrastructure: Postgres, Redis, optional Traefik')
|
|
86
|
+
.description('Start local infrastructure: Postgres, Redis, optional pgAdmin, Redis Commander, Traefik')
|
|
56
87
|
.option('-d, --developer <id>', 'Set developer ID and start infrastructure')
|
|
57
88
|
.option('--adminPwd <password>', 'Override default admin password for new install (Postgres, pgAdmin, Redis Commander)')
|
|
89
|
+
.option('--pgAdmin', 'Include pgAdmin web UI and save to config')
|
|
90
|
+
.option('--no-pgAdmin', 'Exclude pgAdmin and save to config')
|
|
91
|
+
.option('--redisAdmin', 'Include Redis Commander web UI and save to config')
|
|
92
|
+
.option('--no-redisAdmin', 'Exclude Redis Commander and save to config')
|
|
58
93
|
.option('--traefik', 'Include Traefik reverse proxy and save to config')
|
|
59
94
|
.option('--no-traefik', 'Exclude Traefik and save to config')
|
|
60
95
|
.action(async(options) => {
|
|
@@ -175,7 +210,12 @@ function setupDoctorCommand(program) {
|
|
|
175
210
|
}
|
|
176
211
|
if (result.docker === 'ok') {
|
|
177
212
|
try {
|
|
178
|
-
const
|
|
213
|
+
const cfg = await config.getConfig();
|
|
214
|
+
const health = await infra.checkInfraHealth(null, {
|
|
215
|
+
pgadmin: cfg.pgadmin !== false,
|
|
216
|
+
redisCommander: cfg.redisCommander !== false,
|
|
217
|
+
traefik: !!cfg.traefik
|
|
218
|
+
});
|
|
179
219
|
logger.log('\n🏥 Infrastructure Health:');
|
|
180
220
|
Object.entries(health).forEach(([service, status]) => {
|
|
181
221
|
const icon = status === 'healthy' ? '✅' : status === 'unknown' ? '❓' : '❌';
|
package/lib/cli/setup-utility.js
CHANGED
|
@@ -77,9 +77,13 @@ function logSplitJsonResult(result) {
|
|
|
77
77
|
result.datasourceFiles.forEach(filePath => logger.log(` • datasource: ${filePath}`));
|
|
78
78
|
}
|
|
79
79
|
if (result.rbac) {
|
|
80
|
-
logger.log(` • rbac.
|
|
80
|
+
logger.log(` • rbac.yaml: ${result.rbac}`);
|
|
81
|
+
}
|
|
82
|
+
if (result.readmeSkipped) {
|
|
83
|
+
logger.log(` • README.md: (kept existing) ${result.readmeSkipped}`);
|
|
84
|
+
} else if (result.readme) {
|
|
85
|
+
logger.log(` • README.md: ${result.readme}`);
|
|
81
86
|
}
|
|
82
|
-
logger.log(` • README.md: ${result.readme}`);
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
function setupResolveCommand(program) {
|
|
@@ -141,7 +145,7 @@ function setupJsonCommand(program) {
|
|
|
141
145
|
});
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
function
|
|
148
|
+
function setupSplitJsonCommand(program) {
|
|
145
149
|
program.command('split-json <app>')
|
|
146
150
|
.description('Split deployment JSON into component files (env.template, application.yaml, rbac.yml, README.md)')
|
|
147
151
|
.option('-o, --output <dir>', 'Output directory for component files (defaults to same directory as JSON)')
|
|
@@ -153,15 +157,66 @@ function setupSplitJsonConvertShowCommands(program) {
|
|
|
153
157
|
process.exit(1);
|
|
154
158
|
}
|
|
155
159
|
});
|
|
160
|
+
}
|
|
156
161
|
|
|
162
|
+
function setupRepairCommand(program) {
|
|
163
|
+
program.command('repair <app>')
|
|
164
|
+
.description('Repair external integration config: fix drift (file lists, app key, datasource alignment, rbac, manifest)')
|
|
165
|
+
.option('--auth <method>', 'Set authentication method (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none); updates system file and env.template')
|
|
166
|
+
.option('--dry-run', 'Report changes only; do not write')
|
|
167
|
+
.option('--rbac', 'Ensure RBAC permissions per datasource and add default Admin/Reader roles if none exist')
|
|
168
|
+
.option('--expose', 'Set exposed.attributes on each datasource to all fieldMappings.attributes keys')
|
|
169
|
+
.option('--sync', 'Add default sync section to datasources that lack it')
|
|
170
|
+
.option('--test', 'Generate testPayload.payloadTemplate and testPayload.expectedResult from attributes')
|
|
171
|
+
.action(async(appName, options) => {
|
|
172
|
+
try {
|
|
173
|
+
const { repairExternalIntegration } = require('../commands/repair');
|
|
174
|
+
const { detectAppType } = require('../utils/paths');
|
|
175
|
+
const { appPath } = await detectAppType(appName);
|
|
176
|
+
logOfflinePathWhenType(appPath);
|
|
177
|
+
const result = await repairExternalIntegration(appName, {
|
|
178
|
+
auth: options.auth,
|
|
179
|
+
dryRun: options.dryRun,
|
|
180
|
+
rbac: options.rbac,
|
|
181
|
+
expose: options.expose,
|
|
182
|
+
sync: options.sync,
|
|
183
|
+
test: options.test
|
|
184
|
+
});
|
|
185
|
+
if (options.dryRun && result.updated && result.changes.length > 0) {
|
|
186
|
+
logger.log(chalk.yellow('\nWould apply:'));
|
|
187
|
+
result.changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
|
|
188
|
+
} else if (result.updated) {
|
|
189
|
+
logger.log(chalk.green('\n✓ Repaired external integration config.'));
|
|
190
|
+
} else {
|
|
191
|
+
logger.log(chalk.gray('No changes needed; config already matches files on disk.'));
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
handleCommandError(error, 'repair');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function setupConvertCommand(program) {
|
|
157
201
|
program.command('convert <app>')
|
|
158
202
|
.description('Convert integration/external system and datasource config files between JSON and YAML')
|
|
159
|
-
.option('--format <format>', 'Target format: json | yaml (required)')
|
|
203
|
+
.option('--format <format>', 'Target format: json | yaml (required unless config format is set)')
|
|
160
204
|
.option('-f, --force', 'Skip confirmation prompt')
|
|
161
205
|
.action(async(appName, options) => {
|
|
162
206
|
try {
|
|
207
|
+
const config = require('../core/config');
|
|
208
|
+
const effectiveFormat = options.format || (await config.getFormat());
|
|
209
|
+
if (!effectiveFormat) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
'Option --format is required and must be \'json\' or \'yaml\' (or set default with aifabrix dev set-format)'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const normalized = effectiveFormat.trim().toLowerCase();
|
|
215
|
+
if (normalized !== 'json' && normalized !== 'yaml') {
|
|
216
|
+
throw new Error('Option --format must be \'json\' or \'yaml\'');
|
|
217
|
+
}
|
|
163
218
|
const { runConvert } = require('../commands/convert');
|
|
164
|
-
const { converted, deleted } = await runConvert(appName, { format:
|
|
219
|
+
const { converted, deleted } = await runConvert(appName, { format: normalized, force: options.force });
|
|
165
220
|
logger.log(chalk.green('\n✓ Convert complete.'));
|
|
166
221
|
converted.forEach(p => logger.log(` • ${p}`));
|
|
167
222
|
if (deleted.length > 0) {
|
|
@@ -173,7 +228,9 @@ function setupSplitJsonConvertShowCommands(program) {
|
|
|
173
228
|
process.exit(1);
|
|
174
229
|
}
|
|
175
230
|
});
|
|
231
|
+
}
|
|
176
232
|
|
|
233
|
+
function setupShowCommand(program) {
|
|
177
234
|
program.command('show <appKey>')
|
|
178
235
|
.description('Show application info from local builder/ or integration/ (offline) or from controller (--online)')
|
|
179
236
|
.option('--online', 'Fetch application data from the controller')
|
|
@@ -189,25 +246,60 @@ function setupSplitJsonConvertShowCommands(program) {
|
|
|
189
246
|
});
|
|
190
247
|
}
|
|
191
248
|
|
|
249
|
+
function setupSplitJsonConvertShowCommands(program) {
|
|
250
|
+
setupSplitJsonCommand(program);
|
|
251
|
+
setupRepairCommand(program);
|
|
252
|
+
setupConvertCommand(program);
|
|
253
|
+
setupShowCommand(program);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function runValidateCommand(appOrFile, options) {
|
|
257
|
+
const validate = require('../validation/validate');
|
|
258
|
+
const opts = options.opts ? options.opts() : options;
|
|
259
|
+
const integration = opts.integration === true;
|
|
260
|
+
const builder = opts.builder === true;
|
|
261
|
+
const outFormat = (opts.format || 'default').toLowerCase();
|
|
262
|
+
|
|
263
|
+
if (integration || builder) {
|
|
264
|
+
const batchResult = integration && builder
|
|
265
|
+
? await validate.validateAll(opts)
|
|
266
|
+
: integration
|
|
267
|
+
? await validate.validateAllIntegrations(opts)
|
|
268
|
+
: await validate.validateAllBuilderApps(opts);
|
|
269
|
+
if (outFormat === 'json') {
|
|
270
|
+
logger.log(JSON.stringify(batchResult, null, 2));
|
|
271
|
+
} else {
|
|
272
|
+
validate.displayBatchValidationResults(batchResult);
|
|
273
|
+
}
|
|
274
|
+
if (!batchResult.valid) process.exit(1);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!appOrFile || typeof appOrFile !== 'string') {
|
|
279
|
+
logger.log(chalk.red('App name or file path is required, or use --integration / --builder'));
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const result = await validate.validateAppOrFile(appOrFile, opts);
|
|
284
|
+
if (outFormat === 'json') {
|
|
285
|
+
logger.log(JSON.stringify(result, null, 2));
|
|
286
|
+
} else {
|
|
287
|
+
validate.displayValidationResults(result);
|
|
288
|
+
}
|
|
289
|
+
if (!result.valid) process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
|
|
192
292
|
function setupValidateDiffCommands(program) {
|
|
193
|
-
program.command('validate
|
|
194
|
-
.description('Validate application or external integration file')
|
|
293
|
+
program.command('validate [appOrFile]')
|
|
294
|
+
.description('Validate application or external integration file; use --integration or --builder to validate all apps')
|
|
195
295
|
.option('--format <format>', 'Output format: json | default (human-readable)')
|
|
196
|
-
.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const outFormat = (options.format || 'default').toLowerCase();
|
|
201
|
-
if (outFormat === 'json') {
|
|
202
|
-
logger.log(JSON.stringify(result, null, 2));
|
|
203
|
-
} else {
|
|
204
|
-
validate.displayValidationResults(result);
|
|
205
|
-
}
|
|
206
|
-
if (!result.valid) process.exit(1);
|
|
207
|
-
} catch (error) {
|
|
296
|
+
.option('--integration', 'Validate all applications under integration/')
|
|
297
|
+
.option('--builder', 'Validate all applications under builder/')
|
|
298
|
+
.action((appOrFile, options) => {
|
|
299
|
+
runValidateCommand(appOrFile, options).catch((error) => {
|
|
208
300
|
handleCommandError(error, 'validate');
|
|
209
301
|
process.exit(1);
|
|
210
|
-
}
|
|
302
|
+
});
|
|
211
303
|
});
|
|
212
304
|
|
|
213
305
|
program.command('diff <file1> <file2>')
|
|
@@ -238,4 +330,8 @@ function setupUtilityCommands(program) {
|
|
|
238
330
|
setupValidateDiffCommands(program);
|
|
239
331
|
}
|
|
240
332
|
|
|
241
|
-
module.exports = {
|
|
333
|
+
module.exports = {
|
|
334
|
+
setupUtilityCommands,
|
|
335
|
+
resolveSplitJsonApp,
|
|
336
|
+
handleSplitJsonCommand
|
|
337
|
+
};
|