Skip to content

Commit

Permalink
Merge branch 'release/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
inxilpro committed Aug 25, 2014
2 parents 86f4052 + f0858cb commit fd37582
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 38 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
@@ -0,0 +1,4 @@
language: node_js
node_js:
- "0.11"
- "0.10"
68 changes: 50 additions & 18 deletions README.md
@@ -1,54 +1,86 @@
# App Root Path Module

[![Build Status](https://travis-ci.org/inxilpro/node-app-root-path.svg)](https://travis-ci.org/inxilpro/node-app-root-path)

This simple module helps you access your application's root path from anywhere in the application without resorting to `require("../../path")`.

## Installation

``` bash
$ npm install app-root-path --save
$ npm install app-root-path --save
```

## Usage

To simply access the app's root path:

``` js
var appRoot = require('app-root-path'),
myModule = require(appRoot + '/lib/my-module.js');
var appRoot = require('app-root-path');
var myModule = require(appRoot + '/lib/my-module.js');
```

A helper function is also provided:

``` js
var appRequire = require('app-root-path').require(require),
myModule = appRequire('/lib/my-module.js');
var reqlib = require('app-root-path').require;
var myModule = reqlib('/lib/my-module.js');
```

This works by passing the current module's `require` method (each module has its *own* `require` method) to the `app-root-path` module, which then returns a wrapper for that method that prepends the application's root path to whatever path is passed to it.
It's a little hacky, but you can also put this method on your application's `global` object:

``` js
// In app.js
global.reqlib = require('app-root-path').require;

// In lib/module/component/subcomponent.js
var myModule = reqlib('/lib/my-module.js');
```

Finally, you can also just resolve a module path:

``` js
var myModulePath = require('app-root-path').resolve('/lib/my-module.js');
var myModulePath = require('app-root-path').resolve('/lib/my-module.js');
```

You can also explicitly set the path, using the environmental variable `APP_ROOT_PATH` or by calling `require('app-root-path').setPath('/my/app/is/here')`

## How It Works

This module works on the assumption that your application's root path is the parent of the `node_modules` directory. Here's almost all the module's logic:
This module uses two different methods to determine the app's root path, depending on the circumstances.

### Method One (preferred)

If the module is located inside your project's directory, somewhere within the `node_modules` directory (whether directly, or inside a submodule), we just do:

``` js
path.resolve(__dirname).split('/node_modules')[0];
```

This will take a path like `/var/www/node_modules/submodule/node_modules/app-root-path` and return `/var/www`. In 99% of cases, this is just what you need.

### Method Two (for edge cases)

The node module loader will also look in a few other places for modules (for example, ones that you install globally with `npm install -g`). Theses can be in one of:

- `$HOME/.node_modules`
- `$HOME/.node_libraries`
- `$PREFIX/lib/node`

Or, anywhere in the `NODE_PATH` environmental variable ([see documentation](http://nodejs.org/api/modules.html#modules_loading_from_the_global_folders)).

In these cases, we fall back to an alternate trick:

``` js
var appRootPath = path.resolve(__dirname, '..', '..');
path.dirname(require.main.filename);
```

So, given this directory structure:
When a file is run directly from Node, `require.main` is set to its `module`. `module.filename` refers to the filename of that module, so by fetching the directory name for that file, we at least get the directory of the file that was called directly. In some cases (process managers and test suites, for example) this doesn't actually give the correct directory, though, so this method is only used as a fallback.

## Change Log

my-app <-- ..
node_modules <-- ..
app-root-path <-- __dirname
index.js
lib
my-module.js
index.js
### 0.1.0
- Completely rewrote the path resolution method to account for most possible scenarios. This shouldn't cause and backwards compatibility issues, but always test your code.
- Removed the need to pass a modules's `require()` method to the `appRootPath.require()` function. Which it's true that each module has its own `require()` method, in practice it doesn't matter, and it's **much** simpler this way.
- Added tests

This may not work in every case--particularly if you try to use this in a module that is then used by other modules--but it should work 99% of the time when you're using it within the main application.

20 changes: 3 additions & 17 deletions index.js
@@ -1,18 +1,4 @@
var path = require('path'),
appRootPath = path.resolve(__dirname, '..', '..');
'use strict';

exports.resolve = function(pathToModule) {
return path.join(appRootPath, pathToModule);
};

exports.require = function(moduleReqire) {
return function(pathToModule) {
return moduleReqire(exports.resolve(pathToModule));
}
};

exports.toString = function() {
return appRootPath;
};

exports.path = appRootPath;
var lib = require('./lib/app-root-path.js');
module.exports = lib(__dirname);
38 changes: 38 additions & 0 deletions lib/app-root-path.js
@@ -0,0 +1,38 @@
'use strict';

module.exports = function(dirname) {
var path = require('path');
var resolve = require('./resolve.js');
var appRootPath = resolve(dirname);

var publicInterface = {
resolve: function(pathToModule) {
return path.join(appRootPath, pathToModule);
},

require: function(pathToModule) {
// Backwards compatibility check
if ('function' === typeof pathToModule) {
console.warn('Just use appRootPath.require() -- no need to pass in your ' +
'modules\'s require() function any more.');
return function(pathToModule) {
return publicInterface.require(pathToModule);
}
}

return require(publicInterface.resolve(pathToModule));
},

toString: function() {
return appRootPath;
},

setPath: function(explicitlySetPath) {
appRootPath = path.resolve(explicitlySetPath);
},

path: appRootPath
};

return publicInterface;
};
44 changes: 44 additions & 0 deletions lib/resolve.js
@@ -0,0 +1,44 @@
'use strict';

module.exports = function resolve(dirname) {
var path = require('path');

// Check for environmental variable
if (process.env.APP_ROOT_PATH) {
return path.resolve(process.env.APP_ROOT_PATH);
}

var globalPaths = require('module').globalPaths;
var resolved = path.resolve(dirname);
var alternateMethod = false;
var appRootPath = null;

// Make sure that we're not loaded from a global include path
// Eg. $HOME/.node_modules
// $HOME/.node_libraries
// $PREFIX/lib/node
globalPaths.forEach(function(path) {
if (!alternateMethod && 0 === resolved.indexOf(path)) {
alternateMethod = true;
}
});

// If the app-root-path library isn't loaded globally,
// and node_modules exists in the path, just split __dirname
if (!alternateMethod && -1 !== resolved.indexOf('/node_modules')) {
var parts = resolved.split('/node_modules');
if (parts.length) {
appRootPath = parts[0];
parts = null;
}
}

// If the above didn't work, or this module is loaded globally, then
// resort to require.main.filename (See http://nodejs.org/api/modules.html)
if (alternateMethod || null == appRootPath) {
appRootPath = path.dirname(require.main.filename);
}

// Return
return appRootPath;
};
9 changes: 6 additions & 3 deletions package.json
@@ -1,10 +1,10 @@
{
"name": "app-root-path",
"version": "0.0.1",
"version": "0.1.0",
"description": "Determine an app's root path from anywhere inside the app",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "node node_modules/mocha/bin/mocha -R spec"
},
"repository": {
"type": "git",
Expand All @@ -19,5 +19,8 @@
"bugs": {
"url": "https://github.com/inxilpro/node-app-root-path/issues"
},
"homepage": "https://github.com/inxilpro/node-app-root-path"
"homepage": "https://github.com/inxilpro/node-app-root-path",
"devDependencies": {
"mocha": "^1.21.4"
}
}
92 changes: 92 additions & 0 deletions test/index.js
@@ -0,0 +1,92 @@
'use strict';

var path = require('path');
var assert = require('assert');

describe('The path resolution method', function() {
var resolve = require('../lib/resolve.js');

// Check global paths
it('should use require.main.filename if the path is in the globalPaths array', function() {
var expected = path.dirname(require.main.filename);
require('module').globalPaths.forEach(function(globalPath) {
var testPath = globalPath + path.sep + 'node-app-root-path';
assert.equal(resolve(testPath), expected);
});
});

// Check some standard path layouts
it('should use String.split() in common cases', function() {
var cases = [
'/var/www/node_modules/node-app-root-path',
'/var/www/node_modules/somemodule/node_modules/node-app-root-path',
'/var/www/node_modules/somemodule/node_modules/someothermodules/node_modules/node-app-root-path',
];
var expected = '/var/www';
cases.forEach(function(testPath) {
assert.equal(resolve(testPath), expected);
});
});

// Check root path
it('should still use String.split() in the root directory', function() {
assert.equal(resolve('/node_modules'), '');
});

// Check unexpected path
it('should use require.main.filename on unexpected input', function() {
var cases = [
'just-some-jibberish',
'/var/www/libs/node-app-root-path'
];
var expected = path.dirname(require.main.filename);
cases.forEach(function(testPath) {
assert.equal(resolve(testPath), expected);
});
});

// Check when setting via environmental variable
it('should respect the APP_ROOT_PATH environmental variable', function() {
var expected = '/some/arbirary/path';
var originalPath = process.env.APP_ROOT_PATH;
process.env.APP_ROOT_PATH = expected;
assert.equal(resolve('/somewhere/else'), expected);
process.env.APP_ROOT_PATH = originalPath;
});
});

describe('The public interface', function() {
var lib = require('../lib/app-root-path.js');
var root = path.resolve(__dirname);
var pub = lib(root + '/node_modules/app-root-path');

it('should expose a resolve() method that resolves a relative path to the root path', function() {
assert(pub.resolve);
assert.equal(pub.resolve('subdir/filename.js'), root + '/subdir/filename.js');
});

it('should expose a require() method that properly loads a module relative to root', function() {
assert(pub.require);
var testlib = pub.require('lib/testlib.js');
assert.equal(testlib, 'hello world');
});

it('should implement toString()', function() {
assert(pub.toString);
assert.equal(pub + '', root);
assert.equal(pub.toString(), root);
});

it('should allow explicitly setting the root path with setPath()', function() {
assert(pub.setPath);
var originalPath = pub.toString();
pub.setPath('/path/to');
assert.equal(pub.resolve('somewhere'), '/path/to/somewhere');
pub.setPath(originalPath);
});

it('should expose the app root path as a .path property', function() {
assert(pub.path);
assert.equal(pub.path, pub.toString());
});
});
1 change: 1 addition & 0 deletions test/lib/testlib.js
@@ -0,0 +1 @@
module.exports = 'hello world';

0 comments on commit fd37582

Please sign in to comment.