@aifabrix/builder 2.42.0 → 2.43.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/README.md +1 -1
- package/bin/aifabrix.js +1 -1
- package/integration/hubspot-test/README.md +126 -0
- package/integration/{hubspot → hubspot-test}/application.json +6 -6
- package/integration/{hubspot → hubspot-test}/create-hubspot.js +5 -5
- package/integration/hubspot-test/env.template +4 -0
- package/integration/{hubspot/hubspot-datasource-company.json → hubspot-test/hubspot-test-datasource-company.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-contact.json → hubspot-test/hubspot-test-datasource-contact.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-deal.json → hubspot-test/hubspot-test-datasource-deal.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-users.json → hubspot-test/hubspot-test-datasource-users.json} +3 -2
- package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +198 -21
- package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
- package/integration/hubspot-test/rbac.json +166 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test.js +102 -59
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
- package/lib/api/external-test.api.js +1 -1
- package/lib/api/service-users.api.js +111 -2
- package/lib/api/types/service-users.types.js +41 -0
- package/lib/api/wizard.api.js +2 -1
- package/lib/app/index.js +2 -2
- package/lib/app/prompts.js +2 -2
- package/lib/app/readme.js +3 -1
- package/lib/app/register.js +3 -1
- package/lib/app/rotate-secret.js +3 -0
- package/lib/cli/setup-app.js +5 -5
- package/lib/cli/setup-auth.js +19 -11
- package/lib/cli/setup-dev.js +62 -32
- package/lib/cli/setup-environment.js +6 -21
- package/lib/cli/setup-infra.js +13 -0
- package/lib/cli/setup-secrets.js +45 -6
- package/lib/cli/setup-service-user.js +146 -20
- package/lib/cli/setup-utility.js +12 -0
- package/lib/commands/auth-config.js +25 -19
- package/lib/commands/datasource.js +46 -1
- package/lib/commands/dev-init.js +1 -1
- package/lib/commands/repair-env-template.js +14 -8
- package/lib/commands/repair-rbac.js +25 -19
- package/lib/commands/repair.js +108 -31
- package/lib/commands/secrets-remove.js +1 -1
- package/lib/commands/secrets-set.js +6 -0
- package/lib/commands/secrets-validate.js +17 -4
- package/lib/commands/service-user.js +231 -2
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/up-dataplane.js +91 -7
- package/lib/commands/wizard-core-helpers.js +5 -2
- package/lib/commands/wizard-core.js +2 -1
- package/lib/commands/wizard-headless.js +6 -1
- package/lib/commands/wizard.js +13 -6
- package/lib/core/admin-secrets.js +2 -0
- package/lib/core/config.js +7 -5
- package/lib/core/ensure-encryption-key.js +1 -3
- package/lib/core/secrets.js +32 -9
- package/lib/core/templates.js +1 -1
- package/lib/datasource/abac-validator.js +157 -0
- package/lib/datasource/field-reference-validator.js +74 -36
- package/lib/datasource/log-viewer.js +221 -0
- package/lib/datasource/resolve-app.js +109 -0
- package/lib/datasource/test-e2e.js +11 -20
- package/lib/datasource/test-integration.js +42 -22
- package/lib/datasource/validate.js +5 -2
- package/lib/external-system/download-helpers.js +3 -1
- package/lib/external-system/generator.js +12 -8
- package/lib/external-system/test-system-level.js +1 -1
- package/lib/generator/external-controller-manifest.js +3 -3
- package/lib/generator/external-schema-utils.js +3 -1
- package/lib/generator/external.js +7 -7
- package/lib/generator/helpers.js +13 -9
- package/lib/generator/index.js +4 -4
- package/lib/generator/split.js +45 -10
- package/lib/generator/wizard-prompts-secondary.js +39 -7
- package/lib/generator/wizard-readme.js +4 -1
- package/lib/generator/wizard.js +68 -53
- package/lib/infrastructure/helpers.js +50 -35
- package/lib/infrastructure/index.js +39 -23
- package/lib/schema/env-config.yaml +19 -2
- package/lib/schema/external-datasource.schema.json +11 -1
- package/lib/schema/wizard-config.schema.json +7 -1
- package/lib/utils/app-config-resolver.js +23 -1
- package/lib/utils/config-paths.js +48 -4
- package/lib/utils/credential-secrets-env.js +16 -1
- package/lib/utils/env-map.js +7 -3
- package/lib/utils/error-formatter.js +37 -0
- package/lib/utils/external-env-template.js +180 -0
- package/lib/utils/external-readme.js +33 -1
- package/lib/utils/external-system-display.js +43 -0
- package/lib/utils/external-system-validators.js +2 -2
- package/lib/utils/help-builder.js +3 -5
- package/lib/utils/local-secrets.js +26 -3
- package/lib/utils/paths.js +2 -1
- package/lib/utils/secrets-generator.js +2 -2
- package/lib/utils/secrets-utils.js +4 -0
- package/lib/utils/secure-file-permissions.js +91 -0
- package/lib/utils/token-manager.js +36 -3
- package/lib/utils/yaml-preserve.js +59 -1
- package/lib/validation/env-template-auth.js +50 -2
- package/lib/validation/external-manifest-validator.js +8 -0
- package/lib/validation/validate.js +8 -0
- package/lib/validation/validator.js +10 -13
- package/package.json +6 -2
- package/templates/applications/dataplane/env.template +5 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +13 -2
- package/templates/external-system/README.md.hbs +18 -5
- package/templates/external-system/env.template.hbs +22 -0
- package/integration/hubspot/README.md +0 -100
- package/integration/hubspot/env.template +0 -4
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +0 -0
package/lib/app/readme.js
CHANGED
|
@@ -150,6 +150,7 @@ function generateReadmeMd(appName, config) {
|
|
|
150
150
|
const datasources = Array.isArray(config.datasources) && config.datasources.length > 0
|
|
151
151
|
? config.datasources
|
|
152
152
|
: buildExternalDatasourcePlaceholders(systemKey, config.datasourceCount, fileExt);
|
|
153
|
+
const authType = config.authentication?.type || config.authentication?.method || config.authType;
|
|
153
154
|
return generateExternalReadmeContent({
|
|
154
155
|
appName,
|
|
155
156
|
systemKey,
|
|
@@ -157,7 +158,8 @@ function generateReadmeMd(appName, config) {
|
|
|
157
158
|
displayName: config.systemDisplayName,
|
|
158
159
|
description: config.systemDescription,
|
|
159
160
|
fileExt: config.fileExt,
|
|
160
|
-
datasources
|
|
161
|
+
datasources,
|
|
162
|
+
authType
|
|
161
163
|
});
|
|
162
164
|
}
|
|
163
165
|
const context = buildReadmeContext(appName, config);
|
package/lib/app/register.js
CHANGED
|
@@ -150,7 +150,9 @@ async function registerApplication(appKey, options = {}) {
|
|
|
150
150
|
logger.log(chalk.blue('📋 Registering application...\n'));
|
|
151
151
|
|
|
152
152
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
153
|
-
const
|
|
153
|
+
const config = require('../core/config');
|
|
154
|
+
await config.ensureSecretsEncryptionKey();
|
|
155
|
+
const { resolveEnvironment } = config;
|
|
154
156
|
|
|
155
157
|
// Load application config
|
|
156
158
|
const { variables, created } = await loadVariablesYaml(appKey);
|
package/lib/app/rotate-secret.js
CHANGED
|
@@ -339,6 +339,9 @@ async function executeRotation(appKey, actualControllerUrl, environment, token)
|
|
|
339
339
|
async function rotateSecret(appKey, _options = {}) {
|
|
340
340
|
logger.log(chalk.yellow('⚠️ This will invalidate the old ClientSecret!\n'));
|
|
341
341
|
|
|
342
|
+
const { ensureSecretsEncryptionKey } = require('../core/config');
|
|
343
|
+
await ensureSecretsEncryptionKey();
|
|
344
|
+
|
|
342
345
|
const { controllerUrl, environment } = await resolveControllerAndEnvironment();
|
|
343
346
|
const config = await getConfig();
|
|
344
347
|
const { token, actualControllerUrl } = await getRotationAuthToken(controllerUrl, config);
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -84,14 +84,14 @@ async function handleCreateCommand(appName, options) {
|
|
|
84
84
|
const wizardOptions = { app: appName, ...options };
|
|
85
85
|
const normalizedOptions = normalizeExternalOptions(options);
|
|
86
86
|
|
|
87
|
-
const isExternalType = options.type === 'external';
|
|
87
|
+
const isExternalType = options.type === 'external' || !options.type;
|
|
88
88
|
const isNonInteractive = process.stdin && process.stdin.isTTY === false;
|
|
89
89
|
|
|
90
90
|
if (isExternalType && !options.wizard && isNonInteractive) {
|
|
91
91
|
validateNonInteractiveExternalOptions(normalizedOptions);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const shouldUseWizard = options.wizard && (options.type === 'external' ||
|
|
94
|
+
const shouldUseWizard = options.wizard && (options.type === 'external' || !options.type);
|
|
95
95
|
if (shouldUseWizard) {
|
|
96
96
|
const { handleWizard } = require('../commands/wizard');
|
|
97
97
|
await handleWizard(wizardOptions);
|
|
@@ -110,7 +110,7 @@ function setupCreateCommand(program) {
|
|
|
110
110
|
.option('-a, --authentication', 'Requires authentication/RBAC')
|
|
111
111
|
.option('-l, --language <lang>', 'Runtime language (typescript/python)')
|
|
112
112
|
.option('-t, --template <name>', 'Template to use (e.g., miso-controller, keycloak)')
|
|
113
|
-
.option('--type <type>', 'Application type (webapp, api, service, functionapp, external)', '
|
|
113
|
+
.option('--type <type>', 'Application type (webapp, api, service, functionapp, external)', 'external')
|
|
114
114
|
.option('--app', 'Generate minimal application files (package.json, index.ts or requirements.txt, main.py)')
|
|
115
115
|
.option('-g, --github', 'Generate GitHub Actions workflows')
|
|
116
116
|
.option('--github-steps <steps>', 'Extra GitHub workflow steps (comma-separated, e.g., npm,test)')
|
|
@@ -140,12 +140,12 @@ Examples:
|
|
|
140
140
|
$ aifabrix wizard my-integration --silent Run headless with integration/my-integration/wizard.yaml (no prompts)
|
|
141
141
|
$ aifabrix wizard -a my-integration Same as above (app name set)
|
|
142
142
|
$ aifabrix wizard --config wizard.yaml Run headless from a wizard config file
|
|
143
|
-
$ aifabrix wizard hubspot-test
|
|
143
|
+
$ aifabrix wizard hubspot-test --debug Enable debug output and save debug manifests on validation failure
|
|
144
144
|
|
|
145
145
|
Config path: When appName is provided, integration/<appName>/wizard.yaml is used for load/save and error.log.
|
|
146
146
|
To change settings after a run, edit that file and run "aifabrix wizard <app>" again.
|
|
147
147
|
Headless config must include: appName, mode (create-system|add-datasource), source (type + filePath/url/platform).
|
|
148
|
-
See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`;
|
|
148
|
+
See integration/hubspot-test/wizard-hubspot-e2e.yaml for an example.`;
|
|
149
149
|
program.command('wizard [appName]')
|
|
150
150
|
.description('Create or extend external systems (OpenAPI, MCP, or known platforms like HubSpot) via guided steps or a config file')
|
|
151
151
|
.option('-a, --app <app>', 'Application name (synonym for positional appName)')
|
package/lib/cli/setup-auth.js
CHANGED
|
@@ -60,23 +60,31 @@ function setupAuthSubcommands(program) {
|
|
|
60
60
|
process.exit(1);
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
-
const auth = program.command('auth')
|
|
64
|
-
|
|
65
|
-
.
|
|
66
|
-
.option('--
|
|
67
|
-
.action(authStatusHandler);
|
|
68
|
-
auth.command('config')
|
|
69
|
-
.description('Configure authentication settings (controller, environment)')
|
|
70
|
-
.option('--set-controller <url>', 'Set default controller URL')
|
|
71
|
-
.option('--set-environment <env>', 'Set default environment')
|
|
63
|
+
const auth = program.command('auth')
|
|
64
|
+
.description('Authentication status and config (controller, environment)')
|
|
65
|
+
.option('--set-controller <url>', 'Set default controller URL in config')
|
|
66
|
+
.option('--set-environment <env>', 'Set default environment in config')
|
|
72
67
|
.action(async(options) => {
|
|
73
68
|
try {
|
|
74
|
-
|
|
69
|
+
const setController = options.setController || options['set-controller'];
|
|
70
|
+
const setEnvironment = options.setEnvironment || options['set-environment'];
|
|
71
|
+
if (setController || setEnvironment) {
|
|
72
|
+
await handleAuthConfig({
|
|
73
|
+
setController,
|
|
74
|
+
setEnvironment
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await handleAuthStatus(options);
|
|
75
79
|
} catch (error) {
|
|
76
|
-
handleCommandError(error, 'auth
|
|
80
|
+
handleCommandError(error, 'auth');
|
|
77
81
|
process.exit(1);
|
|
78
82
|
}
|
|
79
83
|
});
|
|
84
|
+
auth.command('status')
|
|
85
|
+
.description('Display authentication status for current controller and environment')
|
|
86
|
+
.option('--validate', 'Exit with code 1 when not authenticated (for scripted use, e.g. manual test setup)')
|
|
87
|
+
.action(authStatusHandler);
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
/**
|
package/lib/cli/setup-dev.js
CHANGED
|
@@ -73,51 +73,26 @@ async function handleSetFormat(format) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* Register dev
|
|
76
|
+
* Register dev show and set-id commands.
|
|
77
77
|
* @param {Command} dev - dev subcommand group
|
|
78
78
|
*/
|
|
79
|
-
function
|
|
79
|
+
function setupDevShowAndSetId(dev) {
|
|
80
80
|
dev
|
|
81
|
-
.command('
|
|
82
|
-
.description('Show
|
|
83
|
-
.
|
|
84
|
-
.action(async(options) => {
|
|
81
|
+
.command('show')
|
|
82
|
+
.description('Show developer configuration (ports and config vars)')
|
|
83
|
+
.action(async() => {
|
|
85
84
|
try {
|
|
86
|
-
const setIdValue = options.setId || options['set-id'];
|
|
87
|
-
if (setIdValue) {
|
|
88
|
-
const digitsOnly = /^[0-9]+$/.test(setIdValue);
|
|
89
|
-
if (!digitsOnly) {
|
|
90
|
-
throw new Error('Developer ID must be a non-negative digit string (0 = default infra, > 0 = developer-specific)');
|
|
91
|
-
}
|
|
92
|
-
await config.setDeveloperId(setIdValue);
|
|
93
|
-
process.env.AIFABRIX_DEVELOPERID = setIdValue;
|
|
94
|
-
logger.log(chalk.green(`✓ Developer ID set to ${setIdValue}`));
|
|
95
|
-
await displayDevConfig(setIdValue);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
85
|
const devId = await config.getDeveloperId();
|
|
99
86
|
await displayDevConfig(devId);
|
|
100
87
|
} catch (error) {
|
|
101
|
-
handleCommandError(error, 'dev
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
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');
|
|
88
|
+
handleCommandError(error, 'dev show');
|
|
114
89
|
process.exit(1);
|
|
115
90
|
}
|
|
116
91
|
});
|
|
117
92
|
|
|
118
93
|
dev
|
|
119
94
|
.command('set-id <id>')
|
|
120
|
-
.description('Set developer ID (
|
|
95
|
+
.description('Set developer ID (0 = default infra, > 0 = developer-specific)')
|
|
121
96
|
.action(async(id) => {
|
|
122
97
|
try {
|
|
123
98
|
const digitsOnly = /^[0-9]+$/.test(id);
|
|
@@ -135,6 +110,61 @@ function setupDevConfigCommands(dev) {
|
|
|
135
110
|
});
|
|
136
111
|
}
|
|
137
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Register dev set-env-config, set-home and set-format commands.
|
|
115
|
+
* @param {Command} dev - dev subcommand group
|
|
116
|
+
*/
|
|
117
|
+
function setupDevPathAndFormatCommands(dev) {
|
|
118
|
+
dev
|
|
119
|
+
.command('set-env-config <filePath>')
|
|
120
|
+
.description('Set aifabrix-env-config path in config.yaml (pass empty to clear; path is not checked for existence)')
|
|
121
|
+
.action(async(filePath) => {
|
|
122
|
+
try {
|
|
123
|
+
const trimmed = (filePath || '').trim();
|
|
124
|
+
await config.setAifabrixEnvConfigPath(trimmed);
|
|
125
|
+
logger.log(trimmed === '' ? chalk.green('✓ Env config path cleared') : chalk.green(`✓ Env config path set to ${trimmed}`));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
handleCommandError(error, 'dev set-env-config');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
dev
|
|
133
|
+
.command('set-home <path>')
|
|
134
|
+
.description('Set aifabrix-home path in config.yaml (pass empty string to clear)')
|
|
135
|
+
.action(async(homePath) => {
|
|
136
|
+
try {
|
|
137
|
+
const trimmed = (homePath || '').trim();
|
|
138
|
+
await config.setAifabrixHomeOverride(trimmed);
|
|
139
|
+
logger.log(trimmed === '' ? chalk.green('✓ Home path cleared') : chalk.green(`✓ Home path set to ${trimmed}`));
|
|
140
|
+
} catch (error) {
|
|
141
|
+
handleCommandError(error, 'dev set-home');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
dev
|
|
147
|
+
.command('set-format <format>')
|
|
148
|
+
.description('Set default output format for download/convert (json | yaml); used when --format not passed')
|
|
149
|
+
.action(async(format) => {
|
|
150
|
+
try {
|
|
151
|
+
await handleSetFormat(format);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
handleCommandError(error, 'dev set-format');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Register dev show, set-id, set-env-config, set-home and set-format commands.
|
|
161
|
+
* @param {Command} dev - dev subcommand group
|
|
162
|
+
*/
|
|
163
|
+
function setupDevConfigCommands(dev) {
|
|
164
|
+
setupDevShowAndSetId(dev);
|
|
165
|
+
setupDevPathAndFormatCommands(dev);
|
|
166
|
+
}
|
|
167
|
+
|
|
138
168
|
/**
|
|
139
169
|
* Register dev init and refresh commands.
|
|
140
170
|
* @param {Command} dev - dev subcommand group
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI environment deployment command setup (
|
|
2
|
+
* CLI environment deployment command setup (env deploy).
|
|
3
3
|
*
|
|
4
4
|
* @fileoverview Environment command definitions for AI Fabrix Builder CLI
|
|
5
5
|
* @author AI Fabrix Team
|
|
@@ -18,35 +18,20 @@ function setupEnvironmentCommands(program) {
|
|
|
18
18
|
const environmentDeploy = require('../deployment/environment');
|
|
19
19
|
await environmentDeploy.deployEnvironment(envKey, options);
|
|
20
20
|
} catch (error) {
|
|
21
|
-
handleCommandError(error, '
|
|
21
|
+
handleCommandError(error, 'env deploy');
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
const environment = program
|
|
27
|
-
.command('environment')
|
|
28
|
-
.description('Deploy and manage Miso Controller environments (dev, tst, pro, miso)');
|
|
29
|
-
|
|
30
26
|
const deployExamples = `
|
|
31
27
|
Examples:
|
|
32
|
-
$ aifabrix
|
|
33
|
-
$ aifabrix
|
|
34
|
-
$ aifabrix
|
|
35
|
-
|
|
36
|
-
environment
|
|
37
|
-
.command('deploy <env>')
|
|
38
|
-
.description('Deploy environment infrastructure in Miso Controller (run before deploy <app>)')
|
|
39
|
-
.option('--config <file>', 'Environment configuration file (optional if --preset is used)')
|
|
40
|
-
.option('--preset <size>', 'Environment size preset: s, m, l, xl (default: s)', 's')
|
|
41
|
-
.option('--skip-validation', 'Skip environment validation')
|
|
42
|
-
.option('--poll', 'Poll for deployment status', true)
|
|
43
|
-
.option('--no-poll', 'Do not poll for status')
|
|
44
|
-
.addHelpText('after', deployExamples)
|
|
45
|
-
.action(deployEnvHandler);
|
|
28
|
+
$ aifabrix env deploy dev
|
|
29
|
+
$ aifabrix env deploy tst --preset m
|
|
30
|
+
$ aifabrix env deploy dev --config ./env-config.json --no-poll`;
|
|
46
31
|
|
|
47
32
|
const env = program
|
|
48
33
|
.command('env')
|
|
49
|
-
.description('Deploy and manage Miso Controller environments (
|
|
34
|
+
.description('Deploy and manage Miso Controller environments (dev, tst, pro, miso)');
|
|
50
35
|
|
|
51
36
|
env
|
|
52
37
|
.command('deploy <env>')
|
package/lib/cli/setup-infra.js
CHANGED
|
@@ -17,6 +17,7 @@ const { resolveControllerUrl } = require('../utils/controller-url');
|
|
|
17
17
|
const { handleLogin } = require('../commands/login');
|
|
18
18
|
const { handleUpMiso } = require('../commands/up-miso');
|
|
19
19
|
const { handleUpDataplane } = require('../commands/up-dataplane');
|
|
20
|
+
const { cleanBuilderAppDirs } = require('../commands/up-common');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Persists optional service flag to config when explicitly set.
|
|
@@ -108,8 +109,12 @@ function setupUpPlatformCommand(program) {
|
|
|
108
109
|
.option('-r, --registry <url>', 'Override registry for all apps (e.g. myacr.azurecr.io)')
|
|
109
110
|
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
110
111
|
.option('-i, --image <key>=<value>', 'Override image (e.g. keycloak=myreg/k:v1, miso-controller=myreg/m:v1, dataplane=myreg/d:v1); can be repeated', (v, prev) => (prev || []).concat([v]))
|
|
112
|
+
.option('-f, --force', 'Clean builder/keycloak, builder/miso-controller, builder/dataplane and re-fetch from templates')
|
|
111
113
|
.action(async(options) => {
|
|
112
114
|
try {
|
|
115
|
+
if (options.force) {
|
|
116
|
+
await cleanBuilderAppDirs(['keycloak', 'miso-controller', 'dataplane']);
|
|
117
|
+
}
|
|
113
118
|
await handleUpMiso(options);
|
|
114
119
|
await handleUpDataplane(options);
|
|
115
120
|
} catch (error) {
|
|
@@ -137,8 +142,12 @@ function setupUpMisoCommand(program) {
|
|
|
137
142
|
.option('-r, --registry <url>', 'Override registry for all apps (e.g. myacr.azurecr.io)')
|
|
138
143
|
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
139
144
|
.option('-i, --image <key>=<value>', 'Override image (e.g. keycloak=myreg/k:v1, miso-controller=myreg/m:v1); can be repeated', (v, prev) => (prev || []).concat([v]))
|
|
145
|
+
.option('-f, --force', 'Clean builder/keycloak and builder/miso-controller and re-fetch from templates')
|
|
140
146
|
.action(async(options) => {
|
|
141
147
|
try {
|
|
148
|
+
if (options.force) {
|
|
149
|
+
await cleanBuilderAppDirs(['keycloak', 'miso-controller']);
|
|
150
|
+
}
|
|
142
151
|
await handleUpMiso(options);
|
|
143
152
|
} catch (error) {
|
|
144
153
|
handleCommandError(error, 'up-miso');
|
|
@@ -153,8 +162,12 @@ function setupUpDataplaneCommand(program) {
|
|
|
153
162
|
.option('-r, --registry <url>', 'Override registry for dataplane image')
|
|
154
163
|
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
155
164
|
.option('-i, --image <ref>', 'Override dataplane image reference (e.g. myreg/dataplane:latest)')
|
|
165
|
+
.option('-f, --force', 'Clean builder/dataplane and re-fetch from templates')
|
|
156
166
|
.action(async(options) => {
|
|
157
167
|
try {
|
|
168
|
+
if (options.force) {
|
|
169
|
+
await cleanBuilderAppDirs(['dataplane']);
|
|
170
|
+
}
|
|
158
171
|
await handleUpDataplane(options);
|
|
159
172
|
} catch (error) {
|
|
160
173
|
if (isAuthenticationError(error)) {
|
package/lib/cli/setup-secrets.js
CHANGED
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
* @version 2.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
const chalk = require('chalk');
|
|
9
10
|
const { handleCommandError } = require('../utils/cli-utils');
|
|
10
11
|
const { handleSecretsSet } = require('../commands/secrets-set');
|
|
11
12
|
const { handleSecretsList } = require('../commands/secrets-list');
|
|
12
13
|
const { handleSecretsRemove } = require('../commands/secrets-remove');
|
|
13
14
|
const { handleSecretsValidate } = require('../commands/secrets-validate');
|
|
14
15
|
const { handleSecure } = require('../commands/secure');
|
|
16
|
+
const config = require('../core/config');
|
|
17
|
+
const logger = require('../utils/logger');
|
|
15
18
|
|
|
16
19
|
function setupSecretValidateCommand(secretCmd) {
|
|
17
20
|
secretCmd
|
|
@@ -20,6 +23,7 @@ function setupSecretValidateCommand(secretCmd) {
|
|
|
20
23
|
.option('--naming', 'Check key names against *KeyVault convention')
|
|
21
24
|
.action(async(pathArg, options) => {
|
|
22
25
|
try {
|
|
26
|
+
await config.ensureSecretsEncryptionKey();
|
|
23
27
|
const result = await handleSecretsValidate(pathArg, options);
|
|
24
28
|
if (!result.valid) process.exit(1);
|
|
25
29
|
} catch (error) {
|
|
@@ -29,6 +33,25 @@ function setupSecretValidateCommand(secretCmd) {
|
|
|
29
33
|
});
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Registers the secure command on the program.
|
|
38
|
+
* @param {Command} program - Commander program instance
|
|
39
|
+
*/
|
|
40
|
+
function setupSecureCommand(program) {
|
|
41
|
+
program.command('secure')
|
|
42
|
+
.description('Encrypt secrets in secrets.local.yaml files for ISO 27001 compliance')
|
|
43
|
+
.option('--secrets-encryption <key>', 'Encryption key (32 bytes, hex or base64)')
|
|
44
|
+
.action(async(options) => {
|
|
45
|
+
try {
|
|
46
|
+
await config.ensureSecretsEncryptionKey();
|
|
47
|
+
await handleSecure(options);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
handleCommandError(error, 'secure');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
/**
|
|
33
56
|
* Sets up secrets and security commands
|
|
34
57
|
* @param {Command} program - Commander program instance
|
|
@@ -44,6 +67,7 @@ function setupSecretsCommands(program) {
|
|
|
44
67
|
.option('--shared', 'List shared secrets (from config aifabrix-secrets or remote API)')
|
|
45
68
|
.action(async(options) => {
|
|
46
69
|
try {
|
|
70
|
+
await config.ensureSecretsEncryptionKey();
|
|
47
71
|
await handleSecretsList(options);
|
|
48
72
|
} catch (error) {
|
|
49
73
|
handleCommandError(error, 'secret list');
|
|
@@ -57,6 +81,7 @@ function setupSecretsCommands(program) {
|
|
|
57
81
|
.option('--shared', 'Save to general secrets file (from config.yaml aifabrix-secrets) instead of user secrets')
|
|
58
82
|
.action(async(key, value, options) => {
|
|
59
83
|
try {
|
|
84
|
+
await config.ensureSecretsEncryptionKey();
|
|
60
85
|
await handleSecretsSet(key, value, options);
|
|
61
86
|
} catch (error) {
|
|
62
87
|
handleCommandError(error, 'secret set');
|
|
@@ -70,6 +95,7 @@ function setupSecretsCommands(program) {
|
|
|
70
95
|
.option('--shared', 'Remove from shared secrets (file or remote API)')
|
|
71
96
|
.action(async(key, options) => {
|
|
72
97
|
try {
|
|
98
|
+
await config.ensureSecretsEncryptionKey();
|
|
73
99
|
await handleSecretsRemove(key, options);
|
|
74
100
|
} catch (error) {
|
|
75
101
|
handleCommandError(error, 'secret remove');
|
|
@@ -77,16 +103,29 @@ function setupSecretsCommands(program) {
|
|
|
77
103
|
}
|
|
78
104
|
});
|
|
79
105
|
|
|
106
|
+
setupSecretSetSecretsFileCommand(secretCmd);
|
|
80
107
|
setupSecretValidateCommand(secretCmd);
|
|
108
|
+
setupSecureCommand(program);
|
|
109
|
+
}
|
|
81
110
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Register secret set-secrets-file command.
|
|
113
|
+
* @param {Command} secretCmd - secret command group
|
|
114
|
+
*/
|
|
115
|
+
function setupSecretSetSecretsFileCommand(secretCmd) {
|
|
116
|
+
secretCmd
|
|
117
|
+
.command('set-secrets-file <path>')
|
|
118
|
+
.description('Set aifabrix-secrets path in config (local file or https URL; pass empty to clear; path/URL is not checked for existence)')
|
|
119
|
+
.action(async(secretsPath) => {
|
|
86
120
|
try {
|
|
87
|
-
|
|
121
|
+
const trimmed = (secretsPath || '').trim();
|
|
122
|
+
if (trimmed !== '' && trimmed.startsWith('http://')) {
|
|
123
|
+
throw new Error('Only https URLs are allowed for remote secrets');
|
|
124
|
+
}
|
|
125
|
+
await config.setSecretsPath(trimmed);
|
|
126
|
+
logger.log(trimmed === '' ? chalk.green('✓ Secrets file path cleared') : chalk.green(`✓ Secrets file path set to ${trimmed}`));
|
|
88
127
|
} catch (error) {
|
|
89
|
-
handleCommandError(error, '
|
|
128
|
+
handleCommandError(error, 'secret set-secrets-file');
|
|
90
129
|
process.exit(1);
|
|
91
130
|
}
|
|
92
131
|
});
|
|
@@ -10,28 +10,37 @@
|
|
|
10
10
|
const chalk = require('chalk');
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
12
|
const { handleCommandError } = require('../utils/cli-utils');
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
runServiceUserCreate,
|
|
15
|
+
runServiceUserList,
|
|
16
|
+
runServiceUserRotateSecret,
|
|
17
|
+
runServiceUserDelete,
|
|
18
|
+
runServiceUserUpdateGroups,
|
|
19
|
+
runServiceUserUpdateRedirectUris
|
|
20
|
+
} = require('../commands/service-user');
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
* Sets up service-user commands
|
|
17
|
-
* @param {Command} program - Commander program instance
|
|
18
|
-
*/
|
|
19
|
-
function setupServiceUserCommands(program) {
|
|
20
|
-
const serviceUser = program
|
|
21
|
-
.command('service-user')
|
|
22
|
-
.description('Create and manage service users (API clients) for integrations and CI')
|
|
23
|
-
.addHelpText('after', `
|
|
22
|
+
const HELP_AFTER = `
|
|
24
23
|
Service users are dedicated accounts for integrations, CI pipelines, or API clients.
|
|
25
|
-
|
|
24
|
+
Use: list (service-user:read), create (service-user:create), rotate-secret, update-groups, update-redirect-uris (service-user:update), delete (service-user:delete).
|
|
25
|
+
The controller returns a one-time clientSecret on create and rotate-secret—save it immediately; it cannot be retrieved again.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
$ aifabrix service-user create -u
|
|
29
|
-
--redirect-uris
|
|
27
|
+
Examples:
|
|
28
|
+
$ aifabrix service-user create -u postman -e postman@aifabrix.dev \\
|
|
29
|
+
--redirect-uris https://oauth.pstmn.io/v1/callback --group-names AI-Fabrix-Platform-Admins
|
|
30
|
+
$ aifabrix service-user list
|
|
31
|
+
$ aifabrix service-user rotate-secret --id <uuid>
|
|
32
|
+
$ aifabrix service-user delete --id <uuid>
|
|
33
|
+
$ aifabrix service-user update-groups --id <uuid> --group-names Group1,Group2
|
|
34
|
+
$ aifabrix service-user update-redirect-uris --id <uuid> --redirect-uris https://app.example.com/callback
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
Run "aifabrix login" first.`;
|
|
37
|
+
|
|
38
|
+
function parseOptionalInt(val) {
|
|
39
|
+
return (val !== undefined && val !== null) ? parseInt(val, 10) : undefined;
|
|
40
|
+
}
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
function addCreateCommand(serviceUser) {
|
|
43
|
+
serviceUser.command('create')
|
|
35
44
|
.description('Create a service user and receive a one-time clientSecret (save it now; it will not be shown again)')
|
|
36
45
|
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
37
46
|
.option('-u, --username <username>', 'Service user username (required)')
|
|
@@ -41,15 +50,14 @@ Required: permission service-user:create on the controller. Run "aifabrix login"
|
|
|
41
50
|
.option('-d, --description <description>', 'Description for the service user')
|
|
42
51
|
.action(async(options) => {
|
|
43
52
|
try {
|
|
44
|
-
|
|
53
|
+
await runServiceUserCreate({
|
|
45
54
|
controller: options.controller,
|
|
46
55
|
username: options.username,
|
|
47
56
|
email: options.email,
|
|
48
57
|
redirectUris: options.redirectUris,
|
|
49
58
|
groupNames: options.groupNames,
|
|
50
59
|
description: options.description
|
|
51
|
-
};
|
|
52
|
-
await runServiceUserCreate(opts);
|
|
60
|
+
});
|
|
53
61
|
} catch (error) {
|
|
54
62
|
logger.error(chalk.red(`Error: ${error.message}`));
|
|
55
63
|
handleCommandError(error, 'service-user create');
|
|
@@ -58,4 +66,122 @@ Required: permission service-user:create on the controller. Run "aifabrix login"
|
|
|
58
66
|
});
|
|
59
67
|
}
|
|
60
68
|
|
|
69
|
+
function addListCommand(serviceUser) {
|
|
70
|
+
serviceUser.command('list')
|
|
71
|
+
.description('List service users (supports pagination and search)')
|
|
72
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
73
|
+
.option('--page <n>', 'Page number')
|
|
74
|
+
.option('--page-size <n>', 'Items per page')
|
|
75
|
+
.option('--search <term>', 'Search term')
|
|
76
|
+
.option('--sort <field>', 'Sort field/direction')
|
|
77
|
+
.option('--filter <expr>', 'Filter expression')
|
|
78
|
+
.action(async(options) => {
|
|
79
|
+
try {
|
|
80
|
+
await runServiceUserList({
|
|
81
|
+
controller: options.controller,
|
|
82
|
+
page: parseOptionalInt(options.page),
|
|
83
|
+
pageSize: parseOptionalInt(options.pageSize),
|
|
84
|
+
search: options.search,
|
|
85
|
+
sort: options.sort,
|
|
86
|
+
filter: options.filter
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
90
|
+
handleCommandError(error, 'service-user list');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function addRotateSecretCommand(serviceUser) {
|
|
97
|
+
serviceUser.command('rotate-secret')
|
|
98
|
+
.description('Rotate (regenerate) secret for a service user; new secret shown once only')
|
|
99
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
100
|
+
.option('--id <uuid>', 'Service user ID (required)')
|
|
101
|
+
.action(async(options) => {
|
|
102
|
+
try {
|
|
103
|
+
await runServiceUserRotateSecret({ controller: options.controller, id: options.id });
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
106
|
+
handleCommandError(error, 'service-user rotate-secret');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function addDeleteCommand(serviceUser) {
|
|
113
|
+
serviceUser.command('delete')
|
|
114
|
+
.description('Delete (deactivate) a service user')
|
|
115
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
116
|
+
.option('--id <uuid>', 'Service user ID (required)')
|
|
117
|
+
.action(async(options) => {
|
|
118
|
+
try {
|
|
119
|
+
await runServiceUserDelete({ controller: options.controller, id: options.id });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
122
|
+
handleCommandError(error, 'service-user delete');
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function addUpdateGroupsCommand(serviceUser) {
|
|
129
|
+
serviceUser.command('update-groups')
|
|
130
|
+
.description('Update group assignments for a service user')
|
|
131
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
132
|
+
.option('--id <uuid>', 'Service user ID (required)')
|
|
133
|
+
.option('--group-names <names>', 'Comma-separated group names (required)')
|
|
134
|
+
.action(async(options) => {
|
|
135
|
+
try {
|
|
136
|
+
await runServiceUserUpdateGroups({
|
|
137
|
+
controller: options.controller,
|
|
138
|
+
id: options.id,
|
|
139
|
+
groupNames: options.groupNames
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
143
|
+
handleCommandError(error, 'service-user update-groups');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function addUpdateRedirectUrisCommand(serviceUser) {
|
|
150
|
+
serviceUser.command('update-redirect-uris')
|
|
151
|
+
.description('Update redirect URIs for a service user (min 1)')
|
|
152
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
153
|
+
.option('--id <uuid>', 'Service user ID (required)')
|
|
154
|
+
.option('--redirect-uris <uris>', 'Comma-separated redirect URIs (required, min 1)')
|
|
155
|
+
.action(async(options) => {
|
|
156
|
+
try {
|
|
157
|
+
await runServiceUserUpdateRedirectUris({
|
|
158
|
+
controller: options.controller,
|
|
159
|
+
id: options.id,
|
|
160
|
+
redirectUris: options.redirectUris
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
164
|
+
handleCommandError(error, 'service-user update-redirect-uris');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Sets up service-user commands
|
|
172
|
+
* @param {Command} program - Commander program instance
|
|
173
|
+
*/
|
|
174
|
+
function setupServiceUserCommands(program) {
|
|
175
|
+
const serviceUser = program
|
|
176
|
+
.command('service-user')
|
|
177
|
+
.description('Create and manage service users (API clients) for integrations and CI')
|
|
178
|
+
.addHelpText('after', HELP_AFTER);
|
|
179
|
+
addCreateCommand(serviceUser);
|
|
180
|
+
addListCommand(serviceUser);
|
|
181
|
+
addRotateSecretCommand(serviceUser);
|
|
182
|
+
addDeleteCommand(serviceUser);
|
|
183
|
+
addUpdateGroupsCommand(serviceUser);
|
|
184
|
+
addUpdateRedirectUrisCommand(serviceUser);
|
|
185
|
+
}
|
|
186
|
+
|
|
61
187
|
module.exports = { setupServiceUserCommands };
|