237 lines
6.0 KiB
Plaintext
237 lines
6.0 KiB
Plaintext
// cell graph [<locator>] - Emit dependency graph
|
|
//
|
|
// Usage:
|
|
// cell graph Graph current directory package
|
|
// cell graph . Graph current directory package
|
|
// cell graph <locator> Graph specific package
|
|
// cell graph --world Graph all packages in shop (world set)
|
|
//
|
|
// Options:
|
|
// --format <fmt> 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 [<locator>] [options]")
|
|
log.console("")
|
|
log.console("Emit the dependency graph.")
|
|
log.console("")
|
|
log.console("Options:")
|
|
log.console(" --format <fmt> 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()
|