grunt-sync
Advanced tools
Comparing version 0.0.8 to 0.1.0
/*global module:false*/ | ||
module.exports = function(grunt) { | ||
require('time-grunt')(grunt); | ||
// Project configuration. | ||
@@ -5,0 +6,0 @@ grunt.initConfig({ |
{ | ||
"name": "grunt-sync", | ||
"description": "Task to synchronize two directories. Similar to grunt-copy but updates only files that have been changed.", | ||
"version": "0.0.8", | ||
"version": "0.1.0", | ||
"homepage": "https://github.com/tomusdrw/grunt-sync.git", | ||
@@ -33,11 +33,13 @@ "author": { | ||
"dependencies": { | ||
"glob": "^4.0.5", | ||
"promised-io": "0.3.3" | ||
}, | ||
"devDependencies": { | ||
"mocha": "~1.8.1", | ||
"chai": "1.4.2", | ||
"grunt": "0.4.x", | ||
"grunt-complexity": "^0.1.51", | ||
"grunt-contrib-jshint": "0.1.x", | ||
"grunt-simple-mocha": "0.3.x", | ||
"grunt-contrib-jshint": "0.1.x", | ||
"grunt-complexity": "^0.1.51" | ||
"mocha": "~1.8.1", | ||
"time-grunt": "^0.4.0" | ||
}, | ||
@@ -44,0 +46,0 @@ "keywords": [ |
@@ -28,2 +28,3 @@ # Grunt-sync | ||
}], | ||
ignoreInDest: "**/*.js", // Never remove js files from destination | ||
verbose: true // Display log messages when copying files | ||
@@ -47,3 +48,6 @@ } | ||
], | ||
verbose: true | ||
verbose: true, | ||
pretend: true, // Don't do any disk operations - just write log | ||
updateOnly: true // Don't remove any files from `dest` (works around 30% faster) | ||
} | ||
@@ -53,4 +57,7 @@ } | ||
## Changelog | ||
* 0.1.0 - Files missing that are not in `src` are deleted from `dest` (unless you specify `updateOnly`) | ||
## TODO | ||
Task does not remove any files and directories in `dest` that are no longer in `src`. | ||
Research if it's possible to have better integration with `grunt-contrib-watch` - update only changed files instead of scanning everything. |
{ | ||
"main": { | ||
"files": [{ | ||
"src": ["**"], | ||
"dest": "bin/to", | ||
"cwd": "bin/from" | ||
}], | ||
"verbose": true | ||
} | ||
} | ||
"main": { | ||
"files": [{ | ||
"src": ["**"], | ||
"dest": "bin/to", | ||
"expand": true, | ||
"cwd": "bin/from" | ||
}], | ||
"ignoreInDest": ["t", "*.js"], | ||
"updateOnly": false, | ||
"verbose": true, | ||
"pretend": true | ||
} | ||
} |
@@ -1,90 +0,252 @@ | ||
var fs = require('promised-io/fs'); | ||
var promise = require('promised-io/promise'); | ||
var path = require('path'); | ||
var fs = require('promised-io/fs'), | ||
promise = require('promised-io/promise'), | ||
path = require('path'), | ||
glob = require('glob'), | ||
util = require('util'); | ||
module.exports = function(grunt) { | ||
var tryCopy = function(src, dest) { | ||
try { | ||
grunt.file.copy(src, dest); | ||
} catch (e) { | ||
grunt.log.warn('Cannot copy to ' + dest.red); | ||
} | ||
var tryCopy = function(src, dest) { | ||
try { | ||
grunt.file.copy(src, dest); | ||
} catch (e) { | ||
grunt.log.warn('Cannot copy to ' + dest.red); | ||
} | ||
}; | ||
var tryMkdir = function(dest) { | ||
try { | ||
grunt.file.mkdir(dest); | ||
} catch (e) { | ||
grunt.log.warn('Cannot create directory ' + dest.red); | ||
} | ||
var tryMkdir = function(dest) { | ||
try { | ||
grunt.file.mkdir(dest); | ||
} catch (e) { | ||
grunt.log.warn('Cannot create directory ' + dest.red); | ||
} | ||
}; | ||
var overwriteDest = function(options, src, dest) { | ||
grunt[options.logMethod].writeln('Overwriting ' + dest.cyan + 'because type differs.'); | ||
try { | ||
grunt.file['delete'](dest); | ||
grunt.file.copy(src, dest); | ||
} catch(e) { | ||
grunt.log.warn('Cannot overwrite ' + dest.red); | ||
} | ||
var overwriteDest = function(src, dest) { | ||
try { | ||
grunt.file['delete'](dest); | ||
grunt.file.copy(src, dest); | ||
} catch (e) { | ||
grunt.log.warn('Cannot overwrite ' + dest.red); | ||
} | ||
}; | ||
var updateIfNeeded = function(options, src, dest, srcStat, destStat) { | ||
// we can now compare modification dates of files | ||
if(srcStat.mtime.getTime() > destStat.mtime.getTime()) { | ||
grunt[options.logMethod].writeln('Updating file ' + dest.cyan); | ||
// and just update destination | ||
tryCopy(src, dest); | ||
} | ||
var processPair = function(justPretend, logger, src, dest) { | ||
var doOrPretend = function(operation) { | ||
if (justPretend) { | ||
return; | ||
} | ||
operation(); | ||
}; | ||
var overwriteOrUpdate = function(isSrcDirectory, typeDiffers, srcStat, destStat) { | ||
// If types differ we have to overwrite destination. | ||
if (typeDiffers) { | ||
logger.writeln('Overwriting ' + dest.cyan + ' because type differs.'); | ||
doOrPretend(function() { | ||
overwriteDest(src, dest); | ||
}); | ||
return; | ||
} | ||
// we can now compare modification dates of files | ||
if (isSrcDirectory || srcStat.mtime.getTime() <= destStat.mtime.getTime()) { | ||
return; | ||
} | ||
logger.writeln('Updating file ' + dest.cyan); | ||
doOrPretend(function() { | ||
// and just update destination | ||
tryCopy(src, dest); | ||
}); | ||
}; | ||
//stat destination file | ||
return promise.all([fs.stat(src), fs.stat(dest)]).then(function(result) { | ||
var srcStat = result[0], | ||
destStat = result[1]; | ||
var isSrcDirectory = srcStat.isDirectory(); | ||
var typeDiffers = isSrcDirectory !== destStat.isDirectory(); | ||
overwriteOrUpdate(isSrcDirectory, typeDiffers, srcStat, destStat); | ||
}, function() { | ||
// we got an error which means that destination file does not exist | ||
// so make a copy | ||
if (grunt.file.isDir(src)) { | ||
logger.writeln('Creating ' + dest.cyan); | ||
doOrPretend(function() { | ||
tryMkdir(dest); | ||
}); | ||
} else { | ||
logger.writeln('Copying ' + src.cyan + ' -> ' + dest.cyan); | ||
doOrPretend(function() { | ||
tryCopy(src, dest); | ||
}); | ||
} | ||
}); | ||
}; | ||
var processPair = function(options, src, dest) { | ||
//stat destination file | ||
return promise.all([fs.stat(src), fs.stat(dest)]).then(function(result) { | ||
var srcStat = result[0], destStat = result[1]; | ||
var removePaths = function(justPretend, logger, paths) { | ||
var isSrcDirectory = srcStat.isDirectory(); | ||
var typeDiffers = isSrcDirectory !== destStat.isDirectory(); | ||
return promise.all(paths.map(function(file) { | ||
return fs.stat(file).then(function(stat) { | ||
return { | ||
file: file, | ||
isDirectory: stat.isDirectory() | ||
}; | ||
}); | ||
})).then(function(stats) { | ||
var paths = splitFilesAndDirs(stats); | ||
// If types differ we have to overwrite destination. | ||
if(typeDiffers) { | ||
overwriteDest(options,src, dest); | ||
} else if(!isSrcDirectory) { | ||
updateIfNeeded(options, src, dest, srcStat, destStat); | ||
} | ||
}, function() { | ||
// we got an error which means that destination file does not exist | ||
// so make a copy | ||
if(grunt.file.isDir(src)) { | ||
grunt[options.logMethod].writeln('Creating ' + dest.cyan); | ||
tryMkdir(dest); | ||
} else { | ||
grunt[options.logMethod].writeln('Copying ' + src.cyan + ' -> ' + dest.cyan); | ||
tryCopy(src, dest); | ||
} | ||
}); | ||
// First we need to process files | ||
return promise.all(paths.files.map(function(filePath) { | ||
logger.writeln('Unlinking ' + filePath.cyan + ' because it was removed from src.'); | ||
if (justPretend) { | ||
return; | ||
} | ||
return fs.unlink(filePath); | ||
})).then(function() { | ||
// Then process directories in ascending order | ||
var sortedDirs = paths.dirs.sort(function(a, b) { | ||
return b.length - a.length; | ||
}); | ||
return promise.all(sortedDirs.map(function(dir) { | ||
logger.writeln('Removing dir ' + dir.cyan + ' because not longer in src.'); | ||
if (justPretend) { | ||
return; | ||
} | ||
return fs.rmdir(dir); | ||
})); | ||
}); | ||
}); | ||
}; | ||
grunt.registerMultiTask('sync', 'Synchronize content of two directories.', function() { | ||
var done = this.async(); | ||
var options = { | ||
logMethod: this.data.verbose ? 'log' : 'verbose' | ||
var splitFilesAndDirs = function(stats) { | ||
return stats.reduce(function(memo, stat) { | ||
if (stat.isDirectory) { | ||
memo.dirs.push(stat.file); | ||
} else { | ||
memo.files.push(stat.file); | ||
} | ||
return memo; | ||
}, { | ||
files: [], | ||
dirs: [] | ||
}); | ||
}; | ||
promise.all(this.files.map(function(fileDef) { | ||
var cwd = fileDef.cwd ? fileDef.cwd : '.'; | ||
return promise.all(fileDef.src.map(function(src){ | ||
var dest = path.join(fileDef.dest, src); | ||
// when using expanded mapping dest is the destination file | ||
// not the destination folder | ||
if(fileDef.orig.expand) { | ||
dest = fileDef.dest; | ||
} | ||
return processPair(options, path.join(cwd, src), dest); | ||
})); | ||
})).then(function(promises) { | ||
promise.all(promises).then(done); | ||
var fastArrayDiff = function(from, diff) { | ||
diff.map(function(v) { | ||
from[from.indexOf(v)] = undefined; | ||
}); | ||
return from.filter(function(v) { | ||
return v; | ||
}); | ||
}; | ||
grunt.registerMultiTask('sync', 'Synchronize content of two directories.', function() { | ||
var done = this.async(), | ||
logger = grunt[this.data.verbose ? 'log' : 'verbose'], | ||
updateOnly = !!this.data.updateOnly, | ||
justPretend = !!this.data.pretend, | ||
ignoredPatterns = this.data.ignoreInDest, | ||
expandedPaths = {}; | ||
var getExpandedPaths = function(origDest) { | ||
expandedPaths[origDest] = expandedPaths[origDest] || []; | ||
return expandedPaths[origDest]; | ||
}; | ||
promise.all(this.files.map(function(fileDef) { | ||
var cwd = fileDef.cwd ? fileDef.cwd : '.'; | ||
var isExpanded = fileDef.orig.expand; | ||
var origDest = fileDef.orig.dest; | ||
var processedDestinations = getExpandedPaths(origDest); | ||
return promise.all(fileDef.src.map(function(src) { | ||
var dest; | ||
// when using expanded mapping dest is the destination file | ||
// not the destination folder | ||
if (isExpanded) { | ||
dest = fileDef.dest; | ||
} else { | ||
dest = path.join(fileDef.dest, src); | ||
} | ||
processedDestinations.push(dest); | ||
return processPair(justPretend, logger, path.join(cwd, src), dest); | ||
})); | ||
})).then(function() { | ||
if (updateOnly) { | ||
return; | ||
} | ||
var getDestPaths = function(dest, pattern) { | ||
var defer = new promise.Deferred(); | ||
glob(path.join(dest, pattern), { | ||
dot: true | ||
}, function(err, result) { | ||
if (err) { | ||
defer.reject(err); | ||
return; | ||
} | ||
defer.resolve(result); | ||
}); | ||
return defer.promise; | ||
}; | ||
var getIgnoredPaths = function(dest, ignore) { | ||
var defer = new promise.Deferred(); | ||
if (!ignore) { | ||
defer.resolve([]); | ||
return defer.promise; | ||
} | ||
if (!util.isArray(ignore)) { | ||
ignore = [ignore] | ||
} | ||
promise.all(ignore.map(function(pattern) { | ||
return getDestPaths(dest, pattern); | ||
})).then(function(results) { | ||
var flat = results.reduce(function(memo, a) { | ||
return memo.concat(a); | ||
}, []); | ||
defer.resolve(flat); | ||
}, function(err) { | ||
defer.reject(err); | ||
}); | ||
return defer.promise; | ||
}; | ||
// Second pass | ||
return promise.all(Object.keys(expandedPaths).map(function(dest) { | ||
var processedDestinations = expandedPaths[dest]; | ||
// We have to do second pass to remove objects from dest | ||
var destPaths = getDestPaths(dest, '**'); | ||
// Check if we have any ignore patterns | ||
var ignoredPaths = getIgnoredPaths(dest, ignoredPatterns); | ||
return promise.all([destPaths, ignoredPaths]).then(function(result) { | ||
// Calculate diff | ||
var toRemove = fastArrayDiff(result[0], processedDestinations); | ||
// And filter also ignored paths | ||
toRemove = fastArrayDiff(toRemove, result[1]); | ||
return removePaths(justPretend, logger, toRemove); | ||
}); | ||
})); | ||
}).then(done); | ||
}); | ||
}); | ||
}; | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
13455
264
60
2
7
1
+ Addedglob@^4.0.5
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedglob@4.5.3(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedminimatch@2.0.10(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedwrappy@1.0.2(transitive)