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
JavaScript
;
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 };