"use strict"; var _path = _interopRequireDefault(require("path")); var _ssh = _interopRequireDefault(require("ssh2")); var _pMap = _interopRequireDefault(require("p-map")); var _assert = _interopRequireDefault(require("assert")); var _sbScandir = _interopRequireDefault(require("sb-scandir")); var _shellEscape = _interopRequireDefault(require("shell-escape")); var Helpers = _interopRequireWildcard(require("./helpers")); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } class SSH { constructor() { this.connection = null; } connect(givenConfig) { const connection = new _ssh.default(); this.connection = connection; return new Promise(function (resolve) { resolve(Helpers.normalizeConfig(givenConfig)); }).then(config => new Promise((resolve, reject) => { connection.on('error', reject); if (config.onKeyboardInteractive) { connection.on('keyboard-interactive', config.onKeyboardInteractive); } connection.on('ready', () => { connection.removeListener('error', reject); resolve(this); }); connection.on('end', () => { if (this.connection === connection) { this.connection = null; } }); connection.on('close', () => { if (this.connection === connection) { this.connection = null; } const error = new Error('No response from server'); // $FlowIgnore: Custom attribute error.code = 'ETIMEDOUT'; reject(error); }); connection.connect(config); })); } requestShell() { var _this = this; return _asyncToGenerator(function* () { const connection = _this.connection; (0, _assert.default)(connection, 'Not connected to server'); return new Promise(function (resolve, reject) { connection.shell(Helpers.generateCallback(resolve, reject)); }); })(); } requestSFTP() { var _this2 = this; return _asyncToGenerator(function* () { const connection = _this2.connection; (0, _assert.default)(connection, 'Not connected to server'); return new Promise(function (resolve, reject) { connection.sftp(Helpers.generateCallback(resolve, reject)); }); })(); } mkdir(path, type = 'sftp', givenSftp = null) { var _this3 = this; return _asyncToGenerator(function* () { (0, _assert.default)(_this3.connection, 'Not connected to server'); (0, _assert.default)(type === 'exec' || type === 'sftp', 'Type should either be sftp or exec'); if (type === 'exec') { const output = yield _this3.exec('mkdir', ['-p', path]); if (output.stdout) { throw new Error(output.stdout); } } else { (0, _assert.default)(!givenSftp || typeof givenSftp === 'object', 'sftp must be an object'); const sftp = givenSftp || (yield _this3.requestSFTP()); const makeSftpDirectory = retry => Helpers.mkdirSftp(path, sftp).catch(error => { if (retry && error && (error.message === 'No such file' || error.code === 'ENOENT')) { return _this3.mkdir(_path.default.dirname(path), 'sftp', sftp).then(() => makeSftpDirectory(false)); } throw error; }); try { yield makeSftpDirectory(true); } finally { if (!givenSftp) { sftp.end(); } } } })(); } exec(command, parameters = [], options = {}) { var _this4 = this; return _asyncToGenerator(function* () { (0, _assert.default)(_this4.connection, 'Not connected to server'); (0, _assert.default)(typeof options === 'object' && options, 'options must be an Object'); (0, _assert.default)(!options.cwd || typeof options.cwd === 'string', 'options.cwd must be a string'); (0, _assert.default)(!options.stdin || typeof options.stdin === 'string', 'options.stdin must be a string'); (0, _assert.default)(!options.stream || ['stdout', 'stderr', 'both'].indexOf(options.stream) !== -1, 'options.stream must be among "stdout", "stderr" and "both"'); (0, _assert.default)(!options.options || typeof options.options === 'object', 'options.options must be an object'); const output = yield _this4.execCommand([command].concat((0, _shellEscape.default)(parameters)).join(' '), options); if (!options.stream || options.stream === 'stdout') { if (output.stderr) { throw new Error(output.stderr); } return output.stdout; } if (options.stream === 'stderr') { return output.stderr; } return output; })(); } execCommand(givenCommand, options = {}) { var _this5 = this; return _asyncToGenerator(function* () { let command = givenCommand; const connection = _this5.connection; (0, _assert.default)(connection, 'Not connected to server'); (0, _assert.default)(typeof options === 'object' && options, 'options must be an Object'); (0, _assert.default)(!options.cwd || typeof options.cwd === 'string', 'options.cwd must be a string'); (0, _assert.default)(!options.stdin || typeof options.stdin === 'string', 'options.stdin must be a string'); (0, _assert.default)(!options.options || typeof options.options === 'object', 'options.options must be an object'); if (options.cwd) { // NOTE: Output piping cd command to hide directory non-existent errors command = `cd ${(0, _shellEscape.default)([options.cwd])} 1> /dev/null 2> /dev/null; ${command}`; } const output = { stdout: [], stderr: [] }; return new Promise(function (resolve, reject) { connection.exec(command, options.options || {}, Helpers.generateCallback(function (stream) { stream.on('data', function (chunk) { if (options.onStdout) options.onStdout(chunk); output.stdout.push(chunk); }); stream.stderr.on('data', function (chunk) { if (options.onStderr) options.onStderr(chunk); output.stderr.push(chunk); }); if (options.stdin) { stream.write(options.stdin); stream.end(); } stream.on('close', function (code, signal) { resolve({ code, signal, stdout: output.stdout.join('').trim(), stderr: output.stderr.join('').trim() }); }); }, reject)); }); })(); } getFile(localFile, remoteFile, givenSftp = null, givenOpts = null) { var _this6 = this; return _asyncToGenerator(function* () { (0, _assert.default)(_this6.connection, 'Not connected to server'); (0, _assert.default)(typeof localFile === 'string' && localFile, 'localFile must be a string'); (0, _assert.default)(typeof remoteFile === 'string' && remoteFile, 'remoteFile must be a string'); (0, _assert.default)(!givenSftp || typeof givenSftp === 'object', 'sftp must be an object'); (0, _assert.default)(!givenOpts || typeof givenOpts === 'object', 'opts must be an object'); const opts = givenOpts || {}; const sftp = givenSftp || (yield _this6.requestSFTP()); try { yield new Promise(function (resolve, reject) { sftp.fastGet(remoteFile, localFile, opts, Helpers.generateCallback(resolve, reject)); }); } finally { if (!givenSftp) { sftp.end(); } } })(); } putFile(localFile, remoteFile, givenSftp = null, givenOpts = null) { var _this7 = this; return _asyncToGenerator(function* () { (0, _assert.default)(_this7.connection, 'Not connected to server'); (0, _assert.default)(typeof localFile === 'string' && localFile, 'localFile must be a string'); (0, _assert.default)(typeof remoteFile === 'string' && remoteFile, 'remoteFile must be a string'); (0, _assert.default)(!givenSftp || typeof givenSftp === 'object', 'sftp must be an object'); (0, _assert.default)(!givenOpts || typeof givenOpts === 'object', 'opts must be an object'); (0, _assert.default)((yield Helpers.exists(localFile)), `localFile does not exist at ${localFile}`); const that = _this7; const opts = givenOpts || {}; const sftp = givenSftp || (yield _this7.requestSFTP()); function putFile(retry) { return new Promise(function (resolve, reject) { sftp.fastPut(localFile, remoteFile, opts, Helpers.generateCallback(resolve, function (error) { if (error.message === 'No such file' && retry) { resolve(that.mkdir(_path.default.dirname(remoteFile), 'sftp', sftp).then(() => putFile(false))); } else { reject(error); } })); }); } try { yield putFile(true); } finally { if (!givenSftp) { sftp.end(); } } })(); } putFiles(files, givenConfig = {}) { var _this8 = this; return _asyncToGenerator(function* () { (0, _assert.default)(_this8.connection, 'Not connected to server'); (0, _assert.default)(Array.isArray(files), 'files must be an array'); for (let i = 0, length = files.length; i < length; ++i) { const file = files[i]; (0, _assert.default)(file, 'files items must be valid objects'); (0, _assert.default)(file.local && typeof file.local === 'string', `files[${i}].local must be a string`); (0, _assert.default)(file.remote && typeof file.remote === 'string', `files[${i}].remote must be a string`); } const transferred = []; const config = Helpers.normalizePutFilesOptions(givenConfig); const sftp = config.sftp || (yield _this8.requestSFTP()); try { yield (0, _pMap.default)(files, /*#__PURE__*/ function () { var _ref = _asyncToGenerator(function* (file) { yield _this8.putFile(file.local, file.remote, sftp, config.sftpOptions); transferred.push(file); }); return function (_x) { return _ref.apply(this, arguments); }; }()); } catch (error) { error.transferred = transferred; throw error; } finally { if (!sftp) { sftp.end(); } } })(); } putDirectory(localDirectory, remoteDirectory, givenConfig = {}) { var _this9 = this; return _asyncToGenerator(function* () { (0, _assert.default)(_this9.connection, 'Not connected to server'); (0, _assert.default)(typeof localDirectory === 'string' && localDirectory, 'localDirectory must be a string'); (0, _assert.default)(typeof remoteDirectory === 'string' && remoteDirectory, 'remoteDirectory must be a string'); (0, _assert.default)((yield Helpers.exists(localDirectory)), `localDirectory does not exist at ${localDirectory}`); (0, _assert.default)((yield Helpers.stat(localDirectory)).isDirectory(), `localDirectory is not a directory at ${localDirectory}`); (0, _assert.default)(typeof givenConfig === 'object' && givenConfig, 'config must be an object'); const config = Helpers.normalizePutDirectoryOptions(givenConfig); const sftp = config.sftp || (yield _this9.requestSFTP()); const scanned = yield (0, _sbScandir.default)(localDirectory, config.recursive, config.validate); const files = scanned.files.map(i => _path.default.relative(localDirectory, i)); const directories = scanned.directories.map(i => _path.default.relative(localDirectory, i)); let failed = false; let directoriesQueue = Promise.resolve(); const directoriesCreated = new Set(); const createDirectory = /*#__PURE__*/ function () { var _ref2 = _asyncToGenerator(function* (path) { if (!directoriesCreated.has(path)) { directoriesCreated.add(path); directoriesQueue = directoriesQueue.then(() => _this9.mkdir(path, 'sftp', sftp)); yield directoriesQueue; } }); return function createDirectory(_x2) { return _ref2.apply(this, arguments); }; }(); try { yield (0, _pMap.default)(files, /*#__PURE__*/ function () { var _ref3 = _asyncToGenerator(function* (file) { const localFile = _path.default.join(localDirectory, file); const remoteFile = _path.default.join(remoteDirectory, file).split(_path.default.sep).join('/'); const remoteFileDirectory = _path.default.dirname(remoteFile); yield createDirectory(remoteFileDirectory); try { yield _this9.putFile(localFile, remoteFile, sftp, config.sftpOptions); config.tick(localFile, remoteFile, null); } catch (_) { failed = true; config.tick(localFile, remoteFile, _); } }); return function (_x3) { return _ref3.apply(this, arguments); }; }(), { concurrency: config.concurrency }); yield (0, _pMap.default)(directories, /*#__PURE__*/ function () { var _ref4 = _asyncToGenerator(function* (entry) { const remoteEntry = _path.default.join(remoteDirectory, entry).split(_path.default.sep).join('/'); yield createDirectory(remoteEntry); }); return function (_x4) { return _ref4.apply(this, arguments); }; }(), { concurrency: config.concurrency }); } finally { if (!config.sftp) { sftp.end(); } } return !failed; })(); } dispose() { if (this.connection) { this.connection.end(); } } } module.exports = SSH;