@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,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two datasource files on one capability slice (+ optional profile).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview capability diff runner
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { resolveValidateInputPath } = require('../validate');
|
|
12
|
+
const { normalizeCapabilityKey } = require('./capability-key');
|
|
13
|
+
const { extractCapabilitySliceForDiff } = require('./capability-diff-slice');
|
|
14
|
+
const {
|
|
15
|
+
compareObjects,
|
|
16
|
+
identifyBreakingChanges,
|
|
17
|
+
formatDiffOutput
|
|
18
|
+
} = require('../../core/diff');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} RunCapabilityDiffOpts
|
|
22
|
+
* @property {string} fileA
|
|
23
|
+
* @property {string} fileB
|
|
24
|
+
* @property {string} [capability] - Same key both sides
|
|
25
|
+
* @property {string} [capabilityA]
|
|
26
|
+
* @property {string} [capabilityB]
|
|
27
|
+
* @property {string} [profile] - Same profile both sides
|
|
28
|
+
* @property {string} [profileA]
|
|
29
|
+
* @property {string} [profileB]
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {object} comparison - compareObjects result
|
|
34
|
+
* @param {string} label1
|
|
35
|
+
* @param {string} label2
|
|
36
|
+
* @returns {object}
|
|
37
|
+
*/
|
|
38
|
+
function buildSliceDiffResult(comparison, label1, label2) {
|
|
39
|
+
const breakingChanges = identifyBreakingChanges(comparison);
|
|
40
|
+
return {
|
|
41
|
+
identical: comparison.identical,
|
|
42
|
+
file1: label1,
|
|
43
|
+
file2: label2,
|
|
44
|
+
version1: null,
|
|
45
|
+
version2: null,
|
|
46
|
+
versionChanged: false,
|
|
47
|
+
added: comparison.added,
|
|
48
|
+
removed: comparison.removed,
|
|
49
|
+
changed: comparison.changed,
|
|
50
|
+
breakingChanges,
|
|
51
|
+
summary: {
|
|
52
|
+
totalAdded: comparison.added.length,
|
|
53
|
+
totalRemoved: comparison.removed.length,
|
|
54
|
+
totalChanged: comparison.changed.length,
|
|
55
|
+
totalBreaking: breakingChanges.length
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve capability keys for left/right files.
|
|
62
|
+
*
|
|
63
|
+
* @param {RunCapabilityDiffOpts} opts
|
|
64
|
+
* @returns {{ capA: string, capB: string }}
|
|
65
|
+
*/
|
|
66
|
+
function resolveCapabilityKeys(opts) {
|
|
67
|
+
const shared = opts.capability ? String(opts.capability).trim() : '';
|
|
68
|
+
let capA = opts.capabilityA ? String(opts.capabilityA).trim() : '';
|
|
69
|
+
let capB = opts.capabilityB ? String(opts.capabilityB).trim() : '';
|
|
70
|
+
if (shared) {
|
|
71
|
+
capA = shared;
|
|
72
|
+
capB = shared;
|
|
73
|
+
}
|
|
74
|
+
if (!capA || !capB) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Provide --capability <key> for both sides, or both --capability-a and --capability-b'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
capA: normalizeCapabilityKey(capA, '--capability-a'),
|
|
81
|
+
capB: normalizeCapabilityKey(capB, '--capability-b')
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {RunCapabilityDiffOpts} opts
|
|
87
|
+
* @returns {{ profA: string|undefined, profB: string|undefined }}
|
|
88
|
+
*/
|
|
89
|
+
function resolveProfileKeys(opts) {
|
|
90
|
+
const shared = opts.profile ? String(opts.profile).trim() : '';
|
|
91
|
+
let profA = opts.profileA ? String(opts.profileA).trim() : '';
|
|
92
|
+
let profB = opts.profileB ? String(opts.profileB).trim() : '';
|
|
93
|
+
if (shared) {
|
|
94
|
+
profA = shared;
|
|
95
|
+
profB = shared;
|
|
96
|
+
}
|
|
97
|
+
const outA = profA || undefined;
|
|
98
|
+
const outB = profB || undefined;
|
|
99
|
+
return { profA: outA, profB: outB };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Deep-compare capability slices; prints via formatDiffOutput.
|
|
104
|
+
*
|
|
105
|
+
* @param {RunCapabilityDiffOpts} opts
|
|
106
|
+
* @returns {{ identical: boolean, diffResult: object }}
|
|
107
|
+
*/
|
|
108
|
+
function runCapabilityDiff(opts) {
|
|
109
|
+
const pathA = resolveValidateInputPath(opts.fileA.trim());
|
|
110
|
+
const pathB = resolveValidateInputPath(opts.fileB.trim());
|
|
111
|
+
const docA = JSON.parse(fs.readFileSync(pathA, 'utf8'));
|
|
112
|
+
const docB = JSON.parse(fs.readFileSync(pathB, 'utf8'));
|
|
113
|
+
|
|
114
|
+
const { capA, capB } = resolveCapabilityKeys(opts);
|
|
115
|
+
const { profA, profB } = resolveProfileKeys(opts);
|
|
116
|
+
|
|
117
|
+
const sliceA = extractCapabilitySliceForDiff(docA, capA, profA);
|
|
118
|
+
const sliceB = extractCapabilitySliceForDiff(docB, capB, profB);
|
|
119
|
+
|
|
120
|
+
const comparison = compareObjects(sliceA, sliceB);
|
|
121
|
+
const label1 = `${path.basename(pathA)} → ${capA}${profA ? ` + profile:${profA}` : ''}`;
|
|
122
|
+
const label2 = `${path.basename(pathB)} → ${capB}${profB ? ` + profile:${profB}` : ''}`;
|
|
123
|
+
const diffResult = buildSliceDiffResult(comparison, label1, label2);
|
|
124
|
+
|
|
125
|
+
formatDiffOutput(diffResult);
|
|
126
|
+
|
|
127
|
+
return { identical: comparison.identical, diffResult };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
runCapabilityDiff,
|
|
132
|
+
resolveCapabilityKeys,
|
|
133
|
+
resolveProfileKeys,
|
|
134
|
+
buildSliceDiffResult
|
|
135
|
+
};
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-backed capability dimension (root dimensions binding) with semantic validation, backup, and atomic write.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview runCapabilityDimension
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const { resolveControllerUrl } = require('../../utils/controller-url');
|
|
13
|
+
const { normalizeControllerUrl } = require('../../core/config');
|
|
14
|
+
const { getOrRefreshDeviceToken } = require('../../utils/token-manager');
|
|
15
|
+
const { listDimensions } = require('../../api/dimensions.api');
|
|
16
|
+
const {
|
|
17
|
+
resolveValidateInputPath,
|
|
18
|
+
validateDatasourceParsed
|
|
19
|
+
} = require('../validate');
|
|
20
|
+
const { applyCapabilityDimension } = require('./dimension-operations');
|
|
21
|
+
const { validateDimensionSemantics } = require('./dimension-validate');
|
|
22
|
+
const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
|
|
23
|
+
const { tryResolveDatasourceKeyToLocalPath, readJsonFile } = require('../../resolvers/datasource-resolver');
|
|
24
|
+
const { tryFetchDatasourceConfig } = require('../../resolvers/manifest-resolver');
|
|
25
|
+
|
|
26
|
+
function resolveSystemKeyForAuth(sourceDoc) {
|
|
27
|
+
return typeof sourceDoc?.systemKey === 'string' ? sourceDoc.systemKey.trim() : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function maybeLoadDimensionCatalogKeys() {
|
|
31
|
+
try {
|
|
32
|
+
const controllerUrl = await resolveControllerUrl();
|
|
33
|
+
if (!controllerUrl) {
|
|
34
|
+
return { ok: false, notAuthenticated: true, keys: null, reason: 'controller_url_missing' };
|
|
35
|
+
}
|
|
36
|
+
const normalized = normalizeControllerUrl(controllerUrl);
|
|
37
|
+
const deviceToken = await getOrRefreshDeviceToken(normalized);
|
|
38
|
+
if (!deviceToken || !deviceToken.token) {
|
|
39
|
+
return { ok: false, notAuthenticated: true, keys: null, reason: 'no_token' };
|
|
40
|
+
}
|
|
41
|
+
const authConfig = { type: 'bearer', token: deviceToken.token };
|
|
42
|
+
const res = await listDimensions(deviceToken.controller || normalized, authConfig, {
|
|
43
|
+
page: 1,
|
|
44
|
+
pageSize: 500
|
|
45
|
+
});
|
|
46
|
+
const items = res?.data?.items || res?.data?.data?.items || res?.items || [];
|
|
47
|
+
const keys = new Set(
|
|
48
|
+
Array.isArray(items) ? items.map((d) => String(d?.key || '').trim()).filter(Boolean) : []
|
|
49
|
+
);
|
|
50
|
+
return { ok: true, notAuthenticated: false, keys, reason: undefined };
|
|
51
|
+
} catch (e) {
|
|
52
|
+
const msg = e?.message || String(e);
|
|
53
|
+
const notAuthenticated = /Not authenticated|Authentication required|login/i.test(msg);
|
|
54
|
+
return { ok: false, notAuthenticated, keys: null, reason: msg };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseViaList(via) {
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const raw of Array.isArray(via) ? via : []) {
|
|
61
|
+
const s = String(raw || '').trim();
|
|
62
|
+
if (!s) continue;
|
|
63
|
+
const idx = s.indexOf(':');
|
|
64
|
+
if (idx <= 0 || idx >= s.length - 1) {
|
|
65
|
+
throw new Error(`--via must be in form <fkName>:<dimensionKey>, got "${s}"`);
|
|
66
|
+
}
|
|
67
|
+
out.push({ fk: s.slice(0, idx).trim(), dimension: s.slice(idx + 1).trim() });
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function collectTargetDatasourceKeysForVia(sourceDoc, via) {
|
|
73
|
+
const fks = Array.isArray(sourceDoc?.foreignKeys) ? sourceDoc.foreignKeys : [];
|
|
74
|
+
const byName = new Map();
|
|
75
|
+
for (const fk of fks) {
|
|
76
|
+
const name = typeof fk?.name === 'string' ? fk.name.trim() : '';
|
|
77
|
+
if (name) byName.set(name, fk);
|
|
78
|
+
}
|
|
79
|
+
const targets = new Set();
|
|
80
|
+
for (const hop of via) {
|
|
81
|
+
const fkRow = byName.get(String(hop?.fk || '').trim());
|
|
82
|
+
const t = typeof fkRow?.targetDatasource === 'string' ? fkRow.targetDatasource.trim() : '';
|
|
83
|
+
if (t) targets.add(t);
|
|
84
|
+
}
|
|
85
|
+
return [...targets];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function loadRemoteTargetsByKey({ sourceDoc, via, systemKeyForAuth }) {
|
|
89
|
+
/** @type {Record<string, any>} */
|
|
90
|
+
const remoteTargetsByKey = {};
|
|
91
|
+
const meta = { attempted: false, ok: false, notAuthenticated: false, fetchedKeys: [] };
|
|
92
|
+
|
|
93
|
+
const targetDatasourceKeys = collectTargetDatasourceKeysForVia(sourceDoc, via);
|
|
94
|
+
if (targetDatasourceKeys.length === 0) {
|
|
95
|
+
return { remoteTargetsByKey, remoteFetchMeta: meta };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_loadTargetsFromDisk(targetDatasourceKeys, remoteTargetsByKey);
|
|
99
|
+
await _maybeFetchTargetsRemote(targetDatasourceKeys, remoteTargetsByKey, systemKeyForAuth, meta);
|
|
100
|
+
return { remoteTargetsByKey, remoteFetchMeta: meta };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function _loadTargetsFromDisk(targetDatasourceKeys, remoteTargetsByKey) {
|
|
104
|
+
targetDatasourceKeys.forEach((dsKey) => {
|
|
105
|
+
const local = tryResolveDatasourceKeyToLocalPath(dsKey);
|
|
106
|
+
if (local.ok) {
|
|
107
|
+
remoteTargetsByKey[dsKey] = readJsonFile(local.path);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function _maybeFetchTargetsRemote(targetDatasourceKeys, remoteTargetsByKey, systemKeyForAuth, meta) {
|
|
113
|
+
if (!systemKeyForAuth) return;
|
|
114
|
+
const remaining = targetDatasourceKeys.filter((k) => !remoteTargetsByKey[k]);
|
|
115
|
+
if (remaining.length === 0) {
|
|
116
|
+
meta.ok = true;
|
|
117
|
+
meta.fetchedKeys = targetDatasourceKeys;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
meta.attempted = true;
|
|
121
|
+
await _fetchRemainingTargets(remaining, remoteTargetsByKey, systemKeyForAuth, meta);
|
|
122
|
+
meta.ok = meta.fetchedKeys.length > 0 && !meta.notAuthenticated;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function _fetchRemainingTargets(remaining, remoteTargetsByKey, systemKeyForAuth, meta) {
|
|
126
|
+
for (const dsKey of remaining) {
|
|
127
|
+
const remote = await tryFetchDatasourceConfig(systemKeyForAuth, dsKey, { silent: true });
|
|
128
|
+
if (remote.ok) {
|
|
129
|
+
remoteTargetsByKey[dsKey] = remote.datasourceConfig;
|
|
130
|
+
meta.fetchedKeys.push(dsKey);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (remote.code === 'not_authenticated') {
|
|
134
|
+
meta.notAuthenticated = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function runSemanticValidation({ sourceDoc, opts, remoteTargetsByKey, catalogKeys }) {
|
|
140
|
+
const semantic = validateDimensionSemantics({
|
|
141
|
+
localContext: {
|
|
142
|
+
sourceDoc,
|
|
143
|
+
dimensionKey: opts.dimension,
|
|
144
|
+
type: opts.type,
|
|
145
|
+
field: opts.field,
|
|
146
|
+
via: opts.via
|
|
147
|
+
},
|
|
148
|
+
remoteTargetsByKey,
|
|
149
|
+
catalogDimensionKeys: catalogKeys
|
|
150
|
+
});
|
|
151
|
+
if (!semantic.ok) {
|
|
152
|
+
const err = new Error(semantic.errors.join('\n'));
|
|
153
|
+
err.validationErrors = semantic.errors;
|
|
154
|
+
err.validationWarnings = semantic.warnings;
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
return semantic;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeRunOptions(rawOpts) {
|
|
161
|
+
return {
|
|
162
|
+
...rawOpts,
|
|
163
|
+
fileOrKey: String(rawOpts.fileOrKey || '').trim(),
|
|
164
|
+
dimension: String(rawOpts.dimension || '').trim(),
|
|
165
|
+
type: /** @type {any} */ (String(rawOpts.type || '').trim()),
|
|
166
|
+
field: rawOpts.field !== undefined && rawOpts.field !== null ? String(rawOpts.field).trim() : undefined,
|
|
167
|
+
via: parseViaList(rawOpts.via),
|
|
168
|
+
actor: rawOpts.actor !== undefined && rawOpts.actor !== null ? String(rawOpts.actor).trim() : undefined,
|
|
169
|
+
operator: rawOpts.operator !== undefined && rawOpts.operator !== null ? String(rawOpts.operator).trim() : undefined,
|
|
170
|
+
overwrite: Boolean(rawOpts.overwrite),
|
|
171
|
+
dryRun: Boolean(rawOpts.dryRun),
|
|
172
|
+
noBackup: Boolean(rawOpts.noBackup)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function buildSemanticWarnings({ opts, semantic, catalog }) {
|
|
177
|
+
const warnings = [...(Array.isArray(semantic?.warnings) ? semantic.warnings : [])];
|
|
178
|
+
if (opts.type === 'fk' && (!opts.actor || !opts.actor.trim())) {
|
|
179
|
+
warnings.push('dimension type=fk without actor; set --actor for predictable ABAC binding.');
|
|
180
|
+
}
|
|
181
|
+
if (!catalog.ok && catalog.notAuthenticated) {
|
|
182
|
+
warnings.push('Dimension catalog validation skipped (not authenticated).');
|
|
183
|
+
}
|
|
184
|
+
return warnings;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function applyBindingAndValidateSchema({ parsed, opts, semanticWarnings, remoteFetchMeta }) {
|
|
188
|
+
const result = applyCapabilityDimension(parsed, {
|
|
189
|
+
dimension: opts.dimension,
|
|
190
|
+
type: opts.type,
|
|
191
|
+
field: opts.field,
|
|
192
|
+
via: opts.via,
|
|
193
|
+
actor: opts.actor,
|
|
194
|
+
operator: opts.operator,
|
|
195
|
+
required: opts.required,
|
|
196
|
+
overwrite: Boolean(opts.overwrite)
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const validation = validateDatasourceParsed(result.doc);
|
|
200
|
+
if (!validation.valid) {
|
|
201
|
+
const err = new Error(validation.errors.join('\n'));
|
|
202
|
+
err.validationErrors = validation.errors;
|
|
203
|
+
err.validationWarnings = semanticWarnings;
|
|
204
|
+
err.remoteValidation = remoteFetchMeta;
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { result, validation };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @typedef {object} RunCapabilityDimensionOpts
|
|
213
|
+
* @property {string} fileOrKey
|
|
214
|
+
* @property {string} dimension
|
|
215
|
+
* @property {'local'|'fk'} type
|
|
216
|
+
* @property {string|undefined} [field]
|
|
217
|
+
* @property {string[]|undefined} [via] - raw CLI strings; parsed before validate
|
|
218
|
+
* @property {string|undefined} [actor]
|
|
219
|
+
* @property {string|undefined} [operator]
|
|
220
|
+
* @property {boolean|undefined} [required]
|
|
221
|
+
* @property {boolean} [dryRun=false]
|
|
222
|
+
* @property {boolean} [noBackup=false]
|
|
223
|
+
* @property {boolean} [overwrite=false]
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @param {RunCapabilityDimensionOpts} rawOpts
|
|
228
|
+
* @returns {Promise<any>}
|
|
229
|
+
*/
|
|
230
|
+
async function runCapabilityDimension(rawOpts) {
|
|
231
|
+
const opts = normalizeRunOptions(rawOpts);
|
|
232
|
+
const resolvedPath = resolveValidateInputPath(opts.fileOrKey);
|
|
233
|
+
const raw = fs.readFileSync(resolvedPath, 'utf8');
|
|
234
|
+
const parsed = JSON.parse(raw);
|
|
235
|
+
|
|
236
|
+
const systemKeyForAuth = resolveSystemKeyForAuth(parsed);
|
|
237
|
+
const { remoteTargetsByKey, remoteFetchMeta } = await loadRemoteTargetsByKey({
|
|
238
|
+
sourceDoc: parsed,
|
|
239
|
+
via: opts.via,
|
|
240
|
+
systemKeyForAuth
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const catalog = await maybeLoadDimensionCatalogKeys();
|
|
244
|
+
const catalogKeys = catalog.ok ? catalog.keys : null;
|
|
245
|
+
|
|
246
|
+
const semantic = runSemanticValidation({
|
|
247
|
+
sourceDoc: parsed,
|
|
248
|
+
opts,
|
|
249
|
+
remoteTargetsByKey,
|
|
250
|
+
catalogKeys
|
|
251
|
+
});
|
|
252
|
+
const semanticWarnings = buildSemanticWarnings({ opts, semantic, catalog });
|
|
253
|
+
const { result, validation } = applyBindingAndValidateSchema({
|
|
254
|
+
parsed,
|
|
255
|
+
opts,
|
|
256
|
+
semanticWarnings,
|
|
257
|
+
remoteFetchMeta
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
if (opts.dryRun) {
|
|
261
|
+
return {
|
|
262
|
+
dryRun: true,
|
|
263
|
+
resolvedPath,
|
|
264
|
+
patchOperations: result.patchOperations,
|
|
265
|
+
updatedSections: result.updatedSections,
|
|
266
|
+
backupPath: null,
|
|
267
|
+
validation,
|
|
268
|
+
semanticWarnings,
|
|
269
|
+
remoteValidation: remoteFetchMeta
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const backupPath = writeBackup(resolvedPath, Boolean(opts.noBackup));
|
|
274
|
+
atomicWriteJson(resolvedPath, result.doc);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
dryRun: false,
|
|
278
|
+
resolvedPath,
|
|
279
|
+
patchOperations: result.patchOperations,
|
|
280
|
+
updatedSections: result.updatedSections,
|
|
281
|
+
backupPath,
|
|
282
|
+
validation,
|
|
283
|
+
semanticWarnings,
|
|
284
|
+
remoteValidation: remoteFetchMeta
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
module.exports = {
|
|
289
|
+
runCapabilityDimension
|
|
290
|
+
};
|
|
291
|
+
|