@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,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive edit of one capability slice in a datasource JSON file.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview capability edit (inquirer + atomic write)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
const inquirer = require('inquirer');
|
|
12
|
+
const {
|
|
13
|
+
resolveValidateInputPath,
|
|
14
|
+
validateDatasourceParsed
|
|
15
|
+
} = require('../validate');
|
|
16
|
+
const { normalizeCapabilityKey } = require('./capability-key');
|
|
17
|
+
const {
|
|
18
|
+
resolveLogicalNameForRemove,
|
|
19
|
+
resolveSingleOpsKey,
|
|
20
|
+
resolveProfileKeyForLogical
|
|
21
|
+
} = require('./capability-resolve');
|
|
22
|
+
const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} RunCapabilityEditOpts
|
|
26
|
+
* @property {string} fileOrKey
|
|
27
|
+
* @property {string} [capability]
|
|
28
|
+
* @property {'openapi'|'cip'|'profile'} [section]
|
|
29
|
+
* @property {string} [profile]
|
|
30
|
+
* @property {boolean} [noBackup=false]
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {object} doc
|
|
35
|
+
* @returns {string[]}
|
|
36
|
+
*/
|
|
37
|
+
function collectCapabilityKeys(doc) {
|
|
38
|
+
const s = new Set();
|
|
39
|
+
if (Array.isArray(doc.capabilities)) {
|
|
40
|
+
doc.capabilities.forEach((c) => s.add(String(c)));
|
|
41
|
+
}
|
|
42
|
+
if (doc.openapi?.operations) {
|
|
43
|
+
Object.keys(doc.openapi.operations).forEach((k) => s.add(k));
|
|
44
|
+
}
|
|
45
|
+
if (doc.execution?.cip?.operations) {
|
|
46
|
+
Object.keys(doc.execution.cip.operations).forEach((k) => s.add(k));
|
|
47
|
+
}
|
|
48
|
+
return [...s].sort();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string[]} keys
|
|
53
|
+
* @param {string} [preset]
|
|
54
|
+
* @returns {Promise<string>}
|
|
55
|
+
*/
|
|
56
|
+
async function promptCapabilityKey(keys, preset) {
|
|
57
|
+
if (preset && preset.trim()) {
|
|
58
|
+
return normalizeCapabilityKey(preset.trim(), '--capability');
|
|
59
|
+
}
|
|
60
|
+
const { key } = await inquirer.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: 'list',
|
|
63
|
+
name: 'key',
|
|
64
|
+
message: 'Which capability?',
|
|
65
|
+
choices: keys,
|
|
66
|
+
pageSize: 15
|
|
67
|
+
}
|
|
68
|
+
]);
|
|
69
|
+
return normalizeCapabilityKey(key, 'capability');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve openapi/cip operations object key (exact or case-insensitive) for editing.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} doc
|
|
76
|
+
* @param {string} logicalKey - Canonical name from capabilities[] when possible
|
|
77
|
+
* @param {'openapi'|'cip'} section
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
function resolveOpsKeyForEdit(doc, logicalKey, section) {
|
|
81
|
+
const ops =
|
|
82
|
+
section === 'openapi' ? doc.openapi?.operations : doc.execution?.cip?.operations;
|
|
83
|
+
const label = section === 'openapi' ? 'openapi.operations' : 'execution.cip.operations';
|
|
84
|
+
const k = resolveSingleOpsKey(ops, logicalKey, label);
|
|
85
|
+
if (!k) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Missing ${label} entry for capability "${logicalKey}" (no exact or case-insensitive key match).`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return k;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} [preset]
|
|
95
|
+
* @returns {Promise<'openapi'|'cip'|'profile'>}
|
|
96
|
+
*/
|
|
97
|
+
async function promptSection(preset) {
|
|
98
|
+
const allowed = ['openapi', 'cip', 'profile'];
|
|
99
|
+
if (preset && allowed.includes(preset)) {
|
|
100
|
+
return preset;
|
|
101
|
+
}
|
|
102
|
+
const { section } = await inquirer.prompt([
|
|
103
|
+
{
|
|
104
|
+
type: 'list',
|
|
105
|
+
name: 'section',
|
|
106
|
+
message: 'Which slice to edit?',
|
|
107
|
+
choices: [
|
|
108
|
+
{ name: 'openapi.operations.<key> (JSON)', value: 'openapi' },
|
|
109
|
+
{ name: 'execution.cip.operations.<key> (JSON)', value: 'cip' },
|
|
110
|
+
{ name: 'exposed.profiles.<profile> (JSON)', value: 'profile' }
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
]);
|
|
114
|
+
return section;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* If EDITOR/VISUAL unset, prefer nano when available on PATH (common on minimal Linux images).
|
|
119
|
+
*
|
|
120
|
+
* @returns {void}
|
|
121
|
+
*/
|
|
122
|
+
function ensureDefaultEditorFallback() {
|
|
123
|
+
if (process.env.VISUAL && String(process.env.VISUAL).trim()) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (process.env.EDITOR && String(process.env.EDITOR).trim()) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
execSync('command -v nano', { stdio: 'ignore' });
|
|
131
|
+
process.env.EDITOR = 'nano';
|
|
132
|
+
} catch {
|
|
133
|
+
/* fall through to vi / platform default */
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {object} doc
|
|
139
|
+
* @param {string} [preset]
|
|
140
|
+
* @param {boolean} [capabilityFromCli=false] - If true and no profile matches the capability, throw (do not list).
|
|
141
|
+
* @param {string} [logicalCapKey]
|
|
142
|
+
* @returns {Promise<string>}
|
|
143
|
+
*/
|
|
144
|
+
async function promptProfileKey(doc, preset, capabilityFromCli, logicalCapKey) {
|
|
145
|
+
const keys = doc.exposed?.profiles ? Object.keys(doc.exposed.profiles).sort() : [];
|
|
146
|
+
if (keys.length === 0) {
|
|
147
|
+
throw new Error('No exposed.profiles in this datasource');
|
|
148
|
+
}
|
|
149
|
+
if (preset && preset.trim()) {
|
|
150
|
+
const k = preset.trim();
|
|
151
|
+
if (!keys.includes(k)) {
|
|
152
|
+
throw new Error(`exposed.profiles.${k} not found (available: ${keys.join(', ')})`);
|
|
153
|
+
}
|
|
154
|
+
return k;
|
|
155
|
+
}
|
|
156
|
+
if (logicalCapKey) {
|
|
157
|
+
const pk = resolveProfileKeyForLogical(doc, logicalCapKey);
|
|
158
|
+
if (pk !== null && doc.exposed?.profiles?.[pk] !== undefined) {
|
|
159
|
+
return pk;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (capabilityFromCli && logicalCapKey) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`No exposed.profiles row matches capability "${logicalCapKey}" for -c / --capability. ` +
|
|
165
|
+
`Pass --profile <key> or choose from: ${keys.join(', ')}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const { pk } = await inquirer.prompt([
|
|
169
|
+
{
|
|
170
|
+
type: 'list',
|
|
171
|
+
name: 'pk',
|
|
172
|
+
message: 'Which exposure profile?',
|
|
173
|
+
choices: keys,
|
|
174
|
+
pageSize: 15
|
|
175
|
+
}
|
|
176
|
+
]);
|
|
177
|
+
return pk;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {string} initialJson
|
|
182
|
+
* @returns {Promise<string>}
|
|
183
|
+
*/
|
|
184
|
+
async function promptEditorJson(initialJson) {
|
|
185
|
+
ensureDefaultEditorFallback();
|
|
186
|
+
const { body } = await inquirer.prompt([
|
|
187
|
+
{
|
|
188
|
+
type: 'editor',
|
|
189
|
+
name: 'body',
|
|
190
|
+
message: 'Edit JSON (save and close editor to continue)',
|
|
191
|
+
default: initialJson,
|
|
192
|
+
postfix: '.json'
|
|
193
|
+
}
|
|
194
|
+
]);
|
|
195
|
+
return body;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @param {object} doc
|
|
200
|
+
* @param {string} capKey
|
|
201
|
+
* @param {'openapi'|'cip'|'profile'} section
|
|
202
|
+
* @param {string} profileKey
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
function getEditableJson(doc, logicalCapKey, section, profileKey) {
|
|
206
|
+
if (section === 'openapi') {
|
|
207
|
+
const k = resolveOpsKeyForEdit(doc, logicalCapKey, 'openapi');
|
|
208
|
+
const v = doc.openapi.operations[k];
|
|
209
|
+
return JSON.stringify(v, null, 2);
|
|
210
|
+
}
|
|
211
|
+
if (section === 'cip') {
|
|
212
|
+
const k = resolveOpsKeyForEdit(doc, logicalCapKey, 'cip');
|
|
213
|
+
const v = doc.execution.cip.operations[k];
|
|
214
|
+
return JSON.stringify(v, null, 2);
|
|
215
|
+
}
|
|
216
|
+
const pk =
|
|
217
|
+
profileKey && profileKey.trim()
|
|
218
|
+
? profileKey.trim()
|
|
219
|
+
: resolveProfileKeyForLogical(doc, logicalCapKey);
|
|
220
|
+
if (!pk || doc.exposed?.profiles?.[pk] === undefined) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Missing exposed.profiles.${profileKey || logicalCapKey} (no matching profile key).`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const v = doc.exposed.profiles[pk];
|
|
226
|
+
return JSON.stringify(v, null, 2);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {string} jsonBody
|
|
231
|
+
* @returns {object}
|
|
232
|
+
*/
|
|
233
|
+
function parseEditorJsonPayload(jsonBody) {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(jsonBody);
|
|
236
|
+
} catch (e) {
|
|
237
|
+
throw new Error(`Invalid JSON: ${e.message}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @param {object} doc
|
|
243
|
+
* @param {string} capKey
|
|
244
|
+
* @param {object} value
|
|
245
|
+
* @returns {void}
|
|
246
|
+
*/
|
|
247
|
+
function assignOpenapiOperation(doc, capKey, value) {
|
|
248
|
+
if (!doc.openapi) {
|
|
249
|
+
doc.openapi = {};
|
|
250
|
+
}
|
|
251
|
+
if (!doc.openapi.operations) {
|
|
252
|
+
doc.openapi.operations = {};
|
|
253
|
+
}
|
|
254
|
+
doc.openapi.operations[capKey] = value;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @param {object} doc
|
|
259
|
+
* @param {string} capKey
|
|
260
|
+
* @param {object} value
|
|
261
|
+
* @returns {void}
|
|
262
|
+
*/
|
|
263
|
+
function assignCipOperation(doc, capKey, value) {
|
|
264
|
+
if (!doc.execution) {
|
|
265
|
+
doc.execution = {};
|
|
266
|
+
}
|
|
267
|
+
if (!doc.execution.cip) {
|
|
268
|
+
doc.execution.cip = {};
|
|
269
|
+
}
|
|
270
|
+
if (!doc.execution.cip.operations) {
|
|
271
|
+
doc.execution.cip.operations = {};
|
|
272
|
+
}
|
|
273
|
+
doc.execution.cip.operations[capKey] = value;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {object} doc
|
|
278
|
+
* @param {string} profileKey
|
|
279
|
+
* @param {object} value
|
|
280
|
+
* @returns {void}
|
|
281
|
+
*/
|
|
282
|
+
function assignExposedProfile(doc, profileKey, value) {
|
|
283
|
+
if (!doc.exposed) {
|
|
284
|
+
doc.exposed = {};
|
|
285
|
+
}
|
|
286
|
+
if (!doc.exposed.profiles) {
|
|
287
|
+
doc.exposed.profiles = {};
|
|
288
|
+
}
|
|
289
|
+
doc.exposed.profiles[profileKey] = value;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @param {object} doc
|
|
294
|
+
* @param {string} capKey
|
|
295
|
+
* @param {'openapi'|'cip'|'profile'} section
|
|
296
|
+
* @param {string} profileKey
|
|
297
|
+
* @param {string} jsonBody
|
|
298
|
+
* @returns {void}
|
|
299
|
+
*/
|
|
300
|
+
function applyEditedJson(doc, logicalCapKey, section, profileKey, jsonBody) {
|
|
301
|
+
const parsed = parseEditorJsonPayload(jsonBody);
|
|
302
|
+
if (section === 'openapi') {
|
|
303
|
+
const k = resolveOpsKeyForEdit(doc, logicalCapKey, 'openapi');
|
|
304
|
+
assignOpenapiOperation(doc, k, parsed);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (section === 'cip') {
|
|
308
|
+
const k = resolveOpsKeyForEdit(doc, logicalCapKey, 'cip');
|
|
309
|
+
assignCipOperation(doc, k, parsed);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const pk =
|
|
313
|
+
profileKey && profileKey.trim()
|
|
314
|
+
? profileKey.trim()
|
|
315
|
+
: resolveProfileKeyForLogical(doc, logicalCapKey);
|
|
316
|
+
if (!pk) {
|
|
317
|
+
throw new Error(`Cannot resolve exposed.profiles key for "${logicalCapKey}".`);
|
|
318
|
+
}
|
|
319
|
+
assignExposedProfile(doc, pk, parsed);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @param {object} doc
|
|
324
|
+
* @param {RunCapabilityEditOpts} opts
|
|
325
|
+
* @returns {Promise<{ capKey: string, section: string, profileKey: string }>}
|
|
326
|
+
*/
|
|
327
|
+
async function resolveEditTargets(doc, opts) {
|
|
328
|
+
const keys = collectCapabilityKeys(doc);
|
|
329
|
+
if (keys.length === 0) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
'No capability keys found (check capabilities[], openapi.operations, execution.cip.operations)'
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const capKeyRaw = await promptCapabilityKey(keys, opts.capability);
|
|
335
|
+
const capKey = resolveLogicalNameForRemove(doc, capKeyRaw);
|
|
336
|
+
const section = await promptSection(opts.section);
|
|
337
|
+
let profileKey = '';
|
|
338
|
+
if (section === 'profile') {
|
|
339
|
+
const capabilityFromCli = Boolean(opts.capability && String(opts.capability).trim());
|
|
340
|
+
profileKey = await promptProfileKey(doc, opts.profile, capabilityFromCli, capKey);
|
|
341
|
+
}
|
|
342
|
+
return { capKey, section, profileKey };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @param {RunCapabilityEditOpts} opts
|
|
347
|
+
* @returns {Promise<{ resolvedPath: string, backupPath: string|null }>}
|
|
348
|
+
*/
|
|
349
|
+
async function runCapabilityEdit(opts) {
|
|
350
|
+
if (!process.stdin.isTTY) {
|
|
351
|
+
throw new Error('capability edit requires an interactive terminal (TTY). Use a terminal or SSH with -t.');
|
|
352
|
+
}
|
|
353
|
+
const resolvedPath = resolveValidateInputPath(opts.fileOrKey.trim());
|
|
354
|
+
const doc = JSON.parse(fs.readFileSync(resolvedPath, 'utf8'));
|
|
355
|
+
const { capKey, section, profileKey } = await resolveEditTargets(doc, opts);
|
|
356
|
+
const initial = getEditableJson(doc, capKey, section, profileKey);
|
|
357
|
+
const body = await promptEditorJson(initial);
|
|
358
|
+
applyEditedJson(doc, capKey, section, profileKey, body);
|
|
359
|
+
|
|
360
|
+
const validation = validateDatasourceParsed(doc);
|
|
361
|
+
if (!validation.valid) {
|
|
362
|
+
const err = new Error(validation.errors.join('\n'));
|
|
363
|
+
err.validationErrors = validation.errors;
|
|
364
|
+
throw err;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const backupPath = writeBackup(resolvedPath, Boolean(opts.noBackup));
|
|
368
|
+
atomicWriteJson(resolvedPath, doc);
|
|
369
|
+
|
|
370
|
+
return { resolvedPath, backupPath };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
module.exports = {
|
|
374
|
+
runCapabilityEdit,
|
|
375
|
+
collectCapabilityKeys,
|
|
376
|
+
resolveOpsKeyForEdit
|
|
377
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-backed capability relate (foreignKeys metadata).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview runCapabilityRelate
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const {
|
|
11
|
+
resolveValidateInputPath,
|
|
12
|
+
validateDatasourceParsed
|
|
13
|
+
} = require('../validate');
|
|
14
|
+
const { applyCapabilityRelate } = require('./relate-operations');
|
|
15
|
+
const { validateRelateSemantics } = require('./relate-validate');
|
|
16
|
+
const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
|
|
17
|
+
const { tryResolveDatasourceKeyToLocalPath, readJsonFile } = require('../../resolvers/datasource-resolver');
|
|
18
|
+
const { tryFetchDatasourceConfig } = require('../../resolvers/manifest-resolver');
|
|
19
|
+
|
|
20
|
+
function resolveSystemKeyForAuth(sourceDoc) {
|
|
21
|
+
return typeof sourceDoc?.systemKey === 'string' ? sourceDoc.systemKey.trim() : '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadTargetDocLocal(targetDatasourceKey) {
|
|
25
|
+
const localTarget = tryResolveDatasourceKeyToLocalPath(targetDatasourceKey);
|
|
26
|
+
return localTarget.ok ? readJsonFile(localTarget.path) : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function maybeFetchRemoteTargetConfig({ systemKeyForAuth, targetDatasourceKey }) {
|
|
30
|
+
const remoteFetchMeta = { attempted: false, ok: false, notAuthenticated: false };
|
|
31
|
+
if (!systemKeyForAuth) {
|
|
32
|
+
return { remoteTargetConfig: null, remoteFetchMeta };
|
|
33
|
+
}
|
|
34
|
+
remoteFetchMeta.attempted = true;
|
|
35
|
+
const remote = await tryFetchDatasourceConfig(systemKeyForAuth, targetDatasourceKey, { silent: true });
|
|
36
|
+
if (remote.ok) {
|
|
37
|
+
return {
|
|
38
|
+
remoteTargetConfig: remote.datasourceConfig,
|
|
39
|
+
remoteFetchMeta: { attempted: true, ok: true, notAuthenticated: false }
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (remote.code === 'not_authenticated') {
|
|
43
|
+
remoteFetchMeta.notAuthenticated = true;
|
|
44
|
+
}
|
|
45
|
+
return { remoteTargetConfig: null, remoteFetchMeta };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function runSemanticValidation({ sourceDoc, targetDocLocal, remoteTargetConfig, opts, remoteFetchMeta }) {
|
|
49
|
+
const semantic = validateRelateSemantics({
|
|
50
|
+
localContext: {
|
|
51
|
+
sourceDoc,
|
|
52
|
+
targetDocLocal,
|
|
53
|
+
targetDatasourceKey: opts.targetDatasource,
|
|
54
|
+
fields: opts.fields,
|
|
55
|
+
targetFields: opts.targetFields
|
|
56
|
+
},
|
|
57
|
+
remoteManifest: remoteTargetConfig
|
|
58
|
+
});
|
|
59
|
+
if (!semantic.ok) {
|
|
60
|
+
const err = new Error(semantic.errors.join('\n'));
|
|
61
|
+
err.validationErrors = semantic.errors;
|
|
62
|
+
err.validationWarnings = semantic.warnings;
|
|
63
|
+
err.remoteValidation = remoteFetchMeta;
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
return semantic;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildRelateResultEnvelope({ dryRun, resolvedPath, result, backupPath, validation, semanticWarnings, remoteValidation }) {
|
|
70
|
+
return {
|
|
71
|
+
dryRun,
|
|
72
|
+
resolvedPath,
|
|
73
|
+
patchOperations: result.patchOperations,
|
|
74
|
+
updatedSections: result.updatedSections,
|
|
75
|
+
backupPath,
|
|
76
|
+
validation,
|
|
77
|
+
semanticWarnings,
|
|
78
|
+
remoteValidation
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function applyRelateAndValidateSchema(parsed, opts, semanticWarnings, remoteValidation) {
|
|
83
|
+
const result = applyCapabilityRelate(parsed, {
|
|
84
|
+
relationName: opts.relationName,
|
|
85
|
+
targetDatasource: opts.targetDatasource,
|
|
86
|
+
fields: opts.fields,
|
|
87
|
+
targetFields: opts.targetFields,
|
|
88
|
+
// Optional enrichment for metadataSchema.properties.<relationName> generation
|
|
89
|
+
targetDoc: opts.targetDoc,
|
|
90
|
+
resolvedTargetFields: opts.resolvedTargetFields,
|
|
91
|
+
required: opts.required,
|
|
92
|
+
description: opts.description,
|
|
93
|
+
overwrite: Boolean(opts.overwrite),
|
|
94
|
+
addMetadataProperty: opts.addMetadataProperty !== false
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const validation = validateDatasourceParsed(result.doc);
|
|
98
|
+
if (!validation.valid) {
|
|
99
|
+
const err = new Error(validation.errors.join('\n'));
|
|
100
|
+
err.validationErrors = validation.errors;
|
|
101
|
+
err.validationWarnings = semanticWarnings;
|
|
102
|
+
err.remoteValidation = remoteValidation;
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { result, validation };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @typedef {object} RunCapabilityRelateOpts
|
|
111
|
+
* @property {string} fileOrKey
|
|
112
|
+
* @property {string} relationName
|
|
113
|
+
* @property {string} targetDatasource
|
|
114
|
+
* @property {string[]} fields
|
|
115
|
+
* @property {string[]|undefined} targetFields
|
|
116
|
+
* @property {boolean|undefined} [required]
|
|
117
|
+
* @property {string|undefined} [description]
|
|
118
|
+
* @property {boolean} [dryRun=false]
|
|
119
|
+
* @property {boolean} [noBackup=false]
|
|
120
|
+
* @property {boolean} [overwrite=false]
|
|
121
|
+
* @property {boolean} [addMetadataProperty=true]
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {RunCapabilityRelateOpts} opts
|
|
126
|
+
* @returns {Promise<{
|
|
127
|
+
* dryRun: boolean,
|
|
128
|
+
* resolvedPath: string,
|
|
129
|
+
* patchOperations: object[],
|
|
130
|
+
* updatedSections: string[],
|
|
131
|
+
* backupPath: string|null,
|
|
132
|
+
* validation: object
|
|
133
|
+
* }>}
|
|
134
|
+
*/
|
|
135
|
+
async function runCapabilityRelate(opts) {
|
|
136
|
+
const resolvedPath = resolveValidateInputPath(opts.fileOrKey.trim());
|
|
137
|
+
const raw = fs.readFileSync(resolvedPath, 'utf8');
|
|
138
|
+
const parsed = JSON.parse(raw);
|
|
139
|
+
|
|
140
|
+
const targetDocLocal = loadTargetDocLocal(opts.targetDatasource);
|
|
141
|
+
const systemKeyForAuth = resolveSystemKeyForAuth(parsed);
|
|
142
|
+
const { remoteTargetConfig, remoteFetchMeta } = await maybeFetchRemoteTargetConfig({
|
|
143
|
+
systemKeyForAuth,
|
|
144
|
+
targetDatasourceKey: opts.targetDatasource
|
|
145
|
+
});
|
|
146
|
+
const semantic = runSemanticValidation({
|
|
147
|
+
sourceDoc: parsed,
|
|
148
|
+
targetDocLocal,
|
|
149
|
+
remoteTargetConfig,
|
|
150
|
+
opts,
|
|
151
|
+
remoteFetchMeta
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Feed resolved target metadata into metadataSchema relation-property generation.
|
|
155
|
+
opts.targetDoc = semantic?.resolved?.targetDoc || null;
|
|
156
|
+
opts.resolvedTargetFields = semantic?.resolved?.resolvedTargetFields || null;
|
|
157
|
+
|
|
158
|
+
const { result, validation } = applyRelateAndValidateSchema(
|
|
159
|
+
parsed,
|
|
160
|
+
opts,
|
|
161
|
+
semantic.warnings,
|
|
162
|
+
remoteFetchMeta
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (opts.dryRun) {
|
|
166
|
+
return buildRelateResultEnvelope({
|
|
167
|
+
dryRun: true,
|
|
168
|
+
resolvedPath,
|
|
169
|
+
result,
|
|
170
|
+
backupPath: null,
|
|
171
|
+
validation,
|
|
172
|
+
semanticWarnings: semantic.warnings,
|
|
173
|
+
remoteValidation: remoteFetchMeta
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const backupPath = writeBackup(resolvedPath, Boolean(opts.noBackup));
|
|
178
|
+
atomicWriteJson(resolvedPath, result.doc);
|
|
179
|
+
|
|
180
|
+
return buildRelateResultEnvelope({
|
|
181
|
+
dryRun: false,
|
|
182
|
+
resolvedPath,
|
|
183
|
+
result,
|
|
184
|
+
backupPath,
|
|
185
|
+
validation,
|
|
186
|
+
semanticWarnings: semantic.warnings,
|
|
187
|
+
remoteValidation: remoteFetchMeta
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
runCapabilityRelate
|
|
193
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-backed capability remove with validation, backup, and atomic write.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Run datasource capability remove CLI operation
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const {
|
|
11
|
+
resolveValidateInputPath,
|
|
12
|
+
validateDatasourceParsed
|
|
13
|
+
} = require('../validate');
|
|
14
|
+
const { normalizeCapabilityKey } = require('./capability-key');
|
|
15
|
+
const { applyCapabilityRemove } = require('./remove-operations');
|
|
16
|
+
const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {{ valid: boolean, errors: string[] }} validation
|
|
20
|
+
* @returns {void}
|
|
21
|
+
* @throws {Error} When validation failed
|
|
22
|
+
*/
|
|
23
|
+
function assertDatasourceValid(validation) {
|
|
24
|
+
if (!validation.valid) {
|
|
25
|
+
const err = new Error(validation.errors.join('\n'));
|
|
26
|
+
err.validationErrors = validation.errors;
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} resolvedPath
|
|
33
|
+
* @param {string} capability
|
|
34
|
+
* @param {object} result - applyCapabilityRemove result
|
|
35
|
+
* @param {object} validation
|
|
36
|
+
* @param {boolean} dryRun
|
|
37
|
+
* @param {string|null} backupPath
|
|
38
|
+
* @returns {object}
|
|
39
|
+
*/
|
|
40
|
+
function makeRemoveResult(resolvedPath, capability, result, validation, dryRun, backupPath) {
|
|
41
|
+
return {
|
|
42
|
+
dryRun,
|
|
43
|
+
resolvedPath,
|
|
44
|
+
capability,
|
|
45
|
+
removed: result.removed,
|
|
46
|
+
patchOperations: result.patchOperations,
|
|
47
|
+
updatedSections: result.updatedSections,
|
|
48
|
+
backupPath,
|
|
49
|
+
validation
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {object} RunCapabilityRemoveOpts
|
|
55
|
+
* @property {string} fileOrKey
|
|
56
|
+
* @property {string} capability
|
|
57
|
+
* @property {boolean} [dryRun=false]
|
|
58
|
+
* @property {boolean} [noBackup=false]
|
|
59
|
+
* @property {boolean} [force=false]
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Execute capability remove on disk (or dry-run).
|
|
64
|
+
*
|
|
65
|
+
* @param {RunCapabilityRemoveOpts} opts
|
|
66
|
+
* @returns {Promise<object>}
|
|
67
|
+
*/
|
|
68
|
+
async function runCapabilityRemove(opts) {
|
|
69
|
+
const resolvedPath = resolveValidateInputPath(opts.fileOrKey.trim());
|
|
70
|
+
const raw = fs.readFileSync(resolvedPath, 'utf8');
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
|
|
73
|
+
const capability = normalizeCapabilityKey(opts.capability, 'Capability (--capability)');
|
|
74
|
+
|
|
75
|
+
const result = applyCapabilityRemove(parsed, {
|
|
76
|
+
capability,
|
|
77
|
+
force: Boolean(opts.force)
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/** Skip AJV when already absent with --force: doc is unchanged clone; no disk write; avoids CI/schema flakes. */
|
|
81
|
+
let validation;
|
|
82
|
+
if (opts.force && !result.removed) {
|
|
83
|
+
validation = { valid: true, errors: [], warnings: [], summary: null };
|
|
84
|
+
} else {
|
|
85
|
+
validation = validateDatasourceParsed(result.doc);
|
|
86
|
+
assertDatasourceValid(validation);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (opts.dryRun) {
|
|
90
|
+
return makeRemoveResult(resolvedPath, capability, result, validation, true, null);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!result.removed) {
|
|
94
|
+
return makeRemoveResult(resolvedPath, capability, result, validation, false, null);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const backupPath = writeBackup(resolvedPath, Boolean(opts.noBackup));
|
|
98
|
+
atomicWriteJson(resolvedPath, result.doc);
|
|
99
|
+
|
|
100
|
+
return makeRemoveResult(resolvedPath, capability, result, validation, false, backupPath);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
runCapabilityRemove
|
|
105
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapiOperation": {
|
|
3
|
+
"operationId": "minimal-fetch-placeholder",
|
|
4
|
+
"method": "GET",
|
|
5
|
+
"path": "/minimal-fetch"
|
|
6
|
+
},
|
|
7
|
+
"cipOperation": {
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"steps": [
|
|
10
|
+
{
|
|
11
|
+
"fetch": {
|
|
12
|
+
"source": "openapi",
|
|
13
|
+
"openapiRef": "__STORAGE_KEY__"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|