UNPKG

iobroker.js-controller

Version:

Updated by reinstall.js on 2018-06-11T15:19:56.688Z

2,919 lines (2,703 loc) • 127 kB
/* eslint-disable no-inner-declarations */ /** * * ioBroker Command Line Interface (CLI) * * 7'2014-2020 bluefox <dogafox@gmail.com> * 2014 hobbyquaker <hq@ccu.io> * */ /* jshint -W097 */ /* jshint strict:false */ /* jslint node: true */ 'use strict'; // TODO need info about progress of stopping const fs = require('fs-extra'); const pathLib = require('path'); const tools = require('./tools.js'); const cli = require('./cli/index.js'); const EXIT_CODES = require('./exitCodes'); const {enumHosts} = require('./cli/cliTools'); const deepClone = require('deep-clone'); const { isDeepStrictEqual } = require('util'); const debug = require('debug')('iobroker:cli'); // @ts-ignore require('events').EventEmitter.prototype._maxListeners = 100; process.setMaxListeners(0); /** @type {import('yargs')} */ let yargs; function initYargs() { yargs = require('yargs') .scriptName(tools.appName) .locale('en')// otherwise it could be mixed, because our implementations are in english .version(false) // disable yargs own version handling, because we have our own depending on passed instances .completion('_createCompletion', false) // can be created via iob _createCompletion >> ~/.bashrc or ~/.bash_profile for OSX .command('setup', 'Setup ioBroker', { redis: { describe: 'Setup as redis', type: 'boolean' }, objects: { describe: 'Objects <host>', default: '127.0.0.1', type: 'number' }, states: { describe: 'States <host>', default: '127.0.0.1', type: 'number' }, 'port <port>': { describe: 'Port of redis', default: 6379, type: 'number' }, custom: { describe: 'Custom setup', type: 'boolean' }, first: { describe: 'Initial setup', type: 'boolean' } }) .command('start', 'Starts the js-controller', yargs => { yargs .command('all', 'Starts js-controller and all adapters') .command('<adapter>[.<instance>]', 'Starts a specified adapter instance'); }) .command('stop', 'stops the js-controller', yargs => { yargs .command('<adapter>[.<instance>]', 'Stops a specified adapter instance'); }) .command('restart', 'Restarts js-controller', yargs => { yargs .command('<adapter>[.<instance>]', 'Restarts a specified adapter instance', {}); }) .command('debug <adapter>[.<instance>]', 'Starts a Node.js debugging session for the adapter instance', { ip: { describe: 'IP-address <ip>', type: 'string' }, port: { describe: 'Port <port>', type: 'number' }, wait: { describe: 'Wait', type: 'boolean' } }) .command('info', 'Shows the host info', {}) .command('logs [<adapter>]', 'Monitor log', { 'lines=1000': { // TODO: it's the only place we use = we should avoid this describe: 'Number of lines', type: 'string' }, watch: { describe: 'Watch', type: 'boolean' } }) .command('add <adapter> [desiredNumber]', 'Add instance of adapter', { enabled: { describe: 'Enable adapter', type: 'boolean' }, host: { describe: 'Host <host>', type: 'string' }, port: { describe: 'Port <port>', type: 'number' } }) .command('install <adapter>', 'Installs a specified adapter', {}) .command('rebuild <adapter>|self', 'Rebuilds a specified adapter', { install: { describe: 'Install', type: 'boolean' } }) .command('url <url> [<name>]', 'Install adapter from specified url, e.g. GitHub', {}) .command('del <adapter>', 'Remove adapter from system', {}) .command('del <adapter>.<instance>', 'Remove adapter instance', {}) .command('update [<repositoryUrl>]', 'Update repository and list adapters', { updateable: { describe: 'Only show updateable adapters', alias: 'u', type: 'boolean' }, all: { describe: 'Show all available adapters', alias: 'a', type: 'boolean' }, force: { describe: 'Bypass hash check', alias: 'f', type: 'boolean' } }) .command('upgrade', 'Upgrade management', yargs => { yargs .option('yes', { describe: 'Bypass questionnaire', alias: 'y', type: 'boolean' }) .command('[<repositoryUrl>]', 'Upgrade all adapters, optionally you can specify the repository url', {}) .command('all [<repositoryUrl>]', 'Upgrade all adapters, optionally you can specify the repository url', {}) .command('self [<repositoryUrl>]', 'Upgrade js-controller, optionally you can specify the repository url', {}) .command('<adapter> [<repositoryUrl>]', 'Upgrade specified adapter, optionally you can specify the repository url', {}); }) .command('upload', 'Upload management', yargs => { yargs .command(`<pathToLocalFile> <pathIn${tools.appName}>`, 'Upload given files to provided path to make them available for instances', {}) .command('all', 'Upload all adapter files to make them available for instances', {}) .command('<adapter>', 'Upload specified adapter files to make them available for instances', {}); }) .command('object', 'Object management', yargs => { yargs .command('get <id>', 'Get object specified by id', {}) .command('set <id> <json-value>', 'Set object with the given id by providing a new json object', {}) .command('set <id> propertyname=<value or json-value>', 'Update part of the object by providing a new value or partial object', {}) .command('extend <id> <json-value>', 'Extend object with the given id by providing a new json object', {}) .command('del <id|pattern>', 'Delete object with given id or all objects matching the pattern', { y: { describe: 'Bypass questionnaire', alias: 'y', type: 'boolean' } }) .command('chmod <object-mode> [state-mode] <id>', 'Change object rights', {}) .command('chown <user> <group> <id>', 'Change object ownership', {}) .command('list <pattern>', 'List object matching given pattern', {}); }) .command('state', 'State management', yargs => { yargs .command('get <id>', 'Get state, specified by id', {}) .command('getplain <id>', 'Get plain state, specified by id', { pretty: { describe: 'Prettify output', type: 'boolean' } }) .command('getvalue <id>', 'Get state value, specified by id', {}) .command('set <id> <value> [<ack>]', 'Set state, specified by id', {}) .command('del <id>', 'Delete state, specified by id', {}); }) .command('message <adapter>[.instance] <command> [<message>]', 'Send message to adapter instance/s', {}) .command('list <type> [<filter>]', 'List all entries, like objects', {}) .command('chmod <mode> <file>', 'Change file rights', {}) .command('chown <user> <group> <file>', 'Change file ownership', {}) .command('touch <file>', 'Touch file', {}) .command('rm <file>', 'Remove file', {}) .command('file', 'File management', yargs => { yargs .command(`read <${tools.appName}-path-to-read> [<filesystem-path-to-write>]`, `Read file from ${tools.appName} path and optionally write to destination`, {}) .command(`write <filesystem-path-to-read> <${tools.appName}-path-to-write>`, `Read file from path and write it to ${tools.appName} path`,{}) .command(`rm <${tools.appName}-path-to-delete>`, 'Remove file', {}) .command('sync', 'Sync files', {}); }) .command('user', 'User commands', yargs => { yargs .command('add <user>', 'Add new user',yargs => { yargs.option('ingroup', { describe: 'User group', type: 'string' }) .option('password', { describe: 'User password', type: 'string' }); }) .command('del <user>', 'Delete user', {}) .command('passwd <user>', 'Change user password', yargs => { yargs .option('password', { describe: 'User password', type: 'string' }); }) .command('enable <user>', 'Enable user', {}) .command('disable <user>', 'Disable user', {}) .command('get <user>', 'Get user', {}) .command('check <user>', 'Check user password', yargs => { yargs .option('password', { describe: 'User password', type: 'string' }); }); }) .command('group', 'group management', yargs => { yargs .command('add <group>', 'Add group', {}) .command('del <group>', 'Remove group', {}) .command('list <group>', 'List group', {}) .command('enable <group>', 'Enable group', {}) .command('disable <group>', 'Disable group', {}) .command('get <group>', 'Get group', {}) .command('adduser <group> <user>', 'Add user to group', {}) .command('deluser <group> <user>', 'Remove user from group', {}); }) .command('host <hostname>', 'Set host to given hostname', yargs => { yargs .command('this', 'Initialize current host', {}) .command('set <hostname>', 'Set host with specified hostname', {}) .command('remove <hostname>', 'Remove host with specified hostname', {}); }) .command('set <adapter>.<instance>', 'Change settings of adapter config', { customOption: { describe: 'Set the name of the parameter you want to change as option followed by its value, e. g. --port 80' } }) .command('license <license.file or license.text>', 'Update license by given file', {}) .command('cert', 'Certificate management', yargs => { yargs .command('create', 'Create certificate', {}) .command('view [<certificate name>]', 'Show certificate', {}); }) .command('clean <yes>', 'Clears all objects and states', {}) .command('backup', 'Create backup', {}) .command('restore <backup name or path>', 'Restore a specified backup', {}) .command('validate <backup name or path>', 'Validate a specified backup', {}) .command('status [all|<adapter>.<instance>]', 'Status of ioBroker or adapter instance', {}) .command('repo [<name>]', 'Show repo information', yargs => { yargs .command('set <name>', 'Set active repository') .command('del <name>', 'Remove repository') .command('add <name> <url>', 'Add repository') .command('addset <name> <url>', 'Add repository and set it as active one') .command('show', 'List repositories'); }) .command('uuid', 'Show uuid of the installation', {}) .command('unsetup', 'Reset license, installation secret and language', {}) .command('fix', 'Execute the installation fixer script, this updates your ioBroker installation', {}) .command('multihost', 'Multihost management', yargs => { yargs .command('enable', 'Enable multihost discovery', { secure: { describe: 'Use secure connection', type: 'boolean' }, persist: { describe: 'Enable persistent discovery', type: 'boolean' } }) .command('disable', 'Disable multihost discovery') .command('browse', 'Browse for multihost server') .command('connect', 'Connect to multihost server'); }) .command('compact', 'compact group management', yargs => { yargs .command('enable', 'Enable compact mode in general') .command('on', 'Enable compact mode in general') .command('disable', 'Disable compact mode in general') .command('off', 'Disable compact mode in general') .command('<adapter>.<instance> status', 'Show if compact mode is enabled for a specific instance') .command('<adapter>.<instance> group <group-id>', 'Define compact group of a specific adapter') .command('<adapter>.<instance> <disable|off> [group-id]', 'Enable or disable compact mode for specified adapter instance and set compact group optionally') .command('<adapter>.<instance> <enable|on> [group-id]', 'Enable or disable compact mode for specified adapter instance and set compact group optionally'); }) .command('plugin', 'Plugin management', yargs => { yargs .command('enable <pluginname>', 'Enables a plugin for the specified host or instance. If no host is specified, the current one is used', { host: { describe: 'Hostname', type: 'string' }, instance: { describe: 'Instance, e.g. hm-rpc.0', type: 'string' } }) .command('disable <pluginname>', 'Disables a plugin for the specified host or instance. If no host is specified, the current one is used', { host: { describe: 'Hostname', type: 'string' }, instance: { describe: 'Instance, e.g. hm-rpc.0', type: 'string' } }) .command('status <pluginname>', 'Checks if a plugin is enabled for the specified host or instance. If no host is specified, the current one is used', { host: { describe: 'Hostname', type: 'string' }, instance: { describe: 'Instance, e.g. hm-rpc.0', type: 'string' } }); }) .command('version [<adapter>]', 'Show version of js-controller or specified adapter') .option('version', { describe: 'Show version', type: 'boolean', alias: 'v' }) .wrap(null) ; return yargs; } /** * Show yargs help, if processCommand is used as import, yargs won't be initialized * @param {object} _yargs - yargs instance */ function showHelp(_yargs) { if (_yargs) { _yargs.showHelp(); } else if (yargs) { yargs.showHelp(); } } let Objects; // constructor let objects; // instance let States; // constructor let states; // instance /** * Process the given CLI command * * @param {string|number} command - command to execute * @param {any[]} args - arguments passed to yargs * @param {object} params - object with parsed params by yargs, e. g. --force is params.force * @param {(exitCode?: number) => void} callback */ function processCommand(command, args, params, callback) { if (typeof args === 'function') { callback = args; args = null; } if (typeof params === 'function') { callback = params; params = null; } if (!params) { params = {}; } if (!args) { args = []; } if (!callback) { callback = processExit; } /** @type {import('./cli/cliCommand').CLICommandContext} */ const commandContext = {dbConnect, callback, showHelp}; /** @type {import('./cli/cliCommand').CLICommandOptions} */ const commandOptions = Object.assign({}, params, commandContext); debug(`commandOptions: ${JSON.stringify(commandOptions)}`); debug(`args: ${args}`); switch (command) { case 'start': case 'stop': { const procCommand = new cli.command.process(commandOptions); procCommand[command](args); break; } case 'debug': { const debugCommand = new cli.command.debug(commandOptions); debugCommand.execute(args); break; } case 'status': case 'isrun': { const procCommand = new cli.command.process(commandOptions); procCommand.status(args); break; } case 'r': case 'restart': { const procCommand = new cli.command.process(commandOptions); procCommand.restart(args); break; } case '_restart': restartController(callback); break; case 'update': { Objects = require('./objects'); const repoUrl = args[0]; // Repo url or name dbConnect(params, (_objects, _states) => { const Repo = require('./setup/setupRepo.js'); const repo = new Repo({ objects: _objects, states: _states }); repo.showRepo(repoUrl, params, () => { setTimeout(callback, 2000); }); }); break; } case 'setup': { const Setup = require('./setup/setupSetup.js'); const setup = new Setup({ dbConnect, processExit: callback, cleanDatabase, restartController, resetDbConnect, params }); if (args[0] === 'custom' || params.custom) { setup.setupCustom(callback); } else { let isFirst; let isRedis; // we support "first" and "redis" without "--" flag for (const arg of args) { if (arg === 'first') { isFirst = true; } else if (arg === 'redis') { isRedis = true; } } // and as --flag isRedis = params.redis || isRedis; isFirst = params.first || isFirst; setup.setup(async (isFirst, _isRedis) => { if (isFirst) { // Creates all instances that are needed on a fresh installation const createInitialInstances = async () => { const Install = require('./setup/setupInstall.js'); const install = new Install({ objects, states, installNpm, getRepository, processExit: callback, params }); // In order to loop the instance creation, we need a promisified version of the method const createInstanceAsync = tools.promisifyNoError(install.createInstance, install); // Define the necessary instances const initialInstances = ['admin', 'discovery', 'backitup']; // And try to install each of them for (const instance of initialInstances) { try { const path = require.resolve(tools.appName + '.' + instance); if (path) { await createInstanceAsync(instance, {enabled: true, ignoreIfExists: true}); } } catch { // not found, just continue } } }; createInitialInstances() .then(() => new Promise(resolve => { // Creates a fresh certificate const Cert = require('./cli/cliCert'); // Create a new instance of the cert command, // but use the resolve method as a callback const cert = new Cert(Object.assign({}, commandOptions, {callback: resolve})); cert.create(); })) .then(() => callback && callback()); } else { // else we update existing stuff (this is executed on installation) // Rename repositories const Repo = require('./setup/setupRepo.js'); const repo = new Repo({ objects: objects, states: states }); try { await repo.rename('default', 'stable', 'http://download.iobroker.net/sources-dist.json'); await repo.rename('latest', 'beta', 'http://download.iobroker.net/sources-dist-latest.json'); } catch (e) { console.warn(e.message); } try { const configFile = tools.getConfigFileName(); const configOrig = fs.readJSONSync(configFile); const config = deepClone(configOrig); config.objects.options = config.objects.options || { 'auth_pass' : null, 'retry_max_delay' : 5000 }; if (config.objects.options.retry_max_delay === 15000 || !config.objects.options.retry_max_delay) { config.objects.options.retry_max_delay = 5000; } config.states.options = config.states.options || { 'auth_pass' : null, 'retry_max_delay' : 5000 }; if (config.states.options.retry_max_delay === 15000 || !config.states.options.retry_max_delay) { config.states.options.retry_max_delay = 5000; } if (!isDeepStrictEqual(config, configOrig)) { fs.writeFileSync(configFile, JSON.stringify(config, null, 2)); console.log('ioBroker configuration updated'); } } catch(err) { console.log('Could not update ioBroker configuration: ' + err.message); } return void callback(); } }, isFirst, isRedis); } break; } case 'url': { Objects = require('./objects'); let url = args[0]; const name = args[1]; if (!url) { console.log('Please provide a URL to install from and optionally a name of the adapter to install'); callback(EXIT_CODES.INVALID_ARGUMENTS); } if (url[0] === '"' && url[url.length - 1] === '"') { url = url.substring(1, url.length - 1); } url = url.trim(); dbConnect(params, () => { const Install = require('./setup/setupInstall.js'); const install = new Install({ objects, states, installNpm, getRepository, processExit: callback, params }); install.installAdapterFromUrl(url, name, callback); }); break; } case 'info': { Objects = require('./objects'); dbConnect(params, objects => { tools.getHostInfo(objects, (err, data) => { if (err) { console.error('Cannot read host info: '+ err); if (!data) { return callback(EXIT_CODES.CANNOT_GET_HOST_INFO); } } const formatters = require('./formatters'); const formatInfo = { 'Uptime': formatters.formatSeconds, 'System uptime': formatters.formatSeconds, 'RAM': formatters.formatRam, 'Speed': formatters.formatSpeed, 'Disk size': formatters.formatBytes, 'Disk free': formatters.formatBytes }; for (const attr of Object.keys(data)) { console.log(`${attr}${attr.length < 16 ? new Array(16 - attr.length).join(' ') : ''}: ${formatInfo[attr] ? formatInfo[attr](data[attr]) : data[attr] || ''}`); } return void callback(); }); }); break; } case 'a': case 'add': case 'install': case 'i': { Objects = require('./objects'); let name = args[0]; let instance = args[1]; let repoUrl = args[2]; if (instance === 0) { instance = '0'; } if (repoUrl === 0) { repoUrl = '0'; } if (parseInt(instance, 10).toString() !== (instance || '').toString()) { repoUrl = instance; instance = null; } if (parseInt(repoUrl, 10).toString() === (repoUrl || '').toString()) { const temp = instance; instance = repoUrl; repoUrl = temp; } if (parseInt(instance, 10).toString() === (instance || '').toString()) { instance = parseInt(instance, 10); params.instance = instance; } // If user accidentally wrote tools.appName.adapter => remove adapter name = cli.tools.normalizeAdapterName(name); const parsedName = cli.tools.splitAdapterOrInstanceIdentifierWithVersion(name); if (!parsedName) { console.log('Invalid adapter name for install'); showHelp(); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } // split the adapter into its parts if necessary if (parsedName.instance !== null) { params.instance = parsedName.instance; } name = parsedName.name; const installName = parsedName.nameWithVersion; const adapterDir = tools.getAdapterDir(name); dbConnect(params, async () => { const Install = require('./setup/setupInstall.js'); const install = new Install({ objects, states, installNpm, getRepository, processExit: callback, params }); if (params.host && params.host !== tools.getHostName()) { // if host argument provided we should check, that host actually exists in mh environment let obj; try { obj = await objects.getObjectAsync(`system.host.${params.host}`); } catch (e) { console.warn(`Could not check existence of host "${params.host}": ${e.message}`); } if (!obj) { console.error(`Cannot add instance to non-existing host "${params.host}"`); return void callback(EXIT_CODES.NON_EXISTING_HOST); } } if (!fs.existsSync(adapterDir)) { install.downloadPacket(repoUrl, installName, null, enableAdapterCallback => { install.installAdapter(installName, repoUrl, () => { enableAdapterCallback(() => { if (command !== 'install' && command !== 'i') { install.createInstance(name, params, callback); } else { return void callback(); } }); }); }); } else { if (command !== 'install' && command !== 'i') { install.createInstance(name, params, callback); } else { console.log(`adapter "${name}" already installed. Use "upgrade" to upgrade to a newer version.`); return void callback(EXIT_CODES.ADAPTER_ALREADY_INSTALLED); } } }); break; } case 'rebuild': { let name = args[0]; // If user accidentally wrote tools.appName.adapter => remove adapter name = cli.tools.normalizeAdapterName(name); if (name.indexOf('@') !== -1) { name = name.split('@')[0]; } if (!name) { console.log('Please provide the name of the adapter to rebuild'); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } const rebuildCommand = params.install ? 'install' : 'rebuild'; installNpm(name, rebuildCommand, (err, _adapter) => { if (err) { processExit(err); } else { console.log(); console.log('Rebuild ' + name + ' done'); return void callback(); } }); break; } case 'upload': case 'u': { Objects = require('./objects'); const name = args[0]; const subTree = args[1]; if (name) { dbConnect(params, () => { const Upload = require('./setup/setupUpload.js'); const upload = new Upload({ states: states, objects: objects }); if (name === 'all') { objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (_err, objs) => { const adapters = []; for (let i = 0; i < objs.rows.length; i++) { if (objs.rows[i].value.type !== 'adapter') { continue; } adapters.push(objs.rows[i].value.common.name); } upload.uploadAdapterFull(adapters, callback); }); } else { // if upload of file if (name.indexOf('.') !== -1) { if (!subTree) { console.log('Please specify target name, like:\n ' + tools.appName + ' upload /file/picture.png /vis.0/main/img/picture.png'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } upload.uploadFile(name, subTree, (err, newName) => { !err && console.log('File "' + name + '" is successfully saved under ' + newName); return void callback(err ? EXIT_CODES.CANNOT_UPLOAD_DATA : undefined); }); } else { if (subTree) { upload.uploadAdapter(name, false, true, subTree, callback); } else { upload.uploadAdapterFull([name], callback); } } } }); } else { console.log('No adapter name found!'); showHelp(); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } break; } case 'delete': case 'del': { let adapter = args[0]; let instance = args[1]; // The adapter argument is required if (!adapter) { showHelp(); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } // If the user accidentally wrote <tools.appName>.adapter, // remove <tools.appName> from the adapter name adapter = cli.tools.normalizeAdapterName(adapter); // Avoid deleting stuff we don't want to delete // e.g. `system.adapter.*` if (!instance) { // Ensure that adapter contains a valid adapter (without instance nr) // or instance (with instance nr) identifier if (!cli.tools.validateAdapterOrInstanceIdentifier(adapter)) { showHelp(); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } // split the adapter into adapter + instance if necessary if (adapter.indexOf('.') > -1) { ([adapter, instance] = adapter.split('.', 2)); } } else { // ensure that adapter contains a valid adapter identifier // and the instance is a number if ( !cli.tools.validateAdapterIdentifier(adapter) || !/^\d+$/.test(instance) ) { showHelp(); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } } if (instance || instance === 0) { dbConnect(params, () => { const Install = require('./setup/setupInstall.js'); const install = new Install({ objects, states, installNpm, getRepository, processExit: callback, params }); console.log('Delete adapter "' + adapter + '.' + instance + '"'); install.deleteInstance(adapter, instance, callback); }); } else { dbConnect(params, () => { const Install = require('./setup/setupInstall.js'); const install = new Install({ objects, states, installNpm, getRepository, processExit: callback, params }); console.log('Delete adapter "' + adapter + '"'); install.deleteAdapter(adapter, (_a, resultCode) => void callback(resultCode)); }); } break; } case 'unsetup': { const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout }); if (params.yes || params.y || params.Y) { unsetup(params, callback); } else { rl.question('UUID will be deleted. Are you sure? [y/N]: ', answer => { rl.close(); answer = answer.toLowerCase(); if (answer === 'y' || answer === 'yes' || answer === 'ja' || answer === 'j') { unsetup(params, callback); } else { console.log('Nothing deleted'); return void callback(); } }); } break; } case 'o': case 'object': { const objectsCommand = new cli.command.object(commandOptions); objectsCommand.execute(args); break; } case 's': case 'state': { const statesCommand = new cli.command.state(commandOptions); statesCommand.execute(args); break; } case 'msg': case 'message': { const messageCommand = new cli.command.message(commandOptions); messageCommand.execute(args); break; } case 'logs': { const logsCommand = new cli.command.logs(commandOptions); logsCommand.execute(args, params); break; } case 'upgrade': { Objects = require('./objects'); let adapter = cli.tools.normalizeAdapterName(args[0]); let repoUrl = args[1]; if (adapter && !repoUrl && adapter.indexOf('/') !== -1) { repoUrl = adapter; adapter = null; } if (adapter === 'all') { adapter = null; } dbConnect(params, () => { const Upgrade = require('./setup/setupUpgrade.js'); const upgrade = new Upgrade({ objects, states, installNpm, getRepository, params, processExit: callback, restartController }); if (adapter) { if (adapter === 'self') { states.getState(`system.host.${tools.getHostName()}.alive`, (err, hostAlive) => upgrade.upgradeController(repoUrl, params.force || params.f, hostAlive && hostAlive.val, callback)); } else { upgrade.upgradeAdapter(repoUrl, adapter, params.force || params.f, params.y || params.yes, false, callback); } } else { // upgrade all getRepository(repoUrl, (err, links) => { const result = []; for (const name of Object.keys(links)) { result.push(name); } if (err) { console.log(err); } if (links) { result.sort(); upgrade.upgradeAdapterHelper(links, result, false, params.y || params.yes, callback); } else { // No information return void callback(EXIT_CODES.INVALID_REPO); } }); } }); break; } case 'clean': { const yes = args[0]; if (yes !== 'yes') { console.log(`Command "clean" clears all Objects and States. To execute it write "${tools.appName} clean yes"`); } else { dbConnect(params, (_obj, _stat, isNotRun) => { if (!isNotRun) { console.error(`Stop ${tools.appName} first!`); return void callback(EXIT_CODES.CONTROLLER_RUNNING); } cleanDatabase(true, count => { console.log('Deleted ' + count + ' states'); restartController(() => { console.log('Restarting ' + tools.appName + '...'); return void callback(); }); }); }); } break; } case 'restore': { const Backup = require('./setup/setupBackup.js'); dbConnect(params, (_obj, _stat, isNotRun) => { if (!isNotRun) { console.error('Stop ' + tools.appName + ' first!'); return void callback(EXIT_CODES.CONTROLLER_RUNNING); } const backup = new Backup({ states: states, objects: objects, cleanDatabase: cleanDatabase, restartController: restartController, processExit: callback }); backup.restoreBackup(args[0], () => { console.log('System successfully restored!'); return void callback(EXIT_CODES.NO_ERROR); }); }); break; } case 'backup': { const name = args[0]; const Backup = require('./setup/setupBackup.js'); dbConnect(params, () => { const backup = new Backup({ states: states, objects: objects, cleanDatabase: cleanDatabase, restartController: restartController, processExit: callback }); backup.createBackup(name, filePath => { console.log('Backup created: ' + filePath); return void callback(EXIT_CODES.NO_ERROR); }); }); break; } case 'validate': { const name = args[0]; const Backup = require('./setup/setupBackup.js'); dbConnect(params, () => { const backup = new Backup({ states: states, objects: objects, cleanDatabase: cleanDatabase, restartController: restartController, processExit: callback }); backup.validateBackup(name).then(() => { console.log('Backup OK'); processExit(0); }).catch(e => { console.log(`Backup check failed: ${e.message}`); processExit(1); }); }); break; } case 'l': case 'list': { dbConnect(params, (_objects, _states, _isOffline, _objectsType, config) => { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback, config: config }); list.list(args[0], args[1], params); }); break; } case 'touch': { let pattern = args[0]; if (!pattern) { console.log('No file path found. Example: "touch /vis.0/main/*"'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } dbConnect(params, () => { // extract id pattern = pattern.replace(/\\/g, '/'); if (pattern[0] === '/') { pattern = pattern.substring(1); } if (pattern === '*') { objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, arr) => { if (!err && arr && arr.rows) { const files = []; let count = 0; for (let i = 0; i < arr.rows.length; i++) { if (arr.rows[i].value.type !== 'adapter') { continue; } count++; objects.touch(arr.rows[i].value.common.name, '*', {user: 'system.user.admin'}, (err, processed, _id) => { if (!err && processed) { files.push({id: _id, processed: processed}); } if (!--count) { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback }); files.sort((a, b) => a.id.localeCompare(b.id)); for (let k = 0; k < files.length; k++) { for (let t = 0; t < files[k].processed.length; t++) { list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]); } } setTimeout(callback, 1000); } }); } if (!count) { console.log('Nothing found'); return void callback(); } } }); } else { const parts = pattern.split('/'); const id = parts.shift(); const path = parts.join('/'); objects.touch(id, path, {user: 'system.user.admin'}, (err, processed) => { if (err) { console.error(err); } else { if (processed) { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback }); for (let i = 0; i < processed.length; i++) { list.showFile(id, processed[i].path, processed[i]); } } } setTimeout(callback, 1000); }); } }); break; } case 'rm': { let pattern = args[0]; if (!pattern) { console.log('No file path found. Example: "touch /vis.0/main/*"'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } dbConnect(params, () => { // extract id pattern = pattern.replace(/\\/g, '/'); if (pattern[0] === '/') { pattern = pattern.substring(1); } if (pattern === '*') { objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, arr) => { if (!err && arr && arr.rows) { const files = []; let count = 0; for (let i = 0; i < arr.rows.length; i++) { if (arr.rows[i].value.type !== 'adapter') { continue; } count++; objects.rm(arr.rows[i].value.common.name, '*', {user: 'system.user.admin'}, (err, processed, _id) => { if (!err && processed) { files.push({id: _id, processed: processed}); } if (!--count) { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback }); files.sort((a, b) => a.id.localeCompare(b.id)); list.showFileHeader(); for (let k = 0; k < files.length; k++) { for (let t = 0; t < files[k].processed.length; t++) { list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]); } } setTimeout(callback, 1000); } }); } if (!count) { console.log('Nothing found'); return void callback(); } } }); } else { const parts = pattern.split('/'); const id = parts.shift(); const path = parts.join('/'); objects.rm(id, path, {user: 'system.user.admin'}, (err, processed) => { if (err) { console.error(err); } else { if (processed) { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback }); list.showFileHeader(); for (let i = 0; i < processed.length; i++) { list.showFile(id, processed[i].path, processed[i]); } } } setTimeout(callback, 1000); }); } }); break; } case 'chmod': { let mode = args[0]; let pattern = args[1]; if (!mode) { cli.error.requiredArgumentMissing('mode', 'chmod 777 /vis.0/main/*'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } else { //yargs has converted it to number mode = parseInt(mode.toString(), 16); } if (!pattern) { cli.error.requiredArgumentMissing('file path', 'chmod 777 /vis.0/main/*'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } dbConnect(params, () => { // extract id pattern = pattern.replace(/\\/g, '/'); if (pattern[0] === '/') { pattern = pattern.substring(1); } if (pattern === '*') { objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, arr) => { if (!err && arr && arr.rows) { const files = []; let count = 0; for (let i = 0; i < arr.rows.length; i++) { if (arr.rows[i].value.type !== 'adapter') { continue; } count++; objects.chmodFile(arr.rows[i].value.common.name, '*', {user: 'system.user.admin', mode}, (err, processed, _id) => { if (!err && processed) { files.push({id: _id, processed: processed}); } if (!--count) { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback }); files.sort((a, b) => a.id.localeCompare(b.id)); list.showFileHeader(); for (let k = 0; k < files.length; k++) { for (let t = 0; t < files[k].processed.length; t++) { list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]); } } setTimeout(callback, 1000); } }); } if (!count) { console.log('Nothing found'); return void callback(); } } }); } else { const parts = pattern.split('/'); const id = parts.shift(); const path = parts.join('/'); objects.chmodFile(id, path, {user: 'system.user.admin', mode: mode}, (err, processed) => { if (err) { console.error(err); } else { if (processed) { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback }); list.showFileHeader(); for (let i = 0; i < processed.length; i++) { list.showFile(id, processed[i].path, processed[i]); } } } setTimeout(callback, 1000); }); } }); break; } case 'chown': { let user = args[0]; let group = args[1]; let pattern = args[2]; if (!pattern) { pattern = group; group = undefined; } if (!user) { cli.error.requiredArgumentMissing('user', 'chown user /vis.0/main/*'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } else if (user.substring(12) !== 'system.user.') { user = 'system.user.' + user; } if (group && group.substring(13) !== 'system.group.') { group = 'system.group.' + group; } if (!pattern) { cli.error.requiredArgumentMissing('file path', 'chown user /vis.0/main/*'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } dbConnect(params, () => { // extract id pattern = pattern.replace(/\\/g, '/'); if (pattern[0] === '/') { pattern = pattern.substring(1); } if (pattern === '*') { objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, arr) => { if (!err && arr && arr.rows) { const files = []; let count = 0; for (let i = 0; i < arr.rows.length; i++) { if (arr.rows[i].value.type !== 'adapter') { continue; } count++; objects.chownFile(arr.rows[i].value.common.name, '*', {user: 'system.user.admin', owner: user, ownerGroup: group}, (err, processed, _id) => { if (!err && processed) { files.push({id: _id, processed: processed}); } if (!--count) { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback }); files.sort((a, b) => a.id.localeCompare(b.id)); list.showFileHeader(); for (let k = 0; k < files.length; k++) { for (let t = 0; t < files[k].processed.length; t++) { list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]); } } setTimeout(callback, 1000); } }); } if (!count) { console.log('Nothing found'); return void callback(); } } }); } else { const parts = pattern.split('/'); const id = parts.shift(); const path = parts.join('/'); objects.chownFile(id, path, {user: 'system.user.admin', owner: user, ownerGroup: group}, (err, processed) => { if (err) { console.error(err); } else { // call here list if (processed) { const List = require('./setup/setupList.js'); const list = new List({ states: states, objects: objects, processExit: callback }); list.showFileHeader(); for (let i = 0; i < processed.length; i++) { list.showFile(id, processed[i].path, processed[i]); } } } setTimeout(callback, 1000); }); } }); break; } case 'user': { const command = args[0] || ''; let user = args[1] || ''; if (user && user.match(/^system\.user\./)) { user = user.substring('system.user.'.length); } dbConnect(params, () => { const Users = require('./setup/setupUsers.js'); const users = new Users({ objects: objects, processExit: callback }); const password = params.password; const group = params.ingroup || 'system.group.administrator'; if (command === 'add') { users.addUserPrompt(user, group, password, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" created (Group: ' + group.replace('system.group.', '') + ')'); return void callback(); } }); } else if (command === 'del' || command === 'delete') { users.delUser(user, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" deleted'); return void callback(); } }); } else if (command === 'check') { users.checkUserPassword(user, password, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('Password for user "' + user + '" matches.'); return void callback(); } }); } else if (command === 'set' || command === 'passwd') { users.setUserPassword(user, password, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('Password for "' + user + '" was successfully set.'); return void callback(); } }); } else if (command === 'enable' || command === 'e') { users.enableUser(user, true, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" was successfully enabled.'); return void callback(); } }); } else if (command === 'disable' || command === 'd') { users.enableUser(user, false, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" was successfully disabled.'); return void callback(); } }); } else if (command === 'get') { users.getUser(user, (err, isEnabled) => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" is ' + (isEnabled ? 'enabled' : 'disabled')); return void callback(); } }); } else { console.warn(`Unknown command "${command}". Available commands are: add, del, passwd, enable, disable, check, get`); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } }); break; } case 'g': case 'group': { const command = args[0] || ''; let group = args[1] || ''; let user = args[2] || ''; if (group && group.match(/^system\.group\./)) { group = group.substring('system.group.'.length); } if (user && user.match(/^system\.user\./)) { user = user.substring('system.user.'.length); } if (!command) { console.warn('Unknown command "' + command + '". Available commands are: add, del, passwd, enable, disable, list, get'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } if (!group) { console.warn('Please define group name: group ' + command + ' groupName'); return callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } dbConnect(params, () => { const Users = require('./setup/setupUsers.js'); const users = new Users({ objects: objects, processExit: callback }); if (command === 'useradd' || command === 'adduser') { if (!user) { console.warn('Please define user name: group useradd groupName userName'); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } users.addUserToGroup(user, group, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" created'); return void callback(); } }); } else if (command === 'userdel' || command === 'deluser') { if (!user) { console.warn('Please define user name: group userdel groupName userName'); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } users.removeUserFromGroup(user, group, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" created'); return void callback(); } }); } else if (command === 'add') { users.addGroup(group, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + group + '" created'); return void callback(); } }); } else if (command === 'del' || command === 'delete') { users.delGroup(group, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + group + '" deleted'); return void callback(); } }); } else if (command === 'list' || command === 'l') { users.getGroup(group, (err, isEnabled, list) => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('Group "' + group + '" is ' + (isEnabled ? 'enabled' : 'disabled') + ' and has following members:'); if (list) { for (let i = 0; i < list.length; i++) { console.log(list[i].substring('system.user.'.length)); } } return void callback(); } }); } else if (command === 'enable' || command === 'e') { users.enableGroup(group, true, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('Group "' + group + '" was successfully enabled.'); return void callback(); } }); } else if (command === 'disable' || command === 'd') { users.enableGroup(group, false, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('Group "' + group + '" was successfully disabled.'); return void callback(); } }); } else if (command === 'get') { users.getGroup(group, (err, isEnabled, _list) => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('Group "' + group + '" is ' + (isEnabled ? 'enabled' : 'disabled')); return void callback(); } }); } else { console.warn('Unknown command "' + command + '". Available commands are: add, del, passwd, enable, disable, list, get'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } }); break; } case 'adduser': { const user = args[0]; const group = params.ingroup || 'system.group.administrator'; const password = params.password; dbConnect(params, () => { const Users = require('./setup/setupUsers.js'); const users = new Users({ objects: objects, processExit: callback }); users.addUserPrompt(user, group, password, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" created (Group: ' + group.replace('system.group.', '') + ')'); return void callback(); } }); }); break; } case 'passwd': { const user = args[0]; const password = params.password; dbConnect(params, () => { const Users = require('./setup/setupUsers.js'); const users = new Users({ objects: objects, processExit: callback }); users.setUserPassword(user, password, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('Password for "' + user + '" was successfully set.'); return void callback(); } }); }); break; } case 'ud': case 'udel': case 'userdel': case 'deluser': { const user = args[0]; dbConnect(params, () => { const Users = require('./setup/setupUsers.js'); const users = new Users({ objects: objects, processExit: callback }); users.delUser(user, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { console.log('User "' + user + '" deleted'); return void callback(); } }); }); break; } // Create package.json in /opt/' + tools.appName + ' case 'package': { const json = { name: tools.appName, engines: { node: '>=6' }, optionalDependencies: { }, dependencies: {}, author: 'bluefox <dogafox@gmail.com>' }; json.dependencies[tools.appName + '.js-controller'] = '*'; json.dependencies[tools.appName + '.admin'] = '*'; tools.getRepositoryFile(null, null, (_err, sources, _sourcesHash) => { if (sources) { for (const s in sources) { if (Object.prototype.hasOwnProperty.call(sources, s)) { if (sources[s].url) { if (!json.dependencies[tools.appName + '.' + s]) { json.optionalDependencies[tools.appName + '.' + s] = sources[s].url; } } else { if (!json.dependencies[tools.appName + '.' + s]) { json.optionalDependencies[tools.appName + '.' + s] = '*'; } } } } } fs.writeFileSync(__dirname + '/../../../package.json', JSON.stringify(json, null, 2)); return void callback(); }); break; } case 'set': { const instance = args[0]; if (!instance) { console.warn('please specify instance.'); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } if (instance.indexOf('.') === -1) { console.warn('please specify instance, like "' + instance + '.0"'); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } dbConnect(params, () => { objects.getObject('system.adapter.' + instance, (err, obj) => { if (!err && obj) { let changed = false; for (let a = 0; a < process.argv.length; a++) { if (process.argv[a].match(/^--/) && process.argv[a + 1] && !process.argv[a + 1].match(/^--/)) { const attr = process.argv[a].substring(2); /** @type {number | string | boolean} */ let val = process.argv[a + 1]; if (val === '__EMPTY__') { val = ''; } else if (val === 'true') { val = true; } else if (val === 'false') { val = false; } else if (parseFloat(val).toString() === val) { val = parseFloat(val); } if (attr.indexOf('.') !== -1) { const parts = attr.split('.'); if (!obj.native[parts[0]] || obj.native[parts[0]][parts[1]] === undefined) { console.warn('Adapter "' + instance + '" has no setting "' + attr + '".'); } else { changed = true; obj.native[parts[0]][parts[1]] = val; console.log('New ' + attr + ' for "' + instance + '" is: ' + val); } } else { if (obj.native[attr] === undefined) { console.warn('Adapter "' + instance + '" has no setting "' + attr + '".'); } else { changed = true; obj.native[attr] = val; console.log('New ' + attr + ' for "' + instance + '" is: ' + val); } } a++; } } if (changed) { obj.from = 'system.host.' + tools.getHostName() + '.cli'; obj.ts = new Date().getTime(); objects.setObject('system.adapter.' + instance, obj, () => { console.log('Instance settings for "' + instance + '" are changed.'); return void callback(); }); } else { console.log('No parameters set.'); return void callback(); } } else { cli.error.invalidInstance(instance); return void callback(EXIT_CODES.INVALID_ADAPTER_ID); } }); }); break; } case 'host': { const hostCommand = new cli.command.host(commandOptions); hostCommand.execute(args); break; } case 'visdebug': { let widgetset = args[0]; if (widgetset && widgetset.match('/^vis-/')) { widgetset = widgetset.substring(4); } const VisDebug = require('./setup/setupVisDebug.js'); dbConnect(params, _objects => { const visDebug = new VisDebug({ objects: _objects, processExit: callback }); visDebug.enableDebug(widgetset); }); break; } case 'file': case 'f': { const cmd = args[0]; if (cmd !== 'read' && cmd !== 'r' && cmd !== 'w' && cmd !== 'write' && cmd !== 'sync' && cmd !== 'rm' && cmd !== 'unlink' && cmd !== 'del') { console.log('Invalid parameters: write "file read /vis.0/main/img/picture.png /opt/picture/image.png" to read the file'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } if (cmd !== 'sync' && !args[1]) { console.log('Invalid parameters: write "file read /vis.0/main/img/picture.png /opt/picture/image.png" to read the file from DB and store it on disk'); console.log('or "file write /opt/SOURCE/image.png /vis.0/main/DESTINATION/picture.png" to write the file into DB from disk'); console.log('or "file rm /vis.0/main/img/picture.png" to delete the file in DB'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } dbConnect(params, async (objects, _states, isOffline, objectType) => { if (cmd === 'read' || cmd ==='r') { const toRead = args[1]; const parts = toRead.replace(/\\/g, '/').split('/'); const path = (args[2] || process.cwd()).replace(/\\/g, '/').split('/'); const file = path[path.length - 1]; if (!file.match(/\.[a-zA-Z0-9]+$/)) { // If destination location seems to be a directory, add filename if (file !== '') { path.push(parts[parts.length - 1]); } else { // trailing slash path[path.length - 1] = parts[parts.length - 1]; } } let adapt = parts.shift(); if (!adapt) { // leading slash adapt = parts.shift(); } if (!adapt) { console.log(`Invalid parameters: adapter cannot be found!`); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } if (!parts.length) { console.log('Invalid parameters: file cannot be found: file not provided'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } objects.readFile(adapt, parts.join('/'), (err, data) => { err && console.error(err); if (data) { const destFilename = path.join('/'); fs.writeFileSync(destFilename, data); console.log('File "' + toRead + '" stored as "' + destFilename + '"'); } return void callback(EXIT_CODES.NO_ERROR); }); } else if (cmd === 'write' || cmd ==='w') { const toRead = args[1] || ''; const parts = toRead.replace(/\\/g, '/').split('/'); const path = (args[2] || '').replace(/\\/g, '/').split('/'); let adapt = path.shift(); if (!adapt) { adapt = path.shift(); } if (!path.length) { path.push(''); } const fileSrc = parts[parts.length - 1]; let fileDest = path[path.length - 1]; if (!fileDest || !fileDest.match(/\.[a-zA-Z0-9]+$/)) { // last portion of destination has no extension, consider being a directory fileDest = ''; } if (!fileSrc || !fs.existsSync(toRead)) { console.log(`Please provide a valid file name as source file: "file write /opt/SOURCE/script.js /vis/DESTINATION/script.js"`); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } const srcStat = fs.statSync(toRead); if (!srcStat.isFile()) { console.log(`Please provide a valid file name as source file: "file write /opt/SOURCE/script.js /vis/DESTINATION/script.js"`); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } if (!fileDest) { // destination filename is not given, use same name as source file fileDest = fileSrc; } if (fileDest !== path[path.length - 1]) { // if last part of path is different then filename, add filename if (path[path.length - 1] !== '') { path.push(fileDest); } else { // trailing slash path[path.length - 1] = fileDest; } } const destFilename = path.length ? path.join('/') : '/'; const data = fs.readFileSync(toRead); if (!adapt) { console.log('Invalid parameters: destination adapter cannot be found!'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } objects.writeFile(adapt, destFilename, data, _err => { console.log('File "' + toRead + '" stored as "' + destFilename + '"'); return void callback(EXIT_CODES.NO_ERROR); }); } else if (cmd === 'del' || cmd ==='rm' || cmd ==='unlink') { const toDelete = args[1]; const parts = toDelete.replace(/\\/g, '/').split('/'); let adapt = parts.shift(); if (!adapt) { // leading slash adapt = parts.shift(); } if (!adapt) { console.log('Invalid parameters: adapter cannot be found!'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } if (!parts.length) { console.log('Invalid parameters: file cannot be found: file not provided'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } objects.unlink(adapt, parts.join('/'), err => { err && console.error(err); !err && console.log('File "' + toDelete + '" was deleted'); return void callback(EXIT_CODES.NO_ERROR); }); } else if (cmd === 'sync') { // Sync if (objectType !== 'file') { console.log('File Sync is only available when database type "file" is used.'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } if (!objects.syncFileDirectory || !objects.dirExists) { // functionality only exists in server class console.log('Please stop ioBroker before syncing files and only use this command on the ioBroker master host!'); return void callback(EXIT_CODES.CONTROLLER_RUNNING); } // check meta.user try { const objExists = await objects.objectExists('meta.user'); if (objExists) { // check if dir is missing const dirExists = objects.dirExists('meta.user'); if (!dirExists) { // create meta.user, so users see them as upload target await objects.mkdirAsync('meta.user'); console.log('Successfully created "meta.user" directory'); } } } catch (e) { console.warn(`Could not create directory "meta.user": ${e.message}`); } try { const {numberSuccess, notifications} = objects.syncFileDirectory(args[1]); console.log(`${numberSuccess} file(s) successfully synchronized with ioBroker storage`); if (notifications.length) { console.log(); console.log('The following notifications happened during sync: '); notifications.forEach(el => console.log('- ' + el)); } return void callback(EXIT_CODES.NO_ERROR); } catch (err) { console.error(`Error on sync: ${err.message}. Partial content might have been synced.`); return void callback(EXIT_CODES.CANNOT_SYNC_FILES); } } else { console.log('Invalid parameters: write "file read /vis.0/main/img/picture.png /opt/picture/image.png" to read the file from DB and store it on disk'); console.log('or "file write /opt/SOURCE/image.png /vis.0/main/DESTINATION/picture.png" to write the file into DB from disk'); console.log('or "file rm /vis.0/main/img/picture.png" to delete the file in DB'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } }); break; } case 'id': case 'uuid': { dbConnect(params, objects => { objects.getObject('system.meta.uuid', (err, obj) => { if (err) { console.error('Error: ' + err); return void callback(EXIT_CODES.CANNOT_GET_UUID); } if (obj && obj.native) { console.log(obj.native.uuid); return void callback(); } else { console.error('Error: no UUID found'); return void callback(EXIT_CODES.CANNOT_GET_UUID); } }); }); break; } case 'v': case 'version': { const adapter = args[0]; let iopckg; if (adapter) { try { iopckg = require(tools.appName + '.' + adapter + '/package.json'); } catch { iopckg = {version: '"' + adapter + '" not found'}; } } else { iopckg = require('../package.json'); } console.log(iopckg.version); return void callback(); } case 'checklog': { dbConnect(params, (objects, states, isOffline, objectType) => { if (isOffline && tools.objectsDbHasServer(objectType)) { console.log(tools.appName + ' is not running'); return void callback(EXIT_CODES.CONTROLLER_NOT_RUNNING); } else { console.log(tools.appName + ' is running'); objects.getObjectList({startkey: 'system.host.', endkey: 'system.host.' + '\u9999'}, null, (err, res) => { if (!err && res.rows.length) { for (let i = 0; i < res.rows.length; i++) { const parts = res.rows[i].id.split('.'); // ignore system.host.name.alive and so on if (parts.length === 3) { states.pushMessage(res.rows[i].id, {command: 'checkLogging', message: null, from: 'console'}); } } } setTimeout(callback, 200); }); } }); break; } case 'repo': { Objects = require('./objects'); let repoUrlOrCommand = args[0]; // Repo url or name or "add" / "del" / "set" / "show" / "addset" const repoName = args[1]; // Repo url or name let repoUrl = args[2]; // Repo url or name if (repoUrlOrCommand !== 'add' && repoUrlOrCommand !== 'del' && repoUrlOrCommand !== 'set' && repoUrlOrCommand !== 'show' && repoUrlOrCommand !== 'addset') { repoUrl = repoUrlOrCommand; repoUrlOrCommand = 'show'; } dbConnect(params, (_objects, _states) => { const Repo = require('./setup/setupRepo.js'); const repo = new Repo({ objects: _objects, states: _states }); if (repoUrlOrCommand === 'show') { repo.showRepoStatus(callback); } else if (repoUrlOrCommand === 'add' || repoUrlOrCommand === 'del' || repoUrlOrCommand === 'set' || repoUrlOrCommand === 'addset') { if (!repoName || !repoName.match(/[-_\w\d]+/)) { console.error(`Invalid repository name: "${repoName}"`); return void callback(); } else { if (repoUrlOrCommand === 'add' || repoUrlOrCommand === 'addset') { if (!repoUrl) { console.warn(`Please define repository URL or path: ${tools.appName} add <repoName> <repoUrlOrPath>`); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } else { repo.add(repoName, repoUrl, err => { if (err) { console.error(err); return void callback(EXIT_CODES.INVALID_REPO); } else { if (repoUrlOrCommand === 'addset') { repo.setActive(repoName, err => { if (err) { console.error(err); return void callback(EXIT_CODES.INVALID_REPO); } else { console.log('Repository "' + repoName + '" set as active: "' + repoUrl + '"'); repo.showRepoStatus(callback); } }); } else { console.log('Repository "' + repoName + '" added as "' + repoUrl + '"'); repo.showRepoStatus(callback); } } }); } } else if (repoUrlOrCommand === 'set') { repo.setActive(repoName, err => { if (err) { console.error(err); return void callback(EXIT_CODES.INVALID_REPO); } else { console.log('Repository "' + repoName + '" set as active.'); repo.showRepoStatus(callback); } }); } else if (repoUrlOrCommand === 'del') { repo.del(repoName, err => { if (err) { console.error(err); return void callback(EXIT_CODES.INVALID_REPO); } else { console.log('Repository "' + repoName + '" deleted.'); repo.showRepoStatus(callback); } }); } else { console.warn('Unknown repo command: ' + repoUrlOrCommand); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } } } }); break; } case 'multihost': case 'mh': { const cmd = args[0]; if (cmd !== 'c' && cmd !== 'connect' && cmd !== 's' && cmd !== 'status' && cmd !== 'b' && cmd !== 'browse' && cmd !== 'e' && cmd !== 'enable' && cmd !== 'd' && cmd !== 'disable') { console.log('Invalid parameters. Following is possible: enable, browse, connect, status'); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } else { dbConnect(params, () => { const Multihost = require('./setup/setupMultihost.js'); const mh = new Multihost({ params: params, processExit: callback, objects: objects }); if (cmd === 's' || cmd === 'status') { mh.status(() => void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP)); } else if (cmd === 'b' || cmd === 'browse') { mh.browse((err, list) => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP); } else { mh.showHosts(list); return void callback(); } }); } else if (cmd === 'e' || cmd === 'enable') { mh.enable(true, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_ENABLE_MULTIHOST); } else { states.pushMessage(`system.host.${tools.getHostName()}`, { command: 'updateMultihost', message: null, from: 'setup' }, callback); } }); } else if (cmd === 'd' || cmd === 'disable') { mh.enable(false, err => { if (err) { console.error(err); return void callback(EXIT_CODES.CANNOT_ENABLE_MULTIHOST); } else { states.pushMessage(`system.host.${tools.getHostName()}`, {command: 'updateMultihost', message: null, from: 'setup'}, callback); } }); } else if (cmd === 'c' || cmd === 'connect') { mh.connect(args[1], args[2], err => { if (err) { console.error(err); } return void callback(err ? 1 : 0); }); } }); } break; } case 'vendor': { const password = args[0]; const file = args[1]; if (!password) { console.warn(`Please specify the password to update the vendor information!\n${tools.appName.toLowerCase()} vendor <PASS_PHRASE> <vendor.json>`); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } if (!file) { console.warn(`Please specify the path to the vendor file to update the vendor information!\n${tools.appName.toLowerCase()} vendor <PASS_PHRASE> <vendor.json>`); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } else { dbConnect(params, () => { const Vendor = require('./setup/setupVendor'); const vendor = new Vendor({ objects: objects }); vendor.checkVendor(file, password).then(() => { console.log(`Synchronised vendor information.`); return void callback(); }).catch(err => { console.error(`Cannot update vendor information: ${err.message}`); return void callback(EXIT_CODES.CANNOT_UPDATE_VENDOR); }); }); } break; } case 'cert': { const certCommand = new cli.command.cert(commandOptions); certCommand.execute(args); break; } case 'compact': { const compactCommand = new cli.command.compact(commandOptions); compactCommand.execute(args); break; } case 'plugin': { const pluginCommand = new cli.command.plugin(commandOptions); pluginCommand.execute(args); break; } case 'license': { const file = args[0]; if (!file) { console.warn(`Please specify the path to the license file or place license text directly!\n${tools.appName.toLowerCase()} license <license.file or license.text>`); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } else { dbConnect(params, () => { const License = require('./setup/setupLicense'); const license = new License({ objects: objects }); license.setLicense(file).then(type => { console.log(`License ${type} updated.`); return void callback(); }).catch(err => { console.error(`Cannot update license: ${err.message}`); return void callback(EXIT_CODES.CANNOT_UPDATE_LICENSE); }); }); } break; } default: { if (params.v || params.version) { let iopckg; if (command) { try { iopckg = require(tools.appName + '.' + command + '/package.json'); } catch { iopckg = {version: '"' + command + '" not found'}; } } else { iopckg = require('../package.json'); } console.log(iopckg.version); } else { showHelp(); return void callback(EXIT_CODES.INVALID_ARGUMENTS); } return void callback(); } } } // Save objects before exit async function processExit(exitCode) { if (objects && objects.destroy) { await objects.destroy(); } if (states && states.destroy) { await states.destroy(); } setTimeout(() => { process.exit(exitCode); }, 1000); } function delObjects(ids, callback) { if (!ids || !ids.length) { return void callback(); } else { const id = ids.shift(); objects.delObject(id, err => { if (err && id !== 'system.group.user' && id !== 'system.group.administrator' && id !== 'system.user.admin' ) { console.warn(`[Not critical] Cannot delete object ${id}: ${JSON.stringify(err)}`); } setImmediate(delObjects, ids, callback); }); } } function cleanDatabase(isDeleteDb, callback) { let taskCnt = 0; if (isDeleteDb) { objects.destroyDB(() => { // Clean up states states.getKeys('*', (_err, obj) => { const delState = []; let i; if (obj) { for (i = 0; i < obj.length; i++) { delState.push(obj[i]); } } taskCnt = 0; for (i = 0; i < obj.length; i++) { taskCnt++; states.delState(delState[i], () => !(--taskCnt) && callback && callback(obj.length)); } }); }); } else { // Clean only objects, not the views objects.getObjectList({startkey: '\u0000', endkey: '\u9999'}, (err, res) => { let ids = []; if (!err && res.rows.length) { console.log('clean ' + res.rows.length + ' objects...'); ids = res.rows.map(e => e.id); } delObjects(ids, () => { // Clean up states states.getKeys('*', (_err, obj) => { const delState = []; let i; if (obj) { for (i = 0; i < obj.length; i++) { delState.push(obj[i]); } } taskCnt = 0; console.log('clean ' + obj.length + ' states...'); for (i = 0; i < obj.length; i++) { taskCnt++; states.delState(delState[i], () => !(--taskCnt) && callback && callback(obj.length)); } if (!taskCnt && callback) { return void callback(obj.length); } }); }); }); } } function unsetup(params, callback) { dbConnect(params, () => { objects.delObject('system.meta.uuid', err => { if (err) { console.log('uuid cannot be deleted: ' + err); } else { console.log('system.meta.uuid deleted'); } objects.getObject('system.config', (_err, obj) => { if (obj.common.licenseConfirmed || obj.common.language || (obj.native && obj.native.secret)) { obj.common.licenseConfirmed = false; obj.common.language = ''; if (!params.keepsecret) { obj.native && delete obj.native.secret; } obj.from = 'system.host.' + tools.getHostName() + '.cli'; obj.ts = new Date().getTime(); objects.setObject('system.config', obj, err => { if (err) { console.log('not found: ' + err); return void callback(EXIT_CODES.CANNOT_SET_OBJECT); } else { console.log('system.config reset'); return void callback(); } }); } else { console.log('system.config is OK'); return void callback(); } }); }); }); } function restartController(callback) { const spawn = require('child_process').spawn; console.log('Starting node restart.js'); const child = spawn('node', [__dirname + '/restart.js'], { detached: true, stdio: ['ignore', 'ignore', 'ignore'], windowsHide: true }); child.unref(); if (callback) { callback(); } else { processExit(); } } function installNpm(adapter, rebuildCommand, callback) { if (typeof rebuildCommand === 'function') { callback = rebuildCommand; rebuildCommand = false; } let path = __dirname; if (typeof adapter === 'function') { callback = adapter; adapter = undefined; } if (adapter) { if (rebuildCommand && adapter === 'self') { path = pathLib.join(__dirname, '..'); } else { path = tools.getAdapterDir(adapter); } } let debug = false; for (let i = 0; i < process.argv.length; i++) { if (process.argv[i] === '--debug') { debug = true; break; } } if (!path) { console.log(`Cannot install ${tools.appName}.${adapter}: adapter path not found`); return (callback || processExit)(EXIT_CODES.CANNOT_INSTALL_NPM_PACKET); } const npmCommand = typeof rebuildCommand === 'string' ? rebuildCommand : 'install'; // iob_npm.done file was created if "npm i" yet called there if (fs.existsSync(pathLib.join(path, 'package.json')) && (rebuildCommand || !fs.existsSync(pathLib.join(path, 'iob_npm.done')))) { let cmd = `npm ${npmCommand} ${debug ? '' : '--loglevel error'}`; if (npmCommand === 'install') { cmd += ' --production'; } console.log(`${cmd} (System call1) in "${path}"`); // Install node modules as system call // System call used for update of js-controller itself, // because during installation npm packet will be deleted too, but some files must be loaded even during the install process. const exec = require('child_process').exec; const child = exec(cmd, { cwd: path, windowsHide: true }); tools.pipeLinewise(child.stderr, process.stdout); debug && tools.pipeLinewise(child.stdout, process.stdout); child.on('exit', (code, _signal) => { // code 1 is strange error that cannot be explained. Everything is installed but error :( if (code && code !== 1) { console.log(`Cannot install ${tools.appName}.${adapter}: ${code}`); (callback || processExit)(EXIT_CODES.CANNOT_INSTALL_NPM_PACKET); return; } // command succeeded if (!rebuildCommand || rebuildCommand === 'install') { fs.writeFileSync(path + '/iob_npm.done', ' '); } typeof callback === 'function' && callback(null, adapter); }); } else if (typeof callback === 'function') { callback(null, adapter); } } function getRepository(repoUrl, params, callback) { if (typeof params === 'function') { callback = params; params = {}; } params = params || {}; if (!repoUrl || typeof repoUrl !== 'object') { if (!objects) { dbConnect(params, () => getRepository(repoUrl, params, callback)); } else { // try to read repository objects.getObject('system.config', (_err, systemConfig) => { objects.getObject('system.repositories', (err, repos) => { // Check if repositories exists if (!err && repos && repos.native && repos.native.repositories) { const active = systemConfig.common.activeRepo; if (repos.native.repositories[active]) { if (typeof repos.native.repositories[active] === 'string') { repos.native.repositories[active] = { link: repos.native.repositories[active], json: null }; } // If repo is not yet loaded if (!repos.native.repositories[active].json) { console.log(`Update repository "${active}" under "${repos.native.repositories[active].link}"`); // Load it tools.getRepositoryFile(repos.native.repositories[active].link, { hash: repos.native.repositories[active].hash, sources: repos.native.repositories[active].json, controller: require('../io-package.json').common.version, node: process.version, name: tools.appName }, (_err, sources, sourcesHash) => { if (_err && !sources) { callback(_err, sources); } else { repos.native.repositories[active].json = sources; repos.native.repositories[active].hash = sourcesHash; repos.from = `system.host.${tools.getHostName()}.cli`; repos.ts = new Date().getTime(); // Store uploaded repo objects.setObject('system.repositories', repos, () => void callback(null, sources)); } }); } else { // We have already repo, give it back return void callback(null, repos.native.repositories[active].json); } } else { console.log('Requested repository "' + active + '" does not exist in config.'); return void callback(EXIT_CODES.INVALID_REPO); } } else { console.log('No repositories defined.'); return void callback(EXIT_CODES.INVALID_REPO); } }); }); } } else { return void callback(null, repoUrl); } } async function resetDbConnect(_callback) { if (objects) { await objects.destroy(); objects = null; } if (states) { await states.destroy(); states = null; } if (Objects) { delete require.cache[require.resolve(__dirname + '/objects')]; Objects = null; } if (States) { delete require.cache[require.resolve(__dirname + '/states')]; States = null; } } // function showConfig(config, root) { // root = root || []; // const prefix = root.join('/').toUpperCase(); // for (const attr in config) { // if (!config.hasOwnProperty(attr)) continue; // if (attr.match(/comment$/i)) continue; // if (typeof config[attr] === 'object') { // const nextRoot = deepClone(root); // nextRoot.push(attr); // showConfig(config[attr], nextRoot); // } else { // console.log(`${prefix}${(prefix ? '/' : '') + attr}: ` + config[attr]); // } // } // } function checkSystemOffline(onlyCheck, callback) { if (!objects || !states) { // should never happen callback && callback(true); return; } if (onlyCheck) { callback && callback(true); return; } setTimeout(() => { // Slight delay to allow "setup first" from Pre 2.0 to 2.0 enumHosts(objects).then(hosts => { const hostToCheck = hosts.map(host => 'system.host.' + host.common.hostname + '.alive'); states.getStates(hostToCheck, (err, res) => { !err && Array.isArray(res) && res.forEach(aliveState => { if (aliveState && aliveState.val) { callback && callback(false); callback = null; } }); callback && callback(true); }); }).catch(() => { callback && callback(true); }); }, 500); } /** * Connects to the DB or tests the connection. The callback has the following signature: * `(objects: any, states: any, isOffline?: boolean, objectsDBType?: string) => void` */ function dbConnect(onlyCheck, params, callback) { if (typeof onlyCheck === 'object') { callback = params; params = onlyCheck; onlyCheck = false; } if (typeof onlyCheck === 'function') { callback = onlyCheck; onlyCheck = false; } if (typeof params === 'function') { callback = params; params = null; } params = params || {}; const config = fs.readJSONSync(tools.getConfigFileName()); if (objects && states) { return void callback(objects, states, false, config.objects.type, config); } config.states = config.states || {type: 'file'}; config.objects = config.objects || {type: 'file'}; Objects = require('./objects'); // Objects DB Client object States = require('./states'); // States DB Client object // Give to controller 2 seconds for connection let isObjectConnected = false; let isStatesConnected = false; // Detect timeout or try to open file itself setTimeout(async () => { if (isObjectConnected && isStatesConnected) { return; } if (!isObjectConnected) { if (objects) { // Destroy Client we tried to connect with await objects.destroy(); objects = null; } if (tools.objectsDbHasServer(config.objects.type)) { // Just open in memory DB itself Objects = require(`@iobroker/db-objects-${config.objects.type}`).Server; objects = new Objects({ connection: config.objects, logger: { silly: _msg => {}, debug: _msg => {}, info: _msg => {}, warn: msg => console.log(msg), error: msg => console.log(msg) }, connected: () => { isObjectConnected = true; if (isStatesConnected && typeof callback === 'function') { return void callback(objects, states, true, config.objects.type, config); } } }); } else { console.log('No connection to objects ' + config.objects.host + ':' + config.objects.port + '[' + config.objects.type + ']'); if (onlyCheck) { callback && callback(objects, states, true, config.objects.type, config); callback = null; } else { processExit(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB); } } } if (!isStatesConnected) { if (states) { // Destroy Client we tried to connect with await states.destroy(); states = null; } if (tools.statesDbHasServer(config.states.type)) { // Just open in memory DB itself States = require(`@iobroker/db-states-${config.states.type}`).Server; states = new States({ connection: config.states, logger: { silly: _msg => {}, debug: _msg => {}, info: _msg => {}, warn: msg => console.log(msg), error: msg => console.log(msg) }, connected: () => { isStatesConnected = true; if (isObjectConnected && typeof callback === 'function') { return void callback(objects, states, true, config.objects.type, config); } }, // react on change change: (id, msg) => states.onChange && states.onChange(id, msg) }); states.onChange = null; // here the custom onChange handler could be installed } else { if (states) { // Destroy Client we tried to connect with await states.destroy(); states = null; } if (objects) { // Destroy Client we tried to connect with await objects.destroy(); objects = null; } console.log('No connection to states ' + config.states.host + ':' + config.states.port + '[' + config.states.type + ']'); if (onlyCheck) { callback && callback(objects, states, true, config.objects.type, config); callback = null; } else { processExit(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB); } } } setTimeout(() => { // Failsafe if (isObjectConnected && isStatesConnected) { return; } console.log('No connection to databases possible ...'); if (onlyCheck) { callback && callback(null, null, true, config.objects.type, config); callback = null; } else { processExit(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB); } }, params.timeout || (10000 + config.objects.connectTimeout) || 12000); }, params.timeout || (config.objects.connectTimeout * 2) || 4000); // try to connect as client objects = new Objects({ connection: config.objects, logger: { silly: _msg => {}, debug: _msg => {}, info: _msg => {}, warn: msg => console.log(msg), error: msg => console.log(msg) }, connected: () => { if (isObjectConnected) { return; } isObjectConnected = true; if (isStatesConnected && typeof callback === 'function') { checkSystemOffline(onlyCheck, isOffline => { callback(objects, states, isOffline, config.objects.type, config); }); } } }); states = new States({ connection: config.states, logger: { silly: _msg => {}, debug: _msg => {}, info: _msg => {}, warn: msg => console.log(msg), error: msg => console.log(msg) }, connected: () => { if (isStatesConnected) { return; } isStatesConnected = true; if (isObjectConnected && typeof callback === 'function') { checkSystemOffline(onlyCheck, isOffline => { callback(objects, states, isOffline, config.objects.type, config); }); } }, change: (id, state) => states.onChange && states.onChange(id, state) }); } module.exports.processCommand = function (_objects, _states, command, args, params, callback) { objects = _objects; states = _states; processCommand(command, args, params, callback); }; const processCommandAsync = tools.promisifyNoError(module.exports.processCommand); module.exports.processCommandAsync = function (_objects, _states, _command, _args, _params) { return processCommandAsync.apply(undefined, arguments); }; module.exports.execute = function () { // direct call const _yargs = initYargs(); const command = _yargs.argv._[0]; const args = []; for (let a = 1; a < _yargs.argv._.length; a++) { args.push(_yargs.argv._[a]); } processCommand(command, args, _yargs.argv, processExit); }; process.on('unhandledRejection', err => { console.error(`Uncaught Rejection: ${err.stack || err}`); processExit(EXIT_CODES.UNCAUGHT_EXCEPTION); });