@aifabrix/builder 2.44.5 ā 2.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/cli-layout.mdc +8 -4
- package/.cursor/rules/project-rules.mdc +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 +104 -2
- 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/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/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +126 -0
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +58 -19
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +148 -74
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +83 -49
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +460 -0
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.js +31 -3
- package/lib/cli/setup-auth.js +98 -27
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +132 -118
- 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-resolve.js +132 -0
- package/lib/cli/setup-utility.js +143 -84
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- 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 +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +468 -0
- package/lib/commands/setup-prompts.js +421 -0
- package/lib/commands/setup.js +254 -0
- package/lib/commands/teardown.js +277 -0
- package/lib/commands/up-common.js +113 -19
- package/lib/commands/up-dataplane.js +44 -19
- package/lib/commands/up-miso.js +18 -18
- package/lib/commands/upload.js +111 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- 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 +428 -0
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +29 -1
- package/lib/core/secrets-load.js +252 -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 +9 -2
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/internal/node-fs.js +2 -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/application-schema.json +4 -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 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-config-resolver.js +24 -1
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- 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/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- 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/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/external-readme.js +71 -2
- 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 +28 -10
- package/lib/utils/health-check.js +139 -107
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +162 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +308 -76
- 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 +49 -4
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-canonical.js +10 -3
- 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 +26 -13
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/system-builder-root.js +42 -0
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -388
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +52 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +158 -76
- 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 +3 -1
- package/templates/applications/dataplane/application.yaml +5 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +9 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- package/.npmrc.token +0 -1
- 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,430 @@
|
|
|
1
|
+
const {
|
|
2
|
+
formatBlockingError,
|
|
3
|
+
formatSuccessLine,
|
|
4
|
+
formatSuccessParagraph,
|
|
5
|
+
sectionTitle
|
|
6
|
+
} = require('../utils/cli-layout-chalk');
|
|
7
|
+
/**
|
|
8
|
+
* Integration client commands ā OAuth/API clients on the Controller.
|
|
9
|
+
*
|
|
10
|
+
* @fileoverview integration-client command implementation
|
|
11
|
+
* @author AI Fabrix Team
|
|
12
|
+
* @version 2.0.0
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const chalk = require('chalk');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
18
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
19
|
+
const { normalizeControllerUrl } = require('../core/config');
|
|
20
|
+
const {
|
|
21
|
+
createIntegrationClient,
|
|
22
|
+
listIntegrationClients,
|
|
23
|
+
regenerateIntegrationClientSecret,
|
|
24
|
+
deleteIntegrationClient,
|
|
25
|
+
updateIntegrationClientGroups,
|
|
26
|
+
updateIntegrationClientRedirectUris
|
|
27
|
+
} = require('../api/integration-clients.api');
|
|
28
|
+
|
|
29
|
+
const ONE_TIME_WARNING =
|
|
30
|
+
'Save this secret now; it will not be shown again.';
|
|
31
|
+
|
|
32
|
+
/** Controller-valid key: lowercase letter/digit start, then alphanumeric + hyphens */
|
|
33
|
+
const INTEGRATION_CLIENT_KEY_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string|undefined} key - Raw key from CLI
|
|
37
|
+
* @returns {string} Trimmed key
|
|
38
|
+
*/
|
|
39
|
+
function requireValidIntegrationClientKey(key) {
|
|
40
|
+
const trimmed = key?.trim();
|
|
41
|
+
if (!trimmed) {
|
|
42
|
+
logger.error(formatBlockingError('Key is required. Use --key <key>.'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (!INTEGRATION_CLIENT_KEY_PATTERN.test(trimmed)) {
|
|
46
|
+
logger.error(
|
|
47
|
+
formatBlockingError(
|
|
48
|
+
'Key must start with a letter or digit and contain only lowercase letters, digits, and hyphens (e.g. my-ci-client).'
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
return trimmed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} controllerUrl - Controller base URL
|
|
58
|
+
* @returns {Promise<{token: string, controllerUrl: string}|null>}
|
|
59
|
+
*/
|
|
60
|
+
async function getIntegrationClientAuth(controllerUrl) {
|
|
61
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
62
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
63
|
+
if (deviceToken && deviceToken.token) {
|
|
64
|
+
return {
|
|
65
|
+
token: deviceToken.token,
|
|
66
|
+
controllerUrl: deviceToken.controller || normalizedUrl
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {Object} response - API response (success: true, data: body)
|
|
74
|
+
* @returns {{ clientId: string, clientSecret: string }}
|
|
75
|
+
*/
|
|
76
|
+
function extractCreateResponse(response) {
|
|
77
|
+
const payload = response?.data?.data ?? response?.data ?? response;
|
|
78
|
+
const ic = payload?.integrationClient;
|
|
79
|
+
const clientId =
|
|
80
|
+
ic?.keycloakClientId ??
|
|
81
|
+
ic?.key ??
|
|
82
|
+
payload?.clientId ??
|
|
83
|
+
'';
|
|
84
|
+
const clientSecret = payload?.clientSecret ?? '';
|
|
85
|
+
return { clientId, clientSecret };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const ID_WIDTH = 38;
|
|
89
|
+
const KEY_WIDTH = 22;
|
|
90
|
+
const DISPLAY_WIDTH = 28;
|
|
91
|
+
const CLIENT_ID_WIDTH = 26;
|
|
92
|
+
const STATUS_WIDTH = 12;
|
|
93
|
+
const TABLE_SEPARATOR_LENGTH =
|
|
94
|
+
ID_WIDTH + KEY_WIDTH + DISPLAY_WIDTH + CLIENT_ID_WIDTH + STATUS_WIDTH;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Object} response - API response with success: false
|
|
98
|
+
*/
|
|
99
|
+
function handleCreateError(response) {
|
|
100
|
+
const status = response.status;
|
|
101
|
+
const msg = response.formattedError || response.error || 'Request failed';
|
|
102
|
+
if (status === 400) {
|
|
103
|
+
logger.error(formatBlockingError(`Validation error: ${msg}`));
|
|
104
|
+
} else if (status === 401) {
|
|
105
|
+
logger.error(formatBlockingError('Unauthorized. Run "aifabrix login" and try again.'));
|
|
106
|
+
} else if (status === 403) {
|
|
107
|
+
logger.error(formatBlockingError('Missing permission: integration-client:create'));
|
|
108
|
+
logger.error(chalk.gray('Your account needs the integration-client:create permission on the controller.'));
|
|
109
|
+
} else {
|
|
110
|
+
logger.error(formatBlockingError(`Failed to create integration client: ${msg}`));
|
|
111
|
+
}
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Object} response - API response with success: false
|
|
117
|
+
* @param {'read'|'update'|'delete'} permissionScope - Permission hint
|
|
118
|
+
*/
|
|
119
|
+
function handleIntegrationClientApiError(response, permissionScope) {
|
|
120
|
+
const status = response.status;
|
|
121
|
+
const msg = response.formattedError || response.error || 'Request failed';
|
|
122
|
+
if (status === 400) {
|
|
123
|
+
logger.error(formatBlockingError(`Validation error: ${msg}`));
|
|
124
|
+
} else if (status === 401) {
|
|
125
|
+
logger.error(formatBlockingError('Unauthorized. Run "aifabrix login" and try again.'));
|
|
126
|
+
} else if (status === 403) {
|
|
127
|
+
logger.error(formatBlockingError(`Missing permission: integration-client:${permissionScope}`));
|
|
128
|
+
logger.error(
|
|
129
|
+
chalk.gray(
|
|
130
|
+
`Your account needs the integration-client:${permissionScope} permission on the controller.`
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
} else if (status === 404) {
|
|
134
|
+
logger.error(formatBlockingError('Integration client not found.'));
|
|
135
|
+
const detail = response.error || '';
|
|
136
|
+
if (detail) {
|
|
137
|
+
logger.error(chalk.gray(detail));
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
logger.error(formatBlockingError(`Request failed: ${msg}`));
|
|
141
|
+
}
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @async
|
|
147
|
+
* @param {Object} options - CLI options (controller optional)
|
|
148
|
+
* @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
|
|
149
|
+
*/
|
|
150
|
+
async function resolveControllerAndAuth(options) {
|
|
151
|
+
const controllerUrl = options.controller || (await resolveControllerUrl());
|
|
152
|
+
if (!controllerUrl) {
|
|
153
|
+
logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
const authResult = await getIntegrationClientAuth(controllerUrl);
|
|
157
|
+
if (!authResult || !authResult.token) {
|
|
158
|
+
logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
|
|
159
|
+
logger.error(chalk.gray('Run: aifabrix login'));
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
controllerUrl: authResult.controllerUrl,
|
|
164
|
+
authConfig: { type: 'bearer', token: authResult.token }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {Array<Record<string, unknown>>} items - Integration clients
|
|
170
|
+
*/
|
|
171
|
+
function displayIntegrationClientList(items) {
|
|
172
|
+
logger.log(`\n${sectionTitle('Integration clients:')}\n`);
|
|
173
|
+
if (!items || items.length === 0) {
|
|
174
|
+
logger.log(chalk.gray(' No integration clients found.\n'));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const idCol = 'Id'.padEnd(ID_WIDTH);
|
|
178
|
+
const keyCol = 'Key'.padEnd(KEY_WIDTH);
|
|
179
|
+
const displayCol = 'Display'.padEnd(DISPLAY_WIDTH);
|
|
180
|
+
const clientIdCol = 'ClientId'.padEnd(CLIENT_ID_WIDTH);
|
|
181
|
+
const statusCol = 'Status'.padEnd(STATUS_WIDTH);
|
|
182
|
+
logger.log(chalk.gray(`${idCol}${keyCol}${displayCol}${clientIdCol}${statusCol}`));
|
|
183
|
+
logger.log(chalk.gray('-'.repeat(TABLE_SEPARATOR_LENGTH)));
|
|
184
|
+
items.forEach((row) => {
|
|
185
|
+
const id = (row.id ?? '').toString().padEnd(ID_WIDTH);
|
|
186
|
+
const key = (row.key ?? 'ā').toString().padEnd(KEY_WIDTH);
|
|
187
|
+
const displayName = (row.displayName ?? 'ā').toString().padEnd(DISPLAY_WIDTH);
|
|
188
|
+
const kcId = (row.keycloakClientId ?? 'ā').toString().padEnd(CLIENT_ID_WIDTH);
|
|
189
|
+
const status = (row.status ?? 'ā').toString().padEnd(STATUS_WIDTH);
|
|
190
|
+
logger.log(`${id}${key}${displayName}${kcId}${status}`);
|
|
191
|
+
});
|
|
192
|
+
logger.log('');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {string} clientId - OAuth client id (Keycloak)
|
|
197
|
+
* @param {string} clientSecret - One-time client secret
|
|
198
|
+
*/
|
|
199
|
+
function displayCreateSuccess(clientId, clientSecret) {
|
|
200
|
+
logger.log(formatSuccessParagraph('Integration client created.'));
|
|
201
|
+
logger.log(`${chalk.gray(' clientId:')} ${chalk.cyan(clientId)}`);
|
|
202
|
+
logger.log(`${chalk.gray(' clientSecret:')} ${chalk.cyan(clientSecret)}`);
|
|
203
|
+
logger.log(chalk.yellow(`\nā ${ONE_TIME_WARNING}\n`));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {string} [val] - Comma-separated value
|
|
208
|
+
* @returns {string[]}
|
|
209
|
+
*/
|
|
210
|
+
function parseList(val) {
|
|
211
|
+
if (val === undefined || val === null || String(val).trim() === '') {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
return String(val)
|
|
215
|
+
.split(',')
|
|
216
|
+
.map(s => s.trim())
|
|
217
|
+
.filter(Boolean);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {Object} options - CLI options
|
|
222
|
+
* @returns {{ key: string, displayName: string, redirectUris: string[], groupNames: string[], description?: string, keycloakClientId?: string }}
|
|
223
|
+
*/
|
|
224
|
+
function validateIntegrationClientCreateOptions(options) {
|
|
225
|
+
const key = requireValidIntegrationClientKey(options.key);
|
|
226
|
+
const displayName = options.displayName?.trim();
|
|
227
|
+
const redirectUris = parseList(options.redirectUris);
|
|
228
|
+
const groupNames = parseList(options.groupNames);
|
|
229
|
+
if (!displayName) {
|
|
230
|
+
logger.error(formatBlockingError('Display name is required. Use --display-name <name>.'));
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
if (redirectUris.length === 0) {
|
|
234
|
+
logger.error(formatBlockingError('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
const out = {
|
|
238
|
+
key,
|
|
239
|
+
displayName,
|
|
240
|
+
redirectUris,
|
|
241
|
+
groupNames,
|
|
242
|
+
description: options.description?.trim() || undefined
|
|
243
|
+
};
|
|
244
|
+
const kc = options.keycloakClientId?.trim();
|
|
245
|
+
if (kc) {
|
|
246
|
+
out.keycloakClientId = kc;
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @async
|
|
253
|
+
* @param {Object} options - CLI options
|
|
254
|
+
* @returns {Promise<Object>}
|
|
255
|
+
*/
|
|
256
|
+
async function resolveOptionsAndAuth(options) {
|
|
257
|
+
const validated = validateIntegrationClientCreateOptions(options);
|
|
258
|
+
const controllerUrl = options.controller || (await resolveControllerUrl());
|
|
259
|
+
if (!controllerUrl) {
|
|
260
|
+
logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
const authResult = await getIntegrationClientAuth(controllerUrl);
|
|
264
|
+
if (!authResult || !authResult.token) {
|
|
265
|
+
logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
|
|
266
|
+
logger.error(chalk.gray('Run: aifabrix login'));
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
...validated,
|
|
271
|
+
controllerUrl: authResult.controllerUrl,
|
|
272
|
+
authConfig: { type: 'bearer', token: authResult.token }
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @async
|
|
278
|
+
* @param {Object} [options]
|
|
279
|
+
* @returns {Promise<void>}
|
|
280
|
+
*/
|
|
281
|
+
async function runIntegrationClientCreate(options = {}) {
|
|
282
|
+
const ctx = await resolveOptionsAndAuth(options);
|
|
283
|
+
const body = {
|
|
284
|
+
key: ctx.key,
|
|
285
|
+
displayName: ctx.displayName,
|
|
286
|
+
redirectUris: ctx.redirectUris,
|
|
287
|
+
groupNames: ctx.groupNames,
|
|
288
|
+
description: ctx.description,
|
|
289
|
+
keycloakClientId: ctx.keycloakClientId
|
|
290
|
+
};
|
|
291
|
+
const response = await createIntegrationClient(ctx.controllerUrl, ctx.authConfig, body);
|
|
292
|
+
if (!response.success) {
|
|
293
|
+
handleCreateError(response);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const { clientId, clientSecret } = extractCreateResponse(response);
|
|
297
|
+
displayCreateSuccess(clientId, clientSecret);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @param {string} [id]
|
|
302
|
+
* @returns {string}
|
|
303
|
+
*/
|
|
304
|
+
function requireIntegrationClientId(id) {
|
|
305
|
+
const trimmed = (id && typeof id === 'string' ? id.trim() : '') || '';
|
|
306
|
+
if (!trimmed) {
|
|
307
|
+
logger.error(formatBlockingError('Integration client ID is required. Use --id <uuid>.'));
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
return trimmed;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @async
|
|
315
|
+
* @param {Object} [options]
|
|
316
|
+
* @returns {Promise<void>}
|
|
317
|
+
*/
|
|
318
|
+
async function runIntegrationClientList(options = {}) {
|
|
319
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
320
|
+
const listOptions = {
|
|
321
|
+
page: options.page,
|
|
322
|
+
pageSize: options.pageSize,
|
|
323
|
+
sort: options.sort,
|
|
324
|
+
filter: options.filter,
|
|
325
|
+
search: options.search
|
|
326
|
+
};
|
|
327
|
+
const response = await listIntegrationClients(controllerUrl, authConfig, listOptions);
|
|
328
|
+
if (response && response.success === false) {
|
|
329
|
+
handleIntegrationClientApiError(response, 'read');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const body = response?.data?.data ?? response?.data ?? response ?? {};
|
|
333
|
+
const items = Array.isArray(body) ? body : (body.data ?? []);
|
|
334
|
+
displayIntegrationClientList(items);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @async
|
|
339
|
+
* @param {Object} [options]
|
|
340
|
+
* @returns {Promise<void>}
|
|
341
|
+
*/
|
|
342
|
+
async function runIntegrationClientRotateSecret(options = {}) {
|
|
343
|
+
const id = requireIntegrationClientId(options.id);
|
|
344
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
345
|
+
const response = await regenerateIntegrationClientSecret(controllerUrl, authConfig, id);
|
|
346
|
+
if (response && response.success === false) {
|
|
347
|
+
handleIntegrationClientApiError(response, 'update');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const payload = response?.data?.data ?? response?.data ?? response ?? {};
|
|
351
|
+
const clientSecret = payload?.clientSecret ?? '';
|
|
352
|
+
if (response && response.success === true) {
|
|
353
|
+
logger.log(formatSuccessParagraph('Secret rotated.'));
|
|
354
|
+
logger.log(`${chalk.gray(' clientSecret:')} ${chalk.cyan(clientSecret)}`);
|
|
355
|
+
logger.log(chalk.yellow(`\nā ${ONE_TIME_WARNING}\n`));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @async
|
|
361
|
+
* @param {Object} [options]
|
|
362
|
+
* @returns {Promise<void>}
|
|
363
|
+
*/
|
|
364
|
+
async function runIntegrationClientDelete(options = {}) {
|
|
365
|
+
const id = requireIntegrationClientId(options.id);
|
|
366
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
367
|
+
const response = await deleteIntegrationClient(controllerUrl, authConfig, id);
|
|
368
|
+
if (response && response.success === false) {
|
|
369
|
+
handleIntegrationClientApiError(response, 'delete');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (response && response.success === true) {
|
|
373
|
+
logger.log(formatSuccessLine('Integration client deactivated.\n'));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @async
|
|
379
|
+
* @param {Object} [options]
|
|
380
|
+
* @returns {Promise<void>}
|
|
381
|
+
*/
|
|
382
|
+
async function runIntegrationClientUpdateGroups(options = {}) {
|
|
383
|
+
const id = requireIntegrationClientId(options.id);
|
|
384
|
+
const groupNames = parseList(options.groupNames);
|
|
385
|
+
if (groupNames.length === 0) {
|
|
386
|
+
logger.error(formatBlockingError('At least one group name is required. Use --group-names <name1,name2,...>.'));
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
390
|
+
const response = await updateIntegrationClientGroups(controllerUrl, authConfig, id, { groupNames });
|
|
391
|
+
if (response && response.success === false) {
|
|
392
|
+
handleIntegrationClientApiError(response, 'update');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (response && response.success === true) {
|
|
396
|
+
logger.log(formatSuccessLine('Integration client groups updated.\n'));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* @async
|
|
402
|
+
* @param {Object} [options]
|
|
403
|
+
* @returns {Promise<void>}
|
|
404
|
+
*/
|
|
405
|
+
async function runIntegrationClientUpdateRedirectUris(options = {}) {
|
|
406
|
+
const id = requireIntegrationClientId(options.id);
|
|
407
|
+
const redirectUris = parseList(options.redirectUris);
|
|
408
|
+
if (redirectUris.length === 0) {
|
|
409
|
+
logger.error(formatBlockingError('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
413
|
+
const response = await updateIntegrationClientRedirectUris(controllerUrl, authConfig, id, { redirectUris });
|
|
414
|
+
if (response && response.success === false) {
|
|
415
|
+
handleIntegrationClientApiError(response, 'update');
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (response && response.success === true) {
|
|
419
|
+
logger.log(formatSuccessLine('Integration client redirect URIs updated.\n'));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
module.exports = {
|
|
424
|
+
runIntegrationClientCreate,
|
|
425
|
+
runIntegrationClientList,
|
|
426
|
+
runIntegrationClientRotateSecret,
|
|
427
|
+
runIntegrationClientDelete,
|
|
428
|
+
runIntegrationClientUpdateGroups,
|
|
429
|
+
runIntegrationClientUpdateRedirectUris
|
|
430
|
+
};
|
|
@@ -16,6 +16,7 @@ const { setCurrentEnvironment, saveDeviceToken, setControllerUrl } = require('..
|
|
|
16
16
|
const { initiateDeviceCodeFlow } = require('../api/auth.api');
|
|
17
17
|
const { pollDeviceCodeToken, displayDeviceCodeInfo } = require('../utils/api');
|
|
18
18
|
const logger = require('../utils/logger');
|
|
19
|
+
const { formatSuccessLine } = require('../utils/cli-layout-chalk');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Validate environment key format
|
|
@@ -79,18 +80,22 @@ async function saveDeviceLoginConfig(controllerUrl, token, refreshToken, expires
|
|
|
79
80
|
* @param {string} envKey - Environment key
|
|
80
81
|
* @returns {Promise<void>}
|
|
81
82
|
*/
|
|
82
|
-
async function saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, expiresAt, envKey) {
|
|
83
|
+
async function saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, expiresAt, envKey, opts = {}) {
|
|
83
84
|
await saveDeviceLoginConfig(controllerUrl, token, refreshToken, expiresAt);
|
|
84
85
|
await setControllerUrl(controllerUrl);
|
|
85
86
|
if (envKey) {
|
|
86
87
|
await setCurrentEnvironment(envKey);
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
if (!opts.compact) {
|
|
90
|
+
logger.log(formatSuccessParagraph('Successfully logged in!'));
|
|
91
|
+
logger.log(chalk.gray(`Controller: ${controllerUrl}`));
|
|
92
|
+
if (envKey) {
|
|
93
|
+
logger.log(chalk.gray(`Environment: ${envKey}`));
|
|
94
|
+
}
|
|
95
|
+
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
96
|
+
} else {
|
|
97
|
+
logger.log(formatSuccessLine('Authentication successful'));
|
|
92
98
|
}
|
|
93
|
-
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
/**
|
|
@@ -103,16 +108,21 @@ async function saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, ex
|
|
|
103
108
|
* @param {string} envKey - Environment key
|
|
104
109
|
* @returns {Promise<{token: string, environment: string}>} Token and environment
|
|
105
110
|
*/
|
|
106
|
-
async function pollAndSaveDeviceCodeToken(controllerUrl, deviceCode, interval, expiresIn, envKey) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
spinner: 'dots'
|
|
110
|
-
|
|
111
|
+
async function pollAndSaveDeviceCodeToken(controllerUrl, deviceCode, interval, expiresIn, envKey, opts = {}) {
|
|
112
|
+
const useSpinner = !opts.compact;
|
|
113
|
+
const spinner = useSpinner
|
|
114
|
+
? ora({ text: 'Waiting for approval', spinner: 'dots' }).start()
|
|
115
|
+
: null;
|
|
116
|
+
if (!useSpinner) {
|
|
117
|
+
logger.log('Waiting for approval...');
|
|
118
|
+
}
|
|
111
119
|
|
|
112
120
|
let pollCount = 0;
|
|
113
121
|
const pollCallback = () => {
|
|
114
122
|
pollCount++;
|
|
115
|
-
|
|
123
|
+
if (spinner) {
|
|
124
|
+
spinner.text = `Waiting for approval (attempt ${pollCount})...`;
|
|
125
|
+
}
|
|
116
126
|
};
|
|
117
127
|
|
|
118
128
|
try {
|
|
@@ -124,16 +134,20 @@ async function pollAndSaveDeviceCodeToken(controllerUrl, deviceCode, interval, e
|
|
|
124
134
|
pollCallback
|
|
125
135
|
);
|
|
126
136
|
|
|
127
|
-
spinner
|
|
137
|
+
if (spinner) {
|
|
138
|
+
spinner.succeed('Authentication approved!');
|
|
139
|
+
}
|
|
128
140
|
|
|
129
141
|
const token = tokenResponse.access_token;
|
|
130
142
|
const refreshToken = tokenResponse.refresh_token;
|
|
131
143
|
const expiresAt = new Date(Date.now() + (tokenResponse.expires_in * 1000)).toISOString();
|
|
132
144
|
|
|
133
|
-
await saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, expiresAt, envKey);
|
|
145
|
+
await saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, expiresAt, envKey, opts);
|
|
134
146
|
return { token, environment: envKey };
|
|
135
147
|
} catch (pollError) {
|
|
136
|
-
spinner
|
|
148
|
+
if (spinner) {
|
|
149
|
+
spinner.fail('Authentication failed');
|
|
150
|
+
}
|
|
137
151
|
throw pollError;
|
|
138
152
|
}
|
|
139
153
|
}
|
|
@@ -210,6 +224,32 @@ function convertDeviceCodeResponse(apiResponse) {
|
|
|
210
224
|
};
|
|
211
225
|
}
|
|
212
226
|
|
|
227
|
+
function logDeviceFlowStart(online, requestScope, opts) {
|
|
228
|
+
if (opts.compact) return;
|
|
229
|
+
logger.log(chalk.blue('\nš± Initiating device code flow...\n'));
|
|
230
|
+
if (!online && requestScope.includes('offline_access')) {
|
|
231
|
+
logger.log(chalk.gray(`Requesting offline token (scope: ${requestScope})\n`));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function logDeviceFlowError(deviceError, opts) {
|
|
236
|
+
if (opts.exitOnFailure === false) {
|
|
237
|
+
throw deviceError;
|
|
238
|
+
}
|
|
239
|
+
if (deviceError.formattedError) {
|
|
240
|
+
logger.error(chalk.red('\nā Device code flow failed:'));
|
|
241
|
+
logger.log(deviceError.formattedError);
|
|
242
|
+
} else {
|
|
243
|
+
logger.error(chalk.red(`\nā Device code flow failed: ${deviceError.message}`));
|
|
244
|
+
}
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function printCompactDeviceUrl(deviceCodeResponse) {
|
|
249
|
+
logger.log('\nOpen browser to authenticate:');
|
|
250
|
+
logger.log(chalk.yellow(`${deviceCodeResponse.verification_uri}?user_code=${deviceCodeResponse.user_code}\n`));
|
|
251
|
+
}
|
|
252
|
+
|
|
213
253
|
/**
|
|
214
254
|
* Handle device code flow login
|
|
215
255
|
* @async
|
|
@@ -219,14 +259,11 @@ function convertDeviceCodeResponse(apiResponse) {
|
|
|
219
259
|
* @param {string} [scope] - Custom scope string
|
|
220
260
|
* @returns {Promise<{token: string, environment: string}>} Token and environment
|
|
221
261
|
*/
|
|
222
|
-
async function handleDeviceCodeLogin(controllerUrl, environment, online, scope) {
|
|
262
|
+
async function handleDeviceCodeLogin(controllerUrl, environment, online, scope, opts = {}) {
|
|
223
263
|
const envKey = await getEnvironmentKey(environment);
|
|
224
264
|
const requestScope = buildScope(online, scope);
|
|
225
265
|
|
|
226
|
-
|
|
227
|
-
if (!online && requestScope.includes('offline_access')) {
|
|
228
|
-
logger.log(chalk.gray(`Requesting offline token (scope: ${requestScope})\n`));
|
|
229
|
-
}
|
|
266
|
+
logDeviceFlowStart(online, requestScope, opts);
|
|
230
267
|
|
|
231
268
|
try {
|
|
232
269
|
// Use centralized API client for device code flow initiation
|
|
@@ -239,25 +276,23 @@ async function handleDeviceCodeLogin(controllerUrl, environment, online, scope)
|
|
|
239
276
|
const apiResponse = deviceCodeApiResponse.data;
|
|
240
277
|
const deviceCodeResponse = convertDeviceCodeResponse(apiResponse);
|
|
241
278
|
|
|
242
|
-
|
|
279
|
+
if (!opts.compact) {
|
|
280
|
+
displayDeviceCodeInfo(deviceCodeResponse.user_code, deviceCodeResponse.verification_uri, logger, chalk);
|
|
281
|
+
} else {
|
|
282
|
+
printCompactDeviceUrl(deviceCodeResponse);
|
|
283
|
+
}
|
|
243
284
|
|
|
244
285
|
return await pollAndSaveDeviceCodeToken(
|
|
245
286
|
controllerUrl,
|
|
246
287
|
deviceCodeResponse.device_code,
|
|
247
288
|
deviceCodeResponse.interval,
|
|
248
289
|
deviceCodeResponse.expires_in,
|
|
249
|
-
envKey
|
|
290
|
+
envKey,
|
|
291
|
+
opts
|
|
250
292
|
);
|
|
251
293
|
|
|
252
294
|
} catch (deviceError) {
|
|
253
|
-
|
|
254
|
-
if (deviceError.formattedError) {
|
|
255
|
-
logger.error(chalk.red('\nā Device code flow failed:'));
|
|
256
|
-
logger.log(deviceError.formattedError);
|
|
257
|
-
} else {
|
|
258
|
-
logger.error(chalk.red(`\nā Device code flow failed: ${deviceError.message}`));
|
|
259
|
-
}
|
|
260
|
-
process.exit(1);
|
|
295
|
+
logDeviceFlowError(deviceError, opts);
|
|
261
296
|
}
|
|
262
297
|
}
|
|
263
298
|
|
package/lib/commands/login.js
CHANGED
|
@@ -87,7 +87,9 @@ async function normalizeControllerUrl(options) {
|
|
|
87
87
|
controllerUrl = String(controllerUrl).replace(/\/+$/, '');
|
|
88
88
|
// Save controller URL to config
|
|
89
89
|
await setControllerUrl(controllerUrl);
|
|
90
|
-
|
|
90
|
+
if (!options.compact) {
|
|
91
|
+
logger.log(chalk.gray(`Controller URL: ${controllerUrl}`));
|
|
92
|
+
}
|
|
91
93
|
return controllerUrl;
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -102,7 +104,9 @@ async function handleEnvironmentConfig(options) {
|
|
|
102
104
|
if (options.environment) {
|
|
103
105
|
const environment = options.environment.trim();
|
|
104
106
|
await setCurrentEnvironment(environment);
|
|
105
|
-
|
|
107
|
+
if (!options.compact) {
|
|
108
|
+
logger.log(chalk.gray(`Environment: ${environment}`));
|
|
109
|
+
}
|
|
106
110
|
return environment;
|
|
107
111
|
}
|
|
108
112
|
// Get current environment from config
|
|
@@ -151,11 +155,16 @@ async function handleCredentialsLoginFlow(controllerUrl, environment, options) {
|
|
|
151
155
|
* @returns {Promise<{token: string, environment: string}>} Login result
|
|
152
156
|
*/
|
|
153
157
|
async function handleDeviceCodeLoginFlow(controllerUrl, environment, options) {
|
|
154
|
-
return await handleDeviceCodeLogin(controllerUrl, environment, options.online, options.scope
|
|
158
|
+
return await handleDeviceCodeLogin(controllerUrl, environment, options.online, options.scope, {
|
|
159
|
+
compact: Boolean(options.compact),
|
|
160
|
+
exitOnFailure: options.exitOnFailure !== false
|
|
161
|
+
});
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
async function handleLogin(options) {
|
|
158
|
-
|
|
165
|
+
if (!options.compact) {
|
|
166
|
+
logger.log(chalk.blue('\nš Logging in to Miso Controller...\n'));
|
|
167
|
+
}
|
|
159
168
|
|
|
160
169
|
const controllerUrl = await normalizeControllerUrl(options);
|
|
161
170
|
const environment = await handleEnvironmentConfig(options);
|
|
@@ -170,13 +179,15 @@ async function handleLogin(options) {
|
|
|
170
179
|
return; // Early return for device flow (already saved config)
|
|
171
180
|
}
|
|
172
181
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
if (!options.compact) {
|
|
183
|
+
logger.log(formatSuccessParagraph('Successfully logged in!'));
|
|
184
|
+
logger.log(chalk.gray(`Controller: ${controllerUrl}`));
|
|
185
|
+
logger.log(chalk.gray(`Environment: ${environment}`));
|
|
186
|
+
if (options.app) {
|
|
187
|
+
logger.log(chalk.gray(`App: ${options.app}`));
|
|
188
|
+
}
|
|
189
|
+
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
178
190
|
}
|
|
179
|
-
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
180
191
|
}
|
|
181
192
|
|
|
182
193
|
module.exports = { handleLogin };
|