UNPKG

gitdig

Version:

A simple git tool for documenting and discovering multi-repo projects

321 lines (276 loc) 10.6 kB
const _ = require('lodash') const checkNameConflict = (nodes, repoId) => { if (_.has(nodes, repoId)) { console.log(`ERROR: repository name "${repoId}" conflict.`, nodes[repoId]) process.exit(1) } } const addRepoNode = (nodes, repo) => { const repoVersion = _.hasIn(repo, 'package.version') ? repo.package.version : '*' const repoId = `${repo.name}` checkNameConflict(nodes, repoId) nodes[repoId] = { id: repoId, owner: repo.owner, name: repo.name, version: repoVersion, group: 1, type: 'REPOSITORY' } return nodes } const addComponentNodes = (nodes, components) => { const reduceComponentNodes = (components, nodes) => { return _.reduce(components, (nodes, component, key) => { if (_.isObject(component)) { checkNameConflict(nodes, key) nodes[key] = { id: key, owner: '', name: key, group: 2, type: 'VIRTUAL' } if (_.has(component, "components")) { return reduceComponentNodes(component.components, nodes) } } else { return nodes } }, nodes) } return reduceComponentNodes(components, nodes) } const addDependencyNodes = (nodes, dependencies) => { return _.reduce(dependencies, (nodes, dependency, key) => { if (! _.has(nodes, key)) { nodes[key] = { id: key, owner: '', name: key, group: 3, type: 'DEPENDENCY' } } return nodes }, nodes) } const collectNodes = (repos) => { // Add repositories to the node list let results = _.reduce(repos, (nodes, repo) => addRepoNode(nodes, repo), {}) results = _.reduce(repos, (nodes, repo) => { // Add virtual nodes from .gitdig info, if there is any if( _.hasIn(repo, 'package.gitdig.components')) { nodes = addComponentNodes(nodes, repo.package.gitdig.components) } return nodes }, results) results = _.reduce(repos, (nodes, repo) => { if (_.has(repo, 'dependencies')) { nodes = addDependencyNodes(nodes, repo.dependencies) } return nodes }, results) return results } const addComponentLinks = (links, components, repoId) => { const reduceComponentLinks = (components, links, sourceId) => { return _.reduce(components, (links, component, key) => { if (_.isObject(component)) { links.push({ source: sourceId, target: key, value: 10, type: 'COMPONENT' }) if (_.has(component, "components")) { return reduceComponentLinks(component.components, links, key) } } else { links.push({ source: sourceId, target: key, value: 10, type: 'COMPONENT' }) return links } }, links) } return reduceComponentLinks(components, links, repoId) } const collectLinks = (repos) => { let results = [] results = _.reduce(repos, (links, repo) => { // Add virtual nodes from .gitdig info, if there is any if( _.hasIn(repo, 'package.gitdig.components')) { links = addComponentLinks(links, repo.package.gitdig.components, repo.name) } return links }, results) results = _.reduce(repos, (links, repo) => { if (_.has(repo, 'dependencies')) { return _.reduce(_.map(_.keys(repo.dependencies), (dependency) => ({ source: repo.name, target: dependency, value: 10, type: "REQUIRED" })), (links, link) => { links.push(link) return links }, links) } return links }, results) return results } /** * Collect and aggregate graph data on repos, dependencies, etc. and prints out the results. * * @param {Object} config Configuration parameters * * @return {Object} The graph object, which has two properties: { nodes, links }. */ const getGraph = (repos, externals) => { const nodes = collectNodes(repos) const links = collectLinks(repos) const isInternalNode = (nodes, id) => (_.has(nodes, id) && nodes[id].type !== 'DEPENDENCY') let graph = { nodes: _.values(nodes), links: links } if (! externals) { // Remove external nodes and links graph = { nodes: _.values(_.filter(nodes, (node) => isInternalNode(nodes, node.id))), links: _.filter(links, (link) => (isInternalNode(nodes, link.source) && isInternalNode(nodes, link.target))) } } return graph } /** * Build a lookup dictionary to list items, using the `idField` value of list items as keys * * @param {List} list The list of items to make the lookup dictionary for * @param {String} idField The field name to use as key in the lookup dictionary. * This value has to be unique among the list items. * * @return {Object} The dictionary object */ const getLookup = (list, idField) => _.reduce(list, (lookup, item, index) => (lookup[item[idField]] = list[index], lookup), {}) /** * Find target nodes of links, which origins from the `sourceId` node * * @param {List} links The list of link objects: { source, target } * @param {sourceId} String The unique id of the source node * * @return {List} The list of target node ids */ const findTargets = (links, sourceId) => _.reduce(links, (results, link) => { if (link.source === sourceId) { results.push(link.target) } return results }, []) /** * Build a tree representation of the repos, which can be reached from the root nodes. * * @param {List} repos The list of repository descriptor nodes. * @param, {Boolean} externals If true, then external dependencies must be contained by the tree, * otherwise returns only with internal nodes and links. * * @return {Object} A structured object, that is the root of the tree. * The object holds the whole tree in the form of branches of sub objects, * listed under the `children` property at each level. */ const getTree = (repos, externals) => { const repoLookup = getLookup(repos, "name") const graph = getGraph(repos, externals) // const nodes = graph.nodes const links = graph.links const linkSources = _.uniq(_.map(links, (link) => link.source)) const linkTargets = _.uniq(_.map(links, (link) => link.target)) const roots = _.difference(linkSources, linkTargets) const reposWithLinkSource = _.reduce(linkSources, (results, linkSource) => (results[linkSource] = { targets: findTargets(links, linkSource) }, results), {}) // console.log(JSON.stringify(repoLookup, null, ' ')) // console.log(JSON.stringify(reposWithLinkSource, null, ' ')) const makeTreeNode = (nodeId) => { let owner = "" if (_.has(repoLookup, nodeId)) { owner = repoLookup[nodeId].owner } let result = { owner: owner, name: nodeId, size: 10 } if (_.has(reposWithLinkSource, nodeId)) { result.children = makeChildrenNodes(reposWithLinkSource[nodeId].targets) } return result } const makeChildrenNodes = (targetIds) => { return _.map(targetIds, (targetId) => makeTreeNode(targetId)) } return { name: "/", children: makeChildrenNodes(roots) } } const getNodes = (repos, externals) => { const graph = getGraph(repos, externals) const nodes = graph.nodes const links = graph.links const allNodes = _.map(nodes, (node) => node.id) const linkSources = _.uniq(_.map(links, (link) => link.source)) const linkTargets = _.uniq(_.map(links, (link) => link.target)) const linkInters = _.intersection(linkSources, linkTargets) const connected = _.union(linkSources, linkTargets) const orphans = _.difference(allNodes, connected) const roots = _.difference(linkSources, linkTargets) return { allNodes: allNodes, linkSources: linkSources, linkTargets: linkTargets, linkInters: linkInters, connected: connected, orphans: orphans, roots: roots } } const getDependencies = (repos, externals) => { const repoLookup = getLookup(repos, "name") const graph = getGraph(repos, externals) const isInternalDependency = (nodes, id) => (_.has(nodes, id)) const dependencies = _.reduce(graph.nodes, (allDependencies, node) => { const nodeDetails = repoLookup[node.id] // virtual nodes have no "real bodies" in repoLookup!!! if (nodeDetails && _.has(nodeDetails, "dependencies") && _.isObject(nodeDetails.dependencies)) { const nodeDependencies = repoLookup[node.id].dependencies return _.reduce(_.keys(nodeDependencies), (results, key) => { if (! _.has(results, key) && (externals || isInternalDependency(repoLookup, key))) { results[key] = nodeDependencies[key] } return results }, allDependencies) } else { return allDependencies } }, {}) return dependencies //return _.sortBy(_.map(_.keys(dependencies), (dependency, key) => [dependency, dependencies[dependency]])) } const getDependants = (repos, dependency) => _.reduce(repos, (dependants, repo, key) => { if (_.has(repo.dependencies, dependency)) { dependants.push(repo.name) } return dependants }, []) module.exports = { getGraph: getGraph, getTree: getTree, getNodes: getNodes, getDependencies: getDependencies, getDependants: getDependants }