ssh-client-wrapper 2.12.4 → 2.13.1
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.
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/coverage_report.yml +3 -3
- package/.github/workflows/release.yml +29 -0
- package/.github/workflows/run_test.yml +7 -10
- package/.husky/_pre-commit +1 -0
- package/.husky/pre-commit +0 -3
- package/.releaserc.json +16 -0
- package/CHANGELOG.md +20 -0
- package/README.md +99 -0
- package/eslint.config.mjs +77 -102
- package/lib/fork.js +10 -14
- package/lib/index.js +23 -21
- package/lib/rsyncExec.js +13 -17
- package/lib/sshExec.js +15 -23
- package/lib/util.js +22 -36
- package/package.json +28 -25
- package/test/getSshOption.js +6 -6
- package/test/interface.js +30 -27
- package/test/rsyncExec.js +17 -17
- package/test/sanityCheck.js +6 -6
- package/test/sshExec.js +17 -13
- package/test/testUtil/hostInfo.js +1 -2
- package/test/testUtil/testFiles.js +16 -19
- package/.eslintrc.json +0 -34
- package/.github/workflows/npm_publish.yml +0 -22
- package/test/.eslintrc.json +0 -28
package/lib/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import Debug from "debug";
|
|
4
|
+
import * as sshExecModule from "./sshExec.js";
|
|
5
|
+
import * as rsyncExecModule from "./rsyncExec.js";
|
|
6
|
+
import { isArrayOfString, sanityCheck } from "./util.js";
|
|
7
|
+
|
|
8
|
+
const debug = Debug("sshClientWrapper:debug:interface");
|
|
9
|
+
const debugVerbose = Debug("sshClientWrapper:verbose:interface");
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Integer means integer number but it is defined as Object for now
|
|
@@ -25,10 +26,10 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
|
|
|
25
26
|
* @param {string | Function} hostInfo.passphrase - passphrase or function which return passphrase of private key
|
|
26
27
|
* @param {string} hostInfo.keyFile - private key filename
|
|
27
28
|
* @param {boolean} hostInfo.noStrictHostKeyChecking - bypass host key checking if true
|
|
28
|
-
* @param {Integer} hostInfo.ControlPersist
|
|
29
|
-
* @param {Integer} hostInfo.ConnectTimeout
|
|
30
|
-
* @param {Integer} hostInfo.maxRetry
|
|
31
|
-
* @param {Integer} hostInfo.retryDuration
|
|
29
|
+
* @param {Integer} hostInfo.ControlPersist - how long keep master connection after last client connection has been closed (sec).
|
|
30
|
+
* @param {Integer} hostInfo.ConnectTimeout - timeout used when connectiong to ths SSH server
|
|
31
|
+
* @param {Integer} hostInfo.maxRetry - max number of retry
|
|
32
|
+
* @param {Integer} hostInfo.retryDuration - duration time (msec) between each retry
|
|
32
33
|
* @param {string} hostInfo.rcfile - rcfile path which will be sourced before executing actual command
|
|
33
34
|
* @param {string} hostInfo.prependCmd - command string which will be executed before executing actual command
|
|
34
35
|
* @param {string[]} hostInfo.sshOpt - additional options for ssh
|
|
@@ -44,13 +45,14 @@ const logAndReject = (message)=>{
|
|
|
44
45
|
* @class
|
|
45
46
|
*/
|
|
46
47
|
class SshClientWrapper {
|
|
47
|
-
constructor(hostInfo) {
|
|
48
|
+
constructor(hostInfo, deps = { ...sshExecModule, ...rsyncExecModule }) {
|
|
48
49
|
debug("constructor called for", hostInfo.host);
|
|
49
50
|
debugVerbose("hostInfo=", hostInfo);
|
|
50
51
|
this.hostInfo = { ...hostInfo };
|
|
51
52
|
sanityCheck(this.hostInfo);
|
|
52
53
|
this.hostInfo.masterPty = null;
|
|
53
54
|
this.hostInfo.rsyncVersion = null;
|
|
55
|
+
this.deps = deps;
|
|
54
56
|
|
|
55
57
|
if (process.env.HOME) {
|
|
56
58
|
try {
|
|
@@ -80,7 +82,7 @@ class SshClientWrapper {
|
|
|
80
82
|
if (typeof cmd !== "string" || cmd === "") {
|
|
81
83
|
return logAndReject("cmd must be string");
|
|
82
84
|
}
|
|
83
|
-
return sshExec(this.hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd);
|
|
85
|
+
return this.deps.sshExec(this.hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd);
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
/**
|
|
@@ -99,7 +101,7 @@ class SshClientWrapper {
|
|
|
99
101
|
};
|
|
100
102
|
let rt = null;
|
|
101
103
|
try {
|
|
102
|
-
rt = await sshExec(this.hostInfo, cmd, timeout, cb, rcfile, prependCmd);
|
|
104
|
+
rt = await this.deps.sshExec(this.hostInfo, cmd, timeout, cb, rcfile, prependCmd);
|
|
103
105
|
} catch (e) {
|
|
104
106
|
//non-zero retrun should be allowed
|
|
105
107
|
if (typeof e.rt === "undefined" || e.rt < 0) {
|
|
@@ -138,7 +140,7 @@ class SshClientWrapper {
|
|
|
138
140
|
*/
|
|
139
141
|
async expect(cmd, expects, timeout = 180) {
|
|
140
142
|
debug("expect called", expects);
|
|
141
|
-
return expect(this.hostInfo, cmd, expects, timeout);
|
|
143
|
+
return this.deps.expect(this.hostInfo, cmd, expects, timeout);
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
/**
|
|
@@ -169,7 +171,7 @@ class SshClientWrapper {
|
|
|
169
171
|
if (typeof opt !== "undefined" && !isArrayOfString(opt)) {
|
|
170
172
|
return logAndReject("opt must be array of string");
|
|
171
173
|
}
|
|
172
|
-
return send(this.hostInfo, src, dst, opt, timeout);
|
|
174
|
+
return this.deps.send(this.hostInfo, src, dst, opt, timeout);
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
/**
|
|
@@ -200,7 +202,7 @@ class SshClientWrapper {
|
|
|
200
202
|
if (typeof opt !== "undefined" && !isArrayOfString(opt)) {
|
|
201
203
|
return logAndReject("opt must be array of string");
|
|
202
204
|
}
|
|
203
|
-
return recv(this.hostInfo, src, dst, opt, timeout);
|
|
205
|
+
return this.deps.recv(this.hostInfo, src, dst, opt, timeout);
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
/**
|
|
@@ -215,7 +217,7 @@ class SshClientWrapper {
|
|
|
215
217
|
if (!Number.isInteger(timeout) || timeout <= 0) {
|
|
216
218
|
return logAndReject("timeout must be positive integer");
|
|
217
219
|
}
|
|
218
|
-
return canConnect(this.hostInfo, timeout);
|
|
220
|
+
return this.deps.canConnect(this.hostInfo, timeout);
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
/**
|
|
@@ -223,8 +225,8 @@ class SshClientWrapper {
|
|
|
223
225
|
*/
|
|
224
226
|
async disconnect() {
|
|
225
227
|
debug(`disconnect from ${this.hostInfo.host} called`);
|
|
226
|
-
disconnect(this.hostInfo);
|
|
228
|
+
this.deps.disconnect(this.hostInfo);
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
231
|
|
|
230
|
-
|
|
232
|
+
export default SshClientWrapper;
|
package/lib/rsyncExec.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const debugVerbose = require("debug")("sshClientWrapper:verbose:rsyncExec");
|
|
6
|
-
const { glob, hasMagic } = require("glob");
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import Debug from "debug";
|
|
4
|
+
import { glob, hasMagic } from "glob";
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import { connect, retryWrapper, sshExec } from "./sshExec.js";
|
|
7
|
+
import { fork } from "./fork.js";
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
import { sshCmd, rsyncCmd, acceptableRsyncRetrunCodes, getSshOption } from "./util.js";
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
const debug = Debug("sshClientWrapper:debug:rsyncExec");
|
|
12
|
+
const debugVerbose = Debug("sshClientWrapper:verbose:rsyncExec");
|
|
13
|
+
|
|
14
|
+
export async function checkRsyncVersion(timeout) {
|
|
14
15
|
const rt = {};
|
|
15
16
|
await fork(null, rsyncCmd, ["--version"], timeout, (output)=>{
|
|
16
17
|
const result = /^rsync *version *(\d+)\.(\d+)\.(\d+).*$/m.exec(output);
|
|
@@ -34,7 +35,7 @@ async function checkRsyncVersion(timeout) {
|
|
|
34
35
|
*
|
|
35
36
|
* this function execute rsync -avv --copy-unsafe-links src rsync://[user@]host[:port]/dst
|
|
36
37
|
*/
|
|
37
|
-
async function send(hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
38
|
+
export async function send(hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
38
39
|
await connect(hostInfo);
|
|
39
40
|
const promises = await Promise.allSettled(argSrc.map((e)=>{
|
|
40
41
|
return glob(e);
|
|
@@ -108,7 +109,7 @@ async function isNewRsync(hostInfo) {
|
|
|
108
109
|
*
|
|
109
110
|
* this function execute rsync -av --copy-unsafe-links rsync://[user@]host[:port]/src dst
|
|
110
111
|
*/
|
|
111
|
-
async function recv(hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
112
|
+
export async function recv(hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
112
113
|
await connect(hostInfo);
|
|
113
114
|
const src = `${hostInfo.host}:${argSrc.join(" ")}`;
|
|
114
115
|
const sshArgs = getSshOption(hostInfo, true);
|
|
@@ -126,8 +127,3 @@ async function recv(hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
|
126
127
|
debugVerbose(`recv: ${rsyncCmd} ${args.join(" ")}`);
|
|
127
128
|
return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout, null, acceptableRsyncRetrunCodes), hostInfo);
|
|
128
129
|
}
|
|
129
|
-
module.exports = {
|
|
130
|
-
checkRsyncVersion,
|
|
131
|
-
send,
|
|
132
|
-
recv
|
|
133
|
-
};
|
package/lib/sshExec.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import { setTimeout as setTimeoutPromise } from "timers/promises";
|
|
3
|
+
import Debug from "debug";
|
|
4
|
+
import { sendPty, sshCmd, getSshOption, sanityCheck, watchDogTimer } from "./util.js";
|
|
5
|
+
import { fork, createMasterPty, sshLoginCallback } from "./fork.js";
|
|
6
|
+
|
|
7
|
+
const debug = Debug("sshClientWrapper:debug:sshExec");
|
|
8
|
+
const debugVerbose = Debug("sshClientWrapper:verbose:sshExec");
|
|
9
|
+
const debugSendMasterPty = Debug("sshClientWrapper:insecure:sshExec:masterPty");
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @typedef Integer {number} - integer number
|
|
@@ -21,7 +22,7 @@ const { fork, createMasterPty, sshLoginCallback } = require("./fork.js");
|
|
|
21
22
|
* @param {string} prependCmd - command string which will be executed before executing actual command
|
|
22
23
|
* @returns {Integer} - return value of cmd
|
|
23
24
|
*/
|
|
24
|
-
async function sshExec(hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd) {
|
|
25
|
+
export async function sshExec(hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd) {
|
|
25
26
|
debug("sshExec called");
|
|
26
27
|
await connect(hostInfo);
|
|
27
28
|
debug("exec", cmd, "on remote server");
|
|
@@ -57,7 +58,7 @@ async function sshExec(hostInfo, cmd, timeout, outputCallback, rcfile, prependCm
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
async function expect(hostInfo, cmd, expects, timeout) {
|
|
61
|
+
export async function expect(hostInfo, cmd, expects, timeout) {
|
|
61
62
|
debug("expect called");
|
|
62
63
|
await connect(hostInfo);
|
|
63
64
|
debugVerbose("exec", cmd, "on remote server with", expects);
|
|
@@ -95,7 +96,7 @@ async function expect(hostInfo, cmd, expects, timeout) {
|
|
|
95
96
|
* @param {object} hostInfo - hostinfo object
|
|
96
97
|
* @param {Integer} timeout - timeout in secconds
|
|
97
98
|
*/
|
|
98
|
-
async function canConnect(hostInfo, timeout) {
|
|
99
|
+
export async function canConnect(hostInfo, timeout) {
|
|
99
100
|
debug("canConnect called");
|
|
100
101
|
sanityCheck(hostInfo);
|
|
101
102
|
|
|
@@ -143,7 +144,7 @@ async function existsMaster(hostInfo, argTimeout) {
|
|
|
143
144
|
* create master ssh connection
|
|
144
145
|
* @param {object} hostInfo - hostinfo object
|
|
145
146
|
*/
|
|
146
|
-
async function connect(hostInfo, timeout = 60) {
|
|
147
|
+
export async function connect(hostInfo, timeout = 60) {
|
|
147
148
|
debug("connect called");
|
|
148
149
|
|
|
149
150
|
if (await existsMaster(hostInfo, timeout)) {
|
|
@@ -228,7 +229,7 @@ async function connect(hostInfo, timeout = 60) {
|
|
|
228
229
|
* remove master session
|
|
229
230
|
* @param {object} hostInfo - hostinfo object
|
|
230
231
|
*/
|
|
231
|
-
async function disconnect(hostInfo) {
|
|
232
|
+
export async function disconnect(hostInfo) {
|
|
232
233
|
debug("disconnect called");
|
|
233
234
|
|
|
234
235
|
if (await existsMaster(hostInfo)) {
|
|
@@ -271,7 +272,7 @@ async function disconnect(hostInfo) {
|
|
|
271
272
|
* @param {Integer} argDuration - time to wait before retring
|
|
272
273
|
* @returns {Integer} - return value of func
|
|
273
274
|
*/
|
|
274
|
-
async function retryWrapper(func, hostInfo, argMaxRetry, argDuration) {
|
|
275
|
+
export async function retryWrapper(func, hostInfo, argMaxRetry, argDuration) {
|
|
275
276
|
const maxRetry = argMaxRetry || hostInfo.maxRetry || 3;
|
|
276
277
|
const duration = argDuration || hostInfo.retryDuration || 1000;
|
|
277
278
|
if (maxRetry === 1) {
|
|
@@ -301,12 +302,3 @@ async function retryWrapper(func, hostInfo, argMaxRetry, argDuration) {
|
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
|
-
|
|
305
|
-
module.exports = {
|
|
306
|
-
sshExec,
|
|
307
|
-
canConnect,
|
|
308
|
-
connect,
|
|
309
|
-
disconnect,
|
|
310
|
-
expect,
|
|
311
|
-
retryWrapper
|
|
312
|
-
};
|
package/lib/util.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { EOL } from "os";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import Debug from "debug";
|
|
4
|
+
import { setTimeout as setTimeoutPromise } from "timers/promises";
|
|
5
|
+
import Ajv from "ajv";
|
|
6
|
+
import ajvKeywords from "ajv-keywords";
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
const rsyncCmd = "rsync";
|
|
9
|
-
const acceptableRsyncRetrunCodes = [10, 11, 12, 13, 14];
|
|
8
|
+
const debug = Debug("sshClientWrapper:debug:util");
|
|
10
9
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
10
|
+
export const sshCmd = "ssh";
|
|
11
|
+
export const rsyncCmd = "rsync";
|
|
12
|
+
export const acceptableRsyncRetrunCodes = [10, 11, 12, 13, 14];
|
|
13
|
+
|
|
14
|
+
export const rePwPrompt = /password:/;
|
|
15
|
+
export const rePhPrompt = /Enter passphrase for key/;
|
|
16
|
+
export const reNewHostPrompt = /Are you sure you want to continue connecting/;
|
|
17
|
+
export const reRemoteHostIdentificationHasChanged = /WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! /;
|
|
16
18
|
|
|
17
19
|
const ajv = new Ajv({
|
|
18
20
|
allErrors: true,
|
|
@@ -24,7 +26,7 @@ const ajv = new Ajv({
|
|
|
24
26
|
error: debug
|
|
25
27
|
}
|
|
26
28
|
});
|
|
27
|
-
|
|
29
|
+
ajvKeywords(ajv, "transform");
|
|
28
30
|
|
|
29
31
|
//never validate password and passphrase value to avoid security insident
|
|
30
32
|
const hostInfoSchema = {
|
|
@@ -70,7 +72,7 @@ const validate = ajv.compile(hostInfoSchema);
|
|
|
70
72
|
* @param {Function} func - function
|
|
71
73
|
* @param {data} data - binary data which can be converted to string
|
|
72
74
|
*/
|
|
73
|
-
const buff2String = (func, data)=>{
|
|
75
|
+
export const buff2String = (func, data)=>{
|
|
74
76
|
func(data.toString().replace(/\r\n/g, EOL));
|
|
75
77
|
};
|
|
76
78
|
|
|
@@ -78,7 +80,7 @@ const buff2String = (func, data)=>{
|
|
|
78
80
|
* check if given hostInfo object is OK
|
|
79
81
|
* @param {object} hostInfo - host information object defined in index.js
|
|
80
82
|
*/
|
|
81
|
-
const sanityCheck = (hostInfo)=>{
|
|
83
|
+
export const sanityCheck = (hostInfo)=>{
|
|
82
84
|
debug("sanityCheck called", hostInfo);
|
|
83
85
|
|
|
84
86
|
//keep password, passphrase, and masterPty before validate.
|
|
@@ -155,7 +157,7 @@ function getControlPersistDir(hostInfo) {
|
|
|
155
157
|
* @param {object} hostInfo - host information object defined in index.js
|
|
156
158
|
* @param {boolean} withoutDestination - return except for hostname
|
|
157
159
|
*/
|
|
158
|
-
const getSshOption = (hostInfo, withoutDestination)=>{
|
|
160
|
+
export const getSshOption = (hostInfo, withoutDestination)=>{
|
|
159
161
|
debug("getSshOption called");
|
|
160
162
|
const args = [];
|
|
161
163
|
|
|
@@ -217,7 +219,7 @@ const getSshOption = (hostInfo, withoutDestination)=>{
|
|
|
217
219
|
return args;
|
|
218
220
|
};
|
|
219
221
|
|
|
220
|
-
const isArrayOfString = (target)=>{
|
|
222
|
+
export const isArrayOfString = (target)=>{
|
|
221
223
|
if (!Array.isArray(target)) {
|
|
222
224
|
return false;
|
|
223
225
|
}
|
|
@@ -234,7 +236,7 @@ const isArrayOfString = (target)=>{
|
|
|
234
236
|
* @param {Function} logger - logging function
|
|
235
237
|
* @param {string} label - where this function is called
|
|
236
238
|
*/
|
|
237
|
-
const sendPty = async (pty, message, logger, label)=>{
|
|
239
|
+
export const sendPty = async (pty, message, logger, label)=>{
|
|
238
240
|
logger(`${label}: ${message}`);
|
|
239
241
|
pty.write(message);
|
|
240
242
|
};
|
|
@@ -246,7 +248,7 @@ const sendPty = async (pty, message, logger, label)=>{
|
|
|
246
248
|
* @param {Function} cb - call back function which will be called after timeout
|
|
247
249
|
* @returns {AbortController} - ac to stop watch dog timer
|
|
248
250
|
*/
|
|
249
|
-
const watchDogTimer = (timeout, label, cb)=>{
|
|
251
|
+
export const watchDogTimer = (timeout, label, cb)=>{
|
|
250
252
|
const ac = new AbortController();
|
|
251
253
|
const signal = ac.signal;
|
|
252
254
|
setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
|
|
@@ -263,19 +265,3 @@ const watchDogTimer = (timeout, label, cb)=>{
|
|
|
263
265
|
});
|
|
264
266
|
return ac;
|
|
265
267
|
};
|
|
266
|
-
|
|
267
|
-
module.exports = {
|
|
268
|
-
sshCmd,
|
|
269
|
-
rsyncCmd,
|
|
270
|
-
acceptableRsyncRetrunCodes,
|
|
271
|
-
rePwPrompt,
|
|
272
|
-
rePhPrompt,
|
|
273
|
-
reNewHostPrompt,
|
|
274
|
-
reRemoteHostIdentificationHasChanged,
|
|
275
|
-
buff2String,
|
|
276
|
-
getSshOption,
|
|
277
|
-
sanityCheck,
|
|
278
|
-
isArrayOfString,
|
|
279
|
-
sendPty,
|
|
280
|
-
watchDogTimer
|
|
281
|
-
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ssh-client-wrapper",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"description": "wrapper library for open ssh client and rsync",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ssh",
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"wrapper",
|
|
12
12
|
"rsync"
|
|
13
13
|
],
|
|
14
|
+
"type": "module",
|
|
14
15
|
"main": "lib/index.js",
|
|
15
16
|
"scripts": {
|
|
16
17
|
"mocha": "mocha --recursive test",
|
|
17
18
|
"lint": "eslint --fix ./",
|
|
18
|
-
"test": "
|
|
19
|
-
"coverage": "
|
|
20
|
-
"
|
|
21
|
-
"prepare": "husky install"
|
|
19
|
+
"test": "c8 --reporter=html --reporter=text mocha --recursive test",
|
|
20
|
+
"coverage": "c8 report --reporter=text-lcov > coverage-report.lcov",
|
|
21
|
+
"prepare": "husky"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
|
-
"node": ">=
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
25
|
},
|
|
26
26
|
"author": "Naoyuki Sogo <sogo@longtail-software.co.jp>",
|
|
27
27
|
"license": "MIT",
|
|
@@ -36,27 +36,30 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"ajv": "^8.17.1",
|
|
38
38
|
"ajv-keywords": "^5.1.0",
|
|
39
|
-
"debug": "^4.4.
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"node-pty": "^0.10.1",
|
|
43
|
-
"promise-retry": "^2.0.1"
|
|
39
|
+
"debug": "^4.4.1",
|
|
40
|
+
"glob": "^11.0.3",
|
|
41
|
+
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5"
|
|
44
42
|
},
|
|
45
43
|
"devDependencies": {
|
|
46
|
-
"@eslint/js": "^9.
|
|
47
|
-
"@
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"eslint-plugin
|
|
44
|
+
"@eslint/js": "^9.33.0",
|
|
45
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
46
|
+
"@semantic-release/git": "^10.0.1",
|
|
47
|
+
"@semantic-release/github": "^11.0.4",
|
|
48
|
+
"@semantic-release/npm": "^12.0.2",
|
|
49
|
+
"@stylistic/eslint-plugin": "^5.2.3",
|
|
50
|
+
"c8": "^10.1.3",
|
|
51
|
+
"chai": "^5.3.1",
|
|
52
|
+
"chai-as-promised": "^8.0.1",
|
|
53
|
+
"eslint-plugin-chai-friendly": "^1.1.0",
|
|
54
|
+
"eslint-plugin-jsdoc": "^54.1.0",
|
|
52
55
|
"eslint-plugin-node": "^11.1.0",
|
|
53
|
-
"fs-extra": "^11.
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"sinon": "^
|
|
60
|
-
"sinon-chai": "^
|
|
56
|
+
"fs-extra": "^11.3.1",
|
|
57
|
+
"globals": "^16.3.0",
|
|
58
|
+
"husky": "^9.1.7",
|
|
59
|
+
"lint-staged": "^16.1.5",
|
|
60
|
+
"mocha": "^11.7.1",
|
|
61
|
+
"semantic-release": "^24.2.7",
|
|
62
|
+
"sinon": "^21.0.0",
|
|
63
|
+
"sinon-chai": "^4.0.0"
|
|
61
64
|
}
|
|
62
65
|
}
|
package/test/getSshOption.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"
|
|
2
|
-
process.on("unhandledRejection", console.dir); //eslint-disable-line no-console
|
|
1
|
+
process.on("unhandledRejection", console.dir);
|
|
3
2
|
|
|
4
3
|
//setup test framework
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import * as chai from "chai";
|
|
5
|
+
import { expect } from "chai";
|
|
6
|
+
import chaiAsPromised from "chai-as-promised";
|
|
7
|
+
chai.use(chaiAsPromised);
|
|
8
8
|
|
|
9
9
|
//testee
|
|
10
|
-
|
|
10
|
+
import { getSshOption } from "../lib/util.js";
|
|
11
11
|
|
|
12
12
|
describe("test for getSshOption", ()=>{
|
|
13
13
|
const defaultValues = {
|
package/test/interface.js
CHANGED
|
@@ -1,51 +1,53 @@
|
|
|
1
|
-
"
|
|
2
|
-
process.on("unhandledRejection", console.dir); //eslint-disable-line no-console
|
|
1
|
+
process.on("unhandledRejection", console.dir);
|
|
3
2
|
Error.traceLimit = 100000;
|
|
4
3
|
|
|
5
4
|
//setup test framework
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import * as chai from "chai";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import sinon from "sinon";
|
|
8
|
+
import sinonChai from "sinon-chai";
|
|
9
|
+
import chaiAsPromised from "chai-as-promised";
|
|
10
|
+
chai.use(sinonChai);
|
|
11
|
+
chai.use(chaiAsPromised);
|
|
13
12
|
|
|
14
13
|
//testee
|
|
15
|
-
|
|
14
|
+
import SshClientWrapper from "../lib/index.js";
|
|
16
15
|
|
|
17
16
|
const sshExec = sinon.stub();
|
|
18
17
|
const canConnect = sinon.stub();
|
|
19
18
|
const disconnect = sinon.stub();
|
|
19
|
+
const expectStub = sinon.stub();
|
|
20
20
|
const send = sinon.stub();
|
|
21
21
|
const recv = sinon.stub();
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const deps = {
|
|
24
|
+
sshExec,
|
|
25
|
+
canConnect,
|
|
26
|
+
disconnect,
|
|
27
|
+
expect: expectStub,
|
|
28
|
+
send,
|
|
29
|
+
recv
|
|
30
|
+
};
|
|
27
31
|
|
|
28
32
|
describe("test for interface", ()=>{
|
|
29
33
|
describe("test for constructor", ()=>{
|
|
30
34
|
it("should throw error when instanciate with non-empty string host", ()=>{
|
|
31
35
|
expect(()=>{
|
|
32
|
-
|
|
33
|
-
new SshClientWrapper({});
|
|
36
|
+
new SshClientWrapper({}, deps);
|
|
34
37
|
}).to.throw("host is required");
|
|
35
38
|
expect(()=>{
|
|
36
|
-
|
|
37
|
-
new SshClientWrapper({ host: "" });
|
|
39
|
+
new SshClientWrapper({ host: "" }, deps);
|
|
38
40
|
}).to.throw("empty host is not allowed");
|
|
39
41
|
});
|
|
40
42
|
it("should successfully instanciate only with host prop", ()=>{
|
|
41
|
-
expect(new SshClientWrapper({ host: "hoge" })).to.have.property("exec");
|
|
42
|
-
expect(new SshClientWrapper({ host: "hoge" })).to.have.property("execAndGetOutput");
|
|
43
|
-
expect(new SshClientWrapper({ host: "hoge" })).to.have.property("ls");
|
|
44
|
-
expect(new SshClientWrapper({ host: "hoge" })).to.have.property("expect");
|
|
45
|
-
expect(new SshClientWrapper({ host: "hoge" })).to.have.property("send");
|
|
46
|
-
expect(new SshClientWrapper({ host: "hoge" })).to.have.property("recv");
|
|
47
|
-
expect(new SshClientWrapper({ host: "hoge" })).to.have.property("canConnect");
|
|
48
|
-
expect(new SshClientWrapper({ host: "hoge" })).to.have.property("disconnect");
|
|
43
|
+
expect(new SshClientWrapper({ host: "hoge" }, deps)).to.have.property("exec");
|
|
44
|
+
expect(new SshClientWrapper({ host: "hoge" }, deps)).to.have.property("execAndGetOutput");
|
|
45
|
+
expect(new SshClientWrapper({ host: "hoge" }, deps)).to.have.property("ls");
|
|
46
|
+
expect(new SshClientWrapper({ host: "hoge" }, deps)).to.have.property("expect");
|
|
47
|
+
expect(new SshClientWrapper({ host: "hoge" }, deps)).to.have.property("send");
|
|
48
|
+
expect(new SshClientWrapper({ host: "hoge" }, deps)).to.have.property("recv");
|
|
49
|
+
expect(new SshClientWrapper({ host: "hoge" }, deps)).to.have.property("canConnect");
|
|
50
|
+
expect(new SshClientWrapper({ host: "hoge" }, deps)).to.have.property("disconnect");
|
|
49
51
|
});
|
|
50
52
|
});
|
|
51
53
|
describe("test for public method", ()=>{
|
|
@@ -58,12 +60,13 @@ describe("test for interface", ()=>{
|
|
|
58
60
|
};
|
|
59
61
|
const hostInfo = { ...hostInfoOrg, masterPty: null, rsyncVersion: null };
|
|
60
62
|
beforeEach(()=>{
|
|
61
|
-
ssh = new SshClientWrapper(hostInfoOrg);
|
|
63
|
+
ssh = new SshClientWrapper(hostInfoOrg, deps);
|
|
62
64
|
sshExec.reset();
|
|
63
65
|
canConnect.reset();
|
|
64
66
|
disconnect.reset();
|
|
65
67
|
send.reset();
|
|
66
68
|
recv.reset();
|
|
69
|
+
expectStub.reset();
|
|
67
70
|
});
|
|
68
71
|
describe("test for exec", ()=>{
|
|
69
72
|
it("should call sshExec with cmd", ()=>{
|
package/test/rsyncExec.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const exec = util.promisify(
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import util from "util";
|
|
4
|
+
import { exec as exec_ } from "node:child_process";
|
|
5
|
+
const exec = util.promisify(exec_);
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* to enable debug log dynamically
|
|
@@ -14,22 +14,22 @@ const exec = util.promisify(require("node:child_process").exec);
|
|
|
14
14
|
* debug.disable();
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
process.on("unhandledRejection", console.dir);
|
|
17
|
+
process.on("unhandledRejection", console.dir);
|
|
18
18
|
Error.traceLimit = 100000;
|
|
19
19
|
|
|
20
20
|
//setup test framework
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
chai.use(
|
|
21
|
+
import * as chai from "chai";
|
|
22
|
+
import { expect } from "chai";
|
|
23
|
+
import chaiAsPromised from "chai-as-promised";
|
|
24
|
+
chai.use(chaiAsPromised);
|
|
25
25
|
|
|
26
26
|
//testee
|
|
27
|
-
|
|
27
|
+
import { checkRsyncVersion, send, recv } from "../lib/rsyncExec.js";
|
|
28
28
|
|
|
29
29
|
//test helpers
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
import { sshExec, disconnect } from "../lib/sshExec.js";
|
|
31
|
+
import hostInfo from "./testUtil/hostInfo.js";
|
|
32
|
+
import {
|
|
33
33
|
clearLocalTestFiles,
|
|
34
34
|
clearRemoteTestFiles,
|
|
35
35
|
createLocalFiles,
|
|
@@ -40,7 +40,7 @@ const {
|
|
|
40
40
|
remoteRoot,
|
|
41
41
|
remoteEmptyDir,
|
|
42
42
|
remoteFiles
|
|
43
|
-
}
|
|
43
|
+
} from "./testUtil/testFiles.js";
|
|
44
44
|
|
|
45
45
|
const formatLsOutput = (array)=>{
|
|
46
46
|
const rt = [];
|
|
@@ -56,13 +56,13 @@ const formatLsOutput = (array)=>{
|
|
|
56
56
|
describe("test checkRsyncVersion", async ()=>{
|
|
57
57
|
const { major, minor, patch } = await checkRsyncVersion(1);
|
|
58
58
|
const versionString = `${major}.${minor}.${patch}`;
|
|
59
|
-
console.log(`==== test with rsync version ${versionString} ====`);
|
|
59
|
+
console.log(`==== test with rsync version ${versionString} ====`);
|
|
60
60
|
const { stdout } = await exec("rsync --version |grep 'version'");
|
|
61
61
|
expect(stdout).to.match(new RegExp(versionString));
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
describe("test rsync exec", function () {
|
|
65
|
-
this.timeout(10000)
|
|
65
|
+
this.timeout(10000);
|
|
66
66
|
const rt = [];
|
|
67
67
|
const output = (data)=>{
|
|
68
68
|
rt.push(data);
|
package/test/sanityCheck.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"
|
|
2
|
-
process.on("unhandledRejection", console.dir); //eslint-disable-line no-console
|
|
1
|
+
process.on("unhandledRejection", console.dir);
|
|
3
2
|
|
|
4
3
|
//setup test framework
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import * as chai from "chai";
|
|
5
|
+
import { expect } from "chai";
|
|
6
|
+
import chaiAsPromised from "chai-as-promised";
|
|
7
|
+
chai.use(chaiAsPromised);
|
|
8
8
|
|
|
9
9
|
//testee
|
|
10
|
-
|
|
10
|
+
import { sanityCheck } from "../lib/util.js";
|
|
11
11
|
|
|
12
12
|
describe("test for sanityCheck", ()=>{
|
|
13
13
|
const host = "testHostName";
|