├── examples ├── project2 │ ├── watchfile.js │ ├── gulpfile.js │ └── package.json ├── project1 │ ├── project1A │ │ ├── watchfile.js │ │ ├── gulpfile.js │ │ └── package.json │ ├── project1B │ │ ├── watchfile.js │ │ ├── gulpfile.js │ │ └── package.json │ ├── watchfile.js │ └── gulpfile.js └── gulpfile.js ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .editorconfig ├── gulpfile.js ├── README.md ├── lib ├── hub-util.js ├── resolve-glob.js ├── add-task.js ├── get-subfiles.js ├── index.js ├── load-subfile.js ├── index-streams.js └── add-subtask.js ├── LICENSE ├── test ├── resolve-glob-spec.js ├── lib │ └── test-util.js ├── hub-util-spec.js ├── load-subfile-spec.js ├── add-task-spec.js ├── get-subfiles-spec.js ├── index-spec.js └── add-subtask-spec.js └── package.json /examples/project2/watchfile.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /examples/project1/project1A/watchfile.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/project1/project1B/watchfile.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/project1/watchfile.js: -------------------------------------------------------------------------------- 1 | watchfile.js -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "unused": true, 3 | "lastsemic": true, 4 | "expr": true 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "4" 6 | - "5" 7 | - "6" 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /examples/project1/gulpfile.js: -------------------------------------------------------------------------------- 1 | var hub = require('../../lib/index.js'); 2 | var gulp = require('gulp'); 3 | var gutil = require('gulp-util'); 4 | 5 | gulp.task('compile', function() { 6 | gutil.log('compiling project1'); 7 | }); 8 | 9 | gulp.task('default', [ 'compile' ]); 10 | 11 | hub(['project*/gulpfile.js']); -------------------------------------------------------------------------------- /examples/gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | You can run this by calling gulp from the command line. 3 | > gulp 4 | > gulp watch 5 | > gulp compile 6 | */ 7 | var gulp = require('gulp'); 8 | var gutil = require('gulp-util'); 9 | var hub = require('../lib/index.js'); 10 | 11 | gulp.task('compile', function(cb) { 12 | gutil.log('compiling example'); 13 | cb(); 14 | }); 15 | 16 | gulp.task('default', [ 'compile' ]); 17 | 18 | hub(['project1/gulpfile.js', 'proj*/gulpfile.js', 'gulpfile.js']); -------------------------------------------------------------------------------- /examples/project1/project1A/gulpfile.js: -------------------------------------------------------------------------------- 1 | // this will use a private gulp instance 2 | var gulp = require('gulp'); 3 | var gutil = require('gulp-util'); 4 | 5 | gulp.task('precompile', function() { 6 | gutil.log('precompiling project1A') 7 | }); 8 | 9 | gulp.task('compile', [ 'precompile' ], function() { 10 | gutil.log('compiling project1A') 11 | }); 12 | 13 | gulp.task('watch', function() { 14 | gulp.watch(['watchfile.js'], function() { 15 | gutil.log('watched project1A'); 16 | }); 17 | }); 18 | 19 | gulp.task('default', [ 'compile' ]); 20 | -------------------------------------------------------------------------------- /examples/project1/project1B/gulpfile.js: -------------------------------------------------------------------------------- 1 | // this will use a private gulp instance 2 | var gulp = require('gulp'); 3 | var gutil = require('gulp-util'); 4 | 5 | gulp.task('precompile', function() { 6 | gutil.log('precompiling project1B') 7 | }); 8 | 9 | gulp.task('compile', [ 'precompile' ], function() { 10 | gutil.log('compiling project1B') 11 | }); 12 | 13 | gulp.task('watch', function() { 14 | gulp.watch(['watchfile.js'], function() { 15 | gutil.log('watched project1B'); 16 | }); 17 | }); 18 | 19 | gulp.task('default', [ 'compile' ]); 20 | -------------------------------------------------------------------------------- /examples/project2/gulpfile.js: -------------------------------------------------------------------------------- 1 | // this will use a private gulp instance 2 | var gulp = require('gulp'); 3 | var gutil = require('gulp-util'); 4 | 5 | gulp.task('precompile', function(cb) { 6 | gutil.log('precompiling project2'); 7 | cb(); 8 | }); 9 | 10 | gulp.task('compile', [ 'precompile' ], function(cb) { 11 | gutil.log('compiling project2'); 12 | cb(); 13 | }); 14 | 15 | gulp.task('watch', function() { 16 | gulp.watch(['watchfile.js'], function() { 17 | gutil.log('watched project2'); 18 | }); 19 | }); 20 | 21 | gulp.task('default', [ 'compile' ]); 22 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var istanbul = require('gulp-istanbul'); 3 | var mocha = require('gulp-mocha'); 4 | 5 | gulp.task('bump', ['compile'], function(){ 6 | return gulp.src('./package.json') 7 | .pipe(bump({type:'minor'})) 8 | .pipe(gulp.dest('./')); 9 | }); 10 | 11 | gulp.task('test', function (cb) { 12 | gulp.src('lib/**/*.js') 13 | .pipe(istanbul()) // Covering files 14 | .on('finish', function () { 15 | gulp.src(['test/*.js']) 16 | .pipe(mocha()) 17 | .pipe(istanbul.writeReports()) // Creating the reports after tests runned 18 | .on('end', cb); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gulp-hub 2 | 3 | > [Gulp](http://gulpjs.com/) extension to run tasks from multiple gulpfiles. 4 | 5 | [![build status](https://secure.travis-ci.org/frankwallis/gulp-hub.png?branch=master)](http://travis-ci.org/frankwallis/gulp-hub?branch=master) 6 | 7 | Usage: 8 | 9 | 1. Install gulp-hub: 10 | 11 | ```sh 12 | $ npm install gulp gulp-hub 13 | ``` 14 | 15 | 2. Create a gulpfile.js which looks like this: 16 | 17 | ```js 18 | var hub = require('gulp-hub'); 19 | hub(['./project1/gulpfile.js', './project1/gulpfile.js']); 20 | ``` 21 | 22 | 3. Run `gulp [taskname]` 23 | 24 | Gulp-hub will execute that task in all of the gulpfiles. 25 | -------------------------------------------------------------------------------- /lib/hub-util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gulp Hub utility module 3 | */ 4 | var _ = require( 'lodash' ); 5 | 6 | /** 7 | * Returns true if `file` is a valid gulp-hub file object. 8 | * 9 | * @param file - The variable to test 10 | * @return - True if the variable is a gulp-hub object, false if not 11 | */ 12 | module.exports.isValidHubFile = function (file) { 13 | if ( 14 | _.isPlainObject( file ) && 15 | file.relativePath && typeof file.relativePath === 'string' && 16 | file.absolutePath && typeof file.absolutePath === 'string' && 17 | file.uniqueName && typeof file.uniqueName === 'string' 18 | ){ 19 | return true; 20 | } 21 | return false; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/resolve-glob.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var glob = require( 'glob' ); 3 | 4 | /** 5 | * Resolves a gulp-like glob pattern, 6 | * https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulpsrcglobs-options, 7 | * to a minimatch glob string, https://github.com/isaacs/minimatch 8 | * 9 | * @param {string|array} pattern - A glob or an array of globs 10 | * @returns - An array of files matched by the glob 11 | */ 12 | module.exports = function(pattern, rootDir) { 13 | if (Array.isArray(pattern)) { 14 | if (pattern.length === 1) 15 | pattern = pattern[0]; 16 | else 17 | pattern = '{' + pattern.join(',') + '}'; 18 | } 19 | 20 | return glob.sync(pattern, { "nosort": true, "cwd": rootDir }) 21 | } 22 | -------------------------------------------------------------------------------- /examples/project2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project2", 3 | "description": "gulp-hub example project", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/frankwallis/gulp-hub", 6 | "author": { 7 | "name": "Frank Wallis", 8 | "email": "fwallis@outlook.com" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/frankwallis/gulp-hub/issues" 12 | }, 13 | "licenses": [ 14 | { 15 | "type": "MIT", 16 | "url": "https://github.com/frankwallis/gulp-hub/blob/master/LICENSE-MIT" 17 | } 18 | ], 19 | "main": "watchfile.js", 20 | "engines": { 21 | "node": ">= 0.8.0" 22 | }, 23 | "scripts": { 24 | "test": "gulp test" 25 | }, 26 | "dependencies": { 27 | }, 28 | "devDependencies": { 29 | "gulp": "^3.5.5" 30 | }, 31 | "keywords": [ 32 | "gulp", 33 | "hub", 34 | "grunt-hub", 35 | "gulp-chug", 36 | "multi", 37 | "gulpplugin" 38 | ], 39 | "private": true 40 | } 41 | -------------------------------------------------------------------------------- /examples/project1/project1A/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project1A", 3 | "description": "gulp-hub example project", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/frankwallis/gulp-hub", 6 | "author": { 7 | "name": "Frank Wallis", 8 | "email": "fwallis@outlook.com" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/frankwallis/gulp-hub/issues" 12 | }, 13 | "licenses": [ 14 | { 15 | "type": "MIT", 16 | "url": "https://github.com/frankwallis/gulp-hub/blob/master/LICENSE-MIT" 17 | } 18 | ], 19 | "main": "watchfile.js", 20 | "engines": { 21 | "node": ">= 0.8.0" 22 | }, 23 | "scripts": { 24 | "test": "gulp test" 25 | }, 26 | "dependencies": { 27 | }, 28 | "devDependencies": { 29 | "gulp": "^3.5.5" 30 | }, 31 | "keywords": [ 32 | "gulp", 33 | "hub", 34 | "grunt-hub", 35 | "gulp-chug", 36 | "multi", 37 | "gulpplugin" 38 | ], 39 | "private": true 40 | } 41 | -------------------------------------------------------------------------------- /examples/project1/project1B/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project1B", 3 | "description": "gulp-hub example project", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/frankwallis/gulp-hub", 6 | "author": { 7 | "name": "Frank Wallis", 8 | "email": "fwallis@outlook.com" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/frankwallis/gulp-hub/issues" 12 | }, 13 | "licenses": [ 14 | { 15 | "type": "MIT", 16 | "url": "https://github.com/frankwallis/gulp-hub/blob/master/LICENSE-MIT" 17 | } 18 | ], 19 | "main": "watchfile.js", 20 | "engines": { 21 | "node": ">= 0.8.0" 22 | }, 23 | "scripts": { 24 | "test": "gulp test" 25 | }, 26 | "dependencies": { 27 | }, 28 | "devDependencies": { 29 | "gulp": "^3.5.5" 30 | }, 31 | "keywords": [ 32 | "gulp", 33 | "hub", 34 | "grunt-hub", 35 | "gulp-chug", 36 | "multi", 37 | "gulpplugin" 38 | ], 39 | "private": true 40 | } 41 | -------------------------------------------------------------------------------- /lib/add-task.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var runSequence = require('run-sequence'); 3 | 4 | /** 5 | * Register the subtasks of the given tasks, and configure the master task to 6 | * run them. 7 | * 8 | * @param {object} task - A task object from the task registry 9 | */ 10 | module.exports = function(task) { 11 | // create the unique subtasks 12 | task.subtasks.forEach(function (subtask) { 13 | gulp.task(subtask.name, subtask.param1, subtask.param2); 14 | }); 15 | 16 | var args = task.subtasks.map(function (subtask) { return subtask.name; }); 17 | 18 | // create the master task which will run all the subtasks in sequence 19 | gulp.task(task.name, function(cb) { 20 | var cwd = process.cwd(); 21 | var _args = args.slice(); 22 | _args.push(function() { 23 | // restore current working directory (see #23) 24 | process.chdir(cwd); 25 | cb.apply(null, arguments); 26 | }); 27 | runSequence.apply(null, _args); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Frank Wallis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test/resolve-glob-spec.js: -------------------------------------------------------------------------------- 1 | var should = require( 'should' ); 2 | var sinon = require( 'sinon' ); 3 | var pequire = require( 'proxyquire' ); 4 | 5 | describe( 'resolve-glob', function () { 6 | 7 | it( 'resolves glob pattern to a list of files', function () { 8 | var pdeps = { glob: { sync: sinon.spy() } }; 9 | var resolveGlob = pequire( '../lib/resolve-glob', pdeps ); 10 | 11 | var spy = pdeps.glob.sync; 12 | var globOpts = { 'nosort': true, 'cwd': undefined }; 13 | 14 | resolveGlob( 'single-glob-pattern' ); 15 | spy.calledOnce.should.be.true; 16 | spy.calledWith( 'single-glob-pattern', globOpts ).should.be.true; 17 | 18 | spy.reset(); 19 | 20 | resolveGlob( [ 'array-with-one-element' ] ); 21 | spy.calledOnce.should.be.true; 22 | spy.calledWith( 'array-with-one-element', globOpts ).should.be.true; 23 | 24 | spy.reset(); 25 | 26 | globOpts = { 'nosort': true, 'cwd': 'somedir' }; 27 | resolveGlob( [ 'array', 'with', 'more', 'than', 'one', 'element' ], 'somedir' ); 28 | spy.calledOnce.should.be.true; 29 | spy.calledWith( '{array,with,more,than,one,element}', globOpts ).should.be.true; 30 | } ); 31 | } ); 32 | -------------------------------------------------------------------------------- /test/lib/test-util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test utilities. 3 | */ 4 | var _ = require( 'lodash' ); 5 | 6 | /** 7 | * Return an array of example JavaScript types including String, Number, 8 | * Boolean, Object, Array, Null, Undefined[1]. 9 | * 10 | * Exclude a type example by providing a validation function, e.g., 11 | * getTypeExamples( _.isArray ) will return all type examples except arrays[2]. 12 | * 13 | * [1] http://msdn.microsoft.com/en-us/library/ie/7wkd9z69(v=vs.94).aspx 14 | * [2] http://lodash.com/docs 15 | * 16 | * @param {[function]} excludeFunc - Validation function for the type you wish to exclude 17 | */ 18 | module.exports.getTypeExamples = function ( excludeFunc ) { 19 | 20 | // Examples of each 21 | var ALL_TYPE_EXAMPLES = [ 22 | '', 'a', 0, 1, false, true, {}, { a: 1 }, [], [ 'a' ], null, undefined 23 | ]; 24 | 25 | // If no exclusions, return all type examples 26 | if ( _.isUndefined( excludeFunc ) || !_.isFunction( excludeFunc ) ) { 27 | return ALL_TYPE_EXAMPLES; 28 | } 29 | 30 | // Otherwise, remove the specified exclude type from the type examples 31 | var typeExamples = _.remove( ALL_TYPE_EXAMPLES, function ( el ) { 32 | return !excludeFunc( el ); 33 | } ); 34 | 35 | return typeExamples; 36 | }; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-hub", 3 | "description": "A gulp plugin to run tasks from multiple gulpfiles", 4 | "version": "0.8.0", 5 | "homepage": "https://github.com/frankwallis/gulp-hub", 6 | "author": { 7 | "name": "Frank Wallis", 8 | "email": "fwallis@outlook.com" 9 | }, 10 | "contributors": [ 11 | "Rob McGuire-Dale ", 12 | "Andrew Gatlabayan " 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/frankwallis/gulp-hub.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/frankwallis/gulp-hub/issues" 20 | }, 21 | "licenses": [ 22 | { 23 | "type": "MIT", 24 | "url": "https://github.com/frankwallis/gulp-hub/blob/master/LICENSE-MIT" 25 | } 26 | ], 27 | "main": "lib/index.js", 28 | "engines": { 29 | "node": ">= 0.8.0" 30 | }, 31 | "scripts": { 32 | "test": "gulp test" 33 | }, 34 | "dependencies": { 35 | "callsite": "^1.0.0", 36 | "glob": "^7.0.3", 37 | "gulp": "^3.8.10", 38 | "gulp-util": "^3.0.7", 39 | "lodash": "^4.13.1", 40 | "proxyquire": "^1.0.1", 41 | "run-sequence": "^1.0.2" 42 | }, 43 | "devDependencies": { 44 | "gulp": "^3.5.5", 45 | "gulp-bump": "^2.1.0", 46 | "gulp-istanbul": "^1.0.0", 47 | "gulp-mocha": "^2.2.0", 48 | "mocha": "^2.5.3", 49 | "should": "^9.0.2", 50 | "sinon": "^1.10.3" 51 | }, 52 | "keywords": [ 53 | "gulp", 54 | "hub", 55 | "grunt-hub", 56 | "gulp-chug", 57 | "multi", 58 | "gulpfriendly" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /lib/get-subfiles.js: -------------------------------------------------------------------------------- 1 | var path = require( 'path' ); 2 | 3 | /** 4 | * Given a list of file paths, return a list of objects representing each file's 5 | * relative path, absolute path, and unique name. (The unique name is just the 6 | * relative path for now.) 7 | * 8 | * @param {array} filelist - A list of file paths 9 | * @returns - An array of objects containing each file's absolute path, relative path, and a unique name 10 | * @example 11 | * > var getSubfiles = require('./get-subfiles'); 12 | * > getSubfiles(['b/c.js', '2/3.js']); 13 | * [ 14 | * { 15 | * relativePath: b/c.js, 16 | * absolutePath: /a/b/c.js, 17 | * uniqueName: b/c.js 18 | * }, 19 | * { 20 | * relativePath: 2/3.js, 21 | * absolutePath: /1/2/3.js, 22 | * uniqueName: 2/3.js 23 | * } 24 | * ] 25 | */ 26 | module.exports = function(filelist) { 27 | 28 | if(!Array.isArray(filelist)) { 29 | throw new TypeError( 'An array of file paths is required.' ); 30 | } 31 | 32 | var subfiles = []; 33 | 34 | filelist.forEach(function (file) { 35 | var subfile = {}; 36 | 37 | subfile.relativePath = file; 38 | subfile.absolutePath = path.resolve(file); 39 | 40 | // a subfile's unique name is just its relative path 41 | subfile.uniqueName = subfile.relativePath; 42 | 43 | subfiles.push(subfile); 44 | }); 45 | 46 | return subfiles; 47 | }; 48 | -------------------------------------------------------------------------------- /test/hub-util-spec.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | var should = require( 'should' ); 3 | var tutil = require( './lib/test-util' ); 4 | 5 | describe( 'hub-util', function () { 6 | var hutil = require('../lib/hub-util'); 7 | 8 | describe( 'isValidHubFile', function () { 9 | var isValidHubFile = hutil.isValidHubFile; 10 | var EXPECTED_PROPS = [ 'absolutePath', 'relativePath', 'uniqueName' ]; 11 | var VALID_FILE = { absolutePath: 'a', relativePath: 'r', uniqueName: 'r' }; 12 | 13 | it( 'returns true if the file is a valid gulp-hub file', function () { 14 | isValidHubFile( VALID_FILE ).should.be.true; 15 | } ); 16 | 17 | it( 'returns false if not a plain object', function () { 18 | tutil.getTypeExamples( _.isPlainObject ).forEach( function ( type ) { 19 | isValidHubFile( type ).should.be.false; 20 | } ); 21 | } ); 22 | 23 | it( 'returns false if missing properties', function () { 24 | EXPECTED_PROPS.forEach( function ( prop ) { 25 | var missingProp = _.omit( VALID_FILE, prop ); 26 | isValidHubFile( missingProp ).should.be.false; 27 | } ); 28 | } ); 29 | 30 | it( 'returns false if properties are not strings', function () { 31 | tutil.getTypeExamples( _.isString ).forEach( function ( type ) { 32 | EXPECTED_PROPS.forEach( function ( prop ) { 33 | var testFile = _.assign( {}, VALID_FILE ); 34 | testFile[ prop ] = type; 35 | isValidHubFile( testFile ).should.be.false; 36 | } ); 37 | } ); 38 | } ); 39 | } ); 40 | } ); 41 | -------------------------------------------------------------------------------- /test/load-subfile-spec.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | var should = require( 'should' ); 3 | var sinon = require( 'sinon' ); 4 | var pequire = require( 'proxyquire' ).noCallThru(); 5 | var tutil = require( './lib/test-util' ); 6 | 7 | var HAPPY_PROXY_DEPS = { 8 | gulp: { Gulp: _.noop }, 9 | './hub-util': { isValidHubFile: function (){ return true } }, 10 | './add-subtask': _.noop 11 | }; 12 | 13 | var getLoad = function ( proxyDeps ) { 14 | return pequire( '../lib/load-subfile', _.assign( {}, HAPPY_PROXY_DEPS, proxyDeps ) ); 15 | }; 16 | 17 | describe( 'load-subfile', function () { 18 | 19 | it( 'errors if subfile is not a valid Gulp Hub file', function () { 20 | var loadSubfile = getLoad( { 21 | './hub-util': { 22 | isValidHubFile: function () { return false } 23 | } 24 | } ); 25 | loadSubfile.should.throw( '`subfile` must be a valid Gulp Hub file object.' ); 26 | } ); 27 | 28 | it( 'errors if the task registry is not an object', function () { 29 | var loadSubfile = getLoad(); 30 | tutil.getTypeExamples( _.isPlainObject ).forEach( function ( testTasksParam ) { 31 | loadSubfile.bind( null, null, testTasksParam ).should.throw( '`task` must be an object' ); 32 | } ); 33 | } ); 34 | 35 | // TODO: Implement these specs. I'm not sure exactly how to test these 36 | // specs as the current implementation involves the `require`, `module` 37 | // objects which are local to each script. - @robatron 38 | 39 | it( 'proxies `gulp.task` to our add-subtask' ); 40 | 41 | it( '`require`s the specified file' ); 42 | 43 | it( 'adds tasks from the local gulpfile' ); 44 | } ); 45 | -------------------------------------------------------------------------------- /test/add-task-spec.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | var should = require( 'should' ); 3 | var sinon = require( 'sinon' ); 4 | var pequire = require( 'proxyquire' ).noCallThru(); 5 | 6 | var HAPPY_PROXY_DEPS = { 7 | gulp: { task: _.noop }, 8 | 'run-sequence': _.noop 9 | }; 10 | 11 | var getAddTask = function ( proxyDeps ) { 12 | return pequire( '../lib/add-task', _.assign( {}, HAPPY_PROXY_DEPS, proxyDeps ) ); 13 | }; 14 | 15 | describe( 'add-task', function () { 16 | 17 | it( 'adds each tasks\'s subtasks and their parameters to gulp', function () { 18 | var taskSpy = sinon.spy(); 19 | var addTask = getAddTask( { gulp: { task: taskSpy } } ); 20 | addTask( { 21 | name: 'task-name', 22 | subtasks: [ 23 | { name: 'sub-task-name', param1: 'param1', param2: 'param2' } 24 | ] 25 | } ); 26 | taskSpy.calledTwice.should.be.true; 27 | taskSpy.calledWith( 'sub-task-name', 'param1', 'param2' ).should.be.true; 28 | taskSpy.calledWith( 'task-name' ).should.be.true; 29 | } ); 30 | 31 | it( 'creates a master task to run all subtasks in sequence', function () { 32 | var runSeqSpy = sinon.spy(); 33 | var taskSpy = sinon.spy( function ( name, callback ) { 34 | callback(); 35 | } ); 36 | 37 | var addTask = getAddTask( { 38 | gulp: { 39 | task: taskSpy 40 | }, 41 | 'run-sequence': runSeqSpy 42 | } ); 43 | 44 | addTask( { 45 | name: 'task-name', 46 | subtasks: [ 47 | { 48 | name: 'subfile-unique-name-1-task-name', 49 | param1: _.noop 50 | }, 51 | { 52 | name: 'subfile-unique-name-2-task-name', 53 | param1: _.noop 54 | } 55 | ] } 56 | ); 57 | 58 | taskSpy.calledThrice.should.be.true; 59 | taskSpy.calledWith( 'task-name' ).should.be.true; 60 | taskSpy.calledWith( 'subfile-unique-name-1-task-name' ).should.be.true; 61 | taskSpy.calledWith( 'subfile-unique-name-2-task-name' ).should.be.true; 62 | 63 | runSeqSpy.calledOnce.should.be.true; 64 | runSeqSpy.calledWith( 65 | 'subfile-unique-name-1-task-name', 66 | 'subfile-unique-name-2-task-name' 67 | ).should.be.true; 68 | } ); 69 | 70 | } ); 71 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * gulp-hub 3 | * https://github.com/frankwallis/gulp-hub 4 | * 5 | * Copyright (c) 2014 Frank Wallis 6 | * Licensed under the MIT license. 7 | */ 8 | var path = require('path'); 9 | var util = require('gulp-util'); 10 | var resolveGlob = require('./resolve-glob'); 11 | var getSubfiles = require('./get-subfiles'); 12 | var loadSubfile = require('./load-subfile'); 13 | var addTask = require('./add-task'); 14 | 15 | // Creates a vanilla object with a `null` prototype (so we don't have to use 16 | // `hasOwnProperty`) to maintain the state of the tasks registry 17 | var tasks = Object.create(null); 18 | 19 | /** 20 | * Load tasks from gulpfiles specified by the file `pattern`. 21 | * @param {string|array} pattern - A gulp-style file pattern to glob gulpfiles 22 | */ 23 | function loadFiles(pattern, rootDir) { 24 | // assert `pattern` is a valid glob (non-empty string) or array of globs 25 | var isString = typeof pattern === 'string'; 26 | var isArray = Array.isArray(pattern); 27 | if ((!isString && !isArray) || (pattern.length === 0)) { 28 | throw new TypeError('A glob pattern or an array of glob patterns is required.'); 29 | } 30 | 31 | // find all the gulpfiles - needs to happen synchronously so we create the tasks before gulp runs 32 | var filelist = resolveGlob(pattern, rootDir) 33 | .map(function(filename) { 34 | return path.relative(process.cwd(), path.join(rootDir, filename)); 35 | }); 36 | 37 | var subfiles = getSubfiles(filelist); 38 | 39 | // load all the gulpfiles 40 | subfiles.forEach(function (subfile) { 41 | util.log( 'Loading', util.colors.yellow(subfile.relativePath)); 42 | loadSubfile(subfile, tasks); 43 | }); 44 | } 45 | 46 | /** 47 | * Returns the path of the file containing the function which called the function 48 | * which called this function. 49 | */ 50 | function getCallSite() { 51 | var _callsite = require('callsite'); 52 | var stack = _callsite(); 53 | return stack[2].getFileName(); 54 | } 55 | 56 | // support recursive calls (see examples) 57 | var inhub = false; 58 | 59 | function hub(pattern) { 60 | var callsite = getCallSite(); 61 | var recursive = inhub; 62 | inhub = true; 63 | 64 | loadFiles(pattern, path.dirname(callsite)); 65 | 66 | // only create the task tree once 67 | if (!recursive) { 68 | for (var name in tasks) 69 | addTask(tasks[name]); 70 | 71 | inhub = false; 72 | } 73 | } 74 | 75 | module.exports = hub; -------------------------------------------------------------------------------- /test/get-subfiles-spec.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | var should = require( 'should' ); 3 | var sinon = require( 'sinon' ); 4 | var pequire = require( 'proxyquire' ); 5 | var tutil = require( './lib/test-util' ); 6 | 7 | var MODULE_PATH = '../lib/get-subfiles'; 8 | 9 | describe( 'get-subfiles', function () { 10 | 11 | it( 'requires an array of file paths', function () { 12 | var getSubfiles = require( MODULE_PATH ); 13 | tutil.getTypeExamples( _.isArray ).forEach( function ( testValue ) { 14 | getSubfiles.bind( null, testValue ) 15 | .should.throw( 'An array of file paths is required.' ); 16 | } ); 17 | 18 | // Assert we don't get an error for valid values 19 | getSubfiles.bind( null, [] ).should.not.throw(); 20 | } ); 21 | 22 | it( 'returns an empty array if supplied no file paths', function () { 23 | var getSubfiles = require( MODULE_PATH ); 24 | getSubfiles( [] ).should.be.instanceof( Array ).and.have.lengthOf( 0 ); 25 | } ); 26 | 27 | it( 'adds an absolutePath, relativePath, and uniqueName property to each supplied file path', function () { 28 | var resolveSpy = sinon.spy( function ( filepath ) { return filepath + '-abs' } ); 29 | var getSubfiles = pequire( MODULE_PATH, { path: { resolve: resolveSpy } } ); 30 | 31 | var subfiles = getSubfiles( [ 'file-path' ] ); 32 | subfiles.should.be.instanceof( Array ).and.have.lengthOf( 1 ); 33 | subfiles[ 0 ].should.eql( { 34 | relativePath: 'file-path', 35 | absolutePath: 'file-path-abs', 36 | uniqueName: 'file-path' 37 | } ); 38 | resolveSpy.calledOnce.should.be.true; 39 | resolveSpy.calledWith( 'file-path' ).should.be.true; 40 | 41 | resolveSpy.reset(); 42 | 43 | var subfilesMulti = getSubfiles( [ 'file-path-a', 'file-path-b' ] ); 44 | subfilesMulti.should.be.instanceof( Array ).and.have.lengthOf( 2 ); 45 | subfilesMulti.should.eql( [ 46 | { 47 | relativePath: 'file-path-a', 48 | absolutePath: 'file-path-a-abs', 49 | uniqueName: 'file-path-a' 50 | }, { 51 | relativePath: 'file-path-b', 52 | absolutePath: 'file-path-b-abs', 53 | uniqueName: 'file-path-b' 54 | } 55 | ] ); 56 | resolveSpy.calledTwice.should.be.true; 57 | resolveSpy.calledWith( 'file-path-a' ).should.be.true; 58 | resolveSpy.calledWith( 'file-path-b' ).should.be.true; 59 | 60 | } ); 61 | } ); 62 | -------------------------------------------------------------------------------- /lib/load-subfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var hutil = require('./hub-util'); 3 | var addSubtask = require('./add-subtask'); 4 | var path = require('path'); 5 | var proxyquire = require('proxyquire'); 6 | 7 | /** 8 | * Load the tasks from the specified gulpfile and register them with the task 9 | * registry. 10 | * 11 | * @param {object} subfile - A gulp-hub file object 12 | * @param {object} tasks - The task registry. Will be modified. 13 | */ 14 | module.exports = function(subfile, tasks) { 15 | 16 | // param validation 17 | if(!hutil.isValidHubFile(subfile)) { 18 | throw new TypeError('`subfile` must be a valid Gulp Hub file object.'); 19 | } 20 | if(!(typeof tasks === 'object' && tasks !== null && !Array.isArray(tasks))) { 21 | throw new TypeError('`task` must be an object'); 22 | } 23 | 24 | // keep reference to gulp.task function 25 | var originalTaskFn = gulp.Gulp.prototype.task; 26 | var usingLocal = true; 27 | 28 | // redirect gulp.task to call our function instead in case they are using our instance of gulp 29 | gulp.Gulp.prototype.task = function (name, parm1, parm2) { 30 | addSubtask(subfile, tasks, name, parm1, parm2); 31 | usingLocal = false; 32 | }; 33 | 34 | proxyquire(subfile.absolutePath,{ 35 | gulp:gulp 36 | }); 37 | 38 | // if that gulpfile used its own local gulp installation 39 | // then we need to transfer the tasks out of that into ours 40 | if (usingLocal) { 41 | var submodule = findModule(function(mod) { 42 | return (mod.id === subfile.absolutePath); 43 | }); 44 | 45 | if (submodule) 46 | addLocalGulpTasks(subfile, submodule, tasks); 47 | } 48 | 49 | // restore gulp.task function 50 | gulp.Gulp.prototype.task = originalTaskFn; 51 | }; 52 | 53 | function findModule(pred, parent) { 54 | parent = parent || module; 55 | 56 | for (i = 0; i < parent.children.length; i ++) { 57 | if (pred(parent.children[i])) 58 | return parent.children[i]; 59 | } 60 | 61 | return parent.parent ? findModule(pred, parent.parent) : undefined; 62 | } 63 | 64 | // I see trouble ahead... 65 | function addLocalGulpTasks(subfile, submodule, tasks) { 66 | 67 | var gulpMod = findModule(function(mod) { 68 | return (path.basename(path.dirname(mod.id)) === 'gulp'); 69 | }, submodule); 70 | 71 | var localInst = gulpMod.exports; 72 | 73 | // copy all the tasks over 74 | for (var name in localInst.tasks) { 75 | if (localInst.tasks.hasOwnProperty(name)) { 76 | var task = localInst.tasks[name]; 77 | 78 | if (!task.__hubadded) { 79 | task.__hubadded = true; 80 | addSubtask(subfile, tasks, task.name, task.dep, task.fn); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/index-streams.js: -------------------------------------------------------------------------------- 1 | /* 2 | * gulp-hub 3 | * https://github.com/frankwallis/gulp-hub 4 | * 5 | * Copyright (c) 2014 Frank Wallis 6 | * Licensed under the MIT license. 7 | */ 8 | /// 9 | /// 10 | var gutil = require('gulp-util'); 11 | var through = require('through2'); 12 | var gulp = require('gulp'); 13 | var proxyquire = require('proxyquire'); 14 | 15 | var filelist = []; 16 | var tasks = {}; 17 | 18 | function eachFile(file, encoding, done) { 19 | var _stream = this; 20 | console.log('here'); 21 | 22 | if (file.isNull()) { 23 | _stream.emit('error', new gutil.PluginError('gulp-hub', 'file is null')); 24 | _stream.push(file); 25 | return done(); 26 | } 27 | 28 | if (file.isStream()) { 29 | _stream.emit('error', new gutil.PluginError('gulp-hub', 'Streaming not supported')); 30 | return done(); 31 | } 32 | 33 | console.log('Received ' + file.relative); 34 | 35 | //console.log('Received ' + JSON.stringify(file)); 36 | filelist.push(file.path); 37 | return done(); 38 | } 39 | 40 | function endStream(done) { 41 | gutil.log(gutil.colors.yellow('Loaded ' + filelist.length + ' gulpfile(s)')); 42 | 43 | if (filelist.length === 0) { 44 | return done(); 45 | } 46 | 47 | var originalTaskFn = gulp.Gulp.prototype.task; 48 | 49 | filelist.forEach(function (file, idx) { 50 | var directory = ''; 51 | 52 | gulp.Gulp.prototype.task = function (name, deps, task) { 53 | addSubtask(directory, idx, name, deps, task); 54 | }; 55 | 56 | proxyquire(file,{ 57 | gulp:gulp 58 | }); 59 | }); 60 | 61 | gulp.Gulp.prototype.task = originalTaskFn; 62 | 63 | for (var name in tasks) { 64 | if (tasks.hasOwnProperty(name)) { 65 | var task = tasks[name]; 66 | 67 | task.subtasks.forEach(function (subtask) { 68 | return gulp.add(subtask.name, subtask.deps, subtask.fn); 69 | }); 70 | 71 | // create the master task which is dependent on all of the subtasks 72 | gulp.add(name, task.subtasks.map(function (subtask) { 73 | return subtask.name; 74 | })); 75 | } 76 | } 77 | 78 | 79 | return done(); 80 | } 81 | 82 | function addSubtask(directory, idx, name, deps, task) { 83 | console.log('adding task ' + name); 84 | 85 | if (!tasks[name]) { 86 | tasks[name] = {}; 87 | tasks[name].subtasks = []; 88 | } 89 | 90 | var subname = name + idx; 91 | var subdeps = deps; 92 | 93 | if (Array.isArray(deps)) 94 | subdeps = deps.map(function (dep) { 95 | return dep + idx; 96 | }); 97 | 98 | tasks[name].subtasks.push({ name: subname, directory: directory, deps: subdeps, fn: task }); 99 | } 100 | 101 | function hub(glob) { 102 | console.log('here ' + glob); 103 | 104 | //gulp.src(glob).pipe(through.obj(eachFile, endStream)); 105 | gulp.src(glob).pipe(through.obj(function () { 106 | console.log('hello'); 107 | }, endStream)).on('data', function () { 108 | }); 109 | } 110 | 111 | module.exports = hub; 112 | //# sourceMappingURL=index.js.map 113 | -------------------------------------------------------------------------------- /lib/add-subtask.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var _ = require( 'lodash' ); 3 | var hutil = require('./hub-util'); 4 | 5 | var makeSubtaskFunction = function(subfile, subfn) { 6 | if (subfn.length > 0) { 7 | // subfn is asynchronous (has callback parameter) 8 | return function(cb) { 9 | // give the task function the correct working directory 10 | process.chdir(path.dirname(subfile.absolutePath)); 11 | return subfn(cb); 12 | }; 13 | } 14 | // subfn is synchronous (no callback parameter) 15 | return function() { 16 | // give the task function the correct working directory 17 | process.chdir(path.dirname(subfile.absolutePath)); 18 | return subfn(); 19 | }; 20 | }; 21 | 22 | /** 23 | * Adds a subfile's tasks, task dependencies, and callback to the task registry 24 | * under the master task name, e.g., given a task name `task-name`, and a 25 | * subfile like the following, 26 | * 27 | * { 28 | * absolutePath: /a/b/c.js, 29 | * relativepath: b/c.js, 30 | * uniqueName: b/c.js 31 | * } 32 | * 33 | * the task registry would gain an entry like the following, 34 | * 35 | * { 36 | * 'task-name': { 37 | * name: 'task-name', // master task name 38 | * subtasks: [ 39 | * { 40 | * 'name': 'b/c.js-task-name', // prefixed subtask name 41 | * 'param1': [ // prefixed subtask deps (optional) 42 | * 'b/c.js-task-name-dep-1', 43 | * 'b/c.js-task-name-dep-2', 44 | * ], 45 | * 'param2': function () { ... } // subtask callback 46 | * }, 47 | * ... // other subtask records from other subfiles 48 | * ] 49 | * }, 50 | * ... // other master tasks 51 | * } 52 | * 53 | * Note that subtask dependencies are optional, i.e., `param1` would contain 54 | * the subtask callback, and `param2` would be `undefined`. 55 | * 56 | * @param {object} subfile - A subfile in `get-subfiles` form 57 | * @param {object} tasks - The task registry. Will be modified! 58 | * @param {string} name - The task name 59 | * @param {array|function} param1 - Gulp task param 1, a task dependency list, or a function 60 | * @param {[function]} param2 - Gulp task param 2, a function (optional) 61 | * 62 | * @returns - The output of the task function 63 | */ 64 | module.exports = function(subfile, tasks, name, param1, param2) { 65 | 66 | // param validation 67 | if (!hutil.isValidHubFile(subfile)) { 68 | throw new TypeError('`subfile` must be a valid Gulp Hub file object.'); 69 | } 70 | if (!_.isPlainObject(tasks)) { 71 | throw new TypeError('`tasks` must be a plain object.'); 72 | } 73 | if (!_.isString(name)) { 74 | throw new TypeError('`name` must be a string.'); 75 | } 76 | if (!_.isArray(param1) && !_.isFunction(param1)) { 77 | throw new TypeError('`param1` must be an array or function.'); 78 | } 79 | if (!_.isFunction(param2) && !_.isUndefined(param2)) { 80 | throw new TypeError('`param2` must be a function or undefined.'); 81 | } 82 | 83 | // register a master task with this name 84 | if (!tasks[name]) { 85 | tasks[name] = {}; 86 | tasks[name].name = name; 87 | tasks[name].subtasks = []; 88 | } 89 | 90 | // give the subtask a unique name 91 | var subname = subfile.uniqueName + '-' + name; 92 | 93 | // sort out the task parameters 94 | var subparam1, subparam2; 95 | 96 | if (Array.isArray(param1)) { 97 | // translate the dependencies to their unique names 98 | subparam1 = param1.map(function (dep) { return subfile.uniqueName + '-' + dep; }); 99 | 100 | if (param2) { 101 | subparam2 = makeSubtaskFunction(subfile, param2); 102 | } 103 | else { 104 | subparam2 = undefined; 105 | } 106 | } 107 | else { 108 | subparam1 = makeSubtaskFunction(subfile, param1); 109 | subparam2 = undefined; 110 | } 111 | 112 | // add it to the master task 113 | tasks[name].subtasks.push({ "name": subname, "param1": subparam1, "param2": subparam2 }); 114 | }; 115 | -------------------------------------------------------------------------------- /test/index-spec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var _ = require( 'lodash' ); 3 | var should = require( 'should' ); 4 | var sinon = require( 'sinon' ); 5 | var pequire = require( 'proxyquire' ); 6 | 7 | // Happy-path proxyquire dependencies, i.e., dependencies that will allow 8 | // gulp-hub to complete without errors 9 | var HAPPY_PROXY_DEPS = { 10 | 'gulp-util': { 11 | log: _.noop, 12 | colors: { yellow: _.noop } 13 | }, 14 | './resolve-glob': function () { return [] }, 15 | './get-subfiles': function () { return [] }, 16 | './load-subfile': _.noop, 17 | './add-task': _.noop, 18 | }; 19 | 20 | // Proxyquire gulp-hub, optionally extending the happy-path proxy dependencies 21 | var getHub = function ( proxyDeps ) { 22 | return pequire( '../lib/index', _.assign( {}, HAPPY_PROXY_DEPS, proxyDeps ) ); 23 | }; 24 | 25 | describe( 'index', function () { 26 | 27 | it( 'is a function', function () { 28 | getHub().should.be.an.instanceOf( Function ); 29 | } ); 30 | 31 | it( 'takes a glob or an array of globs', function () { 32 | var hub = getHub(); 33 | var INVALID_VALUES = [ '', 0, 1, true, false, [], {}, { a: 'foo' }, null, undefined ]; 34 | 35 | // Assert we get an error for invalid values 36 | INVALID_VALUES.forEach( function ( testValue ) { 37 | hub.bind( null, testValue ).should.throw( 38 | 'A glob pattern or an array of glob patterns is required.' 39 | ); 40 | } ); 41 | 42 | // Assert we don't get an error for a valid glob (non-empty string) 43 | hub.bind( null, 'ok' ).should.not.throw(); 44 | } ); 45 | 46 | it( 'resolves a glob pattern to a file list', function () { 47 | var resolveGlobSpy = sinon.spy(function() { return []; }); 48 | var hub = getHub( { './resolve-glob': resolveGlobSpy } ); 49 | hub( 'test-pattern' ); 50 | resolveGlobSpy.calledOnce.should.be.true; 51 | resolveGlobSpy.calledWith( 'test-pattern' ); 52 | } ); 53 | 54 | it( 'creates a list of Gulp Hub files from a file list', function () { 55 | var spy = sinon.spy( HAPPY_PROXY_DEPS[ './get-subfiles' ] ); 56 | var hub = getHub( { 57 | './resolve-glob': function () { return ['resolve-glob-return'] }, 58 | './get-subfiles': spy 59 | } ); 60 | hub( 'test-pattern' ); 61 | spy.calledOnce.should.be.true; 62 | spy.calledWith( ['test/resolve-glob-return'] ).should.be.true; 63 | } ); 64 | 65 | it( 'logs each file it loads, path in yellow', function () { 66 | var logSpy = sinon.spy(); 67 | var colorSpy = sinon.spy( function ( s ) { return 'yellow-' + s } ); 68 | 69 | var hub = getHub( { 70 | 'gulp-util': { 71 | log: logSpy, 72 | colors: { yellow: colorSpy } 73 | }, 74 | './get-subfiles': function () { return [ 75 | { relativePath: 'rel-path-1' }, 76 | { relativePath: 'rel-path-2' } 77 | ] } 78 | } ); 79 | 80 | hub( 'test-pattern' ); 81 | 82 | logSpy.calledTwice.should.be.true; 83 | logSpy.calledWith( 'Loading', 'yellow-rel-path-1' ).should.be.true; 84 | logSpy.calledWith( 'Loading', 'yellow-rel-path-2' ).should.be.true; 85 | 86 | colorSpy.calledTwice.should.be.true; 87 | colorSpy.calledWith( 'rel-path-1' ).should.be.true; 88 | colorSpy.calledWith( 'rel-path-2' ).should.be.true; 89 | } ); 90 | 91 | it( 'loads each subfile', function () { 92 | var loadSpy = sinon.spy(); 93 | var hub = getHub( { 94 | './get-subfiles': function () { return [ 95 | { relativePath: 'rel-path-1' }, 96 | { relativePath: 'rel-path-2' } 97 | ] }, 98 | './load-subfile': loadSpy 99 | } ); 100 | hub( 'test-pattern' ); 101 | loadSpy.calledTwice.should.be.true; 102 | loadSpy.calledWith( { relativePath: 'rel-path-1' }, {} ).should.be.true; 103 | loadSpy.calledWith( { relativePath: 'rel-path-2' }, {} ).should.be.true; 104 | } ); 105 | 106 | it( 'adds each subfile\'s task', function () { 107 | var addTaskSpy = sinon.spy(); 108 | 109 | var hub = getHub( { 110 | './get-subfiles': function () { return [ 1, 2 ] }, 111 | './load-subfile': function ( subfile, tasks ) { 112 | tasks.a = 'foo'; 113 | tasks.b = 'bar'; 114 | }, 115 | './add-task': addTaskSpy 116 | } ); 117 | hub( 'test-pattern' ); 118 | 119 | addTaskSpy.calledTwice.should.be.true; 120 | addTaskSpy.calledWith( 'foo' ).should.be.true; 121 | addTaskSpy.calledWith( 'bar' ).should.be.true; 122 | } ); 123 | } ); 124 | -------------------------------------------------------------------------------- /test/add-subtask-spec.js: -------------------------------------------------------------------------------- 1 | var _ = require( 'lodash' ); 2 | var should = require( 'should' ); 3 | var sinon = require( 'sinon' ); 4 | var pequire = require( 'proxyquire' ); 5 | var tutil = require( './lib/test-util' ); 6 | 7 | var HAPPY_PROXY_DEPS = { 8 | path: { dirname: _.noop }, 9 | './hub-util': { isValidHubFile: function (){ return true } } 10 | }; 11 | 12 | var getAddSubtask = function ( proxyDeps ) { 13 | return pequire( '../lib/add-subtask', _.assign( {}, HAPPY_PROXY_DEPS, proxyDeps ) ); 14 | }; 15 | 16 | describe( 'add-subtask', function () { 17 | 18 | it( 'errors if subfile is not a valid Gulp Hub file', function () { 19 | var addSubtask = getAddSubtask( { 20 | './hub-util': { isValidHubFile: function () { return false } } 21 | } ); 22 | addSubtask.should.throw( '`subfile` must be a valid Gulp Hub file object.' ); 23 | } ); 24 | 25 | it( 'errors if the task registry is not a plain object', function () { 26 | var addSubtask = getAddSubtask(); 27 | tutil.getTypeExamples( _.isPlainObject ).forEach( function ( type ) { 28 | addSubtask.bind( null, undefined, type ) 29 | .should.throw('`tasks` must be a plain object.'); 30 | } ); 31 | } ); 32 | 33 | it( 'errors if name is not a string', function () { 34 | var addSubtask = getAddSubtask(); 35 | tutil.getTypeExamples( _.isString ).forEach( function ( type ) { 36 | addSubtask.bind( null, undefined, {}, type ) 37 | .should.throw('`name` must be a string.'); 38 | } ); 39 | } ); 40 | 41 | it( 'errors if param1 is not an array or function', function () { 42 | var addSubtask = getAddSubtask(); 43 | var excludeFunc = function ( el ) { 44 | return _.isArray( el ) || _.isFunction( el ); 45 | }; 46 | tutil.getTypeExamples( excludeFunc ).forEach( function ( type ) { 47 | addSubtask.bind( null, undefined, {}, 'string', type ) 48 | .should.throw('`param1` must be an array or function.'); 49 | } ); 50 | } ); 51 | 52 | it( 'errors if param2 is not a or function or undefined', function () { 53 | var addSubtask = getAddSubtask(); 54 | var excludeFunc = function ( el ) { 55 | return _.isFunction( el ) || _.isUndefined( el ); 56 | }; 57 | tutil.getTypeExamples( excludeFunc ).forEach( function ( type ) { 58 | addSubtask.bind( null, undefined, {}, 'string', [], type ) 59 | .should.throw('`param2` must be a function or undefined.'); 60 | } ); 61 | } ); 62 | 63 | it( 'registers a master task with `name` if it doesn\'t already exist', function () { 64 | var testTasks = {}; 65 | var addSubtask = getAddSubtask(); 66 | addSubtask( { uniqueName: 'unique-name' }, testTasks, 'task-name', [] ); 67 | 68 | var taskObj = testTasks[ 'task-name' ]; 69 | _.isPlainObject( taskObj ).should.be.true; 70 | taskObj.name = 'task-name'; 71 | _.isArray( taskObj.subtasks ).should.be.true; 72 | } ); 73 | 74 | it( 'skips task registry record initialization if `name` already exists', function () { 75 | var TASK_NAME = 'task-name'; 76 | var testTasks = {}; 77 | testTasks[ TASK_NAME ] = { 78 | name: TASK_NAME, 79 | subtasks: [] 80 | }; 81 | var addSubtask = getAddSubtask(); 82 | addSubtask( { uniqueName: 'unique-name' }, testTasks, TASK_NAME, [] ); 83 | 84 | var taskObj = testTasks[ TASK_NAME ]; 85 | _.isPlainObject( taskObj ).should.be.true; 86 | taskObj.name = TASK_NAME; 87 | _.isArray( taskObj.subtasks ).should.be.true; 88 | } ); 89 | 90 | it( 'registers the subfile\'s tasks prefixed with its unique name under the master task name', function () { 91 | var testTasks = {}; 92 | var addSubtask = getAddSubtask(); 93 | addSubtask( { uniqueName: 'unique-name' }, testTasks, 'task-name', [] ); 94 | testTasks[ 'task-name' ].subtasks[ 0 ].name.should.eql( 'unique-name-task-name' ); 95 | } ); 96 | 97 | it( 'prefixes the subfile\'s task dependencies with its unique name', function () { 98 | var testTasks = {}; 99 | var addSubtask = getAddSubtask(); 100 | addSubtask( 101 | { uniqueName: 'subfile-unique-name' }, testTasks, 'task-name', 102 | [ 'task-dep-1', 'task-dep-2' ] 103 | ); 104 | var subtaskDeps = testTasks[ 'task-name' ].subtasks[ 0 ].param1; 105 | subtaskDeps[ 0 ].should.eql( 'subfile-unique-name-task-dep-1' ); 106 | subtaskDeps[ 1 ].should.eql( 'subfile-unique-name-task-dep-2' ); 107 | } ); 108 | 109 | it( 'wraps the subtask\'s callbacks in a function that corrects the working directory', function () { 110 | var testTasks = {}; 111 | var callbackSpy = sinon.spy(); 112 | var dirnameSpy = sinon.spy( function () { return '.' } ); 113 | 114 | var addSubtask = getAddSubtask( { path: { dirname: dirnameSpy } } ); 115 | addSubtask( 116 | { uniqueName: 'subfile-unique-name', absolutePath: '.' }, 117 | testTasks, 'task-name', callbackSpy 118 | ); 119 | testTasks[ 'task-name' ].subtasks[ 0 ].param1(); 120 | 121 | dirnameSpy.calledOnce.should.be.true; 122 | dirnameSpy.calledWith( '.' ).should.be.true; 123 | callbackSpy.calledOnce.should.be.true; 124 | 125 | dirnameSpy.reset(); 126 | callbackSpy.reset(); 127 | 128 | addSubtask( 129 | { uniqueName: 'subfile-unique-name-2', absolutePath: '.' }, 130 | testTasks, 'task-name-2', [], callbackSpy 131 | ); 132 | testTasks[ 'task-name-2' ].subtasks[ 0 ].param2(); 133 | 134 | dirnameSpy.calledOnce.should.be.true; 135 | dirnameSpy.calledWith( '.' ).should.be.true; 136 | callbackSpy.calledOnce.should.be.true; 137 | } ); 138 | } ); 139 | --------------------------------------------------------------------------------