@aifabrix/builder 2.46.0 → 2.47.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.
Files changed (175) hide show
  1. package/.npmrc.token +1 -0
  2. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  3. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  4. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  5. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  7. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  8. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  9. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  10. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  14. package/.temp/validation/summary.txt +5 -0
  15. package/jest.isolated-projects.js +18 -2
  16. package/jest.projects.js +24 -0
  17. package/lib/api/dataplane-health.api.js +19 -7
  18. package/lib/api/lifecycle.api.js +22 -1
  19. package/lib/api/resource-types.api.js +107 -0
  20. package/lib/api/runtime-configuration.api.js +72 -0
  21. package/lib/api/types/resource-types.types.js +45 -0
  22. package/lib/api/user-groups.api.js +9 -9
  23. package/lib/api/users.api.js +8 -8
  24. package/lib/api/wizard.api.js +10 -2
  25. package/lib/app/down.js +58 -26
  26. package/lib/app/index.js +2 -2
  27. package/lib/app/run-resolve-image.js +2 -1
  28. package/lib/app/run-stop-containers.js +113 -0
  29. package/lib/app/run.js +1 -31
  30. package/lib/app/show-display.js +35 -0
  31. package/lib/app/show.js +32 -1
  32. package/lib/build/index.js +17 -5
  33. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  34. package/lib/channels/add-channel.js +348 -0
  35. package/lib/channels/channel-artifact-generator.js +141 -0
  36. package/lib/channels/channel-audit.js +101 -0
  37. package/lib/channels/channel-auth-context.js +37 -0
  38. package/lib/channels/channel-credential.js +200 -0
  39. package/lib/channels/channel-display-name.js +64 -0
  40. package/lib/channels/channel-external-system.js +106 -0
  41. package/lib/channels/channel-integration-generator.js +274 -0
  42. package/lib/channels/channel-probe.js +89 -0
  43. package/lib/channels/channel-runtime-resolver.js +152 -0
  44. package/lib/channels/channel-status-display.js +141 -0
  45. package/lib/channels/channel-targets.js +115 -0
  46. package/lib/cli/index.js +16 -4
  47. package/lib/cli/setup-app.js +54 -3
  48. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  49. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  50. package/lib/cli/setup-platform.js +1 -1
  51. package/lib/cli/setup-utility-help-after.js +4 -1
  52. package/lib/cli/setup-utility-resolve.js +8 -5
  53. package/lib/commands/app-logs.js +54 -26
  54. package/lib/commands/app.js +41 -0
  55. package/lib/commands/auth-status-dataplane-version.js +97 -61
  56. package/lib/commands/auth-status.js +2 -7
  57. package/lib/commands/channel.js +58 -0
  58. package/lib/commands/datasource.js +1 -1
  59. package/lib/commands/identity-apply-sync.js +49 -8
  60. package/lib/commands/identity-group-cli.js +66 -26
  61. package/lib/commands/identity-membership-role-cache.js +58 -37
  62. package/lib/commands/identity-shared.js +22 -1
  63. package/lib/commands/identity-user-cli.js +80 -34
  64. package/lib/commands/repair-auth-apply.js +30 -4
  65. package/lib/commands/repair-auth-config.js +8 -13
  66. package/lib/commands/repair-datasource-com-v2.js +15 -0
  67. package/lib/commands/repair-datasource-service-contract.js +206 -0
  68. package/lib/commands/repair-datasource.js +53 -18
  69. package/lib/commands/repair-env-template.js +12 -12
  70. package/lib/commands/repair-openapi-sync.js +26 -4
  71. package/lib/commands/repair-steps.js +28 -13
  72. package/lib/commands/repair-system-alignment.js +7 -4
  73. package/lib/commands/resource-type.js +371 -0
  74. package/lib/commands/setup-modes.js +1 -1
  75. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  76. package/lib/commands/test-trust-external.js +39 -90
  77. package/lib/commands/up-common.js +33 -1
  78. package/lib/commands/up-dataplane.js +8 -4
  79. package/lib/commands/upload.js +24 -3
  80. package/lib/commands/verify-trust-command-action.js +4 -1
  81. package/lib/commands/verify-trust-external.js +12 -20
  82. package/lib/core/config-setup-platform-mode.js +3 -3
  83. package/lib/core/setup-platform-mode.js +41 -6
  84. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  85. package/lib/datasource/capability/capability-manifest.js +254 -0
  86. package/lib/datasource/capability/capability-resolve.js +3 -4
  87. package/lib/datasource/capability/copy-operations.js +5 -3
  88. package/lib/datasource/capability/remove-operations.js +2 -1
  89. package/lib/datasource/capability/run-capability-copy.js +32 -4
  90. package/lib/datasource/capability/run-capability-diff.js +3 -3
  91. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  92. package/lib/datasource/capability/run-capability-edit.js +4 -7
  93. package/lib/datasource/capability/run-capability-relate.js +2 -4
  94. package/lib/datasource/capability/run-capability-remove.js +2 -4
  95. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  96. package/lib/datasource/datasource-identifier.js +2 -1
  97. package/lib/datasource/datasource-validate-display.js +1 -1
  98. package/lib/datasource/field-reference-validator.js +24 -3
  99. package/lib/datasource/orphan-profiles-validator.js +98 -0
  100. package/lib/datasource/validate.js +41 -23
  101. package/lib/external-system/download-helpers.js +87 -0
  102. package/lib/external-system/download.js +61 -122
  103. package/lib/external-system/generator.js +1 -59
  104. package/lib/external-system/test.js +10 -15
  105. package/lib/generator/builders.js +11 -1
  106. package/lib/generator/external-controller-manifest.js +5 -2
  107. package/lib/generator/split.js +3 -1
  108. package/lib/generator/wizard.js +1 -28
  109. package/lib/identity/fk-reference.js +65 -0
  110. package/lib/identity/identity-apply-core.js +13 -58
  111. package/lib/identity/identity-apply-phases.js +5 -1
  112. package/lib/identity/identity-display.js +243 -0
  113. package/lib/identity/identity-response-helpers.js +52 -0
  114. package/lib/identity/identity-sync-errors.js +62 -0
  115. package/lib/identity/resolve-user-id.js +129 -0
  116. package/lib/identity/username-default.js +31 -0
  117. package/lib/lifecycle/certification-progress.js +2 -2
  118. package/lib/lifecycle/report-display-trust.js +70 -0
  119. package/lib/lifecycle/report-display.js +14 -13
  120. package/lib/resolvers/datasource-resolver.js +2 -3
  121. package/lib/resolvers/resource-type-file.js +159 -0
  122. package/lib/schema/application-schema.json +16 -0
  123. package/lib/schema/deployment-rules.yaml +2 -1
  124. package/lib/schema/external-datasource.schema.json +372 -58
  125. package/lib/schema/external-system.schema.json +331 -2
  126. package/lib/schema/type/document-storage.json +9 -5
  127. package/lib/utils/app-run-containers.js +25 -2
  128. package/lib/utils/authentication-shape.js +224 -0
  129. package/lib/utils/build-copy.js +36 -5
  130. package/lib/utils/build-resolve-image.js +66 -12
  131. package/lib/utils/celery-companion-apps.js +53 -0
  132. package/lib/utils/cli-utils.js +4 -0
  133. package/lib/utils/compose-celery-worker.js +23 -0
  134. package/lib/utils/compose-generate-docker-compose.js +9 -1
  135. package/lib/utils/configuration-env-resolver.js +36 -0
  136. package/lib/utils/credential-secrets-env-kv.js +216 -0
  137. package/lib/utils/credential-secrets-env.js +52 -262
  138. package/lib/utils/dataplane-resolver.js +4 -115
  139. package/lib/utils/declarative-url-ports.js +24 -1
  140. package/lib/utils/docker-build.js +1 -4
  141. package/lib/utils/external-env-template.js +10 -3
  142. package/lib/utils/external-system-validators.js +8 -2
  143. package/lib/utils/help-builder.js +2 -0
  144. package/lib/utils/paths.js +25 -0
  145. package/lib/utils/platform-controller-url.js +2 -1
  146. package/lib/utils/platform-kv-ref.js +57 -0
  147. package/lib/utils/runtime-configuration-download.js +189 -0
  148. package/lib/utils/runtime-configuration-values.js +119 -0
  149. package/lib/utils/schema-loader.js +19 -4
  150. package/lib/utils/worker-sidecars.js +142 -0
  151. package/lib/validation/external-manifest-validator.js +2 -0
  152. package/lib/validation/runtime-configuration-rules.js +98 -0
  153. package/lib/validation/validate-display-dimensions.js +154 -0
  154. package/lib/validation/validate-display-sections.js +276 -0
  155. package/lib/validation/validate-display-steps.js +67 -0
  156. package/lib/validation/validate-display.js +24 -402
  157. package/package.json +1 -1
  158. package/templates/applications/dataplane/application.yaml +5 -0
  159. package/templates/applications/dataplane/env.template +26 -13
  160. package/templates/applications/dataplane/rbac.yaml +108 -1
  161. package/templates/applications/miso-controller/application.yaml +2 -2
  162. package/templates/applications/miso-controller/env.template +37 -122
  163. package/templates/applications/miso-controller/rbac.yaml +28 -13
  164. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  165. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  166. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  167. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  168. package/templates/channels/slack/install.template.md.hbs +49 -0
  169. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  170. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  171. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  172. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  173. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  174. package/templates/python/docker-compose.hbs +56 -0
  175. package/templates/typescript/docker-compose.hbs +52 -0
@@ -7,7 +7,7 @@
7
7
  'use strict';
8
8
 
9
9
  const logger = require('../utils/logger');
10
- const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
10
+ const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
11
11
  const {
12
12
  resolveControllerAndAuth,
13
13
  requireNonEmpty,
@@ -15,11 +15,26 @@ const {
15
15
  throwIfApiFailed
16
16
  } = require('./identity-shared');
17
17
  const { listGroups, getGroup, createGroup, listGroupMembers } = require('../api/groups.api');
18
+ const {
19
+ extractPaginatedList,
20
+ printGroupMembersTable,
21
+ printGroupRecord,
22
+ printIdentityEmpty,
23
+ printIdentityListHeader,
24
+ printIdentityNotFound,
25
+ printIdentityTable,
26
+ printListPaginationFooter,
27
+ GROUP_LIST_COLUMNS
28
+ } = require('../identity/identity-display');
18
29
 
19
30
  function printJson(data) {
20
31
  logger.log(JSON.stringify(data, null, 2));
21
32
  }
22
33
 
34
+ function groupNotFoundHints() {
35
+ return ['af identity group list --search <text>', 'Use group name or id from group list'];
36
+ }
37
+
23
38
  function registerGroupCreate(groupCmd) {
24
39
  groupCmd
25
40
  .command('create')
@@ -51,7 +66,7 @@ function registerGroupList(groupCmd) {
51
66
  .description('List groups')
52
67
  .option('--page <n>', 'Page', (v) => parseInt(v, 10))
53
68
  .option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
54
- .option('--search <text>', 'Search groups')
69
+ .option('--search <text>', 'Search groups by name or display name')
55
70
  .option('--json', 'JSON output')
56
71
  .action(async(options) => {
57
72
  try {
@@ -62,14 +77,24 @@ function registerGroupList(groupCmd) {
62
77
  search: options.search
63
78
  });
64
79
  throwIfApiFailed(res);
65
- const data = unwrapControllerData(res);
66
80
  if (options.json) {
67
- printJson(data);
81
+ printJson(res.data ?? res);
68
82
  return;
69
83
  }
70
- const items = Array.isArray(data) ? data : data?.data || [];
71
- items.forEach((g) => logger.log(` ${g.id} ${g.name}`));
72
- logger.log('');
84
+ const { items, meta } = extractPaginatedList(res);
85
+ printIdentityListHeader({
86
+ emoji: '👥',
87
+ title: 'Groups',
88
+ context: controllerUrl,
89
+ shown: items.length,
90
+ total: meta?.totalItems ?? items.length
91
+ });
92
+ if (items.length === 0) {
93
+ printIdentityEmpty('No groups found.', ['Try --search']);
94
+ return;
95
+ }
96
+ printIdentityTable(GROUP_LIST_COLUMNS, items);
97
+ printListPaginationFooter(meta);
73
98
  } catch (e) {
74
99
  logger.error(formatBlockingError(e.message));
75
100
  process.exit(1);
@@ -79,22 +104,28 @@ function registerGroupList(groupCmd) {
79
104
 
80
105
  function registerGroupGet(groupCmd) {
81
106
  groupCmd
82
- .command('get <idOrName>')
107
+ .command('get <groupIdOrName>')
83
108
  .description('Get group by id or name')
84
109
  .option('--json', 'JSON output')
85
- .action(async(idOrName, options) => {
110
+ .action(async(groupIdOrName, options) => {
86
111
  try {
87
112
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
88
- const res = await getGroup(controllerUrl, authConfig, requireNonEmpty(idOrName, 'idOrName'));
89
- throwIfApiFailed(res);
113
+ const value = requireNonEmpty(groupIdOrName, 'groupIdOrName');
114
+ const res = await getGroup(controllerUrl, authConfig, value);
115
+ if (!res || res.success === false) {
116
+ printIdentityNotFound('Group', value, groupNotFoundHints());
117
+ process.exit(1);
118
+ }
90
119
  const row = unwrapControllerData(res);
120
+ if (!row?.id) {
121
+ printIdentityNotFound('Group', value, groupNotFoundHints());
122
+ process.exit(1);
123
+ }
91
124
  if (options.json) {
92
125
  printJson(row);
93
126
  return;
94
127
  }
95
- logger.log(headerKeyValue('Id:', row.id));
96
- logger.log(headerKeyValue('Name:', row.name));
97
- logger.log('');
128
+ printGroupRecord(row);
98
129
  } catch (e) {
99
130
  logger.error(formatBlockingError(e.message));
100
131
  process.exit(1);
@@ -104,26 +135,35 @@ function registerGroupGet(groupCmd) {
104
135
 
105
136
  function registerGroupMembers(groupCmd) {
106
137
  groupCmd
107
- .command('members <idOrName>')
138
+ .command('members <groupIdOrName>')
108
139
  .description('List members of a group')
109
140
  .option('--json', 'JSON output')
110
- .action(async(idOrName, options) => {
141
+ .action(async(groupIdOrName, options) => {
111
142
  try {
112
143
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
113
- const res = await listGroupMembers(
114
- controllerUrl,
115
- authConfig,
116
- requireNonEmpty(idOrName, 'idOrName')
117
- );
118
- throwIfApiFailed(res);
144
+ const value = requireNonEmpty(groupIdOrName, 'groupIdOrName');
145
+ const res = await listGroupMembers(controllerUrl, authConfig, value);
146
+ if (!res || res.success === false) {
147
+ printIdentityNotFound('Group', value, groupNotFoundHints());
148
+ process.exit(1);
149
+ }
119
150
  const data = unwrapControllerData(res);
151
+ const items = Array.isArray(data) ? data : data?.data || [];
120
152
  if (options.json) {
121
- printJson(data);
153
+ printJson(items);
122
154
  return;
123
155
  }
124
- const items = Array.isArray(data) ? data : data?.data || [];
125
- items.forEach((m) => logger.log(` ${m.userId || m.id} ${m.email || ''}`));
126
- logger.log('');
156
+ printIdentityListHeader({
157
+ emoji: '👤',
158
+ title: `Members of ${value}`,
159
+ shown: items.length,
160
+ total: items.length
161
+ });
162
+ if (items.length === 0) {
163
+ printIdentityEmpty('No members in this group.');
164
+ return;
165
+ }
166
+ printGroupMembersTable(items);
127
167
  } catch (e) {
128
168
  logger.error(formatBlockingError(e.message));
129
169
  process.exit(1);
@@ -19,30 +19,43 @@ const {
19
19
  const { addUserToGroup, removeUserFromGroup } = require('../api/user-groups.api');
20
20
  const { listEnvironmentRoles, updateRoleGroups } = require('../api/environments.api');
21
21
  const { clearAuthCache, invalidateAuthCache } = require('../api/auth-cache.api');
22
+ const {
23
+ printIdentityEmpty,
24
+ printIdentityListHeader,
25
+ printIdentityTable
26
+ } = require('../identity/identity-display');
22
27
 
23
28
  function printJson(data) {
24
29
  logger.log(JSON.stringify(data, null, 2));
25
30
  }
26
31
 
32
+ const ROLE_LIST_COLUMNS = [
33
+ { label: 'Role', width: 36, value: (r) => r.value || r.role || r.name || '—' },
34
+ {
35
+ label: 'Groups',
36
+ width: 44,
37
+ value: (r) => {
38
+ const groups = Array.isArray(r.groups) ? r.groups.filter(Boolean) : [];
39
+ return groups.length > 0 ? groups.join(', ') : '—';
40
+ }
41
+ }
42
+ ];
43
+
27
44
  /**
28
45
  * @param {import('commander').Command} memCmd
29
46
  */
30
47
  function setupIdentityMembershipCommands(memCmd) {
31
48
  memCmd
32
- .command('add <userId> <groupId>')
33
- .description('Add user to group (controller ids)')
34
- .action(async(userId, groupId) => {
49
+ .command('add <userIdOrEmail> <groupIdOrName>')
50
+ .description('Add user to group (id, email, username + group id or name)')
51
+ .action(async(userIdOrEmail, groupIdOrName) => {
35
52
  try {
36
53
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
37
- const res = await addUserToGroup(
38
- controllerUrl,
39
- authConfig,
40
- requireNonEmpty(userId, 'userId'),
41
- requireNonEmpty(groupId, 'groupId'),
42
- {}
43
- );
54
+ const userRef = requireNonEmpty(userIdOrEmail, 'userIdOrEmail');
55
+ const groupRef = requireNonEmpty(groupIdOrName, 'groupIdOrName');
56
+ const res = await addUserToGroup(controllerUrl, authConfig, userRef, groupRef, {});
44
57
  throwIfApiFailed(res);
45
- logger.log(formatSuccessLine(`Membership created: ${userId} → ${groupId}`));
58
+ logger.log(formatSuccessLine(`Membership created: ${userRef} → ${groupRef}`));
46
59
  } catch (e) {
47
60
  logger.error(formatBlockingError(e.message));
48
61
  process.exit(1);
@@ -50,19 +63,16 @@ function setupIdentityMembershipCommands(memCmd) {
50
63
  });
51
64
 
52
65
  memCmd
53
- .command('remove <userId> <groupId>')
66
+ .command('remove <userIdOrEmail> <groupIdOrName>')
54
67
  .description('Remove user from group')
55
- .action(async(userId, groupId) => {
68
+ .action(async(userIdOrEmail, groupIdOrName) => {
56
69
  try {
57
70
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
58
- const res = await removeUserFromGroup(
59
- controllerUrl,
60
- authConfig,
61
- requireNonEmpty(userId, 'userId'),
62
- requireNonEmpty(groupId, 'groupId')
63
- );
71
+ const userRef = requireNonEmpty(userIdOrEmail, 'userIdOrEmail');
72
+ const groupRef = requireNonEmpty(groupIdOrName, 'groupIdOrName');
73
+ const res = await removeUserFromGroup(controllerUrl, authConfig, userRef, groupRef);
64
74
  throwIfApiFailed(res);
65
- logger.log(formatSuccessLine(`Membership removed: ${userId} → ${groupId}`));
75
+ logger.log(formatSuccessLine(`Membership removed: ${userRef} → ${groupRef}`));
66
76
  } catch (e) {
67
77
  logger.error(formatBlockingError(e.message));
68
78
  process.exit(1);
@@ -73,29 +83,40 @@ function setupIdentityMembershipCommands(memCmd) {
73
83
  /**
74
84
  * @param {import('commander').Command} roleCmd
75
85
  */
86
+ async function handleRoleListAction(options) {
87
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth();
88
+ const envKey = await resolveEnvKey(options);
89
+ const res = await listEnvironmentRoles(controllerUrl, envKey, authConfig);
90
+ throwIfApiFailed(res);
91
+ const data = unwrapControllerData(res);
92
+ if (options.json) {
93
+ printJson(data);
94
+ return;
95
+ }
96
+ const items = Array.isArray(data) ? data : data?.data || data?.roles || [];
97
+ printIdentityListHeader({
98
+ emoji: '🔐',
99
+ title: 'Role → group mappings',
100
+ context: `environment: ${envKey}`,
101
+ shown: (items || []).length,
102
+ total: (items || []).length
103
+ });
104
+ if (!items || items.length === 0) {
105
+ printIdentityEmpty('No roles found for this environment.');
106
+ return;
107
+ }
108
+ printIdentityTable(ROLE_LIST_COLUMNS, items);
109
+ }
110
+
76
111
  function setupIdentityRoleCommands(roleCmd) {
77
112
  roleCmd
78
113
  .command('list')
79
114
  .description('List roles and group mappings for an environment')
80
- .requiredOption('-e, --env <key>', 'Environment key (dev, tst, pro)')
115
+ .option('-e, --env <key>', 'Environment key (defaults from aifabrix config)')
81
116
  .option('--json', 'JSON output')
82
117
  .action(async(options) => {
83
118
  try {
84
- const { controllerUrl, authConfig } = await resolveControllerAndAuth();
85
- const envKey = await resolveEnvKey(options);
86
- const res = await listEnvironmentRoles(controllerUrl, envKey, authConfig);
87
- throwIfApiFailed(res);
88
- const data = unwrapControllerData(res);
89
- if (options.json) {
90
- printJson(data);
91
- return;
92
- }
93
- const items = Array.isArray(data) ? data : data?.data || data?.roles || [];
94
- (items || []).forEach((r) => {
95
- const groups = Array.isArray(r.groups) ? r.groups.join(', ') : '';
96
- logger.log(` ${r.value || r.role || r.name} → ${groups || '—'}`);
97
- });
98
- logger.log('');
119
+ await handleRoleListAction(options);
99
120
  } catch (e) {
100
121
  logger.error(formatBlockingError(e.message));
101
122
  process.exit(1);
@@ -105,7 +126,7 @@ function setupIdentityRoleCommands(roleCmd) {
105
126
  roleCmd
106
127
  .command('set-groups <roleValue>')
107
128
  .description('Map role to groups in an environment')
108
- .requiredOption('-e, --env <key>', 'Environment key')
129
+ .option('-e, --env <key>', 'Environment key (defaults from aifabrix config)')
109
130
  .requiredOption('--groups <names>', 'Comma-separated group names')
110
131
  .action(async(roleValue, options) => {
111
132
  try {
@@ -6,6 +6,9 @@
6
6
 
7
7
  'use strict';
8
8
 
9
+ const chalk = require('chalk');
10
+ const logger = require('../utils/logger');
11
+ const { formatBlockingError } = require('../utils/cli-layout-chalk');
9
12
  const { resolveControllerUrl } = require('../utils/controller-url');
10
13
  const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
11
14
  const { getOrRefreshDeviceToken } = require('../utils/token-manager');
@@ -82,6 +85,23 @@ function parseGroupsList(groupsArg) {
82
85
  return parts;
83
86
  }
84
87
 
88
+ /**
89
+ * Print a blocking CLI error with optional multi-line guidance in gray.
90
+ *
91
+ * @param {string} message
92
+ * @returns {void}
93
+ */
94
+ function logIdentityCliError(message) {
95
+ const lines = String(message || '').split('\n');
96
+ const head = lines[0] || 'Request failed';
97
+ logger.error(formatBlockingError(head));
98
+ lines.slice(1).forEach((line) => {
99
+ if (line.trim()) {
100
+ logger.log(chalk.gray(line));
101
+ }
102
+ });
103
+ }
104
+
85
105
  module.exports = {
86
106
  resolveControllerAndAuth,
87
107
  resolveEnvKey,
@@ -90,5 +110,6 @@ module.exports = {
90
110
  unwrapControllerData,
91
111
  throwIfApiFailed,
92
112
  assertSyncStatsOk,
93
- apiErrorMessage
113
+ apiErrorMessage,
114
+ logIdentityCliError
94
115
  };
@@ -6,9 +6,8 @@
6
6
 
7
7
  'use strict';
8
8
 
9
- const chalk = require('chalk');
10
9
  const logger = require('../utils/logger');
11
- const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
10
+ const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
12
11
  const {
13
12
  resolveControllerAndAuth,
14
13
  requireNonEmpty,
@@ -21,11 +20,27 @@ const {
21
20
  createUser,
22
21
  listUserGroups
23
22
  } = require('../api/users.api');
23
+ const {
24
+ extractPaginatedList,
25
+ printIdentityEmpty,
26
+ printIdentityListHeader,
27
+ printIdentityNotFound,
28
+ printIdentityTable,
29
+ printListPaginationFooter,
30
+ printUserGroupsTable,
31
+ printUserRecord,
32
+ USER_LIST_COLUMNS
33
+ } = require('../identity/identity-display');
34
+ const { resolveIdentityUsername } = require('../identity/username-default');
24
35
 
25
36
  function printJson(data) {
26
37
  logger.log(JSON.stringify(data, null, 2));
27
38
  }
28
39
 
40
+ function userNotFoundHints() {
41
+ return ['af identity user list --search <text>', 'Check email, username, or id spelling'];
42
+ }
43
+
29
44
  function registerUserCreate(userCmd) {
30
45
  userCmd
31
46
  .command('create')
@@ -38,12 +53,13 @@ function registerUserCreate(userCmd) {
38
53
  .action(async(options) => {
39
54
  try {
40
55
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
56
+ const email = requireNonEmpty(options.email, 'email');
41
57
  const res = await createUser(controllerUrl, authConfig, {
42
- email: requireNonEmpty(options.email, 'email'),
58
+ email,
43
59
  firstName: options.firstName,
44
60
  lastName: options.lastName,
45
61
  displayName: options.displayName,
46
- username: options.username,
62
+ username: resolveIdentityUsername({ username: options.username, email }),
47
63
  status: 'active'
48
64
  });
49
65
  throwIfApiFailed(res);
@@ -62,26 +78,41 @@ function registerUserList(userCmd) {
62
78
  .description('List users')
63
79
  .option('--page <n>', 'Page', (v) => parseInt(v, 10))
64
80
  .option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
65
- .option('--search <text>', 'Search users')
81
+ .option('--search <text>', 'Search users (email, username, name)')
82
+ .option('--email <email>', 'Exact email filter')
66
83
  .option('--json', 'JSON output')
67
84
  .action(async(options) => {
68
85
  try {
69
86
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
70
- const res = await listUsers(controllerUrl, authConfig, {
87
+ const params = {
71
88
  page: options.page,
72
89
  pageSize: options.pageSize,
73
90
  search: options.search
74
- });
91
+ };
92
+ if (options.email) {
93
+ params.filter = `email:eq:${options.email}`;
94
+ delete params.search;
95
+ }
96
+ const res = await listUsers(controllerUrl, authConfig, params);
75
97
  throwIfApiFailed(res);
76
- const data = unwrapControllerData(res);
77
98
  if (options.json) {
78
- printJson(data);
99
+ printJson(res.data ?? res);
79
100
  return;
80
101
  }
81
- const items = Array.isArray(data) ? data : data?.data || [];
82
- logger.log(chalk.bold(`\nUsers (${items.length} shown):\n`));
83
- items.forEach((u) => logger.log(` ${u.id} ${u.email}`));
84
- logger.log('');
102
+ const { items, meta } = extractPaginatedList(res);
103
+ printIdentityListHeader({
104
+ emoji: '👤',
105
+ title: 'Users',
106
+ context: controllerUrl,
107
+ shown: items.length,
108
+ total: meta?.totalItems ?? items.length
109
+ });
110
+ if (items.length === 0) {
111
+ printIdentityEmpty('No users found.', ['Try --search or --email']);
112
+ return;
113
+ }
114
+ printIdentityTable(USER_LIST_COLUMNS, items);
115
+ printListPaginationFooter(meta);
85
116
  } catch (e) {
86
117
  logger.error(formatBlockingError(e.message));
87
118
  process.exit(1);
@@ -91,22 +122,28 @@ function registerUserList(userCmd) {
91
122
 
92
123
  function registerUserGet(userCmd) {
93
124
  userCmd
94
- .command('get <idOrEmail>')
95
- .description('Get user by id or email')
125
+ .command('get <userIdOrEmail>')
126
+ .description('Get user by id, email, or username')
96
127
  .option('--json', 'JSON output')
97
- .action(async(idOrEmail, options) => {
128
+ .action(async(userIdOrEmail, options) => {
98
129
  try {
99
130
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
100
- const res = await getUser(controllerUrl, authConfig, requireNonEmpty(idOrEmail, 'idOrEmail'));
101
- throwIfApiFailed(res);
131
+ const value = requireNonEmpty(userIdOrEmail, 'userIdOrEmail');
132
+ const res = await getUser(controllerUrl, authConfig, value);
133
+ if (!res || res.success === false) {
134
+ printIdentityNotFound('User', value, userNotFoundHints());
135
+ process.exit(1);
136
+ }
102
137
  const row = unwrapControllerData(res);
138
+ if (!row?.id) {
139
+ printIdentityNotFound('User', value, userNotFoundHints());
140
+ process.exit(1);
141
+ }
103
142
  if (options.json) {
104
143
  printJson(row);
105
144
  return;
106
145
  }
107
- logger.log(headerKeyValue('Id:', row.id));
108
- logger.log(headerKeyValue('Email:', row.email));
109
- logger.log('');
146
+ printUserRecord(row);
110
147
  } catch (e) {
111
148
  logger.error(formatBlockingError(e.message));
112
149
  process.exit(1);
@@ -116,26 +153,35 @@ function registerUserGet(userCmd) {
116
153
 
117
154
  function registerUserGroups(userCmd) {
118
155
  userCmd
119
- .command('groups <idOrEmail>')
120
- .description('List groups for a user')
156
+ .command('groups <userIdOrEmail>')
157
+ .description('List groups for a user (by id, email, or username)')
121
158
  .option('--json', 'JSON output')
122
- .action(async(idOrEmail, options) => {
159
+ .action(async(userIdOrEmail, options) => {
123
160
  try {
124
161
  const { controllerUrl, authConfig } = await resolveControllerAndAuth();
125
- const res = await listUserGroups(
126
- controllerUrl,
127
- authConfig,
128
- requireNonEmpty(idOrEmail, 'idOrEmail')
129
- );
130
- throwIfApiFailed(res);
162
+ const value = requireNonEmpty(userIdOrEmail, 'userIdOrEmail');
163
+ const res = await listUserGroups(controllerUrl, authConfig, value);
164
+ if (!res || res.success === false) {
165
+ printIdentityNotFound('User', value, userNotFoundHints());
166
+ process.exit(1);
167
+ }
131
168
  const data = unwrapControllerData(res);
169
+ const items = Array.isArray(data) ? data : data?.data || [];
132
170
  if (options.json) {
133
- printJson(data);
171
+ printJson(items);
134
172
  return;
135
173
  }
136
- const items = Array.isArray(data) ? data : data?.data || [];
137
- items.forEach((g) => logger.log(` ${g.id || g.groupId} ${g.name || ''}`));
138
- logger.log('');
174
+ printIdentityListHeader({
175
+ emoji: '👥',
176
+ title: `Groups for ${value}`,
177
+ shown: items.length,
178
+ total: items.length
179
+ });
180
+ if (items.length === 0) {
181
+ printIdentityEmpty('No group memberships found.');
182
+ return;
183
+ }
184
+ printUserGroupsTable(items);
139
185
  } catch (e) {
140
186
  logger.error(formatBlockingError(e.message));
141
187
  process.exit(1);
@@ -10,7 +10,11 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const { buildAuthenticationFromMethod } = require('../external-system/generator');
13
+ const {
14
+ buildAuthenticationFromMethod,
15
+ getAuthCredentialTemplate,
16
+ migrateAuthenticationToNestedSchema
17
+ } = require('../utils/authentication-shape');
14
18
 
15
19
  /** Repair CLI --auth values (normalized lowercase). */
16
20
  const ALLOWED_REPAIR_AUTH = [
@@ -95,9 +99,10 @@ const MISSING_TEST_ENDPOINT_WARNING =
95
99
  * @param {string[]} warnings - Mutable warnings list
96
100
  */
97
101
  function appendTestEndpointWarningIfMissing(authentication, warnings) {
98
- const method = String(authentication?.method || '').toLowerCase();
102
+ const credential = getAuthCredentialTemplate(authentication);
103
+ const method = String(credential?.method || authentication?.method || '').toLowerCase();
99
104
  if (method !== 'apikey' && method !== 'bearertoken') return;
100
- const testEndpoint = String(authentication?.variables?.testEndpoint || '').trim();
105
+ const testEndpoint = String(credential?.variables?.testEndpoint || authentication?.variables?.testEndpoint || '').trim();
101
106
  if (testEndpoint) return;
102
107
  if (warnings.includes(MISSING_TEST_ENDPOINT_WARNING)) return;
103
108
  warnings.push(MISSING_TEST_ENDPOINT_WARNING);
@@ -114,6 +119,25 @@ function auditRepairAuthenticationWarnings(systemParsed, warnings) {
114
119
  appendTestEndpointWarningIfMissing(authentication, warnings);
115
120
  }
116
121
 
122
+ /**
123
+ * Ensures system file authentication matches nested schema (authType + credential).
124
+ * @param {Object} systemParsed - Parsed system config (mutated)
125
+ * @param {string} systemKey
126
+ * @param {string[]} changes
127
+ * @returns {boolean}
128
+ */
129
+ function normalizeAuthenticationSchemaShape(systemParsed, systemKey, changes) {
130
+ const current = systemParsed.authentication || systemParsed.auth;
131
+ const { authentication, changed } = migrateAuthenticationToNestedSchema(current, systemKey);
132
+ if (!changed) {
133
+ return false;
134
+ }
135
+ systemParsed.authentication = authentication;
136
+ delete systemParsed.auth;
137
+ changes.push('Normalized authentication to authType + credential (schema 2.4.x)');
138
+ return true;
139
+ }
140
+
117
141
  module.exports = {
118
142
  ALLOWED_REPAIR_AUTH,
119
143
  MISSING_TEST_ENDPOINT_WARNING,
@@ -123,5 +147,7 @@ module.exports = {
123
147
  mergePreservedAuthVariables,
124
148
  repairAuthChangeLabel,
125
149
  appendTestEndpointWarningIfMissing,
126
- auditRepairAuthenticationWarnings
150
+ auditRepairAuthenticationWarnings,
151
+ normalizeAuthenticationSchemaShape,
152
+ getAuthCredentialTemplate
127
153
  };
@@ -8,17 +8,7 @@
8
8
  'use strict';
9
9
 
10
10
  const { systemKeyToKvPrefix, securityKeyToVar, kvEnvKeyToPath } = require('../utils/credential-secrets-env');
11
-
12
- /**
13
- * Returns true if a kv value looks like legacy format (KeyVault suffix or no path segments).
14
- * @param {string} val - Value from authentication.security or configuration
15
- * @returns {boolean}
16
- */
17
- function isLegacyKvValue(val) {
18
- if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return false;
19
- const after = val.trim().slice(5); // after 'kv://'
20
- return after.includes('KeyVault') || !after.includes('/');
21
- }
11
+ const { isLegacyKvValue } = require('../utils/platform-kv-ref');
22
12
 
23
13
  /**
24
14
  * Normalizes authentication.security keyvault entries to path-style kv:// values (kv://systemKey/variable).
@@ -87,7 +77,9 @@ function normalizeConfigurationSection(config, prefix, systemKey, changes) {
87
77
  function normalizeSystemFileAuthAndConfig(systemParsed, systemKey, changes) {
88
78
  const prefix = systemKeyToKvPrefix(systemKey);
89
79
  if (!prefix) return false;
90
- const security = systemParsed.authentication?.security;
80
+ const { getAuthCredentialTemplate } = require('../utils/authentication-shape');
81
+ const credential = getAuthCredentialTemplate(systemParsed.authentication);
82
+ const security = credential?.security ?? systemParsed.authentication?.security;
91
83
  let updated = (security && typeof security === 'object' && normalizeSecuritySection(security, prefix, systemKey, changes));
92
84
  const config = systemParsed.configuration;
93
85
  if (Array.isArray(config)) {
@@ -96,4 +88,7 @@ function normalizeSystemFileAuthAndConfig(systemParsed, systemKey, changes) {
96
88
  return updated;
97
89
  }
98
90
 
99
- module.exports = { normalizeSystemFileAuthAndConfig, isLegacyKvValue };
91
+ module.exports = {
92
+ normalizeSystemFileAuthAndConfig,
93
+ isLegacyKvValue
94
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * COM v2 datasource manifest repair (capabilities objects, strip context).
3
+ *
4
+ * @fileoverview repair-datasource-com-v2
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { repairComV2Manifest } = require('../datasource/capability/capability-manifest');
12
+
13
+ module.exports = {
14
+ repairComV2Manifest
15
+ };