@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.
- package/.cursor/rules/cli-layout.mdc +1 -1
- package/.cursor/rules/project-rules.mdc +1 -1
- package/.npmrc.token +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +68 -17
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/types/wizard.types.js +2 -1
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +1 -1
- package/lib/app/restart-display.js +95 -0
- package/lib/app/rotate-secret.js +1 -1
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +44 -12
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +99 -73
- package/lib/build/index.js +75 -45
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +445 -0
- package/lib/cli/setup-app.help.js +1 -1
- package/lib/cli/setup-app.js +20 -2
- package/lib/cli/setup-app.test-commands.js +9 -5
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +138 -61
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility.js +97 -33
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +225 -19
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -354
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +455 -0
- package/lib/commands/setup-prompts.js +388 -0
- package/lib/commands/setup.js +149 -0
- package/lib/commands/teardown.js +228 -0
- package/lib/commands/test-e2e-external.js +4 -3
- package/lib/commands/up-common.js +97 -12
- package/lib/commands/up-dataplane.js +33 -11
- package/lib/commands/up-miso.js +7 -11
- package/lib/commands/upload.js +109 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +58 -15
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +72 -14
- package/lib/commands/wizard-headless.js +7 -3
- package/lib/commands/wizard-helpers.js +13 -1
- package/lib/commands/wizard.js +210 -61
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +432 -0
- package/lib/core/secrets-env-write.js +27 -1
- package/lib/core/secrets-load.js +248 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +4 -1
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/generator/wizard-prompts.js +7 -1
- package/lib/generator/wizard.js +34 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -11
- package/lib/schema/wizard-config.schema.json +2 -2
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/external-readme.js +117 -4
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +19 -4
- package/lib/utils/health-check.js +135 -105
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +108 -25
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +42 -3
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +24 -10
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/system-builder-root.js +34 -0
- package/lib/utils/url-declarative-resolve-build.js +6 -1
- package/lib/utils/url-declarative-runtime-base-path.js +32 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry.js +73 -20
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +7 -0
- package/templates/applications/miso-controller/env.template +7 -7
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +89 -102
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- 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
|
+
|