1 | #!/usr/bin/env node
|
2 | 'use strict';
|
3 |
|
4 | const path = require('path');
|
5 | const process = require('process');
|
6 | const program = require('commander');
|
7 | const rc = require('rc')('madge');
|
8 | const version = require('../package.json').version;
|
9 | const ora = require('ora');
|
10 | const chalk = require('chalk');
|
11 | const startTime = Date.now();
|
12 |
|
13 | program
|
14 | .version(version)
|
15 | .usage('[options] <src...>')
|
16 | .option('-b, --basedir <path>', 'base directory for resolving paths')
|
17 | .option('-s, --summary', 'show dependency count summary')
|
18 | .option('-c, --circular', 'show circular dependencies')
|
19 | .option('-d, --depends <name>', 'show module dependents')
|
20 | .option('-x, --exclude <regexp>', 'exclude modules using RegExp')
|
21 | .option('-j, --json', 'output as JSON')
|
22 | .option('-i, --image <file>', 'write graph to file as an image')
|
23 | .option('-l, --layout <name>', 'layout engine to use for graph (dot/neato/fdp/sfdp/twopi/circo)')
|
24 | .option('--orphans', 'show modules that no one is depending on')
|
25 | .option('--dot', 'show graph using the DOT language')
|
26 | .option('--extensions <list>', 'comma separated string of valid file extensions')
|
27 | .option('--require-config <file>', 'path to RequireJS config')
|
28 | .option('--webpack-config <file>', 'path to webpack config')
|
29 | .option('--include-npm', 'include shallow NPM modules', false)
|
30 | .option('--no-color', 'disable color in output and image', false)
|
31 | .option('--stdin', 'read predefined tree from STDIN', false)
|
32 | .option('--warning', 'show warnings about skipped files', false)
|
33 | .option('--debug', 'turn on debug output', false)
|
34 | .parse(process.argv);
|
35 |
|
36 | if (!program.args.length && !program.stdin) {
|
37 | console.log(program.helpInformation());
|
38 | process.exit(1);
|
39 | }
|
40 |
|
41 | if (program.debug) {
|
42 | process.env.DEBUG = '*';
|
43 | }
|
44 |
|
45 | if (!program.color) {
|
46 | process.env.DEBUG_COLORS = false;
|
47 | }
|
48 |
|
49 | const log = require('../lib/log');
|
50 | const output = require('../lib/output');
|
51 | const madge = require('../lib/api');
|
52 | const config = Object.assign({}, rc);
|
53 |
|
54 | program.options.forEach((opt) => {
|
55 | const name = opt.name();
|
56 |
|
57 | if (program[name]) {
|
58 | config[name] = program[name];
|
59 | }
|
60 | });
|
61 |
|
62 | const spinner = ora({
|
63 | text: 'Finding files',
|
64 | color: 'white',
|
65 | interval: 100000
|
66 | });
|
67 |
|
68 | let exitCode = 0;
|
69 |
|
70 | delete config._;
|
71 | delete config.config;
|
72 | delete config.configs;
|
73 |
|
74 | if (rc.config) {
|
75 | log('using runtime config %s', rc.config);
|
76 | }
|
77 |
|
78 | if (program.basedir) {
|
79 | config.baseDir = program.basedir;
|
80 | }
|
81 |
|
82 | if (program.exclude) {
|
83 | config.excludeRegExp = [program.exclude];
|
84 | }
|
85 |
|
86 | if (program.extensions) {
|
87 | config.fileExtensions = program.extensions.split(',').map((s) => s.trim());
|
88 | }
|
89 |
|
90 | if (program.requireConfig) {
|
91 | config.requireConfig = program.requireConfig;
|
92 | }
|
93 |
|
94 | if (program.webpackConfig) {
|
95 | config.webpackConfig = program.webpackConfig;
|
96 | }
|
97 |
|
98 | if (program.includeNpm) {
|
99 | config.includeNpm = program.includeNpm;
|
100 | }
|
101 |
|
102 | if (!program.color) {
|
103 | config.backgroundColor = '#ffffff';
|
104 | config.nodeColor = '#00000';
|
105 | config.noDependencyColor = '#00000';
|
106 | config.cyclicNodeColor = '#000000';
|
107 | config.edgeColor = '#757575';
|
108 | }
|
109 |
|
110 | function dependencyFilter() {
|
111 | let prevFile;
|
112 |
|
113 | return (dependencyFilePath, traversedFilePath, baseDir) => {
|
114 | if (prevFile !== traversedFilePath) {
|
115 | const relPath = path.relative(baseDir, traversedFilePath);
|
116 | const dir = path.dirname(relPath) + '/';
|
117 | const file = path.basename(relPath);
|
118 |
|
119 | spinner.text = chalk.grey(dir) + chalk.cyan(file);
|
120 | spinner.render();
|
121 | prevFile = traversedFilePath;
|
122 | }
|
123 | };
|
124 | }
|
125 |
|
126 | new Promise((resolve, reject) => {
|
127 | if (program.stdin) {
|
128 | let buffer = '';
|
129 |
|
130 | process.stdin
|
131 | .resume()
|
132 | .setEncoding('utf8')
|
133 | .on('data', (chunk) => {
|
134 | buffer += chunk;
|
135 | })
|
136 | .on('end', () => {
|
137 | try {
|
138 | resolve(JSON.parse(buffer));
|
139 | } catch (e) {
|
140 | reject(e);
|
141 | }
|
142 | });
|
143 | } else {
|
144 | resolve(program.args);
|
145 | }
|
146 | })
|
147 | .then((src) => {
|
148 | if (!program.json && !program.dot) {
|
149 | spinner.start();
|
150 | config.dependencyFilter = dependencyFilter();
|
151 | }
|
152 |
|
153 | return madge(src, config);
|
154 | })
|
155 | .then((res) => {
|
156 | if (!program.json && !program.dot) {
|
157 | spinner.stop();
|
158 | output.getResultSummary(res, startTime);
|
159 | }
|
160 |
|
161 | if (program.summary) {
|
162 | output.summary(res.obj(), {
|
163 | json: program.json
|
164 | });
|
165 |
|
166 | return res;
|
167 | }
|
168 |
|
169 | if (program.depends) {
|
170 | output.modules(res.depends(program.depends), {
|
171 | json: program.json
|
172 | });
|
173 |
|
174 | return res;
|
175 | }
|
176 |
|
177 | if (program.orphans) {
|
178 | output.modules(res.orphans(), {
|
179 | json: program.json
|
180 | });
|
181 |
|
182 | return res;
|
183 | }
|
184 |
|
185 | if (program.circular) {
|
186 | const circular = res.circular();
|
187 |
|
188 | output.circular(spinner, res, circular, {
|
189 | json: program.json
|
190 | });
|
191 |
|
192 | if (circular.length) {
|
193 | exitCode = 1;
|
194 | }
|
195 |
|
196 | return res;
|
197 | }
|
198 |
|
199 | if (program.image) {
|
200 | return res.image(program.image).then((imagePath) => {
|
201 | spinner.succeed(`${chalk.bold('Image created at')} ${chalk.cyan.bold(imagePath)}`);
|
202 | return res;
|
203 | });
|
204 | }
|
205 |
|
206 | if (program.dot) {
|
207 | return res.dot().then((output) => {
|
208 | process.stdout.write(output);
|
209 | return res;
|
210 | });
|
211 | }
|
212 |
|
213 | output.list(res.obj(), {
|
214 | json: program.json
|
215 | });
|
216 |
|
217 | return res;
|
218 | })
|
219 | .then((res) => {
|
220 | if (program.warning && !program.json) {
|
221 | output.warnings(res);
|
222 | }
|
223 |
|
224 | if (!program.json && !program.dot) {
|
225 | console.log('');
|
226 | }
|
227 |
|
228 | process.exit(exitCode);
|
229 | })
|
230 | .catch((err) => {
|
231 | spinner.stop();
|
232 | console.log('\n%s %s\n', chalk.red('✖'), err.stack);
|
233 | process.exit(1);
|
234 | });
|