ssh2-sftp-client
Advanced tools
Comparing version 2.4.3 to 2.5.0
{ | ||
"name": "ssh2-sftp-client", | ||
"version": "2.4.3", | ||
"version": "2.5.0", | ||
"description": "ssh2 sftp client for node", | ||
@@ -15,3 +15,3 @@ "main": "src/index.js", | ||
"scripts": { | ||
"test": "mocha test/index.js" | ||
"test": "mocha" | ||
}, | ||
@@ -22,3 +22,4 @@ "author": "见见", | ||
"dependencies": { | ||
"ssh2": "^0.6.1" | ||
"concat-stream": "^2.0.0", | ||
"ssh2": "^0.8.2" | ||
}, | ||
@@ -29,2 +30,3 @@ "devDependencies": { | ||
"chai-subset": "^1.6.0", | ||
"checksum": "^0.1.1", | ||
"dotenv": "^6.1.0", | ||
@@ -31,0 +33,0 @@ "mocha": "^5.2.0" |
## SSH2 SFTP Client | ||
a SFTP client for node.js, a wrapper for [ssh2](https://github.com/mscdex/ssh2) | ||
Additional documentation on the methods and available options can be found in | ||
the [ssh2](https://github.com/mscdex/ssh2) and | ||
[ssh2-streams](https://github.com/mscdex/ssh2-streams) documentation. | ||
### Installation | ||
@@ -27,2 +31,47 @@ ```shell | ||
### Breaking Changes | ||
Due to some incompatibilities with stream handling which breaks this module when | ||
used with Node 10.x, some changes have been implemented that should enhance the | ||
interface, but which also break compatibility with previous versions. | ||
#### Option Changes | ||
- The default encoding is null not utf8 as it was previously. This is consistent | ||
with the defaults for the underlying SSH2 module. | ||
- The usedCompressed option has been removed. None of the shh2-steams methods | ||
actually support this option. The 'compress' option can be set as part of the | ||
connection options. See [ssh2 client event](https://github.com/mscdex/ssh2#user-content-client-methods). | ||
- The separate explicit option arguments for encoding and useCompression for some methods | ||
have been replaced with a single 'options' argument, which is an object that | ||
can have the following properties (defaults shown). See the | ||
[ssh2-streams](https://github.com/mscdex/ssh2-streams) documentation for an | ||
explination of the opt8ons. | ||
```javascript | ||
const defaults = { | ||
highWaterMark: 32 * 1024, | ||
debug: undefined, | ||
concurrency: 64, | ||
chunkSize: 32768, | ||
step: undefined, | ||
mode: 0o666, | ||
autoClose: true, | ||
encoding: null | ||
}; | ||
``` | ||
#### Method Changes | ||
#### get(srcPath, dst, options) | ||
Used to retrieve a file from a remote SFTP server. | ||
- srcPath: path to the file on the remote server | ||
- dst: Either a string, which will be used as the path to store the file on the | ||
local system or a writable stream, which will be used as the destination for a | ||
stream pipe. If undefined, the remote file will be read into a Buffer and | ||
the buffer returned. | ||
- options: Options for the get operation e.g. encoding. | ||
### Documentation | ||
@@ -33,2 +82,3 @@ the connection to server config pls see [ssh2 client event](https://github.com/mscdex/ssh2#user-content-client-methods). | ||
all the methods will return a Promise; | ||
#### List | ||
@@ -59,6 +109,6 @@ Retrieves a directory listing. | ||
#### Get | ||
Get a `ReadableStream` from remotePath. The encoding is passed to Node Stream (https://nodejs.org/api/stream.html) and it controls how the content is encoded. For example, when downloading binary data, 'null' should be passed (check node stream documentation). Default to 'utf8'. | ||
Get a `ReadableStream` from remotePath. The encoding is passed to Node Stream (https://nodejs.org/api/stream.html) and it controls how the content is encoded. For example, when downloading binary data, 'null' should be passed (check node stream documentation). Default to 'null'. | ||
```javascript | ||
sftp.get(remoteFilePath, [useCompression], [encoding], [addtionalOptions]); | ||
sftp.get(remoteFilePath, [options]); | ||
``` | ||
@@ -77,5 +127,5 @@ | ||
```javascript | ||
sftp.put(localFilePath, remoteFilePath, [useCompression], [encoding], [addtionalOptions]); | ||
sftp.put(Buffer, remoteFilePath, [useCompression], [encoding], [addtionalOptions]); | ||
sftp.put(Stream, remoteFilePath, [useCompression], [encoding], [addtionalOptions]); | ||
sftp.put(localFilePath, remoteFilePath, [optons]); | ||
sftp.put(Buffer, remoteFilePath, [options]); | ||
sftp.put(Stream, remoteFilePath, [options]); | ||
``` | ||
@@ -82,0 +132,0 @@ |
274
src/index.js
@@ -10,4 +10,6 @@ /** | ||
const utils = require('./utils'); | ||
const fs = require('fs'); | ||
const concat = require('concat-stream'); | ||
let SftpClient = function(){ | ||
let SftpClient = function() { | ||
this.client = new Client(); | ||
@@ -47,3 +49,3 @@ }; | ||
user: item.longname.substr(1, 3).replace(reg, ''), | ||
group: item.longname.substr(4,3).replace(reg, ''), | ||
group: item.longname.substr(4, 3).replace(reg, ''), | ||
other: item.longname.substr(7, 3).replace(reg, '') | ||
@@ -87,6 +89,10 @@ }, | ||
} else { | ||
reject(new Error(`Error listing ${dir}: code: ${err.code} ${err.message}`)); | ||
reject( | ||
new Error(`Error listing ${dir}: code: ${err.code} ${err.message}`) | ||
); | ||
} | ||
} else { | ||
let [type] = list.filter(item => item.filename === base).map(item => item.longname.substr(0, 1)); | ||
let [type] = list | ||
.filter(item => item.filename === base) | ||
.map(item => item.longname.substr(0, 1)); | ||
if (type) { | ||
@@ -114,9 +120,9 @@ resolve(type); | ||
if (!sftp) { | ||
return reject(Error('sftp connect error')); | ||
return reject(Error('sftp connect error')); | ||
} | ||
sftp.stat(remotePath, function (err, stats) { | ||
if (err){ | ||
sftp.stat(remotePath, function(err, stats) { | ||
if (err) { | ||
reject(new Error(`Failed to stat ${remotePath}: ${err.message}`)); | ||
} else { | ||
// format similarly to sftp.list | ||
// format similarly to sftp.list | ||
resolve({ | ||
@@ -126,3 +132,3 @@ mode: stats.mode, | ||
owner: stats.uid, | ||
group: stats.guid, | ||
group: stats.gid, | ||
size: stats.size, | ||
@@ -141,12 +147,14 @@ accessTime: stats.atime * 1000, | ||
* | ||
* @param {String} path, path | ||
* @param {Object} useCompression, config options | ||
* @param {String} encoding. Encoding for the ReadStream, can be any value | ||
* supported by node streams. Use 'null' for binary | ||
* (https://nodejs.org/api/stream.html#stream_readable_setencoding_encoding) | ||
* @return {Promise} stream, readable stream | ||
* If a dst argument is provided, it must be either a string, representing the | ||
* local path to where the data will be put, a stream, in which case data is | ||
* piped into the stream or undefined, in which case the data is returned as | ||
* a Buffer object. | ||
* | ||
* @param {String} path, remote file path | ||
* @param {string|stream|undefined} dst, data destination | ||
* @param {Object} userOptions, options passed to get | ||
* | ||
* @return {Promise} | ||
*/ | ||
SftpClient.prototype.get = function(path, useCompression, encoding, otherOptions) { | ||
let options = this.getOptions(useCompression, encoding, otherOptions); | ||
SftpClient.prototype.get = function(path, dst, options) { | ||
return new Promise((resolve, reject) => { | ||
@@ -157,15 +165,32 @@ let sftp = this.sftp; | ||
try { | ||
this.client.on('error', reject); | ||
let stream = sftp.createReadStream(path, options); | ||
stream.on('error', (err) => { | ||
this.client.removeListener('error', reject); | ||
return reject(new Error(`Failed get for ${path}: ${err.message}`)); | ||
let rdr = sftp.createReadStream(path, options); | ||
rdr.on('error', err => { | ||
return reject(new Error(`Failed to get ${path}: ${err.message}`)); | ||
}); | ||
stream.on('readable', () => { | ||
this.client.removeListener('error', reject); | ||
return resolve(stream); | ||
}); | ||
} catch(err) { | ||
if (dst === undefined) { | ||
// no dst specified, return buffer of data | ||
let concatStream = concat(buff => { | ||
return resolve(buff); | ||
}); | ||
rdr.pipe(concatStream); | ||
} else if (typeof dst === 'string') { | ||
// dst local file path | ||
let wtr = fs.createWriteStream(dst); | ||
wtr.on('error', err => { | ||
return reject(new Error(`Failed get for ${path}: ${err.message}`)); | ||
}); | ||
wtr.on('finish', () => { | ||
return resolve(dst); | ||
}); | ||
rdr.pipe(wtr); | ||
} else { | ||
// assume dst is a writeStream | ||
dst.on('finish', () => { | ||
return resolve(dst); | ||
}); | ||
rdr.pipe(dst); | ||
} | ||
} catch (err) { | ||
this.client.removeListener('error', reject); | ||
@@ -190,3 +215,2 @@ return reject(new Error(`Failed get on ${path}: ${err.message}`)); | ||
SftpClient.prototype.fastGet = function(remotePath, localPath, options) { | ||
options = options || {concurrency: 64, chunkSize: 32768}; | ||
return new Promise((resolve, reject) => { | ||
@@ -196,6 +220,6 @@ let sftp = this.sftp; | ||
if (!sftp) { | ||
return reject(Error('sftp connect error')); | ||
return reject(Error('sftp connect error')); | ||
} | ||
sftp.fastGet(remotePath, localPath, options, function (err) { | ||
if (err){ | ||
sftp.fastGet(remotePath, localPath, options, function(err) { | ||
if (err) { | ||
reject(new Error(`Failed to get ${remotePath}: ${err.message}`)); | ||
@@ -219,3 +243,2 @@ } | ||
SftpClient.prototype.fastPut = function(localPath, remotePath, options) { | ||
options = options || {}; | ||
return new Promise((resolve, reject) => { | ||
@@ -225,7 +248,11 @@ let sftp = this.sftp; | ||
if (!sftp) { | ||
return reject(new Error('sftp connect error')); | ||
return reject(new Error('sftp connect error')); | ||
} | ||
sftp.fastPut(localPath, remotePath, options, function (err) { | ||
sftp.fastPut(localPath, remotePath, options, function(err) { | ||
if (err) { | ||
reject(new Error(`Failed to upload ${localPath} to ${remotePath}: ${err.message}`)); | ||
reject( | ||
new Error( | ||
`Failed to upload ${localPath} to ${remotePath}: ${err.message}` | ||
) | ||
); | ||
} | ||
@@ -238,3 +265,2 @@ resolve(`${localPath} was successfully uploaded to ${remotePath}!`); | ||
/** | ||
@@ -249,5 +275,3 @@ * Create file | ||
*/ | ||
SftpClient.prototype.put = function(input, remotePath, useCompression, encoding, otherOptions) { | ||
let options = this.getOptions(useCompression, encoding, otherOptions); | ||
SftpClient.prototype.put = function(input, remotePath, options) { | ||
return new Promise((resolve, reject) => { | ||
@@ -258,5 +282,9 @@ let sftp = this.sftp; | ||
if (typeof input === 'string') { | ||
sftp.fastPut(input, remotePath, options, (err) => { | ||
sftp.fastPut(input, remotePath, options, err => { | ||
if (err) { | ||
return reject(new Error(`Failed to upload ${input} to ${remotePath}: ${err.message}`)); | ||
return reject( | ||
new Error( | ||
`Failed to upload ${input} to ${remotePath}: ${err.message}` | ||
) | ||
); | ||
} | ||
@@ -270,9 +298,13 @@ return resolve(`Uploaded ${input} to ${remotePath}`); | ||
stream.on('error', err => { | ||
return reject(new Error(`Failed to upload data stream to ${remotePath}: ${err.message}`)); | ||
return reject( | ||
new Error( | ||
`Failed to upload data stream to ${remotePath}: ${err.message}` | ||
) | ||
); | ||
}); | ||
stream.on('close', () => { | ||
stream.on('finish', () => { | ||
return resolve(`Uploaded data stream to ${remotePath}`); | ||
}); | ||
if (input instanceof Buffer) { | ||
@@ -289,2 +321,52 @@ stream.end(input); | ||
/** | ||
* Append to file | ||
* | ||
* @param {Buffer|stream} input | ||
* @param {String} remotePath, | ||
* @param {Object} options | ||
* @return {[type]} [description] | ||
*/ | ||
SftpClient.prototype.append = function(input, remotePath, options) { | ||
return new Promise((resolve, reject) => { | ||
let sftp = this.sftp; | ||
if (sftp) { | ||
if (typeof input === 'string') { | ||
throw new Error('Cannot append a file to another'); | ||
} | ||
let stream = sftp.createWriteStream(remotePath, options); | ||
stream.on('error', err => { | ||
return reject( | ||
new Error( | ||
`Failed to upload data stream to ${remotePath}: ${err.message}` | ||
) | ||
); | ||
}); | ||
stream.on('finish', () => { | ||
return resolve(`Uploaded data stream to ${remotePath}`); | ||
}); | ||
if (input instanceof Buffer) { | ||
stream.end(input); | ||
return false; | ||
} | ||
input.pipe(stream); | ||
} else { | ||
return reject(Error('sftp connect error')); | ||
} | ||
}); | ||
}; | ||
/** | ||
* @async | ||
* | ||
* Make a dirextory on remote server | ||
* | ||
* @param {string} path, remote directory path. | ||
* @param {boolean} recursive, if true, recursively create directories | ||
* @return {Promise}. | ||
*/ | ||
SftpClient.prototype.mkdir = function(path, recursive = false) { | ||
@@ -295,4 +377,2 @@ let sftp = this.sftp; | ||
return new Promise((resolve, reject) => { | ||
if (!sftp) { | ||
@@ -315,8 +395,10 @@ return reject(new Error('sftp connect error')); | ||
let mkdir = p => { | ||
let {dir} = osPath.parse(p); | ||
return this.exists(dir).then((type) => { | ||
let {dir} = osPath.parse(p); | ||
return this.exists(dir) | ||
.then(type => { | ||
if (!type) { | ||
return mkdir(dir); | ||
} | ||
}).then(() => { | ||
}) | ||
.then(() => { | ||
return doMkdir(p); | ||
@@ -328,2 +410,11 @@ }); | ||
/** | ||
* @async | ||
* | ||
* Remove directory on remote server | ||
* | ||
* @param {string} path, path to directory to be removed | ||
* @param {boolean} recursive, if true, remove direcories/files in target | ||
* @return {Promise}.. | ||
*/ | ||
SftpClient.prototype.rmdir = function(path, recursive = false) { | ||
@@ -334,3 +425,2 @@ let sftp = this.sftp; | ||
return new Promise((resolve, reject) => { | ||
if (!sftp) { | ||
@@ -357,16 +447,19 @@ return reject(new Error('sftp connect error')); | ||
let dirs; | ||
return this.list(p).then((res) => { | ||
list = res; | ||
files = list.filter(item => item.type === '-'); | ||
dirs = list.filter(item => item.type === 'd'); | ||
return utils.forEachAsync(files, (f) => { | ||
return this.delete(osPath.join(p, f.name)); | ||
return this.list(p) | ||
.then(res => { | ||
list = res; | ||
files = list.filter(item => item.type === '-'); | ||
dirs = list.filter(item => item.type === 'd'); | ||
return utils.forEachAsync(files, f => { | ||
return this.delete(osPath.join(p, f.name)); | ||
}); | ||
}) | ||
.then(() => { | ||
return utils.forEachAsync(dirs, d => { | ||
return rmdir(osPath.join(p, d.name)); | ||
}); | ||
}) | ||
.then(() => { | ||
return doRmdir(p); | ||
}); | ||
}).then(() => { | ||
return utils.forEachAsync(dirs, (d) => { | ||
return rmdir(osPath.join(p, d.name)); | ||
}); | ||
}).then(() => { | ||
return doRmdir(p); | ||
}); | ||
}; | ||
@@ -383,3 +476,3 @@ return rmdir(path); | ||
* @return {Promise} with string 'Successfully deleeted file' once resolved | ||
* | ||
* | ||
*/ | ||
@@ -391,5 +484,5 @@ SftpClient.prototype.delete = function(path) { | ||
if (!sftp) { | ||
return reject(new Error('sftp connect error')); | ||
return reject(new Error('sftp connect error')); | ||
} | ||
sftp.unlink(path, (err) => { | ||
sftp.unlink(path, err => { | ||
if (err) { | ||
@@ -413,3 +506,3 @@ reject(new Error(`Failed to delete file ${path}: ${err.message}`)); | ||
* @return {Promise} | ||
* | ||
* | ||
*/ | ||
@@ -421,7 +514,11 @@ SftpClient.prototype.rename = function(srcPath, remotePath) { | ||
if (!sftp) { | ||
return reject(new Error('sftp connect error')); | ||
return reject(new Error('sftp connect error')); | ||
} | ||
sftp.rename(srcPath, remotePath, (err) => { | ||
sftp.rename(srcPath, remotePath, err => { | ||
if (err) { | ||
reject(new Error(`Failed to rename file ${srcPath} to ${remotePath}: ${err.message}`)); | ||
reject( | ||
new Error( | ||
`Failed to rename file ${srcPath} to ${remotePath}: ${err.message}` | ||
) | ||
); | ||
} | ||
@@ -449,7 +546,9 @@ resolve(`Successfully renamed ${srcPath} to ${remotePath}`); | ||
if (!sftp) { | ||
return reject(new Error('sftp connect error')); | ||
return reject(new Error('sftp connect error')); | ||
} | ||
sftp.chmod(remotePath, mode, (err) => { | ||
sftp.chmod(remotePath, mode, err => { | ||
if (err) { | ||
reject(new Error(`Failed to change mode for ${remotePath}: ${err.message}`)); | ||
reject( | ||
new Error(`Failed to change mode for ${remotePath}: ${err.message}`) | ||
); | ||
} | ||
@@ -471,3 +570,3 @@ resolve('Successfully change file mode'); | ||
* @return {Promise} which will resolve to an sftp client object | ||
* | ||
* | ||
*/ | ||
@@ -499,6 +598,6 @@ SftpClient.prototype.connect = function(config, connectMethod) { | ||
* Close the SFTP connection | ||
* | ||
* | ||
*/ | ||
SftpClient.prototype.end = function() { | ||
return new Promise((resolve) => { | ||
return new Promise(resolve => { | ||
this.client.end(); | ||
@@ -509,10 +608,2 @@ resolve(); | ||
SftpClient.prototype.getOptions = function(useCompression, encoding, otherOptions) { | ||
if(encoding === undefined){ | ||
encoding = 'utf8'; | ||
} | ||
let options = Object.assign({}, otherOptions || {}, {encoding: encoding}, useCompression); | ||
return options; | ||
}; | ||
// add Event type support | ||
@@ -523,9 +614,2 @@ SftpClient.prototype.on = function(eventType, callback) { | ||
module.exports = SftpClient; | ||
// sftp = new SftpClient() | ||
// sftp.client.on('event') | ||
// | ||
// sftp.on('end', ()=>{}) => this.client.on('event', callback) | ||
// sftp.on('error', () => {}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
7644098
40
2588
262
2
6
14
2
+ Addedconcat-stream@^2.0.0
+ Addedbcrypt-pbkdf@1.0.2(transitive)
+ Addedbuffer-from@1.1.2(transitive)
+ Addedconcat-stream@2.0.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedssh2@0.8.9(transitive)
+ Addedssh2-streams@0.4.10(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedtweetnacl@0.14.5(transitive)
+ Addedtypedarray@0.0.6(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
- Removedsemver@5.7.2(transitive)
- Removedssh2@0.6.2(transitive)
- Removedssh2-streams@0.2.1(transitive)
Updatedssh2@^0.8.2