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.
@@ -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"
@@ -1,8 +1,8 @@
1
1
  name: report coverage data to code climate
2
2
  on:
3
3
  push:
4
- tags:
5
- - 'v*'
4
+ branches:
5
+ - main
6
6
  jobs:
7
7
  test:
8
8
  uses: ./.github/workflows/run_test.yml
@@ -12,7 +12,7 @@ jobs:
12
12
  name: coverage report
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v3
15
+ - uses: actions/checkout@v4
16
16
  - uses: actions/download-artifact@v4
17
17
  with:
18
18
  name: code-coverage-report
@@ -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
- push:
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 }}
@@ -24,19 +21,19 @@ jobs:
24
21
  runs-on: ubuntu-latest
25
22
  strategy:
26
23
  matrix:
27
- node-version: [18.x, 20.x, 16.x]
24
+ node-version: [16.x, 18.x, 20.x, 22.x]
28
25
  steps:
29
- - uses: actions/checkout@v3
26
+ - uses: actions/checkout@v4
30
27
  - name: Use Node.js ${{ matrix.node-version }}
31
- uses: actions/setup-node@v3
28
+ uses: actions/setup-node@v4
32
29
  with:
33
30
  node-version: ${{ matrix.node-version }}
34
31
  - run: npm ci
35
32
  - run: npm run test
36
33
  - run: npm run coverage
37
- if: matrix.node-version == '20.x'
34
+ if: matrix.node-version == '22.x'
38
35
  - uses: actions/upload-artifact@v4
39
- if: matrix.node-version == '20.x'
36
+ if: matrix.node-version == '22.x'
40
37
  with:
41
38
  name: code-coverage-report
42
39
  path: coverage-report.lcov
@@ -0,0 +1 @@
1
+ npx lint-staged
package/.husky/pre-commit CHANGED
@@ -1,4 +1 @@
1
- #!/usr/bin/env sh
2
- . "$(dirname -- "$0")/_/husky.sh"
3
-
4
1
  npx lint-staged
@@ -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,20 @@
1
+ ## [2.13.1](https://github.com/so5/ssh-client-wrapper/compare/v2.13.0...v2.13.1) (2025-08-20)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Update Code Climate workflow trigger ([7cf0a93](https://github.com/so5/ssh-client-wrapper/commit/7cf0a93913ceef762c267cb3a947c176479ce068))
7
+
8
+ # [2.13.0](https://github.com/so5/ssh-client-wrapper/compare/v2.12.4...v2.13.0) (2025-08-20)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * upgrade debug from 4.4.0 to 4.4.1 ([35f966a](https://github.com/so5/ssh-client-wrapper/commit/35f966a7212d6bb9677964688bb94ae06f869ada))
14
+
15
+
16
+ ### Features
17
+
18
+ * Improve DX and automate releases ([805f2ea](https://github.com/so5/ssh-client-wrapper/commit/805f2ea7756f6ad61238a4a21d86575a17e748d9))
19
+ * Rewrite in ESM ([c5a95c1](https://github.com/so5/ssh-client-wrapper/commit/c5a95c18401e7b373249d3707609f2a9576f7400))
20
+ * Rewrite in ESM ([71ed6f5](https://github.com/so5/ssh-client-wrapper/commit/71ed6f56af43cac1471cbf4a4ed3016f7e55ab4d))
package/README.md CHANGED
@@ -4,3 +4,102 @@
4
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/8f3c0ea00e755ae31081/maintainability)](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.
package/eslint.config.mjs CHANGED
@@ -1,39 +1,25 @@
1
- "use strict";
2
1
  import js from "@eslint/js";
3
2
  import globals from "globals";
4
3
  import stylistic from "@stylistic/eslint-plugin";
5
4
  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
- };
5
+ import eslintPluginChaiFriendly from "eslint-plugin-chai-friendly";
6
+ import eslintPluginChaiExpect from "eslint-plugin-chai-expect";
22
7
 
23
8
  const styleRules = {
9
+ "arrow-body-style": ["error", "always"],
24
10
  "@stylistic/arrow-spacing": [
25
11
  "error",
26
12
  {
27
13
  after: false,
28
- before: false,
14
+ before: false
29
15
  }],
30
16
  "@stylistic/comma-dangle": [
31
17
  "error",
32
- "never",
18
+ "never"
33
19
  ],
34
20
  "@stylistic/spaced-comment": [
35
21
  "error",
36
- "never",
22
+ "never"
37
23
  ],
38
24
  "@stylistic/lines-around-comment": [
39
25
  "error",
@@ -42,32 +28,32 @@ const styleRules = {
42
28
  afterBlockComment: false,
43
29
  beforeLineComment: false,
44
30
  afterLineComment: false,
45
- allowBlockStart: true,
46
- },
31
+ allowBlockStart: true
32
+ }
47
33
  ],
48
34
  "@stylistic/lines-between-class-members": [
49
35
  "error",
50
36
  "always",
51
37
  {
52
- exceptAfterSingleLine: true,
53
- },
38
+ exceptAfterSingleLine: true
39
+ }
54
40
  ],
55
41
  "@stylistic/newline-per-chained-call": [
56
42
  "error",
57
43
  {
58
- ignoreChainWithDepth: 2,
59
- },
44
+ ignoreChainWithDepth: 2
45
+ }
60
46
  ],
61
47
  "@stylistic/padded-blocks": [
62
48
  "error",
63
- "never",
49
+ "never"
64
50
  ],
65
51
  "@stylistic/brace-style": [
66
52
  "error",
67
53
  "1tbs",
68
54
  {
69
- allowSingleLine: true,
70
- },
55
+ allowSingleLine: true
56
+ }
71
57
  ],
72
58
  "@stylistic/padding-line-between-statements": [
73
59
  "error",
@@ -76,9 +62,9 @@ const styleRules = {
76
62
  prev: [
77
63
  "const",
78
64
  "let",
79
- "var",
65
+ "var"
80
66
  ],
81
- next: "*",
67
+ next: "*"
82
68
  },
83
69
  {
84
70
  blankLine: "always",
@@ -90,8 +76,8 @@ const styleRules = {
90
76
  "function",
91
77
  "switch",
92
78
  "try",
93
- "while",
94
- ],
79
+ "while"
80
+ ]
95
81
  },
96
82
  {
97
83
  blankLine: "any",
@@ -103,7 +89,7 @@ const styleRules = {
103
89
  "while",
104
90
  "do",
105
91
  "block-like",
106
- "multiline-block-like",
92
+ "multiline-block-like"
107
93
  ],
108
94
  next: [
109
95
  "block-like",
@@ -112,95 +98,84 @@ const styleRules = {
112
98
  "multiline-block-like",
113
99
  "switch",
114
100
  "try",
115
- "while",
116
- ],
117
- },
101
+ "while"
102
+ ]
103
+ }
118
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"
119
127
  };
120
128
 
121
129
  export default [
130
+ // Global configuration
131
+ {
132
+ ignores: ["coverage-report.lcov", "node_modules/"]
133
+ },
122
134
  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
135
  {
132
- ignores: ["node_modules/"],
136
+ plugins: {
137
+ "@stylistic": stylistic
138
+ }
133
139
  },
140
+ // Lib configuration
134
141
  {
135
- files: ["test/**/*.js"],
142
+ files: ["lib/**/*.js"],
136
143
  plugins: {
137
- chaiFriendly,
144
+ jsdoc
138
145
  },
139
146
  languageOptions: {
147
+ ecmaVersion: "latest",
148
+ sourceType: "module",
140
149
  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
+ ...globals.es2021,
151
+ ...globals.node
152
+ }
150
153
  },
154
+ rules: {
155
+ ...styleRules,
156
+ ...jsdocRules
157
+ }
151
158
  },
159
+ // Test configuration
152
160
  {
153
- files: ["**/*.js"],
161
+ files: ["test/**/*.js"],
154
162
  plugins: {
155
- node,
156
- "@stylistic": stylistic,
157
- jsdoc,
163
+ "chai-friendly": eslintPluginChaiFriendly,
164
+ "chai-expect": eslintPluginChaiExpect
158
165
  },
159
166
  languageOptions: {
167
+ ecmaVersion: "latest",
168
+ sourceType: "module",
160
169
  globals: {
161
- ...globals.nodeBuiltin,
170
+ ...globals.es2021,
162
171
  ...globals.node,
163
- },
164
- sourceType: "commonjs",
172
+ ...globals.mocha
173
+ }
165
174
  },
166
175
  rules: {
167
176
  ...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
- },
177
+ "no-unused-expressions": "off",
178
+ "chai-friendly/no-unused-expressions": "error"
179
+ }
180
+ }
206
181
  ];
package/lib/fork.js CHANGED
@@ -1,17 +1,18 @@
1
- "use strict";
2
- const debug = require("debug")("sshClientWrapper:debug:fork");
3
- const debugVerbose = require("debug")("sshClientWrapper:verbose:fork");
4
- const debugSendPty = require("debug")("sshClientWrapper:insecure:fork");
5
- const { spawn } = require("node-pty");
1
+ import Debug from "debug";
2
+ import { spawn } from "node-pty-prebuilt-multiarch";
6
3
 
7
- const { rePwPrompt, rePhPrompt, reNewHostPrompt, reRemoteHostIdentificationHasChanged, buff2String, sendPty, watchDogTimer } = require("./util.js");
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(output, ptyProcess, pw, ph, logger = debugSendPty) {
15
+ export function sshLoginCallback(output, ptyProcess, pw, ph, logger = debugSendPty) {
15
16
  return new Promise((resolve, reject)=>{
16
17
  if (reRemoteHostIdentificationHasChanged.test(output)) {
17
18
  debug("REMOTE HOST IDENTIFICATION HAS CHANGED");
@@ -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(hostInfo) {
74
+ export function createMasterPty(hostInfo) {
74
75
  debug("fork pty for master connection");
75
76
 
76
77
  if (hostInfo.masterPty) {
@@ -81,7 +82,7 @@ function createMasterPty(hostInfo) {
81
82
  hostInfo.masterPty = ptyProcess;
82
83
  }
83
84
 
84
- async function fork(hostInfo, cmd, args, timeout, outputCallback, retryableExitCodes = [], expects = []) {
85
+ export async function fork(hostInfo, cmd, args, timeout, outputCallback, retryableExitCodes = [], expects = []) {
85
86
  return new Promise((resolve, reject)=>{
86
87
  debug(`cmd=${cmd}: args=${args}`);
87
88
  const ptyProcess = spawn(cmd, args, nodePtyOpt);
@@ -150,8 +151,3 @@ async function fork(hostInfo, cmd, args, timeout, outputCallback, retryableExitC
150
151
  });
151
152
  });
152
153
  }
153
- module.exports = {
154
- fork,
155
- createMasterPty,
156
- sshLoginCallback
157
- };