ssh-client-wrapper 2.11.0 → 2.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
15
  - uses: actions/checkout@v3
16
- - uses: actions/download-artifact@v3
16
+ - uses: actions/download-artifact@v4
17
17
  with:
18
18
  name: code-coverage-report
19
19
  - uses: paambaati/codeclimate-action@v5.0.0
@@ -21,7 +21,6 @@ jobs:
21
21
  TEST_USER: testuser
22
22
  TEST_PW: passw0rd
23
23
  TEST_PORT: 10022
24
- DEBUG: 'ssh*'
25
24
  runs-on: ubuntu-latest
26
25
  strategy:
27
26
  matrix:
@@ -35,7 +34,9 @@ jobs:
35
34
  - run: npm ci
36
35
  - run: npm run test
37
36
  - run: npm run coverage
38
- - uses: actions/upload-artifact@v3
37
+ if: matrix.node-version == '20.x'
38
+ - uses: actions/upload-artifact@v4
39
+ if: matrix.node-version == '20.x'
39
40
  with:
40
41
  name: code-coverage-report
41
42
  path: coverage-report.lcov
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ import js from "@eslint/js";
3
+ import globals from "globals";
4
+ import stylistic from "@stylistic/eslint-plugin";
5
+ import jsdoc from "eslint-plugin-jsdoc";
6
+ import node from "eslint-plugin-node";
7
+ import chaiFriendly from "eslint-plugin-chai-friendly";
8
+
9
+ const jsdocRules = {
10
+ "jsdoc/require-jsdoc": ["warn", { enableFixer: false }],
11
+ "jsdoc/require-param": ["warn", { enableFixer: false }],
12
+ "jsdoc/require-param-description": "warn",
13
+ "jsdoc/require-param-name": "warn",
14
+ "jsdoc/require-param-type": "warn",
15
+ "jsdoc/require-hyphen-before-param-description": [
16
+ "warn",
17
+ "always",
18
+ ],
19
+ "jsdoc/require-returns": "warn",
20
+ "jsdoc/require-returns-check": "error",
21
+ };
22
+
23
+ const styleRules = {
24
+ "@stylistic/arrow-spacing": [
25
+ "error",
26
+ {
27
+ after: false,
28
+ before: false,
29
+ }],
30
+ "@stylistic/comma-dangle": [
31
+ "error",
32
+ "never",
33
+ ],
34
+ "@stylistic/spaced-comment": [
35
+ "error",
36
+ "never",
37
+ ],
38
+ "@stylistic/lines-around-comment": [
39
+ "error",
40
+ {
41
+ beforeBlockComment: true,
42
+ afterBlockComment: false,
43
+ beforeLineComment: false,
44
+ afterLineComment: false,
45
+ allowBlockStart: true,
46
+ },
47
+ ],
48
+ "@stylistic/lines-between-class-members": [
49
+ "error",
50
+ "always",
51
+ {
52
+ exceptAfterSingleLine: true,
53
+ },
54
+ ],
55
+ "@stylistic/newline-per-chained-call": [
56
+ "error",
57
+ {
58
+ ignoreChainWithDepth: 2,
59
+ },
60
+ ],
61
+ "@stylistic/padded-blocks": [
62
+ "error",
63
+ "never",
64
+ ],
65
+ "@stylistic/brace-style": [
66
+ "error",
67
+ "1tbs",
68
+ {
69
+ allowSingleLine: true,
70
+ },
71
+ ],
72
+ "@stylistic/padding-line-between-statements": [
73
+ "error",
74
+ {
75
+ blankLine: "any",
76
+ prev: [
77
+ "const",
78
+ "let",
79
+ "var",
80
+ ],
81
+ next: "*",
82
+ },
83
+ {
84
+ blankLine: "always",
85
+ prev: "*",
86
+ next: [
87
+ "class",
88
+ "do",
89
+ "for",
90
+ "function",
91
+ "switch",
92
+ "try",
93
+ "while",
94
+ ],
95
+ },
96
+ {
97
+ blankLine: "any",
98
+ prev: [
99
+ "const",
100
+ "let",
101
+ "var",
102
+ "for",
103
+ "while",
104
+ "do",
105
+ "block-like",
106
+ "multiline-block-like",
107
+ ],
108
+ next: [
109
+ "block-like",
110
+ "do",
111
+ "for",
112
+ "multiline-block-like",
113
+ "switch",
114
+ "try",
115
+ "while",
116
+ ],
117
+ },
118
+ ],
119
+ };
120
+
121
+ export default [
122
+ js.configs.recommended,
123
+ jsdoc.configs["flat/recommended"],
124
+ stylistic.configs["disable-legacy"],
125
+ stylistic.configs.customize({
126
+ indent: 2,
127
+ quotes: "double",
128
+ semi: true,
129
+ arrowParens: true,
130
+ }),
131
+ {
132
+ ignores: ["node_modules/"],
133
+ },
134
+ {
135
+ files: ["test/**/*.js"],
136
+ plugins: {
137
+ chaiFriendly,
138
+ },
139
+ languageOptions: {
140
+ globals: {
141
+ ...globals.nodeBuiltin,
142
+ ...globals.node,
143
+ it: "readonly",
144
+ describe: "readonly",
145
+ before: "readonly",
146
+ after: "readonly",
147
+ beforeEach: "readonly",
148
+ afterEach: "readonly",
149
+ },
150
+ },
151
+ },
152
+ {
153
+ files: ["**/*.js"],
154
+ plugins: {
155
+ node,
156
+ "@stylistic": stylistic,
157
+ jsdoc,
158
+ },
159
+ languageOptions: {
160
+ globals: {
161
+ ...globals.nodeBuiltin,
162
+ ...globals.node,
163
+ },
164
+ sourceType: "commonjs",
165
+ },
166
+ rules: {
167
+ ...styleRules,
168
+ ...jsdocRules,
169
+ "node/exports-style": [
170
+ "error",
171
+ "module.exports",
172
+ ],
173
+ "no-nested-ternary": "off",
174
+ "no-param-reassign": "warn",
175
+ "camelcase": [
176
+ "error",
177
+ {
178
+ properties: "never",
179
+ },
180
+ ],
181
+ "eqeqeq": [
182
+ "error",
183
+ "always",
184
+ {
185
+ null: "ignore",
186
+ },
187
+ ],
188
+ "func-style": [
189
+ "error",
190
+ "declaration",
191
+ {
192
+ allowArrowFunctions: true,
193
+ },
194
+ ],
195
+ "no-use-before-define": [
196
+ "error",
197
+ {
198
+ functions: false,
199
+ },
200
+ ],
201
+ "no-warning-comments": "warn",
202
+ "require-unicode-regexp": "off",
203
+
204
+ },
205
+ },
206
+ ];
package/lib/fork.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  const debug = require("debug")("sshClientWrapper:debug:fork");
3
3
  const debugVerbose = require("debug")("sshClientWrapper:verbose:fork");
4
- const debugSendPty = require("debug")("sshClientWrapperInsecure:fork_sendpty");
4
+ const debugSendPty = require("debug")("sshClientWrapper:insecure:fork");
5
5
  const { spawn } = require("node-pty");
6
6
 
7
7
  const { rePwPrompt, rePhPrompt, reNewHostPrompt, reRemoteHostIdentificationHasChanged, buff2String, sendPty, watchDogTimer } = require("./util.js");
@@ -11,8 +11,8 @@ const nodePtyOpt = {
11
11
  windowsHide: true
12
12
  };
13
13
 
14
- function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
15
- return new Promise((resolve, reject) => {
14
+ function sshLoginCallback(output, ptyProcess, pw, ph, logger = debugSendPty) {
15
+ return new Promise((resolve, reject)=>{
16
16
  if (reRemoteHostIdentificationHasChanged.test(output)) {
17
17
  debug("REMOTE HOST IDENTIFICATION HAS CHANGED");
18
18
  const rt = /known_hosts:(\d+)/.exec(output);
@@ -38,10 +38,10 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
38
38
  if (typeof pw === "function") {
39
39
  debug("call password callback function");
40
40
  pw()
41
- .then((v) => {
41
+ .then((v)=>{
42
42
  sendPty(ptyProcess, `${v}\n`, logger, 30);
43
43
  })
44
- .catch((e) => {
44
+ .catch((e)=>{
45
45
  reject(e);
46
46
  });
47
47
  resolve();
@@ -56,10 +56,10 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
56
56
  if (typeof ph === "function") {
57
57
  debug("call passphrase callback function");
58
58
  ph()
59
- .then((v) => {
59
+ .then((v)=>{
60
60
  sendPty(ptyProcess, `${v}\n`, logger, 45);
61
61
  })
62
- .catch((e) => {
62
+ .catch((e)=>{
63
63
  reject(e);
64
64
  });
65
65
  }
@@ -70,7 +70,7 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
70
70
  /**
71
71
  * create pty for master ssh session and store to hostInfo.masterPty
72
72
  */
73
- function createMasterPty (hostInfo) {
73
+ function createMasterPty(hostInfo) {
74
74
  debug("fork pty for master connection");
75
75
 
76
76
  if (hostInfo.masterPty) {
@@ -81,13 +81,13 @@ function createMasterPty (hostInfo) {
81
81
  hostInfo.masterPty = ptyProcess;
82
82
  }
83
83
 
84
- async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExitCodes = []) {
85
- return new Promise((resolve, reject) => {
84
+ async function fork(hostInfo, cmd, args, timeout, outputCallback, retryableExitCodes = [], expects = []) {
85
+ return new Promise((resolve, reject)=>{
86
86
  debug(`cmd=${cmd}: args=${args}`);
87
87
  const ptyProcess = spawn(cmd, args, nodePtyOpt);
88
88
  let ac = null;
89
89
  if (timeout > 0) {
90
- ac = watchDogTimer(timeout, cmd, () => {
90
+ ac = watchDogTimer(timeout, cmd, ()=>{
91
91
  ptyProcess.kill();
92
92
  const err = new Error(`watchdog timer expired ${timeout}`);
93
93
  err.code = "TIMEEXPIRE";
@@ -97,7 +97,7 @@ async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExit
97
97
  ptyProcess.onData(buff2String.bind(null, debugVerbose));
98
98
 
99
99
  if (hostInfo !== null) {
100
- ptyProcess.onData(async (data) => {
100
+ ptyProcess.onData((data)=>{
101
101
  const output = data.toString();
102
102
  sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase)
103
103
  .catch(reject);
@@ -108,13 +108,25 @@ async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExit
108
108
  err.retryable = true;
109
109
  reject(err);
110
110
  }
111
+
112
+ if (Array.isArray(expects) && expects.length > 0) {
113
+ const { reExpect, send, numKeep } = expects.shift();
114
+ if (reExpect.test(output)) {
115
+ sendPty(ptyProcess, `${send}\n`, debugSendPty, "userDefinedHandler").catch(reject);
116
+ if (numKeep > 0) {
117
+ expects.unshift({ reExpect, send, numKeep: numKeep - 1 });
118
+ }
119
+ } else {
120
+ expects.unshift({ reExpect, send, numKeep });
121
+ }
122
+ }
111
123
  });
112
124
  }
113
125
 
114
126
  if (typeof outputCallback === "function") {
115
127
  ptyProcess.onData(buff2String.bind(null, outputCallback));
116
128
  }
117
- ptyProcess.onExit(({ exitCode, signal }) => {
129
+ ptyProcess.onExit(({ exitCode, signal })=>{
118
130
  debug("onExit", exitCode, signal);
119
131
 
120
132
  if (ac !== null) {
package/lib/index.js CHANGED
@@ -1,22 +1,23 @@
1
1
  "use strict";
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
- const debug = require("debug")("sshClientWrapper:interface");
4
+ const debug = require("debug")("sshClientWrapper:debug:interface");
5
5
  const debugVerbose = require("debug")("sshClientWrapper:verbose:interface");
6
- const { sshExec, canConnect, disconnect, ls } = require("./sshExec.js");
6
+ const { sshExec, canConnect, disconnect, expect } = require("./sshExec.js");
7
7
  const { send, recv } = require("./rsyncExec.js");
8
8
  const { isArrayOfString, sanityCheck } = require("./util.js");
9
9
 
10
10
  /**
11
11
  * Integer means integer number but it is defined as Object for now
12
12
  * workaround for eslint-plugin-jsdoc's no-undefined-types rule bug.
13
- * @typedef {Object} Integer
13
+ * @typedef {object} Integer
14
+ * @property
14
15
  */
15
16
 
16
17
  /**
17
18
  * host info object which contain all settings for 1 remote host
18
19
  * it will be (shallow) copied in constructor.
19
- * @param {Object} hostInfo - option object
20
+ * @param {object} hostInfo - option object
20
21
  * @param {string} hostInfo.host - destination
21
22
  * @param {string} hostInfo.user - login name
22
23
  * @param {number} hostInfo.port - port number
@@ -33,17 +34,17 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
33
34
  * @param {string[]} hostInfo.sshOpt - additional options for ssh
34
35
  */
35
36
 
36
- const logAndReject = (message) => {
37
+ const logAndReject = (message)=>{
37
38
  debug(message);
38
39
  return Promise.reject(new Error(message));
39
40
  };
40
41
 
41
42
  /**
42
43
  * Facade class.
43
- * @constructor
44
+ * @class
44
45
  */
45
46
  class SshClientWrapper {
46
- constructor (hostInfo) {
47
+ constructor(hostInfo) {
47
48
  debug("constructor called for", hostInfo.host);
48
49
  debugVerbose("hostInfo=", hostInfo);
49
50
  this.hostInfo = { ...hostInfo };
@@ -73,7 +74,7 @@ class SshClientWrapper {
73
74
  *
74
75
  * note rcfile and prependCmd override hostInfo.rcfile and hostInfo.prependCmd respectively
75
76
  */
76
- async exec (cmd, timeout = 0, outputCallback = null, rcfile = null, prependCmd = null) {
77
+ async exec(cmd, timeout = 0, outputCallback = null, rcfile = null, prependCmd = null) {
77
78
  debug("exec called", cmd);
78
79
 
79
80
  if (typeof cmd !== "string" || cmd === "") {
@@ -82,6 +83,39 @@ class SshClientWrapper {
82
83
  return sshExec(this.hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd);
83
84
  }
84
85
 
86
+ /**
87
+ * execute command on remote host and get output text
88
+ * @param {string} cmd - Cmdline which will be executed.
89
+ * @param {Integer} timeout - timeout in secconds
90
+ * @param {string} rcfile - rcfile path which will be sourced before executing actual command
91
+ * @param {string} prependCmd - command string which will be executed before executing actual command
92
+ * @returns {object} - output test array (output) and return value of cmd (rt)
93
+ */
94
+ async execAndGetOutput(cmd, timeout = 0, rcfile = null, prependCmd = null) {
95
+ debug("execAndGetOutput called", cmd);
96
+ const outputArray = [];
97
+ const cb = (data)=>{
98
+ outputArray.push(data);
99
+ };
100
+ let rt = null;
101
+ try {
102
+ rt = await sshExec(this.hostInfo, cmd, timeout, cb, rcfile, prependCmd);
103
+ } catch (e) {
104
+ //non-zero retrun should be allowed
105
+ if (typeof e.rt === "undefined" || e.rt < 0) {
106
+ throw e;
107
+ }
108
+ }
109
+ const output = outputArray.length === 0
110
+ ? []
111
+ : outputArray
112
+ .join("\n")
113
+ .replace(/\n+/g, "\n")
114
+ .replace(/\n$/, "")
115
+ .split("\n");
116
+ return { output, rt };
117
+ }
118
+
85
119
  /**
86
120
  * execute ls command and return output
87
121
  * @param {string} target - file or directory path to watch
@@ -89,9 +123,22 @@ class SshClientWrapper {
89
123
  * @param {Integer} timeout - timeout in secconds
90
124
  * @returns {string[]} - output from ls
91
125
  */
92
- async ls (target, lsOpt = [], timeout = 0) {
126
+ async ls(target, lsOpt = [], timeout = 0) {
93
127
  debug("ls called", target, lsOpt);
94
- return ls(this.hostInfo, target, lsOpt, timeout);
128
+ const { output, rt } = await this.execAndGetOutput(`ls ${lsOpt.join(" ")} ${target}`, timeout);
129
+ return rt === 0 ? output : rt;
130
+ }
131
+
132
+ /**
133
+ * execute series of comands like expect(3)
134
+ * @param {string} cmd - Cmdline which will be executed.
135
+ * @param {Array} expects - array of expect and send string pairs
136
+ * @param {Integer} timeout - timeout in secconds
137
+ * @returns {Integer} - Return code of cmd.
138
+ */
139
+ async expect(cmd, expects, timeout = 180) {
140
+ debug("expect called", expects);
141
+ return expect(this.hostInfo, cmd, expects, timeout);
95
142
  }
96
143
 
97
144
  /**
@@ -102,13 +149,13 @@ class SshClientWrapper {
102
149
  * @param {Integer} timeout - timeout in secconds
103
150
  * @returns {Promise} - Resolved with undefined when file transfer is done.
104
151
  */
105
- async send (src, dst, opt = [], timeout = 0) {
152
+ async send(src, dst, opt = [], timeout = 0) {
106
153
  debug("send called", src, dst, opt);
107
154
 
108
155
  if (!isArrayOfString(src)) {
109
156
  return logAndReject("src must be array of string");
110
157
  }
111
- if (!src.some((e) => {
158
+ if (!src.some((e)=>{
112
159
  return e !== "";
113
160
  })) {
114
161
  return logAndReject("src must contain non-empty string");
@@ -133,13 +180,13 @@ class SshClientWrapper {
133
180
  * @param {Integer} timeout - timeout in secconds
134
181
  * @returns {Promise} - Resolved with undefined when file transfer is done.
135
182
  */
136
- async recv (src, dst, opt = [], timeout = 0) {
183
+ async recv(src, dst, opt = [], timeout = 0) {
137
184
  debug("recv called", src, dst, opt);
138
185
 
139
186
  if (!isArrayOfString(src)) {
140
187
  return logAndReject("src must be array of string");
141
188
  }
142
- if (!src.some((e) => {
189
+ if (!src.some((e)=>{
143
190
  return e !== "";
144
191
  })) {
145
192
  return logAndReject("src must contain non-empty string");
@@ -161,7 +208,7 @@ class SshClientWrapper {
161
208
  * @param {Integer} timeout - timeout in secconds
162
209
  * @returns {Promise} - Resolved with true on success, otherwise rejected with Error.
163
210
  */
164
- async canConnect (timeout = 60) {
211
+ async canConnect(timeout = 60) {
165
212
  debug("canConnect called with timeout=", timeout);
166
213
  debugVerbose("hostInfo=", this.hostInfo);
167
214
 
@@ -174,7 +221,7 @@ class SshClientWrapper {
174
221
  /**
175
222
  * Disconnect master session
176
223
  */
177
- async disconnect () {
224
+ async disconnect() {
178
225
  debug(`disconnect from ${this.hostInfo.host} called`);
179
226
  disconnect(this.hostInfo);
180
227
  }
package/lib/rsyncExec.js CHANGED
@@ -10,9 +10,9 @@ const { fork } = require("./fork.js");
10
10
 
11
11
  const { sshCmd, rsyncCmd, acceptableRsyncRetrunCodes, getSshOption } = require("./util.js");
12
12
 
13
- async function checkRsyncVersion (timeout) {
13
+ async function checkRsyncVersion(timeout) {
14
14
  const rt = {};
15
- await fork(null, rsyncCmd, ["--version"], timeout, (output) => {
15
+ await fork(null, rsyncCmd, ["--version"], timeout, (output)=>{
16
16
  const result = /^rsync *version *(\d+)\.(\d+)\.(\d+).*$/m.exec(output);
17
17
 
18
18
  if (result) {
@@ -26,7 +26,7 @@ async function checkRsyncVersion (timeout) {
26
26
 
27
27
  /**
28
28
  * Send file or directory and its child to server.
29
- * @param {Object} hostInfo - hostinfo object
29
+ * @param {object} hostInfo - hostinfo object
30
30
  * @param {[string]} argSrc - File or directory name which to be send.
31
31
  * @param {string} dst - Destination path.
32
32
  * @param {[string]} rsyncOpt - optional arguments for rsync
@@ -34,13 +34,13 @@ async function checkRsyncVersion (timeout) {
34
34
  *
35
35
  * this function execute rsync -avv --copy-unsafe-links src rsync://[user@]host[:port]/dst
36
36
  */
37
- async function send (hostInfo, argSrc, dst, rsyncOpt, timeout) {
37
+ async function send(hostInfo, argSrc, dst, rsyncOpt, timeout) {
38
38
  await connect(hostInfo);
39
- const promises = await Promise.allSettled(argSrc.map((e) => {
39
+ const promises = await Promise.allSettled(argSrc.map((e)=>{
40
40
  return glob(e);
41
41
  }));
42
42
  const src = promises
43
- .map((e, i) => {
43
+ .map((e, i)=>{
44
44
  if (e.status !== "fulfilled") {
45
45
  return null;
46
46
  }
@@ -49,10 +49,10 @@ async function send (hostInfo, argSrc, dst, rsyncOpt, timeout) {
49
49
  }
50
50
  return e.value;
51
51
  })
52
- .filter((e) => {
52
+ .filter((e)=>{
53
53
  return e !== null;
54
54
  })
55
- .flatMap((e) => {
55
+ .flatMap((e)=>{
56
56
  return e;
57
57
  });
58
58
 
@@ -85,14 +85,14 @@ async function send (hostInfo, argSrc, dst, rsyncOpt, timeout) {
85
85
 
86
86
  /**
87
87
  * format src array to src string for recv
88
- * @param {Object} hostInfo - hostinfo object
88
+ * @param {object} hostInfo - hostinfo object
89
89
  * @returns {boolean} - rsync command version is higher than 3.2.4 or not
90
90
  */
91
- async function isNewRsync (hostInfo) {
92
- if (!hostInfo.rsyncVersion ||
93
- !Number.isInteger(hostInfo.rsyncVersion.major) ||
94
- !Number.isInteger(hostInfo.rsyncVersion.minor) ||
95
- !Number.isInteger(hostInfo.rsyncVersion.patch)) {
91
+ async function isNewRsync(hostInfo) {
92
+ if (!hostInfo.rsyncVersion
93
+ || !Number.isInteger(hostInfo.rsyncVersion.major)
94
+ || !Number.isInteger(hostInfo.rsyncVersion.minor)
95
+ || !Number.isInteger(hostInfo.rsyncVersion.patch)) {
96
96
  hostInfo.rsyncVersion = await checkRsyncVersion(3);
97
97
  }
98
98
  return hostInfo.rsyncVersion.major >= 3 && hostInfo.rsyncVersion.minor >= 2 && hostInfo.rsyncVersion.patch >= 4;
@@ -100,7 +100,7 @@ async function isNewRsync (hostInfo) {
100
100
 
101
101
  /**
102
102
  * Send file or directory and its child to server.
103
- * @param {Object} hostInfo - hostinfo object
103
+ * @param {object} hostInfo - hostinfo object
104
104
  * @param {[string]} argSrc - File or directory name which to be send.
105
105
  * @param {string} dst - Destination path.
106
106
  * @param {[string]} rsyncOpt - optional arguments for rsync
@@ -108,7 +108,7 @@ async function isNewRsync (hostInfo) {
108
108
  *
109
109
  * this function execute rsync -av --copy-unsafe-links rsync://[user@]host[:port]/src dst
110
110
  */
111
- async function recv (hostInfo, argSrc, dst, rsyncOpt, timeout) {
111
+ async function recv(hostInfo, argSrc, dst, rsyncOpt, timeout) {
112
112
  await connect(hostInfo);
113
113
  const src = `${hostInfo.host}:${argSrc.join(" ")}`;
114
114
  const sshArgs = getSshOption(hostInfo, true);
@@ -118,7 +118,7 @@ async function recv (hostInfo, argSrc, dst, rsyncOpt, timeout) {
118
118
  args.unshift("--old-args");
119
119
  }
120
120
 
121
- // make destination directory
121
+ //make destination directory
122
122
  const dstDir = argSrc.len > 1 || hasMagic(argSrc[0]) ? dst : path.dirname(dst);
123
123
  await fs.mkdir(dstDir, { recursive: true });
124
124