@aifabrix/builder 2.44.3 → 2.44.4
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/.npmrc.token +1 -1
- package/integration/roundtrip-test-local/README.md +1 -2
- package/integration/roundtrip-test-local2/README.md +1 -2
- package/jest.projects.js +12 -1
- package/lib/api/certificates.api.js +21 -3
- package/lib/certification/post-unified-cert-sync.js +13 -2
- package/lib/certification/sync-after-external-command.js +6 -3
- package/lib/certification/sync-system-certification.js +60 -14
- package/lib/cli/setup-app.test-commands.js +67 -35
- package/lib/cli/setup-utility.js +1 -1
- package/lib/commands/datasource-unified-test-cli.js +81 -46
- package/lib/commands/datasource-unified-test-cli.options.js +4 -2
- package/lib/commands/datasource.js +3 -31
- package/lib/commands/repair-datasource-keys.js +1 -1
- package/lib/commands/repair-datasource-openapi.js +57 -0
- package/lib/commands/repair-datasource.js +5 -0
- package/lib/commands/repair-internal.js +2 -4
- package/lib/commands/repair.js +1 -2
- package/lib/commands/test-e2e-external.js +5 -6
- package/lib/commands/upload.js +18 -4
- package/lib/commands/wizard-dataplane.js +14 -6
- package/lib/datasource/datasource-validate-display.js +162 -0
- package/lib/datasource/datasource-validate-summary.js +194 -0
- package/lib/datasource/test-e2e.js +65 -37
- package/lib/datasource/unified-validation-run-body.js +1 -2
- package/lib/datasource/validate.js +14 -6
- package/lib/external-system/test.js +12 -8
- package/lib/generator/external-controller-manifest.js +12 -2
- package/lib/schema/cip-capacity-display.fallback.json +7 -0
- package/lib/schema/datasource-test-run.schema.json +79 -1
- package/lib/schema/external-datasource.schema.json +94 -2
- package/lib/schema/flag-map-validation-run.json +1 -2
- package/lib/schema/type/document-storage.json +83 -3
- package/lib/utils/configuration-env-resolver.js +38 -0
- package/lib/utils/dataplane-resolver.js +3 -2
- package/lib/utils/datasource-test-run-capacity-operations.js +149 -0
- package/lib/utils/datasource-test-run-debug-display.js +143 -1
- package/lib/utils/datasource-test-run-display.js +46 -33
- package/lib/utils/datasource-test-run-tty-log.js +6 -2
- package/lib/utils/datasource-test-run-tty-meta-lines.js +123 -0
- package/lib/utils/error-formatter.js +32 -2
- package/lib/utils/external-system-readiness-core.js +39 -0
- package/lib/utils/external-system-readiness-deploy-display.js +2 -3
- package/lib/utils/external-system-readiness-display-internals.js +3 -2
- package/lib/utils/external-system-system-test-tty.js +33 -9
- package/lib/utils/external-system-validators.js +62 -5
- package/lib/utils/load-cip-capacity-display-config.js +130 -0
- package/lib/utils/paths.js +10 -3
- package/lib/utils/schema-resolver.js +98 -2
- package/lib/utils/validation-run-poll.js +15 -4
- package/lib/utils/validation-run-request.js +4 -6
- package/lib/validation/dimension-display-helpers.js +60 -0
- package/lib/validation/validate-display-log-helpers.js +39 -0
- package/lib/validation/validate-display.js +89 -83
- package/package.json +1 -1
- package/templates/external-system/README.md.hbs +1 -2
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const chalk = require('chalk');
|
|
14
13
|
const logger = require('../utils/logger');
|
|
15
|
-
const {
|
|
14
|
+
const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
|
|
16
15
|
const { validateDatasourceFile } = require('../datasource/validate');
|
|
16
|
+
const { logDatasourceValidateOutcome } = require('../datasource/datasource-validate-display');
|
|
17
17
|
const { listDatasources } = require('../datasource/list');
|
|
18
18
|
const { compareDatasources } = require('../datasource/diff');
|
|
19
19
|
const { deployDatasource } = require('../datasource/deploy');
|
|
@@ -53,34 +53,6 @@ Examples:
|
|
|
53
53
|
$ af ds upload ../integration/hubspot/hubspot-datasource-deals.json
|
|
54
54
|
`;
|
|
55
55
|
|
|
56
|
-
/**
|
|
57
|
-
* TTY layout for local datasource JSON validation (aligned with cli-test-layout-chalk).
|
|
58
|
-
* @param {{ valid: boolean, errors: string[], resolvedPath: string }} result
|
|
59
|
-
* @param {string} trimmed - original CLI argument
|
|
60
|
-
* @param {boolean} showMapping - show Key + File when key resolved to a path
|
|
61
|
-
*/
|
|
62
|
-
function logDatasourceValidateOutcome(result, trimmed, showMapping) {
|
|
63
|
-
logger.log('');
|
|
64
|
-
logger.log(sectionTitle('Datasource validation'));
|
|
65
|
-
logger.log(metadata('Offline — JSON schema and integration wiring'));
|
|
66
|
-
logger.log('');
|
|
67
|
-
if (!result.valid) {
|
|
68
|
-
logger.log(headerKeyValue('File:', result.resolvedPath));
|
|
69
|
-
logger.log('');
|
|
70
|
-
logger.log(formatBlockingError('Datasource file has errors:'));
|
|
71
|
-
result.errors.forEach(error => logger.log(chalk.red(` • ${error}`)));
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (showMapping) {
|
|
75
|
-
logger.log(headerKeyValue('Key:', trimmed));
|
|
76
|
-
logger.log(headerKeyValue('File:', result.resolvedPath));
|
|
77
|
-
} else {
|
|
78
|
-
logger.log(headerKeyValue('File:', result.resolvedPath));
|
|
79
|
-
}
|
|
80
|
-
logger.log('');
|
|
81
|
-
logger.log(formatSuccessLine('Datasource file is valid.'));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
56
|
function setupDatasourceValidateCommand(datasource) {
|
|
85
57
|
datasource.command('validate <file-or-key>')
|
|
86
58
|
.description('Validate datasource JSON (file path or datasource key under integration/<app>/)')
|
|
@@ -97,7 +69,7 @@ function setupDatasourceValidateCommand(datasource) {
|
|
|
97
69
|
process.exit(1);
|
|
98
70
|
}
|
|
99
71
|
} catch (error) {
|
|
100
|
-
logger.error(formatBlockingError(
|
|
72
|
+
logger.error(formatBlockingError(`Validation failed: ${error.message}`));
|
|
101
73
|
process.exit(1);
|
|
102
74
|
}
|
|
103
75
|
});
|
|
@@ -193,7 +193,7 @@ function normalizeDatasourceKeysAndFilenames(appPath, datasourceFiles, systemKey
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
if (updated && variables.externalIntegration && Array.isArray(variables.externalIntegration.dataSources)) {
|
|
196
|
-
variables.externalIntegration.dataSources = [...newDatasourceFiles]
|
|
196
|
+
variables.externalIntegration.dataSources = [...newDatasourceFiles];
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
return { updated, datasourceFiles: newDatasourceFiles };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Repair OpenAPI block on external datasource JSON.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
function hasNonEmptyObject(v) {
|
|
10
|
+
return !!v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Repair `openapi` section:
|
|
15
|
+
* - If operations exist, ensure `openapi.enabled=true`
|
|
16
|
+
* - If enabled/operations and missing documentKey/fileId, default documentKey to datasource key (common convention)
|
|
17
|
+
* - If enabled+operations and autoRbac missing, default to true (wizard/fixtures expectation)
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} parsed - Parsed datasource (mutated)
|
|
20
|
+
* @param {string[]} changes - Change log
|
|
21
|
+
* @returns {boolean} True if updated
|
|
22
|
+
*/
|
|
23
|
+
function repairOpenapiSection(parsed, changes) {
|
|
24
|
+
const openapi = parsed?.openapi;
|
|
25
|
+
if (!openapi || typeof openapi !== 'object') {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const hasOps = hasNonEmptyObject(openapi.operations);
|
|
30
|
+
const enabled = openapi.enabled === true;
|
|
31
|
+
let updated = false;
|
|
32
|
+
|
|
33
|
+
if (hasOps && openapi.enabled !== true) {
|
|
34
|
+
openapi.enabled = true;
|
|
35
|
+
changes.push('Set openapi.enabled=true (operations present)');
|
|
36
|
+
updated = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ((enabled || hasOps) && !openapi.documentKey && !openapi.fileId) {
|
|
40
|
+
if (typeof parsed.key === 'string' && parsed.key.trim()) {
|
|
41
|
+
openapi.documentKey = parsed.key.trim();
|
|
42
|
+
changes.push(`Set openapi.documentKey=${openapi.documentKey}`);
|
|
43
|
+
updated = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if ((enabled || hasOps) && hasOps && openapi.autoRbac === undefined) {
|
|
48
|
+
openapi.autoRbac = true;
|
|
49
|
+
changes.push('Set openapi.autoRbac=true (enabled operations, default)');
|
|
50
|
+
updated = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return updated;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { repairOpenapiSection };
|
|
57
|
+
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
+
const { repairOpenapiSection } = require('./repair-datasource-openapi');
|
|
13
|
+
|
|
12
14
|
const DEFAULT_SYNC = {
|
|
13
15
|
mode: 'pull',
|
|
14
16
|
batchSize: 500
|
|
@@ -446,6 +448,8 @@ function repairDatasourceFile(parsed, options = {}, changes = []) {
|
|
|
446
448
|
updated = repairMetadataSchemaFromAttributes(parsed, out) || updated;
|
|
447
449
|
}
|
|
448
450
|
|
|
451
|
+
updated = repairOpenapiSection(parsed, out) || updated;
|
|
452
|
+
|
|
449
453
|
if (options.expose) {
|
|
450
454
|
updated = repairExposeFromAttributes(parsed, out) || updated;
|
|
451
455
|
}
|
|
@@ -472,6 +476,7 @@ module.exports = {
|
|
|
472
476
|
repairExposeFromAttributes,
|
|
473
477
|
repairSyncSection,
|
|
474
478
|
repairTestPayload,
|
|
479
|
+
repairOpenapiSection,
|
|
475
480
|
sanitizeTestPayloadTopLevel,
|
|
476
481
|
repairDatasourceFile,
|
|
477
482
|
DEFAULT_SYNC,
|
|
@@ -40,7 +40,6 @@ function discoverIntegrationFiles(appPath) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
systemFiles.sort();
|
|
43
|
-
datasourceFiles.sort();
|
|
44
43
|
return { systemFiles, datasourceFiles };
|
|
45
44
|
}
|
|
46
45
|
|
|
@@ -48,8 +47,8 @@ function discoverIntegrationFiles(appPath) {
|
|
|
48
47
|
* Builds the effective datasource file list from application.yaml and discovered files.
|
|
49
48
|
* @param {string} appPath - Application directory path
|
|
50
49
|
* @param {string[]} discoveredDatasourceFiles - Filenames from discoverIntegrationFiles
|
|
51
|
-
* @param {string[]} [existingDataSources] - externalIntegration.dataSources from application
|
|
52
|
-
* @returns {string[]}
|
|
50
|
+
* @param {string[]} [existingDataSources] - externalIntegration.dataSources from application config
|
|
51
|
+
* @returns {string[]} Deduplicated list of datasource filenames (application order first, then remaining discovered files in directory order)
|
|
53
52
|
*/
|
|
54
53
|
function buildEffectiveDatasourceFiles(appPath, discoveredDatasourceFiles, existingDataSources) {
|
|
55
54
|
const existing = Array.isArray(existingDataSources) ? existingDataSources : [];
|
|
@@ -75,7 +74,6 @@ function buildEffectiveDatasourceFiles(appPath, discoveredDatasourceFiles, exist
|
|
|
75
74
|
seen.add(name);
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
|
-
result.sort();
|
|
79
77
|
return result;
|
|
80
78
|
}
|
|
81
79
|
|
package/lib/commands/repair.js
CHANGED
|
@@ -204,8 +204,7 @@ function alignSystemFileDataSources(appPath, systemParsed, datasourceFiles, syst
|
|
|
204
204
|
keys.push(deriveDatasourceKeyFromFileName(fileName, systemKey));
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
|
-
|
|
208
|
-
const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources].sort() : [];
|
|
207
|
+
const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources] : [];
|
|
209
208
|
if (JSON.stringify(prev) === JSON.stringify(keys)) return false;
|
|
210
209
|
systemParsed.dataSources = keys;
|
|
211
210
|
changes.push(`dataSources: [${prev.join(', ') || '(none)'}] → [${keys.join(', ')}] (keys from each datasource file's "key" or filename)`);
|
|
@@ -42,8 +42,8 @@ function deriveDatasourceKeyFromFileName(fileName, systemKey) {
|
|
|
42
42
|
* @param {Object} variables - Loaded application variables (externalIntegration.dataSources = filenames)
|
|
43
43
|
* @param {string} systemKey - System key from system file
|
|
44
44
|
* @param {Object} systemParsed - Parsed system config (may have dataSources array of keys)
|
|
45
|
-
* @param {string[]} datasourceFiles - Discovered datasource filenames
|
|
46
|
-
* @returns {string[]}
|
|
45
|
+
* @param {string[]} datasourceFiles - Discovered datasource filenames (application order when built via repair)
|
|
46
|
+
* @returns {string[]} Datasource keys in declaration order (system JSON or file list — never sort; FK targets must run first)
|
|
47
47
|
*/
|
|
48
48
|
function getDatasourceKeys(appPath, configPath, variables, systemKey, systemParsed, datasourceFiles) {
|
|
49
49
|
const fromSystem = Array.isArray(systemParsed.dataSources) && systemParsed.dataSources.length > 0
|
|
@@ -54,11 +54,11 @@ function getDatasourceKeys(appPath, configPath, variables, systemKey, systemPars
|
|
|
54
54
|
if (fromSystem) {
|
|
55
55
|
fromSystem.forEach(k => {
|
|
56
56
|
if (k && typeof k === 'string' && !seen.has(k)) {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const trimmed = k.trim();
|
|
58
|
+
keys.push(trimmed);
|
|
59
|
+
seen.add(trimmed);
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
|
-
keys.sort();
|
|
62
62
|
return keys;
|
|
63
63
|
}
|
|
64
64
|
for (const fileName of datasourceFiles) {
|
|
@@ -81,7 +81,6 @@ function getDatasourceKeys(appPath, configPath, variables, systemKey, systemPars
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
keys.sort();
|
|
85
84
|
return keys;
|
|
86
85
|
}
|
|
87
86
|
|
package/lib/commands/upload.js
CHANGED
|
@@ -17,7 +17,8 @@ const { getIntegrationPath } = require('../utils/paths');
|
|
|
17
17
|
const { pushCredentialSecrets } = require('../utils/credential-secrets-env');
|
|
18
18
|
const {
|
|
19
19
|
buildResolvedEnvMapForIntegration,
|
|
20
|
-
resolveConfigurationValues
|
|
20
|
+
resolveConfigurationValues,
|
|
21
|
+
collectResolvedVariableConfigurationNames
|
|
21
22
|
} = require('../utils/configuration-env-resolver');
|
|
22
23
|
const { validateExternalSystemComplete } = require('../validation/validate');
|
|
23
24
|
const { displayValidationResults } = require('../validation/validate-display');
|
|
@@ -70,9 +71,11 @@ function buildUploadPayload(manifest) {
|
|
|
70
71
|
/**
|
|
71
72
|
* Resolves dataplane URL and auth (same pattern as download).
|
|
72
73
|
* @param {string} systemKey - System key
|
|
74
|
+
* @param {{ silent?: boolean }} [opts] - silent: omit “Resolving dataplane URL…” and discovery success lines
|
|
73
75
|
* @returns {Promise<{ dataplaneUrl: string, authConfig: Object, environment: string }>}
|
|
74
76
|
*/
|
|
75
|
-
async function resolveDataplaneAndAuth(systemKey) {
|
|
77
|
+
async function resolveDataplaneAndAuth(systemKey, opts = {}) {
|
|
78
|
+
const silent = opts.silent === true;
|
|
76
79
|
const { resolveEnvironment } = require('../core/config');
|
|
77
80
|
const environment = await resolveEnvironment();
|
|
78
81
|
const controllerUrl = await resolveControllerUrl();
|
|
@@ -82,8 +85,10 @@ async function resolveDataplaneAndAuth(systemKey) {
|
|
|
82
85
|
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <systemKey>" first.');
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
if (!silent) {
|
|
89
|
+
logger.log(chalk.gray('Resolving dataplane URL...'));
|
|
90
|
+
}
|
|
91
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, { silent });
|
|
87
92
|
return { dataplaneUrl, authConfig, environment };
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -167,6 +172,15 @@ async function pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey,
|
|
|
167
172
|
} else {
|
|
168
173
|
logger.log(chalk.yellow('Secret push skipped'));
|
|
169
174
|
}
|
|
175
|
+
const resolvedParamNames = collectResolvedVariableConfigurationNames(payload);
|
|
176
|
+
if (resolvedParamNames.length > 0) {
|
|
177
|
+
const nameList = ` (${resolvedParamNames.join(', ')})`;
|
|
178
|
+
logger.log(
|
|
179
|
+
formatSuccessLine(
|
|
180
|
+
`Resolved ${resolvedParamNames.length} integration parameter(s) from .env for publish${nameList}.`
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
170
184
|
if (pushResult.warning) {
|
|
171
185
|
logger.log(chalk.yellow(`Warning: ${pushResult.warning}`));
|
|
172
186
|
}
|
|
@@ -73,10 +73,12 @@ function createDataplaneNotFoundError() {
|
|
|
73
73
|
* @returns {Promise<string>} Dataplane URL
|
|
74
74
|
* @throws {Error} If dataplane URL cannot be retrieved
|
|
75
75
|
*/
|
|
76
|
-
async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
76
|
+
async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig, silent) {
|
|
77
77
|
try {
|
|
78
78
|
const fallbackUrl = await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
|
|
79
|
-
|
|
79
|
+
if (!silent) {
|
|
80
|
+
logger.log(formatSuccessLine(`Dataplane URL: ${fallbackUrl}`));
|
|
81
|
+
}
|
|
80
82
|
return fallbackUrl;
|
|
81
83
|
} catch (fallbackError) {
|
|
82
84
|
if (isNotFoundError(fallbackError)) {
|
|
@@ -93,19 +95,25 @@ async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
|
93
95
|
* @param {string} controllerUrl - Controller URL
|
|
94
96
|
* @param {string} environment - Environment key
|
|
95
97
|
* @param {Object} authConfig - Authentication configuration
|
|
98
|
+
* @param {{ silent?: boolean }} [opts] - When silent, skip progress/success lines (e.g. cert sync right after a run).
|
|
96
99
|
* @returns {Promise<string>} Dataplane URL
|
|
97
100
|
* @throws {Error} If dataplane URL cannot be discovered
|
|
98
101
|
*/
|
|
99
|
-
async function discoverDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
100
|
-
|
|
102
|
+
async function discoverDataplaneUrl(controllerUrl, environment, authConfig, opts = {}) {
|
|
103
|
+
const silent = opts.silent === true;
|
|
104
|
+
if (!silent) {
|
|
105
|
+
logger.log(infoLine('🌐 Getting dataplane URL from controller...'));
|
|
106
|
+
}
|
|
101
107
|
try {
|
|
102
108
|
const dataplaneAppKey = await findDataplaneServiceAppKey(controllerUrl, environment, authConfig);
|
|
103
109
|
if (dataplaneAppKey) {
|
|
104
110
|
const dataplaneUrl = await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
|
|
105
|
-
|
|
111
|
+
if (!silent) {
|
|
112
|
+
logger.log(formatSuccessLine(`Dataplane URL: ${dataplaneUrl}`));
|
|
113
|
+
}
|
|
106
114
|
return dataplaneUrl;
|
|
107
115
|
}
|
|
108
|
-
return await tryFallbackDataplaneUrl(controllerUrl, environment, authConfig);
|
|
116
|
+
return await tryFallbackDataplaneUrl(controllerUrl, environment, authConfig, silent);
|
|
109
117
|
} catch (error) {
|
|
110
118
|
if (error.message.includes('Could not discover dataplane URL')) {
|
|
111
119
|
throw error;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TTY output for `aifabrix datasource validate` (cli-test-layout-chalk).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const {
|
|
12
|
+
sectionTitle,
|
|
13
|
+
headerKeyValue,
|
|
14
|
+
metadata,
|
|
15
|
+
formatSuccessLine,
|
|
16
|
+
formatBlockingError,
|
|
17
|
+
successGlyph,
|
|
18
|
+
failureGlyph
|
|
19
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
20
|
+
|
|
21
|
+
function logSubOk(whitePart, grayPart) {
|
|
22
|
+
if (grayPart) {
|
|
23
|
+
logger.log(` ${successGlyph()} ${chalk.white(whitePart)} ${metadata(grayPart)}`);
|
|
24
|
+
} else {
|
|
25
|
+
logger.log(` ${successGlyph()} ${chalk.white(whitePart)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function logHeaderAndIdentity(result, trimmed, showMapping) {
|
|
30
|
+
const { resolvedPath, summary } = result;
|
|
31
|
+
logger.log('');
|
|
32
|
+
logger.log(sectionTitle('Datasource validation'));
|
|
33
|
+
logger.log(metadata('Offline — JSON schema and integration wiring'));
|
|
34
|
+
logger.log('');
|
|
35
|
+
if (summary) {
|
|
36
|
+
logger.log(` ${headerKeyValue('Key:', summary.key)}`);
|
|
37
|
+
logger.log(` ${headerKeyValue('Resource type:', summary.resourceType)}`);
|
|
38
|
+
logger.log(` ${headerKeyValue('Entity type:', summary.entityType)}`);
|
|
39
|
+
logger.log(` ${headerKeyValue('File:', resolvedPath)}`);
|
|
40
|
+
if (showMapping && trimmed && String(trimmed) !== String(summary.key)) {
|
|
41
|
+
logger.log(` ${headerKeyValue('CLI input:', trimmed)}`);
|
|
42
|
+
}
|
|
43
|
+
logger.log('');
|
|
44
|
+
} else {
|
|
45
|
+
logger.log(` ${headerKeyValue('File:', resolvedPath)}`);
|
|
46
|
+
logger.log('');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function logInvalidBody(errors, warnings) {
|
|
51
|
+
logger.log(` ${formatBlockingError('Datasource file has errors')}`);
|
|
52
|
+
(errors || []).forEach(err => {
|
|
53
|
+
logger.log(` ${failureGlyph()} ${chalk.red(err)}`);
|
|
54
|
+
});
|
|
55
|
+
if (warnings && warnings.length > 0) {
|
|
56
|
+
logger.log('');
|
|
57
|
+
logger.log(sectionTitle('Warnings'));
|
|
58
|
+
warnings.forEach(w => {
|
|
59
|
+
logger.log(` ${chalk.yellow('⚠')} ${chalk.white(w)}`);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function logValidMappingsAndRelations(summary) {
|
|
65
|
+
const mc = summary.metadataSchemaPropertyCount;
|
|
66
|
+
const mcLabel = `${mc} propert${mc === 1 ? 'y' : 'ies'}`;
|
|
67
|
+
logSubOk('Metadata schema', mcLabel);
|
|
68
|
+
logSubOk('Field mappings', `${summary.fieldMappingAttributeCount} attribute(s)`);
|
|
69
|
+
logger.log('');
|
|
70
|
+
logSubOk(`Primary key: ${summary.primaryKey}`);
|
|
71
|
+
logSubOk(`Label key: ${summary.labelKey}`);
|
|
72
|
+
logger.log('');
|
|
73
|
+
if (summary.foreignKeys.length > 0) {
|
|
74
|
+
logSubOk('Foreign keys', `${summary.foreignKeys.length} reference(s)`);
|
|
75
|
+
summary.foreignKeys.forEach(fk => {
|
|
76
|
+
logger.log(metadata(` • ${fk.name} → ${fk.target} (${fk.fields})`));
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
logSubOk('Foreign keys', 'none');
|
|
80
|
+
}
|
|
81
|
+
logger.log('');
|
|
82
|
+
if (summary.dimensionKeys.length > 0) {
|
|
83
|
+
logSubOk('Dimensions (ABAC)', '');
|
|
84
|
+
summary.dimensionKeys.forEach(k => {
|
|
85
|
+
logger.log(metadata(` ${k} → ${summary.dimensions[k]}`));
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
logSubOk('Dimensions (ABAC)', 'none configured');
|
|
89
|
+
}
|
|
90
|
+
logger.log('');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function logOpenapiSection(summary) {
|
|
94
|
+
if (summary.hasOpenapi) {
|
|
95
|
+
logSubOk('OpenAPI', summary.openapiLine);
|
|
96
|
+
if (summary.capabilityKeys.length > 0) {
|
|
97
|
+
const preview = summary.capabilityKeys.slice(0, 12).join(', ');
|
|
98
|
+
const more = summary.capabilityKeys.length > 12 ? ', …' : '';
|
|
99
|
+
logger.log(metadata(` Operations: ${preview}${more}`));
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
logSubOk('OpenAPI', 'not configured');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function logValidInterfaceAndFooter(summary) {
|
|
107
|
+
if (summary.exposedProfileNames.length > 0) {
|
|
108
|
+
logSubOk('Exposed profiles', summary.exposedProfileNames.join(', '));
|
|
109
|
+
} else {
|
|
110
|
+
logSubOk('Exposed profiles', 'none');
|
|
111
|
+
}
|
|
112
|
+
logger.log('');
|
|
113
|
+
logOpenapiSection(summary);
|
|
114
|
+
logger.log('');
|
|
115
|
+
logSubOk('Test payload', summary.testPayloadLine);
|
|
116
|
+
logger.log('');
|
|
117
|
+
logSubOk('Synchronization', summary.syncLine);
|
|
118
|
+
logger.log('');
|
|
119
|
+
const caps = summary.capabilityKeys;
|
|
120
|
+
const capHint =
|
|
121
|
+
caps.length > 0 ? `${caps.slice(0, 8).join(', ')}${caps.length > 8 ? ', …' : ''}` : 'none (no OpenAPI operations)';
|
|
122
|
+
logSubOk('Capabilities', capHint);
|
|
123
|
+
logger.log('');
|
|
124
|
+
logger.log(` ${formatSuccessLine('Datasource file is valid.')}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function logWarningsOnly(warnings) {
|
|
128
|
+
if (!warnings || warnings.length === 0) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
logger.log('');
|
|
132
|
+
logger.log(sectionTitle('Warnings'));
|
|
133
|
+
warnings.forEach(w => {
|
|
134
|
+
logger.log(` ${chalk.yellow('⚠')} ${chalk.white(w)}`);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {object} result - validateDatasourceFile result (includes optional summary)
|
|
140
|
+
* @param {string} trimmed - CLI argument
|
|
141
|
+
* @param {boolean} showMapping - true when key resolved to a different path than arg
|
|
142
|
+
*/
|
|
143
|
+
function logDatasourceValidateOutcome(result, trimmed, showMapping) {
|
|
144
|
+
logHeaderAndIdentity(result, trimmed, showMapping);
|
|
145
|
+
const { valid, errors, warnings, summary } = result;
|
|
146
|
+
if (!valid) {
|
|
147
|
+
logInvalidBody(errors, warnings);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!summary) {
|
|
151
|
+
logger.log(` ${formatSuccessLine('Datasource file is valid.')}`);
|
|
152
|
+
logWarningsOnly(warnings);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
logValidMappingsAndRelations(summary);
|
|
156
|
+
logValidInterfaceAndFooter(summary);
|
|
157
|
+
logWarningsOnly(warnings);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
logDatasourceValidateOutcome
|
|
162
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Extract display summary from parsed external-datasource JSON (offline validate).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { flattenRootDimensionsForDisplay } = require('../validation/dimension-display-helpers');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Count JSON Schema property nodes (recursive `properties` bags).
|
|
13
|
+
* @param {object|null|undefined} metadataSchema
|
|
14
|
+
* @returns {number}
|
|
15
|
+
*/
|
|
16
|
+
function countMetadataSchemaProperties(metadataSchema) {
|
|
17
|
+
if (!metadataSchema || typeof metadataSchema !== 'object') {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
let n = 0;
|
|
21
|
+
function walk(node, depth) {
|
|
22
|
+
if (depth > 25 || !node || typeof node !== 'object') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const props = node.properties;
|
|
26
|
+
if (props && typeof props === 'object' && !Array.isArray(props)) {
|
|
27
|
+
for (const key of Object.keys(props)) {
|
|
28
|
+
n += 1;
|
|
29
|
+
walk(props[key], depth + 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(node.allOf)) {
|
|
33
|
+
for (const sub of node.allOf) {
|
|
34
|
+
walk(sub, depth + 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (node.items && typeof node.items === 'object') {
|
|
38
|
+
walk(node.items, depth + 1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
walk(metadataSchema, 0);
|
|
42
|
+
return n;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {object} fk
|
|
47
|
+
* @returns {{ name: string, target: string, fields: string }}
|
|
48
|
+
*/
|
|
49
|
+
function normalizeForeignKeyRow(fk) {
|
|
50
|
+
return {
|
|
51
|
+
name: fk.name,
|
|
52
|
+
target: fk.targetDatasource,
|
|
53
|
+
fields: Array.isArray(fk.fields) ? fk.fields.join(', ') : ''
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {object} parsed
|
|
59
|
+
* @returns {{ dimensionKeys: string[], dimensions: Record<string, string> }}
|
|
60
|
+
*/
|
|
61
|
+
function summarizeDimensions(parsed) {
|
|
62
|
+
const dimFlat = flattenRootDimensionsForDisplay(parsed.dimensions);
|
|
63
|
+
const abacDims =
|
|
64
|
+
parsed.abac && parsed.abac.dimensions && typeof parsed.abac.dimensions === 'object' ? parsed.abac.dimensions : {};
|
|
65
|
+
const allDims = { ...dimFlat, ...abacDims };
|
|
66
|
+
return { dimensionKeys: Object.keys(allDims), dimensions: allDims };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {object|null} sync
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
function summarizeSyncLine(sync) {
|
|
74
|
+
if (!sync || typeof sync !== 'object') {
|
|
75
|
+
return 'Not configured';
|
|
76
|
+
}
|
|
77
|
+
const parts = [];
|
|
78
|
+
if (sync.mode) {
|
|
79
|
+
parts.push(String(sync.mode));
|
|
80
|
+
}
|
|
81
|
+
if (sync.batchSize !== undefined && sync.batchSize !== null) {
|
|
82
|
+
parts.push(`batch size ${sync.batchSize}`);
|
|
83
|
+
}
|
|
84
|
+
if (sync.schedule) {
|
|
85
|
+
parts.push(`schedule ${JSON.stringify(sync.schedule)}`);
|
|
86
|
+
}
|
|
87
|
+
return parts.length ? parts.join(', ') : 'Present';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {object} parsed
|
|
92
|
+
* @returns {{ openapiLine: string, hasOpenapi: boolean, capabilityKeys: string[] }}
|
|
93
|
+
*/
|
|
94
|
+
function summarizeOpenapi(parsed) {
|
|
95
|
+
const openapi = parsed.openapi && typeof parsed.openapi === 'object' ? parsed.openapi : {};
|
|
96
|
+
const operations = openapi.operations && typeof openapi.operations === 'object' ? openapi.operations : {};
|
|
97
|
+
const capabilityKeys = Object.keys(operations);
|
|
98
|
+
let openapiLine = 'Not configured';
|
|
99
|
+
if (openapi.enabled === true) {
|
|
100
|
+
openapiLine = openapi.autoRbac ? 'enabled, auto RBAC' : 'enabled';
|
|
101
|
+
} else if (parsed.openapi && Object.prototype.hasOwnProperty.call(parsed.openapi, 'enabled')) {
|
|
102
|
+
openapiLine = 'disabled';
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
openapiLine,
|
|
106
|
+
hasOpenapi: Object.keys(openapi).length > 0,
|
|
107
|
+
capabilityKeys
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {object} parsed
|
|
113
|
+
* @returns {string}
|
|
114
|
+
*/
|
|
115
|
+
function summarizeTestPayloadLine(parsed) {
|
|
116
|
+
const tp = parsed.testPayload;
|
|
117
|
+
const hasTestPayload = !!tp && typeof tp === 'object';
|
|
118
|
+
if (!hasTestPayload) {
|
|
119
|
+
return 'Not configured';
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(tp.scenarios)) {
|
|
122
|
+
return `${tp.scenarios.length} scenario(s)`;
|
|
123
|
+
}
|
|
124
|
+
return 'Present';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {object} parsed
|
|
129
|
+
* @returns {string[]}
|
|
130
|
+
*/
|
|
131
|
+
function summarizeExposedProfileNames(parsed) {
|
|
132
|
+
if (parsed.exposed && parsed.exposed.profiles && typeof parsed.exposed.profiles === 'object') {
|
|
133
|
+
return Object.keys(parsed.exposed.profiles);
|
|
134
|
+
}
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {string|undefined} v
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
function dash(v) {
|
|
143
|
+
return v || '—';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {object} parsed
|
|
148
|
+
* @returns {object}
|
|
149
|
+
*/
|
|
150
|
+
function assembleSummary(parsed) {
|
|
151
|
+
const attrs = parsed.fieldMappings && parsed.fieldMappings.attributes;
|
|
152
|
+
const attrCount = attrs && typeof attrs === 'object' ? Object.keys(attrs).length : 0;
|
|
153
|
+
const pk = Array.isArray(parsed.primaryKey) ? parsed.primaryKey.join(', ') : String(parsed.primaryKey || '');
|
|
154
|
+
const lk = Array.isArray(parsed.labelKey) ? parsed.labelKey.join(', ') : String(parsed.labelKey || '');
|
|
155
|
+
const fks = Array.isArray(parsed.foreignKeys) ? parsed.foreignKeys.map(normalizeForeignKeyRow) : [];
|
|
156
|
+
const { dimensionKeys, dimensions } = summarizeDimensions(parsed);
|
|
157
|
+
const oa = summarizeOpenapi(parsed);
|
|
158
|
+
const sync = parsed.sync && typeof parsed.sync === 'object' ? parsed.sync : null;
|
|
159
|
+
return {
|
|
160
|
+
key: dash(parsed.key),
|
|
161
|
+
resourceType: dash(parsed.resourceType),
|
|
162
|
+
entityType: dash(parsed.entityType),
|
|
163
|
+
metadataSchemaPropertyCount: countMetadataSchemaProperties(parsed.metadataSchema),
|
|
164
|
+
fieldMappingAttributeCount: attrCount,
|
|
165
|
+
primaryKey: pk || '—',
|
|
166
|
+
labelKey: lk || '—',
|
|
167
|
+
foreignKeys: fks,
|
|
168
|
+
dimensionKeys,
|
|
169
|
+
dimensions,
|
|
170
|
+
exposedProfileNames: summarizeExposedProfileNames(parsed),
|
|
171
|
+
openapiLine: oa.openapiLine,
|
|
172
|
+
hasOpenapi: oa.hasOpenapi,
|
|
173
|
+
capabilityKeys: oa.capabilityKeys,
|
|
174
|
+
testPayloadLine: summarizeTestPayloadLine(parsed),
|
|
175
|
+
hasTestPayload: !!parsed.testPayload && typeof parsed.testPayload === 'object',
|
|
176
|
+
syncLine: summarizeSyncLine(sync)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {object} parsed - Parsed datasource JSON
|
|
182
|
+
* @returns {object|null}
|
|
183
|
+
*/
|
|
184
|
+
function buildDatasourceValidateSummary(parsed) {
|
|
185
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return assembleSummary(parsed);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
buildDatasourceValidateSummary,
|
|
193
|
+
countMetadataSchemaProperties
|
|
194
|
+
};
|