-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.ts
152 lines (128 loc) · 4.23 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/* eslint-disable no-console, no-process-exit -- this is a cli tool */
import { existsSync, createWriteStream, promises as fs } from "fs";
import path from "path";
import { ArgumentParser } from "argparse";
import findUp from "find-up";
import tmp from "tmp";
import stylish from "@html-validate/stylish";
import { setupBlacklist } from "./blacklist";
import { verify, VerifyOptions } from "./verify";
import PackageJson from "./types/package-json";
import { tarballLocation } from "./tarball-location";
import { getFileContent, TarballMeta } from "./tarball";
/* eslint-disable-next-line @typescript-eslint/no-var-requires -- technical debt, should use fs.readFile */
const { version } = require("../package.json");
const PACKAGE_JSON = "package.json";
interface ParsedArgs {
pkgfile: string;
tarball?: string;
ignore_missing_fields?: boolean;
allow_types_dependencies?: boolean;
}
interface GetPackageJsonResults {
pkg: PackageJson;
pkgPath: string;
}
async function preloadStdin(): Promise<string> {
return new Promise((resolve, reject) => {
tmp.file((err, path, fd) => {
if (err) {
reject(err);
return;
}
const st = createWriteStream("", { fd, autoClose: true });
process.stdin.pipe(st);
st.on("finish", () => {
resolve(path);
});
st.on("error", (err) => {
reject(err);
});
});
});
}
async function getPackageJson(
args: ParsedArgs,
regenerateReportName: boolean
): Promise<GetPackageJsonResults | Record<string, never>> {
/* get from explicit path passed as argument */
if (args.pkgfile) {
return {
pkg: JSON.parse(await fs.readFile(args.pkgfile, "utf-8")),
pkgPath: args.pkgfile,
};
}
/* extract package.json from explicit tarball location */
if (args.tarball) {
const contents = await getFileContent({ filePath: args.tarball }, [PACKAGE_JSON]);
const pkg = JSON.parse(contents[PACKAGE_JSON].toString("utf-8"));
return {
pkg,
pkgPath: path.join(
regenerateReportName ? `${pkg.name}-${pkg.version}.tgz` : args.tarball,
PACKAGE_JSON
),
};
}
/* try to locate package.json from file structure */
const pkgPath = await findUp(PACKAGE_JSON);
if (pkgPath) {
const relPath = path.relative(process.cwd(), pkgPath);
return {
pkg: JSON.parse(await fs.readFile(relPath, "utf-8")),
pkgPath: relPath,
};
}
return {};
}
async function run(): Promise<void> {
const parser = new ArgumentParser({
description: "npm package linter",
});
parser.add_argument("-v", "--version", { action: "version", version });
parser.add_argument("-t", "--tarball", { help: "specify tarball location" });
parser.add_argument("-p", "--pkgfile", { help: "specify package.json location" });
parser.add_argument("--allow-types-dependencies", {
action: "store_true",
help: "allow dependencies to `@types/*`",
});
parser.add_argument("--ignore-missing-fields", {
action: "store_true",
help: "ignore errors for missing fields (but still checks for empty and valid)",
});
const args: ParsedArgs = parser.parse_args();
/* this library assumes the file source can be randomly accessed but with
* stdin this is not possible so stdin is read into a temporary file which is
* used instead */
let regenerateReportName = false;
if (args.tarball === "-") {
args.tarball = await preloadStdin();
regenerateReportName = true;
}
const { pkg, pkgPath } = await getPackageJson(args, regenerateReportName);
if (!pkg) {
console.error("Failed to locate package.json and no location was specificed with `--pkgfile'");
process.exit(1);
}
const tarball: TarballMeta = {
filePath: args.tarball ?? tarballLocation(pkg, pkgPath),
reportPath: regenerateReportName ? `${pkg.name}-${pkg.version}.tgz` : undefined,
};
if (!existsSync(tarball.filePath)) {
console.error(`"${tarball.filePath}" does not exist, did you forget to run \`npm pack'?`);
process.exit(1);
}
setupBlacklist(pkg.name);
const options: VerifyOptions = {
allowTypesDependencies: args.allow_types_dependencies,
ignoreMissingFields: args.ignore_missing_fields,
};
const results = await verify(pkg, pkgPath, tarball, options);
const output = stylish(results);
process.stdout.write(output);
const totalErrors = results.reduce((sum, result) => {
return sum + result.errorCount;
}, 0);
process.exit(totalErrors > 0 ? 1 : 0);
}
run();