UNPKG

4.33 kBJavaScriptView Raw
1'use strict';
2
3const path = require('path');
4const {promisify} = require('util');
5const gv = require('ts-graphviz');
6const adapter = require('ts-graphviz/adapter');
7const toArray = require('stream-to-array');
8const exec = promisify(require('child_process').execFile);
9const writeFile = promisify(require('fs').writeFile);
10
11/**
12 * Set color on a node.
13 * @param {Object} node
14 * @param {String} color
15 */
16function setNodeColor(node, color) {
17 node.attributes.set('color', color);
18 node.attributes.set('fontcolor', color);
19}
20
21/**
22 * Check if Graphviz is installed on the system.
23 * @param {Object} config
24 * @return {Promise}
25 */
26async function checkGraphvizInstalled(config) {
27 const cmd = config.graphVizPath ? path.join(config.graphVizPath, 'gvpr') : 'gvpr';
28
29 try {
30 await exec(cmd, ['-V']);
31 } catch (err) {
32 if (err.code === 'ENOENT') {
33 throw new Error(`Graphviz could not be found. Ensure that "gvpr" is in your $PATH. ${err}`);
34 } else {
35 throw new Error(`Unexpected error when calling Graphviz "${cmd}". ${err}`);
36 }
37 }
38}
39
40/**
41 * Return options to use with graphviz digraph.
42 * @param {Object} config
43 * @return {Object}
44 */
45function createGraphvizOptions(config) {
46 const graphVizOptions = config.graphVizOptions || {};
47
48 return {
49 dotCommand: config.graphVizPath ? config.graphVizPath : null,
50 attributes: {
51 // Graph
52 graph: Object.assign({
53 overlap: false,
54 pad: 0.3,
55 rankdir: config.rankdir,
56 layout: config.layout,
57 bgcolor: config.backgroundColor
58 }, graphVizOptions.G),
59 // Edge
60 edge: Object.assign({
61 color: config.edgeColor
62 }, graphVizOptions.E),
63 // Node
64 node: Object.assign({
65 fontname: config.fontName,
66 fontsize: config.fontSize,
67 color: config.nodeColor,
68 shape: config.nodeShape,
69 style: config.nodeStyle,
70 height: 0,
71 fontcolor: config.nodeColor
72 }, graphVizOptions.N)
73 }
74 };
75}
76
77/**
78 * Creates the graphviz graph.
79 * @param {Object} modules
80 * @param {Array} circular
81 * @param {Object} config
82 * @param {Object} options
83 * @return {Promise}
84 */
85function createGraph(modules, circular, config, options) {
86 const g = gv.digraph('G');
87 const nodes = {};
88 const cyclicModules = circular.reduce((a, b) => a.concat(b), []);
89
90 Object.keys(modules).forEach((id) => {
91 nodes[id] = nodes[id] || g.createNode(id);
92
93 if (!modules[id].length) {
94 setNodeColor(nodes[id], config.noDependencyColor);
95 } else if (cyclicModules.indexOf(id) >= 0) {
96 setNodeColor(nodes[id], config.cyclicNodeColor);
97 }
98
99 modules[id].forEach((depId) => {
100 nodes[depId] = nodes[depId] || g.createNode(depId);
101
102 if (!modules[depId]) {
103 setNodeColor(nodes[depId], config.noDependencyColor);
104 }
105
106 g.createEdge([nodes[id], nodes[depId]]);
107 });
108 });
109 const dot = gv.toDot(g);
110 return adapter
111 .toStream(dot, options)
112 .then(toArray)
113 .then(Buffer.concat);
114}
115
116/**
117 * Return the module dependency graph XML SVG representation as a Buffer.
118 * @param {Object} modules
119 * @param {Array} circular
120 * @param {Object} config
121 * @return {Promise}
122 */
123module.exports.svg = function (modules, circular, config) {
124 const options = createGraphvizOptions(config);
125
126 options.format = 'svg';
127
128 return checkGraphvizInstalled(config)
129 .then(() => createGraph(modules, circular, config, options));
130};
131
132/**
133 * Creates an image from the module dependency graph.
134 * @param {Object} modules
135 * @param {Array} circular
136 * @param {String} imagePath
137 * @param {Object} config
138 * @return {Promise}
139 */
140module.exports.image = function (modules, circular, imagePath, config) {
141 const options = createGraphvizOptions(config);
142
143 options.format = path.extname(imagePath).replace('.', '') || 'png';
144
145 return checkGraphvizInstalled(config)
146 .then(() => {
147 return createGraph(modules, circular, config, options)
148 .then((image) => writeFile(imagePath, image))
149 .then(() => path.resolve(imagePath));
150 });
151};
152
153/**
154 * Return the module dependency graph as DOT output.
155 * @param {Object} modules
156 * @param {Array} circular
157 * @param {Object} config
158 * @return {Promise}
159 */
160module.exports.dot = function (modules, circular, config) {
161 const options = createGraphvizOptions(config);
162
163 options.format = 'dot';
164
165 return checkGraphvizInstalled(config)
166 .then(() => createGraph(modules, circular, config, options))
167 .then((output) => output.toString('utf8'));
168};