ssh-client-wrapper 2.10.2 → 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
@@ -23,7 +23,7 @@ services:
23
23
  TEST_PW: passw0rd
24
24
  TEST_HOST: testbed
25
25
  working_dir: /work/ssh-client-wrapper
26
- command: bash -c "mkdir /root/.ssh 2>/dev/null; npm run mocha"
26
+ command: bash -c "mkdir /root/.ssh 2>/dev/null; npm run mocha"
27
27
  tester-buster:
28
28
  build:
29
29
  context: .
@@ -33,4 +33,4 @@ services:
33
33
  TEST_PW: passw0rd
34
34
  TEST_HOST: testbed
35
35
  working_dir: /work/ssh-client-wrapper
36
- command: bash -c "mkdir /root/.ssh 2>/dev/null; npm run mocha"
36
+ command: bash -c "mkdir /root/.ssh 2>/dev/null; npm run mocha"
@@ -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
@@ -28,20 +29,22 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
28
29
  * @param {Integer} hostInfo.ConnectTimeout=60 - timeout used when connectiong to ths SSH server
29
30
  * @param {Integer} hostInfo.maxRetry=3 - max number of retry
30
31
  * @param {Integer} hostInfo.retryDuration=1000 - duration time (msec) between each retry
32
+ * @param {string} hostInfo.rcfile - rcfile path which will be sourced before executing actual command
33
+ * @param {string} hostInfo.prependCmd - command string which will be executed before executing actual command
31
34
  * @param {string[]} hostInfo.sshOpt - additional options for ssh
32
35
  */
33
36
 
34
- const logAndReject = (message) => {
37
+ const logAndReject = (message)=>{
35
38
  debug(message);
36
39
  return Promise.reject(new Error(message));
37
40
  };
38
41
 
39
42
  /**
40
43
  * Facade class.
41
- * @constructor
44
+ * @class
42
45
  */
43
46
  class SshClientWrapper {
44
- constructor (hostInfo) {
47
+ constructor(hostInfo) {
45
48
  debug("constructor called for", hostInfo.host);
46
49
  debugVerbose("hostInfo=", hostInfo);
47
50
  this.hostInfo = { ...hostInfo };
@@ -65,15 +68,52 @@ class SshClientWrapper {
65
68
  * @param {string} cmd - Cmdline which will be executed.
66
69
  * @param {Integer} timeout - timeout in secconds
67
70
  * @param { Function } outputCallback - call back routine for output
71
+ * @param {string} rcfile - rcfile path which will be sourced before executing actual command
72
+ * @param {string} prependCmd - command string which will be executed before executing actual command
68
73
  * @returns {Integer} - Return code of cmd.
74
+ *
75
+ * note rcfile and prependCmd override hostInfo.rcfile and hostInfo.prependCmd respectively
69
76
  */
70
- async exec (cmd, timeout = 0, outputCallback = null) {
77
+ async exec(cmd, timeout = 0, outputCallback = null, rcfile = null, prependCmd = null) {
71
78
  debug("exec called", cmd);
72
79
 
73
80
  if (typeof cmd !== "string" || cmd === "") {
74
81
  return logAndReject("cmd must be string");
75
82
  }
76
- return sshExec(this.hostInfo, cmd, timeout, outputCallback);
83
+ return sshExec(this.hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd);
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 };
77
117
  }
78
118
 
79
119
  /**
@@ -83,9 +123,22 @@ class SshClientWrapper {
83
123
  * @param {Integer} timeout - timeout in secconds
84
124
  * @returns {string[]} - output from ls
85
125
  */
86
- async ls (target, lsOpt = [], timeout = 0) {
126
+ async ls(target, lsOpt = [], timeout = 0) {
87
127
  debug("ls called", target, lsOpt);
88
- 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);
89
142
  }
90
143
 
91
144
  /**
@@ -96,13 +149,13 @@ class SshClientWrapper {
96
149
  * @param {Integer} timeout - timeout in secconds
97
150
  * @returns {Promise} - Resolved with undefined when file transfer is done.
98
151
  */
99
- async send (src, dst, opt = [], timeout = 0) {
152
+ async send(src, dst, opt = [], timeout = 0) {
100
153
  debug("send called", src, dst, opt);
101
154
 
102
155
  if (!isArrayOfString(src)) {
103
156
  return logAndReject("src must be array of string");
104
157
  }
105
- if (!src.some((e) => {
158
+ if (!src.some((e)=>{
106
159
  return e !== "";
107
160
  })) {
108
161
  return logAndReject("src must contain non-empty string");
@@ -127,13 +180,13 @@ class SshClientWrapper {
127
180
  * @param {Integer} timeout - timeout in secconds
128
181
  * @returns {Promise} - Resolved with undefined when file transfer is done.
129
182
  */
130
- async recv (src, dst, opt = [], timeout = 0) {
183
+ async recv(src, dst, opt = [], timeout = 0) {
131
184
  debug("recv called", src, dst, opt);
132
185
 
133
186
  if (!isArrayOfString(src)) {
134
187
  return logAndReject("src must be array of string");
135
188
  }
136
- if (!src.some((e) => {
189
+ if (!src.some((e)=>{
137
190
  return e !== "";
138
191
  })) {
139
192
  return logAndReject("src must contain non-empty string");
@@ -155,7 +208,7 @@ class SshClientWrapper {
155
208
  * @param {Integer} timeout - timeout in secconds
156
209
  * @returns {Promise} - Resolved with true on success, otherwise rejected with Error.
157
210
  */
158
- async canConnect (timeout = 60) {
211
+ async canConnect(timeout = 60) {
159
212
  debug("canConnect called with timeout=", timeout);
160
213
  debugVerbose("hostInfo=", this.hostInfo);
161
214
 
@@ -168,7 +221,7 @@ class SshClientWrapper {
168
221
  /**
169
222
  * Disconnect master session
170
223
  */
171
- async disconnect () {
224
+ async disconnect() {
172
225
  debug(`disconnect from ${this.hostInfo.host} called`);
173
226
  disconnect(this.hostInfo);
174
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