@aifabrix/builder 2.52.0 → 2.53.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 (310) hide show
  1. package/lib/api/datasources-extended.api.js +8 -8
  2. package/lib/api/keycloak.api.js +92 -0
  3. package/lib/api/token-providers.api.js +77 -0
  4. package/lib/api/types/keycloak.types.js +23 -0
  5. package/lib/api/types/token-providers.types.js +65 -0
  6. package/lib/app/deploy-config.js +3 -2
  7. package/lib/app/down.js +16 -1
  8. package/lib/channels/add-channel.js +134 -57
  9. package/lib/channels/approval-guides/chatgpt.js +1 -1
  10. package/lib/channels/approval-guides/index.js +3 -0
  11. package/lib/channels/approval-guides/microsoft-copilot.js +27 -14
  12. package/lib/channels/channel-add-ensure.js +128 -0
  13. package/lib/channels/channel-add-existing.js +72 -0
  14. package/lib/channels/channel-artifact-generator.js +11 -1
  15. package/lib/channels/channel-branding.js +94 -0
  16. package/lib/channels/channel-credential.js +8 -0
  17. package/lib/channels/channel-discovery.js +330 -0
  18. package/lib/channels/channel-external-system.js +1 -1
  19. package/lib/channels/channel-runtime-resolver.js +107 -14
  20. package/lib/channels/channel-status-display.js +36 -3
  21. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  22. package/lib/channels/microsoft-copilot-entra-sso-form.js +251 -0
  23. package/lib/channels/microsoft-copilot-entra-sso.js +285 -0
  24. package/lib/channels/microsoft-copilot-icons.js +74 -0
  25. package/lib/channels/microsoft-copilot-limits.js +103 -0
  26. package/lib/channels/microsoft-copilot-package.js +178 -52
  27. package/lib/channels/microsoft-copilot-validate.js +227 -0
  28. package/lib/cli/attach-default-help-examples.js +102 -0
  29. package/lib/cli/index.js +4 -0
  30. package/lib/cli/infra-guided.js +13 -6
  31. package/lib/cli/setup-credential-deployment.js +7 -5
  32. package/lib/cli/setup-infra.js +31 -4
  33. package/lib/cli/setup-utility-validate.js +1 -0
  34. package/lib/commands/channel-add-setup.js +28 -9
  35. package/lib/commands/channel.js +14 -10
  36. package/lib/commands/credential-push.js +37 -5
  37. package/lib/commands/integration-definition.js +158 -0
  38. package/lib/commands/package-score.js +72 -0
  39. package/lib/commands/repair-auth-apply.js +19 -5
  40. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  41. package/lib/commands/role-assistant-test-output.js +200 -0
  42. package/lib/commands/role-assistant.js +163 -166
  43. package/lib/datasource/capability/capability-resolve.js +15 -5
  44. package/lib/datasource/capability/copy-operations.js +14 -4
  45. package/lib/deployment/deployer-poll-run.js +2 -2
  46. package/lib/deployment/deployer.js +1 -1
  47. package/lib/deployment/environment.js +2 -2
  48. package/lib/external-system/deploy.js +11 -21
  49. package/lib/infrastructure/index.js +19 -4
  50. package/lib/integration-definition/apply.js +476 -0
  51. package/lib/integration-definition/canonical-json.js +51 -0
  52. package/lib/integration-definition/emit-suite-bid.js +94 -0
  53. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  54. package/lib/integration-definition/enrich-openapi-operations.js +433 -0
  55. package/lib/integration-definition/from-discovery.js +339 -0
  56. package/lib/integration-definition/hash.js +37 -0
  57. package/lib/integration-definition/index.js +37 -0
  58. package/lib/integration-definition/resource-type-catalog.js +59 -0
  59. package/lib/integration-definition/system-kind-enum.js +40 -0
  60. package/lib/integration-definition/validate.js +148 -0
  61. package/lib/lab/package-parity-score.js +417 -0
  62. package/lib/programmatic/builder-api-request.js +5 -1
  63. package/lib/programmatic/builder-help-governance.js +3 -2
  64. package/lib/programmatic/capability-registry.js +20 -0
  65. package/lib/programmatic/help-content/channel-onboarding.md +35 -0
  66. package/lib/programmatic/help-content/cip-overview.md +27 -0
  67. package/lib/programmatic/help-content/connected-system-ui.md +70 -0
  68. package/lib/programmatic/help-content/dimensions-guide.md +23 -0
  69. package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
  70. package/lib/programmatic/help-content/overview.md +25 -0
  71. package/lib/programmatic/help-content/subscription-guide.md +27 -0
  72. package/lib/programmatic/help-content/workflow.md +50 -0
  73. package/lib/programmatic/openapi-component-schemas.js +7 -1
  74. package/lib/programmatic/openapi-descriptions.js +46 -0
  75. package/lib/programmatic/openapi-envelope-examples.js +280 -0
  76. package/lib/programmatic/openapi-operation-bindings.js +50 -0
  77. package/lib/programmatic/openapi-request-schemas.js +40 -0
  78. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  79. package/lib/programmatic/openapi-result-schemas.js +76 -99
  80. package/lib/programmatic/openapi-schema-descriptions-core.js +25 -4
  81. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  82. package/lib/programmatic/openapi-schema-descriptions-operations.js +43 -0
  83. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  84. package/lib/programmatic/openapi-spec.js +11 -1
  85. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  86. package/lib/programmatic/persist-supplied-openapi.js +180 -0
  87. package/lib/programmatic/route-handler-map.js +35 -1
  88. package/lib/programmatic/run-channel-package.js +236 -0
  89. package/lib/programmatic/run-integration-definition.js +77 -0
  90. package/lib/role-assistant/evidence-sync.js +55 -7
  91. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  92. package/lib/role-assistant/knowledge-sync.js +88 -93
  93. package/lib/role-assistant/test-cases-bid.js +155 -0
  94. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  95. package/lib/role-assistant/test-cases-honesty.js +24 -10
  96. package/lib/role-assistant/test-cases.js +28 -0
  97. package/lib/role-assistant/test-runner-attachments.js +118 -0
  98. package/lib/role-assistant/test-runner-bid-apply.js +201 -0
  99. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  100. package/lib/role-assistant/test-runner-start-payload.js +96 -0
  101. package/lib/role-assistant/test-runner-workhub-answers.js +249 -20
  102. package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
  103. package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
  104. package/lib/role-assistant/test-runner-workhub.js +152 -107
  105. package/lib/role-assistant/test-runner.js +5 -63
  106. package/lib/role-assistant/test-suite.js +112 -0
  107. package/lib/schema/application-schema.json +56 -6
  108. package/lib/schema/external-datasource.schema.json +103 -17
  109. package/lib/schema/external-system.schema.json +54 -3
  110. package/lib/schema/integration-definition.schema.json +195 -0
  111. package/lib/utils/authentication-shape.js +6 -0
  112. package/lib/utils/compose-generator.js +10 -1
  113. package/lib/utils/compose-traefik-extra-routes.js +107 -0
  114. package/lib/utils/docker-build.js +2 -2
  115. package/lib/utils/help-builder.js +3 -1
  116. package/lib/utils/promote-dev-docker-image.js +102 -18
  117. package/lib/utils/transient-developer-id.js +28 -0
  118. package/package.json +14 -16
  119. package/templates/README.md +12 -1
  120. package/templates/applications/dataplane/application.yaml +10 -2
  121. package/templates/applications/dataplane/env.template +7 -6
  122. package/templates/applications/miso-controller/application.yaml +23 -8
  123. package/templates/applications/miso-controller/env.template +8 -2
  124. package/templates/applications/miso-controller/rbac.yaml +1 -0
  125. package/templates/channels/README.md +11 -1
  126. package/templates/channels/brand/color-192.png +0 -0
  127. package/templates/channels/brand/dark/128.png +0 -0
  128. package/templates/channels/brand/dark/192.png +0 -0
  129. package/templates/channels/brand/dark/256.png +0 -0
  130. package/templates/channels/brand/dark/32.png +0 -0
  131. package/templates/channels/brand/dark/512.png +0 -0
  132. package/templates/channels/brand/dark/64.png +0 -0
  133. package/templates/channels/brand/light/128.png +0 -0
  134. package/templates/channels/brand/light/192.png +0 -0
  135. package/templates/channels/brand/light/256.png +0 -0
  136. package/templates/channels/brand/light/32.png +0 -0
  137. package/templates/channels/brand/light/512.png +0 -0
  138. package/templates/channels/brand/light/64.png +0 -0
  139. package/templates/channels/brand/mono-white/128.png +0 -0
  140. package/templates/channels/brand/mono-white/192.png +0 -0
  141. package/templates/channels/brand/mono-white/256.png +0 -0
  142. package/templates/channels/brand/mono-white/32.png +0 -0
  143. package/templates/channels/brand/mono-white/512.png +0 -0
  144. package/templates/channels/brand/mono-white/64.png +0 -0
  145. package/templates/channels/brand/outline-32.png +0 -0
  146. package/templates/channels/chatgpt/install.template.md.hbs +6 -4
  147. package/templates/channels/chatgpt/integration/README.md +4 -5
  148. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  149. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +14 -16
  150. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  151. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +30 -0
  152. package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
  153. package/templates/channels/microsoft-copilot/install.template.md.hbs +93 -22
  154. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  155. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  156. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -19
  157. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +15 -0
  158. package/templates/channels/slack/integration/README.md +2 -4
  159. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  160. package/templates/channels/teams-bot/integration/README.md +3 -5
  161. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  162. package/templates/python/docker-compose.hbs +36 -0
  163. package/templates/typescript/docker-compose.hbs +36 -0
  164. package/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +0 -374
  165. package/.cursor/process/README.md +0 -9
  166. package/.cursor/rules/anchor-docs.mdc +0 -15
  167. package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
  168. package/.cursor/rules/cli-layout.mdc +0 -81
  169. package/.cursor/rules/cli-product-neutral.mdc +0 -43
  170. package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
  171. package/.cursor/rules/docs-rules.mdc +0 -32
  172. package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
  173. package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
  174. package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
  175. package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
  176. package/.cursor/rules/project-rules.mdc +0 -913
  177. package/.cursor/skills/README.md +0 -15
  178. package/.dockerignore +0 -52
  179. package/.eslintignore +0 -22
  180. package/.markdownlint.json +0 -14
  181. package/anchor-docs/README.md +0 -10
  182. package/anchor-docs/_TEMPLATE +0 -24
  183. package/babel.config.js +0 -6
  184. package/bin/builder-api-cleanup.js +0 -26
  185. package/bin/builder-api-nest.js +0 -10
  186. package/bin/builder-api-spike.js +0 -19
  187. package/enterprise-knowledge/README.md +0 -50
  188. package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
  189. package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
  190. package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
  191. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
  192. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
  193. package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
  194. package/integration/README.md +0 -81
  195. package/integration/github/README.md +0 -53
  196. package/integration/github/application.json +0 -21
  197. package/integration/github/deploy.js +0 -72
  198. package/integration/github/env.template +0 -11
  199. package/integration/github/github-datasource-issue-comments.json +0 -239
  200. package/integration/github/github-datasource-issues.json +0 -399
  201. package/integration/github/github-datasource-repository.json +0 -354
  202. package/integration/github/github-deploy.json +0 -1202
  203. package/integration/github/github-system.json +0 -82
  204. package/integration/hubspot-test/README.md +0 -159
  205. package/integration/hubspot-test/application.json +0 -54
  206. package/integration/hubspot-test/companies.json +0 -2048
  207. package/integration/hubspot-test/create-hubspot.js +0 -523
  208. package/integration/hubspot-test/env.template +0 -4
  209. package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
  210. package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
  211. package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
  212. package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
  213. package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
  214. package/integration/hubspot-test/hubspot-test-system.json +0 -74
  215. package/integration/hubspot-test/rbac.json +0 -166
  216. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
  217. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
  218. package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
  219. package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
  220. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
  221. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
  222. package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
  223. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
  224. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  225. package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
  226. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
  227. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
  228. package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
  229. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
  230. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  231. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  232. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
  233. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
  234. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
  235. package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
  236. package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
  237. package/integration/hubspot-test/test-dataplane-down.js +0 -209
  238. package/integration/hubspot-test/test.js +0 -1588
  239. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
  240. package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
  241. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
  242. package/integration/roundtrip-test-local/README.md +0 -143
  243. package/integration/roundtrip-test-local/application.yaml +0 -13
  244. package/integration/roundtrip-test-local/env.template +0 -15
  245. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
  246. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
  247. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
  248. package/integration/roundtrip-test-local2/README.md +0 -143
  249. package/integration/roundtrip-test-local2/application.yaml +0 -13
  250. package/integration/roundtrip-test-local2/env.template +0 -15
  251. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
  252. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
  253. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
  254. package/integration/test/wizard.yaml +0 -8
  255. package/jest.config.coverage.js +0 -37
  256. package/jest.config.default.js +0 -11
  257. package/jest.config.integration.fixtures.js +0 -22
  258. package/jest.config.integration.js +0 -33
  259. package/jest.config.isolated.js +0 -11
  260. package/jest.config.manual.js +0 -30
  261. package/jest.global-live-fabrix-hooks.js +0 -36
  262. package/jest.isolated-projects.js +0 -260
  263. package/jest.projects.js +0 -76
  264. package/packages/builder-api/README.md +0 -120
  265. package/packages/builder-api/package.json +0 -30
  266. package/packages/builder-api/src/app.module.ts +0 -7
  267. package/packages/builder-api/src/auth/auth.module.ts +0 -5
  268. package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
  269. package/packages/builder-api/src/main.ts +0 -16
  270. package/packages/builder-api/tsconfig.json +0 -15
  271. package/scripts/check-builder-api-architecture-grep.js +0 -79
  272. package/scripts/check-datasource-test-run-schema-sync.js +0 -34
  273. package/scripts/ci-fix.sh +0 -19
  274. package/scripts/ci-simulate.sh +0 -19
  275. package/scripts/diagnose-cli.js +0 -150
  276. package/scripts/docker_image_content_gate.js +0 -47
  277. package/scripts/docker_image_content_gate.sh +0 -5
  278. package/scripts/export-builder-api-evidence.js +0 -262
  279. package/scripts/install-local.js +0 -490
  280. package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
  281. package/scripts/lib/builder-api-docker-staging-core.js +0 -183
  282. package/scripts/lib/docker-image-content-gate-core.js +0 -166
  283. package/scripts/lib/plan-actor.js +0 -109
  284. package/scripts/lib/plan-config.js +0 -20
  285. package/scripts/lib/plan-gates.js +0 -154
  286. package/scripts/lib/plan-meta.js +0 -104
  287. package/scripts/lib/plan-parse.js +0 -162
  288. package/scripts/lib/plan-prevalidate-gates.js +0 -337
  289. package/scripts/lib/plan-run-core.js +0 -125
  290. package/scripts/lib/plan-run-report.js +0 -119
  291. package/scripts/plan-approve.js +0 -24
  292. package/scripts/plan-archive.js +0 -49
  293. package/scripts/plan-run.js +0 -13
  294. package/scripts/plan-set-actor.js +0 -21
  295. package/scripts/plan-validate.js +0 -80
  296. package/scripts/pnpm/af.mjs +0 -110
  297. package/scripts/pnpm/check-quiet.mjs +0 -32
  298. package/scripts/pnpm/dev-install.mjs +0 -29
  299. package/scripts/pnpm/dev-reload.mjs +0 -18
  300. package/scripts/pnpm/help.mjs +0 -55
  301. package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
  302. package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
  303. package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
  304. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
  305. package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
  306. package/scripts/pnpm/lib/spinner.mjs +0 -42
  307. package/scripts/pnpm-global-remove.js +0 -48
  308. package/scripts/stage_builder_api_for_docker.js +0 -65
  309. package/scripts/sync-builder-api-template.js +0 -44
  310. package/scripts/test-dataplane-bootstrap.js +0 -331
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Bind BID capability keys to OpenAPI method/path (vendor-agnostic heuristics).
3
+ * @fileoverview BID apply OpenAPI enrichment
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const path = require('path');
9
+ const fsReal = require('../internal/fs-real-sync');
10
+ const {
11
+ maybeEnrichCrmStyleFieldMappings
12
+ } = require('./enrich-crm-field-mappings');
13
+
14
+ const PATH_PARAM_RE = /\{(\w+)\}/g;
15
+
16
+ /**
17
+ * @param {string} ref
18
+ * @param {string} [baseDir]
19
+ * @returns {string}
20
+ */
21
+ function resolveOpenApiRefPath(ref, baseDir) {
22
+ const trimmed = String(ref || '').trim();
23
+ if (!trimmed) {
24
+ throw new Error('openapiSourceRef is empty');
25
+ }
26
+ if (path.isAbsolute(trimmed)) {
27
+ return trimmed;
28
+ }
29
+ // Product resolve root is workspace openapi/ (517.2) via explicit baseDir.
30
+ // No ship-local vendor enrichment packs under lib/.
31
+ const candidates = [
32
+ baseDir ? path.resolve(baseDir, trimmed) : null,
33
+ baseDir ? path.resolve(baseDir, path.basename(trimmed)) : null,
34
+ path.resolve(process.cwd(), trimmed)
35
+ ].filter(Boolean);
36
+ for (const candidate of candidates) {
37
+ if (fsReal.existsSync(candidate)) {
38
+ return candidate;
39
+ }
40
+ }
41
+ throw new Error(
42
+ `OpenAPI enrichment document not found for ref "${trimmed}". Tried: ${candidates.join(', ')}`
43
+ );
44
+ }
45
+
46
+ /**
47
+ * @param {string} ref
48
+ * @param {string} [baseDir]
49
+ * @returns {Record<string, unknown>}
50
+ */
51
+ function loadOpenApiDocument(ref, baseDir) {
52
+ const abs = resolveOpenApiRefPath(ref, baseDir);
53
+ const raw = fsReal.readFileSync(abs, 'utf8');
54
+ const doc = JSON.parse(raw);
55
+ if (!doc || typeof doc !== 'object' || !doc.paths || typeof doc.paths !== 'object') {
56
+ throw new Error(`OpenAPI document missing paths: ${abs}`);
57
+ }
58
+ return doc;
59
+ }
60
+
61
+ /**
62
+ * Object-collection segment from paths like `/crm/v3/objects/companies`.
63
+ * @param {string} opPath
64
+ * @returns {string | null}
65
+ */
66
+ function objectTypeFromOpenApiPath(opPath) {
67
+ const match = String(opPath || '').match(/\/objects\/([^/{?]+)/i);
68
+ const value = match && match[1] ? String(match[1]).trim() : '';
69
+ if (!value) {
70
+ return null;
71
+ }
72
+ // Skip opaque numeric type ids (e.g. HubSpot `0-3`); associations query
73
+ // expects named collection segments when available.
74
+ if (/^\d/.test(value)) {
75
+ return null;
76
+ }
77
+ return value;
78
+ }
79
+
80
+ /**
81
+ * @param {Record<string, unknown>} doc
82
+ * @param {string} method
83
+ * @param {string} opPath
84
+ * @returns {boolean}
85
+ */
86
+ /**
87
+ * @param {Record<string, unknown>} doc
88
+ * @param {string} method
89
+ * @param {string} opPath
90
+ * @returns {any | null}
91
+ */
92
+ function operationFromDoc(doc, method, opPath) {
93
+ const pathItem = /** @type {any} */ (doc && doc.paths ? doc.paths[opPath] : null);
94
+ if (!pathItem || typeof pathItem !== 'object') {
95
+ return null;
96
+ }
97
+ return (
98
+ pathItem[String(method || '').toLowerCase()] ||
99
+ pathItem[String(method || '').toUpperCase()] ||
100
+ null
101
+ );
102
+ }
103
+
104
+ /**
105
+ * @param {Record<string, unknown>} doc
106
+ * @param {string} method
107
+ * @param {string} opPath
108
+ * @returns {any[]}
109
+ */
110
+ function operationParameters(doc, method, opPath) {
111
+ const pathItem = /** @type {any} */ (doc && doc.paths ? doc.paths[opPath] : null);
112
+ const op = operationFromDoc(doc, method, opPath);
113
+ return [
114
+ ...(pathItem && Array.isArray(pathItem.parameters) ? pathItem.parameters : []),
115
+ ...(op && Array.isArray(op.parameters) ? op.parameters : [])
116
+ ];
117
+ }
118
+
119
+ function operationDeclaresAssociationsQuery(doc, method, opPath) {
120
+ return operationParameters(doc, method, opPath).some(
121
+ (row) =>
122
+ row &&
123
+ typeof row === 'object' &&
124
+ row.in === 'query' &&
125
+ String(row.name || '').toLowerCase() === 'associations'
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Detect cursor list pagination from OpenAPI query params + response schema/docs.
131
+ * Vendor-agnostic: only emits when both cursor and page-size query params exist and
132
+ * response/docs mention a paging.next.after style cursor field.
133
+ * @param {Record<string, unknown>} doc
134
+ * @param {string} method
135
+ * @param {string} opPath
136
+ * @returns {{ cursorParam: string, pageSizeParam: string, cursorField: string } | null}
137
+ */
138
+ function operationCursorPagination(doc, method, opPath) {
139
+ const params = operationParameters(doc, method, opPath).filter(
140
+ (row) => row && typeof row === 'object' && row.in === 'query'
141
+ );
142
+ const names = new Set(
143
+ params.map((row) => String(row.name || '').toLowerCase()).filter(Boolean)
144
+ );
145
+ if (!names.has('after') || !names.has('limit')) {
146
+ return null;
147
+ }
148
+ const blob = JSON.stringify({
149
+ params,
150
+ op: operationFromDoc(doc, method, opPath)
151
+ });
152
+ if (!/paging\.next\.after/i.test(blob)) {
153
+ return null;
154
+ }
155
+ return {
156
+ cursorParam: 'after',
157
+ pageSizeParam: 'limit',
158
+ cursorField: '$.paging.next.after'
159
+ };
160
+ }
161
+
162
+ /**
163
+ * @param {string} method
164
+ * @param {string} opPath
165
+ * @param {Record<string, unknown>} op
166
+ * @returns {{ method: string, path: string, operationId: string, pathParamCount: number }}
167
+ */
168
+ function normalizeOp(method, opPath, op) {
169
+ const params = [...String(opPath).matchAll(PATH_PARAM_RE)].map((m) => m[1]);
170
+ return {
171
+ method: String(method || '').toUpperCase(),
172
+ path: String(opPath),
173
+ operationId: typeof op.operationId === 'string' ? op.operationId : '',
174
+ pathParamCount: params.length
175
+ };
176
+ }
177
+
178
+ /**
179
+ * @param {Record<string, unknown>} doc
180
+ * @returns {Array<{ method: string, path: string, operationId: string, pathParamCount: number }>}
181
+ */
182
+ function indexOpenApiOperations(doc) {
183
+ const out = [];
184
+ for (const [opPath, methods] of Object.entries(doc.paths || {})) {
185
+ if (!methods || typeof methods !== 'object') {
186
+ continue;
187
+ }
188
+ for (const [method, op] of Object.entries(methods)) {
189
+ if (!op || typeof op !== 'object' || method.startsWith('x-')) {
190
+ continue;
191
+ }
192
+ if (!['get', 'post', 'put', 'patch', 'delete'].includes(method.toLowerCase())) {
193
+ continue;
194
+ }
195
+ out.push(normalizeOp(method, opPath, /** @type {Record<string, unknown>} */ (op)));
196
+ }
197
+ }
198
+ return out;
199
+ }
200
+
201
+ /**
202
+ * @param {string} text
203
+ * @returns {boolean}
204
+ */
205
+ function isBatchOrSearch(text) {
206
+ // Keep DELETE archive ops; exclude batch/search helpers only.
207
+ return /\/batch\/|\bbatch\b|\/search|merge|upsert/i.test(text);
208
+ }
209
+
210
+ /**
211
+ * @param {string} capabilityKey
212
+ * @param {Array<{ method: string, path: string, operationId: string, pathParamCount: number }>} ops
213
+ * @returns {{ method: string, path: string, operationId: string } | null}
214
+ */
215
+ function pickFirstMatchingOp(candidates, predicate, idPattern) {
216
+ const hits = candidates.filter(predicate);
217
+ return hits.find((op) => idPattern.test(op.operationId)) || hits[0] || null;
218
+ }
219
+
220
+ function pickOperationForCapability(capabilityKey, ops) {
221
+ const key = String(capabilityKey || '').toLowerCase();
222
+ const candidates = ops.filter((op) => !isBatchOrSearch(`${op.path} ${op.operationId}`));
223
+ const rules = {
224
+ list: {
225
+ predicate: (op) => op.method === 'GET' && op.pathParamCount === 0,
226
+ idPattern: /getpage|list|getall/i
227
+ },
228
+ get: {
229
+ predicate: (op) => op.method === 'GET' && op.pathParamCount >= 1,
230
+ idPattern: /getbyid|get.*id|read/i
231
+ },
232
+ create: {
233
+ predicate: (op) => op.method === 'POST' && op.pathParamCount === 0,
234
+ idPattern: /create$|_create$/i
235
+ },
236
+ createbasic: {
237
+ predicate: (op) => op.method === 'POST' && op.pathParamCount === 0,
238
+ idPattern: /create$|_create$/i
239
+ },
240
+ update: {
241
+ predicate: (op) =>
242
+ (op.method === 'PATCH' || op.method === 'PUT') && op.pathParamCount >= 1,
243
+ idPattern: /update$|_update$/i
244
+ },
245
+ updatebasic: {
246
+ predicate: (op) =>
247
+ (op.method === 'PATCH' || op.method === 'PUT') && op.pathParamCount >= 1,
248
+ idPattern: /update$|_update$/i
249
+ },
250
+ delete: {
251
+ predicate: (op) => op.method === 'DELETE' && op.pathParamCount >= 1,
252
+ idPattern: /archive|delete/i
253
+ }
254
+ };
255
+ const rule = rules[key];
256
+ if (!rule) {
257
+ return null;
258
+ }
259
+ return pickFirstMatchingOp(candidates, rule.predicate, rule.idPattern);
260
+ }
261
+
262
+ /**
263
+ * @param {Record<string, unknown>} body
264
+ * @param {{ method: string, path: string, operationId: string }} binding
265
+ * @param {string} capabilityKey
266
+ * @param {{
267
+ * hasAssociationsQuery?: boolean,
268
+ * associationTargets?: string[],
269
+ * cursorPagination?: { cursorParam: string, pageSizeParam: string, cursorField: string } | null
270
+ * }} [bindOptions]
271
+ * @returns {void}
272
+ */
273
+ function bindOpenApiOperation(body, binding, capabilityKey) {
274
+ if (!body.openapi || typeof body.openapi !== 'object') {
275
+ body.openapi = {};
276
+ }
277
+ const openapi = /** @type {Record<string, unknown>} */ (body.openapi);
278
+ openapi.enabled = true;
279
+ if (!openapi.operations || typeof openapi.operations !== 'object') {
280
+ openapi.operations = {};
281
+ }
282
+ const ops = /** @type {Record<string, unknown>} */ (openapi.operations);
283
+ const prev = ops[capabilityKey] && typeof ops[capabilityKey] === 'object'
284
+ ? /** @type {Record<string, unknown>} */ (ops[capabilityKey])
285
+ : {};
286
+ ops[capabilityKey] = {
287
+ ...prev,
288
+ operationId: binding.operationId || prev.operationId || capabilityKey,
289
+ method: binding.method,
290
+ path: binding.path
291
+ };
292
+ }
293
+
294
+ function createCipQuery(capabilityKey, bindOptions) {
295
+ const cap = String(capabilityKey || '').toLowerCase();
296
+ const query = {};
297
+ const targets = Array.isArray(bindOptions.associationTargets)
298
+ ? bindOptions.associationTargets.map((row) => String(row || '').trim()).filter(Boolean)
299
+ : [];
300
+ if (targets.length > 0 && bindOptions.hasAssociationsQuery && (cap === 'list' || cap === 'get')) {
301
+ query.associations = targets.join(',');
302
+ }
303
+ const cursorPagination =
304
+ bindOptions.cursorPagination && typeof bindOptions.cursorPagination === 'object'
305
+ ? bindOptions.cursorPagination
306
+ : null;
307
+ if (cap === 'list' && cursorPagination) {
308
+ query[cursorPagination.pageSizeParam] = `{{${cursorPagination.pageSizeParam}}}`;
309
+ query[cursorPagination.cursorParam] = `{{${cursorPagination.cursorParam}}}`;
310
+ }
311
+ return { cap, query, cursorPagination };
312
+ }
313
+
314
+ function createCipSteps(capabilityKey, binding, cipQuery) {
315
+ const { cap, query, cursorPagination } = cipQuery;
316
+ const steps = [{ fetch: { source: 'openapi', openapiRef: capabilityKey, query } }];
317
+ if (cap === 'list' && /\/objects\//i.test(binding.path)) {
318
+ if (cursorPagination) {
319
+ steps.push({
320
+ paginate: {
321
+ strategy: 'cursor',
322
+ cursorField: cursorPagination.cursorField,
323
+ cursorParam: cursorPagination.cursorParam,
324
+ pageSizeParam: cursorPagination.pageSizeParam,
325
+ pageSize: 100,
326
+ maxPages: 100
327
+ }
328
+ });
329
+ }
330
+ steps.push({ map: { useFieldMappings: true, inputPath: '$.results[*]' } });
331
+ } else if (cap === 'get' || /^create/.test(cap) || /^update/.test(cap) || cap === 'delete' || cap === 'archive') {
332
+ steps.push({ map: { useFieldMappings: true, inputPath: '$' } });
333
+ }
334
+ steps.push({ output: { mode: 'records' } });
335
+ return steps;
336
+ }
337
+
338
+ function bindCipOperation(body, binding, capabilityKey, bindOptions) {
339
+ if (!body.execution || typeof body.execution !== 'object') {
340
+ body.execution = { engine: 'cip' };
341
+ }
342
+ const execution = /** @type {Record<string, unknown>} */ (body.execution);
343
+ if (!execution.cip || typeof execution.cip !== 'object') {
344
+ execution.cip = { version: '1.0', operations: {} };
345
+ }
346
+ const cip = /** @type {Record<string, unknown>} */ (execution.cip);
347
+ if (!cip.operations || typeof cip.operations !== 'object') {
348
+ cip.operations = {};
349
+ }
350
+ const cipOps = /** @type {Record<string, unknown>} */ (cip.operations);
351
+ const existing = cipOps[capabilityKey];
352
+ const steps = createCipSteps(capabilityKey, binding, createCipQuery(capabilityKey, bindOptions));
353
+ cipOps[capabilityKey] = {
354
+ ...(existing && typeof existing === 'object' ? existing : {}),
355
+ enabled: true,
356
+ description:
357
+ (existing &&
358
+ typeof existing === 'object' &&
359
+ /** @type {any} */ (existing).description) ||
360
+ `Execute ${capabilityKey} via OpenAPI`,
361
+ steps
362
+ };
363
+ }
364
+
365
+ function applyBindingToDatasource(body, binding, capabilityKey, bindOptions = {}) {
366
+ bindOpenApiOperation(body, binding, capabilityKey);
367
+ bindCipOperation(body, binding, capabilityKey, bindOptions);
368
+ }
369
+
370
+ /**
371
+ * Enrich datasource openapi.operations + CIP steps from an OpenAPI document.
372
+ * @param {Record<string, unknown>} body - datasource body (mutated)
373
+ * @param {string} openapiSourceRef
374
+ * @param {{ baseDir?: string, associationTargets?: string[] }} [options]
375
+ * @returns {string[]} capability keys that received method/path bindings
376
+ */
377
+ function enrichDatasourceFromOpenApi(body, openapiSourceRef, options = {}) {
378
+ const doc = loadOpenApiDocument(openapiSourceRef, options.baseDir);
379
+ const indexed = indexOpenApiOperations(doc);
380
+ const caps = Array.isArray(body.capabilities)
381
+ ? body.capabilities
382
+ .map((row) => (row && typeof row === 'object' ? row.key : row))
383
+ .filter((k) => typeof k === 'string' && k.trim())
384
+ : [];
385
+ const selfTypeCandidates = indexed
386
+ .map((op) => objectTypeFromOpenApiPath(op.path))
387
+ .filter(Boolean);
388
+ // Opaque OpenAPI paths (e.g. /objects/0-3) yield no segment — fall back to BID ds key.
389
+ const selfType =
390
+ selfTypeCandidates[0] ||
391
+ (typeof body.key === 'string' && body.key.trim() ? body.key.trim() : null);
392
+ const associationTargets = (
393
+ Array.isArray(options.associationTargets) ? options.associationTargets : []
394
+ )
395
+ .map((row) => String(row || '').trim())
396
+ .filter((row) => row && row !== selfType);
397
+ const bound = [];
398
+ let samplePath = '';
399
+ for (const capabilityKey of caps) {
400
+ const picked = pickOperationForCapability(capabilityKey, indexed);
401
+ if (!picked) {
402
+ continue;
403
+ }
404
+ applyBindingToDatasource(body, picked, capabilityKey, {
405
+ hasAssociationsQuery: operationDeclaresAssociationsQuery(
406
+ doc,
407
+ picked.method,
408
+ picked.path
409
+ ),
410
+ associationTargets,
411
+ cursorPagination: operationCursorPagination(doc, picked.method, picked.path)
412
+ });
413
+ bound.push(capabilityKey);
414
+ if (!samplePath) {
415
+ samplePath = picked.path;
416
+ }
417
+ }
418
+ if (samplePath) {
419
+ maybeEnrichCrmStyleFieldMappings(body, samplePath);
420
+ }
421
+ return bound;
422
+ }
423
+
424
+ module.exports = {
425
+ resolveOpenApiRefPath,
426
+ loadOpenApiDocument,
427
+ indexOpenApiOperations,
428
+ pickOperationForCapability,
429
+ enrichDatasourceFromOpenApi,
430
+ objectTypeFromOpenApiPath,
431
+ operationDeclaresAssociationsQuery,
432
+ operationCursorPagination
433
+ };