lib/qx/tool/compiler/files/Utils.js
function(from, to, filter) {
      var t = this;

      function copy(statFrom, statTo) {
        if (statFrom.isDirectory()) {
          var p;
          if (statTo === null) {
            p = mkdir(to); 
          } else {
            p = Promise.resolve(); 
          }
          return p.then(() => readdir(from)
              .then(files => Promise.all(files.map(file => t.sync(from + "/" + file, to + "/" + file, filter)))));
          
        } else if (statFrom.isFile()) {
          return qx.Promise.resolve(filter ? filter(from, to) : true)
            .then(result => result && t.copyFile(from, to));
        }
        return undefined;
      }

      return new Promise((resolve, reject) => {
        var statFrom = null;
        var statTo = null;
        
        stat(from)
          .then(tmp => {
            statFrom = tmp;
            return stat(to)
              .then(tmp => statTo = tmp)
              .catch(err => {
                if (err.code !== "ENOENT") {
                  throw err;
                }
              });
          })
          .then(() => {
            if (!statTo || statFrom.isDirectory() != statTo.isDirectory()) {
              return t.deleteRecursive(to)
                .then(() => copy(statFrom, statTo));
            } else if (statFrom.isDirectory() || (statFrom.mtime.getTime() > statTo.mtime.getTime() || statFrom.size != statTo.size)) {
              return copy(statFrom, statTo);
            }
            return undefined;
          })
          .then(resolve)
          .catch(reject);
      });
    }
function(from, to, filter) {
      var t = this;

      function copy(statFrom, statTo) {
        if (statFrom.isDirectory()) {
          var p;
          if (statTo === null) {
            p = mkdir(to); 
          } else {
            p = Promise.resolve(); 
          }
          return p.then(() => readdir(from)
              .then(files => Promise.all(files.map(file => t.sync(from + "/" + file, to + "/" + file, filter)))));
          
        } else if (statFrom.isFile()) {
          return qx.Promise.resolve(filter ? filter(from, to) : true)
            .then(result => result && t.copyFile(from, to));
        }
        return undefined;
      }

      return new Promise((resolve, reject) => {
        var statFrom = null;
        var statTo = null;
        
        stat(from)
          .then(tmp => {
            statFrom = tmp;
            return stat(to)
              .then(tmp => statTo = tmp)
              .catch(err => {
                if (err.code !== "ENOENT") {
                  throw err;
                }
              });
          })
          .then(() => {
            if (!statTo || statFrom.isDirectory() != statTo.isDirectory()) {
              return t.deleteRecursive(to)
                .then(() => copy(statFrom, statTo));
            } else if (statFrom.isDirectory() || (statFrom.mtime.getTime() > statTo.mtime.getTime() || statFrom.size != statTo.size)) {
              return copy(statFrom, statTo);
            }
            return undefined;
          })
          .then(resolve)
          .catch(reject);
      });
    }
Synchronises two files or folders; files are copied from/to but only if their
modification time or size has changed.
@param from {String} path to copy from
@param to {String} path to copy to
@param filter {Function?} optional filter method to validate filenames before sync
@async