grunt-sync
Advanced tools
Comparing version 0.2.3 to 0.2.4
@@ -7,6 +7,6 @@ { | ||
"cyclomatic": 3, | ||
"halstead": 8, | ||
"halstead": 10, | ||
"maintainability": 90 | ||
} | ||
} | ||
} | ||
} |
/*global module:false*/ | ||
module.exports = function(grunt) { | ||
require('time-grunt')(grunt); | ||
module.exports = function (grunt) { | ||
require('time-grunt')(grunt); | ||
// Project configuration. | ||
grunt.initConfig({ | ||
// Project configuration. | ||
grunt.initConfig({ | ||
files: ['Gruntfile.js', 'tasks/**/*.js', 'test/*.js'], | ||
files: ['Gruntfile.js', 'tasks/**/*.js', 'test/*.js'], | ||
watch: { | ||
all: { | ||
files: '<%= files %>' | ||
} | ||
}, | ||
watch: { | ||
all: { | ||
files: '<%= files %>' | ||
} | ||
}, | ||
simplemocha: { | ||
all: { | ||
src: 'test/*.js' | ||
} | ||
}, | ||
simplemocha: { | ||
all: { | ||
src: 'test/*.js' | ||
} | ||
}, | ||
jshint: { | ||
all: '<%= files %>' | ||
}, | ||
jshint: { | ||
all: '<%= files %>' | ||
}, | ||
sync: grunt.file.readJSON('sync.json'), | ||
sync: grunt.file.readJSON('sync.json'), | ||
complexity: grunt.file.readJSON('complexity.json') | ||
}); | ||
grunt.loadNpmTasks('grunt-simple-mocha'); | ||
grunt.loadNpmTasks('grunt-complexity'); | ||
grunt.loadNpmTasks('grunt-contrib-jshint'); | ||
complexity: grunt.file.readJSON('complexity.json') | ||
}); | ||
grunt.loadNpmTasks('grunt-simple-mocha'); | ||
grunt.loadNpmTasks('grunt-complexity'); | ||
grunt.loadNpmTasks('grunt-contrib-jshint'); | ||
grunt.registerTask('default', ['jshint', 'simplemocha', 'complexity']); | ||
grunt.registerTask('default', ['jshint', 'simplemocha', 'complexity']); | ||
// Used for testing only, you shouldn't add this to your code: | ||
grunt.loadTasks('tasks'); | ||
// Used for testing only, you shouldn't add this to your code: | ||
grunt.loadTasks('tasks'); | ||
}; | ||
}; |
{ | ||
"name": "grunt-sync", | ||
"description": "Task to synchronize two directories. Similar to grunt-copy but updates only files that have been changed.", | ||
"version": "0.2.3", | ||
"version": "0.2.4", | ||
"homepage": "https://github.com/tomusdrw/grunt-sync.git", | ||
@@ -30,2 +30,3 @@ "author": { | ||
"scripts": { | ||
"lint": "semistandard", | ||
"test": "mocha" | ||
@@ -45,2 +46,3 @@ }, | ||
"mocha": "~1.8.1", | ||
"semistandard": "^6.1.2", | ||
"time-grunt": "^0.4.0" | ||
@@ -47,0 +49,0 @@ }, |
@@ -16,23 +16,23 @@ # Grunt-sync | ||
```javascript | ||
grunt.initConfig({ | ||
grunt.initConfig({ | ||
sync: { | ||
main: { | ||
files: [{ | ||
cwd: 'src', | ||
src: [ | ||
'**', /* Include everything */ | ||
'!**/*.txt' /* but exclude txt files */ | ||
], | ||
dest: 'bin', | ||
}], | ||
pretend: true, // Don't do any IO. Before you run the task with `updateAndDelete` PLEASE MAKE SURE it doesn't remove too much. | ||
verbose: true // Display log messages when copying files | ||
} | ||
} | ||
sync: { | ||
main: { | ||
files: [{ | ||
cwd: 'src', | ||
src: [ | ||
'**', /* Include everything */ | ||
'!**/*.txt' /* but exclude txt files */ | ||
], | ||
dest: 'bin', | ||
}], | ||
pretend: true, // Don't do any IO. Before you run the task with `updateAndDelete` PLEASE MAKE SURE it doesn't remove too much. | ||
verbose: true // Display log messages when copying files | ||
} | ||
} | ||
}); | ||
}); | ||
grunt.loadNpmTasks('grunt-sync'); | ||
grunt.registerTask('default', 'sync'); | ||
grunt.loadNpmTasks('grunt-sync'); | ||
grunt.registerTask('default', 'sync'); | ||
``` | ||
@@ -48,6 +48,7 @@ | ||
], | ||
verbose: true, | ||
pretend: true, // Don't do any disk operations - just write log | ||
ignoreInDest: "**/*.js", // Never remove js files from destination | ||
updateAndDelete: true // Remove all files from dest that are not found in src | ||
verbose: true, // Default: false | ||
pretend: true, // Don't do any disk operations - just write log. Default: false | ||
failOnError: true, // Fail the task when copying is not possible. Default: false | ||
ignoreInDest: "**/*.js", // Never remove js files from destination. Default: none | ||
updateAndDelete: true // Remove all files from dest that are not found in src. Default: false | ||
@@ -63,3 +64,16 @@ } | ||
## How it works? | ||
In the first phase the plugin compares modification times of files in `src` and `dest`. It only copies files with newer modification time. Second phase deletes files that exists in `dest` but have not been found in `src`. | ||
Details: | ||
1. [1st phase] Read modification time of all files in `src`. | ||
1. [1] Overwrite destination if modification time is newer or destination is directory not file. | ||
1. [2nd phase] Read all files in `dest` and calculate difference between files in destination and source files. | ||
1. [2] Delete all files (and directories) that have been found in `dest` but are not found `src` excluding ignored files. | ||
## Changelog | ||
* 0.2.4 - `failOnError` option | ||
* 0.2.3 - Fixed issue with files defined as array when using `updateAndDelete`. | ||
* 0.2.2 - Fixed issue with `updateAndDelete` when source patterns matches only files. | ||
@@ -66,0 +80,0 @@ * 0.2.1 - Fixed grunt Compact Format. |
@@ -1,189 +0,23 @@ | ||
var fs = require('promised-io/fs'), | ||
promise = require('promised-io/promise'), | ||
path = require('path'), | ||
glob = require('glob'), | ||
util = require('util'), | ||
_ = require('lodash'); | ||
var fs = require('promised-io/fs'); | ||
var promise = require('promised-io/promise'); | ||
var path = require('path'); | ||
var glob = require('glob'); | ||
var util = require('util'); | ||
var _ = require('lodash'); | ||
module.exports = function(grunt) { | ||
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); | ||
} | ||
}; | ||
grunt.registerMultiTask('sync', 'Synchronize content of two directories.', function () { | ||
var done = this.async(); | ||
var logger = grunt[this.data.verbose ? 'log' : 'verbose']; | ||
var updateOnly = !this.data.updateAndDelete; | ||
var justPretend = !!this.data.pretend; | ||
var failOnError = !!this.data.failOnError; | ||
var ignoredPatterns = this.data.ignoreInDest; | ||
var expandedPaths = {}; | ||
var tryMkdir = function(dest) { | ||
try { | ||
grunt.file.mkdir(dest); | ||
} catch (e) { | ||
grunt.log.warn('Cannot create directory ' + 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 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 removePaths = function(justPretend, logger, paths) { | ||
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); | ||
// 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); | ||
})); | ||
}); | ||
}); | ||
}; | ||
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: [] | ||
}); | ||
}; | ||
var fastArrayDiff = function(from, diff) { | ||
diff.map(function(v) { | ||
from[from.indexOf(v)] = undefined; | ||
}); | ||
return from.filter(function(v) { | ||
return v; | ||
}); | ||
}; | ||
var convertPathsToSystemSpecific = function(paths) { | ||
return paths.map(function(filePath) { | ||
return path.join.apply(path, filePath.split('/')); | ||
}); | ||
}; | ||
var addDirectoriesPaths = function(arr, dest) { | ||
var f = dest.split(path.sep); | ||
var i, p; | ||
p = f[0]; | ||
for (i = 1; i < f.length - 1; ++i) { | ||
p += path.sep + f[i]; | ||
if (arr.indexOf(p) === -1) { | ||
arr.push(p); | ||
} | ||
} | ||
}; | ||
grunt.registerMultiTask('sync', 'Synchronize content of two directories.', function() { | ||
var done = this.async(), | ||
logger = grunt[this.data.verbose ? 'log' : 'verbose'], | ||
updateOnly = !this.data.updateAndDelete, | ||
justPretend = !!this.data.pretend, | ||
ignoredPatterns = this.data.ignoreInDest, | ||
expandedPaths = {}; | ||
var getExpandedPaths = function(origDest) { | ||
var getExpandedPaths = function (origDest) { | ||
if (!expandedPaths[origDest]) { | ||
// Always include destination as processed. | ||
expandedPaths[origDest] = [origDest.replace(new RegExp("\\" + path.sep + "$"), '')]; | ||
expandedPaths[origDest] = [origDest.replace(new RegExp('\\' + path.sep + '$'), '')]; | ||
return expandedPaths[origDest]; | ||
@@ -194,3 +28,3 @@ } | ||
promise.all(this.files.map(function(fileDef) { | ||
promise.all(this.files.map(function (fileDef) { | ||
var isCompactForm = this.data.src && this.data.dest; | ||
@@ -203,3 +37,3 @@ var cwd = fileDef.cwd ? fileDef.cwd : '.'; | ||
return promise.all(fileDef.src.map(function(src) { | ||
return promise.all(fileDef.src.map(function (src) { | ||
var dest; | ||
@@ -219,6 +53,6 @@ // when using expanded mapping dest is the destination file | ||
// Process pair | ||
return processPair(justPretend, logger, path.join(cwd, src), dest); | ||
return processPair(justPretend, failOnError, logger, path.join(cwd, src), dest); | ||
})); | ||
}, this)).then(function() { | ||
}, this)).then(function () { | ||
if (updateOnly) { | ||
@@ -228,7 +62,7 @@ return; | ||
var getDestPaths = function(dest, pattern) { | ||
var getDestPaths = function (dest, pattern) { | ||
var defer = new promise.Deferred(); | ||
glob(path.join(dest, pattern), { | ||
dot: true | ||
}, function(err, result) { | ||
}, function (err, result) { | ||
if (err) { | ||
@@ -243,3 +77,3 @@ defer.reject(err); | ||
var getIgnoredPaths = function(dest, ignore) { | ||
var getIgnoredPaths = function (dest, ignore) { | ||
var defer = new promise.Deferred(); | ||
@@ -255,10 +89,10 @@ if (!ignore) { | ||
promise.all(ignore.map(function(pattern) { | ||
promise.all(ignore.map(function (pattern) { | ||
return getDestPaths(dest, pattern); | ||
})).then(function(results) { | ||
var flat = results.reduce(function(memo, a) { | ||
})).then(function (results) { | ||
var flat = results.reduce(function (memo, a) { | ||
return memo.concat(a); | ||
}, []); | ||
defer.resolve(flat); | ||
}, function(err) { | ||
}, function (err) { | ||
defer.reject(err); | ||
@@ -271,3 +105,3 @@ }); | ||
// Second pass | ||
return promise.all(Object.keys(expandedPaths).map(function(dest) { | ||
return promise.all(Object.keys(expandedPaths).map(function (dest) { | ||
var processedDestinations = convertPathsToSystemSpecific(expandedPaths[dest]); | ||
@@ -282,4 +116,4 @@ | ||
return promise.all([destPaths, ignoredPaths, processedDestinations]); | ||
})).then(function(result) { | ||
var files = result.map(function(destAndIgnored) { | ||
})).then(function (result) { | ||
var files = result.map(function (destAndIgnored) { | ||
var paths = convertPathsToSystemSpecific(destAndIgnored[0]); | ||
@@ -289,8 +123,8 @@ var ignoredPaths = convertPathsToSystemSpecific(destAndIgnored[1]); | ||
return [paths, ignoredPaths, destAndIgnored[2]]; | ||
}).reduce(function(memo, destAndIgnored) { | ||
return memo.map(function(val, key) { | ||
}).reduce(function (memo, destAndIgnored) { | ||
return memo.map(function (val, key) { | ||
return val.concat(destAndIgnored[key]); | ||
}); | ||
}, [[], [], []]); | ||
// TODO Find some faster way to ensure uniqueness here | ||
@@ -305,3 +139,3 @@ var paths = _.uniq(files[0]); | ||
toRemove = fastArrayDiff(toRemove, ignoredPaths); | ||
return removePaths(justPretend, logger, toRemove); | ||
@@ -311,2 +145,176 @@ }); | ||
}); | ||
function processPair (justPretend, failOnError, logger, src, dest) { | ||
// stat destination file | ||
return promise.all([fs.stat(src), fs.stat(dest)]).then(function (result) { | ||
var srcStat = result[0]; | ||
var 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); | ||
}); | ||
} | ||
}); | ||
function doOrPretend (operation) { | ||
if (justPretend) { | ||
return; | ||
} | ||
operation(); | ||
} | ||
function warnOrFail (msg) { | ||
if (failOnError) { | ||
grunt.fail.warn(msg); | ||
return; | ||
} | ||
grunt.log.warn(msg); | ||
} | ||
function tryCopy (src, dest) { | ||
try { | ||
grunt.file.copy(src, dest); | ||
} catch (e) { | ||
warnOrFail('Cannot copy to ' + dest.red); | ||
} | ||
} | ||
function tryMkdir (dest) { | ||
try { | ||
grunt.file.mkdir(dest); | ||
} catch (e) { | ||
warnOrFail('Cannot create directory ' + dest.red); | ||
} | ||
} | ||
function overwriteDest (src, dest) { | ||
try { | ||
grunt.file['delete'](dest); | ||
grunt.file.copy(src, dest); | ||
} catch (e) { | ||
warnOrFail('Cannot overwrite ' + dest.red); | ||
} | ||
} | ||
function overwriteOrUpdate (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); | ||
}); | ||
} | ||
} | ||
function removePaths (justPretend, logger, paths) { | ||
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); | ||
// 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); | ||
})); | ||
}); | ||
}); | ||
} | ||
function splitFilesAndDirs (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: [] | ||
}); | ||
} | ||
function fastArrayDiff (from, diff) { | ||
diff.map(function (v) { | ||
from[from.indexOf(v)] = undefined; | ||
}); | ||
return from.filter(function (v) { | ||
return v; | ||
}); | ||
} | ||
function convertPathsToSystemSpecific (paths) { | ||
return paths.map(function (filePath) { | ||
return path.join.apply(path, filePath.split('/')); | ||
}); | ||
} | ||
function addDirectoriesPaths (arr, dest) { | ||
var f = dest.split(path.sep); | ||
var i, p; | ||
p = f[0]; | ||
for (i = 1; i < f.length - 1; ++i) { | ||
p += path.sep + f[i]; | ||
if (arr.indexOf(p) === -1) { | ||
arr.push(p); | ||
} | ||
} | ||
} | ||
}; |
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
16738
346
94
8
1