// 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 < length(args); i++) { if (args[i] == '--format' || args[i] == '-f') { if (i + 1 < length(args)) { 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 (!starts_with(args[i], '-')) { 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 ? text(lock_entry.commit, 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) { arrfor(array(deps), function(alias) { var dep_locator = deps[alias] add_node(dep_locator) push(edges, { 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() arrfor(packages, function(p) { if (p != 'core') { push(roots, p) } }) } else { // Default to current directory if (!target_locator) { target_locator = '.' } // Resolve local paths if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { var resolved = fd.realpath(target_locator) if (resolved) { target_locator = resolved } } push(roots, target_locator) } arrfor(roots, function(root) { 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 = [] arrfor(edges, function(e) { if (e.from == locator) { push(children, e) } }) for (var i = 0; i < length(children); i++) { var child_prefix = prefix + (is_last ? " " : "| ") print_tree(children[i].to, child_prefix, i == length(children) - 1, visited) } } for (var i = 0; i < length(roots); i++) { log.console(roots[i]) var children = [] arrfor(edges, function(e) { if (e.from == roots[i]) { push(children, e) } }) for (var j = 0; j < length(children); j++) { print_tree(children[j].to, "", j == length(children) - 1, {}) } if (i < length(roots) - 1) log.console("") } } else if (format == 'dot') { log.console("digraph dependencies {") log.console(" rankdir=TB;") log.console(" node [shape=box];") log.console("") // Node definitions arrfor(array(nodes), function(id) { 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 = replace(id, /[^a-zA-Z0-9]/g, '_') log.console(' ' + safe_id + ' [' + attrs + '];') }) log.console("") // Edges arrfor(edges, function(e) { var from_id = replace(e.from, /[^a-zA-Z0-9]/g, '_') var to_id = replace(e.to, /[^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: [] } arrfor(array(nodes), function(id) { push(output.nodes, nodes[id]) }) output.edges = edges log.console(json.encode(output)) } $stop()