-
Notifications
You must be signed in to change notification settings - Fork 0
/
lib.js
133 lines (116 loc) · 3.49 KB
/
lib.js
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
/// <reference types="./lib.t" />
// @ts-check
const os = require("os");
const path = require("path");
const fs = require("fs-extra");
const crypto = require("crypto");
const { exec } = require("child_process");
const prettyBytes = require("pretty-bytes");
const fastFolderSize = require("fast-folder-size");
/**
* Calculate the size of a package
* @param {string} name package name, e.g. lodash
* @param {string} version package version, e.g. "4.17.21" or "^4.0.0", recommend use specific version
* @param {string} [command] command to install this package, default is "npm install"
* @returns
*/
async function calcPkgSize(name, version, command = "npm install") {
const json = {
name: `calc-pkg-size-${name}`.replace("@", "").replace("/", "-"),
private: true,
description: `install ${name} ${version} through ${String(command)}`,
dependencies: {
[name]: version,
},
};
const contentHash = crypto
.createHash("md5")
.update(JSON.stringify(json))
.digest("hex");
const tmpDir = path.resolve(os.tmpdir(), `calc-pkg-size/${contentHash}`);
const sizeCachePath = path.resolve(tmpDir, "size.json");
try {
const cached = await fs.readFile(sizeCachePath);
return JSON.parse(cached.toString());
} catch {
// no cache
}
await fs.ensureDir(tmpDir);
await fs.writeFile(
path.resolve(tmpDir, "package.json"),
JSON.stringify(json)
);
await new Promise((resolve, reject) => {
exec(
command,
{
cwd: tmpDir,
env: process.env,
encoding: "utf-8",
},
(error, stdout, stderr) => {
// stdout && process.stdout.write(stdout);
stderr && process.stderr.write(stderr);
if (error) {
reject(error);
} else {
resolve(true);
}
}
);
});
const bytes = await new Promise((resolve, reject) => {
fastFolderSize(path.resolve(tmpDir, "node_modules"), (err, bytes) => {
if (err) {
reject(err);
} else {
resolve(bytes);
}
});
});
const pretty = prettyBytes(bytes);
const result = {
name,
version,
command,
size: bytes,
pretty,
date: new Date().toISOString(),
};
// write cache
await fs.writeFile(sizeCachePath, JSON.stringify(result));
return result;
}
/**
* Calculate each package's size in a package.json
* @param {Object} param
* @param {string} [param.packageJsonPath] package.json absolute path
* @param {Record<string,any>} [param.packageJson] package.json content, if not provided, will read from packageJsonPath
* @param {string} param.command command to install package, default is "npm install", you can use "npm install --omit=dev --prefer-offline --no-audit --no-fund"
*/
async function calcPackageJson({ packageJsonPath, packageJson, command }) {
let pkgJson = {};
if (packageJson) {
pkgJson = packageJson;
} else if (packageJsonPath) {
const pkgJsonRaw = await fs.readFile(packageJsonPath, "utf-8");
pkgJson = JSON.parse(pkgJsonRaw);
}
if (!pkgJson) {
throw new Error("packageJson or packageJsonPath is required");
}
const deps = pkgJson.dependencies;
if (!deps) {
throw new Error("'dependencies' is required in package.json");
}
const asyncJobs = Object.keys(deps).map(function (name) {
return calcPkgSize(name, deps[name], command);
});
const result = await Promise.all(asyncJobs);
const sorted = result.sort((a, b) => (b.size - a.size > 0 ? -1 : 1));
return sorted;
}
module.exports = {
calcPkgSize,
calcPackageJson,
};