UNPKG

4.42 kBJavaScriptView Raw
1'use strict';
2
3const path_ = require('path');
4const tree = require('./tree');
5const cyclic = require('./cyclic');
6const graph = require('./graph');
7const log = require('./log');
8
9const defaultConfig = {
10 baseDir: null,
11 excludeRegExp: false,
12 fileExtensions: ['js'],
13 includeNpm: false,
14 requireConfig: null,
15 webpackConfig: null,
16 tsConfig: null,
17 rankdir: 'LR',
18 layout: 'dot',
19 fontName: 'Arial',
20 fontSize: '14px',
21 backgroundColor: '#111111',
22 nodeColor: '#c6c5fe',
23 nodeShape: 'box',
24 nodeStyle: 'rounded',
25 noDependencyColor: '#cfffac',
26 cyclicNodeColor: '#ff6c60',
27 edgeColor: '#757575',
28 graphVizOptions: false,
29 graphVizPath: false,
30 dependencyFilter: false
31};
32
33class Madge {
34 /**
35 * Class constructor.
36 * @constructor
37 * @api public
38 * @param {String|Array|Object} path
39 * @param {Object} config
40 * @return {Promise}
41 */
42 constructor(path, config) {
43 this.skipped = [];
44
45 if (!path) {
46 throw new Error('path argument not provided');
47 }
48
49 this.config = Object.assign({}, defaultConfig, config);
50 if (typeof this.config.tsConfig === 'string') {
51 const ts = require('typescript');
52 const tsParsedConfig = ts.readJsonConfigFile(this.config.tsConfig, ts.sys.readFile);
53 const obj = ts.parseJsonSourceFileConfigFileContent(tsParsedConfig, ts.sys, path_.dirname(config.tsConfig));
54 this.config.tsConfig = {
55 ...obj.raw,
56 compilerOptions: obj.options
57 };
58 log('using tsconfig %o', this.config.tsConfig);
59 }
60
61 if (typeof path === 'object' && !Array.isArray(path)) {
62 this.tree = path;
63 log('using predefined tree %o', path);
64 return Promise.resolve(this);
65 }
66
67 if (typeof path === 'string') {
68 path = [path];
69 }
70
71 return tree(path, this.config).then((res) => {
72 this.tree = res.tree;
73 this.skipped = res.skipped;
74 return this;
75 });
76 }
77
78 /**
79 * Return the module dependency graph as an object.
80 * @api public
81 * @return {Object}
82 */
83 obj() {
84 return this.tree;
85 }
86
87 /**
88 * Return produced warnings.
89 * @api public
90 * @return {Array}
91 */
92 warnings() {
93 return {
94 skipped: this.skipped
95 };
96 }
97
98 /**
99 * Return the modules that has circular dependencies.
100 * @api public
101 * @return {Array}
102 */
103 circular() {
104 return cyclic(this.tree);
105 }
106
107 /**
108 * Return circular dependency graph.
109 * @api public
110 * @return {Object}
111 */
112 circularGraph() {
113 const circularDeps = this.circular();
114
115 return Object.entries(this.obj())
116 .filter(([k]) => circularDeps.some((x) => x.includes(k)))
117 .reduce((acc, [k, v]) => {
118 acc[k] = v.filter((x) => circularDeps.some((y) => y.includes(x)));
119 return acc;
120 }, {});
121 }
122
123 /**
124 * Return a list of modules that depends on the given module.
125 * @api public
126 * @param {String} id
127 * @return {Array}
128 */
129 depends(id) {
130 const tree = this.obj();
131
132 return Object
133 .keys(tree)
134 .filter((dep) => tree[dep].indexOf(id) >= 0);
135 }
136
137 /**
138 * Return a list of modules that no one is depending on.
139 * @api public
140 * @return {Array}
141 */
142 orphans() {
143 const tree = this.obj();
144 const map = {};
145
146 Object
147 .keys(tree)
148 .forEach((dep) => {
149 tree[dep].forEach((id) => {
150 map[id] = true;
151 });
152 });
153
154 return Object
155 .keys(tree)
156 .filter((dep) => !map[dep]);
157 }
158
159 /**
160 * Return a list of modules that have no dependencies.
161 * @api public
162 * @return {Array}
163 */
164 leaves() {
165 const tree = this.obj();
166 return Object.keys(tree).filter((key) => !tree[key].length);
167 }
168
169 /**
170 * Return the module dependency graph as DOT output.
171 * @api public
172 * @param {Boolean} circularOnly
173 * @return {Promise}
174 */
175 dot(circularOnly) {
176 return graph.dot(
177 circularOnly ? this.circularGraph() : this.obj(),
178 this.circular(),
179 this.config
180 );
181 }
182
183 /**
184 * Write dependency graph to image.
185 * @api public
186 * @param {String} imagePath
187 * @param {Boolean} circularOnly
188 * @return {Promise}
189 */
190 image(imagePath, circularOnly) {
191 if (!imagePath) {
192 return Promise.reject(new Error('imagePath not provided'));
193 }
194
195 return graph.image(
196 circularOnly ? this.circularGraph() : this.obj(),
197 this.circular(),
198 imagePath,
199 this.config
200 );
201 }
202
203 /**
204 * Return Buffer with XML SVG representation of the dependency graph.
205 * @api public
206 * @return {Promise}
207 */
208 svg() {
209 return graph.svg(this.obj(), this.circular(), this.config);
210 }
211}
212
213/**
214 * Expose API.
215 * @param {String|Array} path
216 * @param {Object} config
217 * @return {Promise}
218 */
219module.exports = (path, config) => new Madge(path, config);