ssh-client-wrapper 2.11.0 → 2.13.0
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 +1 -1
- package/.github/workflows/release.yml +29 -0
- package/.github/workflows/run_test.yml +5 -7
- package/.husky/_pre-commit +1 -0
- package/.husky/pre-commit +0 -3
- package/.releaserc.json +16 -0
- package/CHANGELOG.md +13 -0
- package/README.md +99 -0
- package/eslint.config.mjs +181 -0
- package/lib/fork.js +31 -23
- package/lib/index.js +81 -32
- package/lib/rsyncExec.js +27 -31
- package/lib/sshExec.js +76 -74
- package/lib/util.js +41 -51
- package/package.json +31 -30
- package/test/getSshOption.js +12 -12
- package/test/interface.js +77 -74
- package/test/rsyncExec.js +86 -86
- package/test/sanityCheck.js +19 -19
- package/test/sshExec.js +47 -75
- package/test/testUtil/hostInfo.js +1 -2
- package/test/testUtil/testFiles.js +33 -22
- package/.eslintrc.json +0 -34
- package/.github/workflows/npm_publish.yml +0 -22
- package/test/.eslintrc.json +0 -28
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Release and Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
uses: ./.github/workflows/run_test.yml
|
|
11
|
+
|
|
12
|
+
release:
|
|
13
|
+
needs: test
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
issues: write
|
|
18
|
+
pull-requests: write
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 20
|
|
24
|
+
- run: npm ci
|
|
25
|
+
- name: Semantic Release
|
|
26
|
+
uses: cycjimmy/semantic-release-action@v4
|
|
27
|
+
env:
|
|
28
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
29
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
name: run UT
|
|
2
2
|
on:
|
|
3
|
-
|
|
4
|
-
tags-ignore:
|
|
5
|
-
- 'v*'
|
|
3
|
+
pull_request:
|
|
6
4
|
branches:
|
|
7
|
-
-
|
|
8
|
-
- '!main'
|
|
5
|
+
- main
|
|
9
6
|
workflow_call:
|
|
10
7
|
concurrency:
|
|
11
8
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
@@ -21,7 +18,6 @@ jobs:
|
|
|
21
18
|
TEST_USER: testuser
|
|
22
19
|
TEST_PW: passw0rd
|
|
23
20
|
TEST_PORT: 10022
|
|
24
|
-
DEBUG: 'ssh*'
|
|
25
21
|
runs-on: ubuntu-latest
|
|
26
22
|
strategy:
|
|
27
23
|
matrix:
|
|
@@ -35,7 +31,9 @@ jobs:
|
|
|
35
31
|
- run: npm ci
|
|
36
32
|
- run: npm run test
|
|
37
33
|
- run: npm run coverage
|
|
38
|
-
|
|
34
|
+
if: matrix.node-version == '20.x'
|
|
35
|
+
- uses: actions/upload-artifact@v4
|
|
36
|
+
if: matrix.node-version == '20.x'
|
|
39
37
|
with:
|
|
40
38
|
name: code-coverage-report
|
|
41
39
|
path: coverage-report.lcov
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx lint-staged
|
package/.husky/pre-commit
CHANGED
package/.releaserc.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branches": ["main"],
|
|
3
|
+
"plugins": [
|
|
4
|
+
"@semantic-release/commit-analyzer",
|
|
5
|
+
"@semantic-release/release-notes-generator",
|
|
6
|
+
"@semantic-release/changelog",
|
|
7
|
+
["@semantic-release/npm", {
|
|
8
|
+
"npmPublish": true
|
|
9
|
+
}],
|
|
10
|
+
["@semantic-release/git", {
|
|
11
|
+
"assets": ["package.json", "package-lock.json", "CHANGELOG.md"],
|
|
12
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\\n\\n${nextRelease.notes}"
|
|
13
|
+
}],
|
|
14
|
+
"@semantic-release/github"
|
|
15
|
+
]
|
|
16
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# [2.13.0](https://github.com/so5/ssh-client-wrapper/compare/v2.12.4...v2.13.0) (2025-08-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* upgrade debug from 4.4.0 to 4.4.1 ([35f966a](https://github.com/so5/ssh-client-wrapper/commit/35f966a7212d6bb9677964688bb94ae06f869ada))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Improve DX and automate releases ([805f2ea](https://github.com/so5/ssh-client-wrapper/commit/805f2ea7756f6ad61238a4a21d86575a17e748d9))
|
|
12
|
+
* Rewrite in ESM ([c5a95c1](https://github.com/so5/ssh-client-wrapper/commit/c5a95c18401e7b373249d3707609f2a9576f7400))
|
|
13
|
+
* Rewrite in ESM ([71ed6f5](https://github.com/so5/ssh-client-wrapper/commit/71ed6f56af43cac1471cbf4a4ed3016f7e55ab4d))
|
package/README.md
CHANGED
|
@@ -4,3 +4,102 @@
|
|
|
4
4
|
[](https://codeclimate.com/github/so5/ssh-client-wrapper/maintainability)
|
|
5
5
|
# ssh-client-wrapper
|
|
6
6
|
open ssh client wrapper library for nodejs
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
```bash
|
|
10
|
+
npm install ssh-client-wrapper
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
Here is a simple example of how to use `ssh-client-wrapper` to execute a command on a remote server.
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import SshClientWrapper from 'ssh-client-wrapper';
|
|
18
|
+
|
|
19
|
+
const hostInfo = {
|
|
20
|
+
host: 'remote.server.com',
|
|
21
|
+
port: 22,
|
|
22
|
+
user: 'username',
|
|
23
|
+
password: 'password'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const ssh = new SshClientWrapper(hostInfo);
|
|
27
|
+
|
|
28
|
+
async function run() {
|
|
29
|
+
try {
|
|
30
|
+
await ssh.canConnect();
|
|
31
|
+
console.log('Connection successful!');
|
|
32
|
+
|
|
33
|
+
const { output, rt } = await ssh.execAndGetOutput('ls -l /home');
|
|
34
|
+
if (rt === 0) {
|
|
35
|
+
console.log('Directory listing:');
|
|
36
|
+
output.forEach(line => console.log(line));
|
|
37
|
+
} else {
|
|
38
|
+
console.error('Error executing command');
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error('Connection or command failed:', err);
|
|
42
|
+
} finally {
|
|
43
|
+
ssh.disconnect();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
run();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API Guide
|
|
51
|
+
|
|
52
|
+
### `new SshClientWrapper(hostInfo)`
|
|
53
|
+
Creates a new SSH client instance.
|
|
54
|
+
|
|
55
|
+
* `hostInfo` (Object): Connection details for the remote host.
|
|
56
|
+
* `host` (string): The hostname or IP address of the server.
|
|
57
|
+
* `user` (string): The username for authentication.
|
|
58
|
+
* `port` (number, optional): The port number. Defaults to 22.
|
|
59
|
+
* `password` (string | Function, optional): The password for authentication, or a function that returns the password.
|
|
60
|
+
* `keyFile` (string, optional): The path to the private key file for key-based authentication.
|
|
61
|
+
* `passphrase` (string | Function, optional): The passphrase for the private key, or a function that returns it.
|
|
62
|
+
* `noStrictHostKeyChecking` (boolean, optional): If `true`, bypasses strict host key checking.
|
|
63
|
+
* And more... see `lib/index.js` for all available options.
|
|
64
|
+
|
|
65
|
+
### `.exec(cmd, [timeout], [outputCallback], [rcfile], [prependCmd])`
|
|
66
|
+
Executes a command on the remote host.
|
|
67
|
+
|
|
68
|
+
* Returns: `Promise<number>` - The return code of the command.
|
|
69
|
+
|
|
70
|
+
### `.execAndGetOutput(cmd, [timeout], [rcfile], [prependCmd])`
|
|
71
|
+
Executes a command and returns its standard output.
|
|
72
|
+
|
|
73
|
+
* Returns: `Promise<{output: string[], rt: number}>` - An object containing the output as an array of strings and the return code.
|
|
74
|
+
|
|
75
|
+
### `.ls(target, [lsOpt], [timeout])`
|
|
76
|
+
Executes the `ls` command on the remote host.
|
|
77
|
+
|
|
78
|
+
* Returns: `Promise<string[]>` - The output of the `ls` command as an array of strings.
|
|
79
|
+
|
|
80
|
+
### `.expect(cmd, expects, [timeout])`
|
|
81
|
+
Executes a command and interacts with it, similar to the `expect` tool.
|
|
82
|
+
|
|
83
|
+
* Returns: `Promise<number>` - The return code of the command.
|
|
84
|
+
|
|
85
|
+
### `.send(src, dst, [opt], [timeout])`
|
|
86
|
+
Uploads files or directories to the remote host using `rsync`.
|
|
87
|
+
|
|
88
|
+
* `src` (string[]): An array of local paths to send.
|
|
89
|
+
* `dst` (string): The remote destination path.
|
|
90
|
+
* Returns: `Promise<void>`
|
|
91
|
+
|
|
92
|
+
### `.recv(src, dst, [opt], [timeout])`
|
|
93
|
+
Downloads files or directories from the remote host using `rsync`.
|
|
94
|
+
|
|
95
|
+
* `src` (string[]): An array of remote paths to receive.
|
|
96
|
+
* `dst` (string): The local destination path.
|
|
97
|
+
* Returns: `Promise<void>`
|
|
98
|
+
|
|
99
|
+
### `.canConnect([timeout])`
|
|
100
|
+
Checks if a connection to the remote host can be established.
|
|
101
|
+
|
|
102
|
+
* Returns: `Promise<boolean>` - Resolves with `true` on success.
|
|
103
|
+
|
|
104
|
+
### `.disconnect()`
|
|
105
|
+
Closes the master SSH connection to the remote host.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import stylistic from "@stylistic/eslint-plugin";
|
|
4
|
+
import jsdoc from "eslint-plugin-jsdoc";
|
|
5
|
+
import eslintPluginChaiFriendly from "eslint-plugin-chai-friendly";
|
|
6
|
+
import eslintPluginChaiExpect from "eslint-plugin-chai-expect";
|
|
7
|
+
|
|
8
|
+
const styleRules = {
|
|
9
|
+
"arrow-body-style": ["error", "always"],
|
|
10
|
+
"@stylistic/arrow-spacing": [
|
|
11
|
+
"error",
|
|
12
|
+
{
|
|
13
|
+
after: false,
|
|
14
|
+
before: false
|
|
15
|
+
}],
|
|
16
|
+
"@stylistic/comma-dangle": [
|
|
17
|
+
"error",
|
|
18
|
+
"never"
|
|
19
|
+
],
|
|
20
|
+
"@stylistic/spaced-comment": [
|
|
21
|
+
"error",
|
|
22
|
+
"never"
|
|
23
|
+
],
|
|
24
|
+
"@stylistic/lines-around-comment": [
|
|
25
|
+
"error",
|
|
26
|
+
{
|
|
27
|
+
beforeBlockComment: true,
|
|
28
|
+
afterBlockComment: false,
|
|
29
|
+
beforeLineComment: false,
|
|
30
|
+
afterLineComment: false,
|
|
31
|
+
allowBlockStart: true
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"@stylistic/lines-between-class-members": [
|
|
35
|
+
"error",
|
|
36
|
+
"always",
|
|
37
|
+
{
|
|
38
|
+
exceptAfterSingleLine: true
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"@stylistic/newline-per-chained-call": [
|
|
42
|
+
"error",
|
|
43
|
+
{
|
|
44
|
+
ignoreChainWithDepth: 2
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"@stylistic/padded-blocks": [
|
|
48
|
+
"error",
|
|
49
|
+
"never"
|
|
50
|
+
],
|
|
51
|
+
"@stylistic/brace-style": [
|
|
52
|
+
"error",
|
|
53
|
+
"1tbs",
|
|
54
|
+
{
|
|
55
|
+
allowSingleLine: true
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"@stylistic/padding-line-between-statements": [
|
|
59
|
+
"error",
|
|
60
|
+
{
|
|
61
|
+
blankLine: "any",
|
|
62
|
+
prev: [
|
|
63
|
+
"const",
|
|
64
|
+
"let",
|
|
65
|
+
"var"
|
|
66
|
+
],
|
|
67
|
+
next: "*"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
blankLine: "always",
|
|
71
|
+
prev: "*",
|
|
72
|
+
next: [
|
|
73
|
+
"class",
|
|
74
|
+
"do",
|
|
75
|
+
"for",
|
|
76
|
+
"function",
|
|
77
|
+
"switch",
|
|
78
|
+
"try",
|
|
79
|
+
"while"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
blankLine: "any",
|
|
84
|
+
prev: [
|
|
85
|
+
"const",
|
|
86
|
+
"let",
|
|
87
|
+
"var",
|
|
88
|
+
"for",
|
|
89
|
+
"while",
|
|
90
|
+
"do",
|
|
91
|
+
"block-like",
|
|
92
|
+
"multiline-block-like"
|
|
93
|
+
],
|
|
94
|
+
next: [
|
|
95
|
+
"block-like",
|
|
96
|
+
"do",
|
|
97
|
+
"for",
|
|
98
|
+
"multiline-block-like",
|
|
99
|
+
"switch",
|
|
100
|
+
"try",
|
|
101
|
+
"while"
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"@stylistic/quotes": [
|
|
106
|
+
"error",
|
|
107
|
+
"double"
|
|
108
|
+
],
|
|
109
|
+
"@stylistic/semi": [
|
|
110
|
+
"error",
|
|
111
|
+
"always"
|
|
112
|
+
]
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const jsdocRules = {
|
|
116
|
+
"jsdoc/require-jsdoc": ["warn", { enableFixer: false }],
|
|
117
|
+
"jsdoc/require-param": ["warn", { enableFixer: false }],
|
|
118
|
+
"jsdoc/require-param-description": "warn",
|
|
119
|
+
"jsdoc/require-param-name": "warn",
|
|
120
|
+
"jsdoc/require-param-type": "warn",
|
|
121
|
+
"jsdoc/require-hyphen-before-param-description": [
|
|
122
|
+
"warn",
|
|
123
|
+
"always"
|
|
124
|
+
],
|
|
125
|
+
"jsdoc/require-returns": "warn",
|
|
126
|
+
"jsdoc/require-returns-check": "error"
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default [
|
|
130
|
+
// Global configuration
|
|
131
|
+
{
|
|
132
|
+
ignores: ["coverage-report.lcov", "node_modules/"]
|
|
133
|
+
},
|
|
134
|
+
js.configs.recommended,
|
|
135
|
+
{
|
|
136
|
+
plugins: {
|
|
137
|
+
"@stylistic": stylistic
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
// Lib configuration
|
|
141
|
+
{
|
|
142
|
+
files: ["lib/**/*.js"],
|
|
143
|
+
plugins: {
|
|
144
|
+
jsdoc
|
|
145
|
+
},
|
|
146
|
+
languageOptions: {
|
|
147
|
+
ecmaVersion: "latest",
|
|
148
|
+
sourceType: "module",
|
|
149
|
+
globals: {
|
|
150
|
+
...globals.es2021,
|
|
151
|
+
...globals.node
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
rules: {
|
|
155
|
+
...styleRules,
|
|
156
|
+
...jsdocRules
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
// Test configuration
|
|
160
|
+
{
|
|
161
|
+
files: ["test/**/*.js"],
|
|
162
|
+
plugins: {
|
|
163
|
+
"chai-friendly": eslintPluginChaiFriendly,
|
|
164
|
+
"chai-expect": eslintPluginChaiExpect
|
|
165
|
+
},
|
|
166
|
+
languageOptions: {
|
|
167
|
+
ecmaVersion: "latest",
|
|
168
|
+
sourceType: "module",
|
|
169
|
+
globals: {
|
|
170
|
+
...globals.es2021,
|
|
171
|
+
...globals.node,
|
|
172
|
+
...globals.mocha
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
rules: {
|
|
176
|
+
...styleRules,
|
|
177
|
+
"no-unused-expressions": "off",
|
|
178
|
+
"chai-friendly/no-unused-expressions": "error"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
];
|
package/lib/fork.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const debugVerbose = require("debug")("sshClientWrapper:verbose:fork");
|
|
4
|
-
const debugSendPty = require("debug")("sshClientWrapperInsecure:fork_sendpty");
|
|
5
|
-
const { spawn } = require("node-pty");
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
import { spawn } from "node-pty-prebuilt-multiarch";
|
|
6
3
|
|
|
7
|
-
|
|
4
|
+
import { rePwPrompt, rePhPrompt, reNewHostPrompt, reRemoteHostIdentificationHasChanged, buff2String, sendPty, watchDogTimer } from "./util.js";
|
|
5
|
+
|
|
6
|
+
const debug = Debug("sshClientWrapper:debug:fork");
|
|
7
|
+
const debugVerbose = Debug("sshClientWrapper:verbose:fork");
|
|
8
|
+
const debugSendPty = Debug("sshClientWrapper:insecure:fork");
|
|
8
9
|
|
|
9
10
|
const nodePtyOpt = {
|
|
10
11
|
shell: process.platform === "win32" ? process.env.ComSpec : process.env.SHELL,
|
|
11
12
|
windowsHide: true
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
function sshLoginCallback
|
|
15
|
-
return new Promise((resolve, reject)
|
|
15
|
+
export function sshLoginCallback(output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
16
|
+
return new Promise((resolve, reject)=>{
|
|
16
17
|
if (reRemoteHostIdentificationHasChanged.test(output)) {
|
|
17
18
|
debug("REMOTE HOST IDENTIFICATION HAS CHANGED");
|
|
18
19
|
const rt = /known_hosts:(\d+)/.exec(output);
|
|
@@ -38,10 +39,10 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
|
38
39
|
if (typeof pw === "function") {
|
|
39
40
|
debug("call password callback function");
|
|
40
41
|
pw()
|
|
41
|
-
.then((v)
|
|
42
|
+
.then((v)=>{
|
|
42
43
|
sendPty(ptyProcess, `${v}\n`, logger, 30);
|
|
43
44
|
})
|
|
44
|
-
.catch((e)
|
|
45
|
+
.catch((e)=>{
|
|
45
46
|
reject(e);
|
|
46
47
|
});
|
|
47
48
|
resolve();
|
|
@@ -56,10 +57,10 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
|
56
57
|
if (typeof ph === "function") {
|
|
57
58
|
debug("call passphrase callback function");
|
|
58
59
|
ph()
|
|
59
|
-
.then((v)
|
|
60
|
+
.then((v)=>{
|
|
60
61
|
sendPty(ptyProcess, `${v}\n`, logger, 45);
|
|
61
62
|
})
|
|
62
|
-
.catch((e)
|
|
63
|
+
.catch((e)=>{
|
|
63
64
|
reject(e);
|
|
64
65
|
});
|
|
65
66
|
}
|
|
@@ -70,7 +71,7 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
|
70
71
|
/**
|
|
71
72
|
* create pty for master ssh session and store to hostInfo.masterPty
|
|
72
73
|
*/
|
|
73
|
-
function createMasterPty
|
|
74
|
+
export function createMasterPty(hostInfo) {
|
|
74
75
|
debug("fork pty for master connection");
|
|
75
76
|
|
|
76
77
|
if (hostInfo.masterPty) {
|
|
@@ -81,13 +82,13 @@ function createMasterPty (hostInfo) {
|
|
|
81
82
|
hostInfo.masterPty = ptyProcess;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
async function fork
|
|
85
|
-
return new Promise((resolve, reject)
|
|
85
|
+
export async function fork(hostInfo, cmd, args, timeout, outputCallback, retryableExitCodes = [], expects = []) {
|
|
86
|
+
return new Promise((resolve, reject)=>{
|
|
86
87
|
debug(`cmd=${cmd}: args=${args}`);
|
|
87
88
|
const ptyProcess = spawn(cmd, args, nodePtyOpt);
|
|
88
89
|
let ac = null;
|
|
89
90
|
if (timeout > 0) {
|
|
90
|
-
ac = watchDogTimer(timeout, cmd, ()
|
|
91
|
+
ac = watchDogTimer(timeout, cmd, ()=>{
|
|
91
92
|
ptyProcess.kill();
|
|
92
93
|
const err = new Error(`watchdog timer expired ${timeout}`);
|
|
93
94
|
err.code = "TIMEEXPIRE";
|
|
@@ -97,7 +98,7 @@ async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExit
|
|
|
97
98
|
ptyProcess.onData(buff2String.bind(null, debugVerbose));
|
|
98
99
|
|
|
99
100
|
if (hostInfo !== null) {
|
|
100
|
-
ptyProcess.onData(
|
|
101
|
+
ptyProcess.onData((data)=>{
|
|
101
102
|
const output = data.toString();
|
|
102
103
|
sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase)
|
|
103
104
|
.catch(reject);
|
|
@@ -108,13 +109,25 @@ async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExit
|
|
|
108
109
|
err.retryable = true;
|
|
109
110
|
reject(err);
|
|
110
111
|
}
|
|
112
|
+
|
|
113
|
+
if (Array.isArray(expects) && expects.length > 0) {
|
|
114
|
+
const { reExpect, send, numKeep } = expects.shift();
|
|
115
|
+
if (reExpect.test(output)) {
|
|
116
|
+
sendPty(ptyProcess, `${send}\n`, debugSendPty, "userDefinedHandler").catch(reject);
|
|
117
|
+
if (numKeep > 0) {
|
|
118
|
+
expects.unshift({ reExpect, send, numKeep: numKeep - 1 });
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
expects.unshift({ reExpect, send, numKeep });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
111
124
|
});
|
|
112
125
|
}
|
|
113
126
|
|
|
114
127
|
if (typeof outputCallback === "function") {
|
|
115
128
|
ptyProcess.onData(buff2String.bind(null, outputCallback));
|
|
116
129
|
}
|
|
117
|
-
ptyProcess.onExit(({ exitCode, signal })
|
|
130
|
+
ptyProcess.onExit(({ exitCode, signal })=>{
|
|
118
131
|
debug("onExit", exitCode, signal);
|
|
119
132
|
|
|
120
133
|
if (ac !== null) {
|
|
@@ -138,8 +151,3 @@ async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExit
|
|
|
138
151
|
});
|
|
139
152
|
});
|
|
140
153
|
}
|
|
141
|
-
module.exports = {
|
|
142
|
-
fork,
|
|
143
|
-
createMasterPty,
|
|
144
|
-
sshLoginCallback
|
|
145
|
-
};
|