@aifabrix/builder 2.44.4 ā 2.44.6
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/cli-layout.mdc +1 -1
- package/.cursor/rules/project-rules.mdc +1 -1
- package/.npmrc.token +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +68 -17
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/types/wizard.types.js +2 -1
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +1 -1
- package/lib/app/restart-display.js +95 -0
- package/lib/app/rotate-secret.js +1 -1
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +44 -12
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +99 -73
- package/lib/build/index.js +75 -45
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +445 -0
- package/lib/cli/setup-app.help.js +1 -1
- package/lib/cli/setup-app.js +20 -2
- package/lib/cli/setup-app.test-commands.js +9 -5
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +138 -61
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility.js +97 -33
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +225 -19
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -354
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +455 -0
- package/lib/commands/setup-prompts.js +388 -0
- package/lib/commands/setup.js +149 -0
- package/lib/commands/teardown.js +228 -0
- package/lib/commands/test-e2e-external.js +4 -3
- package/lib/commands/up-common.js +97 -12
- package/lib/commands/up-dataplane.js +33 -11
- package/lib/commands/up-miso.js +7 -11
- package/lib/commands/upload.js +109 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +58 -15
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +72 -14
- package/lib/commands/wizard-headless.js +7 -3
- package/lib/commands/wizard-helpers.js +13 -1
- package/lib/commands/wizard.js +210 -61
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +432 -0
- package/lib/core/secrets-env-write.js +27 -1
- package/lib/core/secrets-load.js +248 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +4 -1
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/generator/wizard-prompts.js +7 -1
- package/lib/generator/wizard.js +34 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -11
- package/lib/schema/wizard-config.schema.json +2 -2
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/external-readme.js +117 -4
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +19 -4
- package/lib/utils/health-check.js +135 -105
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +108 -25
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +42 -3
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +24 -10
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/system-builder-root.js +34 -0
- package/lib/utils/url-declarative-resolve-build.js +6 -1
- package/lib/utils/url-declarative-runtime-base-path.js +32 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry.js +73 -20
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +7 -0
- package/templates/applications/miso-controller/env.template +7 -7
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +89 -102
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- package/lib/commands/service-user.js +0 -429
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dimension value commands (Controller).
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* - aifabrix dimension-value create
|
|
6
|
+
* - aifabrix dimension-value list
|
|
7
|
+
* - aifabrix dimension-value delete
|
|
8
|
+
*
|
|
9
|
+
* @fileoverview Dimension value CLI commands
|
|
10
|
+
* @author AI Fabrix Team
|
|
11
|
+
* @version 2.0.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
const logger = require('../utils/logger');
|
|
18
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
19
|
+
const { normalizeControllerUrl } = require('../core/config');
|
|
20
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
21
|
+
const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
|
|
22
|
+
const {
|
|
23
|
+
listDimensionValues,
|
|
24
|
+
createDimensionValue,
|
|
25
|
+
deleteDimensionValue
|
|
26
|
+
} = require('../api/dimension-values.api');
|
|
27
|
+
|
|
28
|
+
const DIMENSION_VALUE_CREATE_HELP_AFTER = `
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
$ aifabrix dimension-value create customerRegion --value emea --display-name "EMEA"
|
|
32
|
+
$ aifabrix dimension-value create customerRegion --value na --display-name "North America"
|
|
33
|
+
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const DIMENSION_VALUE_LIST_HELP_AFTER = `
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
$ aifabrix dimension-value list customerRegion
|
|
40
|
+
$ aifabrix dimension-value list customerRegion --page 1 --page-size 50
|
|
41
|
+
$ aifabrix dimension-value list customerRegion --search emea
|
|
42
|
+
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const DIMENSION_VALUE_DELETE_HELP_AFTER = `
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
$ aifabrix dimension-value delete clx1234567890abcdef
|
|
49
|
+
|
|
50
|
+
Tip: Find ids via "aifabrix dimension get <key>" (it prints values) or "aifabrix dimension-value list <key>".
|
|
51
|
+
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} raw
|
|
56
|
+
* @param {string} label
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
function requireNonEmpty(raw, label) {
|
|
60
|
+
const s = String(raw || '').trim();
|
|
61
|
+
if (!s) throw new Error(`${label} is required.`);
|
|
62
|
+
return s;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function resolveControllerAndAuth() {
|
|
66
|
+
const controllerUrl = await resolveControllerUrl();
|
|
67
|
+
if (!controllerUrl) {
|
|
68
|
+
throw new Error('Controller URL is required. Run "aifabrix login" first.');
|
|
69
|
+
}
|
|
70
|
+
const normalized = normalizeControllerUrl(controllerUrl);
|
|
71
|
+
const deviceToken = await getOrRefreshDeviceToken(normalized);
|
|
72
|
+
if (!deviceToken || !deviceToken.token) {
|
|
73
|
+
throw new Error(`Not authenticated for controller: ${controllerUrl}. Run "aifabrix login" and try again.`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
controllerUrl: deviceToken.controller || normalized,
|
|
77
|
+
authConfig: { type: 'bearer', token: deviceToken.token }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function unwrapControllerData(response) {
|
|
82
|
+
return response?.data?.data ?? response?.data ?? response;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function setupDimensionValueCreateCommand(cmd) {
|
|
86
|
+
cmd
|
|
87
|
+
.command('create <dimensionIdOrKey>')
|
|
88
|
+
.description('Create a value under a dimension')
|
|
89
|
+
.addHelpText('after', DIMENSION_VALUE_CREATE_HELP_AFTER)
|
|
90
|
+
.requiredOption('--value <value>', 'Value (unique within the dimension)')
|
|
91
|
+
.option('--display-name <name>', 'Display name')
|
|
92
|
+
.option('--description <text>', 'Description')
|
|
93
|
+
.action(async(dimensionIdOrKey, options) => {
|
|
94
|
+
try {
|
|
95
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
96
|
+
const dimKey = requireNonEmpty(dimensionIdOrKey, 'dimensionIdOrKey');
|
|
97
|
+
const value = requireNonEmpty(options.value, 'value');
|
|
98
|
+
const res = await createDimensionValue(controllerUrl, authConfig, dimKey, {
|
|
99
|
+
value,
|
|
100
|
+
displayName: options.displayName,
|
|
101
|
+
description: options.description
|
|
102
|
+
});
|
|
103
|
+
const row = unwrapControllerData(res);
|
|
104
|
+
logger.log(formatSuccessLine(`Dimension value created: ${dimKey}.${row.value}`));
|
|
105
|
+
} catch (e) {
|
|
106
|
+
logger.error(formatBlockingError(e.message));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function setupDimensionValueListCommand(cmd) {
|
|
113
|
+
cmd
|
|
114
|
+
.command('list <dimensionIdOrKey>')
|
|
115
|
+
.description('List values for a dimension')
|
|
116
|
+
.addHelpText('after', DIMENSION_VALUE_LIST_HELP_AFTER)
|
|
117
|
+
.option('--page <n>', 'Page', (v) => parseInt(v, 10))
|
|
118
|
+
.option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
|
|
119
|
+
.option('--search <text>', 'Search by value/displayName/description')
|
|
120
|
+
.action(async(dimensionIdOrKey, options) => {
|
|
121
|
+
try {
|
|
122
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
123
|
+
const dimKey = requireNonEmpty(dimensionIdOrKey, 'dimensionIdOrKey');
|
|
124
|
+
const res = await listDimensionValues(controllerUrl, authConfig, dimKey, {
|
|
125
|
+
page: options.page,
|
|
126
|
+
pageSize: options.pageSize,
|
|
127
|
+
search: options.search
|
|
128
|
+
});
|
|
129
|
+
const payload = unwrapControllerData(res);
|
|
130
|
+
const items = payload?.data ?? payload ?? [];
|
|
131
|
+
logger.log(chalk.bold('\nš· Dimension values:\n'));
|
|
132
|
+
logger.log(headerKeyValue('Dimension:', dimKey));
|
|
133
|
+
logger.log('');
|
|
134
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
135
|
+
logger.log(chalk.gray(' No values found.\n'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
items.forEach((v) => {
|
|
139
|
+
const value = v?.value ? String(v.value) : 'ā';
|
|
140
|
+
const display = v?.displayName ? String(v.displayName) : '';
|
|
141
|
+
logger.log(` ${chalk.white(value)}${display ? ` ${chalk.gray(`(${display})`)}` : ''}`);
|
|
142
|
+
});
|
|
143
|
+
logger.log('');
|
|
144
|
+
} catch (e) {
|
|
145
|
+
logger.error(formatBlockingError(e.message));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function setupDimensionValueDeleteCommand(cmd) {
|
|
152
|
+
cmd
|
|
153
|
+
.command('delete <dimensionValueId>')
|
|
154
|
+
.description('Delete a dimension value by id')
|
|
155
|
+
.addHelpText('after', DIMENSION_VALUE_DELETE_HELP_AFTER)
|
|
156
|
+
.action(async(dimensionValueId) => {
|
|
157
|
+
try {
|
|
158
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
159
|
+
const id = requireNonEmpty(dimensionValueId, 'dimensionValueId');
|
|
160
|
+
await deleteDimensionValue(controllerUrl, authConfig, id);
|
|
161
|
+
logger.log(formatSuccessLine(`Dimension value deleted: ${id}`));
|
|
162
|
+
} catch (e) {
|
|
163
|
+
logger.error(formatBlockingError(e.message));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function setupDimensionValueCommands(program) {
|
|
170
|
+
const cmd = program.command('dimension-value').description('Manage dimension values (static dimensions)');
|
|
171
|
+
setupDimensionValueCreateCommand(cmd);
|
|
172
|
+
setupDimensionValueListCommand(cmd);
|
|
173
|
+
setupDimensionValueDeleteCommand(cmd);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
setupDimensionValueCommands
|
|
178
|
+
};
|
|
179
|
+
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dimension catalog commands (Controller).
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* - aifabrix dimension create
|
|
6
|
+
* - aifabrix dimension get
|
|
7
|
+
* - aifabrix dimension list
|
|
8
|
+
*
|
|
9
|
+
* @fileoverview Dimension CLI commands
|
|
10
|
+
* @author AI Fabrix Team
|
|
11
|
+
* @version 2.0.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
const logger = require('../utils/logger');
|
|
18
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
19
|
+
const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
|
|
20
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
21
|
+
const { readDimensionCreateFile } = require('../resolvers/dimension-file');
|
|
22
|
+
const {
|
|
23
|
+
formatBlockingError,
|
|
24
|
+
formatSuccessLine,
|
|
25
|
+
infoLine,
|
|
26
|
+
headerKeyValue
|
|
27
|
+
} = require('../utils/cli-layout-chalk');
|
|
28
|
+
const {
|
|
29
|
+
listDimensions,
|
|
30
|
+
getDimension,
|
|
31
|
+
createDimensionIdempotent
|
|
32
|
+
} = require('../api/dimensions.api');
|
|
33
|
+
const { createDimensionValue } = require('../api/dimension-values.api');
|
|
34
|
+
|
|
35
|
+
const DIMENSION_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9-_]*$/;
|
|
36
|
+
const DATA_TYPES = new Set(['string', 'number', 'boolean']);
|
|
37
|
+
|
|
38
|
+
const DIMENSION_CREATE_HELP_AFTER = `
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
$ aifabrix dimension create --key customerRegion --display-name "Customer Region" --data-type string
|
|
42
|
+
$ aifabrix dimension create --key dataClassification --display-name "Data Classification" --data-type string --required
|
|
43
|
+
$ aifabrix dimension create --file ./customer-region.json
|
|
44
|
+
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const DIMENSION_GET_HELP_AFTER = `
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
$ aifabrix dimension get customerRegion
|
|
51
|
+
$ aifabrix dimension get clx1234567890abcdef
|
|
52
|
+
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const DIMENSION_LIST_HELP_AFTER = `
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
$ aifabrix dimension list
|
|
59
|
+
$ aifabrix dimension list --page 1 --page-size 50
|
|
60
|
+
$ aifabrix dimension list --search region
|
|
61
|
+
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {string} raw
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function requireDimensionKey(raw) {
|
|
69
|
+
const key = String(raw || '').trim();
|
|
70
|
+
if (!key) {
|
|
71
|
+
throw new Error('Dimension key is required (--key <key> or --file <path>).');
|
|
72
|
+
}
|
|
73
|
+
if (!DIMENSION_KEY_PATTERN.test(key)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'Dimension key must start with a letter and contain only letters, numbers, hyphens, and underscores.'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return key;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} raw
|
|
83
|
+
* @returns {'string'|'number'|'boolean'}
|
|
84
|
+
*/
|
|
85
|
+
function requireDataType(raw) {
|
|
86
|
+
const dt = String(raw || '').trim();
|
|
87
|
+
if (!dt) {
|
|
88
|
+
throw new Error('dataType is required (--data-type string|number|boolean or in --file).');
|
|
89
|
+
}
|
|
90
|
+
if (!DATA_TYPES.has(dt)) {
|
|
91
|
+
throw new Error('--data-type must be one of: string, number, boolean');
|
|
92
|
+
}
|
|
93
|
+
return /** @type {any} */ (dt);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Object} options
|
|
98
|
+
* @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
|
|
99
|
+
*/
|
|
100
|
+
async function resolveControllerAndAuth(_options) {
|
|
101
|
+
const controllerUrl = await resolveControllerUrl();
|
|
102
|
+
if (!controllerUrl) {
|
|
103
|
+
throw new Error('Controller URL is required. Run "aifabrix login" first.');
|
|
104
|
+
}
|
|
105
|
+
const normalized = normalizeControllerUrl(controllerUrl);
|
|
106
|
+
const deviceToken = await getOrRefreshDeviceToken(normalized);
|
|
107
|
+
if (!deviceToken || !deviceToken.token) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Not authenticated for controller: ${controllerUrl}. ` +
|
|
110
|
+
'Run "aifabrix login" and try again.'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
controllerUrl: deviceToken.controller || normalized,
|
|
115
|
+
authConfig: { type: 'bearer', token: deviceToken.token }
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function resolveHeaderContext() {
|
|
120
|
+
const env = await resolveEnvironment();
|
|
121
|
+
const controllerUrl = await resolveControllerUrl();
|
|
122
|
+
const normalized = normalizeControllerUrl(controllerUrl);
|
|
123
|
+
return { environment: env || 'dev', controllerUrl: normalized };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {Object} options
|
|
128
|
+
* @returns {Object}
|
|
129
|
+
*/
|
|
130
|
+
function buildCreatePayload(options) {
|
|
131
|
+
let base = {};
|
|
132
|
+
if (options.file) {
|
|
133
|
+
base = readDimensionCreateFile(options.file);
|
|
134
|
+
}
|
|
135
|
+
const payload = {
|
|
136
|
+
...base
|
|
137
|
+
};
|
|
138
|
+
if (options.key) payload.key = options.key;
|
|
139
|
+
if (options.displayName) payload.displayName = options.displayName;
|
|
140
|
+
if (options.description !== undefined) payload.description = options.description;
|
|
141
|
+
if (options.dataType) payload.dataType = options.dataType;
|
|
142
|
+
if (options.required !== undefined && options.required !== null) {
|
|
143
|
+
payload.isRequired = Boolean(options.required);
|
|
144
|
+
}
|
|
145
|
+
payload.key = requireDimensionKey(payload.key);
|
|
146
|
+
if (!payload.displayName) {
|
|
147
|
+
throw new Error('displayName is required (--display-name <name> or in --file).');
|
|
148
|
+
}
|
|
149
|
+
payload.dataType = requireDataType(payload.dataType);
|
|
150
|
+
return payload;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function maybeCreateValuesFromFile(controllerUrl, authConfig, payload) {
|
|
154
|
+
const values = payload?.values;
|
|
155
|
+
if (!Array.isArray(values) || values.length === 0) return;
|
|
156
|
+
const dimKey = String(payload.key || '').trim();
|
|
157
|
+
for (const v of values) {
|
|
158
|
+
const value = String(v?.value || '').trim();
|
|
159
|
+
if (!value) {
|
|
160
|
+
throw new Error('values[].value must be a non-empty string');
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
await createDimensionValue(controllerUrl, authConfig, dimKey, {
|
|
164
|
+
value,
|
|
165
|
+
displayName: v?.displayName,
|
|
166
|
+
description: v?.description
|
|
167
|
+
});
|
|
168
|
+
} catch (e) {
|
|
169
|
+
// Idempotent behavior: if the value already exists (409), treat as success.
|
|
170
|
+
const msg = e?.message || String(e);
|
|
171
|
+
if (/409|Conflict/i.test(msg)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {Object} response
|
|
181
|
+
* @returns {any}
|
|
182
|
+
*/
|
|
183
|
+
function unwrapControllerData(response) {
|
|
184
|
+
return response?.data?.data ?? response?.data ?? response;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {import('commander').Command} dim
|
|
189
|
+
*/
|
|
190
|
+
function setupDimensionCreateCommand(dim) {
|
|
191
|
+
dim
|
|
192
|
+
.command('create')
|
|
193
|
+
.description('Create dimension (idempotent; success if it already exists)')
|
|
194
|
+
.addHelpText('after', DIMENSION_CREATE_HELP_AFTER)
|
|
195
|
+
.option('--file <path>', 'Read dimension create payload from JSON file')
|
|
196
|
+
.option('--key <key>', 'Dimension key (letters, digits, hyphens, underscores)')
|
|
197
|
+
.option('--display-name <name>', 'Display name')
|
|
198
|
+
.option('--description <text>', 'Description')
|
|
199
|
+
.option('--data-type <type>', 'string | number | boolean')
|
|
200
|
+
.option('--required', 'Mark dimension required (isRequired=true)')
|
|
201
|
+
.option('--no-required', 'Mark dimension not required (isRequired=false)')
|
|
202
|
+
.action(async(options) => {
|
|
203
|
+
try {
|
|
204
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
205
|
+
const payload = buildCreatePayload(options);
|
|
206
|
+
const out = await createDimensionIdempotent(controllerUrl, authConfig, payload);
|
|
207
|
+
const dimRow = unwrapControllerData(out.response);
|
|
208
|
+
await maybeCreateValuesFromFile(controllerUrl, authConfig, payload);
|
|
209
|
+
if (out.created) {
|
|
210
|
+
logger.log(formatSuccessLine(`Dimension created: ${dimRow.key}`));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
logger.log(formatSuccessLine(`Dimension exists: ${dimRow.key}`));
|
|
214
|
+
logger.log(infoLine('No changes were needed.'));
|
|
215
|
+
} catch (e) {
|
|
216
|
+
logger.error(formatBlockingError(e.message));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function setupDimensionGetCommand(dim) {
|
|
223
|
+
dim
|
|
224
|
+
.command('get <dimensionIdOrKey>')
|
|
225
|
+
.description('Get dimension by id or key')
|
|
226
|
+
.addHelpText('after', DIMENSION_GET_HELP_AFTER)
|
|
227
|
+
.action(async(dimensionIdOrKey, options) => {
|
|
228
|
+
try {
|
|
229
|
+
const { environment, controllerUrl: headerControllerUrl } = await resolveHeaderContext();
|
|
230
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
231
|
+
const res = await getDimension(controllerUrl, authConfig, String(dimensionIdOrKey).trim(), {
|
|
232
|
+
includeValues: true
|
|
233
|
+
});
|
|
234
|
+
const row = unwrapControllerData(res);
|
|
235
|
+
logger.log(chalk.bold(`\nš Dimension in ${environment} environment (${headerControllerUrl}):\n`));
|
|
236
|
+
logger.log(headerKeyValue('Key:', row.key || 'ā'));
|
|
237
|
+
logger.log(headerKeyValue('Display:', row.displayName || 'ā'));
|
|
238
|
+
logger.log(headerKeyValue('Type:', row.dataType || 'ā'));
|
|
239
|
+
logger.log(headerKeyValue('Required:', String(row.isRequired)));
|
|
240
|
+
if (row.description) {
|
|
241
|
+
logger.log(headerKeyValue('Description:', row.description));
|
|
242
|
+
}
|
|
243
|
+
if (Array.isArray(row.dimensionValues) && row.dimensionValues.length > 0) {
|
|
244
|
+
logger.log('');
|
|
245
|
+
logger.log(chalk.white.bold('Values:'));
|
|
246
|
+
row.dimensionValues.forEach((v) => {
|
|
247
|
+
const value = v?.value ? String(v.value) : 'ā';
|
|
248
|
+
const display = v?.displayName ? String(v.displayName) : '';
|
|
249
|
+
logger.log(` ${chalk.white(value)}${display ? ` ${chalk.gray(`(${display})`)}` : ''}`);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
logger.log('');
|
|
253
|
+
} catch (e) {
|
|
254
|
+
logger.error(formatBlockingError(e.message));
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function setupDimensionListCommand(dim) {
|
|
261
|
+
function displayDimensionList(items, environment, controllerUrl) {
|
|
262
|
+
logger.log(chalk.bold(`\nš Dimensions in ${environment} environment (${controllerUrl}):\n`));
|
|
263
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
264
|
+
logger.log(chalk.gray(' No dimensions found.\n'));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const KEY_WIDTH = 26;
|
|
269
|
+
const DISPLAY_WIDTH = 34;
|
|
270
|
+
const TYPE_WIDTH = 10;
|
|
271
|
+
const REQ_WIDTH = 10;
|
|
272
|
+
const SEP_LEN = KEY_WIDTH + DISPLAY_WIDTH + TYPE_WIDTH + REQ_WIDTH;
|
|
273
|
+
|
|
274
|
+
const keyCol = 'Key'.padEnd(KEY_WIDTH);
|
|
275
|
+
const displayCol = 'Display'.padEnd(DISPLAY_WIDTH);
|
|
276
|
+
const typeCol = 'Type'.padEnd(TYPE_WIDTH);
|
|
277
|
+
const reqCol = 'Required'.padEnd(REQ_WIDTH);
|
|
278
|
+
logger.log(chalk.gray(`${keyCol}${displayCol}${typeCol}${reqCol}`));
|
|
279
|
+
logger.log(chalk.gray('-'.repeat(SEP_LEN)));
|
|
280
|
+
|
|
281
|
+
items.forEach((d) => {
|
|
282
|
+
const key = (d?.key ?? 'ā').toString().padEnd(KEY_WIDTH);
|
|
283
|
+
const displayName = (d?.displayName ?? 'ā').toString().padEnd(DISPLAY_WIDTH);
|
|
284
|
+
const dataType = (d?.dataType ?? 'ā').toString().padEnd(TYPE_WIDTH);
|
|
285
|
+
const required = (d?.isRequired ?? 'ā').toString().padEnd(REQ_WIDTH);
|
|
286
|
+
logger.log(`${key}${displayName}${dataType}${required}`);
|
|
287
|
+
});
|
|
288
|
+
logger.log('');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
dim
|
|
292
|
+
.command('list')
|
|
293
|
+
.description('List dimensions')
|
|
294
|
+
.addHelpText('after', DIMENSION_LIST_HELP_AFTER)
|
|
295
|
+
.option('--page <n>', 'Page', (v) => parseInt(v, 10))
|
|
296
|
+
.option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
|
|
297
|
+
.option('--search <text>', 'Search by key/displayName/description')
|
|
298
|
+
.action(async(options) => {
|
|
299
|
+
try {
|
|
300
|
+
const { environment, controllerUrl: headerControllerUrl } = await resolveHeaderContext();
|
|
301
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
302
|
+
const res = await listDimensions(controllerUrl, authConfig, {
|
|
303
|
+
page: options.page,
|
|
304
|
+
pageSize: options.pageSize,
|
|
305
|
+
search: options.search
|
|
306
|
+
});
|
|
307
|
+
const payload = unwrapControllerData(res);
|
|
308
|
+
const items = payload?.data ?? payload ?? [];
|
|
309
|
+
displayDimensionList(items, environment, headerControllerUrl);
|
|
310
|
+
} catch (e) {
|
|
311
|
+
logger.error(formatBlockingError(e.message));
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* @param {import('commander').Command} program
|
|
319
|
+
*/
|
|
320
|
+
function setupDimensionCommands(program) {
|
|
321
|
+
const dim = program.command('dimension').description('Manage the Controller Dimension Catalog');
|
|
322
|
+
setupDimensionCreateCommand(dim);
|
|
323
|
+
setupDimensionGetCommand(dim);
|
|
324
|
+
setupDimensionListCommand(dim);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = {
|
|
328
|
+
setupDimensionCommands
|
|
329
|
+
};
|
|
330
|
+
|