dependency-tree 11.4.3 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,18 +10,20 @@
10
10
  npm install dependency-tree
11
11
  ```
12
12
 
13
- * Works for JS (AMD, CommonJS, ES6 modules), TypeScript, and CSS preprocessors (CSS (PostCSS), Sass, Stylus, and Less); basically, any module type supported by [precinct](https://github.com/dependents/node-precinct).
14
- - For CommonJS modules, 3rd party dependencies (npm installed dependencies) are included in the tree by default
15
- - Dependency path resolutions are handled by [filing-cabinet](https://github.com/dependents/node-filing-cabinet)
16
- - Supports RequireJS and Webpack loaders
17
- * All core Node modules (assert, path, fs, etc) are removed from the dependency list by default
13
+ * Supports JS (AMD, CommonJS, ES6), TypeScript, and CSS preprocessors (PostCSS, Sass, Stylus, Less) - any type handled by [precinct](https://github.com/dependents/node-precinct)
14
+ - CommonJS: third-party (npm) dependencies are included by default
15
+ - Path resolution is handled by [filing-cabinet](https://github.com/dependents/node-filing-cabinet); RequireJS and webpack loaders are supported
16
+ * Core Node built-ins (assert, path, fs, etc.) are excluded by default
18
17
 
19
18
  ## Usage
20
19
 
21
20
  ```js
22
- const dependencyTree = require('dependency-tree');
21
+ // ESM
22
+ import dependencyTree from 'dependency-tree';
23
+ // CommonJS
24
+ const { default: dependencyTree } = require('dependency-tree');
23
25
 
24
- // Returns a dependency tree object for the given file
26
+ // Returns a nested dependency tree object for the given file
25
27
  const tree = dependencyTree({
26
28
  filename: 'path/to/a/file',
27
29
  directory: 'path/to/all/files',
@@ -31,14 +33,13 @@ const tree = dependencyTree({
31
33
  nodeModulesConfig: {
32
34
  entry: 'module'
33
35
  }, // optional
34
- filter: path => path.indexOf('node_modules') === -1, // optional
36
+ filter: (dependencyPath, parentPath) => !dependencyPath.includes('node_modules'), // optional
35
37
  nonExistent: [], // optional
36
38
  noTypeDefinitions: false // optional
37
39
  });
38
40
 
39
- // Returns a post-order traversal (list form) of the tree with duplicate sub-trees pruned.
40
- // This is useful for bundling source files, because the list gives the concatenation order.
41
- // Note: you can pass the same arguments as you would to dependencyTree()
41
+ // Returns a post-order flat list of absolute paths (dependencies before dependents).
42
+ // Useful as a concatenation order for bundling.
42
43
  const list = dependencyTree.toList({
43
44
  filename: 'path/to/a/file',
44
45
  directory: 'path/to/all/files'
@@ -47,78 +48,72 @@ const list = dependencyTree.toList({
47
48
 
48
49
  ### Options
49
50
 
50
- * `requireConfig`: path to a requirejs config for AMD modules (allows for the result of aliased module paths)
51
- * `webpackConfig`: path to a webpack config for aliased modules
52
- * `tsConfig`: path to a typescript config (or a preloaded object representing the typescript config)
53
- * `tsConfigPath`: a (virtual) path to typescript config file when `tsConfig` option is given as an object, not a string. Needed to calculate [Path Mapping](https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping). If not given when `tsConfig` is an object, **Path Mapping** is ignored. This is not needed when `tsConfig` is given as a path string.
54
- * `nodeModulesConfig`: config for resolving entry file for node_modules
55
- * `visited`: object used for avoiding redundant subtree generations via memoization.
56
- * `nonExistent`: array used for storing the list of partial paths that do not exist
57
- * `filter`: a function used to determine if a module (and its subtree) should be included in the dependency tree
58
- - The first argument given to the filter is an absolute filepath to the dependency and the second is the filepath to the currently traversed file. Should return a `Boolean`. If it returns `true`, the module is included in the resulting tree.
59
- * `detective`: object with configuration specific to detectives used to find dependencies of a file
60
- - for example `detective.amd.skipLazyLoaded: true` tells the AMD detective to omit inner requires
61
- - See [precinct's usage docs](https://github.com/dependents/node-precinct#usage) for the list of module types you can pass options to.
62
- * `noTypeDefinitions`: For TypeScript imports, whether to resolve to `*.js` instead of `*.d.ts`.
63
-
64
- ### Format Details
65
-
66
- The object form is a mapping of the dependency tree to the filesystem –
67
- where every key is an absolute filepath and the value is another object/subtree.
68
-
69
- Example:
51
+ | Option | Type | Default | Description |
52
+ |---|---|---|---|
53
+ | `filename` | `string` | - | **Required.** Absolute path to the entry file |
54
+ | `directory` | `string` | - | **Required.** Root directory used to resolve relative paths |
55
+ | `root` | `string` | `undefined` | Alias for `directory` |
56
+ | `requireConfig` | `string` | `undefined` | Path to a RequireJS config for AMD modules (resolves aliased paths) |
57
+ | `config` | `string` | `undefined` | Alias for `requireConfig` |
58
+ | `webpackConfig` | `string` | `undefined` | Path to a webpack config for aliased modules |
59
+ | `tsConfig` | `string \| object` | `undefined` | Path to a TypeScript config file, or a preloaded config object |
60
+ | `tsConfigPath` | `string` | `undefined` | Virtual path for the TypeScript config when `tsConfig` is an object. Required for [Path Mapping](https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping); ignored when `tsConfig` is a string path |
61
+ | `nodeModulesConfig` | `object` | `undefined` | Config for resolving `node_modules` entry files (e.g. `{ entry: 'module' }`) |
62
+ | `visited` | `object` | `{}` | Memoization cache (filename to subtree) to skip already-processed files |
63
+ | `nonExistent` | `string[]` | `[]` | Array populated with partial paths that could not be resolved |
64
+ | `isListForm` | `boolean` | `false` | Return a flat post-order list of paths instead of a nested tree (same as calling `dependencyTree.toList()`) |
65
+ | `filter` | `(dependencyPath: string, parentPath: string) => boolean` | `undefined` | Return `true` to include a dependency (and its subtree) in the tree |
66
+ | `detectiveConfig` | `object` | `{}` | Options passed to [precinct](https://github.com/dependents/node-precinct#usage) for dependency extraction - e.g. `{ amd: { skipLazyLoaded: true } }`, `{ ts: { skipTypeImports: true } }` |
67
+ | `detective` | `object` | `{}` | Alias for `detectiveConfig` |
68
+ | `noTypeDefinitions` | `boolean` | `false` | Resolve TypeScript imports to `*.js` instead of `*.d.ts` |
69
+
70
+ ### Output format
71
+
72
+ The default output is a nested object where every key is an absolute file path and the value is its own subtree:
70
73
 
71
74
  ```js
72
75
  {
73
- '/Users/mrjoelkemp/Documents/node-dependency-tree/test/example/extended/a.js': {
74
- '/Users/mrjoelkemp/Documents/node-dependency-tree/test/example/extended/b.js': {
75
- '/Users/mrjoelkemp/Documents/node-dependency-tree/test/example/extended/d.js': {},
76
- '/Users/mrjoelkemp/Documents/node-dependency-tree/test/example/extended/e.js': {}
76
+ '/path/to/a.js': {
77
+ '/path/to/b.js': {
78
+ '/path/to/d.js': {},
79
+ '/path/to/e.js': {}
77
80
  },
78
- '/Users/mrjoelkemp/Documents/node-dependency-tree/test/example/extended/c.js': {
79
- '/Users/mrjoelkemp/Documents/node-dependency-tree/test/example/extended/f.js': {},
80
- '/Users/mrjoelkemp/Documents/node-dependency-tree/test/example/extended/g.js': {}
81
+ '/path/to/c.js': {
82
+ '/path/to/f.js': {},
83
+ '/path/to/g.js': {}
81
84
  }
82
85
  }
83
86
  }
84
87
  ```
85
88
 
86
- This structure was chosen to serve as a visual representation of the dependency tree
87
- for use in the [Dependents](https://github.com/mrjoelkemp/sublime-dependents) plugin.
89
+ This format was designed for visual representation in the [Dependents](https://github.com/mrjoelkemp/sublime-dependents) plugin.
88
90
 
89
- ### CLI version
91
+ ### CLI
90
92
 
91
- * Assumes a global install: `npm install -g dependency-tree`
93
+ Requires a global install: `npm install -g dependency-tree`
92
94
 
93
95
  ```
94
- dependency-tree --directory=path/to/all/supported/files [--list-form] [-c path/to/require/config] [-w path/to/webpack/config] filename
96
+ dependency-tree --directory=path/to/files [--list-form] [--es6-mixed-imports] [-c path/to/require/config] [-w path/to/webpack/config] filename
95
97
  ```
96
98
 
97
- Prints the dependency tree of the given filename as stringified json (by default).
98
-
99
- * You can alternatively print out the list form one element per line using the `--list-form` option.
100
-
101
- ## How does this work?
99
+ Prints the dependency tree as JSON. Use `--list-form` to print one path per line instead.
102
100
 
103
- Dependency tree takes in a starting file, extracts its declared dependencies via [precinct](https://github.com/dependents/node-precinct/), resolves each of those dependencies to a file on the filesystem via [filing-cabinet](https://github.com/dependents/node-filing-cabinet), then recursively performs those steps until there are no more dependencies to process.
101
+ ## How it works
104
102
 
105
- In more detail, the starting file is passed to precinct to extract dependencies. Dependency-tree doesn't care about how to extract dependencies, so it delegates that work to precinct: which is a multi-language dependency extractor; we'll focus on JavaScript tree generation for this example. To do the extraction, precinct delegates the abstract-syntax-tree (AST) generation to the default parser for [node-source-walk](https://github.com/dependents/node-source-walk). Precinct uses the AST to determine what type of JS module the file is (Commonjs, AMD, or ES6) and then delegates to the "detective" that's appropriate for that module type. The "detective" contains the logic for how to extract dependencies based on the module syntax format; i.e., the way dependencies are declared in commonjs is different than in AMD (which has 4 ways of doing that, for example).
103
+ dependency-tree passes the entry file to [precinct](https://github.com/dependents/node-precinct/) to extract raw dependency strings, then passes each to [filing-cabinet](https://github.com/dependents/node-filing-cabinet) to resolve them to real filesystem paths, and recurses until the full tree is built.
106
104
 
107
- After using the detective to get the (raw, like './foobar') dependency strings, precinct passes that back to dependency-tree. Of course, in order to find the dependencies in './foobar', we need to resolve that dependency to a real file on the filesystem. To do this, dependency-tree delegates that task to filing-cabinet: which is a multi-language dependency resolver.
105
+ Precinct generates an AST via [node-source-walk](https://github.com/dependents/node-source-walk), detects the module format (CommonJS, AMD, or ES6), and delegates to the appropriate detective to extract dependency declarations.
108
106
 
109
- Filing-cabinet reuses (for performance) the AST that precinct made node-source-walk generate. It then does a similar check on the AST to see which module type (commonjs, amd, or es6) is being used in the file (again, we're assuming a regular JS file for this example) and then delegates to the appropriate resolver for that module type. We need different resolvers because a dependency name in AMD could be aliased via a requirejs config. Similarly, commonjs has its own algorithm for resolving dependencies.
110
-
111
- So after the appropriate resolver finds the file on the filesystem, filing-cabinet has successfully mapped a raw dependency name to a file on the filesystem. Now, dependency-tree has a file that it can also traverse (repeating exactly what was done for the starting file).
112
-
113
- At the end of traversing every file (in a depth-first fashion), we have a fully populated dependency tree. :dancers:
107
+ Filing-cabinet reuses that AST to detect the module format again, then delegates to the right resolver - necessary because AMD supports path aliasing via a RequireJS config, while CommonJS has its own resolution algorithm. The resolver returns an absolute path, which dependency-tree then recurses into.
114
108
 
115
109
  ## FAQ
116
110
 
117
- ### Why aren't some some dependencies being detected?
111
+ ### Why aren't some dependencies being detected?
112
+
113
+ Bugs in [precinct](https://github.com/dependents/node-precinct) or an incomplete `requireConfig`/`webpackConfig`/`tsConfig` are the most common causes. Any path that could not be resolved is appended to the array passed as `nonExistent`.
118
114
 
119
- If there are bugs in [precinct](https://github.com/dependents/node-precinct) or if the `requireConfig`/`webpackConfig`/`tsConfig` options are incomplete,
120
- some dependencies may not be resolved. The optional array passed to the `nonExistent` option will be populated with paths
121
- that could not be resolved. You can check this array to see where problems might exist.
115
+ For detailed resolution logs, set `NODE_DEBUG=*` when using the CLI:
122
116
 
123
- You can also use the `NODE_DEBUG=*` env variable along with the cli version to see debugging information explaining where resolution went wrong.
124
- Example: `NODE_DEBUG=* dependency-tree -w path/to/webpack.config.json path/to/a/file`
117
+ ```sh
118
+ NODE_DEBUG=* dependency-tree -w path/to/webpack.config.json path/to/a/file
119
+ ```
package/bin/cli.js CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- 'use strict';
3
+ import process from 'node:process';
4
+ import { stringifyChunked } from '@discoveryjs/json-ext';
5
+ import { program } from 'commander';
6
+ import dependencyTree from '../index.js';
7
+ import pkg from '../package.json' with { type: 'json' };
4
8
 
5
- const { program } = require('commander');
6
- const dependencyTree = require('../index.js');
7
- const { name, description, version } = require('../package.json');
9
+ const { name, description, version } = pkg;
8
10
 
9
11
  program
10
12
  .name(name)
@@ -16,6 +18,7 @@ program
16
18
  .option('-c, --require-config <path>', 'path to a requirejs config')
17
19
  .option('-w, --webpack-config <path>', 'path to a webpack config')
18
20
  .option('-t, --ts-config <path>', 'path to a typescript config')
21
+ .option('--es6-mixed-imports', 'detect dependencies from files that mix ES6 imports and CJS require() calls')
19
22
  .option('--list-form', 'output the list form of the tree (one element per line)')
20
23
  .showHelpAfterError()
21
24
  .parse();
@@ -26,7 +29,12 @@ const options = {
26
29
  root: cliOptions.directory,
27
30
  config: cliOptions.requireConfig,
28
31
  webpackConfig: cliOptions.webpackConfig,
29
- tsConfig: cliOptions.tsConfig
32
+ tsConfig: cliOptions.tsConfig,
33
+ detective: {
34
+ es6: {
35
+ mixedImports: Boolean(cliOptions.es6MixedImports)
36
+ }
37
+ }
30
38
  };
31
39
 
32
40
  if (cliOptions.listForm) {
@@ -38,5 +46,9 @@ if (cliOptions.listForm) {
38
46
  } else {
39
47
  const tree = dependencyTree(options);
40
48
 
41
- console.log(JSON.stringify(tree));
49
+ for (const chunk of stringifyChunked(tree)) {
50
+ process.stdout.write(chunk);
51
+ }
52
+
53
+ process.stdout.write('\n');
42
54
  }
package/index.js CHANGED
@@ -1,33 +1,19 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const { debuglog } = require('node:util');
6
- const cabinet = require('filing-cabinet');
7
- const precinct = require('precinct');
8
- const Config = require('./lib/config.js');
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { debuglog } from 'node:util';
4
+ import cabinet from 'filing-cabinet';
5
+ import precinct from 'precinct';
6
+ import Config from './lib/config.js';
9
7
 
10
8
  const debug = debuglog('tree');
11
9
 
12
10
  /**
13
- * Recursively find all dependencies (avoiding circular) traversing the entire dependency tree
14
- * and returns a flat list of all unique, visited nodes
11
+ * Returns the dependency tree of a module as a nested object
15
12
  *
16
- * @param {Object} options
17
- * @param {String} options.filename - The path of the module whose tree to traverse
18
- * @param {String} options.directory - The directory containing all JS files
19
- * @param {String} [options.requireConfig] - The path to a requirejs config
20
- * @param {String} [options.webpackConfig] - The path to a webpack config
21
- * @param {String} [options.nodeModulesConfig] - config for resolving entry file for node_modules
22
- * @param {Object} [options.visited] - Cache of visited, absolutely pathed files that should not be reprocessed.
23
- * Format is a filename -> tree as list lookup table
24
- * @param {Array} [options.nonExistent] - List of partials that do not exist
25
- * @param {Boolean} [options.isListForm=false]
26
- * @param {String|Object} [options.tsConfig] Path to a typescript config (or a preloaded one).
27
- * @param {Boolean} [options.noTypeDefinitions] For TypeScript imports, whether to resolve to `*.js` instead of `*.d.ts`.
28
- * @return {Object}
13
+ * @param {import('./lib/config.js').ConfigOptions} [options]
14
+ * @returns {object}
29
15
  */
30
- module.exports = function(options = {}) {
16
+ function dependencyTree(options = {}) {
31
17
  const config = new Config(options);
32
18
 
33
19
  if (!fs.existsSync(config.filename)) {
@@ -47,41 +33,34 @@ module.exports = function(options = {}) {
47
33
  tree = [...results];
48
34
  } else {
49
35
  debug('object form of results requested');
50
- tree = {};
51
- tree[config.filename] = results;
36
+ tree = {
37
+ [config.filename]: results
38
+ };
52
39
  }
53
40
 
54
41
  debug('final tree', tree);
55
42
  return tree;
56
- };
43
+ }
57
44
 
58
45
  /**
59
- * Executes a post-order depth first search on the dependency tree and returns a
60
- * list of absolute file paths. The order of files in the list will be the
61
- * proper concatenation order for bundling.
46
+ * Returns a post-order flat list of absolute file paths (dependencies before dependents).
47
+ * Every file's dependencies appear at lower indices, so the root entry point is last.
48
+ * The list contains no duplicates. Accepts the same options as the default export.
62
49
  *
63
- * In other words, for any file in the list, all of that file's dependencies (direct or indirect) will appear at
64
- * lower indices in the list. The root (entry point) file will therefore appear last.
65
- *
66
- * The list will not contain duplicates.
67
- *
68
- * Params are those of module.exports
50
+ * @param {Parameters<typeof dependencyTree>[0]} options - Same as the default export
51
+ * @returns {string[]}
69
52
  */
70
- module.exports.toList = function(options = {}) {
71
- options.isListForm = true;
72
-
73
- return module.exports(options);
53
+ dependencyTree.toList = function(options = {}) {
54
+ return dependencyTree({ ...options, isListForm: true });
74
55
  };
75
56
 
76
57
  /**
77
- * Returns the list of dependencies for the given filename
58
+ * Returns resolved dependency paths for the file described by `config`.
78
59
  *
79
- * Protected for testing
80
- *
81
- * @param {Config} config
82
- * @return {Array}
60
+ * @param {Config} config
61
+ * @returns {string[]}
83
62
  */
84
- module.exports._getDependencies = function(config = {}) {
63
+ function getDependencies(config) {
85
64
  const precinctOptions = config.detectiveConfig;
86
65
  precinctOptions.includeCore = false;
87
66
  let dependencies;
@@ -89,7 +68,7 @@ module.exports._getDependencies = function(config = {}) {
89
68
  try {
90
69
  dependencies = precinct.paperwork(config.filename, precinctOptions);
91
70
  debug(`extracted ${dependencies.length} dependencies: `, dependencies);
92
- } catch (error) {
71
+ } catch(error) {
93
72
  debug(`error getting dependencies: ${error.message}`);
94
73
  debug(error.stack);
95
74
  return [];
@@ -129,13 +108,13 @@ module.exports._getDependencies = function(config = {}) {
129
108
  }
130
109
 
131
110
  return resolvedDependencies;
132
- };
111
+ }
133
112
 
134
113
  /**
135
- * @param {Config} config
136
- * @return {Object|Set}
114
+ * @param {Config} config
115
+ * @returns {Object|Set<string>}
137
116
  */
138
- function traverse(config = {}) {
117
+ function traverse(config) {
139
118
  const subTree = config.isListForm ? new Set() : {};
140
119
 
141
120
  debug(`traversing ${config.filename}`);
@@ -145,11 +124,10 @@ function traverse(config = {}) {
145
124
  return config.visited[config.filename];
146
125
  }
147
126
 
148
- let dependencies = module.exports._getDependencies(config);
127
+ let dependencies = getDependencies(config);
149
128
 
150
129
  debug('cabinet-resolved all dependencies: ', dependencies);
151
- // Prevents cycles by eagerly marking the current file as read
152
- // so that any dependent dependencies exit
130
+ // Eagerly mark the current file before recursing so any re-entrant visit exits early
153
131
  config.visited[config.filename] = config.isListForm ? [] : {};
154
132
 
155
133
  if (config.filter) {
@@ -163,7 +141,7 @@ function traverse(config = {}) {
163
141
  for (const dependency of dependencies) {
164
142
  const localConfig = config.clone();
165
143
  localConfig.filename = dependency;
166
- localConfig.directory = getDirectory(localConfig);
144
+ localConfig.directory = getLocalConfigDirectory(localConfig);
167
145
 
168
146
  if (localConfig.isListForm) {
169
147
  for (const item of traverse(localConfig)) {
@@ -184,7 +162,10 @@ function traverse(config = {}) {
184
162
  return subTree;
185
163
  }
186
164
 
187
- // Mutate the list input to do a dereferenced modification of the user-supplied list
165
+ // Dedupe in-place so the caller's array reference stays valid
166
+ /**
167
+ * @param {string[]} nonExistent
168
+ */
188
169
  function dedupeNonExistent(nonExistent) {
189
170
  const deduped = new Set(nonExistent);
190
171
  nonExistent.length = deduped.size;
@@ -196,24 +177,41 @@ function dedupeNonExistent(nonExistent) {
196
177
  }
197
178
  }
198
179
 
199
- // If the file is in a node module, use the root directory of the module
200
- function getDirectory(localConfig) {
201
- if (!localConfig.filename.includes('node_modules')) {
202
- return localConfig.directory;
180
+ // If the file is in a node_modules directory, we want to resolve the root of the package,
181
+ // not the file itself, since the file may be buried in a subdirectory and not contain all
182
+ // of the package's dependencies
183
+ /**
184
+ * @param {Config} localConfig
185
+ * @returns {string}
186
+ */
187
+ function getLocalConfigDirectory(localConfig) {
188
+ const { filename, directory } = localConfig;
189
+
190
+ if (!filename.includes('node_modules')) {
191
+ return directory;
203
192
  }
204
193
 
205
- return getProjectPath(path.dirname(localConfig.filename)) || localConfig.directory;
206
- }
194
+ const dir = path.dirname(filename);
195
+ const parts = dir.split('node_modules');
207
196
 
208
- function getProjectPath(filename) {
209
- try {
210
- const nodeModuleParts = filename.split('node_modules');
211
- const packageSubPathPath = nodeModuleParts.pop().split(path.sep).filter(Boolean);
212
- const packageName = packageSubPathPath[0].startsWith('@') ? `${packageSubPathPath[0]}${path.sep}${packageSubPathPath[1]}` : packageSubPathPath[0];
213
-
214
- return path.normalize([...nodeModuleParts, `${path.sep}${packageName}`].join('node_modules'));
215
- } catch {
216
- debug(`Could not determine the root directory of package file ${filename}. Using default`);
217
- return null;
197
+ if (parts.length < 2) {
198
+ return directory;
199
+ }
200
+
201
+ const afterNodeModules = parts.pop();
202
+ if (!afterNodeModules) {
203
+ return directory;
218
204
  }
205
+
206
+ const segments = afterNodeModules.split(path.sep).filter(Boolean);
207
+ if (segments.length === 0) {
208
+ return directory;
209
+ }
210
+
211
+ const packageName = segments[0].startsWith('@') ? `${segments[0]}${path.sep}${segments[1]}` : segments[0];
212
+ const projectPath = path.normalize(`${parts.join('node_modules')}node_modules${path.sep}${packageName}`);
213
+
214
+ return projectPath;
219
215
  }
216
+
217
+ export default dependencyTree;
package/lib/config.js CHANGED
@@ -1,22 +1,46 @@
1
- 'use strict';
1
+ import path from 'node:path';
2
+ import process from 'node:process';
3
+ import { debuglog } from 'node:util';
4
+ import { createRequire } from 'node:module';
2
5
 
3
- const path = require('node:path');
4
- const process = require('node:process');
5
- const { debuglog } = require('node:util');
6
+ const require = createRequire(import.meta.url);
6
7
 
7
8
  const debug = debuglog('tree');
8
9
 
9
- module.exports = class Config {
10
+ /**
11
+ * @typedef {object} ConfigOptions
12
+ * @property {string} [filename] - Entry module path
13
+ * @property {string} [directory] - Root directory containing all files
14
+ * @property {string} [root] - Alias for `directory`
15
+ * @property {string} [requireConfig] - Path to a RequireJS config for AMD modules
16
+ * @property {string} [config] - Alias for `requireConfig`
17
+ * @property {string} [webpackConfig] - Path to a webpack config for aliased modules
18
+ * @property {Record<string, unknown>} [nodeModulesConfig] - Config for resolving node_modules entry files
19
+ * @property {Record<string, object>} [visited] - Memoization cache: filename to subtree
20
+ * @property {string[]} [nonExistent] - Accumulator for unresolvable partials
21
+ * @property {boolean} [isListForm] - Return a flat list instead of a tree
22
+ * @property {string | Record<string, unknown>} [tsConfig] - Path to (or preloaded) TypeScript config
23
+ * @property {string} [tsConfigPath] - Virtual path for the tsConfig object; required for Path Mapping
24
+ * @property {boolean} [noTypeDefinitions] - Resolve TS imports to `*.js` instead of `*.d.ts`
25
+ * @property {Record<string, unknown>} [detectiveConfig] - Options passed to precinct for dependency extraction
26
+ * @property {Record<string, unknown>} [detective] - Alias for `detectiveConfig`
27
+ * @property {(dependencyPath: string, parentPath: string) => boolean} [filter] - Return `true` to include a dependency
28
+ */
29
+
30
+ export default class Config {
31
+ /**
32
+ * @param {ConfigOptions} [options]
33
+ */
10
34
  constructor(options = {}) {
11
35
  this.filename = options.filename;
12
36
  this.directory = options.directory || options.root;
13
- this.visited = options.visited || {};
14
- this.nonExistent = options.nonExistent || [];
15
- this.isListForm = options.isListForm;
16
- this.requireConfig = options.config || options.requireConfig;
37
+ this.visited = options.visited ?? {};
38
+ this.nonExistent = options.nonExistent ?? [];
39
+ this.isListForm = options.isListForm ?? false;
40
+ this.requireConfig = options.config ?? options.requireConfig;
17
41
  this.webpackConfig = options.webpackConfig;
18
42
  this.nodeModulesConfig = options.nodeModulesConfig;
19
- this.detectiveConfig = options.detective || options.detectiveConfig || {};
43
+ this.detectiveConfig = options.detective ?? options.detectiveConfig ?? {};
20
44
  this.tsConfig = options.tsConfig;
21
45
  this.tsConfigPath = options.tsConfigPath;
22
46
  this.noTypeDefinitions = options.noTypeDefinitions;
@@ -27,6 +51,7 @@ module.exports = class Config {
27
51
  if (this.filter && typeof this.filter !== 'function') throw new Error('filter must be a function');
28
52
 
29
53
  if (typeof this.tsConfig === 'string') {
54
+ // Pre-parse once so all recursive clones share the object form
30
55
  debug('preparsing the ts config into an object for performance');
31
56
  const ts = require('typescript');
32
57
  const tsParsedConfig = ts.readJsonConfigFile(this.tsConfig, ts.sys.readFile);
@@ -46,4 +71,4 @@ module.exports = class Config {
46
71
  clone() {
47
72
  return new Config(this);
48
73
  }
49
- };
74
+ }
package/package.json CHANGED
@@ -1,23 +1,30 @@
1
1
  {
2
2
  "name": "dependency-tree",
3
- "version": "11.4.3",
3
+ "version": "12.0.0",
4
4
  "description": "Get the dependency tree of a module",
5
- "main": "index.js",
6
- "types": "index.d.ts",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./types/index.d.ts",
9
+ "default": "./index.js"
10
+ }
11
+ },
7
12
  "files": [
8
13
  "bin/cli.js",
9
14
  "lib/*.js",
10
- "index.d.ts",
15
+ "types/",
11
16
  "index.js"
12
17
  ],
13
18
  "bin": {
14
19
  "dependency-tree": "bin/cli.js"
15
20
  },
16
21
  "scripts": {
22
+ "generate:types": "tsc --allowJs --declaration --emitDeclarationOnly --declarationDir types index.js",
23
+ "check:types": "npm run generate:types && git diff HEAD --exit-code types/index.d.ts",
17
24
  "lint": "xo",
18
25
  "fix": "xo --fix",
19
26
  "mocha": "mocha \"test/*.test.mjs\"",
20
- "test": "npm run lint && npm run mocha",
27
+ "test": "npm run lint && npm run check:types && npm run mocha",
21
28
  "test:ci": "c8 npm run mocha"
22
29
  },
23
30
  "repository": {
@@ -46,71 +53,22 @@
46
53
  },
47
54
  "homepage": "https://github.com/dependents/node-dependency-tree",
48
55
  "engines": {
49
- "node": ">=18"
56
+ "node": ">=20.19.0 || >=22.12.0"
50
57
  },
51
58
  "dependencies": {
52
- "commander": "^12.1.0",
53
- "filing-cabinet": "^5.3.0",
54
- "precinct": "^12.3.1",
55
- "typescript": "^5.9.3"
59
+ "@discoveryjs/json-ext": "^1.1.0",
60
+ "commander": "^14.0.3",
61
+ "filing-cabinet": "^6.0.0",
62
+ "precinct": "^13.0.0",
63
+ "typescript": "^6.0.3"
56
64
  },
57
65
  "devDependencies": {
58
- "c8": "^10.1.3",
66
+ "c8": "^11.0.0",
59
67
  "debug": "^4.4.3",
68
+ "globals": "^17.6.0",
60
69
  "mocha": "^11.7.5",
61
70
  "mock-fs": "^5.5.0",
62
- "resolve": "^1.22.12",
63
- "sinon": "^19.0.5",
64
- "xo": "^0.60.0"
65
- },
66
- "xo": {
67
- "space": true,
68
- "ignores": [
69
- "index.d.ts",
70
- "test/fixtures/*"
71
- ],
72
- "rules": {
73
- "arrow-body-style": "off",
74
- "camelcase": [
75
- "error",
76
- {
77
- "properties": "never"
78
- }
79
- ],
80
- "capitalized-comments": "off",
81
- "comma-dangle": [
82
- "error",
83
- "never"
84
- ],
85
- "curly": [
86
- "error",
87
- "multi-line"
88
- ],
89
- "operator-linebreak": [
90
- "error",
91
- "after"
92
- ],
93
- "object-curly-spacing": [
94
- "error",
95
- "always"
96
- ],
97
- "prefer-template": "error",
98
- "space-before-function-paren": [
99
- "error",
100
- "never"
101
- ],
102
- "unicorn/no-anonymous-default-export": "off",
103
- "unicorn/prefer-module": "off",
104
- "unicorn/prefer-top-level-await": "off",
105
- "unicorn/prevent-abbreviations": "off"
106
- },
107
- "overrides": [
108
- {
109
- "files": "test/**/*.{c,m}js",
110
- "envs": [
111
- "mocha"
112
- ]
113
- }
114
- ]
71
+ "sinon": "^22.0.0",
72
+ "xo": "^2.0.2"
115
73
  }
116
74
  }
@@ -0,0 +1,19 @@
1
+ export default dependencyTree;
2
+ /**
3
+ * Returns the dependency tree of a module as a nested object
4
+ *
5
+ * @param {import('./lib/config.js').ConfigOptions} [options]
6
+ * @returns {object}
7
+ */
8
+ declare function dependencyTree(options?: import("./lib/config.js").ConfigOptions): object;
9
+ declare namespace dependencyTree {
10
+ /**
11
+ * Returns a post-order flat list of absolute file paths (dependencies before dependents).
12
+ * Every file's dependencies appear at lower indices, so the root entry point is last.
13
+ * The list contains no duplicates. Accepts the same options as the default export.
14
+ *
15
+ * @param {Parameters<typeof dependencyTree>[0]} options - Same as the default export
16
+ * @returns {string[]}
17
+ */
18
+ function toList(options?: Parameters<typeof dependencyTree>[0]): string[];
19
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @typedef {object} ConfigOptions
3
+ * @property {string} [filename] - Entry module path
4
+ * @property {string} [directory] - Root directory containing all files
5
+ * @property {string} [root] - Alias for `directory`
6
+ * @property {string} [requireConfig] - Path to a RequireJS config for AMD modules
7
+ * @property {string} [config] - Alias for `requireConfig`
8
+ * @property {string} [webpackConfig] - Path to a webpack config for aliased modules
9
+ * @property {Record<string, unknown>} [nodeModulesConfig] - Config for resolving node_modules entry files
10
+ * @property {Record<string, object>} [visited] - Memoization cache: filename to subtree
11
+ * @property {string[]} [nonExistent] - Accumulator for unresolvable partials
12
+ * @property {boolean} [isListForm] - Return a flat list instead of a tree
13
+ * @property {string | Record<string, unknown>} [tsConfig] - Path to (or preloaded) TypeScript config
14
+ * @property {string} [tsConfigPath] - Virtual path for the tsConfig object; required for Path Mapping
15
+ * @property {boolean} [noTypeDefinitions] - Resolve TS imports to `*.js` instead of `*.d.ts`
16
+ * @property {Record<string, unknown>} [detectiveConfig] - Options passed to precinct for dependency extraction
17
+ * @property {Record<string, unknown>} [detective] - Alias for `detectiveConfig`
18
+ * @property {(dependencyPath: string, parentPath: string) => boolean} [filter] - Return `true` to include a dependency
19
+ */
20
+ export default class Config {
21
+ /**
22
+ * @param {ConfigOptions} [options]
23
+ */
24
+ constructor(options?: ConfigOptions);
25
+ filename: any;
26
+ directory: string;
27
+ visited: Record<string, object>;
28
+ nonExistent: string[];
29
+ isListForm: boolean;
30
+ requireConfig: string | undefined;
31
+ webpackConfig: string | undefined;
32
+ nodeModulesConfig: Record<string, unknown> | undefined;
33
+ detectiveConfig: Record<string, unknown>;
34
+ tsConfig: any;
35
+ tsConfigPath: string | undefined;
36
+ noTypeDefinitions: boolean | undefined;
37
+ filter: ((dependencyPath: string, parentPath: string) => boolean) | undefined;
38
+ clone(): Config;
39
+ }
40
+ export type ConfigOptions = {
41
+ /**
42
+ * - Entry module path
43
+ */
44
+ filename?: string | undefined;
45
+ /**
46
+ * - Root directory containing all files
47
+ */
48
+ directory?: string | undefined;
49
+ /**
50
+ * - Alias for `directory`
51
+ */
52
+ root?: string | undefined;
53
+ /**
54
+ * - Path to a RequireJS config for AMD modules
55
+ */
56
+ requireConfig?: string | undefined;
57
+ /**
58
+ * - Alias for `requireConfig`
59
+ */
60
+ config?: string | undefined;
61
+ /**
62
+ * - Path to a webpack config for aliased modules
63
+ */
64
+ webpackConfig?: string | undefined;
65
+ /**
66
+ * - Config for resolving node_modules entry files
67
+ */
68
+ nodeModulesConfig?: Record<string, unknown> | undefined;
69
+ /**
70
+ * - Memoization cache: filename to subtree
71
+ */
72
+ visited?: Record<string, object> | undefined;
73
+ /**
74
+ * - Accumulator for unresolvable partials
75
+ */
76
+ nonExistent?: string[] | undefined;
77
+ /**
78
+ * - Return a flat list instead of a tree
79
+ */
80
+ isListForm?: boolean | undefined;
81
+ /**
82
+ * - Path to (or preloaded) TypeScript config
83
+ */
84
+ tsConfig?: string | Record<string, unknown> | undefined;
85
+ /**
86
+ * - Virtual path for the tsConfig object; required for Path Mapping
87
+ */
88
+ tsConfigPath?: string | undefined;
89
+ /**
90
+ * - Resolve TS imports to `*.js` instead of `*.d.ts`
91
+ */
92
+ noTypeDefinitions?: boolean | undefined;
93
+ /**
94
+ * - Options passed to precinct for dependency extraction
95
+ */
96
+ detectiveConfig?: Record<string, unknown> | undefined;
97
+ /**
98
+ * - Alias for `detectiveConfig`
99
+ */
100
+ detective?: Record<string, unknown> | undefined;
101
+ /**
102
+ * - Return `true` to include a dependency
103
+ */
104
+ filter?: ((dependencyPath: string, parentPath: string) => boolean) | undefined;
105
+ };
package/index.d.ts DELETED
@@ -1,34 +0,0 @@
1
- declare namespace dependencyTree {
2
- interface TreeInnerNode {
3
- [parent: string]: TreeInnerNode | string;
4
- }
5
- type Tree = TreeInnerNode | string;
6
-
7
- export interface Options {
8
- filename: string;
9
- directory: string;
10
- visited?: Tree;
11
- nonExistent?: string[];
12
- isListForm?: boolean;
13
- requireConfig?: string;
14
- webpackConfig?: string;
15
- nodeModulesConfig?: any;
16
- detectiveConfig?: any;
17
- tsConfig?: string | Record<string, any>;
18
- noTypeDefinitions?: boolean;
19
- filter?: (path: string) => boolean;
20
- }
21
-
22
- interface Config extends Options {
23
- clone: () => Config;
24
- }
25
-
26
- function toList(options: Options): string[];
27
- function _getDependencies(config: Config): string[];
28
- }
29
-
30
- declare function dependencyTree(
31
- options: dependencyTree.Options
32
- ): dependencyTree.Tree;
33
-
34
- export = dependencyTree;