/**
* Command line utils module. Contains service functions for working with the command line arguments
* @example
var argv = require('cmd/argv');
// connect to server
var session = argv.establishConnectionFromCmdLineAttributes();
console.log('Session.uData: ', session.uData, typeof session.uData, session.uData.lang);
userLang = session.uData.lang || 'en';
conn = session.connection;
// obtain domain information
var domainInfo = conn.getDomainInfo();
* @module cmd/argv
*/
var
assert = require('assert'),
ok = assert.ok,
fs = require('fs'),
http = require('http'),
options = require('./options'),
UBConnection = require('UBConnection');
/**
* @deprecated Use `options.switchIndex' instead
* @param switchName
* @returns {Number} switch index if found or -1 otherwise
*/
exports.findCmdLineSwitch = options.switchIndex;
/**
* @deprecated Use `options.switchValue' instead
* @param switchName
* @returns {String} switch value or `undefined` in case switch not found or switch not have value
*/
exports.findCmdLineSwitchValue = options.switchValue;
/**
* Get config file name. if -cfg switch passed then use this switch value, else use default
* @return {String}
*/
exports.getConfigFileName = function getConfigFileName() {
var
cfgFile;
if (cfgFile = options.switchValue('cfg')) {
cfgFile = relToAbs(process.cwd(), cfgFile);
if (!fs.isFile(cfgFile)){
console.warn('passed -cfg file not exist ' + cfgFile);
cfgFile = '';
}
}
if (!cfgFile){
cfgFile = process.cwd() + 'ubConfig.json';
cfgFile = fs.isFile(cfgFile) ? cfgFile: '';
}
if (!cfgFile){
cfgFile = process.binPath + 'ubConfig.json';
cfgFile = fs.isFile(cfgFile) ? cfgFile: '';
}
if (!cfgFile) throw new Error('Server configuration file not found');
return cfgFile;
};
/**
* Describe set of command line attributes taken by {@link cmd.argv#establishConnectionFromCmdLineAttributes} method.
*
* To be used during display command help message:
*
var argv = require('./argv');
if (argv.findCmdLineSwitch('help') !== -1){
console.info([
'This command throw error in case server is started',
'Usage: ',
'>UB -f cmd/checkServerNotStarted ' + argv.establishConnectionFromCmdLineAttributesUsageInfo
].join('\r\n'));
return;
}
* @type {String}
*/
exports.establishConnectionFromCmdLineAttributesUsageInfo = [
'[-host http(s)://hostName:port] [-u userName] [-p password] [-cfg localServerConfig]',
'Where:',
' -host Server URL to connect, including protocol. If omitted UB_HOST environment variable used. Default is `http://localhost:888`',
' -u User name (if omitted UB_USER environment variable used)',
' -p User password (if omitted UB_PWD environment variable used)',
' -cfg Path to server config'
].join('\r\n');
var verboseMode = options.switchIndex('noLogo') === -1 ;
/**
* @class ServerSession
*/
function ServerSession(config){
/**
* @type {String}
* @readonly
*/
this.HOST = config.host;
/**
* @type {String}
* @readonly
*/
this.USER = config.user;
/**
* @type {String}
* @readonly
*/
this.PWD = config.pwd;
/**
* Custom user data returned by server login method
* @type {String}
* @readonly
*/
this.uData = null;
this.__serverStartedByMe = false;
/**
* @type {UBConnection}
*/
this.connection = null;
/**
* Shut down server in case it started during connection establish or logout from remote server
* @method
*/
this.logout = function () {
if (this.__serverStartedByMe) {
if (verboseMode) console.info('Shut down local server');
stopServer();
} else {
this.connection.logout();
}
};
/**
* Result of `getAppInfo` endpoint execution
* @type {Object}
*/
this.appInfo = {};
}
/**
* Parse cmd line and environment variables for command line parameters expected by UnityBase `cmd` mode
* @return {ServerSession}
*/
exports.serverSessionFromCmdLineAttributes = function serverSessionFromCmdLineAttributes(config) {
if (!config){
config = options.describe('', '').add(exports.establishConnectionFromCmdLineAttributes._cmdLineParams).parse();
}
return new ServerSession(config);
};
/**
* Service function for establish UnityBase server connection from client-side command line script.
* Parse command line attributes for switches `host`, `u`, `p` and:
*
* - Check specified server is started (simple request to `host`) and if not started then
* start server locally with local config
* - Establish connection to specified host
* - Retrieve application information and in case authorization is required then call login method using `u` and `p` params
* - Return serverSession object with connection in case of success or throw assertion error
* @param {Object} [config]
* @param {String} [config.host]
* @param {String} [config.user]
* @param {String} [config.pwd]
* @param {Boolean} [config.forceStartServer=false} If we sure local server not started - start it without checking. Faster because check take near 2 sec.
* @return {ServerSession}
*/
exports.establishConnectionFromCmdLineAttributes = function establishConnectionFromCmdLineAttributes(config){
var
serverSession,conn;
if (!config){ // for a backward compatibility with UB 1.11
config = options.describe('', '').add(exports.establishConnectionFromCmdLineAttributes._cmdLineParams).parse();
}
serverSession = this.serverSessionFromCmdLineAttributes(config);
// in case we operate from GUI server use existed connection
if (global.conn){
serverSession.connection = global.conn;
} else {
//if ((hostStart === 'localhost') || (hostStart === '127') || (hostStart === '10')) {
var serverStarted = config.forceStartServer ? false : this.checkServerStarted(serverSession.HOST);
if (serverStarted) {
if (verboseMode) console.info('Server is running - use started server instance');
} else {
if (verboseMode) console.info('Server not started - start local server instance');
ok(startServer(), 'Local server started');
serverSession.__serverStartedByMe = true;
}
//}
conn = serverSession.connection = new UBConnection({ URL: serverSession.HOST });
conn.onRequestAuthParams = function () {
return {login: serverSession.USER, password: serverSession.PWD};
};
ok(serverSession.appInfo = conn.getAppInfo(), 'Run server method getAppInfo fail');
if (verboseMode) console.info('Connected to ', serverSession.HOST);
}
return serverSession;
};
exports.establishConnectionFromCmdLineAttributes._cmdLineParams = [
{short: 'host', long: 'host', param: 'fullServerURL', defaultValue: 'http://localhost:888', searchInEnv: true, help: 'Server URL to connect, including protocol'},
{short: 'u', long: 'user', param: 'userName', searchInEnv: true, help: 'User name'},
{short: 'p', long: 'pwd', param: 'password', searchInEnv: true, help: 'User password'},
{short: 'cfg', long: 'cfg', param: 'localServerConfig', defaultValue: 'ubConfig.json', searchInEnv: false, help: 'Path to server config'}
];
/**
* Perform check somebody listen on URL
* @param {String} URL
* @return {boolean}
*/
exports.checkServerStarted = function checkServerStarted(URL){
if (verboseMode) console.info('Check server is running...');
try {
var resp = http.get({URL: URL, connectTimeout: 1000, receiveTimeout: 1000, sendTimeout: 1000}); //dummy
if (verboseMode) console.debug('STATUS', resp.statusCode);
return true;
} catch(e) {}
return false;
};
/**
* Will replace placeholders %VAR_NAME% to environment variable value
* @private
* @param {String} content
* @return {String}
*/
function replaceEnvironmentVariables(content){
return content.replace(/%(.*?)%/gm, function replacer(match, p1){
return process.env[p1] ? process.env[p1].replace(/\\/g, '\\\\') : 'NOT_FOUND_ENV_VAR(' + match + ')';
});
}
/**
* Will replace placeholders "#include(pathToFile) to file content
* @private
* @param {String} content
* @return {String}
*/
function replaceIncludeVariables(content){
return content.replace(/"#include\((.*)\)"/gm, function replacer(match, p1){
var filePath, content, res;
try {
filePath = JSON.parse('{"f": "' + p1 + '"}');
}catch(e){
return 'INVALID INCLUDE ' + p1;
}
filePath = relToAbs(process.configPath, filePath.f);
if (!fs.statSync(filePath)){
return 'INVALID INCLUDE ' + filePath;
}
content = removeCommentsFromJSON(fs.readFileSync(filePath));
if (!content){
return 'EMPTY INCLUDE ' + filePath;
}
return replaceEnvironmentVariables(content);
});
}
/**
* Read server configuration using file, resolved by argv.getConfigFileName
* parse it in safe mode, replace environment variables by it values and return parsed config
*
* @return {Object}
*/
exports.getServerConfiguration = function getServerConfiguration(){
var cfgFileName = this.getConfigFileName();
if (verboseMode) console.debug('Used config:', cfgFileName);
var content = removeCommentsFromJSON(fs.readFileSync(cfgFileName));
content = replaceIncludeVariables(replaceEnvironmentVariables(content));
var result = safeParseJSON(content, cfgFileName);
// add name attribute for applications
if (!result.application){
result.application = {};
}
result.application.name = result.httpServer.path ? result.httpServer.path : '/';
return result;
};
/**
* Return a URL server actually listen on
* @param {Object} config Server configuration
*/
exports.serverURLFromConfig = function serverURlFromConfig(config){
var httpCfg = config.httpServer || {};
var rUrl = (httpCfg.protocol && httpCfg.protocol === 'https') ? 'https://' : 'http://';
// in case of serverDomainNames in [+, *] replace it to localhost
rUrl += httpCfg.host ? (httpCfg.host.length === 1 ? 'localhost' : httpCfg.host) : 'localhost';
rUrl += httpCfg.port ? ':' + httpCfg.port : ':80';
if (httpCfg.path) rUrl += '/' + httpCfg.path;
return rUrl;
};
/**
* Identify application, for which command is executed.
* First check for command line parameter -app, if not defined - environment variable UB_APP, if not defined - first application in config
*
* @throws Error if application not found
*
* @deprecated Starting from 1.11 config contain only one application, so use `config.application` directly
* @param {Object} serverConfig
* @return {Object} Application config
*/
exports.identifyApp = function identifyApp(serverConfig){
var appName = options.switchValue('app') || process.env['UB_APP'];
if (appName) console.warn('`-app` command line switch and UB_APP environment variable is deprecated. Starting from 1.11 config contain only one application');
var result = serverConfig.application;
if (!result) throw new Error('"application" section not found in server configuration');
if (!result.domain || !result.domain.models){
throw new Error('domain models not configured for application');
}
return result;
};
function safeParseJSON(content, fileName){
var
jsonlint = require('jsonlint');
try{
return JSON.parse(content);
} catch(e) {
}
// we are here in case of wrong JSON - let's parse it slowly and retrieve error line using jsonlint
try {
jsonlint.parse(content); // MPV - buggy implementation of jsonlint replace "\\f" -> "\f" in the string
} catch(e) {
throw new Error('In file: ' + fileName + '\n' + e.message);
}
}
/**
* JSON file parsing, allow to parse semi-JSON files with comments. In case of errors inside JSON show detailed error description
* @todo - rewrite when switch to SpiderMonkey 33 in in which native JSON.parse show error line number
* @param {String} fileName
* @param {Boolean} [allowMultiLineString=false] Replace `\n` before parse (not compatible with JSON format, but multiline string is useful)
* @return {Object}
*/
exports.safeParseJSONfile = function safeParseJSONfile(fileName, allowMultiLineString){
var
content;
content = removeCommentsFromJSON(fs.readFileSync(fileName));
if (allowMultiLineString){
content = content.replace('\n', ' ', 'gm').replace('\r', ' ', 'gm').replace('\t', ' ', 'gm')
}
return safeParseJSON(content, fileName);
};