Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
psyrendust committed Oct 9, 2014
0 parents commit 111df6d
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
npm-debug.log
tmp
.DS_Store
158 changes: 158 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# load-grunt-parent-tasks

> Loads de-duped grunt tasks from parent or sibling modules.
## TL;DR

load-grunt-parent-tasks is a Grunt library that allows you to load all of your
Grunt tasks even if they are in a parent folder. This library was heavily inspired
by this StackOverflow post: ["Centralise Node Modules In Project With Subproject"](http://stackoverflow.com/questions/15225865/centralise-node-modules-in-project-with-subproject), and
and utilizes the `gruntcollection` feature of `grunt.loadNpmTasks`.

## Description

If Grunt task is loaded as a submodule then create a `grunt-collection` module
in your local `node_modules` folder to trick grunt into loading dependencies
from a parent folder. This might be necessary if project A contains a Gruntfile.js
with dependencies X, Y, and Z, and is loaded as a npm dependency of project B
which contains dependencies A, X and Y. In that scenario Npm will de-dupe
project A's Npm dependencies when executing `npm install` in project B and will
look like this:

B
├── node_modules
│   ├── A
│   │   ├── Gruntfile.js
│   │   ├── node_modules
│   │   │   └── Z
│   │   └── package.json (has depencencies X,Y,Z)
│   ├── X
│   └── Y
└── package.json (has dependencies A,X,Y)

If project A's package.json contains an `install` or `postinstall` script that
executes it's Gruntfile.js the Grunt task will throw 2 errors:

Local Npm module "X" not found. Is it installed?
Local Npm module "Y" not found. Is it installed?

Project A's Grunt task will not be able to load the de-duped dependencies because
they reside in the parents node_modules folder. This can be fixed by dropping in
a new module which contains project A's package.json with the addition of a
`keywords` property that contains `"gruntcollection"` in it's array.

B
├── node_modules
│   ├── A
│   │   ├── Gruntfile.js
│   │   ├── node_modules
│   │   │   ├── grunt-collection
│   │   │   │   └── package.json (has dependencies X,Y,Z and 'keywords: ["gruntcollection"]')
│   │   │   └── Z
│   │   └── package.json (has depencencies X,Y,Z)
│   ├── X
│   └── Y
└── package.json (has dependencies A,X,Y)

Then at the beginning of your Gruntfile.js you call `grunt.loadNpmTasks('grunt-collection')`.
Now Grunt will search up for the current directory to find the modules to load.

## Features

- Checks if the main Grunt task is a submodule of another project.
- Creates a `grunt-collection` module in your local `node_modules` folder.
- Creates a `grunt-collection/package.json` that is a mirror of your projects `package.json` file defined by `options.config`.
- Filters out npm modules to load using globbing patterns defined in `options.pattern`.
- Filters out which dependencies key to load from defined by `options.scope`.

##Installation

```bash
npm install --save load-grunt-parent-tasks
```

##Example

Basic Gruntfile.js
```javascript
module.exports = function(grunt) {

require('load-grunt-parent-tasks')(grunt);

};
```

Creates the following:

A
├── Gruntfile.js
├── node_modules
│   └── grunt-collection
│   └── package.json
└── package.json

Gruntfile.js with options
```javascript
module.exports = function(grunt) {

require('load-grunt-parent-tasks')(grunt, {
config: 'package.json',
pattern: 'grunt-*',
scope: 'dependencies',
module: 'abc-def'
});

};

// Can also be written as:
module.exports = function(grunt) {

require('load-grunt-parent-tasks')(grunt, {
config: require('package.json'),
pattern: ['grunt-*'],
scope: ['dependencies'],
module: 'abc-def'
});

};
```

Creates the following:

A
├── Gruntfile.js
├── node_modules
│   └── abc-def
│   └── package.json
└── package.json

## Options

### config

Type: `String`, `Object`
Default: Path to nearest package.json

### pattern

Type: `String`, `Array`
Default: `'grunt-*'` ([globbing pattern](https://github.com/isaacs/minimatch))

### scope

Type: `String`, `Array`
Default: `['dependencies', 'optionalDependencies']`

### module

Type: `String`
Default: `'grunt-collection'`

The `module` option can be changed in case `grunt-collection` ever conflicts with any other package name.

## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).

## License

[MIT](http://psyrendust.mit-license.org/2014/license.html) © [Larry Gordon](https://github.com/psyrendust)
138 changes: 138 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* If Grunt task is loaded as a submodule then create a `grunt-collection` module
* in your local `node_modules` folder to trick grunt into loading dependencies
* from a parent folder. This might be necessary if project A contains a Gruntfile.js
* with dependencies X, Y, and Z, and is loaded as a npm dependency of project B
* which contains dependencies A, X and Y. In that scenario NPM will de-dupe
* project A's NPM dependencies when executing `npm install` in project B and will
* look like this:
*
* B
* ├── node_modules
* │   ├── A
* │   │   ├── Gruntfile.js
* │   │   ├── node_modules
* │   │   │   └── Z
* │   │   └── package.json (has depencencies X,Y,Z)
* │   ├── X
* │   └── Y
* └── package.json (has dependencies A,X,Y)
*
* If project A's package.json contains an `install` or `postinstall` script that
* executes it's Gruntfile.js the Grunt task will throw 2 errors:
*
* Local Npm module "X" not found. Is it installed?
* Local Npm module "Y" not found. Is it installed?
*
* Project A's Grunt task will not be able to load the de-duped dependencies because
* they reside in the parents node_modules folder. This can be fixed by dropping in
* a new module which contains project A's package.json with the addition of a
* `keywords` property that contains `"gruntcollection"` in it's array.
*
* B
* ├── node_modules
* │   ├── A
* │   │   ├── Gruntfile.js
* │   │   ├── node_modules
* │   │   │   ├── grunt-collection
* │   │   │   │   └── package.json (has dependencies X,Y,Z and 'keywords: ["gruntcollection"]')
* │   │   │   └── Z
* │   │   └── package.json (has depencencies X,Y,Z)
* │   ├── X
* │   └── Y
* └── package.json (has dependencies A,X,Y)
*
* Then at the beginning of your Gruntfile.js you call `grunt.loadNpmTasks('grunt-collection')`.
* Now Grunt will search up for the current directory to find the modules to load.
*
* See:
* http://stackoverflow.com/questions/15225865/centralise-node-modules-in-project-with-subproject
* https://github.com/gruntjs/grunt/issues/696
* http://stackoverflow.com/questions/24854215/grunt-doesnt-look-for-node-modules-in-parent-directory-like-node-does
*/
'use strict';
var path = require('path');

/**
* Returns the element as an array.
* @method toArray
* @param {*} el An array or any other data type.
* @return {Array} The element as an array.
*/
function toArray(el) {
return Array.isArray(el) ? el : [el];
}

/**
* Creates a resolution for loading a package.json file.
* @method resolveConfig
* @param {String|Object} config The location of a package.json or the contents of the package.json.
* @return {Object} The contents of the package.json.
*/
function resolveConfig(config) {
if (typeof config === 'string') {
return require(path.resolve(config));
}
return config;
}

function loadGruntParentTasks(grunt, options) {
var log = function() {
grunt.log.writeln(['[loadGruntParentTasks]'.magenta].concat(Array.prototype.slice.call(arguments)).join(' '));
};

options = options || {};

log('process.cwd(): ' + process.cwd().cyan);

var isRoot = process.cwd().split(path.sep).filter(function(name) {
return name === 'node_modules';
}).length === 0;

log('isRoot: ' + ('' + isRoot).cyan);

if (!isRoot) {
var _ = require('lodash');
var findup = require('findup-sync');
var globule = require('globule');

var config = resolveConfig(options.config || findup('package.json'));
var pattern = toArray(options.pattern || ['grunt-*']);
var scope = toArray(options.scope || ['dependencies', 'optionalDependencies']);

var gruntCollection = path.resolve('./node_modules/', (options.module || 'grunt-collection'));
var gruntCollectionJson = path.join(gruntCollection, 'package.json');

var newPkg = {};
var names = [];
var deps = {};

pattern.push('!grunt', '!grunt-cli');

names = scope.reduce(function (result, prop) {
deps[prop] = {};
return result.concat(Object.keys(config[prop] || {}));
}, []);

// Create a new depenencies object of grunt tasks to load based on the
// globbing pattern and scope.
globule.match(pattern, names).map(function(name) {
scope.forEach(function (prop) {
if (config[prop][name]) {
deps[prop][name] = config[prop][name];
}
});
});

newPkg = _.assign({}, config, {
keywords: [ 'gruntcollection' ],
scripts: {}
}, deps);

grunt.file.mkdir(gruntCollection);
grunt.file.write(gruntCollectionJson, JSON.stringify(newPkg, null, 2));
grunt.loadNpmTasks('grunt-collection');
}
}

module.exports = loadGruntParentTasks;
47 changes: 47 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "load-grunt-parent-tasks",
"description": "Loads de-duped grunt tasks from parent or sibling modules.",
"version": "0.1.0",
"homepage": "https://github.com/psyrendust/load-grunt-parent-tasks",
"author": {
"name": "Larry Gordon",
"email": "lgordon@psyrendust.com"
},
"repository": {
"type": "git",
"url": "git://github.com/psyrendust/load-grunt-parent-tasks.git"
},
"bugs": {
"url": "https://github.com/psyrendust/load-grunt-parent-tasks/issues"
},
"licenses": [
{
"type": "MIT",
"url": "http://psyrendust.mit-license.org/2014/license.html"
}
],
"engines": {
"node": ">= 0.10.0"
},
"scripts": {},
"dependencies": {
"findup-sync": "^0.1.3",
"globule": "^0.2.0",
"lodash": "^2.4.1"
},
"devDependencies": {},
"keywords": [
"grunt",
"load",
"require",
"tasks",
"glob",
"pattern",
"match",
"matchdep",
"dependencies",
"loadNpmTasks",
"de-duped",
"submodules"
]
}

0 comments on commit 111df6d

Please sign in to comment.