|
var fs = require('fs'), |
|
path = require('path'); |
|
|
|
module.exports = ncp; |
|
ncp.ncp = ncp; |
|
|
|
function ncp (source, dest, options, callback) { |
|
var cback = callback; |
|
|
|
if (!callback) { |
|
cback = options; |
|
options = {}; |
|
} |
|
|
|
var basePath = process.cwd(), |
|
currentPath = path.resolve(basePath, source), |
|
targetPath = path.resolve(basePath, dest), |
|
filter = options.filter, |
|
rename = options.rename, |
|
transform = options.transform, |
|
clobber = options.clobber !== false, |
|
modified = options.modified, |
|
dereference = options.dereference, |
|
errs = null, |
|
started = 0, |
|
finished = 0, |
|
running = 0, |
|
limit = options.limit || ncp.limit || 16; |
|
|
|
limit = (limit < 1) ? 1 : (limit > 512) ? 512 : limit; |
|
|
|
startCopy(currentPath); |
|
|
|
function startCopy(source) { |
|
started++; |
|
if (filter) { |
|
if (filter instanceof RegExp) { |
|
if (!filter.test(source)) { |
|
return cb(true); |
|
} |
|
} |
|
else if (typeof filter === 'function') { |
|
if (!filter(source)) { |
|
return cb(true); |
|
} |
|
} |
|
} |
|
return getStats(source); |
|
} |
|
|
|
function getStats(source) { |
|
var stat = dereference ? fs.stat : fs.lstat; |
|
if (running >= limit) { |
|
return setImmediate(function () { |
|
getStats(source); |
|
}); |
|
} |
|
running++; |
|
stat(source, function (err, stats) { |
|
var item = {}; |
|
if (err) { |
|
return onError(err); |
|
} |
|
|
|
|
|
item.name = source; |
|
item.mode = stats.mode; |
|
item.mtime = stats.mtime; |
|
item.atime = stats.atime; |
|
|
|
if (stats.isDirectory()) { |
|
return onDir(item); |
|
} |
|
else if (stats.isFile()) { |
|
return onFile(item); |
|
} |
|
else if (stats.isSymbolicLink()) { |
|
|
|
return onLink(source); |
|
} |
|
}); |
|
} |
|
|
|
function onFile(file) { |
|
var target = file.name.replace(currentPath, targetPath); |
|
if(rename) { |
|
target = rename(target); |
|
} |
|
isWritable(target, function (writable) { |
|
if (writable) { |
|
return copyFile(file, target); |
|
} |
|
if(clobber) { |
|
rmFile(target, function () { |
|
copyFile(file, target); |
|
}); |
|
} |
|
if (modified) { |
|
var stat = dereference ? fs.stat : fs.lstat; |
|
stat(target, function(err, stats) { |
|
|
|
if (file.mtime.getTime()>stats.mtime.getTime()) |
|
copyFile(file, target); |
|
else return cb(); |
|
}); |
|
} |
|
else { |
|
return cb(); |
|
} |
|
}); |
|
} |
|
|
|
function copyFile(file, target) { |
|
var readStream = fs.createReadStream(file.name), |
|
writeStream = fs.createWriteStream(target, { mode: file.mode }); |
|
|
|
readStream.on('error', onError); |
|
writeStream.on('error', onError); |
|
|
|
if(transform) { |
|
transform(readStream, writeStream, file); |
|
} else { |
|
writeStream.on('open', function() { |
|
readStream.pipe(writeStream); |
|
}); |
|
} |
|
writeStream.once('finish', function() { |
|
if (modified) { |
|
|
|
fs.utimesSync(target, file.atime, file.mtime); |
|
cb(); |
|
} |
|
else cb(); |
|
}); |
|
} |
|
|
|
function rmFile(file, done) { |
|
fs.unlink(file, function (err) { |
|
if (err) { |
|
return onError(err); |
|
} |
|
return done(); |
|
}); |
|
} |
|
|
|
function onDir(dir) { |
|
var target = dir.name.replace(currentPath, targetPath); |
|
isWritable(target, function (writable) { |
|
if (writable) { |
|
return mkDir(dir, target); |
|
} |
|
copyDir(dir.name); |
|
}); |
|
} |
|
|
|
function mkDir(dir, target) { |
|
fs.mkdir(target, dir.mode, function (err) { |
|
if (err) { |
|
return onError(err); |
|
} |
|
copyDir(dir.name); |
|
}); |
|
} |
|
|
|
function copyDir(dir) { |
|
fs.readdir(dir, function (err, items) { |
|
if (err) { |
|
return onError(err); |
|
} |
|
items.forEach(function (item) { |
|
startCopy(path.join(dir, item)); |
|
}); |
|
return cb(); |
|
}); |
|
} |
|
|
|
function onLink(link) { |
|
var target = link.replace(currentPath, targetPath); |
|
fs.readlink(link, function (err, resolvedPath) { |
|
if (err) { |
|
return onError(err); |
|
} |
|
checkLink(resolvedPath, target); |
|
}); |
|
} |
|
|
|
function checkLink(resolvedPath, target) { |
|
if (dereference) { |
|
resolvedPath = path.resolve(basePath, resolvedPath); |
|
} |
|
isWritable(target, function (writable) { |
|
if (writable) { |
|
return makeLink(resolvedPath, target); |
|
} |
|
fs.readlink(target, function (err, targetDest) { |
|
if (err) { |
|
return onError(err); |
|
} |
|
if (dereference) { |
|
targetDest = path.resolve(basePath, targetDest); |
|
} |
|
if (targetDest === resolvedPath) { |
|
return cb(); |
|
} |
|
return rmFile(target, function () { |
|
makeLink(resolvedPath, target); |
|
}); |
|
}); |
|
}); |
|
} |
|
|
|
function makeLink(linkPath, target) { |
|
fs.symlink(linkPath, target, function (err) { |
|
if (err) { |
|
return onError(err); |
|
} |
|
return cb(); |
|
}); |
|
} |
|
|
|
function isWritable(path, done) { |
|
fs.lstat(path, function (err) { |
|
if (err) { |
|
if (err.code === 'ENOENT') return done(true); |
|
return done(false); |
|
} |
|
return done(false); |
|
}); |
|
} |
|
|
|
function onError(err) { |
|
if (options.stopOnError) { |
|
return cback(err); |
|
} |
|
else if (!errs && options.errs) { |
|
errs = fs.createWriteStream(options.errs); |
|
} |
|
else if (!errs) { |
|
errs = []; |
|
} |
|
if (typeof errs.write === 'undefined') { |
|
errs.push(err); |
|
} |
|
else { |
|
errs.write(err.stack + '\n\n'); |
|
} |
|
return cb(); |
|
} |
|
|
|
function cb(skipped) { |
|
if (!skipped) running--; |
|
finished++; |
|
if ((started === finished) && (running === 0)) { |
|
if (cback !== undefined ) { |
|
return errs ? cback(errs) : cback(null); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|