yoctore/yocto-config

View on GitHub
docs/index.js.html

Summary

Maintainability
Test Coverage
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>index.js - yocto-config</title>

    <script src="scripts/prettify/prettify.js"></script>
    <script src="scripts/prettify/lang-css.js"></script>
    <!--[if lt IE 9]>
      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    <link type="text/css" rel="stylesheet" href="styles/ionicons.min.css">
    <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
    <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
    <link type="text/css" rel="stylesheet" href="styles/custom.css"/>
    <link type="text/css" rel="stylesheet" href="styles/tomorrow-night.min.css"/>
    <script type="text/javascript" src="scripts/lodash.min.js"></script>
    <script type="text/javascript" src="scripts/jquery.min.js"></script>
    <script type="text/javascript" src="scripts/search.js"></script>
</head>
<body>

<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
  <div class="navicon"></div>
</label>

<label for="nav-trigger" class="overlay"></label>

<nav>
  <a href="http://www.yocto.re" target="_blank"><img class="logo" src="./extras/logo-yocto.png" alt="logo-yocto"/></a>
    <h2><a href="index.html">Home</a><span class="version">v2.0.5</span></h2><input class="search" placeholder="Type your search here ...." /><h3>Classes</h3><ul><li><a href="Config.html">Config</a><ul class='methods'><li data-type='method'><a href="Config.html#addCustomSchema">addCustomSchema</a></li><li data-type='method'><a href="Config.html#autoEnableValidators">autoEnableValidators</a></li><li data-type='method'><a href="Config.html#enableExpress">enableExpress</a></li><li data-type='method'><a href="Config.html#enableMongoose">enableMongoose</a></li><li data-type='method'><a href="Config.html#enablePassportJs">enablePassportJs</a></li><li data-type='method'><a href="Config.html#enableRender">enableRender</a></li><li data-type='method'><a href="Config.html#enableRouter">enableRouter</a></li><li data-type='method'><a href="Config.html#enableSchema">enableSchema</a></li><li data-type='method'><a href="Config.html#find">find</a></li><li data-type='method'><a href="Config.html#get">get</a></li><li data-type='method'><a href="Config.html#getConfig">getConfig</a></li><li data-type='method'><a href="Config.html#load">load</a></li><li data-type='method'><a href="Config.html#loadPassport">loadPassport</a></li><li data-type='method'><a href="Config.html#reload">reload</a></li><li data-type='method'><a href="Config.html#set">set</a></li><li data-type='method'><a href="Config.html#setConfigPath">setConfigPath</a></li></ul></li><li><a href="Schema.html">Schema</a><ul class='methods'><li data-type='method'><a href="Schema.html#getExpress">getExpress</a></li><li data-type='method'><a href="Schema.html#getMongoose">getMongoose</a></li><li data-type='method'><a href="Schema.html#getPassportJs">getPassportJs</a></li><li data-type='method'><a href="Schema.html#getRender">getRender</a></li><li data-type='method'><a href="Schema.html#getRouter">getRouter</a></li></ul></li></ul>
</nav>

<div id="main">
    
    <h1 class="page-title">index.js</h1>
    

    



    
    <section>
        <article>
            <pre class="prettyprint source linenums"><code>'use strict';

var path    = require('path');
var _       = require('lodash');
var logger  = require('yocto-logger');
var joi     = require('joi');
var fs      = require('fs');
var glob    = require('glob');
var utils   = require('yocto-utils');
var Q       = require('q');
var schema  = require('./schema');

/**
 * Yocto config manager.
 * Manage your configuration file (all / common / env  &amp; specific file)
 *
 * Config file has priority. And priority is defined like a php ini system.
 * `(Other file).json` &lt; `all.json` &lt; `common.json` &lt; `development.json` &lt; `stagging.json` &lt; `production.json`
 *
 * All specific data must be configured on a each correct file.
 *
 * all.json : contains general data
 * common.json : must contains all common data between each env
 * development.json : must contains development data for development environnement
 * staging.json : must contains stagging data for staging environnement
 * production.json : must contains production data for production environnement
 *
 * This Module use some security format rules based on Lusca NPM module : {@link https://www.npmjs.com/package/lusca}
 * - Cross Site Request Forgery (CSRF) headers : {@link https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)}
 * - Content Security Policy (CSP) headers : {@link https://www.owasp.org/index.php/Content_Security_Policy}
 * - MDN CSP usage : {@link https://developer.mozilla.org/en-US/docs/Web/Security/CSP/Using_Content_Security_Policy}
 * - X-FRAME-OPTIONS : {@link https://www.owasp.org/index.php/Clickjacking}
 * - Platform for Privacy Preferences Project (P3P) headers : {@link http://support.microsoft.com/kb/290333}
 * - HTTP Strict Transport Security (HSTS &amp; Chrome HSTS preload) : {@link https://www.owasp.org/index.php/HTTP_Strict_Transport_Security} &amp; {@link https://hstspreload.appspot.com/}
 * - XssProtection : {@link http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx}
 *
 * @date : 12/05/2015
 * @author : ROBERT Mathieu &lt;mathieu@yocto.re>
 * @copyright : Yocto SAS, All right reserved
 * @class Config
 */
function Config (logger) {
  /**
   * Default config value
   *
   * @public
   * @memberof Config
   * @member {Object} config
   * @default {}
   */
  this.config = {};

  /**
   * Default state value, true if config state is or false otherwise
   *
   * @public
   * @memberof Config
   * @member {Boolean} state
   * @default false
   */
  this.state  = false;

  /**
   * Default env value
   *
   * @public
   * @memberof Config
   * @member {String} env
   * @default development
   */
  this.env    = process.env.NODE_ENV || 'development';

  /**
   * Default base path
   *
   * @public
   * @memberof Config
   * @member {String} base
   */
  this.base   = process.cwd();

  /**
   * Default logger instance. can be override by set function
   *
   * @public
   * @memberof Config
   * @member {Instance} logger
   */
  this.logger = logger;

  /**
   * Prefix to use in case of multiple configuration
   *
   * @public
   * @memberof Config
   * @member {String} prefix
   */
  this.suffix = process.env.CONFIG_SUFFIX_PATH || '';

  /**
   * Default schema validation for config validator
   *
   * @public
   * @memberof Config
   * @member {Object}schema
   * @default {
   *  express       : schema.getExpress(),
   *  mongoose      : schema.getMongoose(),
   *  passportJs    : schema.getPassportJs(),
   *  render        : schema.getRender(),
   *  router        : schema.getRouter()
   * }
   */

  this.schemaList = {
    express       : schema.getExpress(),
    mongoose      : schema.getMongoose(),
    passportJs    : schema.getPassportJs(),
    render        : schema.getRender(),
    router        : schema.getRouter()
  };

  /**
   * Current schema to use for validation
   *
   * @public
   * @memberof Config
   * @property {Object} schema
   * @default {}
   */
  this.schema = {};
}

/**
 * Default find function, retreive a config from given name
 *
 * @param {String} name wanted config
 * @param {String} complete true if we need to complete existing config with new
 * @return {Boolean} true if all is ok falser otherwise
 */
Config.prototype.find = function (name, complete) {
  // message
  this.logger.debug([ '[ Config.find ] - Try to enable config for', name].join(' '));

  // search item
  var item = _.has(this.schemaList, name);

  // has item ?
  if (item) {
    // get value
    item = this.schemaList[name];

    // create default object
    var obj = _.set({}, name, item);

    // express item ??
    if (name === 'express' &amp;&amp; _.has(obj, 'express')) {
      // change depth assign
      obj = obj.express;
    }

    // mongoose or sequelize item ?? transform to db
    if (name === 'mongoose' || name === 'sequelize') {
      // change object name for db
      obj = {
        db : obj[name]
      };
    }

    // add item at the end of list ?
    if (_.isBoolean(complete) &amp;&amp; complete) {
      // not extend but merge current object
      _.merge(this.schema, obj);
    } else {
      // simple assignation
      this.schema = obj;
    }
    // message
    this.logger.info(['[ Config.find ] -', name, 'config was correcty activated.'].join(' '));
    // valid statement
    return true;
  }
  // default statement
  return false;
};

/**
 * Enable Express config
 *
 * @param {Boolean} complete true if we need to append new config after existing
 * @return {Boolean} true if all is ok falser otherwise
 */
Config.prototype.enableExpress =  function (complete) {
  // process
  return this.enableSchema('express', complete);
};

/**
 * Enable Yocto Render config
 *
 * @param {Boolean} complete true if we need to append new config after existing
 * @return {Boolean} true if all is ok falser otherwise
 */
Config.prototype.enableRender =  function (complete) {
  // process
  return this.enableSchema('render', complete);
};

/**
 * Enable Yocto Router config
 *
 * @param {Boolean} complete true if we need to append new config after existing
 * @return {Boolean} true if all is ok falser otherwise
 */
Config.prototype.enableRouter =  function (complete) {
  // process
  return this.enableSchema('router', complete);
};

/**
 * Enable Mongoose config
 *
 * @param {Boolean} complete true if we need to append new config after existing
 * @return {Boolean} true if all is ok falser otherwise
 */
Config.prototype.enableMongoose = function (complete) {
  // process
  return this.enableSchema('mongoose', complete);
};

/**
 * Enable PassportJs config
 *
 * @param {Boolean} complete true if we need to append new config after existing
 * @return {Boolean} true if all is ok falser otherwise
 */
Config.prototype.enablePassportJs = function (complete) {
  // process
  return this.enableSchema('passportJs', complete);
};

/**
 * Enable Specific schema config
 *
 * @param {String} name default name to find in schema
 * @param {Boolean} complete true if we need to add new config after existing
 * @return {Boolean} true if all is ok falser otherwise
 */
Config.prototype.enableSchema = function (name, complete) {
  // force complete to be a boolean
  complete = _.isBoolean(complete) ? complete : false;
  // process
  return this.find(name, complete);
};

/**
 * Add a custom schema on config
 *
 * @param {String} name config name to add in schema
 * @param {Object} value config value to add in schema
 * @param {Boolean} enable true if we need to add auto enable of config
 * @param {Boolean} complete true if we need to add new config after existing
 */
Config.prototype.addCustomSchema = function (name, value, enable, complete) {
  // schema already exists ?
  if (_.has(this.schemaList, name) || !_.isObject(value)) {
    // error schema already exists
    this.logger.error([ '[ Config.addCustomerSchema ] - Cannot add new custom schema. Schema :',
                        name, 'already exist, or given value is invalid.' ].join(' '));
    // invalid schema
    return false;
  }

  // add new schema
  _.extend(this.schemaList, _.set({}, name, value));

  // auto enable ?
  if (_.isBoolean(enable) &amp;&amp; enable) {
    // enable schema
    return this.enableSchema(name, complete);
  }

  // valid statement
  return _.has(this.schemaList, name);
};

/**
 * Default set function, a value to a specific params
 *
 * @param {String} name current name to use
 * @param {String} value current value to assign on params name
 * @return {Boolean} true if all is ok false otherwise
 */
Config.prototype.set = function (name, value) {
  // check requirements
  if (!_.isUndefined(name) &amp;&amp; _.isString(name) &amp;&amp; !_.isEmpty(name)) {
    // need to normalize path ?
    if (name === 'base') {
      // is relative path ?
      if (!path.isAbsolute(value)) {
        // normalize
        value = path.normalize([ process.cwd(), value ].join('/'));
      }
    }

    // assign value
    this[name] = value;
    // valid statement
    return true;
  } else {
    // warn message
    this.logger.warning([ '[ Core.set ] - Invalid value given.',
                          'name must be a string and not empty. Operation aborted !' ].join(' '));
  }

  // invalid statement
  return false;
};

/**
 * Retreive default configuration
 *
 * @return {Object} loaded object
 */
Config.prototype.getConfig = function () {
  // default statement
  return this.get('config');
};

/**
 * Set config path
 *
 * @param {String} path default path to use
 * @return {Boolean} true if all is ok false otherwise
 */
Config.prototype.setConfigPath = function (path) {
  // default statement
  return this.set('base', path);
};

/**
 * Return correct property from given name
 *
 * @param {String} name the property name
 * @return {Mixed} needed data
 */
Config.prototype.get = function (name) {
  // default instance
  return this[name];
};

/**
 * Reload config from path
 *
 * @param {String} base if base exists and is valid reassign base and reload
 * @return {Boolean} true if load succeed false otherwise
 */
Config.prototype.reload = function (base) {
  // check base before load for conditional assignation
  if (_.isString(base) &amp;&amp; !_.isEmpty(base)) {
    // change base
    this.base = base;
  }

  // return load statement
  return this.load();
};

/**
 * Load password schema for current configuration
 *
 * @return {Object} return current promise
 */
Config.prototype.loadPassport = function () {
  // retreive passport schema
  // this.schema = this.passport.get();
  // default statement
  return this.load();
};

/**
 * Auto enable validators schema for given list
 *
 * @param {Array} items array of items to enable
 * @return {Boolean} true if all is ok false otherwise
 */
Config.prototype.autoEnableValidators = function (items) {
  // is valid format ?
  if (!_.isArray(items) || _.isEmpty(items)) {
    // warn message invalid data
    this.logger.warning([ '[ Config.autoEnableValidator ] - Cannot check items.',
                          'Is not an array or is empty.' ].join(' '));
    // invalid statement
    return false;
  }

  // parse item and check
  _.each(items, function (item) {
    // schema exists ?
    if (_.has(this.schemaList, item)) {
      // enable succeed ?
      if (!this.enableSchema(item, true)) {
        // success or error ?
        this.logger.error([ '[ Config.autoEnableValidator ] - Auto enable [',
                            item, '] failed' ].join(' '));
      }
    } else {
      // warning
      this.logger.warning([ '[ Config.autoEnableValidator ] - Cannot enable [',
                            item, '] schema does not exists' ].join(' '));
    }
  }.bind(this));

  // default statement
  return true;
};

/**
 * Default load function, load data from all.js constant file
 *
 * @return {Object} return current promise
 */
Config.prototype.load = function () {
  // create async process here
  var deferred = Q.defer();

  // any errors ?? try/catch it
  try {
    // default config object
    var config  = {};

    // build pattern
    var pattern = path.normalize([ this.base, this.suffix, '*.json' ].join('/'));
    var penv    = path.normalize([ this.base, this.suffix, [
      this.env, '.json' ].join('')
    ].join('/'));

    // get file (sync mode)
    var paths = glob.sync(pattern);

    // has a valid path ? no ? stop process
    if (_.isEmpty(paths)) {
      throw 'No config files was found. Operation aborted !';
    }

    // sort path
    paths = _.sortBy(paths, function (p) {
      var all         = path.normalize([ this.base, this.suffix, 'all.json' ].join('/'));
      var common      = path.normalize([ this.base, this.suffix, 'common.json' ].join('/'));
      var development = path.normalize([ this.base, this.suffix, 'development.json' ].join('/'));
      var staging     = path.normalize([ this.base, this.suffix, 'staging.json' ].join('/'));
      var production  = path.normalize([ this.base, this.suffix, 'production.json' ].join('/'));

      // return data order
      return [ p === production, p === staging,
               p === development, p === common, p === all, p === p ].join('|');
    }.bind(this));

    // parse all and merge if no error
    _.each(paths, function (path) {
      // parse all file contains
      var item = JSON.parse(fs.readFileSync(path, 'utf-8'));
      // merge data
      _.merge(config, item);

      // has current env ? if test return false stop env was founded
      return penv !== path;
    });

    // build schema
    this.schema = joi.object().required().keys(this.schema).unknown(true);

    // validate !!!
    var result = joi.validate(config, this.schema, { abortEarly : false });

    // has error
    if (!_.isNull(result.error)) {
      // parse details
      _.each(result.error.details, function (error) {
        this.logger.warning([ '[ Config.load ] - Cannot update config an error occured. Error is :',
                              utils.obj.inspect(error) ].join(' '));
      }.bind(this));

      // throw exception error occured
      throw 'Config validation failed';
    }

    // build directory value to UPPERCASE constants
    _.each(config.directory, function (dir) {
      // build path
      var p = _.first(_.values(dir));

      // is relative path ?
      if (!path.isAbsolute(p)) {
        p = path.normalize([ process.cwd(), p ].join('/'));
      }

      // build key
      var k = [ _.first(_.keys(dir)), 'directory' ].join('_').toUpperCase();

      // assign
      this[k] = p;
    }.bind(this));

    // change state
    this.state = true;

    // valid so assign config
    this.config = result.value;
    // log message
    this.logger.info([ '[ Config.load ] - Success - Config file was changed with files based on :',
                       [ this.base, this.suffix ].join('/') ].join(' '));
    // resolve with valid value
    deferred.resolve(this.config);
  } catch (e) {
    // error message
    this.logger.error([ '[ Config.load ] - an error occured during load config file. Error is :',
                        e ].join(' '));
    // reject
    deferred.reject(e);
  }

  // return true if all is ok
  return deferred.promise;
};

// Default export
module.exports = function (l) {
  // is a valid logger ?
  if (_.isUndefined(l) || _.isNull(l)) {
    logger.warning('[ Config.constructor ] - Invalid logger given. Use internal logger');
    // assign
    l = logger;
  }
  // default statement
  return new (Config)(l);
};
</code></pre>
        </article>
    </section>




</div>

<br class="clear">

<footer>Documentation for application <b>yocto-config<b> was generated at Thu Jun 29 2017 12:04:57 GMT+0400 (RET) with <a href='https://www.npmjs.com/package/yoctodoc'>yocto-doc</a> module - Copyright Yocto © 2017</footer>

<script>prettyPrint();</script>
<script src="scripts/linenumber.js"></script>
</body>
</html>