@aifabrix/builder 2.44.4 → 2.44.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +68 -17
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/types/wizard.types.js +2 -1
  17. package/lib/api/validation-runner.js +46 -25
  18. package/lib/app/deploy-config.js +11 -1
  19. package/lib/app/deploy-status-display.js +3 -3
  20. package/lib/app/deploy.js +36 -14
  21. package/lib/app/display.js +15 -11
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +1 -1
  24. package/lib/app/restart-display.js +95 -0
  25. package/lib/app/rotate-secret.js +1 -1
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +44 -12
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +99 -73
  32. package/lib/build/index.js +75 -45
  33. package/lib/cli/doctor-check.js +117 -0
  34. package/lib/cli/index.js +8 -2
  35. package/lib/cli/infra-guided.js +445 -0
  36. package/lib/cli/setup-app.help.js +1 -1
  37. package/lib/cli/setup-app.js +20 -2
  38. package/lib/cli/setup-app.test-commands.js +9 -5
  39. package/lib/cli/setup-auth.js +26 -0
  40. package/lib/cli/setup-dev-path-commands.js +50 -3
  41. package/lib/cli/setup-infra.js +138 -61
  42. package/lib/cli/setup-integration-client.js +182 -0
  43. package/lib/cli/setup-parameters.js +21 -2
  44. package/lib/cli/setup-platform.js +102 -0
  45. package/lib/cli/setup-secrets.js +18 -6
  46. package/lib/cli/setup-utility.js +97 -33
  47. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  48. package/lib/commands/datasource-capability-output.js +29 -0
  49. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  50. package/lib/commands/datasource-capability.js +411 -0
  51. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  52. package/lib/commands/datasource.js +53 -13
  53. package/lib/commands/dev-down.js +3 -3
  54. package/lib/commands/dev-infra-gate.js +32 -0
  55. package/lib/commands/dev-init.js +13 -7
  56. package/lib/commands/dimension-value.js +179 -0
  57. package/lib/commands/dimension.js +330 -0
  58. package/lib/commands/integration-client.js +430 -0
  59. package/lib/commands/login-device.js +65 -30
  60. package/lib/commands/login.js +21 -10
  61. package/lib/commands/parameters-validate.js +78 -13
  62. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  63. package/lib/commands/repair-datasource-keys.js +10 -5
  64. package/lib/commands/repair-datasource.js +19 -7
  65. package/lib/commands/repair-env-template.js +4 -1
  66. package/lib/commands/repair-openapi-sync.js +172 -0
  67. package/lib/commands/repair-persist.js +102 -0
  68. package/lib/commands/repair-rbac-extract.js +27 -0
  69. package/lib/commands/repair-rbac-migrate.js +186 -0
  70. package/lib/commands/repair-rbac.js +225 -19
  71. package/lib/commands/repair-system-alignment.js +246 -0
  72. package/lib/commands/repair-system-permissions.js +168 -0
  73. package/lib/commands/repair.js +120 -354
  74. package/lib/commands/secure.js +1 -1
  75. package/lib/commands/setup-modes.js +455 -0
  76. package/lib/commands/setup-prompts.js +388 -0
  77. package/lib/commands/setup.js +149 -0
  78. package/lib/commands/teardown.js +228 -0
  79. package/lib/commands/test-e2e-external.js +4 -3
  80. package/lib/commands/up-common.js +97 -12
  81. package/lib/commands/up-dataplane.js +33 -11
  82. package/lib/commands/up-miso.js +7 -11
  83. package/lib/commands/upload.js +109 -23
  84. package/lib/commands/wizard-core-helpers.js +14 -11
  85. package/lib/commands/wizard-core.js +58 -15
  86. package/lib/commands/wizard-dataplane.js +2 -2
  87. package/lib/commands/wizard-entity-selection.js +72 -14
  88. package/lib/commands/wizard-headless.js +7 -3
  89. package/lib/commands/wizard-helpers.js +13 -1
  90. package/lib/commands/wizard.js +210 -61
  91. package/lib/constants/infra-compose-service-names.js +40 -0
  92. package/lib/core/env-reader.js +16 -3
  93. package/lib/core/secrets-admin-env.js +101 -0
  94. package/lib/core/secrets-ensure-infra.js +34 -1
  95. package/lib/core/secrets-ensure.js +88 -66
  96. package/lib/core/secrets-env-content.js +432 -0
  97. package/lib/core/secrets-env-write.js +27 -1
  98. package/lib/core/secrets-load.js +248 -0
  99. package/lib/core/secrets-names.js +32 -0
  100. package/lib/core/secrets.js +17 -757
  101. package/lib/datasource/capability/basic-exposure.js +76 -0
  102. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  103. package/lib/datasource/capability/capability-key.js +34 -0
  104. package/lib/datasource/capability/capability-resolve.js +172 -0
  105. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  106. package/lib/datasource/capability/copy-operations.js +348 -0
  107. package/lib/datasource/capability/copy-test-payload.js +139 -0
  108. package/lib/datasource/capability/create-operations.js +235 -0
  109. package/lib/datasource/capability/dimension-operations.js +151 -0
  110. package/lib/datasource/capability/dimension-validate.js +219 -0
  111. package/lib/datasource/capability/json-pointer.js +31 -0
  112. package/lib/datasource/capability/reference-rewrite.js +51 -0
  113. package/lib/datasource/capability/relate-operations.js +325 -0
  114. package/lib/datasource/capability/relate-validate.js +219 -0
  115. package/lib/datasource/capability/remove-operations.js +275 -0
  116. package/lib/datasource/capability/run-capability-copy.js +152 -0
  117. package/lib/datasource/capability/run-capability-diff.js +135 -0
  118. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  119. package/lib/datasource/capability/run-capability-edit.js +377 -0
  120. package/lib/datasource/capability/run-capability-relate.js +193 -0
  121. package/lib/datasource/capability/run-capability-remove.js +105 -0
  122. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  123. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  124. package/lib/datasource/list.js +136 -23
  125. package/lib/datasource/log-viewer.js +2 -4
  126. package/lib/datasource/unified-validation-run.js +51 -16
  127. package/lib/datasource/validate.js +53 -1
  128. package/lib/deployment/deploy-poll-ui.js +60 -0
  129. package/lib/deployment/deployer-status.js +29 -3
  130. package/lib/deployment/deployer.js +48 -30
  131. package/lib/deployment/environment.js +7 -2
  132. package/lib/deployment/poll-interval.js +72 -0
  133. package/lib/deployment/push.js +11 -9
  134. package/lib/external-system/deploy.js +4 -1
  135. package/lib/external-system/download.js +61 -32
  136. package/lib/external-system/sync-deploy-manifest.js +33 -0
  137. package/lib/generator/wizard-prompts.js +7 -1
  138. package/lib/generator/wizard.js +34 -0
  139. package/lib/infrastructure/index.js +49 -19
  140. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  141. package/lib/parameters/infra-kv-discovery.js +29 -4
  142. package/lib/parameters/infra-parameter-catalog.js +6 -3
  143. package/lib/parameters/infra-parameter-validate.js +67 -19
  144. package/lib/resolvers/datasource-resolver.js +53 -0
  145. package/lib/resolvers/dimension-file.js +52 -0
  146. package/lib/resolvers/manifest-resolver.js +133 -0
  147. package/lib/schema/external-datasource.schema.json +183 -53
  148. package/lib/schema/external-system.schema.json +23 -10
  149. package/lib/schema/infra.parameter.yaml +26 -11
  150. package/lib/schema/wizard-config.schema.json +2 -2
  151. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  152. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  153. package/lib/utils/app-run-containers.js +2 -2
  154. package/lib/utils/bash-secret-env.js +59 -0
  155. package/lib/utils/cli-secrets-error-format.js +78 -0
  156. package/lib/utils/cli-test-layout-chalk.js +31 -9
  157. package/lib/utils/cli-utils.js +4 -36
  158. package/lib/utils/datasource-test-run-display.js +8 -0
  159. package/lib/utils/dev-hosts-helper.js +3 -2
  160. package/lib/utils/dev-init-ssh-merge.js +2 -1
  161. package/lib/utils/docker-build.js +17 -9
  162. package/lib/utils/docker-reload-mount.js +127 -0
  163. package/lib/utils/external-readme.js +117 -4
  164. package/lib/utils/external-system-local-test-tty.js +3 -2
  165. package/lib/utils/external-system-readiness-core.js +45 -12
  166. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  167. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  168. package/lib/utils/external-system-readiness-display.js +10 -1
  169. package/lib/utils/file-upload.js +40 -3
  170. package/lib/utils/health-check-db-init.js +107 -0
  171. package/lib/utils/health-check-public-warn.js +69 -0
  172. package/lib/utils/health-check-url.js +19 -4
  173. package/lib/utils/health-check.js +135 -105
  174. package/lib/utils/help-builder.js +5 -1
  175. package/lib/utils/image-name.js +34 -7
  176. package/lib/utils/integration-file-backup.js +74 -0
  177. package/lib/utils/mutagen-install.js +30 -3
  178. package/lib/utils/paths.js +108 -25
  179. package/lib/utils/postgres-wipe.js +212 -0
  180. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  181. package/lib/utils/remote-dev-auth.js +21 -5
  182. package/lib/utils/remote-docker-env.js +9 -1
  183. package/lib/utils/remote-secrets-loader.js +42 -3
  184. package/lib/utils/resolve-docker-image-ref.js +9 -3
  185. package/lib/utils/secrets-ancestor-paths.js +47 -0
  186. package/lib/utils/secrets-helpers.js +17 -10
  187. package/lib/utils/secrets-kv-refs.js +42 -0
  188. package/lib/utils/secrets-kv-scope.js +19 -2
  189. package/lib/utils/secrets-materialize-local.js +134 -0
  190. package/lib/utils/secrets-path.js +24 -10
  191. package/lib/utils/secrets-utils.js +2 -2
  192. package/lib/utils/system-builder-root.js +34 -0
  193. package/lib/utils/url-declarative-resolve-build.js +6 -1
  194. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  195. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  196. package/lib/utils/urls-local-registry.js +73 -20
  197. package/lib/utils/validation-poll-ui.js +81 -0
  198. package/lib/utils/validation-run-poll.js +29 -5
  199. package/lib/utils/with-muted-logger.js +53 -0
  200. package/package.json +1 -1
  201. package/templates/applications/dataplane/application.yaml +1 -1
  202. package/templates/applications/dataplane/rbac.yaml +10 -10
  203. package/templates/applications/keycloak/env.template +8 -6
  204. package/templates/applications/miso-controller/application.yaml +7 -0
  205. package/templates/applications/miso-controller/env.template +7 -7
  206. package/templates/applications/miso-controller/rbac.yaml +9 -9
  207. package/templates/external-system/README.md.hbs +89 -102
  208. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  209. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  210. package/.nyc_output/processinfo/index.json +0 -1
  211. package/lib/api/service-users.api.js +0 -150
  212. package/lib/api/types/service-users.types.js +0 -65
  213. package/lib/cli/setup-service-user.js +0 -187
  214. package/lib/commands/service-user.js +0 -429
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Clone testPayload.scenarios rows when copying a capability.
3
+ *
4
+ * @fileoverview Used by applyCapabilityCopy when includeTestPayload is true
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const { jsonPointerPath } = require('./json-pointer');
10
+ const { storageOpsKey } = require('./capability-storage-keys');
11
+
12
+ /**
13
+ * @param {unknown} o
14
+ * @returns {unknown}
15
+ */
16
+ function deepClone(o) {
17
+ return JSON.parse(JSON.stringify(o));
18
+ }
19
+
20
+ /**
21
+ * @param {object[]} initialScenarios
22
+ * @param {Set<string>} aliases
23
+ * @param {string} resolvedAs
24
+ * @param {boolean} overwrite
25
+ * @returns {{ sourceMatches: object[], finalScenarios: object[] }}
26
+ */
27
+ function computeScenarioClone(initialScenarios, aliases, resolvedAs, overwrite) {
28
+ const sourceMatches = initialScenarios.filter((s) => s && aliases.has(s.operation));
29
+ let base = [...initialScenarios];
30
+ if (overwrite) {
31
+ const lower = String(resolvedAs).toLowerCase();
32
+ base = base.filter((s) => !s || String(s.operation).toLowerCase() !== lower);
33
+ }
34
+ const opKey = storageOpsKey(resolvedAs);
35
+ const clones = sourceMatches.map((s) => {
36
+ const c = deepClone(s);
37
+ c.operation = opKey;
38
+ return c;
39
+ });
40
+ return { sourceMatches, clones, finalScenarios: [...base, ...clones] };
41
+ }
42
+
43
+ /**
44
+ * Dry-run JSON Patch: only the additions (incremental array append or new testPayload.scenarios).
45
+ *
46
+ * @param {boolean} hadTestPayloadRoot
47
+ * @param {boolean} hadScenariosArray
48
+ * @param {object[]} clones - New scenario rows only
49
+ * @param {object[]} patchOperations
50
+ * @returns {void}
51
+ */
52
+ function pushTestPayloadScenarioPatches(
53
+ hadTestPayloadRoot,
54
+ hadScenariosArray,
55
+ clones,
56
+ patchOperations
57
+ ) {
58
+ if (clones.length === 0) {
59
+ return;
60
+ }
61
+ const clonedRows = clones.map((c) => deepClone(c));
62
+
63
+ if (!hadTestPayloadRoot) {
64
+ patchOperations.push({
65
+ op: 'add',
66
+ path: '/testPayload',
67
+ value: { scenarios: clonedRows }
68
+ });
69
+ return;
70
+ }
71
+ if (!hadScenariosArray) {
72
+ patchOperations.push({
73
+ op: 'add',
74
+ path: jsonPointerPath('testPayload', 'scenarios'),
75
+ value: clonedRows
76
+ });
77
+ return;
78
+ }
79
+ for (const row of clonedRows) {
80
+ patchOperations.push({
81
+ op: 'add',
82
+ path: '/testPayload/scenarios/-',
83
+ value: row
84
+ });
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @param {object} doc
90
+ * @param {object} opts
91
+ * @param {{ openapiKey: string, cipKey: string, logicalFrom: string }} fromKeys
92
+ * @param {string} resolvedAs
93
+ * @param {object[]} patchOperations
94
+ * @param {string[]} updatedSections
95
+ * @returns {void}
96
+ */
97
+ function copyTestPayloadScenarios(doc, opts, fromKeys, resolvedAs, patchOperations, updatedSections) {
98
+ if (!opts.includeTestPayload) {
99
+ return;
100
+ }
101
+ const { openapiKey, cipKey, logicalFrom } = fromKeys;
102
+ const aliases = new Set([logicalFrom, openapiKey, cipKey].filter(Boolean));
103
+
104
+ const initialScenarios = Array.isArray(doc.testPayload?.scenarios)
105
+ ? doc.testPayload.scenarios
106
+ : [];
107
+
108
+ const hadTestPayloadRoot =
109
+ doc.testPayload !== undefined &&
110
+ doc.testPayload !== null &&
111
+ typeof doc.testPayload === 'object';
112
+ const hadScenariosArray = hadTestPayloadRoot && Array.isArray(doc.testPayload.scenarios);
113
+
114
+ const { sourceMatches, clones, finalScenarios } = computeScenarioClone(
115
+ initialScenarios,
116
+ aliases,
117
+ resolvedAs,
118
+ Boolean(opts.overwrite)
119
+ );
120
+
121
+ if (sourceMatches.length === 0) {
122
+ updatedSections.push('testPayload.scenarios: (no rows matched source operation)');
123
+ return;
124
+ }
125
+
126
+ if (!doc.testPayload) {
127
+ doc.testPayload = {};
128
+ }
129
+ doc.testPayload.scenarios = finalScenarios;
130
+
131
+ pushTestPayloadScenarioPatches(hadTestPayloadRoot, hadScenariosArray, clones, patchOperations);
132
+ updatedSections.push(
133
+ `testPayload.scenarios: +${sourceMatches.length} clone(s) → operation "${resolvedAs}"`
134
+ );
135
+ }
136
+
137
+ module.exports = {
138
+ copyTestPayloadScenarios
139
+ };
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Create capability without --from: OpenAPI operationId match or JSON template.
3
+ *
4
+ * @fileoverview Plan 132 Phase 3 — template / openapi-operation create
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const path = require('path');
10
+ const fsReal = require('../../internal/fs-real-sync');
11
+ const {
12
+ applyCapabilityCopy,
13
+ deepClone,
14
+ removeCapability,
15
+ installPreparedSlices,
16
+ appendCapabilityList,
17
+ copyExposedProfile,
18
+ assertBasicExposureSlot,
19
+ resolveTargetKey
20
+ } = require('./copy-operations');
21
+ const { rewriteCapabilityReferences } = require('./reference-rewrite');
22
+ const { storageOpsKey } = require('./capability-storage-keys');
23
+ const { capabilityLogicalExists } = require('./capability-resolve');
24
+
25
+ /**
26
+ * @param {string} targetOpsKey
27
+ * @returns {object}
28
+ */
29
+ function buildMinimalCip(targetOpsKey) {
30
+ return {
31
+ enabled: true,
32
+ steps: [{ fetch: { source: 'openapi', openapiRef: targetOpsKey } }]
33
+ };
34
+ }
35
+
36
+ /**
37
+ * @param {object} originalDoc
38
+ * @param {object} opts
39
+ * @returns {{ doc: object, resolvedAs: string, targetOpsKey: string }}
40
+ */
41
+ function prepareDocForNewCapability(originalDoc, opts) {
42
+ const doc = deepClone(originalDoc);
43
+ const resolvedAs = resolveTargetKey(doc, opts.to, Boolean(opts.overwrite));
44
+ assertBasicExposureSlot(doc, resolvedAs, Boolean(opts.overwrite), Boolean(opts.basicExposure));
45
+ const targetOpsKey = storageOpsKey(resolvedAs);
46
+ if (opts.overwrite && capabilityLogicalExists(doc, resolvedAs)) {
47
+ removeCapability(doc, resolvedAs);
48
+ }
49
+ return { doc, resolvedAs, targetOpsKey };
50
+ }
51
+
52
+ /**
53
+ * @param {object} doc
54
+ * @param {string} resolvedAs
55
+ * @param {{ targetOpsKey: string, openapiClone: object, cipClone: object }} slices
56
+ * @param {object} opts
57
+ * @param {object[]} patchOperations
58
+ * @param {string[]} updatedSections
59
+ * @returns {void}
60
+ */
61
+ function finalizeNewCapabilitySlices(doc, resolvedAs, slices, opts, patchOperations, updatedSections) {
62
+ installPreparedSlices(doc, resolvedAs, slices, patchOperations, updatedSections);
63
+ appendCapabilityList(doc, resolvedAs, patchOperations, updatedSections);
64
+ copyExposedProfile(
65
+ doc,
66
+ { basicExposure: Boolean(opts.basicExposure), includeTestPayload: false },
67
+ resolvedAs,
68
+ resolvedAs,
69
+ patchOperations,
70
+ updatedSections
71
+ );
72
+ }
73
+
74
+ /**
75
+ * @param {object} doc
76
+ * @param {string} operationId
77
+ * @returns {string[]}
78
+ */
79
+ function findOpenapiKeysByOperationId(doc, operationId) {
80
+ const ops = doc.openapi?.operations;
81
+ if (!ops || typeof ops !== 'object') {
82
+ return [];
83
+ }
84
+ const want = String(operationId).trim().toLowerCase();
85
+ const keys = [];
86
+ for (const k of Object.keys(ops)) {
87
+ const oid = ops[k]?.operationId;
88
+ if (oid !== undefined && String(oid).trim().toLowerCase() === want) {
89
+ keys.push(k);
90
+ }
91
+ }
92
+ return keys;
93
+ }
94
+
95
+ /**
96
+ * @param {object} originalDoc
97
+ * @param {object} opts
98
+ * @param {string} opts.to
99
+ * @param {string} opts.openApiOperationId
100
+ * @param {boolean} [opts.overwrite]
101
+ * @param {boolean} [opts.basicExposure]
102
+ * @returns {object}
103
+ */
104
+ function applyCreateFromOpenApiOperation(originalDoc, opts) {
105
+ const operationId = String(opts.openApiOperationId).trim();
106
+ const keys = findOpenapiKeysByOperationId(originalDoc, operationId);
107
+ if (keys.length === 0) {
108
+ throw new Error(`No openapi.operations entry with operationId "${operationId}"`);
109
+ }
110
+ if (keys.length > 1) {
111
+ throw new Error(
112
+ `Ambiguous operationId "${operationId}": openapi.operations keys ${keys.join(', ')}`
113
+ );
114
+ }
115
+ const sourceOpenapiKey = keys[0];
116
+ const { doc, resolvedAs, targetOpsKey } = prepareDocForNewCapability(originalDoc, opts);
117
+
118
+ const openapiClone = deepClone(doc.openapi.operations[sourceOpenapiKey]);
119
+ rewriteCapabilityReferences(openapiClone, sourceOpenapiKey, targetOpsKey);
120
+
121
+ const patchOperations = [];
122
+ const updatedSections = [];
123
+ finalizeNewCapabilitySlices(
124
+ doc,
125
+ resolvedAs,
126
+ { targetOpsKey, openapiClone, cipClone: buildMinimalCip(targetOpsKey) },
127
+ opts,
128
+ patchOperations,
129
+ updatedSections
130
+ );
131
+
132
+ return {
133
+ doc,
134
+ resolvedAs,
135
+ patchOperations,
136
+ updatedSections
137
+ };
138
+ }
139
+
140
+ /**
141
+ * @param {object} originalDoc
142
+ * @param {object} opts
143
+ * @param {string} opts.to
144
+ * @param {string} opts.template
145
+ * @param {boolean} [opts.overwrite]
146
+ * @param {boolean} [opts.basicExposure]
147
+ * @returns {object}
148
+ */
149
+ function applyCreateFromTemplate(originalDoc, opts) {
150
+ const name = String(opts.template).trim();
151
+ const templatePath = path.join(__dirname, 'templates', `${name}.json`);
152
+ if (!fsReal.existsSync(templatePath)) {
153
+ throw new Error(`Unknown template "${name}" (not found next to capability templates)`);
154
+ }
155
+ const template = _readCapabilityTemplateOrThrow(templatePath, name);
156
+ if (!template.openapiOperation || !template.cipOperation) {
157
+ throw new Error('Template must include top-level openapiOperation and cipOperation objects');
158
+ }
159
+
160
+ const { doc, resolvedAs, targetOpsKey } = prepareDocForNewCapability(originalDoc, opts);
161
+ const openapiClone = deepClone(template.openapiOperation);
162
+ const cipJson = JSON.stringify(template.cipOperation).replace(/__STORAGE_KEY__/g, targetOpsKey);
163
+ const cipClone = JSON.parse(cipJson);
164
+
165
+ const patchOperations = [];
166
+ const updatedSections = [];
167
+ finalizeNewCapabilitySlices(
168
+ doc,
169
+ resolvedAs,
170
+ { targetOpsKey, openapiClone, cipClone },
171
+ opts,
172
+ patchOperations,
173
+ updatedSections
174
+ );
175
+
176
+ return {
177
+ doc,
178
+ resolvedAs,
179
+ patchOperations,
180
+ updatedSections
181
+ };
182
+ }
183
+
184
+ function _readCapabilityTemplateOrThrow(templatePath, name) {
185
+ let templateRaw;
186
+ try {
187
+ templateRaw = fsReal.readFileSync(templatePath, 'utf8');
188
+ } catch (e) {
189
+ // Jest suites may partially mock fs.existsSync; treat missing files as unknown template.
190
+ if (e && e.code === 'ENOENT') {
191
+ throw new Error(`Unknown template "${name}" (not found next to capability templates)`);
192
+ }
193
+ throw e;
194
+ }
195
+ return JSON.parse(templateRaw);
196
+ }
197
+
198
+ /**
199
+ * Exactly one of: opts.from, opts.openApiOperationId, opts.template.
200
+ *
201
+ * @param {object} originalDoc
202
+ * @param {object} opts
203
+ * @returns {object}
204
+ */
205
+ function applyCapabilityCreate(originalDoc, opts) {
206
+ const hasFrom = opts.from !== undefined && opts.from !== null && String(opts.from).trim() !== '';
207
+ const hasOid =
208
+ opts.openApiOperationId !== undefined &&
209
+ opts.openApiOperationId !== null &&
210
+ String(opts.openApiOperationId).trim() !== '';
211
+ const hasTpl =
212
+ opts.template !== undefined && opts.template !== null && String(opts.template).trim() !== '';
213
+
214
+ const count = [hasFrom, hasOid, hasTpl].filter(Boolean).length;
215
+ if (count !== 1) {
216
+ throw new Error(
217
+ 'Specify exactly one of: --from <key>, --template <name>, --openapi-operation <operationId>'
218
+ );
219
+ }
220
+
221
+ if (hasFrom) {
222
+ return applyCapabilityCopy(originalDoc, opts);
223
+ }
224
+ if (hasOid) {
225
+ return applyCreateFromOpenApiOperation(originalDoc, opts);
226
+ }
227
+ return applyCreateFromTemplate(originalDoc, opts);
228
+ }
229
+
230
+ module.exports = {
231
+ applyCapabilityCreate,
232
+ applyCreateFromOpenApiOperation,
233
+ applyCreateFromTemplate,
234
+ findOpenapiKeysByOperationId
235
+ };
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Metadata-only dimension binding updates for datasource JSON.
3
+ *
4
+ * Writes root `dimensions.<dimensionKey>` binding (local or fk) with JSON Patch operations.
5
+ *
6
+ * @fileoverview capability dimension — dimensions{} binding upsert
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const { jsonPointerPath } = require('./json-pointer');
14
+
15
+ /**
16
+ * @param {unknown} o
17
+ * @returns {any}
18
+ */
19
+ function deepClone(o) {
20
+ return JSON.parse(JSON.stringify(o));
21
+ }
22
+
23
+ function _asObject(x) {
24
+ return x && typeof x === 'object' && !Array.isArray(x) ? x : null;
25
+ }
26
+
27
+ /**
28
+ * @param {string} raw
29
+ * @returns {string}
30
+ */
31
+ function normalizeDimensionKey(raw) {
32
+ const s = String(raw || '').trim();
33
+ if (!s) {
34
+ throw new Error('--dimension <key> is required');
35
+ }
36
+ if (!/^[a-zA-Z0-9_]+$/.test(s)) {
37
+ throw new Error(`--dimension "${s}" must match ^[a-zA-Z0-9_]+$`);
38
+ }
39
+ return s;
40
+ }
41
+
42
+ /**
43
+ * @param {string} raw
44
+ * @returns {'local'|'fk'}
45
+ */
46
+ function normalizeDimensionType(raw) {
47
+ const s = String(raw || '').trim().toLowerCase();
48
+ if (!s) {
49
+ throw new Error('--type <local|fk> is required');
50
+ }
51
+ if (s !== 'local' && s !== 'fk') {
52
+ throw new Error('--type must be "local" or "fk"');
53
+ }
54
+ return s;
55
+ }
56
+
57
+ /**
58
+ * @param {any} doc
59
+ * @param {string} dimKey
60
+ * @param {boolean} overwrite
61
+ * @returns {boolean} replaced
62
+ */
63
+ function assertOverwriteAllowed(doc, dimKey, overwrite) {
64
+ const dims = doc?.dimensions;
65
+ const exists = Boolean(dims && typeof dims === 'object' && !Array.isArray(dims) && dims[dimKey] !== undefined);
66
+ if (exists && !overwrite) {
67
+ throw new Error(`dimensions.${dimKey} already exists; pass --overwrite to replace`);
68
+ }
69
+ return exists;
70
+ }
71
+
72
+ /**
73
+ * @param {object} opts
74
+ * @returns {object} dimensionBinding
75
+ */
76
+ function buildDimensionBinding(opts) {
77
+ const type = normalizeDimensionType(opts.type);
78
+
79
+ if (type === 'local') {
80
+ const field = String(opts.field || '').trim();
81
+ if (!field) {
82
+ throw new Error('--field <normalizedAttr> is required for --type local');
83
+ }
84
+ return { type: 'local', field };
85
+ }
86
+
87
+ const via = Array.isArray(opts.via) ? opts.via : [];
88
+ if (via.length === 0) {
89
+ throw new Error('Provide at least one --via <fkName>:<dimensionKey> for --type fk');
90
+ }
91
+
92
+ /** @type {any} */
93
+ const binding = { type: 'fk', via };
94
+ if (opts.actor !== undefined && opts.actor !== null && String(opts.actor).trim()) {
95
+ binding.actor = String(opts.actor).trim();
96
+ }
97
+ if (opts.operator !== undefined && opts.operator !== null && String(opts.operator).trim()) {
98
+ binding.operator = String(opts.operator).trim();
99
+ }
100
+ if (opts.required !== undefined && opts.required !== null) {
101
+ binding.required = Boolean(opts.required);
102
+ }
103
+ return binding;
104
+ }
105
+
106
+ /**
107
+ * @param {any} doc
108
+ * @param {object} opts
109
+ * @returns {{
110
+ * doc: any,
111
+ * patchOperations: object[],
112
+ * updatedSections: string[],
113
+ * replaced: boolean
114
+ * }}
115
+ */
116
+ function applyCapabilityDimension(doc, opts) {
117
+ const dimKey = normalizeDimensionKey(opts.dimension);
118
+ const d = deepClone(doc);
119
+
120
+ if (!_asObject(d.dimensions)) {
121
+ d.dimensions = {};
122
+ }
123
+
124
+ const replaced = assertOverwriteAllowed(d, dimKey, Boolean(opts.overwrite));
125
+ const binding = buildDimensionBinding(opts);
126
+
127
+ /** @type {object[]} */
128
+ const patchOperations = [];
129
+ /** @type {string[]} */
130
+ const updatedSections = [];
131
+
132
+ const path = jsonPointerPath('dimensions', dimKey);
133
+ if (replaced) {
134
+ d.dimensions[dimKey] = binding;
135
+ patchOperations.push({ op: 'replace', path, value: binding });
136
+ } else {
137
+ d.dimensions[dimKey] = binding;
138
+ patchOperations.push({ op: 'add', path, value: binding });
139
+ }
140
+ updatedSections.push(`dimensions.${dimKey}`);
141
+
142
+ return { doc: d, patchOperations, updatedSections, replaced };
143
+ }
144
+
145
+ module.exports = {
146
+ applyCapabilityDimension,
147
+ normalizeDimensionKey,
148
+ normalizeDimensionType,
149
+ buildDimensionBinding
150
+ };
151
+