Skip to content

Commit dce0545

Browse files
authored
Merge pull request #137 from arethetypeswrong/cli-expose-renderer
Expose CLI renderer
2 parents 0224946 + fdb18ff commit dce0545

File tree

11 files changed

+100
-71
lines changed

11 files changed

+100
-71
lines changed

.changeset/nervous-carrots-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@arethetypeswrong/cli": patch
3+
---
4+
5+
Expose internal renderer and exit code API

packages/cli/package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@
2121
},
2222
"files": [
2323
"LICENSE",
24-
"dist/**/*.js"
24+
"dist/**/*.js",
25+
"dist/**/*.js.map",
26+
"dist/**/*.d.ts"
2527
],
2628
"bin": {
2729
"attw": "./dist/index.js"
2830
},
31+
"exports": {
32+
"./internal/getExitCode": "./dist/getExitCode.js",
33+
"./internal/render": "./dist/render/index.js"
34+
},
2935
"publishConfig": {
3036
"access": "public"
3137
},
@@ -42,8 +48,8 @@
4248
"@types/marked-terminal": "^3.1.3",
4349
"@types/node": "^20.2.5",
4450
"@types/semver": "^7.5.3",
45-
"ts-expose-internals": "5.3.2",
46-
"typescript": "5.3.2"
51+
"ts-expose-internals-conditionally": "1.0.0-empty.0",
52+
"typescript": "5.3.3"
4753
},
4854
"dependencies": {
4955
"@arethetypeswrong/core": "0.13.6",

packages/cli/src/getExitCode.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { CheckResult } from "@arethetypeswrong/core";
2+
import { problemFlags } from "./problemUtils.js";
3+
import type { RenderOptions } from "./render/index.js";
4+
5+
export function getExitCode(analysis: CheckResult, opts?: RenderOptions): number {
6+
if (!analysis.types) {
7+
return 0;
8+
}
9+
if (!opts?.ignoreRules) {
10+
return analysis.problems.length > 0 ? 1 : 0;
11+
}
12+
return analysis.problems.some((problem) => !opts.ignoreRules!.includes(problemFlags[problem.kind])) ? 1 : 0;
13+
}

packages/cli/src/index.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,25 @@ import { problemFlags } from "./problemUtils.js";
1414
import { readConfig } from "./readConfig.js";
1515
import * as render from "./render/index.js";
1616
import { major, minor } from "semver";
17+
import { getExitCode } from "./getExitCode.js";
1718

1819
const packageJson = createRequire(import.meta.url)("../package.json");
1920
const version = packageJson.version;
2021

21-
const formats = ["auto", "table", "table-flipped", "ascii", "json"] as const;
22+
const formats = Object.keys({
23+
auto: true,
24+
json: true,
25+
ascii: true,
26+
table: true,
27+
"table-flipped": true,
28+
} satisfies Record<render.Format, any>) as render.Format[];
2229

23-
type Format = (typeof formats)[number];
24-
25-
export interface Opts {
30+
interface Opts extends render.RenderOptions {
2631
pack?: boolean;
2732
fromNpm?: boolean;
2833
definitelyTyped?: boolean | string;
29-
summary?: boolean;
30-
emoji?: boolean;
31-
color?: boolean;
3234
quiet?: boolean;
3335
configPath?: string;
34-
ignoreRules?: string[];
35-
format: Format;
3636

3737
entrypoints?: string[];
3838
includeEntrypoints?: string[];
@@ -81,9 +81,6 @@ particularly ESM-related module resolution issues.`,
8181
.action(async (fileOrDirectory = ".") => {
8282
const opts = program.opts<Opts>();
8383
await readConfig(program, opts.configPath);
84-
opts.ignoreRules = opts.ignoreRules?.map(
85-
(value) => Object.keys(problemFlags).find((key) => problemFlags[key as core.ProblemKind] === value) as string,
86-
);
8784

8885
if (opts.quiet) {
8986
console.log = () => {};
@@ -210,21 +207,24 @@ particularly ESM-related module resolution issues.`,
210207

211208
console.log(JSON.stringify(result));
212209

213-
if (analysis.types && analysis.problems.some((problem) => !opts.ignoreRules?.includes(problem.kind)))
214-
process.exit(1);
210+
if (deleteTgz) {
211+
await unlink(deleteTgz);
212+
}
213+
214+
const exitCode = getExitCode(analysis, opts);
215+
if (exitCode) {
216+
process.exit(exitCode);
217+
}
215218

216219
return;
217220
}
218221

219222
console.log();
220223
if (analysis.types) {
221-
await render.typed(analysis, opts);
222-
223-
if (analysis.types && analysis.problems.some((problem) => !opts.ignoreRules?.includes(problem.kind))) {
224-
process.exitCode = 1;
225-
}
224+
console.log(await render.typed(analysis, opts));
225+
process.exitCode = getExitCode(analysis, opts);
226226
} else {
227-
render.untyped(analysis as core.UntypedResult);
227+
console.log(render.untyped(analysis as core.UntypedResult));
228228
}
229229

230230
if (deleteTgz) {

packages/cli/src/problemUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as core from "@arethetypeswrong/core";
22
import type { ProblemKind } from "@arethetypeswrong/core";
33

4-
export const problemFlags: Record<ProblemKind, string> = {
4+
export const problemFlags = {
55
NoResolution: "no-resolution",
66
UntypedResolution: "untyped-resolution",
77
FalseCJS: "false-cjs",
@@ -13,7 +13,7 @@ export const problemFlags: Record<ProblemKind, string> = {
1313
MissingExportEquals: "missing-export-equals",
1414
UnexpectedModuleSyntax: "unexpected-module-syntax",
1515
InternalResolutionError: "internal-resolution-error",
16-
};
16+
} as const satisfies Record<ProblemKind, string>;
1717

1818
export const resolutionKinds: Record<core.ResolutionKind, string> = {
1919
node10: "node10",

packages/cli/src/render/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,13 @@
1+
import type { problemFlags } from "../problemUtils.js";
2+
3+
export type Format = "auto" | "table" | "table-flipped" | "ascii" | "json";
4+
export interface RenderOptions {
5+
ignoreRules?: (typeof problemFlags)[keyof typeof problemFlags][];
6+
format?: Format;
7+
color?: boolean;
8+
summary?: boolean;
9+
emoji?: boolean;
10+
}
11+
112
export * from "./typed.js";
213
export * from "./untyped.js";

packages/cli/src/render/typed.ts

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,44 @@
11
import * as core from "@arethetypeswrong/core";
2+
import { filterProblems, problemAffectsEntrypoint, problemKindInfo } from "@arethetypeswrong/core/problems";
23
import { allResolutionKinds, getResolutionOption, groupProblemsByKind } from "@arethetypeswrong/core/utils";
34
import chalk from "chalk";
45
import Table, { type GenericTable, type HorizontalTableRow } from "cli-table3";
56
import { marked } from "marked";
6-
7-
import { filterProblems, problemAffectsEntrypoint, problemKindInfo } from "@arethetypeswrong/core/problems";
8-
import type { Opts } from "../index.js";
7+
import TerminalRenderer from "marked-terminal";
98
import { moduleKinds, problemFlags, resolutionKinds } from "../problemUtils.js";
109
import { asciiTable } from "./asciiTable.js";
11-
import TerminalRenderer from "marked-terminal";
10+
import type { RenderOptions } from "./index.js";
1211

13-
export async function typed(analysis: core.Analysis, opts: Opts) {
14-
const problems = analysis.problems.filter((problem) => !opts.ignoreRules || !opts.ignoreRules.includes(problem.kind));
12+
export async function typed(analysis: core.Analysis, opts: RenderOptions): Promise<string> {
13+
let output = "";
14+
const problems = analysis.problems.filter(
15+
(problem) => !opts.ignoreRules || !opts.ignoreRules.includes(problemFlags[problem.kind]),
16+
);
1517
const grouped = groupProblemsByKind(problems);
1618
const entrypoints = Object.keys(analysis.entrypoints);
1719
marked.setOptions({
1820
renderer: new TerminalRenderer(),
1921
});
2022

21-
console.log(`${analysis.packageName} v${analysis.packageVersion}`);
23+
out(`${analysis.packageName} v${analysis.packageVersion}`);
2224
if (analysis.types.kind === "@types") {
23-
console.log(`${analysis.types.packageName} v${analysis.types.packageVersion}`);
25+
out(`${analysis.types.packageName} v${analysis.types.packageVersion}`);
2426
}
25-
console.log();
27+
out();
2628
if (Object.keys(analysis.buildTools).length) {
27-
console.log("Build tools:");
28-
console.log(
29+
out("Build tools:");
30+
out(
2931
Object.entries(analysis.buildTools)
3032
.map(([tool, version]) => {
3133
return `- ${tool}@${version}`;
3234
})
3335
.join("\n"),
3436
);
35-
console.log();
37+
out();
3638
}
3739

3840
if (opts.ignoreRules && opts.ignoreRules.length) {
39-
console.log(
40-
chalk.gray(
41-
` (ignoring rules: ${opts.ignoreRules
42-
.map((rule) => `'${problemFlags[rule as core.ProblemKind]}'`)
43-
.join(", ")})\n`,
44-
),
45-
);
41+
out(chalk.gray(` (ignoring rules: ${opts.ignoreRules.map((rule) => `'${rule}'`).join(", ")})\n`));
4642
}
4743

4844
if (opts.summary) {
@@ -54,7 +50,7 @@ export async function typed(analysis: core.Analysis, opts: Opts) {
5450
return `${emoji}${description}`;
5551
});
5652

57-
console.log(summaryTexts.join("") || defaultSummary);
53+
out(summaryTexts.join("") || defaultSummary);
5854
}
5955

6056
const entrypointNames = entrypoints.map(
@@ -119,25 +115,31 @@ export async function typed(analysis: core.Analysis, opts: Opts) {
119115

120116
switch (opts.format) {
121117
case "table":
122-
console.log(table!.toString());
118+
out(table!.toString());
123119
break;
124120
case "table-flipped":
125-
console.log(flippedTable!.toString());
121+
out(flippedTable!.toString());
126122
break;
127123
case "ascii":
128-
console.log(asciiTable(table!));
124+
out(asciiTable(table!));
129125
break;
130126
case "auto":
131127
const terminalWidth = process.stdout.columns || 133; // This looks like GitHub Actions' width
132128
if (table!.width <= terminalWidth) {
133-
console.log(table!.toString());
129+
out(table!.toString());
134130
} else if (flippedTable!.width <= terminalWidth) {
135-
console.log(flippedTable!.toString());
131+
out(flippedTable!.toString());
136132
} else {
137-
console.log(asciiTable(table!));
133+
out(asciiTable(table!));
138134
}
139135
break;
140136
}
137+
138+
return output.trimEnd();
139+
140+
function out(s: string = "") {
141+
output += s + "\n";
142+
}
141143
}
142144

143145
function memo<Args extends (string | number)[], Result>(fn: (...args: Args) => Result): (...args: Args) => Result {

packages/cli/src/render/untyped.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as core from "@arethetypeswrong/core";
22

33
export function untyped(analysis: core.UntypedResult) {
4-
console.log("This package does not contain types.\nDetails: ", analysis);
4+
return "This package does not contain types.\nDetails: " + JSON.stringify(analysis, null, 2);
55
}

packages/cli/test/snapshots/[email protected]

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ $ attw [email protected] -f table-flipped
55
66
77
This package does not contain types.
8-
Details: { packageName: 'ejs', packageVersion: '3.1.9', types: false }
8+
Details: {
9+
"packageName": "ejs",
10+
"packageVersion": "3.1.9",
11+
"types": false
12+
}
913
1014
1115
```

packages/cli/tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
"compilerOptions": {
44
"module": "nodenext",
55
"rootDir": "src",
6-
"types": ["ts-expose-internals", "node"],
6+
"types": ["ts-expose-internals-conditionally", "node"],
77
"outDir": "./dist",
8-
"declarationDir": "./lib",
98
"sourceMap": true
109
},
1110
"include": ["src"],

0 commit comments

Comments
 (0)