Skip to content

Commit 047f522

Browse files
authored
Merge pull request #61 from arethetypeswrong/auto-table
Make --format auto for CLI
2 parents c8d54a5 + 938a2a8 commit 047f522

21 files changed

+418
-408
lines changed

.changeset/flat-pens-rest.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@arethetypeswrong/cli": minor
3+
---
4+
5+
Automatically pick an output format that fits the terminal width (`--format auto`, the new default)

packages/cli/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,15 @@ In the config file, `fromNpm` can be a boolean value.
8787

8888
#### Format
8989

90-
The format to print the output in. Defaults to `table`.
90+
The format to print the output in. Defaults to `auto`.
9191

9292
The available values are:
9393

94-
- `table`
95-
- `table-flipped`, where the resolution kinds are the table's head, and the entry points label the table's rows
94+
- `table`, where columns are entrypoints and rows are resolution kinds
95+
- `table-flipped`, where columns are resolution kinds and rows are entrypoints
9696
- `ascii`, for large tables where the output is clunky
97-
- `raw`, outputs the raw JSON data (overriding all other rendering options)
97+
- `auto`, which picks whichever of the above best fits the terminal width
98+
- `json` outputs the raw JSON data (overriding all other rendering options)
9899

99100
In the CLI: `--format`, `-f`
100101

packages/cli/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import * as render from "./render/index.js";
1818
const packageJson = createRequire(import.meta.url)("../package.json");
1919
const version = packageJson.version;
2020

21-
const formats = ["table", "table-flipped", "ascii", "json"] as const;
21+
const formats = ["auto", "table", "table-flipped", "ascii", "json"] as const;
2222

2323
type Format = (typeof formats)[number];
2424

@@ -55,7 +55,7 @@ particularly ESM-related module resolution issues.`
5555
)
5656
.option("-P, --pack", "Run `npm pack` in the specified directory and delete the resulting .tgz file afterwards")
5757
.option("-p, --from-npm", "Read from the npm registry instead of a local file")
58-
.addOption(new Option("-f, --format <format>", "Specify the print format").choices(formats).default("table"))
58+
.addOption(new Option("-f, --format <format>", "Specify the print format").choices(formats).default("auto"))
5959
.option("-q, --quiet", "Don't print anything to STDOUT (overrides all other options)")
6060
.option(
6161
"--entrypoints <entrypoints...>",

packages/cli/src/render/tableFlipped.ts renamed to packages/cli/src/render/asciiTable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import chalk from "chalk";
22
import type { GenericTable, HorizontalTableRow } from "cli-table3";
33

4-
export function tableFlipped(table: GenericTable<HorizontalTableRow>) {
4+
export function asciiTable(table: GenericTable<HorizontalTableRow>) {
55
return table.options.head
66
.slice(1)
77
.map((entryPoint, i) => {

packages/cli/src/render/typed.ts

Lines changed: 77 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import { marked } from "marked";
77
import { filterProblems, problemAffectsEntrypoint, problemKindInfo } from "@arethetypeswrong/core/problems";
88
import type { Opts } from "../index.js";
99
import { moduleKinds, problemFlags, resolutionKinds } from "../problemUtils.js";
10-
import { tableFlipped } from "./tableFlipped.js";
10+
import { asciiTable } from "./asciiTable.js";
1111
import TerminalRenderer from "marked-terminal";
1212

1313
export async function typed(analysis: core.Analysis, opts: Opts) {
1414
const problems = analysis.problems.filter((problem) => !opts.ignoreRules || !opts.ignoreRules.includes(problem.kind));
1515
const grouped = groupProblemsByKind(problems);
16-
const subpaths = Object.keys(analysis.entrypoints);
16+
const entrypoints = Object.keys(analysis.entrypoints);
1717
marked.setOptions({
1818
// @ts-expect-error the types are wrong (haha)
1919
renderer: new TerminalRenderer(),
@@ -43,86 +43,90 @@ export async function typed(analysis: core.Analysis, opts: Opts) {
4343
console.log(summaryTexts.join("") || defaultSummary);
4444
}
4545

46-
const entrypoints = subpaths.map((s) => {
46+
const entrypointNames = entrypoints.map(
47+
(s) => `"${s === "." ? analysis.packageName : `${analysis.packageName}/${s.substring(2)}`}"`
48+
);
49+
const entrypointHeaders = entrypoints.map((s, i) => {
4750
const hasProblems = problems.some((p) => problemAffectsEntrypoint(p, s, analysis));
4851
const color = hasProblems ? "redBright" : "greenBright";
49-
50-
if (s === ".") return chalk.bold[color](`"${analysis.packageName}"`);
51-
else return chalk.bold[color](`"${analysis.packageName}/${s.substring(2)}"`);
52+
return chalk.bold[color](entrypointNames[i]);
5253
});
5354

54-
if (opts.format === "table-flipped") {
55-
const table = new Table({
56-
head: ["", ...allResolutionKinds.map((kind) => chalk.reset(resolutionKinds[kind]))],
57-
colWidths: [20, ...allResolutionKinds.map(() => 25)],
58-
});
59-
60-
subpaths.forEach((subpath, i) => {
61-
const point = entrypoints[i];
62-
let row = [point];
63-
64-
row = row.concat(
65-
allResolutionKinds.map((kind) => {
66-
const problemsForCell = groupProblemsByKind(
67-
filterProblems(problems, analysis, { entrypoint: subpath, resolutionKind: kind })
68-
);
69-
const resolution = analysis.entrypoints[subpath].resolutions[kind].resolution;
70-
const kinds = Object.keys(problemsForCell) as core.ProblemKind[];
71-
if (kinds.length) {
72-
return kinds
73-
.map(
74-
(kind) => (opts.emoji ? `${problemKindInfo[kind].emoji} ` : "") + problemKindInfo[kind].shortDescription
75-
)
76-
.join("\n");
77-
}
55+
const getCellContents = memo((entrypoint: string, resolutionKind: core.ResolutionKind) => {
56+
const problemsForCell = groupProblemsByKind(filterProblems(problems, analysis, { entrypoint, resolutionKind }));
57+
const resolution = analysis.entrypoints[entrypoint].resolutions[resolutionKind].resolution;
58+
const kinds = Object.keys(problemsForCell) as core.ProblemKind[];
59+
if (kinds.length) {
60+
return kinds
61+
.map((kind) => (opts.emoji ? `${problemKindInfo[kind].emoji} ` : "") + problemKindInfo[kind].shortDescription)
62+
.join("\n");
63+
}
64+
65+
const jsonResult = !opts.emoji ? "OK (JSON)" : "🟢 (JSON)";
66+
const moduleResult = (!opts.emoji ? "OK " : "🟢 ") + moduleKinds[resolution?.moduleKind?.detectedKind || ""];
67+
return resolution?.isJson ? jsonResult : moduleResult;
68+
});
7869

79-
const jsonResult = !opts.emoji ? "OK (JSON)" : "🟢 (JSON)";
80-
const moduleResult = (!opts.emoji ? "OK " : "🟢 ") + moduleKinds[resolution?.moduleKind?.detectedKind || ""];
81-
return resolution?.isJson ? jsonResult : moduleResult;
70+
const flippedTable =
71+
opts.format === "auto" || opts.format === "table-flipped"
72+
? new Table({
73+
head: ["", ...allResolutionKinds.map((kind) => chalk.reset(resolutionKinds[kind]))],
8274
})
83-
);
84-
85-
table.push(row);
75+
: undefined;
76+
if (flippedTable) {
77+
entrypoints.forEach((subpath, i) => {
78+
flippedTable.push([
79+
entrypointHeaders[i],
80+
...allResolutionKinds.map((resolutionKind) => getCellContents(subpath, resolutionKind)),
81+
]);
8682
});
87-
console.log(table.toString());
88-
return;
8983
}
9084

91-
const table = new Table({
92-
head: ["", ...entrypoints],
93-
colWidths: [20, ...entrypoints.map(() => 35)],
94-
}) as GenericTable<HorizontalTableRow>;
95-
96-
allResolutionKinds.forEach((kind) => {
97-
let row = [resolutionKinds[kind]];
98-
99-
row = row.concat(
100-
subpaths.map((subpath) => {
101-
const problemsForCell = groupProblemsByKind(
102-
filterProblems(problems, analysis, { entrypoint: subpath, resolutionKind: kind })
103-
);
104-
const resolution = analysis.entrypoints[subpath].resolutions[kind].resolution;
105-
const kinds = Object.keys(problemsForCell) as core.ProblemKind[];
106-
if (kinds.length) {
107-
return kinds
108-
.map(
109-
(kind) => (opts.emoji ? `${problemKindInfo[kind].emoji} ` : "") + problemKindInfo[kind].shortDescription
110-
)
111-
.join("\n");
112-
}
113-
114-
const jsonResult = !opts.emoji ? "OK (JSON)" : "🟢 (JSON)";
115-
const moduleResult = (!opts.emoji ? "OK " : "🟢 ") + moduleKinds[resolution?.moduleKind?.detectedKind || ""];
116-
return resolution?.isJson ? jsonResult : moduleResult;
117-
})
118-
);
119-
120-
table.push(row);
121-
});
85+
const table =
86+
opts.format === "auto" || !flippedTable
87+
? (new Table({
88+
head: ["", ...entrypointHeaders],
89+
}) as GenericTable<HorizontalTableRow>)
90+
: undefined;
91+
if (table) {
92+
allResolutionKinds.forEach((kind) => {
93+
table.push([resolutionKinds[kind], ...entrypoints.map((entrypoint) => getCellContents(entrypoint, kind))]);
94+
});
95+
}
12296

123-
if (opts.format === "ascii") {
124-
console.log(tableFlipped(table));
125-
} else {
126-
console.log(table.toString());
97+
switch (opts.format) {
98+
case "table":
99+
console.log(table!.toString());
100+
break;
101+
case "table-flipped":
102+
console.log(flippedTable!.toString());
103+
break;
104+
case "ascii":
105+
console.log(asciiTable(table!));
106+
break;
107+
case "auto":
108+
const terminalWidth = process.stdout.columns || 133; // This looks like GitHub Actions' width
109+
if (table!.width <= terminalWidth) {
110+
console.log(table!.toString());
111+
} else if (flippedTable!.width <= terminalWidth) {
112+
console.log(flippedTable!.toString());
113+
} else {
114+
console.log(asciiTable(table!));
115+
}
116+
break;
127117
}
128118
}
119+
120+
function memo<Args extends (string | number)[], Result>(fn: (...args: Args) => Result): (...args: Args) => Result {
121+
const cache = new Map();
122+
return (...args) => {
123+
const key = "" + args;
124+
if (cache.has(key)) {
125+
return cache.get(key);
126+
}
127+
128+
const result = fn(...args);
129+
cache.set(key, result);
130+
return result;
131+
};
132+
}

0 commit comments

Comments
 (0)