Synchrone method with two option, simple and efficacy.
const path = require('path');const fs = require('fs');
    function toHierarchie_files(pathDir, output_normalize=false, removeEmpty=true)
{
   var result = {}, enqueue = [pathDir];
   //normalize slash separator if output_normalize is true or just return val
   output_normalize = output_normalize == false?val => {return val}:val => {return path.normalize(val)};
   
   //allows absolute or relative path with extended resolution. Returns path normalize absolute to work with or 'none' string.
   const path_exist = (path_test) => {var tmpTab = fs.existsSync(path.normalize(path.resolve(path_test))) == true?[path.normalize(path.resolve(path_test))]:['', '../', '../../'].map(val => path.normalize(path.resolve(__dirname, val+path_test))).filter((val, index) => fs.existsSync(path.normalize(path.resolve(__dirname, val+path_test))) == true);return tmpTab.length > 0?tmpTab[0]:'none'};
   //Check if file exist and return her type or 'none' string
   const getType = (path_test) => {path_test = path_exist(path_test);return path_test == 'none'?'none':fs.lstatSync(path_test).isDirectory() == true?'dir':fs.lstatSync(path_test).isFile() == true?'file':'none';};
   
   
   function recursive()
   {
      //init new entrie
      var parentDir = enqueue.pop();result[parentDir]=[];
      //read dir
      fs.readdirSync(path_exist(parentDir)).forEach((file, index) =>{
         switch(getType(parentDir+'/'+file))
         {
            //if detect dir push in queue
            case 'dir': enqueue.push(output_normalize(parentDir+'/'+file)); break;
            //if file, add in entrie
            case 'file': result[parentDir].push(file); break;
            //else done
            default: break;
         };
      });
      //if optional arg remove empty is true, delete entries if not contains files
      if(result[parentDir].length == 0 && removeEmpty == true){Reflect.deleteProperty(result, parentDir);}
      //if queue is not empty continue processing
      if(enqueue.length > 0){recursive();}
   };
   
   //if dir renseign exist, go recusive
   if(getType(pathDir) == 'dir'){recursive();}
   
   return result;
};
Result:
{
"public/assets": [
    "favicon.ico"
],
"public/assets/js": [
    "dede.js",
    "test.js"
],
"public/assets/js/css/secure": [
    "config.json",
    "index.js"
],
"public/assets/css": [
    "style.css"
]
}