@aifabrix/builder 2.41.0 → 2.42.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/docs-rules.mdc +30 -0
- package/README.md +1 -1
- package/integration/hubspot/README.md +8 -4
- 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 +34 -1
- package/lib/app/config.js +23 -11
- package/lib/app/index.js +3 -1
- package/lib/app/prompts.js +44 -29
- package/lib/app/readme.js +8 -3
- 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 +42 -11
- 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/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 +507 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/upload.js +71 -40
- package/lib/commands/wizard-core-helpers.js +226 -4
- package/lib/commands/wizard-core.js +67 -29
- package/lib/commands/wizard-dataplane.js +1 -1
- package/lib/commands/wizard-entity-selection.js +43 -0
- package/lib/commands/wizard-headless.js +44 -5
- package/lib/commands/wizard-helpers.js +7 -3
- package/lib/commands/wizard.js +86 -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.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 +1 -1
- 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 +294 -0
- package/lib/generator/wizard-prompts.js +105 -106
- package/lib/generator/wizard-readme.js +88 -0
- package/lib/generator/wizard.js +147 -158
- 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 +16 -0
- 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 +56 -29
- 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 +65 -25
- 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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential env command – prompts for KV_* values and writes .env.
|
|
3
|
+
* Used by `aifabrix credential env <system-key>`.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Credential env command – interactive credential capture to .env
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const logger = require('../utils/logger');
|
|
14
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
15
|
+
const { kvEnvKeyToPath } = require('../utils/credential-secrets-env');
|
|
16
|
+
const { parseEnvToMap } = require('../utils/credential-secrets-env');
|
|
17
|
+
|
|
18
|
+
const KV_PREFIX = 'KV_';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Secret var suffixes (use password prompt).
|
|
22
|
+
* @type {Set<string>}
|
|
23
|
+
*/
|
|
24
|
+
const SECRET_SUFFIXES = new Set([
|
|
25
|
+
'CLIENTID', 'CLIENTSECRET', 'APIKEY', 'USERNAME', 'PASSWORD', 'PARAMVALUE',
|
|
26
|
+
'SIGNINGSECRET', 'BEARERTOKEN'
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates system-key format.
|
|
31
|
+
* @param {string} systemKey - System key
|
|
32
|
+
* @throws {Error} If invalid
|
|
33
|
+
*/
|
|
34
|
+
function validateSystemKeyFormat(systemKey) {
|
|
35
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
36
|
+
throw new Error('System key is required and must be a string');
|
|
37
|
+
}
|
|
38
|
+
if (!/^[a-z0-9-_]+$/.test(systemKey)) {
|
|
39
|
+
throw new Error('System key must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extracts KV_* variable names from env.template content.
|
|
45
|
+
* @param {string} content - env.template content
|
|
46
|
+
* @returns {Array<{ key: string, isSecret: boolean }>} KV_* vars to prompt
|
|
47
|
+
*/
|
|
48
|
+
function extractKvVarsFromTemplate(content) {
|
|
49
|
+
if (!content || typeof content !== 'string') return [];
|
|
50
|
+
const vars = [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
const lines = content.split(/\r?\n/);
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
const trimmed = line.trim();
|
|
55
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
56
|
+
const eq = trimmed.indexOf('=');
|
|
57
|
+
if (eq <= 0) continue;
|
|
58
|
+
const key = trimmed.substring(0, eq).trim();
|
|
59
|
+
if (!key.toUpperCase().startsWith(KV_PREFIX)) continue;
|
|
60
|
+
if (kvEnvKeyToPath(key) && !seen.has(key)) {
|
|
61
|
+
seen.add(key);
|
|
62
|
+
const suffix = key.slice(KV_PREFIX.length).split('_').pop() || '';
|
|
63
|
+
vars.push({ key, isSecret: SECRET_SUFFIXES.has(suffix.toUpperCase()) });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return vars;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prompts for KV_* values using inquirer.
|
|
71
|
+
* @async
|
|
72
|
+
* @param {Array<{ key: string, isSecret: boolean }>} vars - Variables to prompt
|
|
73
|
+
* @param {Object} existingMap - Existing .env key-value map (for default values)
|
|
74
|
+
* @returns {Promise<Object.<string, string>>} Key-value map from prompts
|
|
75
|
+
*/
|
|
76
|
+
async function promptForKvValues(vars, existingMap) {
|
|
77
|
+
if (vars.length === 0) return {};
|
|
78
|
+
const inquirer = require('inquirer');
|
|
79
|
+
const questions = vars.map(({ key, isSecret }) => ({
|
|
80
|
+
type: isSecret ? 'password' : 'input',
|
|
81
|
+
name: key,
|
|
82
|
+
message: key,
|
|
83
|
+
default: existingMap[key] || undefined
|
|
84
|
+
}));
|
|
85
|
+
return await inquirer.prompt(questions);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Builds .env content: template lines with KV_* values replaced/merged from prompts.
|
|
90
|
+
* Preserves comments, non-KV lines, and structure; updates KV_* with prompted values.
|
|
91
|
+
* @param {string} templateContent - env.template content
|
|
92
|
+
* @param {Object.<string, string>} promptValues - Values from prompts
|
|
93
|
+
* @returns {string} Final .env content
|
|
94
|
+
*/
|
|
95
|
+
function buildEnvContent(templateContent, promptValues) {
|
|
96
|
+
if (!templateContent || typeof templateContent !== 'string') return '';
|
|
97
|
+
const lines = templateContent.split(/\r?\n/);
|
|
98
|
+
const result = [];
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
102
|
+
result.push(line);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const eq = trimmed.indexOf('=');
|
|
106
|
+
if (eq <= 0) {
|
|
107
|
+
result.push(line);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const key = trimmed.substring(0, eq).trim();
|
|
111
|
+
if (key.toUpperCase().startsWith(KV_PREFIX) && key in promptValues) {
|
|
112
|
+
result.push(`${key}=${promptValues[key]}`);
|
|
113
|
+
} else {
|
|
114
|
+
result.push(line);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result.join('\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Runs credential env command: prompt for KV_* values and write .env.
|
|
122
|
+
* @async
|
|
123
|
+
* @param {string} systemKey - External system key (integration/<system-key>/)
|
|
124
|
+
* @returns {Promise<string>} Path to written .env file
|
|
125
|
+
* @throws {Error} If env.template missing or write fails
|
|
126
|
+
*/
|
|
127
|
+
function loadExistingEnvMap(envPath) {
|
|
128
|
+
if (!fs.existsSync(envPath)) return {};
|
|
129
|
+
return parseEnvToMap(fs.readFileSync(envPath, 'utf8'));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function runCredentialEnv(systemKey) {
|
|
133
|
+
validateSystemKeyFormat(systemKey);
|
|
134
|
+
const appPath = getIntegrationPath(systemKey);
|
|
135
|
+
const envTemplatePath = path.join(appPath, 'env.template');
|
|
136
|
+
const envPath = path.join(appPath, '.env');
|
|
137
|
+
|
|
138
|
+
if (!fs.existsSync(envTemplatePath)) {
|
|
139
|
+
throw new Error(`env.template not found at ${envTemplatePath}. Create the integration first (e.g. aifabrix wizard or download).`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const templateContent = fs.readFileSync(envTemplatePath, 'utf8');
|
|
143
|
+
const vars = extractKvVarsFromTemplate(templateContent);
|
|
144
|
+
|
|
145
|
+
if (vars.length === 0) {
|
|
146
|
+
logger.log(chalk.yellow('No KV_* variables in env.template. Nothing to prompt.'));
|
|
147
|
+
return envPath;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const existingMap = loadExistingEnvMap(envPath);
|
|
151
|
+
logger.log(chalk.blue(`\nEnter credential values for ${systemKey} (integration/${systemKey}/):`));
|
|
152
|
+
const promptValues = await promptForKvValues(vars, existingMap);
|
|
153
|
+
const content = buildEnvContent(templateContent, promptValues);
|
|
154
|
+
|
|
155
|
+
const dir = path.dirname(envPath);
|
|
156
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
157
|
+
fs.writeFileSync(envPath, content, { mode: 0o600 });
|
|
158
|
+
logger.log(chalk.green(`✓ Wrote ${envPath}`));
|
|
159
|
+
return envPath;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = { runCredentialEnv, validateSystemKeyFormat, extractKvVarsFromTemplate, buildEnvContent };
|