@lewishowles/cli-style 0.3.0 → 0.4.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.
@@ -1,13 +1,7 @@
1
1
  import { profiles } from "../profiles/profiles.js";
2
2
  import { status } from "../primitives/status.js";
3
3
  import { resultTypes } from "../theme/results.js";
4
- import {
5
- formatLabel,
6
- isNonEmptyString,
7
- isRecord,
8
- renderSection,
9
- renderTitle,
10
- } from "./helpers.js";
4
+ import { formatLabel, isNonEmptyString, isRecord, renderSection, renderTitle } from "./helpers.js";
11
5
 
12
6
  // Diagnostic reports use a stable title when the caller does not provide one.
13
7
  const defaultTitle = "Diagnostic report";
@@ -49,9 +43,10 @@ export function diagnosticReport(report, options = {}) {
49
43
  renderSkippedChecks(report.skippedChecks, options),
50
44
  renderNextActions(report.nextActions, options),
51
45
  ].filter((section) => section !== "");
52
- const title = typeof report.title === "string" && report.title !== ""
53
- ? report.title
54
- : defaultTitle;
46
+
47
+ const title =
48
+ typeof report.title === "string" && report.title !== "" ? report.title : defaultTitle;
49
+
55
50
  return [renderTitle(title, options), ...sections].join("\n\n");
56
51
  }
57
52
 
@@ -69,11 +64,10 @@ function renderChecks(checks, options) {
69
64
  const validChecks = Array.isArray(checks)
70
65
  ? checks.filter((check) => isRecord(check) && isNonEmptyString(check.name))
71
66
  : [];
72
- const lines = validChecks.map((check) => status(
73
- check.result,
74
- formatLabel(check.name, check.detail, options),
75
- options,
76
- ));
67
+
68
+ const lines = validChecks.map((check) =>
69
+ status(check.result, formatLabel(check.name, check.detail, options), options),
70
+ );
77
71
 
78
72
  return renderSection("Checks", lines, options);
79
73
  }
@@ -92,11 +86,8 @@ function renderFindings(findings, options) {
92
86
  const validFindings = Array.isArray(findings)
93
87
  ? findings.filter((finding) => isRecord(finding) && isNonEmptyString(finding.message))
94
88
  : [];
95
- const lines = validFindings.map((finding) => status(
96
- finding.result,
97
- finding.message,
98
- options,
99
- ));
89
+
90
+ const lines = validFindings.map((finding) => status(finding.result, finding.message, options));
100
91
 
101
92
  return renderSection("Findings", lines, options);
102
93
  }
@@ -115,11 +106,10 @@ function renderSkippedChecks(skippedChecks, options) {
115
106
  const validChecks = Array.isArray(skippedChecks)
116
107
  ? skippedChecks.filter((check) => isRecord(check) && isNonEmptyString(check.name))
117
108
  : [];
118
- const lines = validChecks.map((check) => status(
119
- resultTypes.SKIPPED,
120
- formatLabel(check.name, check.reason, options),
121
- options,
122
- ));
109
+
110
+ const lines = validChecks.map((check) =>
111
+ status(resultTypes.SKIPPED, formatLabel(check.name, check.reason, options), options),
112
+ );
123
113
 
124
114
  return renderSection("Skipped checks", lines, options);
125
115
  }
@@ -135,9 +125,7 @@ function renderSkippedChecks(skippedChecks, options) {
135
125
  * Rendered next-action section.
136
126
  */
137
127
  function renderNextActions(nextActions, options) {
138
- const validActions = Array.isArray(nextActions)
139
- ? nextActions.filter(isNonEmptyString)
140
- : [];
128
+ const validActions = Array.isArray(nextActions) ? nextActions.filter(isNonEmptyString) : [];
141
129
  const lines = validActions.map((action, index) => `${index + 1}. ${action}`);
142
130
 
143
131
  return renderSection("Next actions", lines, options, false);
@@ -81,12 +81,10 @@ export function renderSection(title, lines, options, listItems = true) {
81
81
  return "";
82
82
  }
83
83
 
84
- const heading = options.profile === profiles.AGENT
85
- ? `## ${title}`
86
- : title;
87
- const content = options.profile === profiles.AGENT && listItems
88
- ? lines.map((line) => `- ${line}`)
89
- : lines;
84
+ const heading = options.profile === profiles.AGENT ? `## ${title}` : title;
85
+
86
+ const content =
87
+ options.profile === profiles.AGENT && listItems ? lines.map((line) => `- ${line}`) : lines;
90
88
 
91
89
  return [heading, ...content].join("\n");
92
90
  }
@@ -42,12 +42,17 @@ export function nextStepBlock(nextStep, options = {}) {
42
42
 
43
43
  const title = isNonEmptyString(nextStep.title) ? nextStep.title : defaultTitle;
44
44
  const next = renderNext(nextStep.next, options);
45
+
45
46
  const reason = isNonEmptyString(nextStep.reason)
46
47
  ? renderSection("Why", [nextStep.reason], options, false)
47
48
  : "";
49
+
48
50
  const commands = renderCommands(nextStep.commands, options);
49
- const alternatives = normaliseStringList(nextStep.alternatives)
50
- .map((alternative) => `- ${alternative}`);
51
+
52
+ const alternatives = normaliseStringList(nextStep.alternatives).map(
53
+ (alternative) => `- ${alternative}`,
54
+ );
55
+
51
56
  const sections = [
52
57
  next,
53
58
  reason,
@@ -101,12 +106,7 @@ function renderCommands(commands, options) {
101
106
  }
102
107
 
103
108
  if (options.profile === profiles.AGENT) {
104
- return [
105
- "## Commands",
106
- "```sh",
107
- ...validCommands,
108
- "```",
109
- ].join("\n");
109
+ return ["## Commands", "```sh", ...validCommands, "```"].join("\n");
110
110
  }
111
111
 
112
112
  return renderSection(
@@ -49,6 +49,7 @@ export function taskSummary(summary, options = {}) {
49
49
  const detail = formatLabel(task, summary.summary, options);
50
50
  const completed = normaliseStringList(summary.completed).map((item) => `- ${item}`);
51
51
  const remaining = normaliseStringList(summary.remaining).map((item) => `- ${item}`);
52
+
52
53
  const sections = [
53
54
  status(summary.result ?? resultTypes.UNKNOWN, detail, options),
54
55
  renderSection("Completed", completed, options, false),
@@ -31,9 +31,7 @@ export function barChart(options = {}) {
31
31
  const maximum = Math.max(...rows.map((row) => Math.abs(row.value)), 1);
32
32
  const width = normaliseWidth(options.barWidth);
33
33
 
34
- return rows
35
- .map((row) => renderRow(row, labelWidth, maximum, width, options))
36
- .join("\n");
34
+ return rows.map((row) => renderRow(row, labelWidth, maximum, width, options)).join("\n");
37
35
  }
38
36
 
39
37
  /**
@@ -49,13 +47,14 @@ function normaliseRows(rows) {
49
47
  return [];
50
48
  }
51
49
 
52
- return rows.filter((row) => (
53
- row !== null
54
- && typeof row === "object"
55
- && typeof row.label === "string"
56
- && row.label !== ""
57
- && Number.isFinite(row.value)
58
- ));
50
+ return rows.filter(
51
+ (row) =>
52
+ row !== null &&
53
+ typeof row === "object" &&
54
+ typeof row.label === "string" &&
55
+ row.label !== "" &&
56
+ Number.isFinite(row.value),
57
+ );
59
58
  }
60
59
 
61
60
  /**
@@ -101,9 +100,10 @@ function renderRow(row, labelWidth, maximum, width, options) {
101
100
  }
102
101
 
103
102
  const renderedLabel = foreground(label, chartColours.label, options);
104
- const renderedBar = barWidth === 0
105
- ? ""
106
- : foreground(bar.slice(0, barWidth), barColour(row), options);
103
+
104
+ const renderedBar =
105
+ barWidth === 0 ? "" : foreground(bar.slice(0, barWidth), barColour(row), options);
106
+
107
107
  const barPadding = bar.slice(barWidth);
108
108
  const renderedValue = foreground(String(row.value), chartColours.value, options);
109
109
 
@@ -34,22 +34,16 @@ export function panel(options = {}) {
34
34
  const lines = options.lines ?? [];
35
35
 
36
36
  if (options.profile === profiles.CI) {
37
- return [
38
- ...(options.title === undefined ? [] : [options.title]),
39
- ...lines,
40
- ].join("\n");
37
+ return [...(options.title === undefined ? [] : [options.title]), ...lines].join("\n");
41
38
  }
42
39
 
43
40
  const width = Math.max(options.panelWidth ?? defaultWidth, 4);
44
- const panelLines = [
45
- "",
46
- ...(options.title === undefined ? [] : [options.title]),
47
- ...lines,
48
- "",
49
- ];
41
+ const panelLines = ["", ...(options.title === undefined ? [] : [options.title]), ...lines, ""];
50
42
 
51
43
  return panelLines
52
- .map((line, index) => renderLine(line, width, index === 1 && options.title !== undefined, options))
44
+ .map((line, index) =>
45
+ renderLine(line, width, index === 1 && options.title !== undefined, options),
46
+ )
53
47
  .join("\n");
54
48
  }
55
49
 
@@ -22,9 +22,10 @@ import { getResultSymbol, getResultToken, resultTypes } from "../theme/results.j
22
22
  export function status(resultType = resultTypes.UNKNOWN, detail = "", options = {}) {
23
23
  const token = getResultToken(resultType);
24
24
  const symbol = getResultSymbol(resultType, options);
25
- const label = typeof options.label === "string" && options.label !== ""
26
- ? options.label
27
- : token.label;
25
+
26
+ const label =
27
+ typeof options.label === "string" && options.label !== "" ? options.label : token.label;
28
+
28
29
  const prefix = `${symbol} ${label}`;
29
30
  const detailText = detail === "" ? "" : ` ${detail}`;
30
31
 
@@ -67,12 +67,14 @@ export function stepProgress(options = {}) {
67
67
 
68
68
  const current = normaliseCurrent(options.current, steps.length);
69
69
 
70
- return steps.map((label, index) => {
71
- const state = stateForIndex(index, current);
72
- const count = `${index + 1}/${steps.length}`;
73
-
74
- return `${count} ${step(label, state, options)}`;
75
- }).join("\n");
70
+ return steps
71
+ .map((label, index) => {
72
+ const state = stateForIndex(index, current);
73
+ const count = `${index + 1}/${steps.length}`;
74
+
75
+ return `${count} ${step(label, state, options)}`;
76
+ })
77
+ .join("\n");
76
78
  }
77
79
 
78
80
  /**
@@ -34,8 +34,11 @@ export function table(options = {}) {
34
34
  }
35
35
 
36
36
  const widths = columnWidths(columns, rows);
37
- const naturalWidth = widths.reduce((total, width) => total + width, 0)
38
- + (columnSeparator.length * (columns.length - 1));
37
+
38
+ const naturalWidth =
39
+ widths.reduce((total, width) => total + width, 0) +
40
+ columnSeparator.length * (columns.length - 1);
41
+
39
42
  const availableWidth = Number.isFinite(options.width) ? options.width : defaultWidth;
40
43
 
41
44
  if (naturalWidth > availableWidth) {
@@ -58,14 +61,15 @@ function normaliseColumns(columns) {
58
61
  return [];
59
62
  }
60
63
 
61
- return columns.filter((column) => (
62
- column !== null
63
- && typeof column === "object"
64
- && typeof column.key === "string"
65
- && column.key !== ""
66
- && typeof column.label === "string"
67
- && column.label !== ""
68
- ));
64
+ return columns.filter(
65
+ (column) =>
66
+ column !== null &&
67
+ typeof column === "object" &&
68
+ typeof column.key === "string" &&
69
+ column.key !== "" &&
70
+ typeof column.label === "string" &&
71
+ column.label !== "",
72
+ );
69
73
  }
70
74
 
71
75
  /**
@@ -95,10 +99,9 @@ function normaliseRows(rows) {
95
99
  * Column widths.
96
100
  */
97
101
  function columnWidths(columns, rows) {
98
- return columns.map((column) => Math.max(
99
- column.label.length,
100
- ...rows.map((row) => cellValue(row[column.key]).length),
101
- ));
102
+ return columns.map((column) =>
103
+ Math.max(column.label.length, ...rows.map((row) => cellValue(row[column.key]).length)),
104
+ );
102
105
  }
103
106
 
104
107
  /**
@@ -117,25 +120,28 @@ function columnWidths(columns, rows) {
117
120
  */
118
121
  function renderTable(columns, rows, widths, options) {
119
122
  const header = columns
120
- .map((column, index) => renderHeader(
121
- padColumn(column.label, widths[index], index, columns.length),
122
- options,
123
- ))
123
+ .map((column, index) =>
124
+ renderHeader(padColumn(column.label, widths[index], index, columns.length), options),
125
+ )
124
126
  .join(columnSeparator);
127
+
125
128
  const ruleCharacter = options.unicode === false ? "-" : "─";
126
- const rule = widths
127
- .map((width) => ruleCharacter.repeat(width))
128
- .join(columnSeparator);
129
- const renderedRule = options.colour === true
130
- ? foreground(rule, tableColours.border, options)
131
- : rule;
132
- const body = rows.map((row) => columns
133
- .map((column, index) => renderCell(
134
- padColumn(cellValue(row[column.key]), widths[index], index, columns.length),
135
- index,
136
- options,
137
- ))
138
- .join(columnSeparator));
129
+ const rule = widths.map((width) => ruleCharacter.repeat(width)).join(columnSeparator);
130
+
131
+ const renderedRule =
132
+ options.colour === true ? foreground(rule, tableColours.border, options) : rule;
133
+
134
+ const body = rows.map((row) =>
135
+ columns
136
+ .map((column, index) =>
137
+ renderCell(
138
+ padColumn(cellValue(row[column.key]), widths[index], index, columns.length),
139
+ index,
140
+ options,
141
+ ),
142
+ )
143
+ .join(columnSeparator),
144
+ );
139
145
 
140
146
  return [header, renderedRule, ...body].join("\n");
141
147
  }
@@ -173,14 +179,17 @@ function padColumn(value, width, index, columnCount) {
173
179
  function renderBlocks(columns, rows, options) {
174
180
  const labelWidth = Math.max(...columns.map((column) => column.label.length));
175
181
 
176
- return rows.map((row) => columns
177
- .map((column) => {
178
- const label = renderHeader(column.label.padEnd(labelWidth, " "), options);
179
- const value = renderCell(cellValue(row[column.key]), 0, options);
180
-
181
- return `${label}${columnSeparator}${value}`;
182
- })
183
- .join("\n"))
182
+ return rows
183
+ .map((row) =>
184
+ columns
185
+ .map((column) => {
186
+ const label = renderHeader(column.label.padEnd(labelWidth, " "), options);
187
+ const value = renderCell(cellValue(row[column.key]), 0, options);
188
+
189
+ return `${label}${columnSeparator}${value}`;
190
+ })
191
+ .join("\n"),
192
+ )
184
193
  .join("\n\n");
185
194
  }
186
195
 
@@ -231,9 +240,7 @@ function renderCell(value, columnIndex, options) {
231
240
  return value;
232
241
  }
233
242
 
234
- const colour = columnIndex === 0
235
- ? tableColours.primaryText
236
- : tableColours.secondaryText;
243
+ const colour = columnIndex === 0 ? tableColours.primaryText : tableColours.secondaryText;
237
244
 
238
245
  return foreground(value, colour, options);
239
246
  }
@@ -1,4 +1,5 @@
1
1
  import { isNonEmptyString } from "../patterns/helpers.js";
2
+ import { divider } from "../primitives/divider.js";
2
3
  import { status as renderStatus } from "../primitives/status.js";
3
4
  import {
4
5
  getHighestSeverityResult,
@@ -29,26 +30,66 @@ export function createReporter(options = {}) {
29
30
  ...reporterDefaults,
30
31
  ...options,
31
32
  };
33
+
32
34
  const lines = [];
33
35
 
34
36
  return {
35
- group: (label, items = [], groupOptions = {}) => append(lines, renderGroup(label, items, {
36
- ...reporterOptions,
37
- ...groupOptions,
38
- })),
37
+ divider: (label, detail = "", dividerOptions = {}) =>
38
+ append(
39
+ lines,
40
+ renderReporterDivider(label, detail, {
41
+ ...reporterOptions,
42
+ ...dividerOptions,
43
+ }),
44
+ ),
45
+ group: (label, items = [], groupOptions = {}) =>
46
+ append(
47
+ lines,
48
+ renderGroup(label, items, {
49
+ ...reporterOptions,
50
+ ...groupOptions,
51
+ }),
52
+ ),
39
53
  lines,
40
54
  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
- })),
55
+ section: (label, detail = "", sectionOptions = {}) =>
56
+ append(
57
+ lines,
58
+ renderSection(label, detail, {
59
+ ...reporterOptions,
60
+ ...sectionOptions,
61
+ }),
62
+ ),
63
+ status: (resultType, label, detail = "", statusOptions = {}) =>
64
+ append(
65
+ lines,
66
+ renderReporterStatus(resultType, label, detail, {
67
+ ...reporterOptions,
68
+ ...statusOptions,
69
+ }),
70
+ ),
49
71
  };
50
72
  }
51
73
 
74
+ /**
75
+ * Render a reporter phase divider.
76
+ *
77
+ * @param {string} label
78
+ * Divider label.
79
+ * @param {string} detail
80
+ * Optional supporting detail.
81
+ * @param {object} options
82
+ * Rendering options.
83
+ * @returns {string}
84
+ * Rendered divider.
85
+ */
86
+ export function renderReporterDivider(label, detail = "", options = {}) {
87
+ return divider({
88
+ ...options,
89
+ label: formatLabel(label, detail),
90
+ });
91
+ }
92
+
52
93
  /**
53
94
  * Render a reporter group without mutating a reporter instance.
54
95
  *
@@ -63,7 +104,10 @@ export function createReporter(options = {}) {
63
104
  */
64
105
  export function renderGroup(label, items = [], options = {}) {
65
106
  const normalisedItems = normaliseItems(items);
66
- const resultType = options.result ?? getHighestSeverityResult(normalisedItems.map((item) => item.result));
107
+
108
+ const resultType =
109
+ options.result ?? getHighestSeverityResult(normalisedItems.map((item) => item.result));
110
+
67
111
  const summary = options.summary ?? summariseItems(normalisedItems);
68
112
  const groupLine = renderReporterStatus(resultType, label, summary, options);
69
113
 
@@ -133,6 +177,24 @@ function append(lines, value) {
133
177
  return value;
134
178
  }
135
179
 
180
+ /**
181
+ * Combine a label and optional detail for compact phase dividers.
182
+ *
183
+ * @param {string} label
184
+ * Main label.
185
+ * @param {string} detail
186
+ * Optional supporting detail.
187
+ * @returns {string}
188
+ * Combined label.
189
+ */
190
+ function formatLabel(label, detail) {
191
+ if (!isNonEmptyString(detail)) {
192
+ return label;
193
+ }
194
+
195
+ return `${label} · ${detail}`;
196
+ }
197
+
136
198
  /**
137
199
  * Keep only rows that can render useful output.
138
200
  *
@@ -151,9 +213,10 @@ function normaliseItems(items) {
151
213
  .map((item) => ({
152
214
  detail: isNonEmptyString(item.detail) ? item.detail : "",
153
215
  label: isNonEmptyString(item.label) ? item.label : getResultToken(item.result).label,
154
- result: getResultToken(item.result) === getResultToken(resultTypes.UNKNOWN)
155
- ? resultTypes.UNKNOWN
156
- : item.result,
216
+ result:
217
+ getResultToken(item.result) === getResultToken(resultTypes.UNKNOWN)
218
+ ? resultTypes.UNKNOWN
219
+ : item.result,
157
220
  }));
158
221
  }
159
222
 
@@ -16,8 +16,8 @@ export const severityOrder = [
16
16
  resultTypes.WARNING,
17
17
  resultTypes.PARTIAL,
18
18
  resultTypes.UNKNOWN,
19
- resultTypes.SUCCESS,
20
19
  resultTypes.SKIPPED,
20
+ resultTypes.SUCCESS,
21
21
  resultTypes.INFO,
22
22
  resultTypes.UNCHANGED,
23
23
  ];