@aifabrix/builder 2.44.3 → 2.44.5
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 +31 -15
- package/lib/api/certificates.api.js +21 -3
- package/lib/api/types/wizard.types.js +2 -1
- 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.help.js +1 -1
- package/lib/cli/setup-app.test-commands.js +75 -39
- package/lib/cli/setup-infra.js +6 -2
- package/lib/cli/setup-utility.js +20 -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-rbac.js +25 -2
- package/lib/commands/repair.js +2 -19
- package/lib/commands/test-e2e-external.js +9 -9
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/upload.js +18 -4
- package/lib/commands/wizard-core.js +53 -11
- package/lib/commands/wizard-dataplane.js +14 -6
- package/lib/commands/wizard-entity-selection.js +71 -14
- package/lib/commands/wizard-headless.js +5 -2
- package/lib/commands/wizard-helpers.js +13 -1
- package/lib/commands/wizard.js +208 -60
- 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/generator/wizard-prompts.js +7 -1
- package/lib/generator/wizard.js +34 -0
- 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/schema/wizard-config.schema.json +1 -1
- 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-readme.js +47 -3
- 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/urls-local-registry.js +52 -10
- 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/applications/miso-controller/env.template +6 -6
- package/templates/external-system/README.md.hbs +58 -32
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
const { SEP, appendReferenceLayoutLines } = require('./datasource-test-run-display');
|
|
8
8
|
const { buildDebugEnvelopeSlice } = require('./datasource-test-run-debug-slice');
|
|
9
|
+
const {
|
|
10
|
+
pushCapacityOperationsSummaryLines,
|
|
11
|
+
parseCapacityScenarioOp,
|
|
12
|
+
parseCapacityDetailKey,
|
|
13
|
+
formatCapacityOperationLabel
|
|
14
|
+
} = require('./datasource-test-run-capacity-operations');
|
|
9
15
|
|
|
10
16
|
const FULL_MAX_BYTES_PER_STRING = 8192;
|
|
11
17
|
const RAW_MAX_STRING_TTY = 65536;
|
|
@@ -74,9 +80,140 @@ function redactDebugText(text) {
|
|
|
74
80
|
.replace(/("authorization"\s*:\s*")[^"]*(")/g, '$1[REDACTED]$2');
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
function getBestEffortStepList(envelope) {
|
|
84
|
+
const v = envelope && envelope.validation;
|
|
85
|
+
const metrics = v && v.metricsOutput;
|
|
86
|
+
const steps = metrics && Array.isArray(metrics.steps) ? metrics.steps : [];
|
|
87
|
+
if (steps.length) return steps;
|
|
88
|
+
const dbg = envelope && envelope.debug;
|
|
89
|
+
const stepDebug =
|
|
90
|
+
dbg && dbg.e2eAsyncDebug && Array.isArray(dbg.e2eAsyncDebug.stepDebug) ? dbg.e2eAsyncDebug.stepDebug : [];
|
|
91
|
+
return stepDebug;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findStepByName(steps, name) {
|
|
95
|
+
return steps.find(s => s && String(s.name || s.step) === name);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sumNumber(rows, key) {
|
|
99
|
+
return rows.reduce((acc, r) => acc + (Number(r && r[key]) || 0), 0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function appendE2eWorkerHeadLine(lines, timing) {
|
|
103
|
+
const work = timing.durationSeconds;
|
|
104
|
+
if (work === undefined || work === null || work === '') return;
|
|
105
|
+
const w = Number(work);
|
|
106
|
+
if (Number.isNaN(w)) return;
|
|
107
|
+
let head = `E2E worker: ~${w.toFixed(3)}s`;
|
|
108
|
+
const wall = timing.wallClockSeconds;
|
|
109
|
+
if (wall !== undefined && wall !== null && wall !== '') {
|
|
110
|
+
const wl = Number(wall);
|
|
111
|
+
if (!Number.isNaN(wl)) {
|
|
112
|
+
head += ` (wall ~${wl.toFixed(3)}s)`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
lines.push(head);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function appendE2eStepDurationLines(lines, timing) {
|
|
119
|
+
const sd = timing.stepDurations;
|
|
120
|
+
if (!Array.isArray(sd) || !sd.length) return;
|
|
121
|
+
for (const row of sd) {
|
|
122
|
+
if (!row || typeof row !== 'object') continue;
|
|
123
|
+
const step = row.step !== undefined && row.step !== null ? String(row.step) : '?';
|
|
124
|
+
const sec =
|
|
125
|
+
row.durationSeconds !== undefined && row.durationSeconds !== null
|
|
126
|
+
? Number(row.durationSeconds)
|
|
127
|
+
: NaN;
|
|
128
|
+
if (!Number.isNaN(sec)) {
|
|
129
|
+
lines.push(` ${step}: ~${sec.toFixed(3)}s`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getFirstSyncJobPhaseTimings(e2e) {
|
|
135
|
+
const stepDebug = Array.isArray(e2e.stepDebug) ? e2e.stepDebug : [];
|
|
136
|
+
const syncStep = stepDebug.find(s => s && String(s.name) === 'sync');
|
|
137
|
+
const jobs = syncStep && syncStep.evidence && syncStep.evidence.jobs;
|
|
138
|
+
const first = Array.isArray(jobs) && jobs.length ? jobs[0] : null;
|
|
139
|
+
const audit = first && first.audit;
|
|
140
|
+
const pt = audit && typeof audit === 'object' ? audit.phaseTimingsSeconds : null;
|
|
141
|
+
return pt && typeof pt === 'object' ? pt : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function formatPhaseTimingParts(phaseTimings) {
|
|
145
|
+
const order = ['phase1', 'phase2', 'phase3', 'phase4'];
|
|
146
|
+
const parts = [];
|
|
147
|
+
for (const k of order) {
|
|
148
|
+
const raw = phaseTimings[k];
|
|
149
|
+
if (raw === undefined || raw === null) continue;
|
|
150
|
+
const v = Number(raw);
|
|
151
|
+
if (!Number.isNaN(v)) {
|
|
152
|
+
parts.push(`${k} ~${v.toFixed(3)}s`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return parts;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function appendSyncPhaseTimingsFromE2e(lines, e2e) {
|
|
159
|
+
const phaseTimings = getFirstSyncJobPhaseTimings(e2e);
|
|
160
|
+
if (!phaseTimings) return;
|
|
161
|
+
const parts = formatPhaseTimingParts(phaseTimings);
|
|
162
|
+
if (parts.length) {
|
|
163
|
+
lines.push(`Sync phases (first job): ${parts.join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* E2E poll envelope: `debug.e2eAsyncDebug` from dataplane run store (timing + stepDebug).
|
|
169
|
+
* @param {string[]} lines
|
|
170
|
+
* @param {Object} envelope
|
|
171
|
+
*/
|
|
172
|
+
function pushE2eTimingSummaryLines(lines, envelope) {
|
|
173
|
+
const dbg = envelope && envelope.debug;
|
|
174
|
+
const e2e = dbg && dbg.e2eAsyncDebug;
|
|
175
|
+
if (!e2e || typeof e2e !== 'object') return;
|
|
176
|
+
|
|
177
|
+
const timing = e2e.timing;
|
|
178
|
+
if (timing && typeof timing === 'object') {
|
|
179
|
+
appendE2eWorkerHeadLine(lines, timing);
|
|
180
|
+
appendE2eStepDurationLines(lines, timing);
|
|
181
|
+
}
|
|
182
|
+
appendSyncPhaseTimingsFromE2e(lines, e2e);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function pushSyncSummaryLines(lines, envelope) {
|
|
186
|
+
const steps = getBestEffortStepList(envelope);
|
|
187
|
+
const syncStep = findStepByName(steps, 'sync');
|
|
188
|
+
const syncStatusStep = findStepByName(steps, 'sync_status');
|
|
189
|
+
const persistenceStep = findStepByName(steps, 'persistence');
|
|
190
|
+
|
|
191
|
+
if (syncStep && syncStep.evidence && Array.isArray(syncStep.evidence.jobs)) {
|
|
192
|
+
const jobs = syncStep.evidence.jobs;
|
|
193
|
+
const processed = sumNumber(jobs, 'recordsProcessed');
|
|
194
|
+
const total = sumNumber(jobs, 'totalRecords');
|
|
195
|
+
lines.push(`Sync: ${processed}/${total} processed`);
|
|
196
|
+
}
|
|
197
|
+
if (syncStatusStep && syncStatusStep.evidence && Array.isArray(syncStatusStep.evidence.datasources)) {
|
|
198
|
+
const rows = syncStatusStep.evidence.datasources;
|
|
199
|
+
const total = sumNumber(rows, 'totalRecords');
|
|
200
|
+
const active = sumNumber(rows, 'activeRecords');
|
|
201
|
+
lines.push(`Sync status: ${active}/${total} active`);
|
|
202
|
+
}
|
|
203
|
+
if (persistenceStep && persistenceStep.evidence && persistenceStep.evidence.recordCount !== undefined) {
|
|
204
|
+
lines.push(`Persistence: ${persistenceStep.evidence.recordCount} record(s)`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
77
208
|
function pushSummaryRefLines(lines, envelope) {
|
|
78
209
|
const before = lines.length;
|
|
79
|
-
|
|
210
|
+
try {
|
|
211
|
+
pushSyncSummaryLines(lines, envelope);
|
|
212
|
+
pushE2eTimingSummaryLines(lines, envelope);
|
|
213
|
+
} catch {
|
|
214
|
+
// ignore best-effort debug summary extraction
|
|
215
|
+
}
|
|
216
|
+
appendReferenceLayoutLines(lines, envelope, { maxRefChars: 600, includeDebugMeta: true });
|
|
80
217
|
if (lines.length === before) {
|
|
81
218
|
lines.push('(No audit or debug references on this report.)');
|
|
82
219
|
}
|
|
@@ -130,6 +267,11 @@ module.exports = {
|
|
|
130
267
|
resolveDebugDisplayMode,
|
|
131
268
|
truncateUtf8String,
|
|
132
269
|
formatDatasourceTestRunDebugBlock,
|
|
270
|
+
pushE2eTimingSummaryLines,
|
|
271
|
+
pushCapacityOperationsSummaryLines,
|
|
272
|
+
parseCapacityScenarioOp,
|
|
273
|
+
parseCapacityDetailKey,
|
|
274
|
+
formatCapacityOperationLabel,
|
|
133
275
|
FULL_MAX_BYTES_PER_STRING,
|
|
134
276
|
RAW_MAX_STRING_TTY,
|
|
135
277
|
RAW_MAX_STRING_PIPE,
|
|
@@ -7,12 +7,23 @@
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
const { sectionTitle, headerKeyValue, colorAggregateGlyph, successGlyph, failureGlyph } = require('./cli-test-layout-chalk');
|
|
9
9
|
const { appendCertificateTTY } = require('./datasource-test-run-certificate-tty');
|
|
10
|
+
const { buildTtyMetaLines: buildTtyMetaLinesCore } = require('./datasource-test-run-tty-meta-lines');
|
|
10
11
|
|
|
11
12
|
const SEP = '────────────────────────────────';
|
|
12
13
|
|
|
13
14
|
/** @type {number} */
|
|
14
15
|
const DEFAULT_MAX_REF_CHARS = 200;
|
|
15
16
|
|
|
17
|
+
// UI-only: round float seconds tokens like "1.1713749s" -> "1.171s".
|
|
18
|
+
function formatSecondsText(text) {
|
|
19
|
+
const txt = String(text);
|
|
20
|
+
return txt.replace(/(\d+\.\d+)(?=s\b)/g, m => {
|
|
21
|
+
const n = Number(m);
|
|
22
|
+
if (!Number.isFinite(n)) return m;
|
|
23
|
+
return n.toFixed(3);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
/**
|
|
17
28
|
* @param {'ok'|'warn'|'fail'|'skipped'} status
|
|
18
29
|
* @returns {string}
|
|
@@ -144,23 +155,11 @@ function formatCapabilityFocusSection(envelope, focusKey) {
|
|
|
144
155
|
|
|
145
156
|
/**
|
|
146
157
|
* @param {Object} envelope
|
|
158
|
+
* @param {{ includeDebugExecutionSummary?: boolean }} [ttyOptions]
|
|
147
159
|
* @returns {string[]}
|
|
148
160
|
*/
|
|
149
|
-
function buildTtyMetaLines(envelope) {
|
|
150
|
-
|
|
151
|
-
lines.push(
|
|
152
|
-
headerKeyValue('Datasource:', `${envelope.datasourceKey} (${envelope.systemKey})`)
|
|
153
|
-
);
|
|
154
|
-
lines.push(headerKeyValue('Run:', String(envelope.runType)));
|
|
155
|
-
lines.push(formatEnvelopeStatusLine(envelope));
|
|
156
|
-
const rid = envelope.runId || envelope.testRunId;
|
|
157
|
-
if (rid) lines.push(`${chalk.gray('Run ID:')} ${chalk.cyan(String(rid))}`);
|
|
158
|
-
if (envelope.reportCompleteness && envelope.reportCompleteness !== 'full') {
|
|
159
|
-
lines.push(
|
|
160
|
-
`${chalk.gray('Report:')} ${chalk.yellow(String(envelope.reportCompleteness))}`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
return lines;
|
|
161
|
+
function buildTtyMetaLines(envelope, ttyOptions = {}) {
|
|
162
|
+
return buildTtyMetaLinesCore(envelope, formatEnvelopeStatusLine, ttyOptions);
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
/**
|
|
@@ -236,16 +235,6 @@ function appendDebugPayloadRefLines(lines, dbg, maxRefChars) {
|
|
|
236
235
|
*/
|
|
237
236
|
function appendDebugMetaLines(lines, dbg, maxRefChars) {
|
|
238
237
|
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
|
-
}
|
|
249
238
|
if (dbg.mode) {
|
|
250
239
|
lines.push(
|
|
251
240
|
`${chalk.blue.bold('debug.mode:')} ${chalk.white(String(dbg.mode))}`
|
|
@@ -272,6 +261,7 @@ function appendDebugMetaLines(lines, dbg, maxRefChars) {
|
|
|
272
261
|
*/
|
|
273
262
|
function appendReferenceLayoutLines(lines, envelope, opts = {}) {
|
|
274
263
|
const maxRefChars = opts.maxRefChars ?? DEFAULT_MAX_REF_CHARS;
|
|
264
|
+
const includeDebugMeta = opts.includeDebugMeta === true;
|
|
275
265
|
const audit = envelope && envelope.audit;
|
|
276
266
|
const dbg = envelope && envelope.debug;
|
|
277
267
|
const parts = [];
|
|
@@ -287,7 +277,9 @@ function appendReferenceLayoutLines(lines, envelope, opts = {}) {
|
|
|
287
277
|
|
|
288
278
|
if (dbg && typeof dbg === 'object') {
|
|
289
279
|
parts.push(appendStringRefBlock(lines, 'debug.executionIds:', dbg.executionIds, maxRefChars));
|
|
290
|
-
|
|
280
|
+
if (includeDebugMeta) {
|
|
281
|
+
parts.push(appendDebugMetaLines(lines, dbg, maxRefChars));
|
|
282
|
+
}
|
|
291
283
|
}
|
|
292
284
|
|
|
293
285
|
return parts.some(Boolean);
|
|
@@ -329,6 +321,8 @@ function appendIntegrationStepLines(lines, envelope) {
|
|
|
329
321
|
const integ = envelope && envelope.integration;
|
|
330
322
|
const steps = integ && Array.isArray(integ.stepResults) ? integ.stepResults : [];
|
|
331
323
|
if (!steps.length) return;
|
|
324
|
+
lines.push('');
|
|
325
|
+
lines.push(chalk.gray(SEP));
|
|
332
326
|
lines.push(sectionTitle('Integration steps:'));
|
|
333
327
|
for (const st of steps) {
|
|
334
328
|
lines.push(formatIntegrationStepLine(st));
|
|
@@ -422,22 +416,27 @@ function formatDatasourceTestRunSummary(envelope, options = {}) {
|
|
|
422
416
|
/**
|
|
423
417
|
* Default TTY block (header + verdict line + short summary + optional completeness).
|
|
424
418
|
* @param {Object} envelope
|
|
425
|
-
* @param {{
|
|
419
|
+
* @param {{
|
|
420
|
+
* focusCapabilityKey?: string,
|
|
421
|
+
* includeRefs?: boolean,
|
|
422
|
+
* includeDebugExecutionSummary?: boolean
|
|
423
|
+
* }} [options] - When focusCapabilityKey set (e.g. --capability), append single-cap block (plan §2.3).
|
|
424
|
+
* includeDebugExecutionSummary: show dataplane `debug.executionSummary` in header (TTY debug appendix uses CLI `--debug`).
|
|
426
425
|
* @returns {string}
|
|
427
426
|
*/
|
|
428
427
|
function formatDatasourceTestRunTTY(envelope, options = {}) {
|
|
429
428
|
if (!envelope || typeof envelope !== 'object') return '';
|
|
430
|
-
const lines = [
|
|
429
|
+
const lines = [
|
|
430
|
+
...buildTtyMetaLines(envelope, {
|
|
431
|
+
includeDebugExecutionSummary: options.includeDebugExecutionSummary === true
|
|
432
|
+
})
|
|
433
|
+
];
|
|
431
434
|
appendNoCapabilitiesReportedLine(lines, envelope, options);
|
|
432
435
|
lines.push('');
|
|
433
436
|
lines.push(sectionTitle('Verdict:'));
|
|
434
437
|
lines.push(chalk.white(pickExecutiveVerdictLine(envelope)));
|
|
435
438
|
appendCertificateTTY(lines, envelope);
|
|
436
|
-
lines
|
|
437
|
-
lines.push(chalk.gray(SEP));
|
|
438
|
-
if (appendReferenceLayoutLines(lines, envelope, { maxRefChars: 160 })) {
|
|
439
|
-
lines.push('');
|
|
440
|
-
}
|
|
439
|
+
appendRefsSectionIfEnabled(lines, envelope, options);
|
|
441
440
|
appendValidationIssueLines(lines, envelope);
|
|
442
441
|
appendIntegrationStepLines(lines, envelope);
|
|
443
442
|
const focus = normalizedFocusCapabilityKey(options.focusCapabilityKey);
|
|
@@ -447,6 +446,20 @@ function formatDatasourceTestRunTTY(envelope, options = {}) {
|
|
|
447
446
|
return lines.join('\n');
|
|
448
447
|
}
|
|
449
448
|
|
|
449
|
+
function appendRefsSectionIfEnabled(lines, envelope, options) {
|
|
450
|
+
if (options.includeRefs !== true) return;
|
|
451
|
+
const before = lines.length;
|
|
452
|
+
lines.push('');
|
|
453
|
+
lines.push(chalk.gray(SEP));
|
|
454
|
+
const added = appendReferenceLayoutLines(lines, envelope, { maxRefChars: 160, includeDebugMeta: false });
|
|
455
|
+
if (added) {
|
|
456
|
+
lines.push('');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
// remove the blank + separator when no refs exist
|
|
460
|
+
lines.length = before;
|
|
461
|
+
}
|
|
462
|
+
|
|
450
463
|
module.exports = {
|
|
451
464
|
formatDatasourceTestRunSummary,
|
|
452
465
|
formatDatasourceTestRunTTY,
|
|
@@ -42,7 +42,12 @@ function emitCapabilityScopeDiagnostics(envelope, opts = {}) {
|
|
|
42
42
|
* @param {string} [options.requestedCapabilityKey]
|
|
43
43
|
*/
|
|
44
44
|
function printDatasourceTestRunForTTY(envelope, options = {}) {
|
|
45
|
-
const
|
|
45
|
+
const mode = resolveDebugDisplayMode(options.debug);
|
|
46
|
+
const displayOpts = {
|
|
47
|
+
focusCapabilityKey: options.requestedCapabilityKey,
|
|
48
|
+
includeRefs: Boolean(mode),
|
|
49
|
+
includeDebugExecutionSummary: Boolean(mode)
|
|
50
|
+
};
|
|
46
51
|
if (options.json) {
|
|
47
52
|
logger.log(JSON.stringify(envelope));
|
|
48
53
|
return;
|
|
@@ -52,7 +57,6 @@ function printDatasourceTestRunForTTY(envelope, options = {}) {
|
|
|
52
57
|
} else {
|
|
53
58
|
logger.log(formatDatasourceTestRunTTY(envelope, displayOpts));
|
|
54
59
|
}
|
|
55
|
-
const mode = resolveDebugDisplayMode(options.debug);
|
|
56
60
|
if (mode) {
|
|
57
61
|
const appendix = formatDatasourceTestRunDebugBlock(envelope, mode, process.stdout.isTTY);
|
|
58
62
|
if (appendix) logger.log(appendix);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TTY meta header lines for DatasourceTestRun (extracted for max-lines).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const { headerKeyValue } = require('./cli-test-layout-chalk');
|
|
11
|
+
const { pushCapacityOperationsSummaryLines } = require('./datasource-test-run-capacity-operations');
|
|
12
|
+
|
|
13
|
+
/** @type {number} */
|
|
14
|
+
const DEFAULT_MAX_REF_CHARS = 200;
|
|
15
|
+
|
|
16
|
+
// UI-only: round float seconds tokens like "1.1713749s" -> "1.171s".
|
|
17
|
+
function formatSecondsText(text) {
|
|
18
|
+
const txt = String(text);
|
|
19
|
+
return txt.replace(/(\d+\.\d+)(?=s\b)/g, m => {
|
|
20
|
+
const n = Number(m);
|
|
21
|
+
if (!Number.isFinite(n)) return m;
|
|
22
|
+
return n.toFixed(3);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} str
|
|
28
|
+
* @param {number} maxChars
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function truncateRefLine(str, maxChars) {
|
|
32
|
+
const s = String(str);
|
|
33
|
+
if (s.length <= maxChars) return s;
|
|
34
|
+
return `${s.slice(0, Math.max(0, maxChars - 24))}… [+${s.length - maxChars + 24} chars]`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string[]} lines
|
|
39
|
+
* @param {Object} envelope
|
|
40
|
+
*/
|
|
41
|
+
function pushTtyMetaRunWallLine(lines, envelope) {
|
|
42
|
+
if (envelope.cliWallSeconds === undefined || envelope.cliWallSeconds === null) return;
|
|
43
|
+
const n = Number(envelope.cliWallSeconds);
|
|
44
|
+
if (Number.isFinite(n) && n >= 0) {
|
|
45
|
+
lines.push(`${chalk.gray('Run wall:')} ${chalk.white(`~${n}s`)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string[]} lines
|
|
51
|
+
* @param {Object} envelope
|
|
52
|
+
*/
|
|
53
|
+
function pushTtyMetaCapabilitiesPreviewLine(lines, envelope) {
|
|
54
|
+
const caps = Array.isArray(envelope.capabilities) ? envelope.capabilities : [];
|
|
55
|
+
if (caps.length === 0) return;
|
|
56
|
+
const keys = caps
|
|
57
|
+
.map(c => (c && c.key !== undefined && c.key !== null ? String(c.key) : ''))
|
|
58
|
+
.filter(Boolean);
|
|
59
|
+
if (keys.length === 0) return;
|
|
60
|
+
const preview = keys.slice(0, 10).join(', ');
|
|
61
|
+
const more = keys.length > 10 ? ', …' : '';
|
|
62
|
+
lines.push(`${chalk.gray('Capabilities tested:')} ${chalk.white(`${preview}${more}`)}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {string[]} lines
|
|
67
|
+
* @param {Object} envelope
|
|
68
|
+
*/
|
|
69
|
+
function pushTtyMetaDebugExecutionSummaryLine(lines, envelope) {
|
|
70
|
+
if (!envelope.debug || typeof envelope.debug !== 'object' || !envelope.debug.executionSummary) return;
|
|
71
|
+
lines.push(
|
|
72
|
+
`${chalk.blue.bold('debug.executionSummary:')} ${chalk.white(
|
|
73
|
+
truncateRefLine(formatSecondsText(envelope.debug.executionSummary), DEFAULT_MAX_REF_CHARS)
|
|
74
|
+
)}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {string[]} lines
|
|
80
|
+
* @param {Object} envelope
|
|
81
|
+
*/
|
|
82
|
+
function pushTtyMetaRunIdLine(lines, envelope) {
|
|
83
|
+
const rid = envelope.runId || envelope.testRunId;
|
|
84
|
+
if (rid) lines.push(`${chalk.gray('Run ID:')} ${chalk.cyan(String(rid))}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {string[]} lines
|
|
89
|
+
* @param {Object} envelope
|
|
90
|
+
*/
|
|
91
|
+
function pushTtyMetaReportCompletenessLine(lines, envelope) {
|
|
92
|
+
if (!envelope.reportCompleteness || envelope.reportCompleteness === 'full') return;
|
|
93
|
+
lines.push(`${chalk.gray('Report:')} ${chalk.yellow(String(envelope.reportCompleteness))}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Object} envelope
|
|
98
|
+
* @param {(e: Object) => string} formatEnvelopeStatusLine
|
|
99
|
+
* @param {{ includeDebugExecutionSummary?: boolean }} [options]
|
|
100
|
+
* @returns {string[]}
|
|
101
|
+
*/
|
|
102
|
+
function buildTtyMetaLines(envelope, formatEnvelopeStatusLine, options = {}) {
|
|
103
|
+
const includeDebugExecutionSummary = options.includeDebugExecutionSummary === true;
|
|
104
|
+
const lines = [];
|
|
105
|
+
lines.push(
|
|
106
|
+
headerKeyValue('Datasource:', `${envelope.datasourceKey} (${envelope.systemKey})`)
|
|
107
|
+
);
|
|
108
|
+
lines.push(headerKeyValue('Run:', String(envelope.runType)));
|
|
109
|
+
pushTtyMetaRunWallLine(lines, envelope);
|
|
110
|
+
pushTtyMetaCapabilitiesPreviewLine(lines, envelope);
|
|
111
|
+
lines.push(formatEnvelopeStatusLine(envelope));
|
|
112
|
+
pushCapacityOperationsSummaryLines(lines, envelope);
|
|
113
|
+
if (includeDebugExecutionSummary) {
|
|
114
|
+
pushTtyMetaDebugExecutionSummaryLine(lines, envelope);
|
|
115
|
+
}
|
|
116
|
+
pushTtyMetaRunIdLine(lines, envelope);
|
|
117
|
+
pushTtyMetaReportCompletenessLine(lines, envelope);
|
|
118
|
+
return lines;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
buildTtyMetaLines
|
|
123
|
+
};
|
|
@@ -265,13 +265,38 @@ function formatSingleError(error, options) {
|
|
|
265
265
|
return message || `${field}: ${error.message || 'Validation error'}`;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Removes exact duplicate lines while preserving first-seen order (AJV composite schemas often repeat the same message).
|
|
270
|
+
* @param {string[]} strings
|
|
271
|
+
* @returns {string[]}
|
|
272
|
+
*/
|
|
273
|
+
function dedupeFormattedMessagesPreserveOrder(strings) {
|
|
274
|
+
if (!Array.isArray(strings)) {
|
|
275
|
+
return strings;
|
|
276
|
+
}
|
|
277
|
+
const seen = new Set();
|
|
278
|
+
const out = [];
|
|
279
|
+
for (const s of strings) {
|
|
280
|
+
if (typeof s !== 'string') {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (seen.has(s)) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
seen.add(s);
|
|
287
|
+
out.push(s);
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
268
292
|
/**
|
|
269
293
|
* Formats validation errors into developer-friendly messages
|
|
270
294
|
* Converts technical schema errors into actionable advice
|
|
271
295
|
*
|
|
272
296
|
* @function formatValidationErrors
|
|
273
297
|
* @param {Array} errors - Raw validation errors from Ajv
|
|
274
|
-
* @param {Object} [options] - Optional; pass `{ deploymentManifest }` or `{ rootData }` for RBAC/pattern detail
|
|
298
|
+
* @param {Object} [options] - Optional; pass `{ deploymentManifest }` or `{ rootData }` for RBAC/pattern detail.
|
|
299
|
+
* Pass `{ dedupe: true }` to remove exact duplicate lines (AJV `allOf`/`if-then` often repeats the same message).
|
|
275
300
|
* @returns {Array} Formatted error messages
|
|
276
301
|
*
|
|
277
302
|
* @example
|
|
@@ -283,7 +308,11 @@ function formatValidationErrors(errors, options) {
|
|
|
283
308
|
return ['Unknown validation error'];
|
|
284
309
|
}
|
|
285
310
|
|
|
286
|
-
|
|
311
|
+
const messages = errors.map(e => formatSingleError(e, options));
|
|
312
|
+
if (options && options.dedupe === true) {
|
|
313
|
+
return dedupeFormattedMessagesPreserveOrder(messages);
|
|
314
|
+
}
|
|
315
|
+
return messages;
|
|
287
316
|
}
|
|
288
317
|
|
|
289
318
|
/**
|
|
@@ -308,6 +337,7 @@ function formatMissingDbPasswordError(appKey, opts = {}) {
|
|
|
308
337
|
module.exports = {
|
|
309
338
|
formatSingleError,
|
|
310
339
|
formatValidationErrors,
|
|
340
|
+
dedupeFormattedMessagesPreserveOrder,
|
|
311
341
|
formatMissingDbPasswordError,
|
|
312
342
|
getPatternDescription,
|
|
313
343
|
getValueAtInstancePath,
|
|
@@ -137,11 +137,43 @@ function rbacOptionalFilename(normalizedExt) {
|
|
|
137
137
|
return normalizedExt === '.yaml' || normalizedExt === '.yml' ? 'rbac.yaml' : 'rbac.json';
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Word-wraps plain description text for Markdown (MD013 ~80 columns). Keeps blank
|
|
142
|
+
* lines between paragraphs.
|
|
143
|
+
* @param {string} text - Raw description
|
|
144
|
+
* @param {number} [maxLen=80] - Target max line length
|
|
145
|
+
* @returns {string}
|
|
146
|
+
*/
|
|
147
|
+
function wrapPlainTextForMarkdown(text, maxLen = 80) {
|
|
148
|
+
if (!text || typeof text !== 'string') return text;
|
|
149
|
+
const blocks = text.split(/\n\s*\n/);
|
|
150
|
+
const wrapped = blocks.map((block) => {
|
|
151
|
+
const flat = block.replace(/\s+/g, ' ').trim();
|
|
152
|
+
if (!flat) return '';
|
|
153
|
+
const words = flat.split(' ');
|
|
154
|
+
const lines = [];
|
|
155
|
+
let current = '';
|
|
156
|
+
for (const w of words) {
|
|
157
|
+
const candidate = current ? `${current} ${w}` : w;
|
|
158
|
+
if (candidate.length <= maxLen) {
|
|
159
|
+
current = candidate;
|
|
160
|
+
} else {
|
|
161
|
+
if (current) lines.push(current);
|
|
162
|
+
current = w;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (current) lines.push(current);
|
|
166
|
+
return lines.join('\n');
|
|
167
|
+
});
|
|
168
|
+
return wrapped.filter(Boolean).join('\n\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
140
171
|
function buildExternalReadmeContext(params = {}) {
|
|
141
172
|
const appName = params.appName || params.systemKey || 'external-system';
|
|
142
173
|
const systemKey = params.systemKey || appName;
|
|
143
174
|
const displayName = params.displayName || formatDisplayName(systemKey);
|
|
144
|
-
const
|
|
175
|
+
const rawDescription = params.description || `External system integration for ${systemKey}`;
|
|
176
|
+
const description = wrapPlainTextForMarkdown(rawDescription);
|
|
145
177
|
const systemType = params.systemType || 'openapi';
|
|
146
178
|
const fileExt = params.fileExt !== undefined ? params.fileExt : '.json';
|
|
147
179
|
const normalizedExt = fileExt && fileExt.startsWith('.') ? fileExt : `.${fileExt || 'json'}`;
|
|
@@ -186,13 +218,25 @@ function loadExternalReadmeTemplate() {
|
|
|
186
218
|
* @param {Object} params - Context parameters
|
|
187
219
|
* @returns {string} README content
|
|
188
220
|
*/
|
|
221
|
+
/**
|
|
222
|
+
* Collapses 3+ consecutive newlines to 2 (fixes MD012 from Handlebars spacing).
|
|
223
|
+
* @param {string} md - Markdown body
|
|
224
|
+
* @returns {string}
|
|
225
|
+
*/
|
|
226
|
+
function collapseConsecutiveBlankLines(md) {
|
|
227
|
+
if (!md || typeof md !== 'string') return md;
|
|
228
|
+
return md.replace(/\n{3,}/g, '\n\n');
|
|
229
|
+
}
|
|
230
|
+
|
|
189
231
|
function generateExternalReadmeContent(params = {}) {
|
|
190
232
|
const template = loadExternalReadmeTemplate();
|
|
191
233
|
const context = buildExternalReadmeContext(params);
|
|
192
|
-
return template(context);
|
|
234
|
+
return collapseConsecutiveBlankLines(template(context));
|
|
193
235
|
}
|
|
194
236
|
|
|
195
237
|
module.exports = {
|
|
196
238
|
buildExternalReadmeContext,
|
|
197
|
-
generateExternalReadmeContent
|
|
239
|
+
generateExternalReadmeContent,
|
|
240
|
+
wrapPlainTextForMarkdown,
|
|
241
|
+
collapseConsecutiveBlankLines
|
|
198
242
|
};
|
|
@@ -214,6 +214,40 @@ function datasourceTestRunToLegacyRow(run) {
|
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Map dataplane **datasourceSummaries** item into legacy probe row shape.
|
|
219
|
+
* @param {Object} summary
|
|
220
|
+
* @returns {Object}
|
|
221
|
+
*/
|
|
222
|
+
function datasourceSummaryToLegacyRow(summary) {
|
|
223
|
+
const key = summary.datasourceKey || summary.datasource_key || 'unknown';
|
|
224
|
+
const root = String(summary.status || '').toLowerCase();
|
|
225
|
+
const vStat = String(summary.validationStatus || '').toLowerCase();
|
|
226
|
+
const issues = Array.isArray(summary.issues) ? summary.issues : [];
|
|
227
|
+
const { errors, warnings } = partitionDatasourceTestRunIssues(issues);
|
|
228
|
+
if (vStat === 'warn' && warnings.length === 0) {
|
|
229
|
+
warnings.push('validation warnings');
|
|
230
|
+
}
|
|
231
|
+
const isValid = vStat !== 'fail' && errors.length === 0;
|
|
232
|
+
const cert = String(summary.certificateStatus || '').toLowerCase();
|
|
233
|
+
if (cert === 'not_passed' && isValid) {
|
|
234
|
+
warnings.push('certification not passed');
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
sourceKey: key,
|
|
238
|
+
key,
|
|
239
|
+
success: root !== 'fail',
|
|
240
|
+
skipped: root === 'skipped',
|
|
241
|
+
validationResults: { isValid, errors, warnings },
|
|
242
|
+
endpointTestResults: {
|
|
243
|
+
success: true,
|
|
244
|
+
endpointReachable: true,
|
|
245
|
+
message: null,
|
|
246
|
+
warning: false
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
217
251
|
/**
|
|
218
252
|
* Normalize runtime probe body for Tier B display.
|
|
219
253
|
*
|
|
@@ -225,6 +259,7 @@ function datasourceTestRunToLegacyRow(run) {
|
|
|
225
259
|
* - **Runtime probe (--probe)** uses validation run and returns either:
|
|
226
260
|
* - legacy `{ results: ExternalDataSourceTestResponse[] }`, or
|
|
227
261
|
* - canonical **DatasourceTestRun** envelope (single success shape) which we coerce into a legacy row
|
|
262
|
+
* - optional **datasourceSummaries** on the envelope for multi-datasource system runs
|
|
228
263
|
*
|
|
229
264
|
* This helper intentionally accepts multiple shapes so the CLI output stays truthful across dataplane versions.
|
|
230
265
|
*
|
|
@@ -234,6 +269,10 @@ function datasourceTestRunToLegacyRow(run) {
|
|
|
234
269
|
function coerceProbeRunToResultRows(probeRaw) {
|
|
235
270
|
if (!probeRaw || typeof probeRaw !== 'object') return [];
|
|
236
271
|
if (Array.isArray(probeRaw.results) && probeRaw.results.length > 0) return probeRaw.results;
|
|
272
|
+
const summaries = probeRaw.datasourceSummaries;
|
|
273
|
+
if (Array.isArray(summaries) && summaries.length > 0) {
|
|
274
|
+
return summaries.map(datasourceSummaryToLegacyRow);
|
|
275
|
+
}
|
|
237
276
|
if (isDatasourceTestRunEnvelope(probeRaw)) return [datasourceTestRunToLegacyRow(probeRaw)];
|
|
238
277
|
return [];
|
|
239
278
|
}
|
|
@@ -54,7 +54,7 @@ function logDeployProbeDatasourceSection(probeData) {
|
|
|
54
54
|
const results = coerceProbeRunToResultRows(probeData);
|
|
55
55
|
const probeSummary = summarizeProbeResults(results);
|
|
56
56
|
logSectionTitle('Runtime Readiness:');
|
|
57
|
-
logDatasourceTable(probeSummary.rows, probeSummary);
|
|
57
|
+
logDatasourceTable(probeSummary.rows, probeSummary, 'Per datasource:');
|
|
58
58
|
if (probeSummary.issues.length > 0) {
|
|
59
59
|
logSeparator();
|
|
60
60
|
logSectionTitle('Key Issues:');
|
|
@@ -253,10 +253,9 @@ function logDeployReadinessSummary(ctx) {
|
|
|
253
253
|
logDeployConfigDeploymentRuntime(systemCfg, deploymentOk, probeData, summary, deploymentDetail || null);
|
|
254
254
|
|
|
255
255
|
logSeparator();
|
|
256
|
+
logDatasourceTable(summary.rows, summary, probeData ? 'Configured datasources:' : undefined);
|
|
256
257
|
if (probeData) {
|
|
257
258
|
logDeployProbeDatasourceSection(probeData);
|
|
258
|
-
} else {
|
|
259
|
-
logDatasourceTable(summary.rows, summary);
|
|
260
259
|
}
|
|
261
260
|
|
|
262
261
|
logDeployIdentityAndCredentialBlocks(systemCfg, !!probeData);
|
|
@@ -50,9 +50,10 @@ function verdictLine(v) {
|
|
|
50
50
|
/**
|
|
51
51
|
* @param {Array<{ key: string, tier: string }>} rows
|
|
52
52
|
* @param {{ ready: number, partial: number, failed: number }} counts
|
|
53
|
+
* @param {string} [title]
|
|
53
54
|
*/
|
|
54
|
-
function logDatasourceTable(rows, counts) {
|
|
55
|
-
logSectionTitle('Datasources:');
|
|
55
|
+
function logDatasourceTable(rows, counts, title) {
|
|
56
|
+
logSectionTitle(title && String(title).trim() ? String(title).trim() : 'Datasources:');
|
|
56
57
|
for (const r of rows) {
|
|
57
58
|
const statusLabel = r.tier === 'ready' ? 'Ready' : r.tier === 'failed' ? 'Failed' : 'Partial';
|
|
58
59
|
logger.log(
|