2018-04-17 19:07:00 +00:00
|
|
|
const fs = require('./fs.js');
|
|
|
|
const File = require('./file.js');
|
|
|
|
const Plugins = require('./plugins.js');
|
|
|
|
const utils = require('../utils/utils.js');
|
|
|
|
const path = require('path');
|
2016-08-22 03:40:05 +00:00
|
|
|
|
2018-04-18 16:59:58 +00:00
|
|
|
const httpContractDir = '.embark/contracts/';
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
var Config = function(options) {
|
|
|
|
this.env = options.env;
|
|
|
|
this.blockchainConfig = {};
|
|
|
|
this.contractsConfig = {};
|
|
|
|
this.pipelineConfig = {};
|
|
|
|
this.webServerConfig = {};
|
|
|
|
this.chainTracker = {};
|
|
|
|
this.assetFiles = {};
|
|
|
|
this.contractsFiles = [];
|
|
|
|
this.configDir = options.configDir || 'config/';
|
|
|
|
this.chainsFile = options.chainsFile || './chains.json';
|
|
|
|
this.plugins = options.plugins;
|
|
|
|
this.logger = options.logger;
|
|
|
|
this.events = options.events;
|
2018-03-31 23:35:20 +00:00
|
|
|
this.embarkConfig = {};
|
2017-03-31 11:39:33 +00:00
|
|
|
};
|
2016-08-22 03:40:05 +00:00
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadConfigFiles = function(options) {
|
|
|
|
var interceptLogs = options.interceptLogs;
|
2017-02-06 11:42:58 +00:00
|
|
|
if (options.interceptLogs === undefined) {
|
|
|
|
interceptLogs = true;
|
|
|
|
}
|
2017-02-21 20:45:10 +00:00
|
|
|
|
2018-01-20 03:08:39 +00:00
|
|
|
if (!fs.existsSync(options.embarkConfig)){
|
2017-02-21 20:45:10 +00:00
|
|
|
this.logger.error('Cannot find file ' + options.embarkConfig + '. Please ensure you are running this command inside the Dapp folder');
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
2017-02-19 03:40:42 +00:00
|
|
|
this.embarkConfig = fs.readJSONSync(options.embarkConfig);
|
2017-01-29 06:28:01 +00:00
|
|
|
this.embarkConfig.plugins = this.embarkConfig.plugins || {};
|
2016-08-22 03:40:05 +00:00
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
this.plugins = new Plugins({plugins: this.embarkConfig.plugins, logger: this.logger, interceptLogs: interceptLogs, events: this.events, config: this});
|
2017-01-26 11:34:00 +00:00
|
|
|
this.plugins.loadPlugins();
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
this.loadEmbarkConfigFile();
|
|
|
|
this.loadBlockchainConfigFile();
|
|
|
|
this.loadStorageConfigFile();
|
|
|
|
this.loadCommunicationConfigFile();
|
|
|
|
|
2017-07-05 12:35:51 +00:00
|
|
|
this.loadContractsConfigFile();
|
2017-03-31 11:39:33 +00:00
|
|
|
this.loadPipelineConfigFile();
|
|
|
|
|
|
|
|
this.loadContractsConfigFile();
|
2018-04-12 21:54:08 +00:00
|
|
|
this.loadExternalContractsFiles();
|
2017-03-29 17:04:35 +00:00
|
|
|
this.loadWebServerConfigFile();
|
|
|
|
this.loadChainTrackerFile();
|
|
|
|
this.loadPluginContractFiles();
|
2016-08-22 03:40:05 +00:00
|
|
|
};
|
2016-09-23 08:37:15 +00:00
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.reloadConfig = function() {
|
2017-03-01 02:26:15 +00:00
|
|
|
this.loadEmbarkConfigFile();
|
2016-09-23 08:37:15 +00:00
|
|
|
this.loadBlockchainConfigFile();
|
2017-02-10 12:44:06 +00:00
|
|
|
this.loadStorageConfigFile();
|
|
|
|
this.loadCommunicationConfigFile();
|
2016-09-23 08:37:15 +00:00
|
|
|
this.loadContractsConfigFile();
|
2017-03-01 02:26:15 +00:00
|
|
|
this.loadPipelineConfigFile();
|
2016-09-23 08:37:15 +00:00
|
|
|
this.loadContractsConfigFile();
|
2018-04-12 21:54:08 +00:00
|
|
|
this.loadExternalContractsFiles();
|
2016-09-25 01:10:47 +00:00
|
|
|
this.loadChainTrackerFile();
|
2016-09-23 08:37:15 +00:00
|
|
|
};
|
2016-08-22 03:40:05 +00:00
|
|
|
|
2018-04-02 18:44:55 +00:00
|
|
|
Config.prototype._mergeConfig = function(configFilePath, defaultConfig, env, enabledByDefault) {
|
2018-04-01 01:06:00 +00:00
|
|
|
if (!configFilePath) {
|
2018-04-01 01:15:53 +00:00
|
|
|
let configToReturn = defaultConfig['default'] || {};
|
2018-04-02 18:44:55 +00:00
|
|
|
configToReturn.enabled = enabledByDefault || false;
|
2018-04-01 01:08:25 +00:00
|
|
|
return configToReturn;
|
2018-04-01 01:06:00 +00:00
|
|
|
}
|
|
|
|
|
2018-01-20 03:08:39 +00:00
|
|
|
if (!fs.existsSync(configFilePath)) {
|
2018-04-01 01:15:53 +00:00
|
|
|
// TODO: remove this if
|
|
|
|
if (this.logger) {
|
|
|
|
this.logger.warn("no config file found at " + configFilePath + ". using default config");
|
|
|
|
}
|
2018-01-20 03:08:39 +00:00
|
|
|
return defaultConfig['default'] || {};
|
2017-12-16 13:48:37 +00:00
|
|
|
}
|
2017-03-01 02:26:15 +00:00
|
|
|
|
2018-01-20 03:08:39 +00:00
|
|
|
let config = fs.readJSONSync(configFilePath);
|
|
|
|
let configObject = utils.recursiveMerge(defaultConfig, config);
|
|
|
|
|
|
|
|
if (env) {
|
|
|
|
return utils.recursiveMerge(configObject['default'] || {}, configObject[env]);
|
|
|
|
} else {
|
2018-01-20 14:01:18 +00:00
|
|
|
return configObject;
|
2017-03-01 02:26:15 +00:00
|
|
|
}
|
2016-08-22 03:40:05 +00:00
|
|
|
};
|
|
|
|
|
2018-04-01 01:06:00 +00:00
|
|
|
Config.prototype._getFileOrOject = function(object, filePath, property) {
|
2018-04-01 01:15:53 +00:00
|
|
|
if (typeof (this.configDir) === 'object') {
|
2018-04-01 01:06:00 +00:00
|
|
|
return this.configDir[property];
|
|
|
|
}
|
2018-04-01 01:15:53 +00:00
|
|
|
return this.configDir + filePath;
|
2018-04-01 01:06:00 +00:00
|
|
|
};
|
|
|
|
|
2018-01-20 03:08:39 +00:00
|
|
|
Config.prototype.loadBlockchainConfigFile = function() {
|
|
|
|
var configObject = {
|
|
|
|
"default": {
|
|
|
|
"enabled": true
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-04-02 18:44:55 +00:00
|
|
|
let configFilePath = this._getFileOrOject(this.configDir, 'blockchain.json', 'blockchain');
|
|
|
|
|
|
|
|
this.blockchainConfig = this._mergeConfig(configFilePath, configObject, this.env, true);
|
2018-01-20 03:08:39 +00:00
|
|
|
};
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadContractsConfigFile = function() {
|
2018-04-01 01:06:00 +00:00
|
|
|
var defaultVersions = {
|
|
|
|
"web3.js": "1.0.0-beta",
|
|
|
|
"solc": "0.4.17"
|
2018-04-01 01:15:53 +00:00
|
|
|
};
|
2018-04-01 01:06:00 +00:00
|
|
|
var versions = utils.recursiveMerge(defaultVersions, this.embarkConfig.versions || {});
|
|
|
|
|
2017-07-06 00:24:28 +00:00
|
|
|
var configObject = {
|
2017-12-16 13:12:38 +00:00
|
|
|
"default": {
|
2018-04-01 01:06:00 +00:00
|
|
|
"versions": versions,
|
2017-12-16 13:12:38 +00:00
|
|
|
"deployment": {
|
2018-01-20 03:08:39 +00:00
|
|
|
"host": "localhost", "port": 8545, "type": "rpc"
|
2017-12-16 13:12:38 +00:00
|
|
|
},
|
|
|
|
"dappConnection": [
|
|
|
|
"$WEB3",
|
|
|
|
"localhost:8545"
|
|
|
|
],
|
|
|
|
"gas": "auto",
|
|
|
|
"contracts": {
|
|
|
|
}
|
|
|
|
}
|
2017-07-06 00:24:28 +00:00
|
|
|
};
|
2017-03-31 11:39:33 +00:00
|
|
|
|
2017-12-29 13:08:04 +00:00
|
|
|
var contractsConfigs = this.plugins.getPluginsProperty('contractsConfig', 'contractsConfigs');
|
|
|
|
contractsConfigs.forEach(function(pluginConfig) {
|
|
|
|
configObject = utils.recursiveMerge(configObject, pluginConfig);
|
|
|
|
});
|
2017-01-26 11:34:00 +00:00
|
|
|
|
2018-04-01 01:06:00 +00:00
|
|
|
let configFilePath = this._getFileOrOject(this.configDir, 'contracts.json', 'contracts');
|
|
|
|
|
|
|
|
this.contractsConfig = this._mergeConfig(configFilePath, configObject, this.env);
|
2016-08-22 03:40:05 +00:00
|
|
|
};
|
|
|
|
|
2018-04-17 19:07:00 +00:00
|
|
|
Config.prototype.getExternalContractUrl = function (contract) {
|
|
|
|
let url;
|
|
|
|
const RAW_URL = 'https://raw.githubusercontent.com/';
|
|
|
|
const MALFORMED_ERROR = 'Malformed Github URL for ';
|
|
|
|
if (contract.file.startsWith('https://github')) {
|
|
|
|
const match = contract.file.match(/https:\/\/github\.[a-z]+\/(.*)/);
|
|
|
|
if (!match) {
|
|
|
|
this.logger.error(MALFORMED_ERROR + contract.file);
|
2018-04-19 14:05:11 +00:00
|
|
|
return null;
|
2018-04-17 19:07:00 +00:00
|
|
|
}
|
|
|
|
url = `${RAW_URL}${match[1].replace('blob/', '')}`;
|
|
|
|
} else if (contract.file.startsWith('git')) {
|
|
|
|
// Match values
|
|
|
|
// [0] entire input
|
|
|
|
// [1] git://
|
|
|
|
// [2] user
|
|
|
|
// [3] repository
|
|
|
|
// [4] path
|
|
|
|
// [5] branch
|
|
|
|
const match = contract.file.match(
|
2018-04-19 14:05:11 +00:00
|
|
|
/(git:\/\/)?github\.[a-z]+\/([a-zA-Z0-9_\-.]+)\/([a-zA-Z0-9_\-]+)\/([a-zA-Z0-9_\-\/.]+)#?([a-zA-Z0-1_\-.]*)?/
|
2018-04-17 19:07:00 +00:00
|
|
|
);
|
|
|
|
if (!match) {
|
|
|
|
this.logger.error(MALFORMED_ERROR + contract.file);
|
2018-04-19 14:05:11 +00:00
|
|
|
return null;
|
2018-04-17 19:07:00 +00:00
|
|
|
}
|
|
|
|
let branch = match[5];
|
|
|
|
if (!branch) {
|
|
|
|
branch = 'master';
|
|
|
|
}
|
|
|
|
url = `${RAW_URL}${match[2]}/${match[3]}/${branch}/${match[4]}`;
|
|
|
|
} else {
|
|
|
|
url = contract.file;
|
|
|
|
}
|
2018-04-19 14:05:11 +00:00
|
|
|
const match = url.match(
|
|
|
|
/\.[a-z]+\/([a-zA-Z0-9_\-\/.]+)/
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
url,
|
|
|
|
filePath: match[1]
|
|
|
|
};
|
2018-04-17 19:07:00 +00:00
|
|
|
};
|
|
|
|
|
2018-04-18 16:09:42 +00:00
|
|
|
Config.prototype.loadExternalContractsFiles = function() {
|
2018-04-12 21:54:08 +00:00
|
|
|
let contracts = this.contractsConfig.contracts;
|
|
|
|
for (let contractName in contracts) {
|
|
|
|
let contract = contracts[contractName];
|
2018-04-12 22:50:47 +00:00
|
|
|
if (!contract.file) {
|
|
|
|
continue;
|
|
|
|
}
|
2018-04-17 19:07:00 +00:00
|
|
|
if (contract.file.startsWith('http') || contract.file.startsWith('git')) {
|
2018-04-19 14:05:11 +00:00
|
|
|
const fileObj = this.getExternalContractUrl(contract);
|
|
|
|
if (!fileObj) {
|
|
|
|
return this.logger.error("HTTP contract file not found: " + contract.file);
|
|
|
|
}
|
|
|
|
const localFile = httpContractDir + fileObj.filePath;
|
|
|
|
this.contractsFiles.push(new File({filename: localFile, type: File.types.http, basedir: '', path: fileObj.url}));
|
2018-04-17 19:07:00 +00:00
|
|
|
} else if (fs.existsSync(contract.file)) {
|
2018-04-18 19:02:50 +00:00
|
|
|
this.contractsFiles.push(new File({filename: contract.file, type: File.types.dapp_file, basedir: '', path: contract.file}));
|
2018-04-12 22:50:47 +00:00
|
|
|
} else if (fs.existsSync(path.join('./node_modules/', contract.file))) {
|
2018-04-18 19:02:50 +00:00
|
|
|
this.contractsFiles.push(new File({filename: path.join('./node_modules/', contract.file), type: File.types.dapp_file, basedir: '', path: path.join('./node_modules/', contract.file)}));
|
2018-04-13 00:30:20 +00:00
|
|
|
} else {
|
|
|
|
this.logger.error("contract file not found: " + contract.file);
|
2018-04-12 21:54:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadStorageConfigFile = function() {
|
2018-04-01 01:06:00 +00:00
|
|
|
var versions = utils.recursiveMerge({"ipfs-api": "17.2.4"}, this.embarkConfig.versions || {});
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
var configObject = {
|
2017-03-06 02:27:33 +00:00
|
|
|
"default": {
|
2018-04-01 01:06:00 +00:00
|
|
|
"versions": versions,
|
2017-03-06 02:27:33 +00:00
|
|
|
"enabled": true,
|
|
|
|
"available_providers": ["ipfs"],
|
|
|
|
"ipfs_bin": "ipfs",
|
|
|
|
"provider": "ipfs",
|
|
|
|
"host": "localhost",
|
2017-07-23 12:15:40 +00:00
|
|
|
"port": 5001,
|
|
|
|
"getUrl": "http://localhost:8080/ipfs/"
|
2017-03-31 11:39:33 +00:00
|
|
|
}
|
2017-03-06 02:27:33 +00:00
|
|
|
};
|
2017-02-10 12:44:06 +00:00
|
|
|
|
2018-04-01 01:06:00 +00:00
|
|
|
let configFilePath = this._getFileOrOject(this.configDir, 'storage.json', 'storage');
|
|
|
|
|
|
|
|
this.storageConfig = this._mergeConfig(configFilePath, configObject, this.env);
|
2017-02-10 12:44:06 +00:00
|
|
|
};
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadCommunicationConfigFile = function() {
|
|
|
|
var configObject = {
|
2017-03-06 02:27:33 +00:00
|
|
|
"default": {
|
|
|
|
"enabled": true,
|
|
|
|
"provider": "whisper",
|
2017-10-19 22:55:49 +00:00
|
|
|
"available_providers": ["whisper", "orbit"],
|
|
|
|
"connection": {
|
2018-01-20 03:08:39 +00:00
|
|
|
"host": "localhost", "port": 8546, "type": "ws"
|
2017-10-19 22:55:49 +00:00
|
|
|
}
|
2017-03-06 02:27:33 +00:00
|
|
|
}
|
|
|
|
};
|
2017-02-10 12:44:06 +00:00
|
|
|
|
2018-04-01 01:06:00 +00:00
|
|
|
let configFilePath = this._getFileOrOject(this.configDir, 'communication.json', 'communication');
|
|
|
|
|
|
|
|
this.communicationConfig = this._mergeConfig(configFilePath, configObject, this.env);
|
2017-02-10 12:44:06 +00:00
|
|
|
};
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadWebServerConfigFile = function() {
|
2018-01-20 03:08:39 +00:00
|
|
|
var configObject = {
|
|
|
|
"enabled": true, "host": "localhost", "port": 8000
|
2017-03-04 19:20:28 +00:00
|
|
|
};
|
2018-01-20 03:08:39 +00:00
|
|
|
|
2018-04-01 01:06:00 +00:00
|
|
|
let configFilePath = this._getFileOrOject(this.configDir, 'webserver.json', 'webserver');
|
|
|
|
|
|
|
|
this.webServerConfig = this._mergeConfig(configFilePath, configObject, false);
|
2017-03-04 19:20:28 +00:00
|
|
|
};
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadEmbarkConfigFile = function() {
|
|
|
|
var contracts = this.embarkConfig.contracts;
|
2016-08-22 03:40:05 +00:00
|
|
|
this.contractsFiles = this.loadFiles(contracts);
|
2017-12-15 22:14:00 +00:00
|
|
|
// determine contract 'root' directories
|
|
|
|
this.contractDirectories = contracts.map((dir) => {
|
|
|
|
return dir.split("**")[0];
|
|
|
|
}).map((dir) => {
|
|
|
|
return dir.split("*.")[0];
|
|
|
|
});
|
2018-04-18 16:59:58 +00:00
|
|
|
this.contractDirectories.push(httpContractDir);
|
2016-08-22 03:40:05 +00:00
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
this.buildDir = this.embarkConfig.buildDir;
|
2017-03-01 02:26:15 +00:00
|
|
|
this.configDir = this.embarkConfig.config;
|
|
|
|
};
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadPipelineConfigFile = function() {
|
|
|
|
var assets = this.embarkConfig.app;
|
|
|
|
for(var targetFile in assets) {
|
2017-12-12 17:20:57 +00:00
|
|
|
this.assetFiles[targetFile] = this.loadFiles(assets[targetFile]);
|
2016-08-22 03:40:05 +00:00
|
|
|
}
|
|
|
|
};
|
2016-09-25 01:10:47 +00:00
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadChainTrackerFile = function() {
|
2018-01-20 03:08:39 +00:00
|
|
|
if (!fs.existsSync(this.chainsFile)) {
|
|
|
|
this.logger.info(this.chainsFile + ' file not found, creating it...');
|
2017-02-19 03:40:42 +00:00
|
|
|
fs.writeJSONSync(this.chainsFile, {});
|
2016-09-25 01:10:47 +00:00
|
|
|
}
|
2018-01-20 03:08:39 +00:00
|
|
|
|
|
|
|
this.chainTracker = fs.readJSONSync(this.chainsFile);
|
2016-09-25 01:10:47 +00:00
|
|
|
};
|
2016-08-22 03:40:05 +00:00
|
|
|
|
2018-02-24 01:36:11 +00:00
|
|
|
function findMatchingExpression(filename, filesExpressions) {
|
|
|
|
for (let fileExpression of filesExpressions) {
|
|
|
|
var matchingFiles = utils.filesMatchingPattern(fileExpression);
|
2018-02-24 14:26:43 +00:00
|
|
|
for (let matchFile of matchingFiles) {
|
2018-02-24 01:36:11 +00:00
|
|
|
if (matchFile === filename) {
|
2018-02-24 14:26:43 +00:00
|
|
|
return path.dirname(fileExpression).replace(/\*/g, '');
|
2018-02-24 01:36:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-24 14:26:43 +00:00
|
|
|
return path.dirname(filename);
|
2018-02-24 01:36:11 +00:00
|
|
|
}
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadFiles = function(files) {
|
|
|
|
var self = this;
|
|
|
|
var originalFiles = utils.filesMatchingPattern(files);
|
|
|
|
var readFiles = [];
|
2016-08-22 03:40:05 +00:00
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
originalFiles.filter(function(file) {
|
2017-07-03 22:54:31 +00:00
|
|
|
return (file[0] === '$' || file.indexOf('.') >= 0);
|
2017-03-31 11:39:33 +00:00
|
|
|
}).filter(function(file) {
|
2018-02-24 01:36:11 +00:00
|
|
|
let basedir = findMatchingExpression(file, files);
|
2018-04-18 19:02:50 +00:00
|
|
|
readFiles.push(new File({filename: file, type: File.types.dapp_file, basedir: basedir, path: file}));
|
2016-08-22 03:40:05 +00:00
|
|
|
});
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
var filesFromPlugins = [];
|
|
|
|
var filePlugins = self.plugins.getPluginsFor('pipelineFiles');
|
2018-01-20 03:08:39 +00:00
|
|
|
filePlugins.forEach(function(plugin) {
|
|
|
|
try {
|
|
|
|
var fileObjects = plugin.runFilePipeline();
|
|
|
|
for (var i=0; i < fileObjects.length; i++) {
|
|
|
|
var fileObject = fileObjects[i];
|
|
|
|
filesFromPlugins.push(fileObject);
|
2017-02-03 11:30:08 +00:00
|
|
|
}
|
2018-01-20 03:08:39 +00:00
|
|
|
}
|
|
|
|
catch(err) {
|
|
|
|
self.logger.error(err.message);
|
|
|
|
}
|
|
|
|
});
|
2017-03-31 11:39:33 +00:00
|
|
|
filesFromPlugins.filter(function(file) {
|
2017-02-18 19:37:07 +00:00
|
|
|
if (utils.fileMatchesPattern(files, file.intendedPath)) {
|
2017-02-03 11:30:08 +00:00
|
|
|
readFiles.push(file);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-08-22 03:40:05 +00:00
|
|
|
return readFiles;
|
|
|
|
};
|
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
Config.prototype.loadPluginContractFiles = function() {
|
|
|
|
var self = this;
|
2017-01-26 11:34:00 +00:00
|
|
|
|
2017-03-31 11:39:33 +00:00
|
|
|
var contractsPlugins = this.plugins.getPluginsFor('contractFiles');
|
2018-01-20 03:08:39 +00:00
|
|
|
contractsPlugins.forEach(function(plugin) {
|
|
|
|
plugin.contractsFiles.forEach(function(file) {
|
|
|
|
var filename = file.replace('./','');
|
2018-04-18 19:02:50 +00:00
|
|
|
self.contractsFiles.push(new File({filename: filename, type: File.types.custom, resolver: function(callback) {
|
2018-01-20 03:08:39 +00:00
|
|
|
callback(plugin.loadPluginFile(file));
|
|
|
|
}}));
|
2017-01-26 11:34:00 +00:00
|
|
|
});
|
2018-01-20 03:08:39 +00:00
|
|
|
});
|
2017-01-26 11:34:00 +00:00
|
|
|
};
|
|
|
|
|
2016-08-22 03:40:05 +00:00
|
|
|
module.exports = Config;
|