@aifabrix/builder 2.44.5 → 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 +48 -2
- 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/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.js +20 -2
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +134 -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 +78 -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 +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- 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/up-common.js +79 -19
- 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 +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- 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/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 +1 -1
- 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 +71 -2
- 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 +23 -12
- 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 +1 -1
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- 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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a minimal exposed.profiles row from metadataSchema (primitives + required fields).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Minimal exposed.profiles row from metadataSchema (used by applyCapabilityCopy when basicExposure)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const PRIMITIVE = new Set(['string', 'number', 'integer', 'boolean']);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} prop - JSON Schema property
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
function isPrimitiveSchemaProperty(prop) {
|
|
16
|
+
if (!prop || typeof prop !== 'object') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const t = prop.type;
|
|
20
|
+
if (typeof t === 'string') {
|
|
21
|
+
return PRIMITIVE.has(t);
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(t)) {
|
|
24
|
+
return t.some((x) => PRIMITIVE.has(x));
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Attribute names for a minimal array-style profile: required primitives + all primitive properties.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} parsed - Datasource JSON
|
|
33
|
+
* @returns {string[]} Sorted unique attribute names
|
|
34
|
+
*/
|
|
35
|
+
function listBasicExposureAttributes(parsed) {
|
|
36
|
+
const ms = parsed.metadataSchema;
|
|
37
|
+
const properties = ms && typeof ms === 'object' ? ms.properties : null;
|
|
38
|
+
if (!properties || typeof properties !== 'object') {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const required = Array.isArray(ms.required) ? ms.required : [];
|
|
42
|
+
const names = new Set();
|
|
43
|
+
for (const key of required) {
|
|
44
|
+
if (properties[key] && isPrimitiveSchemaProperty(properties[key])) {
|
|
45
|
+
names.add(key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
49
|
+
if (isPrimitiveSchemaProperty(prop)) {
|
|
50
|
+
names.add(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return [...names].sort();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {object} parsed - Datasource JSON
|
|
58
|
+
* @returns {string[]}
|
|
59
|
+
* @throws {Error} When nothing can be derived
|
|
60
|
+
*/
|
|
61
|
+
function buildBasicExposureProfileArray(parsed) {
|
|
62
|
+
const attrs = listBasicExposureAttributes(parsed);
|
|
63
|
+
if (attrs.length === 0) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'Cannot build basic exposure: metadataSchema.properties has no primitive fields (or missing metadataSchema). ' +
|
|
66
|
+
'Add primitive fields to metadataSchema, copy an existing exposed.profiles.<from> via capability copy, or choose another target key.'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return attrs;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
buildBasicExposureProfileArray,
|
|
74
|
+
listBasicExposureAttributes,
|
|
75
|
+
isPrimitiveSchemaProperty
|
|
76
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract a comparable subtree for `capability diff`.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview capability slice extraction for diff
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { deepClone } = require('./copy-operations');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} doc - Parsed datasource JSON
|
|
13
|
+
* @param {string} capabilityKey - Normalized capability key
|
|
14
|
+
* @param {string} [profileKey] - Optional exposed.profiles key
|
|
15
|
+
* @returns {object} Stable-shape slice for compareObjects
|
|
16
|
+
*/
|
|
17
|
+
function extractCapabilitySliceForDiff(doc, capabilityKey, profileKey) {
|
|
18
|
+
const cap = capabilityKey;
|
|
19
|
+
const listed =
|
|
20
|
+
Boolean(Array.isArray(doc.capabilities) && doc.capabilities.includes(cap));
|
|
21
|
+
const openapiOp = doc.openapi?.operations?.[cap];
|
|
22
|
+
const cipOp = doc.execution?.cip?.operations?.[cap];
|
|
23
|
+
let exposedProfile;
|
|
24
|
+
if (profileKey && String(profileKey).trim()) {
|
|
25
|
+
const pk = String(profileKey).trim();
|
|
26
|
+
const raw = doc.exposed?.profiles?.[pk];
|
|
27
|
+
exposedProfile = raw !== undefined ? deepClone(raw) : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
capabilityKey: cap,
|
|
32
|
+
listedInCapabilities: listed,
|
|
33
|
+
openapiOperation: openapiOp !== undefined ? deepClone(openapiOp) : undefined,
|
|
34
|
+
cipOperation: cipOp !== undefined ? deepClone(cipOp) : undefined,
|
|
35
|
+
exposedProfile
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
extractCapabilitySliceForDiff
|
|
41
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize and validate external datasource capability keys.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Capability key helpers for datasource capability CLI
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Pattern aligned with external-datasource.schema.json capabilities[].items */
|
|
10
|
+
const CAPABILITY_KEY_PATTERN = /^[a-z][a-zA-Z0-9_]*$/;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} raw - Raw CLI input
|
|
14
|
+
* @param {string} label - Option label for errors
|
|
15
|
+
* @returns {string} Normalized key
|
|
16
|
+
* @throws {Error} If empty or invalid
|
|
17
|
+
*/
|
|
18
|
+
function normalizeCapabilityKey(raw, label = 'capability') {
|
|
19
|
+
const s = String(raw || '').trim();
|
|
20
|
+
if (!s) {
|
|
21
|
+
throw new Error(`${label} key is required`);
|
|
22
|
+
}
|
|
23
|
+
if (!CAPABILITY_KEY_PATTERN.test(s)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`${label} key "${s}" is invalid: must match ${CAPABILITY_KEY_PATTERN}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
normalizeCapabilityKey,
|
|
33
|
+
CAPABILITY_KEY_PATTERN
|
|
34
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map logical capability names (capabilities[] / profiles) to openapi + CIP operation object keys.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview capabilities-first resolution when OpenAPI uses different casing
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {object|undefined} operations
|
|
11
|
+
* @param {string} logicalKey
|
|
12
|
+
* @returns {string[]}
|
|
13
|
+
*/
|
|
14
|
+
function findMatchingOpsKeys(operations, logicalKey) {
|
|
15
|
+
if (!operations || typeof operations !== 'object') {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
if (operations[logicalKey] !== undefined) {
|
|
19
|
+
return [logicalKey];
|
|
20
|
+
}
|
|
21
|
+
const lower = String(logicalKey).toLowerCase();
|
|
22
|
+
return Object.keys(operations).filter((k) => k.toLowerCase() === lower);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object|undefined} operations
|
|
27
|
+
* @param {string} logicalKey
|
|
28
|
+
* @param {string} label - For error messages
|
|
29
|
+
* @returns {string|null} Single match, or null
|
|
30
|
+
* @throws {Error} When multiple keys differ only by case
|
|
31
|
+
*/
|
|
32
|
+
function resolveSingleOpsKey(operations, logicalKey, label) {
|
|
33
|
+
const matches = findMatchingOpsKeys(operations, logicalKey);
|
|
34
|
+
if (matches.length === 1) {
|
|
35
|
+
return matches[0];
|
|
36
|
+
}
|
|
37
|
+
if (matches.length === 0) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Ambiguous ${label} operations keys for "${logicalKey}": ${matches.join(', ')}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* For --from on copy: when capabilities[] is non-empty, the name must appear there (case-insensitive).
|
|
47
|
+
*
|
|
48
|
+
* @param {object} doc
|
|
49
|
+
* @param {string} userKey
|
|
50
|
+
* @returns {string} Canonical string from capabilities[] or userKey when no list
|
|
51
|
+
* @throws {Error} When capabilities is non-empty and no entry matches
|
|
52
|
+
*/
|
|
53
|
+
function resolveSourceCapabilityForCopy(doc, userKey) {
|
|
54
|
+
const u = String(userKey).trim();
|
|
55
|
+
const caps = doc.capabilities;
|
|
56
|
+
if (Array.isArray(caps) && caps.length > 0) {
|
|
57
|
+
const hit = caps.find((c) => String(c).toLowerCase() === u.toLowerCase());
|
|
58
|
+
if (hit === undefined) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Source capability "${userKey}" not found in capabilities[]. ` +
|
|
61
|
+
'Use a name that appears in capabilities[] (case-insensitive match is allowed).'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return String(hit);
|
|
65
|
+
}
|
|
66
|
+
return u;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* For remove: prefer canonical name from capabilities when present; otherwise use the user key.
|
|
71
|
+
*
|
|
72
|
+
* @param {object} doc
|
|
73
|
+
* @param {string} userKey
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
function resolveLogicalNameForRemove(doc, userKey) {
|
|
77
|
+
const u = String(userKey).trim();
|
|
78
|
+
const caps = doc.capabilities;
|
|
79
|
+
if (Array.isArray(caps) && caps.length > 0) {
|
|
80
|
+
const hit = caps.find((c) => String(c).toLowerCase() === u.toLowerCase());
|
|
81
|
+
if (hit !== undefined) {
|
|
82
|
+
return String(hit);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return u;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Openapi + CIP object keys for a logical capability (exact or single case-insensitive match per section).
|
|
90
|
+
*
|
|
91
|
+
* @param {object} doc
|
|
92
|
+
* @param {string} logicalKey
|
|
93
|
+
* @returns {{ openapiKey: string, cipKey: string }}
|
|
94
|
+
* @throws {Error} If a section is missing the operation
|
|
95
|
+
*/
|
|
96
|
+
function resolveOpsKeysForCapability(doc, logicalKey) {
|
|
97
|
+
const openapiKey = resolveSingleOpsKey(doc.openapi?.operations, logicalKey, 'openapi');
|
|
98
|
+
const cipKey = resolveSingleOpsKey(doc.execution?.cip?.operations, logicalKey, 'cip');
|
|
99
|
+
if (!openapiKey) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Missing openapi.operations entry for capability "${logicalKey}" ` +
|
|
102
|
+
'(no exact or case-insensitive key match).'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!cipKey) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Missing execution.cip.operations entry for capability "${logicalKey}" ` +
|
|
108
|
+
'(no exact or case-insensitive key match).'
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return { openapiKey, cipKey };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {object} doc
|
|
116
|
+
* @param {string} logicalKey
|
|
117
|
+
* @returns {string|null} profiles key to use, or null
|
|
118
|
+
* @throws {Error} On ambiguous profile keys
|
|
119
|
+
*/
|
|
120
|
+
function resolveProfileKeyForLogical(doc, logicalKey) {
|
|
121
|
+
const prof = doc.exposed?.profiles;
|
|
122
|
+
if (!prof || typeof prof !== 'object') {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
if (prof[logicalKey] !== undefined) {
|
|
126
|
+
return logicalKey;
|
|
127
|
+
}
|
|
128
|
+
const lower = String(logicalKey).toLowerCase();
|
|
129
|
+
const keys = Object.keys(prof).filter((k) => k.toLowerCase() === lower);
|
|
130
|
+
if (keys.length === 1) {
|
|
131
|
+
return keys[0];
|
|
132
|
+
}
|
|
133
|
+
if (keys.length > 1) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Ambiguous exposed.profiles keys for "${logicalKey}": ${keys.join(', ')}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* True if the name appears in capabilities (case-insensitive) or in openapi or CIP operations (case-insensitive).
|
|
143
|
+
*
|
|
144
|
+
* @param {object} doc
|
|
145
|
+
* @param {string} name
|
|
146
|
+
* @returns {boolean}
|
|
147
|
+
*/
|
|
148
|
+
function capabilityLogicalExists(doc, name) {
|
|
149
|
+
if (
|
|
150
|
+
Array.isArray(doc.capabilities) &&
|
|
151
|
+
doc.capabilities.some((c) => String(c).toLowerCase() === String(name).toLowerCase())
|
|
152
|
+
) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (findMatchingOpsKeys(doc.openapi?.operations, name).length > 0) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
if (findMatchingOpsKeys(doc.execution?.cip?.operations, name).length > 0) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
findMatchingOpsKeys,
|
|
166
|
+
resolveSingleOpsKey,
|
|
167
|
+
resolveSourceCapabilityForCopy,
|
|
168
|
+
resolveLogicalNameForRemove,
|
|
169
|
+
resolveOpsKeysForCapability,
|
|
170
|
+
resolveProfileKeyForLogical,
|
|
171
|
+
capabilityLogicalExists
|
|
172
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage naming: openapi/CIP operation map keys and testPayload.scenarios.operation use lowercase.
|
|
3
|
+
* capabilities[] and exposed.profiles use logical camelCase names (--as).
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview capability storage key normalization for copy pipeline
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Lowercase key for openapi.operations / execution.cip.operations / scenario.operation.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} logicalCapabilityKey - Normalized --as / capabilities[] name (e.g. updateCountry)
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
function storageOpsKey(logicalCapabilityKey) {
|
|
17
|
+
return String(logicalCapabilityKey ?? '').trim().toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
storageOpsKey
|
|
22
|
+
};
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone a capability within one datasource JSON document.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview capability copy / collision / profile sync
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { rewriteCapabilityReferences } = require('./reference-rewrite');
|
|
10
|
+
const { jsonPointerPath } = require('./json-pointer');
|
|
11
|
+
const { buildBasicExposureProfileArray } = require('./basic-exposure');
|
|
12
|
+
const {
|
|
13
|
+
resolveProfileKeyForLogical,
|
|
14
|
+
capabilityLogicalExists,
|
|
15
|
+
resolveOpsKeysForCapability,
|
|
16
|
+
resolveSourceCapabilityForCopy,
|
|
17
|
+
findMatchingOpsKeys
|
|
18
|
+
} = require('./capability-resolve');
|
|
19
|
+
const { copyTestPayloadScenarios } = require('./copy-test-payload');
|
|
20
|
+
const { storageOpsKey } = require('./capability-storage-keys');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} doc - Datasource object
|
|
24
|
+
* @param {string} name - Capability key
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
function capabilityExists(doc, name) {
|
|
28
|
+
return capabilityLogicalExists(doc, name);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {object} doc - Mutated datasource
|
|
33
|
+
* @param {string} logicalKey - Logical capability name (matches capabilities[] casing when listed)
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
function removeCapability(doc, logicalKey) {
|
|
37
|
+
const oo = doc.openapi?.operations;
|
|
38
|
+
const co = doc.execution?.cip?.operations;
|
|
39
|
+
const openapiKeys = findMatchingOpsKeys(oo, logicalKey);
|
|
40
|
+
const cipKeys = findMatchingOpsKeys(co, logicalKey);
|
|
41
|
+
if (openapiKeys.length > 1) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Ambiguous openapi.operations keys for "${logicalKey}": ${openapiKeys.join(', ')}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (cipKeys.length > 1) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Ambiguous execution.cip.operations keys for "${logicalKey}": ${cipKeys.join(', ')}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const openapiKey = openapiKeys[0];
|
|
52
|
+
const cipKey = cipKeys[0];
|
|
53
|
+
const profileKey = resolveProfileKeyForLogical(doc, logicalKey);
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(doc.capabilities)) {
|
|
56
|
+
doc.capabilities = doc.capabilities.filter(
|
|
57
|
+
(c) => String(c).toLowerCase() !== String(logicalKey).toLowerCase()
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (openapiKey && oo) {
|
|
61
|
+
delete oo[openapiKey];
|
|
62
|
+
}
|
|
63
|
+
if (cipKey && co) {
|
|
64
|
+
delete co[cipKey];
|
|
65
|
+
}
|
|
66
|
+
if (profileKey && doc.exposed?.profiles) {
|
|
67
|
+
delete doc.exposed.profiles[profileKey];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {object} doc
|
|
73
|
+
* @param {string} to - Desired target key
|
|
74
|
+
* @param {boolean} overwrite
|
|
75
|
+
* @returns {string} Same as `to` when free or when overwriting
|
|
76
|
+
* @throws {Error} When target exists and overwrite is false
|
|
77
|
+
*/
|
|
78
|
+
function resolveTargetKey(doc, to, overwrite) {
|
|
79
|
+
if (overwrite) {
|
|
80
|
+
return to;
|
|
81
|
+
}
|
|
82
|
+
if (!capabilityExists(doc, to)) {
|
|
83
|
+
return to;
|
|
84
|
+
}
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Capability "${to}" already exists (check capabilities[], openapi.operations, execution.cip.operations). ` +
|
|
87
|
+
'Use --overwrite to replace it, or choose another --as name.'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {unknown} o
|
|
93
|
+
* @returns {unknown}
|
|
94
|
+
*/
|
|
95
|
+
function deepClone(o) {
|
|
96
|
+
return JSON.parse(JSON.stringify(o));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* When opts.basicExposure is true, refuse to clobber an existing profile row unless overwriting.
|
|
101
|
+
*
|
|
102
|
+
* @param {object} doc
|
|
103
|
+
* @param {string} resolvedAs
|
|
104
|
+
* @param {boolean} overwrite
|
|
105
|
+
* @param {boolean} basicExposure
|
|
106
|
+
* @returns {void}
|
|
107
|
+
* @throws {Error}
|
|
108
|
+
*/
|
|
109
|
+
function assertBasicExposureSlot(doc, resolvedAs, overwrite, basicExposure) {
|
|
110
|
+
if (!basicExposure || overwrite) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (doc.exposed?.profiles?.[resolvedAs]) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`exposed.profiles.${resolvedAs} already exists. Use --overwrite or choose another --as name.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Copy exposed.profiles[from] → exposed.profiles[resolvedAs], or synthesize basic profile at resolvedAs.
|
|
122
|
+
*
|
|
123
|
+
* @param {object} doc
|
|
124
|
+
* @param {object} opts
|
|
125
|
+
* @param {string} from - Source capability key (matches profiles[from])
|
|
126
|
+
* @param {string} resolvedAs - Target capability key (matches profiles[resolvedAs])
|
|
127
|
+
* @param {object[]} patchOperations
|
|
128
|
+
* @param {string[]} updatedSections
|
|
129
|
+
* @returns {void}
|
|
130
|
+
*/
|
|
131
|
+
function copyExposedProfile(doc, opts, logicalFrom, resolvedAs, patchOperations, updatedSections) {
|
|
132
|
+
if (!doc.exposed) {
|
|
133
|
+
doc.exposed = {};
|
|
134
|
+
}
|
|
135
|
+
if (!doc.exposed.profiles) {
|
|
136
|
+
doc.exposed.profiles = {};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (opts.basicExposure) {
|
|
140
|
+
const value = buildBasicExposureProfileArray(doc);
|
|
141
|
+
doc.exposed.profiles[resolvedAs] = value;
|
|
142
|
+
patchOperations.push({
|
|
143
|
+
op: 'add',
|
|
144
|
+
path: jsonPointerPath('exposed', 'profiles', resolvedAs),
|
|
145
|
+
value
|
|
146
|
+
});
|
|
147
|
+
updatedSections.push(`exposed.profiles.${resolvedAs} (basic from metadataSchema)`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const profileFrom = resolveProfileKeyForLogical(doc, logicalFrom);
|
|
152
|
+
if (profileFrom !== null && doc.exposed.profiles[profileFrom] !== undefined) {
|
|
153
|
+
doc.exposed.profiles[resolvedAs] = deepClone(doc.exposed.profiles[profileFrom]);
|
|
154
|
+
patchOperations.push({
|
|
155
|
+
op: 'add',
|
|
156
|
+
path: jsonPointerPath('exposed', 'profiles', resolvedAs),
|
|
157
|
+
value: doc.exposed.profiles[resolvedAs]
|
|
158
|
+
});
|
|
159
|
+
updatedSections.push(`exposed.profiles.${resolvedAs}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {object} doc
|
|
165
|
+
* @param {string} from
|
|
166
|
+
* @param {string} resolvedAs
|
|
167
|
+
* @param {object[]} patchOperations
|
|
168
|
+
* @param {string[]} updatedSections
|
|
169
|
+
* @returns {void}
|
|
170
|
+
*/
|
|
171
|
+
function installClonedOperations(doc, fromKeys, resolvedAs, patchOperations, updatedSections) {
|
|
172
|
+
const { openapiKey, cipKey, logicalFrom } = fromKeys;
|
|
173
|
+
const targetOpsKey = storageOpsKey(resolvedAs);
|
|
174
|
+
|
|
175
|
+
const openapiClone = deepClone(doc.openapi.operations[openapiKey]);
|
|
176
|
+
rewriteCapabilityReferences(openapiClone, openapiKey, targetOpsKey);
|
|
177
|
+
if (logicalFrom !== openapiKey) {
|
|
178
|
+
rewriteCapabilityReferences(openapiClone, logicalFrom, targetOpsKey);
|
|
179
|
+
}
|
|
180
|
+
const cipClone = deepClone(doc.execution.cip.operations[cipKey]);
|
|
181
|
+
rewriteCapabilityReferences(cipClone, cipKey, targetOpsKey);
|
|
182
|
+
if (logicalFrom !== cipKey) {
|
|
183
|
+
rewriteCapabilityReferences(cipClone, logicalFrom, targetOpsKey);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!doc.openapi.operations) {
|
|
187
|
+
doc.openapi.operations = {};
|
|
188
|
+
}
|
|
189
|
+
if (!doc.execution.cip.operations) {
|
|
190
|
+
doc.execution.cip.operations = {};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
doc.openapi.operations[targetOpsKey] = openapiClone;
|
|
194
|
+
patchOperations.push({
|
|
195
|
+
op: 'add',
|
|
196
|
+
path: jsonPointerPath('openapi', 'operations', targetOpsKey),
|
|
197
|
+
value: openapiClone
|
|
198
|
+
});
|
|
199
|
+
updatedSections.push(`openapi.operations.${targetOpsKey} (logical ${resolvedAs})`);
|
|
200
|
+
|
|
201
|
+
doc.execution.cip.operations[targetOpsKey] = cipClone;
|
|
202
|
+
patchOperations.push({
|
|
203
|
+
op: 'add',
|
|
204
|
+
path: jsonPointerPath('execution', 'cip', 'operations', targetOpsKey),
|
|
205
|
+
value: cipClone
|
|
206
|
+
});
|
|
207
|
+
updatedSections.push(`execution.cip.operations.${targetOpsKey} (logical ${resolvedAs})`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Install already-cloned openapi + CIP slices under storage key (no source read).
|
|
212
|
+
*
|
|
213
|
+
* @param {object} doc
|
|
214
|
+
* @param {string} resolvedAs - Logical capability name (capabilities[])
|
|
215
|
+
* @param {{ targetOpsKey: string, openapiClone: object, cipClone: object }} slices
|
|
216
|
+
* @param {object[]} patchOperations
|
|
217
|
+
* @param {string[]} updatedSections
|
|
218
|
+
* @returns {void}
|
|
219
|
+
*/
|
|
220
|
+
function installPreparedSlices(doc, resolvedAs, slices, patchOperations, updatedSections) {
|
|
221
|
+
const { targetOpsKey, openapiClone, cipClone } = slices;
|
|
222
|
+
if (!doc.openapi.operations) {
|
|
223
|
+
doc.openapi.operations = {};
|
|
224
|
+
}
|
|
225
|
+
if (!doc.execution.cip.operations) {
|
|
226
|
+
doc.execution.cip.operations = {};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
doc.openapi.operations[targetOpsKey] = openapiClone;
|
|
230
|
+
patchOperations.push({
|
|
231
|
+
op: 'add',
|
|
232
|
+
path: jsonPointerPath('openapi', 'operations', targetOpsKey),
|
|
233
|
+
value: openapiClone
|
|
234
|
+
});
|
|
235
|
+
updatedSections.push(`openapi.operations.${targetOpsKey} (logical ${resolvedAs})`);
|
|
236
|
+
|
|
237
|
+
doc.execution.cip.operations[targetOpsKey] = cipClone;
|
|
238
|
+
patchOperations.push({
|
|
239
|
+
op: 'add',
|
|
240
|
+
path: jsonPointerPath('execution', 'cip', 'operations', targetOpsKey),
|
|
241
|
+
value: cipClone
|
|
242
|
+
});
|
|
243
|
+
updatedSections.push(`execution.cip.operations.${targetOpsKey} (logical ${resolvedAs})`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @param {object} doc
|
|
248
|
+
* @param {string} resolvedAs
|
|
249
|
+
* @param {object[]} patchOperations
|
|
250
|
+
* @param {string[]} updatedSections
|
|
251
|
+
* @returns {void}
|
|
252
|
+
*/
|
|
253
|
+
function appendCapabilityList(doc, resolvedAs, patchOperations, updatedSections) {
|
|
254
|
+
if (!Array.isArray(doc.capabilities)) {
|
|
255
|
+
doc.capabilities = [];
|
|
256
|
+
}
|
|
257
|
+
if (doc.capabilities.includes(resolvedAs)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
doc.capabilities.push(resolvedAs);
|
|
261
|
+
patchOperations.push({
|
|
262
|
+
op: 'add',
|
|
263
|
+
path: '/capabilities/-',
|
|
264
|
+
value: resolvedAs
|
|
265
|
+
});
|
|
266
|
+
updatedSections.push(`capabilities[]: ${resolvedAs}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Apply capability copy on a deep clone of the document.
|
|
271
|
+
*
|
|
272
|
+
* @param {object} originalDoc - Parsed datasource JSON
|
|
273
|
+
* @param {object} opts - Options
|
|
274
|
+
* @param {string} opts.from - Source capability key
|
|
275
|
+
* @param {string} opts.to - Desired target key
|
|
276
|
+
* @param {boolean} [opts.overwrite=false]
|
|
277
|
+
* @param {boolean} [opts.basicExposure=false] - Build minimal profile from metadataSchema at exposed.profiles[to]
|
|
278
|
+
* @param {boolean} [opts.includeTestPayload=false] - Clone matching testPayload.scenarios rows to target operation
|
|
279
|
+
* @returns {{
|
|
280
|
+
* doc: object,
|
|
281
|
+
* resolvedAs: string,
|
|
282
|
+
* patchOperations: object[],
|
|
283
|
+
* updatedSections: string[]
|
|
284
|
+
* }}
|
|
285
|
+
*/
|
|
286
|
+
function applyCapabilityCopy(originalDoc, opts) {
|
|
287
|
+
const doc = deepClone(originalDoc);
|
|
288
|
+
const logicalFrom = resolveSourceCapabilityForCopy(doc, opts.from);
|
|
289
|
+
const { openapiKey, cipKey } = resolveOpsKeysForCapability(doc, logicalFrom);
|
|
290
|
+
|
|
291
|
+
const openapiFrom = doc.openapi?.operations?.[openapiKey];
|
|
292
|
+
const cipFrom = doc.execution?.cip?.operations?.[cipKey];
|
|
293
|
+
|
|
294
|
+
if (!openapiFrom) {
|
|
295
|
+
throw new Error(`Missing openapi.operations.${openapiKey}`);
|
|
296
|
+
}
|
|
297
|
+
if (!cipFrom) {
|
|
298
|
+
throw new Error(`Missing execution.cip.operations.${cipKey}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const resolvedAs = resolveTargetKey(doc, opts.to, Boolean(opts.overwrite));
|
|
302
|
+
assertBasicExposureSlot(doc, resolvedAs, Boolean(opts.overwrite), Boolean(opts.basicExposure));
|
|
303
|
+
|
|
304
|
+
const patchOperations = [];
|
|
305
|
+
const updatedSections = [];
|
|
306
|
+
|
|
307
|
+
if (opts.overwrite && capabilityLogicalExists(doc, resolvedAs)) {
|
|
308
|
+
removeCapability(doc, resolvedAs);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
installClonedOperations(
|
|
312
|
+
doc,
|
|
313
|
+
{ openapiKey, cipKey, logicalFrom },
|
|
314
|
+
resolvedAs,
|
|
315
|
+
patchOperations,
|
|
316
|
+
updatedSections
|
|
317
|
+
);
|
|
318
|
+
appendCapabilityList(doc, resolvedAs, patchOperations, updatedSections);
|
|
319
|
+
copyExposedProfile(doc, opts, logicalFrom, resolvedAs, patchOperations, updatedSections);
|
|
320
|
+
copyTestPayloadScenarios(
|
|
321
|
+
doc,
|
|
322
|
+
opts,
|
|
323
|
+
{ openapiKey, cipKey, logicalFrom },
|
|
324
|
+
resolvedAs,
|
|
325
|
+
patchOperations,
|
|
326
|
+
updatedSections
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
doc,
|
|
331
|
+
resolvedAs,
|
|
332
|
+
patchOperations,
|
|
333
|
+
updatedSections
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
module.exports = {
|
|
338
|
+
applyCapabilityCopy,
|
|
339
|
+
capabilityExists,
|
|
340
|
+
capabilityLogicalExists,
|
|
341
|
+
resolveTargetKey,
|
|
342
|
+
deepClone,
|
|
343
|
+
removeCapability,
|
|
344
|
+
installPreparedSlices,
|
|
345
|
+
appendCapabilityList,
|
|
346
|
+
copyExposedProfile,
|
|
347
|
+
assertBasicExposureSlot
|
|
348
|
+
};
|