@systemfsoftware/arethetypeswrong-cli 0.18.5

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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2023 Andrew Branch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the β€œSoftware”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED β€œAS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # arethetypeswrong/cli
2
+
3
+ A CLI for [arethetypeswrong.github.io](https://arethetypeswrong.github.io/).
4
+
5
+ This project attempts to analyze npm package contents for issues with their TypeScript types, particularly ESM-related module resolution issues. The following kinds of problems can be detected in the `node10`, `node16`, and `bundler` module resolution modes:
6
+
7
+ - [πŸ’€ Resolution failed](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/NoResolution.md)
8
+ - [❌ No types](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/UntypedResolution.md)
9
+ - [🎭 Masquerading as CJS](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseCJS.md)
10
+ - [πŸ‘Ί Masquerading as ESM](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseESM.md)
11
+ - [⚠️ ESM (dynamic import only)](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/CJSResolvesToESM.md)
12
+ - [πŸ› Used fallback condition](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FallbackCondition.md)
13
+ - [🀨 CJS default export](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/CJSOnlyExportsDefault.md)
14
+ - [❗️ Incorrect default export](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseExportDefault.md)
15
+ - [❓ Missing `export =`](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/MissingExportEquals.md)
16
+ - [🚭 Unexpected module syntax](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/UnexpectedModuleSyntax.md)
17
+ - [πŸ₯΄ Internal resolution error](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/InternalResolutionError.md)
18
+ - [πŸ•΅οΈβ€β™‚οΈ Named exports](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/NamedExports.md)
19
+
20
+ ## Installation
21
+
22
+ ```shell
23
+ npm i -g @systemfsoftware/arethetypeswrong-cli
24
+ ```
25
+
26
+ <!-- Or, using `npx`: -->
27
+ <!---->
28
+ <!-- ```shell -->
29
+ <!-- npx attw -->
30
+ <!-- ``` -->
31
+
32
+ ## Usage
33
+
34
+ The `attw` command acts very similarly to [arethetypeswrong.github.io](https://arethetypeswrong.github.io/), with some additional features that are useful for command line usage.
35
+
36
+ The CLI can check an `npm pack`ed tarball:
37
+
38
+ ```shell
39
+ npm pack
40
+ attw cool-package-1.0.0.tgz
41
+ # or
42
+ attw $(npm pack)
43
+ ```
44
+
45
+ or pack one in-place by specifying `--pack` and a directory:
46
+
47
+ ```shell
48
+ attw --pack .
49
+ ```
50
+
51
+ or check a package from npm:
52
+
53
+ ```shell
54
+ attw --from-npm @systemfsoftware/arethetypeswrong-cli
55
+ ```
56
+
57
+ You can also use `attw` without installing globally by using `npx`. Pack one in-place by specifying `--pack` and a directory:
58
+
59
+ ```
60
+ npx --yes @systemfsoftware/arethetypeswrong-cli --pack .
61
+ ```
62
+
63
+ or check a package from npm:
64
+
65
+ ```
66
+ npx --yes @systemfsoftware/arethetypeswrong-cli --from-npm @systemfsoftware/arethetypeswrong-cli
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ `attw` supports a JSON config file (by default named `.attw.json`) which allows you to pre-set the command line arguments. The options are a one-to-one mapping of the command line flags, changed to camelCase, and are all documented in their relevant `Options` section below.
72
+
73
+ ### Options
74
+
75
+ #### Help
76
+
77
+ Show help information and exit.
78
+
79
+ In the CLI: `--help`, `-h`
80
+
81
+ ```shell
82
+ attw --help
83
+ ```
84
+
85
+ #### Version
86
+
87
+ Print the current version of `attw` and exit.
88
+
89
+ In the CLI: `--version`, `-v`
90
+
91
+ ```shell
92
+ attw --version
93
+ ```
94
+
95
+ ### Pack
96
+
97
+ Specify a directory to run `npm pack` in (instead of specifying a tarball filename), analyze the resulting tarball, and delete it afterwards.
98
+
99
+ ```shell
100
+ attw --pack .
101
+ ```
102
+
103
+ Please note that the `--pack` option does not support package managers other than npm at this time.
104
+ Therefore, if you use pnpm or yarn, you should generate the tarball yourself first (using `pnpm pack`/`yarn pack`) and run then run `attw <packed-tarball-name>`, rather than using the `--pack` option.
105
+
106
+ #### From NPM
107
+
108
+ Specify the name (and, optionally, version or SemVer range) of a package from the NPM registry instead of a local tarball filename.
109
+
110
+ In the CLI: `--from-npm`, `-p`
111
+
112
+ ```shell
113
+ attw --from-npm <package-name>
114
+ ```
115
+
116
+ In the config file, `fromNpm` can be a boolean value.
117
+
118
+ #### DefinitelyTyped
119
+
120
+ When a package does not contain types, specifies the version or SemVer range of the DefinitelyTyped `@types` package to use. Defaults to inferring the best version match from the implementation package version.
121
+
122
+ In the CLI: `--definitely-typed`, `--no-definitely-typed`
123
+
124
+ ```shell
125
+ attw -p <package-name> --definitely-typed <version>
126
+ attw -p <package-name> --no-definitely-typed
127
+ ```
128
+
129
+ #### Format
130
+
131
+ The format to print the output in. Defaults to `auto`.
132
+
133
+ The available values are:
134
+
135
+ - `table`, where columns are entrypoints and rows are resolution kinds
136
+ - `table-flipped`, where columns are resolution kinds and rows are entrypoints
137
+ - `ascii`, for large tables where the output is clunky
138
+ - `auto`, which picks whichever of the above best fits the terminal width
139
+ - `json` outputs the raw JSON data (overriding all other rendering options)
140
+
141
+ In the CLI: `--format`, `-f`
142
+
143
+ ```shell
144
+ attw --format <format> <file-name>
145
+ ```
146
+
147
+ In the config file, `format` can be a string value.
148
+
149
+ #### Entrypoints
150
+
151
+ `attw` automatically discovers package entrypoints by looking at package.json `exports` and subdirectories with additional package.json files. In a package lacking `exports`, providing the `--entrypoints-legacy` option will include all published code files. This automatic discovery process can be overridden with the `--entrypoints` option, or altered with the `--include-entrypoints` and `--exclude-entrypoints` options:
152
+
153
+ ```shell
154
+ attw --pack . --entrypoints . one two three # Just ".", "./one", "./two", "./three"
155
+ attw --pack . --include-entrypoints added # Auto-discovered entrypoints plus "./added"
156
+ attw --pack . --exclude-entrypoints styles.css # Auto-discovered entrypoints except "./styles.css"
157
+ attw --pack . --entrypoints-legacy # All published code files
158
+ ```
159
+
160
+ #### Profiles
161
+
162
+ Profiles select a set of resolution modes to require/ignore. All are evaluated but failures outside of those required are ignored.
163
+
164
+ The available profiles are:
165
+
166
+ - `strict` - requires all resolutions
167
+ - `node16` - ignores node10 resolution failures
168
+ - `esm-only` - ignores CJS resolution failures
169
+
170
+ In the CLI: `--profile`
171
+
172
+ ```shell
173
+ attw <file-name> --profile <profile>
174
+ ```
175
+
176
+ In the config file, `profile` can be a string value.
177
+
178
+ #### Ignore Rules
179
+
180
+ Specifies rules/problems to ignore (i.e. not raise an error for).
181
+
182
+ The available values are:
183
+
184
+ - `no-resolution`
185
+ - `untyped-resolution`
186
+ - `false-cjs`
187
+ - `false-esm`
188
+ - `cjs-resolves-to-esm`
189
+ - `fallback-condition`
190
+ - `cjs-only-exports-default`
191
+ - `false-export-default`
192
+ - `unexpected-module-syntax`
193
+ - `missing-export-equals`
194
+ - `internal-resolution-error`
195
+ - `named-exports`
196
+
197
+ In the CLI: `--ignore-rules`
198
+
199
+ ```shell
200
+ attw <file-name> --ignore-rules <rules...>
201
+ ```
202
+
203
+ In the config file, `ignoreRules` can be an array of strings.
204
+
205
+ #### Summary/No Summary
206
+
207
+ Whether to display a summary of what the different errors/problems mean. Defaults to showing the summary (`--summary`).
208
+
209
+ In the CLI: `--summary`/`--no-summary`
210
+
211
+ ```shell
212
+ attw --summary/--no-summary <file-name>
213
+ ```
214
+
215
+ In the config file, `summary` can be a boolean value.
216
+
217
+ #### Emoji/No Emoji
218
+
219
+ Whether to print the information with emojis. Defaults to printing with emojis (`--emoji`).
220
+
221
+ In the CLI: `--emoji`/`--no-emoji`
222
+
223
+ ```shell
224
+ attw --emoji/--no-emoji <file-name>
225
+ ```
226
+
227
+ In the config file, `emoji` can be a boolean value.
228
+
229
+ #### Color/No Color
230
+
231
+ Whether to print with colors. Defaults to printing with colors (`--color`).
232
+
233
+ The `FORCE_COLOR` env variable is also available for use (set is to `0` or `1`).
234
+
235
+ In the CLI: `--color`/`--no-color`
236
+
237
+ ```shell
238
+ attw --color/--no-color <file-name>
239
+ ```
240
+
241
+ In the config file, `color` can be a boolean value.
242
+
243
+ #### Quiet
244
+
245
+ When set, nothing will be printed to STDOUT.
246
+
247
+ In the CLI: `--quiet`, `-q`
248
+
249
+ ```shell
250
+ attw --quiet <file-name>
251
+ ```
252
+
253
+ In the config file, `quiet` can be a boolean value.
254
+
255
+ #### Config Path
256
+
257
+ The path to the config file. Defaults to `./.attw.json`.
258
+
259
+ In the CLI: `--config-path <path>`
260
+
261
+ ```shell
262
+ attw --config-path <path> <file-name>
263
+ ```
264
+
265
+ Cannot be set from within the config file itself.
@@ -0,0 +1,6 @@
1
+ import { n as RenderOptions } from "./index-DM-Vgonq.js";
2
+ import { CheckResult } from "@systemfsoftware/arethetypeswrong-core";
3
+ //#region src/getExitCode.d.ts
4
+ declare function getExitCode(analysis: CheckResult, opts?: RenderOptions): number;
5
+ //#endregion
6
+ export { getExitCode };
@@ -0,0 +1,14 @@
1
+ import { n as problemFlags } from "./problemUtils-DrDcGcsf.mjs";
2
+ //#region src/getExitCode.ts
3
+ function getExitCode(analysis, opts) {
4
+ if (!analysis.types) return 0;
5
+ const ignoreRules = opts?.ignoreRules ?? [];
6
+ const ignoreResolutions = opts?.ignoreResolutions ?? [];
7
+ return analysis.problems.some((problem) => {
8
+ const notRuleIgnored = !ignoreRules.includes(problemFlags[problem.kind]);
9
+ const notResolutionIgnored = "resolutionKind" in problem ? !ignoreResolutions.includes(problem.resolutionKind) : true;
10
+ return notRuleIgnored && notResolutionIgnored;
11
+ }) ? 1 : 0;
12
+ }
13
+ //#endregion
14
+ export { getExitCode };
@@ -0,0 +1,36 @@
1
+ import * as core from "@systemfsoftware/arethetypeswrong-core";
2
+ //#region src/problemUtils.d.ts
3
+ declare const problemFlags: {
4
+ readonly NoResolution: "no-resolution";
5
+ readonly UntypedResolution: "untyped-resolution";
6
+ readonly FalseCJS: "false-cjs";
7
+ readonly FalseESM: "false-esm";
8
+ readonly CJSResolvesToESM: "cjs-resolves-to-esm";
9
+ readonly FallbackCondition: "fallback-condition";
10
+ readonly CJSOnlyExportsDefault: "cjs-only-exports-default";
11
+ readonly NamedExports: "named-exports";
12
+ readonly FalseExportDefault: "false-export-default";
13
+ readonly MissingExportEquals: "missing-export-equals";
14
+ readonly UnexpectedModuleSyntax: "unexpected-module-syntax";
15
+ readonly InternalResolutionError: "internal-resolution-error";
16
+ };
17
+ declare const resolutionKinds: Record<core.ResolutionKind, string>;
18
+ //#endregion
19
+ //#region src/render/typed.d.ts
20
+ declare function typed(analysis: core.Analysis, { emoji, summary, format, ignoreRules, ignoreResolutions }: RenderOptions): Promise<string>;
21
+ //#endregion
22
+ //#region src/render/untyped.d.ts
23
+ declare function untyped(analysis: core.UntypedResult): string;
24
+ //#endregion
25
+ //#region src/render/index.d.ts
26
+ type Format = 'auto' | 'table' | 'table-flipped' | 'ascii' | 'json';
27
+ interface RenderOptions {
28
+ ignoreRules?: (typeof problemFlags)[keyof typeof problemFlags][];
29
+ ignoreResolutions?: (keyof typeof resolutionKinds)[];
30
+ format?: Format;
31
+ color?: boolean;
32
+ summary?: boolean;
33
+ emoji?: boolean;
34
+ }
35
+ //#endregion
36
+ export { typed as i, RenderOptions as n, untyped as r, Format as t };
@@ -0,0 +1 @@
1
+ export { };
package/dist/index.mjs ADDED
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import { n as problemFlags } from "./problemUtils-DrDcGcsf.mjs";
3
+ import { getExitCode } from "./getExitCode.mjs";
4
+ import { n as typed, t as untyped } from "./render-CTRBsIjQ.mjs";
5
+ import * as core from "@systemfsoftware/arethetypeswrong-core";
6
+ import { groupProblemsByKind, parsePackageSpec } from "@systemfsoftware/arethetypeswrong-core/utils";
7
+ import { versions } from "@systemfsoftware/arethetypeswrong-core/versions";
8
+ import chalk from "chalk";
9
+ import { execSync } from "child_process";
10
+ import { Option, program } from "commander";
11
+ import { readFile, stat, unlink } from "fs/promises";
12
+ import { createRequire } from "module";
13
+ import path from "path";
14
+ import readline from "readline";
15
+ import { major, minor } from "semver";
16
+ import { Writable } from "stream";
17
+ import { Readable } from "node:stream";
18
+ //#region src/profiles.ts
19
+ const profiles = {
20
+ strict: { ignoreResolutions: [] },
21
+ node16: { ignoreResolutions: ["node10"] },
22
+ "esm-only": { ignoreResolutions: ["node10", "node16-cjs"] }
23
+ };
24
+ /**
25
+ * Merges the profile with the provided options
26
+ *
27
+ * @param profileKey - name of the profile to apply
28
+ * @param opts - options to apply the profile to
29
+ */
30
+ function applyProfile(profileKey, opts) {
31
+ const profile = profiles[profileKey];
32
+ opts.ignoreResolutions = (opts.ignoreResolutions ?? []).concat(profile.ignoreResolutions);
33
+ }
34
+ //#endregion
35
+ //#region src/readConfig.ts
36
+ async function readConfig(program, alternate = ".attw.json") {
37
+ try {
38
+ const results = await readFile(alternate, "utf8");
39
+ if (!results) return;
40
+ const opts = JSON.parse(results);
41
+ for (let key in opts) {
42
+ if (key === "configPath") program.error(`cannot set "configPath" within ${alternate}`, { code: "INVALID_OPTION" });
43
+ const value = opts[key];
44
+ if (key === "ignoreRules") {
45
+ if (!Array.isArray(value)) program.error(`error: config option 'ignoreRules' should be an array.`);
46
+ const invalid = value.find((rule) => !Object.values(problemFlags).includes(rule));
47
+ if (invalid) program.error(`error: config option 'ignoreRules' argument '${invalid}' is invalid. Allowed choices are ${Object.values(problemFlags).join(", ")}.`);
48
+ }
49
+ if (key === "profile") {
50
+ if (typeof value !== "string") program.error(`error: config option 'profile' should be a string.`);
51
+ if (!(value in profiles)) program.error(`error: config option 'profile' argument '${value}' is invalid. Allowed choices are ${Object.keys(profiles).join(", ")}.`);
52
+ }
53
+ if (Array.isArray(value)) {
54
+ const opt = program.getOptionValue(key);
55
+ if (Array.isArray(opt)) {
56
+ program.setOptionValueWithSource(key, [...opt, ...value], "config");
57
+ continue;
58
+ }
59
+ }
60
+ if (key !== "help" && key !== "version") program.setOptionValueWithSource(key, opts[key], "config");
61
+ }
62
+ } catch (error) {
63
+ if (!error || typeof error !== "object" || !("code" in error) || !("message" in error)) program.error("unknown error while reading config file", { code: "UNKNOWN" });
64
+ else if (error.code !== "ENOENT") program.error(`error while reading config file:\n${error.message}`);
65
+ }
66
+ }
67
+ //#endregion
68
+ //#region src/write.ts
69
+ async function write(data, out) {
70
+ return new Promise((resolve, reject) => {
71
+ const stream = new Readable({ read() {
72
+ this.push(data);
73
+ this.push("\n");
74
+ this.push(null);
75
+ } });
76
+ stream.on("data", (chunk) => {
77
+ out.write(chunk);
78
+ });
79
+ stream.on("end", () => {
80
+ resolve();
81
+ });
82
+ out.on("error", (err) => {
83
+ reject(err);
84
+ });
85
+ });
86
+ }
87
+ //#endregion
88
+ //#region src/index.ts
89
+ const version = createRequire(import.meta.url)("../package.json").version;
90
+ const formats = Object.keys({
91
+ auto: true,
92
+ json: true,
93
+ ascii: true,
94
+ table: true,
95
+ "table-flipped": true
96
+ });
97
+ program.addHelpText("before", `ATTW CLI (v${version})\n`).addHelpText("after", `\ncore: v${versions.core}, typescript: v${versions.typescript}`).version(`cli: v${version}\ncore: v${versions.core}\ntypescript: v${versions.typescript}`).name("attw").description(`${chalk.bold.blue("Are the Types Wrong?")} attempts to analyze npm package contents for issues with their TypeScript types,
98
+ particularly ESM-related module resolution issues.`).argument("[file-directory-or-package-spec]", "the packed .tgz, or directory containing package.json with --pack, or package spec with --from-npm").option("-P, --pack", "Run `npm pack` in the specified directory and delete the resulting .tgz file afterwards").option("-p, --from-npm", "Read from the npm registry instead of a local file").addOption(new Option("--definitely-typed [version]", "Specify the version range of @types to use").default(true)).option("--no-definitely-typed", "Don't include @types").addOption(new Option("-f, --format <format>", "Specify the print format").choices(formats).default("auto")).option("-q, --quiet", "Don't print anything to STDOUT (overrides all other options)").option("--entrypoints <entrypoints...>", "Specify an exhaustive list of entrypoints to check. The package root is `\".\" Specifying this option disables automatic entrypoint discovery, and overrides the `--include-entrypoints` and `--exclude-entrypoints` options.").option("--include-entrypoints <entrypoints...>", "Specify entrypoints to check in addition to automatically discovered ones.").option("--exclude-entrypoints <entrypoints...>", "Specify entrypoints to exclude from checking.").option("--entrypoints-legacy", "In packages without the `exports` field, every file is an entry point. Specifying this option only takes effect when no entrypoints are automatically detected, or explicitly provided with other options.").addOption(new Option("--ignore-rules <rules...>", "Specify rules to ignore").choices(Object.values(problemFlags)).default([])).addOption(new Option("--profile <profile>", "Specify analysis profile").choices(Object.keys(profiles)).default("strict")).option("--summary, --no-summary", "Whether to print summary information about the different errors").option("--emoji, --no-emoji", "Whether to use any emojis").option("--color, --no-color", "Whether to use any colors (the FORCE_COLOR env variable is also available)").option("--config-path <path>", "Path to config file (default: ./.attw.json)").action(async (fileOrDirectory = ".") => {
99
+ const opts = program.opts();
100
+ await readConfig(program, opts.configPath);
101
+ if (opts.profile) applyProfile(opts.profile, opts);
102
+ let out = process.stdout;
103
+ if (opts.quiet) out = new class extends Writable {
104
+ _write(_chunk, _encoding, callback) {
105
+ callback();
106
+ }
107
+ }();
108
+ if (!opts.color) process.env.FORCE_COLOR = "0";
109
+ let analysis;
110
+ let deleteTgz;
111
+ const dtIsPath = typeof opts.definitelyTyped === "string" && (opts.definitelyTyped.includes("/") || opts.definitelyTyped.includes("\\") || opts.definitelyTyped.endsWith(".tgz") || opts.definitelyTyped.endsWith(".tar.gz"));
112
+ if (opts.fromNpm) {
113
+ if (opts.pack) program.error("--pack and --from-npm cannot be used together");
114
+ try {
115
+ const result = parsePackageSpec(fileOrDirectory);
116
+ if (result.status === "error") program.error(result.error);
117
+ else {
118
+ let pkg;
119
+ if (dtIsPath) {
120
+ const dtPackage = core.createPackageFromTarballData(new Uint8Array(await readFile(opts.definitelyTyped)));
121
+ const pkgVersion = result.data.versionKind === "none" ? `${major(dtPackage.packageVersion)}.${minor(dtPackage.packageVersion)}` : result.data.version;
122
+ pkg = (await core.createPackageFromNpm(`${result.data.name}@${pkgVersion}`)).mergedWithTypes(dtPackage);
123
+ } else pkg = await core.createPackageFromNpm(`${result.data.name}@${result.data.version}`, { definitelyTyped: opts.definitelyTyped });
124
+ analysis = await core.checkPackage(pkg, {
125
+ entrypoints: opts.entrypoints,
126
+ includeEntrypoints: opts.includeEntrypoints,
127
+ excludeEntrypoints: opts.excludeEntrypoints,
128
+ entrypointsLegacy: opts.entrypointsLegacy
129
+ });
130
+ }
131
+ } catch (error) {
132
+ if (error instanceof Error && "code" in error) program.error(`error while fetching package:\n${error.message}`, { code: "" + error.code });
133
+ handleError(error, "checking package");
134
+ }
135
+ } else try {
136
+ let fileName = fileOrDirectory;
137
+ if (await stat(fileOrDirectory).then((stat) => !stat.isFile()).catch(() => false)) {
138
+ if (!await stat(path.join(fileOrDirectory, "package.json")).catch(() => false)) program.error(`Specified directory must contain a package.json. No package.json found in ${path.resolve(fileOrDirectory)}.`);
139
+ if (!opts.pack) {
140
+ if (!process.stdout.isTTY) program.error("Specifying a directory requires the --pack option to confirm that running `npm pack` is ok.");
141
+ const rl = readline.createInterface(process.stdin, process.stdout);
142
+ const answer = await new Promise((resolve) => {
143
+ rl.question(`Run \`npm pack\`? (Pass -P/--pack to skip) (Y/n) `, resolve);
144
+ });
145
+ rl.close();
146
+ if (answer.trim() && !answer.trim().toLowerCase().startsWith("y")) process.exit(1);
147
+ }
148
+ const manifest = JSON.parse(await readFile(path.join(fileOrDirectory, "package.json"), { encoding: "utf8" }));
149
+ fileName = deleteTgz = path.join(fileOrDirectory, `${manifest.name.replace("@", "").replace("/", "-")}-${manifest.version}.tgz`);
150
+ execSync("npm pack", {
151
+ cwd: fileOrDirectory,
152
+ encoding: "utf8",
153
+ stdio: "ignore"
154
+ });
155
+ }
156
+ const file = await readFile(fileName);
157
+ const data = new Uint8Array(file);
158
+ const pkg = dtIsPath ? core.createPackageFromTarballData(data).mergedWithTypes(core.createPackageFromTarballData(new Uint8Array(await readFile(opts.definitelyTyped)))) : core.createPackageFromTarballData(data);
159
+ analysis = await core.checkPackage(pkg, {
160
+ entrypoints: opts.entrypoints,
161
+ includeEntrypoints: opts.includeEntrypoints,
162
+ excludeEntrypoints: opts.excludeEntrypoints,
163
+ entrypointsLegacy: opts.entrypointsLegacy
164
+ });
165
+ } catch (error) {
166
+ handleError(error, "checking file");
167
+ }
168
+ if (opts.format === "json") {
169
+ const result = { analysis };
170
+ if (analysis.types) result.problems = groupProblemsByKind(analysis.problems);
171
+ await write(JSON.stringify(result, void 0, 2), out);
172
+ if (deleteTgz) await unlink(deleteTgz);
173
+ const exitCode = getExitCode(analysis, opts);
174
+ if (exitCode) process.exit(exitCode);
175
+ return;
176
+ }
177
+ await write("", out);
178
+ if (analysis.types) {
179
+ await write(await typed(analysis, opts), out);
180
+ process.exitCode = getExitCode(analysis, opts);
181
+ } else await write(untyped(analysis), out);
182
+ if (deleteTgz) await unlink(deleteTgz);
183
+ });
184
+ program.parse(process.argv);
185
+ function handleError(error, title) {
186
+ if (error && typeof error === "object" && "message" in error) program.error(`error while ${title}:\n${error.message}`, {
187
+ exitCode: 3,
188
+ code: "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN"
189
+ });
190
+ program.error(`unknown error while ${title}`, {
191
+ code: "UNKNOWN",
192
+ exitCode: 3
193
+ });
194
+ }
195
+ process.on("unhandledRejection", (error) => {
196
+ handleError(error, "checking package");
197
+ });
198
+ //#endregion
199
+ export {};
@@ -0,0 +1,29 @@
1
+ import "@systemfsoftware/arethetypeswrong-core";
2
+ //#region src/problemUtils.ts
3
+ const problemFlags = {
4
+ NoResolution: "no-resolution",
5
+ UntypedResolution: "untyped-resolution",
6
+ FalseCJS: "false-cjs",
7
+ FalseESM: "false-esm",
8
+ CJSResolvesToESM: "cjs-resolves-to-esm",
9
+ FallbackCondition: "fallback-condition",
10
+ CJSOnlyExportsDefault: "cjs-only-exports-default",
11
+ NamedExports: "named-exports",
12
+ FalseExportDefault: "false-export-default",
13
+ MissingExportEquals: "missing-export-equals",
14
+ UnexpectedModuleSyntax: "unexpected-module-syntax",
15
+ InternalResolutionError: "internal-resolution-error"
16
+ };
17
+ const resolutionKinds = {
18
+ node10: "node10",
19
+ "node16-cjs": "node16 (from CJS)",
20
+ "node16-esm": "node16 (from ESM)",
21
+ bundler: "bundler"
22
+ };
23
+ const moduleKinds = {
24
+ 1: "(CJS)",
25
+ 99: "(ESM)",
26
+ "": ""
27
+ };
28
+ //#endregion
29
+ export { problemFlags as n, resolutionKinds as r, moduleKinds as t };
@@ -0,0 +1,2 @@
1
+ import { i as typed, n as RenderOptions, r as untyped, t as Format } from "../index-DM-Vgonq.js";
2
+ export { Format, RenderOptions, typed, untyped };
@@ -0,0 +1,2 @@
1
+ import { n as typed, t as untyped } from "../render-CTRBsIjQ.mjs";
2
+ export { typed, untyped };
@@ -0,0 +1,118 @@
1
+ import { n as problemFlags, r as resolutionKinds, t as moduleKinds } from "./problemUtils-DrDcGcsf.mjs";
2
+ import "@systemfsoftware/arethetypeswrong-core";
3
+ import { allResolutionKinds, getResolutionOption, groupProblemsByKind } from "@systemfsoftware/arethetypeswrong-core/utils";
4
+ import chalk from "chalk";
5
+ import { filterProblems, problemAffectsEntrypoint, problemAffectsResolutionKind, problemKindInfo } from "@systemfsoftware/arethetypeswrong-core/problems";
6
+ import Table from "cli-table3";
7
+ import { marked } from "marked";
8
+ import TerminalRenderer from "marked-terminal";
9
+ //#region src/render/asciiTable.ts
10
+ function asciiTable(table) {
11
+ return table.options.head.slice(1).map((entryPoint, i) => {
12
+ const keyValuePairs = table.reduce((acc, cur) => {
13
+ return acc + `${cur[0]?.toString()}: ${cur[i + 1]?.toString()}\n`;
14
+ }, "");
15
+ return `${chalk.bold.blue(entryPoint)}
16
+
17
+ ${keyValuePairs}
18
+ ***********************************`;
19
+ }).join("\n\n");
20
+ }
21
+ //#endregion
22
+ //#region src/render/typed.ts
23
+ async function typed(analysis, { emoji = true, summary = true, format = "auto", ignoreRules = [], ignoreResolutions = [] }) {
24
+ let output = "";
25
+ const problems = analysis.problems.filter((problem) => !ignoreRules || !ignoreRules.includes(problemFlags[problem.kind]));
26
+ const requiredResolutions = allResolutionKinds.filter((kind) => !ignoreResolutions.includes(kind));
27
+ const ignoredResolutions = allResolutionKinds.filter((kind) => ignoreResolutions.includes(kind));
28
+ const resolutions = requiredResolutions.concat(ignoredResolutions);
29
+ const entrypoints = Object.keys(analysis.entrypoints);
30
+ marked.setOptions({ renderer: new TerminalRenderer() });
31
+ out(`${analysis.packageName} v${analysis.packageVersion}`);
32
+ if (analysis.types.kind === "@types") out(`${analysis.types.packageName} v${analysis.types.packageVersion}`);
33
+ out();
34
+ if (Object.keys(analysis.buildTools).length) {
35
+ out("Build tools:");
36
+ out(Object.entries(analysis.buildTools).map(([tool, version]) => {
37
+ return `- ${tool}@${version}`;
38
+ }).join("\n"));
39
+ out();
40
+ }
41
+ if (ignoreRules && ignoreRules.length) out(chalk.gray(` (ignoring rules: ${ignoreRules.map((rule) => `'${rule}'`).join(", ")})\n`));
42
+ if (ignoreResolutions && ignoreResolutions.length) out(chalk.gray(` (ignoring resolutions: ${ignoreResolutions.map((resolution) => `'${resolution}'`).join(", ")})\n`));
43
+ if (summary) {
44
+ const defaultSummary = marked(!emoji ? " No problems found" : " No problems found 🌟");
45
+ const grouped = groupProblemsByKind(problems);
46
+ out(Object.entries(grouped).map(([kind, kindProblems]) => {
47
+ const info = problemKindInfo[kind];
48
+ const affectsRequiredResolution = kindProblems.some((p) => requiredResolutions.some((r) => problemAffectsResolutionKind(p, r, analysis)));
49
+ const description = marked(`${info.description}${info.details ? ` Use \`-f json\` to see ${info.details}.` : ""} ${info.docsUrl}`);
50
+ return `${affectsRequiredResolution ? "" : "(ignored per resolution) "}${emoji ? `${info.emoji} ` : ""}${description}`;
51
+ }).join("") || defaultSummary);
52
+ }
53
+ const entrypointNames = entrypoints.map((s) => `"${s === "." ? analysis.packageName : `${analysis.packageName}/${s.substring(2)}`}"`);
54
+ const entrypointHeaders = entrypoints.map((s, i) => {
55
+ const color = problems.some((p) => problemAffectsEntrypoint(p, s, analysis)) ? "redBright" : "greenBright";
56
+ return chalk.bold[color](entrypointNames[i]);
57
+ });
58
+ const getCellContents = memo((subpath, resolutionKind) => {
59
+ const ignoredPrefix = ignoreResolutions.includes(resolutionKind) ? "(ignored) " : "";
60
+ const problemsForCell = groupProblemsByKind(filterProblems(problems, analysis, {
61
+ entrypoint: subpath,
62
+ resolutionKind
63
+ }));
64
+ const entrypoint = analysis.entrypoints[subpath].resolutions[resolutionKind];
65
+ const resolution = entrypoint.resolution;
66
+ const kinds = Object.keys(problemsForCell);
67
+ if (kinds.length) return kinds.map((kind) => ignoredPrefix + (emoji ? `${problemKindInfo[kind].emoji} ` : "") + problemKindInfo[kind].shortDescription).join("\n");
68
+ const jsonResult = !emoji ? "OK (JSON)" : "🟒 (JSON)";
69
+ const moduleResult = entrypoint.isWildcard ? "(wildcard)" : (!emoji ? "OK " : "🟒 ") + moduleKinds[analysis.programInfo[getResolutionOption(resolutionKind)].moduleKinds?.[resolution?.fileName ?? ""]?.detectedKind || ""];
70
+ return ignoredPrefix + (resolution?.isJson ? jsonResult : moduleResult);
71
+ });
72
+ const flippedTable = format === "auto" || format === "table-flipped" ? new Table({ head: ["", ...resolutions.map((kind) => chalk.reset(resolutionKinds[kind] + (ignoreResolutions.includes(kind) ? " (ignored)" : "")))] }) : void 0;
73
+ if (flippedTable) entrypoints.forEach((subpath, i) => {
74
+ flippedTable.push([entrypointHeaders[i], ...resolutions.map((resolutionKind) => getCellContents(subpath, resolutionKind))]);
75
+ });
76
+ const table = format === "auto" || !flippedTable ? new Table({ head: ["", ...entrypointHeaders] }) : void 0;
77
+ if (table) resolutions.forEach((kind) => {
78
+ table.push([resolutionKinds[kind], ...entrypoints.map((entrypoint) => getCellContents(entrypoint, kind))]);
79
+ });
80
+ switch (format) {
81
+ case "table":
82
+ out(table.toString());
83
+ break;
84
+ case "table-flipped":
85
+ out(flippedTable.toString());
86
+ break;
87
+ case "ascii":
88
+ out(asciiTable(table));
89
+ break;
90
+ case "auto":
91
+ const terminalWidth = process.stdout.columns || 133;
92
+ if (table.width <= terminalWidth) out(table.toString());
93
+ else if (flippedTable.width <= terminalWidth) out(flippedTable.toString());
94
+ else out(asciiTable(table));
95
+ break;
96
+ }
97
+ return output.trimEnd();
98
+ function out(s = "") {
99
+ output += s + "\n";
100
+ }
101
+ }
102
+ function memo(fn) {
103
+ const cache = /* @__PURE__ */ new Map();
104
+ return (...args) => {
105
+ const key = "" + args;
106
+ if (cache.has(key)) return cache.get(key);
107
+ const result = fn(...args);
108
+ cache.set(key, result);
109
+ return result;
110
+ };
111
+ }
112
+ //#endregion
113
+ //#region src/render/untyped.ts
114
+ function untyped(analysis) {
115
+ return "This package does not contain types.\nDetails: " + JSON.stringify(analysis, null, 2);
116
+ }
117
+ //#endregion
118
+ export { typed as n, untyped as t };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@systemfsoftware/arethetypeswrong-cli",
3
+ "version": "0.18.5",
4
+ "description": "A CLI tool for arethetypeswrong.github.io β€” forked under systemfsoftware",
5
+ "author": "Andrew Branch & ej-shafran (forked by systemfsoftware)",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/systemfsoftware/systemfsoftware.git",
10
+ "directory": "packages/arethetypeswrong/cli"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "LICENSE"
15
+ ],
16
+ "bin": {
17
+ "attw": "./dist/index.mjs"
18
+ },
19
+ "exports": {
20
+ "./package.json": "./package.json",
21
+ "./internal/getExitCode": "./dist/getExitCode.mjs",
22
+ "./internal/render": "./dist/render/index.mjs"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "type": "module",
28
+ "dependencies": {
29
+ "chalk": "^4.1.2",
30
+ "cli-table3": "^0.6.3",
31
+ "commander": "^10.0.1",
32
+ "marked": "^9.1.2",
33
+ "marked-terminal": "^7.1.0",
34
+ "semver": "^7.5.4",
35
+ "@systemfsoftware/arethetypeswrong-core": "0.18.5"
36
+ },
37
+ "devDependencies": {
38
+ "@types/marked": "^5.0.0",
39
+ "@types/marked-terminal": "^3.1.3",
40
+ "@types/node": "^24",
41
+ "@types/semver": "^7.5.3",
42
+ "rimraf": "^6.1.3",
43
+ "ts-expose-internals": "^5.6.0",
44
+ "tsdown": "^0.22.9",
45
+ "vite-tsconfig-paths": "^6.1.1",
46
+ "vitest": "^4",
47
+ "@systemfsoftware/tsconfig": "^1.0.0",
48
+ "@systemfsoftware/vitest-config": "^0.1.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=22"
52
+ },
53
+ "scripts": {
54
+ "build": "tsdown",
55
+ "clean": "rimraf dist",
56
+ "typecheck": "tsc --noEmit --incremental",
57
+ "format": "dprint fmt",
58
+ "test": "vitest run",
59
+ "test:run": "vitest run --coverage"
60
+ }
61
+ }