UNPKG

rugby

Version:

A Node.js CLI to collect npm package tarballs and assets (including those fetched by pre/postinstall scripts) for offline Artifactory use.

113 lines (100 loc) 3.77 kB
"use strict"; const path = require("path"); // Utilities to build and print dependency trees from package-lock.json function buildTreeFromLock(lockJson) { if (lockJson && lockJson.packages && lockJson.packages[""] && lockJson.packages[""]?.dependencies) { return buildFromPackagesMap(lockJson.packages); } // Fallback: use legacy top-level dependencies shape const rootDeps = lockJson.dependencies || {}; function node(name, info) { return { name, version: info && info.version, children: buildChildren(info && info.dependencies), }; } function buildChildren(deps) { if (!deps) return []; return Object.entries(deps) .map(([n, i]) => node(n, i)) .sort((a, b) => a.name.localeCompare(b.name)); } const roots = Object.entries(rootDeps) .map(([n, i]) => node(n, i)) .sort((a, b) => a.name.localeCompare(b.name)); return roots; } function buildFromPackagesMap(packagesMap) { const posixJoin = (...parts) => parts.filter(Boolean).join("/"); function resolveInstalledPath(fromPath, depName) { // Try fromPath/node_modules/depName, then walk up to root let current = fromPath; while (true) { const candidate = posixJoin(current, "node_modules", depName); if (packagesMap[candidate]) return candidate; if (!current) break; const parent = current.replace(/\/?node_modules\/(?:@[^/]+\/)?[^/]+$/, ""); if (parent === current) break; current = parent; } // Last resort: top-level node_modules const top = posixJoin("node_modules", depName); if (packagesMap[top]) return top; return null; } function buildNode(installedPath, visited) { const info = packagesMap[installedPath] || {}; const name = info.name || installedPath.split("node_modules/").pop() || installedPath; const version = info.version; const deps = (info.dependencies && typeof info.dependencies === "object") ? info.dependencies : {}; const children = []; for (const depName of Object.keys(deps).sort()) { const depPath = resolveInstalledPath(installedPath, depName); if (!depPath) continue; const visitKey = `${depPath}`; if (visited.has(visitKey)) { // Avoid infinite loops on cycles/peers continue; } visited.add(visitKey); children.push(buildNode(depPath, visited)); } return { name, version, children }; } const rootPkg = packagesMap[""] || {}; const rootDeps = (rootPkg.dependencies && typeof rootPkg.dependencies === "object") ? rootPkg.dependencies : {}; const roots = []; const visited = new Set(); for (const depName of Object.keys(rootDeps).sort()) { const depPath = resolveInstalledPath("", depName); if (!depPath) continue; visited.add(depPath); roots.push(buildNode(depPath, visited)); } return roots; } function buildFlatListFromGraph(graph) { const items = []; for (const [name, info] of graph.order) { items.push({ name, version: info.version }); } return items; } function printTree(nodes, { markScriptSet = new Set(), indent = "" } = {}) { const lines = []; const lastIdx = nodes.length - 1; nodes.forEach((n, idx) => { const isLast = idx === lastIdx; const branch = isLast ? "└─" : "├─"; const tag = markScriptSet.has(n.name) ? " [from scripts]" : ""; const versionStr = n.version ? `@${n.version}` : ""; lines.push(`${indent}${branch} ${n.name}${versionStr}${tag}`); const childIndent = indent + (isLast ? " " : "│ "); if (n.children && n.children.length) { lines.push(...printTree(n.children, { markScriptSet, indent: childIndent })); } }); return lines; } module.exports = { buildTreeFromLock, printTree, buildFlatListFromGraph };