Node.js: Get the (absolute) root path of an installed npm package

Task

I am looking for a universal way to get the (absolute) root path of an installed npm package in Node.js.

Problem

I know about require.resolve , but this will give me the entry point (path to the main module), and not the root path of the package.

Take bootstrap-sass as an example. Say it is installed locally in the project folder C:\dev\my-project . Then I'm looking for C:\dev\my-project\node_modules\bootstrap-sass . require.resolve('bootstrap-sass') will return C:\dev\my-project\node_modules\bootstrap-sass\assets\javascripts\bootstrap.js .

I can think of several ways how to receive a package root path:

Solution No. 1

 var packageRoot = path.resolve('node_modules/bootstrap-sass'); console.log(packageRoot); 

This will work fine for packages installed locally in the node_modules folder. However, if I'm in a subfolder, I need to enable ../node_modules/bootstrap-sass , and this gets more complicated with more subfolders. In addition, this does not work for modules installed globally.

Decision No. 2

 var packageRoot = require.resolve('bootstrap-sass') .match(/^.*[\/\\]node_modules[\/\\][^\/\\]*/)[0]; console.log(packageRoot); 

This will work for local and global modules installed in the node_modules folder. The regular expression will match everything until the last element of the node_modules path plus the next path element. However, this will happen if the package entry point is set to another package (for example, "main": "./node_modules/sub-package" in package.json ).

Decision No. 3

 var escapeStringRegexp = require('escape-string-regexp'); /** * Get the root path of a npm package installed in node_modules. * @param {string} packageName The name of the package. * @returns {string} Root path of the package without trailing slash. * @throws Will throw an error if the package root path cannot be resolved */ function packageRootPath(packageName) { var mainModulePath = require.resolve(packageName); var escapedPackageName = escapeStringRegexp(packageName); var regexpStr = '^.*[\\/\\\\]node_modules[\\/\\\\]' + escapedPackageName + '(?=[\\/\\\\])'; var rootPath = mainModulePath.match(regexpStr); if (rootPath) { return rootPath[0]; } else { var msg = 'Could not resolve package root path for package `' + packageName + '`.' throw new Error(msg); } } var packageRoot = packageRootPath('bootstrap-sass'); console.log(packageRoot); 

This function should work for all packages installed in the node_modules folder.

But...

I wonder if this rather simple task can solve a simpler and less hacker approach. For me, this is similar to what should already be built into Node.js. Any suggestions?

+7
javascript npm path package
source share
2 answers

Try the following:

 require.resolve('bootstrap-sass/package.json') 

which returns:

 path_to_my_project/node_modules/bootstrap-sass/package.json 

Now you can get rid of the suffix of the package.json path, for example:

 var path = require('path') // npm install path var bootstrapPath = path.dirname(require.resolve('bootstrap-sass/package.json')) 

Since each package must contain a package.json file, this should always work (see What is a package? ).

+8
source share

you do not need to search for the package package.json file you need or even explicitly refer to its location.

Also, you shouldn't do this, as there is no sure way to find out where this npm package ends up in the npm tree (which is why you turned to require.resolve for help).

instead, you can request the npm (or CLI) API using npm ls using the --parseable flag, which will be:

Show syntax output instead of tree view.

eg:

 $ npm ls my-dep -p 

& hellip; will output something like this:

 /Users/me/my-project/node_modules/my-dep 

you should know that this command may display some irrelevant errors, as well as stderr (for example, about extraneous settings) - to get around this, activate the --silent flag (see loglevel in the docs):

 $ npm ls my-dep -ps 

this command can be integrated into your code using a child process , in which case it prefers to run the command without --silent to resolve any error.

if an error is detected, you can decide whether it will be fatal or not (for example, the aforementioned error about an extraneous package should be ignored).

so using CLI through a child process might look like this:

 const exec = require('child_process').exec; const packageName = 'my-dep'; exec(`npm ls ${packageName} --parseable`, (err, stdout, stderr) => { if (!err) { console.log(stdout.trim()); // -> /Users/me/my-project/node_modules/my-dep } }); 

this can be used (along with some closure magic & hellip;) in an asynchronous stream, for example:

 const exec = require('child_process').exec; const async = require('async'); async.waterfall([ npmPackagePathResolver('my-dep'), (packagePath, callback) => { console.log(packagePath) // -> /Users/me/my-project/node_modules/my-dep callback(); } ], (err, results) => console.log('all done!')); function npmPackagePathResolver(packageName) { return (callback) => { exec(`npm ls ${packageName} --parseable`, (err, stdout, stderr) => { callback(err, stdout.trim()); }); }; } 
+1
source share

All Articles