@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,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional capability-key checks after schema validation.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview capability validate command helpers
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { normalizeCapabilityKey } = require('./capability-key');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lists structural locations where a capability key should appear.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} parsed - Datasource JSON
|
|
15
|
+
* @param {string} rawKey - Capability key
|
|
16
|
+
* @returns {{ key: string, missing: string[] }}
|
|
17
|
+
*/
|
|
18
|
+
function checkCapabilitySlices(parsed, rawKey) {
|
|
19
|
+
const key = normalizeCapabilityKey(rawKey, 'capability');
|
|
20
|
+
const missing = [];
|
|
21
|
+
if (!Array.isArray(parsed.capabilities) || !parsed.capabilities.includes(key)) {
|
|
22
|
+
missing.push(`capabilities[] (missing "${key}")`);
|
|
23
|
+
}
|
|
24
|
+
if (!parsed.openapi?.operations?.[key]) {
|
|
25
|
+
missing.push(`openapi.operations.${key}`);
|
|
26
|
+
}
|
|
27
|
+
if (!parsed.execution?.cip?.operations?.[key]) {
|
|
28
|
+
missing.push(`execution.cip.operations.${key}`);
|
|
29
|
+
}
|
|
30
|
+
return { key, missing };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
checkCapabilitySlices
|
|
35
|
+
};
|
package/lib/datasource/list.js
CHANGED
|
@@ -3,7 +3,10 @@ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
|
|
|
3
3
|
* Datasource List Command
|
|
4
4
|
*
|
|
5
5
|
* Lists datasources from the dataplane (GET /api/v1/external).
|
|
6
|
-
* Resolves dataplane URL from the controller, then calls the dataplane list API
|
|
6
|
+
* Resolves dataplane URL from the controller, then calls the dataplane list API with
|
|
7
|
+
* pagination (pageSize up to API max) so results are not capped at the first page.
|
|
8
|
+
* Optional key prefix uses API filter `key:like:<prefix>%` (SQL LIKE prefix) plus a
|
|
9
|
+
* client-side safety pass on the merged rows.
|
|
7
10
|
*
|
|
8
11
|
* @fileoverview Datasource listing for AI Fabrix Builder
|
|
9
12
|
* @author AI Fabrix Team
|
|
@@ -18,6 +21,90 @@ const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
|
18
21
|
const { formatApiError } = require('../utils/api-error-handler');
|
|
19
22
|
const logger = require('../utils/logger');
|
|
20
23
|
|
|
24
|
+
/** Dataplane GET /api/v1/external allows pageSize up to 100 */
|
|
25
|
+
const LIST_DATASOURCE_PAGE_SIZE = 100;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Filter query fragment for datasource keys that start with a prefix (dataplane list API).
|
|
29
|
+
* @param {string} prefix - Trimmed non-empty prefix
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function buildKeyPrefixFilterParam(prefix) {
|
|
33
|
+
return `key:like:${prefix}%`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {Object|null|undefined} meta - Response `meta` object when present
|
|
38
|
+
* @returns {number|null}
|
|
39
|
+
*/
|
|
40
|
+
function readTotalPagesFromMeta(meta) {
|
|
41
|
+
if (meta && typeof meta.totalPages === 'number' && meta.totalPages > 0) {
|
|
42
|
+
return meta.totalPages;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {number} batchLen
|
|
49
|
+
* @param {number} pageSize
|
|
50
|
+
* @param {number} page - 1-based page index
|
|
51
|
+
* @param {number|null} totalPages
|
|
52
|
+
* @returns {boolean}
|
|
53
|
+
*/
|
|
54
|
+
function shouldStopDatasourcePaging(batchLen, pageSize, page, totalPages) {
|
|
55
|
+
if (batchLen < pageSize) return true;
|
|
56
|
+
if (typeof totalPages === 'number' && page >= totalPages) return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Loads every page of datasources from the dataplane for the current query.
|
|
62
|
+
* @async
|
|
63
|
+
* @param {string} dataplaneUrl
|
|
64
|
+
* @param {Object} authConfig
|
|
65
|
+
* @param {string|null} keyPrefix - Trimmed prefix or null for full list
|
|
66
|
+
* @returns {Promise<Array<Object>>}
|
|
67
|
+
*/
|
|
68
|
+
async function fetchAllDatasourcePages(dataplaneUrl, authConfig, keyPrefix) {
|
|
69
|
+
const merged = [];
|
|
70
|
+
let page = 1;
|
|
71
|
+
for (;;) {
|
|
72
|
+
if (page > 10000) break;
|
|
73
|
+
const params = { page, pageSize: LIST_DATASOURCE_PAGE_SIZE };
|
|
74
|
+
if (keyPrefix) params.filter = buildKeyPrefixFilterParam(keyPrefix);
|
|
75
|
+
const response = await listDatasourcesFromDataplane(dataplaneUrl, authConfig, params);
|
|
76
|
+
if (!response.success || !response.data) {
|
|
77
|
+
const err = new Error('LIST_DATASOURCES_FAILED');
|
|
78
|
+
err.response = response;
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
const batch = extractDatasources(response);
|
|
82
|
+
if (!batch.length) break;
|
|
83
|
+
merged.push(...batch);
|
|
84
|
+
const totalPages = readTotalPagesFromMeta(response.data.meta);
|
|
85
|
+
if (shouldStopDatasourcePaging(batch.length, LIST_DATASOURCE_PAGE_SIZE, page, totalPages)) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
page += 1;
|
|
89
|
+
}
|
|
90
|
+
return merged;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @returns {Promise<Array<Object>|null>} Rows or null if API failure was handled (exit)
|
|
95
|
+
*/
|
|
96
|
+
async function fetchDatasourcesForList(dataplaneUrl, authConfig, keyPrefix) {
|
|
97
|
+
try {
|
|
98
|
+
return await fetchAllDatasourcePages(dataplaneUrl, authConfig, keyPrefix);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err && err.response) {
|
|
101
|
+
handleDatasourceApiError(err.response, dataplaneUrl);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
21
108
|
/**
|
|
22
109
|
* Extracts datasources array from API response
|
|
23
110
|
* Handles multiple response formats similar to applications list
|
|
@@ -100,21 +187,47 @@ function extractDatasources(response) {
|
|
|
100
187
|
return datasources;
|
|
101
188
|
}
|
|
102
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Keeps datasources whose `key` starts with the given prefix (trimmed). No-op if prefix empty.
|
|
192
|
+
*
|
|
193
|
+
* @param {Array<Object>} datasources - Datasource objects from API
|
|
194
|
+
* @param {string} [prefix] - Match against datasource key (prefix match)
|
|
195
|
+
* @returns {Array<Object>}
|
|
196
|
+
*/
|
|
197
|
+
function filterDatasourcesByKeyPrefix(datasources, prefix) {
|
|
198
|
+
if (!prefix || typeof prefix !== 'string') return datasources;
|
|
199
|
+
const p = prefix.trim();
|
|
200
|
+
if (!p) return datasources;
|
|
201
|
+
return datasources.filter((ds) => {
|
|
202
|
+
const k = String(ds?.key ?? '');
|
|
203
|
+
return k.startsWith(p);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
103
207
|
/**
|
|
104
208
|
* Displays datasources in a formatted table
|
|
105
209
|
*
|
|
106
210
|
* @function displayDatasources
|
|
107
211
|
* @param {Array} datasources - Array of datasource objects
|
|
108
212
|
* @param {string} environment - Environment key
|
|
109
|
-
* @param {string} dataplaneUrl - Dataplane URL for header display
|
|
213
|
+
* @param {string} [dataplaneUrl] - Dataplane URL for header display
|
|
214
|
+
* @param {string|null} [keyPrefix] - When set, header notes filter on datasource key
|
|
110
215
|
*/
|
|
111
|
-
function displayDatasources(datasources, environment, dataplaneUrl) {
|
|
216
|
+
function displayDatasources(datasources, environment, dataplaneUrl, keyPrefix = null) {
|
|
112
217
|
const environmentName = environment || 'dev';
|
|
113
|
-
const
|
|
218
|
+
const prefixNote =
|
|
219
|
+
keyPrefix && String(keyPrefix).trim()
|
|
220
|
+
? ` — datasource keys starting with "${String(keyPrefix).trim()}"`
|
|
221
|
+
: '';
|
|
222
|
+
const header = `Datasources in ${environmentName} environment${dataplaneUrl ? ` (${dataplaneUrl})` : ''}${prefixNote}`;
|
|
114
223
|
|
|
115
224
|
if (datasources.length === 0) {
|
|
116
225
|
logger.log(chalk.bold(`\n📋 ${header}:\n`));
|
|
117
|
-
|
|
226
|
+
const emptyDetail =
|
|
227
|
+
keyPrefix && String(keyPrefix).trim()
|
|
228
|
+
? ` No datasources with keys starting with "${String(keyPrefix).trim()}".\n`
|
|
229
|
+
: ' No datasources found in this environment.\n';
|
|
230
|
+
logger.log(chalk.gray(emptyDetail));
|
|
118
231
|
return;
|
|
119
232
|
}
|
|
120
233
|
|
|
@@ -133,14 +246,6 @@ function displayDatasources(datasources, environment, dataplaneUrl) {
|
|
|
133
246
|
logger.log('');
|
|
134
247
|
}
|
|
135
248
|
|
|
136
|
-
/**
|
|
137
|
-
* Lists datasources from an environment
|
|
138
|
-
*
|
|
139
|
-
* @async
|
|
140
|
-
* @function listDatasources
|
|
141
|
-
* @param {Object} _options - Command options (unused, kept for compatibility)
|
|
142
|
-
* @throws {Error} If listing fails
|
|
143
|
-
*/
|
|
144
249
|
/**
|
|
145
250
|
* Gets device token from config
|
|
146
251
|
* @async
|
|
@@ -275,7 +380,11 @@ function setupAuthConfig(token, controllerUrl) {
|
|
|
275
380
|
};
|
|
276
381
|
}
|
|
277
382
|
|
|
278
|
-
|
|
383
|
+
/**
|
|
384
|
+
* @param {Object} [options]
|
|
385
|
+
* @param {string} [options.keyPrefix] - If set, only datasources whose `key` starts with this string (after trim)
|
|
386
|
+
*/
|
|
387
|
+
async function listDatasources(options = {}) {
|
|
279
388
|
const config = await getConfig();
|
|
280
389
|
|
|
281
390
|
// Resolve environment from config.yaml (no flags)
|
|
@@ -296,21 +405,25 @@ async function listDatasources(_options) {
|
|
|
296
405
|
// Resolve dataplane URL first (required for list call)
|
|
297
406
|
const dataplaneUrl = await resolveAndValidateDataplaneUrl(controllerUrl, environment, authConfig);
|
|
298
407
|
|
|
299
|
-
|
|
300
|
-
const
|
|
408
|
+
const rawPrefix = options.keyPrefix;
|
|
409
|
+
const keyPrefix =
|
|
410
|
+
typeof rawPrefix === 'string' && rawPrefix.trim() ? rawPrefix.trim() : null;
|
|
301
411
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return; // Ensure we don't continue after exit
|
|
305
|
-
}
|
|
412
|
+
let datasources = await fetchDatasourcesForList(dataplaneUrl, authConfig, keyPrefix);
|
|
413
|
+
if (datasources === null) return;
|
|
306
414
|
|
|
307
|
-
|
|
308
|
-
|
|
415
|
+
if (keyPrefix) {
|
|
416
|
+
datasources = filterDatasourcesByKeyPrefix(datasources, keyPrefix);
|
|
417
|
+
}
|
|
418
|
+
displayDatasources(datasources, environment, dataplaneUrl, keyPrefix);
|
|
309
419
|
}
|
|
310
420
|
|
|
311
421
|
module.exports = {
|
|
312
422
|
listDatasources,
|
|
313
423
|
displayDatasources,
|
|
314
|
-
extractDatasources
|
|
424
|
+
extractDatasources,
|
|
425
|
+
fetchAllDatasourcePages,
|
|
426
|
+
buildKeyPrefixFilterParam,
|
|
427
|
+
filterDatasourcesByKeyPrefix
|
|
315
428
|
};
|
|
316
429
|
|
|
@@ -320,10 +320,8 @@ function parseDatasourceLogJson(content, logPath) {
|
|
|
320
320
|
async function runLogViewer(datasourceKey, options) {
|
|
321
321
|
const { app, file, logType } = options;
|
|
322
322
|
let logPath;
|
|
323
|
-
let fileName;
|
|
324
323
|
if (file && typeof file === 'string' && file.trim()) {
|
|
325
324
|
logPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file.trim());
|
|
326
|
-
fileName = path.basename(logPath);
|
|
327
325
|
} else {
|
|
328
326
|
if (!datasourceKey || typeof datasourceKey !== 'string') {
|
|
329
327
|
throw new Error('Datasource key is required when --file is not provided');
|
|
@@ -347,11 +345,11 @@ async function runLogViewer(datasourceKey, options) {
|
|
|
347
345
|
);
|
|
348
346
|
}
|
|
349
347
|
}
|
|
350
|
-
fileName = path.basename(logPath);
|
|
351
348
|
}
|
|
349
|
+
const resolvedLogPath = path.resolve(logPath);
|
|
352
350
|
const content = await fsp.readFile(logPath, 'utf8');
|
|
353
351
|
const parsed = parseDatasourceLogJson(content, logPath);
|
|
354
|
-
formatLogContent(parsed, logType,
|
|
352
|
+
formatLogContent(parsed, logType, resolvedLogPath);
|
|
355
353
|
}
|
|
356
354
|
|
|
357
355
|
module.exports = {
|
|
@@ -16,11 +16,53 @@ const { getSystemKeyFromAppKey, findDatasourceFileByKey } = require('./integrati
|
|
|
16
16
|
const { loadDatasourceForApp } = require('./unified-validation-run-resolve');
|
|
17
17
|
const { buildUnifiedValidationBody } = require('./unified-validation-run-body');
|
|
18
18
|
const { postValidationRunAndOptionalPoll } = require('./unified-validation-run-post');
|
|
19
|
-
const { publishDatasourceViaPipeline } = require('../api/pipeline.api');
|
|
20
19
|
const { requireBearerForDataplanePipeline } = require('../utils/token-manager');
|
|
21
|
-
const { formatApiError } = require('../utils/api-error-handler');
|
|
22
20
|
const logger = require('../utils/logger');
|
|
23
21
|
|
|
22
|
+
function _resourceTypeFromDatasource(datasource) {
|
|
23
|
+
return datasource && typeof datasource === 'object' && typeof datasource.resourceType === 'string'
|
|
24
|
+
? datasource.resourceType
|
|
25
|
+
: null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _extractOpKeyFromDpSec013Message(msg, datasourceKey) {
|
|
29
|
+
if (typeof msg !== 'string' || !msg.trim()) return '';
|
|
30
|
+
const m = msg.match(new RegExp(`^${datasourceKey}:([^\\s-]+)\\s*-`));
|
|
31
|
+
return m && m[1] ? String(m[1]).trim() : '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function annotateMissingAutoRbacPermissions(envelope, datasourceKey, datasource) {
|
|
35
|
+
const issues =
|
|
36
|
+
envelope && envelope.validation && Array.isArray(envelope.validation.issues)
|
|
37
|
+
? envelope.validation.issues
|
|
38
|
+
: [];
|
|
39
|
+
if (!issues.length) return;
|
|
40
|
+
|
|
41
|
+
const resourceType = _resourceTypeFromDatasource(datasource);
|
|
42
|
+
if (!resourceType) return;
|
|
43
|
+
|
|
44
|
+
for (const iss of issues) {
|
|
45
|
+
if (!iss || iss.code !== 'DP-SEC-013' || typeof iss.message !== 'string') continue;
|
|
46
|
+
const operationKey = _extractOpKeyFromDpSec013Message(iss.message, datasourceKey);
|
|
47
|
+
if (!operationKey) continue;
|
|
48
|
+
|
|
49
|
+
iss.details = iss.details && typeof iss.details === 'object' ? iss.details : {};
|
|
50
|
+
iss.details.resolvedPermission = `${resourceType}:${operationKey}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function maybeSyncDatasourceToDataplane({ options, authConfig, systemKey }) {
|
|
55
|
+
if (options.sync !== true) return;
|
|
56
|
+
requireBearerForDataplanePipeline(authConfig);
|
|
57
|
+
logger.log(chalk.cyan('Syncing local config to dataplane…'));
|
|
58
|
+
// Publish full external system (system + all datasources) to ensure system-level RBAC/permissions
|
|
59
|
+
// are updated before validation runs. Datasource-only publish can still fail DP-SEC-013 when the
|
|
60
|
+
// dataplane system permissions[] are stale.
|
|
61
|
+
const { uploadExternalSystem } = require('../commands/upload');
|
|
62
|
+
await uploadExternalSystem(systemKey, { minimal: true });
|
|
63
|
+
logger.log(formatSuccessLine('Sync complete'));
|
|
64
|
+
}
|
|
65
|
+
|
|
24
66
|
/**
|
|
25
67
|
* Resolve datasource JSON path and loaded object (same rules as test-integration).
|
|
26
68
|
* Re-export for tests.
|
|
@@ -49,19 +91,7 @@ async function runUnifiedDatasourceValidation(datasourceKey, options) {
|
|
|
49
91
|
const configObj = await getConfig();
|
|
50
92
|
const { authConfig, dataplaneUrl } = await setupIntegrationTestAuth(appKey, options, configObj);
|
|
51
93
|
|
|
52
|
-
|
|
53
|
-
requireBearerForDataplanePipeline(authConfig);
|
|
54
|
-
logger.log(chalk.cyan('Syncing local config to dataplane…'));
|
|
55
|
-
const publishResponse = await publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasource);
|
|
56
|
-
if (!publishResponse || publishResponse.success === false) {
|
|
57
|
-
const msg =
|
|
58
|
-
(publishResponse && (publishResponse.formattedError || publishResponse.error)) ||
|
|
59
|
-
formatApiError(publishResponse, dataplaneUrl) ||
|
|
60
|
-
'Publish failed';
|
|
61
|
-
throw new Error(`Sync failed: ${msg}`);
|
|
62
|
-
}
|
|
63
|
-
logger.log(formatSuccessLine('Sync complete'));
|
|
64
|
-
}
|
|
94
|
+
await maybeSyncDatasourceToDataplane({ options, authConfig, systemKey });
|
|
65
95
|
|
|
66
96
|
const useAsync = options.noAsync ? false : options.async !== false;
|
|
67
97
|
const timeoutMs = parseInt(String(options.timeout || '30000'), 10) || 30000;
|
|
@@ -76,7 +106,7 @@ async function runUnifiedDatasourceValidation(datasourceKey, options) {
|
|
|
76
106
|
options
|
|
77
107
|
});
|
|
78
108
|
|
|
79
|
-
|
|
109
|
+
const result = await postValidationRunAndOptionalPoll({
|
|
80
110
|
dataplaneUrl,
|
|
81
111
|
authConfig,
|
|
82
112
|
body,
|
|
@@ -85,6 +115,11 @@ async function runUnifiedDatasourceValidation(datasourceKey, options) {
|
|
|
85
115
|
noAsync: options.noAsync === true || options.async === false,
|
|
86
116
|
verbosePoll: options.verbose === true
|
|
87
117
|
});
|
|
118
|
+
|
|
119
|
+
if (result && result.envelope) {
|
|
120
|
+
annotateMissingAutoRbacPermissions(result.envelope, datasourceKey, datasource);
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
88
123
|
}
|
|
89
124
|
|
|
90
125
|
module.exports = {
|
|
@@ -229,8 +229,60 @@ async function validateDatasourceFile(filePathOrKey) {
|
|
|
229
229
|
};
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Validates parsed datasource JSON (same rules as {@link validateDatasourceFile}, without reading a file).
|
|
234
|
+
*
|
|
235
|
+
* @param {Object} parsed - Parsed datasource object
|
|
236
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[], summary: Object|null }}
|
|
237
|
+
*/
|
|
238
|
+
function validateDatasourceParsed(parsed) {
|
|
239
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
240
|
+
return {
|
|
241
|
+
valid: false,
|
|
242
|
+
errors: ['Datasource JSON must be an object'],
|
|
243
|
+
warnings: [],
|
|
244
|
+
summary: null
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const summary = buildDatasourceValidateSummary(parsed);
|
|
249
|
+
|
|
250
|
+
const validate = loadExternalDataSourceSchema();
|
|
251
|
+
const schemaValid = validate(parsed);
|
|
252
|
+
|
|
253
|
+
if (!schemaValid) {
|
|
254
|
+
return {
|
|
255
|
+
valid: false,
|
|
256
|
+
errors: formatValidationErrors(validate.errors, { rootData: parsed, dedupe: true }),
|
|
257
|
+
warnings: [],
|
|
258
|
+
summary
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const fieldRefErrors = validateFieldReferences(parsed);
|
|
263
|
+
const abacErrors = validateAbac(parsed);
|
|
264
|
+
const postSchemaErrors = [...fieldRefErrors, ...abacErrors];
|
|
265
|
+
if (postSchemaErrors.length > 0) {
|
|
266
|
+
return {
|
|
267
|
+
valid: false,
|
|
268
|
+
errors: postSchemaErrors,
|
|
269
|
+
warnings: [],
|
|
270
|
+
summary
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
valid: true,
|
|
276
|
+
errors: [],
|
|
277
|
+
warnings: [],
|
|
278
|
+
summary
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
232
282
|
module.exports = {
|
|
233
283
|
validateDatasourceFile,
|
|
234
|
-
|
|
284
|
+
validateDatasourceParsed,
|
|
285
|
+
resolveValidateInputPath,
|
|
286
|
+
resolvePathFromIntegrationDatasourceKey
|
|
235
287
|
};
|
|
236
288
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTY ora spinner + non-TTY lines for deploy pipeline polling (aligned with guided infra commands).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Deploy poll presentation helpers
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const ora = require('ora');
|
|
13
|
+
const logger = require('../utils/logger');
|
|
14
|
+
const { buildDeployPollSpinnerText } = require('./deployer-status');
|
|
15
|
+
|
|
16
|
+
function shouldUseDeployPollSpinner() {
|
|
17
|
+
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {number} maxAttempts - Max polling attempts (shown in UI)
|
|
22
|
+
* @param {{ silent?: boolean }} [options]
|
|
23
|
+
* @returns {{ onPollProgress: Function, finish: () => void }}
|
|
24
|
+
*/
|
|
25
|
+
function createDeployPollHandlers(maxAttempts, options = {}) {
|
|
26
|
+
const silent = options && options.silent === true;
|
|
27
|
+
if (silent) {
|
|
28
|
+
return {
|
|
29
|
+
onPollProgress: () => {},
|
|
30
|
+
finish: () => {}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (shouldUseDeployPollSpinner()) {
|
|
34
|
+
const spinner = ora({
|
|
35
|
+
text: buildDeployPollSpinnerText(null, 0, maxAttempts),
|
|
36
|
+
spinner: 'dots'
|
|
37
|
+
}).start();
|
|
38
|
+
return {
|
|
39
|
+
onPollProgress: (deploymentData, attempt, maxA) => {
|
|
40
|
+
spinner.text = buildDeployPollSpinnerText(deploymentData, attempt, maxA);
|
|
41
|
+
},
|
|
42
|
+
finish: () => {
|
|
43
|
+
spinner.stop();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
onPollProgress: (deploymentData, attempt, maxA) => {
|
|
49
|
+
const status = deploymentData.status ?? 'pending';
|
|
50
|
+
const progress = deploymentData.progress ?? 0;
|
|
51
|
+
logger.log(chalk.gray(`Status: ${status} (${progress}%) (attempt ${attempt + 1}/${maxA})`));
|
|
52
|
+
},
|
|
53
|
+
finish: () => {}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
createDeployPollHandlers,
|
|
59
|
+
shouldUseDeployPollSpinner
|
|
60
|
+
};
|
|
@@ -53,7 +53,7 @@ function extractDeploymentData(response) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Logs deployment progress
|
|
56
|
+
* Logs deployment progress (non-spinner / programmatic polling)
|
|
57
57
|
* @param {Object} deploymentData - Deployment data
|
|
58
58
|
* @param {number} attempt - Current attempt
|
|
59
59
|
* @param {number} maxAttempts - Maximum attempts
|
|
@@ -64,6 +64,19 @@ function logDeploymentProgress(deploymentData, attempt, maxAttempts) {
|
|
|
64
64
|
logger.log(chalk.blue(` Status: ${status} (${progress}%) (attempt ${attempt + 1}/${maxAttempts})`));
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Single-line ora text for deploy polling (updates in place; same style as guided infra spinners).
|
|
69
|
+
* @param {Object|null|undefined} deploymentData - Latest deployment payload (optional before first response)
|
|
70
|
+
* @param {number} attempt - Zero-based attempt index
|
|
71
|
+
* @param {number} maxAttempts - Max polling attempts
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function buildDeployPollSpinnerText(deploymentData, attempt, maxAttempts) {
|
|
75
|
+
const status = deploymentData?.status ?? 'pending';
|
|
76
|
+
const progress = deploymentData && Number.isFinite(Number(deploymentData.progress)) ? Number(deploymentData.progress) : 0;
|
|
77
|
+
return `Deploying application... Status: ${status} (${progress}%) (attempt ${attempt + 1}/${maxAttempts})`;
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
/**
|
|
68
81
|
* Process deployment status response
|
|
69
82
|
* @param {Object} response - API response
|
|
@@ -71,9 +84,17 @@ function logDeploymentProgress(deploymentData, attempt, maxAttempts) {
|
|
|
71
84
|
* @param {number} maxAttempts - Maximum attempts
|
|
72
85
|
* @param {number} interval - Polling interval
|
|
73
86
|
* @param {string} deploymentId - Deployment ID for error messages
|
|
87
|
+
* @param {Function|null} [onProgress] - If set, called instead of logDeploymentProgress with (deploymentData, attempt, maxAttempts)
|
|
74
88
|
* @returns {Promise<Object|null>} Deployment data if terminal, null if needs to continue polling
|
|
75
89
|
*/
|
|
76
|
-
async function processDeploymentStatusResponse(
|
|
90
|
+
async function processDeploymentStatusResponse(
|
|
91
|
+
response,
|
|
92
|
+
attempt,
|
|
93
|
+
maxAttempts,
|
|
94
|
+
interval,
|
|
95
|
+
deploymentId,
|
|
96
|
+
onProgress = null
|
|
97
|
+
) {
|
|
77
98
|
if (!response.success || !response.data) {
|
|
78
99
|
handleDeploymentStatusError(response, deploymentId);
|
|
79
100
|
}
|
|
@@ -83,7 +104,11 @@ async function processDeploymentStatusResponse(response, attempt, maxAttempts, i
|
|
|
83
104
|
return deploymentData;
|
|
84
105
|
}
|
|
85
106
|
|
|
86
|
-
|
|
107
|
+
if (typeof onProgress === 'function') {
|
|
108
|
+
onProgress(deploymentData, attempt, maxAttempts);
|
|
109
|
+
} else {
|
|
110
|
+
logDeploymentProgress(deploymentData, attempt, maxAttempts);
|
|
111
|
+
}
|
|
87
112
|
if (attempt < maxAttempts - 1) {
|
|
88
113
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
89
114
|
}
|
|
@@ -97,5 +122,6 @@ module.exports = {
|
|
|
97
122
|
handleDeploymentStatusError,
|
|
98
123
|
extractDeploymentData,
|
|
99
124
|
logDeploymentProgress,
|
|
125
|
+
buildDeployPollSpinnerText,
|
|
100
126
|
processDeploymentStatusResponse
|
|
101
127
|
};
|