Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/forty-islands-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@arethetypeswrong/core": minor
---

Add `options` parameter to `checkPackage` with support for customizing which entrypoints get analyzed.
5 changes: 5 additions & 0 deletions .changeset/thick-cycles-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@arethetypeswrong/cli": minor
---

Add `--entrypoints`, `--include-entrypoints`, and `--exclude-entrypoints` options to customize which entrypoints get analyzed.
55 changes: 42 additions & 13 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,31 @@ npm i -g @arethetypeswrong/cli

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.

The usage is:
The CLI can check an `npm pack`ed tarball:

```shell
npm pack
attw [options] <file-name>
attw cool-package-1.0.0.tgz
# or
attw $(npm pack)
```

Where `<file-name>` is a required positional argument (the path to a local `.tar.gz` file from `npm pack`).
or pack one in-place by specifying `--pack` and a directory:

```shell
attw --pack .
```

or check a package from npm:

```shell
attw --from-npm @arethetypeswrong/cli
```

## Configuration

`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.

Note that the `--config-path` option cannot be set from the config file :upside_down_face:

### Options

#### Help
Expand All @@ -55,11 +65,32 @@ In the CLI: `--version`, `-v`
attw --version
```

### Pack

Specify a directory to run `npm pack` in (instead of specifying a tarball filename), analyze the resulting tarball, and delete it afterwards.

```shell
attw --pack .
```

#### From NPM

Specify the name (and, optionally, version range) of a package from the NPM registry instead of a local tarball filename.

In the CLI: `--from-npm`, `-p`

```shell
attw --from-npm <package-name>
```

In the config file, `fromNpm` can be a boolean value.

#### Format

The format to print the output in. Defaults to `table`.

The available values are:

- `table`
- `table-flipped`, where the resolution kinds are the table's head, and the entry points label the table's rows
- `ascii`, for large tables where the output is clunky
Expand All @@ -73,23 +104,22 @@ attw --format <format> <file-name>

In the config file, `format` can be a string value.

#### From NPM

Treat `<file-name>` as the name (and, optionally, version) of a package from the NPM registry.
#### Entrypoints

In the CLI: `--from-npm`, `-p`
`attw` automatically discovers package entrypoints by looking at package.json `exports` and subdirectories with additional package.json files. This automatic discovery process can be overridden with the `--entrypoints` option, or altered with the `--include-entrypoints` and `--exclude-entrypoints` options:

```shell
attw --from-npm <package-name>
attw --pack . --entrypoints . one two three # Just ".", "./one", "./two", "./three"
attw --pack . --include-entrypoints added # Auto-discovered entyrpoints plus "./added"
attw --pack . --exclude-entrypoints styles.css # Auto-discovered entrypoints except "./styles.css"
```

In the config file, `fromNpm` can be a boolean value.

#### Ignore Rules

Specifies rules/problems to ignore (i.e. not raise an error for).

The available values are:

- `wildcard`
- `no-resolution`
- `untyped-resolution`
Expand Down Expand Up @@ -171,4 +201,3 @@ attw --config-path <path> <file-name>
```

Cannot be set from within the config file itself.

2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"directory": "packages/cli"
},
"files": [
"dist"
"dist/**/*.js"
],
"bin": {
"attw": "./dist/index.js"
Expand Down
46 changes: 35 additions & 11 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export interface Opts {
configPath?: string;
ignoreRules?: string[];
format: Format;

entrypoints?: string[];
includeEntrypoints?: string[];
excludeEntrypoints?: string[];
}

program
Expand All @@ -49,17 +53,28 @@ particularly ESM-related module resolution issues.`
"[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("-f, --format <format>", "specify the print format").choices(formats).default("table"))
.option("-q, --quiet", "don't print anything to STDOUT (overrides all other options)")
.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("-f, --format <format>", "Specify the print format").choices(formats).default("table"))
.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.")
.addOption(
new Option("--ignore-rules <rules...>", "specify rules to ignore").choices(Object.values(problemFlags)).default([])
new Option("--ignore-rules <rules...>", "Specify rules to ignore").choices(Object.values(problemFlags)).default([])
)
.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)")
.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 = ".") => {
const opts = program.opts<Opts>();
await readConfig(program, opts.configPath);
Expand Down Expand Up @@ -87,7 +102,12 @@ particularly ESM-related module resolution issues.`
program.error(result.error);
} else {
analysis = await core.checkPackage(
await core.createPackageFromNpm(`${result.data.name}@${result.data.version}`)
await core.createPackageFromNpm(`${result.data.name}@${result.data.version}`),
{
entrypoints: opts.entrypoints,
includeEntrypoints: opts.includeEntrypoints,
excludeEntrypoints: opts.excludeEntrypoints,
}
);
}
} catch (error) {
Expand Down Expand Up @@ -136,7 +156,11 @@ particularly ESM-related module resolution issues.`
}
const file = await readFile(fileName);
const data = new Uint8Array(file);
analysis = await core.checkPackage(await core.createPackageFromTarballData(data));
analysis = await core.checkPackage(await core.createPackageFromTarballData(data), {
entrypoints: opts.entrypoints,
includeEntrypoints: opts.includeEntrypoints,
excludeEntrypoints: opts.excludeEntrypoints,
});
} catch (error) {
handleError(error, "checking file");
}
Expand Down
44 changes: 35 additions & 9 deletions packages/cli/test/snapshots.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import fs from "fs";
import { access, readFile, writeFile } from "fs/promises";
import { execSync, type SpawnSyncReturns } from "child_process";
import assert from "node:assert";
Expand All @@ -7,6 +6,32 @@ import { after, describe, test } from "node:test";
const attw = `node ${new URL("../../dist/index.js", import.meta.url).pathname}`;
const updateSnapshots = process.env.UPDATE_SNAPSHOTS;

const tests = [
["@apollo__client-3.7.16.tgz"],
["@[email protected]"],
["@[email protected]"],
["@[email protected]"],
["[email protected]"],
["[email protected]"],
["[email protected]"],
["[email protected]", "-f table"],
["[email protected]"],
["[email protected]"],
["[email protected]", "-f ascii"],
["[email protected]"],
["[email protected]"],
["[email protected]"],
["[email protected]"],
["[email protected]"],

["[email protected]", "--entrypoints vue"],
["[email protected]", "--entrypoints . jsx-runtime"],
["[email protected]", "--exclude-entrypoints macros -f ascii"],
["[email protected]", "--include-entrypoints ./foo -f ascii"],
];

const defaultOpts = "-f table-flipped";

describe("snapshots", async () => {
const snapshotsWritten: URL[] = [];

Expand All @@ -22,27 +47,28 @@ describe("snapshots", async () => {
}
});

for (const fixture of fs.readdirSync(new URL("../../../core/test/fixtures", import.meta.url))) {
if (fixture === ".DS_Store") {
continue;
}
for (const [tarball, options] of tests) {
const fixture = tarball + (options ? ` ${options}` : "");
test(fixture, async () => {
const tarballPath = new URL(`../../../core/test/fixtures/${fixture}`, import.meta.url).pathname;
const tarballPath = new URL(`../../../core/test/fixtures/${tarball}`, import.meta.url).pathname;
let stdout;
let exitCode = 0;
try {
stdout = execSync(`${attw} ${tarballPath}`, { encoding: "utf8", env: { ...process.env, FORCE_COLOR: "0" } });
stdout = execSync(`${attw} ${tarballPath} ${options ?? defaultOpts}`, {
encoding: "utf8",
env: { ...process.env, FORCE_COLOR: "0" },
});
} catch (error) {
stdout = (error as SpawnSyncReturns<string>).stdout;
exitCode = (error as SpawnSyncReturns<string>).status ?? 1;
}
const snapshotURL = new URL(`../snapshots/${fixture}.md`, import.meta.url);
const snapshotURL = new URL(`../snapshots/${fixture.replace(/\//g, "")}.md`, import.meta.url);
// prettier-ignore
const expectedSnapshot = [
`# ${fixture}`,
"",
"```",
`$ attw ${fixture}`,
`$ attw ${tarball} ${options ?? defaultOpts}`,
"",
stdout,
"",
Expand Down
Loading