├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── contrib ├── examples │ ├── deg-to-rad.js │ ├── pi.js │ ├── rad-to-deg.js │ ├── to-deg.js │ └── to-rad.js ├── get-path.js ├── help.js ├── running-tasks.js └── standard-task.js ├── gulpfile.js ├── index.js ├── lib ├── dependency-error.js ├── gulp-di.js ├── normalize-path.js ├── parse-argument.js ├── read-directory.js └── resolver.js ├── modules └── paths.js ├── package-lock.json ├── package.json ├── tasks ├── default.js ├── examples.js ├── mocha.js └── semistandard.js └── test ├── 00.js ├── 01-parse-argument.js ├── 02-resolver.js ├── 03-read-directory.js ├── 10-gulp-di.js ├── 11-builtin.js └── 20-contrib.js /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "lts/*" 4 | - "node" 5 | - "12" 6 | - "11" 7 | - "10" 8 | - "9" 9 | 10 | env: 11 | - ENV="development" 12 | 13 | before_script: 14 | - npm install -g gulp 15 | 16 | install: 17 | - npm install 18 | 19 | after_success: 'npm run coveralls' 20 | 21 | script: npm test -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.1.0 - 11/27/2019 2 | 3 | - Adapting to changes in gulp 4.0 (#9) 4 | - Removing CI tests for older Node.js versions below 5.x. 5 | - Removing support for Node below 9.x. 6 | 7 | 0.0.4 - 11/11/2016 8 | 9 | - task() and module() may take absolute or relative paths 10 | - using a fork of [node-introspect](https://github.com/orzarchi/node-introspect.git) instead of [parse-function](https://github.com/tunnckoCore/parse-function) 11 | - adopting [https://github.com/Flet/semistandard](semistandard) style 12 | 13 | 0.0.33 - 05/03/2016 14 | 15 | - Updating depencendies and adapting to changes 16 | 17 | 0.0.32 - 04/02/2016 18 | 19 | - Correcting an example in this file 20 | 21 | 0.0.31 - 03/13/2016 22 | 23 | - options.argv for "runningTasks" test 24 | - options.parentDir 25 | - updating documentation 26 | - extending test suite, adding code coverage report 27 | 28 | 0.0.3 - 03/12/2016 29 | 30 | - ES2015 rewrite 31 | - CI using Travis 32 | - using and providing [lodash](https://github.com/lodash/lodash) by default 33 | - Adding "standardTask" and "getPath" helpers 34 | - supporting [gulp-load-plugins](https://www.npmjs.com/package/gulp-load-plugins)'s lazy loading of gulp plugins 35 | 36 | 0.0.2 - 02/01/2016 37 | 38 | - Updating dependencies 39 | - Adding "runningTasks" helper function 40 | - Exposing gulp-util as "gutil" by default 41 | - Parsing of multi-line comments rewritten 42 | - added new options : noModules, noBuiltin, noRunningTasks 43 | - ES2015+ support with [parse-function](https://github.com/tunnckoCore/parse-function) 44 | 45 | 0.0.1 - 12/20/2015 46 | 47 | - Initial release, incorporating Resolver and tests from [dijs](https://www.npmjs.com/package/dijs). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [gulp](http://gulpjs.com)-di 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | Build Status 10 | 11 | 12 | npm version 13 | 14 | 15 | npm licence 16 | 17 | 18 | Code coverage 19 | 20 | 21 | ECMAScript 2015 22 | 23 |
24 | 25 | gulp-di is a dependency injection framework for the [Gulp](http://gulpjs.com) 26 | streaming build system. 27 | 28 | - Shrink your Gulpfile to less than ten lines. 29 | - Manage gulp plugins solely via npm and variables in your task module files. 30 | - No major refactoring required. 31 | 32 | See below for further examples! 33 | 34 | ## Installation 35 | 36 | ````bash 37 | $ npm install --save gulp-di 38 | ```` 39 | 40 | ## Built-in dependencies 41 | 42 | You can use the following dependencies in your modules and tasks. 43 | 44 | | Name | Type | Description | 45 | |--------------|----------|-----------------------------------------------| 46 | | _ | Function | [lodash](https://github.com/lodash/lodash) | 47 | | basePath | Function | Resolves a path relative to your project path | 48 | | chalk | Object | [chalk](https://www.npmjs.com/package/chalk) for using colors when logging | 49 | | gulp | Object | The gulp instance | 50 | | gutil | Object | [gulp-util](https://www.npmjs.com/package/gulp-util) | 51 | | log | Function | [gulp-util](https://www.npmjs.com/package/gulp-util)'s log | 52 | | Package | Object | package.json as object | 53 | | runningTasks | Array | currently running gulp tasks (experimental) | 54 | | taskInfo | Object | infos about task functions (experimental) | 55 | 56 | In addition, all Gulp modules which are installed in your package.json are 57 | available in your modules, driven by [gulp-load-plugins](https://www.npmjs.com/package/gulp-load-plugins). 58 | 59 | For example, if you install the "gulp-concat" as devDependency (in your 60 | package.json) plugin using ... 61 | 62 | ````bash 63 | $ npm install --save-dev gulp-concat 64 | ```` 65 | 66 | ... it will become available as dependency in each module file: 67 | 68 | ````js 69 | // tasks/concat-texts.js 70 | module.exports = (gulp, concat) => { 71 | /* Concatenates all *.txt files from src/ to public/result.txt */ 72 | return gulp.src('src/**/*.txt') 73 | .pipe(concat('result.txt')) 74 | .pipe(gulp.dest('public/')); 75 | }; 76 | ```` 77 | Please read the API notes below for configuring a pattern for packages which 78 | do not start with "gulp-" or "gulp.*". 79 | 80 | ## Example: Basic refactoring 81 | 82 | Instead of declaring all gulp tasks in a single file, gulp-di allows you to 83 | take a more modular approach. You can separate your stream definitions from 84 | configurations while gulp-di handles the majority of your existing calls to 85 | require() for you. 86 | 87 | In this example, we will see how we can move a call to gulp.task() to a 88 | separate file. 89 | 90 | Additionally, we will see how we can detach a constant (in this case: a glob 91 | matching all files with the "jpg" extension) from the actual task. 92 | 93 | The following task 94 | 95 | ````js 96 | /* gulpfile.js (previous version) */ 97 | 98 | const gulp = require('gulp'); 99 | gulp.task('images', () => { 100 | return gulp.src('./**/*.jpg') 101 | .pipe(gulp.dest('output/')); 102 | }); 103 | ```` 104 | 105 | would be re-factored to the file at tasks/images.js as following. 106 | 107 | ````js 108 | /* tasks/images.js */ 109 | 110 | module.exports = (gulp, imagesPath) => { 111 | gulp.task('images', () => { 112 | return gulp.src(imagesPath) 113 | .pipe(gulp.dest('output/')); 114 | }); 115 | }; 116 | ```` 117 | 118 | Notice that the function uses the "imagePath" constant. Such constants can 119 | be defined in your Gulpfile files in order to separate them from the tasks. 120 | 121 | Thus, you can now use all of your Functional programming skills with Gulp and 122 | **assemble** your tasks from other declarations and build much more 123 | flexible and re-usable tasks. 124 | 125 | gulp-di should help you to reduce your Gulpfile's complexity. 126 | In this example, we will declare additionally the "imagePath" constant which is 127 | being used in our "images" task. 128 | 129 | ````js 130 | /* gulpfile.js (refactored version) */ 131 | 132 | const gulp = require('gulp'); 133 | let di = require('gulp-di')(gulp); 134 | .tasks('./tasks') 135 | .provide('imagesPath', 'src/**/*.jpg') 136 | .resolve(); 137 | ```` 138 | 139 | Additionally, you can now "inject" arbitrary values into your functions, e. g. 140 | run-time configurations, asynchronous methods for usage with Gulp's asynchronous 141 | API, constants etc. 142 | 143 | The following example uses constants and modules in order to compose a helper 144 | function. 145 | 146 | ````js 147 | /* gulpfile.js (refactored version) */ 148 | 149 | const gulp = require('gulp'); 150 | let di = require('gulp-di')(gulp); 151 | .tasks('./tasks') 152 | .provide('sourcePath', 'src') // Provide a string constant. 153 | .module('extensionPath', (sourcePath) => { // Add a module providing a 154 | // helper function to get a 155 | // extension name. 156 | 157 | return (extname) => `${sourcePath}/**/*.${extname}`; 158 | 159 | }) 160 | .task(function (gulp, extensionPath) { 161 | gulp.task('copy-images', function () { 162 | // Copies all *.jpg images from src/ to public/ 163 | return gulp.src(extensionPath('jpg')).pipe(gulp.dest('public/')); 164 | }); 165 | }) 166 | .resolve(); 167 | ```` 168 | 169 | ## API 170 | 171 | ### GulpDI(_object_ gulp, _object_ options) 172 | 173 | Creates a new GulpDI instance. The first argument must always be the gulp 174 | instance from your Gulpfile. 175 | 176 | If you specify options, these will be passed to [gulp-load-plugins](https://www.npmjs.com/package/gulp-load-plugins) under the hood ("camelize" cannot be configured at the moment). 177 | 178 | ```js 179 | const gulp = require('gulp'); 180 | let di = GulpDI(gulp, { 181 | DEBUG: false, // when set to true, the plugin will log info to console. Useful for bug reporting and issue debugging 182 | pattern: ['gulp-*', 'gulp.*'], // the glob(s) to search for 183 | config: 'package.json', // where to find the plugins, by default searched up from process.cwd() 184 | scope: ['dependencies', 'devDependencies', 'peerDependencies'], // which keys in the config to look within 185 | replaceString: /^gulp(-|\.)/, // what to remove from the name of the module when adding it to the context 186 | rename: {}, // a mapping of plugins to rename 187 | renameFn: function (name) { ... } // a function to handle the renaming of plugins (the default works) 188 | }); 189 | ``` 190 | The following options are additionally available: 191 | 192 | | Name | Type | Description | 193 | |------------------|----------|-----------------------------------------------| 194 | | DEBUG | Boolean | Whether to log debugging information or not | 195 | | noBuiltin | Boolean | Disables helpers: basePath, log, Package | 196 | | noHelp | Boolean | Toggles the "help" task/providing taskInfo | 197 | | noModules | Boolean | Do not expose chalk, gutil and lodash (_) | 198 | | noRunningTasks | Boolean | Do not provide the "runningTasks" function | 199 | 200 | ### Chainable methods 201 | 202 | The following methods are chainable: 203 | 204 | #### .inject(_function_ fn, returnValue) 205 | 206 | Executes code which depends on the current's instance dependencies. 207 | Please note that you will have to resolve() your instance if you should depend 208 | on anything else than payloads which have been initialized by provide(). 209 | 210 | When returnValue is set, this method returns the return value of the injected 211 | function (e.g. the function is not chainable when set to true) 212 | 213 | ````js 214 | di.provide({ 'PI' : Math.PI, 'DEG_TO_RAD' : 180 / Math.PI }); 215 | let circle = di.inject(['PI','DEG_TO_RAD', (PI, DEG_TO_RAD) => { 216 | return 2 * PI * DEG_TO_RAD; 217 | }], true); 218 | console.log(circle); // 360 219 | ```` 220 | 221 | #### .provide(_string_ name, _*_ payload) 222 | 223 | Provides a constant for further usage in modules and tasks. You can can also 224 | provide a hashmap of constants as following: 225 | ````js 226 | di.provide({ 'PI' : Math.PI, 'SIN' : Math.sin }); 227 | ```` 228 | 229 | #### .task(_function_ fn) 230 | 231 | Adds a task module. Add your provide()d dependencies to the function's argument 232 | list to require a dependency. Their order does not matter. 233 | 234 | Please note that tasks don't have names (in contrast to modules) as 235 | you shouldn't depend on them using gulp-di. Use the "deps" array when calling 236 | gulp.task() instead in order to achieve a specific task execution order. 237 | 238 | You can also use a hashmap as following: 239 | 240 | ````js 241 | di.task({ 242 | /* Copies all *.png images from src/ to public/ */ 243 | assets : (gulp) => gulp.src('src/**/*.png').pipe(gulp.dest('public/')) 244 | }); 245 | ```` 246 | 247 | #### .module(_string_ name,_function_ fn) 248 | 249 | Adds a module with the given name. In contrast to a task, a module has a name 250 | and its return value will be provided - thus, modules can be injected into tasks 251 | as well as your constants. 252 | 253 | You can provide a hashmap, too. 254 | 255 | #### .resolve() 256 | 257 | Resolves all dependencies and runs all task modules. You need to call this 258 | method after your declarations. 259 | 260 | #### .tasks(_string_ directory) 261 | 262 | Loads all tasks from the specified directory, using [require-dir](https://www.npmjs.com/package/require-dir). 263 | 264 | #### .modules(_string_ directory) 265 | 266 | Loads all modules from the specified directory. 267 | 268 | #### .byId(_string_ name) 269 | 270 | Returns the specified dependency. When you are demanding modules, please keep in 271 | mind that you will need to call resolve() beforehand, otherwise, this would 272 | return your module function. 273 | 274 | ## Using dependency injection in general 275 | 276 | By design, dependency injection is opt-in for everything, thus you can use 277 | gulp-di for use cases beyond your gulp tasks. 278 | 279 | ### Setting values 280 | 281 | When using DI, you can set values on your instance to arbitrary values. They 282 | will become available in your module files. 283 | 284 | ````js 285 | const gulp = require('gulp'); 286 | let di = require('gulp-di')(gulp, { DEBUG: false }); 287 | di.provide('PI', Math.PI); 288 | di.provide('RAD_TO_DEG', 180 / Math.PI); 289 | ```` 290 | 291 | ### Declaring task modules 292 | 293 | You can then declare a single function or a whole directory of 294 | CommonJS modules using the tasks() method. 295 | 296 | ````js 297 | di.task((PI, RAD_TO_DEG, basePath) => { 298 | console.log((PI * 2 * RAD_TO_DEG) + '°'); 299 | }); 300 | ```` 301 | 302 | Note that you have access to the gulp-di instance in the task function's scope. 303 | You could f.e. access the "options" object in your function as following: 304 | 305 | ````js 306 | di.task(() => { 307 | if (this.options.DEBUG) { console.log('Debug is enabled'); } 308 | }); 309 | ```` 310 | 311 | ### Running all modules and resolving dependencies 312 | 313 | You need to execute all provided values and modules by running resolve() 314 | afterwards. 315 | 316 | While you place calls to task(), tasks() and provide, the execution order of your 317 | depends on the declared dependencies. With resolve(), the graph is being 318 | resolved and all modules are called with the provided values. 319 | 320 | ````js 321 | di.resolve(); 322 | // logs 360° 323 | ```` 324 | 325 | ## Experimental functionalities 326 | 327 | Please use these features with caution for now. 328 | 329 | ### gulp help 330 | 331 | This example is included by default. 332 | 333 | An included experiment uses the API to intersect all calls to gulp.task to 334 | collect information about the tasks. 335 | 336 | It registers a gulp "help" task which logs then all comments included in 337 | gulp.task. In addition, a "taskInfo" hashmap is available for injection. It 338 | contains the information about your gulp tasks which is being also used by 339 | the "help" task. 340 | 341 | ````js 342 | const gulpDi = require('gulp-di'); 343 | let di = gulpDi() 344 | .task(function (gulp, taskInfo, log, chalk) { 345 | gulp.task('my-help', function () { 346 | /* Logs basic information about tasks. */ 347 | for (let name in taskInfo) { 348 | let entry = taskInfo[name]; 349 | log(chalk.magenta(name), 'with dependencies', entry.deps); 350 | } 351 | }); 352 | }) 353 | .resolve(); 354 | ```` 355 | ### runningTasks 356 | 357 | This function returns an array of strings, containing all current Gulp tasks, 358 | including dependencies. 359 | 360 | ### Helper functions 361 | 362 | The following functions are currently not integrated into gulp-di, but you can 363 | require them as following: 364 | 365 | ````js 366 | const getPath = require('gulp-di/contrib/get-path'); 367 | const standardTask = require('gulp-di/contrib/standard-task'); 368 | ```` 369 | 370 | #### getPath(_string_ key) 371 | 372 | Returns a property specified by the given dot-delimited key: 373 | 374 | ````js 375 | const data = { 376 | at : 1350506782000, 377 | ids : ['507f1f77bcf86cd799439011', '507f191e810c19729de860ea'] 378 | }; 379 | 380 | let PI = getPath(Math, 'PI'); 381 | let at = getPath(data, 'at')); 382 | let firstId = getPath(data, 'ids.0'); 383 | ```` 384 | 385 | #### standardTask(_string_ name, _string_ src, _string_ dest, ...) 386 | 387 | Generates a default task, assuming you want to pipe _src_ through the plugin specified by _name_ to _dest_. All parameters following "dest" are serialized if necessary and passed 388 | to the plugin. 389 | 390 | Using this method, you will just need to install a gulp plugin with npm and set 391 | up a task as following: 392 | 393 | ````js 394 | let taskFn = standardTask('jade', 'templates/**/*.jade', 'public', { pretty : false }); 395 | 396 | // Register the task function 397 | di.task(taskFn); 398 | 399 | console.log(taskFn.toString()); 400 | 401 | function jadeTask(gulp, jade) { 402 | /** 403 | * Generated task function, registers a "jade" task. 404 | * @param {object} gulp The current gulp instance 405 | * @param {jade} jade The "jade" gulp plugin 406 | */ 407 | 408 | gulp.task("jade", () => { 409 | /** 410 | * jade task, declared at 10-contrib.js:44 411 | */ 412 | return gulp.src("templates/**/*.jade") 413 | .pipe(jade({"pretty":false})) 414 | .pipe(gulp.dest("public")); 415 | }); 416 | } 417 | ```` 418 | 419 | ## FAQ 420 | 421 | ### How do I insert tasks? Gulp claims "Task 'default' is not in your gulpfile" all the time! 422 | 423 | Please call the resolve() method after all of your module and task declarations. 424 | 425 | ### gulp-di does not find my module! 426 | 427 | In order to make your gulp plugins available, gulp-di uses 428 | [gulp-load-plugins](https://www.npmjs.com/package/gulp-load-plugins) internally 429 | by default. 430 | 431 | The default pattern is ['gulp-*', 'gulp.*', '!gulp-di']. You need to initialize 432 | gulp-di using the "options" object in order to make plugins with arbitrary names 433 | available: 434 | 435 | ````js 436 | const gulp = require('gulp'); 437 | let di = require('gulp-di')(gulp, { 438 | pattern : ['gulp-*', 'gulp.*', '!gulp-di', 'webpack-stream'] 439 | }); 440 | 441 | di.task((webpackStream) => { 442 | gulp.task('webpack', () => { 443 | return gulp.src('src/client.js') 444 | .pipe(webpackStream({ output : { filename : '[name].js' }})) 445 | .pipe(gulp.dest('public')); 446 | }); 447 | }); 448 | 449 | di.resolve(); 450 | 451 | ```` 452 | 453 | ### Is it possible to add tasks asynchronously? 454 | 455 | No, you will need to set up asynchronous tasks which depend on each other in 456 | order to perform asynchronous tasks. Nevertheless, all tasks must be declared 457 | synchronously in order to use Gulp correctly. 458 | 459 | ### I dislike the camelCaseName of my favorite module, can I change it? 460 | 461 | Using [gulp-load-plugins](https://www.npmjs.com/package/gulp-load-plugins), you 462 | can change the assigned name for arbitrary modules. 463 | 464 | #### Using a ``rename`` object: 465 | 466 | ````js 467 | let di = require('gulp-di')(gulp, { 468 | rename : { 469 | webpackStream : 'webpack' 470 | } 471 | }); 472 | ```` 473 | 474 | #### Using a ``renameFn``: 475 | ````js 476 | let replacements = { webpackStream : 'webpack'}; 477 | let di = require('gulp-di')(gulp, { 478 | renameFn : (key) => return replacements[key] 479 | }); 480 | ```` 481 | 482 | ## License 483 | 484 | MIT 485 | -------------------------------------------------------------------------------- /contrib/examples/deg-to-rad.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (PI) { 4 | return PI / 180; 5 | }; 6 | -------------------------------------------------------------------------------- /contrib/examples/pi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function () { 4 | return Math.PI; 5 | }; 6 | -------------------------------------------------------------------------------- /contrib/examples/rad-to-deg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (PI) { 4 | return 180 / PI; 5 | }; 6 | -------------------------------------------------------------------------------- /contrib/examples/to-deg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (RAD_TO_DEG) { 4 | return (radians) => radians * RAD_TO_DEG; 5 | }; 6 | -------------------------------------------------------------------------------- /contrib/examples/to-rad.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (DEG_TO_RAD) { 4 | return (degrees) => degrees * DEG_TO_RAD; 5 | }; 6 | -------------------------------------------------------------------------------- /contrib/get-path.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @method getPath 5 | * @param {string} key 6 | * @returns {*|null} 7 | */ 8 | 9 | function getPath (obj, key) { 10 | key = typeof key !== 'undefined' ? key : null; 11 | 12 | if (key === null) { 13 | return obj; 14 | } else if (/\./.test(key) === false) { 15 | return obj[key]; 16 | } 17 | 18 | /** 19 | * Returns a payload or a nested property of any provided payload. 20 | * @method step 21 | * @param {*} object 22 | * @param {string[]} depPath 23 | * @private 24 | */ 25 | 26 | function step (object, depPath) { 27 | return depPath.length ? step(object[depPath.shift()], depPath) : object; 28 | } 29 | 30 | const depPath = key.split(/\./g); 31 | return step(obj, depPath); 32 | } 33 | 34 | module.exports = getPath; 35 | -------------------------------------------------------------------------------- /contrib/help.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const pad = require('pad'); 3 | const LINE_PADDING = 5; 4 | 5 | module.exports = function HelpTask (gulp) { 6 | /** 7 | * Modifies the gulp.task() function and builds a list of all tasks. 8 | * 9 | * Adds a Gulp "help" task which lists them with additional information from 10 | * function comments. 11 | * 12 | * Please use it as following: 13 | * 14 | * ````js 15 | * module.exports = function (gulp) { 16 | * // Copies ./README.md to ./tmp 17 | * 18 | * return gulp.src('./README.md') 19 | * .pipe(gulp.dest('./tmp')) 20 | * } 21 | * ```` 22 | * 23 | * This module needs to be injected before each successive require('gulp') 24 | * as it overwrites the "gulp.task" function. 25 | * 26 | * As such modifications might lead to unexpected behavior, this module is 27 | * experimental. 28 | */ 29 | 30 | const Package = this.byId('Package', true) || {}; 31 | const chalk = this.byId('chalk', true); 32 | const log = this.byId('log', true) || console.log.bind(console); 33 | 34 | const parseFn = require('parse-function'); 35 | const extractComments = require('extract-comments'); 36 | const _task = gulp.task; 37 | const taskInfo = {}; 38 | const RGX_LF = /\r\n|\r|\n/g; 39 | const DEBUG = this.options.DEBUG; 40 | 41 | if (DEBUG) { 42 | log('Wrapping gulp.task() "gulp help" task...'); 43 | } 44 | 45 | // Allows to use this hashmap as dependency 46 | 47 | this.provide('taskInfo', taskInfo); 48 | 49 | /** 50 | * Wraps the gulp.task() method, registers information about the provided 51 | * tasks. 52 | * @method task 53 | * @param {string} id 54 | * @param {string[]} deps 55 | * @param {function} fn 56 | */ 57 | 58 | gulp.task = function (id, deps, fn) { 59 | const args = [].slice.apply(arguments); 60 | 61 | if (typeof deps === 'function') { 62 | fn = arguments[1]; 63 | deps = []; 64 | } 65 | 66 | let entry = null; 67 | 68 | if (typeof fn === 'function') { 69 | const info = parseFn(fn.toString()); 70 | entry = { name: id }; 71 | entry.description = `Runs the ${id} task (no description)`; 72 | const comments = extractComments(info.body, {first: true}); 73 | if (comments.length) { 74 | const comment = comments[0]; 75 | const lines = comment.raw 76 | .split(RGX_LF) 77 | .map(function (line) { 78 | return line.replace(/( )*(\*|\/+)/g, ''); 79 | }); 80 | entry.description = lines; 81 | fn.description = lines; 82 | } 83 | } 84 | if (entry) { 85 | entry.deps = deps; 86 | if (DEBUG) { 87 | let line = [`Adding ${chalk.cyan(entry.name)} task`]; 88 | if (deps.length) { 89 | line.push(` - depending on ${chalk.magenta(deps.join(' '))}`); 90 | } 91 | log.apply(chalk, line); 92 | } 93 | taskInfo[id] = entry; 94 | } 95 | return _task.apply(gulp, args); 96 | }; 97 | 98 | // Registers the "help" task. 99 | 100 | function HelpTask (done) { 101 | /* Prints an overview over all available Gulp tasks. */ 102 | 103 | let lines = ['']; 104 | const paddingStr = new Array(LINE_PADDING).join(' '); 105 | 106 | lines.push(' ' + Package.name + ' ' + Package.version, ''); 107 | 108 | if (Package.description && Package.description.length) { 109 | lines.push(' ' + Package.description, ''); 110 | } 111 | 112 | const taskIds = Object.keys(taskInfo); 113 | const taskLengths = taskIds.map(function (id) { return id.length; }); 114 | const maxLength = Math.max.apply(Math, taskLengths); 115 | const keys = Object.keys(taskInfo); 116 | keys.sort(); 117 | for (var i = 0; i < keys.length; i++) { 118 | const key = keys[i]; 119 | const entry = taskInfo[key]; 120 | const paddingLength = maxLength; 121 | const str = new Array(paddingLength + 2).join(' '); 122 | let descriptionLine = `Runs the ${key} task.`; 123 | if (entry.description) descriptionLine = mapDescription(str, entry.description); 124 | lines.push(' ' + pad(entry.name, maxLength) + paddingStr + descriptionLine.join('\n').trim()); 125 | lines.push(''); 126 | } 127 | 128 | lines.forEach((line) => console.log(line)); 129 | 130 | done(); 131 | /** 132 | * Maps the given array of comment lines by prepending whitespace if 133 | * necessary. 134 | * @method mapDescription 135 | * @param {string} str 136 | * @param {string[]} lines 137 | * @return {string[]} 138 | * @private 139 | */ 140 | 141 | function mapDescription (str, lines) { 142 | if (typeof lines === 'string') lines = [lines]; 143 | return lines.map(function (line, index) { 144 | line = line.trim(); 145 | return index ? str + paddingStr + line : line; 146 | }); 147 | } 148 | } 149 | 150 | HelpTask.description = 'Displays initial help.'; 151 | gulp.task('help', HelpTask); 152 | }; 153 | -------------------------------------------------------------------------------- /contrib/running-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function RunningTasks (gulp) { 4 | const DEBUG = this.options.DEBUG; 5 | const log = this.byId('log', true) || console.log.bind(console); 6 | const gutil = this.byId('gutil', true) || { env: { _: process.argv } }; 7 | 8 | /** 9 | * Adds a function returning an array of strings, containing all current 10 | * Gulp tasks, including dependencies. 11 | * 12 | * Use it as following: 13 | * 14 | * ````js 15 | * module.exports = function (gulp, log, Package, runningTasks) { 16 | * gulp.task('info', function () { 17 | * log('Building ' + chalk.magenta(Package.name) + ', running: ' + chalk.cyan(runningTasks().join(' '))) 18 | * }) 19 | * } 20 | * ```` 21 | * 22 | * This module is currently experimental. 23 | */ 24 | 25 | if (DEBUG) { 26 | log('Adding runningTasks helper...'); 27 | } 28 | 29 | this.provide('runningTasks', () => { 30 | const registry = gulp._registry; 31 | const args = this.options.argv || gutil.env._; 32 | const taskNames = Object.keys(registry.tasks()); 33 | // Filter all available task names using gutil.env._ 34 | const cliTasks = taskNames.filter((name) => args.indexOf(name) !== -1); 35 | return cliTasks; 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /contrib/standard-task.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-eval */ 2 | 'use strict'; 3 | 4 | const parseStack = require('parse-stack'); 5 | const _ = require('lodash'); 6 | const path = require('path'); 7 | 8 | /** 9 | * Generates a default task, assuming you want to pipe "src" through the plugin 10 | * specified by "name" to "dest". All following parameters are serialized if 11 | * necessary and passed on to the plugin. 12 | * 13 | * @method standardTask 14 | * @param {string} name 15 | * @param {string} src 16 | * @param {string} dest 17 | * @returns {function} 18 | */ 19 | 20 | function standardTask (name, src, dest) { 21 | let args = _.toArray(arguments).slice(3); // dependencies 22 | let stack = null; 23 | let filename = ''; 24 | let line = null; 25 | 26 | try { 27 | throw new Error(''); 28 | } catch (e) { 29 | stack = parseStack(e); 30 | } 31 | 32 | if (stack && stack.length > 1) { 33 | let entry = stack[1]; 34 | filename = path.basename(entry.filepath); 35 | line = entry.lineNumber; 36 | } 37 | 38 | // Serialize arguments if necessary 39 | 40 | try { 41 | args = args.map(JSON.stringify).join(', '); 42 | } catch (e) { 43 | console.log(`Could not parse ${args.length} arguments:\n\n${e.stack}`); 44 | args = []; 45 | } 46 | 47 | if (!args.length) { 48 | args = null; 49 | } 50 | 51 | let fnName = _.camelCase(name); 52 | let body = ` 53 | 54 | /** 55 | * Generated task function, registers a "${name}" task. 56 | * @param {object} gulp The current gulp instance 57 | * @param {function} ${name} The "${name}" gulp plugin 58 | */ 59 | 60 | gulp.task("${name}", () => { 61 | /** 62 | * ${name} task, declared at ${filename}${line ? ':' + line : ''} 63 | */ 64 | 65 | let plugin = this.byId("${name}"); 66 | 67 | if (typeof plugin !== "function") { 68 | throw new Error("plugin '${name}' is not a function"); 69 | } 70 | 71 | return gulp.src("${src}") 72 | .pipe(plugin(${args || 'void 0'})) 73 | .pipe(gulp.dest("${dest}")); 74 | }); 75 | `; 76 | let taskFn = eval(`function ${fnName}Task (gulp) { ${body} }; ${fnName}Task;`); 77 | return taskFn; 78 | } 79 | 80 | module.exports = standardTask; 81 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This is currently solely an example on how you could use 5 | * gulp-di in many ways. 6 | * 7 | * Try running 8 | * 9 | * $ gulp 10 | * $ gulp help 11 | * $ gulp log-path 12 | * $ gulp b info wait 13 | */ 14 | 15 | const gulp = require('gulp'); 16 | let di = require('./')(gulp, { 17 | // DEBUG: true 18 | }) 19 | .modules('./modules') 20 | .tasks('./tasks') 21 | // .task(function (noDef) { 22 | // // This should fail if you un-comment it as `noDef` hasn't been defined. 23 | // }) 24 | .resolve(); 25 | 26 | // Using gulp's task(), comments will become available with "gulp help". 27 | // 28 | // In order to use this feature, you'll need to call gulp.task() AFTER 29 | // gulp-di though. 30 | 31 | gulp.task('wait', (done) => { 32 | /** 33 | * Waits for one second, features a 34 | * multi-line comment (see gulpfile.js). 35 | */ 36 | di.inject(() => setTimeout(done, 1e3)); 37 | }); 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/gulp-di'); 2 | -------------------------------------------------------------------------------- /lib/dependency-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @method DependencyError 5 | * @private 6 | * @param {string} id 7 | */ 8 | 9 | const DependencyError = function (id) { 10 | return new Error('Unknown dependency: ' + id); 11 | }; 12 | 13 | module.exports = DependencyError; 14 | -------------------------------------------------------------------------------- /lib/gulp-di.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (function () { 4 | const $parseArg = require('./parse-argument'); 5 | const _ = require('lodash'); 6 | const chalk = require('chalk'); 7 | const findup = require('findup-sync'); 8 | const gutil = require('gulp-util'); 9 | const loadPlugins = require('gulp-load-plugins'); 10 | const path = require('path'); 11 | const Resolver = require('./resolver'); 12 | const defaultPatterns = ['gulp-*', 'gulp.*', '!gulp-di']; 13 | const readDirectory = require('./read-directory'); 14 | const DependencyError = require('./dependency-error'); 15 | const normalizePath = require('./normalize-path'); 16 | 17 | let parentDir = ''; 18 | 19 | /** 20 | * @module GulpDI 21 | */ 22 | 23 | class GulpDI { 24 | /** 25 | * @constructor 26 | * @param {object} gulp The gulp instance to use 27 | * @param {object} options options passed to gulp-load-plugins 28 | */ 29 | 30 | constructor (gulp, options) { 31 | if (typeof gulp === 'undefined' || !gulp) { 32 | throw new Error('A gulp instance must be provided as first argument'); 33 | } 34 | this.byId = this.byId.bind(this); 35 | this.resolver = new Resolver(); 36 | this.tasksCount = 0; 37 | 38 | this.provide('gulp', gulp); 39 | this.parseOptions(options); 40 | } 41 | 42 | /** 43 | * @method $byId 44 | * @param {string} id 45 | * @param {boolean} noError 46 | * @returns {*|null} payload 47 | */ 48 | 49 | byId (id, noError) { 50 | const entry = this.resolver.byId(id); 51 | const isResolved = this.resolver.resolved.indexOf(id) !== -1; 52 | if (noError !== true) { 53 | if (entry === null) { 54 | throw new DependencyError(id); 55 | } 56 | if (!isResolved) { 57 | throw new Error(`Attempted to access unresolved dependency "${id}"`); 58 | } 59 | } 60 | return entry ? entry.payload : null; 61 | } 62 | 63 | /** 64 | * @method parseOptions 65 | * @param {object} options 66 | */ 67 | 68 | parseOptions (options) { 69 | // Necessary as function parameters cannot contain dashes. 70 | options.camelize = true; 71 | if (typeof options.lazy === 'undefined') { 72 | options.lazy = true; 73 | } else { 74 | options.lazy = !!options.lazy; 75 | } 76 | 77 | this.options = options; 78 | 79 | // exposed modules 80 | 81 | if (!options.noModules) { 82 | this.provide({ 83 | '_': _, 84 | 'chalk': chalk, 85 | 'gutil': gutil, 86 | 'log': gutil.log.bind(gutil) 87 | }); 88 | } 89 | 90 | // built-in dependencies 91 | 92 | if (!options.noBuiltin) { 93 | this.provide({ 94 | 'basePath': path.join.bind(path, parentDir), 95 | 'Package': require(options.config) 96 | }); 97 | } 98 | 99 | if (!options.noHelp) { 100 | // installs a gulp.task() wrapper and registers the 'help' task 101 | this.inject(require('../contrib/help')); 102 | } 103 | 104 | if (!options.noRunningTasks) { 105 | this.inject(require('../contrib/running-tasks')); 106 | } 107 | 108 | // Provides all installed Gulp plugins according to package.json 109 | const plugins = loadPlugins(options); 110 | 111 | // When options.lazy is set, loadPlugins loads the plugin as soon as 112 | // the property of the returned "plugins" object is directly accessed. 113 | 114 | if (options.lazy) { 115 | const pluginKeys = _.keys(plugins); 116 | for (let i = 0, l = pluginKeys.length; i < l; i++) { 117 | this.providePlugin(pluginKeys[i], plugins); 118 | } 119 | } else { 120 | this.provide(plugins); 121 | } 122 | } 123 | 124 | /** 125 | * @method inject 126 | * @param {function|Array} arg 127 | * @param {Boolean} returnValue 128 | * @chainable 129 | */ 130 | 131 | inject (arg, returnValue) { 132 | const info = $parseArg(arg); 133 | const retval = info.fn.apply(this, info.params.map((param) => this.byId(param, true))); 134 | return returnValue ? retval : this; 135 | } 136 | 137 | /** 138 | * @method _module 139 | * @param {string} name 140 | * @param {function} fn 141 | * @param {string} type 142 | * @chainable 143 | */ 144 | 145 | module (name, fn, type) { 146 | if (typeof fn === 'string') { 147 | try { 148 | fn = require(normalizePath(parentDir, fn)); 149 | } catch (e) { 150 | throw new Error(`Could not load module "${name}" from "${fn}"`); 151 | } 152 | } 153 | if (typeof name === 'object' && typeof name.length !== 'number') { 154 | const obj = arguments[0]; 155 | _.each(obj, (fn, key) => this.module(key, fn, type)); 156 | return this; 157 | } 158 | const info = $parseArg(fn); 159 | this.resolver.provide(name, info.params, info.fn, type || 'module'); 160 | return this; 161 | } 162 | 163 | /** 164 | * @method modules 165 | * @param {string} directory 166 | * @chainable 167 | */ 168 | 169 | modules (directory) { 170 | return this.module(readDirectory(normalizePath(parentDir, directory), true)); 171 | } 172 | 173 | /** 174 | * @method provide 175 | * @param {string} name 176 | * @param {*} payload 177 | * @chainable 178 | */ 179 | 180 | provide (name, payload) { 181 | if (typeof name === 'object') { 182 | const obj = arguments[0]; 183 | _.each(obj, (payload, name) => this.provide(name, payload)); 184 | return this; 185 | } 186 | this.resolver.provide(name, [], payload, 'provide'); 187 | return this; 188 | } 189 | 190 | /** 191 | * @method providePlugin 192 | * @param {string} name 193 | * @param {*} payload 194 | * @chainable 195 | */ 196 | 197 | providePlugin (name, plugins) { 198 | this.resolver.provideObjectNode(name, [], plugins, 'provide'); 199 | return this; 200 | } 201 | 202 | /** 203 | * @method resolve 204 | * @chainable 205 | */ 206 | 207 | resolve () { 208 | const resolver = this.resolver; 209 | const queue = resolver.resolve(); 210 | for (let i = 0, l = queue.length; i < l; ++i) { 211 | const id = queue[i]; 212 | const entry = resolver.byId(id); 213 | let retval = null; 214 | if (!entry) { 215 | throw new DependencyError(id); 216 | } 217 | if (entry.type === 'module' || entry.type === 'task') { 218 | retval = entry.payload.apply(this, entry.dependencies.map(this.byId)); 219 | if (entry.type === 'module') { 220 | resolver.put(id, retval); 221 | } 222 | } 223 | } 224 | return this; 225 | } 226 | 227 | /** 228 | * @method task 229 | * @param {function|Array} fn 230 | * @chainable 231 | */ 232 | 233 | task (fn) { 234 | if (typeof fn === 'object') { 235 | const obj = arguments[0]; 236 | let keys = Object.keys(obj).sort(); 237 | const defaultIndex = keys.indexOf('default'); 238 | if (~defaultIndex) { 239 | keys.splice(defaultIndex, 1); 240 | keys.push('default'); 241 | } 242 | keys.forEach((name) => this.task(obj[name])); 243 | return this; 244 | } 245 | if (typeof fn === 'string') { 246 | try { 247 | fn = require(normalizePath(parentDir, fn)); 248 | } catch (e) { 249 | throw new Error(`Could not load task from "${fn}"`); 250 | } 251 | } 252 | 253 | this.module(`task_${this.tasksCount++}`, fn, 'task'); 254 | return this; 255 | } 256 | 257 | /** 258 | * @method tasks 259 | * @param {string} directory 260 | * @chainable 261 | */ 262 | 263 | tasks (directory) { 264 | return this.task(readDirectory(normalizePath(parentDir, directory), false)); 265 | } 266 | } 267 | 268 | /** 269 | * @method 270 | * @param {object} gulp 271 | * @param {object} options 272 | * @return {GulpDI} [description] 273 | */ 274 | 275 | return function (gulp, options) { 276 | options = options || {}; 277 | 278 | if (typeof options.parentDir === 'string') { 279 | parentDir = options.parentDir; 280 | } else { 281 | // Set "parentDir" to index.js parent's directory name 282 | parentDir = path.dirname(module.parent.parent.filename); 283 | } 284 | 285 | options.config = options.config || findup('package.json', { cwd: parentDir }); 286 | 287 | // Ensure loadPlugins being called with the default patterns (see above) 288 | 289 | options.pattern = _.chain(options.pattern || []) 290 | .concat(defaultPatterns) 291 | .uniq() 292 | .value(); 293 | 294 | // Necessary to get the current `module.parent` and resolve paths correctly 295 | // @see gulp-load-plugins 296 | 297 | delete require.cache[__filename]; 298 | return new GulpDI(gulp, options); 299 | }; 300 | })(); 301 | -------------------------------------------------------------------------------- /lib/normalize-path.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | function normalizePath (parentDir, directory) { 4 | if (!path.isAbsolute(directory)) { 5 | return path.resolve(parentDir, directory); 6 | } 7 | return directory; 8 | } 9 | 10 | module.exports = normalizePath; 11 | -------------------------------------------------------------------------------- /lib/parse-argument.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const introspect = require('introspect'); 3 | 4 | /** 5 | * Parses the given dependency injection notation, either using a function 6 | * with dependencies as its parameters or using the minification-safe with an 7 | * array. 8 | * @method $parseArg 9 | * @private 10 | * @param {function|Array} arg 11 | * @return {object} 12 | */ 13 | 14 | function $parseArg (arg) { 15 | let params = []; 16 | let fn = null; 17 | // ['dependency', function (dependency) {}] 18 | 19 | if (typeof arg === 'object' && typeof arg.length === 'number') { 20 | let l = arg.length; 21 | params = arg.slice(0, l - 1); 22 | fn = arg[l - 1]; 23 | } else if (typeof arg === 'function') { // function (dependency) {} 24 | params = introspect(arg); 25 | fn = arg; 26 | } else { 27 | throw new SyntaxError('Invalid dependency injection notation'); 28 | } 29 | 30 | if (typeof fn !== 'function') { 31 | throw new Error('Function expected'); 32 | } 33 | 34 | for (let i = 0, l = params.length; i < l; i++) { 35 | if (typeof params[i] !== 'string') { 36 | throw new Error('Invalid dependency notation, string expected'); 37 | } 38 | params[i] = params[i].trim(); 39 | if (params[i].length === 0) { 40 | throw new Error('Invalid dependency notation, empty string provided'); 41 | } 42 | } 43 | 44 | return { fn, params }; 45 | } 46 | 47 | module.exports = $parseArg; 48 | -------------------------------------------------------------------------------- /lib/read-directory.js: -------------------------------------------------------------------------------- 1 | const requireDir = require('require-dir'); 2 | 3 | /** 4 | * @method readDirectory 5 | * @param {string} directory 6 | * @param {Object} options 7 | * @return {object} 8 | */ 9 | 10 | const readDirectory = (directory, options = {}) => requireDir(directory, options); 11 | 12 | module.exports = readDirectory; 13 | -------------------------------------------------------------------------------- /lib/resolver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | 4 | /** 5 | * @module Resolver 6 | * @method Resolver 7 | * @returns {object} 8 | */ 9 | 10 | class Resolver { 11 | /** 12 | * @constructor 13 | */ 14 | constructor () { 15 | this.resolved = []; 16 | this.queue = []; 17 | } 18 | 19 | /** 20 | * @return {number} 21 | */ 22 | 23 | get length () { 24 | return this.queue.length; 25 | } 26 | 27 | /** 28 | * @method byId 29 | * @param {string} id 30 | * @private 31 | */ 32 | 33 | byId (id) { 34 | return _.find(this.queue, (item) => item.key === id) || null; 35 | } 36 | 37 | /** 38 | * @method put 39 | * @param {string} key 40 | * @param {*} payload 41 | */ 42 | 43 | put (key, payload) { 44 | let item = this.byId(key); 45 | if (!item) { 46 | throw new Error('Dependency ' + key + 'is not defined'); 47 | } 48 | item.payload = payload; 49 | } 50 | 51 | /** 52 | * @method provide 53 | * @param {string} key 54 | * @param {string[]} dependencies 55 | * @param {*} payload 56 | * @param {string} type 57 | */ 58 | 59 | provide (key, dependencies, payload, type) { 60 | this.queue.push({ key, dependencies, payload, type }); 61 | } 62 | 63 | /** 64 | * @method provideObjectNode 65 | * @param {string} key 66 | * @param {string[]} dependencies 67 | * @param {*} obj 68 | * @param {string} type 69 | */ 70 | 71 | provideObjectNode (key, dependencies, obj, type) { 72 | let queuedItem = { key, dependencies, type }; 73 | Object.defineProperty(queuedItem, 'payload', { 74 | get: function () { 75 | return obj[key]; 76 | } 77 | }); 78 | this.queue.push(queuedItem); 79 | } 80 | 81 | /** 82 | * Resolves and empties the current queue. 83 | * @method resolve 84 | */ 85 | 86 | resolve () { 87 | /** 88 | * @method isResolved 89 | * @param {string} id 90 | * @returns {boolean} 91 | */ 92 | 93 | const isResolved = (id) => !!(~this.resolved.indexOf(id) || ~resolved.indexOf(id)); 94 | let resolved = []; 95 | 96 | /** 97 | * @method _resolve 98 | * @private 99 | * @param {object} entry 100 | * @param {string[]} unresolved 101 | * @returns {string[]} 102 | */ 103 | 104 | const _resolve = (entry, unresolved) => { 105 | if (isResolved(entry.key)) { 106 | return; 107 | } 108 | 109 | unresolved.push(entry.key); 110 | const dependencies = entry.dependencies.slice(); 111 | 112 | for (let i = 0, l = dependencies.length; i < l; ++i) { 113 | const key = dependencies[i]; 114 | if (isResolved(key)) { 115 | continue; 116 | } 117 | if (~unresolved.indexOf(key)) { 118 | throw new Error(`Circular: ${entry.key} -> ${key}`); 119 | } 120 | const queuedItem = this.byId(key); 121 | _resolve({ key, dependencies: (queuedItem && queuedItem.dependencies) || [] }, unresolved); 122 | } 123 | 124 | unresolved = _.without(unresolved, entry.key); 125 | resolved.push(entry.key); 126 | }; 127 | for (let j = 0, k = this.queue.length; j < k; ++j) { 128 | _resolve(this.queue[j], []); 129 | } 130 | this.resolved = this.resolved.concat(resolved); 131 | return resolved; 132 | } 133 | } 134 | 135 | module.exports = Resolver; 136 | -------------------------------------------------------------------------------- /modules/paths.js: -------------------------------------------------------------------------------- 1 | module.exports = (basePath) => { 2 | return { 3 | mocha: basePath('test', '**/*.js'), 4 | src: [ 5 | basePath('index.js'), 6 | basePath('lib/**/*.js'), 7 | basePath('contrib/**/*.js') 8 | ], 9 | istanbul: [ 10 | basePath('index.js'), 11 | basePath('lib/**/*.js'), 12 | basePath('contrib/**/*.js') 13 | ], 14 | docs: [ 15 | basePath('lib/**/*.js') 16 | ], 17 | tasks: [ 18 | basePath('tasks/**/*.js') 19 | ] 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-di", 3 | "version": "0.1.0", 4 | "description": "Dependency injection framework for the Gulp streaming build system", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/gulp-cli/bin/gulp.js semistandard mocha", 8 | "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls" 9 | }, 10 | "author": { 11 | "name": "Matthias Thoemmes", 12 | "email": "git@gmx.org" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/cmtt/gulp-di.git" 17 | }, 18 | "keywords": [ 19 | "gulp", 20 | "plugin", 21 | "build", 22 | "system", 23 | "framework", 24 | "tool", 25 | "asset", 26 | "pipeline", 27 | "require", 28 | "dependency", 29 | "injection", 30 | "DI", 31 | "dependency injection", 32 | "gulpplugin" 33 | ], 34 | "license": "MIT", 35 | "dependencies": { 36 | "chalk": "^3.0.0", 37 | "extract-comments": "^1.1.0", 38 | "findup-sync": "^4.0.0", 39 | "gulp": "^4.0.2", 40 | "gulp-cli": "^2.2.0", 41 | "gulp-concat": "^2.6.1", 42 | "gulp-istanbul": "^1.1.3", 43 | "gulp-load-plugins": "^2.0.1", 44 | "gulp-mocha": "^7.0.2", 45 | "gulp-rename": "^1.4.0", 46 | "gulp-semistandard": "^1.0.0", 47 | "gulp-util": "^3.0.8", 48 | "introspect": "git+https://github.com/orzarchi/node-introspect.git", 49 | "lodash": "^4.17.15", 50 | "pad": "^3.2.0", 51 | "parse-stack": "^0.1.4", 52 | "require-dir": "^1.2.0" 53 | }, 54 | "devDependencies": { 55 | "coveralls": "^3.0.9", 56 | "parse-function": "^2.3.2" 57 | }, 58 | "semistandard": { 59 | "globals": [ 60 | "afterEach", 61 | "assert", 62 | "basePath", 63 | "beforeEach", 64 | "describe", 65 | "it", 66 | "getGulpInstance", 67 | "getDiInstance" 68 | ], 69 | "esversion": 6 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tasks/default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (gulp, Package, basePath) => { 4 | gulp.task('default', gulp.parallel('mocha', 'semistandard')); 5 | }; 6 | -------------------------------------------------------------------------------- /tasks/examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (Package, basePath, chalk, log, gulp, taskInfo, gutil, runningTasks) => { 4 | // The order of dependencies does not matter ^^^^ 5 | 6 | const path = require('path'); 7 | 8 | gulp.task('log-path', () => { 9 | /** 10 | * Example using the build-in constant Package from your the current 11 | * package.json as well as the path resolving helper basePath. 12 | */ 13 | 14 | console.log(`${Package.name}s location: `, basePath()); 15 | console.log(`this file's location: ${basePath('tasks', path.basename(__filename))}`); 16 | }); 17 | 18 | // You can declare multiple gulp tasks in each file. 19 | 20 | gulp.task('task-info', (cb) => { 21 | /** 22 | * Example logging task information from contrib/help.js 23 | */ 24 | 25 | console.log(`${Package.name}'s task information: `); 26 | console.log(taskInfo); 27 | cb(); 28 | }); 29 | 30 | gulp.task('a', (cb) => cb()); 31 | gulp.task('c', (cb) => cb()); 32 | gulp.task('b', gulp.series('a', 'c')); 33 | 34 | gulp.task('info', (cb) => { 35 | /** 36 | * Demonstrates logging currently running tasks. 37 | * 38 | * If you'd define tasks a, b, c (where b would depend on c and a), 39 | * runningTasks() would return: 40 | * $ gulp a 41 | * // ['a'] 42 | * $ gulp b 43 | * // ['c','a','b'] 44 | * $ gulp c 45 | * // ['c'] 46 | */ 47 | 48 | let line = [ 49 | 'Building', 50 | chalk.magenta(Package.name), 51 | Package.version, 52 | '- running tasks:', 53 | chalk.cyan(runningTasks().join(', ')) 54 | ]; 55 | log(line.join(' ')); 56 | cb(); 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /tasks/mocha.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function (gulp, paths, mocha, istanbul) { 3 | gulp.task('pre-test', () => { 4 | return gulp.src(paths.istanbul) 5 | // Covering files 6 | .pipe(istanbul()) 7 | // Force `require` to return covered files 8 | .pipe(istanbul.hookRequire()); 9 | }); 10 | 11 | gulp.task('mocha', gulp.series('pre-test', () => { 12 | // Runs the unit tests using Mocha 13 | 14 | return gulp.src(paths.mocha, { read: false }) 15 | .pipe(mocha({})) 16 | .pipe(istanbul.writeReports()); 17 | })); 18 | }; 19 | -------------------------------------------------------------------------------- /tasks/semistandard.js: -------------------------------------------------------------------------------- 1 | module.exports = (gulp, semistandard, paths) => { 2 | gulp.task('semistandard', () => { 3 | return gulp.src(paths.src.concat(paths.tasks)) 4 | .pipe(semistandard()) 5 | .pipe(semistandard.reporter('default', { 6 | quiet: true 7 | })); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /test/00.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | global.assert = require('assert'); 4 | global.basePath = path.join.bind(path, __dirname, '..'); 5 | 6 | const diPath = global.basePath('index.js'); 7 | global.GulpDI = require(diPath); 8 | 9 | /** 10 | * @method getGulpInstance 11 | */ 12 | 13 | global.getGulpInstance = () => { 14 | delete require.cache[require.resolve('gulp')]; 15 | delete require.cache[require.resolve('undertaker')]; 16 | delete require.cache[require.resolve('undertaker-registry')]; 17 | delete require.cache[require.resolve('last-run')]; 18 | const gulp = require('gulp'); 19 | gulp.on('error', (e) => { 20 | console.log(e.error); 21 | throw new Error(`Gulp runtime error:\n${JSON.stringify(e)}\n`); 22 | }); 23 | return gulp; 24 | }; 25 | 26 | /** 27 | * @method getDiInstance 28 | */ 29 | 30 | global.getDiInstance = (gulp, config) => { 31 | delete require.cache[require.resolve(diPath)]; 32 | global.GulpDI = require(diPath); 33 | return new global.GulpDI(gulp, config); 34 | }; 35 | 36 | global.hasTask = (gulp, taskId) => { 37 | const registry = gulp._registry; 38 | const tasks = registry.tasks(); 39 | return taskId in tasks; 40 | }; 41 | -------------------------------------------------------------------------------- /test/01-parse-argument.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('parseArgument', function () { 4 | const $parseArg = require('../lib/parse-argument'); 5 | 6 | let concatTask = (gulp, concat) => { 7 | gulp.task('concat', () => { 8 | return gulp 9 | .src('src/**/*.txt') 10 | .pipe(gulp.dest('docs')); 11 | }); 12 | }; 13 | 14 | it('parses the function notation', () => { 15 | let info = $parseArg(concatTask); 16 | assert.deepEqual(info.params, ['gulp', 'concat']); 17 | assert.equal(info.fn.toString(), concatTask.toString()); 18 | }); 19 | 20 | it('parses the minification-safe notation', () => { 21 | let info = $parseArg(['gulp', 'concat', concatTask]); 22 | assert.deepEqual(info.params, ['gulp', 'concat']); 23 | assert.equal(info.fn.toString(), concatTask.toString()); 24 | }); 25 | 26 | it('parses a function without any arguments', () => { 27 | let fn = function () { 28 | console.log(new Date() + ''); 29 | }; 30 | let info = $parseArg(fn); 31 | assert.deepEqual(info.params, []); 32 | assert.equal(info.fn.toString(), fn.toString()); 33 | }); 34 | 35 | it('parses a function without any arguments (minification-safe notation)', () => { 36 | let info = $parseArg([function () {}]); 37 | assert.deepEqual(info.params, []); 38 | assert.equal(typeof info.fn, 'function'); 39 | }); 40 | 41 | it('throws an error when no valid function was declared', () => { 42 | let tests = [ 43 | '', 44 | 0, 45 | 1, 46 | null, 47 | void 0, 48 | {}, 49 | ['test'], 50 | [null, function () {}], 51 | [0], 52 | [' ', function () {}], 53 | [' ', function () {}], 54 | ['', function () {}], 55 | [' ', function (space) {}] 56 | ]; 57 | tests.forEach((test) => assert.throws(() => { 58 | let info = $parseArg(test); 59 | assert.ok(Array.isArray(info)); 60 | })); 61 | }); 62 | 63 | it('can be used with functions and dependencies', () => { 64 | let dependencies = { 65 | A: 'a', 66 | B: 'b', 67 | C: 'c' 68 | }; 69 | 70 | let abc = ['A', 'B', 'C', (A, B, C) => `${A}${B}${C}`]; 71 | 72 | // Solely changing the order in the minification-safe dependency notation 73 | let cab0 = ['C', 'A', 'B', (A, B, C) => `${A}${B}${C}`]; 74 | 75 | // Using function parameters 76 | let cab1 = (C, A, B) => `${C}${A}${B}`; 77 | 78 | let getDependencies = (params) => params.map((key) => dependencies[key]); 79 | 80 | let check = (arg, expected) => { 81 | let info = $parseArg(arg); 82 | let args = getDependencies(info.params); 83 | assert.equal(expected, info.fn.apply(null, args)); 84 | }; 85 | 86 | check(abc, 'abc'); 87 | check(cab0, 'cab'); 88 | check(cab1, 'cab'); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/02-resolver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Resolver', () => { 4 | const Resolver = require('../lib/resolver'); 5 | let d = null; 6 | 7 | beforeEach(() => { 8 | d = new Resolver('test'); 9 | }); 10 | 11 | it('initializes, length is zero', () => { 12 | assert.equal(d.length, 0); 13 | assert.equal(d.resolved.length, 0); 14 | }); 15 | 16 | it('increases its length', () => { 17 | d.provide('one', ['two', 'three']); 18 | assert.equal(d.length, 1); 19 | d.provide('two', ['four']); 20 | assert.equal(d.length, 2); 21 | assert.equal(d.resolved.length, 0); 22 | }); 23 | 24 | it('resolves returns an empty array', () => { 25 | assert.deepEqual(d.resolve(), []); 26 | assert.equal(d.resolved.length, 0); 27 | }); 28 | 29 | it('throws an error when a dependency depends on itself', () => { 30 | d.provide('A', ['A']); 31 | assert.throws(() => { 32 | d.resolve(); 33 | }); 34 | }); 35 | 36 | it('throws an error when circular dependencies are declared', () => { 37 | d.provide('A', ['B']); 38 | d.provide('B', ['A']); 39 | assert.throws(() => { 40 | d.resolve(); 41 | }); 42 | }); 43 | 44 | it('resolves in a correct order', () => { 45 | d.provide('test', ['one', 'two', 'three']); 46 | d.provide('two', []); 47 | d.provide('one', []); 48 | d.provide('three', []); 49 | assert.deepEqual(d.resolve(), ['one', 'two', 'three', 'test']); 50 | assert.equal(d.resolved.length, 4); 51 | }); 52 | 53 | it('provide, byId', () => { 54 | d.provide('test', [], 'test', 'string'); 55 | d.provide('one', ['test'], 1, 'number'); 56 | 57 | assert.deepEqual(d.byId('test'), { 58 | key: 'test', 59 | dependencies: [], 60 | payload: 'test', 61 | type: 'string' 62 | }); 63 | assert.deepEqual(d.byId('one'), { 64 | key: 'one', 65 | dependencies: ['test'], 66 | payload: 1, 67 | type: 'number' 68 | }); 69 | assert.deepEqual(d.resolve(), ['test', 'one']); 70 | }); 71 | 72 | it('put throws an error when a value was not provided', () => { 73 | assert.throws(() => { 74 | d.put('test', 'test'); 75 | }); 76 | }); 77 | 78 | it('put', () => { 79 | d.provide('test', [], { test: false }, 'object'); 80 | d.resolve(); 81 | assert.deepEqual(d.byId('test'), { 82 | key: 'test', 83 | dependencies: [], 84 | payload: { test: false }, 85 | type: 'object' 86 | }); 87 | d.put('test', { test: true }); 88 | assert.deepEqual(d.byId('test').payload, { 89 | test: true 90 | }); 91 | }); 92 | 93 | it('does not resolve twice', () => { 94 | let queue = []; 95 | 96 | d.provide('test0', ['test2'], 'test0'); 97 | d.provide('test1', [], 'test1'); 98 | d.provide('test2', ['test1'], 'test2'); 99 | d.provide('test3', ['test0'], 'test3'); 100 | 101 | assert.equal(d.resolved.length, 0); 102 | 103 | queue = d.resolve(); 104 | assert.equal(queue.length, 4); 105 | assert.equal(d.length, 4); 106 | assert.equal(d.resolved.length, 4); 107 | 108 | queue = d.resolve(); 109 | assert.equal(queue.length, 0); 110 | assert.equal(d.length, 4); 111 | assert.equal(d.resolved.length, 4); 112 | 113 | d.provide('test4', [], 'test4'); 114 | assert.equal(d.length, 5); 115 | assert.equal(d.resolved.length, 4); 116 | 117 | queue = d.resolve(); 118 | assert.equal(queue.length, 1); 119 | assert.equal(d.length, 5); 120 | assert.equal(d.resolved.length, 5); 121 | }); 122 | 123 | it('provideObjectNode', (done) => { 124 | let object = { 125 | a: 1, 126 | b: 2 127 | }; 128 | 129 | Object.defineProperty(object, 'c', { 130 | get: function () { 131 | setTimeout(done); 132 | return 3; 133 | } 134 | }); 135 | 136 | d.provideObjectNode('a', ['c'], object); 137 | d.provideObjectNode('b', [], object); 138 | d.provideObjectNode('c', [], object); 139 | let queue = d.resolve(); 140 | assert.deepEqual(queue, 'cab'.split('')); 141 | assert.equal(d.byId('a').payload, 1); 142 | assert.equal(d.byId('b').payload, 2); 143 | assert.equal(d.byId('c').payload, 3); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /test/03-read-directory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('readDirectory', () => { 4 | const readDirectory = require('../lib/read-directory'); 5 | const path = require('path'); 6 | 7 | it('reads examples', () => { 8 | let list = readDirectory(path.join(__dirname, '..', 'contrib/examples')); 9 | assert.equal(typeof list, 'object'); 10 | let keys = Object.keys(list); 11 | assert.deepEqual(keys, [ 12 | 'deg-to-rad', 13 | 'pi', 14 | 'rad-to-deg', 15 | 'to-deg', 16 | 'to-rad' 17 | ]); 18 | }); 19 | 20 | it('applies options', () => { 21 | const toCamelCase = (str) => { 22 | return str.replace(/[_-][a-z]/ig, function (s) { 23 | return s.substring(1).toUpperCase(); 24 | }); 25 | }; 26 | 27 | let list = readDirectory(path.join(__dirname, '..', 'contrib/examples'), { 28 | mapKey: (value, key) => toCamelCase(key) 29 | }); 30 | assert.equal(typeof list, 'object'); 31 | let keys = Object.keys(list); 32 | assert.deepEqual(keys, [ 33 | 'degToRad', 34 | 'pi', 35 | 'radToDeg', 36 | 'toDeg', 37 | 'toRad' 38 | ]); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/10-gulp-di.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Stream = require('stream'); 3 | const path = require('path'); 4 | 5 | describe('GulpDI', () => { 6 | const PI = Math.PI; 7 | const RAD_TO_DEG = 180 / PI; 8 | const INSTANCE_OPTIONS = { parentDir: basePath() }; 9 | 10 | /** 11 | * Module declaration which assembles the "toDeg" function using PI and 12 | * RAD_TO_DEG 13 | * 14 | * @method toDegModule 15 | * @param {number} PI 16 | * @param {number} RAD_TO_DEG 17 | * @return {function} 18 | */ 19 | 20 | function toDegModule (PI, RAD_TO_DEG) { 21 | return (radValue) => radValue * RAD_TO_DEG; 22 | } 23 | 24 | let gulp = null; 25 | let di = null; 26 | 27 | describe('Initialization', () => { 28 | beforeEach(() => { 29 | gulp = getGulpInstance(); 30 | di = null; 31 | }); 32 | 33 | it('initializes', () => { 34 | di = getDiInstance(gulp).resolve(); 35 | }); 36 | 37 | it('throws an error without gulp as first argument', () => { 38 | assert.throws(() => getDiInstance()); 39 | }); 40 | 41 | it('byId throws an error when a dependency was not found', () => { 42 | assert.throws(getDiInstance(gulp).byId); 43 | }); 44 | 45 | it('byId with noError does not throw an error when a dependency was not found', () => { 46 | di = getDiInstance(gulp).resolve(); 47 | let info = di.byId('UnknownDependency_', true); 48 | assert.equal(info, null); 49 | }); 50 | 51 | it('byId throws an error when a undesolved dependency was requested', () => { 52 | assert.throws(() => { 53 | di = getDiInstance(gulp, INSTANCE_OPTIONS) 54 | .module('PI', './contrib/examples/pi') 55 | .byId('PI'); 56 | }); 57 | }); 58 | 59 | it('provide', () => { 60 | di = getDiInstance(gulp) 61 | .provide('test', 'test') 62 | .resolve(); 63 | assert.equal(di.byId('test'), 'test'); 64 | }); 65 | 66 | it('options.noModules', () => { 67 | di = getDiInstance(gulp, { noModules: true }).resolve(); 68 | assert.throws(() => di.byId('_')); 69 | assert.throws(() => di.byId('chalk')); 70 | assert.throws(() => di.byId('gutil')); 71 | assert.throws(() => di.byId('log')); 72 | }); 73 | 74 | it('options.noBuiltin', () => { 75 | di = getDiInstance(gulp, { noBuiltin: true }).resolve(); 76 | assert.throws(() => di.byId('basePath')); 77 | assert.throws(() => di.byId('Package')); 78 | }); 79 | 80 | it('options.noHelp', () => { 81 | di = getDiInstance(gulp, { noHelp: true }).resolve(); 82 | assert.ok(!hasTask(gulp, 'help')); 83 | }); 84 | }); 85 | 86 | describe('Dependency injection', () => { 87 | it('throws an error when using invalid notation', () => { 88 | di = getDiInstance(gulp); 89 | assert.throws(() => { 90 | di.task(); 91 | }); 92 | assert.throws(() => { 93 | di.module('test', null); 94 | }); 95 | assert.throws(() => { 96 | di.task('null'); 97 | }); 98 | assert.throws(() => { 99 | di.module('test', 0); 100 | }); 101 | }); 102 | 103 | it('module', () => { 104 | di = getDiInstance(gulp) 105 | .provide('PI', PI) 106 | .provide('RAD_TO_DEG', RAD_TO_DEG) 107 | .module('toDeg', toDegModule); 108 | 109 | di.resolve(); 110 | 111 | let toDeg = di.byId('toDeg'); 112 | let pi = di.byId('PI'); 113 | assert.ok(toDeg); 114 | assert.equal(typeof toDeg, 'function'); 115 | assert.equal(typeof pi, 'number'); 116 | 117 | assert.equal(toDeg(pi), 180); 118 | assert.equal(toDeg(2 * pi), 360); 119 | }); 120 | 121 | it('module with relative and absolute paths', () => { 122 | di = getDiInstance(gulp, INSTANCE_OPTIONS) 123 | .module('PI', './contrib/examples/pi') 124 | .module('DEG_TO_RAD', './contrib/examples/deg-to-rad') 125 | .module('RAD_TO_DEG', './contrib/examples/rad-to-deg') 126 | .module('toDeg', './contrib/examples/to-deg') 127 | .module('toRad', path.join(__dirname, '..', 'contrib/examples/to-rad')); 128 | 129 | di.resolve(); 130 | 131 | let toDeg = di.byId('toDeg'); 132 | let toRad = di.byId('toRad'); 133 | let pi = di.byId('PI'); 134 | assert.equal(typeof toDeg, 'function'); 135 | assert.equal(typeof toRad, 'function'); 136 | 137 | assert.equal(toDeg(pi), 180); 138 | assert.equal(toDeg(2 * pi), 360); 139 | assert.equal(toRad(180), pi); 140 | assert.equal(toRad(360), 2 * pi); 141 | }); 142 | 143 | it('module throws an error with invalid paths', () => { 144 | assert.throws(() => { 145 | di = getDiInstance(gulp) 146 | .module('PI', './throws/an/error'); 147 | }); 148 | }); 149 | 150 | it('modules', (done) => { 151 | di = getDiInstance(gulp, INSTANCE_OPTIONS) 152 | .modules('./modules') 153 | .resolve(); 154 | let paths = di.byId('paths'); 155 | assert.equal(typeof paths, 'object'); 156 | done(); 157 | }); 158 | 159 | it('inject (chainable)', (done) => { 160 | di = getDiInstance(gulp) 161 | .provide('PI', PI) 162 | .provide('RAD_TO_DEG', RAD_TO_DEG) 163 | .module('toDeg', toDegModule) 164 | .resolve(); 165 | let injectCalled = 0; 166 | di.inject((toDeg, PI) => { 167 | ++injectCalled; 168 | assert.equal(toDeg(PI), 180); 169 | assert.equal(toDeg(2 * PI), 360); 170 | }) 171 | .inject(() => { 172 | assert.equal(injectCalled, 1); 173 | done(); 174 | }); 175 | }); 176 | 177 | it('inject (w/ return value)', () => { 178 | di = getDiInstance(gulp) 179 | .provide('PI', PI) 180 | .provide('RAD_TO_DEG', RAD_TO_DEG) 181 | .module('toDeg', toDegModule) 182 | .resolve(); 183 | let injectCalled = 0; 184 | let returnValue = di.inject((toDeg, PI) => { 185 | ++injectCalled; 186 | return toDeg(2 * PI); 187 | }, true); 188 | assert.equal(injectCalled, 1); 189 | assert.equal(returnValue, 360); 190 | }); 191 | 192 | it('throws an error when a missing dependency is declared', () => { 193 | di = getDiInstance(gulp) 194 | .task(function (test) {}); 195 | assert.throws(() => di.resolve()); 196 | }); 197 | }); 198 | 199 | describe('Tasks', () => { 200 | it('task', (done) => { 201 | di = getDiInstance(gulp) 202 | .provide('PI', PI) 203 | .provide('RAD_TO_DEG', RAD_TO_DEG) 204 | .module('toDeg', toDegModule) 205 | .provide('test', 'test') 206 | .task((test, toDeg, PI) => { 207 | assert.equal(test, 'test'); 208 | assert.equal(toDeg(PI), 180); 209 | assert.equal(toDeg(2 * PI), 360); 210 | 211 | done(); 212 | }) 213 | .resolve(); 214 | }); 215 | 216 | it('task with an object', (done) => { 217 | di = getDiInstance(gulp) 218 | .task({ 219 | 'first': () => { 220 | done(); 221 | } 222 | }) 223 | .resolve(); 224 | }); 225 | 226 | it('tasks', (done) => { 227 | di = getDiInstance(gulp, INSTANCE_OPTIONS) 228 | .modules('./modules') 229 | .tasks('./tasks') 230 | .resolve(); 231 | di.inject((gulp) => { 232 | assert.ok(hasTask(gulp, 'semistandard'), 'has "semistandard" task'); 233 | done(); 234 | }); 235 | }); 236 | 237 | it('includes gulp and can run a task', (done) => { 238 | let taskCalled = false; 239 | 240 | di = getDiInstance(gulp) 241 | .task((gulp, PI, toDeg) => { 242 | assert.ok(gulp); 243 | 244 | gulp.task('default', (cb) => { 245 | taskCalled = true; 246 | assert.equal(toDeg(2 * PI), 360); 247 | cb(); 248 | }); 249 | }) 250 | .module('toDeg', toDegModule) 251 | .provide('PI', PI) 252 | .provide('RAD_TO_DEG', RAD_TO_DEG) 253 | .resolve(); 254 | 255 | gulp.series('default', (cb) => { 256 | assert.equal(taskCalled, true); 257 | cb(); 258 | done(); 259 | })(); 260 | }); 261 | 262 | it('can concatenate all spec files', (done) => { 263 | let di = getDiInstance(gulp, { parentDir: basePath(), lazy: false }); 264 | let s = new Stream.Transform(); 265 | let l = 0; 266 | let count = 0; 267 | let ended = false; 268 | s.write = function (file) { 269 | ++count; 270 | l += file._contents.length; 271 | }; 272 | s.on('end', () => { 273 | ended = true; 274 | }); 275 | 276 | di.task((gulp, concat) => { 277 | gulp.task('concat', () => { 278 | /** 279 | * This task comment should appear in this test 280 | * and it might have multiple lines 281 | */ 282 | return gulp.src('test/**/*.js') 283 | .pipe(concat('all_specs.js')) 284 | .pipe(s); 285 | // console.log('The concat command', concat) 286 | }); 287 | }); 288 | di.resolve(); 289 | gulp.series('concat', (cb) => { 290 | assert.ok(ended, 'Stream was closed'); 291 | assert.ok(l > 0, 'Read more than zero bytes'); 292 | assert.equal(count, 1, 'Solely one file written'); 293 | cb(); 294 | done(); 295 | })(); 296 | }); 297 | }); 298 | }); 299 | -------------------------------------------------------------------------------- /test/11-builtin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Built-in', () => { 4 | let gulp = null; 5 | let di = null; 6 | 7 | beforeEach(() => { 8 | gulp = getGulpInstance(); 9 | di = getDiInstance(gulp, { DEBUG: false, pattern: [], someTestSetting: '1', parentDir: basePath() }); 10 | }); 11 | 12 | it('gulp', (done) => { 13 | di 14 | .task((Package) => { 15 | gulp.task('default', (cb) => cb()); 16 | }) 17 | .resolve(); 18 | 19 | gulp.series('default', (cb) => { 20 | cb(); 21 | done(); 22 | })(); 23 | }); 24 | 25 | it('Package', (done) => { 26 | di 27 | .task((Package) => { 28 | assert.equal(Package.name, 'gulp-di'); 29 | done(); 30 | }) 31 | .resolve(); 32 | }); 33 | 34 | it('basePath', (done) => { 35 | di 36 | .task((basePath) => { 37 | let Package = require(basePath('package.json')); 38 | assert.equal(Package.name, 'gulp-di'); 39 | done(); 40 | }) 41 | .resolve(); 42 | }); 43 | 44 | it('options', (done) => { 45 | di 46 | .task(function (basePath) { 47 | assert.equal(typeof this.options, 'object'); 48 | assert.equal(this.options.someTestSetting, '1'); 49 | done(); 50 | }) 51 | .resolve(); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/20-contrib.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('contrib', () => { 4 | const getPath = require(basePath('contrib', 'get-path')); 5 | const standardTask = require(basePath('contrib', 'standard-task')); 6 | const parseFn = require('parse-function'); 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | const basename = path.basename(__filename); 10 | 11 | it('getPath', () => { 12 | let obj = { 13 | first: { 14 | '1': 1, 15 | second: { 16 | '2': 2, 17 | third: { 18 | '3': 3 19 | } 20 | } 21 | } 22 | }; 23 | let array = [0, 1, 2, { test: 'test' }]; 24 | assert.equal(typeof getPath(obj), 'object'); 25 | assert.equal(typeof getPath(obj, 'first'), 'object'); 26 | assert.equal(getPath(obj, 'first.1'), 1); 27 | assert.equal(typeof getPath(obj, 'first.second'), 'object'); 28 | assert.equal(getPath(obj, 'first.second.2'), 2); 29 | assert.equal(typeof getPath(obj, 'first.second.third'), 'object'); 30 | assert.equal(getPath(obj, 'first.second.third.3'), 3); 31 | 32 | assert.equal(getPath(array, '0'), 0); 33 | assert.equal(getPath(array, 0), 0); 34 | assert.equal(getPath(array, '1'), 1); 35 | assert.equal(getPath(array, '2'), 2); 36 | assert.deepEqual(getPath(array, '3'), { test: 'test' }); 37 | assert.equal(getPath(array, '3.test'), 'test'); 38 | }); 39 | 40 | it('help', (done) => { 41 | let gulp = getGulpInstance(); 42 | let di = getDiInstance(gulp, { DEBUG: true }); 43 | di.resolve(); 44 | gulp.series('help', (cb) => { 45 | cb(); 46 | done(); 47 | })(); 48 | }); 49 | 50 | it('runningTasks', (done) => { 51 | let gulp = getGulpInstance(); 52 | let di = getDiInstance(gulp, { 53 | argv: [null, null, 'mocha', 'runningTasks'] 54 | }); 55 | di.task((gulp, runningTasks, gutil) => { 56 | gulp.task('runningTasks', (cb) => { 57 | let tasks = runningTasks(); 58 | assert.ok(Array.isArray(tasks)); 59 | cb(); 60 | }); 61 | }); 62 | di.resolve(); 63 | gulp.series('runningTasks', (cb) => { 64 | cb(); 65 | done(); 66 | })(); 67 | }); 68 | 69 | describe('standardTask', () => { 70 | it('returns a function which injects solely gulp', () => { 71 | let fn = standardTask('concat', 'templates/**/*.txt', 'public', { pretty: false }); 72 | let info = parseFn(fn); 73 | assert.equal(info.name, 'concatTask'); 74 | assert.deepEqual(info.args, ['gulp']); 75 | }); 76 | 77 | it('throws an error when initialized without a function', () => { 78 | let gulp = getGulpInstance(); 79 | let di = getDiInstance(gulp); 80 | 81 | let fn = standardTask('Package', 'templates/**/*.txt', 'public', { pretty: false }); 82 | di.task(fn); 83 | di.resolve(); 84 | assert.throws(() => { 85 | gulp.start('Package'); 86 | }); 87 | }); 88 | 89 | it('throws an error when an argument cannot be serialized', () => { 90 | let gulp = getGulpInstance(); 91 | let di = getDiInstance(gulp); 92 | let data = {}; 93 | data.data = data; 94 | let fn = standardTask('concat', 'templates/**/*.txt', 'public', data); 95 | di.task(fn); 96 | di.resolve(); 97 | assert.throws(() => { 98 | gulp.start('concat'); 99 | }); 100 | }); 101 | 102 | it('function calls are valid', () => { 103 | let fn = standardTask('concat', 'templates/**/*.txt', 'public', { pretty: false }); 104 | assert.equal(typeof fn, 'function'); 105 | let fnString = fn + ''; 106 | let filenameIndex = fnString.indexOf(basename); 107 | let pluginNameIndex = fnString.indexOf('this.byId("concat");'); 108 | assert.ok(~filenameIndex, 'current filename is inside a comment'); 109 | assert.ok(~pluginNameIndex, 'task plugin is retrieved with byId("concat")'); 110 | assert.equal(fnString[filenameIndex + basename.length], ':', 'line number is delimited from filename'); 111 | assert.ok((/\d/).test(fnString[filenameIndex + basename.length + 1]), 'line number is present'); 112 | }); 113 | 114 | it('can concatenate all spec files', (done) => { 115 | let gulp = getGulpInstance(); 116 | let di = getDiInstance(gulp); 117 | 118 | let fn = standardTask('concat', 'test/**/*.js', 'trash', 'all.js'); 119 | let destPath = basePath('trash', 'all.js'); 120 | 121 | di.task(fn); 122 | 123 | di.resolve(); 124 | gulp.series('concat', (cb) => { 125 | cb(); 126 | assert.ok(fs.existsSync(destPath)); 127 | try { 128 | fs.unlinkSync(destPath); 129 | } catch (e) { 130 | console.log(`Could not remove ${destPath}: ${e.message}`); 131 | } 132 | done(); 133 | })(); 134 | }); 135 | }); 136 | }); 137 | --------------------------------------------------------------------------------