src/FileUpdater.js
function mergeAndUpdateDir(sourceDirs, targetDir, options, log) {
    if (sourceDirs && typeof sourceDirs === "string") {
        sourceDirs = [ sourceDirs ];
    } else if (!Array.isArray(sourceDirs)) {
        throw new Error("A source directory path or array of paths is required.");
    }

    if (!targetDir || typeof targetDir !== "string") {
        throw new Error("A target directory path is required.");
    }

    log = log || function(message) { };

    var rootDir = (options && options.rootDir) || "";

    var include = (options && options.include) || [ "**" ];
    if (typeof include === "string") {
        include = [ include ];
    } else if (!Array.isArray(include)) {
        throw new Error("Include parameter must be a glob string or array of glob strings.");
    }

    var exclude = (options && options.exclude) || [];
    if (typeof exclude === "string") {
        exclude = [ exclude ];
    } else if (!Array.isArray(exclude)) {
        throw new Error("Exclude parameter must be a glob string or array of glob strings.");
    }

    // Scan the files in each of the source directories.
    var sourceMaps = sourceDirs.map(function (sourceDir) {
            return path.join(rootDir, sourceDir);
        }).map(function (sourcePath) {
            if (!fs.existsSync(sourcePath)) {
                throw new Error("Source directory does not exist: " + sourcePath);
            }
            return mapDirectory(rootDir, path.relative(rootDir, sourcePath), include, exclude);
        });

    // Scan the files in the target directory, if it exists.
    var targetMap = {};
    var targetFullPath = path.join(rootDir, targetDir);
    if (fs.existsSync(targetFullPath)) {
        targetMap = mapDirectory(rootDir, targetDir, include, exclude);
    }

    var pathMap = mergePathMaps(sourceMaps, targetMap, targetDir);

    var updated = false;

    // Iterate in sorted order to ensure directories are created before files under them.
    Object.keys(pathMap).sort().forEach(function (subPath) {
        var entry = pathMap[subPath];
        updated = updatePathWithStats(
            entry.sourcePath,
            entry.sourceStats,
            entry.targetPath,
            entry.targetStats,
            options,
            log) || updated;
    });

    return updated;
}
function mergeAndUpdateDir(sourceDirs, targetDir, options, log) {
    if (sourceDirs && typeof sourceDirs === "string") {
        sourceDirs = [ sourceDirs ];
    } else if (!Array.isArray(sourceDirs)) {
        throw new Error("A source directory path or array of paths is required.");
    }

    if (!targetDir || typeof targetDir !== "string") {
        throw new Error("A target directory path is required.");
    }

    log = log || function(message) { };

    var rootDir = (options && options.rootDir) || "";

    var include = (options && options.include) || [ "**" ];
    if (typeof include === "string") {
        include = [ include ];
    } else if (!Array.isArray(include)) {
        throw new Error("Include parameter must be a glob string or array of glob strings.");
    }

    var exclude = (options && options.exclude) || [];
    if (typeof exclude === "string") {
        exclude = [ exclude ];
    } else if (!Array.isArray(exclude)) {
        throw new Error("Exclude parameter must be a glob string or array of glob strings.");
    }

    // Scan the files in each of the source directories.
    var sourceMaps = sourceDirs.map(function (sourceDir) {
            return path.join(rootDir, sourceDir);
        }).map(function (sourcePath) {
            if (!fs.existsSync(sourcePath)) {
                throw new Error("Source directory does not exist: " + sourcePath);
            }
            return mapDirectory(rootDir, path.relative(rootDir, sourcePath), include, exclude);
        });

    // Scan the files in the target directory, if it exists.
    var targetMap = {};
    var targetFullPath = path.join(rootDir, targetDir);
    if (fs.existsSync(targetFullPath)) {
        targetMap = mapDirectory(rootDir, targetDir, include, exclude);
    }

    var pathMap = mergePathMaps(sourceMaps, targetMap, targetDir);

    var updated = false;

    // Iterate in sorted order to ensure directories are created before files under them.
    Object.keys(pathMap).sort().forEach(function (subPath) {
        var entry = pathMap[subPath];
        updated = updatePathWithStats(
            entry.sourcePath,
            entry.sourceStats,
            entry.targetPath,
            entry.targetStats,
            options,
            log) || updated;
    });

    return updated;
}
function mergeAndUpdateDir ( sourceDirs , targetDir , options , log ) { if ( sourceDirs && typeof sourceDirs === "string" ) { sourceDirs = [ sourceDirs ] ; } else if ( ! Array . isArray ( sourceDirs ) ) { throw new Error ( "A source directory path or array of paths is required." ) ; } if ( ! targetDir || typeof targetDir !== "string" ) { throw new Error ( "A target directory path is required." ) ; } log = log || function ( message ) { } ; var rootDir = ( options && options . rootDir ) || "" ; var include = ( options && options . include ) || [ "**" ] ; if ( typeof include === "string" ) { include = [ include ] ; } else if ( ! Array . isArray ( include ) ) { throw new Error ( "Include parameter must be a glob string or array of glob strings." ) ; } var exclude = ( options && options . exclude ) || [ ] ; if ( typeof exclude === "string" ) { exclude = [ exclude ] ; } else if ( ! Array . isArray ( exclude ) ) { throw new Error ( "Exclude parameter must be a glob string or array of glob strings." ) ; } // Scan the files in each of the source directories. var sourceMaps = sourceDirs . map ( function ( sourceDir ) { return path . join ( rootDir , sourceDir ) ; } ) . map ( function ( sourcePath ) { if ( ! fs . existsSync ( sourcePath ) ) { throw new Error ( "Source directory does not exist: " + sourcePath ) ; } return mapDirectory ( rootDir , path . relative ( rootDir , sourcePath ) , include , exclude ) ; } ) ; // Scan the files in the target directory, if it exists. var targetMap = { } ; var targetFullPath = path . join ( rootDir , targetDir ) ; if ( fs . existsSync ( targetFullPath ) ) { targetMap = mapDirectory ( rootDir , targetDir , include , exclude ) ; } var pathMap = mergePathMaps ( sourceMaps , targetMap , targetDir ) ; var updated = false ; // Iterate in sorted order to ensure directories are created before files under them. Object . keys ( pathMap ) . sort ( ) . forEach ( function ( subPath ) { var entry = pathMap [ subPath ] ; updated = updatePathWithStats ( entry . sourcePath , entry . sourceStats , entry . targetPath , entry . targetStats , options , log ) || updated ; } ) ; return updated ; }
Updates a target directory with merged files and subdirectories from source directories.

@param {string|string[]} sourceDirs Required source directory or array of source directories
to be merged into the target. The directories are listed in order of precedence; files in
directories later in the array supersede files in directories earlier in the array
(regardless of timestamps).
@param {string} targetDir Required destination directory to be updated. If it does not exist,
it will be created. If it exists, newer files from source directories will be copied over,
and files missing in the source directories will be deleted.
@param {Object} [options] Optional additional parameters for the update.
@param {string} [options.rootDir] Optional root directory (such as a project) to which target
and source path parameters are relative; may be omitted if the paths are absolute. The
rootDir is always omitted from any logged paths, to make the logs easier to read.
@param {boolean} [options.all] If true, all files are copied regardless of last-modified times.
Otherwise, a file is copied if the source's last-modified time is greather than or
equal to the target's last-modified time, or if the file sizes are different.
@param {string|string[]} [options.include] Optional glob string or array of glob strings that
are tested against both target and source relative paths to determine if they are included
in the merge-and-update. If unspecified, all items are included.
@param {string|string[]} [options.exclude] Optional glob string or array of glob strings that
are tested against both target and source relative paths to determine if they are excluded
from the merge-and-update. Exclusions override inclusions. If unspecified, no items are
excluded.
@param {loggingCallback} [log] Optional logging callback that takes a string message
describing any file operations that are performed.
@return {boolean} true if any changes were made, or false if the force flag is not set
and everything was up to date
Updates a target directory with merged files and subdirectories from source directories .