OpenHue MCP Server

var common = require('./common'); var _tempDir = require('./tempdir').tempDir; var _pwd = require('./pwd'); var path = require('path'); var fs = require('fs'); var child = require('child_process'); var DEFAULT_MAXBUFFER_SIZE = 20 * 1024 * 1024; var DEFAULT_ERROR_CODE = 1; common.register('exec', _exec, { unix: false, canReceivePipe: true, wrapOutput: false, }); // We use this function to run `exec` synchronously while also providing realtime // output. function execSync(cmd, opts, pipe) { if (!common.config.execPath) { common.error('Unable to find a path to the node binary. Please manually set config.execPath'); } var tempDir = _tempDir(); var paramsFile = path.resolve(tempDir + '/' + common.randomFileName()); var stderrFile = path.resolve(tempDir + '/' + common.randomFileName()); var stdoutFile = path.resolve(tempDir + '/' + common.randomFileName()); opts = common.extend({ silent: common.config.silent, cwd: _pwd().toString(), env: process.env, maxBuffer: DEFAULT_MAXBUFFER_SIZE, encoding: 'utf8', }, opts); if (fs.existsSync(paramsFile)) common.unlinkSync(paramsFile); if (fs.existsSync(stderrFile)) common.unlinkSync(stderrFile); if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile); opts.cwd = path.resolve(opts.cwd); var paramsToSerialize = { command: cmd, execOptions: opts, pipe: pipe, stdoutFile: stdoutFile, stderrFile: stderrFile, }; // Create the files and ensure these are locked down (for read and write) to // the current user. The main concerns here are: // // * If we execute a command which prints sensitive output, then // stdoutFile/stderrFile must not be readable by other users. // * paramsFile must not be readable by other users, or else they can read it // to figure out the path for stdoutFile/stderrFile and create these first // (locked down to their own access), which will crash exec() when it tries // to write to the files. function writeFileLockedDown(filePath, data) { fs.writeFileSync(filePath, data, { encoding: 'utf8', mode: parseInt('600', 8), }); } writeFileLockedDown(stdoutFile, ''); writeFileLockedDown(stderrFile, ''); writeFileLockedDown(paramsFile, JSON.stringify(paramsToSerialize)); var execArgs = [ path.join(__dirname, 'exec-child.js'), paramsFile, ]; /* istanbul ignore else */ if (opts.silent) { opts.stdio = 'ignore'; } else { opts.stdio = [0, 1, 2]; } var code = 0; // Welcome to the future try { // Bad things if we pass in a `shell` option to child_process.execFileSync, // so we need to explicitly remove it here. delete opts.shell; child.execFileSync(common.config.execPath, execArgs, opts); } catch (e) { // Commands with non-zero exit code raise an exception. code = e.status || DEFAULT_ERROR_CODE; } // fs.readFileSync uses buffer encoding by default, so call // it without the encoding option if the encoding is 'buffer'. // Also, if the exec timeout is too short for node to start up, // the files will not be created, so these calls will throw. var stdout = ''; var stderr = ''; if (opts.encoding === 'buffer') { stdout = fs.readFileSync(stdoutFile); stderr = fs.readFileSync(stderrFile); } else { stdout = fs.readFileSync(stdoutFile, opts.encoding); stderr = fs.readFileSync(stderrFile, opts.encoding); } // No biggie if we can't erase the files now -- they're in a temp dir anyway // and we locked down permissions (see the note above). try { common.unlinkSync(paramsFile); } catch (e) {} try { common.unlinkSync(stderrFile); } catch (e) {} try { common.unlinkSync(stdoutFile); } catch (e) {} if (code !== 0) { // Note: `silent` should be unconditionally true to avoid double-printing // the command's stderr, and to avoid printing any stderr when the user has // set `shell.config.silent`. common.error(stderr, code, { continue: true, silent: true }); } var obj = common.ShellString(stdout, stderr, code); return obj; } // execSync() // Wrapper around exec() to enable echoing output to console in real time function execAsync(cmd, opts, pipe, callback) { opts = common.extend({ silent: common.config.silent, cwd: _pwd().toString(), env: process.env, maxBuffer: DEFAULT_MAXBUFFER_SIZE, encoding: 'utf8', }, opts); var c = child.exec(cmd, opts, function (err, stdout, stderr) { if (callback) { if (!err) { callback(0, stdout, stderr); } else if (err.code === undefined) { // See issue #536 /* istanbul ignore next */ callback(1, stdout, stderr); } else { callback(err.code, stdout, stderr); } } }); if (pipe) c.stdin.end(pipe); if (!opts.silent) { c.stdout.pipe(process.stdout); c.stderr.pipe(process.stderr); } return c; } //@ //@ ### exec(command [, options] [, callback]) //@ //@ Available options: //@ //@ + `async`: Asynchronous execution. If a callback is provided, it will be set to //@ `true`, regardless of the passed value (default: `false`). //@ + `silent`: Do not echo program output to console (default: `false`). //@ + `encoding`: Character encoding to use. Affects the values returned to stdout and stderr, and //@ what is written to stdout and stderr when not in silent mode (default: `'utf8'`). //@ + and any option available to Node.js's //@ [`child_process.exec()`](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback) //@ //@ Examples: //@ //@ ```javascript //@ var version = exec('node --version', {silent:true}).stdout; //@ //@ var child = exec('some_long_running_process', {async:true}); //@ child.stdout.on('data', function(data) { //@ /* ... do something with data ... */ //@ }); //@ //@ exec('some_long_running_process', function(code, stdout, stderr) { //@ console.log('Exit code:', code); //@ console.log('Program output:', stdout); //@ console.log('Program stderr:', stderr); //@ }); //@ ``` //@ //@ Executes the given `command` _synchronously_, unless otherwise specified. When in synchronous //@ mode, this returns a `ShellString` (compatible with ShellJS v0.6.x, which returns an object //@ of the form `{ code:..., stdout:... , stderr:... }`). Otherwise, this returns the child process //@ object, and the `callback` receives the arguments `(code, stdout, stderr)`. //@ //@ Not seeing the behavior you want? `exec()` runs everything through `sh` //@ by default (or `cmd.exe` on Windows), which differs from `bash`. If you //@ need bash-specific behavior, try out the `{shell: 'path/to/bash'}` option. function _exec(command, options, callback) { options = options || {}; if (!command) common.error('must specify command'); var pipe = common.readFromPipe(); // Callback is defined instead of options. if (typeof options === 'function') { callback = options; options = { async: true }; } // Callback is defined with options. if (typeof options === 'object' && typeof callback === 'function') { options.async = true; } options = common.extend({ silent: common.config.silent, async: false, }, options); if (options.async) { return execAsync(command, options, pipe, callback); } else { return execSync(command, options, pipe); } } module.exports = _exec;