Skip to content

Commit 12014d8

Browse files
authored
Add support for npm packing a directory (#49)
* Add support for npm packing a directory * Default to current directory * Add changeset
1 parent 5a61640 commit 12014d8

File tree

2 files changed

+63
-10
lines changed

2 files changed

+63
-10
lines changed

.changeset/good-mice-whisper.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+
Add --pack flag to support running `npm pack` in a directory and deleting the file afterwards

packages/cli/src/index.ts

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
#!/usr/bin/env node
22

33
import * as core from "@arethetypeswrong/core";
4+
import { groupProblemsByKind, parsePackageSpec } from "@arethetypeswrong/core/utils";
45
import { versions } from "@arethetypeswrong/core/versions";
5-
import { Option, program } from "commander";
66
import chalk from "chalk";
7-
import { readFile } from "fs/promises";
8-
import { FetchError } from "node-fetch";
7+
import { execSync } from "child_process";
8+
import { Option, program } from "commander";
9+
import { readFile, stat, unlink } from "fs/promises";
910
import { createRequire } from "module";
10-
11-
import * as render from "./render/index.js";
12-
import { readConfig } from "./readConfig.js";
11+
import { FetchError } from "node-fetch";
12+
import path from "path";
13+
import readline from "readline/promises";
1314
import { problemFlags } from "./problemUtils.js";
14-
import { groupProblemsByKind, parsePackageSpec } from "@arethetypeswrong/core/utils";
15+
import { readConfig } from "./readConfig.js";
16+
import * as render from "./render/index.js";
1517

1618
const packageJson = createRequire(import.meta.url)("../package.json");
1719
const version = packageJson.version;
@@ -21,6 +23,7 @@ const formats = ["table", "table-flipped", "ascii", "json"] as const;
2123
type Format = (typeof formats)[number];
2224

2325
export interface Opts {
26+
pack?: boolean;
2427
fromNpm?: boolean;
2528
summary?: boolean;
2629
emoji?: boolean;
@@ -42,7 +45,11 @@ program
4245
)} attempts to analyze npm package contents for issues with their TypeScript types,
4346
particularly ESM-related module resolution issues.`
4447
)
45-
.argument("<file-name>", "the file to check; by default a path to a .tar.gz file, unless --from-npm is set")
48+
.argument(
49+
"[file-directory-or-package-spec]",
50+
"the packed .tgz, or directory containing package.json with --pack, or package spec with --from-npm"
51+
)
52+
.option("-P, --pack", "run `npm pack` in the specified directory and delete the resulting .tgz file afterwards")
4653
.option("-p, --from-npm", "read from the npm registry instead of a local file")
4754
.addOption(new Option("-f, --format <format>", "specify the print format").choices(formats).default("table"))
4855
.option("-q, --quiet", "don't print anything to STDOUT (overrides all other options)")
@@ -53,7 +60,7 @@ particularly ESM-related module resolution issues.`
5360
.option("--emoji, --no-emoji", "whether to use any emojis")
5461
.option("--color, --no-color", "whether to use any colors (the FORCE_COLOR env variable is also available)")
5562
.option("--config-path <path>", "path to config file (default: ./.attw.json)")
56-
.action(async (fileName: string) => {
63+
.action(async (fileOrDirectory = ".") => {
5764
const opts = program.opts<Opts>();
5865
await readConfig(program, opts.configPath);
5966
opts.ignoreRules = opts.ignoreRules?.map(
@@ -69,9 +76,13 @@ particularly ESM-related module resolution issues.`
6976
}
7077

7178
let analysis: core.CheckResult;
79+
let deleteTgz;
7280
if (opts.fromNpm) {
81+
if (opts.pack) {
82+
program.error("--pack and --from-npm cannot be used together");
83+
}
7384
try {
74-
const result = parsePackageSpec(fileName);
85+
const result = parsePackageSpec(fileOrDirectory);
7586
if (result.status === "error") {
7687
program.error(result.error);
7788
} else {
@@ -86,6 +97,39 @@ particularly ESM-related module resolution issues.`
8697
}
8798
} else {
8899
try {
100+
let fileName = fileOrDirectory;
101+
if (
102+
await stat(fileOrDirectory)
103+
.then((stat) => !stat.isFile())
104+
.catch(() => false)
105+
) {
106+
if (!(await stat(path.join(fileOrDirectory, "package.json")).catch(() => false))) {
107+
program.error(
108+
`Specified directory must contain a package.json. No package.json found in ${path.resolve(
109+
fileOrDirectory
110+
)}.`
111+
);
112+
}
113+
114+
if (!opts.pack) {
115+
if (!process.stdout.isTTY) {
116+
program.error(
117+
"Specifying a directory requires the --pack option to confirm that running `npm pack` is ok."
118+
);
119+
}
120+
const rl = readline.createInterface(process.stdin, process.stdout);
121+
const answer = await rl.question(`Run \`npm pack\`? (Pass -P/--pack to skip) (Y/n) `);
122+
rl.close();
123+
if (answer.trim() && !answer.trim().toLowerCase().startsWith("y")) {
124+
process.exit(1);
125+
}
126+
}
127+
128+
fileName = deleteTgz = path.resolve(
129+
fileOrDirectory,
130+
execSync("npm pack", { cwd: fileOrDirectory, encoding: "utf8", stdio: "pipe" }).trim()
131+
);
132+
}
89133
const file = await readFile(fileName);
90134
const data = new Uint8Array(file);
91135
analysis = await core.checkTgz(data);
@@ -122,6 +166,10 @@ particularly ESM-related module resolution issues.`
122166
} else {
123167
render.untyped(analysis as core.UntypedResult);
124168
}
169+
170+
if (deleteTgz) {
171+
await unlink(deleteTgz);
172+
}
125173
});
126174

127175
program.parse(process.argv);

0 commit comments

Comments
 (0)