'use strict'; const fs = require('fs'); const path = require('path'); const semver = require('semver'); require('events').EventEmitter.prototype._maxListeners = 100; let request; let extend; let password; let npmVersion; let crypto; /** * recursively copy values from old object to new one * * @alias copyAttributes * @memberof tools * @param {object} oldObj source object * @param {object} newObj destination object * @param {object} originalObj optional object for read __no_change__ values * @param {boolean} isNonEdit optional indicator if copy is in nonEdit part * */ function copyAttributes(oldObj, newObj, originalObj, isNonEdit) { for (let attr in oldObj) { if (oldObj.hasOwnProperty(attr)) { if (typeof oldObj[attr] !== 'object' || oldObj[attr] instanceof Array) { if (oldObj[attr] === '__no_change__' && originalObj && !isNonEdit) { newObj[attr] = JSON.parse(JSON.stringify(originalObj[attr])); } else if (oldObj[attr] === '__delete__' && !isNonEdit) { if (newObj[attr] !== undefined) { delete newObj[attr]; } } else { newObj[attr] = oldObj[attr]; } } else { newObj[attr] = newObj[attr] || {}; copyAttributes(oldObj[attr], newObj[attr], originalObj && originalObj[attr], isNonEdit || attr === 'nonEdit'); } } } } /** * Checks the flag nonEdit and restores non-changeable values if required * * @alias checkNonEditable * @memberof tools * @param {object} oldObject source object * @param {object} newObject destination object * */ function checkNonEditable(oldObject, newObject) { if (!oldObject) return true; if (!oldObject.nonEdit && !newObject.nonEdit) return true; // if nonEdit is protected with password if (oldObject.nonEdit && oldObject.nonEdit.passHash) { // If new Object wants to update the nonEdit information if (newObject.nonEdit && newObject.nonEdit.password) { crypto = crypto || require('crypto'); const hash = crypto.createHash('sha256').update(newObject.nonEdit.password).digest('base64'); if (oldObject.nonEdit.passHash !== hash) { delete newObject.nonEdit; return false; } else { oldObject.nonEdit = JSON.parse(JSON.stringify(newObject.nonEdit)); delete oldObject.nonEdit.password; delete newObject.nonEdit.password; oldObject.nonEdit.passHash = hash; newObject.nonEdit.passHash = hash; } copyAttributes(newObject.nonEdit, newObject, newObject); return true; } else if (newObject.nonEdit !== undefined) { delete newObject.nonEdit; } } else if (newObject.nonEdit) { oldObject.nonEdit = JSON.parse(JSON.stringify(newObject.nonEdit)); if (newObject.nonEdit.password) { crypto = crypto || require('crypto'); const hash = crypto.createHash('sha256').update(newObject.nonEdit.password).digest('base64'); delete oldObject.nonEdit.password; delete newObject.nonEdit.password; oldObject.nonEdit.passHash = hash; newObject.nonEdit.passHash = hash; } } // restore settings copyAttributes(oldObject.nonEdit, newObject, oldObject); if (newObject.passHash) delete newObject.passHash; if (newObject.nonEdit && newObject.nonEdit.password) delete newObject.nonEdit.password; return true; } // Compare versions function upToDate(online, installed) { online = online.split('.'); installed = installed.split('.'); online[0] = parseInt(online[0], 10); installed[0] = parseInt(installed[0], 10); if (online[0] > installed[0]) { return false; } else if (online[0] === installed[0]) { online[1] = parseInt(online[1], 10); installed[1] = parseInt(installed[1], 10); if (online[1] > installed[1]) { return false; } else if (online[1] === installed[1]) { online[2] = parseInt(online[2], 10); installed[2] = parseInt(installed[2], 10); return installed[2] >= online[2]; } else { return true; } } else { return true; } } function encryptPhrase(password, phrase, callback) { // encrypt secret crypto = crypto || require('crypto'); const cipher = crypto.createCipher('aes192', password); let encrypted = ''; cipher.on('readable', function () { const data = cipher.read(); if (data) { encrypted += data.toString('hex'); } }); cipher.on('end', function () { callback(encrypted); }); cipher.write(phrase); cipher.end(); } function decryptPhrase(password, data, callback) { crypto = crypto || require('crypto'); const decipher = crypto.createDecipher('aes192', password); try { let decrypted = ''; decipher.on('readable', function () { let data = decipher.read(); if (data) { decrypted += data.toString('utf8'); } }); decipher.on('error', function (error) { console.error('Cannot decode secret: ' + error); callback(null); }); decipher.on('end', function () { callback(decrypted); }); decipher.write(data, 'hex'); decipher.end(); } catch (e) { console.error('Cannot decode secret: ' + e); callback(null); } } function getAppName() { const parts = __dirname.replace(/\\/g, '/').split('/'); return parts[parts.length - 2].split('.')[0]; } function rmdirRecursiveSync(path) { if (fs.existsSync(path)) { fs.readdirSync(path).forEach(function (file/*, index*/) { const curPath = path + '/' + file; if (fs.statSync(curPath).isDirectory()) { // recurse rmdirRecursiveSync(curPath); } else { // delete file fs.unlinkSync(curPath); } }); // delete (hopefully) empty folder try { fs.rmdirSync(path); } catch (e) { console.log('Cannot delete directory ' + path + ': ' + e.toString()); } } } function findIPs() { const ifaces = require('os').networkInterfaces(); let ipArr = []; for (let dev in ifaces) { if (!ifaces.hasOwnProperty(dev)) continue; /*jshint loopfunc:true */ ifaces[dev].forEach(function (details) { //noinspection JSUnresolvedVariable if (!details.internal) ipArr.push(details.address); }); } return ipArr; } function findPath(path, url) { if (!url) return ''; if (url.substring(0, 'http://'.length) === 'http://' || url.substring(0, 'https://'.length) === 'https://') { return url; } else { if (path.substring(0, 'http://'.length) === 'http://' || path.substring(0, 'https://'.length) === 'https://') { return (path + url).replace(/\/\//g, '/').replace('http:/', 'http://').replace('https:/', 'https://'); } else { if (url && url[0] === '/') { return __dirname + '/..' + url; } else { return __dirname + '/../' + path + url; } } } } function getMac(callback) { const macRegex = /(?:[a-z0-9]{2}[:\-]){5}[a-z0-9]{2}/ig; const zeroRegex = /(?:[0]{2}[:\-]){5}[0]{2}/; const command = (process.platform.indexOf('win') === 0) ? 'getmac' : 'ifconfig || ip link'; require('child_process').exec(command, function(err, stdout, stderr) { if (err) { callback(err); } else { let macAddress; let match; let result = null; while (match = macRegex.exec(stdout)) { macAddress = match[0]; if (!zeroRegex.test(macAddress) && !result) result = macAddress; } if (result === null) { callback(new Error('could not determine the mac address from:\n' + stdout)); } else { callback(null, result.replace(/-/g, ':').toLowerCase()); } } }); } // Build unique uuid based on MAC address if possible function uuid(givenMac, callback) { if (typeof givenMac === 'function') { callback = givenMac; givenMac = ''; } let mac = (givenMac !== null) ? (givenMac || '') : null; let u; if (mac === '') { const ifaces = require('os').networkInterfaces(); // Find first not empty MAC for (let n in ifaces) { if (!ifaces.hasOwnProperty(n)) continue; for (let c = 0; c < ifaces[n].length; c++) { if (ifaces[n][c].mac && ifaces[n][c].mac !== '00:00:00:00:00:00') { mac = ifaces[n][c].mac; break; } } if (mac) break; } } if (mac === '') { getMac(function(err, mac) { uuid(mac || null, callback); }); return; } if (mac) { const md5sum = require('crypto').createHash('md5'); md5sum.update(mac); mac = md5sum.digest('hex'); u = mac.substring(0, 8) + '-' + mac.substring(8, 12) + '-' + mac.substring(12, 16) + '-' + mac.substring(16, 20) + '-' + mac.substring(20); } else { // Returns a RFC4122 compliant v4 UUID https://gist.github.com/LeverOne/1308368 (DO WTF YOU WANT TO PUBLIC LICENSE) let a; let b; b = a = ''; while (a++ < 36) { b += ((a * 51) & 52) ? (a ^ 15 ? 8 ^ Math.random() * (a ^ 20 ? 16 : 4) : 4).toString(16) : '-'; } u = b; } // todo => delete it after the vendor works 2018.03.28 BF if (fs.existsSync('/opt/io-box/config/setup.cfg')) { u = 'CM' + u; } callback(u); } function createUuid(_objects, callback) { let tasks = 2; let _uuid; _objects.getObject('system.user.admin', function (err, obj) { if (err || !obj) { password = password || require(__dirname + '/password'); // Default Password for user 'admin' is application name in lower case password(getAppName()).hash(null, null, function (err, res) { if (err) console.error(err); // Create user here and not in io-package.js because of hash password let tasks = 0; tasks++; _objects.setObject('system.user.admin', { type: 'user', common: { name: 'admin', password: res, dontDelete: true, enabled: true }, ts: new Date().getTime(), from: 'system.host.' + getHostName() + '.tools', native: {} }, function () { console.log('object system.user.admin created'); if (!--tasks && callback) callback(_uuid); }); }); } else { if (!--tasks && callback) callback(_uuid); } }); _objects.getObject('system.meta.uuid', function (err, obj) { if (!err && obj && obj.native && obj.native.uuid) { if (!--tasks && callback) callback(); } else { uuid(function (res) { _uuid = res; _objects.setObject('system.meta.uuid', { type: 'meta', common: { name: 'uuid', type: 'uuid' }, ts: new Date().getTime(), from: 'system.host.' + getHostName() + '.tools', native: { uuid: res } }, function () { console.log('object system.meta.uuid created: ' + res); if (!--tasks && callback) callback(_uuid); }); }); } }); } // Download file to tmp or return file name directly function getFile(urlOrPath, fileName, callback) { if (!request) request = require('request'); // If object was read if (urlOrPath.substring(0, 'http://'.length) === 'http://' || urlOrPath.substring(0, 'https://'.length) === 'https://') { const tmpFile = __dirname + '/../tmp/' + (fileName || Math.floor(Math.random() * 0xFFFFFFE) + '.zip'); request(urlOrPath).on('error', function (error) { console.log('Cannot download "' + tmpFile + '": ' + error); if (callback) callback(tmpFile); }).pipe(fs.createWriteStream(tmpFile)).on('close', function () { console.log('downloaded ' + tmpFile); if (callback) callback(tmpFile); }); } else { if (fs.existsSync(urlOrPath)) { if (callback) callback(urlOrPath); } else if (fs.existsSync(__dirname + '/../' + urlOrPath)) { if (callback) callback(__dirname + '/../' + urlOrPath); } else if (fs.existsSync(__dirname + '/../tmp/' + urlOrPath)) { if (callback) callback(__dirname + '/../tmp/' + urlOrPath); } else { console.log('File not found: ' + urlOrPath); process.exit(1); } } } // Return content of the json file. Download it or read directly function getJson(urlOrPath, callback) { if (!request) request = require('request'); let sources = {}; // If object was read if (urlOrPath && typeof urlOrPath === 'object') { if (callback) callback(urlOrPath); } else if (!urlOrPath) { console.log('Empty url!'); if (callback) callback(null); } else { if (urlOrPath.substring(0, 'http://'.length) === 'http://' || urlOrPath.substring(0, 'https://'.length) === 'https://') { request({url: urlOrPath, timeout: 10000}, function (error, response, body) { if (error || !body || response.statusCode !== 200) { console.log('Cannot download json from ' + urlOrPath + '. Error: ' + (error || body)); if (callback) callback(null, urlOrPath); return; } try { sources = JSON.parse(body); } catch (e) { console.log('Json file is invalid on ' + urlOrPath); if (callback) callback(null, urlOrPath); return; } if (callback) callback(sources, urlOrPath); }).on('error', function (error) { //console.log('Cannot download json from ' + urlOrPath + '. Error: ' + error); //if (callback) callback(null, urlOrPath); }); } else { if (fs.existsSync(urlOrPath)) { try { sources = JSON.parse(fs.readFileSync(urlOrPath, 'utf8')); } catch (e) { console.log('Cannot parse json file from ' + urlOrPath + '. Error: ' + e); if (callback) callback(null, urlOrPath); return; } if (callback) callback(sources, urlOrPath); } else if (fs.existsSync(__dirname + '/../' + urlOrPath)) { try { sources = JSON.parse(fs.readFileSync(__dirname + '/../' + urlOrPath, 'utf8')); }catch (e) { console.log('Cannot parse json file from ' + __dirname + '/../' + urlOrPath + '. Error: ' + e); if (callback) callback(null, urlOrPath); return; } if (callback) callback(sources, urlOrPath); } else if (fs.existsSync(__dirname + '/../tmp/' + urlOrPath)) { try { sources = JSON.parse(fs.readFileSync(__dirname + '/../tmp/' + urlOrPath, 'utf8')); } catch (e) { console.log('Cannot parse json file from ' + __dirname + '/../tmp/' + urlOrPath + '. Error: ' + e); if (callback) callback(null, urlOrPath); return; } if (callback) callback(sources, urlOrPath); } else { //if (urlOrPath.indexOf('/example/') === -1) console.log('Json file not found: ' + urlOrPath); if (callback) callback(null, urlOrPath); } } } } function scanDirectory(dirName, list, regExp) { if (fs.existsSync(dirName)) { const dirs = fs.readdirSync(dirName); for (let i = 0; i < dirs.length; i++) { try { const fullPath = path.join(dirName, dirs[i]); const fileIoName = path.join(fullPath, 'io-package.json'); const fileName = path.join(fullPath, 'package.json'); if (regExp.test(dirs[i]) && fs.existsSync(fileIoName)) { const ioPackage = JSON.parse(fs.readFileSync(fileIoName, 'utf8')); const package_ = fs.existsSync(fileName) ? JSON.parse(fs.readFileSync(fileName, 'utf8')) : {}; //noinspection JSUnresolvedVariable list[ioPackage.common.name] = { controller: ioPackage.common.controller || false, version: ioPackage.common.version, icon: ioPackage.common.extIcon || (ioPackage.common.icon ? '/adapter/' + dirs[i] + '/' + ioPackage.common.icon : ''), title: ioPackage.common.title, desc: ioPackage.common.desc, platform: ioPackage.common.platform, keywords: ioPackage.common.keywords, readme: ioPackage.common.readme, type: ioPackage.common.type, license: ioPackage.common.license ? ioPackage.common.license : ((package_.licenses && package_.licenses.length) ? package_.licenses[0].type : ''), licenseUrl: (package_.licenses && package_.licenses.length) ? package_.licenses[0].url : '' }; } } catch (e) { console.log('Cannot read or parse ' + __dirname + '/../node_modules/' + dirs[i] + '/io-package.json: ' + e.toString()); } } } } // Get list of all installed adapters and controller version on this host function getInstalledInfo(hostRunningVersion) { let result = {}; const path = __dirname + '/../'; // Get info about host const ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8')); const package_ = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {}; const regExp = new RegExp('^' + module.exports.appName + '\\.', 'i'); //noinspection JSUnresolvedVariable result[ioPackage.common.name] = { controller: true, version: ioPackage.common.version, icon: ioPackage.common.extIcon || ioPackage.common.icon, title: ioPackage.common.title, desc: ioPackage.common.desc, platform: ioPackage.common.platform, keywords: ioPackage.common.keywords, readme: ioPackage.common.readme, runningVersion: hostRunningVersion, license: ioPackage.common.license ? ioPackage.common.license : ((package_.licenses && package_.licenses.length) ? package_.licenses[0].type : ''), licenseUrl: (package_.licenses && package_.licenses.length) ? package_.licenses[0].url : '' }; scanDirectory(__dirname + '/../node_modules', result, regExp); scanDirectory(__dirname + '/../../node_modules', result, regExp); if (fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName.toLowerCase() + '.js-controller') || fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName + '.js-controller')) { scanDirectory(__dirname + '/../..', result, regExp); } return result; } /** * Reads an adapter's npm version * @param {string | null} adapter The adapter to read the npm version from. Null for the root yunkong2 packet * @param {(err: Error | null, version: string) => void} [callback] */ function getNpmVersion(adapter, callback) { adapter = adapter ? module.exports.appName + '.' + adapter : module.exports.appName; adapter = adapter.toLowerCase(); const cliCommand = `npm view ${adapter}@latest version`; const exec = require('child_process').exec; exec(cliCommand, {timeout: 2000}, (error, stdout, stderr) => { let version; if (error) { // command failed if (typeof callback === 'function') { callback(error); return; } } else if (stdout) { version = semver.valid(stdout.trim()); } if (typeof callback === 'function') callback(null, version); }); } function getIoPack(sources, name, callback) { getJson(sources[name].meta, function (ioPack) { const packUrl = sources[name].meta.replace('io-package.json', 'package.json'); if (!ioPack) { if (sources._helper) sources._helper.failCounter.push(name); if (callback) callback(sources, name); } else { setImmediate(function () { getJson(packUrl, function (pack) { const version = sources[name].version; const type = sources[name].type; // If installed from git or something else // js-controller is exception, because can be installed from npm and from git if (sources[name].url && name !== 'js-controller') { if (ioPack && ioPack.common) { sources[name] = extend(true, sources[name], ioPack.common); // overwrite type of adapter from repository if (type) { sources[name].type = type; } if (pack && pack.licenses && pack.licenses.length) { if (!sources[name].license) sources[name].license = pack.licenses[0].type; if (!sources[name].licenseUrl) sources[name].licenseUrl = pack.licenses[0].url; } } if (callback) callback(sources, name); } else { if (ioPack && ioPack.common) { sources[name] = extend(true, sources[name], ioPack.common); if (pack && pack.licenses && pack.licenses.length) { if (!sources[name].license) sources[name].license = pack.licenses[0].type; if (!sources[name].licenseUrl) sources[name].licenseUrl = pack.licenses[0].url; } } // overwrite type of adapter from repository if (type) { sources[name].type = type; } if (version) { sources[name].version = version; if (callback) callback(sources, name); } else { if (sources[name].meta.substring(0, 'http://'.length) === 'http://' || sources[name].meta.substring(0, 'https://'.length) === 'https://') { //installed from npm getNpmVersion(name, function (err, version) { if (version) { sources[name].version = version; } else { sources[name].version = 'npm error'; } if (callback) callback(sources, name); }); } else { if (callback) callback(sources, name); } } } }); }); } }); } function _getRepositoryFile(sources, path, callback) { if (!sources._helper) { let count = 0; for (let _name in sources) { if (!sources.hasOwnProperty(_name)) continue; count++; } sources._helper = {failCounter: []}; sources._helper.timeout = setTimeout(function () { if (sources._helper) { delete sources._helper; for (let __name in sources) { if (!sources.hasOwnProperty(__name)) continue; if (sources[__name].processed !== undefined) delete sources[__name].processed; } if (callback) callback('Timeout by read all package.json (' + count + ') seconds', sources); callback = null; } }, count * 1000); } for (let name in sources) { if (!sources.hasOwnProperty(name)) continue; if (sources[name].processed || name === '_helper') continue; sources[name].processed = true; if (sources[name].url) sources[name].url = findPath(path, sources[name].url); if (sources[name].meta) sources[name].meta = findPath(path, sources[name].meta); if (sources[name].icon) sources[name].icon = findPath(path, sources[name].icon); if (!sources[name].name && sources[name].meta) { getIoPack(sources, name, function (ignore/*, name*/) { if (sources._helper) { if (sources._helper.failCounter.length > 10) { clearTimeout(sources._helper.timeout); delete sources._helper; for (let _name in sources) { if (!sources.hasOwnProperty(_name)) continue; if (sources[_name].processed !== undefined) delete sources[_name].processed; } if (callback) callback('Looks like there is no internet.', sources); callback = null; } else { // process next setImmediate(function () { _getRepositoryFile(sources, path, callback); }); } } }); return; } } // all packages are processed if (sources._helper) { let err; if (sources._helper.failCounter.length) { err = 'Following packages cannot be read: ' + sources._helper.failCounter.join(', '); } clearTimeout(sources._helper.timeout); delete sources._helper; for (let __name in sources) { if (!sources.hasOwnProperty(__name)) continue; if (sources[__name].processed !== undefined) delete sources[__name].processed; } if (callback) callback(err, sources); callback = null; } } // Get list of all adapters and controller in some repository file or in /conf/source-dist.json function getRepositoryFile(urlOrPath, additionalInfo, callback) { let sources = {}; let path = ''; if (typeof additionalInfo === 'function') { callback = additionalInfo; additionalInfo = {}; } if (!additionalInfo) additionalInfo = {}; if (!extend) extend = require('node.extend'); if (urlOrPath) { let parts = urlOrPath.split('/'); path = parts.splice(0, parts.length - 1).join('/') + '/'; } // If object was read if (urlOrPath && typeof urlOrPath === 'object') { if (typeof callback === 'function') callback(null, urlOrPath); } else if (!urlOrPath) { try { sources = JSON.parse(fs.readFileSync(getDefaultDataDir() + 'sources.json', 'utf8')); } catch (e) { sources = {}; } try { const sourcesDist = JSON.parse(fs.readFileSync(__dirname + '/../conf/sources-dist.json', 'utf8')); sources = extend(true, sourcesDist, sources); } catch (e) { } for (let s in sources) { if (sources.hasOwnProperty(s) && additionalInfo[s] && additionalInfo[s].published) { sources[s].published = additionalInfo[s].published } } _getRepositoryFile(sources, path, function (err) { if (err) console.error('[' + new Date() + '] ' + err); if (typeof callback === 'function') callback(err, sources); }); } else { getJson(urlOrPath, function (sources) { if (sources) { for (let s in sources) { if (sources.hasOwnProperty(s) && additionalInfo[s] && additionalInfo[s].published) { sources[s].published = additionalInfo[s].published } } setImmediate(function () { _getRepositoryFile(sources, path, function (err) { if (err) console.error('[' + new Date() + '] ' + err); if (typeof callback === 'function') callback(err, sources); }); }); } else { if (typeof callback === 'function') callback('Cannot read "' + urlOrPath + '"', {}); } }); } } function sendDiagInfo(obj, callback) { if (!request) request = require('request'); console.log('Send diag info: ' + JSON.stringify(obj)); request.post({ url: 'http://download.' + module.exports.appName + '.net/diag.php', method: 'POST', headers: {'content-type': 'application/x-www-form-urlencoded'}, body: 'data=' + JSON.stringify(obj), timeout: 2000 }, function (err, response, body) { /*if (err || !body || response.statusCode !== 200) { }*/ if (typeof callback === 'function') callback(); }).on('error', function (error) { console.log('Cannot send diag info: ' + error.message); if (typeof callback === 'function') callback(error); }); } function getAdapterDir(adapter) { const appName = module.exports.appName; if (adapter.substring(0, appName.length + 1) === appName + '.') { adapter = adapter.substring(appName.length + 1); } let possibilities = [ appName.toLowerCase() + '.' + adapter + '/package.json', appName + '.' + adapter + '/package.json' ]; /** @type {string} */ let controllerPath; for (let i = 0; i < possibilities.length; i++) { // special case to not read adapters from js-controller/node_module/adapter adn check first in parent directory if (fs.existsSync(__dirname + '/../../' + possibilities[i])) { controllerPath = __dirname + '/../../' + possibilities[i]; } else { try { controllerPath = require.resolve(possibilities[i]); break; } catch (e) { /* not found */ } } } if (!controllerPath) { return null; // inactive } else { let parts = path.normalize(controllerPath).split(/[\\\/]/g); parts.pop(); return parts.join('/'); } } function getHostName() { try { const configName = getConfigFileName(); const config = JSON.parse(fs.readFileSync(configName, 'utf8')); return config.system ? config.system.hostname || require('os').hostname() : require('os').hostname(); } catch (err) { return require('os').hostname(); } } /** * Read version of systen npm * * @alias getSystemNpmVersion * @memberof Tools * @param {function} callback return result *

 *            function (err, version) {
 *              adapter.log.debug('NPM version is: ' + version);
 *            }
 *        
*/ function getSystemNpmVersion(callback) { const exec = require('child_process').exec; // remove local node_modules\.bin dir from path // or we potentially get a wrong npm version let newEnv = Object.assign({}, process.env); newEnv.PATH = (newEnv.PATH || newEnv.Path || newEnv.path) .split(path.delimiter) .filter(dir => { dir = dir.toLowerCase(); return !(dir.indexOf('yunkong2') > -1 && dir.indexOf(path.join('node_modules', '.bin')) > -1); }) .join(path.delimiter); exec('npm -v', {encoding: 'utf8', env: newEnv}, function (error, stdout) {//, stderr) { if (stdout) stdout = semver.valid(stdout.trim()); if (callback) callback(error, stdout); }); } /** * Collects information about host and available adapters * * Following info will be collected: * - available adapters * - node.js --version * - npm --version * * @alias getHostInfo * @memberof Tools * @param {object} objects * @param {function} callback return result *

 *            function (err, result) {
 *              adapter.log.debug('Info about host: ' + JSON.stringify(result, null, 2);
 *            }
 *        
*/ function getHostInfo(objects, callback) { const os = require('os'); const cpus = os.cpus(); let data = { 'Platform': os.platform(), 'Architecture': os.arch(), 'CPUs': cpus.length, 'Speed': cpus[0].speed, 'Model': cpus[0].model, 'RAM': os.totalmem(), 'System uptime': Math.round(os.uptime()), 'Node.js': process.version }; let task = 0; task++; objects.getObject('system.config', function (err, systemConfig) { objects.getObject('system.repositories', function (err, repos) { // Check if repositories exists if (!err && repos && repos.native && repos.native.repositories) { const repo = repos.native.repositories[systemConfig.common.activeRepo]; if (repo && repo.json) { let count = 0; for (let a in repo.json) { count++; } data['adapters count'] = count; } } if (!--task) { callback(err, data); } }); }); if (!npmVersion) { task++; getSystemNpmVersion(function (err, version) { data['NPM'] = 'v' + version; npmVersion = version; if (!--task) { callback(err, data); } }); } else { data['NPM'] = npmVersion; if (!task) { callback(null, data); } } } // All pathes are returned always relative to /node_modules/' + module.exports.appName + '.js-controller // the result has always "/" as last symbol function getDefaultDataDir() { //var dataDir = __dirname.replace(/\\/g, '/'); //dataDir = dataDir.split('/'); // if debugging with npm5 if (fs.existsSync(__dirname + '/../../node_modules/' + module.exports.appName + '.js-controller')) { return '../' + module.exports.appName + '-data/'; } else // If installed with npm if (fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName + '.js-controller')) { return '../../' + module.exports.appName + '-data/'; } else { //dataDir.splice(dataDir.length - 1, 1); //dataDir = dataDir.join('/'); return './data/'; } } function getConfigFileName() { /** @type {string|string[]} */ let configDir = __dirname.replace(/\\/g, '/'); configDir = configDir.split('/'); // If installed with npm if (fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName.toLowerCase() + '.js-controller') || fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName + '.js-controller')) { // remove /node_modules/' + module.exports.appName + '.js-controller/lib configDir.splice(configDir.length - 3, 3); configDir = configDir.join('/'); return configDir + '/' + module.exports.appName + '-data/' + module.exports.appName + '.json'; } else // if debugging with npm5 if (fs.existsSync(__dirname + '/../../node_modules/' + module.exports.appName.toLowerCase() + '.js-controller') || fs.existsSync(__dirname + '/../../node_modules/' + module.exports.appName + '.js-controller')) { // remove /node_modules/' + module.exports.appName + '.js-controller/lib configDir.splice(configDir.length - 2, 2); configDir = configDir.join('/'); return configDir + '/' + module.exports.appName + '-data/' + module.exports.appName + '.json'; } else { // Remove /lib configDir.splice(configDir.length - 1, 1); configDir = configDir.join('/'); if (fs.existsSync(__dirname + '/../conf/' + module.exports.appName + '.json')) { return configDir + '/conf/' + module.exports.appName + '.json'; } else { return configDir + '/data/' + module.exports.appName + '.json'; } } } /** * Puts all values from an `arguments` object into an array, starting at the given index * @param {IArguments} argsObj An `arguments` object as passed to a function * @param {number} [startIndex=0] The optional index to start taking the arguments from */ function sliceArgs(argsObj, startIndex) { if (startIndex == null) startIndex = 0; const ret = []; for (let i = startIndex; i < argsObj.length; i++) { ret.push(argsObj[i]); } return ret; } /** * Promisifies a function which returns an error as the first argument in its callback * @param {Function} fn The function to promisify * @param {any} [context=this] (optional) The context (value of `this` to bind the function to) * @param {string[]} [returnArgNames] (optional) If the callback contains multiple arguments, * you can combine them into one object by passing the names as an array. * Otherwise the Promise will resolve with an array * @returns {(...args: any[]) => Promise} */ function promisify(fn, context, returnArgNames) { return function () { const args = sliceArgs(arguments); context = context || this; return new Promise(function (resolve, reject) { fn.apply(context, args.concat([ function (error, result) { if (error) { return reject(error); } else { // decide on how we want to return the callback arguments switch (arguments.length) { case 1: // only an error was given return resolve(); // Promise case 2: // a single value (result) was returned return resolve(result); default: // multiple values should be returned /** @type {{} | any[]} */ let ret; const extraArgs = sliceArgs(arguments, 1); if (returnArgNames && returnArgNames.length === extraArgs.length) { // we can build an object ret = {}; for (let i = 0; i < returnArgNames.length; i++) { ret[returnArgNames[i]] = extraArgs[i]; } } else { // we return the raw array ret = extraArgs; } return resolve(ret); } } } ])); }); }; } /** * Promisifies a function which does not provide an error as the first argument in its callback * @param {Function} fn The function to promisify * @param {any} context (optional) The context (value of `this` to bind the function to) * @param {string[]} returnArgNames (optional) If the callback contains multiple arguments, * you can combine them into one object by passing the names as an array. * Otherwise the Promise will resolve with an array * @returns {(...args: any[]) => Promise} */ function promisifyNoError(fn, context, returnArgNames) { return function () { const args = sliceArgs(arguments); context = context || this; return new Promise(function (resolve, reject) { fn.apply(context, args.concat([ function (result) { // decide on how we want to return the callback arguments switch (arguments.length) { case 0: // no arguments were given return resolve(); // Promise case 1: // a single value (result) was returned return resolve(result); default: // multiple values should be returned /** @type {{} | any[]} */ let ret; const extraArgs = sliceArgs(arguments, 0); if (returnArgNames && returnArgNames.length === extraArgs.length) { // we can build an object ret = {}; for (let i = 0; i < returnArgNames.length; i++) { ret[returnArgNames[i]] = extraArgs[i]; } } else { // we return the raw array ret = extraArgs; } return resolve(ret); } } ])); }); }; } /** * Creates and executes an array of promises in sequence * @param {((...args: any[]) => Promise)[]} promiseFactories An array of promise-returning functions */ function promiseSequence(promiseFactories) { return promiseFactories.reduce((promise, factory) => { return promise.then(result => factory().then(Array.prototype.concat.bind(result))) }, Promise.resolve([])); } /** * Poor man's async/await using generator functions. Turns a generator function into a promise returning function. * yield equals await. * @template TReturn * @param {(...args: any[]) => IterableIterator} makeGenerator A generator function to sequentially execute. * @returns {(...args: any[]) => Promise} */ function poorMansAsync(makeGenerator) { return function () { let generator = makeGenerator.apply(this, arguments); function handle(result) { // result => { done: [Boolean], value: [Object] } if (result.done) return Promise.resolve(result.value); return Promise.resolve(result.value).then(function (res) { return handle(generator.next(res)); }, function (err) { if (typeof err === 'string') err = new Error(err); return handle(generator.throw(err)); }); } try { return handle(generator.next()); } catch (ex) { return Promise.reject(ex); } } } // // Example usage: // function *test(a, b, c) { // yield somethingAsyncThatReturnsAPromise(a); // // write progress report // yield somethingElseThatReturnsAPromise(b); // // white progress report // yield c; // } // var testAsync = gen2Async(test); // testAsync(1,2,3).then(() => /* we're done */ ); let packageLockDisabled = false; /** * Ensures that package-lock.json gets ignored. Fixes installation issues on npm5 * @param {(err?) => void} callback The callback to invoke after the command has finished */ function disablePackageLock(callback) { if (packageLockDisabled) return callback(); const npmrcPath = path.join(__dirname, '../../..', '.npmrc'); getSystemNpmVersion(function (err, version) { packageLockDisabled = true; if (version && semver.gte(version, '5.0.0')) { // we need to disable the package lock if (!fs.existsSync(npmrcPath)) { // create the file fs.writeFile(npmrcPath, 'package-lock=false\n', {encoding: 'utf8'}, callback); return; } } callback(); }); } module.exports = { appName: getAppName(), createUuid, decryptPhrase, disablePackageLock, encryptPhrase, findIPs, poorMansAsync, getAdapterDir, getConfigFileName, getDefaultDataDir, getFile, getHostInfo, getHostName, getInstalledInfo, getIoPack, getJson, getRepositoryFile, getSystemNpmVersion, promisify, promisifyNoError, promiseSequence, rmdirRecursiveSync, sendDiagInfo, upToDate, checkNonEditable, copyAttributes };