"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.doExec = void 0;

var _debug = _interopRequireDefault(require("debug"));

var _child_process = require("child_process");

var _core = require("@kui-shell/core");

var _exec = require("../util/exec");

var _json = require("../util/json");

var _usageHelpers = require("../util/usage-helpers");

var _catchall = require("./catchall");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/*
 * Copyright 2017-19 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }

  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }

    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }

    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }

    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};
/**
 * This plugin introduces commands that dispatch to a local bash-like
 * shell
 *
 */


const strings = (0, _core.i18n)('plugin-bash-like');
const debug = (0, _debug.default)('plugins/bash-like/cmds/general');

const doExec = (cmdLine, execOptions // eslint-disable-next-line @typescript-eslint/no-explicit-any
) => // eslint-disable-next-line no-async-promise-executor
new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
  // purposefully imported lazily, so that we don't spoil browser mode (where shell is not available)
  const options = {
    maxBuffer: 1 * 1024 * 1024,
    env: Object.assign({}, process.env, execOptions['env'] || {})
  };

  if (process.env.SHELL) {
    options.shell = process.env.SHELL;
  }

  const proc = (0, _child_process.exec)(cmdLine, options); // accumulate doms from the output of the subcommand

  let rawOut = '';
  let rawErr = '';
  proc.stdout.on('data', data => __awaiter(void 0, void 0, void 0, function* () {
    const out = data.toString();

    if (execOptions.stdout) {
      execOptions.stdout(data);
    } else {
      rawOut += out;
    }
  }));
  proc.stderr.on('data', data => {
    rawErr += data;

    if (execOptions.stderr) {
      execOptions.stderr(data.toString()); // stderrLines += data.toString()
    }
  });
  proc.on('close', exitCode => __awaiter(void 0, void 0, void 0, function* () {
    if (exitCode === 0) {
      // great, the process exited normally. resolve!
      if (execOptions && execOptions['json']) {
        // caller expects JSON back
        try {
          resolve(JSON.parse(rawOut));
        } catch (err) {
          const error = new Error('unexpected non-JSON');
          error['value'] = rawOut;
          reject(error);
        }
      } else if (execOptions && execOptions.raw) {
        // caller just wants the raw textual output
        resolve(rawOut);
      } else if (!rawOut && !rawErr) {
        // in this case, the command produced nothing, but it did exit
        // with a 0 exit code
        resolve(true);
      } else {
        // else, we pass back a formatted form of the output
        const json = (0, _json.extractJSON)(rawOut);

        if (json && typeof json === 'object') {
          json['type'] = 'shell';
          json['verb'] = 'get';
          resolve(json);
        } else {
          resolve(rawOut);
        }
      }
    } else {
      // oops, non-zero exit code. reject!
      debug('non-zero exit code', exitCode); // strip off e.g. /bin/sh: line 0:

      const cleanErr = rawErr.replace(/(^\/[^/]+\/[^:]+: )(line \d+: )?/, '');

      try {
        (0, _exec.handleNonZeroExitCode)(cmdLine, exitCode, rawOut, cleanErr, execOptions);
      } catch (err) {
        reject(err);
      }
    }
  }));
}));

exports.doExec = doExec;

const doShell = (argv, args) => // eslint-disable-next-line no-async-promise-executor
new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
  const {
    execOptions,
    REPL
  } = args;

  if ((0, _core.inBrowser)()) {
    reject(new Error('Local file access not supported when running in a browser'));
  } // purposefully imported lazily, so that we don't spoil browser mode (where shell is not available)


  const shell = yield Promise.resolve().then(() => require('shelljs'));

  if (argv.length < 2) {
    reject(new Error('Please provide a bash command'));
  }

  const cmd = argv[1];
  debug('argv', argv);
  debug('cmd', cmd); // shell.echo prints the the outer console, which we don't want

  if (shell[cmd] && ((0, _core.inBrowser)() || cmd !== 'mkdir' && cmd !== 'echo')) {
    const args = argv.slice(2); // remember OLDPWD, so that `cd -` works (shell issue #78)

    if (process.env.OLDPWD === undefined) {
      process.env.OLDPWD = '';
    }

    const OLDPWD = shell.pwd(); // remember it for when we're done

    if (cmd === 'cd' && args[0] === '-') {
      // special case for "cd -"
      args[0] = process.env.OLDPWD;
    } // see if we should use the built-in shelljs support


    if (!args.find(arg => arg.charAt(0) === '-') && // any options? then no
    !args.find(arg => arg === '>') && // redirection? then no
    cmd !== 'ls') {
      // shelljs doesn't like dash args
      // otherwise, shelljs has a built-in handler for this
      debug('using internal shelljs', cmd, args);
      const output = shell[cmd](args);

      if (cmd === 'cd') {
        const newDir = shell.pwd().toString();
        process.env.OLDPWD = OLDPWD;
        process.env.PWD = newDir;

        if (output.code === 0) {
          // special case: if the user asked to change working
          // directory, respond with the new working directory
          resolve(shell.pwd().toString());
        } else {
          reject(new Error(output.stderr));
        }
      } else {
        // otherwise, respond with the output of the command;
        if (output && output.length > 0) {
          if (execOptions && execOptions['json']) {
            resolve(JSON.parse(output));
          } else {
            resolve(output.toString());
          }
        } else {
          resolve(true);
        }
      }
    }
  } //
  // otherwise, we use exec to implement the shell command; here, we
  // cross our fingers that the platform implements the requested
  // command
  //


  const rest = argv.slice(1); // skip over '!'

  const cmdLine = rest.map(_ => REPL.encodeComponent(_)).join(' ');
  debug('cmdline', cmdLine, rest);
  doExec(cmdLine, execOptions).then(resolve, reject);
}));

const usage = {
  cd: {
    strict: 'cd',
    command: 'cd',
    title: strings('cdUsageTitle'),
    header: strings('cdUsageHeader'),
    optional: _usageHelpers.localFilepath
  }
};
/**
 * cd command
 *
 */

const cd = args => {
  const dir = args.REPL.split(args.command, true, true)[1] || '';
  debug('cd dir', dir);
  return doShell(['!', 'cd', dir], args).then(newDir => {
    if (args.tab.state) {
      args.tab.state.capture();
    }

    return newDir;
  }).catch(err => {
    err['code'] = 500;
    throw err;
  });
};

const bcd = ({
  command,
  execOptions,
  REPL
}) => __awaiter(void 0, void 0, void 0, function* () {
  const pwd = yield REPL.qexec(command.replace(/^cd/, 'kuicd'), undefined, undefined, execOptions);
  debug('pwd', pwd);
  process.env.PWD = pwd;
  return pwd;
});

const specialHandler = args => {
  if (args.execOptions.type === _core.ExecType.TopLevel) {
    throw new Error('this command is intended for internal consumption only');
  }

  return (0, _catchall.dispatchToShell)(args);
};
/**
 * Register command handlers
 *
 */


var _default = commandTree => {
  commandTree.listen('/!', _catchall.dispatchToShell, {
    docs: 'Execute a UNIX shell command',
    requiresLocal: true
  });
  commandTree.listen('/sendtopty', specialHandler, {
    docs: 'Execute a UNIX shell command with a PTY',
    hidden: true
  });
  commandTree.listen('/kuicd', cd, {
    requiresLocal: true
  });

  if (!(0, _core.inBrowser)()) {
    commandTree.listen('/cd', cd, {
      usage: usage.cd
    });
  } else {
    commandTree.listen('/cd', bcd, {
      usage: usage.cd
    });
  }
};

exports.default = _default;