@aifabrix/builder 2.45.0 → 2.45.5
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 +3 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.projects.js +44 -1
- package/lib/api/agent-metadata-validation.api.js +93 -0
- package/lib/api/audit-api-helpers.js +68 -0
- package/lib/api/audit.api.js +119 -0
- package/lib/api/auth-cache.api.js +44 -0
- package/lib/api/certificates.api.js +4 -4
- package/lib/api/credential.api.js +2 -2
- package/lib/api/dataplane-health.api.js +137 -0
- package/lib/api/dataplane-sync.api.js +27 -0
- package/lib/api/datasources-core.api.js +21 -18
- package/lib/api/datasources-extended.api.js +22 -22
- package/lib/api/dimensions.api.js +3 -0
- package/lib/api/external-systems.api.js +13 -13
- package/lib/api/governance-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +45 -2
- package/lib/api/pipeline.api.js +4 -4
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +30 -0
- package/lib/api/records-search-parse.js +49 -0
- package/lib/api/records-search.api.js +29 -0
- package/lib/api/types/agent-metadata-validation.types.js +67 -0
- package/lib/api/types/audit.types.js +72 -0
- package/lib/api/types/dataplane-health.types.js +27 -0
- package/lib/api/types/dimensions.types.js +2 -0
- package/lib/api/types/governance-scenarios.types.js +39 -0
- package/lib/api/types/protection.types.js +69 -0
- package/lib/api/types/records-bulk.types.js +35 -0
- package/lib/api/types/records-search.types.js +28 -0
- package/lib/api/user-groups.api.js +50 -0
- package/lib/api/users.api.js +95 -0
- package/lib/api/validation-run.api.js +3 -3
- package/lib/api/validation-runner.js +3 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy.js +34 -19
- package/lib/cli/index.js +4 -2
- package/lib/cli/infra-guided.js +22 -3
- package/lib/cli/setup-app.help.js +50 -2
- package/lib/cli/setup-app.js +33 -24
- package/lib/cli/setup-app.test-commands.js +80 -21
- package/lib/cli/setup-auth.js +30 -3
- package/lib/cli/setup-external-system.js +37 -3
- package/lib/cli/setup-utility-help-after.js +45 -0
- package/lib/cli/setup-utility-resolve.js +2 -2
- package/lib/cli/setup-utility.js +99 -81
- package/lib/commands/auth-status-dataplane-version.js +231 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +70 -6
- package/lib/commands/credential-push.js +10 -5
- package/lib/commands/datasource-load-export-cli.js +178 -0
- package/lib/commands/datasource-test-trust-cli.js +114 -0
- package/lib/commands/datasource-unified-test-cli.js +25 -17
- package/lib/commands/datasource-unified-test-cli.options.js +18 -3
- package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
- package/lib/commands/datasource-verify-audit-cli.js +96 -0
- package/lib/commands/datasource.js +68 -1
- package/lib/commands/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- package/lib/commands/identity-apply-sync.js +131 -0
- package/lib/commands/identity-group-cli.js +146 -0
- package/lib/commands/identity-membership-role-cache.js +175 -0
- package/lib/commands/identity-shared.js +94 -0
- package/lib/commands/identity-user-cli.js +158 -0
- package/lib/commands/identity.js +43 -0
- package/lib/commands/login.js +46 -16
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair.js +13 -3
- package/lib/commands/test-e2e-external.js +40 -9
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-command-action.js +55 -0
- package/lib/commands/test-governance-external.js +88 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-command-action.js +118 -0
- package/lib/commands/test-trust-external.js +127 -0
- package/lib/commands/upload.js +34 -13
- package/lib/commands/wizard-core.js +3 -0
- package/lib/core/config-device-dataplane.js +185 -0
- package/lib/datasource/agent-trust-debug-log.js +45 -0
- package/lib/datasource/agent-trust-fetch.js +115 -0
- package/lib/datasource/agent-trust-map.js +101 -0
- package/lib/datasource/agent-trust-mock.js +44 -0
- package/lib/datasource/agent-trust-run.js +93 -0
- package/lib/datasource/agent-trust-timeout.js +25 -0
- package/lib/datasource/audit-evidence-display.js +80 -0
- package/lib/datasource/audit-evidence-extract-debug.js +103 -0
- package/lib/datasource/audit-evidence-extract.js +177 -0
- package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
- package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
- package/lib/datasource/audit-evidence-poll.js +109 -0
- package/lib/datasource/audit-evidence-run.js +177 -0
- package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
- package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +88 -0
- package/lib/datasource/bulk-loader-service-retry.js +52 -0
- package/lib/datasource/bulk-loader-service.js +127 -0
- package/lib/datasource/capability/run-capability-dimension.js +2 -1
- package/lib/datasource/datasource-exporter-resolve.js +82 -0
- package/lib/datasource/datasource-exporter-service.js +144 -0
- package/lib/datasource/datasource-identifier.js +148 -0
- package/lib/datasource/datasource-load-export-context.js +92 -0
- package/lib/datasource/integration-context.js +6 -4
- package/lib/datasource/local-data-paths.js +145 -0
- package/lib/datasource/log-cleaner.js +241 -0
- package/lib/datasource/log-viewer.js +111 -6
- package/lib/datasource/record-file-parser.js +85 -0
- package/lib/datasource/record-mapper.js +129 -0
- package/lib/datasource/resolve-app.js +74 -55
- package/lib/datasource/test-e2e.js +26 -2
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +43 -17
- package/lib/datasource/validate.js +37 -20
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +3 -1
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +119 -0
- package/lib/identity/identity-apply-core.js +108 -0
- package/lib/identity/identity-apply-phases.js +244 -0
- package/lib/identity/identity-apply-service.js +77 -0
- package/lib/identity/identity-csv-parser.js +114 -0
- package/lib/internal/fs-real-sync.js +29 -1
- package/lib/protection/auth-context.js +45 -0
- package/lib/protection/convert-batch.js +90 -0
- package/lib/protection/load.js +41 -0
- package/lib/protection/paths.js +158 -0
- package/lib/protection/preflight-datasource-ready.js +46 -0
- package/lib/protection/protection-create-probe-lines.js +142 -0
- package/lib/protection/protection-create-probes.js +95 -0
- package/lib/protection/protection-create-scaffold.js +281 -0
- package/lib/protection/protection-display-helpers.js +445 -0
- package/lib/protection/protection-display.js +215 -0
- package/lib/protection/protection-preset-registry.js +97 -0
- package/lib/protection/report-exit.js +62 -0
- package/lib/protection/resolve.js +202 -0
- package/lib/protection/run-commands-validate.js +130 -0
- package/lib/protection/run-commands.js +216 -0
- package/lib/protection/run-protection-create-helpers.js +134 -0
- package/lib/protection/run-protection-create-probes-flow.js +43 -0
- package/lib/protection/run-protection-create.js +191 -0
- package/lib/protection/scope.js +22 -0
- package/lib/protection/sync-after-upload.js +64 -0
- package/lib/protection/upload-batch.js +127 -0
- package/lib/protection/validate-batch.js +113 -0
- package/lib/protection/validate-local.js +65 -0
- package/lib/resolvers/dimension-file.js +29 -4
- package/lib/schema/external-datasource.schema.json +47 -13
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +636 -0
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/agent-trust-run-display.js +231 -0
- package/lib/utils/agent-trust-run-exit.js +59 -0
- package/lib/utils/agent-trust-wait-ui.js +94 -0
- package/lib/utils/cli-exit-codes.js +30 -0
- package/lib/utils/cli-sync-options.js +33 -0
- package/lib/utils/credential-push-ui.js +90 -0
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- package/lib/utils/datasource-export-display.js +137 -0
- package/lib/utils/datasource-load-display.js +174 -0
- package/lib/utils/datasource-load-export-ui.js +120 -0
- package/lib/utils/datasource-test-run-capability-scope.js +3 -3
- package/lib/utils/datasource-test-run-display.js +3 -3
- package/lib/utils/datasource-test-run-tty-log.js +5 -1
- package/lib/utils/help-builder.js +30 -24
- package/lib/utils/manifest-source-emit.js +22 -0
- package/lib/utils/semver-compare.js +127 -0
- package/lib/utils/upload-pipeline-ui.js +91 -0
- package/lib/utils/validation-poll-ui.js +14 -7
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +1 -1
- package/templates/applications/dataplane/env.template +13 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- package/templates/external-system/README.md.hbs +56 -8
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* AI Fabrix Builder - Datasource Commands
|
|
3
3
|
*
|
|
4
4
|
* Handles datasource validation, listing, comparison, deployment, and online validation runs.
|
|
5
|
-
* Subcommands `test`, `test-integration`, and `test-
|
|
5
|
+
* Subcommands `test`, `test-integration`, `test-e2e`, and `test-trust` call the dataplane; `log-*` read saved debug JSON locally; `clean-logs` removes those files. Permissions are summarized in `docs/commands/permissions.md`.
|
|
6
6
|
*
|
|
7
7
|
* @fileoverview Datasource management commands for AI Fabrix Builder
|
|
8
8
|
* @author AI Fabrix Team
|
|
@@ -18,11 +18,15 @@ const { listDatasources } = require('../datasource/list');
|
|
|
18
18
|
const { compareDatasources } = require('../datasource/diff');
|
|
19
19
|
const { deployDatasource } = require('../datasource/deploy');
|
|
20
20
|
const { runLogViewer } = require('../datasource/log-viewer');
|
|
21
|
+
const { runCleanLogs } = require('../datasource/log-cleaner');
|
|
21
22
|
const {
|
|
22
23
|
setupDatasourceTestCommand,
|
|
23
24
|
setupDatasourceTestIntegrationCommand,
|
|
24
25
|
setupDatasourceTestE2ECommand
|
|
25
26
|
} = require('./datasource-unified-test-cli');
|
|
27
|
+
const { setupDatasourceTestTrustCommand } = require('./datasource-test-trust-cli');
|
|
28
|
+
const { setupDatasourceVerifyAuditCommand } = require('./datasource-verify-audit-cli');
|
|
29
|
+
const { setupDatasourceLoadExportCommands } = require('./datasource-load-export-cli');
|
|
26
30
|
const { setupDatasourceCapabilityCommands } = require('./datasource-capability');
|
|
27
31
|
|
|
28
32
|
/**
|
|
@@ -38,10 +42,17 @@ Examples:
|
|
|
38
42
|
$ af ds upload <datasourceKey> # same as: aifabrix datasource upload …
|
|
39
43
|
$ af ds test <datasourceKey> --app <integrationFolder> --debug
|
|
40
44
|
$ af ds test-e2e <datasourceKey> --app <integrationFolder> -v
|
|
45
|
+
$ af ds test-e2e <datasourceKey> --verify-audit
|
|
46
|
+
$ af ds verify-audit <datasourceKey>
|
|
47
|
+
$ af ds test-trust <datasourceKey> --app <integrationFolder> -v
|
|
48
|
+
$ af ds load <datasourceKey> --app <integrationFolder> --dry-run
|
|
49
|
+
$ af ds export <datasourceKey> --app <integrationFolder> --format ndjson -v
|
|
41
50
|
$ aifabrix datasource capability copy <file-or-key> --from create --as createBasicTrial --dry-run
|
|
42
51
|
$ aifabrix datasource capability remove <file-or-key> --capability createTrial --dry-run
|
|
43
52
|
$ aifabrix datasource capability diff ./a.json ./b.json --capability list
|
|
44
53
|
$ aifabrix datasource capability edit <file-or-key> --capability create --section openapi
|
|
54
|
+
$ aifabrix datasource clean-logs --app test-e2e-hubspot --dry-run
|
|
55
|
+
$ aifabrix datasource clean-logs --all --type e2e
|
|
45
56
|
|
|
46
57
|
Per-command help: aifabrix datasource <command> --help
|
|
47
58
|
Shorthand: af ds (alias for datasource)
|
|
@@ -220,6 +231,57 @@ function setupDatasourceLogTestCommand(datasource) {
|
|
|
220
231
|
});
|
|
221
232
|
}
|
|
222
233
|
|
|
234
|
+
function setupDatasourceCleanLogsCommand(datasource) {
|
|
235
|
+
datasource
|
|
236
|
+
.command('clean-logs')
|
|
237
|
+
.description('Remove saved test debug JSON under integration/<app>/logs/')
|
|
238
|
+
.option('-a, --app <app>', 'Integration folder name (system key)')
|
|
239
|
+
.option('--all', 'Clean logs under every integration/<app>/logs/ folder')
|
|
240
|
+
.option(
|
|
241
|
+
'-t, --type <type>',
|
|
242
|
+
'Log kind: test, integration, e2e, trust, or all (default: all)',
|
|
243
|
+
'all'
|
|
244
|
+
)
|
|
245
|
+
.option('--dry-run', 'List files that would be removed without deleting')
|
|
246
|
+
.option('--json', 'Machine-readable summary on stdout')
|
|
247
|
+
.action(async options => {
|
|
248
|
+
try {
|
|
249
|
+
await runCleanLogs({
|
|
250
|
+
app: options.app,
|
|
251
|
+
all: options.all,
|
|
252
|
+
type: options.type,
|
|
253
|
+
dryRun: options.dryRun,
|
|
254
|
+
json: options.json
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
logger.error(formatBlockingError('clean-logs failed:'), error.message);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function setupDatasourceLogTrustCommand(datasource) {
|
|
264
|
+
datasource.command('log-trust <datasourceKey>')
|
|
265
|
+
.description('Show agent trust log from datasource test-trust (latest test-trust-*.json or --file)')
|
|
266
|
+
.option(
|
|
267
|
+
'-a, --app <app>',
|
|
268
|
+
'Integration folder name (optional: resolve from cwd or datasource key if single match)'
|
|
269
|
+
)
|
|
270
|
+
.option('-f, --file <path>', 'Path to log file (default: latest test-trust log in app logs folder)')
|
|
271
|
+
.action(async(datasourceKey, options) => {
|
|
272
|
+
try {
|
|
273
|
+
await runLogViewer(datasourceKey, {
|
|
274
|
+
app: options.app,
|
|
275
|
+
file: options.file,
|
|
276
|
+
logType: 'test-trust'
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
logger.error(formatBlockingError('log-trust failed:'), error.message);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
223
285
|
/**
|
|
224
286
|
* Setup datasource management commands
|
|
225
287
|
* @param {Command} program - Commander program instance
|
|
@@ -237,12 +299,17 @@ function setupDatasourceCommands(program) {
|
|
|
237
299
|
setupDatasourceListCommand(datasource);
|
|
238
300
|
setupDatasourceDiffCommand(datasource);
|
|
239
301
|
setupDatasourceUploadCommand(datasource);
|
|
302
|
+
setupDatasourceLoadExportCommands(datasource);
|
|
240
303
|
setupDatasourceTestCommand(datasource);
|
|
241
304
|
setupDatasourceTestIntegrationCommand(datasource);
|
|
242
305
|
setupDatasourceTestE2ECommand(datasource);
|
|
306
|
+
setupDatasourceVerifyAuditCommand(datasource);
|
|
307
|
+
setupDatasourceTestTrustCommand(datasource);
|
|
243
308
|
setupDatasourceLogE2ECommand(datasource);
|
|
244
309
|
setupDatasourceLogIntegrationCommand(datasource);
|
|
245
310
|
setupDatasourceLogTestCommand(datasource);
|
|
311
|
+
setupDatasourceLogTrustCommand(datasource);
|
|
312
|
+
setupDatasourceCleanLogsCommand(datasource);
|
|
246
313
|
}
|
|
247
314
|
|
|
248
315
|
module.exports = { setupDatasourceCommands };
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Dimension value commands (Controller).
|
|
3
3
|
*
|
|
4
4
|
* Commands:
|
|
5
|
-
* - aifabrix dimension
|
|
6
|
-
* - aifabrix dimension
|
|
7
|
-
* - aifabrix dimension
|
|
5
|
+
* - aifabrix dimension value create
|
|
6
|
+
* - aifabrix dimension value list
|
|
7
|
+
* - aifabrix dimension value delete
|
|
8
8
|
*
|
|
9
9
|
* @fileoverview Dimension value CLI commands
|
|
10
10
|
* @author AI Fabrix Team
|
|
@@ -25,29 +25,39 @@ const {
|
|
|
25
25
|
deleteDimensionValue
|
|
26
26
|
} = require('../api/dimension-values.api');
|
|
27
27
|
|
|
28
|
+
const DIMENSION_VALUE_HELP_AFTER = `
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
$ aifabrix dimension value create customerRegion --value emea --display-name "EMEA"
|
|
32
|
+
$ aifabrix dimension value list customerRegion
|
|
33
|
+
$ aifabrix dimension value delete clx1234567890abcdef
|
|
34
|
+
|
|
35
|
+
Per-command help: aifabrix dimension value <command> --help
|
|
36
|
+
`;
|
|
37
|
+
|
|
28
38
|
const DIMENSION_VALUE_CREATE_HELP_AFTER = `
|
|
29
39
|
|
|
30
40
|
Examples:
|
|
31
|
-
$ aifabrix dimension
|
|
32
|
-
$ aifabrix dimension
|
|
41
|
+
$ aifabrix dimension value create customerRegion --value emea --display-name "EMEA"
|
|
42
|
+
$ aifabrix dimension value create customerRegion --value na --display-name "North America"
|
|
33
43
|
|
|
34
44
|
`;
|
|
35
45
|
|
|
36
46
|
const DIMENSION_VALUE_LIST_HELP_AFTER = `
|
|
37
47
|
|
|
38
48
|
Examples:
|
|
39
|
-
$ aifabrix dimension
|
|
40
|
-
$ aifabrix dimension
|
|
41
|
-
$ aifabrix dimension
|
|
49
|
+
$ aifabrix dimension value list customerRegion
|
|
50
|
+
$ aifabrix dimension value list customerRegion --page 1 --page-size 50
|
|
51
|
+
$ aifabrix dimension value list customerRegion --search emea
|
|
42
52
|
|
|
43
53
|
`;
|
|
44
54
|
|
|
45
55
|
const DIMENSION_VALUE_DELETE_HELP_AFTER = `
|
|
46
56
|
|
|
47
57
|
Examples:
|
|
48
|
-
$ aifabrix dimension
|
|
58
|
+
$ aifabrix dimension value delete clx1234567890abcdef
|
|
49
59
|
|
|
50
|
-
Tip: Find ids via "aifabrix dimension get <key>" (it prints values) or "aifabrix dimension
|
|
60
|
+
Tip: Find ids via "aifabrix dimension get <key>" (it prints values) or "aifabrix dimension value list <key>".
|
|
51
61
|
|
|
52
62
|
`;
|
|
53
63
|
|
|
@@ -62,6 +72,17 @@ function requireNonEmpty(raw, label) {
|
|
|
62
72
|
return s;
|
|
63
73
|
}
|
|
64
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @param {Object} response - Controller API response
|
|
77
|
+
* @throws {Error} When response.success === false
|
|
78
|
+
*/
|
|
79
|
+
function throwIfApiFailed(response) {
|
|
80
|
+
if (!response || response.success !== false) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
throw new Error(response.formattedError || response.error || 'Request failed');
|
|
84
|
+
}
|
|
85
|
+
|
|
65
86
|
async function resolveControllerAndAuth() {
|
|
66
87
|
const controllerUrl = await resolveControllerUrl();
|
|
67
88
|
if (!controllerUrl) {
|
|
@@ -100,6 +121,7 @@ function setupDimensionValueCreateCommand(cmd) {
|
|
|
100
121
|
displayName: options.displayName,
|
|
101
122
|
description: options.description
|
|
102
123
|
});
|
|
124
|
+
throwIfApiFailed(res);
|
|
103
125
|
const row = unwrapControllerData(res);
|
|
104
126
|
logger.log(formatSuccessLine(`Dimension value created: ${dimKey}.${row.value}`));
|
|
105
127
|
} catch (e) {
|
|
@@ -126,6 +148,7 @@ function setupDimensionValueListCommand(cmd) {
|
|
|
126
148
|
pageSize: options.pageSize,
|
|
127
149
|
search: options.search
|
|
128
150
|
});
|
|
151
|
+
throwIfApiFailed(res);
|
|
129
152
|
const payload = unwrapControllerData(res);
|
|
130
153
|
const items = payload?.data ?? payload ?? [];
|
|
131
154
|
logger.log(chalk.bold('\n🏷 Dimension values:\n'));
|
|
@@ -157,7 +180,8 @@ function setupDimensionValueDeleteCommand(cmd) {
|
|
|
157
180
|
try {
|
|
158
181
|
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
159
182
|
const id = requireNonEmpty(dimensionValueId, 'dimensionValueId');
|
|
160
|
-
await deleteDimensionValue(controllerUrl, authConfig, id);
|
|
183
|
+
const res = await deleteDimensionValue(controllerUrl, authConfig, id);
|
|
184
|
+
throwIfApiFailed(res);
|
|
161
185
|
logger.log(formatSuccessLine(`Dimension value deleted: ${id}`));
|
|
162
186
|
} catch (e) {
|
|
163
187
|
logger.error(formatBlockingError(e.message));
|
|
@@ -166,8 +190,14 @@ function setupDimensionValueDeleteCommand(cmd) {
|
|
|
166
190
|
});
|
|
167
191
|
}
|
|
168
192
|
|
|
169
|
-
|
|
170
|
-
|
|
193
|
+
/**
|
|
194
|
+
* @param {import('commander').Command} dimensionCmd - Parent `dimension` command
|
|
195
|
+
*/
|
|
196
|
+
function setupDimensionValueCommands(dimensionCmd) {
|
|
197
|
+
const cmd = dimensionCmd
|
|
198
|
+
.command('value')
|
|
199
|
+
.description('Manage dimension values (static dimensions)')
|
|
200
|
+
.addHelpText('after', DIMENSION_VALUE_HELP_AFTER);
|
|
171
201
|
setupDimensionValueCreateCommand(cmd);
|
|
172
202
|
setupDimensionValueListCommand(cmd);
|
|
173
203
|
setupDimensionValueDeleteCommand(cmd);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* - aifabrix dimension create
|
|
6
6
|
* - aifabrix dimension get
|
|
7
7
|
* - aifabrix dimension list
|
|
8
|
+
* - aifabrix dimension value create|list|delete
|
|
8
9
|
*
|
|
9
10
|
* @fileoverview Dimension CLI commands
|
|
10
11
|
* @author AI Fabrix Team
|
|
@@ -18,7 +19,7 @@ const logger = require('../utils/logger');
|
|
|
18
19
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
19
20
|
const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
|
|
20
21
|
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
21
|
-
const { readDimensionCreateFile } = require('../resolvers/dimension-file');
|
|
22
|
+
const { readDimensionCreateFile, normalizeValueType } = require('../resolvers/dimension-file');
|
|
22
23
|
const {
|
|
23
24
|
formatBlockingError,
|
|
24
25
|
formatSuccessLine,
|
|
@@ -31,8 +32,23 @@ const {
|
|
|
31
32
|
createDimensionIdempotent
|
|
32
33
|
} = require('../api/dimensions.api');
|
|
33
34
|
const { createDimensionValue } = require('../api/dimension-values.api');
|
|
35
|
+
const { setupDimensionValueCommands } = require('./dimension-value');
|
|
34
36
|
|
|
35
37
|
const DIMENSION_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9-_]*$/;
|
|
38
|
+
|
|
39
|
+
/** Parent help for `aifabrix dimension -h` (leaf commands have their own Examples blocks). */
|
|
40
|
+
const DIMENSION_HELP_AFTER = `
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
$ aifabrix dimension create --key customerRegion --display-name "Customer Region" --data-type string
|
|
44
|
+
$ aifabrix dimension get customerRegion
|
|
45
|
+
$ aifabrix dimension list --search region
|
|
46
|
+
$ aifabrix dimension value create customerRegion --value emea --display-name "EMEA"
|
|
47
|
+
$ aifabrix dimension value list customerRegion
|
|
48
|
+
|
|
49
|
+
Per-command help: aifabrix dimension <command> --help
|
|
50
|
+
`;
|
|
51
|
+
|
|
36
52
|
const DATA_TYPES = new Set(['string', 'number', 'boolean']);
|
|
37
53
|
|
|
38
54
|
const DIMENSION_CREATE_HELP_AFTER = `
|
|
@@ -139,6 +155,11 @@ function buildCreatePayload(options) {
|
|
|
139
155
|
if (options.displayName) payload.displayName = options.displayName;
|
|
140
156
|
if (options.description !== undefined) payload.description = options.description;
|
|
141
157
|
if (options.dataType) payload.dataType = options.dataType;
|
|
158
|
+
if (options.valueType) {
|
|
159
|
+
payload.valueType = normalizeValueType(options.valueType);
|
|
160
|
+
} else if (payload.valueType === undefined) {
|
|
161
|
+
payload.valueType = 'static';
|
|
162
|
+
}
|
|
142
163
|
if (options.required !== undefined && options.required !== null) {
|
|
143
164
|
payload.isRequired = Boolean(options.required);
|
|
144
165
|
}
|
|
@@ -197,6 +218,7 @@ function setupDimensionCreateCommand(dim) {
|
|
|
197
218
|
.option('--display-name <name>', 'Display name')
|
|
198
219
|
.option('--description <text>', 'Description')
|
|
199
220
|
.option('--data-type <type>', 'string | number | boolean')
|
|
221
|
+
.option('--value-type <mode>', 'static | dynamic | both (catalog assignment mode)')
|
|
200
222
|
.option('--required', 'Mark dimension required (isRequired=true)')
|
|
201
223
|
.option('--no-required', 'Mark dimension not required (isRequired=false)')
|
|
202
224
|
.action(async(options) => {
|
|
@@ -206,11 +228,12 @@ function setupDimensionCreateCommand(dim) {
|
|
|
206
228
|
const out = await createDimensionIdempotent(controllerUrl, authConfig, payload);
|
|
207
229
|
const dimRow = unwrapControllerData(out.response);
|
|
208
230
|
await maybeCreateValuesFromFile(controllerUrl, authConfig, payload);
|
|
231
|
+
const vt = dimRow.valueType || payload.valueType || 'static';
|
|
209
232
|
if (out.created) {
|
|
210
|
-
logger.log(formatSuccessLine(`Dimension created: ${dimRow.key}`));
|
|
233
|
+
logger.log(formatSuccessLine(`Dimension created: ${dimRow.key} (valueType: ${vt})`));
|
|
211
234
|
return;
|
|
212
235
|
}
|
|
213
|
-
logger.log(formatSuccessLine(`Dimension exists: ${dimRow.key}`));
|
|
236
|
+
logger.log(formatSuccessLine(`Dimension exists: ${dimRow.key} (valueType: ${vt})`));
|
|
214
237
|
logger.log(infoLine('No changes were needed.'));
|
|
215
238
|
} catch (e) {
|
|
216
239
|
logger.error(formatBlockingError(e.message));
|
|
@@ -236,6 +259,7 @@ function setupDimensionGetCommand(dim) {
|
|
|
236
259
|
logger.log(headerKeyValue('Key:', row.key || '—'));
|
|
237
260
|
logger.log(headerKeyValue('Display:', row.displayName || '—'));
|
|
238
261
|
logger.log(headerKeyValue('Type:', row.dataType || '—'));
|
|
262
|
+
logger.log(headerKeyValue('Value type:', row.valueType || '—'));
|
|
239
263
|
logger.log(headerKeyValue('Required:', String(row.isRequired)));
|
|
240
264
|
if (row.description) {
|
|
241
265
|
logger.log(headerKeyValue('Description:', row.description));
|
|
@@ -268,22 +292,25 @@ function setupDimensionListCommand(dim) {
|
|
|
268
292
|
const KEY_WIDTH = 26;
|
|
269
293
|
const DISPLAY_WIDTH = 34;
|
|
270
294
|
const TYPE_WIDTH = 10;
|
|
295
|
+
const VTYPE_WIDTH = 8;
|
|
271
296
|
const REQ_WIDTH = 10;
|
|
272
|
-
const SEP_LEN = KEY_WIDTH + DISPLAY_WIDTH + TYPE_WIDTH + REQ_WIDTH;
|
|
297
|
+
const SEP_LEN = KEY_WIDTH + DISPLAY_WIDTH + TYPE_WIDTH + VTYPE_WIDTH + REQ_WIDTH;
|
|
273
298
|
|
|
274
299
|
const keyCol = 'Key'.padEnd(KEY_WIDTH);
|
|
275
300
|
const displayCol = 'Display'.padEnd(DISPLAY_WIDTH);
|
|
276
301
|
const typeCol = 'Type'.padEnd(TYPE_WIDTH);
|
|
302
|
+
const vtypeCol = 'VType'.padEnd(VTYPE_WIDTH);
|
|
277
303
|
const reqCol = 'Required'.padEnd(REQ_WIDTH);
|
|
278
|
-
logger.log(chalk.gray(`${keyCol}${displayCol}${typeCol}${reqCol}`));
|
|
304
|
+
logger.log(chalk.gray(`${keyCol}${displayCol}${typeCol}${vtypeCol}${reqCol}`));
|
|
279
305
|
logger.log(chalk.gray('-'.repeat(SEP_LEN)));
|
|
280
306
|
|
|
281
307
|
items.forEach((d) => {
|
|
282
308
|
const key = (d?.key ?? '—').toString().padEnd(KEY_WIDTH);
|
|
283
309
|
const displayName = (d?.displayName ?? '—').toString().padEnd(DISPLAY_WIDTH);
|
|
284
310
|
const dataType = (d?.dataType ?? '—').toString().padEnd(TYPE_WIDTH);
|
|
311
|
+
const valueType = (d?.valueType ?? '—').toString().padEnd(VTYPE_WIDTH);
|
|
285
312
|
const required = (d?.isRequired ?? '—').toString().padEnd(REQ_WIDTH);
|
|
286
|
-
logger.log(`${key}${displayName}${dataType}${required}`);
|
|
313
|
+
logger.log(`${key}${displayName}${dataType}${valueType}${required}`);
|
|
287
314
|
});
|
|
288
315
|
logger.log('');
|
|
289
316
|
}
|
|
@@ -318,10 +345,14 @@ function setupDimensionListCommand(dim) {
|
|
|
318
345
|
* @param {import('commander').Command} program
|
|
319
346
|
*/
|
|
320
347
|
function setupDimensionCommands(program) {
|
|
321
|
-
const dim = program
|
|
348
|
+
const dim = program
|
|
349
|
+
.command('dimension')
|
|
350
|
+
.description('Manage the Controller Dimension Catalog')
|
|
351
|
+
.addHelpText('after', DIMENSION_HELP_AFTER);
|
|
322
352
|
setupDimensionCreateCommand(dim);
|
|
323
353
|
setupDimensionGetCommand(dim);
|
|
324
354
|
setupDimensionListCommand(dim);
|
|
355
|
+
setupDimensionValueCommands(dim);
|
|
325
356
|
}
|
|
326
357
|
|
|
327
358
|
module.exports = {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Identity apply and sync commands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const logger = require('../utils/logger');
|
|
10
|
+
const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
|
|
11
|
+
const {
|
|
12
|
+
resolveControllerAndAuth,
|
|
13
|
+
resolveEnvKey,
|
|
14
|
+
requireNonEmpty,
|
|
15
|
+
unwrapControllerData,
|
|
16
|
+
throwIfApiFailed,
|
|
17
|
+
assertSyncStatsOk
|
|
18
|
+
} = require('./identity-shared');
|
|
19
|
+
const { clearAuthCache } = require('../api/auth-cache.api');
|
|
20
|
+
const { fullSyncToDataplane } = require('../api/dataplane-sync.api');
|
|
21
|
+
const { runIdentityApply } = require('../identity/identity-apply-service');
|
|
22
|
+
|
|
23
|
+
function printJson(data) {
|
|
24
|
+
logger.log(JSON.stringify(data, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {import('commander').Command} identityCmd
|
|
29
|
+
*/
|
|
30
|
+
function setupIdentityApplyCommand(identityCmd) {
|
|
31
|
+
identityCmd
|
|
32
|
+
.command('apply')
|
|
33
|
+
.description('Apply users/groups/memberships from a CSV file (users.csv shape)')
|
|
34
|
+
.requiredOption('--file <path>', 'CSV file path')
|
|
35
|
+
.option('-e, --env <key>', 'Environment for --sync')
|
|
36
|
+
.option('--filter-prefix <prefix>', 'Only rows whose Id starts with this prefix')
|
|
37
|
+
.option('--dry-run', 'Parse and plan only; no writes')
|
|
38
|
+
.option('--sync', 'Run dataplane full sync after apply')
|
|
39
|
+
.option('--purge-cache', 'Clear controller cache before sync')
|
|
40
|
+
.option('--allow-empty-sync', 'Do not fail when sync reports usersProcessed=0')
|
|
41
|
+
.option('--json', 'JSON summary')
|
|
42
|
+
.action(async(options) => {
|
|
43
|
+
try {
|
|
44
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
45
|
+
const envKey = options.sync ? await resolveEnvKey(options) : undefined;
|
|
46
|
+
const summary = await runIdentityApply(controllerUrl, authConfig, {
|
|
47
|
+
filePath: requireNonEmpty(options.file, 'file'),
|
|
48
|
+
filterPrefix: options.filterPrefix,
|
|
49
|
+
dryRun: Boolean(options.dryRun),
|
|
50
|
+
purgeCache: Boolean(options.purgeCache),
|
|
51
|
+
sync: Boolean(options.sync),
|
|
52
|
+
envKey,
|
|
53
|
+
allowEmptySync: Boolean(options.allowEmptySync)
|
|
54
|
+
});
|
|
55
|
+
if (options.json) {
|
|
56
|
+
printJson(summary);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
logger.log(formatSuccessLine('Identity apply finished'));
|
|
60
|
+
logger.log(
|
|
61
|
+
` groups: ${summary.groupsProcessed} processed (${summary.groupsCreated} created)`
|
|
62
|
+
);
|
|
63
|
+
logger.log(` users: ${summary.usersProcessed} processed (${summary.usersCreated} created)`);
|
|
64
|
+
logger.log(
|
|
65
|
+
` memberships: ${summary.membershipsProcessed} processed (${summary.membershipsCreated} created, ${summary.membershipsSkipped} skipped)`
|
|
66
|
+
);
|
|
67
|
+
if (summary.syncStats) {
|
|
68
|
+
logger.log(
|
|
69
|
+
` sync: usersProcessed=${summary.syncStats.usersProcessed ?? 0} groupsProcessed=${summary.syncStats.groupsProcessed ?? 0}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
logger.error(formatBlockingError(e.message));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {import('commander').Command} identityCmd
|
|
81
|
+
*/
|
|
82
|
+
function setupIdentitySyncCommand(identityCmd) {
|
|
83
|
+
identityCmd
|
|
84
|
+
.command('sync')
|
|
85
|
+
.description('Full identity sync from controller to dataplane')
|
|
86
|
+
.option('-e, --env <key>', 'Environment key')
|
|
87
|
+
.option('--purge-cache', 'Clear controller cache before sync')
|
|
88
|
+
.option('--allow-empty-sync', 'Do not fail when usersProcessed=0')
|
|
89
|
+
.option('--json', 'JSON stats')
|
|
90
|
+
.action(async(options) => runIdentitySyncAction(options));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Object} options
|
|
95
|
+
* @returns {Promise<void>}
|
|
96
|
+
*/
|
|
97
|
+
async function maybePurgeCache(controllerUrl, authConfig, purgeCache) {
|
|
98
|
+
if (!purgeCache) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const cacheRes = await clearAuthCache(controllerUrl, authConfig);
|
|
102
|
+
throwIfApiFailed(cacheRes);
|
|
103
|
+
logger.log(formatSuccessLine('Controller cache cleared'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function runIdentitySyncAction(options) {
|
|
107
|
+
try {
|
|
108
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
109
|
+
const envKey = await resolveEnvKey(options);
|
|
110
|
+
await maybePurgeCache(controllerUrl, authConfig, options.purgeCache);
|
|
111
|
+
const res = await fullSyncToDataplane(controllerUrl, authConfig, envKey);
|
|
112
|
+
throwIfApiFailed(res);
|
|
113
|
+
const stats = unwrapControllerData(res);
|
|
114
|
+
assertSyncStatsOk(stats, { allowEmptySync: options.allowEmptySync });
|
|
115
|
+
if (options.json) {
|
|
116
|
+
printJson(stats);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
logger.log(formatSuccessLine(`Identity synced to dataplane (${envKey})`));
|
|
120
|
+
logger.log(` usersProcessed=${stats?.usersProcessed ?? 0}`);
|
|
121
|
+
logger.log(` groupsProcessed=${stats?.groupsProcessed ?? 0}`);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
logger.error(formatBlockingError(e.message));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
setupIdentityApplyCommand,
|
|
130
|
+
setupIdentitySyncCommand
|
|
131
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Identity group subcommands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const logger = require('../utils/logger');
|
|
10
|
+
const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
|
|
11
|
+
const {
|
|
12
|
+
resolveControllerAndAuth,
|
|
13
|
+
requireNonEmpty,
|
|
14
|
+
unwrapControllerData,
|
|
15
|
+
throwIfApiFailed
|
|
16
|
+
} = require('./identity-shared');
|
|
17
|
+
const { listGroups, getGroup, createGroup, listGroupMembers } = require('../api/groups.api');
|
|
18
|
+
|
|
19
|
+
function printJson(data) {
|
|
20
|
+
logger.log(JSON.stringify(data, null, 2));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function registerGroupCreate(groupCmd) {
|
|
24
|
+
groupCmd
|
|
25
|
+
.command('create')
|
|
26
|
+
.description('Create a group')
|
|
27
|
+
.requiredOption('--name <name>', 'Group name (unique key)')
|
|
28
|
+
.requiredOption('--display-name <name>', 'Display name')
|
|
29
|
+
.option('--description <text>', 'Description')
|
|
30
|
+
.action(async(options) => {
|
|
31
|
+
try {
|
|
32
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
33
|
+
const res = await createGroup(controllerUrl, authConfig, {
|
|
34
|
+
name: requireNonEmpty(options.name, 'name'),
|
|
35
|
+
displayName: requireNonEmpty(options.displayName, 'display-name'),
|
|
36
|
+
description: options.description
|
|
37
|
+
});
|
|
38
|
+
throwIfApiFailed(res);
|
|
39
|
+
const row = unwrapControllerData(res);
|
|
40
|
+
logger.log(formatSuccessLine(`Group created: ${row.id} (${row.name})`));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
logger.error(formatBlockingError(e.message));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registerGroupList(groupCmd) {
|
|
49
|
+
groupCmd
|
|
50
|
+
.command('list')
|
|
51
|
+
.description('List groups')
|
|
52
|
+
.option('--page <n>', 'Page', (v) => parseInt(v, 10))
|
|
53
|
+
.option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
|
|
54
|
+
.option('--search <text>', 'Search groups')
|
|
55
|
+
.option('--json', 'JSON output')
|
|
56
|
+
.action(async(options) => {
|
|
57
|
+
try {
|
|
58
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
59
|
+
const res = await listGroups(controllerUrl, authConfig, {
|
|
60
|
+
page: options.page,
|
|
61
|
+
pageSize: options.pageSize,
|
|
62
|
+
search: options.search
|
|
63
|
+
});
|
|
64
|
+
throwIfApiFailed(res);
|
|
65
|
+
const data = unwrapControllerData(res);
|
|
66
|
+
if (options.json) {
|
|
67
|
+
printJson(data);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
71
|
+
items.forEach((g) => logger.log(` ${g.id} ${g.name}`));
|
|
72
|
+
logger.log('');
|
|
73
|
+
} catch (e) {
|
|
74
|
+
logger.error(formatBlockingError(e.message));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function registerGroupGet(groupCmd) {
|
|
81
|
+
groupCmd
|
|
82
|
+
.command('get <idOrName>')
|
|
83
|
+
.description('Get group by id or name')
|
|
84
|
+
.option('--json', 'JSON output')
|
|
85
|
+
.action(async(idOrName, options) => {
|
|
86
|
+
try {
|
|
87
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
88
|
+
const res = await getGroup(controllerUrl, authConfig, requireNonEmpty(idOrName, 'idOrName'));
|
|
89
|
+
throwIfApiFailed(res);
|
|
90
|
+
const row = unwrapControllerData(res);
|
|
91
|
+
if (options.json) {
|
|
92
|
+
printJson(row);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
logger.log(headerKeyValue('Id:', row.id));
|
|
96
|
+
logger.log(headerKeyValue('Name:', row.name));
|
|
97
|
+
logger.log('');
|
|
98
|
+
} catch (e) {
|
|
99
|
+
logger.error(formatBlockingError(e.message));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function registerGroupMembers(groupCmd) {
|
|
106
|
+
groupCmd
|
|
107
|
+
.command('members <idOrName>')
|
|
108
|
+
.description('List members of a group')
|
|
109
|
+
.option('--json', 'JSON output')
|
|
110
|
+
.action(async(idOrName, options) => {
|
|
111
|
+
try {
|
|
112
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
113
|
+
const res = await listGroupMembers(
|
|
114
|
+
controllerUrl,
|
|
115
|
+
authConfig,
|
|
116
|
+
requireNonEmpty(idOrName, 'idOrName')
|
|
117
|
+
);
|
|
118
|
+
throwIfApiFailed(res);
|
|
119
|
+
const data = unwrapControllerData(res);
|
|
120
|
+
if (options.json) {
|
|
121
|
+
printJson(data);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
125
|
+
items.forEach((m) => logger.log(` ${m.userId || m.id} ${m.email || ''}`));
|
|
126
|
+
logger.log('');
|
|
127
|
+
} catch (e) {
|
|
128
|
+
logger.error(formatBlockingError(e.message));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {import('commander').Command} groupCmd
|
|
136
|
+
*/
|
|
137
|
+
function setupIdentityGroupCommands(groupCmd) {
|
|
138
|
+
registerGroupCreate(groupCmd);
|
|
139
|
+
registerGroupList(groupCmd);
|
|
140
|
+
registerGroupGet(groupCmd);
|
|
141
|
+
registerGroupMembers(groupCmd);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
setupIdentityGroupCommands
|
|
146
|
+
};
|