-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e7c5fbd
Showing
5 changed files
with
657 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
'use strict'; | ||
|
||
const fs = require('fs'); | ||
const path = require('path'); | ||
const globby = require('globby'); | ||
const gutil = require('gulp-util'); | ||
const Transform = require('stream').Transform; | ||
|
||
function verify(condition, message) { | ||
if (!condition) { | ||
throw new gutil.PluginError('gulp-prune', message); | ||
} | ||
} | ||
|
||
function normalize(file) { | ||
if (process.platform === 'win32') { | ||
file = file.replace(/\\/g, '/'); | ||
} | ||
return file; | ||
} | ||
|
||
// The mapping function converts source name to one or more destination paths. | ||
function getMapper(options) { | ||
if (options.map !== undefined) { | ||
verify(typeof options.map === 'function', 'options.map must be a function'); | ||
verify(options.ext === undefined, 'options.map and options.ext are exclusive'); | ||
return options.map; | ||
} else if (typeof options.ext === 'string') { | ||
let mapExt = options.ext; | ||
return (name) => name.replace(/(\.[^.]*)?$/, mapExt); | ||
} else if (options.ext !== undefined) { | ||
verify(options.ext instanceof Array && options.ext.every(e => typeof e === 'string'), 'options.ext must be a string or string[]'); | ||
let mapExtList = options.ext.slice(); | ||
return (name) => mapExtList.map(e => name.replace(/(\.[^.]*)?$/, e)); | ||
} else { | ||
return (name) => name; | ||
} | ||
} | ||
|
||
// The delete pattern is a minimatch pattern used to find files in the dest directory. | ||
function getDeletePattern(options) { | ||
if (typeof options.filter === 'string') { | ||
return options.filter; | ||
} else { | ||
verify(options.filter === undefined || typeof options.filter === 'function', | ||
'options.filter must be a string or function'); | ||
return '**/*'; | ||
} | ||
} | ||
|
||
// The delete filter is a function that selects what files to delete. `keep` will be populated later | ||
// as it sees files in the stream. | ||
function getDeleteFilter(options, keep) { | ||
if (typeof options.filter === 'function') { | ||
const filter = options.filter; | ||
return (name) => !keep.hasOwnProperty(name) && filter(name); | ||
} else { | ||
return (name) => !keep.hasOwnProperty(name); | ||
} | ||
} | ||
|
||
class PruneTransform extends Transform { | ||
|
||
constructor(dest, options) { | ||
super({ objectMode: true }); | ||
|
||
// Accept prune(dest, [options]), prune(options) | ||
verify(arguments.length <= 2, 'too many arguments'); | ||
if (typeof dest === 'string') { | ||
options = options || {}; | ||
verify(typeof options === 'object', 'options must be an object'); | ||
verify(options.dest === undefined, 'options.dest should not be specified with a dest argument'); | ||
} else { | ||
verify(options === undefined, 'dest must be a string'); | ||
options = dest; | ||
verify(typeof options === 'object', 'expected dest string or options object'); | ||
dest = options.dest; | ||
verify(typeof dest === 'string', 'options.dest or dest argument must be string'); | ||
} | ||
|
||
this._dest = path.resolve(dest); | ||
this._kept = {}; | ||
this._mapper = getMapper(options); | ||
this._pattern = getDeletePattern(options); | ||
this._filter = getDeleteFilter(options, this._kept); | ||
|
||
verify(options.verbose === undefined || typeof options.verbose === 'boolean', 'options.verbose must be a boolean'); | ||
this._verbose = !!options.verbose; | ||
} | ||
|
||
_transform(file, encoding, callback) { | ||
Promise.resolve() | ||
.then(() => { | ||
const name = path.relative(file.base, file.path); | ||
return this._mapper(name); | ||
}) | ||
.then(mapped => { | ||
switch (typeof mapped) { | ||
case 'string': | ||
this._kept[normalize(mapped)] = true; | ||
break; | ||
case 'object': | ||
for (let i = 0; i < mapped.length; ++i) { | ||
this._kept[normalize(mapped[i])] = true; | ||
} | ||
break; | ||
default: | ||
check(false, 'options.map function must return a string or string[], or a Promise that resolves to that.'); | ||
} | ||
}) | ||
.then(() => callback(null, file), callback); | ||
} | ||
|
||
_flush(callback) { | ||
globby(this._pattern, { cwd: this._dest, nodir: true }) | ||
.then(candidates => { | ||
let deleting = candidates.filter(this._filter); | ||
return Promise.all(deleting.map(f => { | ||
let file = path.join(this._dest, f); | ||
this._remove(file); | ||
})); | ||
}) | ||
.then(deleted => callback(), callback); | ||
} | ||
|
||
_remove(file) { | ||
return new Promise((resolve, reject) => { | ||
fs.unlink(file, (error) => { | ||
try { | ||
const fileRelative = path.relative('.', file); | ||
if (error) { | ||
if (this._verbose) { | ||
gutil.log('Prune:', gutil.colors.red(`${fileRelative}: ${error.message || error}`)); | ||
} | ||
reject(new Error(`${fileRelative}: ${error.message || error}`)); | ||
} else { | ||
if (this._verbose) { | ||
gutil.log('Prune:', gutil.colors.yellow(fileRelative)); | ||
} | ||
resolve(); | ||
} | ||
} catch (e) { | ||
reject(e); | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
module.exports = function prune(dest, options) { | ||
return new PruneTransform(dest, options); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "gulp-prune", | ||
"version": "0.1.0", | ||
"description": "Delete extraneous files from target directory on stream flush", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha" | ||
}, | ||
"keywords": [ | ||
"gulpplugin", | ||
"prune", | ||
"files" | ||
], | ||
"author": { | ||
"name": "Kurt Blackwell", | ||
"url": "https://github.com/hh10k" | ||
}, | ||
"license": "ISC", | ||
"devDependencies": { | ||
"mocha": "^2.5.3", | ||
"mock-fs": "^3.11.0" | ||
}, | ||
"dependencies": { | ||
"globby": "^5.0.0", | ||
"gulp-util": "^3.0.7" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# gulp-prune | ||
|
||
A [Gulp](http://gulpjs.com/) plugin to delete files that should not be in the destination directory. | ||
|
||
Files that have not been seen will be deleted after the stream is flushed. | ||
|
||
## Examples | ||
|
||
### Prune with 1:1 mapping | ||
|
||
All files in the target directory will be deleted unless they match the source name. | ||
|
||
```js | ||
var gulp = require('gulp'); | ||
var prune = require('gulp-prune'); | ||
var newer = require('gulp-newer'); | ||
var babel = require('gulp-babel'); | ||
|
||
gulp.task('build', () => { | ||
return gulp.src('src/**/*.js') | ||
.pipe(prune('build/')) | ||
.pipe(newer('build/')) | ||
.pipe(babel({ presets: ['es2015'] })) | ||
.pipe(gulp.dest('build/')); | ||
}); | ||
``` | ||
|
||
### Prune with custom mapping | ||
|
||
If the source and destination files names are different then the mapping can be customised. | ||
|
||
```js | ||
var gulp = require('gulp'); | ||
var prune = require('gulp-prune'); | ||
var newer = require('gulp-newer'); | ||
var sourcemaps = require('gulp-sourcemaps'); | ||
var typescript = require('gulp-typescript'); | ||
|
||
gulp.task('build', () => { | ||
return gulp.src('src/**/*.ts') | ||
.pipe(prune({ dest: 'build/', ext: [ '.js', '.js.map' ] })) | ||
.pipe(newer({ dest: 'build/', ext: '.js' })) | ||
.pipe(sourcemaps.init()) | ||
.pipe(typescript()) | ||
.pipe(sourcemaps.write('.')) | ||
.pipe(gulp.dest('build/')); | ||
}); | ||
``` | ||
|
||
### Prune with restrictions | ||
|
||
If multiple build tasks all output to the same directory, add a filter so that prune only deletes what that task is expected to output. | ||
|
||
```js | ||
var gulp = require('gulp'); | ||
var prune = require('gulp-prune'); | ||
var newer = require('gulp-newer'); | ||
var imagemin = require('gulp-imagemin'); | ||
var uglify = require('gulp-uglify'); | ||
|
||
gulp.task('build-images', () => { | ||
return gulp.src('src/**/*@(.jpg|.png|.gif)') | ||
.pipe(prune('build/', { filter: '**/*@(.jpg|.png|.gif)' })) | ||
.pipe(newer('build/')) | ||
.pipe(imagemin()) | ||
.pipe(gulp.dest('build/')); | ||
}); | ||
|
||
gulp.task('build-sources', () => { | ||
return gulp.src('src/*.js') | ||
.pipe(prune('build/', { filter: '*.js' })) | ||
.pipe(newer('build/')) | ||
.pipe(uglify()) | ||
.pipe(gulp.dest('build/')); | ||
}); | ||
``` | ||
|
||
## API | ||
|
||
### Export | ||
|
||
- `prune(dest)` | ||
- `prune(dest, options)` | ||
- `prune(options)` | ||
|
||
### Options | ||
|
||
- `options.dest` (or `dest` argument) | ||
|
||
The directory to prune files from. | ||
|
||
- `options.map` | ||
|
||
A function that maps the source file name to what will be output to the `dest` directory. The function may return a string or array of string file names. | ||
|
||
- `options.ext` | ||
|
||
A convenience version of `options.map` to replace the file extension. May be a single string or an array of strings. | ||
|
||
- `options.filter` | ||
|
||
If a string, only files that match this [Minimatch](https://www.npmjs.com/package/minimatch) pattern may be pruned. | ||
|
||
If a function, will be called for each file to be pruned. Return true to delete it. | ||
|
||
- `options.verbose` | ||
|
||
Set to true to log all deleted files. |
Oops, something went wrong.