UNPKG

watch_reapp

Version:

Restarts your nodejs app when you save a file in your specified directory.

198 lines (197 loc) 9.78 kB
module.exports = watch_reapp /** *Runs and restarts your app when you make changes to any files in it's or a differently specified directory. * * Ignores supplied Arrays of filenames, paths or wildcard names. * * Optionally LOGS your process to file. * * config is an OPTIONAL parameter and so are it's keys, * * default values are: * - dir = './' * - watch_ext = ['.js', '.html', '.css', '.json'] * - ignore_dirs = ['./node_modules/', './.vscode/', './LOGS/', './.git/'] * - ignore_files = [] * - ignore_dirs_containing = [] * - log = false * - log_dir = './LOGS/' * * be sure to re-include ['./node_modules/', './.vscode/', './.git/'] to ignore_dirs; you might end up watching thousands of files otherwise; computer could stop function in a very unimpressive manner. * * @param {String} app_str path and name of app you wish to restart on change (extension not necessary if name is unique) * @param {Object} [config] Config Object and all it's keys are OPTIONAL * @param {String} [config.dir='./'] the directory you wish to observe, only files contained in this directory will trigger a restart on your app Default = "./" * @param {[String]} [config.watch_ext=['.js', '.html', '.css', '.json']] extensions of files you wish to watch * @param {[String]} [config.ignore_dirs=['./node_modules/', './.vscode/', "./.git/"]] directories within the dir that you wish to ignore * @param {[String]} [config.ignore_files=[]] files within dir that you wish to ignore * @param {[String]} [config.ignore_dirs_containing=[]] wildcard names of dirs you wish you ignore (it uses indexOf so if the path contains anything that is placed here, it will be ignored) * @param {Boolean} [config.log] indicates if you want to log to file * @param {String} [config.logs_dir] * @author Mattias Lemmens * @exports ./watch_reapp.js */ function watch_reapp(app_str, config){//app_fn will execute when watch_cycle ends const fs = require('fs'); const path = require('path'); const util = require('util'); const colors = require('./colors.js'); const on_file_change = require('on-file-change');//by Jan Święcki <jan.swiecki@gmail.com> //defaults let logs_dir = './LOGS/'; let dir = './';//starting dir, './ to look in the folder where the app is running let watch_ext = ['.js', '.html', '.css', '.json']; //file extensions to watch let ignore_dirs = ['./node_modules/', './.vscode/', "./.git/"]; //directories to ignore, let ignore_files = []; //files to ignore, let ignore_dirs_containing = [];//ignore these dirs with prefix let keep_logs = false;//do not keep logs or create the log directory //they must be complete, if you have nested directories, you must add the complete path //ignore_dirs example = './dir/sub_dir/ignore_this_dir/' //ignore_dirs example = './dir/sub_dir/ignore_this_file.js' //folders that contain a '.' will ALWAYS be ignored if(config){ if(config.dir) dir = config.dir; if(config.watch_ext) watch_ext = config.watch_ext; if(config.ignore_dirs){ ignore_dirs = config.ignore_dirs if(ignore_dirs.indexOf(logs_dir) === -1 && config.logs === true) ignore_dirs.push(logs_dir); }; if(config.ignore_files) ignore_files = config.ignore_files; if(config.ignore_dirs_containing) ignore_dirs_containing = config.ignore_dirs_containing; if(config.logs_dir) logs_dir = config.logs_dir; if(config.log === true){ keep_logs = true; ignore_dirs.push(logs_dir)}; } let watch_arr = [];//file+dir goes here to watch let watching_arr = []; //CREATE ./LOGS/ DIR IF IT DOES NOT EXIST if(!fs.existsSync(logs_dir) && keep_logs === true) fs.mkdirSync(logs_dir); //CHILD PROCESS RUNS IN HERE prep child process/app_fn const spawn = require('child_process').spawn; function new_child_process(){ let app = spawn('node',[app_str]) //route console output to main app console output app.stdout.on('data', function (data){ var str = data.toString(), lines = str.split(/(\r?\n)/g); lines.map((x)=>{ //filter out new lines and line backs so that they do not get applied twice if(x.length > 0 && !x.includes("\n")&& !x.includes("\r"))console.log(x); }) if(keep_logs === true)logger.log(data.toString()); }) app.stderr.on('data', function (data) { console.log(red(data.toString())) if(keep_logs === true) logger.error(data.toString()); console.log('awaiting file-change') }); app.on('close', (code)=>{ if(code === null) console.log('child process killed '+bright_yellow('NATURALLY')+' by '+bright_white(process.title)); // else console.log(`child process exited with code ${code}`) }) return app } let running_app = null// will hold the child app once running //LOGGER INITIATED HERE const logger = function(){ if(keep_logs === true){ const Console_Prototype = require('console').Console;//get console prototype return new Console_Prototype( fs.createWriteStream(logs_dir+'stdout.log'),//must mod to add incremented file numbering fs.createWriteStream(logs_dir+'stderr.log')//must mod to add incremented file numbering );//create new logger prototype }else{ return console }; }(); console.log(bright_green(process.title + ': ') ,'initiated on ' , new Date().toLocaleString()) //return array of directories based on path ignores other types function getDirectories(srcpath) { return fs.readdirSync(srcpath).filter(function(file) { return fs.statSync(path.join(srcpath, file)).isDirectory() }) } // function recurs_dir_scan(dir){//ignores dirs where applicable if(ignore_dirs_containing.map((x)=>{if(dir.includes(x)) return true }).includes(true)) return;//ignores dirs containing x if(ignore_dirs.includes(dir)) return;//ignores entire dirs ;(fs.readdirSync(dir)||[]).map((file)=> { let file_ext = path.extname(dir+file)||'' if(watch_ext.includes(file_ext) && fs.lstatSync(dir+file).isFile()) watch_arr.push(dir+file)//adds files to watch to watch_arr if(!(file).includes('.')&&fs.lstatSync(dir+file).isDirectory()) recurs_dir_scan(dir+file+'/')//recursive_perform dir_scan a directory level deeper }) return watch_arr//function returns array of dir+file to watch } //init file watch, build watch_arr function init_watch(dir_file){ on_file_change(dir_file,(dir_file)=>{ console.log(green('file-change for: ') + dir_file) running_app.kill() console.log(green('restarting: ') + app_str) running_app = new_child_process() }) } let ignore_count = 0 //return false if dir_file is in ignore list function is_ignored(dir_file){ if(!ignore_files.includes(dir_file)){ return false }else{ ignore_count++ return true } } //INITIATES HERE iterate watch_arr function init(){ ignore_count = 0 watch_arr = [] recurs_dir_scan(dir).map((x,i,a)=>{ if(is_ignored(x)){ }else{ let files_c = a.length - ignore_count+1;//files_to_watch_count let i_c = i - ignore_count+1 if(!watching_arr.includes(x)){//x exists in watching_arr dont add it init_watch(x) watching_arr.push(x) if(!running_app) init_child_and_progress_report(i_c-1, files_c-1)//disable init progress report if app is already running else console.log(green('added file: ') + x)//report file added if } } if(a.length-1 === i) setTimeout(init, 30000)//reinit new file check every 30 seconds }) } init()//START function init_child_and_progress_report(i_c, files_c){ if(i_c === files_c-1){ process.stdout.write(green('watching '+ zero_out(i_c+1, files_c) +' of '+ files_c+' files ') + white(node_prog_bar(i_c+1, files_c))+'\n')//create new line at end console.log(green('running:'), app_str) running_app = new_child_process() }else{ process.stdout.write(green('watching '+ zero_out(i_c+1, files_c) +' of '+ files_c+' files ') + white(node_prog_bar(i_c+1, files_c))+'\r')//return to beginning of line } } //zero out and return digit for instance(1 becomes '001' if max value ahs 3 digits function zero_out(num, max_num){ let digits = max_num.toString().length if(digits === num.toString().length) return num.toString() let i_d = num.toString().length let pf_d = digits-i_d return (Array(pf_d+1).join('0')+num.toString()) } //build and return progress bar function node_prog_bar(i, max){//returns progress bar string let bar_l = 40//character length of progress par let b_e = Math.round(i/(max/bar_l))//bar end return ' ['+Array(bar_l+1).join('_').split('').map((x,xi, a)=>{ if(xi<=b_e) return '■' else return ' ' }).join('')+'] ' + (parseInt((b_e/bar_l)*100)) +'%' } //RETURN COLOR STRING function bright_yellow(string){return colors.fg.yellow+ colors.bright + string + colors.reset} function bright_green(string){return colors.fg.green+ colors.bright + string + colors.reset} function bright_white(string){return colors.fg.white+ colors.bright + string + colors.reset} function green(string){return colors.fg.green + string + colors.reset} function yellow(string){return colors.fg.yellow + string + colors.reset} function white(string){return colors.fg.white + string + colors.reset} function red(string){return colors.fg.red + string + colors.reset} }