@aifabrix/builder 2.46.0 → 2.47.1

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 (191) 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 +18 -4
  47. package/lib/cli/infra-guided-auth.js +36 -12
  48. package/lib/cli/setup-app.js +54 -3
  49. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  50. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  51. package/lib/cli/setup-npm.js +59 -0
  52. package/lib/cli/setup-platform.js +1 -1
  53. package/lib/cli/setup-secrets.js +1 -1
  54. package/lib/cli/setup-utility-help-after.js +4 -1
  55. package/lib/cli/setup-utility-resolve.js +8 -5
  56. package/lib/commands/app-logs.js +54 -26
  57. package/lib/commands/app.js +41 -0
  58. package/lib/commands/auth-status-dataplane-version.js +97 -61
  59. package/lib/commands/auth-status.js +2 -7
  60. package/lib/commands/channel.js +58 -0
  61. package/lib/commands/datasource.js +1 -1
  62. package/lib/commands/identity-apply-sync.js +49 -8
  63. package/lib/commands/identity-group-cli.js +66 -26
  64. package/lib/commands/identity-membership-role-cache.js +58 -37
  65. package/lib/commands/identity-shared.js +22 -1
  66. package/lib/commands/identity-user-cli.js +80 -34
  67. package/lib/commands/login-device.js +11 -3
  68. package/lib/commands/login.js +8 -3
  69. package/lib/commands/npm-config.js +103 -0
  70. package/lib/commands/repair-auth-apply.js +30 -4
  71. package/lib/commands/repair-auth-config.js +8 -13
  72. package/lib/commands/repair-datasource-com-v2.js +15 -0
  73. package/lib/commands/repair-datasource-service-contract.js +206 -0
  74. package/lib/commands/repair-datasource.js +53 -18
  75. package/lib/commands/repair-env-template.js +12 -12
  76. package/lib/commands/repair-openapi-sync.js +26 -4
  77. package/lib/commands/repair-steps.js +28 -13
  78. package/lib/commands/repair-system-alignment.js +7 -4
  79. package/lib/commands/resource-type.js +371 -0
  80. package/lib/commands/setup-modes.js +1 -1
  81. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  82. package/lib/commands/test-trust-external.js +39 -90
  83. package/lib/commands/up-common.js +33 -1
  84. package/lib/commands/up-dataplane.js +8 -4
  85. package/lib/commands/upload.js +24 -3
  86. package/lib/commands/verify-trust-command-action.js +4 -1
  87. package/lib/commands/verify-trust-external.js +12 -20
  88. package/lib/core/config-setup-platform-mode.js +3 -3
  89. package/lib/core/setup-platform-mode.js +41 -6
  90. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  91. package/lib/datasource/capability/capability-manifest.js +254 -0
  92. package/lib/datasource/capability/capability-resolve.js +3 -4
  93. package/lib/datasource/capability/copy-operations.js +5 -3
  94. package/lib/datasource/capability/remove-operations.js +2 -1
  95. package/lib/datasource/capability/run-capability-copy.js +32 -4
  96. package/lib/datasource/capability/run-capability-diff.js +3 -3
  97. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  98. package/lib/datasource/capability/run-capability-edit.js +4 -7
  99. package/lib/datasource/capability/run-capability-relate.js +2 -4
  100. package/lib/datasource/capability/run-capability-remove.js +2 -4
  101. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  102. package/lib/datasource/datasource-identifier.js +2 -1
  103. package/lib/datasource/datasource-validate-display.js +1 -1
  104. package/lib/datasource/field-reference-validator.js +24 -3
  105. package/lib/datasource/orphan-profiles-validator.js +98 -0
  106. package/lib/datasource/validate.js +41 -23
  107. package/lib/external-system/download-helpers.js +87 -0
  108. package/lib/external-system/download.js +61 -122
  109. package/lib/external-system/generator.js +1 -59
  110. package/lib/external-system/test.js +10 -15
  111. package/lib/generator/builders.js +11 -1
  112. package/lib/generator/external-controller-manifest.js +5 -2
  113. package/lib/generator/split.js +3 -1
  114. package/lib/generator/wizard.js +1 -28
  115. package/lib/identity/fk-reference.js +65 -0
  116. package/lib/identity/identity-apply-core.js +13 -58
  117. package/lib/identity/identity-apply-phases.js +5 -1
  118. package/lib/identity/identity-display.js +243 -0
  119. package/lib/identity/identity-response-helpers.js +52 -0
  120. package/lib/identity/identity-sync-errors.js +62 -0
  121. package/lib/identity/resolve-user-id.js +129 -0
  122. package/lib/identity/username-default.js +31 -0
  123. package/lib/lifecycle/certification-progress.js +2 -2
  124. package/lib/lifecycle/report-display-trust.js +70 -0
  125. package/lib/lifecycle/report-display.js +14 -13
  126. package/lib/resolvers/datasource-resolver.js +2 -3
  127. package/lib/resolvers/resource-type-file.js +159 -0
  128. package/lib/schema/application-schema.json +16 -0
  129. package/lib/schema/deployment-rules.yaml +2 -1
  130. package/lib/schema/external-datasource.schema.json +372 -58
  131. package/lib/schema/external-system.schema.json +331 -2
  132. package/lib/schema/type/document-storage.json +9 -5
  133. package/lib/utils/api.js +2 -1
  134. package/lib/utils/app-run-containers.js +25 -2
  135. package/lib/utils/authentication-shape.js +224 -0
  136. package/lib/utils/build-copy.js +36 -5
  137. package/lib/utils/build-resolve-image.js +66 -12
  138. package/lib/utils/celery-companion-apps.js +53 -0
  139. package/lib/utils/cli-utils.js +4 -0
  140. package/lib/utils/compose-celery-worker.js +23 -0
  141. package/lib/utils/compose-generate-docker-compose.js +9 -1
  142. package/lib/utils/configuration-env-resolver.js +36 -0
  143. package/lib/utils/controller-url.js +4 -1
  144. package/lib/utils/credential-secrets-env-kv.js +216 -0
  145. package/lib/utils/credential-secrets-env.js +52 -262
  146. package/lib/utils/dataplane-resolver.js +4 -115
  147. package/lib/utils/declarative-url-ports.js +24 -1
  148. package/lib/utils/dev-platform-tls.js +148 -0
  149. package/lib/utils/docker-build.js +1 -4
  150. package/lib/utils/external-env-template.js +10 -3
  151. package/lib/utils/external-system-validators.js +8 -2
  152. package/lib/utils/health-check-request.js +114 -0
  153. package/lib/utils/health-check-url.js +3 -1
  154. package/lib/utils/health-check.js +1 -113
  155. package/lib/utils/help-builder.js +3 -0
  156. package/lib/utils/keycloak-realm-ready.js +5 -1
  157. package/lib/utils/localhost-public-host.js +31 -0
  158. package/lib/utils/npm-user-config.js +145 -0
  159. package/lib/utils/paths.js +25 -0
  160. package/lib/utils/platform-controller-url.js +58 -4
  161. package/lib/utils/platform-kv-ref.js +57 -0
  162. package/lib/utils/runtime-configuration-download.js +189 -0
  163. package/lib/utils/runtime-configuration-values.js +119 -0
  164. package/lib/utils/schema-loader.js +19 -4
  165. package/lib/utils/url-declarative-public-base.js +33 -25
  166. package/lib/utils/worker-sidecars.js +142 -0
  167. package/lib/validation/external-manifest-validator.js +2 -0
  168. package/lib/validation/runtime-configuration-rules.js +98 -0
  169. package/lib/validation/validate-display-dimensions.js +154 -0
  170. package/lib/validation/validate-display-sections.js +276 -0
  171. package/lib/validation/validate-display-steps.js +67 -0
  172. package/lib/validation/validate-display.js +24 -402
  173. package/package.json +1 -1
  174. package/templates/applications/dataplane/application.yaml +5 -0
  175. package/templates/applications/dataplane/env.template +26 -13
  176. package/templates/applications/dataplane/rbac.yaml +108 -1
  177. package/templates/applications/miso-controller/application.yaml +2 -2
  178. package/templates/applications/miso-controller/env.template +37 -122
  179. package/templates/applications/miso-controller/rbac.yaml +28 -13
  180. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  181. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  182. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  183. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  184. package/templates/channels/slack/install.template.md.hbs +49 -0
  185. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  186. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  187. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  188. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  189. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  190. package/templates/python/docker-compose.hbs +56 -0
  191. package/templates/typescript/docker-compose.hbs +52 -0
@@ -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);
@@ -81,8 +81,11 @@ async function saveDeviceLoginConfig(controllerUrl, token, refreshToken, expires
81
81
  * @returns {Promise<void>}
82
82
  */
83
83
  async function saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, expiresAt, envKey, opts = {}) {
84
- await saveDeviceLoginConfig(controllerUrl, token, refreshToken, expiresAt);
85
- await setControllerUrl(controllerUrl);
84
+ const storageUrl = opts.tokenControllerUrl
85
+ ? String(opts.tokenControllerUrl).trim().replace(/\/+$/, '')
86
+ : controllerUrl;
87
+ await saveDeviceLoginConfig(storageUrl, token, refreshToken, expiresAt);
88
+ await setControllerUrl(storageUrl);
86
89
  if (envKey) {
87
90
  await setCurrentEnvironment(envKey);
88
91
  }
@@ -219,6 +222,8 @@ function convertDeviceCodeResponse(apiResponse) {
219
222
  device_code: deviceCodeData.deviceCode || deviceCodeData.device_code,
220
223
  user_code: deviceCodeData.userCode || deviceCodeData.user_code,
221
224
  verification_uri: deviceCodeData.verificationUri || deviceCodeData.verification_uri,
225
+ verification_uri_complete:
226
+ deviceCodeData.verificationUriComplete || deviceCodeData.verification_uri_complete,
222
227
  expires_in: deviceCodeData.expiresIn || deviceCodeData.expires_in || 600,
223
228
  interval: deviceCodeData.interval || 5
224
229
  };
@@ -246,8 +251,11 @@ function logDeviceFlowError(deviceError, opts) {
246
251
  }
247
252
 
248
253
  function printCompactDeviceUrl(deviceCodeResponse) {
254
+ const visitUrl =
255
+ deviceCodeResponse.verification_uri_complete ||
256
+ `${deviceCodeResponse.verification_uri}?user_code=${deviceCodeResponse.user_code}`;
249
257
  logger.log('\nOpen browser to authenticate:');
250
- logger.log(chalk.yellow(`${deviceCodeResponse.verification_uri}?user_code=${deviceCodeResponse.user_code}\n`));
258
+ logger.log(chalk.yellow(`${visitUrl}\n`));
251
259
  }
252
260
 
253
261
  /**
@@ -86,8 +86,12 @@ async function normalizeControllerUrl(options) {
86
86
  controllerUrl = await resolveControllerUrl();
87
87
  }
88
88
  controllerUrl = String(controllerUrl).trim().replace(/\/+$/, '');
89
- // Save controller URL to config
90
- await setControllerUrl(controllerUrl);
89
+ if (!options.skipControllerUrlPersist) {
90
+ const persistUrl = options.tokenControllerUrl
91
+ ? String(options.tokenControllerUrl).trim().replace(/\/+$/, '')
92
+ : controllerUrl;
93
+ await setControllerUrl(persistUrl);
94
+ }
91
95
  if (!options.compact) {
92
96
  logger.log(chalk.gray(`Controller URL: ${controllerUrl}`));
93
97
  }
@@ -158,7 +162,8 @@ async function handleCredentialsLoginFlow(controllerUrl, environment, options) {
158
162
  async function handleDeviceCodeLoginFlow(controllerUrl, environment, options) {
159
163
  return await handleDeviceCodeLogin(controllerUrl, environment, options.online, options.scope, {
160
164
  compact: Boolean(options.compact),
161
- exitOnFailure: options.exitOnFailure !== false
165
+ exitOnFailure: options.exitOnFailure !== false,
166
+ tokenControllerUrl: options.tokenControllerUrl
162
167
  });
163
168
  }
164
169
 
@@ -0,0 +1,103 @@
1
+ /**
2
+ * aifabrix npm config — apply user-global npm auth from BASH_NPM_TOKEN.
3
+ *
4
+ * @fileoverview npm config command handler
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { execFile } = require('child_process');
12
+ const { promisify } = require('util');
13
+ const chalk = require('chalk');
14
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
15
+ const logger = require('../utils/logger');
16
+ const secretsLoad = require('../core/secrets-load');
17
+ const { collectBashPrefixedEnv } = require('../utils/bash-secret-env');
18
+ const {
19
+ REGISTRY_URL,
20
+ getUserNpmrcPath,
21
+ writeUserNpmrcFromToken
22
+ } = require('../utils/npm-user-config');
23
+
24
+ const execFileAsync = promisify(execFile);
25
+
26
+ /**
27
+ * Resolve npm token from merged secrets or --from-env.
28
+ *
29
+ * @param {{ fromEnv?: boolean }} options
30
+ * @returns {Promise<string>}
31
+ */
32
+ async function resolveNpmToken(options = {}) {
33
+ if (options.fromEnv) {
34
+ const fromProcess = (process.env.NPM_TOKEN || '').trim();
35
+ if (!fromProcess) {
36
+ throw new Error('NPM_TOKEN is not set in the environment (--from-env)');
37
+ }
38
+ return fromProcess;
39
+ }
40
+ const secrets = await secretsLoad.loadSecrets();
41
+ const bashEnv = collectBashPrefixedEnv(secrets);
42
+ const token = bashEnv.NPM_TOKEN;
43
+ if (!token) {
44
+ throw new Error(
45
+ 'BASH_NPM_TOKEN is not set in merged secrets. Run: aifabrix secret set BASH_NPM_TOKEN "<token>" --shared'
46
+ );
47
+ }
48
+ return token;
49
+ }
50
+
51
+ /**
52
+ * Run npm whoami against registry.npmjs.org using the given token.
53
+ *
54
+ * @param {string} token
55
+ * @returns {Promise<string>} username
56
+ */
57
+ async function npmWhoamiWithToken(token) {
58
+ const { stdout } = await execFileAsync(
59
+ 'npm',
60
+ ['whoami', `--registry=${REGISTRY_URL}`, `--//registry.npmjs.org/:_authToken=${token}`],
61
+ { env: { ...process.env }, timeout: 30000 }
62
+ );
63
+ return String(stdout).trim();
64
+ }
65
+
66
+ /**
67
+ * @param {{ dryRun?: boolean, check?: boolean, fromEnv?: boolean }} options
68
+ * @returns {Promise<{ ok: boolean, npmrcPath?: string, username?: string, dryRun?: boolean }>}
69
+ */
70
+ async function handleNpmConfig(options = {}) {
71
+ const dryRun = Boolean(options.dryRun);
72
+ const checkOnly = Boolean(options.check);
73
+ const token = await resolveNpmToken({ fromEnv: Boolean(options.fromEnv) });
74
+ const npmrcPath = getUserNpmrcPath();
75
+
76
+ if (checkOnly) {
77
+ const username = await npmWhoamiWithToken(token);
78
+ return { ok: true, npmrcPath, username };
79
+ }
80
+
81
+ const result = writeUserNpmrcFromToken(token, { dryRun, npmrcPath });
82
+
83
+ if (dryRun) {
84
+ logger.log(chalk.blue('Dry run — would update user npm config:'));
85
+ logger.log(` path: ${npmrcPath}`);
86
+ for (const key of result.managedKeys) {
87
+ const suffix = key.includes('_authToken') ? '=<redacted>' : '';
88
+ logger.log(` ${key}${suffix}`);
89
+ }
90
+ return { ok: true, npmrcPath, dryRun: true };
91
+ }
92
+
93
+ const username = await npmWhoamiWithToken(token);
94
+ logger.log(formatSuccessLine(`Updated user npm config: ${npmrcPath}`));
95
+ logger.log(chalk.gray(`npm whoami: ${username}`));
96
+ return { ok: true, npmrcPath, username };
97
+ }
98
+
99
+ module.exports = {
100
+ resolveNpmToken,
101
+ npmWhoamiWithToken,
102
+ handleNpmConfig
103
+ };
@@ -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
+ };
@@ -0,0 +1,206 @@
1
+ /**
2
+ * serviceContract repair helpers — migrate deprecated none+llm manifests (439.1).
3
+ *
4
+ * @fileoverview repair-datasource-service-contract
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const LLM_CANONICAL_METADATA_SCHEMA = {
12
+ type: 'object',
13
+ properties: {
14
+ successContent: { type: 'string', nullable: true },
15
+ successEmbedding: {
16
+ type: 'array',
17
+ items: { type: 'number' },
18
+ nullable: true
19
+ },
20
+ successRole: { type: 'string', nullable: true },
21
+ successModel: { type: 'string', nullable: true },
22
+ successFinishReason: { type: 'string', nullable: true },
23
+ errorCode: { type: 'string', nullable: true },
24
+ errorMessage: { type: 'string', nullable: true },
25
+ errorType: { type: 'string', nullable: true },
26
+ usagePromptTokens: { type: 'integer', nullable: true },
27
+ usageCompletionTokens: { type: 'integer', nullable: true },
28
+ usageTotalTokens: { type: 'integer', nullable: true },
29
+ metadataProvider: { type: 'string', nullable: true },
30
+ metadataOperation: { type: 'string', nullable: true }
31
+ }
32
+ };
33
+
34
+ const LLM_CANONICAL_EXPOSED_SCHEMA = {
35
+ success: {
36
+ content: 'metadata.successContent',
37
+ embedding: 'metadata.successEmbedding',
38
+ role: 'metadata.successRole',
39
+ model: 'metadata.successModel',
40
+ finishReason: 'metadata.successFinishReason'
41
+ },
42
+ error: {
43
+ code: 'metadata.errorCode',
44
+ message: 'metadata.errorMessage',
45
+ type: 'metadata.errorType'
46
+ },
47
+ usage: {
48
+ promptTokens: 'metadata.usagePromptTokens',
49
+ completionTokens: 'metadata.usageCompletionTokens',
50
+ totalTokens: 'metadata.usageTotalTokens'
51
+ },
52
+ metadata: {
53
+ provider: 'metadata.metadataProvider',
54
+ operation: 'metadata.metadataOperation'
55
+ }
56
+ };
57
+
58
+ const LLM_STUB_FIELD_MAPPINGS = {
59
+ successContent: { expression: '{{raw}}' },
60
+ metadataProvider: { expression: '{{inline.metadataProvider}}' },
61
+ metadataOperation: { expression: '{{inline.metadataOperation}}' }
62
+ };
63
+
64
+ /**
65
+ * @param {Object} parsed - Parsed datasource manifest
66
+ * @returns {boolean}
67
+ */
68
+ function isDeprecatedNoneLlmManifest(parsed) {
69
+ return parsed?.entityType === 'none' && parsed?.resourceType === 'llm';
70
+ }
71
+
72
+ /**
73
+ * @param {Object} parsed
74
+ * @param {string[]} changes
75
+ * @returns {boolean}
76
+ */
77
+ function stripStorageKeysForServiceContract(parsed, changes) {
78
+ let updated = false;
79
+ for (const key of ['primaryKey', 'labelKey', 'sync']) {
80
+ if (parsed[key] !== undefined) {
81
+ delete parsed[key];
82
+ changes.push(`Removed ${key} (not allowed on serviceContract)`);
83
+ updated = true;
84
+ }
85
+ }
86
+ return updated;
87
+ }
88
+
89
+ /**
90
+ * @param {Object} parsed
91
+ * @param {string} systemKey
92
+ * @param {string[]} changes
93
+ * @returns {boolean}
94
+ */
95
+ function ensureLlmCipMapSteps(parsed, systemKey, changes) {
96
+ const operations = parsed?.execution?.cip?.operations;
97
+ if (!operations || typeof operations !== 'object') return false;
98
+ let updated = false;
99
+ for (const opKey of ['call', 'embed']) {
100
+ const op = operations[opKey];
101
+ if (!op || !Array.isArray(op.steps)) continue;
102
+ if (op.steps.some((step) => step && step.map)) continue;
103
+ const fetchIdx = op.steps.findIndex((step) => step && step.fetch);
104
+ if (fetchIdx < 0) continue;
105
+ const mapStep = {
106
+ map: {
107
+ useFieldMappings: true,
108
+ inputPath: '$',
109
+ inline: {
110
+ metadataProvider: systemKey || parsed.systemKey || 'provider',
111
+ metadataOperation: opKey
112
+ }
113
+ }
114
+ };
115
+ op.steps.splice(fetchIdx + 1, 0, mapStep);
116
+ if (!op.steps.some((step) => step && step.output)) {
117
+ op.steps.push({ output: { mode: 'records' } });
118
+ }
119
+ changes.push(`Inserted map+output steps for CIP operation "${opKey}"`);
120
+ updated = true;
121
+ }
122
+ return updated;
123
+ }
124
+
125
+ /**
126
+ * @param {Object} parsed
127
+ * @param {string[]} changes
128
+ * @returns {boolean}
129
+ */
130
+ function stripDeprecatedServiceCategory(parsed, changes) {
131
+ if (parsed?.serviceCategory === undefined) return false;
132
+ delete parsed.serviceCategory;
133
+ changes.push('Removed deprecated serviceCategory (use resourceType for classification)');
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * @param {*} value
139
+ * @returns {*}
140
+ */
141
+ function deepClone(value) {
142
+ return JSON.parse(JSON.stringify(value));
143
+ }
144
+
145
+ /**
146
+ * @param {Object} parsed
147
+ * @param {string[]} changes
148
+ * @returns {boolean}
149
+ */
150
+ function applyCanonicalLlmSchemaStubs(parsed, changes) {
151
+ parsed.entityType = 'serviceContract';
152
+ changes.push('Migrated entityType none → serviceContract');
153
+
154
+ parsed.metadataSchema = deepClone(LLM_CANONICAL_METADATA_SCHEMA);
155
+ changes.push('Set canonical LLM metadataSchema stub');
156
+
157
+ if (!parsed.exposed) parsed.exposed = {};
158
+ parsed.exposed.schema = deepClone(LLM_CANONICAL_EXPOSED_SCHEMA);
159
+ if (parsed.exposed.attributes !== undefined) {
160
+ delete parsed.exposed.attributes;
161
+ }
162
+ changes.push('Set canonical LLM exposed.schema envelope');
163
+ return true;
164
+ }
165
+
166
+ /**
167
+ * @param {Object} parsed
168
+ * @param {string[]} changes
169
+ * @returns {boolean}
170
+ */
171
+ function applyStubFieldMappingsIfMissing(parsed, changes) {
172
+ const attrs = parsed.fieldMappings?.attributes;
173
+ if (attrs && typeof attrs === 'object' && Object.keys(attrs).length > 0) {
174
+ return false;
175
+ }
176
+ if (!parsed.fieldMappings) parsed.fieldMappings = {};
177
+ parsed.fieldMappings.attributes = deepClone(LLM_STUB_FIELD_MAPPINGS);
178
+ changes.push(
179
+ 'Added stub fieldMappings.attributes — customize provider raw.* expressions'
180
+ );
181
+ return true;
182
+ }
183
+
184
+ /**
185
+ * Migrate deprecated none+llm manifest to serviceContract canonical stub.
186
+ *
187
+ * @param {Object} parsed - Parsed datasource (mutated)
188
+ * @param {string[]} changes - Change log
189
+ * @returns {boolean}
190
+ */
191
+ function repairNoneLlmToServiceContract(parsed, changes) {
192
+ if (!isDeprecatedNoneLlmManifest(parsed)) return false;
193
+ let updated = applyCanonicalLlmSchemaStubs(parsed, changes);
194
+ updated = applyStubFieldMappingsIfMissing(parsed, changes) || updated;
195
+ updated = stripStorageKeysForServiceContract(parsed, changes) || updated;
196
+ updated = ensureLlmCipMapSteps(parsed, parsed.systemKey, changes) || updated;
197
+ return updated;
198
+ }
199
+
200
+ module.exports = {
201
+ LLM_CANONICAL_METADATA_SCHEMA,
202
+ LLM_CANONICAL_EXPOSED_SCHEMA,
203
+ isDeprecatedNoneLlmManifest,
204
+ stripDeprecatedServiceCategory,
205
+ repairNoneLlmToServiceContract
206
+ };