npm-install-size
Version:
Check the install size of any NPM package before installing it.
66 lines (63 loc) • 2.47 kB
JavaScript
import fetch from 'node-fetch';
import { retry, parsePkgArg } from './fetch.js';
import { getDirStats, cleanup } from './analyze.js';
import tar from 'tar';
import fs from 'fs/promises';
import os from 'os';
import path from 'path';
async function fetchPackageMeta(pkgArg) {
const { name, version } = parsePkgArg(pkgArg);
const encoded = encodeURIComponent(name);
const url = `https://registry.npmjs.org/${encoded}`;
const res = await retry(() => fetch(url));
if (!res.ok) throw new Error(`Package not found`);
const data = await res.json();
let ver = version || data['dist-tags']?.latest;
if (!ver || !data.versions[ver]) throw new Error(`Version not found`);
return { meta: data, version: ver, tarball: data.versions[ver].dist.tarball, dependencies: data.versions[ver].dependencies || {} };
}
async function getPackageSizeFromTarball(tarballUrl) {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), `npm-install-size-tree-`));
try {
const res = await retry(() => fetch(tarballUrl));
if (!res.ok) throw new Error(`Failed to download tarball`);
await fs.mkdir(tempDir, { recursive: true });
await new Promise((resolve, reject) => {
const extractStream = tar.x({ cwd: tempDir, strip: 1 });
res.body.pipe(extractStream);
res.body.on('error', reject);
extractStream.on('finish', resolve);
extractStream.on('error', reject);
});
const stats = await getDirStats(tempDir);
return stats.total;
} finally {
await cleanup(tempDir);
}
}
export async function getDependencyTreeSize(pkgArg, options = {}, seen = new Set()) {
const { name, version } = parsePkgArg(pkgArg);
const key = version ? `${name}@${version}` : name;
if (seen.has(key)) {
return { totalSize: 0, tree: { name: key, size: 0, dependencies: {} } };
}
seen.add(key);
const { tarball, version: resolvedVersion, dependencies } = await fetchPackageMeta(pkgArg);
const size = await getPackageSizeFromTarball(tarball);
let depTree = {};
let depTotal = 0;
for (const dep in dependencies) {
const depSpec = `${dep}@${dependencies[dep]}`;
const { totalSize: depSize, tree: depSubtree } = await getDependencyTreeSize(depSpec, options, seen);
depTree[depSpec] = depSubtree;
depTotal += depSize;
}
return {
totalSize: size + depTotal,
tree: {
name: key,
size,
dependencies: depTree
}
};
}