@aifabrix/builder 2.46.0 → 2.47.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/.npmrc.token +1 -0
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
- package/.temp/validation/summary.txt +5 -0
- package/jest.isolated-projects.js +18 -2
- package/jest.projects.js +24 -0
- package/lib/api/dataplane-health.api.js +19 -7
- package/lib/api/lifecycle.api.js +22 -1
- package/lib/api/resource-types.api.js +107 -0
- package/lib/api/runtime-configuration.api.js +72 -0
- package/lib/api/types/resource-types.types.js +45 -0
- package/lib/api/user-groups.api.js +9 -9
- package/lib/api/users.api.js +8 -8
- package/lib/api/wizard.api.js +10 -2
- package/lib/app/down.js +58 -26
- package/lib/app/index.js +2 -2
- package/lib/app/run-resolve-image.js +2 -1
- package/lib/app/run-stop-containers.js +113 -0
- package/lib/app/run.js +1 -31
- package/lib/app/show-display.js +35 -0
- package/lib/app/show.js +32 -1
- package/lib/build/index.js +17 -5
- package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
- package/lib/channels/add-channel.js +348 -0
- package/lib/channels/channel-artifact-generator.js +141 -0
- package/lib/channels/channel-audit.js +101 -0
- package/lib/channels/channel-auth-context.js +37 -0
- package/lib/channels/channel-credential.js +200 -0
- package/lib/channels/channel-display-name.js +64 -0
- package/lib/channels/channel-external-system.js +106 -0
- package/lib/channels/channel-integration-generator.js +274 -0
- package/lib/channels/channel-probe.js +89 -0
- package/lib/channels/channel-runtime-resolver.js +152 -0
- package/lib/channels/channel-status-display.js +141 -0
- package/lib/channels/channel-targets.js +115 -0
- package/lib/cli/index.js +18 -4
- package/lib/cli/infra-guided-auth.js +36 -12
- package/lib/cli/setup-app.js +54 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
- package/lib/cli/setup-infra-up-platform-action.js +2 -1
- package/lib/cli/setup-npm.js +59 -0
- package/lib/cli/setup-platform.js +1 -1
- package/lib/cli/setup-secrets.js +1 -1
- package/lib/cli/setup-utility-help-after.js +4 -1
- package/lib/cli/setup-utility-resolve.js +8 -5
- package/lib/commands/app-logs.js +54 -26
- package/lib/commands/app.js +41 -0
- package/lib/commands/auth-status-dataplane-version.js +97 -61
- package/lib/commands/auth-status.js +2 -7
- package/lib/commands/channel.js +58 -0
- package/lib/commands/datasource.js +1 -1
- package/lib/commands/identity-apply-sync.js +49 -8
- package/lib/commands/identity-group-cli.js +66 -26
- package/lib/commands/identity-membership-role-cache.js +58 -37
- package/lib/commands/identity-shared.js +22 -1
- package/lib/commands/identity-user-cli.js +80 -34
- package/lib/commands/login-device.js +11 -3
- package/lib/commands/login.js +8 -3
- package/lib/commands/npm-config.js +103 -0
- package/lib/commands/repair-auth-apply.js +30 -4
- package/lib/commands/repair-auth-config.js +8 -13
- package/lib/commands/repair-datasource-com-v2.js +15 -0
- package/lib/commands/repair-datasource-service-contract.js +206 -0
- package/lib/commands/repair-datasource.js +53 -18
- package/lib/commands/repair-env-template.js +12 -12
- package/lib/commands/repair-openapi-sync.js +26 -4
- package/lib/commands/repair-steps.js +28 -13
- package/lib/commands/repair-system-alignment.js +7 -4
- package/lib/commands/resource-type.js +371 -0
- package/lib/commands/setup-modes.js +1 -1
- package/lib/commands/setup-prompts-platform-mode.js +24 -21
- package/lib/commands/test-trust-external.js +39 -90
- package/lib/commands/up-common.js +33 -1
- package/lib/commands/up-dataplane.js +8 -4
- package/lib/commands/upload.js +24 -3
- package/lib/commands/verify-trust-command-action.js +4 -1
- package/lib/commands/verify-trust-external.js +12 -20
- package/lib/core/config-setup-platform-mode.js +3 -3
- package/lib/core/setup-platform-mode.js +41 -6
- package/lib/datasource/capability/capability-diff-slice.js +2 -2
- package/lib/datasource/capability/capability-manifest.js +254 -0
- package/lib/datasource/capability/capability-resolve.js +3 -4
- package/lib/datasource/capability/copy-operations.js +5 -3
- package/lib/datasource/capability/remove-operations.js +2 -1
- package/lib/datasource/capability/run-capability-copy.js +32 -4
- package/lib/datasource/capability/run-capability-diff.js +3 -3
- package/lib/datasource/capability/run-capability-dimension.js +2 -4
- package/lib/datasource/capability/run-capability-edit.js +4 -7
- package/lib/datasource/capability/run-capability-relate.js +2 -4
- package/lib/datasource/capability/run-capability-remove.js +2 -4
- package/lib/datasource/capability/validate-capability-slice.js +2 -1
- package/lib/datasource/datasource-identifier.js +2 -1
- package/lib/datasource/datasource-validate-display.js +1 -1
- package/lib/datasource/field-reference-validator.js +24 -3
- package/lib/datasource/orphan-profiles-validator.js +98 -0
- package/lib/datasource/validate.js +41 -23
- package/lib/external-system/download-helpers.js +87 -0
- package/lib/external-system/download.js +61 -122
- package/lib/external-system/generator.js +1 -59
- package/lib/external-system/test.js +10 -15
- package/lib/generator/builders.js +11 -1
- package/lib/generator/external-controller-manifest.js +5 -2
- package/lib/generator/split.js +3 -1
- package/lib/generator/wizard.js +1 -28
- package/lib/identity/fk-reference.js +65 -0
- package/lib/identity/identity-apply-core.js +13 -58
- package/lib/identity/identity-apply-phases.js +5 -1
- package/lib/identity/identity-display.js +243 -0
- package/lib/identity/identity-response-helpers.js +52 -0
- package/lib/identity/identity-sync-errors.js +62 -0
- package/lib/identity/resolve-user-id.js +129 -0
- package/lib/identity/username-default.js +31 -0
- package/lib/lifecycle/certification-progress.js +2 -2
- package/lib/lifecycle/report-display-trust.js +70 -0
- package/lib/lifecycle/report-display.js +14 -13
- package/lib/resolvers/datasource-resolver.js +2 -3
- package/lib/resolvers/resource-type-file.js +159 -0
- package/lib/schema/application-schema.json +16 -0
- package/lib/schema/deployment-rules.yaml +2 -1
- package/lib/schema/external-datasource.schema.json +372 -58
- package/lib/schema/external-system.schema.json +331 -2
- package/lib/schema/type/document-storage.json +9 -5
- package/lib/utils/api.js +2 -1
- package/lib/utils/app-run-containers.js +25 -2
- package/lib/utils/authentication-shape.js +224 -0
- package/lib/utils/build-copy.js +36 -5
- package/lib/utils/build-resolve-image.js +66 -12
- package/lib/utils/celery-companion-apps.js +53 -0
- package/lib/utils/cli-utils.js +4 -0
- package/lib/utils/compose-celery-worker.js +23 -0
- package/lib/utils/compose-generate-docker-compose.js +9 -1
- package/lib/utils/configuration-env-resolver.js +36 -0
- package/lib/utils/controller-url.js +4 -1
- package/lib/utils/credential-secrets-env-kv.js +216 -0
- package/lib/utils/credential-secrets-env.js +52 -262
- package/lib/utils/dataplane-resolver.js +4 -115
- package/lib/utils/declarative-url-ports.js +24 -1
- package/lib/utils/dev-platform-tls.js +148 -0
- package/lib/utils/docker-build.js +1 -4
- package/lib/utils/external-env-template.js +10 -3
- package/lib/utils/external-system-validators.js +8 -2
- package/lib/utils/health-check-request.js +114 -0
- package/lib/utils/health-check-url.js +3 -1
- package/lib/utils/health-check.js +1 -113
- package/lib/utils/help-builder.js +3 -0
- package/lib/utils/keycloak-realm-ready.js +5 -1
- package/lib/utils/localhost-public-host.js +31 -0
- package/lib/utils/npm-user-config.js +145 -0
- package/lib/utils/paths.js +25 -0
- package/lib/utils/platform-controller-url.js +58 -4
- package/lib/utils/platform-kv-ref.js +57 -0
- package/lib/utils/runtime-configuration-download.js +189 -0
- package/lib/utils/runtime-configuration-values.js +119 -0
- package/lib/utils/schema-loader.js +19 -4
- package/lib/utils/url-declarative-public-base.js +33 -25
- package/lib/utils/worker-sidecars.js +142 -0
- package/lib/validation/external-manifest-validator.js +2 -0
- package/lib/validation/runtime-configuration-rules.js +98 -0
- package/lib/validation/validate-display-dimensions.js +154 -0
- package/lib/validation/validate-display-sections.js +276 -0
- package/lib/validation/validate-display-steps.js +67 -0
- package/lib/validation/validate-display.js +24 -402
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +5 -0
- package/templates/applications/dataplane/env.template +26 -13
- package/templates/applications/dataplane/rbac.yaml +108 -1
- package/templates/applications/miso-controller/application.yaml +2 -2
- package/templates/applications/miso-controller/env.template +37 -122
- package/templates/applications/miso-controller/rbac.yaml +28 -13
- package/templates/channels/chatgpt/install.template.md.hbs +49 -0
- package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
- package/templates/channels/slack/install.template.md.hbs +49 -0
- package/templates/channels/slack/manifest.template.json.hbs +66 -0
- package/templates/channels/teams-bot/install.template.md.hbs +49 -0
- package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
- package/templates/channels/web-embed/install.template.md.hbs +49 -0
- package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
- package/templates/python/docker-compose.hbs +56 -0
- package/templates/typescript/docker-compose.hbs +52 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel CLI commands (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Scaffold communication external systems for channel deploy step 1
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const { formatSuccessLine, formatBlockingError, sectionTitle, metadata } = require('../utils/cli-test-layout-chalk');
|
|
14
|
+
const { CHANNEL_TARGET_KEYS, isChannelTargetKey } = require('../channels/channel-targets');
|
|
15
|
+
const { scaffoldChannelIntegration } = require('../channels/channel-integration-generator');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('commander').Command} program
|
|
19
|
+
*/
|
|
20
|
+
function setupChannelCommands(program) {
|
|
21
|
+
const channel = program
|
|
22
|
+
.command('channel')
|
|
23
|
+
.description('Assistant channel integration scaffolds (plan 152)');
|
|
24
|
+
|
|
25
|
+
channel
|
|
26
|
+
.command('scaffold <channelTarget>')
|
|
27
|
+
.description('Generate integration/<target>/ communication external system for deploy step 1')
|
|
28
|
+
.option('--force', 'Overwrite generated scaffold files when folder exists')
|
|
29
|
+
.option('--allow-experimental', 'Allow web-embed scaffold')
|
|
30
|
+
.action(async(channelTarget, options) => {
|
|
31
|
+
try {
|
|
32
|
+
if (!isChannelTargetKey(channelTarget)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Unknown channel target "${channelTarget}". Expected: ${CHANNEL_TARGET_KEYS.join(', ')}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const result = scaffoldChannelIntegration(channelTarget, {
|
|
38
|
+
force: options.force === true,
|
|
39
|
+
allowExperimental: options.allowExperimental === true
|
|
40
|
+
});
|
|
41
|
+
logger.log(sectionTitle(`${channelTarget} channel scaffold`));
|
|
42
|
+
logger.log('');
|
|
43
|
+
logger.log(formatSuccessLine(`Created integration at ${result.integrationDir}`));
|
|
44
|
+
for (const file of result.files) {
|
|
45
|
+
logger.log(metadata(file));
|
|
46
|
+
}
|
|
47
|
+
logger.log('');
|
|
48
|
+
logger.log(chalk.white('Next steps:'));
|
|
49
|
+
logger.log(chalk.gray(` aifabrix deploy ${channelTarget}`));
|
|
50
|
+
logger.log(chalk.gray(` aifabrix app add-channel ${channelTarget} --name "Your Assistant"`));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logger.error(formatBlockingError(error.message || String(error)));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { setupChannelCommands };
|
|
@@ -97,7 +97,7 @@ Omit [prefix] to list all datasources for the active environment.
|
|
|
97
97
|
|
|
98
98
|
function setupDatasourceValidateCommand(datasource) {
|
|
99
99
|
datasource.command('validate <file-or-key>')
|
|
100
|
-
.description('Validate datasource JSON (file path or datasource key under integration/<app>/)')
|
|
100
|
+
.description('Validate datasource YAML/JSON (file path or datasource key under integration/<app>/)')
|
|
101
101
|
.addHelpText('after', DATASOURCE_VALIDATE_HELP_AFTER)
|
|
102
102
|
.action(async(fileOrKey) => {
|
|
103
103
|
try {
|
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
const logger = require('../utils/logger');
|
|
10
|
-
const {
|
|
10
|
+
const { formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
|
|
11
11
|
const {
|
|
12
12
|
resolveControllerAndAuth,
|
|
13
13
|
resolveEnvKey,
|
|
14
14
|
requireNonEmpty,
|
|
15
15
|
unwrapControllerData,
|
|
16
16
|
throwIfApiFailed,
|
|
17
|
-
assertSyncStatsOk
|
|
17
|
+
assertSyncStatsOk,
|
|
18
|
+
logIdentityCliError
|
|
18
19
|
} = require('./identity-shared');
|
|
19
20
|
const { clearAuthCache } = require('../api/auth-cache.api');
|
|
20
21
|
const { fullSyncToDataplane } = require('../api/dataplane-sync.api');
|
|
@@ -70,7 +71,7 @@ function setupIdentityApplyCommand(identityCmd) {
|
|
|
70
71
|
);
|
|
71
72
|
}
|
|
72
73
|
} catch (e) {
|
|
73
|
-
|
|
74
|
+
logIdentityCliError(e.message);
|
|
74
75
|
process.exit(1);
|
|
75
76
|
}
|
|
76
77
|
});
|
|
@@ -103,6 +104,46 @@ async function maybePurgeCache(controllerUrl, authConfig, purgeCache) {
|
|
|
103
104
|
logger.log(formatSuccessLine('Controller cache cleared'));
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
/**
|
|
108
|
+
* @param {string} controllerUrl
|
|
109
|
+
* @param {string} envKey
|
|
110
|
+
* @param {Object} authConfig
|
|
111
|
+
* @returns {Promise<string>}
|
|
112
|
+
*/
|
|
113
|
+
async function resolveSyncDataplaneUrl(controllerUrl, envKey, authConfig) {
|
|
114
|
+
try {
|
|
115
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
116
|
+
return await resolveDataplaneUrl(controllerUrl, envKey, authConfig, { silent: true });
|
|
117
|
+
} catch {
|
|
118
|
+
return '—';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {Object} params
|
|
124
|
+
* @param {string} params.controllerUrl
|
|
125
|
+
* @param {string} params.envKey
|
|
126
|
+
* @param {Object} params.authConfig
|
|
127
|
+
* @param {Object|null|undefined} params.stats
|
|
128
|
+
*/
|
|
129
|
+
async function printIdentitySyncSuccess({ controllerUrl, envKey, authConfig, stats }) {
|
|
130
|
+
const dataplaneUrl = await resolveSyncDataplaneUrl(controllerUrl, envKey, authConfig);
|
|
131
|
+
const usersProcessed = Number(stats?.usersProcessed ?? 0);
|
|
132
|
+
const groupsProcessed = Number(stats?.groupsProcessed ?? 0);
|
|
133
|
+
const syncErrors = Number(stats?.errors ?? 0);
|
|
134
|
+
|
|
135
|
+
logger.log('');
|
|
136
|
+
logger.log(formatSuccessLine(`Identity synced to dataplane (${envKey})`));
|
|
137
|
+
logger.log(headerKeyValue(' Controller:', controllerUrl));
|
|
138
|
+
logger.log(headerKeyValue(' Dataplane:', dataplaneUrl));
|
|
139
|
+
logger.log('');
|
|
140
|
+
logger.log(` Users: ${usersProcessed} processed`);
|
|
141
|
+
logger.log(` Groups: ${groupsProcessed} processed`);
|
|
142
|
+
if (syncErrors > 0) {
|
|
143
|
+
logger.log(` Errors: ${syncErrors}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
106
147
|
async function runIdentitySyncAction(options) {
|
|
107
148
|
try {
|
|
108
149
|
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
@@ -116,16 +157,16 @@ async function runIdentitySyncAction(options) {
|
|
|
116
157
|
printJson(stats);
|
|
117
158
|
return;
|
|
118
159
|
}
|
|
119
|
-
|
|
120
|
-
logger.log(` usersProcessed=${stats?.usersProcessed ?? 0}`);
|
|
121
|
-
logger.log(` groupsProcessed=${stats?.groupsProcessed ?? 0}`);
|
|
160
|
+
await printIdentitySyncSuccess({ controllerUrl, envKey, authConfig, stats });
|
|
122
161
|
} catch (e) {
|
|
123
|
-
|
|
162
|
+
logIdentityCliError(e.message);
|
|
124
163
|
process.exit(1);
|
|
125
164
|
}
|
|
126
165
|
}
|
|
127
166
|
|
|
128
167
|
module.exports = {
|
|
129
168
|
setupIdentityApplyCommand,
|
|
130
|
-
setupIdentitySyncCommand
|
|
169
|
+
setupIdentitySyncCommand,
|
|
170
|
+
printIdentitySyncSuccess,
|
|
171
|
+
resolveSyncDataplaneUrl
|
|
131
172
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
const logger = require('../utils/logger');
|
|
10
|
-
const { formatBlockingError, formatSuccessLine
|
|
10
|
+
const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
|
|
11
11
|
const {
|
|
12
12
|
resolveControllerAndAuth,
|
|
13
13
|
requireNonEmpty,
|
|
@@ -15,11 +15,26 @@ const {
|
|
|
15
15
|
throwIfApiFailed
|
|
16
16
|
} = require('./identity-shared');
|
|
17
17
|
const { listGroups, getGroup, createGroup, listGroupMembers } = require('../api/groups.api');
|
|
18
|
+
const {
|
|
19
|
+
extractPaginatedList,
|
|
20
|
+
printGroupMembersTable,
|
|
21
|
+
printGroupRecord,
|
|
22
|
+
printIdentityEmpty,
|
|
23
|
+
printIdentityListHeader,
|
|
24
|
+
printIdentityNotFound,
|
|
25
|
+
printIdentityTable,
|
|
26
|
+
printListPaginationFooter,
|
|
27
|
+
GROUP_LIST_COLUMNS
|
|
28
|
+
} = require('../identity/identity-display');
|
|
18
29
|
|
|
19
30
|
function printJson(data) {
|
|
20
31
|
logger.log(JSON.stringify(data, null, 2));
|
|
21
32
|
}
|
|
22
33
|
|
|
34
|
+
function groupNotFoundHints() {
|
|
35
|
+
return ['af identity group list --search <text>', 'Use group name or id from group list'];
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
function registerGroupCreate(groupCmd) {
|
|
24
39
|
groupCmd
|
|
25
40
|
.command('create')
|
|
@@ -51,7 +66,7 @@ function registerGroupList(groupCmd) {
|
|
|
51
66
|
.description('List groups')
|
|
52
67
|
.option('--page <n>', 'Page', (v) => parseInt(v, 10))
|
|
53
68
|
.option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
|
|
54
|
-
.option('--search <text>', 'Search groups')
|
|
69
|
+
.option('--search <text>', 'Search groups by name or display name')
|
|
55
70
|
.option('--json', 'JSON output')
|
|
56
71
|
.action(async(options) => {
|
|
57
72
|
try {
|
|
@@ -62,14 +77,24 @@ function registerGroupList(groupCmd) {
|
|
|
62
77
|
search: options.search
|
|
63
78
|
});
|
|
64
79
|
throwIfApiFailed(res);
|
|
65
|
-
const data = unwrapControllerData(res);
|
|
66
80
|
if (options.json) {
|
|
67
|
-
printJson(data);
|
|
81
|
+
printJson(res.data ?? res);
|
|
68
82
|
return;
|
|
69
83
|
}
|
|
70
|
-
const items =
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
const { items, meta } = extractPaginatedList(res);
|
|
85
|
+
printIdentityListHeader({
|
|
86
|
+
emoji: '👥',
|
|
87
|
+
title: 'Groups',
|
|
88
|
+
context: controllerUrl,
|
|
89
|
+
shown: items.length,
|
|
90
|
+
total: meta?.totalItems ?? items.length
|
|
91
|
+
});
|
|
92
|
+
if (items.length === 0) {
|
|
93
|
+
printIdentityEmpty('No groups found.', ['Try --search']);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
printIdentityTable(GROUP_LIST_COLUMNS, items);
|
|
97
|
+
printListPaginationFooter(meta);
|
|
73
98
|
} catch (e) {
|
|
74
99
|
logger.error(formatBlockingError(e.message));
|
|
75
100
|
process.exit(1);
|
|
@@ -79,22 +104,28 @@ function registerGroupList(groupCmd) {
|
|
|
79
104
|
|
|
80
105
|
function registerGroupGet(groupCmd) {
|
|
81
106
|
groupCmd
|
|
82
|
-
.command('get <
|
|
107
|
+
.command('get <groupIdOrName>')
|
|
83
108
|
.description('Get group by id or name')
|
|
84
109
|
.option('--json', 'JSON output')
|
|
85
|
-
.action(async(
|
|
110
|
+
.action(async(groupIdOrName, options) => {
|
|
86
111
|
try {
|
|
87
112
|
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
88
|
-
const
|
|
89
|
-
|
|
113
|
+
const value = requireNonEmpty(groupIdOrName, 'groupIdOrName');
|
|
114
|
+
const res = await getGroup(controllerUrl, authConfig, value);
|
|
115
|
+
if (!res || res.success === false) {
|
|
116
|
+
printIdentityNotFound('Group', value, groupNotFoundHints());
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
90
119
|
const row = unwrapControllerData(res);
|
|
120
|
+
if (!row?.id) {
|
|
121
|
+
printIdentityNotFound('Group', value, groupNotFoundHints());
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
91
124
|
if (options.json) {
|
|
92
125
|
printJson(row);
|
|
93
126
|
return;
|
|
94
127
|
}
|
|
95
|
-
|
|
96
|
-
logger.log(headerKeyValue('Name:', row.name));
|
|
97
|
-
logger.log('');
|
|
128
|
+
printGroupRecord(row);
|
|
98
129
|
} catch (e) {
|
|
99
130
|
logger.error(formatBlockingError(e.message));
|
|
100
131
|
process.exit(1);
|
|
@@ -104,26 +135,35 @@ function registerGroupGet(groupCmd) {
|
|
|
104
135
|
|
|
105
136
|
function registerGroupMembers(groupCmd) {
|
|
106
137
|
groupCmd
|
|
107
|
-
.command('members <
|
|
138
|
+
.command('members <groupIdOrName>')
|
|
108
139
|
.description('List members of a group')
|
|
109
140
|
.option('--json', 'JSON output')
|
|
110
|
-
.action(async(
|
|
141
|
+
.action(async(groupIdOrName, options) => {
|
|
111
142
|
try {
|
|
112
143
|
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
144
|
+
const value = requireNonEmpty(groupIdOrName, 'groupIdOrName');
|
|
145
|
+
const res = await listGroupMembers(controllerUrl, authConfig, value);
|
|
146
|
+
if (!res || res.success === false) {
|
|
147
|
+
printIdentityNotFound('Group', value, groupNotFoundHints());
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
119
150
|
const data = unwrapControllerData(res);
|
|
151
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
120
152
|
if (options.json) {
|
|
121
|
-
printJson(
|
|
153
|
+
printJson(items);
|
|
122
154
|
return;
|
|
123
155
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
156
|
+
printIdentityListHeader({
|
|
157
|
+
emoji: '👤',
|
|
158
|
+
title: `Members of ${value}`,
|
|
159
|
+
shown: items.length,
|
|
160
|
+
total: items.length
|
|
161
|
+
});
|
|
162
|
+
if (items.length === 0) {
|
|
163
|
+
printIdentityEmpty('No members in this group.');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
printGroupMembersTable(items);
|
|
127
167
|
} catch (e) {
|
|
128
168
|
logger.error(formatBlockingError(e.message));
|
|
129
169
|
process.exit(1);
|
|
@@ -19,30 +19,43 @@ const {
|
|
|
19
19
|
const { addUserToGroup, removeUserFromGroup } = require('../api/user-groups.api');
|
|
20
20
|
const { listEnvironmentRoles, updateRoleGroups } = require('../api/environments.api');
|
|
21
21
|
const { clearAuthCache, invalidateAuthCache } = require('../api/auth-cache.api');
|
|
22
|
+
const {
|
|
23
|
+
printIdentityEmpty,
|
|
24
|
+
printIdentityListHeader,
|
|
25
|
+
printIdentityTable
|
|
26
|
+
} = require('../identity/identity-display');
|
|
22
27
|
|
|
23
28
|
function printJson(data) {
|
|
24
29
|
logger.log(JSON.stringify(data, null, 2));
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
const ROLE_LIST_COLUMNS = [
|
|
33
|
+
{ label: 'Role', width: 36, value: (r) => r.value || r.role || r.name || '—' },
|
|
34
|
+
{
|
|
35
|
+
label: 'Groups',
|
|
36
|
+
width: 44,
|
|
37
|
+
value: (r) => {
|
|
38
|
+
const groups = Array.isArray(r.groups) ? r.groups.filter(Boolean) : [];
|
|
39
|
+
return groups.length > 0 ? groups.join(', ') : '—';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
|
|
27
44
|
/**
|
|
28
45
|
* @param {import('commander').Command} memCmd
|
|
29
46
|
*/
|
|
30
47
|
function setupIdentityMembershipCommands(memCmd) {
|
|
31
48
|
memCmd
|
|
32
|
-
.command('add <
|
|
33
|
-
.description('Add user to group (
|
|
34
|
-
.action(async(
|
|
49
|
+
.command('add <userIdOrEmail> <groupIdOrName>')
|
|
50
|
+
.description('Add user to group (id, email, username + group id or name)')
|
|
51
|
+
.action(async(userIdOrEmail, groupIdOrName) => {
|
|
35
52
|
try {
|
|
36
53
|
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
requireNonEmpty(userId, 'userId'),
|
|
41
|
-
requireNonEmpty(groupId, 'groupId'),
|
|
42
|
-
{}
|
|
43
|
-
);
|
|
54
|
+
const userRef = requireNonEmpty(userIdOrEmail, 'userIdOrEmail');
|
|
55
|
+
const groupRef = requireNonEmpty(groupIdOrName, 'groupIdOrName');
|
|
56
|
+
const res = await addUserToGroup(controllerUrl, authConfig, userRef, groupRef, {});
|
|
44
57
|
throwIfApiFailed(res);
|
|
45
|
-
logger.log(formatSuccessLine(`Membership created: ${
|
|
58
|
+
logger.log(formatSuccessLine(`Membership created: ${userRef} → ${groupRef}`));
|
|
46
59
|
} catch (e) {
|
|
47
60
|
logger.error(formatBlockingError(e.message));
|
|
48
61
|
process.exit(1);
|
|
@@ -50,19 +63,16 @@ function setupIdentityMembershipCommands(memCmd) {
|
|
|
50
63
|
});
|
|
51
64
|
|
|
52
65
|
memCmd
|
|
53
|
-
.command('remove <
|
|
66
|
+
.command('remove <userIdOrEmail> <groupIdOrName>')
|
|
54
67
|
.description('Remove user from group')
|
|
55
|
-
.action(async(
|
|
68
|
+
.action(async(userIdOrEmail, groupIdOrName) => {
|
|
56
69
|
try {
|
|
57
70
|
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
requireNonEmpty(userId, 'userId'),
|
|
62
|
-
requireNonEmpty(groupId, 'groupId')
|
|
63
|
-
);
|
|
71
|
+
const userRef = requireNonEmpty(userIdOrEmail, 'userIdOrEmail');
|
|
72
|
+
const groupRef = requireNonEmpty(groupIdOrName, 'groupIdOrName');
|
|
73
|
+
const res = await removeUserFromGroup(controllerUrl, authConfig, userRef, groupRef);
|
|
64
74
|
throwIfApiFailed(res);
|
|
65
|
-
logger.log(formatSuccessLine(`Membership removed: ${
|
|
75
|
+
logger.log(formatSuccessLine(`Membership removed: ${userRef} → ${groupRef}`));
|
|
66
76
|
} catch (e) {
|
|
67
77
|
logger.error(formatBlockingError(e.message));
|
|
68
78
|
process.exit(1);
|
|
@@ -73,29 +83,40 @@ function setupIdentityMembershipCommands(memCmd) {
|
|
|
73
83
|
/**
|
|
74
84
|
* @param {import('commander').Command} roleCmd
|
|
75
85
|
*/
|
|
86
|
+
async function handleRoleListAction(options) {
|
|
87
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
88
|
+
const envKey = await resolveEnvKey(options);
|
|
89
|
+
const res = await listEnvironmentRoles(controllerUrl, envKey, authConfig);
|
|
90
|
+
throwIfApiFailed(res);
|
|
91
|
+
const data = unwrapControllerData(res);
|
|
92
|
+
if (options.json) {
|
|
93
|
+
printJson(data);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const items = Array.isArray(data) ? data : data?.data || data?.roles || [];
|
|
97
|
+
printIdentityListHeader({
|
|
98
|
+
emoji: '🔐',
|
|
99
|
+
title: 'Role → group mappings',
|
|
100
|
+
context: `environment: ${envKey}`,
|
|
101
|
+
shown: (items || []).length,
|
|
102
|
+
total: (items || []).length
|
|
103
|
+
});
|
|
104
|
+
if (!items || items.length === 0) {
|
|
105
|
+
printIdentityEmpty('No roles found for this environment.');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
printIdentityTable(ROLE_LIST_COLUMNS, items);
|
|
109
|
+
}
|
|
110
|
+
|
|
76
111
|
function setupIdentityRoleCommands(roleCmd) {
|
|
77
112
|
roleCmd
|
|
78
113
|
.command('list')
|
|
79
114
|
.description('List roles and group mappings for an environment')
|
|
80
|
-
.
|
|
115
|
+
.option('-e, --env <key>', 'Environment key (defaults from aifabrix config)')
|
|
81
116
|
.option('--json', 'JSON output')
|
|
82
117
|
.action(async(options) => {
|
|
83
118
|
try {
|
|
84
|
-
|
|
85
|
-
const envKey = await resolveEnvKey(options);
|
|
86
|
-
const res = await listEnvironmentRoles(controllerUrl, envKey, authConfig);
|
|
87
|
-
throwIfApiFailed(res);
|
|
88
|
-
const data = unwrapControllerData(res);
|
|
89
|
-
if (options.json) {
|
|
90
|
-
printJson(data);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const items = Array.isArray(data) ? data : data?.data || data?.roles || [];
|
|
94
|
-
(items || []).forEach((r) => {
|
|
95
|
-
const groups = Array.isArray(r.groups) ? r.groups.join(', ') : '';
|
|
96
|
-
logger.log(` ${r.value || r.role || r.name} → ${groups || '—'}`);
|
|
97
|
-
});
|
|
98
|
-
logger.log('');
|
|
119
|
+
await handleRoleListAction(options);
|
|
99
120
|
} catch (e) {
|
|
100
121
|
logger.error(formatBlockingError(e.message));
|
|
101
122
|
process.exit(1);
|
|
@@ -105,7 +126,7 @@ function setupIdentityRoleCommands(roleCmd) {
|
|
|
105
126
|
roleCmd
|
|
106
127
|
.command('set-groups <roleValue>')
|
|
107
128
|
.description('Map role to groups in an environment')
|
|
108
|
-
.
|
|
129
|
+
.option('-e, --env <key>', 'Environment key (defaults from aifabrix config)')
|
|
109
130
|
.requiredOption('--groups <names>', 'Comma-separated group names')
|
|
110
131
|
.action(async(roleValue, options) => {
|
|
111
132
|
try {
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const { formatBlockingError } = require('../utils/cli-layout-chalk');
|
|
9
12
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
10
13
|
const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
|
|
11
14
|
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
@@ -82,6 +85,23 @@ function parseGroupsList(groupsArg) {
|
|
|
82
85
|
return parts;
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Print a blocking CLI error with optional multi-line guidance in gray.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} message
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*/
|
|
94
|
+
function logIdentityCliError(message) {
|
|
95
|
+
const lines = String(message || '').split('\n');
|
|
96
|
+
const head = lines[0] || 'Request failed';
|
|
97
|
+
logger.error(formatBlockingError(head));
|
|
98
|
+
lines.slice(1).forEach((line) => {
|
|
99
|
+
if (line.trim()) {
|
|
100
|
+
logger.log(chalk.gray(line));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
85
105
|
module.exports = {
|
|
86
106
|
resolveControllerAndAuth,
|
|
87
107
|
resolveEnvKey,
|
|
@@ -90,5 +110,6 @@ module.exports = {
|
|
|
90
110
|
unwrapControllerData,
|
|
91
111
|
throwIfApiFailed,
|
|
92
112
|
assertSyncStatsOk,
|
|
93
|
-
apiErrorMessage
|
|
113
|
+
apiErrorMessage,
|
|
114
|
+
logIdentityCliError
|
|
94
115
|
};
|