@aifabrix/builder 2.44.0 → 2.44.1
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/datasource/log-viewer.js +105 -14
- 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/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/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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview System-level TTY renderer for DatasourceTestRun fan-out results (plan §17).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const logger = require('./logger');
|
|
9
|
+
const {
|
|
10
|
+
SEP,
|
|
11
|
+
statusGlyph
|
|
12
|
+
} = require('./datasource-test-run-display');
|
|
13
|
+
const {
|
|
14
|
+
sectionTitle,
|
|
15
|
+
headerKeyValue,
|
|
16
|
+
formatStatusKeyValue,
|
|
17
|
+
integrationFooterLine,
|
|
18
|
+
colorRollupPrefixedLine,
|
|
19
|
+
metadata: metaGray
|
|
20
|
+
} = require('./cli-test-layout-chalk');
|
|
21
|
+
const {
|
|
22
|
+
formatDataQualityLines,
|
|
23
|
+
readinessLineFromDataReadiness,
|
|
24
|
+
verdictLineFromEnvelope
|
|
25
|
+
} = require('./validation-report-tty-kit');
|
|
26
|
+
const {
|
|
27
|
+
logCapabilitiesOverview: logCapabilitiesOverviewSection,
|
|
28
|
+
logIntegrationHealthSection: logIntegrationHealthSectionBlock
|
|
29
|
+
} = require('./external-system-system-test-tty-overview');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {'ok'|'warn'|'fail'|'skipped'|null} st
|
|
33
|
+
* @returns {number}
|
|
34
|
+
*/
|
|
35
|
+
function statusRank(st) {
|
|
36
|
+
if (st === 'fail') return 0;
|
|
37
|
+
if (st === 'warn') return 1;
|
|
38
|
+
if (st === 'ok') return 2;
|
|
39
|
+
if (st === 'skipped') return 3;
|
|
40
|
+
return 4;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Per-row status for system rollup and tables: CLI/transport failure overrides envelope-only OK.
|
|
45
|
+
* @param {{ skipped?: boolean, success?: boolean, datasourceTestRun?: { status?: string }|null }} r
|
|
46
|
+
* @returns {'ok'|'warn'|'fail'|'skipped'}
|
|
47
|
+
*/
|
|
48
|
+
function rollupRowStatus(r) {
|
|
49
|
+
if (r && r.skipped) return 'skipped';
|
|
50
|
+
if (r && r.success === false) return 'fail';
|
|
51
|
+
const env = r && r.datasourceTestRun;
|
|
52
|
+
return env && typeof env.status === 'string' ? env.status : 'ok';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Array<{ key: string, skipped?: boolean, datasourceTestRun?: Object|null, success?: boolean }>} rows
|
|
57
|
+
* @returns {'ok'|'warn'|'fail'|'skipped'}
|
|
58
|
+
*/
|
|
59
|
+
function deriveSystemStatus(rows) {
|
|
60
|
+
if (!Array.isArray(rows) || rows.length === 0) return 'ok';
|
|
61
|
+
const statuses = rows.map(rollupRowStatus);
|
|
62
|
+
if (statuses.some(s => s === 'fail')) return 'fail';
|
|
63
|
+
if (statuses.some(s => s === 'warn')) return 'warn';
|
|
64
|
+
if (statuses.every(s => s === 'skipped')) return 'skipped';
|
|
65
|
+
// Mixed ok/skipped => warn per plan.
|
|
66
|
+
return statuses.every(s => s === 'ok') ? 'ok' : 'warn';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function bucketIssueSeverity(issue) {
|
|
70
|
+
const sev = issue && issue.severity ? String(issue.severity).toLowerCase() : '';
|
|
71
|
+
if (sev === 'error' || sev === 'critical' || sev === 'high' || sev === 'fatal') return 'fail';
|
|
72
|
+
if (sev === 'warn' || sev === 'warning' || sev === 'medium') return 'warn';
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Minimal heuristic rollups backed by envelope fields (no engine codenames).
|
|
78
|
+
* @param {Object|null|undefined} env
|
|
79
|
+
* @returns {'ok'|'warn'|'fail'}
|
|
80
|
+
*/
|
|
81
|
+
function dqFromEnvelope(env) {
|
|
82
|
+
const v = env && env.validation;
|
|
83
|
+
const st = v && typeof v.status === 'string' ? String(v.status) : null;
|
|
84
|
+
if (st === 'fail') return 'fail';
|
|
85
|
+
if (st === 'warn') return 'warn';
|
|
86
|
+
|
|
87
|
+
const issues = v && Array.isArray(v.issues) ? v.issues : [];
|
|
88
|
+
if (issues.some(i => bucketIssueSeverity(i) === 'fail')) return 'fail';
|
|
89
|
+
if (issues.some(i => bucketIssueSeverity(i) === 'warn')) return 'warn';
|
|
90
|
+
return 'ok';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Array} rows
|
|
95
|
+
* @returns {{ schema: 'ok'|'warn'|'fail', consistency: 'ok'|'warn'|'fail', reliability: 'ok'|'warn'|'fail' }}
|
|
96
|
+
*/
|
|
97
|
+
function deriveSystemDataQuality(rows) {
|
|
98
|
+
const envs = rows
|
|
99
|
+
.map(r => (r && r.datasourceTestRun && typeof r.datasourceTestRun === 'object' ? r.datasourceTestRun : null))
|
|
100
|
+
.filter(Boolean);
|
|
101
|
+
if (envs.length === 0) {
|
|
102
|
+
return { schema: 'warn', consistency: 'warn', reliability: 'warn' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const picks = envs.map(dqFromEnvelope);
|
|
106
|
+
const agg = picks.some(x => x === 'fail') ? 'fail' : picks.some(x => x === 'warn') ? 'warn' : 'ok';
|
|
107
|
+
return { schema: agg, consistency: agg, reliability: agg };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {Array} rows
|
|
112
|
+
* @returns {'ready'|'partial'|'not_ready'|null}
|
|
113
|
+
*/
|
|
114
|
+
function deriveSystemReadiness(rows) {
|
|
115
|
+
const envs = rows
|
|
116
|
+
.map(r => (r && r.datasourceTestRun && typeof r.datasourceTestRun === 'object' ? r.datasourceTestRun : null))
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
const drs = envs
|
|
119
|
+
.map(e => e.validation && e.validation.dataReadiness)
|
|
120
|
+
.filter(Boolean);
|
|
121
|
+
if (drs.length === 0) return null;
|
|
122
|
+
if (drs.some(x => x === 'not_ready')) return 'not_ready';
|
|
123
|
+
if (drs.some(x => x === 'partial')) return 'partial';
|
|
124
|
+
return 'ready';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function countByStatus(rows) {
|
|
128
|
+
const counts = { ok: 0, warn: 0, fail: 0, skipped: 0 };
|
|
129
|
+
for (const r of rows) {
|
|
130
|
+
const s = rollupRowStatus(r);
|
|
131
|
+
if (counts[s] !== undefined) counts[s] += 1;
|
|
132
|
+
}
|
|
133
|
+
return counts;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function pickBlockingDatasourceKey(rows) {
|
|
137
|
+
const keys = rows
|
|
138
|
+
.map(r => {
|
|
139
|
+
const env = r && r.datasourceTestRun;
|
|
140
|
+
const st = rollupRowStatus(r);
|
|
141
|
+
const key =
|
|
142
|
+
env && env.datasourceKey
|
|
143
|
+
? String(env.datasourceKey)
|
|
144
|
+
: r && r.key
|
|
145
|
+
? String(r.key)
|
|
146
|
+
: '';
|
|
147
|
+
return { key, st };
|
|
148
|
+
})
|
|
149
|
+
.filter(x => x.key);
|
|
150
|
+
keys.sort((a, b) => {
|
|
151
|
+
const d = statusRank(a.st) - statusRank(b.st);
|
|
152
|
+
if (d !== 0) return d;
|
|
153
|
+
return a.key.localeCompare(b.key);
|
|
154
|
+
});
|
|
155
|
+
return keys.length ? keys[0].key : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function issueKey(issue) {
|
|
159
|
+
const code = issue && issue.code ? String(issue.code) : '';
|
|
160
|
+
const msg = issue && issue.message ? String(issue.message) : '';
|
|
161
|
+
return `${code}::${msg}`.toLowerCase();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function issueSortKey(it) {
|
|
165
|
+
return `${statusRank(it.severity)}::${it.datasourceKey}::${it.message}`.toLowerCase();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function extractRowIssues(row) {
|
|
169
|
+
const env = row && row.datasourceTestRun;
|
|
170
|
+
const datasourceKey = (env && env.datasourceKey) || (row && row.key) || 'datasource';
|
|
171
|
+
const issues = env && env.validation && Array.isArray(env.validation.issues) ? env.validation.issues : [];
|
|
172
|
+
const envStatus = env && typeof env.status === 'string' ? env.status : null;
|
|
173
|
+
return { datasourceKey: String(datasourceKey), issues, envStatus };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function toIssueItem(datasourceKey, envStatus, iss) {
|
|
177
|
+
const sev =
|
|
178
|
+
bucketIssueSeverity(iss) ||
|
|
179
|
+
(envStatus === 'fail' ? 'fail' : envStatus === 'warn' ? 'warn' : 'ok');
|
|
180
|
+
const msg = iss && iss.message ? String(iss.message) : iss && iss.code ? String(iss.code) : 'Issue';
|
|
181
|
+
return { datasourceKey, message: msg, severity: sev };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function collectKeyIssues(rows, cap) {
|
|
185
|
+
/** @type {{ datasourceKey: string, message: string, severity: 'fail'|'warn'|'ok' }[]} */
|
|
186
|
+
const out = [];
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
for (const row of rows) {
|
|
189
|
+
const { datasourceKey, issues, envStatus } = extractRowIssues(row);
|
|
190
|
+
for (const iss of issues) {
|
|
191
|
+
const k = `${datasourceKey}::${issueKey(iss)}`;
|
|
192
|
+
if (seen.has(k)) continue;
|
|
193
|
+
seen.add(k);
|
|
194
|
+
out.push(toIssueItem(datasourceKey, envStatus, iss));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
out.sort((a, b) => issueSortKey(a).localeCompare(issueSortKey(b)));
|
|
198
|
+
return out.slice(0, Math.max(0, cap));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function certificateBucket(env) {
|
|
202
|
+
const cert = env && env.certificate;
|
|
203
|
+
if (!cert || typeof cert !== 'object') return { status: null, level: null };
|
|
204
|
+
return {
|
|
205
|
+
status: cert.status ? String(cert.status) : null,
|
|
206
|
+
level: cert.level ? String(cert.level) : null
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function systemCertStatus(rows) {
|
|
211
|
+
const certs = rows
|
|
212
|
+
.map(r => (r && r.datasourceTestRun ? certificateBucket(r.datasourceTestRun) : { status: null, level: null }))
|
|
213
|
+
.filter(c => c.status !== null);
|
|
214
|
+
if (certs.length === 0) return null;
|
|
215
|
+
if (certs.some(c => c.status === 'not_passed')) return 'not_passed';
|
|
216
|
+
return 'passed';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function drillDownCommand(runType, datasourceKey) {
|
|
220
|
+
if (!datasourceKey) return null;
|
|
221
|
+
if (runType === 'e2e') return `aifabrix datasource test-e2e ${datasourceKey}`;
|
|
222
|
+
if (runType === 'integration') return `aifabrix datasource test-integration ${datasourceKey}`;
|
|
223
|
+
return `aifabrix datasource test ${datasourceKey}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function logSystemHeader(results, runType, systemStatus) {
|
|
227
|
+
logger.log('');
|
|
228
|
+
logger.log(sectionTitle('Server test results'));
|
|
229
|
+
logger.log('');
|
|
230
|
+
logger.log(headerKeyValue('System:', results.systemKey));
|
|
231
|
+
logger.log(
|
|
232
|
+
headerKeyValue(
|
|
233
|
+
'Run:',
|
|
234
|
+
runType === 'e2e' ? 'test-e2e (dataplane)' : 'test-integration (dataplane)'
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
logger.log(formatStatusKeyValue(systemStatus, statusGlyph(systemStatus)));
|
|
238
|
+
logger.log('');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function logVerdictAndSummary(runType, systemStatus, certStatus, counts) {
|
|
242
|
+
logger.log(sectionTitle('Verdict:'));
|
|
243
|
+
logger.log(chalk.white(verdictLineFromEnvelope(systemStatus, certStatus, runType)));
|
|
244
|
+
logger.log('');
|
|
245
|
+
logger.log(sectionTitle('Summary:'));
|
|
246
|
+
logger.log(
|
|
247
|
+
chalk.white(
|
|
248
|
+
`${counts.ok + counts.warn + counts.fail} datasource(s): ${counts.ok} ok, ${counts.warn} warn, ${counts.fail} fail${counts.skipped ? `, ${counts.skipped} skipped` : ''}`
|
|
249
|
+
)
|
|
250
|
+
);
|
|
251
|
+
logger.log('');
|
|
252
|
+
logger.log(metaGray(SEP));
|
|
253
|
+
logger.log('');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function logDataQualityAndReadiness(rows) {
|
|
257
|
+
logger.log(sectionTitle('Data Quality:'));
|
|
258
|
+
const dq = deriveSystemDataQuality(rows);
|
|
259
|
+
const dqLines = formatDataQualityLines(dq, {
|
|
260
|
+
schema: 'structural coverage aggregated across datasources.',
|
|
261
|
+
consistency: 'issues aggregated across datasources.',
|
|
262
|
+
reliability: 'issues aggregated across datasources.'
|
|
263
|
+
});
|
|
264
|
+
dqLines.map(colorRollupPrefixedLine).forEach(l => logger.log(l));
|
|
265
|
+
|
|
266
|
+
const readiness = deriveSystemReadiness(rows);
|
|
267
|
+
const readinessLine = readinessLineFromDataReadiness(readiness);
|
|
268
|
+
if (readinessLine) {
|
|
269
|
+
logger.log('');
|
|
270
|
+
logger.log(colorRollupPrefixedLine(readinessLine));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
logger.log('');
|
|
274
|
+
logger.log(metaGray(SEP));
|
|
275
|
+
logger.log('');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function logDatasourceTable(rows, counts, verbose) {
|
|
279
|
+
logger.log(sectionTitle('Datasources:'));
|
|
280
|
+
logger.log('');
|
|
281
|
+
if (!verbose && counts.ok > 0) {
|
|
282
|
+
logger.log(chalk.gray(`✔ ${counts.ok} datasource(s) fully ready`));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function rowStatus(r) {
|
|
286
|
+
return rollupRowStatus(r);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function readinessLabel(env, st) {
|
|
290
|
+
const ready = env && env.validation ? env.validation.dataReadiness : null;
|
|
291
|
+
if (ready === 'not_ready') return 'Not ready';
|
|
292
|
+
if (ready === 'partial') return 'Partial';
|
|
293
|
+
if (ready === 'ready') return 'Ready';
|
|
294
|
+
if (st === 'fail') return 'Not ready';
|
|
295
|
+
if (st === 'warn') return 'Partial';
|
|
296
|
+
return 'Ready';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function shouldListRow(r) {
|
|
300
|
+
if (verbose) return true;
|
|
301
|
+
const st = rowStatus(r);
|
|
302
|
+
return st === 'warn' || st === 'fail';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const listRows = rows.filter(shouldListRow);
|
|
306
|
+
for (const r of listRows) {
|
|
307
|
+
const env = r && r.datasourceTestRun;
|
|
308
|
+
const key = (env && env.datasourceKey) || (r && r.key) || 'datasource';
|
|
309
|
+
const st = rowStatus(r);
|
|
310
|
+
const readyLabel = readinessLabel(env, st);
|
|
311
|
+
logger.log(`${statusGlyph(st)} ${chalk.white(String(key).padEnd(22))} (${readyLabel})`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function logBlockingDatasource(blocking) {
|
|
316
|
+
if (!blocking) return;
|
|
317
|
+
logger.log('');
|
|
318
|
+
logger.log(chalk.white(`Blocking datasource: ${blocking}`));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function logKeyIssuesSection(rows) {
|
|
322
|
+
const issues = collectKeyIssues(rows, 5);
|
|
323
|
+
if (issues.length === 0) return false;
|
|
324
|
+
logger.log('');
|
|
325
|
+
logger.log(metaGray(SEP));
|
|
326
|
+
logger.log('');
|
|
327
|
+
logger.log(sectionTitle('Key issues:'));
|
|
328
|
+
logger.log('');
|
|
329
|
+
let cur = null;
|
|
330
|
+
for (const it of issues) {
|
|
331
|
+
if (cur !== it.datasourceKey) {
|
|
332
|
+
cur = it.datasourceKey;
|
|
333
|
+
logger.log(chalk.white(cur));
|
|
334
|
+
}
|
|
335
|
+
logger.log(chalk.white(`- ${it.message}`));
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function logCertificationSection(rows) {
|
|
341
|
+
const certSt = systemCertStatus(rows);
|
|
342
|
+
if (!certSt) return;
|
|
343
|
+
logger.log('');
|
|
344
|
+
logger.log(metaGray(SEP));
|
|
345
|
+
logger.log('');
|
|
346
|
+
logger.log(sectionTitle('Certification:'));
|
|
347
|
+
logger.log('');
|
|
348
|
+
logger.log(chalk.white(`System level: ${certSt === 'passed' ? '✔ Achieved' : '✖ Not achieved'}`));
|
|
349
|
+
logger.log('');
|
|
350
|
+
logger.log(chalk.white('Breakdown:'));
|
|
351
|
+
for (const r of rows) {
|
|
352
|
+
const env = r && r.datasourceTestRun;
|
|
353
|
+
if (!env) continue;
|
|
354
|
+
const cert = certificateBucket(env);
|
|
355
|
+
if (!cert.status) continue;
|
|
356
|
+
const g = cert.status === 'passed' ? '✔' : '✖';
|
|
357
|
+
const tier = cert.level ? cert.level : '(no level)';
|
|
358
|
+
logger.log(chalk.white(`- ${env.datasourceKey}: ${g} ${tier}`));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function logUseAndFooter(results, runType, systemStatus, blocking) {
|
|
363
|
+
logger.log('');
|
|
364
|
+
logger.log(metaGray(SEP));
|
|
365
|
+
logger.log('');
|
|
366
|
+
logger.log(sectionTitle('Use:'));
|
|
367
|
+
const cmd = drillDownCommand(runType, blocking);
|
|
368
|
+
logger.log(chalk.white(cmd || 'aifabrix datasource test <datasourceKey>'));
|
|
369
|
+
logger.log(
|
|
370
|
+
integrationFooterLine(
|
|
371
|
+
results.success,
|
|
372
|
+
systemStatus,
|
|
373
|
+
'All server tests passed.',
|
|
374
|
+
'Server tests completed with warnings.',
|
|
375
|
+
'Some server tests failed.'
|
|
376
|
+
)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Render system-level aggregate (plan §17) for DatasourceTestRun wrapper results.
|
|
382
|
+
* @param {Object} results
|
|
383
|
+
* @param {Object} opts
|
|
384
|
+
* @param {'integration'|'e2e'} opts.runType
|
|
385
|
+
* @param {boolean} opts.verbose
|
|
386
|
+
*/
|
|
387
|
+
function displaySystemAggregateDatasourceTestRuns(results, opts) {
|
|
388
|
+
const rows = Array.isArray(results.datasourceResults) ? results.datasourceResults : [];
|
|
389
|
+
const runType = opts.runType === 'e2e' ? 'e2e' : 'integration';
|
|
390
|
+
const systemStatus = deriveSystemStatus(rows);
|
|
391
|
+
const counts = countByStatus(rows);
|
|
392
|
+
const blocking = pickBlockingDatasourceKey(rows);
|
|
393
|
+
logSystemHeader(results, runType, systemStatus);
|
|
394
|
+
logVerdictAndSummary(runType, systemStatus, systemCertStatus(rows), counts);
|
|
395
|
+
logDataQualityAndReadiness(rows);
|
|
396
|
+
logDatasourceTable(rows, counts, Boolean(opts.verbose));
|
|
397
|
+
logBlockingDatasource(blocking);
|
|
398
|
+
logKeyIssuesSection(rows);
|
|
399
|
+
const ttyIo = { log: logger.log.bind(logger), chalk, metaGray, sectionTitle, statusGlyph, SEP };
|
|
400
|
+
logCapabilitiesOverviewSection(rows, ttyIo);
|
|
401
|
+
logIntegrationHealthSectionBlock(rows, runType, ttyIo);
|
|
402
|
+
logCertificationSection(rows);
|
|
403
|
+
logUseAndFooter(results, runType, systemStatus, blocking);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
module.exports = {
|
|
407
|
+
displaySystemAggregateDatasourceTestRuns,
|
|
408
|
+
deriveSystemStatus,
|
|
409
|
+
deriveSystemDataQuality,
|
|
410
|
+
deriveSystemReadiness,
|
|
411
|
+
rollupRowStatus,
|
|
412
|
+
pickBlockingDatasourceKey,
|
|
413
|
+
collectKeyIssues,
|
|
414
|
+
systemCertStatus,
|
|
415
|
+
drillDownCommand
|
|
416
|
+
};
|
|
417
|
+
|
package/lib/utils/paths.js
CHANGED
|
@@ -332,9 +332,23 @@ function getAppPath(appName, appType) {
|
|
|
332
332
|
|
|
333
333
|
/**
|
|
334
334
|
* Base directory for integration/builder: project root when cwd is inside project, else cwd.
|
|
335
|
+
* When `aifabrix-work` / `AIFABRIX_WORK` points at a repo that contains `integration/`, use that
|
|
336
|
+
* root even if cwd is elsewhere (e.g. global CLI install + cwd under `integration/<app>/`).
|
|
335
337
|
* @returns {string} Directory to resolve integration/ and builder/ from
|
|
336
338
|
*/
|
|
337
339
|
function getIntegrationBuilderBaseDir() {
|
|
340
|
+
const work = getAifabrixWork();
|
|
341
|
+
if (work) {
|
|
342
|
+
const workNorm = path.resolve(work);
|
|
343
|
+
const integrationUnderWork = path.join(workNorm, 'integration');
|
|
344
|
+
try {
|
|
345
|
+
if (fs.existsSync(integrationUnderWork)) {
|
|
346
|
+
return workNorm;
|
|
347
|
+
}
|
|
348
|
+
} catch {
|
|
349
|
+
// ignore fs errors
|
|
350
|
+
}
|
|
351
|
+
}
|
|
338
352
|
const root = getProjectRoot();
|
|
339
353
|
const cwd = path.resolve(process.cwd());
|
|
340
354
|
const rootNorm = path.resolve(root);
|