├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '7' 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 shonny-ua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/shonny-ua/gulp-rev-outdated.svg)](https://travis-ci.org/shonny-ua/gulp-rev-outdated) 2 | [![Dependencies](https://david-dm.org/shonny-ua/gulp-rev-outdated.svg)](https://david-dm.org/shonny-ua/gulp-rev-outdated) 3 | [![devDependencies](https://david-dm.org/shonny-ua/gulp-rev-outdated/dev-status.svg)](https://david-dm.org/shonny-ua/gulp-rev-outdated#info=devDependencies&view=table) 4 | [![NPM version](https://badge.fury.io/js/gulp-rev-outdated.svg)](http://badge.fury.io/js/gulp-rev-outdated) 5 | 6 | # [gulp](https://github.com/wearefractal/gulp)-rev-outdated 7 | 8 | [![NPM](https://nodei.co/npm/gulp-rev-outdated.png?downloads=true&stars=true)](https://nodei.co/npm/gulp-rev-outdated/) 9 | 10 | > Old static asset revision files filter. 11 | 12 | ## Install 13 | 14 | ```sh 15 | $ npm install --save-dev gulp-rev-outdated 16 | ``` 17 | 18 | ## Usage 19 | 20 | We can use [gulp-rev](https://github.com/sindresorhus/gulp-rev) to cache-bust several assets. Every modification of source files caused a new revisioned asset creation. In case of using separate http://static.exsample.com/ domain for distributing static assets we have some problem with a lot of accumulated revisioned asset files. If we have several different frontends (e.q. [www-1.exsample.com, www-2.exsample.cpm, ... www-12.exsample com]) worked with different software releases, We can't remove all revisioned asset files on static.exsample.com. We need to save number of recent revisioned assets. gulp-rev-outdated filter revisioned assets and exclude parametrised quantity of recent files for removing. 21 | 22 | Takes one parameter [ keepQuantity ] - number of saved recent files. 23 | Default value == 2. 24 | 25 | ```js 26 | var gulp = require('gulp'); 27 | var gutil = require('gulp-util'); 28 | var rimraf = require('rimraf'); 29 | var revOutdated = require('gulp-rev-outdated'); 30 | var path = require('path'); 31 | var through = require('through2'); 32 | 33 | function cleaner() { 34 | return through.obj(function(file, enc, cb){ 35 | rimraf( path.resolve( (file.cwd || process.cwd()), file.path), function (err) { 36 | if (err) { 37 | this.emit('error', new gutil.PluginError('Cleanup old files', err)); 38 | } 39 | this.push(file); 40 | cb(); 41 | }.bind(this)); 42 | }); 43 | } 44 | 45 | gulp.task('clean', function() { 46 | gulp.src( ['dist/js/vendors*.js'], {read: false}) 47 | .pipe( revOutdated(1) ) // leave 1 latest asset file 48 | .pipe( cleaner() ); 49 | 50 | gulp.src( ['dist/js/bundle*.js'], {read: false}) 51 | .pipe( revOutdated(3) ) // leave 3 recent assets 52 | .pipe( cleaner() ); 53 | 54 | gulp.src( ['dist/css/*.css'], {read: false}) 55 | .pipe( revOutdated() ) // leave 2 recent assets (default value) 56 | .pipe( cleaner() ); 57 | 58 | return; 59 | }); 60 | ``` 61 | 62 | It's also possible to pass in all your asset files at once: 63 | 64 | ```js 65 | [...] 66 | 67 | gulp.task('clean', function() { 68 | gulp.src( ['dist/**/*.*'], {read: false}) 69 | .pipe( revOutdated(1) ) // leave 1 latest asset file for every file name prefix. 70 | .pipe( cleaner() ); 71 | 72 | return; 73 | }); 74 | ``` 75 | 76 | gulp.src option read false prevents gulp to read the contents of the file and makes this task a lot faster. If you need the file and it's contents after cleaning in the same stream, do not set the read option to false. 77 | 78 | ### Works with gulp-rev-outdated 79 | 80 | - [gulp-rev](https://github.com/sindresorhus/gulp-rev) 81 | 82 | ## License 83 | 84 | [MIT](http://opensource.org/licenses/MIT) © [Oleksandr Ovcharenko](mailto:shonny.ua@gmail.com) 85 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var through = require('through2'); 3 | var path = require('path'); 4 | 5 | var PLUGIN_NAME = 'gulp-rev-outdated'; 6 | 7 | function plugin(keepQuantity) { 8 | 'use strict'; 9 | keepQuantity = parseInt(keepQuantity) || 2; 10 | var lists = {}; 11 | 12 | return through.obj(function (file, enc, cb) { 13 | var regex = new RegExp('^(.*)-[0-9a-f]{8,10}(?:\\.min)?\\' + path.extname(file.path) + '$'); 14 | if (regex.test(file.path)) { 15 | var identifier = regex.exec(file.path)[1] + path.extname(file.path); 16 | if (lists[identifier] === undefined) { 17 | lists[identifier] = []; 18 | } 19 | lists[identifier].push({ 20 | file: file, 21 | time: file.stat.mtime.getTime() 22 | }); 23 | } 24 | cb(); 25 | }, function (cb) { 26 | Object.keys(lists).forEach(function (identifier) { 27 | lists[identifier].sort(function (a, b) { 28 | return b.time - a.time; 29 | }) 30 | .slice(keepQuantity) 31 | .forEach(function (f) { 32 | this.push(f.file); 33 | }, this); 34 | }, this); 35 | cb(); 36 | }); 37 | } 38 | 39 | module.exports = plugin; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-rev-outdated", 3 | "version": "1.0.1", 4 | "description": "Old static asset revision files filter", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/shonny-ua/gulp-rev-outdated" 9 | }, 10 | "author": { 11 | "name": "Oleksandr Ovcharenko", 12 | "email": "shonny.ua@gmail.com" 13 | }, 14 | "main": "index.js", 15 | "files": [ 16 | "index.js" 17 | ], 18 | "scripts": { 19 | "test": "mocha" 20 | }, 21 | "engines": { 22 | "node": ">= 4.0.0" 23 | }, 24 | "keywords": [ 25 | "gulpplugin", 26 | "gulp", 27 | "rev", 28 | "revision", 29 | "hash", 30 | "optimize", 31 | "version", 32 | "versioning", 33 | "cache", 34 | "expire", 35 | "old", 36 | "outdated", 37 | "static", 38 | "asset", 39 | "assets", 40 | "clean", 41 | "cleanup", 42 | "filtering", 43 | "filter" 44 | ], 45 | "dependencies": { 46 | "through2": "^3.0.0", 47 | "vinyl": "^2.2.0" 48 | }, 49 | "devDependencies": { 50 | "mocha": "^5.2.0" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/shonny-ua/gulp-rev-outdated/issues" 54 | }, 55 | "homepage": "https://github.com/shonny-ua/gulp-rev-outdated" 56 | } 57 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var Vinyl = require('vinyl'); 4 | var revOutdated = require('./index'); 5 | var path = require('path'); 6 | 7 | var assets = [ 8 | {path: 'css/style.css', time: 0}, 9 | {path: 'css/stylexxx.css', time: 0}, 10 | {path: 'css/style-22222222.css', time: 1403184415416}, 11 | {path: 'css/style-61e0be79.css', time: 1403184377571}, 12 | {path: 'css/style-a42f5380.css', time: 1403184303451}, 13 | {path: 'css/style-1d87bebe.css', time: 1222222222222}, 14 | {path: 'css/style-11111111.css', time: 1111111111111}, 15 | {path: 'css/style-11111111.min.css', time: 1111111111111}, 16 | {path: 'css/style-00000000.css', time: 0}, 17 | // Additional unique file 18 | {path: 'css/vendor.css', time: 0}, 19 | {path: 'css/vendorxxx.css', time: 0}, 20 | {path: 'css/vendor-22222222.css', time: 1403184415416}, 21 | {path: 'css/vendor-61e0be79.css', time: 1403184377571}, 22 | {path: 'css/vendor-a42f5380.css', time: 1403184303451}, 23 | {path: 'css/vendor-1d87bebe.css', time: 1222222222222}, 24 | {path: 'css/vendor-11111111.css', time: 1111111111111}, 25 | {path: 'css/vendor-11111111.min.css', time: 1111111111111}, 26 | {path: 'css/vendor-00000000.css', time: 0}, 27 | // Additional unique another type file 28 | {path: 'css/vendor.js', time: 0}, 29 | {path: 'css/vendorxxx.js', time: 0}, 30 | {path: 'css/vendor-22222222.js', time: 1403184415416}, 31 | {path: 'css/vendor-61e0be79.js', time: 1403184377571}, 32 | {path: 'css/vendor-a42f5380.js', time: 1403184303451}, 33 | {path: 'css/vendor-1d87bebe.js', time: 1222222222222}, 34 | {path: 'css/vendor-11111111.js', time: 1111111111111}, 35 | {path: 'css/vendor-11111111.min.js', time: 1111111111111}, 36 | {path: 'css/vendor-00000000.js', time: 0}, 37 | // Test nested files 38 | {path: 'css/fonts/fontstyle.css', time: 0}, 39 | {path: 'css/fonts/fontstylexxx.css', time: 0}, 40 | {path: 'css/fonts/fontstyle-22222222.css', time: 1403184415416}, 41 | {path: 'css/fonts/fontstyle-61e0be79.css', time: 1403184377571}, 42 | {path: 'css/fonts/fontstyle-a42f5380.css', time: 1403184303451}, 43 | {path: 'css/fonts/fontstyle-1d87bebe.css', time: 1222222222222}, 44 | {path: 'css/fonts/fontstyle-11111111.css', time: 1111111111111}, 45 | {path: 'css/fonts/fontstyle-11111111.min.css', time: 1111111111111}, 46 | {path: 'css/fonts/fontstyle-00000000.css', time: 0}, 47 | // Try to trip regex 48 | {path: 'css/try-to-trip-regex.css', time: 0}, 49 | {path: 'css/try-to-trip-regexxxx.css', time: 0}, 50 | {path: 'css/try-to-trip-regex-22222222.css', time: 1403184415416}, 51 | {path: 'css/try-to-trip-regex-61e0be79.css', time: 1403184377571}, 52 | {path: 'css/try-to-trip-regex-a42f5380.css', time: 1403184303451}, 53 | {path: 'css/try-to-trip-regex-1d87bebe.css', time: 1222222222222}, 54 | {path: 'css/try-to-trip-regex-11111111.css', time: 1111111111111}, 55 | {path: 'css/try-to-trip-regex-11111111.min.css', time: 1111111111111}, 56 | {path: 'css/try-to-trip-regex-00000000.css', time: 0} 57 | ]; 58 | 59 | var keepQuantity; 60 | // Uniques in 'assets' 61 | var uniqueFiles = 5; 62 | var filteredQuantity; 63 | var fileCount; 64 | 65 | it('should filter 30 files', function (cb) { 66 | keepQuantity = 1; 67 | filteredQuantity = 7 * uniqueFiles - keepQuantity * uniqueFiles; 68 | fileCount = 0; 69 | 70 | var stream = initStream(revOutdated(keepQuantity)); 71 | 72 | stream.on('data', streamDataCheck); 73 | 74 | stream.on('end', function () { 75 | assert.equal(fileCount, filteredQuantity, 'Only ' + filteredQuantity + ' files should pass through the stream'); 76 | cb(); 77 | }); 78 | 79 | stream.end(); 80 | }); 81 | 82 | it('should filter 25 files of different types using default keepQuantity option', function (cb) { 83 | keepQuantity = undefined; 84 | filteredQuantity = 7 * uniqueFiles - 2 * uniqueFiles; 85 | fileCount = 0; 86 | 87 | var stream = initStream(revOutdated()); 88 | 89 | stream.on('data', function (file) { 90 | streamDataCheck(file); 91 | assert( 92 | !/\/vendor-61e0be79\.(css|js)$/.test(file.path), 93 | 'should filter correct files' 94 | ); 95 | }); 96 | 97 | stream.on('end', function () { 98 | assert.equal(fileCount, filteredQuantity, 'Only ' + filteredQuantity + ' files should pass through the stream'); 99 | cb(); 100 | }); 101 | 102 | stream.end(); 103 | }); 104 | 105 | it('should filter correct files', function (cb) { 106 | keepQuantity = 3; 107 | filteredQuantity = 7 * uniqueFiles - keepQuantity * uniqueFiles; 108 | fileCount = 0; 109 | 110 | var stream = initStream(revOutdated(keepQuantity)); 111 | 112 | stream.on('data', function (file) { 113 | streamDataCheck(file); 114 | assert( 115 | /\/(style|vendor|fontstyle|try-to-trip-regex)-(1d87bebe|11111111|00000000)(?:\.min)?\.(css|js)$/.test(file.path), 116 | 'should filter correct files' 117 | ); 118 | }); 119 | 120 | stream.on('end', function () { 121 | assert.equal(fileCount, filteredQuantity, 'Only ' + filteredQuantity + ' files should pass through the stream'); 122 | cb(); 123 | }); 124 | 125 | stream.end(); 126 | }); 127 | 128 | function initStream(stream) { 129 | assets.forEach(function (asset) { 130 | var dateByTime = new Date(asset.time); 131 | stream.write(new Vinyl({ 132 | path: asset.path, 133 | stat: { 134 | ctime: dateByTime, 135 | mtime: dateByTime, 136 | atime: dateByTime 137 | }, 138 | contents: new Buffer(' ') 139 | })); 140 | }); 141 | return stream; 142 | } 143 | 144 | function streamDataCheck(file) { 145 | assert( 146 | /\/(style|vendor|fontstyle|try-to-trip-regex)-[0-9a-f]{8}(?:\.min)?\.(css|js)$/.test(file.path), 147 | 'should filter only revisioned files' 148 | ); 149 | fileCount++; 150 | } 151 | --------------------------------------------------------------------------------