@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
@@ -12,6 +12,12 @@
12
12
  const { repairOpenapiSection } = require('./repair-datasource-openapi');
13
13
  const { normalizeAutoRbacOperationKeys } = require('./repair-datasource-auto-rbac');
14
14
  const { repairTestPayload } = require('./repair-datasource-test-payload');
15
+ const { repairComV2Manifest } = require('./repair-datasource-com-v2');
16
+ const {
17
+ isDeprecatedNoneLlmManifest,
18
+ repairNoneLlmToServiceContract,
19
+ stripDeprecatedServiceCategory
20
+ } = require('./repair-datasource-service-contract');
15
21
 
16
22
  const DEFAULT_SYNC = {
17
23
  mode: 'pull',
@@ -380,6 +386,41 @@ function sanitizeTestPayloadTopLevel(parsed, changes) {
380
386
  return true;
381
387
  }
382
388
 
389
+ /**
390
+ * @param {Object} parsed
391
+ * @param {Object} options
392
+ * @returns {boolean}
393
+ */
394
+ function shouldApplyServiceContractRepair(parsed, options) {
395
+ return (
396
+ isDeprecatedNoneLlmManifest(parsed) ||
397
+ (options.serviceContract && parsed?.resourceType === 'llm')
398
+ );
399
+ }
400
+
401
+ /**
402
+ * @param {Object} parsed
403
+ * @param {Object} options
404
+ * @param {boolean} none
405
+ * @param {string[]} changes
406
+ * @returns {boolean}
407
+ */
408
+ function applyOptionalDatasourceRepairs(parsed, options, none, changes) {
409
+ let updated = false;
410
+ if (options.expose) {
411
+ updated = repairExposeFromAttributes(parsed, changes) || updated;
412
+ }
413
+ if (!none && options.sync) {
414
+ updated = repairSyncSection(parsed, changes) || updated;
415
+ }
416
+ if (options.test) {
417
+ sanitizeTestPayloadTopLevel(parsed, changes);
418
+ updated = repairTestPayload(parsed, changes) || updated;
419
+ updated = sanitizeTestPayloadTopLevel(parsed, changes) || updated;
420
+ }
421
+ return updated;
422
+ }
423
+
383
424
  /**
384
425
  * Runs all requested datasource repairs.
385
426
  *
@@ -390,32 +431,24 @@ function sanitizeTestPayloadTopLevel(parsed, changes) {
390
431
  */
391
432
  function repairDatasourceFile(parsed, options = {}, changes = []) {
392
433
  const out = Array.isArray(changes) ? changes : [];
393
- let updated = false;
394
- const none = isNoneEntityType(parsed?.entityType);
434
+ let updated = stripDeprecatedServiceCategory(parsed, out);
395
435
 
396
- updated = applyBaseDatasourceRepairs(parsed, none, out) || updated;
397
- updated = applyOpenapiDatasourceRepairs(parsed, out) || updated;
398
-
399
- if (options.expose) {
400
- updated = repairExposeFromAttributes(parsed, out) || updated;
436
+ if (shouldApplyServiceContractRepair(parsed, options)) {
437
+ updated = repairNoneLlmToServiceContract(parsed, out) || updated;
401
438
  }
402
439
 
403
- if (!none && options.sync) {
404
- updated = repairSyncSection(parsed, out) || updated;
405
- }
440
+ const none = isNoneEntityType(parsed?.entityType);
406
441
 
407
- if (options.test) {
408
- sanitizeTestPayloadTopLevel(parsed, out);
409
- updated = repairTestPayload(parsed, out) || updated;
410
- updated = sanitizeTestPayloadTopLevel(parsed, out) || updated;
411
- }
442
+ updated = applyBaseDatasourceRepairs(parsed, none, out) || updated;
443
+ updated = applyOpenapiDatasourceRepairs(parsed, out) || updated;
444
+ updated = applyOptionalDatasourceRepairs(parsed, options, none, out) || updated;
412
445
 
413
446
  return { updated, changes: out };
414
447
  }
415
448
 
416
449
  function applyBaseDatasourceRepairs(parsed, none, changes) {
417
- if (none) return false;
418
- let updated = false;
450
+ let updated = repairComV2Manifest(parsed, changes);
451
+ if (none) return updated;
419
452
  updated = repairDimensionBindingShape(parsed, changes) || updated;
420
453
  updated = repairRootDimensionsFromAttributes(parsed, changes) || updated;
421
454
  updated = repairMetadataSchemaFromAttributes(parsed, changes) || updated;
@@ -444,5 +477,7 @@ module.exports = {
444
477
  DEFAULT_SYNC,
445
478
  MINIMAL_METADATA_SCHEMA,
446
479
  TEST_PAYLOAD_TOP_LEVEL_ALLOW,
447
- isNoneEntityType
480
+ isNoneEntityType,
481
+ isDeprecatedNoneLlmManifest,
482
+ repairNoneLlmToServiceContract
448
483
  };
@@ -77,9 +77,16 @@ function addFromConfiguration(effective, config, prefix, seenNames, systemKey) {
77
77
  * @param {string} systemKey - System key for path namespace
78
78
  */
79
79
  function addFromAuthSecurity(effective, systemParsed, prefix, seenNames, systemKey) {
80
- const security = systemParsed.authentication?.security;
80
+ const { getAuthCredentialTemplate } = require('../utils/authentication-shape');
81
+ const { isPlatformCatalogKvRef } = require('../utils/platform-kv-ref');
82
+ const credential = getAuthCredentialTemplate(systemParsed.authentication);
83
+ const security = credential?.security ?? systemParsed.authentication?.security;
81
84
  if (!security || typeof security !== 'object') return;
82
85
  for (const key of Object.keys(security)) {
86
+ const securityVal = security[key];
87
+ if (typeof securityVal === 'string' && isPlatformCatalogKvRef(securityVal)) {
88
+ continue;
89
+ }
83
90
  const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
84
91
  if (seenNames.has(envName)) continue;
85
92
  const pathVal = kvEnvKeyToPath(envName, systemKey);
@@ -316,16 +323,7 @@ function repairEnvTemplate(appPath, systemParsed, systemKey, dryRun, changes, ba
316
323
  return true;
317
324
  }
318
325
 
319
- /**
320
- * Returns true if a kv value looks like legacy format (KeyVault suffix or no path segments).
321
- * @param {string} val - Value from authentication.security or configuration
322
- * @returns {boolean}
323
- */
324
- function isLegacyKvValue(val) {
325
- if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return false;
326
- const after = val.trim().slice(5); // after 'kv://'
327
- return after.includes('KeyVault') || !after.includes('/');
328
- }
326
+ const { isLegacyKvValue } = require('../utils/platform-kv-ref');
329
327
 
330
328
  /**
331
329
  * Normalizes authentication.security values to path-style kv:// (kv://systemKey/variable).
@@ -392,7 +390,9 @@ function normalizeConfigurationSection(config, prefix, systemKey, changes) {
392
390
  function normalizeSystemFileAuthAndConfig(systemParsed, systemKey, changes) {
393
391
  const prefix = systemKeyToKvPrefix(systemKey);
394
392
  if (!prefix) return false;
395
- const security = systemParsed.authentication?.security;
393
+ const { getAuthCredentialTemplate } = require('../utils/authentication-shape');
394
+ const credential = getAuthCredentialTemplate(systemParsed.authentication);
395
+ const security = credential?.security ?? systemParsed.authentication?.security;
396
396
  let updated = (security && typeof security === 'object' && normalizeSecuritySection(security, prefix, systemKey, changes));
397
397
  const config = systemParsed.configuration;
398
398
  if (Array.isArray(config)) {
@@ -15,10 +15,15 @@ const path = require('path');
15
15
  const fs = require('fs');
16
16
 
17
17
  const { resolveControllerUrl } = require('../utils/controller-url');
18
- const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
18
+ const {
19
+ getDeploymentAuth,
20
+ requireBearerForDataplanePipeline,
21
+ applyLabDataplaneApiKeyBearer
22
+ } = require('../utils/token-manager');
19
23
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
20
24
  const { uploadFileAs } = require('../utils/file-upload');
21
25
  const { listOpenAPIFiles } = require('../api/external-systems.api');
26
+ const { resolveLabDataplaneApiKey } = require('../utils/credential-secrets-env');
22
27
 
23
28
  async function fileExistsAsync(filePath) {
24
29
  try {
@@ -29,11 +34,25 @@ async function fileExistsAsync(filePath) {
29
34
  }
30
35
  }
31
36
 
32
- async function resolveDataplaneAndAuth(systemKey) {
37
+ function authForJwtOnlyDataplaneRoutes(authConfig) {
38
+ const copy = { ...(authConfig || {}) };
39
+ if (copy.type === 'client-token' && resolveLabDataplaneApiKey()) {
40
+ return applyLabDataplaneApiKeyBearer(copy);
41
+ }
42
+ return copy;
43
+ }
44
+
45
+ async function resolveDataplaneAndAuth(systemKey, preResolved = {}) {
46
+ if (preResolved.dataplaneUrl && preResolved.authConfig) {
47
+ const authConfig = authForJwtOnlyDataplaneRoutes(preResolved.authConfig);
48
+ return { dataplaneUrl: preResolved.dataplaneUrl, authConfig };
49
+ }
33
50
  const { resolveEnvironment } = require('../core/config');
34
51
  const environment = await resolveEnvironment();
35
52
  const controllerUrl = await resolveControllerUrl();
36
- const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
53
+ const authConfig = authForJwtOnlyDataplaneRoutes(
54
+ await getDeploymentAuth(controllerUrl, environment, systemKey)
55
+ );
37
56
  requireBearerForDataplanePipeline(authConfig);
38
57
  const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, { silent: true });
39
58
  return { dataplaneUrl, authConfig };
@@ -115,7 +134,10 @@ async function syncOpenApiFilesForMcp(opts) {
115
134
  const openapiDir = path.join(appPath, 'openapi');
116
135
  if (!(await fileExistsAsync(openapiDir))) return { uploaded: 0, skipped: 0 };
117
136
 
118
- const { dataplaneUrl, authConfig } = await resolveDataplaneAndAuth(systemKey);
137
+ const { dataplaneUrl, authConfig } = await resolveDataplaneAndAuth(systemKey, {
138
+ dataplaneUrl: opts.dataplaneUrl,
139
+ authConfig: opts.authConfig
140
+ });
119
141
  const existingKeys = await getExistingOpenApiKeys(dataplaneUrl, systemKey, authConfig);
120
142
  const uploadedKeys = new Set(existingKeys);
121
143
  let uploaded = 0;
@@ -26,7 +26,9 @@ const {
26
26
  buildAuthenticationForRepair,
27
27
  mergePreservedAuthVariables,
28
28
  repairAuthChangeLabel,
29
- auditRepairAuthenticationWarnings
29
+ auditRepairAuthenticationWarnings,
30
+ normalizeAuthenticationSchemaShape,
31
+ getAuthCredentialTemplate
30
32
  } = require('./repair-auth-apply');
31
33
 
32
34
  /**
@@ -45,19 +47,24 @@ function applyAuthMethod(ctx) {
45
47
  }
46
48
  const existingAuth = ctx.systemParsed.authentication || ctx.systemParsed.auth || {};
47
49
  const newAuth = buildAuthenticationForRepair(ctx.systemKey, authOption);
48
- const existingVars =
49
- existingAuth.variables && typeof existingAuth.variables === 'object'
50
- ? existingAuth.variables
51
- : {};
52
- const mergedVariables = { ...newAuth.variables };
53
- mergePreservedAuthVariables(mergedVariables, existingVars);
54
- ctx.systemParsed.authentication = {
55
- ...newAuth,
56
- variables: mergedVariables
57
- };
58
- if (existingAuth.displayName !== undefined) {
59
- ctx.systemParsed.authentication.displayName = existingAuth.displayName;
50
+ const existingCredential = getAuthCredentialTemplate(existingAuth) || {};
51
+ const mergedVariables = { ...(newAuth.credential?.variables || {}) };
52
+ mergePreservedAuthVariables(
53
+ mergedVariables,
54
+ existingCredential.variables && typeof existingCredential.variables === 'object'
55
+ ? existingCredential.variables
56
+ : existingAuth.variables
57
+ );
58
+ if (newAuth.authType === 'credential' && newAuth.credential) {
59
+ newAuth.credential.variables = mergedVariables;
60
+ if (existingCredential.displayName !== undefined) {
61
+ newAuth.credential.displayName = existingCredential.displayName;
62
+ } else if (existingAuth.displayName !== undefined) {
63
+ newAuth.credential.displayName = existingAuth.displayName;
64
+ }
60
65
  }
66
+ ctx.systemParsed.authentication = newAuth;
67
+ delete ctx.systemParsed.auth;
61
68
  ctx.changes.push(`Set authentication method to ${repairAuthChangeLabel(authOption)}`);
62
69
  return true;
63
70
  }
@@ -70,6 +77,7 @@ function applyAuthMethod(ctx) {
70
77
  function persistSystemFileRepair(ctx, flags) {
71
78
  const systemFileUpdated =
72
79
  flags.authReplaced ||
80
+ flags.authShapeNormalized ||
73
81
  flags.systemAuthConfigNormalized ||
74
82
  flags.systemDataSourcesAligned ||
75
83
  flags.authVarsRemoved;
@@ -95,6 +103,12 @@ function runRepairSteps(ctx) {
95
103
  );
96
104
  const authReplaced = applyAuthMethod(ctx);
97
105
  updated = updated || authReplaced;
106
+ const authShapeNormalized = normalizeAuthenticationSchemaShape(
107
+ ctx.systemParsed,
108
+ ctx.systemKey,
109
+ ctx.changes
110
+ );
111
+ updated = updated || authShapeNormalized;
98
112
  const systemAuthConfigNormalized = normalizeSystemFileAuthAndConfig(
99
113
  ctx.systemParsed, ctx.systemKey, ctx.changes
100
114
  );
@@ -106,6 +120,7 @@ function runRepairSteps(ctx) {
106
120
  );
107
121
  const systemFileUpdated = persistSystemFileRepair(ctx, {
108
122
  authReplaced,
123
+ authShapeNormalized,
109
124
  systemAuthConfigNormalized,
110
125
  systemDataSourcesAligned,
111
126
  authVarsRemoved
@@ -177,13 +177,16 @@ function alignSystemFileDataSources(appPath, systemParsed, datasourceFiles, syst
177
177
  function buildAuthVarNames(systemParsed, _systemKey) {
178
178
  const names = new Set();
179
179
  const auth = systemParsed.authentication;
180
- if (auth && typeof auth.variables === 'object') {
181
- for (const k of Object.keys(auth.variables)) {
180
+ if (!auth || typeof auth !== 'object') return names;
181
+ const credential =
182
+ auth.credential && typeof auth.credential === 'object' ? auth.credential : auth;
183
+ if (typeof credential.variables === 'object') {
184
+ for (const k of Object.keys(credential.variables)) {
182
185
  names.add(String(k).toUpperCase().replace(/_/g, ''));
183
186
  }
184
187
  }
185
- if (auth && typeof auth.security === 'object') {
186
- for (const k of Object.keys(auth.security)) {
188
+ if (typeof credential.security === 'object') {
189
+ for (const k of Object.keys(credential.security)) {
187
190
  names.add(securityKeyToVar(k));
188
191
  }
189
192
  }
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Resource type catalog commands (Controller).
3
+ *
4
+ * Commands:
5
+ * - aifabrix resource-type create
6
+ * - aifabrix resource-type import
7
+ * - aifabrix resource-type get
8
+ * - aifabrix resource-type list
9
+ *
10
+ * @fileoverview Resource type CLI commands
11
+ * @author AI Fabrix Team
12
+ * @version 1.0.0
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const chalk = require('chalk');
18
+ const logger = require('../utils/logger');
19
+ const { resolveControllerUrl } = require('../utils/controller-url');
20
+ const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
21
+ const { getOrRefreshDeviceToken } = require('../utils/token-manager');
22
+ const {
23
+ readResourceTypeCreateFile,
24
+ readResourceTypeCatalogFile
25
+ } = require('../resolvers/resource-type-file');
26
+ const {
27
+ formatBlockingError,
28
+ formatSuccessLine,
29
+ infoLine,
30
+ headerKeyValue
31
+ } = require('../utils/cli-layout-chalk');
32
+ const {
33
+ listResourceTypes,
34
+ getResourceType,
35
+ createResourceTypeIdempotent
36
+ } = require('../api/resource-types.api');
37
+
38
+ const RESOURCE_TYPE_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
39
+
40
+ /** Parent help for `aifabrix resource-type -h`. */
41
+ const RESOURCE_TYPE_HELP_AFTER = `
42
+
43
+ Examples:
44
+ $ aifabrix resource-type create --key customer --display-name "Customer"
45
+ $ aifabrix resource-type get customer
46
+ $ aifabrix resource-type list --search customer
47
+
48
+ Per-command help: aifabrix resource-type <command> --help
49
+ `;
50
+
51
+ const RESOURCE_TYPE_IMPORT_HELP_AFTER = `
52
+
53
+ Examples:
54
+ $ aifabrix resource-type import --file ./baseline-resourcetypes.json
55
+ $ aifabrix resource-type import --file /path/to/catalog.json
56
+
57
+ `;
58
+
59
+ const RESOURCE_TYPE_CREATE_HELP_AFTER = `
60
+
61
+ Examples:
62
+ $ aifabrix resource-type create --key customer --display-name "Customer"
63
+ $ aifabrix resource-type create --key deal --display-name "Deal" --category business --default-risk-level medium
64
+ $ aifabrix resource-type create --file ./customer.json
65
+
66
+ `;
67
+
68
+ const RESOURCE_TYPE_GET_HELP_AFTER = `
69
+
70
+ Examples:
71
+ $ aifabrix resource-type get customer
72
+ $ aifabrix resource-type get clx1234567890abcdef
73
+
74
+ `;
75
+
76
+ const RESOURCE_TYPE_LIST_HELP_AFTER = `
77
+
78
+ Examples:
79
+ $ aifabrix resource-type list
80
+ $ aifabrix resource-type list --page 1 --page-size 50
81
+ $ aifabrix resource-type list --search customer
82
+
83
+ `;
84
+
85
+ /**
86
+ * @param {string} raw
87
+ * @returns {string}
88
+ */
89
+ function requireResourceTypeKey(raw) {
90
+ const key = String(raw || '').trim();
91
+ if (!key) {
92
+ throw new Error('Resource type key is required (--key <key> or --file <path>).');
93
+ }
94
+ if (!RESOURCE_TYPE_KEY_PATTERN.test(key)) {
95
+ throw new Error(
96
+ 'Resource type key must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.'
97
+ );
98
+ }
99
+ return key;
100
+ }
101
+
102
+ /**
103
+ * @param {Object} options
104
+ * @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
105
+ */
106
+ async function resolveControllerAndAuth(_options) {
107
+ const controllerUrl = await resolveControllerUrl();
108
+ if (!controllerUrl) {
109
+ throw new Error('Controller URL is required. Run "aifabrix login" first.');
110
+ }
111
+ const normalized = normalizeControllerUrl(controllerUrl);
112
+ const deviceToken = await getOrRefreshDeviceToken(normalized);
113
+ if (!deviceToken || !deviceToken.token) {
114
+ throw new Error(
115
+ `Not authenticated for controller: ${controllerUrl}. ` +
116
+ 'Run "aifabrix login" and try again.'
117
+ );
118
+ }
119
+ return {
120
+ controllerUrl: deviceToken.controller || normalized,
121
+ authConfig: { type: 'bearer', token: deviceToken.token }
122
+ };
123
+ }
124
+
125
+ async function resolveHeaderContext() {
126
+ const env = await resolveEnvironment();
127
+ const controllerUrl = await resolveControllerUrl();
128
+ const normalized = normalizeControllerUrl(controllerUrl);
129
+ return { environment: env || 'dev', controllerUrl: normalized };
130
+ }
131
+
132
+ /**
133
+ * @param {Object} options
134
+ * @returns {Object}
135
+ */
136
+ function buildCreatePayload(options) {
137
+ let base = {};
138
+ if (options.file) {
139
+ base = readResourceTypeCreateFile(options.file);
140
+ }
141
+ const payload = { ...base };
142
+ if (options.key) payload.key = options.key;
143
+ if (options.displayName) payload.displayName = options.displayName;
144
+ if (options.description !== undefined) payload.description = options.description;
145
+ if (options.category) payload.category = options.category;
146
+ if (options.defaultRiskLevel) payload.defaultRiskLevel = options.defaultRiskLevel;
147
+ if (options.status) payload.status = options.status;
148
+ payload.key = requireResourceTypeKey(payload.key);
149
+ if (!payload.displayName) {
150
+ throw new Error('displayName is required (--display-name <name> or in --file).');
151
+ }
152
+ return payload;
153
+ }
154
+
155
+ /**
156
+ * @param {Object} response
157
+ * @returns {any}
158
+ */
159
+ function unwrapControllerData(response) {
160
+ return response?.data?.data ?? response?.data ?? response;
161
+ }
162
+
163
+ /**
164
+ * @param {import('commander').Command} rt
165
+ */
166
+ /**
167
+ * @param {Object[]} rows
168
+ * @param {string} controllerUrl
169
+ * @param {Object} authConfig
170
+ * @returns {Promise<{ created: number, existed: number, total: number }>}
171
+ */
172
+ async function importResourceTypeRows(rows, controllerUrl, authConfig) {
173
+ let created = 0;
174
+ let existed = 0;
175
+ for (const row of rows) {
176
+ const payload = { ...row };
177
+ payload.key = requireResourceTypeKey(payload.key);
178
+ const out = await createResourceTypeIdempotent(controllerUrl, authConfig, payload);
179
+ if (out.created) {
180
+ created += 1;
181
+ continue;
182
+ }
183
+ existed += 1;
184
+ }
185
+ return { created, existed, total: rows.length };
186
+ }
187
+
188
+ function setupResourceTypeImportCommand(rt) {
189
+ rt
190
+ .command('import')
191
+ .description('Import resource types from baseline catalog JSON (idempotent)')
192
+ .addHelpText('after', RESOURCE_TYPE_IMPORT_HELP_AFTER)
193
+ .requiredOption('--file <path>', 'Baseline catalog (resourceTypes[]) or JSON array')
194
+ .action(async(options) => {
195
+ try {
196
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
197
+ const rows = readResourceTypeCatalogFile(options.file);
198
+ const summary = await importResourceTypeRows(rows, controllerUrl, authConfig);
199
+ logger.log(
200
+ formatSuccessLine(
201
+ `Resource types imported: ${summary.total} total ` +
202
+ `(${summary.created} created, ${summary.existed} already existed)`
203
+ )
204
+ );
205
+ } catch (e) {
206
+ logger.error(formatBlockingError(e.message));
207
+ process.exit(1);
208
+ }
209
+ });
210
+ }
211
+
212
+ function setupResourceTypeCreateCommand(rt) {
213
+ rt
214
+ .command('create')
215
+ .description('Create resource type (idempotent; success if it already exists)')
216
+ .addHelpText('after', RESOURCE_TYPE_CREATE_HELP_AFTER)
217
+ .option('--file <path>', 'Read resource type create payload from JSON file')
218
+ .option('--key <key>', 'Catalog key (lowercase letters, digits, hyphens)')
219
+ .option('--display-name <name>', 'Display name')
220
+ .option('--description <text>', 'Description')
221
+ .option(
222
+ '--category <category>',
223
+ 'business | collaboration | governance | content | communication | people'
224
+ )
225
+ .option('--default-risk-level <level>', 'low | medium | high | critical')
226
+ .option('--status <status>', 'draft | active | deprecated | retired')
227
+ .action(async(options) => {
228
+ try {
229
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
230
+ const payload = buildCreatePayload(options);
231
+ const out = await createResourceTypeIdempotent(controllerUrl, authConfig, payload);
232
+ const row = unwrapControllerData(out.response);
233
+ const category = row.category || payload.category || 'business';
234
+ const risk = row.defaultRiskLevel || payload.defaultRiskLevel || 'medium';
235
+ if (out.created) {
236
+ logger.log(
237
+ formatSuccessLine(`Resource type created: ${row.key} (category: ${category}, risk: ${risk})`)
238
+ );
239
+ return;
240
+ }
241
+ logger.log(formatSuccessLine(`Resource type exists: ${row.key} (category: ${category}, risk: ${risk})`));
242
+ logger.log(infoLine('No changes were needed.'));
243
+ } catch (e) {
244
+ logger.error(formatBlockingError(e.message));
245
+ process.exit(1);
246
+ }
247
+ });
248
+ }
249
+
250
+ function setupResourceTypeGetCommand(rt) {
251
+ rt
252
+ .command('get <resourceTypeIdOrKey>')
253
+ .description('Get resource type by id or key')
254
+ .addHelpText('after', RESOURCE_TYPE_GET_HELP_AFTER)
255
+ .action(async(resourceTypeIdOrKey, options) => {
256
+ try {
257
+ const { environment, controllerUrl: headerControllerUrl } = await resolveHeaderContext();
258
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
259
+ const res = await getResourceType(controllerUrl, authConfig, String(resourceTypeIdOrKey).trim());
260
+ const row = unwrapControllerData(res);
261
+ logger.log(
262
+ chalk.bold(`\nšŸ·ļø Resource type in ${environment} environment (${headerControllerUrl}):\n`)
263
+ );
264
+ logger.log(headerKeyValue('Key:', row.key || '—'));
265
+ logger.log(headerKeyValue('Display:', row.displayName || '—'));
266
+ logger.log(headerKeyValue('Category:', row.category || '—'));
267
+ logger.log(headerKeyValue('Default risk:', row.defaultRiskLevel || '—'));
268
+ logger.log(headerKeyValue('Status:', row.status || '—'));
269
+ if (row.description) {
270
+ logger.log(headerKeyValue('Description:', row.description));
271
+ }
272
+ logger.log('');
273
+ } catch (e) {
274
+ logger.error(formatBlockingError(e.message));
275
+ process.exit(1);
276
+ }
277
+ });
278
+ }
279
+
280
+ /**
281
+ * @param {Array<Object>} items
282
+ * @param {string} environment
283
+ * @param {string} controllerUrl
284
+ */
285
+ function displayResourceTypeList(items, environment, controllerUrl) {
286
+ logger.log(
287
+ chalk.bold(`\nšŸ·ļø Resource types in ${environment} environment (${controllerUrl}):\n`)
288
+ );
289
+ if (!Array.isArray(items) || items.length === 0) {
290
+ logger.log(chalk.gray(' No resource types found.\n'));
291
+ return;
292
+ }
293
+
294
+ const KEY_WIDTH = 22;
295
+ const DISPLAY_WIDTH = 30;
296
+ const CATEGORY_WIDTH = 16;
297
+ const RISK_WIDTH = 10;
298
+ const STATUS_WIDTH = 12;
299
+ const SEP_LEN = KEY_WIDTH + DISPLAY_WIDTH + CATEGORY_WIDTH + RISK_WIDTH + STATUS_WIDTH;
300
+
301
+ const keyCol = 'Key'.padEnd(KEY_WIDTH);
302
+ const displayCol = 'Display'.padEnd(DISPLAY_WIDTH);
303
+ const categoryCol = 'Category'.padEnd(CATEGORY_WIDTH);
304
+ const riskCol = 'Risk'.padEnd(RISK_WIDTH);
305
+ const statusCol = 'Status'.padEnd(STATUS_WIDTH);
306
+ logger.log(chalk.gray(`${keyCol}${displayCol}${categoryCol}${riskCol}${statusCol}`));
307
+ logger.log(chalk.gray('-'.repeat(SEP_LEN)));
308
+
309
+ items.forEach((d) => {
310
+ const key = (d?.key ?? '—').toString().padEnd(KEY_WIDTH);
311
+ const displayName = (d?.displayName ?? '—').toString().padEnd(DISPLAY_WIDTH);
312
+ const category = (d?.category ?? '—').toString().padEnd(CATEGORY_WIDTH);
313
+ const risk = (d?.defaultRiskLevel ?? '—').toString().padEnd(RISK_WIDTH);
314
+ const status = (d?.status ?? '—').toString().padEnd(STATUS_WIDTH);
315
+ logger.log(`${key}${displayName}${category}${risk}${status}`);
316
+ });
317
+ logger.log('');
318
+ }
319
+
320
+ function setupResourceTypeListCommand(rt) {
321
+ rt
322
+ .command('list')
323
+ .description('List resource types')
324
+ .addHelpText('after', RESOURCE_TYPE_LIST_HELP_AFTER)
325
+ .option('--page <n>', 'Page', (v) => parseInt(v, 10))
326
+ .option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
327
+ .option('--search <text>', 'Search by key/displayName/description')
328
+ .option('--category <category>', 'Filter by category')
329
+ .option('--status <status>', 'Filter by status')
330
+ .action(async(options) => {
331
+ try {
332
+ const { environment, controllerUrl: headerControllerUrl } = await resolveHeaderContext();
333
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
334
+ const res = await listResourceTypes(controllerUrl, authConfig, {
335
+ page: options.page,
336
+ pageSize: options.pageSize,
337
+ search: options.search,
338
+ category: options.category,
339
+ status: options.status
340
+ });
341
+ const payload = unwrapControllerData(res);
342
+ const items = payload?.data ?? payload ?? [];
343
+ displayResourceTypeList(items, environment, headerControllerUrl);
344
+ } catch (e) {
345
+ logger.error(formatBlockingError(e.message));
346
+ process.exit(1);
347
+ }
348
+ });
349
+ }
350
+
351
+ /**
352
+ * @param {import('commander').Command} program
353
+ */
354
+ function setupResourceTypeCommands(program) {
355
+ const rt = program
356
+ .command('resource-type')
357
+ .description('Manage the Controller Resource Type Catalog (COM)')
358
+ .addHelpText('after', RESOURCE_TYPE_HELP_AFTER);
359
+ setupResourceTypeCreateCommand(rt);
360
+ setupResourceTypeImportCommand(rt);
361
+ setupResourceTypeGetCommand(rt);
362
+ setupResourceTypeListCommand(rt);
363
+ }
364
+
365
+ module.exports = {
366
+ setupResourceTypeCommands,
367
+ requireResourceTypeKey,
368
+ buildCreatePayload,
369
+ unwrapControllerData,
370
+ importResourceTypeRows
371
+ };
@@ -263,7 +263,7 @@ async function runUpPlatform(opts = {}) {
263
263
  if (opts.force === true && opts.skipForceClean !== true) {
264
264
  const cleanedApps =
265
265
  preflight.skipClean !== true
266
- ? await upCommon.cleanBuilderAppDirs(PLATFORM_APPS, { silent: true })
266
+ ? await upCommon.cleanBuilderAppDirs(upCommon.PLATFORM_BUILDER_CLEAN_APP_KEYS, { silent: true })
267
267
  : [];
268
268
  forceCleanSummary = { forceSummary: authCtx.forceSummary, cleanedApps };
269
269
  }