@quenty/cli-output-helpers 1.9.0 → 1.10.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/CHANGELOG.md +22 -0
- package/dist/reporting/composite-reporter.d.ts +3 -1
- package/dist/reporting/composite-reporter.d.ts.map +1 -1
- package/dist/reporting/composite-reporter.js +9 -0
- package/dist/reporting/composite-reporter.js.map +1 -1
- package/dist/reporting/github/comment-table-reporter.d.ts +2 -1
- package/dist/reporting/github/comment-table-reporter.d.ts.map +1 -1
- package/dist/reporting/github/comment-table-reporter.js +3 -0
- package/dist/reporting/github/comment-table-reporter.js.map +1 -1
- package/dist/reporting/github/formatting.d.ts +3 -3
- package/dist/reporting/github/formatting.d.ts.map +1 -1
- package/dist/reporting/github/formatting.js +31 -16
- package/dist/reporting/github/formatting.js.map +1 -1
- package/dist/reporting/grouped-reporter.d.ts.map +1 -1
- package/dist/reporting/grouped-reporter.js +14 -2
- package/dist/reporting/grouped-reporter.js.map +1 -1
- package/dist/reporting/index.d.ts +2 -1
- package/dist/reporting/index.d.ts.map +1 -1
- package/dist/reporting/index.js +2 -0
- package/dist/reporting/index.js.map +1 -1
- package/dist/reporting/progress-format.d.ts +35 -0
- package/dist/reporting/progress-format.d.ts.map +1 -0
- package/dist/reporting/progress-format.js +88 -0
- package/dist/reporting/progress-format.js.map +1 -0
- package/dist/reporting/reporter.d.ts +27 -1
- package/dist/reporting/reporter.d.ts.map +1 -1
- package/dist/reporting/reporter.js +1 -0
- package/dist/reporting/reporter.js.map +1 -1
- package/dist/reporting/simple-reporter.d.ts.map +1 -1
- package/dist/reporting/simple-reporter.js +4 -1
- package/dist/reporting/simple-reporter.js.map +1 -1
- package/dist/reporting/spinner-reporter.d.ts.map +1 -1
- package/dist/reporting/spinner-reporter.js +25 -6
- package/dist/reporting/spinner-reporter.js.map +1 -1
- package/dist/reporting/state/live-state-tracker.d.ts +3 -1
- package/dist/reporting/state/live-state-tracker.d.ts.map +1 -1
- package/dist/reporting/state/live-state-tracker.js +15 -0
- package/dist/reporting/state/live-state-tracker.js.map +1 -1
- package/dist/reporting/state/loaded-state-tracker.d.ts +2 -1
- package/dist/reporting/state/loaded-state-tracker.d.ts.map +1 -1
- package/dist/reporting/state/loaded-state-tracker.js +4 -0
- package/dist/reporting/state/loaded-state-tracker.js.map +1 -1
- package/dist/reporting/state/state-tracker.d.ts +3 -1
- package/dist/reporting/state/state-tracker.d.ts.map +1 -1
- package/dist/reporting/summary-table-reporter.d.ts.map +1 -1
- package/dist/reporting/summary-table-reporter.js +34 -6
- package/dist/reporting/summary-table-reporter.js.map +1 -1
- package/package.json +2 -2
- package/src/reporting/composite-reporter.ts +12 -1
- package/src/reporting/github/comment-table-reporter.ts +5 -0
- package/src/reporting/github/formatting.ts +43 -17
- package/src/reporting/grouped-reporter.ts +15 -4
- package/src/reporting/index.ts +12 -0
- package/src/reporting/progress-format.ts +97 -0
- package/src/reporting/reporter.ts +34 -1
- package/src/reporting/simple-reporter.ts +4 -1
- package/src/reporting/spinner-reporter.ts +24 -12
- package/src/reporting/state/live-state-tracker.ts +16 -1
- package/src/reporting/state/loaded-state-tracker.ts +6 -0
- package/src/reporting/state/state-tracker.ts +3 -1
- package/src/reporting/summary-table-reporter.ts +40 -6
- package/tsconfig.tsbuildinfo +1 -1
package/src/reporting/index.ts
CHANGED
|
@@ -6,8 +6,20 @@ export {
|
|
|
6
6
|
type PackageStatus,
|
|
7
7
|
type PackageResult,
|
|
8
8
|
type BatchSummary,
|
|
9
|
+
type ProgressSummary,
|
|
10
|
+
type TestCountProgress,
|
|
11
|
+
type ByteProgress,
|
|
12
|
+
type StepProgress,
|
|
9
13
|
} from './reporter.js';
|
|
10
14
|
|
|
15
|
+
// Progress formatting helpers
|
|
16
|
+
export {
|
|
17
|
+
formatProgressInline,
|
|
18
|
+
formatProgressResult,
|
|
19
|
+
isEmptyTestRun,
|
|
20
|
+
summarizeFailure,
|
|
21
|
+
} from './progress-format.js';
|
|
22
|
+
|
|
11
23
|
// State tracking
|
|
12
24
|
export {
|
|
13
25
|
type IStateTracker,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatting helpers for ProgressSummary values.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type ProgressSummary, type JobPhase } from './reporter.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Format progress for inline display in spinners and running status.
|
|
9
|
+
* Returns empty string when progress is undefined.
|
|
10
|
+
*
|
|
11
|
+
* - test-counts: "(5/23)"
|
|
12
|
+
* - bytes: "12.3 MB" or "45%"
|
|
13
|
+
* - steps: "(3/10)"
|
|
14
|
+
*/
|
|
15
|
+
export function formatProgressInline(progress?: ProgressSummary): string {
|
|
16
|
+
if (!progress) return '';
|
|
17
|
+
|
|
18
|
+
switch (progress.kind) {
|
|
19
|
+
case 'test-counts':
|
|
20
|
+
return `(${progress.passed}/${progress.total})`;
|
|
21
|
+
case 'bytes':
|
|
22
|
+
if (progress.totalBytes > 0 && progress.transferredBytes > 0) {
|
|
23
|
+
return `(${_formatBytes(progress.transferredBytes)}/${_formatBytes(progress.totalBytes)})`;
|
|
24
|
+
}
|
|
25
|
+
return `(${_formatBytes(progress.totalBytes)})`;
|
|
26
|
+
case 'steps':
|
|
27
|
+
if (progress.total > 0) {
|
|
28
|
+
return `(${progress.completed}/${progress.total})`;
|
|
29
|
+
}
|
|
30
|
+
// Indeterminate: show label or just the count
|
|
31
|
+
return progress.label ? `(${progress.label})` : `(${progress.completed})`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format progress for final result display (passed/failed lines).
|
|
37
|
+
* Returns empty string when progress is undefined.
|
|
38
|
+
*
|
|
39
|
+
* - test-counts: "(23/100)" or "(0/0)"
|
|
40
|
+
* - bytes: "(12.3 MB)"
|
|
41
|
+
* - steps: "(3/10)"
|
|
42
|
+
*/
|
|
43
|
+
export function formatProgressResult(progress?: ProgressSummary): string {
|
|
44
|
+
if (!progress) return '';
|
|
45
|
+
|
|
46
|
+
switch (progress.kind) {
|
|
47
|
+
case 'test-counts':
|
|
48
|
+
return `(${progress.passed}/${progress.total})`;
|
|
49
|
+
case 'bytes':
|
|
50
|
+
return `(${_formatBytes(progress.totalBytes)})`;
|
|
51
|
+
case 'steps':
|
|
52
|
+
return `(${progress.completed}/${progress.total})`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** True when progress is test-counts with total === 0. */
|
|
57
|
+
export function isEmptyTestRun(progress?: ProgressSummary): boolean {
|
|
58
|
+
return progress?.kind === 'test-counts' && progress.total === 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Condense a raw error string and optional failedPhase into a short one-liner.
|
|
63
|
+
*
|
|
64
|
+
* Examples:
|
|
65
|
+
* summarizeFailure("Upload failed: 409 Conflict: {...}", "uploading")
|
|
66
|
+
* → "at uploading: Upload failed (409)"
|
|
67
|
+
* summarizeFailure("timeout after 120s", "executing")
|
|
68
|
+
* → "at executing: timeout after 120s"
|
|
69
|
+
*/
|
|
70
|
+
export function summarizeFailure(
|
|
71
|
+
error?: string,
|
|
72
|
+
failedPhase?: JobPhase
|
|
73
|
+
): string {
|
|
74
|
+
const parts: string[] = [];
|
|
75
|
+
|
|
76
|
+
if (failedPhase) {
|
|
77
|
+
parts.push(`at ${failedPhase}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (error) {
|
|
81
|
+
const firstLine = error.split('\n')[0];
|
|
82
|
+
const short = firstLine.length > 60 ? firstLine.slice(0, 57) + '...' : firstLine;
|
|
83
|
+
if (parts.length > 0) {
|
|
84
|
+
parts.push(`: ${short}`);
|
|
85
|
+
} else {
|
|
86
|
+
parts.push(short);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return parts.join('');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function _formatBytes(bytes: number): string {
|
|
94
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
95
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
96
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
97
|
+
}
|
|
@@ -6,11 +6,38 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/** Execution phases a package can move through. */
|
|
9
|
-
export type JobPhase = 'building' | 'downloading' | 'merging' | 'uploading' | 'scheduling' | 'launching' | 'connecting' | 'executing';
|
|
9
|
+
export type JobPhase = 'waiting' | 'building' | 'downloading' | 'merging' | 'combining' | 'uploading' | 'scheduling' | 'launching' | 'connecting' | 'executing';
|
|
10
10
|
|
|
11
11
|
/** Unified status for a package moving through the job lifecycle. */
|
|
12
12
|
export type PackageStatus = 'pending' | JobPhase | 'passed' | 'failed';
|
|
13
13
|
|
|
14
|
+
// ── Progress summary types ─────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/** Test count progress: "23/100 passed" */
|
|
17
|
+
export interface TestCountProgress {
|
|
18
|
+
kind: 'test-counts';
|
|
19
|
+
passed: number;
|
|
20
|
+
failed: number;
|
|
21
|
+
total: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Byte-level transfer progress: "45% of 12.3 MB" */
|
|
25
|
+
export interface ByteProgress {
|
|
26
|
+
kind: 'bytes';
|
|
27
|
+
transferredBytes: number;
|
|
28
|
+
totalBytes: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Generic step progress: "3/10 packages" */
|
|
32
|
+
export interface StepProgress {
|
|
33
|
+
kind: 'steps';
|
|
34
|
+
completed: number;
|
|
35
|
+
total: number;
|
|
36
|
+
label?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ProgressSummary = TestCountProgress | ByteProgress | StepProgress;
|
|
40
|
+
|
|
14
41
|
/** Result for a single package in a batch run. */
|
|
15
42
|
export interface PackageResult {
|
|
16
43
|
packageName: string;
|
|
@@ -18,6 +45,8 @@ export interface PackageResult {
|
|
|
18
45
|
logs: string;
|
|
19
46
|
durationMs: number;
|
|
20
47
|
error?: string;
|
|
48
|
+
progressSummary?: ProgressSummary;
|
|
49
|
+
failedPhase?: JobPhase;
|
|
21
50
|
}
|
|
22
51
|
|
|
23
52
|
/** Summary of a complete batch run. */
|
|
@@ -47,6 +76,9 @@ export interface Reporter {
|
|
|
47
76
|
/** Called when a package transitions phases (building, uploading, executing, etc). */
|
|
48
77
|
onPackagePhaseChange(packageName: string, phase: JobPhase): void;
|
|
49
78
|
|
|
79
|
+
/** Called when a package's in-progress metrics update (high-frequency). */
|
|
80
|
+
onPackageProgressUpdate(packageName: string, progress: ProgressSummary): void;
|
|
81
|
+
|
|
50
82
|
/** Called when a single package job completes. */
|
|
51
83
|
onPackageResult(result: PackageResult, bufferedOutput?: string[]): void;
|
|
52
84
|
|
|
@@ -62,6 +94,7 @@ export class BaseReporter implements Reporter {
|
|
|
62
94
|
async startAsync(): Promise<void> {}
|
|
63
95
|
onPackageStart(_packageName: string): void {}
|
|
64
96
|
onPackagePhaseChange(_packageName: string, _phase: JobPhase): void {}
|
|
97
|
+
onPackageProgressUpdate(_packageName: string, _progress: ProgressSummary): void {}
|
|
65
98
|
onPackageResult(_result: PackageResult, _bufferedOutput?: string[]): void {}
|
|
66
99
|
async stopAsync(): Promise<void> {}
|
|
67
100
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OutputHelper } from '../outputHelper.js';
|
|
2
2
|
import { type PackageResult, BaseReporter } from './reporter.js';
|
|
3
3
|
import { type IStateTracker } from './state/state-tracker.js';
|
|
4
|
+
import { formatProgressResult } from './progress-format.js';
|
|
4
5
|
|
|
5
6
|
export interface SimpleReporterOptions {
|
|
6
7
|
alwaysShowLogs: boolean;
|
|
@@ -37,8 +38,10 @@ export class SimpleReporter extends BaseReporter {
|
|
|
37
38
|
OutputHelper.info('(no output)');
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
const progressText = formatProgressResult(result.progressSummary);
|
|
40
42
|
if (result.success) {
|
|
41
|
-
|
|
43
|
+
const msg = progressText ? `${this._successMessage} ${progressText}` : this._successMessage;
|
|
44
|
+
OutputHelper.info(msg);
|
|
42
45
|
} else {
|
|
43
46
|
OutputHelper.error(this._failureMessage);
|
|
44
47
|
}
|
|
@@ -2,6 +2,7 @@ import { OutputHelper } from '../outputHelper.js';
|
|
|
2
2
|
import { formatDurationMs } from '../cli-utils.js';
|
|
3
3
|
import { type PackageResult, BaseReporter } from './reporter.js';
|
|
4
4
|
import { type IStateTracker } from './state/state-tracker.js';
|
|
5
|
+
import { formatProgressInline, formatProgressResult, isEmptyTestRun } from './progress-format.js';
|
|
5
6
|
|
|
6
7
|
export interface SpinnerReporterOptions {
|
|
7
8
|
showLogs: boolean;
|
|
@@ -17,7 +18,9 @@ const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'];
|
|
|
17
18
|
|
|
18
19
|
/** Emoji + label for each active phase in the spinner. */
|
|
19
20
|
const PHASE_LABELS: Record<string, string> = {
|
|
21
|
+
waiting: '◇ Waiting',
|
|
20
22
|
building: '⚙ Building',
|
|
23
|
+
combining: '🔗 Combining',
|
|
21
24
|
uploading: '▲ Uploading',
|
|
22
25
|
scheduling: '◇ Scheduling',
|
|
23
26
|
launching: '🚀 Launching',
|
|
@@ -164,22 +167,31 @@ export class SpinnerReporter extends BaseReporter {
|
|
|
164
167
|
)} ${statusText}`;
|
|
165
168
|
} else if (phaseLabel) {
|
|
166
169
|
const icon = OutputHelper.formatInfo(spinner);
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
const progressText = formatProgressInline(state.progress);
|
|
171
|
+
const plain = progressText
|
|
172
|
+
? `${phaseLabel} ${progressText}`
|
|
173
|
+
: phaseLabel;
|
|
174
|
+
const statusText = OutputHelper.formatInfo(plain.padEnd(22));
|
|
175
|
+
line = ` ${icon} ${state.name.padEnd(30)} ${statusText} ${OutputHelper.formatDim(time)}`;
|
|
171
176
|
} else if (state.status === 'passed') {
|
|
172
177
|
const icon = OutputHelper.formatSuccess('✓');
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
const progressText = formatProgressResult(state.result?.progressSummary);
|
|
179
|
+
const label = this._options.successLabel ?? 'Passed';
|
|
180
|
+
const empty = isEmptyTestRun(state.result?.progressSummary);
|
|
181
|
+
let plain = progressText ? `${label} ${progressText}` : label;
|
|
182
|
+
if (empty) plain += ' ⚠';
|
|
183
|
+
const statusText = empty
|
|
184
|
+
? OutputHelper.formatWarning(plain.padEnd(22))
|
|
185
|
+
: OutputHelper.formatSuccess(plain.padEnd(22));
|
|
186
|
+
line = ` ${icon} ${state.name.padEnd(30)} ${statusText} ${OutputHelper.formatDim(time)}`;
|
|
177
187
|
} else {
|
|
178
188
|
const icon = OutputHelper.formatError('✗');
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
const failedPhase = state.result?.failedPhase;
|
|
190
|
+
const plain = failedPhase
|
|
191
|
+
? `${this._options.failureLabel ?? 'FAILED'} at ${failedPhase}`
|
|
192
|
+
: (this._options.failureLabel ?? 'FAILED');
|
|
193
|
+
const statusText = OutputHelper.formatError(plain.padEnd(22));
|
|
194
|
+
line = ` ${icon} ${state.name.padEnd(30)} ${statusText} ${OutputHelper.formatDim(time)}`;
|
|
183
195
|
}
|
|
184
196
|
|
|
185
197
|
lines.push(line);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PackageResult, type JobPhase, BaseReporter } from '../reporter.js';
|
|
1
|
+
import { type PackageResult, type PackageStatus, type JobPhase, type ProgressSummary, BaseReporter } from '../reporter.js';
|
|
2
2
|
import { type IStateTracker, type PackageState } from './state-tracker.js';
|
|
3
3
|
|
|
4
4
|
export type { PackageState } from './state-tracker.js';
|
|
@@ -54,6 +54,10 @@ export class LiveStateTracker
|
|
|
54
54
|
return this._failures;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
getCurrentPhase(name: string): PackageStatus | undefined {
|
|
58
|
+
return this._packages.get(name)?.status;
|
|
59
|
+
}
|
|
60
|
+
|
|
57
61
|
override async startAsync(): Promise<void> {
|
|
58
62
|
this._startTimeMs = Date.now();
|
|
59
63
|
}
|
|
@@ -68,7 +72,15 @@ export class LiveStateTracker
|
|
|
68
72
|
override onPackagePhaseChange(name: string, phase: JobPhase): void {
|
|
69
73
|
const state = this._packages.get(name);
|
|
70
74
|
if (!state) return;
|
|
75
|
+
if (state.status === 'passed' || state.status === 'failed') return; // don't regress terminal states
|
|
71
76
|
state.status = phase;
|
|
77
|
+
state.progress = undefined; // clear progress on phase transition
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override onPackageProgressUpdate(name: string, progress: ProgressSummary): void {
|
|
81
|
+
const state = this._packages.get(name);
|
|
82
|
+
if (!state) return;
|
|
83
|
+
state.progress = progress;
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
override onPackageResult(
|
|
@@ -82,6 +94,9 @@ export class LiveStateTracker
|
|
|
82
94
|
state.durationMs = result.durationMs;
|
|
83
95
|
state.result = result;
|
|
84
96
|
state.bufferedOutput = bufferedOutput;
|
|
97
|
+
if (result.progressSummary) {
|
|
98
|
+
state.progress = result.progressSummary;
|
|
99
|
+
}
|
|
85
100
|
this._completed++;
|
|
86
101
|
|
|
87
102
|
this._allResults.push(result);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import {
|
|
3
3
|
type PackageResult,
|
|
4
|
+
type PackageStatus,
|
|
4
5
|
type BatchSummary,
|
|
5
6
|
} from '../reporter.js';
|
|
6
7
|
import {
|
|
@@ -48,6 +49,7 @@ export class LoadedStateTracker implements IStateTracker {
|
|
|
48
49
|
status: result.success ? 'passed' : 'failed',
|
|
49
50
|
durationMs: result.durationMs,
|
|
50
51
|
result,
|
|
52
|
+
progress: result.progressSummary,
|
|
51
53
|
});
|
|
52
54
|
if (!result.success) {
|
|
53
55
|
failures.push(result);
|
|
@@ -92,4 +94,8 @@ export class LoadedStateTracker implements IStateTracker {
|
|
|
92
94
|
getFailures(): PackageResult[] {
|
|
93
95
|
return this._failures;
|
|
94
96
|
}
|
|
97
|
+
|
|
98
|
+
getCurrentPhase(name: string): PackageStatus | undefined {
|
|
99
|
+
return this._packages.get(name)?.status;
|
|
100
|
+
}
|
|
95
101
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PackageResult, type PackageStatus } from '../reporter.js';
|
|
1
|
+
import { type PackageResult, type PackageStatus, type ProgressSummary } from '../reporter.js';
|
|
2
2
|
|
|
3
3
|
export interface PackageState {
|
|
4
4
|
name: string;
|
|
@@ -7,6 +7,7 @@ export interface PackageState {
|
|
|
7
7
|
durationMs?: number;
|
|
8
8
|
result?: PackageResult;
|
|
9
9
|
bufferedOutput?: string[];
|
|
10
|
+
progress?: ProgressSummary;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -21,4 +22,5 @@ export interface IStateTracker {
|
|
|
21
22
|
getAllPackages(): PackageState[];
|
|
22
23
|
getResults(): PackageResult[];
|
|
23
24
|
getFailures(): PackageResult[];
|
|
25
|
+
getCurrentPhase(name: string): PackageStatus | undefined;
|
|
24
26
|
}
|
|
@@ -2,6 +2,7 @@ import { OutputHelper } from '../outputHelper.js';
|
|
|
2
2
|
import { formatDurationMs } from '../cli-utils.js';
|
|
3
3
|
import { BaseReporter } from './reporter.js';
|
|
4
4
|
import { type IStateTracker } from './state/state-tracker.js';
|
|
5
|
+
import { formatProgressResult, isEmptyTestRun } from './progress-format.js';
|
|
5
6
|
|
|
6
7
|
export interface SummaryTableReporterOptions {
|
|
7
8
|
/** Label for successful results in the table. Default: "Passed" */
|
|
@@ -38,18 +39,43 @@ export class SummaryTableReporter extends BaseReporter {
|
|
|
38
39
|
const passed = results.length - failures.length;
|
|
39
40
|
const durationMs = Date.now() - this._state.startTimeMs;
|
|
40
41
|
|
|
42
|
+
const STATUS_WIDTH = 26;
|
|
43
|
+
|
|
41
44
|
console.log('');
|
|
42
|
-
console.log('Package'.padEnd(40) + 'Status'.padEnd(
|
|
43
|
-
console.log('
|
|
45
|
+
console.log('Package'.padEnd(40) + 'Status'.padEnd(STATUS_WIDTH) + 'Duration');
|
|
46
|
+
console.log('─'.repeat(40 + STATUS_WIDTH + 8));
|
|
44
47
|
|
|
48
|
+
let emptyRunCount = 0;
|
|
45
49
|
for (const result of results) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
const progressText = formatProgressResult(result.progressSummary);
|
|
51
|
+
const empty = isEmptyTestRun(result.progressSummary);
|
|
52
|
+
if (empty) emptyRunCount++;
|
|
53
|
+
|
|
54
|
+
let label: string;
|
|
55
|
+
if (result.success) {
|
|
56
|
+
label = progressText ? `${this._successLabel} ${progressText}` : this._successLabel;
|
|
57
|
+
} else {
|
|
58
|
+
const failedPhase = result.failedPhase;
|
|
59
|
+
label = failedPhase
|
|
60
|
+
? `${this._failureLabel} at ${failedPhase}`
|
|
61
|
+
: this._failureLabel;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Pad the plain text BEFORE wrapping in ANSI so padEnd counts visible chars
|
|
65
|
+
const paddedLabel = label.padEnd(STATUS_WIDTH);
|
|
66
|
+
let status: string;
|
|
67
|
+
if (result.success) {
|
|
68
|
+
status = empty
|
|
69
|
+
? OutputHelper.formatWarning(paddedLabel)
|
|
70
|
+
: OutputHelper.formatSuccess(paddedLabel);
|
|
71
|
+
} else {
|
|
72
|
+
status = OutputHelper.formatError(paddedLabel);
|
|
73
|
+
}
|
|
74
|
+
|
|
49
75
|
const duration = OutputHelper.formatDim(
|
|
50
76
|
formatDurationMs(result.durationMs)
|
|
51
77
|
);
|
|
52
|
-
console.log(result.packageName.padEnd(40) + status
|
|
78
|
+
console.log(result.packageName.padEnd(40) + status + duration);
|
|
53
79
|
}
|
|
54
80
|
|
|
55
81
|
console.log('');
|
|
@@ -64,5 +90,13 @@ export class SummaryTableReporter extends BaseReporter {
|
|
|
64
90
|
console.log(
|
|
65
91
|
`${results.length} ${this._summaryVerb}, ${passedText}, ${failedText} ${totalTime}`
|
|
66
92
|
);
|
|
93
|
+
|
|
94
|
+
if (emptyRunCount > 0) {
|
|
95
|
+
console.log(
|
|
96
|
+
OutputHelper.formatWarning(
|
|
97
|
+
`⚠ ${emptyRunCount} package(s) ran 0 tests — check test discovery`
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
67
101
|
}
|
|
68
102
|
}
|