@aifabrix/builder 2.44.5 → 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.
Files changed (207) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +48 -2
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/validation-runner.js +46 -25
  17. package/lib/app/deploy-config.js +11 -1
  18. package/lib/app/deploy-status-display.js +3 -3
  19. package/lib/app/deploy.js +36 -14
  20. package/lib/app/display.js +15 -11
  21. package/lib/app/push.js +46 -23
  22. package/lib/app/register.js +1 -1
  23. package/lib/app/restart-display.js +95 -0
  24. package/lib/app/rotate-secret.js +1 -1
  25. package/lib/app/run-container-start.js +12 -6
  26. package/lib/app/run-env-compose.js +30 -1
  27. package/lib/app/run-helpers.js +44 -12
  28. package/lib/app/run-reload-sync.js +148 -0
  29. package/lib/app/run-resolve-image.js +51 -1
  30. package/lib/app/run.js +99 -73
  31. package/lib/build/index.js +75 -45
  32. package/lib/cli/doctor-check.js +117 -0
  33. package/lib/cli/index.js +8 -2
  34. package/lib/cli/infra-guided.js +445 -0
  35. package/lib/cli/setup-app.js +20 -2
  36. package/lib/cli/setup-auth.js +26 -0
  37. package/lib/cli/setup-dev-path-commands.js +50 -3
  38. package/lib/cli/setup-infra.js +134 -61
  39. package/lib/cli/setup-integration-client.js +182 -0
  40. package/lib/cli/setup-parameters.js +21 -2
  41. package/lib/cli/setup-platform.js +102 -0
  42. package/lib/cli/setup-secrets.js +18 -6
  43. package/lib/cli/setup-utility.js +78 -33
  44. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  45. package/lib/commands/datasource-capability-output.js +29 -0
  46. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  47. package/lib/commands/datasource-capability.js +411 -0
  48. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  49. package/lib/commands/datasource.js +53 -13
  50. package/lib/commands/dev-down.js +3 -3
  51. package/lib/commands/dev-infra-gate.js +32 -0
  52. package/lib/commands/dev-init.js +13 -7
  53. package/lib/commands/dimension-value.js +179 -0
  54. package/lib/commands/dimension.js +330 -0
  55. package/lib/commands/integration-client.js +430 -0
  56. package/lib/commands/login-device.js +65 -30
  57. package/lib/commands/login.js +21 -10
  58. package/lib/commands/parameters-validate.js +78 -13
  59. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  60. package/lib/commands/repair-datasource-keys.js +10 -5
  61. package/lib/commands/repair-datasource.js +19 -7
  62. package/lib/commands/repair-env-template.js +4 -1
  63. package/lib/commands/repair-openapi-sync.js +172 -0
  64. package/lib/commands/repair-persist.js +102 -0
  65. package/lib/commands/repair-rbac-extract.js +27 -0
  66. package/lib/commands/repair-rbac-migrate.js +186 -0
  67. package/lib/commands/repair-rbac.js +214 -31
  68. package/lib/commands/repair-system-alignment.js +246 -0
  69. package/lib/commands/repair-system-permissions.js +168 -0
  70. package/lib/commands/repair.js +120 -338
  71. package/lib/commands/secure.js +1 -1
  72. package/lib/commands/setup-modes.js +455 -0
  73. package/lib/commands/setup-prompts.js +388 -0
  74. package/lib/commands/setup.js +149 -0
  75. package/lib/commands/teardown.js +228 -0
  76. package/lib/commands/up-common.js +79 -19
  77. package/lib/commands/up-dataplane.js +33 -11
  78. package/lib/commands/up-miso.js +7 -11
  79. package/lib/commands/upload.js +109 -23
  80. package/lib/commands/wizard-core-helpers.js +14 -11
  81. package/lib/commands/wizard-core.js +6 -5
  82. package/lib/commands/wizard-dataplane.js +2 -2
  83. package/lib/commands/wizard-entity-selection.js +4 -3
  84. package/lib/commands/wizard-headless.js +2 -1
  85. package/lib/commands/wizard.js +2 -1
  86. package/lib/constants/infra-compose-service-names.js +40 -0
  87. package/lib/core/env-reader.js +16 -3
  88. package/lib/core/secrets-admin-env.js +101 -0
  89. package/lib/core/secrets-ensure-infra.js +34 -1
  90. package/lib/core/secrets-ensure.js +88 -66
  91. package/lib/core/secrets-env-content.js +432 -0
  92. package/lib/core/secrets-env-write.js +27 -1
  93. package/lib/core/secrets-load.js +248 -0
  94. package/lib/core/secrets-names.js +32 -0
  95. package/lib/core/secrets.js +17 -757
  96. package/lib/datasource/capability/basic-exposure.js +76 -0
  97. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  98. package/lib/datasource/capability/capability-key.js +34 -0
  99. package/lib/datasource/capability/capability-resolve.js +172 -0
  100. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  101. package/lib/datasource/capability/copy-operations.js +348 -0
  102. package/lib/datasource/capability/copy-test-payload.js +139 -0
  103. package/lib/datasource/capability/create-operations.js +235 -0
  104. package/lib/datasource/capability/dimension-operations.js +151 -0
  105. package/lib/datasource/capability/dimension-validate.js +219 -0
  106. package/lib/datasource/capability/json-pointer.js +31 -0
  107. package/lib/datasource/capability/reference-rewrite.js +51 -0
  108. package/lib/datasource/capability/relate-operations.js +325 -0
  109. package/lib/datasource/capability/relate-validate.js +219 -0
  110. package/lib/datasource/capability/remove-operations.js +275 -0
  111. package/lib/datasource/capability/run-capability-copy.js +152 -0
  112. package/lib/datasource/capability/run-capability-diff.js +135 -0
  113. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  114. package/lib/datasource/capability/run-capability-edit.js +377 -0
  115. package/lib/datasource/capability/run-capability-relate.js +193 -0
  116. package/lib/datasource/capability/run-capability-remove.js +105 -0
  117. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  118. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  119. package/lib/datasource/list.js +136 -23
  120. package/lib/datasource/log-viewer.js +2 -4
  121. package/lib/datasource/unified-validation-run.js +51 -16
  122. package/lib/datasource/validate.js +53 -1
  123. package/lib/deployment/deploy-poll-ui.js +60 -0
  124. package/lib/deployment/deployer-status.js +29 -3
  125. package/lib/deployment/deployer.js +48 -30
  126. package/lib/deployment/environment.js +7 -2
  127. package/lib/deployment/poll-interval.js +72 -0
  128. package/lib/deployment/push.js +11 -9
  129. package/lib/external-system/deploy.js +4 -1
  130. package/lib/external-system/download.js +61 -32
  131. package/lib/external-system/sync-deploy-manifest.js +33 -0
  132. package/lib/infrastructure/index.js +49 -19
  133. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  134. package/lib/parameters/infra-kv-discovery.js +29 -4
  135. package/lib/parameters/infra-parameter-catalog.js +6 -3
  136. package/lib/parameters/infra-parameter-validate.js +67 -19
  137. package/lib/resolvers/datasource-resolver.js +53 -0
  138. package/lib/resolvers/dimension-file.js +52 -0
  139. package/lib/resolvers/manifest-resolver.js +133 -0
  140. package/lib/schema/external-datasource.schema.json +183 -53
  141. package/lib/schema/external-system.schema.json +23 -10
  142. package/lib/schema/infra.parameter.yaml +26 -11
  143. package/lib/schema/wizard-config.schema.json +1 -1
  144. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  145. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  146. package/lib/utils/app-run-containers.js +2 -2
  147. package/lib/utils/bash-secret-env.js +59 -0
  148. package/lib/utils/cli-secrets-error-format.js +78 -0
  149. package/lib/utils/cli-test-layout-chalk.js +31 -9
  150. package/lib/utils/cli-utils.js +4 -36
  151. package/lib/utils/datasource-test-run-display.js +8 -0
  152. package/lib/utils/dev-hosts-helper.js +3 -2
  153. package/lib/utils/dev-init-ssh-merge.js +2 -1
  154. package/lib/utils/docker-build.js +17 -9
  155. package/lib/utils/docker-reload-mount.js +127 -0
  156. package/lib/utils/external-readme.js +71 -2
  157. package/lib/utils/external-system-local-test-tty.js +3 -2
  158. package/lib/utils/external-system-readiness-core.js +45 -12
  159. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  160. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  161. package/lib/utils/external-system-readiness-display.js +10 -1
  162. package/lib/utils/file-upload.js +40 -3
  163. package/lib/utils/health-check-db-init.js +107 -0
  164. package/lib/utils/health-check-public-warn.js +69 -0
  165. package/lib/utils/health-check-url.js +19 -4
  166. package/lib/utils/health-check.js +135 -105
  167. package/lib/utils/help-builder.js +5 -1
  168. package/lib/utils/image-name.js +34 -7
  169. package/lib/utils/integration-file-backup.js +74 -0
  170. package/lib/utils/mutagen-install.js +30 -3
  171. package/lib/utils/paths.js +108 -25
  172. package/lib/utils/postgres-wipe.js +212 -0
  173. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  174. package/lib/utils/remote-dev-auth.js +21 -5
  175. package/lib/utils/remote-docker-env.js +9 -1
  176. package/lib/utils/remote-secrets-loader.js +42 -3
  177. package/lib/utils/resolve-docker-image-ref.js +9 -3
  178. package/lib/utils/secrets-ancestor-paths.js +47 -0
  179. package/lib/utils/secrets-helpers.js +17 -10
  180. package/lib/utils/secrets-kv-refs.js +42 -0
  181. package/lib/utils/secrets-kv-scope.js +19 -2
  182. package/lib/utils/secrets-materialize-local.js +134 -0
  183. package/lib/utils/secrets-path.js +24 -10
  184. package/lib/utils/secrets-utils.js +2 -2
  185. package/lib/utils/system-builder-root.js +34 -0
  186. package/lib/utils/url-declarative-resolve-build.js +6 -1
  187. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  188. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  189. package/lib/utils/urls-local-registry.js +23 -12
  190. package/lib/utils/validation-poll-ui.js +81 -0
  191. package/lib/utils/validation-run-poll.js +29 -5
  192. package/lib/utils/with-muted-logger.js +53 -0
  193. package/package.json +1 -1
  194. package/templates/applications/dataplane/application.yaml +1 -1
  195. package/templates/applications/dataplane/rbac.yaml +10 -10
  196. package/templates/applications/keycloak/env.template +8 -6
  197. package/templates/applications/miso-controller/application.yaml +7 -0
  198. package/templates/applications/miso-controller/env.template +1 -1
  199. package/templates/applications/miso-controller/rbac.yaml +9 -9
  200. package/templates/external-system/README.md.hbs +83 -123
  201. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  202. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  203. package/.nyc_output/processinfo/index.json +0 -1
  204. package/lib/api/service-users.api.js +0 -150
  205. package/lib/api/types/service-users.types.js +0 -65
  206. package/lib/cli/setup-service-user.js +0 -187
  207. package/lib/commands/service-user.js +0 -429
@@ -1,429 +0,0 @@
1
- const { formatBlockingError, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
2
- /**
3
- * Service user create command – create service user and get one-time secret
4
- * POST /api/v1/service-users. Used by `aifabrix service-user create`.
5
- *
6
- * @fileoverview Service user create command implementation
7
- * @author AI Fabrix Team
8
- * @version 2.0.0
9
- */
10
-
11
- const chalk = require('chalk');
12
- const logger = require('../utils/logger');
13
- const { resolveControllerUrl } = require('../utils/controller-url');
14
- const { getOrRefreshDeviceToken } = require('../utils/token-manager');
15
- const { normalizeControllerUrl } = require('../core/config');
16
- const {
17
- createServiceUser,
18
- listServiceUsers,
19
- regenerateSecretServiceUser,
20
- deleteServiceUser,
21
- updateGroupsServiceUser,
22
- updateRedirectUrisServiceUser
23
- } = require('../api/service-users.api');
24
-
25
- const ONE_TIME_WARNING =
26
- 'Save this secret now; it will not be shown again.';
27
-
28
- /**
29
- * Get auth token for service-user (device token from config)
30
- * @async
31
- * @param {string} controllerUrl - Controller base URL
32
- * @returns {Promise<{token: string, controllerUrl: string}|null>}
33
- */
34
- async function getServiceUserAuth(controllerUrl) {
35
- const normalizedUrl = normalizeControllerUrl(controllerUrl);
36
- const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
37
- if (deviceToken && deviceToken.token) {
38
- return {
39
- token: deviceToken.token,
40
- controllerUrl: deviceToken.controller || normalizedUrl
41
- };
42
- }
43
- return null;
44
- }
45
-
46
- /**
47
- * Extract clientId and clientSecret from API response.
48
- * Controller returns { data: { user, clientSecret } }; API client puts body in response.data.
49
- * So payload is at response.data.data. clientId may be on user.clientId or user.federatedIdentity.keycloakClientId.
50
- * @param {Object} response - API response (success: true, data: body)
51
- * @returns {{ clientId: string, clientSecret: string }}
52
- */
53
- function extractCreateResponse(response) {
54
- const payload = response?.data?.data ?? response?.data ?? response;
55
- const user = payload?.user;
56
- const clientId =
57
- user?.clientId ??
58
- user?.federatedIdentity?.keycloakClientId ??
59
- payload?.clientId ??
60
- '';
61
- const clientSecret = payload?.clientSecret ?? '';
62
- return { clientId, clientSecret };
63
- }
64
-
65
- const ID_WIDTH = 38;
66
- const USERNAME_WIDTH = 22;
67
- const EMAIL_WIDTH = 28;
68
- const CLIENT_ID_WIDTH = 24;
69
- const TABLE_SEPARATOR_LENGTH = 130;
70
-
71
- /**
72
- * Log error for failed create response and exit
73
- * @param {Object} response - API response with success: false
74
- */
75
- function handleCreateError(response) {
76
- const status = response.status;
77
- const msg = response.formattedError || response.error || 'Request failed';
78
- if (status === 400) {
79
- logger.error(formatBlockingError(`Validation error: ${msg}`));
80
- } else if (status === 401) {
81
- logger.error(formatBlockingError('Unauthorized. Run "aifabrix login" and try again.'));
82
- } else if (status === 403) {
83
- logger.error(formatBlockingError('Missing permission: service-user:create'));
84
- logger.error(chalk.gray('Your account needs the service-user:create permission on the controller.'));
85
- } else {
86
- logger.error(formatBlockingError(`Failed to create service user: ${msg}`));
87
- }
88
- process.exit(1);
89
- }
90
-
91
- /**
92
- * Log error for service-user API response and exit
93
- * @param {Object} response - API response with success: false
94
- * @param {string} permissionScope - Permission hint: 'read' | 'update' | 'delete'
95
- */
96
- function handleServiceUserApiError(response, permissionScope) {
97
- const status = response.status;
98
- const msg = response.formattedError || response.error || 'Request failed';
99
- if (status === 400) {
100
- logger.error(formatBlockingError(`Validation error: ${msg}`));
101
- } else if (status === 401) {
102
- logger.error(formatBlockingError('Unauthorized. Run "aifabrix login" and try again.'));
103
- } else if (status === 403) {
104
- logger.error(formatBlockingError(`Missing permission: service-user:${permissionScope}`));
105
- logger.error(chalk.gray(`Your account needs the service-user:${permissionScope} permission on the controller.`));
106
- } else if (status === 404) {
107
- logger.error(formatBlockingError('Service user not found.'));
108
- const detail = response.error || '';
109
- if (detail) {
110
- logger.error(chalk.gray(detail));
111
- }
112
- } else {
113
- logger.error(formatBlockingError(`Request failed: ${msg}`));
114
- }
115
- process.exit(1);
116
- }
117
-
118
- /**
119
- * Resolve controller URL and auth for list/rotate/delete/update (no create-specific validation)
120
- * @async
121
- * @param {Object} options - CLI options (controller optional)
122
- * @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
123
- */
124
- async function resolveControllerAndAuth(options) {
125
- const controllerUrl = options.controller || (await resolveControllerUrl());
126
- if (!controllerUrl) {
127
- logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
128
- process.exit(1);
129
- }
130
- const authResult = await getServiceUserAuth(controllerUrl);
131
- if (!authResult || !authResult.token) {
132
- logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
133
- logger.error(chalk.gray('Run: aifabrix login'));
134
- process.exit(1);
135
- }
136
- return {
137
- controllerUrl: authResult.controllerUrl,
138
- authConfig: { type: 'bearer', token: authResult.token }
139
- };
140
- }
141
-
142
- /**
143
- * Display service user list as a table (id, username, email, clientId, active).
144
- * API shape: items have id, username, email, status, federatedIdentity.keycloakClientId.
145
- * @param {Array<{ id?: string, username?: string, email?: string, status?: string, active?: boolean, clientId?: string, federatedIdentity?: { keycloakClientId?: string } }>} items - Service users
146
- */
147
- function displayServiceUserList(items) {
148
- logger.log(chalk.bold('\n📋 Service users:\n'));
149
- if (!items || items.length === 0) {
150
- logger.log(chalk.gray(' No service users found.\n'));
151
- return;
152
- }
153
- const idCol = 'Id'.padEnd(ID_WIDTH);
154
- const usernameCol = 'Username'.padEnd(USERNAME_WIDTH);
155
- const emailCol = 'Email'.padEnd(EMAIL_WIDTH);
156
- const clientIdCol = 'ClientId'.padEnd(CLIENT_ID_WIDTH);
157
- const activeCol = 'Active';
158
- logger.log(chalk.gray(`${idCol}${usernameCol}${emailCol}${clientIdCol}${activeCol}`));
159
- logger.log(chalk.gray('-'.repeat(TABLE_SEPARATOR_LENGTH)));
160
- items.forEach((row) => {
161
- const id = (row.id ?? '').toString().padEnd(ID_WIDTH);
162
- const username = (row.username ?? '—').padEnd(USERNAME_WIDTH);
163
- const email = (row.email ?? '—').padEnd(EMAIL_WIDTH);
164
- const clientId = (row.federatedIdentity?.keycloakClientId ?? row.clientId ?? '—').padEnd(CLIENT_ID_WIDTH);
165
- const active = row.status === 'active' ? 'yes' : (row.status ?? (row.active === true ? 'yes' : row.active === false ? 'no' : '—'));
166
- logger.log(`${id}${username}${email}${clientId}${active}`);
167
- });
168
- logger.log('');
169
- }
170
-
171
- /**
172
- * Display success output with clientId, clientSecret and one-time warning
173
- * @param {string} clientId - Service user client ID
174
- * @param {string} clientSecret - One-time client secret
175
- */
176
- function displayCreateSuccess(clientId, clientSecret) {
177
- logger.log(chalk.bold('\n✔ Service user created\n'));
178
- logger.log(chalk.cyan(' clientId: ') + clientId);
179
- logger.log(chalk.cyan(' clientSecret: ') + clientSecret);
180
- logger.log('');
181
- logger.log(chalk.yellow('⚠ ' + ONE_TIME_WARNING));
182
- logger.log('');
183
- }
184
-
185
- /**
186
- * Parse comma-separated string into non-empty trimmed array
187
- * @param {string} [val] - Comma-separated value
188
- * @returns {string[]}
189
- */
190
- function parseList(val) {
191
- if (val === undefined || val === null || String(val).trim() === '') {
192
- return [];
193
- }
194
- return String(val)
195
- .split(',')
196
- .map(s => s.trim())
197
- .filter(Boolean);
198
- }
199
-
200
- /**
201
- * Validate username, email, redirectUris, groupIds; exit on failure
202
- * @param {Object} options - CLI options
203
- * @returns {{ username: string, email: string, redirectUris: string[], groupNames: string[], description?: string }}
204
- */
205
- function validateServiceUserOptions(options) {
206
- const username = options.username?.trim();
207
- const email = options.email?.trim();
208
- const redirectUris = parseList(options.redirectUris);
209
- const groupNames = parseList(options.groupNames);
210
- if (!username) {
211
- logger.error(formatBlockingError('Username is required. Use --username <username>.'));
212
- process.exit(1);
213
- }
214
- if (!email) {
215
- logger.error(formatBlockingError('Email is required. Use --email <email>.'));
216
- process.exit(1);
217
- }
218
- if (redirectUris.length === 0) {
219
- logger.error(formatBlockingError('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
220
- process.exit(1);
221
- }
222
- if (groupNames.length === 0) {
223
- logger.error(formatBlockingError('At least one group name is required. Use --group-names <name1,name2,...>.'));
224
- process.exit(1);
225
- }
226
- return {
227
- username,
228
- email,
229
- redirectUris,
230
- groupNames,
231
- description: options.description?.trim() || undefined
232
- };
233
- }
234
-
235
- /**
236
- * Resolve controller URL and auth; exit on failure
237
- * @async
238
- * @param {Object} options - CLI options
239
- * @returns {Promise<{ username: string, email: string, redirectUris: string[], groupNames: string[], description?: string, controllerUrl: string, authConfig: Object }>}
240
- */
241
- async function resolveOptionsAndAuth(options) {
242
- const validated = validateServiceUserOptions(options);
243
- const controllerUrl = options.controller || (await resolveControllerUrl());
244
- if (!controllerUrl) {
245
- logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
246
- process.exit(1);
247
- }
248
- const authResult = await getServiceUserAuth(controllerUrl);
249
- if (!authResult || !authResult.token) {
250
- logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
251
- logger.error(chalk.gray('Run: aifabrix login'));
252
- process.exit(1);
253
- }
254
- return {
255
- ...validated,
256
- controllerUrl: authResult.controllerUrl,
257
- authConfig: { type: 'bearer', token: authResult.token }
258
- };
259
- }
260
-
261
- /**
262
- * Run service-user create: call POST /api/v1/service-users and display one-time secret with warning
263
- * @async
264
- * @param {Object} options - CLI options
265
- * @param {string} [options.controller] - Controller URL override
266
- * @param {string} options.username - Username (required)
267
- * @param {string} options.email - Email (required)
268
- * @param {string} options.redirectUris - Comma-separated redirect URIs (required, min 1)
269
- * @param {string} options.groupNames - Comma-separated group names (required, e.g. AI-Fabrix-Developers)
270
- * @param {string} [options.description] - Optional description
271
- * @returns {Promise<void>}
272
- */
273
- async function runServiceUserCreate(options = {}) {
274
- const ctx = await resolveOptionsAndAuth(options);
275
- const body = {
276
- username: ctx.username,
277
- email: ctx.email,
278
- redirectUris: ctx.redirectUris,
279
- groupNames: ctx.groupNames,
280
- description: ctx.description
281
- };
282
- const response = await createServiceUser(ctx.controllerUrl, ctx.authConfig, body);
283
- if (!response.success) {
284
- handleCreateError(response);
285
- return;
286
- }
287
- const { clientId, clientSecret } = extractCreateResponse(response);
288
- displayCreateSuccess(clientId, clientSecret);
289
- }
290
-
291
- /**
292
- * Require service user id option; exit with message if missing
293
- * @param {string} [id] - Service user ID from options
294
- * @returns {string} Trimmed id
295
- */
296
- function requireServiceUserId(id) {
297
- const trimmed = (id && typeof id === 'string' ? id.trim() : '') || '';
298
- if (!trimmed) {
299
- logger.error(formatBlockingError('Service user ID is required. Use --id <uuid>.'));
300
- process.exit(1);
301
- }
302
- return trimmed;
303
- }
304
-
305
- /**
306
- * Run service-user list: call GET /api/v1/service-users and display table
307
- * @async
308
- * @param {Object} options - CLI options (controller, page, pageSize, sort, filter, search)
309
- * @returns {Promise<void>}
310
- */
311
- async function runServiceUserList(options = {}) {
312
- const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
313
- const listOptions = {
314
- page: options.page,
315
- pageSize: options.pageSize,
316
- sort: options.sort,
317
- filter: options.filter,
318
- search: options.search
319
- };
320
- const response = await listServiceUsers(controllerUrl, authConfig, listOptions);
321
- if (response && response.success === false) {
322
- handleServiceUserApiError(response, 'read');
323
- return;
324
- }
325
- const body = response?.data?.data ?? response?.data ?? response ?? {};
326
- const items = Array.isArray(body) ? body : (body.data ?? []);
327
- displayServiceUserList(items);
328
- }
329
-
330
- /**
331
- * Run service-user rotate-secret: call POST .../regenerate-secret and print new secret once with warning
332
- * @async
333
- * @param {Object} options - CLI options (controller, id required)
334
- * @returns {Promise<void>}
335
- */
336
- async function runServiceUserRotateSecret(options = {}) {
337
- const id = requireServiceUserId(options.id);
338
- const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
339
- const response = await regenerateSecretServiceUser(controllerUrl, authConfig, id);
340
- if (response && response.success === false) {
341
- handleServiceUserApiError(response, 'update');
342
- return;
343
- }
344
- const payload = response?.data?.data ?? response?.data ?? response ?? {};
345
- const clientSecret = payload?.clientSecret ?? '';
346
- if (response && response.success === true) {
347
- logger.log(chalk.bold('\n✔ Secret rotated\n'));
348
- logger.log(chalk.cyan(' clientSecret: ') + clientSecret);
349
- logger.log('');
350
- logger.log(chalk.yellow('⚠ ' + ONE_TIME_WARNING));
351
- logger.log('');
352
- }
353
- }
354
-
355
- /**
356
- * Run service-user delete: call DELETE .../service-users/{id} (deactivates the user)
357
- * @async
358
- * @param {Object} options - CLI options (controller, id required)
359
- * @returns {Promise<void>}
360
- */
361
- async function runServiceUserDelete(options = {}) {
362
- const id = requireServiceUserId(options.id);
363
- const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
364
- const response = await deleteServiceUser(controllerUrl, authConfig, id);
365
- if (response && response.success === false) {
366
- handleServiceUserApiError(response, 'delete');
367
- return;
368
- }
369
- if (response && response.success === true) {
370
- logger.log(formatSuccessLine('Service user deactivated.\n'));
371
- }
372
- }
373
-
374
- /**
375
- * Run service-user update-groups: call PUT .../groups with groupNames
376
- * @async
377
- * @param {Object} options - CLI options (controller, id, groupNames required)
378
- * @returns {Promise<void>}
379
- */
380
- async function runServiceUserUpdateGroups(options = {}) {
381
- const id = requireServiceUserId(options.id);
382
- const groupNames = parseList(options.groupNames);
383
- if (groupNames.length === 0) {
384
- logger.error(formatBlockingError('At least one group name is required. Use --group-names <name1,name2,...>.'));
385
- process.exit(1);
386
- }
387
- const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
388
- const response = await updateGroupsServiceUser(controllerUrl, authConfig, id, { groupNames });
389
- if (response && response.success === false) {
390
- handleServiceUserApiError(response, 'update');
391
- return;
392
- }
393
- if (response && response.success === true) {
394
- logger.log(formatSuccessLine('Service user groups updated.\n'));
395
- }
396
- }
397
-
398
- /**
399
- * Run service-user update-redirect-uris: call PUT .../redirect-uris (min 1 URI)
400
- * @async
401
- * @param {Object} options - CLI options (controller, id, redirectUris required, min 1)
402
- * @returns {Promise<void>}
403
- */
404
- async function runServiceUserUpdateRedirectUris(options = {}) {
405
- const id = requireServiceUserId(options.id);
406
- const redirectUris = parseList(options.redirectUris);
407
- if (redirectUris.length === 0) {
408
- logger.error(formatBlockingError('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
409
- process.exit(1);
410
- }
411
- const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
412
- const response = await updateRedirectUrisServiceUser(controllerUrl, authConfig, id, { redirectUris });
413
- if (response && response.success === false) {
414
- handleServiceUserApiError(response, 'update');
415
- return;
416
- }
417
- if (response && response.success === true) {
418
- logger.log(formatSuccessLine('Service user redirect URIs updated.\n'));
419
- }
420
- }
421
-
422
- module.exports = {
423
- runServiceUserCreate,
424
- runServiceUserList,
425
- runServiceUserRotateSecret,
426
- runServiceUserDelete,
427
- runServiceUserUpdateGroups,
428
- runServiceUserUpdateRedirectUris
429
- };