UNPKG

@sizium/core

Version:

Get the actual size of any local or remote package

238 lines (231 loc) 7.78 kB
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 };