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('--ts-config <file>', 'path to typescript config')
|
30 | .option('--include-npm', 'include shallow NPM modules', false)
|
31 | .option('--no-color', 'disable color in output and image', false)
|
32 | .option('--no-spinner', 'disable progress spinner', false)
|
33 | .option('--stdin', 'read predefined tree from STDIN', false)
|
34 | .option('--warning', 'show warnings about skipped files', false)
|
35 | .option('--debug', 'turn on debug output', false)
|
36 | .parse(process.argv);
|
37 |
|
38 | if (!program.args.length && !program.stdin) {
|
39 | console.log(program.helpInformation());
|
40 | process.exit(1);
|
41 | }
|
42 |
|
43 | if (program.debug) {
|
44 | process.env.DEBUG = '*';
|
45 | }
|
46 |
|
47 | if (!program.color) {
|
48 | process.env.DEBUG_COLORS = false;
|
49 | }
|
50 |
|
51 | const log = require('../lib/log');
|
52 | const output = require('../lib/output');
|
53 | const madge = require('../lib/api');
|
54 | const config = Object.assign({}, rc);
|
55 |
|
56 | program.options.forEach((opt) => {
|
57 | const name = opt.name();
|
58 |
|
59 | if (program[name]) {
|
60 | config[name] = program[name];
|
61 | }
|
62 | });
|
63 |
|
64 | const spinner = ora({
|
65 | text: 'Finding files',
|
66 | color: 'white',
|
67 | interval: 100000,
|
68 | isEnabled: program.spinner
|
69 | });
|
70 |
|
71 | let exitCode = 0;
|
72 |
|
73 | delete config._;
|
74 | delete config.config;
|
75 | delete config.configs;
|
76 |
|
77 | if (rc.config) {
|
78 | log('using runtime config %s', rc.config);
|
79 | }
|
80 |
|
81 | if (program.basedir) {
|
82 | config.baseDir = program.basedir;
|
83 | }
|
84 |
|
85 | if (program.exclude) {
|
86 | config.excludeRegExp = [program.exclude];
|
87 | }
|
88 |
|
89 | if (program.extensions) {
|
90 | config.fileExtensions = program.extensions.split(',').map((s) => s.trim());
|
91 | }
|
92 |
|
93 | if (program.requireConfig) {
|
94 | config.requireConfig = program.requireConfig;
|
95 | }
|
96 |
|
97 | if (program.webpackConfig) {
|
98 | config.webpackConfig = program.webpackConfig;
|
99 | }
|
100 |
|
101 | if (program.tsConfig) {
|
102 | config.tsConfig = program.tsConfig;
|
103 | }
|
104 |
|
105 | if (program.includeNpm) {
|
106 | config.includeNpm = program.includeNpm;
|
107 | }
|
108 |
|
109 | if (!program.color) {
|
110 | config.backgroundColor = '#ffffff';
|
111 | config.nodeColor = '#000000';
|
112 | config.noDependencyColor = '#000000';
|
113 | config.cyclicNodeColor = '#000000';
|
114 | config.edgeColor = '#757575';
|
115 | }
|
116 |
|
117 | function dependencyFilter() {
|
118 | let prevFile;
|
119 |
|
120 | return (dependencyFilePath, traversedFilePath, baseDir) => {
|
121 | if (prevFile !== traversedFilePath) {
|
122 | const relPath = path.relative(baseDir, traversedFilePath);
|
123 | const dir = path.dirname(relPath) + '/';
|
124 | const file = path.basename(relPath);
|
125 |
|
126 | if (program.spinner) {
|
127 | spinner.text = chalk.grey(dir) + chalk.cyan(file);
|
128 | spinner.render();
|
129 | }
|
130 | prevFile = traversedFilePath;
|
131 | }
|
132 | };
|
133 | }
|
134 |
|
135 | new Promise((resolve, reject) => {
|
136 | if (program.stdin) {
|
137 | let buffer = '';
|
138 |
|
139 | process.stdin
|
140 | .resume()
|
141 | .setEncoding('utf8')
|
142 | .on('data', (chunk) => {
|
143 | buffer += chunk;
|
144 | })
|
145 | .on('end', () => {
|
146 | try {
|
147 | resolve(JSON.parse(buffer));
|
148 | } catch (e) {
|
149 | reject(e);
|
150 | }
|
151 | });
|
152 | } else {
|
153 | resolve(program.args);
|
154 | }
|
155 | })
|
156 | .then((src) => {
|
157 | if (!program.json && !program.dot) {
|
158 | spinner.start();
|
159 | config.dependencyFilter = dependencyFilter();
|
160 | }
|
161 |
|
162 | return madge(src, config);
|
163 | })
|
164 | .then((res) => {
|
165 | if (!program.json && !program.dot) {
|
166 | spinner.stop();
|
167 | output.getResultSummary(res, startTime);
|
168 | }
|
169 |
|
170 | if (program.summary) {
|
171 | output.summary(res.obj(), {
|
172 | json: program.json
|
173 | });
|
174 |
|
175 | return res;
|
176 | }
|
177 |
|
178 | if (program.depends) {
|
179 | output.modules(res.depends(program.depends), {
|
180 | json: program.json
|
181 | });
|
182 |
|
183 | return res;
|
184 | }
|
185 |
|
186 | if (program.orphans) {
|
187 | output.modules(res.orphans(), {
|
188 | json: program.json
|
189 | });
|
190 |
|
191 | return res;
|
192 | }
|
193 |
|
194 | if (program.circular) {
|
195 | const circular = res.circular();
|
196 |
|
197 | output.circular(spinner, res, circular, {
|
198 | json: program.json
|
199 | });
|
200 |
|
201 | if (circular.length) {
|
202 | exitCode = 1;
|
203 | }
|
204 |
|
205 | return res;
|
206 | }
|
207 |
|
208 | if (program.image) {
|
209 | return res.image(program.image).then((imagePath) => {
|
210 | spinner.succeed(`${chalk.bold('Image created at')} ${chalk.cyan.bold(imagePath)}`);
|
211 | return res;
|
212 | });
|
213 | }
|
214 |
|
215 | if (program.dot) {
|
216 | return res.dot().then((output) => {
|
217 | process.stdout.write(output);
|
218 | return res;
|
219 | });
|
220 | }
|
221 |
|
222 | output.list(res.obj(), {
|
223 | json: program.json
|
224 | });
|
225 |
|
226 | return res;
|
227 | })
|
228 | .then((res) => {
|
229 | if (program.warning && !program.json) {
|
230 | output.warnings(res);
|
231 | }
|
232 |
|
233 | if (!program.json && !program.dot) {
|
234 | console.log('');
|
235 | }
|
236 |
|
237 | process.exit(exitCode);
|
238 | })
|
239 | .catch((err) => {
|
240 | spinner.stop();
|
241 | console.log('\n%s %s\n', chalk.red('✖'), err.stack);
|
242 | process.exit(1);
|
243 | });
|