Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Task

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

Problem

I know about require.resolve, but that will give me the entry point (path to the main module) rather than the root path of the package.

Take bootstrap-sass as an example. Say it's installed locally in a project folder C:\dev\my-project. Then what I'm looking for is 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 methods how to get the package's root path:

Solution #1

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

This will work fine for packages installed locally in node_modules folder. However, if I'm in a subfolder, I need to resolve ../node_modules/bootstrap-sass, and it get's more complicated with more nested folders. In addition, this does not work for globally installed modules.

Solution #2

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

This will work for local and global modules installed in node_modules folder. The regex will match everything up to the last node_modules path element plus the following path element. However this will fail if a package's entry point is set to another package (e.g. "main": "./node_modules/sub-package" in package.json).

Solution #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 a node_modules folder.

But...

I wonder if this rather simple task cannot be solved in a simpler and less hacky way. To me it looks like something that should already be built into Node.js. Any suggestions?

like image 670
x-ray Avatar asked Jan 04 '16 04:01

x-ray


1 Answers

Try this:

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

which returns:

path_to_my_project/node_modules/bootstrap-sass/package.json 

You can now get rid of 'package.json' path suffix such as:

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

Since it is mandatory for every package to contain package.json file, this should always work (see What is a package?).

like image 199
Tomas Kulich Avatar answered Sep 20 '22 08:09

Tomas Kulich