@sizium/core
Version:
Get the actual size of any local or remote package
238 lines (231 loc) • 7.78 kB
JavaScript
import { clean, valid, rcompare, maxSatisfying } from 'semver';
const getVersion = (version, availableVersions) => {
if (version === "latest") return clean([...availableVersions].filter((v) => valid(v)).sort(rcompare)[0]) || void 0;
let selectedVersion;
if (valid(version)) selectedVersion = version;
else selectedVersion = maxSatisfying(availableVersions, version) || void 0;
return selectedVersion?.replace(/^v(?=\d)/, "");
};
const parseName = (input) => {
const RE_SCOPED = /^(@[^\\/]+\/[^@\\/]+)(?:@([^\\/]+))?(\/.*)?$/;
const RE_NON_SCOPED = /^([^@\\/]+)(?:@([^\\/]+))?(\/.*)?$/;
input = input.toLowerCase();
const m = RE_SCOPED.exec(input) || RE_NON_SCOPED.exec(input);
if (!m) return void 0;
const rawPath = typeof m[3] === "string" ? m[3].trim() : "";
return {
name: m[1] || "",
version: m[2] || "latest",
path: rawPath !== "" ? rawPath : void 0
};
};
const ERROR_ID = {
INVALID_PKG_NAME: "INVALID_PKG_NAME",
GETTING_PKG_NAME: "GETTING_PKG_NAME",
GETTING_REGISTRY_DATA: "GETTING_REGISTRY_DATA",
GETTING_LOCAL_DATA: "GETTING_LOCAL_DATA"
};
const LIFE_CYCLE_SCRIPTS = [
"preinstall",
"install",
"postinstall",
"prepublish",
"preprepare",
"prepare",
"postprepare"
];
class TypedError extends Error {
data;
constructor(message, data) {
super(message);
this.data = data;
Error.captureStackTrace(this, this.constructor);
}
}
const normalizeRepositoryUrl = (url) => {
if (!url) return void 0;
try {
const parsedUrl = new URL(url);
parsedUrl.protocol = "https:";
if (parsedUrl.pathname.endsWith(".git"))
parsedUrl.pathname = parsedUrl.pathname.slice(0, -4);
return parsedUrl.toString();
} catch (_error) {
return void 0;
}
};
class SiziumError extends TypedError {
}
class PackageSuper {
constructor(input, opts) {
this.input = input;
this.opts = opts;
}
ERROR_ID = ERROR_ID;
Error = SiziumError;
#processedPackages = /* @__PURE__ */ new Set();
LIFE_CYCLE_SCRIPTS = LIFE_CYCLE_SCRIPTS;
// #getFixedVersion( version: string ) {
// const validVersion = semver.valid( semver.coerce( version ) )
// return validVersion ? validVersion : 'latest'
// }
getMainPkgData(allPackages) {
const totalSize = allPackages.reduce((sum, pkg) => {
const size = pkg.unpackedSize ?? 0;
return sum + size;
}, 0);
return {
id: allPackages[0].name,
packageNum: allPackages.length,
size: totalSize,
sizeKB: totalSize / 1e3,
sizeMB: totalSize / 1e6,
packages: allPackages
};
}
getPkgData(opts) {
const {
data,
level = 0,
unpackedSize,
installedBy
} = opts;
const lcScripts = {};
if (data.scripts) {
const scripts = Object.keys(data.scripts);
scripts.forEach((script) => {
if (this.LIFE_CYCLE_SCRIPTS.includes(script))
lcScripts[script] = data.scripts[script];
});
}
const size = unpackedSize ?? 0;
return {
id: `${data.name}@${data.version}`,
name: data.name,
version: data.version,
description: data.description,
license: data.license,
isESM: data.type === "module",
isCommonJS: data.type !== "module",
types: !!data.types,
author: data.author && data.author.url ? {
name: data.author.name,
url: data.author.url
} : void 0,
url: {
homepage: normalizeRepositoryUrl(data.homepage),
repository: normalizeRepositoryUrl(
typeof data.repository === "string" ? data.repository : data.repository?.url
),
funding: Array.isArray(data.funding) ? typeof data.funding[0] === "string" ? data.funding[0] : data.funding[0]?.url : typeof data.funding === "string" ? data.funding : data.funding?.url,
npm: `https://www.npmjs.com/package/${data.name}/v/${data.version}`,
unpkg: `https://unpkg.com/${data.name}@${data.version}/`
},
unpackedSize: size,
unpackedSizeKB: size / 1e3,
unpackedSizeMB: size / 1e6,
dependencies: data.dependencies,
// dependencies : !data.dependencies
// ? undefined
// : Object.fromEntries( Object.entries( data.dependencies ).map( ( [ key, value ] ) => {
// return [
// key,
// {
// version : this.#getFixedVersion( value || 'latest' ),
// packageVersion : value,
// },
// ]
// } ) ),
devDependencies: data.devDependencies,
lifeCycleScripts: Object.keys(lcScripts).length ? lcScripts : void 0,
installedBy: installedBy === void 0 ? void 0 : Array.isArray(installedBy) ? installedBy : [installedBy],
level
};
}
async getRegistryData(opts) {
const {
name: packageName,
version,
level = 0,
installedBy
} = opts;
try {
const response = await fetch(`https://registry.npmjs.org/${packageName}`);
if (!response.ok) {
if (response.status === 404)
throw new Error(`Package "${packageName}" not found (404 Not Found).`);
else
throw new Error(`Failed to fetch data. HTTP Status: ${response.status}`);
}
const res = await response.json();
const selectedVersion = getVersion(version, Object.keys(res.versions)) || res["dist-tags"].latest || "latest";
const data = res.versions[selectedVersion];
if (!data) throw new Error(`Version ${version} (${selectedVersion}) not found`);
const size = data.dist?.unpackedSize;
const secureSize = typeof size === "string" ? Number(size) : typeof size === "number" ? size : void 0;
return this.getPkgData({
data,
level,
unpackedSize: secureSize,
installedBy
});
} catch (e) {
throw new this.Error(
this.ERROR_ID.GETTING_REGISTRY_DATA,
{
msg: `Error getting "${packageName}" dependence data. ${e instanceof Error ? e.message : ""}`,
e
}
);
}
}
async #processDependencies(dependencies, level, parentName) {
const packages = /* @__PURE__ */ new Map();
const addPackage = (pkg) => {
const pkgExist = packages.get(pkg.id);
packages.set(
pkg.id,
pkgExist ? {
...pkgExist,
installedBy: !pkg.installedBy ? pkgExist.installedBy : [...pkgExist.installedBy || [], ...pkg.installedBy]
} : pkg
);
};
const promises = Object.entries(dependencies).map(async ([depName, depVersion]) => {
try {
if (!depVersion) return;
const packageKey = `${depName}@${depVersion}`;
const installedBy = parentName;
if (this.#processedPackages.has(packageKey)) return;
this.#processedPackages.add(packageKey);
const depData = await this.getRegistryData({
name: depName,
version: depVersion,
level,
installedBy
});
addPackage(depData);
if (depData.dependencies) {
const subDeps = await this.#processDependencies(depData.dependencies, level + 1, packageKey);
for (const pkg of subDeps) addPackage(pkg);
}
} catch (e) {
if (this.opts?.skipError) return;
throw e;
}
});
await Promise.all(promises);
return Array.from(packages.values());
}
async getPackagesData(mainPackage) {
const allPackages = [mainPackage];
const mainId = `${mainPackage.name}@${mainPackage.version}`;
this.#processedPackages.add(mainId);
if (mainPackage.dependencies) {
const deps = await this.#processDependencies(mainPackage.dependencies, 1, mainId);
allPackages.push(...deps);
}
return allPackages;
}
}
export { PackageSuper as P, SiziumError as S, getVersion as g, parseName as p };