From eff6b24c62caecd4feea7d90af5ca7d81b129096 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Sat, 7 Nov 2015 17:39:17 +0900 Subject: [PATCH 001/216] Add browserify (multiple destination) example --- docs/recipes/README.md | 1 + .../browserify-multiple-destination.md | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 docs/recipes/browserify-multiple-destination.md diff --git a/docs/recipes/README.md b/docs/recipes/README.md index 37096cf83..235fd734d 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -20,5 +20,6 @@ * [Using multiple sources in one task](using-multiple-sources-in-one-task.md) * [Browserify + Uglify with sourcemaps](browserify-uglify-sourcemap.md) * [Browserify + Globs](browserify-with-globs.md) +* [Browserify + Globs (multiple destination)](browserify-multiple-destination.md) * [Output both a minified and non-minified version](minified-and-non-minified.md) * [Templating with Swig and YAML front-matter](templating-with-swig-and-yaml-front-matter.md) diff --git a/docs/recipes/browserify-multiple-destination.md b/docs/recipes/browserify-multiple-destination.md new file mode 100644 index 000000000..3df6cc79c --- /dev/null +++ b/docs/recipes/browserify-multiple-destination.md @@ -0,0 +1,43 @@ +# Browserify + Globs (multiple destination) + +This example shows how to set up a task of bundling multiple entry points into multiple destinations using browserify. + +The below `js` task bundles all the `.js` files under `src/` as entry points and writes the results under `dest/`. + + +```js +var gulp = require('gulp'); +var plugins = require('gulp-load-plugins')(); +var browserify = require('browserify'); +var gutil = require('gulp-util'); + +gulp.task('js', function () { + + return gulp.src('src/**/*.js', {base: 'src'}) + + // transform file objects using gulp-map plugin + .pipe(plugins.tap(function (file) { + + gutil.log('bundling ' + file.path); + + // replace file contents with browserify's bundle stream + file.contents = browserify(file.path, {debug: true}).bundle(); + + })) + + // transform streaming contents into buffer contents (because gulp-sourcemaps does not support streaming contents) + .pipe(plugins.buffer()) + + // load and init sourcemaps + .pipe(plugins.sourcemaps.init({loadMaps: true})) + + // uglify + .pipe(plugins.uglify()) + + // write sourcemaps + .pipe(plugins.sourcemaps.write('./')) + + .pipe(gulp.dest('dest')); + +}); +``` From 3407b2ad8c46d72e2ab252e75fb4ebb382a4f838 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 10 Nov 2015 13:30:14 +0900 Subject: [PATCH 002/216] Do not use gulp-load-plugins and fix an error in comment --- .../recipes/browserify-multiple-destination.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/recipes/browserify-multiple-destination.md b/docs/recipes/browserify-multiple-destination.md index 3df6cc79c..f57584de9 100644 --- a/docs/recipes/browserify-multiple-destination.md +++ b/docs/recipes/browserify-multiple-destination.md @@ -7,16 +7,19 @@ The below `js` task bundles all the `.js` files under `src/` as entry points and ```js var gulp = require('gulp'); -var plugins = require('gulp-load-plugins')(); var browserify = require('browserify'); var gutil = require('gulp-util'); +var tap = require('gulp-tap'); +var buffer = require('gulp-buffer'); +var sourcemaps = require('gulp-sourcemaps'); +var uglify = require('gulp-uglify'); gulp.task('js', function () { return gulp.src('src/**/*.js', {base: 'src'}) - // transform file objects using gulp-map plugin - .pipe(plugins.tap(function (file) { + // transform file objects using gulp-tap plugin + .pipe(tap(function (file) { gutil.log('bundling ' + file.path); @@ -26,16 +29,15 @@ gulp.task('js', function () { })) // transform streaming contents into buffer contents (because gulp-sourcemaps does not support streaming contents) - .pipe(plugins.buffer()) + .pipe(buffer()) // load and init sourcemaps - .pipe(plugins.sourcemaps.init({loadMaps: true})) + .pipe(sourcemaps.init({loadMaps: true})) - // uglify - .pipe(plugins.uglify()) + .pipe(uglify()) // write sourcemaps - .pipe(plugins.sourcemaps.write('./')) + .pipe(sourcemaps.write('./')) .pipe(gulp.dest('dest')); From 284b6ab1de502735e2c4986f861f19dc7371f790 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 22 Dec 2015 13:22:17 +0900 Subject: [PATCH 003/216] Add `read: false` option in gulp.src and remove `base: 'src'` option --- docs/recipes/browserify-multiple-destination.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes/browserify-multiple-destination.md b/docs/recipes/browserify-multiple-destination.md index f57584de9..6a9af199a 100644 --- a/docs/recipes/browserify-multiple-destination.md +++ b/docs/recipes/browserify-multiple-destination.md @@ -16,7 +16,7 @@ var uglify = require('gulp-uglify'); gulp.task('js', function () { - return gulp.src('src/**/*.js', {base: 'src'}) + return gulp.src('src/**/*.js', {read: false}) // no need of reading file because browserify does. // transform file objects using gulp-tap plugin .pipe(tap(function (file) { From 59ddc255496b5b5881c5f471973c1842b5ac7a4c Mon Sep 17 00:00:00 2001 From: Logan Arnett Date: Wed, 3 Feb 2016 10:14:40 -0500 Subject: [PATCH 004/216] make comments easier to comprehend --- docs/recipes/running-task-steps-per-folder.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/recipes/running-task-steps-per-folder.md b/docs/recipes/running-task-steps-per-folder.md index b1bf4632c..2a3113eed 100644 --- a/docs/recipes/running-task-steps-per-folder.md +++ b/docs/recipes/running-task-steps-per-folder.md @@ -40,17 +40,12 @@ gulp.task('scripts', function() { var folders = getFolders(scriptsPath); var tasks = folders.map(function(folder) { - // concat into foldername.js - // write to output - // minify - // rename to folder.min.js - // write to output again return gulp.src(path.join(scriptsPath, folder, '/**/*.js')) - .pipe(concat(folder + '.js')) - .pipe(gulp.dest(scriptsPath)) - .pipe(uglify()) - .pipe(rename(folder + '.min.js')) - .pipe(gulp.dest(scriptsPath)); + .pipe(concat(folder + '.js')) // concat into foldername.js + .pipe(gulp.dest(scriptsPath)) // write to output + .pipe(uglify()) // minify + .pipe(rename(folder + '.min.js')) // rename to folder.min.js + .pipe(gulp.dest(scriptsPath)); // write to output again }); // process all remaining files in scriptsPath root into main.js and main.min.js files From 9b697393e34a170fe65335f578b7a7a6d8aa94d8 Mon Sep 17 00:00:00 2001 From: Logan Arnett Date: Wed, 3 Feb 2016 14:01:35 -0500 Subject: [PATCH 005/216] comments above each pipe --- docs/recipes/running-task-steps-per-folder.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/recipes/running-task-steps-per-folder.md b/docs/recipes/running-task-steps-per-folder.md index 2a3113eed..905f697b9 100644 --- a/docs/recipes/running-task-steps-per-folder.md +++ b/docs/recipes/running-task-steps-per-folder.md @@ -41,11 +41,16 @@ gulp.task('scripts', function() { var tasks = folders.map(function(folder) { return gulp.src(path.join(scriptsPath, folder, '/**/*.js')) - .pipe(concat(folder + '.js')) // concat into foldername.js - .pipe(gulp.dest(scriptsPath)) // write to output - .pipe(uglify()) // minify - .pipe(rename(folder + '.min.js')) // rename to folder.min.js - .pipe(gulp.dest(scriptsPath)); // write to output again + // concat into foldername.js + .pipe(concat(folder + '.js')) + // write to output + .pipe(gulp.dest(scriptsPath)) + // minify + .pipe(uglify()) + // rename to folder.min.js + .pipe(rename(folder + '.min.js')) + // write to output again + .pipe(gulp.dest(scriptsPath)); }); // process all remaining files in scriptsPath root into main.js and main.min.js files From b842fdf8e0477a03026efd1eb7d462fdcbda145e Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Tue, 9 Feb 2016 11:56:19 -0700 Subject: [PATCH 006/216] update changelog for @code-chris - closes #1521 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9846eab7..74e9767d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # gulp changelog +## 3.9.1 + +- update interpret to 1.0.0 (support for babel-register) +- fix to include manpages in published tarball +- documentation/recipe updates + ## 3.9.0 - add babel support From bec9ab00e8f59c890792af8614d8d958189cc592 Mon Sep 17 00:00:00 2001 From: Alexandr Ishchenko Date: Wed, 9 Mar 2016 18:37:53 +0300 Subject: [PATCH 007/216] Update README.md --- docs/writing-a-plugin/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md index 8072d618d..e7a2c0ee9 100644 --- a/docs/writing-a-plugin/README.md +++ b/docs/writing-a-plugin/README.md @@ -38,7 +38,7 @@ module.exports = function() { var error = null, output = doSomethingWithTheFile(file); callback(error, output); - }); + }; return transformStream; }; From da47c760ce2f4d5d3ad767a587557f2cd29411fa Mon Sep 17 00:00:00 2001 From: kdex Date: Tue, 5 Apr 2016 04:46:55 +0200 Subject: [PATCH 008/216] Add undocumented event.type This event type is fired when renaming a file. --- docs/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API.md b/docs/API.md index 0129f58d1..5b9fe1252 100644 --- a/docs/API.md +++ b/docs/API.md @@ -295,7 +295,7 @@ The callback will be passed an object, `event`, that describes the change: ##### event.type Type: `String` -The type of change that occurred, either `added`, `changed` or `deleted`. +The type of change that occurred, either `added`, `changed`, `deleted` or `renamed`. ##### event.path Type: `String` From 5585720cbfa927f041602088d27b551659916e88 Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Tue, 5 Apr 2016 12:37:35 -0700 Subject: [PATCH 009/216] fix links after repos were moved --- docs/writing-a-plugin/README.md | 48 ++++++++++---------- docs/writing-a-plugin/guidelines.md | 4 +- docs/writing-a-plugin/recommended-modules.md | 2 +- docs/writing-a-plugin/testing.md | 4 +- docs/writing-a-plugin/using-buffers.md | 2 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md index e7a2c0ee9..d172b9af0 100644 --- a/docs/writing-a-plugin/README.md +++ b/docs/writing-a-plugin/README.md @@ -13,11 +13,11 @@ If you plan to create your own Gulp plugin, you will save time by reading the fu A gulp plugin always returns a stream in [object mode](http://nodejs.org/api/stream.html#stream_object_mode) that does the following: -1. Takes in [vinyl File objects](http://github.com/wearefractal/vinyl) -2. Outputs [vinyl File objects](http://github.com/wearefractal/vinyl) (via `transform.push()` and/or the plugin's callback function) +1. Takes in [vinyl File objects](http://github.com/gulpjs/vinyl) +2. Outputs [vinyl File objects](http://github.com/gulpjs/vinyl) (via `transform.push()` and/or the plugin's callback function) -These are known as [transform streams](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) -(also sometimes called through streams). +These are known as [transform streams](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) +(also sometimes called through streams). Transform streams are streams that are readable and writable; they manipulate objects as they're being passed through. All gulp plugins essentially boil down to this: @@ -25,21 +25,21 @@ All gulp plugins essentially boil down to this: var Transform = require('transform'); module.exports = function() { - // Monkey patch Transform or create your own subclass, + // Monkey patch Transform or create your own subclass, // implementing `_transform()` and optionally `_flush()` var transformStream = new Transform({objectMode: true}); /** * @param {Buffer|string} file * @param {string=} encoding - ignored if file contains a Buffer - * @param {function(Error, object)} callback - Call this function (optionally with an + * @param {function(Error, object)} callback - Call this function (optionally with an * error argument and data) when you are done processing the supplied chunk. */ transformStream._transform = function(file, encoding, callback) { - var error = null, + var error = null, output = doSomethingWithTheFile(file); callback(error, output); }; - + return transformStream; }; ``` @@ -59,13 +59,13 @@ module.exports = function() { The stream returned from `through()` (and `this` within your transform function) is an instance of the [Transform](https://github.com/iojs/readable-stream/blob/master/lib/_stream_transform.js) class, which extends [Duplex](https://github.com/iojs/readable-stream/blob/master/lib/_stream_duplex.js), [Readable](https://github.com/iojs/readable-stream/blob/master/lib/_stream_readable.js) -(and parasitically from Writable) and ultimately [Stream](https://nodejs.org/api/stream.html). +(and parasitically from Writable) and ultimately [Stream](https://nodejs.org/api/stream.html). If you need to parse additional options, you can call the `through()` function directly: ```js return through({objectMode: true /* other options... */}, function(file, encoding, callback) { ... ``` - + Supported options include: * highWaterMark (defaults to 16) @@ -82,19 +82,19 @@ The function parameter that you pass to `through.obj()` is a [_transform](https: function which will operate on the input `file`. You may also provide an optional [_flush](https://nodejs.org/api/stream.html#stream_transform_flush_callback) function if you need to emit a bit more data at the end of the stream. -From within your transform function call `this.push(file)` 0 or more times to pass along transformed/cloned files. +From within your transform function call `this.push(file)` 0 or more times to pass along transformed/cloned files. You don't need to call `this.push(file)` if you provide all output to the `callback()` function. -Call the `callback` function only when the current file (stream/buffer) is completely consumed. -If an error is encountered, pass it as the first argument to the callback, otherwise set it to null. +Call the `callback` function only when the current file (stream/buffer) is completely consumed. +If an error is encountered, pass it as the first argument to the callback, otherwise set it to null. If you have passed all output data to `this.push()` you can omit the second argument to the callback. Generally, a gulp plugin would update `file.contents` and then choose to either: - - call `callback(null, file)` - _or_ + - call `callback(null, file)` + _or_ - make one call to `this.push(file)` - + If a plugin creates multiple files from a single input file, it would make multiple calls to `this.push()` - eg: ```js @@ -105,10 +105,10 @@ module.exports = function() { var transform = function(file, encoding, callback) { var files = splitFile(file); this.push(files[0]); - this.push(files[1]); + this.push(files[1]); callback(); - }; - + }; + return through.obj(transform); }; ``` @@ -141,14 +141,14 @@ module.exports = function() { if (file.isStream()) { // file.contents is a Stream - https://nodejs.org/api/stream.html this.emit('error', new PluginError(PLUGIN_NAME, 'Streams not supported!')); - + // or, if you can handle Streams: //file.contents = file.contents.pipe(... //return callback(null, file); } else if (file.isBuffer()) { // file.contents is a Buffer - https://nodejs.org/api/buffer.html this.emit('error', new PluginError(PLUGIN_NAME, 'Buffers not supported!')); - + // or, if you can handle Buffers: //file.contents = ... //return callback(null, file); @@ -176,17 +176,17 @@ if (someCondition) { ## Useful resources -* [File object](https://github.com/wearefractal/gulp-util/#new-fileobj) +* [File object](https://github.com/gulpjs/gulp-util/#new-fileobj) * [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) * [event-stream](https://github.com/dominictarr/event-stream) * [BufferStream](https://github.com/nfroidure/BufferStream) -* [gulp-util](https://github.com/wearefractal/gulp-util) +* [gulp-util](https://github.com/gulpjs/gulp-util) ## Sample plugins * [sindresorhus' gulp plugins](https://github.com/search?q=%40sindresorhus+gulp-) -* [Fractal's gulp plugins](https://github.com/search?q=%40wearefractal+gulp-) +* [contra's gulp plugins](https://github.com/search?q=%40contra+gulp-) * [gulp-replace](https://github.com/lazd/gulp-replace) diff --git a/docs/writing-a-plugin/guidelines.md b/docs/writing-a-plugin/guidelines.md index 014bff1fb..158b1f80b 100644 --- a/docs/writing-a-plugin/guidelines.md +++ b/docs/writing-a-plugin/guidelines.md @@ -13,7 +13,7 @@ - Avoid config options that make your plugin do completely different tasks - For example: A JS minification plugin should not have an option that adds a header as well 1. Your plugin shouldn't do things that other plugins are responsible for - - It should not concat, [gulp-concat](https://github.com/wearefractal/gulp-concat) does that + - It should not concat, [gulp-concat](https://github.com/contra/gulp-concat) does that - It should not add headers, [gulp-header](https://github.com/godaddy/gulp-header) does that - It should not add footers, [gulp-footer](https://github.com/godaddy/gulp-footer) does that - If it's a common but optional use case, document that your plugin is often used with another plugin @@ -35,7 +35,7 @@ - If file.contents is a Stream and you don't support that just emit an error - Do not buffer a stream to shoehorn your plugin to work with streams. This will cause horrible things to happen. 1. Do not pass the `file` object downstream until you are done with it -1. Use [`file.clone()`](https://github.com/wearefractal/vinyl#clone) when cloning a file or creating a new one based on a file. +1. Use [`file.clone()`](https://github.com/gulpjs/vinyl#clone) when cloning a file or creating a new one based on a file. 1. Use modules from our [recommended modules page](recommended-modules.md) to make your life easier 1. Do NOT require `gulp` as a dependency or peerDependency in your plugin - Using gulp to test or automate your plugin workflow is totally cool, just make sure you put it as a devDependency diff --git a/docs/writing-a-plugin/recommended-modules.md b/docs/writing-a-plugin/recommended-modules.md index 4e027e7cf..ca1d7e71b 100644 --- a/docs/writing-a-plugin/recommended-modules.md +++ b/docs/writing-a-plugin/recommended-modules.md @@ -10,7 +10,7 @@ Use [replace-ext](https://github.com/wearefractal/replace-ext) #### Errors -Use [BetterError](https://github.com/wearefractal/BetterError) when it is finished +Use [BetterError](https://github.com/contra/BetterError) when it is finished #### String colors diff --git a/docs/writing-a-plugin/testing.md b/docs/writing-a-plugin/testing.md index 573d8c64a..487a87f95 100644 --- a/docs/writing-a-plugin/testing.md +++ b/docs/writing-a-plugin/testing.md @@ -94,8 +94,8 @@ describe('gulp-prefixer', function() { }); ``` - + ## Some plugins with high-quality Testing * [gulp-cat](https://github.com/ben-eb/gulp-cat/blob/master/test.js) -* [gulp-concat](https://github.com/wearefractal/gulp-concat/blob/master/test/main.js) +* [gulp-concat](https://github.com/contra/gulp-concat/blob/master/test/main.js) diff --git a/docs/writing-a-plugin/using-buffers.md b/docs/writing-a-plugin/using-buffers.md index ff58b1eb7..ae021448b 100644 --- a/docs/writing-a-plugin/using-buffers.md +++ b/docs/writing-a-plugin/using-buffers.md @@ -66,7 +66,7 @@ Unfortunately, the above plugin will error when using gulp.src in non-buffered ( ## Some plugins based on buffers -* [gulp-coffee](https://github.com/wearefractal/gulp-coffee) +* [gulp-coffee](https://github.com/contra/gulp-coffee) * [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin) * [gulp-marked](https://github.com/lmtm/gulp-marked) * [gulp-svg2ttf](https://github.com/nfroidure/gulp-svg2ttf) From e90e289d872c58180df3e5e7a026c9dd187c62f8 Mon Sep 17 00:00:00 2001 From: Christian Theilemann Date: Tue, 5 Apr 2016 13:12:08 -0700 Subject: [PATCH 010/216] add `run-grunt-tasks-from-gulp` recipe --- docs/recipes/run-grunt-tasks-from-gulp.md | 48 +++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/recipes/run-grunt-tasks-from-gulp.md diff --git a/docs/recipes/run-grunt-tasks-from-gulp.md b/docs/recipes/run-grunt-tasks-from-gulp.md new file mode 100644 index 000000000..80e54a448 --- /dev/null +++ b/docs/recipes/run-grunt-tasks-from-gulp.md @@ -0,0 +1,48 @@ +# Run Grunt Tasks from Gulp + +It is possible to run Grunt tasks / Grunt plugins from within Gulp. This can be useful during a gradual migration from Grunt to Gulp or if there's a specific plugin that you need. With the described approach no Grunt CLI and no Gruntfile is required. + +**This approach requires Grunt >=1.0.0** + +very simple example `gulpfile.js`: + +```js +// npm install gulp grunt grunt-contrib-copy --save-dev + +var gulp = require('gulp'); +var grunt = require('grunt'); + +grunt.initConfig({ + copy: { + main: { + src: 'src/*', + dest: 'dest/' + } + } +}); +grunt.loadNpmTasks('grunt-contrib-copy'); + +gulp.task('copy', function (done) { + grunt.tasks( + ['copy:main'], //you can add more grunt tasks in this array + {gruntfile: false}, //don't look for a Gruntfile - there is none. :-) + function () {done();} + ); +}); + +``` + +Now start the task with: +`gulp copy` + +With the aforementioned approach the grunt tasks get registered within gulp's task system. **Keep in mind grunt tasks are usually blocking (unlike gulp), therefore no other task (not even a gulp task) can run until a grunt task is completed.** + + +### A few words on alternatives + +There's a *gulpfriendly* node module `gulp-grunt` [available](https://www.npmjs.org/package/gulp-grunt) which takes a different approach. It spawns child processes and within them the grunt tasks are executed. The way it works implies some limitations though: + +* It is at the moment not possible to pass options / cli args etc. to the grunt tasks via `gulp-grunt` +* All grunt tasks have to be defined in a seperate Gruntfile +* You need to have the Grunt CLI installed +* The output of some grunt tasks gets malformatted (.i.e. color coding). From 55dec395fc52b27f78aab5928def2c1048f0010c Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Wed, 6 Apr 2016 16:58:47 -0700 Subject: [PATCH 011/216] fix remaining links + add missing link to recipe --- docs/API.md | 16 ++++++++-------- docs/recipes/README.md | 1 + docs/recipes/specifying-a-cwd.md | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/API.md b/docs/API.md index 5b9fe1252..0c03c2e3f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -8,9 +8,9 @@ Jump to: ### gulp.src(globs[, options]) -Emits files matching provided glob or an array of globs. -Returns a [stream](http://nodejs.org/api/stream.html) of [Vinyl files](https://github.com/wearefractal/vinyl-fs) -that can be [piped](http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) +Emits files matching provided glob or an array of globs. +Returns a [stream](http://nodejs.org/api/stream.html) of [Vinyl files](https://github.com/gulpjs/vinyl-fs) +that can be [piped](http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) to plugins. ```javascript @@ -35,7 +35,7 @@ A glob that begins with `!` excludes matching files from the glob results up to The following expression matches `a.js` and `bad.js`: gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js']) - + #### options Type: `Object` @@ -85,13 +85,13 @@ gulp.src('./client/templates/*.jade') ``` The write path is calculated by appending the file relative path to the given -destination directory. In turn, relative paths are calculated against the file base. +destination directory. In turn, relative paths are calculated against the file base. See `gulp.src` above for more info. #### path Type: `String` or `Function` -The path (output folder) to write files to. Or a function that returns it, the function will be provided a [vinyl File instance](https://github.com/wearefractal/vinyl). +The path (output folder) to write files to. Or a function that returns it, the function will be provided a [vinyl File instance](https://github.com/gulpjs/vinyl). #### options Type: `Object` @@ -156,7 +156,7 @@ gulp.task('buildStuff', function() { .pipe(somePlugin()) .pipe(someOtherPlugin()) .pipe(gulp.dest(/*some destination*/)); - + return stream; }); ``` @@ -306,7 +306,7 @@ The path to the file that triggered the event. [node-glob]: https://github.com/isaacs/node-glob [node-glob documentation]: https://github.com/isaacs/node-glob#options [node-glob syntax]: https://github.com/isaacs/node-glob -[glob-stream]: https://github.com/wearefractal/glob-stream +[glob-stream]: https://github.com/gulpjs/glob-stream [gulp-if]: https://github.com/robrich/gulp-if [Orchestrator]: https://github.com/robrich/orchestrator [glob2base]: https://github.com/wearefractal/glob2base diff --git a/docs/recipes/README.md b/docs/recipes/README.md index a2b79be07..9e8124bc7 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -23,4 +23,5 @@ * [Browserify + Globs (multiple destination)](browserify-multiple-destination.md) * [Output both a minified and non-minified version](minified-and-non-minified.md) * [Templating with Swig and YAML front-matter](templating-with-swig-and-yaml-front-matter.md) +* [Run Grunt Tasks from Gulp](run-grunt-tasks-from-gulp.md) * [Exports as tasks](exports-as-tasks.md) diff --git a/docs/recipes/specifying-a-cwd.md b/docs/recipes/specifying-a-cwd.md index d3858fcd7..eba0ceca9 100644 --- a/docs/recipes/specifying-a-cwd.md +++ b/docs/recipes/specifying-a-cwd.md @@ -16,7 +16,7 @@ From the `project/` directory: gulp --cwd layer1 ``` -If you only need to specify a cwd for a certain glob, you can use the `cwd` option on a [glob-stream](https://github.com/wearefractal/glob-stream): +If you only need to specify a cwd for a certain glob, you can use the `cwd` option on a [glob-stream](https://github.com/gulpjs/glob-stream): ```js gulp.src('./some/dir/**/*.js', { cwd: 'public' }); From b2915d7fa094475e42bf0400dacd0070029051c8 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Fri, 8 Apr 2016 12:34:05 -0700 Subject: [PATCH 012/216] Surface 'on' listener functionality (#1424) * Surface 'on' listener functionality * change to fit with existing structure of examples --- docs/API.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/API.md b/docs/API.md index 0c03c2e3f..4bb90bbe8 100644 --- a/docs/API.md +++ b/docs/API.md @@ -177,6 +177,17 @@ gulp.task('jekyll', function(cb) { cb(); // finished task }); }); + +// use an async result in a pipe +gulp.task('somename', function(cb) { + getFilesAsync(function(err, res) { + if (err) return cb(err); + var stream = gulp.src(res) + .pipe(minify()) + .pipe(gulp.dest('build')) + .on('end', cb); + }); +}); ``` ##### Return a stream From 9f1b90f7c1c16be8aef9f290576aedbb66e1548a Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 8 Apr 2016 22:35:31 +0300 Subject: [PATCH 013/216] =?UTF-8?q?Updated=20=C2=ABSplit=20tasks=20across?= =?UTF-8?q?=20multiple=20files=C2=BB=20recipe=20(#1585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated «Split tasks across multiple files» recipe. * Added deprecation notice. --- .../split-tasks-across-multiple-files.md | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/recipes/split-tasks-across-multiple-files.md b/docs/recipes/split-tasks-across-multiple-files.md index 023891976..fb820ad1d 100644 --- a/docs/recipes/split-tasks-across-multiple-files.md +++ b/docs/recipes/split-tasks-across-multiple-files.md @@ -1,8 +1,22 @@ # Split tasks across multiple files -If your `gulpfile.js` is starting to grow too large, you can split the tasks -into separate files by using the [require-dir](https://github.com/aseemk/requireDir) -module. +If your `gulpfile.js` is starting to grow too large, you can split +the tasks into separate files using one of the methods below. + +> Be advised, that this approach is [considered deprecated][deprecated] +> and could lead to problems when migrating to the `gulp 4`. + + +## Using `gulp-require-tasks` + +You can use the [gulp-require-tasks][gulp-require-tasks] +module to automatically load all your tasks from the individual files. + +Please see the [module's README][gulp-require-tasks] for up-to-date instructions. + +## Using `require-dir` + +You can also use the [require-dir][require-dir] module to load your tasks manually. Imagine the following file structure: @@ -26,3 +40,8 @@ Add the following lines to your `gulpfile.js` file: var requireDir = require('require-dir'); var tasks = requireDir('./tasks'); ``` + + + [gulp-require-tasks]: https://github.com/betsol/gulp-require-tasks + [require-dir]: https://github.com/aseemk/requireDir + [deprecated]: https://github.com/gulpjs/gulp/pull/1554#issuecomment-202614391 From db86eedc1a7ae306ec0813e4d554b95a930ecea9 Mon Sep 17 00:00:00 2001 From: Adrian Sieber Date: Wed, 13 Apr 2016 18:46:39 +0000 Subject: [PATCH 014/216] Fix typos --- docs/recipes/run-grunt-tasks-from-gulp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes/run-grunt-tasks-from-gulp.md b/docs/recipes/run-grunt-tasks-from-gulp.md index 80e54a448..cdd4f6861 100644 --- a/docs/recipes/run-grunt-tasks-from-gulp.md +++ b/docs/recipes/run-grunt-tasks-from-gulp.md @@ -43,6 +43,6 @@ With the aforementioned approach the grunt tasks get registered within gulp's ta There's a *gulpfriendly* node module `gulp-grunt` [available](https://www.npmjs.org/package/gulp-grunt) which takes a different approach. It spawns child processes and within them the grunt tasks are executed. The way it works implies some limitations though: * It is at the moment not possible to pass options / cli args etc. to the grunt tasks via `gulp-grunt` -* All grunt tasks have to be defined in a seperate Gruntfile +* All grunt tasks have to be defined in a separate Gruntfile * You need to have the Grunt CLI installed * The output of some grunt tasks gets malformatted (.i.e. color coding). From 2395467ed4e4942b24763f421177eab683924bfe Mon Sep 17 00:00:00 2001 From: dargaCode Date: Mon, 25 Apr 2016 14:01:30 -0700 Subject: [PATCH 015/216] Fix small typo (#1612) Replace `it's` with `its` --- docs/recipes/browserify-with-globs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes/browserify-with-globs.md b/docs/recipes/browserify-with-globs.md index 0285c1f5e..ad7df6f83 100644 --- a/docs/recipes/browserify-with-globs.md +++ b/docs/recipes/browserify-with-globs.md @@ -1,6 +1,6 @@ # Browserify + Globs -[Browserify + Uglify2](https://github.com/gulpjs/gulp/blob/master/docs/recipes/browserify-uglify-sourcemap.md) shows how to setup a basic gulp task to bundle a JavaScript file with it's dependencies, and minify the bundle with UglifyJS while preserving source maps. +[Browserify + Uglify2](https://github.com/gulpjs/gulp/blob/master/docs/recipes/browserify-uglify-sourcemap.md) shows how to setup a basic gulp task to bundle a JavaScript file with its dependencies, and minify the bundle with UglifyJS while preserving source maps. It does not, however, show how one may use gulp and Browserify with multiple entry files. See also: the [Combining Streams to Handle Errors](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) recipe for handling errors with Browserify or UglifyJS in your stream. From 02fe60fe14cf37c213ef79b2c48e4382aae6e7ee Mon Sep 17 00:00:00 2001 From: Jaron Thatcher Date: Wed, 27 Apr 2016 15:25:02 -0700 Subject: [PATCH 016/216] Bottom example is now runnable (#1615) * Bottom example is now runnable Previously, the app would throw the error: "Task 'default' is not in your gulpfile" * Revert change to glob The change is now closer to the original example, yet still contains the intended reason for the pull request --- docs/recipes/mocha-test-runner-with-gulp.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/recipes/mocha-test-runner-with-gulp.md b/docs/recipes/mocha-test-runner-with-gulp.md index d7cd96e48..69788aad8 100644 --- a/docs/recipes/mocha-test-runner-with-gulp.md +++ b/docs/recipes/mocha-test-runner-with-gulp.md @@ -28,13 +28,13 @@ var gulp = require('gulp'); var mocha = require('gulp-mocha'); var gutil = require('gulp-util'); +gulp.task('default', function() { + gulp.watch(['lib/**', 'test/**'], ['mocha']); +}); + gulp.task('mocha', function() { return gulp.src(['test/*.js'], { read: false }) .pipe(mocha({ reporter: 'list' })) .on('error', gutil.log); }); - -gulp.task('watch-mocha', function() { - gulp.watch(['lib/**', 'test/**'], ['mocha']); -}); ``` From 7f5ac012315fca6a3ad8105b6f50e8143b237c94 Mon Sep 17 00:00:00 2001 From: Jonathan Lee Date: Tue, 3 May 2016 16:23:16 -0400 Subject: [PATCH 017/216] #1445 - adding note to let folks know this only works with the latest published gulp-cli version (#1632) --- docs/recipes/exports-as-tasks.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/recipes/exports-as-tasks.md b/docs/recipes/exports-as-tasks.md index 5ea06fd44..2eaea4905 100644 --- a/docs/recipes/exports-as-tasks.md +++ b/docs/recipes/exports-as-tasks.md @@ -18,3 +18,5 @@ export default function dev() { gulp.watch('src/*.js', ['build']); } ``` + +This will **not** work with the gulp-cli version bundled with gulp 3.x. You must use the latest published version. From 5944c1b4d4c0a9dea804bc426b936aa742792cd4 Mon Sep 17 00:00:00 2001 From: mako yass Date: Sat, 14 May 2016 06:50:29 +1200 Subject: [PATCH 018/216] correct gulp.task signature (#1653) * correct gulp.task signature `gulp.task(name [, deps, fn])` means that only `gulp.task(name, deps, fn)` and `gulp.task(name)` are allowed, which is apparently not the case and it's a bit confusing. The correct notation, `gulp.task(name [, deps] [, fn])`, also permits for `gulp.task(name, deps)`, and `gulp.task(name, fn)`. * fixed indexing --- docs/API.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/API.md b/docs/API.md index 4bb90bbe8..d06906f13 100644 --- a/docs/API.md +++ b/docs/API.md @@ -3,7 +3,7 @@ Jump to: [gulp.src](#gulpsrcglobs-options) | [gulp.dest](#gulpdestpath-options) | - [gulp.task](#gulptaskname--deps-fn) | + [gulp.task](#gulptaskname--deps--fn) | [gulp.watch](#gulpwatchglob--opts-tasks-or-gulpwatchglob--opts-cb) ### gulp.src(globs[, options]) @@ -108,7 +108,7 @@ Default: `0777` Octal permission string specifying mode for any folders that need to be created for output folder. -### gulp.task(name [, deps, fn]) +### gulp.task(name [, deps] [, fn]) Define a task using [Orchestrator]. From f6e85a85ad4e7253dbd179ac6195c9b2ae59baa9 Mon Sep 17 00:00:00 2001 From: Alejandro Oviedo Date: Tue, 17 May 2016 15:26:36 -0300 Subject: [PATCH 019/216] link build badge to master branch (#1666) ...and change gitter image badge to svg --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b67798c75..db07c9ef0 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,10 @@ Anyone can help make this project better - check out our [Contributing guide](/C [npm-image]: https://img.shields.io/npm/v/gulp.svg [travis-url]: https://travis-ci.org/gulpjs/gulp -[travis-image]: https://img.shields.io/travis/gulpjs/gulp.svg +[travis-image]: https://img.shields.io/travis/gulpjs/gulp/master.svg [coveralls-url]: https://coveralls.io/r/gulpjs/gulp [coveralls-image]: https://img.shields.io/coveralls/gulpjs/gulp/master.svg [gitter-url]: https://gitter.im/gulpjs/gulp -[gitter-image]: https://badges.gitter.im/gulpjs/gulp.png +[gitter-image]: https://badges.gitter.im/gulpjs/gulp.svg From 4ec7e71f39fe8ab3c95b81b3266317366252e8e7 Mon Sep 17 00:00:00 2001 From: Trey Thomas Date: Thu, 19 May 2016 13:36:47 -0600 Subject: [PATCH 020/216] Fix typo in README.md (#1670) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db07c9ef0..713bc5e8d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ## Documentation -For a Getting started guide, API docs, recipes, making a plugin, etc. check out or docs! +For a Getting started guide, API docs, recipes, making a plugin, etc. check out our docs! - Need something reliable? Check out the [documentation for the current release](/docs/README.md)! - Want to help us test the latest and greatest? Check out the [documentation for the next release](https://github.com/gulpjs/gulp/tree/4.0)! From 362306150fc5be48a61df0d950c83cca356bd593 Mon Sep 17 00:00:00 2001 From: MikailBag Date: Sun, 19 Jun 2016 04:57:52 +0300 Subject: [PATCH 021/216] Build: CI test under node version 5 and 6 (#1692) --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b60f49053..5d5228b1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,8 @@ node_js: - "0.10" - "0.12" - "4" - - "stable" + - "5" + - "6" after_script: - npm run coveralls git: From b42acd91ae5785c88d37218787f0d85089e8f705 Mon Sep 17 00:00:00 2001 From: Permutator Date: Sat, 18 Jun 2016 20:31:42 -0700 Subject: [PATCH 022/216] Docs: Add "Rollup with rollup-stream" recipe. (#1690) --- docs/recipes/README.md | 1 + docs/recipes/rollup-with-rollup-stream.md | 62 +++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 docs/recipes/rollup-with-rollup-stream.md diff --git a/docs/recipes/README.md b/docs/recipes/README.md index 9e8124bc7..b532750ae 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -25,3 +25,4 @@ * [Templating with Swig and YAML front-matter](templating-with-swig-and-yaml-front-matter.md) * [Run Grunt Tasks from Gulp](run-grunt-tasks-from-gulp.md) * [Exports as tasks](exports-as-tasks.md) +* [Rollup with rollup-stream](rollup-with-rollup-stream.md) diff --git a/docs/recipes/rollup-with-rollup-stream.md b/docs/recipes/rollup-with-rollup-stream.md new file mode 100644 index 000000000..abee2dd32 --- /dev/null +++ b/docs/recipes/rollup-with-rollup-stream.md @@ -0,0 +1,62 @@ +# Rollup with rollup-stream + +Like Browserify, [Rollup](http://rollupjs.org/) is a bundler and thus only fits naturally into gulp if it's at the start of the pipeline. Unlike Browserify, Rollup doesn't natively produce a stream as output and needs to be wrapped before it can take this position. [rollup-stream](https://github.com/Permutatrix/rollup-stream) does this for you, producing output just like that of Browserify's `bundle()` method—as a result, most of the Browserify recipes here will also work with rollup-stream. + +## Basic usage +```js +// npm install --save-dev rollup-stream vinyl-source-stream +var gulp = require('gulp'); +var rollup = require('rollup-stream'); +var source = require('vinyl-source-stream'); + +gulp.task('rollup', function() { + return rollup({ + entry: './src/main.js' + }) + + // give the file the name you want to output with + .pipe(source('app.js')) + + // and output to ./dist/app.js as normal. + .pipe(gulp.dest('./dist')); +}); +``` + +## Usage with sourcemaps +```js +// npm install --save-dev rollup-stream gulp-sourcemaps vinyl-source-stream vinyl-buffer +// optional: npm install --save-dev gulp-rename +var gulp = require('gulp'); +var rollup = require('rollup-stream'); +var sourcemaps = require('gulp-sourcemaps'); +//var rename = require('gulp-rename'); +var source = require('vinyl-source-stream'); +var buffer = require('vinyl-buffer'); + +gulp.task('rollup', function() { + return rollup({ + entry: './src/main.js', + sourceMap: true + }) + + // point to the entry file. + .pipe(source('main.js', './src')) + + // buffer the output. most gulp plugins, including gulp-sourcemaps, don't support streams. + .pipe(buffer()) + + // tell gulp-sourcemaps to load the inline sourcemap produced by rollup-stream. + .pipe(sourcemaps.init({loadMaps: true})) + + // transform the code further here. + + // if you want to output with a different name from the input file, use gulp-rename here. + //.pipe(rename('index.js')) + + // write the sourcemap alongside the output file. + .pipe(sourcemaps.write('.')) + + // and output to ./dist/main.js as normal. + .pipe(gulp.dest('./dist')); +}); +``` From 71953b58ee8077ee882272d1834e3f0ded9a2b92 Mon Sep 17 00:00:00 2001 From: Cerem Cem ASLAN Date: Wed, 29 Jun 2016 00:43:14 +0300 Subject: [PATCH 023/216] Docs: Add npm init step to Getting Started (#1706) --- docs/getting-started.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index ef23c5945..cb3ecf553 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -9,13 +9,19 @@ to make sure your old version doesn't collide with gulp-cli.__ $ npm install --global gulp-cli ``` -#### 2. Install gulp in your project devDependencies: +#### 2. Initialize your project directory: + +```sh +$ npm init +``` + +#### 3. Install gulp in your project devDependencies: ```sh $ npm install --save-dev gulp ``` -#### 3. Create a `gulpfile.js` at the root of your project: +#### 4. Create a `gulpfile.js` at the root of your project: ```js var gulp = require('gulp'); @@ -25,7 +31,7 @@ gulp.task('default', function() { }); ``` -#### 4. Run gulp: +#### 5. Run gulp: ```sh $ gulp From 347ed5a731fe4b479d72c35d38503680621c316a Mon Sep 17 00:00:00 2001 From: Pia Mancini Date: Thu, 30 Jun 2016 15:06:53 -0600 Subject: [PATCH 024/216] Docs: Add backers and sponsors from OpenCollective (#1705) Your open collective backers & sponsors will show up on your readme automatically. Also added badges on top. --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 713bc5e8d..3789d085f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@

The streaming build system

-[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![Gitter chat][gitter-image]][gitter-url] +[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![OpenCollective Backers][backer-badge]][backer-url] [![OpenCollective Sponsors][sponsor-badge]][sponsor-url] [![Gitter chat][gitter-image]][gitter-url] + ## What is gulp? @@ -89,6 +90,18 @@ We recommend these plugins: Anyone can help make this project better - check out our [Contributing guide](/CONTRIBUTING.md)! +## Backers + +Support us with a monthly donation and help us continue our activities. + +[![Backers][backers-image]][support-url] + +## Sponsors + +Become a sponsor to get your logo on our README on Github. + +[![Sponsors][sponsors-image]][support-url] + [downloads-image]: https://img.shields.io/npm/dm/gulp.svg [npm-url]: https://www.npmjs.com/package/gulp [npm-image]: https://img.shields.io/npm/v/gulp.svg @@ -101,3 +114,13 @@ Anyone can help make this project better - check out our [Contributing guide](/C [gitter-url]: https://gitter.im/gulpjs/gulp [gitter-image]: https://badges.gitter.im/gulpjs/gulp.svg + +[backer-url]: #backers +[backer-badge]: https://opencollective.com/gulpjs/backers/badge.svg?color=blue +[sponsor-url]: #sponsors +[sponsor-badge]: https://opencollective.com/gulpjs/sponsors/badge.svg?color=blue + +[support-url]: https://opencollective.com/gulpjs#support + +[backers-image]: https://opencollective.com/gulpjs/backers.svg +[sponsors-image]: https://opencollective.com/gulpjs/sponsors.svg From de1acf66a6253302c856971f8c00f65ea6c63882 Mon Sep 17 00:00:00 2001 From: Derek Date: Fri, 26 Aug 2016 11:20:50 -0700 Subject: [PATCH 025/216] Docs: Fix grammar in dealing-with-streams.md --- docs/writing-a-plugin/dealing-with-streams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/writing-a-plugin/dealing-with-streams.md b/docs/writing-a-plugin/dealing-with-streams.md index ae74dda3b..4c6d53830 100644 --- a/docs/writing-a-plugin/dealing-with-streams.md +++ b/docs/writing-a-plugin/dealing-with-streams.md @@ -2,7 +2,7 @@ > It is highly recommended to write plugins supporting streams. Here is some information on creating a gulp plugin that supports streams. -> Make sure to follow the best practice regarding error handling and add the line that make the gulp plugin re-emit the first error caught during the transformation of the content +> Make sure to follow the best practices regarding error handling and add a line that makes the gulp plugin re-emit the first error caught during the transformation of the content. [Writing a Plugin](README.md) > Writing stream based plugins From 4d1a8a8a86127441ce9a94e9d34f7587fddae389 Mon Sep 17 00:00:00 2001 From: Callum Macrae Date: Fri, 16 Sep 2016 10:46:39 +0100 Subject: [PATCH 026/216] Docs: Create issue template to cover common issues (#1808) --- .github/ISSUE_TEMPLATE.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..1ec2c2c73 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,28 @@ +This tracker is for bug reports only. + + +Before opening an issue, please make sure you've checked the following: + +- For support requests, please use Stack Overflow (stackoverflow.com) or Gitter (see the README). + +- If the bug is in a plugin, open an issue on the plugin repository, not the gulp repository. + +- If you're getting a deprecated module warning, don't worry about it: we're aware of it and it's not an issue. To make it go away, update to Gulp 4.0. + +- If you're asking about the status of Gulp 4, please don't! You can see the remaining issues on the gulp4 label: https://github.com/gulpjs/gulp/issues?q=is%3Aissue+is%3Aopen+label%3Agulp4 + +---- + +**What were you expecting to happen?** + +**What actually happened?** + +**Please post a sample of your gulpfile (preferably reduced to just the bit that's not working)** + +```js +gulp.task(function () {}); +``` + +**What version of gulp are you using?** + +**What versions of npm and node are you using?** From 0469094bb8c4c54e5bb8b2cf5994dd72fa8db713 Mon Sep 17 00:00:00 2001 From: contra Date: Wed, 21 Sep 2016 12:07:51 -0400 Subject: [PATCH 027/216] clarify what an API should look like --- docs/writing-a-plugin/guidelines.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/writing-a-plugin/guidelines.md b/docs/writing-a-plugin/guidelines.md index 158b1f80b..79cb3e703 100644 --- a/docs/writing-a-plugin/guidelines.md +++ b/docs/writing-a-plugin/guidelines.md @@ -22,6 +22,9 @@ - Testing a gulp plugin is easy, you don't even need gulp to test it - Look at other plugins for examples 1. Add `gulpplugin` as a keyword in your `package.json` so you show up on our search +1. Your plugin API should be a function that returns a stream + - If you need to store state somewhere, do it internally + - If you need to pass state/options between plugins, tack it on the file object 1. Do not throw errors inside a stream - Instead, you should emit it as an **error** event. - If you encounter an error **outside** the stream, such as invalid configuration while creating the stream, you may throw it. From 54169eb802228f810e77602b4131be1a2a5e6506 Mon Sep 17 00:00:00 2001 From: Ben Halverson Date: Mon, 7 Nov 2016 19:25:36 -0800 Subject: [PATCH 028/216] Docs: Fix broken gulp-header/gulp-footer links (closes #1851) (#1854) --- docs/writing-a-plugin/guidelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/writing-a-plugin/guidelines.md b/docs/writing-a-plugin/guidelines.md index 79cb3e703..4c924574c 100644 --- a/docs/writing-a-plugin/guidelines.md +++ b/docs/writing-a-plugin/guidelines.md @@ -14,8 +14,8 @@ - For example: A JS minification plugin should not have an option that adds a header as well 1. Your plugin shouldn't do things that other plugins are responsible for - It should not concat, [gulp-concat](https://github.com/contra/gulp-concat) does that - - It should not add headers, [gulp-header](https://github.com/godaddy/gulp-header) does that - - It should not add footers, [gulp-footer](https://github.com/godaddy/gulp-footer) does that + - It should not add headers, [gulp-header](https://www.npmjs.com/package/gulp-header) does that + - It should not add footers, [gulp-footer](https://www.npmjs.com/package/gulp-footer) does that - If it's a common but optional use case, document that your plugin is often used with another plugin - Make use of other plugins within your plugin! This reduces the amount of code you have to write and ensures a stable ecosystem. 1. Your plugin **must be tested** From af33d4b8eaf393854499c0b20ad607c6a1346844 Mon Sep 17 00:00:00 2001 From: Bruno Belotti Date: Tue, 13 Dec 2016 11:45:56 +0000 Subject: [PATCH 029/216] fix link to tagtree video (#1869) tagtree.io instead of tagtree.tv --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 0a7e40897..fbcef8bcd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,7 +29,7 @@ Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagg ## Articles -* [Tagtree intro to gulp video](http://tagtree.tv/gulp) +* [Tagtree intro to gulp video](http://tagtree.io/gulp) * [Introduction to node.js streams](https://github.com/substack/stream-handbook) * [Video introduction to node.js streams](http://www.youtube.com/watch?v=QgEuZ52OZtU) * [Getting started with gulp (by @markgdyr)](http://markgoodyear.com/2014/01/getting-started-with-gulp/) From 6899a6c94ce662e9bdcb3e82f39d3c78138e4c3c Mon Sep 17 00:00:00 2001 From: Mohammad Kermani Date: Thu, 15 Dec 2016 01:28:10 +0330 Subject: [PATCH 030/216] Docs: Update browserify-uglify-sourcemap recipe with clarification (#1813) --- docs/recipes/browserify-uglify-sourcemap.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/recipes/browserify-uglify-sourcemap.md b/docs/recipes/browserify-uglify-sourcemap.md index 467792023..756b89ac3 100644 --- a/docs/recipes/browserify-uglify-sourcemap.md +++ b/docs/recipes/browserify-uglify-sourcemap.md @@ -4,7 +4,9 @@ tool but requires being wrapped before working well with gulp. Below is a simple recipe for using Browserify with full sourcemaps that resolve to the original individual files. -See also: the [Combining Streams to Handle Errors](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) recipe for handling errors with browserify or uglify in your stream. +See also: the [Combining Streams to Handle Errors](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) recipe for handling errors with browserify or uglify in your stream. + +A simple `gulpfile.js` file for Browserify + Uglify2 with sourcemaps: ``` javascript 'use strict'; From 9703acac2b1387e3f23d227ef739443bb6539709 Mon Sep 17 00:00:00 2001 From: contra Date: Fri, 30 Dec 2016 13:15:50 -0500 Subject: [PATCH 031/216] make getting started more readable --- docs/getting-started.md | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index cb3ecf553..e2e8dc83a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,27 +1,24 @@ # Getting Started -#### 1. Install gulp globally: +*If you've previously installed a version of gulp globally, run `npm rm --global gulp` before following these instructions.* -__If you have previously installed a version of gulp globally, please run `npm rm --global gulp` -to make sure your old version doesn't collide with gulp-cli.__ +#### Install the `gulp` command ```sh -$ npm install --global gulp-cli +npm install --global gulp-cli ``` -#### 2. Initialize your project directory: +#### Install `gulp` in your devDependencies -```sh -$ npm init -``` - -#### 3. Install gulp in your project devDependencies: +Run this command in your project directory: ```sh -$ npm install --save-dev gulp +npm install --save-dev gulp ``` -#### 4. Create a `gulpfile.js` at the root of your project: +#### Create a `gulpfile` + +Create a file called `gulpfile.js` in your project root with these contents: ```js var gulp = require('gulp'); @@ -31,19 +28,21 @@ gulp.task('default', function() { }); ``` -#### 5. Run gulp: +#### Test it out + +Run the gulp command in your project directory: ```sh -$ gulp +gulp ``` -The default task will run and do nothing. +Voila! The default task will run and do nothing. -To run individual tasks, use `gulp `. +To run multiple tasks, you can use `gulp `. ## Where do I go now? -You have an empty gulpfile and everything is installed. How do you REALLY get started? Check out the [recipes](recipes) and the [list of articles](README.md#articles) for more information. +You have an empty gulpfile and everything is installed. Check out the [recipes](recipes) and the [list of articles](README.md#articles) for more information. ## .src, .watch, .dest, CLI args - How do I use these things? From 0070cb162abf56f195ac8a0adca8047a249707d1 Mon Sep 17 00:00:00 2001 From: contra Date: Fri, 30 Dec 2016 13:17:43 -0500 Subject: [PATCH 032/216] more readable getting started --- docs/getting-started.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index e2e8dc83a..1b3bab799 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,6 +1,6 @@ # Getting Started -*If you've previously installed a version of gulp globally, run `npm rm --global gulp` before following these instructions.* +*If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions.* #### Install the `gulp` command @@ -42,12 +42,7 @@ To run multiple tasks, you can use `gulp `. ## Where do I go now? -You have an empty gulpfile and everything is installed. Check out the [recipes](recipes) and the [list of articles](README.md#articles) for more information. - -## .src, .watch, .dest, CLI args - How do I use these things? - -For API specific documentation you can check out the [documentation for that](API.md). - -## Available Plugins - -The gulp community is growing, with new plugins being added daily. See the [main website](http://gulpjs.com/plugins/) for a complete list. +- [API Documentation](API.md) +- [Recipes](recipes) +- [Help Articles](README.md#articles) +- [Plugins](http://gulpjs.com/plugins/) From 3f798f5d28782d3ed1da0f25dc9785bb36cc5680 Mon Sep 17 00:00:00 2001 From: contra Date: Wed, 25 Jan 2017 12:20:02 -0500 Subject: [PATCH 033/216] add a little not about testing 4.0 --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 3789d085f..aff68a898 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,16 @@ We recommend these plugins: - [gulp-remember](https://github.com/ahaurw01/gulp-remember) - pairs nicely with gulp-cached - [gulp-newer](https://github.com/tschaub/gulp-newer) - pass through newer source files only, supports many:1 source:dest +## Want to test the latest and greatest? + +We're hard at work on our latest release, but we need your help! Install it, use it, and open issues if anything isn't right! + +```sh +npm install gulpjs/gulp#4.0 +``` + +There's a slew of major (wonderful) changes in 4.0, so make sure you check out the [docs on that branch](https://github.com/gulpjs/gulp/tree/4.0)! + ## Want to contribute? Anyone can help make this project better - check out our [Contributing guide](/CONTRIBUTING.md)! From 325bb225874f86cd9bbb5dafdcdc79dcec9a4ff1 Mon Sep 17 00:00:00 2001 From: contra Date: Wed, 25 Jan 2017 12:21:03 -0500 Subject: [PATCH 034/216] wow that was overexcited --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aff68a898..8a5ddebab 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ We recommend these plugins: ## Want to test the latest and greatest? -We're hard at work on our latest release, but we need your help! Install it, use it, and open issues if anything isn't right! +We're hard at work on our latest release, but we need your help testing it! ```sh npm install gulpjs/gulp#4.0 From 90340b9e64c1e88debc2238fda14b25a4270fb49 Mon Sep 17 00:00:00 2001 From: Mordax Date: Tue, 31 Jan 2017 11:27:27 -0500 Subject: [PATCH 035/216] Replace tags with keywords in package.json (#1894) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b60ba7d52..edf2e2e89 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "homepage": "http://gulpjs.com", "repository": "gulpjs/gulp", "author": "Fractal (http://wearefractal.com/)", - "tags": [ + "keywords": [ "build", "stream", "system", From 62323fc55dd080a58ed030c5e9df176bdf551eae Mon Sep 17 00:00:00 2001 From: Kyle Cordes Date: Thu, 23 Feb 2017 16:26:44 -0600 Subject: [PATCH 036/216] Docs: Clarify CLI semantics when listing more than one task (#1916) --- docs/CLI.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/CLI.md b/docs/CLI.md index 46c1ffb9d..a536c913f 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -22,7 +22,20 @@ Refer to this [StackOverflow](http://stackoverflow.com/questions/23023650/is-it- ### Tasks -Tasks can be executed by running `gulp `. Just running `gulp` will execute the task you registered called `default`. If there is no `default` task gulp will error. +Tasks can be executed by running `gulp ...`. + +If more than one task is listed, Gulp will execute all of them +concurrently, that is, as if they had all been listed as dependencies of +a single task. + +Gulp does not serialize tasks listed on the command line. From using +other comparable tools users may expect to execute something like +`gulp clean build`, with tasks named `clean` and `build`. This will not +produce the intended result, as the two tasks will be executed +concurrently. + +Just running `gulp` will execute the task `default`. If there is no +`default` task, gulp will error. ### Compilers From a2badd687dddd7a5c66599eeb0b4d25452c61ced Mon Sep 17 00:00:00 2001 From: Simon Meusel Date: Mon, 1 May 2017 17:17:39 +0200 Subject: [PATCH 037/216] Docs: Fix issue with formatting in dealing-with-streams.md (#1948) --- docs/writing-a-plugin/dealing-with-streams.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/writing-a-plugin/dealing-with-streams.md b/docs/writing-a-plugin/dealing-with-streams.md index 4c6d53830..57d390b96 100644 --- a/docs/writing-a-plugin/dealing-with-streams.md +++ b/docs/writing-a-plugin/dealing-with-streams.md @@ -8,7 +8,7 @@ ## Dealing with streams -Let's implement a plugin prepending some text to files. This plugin supports all possible forms of file.contents. +Let's implement a plugin prepending some text to files. This plugin supports all possible forms of `file.contents`. ```js var through = require('through2'); @@ -73,7 +73,7 @@ gulp.src('files/**/*.js', { buffer: false }) .pipe(gulp.dest('modified-files')); ``` -## Some plugins using streams +## Some plugins using streams * [gulp-svgicons2svgfont](https://github.com/nfroidure/gulp-svgiconstosvgfont) From d634e9577bd8bb7ef59b74674a64f08495d0c971 Mon Sep 17 00:00:00 2001 From: Tamara Jordan Date: Wed, 17 May 2017 18:29:57 +0100 Subject: [PATCH 038/216] Docs: Fix sub-lists in writing-a-plugin guidelines (#1955) --- docs/writing-a-plugin/guidelines.md | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/writing-a-plugin/guidelines.md b/docs/writing-a-plugin/guidelines.md index 4c924574c..8d97e4e9c 100644 --- a/docs/writing-a-plugin/guidelines.md +++ b/docs/writing-a-plugin/guidelines.md @@ -5,45 +5,45 @@ [Writing a Plugin](README.md) > Guidelines 1. Your plugin should not do something that can be done easily with an existing node module - - For example: deleting a folder does not need to be a gulp plugin. Use a module like [del](https://github.com/sindresorhus/del) within a task instead. - - Wrapping every possible thing just for the sake of wrapping it will pollute the ecosystem with low quality plugins that don't make sense within the gulp paradigm. - - gulp plugins are for file-based operations! If you find yourself shoehorning a complex process into streams just make a normal node module instead. - - A good example of a gulp plugin would be something like gulp-coffee. The coffee-script module does not work with Vinyl out of the box, so we wrap it to add this functionality and abstract away pain points to make it work well within gulp. + - For example: deleting a folder does not need to be a gulp plugin. Use a module like [del](https://github.com/sindresorhus/del) within a task instead. + - Wrapping every possible thing just for the sake of wrapping it will pollute the ecosystem with low quality plugins that don't make sense within the gulp paradigm. + - gulp plugins are for file-based operations! If you find yourself shoehorning a complex process into streams just make a normal node module instead. + - A good example of a gulp plugin would be something like gulp-coffee. The coffee-script module does not work with Vinyl out of the box, so we wrap it to add this functionality and abstract away pain points to make it work well within gulp. 1. Your plugin should only do **one thing**, and do it well. - - Avoid config options that make your plugin do completely different tasks - - For example: A JS minification plugin should not have an option that adds a header as well + - Avoid config options that make your plugin do completely different tasks + - For example: A JS minification plugin should not have an option that adds a header as well 1. Your plugin shouldn't do things that other plugins are responsible for - - It should not concat, [gulp-concat](https://github.com/contra/gulp-concat) does that - - It should not add headers, [gulp-header](https://www.npmjs.com/package/gulp-header) does that - - It should not add footers, [gulp-footer](https://www.npmjs.com/package/gulp-footer) does that - - If it's a common but optional use case, document that your plugin is often used with another plugin - - Make use of other plugins within your plugin! This reduces the amount of code you have to write and ensures a stable ecosystem. + - It should not concat, [gulp-concat](https://github.com/contra/gulp-concat) does that + - It should not add headers, [gulp-header](https://www.npmjs.com/package/gulp-header) does that + - It should not add footers, [gulp-footer](https://www.npmjs.com/package/gulp-footer) does that + - If it's a common but optional use case, document that your plugin is often used with another plugin + - Make use of other plugins within your plugin! This reduces the amount of code you have to write and ensures a stable ecosystem. 1. Your plugin **must be tested** - - Testing a gulp plugin is easy, you don't even need gulp to test it - - Look at other plugins for examples + - Testing a gulp plugin is easy, you don't even need gulp to test it + - Look at other plugins for examples 1. Add `gulpplugin` as a keyword in your `package.json` so you show up on our search 1. Your plugin API should be a function that returns a stream - - If you need to store state somewhere, do it internally - - If you need to pass state/options between plugins, tack it on the file object + - If you need to store state somewhere, do it internally + - If you need to pass state/options between plugins, tack it on the file object 1. Do not throw errors inside a stream - - Instead, you should emit it as an **error** event. - - If you encounter an error **outside** the stream, such as invalid configuration while creating the stream, you may throw it. + - Instead, you should emit it as an **error** event. + - If you encounter an error **outside** the stream, such as invalid configuration while creating the stream, you may throw it. 1. Prefix any errors with the name of your plugin - - For example: `gulp-replace: Cannot do regexp replace on a stream` - - Use gulp-util's [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) class to make this easy + - For example: `gulp-replace: Cannot do regexp replace on a stream` + - Use gulp-util's [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) class to make this easy 1. Name your plugin appropriately: it should begin with "gulp-" if it is a gulp plugin - - If it is not a gulp plugin, it should not begin with "gulp-" + - If it is not a gulp plugin, it should not begin with "gulp-" 1. The type of `file.contents` should always be the same going out as it was when it came in - - If file.contents is null (non-read) just ignore the file and pass it along - - If file.contents is a Stream and you don't support that just emit an error - - Do not buffer a stream to shoehorn your plugin to work with streams. This will cause horrible things to happen. + - If file.contents is null (non-read) just ignore the file and pass it along + - If file.contents is a Stream and you don't support that just emit an error + - Do not buffer a stream to shoehorn your plugin to work with streams. This will cause horrible things to happen. 1. Do not pass the `file` object downstream until you are done with it 1. Use [`file.clone()`](https://github.com/gulpjs/vinyl#clone) when cloning a file or creating a new one based on a file. 1. Use modules from our [recommended modules page](recommended-modules.md) to make your life easier 1. Do NOT require `gulp` as a dependency or peerDependency in your plugin - - Using gulp to test or automate your plugin workflow is totally cool, just make sure you put it as a devDependency - - Requiring gulp as a dependency of your plugin means that anyone who installs your plugin is also installing a new gulp and its entire dependency tree. - - There is no reason you should be using gulp within your actual plugin code. If you find yourself doing this open an issue so we can help you out. + - Using gulp to test or automate your plugin workflow is totally cool, just make sure you put it as a devDependency + - Requiring gulp as a dependency of your plugin means that anyone who installs your plugin is also installing a new gulp and its entire dependency tree. + - There is no reason you should be using gulp within your actual plugin code. If you find yourself doing this open an issue so we can help you out. ## Why are these guidelines so strict? From a0ec3ff91c84baa1bd8de5331a502e330f5e3cfe Mon Sep 17 00:00:00 2001 From: Packt Date: Wed, 24 May 2017 01:09:34 +0530 Subject: [PATCH 039/216] Docs: Add "Getting Started with Gulp" to books section (#1961) --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index fbcef8bcd..9d63fe3f8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,7 @@ Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagg ## Books * [Developing a gulp Edge](http://shop.oreilly.com/product/9781939902146.do) +* [Getting Started with Gulp – Second Edition](https://www.packtpub.com/application-development/getting-started-gulp-%E2%80%93-second-edition) - Travis Maynard, Packt (April 2017) ## Articles From 45adfc35aeeeaac5678ee63d20a6cdf985f6ceaf Mon Sep 17 00:00:00 2001 From: Terin Stock Date: Sat, 17 Jun 2017 15:04:16 -0700 Subject: [PATCH 040/216] Docs: Integrate pump documentation from gulp-uglify (closes #1791) --- docs/README.md | 1 + docs/why-use-pump/README.md | 122 +++++++++++++++++++++++++++++++ docs/why-use-pump/pipe-error.png | Bin 0 -> 144530 bytes docs/why-use-pump/pump-error.png | Bin 0 -> 67191 bytes 4 files changed, 123 insertions(+) create mode 100644 docs/why-use-pump/README.md create mode 100644 docs/why-use-pump/pipe-error.png create mode 100644 docs/why-use-pump/pump-error.png diff --git a/docs/README.md b/docs/README.md index 9d63fe3f8..253859992 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,7 @@ * [API documentation](API.md) - Learn the ins and outs of using gulp * [CLI documentation](CLI.md) - Learn how to call tasks and use compilers * [Writing a Plugin](writing-a-plugin/README.md) - So you're writing a gulp plugin? Go here for the essential dos and don'ts. +* [Why Use Pump?](why-use-pump/README.md) - Why you should use the `pump` module instead of calling `.pipe` yourself. * [Spanish documentation][SpanishDocs] - gulp en Español. * [Simplified Chinese documentation][SimplifiedChineseDocs] - gulp 简体中文文档. * [Korean documentation][KoreanDocs] - gulp 한국어 참조 문서. diff --git a/docs/why-use-pump/README.md b/docs/why-use-pump/README.md new file mode 100644 index 000000000..de6ebc85f --- /dev/null +++ b/docs/why-use-pump/README.md @@ -0,0 +1,122 @@ +# Why Use Pump? + +When using `pipe` from the Node.js streams, errors are not propagated forward +through the piped streams, and source streams aren’t closed if a destination +stream closed. The [`pump`][pump] module normalizes these problems and passes +you the errors in a callback. + +## A common gulpfile example + +A common pattern in gulp files is to simply return a Node.js stream, and expect +the gulp tool to handle errors. + +```javascript +// example of a common gulpfile +var gulp = require('gulp'); +var uglify = require('gulp-uglify'); + +gulp.task('compress', function () { + // returns a Node.js stream, but no handling of error messages + return gulp.src('lib/*.js') + .pipe(uglify()) + .pipe(gulp.dest('dist')); +}); +``` + +![pipe error](pipe-error.png) + +There’s an error in one of the JavaScript files, but that error message is the +opposite of helpful. You want to know what file and line contains the error. So +what is this mess? + +When there’s an error in a stream, the Node.js stream fire the 'error' event, +but if there’s no handler for this event, it instead goes to the defined +[uncaught exception][uncaughtException] handler. The default behavior of the +uncaught exception handler is documented: + +> By default, Node.js handles such exceptions by printing the stack trace to +> stderr and exiting. + +## Handling the Errors + +Since allowing the errors to make it to the uncaught exception handler isn’t +useful, we should handle the exceptions properly. Let’s give that a quick shot. + +```javascript +var gulp = require('gulp'); +var uglify = require('gulp-uglify'); + +gulp.task('compress', function () { + return gulp.src('lib/*.js') + .pipe(uglify()) + .pipe(gulp.dest('dist')) + .on('error', function(err) { + console.error('Error in compress task', err.toString()); + }); +}); +``` + +Unfortunately, Node.js stream’s `pipe` function doesn’t forward errors through +the chain, so this error handler only handles the errors given by +`gulp.dest`. Instead we need to handle errors for each stream. + +```javascript +var gulp = require('gulp'); +var uglify = require('gulp-uglify'); + +gulp.task('compress', function () { + function createErrorHandler(name) { + return function (err) { + console.error('Error from ' + name + ' in compress task', err.toString()); + }; + } + + return gulp.src('lib/*.js') + .on('error', createErrorHandler('gulp.src')) + .pipe(uglify()) + .on('error', createErrorHandler('uglify')) + .pipe(gulp.dest('dist')) + .on('error', createErrorHandler('gulp.dest')); +}); +``` + +This is a lot of complexity to add in each of your gulp tasks, and it’s easy to +forget to do it. In addition, it’s still not perfect, as it doesn’t properly +signal to gulp’s task system that the task has failed. We can fix this, and we +can handle the other pesky issues with error propogations with streams, but it’s +even more work! + +## Using pump + +The [`pump`][pump] module is a cheat code of sorts. It’s a wrapper around the +`pipe` functionality that handles these cases for you, so you can stop hacking +on your gulpfiles, and get back to hacking new features into your app. + +```javascript +var gulp = require('gulp'); +var uglify = require('gulp-uglify'); +var pump = require('pump'); + +gulp.task('compress', function (cb) { + pump([ + gulp.src('lib/*.js'), + uglify(), + gulp.dest('dist') + ], + cb + ); +}); +``` + +The gulp task system provides a gulp task with a callback, which can signal +successful task completion (being called with no arguments), or a task failure +(being called with an Error argument). Fortunately, this is the exact same +format `pump` uses! + +![pump error](pump-error.png) + +Now it’s very clear what plugin the error was from, what the error actually was, +and from what file and line number. + +[pump]: https://github.com/mafintosh/pump +[uncaughtException]: https://nodejs.org/api/process.html#process_event_uncaughtexception diff --git a/docs/why-use-pump/pipe-error.png b/docs/why-use-pump/pipe-error.png new file mode 100644 index 0000000000000000000000000000000000000000..126856a4a6de7614d784941f812ba5c8cb6a9205 GIT binary patch literal 144530 zcmZs@b97|U)~_9-qmGU4sAJnncdU+W+pO5OZQHhOn;lfl3cfz)J?Gr-p8Nh$qeksD zYVEme%{B4-)~GOfSuvz9cwfN4z>p-we=2~1K|KF`#lgY+z4C2s%l_{hxTAuYFj)08 z!5=U%K`@D*LP~Dn7uv9Tic7Db^JIj7bQ+6-8fUS7SYPHBH3}vbSqT-6D`5nM_AY1^|{{qt%wqfo-rF@qT+=R`2UbLM&a3X~Oh z73oe0E)nca49vo_iFd+Sp`FtM$_4^A_e$Exhj>NJ_~uX|91i~eL<41zVbhe zK9;MsLE9O&-7nE3{QMttG8ydFNfSqjBr*>UQekjuc5JC z;9S$53{U9!2ml9S9XS28>{Bm2BQnRpU~7SRI3L! z=s6So&82vZg-Q`G?C*-U@*$3vld!Q7)4n1F2?5czdDA6pgiIu8LVs!(P=S$U+pU24 zUQX=z`_%xTNI~`QDl4g(PvtP;GVRFGetWYs%aKv}Hw{Q|;KZJF!!_{p3Cw+xJ28rS zu5#+;5WIKGY-;aw!lx^k8kU$^otY@eqKC`JMg2_{s8E5hzFjanPXr#1=G2lQlMZP_ zKEif4w?W?3Z=FxHxQ3%r`We50NAl^}Fyzc&)^qUW$h!w8kQ(hW;{_q&x52!vi3nfO z&LGp#&cFS7+MVCw7?2(Msb=rmW8+s|tROyS<)1eI(?_RQnE^Nx`sec7B!b6D7VXPO znE;Q>Han7%eMgff{SVv(S;)SsN%an| zG2R@?*a0(YdLQA5-F}RQ#WB!G`pN9PL3xK-zNpy_%Pqws;4|sGNu3K?&=|c*zsr}6 z_Q5!ZN(K1@ml-Lm>LDCMyD|jDn8Nt_A=Q-o=d9THH|)aFK*9!Cuk`ul{n+kUuPnG; zl2hN>m-cwIZk^fb&hV+>l@~bJq#*&FVy({6o!?x)jwtpm3GV3}m1DPIdVd)B%QeHA4}xHq-;Wl&M+u zwESk?J8wsu378#gSb)kFyvGSL{xleF{XJl0F3qtTNqts(Z95UVM7AcZvHx&nYsAqC z`o0FsZYUgfA_J)yp`zK!5mHaS{Nn-2XECk0v6#Ib_`d6pv)1V~C3nYcFdX$k zQ5KdVH;lI4S~xa45YSc6|%G)4x|JuEw}I zm8z8RepEzNTLA?tG|=Ut&HkFks8q4mE*=SBdH+IY7uY&vf4zRJ$mo6`trz0|TK0$S zpTGXICC=$wuc~(Yq}7CerQK~t|GkSw?O*`g?bvdbM`T4drv(1!OI3Ay@89ua#ZX#i zs{AAgS_hVWO1=F>L#?JKCs{6ynZ$JjJ$=KWUPxBp1EqSX+C?%hX^z#_H~SLL+lhto zqMd)Ff}9E8`YL4q5r&{&iZDXABO@f(_;#)Xvz!zxsLjqzp==&abMnFDIxAW{kh(n$ z_^E9lyEoTwnG&;d{Ti=LD>MBqITv6q%{rI@`3z{kGZ^@SlfwPRMlIe>Mq`Xon%yzoBlcJ0bE-|f025fW#@ zT-noKZ>@mr-3=(-b|C8Wb77zhC+&5W-BC`1v8wrLeD@+F`VyF~GeTaPZZCT}7)?!gYT&n{NxwWwdAfI5IkxyAmW9>#c zn;=O{nC@99jUnaZS|PVzWfj?HvESanGzfsfrY9({2%Fx{e^$L$JyNxiZt>ZXh}`sO z?}zF_($>)ytDvtpP_BAyDB6ezYt+D)AlERFN5Eb48XsbHnBfgP8jz$zl63x=+Wj!SkEWczu%r_@vK-&^pdEG%%ekr{2mdAp}-Lt4n8tRjL zG!c=wqul`GA?L66Br$)-j`l}K=fmc8NcbJXdb%dN-!5*gfW|8E9RDaLdUeNrR~<2E zZ-2s3u|H2U}Gf=r&Wo@6rSoKLQsMPgoM?^^O0Xvwq+&f%n*xfnKuC>vD zE0vt-whjR?PvX zdkYKEF_{<1>r%T{Au0Hlu1~nscXQ_hk{Bo(#8t8FmxnAIn>5Zei9I|7POy^&VvEdRM~=R#(R*c4*5n7=+@R?3*)w$#ZDE%$$o3CU*$NXcew%H^dhC1# z&%D5gozMw~1O+3yc^;(5o7R2F#@j+!D7ev3fFVy>(kn<55m9C*c-iCLY&p~)Eb(v7 zZ@7lJWwBZ>xK=2IRnq2#(FMH1G}3#b1Bd8%xjMNkPvKR5>3I)FUDh%i_wr4fj8vW=mul&hVq5zfs~MS{hRWY zzKwQd9>wT zto*GeNG281SBZZn=?`ickIcR+I>veP9ZA=ciH@Xp1@(WFPWrxCFTW6CnIL420xv!O zdWsGl-^TCW!4hoYRWRndUGM^8Wd8=r^E}9baRr}|wR>Df{Z;|-L@@fN$Pk1P#k|&X z=wRe~fBV$$9p&(^18kk)0e(6Pwc(Jfi7F82AcQ^6SAL-J%p~WhMOkq@LO(l9)% zQv$IM%x%smoCCFqZwgPv!eWx+H-)>ORgqjfB$(bCEtu0;m40G69>l_%jSaUyAwA&; zP{CE@Z2TjFo_ftT$KyTsdP1LsaQM{%VZ8Y*$u!0Fm&Ayf+~YaoHV~19w_YgoI`fbK z84K6t47~4)?-%Sj{G_{|D(!;iuy~J%Jx0+VWPoQM%C!6S7@GqWr^-*IWr{r*bI30 zDwSuL2lmk(Hyq?d2wu)r%z{NdL@=-Jbt$>Jc3y7v24i!m?#Uu7osC48q=XQEd;_2p z;cOx_qf;kc0qhNj>jk8a^xWv)e2gt=jN;zdSb#l*IFj`Po3sK8Kq#{99n8&c0LBdr zmXlWO0Ziy_&kr>qO(^iLwd{Ip481)G`!=-}`b=Sd5LttpX_n@nb^i4V)9e9acdsW{ zDZYgtue_AW*$rgL;8@G@9@_^cWo)I(AVH#1@;^B0t9{JTqvYPF zqhZ|AS{Ftyn_y#LKg<|lZw2WjrbFikMzr#MVCg~et$ce26A{6`|Mo6EFpwGp;Z|)2 zwx6$NYKDr2EHJ#*bv^M^sQ@iPIvyqjx?boz6&4y) zTI9U;JAGYD^y1)#p15}kL!mS3-%8VJzo1sI>|Q6mmYE-Q=zYT1%w-> zejyvDp!D@{bRr{LZV%&v(5?x$W73XH5)kry^hgo`x=>REF!!Y_>U=K;*G!XD43STN z3KmctVVj_X-=Coq?=KOG%vM3rFMa(d$-UaZ(Y(xv8eqE`cWf2E`>Z69{}RyRyG40= zF2o(s*19@{624bx=#R{GPQza9?T#zUk;J_NO2(Hb?02}Fo_92K*f-GubQEddZ#HQ1 zBv>fA0N-Rg8R4PjU8(WtO%@_6ucTaE+cTOB)c3-FHkBMt`1ST~I zhJKca4h~Z|hY-iD(l}h{FLQfAX9N&*Y%6IC4x>7rCNq32cD+hJvFb+ctC9#(%7$Bi z#HwnQioj{vZck(;i~MyGPhgV-&H>STsFs5kMVM41*xBFq)sH6)ycx+7$we`)-a4aj zNWl90RFg)PppWCQs!aBsP>G7B&`zJ%jR9-tXN~w4Q@8_e_SO#*)A_9sX}@{7ZYaP# zc5&tD#Aq{+0}jG|=3yjb&Z_H*s+QD!mn+Li)x+8gzjSL2AUFq}bb2D)@65Ssd2jLt z2GE}iXRVWl4uKU?ICDNEO$9%Q z4DSi6_P(j5Zw8gpzzF*KPsI5euf3q39LebvyP=&3^kTE7e+YgxFA4o70`#*;?w&Lh zuv>&_it5J@sU1x4QxIPn`7G1C;y}!o@EUFK&}b+GbSH`m$|t@$2rcH70aaTsB)P6O zxYQtwNtx(`W5sMw6yOarFHC6P;82SH>AXptNHU1CbtLtMc)keZpuUWy3MJO z!{W7+!hr?U@l+zyUIO<&_r4u0qi+bK84oqkHZ;_C{bM`V%<5-rW!)s;TswggEoAUg zB75AJb1@wW+T9SraI^u2kLfJT>NYAyOdlsqWdD(jVcnh+QTav0lGNosHFFfz>_aWB zH!zNq%tr3kjMJ4bw^Lr{W0BieR57C1R)JkRq>5%cM3IYgV{${+8BhK7h7t8;CT)4! z5U#YLJEVZ>Wp;fVxyW2he+hv>(1A;qQSZCu6ZnNy6*mElr&=BJ9$@Vyzg)VNPp8X_e0%dV zCTZ&3NnO_8OdG_x--%hGZdBi9;J7}))g z&GJV4)V`J%7)PmsY(#zwbIu*1)wqo>xBUHtQ!Uy<&uAa6)OLEm9w z4!AJbLk#y`$St%Z<~&GS(PC&XM}_jO|6~P=lu8zQ$xK+qE$)yD=rRV8XeS##=hJK^ ziO16X`!S&lQ=kfF@HdhU_sl0l*{ZXa7+s% z@K+x2S|!DgLxMP;SDL+U8R>0?Oo7(#l)WsDi`R1;=X=;uX9fE?!Qz{f`mur#W0LQg%>pynQ5>vKCFj)VT)IcyU_U8drMidK7H zmQY6NQ#6PgUL9y#>ldhKk8b1wtFNT1`#_uc5@@KHHtfgzZ~WA?3<<;qki|{CcA7h! zZhD-@ppQ?cHkfF<=(D7HZUj(&=2FbSv};Q*NEMlRuX~_m;0*Mp&Sg<=|4sn71#(eH zlY$ZX==1UmhSMTvg3(}??gi8c%-yh`)HSq~XsN4mw=!b0C&Njw{aNy7sv zFF`^KxU*|Jt&nA+jOB(OXd+uG(EaITj1^sxd`8F_qb4M!nLvmlM+~@1yBR|UEw=7= z<~n|OdN!@#PIj!Em?V_I)g9;o!u;N$eAio#IJPLszXj;q7H)3}^VsYx`#=?!f!421 z+>(Apy=!;vultZ098~41jH`ek683ywE&72yUz{AVR&XV=*6|x~9i{Mu905(!;~`^` z!iz_+KY>e1s+mOth?_s{{Ptde{S3=gSgp?FK2^} zMxFe>jGF($+64>+@PBlgNK7PAfm{gK;UzyM+Exf{#ec~B{68ePH_LW+X2<+7QvtK$ zU9;A@{V+KEawsAHByoHdS8mQ)0_F{=6`MO-2MaWk3Z4w};Zu@v!;nT!mKo_~g0aah z!ej@ZDJ~xB#lbIrpQ5Nwom@%@O^NCVapy|$m=z?nuNl3iRLzzupo3@HsPjukL$B>3 zj50s@^IYf(zu%l1i+|4t#uV_f!1y29y$J*J;c-wzS=3CdqTOBc=%4n^Xg|9+OH$W0 zgM>?`h=uY9UK(PV<6B-r`>DQnA%csA=83S_@qSeb$`2dRQoaQYtu4&H1dhqj%=d}c zx;Ri+|FmD&w~_P>Q;VlBZLzYERFHx}NSVgR*C(~dGHAZd7;l%_-?o!sR&`avx3-nY z@!I~7ZSxU6c)&1Dv661TM8Yb7_=K8{JeGm{&l1~Z;4oV3g1nz~@5bb!BEF`>+a>EF zEr|)VzCH|MpMfp+IShs4xF1*V&p;d?@bdE-e_Y?j#To1V?XKSAy)K_JNA2LF@L5xL zm1#7R(r88~2w%v4AA#KKj}}Kn2K=ORX)=_fk_eq2y|T3NWW8fwj$mTWOdt6PHj?AC zjo1cjh}BihCivCrU1u9RnV<2z)t{Ydq~Bp6N`hSq`Z#6ZOKtlF&52u!&ph$l zqQiZ38gJvMgmhfKTl%VSHW-SGmm|Thlh~mWtT8jNe;yM5$z~#b1BFigY?I(=*w()J z{VN?+J@UyHewKfYp}D>~Z**v`n;17wiS;mP^6k}5C!T_q^n67sPoRV&0Ayz7&G>^I z;cDj;r+k&GQLYt_vKX4Wt-Mx7`^4HB7_SXravUYkuqa@DsnuI;{H-|I(5rfq@RT#)0DFq3qN1wCdY%CZErHYg*1g7l1!@b9tyc`t$Wl(u zdN{2vNFqaoIM`b#@$Youy>Ymy5~w%-A|37UG{N=eWxq#|j zd|8l^zgt}a(;vOxCs#@biY-KR+RvvA+ED6Z+Y1Bl6L{u7Z6Ag;;-}Tx?v_(N+Z$uM zM{ttGRz7x_T|@J)nlwU+zY1W_6o*GNofk962!y|EnYCr8P`01Tdi~4ScWX?2R}Bel zz&ASpfWq7Y1@EJ+hcL{drX_lG733_|q@P~M^VJwwYDj5Iw!;w&VL%^lm(+Gr)en5U zCq`$=uLs=)_recHw$ZxsVPwLeZ20~X)L{i`WMAQ+kyNX(eMeQy&Sxpi&CJZ3k%@rd zdP34_wM6?HH8-1e=SR>}T@6=M=s9AV)Nd97Etf7TA+m!`J5 z(kT+ZlV|m4XD>GWY{VQN2|)Ct`!=s{pxoiGv($J;d9D%8O^WQ-B+~VLwRZB(-1@ft z+k9=_25c;jv|4MMMzP646Q(=o6 zEk1+0*I7Js1q2Ukn3GOmRyggPe>$Z++aG7(8r#=)(2>6C1TY>{ZH}uoSbNv6wpLdI z;-k4{OUqE+uA~8KQo@LIpbL$gS4Gu3)E|2%^zpgo|e=*hV z5Im7QXHkzL_XhW#2IB}HA|NAyl5(U;7ITI5OHiYo-(s!DG}kV)M8~ExazNfp)Q3M{2xN zx9H?ZqltiDMIQ;)eS1Ec$CE6s7JRu}&0h6qVrnDdwyvrizoz-E(b0#?yoF;JH-eng zMoq8;`KA&%OP;g(yZ+67S)SM7i)1ssaS80;6ucUvdbqJk=jO{ff&dl*4NV>9GSf*j z!YwgMN#S~X5O|XG1OzUp@6GLrn?}+Z}Wd30wWP&G7v$B?w)^QOdGB z=rO=bSRCh|e9!5$&A_1aWa^JkUSknqOJFU!D_QH_X?caF*tX`&*1$-^Xl2toUku(> z&qU}sI3+r)K=2F!ui91|rQDiL|6^t^$!SPK6gIL~=KD)@4Q5S`V__@iR6yQ9LFm?0 zYXpyug!;$w>u`&}Yg0a^4Ati~XMIqo@X>KtieQvGtm z?Dy`@R5$?-r;Q*-2TX_MwgNw?<3=VHTKr^wuFB)Fp>OLw>*R8Q#xq0*;E;v8zCmem zpz|elO4;TZ9vzL`jTPox0eDFEiwb@)SeCzi6Omx*$r=3ii*5zd#N_2@y zFeG<{IBtyF==}MM=-et@qi}(b`$66kg7=u)!>eD0P&cPUFF<%@uxU;Z`M; zI4zK4{N5wMH1j^qMz7s6Ja*k zt;{q*G8P4sB)?9OOkbQ~AhEH)lYbwFJp^gCc+m^QX(*eSkk^m$ZK-!TkI5WKC-`c@ zW;Ft_)7um4ZMBb2cxvgZJ}Mtq7VqC&9C^TP+YG62su=hTzL_^*67$}8+`P6lbHpPL zY;Q{rYP!CRXE9WE3@?WKtB39HYpY1Xl_o$1->7R&sB-tu@KrzU}i0`LgJ4U7f25%Pejm|gW@1)A4AU4|3ER7Tl` z3n?O1Gj(#m!2X)73Y^0lRKa?CWr~B|6{HxO8=Kv4WJW*bB=y!}romIP7WV5!%#!8r zhU=5gQ`jB#eLxGpQ0Ky<{h3^hVfWC>DBaTT%jBdV3+|`M%*vDPuqVFNn@<%DeBUPE zSi79Ee{K|!GJbH>bTzRtJ&cc`Ftbiz0rf25R%9m z;9)PQeu7!Ew0(B^u?h~7ijiRYnXw?F&EJKgLvp@AQAlbwtUMJ(H*tPlh-`HHL=w6{iub2?xUK(xr-zAFDwlj z5`^t)eN7?Ds-_oW?tJ^nT-O#iqE^hX81(0ffw0hrQAKx^ztM8XF!Wc2GIOQ-UQk24 z|AIv=VgDlbbo+!V5TGCV{XXE|?qCGdiUfdVB&9IBtWHbm4&T|>)b@jBG;xvtded?r z^_0m75Za#m$?D7RvosI+w4_SB>UQ*|N`>?-Ljdn=8T*jOTah@T8GcBr|0abBNdul@IjE^Un}EXyszAUhHp27RF$n-<{rK#)pVY+b%a zQ$lsChkhSknB=d3$CWgmI#gZyw6KyV=haLY6Od<%kqFQ@Qepb7-QW-SiIm|!t+IRC z<<(L0K&0(Q0DH4%pCT#Xmm=(|=P=jfpC^p$$%glz^wVl`elo~s;+C4F!3jB8gS^sq0OJV#NTw1xR}3*A zZiv2-T6*on&7obcwKay4acTwATG0e|?ICkHy0wP+e{E zX9(;81@tCyPY<>G2<9;p6o2*0xOj+P1EKxy;{MUgaX%WyXSG=*N9(7D-cFGTYrH11 z?&!PQ#%~vrKGo)SK&0W`iV$V)UB2a-!RYg|TO0|UkbR>i4kbfYe!EUuIQbp{agWVFo#FFtY!m~>*(?R#Fozf~C#L-5a3 z!uApL2T1Xm?sX_!WuqA+@vdNrl_)%09X@+KR+j$VxB}uqDn$X|mGeji%r%ei58-Fo zIWQ4&J*fP)!JEd-ngJ|QC?z$l%TJJq8;nQ#AT|+5QWF%1dhjQv*=R9qlNq98{Vk2E z*Mjz%dC#A%l#TS&|C^8~$Y`BCG=w}Y2 zOzO>oy8(pmf25Vd3XA#zv)x*CFOx|(>E|WS27hNzHR~T3!VPPO8s<7XkClCe;4j(pI4y;Io$yrq5@Hx(^V-b}cyCHq1JWN9ij1A5!7R6An%q{KR8tvJxp} z-_S2?@C1w?55;U!#gEU~S^*zu5mp=Y^}EE~h(PXcQ9>m^^D^J8)c=O!!@qS43KG-U zAa0lX!+teT3!hrd#m1yd@O5p*_F-cH-_=<6y8|~G{sTuEIzv`NqG(5y7V)JhuLcV; z2H|(l1>BrIi z!&P--me+X$DIZ?0KJmesANH{qH^^4T**Ipl)(caD=ky+5Wnmbnn}IgA(GI5fG!+wc z;WH#kg}r@e9k;|z_7C3NgKYI>RaLUq#>Ta0zje2#$I|T60D200CE>N1&^FVMImq_F zc-=3Z=|`UCbo`8<{n9wn(8TcI{`e<0&L;f{HvLP&hbiK?1fT2Qi;>NK&VDxYezyC> z5-amM8@<7MGV>GK_WpLfN2kxn+GQFH#~nq= zGR3QH6QLzmd#;>LoNN!e3B#~8{jW(m>FGOX1Xd&C%f%TuFT=@U+k-XK2X1?^=&Ii} z5Hzl7i>qa65(=^(e2-3aO^W#VT@3U%GFxjtZ;oP+T9%C1%q}u}g$&2|pKlWlCB!yX zJ*J8RAm8FN5(T{HH+Qck9Mcw$N_UVQBvI^#TTLMB?=V$y$+cB-B%{WNiFquq(^u7{ zC+`zB=GR;>pya;GGQ)kF20IB`W=xJ8o!{**fDd5j^wo4k zB-G^|NbPDv7*xM&WE}_)KdIU}s%KSUe||Y_Gj*e`jk9ta$GQt23=vE~YFHTUbNUU0 z_#vloe)dz3O!^UMxIEXl-Y@;rQR}pPT$NZ#lGL!4XcNgG4hEv$0n#}^1eQ?PhPK#l zN?BB+PT_FzQKgX-!whjEJ_?ra7-mH(2bYcEcgL0Yi_gb~PrG&a(pH0x3G8#%QDkSO z<;2@+#dGfU0>0!rL{Zm~<^UzXPkeC&Be#mjO3&sOTJ7^{tS@#cJouB?#pr?z|L)=0 z8Rd0!h|YI+eNU+N)f1jajv7BKoJ+>$78eV-pkryE4^rUPHqd#VZt$W^apYYDugWwL z_t}7Hh0Mp_G{IxDz`NLhVPN!`41PVfx^ngNvz`apqqM!m#RGfID6uFFy_eh0+R$qI3>Fvcd_UdQr5?~bk^z;VVM7NaI-vc0HZ2q(!d%Oh zft0VTrSco@h{N$@Q2EQ=0jy1Lm0}jxfjeIagLC!ueR>1(qo3MlzL@OCACrP}(?R#C zp%V7*x~c5ZA5V0>^F=@x$&-P8>}4uHoqs!N){r0y%d-#BYF_IT(TAIybCPL(kE>a* z$Zo__=pyCL?@jKsE|FYfPkvxm$n9PEr8Z^P1L?!MD9iztBT8c& z9qkSacu@VPH(pb;>l;h!lwNdnbkGm3P|X=C$k3Y-T6czVqEXmCL4Q>fjh4h>DwJ$? zW6WO}`5;#eet}$i&f<^rb6kD~Pgwmw(-;qV|BCgRQ@vA@yE{xUA5|1V;|2fCs^7G6 zTX7+E9VLf%l%2!>7kE5tx@&8+%j4V8LD|Z4I`B!{o-Ep*8H6yTI2srHIS|cjM|D}g zBk=R7+F}9pX%9md_mv@wGW*c1Q%34C*u@s*^Po}w;qO5LSQyTYVtDJ<`ta9QfETjJULVL%_!48NUSx+r z;;OxAlg_HsP2Gjc8d&Lb;f)Y24N{zFXSi;Jo8-~D4qQfR7{I?skAnxCSc<3k)3GXgR? zorP_3>z$l`imX*vv4N)}R}O@1=6*-a0JR44iKaun`SnivKA(5%#RUI^bL_#|Osc(> zDJkiBDOg@cqHcFviQ1Y5F2{SL{$;l@u9By+b&ci8joiG-%ur@f5^GL2XKOruvf4TUQ^|d*`9jO?gQouT-&E zEx6p&((T^p={h#^ksa4a>!_DvX|}YPt!IDP-LWnJ%7ZrTjSjw68a; zt@9B&{5{{V8Xw~@-*N8SDPg3K+9|nOxHkhI^%Rm@gnKfE`WM)_Htwg6S0I^w1MjMm z-M28eHxD)3I)NhS$baQ)5~Pn2M!n5mu`i)Q+VPDH=Ocr+c<_t+`nH2eepC$uUVMbs zn`2qylL~>b)GqekjNFSnf$BtG)is<@uF$kyC*r2RK^e@prYIj{QRq%m*WVyFN8zy2 zK4;%v1nMq4Z-0!(B3H1+#l_V8cYK`nC((}J-UNW=)0$*Ty%Th$J#7X{DdXUBV^_Nt|+sBbvMWByLb+qY7)0xQ zSm&1hcW_WVkKqf>wK=VV+=nX~1BIT%^v32#ww1iFh~pDGU&#(Dw(gy1hiwBtkE%Jp zpxM@Tr6mgAoM8XUi%|zYeqDmZdotC_&CuS6L25jh0*$4o7js5lq7P8pw)I0%ojH#$ z)&C^<)HOlvd7Z*EqkHVlW1pPL)pNu1QfD>h;*T=%T!I$fN6(Dl42<{sSi3e){C(tx zi{%ArG$R%>{p6TLWn-!JX=(pT@A{t*l&~}dKIT!D#wv=A z!{=(<+k4ZGwt22ro;3Ac+T$R{kuNL~hn>x$#69L;cz7r?cdg7waUCWOMuguYQXP&DUg>rLsH=R7BH=TzGkk7ioMuu{KfD;NZRV#dT~z@}LMjUR z`#IQE03$$bN>-Dzm}Z8wnX&Qj>|2p($O(S>g8@s{t$&hoqA;0_SFPJSBNuHv6whvT zZeCANIl|#bhxM3)tSl>M#MhDzn3D$Uj9Vko!n+WuOV6dXd`hebBdt1eklO1<9asVDgdgq>naY{eA* ziNI@E63*gmh%1mRW_Y=@TCDuV*x~os(GLC^JKZHH{F-J5er|m!4HmW&CmJ0Q%k-9k zTnfZnvN~VX{YLqEB=7ug>K?)D@~2A8?cHnoSOi)Rw~JWfHKL8|R{nqmjnOSJoG3oZ>N2e@=g0q@61G-7~@aF z@~yUO$G3`_VlA?)s(`J6Xiu?VQ0%M3*}P>Ty}l-sjH?RAmMO~7NJ+pq)2dv{7VbR) zY`{Y#>_kgBNJ(@H7*~kHUhq^|4L|zVgPN=heEbwhPUvELS^Rh+(vQ2WlT3RIF%|&w+gCAt%Xr za$X@EE9mWScL)96jVa?5Hh5AO&N~Vp!%z|NPjTdv?12ih0iB{G7_~j0>1NS3(bGS3 z2j|wN^h|cd+da~xtNLFQ@v%+NFcrB2iqZrN6|3-p7Dw7?o*{A9;KNPg|@ou1=YFab6KV8D~Jt)?_kFl``}=Ke6?( z1KLSCPoF*??L|N|nYZ=?6v~C4bOgh2Yrb;x?}8-n#Uqa`2rn>zyCapN=8Dh~hC{&T z>3zo}fw~BlbTGeJ;RP(oT#YJ)T`6+AAxbnA#Gka`_sdi2#92HFx(O`f=em0_u~Fhl zZRMgy_w*;?CYte$S9W>Cc#dh#0&I813lQK;BN5c!_3LI1;jop9FETdi!bb4%Z^g{+ z2A;9hQ2e!O38-C)2+M{X?6UdX>Pg`$r>rKef{?M)o8Z;IIQ~_D?*1x3Q&HsI&EC^* zYHDeD^aGH$G_ED+X^>gPpeWHanM>U9_aaYv9V^SS__28ll;n%H&KAH<(VYzD;jUZO z`Lrn+?JvTML)DCK>Sy*c-$iYAj|t#>fST)&p3GTpvL%A19}6;`(^I4py-wtLHDteg zUkWj?Y!xFiZPZw8#q&twdy?w>R1iDXvWB*JJhBxEqG^;ewR6?}&$aEwt|M3mZ7aGI zY&|dYp;xqhvCC`I;*v+@beniJO_aQYkYJKjRInS(vtE2g+hnI0_LK@XSqvNE?o&_!$C)sB>V8|0kX~2e!-b8}CI@w_iVrrmk2}XKNT@@)$;8j$&$#xAymk1*I)#+;jTp z@fbPSY?q3JDd#@bnZJC{)(61M!{l_7-m?V^>S}sX@20O?`ul*l)8M^R0gajpJz{V`F&a7bYP0r7pShX4 zm7$eksAkteHxmB3(~N!YX|~hJBprdj!^v1)yVG}^L9oS8@bt@)G#ZT}BW zboIqddHXuTI;9d7S*KK1yHZF4^UV$WYV+TFN+D{*R2S_y=bVebi>7~PRu3+H+jbgu z1_S&^ICQPMSh$!v9BEfkBS60?<}49=e_i_(K3x$c z=(Lk=-}GU)Vz%n@-gW_V)kP#E+?I+RA$xpIHB-xsA0{mj@*4}&CdF+_2Pt!gEKQrE>OUUB(Upaw<+;0K8`dIh8jt}g0C1{gDu-YM8WwL>|3ZbbbdI(^7L4gTj&d&D+#Z7DB# zYdynVbY2TLmrrq9&k2^bNo$QSvELfc5`#69K3h$yYx=MW`Ls-9du~E))j`s%>AZ(+ z_&)1~)r^&vH8ZiX_`x&Y+)S)p+v^>QLCJeNdcGL>h)%J;xWDA}#r zZ|`Fnr|22*YiTe{QA-Q7_6pyNmQOLZ$J-0 z0-jje9;hXFUZ&66+WGQd^CG8dtYz*TsqXD&R|IA0dk>)qa8aljQ0lAD^1RyuB{>xS zA3f^_`@i+9ICZ+=HHRss27&r7Z6;0qB(lthCoRb_>i5=%+we`wK%MGpOsbxv(?jzk z7tpa>$v~yV%0qVsXA~@MuhWn@Ro(aPNfZpwLNpDB76jt`e)#MV3M>&+%GIh5v<+?r zwh3}Nt_z<)fYi5Cc&lHtub2cj-noa~f7(mU5N9f=&j=iMnL$6o2Hz#;d}i?JWANe5 z03?t;gSfQSQji>NF2j97kO4<-mr3=w^R*wOu+Zee zVO&h(M9Lw);W@(^a=Iq%xX;8a;2>x91p91tZ3l%iId`;#7_y}uv2+HczM`35eWR;+ z{}zFx&pk3miC?Zu>2xz!O3`^5Qumbg@C!Rw6G^8nQEReTCLmzxsc^rL8)hD zjs?XOARysY(lE2%>rg~5R~;*hKD4IV{*}um{%eUD0_;fre1$5QFwS3?N0~wF%d}NQ z-S!$hUgI2MPv~|AK(|RcoS0GL6X6qYV73c>5?v=Q-c1;TJKRl26n%Lm^4;0Hn;&eP zDXRMo&BnvrGmk&szA?;6PsC=Li&ZJCvnHIpWoM4FC9`WZ@A`6zN;!Nok8*yHvnEIK z<+x>BZNsQ>-+V#4(Zp6}E{i2E+jNEZ<*Z4E*+T-C9_iZyzbva z5=oEE?8p#pk}w8&K}|(TflXv~pG7`_>WMiSmYTn8w*K7VPDbMSaJ#|*Ee(I$*_iI3 zn%sp^4x-=*uaj(G>rZPjKO~)nFI-mFPSWsmiP=-C?*58i zgNgJ^AOG+F0w7$vu+l{5QD5EE7+4+6P-Q6JLY!wQpcG3D8j^N$W3*A|eZK)!#5>O{Jk$+`0%w=D*|U`M>saM`7$a1~7DdvE?Ag1)SO$JzEgtYo5D1eDOg`}0 z{_}(rn440%bWuU5^KP)ZnRI;GAWb^Rnh>YTFAgRob7>5(2Ha@Hv;3u@1=P!-r<=Fc z5i_X6ASqp6s*Cp3BVfq1k;RwG^`&B3YJorgqfra%#GwA{btn zJ&2fZ@lYY_u(mn;(UEVLebFo1EA&Su%l6f$70}7#1|P2b>gkslg`7YibraEec1P&E z{af1WrtgwVqDidc!O-zW;w+Gf(Cu7mr9BWg>M5O0frElNnggpB`%i>rPcOe^qFAeX z=|LBAgBB1W^~^b!THTQug%V3zwHjj-P?@z^qm(1_^K!KN44k@&UU&e#PhP?a97Z5=b&nIQtV2N~z zsO)f!L~4kp@Q^Gnly zBHwA>g}1kN8`l|vRb63t&Vjp-F1AqlxP%OvWMJXJ3I;kR@Y>z(kX zhMB_`A912ioo`0D5~OrB$d3X7Yz!JDrj^Is3R@x1PtfsDv0-L-&8gzzK4unn`uzPA zC)~S3KA7j=XO0OQ-I=WWm(Ki$?kv@MHs)uWIC5}lTg9 zpoHBwDxaT6eqm`b?M^;q?|}bw;9ya|xQE4W{NOAja168$xv7@4d~b0%34?j2?f6#k z%g>$li5iQ-uW|zI`0&0huRbT;*3jqjMOJ90T`XmgpDgfkV~jfRE}|5p3}3L2Y^lL@ zmlOpKGw`NVWYY{fRC@7GcXqWt0YB*R!7#EPTMxoi04bI}!&nJpI=|FkDzMK4qm6;F zNS@M{YiXLV@5>$|HMHL*o)AJ>ZSc1Mx!ElDc6FhZ+xUnXB%H)KHVa}6Gdi&a+Xf^i z4)Bf4K{9DuDawoITs073os~Id&K#-i9I|0Ho&l{Cj-twd7RIiU^xIH5#(SnzmJp2G z;o{4mBzeP$V{m<%_A^VUuTy7_ywf5aB`c>n#xE^eobgObrex?U6v_yn2qhvT;LH0P5_E8|c(TA2~DncHL{ zNXK`pXl6-EXJ3A?9@H5AA5ZB)C1MuC>aZi>7fIpQ%rUI-5-; z&HKc5b|<`4W@A*FVe&vg*F3MeeI*hkn=C)Z)G z7&I50xQ6-?EExwmj~3N|6AKH*b;UxHaS8DQ5s<-Xd8oXL5)jNJnZ zlujA$p&Dn=YVb=_@FSk!Xnd*KTq+~?>l=f_WI*27iumAmiPe@NvhL=XuwYIF_3W^y zB1Lrm;AF41dG=V-)3;B$S_L0tGZ$n=oNe51;?Djei#LjOF2d3^uRDMnNzo_#Z!7pyb9nTX53&WatLk6?v{C9qOv{2IjkQ#P=7o@6$@uLSU8M{sCVvK`9r zaolTf)VwV0%(tG0x^wPuS;dqWA0~EXqHbrb0H>7=l4SnCm~PM8yzd$tOC`plQs(B` zn3O7KeAFVNl4jMq7sp6lPa2p7ws{SHIc|9I4C0{ffT_3f76K@2;6RF3tYx%)?#^NO zJioLAwv&v^-ycW_8fdBlu6VjsT&PNW5O<;2Tfr!wzDHIdni-7Ow!hi+*d6X167iKQ zHSU&-0~`G1wEL-s&woRoYl%uiH$i%Aid)X#VdA!}!|1dUmPu>SiV;rt`1=_#^N={B zI(Vb~xxkRaEi~$IB#M!xS(Pls5U_=`?&IN7E$<_eaTBLGTnN=EFUWcQ(M`-C6K%$< zWN>f)EiT4pta&esMs!NkjD@*1^B9jh0P|Lm`ee}?RXRq|;kqaWC+YfObkkkW=8dI3 z(64(1+fXJ>uXU}6vt*qlp~Skiu^4YiR9{)mLnVE&sdPnN!#FjRzW=+a-qKfXO}b)s zKkwsi=SN;M;0jpII`#6EAe^F_l@Ay(R0!4Sc{?^3GTgR|Qaz##JiMcS6%2e|*-Gp^ z=Sq0iV0dE3VJ#Ma{en&7MyM0*Q8DtOk9Mj)w=e5qrr6Jf@ec86{8pVK2d_P3k$1r? z&D}zN-pFQypapd@vi@~-&eJ#7BF9r`nE~LG>%lACl>0P#Wmp@W#=9mvt0MexaKum| zz{Xx2=(@_t+WNjv$0Kx=&J@T6r>VV?hvOJKe|B~@p`{gZ&DLtOBMC?_Hi_eB74E`_ zIoyC1$fHe$uY=J(V<09{e7+)!-4Mg2nfL-U9wu_k6f4y=F=Z;Y4M8v3KsEJGIz0L? znpXdxe0Wl)fxE*mgcd$iT6w3w6;Aa7MsdwRL$Ncfjc4fu z2ySB&I2s8o!(bI$trsv(xf`*;Dwxj@Ja>U?;i&XP&8Z?rMt<^M%Djm?a+iAHnA=Zv*``dV%uI~Mtnx5?svfri!P)hq= zF45JUE|dLv4nDu7&ZgsY$6BZKloUlrd0`HTm}4W9-==?ibxTB1MBA1p#(Int7=syK znG8T!)I17hf>_TV{>e+9w~36w-=AJCOcB5*(Xc(=Dh!OdJD(h&4*o%>x=ve478$ju zu*n{lyGAY2&$rI-#?rdTlOJ-|o+^?>Z#m3g&1d%zjx0~NqfU?K-8qFe&Q0GTYhNXP zJ!GFouRz~nZ99kgp?=MnpK7V-OjQnbTybzYYX%~Fvu8EJgNZ-Wlg2?)}Vm< zqsIiN9e-X@QWA4nT@=D*?&L9{d#@EEng-5@a(6KVWisicb+bEU`dy&osh&t{9z?qu zBStmS?{maTqWQ``E%%B=7869K&5y-t*9%V+o)lTL*q@g~Osp3p$oGMb1L&WUk6&xT+9WhFv77;XD!+u_}i2s+U2UK00(f(^gVK@yEU3-bSL@XL_Do# zooV%|8e_#N^;9&|?rsq|><^3+q!sYcGe-1l)JS^XM|__=PKit~r(KXXOOsA_W`M8t z^GMG;`!ozt_m~8`V;TXaP@639ygst@g^##8mYDVPY&ESXnxWUCNdLuF@t~j5bsJu{ z>`MhkSYb2nz1zK56n;rgmDYDlU-Y^vS00Yv{s>RC%ASvYmwpgQPBZ?%^|efs*2*CA8?{{M1dTKdn_HwA4&0~_W2Rq<`0y0x6^BR?9da9-HHp`) z8J*%IpkO|tNMdpR&4?Z^9GyDo&IS*_xUFWbKF~RB!kI7`UNF7aeT&6t+QOr0E#X0Y z)#G2^ocRs!`5~3}+D6f~!f>Nm{E6HnpfL;)LM^i7-4*NMF^(+)Q9#{kZnfZ!VR!SW zgmX@l%hD?FtRAFL7KI8&4Q^;T~!F$=;U-^DHr{@cFE7t8u z!^m>S?H4mx9nAf3sNMN<$J%!Fitg~Eu}muXXIrbPMxeNO%vWG9HT0+U?mr?Lu2CP} z*+FF`|2|;#SGF}TVld0Tj`_5p3n#fm-T#KX{WBq380`f!-Nhh3AY(&nY#?up0nEB& z^3RBdoV-@&fDVR92ISul-*u!9dSB|%UFyf_sqgih;#|ovXj!V#Z4QPufv;l*bot%) z|6C#sF$ip9jaj?>p;%vexn&X!k`qViUl+*Q6+YP+Kjzirp_J>FzqvHJhqUOGclt`` zzfFM!Tcp9VioY3s_@gSoNQQj#YLn6kF@&ep{TjF8LC45SDFs2wQYEHA?VgV;f`dV; zM#)p>wIZ1|yHo=$FXM&uHGLMt44Pq6T=qdoK3#g!^mG!GLdGMa`D7d2xe7%(=VhyZ-9=%TzW5>0hT2FUEB{8hj!- z;Qg8iOtj}BgLfGb+__~CyGP-Z=-5~0rzZngk2MVpNvn_gl5xUdZ()BVfD0rbuXZ;R zPB>fVb)#tZX4z1ej8ICnB`NA}W0_;n8mnAz?k++;8ErZx4*Zj|@Atc(9a3_@M!y%3 z>N=|s!$6=QXCv_Q7vB#L|Kl(oM^*o5EQCfxno(q*hr`fj+G_ir0&EcWF$=4uR7XwZ zYjjImnp1_1Gu}9*HX|(&{b9}c@;d3gv~f#D4tuvo==Pm^&9}kj6gT(Wv(v%%inu4~ zxz~!F^gYS{oonP#26JV)n3kG5U5d^#S^@BzsmqHic^Vz3os66h4-a1j`W+zxynp8%ws#B4Wv4o+u2?u+ejoc$zzB-!k0)bmd!u+>t} zja+B&Pftf|+o<+&F@dR<@J#C&DyMFiOLc6Nl4e%&+0koObY4k=(&i)7{we%3Wv8?Z zRG-IwvqK$;8?(FC&GXk9x91BhOF|x0h&KL{`{Tpr36uaw8W(3xg$e~Sp?l5NOHC$W z-MCsKw$WQ|Qt~?>rKRtDP20oZ*yQe^|NnY=i+VTKg{|^+AfaFPboUf0nXg>p67 z!_T-_IfzQsRjFkBcDWt56$w|Xn(e6-`33rO<>wjzhpKp6i10X`QNZzlWoWIowo+^U?2PIK+Ov)3f3#mX(5KNgf#UPr zm{fHvX9=aS(^x9oPXy#l-N5HBFSuxZe}G_t6w{4q4w*#FLo96HIIyucv9clhAP5^V z`z3qeA08Rd)j`c8m}?|`{w#4ARw0fOr0_6gQYK@W%)!!oq7MAD#>jf{kv$UqX5vJ( zO)n=v#dO!fGu4d#^oGt+QLBuI98-QS4fArbnVLlsjSwjhi@g9($WG5Bz;^v?6@~g6 zv+^KW`OMjR>+{taNHaBIVf-*qJs+j;cE-qy)k+I}(V7&Q%Rv?E+LAd%X=#trSexUR z1g8%ir?oq=>r5oIE!Q&Q;P$fqB2KqyC~MEw=S+bYGYIGgs1Y=W0 zRvL4=QHh`Km=W8QR$Gpll2xH@ox|{GN$3D8*>rzm^IVIc^JY`&>wyCp*#p6=sD_Nv zg#S9tX*t`6F5;`FA#Q$@)s4~4EHr{eWGtvxYHdE#*#1a_V&MsNxp8;UAfnh8yd~B% zl{TqjPJf#XuTCl(OD(|ezV}tBI{q8T@8gOn}q7IvP^dU+N3p~% z33vg`pPwCnyzh^+IT;xyB0(Q8d+HmGZ994HyLlSjF&mAIGy+WIw;|p4I8KVH6MX}& zgtv{5cG4u`@Iu4N#5ELgJ7JH0iDv(Db28FW1&DM~AZB9?%CTWtZuh!MJrBk8mfG0wwBU}mtrIJouK!x*YRCs8EH2%^Hl?82~@yk_WB2v~I4rHs#e z(0B|VT7DvB^VQke7;!Lv*sQ6U#VbMxE}y+(YS$>Xwcg$^@QB8zs@U8}RQ}bVp(R+q z3duFbm+hBEv6vS{W~L;14jTp~?DW=nBPOovs%>QSdDcAdBmhSlCC-cYh~~X*`3#gB z+zS^jxVOPP=02p_cCt7GY-Hp%8NTP%dJi|dJtnTOAxeE##zS`YW_VHk441YokAvgA zG`S^sg{(rEL|m$V1^$#knqo$pvNCoYRrnh&hUV%l?F9-15b$>(P;Vf3pif>TK8_9- z&p%&AJ+sKiYc4bNZhL}hIo&w7_WF6HL|AMTq%X5$8E0TpU$#dHAAb#0CL~;WctyF{ zZe8?aUmmTM^`DxdPy-$~k#&AKLxxN6KN|MO@5{O_J=%ISX!nlzZ{umKXkOIf=x?|m z%d6NaB(%!cvAFFc_29S}*mS`}s>KwNK&$sTseu8)#jzZB(GGu%AAFo2XFId5U&3mvXrchU~jj} zEoBQq&+Jt^6(L&z@gN(AdW$3cq6ON<+nk7|xD#wO_5@k@blXiEUI?ezdV3_yjIpp) ztBZP69v;B?A$uO6|4hUi$)~+FLkuy?+3}ccTSmqKHPiFevb|bc^`>Tr>kSgM)F{sR z$=Sh0e`hWIYgD=sse}2##FnF_^ph7|Q!N%ZE~{@d6{&iQMVz~Vzk=~zWz*heu(1~k zUW7iR^dp74!1eA?{TsE=f|JTyhNerq?D|#_zhoI1VFq@&UD}sxbtej*$;kmKeEy}7 zq3Ve~r<$`TFrz_GEe{Qt>ucw7wQma6Ix7@wHQ9ddUW;{HURI7S+wv7{%-;=3th|3< zck7t|)*9ynQio;&@MpqGeqwDJHVzgiJ$(xh_OUT@-gb|=zkGG`YL2uLK7z|=yX%d> zf0}ge?wP}GHeD(@1W1A3H?UyG?2#E1*f=aTM+hC~F*6n$s0|R$({;-`U$lHzcpn{( zIbO~`azcO+Yr1vh`#{^rZBjW##te4ou{rUG!ES<<)mObUIVyY6g0eWj35?gf+33nt zuuthZajhm;DwLPm)PCmu{tvm+0HURVv9Iz%9(OXPs$W>gm-XQY!`O;plEx}lVuH^RBE@1fXIj6wo)zDhR2PwiZk({gW-Bq zGAhMrcF@8}yXX6cG_9ep5{@UFyYzZ||?b7?>|_#xn=9*$>C$=$SR_P!U0>MW(G~rLkIL^e-%& zZ%=vY#e%cC2gZzy%$u94C^LBd-9oGZwx(Mcd@)M zteR?w96vTz>QVeNxufEzOBDcsp>xJn*-(aOabkKbNOTk`QPgY?`wbjM@r_9T%&&^b z`j}=>v0_Y20}hJQ*aK(losk+EQC`*;Jhz}I*jHB}_HehG@k?)~Z}`8SQmsX~Up|>- zGUh(TLXl7#tu?f2z8=b0q}`_s_z;v|?wmp2_lWd%Q!m9xD$eoj9VJ=xbUG2pn_PGy zkdD6qexdK<_mlbOC=woSXI`j1U`oDwI+Rv{-{CH7zkHI5jrIH1mOn{LfzWVGm#OA)_Ysx`72b zVO|^(^_;UyuA4ui1{kJ?SP`F2%!Sw1)_zr0BcQFpROT!9N5`9|SAweqlbO&h5lnYu ze2tc%i?*gEw(BmH^*;UPoJVO^~ucxcw8#aNa|1Us*7i7rU2 z9u*W|o{WLpPra!1z7?>NY-PZePN9)8k?Kd*OLl#=VE-d@8nTe{`zd~Wj1^h36`(I! z+!y|@Ete;&1pZfPY1qZZ1rO%SrSMgz<#a#^Lj_?BgM)(ug}lAS?0P*Wm)mIN%HqQ@ z-a`q&dq)J$>nf0D^vDmDAA2HwNzFl&sgiW(TdAK@u8=KOmYt-GakMSIX^Dhn6f^N+ z+}lTJB?n*6BkyN?lP0bSH_c8D`4Hg|T9^&lMvK^t`xjddWl zM6vnJ$wb^0P}^PCJe*_l4}Lb^D-L zrvdK9H@or^IkjOwMO-k87u+A#zof0K@Ctj|*ee2)J0K5m?D{x5dpBXn&LWPS&90Im zeS!W}so|%vF?*d8jFmY2cF_H;=!2CNy|3>YSEclXoUX~MwC-&bDHVh4z#eWKMh{+V z$!AhqXxETili4dHL6T;3u4ltstiA)Up51010}#$SE2h|Jml;?)yVD-VD;*~%=45X* z1lfqutW2Byftd_0wh=hkZI-*@s}BUe4v1N|q{6SFtc#185zZEuT~UzuKVg?B67;21 z@AAnfNj>%h{cdt^oF-3~nUB4eZVleNpo`0Goi!|Dm9h-qw^AB2BH5T(wTc>)N2~TP zW-b%1lY@NZe;=U^A}|;@H%x04gQK&ws%-aOBUzpPWV^Wbgt+fnLIBqcfR znzlS0Ce;FBZ(9Y!qY6n8u zdXh;?BBhHClTg@NJKCM>ZIl*O7$kE6N4iGKI!oiMb3MsEAsWwa`7JP?QawJr-d=L`g8;9$tf&IR00>_Dp<)llqCXw`|^Ol z=+9h87yyeojPm`5@j9>KmQ}fk2p#lekbIfKcO=5e(N1%im5dM?*&>ObcLovEDJZOr zI(62?>!xW1c^ezEUqZQ?MB_zwt1kL$DaiE=E!0=j>b@YUMwN!brZM+>>f;EHyCI#; zn$3GIUfX2XXJXk3duczrj#DRiDdErN{{;DcrCnS=Bsou~_-ZI((y4MfJ%1WJUe22t zXwHV(cibU0MG(%t_?e9D>Fg?{G*_iYmO+{My+;Ogn>KR4IC(Bdy4t|N{S>)p9F5&2 zNps*=FgCS-6qGZSrFNZwy_BdNWX}F_D+}$fd91@K_DoB0=4oYWQ7?T_BAj839z1!U zu=V-)Sq-a3Ia#GqdA8aM@SDqx0rixqnp{0NYB&@WkE#KNOx|yp|Jo?PiFmldwKZ~z z`*J)2x#!({dAtj#mp8<;lwQ9x$!xn9W|wX)U9}5xl3K@Un?CLGsFLSHs&k#Zs80c; zh^*{yLr>au*RJpEVq?;D2hzOWFkfsfrF#0Dh??1l$P|>zl{OpSAXpvkCJ(v(bWr)S z*K6#wAkZv|;1R6+M~c{z>BID)ataqmgVH;{oJ?c2`+2Vm28HjA1%>bBfM^58GO(R& z`ohqZwUREo;@^>#5@UC(i7=F_N9v*&gPf#2zUW|HS5v6`< z7&otD0h|~>(}xR|QES)q;~24UOj!zp%~eP6hhw8>1e-V)FAXDPdlheaY&QD@$+hHD z)w2B0ns|A{b@-SpZCPH=r-GP1ZRm>kr;@=IQlu$`y))5g zW<_O_Ws_A3I=V+p+eh~09!n-t)!lfqb>7~ZbZz9CZQHJklhZd;R8rKy z4l~Gg*A`pD{;RQM9s>h|pMinkTh=TyUTl{H5$0s^Pe{tP8p_InA|#=H(lHKGU!=PS z;VDBp>Op6S$T?LX&%z9j5`EZB;(09osSZBvU_RhB7ia|s_Q2K#?32gjWYSL!?A9{Z zl=k2Gb3CQ9pcTFp|Eb_WpZ=P>BnW#=aGZISl=SSbSjU@jiX;*qmfx+xhx=l$opXqH z>thN8{Ck58AO6St)F@m$_}>+N2=l-cwCSf`X6yfC!RfwSMz&lo(OU7 zMf;ZMjk;rFWAfLE@NBJOCZY~xqok?0!o8PR7oVfgL0_iPu~YZx!^|r5B+<;aKDmfA zM8yU#rITdlJngSZ>&OH**SFDrT zngh}GLUvRM8Swu)Z9IVl6Ght9U&7Q!ms<#2e=c>0@h>h;KP@gVU)wrSj(TxoFvh^G zxj`Vfo(yAi{i<)pI!|#P->pyZ)DmnnyrF5Mkk0 zDm7U8q_@7&mdL_t?A?q=NDn>y0-hi`RddLtXb-AFjj@J(&TOW^^Y@|ZD+aWcLPYk+L9VuKNC zVCcuyGGX=kd)?K_-3c91_4nzdLN%PaV=~_L^C%K)Pr(;+M9#}1n>H~01=v2D0J_oL zq=2Anu@sCg;oPN?#B+>3v|$FS$gqdEK36a&+Zp7dbtqTpbAY-1(s22pH{tKyz&m)o7beDz$N8QXi1m-ewVwIn7(m0DL@1?hv@`v%|ED8qx)5zwi5 z4(>Pw?bfs4J(F}`pUyACHqU#(Y(YR?Tx;jFD(IauzKHr~Gbi-Ho|BTBF1%be$%8|i zmPgDE5ELaj$PQmXI>(_C-QQfEPIt?cBK7-R3oX0l64@Uai=~^`0$8SNPxfI4zP>|8 zV4~>^u*r3&RAF5(BoQt&R*(Yse0^Lhc6w}yc6yeo+{lvw@tJ&hSoIN}WEza#9 zU*khhJ|ci=GQN6u6q8fca>!-k-rmnaR7+Mm%S?wKvMP?5!A4G%9{&+qBiABJM>H39 z`km7UaJCem+47K^x_towwPu|Z(1HhxZbFYADvCrz2YUhlGg8mK3*97!%ZIyb6{JlSZLcof#sV&cKwJ;W2Vwf@}x z^Y{1+{QWTX57$o&9rHFs$1IMQ47~du%-DXCU~~!4#A=3-BUJRoR-LOy9=GqQ!5QU@ zEl#?C5Y58eMhG%lx)we5n|bNt*3)N!8br;DYK&6SM1dk{jz?ivUr1Jm*b_Meb0rN zuyzjoj3jpQ1&bdUnppHm`-}oKL`Gf%mer_dShA6fd~9l*#{*!mUJd4-URoD-!=+oF z*1VIlIkqcsCkHz@NWo>7g;>H}wo8^JXDQ~}^+)GmyL8C)r%1aI`joIGnQ9!FMRG&+ zYNY*cmiRTfEO^xKk&mn`g)?%9Y#5-VlMXinGmG^NuuAenbcTr`s>DLUPp6fTJ_Y~1 z9rAukcMXXEa31Mj2`RS^ypiv5T5&Uz4w8b6CkiigPV~{AH#+Y%3=&bvDsF#&zerxA z)$6lHKooLq*kCM7Vb%vL&K)-e0Cz0!B~oz&m8N4X1i3_heGrBNKNhHhDB9RSgo>)YZE zZ!3%&+rLyb^et0aQMb%JQ2HDTBemmooVyVGaG4fHGBR~6m9=kv@N?nepAa)u1PbMj z9lSbw<&Xlt@wGPK%DyXUIei6iaGXEbVVbFy&G1+&Yq6?c%8x!oR!;e@J`?*%_%Xjg z8#XG@;<7Ixa+HYSY4XlQ67%FQp#lLre$vzheNVm7t1pBwA}=Ir!zGw~MMBysG-76m z$-e3XYqb2S>g3&>8b36r^w?U!PT)7`2x_r{jjM`n`rB(EmF8wDL&at zKC6h|(s0IHJ+0WAA4>l0me8zOb6{^~AoU-o8n5QRPIa8vNK;fC#OLSxd@WrHk=Of0 z{G;!~Uj1SFKesA?uvTN_j1!V5KSayK15dt+1t1uIGBULjTDnCZsaAEI0q_?%5r}yxVevlIrJkgSSkwy92#0X`5RU( z2gG9>GI9cw^Jm$x0rqdu2?Sx*_)uG;+@=W~IMCeq#S{bZIMF4`32j)Y2E~Gl7xQ$UMu1`4>o7 zSXwMV|Me_8_`Jiij=W-35gY?ZfU&VE{(w?LVe1*|Ht~IcnuAp`YBhJF>8d2MnZXr9 z6z7I_awFv(AB6837q|Ur>90#2E{rN950k%5-YlfzYmd-c=m1!T^&t|ul0x%V8!l{` zzm6?AZeKq>EI=KwXrsp9K{gH=Pxnp1G7am?d*^ci%u8+Zu$Gp&7NKhEU;%sb$5U+{ zDP^AU;Gq9m3t4(!{{~WF1D~V|v4Zha#qu*uq#}-wj($lCr962Nzye_Tzk|I}-H(LBg%-KJ%248sR+?hc}#bes-?nEPow&Z2?YGNt^dHIgFk@8cf?KZ zmz+mq^V)Vz6Ct4zB)uhchX)dveO?rLHA2JWg)MAPi!9K86axYt{wP;J0wMQXZ9qO! z{2!3z3)Ll3y418>7Rlcp6AmUo&(l9V8c>VZ<;R~Q&v>kPk$&sg_X}afgHN!Y6zDih{r7Sa z4}7t9-rGG59aL4$^Eeg|GT8rYoM-;~#39zpK%R{h=Ok9G)#Fr3OL4){CZZnpA-W_; z9nh!0H-SGuTMOceK`eK!6gpX&9j(jDD+Z1GS8yq9n_G-|Zfxo>sQva9p1A4ZYolhxad+Ys1`o@vDvwwq4M=q!Xhr5`y597&Abhew#BM z?*O0@Kkc{W$U4#-xqOqhM>DU3$1NFy_Ru{^xyc~QMT=g`XGs(5; zMBPu|4c8i9O?g;zc6@gSpLYeg)VZdwHZgx6DG;#hr!czl-KX`3Iwl1+;OnMzTw`uoG7BTPSiWbyeeb~JV3--qFl;yD3?$sml@m6 zU}MQ&aYQvUd%gZDA*AD*z_n!Qu+DEUjJ-66ZLjE zjjs;z|NlC8fc4_)AL6|FCR+nKoO^>X7G(KN2V8sYFM4uP_%I@AJ0dH58K0nJJGX1? z>1NP}pgTWp6=kY7sOg8!SRvwJ$ z1=OEixc^=<=`kProL;@iWAb}zJb&n?D;4~|l#7kjmz!_ibD z9Q>V(IoY-0({C^))XoGCfyXF|Ans zWbDj)$p@g*$KmDL16sQoi0@}-C-^lNCxMCxa531z^L$5EJbe>*S8N@apx2p@vtB(s zIvVC|0kideMLA-#?b07$WjwY#&15cQ@T*X0pu3WOGp|u96cN#1T+5}ipGfn|Ekono z4(2-h%P7lc_p@^k|H(8{Rb|v*@g4n+2`{#uz3`TBMB0x6&eRAj=NJEF!Dx7<0_oHzxo93&us z7p%U{%rro%)|O9#0V#@88v2EZrQF$!IJYt*&DZz8>N9vc#oxmSwiKa#B0Z zPrb}D2V&apMqlcFz5lwvZ9P48p6~hio>hHvL3+f63-j=M*4o<10zQ)} zhhWU2khSgPj=3M+uXxSg7uYv{-bb-&D~hL-cp5p(lwS!fPl~b@%K4(dB0h1?P5^Z> zL%wBXhS11n7xY$m@1=0d|3T82aL=5CMsWGl^&n%B$DGgg##Yxd(6$YvmB6K=PBKi4 zw}bO>&(hQ4=f}9VuqZ5^>y`v0b9Y922q<$o99b%RCx9^uw+i^<0 ziZ~;EvmNz1i#32uYJg8R$C#{nsmC#s7sxk6!C}%|*!pQ>b(PTi4UwfIUARsb)-rdu zAv_4%{Bhxiz=qnrVPMPtOt@U3~)C@W(j8^xm6}47Qg9$Nnqx{>LR6yBis@Rn&08b#(E8r%81< zw1BVTZEPdg7>F6{7hh0f+*PaJUbgS&+cA+>d{?5x`9zMe5seU1C=luD4~D%JaS|J5q5=j-gq!m7{SYKk{|Vtz43Lh*v+vVhJzVgY@Z( zyRQ5XVN9sN0^Cai-a1sAT4NptRHVfn`xcZd34}CBQbmFe%8eD6NRcRAn%6l~2`S~> zpegQj-M0-&iHm&i(39~H$mInTm8a|y-N3^-q~Us#ZG|`~Z%q2!gIc9)miJGBjvwv^ z)UVy_N%xnb&ne%V`mo;nsunqNsVGKRl-?~+_^N~Fvl5{LRVuyZp)+ZzVUp8)>jHoqiC_ki>M z`aJ6VdMX2MRK6X2T1fbEU?5gtHrL{GzcO25(G{B?9;8XX1RX(d>Er;pgrmI8()yk= z-~AGFW5UElT(X#++cuQaIM>$VXm+rviMr&1g2Xlyuzx5D3{&5jR?ANn!^}C?+ zrfW?@f{`m+r1)2@m1<_j+mXYN=UGX`ebf8=f(*&k%%@ZVHBsbcgGJ0J{5KR#R=O+( z{QLv&hoxO%|LGSz{|8!DD2M%>x4&`$@LN%YW39<6%~U>aI~2kq&uCy_)DtjZM}n=k z6&jymeCvYi#&>(?%%FW~UEs0Q@z#!{IewyA2*6cPYP43B|B6BcDykNJ&ft!Jc5SXwz`!hAf zt6W6tjg7iQM2Yrj5luQQzu=X|t||`^ZOi$l2nl4ae09REJD}pp9k9nJ9TvNk!;E33*kCS4@wP4r(fDNvJZ z#O$R8wZQK@dsc)-9*1bg!Md&oC$R;{-}JN7x!Z!936O_1ni>}yS>0H)*j$dP5tb`D zjuXtr3>Lq%194MN37PLUZeZ{4Y?QVYxVTw!T=IB7rSJ@_@>3B9YNCupF>9$ zxbNZ!YvKnPCNL4HPwhjeF)yuQFlNgO%MTECboJ#B4>N{B7@WbGe{2XzM}w9|C-aa( znt8gl!USXssj@0N8z*}UlFLeQ=p%PI^9bymFdVFGsz?Wb9K|BJD5NRX8~x*H^GExk ziCYuLXM%QN-so-+G1x+p!Sl(epoV_k$}-hzmx6V&pGnD8^_k@e3>@7Py6&qE74dTx z%hmE4A;U6*WApD$<85Mc*mgM6LwVadbFt-TOBy$BfUAX@8L`|+|4}eAF?BeZw!YRQ zrCy8ONe`2$nUAuQLz`AHG0_ z_b?wsGIII~SCCWfD5aOT{c3W4IVls(rfrLiglBsx%mEvA;s*(YJbB*6$)4NXTs>d3 zYp5=UBKOQftL>8N*+POPPNYuR!$4CRt-0EYm+P&Cb>O^Ts z4EmtRyEHUVoH%aIB?&qQgl$|;I@RS0LssT`A*Hi;7eWz6ImVOKw~@Ct z(~@DHzB(VqyFpJsgEf(yM1O}@C6g9z%<#kF7!9z}LbJ#a!xPM;%v8Tlra9YD9^MM1 zSd3yfHNF~lnHh~#0$a@(On{lB6L`@}#mzB%90Fo@&p*`q1RIxe#!5$}WZ!LYy-U9(Vz! z*)2Uicte*vS{aEZIFUIq%V|BqGP^wO>z=whdPL01Lf*fY;dcJgsUCWdmmEzHdxd(- zFr@E_Cn&+BlF%A^#fZQpG>~y%SKq(3qu=9|`2N;7ClvuBm)K*7SlT|#q@})SPy37A!FWojV6w>$z&4X@cz0Z<28C#*iAL09 z|IH0D%q_Iv_!`H<$o#r#Xu)xU7Sp}dt=&5a8`{q3*|a1(PH`3kcCut$$~?8HMdi!|m`=SM6#K*&VA#|>QH>7S%Qu+25(9nAUmka&!yyCeG+hjIYlNTM!7# zzGBeyV(qzm>w3bopXg(>T16+0kjSN);tWdQJ&BsA?c66h+;gkcSZ1vlrDS|F)67*~ zCM6-EQNq9XVYxt)Iz1!&5%|#rU1y_Yy# zZURM~G|9@2^+8h1y#CE8=oU)-hf_!;Um|8pSng~fF&m>8G`I8Wh$njG^)D2fVV*xu z*AAyM-=)37U-=<|DUU(B-;FpTg`_AI`i{{{PmxF6F8A`fi>Q(iHPw&Zcp>^FUL@G6 z7z1`O$>9P)OjMJ=O@E>zM{OI1@^{|RJ+K37>E<)7^_gWo zn-0aV`?_h^>4g3=pvj%#9wLY6)Ph`m;28*hE9@XS4^H#PQU@NY)JAWz ztARmuFx*)D`C}67-F;<|%`kDPPG|~AC`C4(`&xu4_M1&1mOrZaFVhTEqXy8q3+iM)FGT++}fwnQS!{2j@75Voz}8 zw#vk%f-Va(5*&35B`1H&bo*b{TW;kPt4|gH&sCW;<)#R4{PGX!EUml4|{MF!D36v32ws_b6R6!~w6T^k)M>%qN)=f&Q zqS9XgM4zqoQFW|lk+27)s!^hASuITw=kiYWG!&&uDbf?X%JQYJs;4LQi|Lixj!4Cx ziG{NvSoigLaFK&$gpr@dqzzp#dh?DdJv}DDJ@HC7VQ5&9C_E<>wzPE}(H|Q@IE7L! z*6Kz5aYNS)^u+n~;uxXIf)*>C?DPc(3~(LM2PF4M2@>vt{Y|BJDV|_!6vIha(Hz=0 zI1E-pUFs?=@vdQnljK)WbwM%vhK5F1_wa*y4Y=TJWJ@*Ai6LE(+*&>KCrFz@A%A+X z$ycd-0SjtkJ;M1TIXSqrbgVL?MV@N#XV^({Jzgd=K?z-+^uLoC)_m+c0uJ_ve*`o9 zu3Iq>+{NkR8!ZmNn(^1=@q3_;>`y%<$hU_S;_k@`QfZ$IH9oc#e#9;yjPTciL`(NrvcT2T0?L z-f7RodW9DtWA3HP!J}bum1wrIyCI|}2+;E^{@RW9D_UyXeFXk?Ad zEqdZ3ri6TL7}grUIyKS!jHLt(P2X6u_3nGm53w4z&(FU<@qEU`&aHOmNa<(XdS-Mq z>nSPdf3K1~4&Nu~Q8+=fmtF+rLoWl5FtL69_WKE@Q5^7q<~K_2hyBlW?fw0u_&B5l z-Ol#sG5i{5fM=zPaq5pL>CjiK?S>`@nFp`W&YNi&AoqSK;}L8&L+kW>`>CbsW0Y*yqle9tUq9s6ej8hXNX&{H(?>yJlW8 z26P!UJ`B4SJzdGA$aSv|3;M~2*}@anN4+*NH6p>t%-){=;ppMA{>#zpck9?JO7|YdB3h-@GtjK zJ~~9Hs94y^dc{&7SR4$%R=U2Sb8Pk(;Zdm%JymmzxM^o%HBHxsVm}6c*a>=8_|iJi zc>J)7@$w5>_k51W`}2(K(0NT_`P@-Xs+n-(6?`?1F-TQ+_FozjANe+g^s?lp@DTq_|tLLcAt>9=M%$kk=0e6#yJBNV_*EY2- zEc-9j_s8a+sxP~3Qqo1?u;DZ9S0H@x@Ls?Vi7>(rG#GX}3q_4A$(Q!6B;NcUp=`b=NRHx;-XZn=;i;?G$Cs z-PPsIA%tem2*^9iCn9VXWX72k8=xD(T9*E*JWkz5;hS=5ET#RWuY_~biV-WCAc-a6 z*G>ol_uIXLsSh&fABKG*rA%~mOnj6gdC9#-ckiAiYrVIW+BF}qCFBUj0i1$HRphBQ zBTkZ39xT>&fS*~`0&t5B=Mx7=!+OGPwK}n!L9~3gDjN=_RprO-F_nT9GiexO1(ZSh zO(#Sq`puF|VDG&G#-69I5A)ILjCM~O%08(JV-=spUGUUh14|TzN?F{8;m;GcV{);| zE^_af)A2f%fe`~U>c&UtD56os2_l0vqK_G|8nKNz`(|$Z=+M!ukMx|1DZoYs zIB)uE=Dsxv==7=wN7aR1jZstbs%7~3RgKZl`8Nn_) zdtk8kWA`#p$#Kuu?#aE(DW?b&M zdm5BVoWmb6aa+=97;z@4CITKG%Eeu?QAs*Xz=%`Ilu6W>#Bjc=1oG61V)Wc+;-AI4 z&j~EvUhji*8;J6)?e@Hx*cO2G8r9E)q>l`5Jl-g26b*d@sAS-Mw4PE-aE=Zpsctx|%yE6!KG7u0s^P2IW=PRtl!Ec=^F-PZ;6n`aeCTVF@2iBGBmJO_RnHsKC7>5}! zaf@=im+WzVj6PnAu^jl|MuDGK{??$GqqbQ!-QnGxqqzI;8DluK7|nJUnA{dgcaGDk;|J{u>ZFDL19SNjGV z&%fLV&Gb!&JYJZ)z}aa{SkpvJRAJra9-I9HdG1{AXEQtvp5)g3BIDk#|Ab(h=>CLY zR}D&vi%By=kGPNp@;0=NBA5u%|l8N&-6w?HKn*=fV7ihX&Ms;@wDIhvM_Kj;MtfF<5C(75liqx}jtj<)RWU<@w(E7G(B99+D#dkB!Z3CN;MJCVmZWDiNB%ozzlo5b`4B7I#LYucI* z(Q&6E_VKYyLe2dzLpII7!-wm(seIZldC@!jKfT&FuuNzbN9$W6a;)gPJj#zX#CoV7 zCmG%^wh|5?(B1m|fZVZGhrJRg%WQL@#h<@4x$S zzS~H$Jx=ju&SO5D^=wYCCgWbdc;2^73(whx^tp#Pr4@Fs)$&Db^L^HJbznO-5sgON zt&ZO;gK{wdh%k1x8KC9b;*Fgb#|8s~8_C<>i%TQrJQCm;d?+>mbJf2a^x|M%(sWa;wFjgr??7-kO&q2x zelckF7+F;NjOS9SO~!8xD5aen)$+B%>RnO#6@FZL<`d<`Nx<>Z>==*?|>OvgBGspO$)7KdKyGq&oHE4T9TwDnO>bHPIC z@m)jFc^yrRYQ*o}cn@dwb7Y5;l!`Qq4u?KZ!> z5BMJ*%AZ~5&WBcWjI@^s3F$(G4JcWp4~cb*c=WB_{L5O+ghJQ0I{RSkKD76|0o8OR zONAHrGcTLm@+PcWMV5J|HeBL%LT4ux_^oM+yi@I;?hC$W5E--tmdk?%e?xPmn^b~Z zzA@|bW4y~W7qWqo)S;23<@oSQE!)eL#HO&$a!QKij*2#(Xo%a9msTz>M z5{C=;{jYA7db_7F7${-Z=2_v8Sak%L+X4B1SZjQ|2#d2_rO!$g=zOysguv5ya|pS7 z<*SDr$}%`40@VjcqqKi}GfUcT{h&o(5vsRbD|tESiMYFg(%eVI@7ckpNAp&a*5KX~ z9yD_0X1JG2(IovgT1Dff{EzszM(%8sFkdGZ>t`*4|u2md57b zpkiV1Eq5DQrBb1~Rt3ABD3R=2!i8tS;Kzib$ockXa`WaVX!vt`L|d0Yhx{4b>wn*S zpx*jNUGm6BUdTC{7VV3x3hPAqRHx{1!Wc;`a@EiY(3BM3?gejemtRx`>tp7mr=9mG z$22bgiDPdO-@fYcU|86N(_VU4Il1xFvIeacdHXU4EmQEq^iyHGSZ>@rHsbe?=zPRO za>ieaK?a6@1L9Krfkq*q&kkR}_%O%Z3rqQwng;OPo1UFfq)`9CpUaBQB5H%k5$QD^ z)kPWa4AcX*Tlkawgnl4g5z6DZ=$RayrB`M9j}n*j+E--mf|WI%L}~JmI~O}C*JZ#_ z{=f)J;+4QAJ61eS8~rg?6N72=Bw$Q->Oe(vmC9=H{3aXC)KCwAxbEq7;OFX_OM`xataOQ$TTdHPIns?0GuO>+@;$J3-SnGeABvWP;&Hr>s z9Mo(&&>9ECmiAisxzcY9mEFg5=bWgo5~C1oWNnHVd&t@t`H~@Vo&*N<;Ki7Bi(i7H zvWK0lZ69-Ub8k=YbmrC-#Kw7K&{U3uaBd6u4`O`KpLe1h)`6KB$R$V+nfQV#L?Zec z73>2z`QPQlo+tPYJ`c)VbwK5zYhvWPAB)ptKK~uy+82M1BYZYI zak6~{?&Z2Yr!tgHY?GXiq7+<~fItgbhze2i8zKE# z_fq>`VZJ9_VsENTSR!gc_w^9k6d_sip&f-EfoN_-#3Vg{CirCBEjB5PQ25#O`2rxv z1MVd62f%cU+tr2^^)`yU4Fi*pu+HeTEzm;fPFhZjO zu$Zxkfk@EROx8G*Q9-bdXT8>nIG50am%I@12JuKS>BY7e&Z2i9o!Dp9^SDtUsw>Mw zKlZ`HFZccBtyVIt)sM=>u!$2!j#P3f#_O(0r2(vT@GsYt_gaf#jhnkQz55&x6PWqlFfu+ zpl@i$A%mw9uS@J3-g~9VAgEgnpZBlCzi6D=THNXi4rchZN6Y0I+Y%hg{3heko>6WzL@ox zG%G%aJFJf5L`qhMH+q{5F{f6UjNGC1w&Fh1cG&*O@q&45q zhCjsWmVv^z#eQ;6id(tlPBnJjn-RZE6Y3~H>VFgSmOPl<>1YV}z!S?-|16g`&5oms15@VB2R^d)55^g{kKiun`!ClMAC-&bbQ9DcSb zC>T|l_7aQYlTW1v=Ms0dsu=WlMIw(AD_WLH#R zqx!f89o`Gj5AeI(Q3`HvcYBA;kc&|v!r-QR^)t6;yz1zf`v=DAyoH!&f3{>PZ+-Sk zA;Hxsji0QTLA9ezr&PwYTFR1Cvj4MyV5xcvaTXk=e%x$&gcG;6YZU83bkhkvmAwL6 zdXsO3d+%)io^JC6q3AVs{U!_QXehnPP%!A%^GZ7I63@mYjd$uk*P5xROQ-cN}AhPVZ zLi8^b;6rs8AL%C&?0zlUvFZCIFzoaD8nm?B+4D;pzp*?U=Jl{VaBJ_bKP=Q?B1p-e5*y1Kn>|E18nubK z=+1i@N)0ZF)ea-cmm4b-4InXvv<8lA$kk+ilz-v1J{eT>I@xHK7XT9oie^_!Nf0a~ zEy`wgr1bZm?y2(yr@2-c5G!iPjr7eC7=fqHP)gHqDSqqyJ)SfYzbp~D@$qj)?(YPe z!FWb*|m0fH;6yK|J?aHy^Rk--FEW0!2sHV zu3e=av=}TPD6SHd3fmIO;qi!wkdT5=ARpW2b#pnLUqt29+m-T675B{IkPukg7{5x7 z44_%gkhF3q97h*yx;D6CgGn`Bu9^4H^J#HY{ks%qLf>Fn#T>>rAFO{pJBR@f%^i)8 zGETyJ47@S3An#YFWB@jCi8x1)Gkg!#8tBDO6GvkROgF@yAN4|?2R8F|v8sO4)oW~< z*Aa}1pUl3Rg9<>Za_k{pxH&aR504CWLJ!SY|Hx}XmOd`;@9_k?Q}CVsv6oT4jr~Qm zk?DE{e;E5kYv3U})vK#E#O5X7@S9B?+p_5fBC3suPQK`531Sl8tfXuy3;yw3T-R(} zS!b-chAIe(uPEKtc`Pikfbv6WFxrmT=mS#zVCdMw5NMvS61$eZ_rM~kbme@w;RT&O zD}78qNT8e18YoS=-s+X3r}21hSFP^GI%){O-Nc(A zVgZW?dq*s}EN0m=Gt^{THr`ou<%>T#>f4mEC66FnW{X;b)>g8)9&y$Yn zHop0fq;cS%vgf7fR^Ksd^vvzfO1*%)%_q3_*y@LGJnWt?u)3k$7m3((uO&B6!u6ih zbY7kqNqQP8kx}ZO>&3?Jp)qlZ%*`VY2~bzw>P~)WH+EUq*4dzq$@YndA<3BP>Y9Qs z|8#T|Es#X{U<|k&h{YYhk)A5odqmj%7e^S&yEgHbpI#$ zapqdQ1ekeKu17upmHVhHwD^EDP3Gp3B2DW;r{+T1XKDA6tZVIOLWnm6K5wW# z$ghQSNfCU!usiTB2ztk=rhDl5_`I*$d0wo==<$$Wh`czpwCthRHZt782Nu?!=IaFF z`7~-K%KW_N4M;W`*Ju2E*K{1QCBRQ~b8s%7L7|m%KeUdXWHGms)-s)nify>b_`x0T z?wvy3Qr_l}k3Y!PiYg|}1o!WS^Y1T8lwX7?q$U(t@FF@mG@7QT)HoLj0z^_~v@~Ua zl*MAYb4`-Lh*~x&tsHqqvOhq2mf>#90g4s*OjW_Agkx&?SI-4jE=lG)COwr$`v$7T zl&#t>N%E@diR*$K4xsji!)Tx%@W3BMryGvXE7(7 zbTM}XQUA!zwa(;WdhXR3hLm35B_Oe|K|Hx<7=h9^GNNZAzEng*TuoM2Sj+e-LDq@8 zcF~09S(sczT{=-LM5-)by{ftC#ur_NbUPc|PxGHc?-MjX#Jk=9s1d2FA^zMH$!ikx zEUETe=F_a)zQ2X$knx2@oz!ytJIt!cFews zE$`#O?=Xx{#n|J>kh433Io>{j;HnD{YP2R{cG6zjH_Setzq~Tb)RV_U7$hKgz!Jd!k^UwJcNEp}*gH^X`u` z@oNA0M}EHa1^Gb>mP#J;Ke2c_#dn@aFvL=?J+gn5&)CiSziWL(@%Q!LpV=GDwm4ri zTdf@EBhC3u*LFbH!r`|!p!xpC)}JDRrgo$yG8jb(Nm=Cw(whYi`Leq;(!YWZ{}^ zqLnnyOhR0mYW!VvJ#d=@;_;(w-A>Oqw|x0D7Ow@I0^OTh704karh?zo>u4sU8kDmT zijm+qC(58yzGj!Ik^Y#k699aSzQQS8Tl4{%m?R(eBsX*f7%k-+u%xLKhhQFe$otN0 zye>gsdEcT|Ci!cxM^-!rMGXWTJ7$DB?<5tXrZ;%}2hp{T^BHSFi_)a&{8b13KT=!1 zKM!7I*cv2E*{!8ney>d9Ixwy|9^mc#o@UAOOY?kE>|&g}R&J&TsGwFq9i+ezVI5;A zf^@jM+1ne9c}zyQuz0cv0se!Q9xW9?N)Ura!OlLWPcKf7g0@e>Dy(H!cN%`6N z0JgFWxtIf|;c1YPaa(`@?xZx!$+mEeJ-PT*#lhn%FP&mn6*(yTfO}r^DTKM{|4mFo ztG0^PzSa$#OPtZncz;F`F^#r*PY^uo;shSvmiiNDpb2a2C12@NC=_yrFj=BxkxtI&lq( z@qupOZT#HgCKFGETaMF|q59tRd6C$q?f-^A2f8h#(M#goP*^Sin7yFR-s$`>M2>G< zhEM9dVi$DX=oTW1XkN2lA-MTACg~H*JWKLgg;Y5+?n|u6{xZ!og!|uN|7l@Zu|JV* zvOuMtagI43dS9{pZuUpp0)cKH zhwh+KfU}2Qmn}@&#eps`ni1$dC9z3_`76H&KlE6eKLtuq#H7a`jZpl>JBOF^N=)1DX}GOW{~LB5 zi&2NN)Zn8Apa#WJXBW{PG!C~y39nO@CtM;KEVpq{D(;<%DqPu{H&$0l7}Sdb(X9ZB#RK z3082*0_J689-3ZbcwsS(JA9?Svh^v^9Z60W#pV|%@YV-IGzD7_Ud=o0;g3iL=dT6Q zw!<;Uq$sB0fZYE-7JA_aEdX2j|6T`WNucVlG-~mG7D#RW=wZ6-o0{L?HxNQf5!7@9 zYkwV`sr)1Qb-s_cK=8jH&FmU}+zkH&lga-CljB%s@>rfQGp%!=?`JRwnoReXNm~%H z^$1zYYKI~b;Bo15732TvRq%WHqY^7gw{u$91&Lrp*y75XO@xlc!qQQ8%=mJwZ^d0G zgCZ1C#tS+d^p4^#{;?N(WN-B){8r0qd*Js}Xlyprrda|prsPEkMs)4x`NcqPE!^FO zFHQp7i$2D^8FV!l{hZbbJ-SH%PEKAv`I<^G^yhBASzpl(dzT(`WG1Fzlzt$CAZuW^K^e8t>R)Avj5I_%c?`2(Hx z!O1$_qA&HBr9b1|3DWE6_00koAP2EohoJa}@6y|6zoWaUWv3$ifAsKU`B;obkFshe zC@22E_~a?!5qELgnDCIMSBR)=RH)sE_H|iT%7&*gZ(fmKbM>&I95j~`zvqQ{dK7SdMIxSQ#eZ#nuSMn)R>#&6s*_EPz6cSlm$z?@gV zoMW!zPX?YbMpucipKNw@lj5h1m>16`7X`&UBp?aWY@9mkR(nl=vxfvHQz>Chfq1EL zzUU?5v3^YRJKUF3dN_nZVMKUj-j7R(dhS>Yf=l`BOo&P0^u!q|_n#UWKRtYEmtqQR zG(T*3y6wjcZJXt;3&VAj!1}pJ&f{)>vHPGR&3NNAj@gbot;K?v)m0+y=OIB+l+?T?J{Glp&9I9{qd#Bx>WnUY`nHbL4pVl$tx^^26t9moig};Kj@Nq}=g0 z4<))LC#gN5JY+$|E|REgKub|y{G#*rakCt`z~rPII-q2u=erABKAdaKr+k}vX==pF z^2`!8Wg3i#r>{AH`!BXea@!wkPOBRePRGQLvG%jZzvv!V=Yiaz(Vosnkwo9)bH@c7sW5!&61$XoybJ>p zibc+*b65mbCr-ByXxde@7T5BkvCjv9HMMVe>Dn5e$hQPydeJD+#|1X;WxWP)vn0@m zC#86BF%3m{A1_LDRZi5K2v@p~3yeIIZjFW<_ynZ7z8UNFreNM2-?^`E%DsO&xyv~O z)SXgInPvph91zl-*kh*SZS5Esdad-0pfxZyfQjbKd_4wlKfU-jLEnoZV_-HmaIHNZ zcZ~YF7YvC<`w3{K8v4;OvIUOAgs?iwwilkKY%7f0HG-_Qbam4X4rG`&kE@kw8g6?A zQ#~ZWf-+V<#E+6`3#}VU_c!C!HsU`%mIiI3`%|9NiI&L0JSd`J#oK|WyM*Hh`^;ns zi)4IA-Q#6AY<0czN^b$$N4m`ABF_CnFcjt5r&gmvl^M-QJ% zbyGi@Yw7h0<9BSE4OUtpo@GI=Ky4Em6;a%=#!V`$6zE7zf-Eg~VC#K)SFlG6PsW`D zJWYFs-wqov-l!n|MA$g!y!VwVM^C{1^1UmkcQwiq5c3%~*UHYI9ls-#(slykhfvKQ z%qjuJ)DtzEC1_tw;&A*!P=iSu6W<72K@@I7ca;icss^0?IQo@to`W2~dQldUfjVW& zg$ULsS#epB_B2`UoR1G1!CY`O02o#uBggfnOocHdN8J3c zi3RY_6Ta{F$J`lgZKdbmA|jnoP|JC`QX}*XQ*kj2eD^jh_|B7f<6w=dBlB_K9>+5C zBDn=>;c$)}$)!|MB*@xi@2wP-`FjB^-Qdox!wnB2jSB}tJ_*%e?H-9PlWswixKJ`h ze5hWICAPPezzptSzm49JfJD(n!N#YN$bjD#bMe$`Jr*lSl2353>9BB*JNo_8z!0R2pU1H+%JmT*^ukKi7g*6^ zxJe(O&Skw)Tx6$O3E(v{+BSEK!bCZ-U^lSrjhWtvEaiO=W^E9{A+Ga;e@MBRF-+Ha@(IMv0zM=AxHU`yEr9QGv0BH-{Xa zsP?^9Oj!Hq$GNhyr<>c{BItnya3Xj!7QmnV5K`KfNQ&jEtEozEu7RP{h$$A^-H&Jo zPsB6yFcUcWc1!mrVQ)Q;z^zk`v8%P(^x-nqd5Ntr38(ESS^Ro^l`nx=A|D8?+~f~` z|3O-rR)SOU1O~IuW_Pose8}{w_G7VSLCy{fxB2wOa=6@LFmCrmBhO^oD00u#+)UmU`*uqI&KkQwK?Idv;Z<)lyIx) zq#5xX%RE}O78@hxkLd`M&<PbZ_T!)HZDWhg6p>$}Ha!X*ZGuO{T3>Cg3(2{K&7R3Z%0XfKI zFKqH%fgoYezUibuAw2UDVV=sW3=&rC46jPsUeZVDIlT(D9U??HQ{>dfCNF2kUSa&4 zUYj%BiGGEf%~((NwrKBy9ZE&}-;DNWwI-h>b3S>{!JZUZ`LOD;#BT^z&KOyr4>BUj ziavka#G6V6VSi+13Zn1mG9ndbqnzUauS;hLt(VZ>Ax!Q`W5qq`&PnLgJVA2dA;(2?6=3Mg>U8cjz@)Mw303+K4}U1wn(2PZV0>0DA?cdKm6dU zvjWpH$Qh3R!nU}>spY=xUC%($h#j_`nBBMyRzttb7dVYhYcPk>GYgv z5E1FB_c=#MxdlZM_rI^|Qy!~`PGPjl#nr#Yhq%n`pIJ?Y-ZELDC=a_aD~-DR?vx3a z`hc$GMx;@0R&q)m=b)cmkTc4Y+d;ZG6b&TJh-;eBC=m=#h*|$qBSV$^k@xGPOM@pb zAO2;aeq_JzymIBJJ4_%8b?E>}Gd8hs2r!H`soABzsFSf}n8i=_nWPR?EF%jlvsU$y zd?8RCImD~kZ6|MOjwvI3@tcOjPv#Xz9tkwH*a))CRT+ys5RYwu#pFIE0K0arEvq7{~WrFZfI}ZCq%1VS%>QNI$06C&U7rL zl@sbUgq|k;7KtU{5M3%M@G$oSU9?o>Vqy-Ln1$a)W?Z<1F7(TCINfoh611Ywm@;`c zt(M0VwSpY8HrYtW7#>7z5zYXs$34mAau!e})|6n>B2xP67I!CIa;6~6Ofkp}$1o~1 z=WO%arS0mekW9`REIjTLv_ehEx@gcl&p2NkRVLt`{-cCSu{P#wF>b-)m zYCe*4)AEk>t8SCL^pS#LQMkBGPBYc~)+ZrDD|>QCW=VI$lQwrRyO}Z0JLDj5QkSqF zOP1@5cN1Qd{TeW-l!x1v23rp(hbqYlD&CF#UdB1uq(cX`n=qi)+Hf#7x6C36qO+n* zd`cO^t_Ow=z>s!cBdrS6aMa`0xo_ykt;yM$3-yrcDH9F<#lwQo0HAKP1*glkRS{Ja zuj)IlfeiS%Z=AoYYL}rCp@2~UkZ{NZnTD8Y&zs@G-oF{n%AhdL7YUDMfc@qn}`6FtNk ztRGi9-Tq*&b@6DYxH3|qI{u=$x|EaSU2@OL`?!e4`H*~MM9_wCniN&RwT*6?Uut#B z4xxzne%nCt2wlVNse|u(+S0his;O!GVcGpHD8Y6iZ9OCRI$c>=gg^wcK~{$;5KR!$ z&$>5)GsX;8stkWx5KzQkfDe)TJtPT%2VRUsik&usB>CbaCi<4T=eh1y4r7n& zm{0j7feK;q{e(HR!aInzGCYJ0I!zQWxbK!9Jgx154Ilrb+ouci*}X|sPkHRkj=qLa zfp{rr{ML_&R8eHQQR=ZcY(onM^3E(Mn!1 zY{_a7;`-3tMcBOn%4v5>o^n60Usx>@O@TGt5!x zzlw{Ck+gDH3?Iy-g4uUVmu##eCQaxdXP^E1DKD@`;PnjSii4|$r$(S}8uXb5SQ9=W zWK8`U%w6}|t*mfp(6)p_JOk`IQ331pj;r!MZq~A6={mTgZOE8iemgyyR*>_5=CxH4 zxsJe9aD)MCM}!@D^^NI8h&LaPVU5`SiV!%-9<7;7q?Qx|XcBlEthCwfFUA`pomf+Q zx9Q?=ZP2B9Nr$tx6Go-Bv7v><-8eyRLdP%ejNq8Cc25>$QQ#+nI4WlhThcriW7qSqpraM%P`W)@y$Juz>KyUttWG=@B3ZhVb%Cr zsregT3;IEftxs6U_(}TFV>X{I zJ+;P5UQFi7()P#vF7pZ6tl`T`>Y_ghO){^fUC2)16ED)#VA7=cd`An;en;$RV{zMx z`#eYR!Ba@Hj!{h7SYD8FdUwhH_r>@n2JU6^{ifZM)|yuIF+-G4P?yUc7whu$W0r~d zsDAnsOXVo9Kwy&~Y-&sG=W7*x0cQh&NZ*&fH4_e*aNCtTBy+yMy8s*>{WORVZTtI- z!Y_2g*1z`E5RJu;tiLFsoDt{Vy@J5I3=x^9S)(|}RR-jni|;l4L6I*}!{*)8wY&$w^tv9V-QvIW~~o_E$#yBBx4 zY$-0}%=;Ei)6+8$1lTFe*Fu74anI1^7`T=*?bwR1L4;cE81+;A z`82vLW5hbW>T#Ha;YLIsv{tZ-xhvh}dk5fmzi^nb5aTrevJ)8_&=P6KqRKxxw|6M? zQnvj-1?7k|c-?Keq#<|OeCY`cwsh_anYn+czr=MCeCM>~3E9ugOmT(@ne}kqOn$K~ zcWGn4_Dp}>ZuopV*-qcd*_G^mZE#~;8@KF4^Ot$Mu>cm!m|*vK12aP9KQB896jTfA zXx02p?;sOEV%(4@t~{M}Hmy)vd&G5YFdzbCB6e=ml7+xp#R-rjjC+Yu2&cAmkIk+f z6uH;YNJ=V(Zckxib=ua(UCm2|@7@x$eqv^IWs+1P;$<@)&E{OHHj>gxZbvl)D?p~x zGA>!h3IML3h^gFN*5ae5N5r2v&5&o-2`~8g-O^C0!xV$K46=FmXG;&+1y2^8?gbk; z%L04G#tc(@JCKV)DB86Onl{wAJyzTAtC{h7c-$g=`-9qk@}ja?KJLF=aciM!-T;2Y zm9`uiuB=)4YQ2GGT8G<7!49K<3mK(VdN|@dyFR{mY-U!&Ptf$3ww2Ks0q6Urg;MAgw6ALp;EFy)6`XhC`~DGud^UN9{y*xk(d$ysfg zY)8!;=W(_M1C^kn)uXSTvQTr{!wMWp;UlefB%vj?CVFJ6jrL4 zNVtG3)qIEtPX0(e`X)w>!cFEf$2Vy%!E)7|80D%bcDQ~fkYr`BrusbC#|x3 zLKcUKR7f%$HR#z;9&$n&k)cYNAEVZBpJ(e$$A;f5iWY ziX|PJo|=*0=5heH@VcY$v<-T+#oaI_P|KpoFOAqIgK#&@UbJr@^}Z81AA1H&6LC$L z%G2Q5tT7_sEVj3_1OU5& zc^sd^&{u%m^sjh>S$D#gOC3ma&cMKMLQ59OQwf83k~VWa0I8jTv8dJ0@)3vNcHW%M zGbp1H6=Cl`1|=)hGQdvTb+un3wZ~|RL;d+%)+9+>gM3-wjcahS0?My5TgO+w+_k&h zF4<@&G4N9|lu89`0_}fGyTBon=}T22$-3allb%|8`Dij^p$KI59d9o_BKn!R8Bpp^DQ6-Y475La*G2U=KsD{mRVx5Sf}v!O*PS~ z5(F}&KfK2+sAQ#}gaW0;NNoC4mrj{-Y^;nIU6e?Eh2zhQI0K>BZtTMlC7d z=AWW(_Ihb_f;UVzZ~mG*GCqx;*E42p95ldhGfgH&@qZD1$+TGZS$?AQ=L^DH0Lc=2!lRnk%H%Jk_E+TF8 z62WYJUD^6Q7rDRV0=U`tbs11cX8%hOh4W)FYZcf49gd7SQr6L1a#bS9NTG%9tbgb| z#YEWF&=Kji9U>m5l_DrwPBNT73XV1W*g*}VTJ95*AU#_Ty+;f*4q(yweQf;O!s&c8 zOcQ8PVp712nV{153zzseQge2cT>aN zGW`{eR;!C15U8*5z(vA%88LIgW1(D8_O6|D!kkXIl|QTYafC?Y8+)=NEA=f8vm1s=MY9P63^V1Fs=KD|wqOXtA~p z{JyngsU&s5&yW%i)Af7fW=;}6kWLJbg=W`jaBmnpfxzF!s}j9e@4L$#@mlTmW!p&f z!+7h3PQ?E6CXwMzBZFw&&d_);=r38(-@m8*b{D=adM9JW{x>8H3y{dYzvc zVq(%=dZDm{H8!%Eu43K-XIl1j=K3DrP>}VkSd1}kQEXucuV|9v;L<7v1Lq;@R?G4O z((TEpl1Kg&o6!Vuq(7~?!ny0#&eWz&>U4J0 z_vPO``mjEwBK%|SdOXHiCt5E?xZ3$5)J{_F7}j>Xb-I#);@Sgc*u}V8bQra$5TT`b zMa;G_nK;xrmjje=f@k#1^3FXHCl`ROk))#>6NY5YP3cXdjF~Zny3%>juGELz%-UR=Gvy4 zlSsf_%Q0!9wGb|Yx84>j4x7r}5zgem)FpC&9DOTRILr~={FDa%2oi|xjY;3h%slqY zq%D{fy*dy&2L*F&W9^f4yYBOtUHQ*rvbj1B}ef6P-?(Cpy*O20uk#nHBjA>U|)5Ra$(l?4LPU? z5&dm7cnAK1_9nXp={q{%-njM>fDcCC zdYhJCB*g1neomP`JXx1lrVZz8p55ZE3YmOxbVUw1--j6N zc{DEBp3xpB)@;^z^MOK;Ea%olrurz&2Mx zySiG3hrLpJ?Ry{ee5jzAr;koqFtogt!uF3WSrB_p!?I`(Q5&9-ho+_?3gG=MsimRo zge8G4F0wRIh%{^v(#Vi21Y0N+L`G$Bj<0aY7?H0tIVun$W|SyqsJw!8r2(b)C+I$j(-9@W2tI!1l@{A6b~Z24ODyv3<$`uX;Q(HGwL z+Nt{FcN50~P1jH!0S&9~*4)DMn-z{l4XyII-7{kU|tv$?V2U?mcFIsXy5 zkkq&+YUTFfR=yxduURx<2h6fwy#pV6J<|O!%enSq-bJtSO@L+g&v8}CE4$EKZ^t`ZBg_}d2+?+fi3B1WdygTUya<>sq$k1k^6M6$eIH@PKM&GQG0PX zCaK&&z8~7xo$|Qx{#wD`Qm8czZ4#EwMuSaw3;FKO;NCXEnC_vNdNorrK@4t4GN%-2 z>_+$S@Dpd;36?yx>+H?UknTfTNC{y7@)&wdLte&JZzbnk+tT6cU*);ic#YH+xg6?Ba*czb!%qEa!K072)HnAaP7p3|xfdPeZ1kS$2XZ^Aj_Y!-GzSM`{GkF*W_!(M|11B_=`4l#G@ zY!?-SubqaOif!05nl8=qX$lZ+{xjFIU~})z70m`#E~;*ML`&296_XR)iiXaoKi9IZ z;rF5XiqPQaU%Rh0`D6% zgz{G2e->x+ILOKFzwg`0<*G^_j>D?FtC90Kp7qDc_W z3=t)$@s%}y`ExacyPLf_UOY*!i_1{Zll;dT&cQY5d9id&k;JMg0G0CRtN!)+FPg{* z&>r9{ea1vg1)w9?G?cH zvy1BwC9+;;pwo2R6taKw?~o@UX?$NIKLMQT3}74sm&zNi6A!#oAM zw-?$Uv>8}lTSw1P^Brd^rI*M0P9JCIbx2Vw`rj|uT$6lpz~X%MSfiZ@;>rk>T;SufjWtX7Uu2Hj+3xunxqeKn6vStuj^5JC~esc8MU z`WW=1sh}XZHm8`DhUrbofDPU?jof>%bWd?T(|vY1Xl=6yM?;;R)IGX`hzKU~l9Fec z)spx1o&D?&FfRBqm}O`t&ZQh0sRy1?5_c)T+B$Y>3T(-(I?}T=vlgCYbYuD)kPOmk ztZ3>kKO=}Hmj;1@Pf|kn6khx7dI5xLuONywRnS{L=h#5h@j$L!rTa&W^XLx)r@S=b z;n?b)P{de;E?n#t)N49|$+*kX36RAz%jpZOBN9dD!m`~tFIvuOo(dIWoO+G>j+DHX zb2N9F-hP6mg^muPqFI)!^M(PlY35)`(Y4{0EV0uiCeYv!r-SH{h_3QzaPKoysL>IJ zeGEbY18FR0caJz5N3^FN__Liam^?XolswlgHfH0Ex7Mtu(88bE9Y@x;z7j*ZhB`X$ zN)yh=m)UM8UzPPYCtRiQrZBwE3sB~ghEWHm%8<9x74oVa{0MR^gU%;FMEW3!b=Tzb zm^|Xm)6kBIMWLgr)67rZ)!E_rxJy~l06wi-yAj$i>`g5if;yReXVx?}vH?KlI3KBu zHqUE07CpZxM}uP_6o0OG=JqJwJ9YCd9d?h9nx?Q?p<|;zvI7%Q@6g3#$+@K}_aAz^ zrElxpvf~eVVZ|gpZeMV1_y{fggyVV~7q|^&$J#%6LZf|iwG^aMM)3fdwrru+tnhQ- zBTYSCjyL|a_CqxhB#aQT)P2|?-8y~uuJK;_=wWNdd2+w>-JZLA$;_kH;GN4E-Sh`` z!>Rq2cxO6xpHf#(BGzno#?^^0<(6c`I5CLu-U;E(W5TQg@p z8d@nN5mUVHLdS_j8E~ z#irkWfq|b|d{K=phvLug=FK`T{27CJYaOPUl4o%O>#UNO(uYm+NFZWS;83t%bK>)v z#RTTS9Zo?$$llJ3jI#34{^6lg+}%Q~a@Dv7Z$kT0r^_8Dv{cg)Lo}LNitNVq#mTC~ zv`uP=-yA#z12Lyp<8lYR8+^C$`D_ElyOOeZ29uXe98q~UoJ~OUU3mAygb4Lxv9dSi zPcYt_PPr`fWbS9occ5I`mm)AxQ=g*&B~#gdQC zkas+@&XfGS`I)yp=EKsh)_5=?F69&P(ffm7;X`=I3)36bxr8?XT}t*kHrkQy%e^_p z?&Ho41sjfY|uKbAslOYEK&8=pVg>mC6(l76_^UHpnekb-b9 zWn8FKqo3oD{8H}A9uwS$Jg_uz+|K6V;p*YsTwnEJ$mJ92`ljPHW3zF)0xr#)K%p~T z=cV7#4n}nfXQZ|wr^CMa5OOXSzJJEmW+(ElLag>(hq1X7QFn0-Be#>3C_#TzBy=qS zFBbZs472&AI5gMP+wmiqlpR;+pmiD=@i9u_|h`?D}*ZUwPN?%i5%*6 z@2>$pd72&+42)Y%!zWL1(H>}ka*`SE&Q&uObMbc3h@BdMZfM)^sAPTphkuKLTrvS) zfxafh1pkbD!aa|F2R_sA-b|ophVu~)XwCp0LOtuuBiA>OdRTscn4zY*G1BBC5ASSU z9Xvw`*lG64Zert!jV~yF4=*2qmpe%h^GaE}*d-Ic`a9i`yb&TlQfF;9 zt)@bE`0C4$Vk(^bM?HQ-A0i^4kg#g@c3hQM?$jz_AL8@IbCSX!f`6gpoE4geXDuyw z>mVM%GTMSypMw1wSqO3X$W76#pxSaFpYA2$?em$Oj}^vVxQ9sBb2wzt!Pu=wIPxK9 zqN5_MJqT;f{HHBJ89B_m&bR-ssG<@2J)ZF@V%2>J1VE|#Zsr?Qt1i4AzJ1XU>7DW1 zIkx}1v2wf(YA>3ZdSzSIjsmbl2)3Q}r%OH%Sm(9HMqxD_uGZn@W__RTJiF1Wzs;YucZ5 zIayWC+530n)07w3)_?-I+7V=8{az&W`dp!%G(-ZNDX6z~-Zd=_wOQKta-`CmnS=P_<; zd |#WePY#`4|tf&?EE9g#1fMc5+0>`ij`W!)if;a0(9PM&1=gXk@C*${Qy=3u&K z*{eI9r3eHzg*7kmlyL60ac|1T_=hWL9iR_3Yoqp;M* zJNLNY2pjYVjG@EhN_aVU%4M4O7pgz7EuRgBN`zWHnXJ!Uc^$TJz>msgsoIB*%9S)8 z4?T7HKM*66X~?UJOv1;WiTEdpnXs-cF%<;MBA1HQCLRcfhDXr%B_1ya&z?&{A+-Z0 zS&BxVj>eMKXp@G9ewwnm1l5;Uu*wV2?xDi~T;la5nW;~_J>D6l*w|b35W&A;(QE^pa8TtY2fudB?o1pG(f5a zdG)JE!GyQplq#?R9+%?l9j^h@XDGuL{;$~fI1RO#q zv6vO*Xn!BDOh1Em1KKxI9b7F~-o=0cjnh_(jX<1@1** zLR8~TO=4cMtSuXy@gDxVi@Nm3(Hik`ORj{%A6va~{YH1_3V3%>L_{|e%;O1g5IA~r zPYs|bcIHFmknsJ+>)R6p(9CsB?gyZNPrX?0_>xxd%XG%iWk{zH%XqsHsGE(N8iFdA zFh78u_}dNdk3P|h5AB^v7-pTeH}1}Qe+OLU}nI5JWaAIJ%U3WuJyIySK}MO@9U1K9pBl2!RJ80{NhZ9<(HL7@YO2O9>#VpQNlk}$ZamZS zNeE~?D`6X+zbJV^^}vgn(xjkWSGtmI!o$3$cgk>~DS8WRGC$byOr*F1yKl7-jXZQH zY4^Vg=CReDD*FD+47I=dUbI%WO}=x#U9#t|kq%yH4S*jgo~H17k{uc<&gE|P&*d--=FxAsgr~X5+V_^1tMoGG^qh~s5TBCKRdnuSyq;AF z`xr^6tW;R)XHJ$Ykrc|AGH3f)ok1T55B;gmnK(g}!*tJ9OMIs;p&5u` z|MeJoR9jU}yVCP9jpoO59P-ekowLcI8BL5^>3mHf1lROshPKe_g2LTH_sc*RipO@xUfXTX}e8;;MVV5oQ_I?txR@|wHjw< zUvYtg*G+GzJNra&V*rNSGj&{<{|9ws`*IQ-e~l6Ti#m3FrqT76ukW?hVfef%%GABj zwti7Eva*ta>tCnc?#KogX4lD_7QK45?WU>kDV<-LsfwEHDC#Xz8A~DkoK89x)~k3C z04Q6$O#);9V!~^l|f0d53{~;Y`5dLqa zqbGR%?Ej;5jGy^aI-XQZhFyM&5^FVW?%#RFE8f{(8xC6HAkC6ElwcWDF4Tpp)s8(# z@?t&Nc_ggyN>1;4sWete8j(DobOLQ~$6S2HL)&SJZ^wJ%Z^P2Z$EL$ei)(Mng98F; z-1qfS7UcWjB>(`s8!`BXN@pYQ!ip7?f|2T0s7FWAI^Bg8R>SjC|(XI$(F{ z{CE`l9!uIKrsNJr-b?rtv4WF%E#qyO%G|eRmVDH(OI4c0Nfi^i*)Rxm8>@tph$}-3 z<;6ydCsq&TH_H&HdCo3Y1|Kif$#$^2p^oBuh$3S{-NqDHj^!=1aI3{m(K|?Xs*Dle ze0;u$bt8Flmb+`G`}k*Huo%&q&=pfweVG~AO@wCpYnjMw1}hxJ;%_f%k}3=iKdMy*H^A0r>V6HY>}(KU;zX829LLd-W?mPc2BNzpV@JkjOF+kPXfN7syBd4 zlvCoIbu<^9%*spGETgM=B6h=K&sN(5Jv=8@?y^8%v5|_-QV|clRd01BiNYo)PwsGE zthA|SPONc(gq5PJHQsUz)`Ea5jhR&jc=0aM*>i2L!dt9V$+vn{Nc@eKg-um z_HFXOwf$Y_gTC5*sbgt5ceX0BrXUso(NGH0TJdJA$BWvNL+3m-pCH{GXjF8U(6WTu z@0k&prwSFPI2pxg$BxmNV$mCr7V)Yl(FP^TSd)lOlK38raNzsW8`+oaI@^fsG=?wD z$YUMfrZfIrOnCnsVBZrtn$b5R!HQ?->Popr0yT>nXpEPsj7!G7KcY*)x37@Wn^T zHGrXhZ+cI5e?&H~iAO6@Iw#SVdYwL$vE?PZn}5I&$2Rfem_I1xupCok7eRJjuw2U$ zvc1FYuH2_&XRa2GX=JZ;2Gu)7l@64FD6$E2*O*D}NK;5Y$W^vz%d><8d3j>NIrmvZ(ogVWl z7Ip*A{hpiP@*o0A+2;+f3v2)@@Rq+}i~E{!Epno~0Bs=6hO0Z6FWyl5lZbs^WooA^ zR9N1RAB?rt)okd2g5zFcG@UMuinnZsDy~>h>q-Vbr8eyvJ_PQ@d&{gOZSiC4+n7)uZT=kSG8Ie(Q%gJhfgLq zXwKN4l6)&}M)S~*^H|){ot<_IJ_tOe))tc8b%hijUYgiGU$D`GNK**lWKUOaf23=j zg_m_qSU-oIg4Wd*Al;CNLt0<^kK8X?;lr3*wdUK&=ZRne3=h7r*;#ZPEJnGrZ)Mb$g{O+ z$<$md2M4~~X1bd^+QCQ@Ufzn1#>wXS!dTt}=ld%emmwQJepS`&mwzhA*#E&?(ZWNw zLIQv@-1LO<`68afV=9pb?`$E@bX}||iCfe-g`K-+f8F`AxHv*N4HG4N_i)|msVQwH zu~4(#DT51pH2KlZqT#zGxu~pzg!5KI%y}myMGIf_sW6Hnor?O{mU7B}Nmk7b^?|NU zK%4JBK%+x_)mVJ=ACeJA8SYQX$k{pVlXvIS+@sE=2Nv3lR^EgbySBbhHNx0npR(-I zp1vpt#~Rvg@}4PCHaJzGFVlZt6({n+diTt%xH)&F4~k>pksqPj zYNk(qVJVRCwO#3VriSsl&SSZc&RXi_*LRop%Lf)lkra;-oA6^mnwF!Rh*66kp(vd5 zfhTw8PvLdoJq)e1_@M?iUr6igIqkW#y>N6XD={_?-o+gHcWvOo7vT>E^6|Ar2@Gi6t4uL8v+R!Np;#hb%s5+P&|yKex}h!$QH+ zH^PIe2TfE;M6DiOkJaEP<`ZHMSNN?r9)SZfHgtb(3LP)?eF`={UK2*yozJ2jHLRXo zPFuzEdcXq0Zm-Rq;=hi_l$w{V*1Is9$lX(8tr4NZYkcK%_e()4(P$(*+-P0Qlstfs zI9$z0_6;OHXBM=bQ9ct$D=+I?jI5BX&pr!NhYqA%RNrtkU|8{LNQ{n&X9#}KD@5Rw~E0=r95?&)Cw9fi+ z>Tp85N~4kFgJR_@!Ayo9C7vZ&#ND|-5m@|00tLfvwo;9QiY1xLxr-wwT{y|#gfgH4 zPA8FiLiT2tLw_N07p}8mE5+lQJv9y!(8ZK?ZuE}6tw+ko?#UolzgL|&+r;uT5~QMR zv{}${^u8aNh(iUA7@X@%dP4MK1vq7Hmogc#pIniY;|sA2#!$6H(q$9|#UNoMgf~ zoSkHNK=Jx@f8WoHA{L(`#Kfjcx27ie`8>mv{CR&uwrtB|ZJ^^nXyg(hXAXe_sipGp z>~m7R*LaE#LN%NfylW`83mU>Wys(NFKZ*<15Y)4{TYauP z5A?EJO%|!jSFqaJr;5i965{Qh?#ZLz7p!DxsE~VuqzFH0&(?Lscs)X-*_1RVS$Bxs z8?XrEj7Jd#0^E^mg!OHAw&TrGG zYyGnNEAg$PE9pjgMEQn*MDUvoZIadh}p=-W+anaa6bzGs@tD_vg(Lk>9iw< zE0irc?TTu2e`_p1dUmS0z*Y0p*^zC0%IV?$W!e{6E`A{BQ8R>THwiHOt&EGhRFiv=7L*zNLFR_@d6Y=a4Up-N~7vxnd$gJ7`D5rpa=3_a5 zkU>rfY?JSB$?SLGr5Wf7CKQPdxY!+SuAJ%)c#p8fZ)i2$@2hJsqhWz7Up@1j z?;#EHqafs421~~XaEXl9S5+{^V+j>7L-|t+8a{|aOTis!X+E(McaUT#52zKsaj=x# zqeo$<`=O*@l8ygvZ$92OtEqdgL#$|QHB~Hec($p%`|ZDR6-z&;LkfEOh%U0w|LDfE zA?b_wi|$&lD-g+5z`sUFRiqh6jZTmaAh%T<`mlO;juL1i!18!S9MIC4Scwu3kuh-) zrJ#%bxcZQmVKm``e?OnPFu1>X{4sS?mE%3>w@C^ltQEWLEk(A$80}$g_RGD2k#{4H z3LK$HT7CNdLA6xE^%zr=vdU5Hm^_>@V$I2Rvqeh)O8Z{_YnUnk*Ms~AcO5_S5tEtv zwK~D>+pCtnl%v?q7-9n>B{9>8c;y^K+@qP=uZ4APHDafy>2W({a25?hH5fX$A_aPJ z^CIq=SKtYIMRWP_(9RHgUx`2F3DgW8LW4honDQV7IQ)Ld@B>u`AIwBttiUq4^SrXh zNAFTmcPFs zkWA5VebafDc$USjlRB}6^m4t+lrU*>z&#(bJ-Jh0`tfQhxdB9dVURJ2kOEA^iDGd9| zy%H4sG@f+NRq~+>{L%J)iI3|#xwt~D&s6i1`#-6so-MaDv5ISgJQKldQ;`QL3-v`C zJ^YL`a(cbv;IgGQHP`#`)s)k&vtEnBo!=bO|M{-$#o-I$PSj=v9`2;I=?3o)FIz=1UR1~b z_*5J6a``Jaxy#QG)SYGA$lzdv;IE%9DaHtqQs-PO0~Yep|4~4HYj3*62ax>=)3rbK z?Gl9PXeY_}RWe_XT3&IP_px#QWp3E0pQG(rt@^HFliNk7lyr+68|Av+W4W7FE(+45 z;&AkRx`~O*RM!#KK0qwJQ18Aklma$1c`vEaO89(D`h0u#WVaj|8!w|1h8{4Fhg2O0 zz=eH9`ER%2z-N*PB$=w( zRhoxIW|T!?xZb)o$=Gcf79%=B80BRmGnD~OkF2Q+RQW$U-YHC%qj{!M)*-KUJ=E>b zFAFU5Q(QyXktwToDgOmElg@(qvY36Oq!MX#)S$_Vs4C=*MImYtEg{uA%srx&I-?{2 zHU~7r12t{Oub$P0O0zcUGl9Y#i!vqGCPqUa#H0K}f;hBJZ%#WX0@5{pwbZ90v>v;h zi+LIW73+`oM;_B;b&wkPc$^CMD9ZsxHEolyb>{gnBmsd{?JLmBxUH04ZUx=cK2raz z@O5z#e+%aipRdVV-1XMSM*Y>d3ae;S5*rDPI&cpLeu8i36)nkL3A1=DxA355Z`0jR zv_&Nr1TEnM;<4g`9ZN!^h%_67978X<7Y2$mORM3}ItcHxGSF;J=N`vk6>KMot!U)V z-9)k3HtGT?&hof?WwLO1lxXbAK^)0r@hYc&@_&L^C~xn8_+QtDW{o}1MI3a|?Ci>G zkXF*~b8p&cAlsz>49c_E2nz)oAjLWEH-73363{xr^E~NVs^=xX{086e ztKF|SMfS6h2vW4mYtov@5bIMaBAt93V>>w_Gx$4(Oykei`dvv>3Atvm;zk}XYj(Q|UrE4Vs(WvaZsTCBYu<5cqR4>tU*xjk;14jI$k0-}Jtqbx=aI7PTF97OvX-+kQSZ9MjVG*A&8YI&9KdAGFU+|2> z##eR#Wj3K}O=QmV>L{qBoU*Te4ZkL7&`&@A4j;hgCHzvr!l^)iuG_B`0=4fcB z|6O0M{f$5KFYZ*SP=GtX;-T-beBGr~fYQ84HUzkOcKVHfR-yHKq56p0mWi~Ll@*sn zy0J|^_7z&KA@u3EsC|K7S142D?KX%~HHw<~?vtoujEQ?CWs=2*2~1Jc=zFMx1MDnI z`9J|#RMuk<_jZ>amYfA%E)&A2t*@E%eudSQob9U{jX9U^f8izJBEzN`n?b2xrj)pRB9IU;TdHiCQ=ql z9WtwHJ+0sAT2mqVxwhN&Wiht>Q-79*B#3>n`%yxwkTY3PPOp0Wj6XwNTmBpU{6rt8 zio&R>Xgtp0;UKtK8C&m>BXW+)4kJwqG+LNmuJ%*@oBS+-*$uVSvdF7}(ekD>qWjj` z?V*OL6NTy9PCeG@57A3eQUo1_fRnGdq{(q=JT0P-tamt9E$jW`@N7W6o+VSa zz0F|m=BN5mnsN0%fBI)|GNk*R0@~vaLh-aUE;Rhtf8QTgboeElFBAx=8zbmfQ<7T{ zxjCvVP(HU#u#fy4UkvSy1Jte;BDB*r$zwOvV?*(47`Wko@lSrlGEP! zvrF`o{s19bC%Jsa^&|hbzVlJlZK~@7|CurjMpS?EE{~ene4{nyZ=mM`NAY>GHCsb) ze{fhscDH0urrEtA4*J^PqdtZo;GctvLhdTsP(M^qoxhl?^vhAIt?UC?00}2%e?!I* z`0R2M<=H?IA-SmMpx`H+mj9JYC2u7hIBpjCp}lvy8GdUnj`ODEpySyuUMox!F5zj+ zd7pXTB1s$mTcdTWr5Al=8`8UpN*qd6MezfA-A6g;r3Ot$ z_0eDyu7;?K2j!e!feD7EXOq_X(dV=9`dM}Lxh70DGnuXTmXUP$ULwRmk)YIA!ehrX z&ji|Z$H4)cAA(imLn;}0@)w(c!&0if!smvo`D|JnBgV=sa<2Cfsp08O%zPbJn>OAa zo7G@Y%TGVDA8e}oihQq4y+)y}#x9}Dd2?y+5>HGaDrYIbDQ$#@&1lR?s@t8CtLMW^ zXY||D`&6FCJkvOE)hqDke(T#tk+j@!0MSR8`e}8;n=#UVdeGYchaR-;O-n#DnK@I& z{`ruRsDusvS zb0tO=;#8C?Wp}6%RCVWt5{MYQjMBUmjjoKwS~w6*so4*YIDFI98skSn2ilSdDFKP9bIZWyELrhD*H z3=Ph+UoZG3wvfR)vkZiw-*@O}DT0Y$02fI?D(roaL8gh?UCmu0N=ltDjBXL>wHrad|#=O-L7Ka2Bi zVAIc#m>rXs*sbVq`7Jc*y7mAOMn+dzE*y&<;kYx&*)7(2Cv0z#HlA@?|2rB%{5Kvi z>7#a+MZrRs(*EYCkVo-{j!_l4O4b`u-uVDyqY}pR=p0JXuw%VK0Le@$Z>WX3hz%$A zWZ8MPlF7$7CDR?px#vhZGn8Y^5SrQ!O=G6gG5j_xzJ2ZIbs(t5PSFdK`EaiOYCmz* zedB^{Q1Ni7{%Wd`cmIJxMBYe0gfM6^8J{UnUJ-PF4KC(zC8=KRJ^t0$b@5Gj4q9G; zLVTW~Bvd20@a8!`vA*+PT+K z3_f(`f6M~lUm&c8HZu5o&?9OqC9*mDwzdTV^+p#;Xs74Kbhj`*Eh6*?<6WCOn{S3{ zg*M)FZOM0#@pBaRn{OzZbA0n(AON+0U7Ek}2L9#{Id zY99q|1dACzA}6iW@^MQj%k7p2LlZ|mk@a0qt>)cM)w7&i$0yQKRc zIP`z?llGtn%~;eejW9k??T!)>+WnwPm6S|RS7X0Gv8|3FebCe0*LAuh^00mx zaApgu4(eQrk1mU_X#c)k23;P?F=-t6P+@tC)E2R$5^5LA8@Inu$eye>A1ylPlvL6L%2eLSC0mXD3i%>=(@vT9Ljw8=7bB8;P?4B5*bP>*trP zB>W9dlK-#Nt=w`b%d+O)k3Um@+BRR~&5_8=CFX-fVnqt)#v`J|)pAQ2+YwuAoBwCs z?_RKm*)4MEF2>_o6WdY1;rSU%4w!>Gi^Gk2Ci!|;HxtbW=GQZ$#+9a_2yia`FQ8qy z`_#3S3yW#`Ej2Q(Z#gwqpZve`?O{pet560HJbHjMmN@nl1@79cn#KpvqW$)|x5SGY z{)`KM7}p{oQjYLW97~{|IsKEZ968o%`K50hX!w&PJmaMB28bacy*@x z^P6%XfbpLQ^MM5$OlmT=6?bJHYoc@&B&epc+Zq7Ry@lGZ-ksQghfqmeWs+P%XFdCNeVHRj@m93{RZO1Nm zt*O18bL>Z>Ui+MJxq{6}U^K@=(zsW~G92%9D(CP1`O@w`0#CtHM3k22v8ckQn&?is*3h-I> zKe)M}2rw=4leLlc{Bt^FzKuin)Cuume?1T+EIPD8uM*|wu;(c7kZl{UuCAgoy3gyE zsOhGx=@uJNu7lrZT6j261+9SRoaB~TRi2YBY+Y2n;+usQKPJnRVKeIO*(C-3UEALD zXD-)ejfD{K^QQF!@QJUO4X^$c!8L@mu02O^HU6AgKCagP-hytUd3X%jI?EMqASlzg zv;%KMXdp=EPf~igEm+J*{Hq>4{n(21{OI0Yft_V%o*&9IUo7#-9U#JD*zwQQWVgJg zqz#IU@reUyBpk@5f_tMpoF?Vi7GbnH9lHaVA)pW`~E2 z$w4Mq!qHYe=oF*_mrea#m-i|{YT0kdZ&+ru%QT#p%*OTGC_`@8rCsj(g_nYaZ1ucd z#EHuqHhuXDe0UyM(5B+*B!6uSlcR9*K!z%&f1HnC9e>TXyMiSrCeJRnQA?|OL8;8k z7hE0cH}?EaGYY(e(qhwZzh9Pt>6F3@N7>o%`X6&5xu&tXFG%O@3#+7u600TkQ?hkm zf&X1Ymb3rTkWv+B;?NVd+xWZtUSgF+O4>-L#l4m9QZ6S5e@%Jh``L?voSxaB9g0IpPv_+2iu!CPt()Het+pLzOk)WP)MkuvU-6R}Ld|<=Xu&%0iHUS3 zWu?abXEoKXi!IhJzSzOH$UXv=U!W95>(?Rhq$};DMbH0xb=`AD5mqeDL2oA{STT0V zgIahuOr!6?8ajxHC@CJE;l*|hjwCm^-pFK9?sKp5QAc%r6i!>7{p9|C_mlpF0Xt|o zFR)K>;hR7-)Y3vskVo? z71cNqav$#Z?VUjfw^|k_t`3bQ1n7gybBGJKgU!Hha^vs4#RBN_VeT}(XzpFWH zoO95Ut{6;&*AwdA43_zikVY8Dsk++WwcGq26}j`;x}M?&7m+fiEll7F>W?{*9%aik z^9nQVJ!4m|``+y8P<71^y&&tH5yKl{pyvJ@qhs>%rlvUJC1SAT;Oo^&;_PsXt4pHr zZ<^fP?Wwmy4k}IC2bbx`)2RcwiuhzRSV2{lhm*G^qwKcm<*)Zef|>6(OvKxP!2!}y zp`wo&kCJErp5~)XUy+?UY5EIeaxi_N027@WRPfJG1dZt3WfO2mlxe8nX2!v_IFm)2 zfnyA|fa_q>7ZYc<{mupO8;d=vK}cS42oCX>XN|<9@&3B9PXL2P^=Y|e|1x+kGMneB zx4EDjx6r_aWuTNd+l(46#20r-RRAQ;mEQlg1|T24=qAQ0c6AzZl{bOOn>jz0kN49y zw1@=|Fp_g{nzCV=$sbP(^FYmwre7V3OuSHjgUyp z*j77QPW&v%IKVEWlK2I%kg88o8G9}PILJqN)+?R^D{_t(Shd71QRoA)gr_V&M4f9< zk9Nx4JVnlC_Whl-mG>wRKNVF6h@jI2} zfQDE0_DJRn4;oa6La^H&hX<|SA~7Cbib!Qy#d@Q?Km2l1aGyI;oFdOw-AP-U8P3P+ z+-AHcGA%}H!ck<3G2a7uUS)A7-6m6 zjr~Y6j6IF9M54eyiOMp2VlF*=m*bNTweL7o&Pf44encdES2NEY9Pq8hi(SYy*LlOz z?+595hcZH>XBr>8H)MpdQ%$?#SoPwX&mJ0CYG>={C|{$z1Fp6{pcck$1~V)vQl?d{ zv$?9MAw^JOqa~|e&b|t=Zx)Iy7U}vk~7#(T0y>0 z=gM5p(+Mt{XmG&O+Q1jg=V|m+dv9&>m)7(-YV}}8#|rk~=Z}922N5pWzhF`F4K;6N z`o3@zT*Ws%sHlCTz>{vN5zq8{7UgN0j7-;U1I5e{fcoQv;>0k>QRGMODYefQWwK>| zL%93kPE~QDm^v@#Vhft0xa=F`rDk~I9Db}3>|S-z7q$gISM#heK-Z!q=gaAgf&2wg z;@^r;8DWDtf8u{@@;5M)-9z99!m_}>1 z{iJ&=xZ5!;L(n;j!ro`J9O8@7N<$?g6NGDNK#+^hQ2|yB>WDzmcb4erq3M!Kp%u;5 zCOY2yI7b-_3J`n7rc}ik$1@%=s?fb4tQpM@(n}zVU*jc5Ju{XB9_NR}wzl0E9+Sa5 zT@L-W9=7vQg)z)}xcbdYatpYpOo)`wn0?kOr)^rsmkg1jG2zoe2{zty z${v`HKlplG@uoAmekjcKt!e2cn?33-99j(Zwm->t!6^)W%5QfXX5h-0>T)c9dFY@t$KGLl(4VIv2vn!bkheSBO z{lv4fq1Po4de!d&R2xLXl{60keqcwr*u=Z`PqA@khx^Z!rUIun+N@kEch+t;1hs{; z{I?~;q|@My21Zy4OVu+Tm5VSd&%)qVg?dlEx-o=|Dwn`#aM=084ZRHDheF%bVR1pT&WN-T1CYUUz|%% zb9fHz$4>iiV!FbvDycRhJa-hndw!yTAMmm1uA_ z>h5@mpYyXcKu6w{=PtX00YX=z455BP8E^L%(Wy(obPuP&wDIT-RyhS@_vKGGK$&ImuLv^#%=2z*w+aKcMtCF?(R^)smy=Loc~#8oqfHZaZ}e-V^kmg?eEtbW2NEa zLf7qM2e=%{spLjPzaa>vBI3gHsVjKu1|%8+&V_unqVGBEVe0&^>zk3zE9fzUQFk@Z zkhzX*lgwN_@Bn(EIdA*rVyprOrvz#|JQau0qZ0XCO_rcXPj%@fEt(ZO$T)+G%j7i2 znadMkhWM2wea<4SP0CuzoMp*|pMWrO+Up*xe}YW!=oQ z0gu^Ek0T7N)83j{5+uhM6mNt}hvq8h-0Vd9b)HgY&nlTj-YPHomit77LJJWa@+*&D znC5}PIKd%xC3AdQEl!rB&Hkf~^{dSe7RAHnVqK|1*-(_4F^-;Z{qbZtG4Op2CDlap z-B!hIPyUPQXOb=e75@;g7g<9Ey&RAE@*mZ!!Blo2Vej)}!&ww3D(N2`Nmr|0Ud>fU zfu-uNH!N*&hB*i?QjsEOG5X$ryE-W$Xc0hWaj}o~m#;6ui80f5*C+VgOU{+{N`2uX z*ZSRlxL2}qq@)(S;4x}tROY8LtU%Mcj5d#q02OWlE3LK`#~fm@hO9(CKbFIAG_7=k zGw+7!IQBVK5``pc-driQ?zip6WM>tNHPuKwmysZJPxp;2$7d@at4)W{6waJr=b)VY z2_2d51bAEWo<(=phh51;+oLf$KcNVLO40TYdj*yN3Bu-!^tF)mjeK;opp(r^FSSZp zT@3}T;+?r$;{g4(9)A}i-_PLmfn$i4nO*ji_k7cKM3^Bt+YtJcr6-3WQ&z0=-D8pO z84w-&TrWgn%Kqd(&XdMa7a|grad9;-X%BO`|2SJ45Z(Zeb6&eC7d1pSv{Zr8yhWjw z<_ODq*fllrc3Ht;Nbzn47dIn$BUd!hED3Ot6rQULDO1u#yszd>FQ07>zOO-BCl=Zx zoyBK#b%!WSOmSYH7u+Wq)j1q0Bv!P$_Du6Ti2}*D9oqWZ^Onk&-pVRIii?ZC071Ie znUThkPFd9@#VTKYUYc+6(@Pob>guYahcC+WZzh+3BE!2t&VT&NkLY_F!UIv3z`jNk z6)XT!I0Al3EFh4_N`if6c8D6zE+v8VtQ`Uz-}v-!YvHtr%)X9jh$RyzhrrC`PZXRm z?wPBZWaG_FI1FkeZ2>{glE7i*F+v-~iw~27w8lEfti1zkL-FdW5>;};qJC0A2b$$| z8X;RvB!Nsv@h_VVVAeiqxqh3rf7V(H&`f2Y*H0&aYFE06Sbz5_=MQUwNKZyT8a$9X zdZAyeB@uRehKFqwY34Q$fC%pkEz(|Hh@p%psge$+WSTqX1oiFUgJT`_q$tG8Wyif5 z9&Ww67zb`zhO&u2m>jGN87t_P^Ov%kC5Z1X17i4A(D^y>MY1O;1bT)gpA(aMi`Fyb%H$7u&GnkdboI*8wv?$p7DN#n|`&I@H3 zv*SusI8|b?lYjy;tdfA-O)?kNg|YF*9>%ntX7;@W!?H}2$B`OdUanOx#|Jb0UP>aj z?lLZE^T%Y3&(7WdwGRDRk#zVV`tcs%nH%UridF(2l6yoDYGI*qHo5pL^2YHdp@wTN z9T8A58sV<*TsLQt-)whRmNKx^2C-qoL((!J_-VDCxaVZEQpxD93^g5 zsM6B&K^}VKQY?fap3FWcwjI;s|E4VWI(#wYC4pMFAq~#$RuUB43C9>3>}AaOQ51an zV>f@G29K!sV=i%(bqgO{kNub-ewm3p&^y2_JFCi@aaQv^S%QW0`w&f>eJ)~bMu3Q&a6>Gs{H^I&}r@L88}d%-6|i_gK0D#{Angq~RTSv_Hym!-@QrJF?eM-`2z z-&|DC_BT| zgyR4s3#CY1;F;Fw`yac;&UtCMZtWfFG+`)eVv&jMLT}*9*r7HmRp53%&o(Lf935Ht?bARdU-P0E5m{BG~4W&`PMCl zMv(Wb*c>GiHtPs=%lB-n1Ki(<=#&>1kwC$ao#c-o)0A$mj z5j-K>z&f)meK@?A`~)pclrb4r^tzo5mjVz~6;|?`-`XL(7~g}!PUFV@NXsl{RhRk9 z3vIiog5@yy*#0#+%HYCDujs`C>aNDoS2g2UVRU$L7jTsFoglLwDLj4CjrDe3G{bM* z;Q*>9t(P%V&?KAT$LqFjte>SS9&HUH>s$2x1SN$A`4&`GlK3RVW1pG5E|hkoHufsxfevF0e^^f>CHyvd>hR|V`y7mHSTKnluu z1=F%jASqhI$7A#383dbK7_LCJQ*^@$@8IE3)sBPUntt={y(s zDY5fxS=Aj$i5tgXbjAF2LnaztFpuvOaw+=73xgt&X)gq|0-Blv>dt`ZqCX9sU#AaL z#)?ukw-Z(-2fi6w6ums%-*{sLoMj1|R`B77VnEqLVYCzSDOd9EN_ln0B(HlTaSgl@ z!539^CNfW96jlVq1^SH4QYSaqPs7Fo>G(B~f_BF6aJCv;1fetVBCcet$Df7H142f} zO%NNkBi{e*@&Wk0Ul9!BUOhCO^N+`la9{k!JN@T`i-{Ei zZIjQCul$%^KJ;3sPDGhF7o;7yCoDwD3_rlFz;bPs6}_WrYr)YM>PanCt2|R9&DE|s z9y>D*bhZDtZD`nE;+-8hQ_+;bBVbM^`sm2a48BSif+$cwugi?7G2Jh+xVyAEaxKNn zFGHxLb@}yeY%O8QJ|?qdp20m)OAWpy^XN;25Y<*+&XwS>S9<-?g8phH#fI4oGtFhp ze4tJqQaKS_I_qPs@0>@n+XrkDcH}|eoRyBt+j{iSj4F0u!22sJ=Aba)vKVpZeWL#6JCHTAjxW+y0PY2I>q-}s_baTI56_7zU9NF8c`L8{MVV0 zIC4>IjI@!aIyi1d!DpgY^WMZSn3}RT^Wi^E-CTbW z>MACX9G|m~*s3317v#R(MmzQGPpV=;Ib3hfh$0lECpKMel~_4d@KU8zYD`;`tyFa; zTjrk8w*hO})+FzM13zDrT6We|2HR&R`7aN1Pin-pkLLZ8xE@Ewg~b#q!q4^T*2nvU zPL|Y|XeXTs0fYCNe&%R4^+g?(lr;D(vzISP$^39eVeWxsu+Nr|T2jSu3uH%uNu9}7 z9};LF(`cEbJ1mR6Qs}GtnBJZQ%mZQB>JKVBiKD+OS*)wj$#dK`WbjgX=s;08xAJMls{)f6zeOYVFsM(=ekVan{w{S+k) zBwwT?(H*H5Tk~J9tJ}AV%{euOcaH}2I_A2HwrUtz4khelR(o_SBJaSX``2A{pBRiS zrS{33-8H#i zfCO&MoBI=Pxc~~VTV?`i@3e|h|sE%bb=CctTD=^I~NmPyZe1ebyZjQ8)U!n zCR-%D>AG69Vwb=zRAR5N`>^^u}WElpLZj_e`ANaPt1>FQoDbtGdRe667keYcR0cFGK_o{ zpX}HY?I{qq5gKXWqT-`UA?i{+1mK_Y#d97k=WqX#PG6|}92efce%ofDEu!n`2Q(u8 z9;wYae9M^_G`3704rrhdEM9Xc;oUTtZXo+18^J$;?!{6sxj-ZsmE=YQ7r z`28Y1-xm!Y$Hql%-vtlxLBvF@8d-!_7TCpyVK#36eKh0G*H*)IdGa1=e zuR`WvF#?RFUQN_-w-?TAepD`)mQBa?KYqz%d&zC7rq`j-_vca%h!uCnfZijbCeQ@_ z%c@KEj3TNV!38`Ug}U8|{sQk<)?zx5jHHlZdD1P#924kWc==%1x)0S}K6N{swDYR$ ze7rwMXp_O_5d^>1)5h&G?ipk15-KVyF;s*y$zi27^^Mmx=d{~1=kZbvP%y%^YiCVbHQ|yZA+J>EF`Umo!#S&97xCV zp_0|B#ig1cdul7h&b_MN#20V!3R9dFj3b9 zoSVQY;**XKH+DY@orTAuQ{5frb}d9XV45|pphkYn)uQ8$1efRw@)R@70{M73uJv1~ zu!sTvCsDkhYb3ZkDk;e!$@rD#*HW8O=OpCwRb+o~x01ibU18wXd>mCy!R*bUZMbot&I~)tFm6w!7z}|8Va05p!UbYjFCb>^h`4V)F6i{`2wY+MmVDqQjPZ)Cfgw_!6Mz@_=4>e zF;877s)P0%t?KU!tHqCLdCfs_5o5?8%{R#~rnlbR-wYN}vR#v;RP>^r!jmr~UGrg& zJT0q>oFvP#Wgn*+xaWuwhiWDBYuyIwd1C}76yyC^s9NVE{#)O-EhIjg$z%+bBTkTaZ z>y?c&1yadP8_5j4KbLKdvyhY30K0VbpLu)4b-(J7FKYx&U)4hn0_bRHYMsh%m-zw@ z#KyBu-CeP)EZ4q!L4LKze^8j@20}atqEK_hM*mVv^)WNDhATO=d`_Kh_OCdWQqaQg zbnl-VD%c5mIP1GA2`MQAwy-lZR^QwEZI&setR%(1SzR9UXSY2Z3`k~owWp^TvhU!S zLI;9Xx}-QJNzw)4I;d75^qn6bS!lVun-*}~odu>;m8L1FClR1RR{P%L7_yXcU>-<` z^oPXv6tqm6X2i>XVj2BqEvc8I{sf^S{9sE7q2Ma5ILmRW4l0&{Skn1I{T3+rKwg`U z5`5e*-hr>u4&)etuzec2M#)O;FC>RO(wl-4KI3~UE?-&lTPk8lFkw8Rt3WesWt;pE zvX0*b3EBH`kIW3?KXFP+qB^hpNP@H-#U`KrbS}f?*0g63O|t|2I1JvMh)K19CghWV ziyq=MX9t@;pum>Jcu{Gbe|0Xyc*OR$u0~a)U)qYZ@$d+WK;{%?*wy-|`!ZLbNmM>@ zgo^F{c)-n;LaY27Y@zCl{w%jzLgH7eHkHQw9BO3GL=eHYe#X=cA4GkjnaXVuS888T; z-@{ZMI@09+#MLUuZF0hyhS6!)nObd_9}XeYe{ll?&|=r!#U%~E)5}F$Bg8KW(HJdY zDTgEKp|%)GDiWsQNp^;9RjQCOgM<6R;Wq(+bfX0=6uq*V+G?FGG$@uQ)}mLT_u*O> zY0Zu2CF^mHCHA|&7y*Vdq?u8upx7N_(seT3$*FX%*_voz8=sFXqTF0Q9UTxVI$sfIROM3YD?HIn}iBGI__|xXexDu&}f#+HbXha~b z7WOo^bizbqzrpkC1%DzTUyck*U##1-*&vu6nK)0<3@|3W$AZPN62F7-lC9)TOMvZg z8nOiqFx#DnrK22h!sAMIcb>JKNwmwK&zQr9CVZWi%{EqTVyU!?i{jGd?lF~et~q@t zu`QuUoX9%+b&hhztwcubanr%BSQK4`%dDe{X8aGDavxRJWh-7&x^sYj@nEa4uo$_H zTlM?Ud_7FMt_^_L3P30P!=-SCqVb>FEG=Et(D8RbU#)3}7LLph@&PVS@vTL=`W`CK z11=Tb8y_Ad>WI&#xw(3b%y2Q7o6;nVjyDWE0_A?D(o(ogz+jl92U-eSS#X@ z{-C3WW|-H_>CldSKQYyTQ{KKLCfiFobneZtI~Xj}#SB|m5R{fh*SR%fT5YUbo4o^n zLl&k{L^y@OSIBun(6rweZMIND+XVEcg^O@jy%F1{J99XVL~%; ziol)ABD6Nz>rgipIJ!{|jNp5DQv+Z<>iB$#1`arpdK+j8>ppSI=hTAQOx zuuZ4Pn~iRb50~o|1CkSjOf;$z zlrmHOOQ(YejfkmdU*~((RZ*U+%UKZH?9b#VaAx3Ib?%b^o)AWX-d!Ph7TUb zm2`QgR5EnGhZIRT)IBGDX6kPG{xnrV6Mb-W{mlEQa~px~$5#9v6V9qOlIa0)PRXyV z=uQi*qGE#UBl*mi9p4Wk2#VV!<;5k=cZlw8n=|Z|g(DfK$l~Mm28tRAS(HJQYkq47 zT=;Vs{jr>vA7f;5JgTHO!~$hA7cF4KPeygQO6IzY;2D`g5Dh}{;8FwO=X{^t^;ab_N z2^2;l3A)@-SBe<4Ddg$e7{)Jwt*0nBxI?KZcqd06w?`|$(C;89QO4sG&SiMK^)(B9 zc~xEI^SA`P!W_Yp?ajFOqv;|8HTB%nvX}7l;`E`e%M^-jfywlgJpG+ASqL8cIbT0N zxLT|fS*rh~!llJTz{QK-ZR;sbH+OB2wa7N|Q{^KnqF-X5e8t0!XI!RMn$(YyS26nu z<%^Det&AfaD)`+tqI*4K3Z+mhtwvfw(lm`HrbH6jbzBkGV*=7-B_+%>YNjxt5fvsj zSFeT@Sgr1cIjr2KJoZ*gMF2>hPs4aHzM-}%;q(nE+#^q;ILPJe(;|7JY@g6VAETq- zc7-a&$Mbww5rcvWl^;Xat0jsjT0A32%c!n$w5vW`VJO`W5k7Z>_8mW4^1zK_im@a% zm2_kukrl@!k@*$Za~wQ5XnGuD74rb7&<+L;ICVij>*JmZ;Gz(?}vAAxA} zz4im9ARbC1`Js+!dOq1Sa%fEG-8O^KLSTd$6+`h&nB)7&v!QxCdry(dvV4xWoa(Eq zg(->`At^4>rRt?Uas>vi@WSlDgonabiKKzK`X{)wyo>_GS(QrmDqtXj1AR6qgsJ*Ro zG;=SKokAWH3+QzI^}*ajqwdx&!<=h=ZjwU{ft`3dJ$PaiHIB(JZkx5W{^HX(w?!CD^ve#AjbkwN?eCpadZnjx+QfCA6?X zXj}p|O|FB9-Eg!cr%hXGvc%Q{9a{Z}iW73aV7L1q@ks^H<}k!H zM7`TmGVhaLB?nzZe>>k%vp)O{@JzPYg&J zCjMr@0eyH=!+4w0mq=bFe#E0WGx6555j+!GmCX^Bk7tuG+6fkL{nh0nvd~$Uesjo> z*%<+9&_jvw`p=R=V$~z^IW(hWI(RxIlqniyF<%g4E#9K#G$=M|9w}=e<6da1TNEZtOcg*t*jP1&(ylec;xc7S`tVbb=)RwJ1zm1!??zjsG|Gs})KsthD$!g}{^ zP}`W^_H-?%(jwsrjTyoBiB^+kSyhtM&kNRfg0iAqB|L?5n9&gi@;)AmdWtlKEW6JC zAYJ2UK$nP{uR1-BgWP~Yzh0a}Ce$t^=J z?MAvC;rK-0kF3^F+G!VTzYj;6Bh2ATvgsLz{_5ly(<{|EWQ6u#;+Zj-9ZSIxh0!G? zG+UQ0GhU9~b1;ZKK?2!?#BirOX01Cq&!!uS&~Im60>)PgDzY%Pe(|EUL`GiDb;|+3 z+ywRD#8$SC`mQ!FCWobindnKEWApS%6Yq65BzxpzT$;pepN?BEL)&xr;tqPP*|G4) z0Hf#=*Iu}D#$%G-<5=)2a;_5zPn-tKjo!HV^eXvys5}f)UQJ`pirJo`7s{8v-LrKK z1`@4P%&DJv#0b?3C)wQ1BjS;Nf}crz;qdahq?w+>JYM&@DyQ3S_I!l%al~GWfh<{_ z%tb4Ptr_BTGjDg>M7fJgxa_8{C=#ULbI8 zp8Z`p``2flD)`zwp9-z~z2Mmlh_Qd14FAhl$}$2UeG&1Yv=eamjpgNkp4AhlaH;+v zCDuEfiG!cV==Xai{0#<@_7+Br0{tTCB}^zS|1sLYPWSJWA0NVt5JDtz5IrLK=mA*k zwXqGN?=y`_kWT0_pB(5pv2cqb(_+5JH60GQ#Me_ulIMDmKWOn9zdcuXZYQ=HY2|T~ zVb%&DcJm&wzaOwQZYU_4YhtGk3%w|0>D228rfFzyhf4i6>GGFjrgxR&+rOyYf=#QTc6*SjE{ppDZ@0m3vD(8RR!_c=@C)0ThU#r~RZYWFz8ffBL+vZ` z$6UT~D9uP5?mN!HV|S~DKGGW7>qM>~CrYzso9k#J&oBB)pw;eAv6CrH3{9(?uH|Dq zSi}oR2|l2co_Q#~-<%Hit5}*>*JfO+NNAt#%bH)L#qWq@2VEWW;~%F$+^$WWUf*38 zvdz2;HTS#WipT~S_?BxAmpQw~Fgo~FD(!muP!rn`dbB--Hz-#fxm)6dcm@HcMEDWy z-vOKx1WvTOtg5V;FK2Q%cXeQ$P3(6Qge@473 zljK@MItQghSGirRG$z0Afr7h?xSe65b;@5I$5HpQb=fP*^_!!2EA+ejnL593?J(wF z8OKZGUeu!f2`sbawcF^J+rnBHzpXCivbqfL7*jZ#3&AQH1!EiL1RcD-aI*r=S6wy> z(^}YF+2-cB{-eg&OYL*J`43K~H7tl)rs7@=P!Cf0re|u(8N2m~*xQ*EJ$fX#SnumW zxGQIT^Ed-}Tx23~z^Jgkj_CAs&S=S8BZ1Q8-AiE8s)?2VB+(Kn0rz&~bZ{Squ=$oK zH?HJBWRkTb>eg7~Mu`On{^>t}7tak(tm~q}IO{#o<>dtQvSF+}_V{Xrb5e7}!ZG3#*!#2ZBT-tT zS;%rt1i~G=1Y!KOg)5txwz(i6m&v~4pATpObLUiNg$QRd0PB1`(}jwYj(vQrDi=Ke zBC5P%^7T)nAMu-zxIp)u{eKbCB$eaE;eBRh)p&?(Q7rFV;*42H2g{?NpW$e)PP0U>z%D4CY(()17s4WJul_grj$(Tw>4Bur+JoEY2wPnaC$N7d;?lyC?ry`7*NNj;T^-bBQX)^8(sG2>V6sWF z*4A7A832FISj>Z66$L`0@bl2?aeLe$dODidhBZImP85&ln3XkuPA+#i3;Bnr(5VEe z*1v(3?Fn-nr=Rf1L}_ObB|`Xh$9A|NyIe&@J!-m^)b^eL-0m_vP+?p%9N75iMA>4K z<8S6LNemLJ^*F;@?e>RgJ>6W}jybs^X`II(`j^p0M*h~6<|VoI4#5*kP-Xt`K8>>YG(`JX)@&JK`LWy{ ze?Ba0?WWJFG}3`)|Pju)3Z2#Q4v#C2|Vnxo@4J9dfxeD3xb z!Oz`1>g1$MgEjuY>usOS{&&4C3(|q}dY>lwP?Y9AuNa(y7f|HSI5acpZ6iOOXYzrr z^+h*HLO*==o4GTV_>i2R;Q2omn%ACtZA z&j~AQXX`ps3P}+OkfJY)S>)oN3ZmrqUcAd~{|SEssJYHuBsvqb7OFHq7GHSpGIyeQ zzqU*(f0~c_cl`}Z+$ydMZrlE#t7s?Xy!3o;ZIH5z=#|`v-=0yA_#x|jX0X0%^<&sy{78gUI59OX2`y=r$C$Bgxa^HqcU$)$@-ReePvF-L`zOHpB)ah@I zl&0edA0>XaKi#LfsH_UiW>bw6+$JOfJ30*pv7H@p(`dikFIcZ{mUdw4Vq6)*|z0SL|ncpe5E{iy(X0gDG>Bo z)cslJtXxLsC@h{L3|N2_)UxxB;RzE-$Pw#}9>#c*;PVhGf>+{ux&DBoKiEe^m+yDG zU#uI?TT+!gZ{TB-J1dk!eCBGjUBrW1VHZ`4ZH-KV&C z2Fwv>0b*E(AVd&qhUis);Wa~Ek$noWYh1Mcy-XNA4{TrEj^8Lu+n^b!!xx9JyDK!n z&C9m_R^hgHJD4B6$pD*T`01*2=gXBQqKxQ}?3RS?A@4zWxqQ6exsXgK5D_Uq2l0QF z-|AoT3>iy@U;;+dj-6gQ7ok=mJ2pRiXG`rzi`1TX0_$ZixEy{I-)@n|=PXwnJ^psp zMrfS9P~>|XeX~D@_aNkUPFdq8VA4g2@nCe+zs{QpS2d;9V*@oT*LsV#sr*WC$v>}o z#32}a`TJal4Lk@39@~1O#4T=?x9Q(z0~xn&PtXplZfpvhi*a!k&%{*JRc^iKa^w+t zXXYq*_Fl4Vo(Mr&611a>Km39m7=!pBGR-ELhWu@B?cX@rC!pL+;=g3_D`L_@TP3VU zC6X4V=i)u&3cNHAm*lY~qpm`>dv%;$D0nkjjB*yB>s)DzU-g4|Ez*B;qD1HP=pR6P z9p#6xS%K1ZYYK%tjMtv*La(`~^+GT~TActBq$9}2r;h}JsZJr)s-3;NeI?pAz3<8m z&st)9t66Wz+^`U%OOn>{@9B+TLH`hq!e8V)+lxo>Ak-vXujM#p~RytP0Wx+$X2h7ep zsdRHe6nJrW`{w*gp!Dln_yAtxsZVxsIWdfg*{dfDIZdwmO*@gzbTI>5S^|Fc&U37) z*;J?6k(PdHySD&}6mI{Uy8a3eX@kUcro+zhC5xhUbE&SNyrQ3L+;3Jlq9Pb`nr4HN zWSan>E9X{+ekkkFyXn%fWOg8(kGBYkqh`~Cnmny?>X=G6H?V(@;@p=>sy$a*fMNV|npN3w9zTbns@a4&uGHj$!R{@_$tM zC4GhEu1fLjWK3$%FI*6Ye&nf{wu5m*mVx@G2FJDTXhr-jqY>o(4)(Rf`jX#c9-E@70v}0Q;f|tWELF8ElHHnvfi!foTlu` z1wUYVX|t8|IwM*uc{7{L>}vf*4dy)Y636XP=YME+Uu)R3gRy9@b?CRz?YW)K$8*A`JGmU%DJ%$-#iDI-Pyh|G&iK#Y zs+ycG6vu}@DaBNloLw(d^eWHC)FsR-L1{YZB_Lw%>bQN8tV+rp!(D82a5^&Js>K}W zy`ziAZ+y+PH@r`mBcrWetPe7{PIww0e)6r@sZFWC7925vx_q^Mb0KwVTt1jtce~5G z@ENl@YYgi_>@2_+oQ%+ye1YOvt6HzVn5OO7t&@t@6~G@9?z^*`MGb(}2P=IR^vN(( z(jncwq2e&g(fy;^M<*FA4OZqF(RR+J_NZ?^w*DsXDVRme^m|en^=8qJRtvadt_^%x z>#RaEA$n-8^Ayxda4HZsw zHbSTJ$W>ji2#&-qEd}Mui}WuL*4NjwZME6ck4!J!{PsqU9BY-S96t1FS5u1e&6L9l z31SEYi=r8ahC$`TW&>2J{1%0NIDT%90R#${dkVvt7NwNF6r;^~49^IMi`k)4Dh!Fk zhSK|Y@vjN=@p5z5;ZL1zi(&FW-XHjBC>A8~8A}X8H~p(vIo)zFq!=)Su?z-4h`8ZgCDgpf4O8^PQRn*UNpy_lZGX9 zL9O(FKfbFu(h+jjAYW#Kgc1)%P4A~m&D7=mGh;}X0#fKy?6g;0?FqCW@ETcf zJX#*;RZgHxtI8?tRK=y#euVbO7gL`S=la!c``-MEWzjPfAZe=<)R-zUF(Tz3V|MnVZKR zAzi+@B|6jk(N4&GApVQX(qf&Gz6-~a=TR>IXd!BO`?V?94V_;+c>b}-EXi0s_;oLH z1owDt7pyTRcyTN?bhM;PY7BPSBQK345b01PNY7n;6Z=!noI9{j@3JYdhzB?K^18C^ zcnNxSk+iJeN=>%h+O5Re5>W(&S`2 z?S4?IwN+V@y#cp7j(*v(5=XMbd@<0mRO*M^;lj0kAI|+O0W+o%(T*sc-(8P+*U+j^ z>V)QX!pEhqy&PjFt&u&^k;Ix=bw$9_Wj16hVdh7?&3c5Aj&DijtMQx@NUp1lt9Qwe zp=UVs)1+X}G86@`YKb39BeX*59L3sJ<>UJcQxZbldkoGXYpbQZ+?{N=^~16S7%dLr z|J5(FagV-5e&RDXM<7UKHa08s`Z@1?WH@RUAk~yg2fRI@@#t#VFp2r9YwIDmGckdclGY1>1EV~#tl zTf93a?9LeD`#BohqOp~b|Lj&H%KL+}#Ubk~eoHD$yXL=*Ns2GH(bL`QW{Zu6P6@%f zTB7;BV5rETB6!4kWnr_Qp_~Yfj8TX?tJEBSyqIxPTqS?N5tsx=_K~kddF7D4cMuyd zp@zYEnS<*ILtu-On1~Dx=7fUx__QxR#gDUx#r9it+i#MZs8&#BBJ=Ru_B|LrbxG2- zlP~jYdUE>_%Ad;h*uQt{(E1G@EMQ{~fl8KT;3_sx8HLeO4(#yvH?0_Cp1Wvi(8w z0wutUZV%}%MRrFMlf}8YB`TBC=5c)_b{7@Z z6x|qZJIgr%1likhJ6$`wyA~q{`bQg7Z-bKu1Jr2C1^?KQDO{BwS5*OT&>Z51DQeo& zoXqj2)m~f1_=>z>Z z_(T1Z21#@<))dRP*r|)KzdXp`|MDQ&7XCL6@|fSw6VrdMw+&??|BS1a)rOmf9cftg zP6-W!E2_sC_K4%YD$y-aje_9ch3Fth^HLv4=y8Zs>&bB5T{fC}JnpR*0ijLpHemhE zi&Ef|F*7QLIg8xriKQ-Uo26HcM&bqQtfp1)>rlnJp|}T-!q!z9CrsfnGGOoW4>8Mt z&C`mzijT1|%f*UYK`8TL|Y4)YOgaxg0}K^-rME2XrJh|OuVpxoKIif__xW8 z*PGYtNhNkEFI5cpEm>fXPrti>!SX2GV~pNwaI7JzTr}PPJD=b5WJ9OYyW{C)=gl^%msQaM-IV(SOTc~tdF9DG4zF* zacL*f*Xb0_EyqFBQDb8TAa!zBec9oUaSg(s!1V8#5s&cL@XzdCs+?bi-XXlC9!OEc zV}#U+j4~H_Jc~{RrFI8pL1d0LU78q4IBQcR)1f|8Cjyj#X0Wv$FynC~&#nIxYK&MM zO0=&N>OliP0wJ7kE}uI#t(ThDA*cNwK8<2n&N7;d5VQ@Y*R>9CC7J@olvK4|4Aijs4JQ^b615c(gNn08&fD6 zs;gs5+^*$CcT?m1VoA!cSu=B#?@gtk!~6|z{_-imdt!BTD)NFjs=|L5D_eW9X)u$t z2+?3x3qjE;5>b#%Lu0OVK0QHI-K?_&!osn5HC8Jseks~X(CLCno~C+KJTBiF4LtKH z2=sOq9Cs45+P}Bzun81+=M{F)SRIm`}(?FU3r_R>*;%f+qxn??de}p zgS$%1notxEL#8`m5swSUqn&VXcPF~#{`aTiX`l9rdvjD7lGh@1amgVO8cg~TJL)01 z&%YN;Tq!mTZdfF%Cftqz)=Kx{uL8ov8+pRp+9{v(Z|TjG*~9^-_=on@Ud)H0vwq=? zx!!&I;X1l8;<;TN|GTVtQ^P-6CK33`N~iIb2tzT~RxCkZQ{`A$FEV!&T-xa4)lu}G z~6lJCfKr1|88Bn9fdZ4txIs`lc3#W zLMe=!g@yS6;TQLvch(zq^*2!jJcIQzoWY3wsnmn)SGJw%T|N2It@dtrtpp0UsjQ#G z4rF;Bb~MnONx&MNi$WDBN$DC}f0`eeY1C{Bx`Q4mkAkRlEjouLRa_qxlNyMe!bkoa zFVii!|2}hMy@aJ4cip(hw?{09pR3b%yTp-wV;XX>#A+^$;nV@(G6jGVu>_K^;81)% z{0!a6q;U%#4q3+RQ?e2Sjp*h=YJBn!vmqd2o7xXQ@Y`LSvH7Ae7sr=(N2Nchym|Dn zl=hxMsjp&JQYyk)2MX=lAWTK2uwW8w|yh*<@GqUOg*zDEkOw)>6;Q&c7M?iq$$|{hqAW{YlBbx zC0!jJMQ#a?N1@6Z*CiO&G~o0^1zkiN56~bn3X!|T3BqSi5nxPY_Bxz zY>=YL;uX=h0*v0E?`ALC{~Zb)^Qd4pmE@4WVM#@69@Aq}k%#LmSaz3e>)-=k;?A`G z8Zuo)x@I18ijFtq>$=jkX^#b`@k_P1gf~24hkjJ0GMqoYjW2L(%u&f<47)oY8lc}# zhYeeyGZ7jadTBldGzs_%U2N<5X{PIN9@OK6+zNwvhCqr!qwLx=-erAjfe)(Jx%|l4 zM!ZEP+cEF3XrC>gfrpJX?2b$tR}mbA}x^az)h*E^!0>jsMq-7YMRhffzjuDYuM z$RDSLf)ViWJfu$IUHiG}H*G?in`F49CZi^Z2c^N z%TQiU&89+#?)#}5J?uyC?Q=;G?!m`?!aDp&K5x{EF!SW#Ypn4~;!?~I8kaADJo9b0yee(y?B24D``V2PlW0@h(Br>xCXnhz&OLleIqZe?XATo)?+jrp{R0hUv@kI zrPv>9aI}b+b!|C);=PO>7xvAcD`Mw&`g+10)J@kjT>gF_ics*tjWz+6Fv*}U#p4dI zVms8mq=YNZdxrmgFF0#=Mc8G_efME7%v+)Zb{9}XNzG~UYW6un`xEul1DDn(LC0u^ zqKXo5&DEH)nl>>m$d&>nALNAvxv*CmIh%kiPDqu_EoOZK?@r$t?R`thr|gH=Lhswa z5+QxuJdm$k&y4)7A4qlb}mRIVTu?|u21EyWNf54~BI3sYK5&lrrvTFS?qhh&g_zbA1?hqrdn&u*RWOdkGN@*VSdn^yI&a#eUIeXyPZ=#bi^k=EkFbCO9BESBtcw!OZ;Hv6&pIBitUwr-k-vjaY+Ax721`&9kS z&bCPP`(E-+;|A4k_xV^%5VhbLxN##^)n&o!_Nx-fZ?03eSo}LM+f62tru&jS%tVh1 z+p9x6GkBzui7+V}bWBm;CUX!L)1~GpkZHWR>~9QV{xYO@S2Bq36MGYC9!SX|D+T@N z{SOtSw%cZ{edcHkp&AYS3$ zCdjSYsiwb7kSPx;)K|d2U;ahSGsv|MU|h1JD33=vj}9!!FM{Nb+VMk>9}7Kz*`+a4 zUN$RdEm`r$Z;PX0BKZFlD-Ptb+WT5Ow9EYeVP$ z9{)~=%H0%Nrm2yotKp=WJ2LsjB_8U0fmh;TYxb-;xN8sl`s}BR@nLC#danD!rw8ze z&!lc|MA>yxUB(yE7z^-t-3N_zHbxxZi|8Vq-Fp3_HPGM>0PLOrMK_5vVj-}nM&=wx zD-km^95&Q!LijT_leFPBrMbiZKWJQo=?gk`X+AFjZNj`Zc_MZYu6GRjfPr-Sl`qYyuD_6#0+{;iWrTowsv zjH1llo#&VS%1Nq?1Biw}bH$y+SL!I`se3Yo(bM6n?7?Het%|JHc23%oOF*6y`$a`} z99Zj3LBA%0ve$ux*6A3w)HHCJ5{1l-W1Vh+CHZn! zQeZCerCkhY2X&-20GC-g7tY?=#{}LO<`*NaDfXP-o==v3?Fw#X!mIUSR8XYoo7fiB zjKku!UkA%^Bb*@kJi~UqI}|0^MXRct;E>-z!!?lg2-VVt*<#sP#9`r@DeB1dt1) z>LXCD5*Xnyn!1p8?_uv~IzG9+gS3%q@KbsO8a-%?lSfWnai%(URJpa$J!)l8WSb!d zKYk;#QU~`ZQ7fVuK!4&?@&2*(MOQa?d~N$LisF8SVX|SwK!~~lsWKSj>JJ4|@oqEq z=4Td1)^=U7U^`AnY;7TDwdjgf_L`>JZaFp#<|>2LRI-J)Q=pg= zBw-XaP-*lej7@_-X1+u=e^a1>Fi36ZgR~c-sqxDYX{+SrEc+>dP}4VJ;KgLAN}o|A z4LyUb?=lp{xAEd$;XToE4QH#rw>yRWc1a041!j=Z$`i@a$}Cy$)uwB9Hm*JPA?$^; zV)f_|&jM7iw|8XQi{4chBx96|(<@>p$3*rE7sBs5GJheu6WJPMR7%v* z-gyo;3V+)DzNaSwcQj2z9j>LeT-E0g`AXSfT?(9pJgQG*pm10D#c^19 zA2MB#q&CiaYt1WbIMNL29G;8H4>+CeXzkWK)kYXovEMZ~J?0L4i6q%eBt9v5dcKAF z#kgbv6h%>x3CH2TuwGs z`U)*4;tIoLLWkP9S)Wk7#ubvTJ=i2Ag>y;V8Om$#72xYVeROlopTDuH@f;R%M=$nA z)kbqK3vbM**-j;BxS=(sSSXrLn4B(s2a8+x&&r=Ny?hWQgv?2sT2HkAh9oa`hFFy` z!E;_!u)p7UcRHB%C&#?cxfJIe57akl`CkG)7afew4UOS{-{~NIOWJm91#&K24t#^o zLQ;3?PKS9ejR1?@SN{G^KDYvzUuLQ0Zt%8|NH=G+h0b$EVBn?hl7Ry+TqaGGPo(X2 zcj7zg8@MmtwBcckE>5R&2YdOGP1YYa);@HMcw4#^Lg^BDkVg4J}^E5!*S=# zzYwm?O5GpzyocB~K91AX+Rv9#<$9NY_%S%95rg_G>Ymt$%<14qI2|1(mj%xq=$`d( z{-u=2osS40$-c1i*Y1N(;ArguV_+lFr}L!wQ*Z%A*$I#@1UK|Oc^5oz@V!r)Hb!m@ zQRcBNs=g1Qgw-&xz-M58W7KJyuc&@5GMq2u9k7s{c!hN2vmR4Ny6+B$;`1r2h2zod zm=m2e1^O=Y-)!oR7ct*7sRmo8*%{uT|ByFStpf$0imoL9pO1+bzDf0ue+tA)PX7>y zGfjB>NM_JpMt4yg0fHlS7Tlt8-EcE1;6Vga zJx~e?iFCY(3Ajc&9s8>(9LMd(fgsRNXE$W_=Un@o``)h;_T!#s&Bon^>F}+0-1Mw9 z)>t;ayVjat9J(+R+Vac+RieTOC%PZUsry36E98BhtQUVfX_P;mH_)7kI&0VH^NLI) z>X?eGj)=J5y6J*D1&&O3)JY;BC!X01q>1hJ{8@ zvbgoQrk4%M`Ej1Y21(o?aQDO>-xAv^w9oO!hDsRGAkSRiIsRqpIT`}o;z-?8=|HC) zz^BWq**yLZbtk;D0QyrE_HN9ih`Gd~=fxGo`9}!sc`j^*ISwg1N|W6(`UZ*TdNps& zR)V&8tIPazs@QzFs-%&6$0~iZ^A0}jj;KOKt?zaca8ksA}Mc0Zkm?L2EBxApLT6Sh)nM{9P9mAmWm%VD{`5S-%E)PS4;Q8QLMXslE zSF+kd%d_EeY2egfJ{1G=l1pw)jzkl%ri*>;MHv#Y7MJoE&fbqgSA+9iL$wykC!Mb?7h2k8>=%v-tsutOHN~V=^xk8}&$XG4 z|CXp(&glg>um(QK3$wTqbcfD|p9pP$q6u?ONDZ=mPHNcc10NKj>%K0`8d$OumrpD0 zE4c&&KI;KPm(8NnD-RQpWbOfwms-%o)F39Zqr{zfHxDv_i=#voY|R+yM>C&?#t#_i zUK%73GS(+#GJRG#pYCP>MI#*ov=HE-hj3$Bnutd~TGxs*6y=h!@Ny*<`OkKeU7Z(B zxmSOr)9>*Ic5pkfZFCfNsu%59`mq(bItypdZPJCY2t@&kI^-I?q_UEi+c5%#wzh;( zm&mM7SKS33-^D6M04C`K3311fj7WvSv=)c@qoV^eZ=xN&jvmh+VO=j7wXB^-kx7k7 z97~BJmXO^Qy2{5FU2NMfwh#3e&E^67+s<&Ic_&IECZ#uI;=)TL%)1lP8awq4-F!h4 zcai5i{$jH8Y^L(EiuE;^M`BSvb<+@p%IZ|up8(6|_Wre-{cY7+U!`|(cs@?oK@cV8 z2-LYvP1nq>*On_)YMs`+l!|)d{&q$^G^K#!%QUvAc7zLl?O`T33_nlFlkCU{1vM;L z-w(ZQV5HId7c`r7P2a^SAbc?Bj)f;evOsK@G*U@bseOOC&iZ!oIsq57eq=W5k53wL z<9Idfd$OmPV|~^`@>=QK#yya^p^~WRs%CZ}ldC^>1;BIS{B^$kVA=nKfHJ&G>yL_X zQG4jjQ7%FYpRVvk#SfupcoU4T_;%Fv;nb3qN{pjuH7(FA*BKl*V7S(8)UU3-S>D(D zEH>ue=;FI!lU#ahxmy$iY6Gy&2IS?SW&TQFDJ6ZnwJuq(+8?g1ulE8x4Y|{2da66J zI)$0Z6MCUQFN`5;*-&o<98H0x!Q2MU-Zs9BnTel#JuMLSmGQr^VKaRiqZMTM6uTJz zF(ikJg6j(}vUzei-C!4A8dI~fKxr-dc0|g(tRihlPCNKx&|>cLD_ogMw02x!ZvD${ z?~1p4LU*&BN3a_P{m{6G;dj9z8n^o!J*7j)Z<$~7le{RW9Lo@SaLP3~i;aVAs?hN` zH05Izy+Y4aNdkB^lv0>%{WnTo8&M-JVt;Q7S#lcp>_PeJU#o2?lUnzfzmv<{S==+_`Pb!Tc&hwge)rQF6=p>7IHZ%yh?KZ65P0J@|oN z0qM#6MRaKVl$*7S$m2{XTT<|MzjT&lZmq>Pj@(an|LUrwCxRG3;Nzt&GGZV;W_6~`(=`H?bKc<4_ zS7g?vw2ZTRdRnHFay#JeZ6yqwg){NEhHJb#@?(Tybe>L!XXVR7ElSS>dVBdCq8QQb z#mR)ZHk_F(NN_e6!n7%0zT$ImKa&Pb56pKjW2u$~B6V|}04)<%vz+7M0%Rdm-s(WeynLnrd`AfzOlWHiCO=|MB#?*tLh#^& z&4nK1?#j)@)`E&?p|B3=Vy=XzYQ8+r9hTgWGTR;<0*kvxlH($}W+34V%srB6vn>3c zwL)6_d^j>{*iQ8U^h7GcVgYT<4aM^Zg4vDEGL5dWhcBi~p(QnHZoi`QTMVUx5Y=&! z(z*Kz2WUlFl?nb6~!COPXh9Fn8q?hOF{iK*U`E*=mU$>~|th zt_-hu=QdU!a9!|3t}w!p+b;@%I$FGTtIt^vFbq`iECeR0Kc}XmdcRsp4eWnT2tlga zeSP^dLTDMp>%No%qV+HBHt;mkIgl+uWT$dnY)v@H1fv@NrBF-gSaq)XS+N=YvOjka zq5cY+ZQjxB7wfrb%-~3^fO2v6TxAE=<|?XU7Rs6;!FSSKSSCdi9K#!A+y?=xD%T}s zSB)P8Ow5}{^BHX(?oU3oZpfye9niK5YhrB~uqY%Tn#PMyG?ZriioP=uKGG~0>$pf6 zEgwSTXvAL#USmUnk7r7;PZtZe9YEGq+ZIsBu6-K62=U#69Y6x=eq4;=dYBkeTurH@ zB3vH*{R6U`HNa!jbuWFZmXxfcZcZ=1JH2)fc!mfyi11IoFe`F%|4OqakDJ)P`QU`9 z>;WuwFdD@dgNaqlr;XFJ%bP=XrYG?|t(4HcV$@kY(xJO6_)Q~NmWs`Js?Up;cSPd~ z=o5|f9rI)?IPaA-eby)P`nq0kWq7G7vX9I*w&(cf_2SK4w>w>3w;h3e8nccr>1Strb8AzB-3^ow$3X#W`K+~%4rG8Pjy64HNt(qQ#sX+!&r{H!Y_Pxw^NNg2&VChktT z)awv*r_vj&;s@sT-1oqE<{#8SZW4n|&VUrklMVU$5Ky(_za^^#*yj#=6Zv?ZFu;m%)gM)QU@(aT4KTh%8IPg<-f8 zh5N`~?@8up342<2SWnQAV^dAN*Njbg)ZGt;DqQn;(j+g2FI8Ds5&whN5K9(i)o2v>2_kCf$f_LVrs_cV6a}*9<2J zwZHuI5NUD5;~SdoW9F>Ri;TIFU*WV$3SUNyUDxG%i^>U@T}V-wnm_UGA24^XpG#IR z=TW7AEw$q#xJGN;#c#dO+P@Hm2gQ)Og8`lL;6Y*LvLna3aVYj!2~lq!oNxgZLd;&} zfo7sD;^v1~V(%8$>=@n5hu@~xZGJTNtS#MxHzsMNlro z{R8c+o+J}M!*+pOZzk;>9jE22T`8$w%gFp`72&UTa2HST`<0X~$Z-c@SRRIjUBzNm zM)uX3tqjV`->yYtik8hAB{0H$Wx;!O1qIcm-R3iw65Bm$1`j|YeFZy{|C8BknqZ;# zPr_7WcBQIObXT;!-zf9VPrfsLGKyny_CF0NpIxVe1P5180D3Keo@(jkh z0Gl?wPu$=EQpm8-{W}5TwQ=T83ln~HZub#d$?>mDYvF1~=GZr_LW(uxg+pil?h$S! zgwkquN{%DXok!~qCy;W&vOWQAMX}xqF0Zd2w;cNWn(-ko)3s%8^97ccz!UKJz?7^! zs%bl-7CNNz_LwKx^&tJ$d6mZXV2)^}wJp<;+{xe^Z?Fn^?Q^Qp!Kvw{m<|o3#@%;% zJm793nGTarV3W|Yk4lcf{a0h!YTAx`juKxt=8t%#jf$0w;y~Sa%4JPvpFni9e(r=G zNZo#`cM*ZG$6&pWuPy2?CPLP#Lwhr??))~&D+A?$HKx%(T_xxxFB==3CdzTYAHtaWcPL$fOJ55yE~svI>XabYo{*)8h6;)9WT8TQQrC1v}}y zF%!uUm4D#7^xO02&IvY>29ZWzcAx>dRm;BYjyLad&>MPf zyu-#^dbFg~g0n%*yStPzDl|yj9Y$$?34`>QKO?Aqv9bWKzZn z=?&GSR2yGCa$~;ml{4lEdXJHDAl7EZ-$5guH?a7j!R}6fQ4xa2sX(bH7dHqX+}1?y zKCMV3SgSfr-EO6YH1oLa?P}}~_thP3{cu>A(#cr8u+o9>5C&|lPVbyu?olJR8gX>6 z8qPVkTdeGY>Icbw1uImwRz<^0UJsQ_>dAs=xEzSsBV*QPCL{q2Q&E}#cOHF3Ul**$ zVx`Gx=Q-l8m5}o?v-QU}N+G=wj)v4s3TNRZ@v-~#W4*SHG2M}vmtBd&Ps>Jxs<#%( z5nlY}dsvnWc#0d-j1StY8lnAdAitG8AGk|qV5NR$QKwo_&3#xfbWz!rb*fe<^MGYf;0c8Zv~1!D8!T> z_n1!NP5M*Z>B$=fg+}$C?^Qf7aP}C#H;k%&-ha0L-f!=N->`N4-O807GehFPy8r&4 z{C%&#KhlMPjCX$vBmIg5JZQW0CqBSp|70;wv}}r=a6cL8LEXGG=z_)WL*BZqDfvU} z4DSvUJBMbNq7T%^MYV%)v%qs_kSEqT>t>F3xvW%$^R9I8aC|5H{3+t7s0VWYQjR$G z3)GD^PMud`lv(bo)2~WPLu-f+X^^-BGT z55}0MO&&4aVbdeSP3?1X`7^ciFa<&Ir`x|H$!M`pV9PS|n3WR~G*f%y^Gnm$?BVCm zMZe0Ow-v)ZdE1jF`>;sbJ3krqcTzl+z7K^w0R=p zuDkOc%RS5M1)qnPBW05EsrPEpg?V z(V#!d9w@8=vU=jR?J}|0^fu$Fj)~Tv4SRFElXQcI$+p3cne7LkZf$LCgsRr0O32Zi znm9pH(FC*J+N%sy;sB-UGs+%{Ls2TGFJm>;KE>77jNad^zl2p45lFo_bR64%MM&=q zPopeoNLAi2?ni2vJ$i0g5K4P>x$Pf|XXFI;wBO3rr9s!L>;xMLs<91}L3=#=o`?gL z^ILXC^)>}ULgaS&i%sA}?U~cpA(BBxf}#K#o-V1`Ou8%n)ckY*CxDOS zT-9q|R;Qtgx4*+&*S<2i2i_0#ukrP{IwR*3a~7`ObVL}BLeq|*v1n1%lA-Fa`K8sNX zM{J{h?*g-674JfAm8e|WZ`AmvZ%Qq%T}w*sKdP5vfmt+@*1?wJ@$W61nUN;KzEI?k zHOplFW9tCVyG95qH|K?u&0f$NCD{#SF zzV$TM``isVL&1{yWF6W^TCD+(ZI;9JoA45s7z^XJj=x<13ZH2yTT{qY_qMojI-*77 zk5p}c76BU^60@c?o>B;pV9UG+jvOxh&tvt>Bex1o-&e$HT$QLlj=LB&E(~~-3?qU& zE-y6~cXUj>*EyJ;p46>Rird{OBikdZ8ywBjBn-(aBn0#IC!$b|M{ipVs7G0TMX7*I zBrkv}wr(-!eoHF5YV;esoW9c@-wvWttg2V7m=CxQTy9L#KbW`5_?tc5I z+cF_ZgVf8YQ7GzU^cfAAU2o1$_5m=WBbu7O{b*Z@&QAk1nRxGwfHhSpj=)wI2VIih z26jr!IkQ>ik1lV?CVGCNYp^kZl$Ksxfnk808ydt?TMs;Yy9K#T3@mt!l2%2U8o78LW@yiDh*$8ZI1*ZEJXNP*8xc5|O}` z%lf#t5bF|^K=8sV!gqY?P>lI@%Eq&iiErARDVnzC4!`-{$PW_u$w6?($baD@GW5FJ zswB@1Cs%tFo#^c<=afQ^RO>W@0K(k2OT2%;2m#w71?YS3^UTUa`;wyE=J0n@f;F`{T6kB^_^VyE_k+#G9 z^_beJ(=&;8;~D{>e`i4YXF?strTO_nHR!>^HlFG$k9XUo8DiU))o8KO|6}Ys|Cxe^ zOlPTkTvjoOQ^VEj0<@$hQb0VH4-D%)(f^cI+^k4Kr0snjFy->YG6pgLV*K-9*hpo- zgeDaydZ)Pzv0-ZXPx_`IBk^Su?pmQGw=5xtOAU|u+pjB_AY|1QUkuIplWi-B46Adgo@K+4t&>617$1Jsj`=3l=Kc9FA)(pQ}*OmhU$3U-@qC>psDpbSz<~ zUHh(%AOI#|UR+L8kbW{;tB^j%bN}$xw;Az zLaNx3Q=coggCxU<)sga+-;LF?RhT!*E1e+rPXC#1zZjNuyz8k+nez0wUjlo*vNn_= zu_!(WzoCgq`)2d~pK@a#1crpnz+JTrr*m+d-?p^Y`kv`+HD6j?6An^afpv~j1kB|Y z3!{D|U4wy&d|5DbtGI9+*j7q;S{?_K0&N!c>5!*K3~w)eRi#~1`@pbh8>Ca@yEC12 zD@vA$mnLEuybPkjhg`A@n|C?2o!ye64xBAo%u(_MGoL1b4zJvUj|!$0*pW6adRA}j zZTym!9M%CwAnV~%OJ`HtEbs4dnZl4m1ujzuG`CKp#eLMNQ{q;)d9X@&dh&Ch@-lp-)Ig3YsfA0tYevQ2V9axW0 zaLV+erBar>J>5{)5V(=qj^|WYKNjxHY(uw)J+!VciN>dr+B=zZHVhsE51WRUn*c8M z=2fHxcr2c(SXi{*9?O2#v#fQv(tnp!FEbav2zW_m043EPE>G4a4Ri0KgGx%WSji%H zH^(oBX=;lNu~i2mg;H=11{h_{qzTzr%&OByrUo-);4184?e_u|b-MKr6CLI3IIV)a z>X*4PO;#MDZrqcGf!h4o#0|T?GPlN*NKDn-*1&V0K&k~YJ-1@gWO={Qp7mZPaebY4 zZBMk|t}raWh_ZCNm{o9_^3YatSECo|H? zlQ!nX_>5dP`~J!NslJ>BbqR?Y(#=VcW0U>J0J4y@Dot{vnZvchdt+*I7V5?QhAPf0 ze~E1Y#rP33#o18IsB{EAV8BhLhpcnCKrNpDTF$HN5lnE44!iZNM9aS3%5t{77`R%E z-JZVD!EW{%B@@J}{XT#C{Az|bF&fk;EbYF@A3E=cm$kY&9@-}eHl9=LFv^BKEVsj` z!nRlrDubLm_AP-*M?{Se+Qkk19!|>EixQS`auqT%Ll(Gcn=o-9eq)IT1{V)*b%k-l z3Z8}S-M+@8ZzQL0LWu{+TE^`|+zc5JHSZY@fah#B!H|CA7u+DEr7*w?R zgbth=%dRMW$`P(dz=3Sy%Qa0)hDnp+2whu&AoeT6R-a*IiQF&CKi$gH7LeKL8VUi>2vB< zGa_43q4fuUI>TQ6vBgXTD~CC1TB+K^GOF&r=6Vvaktb8L(gv>{$g&KsRi&`k@Wa3_ zj}A92u+Xw=XjZDIFLj{DB$Jqfv2V?1q5L|T12S0pH&g0@Zre5R0M=knR9d~5elmab zM`)l|qaPd?z&y@A4XTX|Qgv4+7bhCu{%z?hr0JE=x8`#Rk>DV?9e?vb#;KGzk)TH-+W-pP8pK*;$Y? zxnr;`v~C6P&~;42;F>qB8MZM@&Vr&R84;8<5cV?=#Og-h6Kne7im9c+lIII z`L$6N7dA|uF%ihRA!2GjFp=1Wj;VLQ;M!PE{u$*HSxQuxi^q%ZhEh{Ksn*%u=4}h6 z!i)dp^mDI34e^`TsStfg$?oJOTEBusfZrR7fOl~8z1;g&lOF--6%R<0NaURAC;MCdQxS&6kAnlH+ z8#1dDc%qY0abn&Hi)u{PO&WhH&M9BY9qf$LPqYWhu9yChU0+tZAb7+lsil3_EPeY` zGv!$IF1w!lrhzf)&a>BlCPl~gFrQy)M<|tkML&m{;9!o?Yhn{UEG1~zU5wUi0<`b@ zWb>EZ_!t-yr$xhKN0wL#u2?*IjsLLH@pDz)%pWF}N@SOm5GMCy$IbNw6$-Cy{gS7y zVuL{|q^WFYt4V-MOijwh@9*SWcK20EdU27RZOH*2W!iBq^seP&m$9=Zcf=8fXk&C; zx#72QX~92L*RKFTmrPQyCD0~Jj2m+z_}Gd>%GFhCI~-S1BR$n%Oq@DN#ZUEh86>Pa z5>g{>SjRLqpJ)anEHrFfpSFAw4tGvYYxYpol1)EVum(yFw-#l8LqS<9182&;PUhg| zF}&C-*Q8?Qp-~>#N8pP7+W9yzSu)v-9pKV8YRyA3Oc@yNgSYWOK-X?-lPOYwGbY89P_(aiacSr2KVV8d%U1lk}1#(P?{FXH+)QAO>;EvXkEU(46 zLYudNQP&Au$&kYisWMAHP5Y|F?s`$wLBeL*pA6%H$VSCUmQhRR)?_Av$mhpb2n7yO ztt^ngG&D^(Z)KGuA%2tPEB=x|dh$c77P)hD?ZQW$7WhWd^Rkx;kvyhl(%BtAf709f z))!PsvVFPtXNtY9sPpe>f6kmAWm!cL+NxHs-~4BOFCFp!GHhu1!-)Owql(#bxhlrb z^~>Y(2Tr#E>h5|-RV>4yq>mW#VAuE-F;0u>k^NRT}IbnWd;5Q0rLHDI+ZiV$BJ!q zi)0S|n@;DLoC2_~J8kyz@W0P|S_lE%%2nW~Q+t@pBHf5_j^XtY23xfyd%WUAuu_=s zgOs^Y8kS#cTz&l;GX*x!tBXkU?BFeiyStn@Sl$ISrDEf^N$pa|wr52Yy8Uu1of&iB z%$9bHX2q43eNKihRSm$B3}J1CH|#B_eQT`L-tV?=Uq=^;xEOmY;B1ez;!ewMRH$p9 zC^VHZPOzG58W)=UcVe}fU^``O@M~%a>EgK0Hp8T(lpEUWRrp2t$Va30 z!M4P4yN+I%7}4!QrdWYOet!wewR`}yy&)tgfLBv&dN@g1)hiuOyX0%Rpvvf08M9%J z?O4pxQ4R9<vNf$AbGa7+Jq9^2FV=-0^8x{%4I)AX@&fBuwyugxBAFcp&X)nG4 zkjV@~%uycsA;T&t-Va#k4|$4lPU8#`n!eqhy8*)n#S)AbQMgF~Di{uZ89`Q%{FqGL zqbd@1V?Xk1m%<8yVaaqqf1Z!{X2rzwU{-gHq%wE0S|Bon)lRz#9Dj_DRME(+86}#s zhi8BI3AABL9!bajX~V9qcN7%n+!G+xFEjY5lAoW?TTxLFJh&tK#tgzv#tMrsmfujK zsI0t0Lc|f}Cr$8N_y(0RD89;SpNCz>ne#;VREXXxA8-APMKx5iAf!W$a3#Hw!m|?x z3bTI9E+|WmDAd68=PY9DcxD!LL(ZO=9YIBt7)MHzg&lz7_A0-f<7X#GRfE2%=AA@zRqZ!)>~-(wcN4b#(z^+}IT{vd!q$E%g=Q4vakDQr;zjt&gq_QWhn_aDyN~0c zFU1ZtVM9k9Sf28*3i=2+ zc~G^kbPES3=C)eq=LdGKTWB3NO44gr!4cISJwo}zsb>bL!v1+yzx`msMr*)yiu>5K z$@KNx{wB74&)U{sW1#;TIk;G0nuPgPs^;%i({Mqg{lgjg=R(Twy+2GL-c{JBTr=ap zk;1TI3ZyO!J|#y7C-E?{i|7i)G-JPEfQz^V(PjS_w4fyh6=C2Y#1$n)ANWU);9`MZ z$1X);t4}rZTp0eca_Cq#joNa>0JClVnJ+nA5{S)xDxX56xY7+9*9N#tk+2COOPFg^yS9y`CZ^ca=ho`2u?`DzAU zRmqhae~4?^yuA*#lx{EjgpamH)X#*97CCR}Dql1z-F$sc+tKz@e%J0Te{<8gmDzZW z>#npafa?TLm%)H_`QfDi1Ya=7wRK5->9VY}oS}p5k__m)egrzNK_(#7q|-Pb*b1Nh zydC<8YbnH)IUt-V%sL#7W1Kt*DshNPL=mJ zTMg2UnqO@(7g_h_ls`t+fO#9N5geuy$61J%vD_PPB&t^OY}{o4R9-uBjj!#!o=(T} z!>>t(jIHZ9K{a1dfx|OK%nHuGx!dO@vp1f0x*}Z>*|La^B1@cib{PdPh%cP?6JBC zlJzET@1?6Kv@ExfveJxpMmgEt2)a61y(SFqfsNubshtV=AxZM;SNC?=-7RIYfJ<&OY z9alx_m?)XD=+6sd7ZA0ZmLHfZn<98_)MdIQv6)H6Z+o}@D^I2?dE@j zfK@nbBu}TJqrac`{Jvi$K0JLk_K$1l^LN4b{lMhi?Vrtcbd7}onmNRV0OLo9{wOS~ zvNVHAvSC&1myZ9Ug|i!az4-@EjVl45y(U!Cv>gXW zRz}toyrjBv!OG1MlrvJx%Oj3{W;rj4adFy2vTA8~(RiGxZ8hI(`Vkh_6pKZ@F&sr1 zAZM(Vo#;w0nW2QES>N~3$lV7!S9UbZQVengAOWOeivwrOsE^9ag4&uj5%KYP4u0JQ zfqS%VI=W0vaxJfnI~dyU|NG|#2W*eAivhe75;t-!2kUq=if?NNv<|5|W^mm7KSDwC zNNrl9gh-p~Lpis@!dQnfP1dcT-1;!G8`#hAw>3c{KV!?TWR6(UV6a z4!!k&_og4p`@=b%2L)H`dX6T2feruk;-$`JmiGbYMV*`rY7a|5Jr}K6;`3e1$<{b< zXX@g9m=*WWq{WdzQL8YWQe*jtDM7w^P8wy>o3H%pNn>S;l;NX`^-N})J(jUCe!7J! zN4pRoYmTD&N&R68e8ASiTOz`3yto7oJRhRWlN8h!%_plftA6Dn*P+26aR%63U4^#( z_>ix$K7B($+o)VviD9Ox_JQL?b8hi_>k=kh{9nzbzpt{YAHWWQwy>Ns$4iIpK{;2( zmDpKXbjbR#KXUs#?=0v+{W{w9lBQeO<*euNPr18n4P1)NF5xVE!4Y3h8KM?SZhOfz zx0WlNShrdNxnwoST#0sANc8%#VI%T@1w@SXnEAjp9Nk0`uVz`w-(&TX^=kXd>5kUt zSytD?Gfy)jD2j&lfM|j=Dc&3I-VnS(N-xWkmeP*sqF#&H_JeO7_x_ zly1tnJ^t^P9xJ%o81RbE(-X<{TsM>gy+Ux3DFcLoET*A%mIf` zJIQwMPv^)al)_H0(ITGM^*4{nX-NL)mgT~1 z$;|w2rJJn-Tw(Jk9*g2ix}Vz^!0T2%y`&x(vjm>PXkk3w0H9JJD6pIT%ywy@=WGbz zP>%ek#sRrD%KtnraEHGi<9c8e*ogtGCQsuw_R~aK6+Ew%RNE&|n06k>q#_Tv zmKWA)`_D!D`#nQa6e?@7GP#`4fI?tHj(lfv816==s=b8$5Y#A0-66SzIH3KRkq9!? zVi)+WZ-3?*>fQeQOh{Pv*a42|q%emKBm^&y)P8LG+oDLWT zP4OKaOO%+H-*)QlM663C1IUPvGz*@=(hh9J8xT6y?)mZFBHrxI;oH*2B(k0OjAx!Q z@m?&JPQneu1*pB}nK%}?O_6c97V7CJ&Sf`bKTG-Ps3d%xz@K7UJdq%ScK&C(b#ebU_NB;7RN!#_H!Q~Q#i6-V40(+B?>g3dyti_i*m!28qMc_Pz$?Ud6ur(@16^2 zGq+fBD|svssS;@iNt8$p5IB>Q*u`mO0i9m^MQEpMoI@i2{ z9=QmJ1YgNxX`#9oFytcq>U|R`jB;j`bXL@pl4WpH2b3B2X*P= z*rZeL?7(44^OctSuemvYOwi%`$L+8|iEr67HucXpzRr-@;Y^!V=xZ?s|LKyle1gow zM6#IWWQ-93<|O8k!~x@99Nba~4ltt!Kl~#pmG7Wb@w5F+Kot}4F=Alhj!7X}zAUWW zh+r$NmC>`89P0S~>vcl;IE7a9ak1?8t?z$-VL1&DtJ{2$-@*EHIi=D%a8BDHladxG zHaQRf>d?2M6IJ*smAaiZq3v7zMz-OL^(7E^b-Jb2R+>I+R?Lj)8b}sf4LI~Bg(Cd( zGW+LeE&X)P$HRWh*_xIEWTL-A_DMC=%`6x$wzJ6cuX|fD;s1}4T3s7lD#pj zfDjHr4}=C*xoKY8!QkX#!)>5xU|gK+>9EYT`7te#`ON$F4z_)qKb!8NgSp&ywoV|I ziqu-~-THchPo7IsUW#XE1EoZNAmaMpq^NW0m2av)(#3nmX*ZL6WT?3J{x3vaols;l zo!6(TlE#!uyP7K3(ToSK0YR5!sVLj$K!13Hbcv~@zT!Lxi}Rw#4@C2U7h*fAj_>9a-L*nEif_s%Yj_u<50kL4kW2OOC?R=kPGXK8%FSE z8L1wKao}3F9}_{CLoTCoV&kuQ_0>KxZ$4RLUly_p9gx_UijWe5GgvwjZ%**`*Yh=Z z->F7O6N;@nX)um$R<}$D>OthHMptOEGKU@wuf}A4pIrfSF!y?nS{(ydVnM#}Um&-K z_~nCd?_Xkb76Gfi*Ei?k^4GTrg{_Y|Ju}b7f}&~t_RN~Jh`6OTBt$YQz^#=xqjEHV z2rbP5O^ySTz8;lX!OXpVSs3f{`B6wRMV=+%!%nAgmkMpShUae89cArPdGSJW>*>pl zmf-2(@6s7MCwtGr#v{?~xF7_ZbCr8;6auBPM2?lZh!9`K6uRSu!@!vgIT`As1DDKH zf{NaUWc@OzLWfNgAxm22ZA+MB+nm$3P(^%m9K7d(gS!Y3}Z3-<@K89k>ab?ykbo^iW zH`};j{T?_7W3l+OB(3flYZE^M_cbs(HNw zat5q8Z;)9e>IE)owu8>L zFl-5okLn<{*9x#Ar{dN@DKqpZOp~q<}^_q z5Tus7qwqV_?Rf-KD4FSprk4i(!dwy2<1~%>F8-!o&m^>ll`={t<3Wx5vf;hh8c=8X zr*hbT!Qak>H2t3F9bE5NNJz$;=-IegL|iytvDfv=J`k=T6M>Cr(rX(1HOR+?Op^Txfp?jl9eea2k>wq;=$p+1Qz;*eMFJN;>vis#l$@`oPsiKKCnA{qK98|-V$~%9ZF`l~aV9`r(?AUk0E*1V4 z;8v{vd77c}#b~xY=v(C&7#gHKFz2U2i{&Y=G2vE5@k7f^AaS3VwQ9RYpHZTCbH3e> zoC7=hB^?FNM@bbNNQ`KtkEv|g%ps#luQ-OhI8Yfn>k@)G*@YHzm z;J*t!1I0Z$&l%zZ*R1q^Vs0FlxbIytJpUHx z1i%aGazs;Xa7S}3VY5TvJ-SVd2w#45y=NUTk7|cW9F*d>qLC_K3&iAE8XB^o`&w!g#6%8_vt=NU-@@l&iwi}>wkc4<t#>F~+EfUNjx#(!NFj-W;2yP#2xdxE{8_t9ra3{PMb z);=jgawbN|Fyq!Vz+D5SWJDvoP?)J6{~C|F`rdyMFJvU9{KJ9LgoTVR+ZY%bx3hF! zS<0$H^XvBvwHH36b1*~ zQs57QFGEt@>gmU$w99vr^yX&r2SDd!;$&`y_DSWV9&(!;q@oc|(8!7Pc^XR3FarNh z`$dt-OK)1nCmi7C3K~qmN*>v$^q_0!qL<9{n39`mK0vjX!W{ZX3>70P4~{@zWG`1& z82e=M#Fez((tpBgduDh`RmN+{VZOsSx-lzC zQQ6E=UkWE?hh=%LB6a(nemSkr*?-5e~#JQQ}+5;q!W8vXe;g=FE$}VQ(QGG2# zv=*@gRC6u(EA&Ej%b>Rg;UFo9+?o+Lb{C-8p9dd}zxezCtx<}pL`y#VhDyw$Lr4m7 zRG}e^grp)(u*lyGm>C|hz5}RGu)DB$$J9tB|Hw;@e_>nTPCW4a2d@SL|0k~|i;0@` zvIiULX7Jw&@`80m)&3Jih-Lea0d3-})(b_2Xg0X2Fs7z@CC>C(Fs~lA#8#8?s(~{Q zHTO~0I6IbhBRamU_@Eu=q^d?4XCc%r`Y6eaPN~#lv2dWzPr_(}A+>xSo71i$-0o6V zy{PRMj^BRp?16y&+?fhX=?*{++7@VvQHpdqtKo?6y3Rrod&FF(-M^-caJ$%!5I4Al z5qTvEKhE%3Bdu3rh$7+**9pn9v#KSPVb%w8U6Al8 z8{w$UH*)JF|&b(94rt*(*V?*t>%7|4gjRsYu_T3=cuGL8}rSzbM zIr29e{&+^WvfLAHK|HEa2z^`AgW%j3Z!?9&gGRp2pbG4T#Gpo>sXhEgCAnKwMFJyb z5&*Dq24>xv$iJ4}Z}^>LExS1-o+rGx0tuw^xgVYf^B~dgU+wz! zNT#)q({Js14lX*V^2mb(QR*g(#6gWe3-rHmYK`S!l?r@mc!(s!K08p;hZV`2f5k8^ z9MRd3h4kO|)1xCP6=Cj(jbOV4dihHT1I@7OnPs3ufYcC%DIl3Sa~Wya`7n`&9SL>~ z6xmgH%mH^%Xxb@aqyIPao&rc^d(lo~v<7n)_C72(7}LQ9lhUTSVcaj(`1^+^L6Ha- zxI8MpWsQZc9Sc-j1>03CRI?1@$pl2KUP`S>dqtgWL)|mNs z=_3)kBqT+;c{vm#94pEwWmcNy6?};o9jzy0)yXSfj3=1v>RKBr%D#_F(6y6bh&jQ5 zisyOr&FD+3;GW)2K0L$5qI%h31NP=4Ye1r9j1)mCLn%s)>izQU0mk}Wl_*vtq@ASL zv__qMYeCKYe-z8tv{sh(zAKVrS$ez$lPM(ZIGg`O)*N=I>7?~Y(2O}$u~6GV=PvdK z*#+h7+0h?25HZ%CvE5J-2<{4zl)xk|mnRN31GZZ%;$#|`J1CA3@* zN;5mWqtk|X!)qi==*yJ|=ZYp&amiNu{lZUCRtK>WEu~n~r~pzCGN>S^JRI7r6y;#3 zfRl<@7}*<$O@6Fhbwa1#WbJehUnw6P95ANKS<)*+-;H)|6D zU!y-%;JMDUJt^iK`Xq0sPBfz**0u0;6_p)AoP zg&@rxi@Wxc_>dTMhBmgm?u6;L_*L>7InJ{`bn()ijwbbEz};Dm0OyLDv@lYqp=6*g zksY^$1NoBav@E*wO8EUI|FgG3=!@pXip z1AvW6@>Bn80KmK)79mt74d&V#-y%=5ga(aV3VFbRexjQ+FDcqE~_d>Ws5WRvT!khVOD^}B`&(qel=yP?4$X8|UB zaec+#fMYt>6taya8QIR@oLWz=A|w-_UVvdZ4v}ecPbYa{iEQN}QvsLhp>0fv%uY$+9@LzvpYm?Lv3+?bc7A>GlzL_mJqg|Q0UP%Bu59g$Te4Odg@+U z#s1hvV`fe<*QnJP$?^<%qD4HXh*!Zb2<7hWh66}OAwnTnEeU4eCnm%7lMCJa1VyJ9tQ%wF|DWFG3 zV;&)JIBJ2y4#5?%P7;}wn{V{3TMjPFf=5}Th4p=#Zs zBR0j-Up5q#VJ2~uYJQ#P6QoiGFPX6bLb2d*yow7Q2f(uL- zsj=+E9)Hc0eU1v#+RYGI1@*jaltV1Ym)f`#LY^wCuiq=4FwUfms^;&$(eY3xZ?nyU z!pP5HVYhCRg(4h=*gMr7YpoZ2`jv79F7t#1H00Xa<53IxhUQ}l&I|)gka431bR-s? zpp_?F%OxX^iy5O`((<1h4H8k&OfH)BG+Tlx1=_0VjU&`kc31@@ZxmSsNwi)X`@X1{ zn6H|;I!Z)e*302LV@Yc)?pRUtn*7qz-XTZ_e3&%8{%2z@$c{^=AY8+6JKx`)b_50` znB6%>Opu#%N+Jp^%n*65k)98uPuOW_)dwx5GuRGR5X7oDz8j>bzOf))HLvt8E~#s> z1C|(5=mh>DI*rX4-i{}%LBoQu2r^4TmBy)ktudZqv=q^m$?Rnv;dui|zv zQ*V3K5>>C|(qFr|-NPa&3cSX1FbQ>Pc>h_Syn=Xe1%*{v?7LZ2zgy+tQ6v~R4&LnI zU~2}i$f?o(n}v3Mf04*`pd4@rB96Bz;nP#j>?$@ha$QTDxK&0Guq9jrUzk|gVA@)) zdk6k!SN#t64IHy{$%$*L&{CWI_qEPHzbgSLVXe>BQP))`7>~tiD~ueJ&)&iQCpbnu z!;y^&??m60y#CprL4f5jE{ApG_gd}Q6fh9Zo9YLv+iu9>RmTJun*KvwWWdEUY#(Swj9UXYN9d6iBMt|UO(O(w9uZ0 zU0&yidBqQ?RhbDx;k!t}XKluKU|&AkMI5@86QZY-67g)0VlpbAt?8Gra%3s5|J&eF zl_9T0t~;k|RVDcGk>zGg2-Gis1ksj2l6y1LMS!bc=Y*WqNfBt&MVp&*Z)w&f6~3?L zK8J1`@{yi6JTW+;Fh?5?_je){jA??|ms)G*PGv{Tf_eK_TFHUBRz}-pMdB6YTm?cR zxT-H^gOf9(mz{S82fdlp?4Cp&h}FhjIxJzgrp!9FSKQFgrb1^3cRAyu6FsSubi;RP z;G@qi09pSbOq#K~v7jSO80hD@D%s9R^cAfjreSI|@q59{SBTvg$(tZm0y-6e1q(+d zVa;EczMEy`j8*BjQGaI%@HwEBQhucULN4AMd*OU{+Dqw8I&l_2TR6h^VGSjI6*~ZZ2@6_jVfb|w)Csv29v#PnRUzLwgt?XxVi0X7s z;*ILSigDf<^Mp;2gYdG?*Ezbb!KhP{v8xu`>LHFv_6|06LfQ24alig6U$!}7_zmVi zq?^SdGN^r>sQ>BQ+=spu%G1JYfBnKv3R(ej?INq&G+Aj~tgR;>343dT z-&O5XK6{5VPp}hZZwAuTup?W?n`qGX{x4)%Z}l5l78>|}l4Z~u^P$K>&xskgbu7bn zuDul#F9riyHmNjYLOH82U&NB4Jnj1ONR21IZO`FWnjC1cY=W$t?Ls2jlxm?$(~;Ph zhd`wD?XF-tcg-m3-#*70r9Jo8CscAR0*>h^*o*`JMuXmwGqi84!K4c^#5}o7@3P>B zhK+DWN5_O9lw5E^jKhQw1=yDjmONfuj$ld|C@$gPHH`BF-%u+r6#PqiNwT{IWOJxF z)!EG-<@rbx29IPk{8&K3>>y=eVj(}!Key_pT^)XVwEv9gbPU_zd~;f`FB9(`r12gR zZ3>sXx$*NfxMf~}QK2PoHa(M&P+~bvdmqE)N$H|{JvfKja3G?)`;kqnyOd|0bj-Fv zT)L+tnhMhUDkI&i9}x#s_J_Ogf_ZwANjt{Ba6aY#XFt0@)HLvDef59xX?I0@U`hX| zJEymCCH}#uePh>=QjB$L%6aeLapejsJdgVFXc#fUw(9&}h3BS%CxDkQjLO_U6{JkO zr{%w0{!6ec`y2mNe>#n#1%_@%F!1Vfl%wK#E)C)EWD}EBR7ZwLrNzit+2nLVp-8dJ zVc}&ayY>A=Q}9)FEVKZ;Y?pSoPuKiKOitEObkfBxdFi=IqgF=Ou4~_|HDAt`L%_-V z-eYq^lk&f$O>Ya+?L0}~JA^C{o?H7y=AQRwH><60B-n-;8@B-0H)W9U5;!}UyOorE zWOHx7eT~qzjJr#)^2)Y^OE+aNiRetl7pS299{Zmz0NuKLX2Esgj5BL+AnaPdE?rSY ztE7f<*>u#i=@`cl3&yJ(zIu4UI*gupyvClX+QfSD%ROeAtceN7&Q z)%%}yWv`(l)~lRMM>m*DVM2khsotpn5BpXgPD^gyRxp_+Arkoo7&9qDRzu8b3(`gM zo2)eTpgsk3eVVeQ5)MQ2%JM|WA@5N0H8PzhivRB^DT>*(86}Ur_nIY*)X;B{mZ9-k zeMYKUcw$BenlPMtt7N?`tppgpmM5<-3eOb>>Rz?9iS!AMuRmvwkorsw%#sD&-Sels z`1fimw7E1c50}T_HTYhJ*)+ank!ybc^b?B_fuR0gwgfV%@y#$%k&MQGW zUFxQbBcpbM{V9K+SpHOntpy@k^lK{)OAoY93$aD`*^K5P@%}VM27cn0nb%I?ILiAh zHDg{F4$)H6C#x}#g8p^FHN`4(j=f$TtYvmlhr3D4k|DLxQeF0dTfkjp95x`RoTpRC z8F~6wY4DgDYF=t7XF~Rh*)OzQjP@_G#7o^G=2ey0qU%f~-QYPj+AP9YdEO1u|03kv z|03j!{vzZEg$uvA01EGBW(dMfvrYsJr2@wjLmF~ovQj~LW^Mobv%?;`-yZR4?~VP@ zz*M)o`?B=We#nGH{^@K4q}_xG{!eG)Pb3*1FrjCZw`+KSxt5e`oiHiwb~5wv-V0#7 zYY4s}{dJ`}`|(C!m8zVs%}_>f0pD-%!!Y=d@MMB-p1cM_FbmmTf3bFyo`@>U?g=4Wg}nQL zwemR_Q6%3Jjy~hyDwAovo;SBLD)xk7J$fFw!>OSPKfb<$fc-+`lrHZBqrxx_Au2#T zDViZK6EYKT!bPbR5#O3Wb8~G}WWM@Pl|N&`rBQQjWd4%#6P>di*B1-ToWUm;o)-=6 z9g+=MlkB6wR&8M49*N@Gc(PZ;sJ6$5qD1 zS#|CN73s@|z_e>bg8pM;KB64rSfL^w)--i>)TyRHF)6+2-@;Hn+7q&X$M$_TOxXyK z?3NQi!`tE1a>+X!&oC;x-j{D5VcGBK@s-6~u~7W+Lw7z;=*o7^?SUocplH45qKn3u z#VFw5K}E~baBg5`A;WdcaH$^I&CS#KHAT!dYBXE19a0cd-*tul1UHT~373p|{^Aa= zVKyCpi4#1<)h-)fc3EEBcN^PtwA0AYu479a>oej9Xjn);@J=x8b}x_!Pt!)-pIhOW zL+|I@ugVO;^(rzXzA4Ro{^&ePZ%p^V=4GZMqAzwXj6iD?0J#UziSO0e$5I)Ot^6Pq zE}@5dmv46slyo4B;g^tC1q>k9M()b{>?BIF@_n90X}$aII^%~Us3RbUQ5#A^`tJpK zD%ti^^FVw~wFjw+#ObUIaCOzXs-4)aU%#aotSLU$ykUXsTY-|*7Z#R7=O?ohaP8PM z9-xwsdq$TBAz@DH&0Gu2ZEb}w@xoZ@0guwzDr{947Aov6<3M{(nHSaoqjNhlW&DUJ z?FRujCr^=Ko+pLi?h~JS=l!;>#`NZE`G^CyXQHV| zwNBYX%C(Gds1TZF-A(&7T3!0;({8eeC#OBZgJcu9Rgx_GQ|czr_rS$4j#UK%6$4a! zxO4BZxohD+Tk@xEfT4?Z#C>q!;HEzPI9^7UoHDT;&8Y1~|NXD6%MYD^=XRH2JQ-D7 z>=_Pst-Jxrc~Mn)QKaUcUKJ9qSMD57uPZ;j=k=RI$TdCkWBtuj8O|>LvSL5g8XBZx z`WLFG_$;t4e#XpVcdE}rknxo#+kxTEW1uP1u5u>TsV%=!#gO@N^f6cli)A}m5NAJ= z9CUeoNcmbCd}4S~ARx=0u$k^F`-MiT29@v0Y-vg1k`GozSlx_A4BH2ve<$HLhipR!11aw5bICqtfaZ?KMw=v1jsKBVXX3WU^Xs=`{YsjjcNID6E zrD_XK@*4vn=e%2!E4=bxRjaJ{Cj8S#DUec`^phhqr%|%j7i1ka2b*X+hkm2hDA1N1 zb%k3R;dD1t9ljvqN3`1yedwBhSK`RtMiZ}_^K*7C3q5lj_93H~@P3ou;DW^{bRS&p z>m|$}G!?ijKm~^wqw({IavA5ojMaC}t6sGFpW{hu19HKA_t6`~jZ?N-Uy@U>f05Pw zaUA}0!&M`uPpI91JJfTSfkpZtrXVBLuz>?-roteDNh!OKA!Tr?tBU1r5d^U?;DtL) zEtd;V3-PWa{IXseiMx#yrC2Fi_~7UD6uu``$1MNqxuEi^D)?$xvamV&Y3jDQKcDAnudv9#$WK>l#u3528d*%*iu-fI@kvYi8$w^bSmvh{S zM^hvDPBHvkGhyhr=L_WIDY=_+#9Yng$&?SQeQvv~BfkdhY#vz~tOgGaeIw9h&U_CO zv_IrW8mRcfU}}F+)q94yWpZSwus^hl6EGgPi{Hd!`GD#CK)Z|ZGolS#ygB6=iSkF` zzYdj7usCnbu8u(YH5h*zPJkI(`U%Pp(TQRQ-x8I@5CMCu-#PgSJgM8>Q6P=nh3rop zjURmpm|%P@uF4Cu%dXnjQ#aJTUj>{vs>I0#1poG2L%P}#HrLu%V?Sp9+hzuxeZ6=4 zzT19sJEmkBL*}MP{M&&GIxWBL4n`EvzkFAJihpDS{pt5b129S6WOmzJiF&WR7or05 zEYiJ~aKr>{Un3c)mDP%qY)J{jHRpK*YFp7O#6{^zDQ0y|bLu|O!)HajkE!iTTG=WH zLoq`nM>M-5ZP0XS{K4AETB*%i*03~ zppv~c-^4?-D97`JXuOLm1T^Cdd@G1PDn3$OI^UkV4}P4^R+)EwT{s!((Fwqx52oj6Caq_N zG%Nxli0MP28UQ`9m{_z103t0)(d96VXF3X+?X)sHy@;b%jO9xVYkDHa9_0-;#;x53 zd2TOZwQ%xlKrhT4Z@&f=;`Y%k^j65@kfU0U3wf0y8cmXhOkhO|U#5-Ig3C*FWXCa{ z+C&^Ayn(m`vUw>!8I8vJUQ$h&i)yfVldR2@O!FGs-4ySVoMJqeSQ`zaTG&)`QetgR z{se#Hw=WmJjg8_B>z^pTg6P5Jy6QjZK@|`^n7i`_J@~Uy-*jZI=Xq+o?kjFxD80B} z!(3OvI-}+%Ph!HVhk^d{P0kgNSjk9!CBJ$E+ErIeAcZOYc_;5%4i6Xi2xd`Q zCCwX)2|$ueW~i{`QEZq>eKl*81~!z{bomaFEf|NK6^zj~%UV_+?`;ITL75m@W|4R* zd;Q_jvn8y91$p$$B;GuFmDY~ebGMS69$zy=ZUVO|&UFguw7PWAEI=MTXDo<$tFI*H ze36Ma3-5wPB!C8=c0@v0SSPba^I~ou1)vHvvs{HsH-guVdcW0&nD%)`ofH%l@K5w# zJ={ZF{~*DI%m^uw%t`x5`+nQ_01NsCfPs8(-9utROj&yoS+8u}X2+dUJ!W{g(!G*j z9P6vv+&MpJ%y+j%x66iXEZ&0F8ZDuo z9x|VvhF?_}pF0W5eQxEfIKy?RMmlLv1~ZRsCBS~pIkrafV2EvV*nd1Om^-zqs*7JI zQ{eb;L$!>58 zJ@`9=V>+PiA4zB3z*ck5Z=4nC)${Oj;=S>PK$2p;NkSy z#1|3;mCD&W=ihbzD)J?D$5`PuF2z22XaI&}RNiT+KLvsBvCuKuhvlZ3x%ujW*uMt| zz{uF6n~< z1Y6StoK|ZGzdH>{Ymm!+%D%j;MhzFgUv^U$8bSNE0LNd6gUpS^w6t9H?Q$=r z#l_SJ+A9J>0CAsstQ7c=R)ZO?7Y7;@gndZh&$(bQ&8uEoGLdgij2}RSNQZIgO=<8> z2^`O0B57~k0$s7FH5Kp6i<+H8(3~$#x$mo+FJ#Mw>_$}#iKFc!ESU>5!uSdjxYo+k zxo?x)?)1rW{jrYdY>XS>2)5Q3xv!f7VX~9CAEQqXE>$_sTDN^OX;eQR(6RhL>(tDV z>X@(e<3Z}_L%$4ws_k@td*7sx6Qh$mtfPXep(T|Jc38bEr010f6}hX=`~wn6rQj5Rl3r^tLp>#3TP-1(QhwJ!OUS_3gv~`8@wanRAZ)A`Jx!H z=pI`6*}%jg<+})UB+=gx$7W$2L8epnZYuQR=0H2wGdTQ>-;UH8!9W>L?X98p@mcW& z&ZdN=d7c0?)l%G{(}L3aPBIpSi5=)(8|W(5bB4G1PI`aTm#(!q1+S z!IVB+)@sHdsLj_|G~XRA6q}J~J{J<`&V4}{(guNlk2aq$0O3ZT5nbAA8HOIHo84$rug=SIL3CN`KX(Pxc%p}u6Yx-nK(%`lz&xm(L1 zDC~lNG~d_Y<~S6WbXjh2^LV0^!wsSwJ)cl#^!jbhXmgOy((>HGgUaHV=|} zRRfUWdtu&P?|5sLK|{GXnn*7x)3dWrnl4$@x^EwOK3BjZ6!mo9=y}ecN-Iz7G z45Mt@0-L_PN%Ca=0B_${Wvj>irS6lJ_wcywx6rwRQy6q?9PVw6xHV|Vs?(@IDG$AmE z#bY#Vgndsw%T0QgLL#QR4i43NVp1N!s$YLY>x3|+K(FK_P~n<|wjh0-tP5eMWYVlx z8(+m_2hR_%t3Sb>iDxedXam176BH5*X@ht^^patI7!8 zMWRB6yf%Z47|6w|Fj-u9hvQMjFB(WRd%Jzd?kw^`5_D5V4HmdlAzH24g-wp9GKJfy z?hmu_bH>iw^YcnPEgoAk!@Qs8V)Uf(K`@_q0L#yI1K-N&mT611D}#)u84D?Pyruez zMr&XyV)>0SyCD5}P$u><&YUMi8$0qHhrRx*qi5lxLSJP?2lqj=6*FPaJo)ITRc%OD zrmW+^%MW6tCO_#FP~s#D z8nRXvt@J2zJql4lIzZ=Vdl0^Eox)B8WCn_w2-G{4Il?D1tM^%l476{ROmO&$#G0P@6xqdoT#jd0omB44 z_-Mxp4mW&P$9s%q7H@x%Tv{@r=LtBh`(d#Ad-SwPZbSaXD5K2%PeCEQsx!2 zoD|&JBK)&&$=GDB&K9cSoRcfBjs2s7IUAW7Uvl`lPtU0MRml%DVhpU6d{leyfZGFU z-?n=8wFh1mM1;8jeU_CvJlFLZ3i%^uDa8+|Naoep&7TW~-N8qpt*zNvk?4}WV+zuw zLs-K6C%CO041P7CpC<1x)P4ML+F!6_{nGN7x=?p=(J}3lG{^H)YlLocTz74QB^ z_AqfOzS);AM3b#1TSBusfaRd`4BTatvG5B|aQUB5AXl?N{h*spH}x*cO$ zNxc38F;rG%^Kriv!u6+dWrfqQoBf}(T=K03hM7^PhJHOhVArtuP}r*9#5ha`wTsGJ z%N#VksjVm_&$b+xPff0hRGK*AK%U6=#fcpzC5%fS`=lop9HJm$vLur>_sIZ6;(2-kRH`YGSm?L*Ed6$PGUCc8KlJ zDRHuu+!n>uN4yxN3g8Ve7(3o3ZJ7W_2ulB}j6 zltS(O)^QYJ#2%^f5_^@V)vEDajeRWkvg0l?2gc?xwXREVf)XAXLoH}}?g7BTKjLZl zcd47XQa8A~w#%Kn%nUxB=C{s0HIC3j{5!=c>B^MhtqoPpLc)TpTpV zy^erkR*J$K+(X^Ihit+pGle4c1&tBHF3J>wDPCd#LPEuF0lwTZt>ZkHXNHr_5x)94 zIhb8ZU&nq1gZ2~OM)vB~Ku0&s{h?RfwUp~*9F8w7NCKCH zObv&uR;yi)8$85b$!&_sw1DZ0n%lKNZmQZbgNHsEj4D`JbGwBTQr_;|YOC#Kbqj`$hL*nOf@@(XVC{>X{CIb*WQ$m)o+F)kPs+~xGyuTY_PIIr799l4`bKDu zdsnN=zR?Axeqe99g|agnRQ{-M!O1~V)w90kloB906}?iY;7E1E#23ijfd5TSah9m* zE2q*UXka|A{_o)BQtRgh=SVves>3v0KKIZEM#dxHhq@E4l~F7+Ob@ESE}bZeAA7Z( zT667*Ep0;R+M76+xZ^uVf{KJ@b4hh>Nhm_B>3*;7TDyYkV78jyZ@0x=`sT??TP%l* z44zvArRT410YK0=IEaU`((f_~M3%~$RWcPuTYwu^T>i8vbYKUv+z)R=$<^;L$R6+% zS(ekhUdU5&d)X83RPDazadLG$fzi)q70Ih_Ix1VaFaz49qsv62zR|iC#0%d-hXcui zb^MPJWW~4>uLdMsJPO$1kk6njK1ox{h%jBxjSM<0Z?TudO~SDV@7T`xVo+^`A`|GQ zb0zd)iz2T|--5#6_w0?k>o6r^OjCl@L=~6`7i*6AZtVe(G03X;WYIlwxjj9OsdUBe z_nYmv`7pY=aIj)5{mRgghO7YkQHV#sk^~vyTG- z8!u+f1qO8|JP*Ccvas8tdroFt{ZjB8o{+23IKzA%X6{{c$)lIlG1QgLbYskY4~Cn- z%@b61JzyKN@x6p7PHh;&nx(}u_8=nyIV9SUuk@q#=^GeW#z~T#^Xlgh-Tp$Ib#AJO z7lnAUu(T=^=ix(%2I|2HvwvrZN3oHOO;i4{pFk`!+?_@0b zMNk7ZHAe+QOza0^We)v^wAP>b?2qm@TRA*3tI5>@3^a7UYi7=p$|RjpuT@s=J7yD; z!}mt$DZ4J^hj=B4&#Uv}83AY6XEaksWgFLuH4Nh24y*9?mgoSpI~ms6F`Zph@=B%+ zm94r8Ca;G5Y@>OX%Bru}{B z%{kXf1o+6+H4AdisYK<{j`zWz`3h7&B~EyUFp7#pyf7nH2dvQiHa2c;3;X&*@E_~3 zZ^6G)2R40!c&=8b_#-m0`*Yg19mS8RgnL2?1PY_DTctaDX0CycG}w3h#bfZ}EZz02 z7}jAO&9G-vo|q3e#jSF4>Ta}wE zH$ljsEIh7yR9&mYE$yEl^77I-7LI*kKMzdz<~l*y(e1Y?zmKo?ND~TlX_!}Duqmc0 zp1k?w@tw|)_&#gPxJ?nFgj^iJ+E3_s+A@3BZ#KDs9?pBLe;t3yX|9&mkB079=k#Lt zq3+B+K2sL6DA|CBH97qnAG%eYXL%L<CK%Sn+C|jji{(K%bXW@d z{FMjuCkwKST1FMJ7>M}_K!bA8n%pYgP}B~ZzM(#M{dsnaTY8h&?$eNnKY8g=*(=pi zC>#SE@xNBl*G`ApH+n*r*L7$OXkrS6AXG+E^n>3M6o>FIfF0*2B{drCbtW`;X!AoE zyhi(&DVlSk=^@zxFoR@1)}iqVlLG1s<7Q zPNM54JD3l#`Z$=)e3IxP5<@Tef9<_xR8()@{!N3VA_z!}NQcrnG}0nS!+=P4H$zBB zDhfzH`O|DR|5*LvPuyqsC=+3VbUpXYZU=jVGIlf`;h zmeAR%=pAnByy~sVEO9R)9^(Y?_P7 zs#OdYM~n1T^;5qt z@-7d+sG-Bo@xAriXg;B1YbIar#U9)V8e5O#vs_u^BM2P^v3?YmYl}oLS(|k_cDa`( zWX|84evDCy#NVIJHPW%bSghtHB=G8*Z0s^OHSXv!nLLCwdlv#3YmYO)7Bq>< zB#mWA1&FrjW7_y1=F_~;3vkZ{oohx}*BEuL_zra``9d7l&xM)c$GBVDBnbOUlfaPq^+sR9v5`p9e17Iy@{yE^obU}L(q*z zyG?Wx?jSa5m$E8jCrr^RIKh_$<9lR~h84ITapvgGurn;Y<;EYzUsIX1b&rncw|&xn zV;>{jX6#*Xtzr`cCL8Aa$a|&)802niA`@!r482D$c49-%2->tyfF9R&)ETZ{LtU;` z`slDu6RGrgHR=wa)@^>i$`2dMCujqkIDJXyD~!y28UeYsu5m)cNcNC`PwV9_>zl>|j^Vf6r;P}!g$xN)NY zA%uZ`Lol8$UvPD^pO~N}r zG5xl}DH!{7woQE+Yf}SRl0{5X@Lx%t^)KDiD?oWDsNR<8W;^aK-mfls@@N&}T%0dZ z^YGoyOJDK+2TLv^_-Ukx!gdOt*MS%FW#vsW@dJUjc8{sUUYugK&Z|xqY#Xo$dil@! zjvMc8VAQyYG`E1Nm-v#t$jg|blARTzEvBPnvsz3Zxz zCTw1E!lgfY{(q#ukgJ!qlFmLr7rnAI$oz3B86s*c8t=idB?;{-H3WUb4$<@_U$&CN z50Xh*9yagu{Mipj|)Z}y&8sgKSk%sgBEpFe1U7h4d{L$Y!t+oYUE^U~h zCbqV$JaW3-s-IJ-uDwV=*mKv!!}X+aH?YF=!S#~Re9K?i(A>1lkrn1{YQ^}7>*1yi z0`cDJ@(QUBYa#zEq3})q&m`>7C63_vX3l&|fu#@95#*NZJf4-u zYfI1dY@{g|dqh;;EVW@J8FATED#hAwaQCNg0{q5y( z-5y8GJ~21{ihSF7K@8#34$s9;Ds`*Z9_K<8HVf#pv$^&zbOC|87?**(O8l2-`wcDroUI2yg|6&~Ebj$Zy)RpdkG zP7>bR<}kbD$48hgdxto%L8XmssBE+urQaU|-gu&S0~OP}r*5V_v@nH@2uH2_dL5X}A|>9S@H@Z-&>&_`e_Y zo0#MCz1{iuCFr~a*dHvL!?vu>3l>kcqk=}+TmhJ{^X*vXx$_q&ejq?E9tAO`k z+-HI36!UXkxDoFF9{G3rZ6KO{?zi%(cw{rEvCT+tpAM}d0&;b@nd(IDA1>d}KEFHA zX25U2TG`0D!`<;anp79chnx^;;02wchwx{Cr$4EY&@vWfSUd&(VDx;E0~PGBTAZ%+ zB6*Mm_*-kpYF^M*wT{F_nSBYu^~u;?ncq6^5Z7Dfp+EX!cJIZt3T|%@lhTU3!Ir=p zwD^IMcxbzcB(9iBUM_Nu%f<7({dA3OXg}Y`Wk1L!jK`hdU^E3G=R8vZXDo_)Au}}c zZ+>l*xgfsxG10x=WrC!dM_$W%wO4CRd!i}=CjnxE>Y^<`2TqdEF0>|eyiz-#NMPM- ze{{)k9d3b)K3bFCW)%=BayGf=K=2Fu?|I@e=&VKyeR$-@$&q(o&vU8%V$B%W2)Phy zkt8+C8g-JeE*E8a)6H?j(uJ`x(mIx4`J3Jw$+DbT{(QLn@F?2v^bZx|G%of7ZuQP4 z9|J0hzLvd{(f41eE@!7+e*#^K#5vk3{vyZJiSLCDb`f#8c;&-x*(X2Q_HHoise#Ik zz`Y>00nM9FaxQe}1r`lMOvOzc8WcznkBrM3w-?G<+oWjHmk^u=;j3KYrmoz){7L?} zS*(xP-23>(g?^N*Y}&{10*qHNu6N-Q@1uc|3hgF?L9dZ)8Q-`A<{iGtW${Icb?~e9 zF#5U=*7Fx#vSkYfuTS^LjLuMJz~nrAq`lSDyrT>7Iv=!dC7t8fCb(S=R)1ZW3?$x+ zA~_8zyQj79=&jA#Et^$Xk~3AE(=nn6P(+#BdyE3}L_UI{bwYG|O6tJ-);f~iubJ7- z9sLVP34J{~cwRkbw{*@9jn;Yi7X`KR;cEsmQk%LC{J09WeTO*m`Jo++dGsydQyq(w z0z|z3N|+7#pGqZu=Ol!*Dd`)X;HVKNE3FpN3nea(f$~r5X_tL}stPKZVPh`>^n3js zE@3{JCANN8YWwoEBL*>>k%Mz59N!o=TM;{7PXG;4cAY03*Pf#P_I&?j%PKT?mI@r7 zh5mrb3VMkJE^PUwFm+ha>i5T~hm@FZPZm0Bwjxb|1icU&rER`}?wQs8X2RImHgf4x zyCDB>Cr0g29ZYv$tN3AY-t${c@?6_dfrdqfAkL*!V92Q585B;3J|%$II#%EisCzPBEi5@~FF*(9k> z%S_=M@4|AqYv;Y#6z|G}mSyuqm@5!sY}gNG=U|QH-Mw9&;%FLCD^n0`Zw&-SU1jBj zzNd-*I<~vU;`h0(k~f*T&h=Latde#85xerB2F zM?kCggq{W?(^vMf+m75iM7a(5?arw8LSNy2b(cT<>Yo4btMgrtfcw?S9{UeeDIL>t z3#=d8R4U{mO`!>b(eZl-CnBJP_|YAGlgBv$*w*_?A=T)wz5)Xt=$jm2f_={d^GHGb3Ip|XlO=H|v0BW4hPaKiR>xye?I(d$q~)7hAGDZH)G)bHxq zX--$mQTg9BobVjMj-->(!!M@Xd7UJAIAHd9xu9w=O+&erk4E)adhWrQ`)?Dv=e%2V7|)^);qf0uw5TL+g=0 ziL&MYuSD6UC%G~C$>&Elwwco1N_5ck516l8q|PIBIte>Q!pv+k2`)Y$3}#bez7C|Y@^83& zCPJFEFD(T!^U(1JVy~CzUV3|Mp9;*2?EUhtY;lx8^OF^ z{1)C}w(!YmMEkfCuT0gJLyi)j=*_K7!rg@K0#PYkPE0BMzrvL1{A9{(a?f}O|Ie7R zTs$QO-Ha_dQZSWfduU!WLXOKBZ=*F!n^{?jfvFtJuo`yN==@dn@=}L7X_+lY(P%%7 z3%AYPo1sRnPKhL*KSJ(zJM2*ozFaX5e`QD zf~k|%XWnSvRh+ed>K_%Lp)tJ4_!#unzD5)m6m}zpK_8S%L|wXv=hD%X{NN5-L07-S z?;saX8=lS0A9&M`o)OsK0ms>zD%5Rs2f5!rRB`D$^&uufR1*o(tbFEPtKB(~Ut)qD z0+3FyH|X`h8bcDe7~)I{p1o0K9z7w#i$3{V1U<(w3ui{W#u+3$pU zh(_x>*FK9jsZ%$#aJQ4Bt%MV5;MlJ4FGjsw!-WcHIF-6oH$*#At8dK?efSq@%oGP| z37xR&`AZLw_Up6_TA5A!96(Av=rD8CzP!5V<@EHZ_}!EH6ujDGY^-$;giW%RB(Beq zpXAk3zsttu);8~l2sykDC(T7AQv9aQ8Cb4sU@KOvIro6T#KgwN5D#Ww)SP|+l73Oa z?|AG)!li;hsbTwq)Q))N+gx0LMkfA2!OJgh?6gdClg<9R9$M-I*ojGHtC@zpTABe% zW$B`MuTvMwpYz&z;jvQcQdQb?@I24neON`4vP7-t*1eLIOMXfu)l7zKHSlGiVkaqy zqw%&f8X?eK+9}xAYBo<^tIXj_Pe!;fN7lRPs0N5fn9=Oh6#hs2>FT?DfuvAa7!zL5 z=a?=ZR1^O!7>>-i&HPvtTBqUq2l0EOaJul;Ze6VmY06|IE3M*r$}WHVoZSCaoD6KM zLl*v3rY2^&a(=3HCdbGspjcNfX|9Wae9Nu$FPUPnBYp%0NTRY)sf6g|UY_hb%0yW&~uIk{gM!tq!DE$~OC8 zJH|;Q`RS;p7IkX}|7m#1jqSSG%X&twPb3Y8mRw7cJwPH!kF#^UYUF4yi;vbNPrY0` z3SQ=;Y|V?M(a-viLM2ZHjJiCd-p4z<-1in=zHJ9YhZ7G5Mck6x3g1&C45>!L!Th(( z*mh+UGP&RYB3vU1`&W&~;bq$;Er;wB)|0VjAlU@KS^V~rHeCpKL>ki z5!D9HhoND8q<6OY@`Nz%d)*T%_|yr;c>LbIK@cw6xT470N!4fW=_ zwrM}RH+Rd(oomX<(EUL_kG#|Qo{(&nXSx!&ew&}Agp8DQCub^BFoX|d%{>ac*RV4$ zw&y?!bK3G%OnvNSc<3VYmzG(mOpZS(j~b8S+-Jl|VYwWPXTr*y8M>>NN=xbxL{Lug z?%ke}6L?#Ln|oSX7k}_sCh^s`8InO~I}OKeMcv1n-`TZ1A`8S(b1O23Mtif_uuCfm zJq{yux{Rt|CEii{aJ3GqeV8s%YYq5t<(0aFJru|t4DEwa@3!Rm0J|?eLMu41>_*?1 zl3E2wjlF>Un0V_?4)C4h6NHP{bXnl~PS)Q2CU;!%hfD2Jfr33hfv~i!_KG5R3Xr|I z+Q>J)+dZ1Jil+zri&trD=upL`YY6{9Y2^wtvB*fwBH^tb7GW3R>|G!XU1m*eiyi0f~;L<5|jZ zdMt3AbfIFJO4daD^b10B(J)~8lG%z;3XXS$*AP8bud$hkF>HTUxkb#TvD+^J4v~2N z)^|COTif^S$O6G>^jDaJ)@EeB*^=A#f$hm}gtFmrrExj=-BmPNtX?tMVh}xt(%#H%Tm)P?e1mr; z&t@@oS^ogWDpD8P!*~=edn9`}oR!amg9`epyQmDW*c6=(7a7eTgH>wGB$S=Gf z=UsgMbj#TjcX1M-GeEwMIIq}|$1s#C_sVUpcVXX|u7HT$N5j@io+U=NM&2ow)>Gw9 z5h*o0g?Iv~c)U%G;!%#;eSt?OBaG&@TJp1kyF}TS%+G{pvf79fo0e+3W>Bg{#eW1A$2#v5q;3;E|=L zS${^Bezf)8WG*BFL&o%}gdaAEoPbEU~l*0fA#zMnY7T{K$EV zy|$SB^6Nt{kt50XJfrsn@s|pQ%Kg;E^j7EIfb)g?v&~YGLunD@LlVCw;g8plKQ`L> zPP)R*@FL1$(!H#lG&Q%rc`|8eUm{xk&PJtj-X$C z5y6Lm6l$TR-Z4(*_-9QOffa}y*|h=asCF9?===h$u#F%3-AQDup8Epn#TT|gmX^qq zz1;6A*|gusy@ISCcUb7S@n41p2Up)7-aYZ!-MqGSA02Tlvpo(eQ*=HOn}7=7rKHg} zoZ+e=c!yniT;eW|e~g7eBz8EYBfJ28%n*uu`W3VU=#-DUnwKZ;%m(ZHP`34srQ`2e zi(Qd&nrC~|QUxCy^jjp6ixAd2z~YgXY`4cF>&U5LleRH6ZN8snXyE~+4}J!essw4w zvA{87UIXEGyxYQ5@eRwdGdKLAr2Tc6l=<>2@3uTjVeC%BYlfD1_i5T^;wa}CIO)GU z%rIk`$t_yKC42Na5wSHFkT2M{I49t^Hp|OvD}aZwS8Bcmm_u)!+N<|`T=hKB+F){H za-mj`%PfqyOoRPWC3O7S$W(4xI5a>hF~aujNnNoYrBEw)Kt^}MDP?>*W!X}!1XrQf z43W8uzuh*xz(30?CPLnj*_?GYqKQ+K$MLnC+sK;9E>@|#`jvJ64T)4u%!;R?mC^XO zUZ`_;SpolbYKKXTVas@c(lc2(ua2eQ{YHbS&x0R^ zI!Y<=m4qnRzGf$~LukyoJ83_<@hLOr89-=AEEj3}H3+%*_17atDCl)%20!=#9~P$x zr|=H~YxM-}mN3rS3&)XE(fCU>j+BB;x~1^mC{-cqi+oqTMZmbo&*kNie8TbEGZB&X zT{J=e5!N0#nzN9+8WH+kHF@H6y>=nGm89=egNI~X0{?W&8uTT2sfJOQS>)$eU5@IR z@?qW6sNUdmQEg{Aye0i$+RXQ}D^@9d{YO)5EhIQr*5PSDxcjT_H$9ALa~7_{{BM70 zi@n9CT>6v}9SvOHUX_sfxb7>bd443`-3VC)YzQ&t63?cZ(j}Jk{?<1em_-VPd%($) zoS=16HplUn2?@&qfN0Z=u+3;3xpw4OE8@MtmGX$fp;<|Vv(BaWTei?##XL*XTyL#{ zJA-@u?+XMN6A9@;-dXJTa6K~cJ@$mOJIg6~4O`UYH*N-(!)I^O6^(m^OE?}^G7)?ka0D*Dn1NyLJcX$uN&E?OR$b7j*uEO z16P00)E4IZH?)|cqF^ggt({1j1ntE4&Zm=7asHAC)ALxo<_Yv6gw<^FEUQ$U1r3;}Ki!I|np#Age~;0Hkb&-T{aHMl;i;>Ze%!zPjLJ|(V+75nKs^pD8yfZS9UwoKgbg_k>IIsSbpSxE?^KEHVj2WB% z#r=N!zE|j0vc}BM>Wo`QCwC>aB?Ci;gE4kywQIH;nn|Fw7{%tUPgq~va5VA!$RTCu zD`k6y&e6w7-u(*E>VAd%=gYATlY0FanJi0l?U4tOyq{!ANaMKQ*x8j0&&-h6@{(=0 zaEn2ZI@83nMh8Czc-y0D*_T3jF9_OOu2NF#62EbA1U9rrOFM%4Z}%pwAD)u_1%xsG z0))Y4B?wPMh-Y3C2P**?RFiImJ&s0Ga&2T!Mr-0o{f^mGP|08btSOVhM=S%0k!mAu z#pn%-Bk?nL;W*^x7)qGn7X!D9dZ0I?9TJx3MX%1)+Ij*h0}QX2orUe5XwxXN-(5MA zX2Wo}zuYiGMA}FzCLa+8wz!_h*ez7=qIU2nE=D1#H)NR5eLo%x0eHETIWIprON=>e zXDz9Et4EO7bxMcZ{G858@I78Tjk|^kZRU8(Fs?DF$qWVs8^U|{^YZed2K1=cFZ%C9 zW?#P2#vHDqrNF=-*GIL^=6nY?2YbSMCyRyOT6fN<8L|zVL-}thFx$z;PBu7+g-#u3 zny$i{M%A(?lwsIV10!{njC9CAwZ&A#apu6eV`I{9Z8LSXxEbdhe&b#t+^7FK&$G#}3y; zh%LN5Te~90Z#<+^H#L%Q3H^g9j3Wb?$mGjclxr*EQq_HWAVMgMrY!Ckh9!rk^9Af6 z{)ic8lBmyAzwMX}`67Pqdx!a~zw(ZKbm~r1G1iYj=2$pJ)MBK&y`*AGCdRaR7~nsD7VrrLM2r0F zbTECcbei`n>ysGB{pNIs&gEqW;AZ&)tMh&waLzL6I6n2vz+JO2LU%KJq{-W+(>Vte zad?>4F+7@(!ZM>8l3j5u2B@5^H}7S4f9M~YJ`sn-Ly;}4_0(C4Lu&AsEsaW4w2vi1 zSmDn*ZEA4IhPWzaubte0utAR-iP5Yeg5_Ht2`8?r_&2N-` zXlJ$3Un8kI+BSeImtK#_x7XjA81MYDC!nk$A4I|@_SQr5PDa{M2B0{!>4ip zowXXBb6QA#-^@3Am38#_d}w{x9RnF$oU*r%=|Z&5`JI&Y+OhU)HI~$6T7Okv7p_~p z6X#v%gg^;oqV7vT^MiKC{8idPd(wma0RZcLN7kFwo+pfgm7lC=Cs|FM$^XBe=tM;Ut+|J-|qly$j4_yUfWHP(CD zYKuOZAH2<}>Si2?aErh#okgYIWvD zPAb}23RZP+&0MeCI@+x{dHlXd)*bdhyp)?yPE^`!;T_!BM~6Q|dp|Rxpd(IxY~+Tt zumzx^vy_}emPiws01JgZt69oW{lHZN%}m0hNEZqF!}SjVU z>##%ngLft5Thal2#(^`hrg3iBxA6*GoEMH7g$nswb;UnVvSnCqwE(T6z-pZI!0icb zi;VrE)J}U1h>X*&H1E58ycYJH^7X-L z8&jS0!nLlLX=FTKT3i}UE9NG9=%fUmOF^yH>FKV>A;h%eX>A^6ytit++uGm;v_&?V z_mFHcJb63qkvZ0asK62_*=BSKeoE5Qx`a7t_7M&ZpEGI&^373=>r}o5EIeg!#6U7>Oq?rr@@G|E7wCqDrhf*(q;D z&nfAM6&;wD5}T5`$~vD2#oS1}^IvVr#7!MV;~mH*p{t#=JnfJ|g2doEZ;_{Oc$}ak zK&hK$OQa0#xJI!zc(C2k6g79y5;nPQ783LcW@KtW;^xw#9$!$))vCPrgyG4Obb!Sz z``b^neuP+$0D@D7F%uz#Oo`1`%U0*kaMTMnUcuFx@j04&9$bxt;u}!VV*7zWa9uFJ zxa7^qy0X3Y?4aX=&lbN;y+5K;(Sc}ZHaabs7rMGapY!c)<}!7>_&-9^ z!2bVl(X>rQUs*+(AMvTEJF)%rx@^JnSKfB2chfjq9q8>zZ}WmE=|8>!@g@u-J+FNk zohPe5tSzUATHliFkW(>LASBRnrNc-fo{<70|tURI4Ir}f3~K3Q_N6Qj@>5|)pNzb(NU zt?ZvLV??$N-#qW|t$?XX87_@iaD z!sv^LCZbO`4*CZ_^ybDcx<^jt&?XZ19DCKtJOWwT=z;7cfAYwU?RdjC1S5NHT%xUYiS3NKcz#<6?xMYLstw zeycwbf1mlHX$tH5sLHy9^D}d)O}9i=TY{4*CBLy|g$enrRHI{dyLe8xUEK9Sp-;J8 z5LZ}BfOI$qg3$gcu zD}39LAAU%D3iA7mY(k%u)hP%u0wtB~_KJa{`PYd z*IS+vYNiImdgd#2Z8MX)I&uLVClN=aJ280K7cLbKOH#B-G2C_!pkb!{ysX`5wrl_5 z^+v`UjW#_u<7DsTY%3zO-1fdx;D}?7hw|d$_UKk1_UwtkM?FNmrABYY4*#P-Enf0` ze_h7lE5SG-|W^vLFmUdUAv- zinS*rI{}-RyOZU5^3X$Q5o31~0c=0t`(+$S8c2iwnhikqh6tkALP>G-xJC3+j=bE* zd6Bn1eYg~Dg9&th5pS+Ff z@PJSjC#6M1v9-fs{OAcuTTiKDf2jrUqiH1}j=`fTB{T)1n0_~8BM|uok{dp>gUbPL z-S~6^_-V<~s?n&GDbd~!8ytAPaRfLh(Z<8GUeU=tdo|yExhHK+bnB7ULPVzU=6`i& zBNry1!k&Bac><~bmPuw>^-i(2HXZ9Bxo{|fXC75Fe!q{b%b#(} z&i(1hmfAr69K1)YhMXk1%Cz4$#fUU8!=_5~Y=(RW$S$}vZO^C1Xyhd%~7a;)G0WJh2=^;ud5yMLqx-9jVzF*EVoibAw%$b&mLa>$j z^WvS&KJ;rAoY(c^?m?WG;SP&z_{Ch86F%sZ-}Wh}cGbqWLQ@%tX2`~}so#$Q^qc;@ zQ=8j`=+5f_0*2K}d1`ofUZS_K{v=Z&^*%**G#NhxY01Wy^W)ruZs-mD+SM|Z`{UOzFYJJG5C*uX9+}XOfq;8 zMgLpv#ry|ZhqdxAMr|EPsYPfR{I$KWRr@K2j#|GDBdWm|7;xA!4g0YW(iWQvEqC^s zN-xa{oX1pkTm>xhIir$)og5JmidL2$zWuFe_`@aB7t--Zw^uLE5IR3Y^l$(3#~7K) z{P1auvc%W??OFZ9;;kRS?jWTC>E+>n`6}j}5AOv_&$Caz*v0+i^jrtA@1x)GG5HWE z{jDARIRyXtslVkEU-$sdMYRVrw~NR<|CpT2`tCC!F)_VyD9!S3V|_bCLPc|NxARD3 z`(K9hpyq8VRN!I^RCSr#Ec)^;rOd<6U-I$Ax#phq>1lf~p6Fajg(P7myGja==t9fe z_pwr^W+rJUQQ^Pe1F1OAhtG00cZEg|AUj6th5Wc>hZK+dQ3L&LG*%#!^*18xW$E7f5`c*St+Ry42#AJtKgFeV<5Yz&$;%(TF>F(XpfXHZLFUB?3G_36Qx}wB1(^#_x52*7@M0*CCwoN9BTPSkmZR?KKW7&PHtr|p zN?R~4N%nTsiP-IRN)abYT|{ILV`5~h6xHp z)vm)|?dZ3pMcj)-i~+WBW-&NsQ3Job+v@!tsyfDUqKC-50-e!YaqCK3?etspj<6VNLX09Q zX3t5lqV(&FkwZp_Dd79OW>-)Os6NiZpa*yeG)>&=O!(hmU{YO|(ud0K+QH+vlh6N! z9P>epPj;=MTOj8@N0mIcHc}fG-kmHvm!rg!I92J^bbuD-F(>U7SuUg2yOdR}Ya!z1 zlve6=m6ez68E8X30@d#dJxqAMlPG5EEu--uw?(MCiCaE4-^SC6)f3j%b1Qk?CUT!A z7ozVpAKo`=E(sG!2B$W8y~1Q11gpI%cnv9No@)B+QDPHV*B12clRlT@9#a8j?A`fg zcpncFVdRy|{uHP>LCRi|eDei+Ere(5Lpxsgk)~zw1Jt@F|FmQNP+dzC5sghOE{S`4 z3hSI2H}{Gva6sQoz>>H|Qw+*bW#t^tDIzr4`8q+F2zuIU|PNJhl#kJ}z;_xZNnR2E7Ea|?Y{#I`x@5Cj$U?7gOf zc_?oL*CkjVSM6O~GG8~^KIAoP6Q^swF3)FG`cyX+w#DaZGmI6{50tswJ5w8>Scd>9 zSqhPG^@m>}9yR2x=9@|y@2Zoo-NDXIIvDkFF0artwpgA;CX`q1uys?qbDn4*7E&Tm zu(^>;m8cQXyv;{ro(1Xzzo(TsPK?5qSCB0qB7LoXS<>)RMO zY`BS3UJ#|Eunp|(uk*OP%v3S4SmZqv$xhBh?mV@Vwi6+DDevNWe#akjOtCbPJQ(W* z8RudIH)EA2OmMAN>7X+qki+i4i#Od`nqU_M5Znrw=$p^jQN}o;eP|wPCd#M2kK2XS z7K~8GqCu%G?lEJzO1aoVMT_#l0Vino!X#QlVEpJfz?R9K#hA6sj0|%+?$eVFB>xv` zO^k$W^Z>T=J-5NSc^%duEK0vE^egu$WQgDp#B&JJD13b=3=xF9x)J9ece)rJjIE}K zFWXH4lAT3@b-X;J>ndiQuTw9^+lPvD%dCfDwD(|V*DZB!UH1Td`QWw`6pZ^+B0dO+ zNI6WyyrxaccyTl)tjX7hL64O7w67DBFB|$h0fcwAH$#PY7e$m`z4UiFS~MmW)s+(g zkeX*Y?wKB4T+%L1`+LGVdL>}Hm(M-*qCb0t(3Cvt98ME=pDNH3{xa8=dR$HSbjG zi8L|kQRsX_aJ$J0B$()xngoOrbFU<}r2va+^N3ZNWb~}j&j~w&H?oB*%z+tPz^&ay zKxSTE-TdXG4Wo^VlS_07_I5)cHn}b^rzY0CXp!Md3I&-*;r>`adHTLaS>D%Ms6vy8 zpcPZ{w2%|z6cX8D+EYbITC+BLG>2=`RKGJ6Q{-yrX0@;#+0@y56I1}glvL__j;94J zfADkoP%sjyeY+4r@*_Hu!&8)Xb~oZYC7#C@-C8<24r5^?6Spyw33T4Biz`s2VwRA} ztn+Gl`k?~w#BDgv9c=hGsiOI`^{P9PWe{+C8-9L5Hm?G*xSa5Bx>sKTH1Glf6`a;G zy&g%PdjJbb(&rhUV>f_0hwiozVT;&CpmS(z5-; zXyDaue95hThndjSYH}#~-#1qGy>W0BMXd+ya_&M#n#uHtg)yIPPz|60+qwcM z3n;An@9ZV9n_ql1NZ5C?wNi&g99$)9r~|M}BpieUovj7t9Vfy&x+BO{0%O&X3gDZruBvK<`lU4us z75y?8%n3bo@c^)SgU-imaGmvVFPZve!^jUz;YVl(J=*l^zbjbXZz-=vT{z%=2zxli~5`}5%2My8{PLo>NAEyYYbicuz>7%fLbd=x#6h0ZjyKd^J1*8D~JT zaYKLLy7D4c-S}>3#g0h6$zc)|nPMNdvdB`j)1u~Wa8?|kK3JmWmURpQ9ZoC?M^apQ z?w274ExI22ZpVT`(mJzmR9uuOdCaz>j$SY97a^CpOh!iYObSgq%+i^ocDu_;Rs6{1>mZ@uBRvjE!J*K{7oZlmYs^%1of2!^%y zg0+WXYd~wAvysyVus}&uu%MnY=iaf4_Zdiq)ry?!Fpt_$ZAwo!ar5}-uT z={jH~fILlr0;qFi@|w~@x74lMfiwqjHoGUeT0;aSNAB%XVgT5fmV!hy!2>Fq6(zN8 z(V;m6HPMkZp4$F~>ovwix`OGR(G$5Ti-w)I=}m%MkZG&ZD{^hOa&3#r!n~$ZOVz2# z$?EzM@2kJoj(q~(h=>SO!>%v}kW$%oQP0t+sX%>8PLBIrO*3?%p~e%=Kv-FoIl*Qx z>o)x-$vqC%&%D9)EO$L^7uaXV{`x%Mu-i66g8k%Oo&EyLB)^W9>Q)v&cWDnLvZgFcKEy(}#5{me=lHBL*Vc<7edcZ8Y#EFua?(o=jPweL{4-Dd@hyzUGv$J;8lgC{w(d!vlzwTyp8MUgqpqo^KO8D>>KRkLpkzV?ynm}gsRTR zC&g1_mgcWBr@H^({sE1a`Mp2aXB3R5ME4Iqf`sHPA;iQEheRYqS|d&K{yY=-_4=qI(yAm(1^s*3x*v>zaq)nq>Yt-VVPRbKpl1sGIhDYV z^#BnOke(Iu&r$8+AHaMnrT;noCsa$)mE-t-Er$1BvQjP4QGPu8>lQ)5kU&AX4vx|K z=cqlXDA#MqqZI#~{(lAq=|6+=pF0KqDE{a1p%S6`&zvIipNaNAs}FpA{Lhk%@t->7 zKgHXRQuzPJik7`{w(i?FjQxQ8nsN6W@7iQ$Y=rfndKPHo%eX{| zb9&?Nb+^;9D3&br&vz9sQ!y{tqGP3~@0i^mYyDH9i|AX_y+OR2IT{5lQF@)grkkQ{rPJq9P^m*es4NE;gHYh7d+tO_v&czeBca&j$^`qkItHe=?Ga62)Tre*X{0 C@v^S~ literal 0 HcmV?d00001 diff --git a/docs/why-use-pump/pump-error.png b/docs/why-use-pump/pump-error.png new file mode 100644 index 0000000000000000000000000000000000000000..fa282632feeb0396794f582479a2c5260df2f373 GIT binary patch literal 67191 zcmZsDW0)pEmu1=PvTeL&ySi-KwrzLWb{D#A+qP}n#`dR~+1dS(Pd=H!jEubZ+;bu_ zTwYcT9tIl*2nYyXLj0#95D>`A-yKr$Qe@hEr+c}vw~t;;hW>upf-`ozN@70tR(hJ4NN?o_=bsHWU5^XQ&XE>t-K?RNg67eS%K+c1b2m5;&u#Oo(?JOZ7K?R9``|=n@;JXr1 zLGAHkaDP@_-PPdvc!q%n10KhpEa`$1DX3BKuTuZB5-1QJd_*u1;QMCR%eCii&+|$* zkMQvDSlhJ*i*jl50!oE{wDUhpP=fd=f&C;#=6O9xj0~ACmdV`O-27xm3#ya(Pq+R% zI}+G0SvhAfT~$@pki^79$dHi#?)-mpy?Bwx>*?u@3b%W+v|eu?>8hx~-W?dn<#q!_ z1bT})_9F2ki3SHTkVCjn(Sr&JuPT6!l`-d)$byR8tEA-U06}et0&nx;zZfn9&z4`7 z1Z;>qIDaO_O2@l5fc>?&WW|us?$a(_UV=GplVqCxzy{DPM z=#HwrEnXLcB~nQ6U;tycZ6>+oC-o(Bwercq#pYn#pOw`ye=W>8xK%G zQ}HZwseHz-za!GZ3MFnuL{b1X0U7OYaAi2fq)AgHVCw-171EhF3Ou08;0V?8gl5-2 zqsA9nn{&~!$X(QHX2NV-s0|*rBaUg@ZAx^GDi_2MuArVCk%Jf$!cw_n3)YS%n2o))grl(eHjLWyW zl~Y3I!)kyJzxsPh_SF9fYD`8`*Vkq(M&@m6*-rL0J;YD;V)sS(rF|1Bo4xm%g0635 z0*qc#qh^HdJCpcXb�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

+i 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®wvK$Y>}qnypvy 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 literal 0 HcmV?d00001 From 24914f31a8fe2125d9c66469d19f8e4b2587ecc6 Mon Sep 17 00:00:00 2001 From: Terin Stock Date: Sat, 17 Jun 2017 15:05:52 -0700 Subject: [PATCH 041/216] Docs: Remove link to Spanish documentation that no longer exists --- docs/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 253859992..fe01fafc8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,7 +5,6 @@ * [CLI documentation](CLI.md) - Learn how to call tasks and use compilers * [Writing a Plugin](writing-a-plugin/README.md) - So you're writing a gulp plugin? Go here for the essential dos and don'ts. * [Why Use Pump?](why-use-pump/README.md) - Why you should use the `pump` module instead of calling `.pipe` yourself. -* [Spanish documentation][SpanishDocs] - gulp en Español. * [Simplified Chinese documentation][SimplifiedChineseDocs] - gulp 简体中文文档. * [Korean documentation][KoreanDocs] - gulp 한국어 참조 문서. From 58b29452e37be23010e259e86434cc5475be0980 Mon Sep 17 00:00:00 2001 From: contra Date: Sun, 2 Jul 2017 19:45:24 -0400 Subject: [PATCH 042/216] Docs: Replace BetterError with plugin-error reference --- docs/writing-a-plugin/recommended-modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/writing-a-plugin/recommended-modules.md b/docs/writing-a-plugin/recommended-modules.md index ca1d7e71b..0b49b39d4 100644 --- a/docs/writing-a-plugin/recommended-modules.md +++ b/docs/writing-a-plugin/recommended-modules.md @@ -10,7 +10,7 @@ Use [replace-ext](https://github.com/wearefractal/replace-ext) #### Errors -Use [BetterError](https://github.com/contra/BetterError) when it is finished +Use [plugin-error](https://github.com/gulpjs/plugin-error) #### String colors From 5df0865bf62e4a399df9c104b9c46fe1532930dc Mon Sep 17 00:00:00 2001 From: HyunSeob Date: Thu, 13 Jul 2017 08:21:29 +0900 Subject: [PATCH 043/216] Docs: Fix a broken header in writing-a-plugin (#1984) --- docs/writing-a-plugin/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md index d172b9af0..c301b6957 100644 --- a/docs/writing-a-plugin/README.md +++ b/docs/writing-a-plugin/README.md @@ -9,7 +9,7 @@ If you plan to create your own Gulp plugin, you will save time by reading the fu ## What it does -### Streaming file objects +### Streaming file objects A gulp plugin always returns a stream in [object mode](http://nodejs.org/api/stream.html#stream_object_mode) that does the following: From c95e09eba1b96b71a27f0da60adfbd3f205e9642 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Wed, 19 Jul 2017 13:54:03 -0700 Subject: [PATCH 044/216] Docs: Improve "Getting Started" (#1985) --- docs/getting-started.md | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 1b3bab799..4ebaa5ab0 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,6 +1,16 @@ # Getting Started -*If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions.* +*If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions.* For more information, read this [Sip](https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467). + +#### Check for Node and npm +Make sure that you've installed Node and npm before attempting to install gulp. + +```sh +node --version +``` +```sh +npm --version +``` #### Install the `gulp` command @@ -8,6 +18,10 @@ npm install --global gulp-cli ``` +#### Create a `package.json` +If you don't have a package.json, create one. If you need help, run an `npm init` which will walk you through giving it a name, version, description, etc. + + #### Install `gulp` in your devDependencies Run this command in your project directory: @@ -18,7 +32,7 @@ npm install --save-dev gulp #### Create a `gulpfile` -Create a file called `gulpfile.js` in your project root with these contents: +In your project directory, create a file named `gulpfile.js` in your project root with these contents: ```js var gulp = require('gulp'); @@ -36,13 +50,21 @@ Run the gulp command in your project directory: gulp ``` +To run multiple tasks, you can use `gulp `. + +#### Result + Voila! The default task will run and do nothing. -To run multiple tasks, you can use `gulp `. +```sh +Using gulpfile ~/my-project/gulpfile.js +[11:15:51] Starting 'default'... +[11:15:51] Finished 'default' after 103 μs +``` ## Where do I go now? -- [API Documentation](API.md) -- [Recipes](recipes) -- [Help Articles](README.md#articles) -- [Plugins](http://gulpjs.com/plugins/) +- [API Documentation](API.md) - The programming interface, defined +- [Recipes](recipes) - Specific examples from the community +- [In Depth Help](https://travismaynard.com/writing/getting-started-with-gulp) - A tutorial from the the guy who wrote the book +- [Plugins](http://gulpjs.com/plugins/) - Building blocks for your gulp file From f1f7d77262c69432fceb6eac3db5314a90abd12b Mon Sep 17 00:00:00 2001 From: Erik Vold Date: Wed, 19 Jul 2017 13:56:14 -0700 Subject: [PATCH 045/216] Build: CI test under node version 7 and 8 (#1995) --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5d5228b1e..8cf5adedd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ node_js: - "4" - "5" - "6" + - "7" + - "8" after_script: - npm run coveralls git: From c4d219e1a2318b9b4a3f921257818113cdd026dc Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Fri, 21 Jul 2017 11:26:10 -0700 Subject: [PATCH 046/216] Docs: Improve link descriptions (#1997) --- docs/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/README.md b/docs/README.md index fe01fafc8..39dc8469e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,12 @@ # gulp documentation -* [Getting Started](getting-started.md) - How to get going with gulp -* [API documentation](API.md) - Learn the ins and outs of using gulp +* [Getting Started](getting-started.md) - Get started with gulp +* [API documentation](API.md) - The programming interface, defined * [CLI documentation](CLI.md) - Learn how to call tasks and use compilers -* [Writing a Plugin](writing-a-plugin/README.md) - So you're writing a gulp plugin? Go here for the essential dos and don'ts. -* [Why Use Pump?](why-use-pump/README.md) - Why you should use the `pump` module instead of calling `.pipe` yourself. -* [Simplified Chinese documentation][SimplifiedChineseDocs] - gulp 简体中文文档. -* [Korean documentation][KoreanDocs] - gulp 한국어 참조 문서. +* [Writing a Plugin](writing-a-plugin/) - The essentials of writing a gulp plugin +* [Why Use Pump?](why-use-pump/README.md) - Why to use the `pump` module instead of calling `.pipe` yourself +* [Simplified Chinese documentation][SimplifiedChineseDocs] - gulp 简体中文文档 +* [Korean documentation][KoreanDocs] - gulp 한국어 참조 문서 ## FAQ @@ -21,7 +21,7 @@ The community has written [recipes](recipes#recipes) for common gulp use-cases. ## Still got questions? -Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagged/gulp), or come chat with us in [#gulpjs](http://webchat.freenode.net/?channels=gulpjs) on [Freenode](http://freenode.net/). +Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagged/gulp) or come chat with us in [#gulpjs](http://webchat.freenode.net/?channels=gulpjs) on [Freenode](http://freenode.net/). ## Books From 2c6d551e089cb3c670548796f7b41ccd65529df6 Mon Sep 17 00:00:00 2001 From: Matt Wade Date: Fri, 3 Nov 2017 18:42:56 -0500 Subject: [PATCH 047/216] Docs: Recipe for running gulp via cron task (#2034) --- docs/recipes/README.md | 1 + docs/recipes/cron-task.md | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 docs/recipes/cron-task.md diff --git a/docs/recipes/README.md b/docs/recipes/README.md index b532750ae..52c3bcd19 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -26,3 +26,4 @@ * [Run Grunt Tasks from Gulp](run-grunt-tasks-from-gulp.md) * [Exports as tasks](exports-as-tasks.md) * [Rollup with rollup-stream](rollup-with-rollup-stream.md) +* [Run gulp task via cron job](cron-task.md) diff --git a/docs/recipes/cron-task.md b/docs/recipes/cron-task.md new file mode 100644 index 000000000..030fb334a --- /dev/null +++ b/docs/recipes/cron-task.md @@ -0,0 +1,25 @@ +# Run gulp task via cron job + +While logged in via a user that has privileges to run `gulp`, run the following: + + crontab -e + +to edit your current "[crontab](https://en.wikipedia.org/wiki/Cron)" file. + +Typically, within a cron job, you want to run any binary using absolute paths, +so an initial approach to running `gulp build` every minute might look like: + + * * * * * cd /your/dir/to/run/in && /usr/local/bin/gulp build + +However, you might see in the cron logs that you get this error: + +> `/usr/bin/env: node: No such file or directory` + +To fix this, we need to add a [symbolic link](https://en.wikipedia.org/wiki/Ln_\(Unix\)) +within `/usr/bin` to point to the actual path of our node binary. + +Be sure you are logged in as a **sudo** user, and paste in the following command to your terminal: + + sudo ln -s $(which node) /usr/bin/node + +Once this link is established, your cron task should run successfully. From 81fc26ddbc47ab10fa14ae4373850b30c92754e0 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Fri, 10 Nov 2017 21:39:13 -0600 Subject: [PATCH 048/216] Docs: Change jade references to pug (#2037) --- docs/API.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/API.md b/docs/API.md index d06906f13..d9239cce4 100644 --- a/docs/API.md +++ b/docs/API.md @@ -14,8 +14,8 @@ that can be [piped](http://nodejs.org/api/stream.html#stream_readable_pipe_desti to plugins. ```javascript -gulp.src('client/templates/*.jade') - .pipe(jade()) +gulp.src('client/templates/*.pug') + .pipe(pug()) .pipe(minify()) .pipe(gulp.dest('build/minified_templates')); ``` @@ -77,8 +77,8 @@ gulp.src('client/js/**/*.js', { base: 'client' }) Can be piped to and it will write files. Re-emits all data passed to it so you can pipe to multiple folders. Folders that don't exist will be created. ```javascript -gulp.src('./client/templates/*.jade') - .pipe(jade()) +gulp.src('./client/templates/*.pug') + .pipe(pug()) .pipe(gulp.dest('./build/templates')) .pipe(minify()) .pipe(gulp.dest('./build/minified_templates')); From 4f9465a420f39832d5ade015c06972378502f8af Mon Sep 17 00:00:00 2001 From: Gregg Christofferson <25208248+GREGG-CHRISTOFFERSON@users.noreply.github.com> Date: Tue, 5 Dec 2017 12:28:17 -0800 Subject: [PATCH 049/216] Docs: Specify where to create package.json (#2053) --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 4ebaa5ab0..057dca3b0 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -18,7 +18,7 @@ npm --version npm install --global gulp-cli ``` -#### Create a `package.json` +#### Create a `package.json` in your project directory If you don't have a package.json, create one. If you need help, run an `npm init` which will walk you through giving it a name, version, description, etc. From 4b118b9eff679ec76725db632a060e7b5ee5c8a9 Mon Sep 17 00:00:00 2001 From: Gabriel Aumala Date: Fri, 21 Jul 2017 17:25:47 -0500 Subject: [PATCH 050/216] Docs: Fix and improve Transform example in writing-a-plugin docs --- docs/writing-a-plugin/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md index c301b6957..0e0586694 100644 --- a/docs/writing-a-plugin/README.md +++ b/docs/writing-a-plugin/README.md @@ -22,7 +22,7 @@ Transform streams are streams that are readable and writable; they manipulate ob All gulp plugins essentially boil down to this: ```js -var Transform = require('transform'); +var Transform = require('stream').Transform; module.exports = function() { // Monkey patch Transform or create your own subclass, @@ -44,7 +44,7 @@ module.exports = function() { }; ``` -Many plugins use the [through2](https://github.com/rvagg/through2/) module to simplify their code: +Alternatively you could pass your transform and flush functions to the `Transform` constructor or even extend `Transform` with ES6 classes, as described by the [Node.js docs](https://nodejs.org/docs/latest/api/stream.html#stream_implementing_a_transform_stream). However, many plugins prefer to use the [through2](https://github.com/rvagg/through2/) module to simplify their code: ```js var through = require('through2'); // npm install --save through2 From ff4e7197a67cecf6191006e6096c9ebe085cac45 Mon Sep 17 00:00:00 2001 From: Ronald Eddy Jr Date: Mon, 25 Dec 2017 09:36:54 -0800 Subject: [PATCH 051/216] Docs: Update urls to https (#2067) --- README.md | 2 +- docs/API.md | 4 ++-- docs/CLI.md | 2 +- docs/FAQ.md | 6 +++--- docs/README.md | 10 +++++----- docs/getting-started.md | 2 +- docs/recipes/browserify-transforms.md | 2 +- docs/recipes/browserify-uglify-sourcemap.md | 2 +- docs/recipes/fast-browserify-builds-with-watchify.md | 6 +++--- docs/recipes/rollup-with-rollup-stream.md | 2 +- .../server-with-livereload-and-css-injection.md | 4 ++-- docs/writing-a-plugin/README.md | 10 +++++----- 12 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 8a5ddebab..ca49c3c2a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

The streaming build system

diff --git a/docs/API.md b/docs/API.md index d9239cce4..b5fac15bc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -9,8 +9,8 @@ Jump to: ### gulp.src(globs[, options]) Emits files matching provided glob or an array of globs. -Returns a [stream](http://nodejs.org/api/stream.html) of [Vinyl files](https://github.com/gulpjs/vinyl-fs) -that can be [piped](http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) +Returns a [stream](https://nodejs.org/api/stream.html) of [Vinyl files](https://github.com/gulpjs/vinyl-fs) +that can be [piped](https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) to plugins. ```javascript diff --git a/docs/CLI.md b/docs/CLI.md index a536c913f..eab0b6b7e 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -18,7 +18,7 @@ The CLI adds process.env.INIT_CWD which is the original cwd it was launched from #### Task specific flags -Refer to this [StackOverflow](http://stackoverflow.com/questions/23023650/is-it-possible-to-pass-a-flag-to-gulp-to-have-it-run-tasks-in-different-ways) link for how to add task specific flags +Refer to this [StackOverflow](https://stackoverflow.com/questions/23023650/is-it-possible-to-pass-a-flag-to-gulp-to-have-it-run-tasks-in-different-ways) link for how to add task specific flags ### Tasks diff --git a/docs/FAQ.md b/docs/FAQ.md index aa8886ab2..31e11df77 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -43,7 +43,7 @@ gulp updates can be found on the following twitters: Yes, come chat with us in #gulpjs on [Freenode]. [Writing a gulp plugin]: writing-a-plugin/README.md -[gulp introduction slideshow]: http://slid.es/contra/gulp -[Freenode]: http://freenode.net/ -[search-gulp-plugins]: http://gulpjs.com/plugins/ +[gulp introduction slideshow]: https://slid.es/contra/gulp +[Freenode]: https://freenode.net/ +[search-gulp-plugins]: https://gulpjs.com/plugins/ [npm plugin search]: https://npmjs.org/browse/keyword/gulpplugin diff --git a/docs/README.md b/docs/README.md index 39dc8469e..d72ff7d2b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ The community has written [recipes](recipes#recipes) for common gulp use-cases. ## Still got questions? -Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagged/gulp) or come chat with us in [#gulpjs](http://webchat.freenode.net/?channels=gulpjs) on [Freenode](http://freenode.net/). +Post on [StackOverflow with a #gulp tag](https://stackoverflow.com/questions/tagged/gulp) or come chat with us in [#gulpjs](https://webchat.freenode.net/?channels=gulpjs) on [Freenode](https://freenode.net/). ## Books @@ -32,14 +32,14 @@ Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagg ## Articles * [Tagtree intro to gulp video](http://tagtree.io/gulp) * [Introduction to node.js streams](https://github.com/substack/stream-handbook) -* [Video introduction to node.js streams](http://www.youtube.com/watch?v=QgEuZ52OZtU) -* [Getting started with gulp (by @markgdyr)](http://markgoodyear.com/2014/01/getting-started-with-gulp/) +* [Video introduction to node.js streams](https://www.youtube.com/watch?v=QgEuZ52OZtU) +* [Getting started with gulp (by @markgdyr)](https://markgoodyear.com/2014/01/getting-started-with-gulp/) * [A cheatsheet for gulp](https://github.com/osscafe/gulp-cheatsheet) * [Why you shouldn’t create a gulp plugin (or, how to stop worrying and learn to love existing node packages)](http://blog.overzealous.com/post/74121048393/why-you-shouldnt-create-a-gulp-plugin-or-how-to-stop) * [Inspiration (slides) about why gulp was made](http://slid.es/contra/gulp) * [Building With Gulp](http://www.smashingmagazine.com/2014/06/11/building-with-gulp/) * [Gulp - The Basics (screencast)](https://www.youtube.com/watch?v=dwSLFai8ovQ) -* [Get started with gulp (video series)](http://www.youtube.com/playlist?list=PLRk95HPmOM6PN-G1xyKj9q6ap_dc9Yckm) +* [Get started with gulp (video series)](https://www.youtube.com/playlist?list=PLRk95HPmOM6PN-G1xyKj9q6ap_dc9Yckm) * [Optimize your web code with gulp](http://www.linuxuser.co.uk/tutorials/optimise-your-web-code-with-gulp-js) * [Automate Your Tasks Easily with Gulp.js ](https://scotch.io/tutorials/automate-your-tasks-easily-with-gulp-js) @@ -53,7 +53,7 @@ Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagg All the documentation is covered by the CC0 license *(do whatever you want with it - public domain)*. -[![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png)](http://creativecommons.org/publicdomain/zero/1.0/) +[![CC0](https://i.creativecommons.org/p/zero/1.0/88x31.png)](https://creativecommons.org/publicdomain/zero/1.0/) To the extent possible under law, [Fractal](http://wearefractal.com) has waived all copyright and related or neighboring rights to this work. diff --git a/docs/getting-started.md b/docs/getting-started.md index 057dca3b0..d859dc3bd 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -67,4 +67,4 @@ Using gulpfile ~/my-project/gulpfile.js - [API Documentation](API.md) - The programming interface, defined - [Recipes](recipes) - Specific examples from the community - [In Depth Help](https://travismaynard.com/writing/getting-started-with-gulp) - A tutorial from the the guy who wrote the book -- [Plugins](http://gulpjs.com/plugins/) - Building blocks for your gulp file +- [Plugins](https://gulpjs.com/plugins/) - Building blocks for your gulp file diff --git a/docs/recipes/browserify-transforms.md b/docs/recipes/browserify-transforms.md index e1f11dbc3..6f343a475 100644 --- a/docs/recipes/browserify-transforms.md +++ b/docs/recipes/browserify-transforms.md @@ -1,6 +1,6 @@ # Browserify + Transforms -[Browserify](http://github.com/substack/node-browserify) has become an important and indispensable +[Browserify](https://github.com/substack/node-browserify) has become an important and indispensable tool but requires being wrapped before working well with gulp. Below is a simple recipe for using Browserify with transforms. diff --git a/docs/recipes/browserify-uglify-sourcemap.md b/docs/recipes/browserify-uglify-sourcemap.md index 756b89ac3..6b822f28e 100644 --- a/docs/recipes/browserify-uglify-sourcemap.md +++ b/docs/recipes/browserify-uglify-sourcemap.md @@ -1,6 +1,6 @@ # Browserify + Uglify2 with sourcemaps -[Browserify](http://github.com/substack/node-browserify) has become an important and indispensable +[Browserify](https://github.com/substack/node-browserify) has become an important and indispensable tool but requires being wrapped before working well with gulp. Below is a simple recipe for using Browserify with full sourcemaps that resolve to the original individual files. diff --git a/docs/recipes/fast-browserify-builds-with-watchify.md b/docs/recipes/fast-browserify-builds-with-watchify.md index ef65e7687..0ac89db32 100644 --- a/docs/recipes/fast-browserify-builds-with-watchify.md +++ b/docs/recipes/fast-browserify-builds-with-watchify.md @@ -1,10 +1,10 @@ # Fast browserify builds with watchify -As a [browserify](http://github.com/substack/node-browserify) project begins to expand, the time to bundle it slowly gets longer and longer. While it might start at 1 second, it's possible to be waiting 30 seconds for your project to build on particularly large projects. +As a [browserify](https://github.com/substack/node-browserify) project begins to expand, the time to bundle it slowly gets longer and longer. While it might start at 1 second, it's possible to be waiting 30 seconds for your project to build on particularly large projects. -That's why [substack](http://github.com/substack) wrote [watchify](http://github.com/substack/watchify), a persistent browserify bundler that watches files for changes and *only rebuilds what it needs to*. This way, that first build might still take 30 seconds, but subsequent builds can still run in under 100ms – which is a huge improvement. +That's why [substack](https://github.com/substack) wrote [watchify](https://github.com/substack/watchify), a persistent browserify bundler that watches files for changes and *only rebuilds what it needs to*. This way, that first build might still take 30 seconds, but subsequent builds can still run in under 100ms – which is a huge improvement. -Watchify doesn't have a gulp plugin, and it doesn't need one: you can use [vinyl-source-stream](http://github.com/hughsk/vinyl-source-stream) to pipe the bundle stream into your gulp pipeline. +Watchify doesn't have a gulp plugin, and it doesn't need one: you can use [vinyl-source-stream](https://github.com/hughsk/vinyl-source-stream) to pipe the bundle stream into your gulp pipeline. ``` javascript 'use strict'; diff --git a/docs/recipes/rollup-with-rollup-stream.md b/docs/recipes/rollup-with-rollup-stream.md index abee2dd32..5311fd510 100644 --- a/docs/recipes/rollup-with-rollup-stream.md +++ b/docs/recipes/rollup-with-rollup-stream.md @@ -1,6 +1,6 @@ # Rollup with rollup-stream -Like Browserify, [Rollup](http://rollupjs.org/) is a bundler and thus only fits naturally into gulp if it's at the start of the pipeline. Unlike Browserify, Rollup doesn't natively produce a stream as output and needs to be wrapped before it can take this position. [rollup-stream](https://github.com/Permutatrix/rollup-stream) does this for you, producing output just like that of Browserify's `bundle()` method—as a result, most of the Browserify recipes here will also work with rollup-stream. +Like Browserify, [Rollup](https://rollupjs.org/) is a bundler and thus only fits naturally into gulp if it's at the start of the pipeline. Unlike Browserify, Rollup doesn't natively produce a stream as output and needs to be wrapped before it can take this position. [rollup-stream](https://github.com/Permutatrix/rollup-stream) does this for you, producing output just like that of Browserify's `bundle()` method—as a result, most of the Browserify recipes here will also work with rollup-stream. ## Basic usage ```js diff --git a/docs/recipes/server-with-livereload-and-css-injection.md b/docs/recipes/server-with-livereload-and-css-injection.md index 78a9b38f4..7a14a667b 100644 --- a/docs/recipes/server-with-livereload-and-css-injection.md +++ b/docs/recipes/server-with-livereload-and-css-injection.md @@ -1,6 +1,6 @@ # Server with live-reloading and CSS injection -With [BrowserSync](http://browsersync.io) and gulp, you can easily create a development server that is accessible to any device on the same WiFi network. BrowserSync also has live-reload built in, so there's nothing else to configure. +With [BrowserSync](https://browsersync.io) and gulp, you can easily create a development server that is accessible to any device on the same WiFi network. BrowserSync also has live-reload built in, so there's nothing else to configure. First install the module: @@ -118,5 +118,5 @@ gulp serve ## Extras -- Live reload, CSS injection and scroll/form syncing works seamlessly inside of [BrowserStack](http://www.browserstack.com/) virtual machines. +- Live reload, CSS injection and scroll/form syncing works seamlessly inside of [BrowserStack](https://www.browserstack.com/) virtual machines. - Set `tunnel: true` to view your local site at a public URL (complete with all BrowserSync features). diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md index 0e0586694..b5042a600 100644 --- a/docs/writing-a-plugin/README.md +++ b/docs/writing-a-plugin/README.md @@ -11,12 +11,12 @@ If you plan to create your own Gulp plugin, you will save time by reading the fu ### Streaming file objects -A gulp plugin always returns a stream in [object mode](http://nodejs.org/api/stream.html#stream_object_mode) that does the following: +A gulp plugin always returns a stream in [object mode](https://nodejs.org/api/stream.html#stream_object_mode) that does the following: -1. Takes in [vinyl File objects](http://github.com/gulpjs/vinyl) -2. Outputs [vinyl File objects](http://github.com/gulpjs/vinyl) (via `transform.push()` and/or the plugin's callback function) +1. Takes in [vinyl File objects](https://github.com/gulpjs/vinyl) +2. Outputs [vinyl File objects](https://github.com/gulpjs/vinyl) (via `transform.push()` and/or the plugin's callback function) -These are known as [transform streams](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) +These are known as [transform streams](https://nodejs.org/api/stream.html#stream_class_stream_transform_1) (also sometimes called through streams). Transform streams are streams that are readable and writable; they manipulate objects as they're being passed through. @@ -195,6 +195,6 @@ if (someCondition) { If you're unfamiliar with streams, you will need to read up on them: * https://github.com/substack/stream-handbook (a MUST read) -* http://nodejs.org/api/stream.html +* https://nodejs.org/api/stream.html Other libraries that are not file manipulating through streams but are made for use with gulp are tagged with the [gulpfriendly](https://npmjs.org/browse/keyword/gulpfriendly) keyword on npm. From 260d5c41e6d74f2d11e48ca1e9a3be0b3918fe49 Mon Sep 17 00:00:00 2001 From: Ronald Eddy Jr Date: Tue, 26 Dec 2017 17:22:56 -0800 Subject: [PATCH 052/216] Docs: Update browserify links (#2072) --- docs/recipes/browserify-transforms.md | 2 +- docs/recipes/browserify-uglify-sourcemap.md | 2 +- docs/recipes/fast-browserify-builds-with-watchify.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/recipes/browserify-transforms.md b/docs/recipes/browserify-transforms.md index 6f343a475..0137c6ed5 100644 --- a/docs/recipes/browserify-transforms.md +++ b/docs/recipes/browserify-transforms.md @@ -1,6 +1,6 @@ # Browserify + Transforms -[Browserify](https://github.com/substack/node-browserify) has become an important and indispensable +[Browserify](https://github.com/browserify/browserify) has become an important and indispensable tool but requires being wrapped before working well with gulp. Below is a simple recipe for using Browserify with transforms. diff --git a/docs/recipes/browserify-uglify-sourcemap.md b/docs/recipes/browserify-uglify-sourcemap.md index 6b822f28e..08287edb7 100644 --- a/docs/recipes/browserify-uglify-sourcemap.md +++ b/docs/recipes/browserify-uglify-sourcemap.md @@ -1,6 +1,6 @@ # Browserify + Uglify2 with sourcemaps -[Browserify](https://github.com/substack/node-browserify) has become an important and indispensable +[Browserify](https://github.com/browserify/browserify) has become an important and indispensable tool but requires being wrapped before working well with gulp. Below is a simple recipe for using Browserify with full sourcemaps that resolve to the original individual files. diff --git a/docs/recipes/fast-browserify-builds-with-watchify.md b/docs/recipes/fast-browserify-builds-with-watchify.md index 0ac89db32..32847d7cf 100644 --- a/docs/recipes/fast-browserify-builds-with-watchify.md +++ b/docs/recipes/fast-browserify-builds-with-watchify.md @@ -1,8 +1,8 @@ # Fast browserify builds with watchify -As a [browserify](https://github.com/substack/node-browserify) project begins to expand, the time to bundle it slowly gets longer and longer. While it might start at 1 second, it's possible to be waiting 30 seconds for your project to build on particularly large projects. +As a [browserify](https://github.com/browserify/browserify) project begins to expand, the time to bundle it slowly gets longer and longer. While it might start at 1 second, it's possible to be waiting 30 seconds for your project to build on particularly large projects. -That's why [substack](https://github.com/substack) wrote [watchify](https://github.com/substack/watchify), a persistent browserify bundler that watches files for changes and *only rebuilds what it needs to*. This way, that first build might still take 30 seconds, but subsequent builds can still run in under 100ms – which is a huge improvement. +That's why [substack](https://github.com/substack) wrote [watchify](https://github.com/browserify/watchify), a persistent browserify bundler that watches files for changes and *only rebuilds what it needs to*. This way, that first build might still take 30 seconds, but subsequent builds can still run in under 100ms – which is a huge improvement. Watchify doesn't have a gulp plugin, and it doesn't need one: you can use [vinyl-source-stream](https://github.com/hughsk/vinyl-source-stream) to pipe the bundle stream into your gulp pipeline. From 5368d2c00cf186e25aa0b0f6e4d762f6e795e0fb Mon Sep 17 00:00:00 2001 From: Andreas Schlapbach Date: Wed, 27 Dec 2017 02:23:21 +0100 Subject: [PATCH 053/216] Docs: Remove duplicate "the" typo (#2071) --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index d859dc3bd..980c22189 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -66,5 +66,5 @@ Using gulpfile ~/my-project/gulpfile.js - [API Documentation](API.md) - The programming interface, defined - [Recipes](recipes) - Specific examples from the community -- [In Depth Help](https://travismaynard.com/writing/getting-started-with-gulp) - A tutorial from the the guy who wrote the book +- [In Depth Help](https://travismaynard.com/writing/getting-started-with-gulp) - A tutorial from the guy who wrote the book - [Plugins](https://gulpjs.com/plugins/) - Building blocks for your gulp file From abd73b7fb0ab6eb2ccdc4407e367c169a0e1a887 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Sat, 6 Sep 2014 19:37:12 +0200 Subject: [PATCH 054/216] New: Add the --verify flag (closes #535) --- docs/CLI.md | 1 + lib/blackList.js | 37 +++++++++++++++++++++++++++++++++++++ lib/verifyDependencies.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 lib/blackList.js create mode 100644 lib/verifyDependencies.js diff --git a/docs/CLI.md b/docs/CLI.md index eab0b6b7e..20fb0325c 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -10,6 +10,7 @@ gulp has very few flags to know about. All other flags are for tasks to use if n - `--cwd ` will manually set the CWD. The search for the gulpfile, as well as the relativity of all requires will be from here - `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile - `--tasks-simple` will display a plaintext list of tasks for the loaded gulpfile +- `--verify` will verify plugins referenced in project's package.json against the plugins black list - `--color` will force gulp and gulp plugins to display colors even when no color support is detected - `--no-color` will force gulp and gulp plugins to not display colors even when color support is detected - `--silent` will disable all gulp logging diff --git a/lib/blackList.js b/lib/blackList.js new file mode 100644 index 000000000..b9f381eaa --- /dev/null +++ b/lib/blackList.js @@ -0,0 +1,37 @@ +'use strict'; + +var http = require('http'); + +/** + * Given a collection of plugin names verifies this collection against + * the black-list. Invokes callback with an object: + * [plugin name]=>[black-listing reason] + * or undefined if none of the plugins to check is black-listed. + * + * @param pluginsToVerify - an array of plugin names to verify + * @param cb + */ +module.exports = function (pluginsToVerify, cb) { + http.get('http://gulpjs.com/plugins/blackList.json', function (res) { + var blackListJSONStr = ''; + + res.on('data', function (chunk) { + blackListJSONStr += chunk; + }); + + res.on('end', function () { + var blackList = JSON.parse(blackListJSONStr); + var result = pluginsToVerify.reduce(function(blackListed, pluginName) { + if (blackList[pluginName]) { + blackListed = blackListed || {}; + blackListed[pluginName] = blackList[pluginName]; + return blackListed; + } + }); + cb(null, result); + }); + + }).on('error', function (e) { + cb(e); + }); +}; diff --git a/lib/verifyDependencies.js b/lib/verifyDependencies.js new file mode 100644 index 000000000..25345847d --- /dev/null +++ b/lib/verifyDependencies.js @@ -0,0 +1,30 @@ +'use strict'; + +var chalk = require('chalk'); +var gutil = require('gulp-util'); +var blackList = require('./blackList'); +var formatError = require('./formatError'); + +module.exports = function verifyDependencies(depNames) { + + blackList(Object.keys(depNames), function(err, blackListed) { + if (err) { + gutil.log(chalk.red('Error: failed to retrieve plugins black-list')); + gutil.log(formatError(err)); + process.exit(1); + } + + if (blackListed) { + gutil.log(chalk.red('Black-listed plugins found in this project:')); + for (var blDependency in blackListed) { + gutil.log(blDependency + ': ' + blackListed[blDependency]); + } + process.exit(1); + } else { + gutil.log( + chalk.green('There are no black-listed plugins in this project') + ); + process.exit(0); + } + }); +}; From f0942aaf9dbdeda83a33273791686a20ae6d4d4a Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 27 Dec 2014 23:22:00 -0700 Subject: [PATCH 055/216] Update: Replace inline CLI code with gulp-cli dependency --- bin/gulp.js | 211 +------------------------------------- completion/README.md | 20 ---- completion/bash | 27 ----- completion/fish | 10 -- completion/powershell | 61 ----------- completion/zsh | 25 ----- docs/CLI.md | 2 +- lib/blackList.js | 37 ------- lib/completion.js | 22 ---- lib/taskTree.js | 14 --- lib/verifyDependencies.js | 30 ------ package.json | 17 +-- test/taskTree.js | 42 -------- 13 files changed, 4 insertions(+), 514 deletions(-) delete mode 100644 completion/README.md delete mode 100644 completion/bash delete mode 100644 completion/fish delete mode 100644 completion/powershell delete mode 100644 completion/zsh delete mode 100644 lib/blackList.js delete mode 100644 lib/completion.js delete mode 100644 lib/taskTree.js delete mode 100644 lib/verifyDependencies.js delete mode 100644 test/taskTree.js diff --git a/bin/gulp.js b/bin/gulp.js index a5374c111..f4b80cc41 100755 --- a/bin/gulp.js +++ b/bin/gulp.js @@ -1,212 +1,3 @@ #!/usr/bin/env node -'use strict'; -var gutil = require('gulp-util'); -var prettyTime = require('pretty-hrtime'); -var chalk = require('chalk'); -var semver = require('semver'); -var archy = require('archy'); -var Liftoff = require('liftoff'); -var tildify = require('tildify'); -var interpret = require('interpret'); -var v8flags = require('v8flags'); -var completion = require('../lib/completion'); -var argv = require('minimist')(process.argv.slice(2)); -var taskTree = require('../lib/taskTree'); - -// Set env var for ORIGINAL cwd -// before anything touches it -process.env.INIT_CWD = process.cwd(); - -var cli = new Liftoff({ - name: 'gulp', - completions: completion, - extensions: interpret.jsVariants, - v8flags: v8flags, -}); - -// Exit with 0 or 1 -var failed = false; -process.once('exit', function(code) { - if (code === 0 && failed) { - process.exit(1); - } -}); - -// Parse those args m8 -var cliPackage = require('../package'); -var versionFlag = argv.v || argv.version; -var tasksFlag = argv.T || argv.tasks; -var tasks = argv._; -var toRun = tasks.length ? tasks : ['default']; - -// This is a hold-over until we have a better logging system -// with log levels -var simpleTasksFlag = argv['tasks-simple']; -var shouldLog = !argv.silent && !simpleTasksFlag; - -if (!shouldLog) { - gutil.log = function() {}; -} - -cli.on('require', function(name) { - gutil.log('Requiring external module', chalk.magenta(name)); -}); - -cli.on('requireFail', function(name) { - gutil.log(chalk.red('Failed to load external module'), chalk.magenta(name)); -}); - -cli.on('respawn', function(flags, child) { - var nodeFlags = chalk.magenta(flags.join(', ')); - var pid = chalk.magenta(child.pid); - gutil.log('Node flags detected:', nodeFlags); - gutil.log('Respawned to PID:', pid); -}); - -cli.launch({ - cwd: argv.cwd, - configPath: argv.gulpfile, - require: argv.require, - completion: argv.completion, -}, handleArguments); - -// The actual logic -function handleArguments(env) { - if (versionFlag && tasks.length === 0) { - gutil.log('CLI version', cliPackage.version); - if (env.modulePackage && typeof env.modulePackage.version !== 'undefined') { - gutil.log('Local version', env.modulePackage.version); - } - process.exit(0); - } - - if (!env.modulePath) { - gutil.log( - chalk.red('Local gulp not found in'), - chalk.magenta(tildify(env.cwd)) - ); - gutil.log(chalk.red('Try running: npm install gulp')); - process.exit(1); - } - - if (!env.configPath) { - gutil.log(chalk.red('No gulpfile found')); - process.exit(1); - } - - // Check for semver difference between cli and local installation - if (semver.gt(cliPackage.version, env.modulePackage.version)) { - gutil.log(chalk.red('Warning: gulp version mismatch:')); - gutil.log(chalk.red('Global gulp is', cliPackage.version)); - gutil.log(chalk.red('Local gulp is', env.modulePackage.version)); - } - - // Chdir before requiring gulpfile to make sure - // we let them chdir as needed - if (process.cwd() !== env.cwd) { - process.chdir(env.cwd); - gutil.log( - 'Working directory changed to', - chalk.magenta(tildify(env.cwd)) - ); - } - - // This is what actually loads up the gulpfile - require(env.configPath); - gutil.log('Using gulpfile', chalk.magenta(tildify(env.configPath))); - - var gulpInst = require(env.modulePath); - logEvents(gulpInst); - - process.nextTick(function() { - if (simpleTasksFlag) { - return logTasksSimple(env, gulpInst); - } - if (tasksFlag) { - return logTasks(env, gulpInst); - } - gulpInst.start.apply(gulpInst, toRun); - }); -} - -function logTasks(env, localGulp) { - var tree = taskTree(localGulp.tasks); - tree.label = 'Tasks for ' + chalk.magenta(tildify(env.configPath)); - archy(tree) - .split('\n') - .forEach(function(v) { - if (v.trim().length === 0) { - return; - } - gutil.log(v); - }); -} - -function logTasksSimple(env, localGulp) { - console.log(Object.keys(localGulp.tasks) - .join('\n') - .trim()); -} - -// Format orchestrator errors -function formatError(e) { - if (!e.err) { - return e.message; - } - - // PluginError - if (typeof e.err.showStack === 'boolean') { - return e.err.toString(); - } - - // Normal error - if (e.err.stack) { - return e.err.stack; - } - - // Unknown (string, number, etc.) - return new Error(String(e.err)).stack; -} - -// Wire up logging events -function logEvents(gulpInst) { - - // Total hack due to poor error management in orchestrator - gulpInst.on('err', function() { - failed = true; - }); - - gulpInst.on('task_start', function(e) { - // TODO: batch these - // so when 5 tasks start at once it only logs one time with all 5 - gutil.log('Starting', '\'' + chalk.cyan(e.task) + '\'...'); - }); - - gulpInst.on('task_stop', function(e) { - var time = prettyTime(e.hrDuration); - gutil.log( - 'Finished', '\'' + chalk.cyan(e.task) + '\'', - 'after', chalk.magenta(time) - ); - }); - - gulpInst.on('task_err', function(e) { - var msg = formatError(e); - var time = prettyTime(e.hrDuration); - gutil.log( - '\'' + chalk.cyan(e.task) + '\'', - chalk.red('errored after'), - chalk.magenta(time) - ); - gutil.log(msg); - }); - - gulpInst.on('task_not_found', function(err) { - gutil.log( - chalk.red('Task \'' + err.task + '\' is not in your gulpfile') - ); - gutil.log('Please check the documentation for proper gulpfile formatting'); - process.exit(1); - }); -} +require('gulp-cli')(); diff --git a/completion/README.md b/completion/README.md deleted file mode 100644 index c0e8c9133..000000000 --- a/completion/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Completion for gulp -> Thanks to grunt team and Tyler Kellen - -To enable tasks auto-completion in shell you should add `eval "$(gulp --completion=shell)"` in your `.shellrc` file. - -## Bash - -Add `eval "$(gulp --completion=bash)"` to `~/.bashrc`. - -## Zsh - -Add `eval "$(gulp --completion=zsh)"` to `~/.zshrc`. - -## Powershell - -Add `Invoke-Expression ((gulp --completion=powershell) -join [System.Environment]::NewLine)` to `$PROFILE`. - -## Fish - -Add `gulp --completion=fish | source` to `~/.config/fish/config.fish`. diff --git a/completion/bash b/completion/bash deleted file mode 100644 index 704c27c13..000000000 --- a/completion/bash +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# Borrowed from grunt-cli -# http://gruntjs.com/ -# -# Copyright (c) 2012 Tyler Kellen, contributors -# Licensed under the MIT license. -# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT - -# Usage: -# -# To enable bash completion for gulp, add the following line (minus the -# leading #, which is the bash comment character) to your ~/.bashrc file: -# -# eval "$(gulp --completion=bash)" - -# Enable bash autocompletion. -function _gulp_completions() { - # The currently-being-completed word. - local cur="${COMP_WORDS[COMP_CWORD]}" - #Grab tasks - local compls=$(gulp --tasks-simple) - # Tell complete what stuff to show. - COMPREPLY=($(compgen -W "$compls" -- "$cur")) -} - -complete -o default -F _gulp_completions gulp diff --git a/completion/fish b/completion/fish deleted file mode 100644 index f27f2248b..000000000 --- a/completion/fish +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env fish - -# Usage: -# -# To enable fish completion for gulp, add the following line to -# your ~/.config/fish/config.fish file: -# -# gulp --completion=fish | source - -complete -c gulp -a "(gulp --tasks-simple)" -f diff --git a/completion/powershell b/completion/powershell deleted file mode 100644 index 08ec4382e..000000000 --- a/completion/powershell +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (c) 2014 Jason Jarrett -# -# Tab completion for the `gulp` -# -# Usage: -# -# To enable powershell completion for gulp you need to be running -# at least PowerShell v3 or greater and add the below to your $PROFILE -# -# Invoke-Expression ((gulp --completion=powershell) -join [System.Environment]::NewLine) -# -# - -$gulp_completion_Process = { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) - - - # Load up an assembly to read the gulpfile's sha1 - if(-not $global:GulpSHA1Managed) { - [Reflection.Assembly]::LoadWithPartialName("System.Security") | out-null - $global:GulpSHA1Managed = new-Object System.Security.Cryptography.SHA1Managed - } - - # setup a global (in-memory) cache - if(-not $global:GulpfileShaCache) { - $global:GulpfileShaCache = @{}; - } - - $cache = $global:GulpfileShaCache; - - # Get the gulpfile's sha1 - $sha1gulpFile = (resolve-path gulpfile.js -ErrorAction Ignore | %{ - $file = [System.IO.File]::Open($_.Path, "open", "read") - [string]::join('', ($global:GulpSHA1Managed.ComputeHash($file) | %{ $_.ToString("x2") })) - $file.Dispose() - }) - - # lookup the sha1 for previously cached task lists. - if($cache.ContainsKey($sha1gulpFile)){ - $tasks = $cache[$sha1gulpFile]; - } else { - $tasks = (gulp --tasks-simple).split("`n"); - $cache[$sha1gulpFile] = $tasks; - } - - - $tasks | - where { $_.startswith($commandName) } - Sort-Object | - foreach { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', ('{0}' -f $_) } -} - -if (-not $global:options) { - $global:options = @{ - CustomArgumentCompleters = @{}; - NativeArgumentCompleters = @{} - } -} - -$global:options['NativeArgumentCompleters']['gulp'] = $gulp_completion_Process -$function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}' diff --git a/completion/zsh b/completion/zsh deleted file mode 100644 index 8169b22d7..000000000 --- a/completion/zsh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/zsh - -# Borrowed from grunt-cli -# http://gruntjs.com/ -# -# Copyright (c) 2012 Tyler Kellen, contributors -# Licensed under the MIT license. -# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT - -# Usage: -# -# To enable zsh completion for gulp, add the following line (minus the -# leading #, which is the zsh comment character) to your ~/.zshrc file: -# -# eval "$(gulp --completion=zsh)" - -# Enable zsh autocompletion. -function _gulp_completion() { - # Grab tasks - compls=$(gulp --tasks-simple) - completions=(${=compls}) - compadd -- $completions -} - -compdef _gulp_completion gulp diff --git a/docs/CLI.md b/docs/CLI.md index 20fb0325c..5bfd67922 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -10,7 +10,7 @@ gulp has very few flags to know about. All other flags are for tasks to use if n - `--cwd ` will manually set the CWD. The search for the gulpfile, as well as the relativity of all requires will be from here - `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile - `--tasks-simple` will display a plaintext list of tasks for the loaded gulpfile -- `--verify` will verify plugins referenced in project's package.json against the plugins black list +- `--verify` will verify plugins referenced in project's package.json against the plugins blacklist - `--color` will force gulp and gulp plugins to display colors even when no color support is detected - `--no-color` will force gulp and gulp plugins to not display colors even when color support is detected - `--silent` will disable all gulp logging diff --git a/lib/blackList.js b/lib/blackList.js deleted file mode 100644 index b9f381eaa..000000000 --- a/lib/blackList.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -var http = require('http'); - -/** - * Given a collection of plugin names verifies this collection against - * the black-list. Invokes callback with an object: - * [plugin name]=>[black-listing reason] - * or undefined if none of the plugins to check is black-listed. - * - * @param pluginsToVerify - an array of plugin names to verify - * @param cb - */ -module.exports = function (pluginsToVerify, cb) { - http.get('http://gulpjs.com/plugins/blackList.json', function (res) { - var blackListJSONStr = ''; - - res.on('data', function (chunk) { - blackListJSONStr += chunk; - }); - - res.on('end', function () { - var blackList = JSON.parse(blackListJSONStr); - var result = pluginsToVerify.reduce(function(blackListed, pluginName) { - if (blackList[pluginName]) { - blackListed = blackListed || {}; - blackListed[pluginName] = blackList[pluginName]; - return blackListed; - } - }); - cb(null, result); - }); - - }).on('error', function (e) { - cb(e); - }); -}; diff --git a/lib/completion.js b/lib/completion.js deleted file mode 100644 index 7000250b4..000000000 --- a/lib/completion.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); - -module.exports = function(name) { - if (typeof name !== 'string') { - throw new Error('Missing completion type'); - } - var file = path.join(__dirname, '../completion', name); - try { - console.log(fs.readFileSync(file, 'utf8')); - process.exit(0); - } catch (err) { - console.log( - 'echo "gulp autocompletion rules for', - '\'' + name + '\'', - 'not found"' - ); - process.exit(5); - } -}; diff --git a/lib/taskTree.js b/lib/taskTree.js deleted file mode 100644 index accb1a77f..000000000 --- a/lib/taskTree.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -module.exports = function(tasks) { - return Object.keys(tasks) - .reduce(function(prev, task) { - prev.nodes.push({ - label: task, - nodes: tasks[task].dep, - }); - return prev; - }, { - nodes: [], - }); -}; diff --git a/lib/verifyDependencies.js b/lib/verifyDependencies.js deleted file mode 100644 index 25345847d..000000000 --- a/lib/verifyDependencies.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var chalk = require('chalk'); -var gutil = require('gulp-util'); -var blackList = require('./blackList'); -var formatError = require('./formatError'); - -module.exports = function verifyDependencies(depNames) { - - blackList(Object.keys(depNames), function(err, blackListed) { - if (err) { - gutil.log(chalk.red('Error: failed to retrieve plugins black-list')); - gutil.log(formatError(err)); - process.exit(1); - } - - if (blackListed) { - gutil.log(chalk.red('Black-listed plugins found in this project:')); - for (var blDependency in blackListed) { - gutil.log(blDependency + ': ' + blackListed[blDependency]); - } - process.exit(1); - } else { - gutil.log( - chalk.green('There are no black-listed plugins in this project') - ); - process.exit(0); - } - }); -}; diff --git a/package.json b/package.json index edf2e2e89..ba9d7eff1 100644 --- a/package.json +++ b/package.json @@ -16,28 +16,17 @@ ], "files": [ "index.js", - "lib", - "bin", - "completion", - "gulp.1" + "bin" ], "bin": { "gulp": "./bin/gulp.js" }, "man": "gulp.1", "dependencies": { - "archy": "^1.0.0", - "chalk": "^1.0.0", "deprecated": "^0.0.1", + "gulp-cli": "^0.1.3", "gulp-util": "^3.0.0", - "interpret": "^1.0.0", - "liftoff": "^2.1.0", - "minimist": "^1.1.0", "orchestrator": "^0.3.0", - "pretty-hrtime": "^1.0.0", - "semver": "^4.1.0", - "tildify": "^1.0.0", - "v8flags": "^2.0.2", "vinyl-fs": "^0.3.0" }, "devDependencies": { @@ -48,7 +37,6 @@ "istanbul": "^0.3.0", "jscs": "^2.3.5", "jscs-preset-gulp": "^1.0.0", - "marked-man": "^0.1.3", "mkdirp": "^0.5.0", "mocha": "^2.0.1", "mocha-lcov-reporter": "^0.0.1", @@ -57,7 +45,6 @@ "should": "^5.0.1" }, "scripts": { - "prepublish": "marked-man --name gulp docs/CLI.md > gulp.1", "lint": "eslint . && jscs *.js bin/ lib/ test/", "pretest": "npm run lint", "test": "mocha --reporter spec", diff --git a/test/taskTree.js b/test/taskTree.js deleted file mode 100644 index 199eb1f27..000000000 --- a/test/taskTree.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -var taskTree = require('../lib/taskTree'); -var should = require('should'); - -require('mocha'); - -describe('taskTree()', function() { - it('should form a tree properly', function(done) { - should.exist(taskTree); // Lol shutup jshint - - var tasks = { - test: { - dep: ['abc', 'def'], - }, - abc: { - dep: ['def'], - }, - def: { - dep: [], - }, - }; - - var expectTree = { - nodes: [ - { - label: 'test', - nodes: ['abc', 'def'], - }, { - label: 'abc', - nodes: ['def'], - }, { - label: 'def', - nodes: [], - }, - ], - }; - - taskTree(tasks).should.eql(expectTree); - done(); - }); -}); From 9fda7b405e15aea6798277bd134b882251784fb4 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 24 Aug 2014 18:40:18 -0700 Subject: [PATCH 056/216] Breaking: Replace Orchestrator with Undertaker --- index.js | 44 ++------------ package.json | 3 +- test/tasks.js | 157 -------------------------------------------------- 3 files changed, 5 insertions(+), 199 deletions(-) delete mode 100644 test/tasks.js diff --git a/index.js b/index.js index 42bc69b3d..a27af337f 100644 --- a/index.js +++ b/index.js @@ -1,26 +1,13 @@ 'use strict'; var util = require('util'); -var Orchestrator = require('orchestrator'); -var gutil = require('gulp-util'); -var deprecated = require('deprecated'); +var Undertaker = require('undertaker'); var vfs = require('vinyl-fs'); function Gulp() { - Orchestrator.call(this); + Undertaker.call(this); } -util.inherits(Gulp, Orchestrator); - -Gulp.prototype.task = Gulp.prototype.add; -Gulp.prototype.run = function() { - // `run()` is deprecated as of 3.5 and will be removed in 4.0 - // Use task dependencies instead - - // Impose our opinion of "default" tasks onto orchestrator - var tasks = arguments.length ? arguments : ['default']; - - this.start.apply(this, tasks); -}; +util.inherits(Gulp, Undertaker); Gulp.prototype.src = vfs.src; Gulp.prototype.dest = vfs.dest; @@ -30,34 +17,11 @@ Gulp.prototype.watch = function(glob, opt, fn) { opt = null; } - // Array of tasks given - if (Array.isArray(fn)) { - return vfs.watch(glob, opt, function() { - this.start.apply(this, fn); - }.bind(this)); - } - - return vfs.watch(glob, opt, fn); + return vfs.watch(glob, opt, this.parallel(fn)); }; // Let people use this class from our instance Gulp.prototype.Gulp = Gulp; -// Deprecations -deprecated.field('gulp.env has been deprecated. ' + - 'Use your own CLI parser instead. ' + - 'We recommend using yargs or minimist.', - console.warn, - Gulp.prototype, - 'env', - gutil.env -); - -Gulp.prototype.run = deprecated.method('gulp.run() has been deprecated. ' + - 'Use task dependencies or gulp.watch task triggering instead.', - console.warn, - Gulp.prototype.run -); - var inst = new Gulp(); module.exports = inst; diff --git a/package.json b/package.json index ba9d7eff1..666f8f271 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,9 @@ }, "man": "gulp.1", "dependencies": { - "deprecated": "^0.0.1", "gulp-cli": "^0.1.3", "gulp-util": "^3.0.0", - "orchestrator": "^0.3.0", + "undertaker": "^0.1.0", "vinyl-fs": "^0.3.0" }, "devDependencies": { diff --git a/test/tasks.js b/test/tasks.js deleted file mode 100644 index d5dcea19a..000000000 --- a/test/tasks.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -var gulp = require('../'); -var Q = require('q'); -var should = require('should'); -require('mocha'); - -describe('gulp tasks', function() { - describe('task()', function() { - it('should define a task', function(done) { - var fn; - fn = function() {}; - gulp.task('test', fn); - should.exist(gulp.tasks.test); - gulp.tasks.test.fn.should.equal(fn); - gulp.reset(); - done(); - }); - }); - describe('run()', function() { - it('should run multiple tasks', function(done) { - var a, fn, fn2; - a = 0; - fn = function() { - this.should.equal(gulp); - ++a; - }; - fn2 = function() { - this.should.equal(gulp); - ++a; - }; - gulp.task('test', fn); - gulp.task('test2', fn2); - gulp.run('test', 'test2'); - a.should.equal(2); - gulp.reset(); - done(); - }); - it('should run all tasks when call run() multiple times', function(done) { - var a, fn, fn2; - a = 0; - fn = function() { - this.should.equal(gulp); - ++a; - }; - fn2 = function() { - this.should.equal(gulp); - ++a; - }; - gulp.task('test', fn); - gulp.task('test2', fn2); - gulp.run('test'); - gulp.run('test2'); - a.should.equal(2); - gulp.reset(); - done(); - }); - it('should run all async promise tasks', function(done) { - var a, fn, fn2; - a = 0; - fn = function() { - var deferred = Q.defer(); - setTimeout(function() { - ++a; - deferred.resolve(); - }, 1); - return deferred.promise; - }; - fn2 = function() { - var deferred = Q.defer(); - setTimeout(function() { - ++a; - deferred.resolve(); - }, 1); - return deferred.promise; - }; - gulp.task('test', fn); - gulp.task('test2', fn2); - gulp.run('test'); - gulp.run('test2', function() { - gulp.isRunning.should.equal(false); - a.should.equal(2); - gulp.reset(); - done(); - }); - gulp.isRunning.should.equal(true); - }); - it('should run all async callback tasks', function(done) { - var a, fn, fn2; - a = 0; - fn = function(cb) { - setTimeout(function() { - ++a; - cb(null); - }, 1); - }; - fn2 = function(cb) { - setTimeout(function() { - ++a; - cb(null); - }, 1); - }; - gulp.task('test', fn); - gulp.task('test2', fn2); - gulp.run('test'); - gulp.run('test2', function() { - gulp.isRunning.should.equal(false); - a.should.equal(2); - gulp.reset(); - done(); - }); - gulp.isRunning.should.equal(true); - }); - it('should emit task_not_found and throw an error when task is not defined', function(done) { - gulp.on('task_not_found', function(err) { - should.exist(err); - should.exist(err.task); - err.task.should.equal('test'); - gulp.reset(); - done(); - }); - try { - gulp.run('test'); - } catch (err) { - should.exist(err); - } - }); - it('should run task scoped to gulp', function(done) { - var a, fn; - a = 0; - fn = function() { - this.should.equal(gulp); - ++a; - }; - gulp.task('test', fn); - gulp.run('test'); - a.should.equal(1); - gulp.isRunning.should.equal(false); - gulp.reset(); - done(); - }); - it('should run default task scoped to gulp', function(done) { - var a, fn; - a = 0; - fn = function() { - this.should.equal(gulp); - ++a; - }; - gulp.task('default', fn); - gulp.run(); - a.should.equal(1); - gulp.isRunning.should.equal(false); - gulp.reset(); - done(); - }); - }); -}); From f787ba51569cc61439f731db9bfe0f0e2452a989 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 24 Aug 2014 20:21:25 -0700 Subject: [PATCH 057/216] Docs: Update syntax in readme example --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ca49c3c2a..8e894f468 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ gulp.task('clean', function() { return del(['build']); }); -gulp.task('scripts', ['clean'], function() { +gulp.task('scripts', function() { // Minify and copy all JavaScript (except vendor scripts) // with sourcemaps all the way down return gulp.src(paths.scripts) @@ -60,7 +60,7 @@ gulp.task('scripts', ['clean'], function() { }); // Copy all static images -gulp.task('images', ['clean'], function() { +gulp.task('images', function() { return gulp.src(paths.images) // Pass in options to the task .pipe(imagemin({optimizationLevel: 5})) @@ -69,12 +69,13 @@ gulp.task('images', ['clean'], function() { // Rerun the task when a file changes gulp.task('watch', function() { - gulp.watch(paths.scripts, ['scripts']); - gulp.watch(paths.images, ['images']); + gulp.watch(paths.scripts, 'scripts'); + gulp.watch(paths.images, 'images'); }); +gulp.task('all', gulp.parallel('watch', 'scripts', 'images')); // The default task (called when you run `gulp` from cli) -gulp.task('default', ['watch', 'scripts', 'images']); +gulp.task('default', gulp.series('clean', 'all')); ``` ## Incremental Builds From 9abb0a4f96cc3d0989494e91b51c312ca1d5a589 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 24 Aug 2014 18:56:00 -0700 Subject: [PATCH 058/216] Update: Improve gulp.watch implementation & tests --- index.js | 16 +++-- package.json | 2 +- test/watch.js | 189 +++++++++++++++++++------------------------------- 3 files changed, 83 insertions(+), 124 deletions(-) diff --git a/index.js b/index.js index a27af337f..13bc263c1 100644 --- a/index.js +++ b/index.js @@ -11,13 +11,21 @@ util.inherits(Gulp, Undertaker); Gulp.prototype.src = vfs.src; Gulp.prototype.dest = vfs.dest; -Gulp.prototype.watch = function(glob, opt, fn) { - if (typeof opt === 'function' || Array.isArray(opt)) { - fn = opt; +Gulp.prototype.watch = function(glob, opt, task) { + var isFunction = (typeof opt === 'function'); + var isString = (typeof opt === 'string'); + var isArray = Array.isArray(opt); + if (isFunction || isString || isArray) { + task = opt; opt = null; } - return vfs.watch(glob, opt, this.parallel(fn)); + var fn; + if (task) { + fn = this.parallel(task); + } + + return vfs.watch(glob, opt, fn); }; // Let people use this class from our instance diff --git a/package.json b/package.json index 666f8f271..12cb3f376 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "should": "^5.0.1" }, "scripts": { - "lint": "eslint . && jscs *.js bin/ lib/ test/", + "lint": "eslint . && jscs *.js bin/ test/", "pretest": "npm run lint", "test": "mocha --reporter spec", "coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage" diff --git a/test/watch.js b/test/watch.js index e299cf907..335ca771a 100644 --- a/test/watch.js +++ b/test/watch.js @@ -11,168 +11,119 @@ require('mocha'); var outpath = path.join(__dirname, './out-fixtures'); +var tempFileContent = 'A test generated this file and it is safe to delete'; + +function createTempFile(path) { + fs.writeFileSync(path, tempFileContent); +} + +function updateTempFile(path) { + var gazeTimeout = 125; + setTimeout(function() { + fs.appendFileSync(path, ' changed'); + }, gazeTimeout); +} + describe('gulp', function() { describe('watch()', function() { beforeEach(rimraf.bind(null, outpath)); beforeEach(mkdirp.bind(null, outpath)); afterEach(rimraf.bind(null, outpath)); - var tempFileContent = 'A test generated this file and it is safe to delete'; - - var writeTimeout = 125; // Wait for it to get to the filesystem - var writeFileWait = function(name, content, cb) { - if (!cb) { - cb = function() {}; - } - setTimeout(function() { - fs.writeFile(name, content, cb); - }, writeTimeout); - }; - it('should call the function when file changes: no options', function(done) { - - // Arrange var tempFile = path.join(outpath, 'watch-func.txt'); - fs.writeFile(tempFile, tempFileContent, function() { - // Assert: it works if it calls done - var watcher = gulp.watch(tempFile, function(evt) { - should.exist(evt); - should.exist(evt.path); - should.exist(evt.type); - evt.type.should.equal('changed'); - evt.path.should.equal(path.resolve(tempFile)); - watcher.end(); - done(); - }); + createTempFile(tempFile); - // Act: change file - writeFileWait(tempFile, tempFileContent + ' changed'); + var watcher = gulp.watch(tempFile, function(cb) { + watcher.end(); + cb(); + done(); }); + + updateTempFile(tempFile); }); it('should call the function when file changes: w/ options', function(done) { - - // Arrange var tempFile = path.join(outpath, 'watch-func-options.txt'); - fs.writeFile(tempFile, tempFileContent, function() { - // Assert: it works if it calls done - var watcher = gulp.watch(tempFile, { debounceDelay: 5 }, function(evt) { - should.exist(evt); - should.exist(evt.path); - should.exist(evt.type); - evt.type.should.equal('changed'); - evt.path.should.equal(path.resolve(tempFile)); - watcher.end(); - done(); - }); + createTempFile(tempFile); - // Act: change file - writeFileWait(tempFile, tempFileContent + ' changed'); + var watcher = gulp.watch(tempFile, {debounceDelay: 5}, function(cb) { + watcher.end(); + cb(); + done(); }); + + updateTempFile(tempFile); }); it('should not drop options when no callback specified', function(done) { - // Arrange var tempFile = path.join(outpath, 'watch-func-nodrop-options.txt'); // By passing a cwd option, ensure options are not lost to gaze var relFile = '../watch-func-nodrop-options.txt'; var cwd = outpath + '/subdir'; - fs.writeFile(tempFile, tempFileContent, function() { - - // Assert: it works if it calls done - var watcher = gulp.watch(relFile, { debounceDelay: 5, cwd: cwd }) - .on('change', function(evt) { - should.exist(evt); - should.exist(evt.path); - should.exist(evt.type); - evt.type.should.equal('changed'); - evt.path.should.equal(path.resolve(tempFile)); - watcher.end(); - done(); - }); - - // Act: change file - writeFileWait(tempFile, tempFileContent + ' changed'); - }); + + createTempFile(tempFile); + + var watcher = gulp.watch(relFile, {debounceDelay: 5, cwd: cwd}) + .on('change', function(evt) { + should.exist(evt); + should.exist(evt.path); + should.exist(evt.type); + evt.type.should.equal('changed'); + evt.path.should.equal(path.resolve(tempFile)); + watcher.end(); + done(); + }); + + updateTempFile(tempFile); }); it('should run many tasks: w/ options', function(done) { - // Arrange var tempFile = path.join(outpath, 'watch-task-options.txt'); - var task1 = 'task1'; - var task2 = 'task2'; - var task3 = 'task3'; var a = 0; - var timeout = writeTimeout * 2.5; - fs.writeFile(tempFile, tempFileContent, function() { + createTempFile(tempFile); - gulp.task(task1, function() { - a++; - }); - gulp.task(task2, function() { - a += 10; - }); - gulp.task(task3, function() { - throw new Error('task3 called!'); - }); - - // It works if it calls the task - var config = { debounceDelay: timeout / 2 }; - var watcher = gulp.watch(tempFile, config, [task1, task2]); - - // Assert - setTimeout(function() { - a.should.equal(11); // Task1 and task2 + gulp.task('task1', function(cb) { + a++; + cb(); + }); + gulp.task('task2', function(cb) { + a += 10; + a.should.equal(11); + watcher.end(); + cb(); + done(); + }); - gulp.reset(); - watcher.end(); - done(); - }, timeout); + var watcher = gulp.watch(tempFile, {debounceDelay: 25}, gulp.series('task1', 'task2')); - // Act: change file - writeFileWait(tempFile, tempFileContent + ' changed'); - }); + updateTempFile(tempFile); }); it('should run many tasks: no options', function(done) { - // Arrange var tempFile = path.join(outpath, 'watch-many-tasks-no-options.txt'); - var task1 = 'task1'; - var task2 = 'task2'; - var task3 = 'task3'; var a = 0; - var timeout = writeTimeout * 2.5; - - fs.writeFile(tempFile, tempFileContent, function() { - - gulp.task(task1, function() { - a++; - }); - gulp.task(task2, function() { - a += 10; - }); - gulp.task(task3, function() { - throw new Error('task3 called!'); - }); - // It works if it calls the task - var watcher = gulp.watch(tempFile, [task1, task2]); + createTempFile(tempFile); - // Assert - setTimeout(function() { - a.should.equal(11); // Task1 and task2 + gulp.task('task1', function(cb) { + a++; + cb(); + }); + gulp.task('task2', function(cb) { + a += 10; + a.should.equal(11); + watcher.end(); + cb(); + done(); + }); - gulp.reset(); - watcher.end(); - done(); - }, timeout); + var watcher = gulp.watch(tempFile, gulp.series('task1', 'task2')); - // Act: change file - writeFileWait(tempFile, tempFileContent + ' changed'); - }); + updateTempFile(tempFile); }); }); From d331a4e73bf257f334e55ac722bfae64adb45d53 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 30 Aug 2014 15:23:13 -0700 Subject: [PATCH 059/216] Docs: Add 4.0 changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e9767d8..939c7acbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # gulp changelog +## 4.0.0 + +- removed task dependency graph, everything must be composed using `gulp.series` or `gulp.parallel` +- removed 3 argument syntax for `gulp.task` due to task dependency graph being removed. +- added `gulp.series` and `gulp.parallel` methods for composing tasks. +- added single argument syntax for `gulp.task` which allows a named function to be used as the name of the task and task function. +- added `gulp.tree` method for retrieving the task tree. Pass `{ deep: true }` for an `archy` compatible node list. +- added `--verify` flag to check the dependencies in package.json against the plugin blacklist. +- added `gulp.registry` for setting custom registries. + ## 3.9.1 - update interpret to 1.0.0 (support for babel-register) From 6830560bb5d2a4e446d9d2811a8a7d71d38a5868 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 16 Nov 2014 16:10:22 -0700 Subject: [PATCH 060/216] Upgrade: Update undertaker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12cb3f376..6d5e7db4c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dependencies": { "gulp-cli": "^0.1.3", "gulp-util": "^3.0.0", - "undertaker": "^0.1.0", + "undertaker": "^0.3.0", "vinyl-fs": "^0.3.0" }, "devDependencies": { From 2d0fa20a030c3111e6ec67be087d8b0c84364f49 Mon Sep 17 00:00:00 2001 From: Eric Schoffstall Date: Sat, 27 Dec 2014 16:47:18 -0800 Subject: [PATCH 061/216] Docs: Update changelog --- CHANGELOG.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 939c7acbb..ade4772bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,21 @@ ## 4.0.0 -- removed task dependency graph, everything must be composed using `gulp.series` or `gulp.parallel` -- removed 3 argument syntax for `gulp.task` due to task dependency graph being removed. -- added `gulp.series` and `gulp.parallel` methods for composing tasks. -- added single argument syntax for `gulp.task` which allows a named function to be used as the name of the task and task function. -- added `gulp.tree` method for retrieving the task tree. Pass `{ deep: true }` for an `archy` compatible node list. +- replaced 3.x task system (orchestrator) with new task system (bach) + - removed gulp.reset + - removed 3 argument syntax for `gulp.task` + - using strings when registering with `gulp.task` should only be done when you will call the task with the CLI + - added `gulp.series` and `gulp.parallel` methods for composing tasks. Everything must use these now. + - added single argument syntax for `gulp.task` which allows a named function to be used as the name of the task and task function. + - added `gulp.tree` method for retrieving the task tree. Pass `{ deep: true }` for an `archy` compatible node list. + - added `gulp.registry` for setting custom registries. - added `--verify` flag to check the dependencies in package.json against the plugin blacklist. -- added `gulp.registry` for setting custom registries. +- added `gulp.symlink` which functions exactly like `gulp.dest`, but symlinks instead. +- added `dirMode` param to `gulp.dest` and `gulp.symlink` which allows better control over the mode of the destination folder that is created. +- globs passed to `gulp.src` will be evaluated in order, which means this is possible `gulp.src(['*.js', '!b*.js', 'bad.js'])` (exclude every JS file that starts with a b except bad.js) +- added `since` option to `gulp.src` which lets you only match files that have been modified since a certain date (for incremental builds) +- split CLI out into a module if you want to save bandwidth/disk space. you can install the gulp CLI using either `npm install gulp -g` or `npm install gulp-cli -g`, where gulp-cli is the smaller one (no module code included) +- add `--tasks-json` flag to CLI to dump the whole tree out for other tools to consume ## 3.9.1 From 6095f350486ea47fc2d8203a68980230b3573af1 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 10 Jan 2015 17:07:25 -0700 Subject: [PATCH 062/216] Update: Remove gulp-util & depend on unpublished gulp-cli --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 6d5e7db4c..28e782d5a 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,7 @@ }, "man": "gulp.1", "dependencies": { - "gulp-cli": "^0.1.3", - "gulp-util": "^3.0.0", + "gulp-cli": "gulpjs/gulp-cli#4.0", "undertaker": "^0.3.0", "vinyl-fs": "^0.3.0" }, From fa85999b3e12627370891332f7e281c5341aea1c Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 14:25:16 -0700 Subject: [PATCH 063/216] Release: 4.0.0-alpha.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28e782d5a..45dbe3b00 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gulp", "description": "The streaming build system", - "version": "3.9.1", + "version": "4.0.0-alpha.1", "homepage": "http://gulpjs.com", "repository": "gulpjs/gulp", "author": "Fractal (http://wearefractal.com/)", From cac9a8ad10f92ef3d3206cd5143700499281363c Mon Sep 17 00:00:00 2001 From: Contra Date: Mon, 26 Jan 2015 13:53:53 -0800 Subject: [PATCH 064/216] Update: Use unpublished vinyl-fs version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 45dbe3b00..90f654e1f 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dependencies": { "gulp-cli": "gulpjs/gulp-cli#4.0", "undertaker": "^0.3.0", - "vinyl-fs": "^0.3.0" + "vinyl-fs": "wearefractal/vinyl-fs" }, "devDependencies": { "coveralls": "^2.7.0", From 87e9cb687356b666cb6a67af44c62ed7bc42b68d Mon Sep 17 00:00:00 2001 From: Contra Date: Mon, 23 Feb 2015 15:17:11 -0800 Subject: [PATCH 065/216] Docs: Improve changelog --- CHANGELOG.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ade4772bd..328646775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 4.0.0 +### Task system changes + - replaced 3.x task system (orchestrator) with new task system (bach) - removed gulp.reset - removed 3 argument syntax for `gulp.task` @@ -10,13 +12,23 @@ - added single argument syntax for `gulp.task` which allows a named function to be used as the name of the task and task function. - added `gulp.tree` method for retrieving the task tree. Pass `{ deep: true }` for an `archy` compatible node list. - added `gulp.registry` for setting custom registries. + +### CLI changes + +- split CLI out into a module if you want to save bandwidth/disk space. you can install the gulp CLI using either `npm install gulp -g` or `npm install gulp-cli -g`, where gulp-cli is the smaller one (no module code included) +- add `--tasks-json` flag to CLI to dump the whole tree out for other tools to consume - added `--verify` flag to check the dependencies in package.json against the plugin blacklist. + +### vinyl/vinyl-fs changes + - added `gulp.symlink` which functions exactly like `gulp.dest`, but symlinks instead. - added `dirMode` param to `gulp.dest` and `gulp.symlink` which allows better control over the mode of the destination folder that is created. - globs passed to `gulp.src` will be evaluated in order, which means this is possible `gulp.src(['*.js', '!b*.js', 'bad.js'])` (exclude every JS file that starts with a b except bad.js) +- performance for gulp.src has improved massively + - `gulp.src(['**/*', '!b.js'])` will no longer eat CPU since negations happen during walking now - added `since` option to `gulp.src` which lets you only match files that have been modified since a certain date (for incremental builds) -- split CLI out into a module if you want to save bandwidth/disk space. you can install the gulp CLI using either `npm install gulp -g` or `npm install gulp-cli -g`, where gulp-cli is the smaller one (no module code included) -- add `--tasks-json` flag to CLI to dump the whole tree out for other tools to consume +- fixed `gulp.src` not following symlinks +- added `overwrite` option to `gulp.dest` which allows you to enable or disable overwriting of existing files ## 3.9.1 From 0ac0a0ec0c2fff79099a4c42d353069093287dab Mon Sep 17 00:00:00 2001 From: Damien Lebrun Date: Sun, 1 Mar 2015 00:14:03 +0000 Subject: [PATCH 066/216] Docs: Add gulp.series/gulp.parallel APIs, update gulp.task API & vinyl-fs options --- README.md | 2 +- docs/API.md | 370 ++++++++++++------ docs/getting-started.md | 3 +- .../combining-streams-to-handle-errors.md | 19 +- docs/recipes/delete-files-folder.md | 4 +- .../incremental-builds-with-concatenate.md | 2 +- ...tain-directory-structure-while-globbing.md | 2 +- docs/recipes/make-stream-from-buffer.md | 52 ++- docs/recipes/mocha-test-runner-with-gulp.md | 8 +- docs/recipes/running-tasks-in-series.md | 109 +++--- ...erver-with-livereload-and-css-injection.md | 4 +- docs/recipes/using-external-config-file.md | 11 +- 12 files changed, 377 insertions(+), 209 deletions(-) diff --git a/README.md b/README.md index 8e894f468..b492ea7d2 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ var paths = { }; // Not all tasks need to use streams -// A gulpfile is just another node program and you can use any package available on npm +// A gulpfile is just another node program and you can use all packages available on npm gulp.task('clean', function() { // You can use multiple globbing patterns as you would with `gulp.src` return del(['build']); diff --git a/docs/API.md b/docs/API.md index b5fac15bc..6c2443e7d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -8,10 +8,8 @@ Jump to: ### gulp.src(globs[, options]) -Emits files matching provided glob or an array of globs. -Returns a [stream](https://nodejs.org/api/stream.html) of [Vinyl files](https://github.com/gulpjs/vinyl-fs) -that can be [piped](https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) -to plugins. +Emits files matching provided glob or array of globs. +Returns a [stream] of [Vinyl files] that can be [piped] to plugins. ```javascript gulp.src('client/templates/*.pug') @@ -20,6 +18,8 @@ gulp.src('client/templates/*.pug') .pipe(gulp.dest('build/minified_templates')); ``` +`glob` refers to [node-glob syntax][node-glob] or it can be a direct file path. + #### globs Type: `String` or `Array` @@ -37,33 +37,48 @@ The following expression matches `a.js` and `bad.js`: gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js']) +Note that globs are evaluated in order, which means this is possible: +``` +// exclude every JS file that starts with a b except bad.js +gulp.src(['*.js', '!b*.js', 'bad.js']) +``` + #### options Type: `Object` Options to pass to [node-glob] through [glob-stream]. -gulp supports all [options supported by node-glob][node-glob documentation] and [glob-stream] except `ignore` and adds the following options. +gulp adds some additional options in addition to the +[options supported by node-glob][node-glob documentation] and [glob-stream]: ##### options.buffer Type: `Boolean` + Default: `true` -Setting this to `false` will return `file.contents` as a stream and not buffer files. This is useful when working with large files. **Note:** Plugins might not implement support for streams. +Setting this to `false` will return `file.contents` as a stream and not +buffer files. This is useful when working with large files. + +**Note:** Plugins might not implement support for streams. ##### options.read Type: `Boolean` + Default: `true` -Setting this to `false` will return `file.contents` as null and not read the file at all. +Setting this to `false` will return `file.contents` as null and not read +the file at all. ##### options.base Type: `String` -Default: everything before a glob starts (see [glob2base]) + +Default: everything before a glob starts (see [glob-parent]) E.g., consider `somefile.js` in `client/js/somedir`: ```js -gulp.src('client/js/**/*.js') // Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/` +// Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/` +gulp.src('client/js/**/*.js') .pipe(minify()) .pipe(gulp.dest('build')); // Writes 'build/somedir/somefile.js' @@ -72,9 +87,25 @@ gulp.src('client/js/**/*.js', { base: 'client' }) .pipe(gulp.dest('build')); // Writes 'build/js/somedir/somefile.js' ``` +##### options.since +Type: `Date` or `Number` + +Setting this to a Date or a time stamp will discard any file that have not been +modified since the time specified. + +##### options.passthrough +Type: `Boolean` + +Default: `false` + +If true, it will create a duplex stream which passes items through and +emits globbed files. + + ### gulp.dest(path[, options]) -Can be piped to and it will write files. Re-emits all data passed to it so you can pipe to multiple folders. Folders that don't exist will be created. +Can be piped to and it will write files. Re-emits all data passed to it so you +can pipe to multiple folders. Folders that don't exist will be created. ```javascript gulp.src('./client/templates/*.pug') @@ -85,32 +116,83 @@ gulp.src('./client/templates/*.pug') ``` The write path is calculated by appending the file relative path to the given -destination directory. In turn, relative paths are calculated against the file base. -See `gulp.src` above for more info. +destination directory. In turn, relative paths are calculated against +the file base. See `gulp.src` above for more info. #### path Type: `String` or `Function` -The path (output folder) to write files to. Or a function that returns it, the function will be provided a [vinyl File instance](https://github.com/gulpjs/vinyl). +The path (output folder) to write files to. Or a function that returns it, +the function will be provided a [vinyl File instance]. #### options Type: `Object` ##### options.cwd Type: `String` + Default: `process.cwd()` -`cwd` for the output folder, only has an effect if provided output folder is relative. +`cwd` for the output folder, only has an effect if provided output folder is +relative. ##### options.mode +Type: `String` or `Number` + +Default: the mode of the input file (file.stat.mode) or the process mode +if the input file has no mode property. + +Octal permission specifying the mode the files should be created with: e.g. +`"0744"`, `0744` or `484` (`0744` in base 10). + +##### options.dirMode +Type: `String` or `Number` + +Default: Default is the process mode. + +Octal permission specifying the mode the directory should be created with: e.g. +`"0755"`, `0755` or `493` (`0755` in base 10). + +##### options.overwrite +Type: `Boolean` + +Default: `true` + +Specify if existing files with the same path should be overwritten or not. + + +### gulp.symlink(folder[, options]) + +Functions exactly like `gulp.dest`, but will create symlinks instead of copying +a directory. + +#### folder +Type: `String` or `Function` + +A folder path or a function that receives in a file and returns a folder path. + +#### options +Type: `Object` + +##### options.cwd Type: `String` -Default: `0777` -Octal permission string specifying mode for any folders that need to be created for output folder. +Default: `process.cwd()` + +`cwd` for the output folder, only has an effect if provided output folder is +relative. + +##### options.dirMode +Type: `String` or `Number` + +Default: Default is the process mode. + +Octal permission specifying the mode the directory should be created with: e.g. +`"0755"`, `0755` or `493` (`0755` in base 10). -### gulp.task(name [, deps] [, fn]) +### gulp.task([name,] fn) -Define a task using [Orchestrator]. +Define a task using [Undertaker]. ```js gulp.task('somename', function() { @@ -121,20 +203,27 @@ gulp.task('somename', function() { #### name Type: `String` -The name of the task. Tasks that you want to run from the command line should not have spaces in them. +Optional, The name of the task. Tasks that you want to run from the command line +should not have spaces in them. -#### deps -Type: `Array` +If the name is not provided, the task will be named after the function +`name` attribute, set on any named function. -An array of tasks to be executed and completed before your task will run. +[Function.name] is not writable; it cannot be set or edited. +It will be empty for anonymous functions: ```js -gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { - // Do stuff -}); +function foo() {}; +foo.name === 'foo' // true + +var bar = function() {}; +bar.name === '' // true + +bar.name = 'bar' +bar.name === '' // true ``` -**Note:** Are your tasks running before the dependencies are complete? Make sure your dependency tasks are correctly using the async run hints: take in a callback or return a promise or event stream. +You should either provide the task name or avoid anonymous functions. You can also omit the function if you only want to run a bundle of dependency tasks: @@ -147,35 +236,31 @@ gulp.task('build', ['array', 'of', 'task', 'names']); #### fn Type: `Function` -The function performs the task's main operations. Generally this takes the form of: - -```js -gulp.task('buildStuff', function() { - // Do something that "builds stuff" - var stream = gulp.src(/*some source path*/) - .pipe(somePlugin()) - .pipe(someOtherPlugin()) - .pipe(gulp.dest(/*some destination*/)); - - return stream; - }); +The function that performs the task's operations. Generally it takes this form: ``` +gulp.task('somename', function() { + return gulp.src(['some/glob/**/*.ext']).pipe(someplugin()); +}) +``` + +Gulp tasks are asynchronous and Gulp uses [async-done] to wait for the tasks' +completion. Tasks are called with a callback parameter to call to signal +completion. Alternatively, Task can return a stream, a promise, a child process +or a RxJS observable to signal the end of the task. -#### Async task support +**Warning:** Sync tasks are not supported and your function will never complete +if the one of the above strategies is not used to signal completion. However, +thrown errors will be caught by Gulp. -Tasks can be made asynchronous if its `fn` does one of the following: +#### Async support ##### Accept a callback -```javascript -// run a command in a shell -var exec = require('child_process').exec; -gulp.task('jekyll', function(cb) { - // build Jekyll - exec('jekyll build', function(err) { - if (err) return cb(err); // return error - cb(); // finished task - }); +```js +var del = require('del'); + +gulp.task('clean', function(done) { + del(['.build/'], done); }); // use an async result in a pipe @@ -190,96 +275,143 @@ gulp.task('somename', function(cb) { }); ``` +The callback accepts an optional `Error` object. If it receives an error, +the task will fail. + ##### Return a stream ```js gulp.task('somename', function() { - var stream = gulp.src('client/**/*.js') + return gulp.src('client/**/*.js') .pipe(minify()) .pipe(gulp.dest('build')); - return stream; }); ``` ##### Return a promise -```javascript -var Q = require('q'); +```js +var Promise = require('promise'); +var del = require('del'); + +gulp.task('clean', function() { + return new Promise(function (resolve, reject) { + del(['.build/'], function(err) { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +}); +``` -gulp.task('somename', function() { - var deferred = Q.defer(); +or: +```js +var promisedDel = require('promised-del'); - // do async stuff - setTimeout(function() { - deferred.resolve(); - }, 1); +gulp.task('clean', function() { + return promisedDel(['.build/']); +}); +``` - return deferred.promise; +##### Return a child process + +```js +gulp.task('clean', function() { + return spawn('rm', ['-rf', path.join(__dirname, 'build')]); }); + ``` -**Note:** By default, tasks run with maximum concurrency -- e.g. it launches all the tasks at once and waits for nothing. If you want to create a series where tasks run in a particular order, you need to do two things: +##### Return a [RxJS] observable -- give it a hint to tell it when the task is done, -- and give it a hint that a task depends on completion of another. +```js +var Observable = require('rx').Observable; -For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order: +gulp.task('sometask', function() { + return Observable.return(42); +}); +``` -1. In task "one" you add a hint to tell it when the task is done. Either take in a callback and call it when you're -done or return a promise or stream that the engine should wait to resolve or end respectively. -2. In task "two" you add a hint telling the engine that it depends on completion of the first task. +### gulp.parallel(...tasks) -So this example would look like this: +Takes a number of task names or functions and returns a function of the composed +tasks or functions. -```js -var gulp = require('gulp'); +When using task names, the task should already be registered. -// takes in a callback so the engine knows when it'll be done -gulp.task('one', function(cb) { - // do stuff -- async or otherwise - cb(err); // if err is not null and not undefined, the run will stop, and note that it failed +When the returned function is executed, the tasks or functions will be executed +in parallel, all being executed at the same time. If an error occurs, +all execution will complete. + +```js +gulp.task('one', function(done) { + // do stuff + done(); }); -// identifies a dependent task must be complete before this one begins -gulp.task('two', ['one'], function() { - // task 'one' is done now +gulp.task('two', function(done) { + // do stuff + done(); }); -gulp.task('default', ['one', 'two']); +gulp.task('default', gulp.parallel('one', 'two', function(done) { + // do more stuff + done(); +})); ``` +#### tasks +Type: `Array`, `String` or `Function` -### gulp.watch(glob [, opts], tasks) or gulp.watch(glob [, opts, cb]) +A task name, a function or an array of either. -Watch files and do something when a file changes. This always returns an EventEmitter that emits `change` events. -### gulp.watch(glob[, opts], tasks) +### gulp.series(...tasks) -#### glob -Type: `String` or `Array` +Takes a number of task names or functions and returns a function of the composed +tasks or functions. -A single glob or array of globs that indicate which files to watch for changes. +When using task names, the task should already be registered. -#### opts -Type: `Object` +When the returned function is executed, the tasks or functions will be executed +in series, each waiting for the prior to finish. If an error occurs, +execution will stop. -Options, that are passed to [`gaze`](https://github.com/shama/gaze). +```js +gulp.task('one', function(done) { + // do stuff + done(); +}); + +gulp.task('two', function(done) { + // do stuff + done(); +}); + +gulp.task('default', gulp.series('one', 'two', function(done) { + // do more stuff + done(); +})); +``` #### tasks -Type: `Array` +Type: `Array`, `String` or `Function` + +A task name, a function or an array of either. -Names of task(s) to run when a file changes, added with `gulp.task()` + +### gulp.watch(glob[, opts], tasks) + +Watch files and do something when a file changes. ```js -var watcher = gulp.watch('js/**/*.js', ['uglify','reload']); -watcher.on('change', function(event) { - console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); -}); +gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); ``` -### gulp.watch(glob[, opts, cb]) - #### glob Type: `String` or `Array` @@ -288,36 +420,50 @@ A single glob or array of globs that indicate which files to watch for changes. #### opts Type: `Object` -Options, that are passed to [`gaze`](https://github.com/shama/gaze). +Options, that are passed to [`gaze`][gaze]. -#### cb(event) -Type: `Function` +#### tasks +Type: `Array`, `Function` or `String` + +A task name, a function or an array of either to run when a file changes. -Callback to be called on each change. +When `tasks` is an array, the tasks will be run in parallel: +``` +gulp.watch('*.js', [one, two]); +// is equivalent to +gulp.watch('*.js', gulp.parallel(one, two)); +``` +`gulp.watch` returns an `EventEmitter` object which emits `change` events with +the [gaze] `event`: ```js -gulp.watch('js/**/*.js', function(event) { - console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); +var watcher = gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); +watcher.on('change', function(event) { + console.log('File ' + event.path + ' was ' + event.type); }); ``` -The callback will be passed an object, `event`, that describes the change: - ##### event.type -Type: `String` +Type: String -The type of change that occurred, either `added`, `changed`, `deleted` or `renamed`. +The type of change that occurred, either "added", "changed" or "deleted". ##### event.path -Type: `String` +Type: String The path to the file that triggered the event. - -[node-glob]: https://github.com/isaacs/node-glob -[node-glob documentation]: https://github.com/isaacs/node-glob#options -[node-glob syntax]: https://github.com/isaacs/node-glob +[Function.name]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name +[gaze]: https://github.com/shama/gaze [glob-stream]: https://github.com/gulpjs/glob-stream +[glob-parent]: https://github.com/es128/glob-parent [gulp-if]: https://github.com/robrich/gulp-if -[Orchestrator]: https://github.com/robrich/orchestrator -[glob2base]: https://github.com/wearefractal/glob2base +[node-glob documentation]: https://github.com/isaacs/node-glob#options +[node-glob]: https://github.com/isaacs/node-glob +[piped]: http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options +[RxJS]: https://www.npmjs.com/package/rx +[stream]: http://nodejs.org/api/stream.html +[async-done]: https://www.npmjs.com/package/async-done +[Undertaker]: https://github.com/phated/undertaker +[vinyl File instance]: https://github.com/gulpjs/vinyl +[Vinyl files]: https://github.com/gulpjs/vinyl-fs diff --git a/docs/getting-started.md b/docs/getting-started.md index 980c22189..06e0c9b8a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -37,8 +37,9 @@ In your project directory, create a file named `gulpfile.js` in your project roo ```js var gulp = require('gulp'); -gulp.task('default', function() { +gulp.task('default', function(done) { // place code for your default task here + done(); }); ``` diff --git a/docs/recipes/combining-streams-to-handle-errors.md b/docs/recipes/combining-streams-to-handle-errors.md index e376f6db5..7c38654af 100644 --- a/docs/recipes/combining-streams-to-handle-errors.md +++ b/docs/recipes/combining-streams-to-handle-errors.md @@ -12,16 +12,13 @@ var uglify = require('gulp-uglify'); var gulp = require('gulp'); gulp.task('test', function() { - var combined = combiner.obj([ - gulp.src('bootstrap/js/*.js'), - uglify(), - gulp.dest('public/bootstrap') - ]); - - // any errors in the above streams will get caught - // by this listener, instead of being thrown: - combined.on('error', console.error.bind(console)); - - return combined; + return combiner.obj([ + gulp.src('bootstrap/js/*.js'), + uglify(), + gulp.dest('public/bootstrap') + ]) + // any errors in the above streams will get caught + // by this listener, instead of being thrown: + .on('error', console.error.bind(console)); }); ``` diff --git a/docs/recipes/delete-files-folder.md b/docs/recipes/delete-files-folder.md index 05e37762f..1373ce6a1 100644 --- a/docs/recipes/delete-files-folder.md +++ b/docs/recipes/delete-files-folder.md @@ -39,7 +39,7 @@ gulp.task('clean:mobile', function () { ]); }); -gulp.task('default', ['clean:mobile']); +gulp.task('default', gulp.series('clean:mobile')); ``` @@ -76,7 +76,7 @@ gulp.task('clean:tmp', function () { .pipe(gulp.dest('dist')); }); -gulp.task('default', ['clean:tmp']); +gulp.task('default', gulp.series('clean:tmp')); ``` This will only delete the tmp dir. diff --git a/docs/recipes/incremental-builds-with-concatenate.md b/docs/recipes/incremental-builds-with-concatenate.md index a1435eb8a..c9fdaa0de 100644 --- a/docs/recipes/incremental-builds-with-concatenate.md +++ b/docs/recipes/incremental-builds-with-concatenate.md @@ -27,7 +27,7 @@ gulp.task('scripts', function() { }); gulp.task('watch', function () { - var watcher = gulp.watch(scriptsGlob, ['scripts']); // watch the same files in our scripts task + var watcher = gulp.watch(scriptsGlob, gulp.series('scripts')); // watch the same files in our scripts task watcher.on('change', function (event) { if (event.type === 'deleted') { // if a file is deleted, forget about it delete cached.caches.scripts[event.path]; // gulp-cached remove api diff --git a/docs/recipes/maintain-directory-structure-while-globbing.md b/docs/recipes/maintain-directory-structure-while-globbing.md index 95d644691..aadd32ef4 100644 --- a/docs/recipes/maintain-directory-structure-while-globbing.md +++ b/docs/recipes/maintain-directory-structure-while-globbing.md @@ -27,7 +27,7 @@ If you want to maintain the structure, you need to pass `{base: '.'}` to `gulp.s ```js gulp.task('task', function () { - gulp.src(['index.html', + return gulp.src(['index.html', 'css/**', 'js/**', 'lib/**', diff --git a/docs/recipes/make-stream-from-buffer.md b/docs/recipes/make-stream-from-buffer.md index 1125451f6..f11c11612 100644 --- a/docs/recipes/make-stream-from-buffer.md +++ b/docs/recipes/make-stream-from-buffer.md @@ -35,7 +35,6 @@ A simple and modular way to do this would be the following: ```js var gulp = require('gulp'); -var runSequence = require('run-sequence'); var source = require('vinyl-source-stream'); var vinylBuffer = require('vinyl-buffer'); var tap = require('gulp-tap'); @@ -50,13 +49,13 @@ var memory = {}; // we'll keep our assets in memory gulp.task('load-lib-files', function() { // read the lib files from the disk return gulp.src('src/libs/*.js') - // concatenate all lib files into one - .pipe(concat('libs.concat.js')) - // tap into the stream to get each file's data - .pipe(tap(function(file) { - // save the file contents in memory - memory[path.basename(file.path)] = file.contents.toString(); - })); + // concatenate all lib files into one + .pipe(concat('libs.concat.js')) + // tap into the stream to get each file's data + .pipe(tap(function(file) { + // save the file contents in memory + memory[path.basename(file.path)] = file.contents.toString(); + })); }); gulp.task('load-versions', function() { @@ -111,30 +110,29 @@ gulp.task('write-versions', function() { }); //============================================ our main task -gulp.task('default', function(taskDone) { - runSequence( - ['load-lib-files', 'load-versions'], // load the files in parallel - 'write-versions', // ready to write once all resources are in memory - taskDone // done - ); -}); +gulp.task('default', gulp.series( + // load the files in parallel + gulp.parallel('load-lib-files', 'load-versions'), + // ready to write once all resources are in memory + 'write-versions' + ) +); //============================================ our watcher task // only watch after having run 'default' once so that all resources // are already in memory -gulp.task('watch', ['default'], function() { - gulp.watch('./src/libs/*.js', function() { - runSequence( - 'load-lib-files', // we only have to load the changed files +gulp.task('watch', gulp.series( + 'default', + function() { + gulp.watch('./src/libs/*.js', gulp.series( + 'load-lib-files', 'write-versions' - ); - }); + )); - gulp.watch('./src/versions/*.js', function() { - runSequence( - 'load-versions', // we only have to load the changed files + gulp.watch('./src/versions/*.js', gulp.series( + 'load-lib-files', 'write-versions' - ); - }); -}); + )); + } +)); ``` diff --git a/docs/recipes/mocha-test-runner-with-gulp.md b/docs/recipes/mocha-test-runner-with-gulp.md index 69788aad8..775e3a722 100644 --- a/docs/recipes/mocha-test-runner-with-gulp.md +++ b/docs/recipes/mocha-test-runner-with-gulp.md @@ -28,13 +28,13 @@ var gulp = require('gulp'); var mocha = require('gulp-mocha'); var gutil = require('gulp-util'); -gulp.task('default', function() { - gulp.watch(['lib/**', 'test/**'], ['mocha']); -}); - gulp.task('mocha', function() { return gulp.src(['test/*.js'], { read: false }) .pipe(mocha({ reporter: 'list' })) .on('error', gutil.log); }); + +gulp.task('watch-mocha', function() { + gulp.watch(['lib/**', 'test/**'], gulp.series('mocha')); +}); ``` diff --git a/docs/recipes/running-tasks-in-series.md b/docs/recipes/running-tasks-in-series.md index 4ac2e6c43..5dfad24d0 100644 --- a/docs/recipes/running-tasks-in-series.md +++ b/docs/recipes/running-tasks-in-series.md @@ -1,68 +1,89 @@ -# Running tasks in series, i.e. Task Dependency +# Running tasks in series -By default, tasks run with maximum concurrency -- e.g. it launches all the tasks at once and waits for nothing. If you want to create a series where tasks run in a particular order, you need to do two things: - -- give it a hint to tell it when the task is done, -- and give it a hint that a task depends on completion of another. - -For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order: - -1. In task "one" you add a hint to tell it when the task is done. Either take in a callback and call it when you're done or return a promise or stream that the engine should wait to resolve or end respectively. - -2. In task "two" you add a hint telling the engine that it depends on completion of the first task. - -So this example would look like: +By default, gulp CLI run tasks with maximum concurrency - e.g. it launches +all the tasks at once and waits for nothing. If you want to create a series +where tasks run in a particular order, you should use `gulp.series`; ```js var gulp = require('gulp'); +var doAsyncStuff = require('./stuff'); -// takes in a callback so the engine knows when it'll be done -gulp.task('one', function (cb) { - // do stuff -- async or otherwise - fs.writeFile('filename', 'data', opts, function (err) { - cb(err); // if err is not null and not undefined, the orchestration will stop, and 'two' will not run - }); +gulp.task('one', function(done) { + doAsyncStuff(function(err){ + done(err); + }); }); -// identifies a dependent task must be complete before this one begins -gulp.task('two', ['one'], function() { - // task 'one' is done now +gulp.task('two', function(done) { + // do things + done(); }); -gulp.task('default', ['one', 'two']); -// alternatively: gulp.task('default', ['two']); +gulp.task('default', gulp.series('one', 'two')); ``` -Another example, which returns the stream instead of using a callback: - +Another example, using a dependency pattern. It uses +[`async-once`](https://www.npmjs.com/package/async-once) to run the `clean` +task operations only once: ```js var gulp = require('gulp'); var del = require('del'); // rm -rf - -gulp.task('clean', function() { - return del(['output']); +var once = require('async-once'); + +gulp.task('clean', once(function(done) { + // run only once. + // for the next call to the clean task, once will call done with + // the same arguments as the first call. + del(['output'], done); +})); + +gulp.task('templates', gulp.series('clean', function() { + return gulp.src(['src/templates/*.hbs']) + // do some concatenation, minification, etc. + .pipe(gulp.dest('output/templates/')); }); -gulp.task('templates', ['clean'], function() { - var stream = gulp.src(['src/templates/*.hbs']) - // do some concatenation, minification, etc. - .pipe(gulp.dest('output/templates/')); - return stream; // return the stream as the completion hint +gulp.task('styles', gulp.series('clean', function() { + return gulp.src(['src/styles/app.less']) + // do some hinting, minification, etc. + .pipe(gulp.dest('output/css/app.css')); +})); + +// templates and styles will be processed in parallel. +// `clean` will be guaranteed to complete before either start. +// `clean` operations will not be run twice, +// even though it is called as a dependency twice. +gulp.task('build', gulp.parallel('templates', 'styles')); + +// an alias. +gulp.task('default', gulp.parallel('build')); +``` + +Note that it's an anti-pattern in Gulp 4 and the logs will show the clean task +running twice. Instead, `templates` and `style` should use dedicated `clean:*` +tasks: +```js +var gulp = require('gulp'); +var del = require('del'); +gulp.task('clean:templates', function() { + return del(['output/templates/']); }); -gulp.task('styles', ['clean'], function() { - var stream = gulp.src(['src/styles/app.less']) - // do some hinting, minification, etc. - .pipe(gulp.dest('output/css/app.css')); - return stream; +gulp.task('templates', gulp.series('clean:templates', function() { + return gulp.src(['src/templates/*.hbs']) + .pipe(gulp.dest('output/templates/')); }); -gulp.task('build', ['templates', 'styles']); +gulp.task('clean:styles', function() { + return del(['output/css/']); +}); -// templates and styles will be processed in parallel. -// clean will be guaranteed to complete before either start. -// clean will not be run twice, even though it is called as a dependency twice. +gulp.task('styles', gulp.series('clean:styles', function() { + return gulp.src(['src/styles/app.less']) + .pipe(gulp.dest('output/css/app.css')); +})); -gulp.task('default', ['build']); +gulp.task('build', gulp.parallel('templates', 'styles')); +gulp.task('default', gulp.parallel('build')); ``` diff --git a/docs/recipes/server-with-livereload-and-css-injection.md b/docs/recipes/server-with-livereload-and-css-injection.md index 7a14a667b..74280505d 100644 --- a/docs/recipes/server-with-livereload-and-css-injection.md +++ b/docs/recipes/server-with-livereload-and-css-injection.md @@ -88,14 +88,14 @@ gulp.task('sass', function() { }); // watch Sass files for changes, run the Sass preprocessor with the 'sass' task and reload -gulp.task('serve', ['sass'], function() { +gulp.task('serve', gulp.series('sass', function() { browserSync({ server: { baseDir: 'app' } }); - gulp.watch('app/scss/*.scss', ['sass']); + gulp.watch('scss/*.scss', gulp.series('sass')); }); ``` diff --git a/docs/recipes/using-external-config-file.md b/docs/recipes/using-external-config-file.md index 5c83779d9..6c740761b 100644 --- a/docs/recipes/using-external-config-file.md +++ b/docs/recipes/using-external-config-file.md @@ -30,9 +30,11 @@ Beneficial because it's keeping tasks DRY and config.json can be used by another ###### `gulpfile.js` ```js -// npm install --save-dev gulp gulp-uglify +// npm install --save-dev gulp gulp-uglify merge-stream var gulp = require('gulp'); var uglify = require('gulp-uglify'); +var merge = require('merge-stream'); + var config = require('./config.json'); function doStuff(cfg) { @@ -42,7 +44,10 @@ function doStuff(cfg) { } gulp.task('dry', function() { - doStuff(config.desktop); - doStuff(config.mobile); + // return a stream to signal completion + return merge([ + doStuff(config.desktop), + doStuff(config.mobile) + ]) }); ``` From 96febce29ae10e4b4a4da001f49ddbe63bf8d0f9 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Fri, 13 Mar 2015 19:59:57 -0700 Subject: [PATCH 067/216] Upgrade: Update undertaker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 90f654e1f..73f3f04ab 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "man": "gulp.1", "dependencies": { "gulp-cli": "gulpjs/gulp-cli#4.0", - "undertaker": "^0.3.0", + "undertaker": "^0.7.0", "vinyl-fs": "wearefractal/vinyl-fs" }, "devDependencies": { From 8806326a4f3823b6deb7e29b31bc0fd82057b653 Mon Sep 17 00:00:00 2001 From: Callum Macrae Date: Tue, 17 Mar 2015 07:48:29 +0000 Subject: [PATCH 068/216] Docs: Added allowEmpty option for gulp.src --- docs/API.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/API.md b/docs/API.md index 6c2443e7d..1d4a903d8 100644 --- a/docs/API.md +++ b/docs/API.md @@ -101,6 +101,21 @@ Default: `false` If true, it will create a duplex stream which passes items through and emits globbed files. +### options.allowEmpty +Type: `Boolean` +Default: `false` + +When true, will allow singular globs to fail to match. Otherwise, globs which are only supposed to match one file (such as `./foo/bar.js`) will cause an error to be thrown if they don't match. + +```js +// Emits an error if app/scripts.js doesn't exist +gulp.src('app/scripts.js') + .pipe(...); + +// Won't emit an error +gulp.src('app/scripts.js', { allowEmpty: true }) + .pipe(...); +``` ### gulp.dest(path[, options]) From d942cf56f774ba47fb344510c8b10b7efe536239 Mon Sep 17 00:00:00 2001 From: Damien Lebrun Date: Fri, 20 Mar 2015 13:07:59 +0000 Subject: [PATCH 069/216] Docs: Improve incremental build example & add gulp.lastRun API --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++----- docs/API.md | 33 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b492ea7d2..6fbc95b20 100644 --- a/README.md +++ b/README.md @@ -80,12 +80,56 @@ gulp.task('default', gulp.series('clean', 'all')); ## Incremental Builds -We recommend these plugins: +You can filter out unchanged files between runs of a task using +the `gulp.src` function's `since` option and `gulp.lastRun`: +```js +gulp.task('images', function() { + return gulp.src(paths.images, {since: gulp.lastRun('images')}) + .pipe(imagemin({optimizationLevel: 5})) + .pipe(gulp.dest('build/img')); +}); + +gulp.task('watch', function() { + gulp.watch(paths.images, 'images'); +}); +``` +Task run times are saved in memory and are lost when gulp exits. It will only +save time during the `watch` task when running the `images` task +for a second time. -- [gulp-changed](https://github.com/sindresorhus/gulp-changed) - only pass through changed files -- [gulp-cached](https://github.com/contra/gulp-cached) - in-memory file cache, not for operation on sets of files -- [gulp-remember](https://github.com/ahaurw01/gulp-remember) - pairs nicely with gulp-cached -- [gulp-newer](https://github.com/tschaub/gulp-newer) - pass through newer source files only, supports many:1 source:dest +If you want to compare modification time between files instead, we recommend these plugins: +- [gulp-changed]; +- or [gulp-newer] - supports many:1 source:dest. + +[gulp-newer] example: +```js +gulp.task('images', function() { + var dest = 'build/img'; + return gulp.src(paths.images) + .pipe(newer(dest)) // pass through newer images only + .pipe(imagemin({optimizationLevel: 5})) + .pipe(gulp.dest(dest)); +}); +``` + +If you can't simply filter out unchanged files, but need them in a later phase +of the stream, we recommend these plugins: +- [gulp-cached] - in-memory file cache, not for operation on sets of files +- [gulp-remember] - pairs nicely with gulp-cached + +[gulp-remember] example: +```js +gulp.task('scripts', function () { + return gulp.src(scriptsGlob) + .pipe(cache('scripts')) // only pass through changed files + .pipe(header('(function () {')) // do special things to the changed files... + .pipe(footer('})();')) // for example, + // add a simple module wrap to each file + .pipe(remember('scripts')) // add back all files to the stream + .pipe(concat('app.js')) // do things that require all files + .pipe(gulp.dest('public/')) +}); +``` ## Want to test the latest and greatest? @@ -135,3 +179,8 @@ Become a sponsor to get your logo on our README on Github. [backers-image]: https://opencollective.com/gulpjs/backers.svg [sponsors-image]: https://opencollective.com/gulpjs/sponsors.svg + +[gulp-cached]: https://github.com/contra/gulp-cached +[gulp-remember]: https://github.com/ahaurw01/gulp-remember +[gulp-changed]: https://github.com/sindresorhus/gulp-changed +[gulp-newer]: https://github.com/tschaub/gulp-newer diff --git a/docs/API.md b/docs/API.md index 1d4a903d8..a885bbcd0 100644 --- a/docs/API.md +++ b/docs/API.md @@ -350,6 +350,38 @@ gulp.task('sometask', function() { }); ``` +### lastRun(taskName, [timeResolution]) + +Returns the timestamp of the last time the task ran successfully. The time +will be the time the task started. Returns `undefined` if the task has +not run yet. + +#### taskName + +Type: `String` + +The name of the registered task or of a function. + +#### timeResolution + +Type: `Number`. + +Default: `1000` on node v0.10, `0` on node v0.12 (and iojs v1.5). + +Set the time resolution of the returned timestamps. Assuming +the task named "someTask" ran at `1426000004321`: +- `gulp.lastRun('someTask', 1000)` would return `1426000004000`. +- `gulp.lastRun('someTask', 100)` would return `1426000004300`. + +`timeResolution` allows you to compare a run time to a file [mtime stat][fs stats] +attribute. This attribute time resolution may vary depending of the node version +and the file system used: +- on node v0.10, a file [mtime stat][fs stats] time resolution of any files will be 1s at best; +- on node v0.12 and iojs v1.5, 1ms at best; +- for files on FAT32, the mtime time resolution is 2s; +- on HFS+ and Ext3, 1s; +- on NTFS, 1s on node v0.10, 100ms on node 0.12; +- on Ext4, 1s on node v0.10, 1ms on node 0.12. ### gulp.parallel(...tasks) @@ -482,3 +514,4 @@ The path to the file that triggered the event. [Undertaker]: https://github.com/phated/undertaker [vinyl File instance]: https://github.com/gulpjs/vinyl [Vinyl files]: https://github.com/gulpjs/vinyl-fs +[fs stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats From 1abb5eda814a42acf1495de6f73842a14365b4db Mon Sep 17 00:00:00 2001 From: Damien Lebrun Date: Sun, 22 Mar 2015 10:51:07 +0000 Subject: [PATCH 070/216] Docs: Outline using named functions and when to use gulp.task --- CHANGELOG.md | 2 +- README.md | 72 ++++++++++++++++++++++++++++++---------------------- docs/API.md | 35 ++++++++++++------------- 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 328646775..860674c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - replaced 3.x task system (orchestrator) with new task system (bach) - removed gulp.reset - removed 3 argument syntax for `gulp.task` - - using strings when registering with `gulp.task` should only be done when you will call the task with the CLI + - `gulp.task` should only be used when you will call the task with the CLI - added `gulp.series` and `gulp.parallel` methods for composing tasks. Everything must use these now. - added single argument syntax for `gulp.task` which allows a named function to be used as the name of the task and task function. - added `gulp.tree` method for retrieving the task tree. Pass `{ deep: true }` for an `archy` compatible node list. diff --git a/README.md b/README.md index 6fbc95b20..e8cf13d4c 100644 --- a/README.md +++ b/README.md @@ -40,16 +40,38 @@ var paths = { images: 'client/img/**/*' }; +/* Register some tasks to expose to the cli */ +gulp.task('build', gulp.series( + clean, + gulp.parallel(scripts, images) +)); +gulp.task(clean); +gulp.task(watch); + +// The default task (called when you run `gulp` from cli) +gulp.task('default', gulp.series('build')); + + +/* Define our tasks using plain functions */ + // Not all tasks need to use streams // A gulpfile is just another node program and you can use all packages available on npm -gulp.task('clean', function() { +function clean() { // You can use multiple globbing patterns as you would with `gulp.src` return del(['build']); -}); +} -gulp.task('scripts', function() { - // Minify and copy all JavaScript (except vendor scripts) - // with sourcemaps all the way down +// Copy all static images +function images() { + return gulp.src(paths.images) + // Pass in options to the task + .pipe(imagemin({optimizationLevel: 5})) + .pipe(gulp.dest('build/img')); +} + +// Minify and copy all JavaScript (except vendor scripts) +// with sourcemaps all the way down +function scripts() { return gulp.src(paths.scripts) .pipe(sourcemaps.init()) .pipe(coffee()) @@ -57,25 +79,13 @@ gulp.task('scripts', function() { .pipe(concat('all.min.js')) .pipe(sourcemaps.write()) .pipe(gulp.dest('build/js')); -}); - -// Copy all static images -gulp.task('images', function() { - return gulp.src(paths.images) - // Pass in options to the task - .pipe(imagemin({optimizationLevel: 5})) - .pipe(gulp.dest('build/img')); -}); +} // Rerun the task when a file changes -gulp.task('watch', function() { - gulp.watch(paths.scripts, 'scripts'); - gulp.watch(paths.images, 'images'); -}); - -gulp.task('all', gulp.parallel('watch', 'scripts', 'images')); -// The default task (called when you run `gulp` from cli) -gulp.task('default', gulp.series('clean', 'all')); +function watch() { + gulp.watch(paths.scripts, scripts); + gulp.watch(paths.images, images); +} ``` ## Incremental Builds @@ -83,15 +93,15 @@ gulp.task('default', gulp.series('clean', 'all')); You can filter out unchanged files between runs of a task using the `gulp.src` function's `since` option and `gulp.lastRun`: ```js -gulp.task('images', function() { +function images() { return gulp.src(paths.images, {since: gulp.lastRun('images')}) .pipe(imagemin({optimizationLevel: 5})) .pipe(gulp.dest('build/img')); -}); +} -gulp.task('watch', function() { - gulp.watch(paths.images, 'images'); -}); +function watch() { + gulp.watch(paths.images, images); +} ``` Task run times are saved in memory and are lost when gulp exits. It will only save time during the `watch` task when running the `images` task @@ -103,13 +113,13 @@ If you want to compare modification time between files instead, we recommend the [gulp-newer] example: ```js -gulp.task('images', function() { +function images() { var dest = 'build/img'; return gulp.src(paths.images) .pipe(newer(dest)) // pass through newer images only .pipe(imagemin({optimizationLevel: 5})) .pipe(gulp.dest(dest)); -}); +} ``` If you can't simply filter out unchanged files, but need them in a later phase @@ -119,7 +129,7 @@ of the stream, we recommend these plugins: [gulp-remember] example: ```js -gulp.task('scripts', function () { +function scripts() { return gulp.src(scriptsGlob) .pipe(cache('scripts')) // only pass through changed files .pipe(header('(function () {')) // do special things to the changed files... @@ -128,7 +138,7 @@ gulp.task('scripts', function () { .pipe(remember('scripts')) // add back all files to the stream .pipe(concat('app.js')) // do things that require all files .pipe(gulp.dest('public/')) -}); +} ``` ## Want to test the latest and greatest? diff --git a/docs/API.md b/docs/API.md index a885bbcd0..d9e3a0778 100644 --- a/docs/API.md +++ b/docs/API.md @@ -207,7 +207,8 @@ Octal permission specifying the mode the directory should be created with: e.g. ### gulp.task([name,] fn) -Define a task using [Undertaker]. +Define a task exposed to gulp-cli, `gulp.series`, `gulp.parallel` and +`gulp.lastRun`; inherited from [undertaker]. ```js gulp.task('somename', function() { @@ -215,16 +216,26 @@ gulp.task('somename', function() { }); ``` +Or get a task that has been registered. + +```js +// somenameTask will be the registered task function +var somenameTask = gulp.task('somename'); +``` + #### name Type: `String` -Optional, The name of the task. Tasks that you want to run from the command line -should not have spaces in them. - If the name is not provided, the task will be named after the function -`name` attribute, set on any named function. +`name` or `displayName` property. The name argument is required if the +`name` and `displayName` properties of `fn` are empty. + +Since the task can be run from the command line, you should avoid using +spaces in task names. -[Function.name] is not writable; it cannot be set or edited. +**Note:** [Function.name] is not writable; it cannot be set or edited. If +you need to assign a function name or use characters that aren't allowed +in function names, use the `displayName` property. It will be empty for anonymous functions: ```js @@ -238,16 +249,6 @@ bar.name = 'bar' bar.name === '' // true ``` -You should either provide the task name or avoid anonymous functions. - -You can also omit the function if you only want to run a bundle of dependency tasks: - -```js -gulp.task('build', ['array', 'of', 'task', 'names']); -``` - -**Note:** The tasks will run in parallel (all at once), so don't assume that the tasks will start/finish in order. - #### fn Type: `Function` @@ -511,7 +512,7 @@ The path to the file that triggered the event. [RxJS]: https://www.npmjs.com/package/rx [stream]: http://nodejs.org/api/stream.html [async-done]: https://www.npmjs.com/package/async-done -[Undertaker]: https://github.com/phated/undertaker +[undertaker]: https://github.com/gulpjs/undertaker [vinyl File instance]: https://github.com/gulpjs/vinyl [Vinyl files]: https://github.com/gulpjs/vinyl-fs [fs stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats From ad627e6110e7a414085e8eaedec0d0131c3dce93 Mon Sep 17 00:00:00 2001 From: Damien Lebrun Date: Sun, 22 Mar 2015 21:52:29 +0000 Subject: [PATCH 071/216] Docs: Mention .description property & add usage examples --- docs/API.md | 71 +++++++++++++++++++++++++++++++++++++++-------------- docs/CLI.md | 2 +- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/docs/API.md b/docs/API.md index d9e3a0778..a4bca45c5 100644 --- a/docs/API.md +++ b/docs/API.md @@ -211,7 +211,7 @@ Define a task exposed to gulp-cli, `gulp.series`, `gulp.parallel` and `gulp.lastRun`; inherited from [undertaker]. ```js -gulp.task('somename', function() { +gulp.task(function someTask() { // Do stuff }); ``` @@ -219,8 +219,8 @@ gulp.task('somename', function() { Or get a task that has been registered. ```js -// somenameTask will be the registered task function -var somenameTask = gulp.task('somename'); +// someTask will be the registered task function +var someTask = gulp.task('someTask'); ``` #### name @@ -233,6 +233,35 @@ If the name is not provided, the task will be named after the function Since the task can be run from the command line, you should avoid using spaces in task names. +#### fn + +The function that performs the task's operations. Generally it takes this form: + +```js +function someTask() { + return gulp.src(['some/glob/**/*.ext']).pipe(someplugin()); +} +someTask.description = 'Does something'; + +gulp.task(someTask) +``` + +Gulp tasks are asynchronous and Gulp uses [async-done] to wait for the task's +completion. Tasks are called with a callback parameter to call to signal +completion. Alternatively, Task can return a stream, a promise, a child process +or a RxJS observable to signal the end of the task. + +**Warning:** Sync tasks are not supported and your function will never complete +if the one of the above strategies is not used to signal completion. However, +thrown errors will be caught by Gulp. + +#### fn properties + +##### fn.name + +`gulp.task` names the task after the function `name` property +if the optional `name` parameter of `gulp.task` is not provided. + **Note:** [Function.name] is not writable; it cannot be set or edited. If you need to assign a function name or use characters that aren't allowed in function names, use the `displayName` property. @@ -249,24 +278,30 @@ bar.name = 'bar' bar.name === '' // true ``` -#### fn -Type: `Function` +##### fn.displayName -The function that performs the task's operations. Generally it takes this form: -``` -gulp.task('somename', function() { - return gulp.src(['some/glob/**/*.ext']).pipe(someplugin()); -}) -``` +`gulp.task` names the task after the function `displayName` property +if function is anonymous and the optional `name` parameter of `gulp.task` +is not provided. -Gulp tasks are asynchronous and Gulp uses [async-done] to wait for the tasks' -completion. Tasks are called with a callback parameter to call to signal -completion. Alternatively, Task can return a stream, a promise, a child process -or a RxJS observable to signal the end of the task. +##### fn.description -**Warning:** Sync tasks are not supported and your function will never complete -if the one of the above strategies is not used to signal completion. However, -thrown errors will be caught by Gulp. +gulp-cli prints this description alongside the task name when listing tasks: +```js +var gulp = require('gulp'); + +function test(done){ + done(); +} +test.description = 'I do nothing'; + +gulp.task(test); +``` +```shell +$> gulp --tasks +[12:00:02] Tasks for ~/Documents/some-project/gulpfile.js +[12:00:02] └── test I do nothing +``` #### Async support diff --git a/docs/CLI.md b/docs/CLI.md index 5bfd67922..0666881dd 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -8,7 +8,7 @@ gulp has very few flags to know about. All other flags are for tasks to use if n - `--require ` will require a module before running the gulpfile. This is useful for transpilers but also has other applications. You can use multiple `--require` flags - `--gulpfile ` will manually set path of gulpfile. Useful if you have multiple gulpfiles. This will set the CWD to the gulpfile directory as well - `--cwd ` will manually set the CWD. The search for the gulpfile, as well as the relativity of all requires will be from here -- `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile +- `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile. It will include the task names and their [description](./API.md#fndescription). - `--tasks-simple` will display a plaintext list of tasks for the loaded gulpfile - `--verify` will verify plugins referenced in project's package.json against the plugins blacklist - `--color` will force gulp and gulp plugins to display colors even when no color support is detected From b085e951ee4e0b5b0a4c9f4810444a08659301b1 Mon Sep 17 00:00:00 2001 From: Damien Lebrun Date: Wed, 25 Mar 2015 17:42:58 +0000 Subject: [PATCH 072/216] Breaking: Remove array & string task support from gulp.watch --- docs/API.md | 18 +++++++----------- index.js | 5 +---- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/docs/API.md b/docs/API.md index a4bca45c5..3cc60467a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -487,7 +487,7 @@ Type: `Array`, `String` or `Function` A task name, a function or an array of either. -### gulp.watch(glob[, opts], tasks) +### gulp.watch(glob[, opts], fn) Watch files and do something when a file changes. @@ -495,6 +495,9 @@ Watch files and do something when a file changes. gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); ``` +In the example, `gulp.watch` runs the function returned by gulp.parallel each +time a file with the `js` extension in `js/` is updated. + #### glob Type: `String` or `Array` @@ -505,17 +508,10 @@ Type: `Object` Options, that are passed to [`gaze`][gaze]. -#### tasks -Type: `Array`, `Function` or `String` - -A task name, a function or an array of either to run when a file changes. +#### fn +Type: `Function` -When `tasks` is an array, the tasks will be run in parallel: -``` -gulp.watch('*.js', [one, two]); -// is equivalent to -gulp.watch('*.js', gulp.parallel(one, two)); -``` +An [async](#async-support) function to run when a file changes. `gulp.watch` returns an `EventEmitter` object which emits `change` events with the [gaze] `event`: diff --git a/index.js b/index.js index 13bc263c1..3cb735384 100644 --- a/index.js +++ b/index.js @@ -12,10 +12,7 @@ util.inherits(Gulp, Undertaker); Gulp.prototype.src = vfs.src; Gulp.prototype.dest = vfs.dest; Gulp.prototype.watch = function(glob, opt, task) { - var isFunction = (typeof opt === 'function'); - var isString = (typeof opt === 'string'); - var isArray = Array.isArray(opt); - if (isFunction || isString || isArray) { + if (typeof opt === 'function') { task = opt; opt = null; } From c4b6922630dc15f9c7c57890d0d49375bdfeb961 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 1 Apr 2015 12:24:26 -0700 Subject: [PATCH 073/216] Docs: Add note about opt-in symlink following --- docs/API.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/API.md b/docs/API.md index 3cc60467a..f9a2e3287 100644 --- a/docs/API.md +++ b/docs/API.md @@ -43,6 +43,9 @@ Note that globs are evaluated in order, which means this is possible: gulp.src(['*.js', '!b*.js', 'bad.js']) ``` +**Note:** glob symlink following behavior is opt-in and you must specify +`follow: true` in the options object that is passed to [node-glob]. + #### options Type: `Object` From 1d70cfbf064d5c6ab1677a4389e29fe276b0657b Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 1 Apr 2015 12:50:55 -0700 Subject: [PATCH 074/216] Breaking: Only support tasks that are functions in gulp.watch --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 3cb735384..d8a18111b 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,7 @@ Gulp.prototype.watch = function(glob, opt, task) { } var fn; - if (task) { + if (typeof task === 'function') { fn = this.parallel(task); } From 6c46116b2c9254d69b596b2fd1102e004430d786 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 1 Apr 2015 14:23:20 -0700 Subject: [PATCH 075/216] New: Expose vfs.symlink API on gulp --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index d8a18111b..6969fa141 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ util.inherits(Gulp, Undertaker); Gulp.prototype.src = vfs.src; Gulp.prototype.dest = vfs.dest; +Gulp.prototype.symlink = vfs.symlink; Gulp.prototype.watch = function(glob, opt, task) { if (typeof opt === 'function') { task = opt; From f7e7d4c16e97d8f240ec2bad2377e9ef4bed1674 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Fri, 3 Apr 2015 14:54:08 -0700 Subject: [PATCH 076/216] Docs: Update "split tasks" recipe to use gulp-hub --- .../split-tasks-across-multiple-files.md | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/docs/recipes/split-tasks-across-multiple-files.md b/docs/recipes/split-tasks-across-multiple-files.md index fb820ad1d..4f69bbde1 100644 --- a/docs/recipes/split-tasks-across-multiple-files.md +++ b/docs/recipes/split-tasks-across-multiple-files.md @@ -1,22 +1,8 @@ # Split tasks across multiple files -If your `gulpfile.js` is starting to grow too large, you can split -the tasks into separate files using one of the methods below. - -> Be advised, that this approach is [considered deprecated][deprecated] -> and could lead to problems when migrating to the `gulp 4`. - - -## Using `gulp-require-tasks` - -You can use the [gulp-require-tasks][gulp-require-tasks] -module to automatically load all your tasks from the individual files. - -Please see the [module's README][gulp-require-tasks] for up-to-date instructions. - -## Using `require-dir` - -You can also use the [require-dir][require-dir] module to load your tasks manually. +If your `gulpfile.js` is starting to grow too large, you can split the tasks +into separate files by using the [gulp-hub](https://github.com/frankwallis/gulp-hub/tree/4.0) +module as a [custom registry](https://github.com/phated/undertaker#registryregistryinstance). Imagine the following file structure: @@ -28,20 +14,25 @@ tasks/ └── test.js ``` -Install the `require-dir` module: +Install the `gulp-hub` module: ```sh -npm install --save-dev require-dir +npm install --save-dev gulp-hub ``` Add the following lines to your `gulpfile.js` file: ```js -var requireDir = require('require-dir'); -var tasks = requireDir('./tasks'); -``` +'use strict'; + +var gulp = require('gulp'); +var HubRegistry = require('gulp-hub'); +/* load some files into the registry */ +var hub = new HubRegistry(['tasks/*.js']); + +/* tell gulp to use the tasks just loaded */ +gulp.registry(hub); +``` - [gulp-require-tasks]: https://github.com/betsol/gulp-require-tasks - [require-dir]: https://github.com/aseemk/requireDir - [deprecated]: https://github.com/gulpjs/gulp/pull/1554#issuecomment-202614391 +This recipe can also be found at https://github.com/frankwallis/gulp-hub/tree/4.0/examples/recipe From 98b95046d788a69746f8d6ce5789edf81149a103 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 4 Apr 2015 15:26:13 -0700 Subject: [PATCH 077/216] Docs: Add recipe for running shell commands with child_process or gulp-exec --- docs/recipes/README.md | 1 + docs/recipes/running-shell-commands.md | 31 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 docs/recipes/running-shell-commands.md diff --git a/docs/recipes/README.md b/docs/recipes/README.md index 52c3bcd19..3e822432a 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -27,3 +27,4 @@ * [Exports as tasks](exports-as-tasks.md) * [Rollup with rollup-stream](rollup-with-rollup-stream.md) * [Run gulp task via cron job](cron-task.md) +* [Running shell commands](running-shell-commands.md) diff --git a/docs/recipes/running-shell-commands.md b/docs/recipes/running-shell-commands.md new file mode 100644 index 000000000..5d3447476 --- /dev/null +++ b/docs/recipes/running-shell-commands.md @@ -0,0 +1,31 @@ +# Running Shell Commands + +Sometimes it is helpful to be able to call existing command line tools from gulp. + +There are 2 ways to handle this: node's [`child_process`](https://nodejs.org/api/child_process.html) +built-in module or [`gulp-exec`](https://github.com/robrich/gulp-exec) if you need to integrate the +command with an existing pipeline. + +```js +'use strict'; + +var cp = require('child_process'); +var gulp = require('gulp'); + +gulp.task('reset', function() { + // In gulp 4, you can return a child process to signal task completion + return cp.execFile('git checkout -- .'); +}); +``` + +```js +'use strict'; + +var gulp = require('gulp'); +var exec = require('gulp-exec'); + +gulp.task('reset', function() { + return gulp.src('./**/**') + .pipe(exec('git checkout -- <%= file.path %>')); +}); +``` From 409f19aacb9489491c82013e9dee28f06f49e8d6 Mon Sep 17 00:00:00 2001 From: Stefan Baumgartner Date: Sun, 5 Apr 2015 22:18:25 +0200 Subject: [PATCH 078/216] Fix: Throw better error when watch parameters are invalid (fixes #1002) --- index.js | 7 +++++++ test/watch.js | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/index.js b/index.js index 6969fa141..1b5841621 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,13 @@ Gulp.prototype.src = vfs.src; Gulp.prototype.dest = vfs.dest; Gulp.prototype.symlink = vfs.symlink; Gulp.prototype.watch = function(glob, opt, task) { + if (typeof opt === 'string' || typeof task === 'string' || + Array.isArray(opt) || Array.isArray(task)) { + throw new Error('watching ' + glob + ': watch task has to be ' + + 'a function (optionally generated by using gulp.parallel ' + + 'or gulp.series)'); + } + if (typeof opt === 'function') { task = opt; opt = null; diff --git a/test/watch.js b/test/watch.js index 335ca771a..1b1f9d6dd 100644 --- a/test/watch.js +++ b/test/watch.js @@ -126,5 +126,29 @@ describe('gulp', function() { updateTempFile(tempFile); }); + it('should throw an error: passed parameter (string) is not a function', function(done) { + var tempFile = path.join(outpath, 'empty.txt'); + + createTempFile(tempFile); + try { + gulp.watch(tempFile, 'task1'); + } catch (err) { + err.message.should.equal('watching ' + tempFile + ': watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)'); + done(); + } + }); + + it('should throw an error: passed parameter (array) is not a function', function(done) { + var tempFile = path.join(outpath, 'empty.txt'); + + createTempFile(tempFile); + try { + gulp.watch(tempFile, ['task1']); + } catch (err) { + err.message.should.equal('watching ' + tempFile + ': watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)'); + done(); + } + }); + }); }); From 8aa1022f22c822db3a2cb62db01e377a88fdd934 Mon Sep 17 00:00:00 2001 From: Devyn Stott Date: Wed, 8 Apr 2015 20:48:20 -0700 Subject: [PATCH 079/216] Docs: Add gulp.tree API & examples --- docs/API.md | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/docs/API.md b/docs/API.md index f9a2e3287..c4252457b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -535,6 +535,154 @@ Type: String The path to the file that triggered the event. +### gulp.tree(options) + +Returns the tree of tasks. Inherited from [undertaker]. See the [undertaker docs for this function](https://github.com/phated/undertaker#treeoptions--object). + +#### options +Type: Object + +Options to pass to [undertaker]. + +##### options.deep +Type: `Boolean` + +Default: `false` + +If set to true whole tree should be returned. + +#### Example gulpfile + +```js +gulp.task('one', function(done) { + // do stuff + done(); +}); + +gulp.task('two', function(done) { + // do stuff + done(); +}); + +gulp.task('three', function(done) { + // do stuff + done(); +}); + +gulp.task('four', gulp.series('one', 'two')); + +gulp.task('five', + gulp.series('four', + gulp.parallel('three', function(done) { + // do more stuff + done(); + }) + ) +); +``` + +#### Example tree output + +```js +gulp.tree() + +// output: [ 'one', 'two', 'three', 'four', 'five' ] + +gulp.tree({ deep: true }) + +/*output: [ + { + "label":"one", + "type":"task", + "nodes":[] + }, + { + "label":"two", + "type":"task", + "nodes":[] + }, + { + "label":"three", + "type":"task", + "nodes":[] + }, + { + "label":"four", + "type":"task", + "nodes":[ + { + "label":"", + "type":"function", + "nodes":[ + { + "label":"one", + "type":"task", + "nodes":[] + }, + { + "label":"two", + "type":"task", + "nodes":[] + } + ] + } + ] + }, + { + "label":"five", + "type":"task", + "nodes":[ + { + "label":"", + "type":"function", + "nodes":[ + { + "label":"four", + "type":"task", + "nodes":[ + { + "label":"", + "type":"function", + "nodes":[ + { + "label":"one", + "type":"task", + "nodes":[] + }, + { + "label":"two", + "type":"task", + "nodes":[] + } + ] + } + ] + }, + { + "label":"", + "type":"function", + "nodes":[ + { + "label":"three", + "type":"task", + "nodes":[] + }, + { + "label":"", + "type":"function", + "nodes":[] + } + ] + } + ] + } + ] + } +] +*/ +``` + + [Function.name]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name [gaze]: https://github.com/shama/gaze [glob-stream]: https://github.com/gulpjs/glob-stream From c1012cd5524d8265f00898bddab491a8f7fcf41e Mon Sep 17 00:00:00 2001 From: Devyn Stott Date: Wed, 8 Apr 2015 21:08:00 -0700 Subject: [PATCH 080/216] Docs: Add example of -T/--tasks and --tasks-simple --- docs/CLI.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/docs/CLI.md b/docs/CLI.md index 0666881dd..c4af45f76 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -41,3 +41,75 @@ Just running `gulp` will execute the task `default`. If there is no ### Compilers You can find a list of supported languages at [interpret](https://github.com/tkellen/node-interpret#jsvariants). If you would like to add support for a new language send pull request/open issues there. + +### Examples + +#### Example gulpfile + +```js +gulp.task('one', function(done) { + // do stuff + done(); +}); + +gulp.task('two', function(done) { + // do stuff + done(); +}); + +gulp.task('three', three); + +function three(done) { + done(); +} +three.description = "This is the description of task three"; + +gulp.task('four', gulp.series('one', 'two')); + +gulp.task('five', + gulp.series('four', + gulp.parallel('three', function(done) { + // do more stuff + done(); + }) + ) +); +``` + +### `-T` or `--tasks` + +Command: `gulp -T` or `gulp --tasks` + +Output: +```shell +[20:58:55] Tasks for ~\exampleProject\gulpfile.js +[20:58:55] ├── one +[20:58:55] ├── two +[20:58:55] ├── three This is the description of task three +[20:58:55] ├─┬ four +[20:58:55] │ └─┬ +[20:58:55] │ ├── one +[20:58:55] │ └── two +[20:58:55] ├─┬ five +[20:58:55] │ └─┬ +[20:58:55] │ ├─┬ four +[20:58:55] │ │ └─┬ +[20:58:55] │ │ ├── one +[20:58:55] │ │ └── two +[20:58:55] │ └─┬ +[20:58:55] │ ├── three +[20:58:55] │ └── +``` + +### `--tasks-simple` + +Command: `gulp --tasks-simple` + +Output: +```shell +one +two +three +four +five +``` From be2df066378166d0cdf5f040291e333044dac074 Mon Sep 17 00:00:00 2001 From: Ivan Yan Date: Sun, 24 May 2015 08:18:48 +0800 Subject: [PATCH 081/216] Docs: Improve API references --- docs/API.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/API.md b/docs/API.md index c4252457b..f0227f2b1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -104,7 +104,7 @@ Default: `false` If true, it will create a duplex stream which passes items through and emits globbed files. -### options.allowEmpty +##### options.allowEmpty Type: `Boolean` Default: `false` @@ -389,7 +389,7 @@ gulp.task('sometask', function() { }); ``` -### lastRun(taskName, [timeResolution]) +### gulp.lastRun(taskName, [timeResolution]) Returns the timestamp of the last time the task ran successfully. The time will be the time the task started. Returns `undefined` if the task has From 2e4809b08b7a955f1758241e3adc708270c2d603 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 1 Jun 2015 16:24:15 -0700 Subject: [PATCH 082/216] Upgrade: Update undertaker & vinyl-fs --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 73f3f04ab..0f01025c2 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "man": "gulp.1", "dependencies": { "gulp-cli": "gulpjs/gulp-cli#4.0", - "undertaker": "^0.7.0", - "vinyl-fs": "wearefractal/vinyl-fs" + "undertaker": "^0.11.0", + "vinyl-fs": "^1.0.0" }, "devDependencies": { "coveralls": "^2.7.0", From 3f843b8585a05500588c741b5bd58c5c87f0b7f7 Mon Sep 17 00:00:00 2001 From: Devyn Stott Date: Thu, 16 Apr 2015 11:56:25 -0700 Subject: [PATCH 083/216] Docs: Add gulp.registry API & examples --- docs/API.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/API.md b/docs/API.md index f0227f2b1..75b727ca1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -682,6 +682,59 @@ gulp.tree({ deep: true }) */ ``` +### gulp.registry([registry]) + +Get or set the underlying task registry. Inherited from [undertaker]; see the undertaker documention on [registries](https://github.com/phated/undertaker#registryregistryinstance). Using this, you can change registries that enhance gulp in different ways. Utilizing a custom registry has at least three use cases: + +- [Sharing tasks](https://github.com/phated/undertaker#sharing-tasks) +- [Sharing functionality](https://github.com/phated/undertaker#sharing-functionalities). (e.g. you could override the task prototype to add some additional logging, bind task metadata or include some config settings.) +- Handling other behavior that hooks into the registry lifecycle (see [gulp-hub](https://github.com/frankwallis/gulp-hub) for an example) + +To build your own custom registry see the [undertaker documentation on custom registries](https://github.com/phated/undertaker#custom-registries). + +#### registry + +A registry instance or constructor. When passed in, the tasks from the current registry will be transferred to the new registry and then current registry will be replaced with the new registry. + +#### Example + +This example shows how to create and use a simple custom registry to add tasks. + +```js +//gulpfile.js +var gulp = require('gulp'); + +var companyTasks = require('./myCompanyTasksRegistry.js'); + +gulp.registry(companyTasks); + +gulp.task('one', gulp.parallel('someCompanyTask', function(done) { + console.log('in task one'); + done(); +})); +``` + +```js +//myCompanyTasksRegistry.js +var util = require('util'); + +var DefaultRegistry = require('undertaker-registry'); + +function MyCompanyTasksRegistry() { + DefaultRegistry.call(this); + + this.set('clean', function(done) { + done(); + }); + this.set('someCompanyTask', function(done) { + console.log('performing some company task.'); + done(); + }); +} +util.inherits(MyCompanyTasksRegistry, DefaultRegistry); + +module.exports = new MyCompanyTasksRegistry(); +``` [Function.name]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name [gaze]: https://github.com/shama/gaze From 646044b6f80bc45213cc42ef0f6a8065e7382442 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Fri, 21 Aug 2015 16:53:40 -0700 Subject: [PATCH 084/216] Upgrade: Update undertaker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f01025c2..9e1adcf77 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "man": "gulp.1", "dependencies": { "gulp-cli": "gulpjs/gulp-cli#4.0", - "undertaker": "^0.11.0", + "undertaker": "^0.12.0", "vinyl-fs": "^1.0.0" }, "devDependencies": { From 2cd0e1e91214e41265469df880815ffa82f20444 Mon Sep 17 00:00:00 2001 From: Stefan Baumgartner Date: Wed, 16 Sep 2015 10:21:13 +0200 Subject: [PATCH 085/216] Breaking: Replace vinyl-fs watch/gaze with chokidar --- docs/API.md | 33 ++++++++++----- .../handling-the-delete-event-on-watch.md | 22 ++++------ index.js | 11 ++++- package.json | 1 + test/watch.js | 42 ++++++++++++------- 5 files changed, 69 insertions(+), 40 deletions(-) diff --git a/docs/API.md b/docs/API.md index 75b727ca1..17263fc31 100644 --- a/docs/API.md +++ b/docs/API.md @@ -509,31 +509,42 @@ A single glob or array of globs that indicate which files to watch for changes. #### opts Type: `Object` -Options, that are passed to [`gaze`][gaze]. +Options, that are passed to [`chokidar`][chokidar]. #### fn Type: `Function` An [async](#async-support) function to run when a file changes. -`gulp.watch` returns an `EventEmitter` object which emits `change` events with -the [gaze] `event`: +`gulp.watch` returns a wrapped [chokidar] FSWatcher object. If provided, +the callback will be triggered upon any `add`, `change`, or `unlink` event. +Listeners can also be set directly for any of [chokidar]'s events. + ```js var watcher = gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); -watcher.on('change', function(event) { - console.log('File ' + event.path + ' was ' + event.type); +watcher.on('change', function(path, stats) { + console.log('File ' + path + ' was changed'); + if (stats) { + console.log('changed size to ' + stats.size); + } +}); + +watcher.on('unlink', function(path) { + console.log('File ' + path + ' was removed'); }); ``` -##### event.type +##### path Type: String -The type of change that occurred, either "added", "changed" or "deleted". +The relative path of the document. -##### event.path -Type: String +##### stats +Type: Object -The path to the file that triggered the event. +[File stats](http://nodejs.org/api/fs.html#fs_class_fs_stats) object when available. +Setting the `alwaysStat` option to true will ensure that a file stat object will be +available. ### gulp.tree(options) @@ -737,7 +748,7 @@ module.exports = new MyCompanyTasksRegistry(); ``` [Function.name]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name -[gaze]: https://github.com/shama/gaze +[chokidar]: https://github.com/paulmillr/chokidar [glob-stream]: https://github.com/gulpjs/glob-stream [glob-parent]: https://github.com/es128/glob-parent [gulp-if]: https://github.com/robrich/gulp-if diff --git a/docs/recipes/handling-the-delete-event-on-watch.md b/docs/recipes/handling-the-delete-event-on-watch.md index 40cf115fb..05bd683a1 100644 --- a/docs/recipes/handling-the-delete-event-on-watch.md +++ b/docs/recipes/handling-the-delete-event-on-watch.md @@ -1,9 +1,8 @@ # Handling the Delete Event on Watch -You can listen for `'change'` events to fire on the watcher returned from `gulp.watch`. - -Each change event has a `type` property. If `type` is `'deleted'`, you can delete the file -from your destination directory, using something like: +You can listen for `'unlink'` events to fire on the watcher returned from `gulp.watch`. +This gets fired when files are removed, so you can delete the file from your destination +directory, using something like: ```js 'use strict'; @@ -24,16 +23,11 @@ gulp.task('scripts', function() { gulp.task('watch', function () { var watcher = gulp.watch('src/**/*.js', ['scripts']); - watcher.on('change', function (event) { - if (event.type === 'deleted') { - // Simulating the {base: 'src'} used with gulp.src in the scripts task - var filePathFromSrc = path.relative(path.resolve('src'), event.path); - - // Concatenating the 'build' absolute path used by gulp.dest in the scripts task - var destFilePath = path.resolve('build', filePathFromSrc); - - del.sync(destFilePath); - } + watcher.on('unlink', function (filepath) { + var filePathFromSrc = path.relative(path.resolve('src'), filepath); + // Concatenating the 'build' absolute path used by gulp.dest in the scripts task + var destFilePath = path.resolve('build', filePathFromSrc); + del.sync(destFilePath); }); }); ``` diff --git a/index.js b/index.js index 1b5841621..46309dbcb 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ var util = require('util'); var Undertaker = require('undertaker'); var vfs = require('vinyl-fs'); +var chokidar = require('chokidar'); function Gulp() { Undertaker.call(this); @@ -30,7 +31,15 @@ Gulp.prototype.watch = function(glob, opt, task) { fn = this.parallel(task); } - return vfs.watch(glob, opt, fn); + var watcher = chokidar.watch(glob, opt); + if (fn) { + watcher + .on('change', fn) + .on('unlink', fn) + .on('add', fn); + } + + return watcher; }; // Let people use this class from our instance diff --git a/package.json b/package.json index 9e1adcf77..8ab6637b2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "man": "gulp.1", "dependencies": { + "chokidar": "^1.0.5", "gulp-cli": "gulpjs/gulp-cli#4.0", "undertaker": "^0.12.0", "vinyl-fs": "^1.0.0" diff --git a/test/watch.js b/test/watch.js index 1b1f9d6dd..788df4b4e 100644 --- a/test/watch.js +++ b/test/watch.js @@ -36,7 +36,7 @@ describe('gulp', function() { createTempFile(tempFile); var watcher = gulp.watch(tempFile, function(cb) { - watcher.end(); + watcher.close(); cb(); done(); }); @@ -44,13 +44,30 @@ describe('gulp', function() { updateTempFile(tempFile); }); + it('should execute the gulp.parallel tasks', function(done) { + var tempFile = path.join(outpath, 'watch-func.txt'); + + createTempFile(tempFile); + + gulp.task('test', function(cb) { + watcher.close(); + cb(); + done(); + }); + + var watcher = gulp.watch(tempFile, gulp.parallel('test')); + + updateTempFile(tempFile); + }); + + it('should call the function when file changes: w/ options', function(done) { var tempFile = path.join(outpath, 'watch-func-options.txt'); createTempFile(tempFile); - var watcher = gulp.watch(tempFile, {debounceDelay: 5}, function(cb) { - watcher.end(); + var watcher = gulp.watch(tempFile, function(cb) { + watcher.close(); cb(); done(); }); @@ -66,14 +83,11 @@ describe('gulp', function() { createTempFile(tempFile); - var watcher = gulp.watch(relFile, {debounceDelay: 5, cwd: cwd}) - .on('change', function(evt) { - should.exist(evt); - should.exist(evt.path); - should.exist(evt.type); - evt.type.should.equal('changed'); - evt.path.should.equal(path.resolve(tempFile)); - watcher.end(); + var watcher = gulp.watch(relFile, {cwd: cwd}) + .on('change', function(filepath) { + should.exist(filepath); + path.resolve(cwd, filepath).should.equal(path.resolve(tempFile)); + watcher.close(); done(); }); @@ -93,12 +107,12 @@ describe('gulp', function() { gulp.task('task2', function(cb) { a += 10; a.should.equal(11); - watcher.end(); + watcher.close(); cb(); done(); }); - var watcher = gulp.watch(tempFile, {debounceDelay: 25}, gulp.series('task1', 'task2')); + var watcher = gulp.watch(tempFile, gulp.series('task1', 'task2')); updateTempFile(tempFile); }); @@ -116,7 +130,7 @@ describe('gulp', function() { gulp.task('task2', function(cb) { a += 10; a.should.equal(11); - watcher.end(); + watcher.close(); cb(); done(); }); From 32abfe5c1479e6662150a692c59b4072d334869c Mon Sep 17 00:00:00 2001 From: Contra Date: Fri, 25 Sep 2015 16:06:20 -0700 Subject: [PATCH 086/216] Upgrade: Update vinyl-fs & mocha-lcov-reporter --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8ab6637b2..3edf58307 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,10 @@ }, "man": "gulp.1", "dependencies": { - "chokidar": "^1.0.5", + "chokidar": "^1.1.0", "gulp-cli": "gulpjs/gulp-cli#4.0", "undertaker": "^0.12.0", - "vinyl-fs": "^1.0.0" + "vinyl-fs": "^2.0.0" }, "devDependencies": { "coveralls": "^2.7.0", @@ -38,7 +38,7 @@ "jscs-preset-gulp": "^1.0.0", "mkdirp": "^0.5.0", "mocha": "^2.0.1", - "mocha-lcov-reporter": "^0.0.1", + "mocha-lcov-reporter": "^1.0.0", "q": "^1.0.0", "rimraf": "^2.2.5", "should": "^5.0.1" From 355fc4e14840fd26db07fe99183e430e8390ae17 Mon Sep 17 00:00:00 2001 From: Elan Shanker Date: Sat, 26 Sep 2015 02:59:13 -0400 Subject: [PATCH 087/216] Fix: Set chokidar option ignoreIntial: true by default --- index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 46309dbcb..dda868a50 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,7 @@ Gulp.prototype.watch = function(glob, opt, task) { if (typeof opt === 'function') { task = opt; - opt = null; + opt = {}; } var fn; @@ -31,6 +31,10 @@ Gulp.prototype.watch = function(glob, opt, task) { fn = this.parallel(task); } + if (opt.ignoreInitial == null) { + opt.ignoreInitial = true; + } + var watcher = chokidar.watch(glob, opt); if (fn) { watcher From 263eeeaad217d4bffb1fbaea1eb57a18a3af2967 Mon Sep 17 00:00:00 2001 From: Elan Shanker Date: Sat, 26 Sep 2015 03:26:30 -0400 Subject: [PATCH 088/216] Docs: Improve gulp.watch API with Chokidar specifics --- docs/API.md | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/docs/API.md b/docs/API.md index 17263fc31..cf3c2071a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -493,6 +493,9 @@ A task name, a function or an array of either. ### gulp.watch(glob[, opts], fn) Watch files and do something when a file changes. +File watching is provided by the [`chokidar`][chokidar] module. +Please report any file watching problems directly to its +[issue tracker](https://github.com/paulmillr/chokidar/issues). ```js gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); @@ -509,7 +512,23 @@ A single glob or array of globs that indicate which files to watch for changes. #### opts Type: `Object` -Options, that are passed to [`chokidar`][chokidar]. +Options that are passed to [`chokidar`][chokidar]. + +Commonly used options: +* `ignored` ([anymatch](https://github.com/es128/anymatch)-compatible definition) +Defines files/paths to be excluded from being watched. +* `usePolling` (boolean, default: `false`). When `true` uses a watch method backed +by stat polling. Usually necessary when watching files on a network mount or on a +VMs file system. +* `cwd` (path string). The base directory from which watch paths are to be +derived. Paths emitted with events will be relative to this. +* `alwaysStat` (boolean, default: `false`). If relying upon the +[`fs.Stats`](http://nodejs.org/api/fs.html#fs_class_fs_stats) object +that may get passed as a second argument with `add`, `addDir`, and `change` events +when available, set this to `true` to ensure it is provided with every event. May +have a slight performance penalty. + +Read about the full set of options in [`chokidar`'s README][chokidar] #### fn Type: `Function` @@ -518,15 +537,13 @@ An [async](#async-support) function to run when a file changes. `gulp.watch` returns a wrapped [chokidar] FSWatcher object. If provided, the callback will be triggered upon any `add`, `change`, or `unlink` event. -Listeners can also be set directly for any of [chokidar]'s events. +Listeners can also be set directly for any of [chokidar]'s events, such as +`addDir`, `unlinkDir`, and `error`. ```js var watcher = gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); watcher.on('change', function(path, stats) { console.log('File ' + path + ' was changed'); - if (stats) { - console.log('changed size to ' + stats.size); - } }); watcher.on('unlink', function(path) { @@ -544,7 +561,22 @@ Type: Object [File stats](http://nodejs.org/api/fs.html#fs_class_fs_stats) object when available. Setting the `alwaysStat` option to true will ensure that a file stat object will be -available. +provided. + +#### watcher methods + +##### watcher.close() + +Shuts down the file watcher. + +##### watcher.add(glob) + +Watch additional glob (or array of globs) with an already-running watcher instance. + +##### watcher.unwatch(glob) + +Stop watching a glob (or array of globs) while leaving the watcher running and +emitting events for the remaining paths it is watching. ### gulp.tree(options) From 88b6c338649b78ff8be3a7131f72205713b9e741 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 1 Oct 2015 13:57:32 -0700 Subject: [PATCH 089/216] Upgrade: Update chokidar (ref #1287) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3edf58307..ebe6378f2 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "man": "gulp.1", "dependencies": { - "chokidar": "^1.1.0", + "chokidar": "^1.2.0", "gulp-cli": "gulpjs/gulp-cli#4.0", "undertaker": "^0.12.0", "vinyl-fs": "^2.0.0" From cacc173eef8bfd514d9be66368ae69276e72c932 Mon Sep 17 00:00:00 2001 From: Da CodeKid Date: Tue, 6 Oct 2015 11:11:43 -0400 Subject: [PATCH 090/216] Docs: Update "clean" task in example for "del" syntax change --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e8cf13d4c..ea3cf1ad4 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,10 @@ gulp.task('default', gulp.series('build')); /* Define our tasks using plain functions */ // Not all tasks need to use streams -// A gulpfile is just another node program and you can use all packages available on npm +// But it must return either a Promise or Stream or take a Callback and call it function clean() { // You can use multiple globbing patterns as you would with `gulp.src` + // If you are using del 2.0 or above, return its promise return del(['build']); } From bc352dd943c4bbd5961901da3798df398d9f0434 Mon Sep 17 00:00:00 2001 From: Nick McCready Date: Thu, 10 Sep 2015 23:37:37 -0400 Subject: [PATCH 091/216] Update: Add test to make sure no functions are kicked off when they should not --- test/watch.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/watch.js b/test/watch.js index 788df4b4e..2dba7dab1 100644 --- a/test/watch.js +++ b/test/watch.js @@ -61,6 +61,21 @@ describe('gulp', function() { }); + it('should not call the function when no file changes: no options', function(done) { + var tempFile = path.join(outpath, 'watch-func.txt'); + + createTempFile(tempFile); + + var watcher = gulp.watch(tempFile, function() { + should.fail('Watcher erroneously called'); + }); + + setTimeout(function() { + watcher.close(); + done(); + }, 10); + }); + it('should call the function when file changes: w/ options', function(done) { var tempFile = path.join(outpath, 'watch-func-options.txt'); From 0f3151edb6f4772aaa8a249a85a42183384c2e3a Mon Sep 17 00:00:00 2001 From: erikkemperman Date: Wed, 21 Oct 2015 16:49:35 +0200 Subject: [PATCH 092/216] Docs: Align API with undertaker docs --- docs/API.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/API.md b/docs/API.md index cf3c2071a..7179223bb 100644 --- a/docs/API.md +++ b/docs/API.md @@ -737,7 +737,7 @@ To build your own custom registry see the [undertaker documentation on custom re #### registry -A registry instance or constructor. When passed in, the tasks from the current registry will be transferred to the new registry and then current registry will be replaced with the new registry. +A registry instance. When passed in, the tasks from the current registry will be transferred to the new registry and then current registry will be replaced with the new registry. #### Example @@ -765,16 +765,18 @@ var DefaultRegistry = require('undertaker-registry'); function MyCompanyTasksRegistry() { DefaultRegistry.call(this); +} +util.inherits(MyCompanyTasksRegistry, DefaultRegistry); - this.set('clean', function(done) { +MyCompanyTasksRegistry.prototype.init = function(gulp) { + gulp.task('clean', function(done) { done(); }); - this.set('someCompanyTask', function(done) { + gulp.task('someCompanyTask', function(done) { console.log('performing some company task.'); done(); }); -} -util.inherits(MyCompanyTasksRegistry, DefaultRegistry); +}; module.exports = new MyCompanyTasksRegistry(); ``` From 7e67502ca611979821ad1036afba5925f0efef64 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 18 Nov 2015 13:00:05 -0700 Subject: [PATCH 093/216] Upgrade: Update undertaker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ebe6378f2..0a9402f33 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dependencies": { "chokidar": "^1.2.0", "gulp-cli": "gulpjs/gulp-cli#4.0", - "undertaker": "^0.12.0", + "undertaker": "^0.13.0", "vinyl-fs": "^2.0.0" }, "devDependencies": { From 430df3667ee52d9c65b1dc719354e4ab6d37fcd7 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 14:45:47 -0700 Subject: [PATCH 094/216] Release: 4.0.0-alpha.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a9402f33..c7e241b72 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gulp", "description": "The streaming build system", - "version": "4.0.0-alpha.1", + "version": "4.0.0-alpha.2", "homepage": "http://gulpjs.com", "repository": "gulpjs/gulp", "author": "Fractal (http://wearefractal.com/)", From d0ced7521dcc4a5ee8004d0daf6d8e0f488e28cc Mon Sep 17 00:00:00 2001 From: cssmagic Date: Tue, 10 Nov 2015 14:56:15 +0800 Subject: [PATCH 095/216] Docs: Improve format of API --- docs/API.md | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/docs/API.md b/docs/API.md index 7179223bb..a420c0405 100644 --- a/docs/API.md +++ b/docs/API.md @@ -38,7 +38,8 @@ The following expression matches `a.js` and `bad.js`: Note that globs are evaluated in order, which means this is possible: -``` + +```js // exclude every JS file that starts with a b except bad.js gulp.src(['*.js', '!b*.js', 'bad.js']) ``` @@ -106,6 +107,7 @@ emits globbed files. ##### options.allowEmpty Type: `Boolean` + Default: `false` When true, will allow singular globs to fail to match. Otherwise, globs which are only supposed to match one file (such as `./foo/bar.js`) will cause an error to be thrown if they don't match. @@ -120,6 +122,7 @@ gulp.src('app/scripts.js', { allowEmpty: true }) .pipe(...); ``` + ### gulp.dest(path[, options]) Can be piped to and it will write files. Re-emits all data passed to it so you @@ -290,6 +293,7 @@ is not provided. ##### fn.description gulp-cli prints this description alongside the task name when listing tasks: + ```js var gulp = require('gulp'); @@ -300,7 +304,8 @@ test.description = 'I do nothing'; gulp.task(test); ``` -```shell + +```sh $> gulp --tasks [12:00:02] Tasks for ~/Documents/some-project/gulpfile.js [12:00:02] └── test I do nothing @@ -389,6 +394,7 @@ gulp.task('sometask', function() { }); ``` + ### gulp.lastRun(taskName, [timeResolution]) Returns the timestamp of the last time the task ran successfully. The time @@ -409,12 +415,14 @@ Default: `1000` on node v0.10, `0` on node v0.12 (and iojs v1.5). Set the time resolution of the returned timestamps. Assuming the task named "someTask" ran at `1426000004321`: + - `gulp.lastRun('someTask', 1000)` would return `1426000004000`. - `gulp.lastRun('someTask', 100)` would return `1426000004300`. `timeResolution` allows you to compare a run time to a file [mtime stat][fs stats] attribute. This attribute time resolution may vary depending of the node version and the file system used: + - on node v0.10, a file [mtime stat][fs stats] time resolution of any files will be 1s at best; - on node v0.12 and iojs v1.5, 1ms at best; - for files on FAT32, the mtime time resolution is 2s; @@ -422,6 +430,7 @@ and the file system used: - on NTFS, 1s on node v0.10, 100ms on node 0.12; - on Ext4, 1s on node v0.10, 1ms on node 0.12. + ### gulp.parallel(...tasks) Takes a number of task names or functions and returns a function of the composed @@ -515,7 +524,8 @@ Type: `Object` Options that are passed to [`chokidar`][chokidar]. Commonly used options: -* `ignored` ([anymatch](https://github.com/es128/anymatch)-compatible definition) + +* `ignored` ([anymatch](https://github.com/es128/anymatch)-compatible definition). Defines files/paths to be excluded from being watched. * `usePolling` (boolean, default: `false`). When `true` uses a watch method backed by stat polling. Usually necessary when watching files on a network mount or on a @@ -523,12 +533,12 @@ VMs file system. * `cwd` (path string). The base directory from which watch paths are to be derived. Paths emitted with events will be relative to this. * `alwaysStat` (boolean, default: `false`). If relying upon the -[`fs.Stats`](http://nodejs.org/api/fs.html#fs_class_fs_stats) object +[`fs.Stats`][fs stats] object that may get passed as a second argument with `add`, `addDir`, and `change` events when available, set this to `true` to ensure it is provided with every event. May have a slight performance penalty. -Read about the full set of options in [`chokidar`'s README][chokidar] +Read about the full set of options in [`chokidar`'s README][chokidar]. #### fn Type: `Function` @@ -552,15 +562,15 @@ watcher.on('unlink', function(path) { ``` ##### path -Type: String +Type: `String` The relative path of the document. ##### stats -Type: Object +Type: `Object` -[File stats](http://nodejs.org/api/fs.html#fs_class_fs_stats) object when available. -Setting the `alwaysStat` option to true will ensure that a file stat object will be +[File stats][fs stats] object when available. +Setting the `alwaysStat` option to `true` will ensure that a file stat object will be provided. #### watcher methods @@ -578,12 +588,13 @@ Watch additional glob (or array of globs) with an already-running watcher instan Stop watching a glob (or array of globs) while leaving the watcher running and emitting events for the remaining paths it is watching. + ### gulp.tree(options) Returns the tree of tasks. Inherited from [undertaker]. See the [undertaker docs for this function](https://github.com/phated/undertaker#treeoptions--object). #### options -Type: Object +Type: `Object` Options to pass to [undertaker]. @@ -592,7 +603,7 @@ Type: `Boolean` Default: `false` -If set to true whole tree should be returned. +If set to `true` whole tree should be returned. #### Example gulpfile @@ -725,12 +736,13 @@ gulp.tree({ deep: true }) */ ``` + ### gulp.registry([registry]) Get or set the underlying task registry. Inherited from [undertaker]; see the undertaker documention on [registries](https://github.com/phated/undertaker#registryregistryinstance). Using this, you can change registries that enhance gulp in different ways. Utilizing a custom registry has at least three use cases: - [Sharing tasks](https://github.com/phated/undertaker#sharing-tasks) -- [Sharing functionality](https://github.com/phated/undertaker#sharing-functionalities). (e.g. you could override the task prototype to add some additional logging, bind task metadata or include some config settings.) +- [Sharing functionality](https://github.com/phated/undertaker#sharing-functionalities) (e.g. you could override the task prototype to add some additional logging, bind task metadata or include some config settings.) - Handling other behavior that hooks into the registry lifecycle (see [gulp-hub](https://github.com/frankwallis/gulp-hub) for an example) To build your own custom registry see the [undertaker documentation on custom registries](https://github.com/phated/undertaker#custom-registries). From b764543fcc2b8a512a771ac104babaa2e4451c44 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Mon, 9 Nov 2015 20:59:53 -0500 Subject: [PATCH 096/216] Docs: Update table of contents for API --- docs/API.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/API.md b/docs/API.md index a420c0405..201114b6d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,10 +1,15 @@ ## gulp API docs -Jump to: - [gulp.src](#gulpsrcglobs-options) | - [gulp.dest](#gulpdestpath-options) | - [gulp.task](#gulptaskname--deps--fn) | - [gulp.watch](#gulpwatchglob--opts-tasks-or-gulpwatchglob--opts-cb) +* [gulp.src](#gulpsrcglobs-options) - Emit files matching one or more globs +* [gulp.dest](#gulpdestpath-options) - Write files to directories +* [gulp.symlink](#gulpsymlinkfolder-options) - Write files to symlinks +* [gulp.task](#gulptaskname-fn) - Define tasks +* [gulp.lastRun](#gulplastruntaskname-timeresolution) - Get timestamp of last successful run +* [gulp.parallel](#gulpparalleltasks) - Run tasks in parallel +* [gulp.series](#gulpseriestasks) - Run tasks in series +* [gulp.watch](#gulpwatchglob-opts-fn) - Do something when a file changes +* [gulp.tree](#gulptreeoptions) - Get the tree of tasks +* [gulp.registry](#gulpregistryregistry) - Get or set the task registry ### gulp.src(globs[, options]) From 468a703d01589429be8ac2922ca24e15b2df55a6 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 21 Dec 2015 16:57:19 -0700 Subject: [PATCH 097/216] Update: Use published gulp-cli --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7e241b72..7d04e53bf 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "man": "gulp.1", "dependencies": { "chokidar": "^1.2.0", - "gulp-cli": "gulpjs/gulp-cli#4.0", + "gulp-cli": "^1.0.0", "undertaker": "^0.13.0", "vinyl-fs": "^2.0.0" }, From c69157281f26b91c7e6581f2e1e0fba9d1c2b7c6 Mon Sep 17 00:00:00 2001 From: Contra Date: Thu, 24 Dec 2015 11:41:11 -0800 Subject: [PATCH 098/216] Update: Bind all undertaker functions on the gulp instance to allow destructuring --- index.js | 9 +++++++++ test/watch.js | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/index.js b/index.js index dda868a50..e3b5b4fe4 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,15 @@ var chokidar = require('chokidar'); function Gulp() { Undertaker.call(this); + + // Bind the functions for destructuring + this.watch = this.watch.bind(this); + this.task = this.task.bind(this); + this.series = this.series.bind(this); + this.parallel = this.parallel.bind(this); + this.registry = this.registry.bind(this); + this.tree = this.tree.bind(this); + this.lastRun = this.lastRun.bind(this); } util.inherits(Gulp, Undertaker); diff --git a/test/watch.js b/test/watch.js index 2dba7dab1..787b6543d 100644 --- a/test/watch.js +++ b/test/watch.js @@ -60,6 +60,23 @@ describe('gulp', function() { updateTempFile(tempFile); }); + it('should work with destructuring', function(done) { + var tempFile = path.join(outpath, 'watch-func.txt'); + var watch = gulp.watch; + var parallel = gulp.parallel; + var task = gulp.task; + createTempFile(tempFile); + + task('test', function(cb) { + watcher.close(); + cb(); + done(); + }); + + var watcher = watch(tempFile, parallel('test')); + + updateTempFile(tempFile); + }); it('should not call the function when no file changes: no options', function(done) { var tempFile = path.join(outpath, 'watch-func.txt'); From 83f563295a22a04f2c4595671fab6b7baf840979 Mon Sep 17 00:00:00 2001 From: Jonathan Lee Date: Thu, 21 Jan 2016 22:51:46 -0500 Subject: [PATCH 099/216] Docs: Replace irc with gitter channel --- docs/FAQ.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 31e11df77..f8a649ead 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -38,9 +38,9 @@ gulp updates can be found on the following twitters: - [@eschoff](https://twitter.com/eschoff) - [@gulpjs](https://twitter.com/gulpjs) -## Does gulp have an IRC channel? +## Does gulp have an chat channel? -Yes, come chat with us in #gulpjs on [Freenode]. +Yes, come chat with us on [Gitter](https://gitter.im/gulpjs/gulp). [Writing a gulp plugin]: writing-a-plugin/README.md [gulp introduction slideshow]: https://slid.es/contra/gulp From 9fc412590747b5e20b4c5b52f59446aaf84632f9 Mon Sep 17 00:00:00 2001 From: Jesse McCarthy Date: Tue, 26 Jan 2016 14:04:10 -0500 Subject: [PATCH 100/216] Fix: Add support for gulp.watch usage w/o opts or callback --- docs/API.md | 12 +++++++----- index.js | 2 ++ test/watch.js | 4 ++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/API.md b/docs/API.md index 201114b6d..e95cc52f7 100644 --- a/docs/API.md +++ b/docs/API.md @@ -504,7 +504,7 @@ Type: `Array`, `String` or `Function` A task name, a function or an array of either. -### gulp.watch(glob[, opts], fn) +### gulp.watch(glob[, opts][, fn]) Watch files and do something when a file changes. File watching is provided by the [`chokidar`][chokidar] module. @@ -515,7 +515,7 @@ Please report any file watching problems directly to its gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); ``` -In the example, `gulp.watch` runs the function returned by gulp.parallel each +In the example, `gulp.watch` runs the function returned by `gulp.parallel` each time a file with the `js` extension in `js/` is updated. #### glob @@ -548,12 +548,14 @@ Read about the full set of options in [`chokidar`'s README][chokidar]. #### fn Type: `Function` -An [async](#async-support) function to run when a file changes. +An [async](#async-support) function to run when a file changes. Does not provide +access to the `path` parameter. `gulp.watch` returns a wrapped [chokidar] FSWatcher object. If provided, the callback will be triggered upon any `add`, `change`, or `unlink` event. Listeners can also be set directly for any of [chokidar]'s events, such as -`addDir`, `unlinkDir`, and `error`. +`addDir`, `unlinkDir`, and `error`. You must set listeners directly to get +access to chokidar's callback parameters, such as `path`. ```js var watcher = gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); @@ -569,7 +571,7 @@ watcher.on('unlink', function(path) { ##### path Type: `String` -The relative path of the document. +Path to the file. If `opts.cwd` is set, `path` is relative to it. ##### stats Type: `Object` diff --git a/index.js b/index.js index e3b5b4fe4..2058ce0b6 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,8 @@ Gulp.prototype.watch = function(glob, opt, task) { opt = {}; } + opt = opt || {}; + var fn; if (typeof task === 'function') { fn = this.parallel(task); diff --git a/test/watch.js b/test/watch.js index 787b6543d..d2d15b482 100644 --- a/test/watch.js +++ b/test/watch.js @@ -126,6 +126,10 @@ describe('gulp', function() { updateTempFile(tempFile); }); + it('should work without options or callback', function() { + gulp.watch('x'); + }); + it('should run many tasks: w/ options', function(done) { var tempFile = path.join(outpath, 'watch-task-options.txt'); var a = 0; From a379529aa5a101550f4b578205c622ad821ccdd1 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Sun, 14 Feb 2016 22:35:17 +0300 Subject: [PATCH 101/216] Upgrade: Update undertaker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d04e53bf..af155392e 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dependencies": { "chokidar": "^1.2.0", "gulp-cli": "^1.0.0", - "undertaker": "^0.13.0", + "undertaker": "^0.15.0", "vinyl-fs": "^2.0.0" }, "devDependencies": { From d90198c0f49305fc54baffa7acc0d731bd7ba781 Mon Sep 17 00:00:00 2001 From: Mehdy Dara Date: Fri, 18 Mar 2016 00:46:51 +0100 Subject: [PATCH 102/216] Docs: Add link to "Intro to Gulp 4" video --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index d72ff7d2b..db9b6cdde 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,6 +23,8 @@ The community has written [recipes](recipes#recipes) for common gulp use-cases. Post on [StackOverflow with a #gulp tag](https://stackoverflow.com/questions/tagged/gulp) or come chat with us in [#gulpjs](https://webchat.freenode.net/?channels=gulpjs) on [Freenode](https://freenode.net/). +## Videos +* [Intro to Gulp 4](https://youtu.be/N42LQ2dLoA8) presented by @addyosmani and @gauntface ## Books * [Developing a gulp Edge](http://shop.oreilly.com/product/9781939902146.do) From e1afdfdbd38add2c059532055eb3c9f0299d09e3 Mon Sep 17 00:00:00 2001 From: Jere Menichelli Date: Wed, 2 Mar 2016 17:10:54 -0300 Subject: [PATCH 103/216] Docs: Add ES2015 gulpfile example to readme --- README.md | 184 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 143 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index ea3cf1ad4..8f102d857 100644 --- a/README.md +++ b/README.md @@ -28,65 +28,167 @@ This file will give you a taste of what gulp does. ```js var gulp = require('gulp'); -var coffee = require('gulp-coffee'); +var less = require('gulp-less'); +var babel = require('gulp-babel'); var concat = require('gulp-concat'); var uglify = require('gulp-uglify'); -var imagemin = require('gulp-imagemin'); -var sourcemaps = require('gulp-sourcemaps'); +var rename = require('gulp-rename'); +var cleanCSS = require('gulp-clean-css'); var del = require('del'); var paths = { - scripts: ['client/js/**/*.coffee', '!client/external/**/*.coffee'], - images: 'client/img/**/*' + styles: { + src: 'src/styles/**/*.less', + dest: 'assets/styles/' + }, + scripts: { + src: 'src/scripts/**/*.js', + dest: 'assets/scripts/' + } }; -/* Register some tasks to expose to the cli */ -gulp.task('build', gulp.series( - clean, - gulp.parallel(scripts, images) -)); -gulp.task(clean); -gulp.task(watch); +/* Not all tasks need to use streams, a gulpfile is just another node program + * and you can use all packages available on npm, but it must return either a + * Promise, a Stream or take a callback and call it + */ +function clean() { + // You can use multiple globbing patterns as you would with `gulp.src`, + // for example if you are using del 2.0 or above, return its promise + return del([ 'assets' ]); +} -// The default task (called when you run `gulp` from cli) -gulp.task('default', gulp.series('build')); +/* + * Define our tasks using plain functions + */ +function styles() { + return gulp.src(paths.styles.src) + .pipe(less()) + .pipe(cleanCSS()) + // pass in options to the stream + .pipe(rename({ + basename: 'main', + suffix: '.min' + })) + .pipe(gulp.dest(paths.styles.dest)); +} +function scripts() { + return gulp.src(paths.scripts.src, { sourcemaps: true }) + .pipe(babel()) + .pipe(uglify()) + .pipe(concat('main.min.js')) + .pipe(gulp.dest(paths.scripts.dest)); +} -/* Define our tasks using plain functions */ +function watch() { + gulp.watch(paths.scripts.src, scripts); + gulp.watch(paths.styles.src, styles); +} -// Not all tasks need to use streams -// But it must return either a Promise or Stream or take a Callback and call it -function clean() { - // You can use multiple globbing patterns as you would with `gulp.src` - // If you are using del 2.0 or above, return its promise - return del(['build']); +/* + * You can use CommonJS `exports` module notation to declare tasks + */ +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.watch = watch; + +/* + * Specify if tasks run in series or parallel using `gulp.series` and `gulp.parallel` + */ +var build = gulp.series(clean, gulp.parallel(styles, scripts)); + +/* + * You can still use `gulp.task` to expose tasks + */ +gulp.task('build', build); + +/* + * Define default task that can be called by just running `gulp` from cli + */ +gulp.task('default', build); +``` + +## Use latest JavaScript version in your gulpfile + +Node already supports a lot of **ES2015**, to avoid compatibility problem we suggest to install Babel and rename your `gulpfile.js` as `gulpfile.babel.js`. + +```sh +npm install --save-dev babel-register babel-preset-es2015 +``` + +Then create a **.babelrc** file with the preset configuration. + +```js +{ + "presets": [ "es2015" ] } +``` -// Copy all static images -function images() { - return gulp.src(paths.images) - // Pass in options to the task - .pipe(imagemin({optimizationLevel: 5})) - .pipe(gulp.dest('build/img')); +And here's the same sample from above written in **ES2015**. + +```js +import gulp from 'gulp'; +import less from 'gulp-less'; +import babel from 'gulp-babel'; +import concat from 'gulp-concat'; +import uglify from 'gulp-uglify'; +import rename from 'gulp-rename'; +import cleanCSS from 'gulp-clean-css'; +import del from 'del'; + +const paths = { + styles: { + src: 'src/styles/**/*.less', + dest: 'assets/styles/' + }, + scripts: { + src: 'src/scripts/**/*.js', + dest: 'assets/scripts/' + } +}; + +/* + * For small tasks you can use arrow functions and export + */ +const clean = () => del([ 'assets' ]); +export { clean }; + +/* + * You can still declare named functions and export them as tasks + */ +export function styles() { + return gulp.src(paths.styles.src) + .pipe(less()) + .pipe(cleanCSS()) + // pass in options to the stream + .pipe(rename({ + basename: 'main', + suffix: '.min' + })) + .pipe(gulp.dest(paths.styles.dest)); } -// Minify and copy all JavaScript (except vendor scripts) -// with sourcemaps all the way down -function scripts() { - return gulp.src(paths.scripts) - .pipe(sourcemaps.init()) - .pipe(coffee()) - .pipe(uglify()) - .pipe(concat('all.min.js')) - .pipe(sourcemaps.write()) - .pipe(gulp.dest('build/js')); +export function scripts() { + return gulp.src(paths.scripts.src, { sourcemaps: true }) + .pipe(babel()) + .pipe(uglify()) + .pipe(concat('main.min.js')) + .pipe(gulp.dest(paths.scripts.dest)); } -// Rerun the task when a file changes -function watch() { - gulp.watch(paths.scripts, scripts); - gulp.watch(paths.images, images); +export function watch() { + gulp.watch(paths.scripts.src, scripts); + gulp.watch(paths.styles.src, styles); } + +const build = gulp.series(clean, gulp.parallel(styles, scripts)); +export { build }; + +/* + * Export a default task + */ +export default build; ``` ## Incremental Builds From 5ddd6734537095c4f8cde846d89160f87d0a54a1 Mon Sep 17 00:00:00 2001 From: Jonathan Lee Date: Fri, 22 Jan 2016 12:20:42 -0500 Subject: [PATCH 104/216] Docs: Update "getting started" example for new syntax --- docs/getting-started.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 06e0c9b8a..724b942b2 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -37,10 +37,12 @@ In your project directory, create a file named `gulpfile.js` in your project roo ```js var gulp = require('gulp'); -gulp.task('default', function(done) { +gulp.task('default', defaultTask); + +function defaultTask(done) { // place code for your default task here done(); -}); +} ``` #### Test it out @@ -63,6 +65,10 @@ Using gulpfile ~/my-project/gulpfile.js [11:15:51] Finished 'default' after 103 μs ``` +## .src, .watch, .dest, .parallel, .series, CLI args - How do I use these things? + +For API specific documentation, you can check out the [documentation for that](API.md). + ## Where do I go now? - [API Documentation](API.md) - The programming interface, defined From e89431257333b8089ab39332c279a93aacd30753 Mon Sep 17 00:00:00 2001 From: Mike Street Date: Tue, 5 Apr 2016 22:33:11 +0100 Subject: [PATCH 105/216] Docs: Add link to "Upgrading to Gulp 4" article --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index db9b6cdde..4f8761b4a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -44,7 +44,7 @@ Post on [StackOverflow with a #gulp tag](https://stackoverflow.com/questions/tag * [Get started with gulp (video series)](https://www.youtube.com/playlist?list=PLRk95HPmOM6PN-G1xyKj9q6ap_dc9Yckm) * [Optimize your web code with gulp](http://www.linuxuser.co.uk/tutorials/optimise-your-web-code-with-gulp-js) * [Automate Your Tasks Easily with Gulp.js ](https://scotch.io/tutorials/automate-your-tasks-easily-with-gulp-js) - +* [How to upgrade to Gulp v4](https://www.liquidlight.co.uk/blog/article/how-do-i-update-to-gulp-4/) ## Examples From 1351fb8d13531f5ff931bbe902d7cda1bf31f182 Mon Sep 17 00:00:00 2001 From: ptb Date: Sat, 9 Apr 2016 15:55:36 -0400 Subject: [PATCH 106/216] Docs: Add missing parenthesis (#1599) --- docs/recipes/running-tasks-in-series.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes/running-tasks-in-series.md b/docs/recipes/running-tasks-in-series.md index 5dfad24d0..b28e692a3 100644 --- a/docs/recipes/running-tasks-in-series.md +++ b/docs/recipes/running-tasks-in-series.md @@ -41,7 +41,7 @@ gulp.task('templates', gulp.series('clean', function() { return gulp.src(['src/templates/*.hbs']) // do some concatenation, minification, etc. .pipe(gulp.dest('output/templates/')); -}); +})); gulp.task('styles', gulp.series('clean', function() { return gulp.src(['src/styles/app.less']) From c3dbc10afa7dcd8c86eebcd7f87967ac1f680da2 Mon Sep 17 00:00:00 2001 From: Gregory Date: Mon, 18 Apr 2016 19:54:56 -0400 Subject: [PATCH 107/216] Docs: Clarify incremental builds example (#1609) --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f102d857..8b8ce9bc5 100644 --- a/README.md +++ b/README.md @@ -196,14 +196,22 @@ export default build; You can filter out unchanged files between runs of a task using the `gulp.src` function's `since` option and `gulp.lastRun`: ```js +const paths = { + ... + images: { + src: 'src/images/**/*.{jpg,jpeg,png}', + dest: 'build/img/' + } +} + function images() { - return gulp.src(paths.images, {since: gulp.lastRun('images')}) + return gulp.src(paths.images.src, {since: gulp.lastRun('images')}) .pipe(imagemin({optimizationLevel: 5})) - .pipe(gulp.dest('build/img')); + .pipe(gulp.dest(paths.images.dest)); } function watch() { - gulp.watch(paths.images, images); + gulp.watch(paths.images.src, images); } ``` Task run times are saved in memory and are lost when gulp exits. It will only From 0c660697b8376c0185bb9e65aca0c01107b7433e Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 27 Apr 2016 20:02:30 -0700 Subject: [PATCH 108/216] Breaking: Replace chokidar as gulp.watch with glob-watcher wrapper --- index.js | 16 ++-------------- package.json | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 2058ce0b6..cddf7eb6f 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ var util = require('util'); var Undertaker = require('undertaker'); var vfs = require('vinyl-fs'); -var chokidar = require('chokidar'); +var watch = require('glob-watcher'); function Gulp() { Undertaker.call(this); @@ -42,19 +42,7 @@ Gulp.prototype.watch = function(glob, opt, task) { fn = this.parallel(task); } - if (opt.ignoreInitial == null) { - opt.ignoreInitial = true; - } - - var watcher = chokidar.watch(glob, opt); - if (fn) { - watcher - .on('change', fn) - .on('unlink', fn) - .on('add', fn); - } - - return watcher; + return watch(glob, opt, fn); }; // Let people use this class from our instance diff --git a/package.json b/package.json index af155392e..a23a0dd88 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "man": "gulp.1", "dependencies": { - "chokidar": "^1.2.0", + "glob-watcher": "^3.0.0", "gulp-cli": "^1.0.0", "undertaker": "^0.15.0", "vinyl-fs": "^2.0.0" From 5dc3b07bd61c217c432c508573583827ef592cd1 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 30 Apr 2016 13:35:10 -0700 Subject: [PATCH 109/216] Docs: Update gulp.watch API to align with glob-watcher --- docs/API.md | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/docs/API.md b/docs/API.md index e95cc52f7..dd41e9c7a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -7,7 +7,7 @@ * [gulp.lastRun](#gulplastruntaskname-timeresolution) - Get timestamp of last successful run * [gulp.parallel](#gulpparalleltasks) - Run tasks in parallel * [gulp.series](#gulpseriestasks) - Run tasks in series -* [gulp.watch](#gulpwatchglob-opts-fn) - Do something when a file changes +* [gulp.watch](#gulpwatchglobs-opts-fn) - Do something when a file changes * [gulp.tree](#gulptreeoptions) - Get the tree of tasks * [gulp.registry](#gulpregistryregistry) - Get or set the task registry @@ -504,28 +504,31 @@ Type: `Array`, `String` or `Function` A task name, a function or an array of either. -### gulp.watch(glob[, opts][, fn]) +### gulp.watch(globs[, opts][, fn]) -Watch files and do something when a file changes. -File watching is provided by the [`chokidar`][chokidar] module. -Please report any file watching problems directly to its -[issue tracker](https://github.com/paulmillr/chokidar/issues). +Takes a path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings as `globs` to watch on the filesystem. Also optionally takes `options` to configure the watcher and a `fn` to execute when a file changes. + +Returns an instance of [`chokidar`][chokidar]. ```js -gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); +gulp.watch('js/**/*.js', gulp.parallel('concat', 'uglify')); ``` In the example, `gulp.watch` runs the function returned by `gulp.parallel` each time a file with the `js` extension in `js/` is updated. -#### glob +#### globs Type: `String` or `Array` -A single glob or array of globs that indicate which files to watch for changes. +A path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings that indicate which files to watch for changes. #### opts Type: `Object` +* `delay` (milliseconds, default: `200`). The delay to wait before triggering the fn. Useful for waiting on many changes before doing the work on changed files, e.g. find-and-replace on many files. +* `queue` (boolean, default: `true`). Whether or not a file change should queue the fn execution if the fn is already running. Useful for a long running fn. +* `ignoreInitial` (boolean, default: `true`). If set to `false` the `fn` is called during [chokidar][chokidar] instantiation as it discovers the file paths. Useful if it is desirable to trigger the `fn` during startup. __Passed through to [chokidar][chokidar], but defaulted to `true` instead of `false`.__ + Options that are passed to [`chokidar`][chokidar]. Commonly used options: @@ -548,17 +551,21 @@ Read about the full set of options in [`chokidar`'s README][chokidar]. #### fn Type: `Function` -An [async](#async-support) function to run when a file changes. Does not provide -access to the `path` parameter. +If the `fn` is passed, it will be called when the watcher emits a `change`, `add` or `unlink` event. It is automatically debounced with a default delay of 200 milliseconds and subsequent calls will be queued and called upon completion. These defaults can be changed using the `options`. + +The `fn` is passed a single argument, `callback`, which is a function that must be called when work in the `fn` is complete. Instead of calling the `callback` function, [async completion][async-completion] can be signalled by: + * Returning a `Stream` or `EventEmitter` + * Returning a `Child Process` + * Returning a `Promise` + * Returning an `Observable` + +Once async completion is signalled, if another run is queued, it will be executed. -`gulp.watch` returns a wrapped [chokidar] FSWatcher object. If provided, -the callback will be triggered upon any `add`, `change`, or `unlink` event. -Listeners can also be set directly for any of [chokidar]'s events, such as -`addDir`, `unlinkDir`, and `error`. You must set listeners directly to get +`gulp.watch` returns a wrapped [chokidar] FSWatcher object. Listeners can also be set directly for any of [chokidar]'s events, such as `addDir`, `unlinkDir`, and `error`. You must set listeners directly to get access to chokidar's callback parameters, such as `path`. ```js -var watcher = gulp.watch('js/**/*.js', gulp.parallel('uglify', 'reload')); +var watcher = gulp.watch('js/**/*.js', gulp.parallel('concat', 'uglify')); watcher.on('change', function(path, stats) { console.log('File ' + path + ' was changed'); }); @@ -815,3 +822,4 @@ module.exports = new MyCompanyTasksRegistry(); [vinyl File instance]: https://github.com/gulpjs/vinyl [Vinyl files]: https://github.com/gulpjs/vinyl-fs [fs stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats +[async-completion]: https://github.com/gulpjs/async-done#completion-and-error-resolution From d4ed3c7e3fb13f3b1bf6f8309cca8459e53f9edb Mon Sep 17 00:00:00 2001 From: xiaoyu2er Date: Fri, 13 May 2016 05:04:37 +0800 Subject: [PATCH 110/216] Docs: Add options.cwd for gulp.src API (#1645) --- docs/API.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/API.md b/docs/API.md index dd41e9c7a..1415c6af2 100644 --- a/docs/API.md +++ b/docs/API.md @@ -60,6 +60,15 @@ Options to pass to [node-glob] through [glob-stream]. gulp adds some additional options in addition to the [options supported by node-glob][node-glob documentation] and [glob-stream]: +##### options.cwd + +The working directory the folder is relative to. + +Type: `String` + +Default: `process.cwd()` + + ##### options.buffer Type: `Boolean` From 477db84761551ab25127447a8425c79dc4808e15 Mon Sep 17 00:00:00 2001 From: Lim H Date: Fri, 13 May 2016 19:49:32 +0100 Subject: [PATCH 111/216] Docs: Add a "BrowserSync with Gulp 4" recipe (#1659) --- .../minimal-browsersync-setup-with-gulp4.md | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 docs/recipes/minimal-browsersync-setup-with-gulp4.md diff --git a/docs/recipes/minimal-browsersync-setup-with-gulp4.md b/docs/recipes/minimal-browsersync-setup-with-gulp4.md new file mode 100644 index 000000000..ec22c0717 --- /dev/null +++ b/docs/recipes/minimal-browsersync-setup-with-gulp4.md @@ -0,0 +1,161 @@ +# Minimal BrowserSync setup with Gulp 4 + +[BrowserSync](https://www.browsersync.io/) is a great tool to streamline +the development process with the ability to reflect code changes instantaneously +in the browser through live-reloading. Setting up a live-reloading +BrowserSync server with Gulp 4 is very clean and easy. + +## Step 1: Install the dependencies + +``` +npm install --save-dev browser-sync +``` + +## Step 2: Setup the project structure + +``` +src/ + scripts/ + |__ index.js +dist/ + scripts/ +index.html +gulpfile.babel.js +``` + +The goal here is to be able to: +- Build the source script file in `src/scripts/`, e.g. compiling with babel, minifying, etc. +- Put the compiled version in `dist/scripts` for use in `index.html` +- Watch for changes in the source file and rebuild the `dist` package +- With each rebuild of the `dist` package, reload the browser to immediately reflect the changes + +## Step 3: Write the gulpfile + +The gulpfile could be broken in 3 parts. + +### 1. Write the task to prepare the dist package as usual + +Refer to the main [README](https://github.com/gulpjs/gulp/blob/4.0/README.md#use-last-javascript-version-in-your-gulpfile) +for more information. + +```javascript +import babel from 'gulp-babel'; +import concat from 'gulp-concat'; +import del from 'del'; +import gulp from 'gulp'; +import uglify from 'gulp-uglify'; + +const paths = { + scripts: { + src: 'src/scripts/*.js', + dest: 'dist/scripts/' + } +}; + +const clean = () => del(['dist']); + +function scripts() { + return gulp.src(paths.scripts.src, { sourcemaps: true }) + .pipe(babel()) + .pipe(uglify()) + .pipe(concat('index.min.js')) + .pipe(gulp.dest(paths.scripts.dest)); +} +``` + +### 2. Setup the BrowserSync server + +And write the tasks to serve and reload the server accordingly. + +```javascript +import browserSync from 'browser-sync'; +const server = browserSync.create(); + +function reload(done) { + server.reload(); + done(); +} + +function serve(done) { + server.init({ + server: { + baseDir: './' + } + }); + done(); +} +``` + +### 3. Watch for source change, rebuild the scripts and reload the server + +This is trivially accomplished with `gulp.series` + +```javascript +const watch = () => gulp.watch(paths.scripts.src, gulp.series(scripts, reload)); +``` + +## Step 4: Bring it all together + +The last step is to expose the default task + +```javascript +const dev = gulp.series(clean, scripts, serve, watch); +export default dev; +``` + +And profit + +```bash +$ gulp +``` + +Now if you go to [http://localhost:3000](http://localhost:3000), which is the default address of the +BrowserSync server, you will see that the end result in the browser is updated everytime you change +the content of the source file. Here is the whole gulpfile: + +```javascript +import babel from 'gulp-babel'; +import concat from 'gulp-concat'; +import del from 'del'; +import gulp from 'gulp'; +import uglify from 'gulp-uglify'; +import browserSync from 'browser-sync'; + +const server = browserSync.create(); + +const paths = { + scripts: { + src: 'src/scripts/*.js', + dest: 'dist/scripts/' + } +}; + +const clean = () => del(['dist']); + +function scripts() { + return gulp.src(paths.scripts.src, { sourcemaps: true }) + .pipe(babel()) + .pipe(uglify()) + .pipe(concat('index.min.js')) + .pipe(gulp.dest(paths.scripts.dest)); +} + +function reload(done) { + server.reload(); + done(); +} + +function serve(done) { + server.init({ + server: { + baseDir: './' + } + }); + done(); +} + +const watch = () => gulp.watch(paths.scripts.src, gulp.series(scripts, reload)); + +const dev = gulp.series(clean, scripts, serve, watch); +export default dev; +``` From e931cb0bbf5acfb68f3e3086dae311ec733f0f14 Mon Sep 17 00:00:00 2001 From: "Chris J. Lee" Date: Tue, 21 Jun 2016 13:59:54 -0500 Subject: [PATCH 112/216] Docs: Fix changelog typos (#1696) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 860674c59..d6555dedd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,9 +40,9 @@ - add babel support - add transpiler fallback support -- add support for some renamed transpilers (livescript, etc) +- add support for some renamed transpilers: livescript, etc - add JSCS -- update dependecies (liftoff, interpret) +- update dependencies (liftoff, interpret) - documentation tweaks ## 3.8.11 From 29ece6fc230fec095b84906437530b0257b26a4c Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 27 Jun 2016 19:54:55 -0700 Subject: [PATCH 113/216] Upgrade: Update undertaker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a23a0dd88..89ce486a3 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dependencies": { "glob-watcher": "^3.0.0", "gulp-cli": "^1.0.0", - "undertaker": "^0.15.0", + "undertaker": "^1.0.0", "vinyl-fs": "^2.0.0" }, "devDependencies": { From d420a6a742af91dcfc3e5a83f7552d2a8114471f Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Mon, 24 Oct 2016 12:48:33 +0100 Subject: [PATCH 114/216] Docs: Have gulp.lastRun take a function to avoid task registration (#1828) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b8ce9bc5..7f9996807 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ const paths = { } function images() { - return gulp.src(paths.images.src, {since: gulp.lastRun('images')}) + return gulp.src(paths.images.src, {since: gulp.lastRun(images)}) .pipe(imagemin({optimizationLevel: 5})) .pipe(gulp.dest(paths.images.dest)); } From 723cbc40e76f3b43c3ec7d56fa4ed36298e912b4 Mon Sep 17 00:00:00 2001 From: Martin van Driel Date: Tue, 8 Nov 2016 04:21:48 +0100 Subject: [PATCH 115/216] Docs: Fix syntax in recipe example (#1715) --- docs/recipes/server-with-livereload-and-css-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes/server-with-livereload-and-css-injection.md b/docs/recipes/server-with-livereload-and-css-injection.md index 74280505d..f5e1222f9 100644 --- a/docs/recipes/server-with-livereload-and-css-injection.md +++ b/docs/recipes/server-with-livereload-and-css-injection.md @@ -96,7 +96,7 @@ gulp.task('serve', gulp.series('sass', function() { }); gulp.watch('scss/*.scss', gulp.series('sass')); -}); +})); ``` and including the pre-processed CSS in `index.html`: From 0ac9e043a4d6947ff2ae4496657ae44b21b5a958 Mon Sep 17 00:00:00 2001 From: Callum Macrae Date: Fri, 18 Nov 2016 21:26:26 +0000 Subject: [PATCH 116/216] Docs: Add "Project structure" section to CONTRIBUTING.md (#1859) --- CONTRIBUTING.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d8f4d5c3..15e8baa31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,53 @@ of the content - formatting: help keep content easy to read with consistent formatting - code: Fix issues or contribute new features to this or any related projects +# Project structure + +Gulp itself is tiny: index.js contains [very few lines of code](https://github.com/gulpjs/gulp/blob/4.0/index.js). +It is powered by a few other libraries which each handle a few specific tasks +each. + +You can view all issues with the "help wanted" label across all gulp projects +here: https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+user%3Agulpjs+label%3A%22help+wanted%22+ + +## Undertaker: task management + +Undertaker handles task management in Gulp: the `gulp.task()`, `gulp.series()` +and `gulp.parallel()` functions. `gulp.series()` and `gulp.parallel()` are in +turn powered by Bach. + +- https://github.com/gulpjs/undertaker +- https://github.com/gulpjs/bach + +## vinyl-fs: file streams + +vinyl-fs powers the `gulp.src()` and `gulp.dest()` functions: they take files +and globs specified by the user, turns them into a stream of file objects, +and then puts them back into the filesystem when `gulp.dest()` is called. + +The file objects themselves are vinyl objects: that's another library (a simple +one!) + +- https://github.com/gulpjs/vinyl-fs +- https://github.com/gulpjs/vinyl + +## chokidar: file watching + +`gulp.watch()` is using chokidar for file watching. It's actually wrapped in a +small library on the gulp organization, glob-watcher. + +- https://github.com/paulmillr/chokidar +- https://github.com/gulpjs/glob-watcher + +## gulp-cli: running gulp + +Finally, we have gulp-cli. This uses liftoff to take what people run in the +command line and run the correct tasks. It works with both gulp 4 and older +versions of gulp. + +- https://github.com/gulpjs/gulp-cli +- https://github.com/js-cli/js-liftoff + # Conduct We are committed to providing a friendly, safe and welcoming environment for From 89acc5c17e115ec03ec6b17341c3ffdf5e2db837 Mon Sep 17 00:00:00 2001 From: Matt Sturgeon Date: Mon, 24 Jul 2017 19:16:35 +0100 Subject: [PATCH 117/216] Docs: Improve ES2015 task exporting examples (#1999) --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7f9996807..3bc84dbaf 100644 --- a/README.md +++ b/README.md @@ -149,13 +149,12 @@ const paths = { }; /* - * For small tasks you can use arrow functions and export + * For small tasks you can export arrow functions */ -const clean = () => del([ 'assets' ]); -export { clean }; +export const clean = () => del([ 'assets' ]); /* - * You can still declare named functions and export them as tasks + * You can also declare named functions and export them as tasks */ export function styles() { return gulp.src(paths.styles.src) @@ -177,13 +176,21 @@ export function scripts() { .pipe(gulp.dest(paths.scripts.dest)); } -export function watch() { + /* + * You could even use `export as` to rename exported tasks + */ +function watchFiles() { gulp.watch(paths.scripts.src, scripts); gulp.watch(paths.styles.src, styles); } +export { watchFiles as watch }; -const build = gulp.series(clean, gulp.parallel(styles, scripts)); -export { build }; +/* + * You can still use `gulp.task` + * for example to set task names that would otherwise be invalid + */ +const clean = gulp.series(clean, gulp.parallel(styles, scripts)); +gulp.task('clean', clean); /* * Export a default task From c1ba80cb6b1a2e1469a7f422ec6ee93ac589d714 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 21 Dec 2017 16:04:04 -0700 Subject: [PATCH 118/216] Breaking: Upgrade major versions of glob-watcher, gulp-cli & vinyl-fs --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 89ce486a3..398cf95ee 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,10 @@ }, "man": "gulp.1", "dependencies": { - "glob-watcher": "^3.0.0", - "gulp-cli": "^1.0.0", + "glob-watcher": "^4.0.0", + "gulp-cli": "^2.0.0", "undertaker": "^1.0.0", - "vinyl-fs": "^2.0.0" + "vinyl-fs": "^3.0.0" }, "devDependencies": { "coveralls": "^2.7.0", From 057df5963034eb6426d99650abba15e4d912b0b7 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 14:59:05 -0700 Subject: [PATCH 119/216] Release: 4.0.0-alpha.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 398cf95ee..3b4d869d2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gulp", "description": "The streaming build system", - "version": "4.0.0-alpha.2", + "version": "4.0.0-alpha.3", "homepage": "http://gulpjs.com", "repository": "gulpjs/gulp", "author": "Fractal (http://wearefractal.com/)", From 064d1001528bc493282f006db6b02d24f97bd981 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 21 Dec 2017 16:12:23 -0700 Subject: [PATCH 120/216] Build: Avoid broken node 9 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8cf5adedd..5756ce0ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,7 @@ node_js: - "0.10" - "0.12" - "4" - - "5" - "6" - - "7" - "8" after_script: - npm run coveralls From 361ab63d5d661dee46e032c3ecac1e085c526f1a Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Fri, 29 Dec 2017 19:05:28 -0700 Subject: [PATCH 121/216] Upgrade: Update glob-watcher --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b4d869d2..5f6679b95 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "man": "gulp.1", "dependencies": { - "glob-watcher": "^4.0.0", + "glob-watcher": "^5.0.0", "gulp-cli": "^2.0.0", "undertaker": "^1.0.0", "vinyl-fs": "^3.0.0" From f27be05f6d7650ffb3ec3f9375f2d9c50c7010df Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Fri, 29 Dec 2017 19:06:54 -0700 Subject: [PATCH 122/216] Update: Remove graceful-fs from test suite --- package.json | 1 - test/dest.js | 3 ++- test/watch.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5f6679b95..7ea402eca 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "coveralls": "^2.7.0", "eslint": "^1.7.3", "eslint-config-gulp": "^2.0.0", - "graceful-fs": "^3.0.0", "istanbul": "^0.3.0", "jscs": "^2.3.5", "jscs-preset-gulp": "^1.0.0", diff --git a/test/dest.js b/test/dest.js index 90dcb9d02..c5f2729f6 100644 --- a/test/dest.js +++ b/test/dest.js @@ -1,10 +1,11 @@ 'use strict'; +var fs = require('fs'); + var gulp = require('../'); var should = require('should'); var join = require('path').join; var rimraf = require('rimraf'); -var fs = require('graceful-fs'); require('mocha'); diff --git a/test/watch.js b/test/watch.js index d2d15b482..d943944e9 100644 --- a/test/watch.js +++ b/test/watch.js @@ -1,7 +1,8 @@ 'use strict'; +var fs = require('fs'); + var gulp = require('../'); -var fs = require('graceful-fs'); var rimraf = require('rimraf'); var mkdirp = require('mkdirp'); var path = require('path'); From 3011cf9a50898030304a159c9e0bad30630380e3 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 13:12:33 -0700 Subject: [PATCH 123/216] Scaffold: Normalize repository --- .editorconfig | 4 +- .gitattributes | 1 + .gitignore | 40 +++++-- .jscsrc | 2 +- .npmignore | 12 --- .travis.yml | 12 +-- LICENSE | 33 +++--- appveyor.yml | 25 +++++ package.json | 67 ++++++------ test/dest.js | 206 ++++++++++++++++++------------------ test/src.js | 254 ++++++++++++++++++++++---------------------- test/watch.js | 279 +++++++++++++++++++++++++------------------------ 12 files changed, 483 insertions(+), 452 deletions(-) create mode 100644 .gitattributes delete mode 100644 .npmignore create mode 100644 appveyor.yml diff --git a/.editorconfig b/.editorconfig index 5d1263484..e000b0ce0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,13 @@ -# editorconfig.org +# http://editorconfig.org root = true [*] indent_style = space indent_size = 2 -end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +end_of_line = lf [*.md] trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..fcadb2cf9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/.gitignore b/.gitignore index cef23f007..6f636468b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,31 @@ -.DS_Store +# Logs +logs *.log -node_modules -build -*.node -components + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul coverage -*.orig -.idea -sandbox -test/out-fixtures/* -test/watch-*.txt -gulp.1 + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Commenting this out is preferred by some people, see +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Users Environment Variables +.lock-wscript + +# Garbage files +.DS_Store diff --git a/.jscsrc b/.jscsrc index d9e4b9a13..703b33fc3 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,3 +1,3 @@ { - "preset": "gulp", + "preset": "gulp" } diff --git a/.npmignore b/.npmignore deleted file mode 100644 index f93c39518..000000000 --- a/.npmignore +++ /dev/null @@ -1,12 +0,0 @@ -.DS_Store -*.log -node_modules -build -*.node -components -coverage -*.orig -.idea -sandbox -test/out-fixtures/* -test/watch-*.txt diff --git a/.travis.yml b/.travis.yml index 5756ce0ce..f621cabd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ sudo: false language: node_js node_js: - - "0.10" - - "0.12" - - "4" - - "6" - - "8" + - '8' + - '6' + - '4' + - '0.12' + - '0.10' after_script: - npm run coveralls -git: - depth: 10 diff --git a/LICENSE b/LICENSE index ee25a9095..6355a4b4a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,21 @@ The MIT License (MIT) -Copyright (c) 2013-2016 Fractal +Copyright (c) 2013-2017 Blaine Bublitz , Eric Schoffstall and other contributors -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..e2d9fb3ad --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,25 @@ +# http://www.appveyor.com/docs/appveyor-yml +# http://www.appveyor.com/docs/lang/nodejs-iojs + +environment: + matrix: + # node.js + - nodejs_version: "0.10" + - nodejs_version: "0.12" + - nodejs_version: "4" + - nodejs_version: "6" + - nodejs_version: "8" + +install: + - ps: Install-Product node $env:nodejs_version + - npm install + +test_script: + - node --version + - npm --version + - cmd: npm test + +build: off + +# build version format +version: "{build}" diff --git a/package.json b/package.json index 7ea402eca..31dab214c 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,34 @@ { "name": "gulp", - "description": "The streaming build system", "version": "4.0.0-alpha.3", + "description": "The streaming build system.", "homepage": "http://gulpjs.com", - "repository": "gulpjs/gulp", - "author": "Fractal (http://wearefractal.com/)", - "keywords": [ - "build", - "stream", - "system", - "make", - "tool", - "asset", - "pipeline" + "author": "Gulp Team (http://gulpjs.com/)", + "contributors": [ + "Eric Schoffstall ", + "Blaine Bublitz " ], + "repository": "gulpjs/gulp", + "license": "MIT", + "engines": { + "node": ">= 0.10" + }, + "main": "index.js", "files": [ + "LICENSE", "index.js", "bin" ], "bin": { "gulp": "./bin/gulp.js" }, - "man": "gulp.1", + "scripts": { + "lint": "eslint . && jscs index.js bin/ test/", + "pretest": "npm run lint", + "test": "mocha --async-only", + "cover": "istanbul cover _mocha --report lcovonly", + "coveralls": "npm run cover && istanbul-coveralls" + }, "dependencies": { "glob-watcher": "^5.0.0", "gulp-cli": "^2.0.0", @@ -29,27 +36,27 @@ "vinyl-fs": "^3.0.0" }, "devDependencies": { - "coveralls": "^2.7.0", "eslint": "^1.7.3", "eslint-config-gulp": "^2.0.0", - "istanbul": "^0.3.0", + "expect": "^1.20.2", + "istanbul": "^0.4.3", + "istanbul-coveralls": "^1.0.3", "jscs": "^2.3.5", "jscs-preset-gulp": "^1.0.0", - "mkdirp": "^0.5.0", - "mocha": "^2.0.1", - "mocha-lcov-reporter": "^1.0.0", - "q": "^1.0.0", - "rimraf": "^2.2.5", - "should": "^5.0.1" - }, - "scripts": { - "lint": "eslint . && jscs *.js bin/ test/", - "pretest": "npm run lint", - "test": "mocha --reporter spec", - "coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage" + "mkdirp": "^0.5.1", + "mocha": "^3.0.0", + "rimraf": "^2.2.5" }, - "engines": { - "node": ">= 0.9" - }, - "license": "MIT" + "keywords": [ + "build", + "stream", + "system", + "make", + "tool", + "asset", + "pipeline", + "series", + "parallel", + "streaming" + ] } diff --git a/test/dest.js b/test/dest.js index c5f2729f6..3dbba4600 100644 --- a/test/dest.js +++ b/test/dest.js @@ -1,133 +1,127 @@ 'use strict'; var fs = require('fs'); +var path = require('path'); -var gulp = require('../'); -var should = require('should'); -var join = require('path').join; +var expect = require('expect'); var rimraf = require('rimraf'); -require('mocha'); +var gulp = require('../'); -var outpath = join(__dirname, './out-fixtures'); +var outpath = path.join(__dirname, './out-fixtures'); -describe('gulp output stream', function() { - describe('dest()', function() { - beforeEach(rimraf.bind(null, outpath)); - afterEach(rimraf.bind(null, outpath)); +describe('gulp.dest()', function() { + beforeEach(rimraf.bind(null, outpath)); + afterEach(rimraf.bind(null, outpath)); - it('should return a stream', function(done) { - var stream = gulp.dest(join(__dirname, './fixtures/')); - should.exist(stream); - should.exist(stream.on); - done(); - }); + it('should return a stream', function(done) { + var stream = gulp.dest(path.join(__dirname, './fixtures/')); + expect(stream).toExist(); + expect(stream.on).toExist(); + done(); + }); - it('should return a output stream that writes files', function(done) { - var instream = gulp.src(join(__dirname, './fixtures/**/*.txt')); - var outstream = gulp.dest(outpath); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // Data should be re-emitted right - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(outpath, './copy/example.txt')); - String(file.contents).should.equal('this is a test'); - }); - outstream.on('end', function() { - fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('this is a test'); - done(); - }); + it('should return a output stream that writes files', function(done) { + var instream = gulp.src('./fixtures/**/*.txt', { cwd: __dirname }); + var outstream = gulp.dest(outpath); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function(file) { + // Data should be re-emitted right + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.contents).toExist(); + expect(file.path).toEqual(path.join(outpath, './copy/example.txt')); + expect(file.contents).toEqual('this is a test'); + }); + outstream.on('end', function() { + fs.readFile(path.join(outpath, 'copy', 'example.txt'), function(err, contents) { + expect(err).toNotExist(); + expect(contents).toExist(); + expect(contents).toEqual('this is a test'); + done(); }); }); + }); - it('should return a output stream that does not write non-read files', function(done) { - var instream = gulp.src(join(__dirname, './fixtures/**/*.txt'), { read: false }); - var outstream = gulp.dest(outpath); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // Data should be re-emitted right - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - join(file.path, '').should.equal(join(outpath, './copy/example.txt')); - }); - outstream.on('end', function() { - fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) { - should.exist(err); - should.not.exist(contents); - done(); - }); + it('should return a output stream that does not write non-read files', function(done) { + var instream = gulp.src('./fixtures/**/*.txt', { read: false, cwd: __dirname }); + var outstream = gulp.dest(outpath); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function(file) { + // Data should be re-emitted right + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.contents).toNotExist(); + expect(file.path).toEqual(path.join(outpath, './copy/example.txt')); + }); + outstream.on('end', function() { + fs.readFile(path.join(outpath, 'copy', 'example.txt'), function(err, contents) { + expect(err).toExist(); + expect(contents).toNotExist(); + done(); }); }); + }); - it('should return a output stream that writes streaming files', function(done) { - var instream = gulp.src(join(__dirname, './fixtures/**/*.txt'), { buffer: false }); - var outstream = instream.pipe(gulp.dest(outpath)); - - outstream.on('error', done); - outstream.on('data', function(file) { - // Data should be re-emitted right - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(outpath, './copy/example.txt')); - }); - outstream.on('end', function() { - fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('this is a test'); - done(); - }); + it('should return a output stream that writes streaming files', function(done) { + var instream = gulp.src('./fixtures/**/*.txt', { buffer: false, cwd: __dirname }); + var outstream = instream.pipe(gulp.dest(outpath)); + + outstream.on('error', done); + outstream.on('data', function(file) { + // Data should be re-emitted right + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.contents).toExist(); + expect(file.path).toEqual(path.join(outpath, './copy/example.txt')); + }); + outstream.on('end', function() { + fs.readFile(path.join(outpath, 'copy', 'example.txt'), function(err, contents) { + expect(err).toNotExist(); + expect(contents).toExist(); + expect(contents).toEqual('this is a test'); + done(); }); }); + }); - it('should return a output stream that writes streaming files into new directories', function(done) { - testWriteDir({}, done); - }); + it('should return a output stream that writes streaming files into new directories', function(done) { + testWriteDir({ cwd: __dirname }, done); + }); - it('should return a output stream that writes streaming files into new directories (buffer: false)', function(done) { - testWriteDir({ buffer: false }, done); - }); + it('should return a output stream that writes streaming files into new directories (buffer: false)', function(done) { + testWriteDir({ buffer: false, cwd: __dirname }, done); + }); - it('should return a output stream that writes streaming files into new directories (read: false)', function(done) { - testWriteDir({ read: false }, done); - }); + it('should return a output stream that writes streaming files into new directories (read: false)', function(done) { + testWriteDir({ read: false, cwd: __dirname }, done); + }); - it('should return a output stream that writes streaming files into new directories (read: false, buffer: false)', function(done) { - testWriteDir({ buffer: false, read: false }, done); - }); + it('should return a output stream that writes streaming files into new directories (read: false, buffer: false)', function(done) { + testWriteDir({ buffer: false, read: false, cwd: __dirname }, done); + }); - function testWriteDir(srcOptions, done) { - var instream = gulp.src(join(__dirname, './fixtures/stuff'), srcOptions); - var outstream = instream.pipe(gulp.dest(outpath)); + function testWriteDir(srcOptions, done) { + var instream = gulp.src('./fixtures/stuff', srcOptions); + var outstream = instream.pipe(gulp.dest(outpath)); - outstream.on('error', done); - outstream.on('data', function(file) { - // Data should be re-emitted right - should.exist(file); - should.exist(file.path); - join(file.path, '').should.equal(join(outpath, './stuff')); - }); - outstream.on('end', function() { - fs.exists(join(outpath, 'stuff'), function(exists) { - /* Stinks that ok is an expression instead of a function call */ - /* jshint expr: true */ - should(exists).be.ok; - /* jshint expr: false */ - done(); - }); + outstream.on('error', done); + outstream.on('data', function(file) { + // Data should be re-emitted right + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.path).toEqual(path.join(outpath, './stuff')); + }); + outstream.on('end', function() { + fs.exists(path.join(outpath, 'stuff'), function(exists) { + expect(exists).toExist(); + done(); }); - } + }); + } - }); }); diff --git a/test/src.js b/test/src.js index a85f237d6..4ec1df962 100644 --- a/test/src.js +++ b/test/src.js @@ -1,152 +1,150 @@ 'use strict'; -var gulp = require('../'); -var should = require('should'); -var join = require('path').join; +var path = require('path'); -require('mocha'); +var expect = require('expect'); -describe('gulp input stream', function() { - describe('src()', function() { - it('should return a stream', function(done) { - var stream = gulp.src(join(__dirname, './fixtures/*.coffee')); - should.exist(stream); - should.exist(stream.on); - done(); +var gulp = require('../'); + +describe('gulp.src()', function() { + it('should return a stream', function(done) { + var stream = gulp.src('./fixtures/*.coffee', { cwd: __dirname }); + expect(stream).toExist(); + expect(stream.on).toExist(); + done(); + }); + it('should return a input stream from a flat glob', function(done) { + var stream = gulp.src('./fixtures/*.coffee', { cwd: __dirname }); + stream.on('error', done); + stream.on('data', function(file) { + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.contents).toExist(); + expect(file.path).toEqual(path.join(__dirname, './fixtures/test.coffee')); + expect(file.contents).toEqual('this is a test'); }); - it('should return a input stream from a flat glob', function(done) { - var stream = gulp.src(join(__dirname, './fixtures/*.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('this is a test'); - }); - stream.on('end', function() { - done(); - }); + stream.on('end', function() { + done(); }); + }); - it('should return a input stream for multiple globs', function(done) { - var globArray = [ - join(__dirname, './fixtures/stuff/run.dmc'), - join(__dirname, './fixtures/stuff/test.dmc'), - ]; - var stream = gulp.src(globArray); + it('should return a input stream for multiple globs', function(done) { + var globArray = [ + './fixtures/stuff/run.dmc', + './fixtures/stuff/test.dmc', + ]; + var stream = gulp.src(globArray, { cwd: __dirname }); - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(2); - files[0].path.should.equal(globArray[0]); - files[1].path.should.equal(globArray[1]); - done(); - }); + var files = []; + stream.on('error', done); + stream.on('data', function(file) { + expect(file).toExist(); + expect(file.path).toExist(); + files.push(file); + }); + stream.on('end', function() { + expect(files.length).toEqual(2); + expect(files[0].path).toEqual(path.join(__dirname, globArray[0])); + expect(files[1].path).toEqual(path.join(__dirname, globArray[1])); + done(); }); + }); - it('should return a input stream for multiple globs, with negation', function(done) { - var expectedPath = join(__dirname, './fixtures/stuff/run.dmc'); - var globArray = [ - join(__dirname, './fixtures/stuff/*.dmc'), - '!' + join(__dirname, './fixtures/stuff/test.dmc'), - ]; - var stream = gulp.src(globArray); + it('should return a input stream for multiple globs, with negation', function(done) { + var expectedPath = path.join(__dirname, './fixtures/stuff/run.dmc'); + var globArray = [ + './fixtures/stuff/*.dmc', + '!fixtures/stuff/test.dmc', + ]; + var stream = gulp.src(globArray, { cwd: __dirname }); - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(1); - files[0].path.should.equal(expectedPath); - done(); - }); + var files = []; + stream.on('error', done); + stream.on('data', function(file) { + expect(file).toExist(); + expect(file.path).toExist(); + files.push(file); }); + stream.on('end', function() { + expect(files.length).toEqual(1); + expect(files[0].path).toEqual(expectedPath); + done(); + }); + }); - it('should return a input stream with no contents when read is false', function(done) { - var stream = gulp.src(join(__dirname, './fixtures/*.coffee'), { read: false }); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - stream.on('end', function() { - done(); - }); + it('should return a input stream with no contents when read is false', function(done) { + var stream = gulp.src('./fixtures/*.coffee', { read: false, cwd: __dirname }); + stream.on('error', done); + stream.on('data', function(file) { + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.contents).toNotExist(); + expect(file.path).toEqual(path.join(__dirname, './fixtures/test.coffee')); }); - it('should return a input stream with contents as stream when buffer is false', function(done) { - var stream = gulp.src(join(__dirname, './fixtures/*.coffee'), { buffer: false }); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - var buf = ''; - file.contents.on('data', function(d) { - buf += d; - }); - file.contents.on('end', function() { - buf.should.equal('this is a test'); - done(); - }); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); + stream.on('end', function() { + done(); }); - it('should return a input stream from a deep glob', function(done) { - var stream = gulp.src(join(__dirname, './fixtures/**/*.jade')); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); - String(file.contents).should.equal('test template'); + }); + it('should return a input stream with contents as stream when buffer is false', function(done) { + var stream = gulp.src('./fixtures/*.coffee', { buffer: false, cwd: __dirname }); + stream.on('error', done); + stream.on('data', function(file) { + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.contents).toExist(); + var buf = ''; + file.contents.on('data', function(d) { + buf += d; }); - stream.on('end', function() { + file.contents.on('end', function() { + expect(buf).toEqual('this is a test'); done(); }); + expect(file.path).toEqual(path.join(__dirname, './fixtures/test.coffee')); }); - it('should return a input stream from a deeper glob', function(done) { - var stream = gulp.src(join(__dirname, './fixtures/**/*.dmc')); - var a = 0; - stream.on('error', done); - stream.on('data', function() { - ++a; - }); - stream.on('end', function() { - a.should.equal(2); - done(); - }); + }); + it('should return a input stream from a deep glob', function(done) { + var stream = gulp.src('./fixtures/**/*.jade', { cwd: __dirname }); + stream.on('error', done); + stream.on('data', function(file) { + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.contents).toExist(); + expect(file.path).toEqual(path.join(__dirname, './fixtures/test/run.jade')); + expect(file.contents).toEqual('test template'); + }); + stream.on('end', function() { + done(); + }); + }); + it('should return a input stream from a deeper glob', function(done) { + var stream = gulp.src('./fixtures/**/*.dmc', { cwd: __dirname }); + var a = 0; + stream.on('error', done); + stream.on('data', function() { + ++a; }); + stream.on('end', function() { + expect(a).toEqual(2); + done(); + }); + }); - it('should return a file stream from a flat path', function(done) { - var a = 0; - var stream = gulp.src(join(__dirname, './fixtures/test.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - ++a; - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('this is a test'); - }); - stream.on('end', function() { - a.should.equal(1); - done(); - }); + it('should return a file stream from a flat path', function(done) { + var a = 0; + var stream = gulp.src(path.join(__dirname, './fixtures/test.coffee')); + stream.on('error', done); + stream.on('data', function(file) { + ++a; + expect(file).toExist(); + expect(file.path).toExist(); + expect(file.contents).toExist(); + expect(file.path).toEqual(path.join(__dirname, './fixtures/test.coffee')); + expect(file.contents).toEqual('this is a test'); + }); + stream.on('end', function() { + expect(a).toEqual(1); + done(); }); }); }); diff --git a/test/watch.js b/test/watch.js index d943944e9..0ac076d23 100644 --- a/test/watch.js +++ b/test/watch.js @@ -1,14 +1,15 @@ 'use strict'; +/* eslint-disable no-use-before-define */ + var fs = require('fs'); +var path = require('path'); -var gulp = require('../'); +var expect = require('expect'); var rimraf = require('rimraf'); var mkdirp = require('mkdirp'); -var path = require('path'); -var should = require('should'); -require('mocha'); +var gulp = require('../'); var outpath = path.join(__dirname, './out-fixtures'); @@ -19,187 +20,189 @@ function createTempFile(path) { } function updateTempFile(path) { - var gazeTimeout = 125; setTimeout(function() { fs.appendFileSync(path, ' changed'); - }, gazeTimeout); + }, 125); } -describe('gulp', function() { - describe('watch()', function() { - beforeEach(rimraf.bind(null, outpath)); - beforeEach(mkdirp.bind(null, outpath)); - afterEach(rimraf.bind(null, outpath)); - - it('should call the function when file changes: no options', function(done) { - var tempFile = path.join(outpath, 'watch-func.txt'); +describe('gulp.watch()', function() { + beforeEach(rimraf.bind(null, outpath)); + beforeEach(mkdirp.bind(null, outpath)); + afterEach(rimraf.bind(null, outpath)); - createTempFile(tempFile); + it('should call the function when file changes: no options', function(done) { + var tempFile = path.join(outpath, 'watch-func.txt'); - var watcher = gulp.watch(tempFile, function(cb) { - watcher.close(); - cb(); - done(); - }); + createTempFile(tempFile); - updateTempFile(tempFile); + var watcher = gulp.watch('watch-func.txt', { cwd: outpath }, function(cb) { + watcher.close(); + cb(); + done(); }); - it('should execute the gulp.parallel tasks', function(done) { - var tempFile = path.join(outpath, 'watch-func.txt'); - - createTempFile(tempFile); + updateTempFile(tempFile); + }); - gulp.task('test', function(cb) { - watcher.close(); - cb(); - done(); - }); + it('should execute the gulp.parallel tasks', function(done) { + var tempFile = path.join(outpath, 'watch-func.txt'); - var watcher = gulp.watch(tempFile, gulp.parallel('test')); + createTempFile(tempFile); - updateTempFile(tempFile); + gulp.task('test', function(cb) { + watcher.close(); + cb(); + done(); }); - it('should work with destructuring', function(done) { - var tempFile = path.join(outpath, 'watch-func.txt'); - var watch = gulp.watch; - var parallel = gulp.parallel; - var task = gulp.task; - createTempFile(tempFile); + var watcher = gulp.watch('watch-func.txt', { cwd: outpath }, gulp.parallel('test')); - task('test', function(cb) { - watcher.close(); - cb(); - done(); - }); - - var watcher = watch(tempFile, parallel('test')); + updateTempFile(tempFile); + }); - updateTempFile(tempFile); + it('should work with destructuring', function(done) { + var tempFile = path.join(outpath, 'watch-func.txt'); + var watch = gulp.watch; + var parallel = gulp.parallel; + var task = gulp.task; + createTempFile(tempFile); + + task('test', function(cb) { + watcher.close(); + cb(); + done(); }); - it('should not call the function when no file changes: no options', function(done) { - var tempFile = path.join(outpath, 'watch-func.txt'); - - createTempFile(tempFile); + var watcher = watch('watch-func.txt', { cwd: outpath }, parallel('test')); - var watcher = gulp.watch(tempFile, function() { - should.fail('Watcher erroneously called'); - }); - - setTimeout(function() { - watcher.close(); - done(); - }, 10); - }); - - it('should call the function when file changes: w/ options', function(done) { - var tempFile = path.join(outpath, 'watch-func-options.txt'); + updateTempFile(tempFile); + }); - createTempFile(tempFile); + it('should not call the function when no file changes: no options', function(done) { + var tempFile = path.join(outpath, 'watch-func.txt'); - var watcher = gulp.watch(tempFile, function(cb) { - watcher.close(); - cb(); - done(); - }); + createTempFile(tempFile); - updateTempFile(tempFile); + var watcher = gulp.watch('watch-func.txt', { cwd: outpath }, function() { + // TODO: proper fail here + expect('Watcher erroneously called'); }); - it('should not drop options when no callback specified', function(done) { - var tempFile = path.join(outpath, 'watch-func-nodrop-options.txt'); - // By passing a cwd option, ensure options are not lost to gaze - var relFile = '../watch-func-nodrop-options.txt'; - var cwd = outpath + '/subdir'; + setTimeout(function() { + watcher.close(); + done(); + }, 10); + }); - createTempFile(tempFile); + it('should call the function when file changes: w/ options', function(done) { + var tempFile = path.join(outpath, 'watch-func-options.txt'); - var watcher = gulp.watch(relFile, {cwd: cwd}) - .on('change', function(filepath) { - should.exist(filepath); - path.resolve(cwd, filepath).should.equal(path.resolve(tempFile)); - watcher.close(); - done(); - }); + createTempFile(tempFile); - updateTempFile(tempFile); + var watcher = gulp.watch('watch-func-options.txt', { cwd: outpath }, function(cb) { + watcher.close(); + cb(); + done(); }); - it('should work without options or callback', function() { - gulp.watch('x'); - }); + updateTempFile(tempFile); + }); - it('should run many tasks: w/ options', function(done) { - var tempFile = path.join(outpath, 'watch-task-options.txt'); - var a = 0; + it('should not drop options when no callback specified', function(done) { + var tempFile = path.join(outpath, 'watch-func-nodrop-options.txt'); + // By passing a cwd option, ensure options are not lost to gaze + var relFile = '../watch-func-nodrop-options.txt'; + var cwd = path.join(outpath, '/subdir'); - createTempFile(tempFile); + createTempFile(tempFile); - gulp.task('task1', function(cb) { - a++; - cb(); - }); - gulp.task('task2', function(cb) { - a += 10; - a.should.equal(11); + var watcher = gulp.watch(relFile, { cwd: cwd }) + .on('change', function(filepath) { + expect(filepath).toExist(); + expect(path.resolve(cwd, filepath)).toEqual(path.resolve(tempFile)); watcher.close(); - cb(); done(); }); - var watcher = gulp.watch(tempFile, gulp.series('task1', 'task2')); + updateTempFile(tempFile); + }); - updateTempFile(tempFile); - }); + it('should work without options or callback', function(done) { + // TODO: check we return watcher? + gulp.watch('x'); + done(); + }); - it('should run many tasks: no options', function(done) { - var tempFile = path.join(outpath, 'watch-many-tasks-no-options.txt'); - var a = 0; + it('should run many tasks: w/ options', function(done) { + var tempFile = path.join(outpath, 'watch-task-options.txt'); + var a = 0; - createTempFile(tempFile); + createTempFile(tempFile); - gulp.task('task1', function(cb) { - a++; - cb(); - }); - gulp.task('task2', function(cb) { - a += 10; - a.should.equal(11); - watcher.close(); - cb(); - done(); - }); + gulp.task('task1', function(cb) { + a++; + cb(); + }); + gulp.task('task2', function(cb) { + a += 10; + expect(a).toEqual(11); + watcher.close(); + cb(); + done(); + }); - var watcher = gulp.watch(tempFile, gulp.series('task1', 'task2')); + var watcher = gulp.watch('watch-task-options.txt', { cwd: outpath }, gulp.series('task1', 'task2')); - updateTempFile(tempFile); - }); + updateTempFile(tempFile); + }); - it('should throw an error: passed parameter (string) is not a function', function(done) { - var tempFile = path.join(outpath, 'empty.txt'); + it('should run many tasks: no options', function(done) { + var tempFile = path.join(outpath, 'watch-many-tasks-no-options.txt'); + var a = 0; - createTempFile(tempFile); - try { - gulp.watch(tempFile, 'task1'); - } catch (err) { - err.message.should.equal('watching ' + tempFile + ': watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)'); - done(); - } + createTempFile(tempFile); + + gulp.task('task1', function(cb) { + a++; + cb(); + }); + gulp.task('task2', function(cb) { + a += 10; + expect(a).toEqual(11); + watcher.close(); + cb(); + done(); }); - it('should throw an error: passed parameter (array) is not a function', function(done) { - var tempFile = path.join(outpath, 'empty.txt'); + var watcher = gulp.watch('./test/out-fixtures/watch-many-tasks-no-options.txt', gulp.series('task1', 'task2')); - createTempFile(tempFile); - try { - gulp.watch(tempFile, ['task1']); - } catch (err) { - err.message.should.equal('watching ' + tempFile + ': watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)'); - done(); - } - }); + updateTempFile(tempFile); + }); + it('should throw an error: passed parameter (string) is not a function', function(done) { + var filename = 'empty.txt'; + var tempFile = path.join(outpath, filename); + + createTempFile(tempFile); + try { + gulp.watch(filename, { cwd: outpath }, 'task1'); + } catch (err) { + expect(err.message).toEqual('watching ' + filename + ': watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)'); + done(); + } }); + + it('should throw an error: passed parameter (array) is not a function', function(done) { + var filename = 'empty.txt'; + var tempFile = path.join(outpath, filename); + + createTempFile(tempFile); + try { + gulp.watch(filename, { cwd: outpath }, ['task1']); + } catch (err) { + expect(err.message).toEqual('watching ' + filename + ': watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)'); + done(); + } + }); + }); From fbc162fe92b6c73c5332b069f2347a0fe8b5d5f3 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 16:50:31 -0700 Subject: [PATCH 124/216] Docs: Remove references to gulp-util --- docs/recipes/automate-release-workflow.md | 4 ++-- docs/recipes/browserify-multiple-destination.md | 4 ++-- docs/recipes/browserify-transforms.md | 4 ++-- docs/recipes/browserify-uglify-sourcemap.md | 4 ++-- docs/recipes/browserify-with-globs.md | 4 ++-- docs/recipes/fast-browserify-builds-with-watchify.md | 6 +++--- docs/recipes/mocha-test-runner-with-gulp.md | 6 +++--- docs/writing-a-plugin/README.md | 11 +++++------ docs/writing-a-plugin/dealing-with-streams.md | 3 +-- docs/writing-a-plugin/guidelines.md | 5 ++--- docs/writing-a-plugin/using-buffers.md | 3 +-- 11 files changed, 25 insertions(+), 29 deletions(-) diff --git a/docs/recipes/automate-release-workflow.md b/docs/recipes/automate-release-workflow.md index 650691c97..6b2f90d04 100644 --- a/docs/recipes/automate-release-workflow.md +++ b/docs/recipes/automate-release-workflow.md @@ -10,7 +10,7 @@ var runSequence = require('run-sequence'); var conventionalChangelog = require('gulp-conventional-changelog'); var conventionalGithubReleaser = require('conventional-github-releaser'); var bump = require('gulp-bump'); -var gutil = require('gulp-util'); +var log = require('gulplog'); var git = require('gulp-git'); var fs = require('fs'); @@ -38,7 +38,7 @@ gulp.task('bump-version', function () { // use minimist (https://www.npmjs.com/package/minimist) to determine with a // command argument whether you are doing a 'major', 'minor' or a 'patch' change. return gulp.src(['./bower.json', './package.json']) - .pipe(bump({type: "patch"}).on('error', gutil.log)) + .pipe(bump({type: "patch"}).on('error', log.error)) .pipe(gulp.dest('./')); }); diff --git a/docs/recipes/browserify-multiple-destination.md b/docs/recipes/browserify-multiple-destination.md index 6a9af199a..474f58286 100644 --- a/docs/recipes/browserify-multiple-destination.md +++ b/docs/recipes/browserify-multiple-destination.md @@ -8,7 +8,7 @@ The below `js` task bundles all the `.js` files under `src/` as entry points and ```js var gulp = require('gulp'); var browserify = require('browserify'); -var gutil = require('gulp-util'); +var log = require('gulplog'); var tap = require('gulp-tap'); var buffer = require('gulp-buffer'); var sourcemaps = require('gulp-sourcemaps'); @@ -21,7 +21,7 @@ gulp.task('js', function () { // transform file objects using gulp-tap plugin .pipe(tap(function (file) { - gutil.log('bundling ' + file.path); + log.info('bundling ' + file.path); // replace file contents with browserify's bundle stream file.contents = browserify(file.path, {debug: true}).bundle(); diff --git a/docs/recipes/browserify-transforms.md b/docs/recipes/browserify-transforms.md index 0137c6ed5..47a0d4d54 100644 --- a/docs/recipes/browserify-transforms.md +++ b/docs/recipes/browserify-transforms.md @@ -13,7 +13,7 @@ var browserify = require('browserify'); var gulp = require('gulp'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); -var gutil = require('gulp-util'); +var log = require('gulplog'); var uglify = require('gulp-uglify'); var sourcemaps = require('gulp-sourcemaps'); var reactify = require('reactify'); @@ -33,7 +33,7 @@ gulp.task('javascript', function () { .pipe(sourcemaps.init({loadMaps: true})) // Add transformation tasks to the pipeline here. .pipe(uglify()) - .on('error', gutil.log) + .on('error', log.error) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./dist/js/')); }); diff --git a/docs/recipes/browserify-uglify-sourcemap.md b/docs/recipes/browserify-uglify-sourcemap.md index 08287edb7..cccfec098 100644 --- a/docs/recipes/browserify-uglify-sourcemap.md +++ b/docs/recipes/browserify-uglify-sourcemap.md @@ -17,7 +17,7 @@ var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var uglify = require('gulp-uglify'); var sourcemaps = require('gulp-sourcemaps'); -var gutil = require('gulp-util'); +var log = require('gulplog'); gulp.task('javascript', function () { // set up the browserify instance on a task basis @@ -32,7 +32,7 @@ gulp.task('javascript', function () { .pipe(sourcemaps.init({loadMaps: true})) // Add transformation tasks to the pipeline here. .pipe(uglify()) - .on('error', gutil.log) + .on('error', log.error) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./dist/js/')); }); diff --git a/docs/recipes/browserify-with-globs.md b/docs/recipes/browserify-with-globs.md index ad7df6f83..72695fb6d 100644 --- a/docs/recipes/browserify-with-globs.md +++ b/docs/recipes/browserify-with-globs.md @@ -14,7 +14,7 @@ var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var globby = require('globby'); var through = require('through2'); -var gutil = require('gulp-util'); +var log = require('gulplog'); var uglify = require('gulp-uglify'); var sourcemaps = require('gulp-sourcemaps'); var reactify = require('reactify'); @@ -33,7 +33,7 @@ gulp.task('javascript', function () { .pipe(sourcemaps.init({loadMaps: true})) // Add gulp plugins to the pipeline here. .pipe(uglify()) - .on('error', gutil.log) + .on('error', log.error) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./dist/js/')); diff --git a/docs/recipes/fast-browserify-builds-with-watchify.md b/docs/recipes/fast-browserify-builds-with-watchify.md index 32847d7cf..b56469589 100644 --- a/docs/recipes/fast-browserify-builds-with-watchify.md +++ b/docs/recipes/fast-browserify-builds-with-watchify.md @@ -14,7 +14,7 @@ var browserify = require('browserify'); var gulp = require('gulp'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); -var gutil = require('gulp-util'); +var log = require('gulplog'); var sourcemaps = require('gulp-sourcemaps'); var assign = require('lodash.assign'); @@ -31,12 +31,12 @@ var b = watchify(browserify(opts)); gulp.task('js', bundle); // so you can run `gulp js` to build the file b.on('update', bundle); // on any dep update, runs the bundler -b.on('log', gutil.log); // output build logs to terminal +b.on('log', log.info); // output build logs to terminal function bundle() { return b.bundle() // log errors if they happen - .on('error', gutil.log.bind(gutil, 'Browserify Error')) + .on('error', log.error.bind(log, 'Browserify Error')) .pipe(source('bundle.js')) // optional, remove if you don't need to buffer file contents .pipe(buffer()) diff --git a/docs/recipes/mocha-test-runner-with-gulp.md b/docs/recipes/mocha-test-runner-with-gulp.md index 775e3a722..4a6b21797 100644 --- a/docs/recipes/mocha-test-runner-with-gulp.md +++ b/docs/recipes/mocha-test-runner-with-gulp.md @@ -22,16 +22,16 @@ gulp.task('default', function() { ### Running mocha tests when files change ```js -// npm install gulp gulp-mocha gulp-util +// npm install gulp gulp-mocha gulplog var gulp = require('gulp'); var mocha = require('gulp-mocha'); -var gutil = require('gulp-util'); +var log = require('gulplog'); gulp.task('mocha', function() { return gulp.src(['test/*.js'], { read: false }) .pipe(mocha({ reporter: 'list' })) - .on('error', gutil.log); + .on('error', log.error); }); gulp.task('watch-mocha', function() { diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md index b5042a600..bcd708356 100644 --- a/docs/writing-a-plugin/README.md +++ b/docs/writing-a-plugin/README.md @@ -126,7 +126,7 @@ A simple example showing how to detect & handle each form is provided below, for approach follow the links above. ```js -var PluginError = require('gulp-util').PluginError; +var PluginError = require('plugin-error'); // consts var PLUGIN_NAME = 'gulp-example'; @@ -176,11 +176,10 @@ if (someCondition) { ## Useful resources -* [File object](https://github.com/gulpjs/gulp-util/#new-fileobj) -* [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) -* [event-stream](https://github.com/dominictarr/event-stream) -* [BufferStream](https://github.com/nfroidure/BufferStream) -* [gulp-util](https://github.com/gulpjs/gulp-util) +* [File object](https://github.com/gulpjs/vinyl) +* [PluginError](https://github.com/gulpjs/plugin-error) +* [through2](https://www.npmjs.com/package/through2) +* [bufferstreams](https://www.npmjs.com/package/bufferstreams) ## Sample plugins diff --git a/docs/writing-a-plugin/dealing-with-streams.md b/docs/writing-a-plugin/dealing-with-streams.md index 57d390b96..fd518bd66 100644 --- a/docs/writing-a-plugin/dealing-with-streams.md +++ b/docs/writing-a-plugin/dealing-with-streams.md @@ -12,8 +12,7 @@ Let's implement a plugin prepending some text to files. This plugin supports all ```js var through = require('through2'); -var gutil = require('gulp-util'); -var PluginError = gutil.PluginError; +var PluginError = require('plugin-error'); // consts const PLUGIN_NAME = 'gulp-prefixer'; diff --git a/docs/writing-a-plugin/guidelines.md b/docs/writing-a-plugin/guidelines.md index 8d97e4e9c..b810ad8a1 100644 --- a/docs/writing-a-plugin/guidelines.md +++ b/docs/writing-a-plugin/guidelines.md @@ -30,7 +30,7 @@ - If you encounter an error **outside** the stream, such as invalid configuration while creating the stream, you may throw it. 1. Prefix any errors with the name of your plugin - For example: `gulp-replace: Cannot do regexp replace on a stream` - - Use gulp-util's [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) class to make this easy + - Use [PluginError](https://github.com/gulpjs/plugin-error) module to make this easy 1. Name your plugin appropriately: it should begin with "gulp-" if it is a gulp plugin - If it is not a gulp plugin, it should not begin with "gulp-" 1. The type of `file.contents` should always be the same going out as it was when it came in @@ -58,8 +58,7 @@ npm is open for everyone, and you are free to make whatever you want but these g ```js // through2 is a thin wrapper around node transform streams var through = require('through2'); -var gutil = require('gulp-util'); -var PluginError = gutil.PluginError; +var PluginError = require('plugin-error'); // Consts const PLUGIN_NAME = 'gulp-prefixer'; diff --git a/docs/writing-a-plugin/using-buffers.md b/docs/writing-a-plugin/using-buffers.md index ae021448b..e35e41b59 100644 --- a/docs/writing-a-plugin/using-buffers.md +++ b/docs/writing-a-plugin/using-buffers.md @@ -9,8 +9,7 @@ If your plugin is relying on a buffer based library, you will probably choose to ```js var through = require('through2'); -var gutil = require('gulp-util'); -var PluginError = gutil.PluginError; +var PluginError = require('plugin-error'); // consts const PLUGIN_NAME = 'gulp-prefixer'; From 76eb4d68c72a87cb1450fdbb069882ee6508f235 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 17:14:34 -0700 Subject: [PATCH 125/216] Docs: Add installation instructions & update badges --- README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3bc84dbaf..abd03bdf7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

The streaming build system

-[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![OpenCollective Backers][backer-badge]][backer-url] [![OpenCollective Sponsors][sponsor-badge]][sponsor-url] [![Gitter chat][gitter-image]][gitter-url] +[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![AppVeyor Build Status][appveyor-image]][appveyor-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![OpenCollective Backers][backer-badge]][backer-url] [![OpenCollective Sponsors][sponsor-badge]][sponsor-url] [![Gitter chat][gitter-image]][gitter-url] ## What is gulp? @@ -15,12 +15,21 @@ - **Strong Ecosystem** - Use npm modules to do anything you want + over 2000 curated plugins for streaming file transformations - **Simple** - By providing only a minimal API surface, gulp is easy to learn and simple to use +## Installation + +There are a few ways to install: + +* gulp v4.0.0 - `npm install gulp@next` +* gulp v4.0.0-alpha.3 - `npm install gulpjs/gulp#4.0.0-alpha.3` +* gulp v4.0.0-alpha.2 - `npm install gulpjs/gulp#4.0.0-alpha.2` +* gulp v3.9.1 - `npm install gulp` + ## Documentation For a Getting started guide, API docs, recipes, making a plugin, etc. check out our docs! -- Need something reliable? Check out the [documentation for the current release](/docs/README.md)! -- Want to help us test the latest and greatest? Check out the [documentation for the next release](https://github.com/gulpjs/gulp/tree/4.0)! +- Check out the [documentation for v4.0.0](/docs/README.md)! __Note: these docs might be behind while we get everything updated.__ +- Using the older v3.9.1? Check out the [documentation at the v3.9.1 tag](https://github.com/gulpjs/gulp/tree/v3.9.1/docs)! ## Sample `gulpfile.js` @@ -259,16 +268,6 @@ function scripts() { } ``` -## Want to test the latest and greatest? - -We're hard at work on our latest release, but we need your help testing it! - -```sh -npm install gulpjs/gulp#4.0 -``` - -There's a slew of major (wonderful) changes in 4.0, so make sure you check out the [docs on that branch](https://github.com/gulpjs/gulp/tree/4.0)! - ## Want to contribute? Anyone can help make this project better - check out our [Contributing guide](/CONTRIBUTING.md)! @@ -290,7 +289,10 @@ Become a sponsor to get your logo on our README on Github. [npm-image]: https://img.shields.io/npm/v/gulp.svg [travis-url]: https://travis-ci.org/gulpjs/gulp -[travis-image]: https://img.shields.io/travis/gulpjs/gulp/master.svg +[travis-image]: https://img.shields.io/travis/gulpjs/gulp.svg?label=travis-ci + +[appveyor-url]: https://ci.appveyor.com/project/gulpjs/gulp +[appveyor-image]: https://img.shields.io/appveyor/ci/gulpjs/gulp.svg?label=appveyor [coveralls-url]: https://coveralls.io/r/gulpjs/gulp [coveralls-image]: https://img.shields.io/coveralls/gulpjs/gulp/master.svg From 2eba29ed5d4d72209548c5b16798ff4c2d2c50a9 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 17:17:53 -0700 Subject: [PATCH 126/216] Docs: Remove run-sequence from recipes --- docs/recipes/automate-release-workflow.md | 36 ++++++++--------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/docs/recipes/automate-release-workflow.md b/docs/recipes/automate-release-workflow.md index 6b2f90d04..0f18006a9 100644 --- a/docs/recipes/automate-release-workflow.md +++ b/docs/recipes/automate-release-workflow.md @@ -6,7 +6,6 @@ Below you have a simple recipe that bumps the project version, commits the chang ``` javascript var gulp = require('gulp'); -var runSequence = require('run-sequence'); var conventionalChangelog = require('gulp-conventional-changelog'); var conventionalGithubReleaser = require('conventional-github-releaser'); var bump = require('gulp-bump'); @@ -48,17 +47,17 @@ gulp.task('commit-changes', function () { .pipe(git.commit('[Prerelease] Bumped version number')); }); -gulp.task('push-changes', function (cb) { - git.push('origin', 'master', cb); +gulp.task('push-changes', function (done) { + git.push('origin', 'master', done); }); -gulp.task('create-new-tag', function (cb) { +gulp.task('create-new-tag', function (done) { var version = getPackageJsonVersion(); git.tag(version, 'Created Tag for version: ' + version, function (error) { if (error) { - return cb(error); + return done(error); } - git.push('origin', 'master', {args: '--tags'}, cb); + git.push('origin', 'master', {args: '--tags'}, done); }); function getPackageJsonVersion () { @@ -68,22 +67,13 @@ gulp.task('create-new-tag', function (cb) { }; }); -gulp.task('release', function (callback) { - runSequence( - 'bump-version', - 'changelog', - 'commit-changes', - 'push-changes', - 'create-new-tag', - 'github-release', - function (error) { - if (error) { - console.log(error.message); - } else { - console.log('RELEASE FINISHED SUCCESSFULLY'); - } - callback(error); - }); -}); +gulp.task('release', gulp.series( + 'bump-version', + 'changelog', + 'commit-changes', + 'push-changes', + 'create-new-tag', + 'github-release' +)); ``` From 03b7c9860bea45649dbfd0c3d83e82a9ea58b4b2 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 17:22:01 -0700 Subject: [PATCH 127/216] Docs: Update recipes to install gulp@next --- docs/getting-started.md | 2 +- docs/recipes/delete-files-folder.md | 4 ++-- docs/recipes/mocha-test-runner-with-gulp.md | 4 ++-- docs/recipes/only-pass-through-changed-files.md | 2 +- docs/recipes/pass-arguments-from-cli.md | 2 +- docs/recipes/rollup-with-rollup-stream.md | 4 ++-- docs/recipes/run-grunt-tasks-from-gulp.md | 2 +- docs/recipes/server-with-livereload-and-css-injection.md | 4 ++-- docs/recipes/split-tasks-across-multiple-files.md | 2 +- docs/recipes/using-external-config-file.md | 2 +- docs/recipes/using-multiple-sources-in-one-task.md | 4 ++-- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 724b942b2..d5240cd1c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -27,7 +27,7 @@ If you don't have a package.json, create one. If you need help, run an `npm init Run this command in your project directory: ```sh -npm install --save-dev gulp +npm install --save-dev gulp@next ``` #### Create a `gulpfile` diff --git a/docs/recipes/delete-files-folder.md b/docs/recipes/delete-files-folder.md index 1373ce6a1..5f7cc1afd 100644 --- a/docs/recipes/delete-files-folder.md +++ b/docs/recipes/delete-files-folder.md @@ -6,7 +6,7 @@ You might want to delete some files before running your build. Since deleting fi Let's use the [`del`](https://github.com/sindresorhus/del) module for this example as it supports multiple files and [globbing](https://github.com/sindresorhus/multimatch#globbing-patterns): ```sh -$ npm install --save-dev gulp del +$ npm install --save-dev gulp@next del ``` Imagine the following file structure: @@ -50,7 +50,7 @@ You might want to delete some files after processing them in a pipeline. We'll use [vinyl-paths](https://github.com/sindresorhus/vinyl-paths) to easily get the file path of files in the stream and pass it to the `del` method. ```sh -$ npm install --save-dev gulp del vinyl-paths +$ npm install --save-dev gulp@next del vinyl-paths ``` Imagine the following file structure: diff --git a/docs/recipes/mocha-test-runner-with-gulp.md b/docs/recipes/mocha-test-runner-with-gulp.md index 4a6b21797..c6ca3873f 100644 --- a/docs/recipes/mocha-test-runner-with-gulp.md +++ b/docs/recipes/mocha-test-runner-with-gulp.md @@ -3,7 +3,7 @@ ### Passing shared module in all tests ```js -// npm install gulp gulp-mocha +// npm install gulp@next gulp-mocha var gulp = require('gulp'); var mocha = require('gulp-mocha'); @@ -22,7 +22,7 @@ gulp.task('default', function() { ### Running mocha tests when files change ```js -// npm install gulp gulp-mocha gulplog +// npm install gulp@next gulp-mocha gulplog var gulp = require('gulp'); var mocha = require('gulp-mocha'); diff --git a/docs/recipes/only-pass-through-changed-files.md b/docs/recipes/only-pass-through-changed-files.md index f2db59722..1b0a34496 100644 --- a/docs/recipes/only-pass-through-changed-files.md +++ b/docs/recipes/only-pass-through-changed-files.md @@ -4,7 +4,7 @@ Files are passed through the whole pipe chain on every run by default. By using ```js -// npm install --save-dev gulp gulp-changed gulp-jscs gulp-uglify +// npm install --save-dev gulp@next gulp-changed gulp-jscs gulp-uglify var gulp = require('gulp'); var changed = require('gulp-changed'); diff --git a/docs/recipes/pass-arguments-from-cli.md b/docs/recipes/pass-arguments-from-cli.md index 48185b6b1..1f9c74b14 100644 --- a/docs/recipes/pass-arguments-from-cli.md +++ b/docs/recipes/pass-arguments-from-cli.md @@ -1,7 +1,7 @@ # Pass arguments from the command line ```js -// npm install --save-dev gulp gulp-if gulp-uglify minimist +// npm install --save-dev gulp@next gulp-if gulp-uglify minimist var gulp = require('gulp'); var gulpif = require('gulp-if'); diff --git a/docs/recipes/rollup-with-rollup-stream.md b/docs/recipes/rollup-with-rollup-stream.md index 5311fd510..24679d557 100644 --- a/docs/recipes/rollup-with-rollup-stream.md +++ b/docs/recipes/rollup-with-rollup-stream.md @@ -4,7 +4,7 @@ Like Browserify, [Rollup](https://rollupjs.org/) is a bundler and thus only fits ## Basic usage ```js -// npm install --save-dev rollup-stream vinyl-source-stream +// npm install --save-dev gulp@next rollup-stream vinyl-source-stream var gulp = require('gulp'); var rollup = require('rollup-stream'); var source = require('vinyl-source-stream'); @@ -24,7 +24,7 @@ gulp.task('rollup', function() { ## Usage with sourcemaps ```js -// npm install --save-dev rollup-stream gulp-sourcemaps vinyl-source-stream vinyl-buffer +// npm install --save-dev gulp@next rollup-stream gulp-sourcemaps vinyl-source-stream vinyl-buffer // optional: npm install --save-dev gulp-rename var gulp = require('gulp'); var rollup = require('rollup-stream'); diff --git a/docs/recipes/run-grunt-tasks-from-gulp.md b/docs/recipes/run-grunt-tasks-from-gulp.md index cdd4f6861..ddf137d66 100644 --- a/docs/recipes/run-grunt-tasks-from-gulp.md +++ b/docs/recipes/run-grunt-tasks-from-gulp.md @@ -7,7 +7,7 @@ It is possible to run Grunt tasks / Grunt plugins from within Gulp. This can be very simple example `gulpfile.js`: ```js -// npm install gulp grunt grunt-contrib-copy --save-dev +// npm install gulp@next grunt grunt-contrib-copy --save-dev var gulp = require('gulp'); var grunt = require('grunt'); diff --git a/docs/recipes/server-with-livereload-and-css-injection.md b/docs/recipes/server-with-livereload-and-css-injection.md index f5e1222f9..a23cc239d 100644 --- a/docs/recipes/server-with-livereload-and-css-injection.md +++ b/docs/recipes/server-with-livereload-and-css-injection.md @@ -2,10 +2,10 @@ With [BrowserSync](https://browsersync.io) and gulp, you can easily create a development server that is accessible to any device on the same WiFi network. BrowserSync also has live-reload built in, so there's nothing else to configure. -First install the module: +First install the modules: ```sh -$ npm install --save-dev browser-sync +$ npm install --save-dev gulp@next browser-sync ``` Then, considering the following file structure... diff --git a/docs/recipes/split-tasks-across-multiple-files.md b/docs/recipes/split-tasks-across-multiple-files.md index 4f69bbde1..afee1be10 100644 --- a/docs/recipes/split-tasks-across-multiple-files.md +++ b/docs/recipes/split-tasks-across-multiple-files.md @@ -17,7 +17,7 @@ tasks/ Install the `gulp-hub` module: ```sh -npm install --save-dev gulp-hub +npm install --save-dev gulp@next gulp-hub ``` Add the following lines to your `gulpfile.js` file: diff --git a/docs/recipes/using-external-config-file.md b/docs/recipes/using-external-config-file.md index 6c740761b..b073f1dfc 100644 --- a/docs/recipes/using-external-config-file.md +++ b/docs/recipes/using-external-config-file.md @@ -30,7 +30,7 @@ Beneficial because it's keeping tasks DRY and config.json can be used by another ###### `gulpfile.js` ```js -// npm install --save-dev gulp gulp-uglify merge-stream +// npm install --save-dev gulp@next gulp-uglify merge-stream var gulp = require('gulp'); var uglify = require('gulp-uglify'); var merge = require('merge-stream'); diff --git a/docs/recipes/using-multiple-sources-in-one-task.md b/docs/recipes/using-multiple-sources-in-one-task.md index 422793783..39e3e3b23 100644 --- a/docs/recipes/using-multiple-sources-in-one-task.md +++ b/docs/recipes/using-multiple-sources-in-one-task.md @@ -1,7 +1,7 @@ # Using multiple sources in one task ```js -// npm install --save-dev gulp merge-stream +// npm install --save-dev gulp@next merge-stream var gulp = require('gulp'); var merge = require('merge-stream'); @@ -20,7 +20,7 @@ gulp.task('test', function() { `gulp.src` will emit files in the order they were added: ```js -// npm install gulp gulp-concat +// npm install gulp@next gulp-concat var gulp = require('gulp'); var concat = require('gulp-concat'); From ec54d09570a891d39a2ccd6f5b13d9a5dd57c68f Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 17:23:34 -0700 Subject: [PATCH 128/216] Docs: Improve note about out-of-date docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abd03bdf7..00e1d1c08 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ There are a few ways to install: For a Getting started guide, API docs, recipes, making a plugin, etc. check out our docs! -- Check out the [documentation for v4.0.0](/docs/README.md)! __Note: these docs might be behind while we get everything updated.__ +- Check out the [documentation for v4.0.0](/docs/README.md)! __Excuse our dust; these docs might be behind while we get everything updated. Please open an issue if something isn't working.__ - Using the older v3.9.1? Check out the [documentation at the v3.9.1 tag](https://github.com/gulpjs/gulp/tree/v3.9.1/docs)! ## Sample `gulpfile.js` From 173a532d0b79912ccf0050e6c5b4ffddd0d0fd1d Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 18:24:54 -0700 Subject: [PATCH 129/216] Docs: Fix the installation instructions --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 00e1d1c08..3fd3f6fe5 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,7 @@ There are a few ways to install: * gulp v4.0.0 - `npm install gulp@next` -* gulp v4.0.0-alpha.3 - `npm install gulpjs/gulp#4.0.0-alpha.3` -* gulp v4.0.0-alpha.2 - `npm install gulpjs/gulp#4.0.0-alpha.2` +* gulp v4.0.0-alpha.3 - `npm install gulpjs/gulp#v4.0.0-alpha.3` * gulp v3.9.1 - `npm install gulp` ## Documentation From 55eb23a268dcc7340bb40808600fd4802848c06f Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 31 Dec 2017 18:25:46 -0700 Subject: [PATCH 130/216] Release: 4.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 31dab214c..b50db5400 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gulp", - "version": "4.0.0-alpha.3", + "version": "4.0.0", "description": "The streaming build system.", "homepage": "http://gulpjs.com", "author": "Gulp Team (http://gulpjs.com/)", From a010db615fae4fbf8ecfdaff5530a4d84107d984 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 1 Jan 2018 20:12:47 -0700 Subject: [PATCH 131/216] Scaffold: Mark *.png and *.jpg as binary files to git --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index fcadb2cf9..1de36465d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,5 @@ * text eol=lf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary From a4e8d4853c1a846654eeb809e6fc6aac30df6c11 Mon Sep 17 00:00:00 2001 From: Charles Samborski Date: Thu, 18 Jan 2018 18:16:31 +0100 Subject: [PATCH 132/216] Docs: Fix error in ES2015 usage example (fixes #2099) (#2100) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3fd3f6fe5..1d6e35c7e 100644 --- a/README.md +++ b/README.md @@ -197,8 +197,8 @@ export { watchFiles as watch }; * You can still use `gulp.task` * for example to set task names that would otherwise be invalid */ -const clean = gulp.series(clean, gulp.parallel(styles, scripts)); -gulp.task('clean', clean); +const build = gulp.series(clean, gulp.parallel(styles, scripts)); +gulp.task('build', build); /* * Export a default task From 126423ac824f0a3bc3ac6a718077b5f40d0d0792 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Wed, 7 Feb 2018 11:17:53 -0800 Subject: [PATCH 133/216] Docs: Add temporary notice for 4.0.0 vs 3.9.1 documentation (#2121) --- docs/API.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/API.md b/docs/API.md index 1415c6af2..338bff408 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,3 +1,7 @@ +Note: these docs are for version v4.0.0 (aka `gulp@next`) If you're on gulp +v3.9.1, which is the current standard `npm` release, you probably want [that +version's documentation](https://github.com/gulpjs/gulp/blob/v3.9.1/docs/API.md). + ## gulp API docs * [gulp.src](#gulpsrcglobs-options) - Emit files matching one or more globs From 45830cf9bab1c5bf2d39602aea12f30f189d3a65 Mon Sep 17 00:00:00 2001 From: contra Date: Fri, 9 Feb 2018 16:31:55 -0500 Subject: [PATCH 134/216] Docs: Improve recipe for empty glob array (closes #2122) --- docs/recipes/running-task-steps-per-folder.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/recipes/running-task-steps-per-folder.md b/docs/recipes/running-task-steps-per-folder.md index 905f697b9..3abd0e17c 100644 --- a/docs/recipes/running-task-steps-per-folder.md +++ b/docs/recipes/running-task-steps-per-folder.md @@ -36,9 +36,9 @@ function getFolders(dir) { }); } -gulp.task('scripts', function() { +gulp.task('scripts', function(done) { var folders = getFolders(scriptsPath); - + if (folder.length === 0) return done(); // nothing to do! var tasks = folders.map(function(folder) { return gulp.src(path.join(scriptsPath, folder, '/**/*.js')) // concat into foldername.js From b065a132a68ee8d68e94a5c650169e45468a3a78 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 14 Feb 2018 12:53:55 -0700 Subject: [PATCH 135/216] Docs: Reword standard to default --- docs/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API.md b/docs/API.md index 338bff408..017164bfb 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,5 +1,5 @@ Note: these docs are for version v4.0.0 (aka `gulp@next`) If you're on gulp -v3.9.1, which is the current standard `npm` release, you probably want [that +v3.9.1, which is the current default `npm` release, you probably want [that version's documentation](https://github.com/gulpjs/gulp/blob/v3.9.1/docs/API.md). ## gulp API docs From 86acdea1d4b01deb9edcb2400b92b6ebc07fc4a7 Mon Sep 17 00:00:00 2001 From: Christopher Andersson Date: Sun, 13 May 2018 18:46:09 -0400 Subject: [PATCH 136/216] Docs: Fix recipe typo (#2156) --- docs/recipes/running-task-steps-per-folder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes/running-task-steps-per-folder.md b/docs/recipes/running-task-steps-per-folder.md index 3abd0e17c..df5c53695 100644 --- a/docs/recipes/running-task-steps-per-folder.md +++ b/docs/recipes/running-task-steps-per-folder.md @@ -38,7 +38,7 @@ function getFolders(dir) { gulp.task('scripts', function(done) { var folders = getFolders(scriptsPath); - if (folder.length === 0) return done(); // nothing to do! + if (folders.length === 0) return done(); // nothing to do! var tasks = folders.map(function(folder) { return gulp.src(path.join(scriptsPath, folder, '/**/*.js')) // concat into foldername.js From d693e499ac7c40b6c52891aa111947cad95aa2ab Mon Sep 17 00:00:00 2001 From: Cole Gentry Date: Mon, 29 Jan 2018 16:01:01 -0500 Subject: [PATCH 137/216] Docs: Add front-matter to each file (#2109) --- docs/API.md | 7 ++++++ docs/CLI.md | 9 +++++++- docs/FAQ.md | 7 ++++++ docs/README.md | 7 ++++++ docs/getting-started.md | 7 ++++++ docs/recipes/README.md | 7 ++++++ docs/recipes/automate-release-workflow.md | 7 ++++++ .../browserify-multiple-destination.md | 7 ++++++ docs/recipes/browserify-transforms.md | 7 ++++++ docs/recipes/browserify-uglify-sourcemap.md | 9 +++++++- docs/recipes/browserify-with-globs.md | 7 ++++++ .../combining-streams-to-handle-errors.md | 7 ++++++ docs/recipes/cron-task.md | 9 +++++++- docs/recipes/delete-files-folder.md | 6 +++++ docs/recipes/exports-as-tasks.md | 7 ++++++ .../fast-browserify-builds-with-watchify.md | 9 +++++++- .../handling-the-delete-event-on-watch.md | 7 ++++++ .../incremental-builds-with-concatenate.md | 7 ++++++ ...tain-directory-structure-while-globbing.md | 22 ++++++++++++------- docs/recipes/make-stream-from-buffer.md | 15 +++++++++---- docs/recipes/minified-and-non-minified.md | 7 ++++++ .../minimal-browsersync-setup-with-gulp4.md | 7 ++++++ docs/recipes/mocha-test-runner-with-gulp.md | 7 ++++++ .../only-pass-through-changed-files.md | 7 ++++++ docs/recipes/pass-arguments-from-cli.md | 7 ++++++ .../recipes/rebuild-only-files-that-change.md | 7 ++++++ docs/recipes/rollup-with-rollup-stream.md | 7 ++++++ docs/recipes/run-grunt-tasks-from-gulp.md | 9 +++++++- docs/recipes/running-shell-commands.md | 7 ++++++ docs/recipes/running-task-steps-per-folder.md | 11 ++++++++-- docs/recipes/running-tasks-in-series.md | 7 ++++++ ...erver-with-livereload-and-css-injection.md | 7 ++++++ .../sharing-streams-with-stream-factories.md | 7 ++++++ docs/recipes/specifying-a-cwd.md | 7 ++++++ .../split-tasks-across-multiple-files.md | 7 ++++++ ...plating-with-swig-and-yaml-front-matter.md | 7 ++++++ docs/recipes/using-external-config-file.md | 7 ++++++ .../using-multiple-sources-in-one-task.md | 7 ++++++ docs/why-use-pump/README.md | 7 ++++++ docs/writing-a-plugin/README.md | 7 ++++++ docs/writing-a-plugin/dealing-with-streams.md | 8 ++++++- docs/writing-a-plugin/guidelines.md | 7 ++++++ docs/writing-a-plugin/recommended-modules.md | 7 ++++++ docs/writing-a-plugin/testing.md | 7 ++++++ docs/writing-a-plugin/using-buffers.md | 7 ++++++ 45 files changed, 332 insertions(+), 20 deletions(-) diff --git a/docs/API.md b/docs/API.md index 017164bfb..5a9771bb1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,3 +1,10 @@ + + Note: these docs are for version v4.0.0 (aka `gulp@next`) If you're on gulp v3.9.1, which is the current default `npm` release, you probably want [that version's documentation](https://github.com/gulpjs/gulp/blob/v3.9.1/docs/API.md). diff --git a/docs/CLI.md b/docs/CLI.md index c4af45f76..daa3f5d09 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,3 +1,10 @@ + + ## gulp CLI docs ### Flags @@ -105,7 +112,7 @@ Output: Command: `gulp --tasks-simple` -Output: +Output: ```shell one two diff --git a/docs/FAQ.md b/docs/FAQ.md index f8a649ead..9c89c143a 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,3 +1,10 @@ + + # FAQ ## Why gulp? Why not ____? diff --git a/docs/README.md b/docs/README.md index 4f8761b4a..d1b9f8c28 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,10 @@ + + # gulp documentation * [Getting Started](getting-started.md) - Get started with gulp diff --git a/docs/getting-started.md b/docs/getting-started.md index d5240cd1c..6cfe981b4 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,3 +1,10 @@ + + # Getting Started *If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions.* For more information, read this [Sip](https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467). diff --git a/docs/recipes/README.md b/docs/recipes/README.md index 3e822432a..e1ec37211 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -1,3 +1,10 @@ + + # Recipes * [Automate release workflow](automate-release-workflow.md) diff --git a/docs/recipes/automate-release-workflow.md b/docs/recipes/automate-release-workflow.md index 0f18006a9..3b929866a 100644 --- a/docs/recipes/automate-release-workflow.md +++ b/docs/recipes/automate-release-workflow.md @@ -1,3 +1,10 @@ + + # Automate release workflow If your project follows a semantic versioning, it may be a good idea to automatize the steps needed to do a release. diff --git a/docs/recipes/browserify-multiple-destination.md b/docs/recipes/browserify-multiple-destination.md index 474f58286..fa1b65cec 100644 --- a/docs/recipes/browserify-multiple-destination.md +++ b/docs/recipes/browserify-multiple-destination.md @@ -1,3 +1,10 @@ + + # Browserify + Globs (multiple destination) This example shows how to set up a task of bundling multiple entry points into multiple destinations using browserify. diff --git a/docs/recipes/browserify-transforms.md b/docs/recipes/browserify-transforms.md index 47a0d4d54..8659614ff 100644 --- a/docs/recipes/browserify-transforms.md +++ b/docs/recipes/browserify-transforms.md @@ -1,3 +1,10 @@ + + # Browserify + Transforms [Browserify](https://github.com/browserify/browserify) has become an important and indispensable diff --git a/docs/recipes/browserify-uglify-sourcemap.md b/docs/recipes/browserify-uglify-sourcemap.md index cccfec098..99800ed53 100644 --- a/docs/recipes/browserify-uglify-sourcemap.md +++ b/docs/recipes/browserify-uglify-sourcemap.md @@ -1,10 +1,17 @@ + + # Browserify + Uglify2 with sourcemaps [Browserify](https://github.com/browserify/browserify) has become an important and indispensable tool but requires being wrapped before working well with gulp. Below is a simple recipe for using Browserify with full sourcemaps that resolve to the original individual files. -See also: the [Combining Streams to Handle Errors](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) recipe for handling errors with browserify or uglify in your stream. +See also: the [Combining Streams to Handle Errors](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) recipe for handling errors with browserify or uglify in your stream. A simple `gulpfile.js` file for Browserify + Uglify2 with sourcemaps: diff --git a/docs/recipes/browserify-with-globs.md b/docs/recipes/browserify-with-globs.md index 72695fb6d..d9396f1b6 100644 --- a/docs/recipes/browserify-with-globs.md +++ b/docs/recipes/browserify-with-globs.md @@ -1,3 +1,10 @@ + + # Browserify + Globs [Browserify + Uglify2](https://github.com/gulpjs/gulp/blob/master/docs/recipes/browserify-uglify-sourcemap.md) shows how to setup a basic gulp task to bundle a JavaScript file with its dependencies, and minify the bundle with UglifyJS while preserving source maps. diff --git a/docs/recipes/combining-streams-to-handle-errors.md b/docs/recipes/combining-streams-to-handle-errors.md index 7c38654af..16b561669 100644 --- a/docs/recipes/combining-streams-to-handle-errors.md +++ b/docs/recipes/combining-streams-to-handle-errors.md @@ -1,3 +1,10 @@ + + # Combining streams to handle errors By default, emitting an error on a stream will cause it to be thrown unless it already has a listener attached to the `error` event. This gets a bit tricky when you're working with longer pipelines of streams. diff --git a/docs/recipes/cron-task.md b/docs/recipes/cron-task.md index 030fb334a..ad90a6f01 100644 --- a/docs/recipes/cron-task.md +++ b/docs/recipes/cron-task.md @@ -1,9 +1,16 @@ + + # Run gulp task via cron job While logged in via a user that has privileges to run `gulp`, run the following: crontab -e - + to edit your current "[crontab](https://en.wikipedia.org/wiki/Cron)" file. Typically, within a cron job, you want to run any binary using absolute paths, diff --git a/docs/recipes/delete-files-folder.md b/docs/recipes/delete-files-folder.md index 5f7cc1afd..731ae4f91 100644 --- a/docs/recipes/delete-files-folder.md +++ b/docs/recipes/delete-files-folder.md @@ -1,3 +1,9 @@ + # Delete files and folders diff --git a/docs/recipes/exports-as-tasks.md b/docs/recipes/exports-as-tasks.md index 2eaea4905..892c6bbf5 100644 --- a/docs/recipes/exports-as-tasks.md +++ b/docs/recipes/exports-as-tasks.md @@ -1,3 +1,10 @@ + + # Exports as Tasks Using the ES2015 module syntax you can use your exports as tasks. diff --git a/docs/recipes/fast-browserify-builds-with-watchify.md b/docs/recipes/fast-browserify-builds-with-watchify.md index b56469589..aa07f1a93 100644 --- a/docs/recipes/fast-browserify-builds-with-watchify.md +++ b/docs/recipes/fast-browserify-builds-with-watchify.md @@ -1,3 +1,10 @@ + + # Fast browserify builds with watchify As a [browserify](https://github.com/browserify/browserify) project begins to expand, the time to bundle it slowly gets longer and longer. While it might start at 1 second, it's possible to be waiting 30 seconds for your project to build on particularly large projects. @@ -24,7 +31,7 @@ var customOpts = { debug: true }; var opts = assign({}, watchify.args, customOpts); -var b = watchify(browserify(opts)); +var b = watchify(browserify(opts)); // add transformations here // i.e. b.transform(coffeeify); diff --git a/docs/recipes/handling-the-delete-event-on-watch.md b/docs/recipes/handling-the-delete-event-on-watch.md index 05bd683a1..ba7a77cd3 100644 --- a/docs/recipes/handling-the-delete-event-on-watch.md +++ b/docs/recipes/handling-the-delete-event-on-watch.md @@ -1,3 +1,10 @@ + + # Handling the Delete Event on Watch You can listen for `'unlink'` events to fire on the watcher returned from `gulp.watch`. diff --git a/docs/recipes/incremental-builds-with-concatenate.md b/docs/recipes/incremental-builds-with-concatenate.md index c9fdaa0de..797bd0cab 100644 --- a/docs/recipes/incremental-builds-with-concatenate.md +++ b/docs/recipes/incremental-builds-with-concatenate.md @@ -1,3 +1,10 @@ + + # Incremental rebuilding, including operating on full file sets The trouble with incremental rebuilds is you often want to operate on _all_ processed files, not just single files. For example, you may want to lint and module-wrap just the file(s) that have changed, then concatenate it with all other linted and module-wrapped files. This is difficult without the use of temp files. diff --git a/docs/recipes/maintain-directory-structure-while-globbing.md b/docs/recipes/maintain-directory-structure-while-globbing.md index aadd32ef4..279eac11d 100644 --- a/docs/recipes/maintain-directory-structure-while-globbing.md +++ b/docs/recipes/maintain-directory-structure-while-globbing.md @@ -1,9 +1,16 @@ + + # Maintain Directory Structure while Globbing If you are planning to read a few files/folders from a directory and maintain their relative path, you need to pass `{base: '.'}` as the second argument to `gulp.src()`. -For example, if you have a directory structure like +For example, if you have a directory structure like ![Dev setup](https://cloud.githubusercontent.com/assets/2562992/3178498/bedf75b4-ec1a-11e3-8a71-a150ad94b450.png) @@ -27,18 +34,17 @@ If you want to maintain the structure, you need to pass `{base: '.'}` to `gulp.s ```js gulp.task('task', function () { - return gulp.src(['index.html', - 'css/**', - 'js/**', - 'lib/**', - 'images/**', + return gulp.src(['index.html', + 'css/**', + 'js/**', + 'lib/**', + 'images/**', 'plugin/**' ], {base: '.'}) .pipe(operation1()) .pipe(operation2()); }); ``` -And the input to your `operation1()` will be a folder structure like +And the input to your `operation1()` will be a folder structure like ![with-base](https://cloud.githubusercontent.com/assets/2562992/3178607/053d6722-ec1c-11e3-9ba8-7ce39e1a480e.png) - diff --git a/docs/recipes/make-stream-from-buffer.md b/docs/recipes/make-stream-from-buffer.md index f11c11612..b376eafce 100644 --- a/docs/recipes/make-stream-from-buffer.md +++ b/docs/recipes/make-stream-from-buffer.md @@ -1,3 +1,10 @@ + + # Make stream from buffer (memory contents) Sometimes you may need to start a stream with files that their contents are in a variable and not in a physical file. In other words, how to start a 'gulp' stream without using `gulp.src()`. @@ -78,9 +85,9 @@ gulp.task('write-versions', function() { availableVersions.forEach(function(v) { // make a new stream with fake file name var stream = source('final.' + v); - + var streamEnd = stream; - + // we load the data from the concatenated libs var fileContents = memory['libs.concat.js'] + // we add the version's data @@ -99,11 +106,11 @@ gulp.task('write-versions', function() { .pipe(vinylBuffer()) //.pipe(tap(function(file) { /* do something with the file contents here */ })) .pipe(gulp.dest('output')); - + // add the end of the stream, otherwise the task would finish before all the processing // is done streams.push(streamEnd); - + }); return es.merge.apply(this, streams); diff --git a/docs/recipes/minified-and-non-minified.md b/docs/recipes/minified-and-non-minified.md index e1a6a4296..3cc603a9e 100644 --- a/docs/recipes/minified-and-non-minified.md +++ b/docs/recipes/minified-and-non-minified.md @@ -1,3 +1,10 @@ + + # Output both a minified and non-minified version Outputting both a minified and non-minified version of your combined JavaScript files can be achieved by using `gulp-rename` and piping to `dest` twice (once before minifying and once after minifying): diff --git a/docs/recipes/minimal-browsersync-setup-with-gulp4.md b/docs/recipes/minimal-browsersync-setup-with-gulp4.md index ec22c0717..55a5ac38c 100644 --- a/docs/recipes/minimal-browsersync-setup-with-gulp4.md +++ b/docs/recipes/minimal-browsersync-setup-with-gulp4.md @@ -1,3 +1,10 @@ + + # Minimal BrowserSync setup with Gulp 4 [BrowserSync](https://www.browsersync.io/) is a great tool to streamline diff --git a/docs/recipes/mocha-test-runner-with-gulp.md b/docs/recipes/mocha-test-runner-with-gulp.md index c6ca3873f..eab0725cd 100644 --- a/docs/recipes/mocha-test-runner-with-gulp.md +++ b/docs/recipes/mocha-test-runner-with-gulp.md @@ -1,3 +1,10 @@ + + # Mocha test-runner with gulp ### Passing shared module in all tests diff --git a/docs/recipes/only-pass-through-changed-files.md b/docs/recipes/only-pass-through-changed-files.md index 1b0a34496..64d16dbe7 100644 --- a/docs/recipes/only-pass-through-changed-files.md +++ b/docs/recipes/only-pass-through-changed-files.md @@ -1,3 +1,10 @@ + + # Only pass through changed files Files are passed through the whole pipe chain on every run by default. By using [gulp-changed](https://github.com/sindresorhus/gulp-changed) only changed files will be passed through. This can speed up consecutive runs considerably. diff --git a/docs/recipes/pass-arguments-from-cli.md b/docs/recipes/pass-arguments-from-cli.md index 1f9c74b14..40c44bc15 100644 --- a/docs/recipes/pass-arguments-from-cli.md +++ b/docs/recipes/pass-arguments-from-cli.md @@ -1,3 +1,10 @@ + + # Pass arguments from the command line ```js diff --git a/docs/recipes/rebuild-only-files-that-change.md b/docs/recipes/rebuild-only-files-that-change.md index 245ddc4fb..d60a80257 100644 --- a/docs/recipes/rebuild-only-files-that-change.md +++ b/docs/recipes/rebuild-only-files-that-change.md @@ -1,3 +1,10 @@ + + # Rebuild only files that change With [`gulp-watch`](https://github.com/floatdrop/gulp-watch): diff --git a/docs/recipes/rollup-with-rollup-stream.md b/docs/recipes/rollup-with-rollup-stream.md index 24679d557..4c77aacc1 100644 --- a/docs/recipes/rollup-with-rollup-stream.md +++ b/docs/recipes/rollup-with-rollup-stream.md @@ -1,3 +1,10 @@ + + # Rollup with rollup-stream Like Browserify, [Rollup](https://rollupjs.org/) is a bundler and thus only fits naturally into gulp if it's at the start of the pipeline. Unlike Browserify, Rollup doesn't natively produce a stream as output and needs to be wrapped before it can take this position. [rollup-stream](https://github.com/Permutatrix/rollup-stream) does this for you, producing output just like that of Browserify's `bundle()` method—as a result, most of the Browserify recipes here will also work with rollup-stream. diff --git a/docs/recipes/run-grunt-tasks-from-gulp.md b/docs/recipes/run-grunt-tasks-from-gulp.md index ddf137d66..ee633125f 100644 --- a/docs/recipes/run-grunt-tasks-from-gulp.md +++ b/docs/recipes/run-grunt-tasks-from-gulp.md @@ -1,3 +1,10 @@ + + # Run Grunt Tasks from Gulp It is possible to run Grunt tasks / Grunt plugins from within Gulp. This can be useful during a gradual migration from Grunt to Gulp or if there's a specific plugin that you need. With the described approach no Grunt CLI and no Gruntfile is required. @@ -32,7 +39,7 @@ gulp.task('copy', function (done) { ``` -Now start the task with: +Now start the task with: `gulp copy` With the aforementioned approach the grunt tasks get registered within gulp's task system. **Keep in mind grunt tasks are usually blocking (unlike gulp), therefore no other task (not even a gulp task) can run until a grunt task is completed.** diff --git a/docs/recipes/running-shell-commands.md b/docs/recipes/running-shell-commands.md index 5d3447476..7bc4eb1fe 100644 --- a/docs/recipes/running-shell-commands.md +++ b/docs/recipes/running-shell-commands.md @@ -1,3 +1,10 @@ + + # Running Shell Commands Sometimes it is helpful to be able to call existing command line tools from gulp. diff --git a/docs/recipes/running-task-steps-per-folder.md b/docs/recipes/running-task-steps-per-folder.md index df5c53695..5ee3b6490 100644 --- a/docs/recipes/running-task-steps-per-folder.md +++ b/docs/recipes/running-task-steps-per-folder.md @@ -1,3 +1,10 @@ + + # Generating a file per folder If you have a set of folders, and wish to perform a set of tasks on each, for instance... @@ -44,11 +51,11 @@ gulp.task('scripts', function(done) { // concat into foldername.js .pipe(concat(folder + '.js')) // write to output - .pipe(gulp.dest(scriptsPath)) + .pipe(gulp.dest(scriptsPath)) // minify .pipe(uglify()) // rename to folder.min.js - .pipe(rename(folder + '.min.js')) + .pipe(rename(folder + '.min.js')) // write to output again .pipe(gulp.dest(scriptsPath)); }); diff --git a/docs/recipes/running-tasks-in-series.md b/docs/recipes/running-tasks-in-series.md index b28e692a3..be60816fb 100644 --- a/docs/recipes/running-tasks-in-series.md +++ b/docs/recipes/running-tasks-in-series.md @@ -1,3 +1,10 @@ + + # Running tasks in series By default, gulp CLI run tasks with maximum concurrency - e.g. it launches diff --git a/docs/recipes/server-with-livereload-and-css-injection.md b/docs/recipes/server-with-livereload-and-css-injection.md index a23cc239d..51b652268 100644 --- a/docs/recipes/server-with-livereload-and-css-injection.md +++ b/docs/recipes/server-with-livereload-and-css-injection.md @@ -1,3 +1,10 @@ + + # Server with live-reloading and CSS injection With [BrowserSync](https://browsersync.io) and gulp, you can easily create a development server that is accessible to any device on the same WiFi network. BrowserSync also has live-reload built in, so there's nothing else to configure. diff --git a/docs/recipes/sharing-streams-with-stream-factories.md b/docs/recipes/sharing-streams-with-stream-factories.md index 16c110a06..932085235 100644 --- a/docs/recipes/sharing-streams-with-stream-factories.md +++ b/docs/recipes/sharing-streams-with-stream-factories.md @@ -1,3 +1,10 @@ + + # Sharing streams with stream factories If you use the same plugins in multiple tasks you might find yourself getting that itch to DRY things up. This method will allow you to create factories to split out your commonly used stream chains. diff --git a/docs/recipes/specifying-a-cwd.md b/docs/recipes/specifying-a-cwd.md index eba0ceca9..b6306d6a4 100644 --- a/docs/recipes/specifying-a-cwd.md +++ b/docs/recipes/specifying-a-cwd.md @@ -1,3 +1,10 @@ + + # Specifying a new cwd (current working directory) This is helpful for projects using a nested directory structure, such as: diff --git a/docs/recipes/split-tasks-across-multiple-files.md b/docs/recipes/split-tasks-across-multiple-files.md index afee1be10..efdd942fa 100644 --- a/docs/recipes/split-tasks-across-multiple-files.md +++ b/docs/recipes/split-tasks-across-multiple-files.md @@ -1,3 +1,10 @@ + + # Split tasks across multiple files If your `gulpfile.js` is starting to grow too large, you can split the tasks diff --git a/docs/recipes/templating-with-swig-and-yaml-front-matter.md b/docs/recipes/templating-with-swig-and-yaml-front-matter.md index 50c98e5f7..dbda0ad14 100644 --- a/docs/recipes/templating-with-swig-and-yaml-front-matter.md +++ b/docs/recipes/templating-with-swig-and-yaml-front-matter.md @@ -1,3 +1,10 @@ + + # Templating with Swig and YAML front-matter Templating can be setup using `gulp-swig` and `gulp-front-matter`: diff --git a/docs/recipes/using-external-config-file.md b/docs/recipes/using-external-config-file.md index b073f1dfc..4a2215d48 100644 --- a/docs/recipes/using-external-config-file.md +++ b/docs/recipes/using-external-config-file.md @@ -1,3 +1,10 @@ + + # Using external config file Beneficial because it's keeping tasks DRY and config.json can be used by another task runner, like `grunt`. diff --git a/docs/recipes/using-multiple-sources-in-one-task.md b/docs/recipes/using-multiple-sources-in-one-task.md index 39e3e3b23..6e5622ec4 100644 --- a/docs/recipes/using-multiple-sources-in-one-task.md +++ b/docs/recipes/using-multiple-sources-in-one-task.md @@ -1,3 +1,10 @@ + + # Using multiple sources in one task ```js diff --git a/docs/why-use-pump/README.md b/docs/why-use-pump/README.md index de6ebc85f..c4810725b 100644 --- a/docs/why-use-pump/README.md +++ b/docs/why-use-pump/README.md @@ -1,3 +1,10 @@ + + # Why Use Pump? When using `pipe` from the Node.js streams, errors are not propagated forward diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md index bcd708356..d876250cd 100644 --- a/docs/writing-a-plugin/README.md +++ b/docs/writing-a-plugin/README.md @@ -1,3 +1,10 @@ + + # Writing a plugin If you plan to create your own Gulp plugin, you will save time by reading the full documentation. diff --git a/docs/writing-a-plugin/dealing-with-streams.md b/docs/writing-a-plugin/dealing-with-streams.md index fd518bd66..d1ad36a87 100644 --- a/docs/writing-a-plugin/dealing-with-streams.md +++ b/docs/writing-a-plugin/dealing-with-streams.md @@ -1,3 +1,10 @@ + + # Dealing with streams > It is highly recommended to write plugins supporting streams. Here is some information on creating a gulp plugin that supports streams. @@ -75,4 +82,3 @@ gulp.src('files/**/*.js', { buffer: false }) ## Some plugins using streams * [gulp-svgicons2svgfont](https://github.com/nfroidure/gulp-svgiconstosvgfont) - diff --git a/docs/writing-a-plugin/guidelines.md b/docs/writing-a-plugin/guidelines.md index b810ad8a1..a1d626c93 100644 --- a/docs/writing-a-plugin/guidelines.md +++ b/docs/writing-a-plugin/guidelines.md @@ -1,3 +1,10 @@ + + # Guidelines > While these guidelines are totally optional, we **HIGHLY** recommend that everyone follows them. Nobody wants to use a bad plugin. These guidelines will actually help make your life easier by giving you assurance that your plugin fits well within gulp. diff --git a/docs/writing-a-plugin/recommended-modules.md b/docs/writing-a-plugin/recommended-modules.md index 0b49b39d4..a2faa94ce 100644 --- a/docs/writing-a-plugin/recommended-modules.md +++ b/docs/writing-a-plugin/recommended-modules.md @@ -1,3 +1,10 @@ + + # Recommended Modules > Sticking to this curated list of recommended modules will make sure you don't violate the plugin guidelines and ensure consistency across plugins. diff --git a/docs/writing-a-plugin/testing.md b/docs/writing-a-plugin/testing.md index 487a87f95..cc9f98469 100644 --- a/docs/writing-a-plugin/testing.md +++ b/docs/writing-a-plugin/testing.md @@ -1,3 +1,10 @@ + + # Testing > Testing your plugin is the only way to ensure quality. It brings confidence to your users and makes your life easier. diff --git a/docs/writing-a-plugin/using-buffers.md b/docs/writing-a-plugin/using-buffers.md index e35e41b59..b7ea05548 100644 --- a/docs/writing-a-plugin/using-buffers.md +++ b/docs/writing-a-plugin/using-buffers.md @@ -1,3 +1,10 @@ + + # Using buffers > Here is some information on creating gulp plugin that manipulates buffers. From 6a0fa00b8cae384fddf6ec6b905766bfcaf89ece Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Wed, 25 Apr 2018 14:53:10 -0700 Subject: [PATCH 138/216] Docs: Rename "Getting Started" to "Quick Start" & update it --- docs/getting-started.md | 84 ------------------------- docs/getting-started/1-quick-start.md | 88 +++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 84 deletions(-) delete mode 100644 docs/getting-started.md create mode 100644 docs/getting-started/1-quick-start.md diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 6cfe981b4..000000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,84 +0,0 @@ - - -# Getting Started - -*If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions.* For more information, read this [Sip](https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467). - -#### Check for Node and npm -Make sure that you've installed Node and npm before attempting to install gulp. - -```sh -node --version -``` -```sh -npm --version -``` - -#### Install the `gulp` command - -```sh -npm install --global gulp-cli -``` - -#### Create a `package.json` in your project directory -If you don't have a package.json, create one. If you need help, run an `npm init` which will walk you through giving it a name, version, description, etc. - - -#### Install `gulp` in your devDependencies - -Run this command in your project directory: - -```sh -npm install --save-dev gulp@next -``` - -#### Create a `gulpfile` - -In your project directory, create a file named `gulpfile.js` in your project root with these contents: - -```js -var gulp = require('gulp'); - -gulp.task('default', defaultTask); - -function defaultTask(done) { - // place code for your default task here - done(); -} -``` - -#### Test it out - -Run the gulp command in your project directory: - -```sh -gulp -``` - -To run multiple tasks, you can use `gulp `. - -#### Result - -Voila! The default task will run and do nothing. - -```sh -Using gulpfile ~/my-project/gulpfile.js -[11:15:51] Starting 'default'... -[11:15:51] Finished 'default' after 103 μs -``` - -## .src, .watch, .dest, .parallel, .series, CLI args - How do I use these things? - -For API specific documentation, you can check out the [documentation for that](API.md). - -## Where do I go now? - -- [API Documentation](API.md) - The programming interface, defined -- [Recipes](recipes) - Specific examples from the community -- [In Depth Help](https://travismaynard.com/writing/getting-started-with-gulp) - A tutorial from the guy who wrote the book -- [Plugins](https://gulpjs.com/plugins/) - Building blocks for your gulp file diff --git a/docs/getting-started/1-quick-start.md b/docs/getting-started/1-quick-start.md new file mode 100644 index 000000000..6f13d1078 --- /dev/null +++ b/docs/getting-started/1-quick-start.md @@ -0,0 +1,88 @@ + + +# Quick Start + +If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions. For more information, read this [Sip][sip-article]. + +### Check for node, npm, and npx +```sh +node --version +``` +![Output: v8.11.1][img-node-version-command] +```sh +npm --version +``` +![Output: 5.6.0][img-npm-version-command] +```sh +npx --version +``` +![Output: 9.7.1][img-npx-version-command] + +If they are not installed, follow the instructions [here][node-install]. + +### Install the gulp command line utility +```sh +npm install --global gulp-cli +``` + + +### Create a project directory and navigate into it +```sh +npx mkdirp my-project +``` +```sh +cd my-project +``` + +### Create a package.json file in your project directory +```sh +npm init +``` + +This will guide you through giving your project a name, version, description, etc. + +### Install the gulp package in your devDependencies +```sh +npm install --save-dev gulp +``` + +### Verify your gulp versions +```sh +gulp --version +``` +![Output: CLI version 2.0.1 & Local version 4.0.0][img-gulp-version-command] + +### Create a gulpfile +Using your text editor, create a file named gulpfile.js in your project root with these contents: +```js +function defaultTask(done) { + // place code for your default task here + done(); +} + +exports.default = defaultTask +``` + +### Test it +Run the gulp command in your project directory: +```sh +gulp +``` +To run multiple tasks, you can use `gulp `. + +### Result +The default task will run and do nothing. +![Output: Starting default & Finished default][img-gulp-command] + +[sip-article]: https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467 +[node-install]: https://nodejs.org/en/ +[img-node-version-command]: https://gulpjs.com/img/docs-node-version-command.png +[img-npm-version-command]: https://gulpjs.com/img/docs-npm-version-command.png +[img-npx-version-command]: https://gulpjs.com/img/docs-npx-version-command.png +[img-gulp-version-command]: https://gulpjs.com/img/docs-gulp-version-command.png +[img-gulp-command]: https://gulpjs.com/img/docs-gulp-command.png From 21b696213603d35bebfbf9da925a0febf0e675eb Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 1 May 2018 15:18:06 -0700 Subject: [PATCH 139/216] Docs: Add "Creating Tasks" documentation --- docs/getting-started/3-creating-tasks.md | 191 +++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 docs/getting-started/3-creating-tasks.md diff --git a/docs/getting-started/3-creating-tasks.md b/docs/getting-started/3-creating-tasks.md new file mode 100644 index 000000000..e870b4951 --- /dev/null +++ b/docs/getting-started/3-creating-tasks.md @@ -0,0 +1,191 @@ + + +# Creating Tasks + +Each gulp task is an asynchronous JavaScript function - a function that accepts an error-first callback or returns a stream, promise, event emitter, child process, or observable ([more on that later][async-completion-docs]). Due to some platform limitations, synchronous tasks aren't supported, though there is a pretty nifty [alternative][using-async-await-docs]. + +## Exporting + +Tasks can be considered **public** or **private**. + +* **Public tasks** are exported from your gulpfile, which allows them to be run by the `gulp` command. +* **Private tasks** are made to be used internally, usually used as part of `series()` or `parallel()` composition. + +A private task looks and acts like any other task, but an end-user can't ever execute it independently. To register a task publicly, export it from your gulpfile. + +```js +const { series } = require('gulp'); + +// The `clean` function is not exported so it can be considered a private task. +// It can still be used within the `series()` composition. +function clean(cb) { + // body omitted + cb(); +} + +// The `build` function is exported so it is public and can be run with the `gulp` command. +// It can also be used within the `series()` composition. +function build(cb) { + // body omitted + cb(); +} + +exports.build = build; +exports.default = series(clean, build); +``` + +![ALT TEXT MISSING][img-gulp-tasks-command] + +In the past, `task()` was used to register your functions as tasks. While that API is still available, exporting should be the primary registration mechanism, except in edge cases where exports won't work. + +## Compose tasks + +Gulp provides two powerful composition methods, `series()` and `parallel()`, allowing individual tasks to be composed into larger operations. Both methods accept any number of task functions or composed operations. `series()` and `parallel()` can be nested within themselves or each other to any depth. + +To have your tasks execute in order, use the `series()` method. +```js +const { series } = require('gulp'); + +function transpile(cb) { + // body omitted + cb(); +} + +function bundle(cb) { + // body omitted + cb(); +} + +exports.build = series(transpile, bundle); +``` + +For tasks to run at maximum concurrency, combine them with the `parallel()` method. +```js +const { parallel } = require('gulp'); + +function javascript(cb) { + // body omitted + cb(); +} + +function css(cb) { + // body omitted + cb(); +} + +exports.build = parallel(javascript, css); +``` + +Tasks are composed immediately when either `series()` or `parallel()` is called. This allows variation in the composition instead of conditional behavior inside individual tasks. + +```js +const { series } = require('gulp'); + +function minify(cb) { + // body omitted + cb(); +} + + +function transpile(cb) { + // body omitted + cb(); +} + +function livereload(cb) { + // body omitted + cb(); +} + +if (process.env.NODE_ENV === 'production') { + exports.build = series(transpile, minify); +} else { + exports.build = series(transpile, livereload); +} +``` + +`series()` and `parallel()` can be nested to any arbitrary depth. + +```js +const { series, parallel } = require('gulp'); + +function clean(cb) { + // body omitted + cb(); +} + +function cssTranspile(cb) { + // body omitted + cb(); +} + +function cssMinify(cb) { + // body omitted + cb(); +} + +function jsTranspile(cb) { + // body omitted + cb(); +} + +function jsBundle(cb) { + // body omitted + cb(); +} + +function jsMinify(cb) { + // body omitted + cb(); +} + +function publish(cb) { + // body omitted + cb(); +} + +exports.build = series( + clean, + parallel( + cssTranspile, + series(jsTranspile, jsBundle) + ), + parallel(cssMinify, jsMinify), + publish +); +``` + +When a composed operation is run, each task will be executed every time it was referenced. For example, a `clean` task referenced before two different tasks would be run twice and lead to undesired results. Tasks can be wrapped with the [async-once][async-once] module if this **(not recommended)** pattern is needed. + +```js +// This pattern is NOT recommended but some edge cases might require it. +const { series } = require('gulp'); +const once = require('async-once'); + +const clean = once(function(cb) { + // body omitted + cb(); +}); + +const css = series(clean, function(cb) { + // body omitted + cb(); +}); + +const javascript = series(clean, function(cb) { + // body omitted + cb(); +}) + +exports.build = series(css, javascript); +``` + +[async-completion-docs]: 4-async-completion.md +[using-async-await-docs]: 4-async-completion.md#using-asyncawait +[img-gulp-tasks-command]: https://gulpjs.com/img/docs-gulp-tasks-command.png +[async-once]: https://github.com/gulpjs/async-once From 31adf075f07d85c238bbf1d025bc9f74a74b4f24 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Mon, 21 May 2018 10:46:09 -0700 Subject: [PATCH 140/216] Docs: Add "JavaScript and Gulpfiles" documentation --- .../2-javascript-and-gulpfiles.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 docs/getting-started/2-javascript-and-gulpfiles.md diff --git a/docs/getting-started/2-javascript-and-gulpfiles.md b/docs/getting-started/2-javascript-and-gulpfiles.md new file mode 100644 index 000000000..0c3b68234 --- /dev/null +++ b/docs/getting-started/2-javascript-and-gulpfiles.md @@ -0,0 +1,36 @@ + + +# JavaScript and Gulpfiles + +Gulp allows you to use existing JavaScript knowledge to write gulpfiles or to use your experience with gulpfiles to write plain JavaScript. Although a few utilities are provided to simplify working with the filesystem and command line, everything else you write is pure JavaScript. + +## Gulpfile explained + +A gulpfile is a file in your project directory titled `gulpfile.js` (or capitalized as `Gulpfile.js`, like Makefile), that automatically loads when you run the `gulp` command. Within this file, you'll often see gulp APIs, like `src()`, `dest()`, `series()`, or `parallel()` but any vanilla JavaScript or Node modules can be used. Any exported functions will be registered into gulp's task system. + +## Transpilation + +You can write a gulpfile using a language that requires transpilation, like TypeScript or Babel, by changing the extension on your `gulpfile.js` to indicate the language and install the matching transpiler module. + +* For TypeScript, rename to `gulpfile.ts` and install the [ts-node][ts-node-module] module. +* For Babel, rename to `gulpfile.babel.js` and install the [@babel/register][babel-register-module] module. + +For a more advanced dive into this topic and the full list of supported extensions, see our [gulpfile transpilation][gulpfile-transpilation-advanced] documentation. + +## Splitting a gulpfile + +Many users start by adding all logic to a gulpfile. If it ever grows too big, it can be refactored into separate files. + +Each task can be split into its own file, then imported into your gulpfile for composition. Not only does this keep things organized, but it allows you to test each task independently or vary composition based on conditions. + +Node's module resolution allows you to replace your `gulpfile.js` with a directory called `gulpfile` that contains an `index.js` file which is treated as a `gulpfile.js`. This directory could then contain your individual modules for tasks. + + +[gulpfile-transpilation-advanced]: LINK_NEEDED +[ts-node-module]: https://www.npmjs.com/package/ts-node +[babel-register-module]: https://www.npmjs.com/package/@babel/register From 50fafc6431c54f4a83dbcb2895ad442dd9341b77 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Mon, 21 May 2018 11:55:50 -0700 Subject: [PATCH 141/216] Docs: Add "Working with Files" documentation --- docs/getting-started/5-working-with-files.md | 98 ++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 docs/getting-started/5-working-with-files.md diff --git a/docs/getting-started/5-working-with-files.md b/docs/getting-started/5-working-with-files.md new file mode 100644 index 000000000..fb5205867 --- /dev/null +++ b/docs/getting-started/5-working-with-files.md @@ -0,0 +1,98 @@ + + +# Working with Files + +The `src()` and `dest()` methods are exposed by gulp to interact with files on your computer. + +`src()` is given a [glob][explaining-globs-docs] to read from the file system and produces a [Node stream][node-streams-docs]. It locates all matching files and reads them into memory to pass through the stream. + +The stream produced by `src()` should be returned from a task to signal async completion, as mentioned in [Creating Tasks][creating-tasks-docs]. + +```js +const { src, dest } = require('gulp'); + +exports.default = function() { + return src('src/*.js') + .pipe(dest('output/')); +} +``` + +The main API of a stream is the `.pipe()` method for chaining Transform or Writable streams. + +```js +const { src, dest } = require('gulp'); +const babel = require('gulp-babel'); + +exports.default = function() { + return src('src/*.js') + .pipe(babel()) + .pipe(dest('output/')); +} +``` + +`dest()` is given an output directory string which is generally used as a terminator stream. When it receives a file passed through the pipeline, it writes the contents and other details out to the filesystem at a given directory. The `symlink()` method is also available and operates like `dest()`, but creates links instead of files (see [`symlink()`][symlink-api-docs] for details). + +Most often plugins will be placed between `src()` and `dest()` using the `.pipe()` method and will transform the files within the stream. + +## Adding files to the stream + +`src()` can also be placed in the middle of a pipeline to add files to the stream based on the given globs. The additional files will only be available to transformations later in the stream. If [globs overlap][overlapping-globs-docs], the files will be added again. + +This can be useful for transpiling some files before adding plain JavaScript files to the pipeline and uglifying everything. + +```js +const { src, dest } = require('gulp'); +const babel = require('gulp-babel'); +const uglify = require('gulp-uglify'); + +exports.default = function() { + return src('src/*.js') + .pipe(babel()) + .pipe(src('vendor/*.js')) + .pipe(uglify()) + .pipe(dest('output/')); +} +``` + +## Output in phases + +`dest()` can be used in the middle of a pipeline to write intermediate states to the filesystem. When a file is received, the current state is written out to the filesystem, the path is updated to represent the new location of the output file, then that file is passed down the pipeline. + +This feature can be useful to create an unminified and minified file with the same pipeline. + +```js +const { src, dest } = require('gulp'); +const babel = require('gulp-babel'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); + +exports.default = function() { + return src('src/*.js') + .pipe(babel()) + .pipe(src('vendor/*.js')) + .pipe(dest('output/')) + .pipe(uglify()) + .pipe(rename({ extname: '.min.js' })) + .pipe(dest('output/')); +} +``` + +## Modes: streaming, buffered, and empty + +`src()` can operate in three modes: buffering, streaming, and empty. These are configured with the `buffer` and `read` [options][src-options-api-docs] on `src()`. + +* Buffering mode is the default and loads the file contents into memory. Plugins usually operate in buffering mode and many don't support streaming mode. +* Streaming mode exists mainly to operate on large files that can't fit in memory, like giant images or movies. The contents are streamed from the filesystem in small chunks instead of loaded all at once. If you need to use streaming mode, look for a plugin that supports it or write your own. +* Empty mode contains no contents and is useful when only working with file metadata. + +[explaining-globs-docs]: 6-explaining-globs.md +[creating-tasks-docs]: 3-creating-tasks.md +[overlapping-globs-docs]: 6-explaining-globs.md#overlapping-globs +[node-streams-docs]: https://nodejs.org/api/stream.html +[symlink-api-docs]: LINK_NEEDED +[src-options-api-docs]: LINK_NEEDED From ad8b568878b894712801ed46fa8262b13091152c Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Wed, 23 May 2018 14:39:51 -0700 Subject: [PATCH 142/216] Docs: Add "Async Completion" documentation --- docs/getting-started/4-async-completion.md | 144 +++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/getting-started/4-async-completion.md diff --git a/docs/getting-started/4-async-completion.md b/docs/getting-started/4-async-completion.md new file mode 100644 index 000000000..ac8165246 --- /dev/null +++ b/docs/getting-started/4-async-completion.md @@ -0,0 +1,144 @@ + + +# Async Completion + +Node libraries handle asynchronicity in a variety of ways. The most common pattern is [error-first callbacks][node-api-error-first-callbacks], but you might also encounter [streams][stream-docs], [promises][promise-docs], [event emitters][event-emitter-docs], [child processes][child-process-docs], or [observables][observable-docs]. Gulp tasks normalize all these types of asynchronicity. + +## Signal task completion + +When a stream, promise, event emitter, child process, or observable is returned from a task, the success or error informs gulp whether to continue or end. If a task errors, gulp will end immediately and show that error. + +When composing tasks with `series()`, an error will end the composition and no further tasks will be executed. When composing tasks with `parallel()`, an error will end the composition but the other parallel tasks may or may not complete. + +### Returning a stream + +```js +const { src, dest } = require('gulp'); + +function streamTask() { + return src('*.js') + .pipe(dest('output')); +} + +exports.default = streamTask; +``` + +### Returning a promise + +```js +function promiseTask() { + return Promise.resolve('some ignored value'); +} + +exports.default = promiseTask; +``` + +### Returning an event emitter + +```js +const { EventEmitter } = require('events'); + +function eventEmitterTask() { + const emitter = new EventEmitter(); + // Emit has to happen async otherwise gulp isn't listening yet + setTimeout(() => emitter.emit('finish'), 250); + return emitter; +} + +exports.default = eventEmitterTask; +``` + +### Returning a child process + +```js +const { exec } = require('child_process'); + +function childProcessTask() { + return exec('date'); +} + +exports.default = childProcessTask; +``` + +### Returning an observable + +```js +const { Observable } = require('rxjs'); + +function observableTask() { + return Observable.of(1, 2, 3); +} + +exports.default = observableTask; +``` + +### Using an error-first callback + +If nothing is returned from your task, you must use the error-first callback to signal completion. The callback will be passed to your task as the only argument - named `done()` in the examples below. + +```js +function callbackTask(done) { + // `done()` should be called by some async work + done(); +} + +exports.default = callbackTask; +``` + +To indicate to gulp that an error occurred in a task using an error-first callback, call it with an `Error` as the only argument. + +```js +function callbackError(done) { + // `done()` should be called by some async work + done(new Error('kaboom')); +} + +exports.default = callbackError; +``` + +However, you'll often pass this callback to another API instead of calling it yourself. + +```js +const fs = require('fs'); + +function passingCallback(done) { + fs.access('gulpfile.js', done); +} + +exports.default = passingCallback; +``` + +## No synchronous tasks + +Synchronous tasks are no longer supported. They often led to subtle mistakes that were hard to debug, like forgetting to return your streams from a task. + +When you see the _"Did you forget to signal async completion?"_ warning, none of the techniques mentioned above were used. You'll need to use the error-first callback or return a stream, promise, event emitter, child process, or observable to resolve the issue. + +## Using async/await + +When not using any of the previous options, you can define your task as an [`async` function][async-await-docs], which wraps your task in a promise. This allows you to work with promises synchronously using `await` and use other synchronous code. + +```js +const fs = require('fs'); + +async function asyncAwaitTask() { + const { version } = fs.readFileSync('package.json'); + console.log(version); + await Promise.resolve('some result'); +} + +exports.default = asyncAwaitTask; +``` + +[node-api-error-first-callbacks]: https://nodejs.org/api/errors.html#errors_error_first_callbacks +[stream-docs]: https://nodejs.org/api/stream.html#stream_stream +[promise-docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises +[event-emitter-docs]: https://nodejs.org/api/events.html#events_events +[child-process-docs]: https://nodejs.org/api/child_process.html#child_process_child_process +[observable-docs]: https://github.com/tc39/proposal-observable/blob/master/README.md +[async-await-docs]: https://developers.google.com/web/fundamentals/primers/async-functions From f8cafa05726f570ef35b08e4fb9e7de623c5d34c Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 22 May 2018 14:56:07 -0700 Subject: [PATCH 143/216] Docs: Add "Explaining Globs" documentation --- docs/getting-started/6-explaining-globs.md | 88 ++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/getting-started/6-explaining-globs.md diff --git a/docs/getting-started/6-explaining-globs.md b/docs/getting-started/6-explaining-globs.md new file mode 100644 index 000000000..de524d843 --- /dev/null +++ b/docs/getting-started/6-explaining-globs.md @@ -0,0 +1,88 @@ + + +# Explaining Globs + +A glob is a string of literal and/or wildcard characters used to match filepaths. Globbing is the act of locating files on a filesystem using one or more globs. + +The `src()` method expects a single glob string or an array of globs to determine which files your pipeline will operate on. At least one match must be found for your glob(s) otherwise `src()` will error. When an array of globs is used, they are matched in array order - especially useful for negative globs. + +## Segments and separators + +A segment is everything between separators. The separator in a glob is always the `/` character - regardless of the operating system - even in Windows where the path separator is `\\`. In a glob, `\\` is reserved as the escape character. + +Here, the * is escaped, so it is treated as a literal instead of a wildcard character. +```js +'glob_with_uncommon_\\*_character.js' +``` + +Avoid using Node's `path` methods, like `path.join`, to create globs. On Windows, it produces an invalid glob because Node uses `\\` as the separator. Also avoid the `__dirname` global, `__filename` global, or `process.cwd()` for the same reasons. + +```js +const invalidGlob = path.join(__dirname, 'src/*.js'); +``` + +## Special character: * (single-star) + +Matches any amount - including none - of characters within a single segment. Useful for globbing files within one directory. + +This glob will match files like `index.js`, but not files like `scripts/index.js` or `scripts/nested/index.js` +```js +'*.js' +``` + +## Special character: ** (double-star) + +Matches any amount - including none - of characters across segments. Useful for globbing files in nested directories. Make sure to appropriately restrict your double-star globs, to avoid matching large directories unnecessarily. + +Here, the glob is appropriately restricted to the `scripts/` directory. It will match files like `scripts/index.js`, `scripts/nested/index.js`, and `scripts/nested/twice/index.js`. + +```js +'scripts/**/*.js' +``` + +In the previous example, if `scripts/` wasn't prefixed, all dependencies in `node_modules` or other directories would also be matched. + +## Special character: ! (negative) + +Since globs are matched in array order, a negative glob must follow at least one non-negative glob in an array. The first finds a set of matches, then the negative glob removes a portion of those results. These are most performant when they only include literal characters. + +```js +['script/**/*.js', '!scripts/vendor/'] +``` + +If any non-negative globs follow a negative, nothing will be removed from the later set of matches. + +```js +['script/**/*.js', '!scripts/vendor/', 'scripts/vendor/react.js'] +``` + +Negative globs can be used as an alternative for restricting double-star globs. + +```js +['**/*.js', '!node_modules/'] +``` + +In the previous example, if the negative glob was `!node_modules/**/*.js`, every match would have to be compared against the negative glob, which would be extremely slow. + +## Overlapping globs + +Two or more globs that (un)intentionally match the same file are considered overlapping. When overlapping globs are used within a single `src()`, gulp does its best to remove the duplicates, but doesn't attempt to deduplicate across separate `src()` calls. + +## Advanced resources + +Most of what you'll need to work with globs in gulp is covered here. If you'd like to get more in depth, here are a few resources. + +* [Micromatch Documentation][micromatch-docs] +* [node-glob's Glob Primer][glob-primer-docs] +* [Begin's Globbing Documentation][begin-globbing-docs] +* [Wikipedia's Glob Page][wikipedia-glob] + +[micromatch-docs]: https://github.com/micromatch/micromatch +[glob-primer-docs]: https://github.com/isaacs/node-glob#glob-primer +[begin-globbing-docs]: https://github.com/begin/globbing#what-is-globbing +[wikipedia-glob]: https://en.wikipedia.org/wiki/Glob_(programming) From 233c3f9aa7f5fbfe6dd4765e3334eb45fddd3f0f Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 29 May 2018 13:41:13 -0700 Subject: [PATCH 144/216] Docs: Add "Using Plugins" documentation --- docs/getting-started/7-using-plugins.md | 113 ++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 docs/getting-started/7-using-plugins.md diff --git a/docs/getting-started/7-using-plugins.md b/docs/getting-started/7-using-plugins.md new file mode 100644 index 000000000..e14a50631 --- /dev/null +++ b/docs/getting-started/7-using-plugins.md @@ -0,0 +1,113 @@ + + +# Using Plugins + +Gulp plugins are [Node Transform Streams][through2-docs] that encapsulate common behavior to transform files in a pipeline - often placed between `src()` and `dest()` using the `.pipe()` method. They can change the filename, metadata, or contents of every file that passes through the stream. + +Plugins from npm - using the "gulpplugin" and "gulpfriendly" keywords - can be browsed and searched on the [plugin search page][gulp-plugin-site]. + +Each plugin should only do a small amount of work, so you can connect them like building blocks. You may need to combine a bunch of them to get the desired result. + +```js +const { src, dest } = require('gulp'); +const uglify = require('gulp-uglify'); +const rename = require('gulp-rename'); + +exports.default = function() { + return src('src/*.js') + // The gulp-uglify plugin won't update the filename + .pipe(uglify()) + // So use gulp-rename to change the extension + .pipe(rename({ extname: '.min.js' })) + .pipe(dest('output/')); +} +``` + +## Do you need a plugin? + +Not everything in gulp should use plugins. They are a quick way to get started, but many operations are improved by using a module or library instead. + +```js +const { rollup } = require('rollup'); + +// Rollup's promise API works great in an `async` task +exports.default = async function() { + const bundle = await rollup.rollup({ + input: 'src/index.js' + }); + + return bundle.write({ + file: 'output/bundle.js', + format: 'iife' + }); +} +``` + +Plugins should always transform files. Use a (non-plugin) Node module or library for any other operations. + +```js +const del = require('delete'); + +exports.default = function(cb) { + // Use the `delete` module directly, instead of using gulp-rimraf + del(['output/*.js'], cb); +} +``` + +## Conditional plugins + +Since plugin operations shouldn't be file-type-aware, you may need a plugin like [gulp-if][gulp-if-package] to transform subsets of files. + +```js +const { src, dest } = require('gulp'); +const gulpif = require('gulp-if'); +const uglify = require('gulp-uglify'); + +function isJavaScript(file) { + // Check if file extension is '.js' + return file.extname === '.js'; +} + +exports.default = function() { + // Include JavaScript and CSS files in a single pipeline + return src(['src/*.js', 'src/*.css']) + // Only apply gulp-uglify plugin to JavaScript files + .pipe(gulpif(isJavaScript, uglify())) + .pipe(dest('output/')); +} +``` + +## Inline plugins + +Inline plugins are one-off Transform Streams you define inside your gulpfile by writing the desired behavior. + +There are two situations where creating an inline plugin is helpful: +* Instead of creating and maintaining your own plugin. +* Instead of forking a plugin that exists to add a feature you want. + +```js +const { src, dest } = require('gulp'); +const uglify = require('uglify-js'); +const through2 = require('through2'); + +exports.default = function() { + return src('src/*.js') + // Instead of using gulp-uglify, you can create an inline plugin + .pipe(through2.obj(function(file, _, cb) { + if (file.isBuffer()) { + const code = uglify.minify(file.contents.toString()) + file.contents = Buffer.from(code) + } + })) + .pipe(dest('output/')); +} +``` + +[gulp-plugin-site]: https://gulpjs.com/plugins/ +[through2-docs]: https://github.com/rvagg/through2 +[gulp-if-package]: https://www.npmjs.com/package/gulp-if From f3f2d9f9b624983fc4985a5ab5b45542e08bcf83 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 31 May 2018 10:46:05 -0700 Subject: [PATCH 145/216] Docs: Add "Watching Files" documentation --- docs/getting-started/8-watching-files.md | 122 +++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 docs/getting-started/8-watching-files.md diff --git a/docs/getting-started/8-watching-files.md b/docs/getting-started/8-watching-files.md new file mode 100644 index 000000000..4ed63d2f2 --- /dev/null +++ b/docs/getting-started/8-watching-files.md @@ -0,0 +1,122 @@ + + +# Watching Files + +The `watch()` API connects [globs][globs-docs] to [tasks][creating-tasks-docs] using a file system watcher. It watches for changes to files that match the globs and executes the task when a change occurs. If the task doesn't signal [Async Completion][async-completion-doc], it will never be run a second time. + +This API provides built-in delay and queueing based on most-common-use defaults. + +```js +const { watch, series } = require('gulp'); + +function clean(cb) { + // Body omitted + cb(); +} + +function javascript(cb) { + // Body omitted + cb(); +} + +function css(cb) { + // Body omitted + cb(); +} + +// You can use a single task +watch('src/*.css', css); +// Or a composed task +watch('src/*.js', series(clean, javascript)); +``` + +## Warning: Avoid synchronous + +A watcher's task cannot be synchronous, like tasks registered into the task system. If you pass a sync task, the completion can't be determined and the task won't run again - it is assumed to still be running. + +There is no error or warning message provided because the file watcher keeps your Node process running. Since the process doesn't exit, it cannot be determined whether the task is done or just taking a really, really long time to run. + +## Watched events + +By default, the watcher executes tasks whenever a file is created, changed, or deleted. +If you need to use different events, you can use the `events` option when calling `watch()`. The available events are `'add'`, `'addDir'`, `'change'`, `'unlink'`, `'unlinkDir'`, `'ready'`, `'error'`. Additionally `'all'` is available, which represents all events other than `'ready'` and `'error'`. + +```js +const { watch } = require('gulp'); + +// All events will be watched +watch('src/*.js', { events: 'all' }, function(cb) { + // Body omitted + cb(); +}); +``` + +## Initial execution + +Upon calling `watch()`, the tasks won't be executed, instead they'll wait for the first file change. + +To execute tasks before the first file change, set the `ignoreInitial` option to `false`. + +```js +const { watch } = require('gulp'); + +// The task will be executed upon startup +watch('src/*.js', { ignoreInitial: false }, function(cb) { + // Body omitted + cb(); +}); +``` + +## Queueing + +Each `watch()` guarantees that its currently running task won't execute again concurrently. When a file change is made while a watcher task is running, another execution will queue up to run when the task finishes. Only one run can be queued up at a time. + +To disable queueing, set the `queue` option to `false`. + +```js +const { watch } = require('gulp'); + +// The task will be run (concurrently) for every change made +watch('src/*.js', { queue: false }, function(cb) { + // Body omitted + cb(); +}); +``` + +## Delay + +Upon file change, a watcher task won't run until a 200ms delay has elapsed. This is to avoid starting a task too early when many files are being changed at once - like find-and-replace. + +To adjust the delay duration, set the `delay` option to a positive integer. + +```js +const { watch } = require('gulp'); + +// The task won't be run until 500ms have elapsed since the first change +watch('src/*.js', { delay: 500 }, function(cb) { + // Body omitted + cb(); +}); +``` + +## Using the watcher instance + +You likely won't use this feature, but if you need full control over changed files - like access to paths or metadata - use the [chokidar][chokidar-module-package] instance returned from `watch()`. + +__Be careful:__ The returned chokidar instance doesn't have queueing, delay, or async completion features. + +## Optional dependency + +Gulp has an optional dependency called [fsevents][fsevents-package], which is a Mac-specific file watcher. If you see an installation warning for fsevents - _"npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents"_ - it is not an issue. +If fsevents installation is skipped, a fallback watcher will be used and any errors occurring in your gulpfile aren't related to this warning. + +[globs-docs]: 6-explaining-globs.md +[creating-tasks-docs]: 3-creating-tasks.md +[async-completion-doc]: 4-async-completion.md +[chokidar-module-package]: https://www.npmjs.com/package/chokidar +[fsevents-package]: https://www.npmjs.com/package/fsevents From a43caf214ebe49397d08b57caffa636268039956 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 31 May 2018 13:43:37 -0700 Subject: [PATCH 146/216] Docs: Add Table of Contents to "Getting Started" directory --- docs/getting-started/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/getting-started/README.md diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md new file mode 100644 index 000000000..fe599a902 --- /dev/null +++ b/docs/getting-started/README.md @@ -0,0 +1,10 @@ +# Getting Started + +1. [Quick Start](1-quick-start.md) +2. [JavaScript and Gulpfiles](2-javascript-and-gulpfiles.md) +3. [Creating Tasks](3-creating-tasks.md) +4. [Async Completion](4-async-completion.md) +5. [Working with Files](5-working-with-files.md) +6. [Explaining Globs](6-explaining-globs.md) +7. [Using Plugins](7-using-plugins.md) +8. [Watching Files](8-watching-files.md) From 9f4a2e96506dec1d85804de8884678e72ffc5aa0 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 31 May 2018 13:57:27 -0700 Subject: [PATCH 147/216] Fix: Temporary workaround for facebook/Docusaurus#257 --- docs/getting-started/3-creating-tasks.md | 2 +- docs/getting-started/5-working-with-files.md | 6 +++--- docs/getting-started/8-watching-files.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/getting-started/3-creating-tasks.md b/docs/getting-started/3-creating-tasks.md index e870b4951..e14942ecf 100644 --- a/docs/getting-started/3-creating-tasks.md +++ b/docs/getting-started/3-creating-tasks.md @@ -7,7 +7,7 @@ sidebar_label: Creating Tasks # Creating Tasks -Each gulp task is an asynchronous JavaScript function - a function that accepts an error-first callback or returns a stream, promise, event emitter, child process, or observable ([more on that later][async-completion-docs]). Due to some platform limitations, synchronous tasks aren't supported, though there is a pretty nifty [alternative][using-async-await-docs]. +Each gulp task is an asynchronous JavaScript function - a function that accepts an error-first callback or returns a stream, promise, event emitter, child process, or observable ([more on that later](4-async-completion.md)). Due to some platform limitations, synchronous tasks aren't supported, though there is a pretty nifty [alternative](4-async-completion.md#using-asyncawait). ## Exporting diff --git a/docs/getting-started/5-working-with-files.md b/docs/getting-started/5-working-with-files.md index fb5205867..38bcd581b 100644 --- a/docs/getting-started/5-working-with-files.md +++ b/docs/getting-started/5-working-with-files.md @@ -9,9 +9,9 @@ sidebar_label: Working with Files The `src()` and `dest()` methods are exposed by gulp to interact with files on your computer. -`src()` is given a [glob][explaining-globs-docs] to read from the file system and produces a [Node stream][node-streams-docs]. It locates all matching files and reads them into memory to pass through the stream. +`src()` is given a [glob](6-explaining-globs.md) to read from the file system and produces a [Node stream][node-streams-docs]. It locates all matching files and reads them into memory to pass through the stream. -The stream produced by `src()` should be returned from a task to signal async completion, as mentioned in [Creating Tasks][creating-tasks-docs]. +The stream produced by `src()` should be returned from a task to signal async completion, as mentioned in [Creating Tasks](3-creating-tasks.md). ```js const { src, dest } = require('gulp'); @@ -41,7 +41,7 @@ Most often plugins will be placed between `src()` and `dest()` using the `.pipe( ## Adding files to the stream -`src()` can also be placed in the middle of a pipeline to add files to the stream based on the given globs. The additional files will only be available to transformations later in the stream. If [globs overlap][overlapping-globs-docs], the files will be added again. +`src()` can also be placed in the middle of a pipeline to add files to the stream based on the given globs. The additional files will only be available to transformations later in the stream. If [globs overlap](6-explaining-globs.md#overlapping-globs), the files will be added again. This can be useful for transpiling some files before adding plain JavaScript files to the pipeline and uglifying everything. diff --git a/docs/getting-started/8-watching-files.md b/docs/getting-started/8-watching-files.md index 4ed63d2f2..550b46e3e 100644 --- a/docs/getting-started/8-watching-files.md +++ b/docs/getting-started/8-watching-files.md @@ -7,7 +7,7 @@ sidebar_label: Watching Files # Watching Files -The `watch()` API connects [globs][globs-docs] to [tasks][creating-tasks-docs] using a file system watcher. It watches for changes to files that match the globs and executes the task when a change occurs. If the task doesn't signal [Async Completion][async-completion-doc], it will never be run a second time. +The `watch()` API connects [globs](6-explaining-globs.md) to [tasks](3-creating-tasks.md) using a file system watcher. It watches for changes to files that match the globs and executes the task when a change occurs. If the task doesn't signal [Async Completion](4-async-completion.md), it will never be run a second time. This API provides built-in delay and queueing based on most-common-use defaults. From 84b023460201ba30805e5cbc684aac93cb48965f Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 29 Jul 2018 14:44:27 -0700 Subject: [PATCH 148/216] Docs: Improve & fix parts of Getting Started --- docs/getting-started/1-quick-start.md | 8 +++- docs/getting-started/3-creating-tasks.md | 40 ++++++++++++++++---- docs/getting-started/4-async-completion.md | 20 +++++----- docs/getting-started/5-working-with-files.md | 6 +-- docs/getting-started/7-using-plugins.md | 1 + docs/getting-started/8-watching-files.md | 14 +++---- 6 files changed, 59 insertions(+), 30 deletions(-) diff --git a/docs/getting-started/1-quick-start.md b/docs/getting-started/1-quick-start.md index 6f13d1078..b724e0481 100644 --- a/docs/getting-started/1-quick-start.md +++ b/docs/getting-started/1-quick-start.md @@ -52,17 +52,21 @@ npm install --save-dev gulp ``` ### Verify your gulp versions + ```sh gulp --version ``` + +Ensure the output matches the screenshot below or you might need to restart the steps in this guide. + ![Output: CLI version 2.0.1 & Local version 4.0.0][img-gulp-version-command] ### Create a gulpfile Using your text editor, create a file named gulpfile.js in your project root with these contents: ```js -function defaultTask(done) { +function defaultTask(cb) { // place code for your default task here - done(); + cb(); } exports.default = defaultTask diff --git a/docs/getting-started/3-creating-tasks.md b/docs/getting-started/3-creating-tasks.md index e14942ecf..48ea9ed64 100644 --- a/docs/getting-started/3-creating-tasks.md +++ b/docs/getting-started/3-creating-tasks.md @@ -160,17 +160,18 @@ exports.build = series( ); ``` -When a composed operation is run, each task will be executed every time it was referenced. For example, a `clean` task referenced before two different tasks would be run twice and lead to undesired results. Tasks can be wrapped with the [async-once][async-once] module if this **(not recommended)** pattern is needed. +When a composed operation is run, each task will be executed every time it was referenced. For example, a `clean` task referenced before two different tasks would be run twice and lead to undesired results. Instead, refactor the `clean` task to be specified in the final composition. + +If you have code like this: ```js -// This pattern is NOT recommended but some edge cases might require it. -const { series } = require('gulp'); -const once = require('async-once'); +// This is INCORRECT +const { series, parallel } = require('gulp'); -const clean = once(function(cb) { +const clean = function(cb) { // body omitted cb(); -}); +}; const css = series(clean, function(cb) { // body omitted @@ -180,9 +181,32 @@ const css = series(clean, function(cb) { const javascript = series(clean, function(cb) { // body omitted cb(); -}) +}); + +exports.build = parallel(css, javascript); +``` + +Migrate to this: + +```js +const { series, parallel } = require('gulp'); + +function clean(cb) { + // body omitted + cb(); +} + +function css(cb) { + // body omitted + cb(); +} + +function javascript(cb) { + // body omitted + cb(); +} -exports.build = series(css, javascript); +exports.build = series(clean, parallel(css, javascript)); ``` [async-completion-docs]: 4-async-completion.md diff --git a/docs/getting-started/4-async-completion.md b/docs/getting-started/4-async-completion.md index ac8165246..908fcc06b 100644 --- a/docs/getting-started/4-async-completion.md +++ b/docs/getting-started/4-async-completion.md @@ -32,7 +32,7 @@ exports.default = streamTask; ```js function promiseTask() { - return Promise.resolve('some ignored value'); + return Promise.resolve('the value is ignored'); } exports.default = promiseTask; @@ -79,12 +79,12 @@ exports.default = observableTask; ### Using an error-first callback -If nothing is returned from your task, you must use the error-first callback to signal completion. The callback will be passed to your task as the only argument - named `done()` in the examples below. +If nothing is returned from your task, you must use the error-first callback to signal completion. The callback will be passed to your task as the only argument - named `cb()` in the examples below. ```js -function callbackTask(done) { - // `done()` should be called by some async work - done(); +function callbackTask(cb) { + // `cb()` should be called by some async work + cb(); } exports.default = callbackTask; @@ -93,9 +93,9 @@ exports.default = callbackTask; To indicate to gulp that an error occurred in a task using an error-first callback, call it with an `Error` as the only argument. ```js -function callbackError(done) { - // `done()` should be called by some async work - done(new Error('kaboom')); +function callbackError(cb) { + // `cb()` should be called by some async work + cb(new Error('kaboom')); } exports.default = callbackError; @@ -106,8 +106,8 @@ However, you'll often pass this callback to another API instead of calling it yo ```js const fs = require('fs'); -function passingCallback(done) { - fs.access('gulpfile.js', done); +function passingCallback(cb) { + fs.access('gulpfile.js', cb); } exports.default = passingCallback; diff --git a/docs/getting-started/5-working-with-files.md b/docs/getting-started/5-working-with-files.md index 38bcd581b..52490572f 100644 --- a/docs/getting-started/5-working-with-files.md +++ b/docs/getting-started/5-working-with-files.md @@ -35,7 +35,7 @@ exports.default = function() { } ``` -`dest()` is given an output directory string which is generally used as a terminator stream. When it receives a file passed through the pipeline, it writes the contents and other details out to the filesystem at a given directory. The `symlink()` method is also available and operates like `dest()`, but creates links instead of files (see [`symlink()`][symlink-api-docs] for details). +`dest()` is given an output directory string and also produces a [Node stream][node-streams-docs] which is generally used as a terminator stream. When it receives a file passed through the pipeline, it writes the contents and other details out to the filesystem at a given directory. The `symlink()` method is also available and operates like `dest()`, but creates links instead of files (see [`symlink()`][symlink-api-docs] for details). Most often plugins will be placed between `src()` and `dest()` using the `.pipe()` method and will transform the files within the stream. @@ -61,9 +61,9 @@ exports.default = function() { ## Output in phases -`dest()` can be used in the middle of a pipeline to write intermediate states to the filesystem. When a file is received, the current state is written out to the filesystem, the path is updated to represent the new location of the output file, then that file is passed down the pipeline. +`dest()` can be used in the middle of a pipeline to write intermediate states to the filesystem. When a file is received, the current state is written out to the filesystem, the path is updated to represent the new location of the output file, then that file continues down the pipeline. -This feature can be useful to create an unminified and minified file with the same pipeline. +This feature can be useful to create unminified and minified files with the same pipeline. ```js const { src, dest } = require('gulp'); diff --git a/docs/getting-started/7-using-plugins.md b/docs/getting-started/7-using-plugins.md index e14a50631..fda305530 100644 --- a/docs/getting-started/7-using-plugins.md +++ b/docs/getting-started/7-using-plugins.md @@ -103,6 +103,7 @@ exports.default = function() { const code = uglify.minify(file.contents.toString()) file.contents = Buffer.from(code) } + cb(null, file); })) .pipe(dest('output/')); } diff --git a/docs/getting-started/8-watching-files.md b/docs/getting-started/8-watching-files.md index 550b46e3e..7c9199edc 100644 --- a/docs/getting-started/8-watching-files.md +++ b/docs/getting-started/8-watching-files.md @@ -15,17 +15,17 @@ This API provides built-in delay and queueing based on most-common-use defaults. const { watch, series } = require('gulp'); function clean(cb) { - // Body omitted + // body omitted cb(); } function javascript(cb) { - // Body omitted + // body omitted cb(); } function css(cb) { - // Body omitted + // body omitted cb(); } @@ -51,7 +51,7 @@ const { watch } = require('gulp'); // All events will be watched watch('src/*.js', { events: 'all' }, function(cb) { - // Body omitted + // body omitted cb(); }); ``` @@ -67,7 +67,7 @@ const { watch } = require('gulp'); // The task will be executed upon startup watch('src/*.js', { ignoreInitial: false }, function(cb) { - // Body omitted + // body omitted cb(); }); ``` @@ -83,7 +83,7 @@ const { watch } = require('gulp'); // The task will be run (concurrently) for every change made watch('src/*.js', { queue: false }, function(cb) { - // Body omitted + // body omitted cb(); }); ``` @@ -99,7 +99,7 @@ const { watch } = require('gulp'); // The task won't be run until 500ms have elapsed since the first change watch('src/*.js', { delay: 500 }, function(cb) { - // Body omitted + // body omitted cb(); }); ``` From 2bd75d035a2630e3de089cd9b1ec355461d00f6e Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 29 Jul 2018 15:01:10 -0700 Subject: [PATCH 149/216] Docs: Create and link-to a "docs missing" page for LINK_NEEDED references --- docs/documentation-missing.md | 5 +++++ docs/getting-started/2-javascript-and-gulpfiles.md | 2 +- docs/getting-started/5-working-with-files.md | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 docs/documentation-missing.md diff --git a/docs/documentation-missing.md b/docs/documentation-missing.md new file mode 100644 index 000000000..1c47aa2c9 --- /dev/null +++ b/docs/documentation-missing.md @@ -0,0 +1,5 @@ +## Excuse our dust! + +We're in the process of rewriting **all** our documentation and some of the links we've added to completed docs haven't been written yet. You've likely clicked on one of those to end up here. We're sorry about that but please check back later on the topic you're interested in. + +-The Gulp Team diff --git a/docs/getting-started/2-javascript-and-gulpfiles.md b/docs/getting-started/2-javascript-and-gulpfiles.md index 0c3b68234..b82193f33 100644 --- a/docs/getting-started/2-javascript-and-gulpfiles.md +++ b/docs/getting-started/2-javascript-and-gulpfiles.md @@ -31,6 +31,6 @@ Each task can be split into its own file, then imported into your gulpfile for c Node's module resolution allows you to replace your `gulpfile.js` with a directory called `gulpfile` that contains an `index.js` file which is treated as a `gulpfile.js`. This directory could then contain your individual modules for tasks. -[gulpfile-transpilation-advanced]: LINK_NEEDED +[gulpfile-transpilation-advanced]: ../documentation-missing.md [ts-node-module]: https://www.npmjs.com/package/ts-node [babel-register-module]: https://www.npmjs.com/package/@babel/register diff --git a/docs/getting-started/5-working-with-files.md b/docs/getting-started/5-working-with-files.md index 52490572f..2e87d2e5b 100644 --- a/docs/getting-started/5-working-with-files.md +++ b/docs/getting-started/5-working-with-files.md @@ -94,5 +94,5 @@ exports.default = function() { [creating-tasks-docs]: 3-creating-tasks.md [overlapping-globs-docs]: 6-explaining-globs.md#overlapping-globs [node-streams-docs]: https://nodejs.org/api/stream.html -[symlink-api-docs]: LINK_NEEDED -[src-options-api-docs]: LINK_NEEDED +[symlink-api-docs]: ../documentation-missing.md +[src-options-api-docs]: ../documentation-missing.md From 53e9727653ce4243e19c480fd382c18b196de8f8 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 29 Jul 2018 14:54:44 -0700 Subject: [PATCH 150/216] Docs: Redirect users to new Getting Started guides --- docs/README.md | 2 +- docs/getting-started.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 docs/getting-started.md diff --git a/docs/README.md b/docs/README.md index d1b9f8c28..f90cabc91 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,7 +7,7 @@ sidebar_label: Docs # gulp documentation -* [Getting Started](getting-started.md) - Get started with gulp +* [Getting Started](getting-started/) - Get started with gulp * [API documentation](API.md) - The programming interface, defined * [CLI documentation](CLI.md) - Learn how to call tasks and use compilers * [Writing a Plugin](writing-a-plugin/) - The essentials of writing a gulp plugin diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 000000000..c3ec6e468 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,5 @@ +## This documentation has moved! + +You can find the new documentation in our [Quick Start](getting-started/1-quick-start.md) guide. + +While you are there, check out our expanded [Getting Started](getting-started/) documentation. From 10272369e517dbb31a9ab9f73242f7f5801fde5c Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 29 Jul 2018 15:37:32 -0700 Subject: [PATCH 151/216] Scaffold: Update some links and license year --- .editorconfig | 2 +- .gitignore | 4 ++-- LICENSE | 2 +- appveyor.yml | 4 ++-- docs/API.md | 4 ++-- package.json | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.editorconfig b/.editorconfig index e000b0ce0..e7b73a7ae 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# http://editorconfig.org +# https://editorconfig.org root = true [*] diff --git a/.gitignore b/.gitignore index 6f636468b..ac88dd1f0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,10 +13,10 @@ lib-cov # Coverage directory used by tools like istanbul coverage -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt -# Compiled binary addons (http://nodejs.org/api/addons.html) +# Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directory diff --git a/LICENSE b/LICENSE index 6355a4b4a..6a29df97f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2017 Blaine Bublitz , Eric Schoffstall and other contributors +Copyright (c) 2013-2018 Blaine Bublitz , Eric Schoffstall and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/appveyor.yml b/appveyor.yml index e2d9fb3ad..5020ef91b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ -# http://www.appveyor.com/docs/appveyor-yml -# http://www.appveyor.com/docs/lang/nodejs-iojs +# https://www.appveyor.com/docs/appveyor-yml +# https://www.appveyor.com/docs/lang/nodejs-iojs environment: matrix: diff --git a/docs/API.md b/docs/API.md index 5a9771bb1..33f8bc602 100644 --- a/docs/API.md +++ b/docs/API.md @@ -834,9 +834,9 @@ module.exports = new MyCompanyTasksRegistry(); [gulp-if]: https://github.com/robrich/gulp-if [node-glob documentation]: https://github.com/isaacs/node-glob#options [node-glob]: https://github.com/isaacs/node-glob -[piped]: http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options +[piped]: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options [RxJS]: https://www.npmjs.com/package/rx -[stream]: http://nodejs.org/api/stream.html +[stream]: https://nodejs.org/api/stream.html [async-done]: https://www.npmjs.com/package/async-done [undertaker]: https://github.com/gulpjs/undertaker [vinyl File instance]: https://github.com/gulpjs/vinyl diff --git a/package.json b/package.json index b50db5400..2d6db9038 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "gulp", "version": "4.0.0", "description": "The streaming build system.", - "homepage": "http://gulpjs.com", - "author": "Gulp Team (http://gulpjs.com/)", + "homepage": "https://gulpjs.com", + "author": "Gulp Team (https://gulpjs.com/)", "contributors": [ "Eric Schoffstall ", "Blaine Bublitz " From 2cecf1ed4826cb6bc5314df338b0533565a34154 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 29 Jul 2018 15:50:47 -0700 Subject: [PATCH 152/216] Docs: Temporarily reference gulp@next in Quick Start --- docs/getting-started/1-quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/1-quick-start.md b/docs/getting-started/1-quick-start.md index b724e0481..6294ecd62 100644 --- a/docs/getting-started/1-quick-start.md +++ b/docs/getting-started/1-quick-start.md @@ -48,7 +48,7 @@ This will guide you through giving your project a name, version, description, et ### Install the gulp package in your devDependencies ```sh -npm install --save-dev gulp +npm install --save-dev gulp@next ``` ### Verify your gulp versions From ad8a2f73c7d54635705255bf2e2b818a29ec0c8b Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Tue, 21 Aug 2018 16:43:33 -0700 Subject: [PATCH 153/216] Build: Remove jscs & update eslint for code formatting rules --- .jscsrc | 3 --- package.json | 8 +++----- 2 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 .jscsrc diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 703b33fc3..000000000 --- a/.jscsrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "preset": "gulp" -} diff --git a/package.json b/package.json index 2d6db9038..a4ce8bcaa 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "gulp": "./bin/gulp.js" }, "scripts": { - "lint": "eslint . && jscs index.js bin/ test/", + "lint": "eslint .", "pretest": "npm run lint", "test": "mocha --async-only", "cover": "istanbul cover _mocha --report lcovonly", @@ -36,13 +36,11 @@ "vinyl-fs": "^3.0.0" }, "devDependencies": { - "eslint": "^1.7.3", - "eslint-config-gulp": "^2.0.0", + "eslint": "^2.13.1", + "eslint-config-gulp": "^3.0.1", "expect": "^1.20.2", "istanbul": "^0.4.3", "istanbul-coveralls": "^1.0.3", - "jscs": "^2.3.5", - "jscs-preset-gulp": "^1.0.0", "mkdirp": "^0.5.1", "mocha": "^3.0.0", "rimraf": "^2.2.5" From a5eac1cff0bcb44683ba71d1e7db03c11dab8d3e Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Tue, 21 Aug 2018 16:46:44 -0700 Subject: [PATCH 154/216] Build: Add node 10 to CI matrices --- .travis.yml | 1 + appveyor.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f621cabd3..7e14b195d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ sudo: false language: node_js node_js: + - '10' - '8' - '6' - '4' diff --git a/appveyor.yml b/appveyor.yml index 5020ef91b..d8845d71e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,6 +9,7 @@ environment: - nodejs_version: "4" - nodejs_version: "6" - nodejs_version: "8" + - nodejs_version: "10" install: - ps: Install-Product node $env:nodejs_version From 49b5aca613b33c5b626ae68c03a385f25c142f55 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Tue, 21 Aug 2018 16:58:32 -0700 Subject: [PATCH 155/216] Scaffold: Add tidelift configuration --- .tidelift.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .tidelift.yml diff --git a/.tidelift.yml b/.tidelift.yml new file mode 100644 index 000000000..3540e8ba7 --- /dev/null +++ b/.tidelift.yml @@ -0,0 +1,15 @@ +ci: + platform: + NPM: + # We use an older version that doesn't use ES6+ features to support back to node 0.10 + eslint: + tests: + outdated: skip + # We use an older version that doesn't use ES6+ features to support back to node 0.10 + expect: + tests: + outdated: skip + # We use an older version that doesn't use ES6+ features to support back to node 0.10 + mocha: + tests: + outdated: skip From 921312c32d4b42e9024d79f8f9afa3997220a078 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 18 Oct 2018 16:55:08 -0700 Subject: [PATCH 156/216] Docs: Use h2 headers within Quick Start documentation (#2241) --- docs/getting-started/1-quick-start.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/getting-started/1-quick-start.md b/docs/getting-started/1-quick-start.md index 6294ecd62..c3210274f 100644 --- a/docs/getting-started/1-quick-start.md +++ b/docs/getting-started/1-quick-start.md @@ -9,7 +9,7 @@ sidebar_label: Quick Start If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions. For more information, read this [Sip][sip-article]. -### Check for node, npm, and npx +## Check for node, npm, and npx ```sh node --version ``` @@ -25,13 +25,13 @@ npx --version If they are not installed, follow the instructions [here][node-install]. -### Install the gulp command line utility +## Install the gulp command line utility ```sh npm install --global gulp-cli ``` -### Create a project directory and navigate into it +## Create a project directory and navigate into it ```sh npx mkdirp my-project ``` @@ -39,19 +39,19 @@ npx mkdirp my-project cd my-project ``` -### Create a package.json file in your project directory +## Create a package.json file in your project directory ```sh npm init ``` This will guide you through giving your project a name, version, description, etc. -### Install the gulp package in your devDependencies +## Install the gulp package in your devDependencies ```sh npm install --save-dev gulp@next ``` -### Verify your gulp versions +## Verify your gulp versions ```sh gulp --version @@ -61,7 +61,7 @@ Ensure the output matches the screenshot below or you might need to restart the ![Output: CLI version 2.0.1 & Local version 4.0.0][img-gulp-version-command] -### Create a gulpfile +## Create a gulpfile Using your text editor, create a file named gulpfile.js in your project root with these contents: ```js function defaultTask(cb) { @@ -72,14 +72,14 @@ function defaultTask(cb) { exports.default = defaultTask ``` -### Test it +## Test it Run the gulp command in your project directory: ```sh gulp ``` To run multiple tasks, you can use `gulp `. -### Result +## Result The default task will run and do nothing. ![Output: Starting default & Finished default][img-gulp-command] From 3d051d868313ee408614ae2c8ecc9b850b4b5f3d Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 18 Oct 2018 16:55:34 -0700 Subject: [PATCH 157/216] Docs: Fixed a capitalization typo in a heading (#2242) --- docs/getting-started/8-watching-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/8-watching-files.md b/docs/getting-started/8-watching-files.md index 7c9199edc..82a16b330 100644 --- a/docs/getting-started/8-watching-files.md +++ b/docs/getting-started/8-watching-files.md @@ -35,7 +35,7 @@ watch('src/*.css', css); watch('src/*.js', series(clean, javascript)); ``` -## Warning: Avoid synchronous +## Warning: avoid synchronous A watcher's task cannot be synchronous, like tasks registered into the task system. If you pass a sync task, the completion can't be determined and the task won't run again - it is assumed to still be running. From 5c079544db5ac12c8efcb6560e1581f2339aadd9 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 18 Oct 2018 17:02:03 -0700 Subject: [PATCH 158/216] Docs: Remove temporary workaround for facebook/Docusaurus#257 --- docs/getting-started/3-creating-tasks.md | 2 +- docs/getting-started/5-working-with-files.md | 6 +++--- docs/getting-started/8-watching-files.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/getting-started/3-creating-tasks.md b/docs/getting-started/3-creating-tasks.md index 48ea9ed64..5d784a5bd 100644 --- a/docs/getting-started/3-creating-tasks.md +++ b/docs/getting-started/3-creating-tasks.md @@ -7,7 +7,7 @@ sidebar_label: Creating Tasks # Creating Tasks -Each gulp task is an asynchronous JavaScript function - a function that accepts an error-first callback or returns a stream, promise, event emitter, child process, or observable ([more on that later](4-async-completion.md)). Due to some platform limitations, synchronous tasks aren't supported, though there is a pretty nifty [alternative](4-async-completion.md#using-asyncawait). +Each gulp task is an asynchronous JavaScript function - a function that accepts an error-first callback or returns a stream, promise, event emitter, child process, or observable ([more on that later][async-completion-docs]). Due to some platform limitations, synchronous tasks aren't supported, though there is a pretty nifty [alternative][using-async-await-docs]. ## Exporting diff --git a/docs/getting-started/5-working-with-files.md b/docs/getting-started/5-working-with-files.md index 2e87d2e5b..1f2e7af71 100644 --- a/docs/getting-started/5-working-with-files.md +++ b/docs/getting-started/5-working-with-files.md @@ -9,9 +9,9 @@ sidebar_label: Working with Files The `src()` and `dest()` methods are exposed by gulp to interact with files on your computer. -`src()` is given a [glob](6-explaining-globs.md) to read from the file system and produces a [Node stream][node-streams-docs]. It locates all matching files and reads them into memory to pass through the stream. +`src()` is given a [glob][explaining-globs-docs] to read from the file system and produces a [Node stream][node-streams-docs]. It locates all matching files and reads them into memory to pass through the stream. -The stream produced by `src()` should be returned from a task to signal async completion, as mentioned in [Creating Tasks](3-creating-tasks.md). +The stream produced by `src()` should be returned from a task to signal async completion, as mentioned in [Creating Tasks][creating-tasks-docs]. ```js const { src, dest } = require('gulp'); @@ -41,7 +41,7 @@ Most often plugins will be placed between `src()` and `dest()` using the `.pipe( ## Adding files to the stream -`src()` can also be placed in the middle of a pipeline to add files to the stream based on the given globs. The additional files will only be available to transformations later in the stream. If [globs overlap](6-explaining-globs.md#overlapping-globs), the files will be added again. +`src()` can also be placed in the middle of a pipeline to add files to the stream based on the given globs. The additional files will only be available to transformations later in the stream. If [globs overlap][overlapping-globs-docs], the files will be added again. This can be useful for transpiling some files before adding plain JavaScript files to the pipeline and uglifying everything. diff --git a/docs/getting-started/8-watching-files.md b/docs/getting-started/8-watching-files.md index 82a16b330..910fdbd41 100644 --- a/docs/getting-started/8-watching-files.md +++ b/docs/getting-started/8-watching-files.md @@ -7,7 +7,7 @@ sidebar_label: Watching Files # Watching Files -The `watch()` API connects [globs](6-explaining-globs.md) to [tasks](3-creating-tasks.md) using a file system watcher. It watches for changes to files that match the globs and executes the task when a change occurs. If the task doesn't signal [Async Completion](4-async-completion.md), it will never be run a second time. +The `watch()` API connects [globs][globs-docs] to [tasks][creating-tasks-docs] using a file system watcher. It watches for changes to files that match the globs and executes the task when a change occurs. If the task doesn't signal [Async Completion][async-completion-doc], it will never be run a second time. This API provides built-in delay and queueing based on most-common-use defaults. From c433c702fda288f7efd214ea348cc5b8423c67cd Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 18 Oct 2018 17:04:41 -0700 Subject: [PATCH 159/216] Docs: Replace some links in Getting Started --- docs/getting-started/3-creating-tasks.md | 4 ++-- docs/getting-started/5-working-with-files.md | 10 +++++----- docs/getting-started/8-watching-files.md | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/getting-started/3-creating-tasks.md b/docs/getting-started/3-creating-tasks.md index 5d784a5bd..a1decb572 100644 --- a/docs/getting-started/3-creating-tasks.md +++ b/docs/getting-started/3-creating-tasks.md @@ -209,7 +209,7 @@ function javascript(cb) { exports.build = series(clean, parallel(css, javascript)); ``` -[async-completion-docs]: 4-async-completion.md -[using-async-await-docs]: 4-async-completion.md#using-asyncawait +[async-completion-docs]: ../getting-started/4-async-completion.md +[using-async-await-docs]: ../getting-started/4-async-completion.md#using-asyncawait [img-gulp-tasks-command]: https://gulpjs.com/img/docs-gulp-tasks-command.png [async-once]: https://github.com/gulpjs/async-once diff --git a/docs/getting-started/5-working-with-files.md b/docs/getting-started/5-working-with-files.md index 1f2e7af71..9ac333790 100644 --- a/docs/getting-started/5-working-with-files.md +++ b/docs/getting-started/5-working-with-files.md @@ -90,9 +90,9 @@ exports.default = function() { * Streaming mode exists mainly to operate on large files that can't fit in memory, like giant images or movies. The contents are streamed from the filesystem in small chunks instead of loaded all at once. If you need to use streaming mode, look for a plugin that supports it or write your own. * Empty mode contains no contents and is useful when only working with file metadata. -[explaining-globs-docs]: 6-explaining-globs.md -[creating-tasks-docs]: 3-creating-tasks.md -[overlapping-globs-docs]: 6-explaining-globs.md#overlapping-globs +[explaining-globs-docs]: ../getting-started/6-explaining-globs.md +[creating-tasks-docs]: ../getting-started/3-creating-tasks.md +[overlapping-globs-docs]: ../getting-started/6-explaining-globs.md#overlapping-globs [node-streams-docs]: https://nodejs.org/api/stream.html -[symlink-api-docs]: ../documentation-missing.md -[src-options-api-docs]: ../documentation-missing.md +[symlink-api-docs]: ../api/symlink.md +[src-options-api-docs]: ../api/src.md#options diff --git a/docs/getting-started/8-watching-files.md b/docs/getting-started/8-watching-files.md index 910fdbd41..9b8268ebe 100644 --- a/docs/getting-started/8-watching-files.md +++ b/docs/getting-started/8-watching-files.md @@ -115,8 +115,8 @@ __Be careful:__ The returned chokidar instance doesn't have queueing, delay, or Gulp has an optional dependency called [fsevents][fsevents-package], which is a Mac-specific file watcher. If you see an installation warning for fsevents - _"npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents"_ - it is not an issue. If fsevents installation is skipped, a fallback watcher will be used and any errors occurring in your gulpfile aren't related to this warning. -[globs-docs]: 6-explaining-globs.md -[creating-tasks-docs]: 3-creating-tasks.md -[async-completion-doc]: 4-async-completion.md +[globs-docs]: ../getting-started/6-explaining-globs.md +[creating-tasks-docs]: ../getting-started/3-creating-tasks.md +[async-completion-doc]: ../getting-started/4-async-completion.md [chokidar-module-package]: https://www.npmjs.com/package/chokidar [fsevents-package]: https://www.npmjs.com/package/fsevents From af4bd514df869ee035b357c68c87437f96ed4616 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 18 Oct 2018 17:09:32 -0700 Subject: [PATCH 160/216] Docs: Fix hash link --- docs/getting-started/3-creating-tasks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/3-creating-tasks.md b/docs/getting-started/3-creating-tasks.md index a1decb572..79f644876 100644 --- a/docs/getting-started/3-creating-tasks.md +++ b/docs/getting-started/3-creating-tasks.md @@ -210,6 +210,6 @@ exports.build = series(clean, parallel(css, javascript)); ``` [async-completion-docs]: ../getting-started/4-async-completion.md -[using-async-await-docs]: ../getting-started/4-async-completion.md#using-asyncawait +[using-async-await-docs]: ../getting-started/4-async-completion.md#using-async-await [img-gulp-tasks-command]: https://gulpjs.com/img/docs-gulp-tasks-command.png [async-once]: https://github.com/gulpjs/async-once From a3b8ce130addedc01a7198942c6b1d96143d5e04 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Tue, 1 May 2018 11:01:34 -0700 Subject: [PATCH 161/216] Docs: Split API docs into separate markdown files --- docs/API.md | 845 ------------------------------------------- docs/README.md | 2 +- docs/api/README.md | 19 + docs/api/dest.md | 66 ++++ docs/api/lastRun.md | 43 +++ docs/api/parallel.md | 40 ++ docs/api/registry.md | 64 ++++ docs/api/series.md | 39 ++ docs/api/src.md | 139 +++++++ docs/api/symlink.md | 35 ++ docs/api/task.md | 194 ++++++++++ docs/api/tree.md | 155 ++++++++ docs/api/watch.md | 109 ++++++ 13 files changed, 904 insertions(+), 846 deletions(-) delete mode 100644 docs/API.md create mode 100644 docs/api/README.md create mode 100644 docs/api/dest.md create mode 100644 docs/api/lastRun.md create mode 100644 docs/api/parallel.md create mode 100644 docs/api/registry.md create mode 100644 docs/api/series.md create mode 100644 docs/api/src.md create mode 100644 docs/api/symlink.md create mode 100644 docs/api/task.md create mode 100644 docs/api/tree.md create mode 100644 docs/api/watch.md diff --git a/docs/API.md b/docs/API.md deleted file mode 100644 index 33f8bc602..000000000 --- a/docs/API.md +++ /dev/null @@ -1,845 +0,0 @@ - - -Note: these docs are for version v4.0.0 (aka `gulp@next`) If you're on gulp -v3.9.1, which is the current default `npm` release, you probably want [that -version's documentation](https://github.com/gulpjs/gulp/blob/v3.9.1/docs/API.md). - -## gulp API docs - -* [gulp.src](#gulpsrcglobs-options) - Emit files matching one or more globs -* [gulp.dest](#gulpdestpath-options) - Write files to directories -* [gulp.symlink](#gulpsymlinkfolder-options) - Write files to symlinks -* [gulp.task](#gulptaskname-fn) - Define tasks -* [gulp.lastRun](#gulplastruntaskname-timeresolution) - Get timestamp of last successful run -* [gulp.parallel](#gulpparalleltasks) - Run tasks in parallel -* [gulp.series](#gulpseriestasks) - Run tasks in series -* [gulp.watch](#gulpwatchglobs-opts-fn) - Do something when a file changes -* [gulp.tree](#gulptreeoptions) - Get the tree of tasks -* [gulp.registry](#gulpregistryregistry) - Get or set the task registry - -### gulp.src(globs[, options]) - -Emits files matching provided glob or array of globs. -Returns a [stream] of [Vinyl files] that can be [piped] to plugins. - -```javascript -gulp.src('client/templates/*.pug') - .pipe(pug()) - .pipe(minify()) - .pipe(gulp.dest('build/minified_templates')); -``` - -`glob` refers to [node-glob syntax][node-glob] or it can be a direct file path. - -#### globs -Type: `String` or `Array` - -Glob or array of globs to read. Globs use [node-glob syntax] except that negation is fully supported. - -A glob that begins with `!` excludes matching files from the glob results up to that point. For example, consider this directory structure: - - client/ - a.js - bob.js - bad.js - -The following expression matches `a.js` and `bad.js`: - - gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js']) - - -Note that globs are evaluated in order, which means this is possible: - -```js -// exclude every JS file that starts with a b except bad.js -gulp.src(['*.js', '!b*.js', 'bad.js']) -``` - -**Note:** glob symlink following behavior is opt-in and you must specify -`follow: true` in the options object that is passed to [node-glob]. - -#### options -Type: `Object` - -Options to pass to [node-glob] through [glob-stream]. - -gulp adds some additional options in addition to the -[options supported by node-glob][node-glob documentation] and [glob-stream]: - -##### options.cwd - -The working directory the folder is relative to. - -Type: `String` - -Default: `process.cwd()` - - -##### options.buffer -Type: `Boolean` - -Default: `true` - -Setting this to `false` will return `file.contents` as a stream and not -buffer files. This is useful when working with large files. - -**Note:** Plugins might not implement support for streams. - -##### options.read -Type: `Boolean` - -Default: `true` - -Setting this to `false` will return `file.contents` as null and not read -the file at all. - -##### options.base -Type: `String` - -Default: everything before a glob starts (see [glob-parent]) - -E.g., consider `somefile.js` in `client/js/somedir`: - -```js -// Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/` -gulp.src('client/js/**/*.js') - .pipe(minify()) - .pipe(gulp.dest('build')); // Writes 'build/somedir/somefile.js' - -gulp.src('client/js/**/*.js', { base: 'client' }) - .pipe(minify()) - .pipe(gulp.dest('build')); // Writes 'build/js/somedir/somefile.js' -``` - -##### options.since -Type: `Date` or `Number` - -Setting this to a Date or a time stamp will discard any file that have not been -modified since the time specified. - -##### options.passthrough -Type: `Boolean` - -Default: `false` - -If true, it will create a duplex stream which passes items through and -emits globbed files. - -##### options.allowEmpty -Type: `Boolean` - -Default: `false` - -When true, will allow singular globs to fail to match. Otherwise, globs which are only supposed to match one file (such as `./foo/bar.js`) will cause an error to be thrown if they don't match. - -```js -// Emits an error if app/scripts.js doesn't exist -gulp.src('app/scripts.js') - .pipe(...); - -// Won't emit an error -gulp.src('app/scripts.js', { allowEmpty: true }) - .pipe(...); -``` - - -### gulp.dest(path[, options]) - -Can be piped to and it will write files. Re-emits all data passed to it so you -can pipe to multiple folders. Folders that don't exist will be created. - -```javascript -gulp.src('./client/templates/*.pug') - .pipe(pug()) - .pipe(gulp.dest('./build/templates')) - .pipe(minify()) - .pipe(gulp.dest('./build/minified_templates')); -``` - -The write path is calculated by appending the file relative path to the given -destination directory. In turn, relative paths are calculated against -the file base. See `gulp.src` above for more info. - -#### path -Type: `String` or `Function` - -The path (output folder) to write files to. Or a function that returns it, -the function will be provided a [vinyl File instance]. - -#### options -Type: `Object` - -##### options.cwd -Type: `String` - -Default: `process.cwd()` - -`cwd` for the output folder, only has an effect if provided output folder is -relative. - -##### options.mode -Type: `String` or `Number` - -Default: the mode of the input file (file.stat.mode) or the process mode -if the input file has no mode property. - -Octal permission specifying the mode the files should be created with: e.g. -`"0744"`, `0744` or `484` (`0744` in base 10). - -##### options.dirMode -Type: `String` or `Number` - -Default: Default is the process mode. - -Octal permission specifying the mode the directory should be created with: e.g. -`"0755"`, `0755` or `493` (`0755` in base 10). - -##### options.overwrite -Type: `Boolean` - -Default: `true` - -Specify if existing files with the same path should be overwritten or not. - - -### gulp.symlink(folder[, options]) - -Functions exactly like `gulp.dest`, but will create symlinks instead of copying -a directory. - -#### folder -Type: `String` or `Function` - -A folder path or a function that receives in a file and returns a folder path. - -#### options -Type: `Object` - -##### options.cwd -Type: `String` - -Default: `process.cwd()` - -`cwd` for the output folder, only has an effect if provided output folder is -relative. - -##### options.dirMode -Type: `String` or `Number` - -Default: Default is the process mode. - -Octal permission specifying the mode the directory should be created with: e.g. -`"0755"`, `0755` or `493` (`0755` in base 10). - -### gulp.task([name,] fn) - -Define a task exposed to gulp-cli, `gulp.series`, `gulp.parallel` and -`gulp.lastRun`; inherited from [undertaker]. - -```js -gulp.task(function someTask() { - // Do stuff -}); -``` - -Or get a task that has been registered. - -```js -// someTask will be the registered task function -var someTask = gulp.task('someTask'); -``` - -#### name -Type: `String` - -If the name is not provided, the task will be named after the function -`name` or `displayName` property. The name argument is required if the -`name` and `displayName` properties of `fn` are empty. - -Since the task can be run from the command line, you should avoid using -spaces in task names. - -#### fn - -The function that performs the task's operations. Generally it takes this form: - -```js -function someTask() { - return gulp.src(['some/glob/**/*.ext']).pipe(someplugin()); -} -someTask.description = 'Does something'; - -gulp.task(someTask) -``` - -Gulp tasks are asynchronous and Gulp uses [async-done] to wait for the task's -completion. Tasks are called with a callback parameter to call to signal -completion. Alternatively, Task can return a stream, a promise, a child process -or a RxJS observable to signal the end of the task. - -**Warning:** Sync tasks are not supported and your function will never complete -if the one of the above strategies is not used to signal completion. However, -thrown errors will be caught by Gulp. - -#### fn properties - -##### fn.name - -`gulp.task` names the task after the function `name` property -if the optional `name` parameter of `gulp.task` is not provided. - -**Note:** [Function.name] is not writable; it cannot be set or edited. If -you need to assign a function name or use characters that aren't allowed -in function names, use the `displayName` property. -It will be empty for anonymous functions: - -```js -function foo() {}; -foo.name === 'foo' // true - -var bar = function() {}; -bar.name === '' // true - -bar.name = 'bar' -bar.name === '' // true -``` - -##### fn.displayName - -`gulp.task` names the task after the function `displayName` property -if function is anonymous and the optional `name` parameter of `gulp.task` -is not provided. - -##### fn.description - -gulp-cli prints this description alongside the task name when listing tasks: - -```js -var gulp = require('gulp'); - -function test(done){ - done(); -} -test.description = 'I do nothing'; - -gulp.task(test); -``` - -```sh -$> gulp --tasks -[12:00:02] Tasks for ~/Documents/some-project/gulpfile.js -[12:00:02] └── test I do nothing -``` - -#### Async support - -##### Accept a callback - -```js -var del = require('del'); - -gulp.task('clean', function(done) { - del(['.build/'], done); -}); - -// use an async result in a pipe -gulp.task('somename', function(cb) { - getFilesAsync(function(err, res) { - if (err) return cb(err); - var stream = gulp.src(res) - .pipe(minify()) - .pipe(gulp.dest('build')) - .on('end', cb); - }); -}); -``` - -The callback accepts an optional `Error` object. If it receives an error, -the task will fail. - -##### Return a stream - -```js -gulp.task('somename', function() { - return gulp.src('client/**/*.js') - .pipe(minify()) - .pipe(gulp.dest('build')); -}); -``` - -##### Return a promise - -```js -var Promise = require('promise'); -var del = require('del'); - -gulp.task('clean', function() { - return new Promise(function (resolve, reject) { - del(['.build/'], function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); -}); -``` - -or: -```js -var promisedDel = require('promised-del'); - -gulp.task('clean', function() { - return promisedDel(['.build/']); -}); -``` - -##### Return a child process - -```js -gulp.task('clean', function() { - return spawn('rm', ['-rf', path.join(__dirname, 'build')]); -}); - -``` - -##### Return a [RxJS] observable - -```js -var Observable = require('rx').Observable; - -gulp.task('sometask', function() { - return Observable.return(42); -}); -``` - - -### gulp.lastRun(taskName, [timeResolution]) - -Returns the timestamp of the last time the task ran successfully. The time -will be the time the task started. Returns `undefined` if the task has -not run yet. - -#### taskName - -Type: `String` - -The name of the registered task or of a function. - -#### timeResolution - -Type: `Number`. - -Default: `1000` on node v0.10, `0` on node v0.12 (and iojs v1.5). - -Set the time resolution of the returned timestamps. Assuming -the task named "someTask" ran at `1426000004321`: - -- `gulp.lastRun('someTask', 1000)` would return `1426000004000`. -- `gulp.lastRun('someTask', 100)` would return `1426000004300`. - -`timeResolution` allows you to compare a run time to a file [mtime stat][fs stats] -attribute. This attribute time resolution may vary depending of the node version -and the file system used: - -- on node v0.10, a file [mtime stat][fs stats] time resolution of any files will be 1s at best; -- on node v0.12 and iojs v1.5, 1ms at best; -- for files on FAT32, the mtime time resolution is 2s; -- on HFS+ and Ext3, 1s; -- on NTFS, 1s on node v0.10, 100ms on node 0.12; -- on Ext4, 1s on node v0.10, 1ms on node 0.12. - - -### gulp.parallel(...tasks) - -Takes a number of task names or functions and returns a function of the composed -tasks or functions. - -When using task names, the task should already be registered. - -When the returned function is executed, the tasks or functions will be executed -in parallel, all being executed at the same time. If an error occurs, -all execution will complete. - -```js -gulp.task('one', function(done) { - // do stuff - done(); -}); - -gulp.task('two', function(done) { - // do stuff - done(); -}); - -gulp.task('default', gulp.parallel('one', 'two', function(done) { - // do more stuff - done(); -})); -``` - -#### tasks -Type: `Array`, `String` or `Function` - -A task name, a function or an array of either. - - -### gulp.series(...tasks) - -Takes a number of task names or functions and returns a function of the composed -tasks or functions. - -When using task names, the task should already be registered. - -When the returned function is executed, the tasks or functions will be executed -in series, each waiting for the prior to finish. If an error occurs, -execution will stop. - -```js -gulp.task('one', function(done) { - // do stuff - done(); -}); - -gulp.task('two', function(done) { - // do stuff - done(); -}); - -gulp.task('default', gulp.series('one', 'two', function(done) { - // do more stuff - done(); -})); -``` - -#### tasks -Type: `Array`, `String` or `Function` - -A task name, a function or an array of either. - - -### gulp.watch(globs[, opts][, fn]) - -Takes a path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings as `globs` to watch on the filesystem. Also optionally takes `options` to configure the watcher and a `fn` to execute when a file changes. - -Returns an instance of [`chokidar`][chokidar]. - -```js -gulp.watch('js/**/*.js', gulp.parallel('concat', 'uglify')); -``` - -In the example, `gulp.watch` runs the function returned by `gulp.parallel` each -time a file with the `js` extension in `js/` is updated. - -#### globs -Type: `String` or `Array` - -A path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings that indicate which files to watch for changes. - -#### opts -Type: `Object` - -* `delay` (milliseconds, default: `200`). The delay to wait before triggering the fn. Useful for waiting on many changes before doing the work on changed files, e.g. find-and-replace on many files. -* `queue` (boolean, default: `true`). Whether or not a file change should queue the fn execution if the fn is already running. Useful for a long running fn. -* `ignoreInitial` (boolean, default: `true`). If set to `false` the `fn` is called during [chokidar][chokidar] instantiation as it discovers the file paths. Useful if it is desirable to trigger the `fn` during startup. __Passed through to [chokidar][chokidar], but defaulted to `true` instead of `false`.__ - -Options that are passed to [`chokidar`][chokidar]. - -Commonly used options: - -* `ignored` ([anymatch](https://github.com/es128/anymatch)-compatible definition). -Defines files/paths to be excluded from being watched. -* `usePolling` (boolean, default: `false`). When `true` uses a watch method backed -by stat polling. Usually necessary when watching files on a network mount or on a -VMs file system. -* `cwd` (path string). The base directory from which watch paths are to be -derived. Paths emitted with events will be relative to this. -* `alwaysStat` (boolean, default: `false`). If relying upon the -[`fs.Stats`][fs stats] object -that may get passed as a second argument with `add`, `addDir`, and `change` events -when available, set this to `true` to ensure it is provided with every event. May -have a slight performance penalty. - -Read about the full set of options in [`chokidar`'s README][chokidar]. - -#### fn -Type: `Function` - -If the `fn` is passed, it will be called when the watcher emits a `change`, `add` or `unlink` event. It is automatically debounced with a default delay of 200 milliseconds and subsequent calls will be queued and called upon completion. These defaults can be changed using the `options`. - -The `fn` is passed a single argument, `callback`, which is a function that must be called when work in the `fn` is complete. Instead of calling the `callback` function, [async completion][async-completion] can be signalled by: - * Returning a `Stream` or `EventEmitter` - * Returning a `Child Process` - * Returning a `Promise` - * Returning an `Observable` - -Once async completion is signalled, if another run is queued, it will be executed. - -`gulp.watch` returns a wrapped [chokidar] FSWatcher object. Listeners can also be set directly for any of [chokidar]'s events, such as `addDir`, `unlinkDir`, and `error`. You must set listeners directly to get -access to chokidar's callback parameters, such as `path`. - -```js -var watcher = gulp.watch('js/**/*.js', gulp.parallel('concat', 'uglify')); -watcher.on('change', function(path, stats) { - console.log('File ' + path + ' was changed'); -}); - -watcher.on('unlink', function(path) { - console.log('File ' + path + ' was removed'); -}); -``` - -##### path -Type: `String` - -Path to the file. If `opts.cwd` is set, `path` is relative to it. - -##### stats -Type: `Object` - -[File stats][fs stats] object when available. -Setting the `alwaysStat` option to `true` will ensure that a file stat object will be -provided. - -#### watcher methods - -##### watcher.close() - -Shuts down the file watcher. - -##### watcher.add(glob) - -Watch additional glob (or array of globs) with an already-running watcher instance. - -##### watcher.unwatch(glob) - -Stop watching a glob (or array of globs) while leaving the watcher running and -emitting events for the remaining paths it is watching. - - -### gulp.tree(options) - -Returns the tree of tasks. Inherited from [undertaker]. See the [undertaker docs for this function](https://github.com/phated/undertaker#treeoptions--object). - -#### options -Type: `Object` - -Options to pass to [undertaker]. - -##### options.deep -Type: `Boolean` - -Default: `false` - -If set to `true` whole tree should be returned. - -#### Example gulpfile - -```js -gulp.task('one', function(done) { - // do stuff - done(); -}); - -gulp.task('two', function(done) { - // do stuff - done(); -}); - -gulp.task('three', function(done) { - // do stuff - done(); -}); - -gulp.task('four', gulp.series('one', 'two')); - -gulp.task('five', - gulp.series('four', - gulp.parallel('three', function(done) { - // do more stuff - done(); - }) - ) -); -``` - -#### Example tree output - -```js -gulp.tree() - -// output: [ 'one', 'two', 'three', 'four', 'five' ] - -gulp.tree({ deep: true }) - -/*output: [ - { - "label":"one", - "type":"task", - "nodes":[] - }, - { - "label":"two", - "type":"task", - "nodes":[] - }, - { - "label":"three", - "type":"task", - "nodes":[] - }, - { - "label":"four", - "type":"task", - "nodes":[ - { - "label":"", - "type":"function", - "nodes":[ - { - "label":"one", - "type":"task", - "nodes":[] - }, - { - "label":"two", - "type":"task", - "nodes":[] - } - ] - } - ] - }, - { - "label":"five", - "type":"task", - "nodes":[ - { - "label":"", - "type":"function", - "nodes":[ - { - "label":"four", - "type":"task", - "nodes":[ - { - "label":"", - "type":"function", - "nodes":[ - { - "label":"one", - "type":"task", - "nodes":[] - }, - { - "label":"two", - "type":"task", - "nodes":[] - } - ] - } - ] - }, - { - "label":"", - "type":"function", - "nodes":[ - { - "label":"three", - "type":"task", - "nodes":[] - }, - { - "label":"", - "type":"function", - "nodes":[] - } - ] - } - ] - } - ] - } -] -*/ -``` - - -### gulp.registry([registry]) - -Get or set the underlying task registry. Inherited from [undertaker]; see the undertaker documention on [registries](https://github.com/phated/undertaker#registryregistryinstance). Using this, you can change registries that enhance gulp in different ways. Utilizing a custom registry has at least three use cases: - -- [Sharing tasks](https://github.com/phated/undertaker#sharing-tasks) -- [Sharing functionality](https://github.com/phated/undertaker#sharing-functionalities) (e.g. you could override the task prototype to add some additional logging, bind task metadata or include some config settings.) -- Handling other behavior that hooks into the registry lifecycle (see [gulp-hub](https://github.com/frankwallis/gulp-hub) for an example) - -To build your own custom registry see the [undertaker documentation on custom registries](https://github.com/phated/undertaker#custom-registries). - -#### registry - -A registry instance. When passed in, the tasks from the current registry will be transferred to the new registry and then current registry will be replaced with the new registry. - -#### Example - -This example shows how to create and use a simple custom registry to add tasks. - -```js -//gulpfile.js -var gulp = require('gulp'); - -var companyTasks = require('./myCompanyTasksRegistry.js'); - -gulp.registry(companyTasks); - -gulp.task('one', gulp.parallel('someCompanyTask', function(done) { - console.log('in task one'); - done(); -})); -``` - -```js -//myCompanyTasksRegistry.js -var util = require('util'); - -var DefaultRegistry = require('undertaker-registry'); - -function MyCompanyTasksRegistry() { - DefaultRegistry.call(this); -} -util.inherits(MyCompanyTasksRegistry, DefaultRegistry); - -MyCompanyTasksRegistry.prototype.init = function(gulp) { - gulp.task('clean', function(done) { - done(); - }); - gulp.task('someCompanyTask', function(done) { - console.log('performing some company task.'); - done(); - }); -}; - -module.exports = new MyCompanyTasksRegistry(); -``` - -[Function.name]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name -[chokidar]: https://github.com/paulmillr/chokidar -[glob-stream]: https://github.com/gulpjs/glob-stream -[glob-parent]: https://github.com/es128/glob-parent -[gulp-if]: https://github.com/robrich/gulp-if -[node-glob documentation]: https://github.com/isaacs/node-glob#options -[node-glob]: https://github.com/isaacs/node-glob -[piped]: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options -[RxJS]: https://www.npmjs.com/package/rx -[stream]: https://nodejs.org/api/stream.html -[async-done]: https://www.npmjs.com/package/async-done -[undertaker]: https://github.com/gulpjs/undertaker -[vinyl File instance]: https://github.com/gulpjs/vinyl -[Vinyl files]: https://github.com/gulpjs/vinyl-fs -[fs stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats -[async-completion]: https://github.com/gulpjs/async-done#completion-and-error-resolution diff --git a/docs/README.md b/docs/README.md index f90cabc91..c783b710b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ sidebar_label: Docs # gulp documentation * [Getting Started](getting-started/) - Get started with gulp -* [API documentation](API.md) - The programming interface, defined +* [API documentation](api/) - The programming interface, defined * [CLI documentation](CLI.md) - Learn how to call tasks and use compilers * [Writing a Plugin](writing-a-plugin/) - The essentials of writing a gulp plugin * [Why Use Pump?](why-use-pump/README.md) - Why to use the `pump` module instead of calling `.pipe` yourself diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 000000000..387e2787e --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,19 @@ + + +## gulp API docs + +* [gulp.src](src.md) - Emit files matching one or more globs +* [gulp.dest](dest.md) - Write files to directories +* [gulp.symlink](symlink.md) - Write files to symlinks +* [gulp.task](task.md) - Define tasks +* [gulp.lastRun](lastRun.md) - Get timestamp of last successful run +* [gulp.parallel](parallel.md) - Run tasks in parallel +* [gulp.series](series.md) - Run tasks in series +* [gulp.watch](watch.md) - Do something when a file changes +* [gulp.tree](tree.md) - Get the tree of tasks +* [gulp.registry](registry.md) - Get or set the task registry diff --git a/docs/api/dest.md b/docs/api/dest.md new file mode 100644 index 000000000..a9b909b77 --- /dev/null +++ b/docs/api/dest.md @@ -0,0 +1,66 @@ + + +# `gulp.dest(path[, options])` + +Can be piped to and it will write files. Re-emits all data passed to it so you +can pipe to multiple folders. Folders that don't exist will be created. + +```javascript +gulp.src('./client/templates/*.pug') + .pipe(pug()) + .pipe(gulp.dest('./build/templates')) + .pipe(minify()) + .pipe(gulp.dest('./build/minified_templates')); +``` + +The write path is calculated by appending the file relative path to the given +destination directory. In turn, relative paths are calculated against +the file base. See `gulp.src` above for more info. + +## path +Type: `String` or `Function` + +The path (output folder) to write files to. Or a function that returns it, +the function will be provided a [vinyl File instance]. + +## options +Type: `Object` + +### options.cwd +Type: `String` + +Default: `process.cwd()` + +`cwd` for the output folder, only has an effect if provided output folder is +relative. + +### options.mode +Type: `String` or `Number` + +Default: the mode of the input file (file.stat.mode) or the process mode +if the input file has no mode property. + +Octal permission specifying the mode the files should be created with: e.g. +`"0744"`, `0744` or `484` (`0744` in base 10). + +### options.dirMode +Type: `String` or `Number` + +Default: Default is the process mode. + +Octal permission specifying the mode the directory should be created with: e.g. +`"0755"`, `0755` or `493` (`0755` in base 10). + +### options.overwrite +Type: `Boolean` + +Default: `true` + +Specify if existing files with the same path should be overwritten or not. + +[vinyl File instance]: https://github.com/gulpjs/vinyl diff --git a/docs/api/lastRun.md b/docs/api/lastRun.md new file mode 100644 index 000000000..2be1e668f --- /dev/null +++ b/docs/api/lastRun.md @@ -0,0 +1,43 @@ + + +# `gulp.lastRun(taskName, [timeResolution])` + +Returns the timestamp of the last time the task ran successfully. The time +will be the time the task started. Returns `undefined` if the task has +not run yet. + +## taskName + +Type: `String` + +The name of the registered task or of a function. + +## timeResolution + +Type: `Number`. + +Default: `1000` on node v0.10, `0` on node v0.12 (and iojs v1.5). + +Set the time resolution of the returned timestamps. Assuming +the task named "someTask" ran at `1426000004321`: + +- `gulp.lastRun('someTask', 1000)` would return `1426000004000`. +- `gulp.lastRun('someTask', 100)` would return `1426000004300`. + +`timeResolution` allows you to compare a run time to a file [mtime stat][fs stats] +attribute. This attribute time resolution may vary depending of the node version +and the file system used: + +- on node v0.10, a file [mtime stat][fs stats] time resolution of any files will be 1s at best; +- on node v0.12 and iojs v1.5, 1ms at best; +- for files on FAT32, the mtime time resolution is 2s; +- on HFS+ and Ext3, 1s; +- on NTFS, 1s on node v0.10, 100ms on node 0.12; +- on Ext4, 1s on node v0.10, 1ms on node 0.12. + +[fs stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats diff --git a/docs/api/parallel.md b/docs/api/parallel.md new file mode 100644 index 000000000..7d15efbbe --- /dev/null +++ b/docs/api/parallel.md @@ -0,0 +1,40 @@ + + +# `gulp.parallel(...tasks)` + +Takes a number of task names or functions and returns a function of the composed +tasks or functions. + +When using task names, the task should already be registered. + +When the returned function is executed, the tasks or functions will be executed +in parallel, all being executed at the same time. If an error occurs, +all execution will complete. + +```js +gulp.task('one', function(done) { + // do stuff + done(); +}); + +gulp.task('two', function(done) { + // do stuff + done(); +}); + +gulp.task('default', gulp.parallel('one', 'two', function(done) { + // do more stuff + done(); +})); +``` + +## tasks +Type: `Array`, `String` or `Function` + +A task name, a function or an array of either. + diff --git a/docs/api/registry.md b/docs/api/registry.md new file mode 100644 index 000000000..2b04e9f8c --- /dev/null +++ b/docs/api/registry.md @@ -0,0 +1,64 @@ + + +# `gulp.registry([registry])` + +Get or set the underlying task registry. Inherited from [undertaker]; see the undertaker documention on [registries](https://github.com/phated/undertaker#registryregistryinstance). Using this, you can change registries that enhance gulp in different ways. Utilizing a custom registry has at least three use cases: + +- [Sharing tasks](https://github.com/phated/undertaker#sharing-tasks) +- [Sharing functionality](https://github.com/phated/undertaker#sharing-functionalities) (e.g. you could override the task prototype to add some additional logging, bind task metadata or include some config settings.) +- Handling other behavior that hooks into the registry lifecycle (see [gulp-hub](https://github.com/frankwallis/gulp-hub) for an example) + +To build your own custom registry see the [undertaker documentation on custom registries](https://github.com/phated/undertaker#custom-registries). + +## registry + +A registry instance. When passed in, the tasks from the current registry will be transferred to the new registry and then current registry will be replaced with the new registry. + +## Example + +This example shows how to create and use a simple custom registry to add tasks. + +```js +//gulpfile.js +var gulp = require('gulp'); + +var companyTasks = require('./myCompanyTasksRegistry.js'); + +gulp.registry(companyTasks); + +gulp.task('one', gulp.parallel('someCompanyTask', function(done) { + console.log('in task one'); + done(); +})); +``` + +```js +//myCompanyTasksRegistry.js +var util = require('util'); + +var DefaultRegistry = require('undertaker-registry'); + +function MyCompanyTasksRegistry() { + DefaultRegistry.call(this); +} +util.inherits(MyCompanyTasksRegistry, DefaultRegistry); + +MyCompanyTasksRegistry.prototype.init = function(gulp) { + gulp.task('clean', function(done) { + done(); + }); + gulp.task('someCompanyTask', function(done) { + console.log('performing some company task.'); + done(); + }); +}; + +module.exports = new MyCompanyTasksRegistry(); +``` + +[undertaker]: https://github.com/gulpjs/undertaker diff --git a/docs/api/series.md b/docs/api/series.md new file mode 100644 index 000000000..9499ed42f --- /dev/null +++ b/docs/api/series.md @@ -0,0 +1,39 @@ + + +# `gulp.series(...tasks)` + +Takes a number of task names or functions and returns a function of the composed +tasks or functions. + +When using task names, the task should already be registered. + +When the returned function is executed, the tasks or functions will be executed +in series, each waiting for the prior to finish. If an error occurs, +execution will stop. + +```js +gulp.task('one', function(done) { + // do stuff + done(); +}); + +gulp.task('two', function(done) { + // do stuff + done(); +}); + +gulp.task('default', gulp.series('one', 'two', function(done) { + // do more stuff + done(); +})); +``` + +## tasks +Type: `Array`, `String` or `Function` + +A task name, a function or an array of either. diff --git a/docs/api/src.md b/docs/api/src.md new file mode 100644 index 000000000..307d9ab6c --- /dev/null +++ b/docs/api/src.md @@ -0,0 +1,139 @@ + + +# `gulp.src(globs[, options])` + +Emits files matching provided glob or array of globs. +Returns a [stream] of [Vinyl files] that can be [piped] to plugins. + +```javascript +gulp.src('client/templates/*.pug') + .pipe(pug()) + .pipe(minify()) + .pipe(gulp.dest('build/minified_templates')); +``` + +`glob` refers to [node-glob syntax][node-glob] or it can be a direct file path. + +## globs +Type: `String` or `Array` + +Glob or array of globs to read. Globs use [node-glob syntax] except that negation is fully supported. + +A glob that begins with `!` excludes matching files from the glob results up to that point. For example, consider this directory structure: + + client/ + a.js + bob.js + bad.js + +The following expression matches `a.js` and `bad.js`: + + gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js']) + + +Note that globs are evaluated in order, which means this is possible: + +```js +// exclude every JS file that starts with a b except bad.js +gulp.src(['*.js', '!b*.js', 'bad.js']) +``` + +**Note:** glob symlink following behavior is opt-in and you must specify +`follow: true` in the options object that is passed to [node-glob]. + +## options +Type: `Object` + +Options to pass to [node-glob] through [glob-stream]. + +gulp adds some additional options in addition to the +[options supported by node-glob][node-glob documentation] and [glob-stream]: + +### options.cwd + +The working directory the folder is relative to. + +Type: `String` + +Default: `process.cwd()` + + +### options.buffer +Type: `Boolean` + +Default: `true` + +Setting this to `false` will return `file.contents` as a stream and not +buffer files. This is useful when working with large files. + +**Note:** Plugins might not implement support for streams. + +### options.read +Type: `Boolean` + +Default: `true` + +Setting this to `false` will return `file.contents` as null and not read +the file at all. + +### options.base +Type: `String` + +Default: everything before a glob starts (see [glob-parent]) + +E.g., consider `somefile.js` in `client/js/somedir`: + +```js +// Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/` +gulp.src('client/js/**/*.js') + .pipe(minify()) + .pipe(gulp.dest('build')); // Writes 'build/somedir/somefile.js' + +gulp.src('client/js/**/*.js', { base: 'client' }) + .pipe(minify()) + .pipe(gulp.dest('build')); // Writes 'build/js/somedir/somefile.js' +``` + +### options.since +Type: `Date` or `Number` + +Setting this to a Date or a time stamp will discard any file that have not been +modified since the time specified. + +### options.passthrough +Type: `Boolean` + +Default: `false` + +If true, it will create a duplex stream which passes items through and +emits globbed files. + +### options.allowEmpty +Type: `Boolean` + +Default: `false` + +When true, will allow singular globs to fail to match. Otherwise, globs which are only supposed to match one file (such as `./foo/bar.js`) will cause an error to be thrown if they don't match. + +```js +// Emits an error if app/scripts.js doesn't exist +gulp.src('app/scripts.js') + .pipe(...); + +// Won't emit an error +gulp.src('app/scripts.js', { allowEmpty: true }) + .pipe(...); +``` + +[glob-stream]: https://github.com/gulpjs/glob-stream +[glob-parent]: https://github.com/es128/glob-parent +[node-glob documentation]: https://github.com/isaacs/node-glob#options +[node-glob]: https://github.com/isaacs/node-glob +[piped]: http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options +[stream]: http://nodejs.org/api/stream.html +[Vinyl files]: https://github.com/gulpjs/vinyl-fs diff --git a/docs/api/symlink.md b/docs/api/symlink.md new file mode 100644 index 000000000..f8b00f0c3 --- /dev/null +++ b/docs/api/symlink.md @@ -0,0 +1,35 @@ + + +# `gulp.symlink(folder[, options])` + +Functions exactly like `gulp.dest`, but will create symlinks instead of copying +a directory. + +## folder +Type: `String` or `Function` + +A folder path or a function that receives in a file and returns a folder path. + +## options +Type: `Object` + +### options.cwd +Type: `String` + +Default: `process.cwd()` + +`cwd` for the output folder, only has an effect if provided output folder is +relative. + +### options.dirMode +Type: `String` or `Number` + +Default: Default is the process mode. + +Octal permission specifying the mode the directory should be created with: e.g. +`"0755"`, `0755` or `493` (`0755` in base 10). diff --git a/docs/api/task.md b/docs/api/task.md new file mode 100644 index 000000000..b644de602 --- /dev/null +++ b/docs/api/task.md @@ -0,0 +1,194 @@ + + +# `gulp.task([name,] fn)` + +Define a task exposed to gulp-cli, `gulp.series`, `gulp.parallel` and +`gulp.lastRun`; inherited from [undertaker]. + +```js +gulp.task(function someTask() { + // Do stuff +}); +``` + +Or get a task that has been registered. + +```js +// someTask will be the registered task function +var someTask = gulp.task('someTask'); +``` + +## name +Type: `String` + +If the name is not provided, the task will be named after the function +`name` or `displayName` property. The name argument is required if the +`name` and `displayName` properties of `fn` are empty. + +Since the task can be run from the command line, you should avoid using +spaces in task names. + +## fn + +The function that performs the task's operations. Generally it takes this form: + +```js +function someTask() { + return gulp.src(['some/glob/**/*.ext']).pipe(someplugin()); +} +someTask.description = 'Does something'; + +gulp.task(someTask) +``` + +Gulp tasks are asynchronous and Gulp uses [async-done] to wait for the task's +completion. Tasks are called with a callback parameter to call to signal +completion. Alternatively, Task can return a stream, a promise, a child process +or a RxJS observable to signal the end of the task. + +**Warning:** Sync tasks are not supported and your function will never complete +if the one of the above strategies is not used to signal completion. However, +thrown errors will be caught by Gulp. + +## fn properties + +### fn.name + +`gulp.task` names the task after the function `name` property +if the optional `name` parameter of `gulp.task` is not provided. + +**Note:** [Function.name] is not writable; it cannot be set or edited. If +you need to assign a function name or use characters that aren't allowed +in function names, use the `displayName` property. +It will be empty for anonymous functions: + +```js +function foo() {}; +foo.name === 'foo' // true + +var bar = function() {}; +bar.name === '' // true + +bar.name = 'bar' +bar.name === '' // true +``` + +### fn.displayName + +`gulp.task` names the task after the function `displayName` property +if function is anonymous and the optional `name` parameter of `gulp.task` +is not provided. + +### fn.description + +gulp-cli prints this description alongside the task name when listing tasks: + +```js +var gulp = require('gulp'); + +function test(done){ + done(); +} +test.description = 'I do nothing'; + +gulp.task(test); +``` + +```sh +$> gulp --tasks +[12:00:02] Tasks for ~/Documents/some-project/gulpfile.js +[12:00:02] └── test I do nothing +``` + +## Async support + +### Accept a callback + +```js +var del = require('del'); + +gulp.task('clean', function(done) { + del(['.build/'], done); +}); + +// use an async result in a pipe +gulp.task('somename', function(cb) { + getFilesAsync(function(err, res) { + if (err) return cb(err); + var stream = gulp.src(res) + .pipe(minify()) + .pipe(gulp.dest('build')) + .on('end', cb); + }); +}); +``` + +The callback accepts an optional `Error` object. If it receives an error, +the task will fail. + +### Return a stream + +```js +gulp.task('somename', function() { + return gulp.src('client/**/*.js') + .pipe(minify()) + .pipe(gulp.dest('build')); +}); +``` + +### Return a promise + +```js +var Promise = require('promise'); +var del = require('del'); + +gulp.task('clean', function() { + return new Promise(function (resolve, reject) { + del(['.build/'], function(err) { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +}); +``` + +or: +```js +var promisedDel = require('promised-del'); + +gulp.task('clean', function() { + return promisedDel(['.build/']); +}); +``` + +### Return a child process + +```js +gulp.task('clean', function() { + return spawn('rm', ['-rf', path.join(__dirname, 'build')]); +}); + +``` + +### Return a [RxJS] observable + +```js +var Observable = require('rx').Observable; + +gulp.task('sometask', function() { + return Observable.return(42); +}); +``` + +[Function.name]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name +[RxJS]: https://www.npmjs.com/package/rx +[async-done]: https://www.npmjs.com/package/async-done +[undertaker]: https://github.com/gulpjs/undertaker diff --git a/docs/api/tree.md b/docs/api/tree.md new file mode 100644 index 000000000..e7e43f1bb --- /dev/null +++ b/docs/api/tree.md @@ -0,0 +1,155 @@ + + +# `gulp.tree(options)` + +Returns the tree of tasks. Inherited from [undertaker]. See the [undertaker docs for this function](https://github.com/phated/undertaker#treeoptions--object). + +## options +Type: `Object` + +Options to pass to [undertaker]. + +### options.deep +Type: `Boolean` + +Default: `false` + +If set to `true` whole tree should be returned. + +## Example gulpfile + +```js +gulp.task('one', function(done) { + // do stuff + done(); +}); + +gulp.task('two', function(done) { + // do stuff + done(); +}); + +gulp.task('three', function(done) { + // do stuff + done(); +}); + +gulp.task('four', gulp.series('one', 'two')); + +gulp.task('five', + gulp.series('four', + gulp.parallel('three', function(done) { + // do more stuff + done(); + }) + ) +); +``` + +## Example tree output + +```js +gulp.tree() + +// output: [ 'one', 'two', 'three', 'four', 'five' ] + +gulp.tree({ deep: true }) + +/*output: [ + { + "label":"one", + "type":"task", + "nodes":[] + }, + { + "label":"two", + "type":"task", + "nodes":[] + }, + { + "label":"three", + "type":"task", + "nodes":[] + }, + { + "label":"four", + "type":"task", + "nodes":[ + { + "label":"", + "type":"function", + "nodes":[ + { + "label":"one", + "type":"task", + "nodes":[] + }, + { + "label":"two", + "type":"task", + "nodes":[] + } + ] + } + ] + }, + { + "label":"five", + "type":"task", + "nodes":[ + { + "label":"", + "type":"function", + "nodes":[ + { + "label":"four", + "type":"task", + "nodes":[ + { + "label":"", + "type":"function", + "nodes":[ + { + "label":"one", + "type":"task", + "nodes":[] + }, + { + "label":"two", + "type":"task", + "nodes":[] + } + ] + } + ] + }, + { + "label":"", + "type":"function", + "nodes":[ + { + "label":"three", + "type":"task", + "nodes":[] + }, + { + "label":"", + "type":"function", + "nodes":[] + } + ] + } + ] + } + ] + } +] +*/ +``` + +[undertaker]: https://github.com/gulpjs/undertaker diff --git a/docs/api/watch.md b/docs/api/watch.md new file mode 100644 index 000000000..002d7a1de --- /dev/null +++ b/docs/api/watch.md @@ -0,0 +1,109 @@ + + +# `gulp.watch(globs[, opts][, fn])` + +Takes a path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings as `globs` to watch on the filesystem. Also optionally takes `options` to configure the watcher and a `fn` to execute when a file changes. + +Returns an instance of [`chokidar`][chokidar]. + +```js +gulp.watch('js/**/*.js', gulp.parallel('concat', 'uglify')); +``` + +In the example, `gulp.watch` runs the function returned by `gulp.parallel` each +time a file with the `js` extension in `js/` is updated. + +## globs +Type: `String` or `Array` + +A path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings that indicate which files to watch for changes. + +## opts +Type: `Object` + +* `delay` (milliseconds, default: `200`). The delay to wait before triggering the fn. Useful for waiting on many changes before doing the work on changed files, e.g. find-and-replace on many files. +* `queue` (boolean, default: `true`). Whether or not a file change should queue the fn execution if the fn is already running. Useful for a long running fn. +* `ignoreInitial` (boolean, default: `true`). If set to `false` the `fn` is called during [chokidar][chokidar] instantiation as it discovers the file paths. Useful if it is desirable to trigger the `fn` during startup. __Passed through to [chokidar][chokidar], but defaulted to `true` instead of `false`.__ + +Options that are passed to [`chokidar`][chokidar]. + +Commonly used options: + +* `ignored` ([anymatch](https://github.com/es128/anymatch)-compatible definition). +Defines files/paths to be excluded from being watched. +* `usePolling` (boolean, default: `false`). When `true` uses a watch method backed +by stat polling. Usually necessary when watching files on a network mount or on a +VMs file system. +* `cwd` (path string). The base directory from which watch paths are to be +derived. Paths emitted with events will be relative to this. +* `alwaysStat` (boolean, default: `false`). If relying upon the +[`fs.Stats`][fs stats] object +that may get passed as a second argument with `add`, `addDir`, and `change` events +when available, set this to `true` to ensure it is provided with every event. May +have a slight performance penalty. + +Read about the full set of options in [`chokidar`'s README][chokidar]. + +## fn +Type: `Function` + +If the `fn` is passed, it will be called when the watcher emits a `change`, `add` or `unlink` event. It is automatically debounced with a default delay of 200 milliseconds and subsequent calls will be queued and called upon completion. These defaults can be changed using the `options`. + +The `fn` is passed a single argument, `callback`, which is a function that must be called when work in the `fn` is complete. Instead of calling the `callback` function, [async completion][async-completion] can be signalled by: + * Returning a `Stream` or `EventEmitter` + * Returning a `Child Process` + * Returning a `Promise` + * Returning an `Observable` + +Once async completion is signalled, if another run is queued, it will be executed. + +`gulp.watch` returns a wrapped [chokidar] FSWatcher object. Listeners can also be set directly for any of [chokidar]'s events, such as `addDir`, `unlinkDir`, and `error`. You must set listeners directly to get +access to chokidar's callback parameters, such as `path`. + +```js +var watcher = gulp.watch('js/**/*.js', gulp.parallel('concat', 'uglify')); +watcher.on('change', function(path, stats) { + console.log('File ' + path + ' was changed'); +}); + +watcher.on('unlink', function(path) { + console.log('File ' + path + ' was removed'); +}); +``` + +### path +Type: `String` + +Path to the file. If `opts.cwd` is set, `path` is relative to it. + +### stats +Type: `Object` + +[File stats][fs stats] object when available. +Setting the `alwaysStat` option to `true` will ensure that a file stat object will be +provided. + +## watcher methods + +### watcher.close() + +Shuts down the file watcher. + +### watcher.add(glob) + +Watch additional glob (or array of globs) with an already-running watcher instance. + +### watcher.unwatch(glob) + +Stop watching a glob (or array of globs) while leaving the watcher running and +emitting events for the remaining paths it is watching. + +[chokidar]: https://github.com/paulmillr/chokidar +[node-glob]: https://github.com/isaacs/node-glob +[fs stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats +[async-completion]: https://github.com/gulpjs/async-done#completion-and-error-resolution From e447d81342a255ba9f7fac981919fd1f5a9ff495 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:16:53 -0700 Subject: [PATCH 162/216] Docs: Update dest() documentation --- docs/api/dest.md | 134 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 39 deletions(-) diff --git a/docs/api/dest.md b/docs/api/dest.md index a9b909b77..399effe50 100644 --- a/docs/api/dest.md +++ b/docs/api/dest.md @@ -5,62 +5,118 @@ hide_title: true sidebar_label: dest() --> -# `gulp.dest(path[, options])` +# dest() -Can be piped to and it will write files. Re-emits all data passed to it so you -can pipe to multiple folders. Folders that don't exist will be created. +Creates a stream for writing [Vinyl][vinyl-concepts] objects to the file system. -```javascript -gulp.src('./client/templates/*.pug') - .pipe(pug()) - .pipe(gulp.dest('./build/templates')) - .pipe(minify()) - .pipe(gulp.dest('./build/minified_templates')); +## Usage + +```js +const { src, dest } = require('gulp'); + +function copy() { + return src('input/*.js') + .pipe(dest('output/')); +} + +exports.copy = copy; +``` + +## Signature + +```js +dest(directory, [options]) ``` -The write path is calculated by appending the file relative path to the given -destination directory. In turn, relative paths are calculated against -the file base. See `gulp.src` above for more info. +### Parameters + +| parameter | type | note | +|:--------------:|:-----:|--------| +| directory
**(required)** | string
function | The path of the output directory where files will be written. If a function is used, the function will be called with each Vinyl object and must return a string directory path. | +| options | object | Detailed in [Options][options-section] below. | + +### Returns + +A stream that can be used in the middle or at the end of a pipeline to create files on the file system. +Whenever a Vinyl object is passed through the stream, it writes the contents and other details out to the file system at the given directory. If the Vinyl object has a `symlink` property, a symbolic link will be created instead of writing the contents. After the file is created, its [metadata will be updated][metadata-updates-section] to match the Vinyl object. + +Whenever a file is created on the file system, the Vinyl object will be modified. +* The `cwd`, `base`, and `path` properties will be updated to match the created file. +* The `stat` property will be updated to match the file on the file system. +* If the `contents` property is a stream, it will be reset so it can be read again. + +### Errors -## path -Type: `String` or `Function` +When `directory` is an empty string, throws an error with the message, "Invalid dest() folder argument. Please specify a non-empty string or a function." -The path (output folder) to write files to. Or a function that returns it, -the function will be provided a [vinyl File instance]. +When `directory` is not a string or function, throws an error with the message, "Invalid dest() folder argument. Please specify a non-empty string or a function." -## options -Type: `Object` +When `directory` is a function that returns an empty string or `undefined`, emits an error with the message, "Invalid output folder". -### options.cwd -Type: `String` +### Options -Default: `process.cwd()` -`cwd` for the output folder, only has an effect if provided output folder is -relative. +**For options that accept a function, the passed function will be called with each Vinyl object and must return a value of another listed type.** -### options.mode -Type: `String` or `Number` +| name | type | default | note | +|:-------:|:------:|-----------|-------| +| cwd | string
function | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `directory` with `path.join()`. | +| mode | number
function | `stat.mode` of the Vinyl object | The mode used when creating files. If not set and `stat.mode` is missing, the process' mode will be used instead. | +| dirMode | number
function | | The mode used when creating directories. If not set, the process' mode will be used. | +| overwrite | boolean
function | true | When true, overwrites existing files with the same path. | +| append | boolean
function | false | If true, adds contents to the end of the file, instead of replacing existing contents. | +| sourcemaps | boolean
string
function | false | If true, writes inline sourcemaps to the output file. Specifying a `string` path will write external [sourcemaps][sourcemaps-section] at the given path. | +| relativeSymlinks | boolean
function | false | When false, any symbolic links created will be absolute.
**Note**: Ignored if a junction is being created, as they must be absolute. | +| useJunctions | boolean
function | true | This option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in [Symbolic links on Windows][symbolic-links-section] below. | -Default: the mode of the input file (file.stat.mode) or the process mode -if the input file has no mode property. +## Metadata updates -Octal permission specifying the mode the files should be created with: e.g. -`"0744"`, `0744` or `484` (`0744` in base 10). +Whenever the `dest()` stream creates a file, the Vinyl object's `mode`, `mtime`, and `atime` are compared to the created file. If they differ, the created file will be updated to reflect the Vinyl object's metadata. If those properties are the same, or gulp doesn't have permissions to make changes, the attempt is skipped silently. + +This functionality is disabled on Windows or other operating systems that don't support Node's `process.getuid()` or `process.geteuid()` methods. This is due to Windows having unexpected results through usage of `fs.fchmod()` and `fs.futimes()`. + +**Note**: The `fs.futimes()` method internally converts `mtime` and `atime` timestamps to seconds. This division by 1000 may cause some loss of precision on 32-bit operating systems. + +## Sourcemaps + +Sourcemap support is built directly into `src()` and `dest()`, but it is disabled by default. Enable it to produce inline or external sourcemaps. + +Inline sourcemaps: +```js +const { src, dest } = require('gulp'); +const uglify = require('gulp-uglify'); + +src('input/**/*.js', { sourcemaps: true }) + .pipe(uglify()) + .pipe(dest('output/', { sourcemaps: true })); +``` + +External sourcemaps: +```js +const { src, dest } = require('gulp'); +const uglify = require('gulp-uglify'); + +src('input/**/*.js', { sourcemaps: true }) + .pipe(uglify()) + .pipe(dest('output/', { sourcemaps: '.' })); +``` -### options.dirMode -Type: `String` or `Number` +## Symbolic links on Windows -Default: Default is the process mode. +When creating symbolic links on Windows, a `type` argument is passed to Node's `fs.symlink()` method which specifies the kind of target being linked. The link type is set to: +* `'file'` when the target is a regular file +* `'junction'` when the target is a directory +* `'dir'` when the target is a directory and the user disables the `useJunctions` option -Octal permission specifying the mode the directory should be created with: e.g. -`"0755"`, `0755` or `493` (`0755` in base 10). -### options.overwrite -Type: `Boolean` +If you try to create a dangling (pointing to a non-existent target) link, the link type can't be determined automatically. In these cases, behavior will vary depending on whether the dangling link is being created via `symlink()` or via `dest()`. -Default: `true` +For dangling links created via `symlink()`, the incoming Vinyl object represents the target, so its stats will determine the desired link type. If `isDirectory()` returns false then a `'file'` link is created, otherwise a `'junction'` or a `'dir'` link is created depending on the value of the `useJunctions` option. -Specify if existing files with the same path should be overwritten or not. +For dangling links created via `dest()`, the incoming Vinyl object represents the link - typically loaded from disk via `src(..., { resolveSymlinks: false })`. In this case, the link type can't be reasonably determined and defaults to using `'file'`. This may cause unexpected behavior if you are creating a dangling link to a directory. **Avoid this scenario.** -[vinyl File instance]: https://github.com/gulpjs/vinyl +[sourcemaps-section]: #sourcemaps +[symbolic-links-section]: #symbolic-links-on-windows +[options-section]: #options +[metadata-updates-section]: #metadata-updates +[vinyl-concepts]: concepts.md#vinyl From 363df219e01f7d7ffed8c99930054c9938cecc31 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:17:59 -0700 Subject: [PATCH 163/216] Docs: Update lastRun() documentation --- docs/api/last-run.md | 83 ++++++++++++++++++++++++++++++++++++++++++++ docs/api/lastRun.md | 43 ----------------------- 2 files changed, 83 insertions(+), 43 deletions(-) create mode 100644 docs/api/last-run.md delete mode 100644 docs/api/lastRun.md diff --git a/docs/api/last-run.md b/docs/api/last-run.md new file mode 100644 index 000000000..c7c2269cc --- /dev/null +++ b/docs/api/last-run.md @@ -0,0 +1,83 @@ + + +# lastRun() + +Retrieves the last time a task was successfully completed during the current running process. Most useful on subsequent task runs while a watcher is running. + +When combined with `src()`, enables incremental builds to speed up your execution times by skipping files that haven't changed since the last successful task completion. + +## Usage + +```js +const { src, dest, lastRun, watch } = require('gulp'); +const imagemin = require('gulp-imagemin'); + +function images() { + return src('src/images/**/*.jpg', { since: lastRun(images) }) + .pipe(imagemin()) + .pipe(dest('build/img/')); +} + +function watch() { + watch('src/images/**/*.jpg', images); +} + +exports.watch = watch; +``` + + +## Signature + +```js +lastRun(task, [precision]) +``` + +### Parameters + +| parameter | type | note | +|:--------------:|:------:|-------| +| task
**(required)** | function
string | The task function or the string alias of a registered task. | +| precision | number | Default: `1000` on Node v0.10, `0` on Node v0.12+. Detailed in Timestamp precision][timestamp-precision-section] section below. | + +### Returns + +A timestamp (in milliseconds), matching the last completion time of the task. If the task has not been run or has failed, returns `undefined`. + +To avoid an invalid state being cached, the returned value will be `undefined` if a task errors. + +### Errors + +When called with a value other than a string or function, throws an error with the message, "Only functions can check lastRun". + +When called on a non-extensible function and Node is missing WeakMap, throws an error with the message, "Only extensible functions can check lastRun". + +## Timestamp precision + +While there are sensible defaults for the precision of timestamps, they can be rounded using the `precision` parameter. Useful if your file system or Node version has a lossy precision on file time attributes. + +* `lastRun(someTask)` returns 1426000001111 +* `lastRun(someTask, 100)` returns 1426000001100 +* `lastRun(someTask, 1000)` returns 1426000001000 + +A file's [mtime stat][fs-stats-concepts] precision may vary depending on the node version and/or the file system used: + + +| platform | precision | +|:-----------:|:------------:| +| Node v0.10 | 1000ms | +| Node v0.12+ | 1ms | +| FAT32 file system | 2000ms | +| HFS+ or Ext3 file systems | 1000ms | +| NTFS using Node v0.10 | 1s | +| NTFS using Node 0.12+ | 100ms | +| Ext4 using Node v0.10 | 1000ms | +| Ext4 using Node 0.12+ | 1ms | + + +[timestamp-precision-section]: #timestamp-precision +[fs-stats-concepts]: concepts.md#file-system-stats diff --git a/docs/api/lastRun.md b/docs/api/lastRun.md deleted file mode 100644 index 2be1e668f..000000000 --- a/docs/api/lastRun.md +++ /dev/null @@ -1,43 +0,0 @@ - - -# `gulp.lastRun(taskName, [timeResolution])` - -Returns the timestamp of the last time the task ran successfully. The time -will be the time the task started. Returns `undefined` if the task has -not run yet. - -## taskName - -Type: `String` - -The name of the registered task or of a function. - -## timeResolution - -Type: `Number`. - -Default: `1000` on node v0.10, `0` on node v0.12 (and iojs v1.5). - -Set the time resolution of the returned timestamps. Assuming -the task named "someTask" ran at `1426000004321`: - -- `gulp.lastRun('someTask', 1000)` would return `1426000004000`. -- `gulp.lastRun('someTask', 100)` would return `1426000004300`. - -`timeResolution` allows you to compare a run time to a file [mtime stat][fs stats] -attribute. This attribute time resolution may vary depending of the node version -and the file system used: - -- on node v0.10, a file [mtime stat][fs stats] time resolution of any files will be 1s at best; -- on node v0.12 and iojs v1.5, 1ms at best; -- for files on FAT32, the mtime time resolution is 2s; -- on HFS+ and Ext3, 1s; -- on NTFS, 1s on node v0.10, 100ms on node 0.12; -- on Ext4, 1s on node v0.10, 1ms on node 0.12. - -[fs stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats From dc3cba7991d94d66cc3a8b111399045a2e7d02c2 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:18:28 -0700 Subject: [PATCH 164/216] Docs: Update parallel() documentation --- docs/api/parallel.md | 113 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 20 deletions(-) diff --git a/docs/api/parallel.md b/docs/api/parallel.md index 7d15efbbe..4229c8456 100644 --- a/docs/api/parallel.md +++ b/docs/api/parallel.md @@ -5,36 +5,109 @@ hide_title: true sidebar_label: parallel() --> -# `gulp.parallel(...tasks)` +# parallel() -Takes a number of task names or functions and returns a function of the composed -tasks or functions. +Combines task functions and/or composed operations into larger operations that will be executed simultaneously. The composed operations from `series()` and `parallel()` can be nested to any depth. -When using task names, the task should already be registered. +## Usage -When the returned function is executed, the tasks or functions will be executed -in parallel, all being executed at the same time. If an error occurs, -all execution will complete. +```js +const { parallel } = require('gulp'); + +function javascript(cb) { + // body omitted + cb(); +} + +function css(cb) { + // body omitted + cb(); +} + +exports.build = parallel(javascript, css); +``` + +## Signature ```js -gulp.task('one', function(done) { - // do stuff - done(); +parallel(...tasks) +``` + +### Parameters + +| parameter | type | note | +|:--------------:|:------:|-------| +| tasks
**(required)** | function
string | Any number of task functions can be passed as individual arguments. Strings can be used if you've registered tasks previously, but this is not recommended. | + +### Returns + +A composed operation to be registered as a task or nested within other `series` and/or `parallel` compositions. + +When the composed operation is executed, all tasks will be run at maximum concurrency. If an error occurs in one task, other tasks nondeterministically may or may not complete. + +### Errors + +When no tasks are passed, throws an error with the message, "One or more tasks should be combined using series or parallel". + +When invalid tasks or unregistered tasks are passed, throws an error with the message, "Task never defined". + +## Forward references + +A forward reference is when you compose tasks, using string references, that haven't been registered yet. This was a common practice in older versions, but this feature was removed to achieve faster task runtime and promote the use of named functions. + +In newer versions, you'll get an error, with the message "Task never defined", if you try to use forward references. You may experience this when trying to use `exports` for task registration _and_ composing tasks by string. In this situation, use named functions instead of string references. + +During migration, you may need the [forward reference registry][undertaker-forward-reference-external]. This will add an extra closure to every task reference and dramatically slow down your build. **Don't rely on this fix for very long**. + +## Avoid duplicating tasks + +When a composed operation is run, each task will be executed every time it was supplied. + +A `clean` task referenced in two different compositions would be run twice and lead to undesired results. Instead, refactor the `clean` task to be specified in the final composition. + +If you have code like this: +```js +// This is INCORRECT +const { series, parallel } = require('gulp'); + +const clean = function(cb) { + // body omitted + cb(); +}; + +const css = series(clean, function(cb) { + // body omitted + cb(); }); -gulp.task('two', function(done) { - // do stuff - done(); +const javascript = series(clean, function(cb) { + // body omitted + cb(); }); -gulp.task('default', gulp.parallel('one', 'two', function(done) { - // do more stuff - done(); -})); +exports.build = parallel(css, javascript); ``` -## tasks -Type: `Array`, `String` or `Function` +Migrate to this: +```js +const { series, parallel } = require('gulp'); + +function clean(cb) { + // body omitted + cb(); +} -A task name, a function or an array of either. +function css(cb) { + // body omitted + cb(); +} + +function javascript(cb) { + // body omitted + cb(); +} + +exports.build = series(clean, parallel(css, javascript)); +``` +[undertaker-forward-reference-external]: https://github.com/gulpjs/undertaker-forward-reference From d6804874b14a8f55ac11a87004ac4bad7814e0b8 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:19:12 -0700 Subject: [PATCH 165/216] Docs: Update registry() documentation --- docs/api/registry.md | 81 ++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/docs/api/registry.md b/docs/api/registry.md index 2b04e9f8c..cd12bc400 100644 --- a/docs/api/registry.md +++ b/docs/api/registry.md @@ -5,60 +5,59 @@ hide_title: true sidebar_label: registry() --> -# `gulp.registry([registry])` +# registry() -Get or set the underlying task registry. Inherited from [undertaker]; see the undertaker documention on [registries](https://github.com/phated/undertaker#registryregistryinstance). Using this, you can change registries that enhance gulp in different ways. Utilizing a custom registry has at least three use cases: -- [Sharing tasks](https://github.com/phated/undertaker#sharing-tasks) -- [Sharing functionality](https://github.com/phated/undertaker#sharing-functionalities) (e.g. you could override the task prototype to add some additional logging, bind task metadata or include some config settings.) -- Handling other behavior that hooks into the registry lifecycle (see [gulp-hub](https://github.com/frankwallis/gulp-hub) for an example) +Allows custom registries to be plugged into the task system, which can provide shared tasks or augmented functionality. -To build your own custom registry see the [undertaker documentation on custom registries](https://github.com/phated/undertaker#custom-registries). +**Note:** Only tasks registered with `task()` will be provided to the custom registry. The task functions passed directly to `series()` or `parallel()` will not be provided - if you need to customize the registry behavior, compose tasks with string references. -## registry +When assigning a new registry, each task from the current registry will be transferred and the current registry will be replaced with the new one. This allows for adding multiple custom registries in sequential order. -A registry instance. When passed in, the tasks from the current registry will be transferred to the new registry and then current registry will be replaced with the new registry. +See [Creating Custom Registries][creating-custom-registries] for details. -## Example - -This example shows how to create and use a simple custom registry to add tasks. +## Usage ```js -//gulpfile.js -var gulp = require('gulp'); +const { registry, task, series } = require('gulp'); +const FwdRef = require('undertaker-forward-reference'); -var companyTasks = require('./myCompanyTasksRegistry.js'); +registry(FwdRef()); -gulp.registry(companyTasks); +task('default', series('forward-ref')); -gulp.task('one', gulp.parallel('someCompanyTask', function(done) { - console.log('in task one'); - done(); -})); +task('forward-ref', function(cb) { + // body omitted + cb(); +}); ``` +## Signature + ```js -//myCompanyTasksRegistry.js -var util = require('util'); - -var DefaultRegistry = require('undertaker-registry'); - -function MyCompanyTasksRegistry() { - DefaultRegistry.call(this); -} -util.inherits(MyCompanyTasksRegistry, DefaultRegistry); - -MyCompanyTasksRegistry.prototype.init = function(gulp) { - gulp.task('clean', function(done) { - done(); - }); - gulp.task('someCompanyTask', function(done) { - console.log('performing some company task.'); - done(); - }); -}; - -module.exports = new MyCompanyTasksRegistry(); +registry([registryInstance]) ``` -[undertaker]: https://github.com/gulpjs/undertaker +### Parameters + +| parameter | type | note | +|:--------------:|:-----:|--------| +| registryInstance | object | An instance - not the class - of a custom registry. | + +### Returns + +If a `registryInstance` is passed, nothing will be returned. If no arguments are passed, returns the current registry instance. + +### Errors + +When a constructor (instead of an instance) is passed as `registryInstance`, throws an error with the message, "Custom registries must be instantiated, but it looks like you passed a constructor". + +When a registry without a `get` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `get` function". + +When a registry without a `set` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `set` function". + +When a registry without an `init` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `init` function" + +When a registry without a `tasks` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `tasks` function". + +[creating-custom-registries]: LINK_NEEDED From 4169cb6de2d1bab681aed6f38c00e441bb80578a Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:19:31 -0700 Subject: [PATCH 166/216] Docs: Update series() documentation --- docs/api/series.md | 114 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 94 insertions(+), 20 deletions(-) diff --git a/docs/api/series.md b/docs/api/series.md index 9499ed42f..fd2c4987a 100644 --- a/docs/api/series.md +++ b/docs/api/series.md @@ -5,35 +5,109 @@ hide_title: true sidebar_label: series() --> -# `gulp.series(...tasks)` +# series() -Takes a number of task names or functions and returns a function of the composed -tasks or functions. +Combines task functions and/or composed operations into larger operations that will be executed one after another, in sequential order. The composed operations from `series()` and `parallel()` can be nested to any depth. -When using task names, the task should already be registered. +## Usage -When the returned function is executed, the tasks or functions will be executed -in series, each waiting for the prior to finish. If an error occurs, -execution will stop. +```js +const { series } = require('gulp'); + +function javascript(cb) { + // body omitted + cb(); +} + +function css(cb) { + // body omitted + cb(); +} + +exports.build = series(javascript, css); +``` + +## Signature + +```js +series(...tasks) +``` + +### Parameters + +| parameter | type | note | +|:--------------:|:------:|-------| +| tasks
**(required)** | function
string | Any number of task functions can be passed as individual arguments. Strings can be used if you've registered tasks previously, but this is not recommended. | + +### Returns + +A composed operation to be registered as a task or nested within other `series` and/or `parallel` compositions. + +When the composed operation is executed, all tasks will be run sequentially. If an error occurs in one task, no subsequent tasks will be run. + +### Errors + +When no tasks are passed, throws an error with the message, "One or more tasks should be combined using series or parallel". +When invalid tasks or unregistered tasks are passed, throws an error with the message, "Task never defined". + +## Forward references + +A forward reference is when you compose tasks, using string references, that haven't been registered yet. This was a common practice in older versions, but this feature was removed to achieve faster task runtime and promote the use of named functions. + +In newer versions, you'll get an error, with the message "Task never defined", if you try to use forward references. You may experience this when trying to use `exports` for your task registration *and* composing tasks by string. In this situation, use named functions instead of string references. + +During migration, you may need to use the [forward reference registry][undertaker-forward-reference-external]. This will add an extra closure to every task reference and dramatically slow down your build. **Don't rely on this fix for very long**. + +## Avoid duplicating tasks + +When a composed operation is run, each task will be executed every time it was supplied. + +A `clean` task referenced in two different compositions would be run twice and lead to undesired results. Instead, refactor the `clean` task to be specified in the final composition. + +If you have code like this: ```js -gulp.task('one', function(done) { - // do stuff - done(); +// This is INCORRECT +const { series, parallel } = require('gulp'); + +const clean = function(cb) { + // body omitted + cb(); +}; + +const css = series(clean, function(cb) { + // body omitted + cb(); }); -gulp.task('two', function(done) { - // do stuff - done(); +const javascript = series(clean, function(cb) { + // body omitted + cb(); }); -gulp.task('default', gulp.series('one', 'two', function(done) { - // do more stuff - done(); -})); +exports.build = parallel(css, javascript); ``` -## tasks -Type: `Array`, `String` or `Function` +Migrate to this: +```js +const { series, parallel } = require('gulp'); + +function clean(cb) { + // body omitted + cb(); +} + +function css(cb) { + // body omitted + cb(); +} + +function javascript(cb) { + // body omitted + cb(); +} + +exports.build = series(clean, parallel(css, javascript)); +``` -A task name, a function or an array of either. +[undertaker-forward-reference-external]: https://github.com/gulpjs/undertaker-forward-reference From d95b45733fe9dc2ce5f96d88e12b1172f22690e1 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:19:58 -0700 Subject: [PATCH 167/216] Docs: Update src() documentation --- docs/api/src.md | 210 ++++++++++++++++++++++-------------------------- 1 file changed, 97 insertions(+), 113 deletions(-) diff --git a/docs/api/src.md b/docs/api/src.md index 307d9ab6c..e66a48320 100644 --- a/docs/api/src.md +++ b/docs/api/src.md @@ -5,135 +5,119 @@ hide_title: true sidebar_label: src() --> -# `gulp.src(globs[, options])` +# src() -Emits files matching provided glob or array of globs. -Returns a [stream] of [Vinyl files] that can be [piped] to plugins. +Creates a stream for reading [Vinyl][vinyl-concepts] objects from the file system. -```javascript -gulp.src('client/templates/*.pug') - .pipe(pug()) - .pipe(minify()) - .pipe(gulp.dest('build/minified_templates')); -``` - -`glob` refers to [node-glob syntax][node-glob] or it can be a direct file path. - -## globs -Type: `String` or `Array` - -Glob or array of globs to read. Globs use [node-glob syntax] except that negation is fully supported. +**Note:** Any UTF-8 BOMs will be removed from UTF-8 files read by `src()`, unless disabled using the `removeBOM` option. -A glob that begins with `!` excludes matching files from the glob results up to that point. For example, consider this directory structure: - - client/ - a.js - bob.js - bad.js - -The following expression matches `a.js` and `bad.js`: - - gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js']) +## Usage +```javascript +const { src, dest } = require('gulp'); -Note that globs are evaluated in order, which means this is possible: +function copy() { + return src('input/*.js') + .pipe(dest('output/')); +} -```js -// exclude every JS file that starts with a b except bad.js -gulp.src(['*.js', '!b*.js', 'bad.js']) +exports.copy = copy; ``` -**Note:** glob symlink following behavior is opt-in and you must specify -`follow: true` in the options object that is passed to [node-glob]. - -## options -Type: `Object` - -Options to pass to [node-glob] through [glob-stream]. - -gulp adds some additional options in addition to the -[options supported by node-glob][node-glob documentation] and [glob-stream]: - -### options.cwd - -The working directory the folder is relative to. - -Type: `String` - -Default: `process.cwd()` - -### options.buffer -Type: `Boolean` - -Default: `true` - -Setting this to `false` will return `file.contents` as a stream and not -buffer files. This is useful when working with large files. - -**Note:** Plugins might not implement support for streams. - -### options.read -Type: `Boolean` - -Default: `true` - -Setting this to `false` will return `file.contents` as null and not read -the file at all. - -### options.base -Type: `String` - -Default: everything before a glob starts (see [glob-parent]) - -E.g., consider `somefile.js` in `client/js/somedir`: +## Signature ```js -// Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/` -gulp.src('client/js/**/*.js') - .pipe(minify()) - .pipe(gulp.dest('build')); // Writes 'build/somedir/somefile.js' - -gulp.src('client/js/**/*.js', { base: 'client' }) - .pipe(minify()) - .pipe(gulp.dest('build')); // Writes 'build/js/somedir/somefile.js' +src(globs, [options]) ``` -### options.since -Type: `Date` or `Number` - -Setting this to a Date or a time stamp will discard any file that have not been -modified since the time specified. - -### options.passthrough -Type: `Boolean` - -Default: `false` - -If true, it will create a duplex stream which passes items through and -emits globbed files. - -### options.allowEmpty -Type: `Boolean` - -Default: `false` +### Parameters + +| parameter | type | note | +|:--------------:|:------:|-------| +| globs | string
array | [Globs][globs-concepts] to watch on the file system. | +| options | object | Detailed in [Options][options-section] below. | + +### Returns + +A stream that can be used at the beginning or in the middle of a pipeline to add files based on the given globs. + +### Errors + +When the `globs` argument can only match one file (such as `foo/bar.js`) and no match is found, throws an error with the message, "File not found with singular glob". To suppress this error, set the `allowEmpty` option to `true`. + +When an invalid glob is given in `globs`, throws an error with the message, "Invalid glob argument". + +### Options + +**For options that accept a function, the passed function will be called with each Vinyl object and must return a value of another listed type.** + + +| name | type | default | note | +|:--------:|:------:|------------|--------| +| buffer | boolean
function | true | When true, file contents are buffered into memory. If false, the Vinyl object's `contents` property will be a paused stream. Contents of large files may not be able to be buffered.
**Note:** Plugins may not implement support for streaming contents. | +| read | boolean
function | true | If false, files will be not be read and their Vinyl objects won't be writable to disk via `.dest()`. | +| since | date
timestamp
function | | When set, only creates Vinyl objects for files that have been modified since the specified time. | +| removeBOM | boolean
function | true | When true, removes the BOM from UTF-8 encoded files. If false, ignores a BOM.. | +| sourcemaps | boolean
function | false | If true, enables [sourcemaps][sourcemaps-section] support on Vinyl objects created. Loads inline sourcemaps and resolves external sourcemap links. | +| resolveSymlinks | boolean
function | true | When true, recursively resolves symbolic links to their targets. If false, preserves the symbolic links and sets the Vinyl object's `symlink` property to the original file's path. | +| cwd | string | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| base | string | | Explicitly set the `base` property on created Vinyl objects. Detailed in [API Concepts][glob-base-concepts].
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| cwdbase | boolean | false | If true, `cwd` and `base` options should be aligned.
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| root | string | | The root path that `globs` are resolved against.
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| allowEmpty | boolean | false | When false, `globs` which can only match one file (such as `foo/bar.js`) causes an error to be thrown if they don't find a match. If true, suppresses glob failures.
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| uniqueBy | string
function | `'path'` | Remove duplicates from the stream by comparing the string property name or the result of the function.
**Note:** When using a function, the function receives the streamed data (objects containing `cwd`, `base`, `path` properties). | +| dot | boolean | false | If true, compare globs against dot files, like `.gitignore`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| silent | boolean | true | When true, suppresses warnings from printing on `stderr`.
**Note:** This option is passed directly to [node-glob][node-glob-external] but defaulted to `true` instead of `false`. | +| mark | boolean | false | If true, a `/` character will be appended to directory matches. Generally not needed because paths are normalized within the pipeline.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nosort | boolean | false | If true, disables sorting the glob results.
_This option is passed directly to [node-glob][node-glob-external]._ | +| stat | boolean | false | If true, `fs.stat()` is called on all results. This adds extra overhead and generally should not be used.
_This option is passed directly to [node-glob][node-glob-external]._ | +| strict | boolean | false | If true, an error will be thrown if an unexpected problem is encountered while attempting to read a directory.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nounique | boolean | false | When false, prevents duplicate files in the result set.
_This option is passed directly to [node-glob][node-glob-external]._ | +| debug | boolean | false | If true, debugging information will be logged to the command line.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nobrace | boolean | false | If true, avoids expanding brace sets - e.g. `{a,b}` or `{1..3}`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| noglobstar | boolean | false | If true, treats double-star glob character as single-star glob character.
_This option is passed directly to [node-glob][node-glob-external]._ | +| noext | boolean | false | If true, avoids matching [extglob][extglob-docs] patterns - e.g. `+(ab)`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nocase | boolean | false | If true, performs a case-insensitive match.
**Note:** On case-insensitive file systems, non-magic patterns will match by default.
_This option is passed directly to [node-glob][node-glob-external]._ | +| matchBase | boolean | false | If true and globs don't contain any `/` characters, traverses all directories and matches that glob - e.g. `*.js` would be treated as equivalent to `**/*.js`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nodir | boolean | false | If true, only matches files, not directories.
**Note:** To match only directories, end your glob with a `/`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| ignore | string
array | | Globs to exclude from matches. This option is combined with negated `globs`.
**Note:** These globs are always matched against dot files, regardless of any other settings.
_This option is passed directly to [node-glob][node-glob-external]._ | +| follow | boolean | false | If true, symlinked directories will be traversed when expanding `**` globs.
**Note:** This can cause problems with cyclical links.
_This option is passed directly to [node-glob][node-glob-external]._ | +| realpath | boolean | false | If true, `fs.realpath()` is called on all results. This may result in dangling links.
_This option is passed directly to [node-glob][node-glob-external]._ | +| cache | object | | A previously generated cache object - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | +| statCache | object | | A previously generated cache of `fs.Stat` results - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | +| symlinks | object | | A previously generated cache of symbolic links - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nocomment | boolean | false | When false, treat a `#` character at the start of a glob as a comment.
_This option is passed directly to [node-glob][node-glob-external]._ | + +## Sourcemaps + +Sourcemap support is built directly into `src()` and `dest()`, but is disabled by default. Enable it to produce inline or external sourcemaps. + +Inline sourcemaps: +```js +const { src, dest } = require('gulp'); +const uglify = require('gulp-uglify'); -When true, will allow singular globs to fail to match. Otherwise, globs which are only supposed to match one file (such as `./foo/bar.js`) will cause an error to be thrown if they don't match. +src('input/**/*.js', { sourcemaps: true }) + .pipe(uglify()) + .pipe(dest('output/', { sourcemaps: true })); +``` +External sourcemaps: ```js -// Emits an error if app/scripts.js doesn't exist -gulp.src('app/scripts.js') - .pipe(...); +const { src, dest } = require('gulp'); +const uglify = require('gulp-uglify'); -// Won't emit an error -gulp.src('app/scripts.js', { allowEmpty: true }) - .pipe(...); +src('input/**/*.js', { sourcemaps: true }) + .pipe(uglify()) + .pipe(dest('output/', { sourcemaps: '.' })); ``` -[glob-stream]: https://github.com/gulpjs/glob-stream -[glob-parent]: https://github.com/es128/glob-parent -[node-glob documentation]: https://github.com/isaacs/node-glob#options -[node-glob]: https://github.com/isaacs/node-glob -[piped]: http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options -[stream]: http://nodejs.org/api/stream.html -[Vinyl files]: https://github.com/gulpjs/vinyl-fs +[sourcemaps-section]: #sourcemaps +[options-section]: #options +[vinyl-concepts]: concepts.md#vinyl +[glob-base-concepts]: concepts.md#glob-base +[globs-concepts]: concepts.md#globs +[extglob-docs]: LINK_NEEDED +[node-glob-external]: https://github.com/isaacs/node-glob +[glob-stream-external]: https://github.com/gulpjs/glob-stream From d580efa2b696bbcd7ee4f973eb2f56fe2a996f66 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:20:18 -0700 Subject: [PATCH 168/216] Docs: Update symlink() documentation --- docs/api/symlink.md | 89 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/docs/api/symlink.md b/docs/api/symlink.md index f8b00f0c3..7fd1e790e 100644 --- a/docs/api/symlink.md +++ b/docs/api/symlink.md @@ -5,31 +5,84 @@ hide_title: true sidebar_label: symlink() --> -# `gulp.symlink(folder[, options])` +# symlink() -Functions exactly like `gulp.dest`, but will create symlinks instead of copying -a directory. +Creates a stream for linking [Vinyl][vinyl-concepts] objects to the file system. -## folder -Type: `String` or `Function` +## Usage +' +```js +const { src, symlink } = require('gulp'); -A folder path or a function that receives in a file and returns a folder path. +function link() { + return src('input/*.js') + .pipe(symlink('output/')); +} -## options -Type: `Object` +exports.link = link; +``` -### options.cwd -Type: `String` +## Signature -Default: `process.cwd()` +```js +symlink(directory, [options]) +``` -`cwd` for the output folder, only has an effect if provided output folder is -relative. +### Parameters -### options.dirMode -Type: `String` or `Number` +| parameter | type | note | +|:--------------:|:-----:|--------| +| directory
**(required)** | string
function | The path of the output directory where symbolic links will be created. If a function is used, the function will be called with each Vinyl object and must return a string directory path. | +| options | object | Detailed in [Options][options-section] below. | -Default: Default is the process mode. +### Returns -Octal permission specifying the mode the directory should be created with: e.g. -`"0755"`, `0755` or `493` (`0755` in base 10). +A stream that can be used in the middle or at the end of a pipeline to create symbolic links on the file system. +Whenever a Vinyl object is passed through the stream, it creates a symbolic link to the original file on the file system at the given directory. + +Whenever a symbolic link is created on the file system, the Vinyl object will be modified. +* The `cwd`, `base`, and `path` properties will be updated to match the created symbolic link. +* The `stat` property will be updated to match the symbolic link on the file system. +* The `contents` property will be set to `null`. +* The `symlink` property will be added or replaced with original path. + +**Note:** On Windows, directory links are created using junctions by default. The `useJunctions` option disables this behavior. + + +### Errors + +When `directory` is an empty string, throws an error with the message, "Invalid symlink() folder argument. Please specify a non-empty string or a function." + +When `directory` is not a string or function, throws an error with the message, "Invalid symlink() folder argument. Please specify a non-empty string or a function." + +When `directory` is a function that returns an empty string or `undefined`, emits an error with the message, "Invalid output folder". + +### Options + +**For options that accept a function, the passed function will be called with each Vinyl object and must return a value of another listed type.** + +| name | type | default | note | +|:-------:|:------:|-----------|-------| +| cwd | string
function | `process.cwd()` |The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `directory` with `path.join()`. | +| dirMode | number
function | | The mode used when creating directories. If not set, the process' mode will be used. | +| overwrite | boolean
function | true | When true, overwrites existing files with the same path. | +| relativeSymlinks | boolean
function | false | When false, any symbolic links created will be absolute.
**Note**: Ignored if a junction is being created, as they must be absolute. | +| useJunctions | boolean
function | true | This option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in [Symbolic links on Windows][symbolic-links-section] below. | + +## Symbolic links on Windows + +When creating symbolic links on Windows, a `type` argument is passed to Node's `fs.symlink()` method which specifies the type of target being linked. The link type is set to: +`'file'` when the target is a regular file +`'junction'` when the target is a directory +`'dir'` when the target is a directory and the user disables the `useJunctions` option + + +If you try to create a dangling (pointing to a non-existent target) link, the link type can't be determined automatically. In these cases, behavior will vary depending on whether the dangling link is being created via `symlink()` or via `dest()`. + +For dangling links created via `symlink()`, the incoming Vinyl object represents the target, so its stats will determine the desired link type. If `isDirectory()` returns false then a `'file'` link is created, otherwise a `'junction'` or a `'dir'` link is created depending on the value of the `useJunctions` option. + +For dangling links created via `dest()`, the incoming Vinyl object represents the link - typically loaded from disk via `src(..., { resolveSymlinks: false })`. In this case, the link type can't be reasonably determined and defaults to using `'file'`. This may cause unexpected behavior when creating a dangling link to a directory. **Avoid this scenario.** + +[options-section]: #options +[symbolic-links-section]: #symbolic-links-on-windows +[vinyl-concepts]: concepts.md#vinyl From b636a9c9600137e158f0cdb9a923d526bfad6f80 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:20:46 -0700 Subject: [PATCH 169/216] Docs: Update task() documentation --- docs/api/task.md | 211 ++++++++++++++--------------------------------- 1 file changed, 63 insertions(+), 148 deletions(-) diff --git a/docs/api/task.md b/docs/api/task.md index b644de602..698cf8437 100644 --- a/docs/api/task.md +++ b/docs/api/task.md @@ -5,190 +5,105 @@ hide_title: true sidebar_label: task() --> -# `gulp.task([name,] fn)` +# task() -Define a task exposed to gulp-cli, `gulp.series`, `gulp.parallel` and -`gulp.lastRun`; inherited from [undertaker]. +**Reminder**: This API isn't the recommended pattern anymore - export your tasks. -```js -gulp.task(function someTask() { - // Do stuff -}); -``` +Defines a task within the task system. The task can then be accessed from the command line and the `series()`, `parallel()`, and `lastRun()` APIs. -Or get a task that has been registered. +## Usage +Register a named function as a task: ```js -// someTask will be the registered task function -var someTask = gulp.task('someTask'); -``` - -## name -Type: `String` - -If the name is not provided, the task will be named after the function -`name` or `displayName` property. The name argument is required if the -`name` and `displayName` properties of `fn` are empty. +const { task } = require('gulp'); -Since the task can be run from the command line, you should avoid using -spaces in task names. - -## fn - -The function that performs the task's operations. Generally it takes this form: - -```js -function someTask() { - return gulp.src(['some/glob/**/*.ext']).pipe(someplugin()); +function build(cb) { + // body omitted + cb(); } -someTask.description = 'Does something'; -gulp.task(someTask) +task(build); ``` -Gulp tasks are asynchronous and Gulp uses [async-done] to wait for the task's -completion. Tasks are called with a callback parameter to call to signal -completion. Alternatively, Task can return a stream, a promise, a child process -or a RxJS observable to signal the end of the task. - -**Warning:** Sync tasks are not supported and your function will never complete -if the one of the above strategies is not used to signal completion. However, -thrown errors will be caught by Gulp. - -## fn properties - -### fn.name - -`gulp.task` names the task after the function `name` property -if the optional `name` parameter of `gulp.task` is not provided. - -**Note:** [Function.name] is not writable; it cannot be set or edited. If -you need to assign a function name or use characters that aren't allowed -in function names, use the `displayName` property. -It will be empty for anonymous functions: - +Register an anonymous function as a task: ```js -function foo() {}; -foo.name === 'foo' // true - -var bar = function() {}; -bar.name === '' // true +const { task } = require('gulp'); -bar.name = 'bar' -bar.name === '' // true +task('build', function(cb) { + // body omitted + cb(); +}); ``` -### fn.displayName - -`gulp.task` names the task after the function `displayName` property -if function is anonymous and the optional `name` parameter of `gulp.task` -is not provided. - -### fn.description - -gulp-cli prints this description alongside the task name when listing tasks: - +Retrieve a task that has been registered previously: ```js -var gulp = require('gulp'); - -function test(done){ - done(); -} -test.description = 'I do nothing'; +const { task } = require('gulp'); -gulp.task(test); -``` +task('build', function(cb) { + // body omitted + cb(); +}); -```sh -$> gulp --tasks -[12:00:02] Tasks for ~/Documents/some-project/gulpfile.js -[12:00:02] └── test I do nothing +const build = task('build'); ``` -## Async support - -### Accept a callback +## Signature ```js -var del = require('del'); +task([taskName], taskFunction) +``` -gulp.task('clean', function(done) { - del(['.build/'], done); -}); +### Parameters -// use an async result in a pipe -gulp.task('somename', function(cb) { - getFilesAsync(function(err, res) { - if (err) return cb(err); - var stream = gulp.src(res) - .pipe(minify()) - .pipe(gulp.dest('build')) - .on('end', cb); - }); -}); -``` +If the `taskName` is not provided, the task will be referenced by the `name` property of a named function or a user-defined `displayName` property. The `taskName` parameter must be used for anonymous functions missing a `displayName` property. -The callback accepts an optional `Error` object. If it receives an error, -the task will fail. +Since any registered task can be run from the command line, avoid using spaces in task names. -### Return a stream +| parameter | type | note | +|:--------------:|:------:|-------| +| taskName | string | An alias for the task function within the the task system. Not needed when using named functions for `taskFunction`. | +| taskFunction
**(required)** | function | A [task function][tasks-concepts] or composed task - generated by `series()` and `parallel()`. Ideally a named function. [Task metadata][task-metadata-section] can be attached to provide extra information to the command line. | -```js -gulp.task('somename', function() { - return gulp.src('client/**/*.js') - .pipe(minify()) - .pipe(gulp.dest('build')); -}); -``` +### Returns -### Return a promise +When registering a task, nothing is returned. -```js -var Promise = require('promise'); -var del = require('del'); - -gulp.task('clean', function() { - return new Promise(function (resolve, reject) { - del(['.build/'], function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); -}); -``` +When retrieving a task, a wrapped task (not the original function) registered as `taskName` will be returned. The wrapped task has an `unwrap()` method that will return the original function. -or: -```js -var promisedDel = require('promised-del'); +### Errors -gulp.task('clean', function() { - return promisedDel(['.build/']); -}); -``` +When registering a task where `taskName` is missing and `taskFunction` is anonymous, will throw an error with the message, "Task name must be specified". + +## Task metadata -### Return a child process +| property | type | note | +|:--------------:|:------:|-------| +| name | string | A special property of named functions. Used to register the task.
**Note:** [`name`][function-name-external] is not writable; it cannot be set or changed. | +| displayName | string | When attached to a `taskFunction` creates an alias for the task. If using characters that aren't allowed in function names, use this property. | +| description | string | When attached to a `taskFunction` provides a description to be printed by the command line when listing tasks. | +| flags | object | When attached to a `taskFunction` provides flags to be printed by the command line when listing tasks. The keys of the object represent the flags and the values are their descriptions. | ```js -gulp.task('clean', function() { - return spawn('rm', ['-rf', path.join(__dirname, 'build')]); -}); +const { task } = require('gulp'); -``` +const clean = function(cb) { + // body omitted + cb(); +}; +clean.displayName = 'clean:all'; -### Return a [RxJS] observable +task(clean); -```js -var Observable = require('rx').Observable; +function build(cb) { + // body omitted + cb(); +} +build.description = 'Build the project'; +build.flags = { '-e': 'An example flag' }; -gulp.task('sometask', function() { - return Observable.return(42); -}); +task(build); ``` -[Function.name]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name -[RxJS]: https://www.npmjs.com/package/rx -[async-done]: https://www.npmjs.com/package/async-done -[undertaker]: https://github.com/gulpjs/undertaker +[task-metadata-section]: #task-metadata +[task-concepts]: concepts.md#tasks +[function-name-external]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name From ebb9818461724d96d80a627925ea133684d43a00 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:21:07 -0700 Subject: [PATCH 170/216] Docs: Update tree() documentation --- docs/api/tree.md | 285 +++++++++++++++++++++++++---------------------- 1 file changed, 154 insertions(+), 131 deletions(-) diff --git a/docs/api/tree.md b/docs/api/tree.md index e7e43f1bb..dc6464a8a 100644 --- a/docs/api/tree.md +++ b/docs/api/tree.md @@ -5,151 +5,174 @@ hide_title: true sidebar_label: tree() --> -# `gulp.tree(options)` +# tree() -Returns the tree of tasks. Inherited from [undertaker]. See the [undertaker docs for this function](https://github.com/phated/undertaker#treeoptions--object). +Fetches the current task dependency tree - in the rare case that it is needed. -## options -Type: `Object` +Generally, `tree()` won't be used by gulp consumers, but it is exposed so the CLI can show the dependency graph of the tasks defined in a gulpfile. -Options to pass to [undertaker]. +## Usage -### options.deep -Type: `Boolean` +Example gulpfile: +```js -Default: `false` +const { series, parallel } = require('gulp'); -If set to `true` whole tree should be returned. +function one(cb) { + // body omitted + cb(); +} -## Example gulpfile +function two(cb) { + // body omitted + cb(); +} -```js -gulp.task('one', function(done) { - // do stuff - done(); -}); - -gulp.task('two', function(done) { - // do stuff - done(); -}); - -gulp.task('three', function(done) { - // do stuff - done(); -}); - -gulp.task('four', gulp.series('one', 'two')); - -gulp.task('five', - gulp.series('four', - gulp.parallel('three', function(done) { - // do more stuff - done(); - }) - ) +function three(cb) { + // body omitted + cb(); +} + +const four = series(one, two); + +const five = series(four, + parallel(three, function(cb) { + // Body omitted + cb(); + }) ); + +module.exports = { one, two, three, four, five }; +``` + +Output for `tree()`: +```js +{ + label: 'Tasks', + nodes: [ 'one', 'two', 'three', 'four', 'five' ] +} ``` -## Example tree output +Output for `tree({ deep: true })`: ```js -gulp.tree() - -// output: [ 'one', 'two', 'three', 'four', 'five' ] - -gulp.tree({ deep: true }) - -/*output: [ - { - "label":"one", - "type":"task", - "nodes":[] - }, - { - "label":"two", - "type":"task", - "nodes":[] - }, - { - "label":"three", - "type":"task", - "nodes":[] - }, - { - "label":"four", - "type":"task", - "nodes":[ - { - "label":"", - "type":"function", - "nodes":[ - { - "label":"one", - "type":"task", - "nodes":[] - }, - { - "label":"two", - "type":"task", - "nodes":[] - } - ] - } +{ + label: "Tasks", + nodes: [ + { + label: "one", + type: "task", + nodes: [] + }, + { + label: "two", + type: "task", + nodes: [] + }, + { + label: "three", + type: "task", + nodes: [] + }, + { + label: "four", + type: "task", + nodes: [ + { + label: "", + type: "function", + branch: true, + nodes: [ + { + label: "one", + type: "function", + nodes: [] + }, + { + label: "two", + type: "function", + nodes: [] + } + ] + } ] - }, - { - "label":"five", - "type":"task", - "nodes":[ - { - "label":"", - "type":"function", - "nodes":[ - { - "label":"four", - "type":"task", - "nodes":[ - { - "label":"", - "type":"function", - "nodes":[ - { - "label":"one", - "type":"task", - "nodes":[] - }, - { - "label":"two", - "type":"task", - "nodes":[] - } - ] - } - ] - }, - { - "label":"", - "type":"function", - "nodes":[ - { - "label":"three", - "type":"task", - "nodes":[] - }, - { - "label":"", - "type":"function", - "nodes":[] - } - ] - } - ] - } + }, + { + label: "five", + type: "task", + nodes: [ + { + label: "", + type: "function", + branch: true, + nodes: [ + { + label: "", + type: "function", + branch: true, + nodes: [ + { + label: "one", + type: "function", + nodes: [] + }, + { + label: "two", + type: "function", + nodes: [] + } + ] + }, + { + label: "", + type: "function", + branch: true, + nodes: [ + { + label: "three", + type: "function", + nodes: [] + }, + { + label: "", + type: "function", + nodes: [] + } + ] + } + ] + } ] - } -] -*/ + } + ] +} +``` + +## Signature + +```js +tree([options]) ``` -[undertaker]: https://github.com/gulpjs/undertaker +### Parameters + +| parameter | type | note | +|:--------------:|------:|--------| +| options | object | Detailed in [Options][options-section] below. | + +### Returns + +An object detailing the tree of registered tasks - containing nested objects with `'label'` and `'nodes'` properties (which is [archy][archy-external] compatible). + +Each object may have a `type` property that can be used to determine if the node is a `task` or `function`. + +Each object may have a `branch` property that - when `true` - indicates the node was created using `series()` or `parallel()`. + +### Options + +| name | type | default | note | +|:-------:|:-------:|------------|--------| +| deep | boolean | false | If true, the entire tree will be returned. When false, only top level tasks will be returned. | + +[options-section]: #options +[archy-external]: https://www.npmjs.com/package/archy From 69c22f0982b3fcede69270609292acab7cb70fee Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Tue, 21 Aug 2018 17:21:27 -0700 Subject: [PATCH 171/216] Docs: Update watch() documentation --- docs/api/watch.md | 156 +++++++++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/docs/api/watch.md b/docs/api/watch.md index 002d7a1de..857d10b49 100644 --- a/docs/api/watch.md +++ b/docs/api/watch.md @@ -5,105 +5,133 @@ hide_title: true sidebar_label: watch() --> -# `gulp.watch(globs[, opts][, fn])` +# watch() -Takes a path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings as `globs` to watch on the filesystem. Also optionally takes `options` to configure the watcher and a `fn` to execute when a file changes. +Allows watching globs and running a task when a change occurs. Tasks are handled uniformly with the rest of the task system. -Returns an instance of [`chokidar`][chokidar]. +## Usage ```js -gulp.watch('js/**/*.js', gulp.parallel('concat', 'uglify')); +const { watch } = require('gulp'); + +watch(['input/*.js', '!input/something.js'], function(cb) { + // body omitted + cb(); +}); ``` -In the example, `gulp.watch` runs the function returned by `gulp.parallel` each -time a file with the `js` extension in `js/` is updated. +## Signature + +```js +watch(globs, [options], [task]) +``` -## globs -Type: `String` or `Array` +### Parameters -A path string, an array of path strings, a [glob][node-glob] string or an array of [glob][node-glob] strings that indicate which files to watch for changes. +| parameter | type | note | +|:--------------:|:-----:|--------| +| globs
**(required)** | string
array | [Globs][globs-concepts] to watch on the file system. | +| options | object | Detailed in [Options][options-section] below. | +| task | function
string | A [task function][tasks-concepts] or composed task - generated by `series()` and `parallel()`. | -## opts -Type: `Object` +### Returns -* `delay` (milliseconds, default: `200`). The delay to wait before triggering the fn. Useful for waiting on many changes before doing the work on changed files, e.g. find-and-replace on many files. -* `queue` (boolean, default: `true`). Whether or not a file change should queue the fn execution if the fn is already running. Useful for a long running fn. -* `ignoreInitial` (boolean, default: `true`). If set to `false` the `fn` is called during [chokidar][chokidar] instantiation as it discovers the file paths. Useful if it is desirable to trigger the `fn` during startup. __Passed through to [chokidar][chokidar], but defaulted to `true` instead of `false`.__ +An instance of [chokidar][chokidar-instance-section] for fine-grained control over your watch setup. -Options that are passed to [`chokidar`][chokidar]. +### Errors -Commonly used options: +When a non-string or array with any non-strings is passed as `globs`, throws an error with the message, "Non-string provided as watch path". -* `ignored` ([anymatch](https://github.com/es128/anymatch)-compatible definition). -Defines files/paths to be excluded from being watched. -* `usePolling` (boolean, default: `false`). When `true` uses a watch method backed -by stat polling. Usually necessary when watching files on a network mount or on a -VMs file system. -* `cwd` (path string). The base directory from which watch paths are to be -derived. Paths emitted with events will be relative to this. -* `alwaysStat` (boolean, default: `false`). If relying upon the -[`fs.Stats`][fs stats] object -that may get passed as a second argument with `add`, `addDir`, and `change` events -when available, set this to `true` to ensure it is provided with every event. May -have a slight performance penalty. +When a string or array is passed as `task`, throws an error with the message, "watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)". -Read about the full set of options in [`chokidar`'s README][chokidar]. +### Options -## fn -Type: `Function` -If the `fn` is passed, it will be called when the watcher emits a `change`, `add` or `unlink` event. It is automatically debounced with a default delay of 200 milliseconds and subsequent calls will be queued and called upon completion. These defaults can be changed using the `options`. +| name | type | default | note | +|:-------:|:------:|-----------|--------| +| ignoreInitial | boolean | true | If false, the task is called during instantiation as file paths are discovered. Use to trigger the task during startup.
**Note:** This option is passed to [chokidar][chokidar-external] but is defaulted to `true` instead of `false`. | +| delay | number | 200 | The millisecond delay between a file change and task execution. Allows for waiting on many changes before executing a task, e.g. find-and-replace on many files. | +| queue | boolean | true | When true and the task is already running, any file changes will queue a single task execution. Keeps long running tasks from overlapping. | +| events | string
array | [ 'add',
'change',
'unlink' ] | The events being watched to trigger task execution. Can be `'add'`, `'addDir'`, `'change'`, `'unlink'`, `'unlinkDir'`, `'ready'`, and/or `'error'`. Additionally `'all'` is available, which represents all events other than `'ready'` and `'error'`.
_This option is passed directly to [chokidar][chokidar-external]._ | +| persistent | boolean | true | If false, the watcher will not keep the Node process running. Disabling this option is not recommended.
_This option is passed directly to [chokidar][chokidar-external]._ | +| ignored | array
string
RegExp
function | | Defines globs to be ignored. If a function is provided, it will be called twice per path - once with just the path, then with the path and the `fs.Stats` object of that file.
_This option is passed directly to [chokidar][chokidar-external]._ | +| followSymlinks | boolean | true | When true, changes to both symbolic links and the linked files trigger events. If false, only changes to the symbolic links trigger events.
_This option is passed directly to [chokidar][chokidar-external]._ | +| cwd | string | | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [chokidar][chokidar-external]._ | +| disableGlobbing | boolean | false | If true, all `globs` are treated as literal path names, even if they have special characters.
_This option is passed directly to [chokidar][chokidar-external]._ | +| usePolling | boolean | false | When false, the watcher will use `fs.watch()` (or [fsevents][fsevents-external] on Mac) for watching. If true, use `fs.watchFile()` polling instead - needed for successfully watching files over a network or other non-standard situations. Overrides the `useFsEvents` default.
_This option is passed directly to [chokidar][chokidar-external]._ | +| interval | number | 100 | Combine with `usePolling: true`. Interval of file system polling.
_This option is passed directly to [chokidar][chokidar-external]._ | +| binaryInterval | number | 300 | Combine with `usePolling: true`. Interval of file system polling for binary files.
_This option is passed directly to [chokidar][chokidar-external]._ | +| useFsEvents | boolean | true | When true, uses fsevents for watching if available. If explicitly set to true, supersedes the `usePolling` option. If set to false, automatically sets `usePolling` to true.
_This option is passed directly to [chokidar][chokidar-external]._ | +| alwaysStat | boolean | false | If true, always calls `fs.stat()` on changed files - will slow down file watcher. The `fs.Stat` object is only available if you are using the chokidar instance directly.
_This option is passed directly to [chokidar][chokidar-external]._ | +| depth | number | | Indicates how many nested levels of directories will be watched.
_This option is passed directly to [chokidar][chokidar-external]._ | +| awaitWriteFinish | boolean | false | Do not use this option, use `delay` instead.
_This option is passed directly to [chokidar][chokidar-external]._ | +| ignorePermissionErrors | boolean | false | Set to true to watch files that don't have read permissions. Then, if watching fails due to EPERM or EACCES errors, they will be skipped silently.
_This option is passed directly to [chokidar][chokidar-external]._ | +| atomic | number | 100 | Only active if `useFsEvents` and `usePolling` are false. Automatically filters out artifacts that occur from "atomic writes" by some editors. If a file is re-added within the specified milliseconds of being deleted, a change event - instead of unlink then add - will be emitted.
_This option is passed directly to [chokidar][chokidar-external]._ | -The `fn` is passed a single argument, `callback`, which is a function that must be called when work in the `fn` is complete. Instead of calling the `callback` function, [async completion][async-completion] can be signalled by: - * Returning a `Stream` or `EventEmitter` - * Returning a `Child Process` - * Returning a `Promise` - * Returning an `Observable` +## Chokidar instance -Once async completion is signalled, if another run is queued, it will be executed. +The `watch()` method returns the underlying instance of [chokidar][chokidar-external], providing fine-grained control over your watch setup. Most commonly used to register individual event handlers that provide the `path` or `stats` of the changed files. -`gulp.watch` returns a wrapped [chokidar] FSWatcher object. Listeners can also be set directly for any of [chokidar]'s events, such as `addDir`, `unlinkDir`, and `error`. You must set listeners directly to get -access to chokidar's callback parameters, such as `path`. +**When using the chokidar instance directly, you will not have access to the task system integrations, including async completion, queueing, and delay.** ```js -var watcher = gulp.watch('js/**/*.js', gulp.parallel('concat', 'uglify')); +const { watch } = require('gulp'); + +const watcher = watch(['input/*.js']); + watcher.on('change', function(path, stats) { - console.log('File ' + path + ' was changed'); + console.log(`File ${path} was changed`); +}); + +watcher.on('add', function(path, stats) { + console.log(`File ${path} was added`); }); -watcher.on('unlink', function(path) { - console.log('File ' + path + ' was removed'); +watcher.on('unlink', function(path, stats) { + console.log(`File ${path} was removed`); }); + +watcher.close(); ``` -### path -Type: `String` -Path to the file. If `opts.cwd` is set, `path` is relative to it. +`watcher.on(eventName, eventHandler)` + +Registers `eventHandler` functions to be called when the specified event occurs. + +| parameter | type | note | +|:--------------:|:-----:|--------| +| eventName | string | The events that may be watched are `'add'`, `'addDir'`, `'change'`, `'unlink'`, `'unlinkDir'`, `'ready'`, `'error'`, or `'all'`. | +| eventHandler | function | Function to be called when the specified event occurs. Arguments detailed in the table below. | -### stats -Type: `Object` +| argument | type | note | +|:-------------:|:-----:|--------| +| path | string | The path of the file that changed. If the `cwd` option was set, the path will be made relative by removing the `cwd`. | +| stats | object | An [fs.Stat][fs-stats-concepts] object, but could be `undefined`. If the `alwaysStat` option was set to `true`, `stats` will always be provided. | -[File stats][fs stats] object when available. -Setting the `alwaysStat` option to `true` will ensure that a file stat object will be -provided. +`watcher.close()` -## watcher methods +Shuts down the file watcher. Once shut down, no more events will be emitted. -### watcher.close() +`watcher.add(globs)` -Shuts down the file watcher. +Adds additional globs to an already-running watcher instance. -### watcher.add(glob) +| parameter | type | note | +|:-------------:|:-----:|--------| +| globs | string
array | The additional globs to be watched. | -Watch additional glob (or array of globs) with an already-running watcher instance. +`watcher.unwatch(globs)` -### watcher.unwatch(glob) +Removes globs that are being watched, while the watcher continues with the remaining paths. -Stop watching a glob (or array of globs) while leaving the watcher running and -emitting events for the remaining paths it is watching. +| parameter | type | note | +|:-------------:|:-----:|--------| +| globs | string
array | The globs to be removed. | -[chokidar]: https://github.com/paulmillr/chokidar -[node-glob]: https://github.com/isaacs/node-glob -[fs stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats -[async-completion]: https://github.com/gulpjs/async-done#completion-and-error-resolution +[chokidar-instance-section]: #chokidar-instance +[tasks-concepts]: concepts.md#tasks +[globs-concepts]: concepts.md#globs +[fs-stats-concepts]: concepts.md#file-system-stats +[chokidar-external]: https://github.com/paulmillr/chokidar +[fsevents-external]: https://github.com/strongloop/fsevents From fc09067fefdea73f255c249f44a2cce48ebbf546 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Mon, 24 Sep 2018 14:39:08 -0700 Subject: [PATCH 172/216] Docs: Add Vinyl documentation --- docs/api/vinyl.md | 141 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 docs/api/vinyl.md diff --git a/docs/api/vinyl.md b/docs/api/vinyl.md new file mode 100644 index 000000000..5107fb8ae --- /dev/null +++ b/docs/api/vinyl.md @@ -0,0 +1,141 @@ + + +# Vinyl + +A virtual file format. When a file is read by `src()`, a Vinyl object is generated to represent the file - including the path, contents, and other metadata. + +Vinyl objects can have transformations applied using [plugins][using-plugins-docs]. They may also be persisted to the file system using `dest()`. + +When creating your own Vinyl objects - instead of generating with `src()` - use the external `vinyl` module, as shown in Usage below. + +## Usage + +```js +const Vinyl = require('vinyl'); + +const file = new Vinyl({ + cwd: '/', + base: '/test/', + path: '/test/file.js', + contents: new Buffer('var x = 123') +}); + +file.relative === 'file.js'; + +file.dirname === '/test'; +file.dirname = '/specs'; +file.path === '/specs/file.js'; + +file.basename === 'file.js'; +file.basename = 'file.txt'; +file.path === '/specs/file.txt'; + +file.stem === 'file'; +file.stem = 'foo'; +file.path === '/specs/foo.txt'; +file.extname === '.txt'; +file.extname = '.js'; +file.path === '/specs/file.js'; +``` + +## Signature + +```js +new Vinyl([options]) +``` + +### Parameters + +| parameter | type | note | +|:--------------:|:------:|-------| +| options | object | Detailed in [Options][options-section] below. | + +### Returns + +An instance of the Vinyl class representing a single virtual file, detailed in [Vinyl instance][vinyl-instance-section] below. + +### Errors + +When any passed options don't conform to the [instance property definitions][instance-properties-section] (like if `path` is set to a number) throws as defined in the table. + +### Options + +| name | type | default | note | +|:-------:|:------:|-----------|--------| +| cwd | string | `process.cwd()` | The directory from which relative paths will be derived. Will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. | +| base | string | | Used to calculate the `relative` instance property. Falls back to the value of `cwd` if not set. Typically set to the [glob base][glob-base-concepts]. Will be [normalized][normalization-and-concatenation-section] and have trailing separators removed.| +| path | string | | The full, absolute file path. Will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. | +| history | array | `[ ]` | An array of paths to pre-populate the `history` of a Vinyl instance. Usually comes from deriving a new Vinyl object from a previous Vinyl object. If `path` and `history` are both passed, `path` is appended to `history`. Each item will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. | +| stat | object | | An instance of `fs.Stats`, usually the result of calling `fs.stat()` on a file. Used to determine if a Vinyl object represents a directory or symbolic link. | +| contents | ReadableStream
Buffer
null | null | The contents of the file. If `contents` is a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. | + +Any other properties on `options` will be directly assigned to the Vinyl instance. + +```js +const Vinyl = require('vinyl'); + +const file = new Vinyl({ foo: 'bar' }); +file.foo === 'bar'; +``` + +## Vinyl instance + +Each instance of a Vinyl object will have properties and methods to access and/or modify information about the virtual file. + +### Instance properties + +All internally managed paths - any instance property except `contents` and `stat` - are normalized and have trailing separators removed. See [Normalization and concatenation][normalization-and-concatenation-section] for more information. + +| property | type | description | throws | +|:-----------:|:------:|----------------|----------| +| contents | ReadableStream
Buffer
`null` | Gets and sets the contents of the virtual file. If set to a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-docs] stream. | If set to any value other than a ReadableStream, a Buffer, or `null`. | +| stat | object | Gets and sets an instance of [`fs.Stats`][fs-stats-concepts]. Used when determining if a Vinyl object represents a directory or symbolic link. | | +| cwd | string | Gets and sets the current working directory. Used for deriving relative paths. | If set to an empty string or any non-string value. | +| base | string | Gets and sets the base directory. Used to calculate the `relative` instance property. On a Vinyl object generated by `src()` will be set to the [glob base][glob-base-concepts]. If set to `null` or `undefined`, falls back to the value of the `cwd` instance property. | If set to an empty string or any non-string value (except `null` or `undefined`). | +| path | string | Gets and sets the full, absolute file path. Setting to a value different from the current `path` appends the new path to the `history` instance property. | If set to any non-string value. | +| history | array | Array of all `path` values the Vinyl object has been assigned. The first element (`history[0]`) is the original path and the last element (`history[file.history.length - 1]`) is the current path. This property and its elements should be treated as read-only and only altered indirectly by setting the `path` instance property. | | +| relative | string | Gets the relative path segment between the `base` and the `path` instance properties. | If set to any value. If accessed when `path` is not available. | +| dirname | string | Gets and sets the directory of the `path` instance property. | If accessed when `path` is not available. | +| stem | string | Gets and sets the stem (filename without extension) of the `path` instance property. | If accessed when `path` is not available. | +| extname | string | Gets and sets the extension of the `path` instance property. | If accessed when `path` is not available. | +| basename | string | Gets and sets the filename (`stem + extname`) of the `path` instance property. | If accessed when `path` is not available. | +| symlink | string | Gets and sets the reference path of a symbolic link. | If set to any non-string value. | + +### Instance methods + +| method | return type | returns | +|:----------:|:--------------:|--------| +| `isBuffer()` | boolean | If the `contents` instance property is a Buffer, returns true. | +| `isStream()` | boolean | If the `contents` instance property is a Stream, returns true. | +| `isNull()` | boolean | If the `contents` instance property is `null`, returns true. | +| `isDirectory()` | boolean | If the instance represents a directory, returns true. An instance is considered a directory when `isNull()` returns true, the `stat` instance property is an object, and `stat.isDirectory()` returns true. This assumes a Vinyl object was constructed with a valid (or properly mocked) `fs.Stats` object. | +| `isSymbolic()` | boolean | If the instance represents a symbolic link, returns true. An instance is considered symbolic when `isNull()` returns true, the `stat` instance property is an object, and `stat.isSymbolicLink()` returns true. This assumes a Vinyl object was constructed with a valid (or properly mocked) `fs.Stats` object. | +| `clone([options])` | object | A new Vinyl object with all properties cloned. By default custom properties are deep cloned. If the `deep` option is false, custom attributes will be shallow cloned. If the `contents` option is false and the `contents` instance property is a Buffer, the Buffer will be reused instead of cloned. | +| `inspect()` | string | Returns a formatted interpretation of the Vinyl object. Automatically called by Node's console.log. | + +## Normalization and concatenation + +All path properties are normalized by their setters. Concatenate paths with `/`, instead of using `path.join()`, and normalization will occur properly on all platforms. Never concatenate with `\` - it is a valid filename character on posix system. + +```js +const file = new File(); +file.path = '/' + 'test' + '/' + 'foo.bar'; + +console.log(file.path); +// posix => /test/foo.bar +// win32 => \\test\\foo.bar +``` + +[options-section]: #options +[vinyl-instance-section]: #vinyl-instance +[instance-properties-section]: #instance-properties +[normalization-and-concatenation-section]: #normalization-and-concatenation +[glob-base-concepts]: concepts.md#glob-base +[fs-stats-concepts]: concepts.md#file-system-stats +[using-plugins-docs]: ../getting-started/7-using-plugins.md +[cloneable-readable-external]: https://github.com/mcollina/cloneable-readable From 25a22bf571c66cbbe279155a1f2f2876b3571162 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Mon, 24 Sep 2018 14:39:37 -0700 Subject: [PATCH 173/216] Docs: Add Vinyl.isVinyl() documentation --- docs/api/vinyl-isvinyl.md | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 docs/api/vinyl-isvinyl.md diff --git a/docs/api/vinyl-isvinyl.md b/docs/api/vinyl-isvinyl.md new file mode 100644 index 000000000..ab9bf2d07 --- /dev/null +++ b/docs/api/vinyl-isvinyl.md @@ -0,0 +1,41 @@ + + +# Vinyl.isVinyl() + +Determines if an object is a Vinyl instance. Use this method instead of `instanceof`. + +**Note**: This method uses an internal property that some older versions of Vinyl didn't expose resulting in a false negative if using an outdated version. + +## Usage + +```js +const Vinyl = require('vinyl'); + +const file = new Vinyl(); +const notAFile = {}; + +Vinyl.isVinyl(file) === true; +Vinyl.isVinyl(notAFile) === false; +``` + +## Signature + +```js +Vinyl.isVinyl(file); +``` + +### Parameters + +| parameter | type | note | +|:--------------:|:------:|-------| +| file | object | The object to check. | + +### Returns + +True if the `file` object is a Vinyl instance. + From 40ee80163cf952f129e6704d98db496c06f03aab Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Mon, 24 Sep 2018 14:40:04 -0700 Subject: [PATCH 174/216] Docs: Add Vinyl.isCustomProp() documentation --- docs/api/vinyl-iscustomprop.md | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/api/vinyl-iscustomprop.md diff --git a/docs/api/vinyl-iscustomprop.md b/docs/api/vinyl-iscustomprop.md new file mode 100644 index 000000000..d72154d23 --- /dev/null +++ b/docs/api/vinyl-iscustomprop.md @@ -0,0 +1,68 @@ + + +# Vinyl.isCustomProp() + +Determines if a property is internally managed by Vinyl. Used by Vinyl when setting values inside the constructor or when copying properties in the `clone()` instance method. + +This method is useful when extending the Vinyl class. Detailed in [Extending Vinyl][extending-vinyl-section] below. + +## Usage + +```js +const Vinyl = require('vinyl'); + +Vinyl.isCustomProp('sourceMap') === true; +Vinyl.isCustomProp('path') === false; +``` + +## Signature + +```js +Vinyl.isCustomProp(property) +``` + +### Parameters + +| parameter | type | note | +|:--------------:|:------:|-------| +| property | string | The property name to check. | + +### Returns + +True if the property is not internally managed. + +## Extending Vinyl + +When custom properties are managed internally, the static `isCustomProp` method must be extended and return false when one of the custom properties is queried. + +```js +const Vinyl = require('vinyl'); + +const builtInProps = ['foo', '_foo']; + +class SuperFile extends Vinyl { + constructor(options) { + super(options); + this._foo = 'example internal read-only value'; + } + + get foo() { + return this._foo; + } + + static isCustomProp(name) { + return super.isCustomProp(name) && builtInProps.indexOf(name) === -1; + } +} +``` + +In the example above, `foo` and `_foo` will not be assigned to the new object when cloning or passed in `options` to `new SuperFile(options)`. + +If your custom properties or logic require special handling during cloning, override the `clone` method while extending Vinyl. + +[extending-vinyl-section]: #extending-vinyl From 8dd336184f41d13a1d08f75c4f0b944404a73176 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Mon, 24 Sep 2018 14:41:16 -0700 Subject: [PATCH 175/216] Docs: Add API Concepts documentation --- docs/api/concepts.md | 82 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 docs/api/concepts.md diff --git a/docs/api/concepts.md b/docs/api/concepts.md new file mode 100644 index 000000000..666b9b90f --- /dev/null +++ b/docs/api/concepts.md @@ -0,0 +1,82 @@ + + +# Concepts + +The following concepts are prerequisites to understanding the API docs. They will be referenced throughout, refer back to this page for detailed explanations. + +If you're new here, begin with the [Getting Started Guide][quick-start-docs]. + +## Vinyl + +Vinyl is a metadata object that describes a file. The main properties of a Vinyl instance are `path` and `contents` - core aspects of a file on your file system. Vinyl objects can be used to describe files from many sources - on a local file system or any remote storage option. + +## Vinyl adapters + +While Vinyl provides a way to describe a file, a way to access these files is needed. Each file source is accessed using a Vinyl adapter. + +An adapter exposes: +* A method with the signature `src(globs, [options])` and returns a stream that produces Vinyl objects. +* A method with the signature `dest(folder, [options])` and returns a stream that consumes Vinyl objects. +* Any extra methods specific to their input/output medium - such as the `symlink` method `vinyl-fs` provides. They should always return streams that produce and/or consume Vinyl objects. + +## Tasks + +Each gulp task is an asynchronous JavaScript function that either accepts an error-first callback or returns a stream, promise, event emitter, child process, or observable. Due to some platform limitations, synchronous tasks aren't supported. + +For a more detailed explanation, see [Creating Tasks][creating-tasks-doc]. + +## Globs + +A glob is a string of literal and/or wildcard characters, like `*`, `**`, `!`, used to match filepaths. Globbing is the act of locating files on a file system using one or more globs. + +If you don't have experience with globs, see [Explaining Globs][explaining-globs-docs]. + +## Glob base + +A glob base - sometimes called glob parent - is the path segment before any special characters in a glob string. As such, the glob base of `/src/js/**.js` is `/src/js/`. All paths that match the glob are guaranteed to share the glob base since that path segment can't be variable. + +Vinyl instances generated by `src()` are constructed with the glob base set as their `base` property. When written to the file system with `dest()`, the `base` will be removed from the output path so directory structures will be preserved. + +For more in depth information, see the [glob-parent][glob-parent-external] repository. + +## File system stats + +File metadata is provided as an instance of Node's [`fs.Stats`][fs-stats-external]. It is available as the `stat` property on your Vinyl instances and used internally to determine if a Vinyl object represents a directory or symbolic link. When written to the file system, permissions and time values are synchronized from the Vinyl object's `stat` property. + +## File system modes + +File system modes determine what permissions exist for a file. Most files and directories on your file system will have a fairly permissive mode, allowing gulp to read/write/update files on your behalf. By default, gulp will create files with the same permissions as the running process, but you can configure the modes through options in `src()`, `dest()`, etc. If you're experiencing permission (EPERM) issues, check the modes on your files. + +## Modules + +Gulp is made up of many small modules that are pulled together to work cohesively. By utilizing [semver][semver-external] within the small modules, we can release bug fixes and features without publishing new versions of gulp. If you encounter an issue related to one of these modules, open an issue on the individual project repository. + +* [undertaker][undertaker-external] - the task registration system +* [vinyl][vinyl-external] - the virtual file objects +* [vinyl-fs][vinyl-fs-external] - a vinyl adapter to your local file system +* [glob-watcher][glob-watcher-external] - the file watcher +* [bach][bach-external] - task orchestration using `series()` and `parallel()` +* [last-run][last-run-external] - tracks the last run time of a task +* [vinyl-sourcemap][vinyl-sourcemap-external] - built-in sourcemap support +* [gulp-cli][gulp-cli-external] - the command line interface for interacting with gulp + + +[quick-start-docs]: ../getting-started/1-quick-start.md +[creating-tasks-doc]: ../getting-started/3-creating-tasks.md +[explaining-globs-docs]: ../getting-started/6-explaining-globs.md +[undertaker-external]: https://github.com/gulpjs/undertaker +[vinyl-external]: https://github.com/gulpjs/vinyl +[vinyl-fs-external]: https://github.com/gulpjs/vinyl-fs +[glob-watcher-external]: https://github.com/gulpjs/glob-watcher +[bach-external]: https://github.com/gulpjs/bach +[last-run-external]: https://github.com/gulpjs/last-run +[vinyl-sourcemap-external]: https://github.com/gulpjs/vinyl-sourcemap +[gulp-cli-external]: https://github.com/gulpjs/gulp-cli +[semver-external]: https://semver.org +[fs-stats-external]: https://nodejs.org/api/fs.html#fs_class_fs_stats +[glob-parent-external]: https://github.com/es128/glob-parent From d6dd4388ee4bec71c0fe2f78e82d537efd7b0902 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 24 Sep 2018 14:56:11 -0700 Subject: [PATCH 176/216] Docs: Update API Table of Contents --- docs/api/README.md | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/docs/api/README.md b/docs/api/README.md index 387e2787e..fe0d498dd 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -1,19 +1,16 @@ - +## Table of Contents -## gulp API docs - -* [gulp.src](src.md) - Emit files matching one or more globs -* [gulp.dest](dest.md) - Write files to directories -* [gulp.symlink](symlink.md) - Write files to symlinks -* [gulp.task](task.md) - Define tasks -* [gulp.lastRun](lastRun.md) - Get timestamp of last successful run -* [gulp.parallel](parallel.md) - Run tasks in parallel -* [gulp.series](series.md) - Run tasks in series -* [gulp.watch](watch.md) - Do something when a file changes -* [gulp.tree](tree.md) - Get the tree of tasks -* [gulp.registry](registry.md) - Get or set the task registry +* [API Concepts](concepts.md) +* [src()](src.md) +* [dest()](dest.md) +* [symlink()](symlink.md) +* [lastRun()](lastRun.md) +* [series()](series.md) +* [parallel()](parallel.md) +* [watch()](watch.md) +* [task()](task.md) +* [registry()](registry.md) +* [tree()](tree.md) +* [Vinyl](vinyl.md) +* [Vinyl.isVinyl()](vinyl-isvinyl.md) +* [Vinyl.isCustomProp()](vinyl-iscustomprop.md) From 0a687105507215758e6b05d06d1803d1b3b789a8 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 18 Oct 2018 11:47:44 -0700 Subject: [PATCH 177/216] Docs: API documentation improvements based on feedback --- docs/api/concepts.md | 4 +++- docs/api/parallel.md | 2 +- docs/api/series.md | 2 +- docs/api/src.md | 4 ++-- docs/api/symlink.md | 2 +- docs/api/task.md | 3 ++- docs/api/vinyl.md | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/api/concepts.md b/docs/api/concepts.md index 666b9b90f..5c5546057 100644 --- a/docs/api/concepts.md +++ b/docs/api/concepts.md @@ -54,7 +54,9 @@ File system modes determine what permissions exist for a file. Most files and di ## Modules -Gulp is made up of many small modules that are pulled together to work cohesively. By utilizing [semver][semver-external] within the small modules, we can release bug fixes and features without publishing new versions of gulp. If you encounter an issue related to one of these modules, open an issue on the individual project repository. +Gulp is made up of many small modules that are pulled together to work cohesively. By utilizing [semver][semver-external] within the small modules, we can release bug fixes and features without publishing new versions of gulp. Often, when you don't see progress on the main repository, work is being done in one of these modules. + +If you're having trouble, ensure your current modules are updated - using the `npm update` command. If the problem persists, open an issue on the individual project repository. * [undertaker][undertaker-external] - the task registration system * [vinyl][vinyl-external] - the virtual file objects diff --git a/docs/api/parallel.md b/docs/api/parallel.md index 4229c8456..def9138be 100644 --- a/docs/api/parallel.md +++ b/docs/api/parallel.md @@ -7,7 +7,7 @@ sidebar_label: parallel() # parallel() -Combines task functions and/or composed operations into larger operations that will be executed simultaneously. The composed operations from `series()` and `parallel()` can be nested to any depth. +Combines task functions and/or composed operations into larger operations that will be executed simultaneously. There are no imposed limits on the nesting depth of composed operations using `series()` and `parallel()`. ## Usage diff --git a/docs/api/series.md b/docs/api/series.md index fd2c4987a..1f5f71edf 100644 --- a/docs/api/series.md +++ b/docs/api/series.md @@ -7,7 +7,7 @@ sidebar_label: series() # series() -Combines task functions and/or composed operations into larger operations that will be executed one after another, in sequential order. The composed operations from `series()` and `parallel()` can be nested to any depth. +Combines task functions and/or composed operations into larger operations that will be executed one after another, in sequential order. There are no imposed limits on the nesting depth of composed operations using `series()` and `parallel()`. ## Usage diff --git a/docs/api/src.md b/docs/api/src.md index e66a48320..923ee6fd5 100644 --- a/docs/api/src.md +++ b/docs/api/src.md @@ -9,7 +9,7 @@ sidebar_label: src() Creates a stream for reading [Vinyl][vinyl-concepts] objects from the file system. -**Note:** Any UTF-8 BOMs will be removed from UTF-8 files read by `src()`, unless disabled using the `removeBOM` option. +**Note:** BOMs (byte order marks) have no purpose in UTF-8 and will be removed from UTF-8 files read by `src()`, unless disabled using the `removeBOM` option. ## Usage @@ -58,7 +58,7 @@ When an invalid glob is given in `globs`, throws an error with the message, "Inv | buffer | boolean
function | true | When true, file contents are buffered into memory. If false, the Vinyl object's `contents` property will be a paused stream. Contents of large files may not be able to be buffered.
**Note:** Plugins may not implement support for streaming contents. | | read | boolean
function | true | If false, files will be not be read and their Vinyl objects won't be writable to disk via `.dest()`. | | since | date
timestamp
function | | When set, only creates Vinyl objects for files that have been modified since the specified time. | -| removeBOM | boolean
function | true | When true, removes the BOM from UTF-8 encoded files. If false, ignores a BOM.. | +| removeBOM | boolean
function | true | When true, removes the BOM from UTF-8 encoded files. If false, ignores a BOM. | | sourcemaps | boolean
function | false | If true, enables [sourcemaps][sourcemaps-section] support on Vinyl objects created. Loads inline sourcemaps and resolves external sourcemap links. | | resolveSymlinks | boolean
function | true | When true, recursively resolves symbolic links to their targets. If false, preserves the symbolic links and sets the Vinyl object's `symlink` property to the original file's path. | | cwd | string | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [glob-stream][glob-stream-external]._ | diff --git a/docs/api/symlink.md b/docs/api/symlink.md index 7fd1e790e..14e3ebd9b 100644 --- a/docs/api/symlink.md +++ b/docs/api/symlink.md @@ -10,7 +10,7 @@ sidebar_label: symlink() Creates a stream for linking [Vinyl][vinyl-concepts] objects to the file system. ## Usage -' + ```js const { src, symlink } = require('gulp'); diff --git a/docs/api/task.md b/docs/api/task.md index 698cf8437..1eded9fa6 100644 --- a/docs/api/task.md +++ b/docs/api/task.md @@ -7,7 +7,7 @@ sidebar_label: task() # task() -**Reminder**: This API isn't the recommended pattern anymore - export your tasks. +**Reminder**: This API isn't the recommended pattern anymore - [export your tasks][creating-tasks-docs]. Defines a task within the task system. The task can then be accessed from the command line and the `series()`, `parallel()`, and `lastRun()` APIs. @@ -106,4 +106,5 @@ task(build); [task-metadata-section]: #task-metadata [task-concepts]: concepts.md#tasks +[creating-tasks-docs]: ../getting-started/3-creating-tasks.md [function-name-external]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name diff --git a/docs/api/vinyl.md b/docs/api/vinyl.md index 5107fb8ae..204bf5e39 100644 --- a/docs/api/vinyl.md +++ b/docs/api/vinyl.md @@ -120,7 +120,7 @@ All internally managed paths - any instance property except `contents` and `stat ## Normalization and concatenation -All path properties are normalized by their setters. Concatenate paths with `/`, instead of using `path.join()`, and normalization will occur properly on all platforms. Never concatenate with `\` - it is a valid filename character on posix system. +All path properties are normalized by their setters. Concatenate paths with `/`, instead of using `path.join()`, and normalization will occur properly on all platforms. Never concatenate with `\` - it is a valid filename character on POSIX system. ```js const file = new File(); From df7cdcbec2303c923b3a94c9acf6071c0d1e27c3 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 18 Oct 2018 13:47:52 -0700 Subject: [PATCH 178/216] Docs: Temporarily point LINK_NEEDED references to documentation-missing.md --- docs/api/registry.md | 2 +- docs/api/src.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/registry.md b/docs/api/registry.md index cd12bc400..9f4b687fe 100644 --- a/docs/api/registry.md +++ b/docs/api/registry.md @@ -60,4 +60,4 @@ When a registry without an `init` method is passed as `registryInstance`, throws When a registry without a `tasks` method is passed as `registryInstance`, throws an error with the message, "Custom registry must have `tasks` function". -[creating-custom-registries]: LINK_NEEDED +[creating-custom-registries]: ../documentation-missing.md diff --git a/docs/api/src.md b/docs/api/src.md index 923ee6fd5..ff9912efd 100644 --- a/docs/api/src.md +++ b/docs/api/src.md @@ -118,6 +118,6 @@ src('input/**/*.js', { sourcemaps: true }) [vinyl-concepts]: concepts.md#vinyl [glob-base-concepts]: concepts.md#glob-base [globs-concepts]: concepts.md#globs -[extglob-docs]: LINK_NEEDED +[extglob-docs]: ../documentation-missing.md [node-glob-external]: https://github.com/isaacs/node-glob [glob-stream-external]: https://github.com/gulpjs/glob-stream From 6a8fd8f0c5a8cf30f7521957808ef69bdcdb84db Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 18 Oct 2018 15:39:18 -0700 Subject: [PATCH 179/216] Docs: Add some more cleanup for Docusaurus --- docs/api/concepts.md | 2 +- docs/api/dest.md | 2 +- docs/api/last-run.md | 2 +- docs/api/parallel.md | 2 +- docs/api/registry.md | 2 +- docs/api/series.md | 2 +- docs/api/src.md | 2 +- docs/api/symlink.md | 2 +- docs/api/task.md | 2 +- docs/api/tree.md | 2 +- docs/api/vinyl-iscustomprop.md | 2 +- docs/api/vinyl-isvinyl.md | 2 +- docs/api/vinyl.md | 6 +++--- docs/api/watch.md | 2 +- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/api/concepts.md b/docs/api/concepts.md index 5c5546057..e0c6f926d 100644 --- a/docs/api/concepts.md +++ b/docs/api/concepts.md @@ -1,5 +1,5 @@ -We're in the process of rewriting **all** our documentation and some of the links we've added to completed docs haven't been written yet. You've likely clicked on one of those to end up here. We're sorry about that but please check back later on the topic you're interested in. +# Excuse our dust! + +We're in the process of rewriting **all** our documentation and some of the links we've added to completed docs haven't been written yet. You've likely clicked on one of those to end up here. We're sorry about that but please check back later on the topic you're interested in. If you want to help out, we'll happily accept a Pull Request for this missing documentation. -The Gulp Team From d35653e4270f31490f9e4e1343d6412b0f8fb74b Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 18 Oct 2018 16:20:41 -0700 Subject: [PATCH 182/216] Docs: Fix broken link in lastRun --- docs/api/last-run.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/last-run.md b/docs/api/last-run.md index e8d0ce5bc..e9350bc33 100644 --- a/docs/api/last-run.md +++ b/docs/api/last-run.md @@ -42,7 +42,7 @@ lastRun(task, [precision]) | parameter | type | note | |:--------------:|:------:|-------| | task
**(required)** | function
string | The task function or the string alias of a registered task. | -| precision | number | Default: `1000` on Node v0.10, `0` on Node v0.12+. Detailed in Timestamp precision][timestamp-precision-section] section below. | +| precision | number | Default: `1000` on Node v0.10, `0` on Node v0.12+. Detailed in [Timestamp precision][timestamp-precision-section] section below. | ### Returns From cb673193a25726208b9c8d3177f36872742e2c5a Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 18 Oct 2018 16:55:50 -0700 Subject: [PATCH 183/216] Docs: Added code ticks to "null" where missing (#2243) --- docs/api/vinyl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/vinyl.md b/docs/api/vinyl.md index 84368f16f..9fd724cdf 100644 --- a/docs/api/vinyl.md +++ b/docs/api/vinyl.md @@ -72,7 +72,7 @@ When any passed options don't conform to the [instance property definitions][ins | path | string | | The full, absolute file path. Will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. | | history | array | `[ ]` | An array of paths to pre-populate the `history` of a Vinyl instance. Usually comes from deriving a new Vinyl object from a previous Vinyl object. If `path` and `history` are both passed, `path` is appended to `history`. Each item will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. | | stat | object | | An instance of `fs.Stats`, usually the result of calling `fs.stat()` on a file. Used to determine if a Vinyl object represents a directory or symbolic link. | -| contents | ReadableStream
Buffer
null | null | The contents of the file. If `contents` is a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. | +| contents | ReadableStream
Buffer
`null` | `null` | The contents of the file. If `contents` is a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. | Any other properties on `options` will be directly assigned to the Vinyl instance. From 01cfcc5224ee8dac426521d9cfea50ba862e717c Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Wed, 24 Oct 2018 15:13:47 -0700 Subject: [PATCH 184/216] Docs: Improve grammar on Concepts (#2247) --- docs/api/concepts.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/concepts.md b/docs/api/concepts.md index e0c6f926d..3f1529057 100644 --- a/docs/api/concepts.md +++ b/docs/api/concepts.md @@ -32,15 +32,15 @@ For a more detailed explanation, see [Creating Tasks][creating-tasks-doc]. ## Globs -A glob is a string of literal and/or wildcard characters, like `*`, `**`, `!`, used to match filepaths. Globbing is the act of locating files on a file system using one or more globs. +A glob is a string of literal and/or wildcard characters, like `*`, `**`, or `!`, used to match filepaths. Globbing is the act of locating files on a file system using one or more globs. If you don't have experience with globs, see [Explaining Globs][explaining-globs-docs]. ## Glob base -A glob base - sometimes called glob parent - is the path segment before any special characters in a glob string. As such, the glob base of `/src/js/**.js` is `/src/js/`. All paths that match the glob are guaranteed to share the glob base since that path segment can't be variable. +A glob base - sometimes called glob parent - is the path segment before any special characters in a glob string. As such, the glob base of `/src/js/**.js` is `/src/js/`. All paths that match the glob are guaranteed to share the glob base - that path segment can't be variable. -Vinyl instances generated by `src()` are constructed with the glob base set as their `base` property. When written to the file system with `dest()`, the `base` will be removed from the output path so directory structures will be preserved. +Vinyl instances generated by `src()` are constructed with the glob base set as their `base` property. When written to the file system with `dest()`, the `base` will be removed from the output path to preserve directory structures. For more in depth information, see the [glob-parent][glob-parent-external] repository. @@ -56,7 +56,7 @@ File system modes determine what permissions exist for a file. Most files and di Gulp is made up of many small modules that are pulled together to work cohesively. By utilizing [semver][semver-external] within the small modules, we can release bug fixes and features without publishing new versions of gulp. Often, when you don't see progress on the main repository, work is being done in one of these modules. -If you're having trouble, ensure your current modules are updated - using the `npm update` command. If the problem persists, open an issue on the individual project repository. +If you're having trouble, ensure your current modules are updated using the `npm update` command. If the problem persists, open an issue on the individual project repository. * [undertaker][undertaker-external] - the task registration system * [vinyl][vinyl-external] - the virtual file objects From eb493a21874b4866f1fe656522d733db1508d6b9 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Wed, 24 Oct 2018 15:31:05 -0700 Subject: [PATCH 185/216] Docs: Improve grammar in src() (#2248) --- docs/api/src.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/src.md b/docs/api/src.md index 8521aeb90..57f249ff2 100644 --- a/docs/api/src.md +++ b/docs/api/src.md @@ -55,9 +55,9 @@ When an invalid glob is given in `globs`, throws an error with the message, "Inv | name | type | default | note | |:--------:|:------:|------------|--------| -| buffer | boolean
function | true | When true, file contents are buffered into memory. If false, the Vinyl object's `contents` property will be a paused stream. Contents of large files may not be able to be buffered.
**Note:** Plugins may not implement support for streaming contents. | +| buffer | boolean
function | true | When true, file contents are buffered into memory. If false, the Vinyl object's `contents` property will be a paused stream. It may not be possible to buffer the contents of large files.
**Note:** Plugins may not implement support for streaming contents. | | read | boolean
function | true | If false, files will be not be read and their Vinyl objects won't be writable to disk via `.dest()`. | -| since | date
timestamp
function | | When set, only creates Vinyl objects for files that have been modified since the specified time. | +| since | date
timestamp
function | | When set, only creates Vinyl objects for files modified since the specified time. | | removeBOM | boolean
function | true | When true, removes the BOM from UTF-8 encoded files. If false, ignores a BOM. | | sourcemaps | boolean
function | false | If true, enables [sourcemaps][sourcemaps-section] support on Vinyl objects created. Loads inline sourcemaps and resolves external sourcemap links. | | resolveSymlinks | boolean
function | true | When true, recursively resolves symbolic links to their targets. If false, preserves the symbolic links and sets the Vinyl object's `symlink` property to the original file's path. | From c960c1db8db1c2dea8ed1ba5357bc563ff1cd727 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 24 Oct 2018 15:36:28 -0700 Subject: [PATCH 186/216] Docs: Remove spaces around
--- docs/api/dest.md | 18 ++++++------ docs/api/last-run.md | 2 +- docs/api/parallel.md | 2 +- docs/api/series.md | 2 +- docs/api/src.md | 68 ++++++++++++++++++++++---------------------- docs/api/symlink.md | 12 ++++---- docs/api/task.md | 4 +-- docs/api/vinyl.md | 4 +-- docs/api/watch.md | 40 +++++++++++++------------- 9 files changed, 76 insertions(+), 76 deletions(-) diff --git a/docs/api/dest.md b/docs/api/dest.md index 2a29c2e66..d4b6fe429 100644 --- a/docs/api/dest.md +++ b/docs/api/dest.md @@ -32,7 +32,7 @@ dest(directory, [options]) | parameter | type | note | |:--------------:|:-----:|--------| -| directory
**(required)** | string
function | The path of the output directory where files will be written. If a function is used, the function will be called with each Vinyl object and must return a string directory path. | +| directory
**(required)** | string
function | The path of the output directory where files will be written. If a function is used, the function will be called with each Vinyl object and must return a string directory path. | | options | object | Detailed in [Options][options-section] below. | ### Returns @@ -60,14 +60,14 @@ When `directory` is a function that returns an empty string or `undefined`, emit | name | type | default | note | |:-------:|:------:|-----------|-------| -| cwd | string
function | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `directory` with `path.join()`. | -| mode | number
function | `stat.mode` of the Vinyl object | The mode used when creating files. If not set and `stat.mode` is missing, the process' mode will be used instead. | -| dirMode | number
function | | The mode used when creating directories. If not set, the process' mode will be used. | -| overwrite | boolean
function | true | When true, overwrites existing files with the same path. | -| append | boolean
function | false | If true, adds contents to the end of the file, instead of replacing existing contents. | -| sourcemaps | boolean
string
function | false | If true, writes inline sourcemaps to the output file. Specifying a `string` path will write external [sourcemaps][sourcemaps-section] at the given path. | -| relativeSymlinks | boolean
function | false | When false, any symbolic links created will be absolute.
**Note**: Ignored if a junction is being created, as they must be absolute. | -| useJunctions | boolean
function | true | This option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in [Symbolic links on Windows][symbolic-links-section] below. | +| cwd | string
function | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `directory` with `path.join()`. | +| mode | number
function | `stat.mode` of the Vinyl object | The mode used when creating files. If not set and `stat.mode` is missing, the process' mode will be used instead. | +| dirMode | number
function | | The mode used when creating directories. If not set, the process' mode will be used. | +| overwrite | boolean
function | true | When true, overwrites existing files with the same path. | +| append | boolean
function | false | If true, adds contents to the end of the file, instead of replacing existing contents. | +| sourcemaps | boolean
string
function | false | If true, writes inline sourcemaps to the output file. Specifying a `string` path will write external [sourcemaps][sourcemaps-section] at the given path. | +| relativeSymlinks | boolean
function | false | When false, any symbolic links created will be absolute.
**Note**: Ignored if a junction is being created, as they must be absolute. | +| useJunctions | boolean
function | true | This option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in [Symbolic links on Windows][symbolic-links-section] below. | ## Metadata updates diff --git a/docs/api/last-run.md b/docs/api/last-run.md index e9350bc33..cf61b31a7 100644 --- a/docs/api/last-run.md +++ b/docs/api/last-run.md @@ -41,7 +41,7 @@ lastRun(task, [precision]) | parameter | type | note | |:--------------:|:------:|-------| -| task
**(required)** | function
string | The task function or the string alias of a registered task. | +| task
**(required)** | function
string | The task function or the string alias of a registered task. | | precision | number | Default: `1000` on Node v0.10, `0` on Node v0.12+. Detailed in [Timestamp precision][timestamp-precision-section] section below. | ### Returns diff --git a/docs/api/parallel.md b/docs/api/parallel.md index bb7d67cd4..c349a8803 100644 --- a/docs/api/parallel.md +++ b/docs/api/parallel.md @@ -37,7 +37,7 @@ parallel(...tasks) | parameter | type | note | |:--------------:|:------:|-------| -| tasks
**(required)** | function
string | Any number of task functions can be passed as individual arguments. Strings can be used if you've registered tasks previously, but this is not recommended. | +| tasks
**(required)** | function
string | Any number of task functions can be passed as individual arguments. Strings can be used if you've registered tasks previously, but this is not recommended. | ### Returns diff --git a/docs/api/series.md b/docs/api/series.md index 49609d42e..0d59a8caf 100644 --- a/docs/api/series.md +++ b/docs/api/series.md @@ -37,7 +37,7 @@ series(...tasks) | parameter | type | note | |:--------------:|:------:|-------| -| tasks
**(required)** | function
string | Any number of task functions can be passed as individual arguments. Strings can be used if you've registered tasks previously, but this is not recommended. | +| tasks
**(required)** | function
string | Any number of task functions can be passed as individual arguments. Strings can be used if you've registered tasks previously, but this is not recommended. | ### Returns diff --git a/docs/api/src.md b/docs/api/src.md index 57f249ff2..243fd9fd9 100644 --- a/docs/api/src.md +++ b/docs/api/src.md @@ -35,7 +35,7 @@ src(globs, [options]) | parameter | type | note | |:--------------:|:------:|-------| -| globs | string
array | [Globs][globs-concepts] to watch on the file system. | +| globs | string
array | [Globs][globs-concepts] to watch on the file system. | | options | object | Detailed in [Options][options-section] below. | ### Returns @@ -55,39 +55,39 @@ When an invalid glob is given in `globs`, throws an error with the message, "Inv | name | type | default | note | |:--------:|:------:|------------|--------| -| buffer | boolean
function | true | When true, file contents are buffered into memory. If false, the Vinyl object's `contents` property will be a paused stream. It may not be possible to buffer the contents of large files.
**Note:** Plugins may not implement support for streaming contents. | -| read | boolean
function | true | If false, files will be not be read and their Vinyl objects won't be writable to disk via `.dest()`. | -| since | date
timestamp
function | | When set, only creates Vinyl objects for files modified since the specified time. | -| removeBOM | boolean
function | true | When true, removes the BOM from UTF-8 encoded files. If false, ignores a BOM. | -| sourcemaps | boolean
function | false | If true, enables [sourcemaps][sourcemaps-section] support on Vinyl objects created. Loads inline sourcemaps and resolves external sourcemap links. | -| resolveSymlinks | boolean
function | true | When true, recursively resolves symbolic links to their targets. If false, preserves the symbolic links and sets the Vinyl object's `symlink` property to the original file's path. | -| cwd | string | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [glob-stream][glob-stream-external]._ | -| base | string | | Explicitly set the `base` property on created Vinyl objects. Detailed in [API Concepts][glob-base-concepts].
_This option is passed directly to [glob-stream][glob-stream-external]._ | -| cwdbase | boolean | false | If true, `cwd` and `base` options should be aligned.
_This option is passed directly to [glob-stream][glob-stream-external]._ | -| root | string | | The root path that `globs` are resolved against.
_This option is passed directly to [glob-stream][glob-stream-external]._ | -| allowEmpty | boolean | false | When false, `globs` which can only match one file (such as `foo/bar.js`) causes an error to be thrown if they don't find a match. If true, suppresses glob failures.
_This option is passed directly to [glob-stream][glob-stream-external]._ | -| uniqueBy | string
function | `'path'` | Remove duplicates from the stream by comparing the string property name or the result of the function.
**Note:** When using a function, the function receives the streamed data (objects containing `cwd`, `base`, `path` properties). | -| dot | boolean | false | If true, compare globs against dot files, like `.gitignore`.
_This option is passed directly to [node-glob][node-glob-external]._ | -| silent | boolean | true | When true, suppresses warnings from printing on `stderr`.
**Note:** This option is passed directly to [node-glob][node-glob-external] but defaulted to `true` instead of `false`. | -| mark | boolean | false | If true, a `/` character will be appended to directory matches. Generally not needed because paths are normalized within the pipeline.
_This option is passed directly to [node-glob][node-glob-external]._ | -| nosort | boolean | false | If true, disables sorting the glob results.
_This option is passed directly to [node-glob][node-glob-external]._ | -| stat | boolean | false | If true, `fs.stat()` is called on all results. This adds extra overhead and generally should not be used.
_This option is passed directly to [node-glob][node-glob-external]._ | -| strict | boolean | false | If true, an error will be thrown if an unexpected problem is encountered while attempting to read a directory.
_This option is passed directly to [node-glob][node-glob-external]._ | -| nounique | boolean | false | When false, prevents duplicate files in the result set.
_This option is passed directly to [node-glob][node-glob-external]._ | -| debug | boolean | false | If true, debugging information will be logged to the command line.
_This option is passed directly to [node-glob][node-glob-external]._ | -| nobrace | boolean | false | If true, avoids expanding brace sets - e.g. `{a,b}` or `{1..3}`.
_This option is passed directly to [node-glob][node-glob-external]._ | -| noglobstar | boolean | false | If true, treats double-star glob character as single-star glob character.
_This option is passed directly to [node-glob][node-glob-external]._ | -| noext | boolean | false | If true, avoids matching [extglob][extglob-docs] patterns - e.g. `+(ab)`.
_This option is passed directly to [node-glob][node-glob-external]._ | -| nocase | boolean | false | If true, performs a case-insensitive match.
**Note:** On case-insensitive file systems, non-magic patterns will match by default.
_This option is passed directly to [node-glob][node-glob-external]._ | -| matchBase | boolean | false | If true and globs don't contain any `/` characters, traverses all directories and matches that glob - e.g. `*.js` would be treated as equivalent to `**/*.js`.
_This option is passed directly to [node-glob][node-glob-external]._ | -| nodir | boolean | false | If true, only matches files, not directories.
**Note:** To match only directories, end your glob with a `/`.
_This option is passed directly to [node-glob][node-glob-external]._ | -| ignore | string
array | | Globs to exclude from matches. This option is combined with negated `globs`.
**Note:** These globs are always matched against dot files, regardless of any other settings.
_This option is passed directly to [node-glob][node-glob-external]._ | -| follow | boolean | false | If true, symlinked directories will be traversed when expanding `**` globs.
**Note:** This can cause problems with cyclical links.
_This option is passed directly to [node-glob][node-glob-external]._ | -| realpath | boolean | false | If true, `fs.realpath()` is called on all results. This may result in dangling links.
_This option is passed directly to [node-glob][node-glob-external]._ | -| cache | object | | A previously generated cache object - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | -| statCache | object | | A previously generated cache of `fs.Stat` results - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | -| symlinks | object | | A previously generated cache of symbolic links - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | -| nocomment | boolean | false | When false, treat a `#` character at the start of a glob as a comment.
_This option is passed directly to [node-glob][node-glob-external]._ | +| buffer | boolean
function | true | When true, file contents are buffered into memory. If false, the Vinyl object's `contents` property will be a paused stream. It may not be possible to buffer the contents of large files.
**Note:** Plugins may not implement support for streaming contents. | +| read | boolean
function | true | If false, files will be not be read and their Vinyl objects won't be writable to disk via `.dest()`. | +| since | date
timestamp
function | | When set, only creates Vinyl objects for files modified since the specified time. | +| removeBOM | boolean
function | true | When true, removes the BOM from UTF-8 encoded files. If false, ignores a BOM. | +| sourcemaps | boolean
function | false | If true, enables [sourcemaps][sourcemaps-section] support on Vinyl objects created. Loads inline sourcemaps and resolves external sourcemap links. | +| resolveSymlinks | boolean
function | true | When true, recursively resolves symbolic links to their targets. If false, preserves the symbolic links and sets the Vinyl object's `symlink` property to the original file's path. | +| cwd | string | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| base | string | | Explicitly set the `base` property on created Vinyl objects. Detailed in [API Concepts][glob-base-concepts].
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| cwdbase | boolean | false | If true, `cwd` and `base` options should be aligned.
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| root | string | | The root path that `globs` are resolved against.
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| allowEmpty | boolean | false | When false, `globs` which can only match one file (such as `foo/bar.js`) causes an error to be thrown if they don't find a match. If true, suppresses glob failures.
_This option is passed directly to [glob-stream][glob-stream-external]._ | +| uniqueBy | string
function | `'path'` | Remove duplicates from the stream by comparing the string property name or the result of the function.
**Note:** When using a function, the function receives the streamed data (objects containing `cwd`, `base`, `path` properties). | +| dot | boolean | false | If true, compare globs against dot files, like `.gitignore`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| silent | boolean | true | When true, suppresses warnings from printing on `stderr`.
**Note:** This option is passed directly to [node-glob][node-glob-external] but defaulted to `true` instead of `false`. | +| mark | boolean | false | If true, a `/` character will be appended to directory matches. Generally not needed because paths are normalized within the pipeline.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nosort | boolean | false | If true, disables sorting the glob results.
_This option is passed directly to [node-glob][node-glob-external]._ | +| stat | boolean | false | If true, `fs.stat()` is called on all results. This adds extra overhead and generally should not be used.
_This option is passed directly to [node-glob][node-glob-external]._ | +| strict | boolean | false | If true, an error will be thrown if an unexpected problem is encountered while attempting to read a directory.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nounique | boolean | false | When false, prevents duplicate files in the result set.
_This option is passed directly to [node-glob][node-glob-external]._ | +| debug | boolean | false | If true, debugging information will be logged to the command line.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nobrace | boolean | false | If true, avoids expanding brace sets - e.g. `{a,b}` or `{1..3}`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| noglobstar | boolean | false | If true, treats double-star glob character as single-star glob character.
_This option is passed directly to [node-glob][node-glob-external]._ | +| noext | boolean | false | If true, avoids matching [extglob][extglob-docs] patterns - e.g. `+(ab)`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nocase | boolean | false | If true, performs a case-insensitive match.
**Note:** On case-insensitive file systems, non-magic patterns will match by default.
_This option is passed directly to [node-glob][node-glob-external]._ | +| matchBase | boolean | false | If true and globs don't contain any `/` characters, traverses all directories and matches that glob - e.g. `*.js` would be treated as equivalent to `**/*.js`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nodir | boolean | false | If true, only matches files, not directories.
**Note:** To match only directories, end your glob with a `/`.
_This option is passed directly to [node-glob][node-glob-external]._ | +| ignore | string
array | | Globs to exclude from matches. This option is combined with negated `globs`.
**Note:** These globs are always matched against dot files, regardless of any other settings.
_This option is passed directly to [node-glob][node-glob-external]._ | +| follow | boolean | false | If true, symlinked directories will be traversed when expanding `**` globs.
**Note:** This can cause problems with cyclical links.
_This option is passed directly to [node-glob][node-glob-external]._ | +| realpath | boolean | false | If true, `fs.realpath()` is called on all results. This may result in dangling links.
_This option is passed directly to [node-glob][node-glob-external]._ | +| cache | object | | A previously generated cache object - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | +| statCache | object | | A previously generated cache of `fs.Stat` results - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | +| symlinks | object | | A previously generated cache of symbolic links - avoids some file system calls.
_This option is passed directly to [node-glob][node-glob-external]._ | +| nocomment | boolean | false | When false, treat a `#` character at the start of a glob as a comment.
_This option is passed directly to [node-glob][node-glob-external]._ | ## Sourcemaps diff --git a/docs/api/symlink.md b/docs/api/symlink.md index 7042295c2..fcdee880f 100644 --- a/docs/api/symlink.md +++ b/docs/api/symlink.md @@ -32,7 +32,7 @@ symlink(directory, [options]) | parameter | type | note | |:--------------:|:-----:|--------| -| directory
**(required)** | string
function | The path of the output directory where symbolic links will be created. If a function is used, the function will be called with each Vinyl object and must return a string directory path. | +| directory
**(required)** | string
function | The path of the output directory where symbolic links will be created. If a function is used, the function will be called with each Vinyl object and must return a string directory path. | | options | object | Detailed in [Options][options-section] below. | ### Returns @@ -63,11 +63,11 @@ When `directory` is a function that returns an empty string or `undefined`, emit | name | type | default | note | |:-------:|:------:|-----------|-------| -| cwd | string
function | `process.cwd()` |The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `directory` with `path.join()`. | -| dirMode | number
function | | The mode used when creating directories. If not set, the process' mode will be used. | -| overwrite | boolean
function | true | When true, overwrites existing files with the same path. | -| relativeSymlinks | boolean
function | false | When false, any symbolic links created will be absolute.
**Note**: Ignored if a junction is being created, as they must be absolute. | -| useJunctions | boolean
function | true | This option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in [Symbolic links on Windows][symbolic-links-section] below. | +| cwd | string
function | `process.cwd()` |The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `directory` with `path.join()`. | +| dirMode | number
function | | The mode used when creating directories. If not set, the process' mode will be used. | +| overwrite | boolean
function | true | When true, overwrites existing files with the same path. | +| relativeSymlinks | boolean
function | false | When false, any symbolic links created will be absolute.
**Note**: Ignored if a junction is being created, as they must be absolute. | +| useJunctions | boolean
function | true | This option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in [Symbolic links on Windows][symbolic-links-section] below. | ## Symbolic links on Windows diff --git a/docs/api/task.md b/docs/api/task.md index 27e95cd08..0fb2ecb4d 100644 --- a/docs/api/task.md +++ b/docs/api/task.md @@ -62,7 +62,7 @@ Since any registered task can be run from the command line, avoid using spaces i | parameter | type | note | |:--------------:|:------:|-------| | taskName | string | An alias for the task function within the the task system. Not needed when using named functions for `taskFunction`. | -| taskFunction
**(required)** | function | A [task function][tasks-concepts] or composed task - generated by `series()` and `parallel()`. Ideally a named function. [Task metadata][task-metadata-section] can be attached to provide extra information to the command line. | +| taskFunction
**(required)** | function | A [task function][tasks-concepts] or composed task - generated by `series()` and `parallel()`. Ideally a named function. [Task metadata][task-metadata-section] can be attached to provide extra information to the command line. | ### Returns @@ -78,7 +78,7 @@ When registering a task where `taskName` is missing and `taskFunction` is anonym | property | type | note | |:--------------:|:------:|-------| -| name | string | A special property of named functions. Used to register the task.
**Note:** [`name`][function-name-external] is not writable; it cannot be set or changed. | +| name | string | A special property of named functions. Used to register the task.
**Note:** [`name`][function-name-external] is not writable; it cannot be set or changed. | | displayName | string | When attached to a `taskFunction` creates an alias for the task. If using characters that aren't allowed in function names, use this property. | | description | string | When attached to a `taskFunction` provides a description to be printed by the command line when listing tasks. | | flags | object | When attached to a `taskFunction` provides flags to be printed by the command line when listing tasks. The keys of the object represent the flags and the values are their descriptions. | diff --git a/docs/api/vinyl.md b/docs/api/vinyl.md index 9fd724cdf..771a6c2e3 100644 --- a/docs/api/vinyl.md +++ b/docs/api/vinyl.md @@ -72,7 +72,7 @@ When any passed options don't conform to the [instance property definitions][ins | path | string | | The full, absolute file path. Will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. | | history | array | `[ ]` | An array of paths to pre-populate the `history` of a Vinyl instance. Usually comes from deriving a new Vinyl object from a previous Vinyl object. If `path` and `history` are both passed, `path` is appended to `history`. Each item will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. | | stat | object | | An instance of `fs.Stats`, usually the result of calling `fs.stat()` on a file. Used to determine if a Vinyl object represents a directory or symbolic link. | -| contents | ReadableStream
Buffer
`null` | `null` | The contents of the file. If `contents` is a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. | +| contents | ReadableStream
Buffer
`null` | `null` | The contents of the file. If `contents` is a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. | Any other properties on `options` will be directly assigned to the Vinyl instance. @@ -93,7 +93,7 @@ All internally managed paths - any instance property except `contents` and `stat | property | type | description | throws | |:-----------:|:------:|----------------|----------| -| contents | ReadableStream
Buffer
`null` | Gets and sets the contents of the virtual file. If set to a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. | If set to any value other than a ReadableStream, a Buffer, or `null`. | +| contents | ReadableStream
Buffer
`null` | Gets and sets the contents of the virtual file. If set to a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. | If set to any value other than a ReadableStream, a Buffer, or `null`. | | stat | object | Gets and sets an instance of [`fs.Stats`][fs-stats-concepts]. Used when determining if a Vinyl object represents a directory or symbolic link. | | | cwd | string | Gets and sets the current working directory. Used for deriving relative paths. | If set to an empty string or any non-string value. | | base | string | Gets and sets the base directory. Used to calculate the `relative` instance property. On a Vinyl object generated by `src()` will be set to the [glob base][glob-base-concepts]. If set to `null` or `undefined`, falls back to the value of the `cwd` instance property. | If set to an empty string or any non-string value (except `null` or `undefined`). | diff --git a/docs/api/watch.md b/docs/api/watch.md index 348396b4a..3c0fce72f 100644 --- a/docs/api/watch.md +++ b/docs/api/watch.md @@ -30,9 +30,9 @@ watch(globs, [options], [task]) | parameter | type | note | |:--------------:|:-----:|--------| -| globs
**(required)** | string
array | [Globs][globs-concepts] to watch on the file system. | +| globs
**(required)** | string
array | [Globs][globs-concepts] to watch on the file system. | | options | object | Detailed in [Options][options-section] below. | -| task | function
string | A [task function][tasks-concepts] or composed task - generated by `series()` and `parallel()`. | +| task | function
string | A [task function][tasks-concepts] or composed task - generated by `series()` and `parallel()`. | ### Returns @@ -49,24 +49,24 @@ When a string or array is passed as `task`, throws an error with the message, "w | name | type | default | note | |:-------:|:------:|-----------|--------| -| ignoreInitial | boolean | true | If false, the task is called during instantiation as file paths are discovered. Use to trigger the task during startup.
**Note:** This option is passed to [chokidar][chokidar-external] but is defaulted to `true` instead of `false`. | +| ignoreInitial | boolean | true | If false, the task is called during instantiation as file paths are discovered. Use to trigger the task during startup.
**Note:** This option is passed to [chokidar][chokidar-external] but is defaulted to `true` instead of `false`. | | delay | number | 200 | The millisecond delay between a file change and task execution. Allows for waiting on many changes before executing a task, e.g. find-and-replace on many files. | | queue | boolean | true | When true and the task is already running, any file changes will queue a single task execution. Keeps long running tasks from overlapping. | -| events | string
array | [ 'add',
'change',
'unlink' ] | The events being watched to trigger task execution. Can be `'add'`, `'addDir'`, `'change'`, `'unlink'`, `'unlinkDir'`, `'ready'`, and/or `'error'`. Additionally `'all'` is available, which represents all events other than `'ready'` and `'error'`.
_This option is passed directly to [chokidar][chokidar-external]._ | -| persistent | boolean | true | If false, the watcher will not keep the Node process running. Disabling this option is not recommended.
_This option is passed directly to [chokidar][chokidar-external]._ | -| ignored | array
string
RegExp
function | | Defines globs to be ignored. If a function is provided, it will be called twice per path - once with just the path, then with the path and the `fs.Stats` object of that file.
_This option is passed directly to [chokidar][chokidar-external]._ | -| followSymlinks | boolean | true | When true, changes to both symbolic links and the linked files trigger events. If false, only changes to the symbolic links trigger events.
_This option is passed directly to [chokidar][chokidar-external]._ | -| cwd | string | | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [chokidar][chokidar-external]._ | -| disableGlobbing | boolean | false | If true, all `globs` are treated as literal path names, even if they have special characters.
_This option is passed directly to [chokidar][chokidar-external]._ | -| usePolling | boolean | false | When false, the watcher will use `fs.watch()` (or [fsevents][fsevents-external] on Mac) for watching. If true, use `fs.watchFile()` polling instead - needed for successfully watching files over a network or other non-standard situations. Overrides the `useFsEvents` default.
_This option is passed directly to [chokidar][chokidar-external]._ | -| interval | number | 100 | Combine with `usePolling: true`. Interval of file system polling.
_This option is passed directly to [chokidar][chokidar-external]._ | -| binaryInterval | number | 300 | Combine with `usePolling: true`. Interval of file system polling for binary files.
_This option is passed directly to [chokidar][chokidar-external]._ | -| useFsEvents | boolean | true | When true, uses fsevents for watching if available. If explicitly set to true, supersedes the `usePolling` option. If set to false, automatically sets `usePolling` to true.
_This option is passed directly to [chokidar][chokidar-external]._ | -| alwaysStat | boolean | false | If true, always calls `fs.stat()` on changed files - will slow down file watcher. The `fs.Stat` object is only available if you are using the chokidar instance directly.
_This option is passed directly to [chokidar][chokidar-external]._ | -| depth | number | | Indicates how many nested levels of directories will be watched.
_This option is passed directly to [chokidar][chokidar-external]._ | -| awaitWriteFinish | boolean | false | Do not use this option, use `delay` instead.
_This option is passed directly to [chokidar][chokidar-external]._ | -| ignorePermissionErrors | boolean | false | Set to true to watch files that don't have read permissions. Then, if watching fails due to EPERM or EACCES errors, they will be skipped silently.
_This option is passed directly to [chokidar][chokidar-external]._ | -| atomic | number | 100 | Only active if `useFsEvents` and `usePolling` are false. Automatically filters out artifacts that occur from "atomic writes" by some editors. If a file is re-added within the specified milliseconds of being deleted, a change event - instead of unlink then add - will be emitted.
_This option is passed directly to [chokidar][chokidar-external]._ | +| events | string
array | [ 'add',
'change',
'unlink' ] | The events being watched to trigger task execution. Can be `'add'`, `'addDir'`, `'change'`, `'unlink'`, `'unlinkDir'`, `'ready'`, and/or `'error'`. Additionally `'all'` is available, which represents all events other than `'ready'` and `'error'`.
_This option is passed directly to [chokidar][chokidar-external]._ | +| persistent | boolean | true | If false, the watcher will not keep the Node process running. Disabling this option is not recommended.
_This option is passed directly to [chokidar][chokidar-external]._ | +| ignored | array
string
RegExp
function | | Defines globs to be ignored. If a function is provided, it will be called twice per path - once with just the path, then with the path and the `fs.Stats` object of that file.
_This option is passed directly to [chokidar][chokidar-external]._ | +| followSymlinks | boolean | true | When true, changes to both symbolic links and the linked files trigger events. If false, only changes to the symbolic links trigger events.
_This option is passed directly to [chokidar][chokidar-external]._ | +| cwd | string | | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [chokidar][chokidar-external]._ | +| disableGlobbing | boolean | false | If true, all `globs` are treated as literal path names, even if they have special characters.
_This option is passed directly to [chokidar][chokidar-external]._ | +| usePolling | boolean | false | When false, the watcher will use `fs.watch()` (or [fsevents][fsevents-external] on Mac) for watching. If true, use `fs.watchFile()` polling instead - needed for successfully watching files over a network or other non-standard situations. Overrides the `useFsEvents` default.
_This option is passed directly to [chokidar][chokidar-external]._ | +| interval | number | 100 | Combine with `usePolling: true`. Interval of file system polling.
_This option is passed directly to [chokidar][chokidar-external]._ | +| binaryInterval | number | 300 | Combine with `usePolling: true`. Interval of file system polling for binary files.
_This option is passed directly to [chokidar][chokidar-external]._ | +| useFsEvents | boolean | true | When true, uses fsevents for watching if available. If explicitly set to true, supersedes the `usePolling` option. If set to false, automatically sets `usePolling` to true.
_This option is passed directly to [chokidar][chokidar-external]._ | +| alwaysStat | boolean | false | If true, always calls `fs.stat()` on changed files - will slow down file watcher. The `fs.Stat` object is only available if you are using the chokidar instance directly.
_This option is passed directly to [chokidar][chokidar-external]._ | +| depth | number | | Indicates how many nested levels of directories will be watched.
_This option is passed directly to [chokidar][chokidar-external]._ | +| awaitWriteFinish | boolean | false | Do not use this option, use `delay` instead.
_This option is passed directly to [chokidar][chokidar-external]._ | +| ignorePermissionErrors | boolean | false | Set to true to watch files that don't have read permissions. Then, if watching fails due to EPERM or EACCES errors, they will be skipped silently.
_This option is passed directly to [chokidar][chokidar-external]._ | +| atomic | number | 100 | Only active if `useFsEvents` and `usePolling` are false. Automatically filters out artifacts that occur from "atomic writes" by some editors. If a file is re-added within the specified milliseconds of being deleted, a change event - instead of unlink then add - will be emitted.
_This option is passed directly to [chokidar][chokidar-external]._ | ## Chokidar instance @@ -119,7 +119,7 @@ Adds additional globs to an already-running watcher instance. | parameter | type | note | |:-------------:|:-----:|--------| -| globs | string
array | The additional globs to be watched. | +| globs | string
array | The additional globs to be watched. | `watcher.unwatch(globs)` @@ -127,7 +127,7 @@ Removes globs that are being watched, while the watcher continues with the remai | parameter | type | note | |:-------------:|:-----:|--------| -| globs | string
array | The globs to be removed. | +| globs | string
array | The globs to be removed. | [chokidar-instance-section]: #chokidar-instance [tasks-concepts]: ../api/concepts.md#tasks From ca6ba3559603ad2c3d7e63a1d98d88ef442d7c0c Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 25 Oct 2018 10:44:51 -0700 Subject: [PATCH 187/216] Docs: Fix formatting error (#2250) --- docs/api/symlink.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api/symlink.md b/docs/api/symlink.md index fcdee880f..a84665ed4 100644 --- a/docs/api/symlink.md +++ b/docs/api/symlink.md @@ -71,15 +71,15 @@ When `directory` is a function that returns an empty string or `undefined`, emit ## Symbolic links on Windows -When creating symbolic links on Windows, a `type` argument is passed to Node's `fs.symlink()` method which specifies the type of target being linked. The link type is set to: -`'file'` when the target is a regular file -`'junction'` when the target is a directory -`'dir'` when the target is a directory and the user disables the `useJunctions` option +When creating symbolic links on Windows, a `type` argument is passed to Node's `fs.symlink()` method which specifies the type of target being linked. The link type is set to: +* `'file'` when the target is a regular file +* `'junction'` when the target is a directory +* `'dir'` when the target is a directory and the user disables the `useJunctions` option If you try to create a dangling (pointing to a non-existent target) link, the link type can't be determined automatically. In these cases, behavior will vary depending on whether the dangling link is being created via `symlink()` or via `dest()`. -For dangling links created via `symlink()`, the incoming Vinyl object represents the target, so its stats will determine the desired link type. If `isDirectory()` returns false then a `'file'` link is created, otherwise a `'junction'` or a `'dir'` link is created depending on the value of the `useJunctions` option. +For dangling links created via `symlink()`, the incoming Vinyl object represents the target, so its stats will determine the desired link type. If `isDirectory()` returns false then a `'file'` link is created, otherwise a `'junction'` or `'dir'` link is created depending on the value of the `useJunctions` option. For dangling links created via `dest()`, the incoming Vinyl object represents the link - typically loaded from disk via `src(..., { resolveSymlinks: false })`. In this case, the link type can't be reasonably determined and defaults to using `'file'`. This may cause unexpected behavior when creating a dangling link to a directory. **Avoid this scenario.** From 8569f85d9e5c84779fe35621a82a2ff00a81186f Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 25 Oct 2018 11:04:30 -0700 Subject: [PATCH 188/216] Docs: Fix formatting of lastRun (#2251) --- docs/api/last-run.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/last-run.md b/docs/api/last-run.md index cf61b31a7..4daa2abfd 100644 --- a/docs/api/last-run.md +++ b/docs/api/last-run.md @@ -9,7 +9,7 @@ sidebar_label: lastRun() Retrieves the last time a task was successfully completed during the current running process. Most useful on subsequent task runs while a watcher is running. -When combined with `src()`, enables incremental builds to speed up your execution times by skipping files that haven't changed since the last successful task completion. +When combined with `src()`, enables incremental builds to speed up execution times by skipping files that haven't changed since the last successful task completion. ## Usage @@ -64,7 +64,7 @@ While there are sensible defaults for the precision of timestamps, they can be r * `lastRun(someTask, 100)` returns 1426000001100 * `lastRun(someTask, 1000)` returns 1426000001000 -A file's [mtime stat][fs-stats-concepts] precision may vary depending on the node version and/or the file system used: +A file's [mtime stat][fs-stats-concepts] precision may vary depending on the node version and/or the file system used. | platform | precision | From e35bdac100d20bfd700a3565961d86a3d077f11c Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 25 Oct 2018 13:10:23 -0700 Subject: [PATCH 189/216] Docs: Add missing link in watch (#2252) --- docs/api/watch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/watch.md b/docs/api/watch.md index 3c0fce72f..2b9dde27b 100644 --- a/docs/api/watch.md +++ b/docs/api/watch.md @@ -46,7 +46,6 @@ When a string or array is passed as `task`, throws an error with the message, "w ### Options - | name | type | default | note | |:-------:|:------:|-----------|--------| | ignoreInitial | boolean | true | If false, the task is called during instantiation as file paths are discovered. Use to trigger the task during startup.
**Note:** This option is passed to [chokidar][chokidar-external] but is defaulted to `true` instead of `false`. | @@ -130,6 +129,7 @@ Removes globs that are being watched, while the watcher continues with the remai | globs | string
array | The globs to be removed. | [chokidar-instance-section]: #chokidar-instance +[options-section]: #options [tasks-concepts]: ../api/concepts.md#tasks [globs-concepts]: ../api/concepts.md#globs [fs-stats-concepts]: ../api/concepts.md#file-system-stats From 6d43750a62f324870d81361c933e3b882f7e3980 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 25 Oct 2018 14:03:04 -0700 Subject: [PATCH 190/216] Docs: Fix broken link in tasks (#2253) --- docs/api/task.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/task.md b/docs/api/task.md index 0fb2ecb4d..12919b06e 100644 --- a/docs/api/task.md +++ b/docs/api/task.md @@ -62,7 +62,7 @@ Since any registered task can be run from the command line, avoid using spaces i | parameter | type | note | |:--------------:|:------:|-------| | taskName | string | An alias for the task function within the the task system. Not needed when using named functions for `taskFunction`. | -| taskFunction
**(required)** | function | A [task function][tasks-concepts] or composed task - generated by `series()` and `parallel()`. Ideally a named function. [Task metadata][task-metadata-section] can be attached to provide extra information to the command line. | +| taskFunction
**(required)** | function | A [task function][task-concepts] or composed task - generated by `series()` and `parallel()`. Ideally a named function. [Task metadata][task-metadata-section] can be attached to provide extra information to the command line. | ### Returns From 8e9fd70e17d0bfc3cb68f22610b813097f249ef3 Mon Sep 17 00:00:00 2001 From: Janice Niemeir Date: Thu, 25 Oct 2018 14:04:08 -0700 Subject: [PATCH 191/216] Docs: Improve punctuation in tree (#2254) --- docs/api/tree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/tree.md b/docs/api/tree.md index e106009f8..1dcfed6d0 100644 --- a/docs/api/tree.md +++ b/docs/api/tree.md @@ -166,7 +166,7 @@ An object detailing the tree of registered tasks - containing nested objects wit Each object may have a `type` property that can be used to determine if the node is a `task` or `function`. -Each object may have a `branch` property that - when `true` - indicates the node was created using `series()` or `parallel()`. +Each object may have a `branch` property that, when `true`, indicates the node was created using `series()` or `parallel()`. ### Options From 96c353d082b340f6b32fdfd354437940869241af Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 25 Oct 2018 17:43:12 -0700 Subject: [PATCH 192/216] Docs: Fix mistake in "Splitting a gulpfile" (fixes #2255) --- docs/getting-started/2-javascript-and-gulpfiles.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/getting-started/2-javascript-and-gulpfiles.md b/docs/getting-started/2-javascript-and-gulpfiles.md index b82193f33..6ccc8161a 100644 --- a/docs/getting-started/2-javascript-and-gulpfiles.md +++ b/docs/getting-started/2-javascript-and-gulpfiles.md @@ -28,8 +28,7 @@ Many users start by adding all logic to a gulpfile. If it ever grows too big, it Each task can be split into its own file, then imported into your gulpfile for composition. Not only does this keep things organized, but it allows you to test each task independently or vary composition based on conditions. -Node's module resolution allows you to replace your `gulpfile.js` with a directory called `gulpfile` that contains an `index.js` file which is treated as a `gulpfile.js`. This directory could then contain your individual modules for tasks. - +Node's module resolution allows you to replace your `gulpfile.js` file with a directory named `gulpfile.js` that contains an `index.js` file which is treated as a `gulpfile.js`. This directory could then contain your individual modules for tasks. [gulpfile-transpilation-advanced]: ../documentation-missing.md [ts-node-module]: https://www.npmjs.com/package/ts-node From c5af6f18f5532628e20a3c187db9a99a96043c05 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Fri, 2 Nov 2018 20:56:05 -0700 Subject: [PATCH 193/216] Docs: Remove front-matter from outdated pages --- docs/CLI.md | 7 ------- docs/FAQ.md | 7 ------- docs/README.md | 7 ------- docs/recipes/README.md | 7 ------- docs/recipes/automate-release-workflow.md | 7 ------- docs/recipes/browserify-multiple-destination.md | 7 ------- docs/recipes/browserify-transforms.md | 7 ------- docs/recipes/browserify-uglify-sourcemap.md | 7 ------- docs/recipes/browserify-with-globs.md | 7 ------- docs/recipes/combining-streams-to-handle-errors.md | 7 ------- docs/recipes/cron-task.md | 7 ------- docs/recipes/delete-files-folder.md | 7 ------- docs/recipes/exports-as-tasks.md | 7 ------- docs/recipes/fast-browserify-builds-with-watchify.md | 7 ------- docs/recipes/handling-the-delete-event-on-watch.md | 7 ------- docs/recipes/incremental-builds-with-concatenate.md | 7 ------- .../maintain-directory-structure-while-globbing.md | 7 ------- docs/recipes/make-stream-from-buffer.md | 7 ------- docs/recipes/minified-and-non-minified.md | 7 ------- docs/recipes/minimal-browsersync-setup-with-gulp4.md | 7 ------- docs/recipes/mocha-test-runner-with-gulp.md | 7 ------- docs/recipes/only-pass-through-changed-files.md | 7 ------- docs/recipes/pass-arguments-from-cli.md | 7 ------- docs/recipes/rebuild-only-files-that-change.md | 7 ------- docs/recipes/rollup-with-rollup-stream.md | 7 ------- docs/recipes/run-grunt-tasks-from-gulp.md | 7 ------- docs/recipes/running-shell-commands.md | 7 ------- docs/recipes/running-task-steps-per-folder.md | 11 ++--------- docs/recipes/running-tasks-in-series.md | 7 ------- .../server-with-livereload-and-css-injection.md | 7 ------- docs/recipes/sharing-streams-with-stream-factories.md | 7 ------- docs/recipes/specifying-a-cwd.md | 7 ------- docs/recipes/split-tasks-across-multiple-files.md | 7 ------- .../templating-with-swig-and-yaml-front-matter.md | 7 ------- docs/recipes/using-external-config-file.md | 7 ------- docs/recipes/using-multiple-sources-in-one-task.md | 7 ------- docs/why-use-pump/README.md | 7 ------- docs/writing-a-plugin/README.md | 7 ------- docs/writing-a-plugin/dealing-with-streams.md | 7 ------- docs/writing-a-plugin/guidelines.md | 7 ------- docs/writing-a-plugin/recommended-modules.md | 7 ------- docs/writing-a-plugin/testing.md | 7 ------- docs/writing-a-plugin/using-buffers.md | 7 ------- 43 files changed, 2 insertions(+), 303 deletions(-) diff --git a/docs/CLI.md b/docs/CLI.md index daa3f5d09..685104125 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,10 +1,3 @@ - - ## gulp CLI docs ### Flags diff --git a/docs/FAQ.md b/docs/FAQ.md index 9c89c143a..f8a649ead 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,10 +1,3 @@ - - # FAQ ## Why gulp? Why not ____? diff --git a/docs/README.md b/docs/README.md index c783b710b..9b5955ac5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,3 @@ - - # gulp documentation * [Getting Started](getting-started/) - Get started with gulp diff --git a/docs/recipes/README.md b/docs/recipes/README.md index e1ec37211..3e822432a 100644 --- a/docs/recipes/README.md +++ b/docs/recipes/README.md @@ -1,10 +1,3 @@ - - # Recipes * [Automate release workflow](automate-release-workflow.md) diff --git a/docs/recipes/automate-release-workflow.md b/docs/recipes/automate-release-workflow.md index 3b929866a..0f18006a9 100644 --- a/docs/recipes/automate-release-workflow.md +++ b/docs/recipes/automate-release-workflow.md @@ -1,10 +1,3 @@ - - # Automate release workflow If your project follows a semantic versioning, it may be a good idea to automatize the steps needed to do a release. diff --git a/docs/recipes/browserify-multiple-destination.md b/docs/recipes/browserify-multiple-destination.md index fa1b65cec..474f58286 100644 --- a/docs/recipes/browserify-multiple-destination.md +++ b/docs/recipes/browserify-multiple-destination.md @@ -1,10 +1,3 @@ - - # Browserify + Globs (multiple destination) This example shows how to set up a task of bundling multiple entry points into multiple destinations using browserify. diff --git a/docs/recipes/browserify-transforms.md b/docs/recipes/browserify-transforms.md index 8659614ff..47a0d4d54 100644 --- a/docs/recipes/browserify-transforms.md +++ b/docs/recipes/browserify-transforms.md @@ -1,10 +1,3 @@ - - # Browserify + Transforms [Browserify](https://github.com/browserify/browserify) has become an important and indispensable diff --git a/docs/recipes/browserify-uglify-sourcemap.md b/docs/recipes/browserify-uglify-sourcemap.md index 99800ed53..58e622292 100644 --- a/docs/recipes/browserify-uglify-sourcemap.md +++ b/docs/recipes/browserify-uglify-sourcemap.md @@ -1,10 +1,3 @@ - - # Browserify + Uglify2 with sourcemaps [Browserify](https://github.com/browserify/browserify) has become an important and indispensable diff --git a/docs/recipes/browserify-with-globs.md b/docs/recipes/browserify-with-globs.md index d9396f1b6..72695fb6d 100644 --- a/docs/recipes/browserify-with-globs.md +++ b/docs/recipes/browserify-with-globs.md @@ -1,10 +1,3 @@ - - # Browserify + Globs [Browserify + Uglify2](https://github.com/gulpjs/gulp/blob/master/docs/recipes/browserify-uglify-sourcemap.md) shows how to setup a basic gulp task to bundle a JavaScript file with its dependencies, and minify the bundle with UglifyJS while preserving source maps. diff --git a/docs/recipes/combining-streams-to-handle-errors.md b/docs/recipes/combining-streams-to-handle-errors.md index 16b561669..7c38654af 100644 --- a/docs/recipes/combining-streams-to-handle-errors.md +++ b/docs/recipes/combining-streams-to-handle-errors.md @@ -1,10 +1,3 @@ - - # Combining streams to handle errors By default, emitting an error on a stream will cause it to be thrown unless it already has a listener attached to the `error` event. This gets a bit tricky when you're working with longer pipelines of streams. diff --git a/docs/recipes/cron-task.md b/docs/recipes/cron-task.md index ad90a6f01..316e0c9bc 100644 --- a/docs/recipes/cron-task.md +++ b/docs/recipes/cron-task.md @@ -1,10 +1,3 @@ - - # Run gulp task via cron job While logged in via a user that has privileges to run `gulp`, run the following: diff --git a/docs/recipes/delete-files-folder.md b/docs/recipes/delete-files-folder.md index 731ae4f91..59e9cc939 100644 --- a/docs/recipes/delete-files-folder.md +++ b/docs/recipes/delete-files-folder.md @@ -1,10 +1,3 @@ - - # Delete files and folders You might want to delete some files before running your build. Since deleting files doesn't work on the file contents, there's no reason to use a gulp plugin. An excellent opportunity to use a vanilla node module. diff --git a/docs/recipes/exports-as-tasks.md b/docs/recipes/exports-as-tasks.md index 892c6bbf5..2eaea4905 100644 --- a/docs/recipes/exports-as-tasks.md +++ b/docs/recipes/exports-as-tasks.md @@ -1,10 +1,3 @@ - - # Exports as Tasks Using the ES2015 module syntax you can use your exports as tasks. diff --git a/docs/recipes/fast-browserify-builds-with-watchify.md b/docs/recipes/fast-browserify-builds-with-watchify.md index aa07f1a93..5513068c2 100644 --- a/docs/recipes/fast-browserify-builds-with-watchify.md +++ b/docs/recipes/fast-browserify-builds-with-watchify.md @@ -1,10 +1,3 @@ - - # Fast browserify builds with watchify As a [browserify](https://github.com/browserify/browserify) project begins to expand, the time to bundle it slowly gets longer and longer. While it might start at 1 second, it's possible to be waiting 30 seconds for your project to build on particularly large projects. diff --git a/docs/recipes/handling-the-delete-event-on-watch.md b/docs/recipes/handling-the-delete-event-on-watch.md index ba7a77cd3..05bd683a1 100644 --- a/docs/recipes/handling-the-delete-event-on-watch.md +++ b/docs/recipes/handling-the-delete-event-on-watch.md @@ -1,10 +1,3 @@ - - # Handling the Delete Event on Watch You can listen for `'unlink'` events to fire on the watcher returned from `gulp.watch`. diff --git a/docs/recipes/incremental-builds-with-concatenate.md b/docs/recipes/incremental-builds-with-concatenate.md index 797bd0cab..c9fdaa0de 100644 --- a/docs/recipes/incremental-builds-with-concatenate.md +++ b/docs/recipes/incremental-builds-with-concatenate.md @@ -1,10 +1,3 @@ - - # Incremental rebuilding, including operating on full file sets The trouble with incremental rebuilds is you often want to operate on _all_ processed files, not just single files. For example, you may want to lint and module-wrap just the file(s) that have changed, then concatenate it with all other linted and module-wrapped files. This is difficult without the use of temp files. diff --git a/docs/recipes/maintain-directory-structure-while-globbing.md b/docs/recipes/maintain-directory-structure-while-globbing.md index 279eac11d..431d30b37 100644 --- a/docs/recipes/maintain-directory-structure-while-globbing.md +++ b/docs/recipes/maintain-directory-structure-while-globbing.md @@ -1,10 +1,3 @@ - - # Maintain Directory Structure while Globbing If you are planning to read a few files/folders from a directory and maintain their relative path, you need to pass `{base: '.'}` as the second argument to `gulp.src()`. diff --git a/docs/recipes/make-stream-from-buffer.md b/docs/recipes/make-stream-from-buffer.md index b376eafce..b7e44b955 100644 --- a/docs/recipes/make-stream-from-buffer.md +++ b/docs/recipes/make-stream-from-buffer.md @@ -1,10 +1,3 @@ - - # Make stream from buffer (memory contents) Sometimes you may need to start a stream with files that their contents are in a variable and not in a physical file. In other words, how to start a 'gulp' stream without using `gulp.src()`. diff --git a/docs/recipes/minified-and-non-minified.md b/docs/recipes/minified-and-non-minified.md index 3cc603a9e..e1a6a4296 100644 --- a/docs/recipes/minified-and-non-minified.md +++ b/docs/recipes/minified-and-non-minified.md @@ -1,10 +1,3 @@ - - # Output both a minified and non-minified version Outputting both a minified and non-minified version of your combined JavaScript files can be achieved by using `gulp-rename` and piping to `dest` twice (once before minifying and once after minifying): diff --git a/docs/recipes/minimal-browsersync-setup-with-gulp4.md b/docs/recipes/minimal-browsersync-setup-with-gulp4.md index 55a5ac38c..ec22c0717 100644 --- a/docs/recipes/minimal-browsersync-setup-with-gulp4.md +++ b/docs/recipes/minimal-browsersync-setup-with-gulp4.md @@ -1,10 +1,3 @@ - - # Minimal BrowserSync setup with Gulp 4 [BrowserSync](https://www.browsersync.io/) is a great tool to streamline diff --git a/docs/recipes/mocha-test-runner-with-gulp.md b/docs/recipes/mocha-test-runner-with-gulp.md index eab0725cd..c6ca3873f 100644 --- a/docs/recipes/mocha-test-runner-with-gulp.md +++ b/docs/recipes/mocha-test-runner-with-gulp.md @@ -1,10 +1,3 @@ - - # Mocha test-runner with gulp ### Passing shared module in all tests diff --git a/docs/recipes/only-pass-through-changed-files.md b/docs/recipes/only-pass-through-changed-files.md index 64d16dbe7..1b0a34496 100644 --- a/docs/recipes/only-pass-through-changed-files.md +++ b/docs/recipes/only-pass-through-changed-files.md @@ -1,10 +1,3 @@ - - # Only pass through changed files Files are passed through the whole pipe chain on every run by default. By using [gulp-changed](https://github.com/sindresorhus/gulp-changed) only changed files will be passed through. This can speed up consecutive runs considerably. diff --git a/docs/recipes/pass-arguments-from-cli.md b/docs/recipes/pass-arguments-from-cli.md index 40c44bc15..1f9c74b14 100644 --- a/docs/recipes/pass-arguments-from-cli.md +++ b/docs/recipes/pass-arguments-from-cli.md @@ -1,10 +1,3 @@ - - # Pass arguments from the command line ```js diff --git a/docs/recipes/rebuild-only-files-that-change.md b/docs/recipes/rebuild-only-files-that-change.md index d60a80257..245ddc4fb 100644 --- a/docs/recipes/rebuild-only-files-that-change.md +++ b/docs/recipes/rebuild-only-files-that-change.md @@ -1,10 +1,3 @@ - - # Rebuild only files that change With [`gulp-watch`](https://github.com/floatdrop/gulp-watch): diff --git a/docs/recipes/rollup-with-rollup-stream.md b/docs/recipes/rollup-with-rollup-stream.md index 4c77aacc1..24679d557 100644 --- a/docs/recipes/rollup-with-rollup-stream.md +++ b/docs/recipes/rollup-with-rollup-stream.md @@ -1,10 +1,3 @@ - - # Rollup with rollup-stream Like Browserify, [Rollup](https://rollupjs.org/) is a bundler and thus only fits naturally into gulp if it's at the start of the pipeline. Unlike Browserify, Rollup doesn't natively produce a stream as output and needs to be wrapped before it can take this position. [rollup-stream](https://github.com/Permutatrix/rollup-stream) does this for you, producing output just like that of Browserify's `bundle()` method—as a result, most of the Browserify recipes here will also work with rollup-stream. diff --git a/docs/recipes/run-grunt-tasks-from-gulp.md b/docs/recipes/run-grunt-tasks-from-gulp.md index ee633125f..51e95e5fb 100644 --- a/docs/recipes/run-grunt-tasks-from-gulp.md +++ b/docs/recipes/run-grunt-tasks-from-gulp.md @@ -1,10 +1,3 @@ - - # Run Grunt Tasks from Gulp It is possible to run Grunt tasks / Grunt plugins from within Gulp. This can be useful during a gradual migration from Grunt to Gulp or if there's a specific plugin that you need. With the described approach no Grunt CLI and no Gruntfile is required. diff --git a/docs/recipes/running-shell-commands.md b/docs/recipes/running-shell-commands.md index 7bc4eb1fe..5d3447476 100644 --- a/docs/recipes/running-shell-commands.md +++ b/docs/recipes/running-shell-commands.md @@ -1,10 +1,3 @@ - - # Running Shell Commands Sometimes it is helpful to be able to call existing command line tools from gulp. diff --git a/docs/recipes/running-task-steps-per-folder.md b/docs/recipes/running-task-steps-per-folder.md index 5ee3b6490..03ff5007b 100644 --- a/docs/recipes/running-task-steps-per-folder.md +++ b/docs/recipes/running-task-steps-per-folder.md @@ -1,10 +1,3 @@ - - # Generating a file per folder If you have a set of folders, and wish to perform a set of tasks on each, for instance... @@ -53,11 +46,11 @@ gulp.task('scripts', function(done) { // write to output .pipe(gulp.dest(scriptsPath)) // minify - .pipe(uglify()) + .pipe(uglify()) // rename to folder.min.js .pipe(rename(folder + '.min.js')) // write to output again - .pipe(gulp.dest(scriptsPath)); + .pipe(gulp.dest(scriptsPath)); }); // process all remaining files in scriptsPath root into main.js and main.min.js files diff --git a/docs/recipes/running-tasks-in-series.md b/docs/recipes/running-tasks-in-series.md index be60816fb..b28e692a3 100644 --- a/docs/recipes/running-tasks-in-series.md +++ b/docs/recipes/running-tasks-in-series.md @@ -1,10 +1,3 @@ - - # Running tasks in series By default, gulp CLI run tasks with maximum concurrency - e.g. it launches diff --git a/docs/recipes/server-with-livereload-and-css-injection.md b/docs/recipes/server-with-livereload-and-css-injection.md index 51b652268..a23cc239d 100644 --- a/docs/recipes/server-with-livereload-and-css-injection.md +++ b/docs/recipes/server-with-livereload-and-css-injection.md @@ -1,10 +1,3 @@ - - # Server with live-reloading and CSS injection With [BrowserSync](https://browsersync.io) and gulp, you can easily create a development server that is accessible to any device on the same WiFi network. BrowserSync also has live-reload built in, so there's nothing else to configure. diff --git a/docs/recipes/sharing-streams-with-stream-factories.md b/docs/recipes/sharing-streams-with-stream-factories.md index 932085235..16c110a06 100644 --- a/docs/recipes/sharing-streams-with-stream-factories.md +++ b/docs/recipes/sharing-streams-with-stream-factories.md @@ -1,10 +1,3 @@ - - # Sharing streams with stream factories If you use the same plugins in multiple tasks you might find yourself getting that itch to DRY things up. This method will allow you to create factories to split out your commonly used stream chains. diff --git a/docs/recipes/specifying-a-cwd.md b/docs/recipes/specifying-a-cwd.md index b6306d6a4..eba0ceca9 100644 --- a/docs/recipes/specifying-a-cwd.md +++ b/docs/recipes/specifying-a-cwd.md @@ -1,10 +1,3 @@ - - # Specifying a new cwd (current working directory) This is helpful for projects using a nested directory structure, such as: diff --git a/docs/recipes/split-tasks-across-multiple-files.md b/docs/recipes/split-tasks-across-multiple-files.md index efdd942fa..afee1be10 100644 --- a/docs/recipes/split-tasks-across-multiple-files.md +++ b/docs/recipes/split-tasks-across-multiple-files.md @@ -1,10 +1,3 @@ - - # Split tasks across multiple files If your `gulpfile.js` is starting to grow too large, you can split the tasks diff --git a/docs/recipes/templating-with-swig-and-yaml-front-matter.md b/docs/recipes/templating-with-swig-and-yaml-front-matter.md index dbda0ad14..50c98e5f7 100644 --- a/docs/recipes/templating-with-swig-and-yaml-front-matter.md +++ b/docs/recipes/templating-with-swig-and-yaml-front-matter.md @@ -1,10 +1,3 @@ - - # Templating with Swig and YAML front-matter Templating can be setup using `gulp-swig` and `gulp-front-matter`: diff --git a/docs/recipes/using-external-config-file.md b/docs/recipes/using-external-config-file.md index 4a2215d48..b073f1dfc 100644 --- a/docs/recipes/using-external-config-file.md +++ b/docs/recipes/using-external-config-file.md @@ -1,10 +1,3 @@ - - # Using external config file Beneficial because it's keeping tasks DRY and config.json can be used by another task runner, like `grunt`. diff --git a/docs/recipes/using-multiple-sources-in-one-task.md b/docs/recipes/using-multiple-sources-in-one-task.md index 6e5622ec4..39e3e3b23 100644 --- a/docs/recipes/using-multiple-sources-in-one-task.md +++ b/docs/recipes/using-multiple-sources-in-one-task.md @@ -1,10 +1,3 @@ - - # Using multiple sources in one task ```js diff --git a/docs/why-use-pump/README.md b/docs/why-use-pump/README.md index c4810725b..de6ebc85f 100644 --- a/docs/why-use-pump/README.md +++ b/docs/why-use-pump/README.md @@ -1,10 +1,3 @@ - - # Why Use Pump? When using `pipe` from the Node.js streams, errors are not propagated forward diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md index d876250cd..bcd708356 100644 --- a/docs/writing-a-plugin/README.md +++ b/docs/writing-a-plugin/README.md @@ -1,10 +1,3 @@ - - # Writing a plugin If you plan to create your own Gulp plugin, you will save time by reading the full documentation. diff --git a/docs/writing-a-plugin/dealing-with-streams.md b/docs/writing-a-plugin/dealing-with-streams.md index d1ad36a87..777b11bff 100644 --- a/docs/writing-a-plugin/dealing-with-streams.md +++ b/docs/writing-a-plugin/dealing-with-streams.md @@ -1,10 +1,3 @@ - - # Dealing with streams > It is highly recommended to write plugins supporting streams. Here is some information on creating a gulp plugin that supports streams. diff --git a/docs/writing-a-plugin/guidelines.md b/docs/writing-a-plugin/guidelines.md index a1d626c93..b810ad8a1 100644 --- a/docs/writing-a-plugin/guidelines.md +++ b/docs/writing-a-plugin/guidelines.md @@ -1,10 +1,3 @@ - - # Guidelines > While these guidelines are totally optional, we **HIGHLY** recommend that everyone follows them. Nobody wants to use a bad plugin. These guidelines will actually help make your life easier by giving you assurance that your plugin fits well within gulp. diff --git a/docs/writing-a-plugin/recommended-modules.md b/docs/writing-a-plugin/recommended-modules.md index a2faa94ce..0b49b39d4 100644 --- a/docs/writing-a-plugin/recommended-modules.md +++ b/docs/writing-a-plugin/recommended-modules.md @@ -1,10 +1,3 @@ - - # Recommended Modules > Sticking to this curated list of recommended modules will make sure you don't violate the plugin guidelines and ensure consistency across plugins. diff --git a/docs/writing-a-plugin/testing.md b/docs/writing-a-plugin/testing.md index cc9f98469..487a87f95 100644 --- a/docs/writing-a-plugin/testing.md +++ b/docs/writing-a-plugin/testing.md @@ -1,10 +1,3 @@ - - # Testing > Testing your plugin is the only way to ensure quality. It brings confidence to your users and makes your life easier. diff --git a/docs/writing-a-plugin/using-buffers.md b/docs/writing-a-plugin/using-buffers.md index b7ea05548..e35e41b59 100644 --- a/docs/writing-a-plugin/using-buffers.md +++ b/docs/writing-a-plugin/using-buffers.md @@ -1,10 +1,3 @@ - - # Using buffers > Here is some information on creating gulp plugin that manipulates buffers. From 9819957a11a1137f3b62bf7476ad26634c635975 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 14 Nov 2018 12:00:01 -0700 Subject: [PATCH 194/216] Scaffold: Add new expense policy --- EXPENSE_POLICY.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 EXPENSE_POLICY.md diff --git a/EXPENSE_POLICY.md b/EXPENSE_POLICY.md new file mode 100644 index 000000000..93308aa0c --- /dev/null +++ b/EXPENSE_POLICY.md @@ -0,0 +1,16 @@ +# Expense Policy + +## Funding can be requested for significant changes made by Core Members. +* Discuss the changes in the private gulp team forum. +* Include a cost estimation with either a fixed price or hours + rate (suggested $50 per hour). +* Notify the team before you exceed an estimate. + +## Bug bounties may be assigned at the Core Members’ discretion to issues of significant importance - usually issues outstanding for at least 6 months. +* Issues with bug bounties will be labeled “Bug Bounty: $x”. +* In order to claim a bug bounty, create a Pull Request that fixes an issue with a “Bug Bounty” label. +* The Pull Request must be reviewed and merged by a Core Member. If competing submissions exist, the best solution will be chosen by a Core Member. All else equal, the first submission will be chosen. +* Once your Pull Request is merged, you can submit an expense to our [Open Collective](https://opencollective.com/gulpjs/expenses/new) which includes the link to your submission in the description (e.g. $100 bug bounty claim for https://github.com/gulpjs/gulp/pull/2226). You will also need to provide an invoice, see the [Open Collective Expense FAQ](https://opencollective.com/faq/expenses) for more details and to get a Google Docs template that you can use. +* Then, add a comment on your Pull Request, noting that you’ve claimed the money, with a link to your Open Collective expense. This is to ensure the same person who fixed the issue is claiming the money. +* Your expense will be validated by a Core Member and then your payment will be dispersed by Open Collective the following Friday. + +## If you're doing other good things for gulp that end up costing you real money, feel free to reach out and we can discuss helping with those expenses! From c6413693a5957b609eeb23ecd7eebfa6fb2dcbe6 Mon Sep 17 00:00:00 2001 From: Takuya Fukuju Date: Thu, 15 Nov 2018 04:56:28 +0900 Subject: [PATCH 195/216] Docs: Fix broken link in Table of Contents (#2260) --- docs/api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/README.md b/docs/api/README.md index fe0d498dd..7f2cc0cb6 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -4,7 +4,7 @@ * [src()](src.md) * [dest()](dest.md) * [symlink()](symlink.md) -* [lastRun()](lastRun.md) +* [lastRun()](last-run.md) * [series()](series.md) * [parallel()](parallel.md) * [watch()](watch.md) From 7239cf197ba69f4ae729da792013794faec6a90f Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 19 Nov 2018 12:44:18 -0700 Subject: [PATCH 196/216] Docs: Update the babel dependencies to install & configuration needed (closes #2136) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1d6e35c7e..7c3e20a2a 100644 --- a/README.md +++ b/README.md @@ -119,21 +119,21 @@ gulp.task('default', build); ## Use latest JavaScript version in your gulpfile -Node already supports a lot of **ES2015**, to avoid compatibility problem we suggest to install Babel and rename your `gulpfile.js` as `gulpfile.babel.js`. +Node already supports a lot of __ES2015+__ features, but to avoid compatibility problems we suggest to install Babel and rename your `gulpfile.js` as `gulpfile.babel.js`. ```sh -npm install --save-dev babel-register babel-preset-es2015 +npm install --save-dev @babel/register @babel/core @babel/preset-env ``` Then create a **.babelrc** file with the preset configuration. ```js { - "presets": [ "es2015" ] + "presets": [ "@babel/preset-env" ] } ``` -And here's the same sample from above written in **ES2015**. +And here's the same sample from above written in **ES2015+**. ```js import gulp from 'gulp'; From 9078c4902358866f8240276ed55b0d58e8a2ecfe Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 19 Nov 2018 15:53:49 -0700 Subject: [PATCH 197/216] Scaffold: Add support-bot template --- .github/support.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/support.yml diff --git a/.github/support.yml b/.github/support.yml new file mode 100644 index 000000000..cd6325e31 --- /dev/null +++ b/.github/support.yml @@ -0,0 +1,13 @@ +# Configuration for support-requests - https://github.com/dessant/support-requests +supportLabel: support +supportComment: > + Issues are reserved for bugs and features. Here are a few places to find answers to your question: + + * For community support, use the `gulp` tag on [StackOverflow](https://stackoverflow.com/questions/tagged/gulp). + + * Participate in community chat on [Gitter](https://gitter.im/gulpjs/gulp). + + * To get paid support directly from the maintainers, sign up for [Tidelift](https://tidelift.com/subscription/pkg/npm-gulp?utm_source=npm-gulp&utm_medium=referral&utm_campaign=support). Subscribers should email support@tidelift.com, mention that it's a question for Gulp, and describe your question. Straightforward questions are answered as part of your subscription. Additional consulting hours are available for more complex help. +close: true +lock: false +setLockReason: false From 75ea6344c2fcd5017fb74375aa81bca6f1e6b041 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 6 Dec 2018 15:59:27 -0700 Subject: [PATCH 198/216] Docs: Add "What's new in 4.0" section (closes #2089) (#2267) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 7c3e20a2a..6b856bb68 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,17 @@ - **Strong Ecosystem** - Use npm modules to do anything you want + over 2000 curated plugins for streaming file transformations - **Simple** - By providing only a minimal API surface, gulp is easy to learn and simple to use +## What's new in 4.0?! + +* The task system was rewritten from the ground-up, allowing task composition using `series()` and `parallel()` methods +* The watcher was updated, now using chokidar (no more need for gulp-watch!), with feature parity to our task system +* First-class support was added for incremental builds using `lastRun()` +* A `symlink()` method was exposed to create symlinks instead of copying files +* Built-in support for sourcemaps was added - the gulp-sourcemaps plugin is no longer necessary! +* Task registration of exported functions - using node or ES exports - is now recommended +* Custom registries were designed, allowing for shared tasks or augmented functionality +* Stream implementations were improved, allowing for better conditional and phased builds + ## Installation There are a few ways to install: From 24e202b3a3dc9e1eecd3fc1e4b69e2b5379928ea Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 10 Dec 2018 10:13:09 -0700 Subject: [PATCH 199/216] Docs: Cleanup README for "latest" bump (#2268) --- README.md | 79 +++++++++++-------------------------------------------- 1 file changed, 16 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 6b856bb68..7991f348b 100644 --- a/README.md +++ b/README.md @@ -28,18 +28,17 @@ ## Installation -There are a few ways to install: +Follow our [Quick Start guide][quick-start]. -* gulp v4.0.0 - `npm install gulp@next` -* gulp v4.0.0-alpha.3 - `npm install gulpjs/gulp#v4.0.0-alpha.3` -* gulp v3.9.1 - `npm install gulp` +## Roadmap + +Find out about all our work-in-progress and outstanding issues at https://github.com/orgs/gulpjs/projects. ## Documentation -For a Getting started guide, API docs, recipes, making a plugin, etc. check out our docs! +Check out the [Getting Started guide][getting-started-guide] and [API docs][api-docs] on our website! -- Check out the [documentation for v4.0.0](/docs/README.md)! __Excuse our dust; these docs might be behind while we get everything updated. Please open an issue if something isn't working.__ -- Using the older v3.9.1? Check out the [documentation at the v3.9.1 tag](https://github.com/gulpjs/gulp/tree/v3.9.1/docs)! +__Excuse our dust! All other docs will be behind until we get everything updated. Please open an issue if something isn't working.__ ## Sample `gulpfile.js` @@ -104,28 +103,23 @@ function watch() { gulp.watch(paths.styles.src, styles); } -/* - * You can use CommonJS `exports` module notation to declare tasks - */ -exports.clean = clean; -exports.styles = styles; -exports.scripts = scripts; -exports.watch = watch; - /* * Specify if tasks run in series or parallel using `gulp.series` and `gulp.parallel` */ var build = gulp.series(clean, gulp.parallel(styles, scripts)); /* - * You can still use `gulp.task` to expose tasks + * You can use CommonJS `exports` module notation to declare tasks */ -gulp.task('build', build); - +exports.clean = clean; +exports.styles = styles; +exports.scripts = scripts; +exports.watch = watch; +exports.build = build; /* * Define default task that can be called by just running `gulp` from cli */ -gulp.task('default', build); +exports.default = build; ``` ## Use latest JavaScript version in your gulpfile @@ -204,13 +198,7 @@ function watchFiles() { } export { watchFiles as watch }; -/* - * You can still use `gulp.task` - * for example to set task names that would otherwise be invalid - */ const build = gulp.series(clean, gulp.parallel(styles, scripts)); -gulp.task('build', build); - /* * Export a default task */ @@ -244,40 +232,6 @@ Task run times are saved in memory and are lost when gulp exits. It will only save time during the `watch` task when running the `images` task for a second time. -If you want to compare modification time between files instead, we recommend these plugins: -- [gulp-changed]; -- or [gulp-newer] - supports many:1 source:dest. - -[gulp-newer] example: -```js -function images() { - var dest = 'build/img'; - return gulp.src(paths.images) - .pipe(newer(dest)) // pass through newer images only - .pipe(imagemin({optimizationLevel: 5})) - .pipe(gulp.dest(dest)); -} -``` - -If you can't simply filter out unchanged files, but need them in a later phase -of the stream, we recommend these plugins: -- [gulp-cached] - in-memory file cache, not for operation on sets of files -- [gulp-remember] - pairs nicely with gulp-cached - -[gulp-remember] example: -```js -function scripts() { - return gulp.src(scriptsGlob) - .pipe(cache('scripts')) // only pass through changed files - .pipe(header('(function () {')) // do special things to the changed files... - .pipe(footer('})();')) // for example, - // add a simple module wrap to each file - .pipe(remember('scripts')) // add back all files to the stream - .pipe(concat('app.js')) // do things that require all files - .pipe(gulp.dest('public/')) -} -``` - ## Want to contribute? Anyone can help make this project better - check out our [Contributing guide](/CONTRIBUTING.md)! @@ -320,7 +274,6 @@ Become a sponsor to get your logo on our README on Github. [backers-image]: https://opencollective.com/gulpjs/backers.svg [sponsors-image]: https://opencollective.com/gulpjs/sponsors.svg -[gulp-cached]: https://github.com/contra/gulp-cached -[gulp-remember]: https://github.com/ahaurw01/gulp-remember -[gulp-changed]: https://github.com/sindresorhus/gulp-changed -[gulp-newer]: https://github.com/tschaub/gulp-newer +[quick-start]: https://gulpjs.com/docs/en/getting-started/quick-start +[getting-started-guide]: https://gulpjs.com/docs/en/getting-started/quick-start +[api-docs]: https://gulpjs.com/docs/en/api/concepts From ed27cbeb576111641a72a9a89b8c3e1eb443172b Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 10 Dec 2018 10:26:31 -0700 Subject: [PATCH 200/216] Docs: Revert "next" reference now that 4.0 is latest --- docs/getting-started/1-quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/1-quick-start.md b/docs/getting-started/1-quick-start.md index c3210274f..6953e7f53 100644 --- a/docs/getting-started/1-quick-start.md +++ b/docs/getting-started/1-quick-start.md @@ -48,7 +48,7 @@ This will guide you through giving your project a name, version, description, et ## Install the gulp package in your devDependencies ```sh -npm install --save-dev gulp@next +npm install --save-dev gulp ``` ## Verify your gulp versions From b2c6c7e4c6cc85e6f7579b3c32081590d9517728 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Wed, 20 Mar 2019 15:06:50 -0400 Subject: [PATCH 201/216] Build: Add Azure Pipelines CI (#2299) --- .ci/.azure-pipelines-steps.yml | 38 +++++++++++++++ .ci/.azure-pipelines.yml | 84 ++++++++++++++++++++++++++++++++++ .gitignore | 4 ++ package.json | 10 ++-- 4 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 .ci/.azure-pipelines-steps.yml create mode 100644 .ci/.azure-pipelines.yml diff --git a/.ci/.azure-pipelines-steps.yml b/.ci/.azure-pipelines-steps.yml new file mode 100644 index 000000000..ef26e4e60 --- /dev/null +++ b/.ci/.azure-pipelines-steps.yml @@ -0,0 +1,38 @@ +steps: +- script: npm i -g npm@$(npm_version) + displayName: Use legacy npm version $(npm_version) + condition: ne(variables['npm_version'], '') + +- task: NodeTool@0 + inputs: + versionSpec: '$(node_version)' + displayName: Use Node $(node_version) + +- script: npm install + displayName: npm install + +- script: npm test + displayName: Run tests + +- script: npm run coveralls + displayName: Run coveralls + env: + # Pretend to be AppVeyor for now + APPVEYOR: true + APPVEYOR_BUILD_NUMBER: $(Build.BuildNumber) + APPVEYOR_BUILD_ID: $(Agent.OS)_$(node_version) + APPVEYOR_REPO_COMMIT: $(Build.SourceVersion) + APPVEYOR_REPO_BRANCH: $(Build.SourceBranchName) + # Overwrite the GitLab Service Name + COVERALLS_SERVICE_NAME: Azure Pipelines + COVERALLS_REPO_TOKEN: $(COVERALLS_REPO_TOKEN_SECRET) + COVERALLS_PARALLEL: true + CI_PULL_REQUEST: $(System.PullRequest.PullRequestNumber) + +- script: npm run azure-pipelines + displayName: Write tests to xml + +- task: PublishTestResults@2 + inputs: + testResultsFiles: '**/test.xunit' + condition: succeededOrFailed() diff --git a/.ci/.azure-pipelines.yml b/.ci/.azure-pipelines.yml new file mode 100644 index 000000000..dcdd68e63 --- /dev/null +++ b/.ci/.azure-pipelines.yml @@ -0,0 +1,84 @@ +trigger: +- master +- releases/* + +jobs: + - job: Test_Linux + displayName: Run Tests on Linux + pool: + vmImage: "Ubuntu 16.04" + strategy: + matrix: + Node_v10: + node_version: 10 + Node_v8: + node_version: 8 + Node_v6: + node_version: 6 + Node_v4: + node_version: 4 + Node_v0_12: + node_version: 0.12 + Node_v0_10: + node_version: 0.10 + steps: + - template: .azure-pipelines-steps.yml + + - job: Test_Windows + displayName: Run Tests on Windows + pool: + vmImage: vs2017-win2016 + strategy: + matrix: + Node_v10: + node_version: 10 + Node_v8: + node_version: 8 + Node_v6: + node_version: 6 + Node_v4: + node_version: 4 + npm_version: 2 + Node_v0_12: + node_version: 0.12 + npm_version: 2 + Node_v0_10: + node_version: 0.10 + npm_version: 2 + steps: + - template: .azure-pipelines-steps.yml + + - job: Test_MacOS + displayName: Run Tests on MacOS + pool: + vmImage: macos-10.13 + strategy: + matrix: + Node_v10: + node_version: 10 + Node_v8: + node_version: 8 + Node_v6: + node_version: 6 + Node_v4: + node_version: 4 + Node_v0_12: + node_version: 0.12 + Node_v0_10: + node_version: 0.10 + steps: + - template: .azure-pipelines-steps.yml + + - job: Notify_Coveralls + displayName: Notify Coveralls that the parallel report is done + pool: + vmImage: "Ubuntu 16.04" + dependsOn: + - Test_Linux + - Test_Windows + - Test_MacOS + steps: + - script: curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$BUILD_NAME&payload[status]=done" + env: + COVERALLS_REPO_TOKEN: $(COVERALLS_REPO_TOKEN_SECRET) + BUILD_NAME: $(Build.BuildNumber) diff --git a/.gitignore b/.gitignore index ac88dd1f0..466085e12 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ lib-cov # Coverage directory used by tools like istanbul coverage +.nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt @@ -29,3 +30,6 @@ node_modules # Garbage files .DS_Store + +# Test results +test.xunit diff --git a/package.json b/package.json index a4ce8bcaa..38b855cf4 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ "scripts": { "lint": "eslint .", "pretest": "npm run lint", - "test": "mocha --async-only", - "cover": "istanbul cover _mocha --report lcovonly", - "coveralls": "npm run cover && istanbul-coveralls" + "test": "nyc mocha --async-only", + "azure-pipelines": "nyc mocha --async-only --reporter xunit -O output=test.xunit", + "coveralls": "nyc report --reporter=text-lcov | coveralls" }, "dependencies": { "glob-watcher": "^5.0.0", @@ -36,13 +36,13 @@ "vinyl-fs": "^3.0.0" }, "devDependencies": { + "coveralls": "github:phated/node-coveralls#2.x", "eslint": "^2.13.1", "eslint-config-gulp": "^3.0.1", "expect": "^1.20.2", - "istanbul": "^0.4.3", - "istanbul-coveralls": "^1.0.3", "mkdirp": "^0.5.1", "mocha": "^3.0.0", + "nyc": "^10.3.2", "rimraf": "^2.2.5" }, "keywords": [ From 34a6d53e85cba8e00d75391fc15a50dca6f9d26a Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 23 Mar 2019 19:47:18 -0700 Subject: [PATCH 202/216] Build: Fix Azure comment (#2307) --- .ci/.azure-pipelines-steps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/.azure-pipelines-steps.yml b/.ci/.azure-pipelines-steps.yml index ef26e4e60..894c21eee 100644 --- a/.ci/.azure-pipelines-steps.yml +++ b/.ci/.azure-pipelines-steps.yml @@ -23,7 +23,7 @@ steps: APPVEYOR_BUILD_ID: $(Agent.OS)_$(node_version) APPVEYOR_REPO_COMMIT: $(Build.SourceVersion) APPVEYOR_REPO_BRANCH: $(Build.SourceBranchName) - # Overwrite the GitLab Service Name + # Overwrite the AppVeyor Service Name COVERALLS_SERVICE_NAME: Azure Pipelines COVERALLS_REPO_TOKEN: $(COVERALLS_REPO_TOKEN_SECRET) COVERALLS_PARALLEL: true From f3f05486b872a85bc810492469d4b0e3d6da2fb4 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 25 Mar 2019 22:51:06 -0400 Subject: [PATCH 203/216] Docs: Add Azure Pipelines badge (#2310) --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7991f348b..129b778d1 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

The streaming build system

-[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![AppVeyor Build Status][appveyor-image]][appveyor-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![OpenCollective Backers][backer-badge]][backer-url] [![OpenCollective Sponsors][sponsor-badge]][sponsor-url] [![Gitter chat][gitter-image]][gitter-url] +[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Azure Pipelines Build Status][azure-pipelines-image]][azure-pipelines-url] [![Build Status][travis-image]][travis-url] [![AppVeyor Build Status][appveyor-image]][appveyor-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![OpenCollective Backers][backer-badge]][backer-url] [![OpenCollective Sponsors][sponsor-badge]][sponsor-url] [![Gitter chat][gitter-image]][gitter-url] ## What is gulp? @@ -252,6 +252,9 @@ Become a sponsor to get your logo on our README on Github. [npm-url]: https://www.npmjs.com/package/gulp [npm-image]: https://img.shields.io/npm/v/gulp.svg +[azure-pipelines-url]: https://dev.azure.com/gulpjs/gulp/_build/latest?definitionId=1&branchName=master +[azure-pipelines-image]: https://dev.azure.com/gulpjs/gulp/_apis/build/status/gulp?branchName=master + [travis-url]: https://travis-ci.org/gulpjs/gulp [travis-image]: https://img.shields.io/travis/gulpjs/gulp.svg?label=travis-ci From 53b9037a08807f0fb5a8837f3550f501af36f05d Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Thu, 28 Mar 2019 18:04:36 +0000 Subject: [PATCH 204/216] Docs: Add note about transpilation to "Splitting a Gulpfile" section (closes #2311) (#2312) --- docs/getting-started/2-javascript-and-gulpfiles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/2-javascript-and-gulpfiles.md b/docs/getting-started/2-javascript-and-gulpfiles.md index 6ccc8161a..82573a13c 100644 --- a/docs/getting-started/2-javascript-and-gulpfiles.md +++ b/docs/getting-started/2-javascript-and-gulpfiles.md @@ -28,7 +28,7 @@ Many users start by adding all logic to a gulpfile. If it ever grows too big, it Each task can be split into its own file, then imported into your gulpfile for composition. Not only does this keep things organized, but it allows you to test each task independently or vary composition based on conditions. -Node's module resolution allows you to replace your `gulpfile.js` file with a directory named `gulpfile.js` that contains an `index.js` file which is treated as a `gulpfile.js`. This directory could then contain your individual modules for tasks. +Node's module resolution allows you to replace your `gulpfile.js` file with a directory named `gulpfile.js` that contains an `index.js` file which is treated as a `gulpfile.js`. This directory could then contain your individual modules for tasks. If you are using a transpiler, name the folder and file accordingly. [gulpfile-transpilation-advanced]: ../documentation-missing.md [ts-node-module]: https://www.npmjs.com/package/ts-node From 88437f2b71f13c44061f6af6b231adbc4a56842a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=AD=90=20B3none?= Date: Sat, 30 Mar 2019 19:55:26 +0000 Subject: [PATCH 205/216] Docs: Improve wording of file rename (#2314) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 129b778d1..013c40770 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ exports.default = build; ## Use latest JavaScript version in your gulpfile -Node already supports a lot of __ES2015+__ features, but to avoid compatibility problems we suggest to install Babel and rename your `gulpfile.js` as `gulpfile.babel.js`. +Node already supports a lot of __ES2015+__ features, but to avoid compatibility problems we suggest to install Babel and rename your `gulpfile.js` to `gulpfile.babel.js`. ```sh npm install --save-dev @babel/register @babel/core @babel/preset-env From d3734d34288c63d9c80af4d2cb0457e0db5b9c51 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 21 Apr 2019 18:45:51 +0200 Subject: [PATCH 206/216] Upgrade: Update glob-watcher, gulp-cli, and undertaker dependencies & rimraf devDep --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 38b855cf4..a0be8efc7 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,9 @@ "coveralls": "nyc report --reporter=text-lcov | coveralls" }, "dependencies": { - "glob-watcher": "^5.0.0", - "gulp-cli": "^2.0.0", - "undertaker": "^1.0.0", + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", "vinyl-fs": "^3.0.0" }, "devDependencies": { @@ -43,7 +43,7 @@ "mkdirp": "^0.5.1", "mocha": "^3.0.0", "nyc": "^10.3.2", - "rimraf": "^2.2.5" + "rimraf": "^2.6.3" }, "keywords": [ "build", From ea3bba4fdf9f2b508699f69569d0191b8c5bc10c Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sun, 21 Apr 2019 18:55:44 +0200 Subject: [PATCH 207/216] Release: 4.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a0be8efc7..a67c7d2be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gulp", - "version": "4.0.0", + "version": "4.0.1", "description": "The streaming build system.", "homepage": "https://gulpjs.com", "author": "Gulp Team (https://gulpjs.com/)", From 5d81f42f9ddc4aa0a0721057eff8b8e24b65861c Mon Sep 17 00:00:00 2001 From: Rafael <37901937+ralomach@users.noreply.github.com> Date: Sat, 27 Apr 2019 10:23:25 -0300 Subject: [PATCH 208/216] Docs: Fix typo in Explaining Globs (#2326) --- docs/getting-started/6-explaining-globs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting-started/6-explaining-globs.md b/docs/getting-started/6-explaining-globs.md index de524d843..d362c28c5 100644 --- a/docs/getting-started/6-explaining-globs.md +++ b/docs/getting-started/6-explaining-globs.md @@ -52,13 +52,13 @@ Here, the glob is appropriately restricted to the `scripts/` directory. It will Since globs are matched in array order, a negative glob must follow at least one non-negative glob in an array. The first finds a set of matches, then the negative glob removes a portion of those results. These are most performant when they only include literal characters. ```js -['script/**/*.js', '!scripts/vendor/'] +['scripts/**/*.js', '!scripts/vendor/'] ``` If any non-negative globs follow a negative, nothing will be removed from the later set of matches. ```js -['script/**/*.js', '!scripts/vendor/', 'scripts/vendor/react.js'] +['scripts/**/*.js', '!scripts/vendor/', 'scripts/vendor/react.js'] ``` Negative globs can be used as an alternative for restricting double-star globs. From ea52a927843ff0e84f142f85813572bd5144a656 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 27 Apr 2019 15:30:09 +0200 Subject: [PATCH 209/216] Docs: Fix syntax error in lastRun API docs (closes #2315) --- docs/api/last-run.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/api/last-run.md b/docs/api/last-run.md index 4daa2abfd..f1bb581e8 100644 --- a/docs/api/last-run.md +++ b/docs/api/last-run.md @@ -23,11 +23,9 @@ function images() { .pipe(dest('build/img/')); } -function watch() { +exports.default = function() { watch('src/images/**/*.jpg', images); -} - -exports.watch = watch; +}; ``` From d9162761f276fce5fe54bc0a60c7eb9efcd5e249 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 27 Apr 2019 15:35:23 +0200 Subject: [PATCH 210/216] Docs: Add default task wrappers to Watching Files examples to make runnable (ref #2322) --- docs/getting-started/8-watching-files.md | 58 ++++++++++++++---------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/docs/getting-started/8-watching-files.md b/docs/getting-started/8-watching-files.md index 9b8268ebe..bb3bf994b 100644 --- a/docs/getting-started/8-watching-files.md +++ b/docs/getting-started/8-watching-files.md @@ -29,10 +29,12 @@ function css(cb) { cb(); } -// You can use a single task -watch('src/*.css', css); -// Or a composed task -watch('src/*.js', series(clean, javascript)); +exports.default = function() { + // You can use a single task + watch('src/*.css', css); + // Or a composed task + watch('src/*.js', series(clean, javascript)); +}; ``` ## Warning: avoid synchronous @@ -49,11 +51,13 @@ If you need to use different events, you can use the `events` option when callin ```js const { watch } = require('gulp'); -// All events will be watched -watch('src/*.js', { events: 'all' }, function(cb) { - // body omitted - cb(); -}); +exports.default = function() { + // All events will be watched + watch('src/*.js', { events: 'all' }, function(cb) { + // body omitted + cb(); + }); +}; ``` ## Initial execution @@ -65,11 +69,13 @@ To execute tasks before the first file change, set the `ignoreInitial` option to ```js const { watch } = require('gulp'); -// The task will be executed upon startup -watch('src/*.js', { ignoreInitial: false }, function(cb) { - // body omitted - cb(); -}); +exports.default = function() { + // The task will be executed upon startup + watch('src/*.js', { ignoreInitial: false }, function(cb) { + // body omitted + cb(); + }); +}; ``` ## Queueing @@ -81,11 +87,13 @@ To disable queueing, set the `queue` option to `false`. ```js const { watch } = require('gulp'); -// The task will be run (concurrently) for every change made -watch('src/*.js', { queue: false }, function(cb) { - // body omitted - cb(); -}); +exports.default = function() { + // The task will be run (concurrently) for every change made + watch('src/*.js', { queue: false }, function(cb) { + // body omitted + cb(); + }); +}; ``` ## Delay @@ -97,11 +105,13 @@ To adjust the delay duration, set the `delay` option to a positive integer. ```js const { watch } = require('gulp'); -// The task won't be run until 500ms have elapsed since the first change -watch('src/*.js', { delay: 500 }, function(cb) { - // body omitted - cb(); -}); +exports.default = function() { + // The task won't be run until 500ms have elapsed since the first change + watch('src/*.js', { delay: 500 }, function(cb) { + // body omitted + cb(); + }); +}; ``` ## Using the watcher instance From 1693a1127116a6804a892a4b931c232b1bec9162 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 27 Apr 2019 15:50:17 +0200 Subject: [PATCH 211/216] Docs: Remove next tag from recipes (closes #2277) --- docs/recipes/delete-files-folder.md | 4 ++-- docs/recipes/mocha-test-runner-with-gulp.md | 4 ++-- docs/recipes/only-pass-through-changed-files.md | 2 +- docs/recipes/pass-arguments-from-cli.md | 2 +- docs/recipes/rollup-with-rollup-stream.md | 4 ++-- docs/recipes/run-grunt-tasks-from-gulp.md | 2 +- docs/recipes/server-with-livereload-and-css-injection.md | 2 +- docs/recipes/split-tasks-across-multiple-files.md | 2 +- docs/recipes/using-external-config-file.md | 2 +- docs/recipes/using-multiple-sources-in-one-task.md | 4 ++-- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/recipes/delete-files-folder.md b/docs/recipes/delete-files-folder.md index 59e9cc939..d974c6283 100644 --- a/docs/recipes/delete-files-folder.md +++ b/docs/recipes/delete-files-folder.md @@ -5,7 +5,7 @@ You might want to delete some files before running your build. Since deleting fi Let's use the [`del`](https://github.com/sindresorhus/del) module for this example as it supports multiple files and [globbing](https://github.com/sindresorhus/multimatch#globbing-patterns): ```sh -$ npm install --save-dev gulp@next del +$ npm install --save-dev gulp del ``` Imagine the following file structure: @@ -49,7 +49,7 @@ You might want to delete some files after processing them in a pipeline. We'll use [vinyl-paths](https://github.com/sindresorhus/vinyl-paths) to easily get the file path of files in the stream and pass it to the `del` method. ```sh -$ npm install --save-dev gulp@next del vinyl-paths +$ npm install --save-dev gulp del vinyl-paths ``` Imagine the following file structure: diff --git a/docs/recipes/mocha-test-runner-with-gulp.md b/docs/recipes/mocha-test-runner-with-gulp.md index c6ca3873f..4a6b21797 100644 --- a/docs/recipes/mocha-test-runner-with-gulp.md +++ b/docs/recipes/mocha-test-runner-with-gulp.md @@ -3,7 +3,7 @@ ### Passing shared module in all tests ```js -// npm install gulp@next gulp-mocha +// npm install gulp gulp-mocha var gulp = require('gulp'); var mocha = require('gulp-mocha'); @@ -22,7 +22,7 @@ gulp.task('default', function() { ### Running mocha tests when files change ```js -// npm install gulp@next gulp-mocha gulplog +// npm install gulp gulp-mocha gulplog var gulp = require('gulp'); var mocha = require('gulp-mocha'); diff --git a/docs/recipes/only-pass-through-changed-files.md b/docs/recipes/only-pass-through-changed-files.md index 1b0a34496..f2db59722 100644 --- a/docs/recipes/only-pass-through-changed-files.md +++ b/docs/recipes/only-pass-through-changed-files.md @@ -4,7 +4,7 @@ Files are passed through the whole pipe chain on every run by default. By using ```js -// npm install --save-dev gulp@next gulp-changed gulp-jscs gulp-uglify +// npm install --save-dev gulp gulp-changed gulp-jscs gulp-uglify var gulp = require('gulp'); var changed = require('gulp-changed'); diff --git a/docs/recipes/pass-arguments-from-cli.md b/docs/recipes/pass-arguments-from-cli.md index 1f9c74b14..48185b6b1 100644 --- a/docs/recipes/pass-arguments-from-cli.md +++ b/docs/recipes/pass-arguments-from-cli.md @@ -1,7 +1,7 @@ # Pass arguments from the command line ```js -// npm install --save-dev gulp@next gulp-if gulp-uglify minimist +// npm install --save-dev gulp gulp-if gulp-uglify minimist var gulp = require('gulp'); var gulpif = require('gulp-if'); diff --git a/docs/recipes/rollup-with-rollup-stream.md b/docs/recipes/rollup-with-rollup-stream.md index 24679d557..2a8840f26 100644 --- a/docs/recipes/rollup-with-rollup-stream.md +++ b/docs/recipes/rollup-with-rollup-stream.md @@ -4,7 +4,7 @@ Like Browserify, [Rollup](https://rollupjs.org/) is a bundler and thus only fits ## Basic usage ```js -// npm install --save-dev gulp@next rollup-stream vinyl-source-stream +// npm install --save-dev gulp rollup-stream vinyl-source-stream var gulp = require('gulp'); var rollup = require('rollup-stream'); var source = require('vinyl-source-stream'); @@ -24,7 +24,7 @@ gulp.task('rollup', function() { ## Usage with sourcemaps ```js -// npm install --save-dev gulp@next rollup-stream gulp-sourcemaps vinyl-source-stream vinyl-buffer +// npm install --save-dev gulp rollup-stream gulp-sourcemaps vinyl-source-stream vinyl-buffer // optional: npm install --save-dev gulp-rename var gulp = require('gulp'); var rollup = require('rollup-stream'); diff --git a/docs/recipes/run-grunt-tasks-from-gulp.md b/docs/recipes/run-grunt-tasks-from-gulp.md index 51e95e5fb..df2ca1771 100644 --- a/docs/recipes/run-grunt-tasks-from-gulp.md +++ b/docs/recipes/run-grunt-tasks-from-gulp.md @@ -7,7 +7,7 @@ It is possible to run Grunt tasks / Grunt plugins from within Gulp. This can be very simple example `gulpfile.js`: ```js -// npm install gulp@next grunt grunt-contrib-copy --save-dev +// npm install gulp grunt grunt-contrib-copy --save-dev var gulp = require('gulp'); var grunt = require('grunt'); diff --git a/docs/recipes/server-with-livereload-and-css-injection.md b/docs/recipes/server-with-livereload-and-css-injection.md index a23cc239d..ee400b8f2 100644 --- a/docs/recipes/server-with-livereload-and-css-injection.md +++ b/docs/recipes/server-with-livereload-and-css-injection.md @@ -5,7 +5,7 @@ With [BrowserSync](https://browsersync.io) and gulp, you can easily create a dev First install the modules: ```sh -$ npm install --save-dev gulp@next browser-sync +$ npm install --save-dev gulp browser-sync ``` Then, considering the following file structure... diff --git a/docs/recipes/split-tasks-across-multiple-files.md b/docs/recipes/split-tasks-across-multiple-files.md index afee1be10..a93217a94 100644 --- a/docs/recipes/split-tasks-across-multiple-files.md +++ b/docs/recipes/split-tasks-across-multiple-files.md @@ -17,7 +17,7 @@ tasks/ Install the `gulp-hub` module: ```sh -npm install --save-dev gulp@next gulp-hub +npm install --save-dev gulp gulp-hub ``` Add the following lines to your `gulpfile.js` file: diff --git a/docs/recipes/using-external-config-file.md b/docs/recipes/using-external-config-file.md index b073f1dfc..6c740761b 100644 --- a/docs/recipes/using-external-config-file.md +++ b/docs/recipes/using-external-config-file.md @@ -30,7 +30,7 @@ Beneficial because it's keeping tasks DRY and config.json can be used by another ###### `gulpfile.js` ```js -// npm install --save-dev gulp@next gulp-uglify merge-stream +// npm install --save-dev gulp gulp-uglify merge-stream var gulp = require('gulp'); var uglify = require('gulp-uglify'); var merge = require('merge-stream'); diff --git a/docs/recipes/using-multiple-sources-in-one-task.md b/docs/recipes/using-multiple-sources-in-one-task.md index 39e3e3b23..422793783 100644 --- a/docs/recipes/using-multiple-sources-in-one-task.md +++ b/docs/recipes/using-multiple-sources-in-one-task.md @@ -1,7 +1,7 @@ # Using multiple sources in one task ```js -// npm install --save-dev gulp@next merge-stream +// npm install --save-dev gulp merge-stream var gulp = require('gulp'); var merge = require('merge-stream'); @@ -20,7 +20,7 @@ gulp.task('test', function() { `gulp.src` will emit files in the order they were added: ```js -// npm install gulp@next gulp-concat +// npm install gulp gulp-concat var gulp = require('gulp'); var concat = require('gulp-concat'); From 3c66d95f014b99f79e0375c43cfa17a7269732a2 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 27 Apr 2019 16:10:39 +0200 Subject: [PATCH 212/216] Docs: Fix the Negative Globs section & examples (closes #2297) --- docs/getting-started/6-explaining-globs.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/getting-started/6-explaining-globs.md b/docs/getting-started/6-explaining-globs.md index d362c28c5..11f34fae0 100644 --- a/docs/getting-started/6-explaining-globs.md +++ b/docs/getting-started/6-explaining-globs.md @@ -49,25 +49,25 @@ Here, the glob is appropriately restricted to the `scripts/` directory. It will ## Special character: ! (negative) -Since globs are matched in array order, a negative glob must follow at least one non-negative glob in an array. The first finds a set of matches, then the negative glob removes a portion of those results. These are most performant when they only include literal characters. +Since globs are matched in array order, a negative glob must follow at least one non-negative glob in an array. The first finds a set of matches, then the negative glob removes a portion of those results. When excluding all files within a directory, you must add `/**` after the directory name, which the globbing library optimizes internally. ```js -['scripts/**/*.js', '!scripts/vendor/'] +['scripts/**/*.js', '!scripts/vendor/**'] ``` If any non-negative globs follow a negative, nothing will be removed from the later set of matches. ```js -['scripts/**/*.js', '!scripts/vendor/', 'scripts/vendor/react.js'] +['scripts/**/*.js', '!scripts/vendor/**', 'scripts/vendor/react.js'] ``` Negative globs can be used as an alternative for restricting double-star globs. ```js -['**/*.js', '!node_modules/'] +['**/*.js', '!node_modules/**'] ``` -In the previous example, if the negative glob was `!node_modules/**/*.js`, every match would have to be compared against the negative glob, which would be extremely slow. +In the previous example, if the negative glob was `!node_modules/**/*.js`, the globbing library wouldn't optimize the negation and every match would have to be compared against the negative glob, which would be extremely slow. To ignore all files in a directory, only add the `/**` glob after the directory name. ## Overlapping globs From 4091bd3aff08801625c6128eec079af4a7b5dccb Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Sat, 27 Apr 2019 16:24:54 +0200 Subject: [PATCH 213/216] Docs: Add notes about esm support (closes #2278) --- README.md | 2 ++ docs/getting-started/2-javascript-and-gulpfiles.md | 3 +++ 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index 013c40770..aac4230e1 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,8 @@ exports.default = build; ## Use latest JavaScript version in your gulpfile +__Most new versions of node support most features that Babel provides, except the `import`/`export` syntax. When only that syntax is desired, rename to `gulpfile.esm.js`, install the [esm][esm-module] module, and skip the Babel portion below.__ + Node already supports a lot of __ES2015+__ features, but to avoid compatibility problems we suggest to install Babel and rename your `gulpfile.js` to `gulpfile.babel.js`. ```sh diff --git a/docs/getting-started/2-javascript-and-gulpfiles.md b/docs/getting-started/2-javascript-and-gulpfiles.md index 82573a13c..a15fad112 100644 --- a/docs/getting-started/2-javascript-and-gulpfiles.md +++ b/docs/getting-started/2-javascript-and-gulpfiles.md @@ -20,6 +20,8 @@ You can write a gulpfile using a language that requires transpilation, like Type * For TypeScript, rename to `gulpfile.ts` and install the [ts-node][ts-node-module] module. * For Babel, rename to `gulpfile.babel.js` and install the [@babel/register][babel-register-module] module. +__Most new versions of node support most features that TypeScript or Babel provide, except the `import`/`export` syntax. When only that syntax is desired, rename to `gulpfile.esm.js` and install the [esm][esm-module] module.__ + For a more advanced dive into this topic and the full list of supported extensions, see our [gulpfile transpilation][gulpfile-transpilation-advanced] documentation. ## Splitting a gulpfile @@ -33,3 +35,4 @@ Node's module resolution allows you to replace your `gulpfile.js` file with a di [gulpfile-transpilation-advanced]: ../documentation-missing.md [ts-node-module]: https://www.npmjs.com/package/ts-node [babel-register-module]: https://www.npmjs.com/package/@babel/register +[esm-module]: https://www.npmjs.com/package/esm From 5667666ffae15c169f99c7096ca59c05f9440272 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 6 May 2019 19:09:08 +0200 Subject: [PATCH 214/216] Fix: Bind src/dest/symlink to the gulp instance to support esm exports (ref standard-things/esm#797) --- index.js | 3 +++ test/index.test.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 test/index.test.js diff --git a/index.js b/index.js index cddf7eb6f..8b7352129 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,9 @@ function Gulp() { this.registry = this.registry.bind(this); this.tree = this.tree.bind(this); this.lastRun = this.lastRun.bind(this); + this.src = this.src.bind(this); + this.dest = this.dest.bind(this); + this.symlink = this.symlink.bind(this); } util.inherits(Gulp, Undertaker); diff --git a/test/index.test.js b/test/index.test.js new file mode 100644 index 000000000..8921084d8 --- /dev/null +++ b/test/index.test.js @@ -0,0 +1,60 @@ +'use strict'; + +var expect = require('expect'); + +var gulp = require('../'); + +describe('gulp', function() { + + describe('hasOwnProperty', function() { + it('src', function(done) { + expect(gulp.hasOwnProperty('src')).toEqual(true); + done(); + }); + + it('dest', function(done) { + expect(gulp.hasOwnProperty('dest')).toEqual(true); + done(); + }); + + it('symlink', function(done) { + expect(gulp.hasOwnProperty('symlink')).toEqual(true); + done(); + }); + + it('watch', function(done) { + expect(gulp.hasOwnProperty('watch')).toEqual(true); + done(); + }); + + it('task', function(done) { + expect(gulp.hasOwnProperty('task')).toEqual(true); + done(); + }); + + it('series', function(done) { + expect(gulp.hasOwnProperty('series')).toEqual(true); + done(); + }); + + it('parallel', function(done) { + expect(gulp.hasOwnProperty('parallel')).toEqual(true); + done(); + }); + + it('tree', function(done) { + expect(gulp.hasOwnProperty('tree')).toEqual(true); + done(); + }); + + it('lastRun', function(done) { + expect(gulp.hasOwnProperty('lastRun')).toEqual(true); + done(); + }); + + it('registry', function(done) { + expect(gulp.hasOwnProperty('registry')).toEqual(true); + done(); + }); + }); +}); From b4b5a68373b4c58d6b92321cea64a6ffcc3ff042 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 6 May 2019 19:15:33 +0200 Subject: [PATCH 215/216] Build: Add node 12 to Travis & Azure --- .ci/.azure-pipelines.yml | 6 ++++++ .travis.yml | 1 + 2 files changed, 7 insertions(+) diff --git a/.ci/.azure-pipelines.yml b/.ci/.azure-pipelines.yml index dcdd68e63..2d717af1b 100644 --- a/.ci/.azure-pipelines.yml +++ b/.ci/.azure-pipelines.yml @@ -9,6 +9,8 @@ jobs: vmImage: "Ubuntu 16.04" strategy: matrix: + Node_v12: + node_version: 12 Node_v10: node_version: 10 Node_v8: @@ -30,6 +32,8 @@ jobs: vmImage: vs2017-win2016 strategy: matrix: + Node_v12: + node_version: 12 Node_v10: node_version: 10 Node_v8: @@ -54,6 +58,8 @@ jobs: vmImage: macos-10.13 strategy: matrix: + Node_v12: + node_version: 12 Node_v10: node_version: 10 Node_v8: diff --git a/.travis.yml b/.travis.yml index 7e14b195d..eda5b0009 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ sudo: false language: node_js node_js: + - '12' - '10' - '8' - '6' From 069350a5febf65adc27bc816a7805471b7d96f03 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 6 May 2019 19:23:08 +0200 Subject: [PATCH 216/216] Release: 4.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a67c7d2be..256684af3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gulp", - "version": "4.0.1", + "version": "4.0.2", "description": "The streaming build system.", "homepage": "https://gulpjs.com", "author": "Gulp Team (https://gulpjs.com/)",

- +