@aifabrix/builder 2.44.0 → 2.44.2
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 +75 -0
- package/.cursor/rules/project-rules.mdc +8 -0
- package/.npmrc.token +1 -0
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/jest.projects.js +15 -2
- package/lib/api/certificates.api.js +62 -0
- package/lib/api/index.js +11 -2
- package/lib/api/types/certificates.types.js +48 -0
- package/lib/api/validation-run.api.js +16 -4
- package/lib/api/validation-runner.js +13 -3
- package/lib/app/certification-show-enrich.js +129 -0
- package/lib/app/certification-verify-rows.js +60 -0
- package/lib/app/show-display.js +43 -0
- package/lib/app/show.js +92 -8
- package/lib/certification/cli-cert-sync-skip.js +21 -0
- package/lib/certification/merge-certification-from-artifact.js +185 -0
- package/lib/certification/post-unified-cert-sync.js +33 -0
- package/lib/certification/sync-after-external-command.js +52 -0
- package/lib/certification/sync-system-certification.js +197 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/cli/setup-app.test-commands.js +24 -8
- package/lib/cli/setup-external-system.js +22 -1
- package/lib/cli/setup-secrets.js +34 -13
- package/lib/cli/setup-utility.js +18 -2
- package/lib/commands/app.js +10 -1
- package/lib/commands/datasource-unified-test-cli.js +50 -117
- package/lib/commands/datasource-unified-test-cli.options.js +44 -2
- package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +106 -0
- package/lib/commands/datasource-validation-cli.js +15 -1
- package/lib/commands/datasource.js +25 -2
- package/lib/commands/upload.js +17 -6
- package/lib/core/secrets.js +47 -13
- package/lib/datasource/log-viewer.js +135 -20
- package/lib/datasource/test-e2e.js +35 -17
- package/lib/datasource/unified-validation-run-body.js +3 -0
- package/lib/datasource/unified-validation-run.js +2 -1
- package/lib/external-system/deploy.js +53 -18
- package/lib/infrastructure/compose.js +12 -3
- package/lib/infrastructure/helpers-docker-check.js +67 -0
- package/lib/infrastructure/helpers.js +47 -58
- package/lib/infrastructure/index.js +3 -1
- package/lib/infrastructure/services.js +4 -56
- package/lib/schema/external-system.schema.json +25 -3
- package/lib/schema/type/document-storage.json +15 -2
- package/lib/utils/api.js +28 -3
- package/lib/utils/app-service-env-from-builder.js +47 -6
- package/lib/utils/config-paths.js +4 -0
- package/lib/utils/configuration-env-resolver.js +11 -8
- package/lib/utils/credential-secrets-env.js +5 -5
- package/lib/utils/datasource-test-run-certificate-tty.js +82 -0
- package/lib/utils/datasource-test-run-display.js +19 -2
- package/lib/utils/datasource-test-run-exit.js +25 -0
- package/lib/utils/external-system-display.js +8 -0
- package/lib/utils/external-system-system-test-tty-overview.js +120 -0
- package/lib/utils/external-system-system-test-tty.js +417 -0
- package/lib/utils/paths.js +14 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +50 -20
- package/lib/utils/urls-local-registry.js +36 -12
- package/lib/utils/validation-run-poll.js +28 -5
- package/lib/utils/validation-run-post-retry.js +20 -8
- package/lib/utils/validation-run-request.js +18 -0
- package/lib/validation/validate-external-cert-sync.js +23 -0
- package/lib/validation/validate.js +4 -1
- package/package.json +4 -3
- package/scripts/install-local.js +4 -1
- package/scripts/pnpm-global-remove.js +48 -0
- package/templates/applications/dataplane/env.template +4 -0
- package/templates/infra/compose.yaml.hbs +15 -14
- package/templates/infra/servers.json.hbs +3 -1
|
@@ -12,8 +12,21 @@
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const yaml = require('js-yaml');
|
|
14
14
|
const fsRealSync = require('../internal/fs-real-sync');
|
|
15
|
+
const pathsUtil = require('./paths');
|
|
15
16
|
const { localHostPort } = require('./declarative-url-ports');
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} p
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
function isExistingDirSync(p) {
|
|
23
|
+
try {
|
|
24
|
+
return Boolean(p && fsRealSync.existsSync(p) && fsRealSync.statSync(p).isDirectory());
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
/**
|
|
18
31
|
* Maps application.yaml `app.key` to env var prefix (MISO_HOST, DATAPLANE_PORT, …).
|
|
19
32
|
* Generic keys not listed are skipped (no guessed PREFIX).
|
|
@@ -110,12 +123,11 @@ function mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName) {
|
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
/**
|
|
113
|
-
* @param {string}
|
|
126
|
+
* @param {string} builderDir
|
|
114
127
|
* @returns {Array<{ doc: object, folderName: string }>}
|
|
115
128
|
*/
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
if (!fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
|
|
129
|
+
function listBuilderApplicationDocsInDir(builderDir) {
|
|
130
|
+
if (!isExistingDirSync(builderDir)) {
|
|
119
131
|
return [];
|
|
120
132
|
}
|
|
121
133
|
const out = [];
|
|
@@ -140,6 +152,33 @@ function listBuilderApplicationDocs(projectRoot) {
|
|
|
140
152
|
return out;
|
|
141
153
|
}
|
|
142
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Prefer projectRoot/builder when present (unit tests, in-repo runs). When missing but projectRoot
|
|
157
|
+
* is the detected CLI package root (global npm install omits builder/), fall back to
|
|
158
|
+
* {@link pathsUtil.getBuilderRoot}.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} projectRoot
|
|
161
|
+
* @returns {string[]}
|
|
162
|
+
*/
|
|
163
|
+
function collectBuilderScanDirs(projectRoot) {
|
|
164
|
+
const legacy = path.join(projectRoot, 'builder');
|
|
165
|
+
if (isExistingDirSync(legacy)) {
|
|
166
|
+
return [legacy];
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const detected = pathsUtil.getProjectRoot();
|
|
170
|
+
if (detected && path.resolve(projectRoot) === path.resolve(detected)) {
|
|
171
|
+
const br = pathsUtil.getBuilderRoot();
|
|
172
|
+
if (isExistingDirSync(br)) {
|
|
173
|
+
return [br];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
/* ignore */
|
|
178
|
+
}
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
143
182
|
/**
|
|
144
183
|
* @param {string|null|undefined} projectRoot
|
|
145
184
|
* @returns {{ docker: Record<string, unknown>, local: Record<string, unknown> }}
|
|
@@ -150,8 +189,10 @@ function buildAppServiceEnvOverlay(projectRoot) {
|
|
|
150
189
|
if (!projectRoot || !fsRealSync.existsSync(projectRoot)) {
|
|
151
190
|
return { docker: overlayDocker, local: overlayLocal };
|
|
152
191
|
}
|
|
153
|
-
for (const
|
|
154
|
-
|
|
192
|
+
for (const builderDir of collectBuilderScanDirs(projectRoot)) {
|
|
193
|
+
for (const { doc, folderName } of listBuilderApplicationDocsInDir(builderDir)) {
|
|
194
|
+
mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName);
|
|
195
|
+
}
|
|
155
196
|
}
|
|
156
197
|
return { docker: overlayDocker, local: overlayLocal };
|
|
157
198
|
}
|
|
@@ -216,6 +216,10 @@ function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
|
|
|
216
216
|
if (fromProject) {
|
|
217
217
|
return fromProject;
|
|
218
218
|
}
|
|
219
|
+
const fromEffective = tryDir(pathsMod.getBuilderRoot());
|
|
220
|
+
if (fromEffective) {
|
|
221
|
+
return fromEffective;
|
|
222
|
+
}
|
|
219
223
|
const work = pathsMod.getAifabrixWork();
|
|
220
224
|
return tryDir(work);
|
|
221
225
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const { getIntegrationPath } = require('./paths');
|
|
13
|
-
const { parseEnvToMap
|
|
13
|
+
const { parseEnvToMap } = require('./credential-secrets-env');
|
|
14
14
|
const { loadSecrets, resolveKvReferences } = require('../core/secrets');
|
|
15
15
|
const { loadEnvTemplate } = require('./secrets-helpers');
|
|
16
16
|
const { getActualSecretsPath } = require('./secrets-path');
|
|
@@ -81,13 +81,13 @@ function substituteVarPlaceholders(value, envMap, systemKey) {
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Resolves configuration array values in place by location: variable → {{VAR}} from envMap;
|
|
84
|
-
* keyvault → kv://
|
|
84
|
+
* keyvault → leaves kv:// as-is (secret value pushed separately by CLI). Does not log or expose secret values.
|
|
85
85
|
*
|
|
86
86
|
* @param {Array<{ name?: string, value?: string, location?: string }>} configArray - Configuration array (mutated)
|
|
87
87
|
* @param {Object.<string, string>} envMap - Resolved env map from buildResolvedEnvMapForIntegration
|
|
88
|
-
* @param {Object} secrets - Loaded secrets for
|
|
88
|
+
* @param {Object} secrets - Loaded secrets (unused for keyvault config values; kept for backward compatibility)
|
|
89
89
|
* @param {string} [systemKey] - System key for error messages
|
|
90
|
-
* @throws {Error} If variable env is missing or keyvault
|
|
90
|
+
* @throws {Error} If variable env is missing or keyvault value is not a kv:// reference (message never contains secret values)
|
|
91
91
|
*/
|
|
92
92
|
function resolveConfigurationValues(configArray, envMap, secrets, systemKey) {
|
|
93
93
|
if (!Array.isArray(configArray)) return;
|
|
@@ -101,11 +101,14 @@ function resolveConfigurationValues(configArray, envMap, secrets, systemKey) {
|
|
|
101
101
|
}
|
|
102
102
|
item.value = substituteVarPlaceholders(item.value, envMap, systemKey);
|
|
103
103
|
} else if (location === 'keyvault') {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if (!item.value.trim().startsWith('kv://')) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Configuration entry '${item.name || 'unknown'}' has location 'keyvault' but value is not kv://. ` +
|
|
107
|
+
`Set value to a kv:// reference and push the secret with KV_* env vars.${hint}`
|
|
108
|
+
);
|
|
107
109
|
}
|
|
108
|
-
|
|
110
|
+
// Intentionally do not resolve kv:// here. Upload keeps kv:// references in config and
|
|
111
|
+
// pushes secret values separately via the credential secret API.
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
}
|
|
@@ -132,7 +132,7 @@ function kvEnvKeyToPath(envKey, systemKey) {
|
|
|
132
132
|
* @param {Object.<string, string>} envMap - Key-value map from .env
|
|
133
133
|
* @returns {Array<{ key: string, value: string }>} Items (key = kv://..., value = raw)
|
|
134
134
|
*/
|
|
135
|
-
function collectKvEnvVarsAsSecretItems(envMap) {
|
|
135
|
+
function collectKvEnvVarsAsSecretItems(envMap, systemKey) {
|
|
136
136
|
if (!envMap || typeof envMap !== 'object') {
|
|
137
137
|
return [];
|
|
138
138
|
}
|
|
@@ -144,7 +144,7 @@ function collectKvEnvVarsAsSecretItems(envMap) {
|
|
|
144
144
|
if (value.startsWith('kv://') && isValidKvPath(value)) {
|
|
145
145
|
kvPath = value;
|
|
146
146
|
}
|
|
147
|
-
if (!kvPath) kvPath = kvEnvKeyToPath(envKey);
|
|
147
|
+
if (!kvPath) kvPath = kvEnvKeyToPath(envKey, systemKey) || kvEnvKeyToPath(envKey);
|
|
148
148
|
if (!kvPath) continue;
|
|
149
149
|
items.push({ key: kvPath, value });
|
|
150
150
|
}
|
|
@@ -223,12 +223,12 @@ function isValidKvPath(key) {
|
|
|
223
223
|
* @param {Object} secrets - Loaded secrets
|
|
224
224
|
* @param {Map<string, string>} itemsByKey - Mutable map to add items to
|
|
225
225
|
*/
|
|
226
|
-
function buildItemsFromEnv(envFilePath, secrets, itemsByKey) {
|
|
226
|
+
function buildItemsFromEnv(envFilePath, secrets, itemsByKey, systemKey) {
|
|
227
227
|
if (!envFilePath || typeof envFilePath !== 'string' || !fs.existsSync(envFilePath)) return;
|
|
228
228
|
try {
|
|
229
229
|
const content = fs.readFileSync(envFilePath, 'utf8');
|
|
230
230
|
const envMap = parseEnvToMap(content);
|
|
231
|
-
const fromEnv = collectKvEnvVarsAsSecretItems(envMap);
|
|
231
|
+
const fromEnv = collectKvEnvVarsAsSecretItems(envMap, systemKey);
|
|
232
232
|
for (const { key, value } of fromEnv) {
|
|
233
233
|
const resolved = resolveKvValue(secrets, value);
|
|
234
234
|
// Skip placeholder: value that equals the kv path (e.g. from env.template) must not be pushed as the secret
|
|
@@ -320,7 +320,7 @@ async function pushCredentialSecrets(dataplaneUrl, authConfig, options = {}) {
|
|
|
320
320
|
secrets = {};
|
|
321
321
|
}
|
|
322
322
|
const itemsByKey = new Map();
|
|
323
|
-
buildItemsFromEnv(envFilePath, secrets, itemsByKey);
|
|
323
|
+
buildItemsFromEnv(envFilePath, secrets, itemsByKey, appName);
|
|
324
324
|
buildItemsFromPayload(payload, secrets, itemsByKey);
|
|
325
325
|
|
|
326
326
|
const items = Array.from(itemsByKey.entries())
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Certificate / certification tier lines for DatasourceTestRun TTY output.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const { sectionTitle } = require('./cli-test-layout-chalk');
|
|
11
|
+
|
|
12
|
+
function trimCertificateField(v) {
|
|
13
|
+
if (v === undefined || v === null) return '';
|
|
14
|
+
return String(v).trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function certificateEnvelopeGlyph(statusRaw) {
|
|
18
|
+
if (statusRaw === 'passed') return '✔';
|
|
19
|
+
if (statusRaw === 'not_passed') return '✖';
|
|
20
|
+
return statusRaw ? '⚠' : ' ';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function certificateTTYHasContent(levelRaw, stRaw, summaryRaw, blockerCount) {
|
|
24
|
+
return Boolean(levelRaw || stRaw || summaryRaw || blockerCount > 0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string[]} lines
|
|
29
|
+
* @param {string} stRaw
|
|
30
|
+
* @param {string} levelRaw
|
|
31
|
+
* @param {string} summaryRaw
|
|
32
|
+
*/
|
|
33
|
+
function appendCertificateStatusAndSummaryLines(lines, stRaw, levelRaw, summaryRaw) {
|
|
34
|
+
if (stRaw || levelRaw) {
|
|
35
|
+
const cg = certificateEnvelopeGlyph(stRaw);
|
|
36
|
+
const tier = levelRaw ? ` — tier ${levelRaw}` : '';
|
|
37
|
+
lines.push(chalk.white(` ${cg} ${stRaw || 'unknown'}${tier}`));
|
|
38
|
+
}
|
|
39
|
+
if (summaryRaw) {
|
|
40
|
+
lines.push(chalk.gray(` ${summaryRaw}`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string[]} lines
|
|
46
|
+
* @param {Object[]} blockers
|
|
47
|
+
* @param {number} maxVisible
|
|
48
|
+
*/
|
|
49
|
+
function appendCertificateBlockerLines(lines, blockers, maxVisible) {
|
|
50
|
+
const cap = Math.min(maxVisible, blockers.length);
|
|
51
|
+
for (let i = 0; i < cap; i += 1) {
|
|
52
|
+
const b = blockers[i];
|
|
53
|
+
const msg = b && b.message ? String(b.message) : '';
|
|
54
|
+
if (msg) lines.push(chalk.yellow(` • ${msg}`));
|
|
55
|
+
}
|
|
56
|
+
if (blockers.length > cap) {
|
|
57
|
+
lines.push(chalk.gray(` … and ${blockers.length - cap} more`));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Certification / certificate tier (integration engine or E2E envelope after active cert attach).
|
|
63
|
+
* @param {string[]} lines
|
|
64
|
+
* @param {Object} envelope
|
|
65
|
+
*/
|
|
66
|
+
function appendCertificateTTY(lines, envelope) {
|
|
67
|
+
const cert = envelope && envelope.certificate;
|
|
68
|
+
if (!cert || typeof cert !== 'object') return;
|
|
69
|
+
const levelRaw = trimCertificateField(cert.level);
|
|
70
|
+
const stRaw = trimCertificateField(cert.status);
|
|
71
|
+
const summaryRaw = trimCertificateField(cert.summary);
|
|
72
|
+
const blockers = Array.isArray(cert.blockers) ? cert.blockers : [];
|
|
73
|
+
if (!certificateTTYHasContent(levelRaw, stRaw, summaryRaw, blockers.length)) return;
|
|
74
|
+
lines.push('');
|
|
75
|
+
lines.push(sectionTitle('Certification:'));
|
|
76
|
+
appendCertificateStatusAndSummaryLines(lines, stRaw, levelRaw, summaryRaw);
|
|
77
|
+
appendCertificateBlockerLines(lines, blockers, 5);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
appendCertificateTTY
|
|
82
|
+
};
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
const { sectionTitle, headerKeyValue, colorAggregateGlyph, successGlyph, failureGlyph } = require('./cli-test-layout-chalk');
|
|
9
|
+
const { appendCertificateTTY } = require('./datasource-test-run-certificate-tty');
|
|
9
10
|
|
|
10
11
|
const SEP = '────────────────────────────────';
|
|
11
12
|
|
|
@@ -235,6 +236,16 @@ function appendDebugPayloadRefLines(lines, dbg, maxRefChars) {
|
|
|
235
236
|
*/
|
|
236
237
|
function appendDebugMetaLines(lines, dbg, maxRefChars) {
|
|
237
238
|
let added = false;
|
|
239
|
+
function formatSecondsText(s) {
|
|
240
|
+
const txt = String(s);
|
|
241
|
+
// UI-only: reduce noisy float seconds to 3 decimals, e.g. "1.1713749s" -> "1.171s".
|
|
242
|
+
// Applies only to number tokens immediately followed by "s".
|
|
243
|
+
return txt.replace(/(\d+\.\d+)(?=s\b)/g, m => {
|
|
244
|
+
const n = Number(m);
|
|
245
|
+
if (!Number.isFinite(n)) return m;
|
|
246
|
+
return n.toFixed(3);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
238
249
|
if (dbg.mode) {
|
|
239
250
|
lines.push(
|
|
240
251
|
`${chalk.blue.bold('debug.mode:')} ${chalk.white(String(dbg.mode))}`
|
|
@@ -244,7 +255,7 @@ function appendDebugMetaLines(lines, dbg, maxRefChars) {
|
|
|
244
255
|
if (dbg.executionSummary) {
|
|
245
256
|
lines.push(
|
|
246
257
|
`${chalk.blue.bold('debug.executionSummary:')} ${chalk.white(
|
|
247
|
-
truncateRefLine(
|
|
258
|
+
truncateRefLine(formatSecondsText(dbg.executionSummary), maxRefChars)
|
|
248
259
|
)}`
|
|
249
260
|
);
|
|
250
261
|
added = true;
|
|
@@ -326,9 +337,14 @@ function appendIntegrationStepLines(lines, envelope) {
|
|
|
326
337
|
|
|
327
338
|
function pickExecutiveVerdictLine(envelope) {
|
|
328
339
|
const dev = envelope.developer;
|
|
340
|
+
const cert = envelope.certificate;
|
|
341
|
+
if (envelope.runType === 'e2e' && cert && typeof cert === 'object') {
|
|
342
|
+
const cs = cert.summary;
|
|
343
|
+
if (cs && String(cs).trim()) return String(cs).trim();
|
|
344
|
+
}
|
|
329
345
|
return (
|
|
330
346
|
(dev && dev.executiveSummary) ||
|
|
331
|
-
(
|
|
347
|
+
(cert && cert.summary) ||
|
|
332
348
|
(envelope.validation && envelope.validation.summary) ||
|
|
333
349
|
(envelope.integration && envelope.integration.summary) ||
|
|
334
350
|
`Run finished with status ${envelope.status}.`
|
|
@@ -416,6 +432,7 @@ function formatDatasourceTestRunTTY(envelope, options = {}) {
|
|
|
416
432
|
lines.push('');
|
|
417
433
|
lines.push(sectionTitle('Verdict:'));
|
|
418
434
|
lines.push(chalk.white(pickExecutiveVerdictLine(envelope)));
|
|
435
|
+
appendCertificateTTY(lines, envelope);
|
|
419
436
|
lines.push('');
|
|
420
437
|
lines.push(chalk.gray(SEP));
|
|
421
438
|
if (appendReferenceLayoutLines(lines, envelope, { maxRefChars: 160 })) {
|
|
@@ -52,7 +52,32 @@ function exitCodeForPollTimeout(lastBody) {
|
|
|
52
52
|
return 3;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
const {
|
|
56
|
+
deriveSystemStatus,
|
|
57
|
+
systemCertStatus
|
|
58
|
+
} = require('./external-system-system-test-tty');
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* System-level exit code from per-datasource result rows (same matrix as §3.1 on synthetic rollup).
|
|
62
|
+
* @param {Array<{ skipped?: boolean, success?: boolean, datasourceTestRun?: Object|null }>} rows
|
|
63
|
+
* @param {Object} [opts]
|
|
64
|
+
* @param {boolean} [opts.warningsAsErrors]
|
|
65
|
+
* @param {boolean} [opts.requireCert]
|
|
66
|
+
* @returns {number}
|
|
67
|
+
*/
|
|
68
|
+
function computeSystemExitCodeFromDatasourceRows(rows, opts = {}) {
|
|
69
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
70
|
+
const status = deriveSystemStatus(list);
|
|
71
|
+
const certAgg = systemCertStatus(list);
|
|
72
|
+
const body = {
|
|
73
|
+
status,
|
|
74
|
+
certificate: certAgg ? { status: certAgg === 'passed' ? 'passed' : 'not_passed' } : undefined
|
|
75
|
+
};
|
|
76
|
+
return computeExitCodeFromDatasourceTestRun(body, opts);
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
module.exports = {
|
|
56
80
|
computeExitCodeFromDatasourceTestRun,
|
|
81
|
+
computeSystemExitCodeFromDatasourceRows,
|
|
57
82
|
exitCodeForPollTimeout
|
|
58
83
|
};
|
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
successGlyph,
|
|
25
25
|
failureGlyph
|
|
26
26
|
} = require('./cli-test-layout-chalk');
|
|
27
|
+
const { displaySystemAggregateDatasourceTestRuns } = require('./external-system-system-test-tty');
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Displays formatted test results (local external `aifabrix test` — structured report layout).
|
|
@@ -269,6 +270,13 @@ function displayServerDatasourceTestRunResults(results, verbose, opts = {}) {
|
|
|
269
270
|
return;
|
|
270
271
|
}
|
|
271
272
|
|
|
273
|
+
// Plan §17: system-level overview for multi-datasource results (no full §16 dump per datasource by default).
|
|
274
|
+
// Keep legacy per-datasource full envelope available only via datasource commands.
|
|
275
|
+
if (integrationResultsHaveEnvelope(results)) {
|
|
276
|
+
displaySystemAggregateDatasourceTestRuns(results, { runType, verbose: Boolean(verbose) });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
272
280
|
const agg = deriveAggregateServerStatus(results);
|
|
273
281
|
logServerDatasourceTestRunHeader(results, runType, agg);
|
|
274
282
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Plan §17 blocks: capabilities overview and integration health (split for file-size limits).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object|null|undefined} env
|
|
9
|
+
* @returns {'ok'|'warn'|'fail'}
|
|
10
|
+
*/
|
|
11
|
+
function integrationRollupStatus(env) {
|
|
12
|
+
const integ = env && env.integration;
|
|
13
|
+
if (!integ || typeof integ !== 'object') return 'ok';
|
|
14
|
+
if (typeof integ.status === 'string') {
|
|
15
|
+
const s = integ.status.toLowerCase();
|
|
16
|
+
if (s === 'fail' || s === 'failed' || s === 'error') return 'fail';
|
|
17
|
+
if (s === 'warn' || s === 'warning') return 'warn';
|
|
18
|
+
if (s === 'ok' || s === 'passed' || s === 'success') return 'ok';
|
|
19
|
+
}
|
|
20
|
+
const steps = Array.isArray(integ.stepResults) ? integ.stepResults : [];
|
|
21
|
+
if (steps.some(s => s && (s.success === false || s.error))) return 'fail';
|
|
22
|
+
return 'ok';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildCapabilityBlocks(rows, statusGlyph) {
|
|
26
|
+
const blocks = [];
|
|
27
|
+
for (const r of rows) {
|
|
28
|
+
if (r && r.skipped) continue;
|
|
29
|
+
const env = r && r.datasourceTestRun;
|
|
30
|
+
if (!env || !Array.isArray(env.capabilities) || env.capabilities.length === 0) continue;
|
|
31
|
+
const dkey = env.datasourceKey || r.key || 'datasource';
|
|
32
|
+
const caps = [...env.capabilities]
|
|
33
|
+
.filter(c => c && c.key)
|
|
34
|
+
.sort((a, b) => String(a.key).localeCompare(String(b.key)))
|
|
35
|
+
.slice(0, 4);
|
|
36
|
+
const parts = caps.map(c => `${statusGlyph(c.status)} ${c.key}`);
|
|
37
|
+
if (parts.length === 0) continue;
|
|
38
|
+
blocks.push({ dkey: String(dkey), line: parts.join(' ') });
|
|
39
|
+
}
|
|
40
|
+
return blocks;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function emitSectionHeader(io, title) {
|
|
44
|
+
const { log, metaGray, sectionTitle, SEP } = io;
|
|
45
|
+
log('');
|
|
46
|
+
log(metaGray(SEP));
|
|
47
|
+
log('');
|
|
48
|
+
log(sectionTitle(title));
|
|
49
|
+
log('');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Array} rows
|
|
54
|
+
* @param {{ log: Function, chalk: object, metaGray: Function, sectionTitle: Function, statusGlyph: Function, SEP: string }} io
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
function logCapabilitiesOverview(rows, io) {
|
|
58
|
+
const blocks = buildCapabilityBlocks(rows, io.statusGlyph);
|
|
59
|
+
if (blocks.length === 0) return false;
|
|
60
|
+
emitSectionHeader(io, 'Capabilities overview:');
|
|
61
|
+
for (const b of blocks) {
|
|
62
|
+
io.log(io.chalk.white(`${b.dkey}:`));
|
|
63
|
+
io.log(io.chalk.white(` ${b.line}`));
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function badIntegrationSteps(integ) {
|
|
69
|
+
if (!integ || !Array.isArray(integ.stepResults)) return [];
|
|
70
|
+
return integ.stepResults.filter(s => s && (s.success === false || s.error)).slice(0, 2);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildIntegrationBlocks(rows) {
|
|
74
|
+
const blocks = [];
|
|
75
|
+
for (const r of rows) {
|
|
76
|
+
if (r && r.skipped) continue;
|
|
77
|
+
const env = r && r.datasourceTestRun;
|
|
78
|
+
if (!env) continue;
|
|
79
|
+
const dkey = env.datasourceKey || r.key || 'datasource';
|
|
80
|
+
const st = integrationRollupStatus(env);
|
|
81
|
+
const integ = env.integration && typeof env.integration === 'object' ? env.integration : null;
|
|
82
|
+
blocks.push({ dkey: String(dkey), st, badSteps: badIntegrationSteps(integ) });
|
|
83
|
+
}
|
|
84
|
+
return blocks;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function emitFailedSteps(block, io) {
|
|
88
|
+
if (block.st !== 'fail' && block.st !== 'warn') return;
|
|
89
|
+
const { log, chalk, statusGlyph } = io;
|
|
90
|
+
for (const stp of block.badSteps) {
|
|
91
|
+
const nm = stp.name || stp.step || 'step';
|
|
92
|
+
const hint = stp.error || stp.message || '';
|
|
93
|
+
log(chalk.white(` ${statusGlyph('fail')} ${nm}${hint ? `: ${hint}` : ''}`));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {Array} rows
|
|
99
|
+
* @param {'integration'|'e2e'} runType
|
|
100
|
+
* @param {{ log: Function, chalk: object, metaGray: Function, sectionTitle: Function, statusGlyph: Function, SEP: string }} io
|
|
101
|
+
* @returns {boolean}
|
|
102
|
+
*/
|
|
103
|
+
function logIntegrationHealthSection(rows, runType, io) {
|
|
104
|
+
if (runType !== 'integration') return false;
|
|
105
|
+
const blocks = buildIntegrationBlocks(rows);
|
|
106
|
+
if (blocks.length === 0) return false;
|
|
107
|
+
emitSectionHeader(io, 'Integration health:');
|
|
108
|
+
const { log, chalk, statusGlyph } = io;
|
|
109
|
+
for (const b of blocks) {
|
|
110
|
+
log(chalk.white(`${b.dkey}: ${statusGlyph(b.st)} ${b.st}`));
|
|
111
|
+
emitFailedSteps(b, io);
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
integrationRollupStatus,
|
|
118
|
+
logCapabilitiesOverview,
|
|
119
|
+
logIntegrationHealthSection
|
|
120
|
+
};
|