// cell graph [] - Emit dependency graph // // Usage: // cell graph Graph current directory package // cell graph . Graph current directory package // cell graph Graph specific package // cell graph --world Graph all packages in shop (world set) // // Options: // --format Output format: tree (default), dot, json // --resolved Show resolved view with links applied (default) // --locked Show lock view without links // --world Graph all packages in shop var shop = use('internal/shop') var pkg = use('package') var link = use('link') var fd = use('fd') var json = use('json') var target_locator = null var format = 'tree' var show_locked = false var show_world = false for (var i = 0; i < args.length; i++) { if (args[i] == '--format' || args[i] == '-f') { if (i + 1 < args.length) { format = args[++i] if (format != 'tree' && format != 'dot' && format != 'json') { log.error('Invalid format: ' + format + '. Must be tree, dot, or json') $stop() } } else { log.error('--format requires a format type') $stop() } } else if (args[i] == '--resolved') { show_locked = false } else if (args[i] == '--locked') { show_locked = true } else if (args[i] == '--world') { show_world = true } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell graph [] [options]") log.console("") log.console("Emit the dependency graph.") log.console("") log.console("Options:") log.console(" --format Output format: tree (default), dot, json") log.console(" --resolved Show resolved view with links applied (default)") log.console(" --locked Show lock view without links") log.console(" --world Graph all packages in shop") $stop() } else if (!args[i].startsWith('-')) { target_locator = args[i] } } var links = show_locked ? {} : link.load() // Get effective locator (after links) function get_effective(locator) { return links[locator] || locator } // Build graph data structure var nodes = {} var edges = [] function add_node(locator) { if (nodes[locator]) return var lock = shop.load_lock() var lock_entry = lock[locator] var link_target = links[locator] var info = shop.resolve_package_info(locator) nodes[locator] = { id: locator, effective: get_effective(locator), linked: link_target != null, local: info == 'local', commit: lock_entry && lock_entry.commit ? lock_entry.commit.substring(0, 8) : null } } function gather_graph(locator, visited) { if (visited[locator]) return visited[locator] = true add_node(locator) try { var deps = pkg.dependencies(locator) if (deps) { for (var alias in deps) { var dep_locator = deps[alias] add_node(dep_locator) edges.push({ from: locator, to: dep_locator, alias: alias }) gather_graph(dep_locator, visited) } } } catch (e) { // Package might not have dependencies } } // Gather graph from roots var roots = [] if (show_world) { // Use all packages in shop as roots var packages = shop.list_packages() for (var p of packages) { if (p != 'core') { roots.push(p) } } } else { // Default to current directory if (!target_locator) { target_locator = '.' } // Resolve local paths if (target_locator == '.' || target_locator.startsWith('./') || target_locator.startsWith('../') || fd.is_dir(target_locator)) { var resolved = fd.realpath(target_locator) if (resolved) { target_locator = resolved } } roots.push(target_locator) } for (var root of roots) { gather_graph(root, {}) } // Output based on format if (format == 'tree') { function print_tree(locator, prefix, is_last, visited) { if (visited[locator]) { log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)") return } visited[locator] = true var node = nodes[locator] var suffix = "" if (node.linked) suffix += " -> " + node.effective if (node.commit) suffix += " @" + node.commit if (node.local) suffix += " (local)" log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix) // Get children var children = [] for (var e of edges) { if (e.from == locator) { children.push(e) } } for (var i = 0; i < children.length; i++) { var child_prefix = prefix + (is_last ? " " : "| ") print_tree(children[i].to, child_prefix, i == children.length - 1, visited) } } for (var i = 0; i < roots.length; i++) { log.console(roots[i]) var children = [] for (var e of edges) { if (e.from == roots[i]) { children.push(e) } } for (var j = 0; j < children.length; j++) { print_tree(children[j].to, "", j == children.length - 1, {}) } if (i < roots.length - 1) log.console("") } } else if (format == 'dot') { log.console("digraph dependencies {") log.console(" rankdir=TB;") log.console(" node [shape=box];") log.console("") // Node definitions for (var id in nodes) { var node = nodes[id] var label = id if (node.commit) label += "\\n@" + node.commit var attrs = 'label="' + label + '"' if (node.linked) attrs += ', style=dashed' if (node.local) attrs += ', color=blue' // Safe node ID for dot var safe_id = id.replaceAll(/[^a-zA-Z0-9]/g, '_') log.console(' ' + safe_id + ' [' + attrs + '];') } log.console("") // Edges for (var e of edges) { var from_id = e.from.replaceAll(/[^a-zA-Z0-9]/g, '_') var to_id = e.to.replaceAll(/[^a-zA-Z0-9]/g, '_') var label = e.alias != e.to ? 'label="' + e.alias + '"' : '' log.console(' ' + from_id + ' -> ' + to_id + (label ? ' [' + label + ']' : '') + ';') } log.console("}") } else if (format == 'json') { var output = { nodes: [], edges: [] } for (var id in nodes) { output.nodes.push(nodes[id]) } output.edges = edges log.console(json.encode(output)) } $stop()