modules/generator/main.js

!function GeneratorModuleWrapper() {
  let core; let mod; let o = {}; const pipelines = function() {};

  /**
   * Blackrock Generator Module
   *
   * @public
   * @class Server.Modules.Generator
   * @augments Server.Modules.Core.Module
   * @param {Server.Modules.Core} coreObj - The Parent Core Object
   * @return {Server.Modules.Generator} module - The Generator Module
   *
   * @description This is the Generator Module of the Blackrock Application Server.
   * It provides tools to generate new apps - providing the foundations for almost
   * any idea for an application or app. There are currently no accessible methods
   * exposed on this module.
   *
   * @example
   * const generatorModule = core.module('generator');
   *
   * @author Darren Smith
   * @copyright Copyright (c) 2021 Darren Smith
   * @license Licensed under the LGPL license.
   */
  module.exports = function GeneratorModule(coreObj) {
    if (mod) return mod;
    core = coreObj; mod = new core.Mod('Generator'); o.log = core.module('logger').log;
    o.log('debug', 'Generator > Initialising...', {module: mod.name}, 'MODULE_INIT');
    process.nextTick(function() {
      pipelines.init();
    });
    return mod;
  };


  /**
   * (Internal > Pipeline [1]) Init Pipeline
   *
   * @private
   * @memberof Server.Modules.Generator
   * @name init
   * @function
   * @ignore
   *
   * @description
   * Tbc...
   *
   * @example
   * pipelines.init();
   */
  pipelines.init = function GeneratorInitPipeline() {
    // noinspection JSUnresolvedFunction
    core.lib.rxPipeline({}).pipe(

      // Fires once on server initialisation:
      pipelines.init.registerWithCLI,
      pipelines.init.listenToStart,

      // Fires once on each CLI command:
      pipelines.init.parseParamsAndCheckForApp,
      pipelines.init.createNewAppWithoutFile,
      pipelines.init.createNewAppWithFile

    ).subscribe();
  };

  /**
   * (Internal > Stream Methods [1]) Register With CLI
   *
   * @private
   * @memberof Server.Modules.Generator
   * @function registerWithCLI
   * @ignore
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.registerWithCLI = function GeneratorRegisterWithCLI(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function(observer, evt) {
      o.log('debug',
          'Generator > [1] Generator registering with CLI...',
          {module: mod.name}, 'MODULE_REGISTER_WITH_CLI');
      core.module.isLoaded('cli').then(function(cliMod) {
        cliMod.register([
          {'cmd': 'create', 'params': '[name] [file]', 'info': 'Create a new app', 'fn': function(params) {
            core.emit('GENERATOR_INIT_GENERATOR', {'command': 'create', 'params': params});
          }},
        ]);
      }).catch(function(err) {
        o.log('error',
            'Generator > Failed to register with CLI - CLI module not loaded',
            {module: mod.name, error: err}, 'CLI_MOD_NOT_LOADED');
      });
      observer.next(evt);
    }, source);
  };

  /**
   * (Internal > Stream Methods [2]) Listen to Start Endpoint
   *
   * @private
   * @memberof Server.Modules.Generator
   * @name listenToStart
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.listenToStart = function GeneratorListenToStart(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function(observer, evt) {
      o.log('debug',
          'Generator > [2a] Listener created for \'GENERATOR_INIT_GENERATOR\' event',
          {module: mod.name}, 'GENERATOR_LISTENER_CREATED');
      core.on('GENERATOR_INIT_GENERATOR', function GeneratorModuleStartGeneratorCallback(genParams) {
        core.stopActivation = true;
        o.log('debug',
            'Generator > [2b] \'GENERATOR_INIT_GENERATOR\' Event Received',
            {module: mod.name}, 'GENERATOR_LISTENER_EVT_RECEIVED');
        evt.command = genParams.command;
        evt.params = genParams.params;
        observer.next(evt);
      });
    }, source);
  };

  /**
   * (Internal > Stream Methods [3]) Parse Params And Check For App
   *
   * @private
   * @memberof Server.Modules.Generator
   * @name parseParamsAndCheckForApp
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.parseParamsAndCheckForApp = function GeneratorParseParamsAndCheckForApp(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function(observer, evt) {
      if (evt.command === 'create') {
        o.log('debug',
            'Generator > [3] Parsing parameters and checking for app...',
            {module: mod.name}, 'GENERATOR_PARSE_PARAMS');
        const params = evt.params.trim().split(' ');
        let appName = ''; let appFile = '';
        const appPath = core.fetchBasePath('apps');
        if (params[0]) {
          appName = params[0];
        }
        if (params[1]) {
          appFile = params[1];
        }
        if (!appName) {
          console.log('You must specify a app name (and optionally a definition file)');
          process.exit();
        }
        if (require('fs').existsSync(appPath + '/' + appName)) {
          console.log('App (' + appName + ') Already Exists');
          process.exit();
        }
        evt.appPath = appPath;
        evt.appName = appName;
        evt.appFile = appFile;
        observer.next(evt);
      }
    }, source);
  };

  /**
   * (Internal > Stream Methods [4]) Create New App Without File
   *
   * @private
   * @memberof Server.Modules.Generator
   * @name createNewAppWithoutFile
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.createNewAppWithoutFile = function GeneratorCreateNewAppWithoutFile(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function(observer, evt) {
      if (evt.appFile) {
        observer.next(evt);
        return;
      }
      let filesWritten = 0;
      o.log('debug',
          'Generator > [4] Creating new app without definition file...',
          {module: mod.name}, 'GENERATOR_CREATING_APP_NO_FILE');
      require('fs').mkdirSync(evt.appPath + '/' + evt.appName);
      const rootFolders = ['controllers', 'html', 'lib', 'locale', 'models', 'test', 'views'];
      for (let i = 0; i < rootFolders.length; i++) {
        require('fs').mkdirSync(evt.appPath + '/' + evt.appName + '/' + rootFolders[i]);
        if (rootFolders[i] !== 'controllers' && rootFolders[i] !== 'views') {
          require('fs').writeFile(evt.appPath + '/' + evt.appName + '/' +
                      rootFolders[i] + '/stub.txt', 'Insert your ' + rootFolders[i] + ' into this folder', (err) => {
            if (err) throw err;
            filesWritten ++;
          });
        }
      }
      const appDefinition = {
        'name': evt.appName,
        'host': 'www.' + evt.appName + '.local',
        'basePath': '',
        'active': true,
      };
      const def = JSON.stringify(appDefinition);
      require('fs').writeFile(evt.appPath + '/' + evt.appName + '/' + 'app.json', def, function(err) {
        if (err) throw err;
        filesWritten ++;
      });
      const ctrlFile = o.createControllerFile('controllers', '[Author]', {'get': {'viewFile': 'home.mustache'}});
      require('fs').writeFile(evt.appPath + '/' + evt.appName + '/' + 'controllers/controller.js',
          ctrlFile, function(err) {
            if (err) throw err;
            filesWritten ++;
          });
      // eslint-disable-next-line max-len
      const viewFile = `<h1>Welcome to Your Sample Site</h1>\n<p>This is your new Blackrock App. Customise it as you see fit.</p>\n         `;
      require('fs').writeFile(evt.appPath + '/' + evt.appName + '/' + 'views/home.mustache', viewFile, function(err) {
        if (err) throw err;
        filesWritten ++;
      });

      // Listener With Timeout Feature:
      const timeout = 1000; let timer = 0;
      const interval = setInterval(function() {
        if (filesWritten >= 8) {
          clearInterval(interval);
          console.log('App (' + evt.appName + ') Created Successfully');
          process.exit();
        } else if (timer >= timeout) {
          clearInterval(interval);
          console.log('Timed Out Creating App (' + evt.appName + ')');
          process.exit();
        }
        timer += 10;
      }, 10);
    }, source);
  };

  /**
   * (Internal > Stream Methods [5]) Create New App With File
   *
   * @private
   * @memberof Server.Modules.Generator
   * @name createNewAppWithFile
   * @ignore
   * @function
   * @param {observable} source - The Source Observable
   * @return {observable} destination - The Destination Observable
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  pipelines.init.createNewAppWithFile = function GeneratorCreateNewAppWithFile(source) {
    // noinspection JSUnresolvedFunction
    return core.lib.rxOperator(function(observer, evt) {
      // Setup:
      let totalFiles = 0;
      let filesWritten = 0;
      o.log('debug',
          'Generator > [5] Creating new app with definition file...',
          {module: mod.name}, 'GENERATOR_CREATING_APP_WITH_FILE');
      let appFile;
      try {
        appFile = require(evt.appFile);
      } catch (err) {
        console.log('Invalid app definition file provided');
        process.exit();
      }

      // Create Root and First-Level Folder Set:
      require('fs').mkdirSync(evt.appPath + '/' + evt.appName);
      totalFiles += 5;
      const rootFolders = ['controllers', 'html', 'lib', 'locale', 'models', 'test', 'views'];
      for (let i = 0; i < rootFolders.length; i++) {
        require('fs').mkdirSync(evt.appPath + '/' + evt.appName + '/' + rootFolders[i]);
        if (rootFolders[i] !== 'controllers' && rootFolders[i] !== 'views') {
          require('fs').writeFile(evt.appPath + '/' + evt.appName + '/' + rootFolders[i] + '/stub.txt',
              'Insert your ' + rootFolders[i] + ' into this folder', function(err) {
                if (err) throw err;
                filesWritten ++;
              });
        }
      }
      const appDefinition = {'name': evt.appName};
      if (appFile.host) {
        appDefinition.host = appFile.host;
      } else {
        appDefinition.host = 'www.' + evt.appName + '.local';
      }
      if (appFile.basePath) {
        appDefinition.basePath = appFile.basePath;
      } else {
        appDefinition.basePath = '';
      }
      if (appFile.active) {
        appDefinition.active = appFile.active;
      } else {
        appDefinition.active = true;
      }
      totalFiles += 1;
      const def = JSON.stringify(appDefinition);
      require('fs').writeFile(evt.appPath + '/' + evt.appName + '/' + 'app.json',
          def, function(err) {
        if (err) throw err;
        filesWritten ++;
      });
      const timerFn = function GeneratorModuleTimerFn() {
        const timeout = 1000; let timer = 0;
        const interval = setInterval(function() {
          if (filesWritten >= totalFiles) {
            clearInterval(interval);
            console.log('App (' + evt.appName + ') Created Successfully');
            process.exit();
          } else if (timer >= timeout) {
            clearInterval(interval);
            console.log('Timed Out Creating App (' + evt.appName + ')');
            process.exit();
          }
          timer += 10;
        }, 10);
      };
      if (!appFile.routes) {
        // Create Root Controller File:
        totalFiles += 1;
        const ctrlFile = o.createControllerFile('controllers', '[Author]',
            {'get': {'viewFile': 'home.mustache'}});
        require('fs').writeFile(evt.appPath + '/' + evt.appName + '/' +
            'controllers/controller.js',
            ctrlFile, function(err) {
              if (err) throw err;
              filesWritten ++;
            });
        // eslint-disable-next-line max-len
        const viewFile = `<h1>Welcome to Your Sample Site</h1>\n<p>This is your new Blackrock App. Customise it as you see fit.</p>\n             `;
        totalFiles += 1;
        require('fs').writeFile(evt.appPath + '/' + evt.appName + '/' + 'views/home.mustache', viewFile, function(err) {
          if (err) throw err;
          filesWritten ++;
        });
        timerFn();
      }
      if (appFile.route) {
        o.countRouteFiles(appFile.route, function(err1, res1) {
          if (err1) {
            console.log('Error Counting Route Files For App (' + evt.appName + ') - ' + err1.message);
            process.exit();
          }
          // noinspection JSUnresolvedVariable
          totalFiles += res1.fileCount;
          o.generateRouteLevel(appFile.route, function(err2, res2) {
            if (err2) {
              console.log('Error Generating Routes For App (' + evt.appName + ') - ' + res2.err);
              process.exit();
            }
            // noinspection JSUnresolvedVariable
            filesWritten += res2.fileCount;
          });
          timerFn();
        });
      }
    }, source);
  };


  /**
   *  UTILITY METHODS...
   */

  /**
   * (Internal > Utility Methods) Create New App With File
   *
   * @private
   * @memberof Server.Modules.Generator
   * @name createControllerFile
   * @ignore
   * @function
   * @param {string} path - The Path
   * @param {string} author - The Author's Name
   * @param {object} verbs - Verbs Object
   * @return {string} ctrlFile - The Controller File (Text Format)
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  o.createControllerFile = function GeneratorCreateCtrlFile(path, author, verbs) {
    const addVerb = function GeneratorCreateCtrlFileAddVerb(verb, viewFile) {
      return `
    /**
     * ` + verb.toUpperCase() + `
     * @param {object} req - Request object
     * @param {object} res - Response object
     */
    ctrl.` + verb + ` = function(req, res){
        var context = {};
        res.render("` + viewFile + `", context);\n  }\n\n\n         `;
    };

    const currentDate = new Date();
    const fullYear = currentDate.getFullYear();
    let ctrlFile = `/*!
* ` + path + `/controller.js
*
* Copyright (c) ` + fullYear + ` ` + author + `\n*/\n\n;!function(undefined) {\n\n  var ctrl = {};\n\n\n        `;

    // eslint-disable-next-line guard-for-in
    for (const verb in verbs) {
      ctrlFile += addVerb(verb, verbs[verb].viewFile);
    }

    ctrlFile += `
    /**
     * (ENTRY POINT FOR EXECUTION)
     */
    module.exports = ctrl;
    
}();
        `;

    return ctrlFile;
  };

  /**
   * (Internal > Utility Methods) Count Route Files
   *
   * @private
   * @memberof Server.Modules.Generator
   * @name countRouteFiles
   * @ignore
   * @function
   * @param {number} routeLevel - The Route Level
   * @param {function} callbackFn - Callback Function
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  o.countRouteFiles = function GeneratorCountRouteFiles(routeLevel, callbackFn) {};

  /**
   * (Internal > Utility Methods) Parse & Generate Route Level
   *
   * @private
   * @memberof Server.Modules.Generator
   * @name generateRouteLevel
   * @ignore
   * @function
   * @param {number} routeLevel - The Route Level
   * @param {function} callbackFn - Callback Function
   *
   * @description
   * Tbc...
   *
   * @example
   * Tbc...
   */
  o.generateRouteLevel = function GeneratorParseGenRouteLevel(routeLevel, callbackFn) {};
}();