@lewishowles/cli-style 0.2.2 → 0.3.0
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 +13 -0
- package/README.md +37 -0
- package/package.json +1 -1
- package/src/create-cli-style.js +5 -0
- package/src/formatters/width.js +92 -0
- package/src/gallery/render-gallery.js +64 -0
- package/src/index.js +15 -1
- package/src/reporters/create-reporter.js +214 -0
- package/src/theme/colours.js +1 -1
- package/src/theme/results.js +34 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-06-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added `createReporter()` for grouped setup and diagnostics output.
|
|
8
|
+
- Added `severityOrder` and `getHighestSeverityResult()` for group result summaries.
|
|
9
|
+
- Added shared `normaliseWidth()` helpers for width-aware renderers.
|
|
10
|
+
- Added reporter output to the style gallery.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Darkened the muted colour token so unchanged rows are easier to distinguish from normal foreground text.
|
|
15
|
+
|
|
3
16
|
## 0.2.2 - 2026-06-29
|
|
4
17
|
|
|
5
18
|
### Added
|
package/README.md
CHANGED
|
@@ -41,6 +41,43 @@ ui.print(output);
|
|
|
41
41
|
|
|
42
42
|
Renderer methods return strings. `ui.print()`, `ui.write()`, and CLI commands handle stdout and stderr.
|
|
43
43
|
|
|
44
|
+
## Reporter
|
|
45
|
+
|
|
46
|
+
Use a reporter when a script needs grouped output rather than one row per file.
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
import { createReporter } from "@lewishowles/cli-style";
|
|
50
|
+
|
|
51
|
+
const reporter = createReporter({
|
|
52
|
+
colour: false,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
reporter.section("Setting up Claude + Codex", "(project)");
|
|
56
|
+
reporter.group("Agent scripts", [
|
|
57
|
+
{
|
|
58
|
+
label: "project-diagnostics.py",
|
|
59
|
+
result: "unchanged",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: "repo-context.py",
|
|
63
|
+
result: "unchanged",
|
|
64
|
+
},
|
|
65
|
+
], {
|
|
66
|
+
summary: "2 already linked",
|
|
67
|
+
});
|
|
68
|
+
reporter.status("success", "Done.");
|
|
69
|
+
|
|
70
|
+
console.log(reporter.render());
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Groups can include verbose detail rows when a caller opts in:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
const reporter = createReporter({
|
|
77
|
+
verbose: process.env.AGENTS_VERBOSE === "1",
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
44
81
|
## CLI
|
|
45
82
|
|
|
46
83
|
Use `cli-style render <renderer>` when a script needs rendered text from JSON input.
|
package/package.json
CHANGED
package/src/create-cli-style.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveProfile } from "./capability/resolve-profile.js";
|
|
2
2
|
import { resolveTerminalCapabilities } from "./capability/terminal.js";
|
|
3
3
|
import { profiles } from "./profiles/profiles.js";
|
|
4
|
+
import { createReporter } from "./reporters/create-reporter.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Create a CLI style renderer with stable, testable defaults.
|
|
@@ -27,6 +28,10 @@ export function createCliStyle(options = {}) {
|
|
|
27
28
|
print: (value) => {
|
|
28
29
|
console.log(value);
|
|
29
30
|
},
|
|
31
|
+
reporter: (reporterOptions = {}) => createReporter({
|
|
32
|
+
...resolvedOptions,
|
|
33
|
+
...reporterOptions,
|
|
34
|
+
}),
|
|
30
35
|
write: (value) => {
|
|
31
36
|
process.stdout.write(value);
|
|
32
37
|
},
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Default terminal width used when no usable width is provided.
|
|
2
|
+
export const defaultWidth = 80;
|
|
3
|
+
|
|
4
|
+
// Narrowest width that still leaves room for meaningful terminal output.
|
|
5
|
+
export const minimumWidth = 20;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a usable terminal width.
|
|
9
|
+
*
|
|
10
|
+
* @param {*} width
|
|
11
|
+
* Caller-provided width.
|
|
12
|
+
* @param {object} options
|
|
13
|
+
* Width bounds.
|
|
14
|
+
* @param {number} options.defaultWidth
|
|
15
|
+
* Width to use when the input is unusable.
|
|
16
|
+
* @param {number} options.maxWidth
|
|
17
|
+
* Optional maximum width.
|
|
18
|
+
* @param {number} options.minWidth
|
|
19
|
+
* Minimum accepted width.
|
|
20
|
+
* @returns {number}
|
|
21
|
+
* Integer width within the requested bounds.
|
|
22
|
+
*/
|
|
23
|
+
export function normaliseWidth(width, options = {}) {
|
|
24
|
+
const fallback = normaliseBound(options.defaultWidth, defaultWidth);
|
|
25
|
+
const lowerBound = normaliseBound(options.minWidth, minimumWidth);
|
|
26
|
+
const upperBound = normaliseOptionalBound(options.maxWidth);
|
|
27
|
+
const numericWidth = Number(width);
|
|
28
|
+
|
|
29
|
+
if (width === null || width === undefined || !Number.isFinite(numericWidth)) {
|
|
30
|
+
return clampWidth(fallback, lowerBound, upperBound);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return clampWidth(Math.floor(numericWidth), lowerBound, upperBound);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a positive numeric bound.
|
|
38
|
+
*
|
|
39
|
+
* @param {*} value
|
|
40
|
+
* Caller-provided bound.
|
|
41
|
+
* @param {number} fallback
|
|
42
|
+
* Bound to use when value is unusable.
|
|
43
|
+
* @returns {number}
|
|
44
|
+
* Positive integer bound.
|
|
45
|
+
*/
|
|
46
|
+
function normaliseBound(value, fallback) {
|
|
47
|
+
const numericValue = Number(value);
|
|
48
|
+
|
|
49
|
+
if (!Number.isFinite(numericValue) || numericValue < 1) {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Math.floor(numericValue);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve an optional maximum width.
|
|
58
|
+
*
|
|
59
|
+
* @param {*} value
|
|
60
|
+
* Caller-provided maximum width.
|
|
61
|
+
* @returns {number|null}
|
|
62
|
+
* Positive integer maximum or null.
|
|
63
|
+
*/
|
|
64
|
+
function normaliseOptionalBound(value) {
|
|
65
|
+
const numericValue = Number(value);
|
|
66
|
+
|
|
67
|
+
if (!Number.isFinite(numericValue) || numericValue < 1) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Math.floor(numericValue);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Clamp a width between configured bounds.
|
|
76
|
+
*
|
|
77
|
+
* @param {number} width
|
|
78
|
+
* Width to clamp.
|
|
79
|
+
* @param {number} minWidth
|
|
80
|
+
* Lower bound.
|
|
81
|
+
* @param {number|null} maxWidth
|
|
82
|
+
* Optional upper bound.
|
|
83
|
+
* @returns {number}
|
|
84
|
+
* Clamped width.
|
|
85
|
+
*/
|
|
86
|
+
function clampWidth(width, minWidth, maxWidth) {
|
|
87
|
+
const lowerClampedWidth = Math.max(width, minWidth);
|
|
88
|
+
|
|
89
|
+
return maxWidth === null
|
|
90
|
+
? lowerClampedWidth
|
|
91
|
+
: Math.min(lowerClampedWidth, Math.max(maxWidth, minWidth));
|
|
92
|
+
}
|
|
@@ -17,6 +17,7 @@ import { row } from "../primitives/row.js";
|
|
|
17
17
|
import { status } from "../primitives/status.js";
|
|
18
18
|
import { step, stepProgress, stepStates } from "../primitives/step-progress.js";
|
|
19
19
|
import { table } from "../primitives/table.js";
|
|
20
|
+
import { createReporter } from "../reporters/create-reporter.js";
|
|
20
21
|
import { resultTypes } from "../theme/results.js";
|
|
21
22
|
|
|
22
23
|
// Chip tone examples should expose every configured pill colour.
|
|
@@ -128,6 +129,7 @@ export const gallerySections = [
|
|
|
128
129
|
// Pattern fixtures available for focused output and interactive search.
|
|
129
130
|
export const galleryFixtures = [
|
|
130
131
|
"diagnostic-report",
|
|
132
|
+
"reporter",
|
|
131
133
|
"command-result",
|
|
132
134
|
"task-summary",
|
|
133
135
|
"agent-transcript",
|
|
@@ -435,6 +437,64 @@ function renderPatterns(options, fixture) {
|
|
|
435
437
|
],
|
|
436
438
|
title: "Project diagnostics",
|
|
437
439
|
}, options);
|
|
440
|
+
const reporter = createReporter(options);
|
|
441
|
+
|
|
442
|
+
reporter.section("Setting up Claude + Codex", "(project)");
|
|
443
|
+
reporter.group("Project files", [
|
|
444
|
+
{
|
|
445
|
+
detail: "already exists",
|
|
446
|
+
label: "AGENTS.md",
|
|
447
|
+
result: resultTypes.UNCHANGED,
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
detail: "already exists",
|
|
451
|
+
label: "WORKSPACE.md",
|
|
452
|
+
result: resultTypes.UNCHANGED,
|
|
453
|
+
},
|
|
454
|
+
], {
|
|
455
|
+
summary: "2 already present",
|
|
456
|
+
});
|
|
457
|
+
reporter.group("Agent scripts", [
|
|
458
|
+
{
|
|
459
|
+
detail: "already linked",
|
|
460
|
+
label: "project-diagnostics.py",
|
|
461
|
+
result: resultTypes.UNCHANGED,
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
detail: "already linked",
|
|
465
|
+
label: "generated-file-guard.py",
|
|
466
|
+
result: resultTypes.UNCHANGED,
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
detail: "already linked",
|
|
470
|
+
label: "repo-context.py",
|
|
471
|
+
result: resultTypes.UNCHANGED,
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
detail: "already linked",
|
|
475
|
+
label: "change-impact.py",
|
|
476
|
+
result: resultTypes.UNCHANGED,
|
|
477
|
+
},
|
|
478
|
+
], {
|
|
479
|
+
summary: "4 already linked",
|
|
480
|
+
});
|
|
481
|
+
reporter.group("Claude files", [
|
|
482
|
+
{
|
|
483
|
+
detail: "already exists",
|
|
484
|
+
label: ".claude/",
|
|
485
|
+
result: resultTypes.UNCHANGED,
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
detail: "already up to date",
|
|
489
|
+
label: ".claude/.claudeignore",
|
|
490
|
+
result: resultTypes.UNCHANGED,
|
|
491
|
+
},
|
|
492
|
+
], {
|
|
493
|
+
summary: "2 already current",
|
|
494
|
+
});
|
|
495
|
+
reporter.status(resultTypes.SUCCESS, "Done.");
|
|
496
|
+
|
|
497
|
+
const reporterOutput = reporter.render();
|
|
438
498
|
const nextStep = nextStepBlock({
|
|
439
499
|
alternatives: [
|
|
440
500
|
"Review the focused gallery fixture",
|
|
@@ -493,6 +553,7 @@ function renderPatterns(options, fixture) {
|
|
|
493
553
|
"confirmation-result": ["Confirmation result", confirmation],
|
|
494
554
|
"diagnostic-report": ["Diagnostic report", report],
|
|
495
555
|
"next-step-block": ["Next-step block", nextStep],
|
|
556
|
+
"reporter": ["Reporter", reporterOutput],
|
|
496
557
|
"task-summary": ["Task summary", summary],
|
|
497
558
|
};
|
|
498
559
|
|
|
@@ -509,6 +570,9 @@ function renderPatterns(options, fixture) {
|
|
|
509
570
|
"Diagnostic report",
|
|
510
571
|
frameExample(report, options),
|
|
511
572
|
"",
|
|
573
|
+
"Reporter",
|
|
574
|
+
frameExample(reporterOutput, options),
|
|
575
|
+
"",
|
|
512
576
|
"Command result",
|
|
513
577
|
frameExample(command, options),
|
|
514
578
|
"",
|
package/src/index.js
CHANGED
|
@@ -23,6 +23,7 @@ export {
|
|
|
23
23
|
} from "./gallery/render-gallery.js";
|
|
24
24
|
|
|
25
25
|
export { background, foreground, stripAnsi, style } from "./formatters/ansi.js";
|
|
26
|
+
export { defaultWidth, minimumWidth, normaliseWidth } from "./formatters/width.js";
|
|
26
27
|
export { agentTranscript } from "./patterns/agent-transcript.js";
|
|
27
28
|
export { auditFinding } from "./patterns/audit-finding.js";
|
|
28
29
|
export { commandResult } from "./patterns/command-result.js";
|
|
@@ -41,6 +42,12 @@ export { row } from "./primitives/row.js";
|
|
|
41
42
|
export { status } from "./primitives/status.js";
|
|
42
43
|
export { step, stepProgress, stepStates } from "./primitives/step-progress.js";
|
|
43
44
|
export { table } from "./primitives/table.js";
|
|
45
|
+
export {
|
|
46
|
+
createReporter,
|
|
47
|
+
renderGroup,
|
|
48
|
+
renderReporterStatus,
|
|
49
|
+
renderSection,
|
|
50
|
+
} from "./reporters/create-reporter.js";
|
|
44
51
|
export {
|
|
45
52
|
chartColours,
|
|
46
53
|
chipColours,
|
|
@@ -54,4 +61,11 @@ export {
|
|
|
54
61
|
terminalColours,
|
|
55
62
|
toneColours,
|
|
56
63
|
} from "./theme/colours.js";
|
|
57
|
-
export {
|
|
64
|
+
export {
|
|
65
|
+
getHighestSeverityResult,
|
|
66
|
+
getResultSymbol,
|
|
67
|
+
getResultToken,
|
|
68
|
+
resultTokens,
|
|
69
|
+
resultTypes,
|
|
70
|
+
severityOrder,
|
|
71
|
+
} from "./theme/results.js";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { isNonEmptyString } from "../patterns/helpers.js";
|
|
2
|
+
import { status as renderStatus } from "../primitives/status.js";
|
|
3
|
+
import {
|
|
4
|
+
getHighestSeverityResult,
|
|
5
|
+
getResultToken,
|
|
6
|
+
resultTypes,
|
|
7
|
+
severityOrder,
|
|
8
|
+
} from "../theme/results.js";
|
|
9
|
+
|
|
10
|
+
// Reporter output default options.
|
|
11
|
+
const reporterDefaults = {
|
|
12
|
+
colour: false,
|
|
13
|
+
unicode: true,
|
|
14
|
+
verbose: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a pure string reporter for grouped CLI output.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} options
|
|
21
|
+
* Rendering and reporter options.
|
|
22
|
+
* @param {boolean} options.verbose
|
|
23
|
+
* Whether groups should include detail rows by default.
|
|
24
|
+
* @returns {object}
|
|
25
|
+
* Reporter helpers.
|
|
26
|
+
*/
|
|
27
|
+
export function createReporter(options = {}) {
|
|
28
|
+
const reporterOptions = {
|
|
29
|
+
...reporterDefaults,
|
|
30
|
+
...options,
|
|
31
|
+
};
|
|
32
|
+
const lines = [];
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
group: (label, items = [], groupOptions = {}) => append(lines, renderGroup(label, items, {
|
|
36
|
+
...reporterOptions,
|
|
37
|
+
...groupOptions,
|
|
38
|
+
})),
|
|
39
|
+
lines,
|
|
40
|
+
render: () => lines.join("\n"),
|
|
41
|
+
section: (label, detail = "", sectionOptions = {}) => append(lines, renderSection(label, detail, {
|
|
42
|
+
...reporterOptions,
|
|
43
|
+
...sectionOptions,
|
|
44
|
+
})),
|
|
45
|
+
status: (resultType, label, detail = "", statusOptions = {}) => append(lines, renderReporterStatus(resultType, label, detail, {
|
|
46
|
+
...reporterOptions,
|
|
47
|
+
...statusOptions,
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Render a reporter group without mutating a reporter instance.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} label
|
|
56
|
+
* Group label.
|
|
57
|
+
* @param {object[]} items
|
|
58
|
+
* Child result rows.
|
|
59
|
+
* @param {object} options
|
|
60
|
+
* Rendering options.
|
|
61
|
+
* @returns {string}
|
|
62
|
+
* Rendered group.
|
|
63
|
+
*/
|
|
64
|
+
export function renderGroup(label, items = [], options = {}) {
|
|
65
|
+
const normalisedItems = normaliseItems(items);
|
|
66
|
+
const resultType = options.result ?? getHighestSeverityResult(normalisedItems.map((item) => item.result));
|
|
67
|
+
const summary = options.summary ?? summariseItems(normalisedItems);
|
|
68
|
+
const groupLine = renderReporterStatus(resultType, label, summary, options);
|
|
69
|
+
|
|
70
|
+
if (options.verbose !== true || normalisedItems.length === 0) {
|
|
71
|
+
return groupLine;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const detailLines = normalisedItems.map((item) => {
|
|
75
|
+
const line = renderReporterStatus(item.result, item.label, item.detail, options);
|
|
76
|
+
|
|
77
|
+
return ` ${line}`;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return [groupLine, ...detailLines].join("\n");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Render a reporter section heading.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} label
|
|
87
|
+
* Section label.
|
|
88
|
+
* @param {string} detail
|
|
89
|
+
* Optional detail.
|
|
90
|
+
* @param {object} options
|
|
91
|
+
* Rendering options.
|
|
92
|
+
* @returns {string}
|
|
93
|
+
* Rendered section row.
|
|
94
|
+
*/
|
|
95
|
+
export function renderSection(label, detail = "", options = {}) {
|
|
96
|
+
return renderReporterStatus(resultTypes.INFO, label, detail, options);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Render one reporter status row.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} resultType
|
|
103
|
+
* Result type.
|
|
104
|
+
* @param {string} label
|
|
105
|
+
* Row label.
|
|
106
|
+
* @param {string} detail
|
|
107
|
+
* Optional detail.
|
|
108
|
+
* @param {object} options
|
|
109
|
+
* Rendering options.
|
|
110
|
+
* @returns {string}
|
|
111
|
+
* Rendered status row.
|
|
112
|
+
*/
|
|
113
|
+
export function renderReporterStatus(resultType, label, detail = "", options = {}) {
|
|
114
|
+
return renderStatus(resultType, detail, {
|
|
115
|
+
...options,
|
|
116
|
+
label,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Append a rendered line to the reporter state.
|
|
122
|
+
*
|
|
123
|
+
* @param {string[]} lines
|
|
124
|
+
* Reporter lines.
|
|
125
|
+
* @param {string} value
|
|
126
|
+
* Rendered value.
|
|
127
|
+
* @returns {string}
|
|
128
|
+
* Appended value.
|
|
129
|
+
*/
|
|
130
|
+
function append(lines, value) {
|
|
131
|
+
lines.push(value);
|
|
132
|
+
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Keep only rows that can render useful output.
|
|
138
|
+
*
|
|
139
|
+
* @param {object[]} items
|
|
140
|
+
* Caller-provided items.
|
|
141
|
+
* @returns {object[]}
|
|
142
|
+
* Normalised items.
|
|
143
|
+
*/
|
|
144
|
+
function normaliseItems(items) {
|
|
145
|
+
if (!Array.isArray(items)) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return items
|
|
150
|
+
.filter((item) => item !== null && typeof item === "object")
|
|
151
|
+
.map((item) => ({
|
|
152
|
+
detail: isNonEmptyString(item.detail) ? item.detail : "",
|
|
153
|
+
label: isNonEmptyString(item.label) ? item.label : getResultToken(item.result).label,
|
|
154
|
+
result: getResultToken(item.result) === getResultToken(resultTypes.UNKNOWN)
|
|
155
|
+
? resultTypes.UNKNOWN
|
|
156
|
+
: item.result,
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Summarise grouped items by result label.
|
|
162
|
+
*
|
|
163
|
+
* @param {object[]} items
|
|
164
|
+
* Normalised group items.
|
|
165
|
+
* @returns {string}
|
|
166
|
+
* Human-readable count summary.
|
|
167
|
+
*/
|
|
168
|
+
function summariseItems(items) {
|
|
169
|
+
if (items.length === 0) {
|
|
170
|
+
return "";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const counts = new Map();
|
|
174
|
+
|
|
175
|
+
for (const item of items) {
|
|
176
|
+
counts.set(item.result, (counts.get(item.result) ?? 0) + 1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return [...counts.entries()]
|
|
180
|
+
.sort(([left], [right]) => resultPriority(left) - resultPriority(right))
|
|
181
|
+
.map(([resultType, count]) => formatCount(resultType, count))
|
|
182
|
+
.join(", ");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Format a result count.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} resultType
|
|
189
|
+
* Result type.
|
|
190
|
+
* @param {number} count
|
|
191
|
+
* Result count.
|
|
192
|
+
* @returns {string}
|
|
193
|
+
* Count label.
|
|
194
|
+
*/
|
|
195
|
+
function formatCount(resultType, count) {
|
|
196
|
+
const token = getResultToken(resultType);
|
|
197
|
+
const label = token.label.toLowerCase();
|
|
198
|
+
|
|
199
|
+
return `${count} ${label}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve a result's severity index.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} resultType
|
|
206
|
+
* Result type.
|
|
207
|
+
* @returns {number}
|
|
208
|
+
* Priority index.
|
|
209
|
+
*/
|
|
210
|
+
function resultPriority(resultType) {
|
|
211
|
+
const highestResult = getHighestSeverityResult([resultType]);
|
|
212
|
+
|
|
213
|
+
return severityOrder.indexOf(highestResult);
|
|
214
|
+
}
|
package/src/theme/colours.js
CHANGED
package/src/theme/results.js
CHANGED
|
@@ -10,6 +10,18 @@ export const resultTypes = {
|
|
|
10
10
|
WARNING: "warning",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// Highest-priority result first for grouped summaries.
|
|
14
|
+
export const severityOrder = [
|
|
15
|
+
resultTypes.FAILED,
|
|
16
|
+
resultTypes.WARNING,
|
|
17
|
+
resultTypes.PARTIAL,
|
|
18
|
+
resultTypes.UNKNOWN,
|
|
19
|
+
resultTypes.SUCCESS,
|
|
20
|
+
resultTypes.SKIPPED,
|
|
21
|
+
resultTypes.INFO,
|
|
22
|
+
resultTypes.UNCHANGED,
|
|
23
|
+
];
|
|
24
|
+
|
|
13
25
|
// Display tokens for result states before colour rendering exists.
|
|
14
26
|
export const resultTokens = {
|
|
15
27
|
[resultTypes.FAILED]: {
|
|
@@ -98,6 +110,28 @@ export function getResultToken(resultType) {
|
|
|
98
110
|
return resultTokens[resultType] ?? resultTokens[resultTypes.UNKNOWN];
|
|
99
111
|
}
|
|
100
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Resolve the highest-priority result from a list.
|
|
115
|
+
*
|
|
116
|
+
* @param {string[]} resultList
|
|
117
|
+
* Result types to compare.
|
|
118
|
+
* @returns {string}
|
|
119
|
+
* Highest-priority result, or unknown when no known result is present.
|
|
120
|
+
*/
|
|
121
|
+
export function getHighestSeverityResult(resultList) {
|
|
122
|
+
if (!Array.isArray(resultList) || resultList.length === 0) {
|
|
123
|
+
return resultTypes.UNKNOWN;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const resultType of severityOrder) {
|
|
127
|
+
if (resultList.includes(resultType)) {
|
|
128
|
+
return resultType;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return resultTypes.UNKNOWN;
|
|
133
|
+
}
|
|
134
|
+
|
|
101
135
|
/**
|
|
102
136
|
* Resolve the display symbol for a result type.
|
|
103
137
|
*
|