OpenHue MCP Server
by lsemenenko
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;