1import chalk from 'chalk'; 2import { EOL } from 'os'; 3 4import { DefaultDependencyKind, DependencyKind } from '../Packages'; 5import PackagesGraph from './PackagesGraph'; 6import PackagesGraphEdge from './PackagesGraphEdge'; 7import PackagesGraphNode from './PackagesGraphNode'; 8 9type NodeVisitingData = { 10 visited: Record<string, boolean>; 11 node: PackagesGraphNode; 12 edges: PackagesGraphEdge[]; 13 kind: DependencyKind; 14 version: string; 15 level: number; 16 prefix: string; 17}; 18 19export function printGraph( 20 graph: PackagesGraph, 21 packageNames: string[] = [], 22 kinds: DependencyKind[] = DefaultDependencyKind 23) { 24 function visitNodeEdges(visitingData: NodeVisitingData) { 25 const { node, edges, kind, version, level, prefix, visited } = visitingData; 26 const name = formatNodeName(node.name, kind, level); 27 28 if (visited[node.name] === true) { 29 process.stdout.write(chalk.dim(`${name} ...`) + EOL); 30 return; 31 } 32 33 visited[node.name] = true; 34 35 process.stdout.write(`${name} ${chalk.cyan(version)}` + EOL); 36 37 if (edges.length === 0 && level === 0) { 38 // It has no dependencies, but let's explicitly show that for the origin nodes. 39 process.stdout.write(edgePointer(true, false, false) + chalk.dim('none') + EOL); 40 return; 41 } 42 for (let i = 0; i < edges.length; i++) { 43 const edge = edges[i]; 44 const isLast = i === edges.length - 1; 45 const nextEdges = edge.destination.getOutgoingEdgesOfKinds(kinds); 46 const hasMore = nextEdges.length > 0 && visited[edge.destination.name] !== true; 47 48 process.stdout.write(chalk.gray(prefix) + edgePointer(isLast, hasMore, edge.isCyclic)); 49 50 visitNodeEdges({ 51 node: edge.destination, 52 edges: nextEdges, 53 kind: edge.getDominantKind(), 54 version: edge.versionRange, 55 level: level + 1, 56 prefix: prefix + nodeIndent(isLast), 57 visited: { ...visited }, 58 }); 59 } 60 } 61 62 for (const node of graph.nodes.values()) { 63 if (packageNames.length > 0 && !packageNames.includes(node.name)) { 64 continue; 65 } 66 visitNodeEdges({ 67 node, 68 edges: node.getOutgoingEdgesOfKinds(kinds), 69 kind: DependencyKind.Normal, 70 version: node.pkg.packageVersion, 71 level: 0, 72 prefix: '', 73 visited: {}, 74 }); 75 } 76} 77 78export function printNodeDependents( 79 node: PackagesGraphNode, 80 kinds: DependencyKind[] = DefaultDependencyKind 81) { 82 const nodes = node.getAllDependents(kinds); 83 84 if (nodes.length === 0) { 85 console.log(`${chalk.bold(node.name)} has no dependents`); 86 return; 87 } 88 89 console.log(`All dependents of the ${chalk.bold(node.name)} package:`); 90 91 for (const node of nodes) { 92 console.log(`- ${chalk.bold(node.name)}`); 93 } 94} 95 96function edgePointer(isLast: boolean, hasMore: boolean, isCyclic: boolean): string { 97 const rawPointer = [ 98 isLast ? '└─' : '├─', 99 hasMore ? '┬─' : '──', 100 isCyclic ? chalk.red('∞') : '', 101 ' ', 102 ].join(''); 103 return chalk.gray(rawPointer); 104} 105 106function nodeIndent(isLast: boolean): string { 107 return isLast ? ' ' : '│ '; 108} 109 110function formatNodeName(name: string, kind: DependencyKind, level: number): string { 111 if (level === 0) { 112 return chalk.bold(name); 113 } 114 switch (kind) { 115 case DependencyKind.Normal: 116 return chalk.green(name); 117 case DependencyKind.Dev: 118 return chalk.yellow(name); 119 case DependencyKind.Peer: 120 return chalk.magenta(name); 121 case DependencyKind.Optional: 122 return chalk.gray(name); 123 } 124} 125