├── .nvmrc
├── test
├── cjs
│ ├── c.js
│ ├── circular
│ │ ├── c.js
│ │ ├── foo.json
│ │ ├── b.js
│ │ ├── d.js
│ │ ├── foo.js
│ │ └── a.js
│ ├── b.js
│ ├── node_modules
│ │ ├── a.js
│ │ ├── b.js
│ │ ├── c.js
│ │ ├── y.js
│ │ ├── doom.js
│ │ └── events2.js
│ ├── normal
│ │ ├── d.js
│ │ ├── a.js
│ │ └── sub
│ │ │ ├── b.js
│ │ │ └── c.js
│ ├── error.js
│ ├── multibase
│ │ ├── 1
│ │ │ └── a.js
│ │ └── 2
│ │ │ └── b.js
│ ├── a.js
│ ├── core.js
│ ├── npm.js
│ ├── missing.js
│ ├── both.js
│ ├── chained.js
│ ├── word.js
│ ├── strings.js
│ └── nested.js
├── git
│ ├── c.js
│ ├── .git_tmp
│ │ └── d.js
│ ├── b.js
│ └── a.js
├── amd
│ ├── ok
│ │ ├── d.js
│ │ ├── a.js
│ │ ├── e.js
│ │ └── sub
│ │ │ ├── c.js
│ │ │ └── b.js
│ ├── requirejs
│ │ ├── vendor
│ │ │ ├── baz.js
│ │ │ ├── quux.js
│ │ │ ├── jquery-2.0.3.js
│ │ │ ├── jquery.bar-1.0.js
│ │ │ └── jquery.foo-1.0.js
│ │ ├── a.js
│ │ ├── orphans
│ │ │ ├── b.js
│ │ │ ├── c.js
│ │ │ └── a.js
│ │ └── config.js
│ ├── circular
│ │ ├── d.js
│ │ ├── b.js
│ │ ├── c.js
│ │ ├── e.js
│ │ ├── f.js
│ │ ├── g.js
│ │ ├── h.js
│ │ ├── a.js
│ │ └── main.js
│ ├── circularAlias
│ │ ├── dos.js
│ │ ├── x86.js
│ │ └── config.js
│ ├── nested
│ │ ├── b.js
│ │ ├── a.js
│ │ └── main.js
│ ├── circularRelative
│ │ ├── a.js
│ │ └── foo
│ │ │ └── b.js
│ ├── plugin.js
│ └── amdes6.js
├── es7
│ ├── other.js
│ └── async.js
├── es6
│ ├── webpack
│ │ ├── src
│ │ │ └── sub
│ │ │ │ ├── abs.js
│ │ │ │ ├── index.js
│ │ │ │ └── rel.js
│ │ └── webpack.config.js
│ ├── absolute
│ │ ├── b.js
│ │ └── a.js
│ ├── circular
│ │ ├── a.js
│ │ ├── b.js
│ │ └── c.js
│ ├── error.js
│ ├── absolute.js
│ └── re-export
│ │ ├── b-star.js
│ │ ├── b-named.js
│ │ ├── b-default.js
│ │ ├── a.js
│ │ └── c.js
├── vue
│ ├── one.ts
│ ├── two.js
│ ├── TwoNested.vue
│ ├── ThreeNested.vue
│ ├── BasicComponent.vue
│ ├── BasicComponentTs.vue
│ ├── OneNested.vue
│ └── OneNestedTs.vue
├── typescript
│ ├── with-config
│ │ ├── index.ts
│ │ ├── tsconfig.base.json
│ │ └── tsconfig.json
│ ├── custom-paths
│ │ ├── subfolder
│ │ │ ├── index.ts
│ │ │ └── require.tsx
│ │ ├── subfolder2
│ │ │ └── export.ts
│ │ └── import.ts
│ ├── mixed.ts
│ ├── export-x.tsx
│ ├── export.ts
│ ├── require.ts
│ ├── require-x.tsx
│ └── import.ts
├── jsx
│ ├── other.jsx
│ └── basic.jsx
├── flow
│ ├── cjs
│ │ ├── geometry.js
│ │ ├── calc.js
│ │ └── math.js
│ └── es
│ │ ├── calc.js
│ │ └── math.js
├── es7.js
├── jsx.js
├── flow.js
├── vue.js
├── output.sh
├── es6.js
├── cjs.js
├── typescript.js
├── amd.js
└── api.js
├── lib
├── log.js
├── cyclic.js
├── output.js
├── graph.js
├── api.js
└── tree.js
├── .gitignore
├── .eslintrc
├── .release-it.json
├── .editorconfig
├── .github
└── workflows
│ └── nodejs.yml
├── LICENSE
├── package.json
├── bin
└── cli.js
├── README.md
└── CHANGELOG.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20
2 |
--------------------------------------------------------------------------------
/test/cjs/c.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/git/c.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/amd/ok/d.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/es7/other.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cjs/circular/c.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/git/.git_tmp/d.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cjs/b.js:
--------------------------------------------------------------------------------
1 | require('./c');
--------------------------------------------------------------------------------
/test/cjs/circular/foo.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/test/cjs/node_modules/a.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cjs/node_modules/b.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cjs/node_modules/c.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cjs/node_modules/y.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/git/b.js:
--------------------------------------------------------------------------------
1 | require('./c');
--------------------------------------------------------------------------------
/test/amd/requirejs/vendor/baz.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/amd/requirejs/vendor/quux.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cjs/node_modules/doom.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cjs/node_modules/events2.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/es6/webpack/src/sub/abs.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/vue/one.ts:
--------------------------------------------------------------------------------
1 | // no content
2 |
--------------------------------------------------------------------------------
/test/vue/two.js:
--------------------------------------------------------------------------------
1 | // no content
2 |
--------------------------------------------------------------------------------
/test/typescript/with-config/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/amd/requirejs/vendor/jquery-2.0.3.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/amd/requirejs/vendor/jquery.bar-1.0.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/amd/requirejs/vendor/jquery.foo-1.0.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/cjs/normal/d.js:
--------------------------------------------------------------------------------
1 | module.exports = 'D';
--------------------------------------------------------------------------------
/test/es6/absolute/b.js:
--------------------------------------------------------------------------------
1 | export const B = 'B';
--------------------------------------------------------------------------------
/test/jsx/other.jsx:
--------------------------------------------------------------------------------
1 | export default 'other';
--------------------------------------------------------------------------------
/test/cjs/circular/b.js:
--------------------------------------------------------------------------------
1 | var c = require('./c');
--------------------------------------------------------------------------------
/test/cjs/circular/d.js:
--------------------------------------------------------------------------------
1 | var a = require('./a');
--------------------------------------------------------------------------------
/test/cjs/circular/foo.js:
--------------------------------------------------------------------------------
1 | require('./foo.json');
--------------------------------------------------------------------------------
/test/cjs/error.js:
--------------------------------------------------------------------------------
1 | if (x) {
2 | return;
3 | }
--------------------------------------------------------------------------------
/test/cjs/multibase/1/a.js:
--------------------------------------------------------------------------------
1 | module.exports = 'A';
--------------------------------------------------------------------------------
/test/cjs/multibase/2/b.js:
--------------------------------------------------------------------------------
1 | module.exports = 'B';
--------------------------------------------------------------------------------
/test/es6/circular/a.js:
--------------------------------------------------------------------------------
1 | import * as B from './b';
--------------------------------------------------------------------------------
/test/es6/circular/b.js:
--------------------------------------------------------------------------------
1 | import * as C from './c';
--------------------------------------------------------------------------------
/test/es6/circular/c.js:
--------------------------------------------------------------------------------
1 | import * as A from './a';
--------------------------------------------------------------------------------
/test/es6/error.js:
--------------------------------------------------------------------------------
1 | if (x) {
2 | return;
3 | }
--------------------------------------------------------------------------------
/test/cjs/a.js:
--------------------------------------------------------------------------------
1 | require('./b');
2 | require('./c');
--------------------------------------------------------------------------------
/test/es6/absolute.js:
--------------------------------------------------------------------------------
1 | import {A} from 'absolute/a';
--------------------------------------------------------------------------------
/test/es6/re-export/b-star.js:
--------------------------------------------------------------------------------
1 | export * from './a'
2 |
--------------------------------------------------------------------------------
/test/es6/re-export/b-named.js:
--------------------------------------------------------------------------------
1 | export { named } from './a';
--------------------------------------------------------------------------------
/test/es6/re-export/b-default.js:
--------------------------------------------------------------------------------
1 | export someDefault from './a'
--------------------------------------------------------------------------------
/test/es6/webpack/src/sub/index.js:
--------------------------------------------------------------------------------
1 | import rel from './rel.js';
--------------------------------------------------------------------------------
/test/es6/webpack/src/sub/rel.js:
--------------------------------------------------------------------------------
1 | import abs from 'sub/abs.js';
--------------------------------------------------------------------------------
/test/amd/circular/d.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | return 'D';
3 | });
--------------------------------------------------------------------------------
/test/cjs/core.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var a = require('a');
--------------------------------------------------------------------------------
/test/vue/TwoNested.vue:
--------------------------------------------------------------------------------
1 | Content without deps
2 |
--------------------------------------------------------------------------------
/test/amd/circularAlias/dos.js:
--------------------------------------------------------------------------------
1 | define(['cpu'], function(cpu) {
2 | });
3 |
--------------------------------------------------------------------------------
/test/amd/nested/b.js:
--------------------------------------------------------------------------------
1 | define('b', function () {
2 | return 'b';
3 | });
--------------------------------------------------------------------------------
/test/amd/ok/a.js:
--------------------------------------------------------------------------------
1 | define(['sub/b'], function (B) {
2 | return 'A';
3 | });
--------------------------------------------------------------------------------
/test/amd/ok/e.js:
--------------------------------------------------------------------------------
1 | define(['sub/c'], function (C) {
2 | return 'E';
3 | });
--------------------------------------------------------------------------------
/test/amd/ok/sub/c.js:
--------------------------------------------------------------------------------
1 | define(['d'], function (D) {
2 | return 'C';
3 | });
--------------------------------------------------------------------------------
/test/cjs/npm.js:
--------------------------------------------------------------------------------
1 | var a = require('a');
2 | var d = require('./normal/d');
--------------------------------------------------------------------------------
/test/typescript/custom-paths/subfolder/index.ts:
--------------------------------------------------------------------------------
1 | export default 42;
2 |
--------------------------------------------------------------------------------
/test/vue/ThreeNested.vue:
--------------------------------------------------------------------------------
1 | Content without deps
2 |
--------------------------------------------------------------------------------
/test/amd/circular/b.js:
--------------------------------------------------------------------------------
1 | define(['d'], function (D) {
2 | return 'B';
3 | });
--------------------------------------------------------------------------------
/test/amd/circular/c.js:
--------------------------------------------------------------------------------
1 | define(['a'], function (A) {
2 | return 'C';
3 | });
--------------------------------------------------------------------------------
/test/amd/circular/e.js:
--------------------------------------------------------------------------------
1 | define(['f'], function (F) {
2 | return 'E';
3 | });
--------------------------------------------------------------------------------
/test/amd/circular/f.js:
--------------------------------------------------------------------------------
1 | define(['g'], function (G) {
2 | return 'F';
3 | });
--------------------------------------------------------------------------------
/test/amd/circular/g.js:
--------------------------------------------------------------------------------
1 | define(['h'], function (H) {
2 | return 'G';
3 | });
--------------------------------------------------------------------------------
/test/amd/circular/h.js:
--------------------------------------------------------------------------------
1 | define(['f'], function (F) {
2 | return 'H';
3 | });
--------------------------------------------------------------------------------
/test/amd/circularAlias/x86.js:
--------------------------------------------------------------------------------
1 | define(['jsdos'], function(dos) {
2 | });
3 |
--------------------------------------------------------------------------------
/test/amd/nested/a.js:
--------------------------------------------------------------------------------
1 | define('a', function () {
2 | return 'A';
3 | });
4 |
--------------------------------------------------------------------------------
/test/amd/ok/sub/b.js:
--------------------------------------------------------------------------------
1 | define(['sub/c'], function (C) {
2 | return 'B';
3 | });
--------------------------------------------------------------------------------
/test/cjs/missing.js:
--------------------------------------------------------------------------------
1 | require('./c');
2 | require('./path/non/existing/file');
--------------------------------------------------------------------------------
/test/cjs/normal/a.js:
--------------------------------------------------------------------------------
1 | var b = require('./sub/b');
2 |
3 | module.exports = 'A';
--------------------------------------------------------------------------------
/test/cjs/normal/sub/b.js:
--------------------------------------------------------------------------------
1 | var c = require('./c');
2 |
3 | module.exports = 'B';
--------------------------------------------------------------------------------
/test/flow/cjs/geometry.js:
--------------------------------------------------------------------------------
1 | export type Square = {
2 | side: number
3 | };
4 |
--------------------------------------------------------------------------------
/test/git/a.js:
--------------------------------------------------------------------------------
1 | require('./b');
2 | require('./c');
3 | require('./.git/d');
--------------------------------------------------------------------------------
/test/typescript/mixed.ts:
--------------------------------------------------------------------------------
1 | require('./export');
2 | import './export-x';
3 |
--------------------------------------------------------------------------------
/lib/log.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('debug')('madge');
4 |
--------------------------------------------------------------------------------
/test/amd/circular/a.js:
--------------------------------------------------------------------------------
1 | define(['b', 'c'], function (B, C) {
2 | return 'A';
3 | });
--------------------------------------------------------------------------------
/test/amd/requirejs/a.js:
--------------------------------------------------------------------------------
1 | define(['jquery'], function ($) {
2 | return 'A';
3 | });
--------------------------------------------------------------------------------
/test/cjs/normal/sub/c.js:
--------------------------------------------------------------------------------
1 | var d = require('../d');
2 |
3 | module.exports = 'C';
--------------------------------------------------------------------------------
/test/es7/async.js:
--------------------------------------------------------------------------------
1 | import {OTHER} from './other';
2 | async function foo() {}
3 |
--------------------------------------------------------------------------------
/test/amd/circular/main.js:
--------------------------------------------------------------------------------
1 | define(['a', 'f'], function () {
2 | return 'MAIN';
3 | });
--------------------------------------------------------------------------------
/test/amd/requirejs/orphans/b.js:
--------------------------------------------------------------------------------
1 | define([], function () {
2 | return 'B';
3 | });
4 |
--------------------------------------------------------------------------------
/test/amd/requirejs/orphans/c.js:
--------------------------------------------------------------------------------
1 | define([], function () {
2 | return 'C';
3 | });
4 |
--------------------------------------------------------------------------------
/test/amd/circularRelative/a.js:
--------------------------------------------------------------------------------
1 | define('a', ['./foo/b'], function () {
2 | return 'A';
3 | });
--------------------------------------------------------------------------------
/test/es6/absolute/a.js:
--------------------------------------------------------------------------------
1 | import {B} from 'test/es6/absolute/b';
2 |
3 | export const A = 'A';
--------------------------------------------------------------------------------
/test/flow/es/calc.js:
--------------------------------------------------------------------------------
1 | import {add} from "./math.js";
2 |
3 | var four: number = add(2, 2);
--------------------------------------------------------------------------------
/test/amd/circularRelative/foo/b.js:
--------------------------------------------------------------------------------
1 | define('foo/b', ['../a'], function () {
2 | return 'B';
3 | });
--------------------------------------------------------------------------------
/test/amd/requirejs/orphans/a.js:
--------------------------------------------------------------------------------
1 | define(['orphans/b'], function () {
2 | return 'A';
3 | });
4 |
--------------------------------------------------------------------------------
/test/es6/re-export/a.js:
--------------------------------------------------------------------------------
1 | export default 'default export value';
2 | export const named = 'named';
--------------------------------------------------------------------------------
/test/cjs/circular/a.js:
--------------------------------------------------------------------------------
1 | var b = require('./b');
2 | var c = require('./c');
3 | var d = require('./d');
--------------------------------------------------------------------------------
/test/amd/plugin.js:
--------------------------------------------------------------------------------
1 | define(['ok/d', './locale!locales/not/exists'], function (D) {
2 | return 'p';
3 | });
--------------------------------------------------------------------------------
/test/cjs/both.js:
--------------------------------------------------------------------------------
1 | require('a');
2 | require('b');
3 | require('c'+x);
4 | var moo = require('d'+y).moo;
5 |
--------------------------------------------------------------------------------
/test/cjs/chained.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | require('c').hello().goodbye()
4 | require('b').hello()
5 | require('a')
6 |
--------------------------------------------------------------------------------
/test/typescript/export-x.tsx:
--------------------------------------------------------------------------------
1 | export default function(props) {
2 | return {props.children}
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .DS_Store
3 | .vscode
4 | .madgerc
5 | *.sublime-project
6 | *.sublime-workspace
7 | .idea
8 |
--------------------------------------------------------------------------------
/test/amd/circularAlias/config.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | paths: {
3 | 'cpu': 'x86',
4 | 'jsdos':'dos'
5 | }
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/test/amd/nested/main.js:
--------------------------------------------------------------------------------
1 | define(['require', 'a'], function(require, a) {
2 | require(['b'], function (b) {
3 |
4 | });
5 | });
--------------------------------------------------------------------------------
/test/es6/re-export/c.js:
--------------------------------------------------------------------------------
1 | import { named } from './b-named'
2 | import { someDefault } from './b-default'
3 | import star from './b-star'
4 |
--------------------------------------------------------------------------------
/test/typescript/export.ts:
--------------------------------------------------------------------------------
1 | class ExportClass {
2 | stringLength(s: string) {
3 | return s.length;
4 | }
5 | }
6 |
7 | export default ExportClass;
8 |
--------------------------------------------------------------------------------
/test/typescript/with-config/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "module": "CommonJS"
5 | }
6 | }
--------------------------------------------------------------------------------
/test/typescript/custom-paths/subfolder/require.tsx:
--------------------------------------------------------------------------------
1 | import x from '@shortcut2/export';
2 |
3 | export default function(props) {
4 | return
{props.children}
5 | }
6 |
--------------------------------------------------------------------------------
/test/typescript/custom-paths/subfolder2/export.ts:
--------------------------------------------------------------------------------
1 | class ExportClass {
2 | stringLength(s: string) {
3 | return s.length;
4 | }
5 | }
6 |
7 | export default ExportClass;
8 |
--------------------------------------------------------------------------------
/test/typescript/require.ts:
--------------------------------------------------------------------------------
1 | import x = require('./export');
2 |
3 | export default class RequireClass {
4 | stringLength(s: string) {
5 | return s.length;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/flow/cjs/calc.js:
--------------------------------------------------------------------------------
1 | // == calc.js == //
2 |
3 | import type { Square } from './geometry.js';
4 | var Math = require('./math.js');
5 |
6 | var four: number = Math.add(2, 2);
7 |
--------------------------------------------------------------------------------
/test/typescript/with-config/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "compilerOptions": {
4 | "target": "ESNext",
5 | "allowJs": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/es6/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | module.exports = {
6 | resolve: {
7 | root: path.resolve(__dirname, './src')
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@aptoma/eslint-config",
3 | "parserOptions": {
4 | "ecmaVersion": 9
5 | },
6 | "env": {
7 | "node": true,
8 | "mocha": true,
9 | "es6": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/amd/amdes6.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'ok/d'
3 | ], function(a) {
4 | 'use strict';
5 |
6 | var x = [1, 2, 3, 4]
7 |
8 | for (var i of x) {
9 | x++;
10 | }
11 |
12 | return x;
13 | });
14 |
--------------------------------------------------------------------------------
/test/typescript/require-x.tsx:
--------------------------------------------------------------------------------
1 | import * as x from './export';
2 | import SpanWrapper from './export-x';
3 |
4 | export default function(props) {
5 | return {props.children}
6 | }
7 |
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "pkgFiles": ["package.json", "package-lock.json"],
3 | "git": {
4 | "requireCleanWorkingDir": false
5 | },
6 | "hooks": {
7 | "after:bump": "auto-changelog --hide-credit --package"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/vue/BasicComponent.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/vue/BasicComponentTs.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/typescript/import.ts:
--------------------------------------------------------------------------------
1 | import B from './require';
2 | import heading from './require-x';
3 |
4 | class ImportClass {
5 | constructor(public greeting: string) { }
6 | greet() {
7 | return "" + this.greeting + " ";
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/test/jsx/basic.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import other from './other.jsx';
3 |
4 | export default React.createClass({
5 | render: function() {
6 | return
7 | foo
8 | {this.props.children}
9 |
10 | }
11 | });
12 |
--------------------------------------------------------------------------------
/test/vue/OneNested.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {{ count }}
11 |
12 |
--------------------------------------------------------------------------------
/test/vue/OneNestedTs.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {{ count }}
11 |
12 |
--------------------------------------------------------------------------------
/test/typescript/custom-paths/import.ts:
--------------------------------------------------------------------------------
1 | import heading from '@shortcut/require';
2 | import fortyTwo from './subfolder';
3 |
4 | class ImportClass {
5 | constructor(public greeting: string) { }
6 | greet() {
7 | return "" + this.greeting + " ";
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = tab
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [{package.json,.travis.yml,.github/**/*.yml}]
12 | indent_style = space
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/test/cjs/word.js:
--------------------------------------------------------------------------------
1 | var a = load('a');
2 | var b = load('b');
3 | var c = load('c');
4 | var abc = a.b(c);
5 |
6 | var EventEmitter = load('events').EventEmitter;
7 |
8 | var x = load('doom')(5,6,7);
9 | x(8,9);
10 | c.load('notthis');
11 | var y = load('y') * 100;
12 |
13 | var EventEmitter2 = load('events2').EventEmitter();
14 |
--------------------------------------------------------------------------------
/test/cjs/strings.js:
--------------------------------------------------------------------------------
1 | var a = require('a');
2 | var b = require('b');
3 | var c = require('c');
4 | var abc = a.b(c);
5 |
6 | var EventEmitter = require('events').EventEmitter;
7 |
8 | var x = require('doom')(5,6,7);
9 | x(8,9);
10 | c.require('notthis');
11 | var y = require('y') * 100;
12 |
13 | var EventEmitter2 = require('events2').EventEmitter();
--------------------------------------------------------------------------------
/test/flow/cjs/math.js:
--------------------------------------------------------------------------------
1 | // == `math.js` == //
2 |
3 | function add(num1: number, num2: number): number {
4 | return num1 + num2;
5 | };
6 | // This is how we export the `add()` function in CommonJS
7 | exports.add = add;
8 |
9 | function sub(num1, num2) {
10 | return num1 - num2;
11 | }
12 |
13 | var two: number = add(1, 2);
14 | var one: number = sub(2, 1);
--------------------------------------------------------------------------------
/test/cjs/nested.js:
--------------------------------------------------------------------------------
1 |
2 | if (true) {
3 | (function () {
4 | require('a');
5 | })();
6 | }
7 | if (false) {
8 | (function () {
9 | var x = 10;
10 | switch (x) {
11 | case 1 : require('b'); break;
12 | default : break;
13 | }
14 | })()
15 | }
16 |
17 | function qqq () {
18 | require
19 | (
20 | "c"
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/test/es7.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const madge = require('../lib/api');
5 | require('should');
6 |
7 | describe('ES7', () => {
8 | const dir = __dirname + '/es7';
9 |
10 | it('extracts dependencies', (done) => {
11 | madge(dir + '/async.js').then((res) => {
12 | res.obj().should.eql({
13 | 'other.js': [],
14 | 'async.js': ['other.js']
15 | });
16 | done();
17 | }).catch(done);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/jsx.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const madge = require('../lib/api');
5 | require('should');
6 |
7 | describe('JSX', () => {
8 | const dir = __dirname + '/jsx';
9 |
10 | it('finds import in JSX files', (done) => {
11 | madge(dir + '/basic.jsx').then((res) => {
12 | res.obj().should.eql({
13 | 'basic.jsx': ['other.jsx'],
14 | 'other.jsx': []
15 | });
16 | done();
17 | }).catch(done);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/amd/requirejs/config.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | paths: {
3 | 'jquery': 'vendor/jquery-2.0.3',
4 | 'jquery.foo': 'vendor/jquery.foo-1.0',
5 | 'jquery.bar': 'vendor/jquery.bar-1.0',
6 | 'baz': 'vendor/baz',
7 | 'quux': 'vendor/quux'
8 | },
9 | shim: {
10 | 'jquery': { exports: '$' },
11 | 'jquery.foo': { deps: ['jquery'] },
12 | 'jquery.bar': { deps: ['jquery'] },
13 | 'baz': { exports: 'baz', deps: ['quux'] },
14 | 'quux': { exports: 'quux' }
15 | }
16 | });
--------------------------------------------------------------------------------
/test/flow/es/math.js:
--------------------------------------------------------------------------------
1 | // == `math.js` == //
2 |
3 | // This function is exported, so it's available for other modules to import
4 | export function add(num1: number, num2: number): number {
5 | return num1 + num2;
6 | };
7 |
8 | // This function isn't exported, so it's only available in the local scope
9 | // of this module
10 | function sub(num1, num2) {
11 | return num1 - num2;
12 | }
13 |
14 | // Note that we can use both exported and non-exported items within this
15 | // module
16 | var two: number = add(1, 2);
17 | var one: number = sub(2, 1);
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | build:
7 | strategy:
8 | matrix:
9 | os: [ubuntu-latest]
10 | node-version: [18.x, 20.x]
11 |
12 | runs-on: ${{ matrix.os }}
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: ${{ matrix.os }} / Node.js ${{ matrix.node-version }}
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 | - name: npm install, build, and test
22 | run: |
23 | sudo apt install -y --no-install-recommends graphviz
24 | npm i -g npm@9
25 | npm ci
26 | npm run test
27 | npm run debug
28 | npm run generate
29 |
--------------------------------------------------------------------------------
/test/flow.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const madge = require('../lib/api');
5 | require('should');
6 |
7 | describe('Flow', () => {
8 | const dir = __dirname + '/flow';
9 |
10 | it('extracts ES module ependencies', (done) => {
11 | madge(dir + '/es/calc.js').then((res) => {
12 | res.obj().should.eql({
13 | 'math.js': [],
14 | 'calc.js': ['math.js']
15 | });
16 | done();
17 | }).catch(done);
18 | });
19 |
20 | it('extracts CommonsJS module dependencies', (done) => {
21 | madge(dir + '/cjs/calc.js').then((res) => {
22 | res.obj().should.eql({
23 | 'geometry.js': [],
24 | 'calc.js': ['geometry.js']
25 | });
26 | done();
27 | }).catch(done);
28 | });
29 |
30 | it('extracts CommonsJS module dependencies with mixed import syntax', (done) => {
31 | madge(dir + '/cjs/calc.js', {detectiveOptions: {es6: {mixedImports: true}}}).then((res) => {
32 | res.obj().should.eql({
33 | 'geometry.js': [],
34 | 'math.js': [],
35 | 'calc.js': ['geometry.js', 'math.js']
36 | });
37 | done();
38 | }).catch(done);
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Patrik Henningsson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/vue.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const madge = require('../lib/api');
5 | require('should');
6 |
7 | describe('Vue', () => {
8 | const dir = __dirname + '/vue';
9 | // this seems necessary to run the tests successfully
10 | const emptyTsConfig = {compilerOptions: {}};
11 |
12 | it('finds import in Vue files using TS', (done) => {
13 | madge(dir + '/BasicComponentTs.vue', {tsConfig: emptyTsConfig}).then((res) => {
14 | res.obj().should.eql({
15 | 'one.ts': [],
16 | 'BasicComponentTs.vue': ['OneNestedTs.vue', 'TwoNested.vue'],
17 | 'OneNestedTs.vue': ['ThreeNested.vue', 'one.ts'],
18 | 'ThreeNested.vue': [],
19 | 'TwoNested.vue': []
20 | });
21 | done();
22 | }).catch(done);
23 | });
24 |
25 | it('finds import in Vue files', (done) => {
26 | madge(dir + '/BasicComponent.vue', {tsConfig: emptyTsConfig}).then((res) => {
27 | res.obj().should.eql({
28 | 'two.js': [],
29 | 'BasicComponent.vue': ['OneNested.vue', 'TwoNested.vue'],
30 | 'OneNested.vue': ['ThreeNested.vue', 'two.js'],
31 | 'ThreeNested.vue': [],
32 | 'TwoNested.vue': []
33 | });
34 | done();
35 | }).catch(done);
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/test/output.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | make_title() {
4 | printf '\033[01;38;5;022m############### %s ###############\033[0m\n' "$1"
5 | }
6 |
7 | make_title "LIST"
8 | ./bin/cli.js lib/api.js
9 |
10 | make_title "SUMMARY"
11 | ./bin/cli.js lib/api.js -s
12 |
13 | make_title "DEPENDS"
14 | ./bin/cli.js lib/api.js -d log.js
15 |
16 | make_title "CIRCULAR (OK)"
17 | ./bin/cli.js test/cjs/a.js -c
18 |
19 | make_title "CIRCULAR (FOUND, NO INDEX COUNTING)"
20 | ./bin/cli.js test/cjs/circular/a.js -c --no-count
21 |
22 | make_title "CIRCULAR (FOUND, WITH INDEX COUNT)"
23 | ./bin/cli.js test/cjs/circular/a.js -c
24 |
25 | make_title "NPM"
26 | ./bin/cli.js test/cjs/npm.js --include-npm
27 |
28 | make_title "STDIN"
29 | ./bin/cli.js --json lib/api.js | tr '[a-z]' '[A-Z]' | ./bin/cli.js --stdin
30 |
31 | make_title "IMAGE"
32 | ./bin/cli.js lib/api.js --image /tmp/test.svg
33 |
34 | make_title "DOT"
35 | ./bin/cli.js lib/api.js --dot
36 |
37 | make_title "JSON"
38 | ./bin/cli.js lib/api.js --json
39 |
40 | make_title "NO COLOR"
41 | ./bin/cli.js lib/api.js --no-color
42 |
43 | make_title "SHOW EXTENSION"
44 | ./bin/cli.js lib/api.js --show-extension
45 |
46 | make_title "WARNINGS (NOTE)"
47 | ./bin/cli.js test/cjs/missing.js -c
48 |
49 | make_title "WARNINGS (LIST)"
50 | ./bin/cli.js test/cjs/missing.js -c --warning
51 |
52 | make_title "ERROR"
53 | ./bin/cli.js file/not/found.js
54 |
55 | make_title "DEBUG"
56 | ./bin/cli.js lib/log.js --debug
57 |
58 | exit 0
59 |
--------------------------------------------------------------------------------
/test/es6.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const madge = require('../lib/api');
5 | require('should');
6 |
7 | describe('ES6', () => {
8 | const dir = __dirname + '/es6';
9 |
10 | it('extracts dependencies', (done) => {
11 | madge(dir + '/absolute.js').then((res) => {
12 | res.obj().should.eql({
13 | 'absolute.js': ['absolute/a.js'],
14 | 'absolute/a.js': []
15 | });
16 | done();
17 | }).catch(done);
18 | });
19 |
20 | it('finds circular dependencies', (done) => {
21 | madge(dir + '/circular/a.js').then((res) => {
22 | res.circular().should.eql([
23 | ['a.js', 'b.js', 'c.js']
24 | ]);
25 | done();
26 | }).catch(done);
27 | });
28 |
29 | it('tackles error in files', (done) => {
30 | madge(dir + '/error.js').then((res) => {
31 | res.obj().should.eql({
32 | 'error.js': []
33 | });
34 | done();
35 | }).catch(done);
36 | });
37 |
38 | it('supports export x from "./file"', (done) => {
39 | madge(dir + '/re-export/c.js').then((res) => {
40 | res.obj().should.eql({
41 | 'a.js': [],
42 | 'b-default.js': ['a.js'],
43 | 'b-named.js': ['a.js'],
44 | 'b-star.js': ['a.js'],
45 | 'c.js': [
46 | 'b-default.js',
47 | 'b-named.js',
48 | 'b-star.js'
49 | ]
50 | });
51 | done();
52 | }).catch(done);
53 | });
54 |
55 | it('supports resolve root paths in webpack config', (done) => {
56 | madge(dir + '/webpack/src/sub/index.js', {
57 | webpackConfig: dir + '/webpack/webpack.config.js'
58 | }).then((res) => {
59 | res.obj().should.eql({
60 | 'index.js': ['rel.js'],
61 | 'abs.js': [],
62 | 'rel.js': ['abs.js']
63 | });
64 | done();
65 | }).catch(done);
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/lib/cyclic.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Get path to the circular dependency.
5 | * @param {String} parent
6 | * @param {Object} unresolved
7 | * @return {Array}
8 | */
9 | function getPath(parent, unresolved) {
10 | let parentVisited = false;
11 |
12 | return Object.keys(unresolved).filter((module) => {
13 | if (module === parent) {
14 | parentVisited = true;
15 | }
16 | return parentVisited && unresolved[module];
17 | });
18 | }
19 |
20 | /**
21 | * A circular dependency is occurring when we see a software package
22 | * more than once, unless that software package has all its dependencies resolved.
23 | * @param {String} id
24 | * @param {Object} modules
25 | * @param {Object} circular
26 | * @param {Object} resolved
27 | * @param {Object} unresolved
28 | */
29 | function resolver(id, modules, circular, resolved, unresolved) {
30 | unresolved[id] = true;
31 |
32 | if (modules[id]) {
33 | modules[id].forEach((dependency) => {
34 | if (!resolved[dependency]) {
35 | if (unresolved[dependency]) {
36 | circular.push(getPath(dependency, unresolved));
37 | return;
38 | }
39 | resolver(dependency, modules, circular, resolved, unresolved);
40 | }
41 | });
42 | }
43 |
44 | resolved[id] = true;
45 | unresolved[id] = false;
46 | }
47 |
48 | /**
49 | * Finds all circular dependencies for the given modules.
50 | * @param {Object} modules
51 | * @return {Array}
52 | */
53 | module.exports = function (modules) {
54 | const circular = [];
55 | const resolved = {};
56 | const unresolved = {};
57 |
58 | Object.keys(modules).forEach((id) => {
59 | resolver(id, modules, circular, resolved, unresolved);
60 | });
61 |
62 | return circular;
63 | };
64 |
--------------------------------------------------------------------------------
/test/cjs.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const madge = require('../lib/api');
5 | require('should');
6 |
7 | describe('CommonJS', () => {
8 | const dir = __dirname + '/cjs';
9 |
10 | it('finds recursive dependencies', (done) => {
11 | madge(dir + '/normal/a.js').then((res) => {
12 | res.obj().should.eql({
13 | 'a.js': ['sub/b.js'],
14 | 'd.js': [],
15 | 'sub/b.js': ['sub/c.js'],
16 | 'sub/c.js': ['d.js']
17 | });
18 | done();
19 | }).catch(done);
20 | });
21 |
22 | it('handles path outside directory', (done) => {
23 | madge(dir + '/normal/sub/c.js').then((res) => {
24 | res.obj().should.eql({
25 | '../d.js': [],
26 | 'c.js': ['../d.js']
27 | });
28 | done();
29 | }).catch(done);
30 | });
31 |
32 | it('finds circular dependencies', (done) => {
33 | madge(dir + '/circular/a.js').then((res) => {
34 | res.circular().should.eql([
35 | ['a.js', 'd.js']
36 | ]);
37 | done();
38 | }).catch(done);
39 | });
40 |
41 | it('handle extensions when finding circular dependencies', (done) => {
42 | madge(dir + '/circular/foo.js').then((res) => {
43 | res.circular().should.eql([]);
44 | done();
45 | }).catch(done);
46 | });
47 |
48 | it('excludes core modules by default', (done) => {
49 | madge(dir + '/core.js').then((res) => {
50 | res.obj().should.eql({
51 | 'core.js': []
52 | });
53 | done();
54 | }).catch(done);
55 | });
56 |
57 | it('excludes NPM modules by default', (done) => {
58 | madge(dir + '/npm.js').then((res) => {
59 | res.obj().should.eql({
60 | 'normal/d.js': [],
61 | 'npm.js': ['normal/d.js']
62 | });
63 | done();
64 | }).catch(done);
65 | });
66 |
67 | it('can include shallow NPM modules', (done) => {
68 | madge(dir + '/npm.js', {
69 | includeNpm: true
70 | }).then((res) => {
71 | res.obj().should.eql({
72 | 'normal/d.js': [],
73 | 'npm.js': ['node_modules/a.js', 'normal/d.js']
74 | });
75 | done();
76 | }).catch(done);
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/test/typescript.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const madge = require('../lib/api');
5 | const ts = require('typescript');
6 | require('should');
7 |
8 | describe('TypeScript', () => {
9 | const dir = __dirname + '/typescript';
10 |
11 | it('extracts module dependencies', (done) => {
12 | madge(dir + '/import.ts').then((res) => {
13 | res.obj().should.eql({
14 | 'import.ts': ['require-x.tsx', 'require.ts'],
15 | 'require.ts': ['export.ts'],
16 | 'require-x.tsx': ['export-x.tsx', 'export.ts'],
17 | 'export.ts': [],
18 | 'export-x.tsx': []
19 | });
20 | done();
21 | }).catch(done);
22 | });
23 |
24 | it('reads paths from a custom tsConfig', (done) => {
25 | const tsConfig = {
26 | compilerOptions: {
27 | baseUrl: dir,
28 | moduleResolution: 'node',
29 | paths: {
30 | '@shortcut/*': ['custom-paths/subfolder/*'],
31 | '@shortcut2/*': ['custom-paths/subfolder2/*']
32 | }
33 | }
34 | };
35 | madge(dir + '/custom-paths/import.ts', {tsConfig: tsConfig}).then((res) => {
36 | res.obj().should.eql({
37 | 'import.ts': ['subfolder/index.ts', 'subfolder/require.tsx'],
38 | 'subfolder/index.ts': [],
39 | 'subfolder/require.tsx': ['subfolder2/export.ts'],
40 | 'subfolder2/export.ts': []
41 | });
42 | done();
43 | }).catch(done);
44 | });
45 |
46 | it('got tsConfig as a string, "extends" field is interpreted', (done) => {
47 | madge(dir + '/custom-paths/import.ts', {tsConfig: dir + '/with-config/tsconfig.json'}).then((res) => {
48 | res.config.tsConfig.should.eql({
49 | extends: './tsconfig.base.json',
50 | compilerOptions: {
51 | target: ts.ScriptTarget.ESNext,
52 | module: ts.ModuleKind.CommonJS,
53 | allowJs: true,
54 | configFilePath: undefined
55 | }
56 | });
57 | done();
58 | }).catch(done);
59 | });
60 |
61 | it('supports CJS modules when using mixedImports option', (done) => {
62 | madge(dir + '/mixed.ts', {detectiveOptions: {ts: {mixedImports: true}}}).then((res) => {
63 | res.obj().should.eql({
64 | 'export-x.tsx': [],
65 | 'export.ts': [],
66 | 'mixed.ts': ['export-x.tsx', 'export.ts']
67 | });
68 | done();
69 | }).catch(done);
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "madge",
3 | "version": "8.0.0",
4 | "author": "Patrik Henningsson ",
5 | "repository": "git://github.com/pahen/madge",
6 | "homepage": "https://github.com/pahen/madge",
7 | "license": "MIT",
8 | "reveal": true,
9 | "description": "Create graphs from module dependencies.",
10 | "keywords": [
11 | "ES6",
12 | "ES7",
13 | "AMD",
14 | "RequireJS",
15 | "require",
16 | "module",
17 | "circular",
18 | "dependency",
19 | "dependencies",
20 | "graphviz",
21 | "graph"
22 | ],
23 | "engines": {
24 | "node": ">=18"
25 | },
26 | "main": "./lib/api",
27 | "bin": {
28 | "madge": "./bin/cli.js"
29 | },
30 | "scripts": {
31 | "test": "npm run lint && npm run mocha",
32 | "mocha": "mocha test/*.js",
33 | "watch": "mocha --watch --growl test/*.js",
34 | "lint": "eslint bin/cli.js lib test/*.js",
35 | "debug": "node bin/cli.js --debug bin lib",
36 | "generate": "npm run generate:small && npm run generate:madge",
37 | "generate:small": "bin/cli.js --image /tmp/simple.svg test/cjs/circular/a.js",
38 | "generate:madge": "bin/cli.js --image /tmp/madge.svg bin lib",
39 | "test:output": "./test/output.sh",
40 | "release": "npm test && release-it"
41 | },
42 | "funding": {
43 | "type": "individual",
44 | "url": "https://www.paypal.me/pahen"
45 | },
46 | "dependencies": {
47 | "chalk": "^4.1.2",
48 | "commander": "^7.2.0",
49 | "commondir": "^1.0.1",
50 | "debug": "^4.3.4",
51 | "dependency-tree": "^11.0.0",
52 | "ora": "^5.4.1",
53 | "pluralize": "^8.0.0",
54 | "pretty-ms": "^7.0.1",
55 | "rc": "^1.2.8",
56 | "stream-to-array": "^2.3.0",
57 | "ts-graphviz": "^2.1.2",
58 | "walkdir": "^0.4.1"
59 | },
60 | "peerDependencies": {
61 | "typescript": "^5.4.4"
62 | },
63 | "peerDependenciesMeta": {
64 | "typescript": {
65 | "optional": true
66 | }
67 | },
68 | "devDependencies": {
69 | "@aptoma/eslint-config": "^7.0.1",
70 | "auto-changelog": "^2.4.0",
71 | "eslint": "^7.29.0",
72 | "mocha": "^9.0.1",
73 | "mz": "^2.7.0",
74 | "release-it": "^16.2.1",
75 | "should": "^13.2.3",
76 | "typescript": "^5.4.4"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/test/amd.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const madge = require('../lib/api');
5 | require('should');
6 |
7 | describe('AMD', () => {
8 | const dir = __dirname + '/amd';
9 |
10 | it('finds recursive dependencies', (done) => {
11 | madge(dir + '/ok/a.js').then((res) => {
12 | res.obj().should.eql({
13 | 'a.js': ['sub/b.js'],
14 | 'sub/b.js': ['sub/c.js'],
15 | 'sub/c.js': ['d.js'],
16 | 'd.js': []
17 | });
18 | done();
19 | }).catch(done);
20 | });
21 |
22 | it('ignores plugins', (done) => {
23 | madge(dir + '/plugin.js').then((res) => {
24 | res.obj().should.eql({
25 | 'plugin.js': ['ok/d.js'],
26 | 'ok/d.js': []
27 | });
28 | done();
29 | }).catch(done);
30 | });
31 |
32 | it('finds nested dependencies', (done) => {
33 | madge(dir + '/nested/main.js').then((res) => {
34 | res.obj().should.eql({
35 | 'a.js': [],
36 | 'b.js': [],
37 | 'main.js': [
38 | 'a.js',
39 | 'b.js'
40 | ]
41 | });
42 | done();
43 | }).catch(done);
44 | });
45 |
46 | it('finds circular dependencies', (done) => {
47 | madge(dir + '/circular/main.js').then((res) => {
48 | res.circular().should.eql([
49 | ['a.js', 'c.js'],
50 | ['f.js', 'g.js', 'h.js']
51 | ]);
52 | done();
53 | }).catch(done);
54 | });
55 |
56 | it('finds circular dependencies with relative paths', (done) => {
57 | madge(dir + '/circularRelative/a.js').then((res) => {
58 | res.circular().should.eql([['a.js', 'foo/b.js']]);
59 | done();
60 | }).catch(done);
61 | });
62 |
63 | it('finds circular dependencies with alias', (done) => {
64 | madge(dir + '/circularAlias/dos.js', {
65 | requireConfig: dir + '/circularAlias/config.js'
66 | }).then((res) => {
67 | res.circular().should.eql([['dos.js', 'x86.js']]);
68 | done();
69 | }).catch(done);
70 | });
71 |
72 | it('works for files with ES6 code inside', (done) => {
73 | madge(dir + '/amdes6.js').then((res) => {
74 | res.obj().should.eql({
75 | 'amdes6.js': ['ok/d.js'],
76 | 'ok/d.js': []
77 | });
78 | done();
79 | }).catch(done);
80 | });
81 |
82 | it('uses paths found in RequireJS config', (done) => {
83 | madge(dir + '/requirejs/a.js', {
84 | requireConfig: dir + '/requirejs/config.js'
85 | }).then((res) => {
86 | res.obj().should.eql({
87 | 'a.js': ['vendor/jquery-2.0.3.js'],
88 | 'vendor/jquery-2.0.3.js': []
89 | });
90 | done();
91 | }).catch(done);
92 | });
93 |
94 | it('returns modules that no one is depending on', (done) => {
95 | madge(dir + '/requirejs/orphans', {
96 | requireConfig: dir + '/requirejs/config.js'
97 | }).then((res) => {
98 | res.orphans().should.eql([
99 | 'a.js',
100 | 'c.js'
101 | ]);
102 | done();
103 | }).catch(done);
104 | });
105 | });
106 |
--------------------------------------------------------------------------------
/lib/output.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const chalk = require('chalk');
4 | const pluralize = require('pluralize');
5 | const prettyMs = require('pretty-ms');
6 |
7 | /**
8 | * Print given object as JSON.
9 | * @param {Object} obj
10 | * @return {String}
11 | */
12 | function printJSON(obj) {
13 | return console.log(JSON.stringify(obj, null, ' '));
14 | }
15 |
16 | /**
17 | * Print module dependency graph as indented text (or JSON).
18 | * @param {Object} modules
19 | * @param {Object} opts
20 | * @return {undefined}
21 | */
22 | module.exports.list = function (modules, opts) {
23 | opts = opts || {};
24 |
25 | if (opts.json) {
26 | return printJSON(modules);
27 | }
28 |
29 | Object.keys(modules).forEach((id) => {
30 | console.log(chalk.cyan.bold(id));
31 | modules[id].forEach((depId) => {
32 | console.log(chalk.grey(` ${depId}`));
33 | });
34 | });
35 | };
36 |
37 | /**
38 | * Print a summary of module dependencies.
39 | * @param {Object} modules
40 | * @param {Object} opts
41 | * @return {undefined}
42 | */
43 | module.exports.summary = function (modules, opts) {
44 | const o = {};
45 |
46 | opts = opts || {};
47 |
48 | Object.keys(modules).sort((a, b) => {
49 | return modules[b].length - modules[a].length;
50 | }).forEach((id) => {
51 | if (opts.json) {
52 | o[id] = modules[id].length;
53 | } else {
54 | console.log('%s %s', chalk.cyan.bold(modules[id].length), chalk.grey(id));
55 | }
56 | });
57 |
58 | if (opts.json) {
59 | return printJSON(o);
60 | }
61 | };
62 |
63 | /**
64 | * Print the result from Madge.circular().
65 | * @param {Object} spinner
66 | * @param {Object} res
67 | * @param {Array} circular
68 | * @param {Object} opts
69 | * @return {undefined}
70 | */
71 | module.exports.circular = function (spinner, res, circular, opts) {
72 | if (opts.json) {
73 | return printJSON(circular);
74 | }
75 |
76 | const cyclicCount = Object.keys(circular).length;
77 |
78 | if (!circular.length) {
79 | spinner.succeed(chalk.bold('No circular dependency found!'));
80 | } else {
81 | spinner.fail(chalk.red.bold(`Found ${pluralize('circular dependency', cyclicCount, true)}!\n`));
82 | circular.forEach((path, idx) => {
83 | if (opts.printCount) {
84 | process.stdout.write(chalk.dim(idx + 1 + ') '));
85 | }
86 | path.forEach((module, idx) => {
87 | if (idx) {
88 | process.stdout.write(chalk.dim(' > '));
89 | }
90 | process.stdout.write(chalk.cyan.bold(module));
91 | });
92 | process.stdout.write('\n');
93 | });
94 | }
95 | };
96 |
97 | /**
98 | * Print the given modules.
99 | * @param {Array} modules
100 | * @param {Object} opts
101 | * @return {undefined}
102 | */
103 | module.exports.modules = function (modules, opts) {
104 | if (opts.json) {
105 | return printJSON(modules);
106 | }
107 |
108 | modules.forEach((id) => {
109 | console.log(chalk.cyan.bold(id));
110 | });
111 | };
112 |
113 | /**
114 | * Print warnings to the console.
115 | * @param {Object} res
116 | * @return {undefined}
117 | */
118 | module.exports.warnings = function (res) {
119 | const skipped = res.warnings().skipped;
120 |
121 | if (skipped.length) {
122 | console.log(chalk.yellow.bold(`\n✖ Skipped ${pluralize('file', skipped.length, true)}\n`));
123 |
124 | skipped.forEach((file) => {
125 | console.log(chalk.yellow(file));
126 | });
127 | }
128 | };
129 |
130 | /**
131 | * Get a summary from the result.
132 | * @param {Object} res
133 | * @param {Number} startTime
134 | * @return {undefined}
135 | */
136 | module.exports.getResultSummary = function (res, startTime) {
137 | const warningCount = res.warnings().skipped.length;
138 | const fileCount = Object.keys(res.obj()).length;
139 |
140 | console.log('Processed %s %s %s %s\n',
141 | chalk.bold(fileCount),
142 | pluralize('file', fileCount),
143 | chalk.dim(`(${prettyMs(Date.now() - startTime)})`),
144 | warningCount ? '(' + chalk.yellow.bold(pluralize('warning', warningCount, true)) + ')' : ''
145 | );
146 | };
147 |
--------------------------------------------------------------------------------
/lib/graph.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const {promisify} = require('util');
5 | const gv = require('ts-graphviz');
6 | const adapter = require('ts-graphviz/adapter');
7 | const toArray = require('stream-to-array');
8 | const exec = promisify(require('child_process').execFile);
9 | const writeFile = promisify(require('fs').writeFile);
10 |
11 | /**
12 | * Set color on a node.
13 | * @param {Object} node
14 | * @param {String} color
15 | */
16 | function 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 | */
26 | async 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 | */
45 | function 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 | */
85 | function 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 | */
123 | module.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 | */
140 | module.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 | */
160 | module.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 | };
169 |
--------------------------------------------------------------------------------
/lib/api.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path_ = require('path');
4 | const tree = require('./tree');
5 | const cyclic = require('./cyclic');
6 | const graph = require('./graph');
7 | const log = require('./log');
8 |
9 | const 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 |
33 | class 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 | */
219 | module.exports = (path, config) => new Madge(path, config);
220 |
--------------------------------------------------------------------------------
/bin/cli.js:
--------------------------------------------------------------------------------
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 | // Revert https://github.com/tj/commander.js/pull/1409
14 | program.storeOptionsAsProperties();
15 |
16 | program
17 | .version(version)
18 | .usage('[options] ')
19 | .option('-b, --basedir ', 'base directory for resolving paths')
20 | .option('-s, --summary', 'show dependency count summary')
21 | .option('-c, --circular', 'show circular dependencies')
22 | .option('-d, --depends ', 'show module dependents')
23 | .option('-x, --exclude ', 'exclude modules using RegExp')
24 | .option('-j, --json', 'output as JSON')
25 | .option('-i, --image ', 'write graph to file as an image')
26 | .option('-l, --layout ', 'layout engine to use for graph (dot/neato/fdp/sfdp/twopi/circo)')
27 | .option('--orphans', 'show modules that no one is depending on')
28 | .option('--leaves', 'show modules that have no dependencies')
29 | .option('--dot', 'show graph using the DOT language')
30 | .option('--rankdir ', 'set the direction of the graph layout')
31 | .option('--extensions ', 'comma separated string of valid file extensions')
32 | .option('--require-config ', 'path to RequireJS config')
33 | .option('--webpack-config ', 'path to webpack config')
34 | .option('--ts-config ', 'path to typescript config')
35 | .option('--include-npm', 'include shallow NPM modules', false)
36 | .option('--no-color', 'disable color in output and image', false)
37 | .option('--no-spinner', 'disable progress spinner', false)
38 | .option('--no-count', 'disable circular dependencies counting', false)
39 | .option('--stdin', 'read predefined tree from STDIN', false)
40 | .option('--warning', 'show warnings about skipped files', false)
41 | .option('--debug', 'turn on debug output', false)
42 | .parse(process.argv);
43 |
44 | if (!program.args.length && !program.stdin) {
45 | console.log(program.helpInformation());
46 | process.exit(1);
47 | }
48 |
49 | if (program.debug) {
50 | process.env.DEBUG = '*';
51 | }
52 |
53 | if (!program.color) {
54 | process.env.DEBUG_COLORS = false;
55 | }
56 |
57 | const log = require('../lib/log');
58 | const output = require('../lib/output');
59 | const madge = require('../lib/api');
60 |
61 | let packageConfig = {};
62 | try {
63 | packageConfig = require(path.join(process.cwd(), 'package.json')).madge;
64 | } catch (e) { }
65 | const config = Object.assign(rc, packageConfig);
66 |
67 | program.options.forEach((opt) => {
68 | const name = opt.name();
69 |
70 | if (program[name]) {
71 | config[name] = program[name];
72 | }
73 | });
74 |
75 | const spinner = ora({
76 | text: 'Finding files',
77 | color: 'white',
78 | interval: 100000,
79 | isEnabled: program.spinner === 'false' ? false : null
80 | });
81 |
82 | let exitCode = 0;
83 |
84 | delete config._;
85 | delete config.config;
86 | delete config.configs;
87 |
88 | if (rc.config) {
89 | log('using runtime config %s', rc.config);
90 | }
91 |
92 | if (program.basedir) {
93 | config.baseDir = program.basedir;
94 | }
95 |
96 | if (program.exclude) {
97 | config.excludeRegExp = [program.exclude];
98 | }
99 |
100 | if (program.extensions) {
101 | config.fileExtensions = program.extensions.split(',').map((s) => s.trim());
102 | }
103 |
104 | if (program.requireConfig) {
105 | config.requireConfig = program.requireConfig;
106 | }
107 |
108 | if (program.webpackConfig) {
109 | config.webpackConfig = program.webpackConfig;
110 | }
111 |
112 | if (program.tsConfig) {
113 | config.tsConfig = program.tsConfig;
114 | }
115 |
116 | if (program.includeNpm) {
117 | config.includeNpm = program.includeNpm;
118 | }
119 |
120 | if (!program.color) {
121 | config.backgroundColor = '#ffffff';
122 | config.nodeColor = '#000000';
123 | config.noDependencyColor = '#000000';
124 | config.cyclicNodeColor = '#000000';
125 | config.edgeColor = '#757575';
126 | }
127 |
128 | if (program.rankdir) {
129 | config.rankdir = program.rankdir;
130 | }
131 |
132 | function dependencyFilter() {
133 | let prevFile;
134 |
135 | return (dependencyFilePath, traversedFilePath, baseDir) => {
136 | if (prevFile !== traversedFilePath) {
137 | const relPath = path.relative(baseDir, traversedFilePath);
138 | const dir = path.dirname(relPath) + '/';
139 | const file = path.basename(relPath);
140 |
141 | spinner.text = chalk.grey(dir) + chalk.cyan(file);
142 |
143 | prevFile = traversedFilePath;
144 | }
145 | };
146 | }
147 |
148 | new Promise((resolve, reject) => {
149 | if (program.stdin) {
150 | let buffer = '';
151 |
152 | process.stdin
153 | .resume()
154 | .setEncoding('utf8')
155 | .on('data', (chunk) => {
156 | buffer += chunk;
157 | })
158 | .on('end', () => {
159 | try {
160 | resolve(JSON.parse(buffer));
161 | } catch (e) {
162 | reject(e);
163 | }
164 | });
165 | } else {
166 | resolve(program.args);
167 | }
168 | })
169 | .then((src) => {
170 | if (!program.json && !program.dot) {
171 | spinner.start();
172 | config.dependencyFilter = dependencyFilter();
173 | }
174 |
175 | return madge(src, config);
176 | })
177 | .then((res) => {
178 | if (!program.json && !program.dot) {
179 | spinner.stop();
180 | output.getResultSummary(res, startTime);
181 | }
182 |
183 | const result = createOutputFromOptions(program, res);
184 | if (result !== undefined) {
185 | return result;
186 | }
187 |
188 | output.list(res.obj(), {
189 | json: program.json
190 | });
191 |
192 | return res;
193 | })
194 | .then((res) => {
195 | if (program.warning && !program.json) {
196 | output.warnings(res);
197 | }
198 |
199 | if (!program.json && !program.dot) {
200 | console.log('');
201 | }
202 |
203 | process.exit(exitCode);
204 | })
205 | .catch((err) => {
206 | spinner.stop();
207 | console.log('\n%s %s\n', chalk.red('✖'), err.stack);
208 | process.exit(1);
209 | });
210 |
211 | function createOutputFromOptions(program, res) {
212 | if (program.summary) {
213 | output.summary(res.obj(), {
214 | json: program.json
215 | });
216 |
217 | return res;
218 | }
219 |
220 | if (program.depends) {
221 | output.modules(res.depends(program.depends), {
222 | json: program.json
223 | });
224 |
225 | return res;
226 | }
227 |
228 | if (program.orphans) {
229 | output.modules(res.orphans(), {
230 | json: program.json
231 | });
232 |
233 | return res;
234 | }
235 |
236 | if (program.leaves) {
237 | output.modules(res.leaves(), {
238 | json: program.json
239 | });
240 |
241 | return res;
242 | }
243 |
244 | if (program.image) {
245 | return res.image(program.image, program.circular).then((imagePath) => {
246 | spinner.succeed(`${chalk.bold('Image created at')} ${chalk.cyan.bold(imagePath)}`);
247 | return res;
248 | });
249 | }
250 |
251 | if (program.dot) {
252 | return res.dot(program.circular).then((output) => {
253 | process.stdout.write(output);
254 | return res;
255 | });
256 | }
257 |
258 | if (program.circular) {
259 | const circular = res.circular();
260 |
261 | output.circular(spinner, res, circular, {
262 | json: program.json,
263 | printCount: program.count
264 | });
265 |
266 | if (circular.length) {
267 | exitCode = 1;
268 | }
269 |
270 | return res;
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/lib/tree.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const os = require('os');
4 | const path = require('path');
5 | const {promisify} = require('util');
6 | const commondir = require('commondir');
7 | const walk = require('walkdir');
8 | const dependencyTree = require('dependency-tree');
9 | const log = require('./log');
10 |
11 | const stat = promisify(require('fs').stat);
12 |
13 | /**
14 | * Check if running on Windows.
15 | * @type {Boolean}
16 | */
17 | const isWin = (os.platform() === 'win32');
18 |
19 | class Tree {
20 | /**
21 | * Class constructor.
22 | * @constructor
23 | * @api public
24 | * @param {Array} srcPaths
25 | * @param {Object} config
26 | * @return {Promise}
27 | */
28 | constructor(srcPaths, config) {
29 | this.srcPaths = srcPaths.map((s) => path.resolve(s));
30 | log('using src paths %o', this.srcPaths);
31 |
32 | this.config = config;
33 | log('using config %o', this.config);
34 |
35 | return this.getDirs()
36 | .then(this.setBaseDir.bind(this))
37 | .then(this.getFiles.bind(this))
38 | .then(this.generateTree.bind(this));
39 | }
40 |
41 | /**
42 | * Set the base directory (compute the common one if multiple).
43 | * @param {Array} dirs
44 | */
45 | setBaseDir(dirs) {
46 | if (this.config.baseDir) {
47 | this.baseDir = path.resolve(this.config.baseDir);
48 | } else {
49 | this.baseDir = commondir(dirs);
50 | }
51 |
52 | log('using base directory %s', this.baseDir);
53 | }
54 |
55 | /**
56 | * Get directories from the source paths
57 | * @return {Promise} resolved with an array of directories
58 | */
59 | getDirs() {
60 | return Promise
61 | .all(this.srcPaths.map((srcPath) => {
62 | return stat(srcPath)
63 | .then((stats) => stats.isDirectory() ? srcPath : path.dirname(path.resolve(srcPath)));
64 | }));
65 | }
66 |
67 | /**
68 | * Get all files found from the source paths
69 | * @return {Promise} resolved with an array of files
70 | */
71 | getFiles() {
72 | const files = [];
73 |
74 | return Promise
75 | .all(this.srcPaths.map((srcPath) => {
76 | return stat(srcPath)
77 | .then((stats) => {
78 | if (stats.isFile()) {
79 | if (this.isGitPath(srcPath)) {
80 | return;
81 | }
82 |
83 | files.push(path.resolve(srcPath));
84 |
85 | return;
86 | }
87 |
88 | walk.sync(srcPath, (filePath, stat) => {
89 | if (this.isGitPath(filePath) || this.isNpmPath(filePath) || !stat.isFile()) {
90 | return;
91 | }
92 |
93 | const ext = path.extname(filePath).replace('.', '');
94 |
95 | if (files.indexOf(filePath) < 0 && this.config.fileExtensions.indexOf(ext) >= 0) {
96 | files.push(filePath);
97 | }
98 | });
99 | });
100 | }))
101 | .then(() => files);
102 | }
103 |
104 | /**
105 | * Generate the tree from the given files
106 | * @param {Array} files
107 | * @return {Object}
108 | */
109 | generateTree(files) {
110 | const depTree = {};
111 | const visited = {};
112 | const nonExistent = [];
113 | const npmPaths = {};
114 | const pathCache = {};
115 |
116 | files.forEach((file) => {
117 | if (visited[file]) {
118 | return;
119 | }
120 |
121 | Object.assign(depTree, dependencyTree({
122 | filename: file,
123 | directory: this.baseDir,
124 | requireConfig: this.config.requireConfig,
125 | webpackConfig: this.config.webpackConfig,
126 | tsConfig: this.config.tsConfig,
127 | visited: visited,
128 | filter: (dependencyFilePath, traversedFilePath) => {
129 | let dependencyFilterRes = true;
130 | const isNpmPath = this.isNpmPath(dependencyFilePath);
131 |
132 | if (this.isGitPath(dependencyFilePath)) {
133 | return false;
134 | }
135 |
136 | if (this.config.dependencyFilter) {
137 | dependencyFilterRes = this.config.dependencyFilter(dependencyFilePath, traversedFilePath, this.baseDir);
138 | }
139 |
140 | if (this.config.includeNpm && isNpmPath) {
141 | (npmPaths[traversedFilePath] = npmPaths[traversedFilePath] || []).push(dependencyFilePath);
142 | }
143 |
144 | return !isNpmPath && (dependencyFilterRes || dependencyFilterRes === undefined);
145 | },
146 | detective: this.config.detectiveOptions,
147 | nonExistent: nonExistent
148 | }));
149 | });
150 |
151 | let tree = this.convertTree(depTree, {}, pathCache, npmPaths);
152 |
153 | for (const npmKey in npmPaths) {
154 | const id = this.processPath(npmKey, pathCache);
155 |
156 | npmPaths[npmKey].forEach((npmPath) => {
157 | tree[id].push(this.processPath(npmPath, pathCache));
158 | });
159 | }
160 |
161 | if (this.config.excludeRegExp) {
162 | tree = this.exclude(tree, this.config.excludeRegExp);
163 | }
164 |
165 | return {
166 | tree: this.sort(tree),
167 | skipped: nonExistent
168 | };
169 | }
170 |
171 | /**
172 | * Convert deep tree produced by dependency-tree to a
173 | * shallow (one level deep) tree used by madge.
174 | * @param {Object} depTree
175 | * @param {Object} tree
176 | * @param {Object} pathCache
177 | * @return {Object}
178 | */
179 | convertTree(depTree, tree, pathCache) {
180 | for (const key in depTree) {
181 | const id = this.processPath(key, pathCache);
182 |
183 | if (!tree[id]) {
184 | tree[id] = [];
185 |
186 | for (const dep in depTree[key]) {
187 | tree[id].push(this.processPath(dep, pathCache));
188 | }
189 |
190 | this.convertTree(depTree[key], tree, pathCache);
191 | }
192 | }
193 |
194 | return tree;
195 | }
196 |
197 | /**
198 | * Process absolute path and return a shorter one.
199 | * @param {String} absPath
200 | * @param {Object} cache
201 | * @return {String}
202 | */
203 | processPath(absPath, cache) {
204 | if (cache[absPath]) {
205 | return cache[absPath];
206 | }
207 |
208 | let relPath = path.relative(this.baseDir, absPath);
209 |
210 | if (isWin) {
211 | relPath = relPath.replace(/\\/g, '/');
212 | }
213 |
214 | cache[absPath] = relPath;
215 |
216 | return relPath;
217 | }
218 |
219 | /**
220 | * Check if path is from NPM folder
221 | * @param {String} path
222 | * @return {Boolean}
223 | */
224 | isNpmPath(path) {
225 | return path.indexOf('node_modules') >= 0;
226 | }
227 |
228 | /**
229 | * Check if path is from .git folder
230 | * @param {String} filePath
231 | * @return {Boolean}
232 | */
233 | isGitPath(filePath) {
234 | return filePath.split(path.sep).indexOf('.git') !== -1;
235 | }
236 |
237 | /**
238 | * Exclude modules from tree using RegExp.
239 | * @param {Object} tree
240 | * @param {Array} excludeRegExp
241 | * @return {Object}
242 | */
243 | exclude(tree, excludeRegExp) {
244 | const regExpList = excludeRegExp.map((re) => new RegExp(re));
245 |
246 | function regExpFilter(id) {
247 | return regExpList.findIndex((regexp) => regexp.test(id)) < 0;
248 | }
249 |
250 | return Object
251 | .keys(tree)
252 | .filter(regExpFilter)
253 | .reduce((acc, id) => {
254 | acc[id] = tree[id].filter(regExpFilter);
255 | return acc;
256 | }, {});
257 | }
258 |
259 | /**
260 | * Sort tree.
261 | * @param {Object} tree
262 | * @return {Object}
263 | */
264 | sort(tree) {
265 | return Object
266 | .keys(tree)
267 | .sort()
268 | .reduce((acc, id) => {
269 | acc[id] = tree[id].sort();
270 | return acc;
271 | }, {});
272 | }
273 | }
274 |
275 | /**
276 | * Expose API.
277 | * @param {Array} srcPaths
278 | * @param {Object} config
279 | * @return {Promise}
280 | */
281 | module.exports = (srcPaths, config) => new Tree(srcPaths, config);
282 |
--------------------------------------------------------------------------------
/test/api.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | 'use strict';
3 |
4 | const os = require('os');
5 | const path = require('path');
6 | const fs = require('mz/fs');
7 | const madge = require('../lib/api');
8 |
9 | require('should');
10 |
11 | describe('API', () => {
12 | it('throws error on missing path argument', () => {
13 | (() => {
14 | madge();
15 | }).should.throw('path argument not provided');
16 | });
17 |
18 | it('returns a Promise', () => {
19 | madge(__dirname + '/cjs/a.js').should.be.Promise(); // eslint-disable-line new-cap
20 | });
21 |
22 | it('throws error if file or directory does not exists', (done) => {
23 | madge(__dirname + '/missing.js').catch((err) => {
24 | err.message.should.match(/no such file or directory/);
25 | done();
26 | }).catch(done);
27 | });
28 |
29 | it('takes single file as path', (done) => {
30 | madge(__dirname + '/cjs/a.js').then((res) => {
31 | res.obj().should.eql({
32 | 'a.js': ['b.js', 'c.js'],
33 | 'b.js': ['c.js'],
34 | 'c.js': []
35 | });
36 | done();
37 | }).catch(done);
38 | });
39 |
40 | it('takes an array of files as path and combines the result', (done) => {
41 | madge([__dirname + '/cjs/a.js', __dirname + '/cjs/normal/d.js']).then((res) => {
42 | res.obj().should.eql({
43 | 'a.js': ['b.js', 'c.js'],
44 | 'b.js': ['c.js'],
45 | 'c.js': [],
46 | 'normal/d.js': []
47 | });
48 | done();
49 | }).catch(done);
50 | });
51 |
52 | it('take a single directory as path and find files in it', (done) => {
53 | madge(__dirname + '/cjs/normal').then((res) => {
54 | res.obj().should.eql({
55 | 'a.js': ['sub/b.js'],
56 | 'd.js': [],
57 | 'sub/b.js': ['sub/c.js'],
58 | 'sub/c.js': ['d.js']
59 | });
60 | done();
61 | }).catch(done);
62 | });
63 |
64 | it('takes an array of directories as path and compute the basedir correctly', (done) => {
65 | madge([__dirname + '/cjs/multibase/1', __dirname + '/cjs/multibase/2']).then((res) => {
66 | res.obj().should.eql({
67 | '1/a.js': [],
68 | '2/b.js': []
69 | });
70 | done();
71 | }).catch(done);
72 | });
73 |
74 | it('takes a predefined tree', (done) => {
75 | madge({
76 | a: ['b', 'c', 'd'],
77 | b: ['c'],
78 | c: [],
79 | d: ['a']
80 | }).then((res) => {
81 | res.obj().should.eql({
82 | a: ['b', 'c', 'd'],
83 | b: ['c'],
84 | c: [],
85 | d: ['a']
86 | });
87 | done();
88 | }).catch(done);
89 | });
90 |
91 | it('can exclude modules using RegExp', (done) => {
92 | madge(__dirname + '/cjs/a.js', {
93 | excludeRegExp: ['^b.js$']
94 | }).then((res) => {
95 | res.obj().should.eql({
96 | 'a.js': ['c.js'],
97 | 'c.js': []
98 | });
99 | done();
100 | }).catch(done);
101 | });
102 |
103 | it('extracts dependencies but excludes .git', (done) => {
104 | // eslint-disable-next-line no-sync
105 | fs.renameSync(`${__dirname}/git/.git_tmp`, `${__dirname}/git/.git`);
106 |
107 | madge(__dirname + '/git/a.js', {}).then((res) => {
108 | res.obj().should.eql({
109 | 'a.js': ['b.js', 'c.js'],
110 | 'b.js': ['c.js'],
111 | 'c.js': []
112 | });
113 | done();
114 | }).catch(() => {
115 | done();
116 | }).finally(() => {
117 | // eslint-disable-next-line no-sync
118 | fs.renameSync(`${__dirname}/git/.git`, `${__dirname}/git/.git_tmp`);
119 | });
120 | });
121 |
122 | describe('dependencyFilter', () => {
123 | it('will stop traversing when returning false', (done) => {
124 | madge(__dirname + '/cjs/a.js', {
125 | dependencyFilter: () => {
126 | return false;
127 | }
128 | }).then((res) => {
129 | res.obj().should.eql({
130 | 'a.js': []
131 | });
132 | done();
133 | }).catch(done);
134 | });
135 |
136 | it('will not stop traversing when not returning anything', (done) => {
137 | madge(__dirname + '/cjs/a.js', {
138 | dependencyFilter: () => {}
139 | }).then((res) => {
140 | res.obj().should.eql({
141 | 'a.js': ['b.js', 'c.js'],
142 | 'b.js': ['c.js'],
143 | 'c.js': []
144 | });
145 | done();
146 | }).catch(done);
147 | });
148 |
149 | it('will pass arguments to the function', (done) => {
150 | let counter = 0;
151 |
152 | madge(__dirname + '/cjs/a.js', {
153 | dependencyFilter: (dependencyFilePath, traversedFilePath, baseDir) => {
154 | if (counter === 0) {
155 | dependencyFilePath.should.match(/test\/cjs\/b\.js$/);
156 | traversedFilePath.should.match(/test\/cjs\/a\.js$/);
157 | baseDir.should.match(/test\/cjs$/);
158 | }
159 |
160 | if (counter === 1) {
161 | dependencyFilePath.should.match(/test\/cjs\/c\.js$/);
162 | traversedFilePath.should.match(/test\/cjs\/a\.js$/);
163 | baseDir.should.match(/test\/cjs$/);
164 | }
165 |
166 | if (counter === 2) {
167 | dependencyFilePath.should.match(/test\/cjs\/c\.js$/);
168 | traversedFilePath.should.match(/test\/cjs\/b\.js$/);
169 | baseDir.should.match(/test\/cjs$/);
170 | }
171 |
172 | counter++;
173 | }
174 | }).then(() => {
175 | done();
176 | }).catch(done);
177 | });
178 | });
179 |
180 | describe('obj()', () => {
181 | it('returns dependency object', (done) => {
182 | madge(__dirname + '/cjs/a.js').then((res) => {
183 | res.obj().should.eql({
184 | 'a.js': ['b.js', 'c.js'],
185 | 'b.js': ['c.js'],
186 | 'c.js': []
187 | });
188 | done();
189 | }).catch(done);
190 | });
191 | });
192 |
193 | describe('circular()', () => {
194 | it('returns list of circular dependencies', (done) => {
195 | madge(__dirname + '/cjs/circular/a.js').then((res) => {
196 | res.circular().should.eql([
197 | ['a.js', 'd.js']
198 | ]);
199 | done();
200 | }).catch(done);
201 | });
202 | });
203 |
204 | describe('circularGraph()', () => {
205 | it('returns graph with only circular dependencies', (done) => {
206 | madge(__dirname + '/cjs/circular/a.js').then((res) => {
207 | res.circularGraph().should.eql({
208 | 'a.js': ['d.js'],
209 | 'd.js': ['a.js']
210 | });
211 | done();
212 | }).catch(done);
213 | });
214 | });
215 |
216 | describe('warnings()', () => {
217 | it('returns an array of skipped files', (done) => {
218 | madge(__dirname + '/cjs/missing.js').then((res) => {
219 | res.obj().should.eql({
220 | 'missing.js': ['c.js'],
221 | 'c.js': []
222 | });
223 | res.warnings().should.eql({
224 | skipped: ['./path/non/existing/file']
225 | });
226 | done();
227 | }).catch(done);
228 | });
229 | });
230 |
231 | describe('dot()', () => {
232 | it('returns a promise resolved with graphviz DOT output', async () => {
233 | const res = await madge(__dirname + '/cjs/b.js');
234 | const output = await res.dot();
235 | output.should.match(/digraph G/);
236 | output.should.match(/bgcolor="#111111"/);
237 | output.should.match(/fontcolor="#c6c5fe"/);
238 | output.should.match(/color="#757575"/);
239 | output.should.match(/fontcolor="#cfffac"/);
240 | });
241 | });
242 |
243 | describe('depends()', () => {
244 | it('returns modules that depends on another', (done) => {
245 | madge(__dirname + '/cjs/a.js').then((res) => {
246 | res.depends('c.js').should.eql(['a.js', 'b.js']);
247 | done();
248 | }).catch(done);
249 | });
250 | });
251 |
252 | describe('orphans()', () => {
253 | it('returns modules that no one is depending on', (done) => {
254 | madge(__dirname + '/cjs/normal').then((res) => {
255 | res.orphans().should.eql(['a.js']);
256 | done();
257 | }).catch(done);
258 | });
259 | });
260 |
261 | describe('leaves()', () => {
262 | it('returns modules that have no dependencies', (done) => {
263 | madge(__dirname + '/cjs/normal').then((res) => {
264 | res.leaves().should.eql(['d.js']);
265 | done();
266 | }).catch(done);
267 | });
268 | });
269 |
270 | describe('svg()', () => {
271 | it('returns a promise resolved with XML SVG output in a Buffer', (done) => {
272 | madge(__dirname + '/cjs/b.js')
273 | .then((res) => res.svg())
274 | .then((output) => {
275 | output.should.instanceof(Buffer);
276 | output.toString().should.match(/ {
284 | let imagePath;
285 |
286 | beforeEach(() => {
287 | imagePath = path.join(os.tmpdir(), 'madge_' + Date.now() + '_image.png');
288 | });
289 |
290 | afterEach(() => {
291 | return fs.unlink(imagePath).catch(() => {});
292 | });
293 |
294 | it('rejects if a filename is not supplied', (done) => {
295 | madge(__dirname + '/cjs/a.js')
296 | .then((res) => res.image())
297 | .catch((err) => {
298 | err.message.should.eql('imagePath not provided');
299 | done();
300 | });
301 | });
302 |
303 | it('rejects on unsupported image format', (done) => {
304 | madge(__dirname + '/cjs/a.js')
305 | .then((res) => res.image('image.zyx'))
306 | .catch((err) => {
307 | err.message.should.match(/Format: "zyx" not recognized/);
308 | done();
309 | });
310 | });
311 |
312 | it('rejects if graphviz is not installed', (done) => {
313 | madge(__dirname + '/cjs/a.js', {graphVizPath: '/invalid/path'})
314 | .then((res) => res.image('image.png'))
315 | .catch((err) => {
316 | err.message.should.eql('Graphviz could not be found. Ensure that "gvpr" is in your $PATH. Error: spawn /invalid/path/gvpr ENOENT');
317 | done();
318 | });
319 | });
320 |
321 | it('writes image to file', (done) => {
322 | madge(__dirname + '/cjs/a.js')
323 | .then((res) => res.image(imagePath))
324 | .then((writtenImagePath) => {
325 | writtenImagePath.should.eql(imagePath);
326 |
327 | return fs
328 | .exists(imagePath)
329 | .then((exists) => {
330 | if (!exists) {
331 | throw new Error(imagePath + ' not created');
332 | }
333 | done();
334 | });
335 | })
336 | .catch(done);
337 | });
338 | });
339 | });
340 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | **Madge** is a developer tool for generating a visual graph of your module dependencies, finding circular dependencies, and giving you other useful info. Joel Kemp's awesome [dependency-tree](https://github.com/mrjoelkemp/node-dependency-tree) is used for extracting the dependency tree.
16 |
17 | * Works for JavaScript (AMD, CommonJS, and ES6 modules)
18 | * Also works for CSS preprocessors (Sass, Stylus, and Less)
19 | * NPM installed dependencies are excluded by default (can be enabled)
20 | * All core Node.js modules (assert, path, fs, etc) are excluded
21 | * Will traverse child dependencies automatically
22 |
23 | Read the [changelog](CHANGELOG.md) for latest changes.
24 |
25 | > I've worked with Madge on my free time for the last couple of years and it's been a great experience. It started as an experiment but turned out to be a very useful tool for many developers. I have many ideas for the project and it would definitely be easier to dedicate more time to it with some [financial support](#donations-%EF%B8%8F) 🙏
26 | >
27 | > Regardless of your contribution, thanks for your support!
28 |
29 | ## Examples
30 |
31 | > Graph generated from madge's own code and dependencies.
32 |
33 |
34 |
35 |
36 |
37 | > A graph with circular dependencies. Blue has dependencies, green has no dependencies, and red has circular dependencies.
38 |
39 |
40 |
41 |
42 |
43 | ## See it in action
44 |
45 |
46 |
47 |
48 |
49 | # Installation
50 |
51 | ```sh
52 | npm -g install madge
53 | ```
54 |
55 | ## Graphviz (optional)
56 |
57 | > [Graphviz](http://www.graphviz.org/) is only required if you want to generate visual graphs (e.g. in SVG or DOT format).
58 |
59 | ### Mac OS X
60 |
61 | ```sh
62 | brew install graphviz || port install graphviz
63 | ```
64 |
65 | ### Ubuntu
66 |
67 | ```sh
68 | apt-get install graphviz
69 | ```
70 |
71 | # API
72 |
73 | ## madge(path: string|array|object, config: object)
74 |
75 | > `path` is a single file or directory, or an array of files/directories to read. A predefined tree can also be passed in as an object.
76 |
77 | > `config` is optional and should be the [configuration](#configuration) to use.
78 |
79 | > Returns a `Promise` resolved with the Madge instance object.
80 |
81 | ## Functions
82 |
83 | #### .obj()
84 |
85 | > Returns an `Object` with all dependencies.
86 |
87 | ```javascript
88 | const madge = require('madge');
89 |
90 | madge('path/to/app.js').then((res) => {
91 | console.log(res.obj());
92 | });
93 | ```
94 |
95 | #### .warnings()
96 |
97 | > Returns an `Object` of warnings.
98 |
99 | ```javascript
100 | const madge = require('madge');
101 |
102 | madge('path/to/app.js').then((res) => {
103 | console.log(res.warnings());
104 | });
105 | ```
106 |
107 | #### .circular()
108 |
109 | > Returns an `Array` of all modules that have circular dependencies.
110 |
111 | ```javascript
112 | const madge = require('madge');
113 |
114 | madge('path/to/app.js').then((res) => {
115 | console.log(res.circular());
116 | });
117 | ```
118 |
119 | #### .circularGraph()
120 |
121 | > Returns an `Object` with only circular dependencies.
122 |
123 | ```javascript
124 | const madge = require('madge');
125 |
126 | madge('path/to/app.js').then((res) => {
127 | console.log(res.circularGraph());
128 | });
129 | ```
130 |
131 | #### .depends()
132 |
133 | > Returns an `Array` of all modules that depend on a given module.
134 |
135 | ```javascript
136 | const madge = require('madge');
137 |
138 | madge('path/to/app.js').then((res) => {
139 | console.log(res.depends('lib/log.js'));
140 | });
141 | ```
142 |
143 | #### .orphans()
144 |
145 | > Return an `Array` of all modules that no one is depending on.
146 |
147 | ```javascript
148 | const madge = require('madge');
149 |
150 | madge('path/to/app.js').then((res) => {
151 | console.log(res.orphans());
152 | });
153 | ```
154 |
155 | #### .leaves()
156 |
157 | > Return an `Array` of all modules that have no dependencies.
158 |
159 | ```javascript
160 | const madge = require('madge');
161 |
162 | madge('path/to/app.js').then((res) => {
163 | console.log(res.leaves());
164 | });
165 | ```
166 |
167 | #### .dot([circularOnly: boolean])
168 |
169 | > Returns a `Promise` resolved with a DOT representation of the module dependency graph. Set `circularOnly` to only include circular dependencies.
170 |
171 | ```javascript
172 | const madge = require('madge');
173 |
174 | madge('path/to/app.js')
175 | .then((res) => res.dot())
176 | .then((output) => {
177 | console.log(output);
178 | });
179 | ```
180 |
181 | #### .image(imagePath: string, [circularOnly: boolean])
182 |
183 | > Write the graph as an image to the given image path. Set `circularOnly` to only include circular dependencies. The [image format](http://www.graphviz.org/content/output-formats) to use is determined from the file extension. Returns a `Promise` resolved with a full path to the written image.
184 |
185 | ```javascript
186 | const madge = require('madge');
187 |
188 | madge('path/to/app.js')
189 | .then((res) => res.image('path/to/image.svg'))
190 | .then((writtenImagePath) => {
191 | console.log('Image written to ' + writtenImagePath);
192 | });
193 | ```
194 |
195 | #### .svg()
196 |
197 | > Return a `Promise` resolved with the XML SVG representation of the dependency graph as a `Buffer`.
198 |
199 | ```javascript
200 | const madge = require('madge');
201 |
202 | madge('path/to/app.js')
203 | .then((res) => res.svg())
204 | .then((output) => {
205 | console.log(output.toString());
206 | });
207 | ```
208 |
209 | # Configuration
210 |
211 | Property | Type | Default | Description
212 | --- | --- | --- | ---
213 | `baseDir` | String | null | Base directory to use instead of the default
214 | `includeNpm` | Boolean | false | If shallow NPM modules should be included
215 | `fileExtensions` | Array | ['js'] | Valid file extensions used to find files in directories
216 | `excludeRegExp` | Array | false | An array of RegExp for excluding modules
217 | `requireConfig` | String | null | RequireJS config for resolving aliased modules
218 | `webpackConfig` | String | null | Webpack config for resolving aliased modules
219 | `tsConfig` | String\|Object | null | TypeScript config for resolving aliased modules - Either a path to a tsconfig file or an object containing the config
220 | `layout` | String | dot | Layout to use in the graph
221 | `rankdir` | String | LR | Sets the [direction](https://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:rankdir) of the graph layout
222 | `fontName` | String | Arial | Font name to use in the graph
223 | `fontSize` | String | 14px | Font size to use in the graph
224 | `backgroundColor` | String | #000000 | Background color for the graph
225 | `nodeShape` | String | box | A string specifying the [shape](https://graphviz.gitlab.io/_pages/doc/info/attrs.html#k:shape) of a node in the graph
226 | `nodeStyle` | String | rounded | A string specifying the [style](https://graphviz.gitlab.io/_pages/doc/info/attrs.html#k:style) of a node in the graph
227 | `nodeColor` | String | #c6c5fe | Default node color to use in the graph
228 | `noDependencyColor` | String | #cfffac | Color to use for nodes with no dependencies
229 | `cyclicNodeColor` | String | #ff6c60 | Color to use for circular dependencies
230 | `edgeColor` | String | #757575 | Edge color to use in the graph
231 | `graphVizOptions` | Object | false | Custom Graphviz [options](https://graphviz.gitlab.io/_pages/doc/info/attrs.html)
232 | `graphVizPath` | String | null | Custom Graphviz path
233 | `detectiveOptions` | Object | false | Custom `detective` options for [dependency-tree](https://github.com/dependents/node-dependency-tree) and [precinct](https://github.com/dependents/node-precinct#usage)
234 | `dependencyFilter` | Function | false | Function called with a dependency filepath (exclude subtrees by returning false)
235 |
236 | You can use configuration file either in `.madgerc` in your project or home folder or directly in `package.json`. Look [here](https://github.com/dominictarr/rc#standards) for alternative locations for the file.
237 |
238 | > .madgerc
239 |
240 | ```json
241 | {
242 | "fontSize": "10px",
243 | "graphVizOptions": {
244 | "G": {
245 | "rankdir": "LR"
246 | }
247 | }
248 | }
249 | ```
250 |
251 | > package.json
252 |
253 | ```json
254 | {
255 | "name": "foo",
256 | "version": "0.0.1",
257 | ...
258 | "madge": {
259 | "fontSize": "10px",
260 | "graphVizOptions": {
261 | "G": {
262 | "rankdir": "LR"
263 | }
264 | }
265 | }
266 | }
267 | ```
268 |
269 | # CLI
270 |
271 | ## Examples
272 |
273 | > List dependencies from a single file
274 |
275 | ```sh
276 | madge path/src/app.js
277 | ```
278 |
279 | > List dependencies from multiple files
280 |
281 | ```sh
282 | madge path/src/foo.js path/src/bar.js
283 | ```
284 |
285 | > List dependencies from all *.js files found in a directory
286 |
287 | ```sh
288 | madge path/src
289 | ```
290 |
291 | > List dependencies from multiple directories
292 |
293 | ```sh
294 | madge path/src/foo path/src/bar
295 | ```
296 |
297 | > List dependencies from all \*.js and \*.jsx files found in a directory
298 |
299 | ```sh
300 | madge --extensions js,jsx path/src
301 | ```
302 |
303 | > Finding circular dependencies
304 |
305 | ```sh
306 | madge --circular path/src/app.js
307 | ```
308 |
309 | > Show modules that depends on a given module
310 |
311 | ```sh
312 | madge --depends wheels.js path/src/app.js
313 | ```
314 |
315 | > Show modules that no one is depending on
316 |
317 | ```sh
318 | madge --orphans path/src/
319 | ```
320 |
321 | > Show modules that have no dependencies
322 |
323 | ```sh
324 | madge --leaves path/src/
325 | ```
326 |
327 | > Excluding modules
328 |
329 | ```sh
330 | madge --exclude '^(foo|bar)\.js$' path/src/app.js
331 | ```
332 |
333 | > Save graph as a SVG image (requires [Graphviz](#graphviz-optional))
334 |
335 | ```sh
336 | madge --image graph.svg path/src/app.js
337 | ```
338 |
339 | > Save graph with only circular dependencies
340 |
341 | ```sh
342 | madge --circular --image graph.svg path/src/app.js
343 | ```
344 |
345 | > Save graph as a [DOT](http://en.wikipedia.org/wiki/DOT_language) file for further processing (requires [Graphviz](#graphviz-optional))
346 |
347 | ```sh
348 | madge --dot path/src/app.js > graph.gv
349 | ```
350 |
351 | > Using pipe to transform tree (this example will uppercase all paths)
352 |
353 | ```sh
354 | madge --json path/src/app.js | tr '[a-z]' '[A-Z]' | madge --stdin
355 | ```
356 |
357 | # Debugging
358 |
359 | > To enable debugging output if you encounter problems, run madge with the `--debug` option then throw the result in a gist when creating issues on GitHub.
360 |
361 | ```sh
362 | madge --debug path/src/app.js
363 | ```
364 |
365 | # Running tests
366 |
367 | ```sh
368 | npm install
369 | npm test
370 | ```
371 |
372 | # Creating a release
373 |
374 | ```sh
375 | npm run release
376 | ```
377 |
378 | # FAQ
379 |
380 | ## Missing dependencies?
381 |
382 | It could happen that the files you're not seeing have been skipped due to errors or that they can't be resolved. Run madge with the `--warning` option to see skipped files. If you need even more info run with the `--debug` option.
383 |
384 | ## Using both Javascript and Typescript in your project?
385 |
386 | Madge uses [dependency-tree](https://www.npmjs.com/package/dependency-tree) which uses [filing-cabinet](https://www.npmjs.com/package/filing-cabinet) to resolve modules. However it requires configurations for each file type (js/jsx) and (ts/tsx). So provide both `webpackConfig` and `tsConfig` options to madge.
387 |
388 | ## Using mixed import syntax in the same file?
389 |
390 | Only one syntax is used by default. You can use both though if you're willing to take the degraded performance. Put this in your madge config to enable mixed imports.
391 |
392 | For ES6 + CommonJS:
393 |
394 | ```json
395 | {
396 | "detectiveOptions": {
397 | "es6": {
398 | "mixedImports": true
399 | }
400 | }
401 | }
402 | ```
403 |
404 | For TypeScript + CommonJS:
405 |
406 | ```json
407 | {
408 | "detectiveOptions": {
409 | "ts": {
410 | "mixedImports": true
411 | }
412 | }
413 | }
414 | ```
415 |
416 | ## How to ignore `import type` statements in ES6 + Flow?
417 |
418 | Put this in your madge config.
419 |
420 | ```json
421 | {
422 | "detectiveOptions": {
423 | "es6": {
424 | "skipTypeImports": true
425 | }
426 | }
427 | }
428 | ```
429 |
430 | ## How to ignore `import` in type annotations in TypeScript?
431 |
432 | Put this in your madge config.
433 |
434 | ```json
435 | {
436 | "detectiveOptions": {
437 | "ts": {
438 | "skipTypeImports": true
439 | }
440 | }
441 | }
442 | ```
443 |
444 | ## How to ignore dynamic imports in Typescript?
445 |
446 | Put this in your madge config.
447 |
448 | ```json
449 | {
450 | "detectiveOptions": {
451 | "ts": {
452 | "skipAsyncImports": true
453 | },
454 | "tsx": {
455 | "skipAsyncImports": true
456 | }
457 | }
458 | }
459 | ```
460 |
461 | Note: `tsx` is optional, use this when working with JSX.
462 |
463 | ## Mixing TypesScript and Javascript imports?
464 |
465 | Ensure you have this in your `.tsconfig` file.
466 |
467 | ```json
468 | {
469 | "compilerOptions": {
470 | "module": "commonjs",
471 | "allowJs": true
472 | }
473 | }
474 | ```
475 |
476 | ## What's the "Error: write EPIPE" when exporting graph to image?
477 |
478 | Ensure you have [installed Graphviz](#graphviz-optional). If you're running Windows, note that Graphviz is not added to the `PATH` variable during install. You should add the folder of `gvpr.exe` (typically `%Graphviz_folder%/bin`) to the `PATH` variable manually.
479 |
480 | ## How do I fix the "Graphviz not built with triangulation library" error when using sfdp layout?
481 |
482 | Homebrew doesn't include GTS by default. Fix this by doing:
483 |
484 | ```sh
485 | brew uninstall graphviz
486 | brew install gts
487 | brew install graphviz
488 | ```
489 |
490 | ## The image produced by madge is very hard to read, what's wrong?
491 |
492 | Try running madge with a different layout, here's a list of the ones you can try:
493 |
494 | * **dot** "hierarchical" or layered drawings of directed graphs. This is the default tool to use if edges have directionality.
495 |
496 | * **neato** "spring model'' layouts. This is the default tool to use if the graph is not too large (about 100 nodes) and you don't know anything else about it. Neato attempts to
497 | minimize a global energy function, which is equivalent to statistical multi-dimensional scaling.
498 |
499 | * **fdp** "spring model'' layouts similar to those of neato, but does this by reducing forces rather than working with energy.
500 |
501 | * **sfdp** multiscale version of fdp for the layout of large graphs.
502 |
503 | * **twopi** radial layouts, after Graham Wills 97. Nodes are placed on concentric circles depending their distance from a given root node.
504 |
505 | * **circo** circular layout, after Six and Tollis 99, Kauffman and Wiese 02. This is suitable for certain diagrams of multiple cyclic structures, such as certain telecommunications networks.
506 |
507 | # Credits
508 |
509 | ## Contributors
510 |
511 | This project exists thanks to all the people who contribute.
512 |
513 |
514 |
515 |
516 | ## Donations ❤️
517 |
518 | Thanks to the awesome people below for making donations! 🙏[Donate](https://paypal.me/pahen)
519 |
520 |
521 |
522 | Bernard Stojanović (24 Mars, 2021)
523 |
524 |
525 |
526 |
527 |
528 |
529 | Ole Jørgen Brønner (Oct 8, 2020)
530 |
531 |
532 |
533 |
534 |
535 |
536 | RxDB (Apr 1, 2020)
537 |
538 |
539 |
540 |
541 |
542 |
543 | Peter Verswyvelen (Feb 24, 2020)
544 |
545 |
546 |
547 |
548 |
549 |
550 | Landon Alder (Mar 19, 2019)
551 |
552 |
553 |
554 |
555 | # License
556 |
557 | MIT License
558 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### Changelog
2 |
3 | All notable changes to this project will be documented in this file. Dates are displayed in UTC.
4 |
5 | #### [v8.0.0](https://github.com/pahen/madge/compare/v7.0.0...v8.0.0)
6 |
7 | - chore: edit test/output.sh comply with POSIX [`#391`](https://github.com/pahen/madge/pull/391)
8 | - Update ts-graphviz@2.1.2 and typescript@5.4.4 [`#424`](https://github.com/pahen/madge/pull/424)
9 | - Remove not required dep on precinct [`#421`](https://github.com/pahen/madge/pull/421)
10 | - Cleanup README and remove Travis config [`#422`](https://github.com/pahen/madge/pull/422)
11 | - Correct engine requirements [`#419`](https://github.com/pahen/madge/pull/419)
12 | - Add tests for Vue [`#418`](https://github.com/pahen/madge/pull/418)
13 | - Update to dependency-tree@11 [`#417`](https://github.com/pahen/madge/pull/417)
14 | - Add empty TS config to fix the tests [`0163dc4`](https://github.com/pahen/madge/commit/0163dc469b3a52af423540ea75301d2f6e01b018)
15 | - Add comment [`3a52fae`](https://github.com/pahen/madge/commit/3a52fae6cf4ad3018c3a8c43be4e361cbf4679ec)
16 | - Remove integrated npm cache [`e25fa64`](https://github.com/pahen/madge/commit/e25fa6440631af238edafe02706f3a4616862ffa)
17 |
18 | ### [v7.0.0](https://github.com/pahen/madge/compare/v6.1.0...v7.0.0)
19 |
20 | > 8 April 2024
21 |
22 | - devDeps: release-it@^15.6.0->^16.2.1 [`#393`](https://github.com/pahen/madge/pull/393)
23 | - ci: Run build & test scripts on GitHub Actions [`#394`](https://github.com/pahen/madge/pull/394)
24 | - chore: bump and align dependencies; dedupe typescript [`#379`](https://github.com/pahen/madge/pull/379)
25 | - Adding option to not print the circular file count [`#387`](https://github.com/pahen/madge/pull/387)
26 | - Update README, explaining to how ignore typed imports on .tsx files [`#389`](https://github.com/pahen/madge/pull/389)
27 | - docs: update README to include ignore dynamic imports FAQ [`#360`](https://github.com/pahen/madge/pull/360)
28 | - docs: fix donate anchor [`#362`](https://github.com/pahen/madge/pull/362)
29 | - Release 6.1.0 [`#378`](https://github.com/pahen/madge/pull/378)
30 | - chore: check in package-lock.json [`2137846`](https://github.com/pahen/madge/commit/2137846544697e3cdad9fb9d3599875f241dcb3d)
31 | - devDeps: release-it@^16.1.5->^16.2.1 [`1bcdc12`](https://github.com/pahen/madge/commit/1bcdc12093131b0a92fc9dc76a701cfccdf03867)
32 | - ci: add build/test workflow for GitHub Actions [`fa34634`](https://github.com/pahen/madge/commit/fa34634e06116572d606e6035f7402a7d276ab6e)
33 |
34 | #### [v6.1.0](https://github.com/pahen/madge/compare/v6.0.0...v6.1.0)
35 |
36 | > 4 June 2023
37 |
38 | - constrain and bump typescript versions [`#376`](https://github.com/pahen/madge/pull/376)
39 | - Disable spinner when running in CI [`#356`](https://github.com/pahen/madge/pull/356)
40 | - Remove deploy to NPM from Travis build [`#354`](https://github.com/pahen/madge/pull/354)
41 | - feat: support for rankdir in CLI [`#311`](https://github.com/pahen/madge/pull/311)
42 | - Move typescript to peer dependencies [`#350`](https://github.com/pahen/madge/pull/350)
43 | - Release 6.0.0 [`#352`](https://github.com/pahen/madge/pull/352)
44 | - Release 6.1.0 [`66bb0c3`](https://github.com/pahen/madge/commit/66bb0c30798b31557d239ca61b2a43ffa6f53fba)
45 | - install and use ci-info [`c35da47`](https://github.com/pahen/madge/commit/c35da472e7352340209f4537d020b2702f9e67e7)
46 | - remove ci-info and change isEnabled setting [`a771115`](https://github.com/pahen/madge/commit/a771115ce570c8ea8d4c3ca68300dcfa0b5759ea)
47 |
48 | ### [v6.0.0](https://github.com/pahen/madge/compare/v5.0.2...v6.0.0)
49 |
50 | > 29 January 2023
51 |
52 | - Handle collect tsconfig's extends fileds [`#349`](https://github.com/pahen/madge/pull/349)
53 | - Update packages detective-* to latest [`#348`](https://github.com/pahen/madge/pull/348)
54 | - chore: bump 'detective-typescript' package to allow newer TS versions [`#321`](https://github.com/pahen/madge/pull/321)
55 | - Replace to ts-graphviz package [`#341`](https://github.com/pahen/madge/pull/341)
56 | - chore: bump detective-postcss to v6 [`#319`](https://github.com/pahen/madge/pull/319)
57 | - Drop support Node.js EOL versions(<14) [`#342`](https://github.com/pahen/madge/pull/342)
58 | - handle collect tsconfig's extends fileds [`#323`](https://github.com/pahen/madge/issues/323)
59 | - Changed to depend on the ts-graphviz package [`9b9ae87`](https://github.com/pahen/madge/commit/9b9ae878622be4a58951c5fcb9d46cc0c44e4593)
60 | - Release 6.0.0 [`2aa75b2`](https://github.com/pahen/madge/commit/2aa75b2aba279d02dd7cf7640a0fcd6236322228)
61 | - update packages detective-* to latest [`60b6557`](https://github.com/pahen/madge/commit/60b6557efc5a1da04452dfc0f0b1a0281f9c8776)
62 |
63 | #### [v5.0.2](https://github.com/pahen/madge/compare/v5.0.1...v5.0.2)
64 |
65 | > 27 January 2023
66 |
67 | - Add NPM task and docs for creating a release [`#346`](https://github.com/pahen/madge/pull/346)
68 | - Fix broken link to Travis in README [`#345`](https://github.com/pahen/madge/pull/345)
69 | - docs: fix subtrees typo in README [`#325`](https://github.com/pahen/madge/pull/325)
70 | - minor typo in README.md [`#300`](https://github.com/pahen/madge/pull/300)
71 | - Release 5.0.2 [`614d44d`](https://github.com/pahen/madge/commit/614d44d5f1ab3cc9e9875e67d1e46f4b971d660a)
72 | - minor typos in README.md [`edc27c8`](https://github.com/pahen/madge/commit/edc27c85c5257df65184fd44c80549ffd0346360)
73 | - minor type in README.md [`9f5514c`](https://github.com/pahen/madge/commit/9f5514c568aa1d3944a17c459daee93c2948e144)
74 |
75 | #### [v5.0.1](https://github.com/pahen/madge/compare/v5.0.0...v5.0.1)
76 |
77 | > 23 June 2021
78 |
79 | - Fix issue with command line options stopped working [`512c2cb`](https://github.com/pahen/madge/commit/512c2cbfec1db53068e9b805e181ec1b38d30a41)
80 | - Update README [`8e98136`](https://github.com/pahen/madge/commit/8e98136c5597f87acfa93cdae18c477cbf8a30da)
81 |
82 | ### [v5.0.0](https://github.com/pahen/madge/compare/v4.0.2...v5.0.0)
83 |
84 | > 22 June 2021
85 |
86 | - Improve Graphviz support detection [`#274`](https://github.com/pahen/madge/pull/274)
87 | - Update list of donations [`aa49a0a`](https://github.com/pahen/madge/commit/aa49a0a94fd966be25141c92c75f97d90ad73dc6)
88 | - Update deps [`820c9b8`](https://github.com/pahen/madge/commit/820c9b827b3ec1c51e454b0ce7ea6f2427f3b6da)
89 |
90 | #### [v4.0.2](https://github.com/pahen/madge/compare/v4.0.1...v4.0.2)
91 |
92 | > 8 March 2021
93 |
94 | - Bump detective-typescript [`e6985d2`](https://github.com/pahen/madge/commit/e6985d25518ea48f816ee007d0bde519ce6a3106)
95 |
96 | #### [v4.0.1](https://github.com/pahen/madge/compare/v4.0.0...v4.0.1)
97 |
98 | > 5 March 2021
99 |
100 | - Fix potential command injection vulnerability [`da5cbc9`](https://github.com/pahen/madge/commit/da5cbc9ab30372d687fa7c324b22af7ffa5c6332)
101 |
102 | ### [v4.0.0](https://github.com/pahen/madge/compare/v3.12.0...v4.0.0)
103 |
104 | > 5 January 2021
105 |
106 | - Upgrade dependencies & raise minimum Node.js version [`#269`](https://github.com/pahen/madge/pull/269)
107 | - Upgrade core dependencies [`fe8a186`](https://github.com/pahen/madge/commit/fe8a186b18a08a6e6f69d49d95f156660f575851)
108 | - Upgrade commander to version 6 [`5ca410c`](https://github.com/pahen/madge/commit/5ca410c4b4332c67a3104b7e851d0976be78de9e)
109 | - Require Node.js 10.13 [`eee3dc0`](https://github.com/pahen/madge/commit/eee3dc0540ce3592fbbbada8586cf5b59e5ba33d)
110 |
111 | #### [v3.12.0](https://github.com/pahen/madge/compare/v3.11.0...v3.12.0)
112 |
113 | > 2 November 2020
114 |
115 | - Remove pify again [`#264`](https://github.com/pahen/madge/pull/264)
116 | - Update ora to version 5 [`#262`](https://github.com/pahen/madge/pull/262)
117 | - Replace pify with util.promisify [`#263`](https://github.com/pahen/madge/pull/263)
118 | - Update README [`e4be868`](https://github.com/pahen/madge/commit/e4be868c6c057be3799d8b8fd0c9fa928bbc9b80)
119 | - Bump dependency-tree to 7.2.2 [`0eaccb7`](https://github.com/pahen/madge/commit/0eaccb728dbdcf36e67850fc89a552361c69a200)
120 |
121 | #### [v3.11.0](https://github.com/pahen/madge/compare/v3.10.0...v3.11.0)
122 |
123 | > 1 October 2020
124 |
125 | - Add support for combining --circular and --dot [`d2ce3f7`](https://github.com/pahen/madge/commit/d2ce3f7ac0dec928eaee37ab8d768d291fa24f84)
126 |
127 | #### [v3.10.0](https://github.com/pahen/madge/compare/v3.9.2...v3.10.0)
128 |
129 | > 14 September 2020
130 |
131 | - Add support for combining --image and --circular [`7a4bd3b`](https://github.com/pahen/madge/commit/7a4bd3be2fbefab3e9468272c912991602360562)
132 |
133 | #### [v3.9.2](https://github.com/pahen/madge/compare/v3.9.1...v3.9.2)
134 |
135 | > 16 June 2020
136 |
137 | - Bump dependencies [`a8e3674`](https://github.com/pahen/madge/commit/a8e367464f5a4bc1586c4a21735691253406ddb4)
138 | - Downgrade to ESLint 6.8.0 [`bc539af`](https://github.com/pahen/madge/commit/bc539afcda79291b5f694c0b8ab8423356f0d53e)
139 |
140 | #### [v3.9.1](https://github.com/pahen/madge/compare/v3.9.0...v3.9.1)
141 |
142 | > 8 June 2020
143 |
144 | - chore: significant speedup by skipping filing-cabinet ts-config parsing [`#237`](https://github.com/pahen/madge/pull/237)
145 | - Clarification for mixed projects (with JS and TS) [`#246`](https://github.com/pahen/madge/pull/246)
146 |
147 | #### [v3.9.0](https://github.com/pahen/madge/compare/v3.8.0...v3.9.0)
148 |
149 | > 7 May 2020
150 |
151 | - Remove info about Patreon and Open Collective [`a57eeff`](https://github.com/pahen/madge/commit/a57eeffd27efdf18dbd3d5f941bb23a1aab18f48)
152 | - Update list of backers [`545be08`](https://github.com/pahen/madge/commit/545be086dec854fd46024cf6a1f6df2c9d710d5e)
153 | - Update dependencies [`ffa4fdd`](https://github.com/pahen/madge/commit/ffa4fdd64695b22002adaf2add100f1a3da028bc)
154 |
155 | #### [v3.8.0](https://github.com/pahen/madge/compare/v3.7.0...v3.8.0)
156 |
157 | > 9 March 2020
158 |
159 | - Add `leaves` option to show modules that do not have dependencies [`97ed27f`](https://github.com/pahen/madge/commit/97ed27f846f3e53dc127c577e6667f5faf6a814b)
160 | - Update README [`9c1f7d2`](https://github.com/pahen/madge/commit/9c1f7d2d9a23b42582caebd529cf73753a3226cc)
161 | - Updated list of donations [`899f15f`](https://github.com/pahen/madge/commit/899f15f1d4b7673d0a7f35b74ae171517eaad5a9)
162 |
163 | #### [v3.7.0](https://github.com/pahen/madge/compare/v3.6.0...v3.7.0)
164 |
165 | > 30 January 2020
166 |
167 | - feat: support package.json config [`#236`](https://github.com/pahen/madge/pull/236)
168 | - review [`0acebb7`](https://github.com/pahen/madge/commit/0acebb7417ea17a0980630685ee4dfe182fbcdbb)
169 | - Drop Node.js 8 support [`2cba3e1`](https://github.com/pahen/madge/commit/2cba3e1a93e45b74cf680119e3affffc0feaa695)
170 | - Drop Node.JS 6 support [`ca3c555`](https://github.com/pahen/madge/commit/ca3c5553e72457d7f96fefa828ef03f6a1cbe0b8)
171 |
172 | #### [v3.6.0](https://github.com/pahen/madge/compare/v3.5.1...v3.6.0)
173 |
174 | > 11 November 2019
175 |
176 | - Add test for TypeScript with mixed import syntax [`50c1c10`](https://github.com/pahen/madge/commit/50c1c10c1e7d369c5e7d1017463451f8d270cdf9)
177 | - Update deps [`f1125d0`](https://github.com/pahen/madge/commit/f1125d0af5da27510f709a4b8a2e84b45ff5f578)
178 |
179 | #### [v3.5.1](https://github.com/pahen/madge/compare/v3.5.0...v3.5.1)
180 |
181 | > 7 November 2019
182 |
183 | - Add funding to package.json [`8ee9126`](https://github.com/pahen/madge/commit/8ee91265973985eee08b873eff0e5104d48d4f61)
184 |
185 | #### [v3.5.0](https://github.com/pahen/madge/compare/v3.4.4...v3.5.0)
186 |
187 | > 28 October 2019
188 |
189 | - Add an .svg public method to the API [`#171`](https://github.com/pahen/madge/pull/171)
190 | - Respect graphVizOptions in DOT output [`4edf82a`](https://github.com/pahen/madge/commit/4edf82a3578639972229034aed4ebe1d51ae2bdf)
191 | - Added credits to README [`9287c3c`](https://github.com/pahen/madge/commit/9287c3c1f1f51ffdb6c27a890c51f32f383b9c5c)
192 | - Add .svg() in API to export the svg as a Buffer [`d01f6f3`](https://github.com/pahen/madge/commit/d01f6f33a67ecfb7072667b08f6c6550d52a5043)
193 |
194 | #### [v3.4.4](https://github.com/pahen/madge/compare/v3.4.3...v3.4.4)
195 |
196 | > 12 February 2019
197 |
198 | - [Fixes #203] Exclude test folder from npm registry [`#205`](https://github.com/pahen/madge/pull/205)
199 | - Merge pull request #205 from SethDavenport/chore(exclude-test-folder-from-npm) [`#203`](https://github.com/pahen/madge/issues/203)
200 | - Add NPM releasing from Travis [`97f060b`](https://github.com/pahen/madge/commit/97f060b1b0f19c6d08b0fc53c24494df7fdd6cdc)
201 | - Exclude test folder from npm registry [`41f94f2`](https://github.com/pahen/madge/commit/41f94f22f5521bde0523ec7df17fbdf43e587e33)
202 |
203 | #### [v3.4.3](https://github.com/pahen/madge/compare/v3.4.2...v3.4.3)
204 |
205 | > 17 January 2019
206 |
207 | - Add link to my Patreon page in README [`9ee722a`](https://github.com/pahen/madge/commit/9ee722a8d4708f978042c17ffc172409470dfa86)
208 | - Add info about --orphans to CLI docs [`925c57e`](https://github.com/pahen/madge/commit/925c57efcbe2357f49131e51050107e9ad54eab1)
209 | - Bump dependency-tree [`c2ce2ac`](https://github.com/pahen/madge/commit/c2ce2ac17888c71aa020ac7da5277535ffaacdac)
210 |
211 | #### [v3.4.2](https://github.com/pahen/madge/compare/v3.4.1...v3.4.2)
212 |
213 | > 10 January 2019
214 |
215 | - Eslint should not be a dev dependency [`3165988`](https://github.com/pahen/madge/commit/316598895e64bbca42654ae263422dba0d365772)
216 |
217 | #### [v3.4.1](https://github.com/pahen/madge/compare/v3.4.0...v3.4.1)
218 |
219 | > 10 January 2019
220 |
221 | - Update eslint (peer dependency for typescript-eslint-parser) [`2e6643a`](https://github.com/pahen/madge/commit/2e6643af90b70cc055e37725942a2177f3e05bdd)
222 |
223 | #### [v3.4.0](https://github.com/pahen/madge/compare/v3.3.0...v3.4.0)
224 |
225 | > 7 January 2019
226 |
227 | - Support .tsx files and specifying a tsconfig [`#193`](https://github.com/pahen/madge/pull/193)
228 | - README: improve instructions related to Graphviz [`#183`](https://github.com/pahen/madge/pull/183)
229 |
230 | #### [v3.3.0](https://github.com/pahen/madge/compare/v3.2.0...v3.3.0)
231 |
232 | > 31 October 2018
233 |
234 | - Update dependencies & test on Node.js 10 [`#176`](https://github.com/pahen/madge/pull/176)
235 | - Add --no-spinner option [`b1ad3eb`](https://github.com/pahen/madge/commit/b1ad3eb5d878e31f1f87d99f6b3de713003079c1)
236 | - Update dependencies [`9b1293e`](https://github.com/pahen/madge/commit/9b1293e4f7c990efd1e58cd9f069bdd3a6b36fb7)
237 | - Update dev dependencies [`2260b61`](https://github.com/pahen/madge/commit/2260b613ecddb6f623584b82b1a30542b01c90e7)
238 |
239 | #### [v3.2.0](https://github.com/pahen/madge/compare/v3.1.1...v3.2.0)
240 |
241 | > 26 June 2018
242 |
243 | - Plot nodes as boxes [`#165`](https://github.com/pahen/madge/pull/165)
244 | - Document new graph settings [`c6e742f`](https://github.com/pahen/madge/commit/c6e742f97a065a452445702645ec06c1ea91ea07)
245 | - aesthetic changes: plot rounded boxes, prefer left to right. [`7a7ed8c`](https://github.com/pahen/madge/commit/7a7ed8c2f9f4f189d0ba1372d30b06fef7e50cc4)
246 | - fix incorrect hex. 5 -> 6 digits. [`e8e330c`](https://github.com/pahen/madge/commit/e8e330c9f5b70c3d925ec1674c7d62253750196e)
247 |
248 | #### [v3.1.1](https://github.com/pahen/madge/compare/v3.1.0...v3.1.1)
249 |
250 | > 24 May 2018
251 |
252 | #### [v3.1.0](https://github.com/pahen/madge/compare/v3.0.1...v3.1.0)
253 |
254 | > 22 May 2018
255 |
256 | - Bind all dependencies to latest version [`#161`](https://github.com/pahen/madge/pull/161)
257 | - Update ora to version 2 [`#155`](https://github.com/pahen/madge/pull/155)
258 | - Remove mz as a production dependency. Instead use pify for promisifying. [`#154`](https://github.com/pahen/madge/pull/154)
259 | - Remove package-lock.json [`8fd1859`](https://github.com/pahen/madge/commit/8fd18595a9b8bf28f67c01795b3ed182eb2d4b8d)
260 | - Bind all dependencies to latest version, It fixes security issue in rc => deep-extend library. And added .idea project files to gitignore [`147d431`](https://github.com/pahen/madge/commit/147d431e506dac03230fedb2cf12be5a11e79c7d)
261 | - Use caret ranges for all dependencies [`b0e334a`](https://github.com/pahen/madge/commit/b0e334ac22059edcf3bee059ba075cd1b12da985)
262 |
263 | #### [v3.0.1](https://github.com/pahen/madge/compare/v3.0.0...v3.0.1)
264 |
265 | > 5 February 2018
266 |
267 | - Fix broken link [`#149`](https://github.com/pahen/madge/pull/149)
268 | - Update deps [`3ba103c`](https://github.com/pahen/madge/commit/3ba103c8afa978b9163e9958d3197b294f9a93ce)
269 | - Fix issue with short CLI options not working properly [`f73704d`](https://github.com/pahen/madge/commit/f73704dc3bd88fbc6308f1799319825d10251505)
270 |
271 | ### [v3.0.0](https://github.com/pahen/madge/compare/v2.2.0...v3.0.0)
272 |
273 | > 22 January 2018
274 |
275 | - chore: upgrade dependency-tree to 6.0.0 to address warning [`#147`](https://github.com/pahen/madge/pull/147)
276 | - Update dev-dependencies [`#144`](https://github.com/pahen/madge/pull/144)
277 | - Use caret ranges for all dependencies [`#141`](https://github.com/pahen/madge/pull/141)
278 | - Update chalk to 2.3.0 [`#140`](https://github.com/pahen/madge/pull/140)
279 | - Updates dependencies (not dev-dependencies) [`#134`](https://github.com/pahen/madge/pull/134)
280 | - deps: Update debug to fix security issue [`#130`](https://github.com/pahen/madge/pull/130)
281 | - Regenerate package-lock.json [`5d26187`](https://github.com/pahen/madge/commit/5d261874f7471cf66898205b83c967401c050fd3)
282 | - dependency-tree@5.11.1 [`c35174c`](https://github.com/pahen/madge/commit/c35174c39c58ed7a5cf4e9560c1d16407870c4ea)
283 | - Updat eslint to 4.13.0 and @aptoma/eslint-config to 7.0.1. [`b184021`](https://github.com/pahen/madge/commit/b184021cf758eeffad2eb7f590e218c463e191dc)
284 |
285 | #### [v2.2.0](https://github.com/pahen/madge/compare/v2.1.0...v2.2.0)
286 |
287 | > 29 August 2017
288 |
289 | #### [v2.1.0](https://github.com/pahen/madge/compare/v2.0.0...v2.1.0)
290 |
291 | > 26 August 2017
292 |
293 | - Support for TypeScript [`#124`](https://github.com/pahen/madge/pull/124)
294 | - Add tests for TypeScript [`50eb3ca`](https://github.com/pahen/madge/commit/50eb3ca5a884ce7cf23118755b8ed13f2ccf01a1)
295 | - Center badges in README [`5b981cd`](https://github.com/pahen/madge/commit/5b981cd637787241e119587207ef09a878650136)
296 | - Create LICENSE [`741cd76`](https://github.com/pahen/madge/commit/741cd761d4d0358022fee20ad3312f5230708603)
297 |
298 | ### [v2.0.0](https://github.com/pahen/madge/compare/v1.6.0...v2.0.0)
299 |
300 | > 15 July 2017
301 |
302 | - Add —-orphans to show modules that no one is depending on [`#121`](https://github.com/pahen/madge/pull/121)
303 | - Fix typo [`#119`](https://github.com/pahen/madge/pull/119)
304 | - fix typo in `.image()` example [`#113`](https://github.com/pahen/madge/pull/113)
305 | - Always include file extension [`b6cac48`](https://github.com/pahen/madge/commit/b6cac48c9162736a40e1f6b7aa9dd3a8d225d6da)
306 | - Fix bug with --extensions not working [`fc4acce`](https://github.com/pahen/madge/commit/fc4acce1517514dc7e34e7a6fb569e83047251b4)
307 | - Support for Less [`d2d4b96`](https://github.com/pahen/madge/commit/d2d4b96cd160b4bea3d24b8399100a0030158feb)
308 |
309 | #### [v1.6.0](https://github.com/pahen/madge/compare/v1.5.0...v1.6.0)
310 |
311 | > 8 February 2017
312 |
313 | - Show CLI spinner with the currently processed file [`f57480a`](https://github.com/pahen/madge/commit/f57480aaaa4e70e9d9afcab78f425ee0ee6f5754)
314 | - Add support for dependencyFilter function [`eac8591`](https://github.com/pahen/madge/commit/eac85914a69e8359aca3dd737ff1d6026419f79b)
315 | - Add script for testing output [`e74df55`](https://github.com/pahen/madge/commit/e74df55c6a5d1f16eda3ef708b1f43bddb7a25d9)
316 |
317 | #### [v1.5.0](https://github.com/pahen/madge/compare/v1.4.6...v1.5.0)
318 |
319 | > 13 January 2017
320 |
321 | - Tweak output colors and error messages [`60d59a6`](https://github.com/pahen/madge/commit/60d59a664dda42d98a4cd7f772afb9acbb38da70)
322 | - Support running —circular with —-warning [`b8a0371`](https://github.com/pahen/madge/commit/b8a0371c2695dbd4afbade785b87e9be42182ca7)
323 | - Update changelog [`fa7cc99`](https://github.com/pahen/madge/commit/fa7cc99a09cf4e1af9f9a6664dd57eda1f862fa0)
324 |
325 | #### [v1.4.6](https://github.com/pahen/madge/compare/v1.4.5...v1.4.6)
326 |
327 | > 9 January 2017
328 |
329 | - Update circular dependency check output [`4ec6322`](https://github.com/pahen/madge/commit/4ec6322ec50b5e8bdd16fc0a697fd0f68e803ba7)
330 | - Update changelog [`d718612`](https://github.com/pahen/madge/commit/d718612b0d00e3bf382a8042ce97aa76fa232de4)
331 | - Bump dependency-tree [`3bc9689`](https://github.com/pahen/madge/commit/3bc9689c903b2431e0dee3e6796fa23b3bd7886f)
332 |
333 | #### [v1.4.5](https://github.com/pahen/madge/compare/v1.4.4...v1.4.5)
334 |
335 | > 7 January 2017
336 |
337 | - Keep file extension in module paths until we output the graph [`bd980cf`](https://github.com/pahen/madge/commit/bd980cfdbafa1952607df442ca771e68d58a4757)
338 | - Update changelog [`0492763`](https://github.com/pahen/madge/commit/04927633ce7223980dbbc4818e6e1fbb533737b3)
339 |
340 | #### [v1.4.4](https://github.com/pahen/madge/compare/v1.4.3...v1.4.4)
341 |
342 | > 4 January 2017
343 |
344 | - Add tests for resolving using webpack resolve.root [`8452575`](https://github.com/pahen/madge/commit/8452575bb2ea567593ef1488e6148f96f82acd26)
345 | - Update changelog [`13648c7`](https://github.com/pahen/madge/commit/13648c709886dfaf8bff1bd4fa24624d12d769ba)
346 | - Bump dependencies [`c9b1795`](https://github.com/pahen/madge/commit/c9b17951afd7e9caeaa00cd5b61d4ebdd262a937)
347 |
348 | #### [v1.4.3](https://github.com/pahen/madge/compare/v1.4.2...v1.4.3)
349 |
350 | > 12 October 2016
351 |
352 | - Fix bug with CLI —-require-config and --webpack-config not working [`386d710`](https://github.com/pahen/madge/commit/386d71055e263f04cc05779ebd259979c1415b7b)
353 | - Update changelog [`cc4cb9e`](https://github.com/pahen/madge/commit/cc4cb9ea38f60341b1a57996a52036da1626f9a2)
354 | - Update changelog [`97ba4c2`](https://github.com/pahen/madge/commit/97ba4c2cb78568766593fabbe89eec738ea4f65d)
355 |
356 | #### [v1.4.2](https://github.com/pahen/madge/compare/v1.4.1...v1.4.2)
357 |
358 | > 6 October 2016
359 |
360 | - Rename —-show-skipped to —-warning [`a2d7c99`](https://github.com/pahen/madge/commit/a2d7c99d9b8e8c04e982a9505e1c95f682f22dc4)
361 |
362 | #### [v1.4.1](https://github.com/pahen/madge/compare/v1.4.0...v1.4.1)
363 |
364 | > 6 October 2016
365 |
366 | - Don’t show warnings about skipped files by default [`2cfa8d7`](https://github.com/pahen/madge/commit/2cfa8d74d7fad3d85225523515a9c36addbe0b3a)
367 |
368 | #### [v1.4.0](https://github.com/pahen/madge/compare/v1.3.2...v1.4.0)
369 |
370 | > 6 October 2016
371 |
372 | - Show skipped files as warnings (disable with —-no-warning) [`#108`](https://github.com/pahen/madge/pull/108)
373 | - Update changelog [`4cb2c4c`](https://github.com/pahen/madge/commit/4cb2c4c806cfe87db8dabbc32920ac8f539e3dcc)
374 |
375 | #### [v1.3.2](https://github.com/pahen/madge/compare/v1.3.1...v1.3.2)
376 |
377 | > 3 October 2016
378 |
379 | - Bump dependency-tree [`6fb76b2`](https://github.com/pahen/madge/commit/6fb76b2fe29a760b4652edf60724880f10518dda)
380 |
381 | #### [v1.3.1](https://github.com/pahen/madge/compare/v1.3.0...v1.3.1)
382 |
383 | > 1 October 2016
384 |
385 | - Allow to pass options to detectives [`#105`](https://github.com/pahen/madge/pull/105)
386 | - Bump dependency-tree to 5.7.0 [`d5c5de2`](https://github.com/pahen/madge/commit/d5c5de26c1c12f716d58fb0a780094ea939ed954)
387 |
388 | #### [v1.3.0](https://github.com/pahen/madge/compare/v1.2.0...v1.3.0)
389 |
390 | > 6 September 2016
391 |
392 | - Improve performance on large codebase [`#104`](https://github.com/pahen/madge/pull/104)
393 | - Cache paths when converting tree for better performance [`fc67d00`](https://github.com/pahen/madge/commit/fc67d0060d3050357f161cf5a86973a53b82910c)
394 | - Rename commonjs to cjs [`8c56037`](https://github.com/pahen/madge/commit/8c56037570f53ed0c0560fcff391efe05227dc1b)
395 | - Remove unnecessary mapping of CLI args to config [`93f19da`](https://github.com/pahen/madge/commit/93f19da023ff3ddfd52b89854272555640b3104e)
396 |
397 | #### [v1.2.0](https://github.com/pahen/madge/compare/v1.1.0...v1.2.0)
398 |
399 | > 1 September 2016
400 |
401 | - Add option —-stdin for piping predefined tree [`#103`](https://github.com/pahen/madge/pull/103)
402 | - Cleanup in tests [`75dce71`](https://github.com/pahen/madge/commit/75dce7194059337163632f284e78c6e4dd795883)
403 | - Add missing test file [`1ce0c01`](https://github.com/pahen/madge/commit/1ce0c015269b56f32994621226753579ddac2c64)
404 |
405 | #### [v1.1.0](https://github.com/pahen/madge/compare/v1.0.0...v1.1.0)
406 |
407 | > 23 August 2016
408 |
409 | - Fix failing tests on Windows [`#98`](https://github.com/pahen/madge/pull/98)
410 | - Support for setting custom GraphViz options. Fixes #94 [`#94`](https://github.com/pahen/madge/issues/94)
411 | - Replace parsers with dependency-tree module [`4be7db2`](https://github.com/pahen/madge/commit/4be7db2f096c6695ac1b8eacfd0095c5fa7ac7fb)
412 | - Use promises in API [`dbad4b6`](https://github.com/pahen/madge/commit/dbad4b6ba89a05168be8eb702340f9887e1bf46a)
413 | - Move tree generation to tree.js and add support folders [`07a36ed`](https://github.com/pahen/madge/commit/07a36edb65d84a84096ef3bb7228f4b9448df37d)
414 |
415 | ### [v1.0.0](https://github.com/pahen/madge/compare/0.6.0...v1.0.0)
416 |
417 | > 19 August 2016
418 |
419 | - Version 1.0 [`#96`](https://github.com/pahen/madge/pull/96)
420 |
421 | #### [0.6.0](https://github.com/pahen/madge/compare/0.5.5...0.6.0)
422 |
423 | > 6 July 2016
424 |
425 | - Convert classes to ES6. [`8134400`](https://github.com/pahen/madge/commit/8134400915e1e6bef99fc3bceb5c790e7aa2caa7)
426 | - Convert tests to ES6. [`4a9797e`](https://github.com/pahen/madge/commit/4a9797eac699df69e86d36a12ae92beb28836395)
427 | - Use ES6 internally. [`6a0e2ca`](https://github.com/pahen/madge/commit/6a0e2ca5838ceb509ba3330fa4031ea1a6f7f7e5)
428 |
429 | #### [0.5.5](https://github.com/pahen/madge/compare/0.5.4...0.5.5)
430 |
431 | > 3 July 2016
432 |
433 | - Fix matching absolute path [`#80`](https://github.com/pahen/madge/pull/80)
434 | - Support for es6 re-export syntax [`#91`](https://github.com/pahen/madge/pull/91)
435 | - AMD: Support files with embedded es6 [`#90`](https://github.com/pahen/madge/pull/90)
436 | - Improve readme circular return object [`#85`](https://github.com/pahen/madge/pull/85)
437 | - AMD: Support files with embedded es6 (#90) [`#84`](https://github.com/pahen/madge/issues/84)
438 | - Update code to pass ESLint rules. [`e0866d5`](https://github.com/pahen/madge/commit/e0866d56e6b7dc7112f71616ac22dc7c0d954ea2)
439 | - Remove react-tools since detective-es6 handles it now. [`b16f946`](https://github.com/pahen/madge/commit/b16f94625846436859ef7a5a5f68545305db509e)
440 | - Move RequireJS specific code into amd.js [`374d14c`](https://github.com/pahen/madge/commit/374d14c1742a1feba2e6fb7805ab1d510b78d661)
441 |
442 | #### [0.5.4](https://github.com/pahen/madge/compare/0.5.3...0.5.4)
443 |
444 | > 13 June 2016
445 |
446 | - Bump detective-es6 for JSX and ES7 support [`#83`](https://github.com/pahen/madge/pull/83)
447 | - Don't use sudo to install the package [`#76`](https://github.com/pahen/madge/pull/76)
448 | - Correct CLI API for mainRequireModule [`#72`](https://github.com/pahen/madge/pull/72)
449 | - Bump detective-es6 for JSX and ES7 support [`#81`](https://github.com/pahen/madge/issues/81) [`#61`](https://github.com/pahen/madge/issues/61)
450 | - Update status icons in README. [`61429de`](https://github.com/pahen/madge/commit/61429ded932c32426b20c86552ff5096d715d726)
451 | - Update releasenotes. [`bf0e987`](https://github.com/pahen/madge/commit/bf0e987b7da6d2d84e142a3cbb5388e7a2e0659c)
452 | - Bump to version 0.5.4 [`38c40ac`](https://github.com/pahen/madge/commit/38c40ac17d653103187bcc1c5e2512aa3c5b0c4c)
453 |
454 | #### [0.5.3](https://github.com/pahen/madge/compare/0.5.2...0.5.3)
455 |
456 | > 25 November 2015
457 |
458 | - Correct regex on CommonJS parser to detect a core module [`#71`](https://github.com/pahen/madge/pull/71)
459 | - Update README.md [`#70`](https://github.com/pahen/madge/pull/70)
460 | - List "es6" format in CLI help [`#66`](https://github.com/pahen/madge/pull/66)
461 | - Update releasenotes. [`153235d`](https://github.com/pahen/madge/commit/153235dba6df0b40b4305c1331fa7856b9a9bd25)
462 | - Bump to version 0.5.3 [`c00dd70`](https://github.com/pahen/madge/commit/c00dd70f684cb71c651a7e57eeeb89728cb9434c)
463 | - Correct regex on CJS parser to detect a core module [`67a449a`](https://github.com/pahen/madge/commit/67a449a276d250950dd839ffb37bb2b9bb7a82bd)
464 |
465 | #### [0.5.2](https://github.com/pahen/madge/compare/0.5.1...0.5.2)
466 |
467 | > 16 October 2015
468 |
469 | - Update resolve to latest version. [`21f787c`](https://github.com/pahen/madge/commit/21f787ced85df3002508a1de4fc3b4fe23fbfc06)
470 | - Bump to version 0.5.2 [`95faed0`](https://github.com/pahen/madge/commit/95faed027d21af80e58697588f061787b2f8592f)
471 |
472 | #### [0.5.1](https://github.com/pahen/madge/compare/0.5.0...0.5.1)
473 |
474 | > 15 October 2015
475 |
476 | - Update modules without any change to the code [`#65`](https://github.com/pahen/madge/pull/65)
477 | - Update all dependencies that will not break tests [`65dcaf3`](https://github.com/pahen/madge/commit/65dcaf318defa3140222c9774b35d51373bd5aa1)
478 | - Update shrinkwrap [`08ad266`](https://github.com/pahen/madge/commit/08ad266ea7e922762798b5a5bacc103c7d6b9762)
479 | - Update releasenotes. [`774f8d8`](https://github.com/pahen/madge/commit/774f8d855bf77da0211df97c97b97dc2d44cab69)
480 |
481 | #### [0.5.0](https://github.com/pahen/madge/compare/0.4.1...0.5.0)
482 |
483 | > 2 April 2015
484 |
485 | - Add 'comma separated' to -p usage for clarity [`#49`](https://github.com/pahen/madge/pull/49)
486 | - Add ES6 module support [`d5b0b60`](https://github.com/pahen/madge/commit/d5b0b60cff78aa473e60cf81322a9cfc716643a4)
487 | - Use npm-shrinkwrap instead of “npm shrinkwrap” to get consistent “resolved” fields. [`082abcb`](https://github.com/pahen/madge/commit/082abcb83a34aec2db0edeb728b85cf2e0be2e0b)
488 | - Cleanup after PL. [`a0d18ff`](https://github.com/pahen/madge/commit/a0d18ffdf8530ca10ebc6ab8e4e86f160b4b524c)
489 |
490 | #### [0.4.1](https://github.com/pahen/madge/compare/0.4.0...0.4.1)
491 |
492 | > 19 December 2014
493 |
494 | - Move method to proper file. [`f17a64a`](https://github.com/pahen/madge/commit/f17a64ae0f1c1e506a99fe739b2bc4a6ef2013cc)
495 | - Fix bug with absolute paths for module IDs in Windows. [`f64697a`](https://github.com/pahen/madge/commit/f64697a25718ca16e5af5aa21594cfd98bc23701)
496 | - Fix issues with absolute paths for modules IDs in Windows (all tests should now pass on Windows too). [`9d524e4`](https://github.com/pahen/madge/commit/9d524e4cc044533029ca6a67e1fba1fac1532984)
497 |
498 | #### [0.4.0](https://github.com/pahen/madge/compare/0.3.5...0.4.0)
499 |
500 | > 19 December 2014
501 |
502 | - Update NPM shrinkwrap file. [`a23178c`](https://github.com/pahen/madge/commit/a23178c990c87556e2f126a548005ce2a8f35b37)
503 | - Resolve the module IDs from the RequireJS paths-config properly. [`41b54e2`](https://github.com/pahen/madge/commit/41b54e2ecde61972ea1f2dfd1a75904ec1029d5a)
504 | - Add support for JSX (React) and additional module paths. [`858cd72`](https://github.com/pahen/madge/commit/858cd7290d87477ada8b588d85c93f32a6b932e9)
505 |
506 | #### [0.3.5](https://github.com/pahen/madge/compare/0.3.3...0.3.5)
507 |
508 | > 22 September 2014
509 |
510 | - IMPROVED: correctly detect circular dependencies when using path aliases (amd) [`324b12b`](https://github.com/pahen/madge/commit/324b12b7a04c7f3bfd431b057e1487e6cf0e6442)
511 | - Clear generated graph nodes on each render of a graph. [`4bd4f00`](https://github.com/pahen/madge/commit/4bd4f0035ea2989e7c59249d0ef54be762fbaad5)
512 | - Update releasenotes. [`d3df3ab`](https://github.com/pahen/madge/commit/d3df3ab7e22234a21a229296811c8171ee3beefb)
513 |
514 | #### [0.3.3](https://github.com/pahen/madge/compare/0.3.2...0.3.3)
515 |
516 | > 11 July 2014
517 |
518 | - Use path.resolve() to resolve relative paths in AMD dependencies instead of substack’s resolve lib since it works as expected (fixes #33). [`#33`](https://github.com/pahen/madge/issues/33)
519 | - Use amdetective infavor of parse.js for parsing AMD dependencies. [`28e462d`](https://github.com/pahen/madge/commit/28e462d6a016f5b44aa1324392532073d8442005)
520 | - Bump to version 0.3.3 [`dc35cf4`](https://github.com/pahen/madge/commit/dc35cf45fa7bacc23f31c5cd0cc5888806624ddc)
521 |
522 | #### [0.3.2](https://github.com/pahen/madge/compare/0.3.1...0.3.2)
523 |
524 | > 25 June 2014
525 |
526 | - convert spaces to tabs [`#30`](https://github.com/pahen/madge/pull/30)
527 | - Code cleanup. [`a235a00`](https://github.com/pahen/madge/commit/a235a0074ba9fb3ca7e6a02aedc6d96c40c8ad5b)
528 | - Added failing test cases. [`d095e6a`](https://github.com/pahen/madge/commit/d095e6af71093c16ed0ba704a11f8eed05f76912)
529 | - Added code to pass the tests. [`b2276f5`](https://github.com/pahen/madge/commit/b2276f5fc8a121d87d9d38a94b5a894e5103d7dc)
530 |
531 | #### [0.3.1](https://github.com/pahen/madge/compare/0.3.0...0.3.1)
532 |
533 | > 3 June 2014
534 |
535 | - Apply exclude to RequireJS shim dependencies [`02f3d28`](https://github.com/pahen/madge/commit/02f3d28f26af12d0b0ade3c56df42451161b3882)
536 | - Update releasenotes. [`8dd4fb2`](https://github.com/pahen/madge/commit/8dd4fb2168c357e9ccdfabe68c5d62b13e654c26)
537 | - Bump to version 0.3.1 [`c41987b`](https://github.com/pahen/madge/commit/c41987b0ad5c952af9554552bcbbddb87139b2e6)
538 |
539 | #### [0.3.0](https://github.com/pahen/madge/compare/0.2.0...0.3.0)
540 |
541 | > 26 May 2014
542 |
543 | - Lock down dependencies. [`9dcc9b7`](https://github.com/pahen/madge/commit/9dcc9b71348b733aad12e5373f9955158d085509)
544 | - make pluggbable by adding onParseFile and onAddModule options that hook into these events with returned madge as the bound context [`ee04d41`](https://github.com/pahen/madge/commit/ee04d418a0cfc61cffbf210edd60129ca609be98)
545 | - Add some documentation about onParseFile and onAddModule options. [`dfa9c2b`](https://github.com/pahen/madge/commit/dfa9c2b4491bd34e6444b0f602bc329eb6c80639)
546 |
547 | #### 0.2.0
548 |
549 | > 18 April 2014
550 |
551 | - Update README.md [`#13`](https://github.com/pahen/madge/pull/13)
552 | - Using require() to get version number instead of version.js [`#11`](https://github.com/pahen/madge/pull/11)
553 | - Complete path in circular dependencies is now printed (and marked as red in image graphs). Fixes #4 [`#4`](https://github.com/pahen/madge/issues/4)
554 | - Fixed Node.js v0.8 issues. Closes #2 [`#2`](https://github.com/pahen/madge/issues/2)
555 | - first commit [`c730a52`](https://github.com/pahen/madge/commit/c730a52ac6eb49c7a5813146c42ef9861c364aee)
556 | - Added support for CoffeeScript. Files with extension .coffee will automatically be compiled on-the-fly. [`67fa4ec`](https://github.com/pahen/madge/commit/67fa4ec53d2ae2a85ed0c83ea656dd20364f7f03)
557 | - Some code cleanup. [`d976942`](https://github.com/pahen/madge/commit/d9769421ad0aa8e5a3109158f33ce80ea926881b)
558 |
--------------------------------------------------------------------------------