Comparing version 1.0.1 to 1.1.0
@@ -0,1 +1,14 @@ | ||
## 1.1.0 / 2023-07-04 | ||
* Added `TS` support. | ||
* Added _ts tests_. | ||
* Added `package-lock.json`. | ||
* Improved _tests_. | ||
* Improved _readme_. | ||
* Improved _error messages_ and added param `code` in _responseKO error_. | ||
* Fixed reducing duration of _connection timeout_ when fails. | ||
* Fixed method `delete` allowing to remove folder (as `rmdir`) when it's empty. | ||
* Updated lib `oro-functions` to `v1.3.2`. | ||
* Updated lib `ssh2-sftp-client` to `v9.1.0`. | ||
* Updated lib-dev `jest` to `v29.5.0`. | ||
## 1.0.3 / 2022-06-21 | ||
@@ -2,0 +15,0 @@ * Updated lib `oro-functions` to `v1.1.7`. |
280
index.js
@@ -6,7 +6,25 @@ const path = require( 'path' ); | ||
class OroSftp { | ||
function getMsgAndCodeByErr( err ) { | ||
let msg = err.toString().split( '\r\n' )[ 0 ].replace( 'Error: ', '' ); | ||
let code = err.code; | ||
#ftpClient | ||
#ftpConfig | ||
if( msg.includes( 'No SFTP connection available' ) ) { | ||
msg = `FtpConnectionError: connection status is not yet connected`; | ||
code = 'UNCONNECTED'; | ||
} | ||
switch( true ) { | ||
case code === 2: code = 'ENOTFOUND'; break; | ||
case code === 4: code = 'ENOTEMPTY'; break; | ||
} | ||
return { msg, code }; | ||
} | ||
class OSFtp { | ||
#ftpClient; | ||
#config; | ||
constructor( config = {} ) { | ||
@@ -21,11 +39,12 @@ ! Ofn.objIsEmpty( config ) && this.#setFtpConfig( config ); | ||
#setFtpConfig( config ) { | ||
this.#ftpConfig = Ofn.cloneObject( config ); | ||
this.#config = Ofn.cloneObject( config ); | ||
if( this.#ftpConfig.user ) { | ||
this.#ftpConfig.username = this.#ftpConfig.user; | ||
delete this.#ftpConfig.user; | ||
if( this.#config.user ) { | ||
this.#config.username = this.#config.user; | ||
delete this.#config.user; | ||
} | ||
this.#ftpConfig.readyTimeout === undefined && ( this.#ftpConfig.readyTimeout = 3000 ); | ||
this.#ftpConfig.disconnectWhenError === undefined && ( this.#ftpConfig.disconnectWhenError = true ); | ||
this.#config.readyTimeout === undefined && ( this.#config.readyTimeout = 3000 ); | ||
this.#config.retry_minTimeout === undefined && ( this.#config.retry_minTimeout = this.#config.readyTimeout ); | ||
this.#config.disconnectWhenError === undefined && ( this.#config.disconnectWhenError = true ); | ||
} | ||
@@ -36,15 +55,22 @@ | ||
if( Ofn.objIsEmpty( this.#ftpConfig ) ) { | ||
return Ofn.setResponseKO( `SFTP Connect failed: ftpConfig is empty.` ); | ||
if( Ofn.objIsEmpty( this.#config ) ) { | ||
return Ofn.setResponseKO( | ||
`SFTP Connect failed: config is empty.`, | ||
{ code: 'UNCONNECTED', config: {} } | ||
); | ||
} | ||
return await this.#ftpClient.connect( this.#ftpConfig ) | ||
return await this.#ftpClient.connect( this.#config ) | ||
.then( data => Ofn.setResponseOK() ) | ||
.catch( err => { | ||
let cloneConfig = Ofn.cloneObject( this.#ftpConfig ); | ||
cloneConfig.password && (cloneConfig.password = new Array( cloneConfig.password.length ).fill( '*' ).join( '' )); | ||
let errArray = err.toString().split( '\r\n' ); | ||
let tryAgain = errArray[ 0 ] !== 'Error: Invalid username'; | ||
return Ofn.setResponseKO( `SFTP Connect failed: ${errArray[ 0 ]}.`, | ||
{ ftpConfig: cloneConfig, ftpError: errArray }, tryAgain ) | ||
const config = Ofn.cloneObject( this.#config ); | ||
if( config.password ) { | ||
config.password = new Array( config.password.length ).fill( '*' ).join( '' ); | ||
} | ||
const { msg } = getMsgAndCodeByErr( err ); | ||
const code = msg.includes( 'Timed out while waiting for handshake' ) ? 'ENTIMEOUT' : err.code +''; | ||
const tryAgain = msg !== 'Invalid username'; | ||
return Ofn.setResponseKO( `SFTP Connect failed: ${msg.replace( 'connect: ', '' )}.`, | ||
{ config, code }, tryAgain ) | ||
} ); | ||
@@ -58,4 +84,7 @@ } | ||
if( ! await fsExtra.exists( filepathFrom ) ) { | ||
this.#ftpConfig.disconnectWhenError && ( await this.disconnect() ); | ||
return Ofn.setResponseKO( `SFTP Upload failed: File to upload not exist.`, { filepathFrom } ); | ||
this.#config.disconnectWhenError && ( await this.disconnect() ); | ||
return Ofn.setResponseKO( | ||
`SFTP Upload failed: File (From) to upload not exist.`, | ||
{ filepathFrom, filepathTo, code: 'ENOTFOUND' } | ||
); | ||
} | ||
@@ -65,8 +94,14 @@ | ||
.then( data => { | ||
return Ofn.setResponseOK( { filename: Ofn.getFilenameByPath( filepathTo ), filepath: filepathTo } ); | ||
return Ofn.setResponseOK( { | ||
filename: Ofn.getFilenameByPath( filepathTo ), | ||
filepath: filepathTo | ||
} ); | ||
} ) | ||
.catch( err => { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
let errArray = err.toString().split( '\r\n' ); | ||
return Ofn.setResponseKO( `SFTP Upload failed: ${errArray[0]}.`, { ftp: errArray } ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
const { msg, code } = getMsgAndCodeByErr( err ); | ||
return Ofn.setResponseKO( | ||
`SFTP Upload failed: ${msg.replace( '_put: ', '' ).replace( 'Write stream error: ', '' )}.`, | ||
{ filepathFrom, filepathTo, code } | ||
); | ||
} ); | ||
@@ -80,4 +115,7 @@ } | ||
if( ! await fsExtra.exists( Ofn.getFolderByPath( filepathTo ) ) ) { | ||
this.#ftpConfig.disconnectWhenError && ( await this.disconnect() ); | ||
return Ofn.setResponseKO( `SFTP Download failed: Folder to download not exist.`, { filepathFrom, filepath: filepathTo } ); | ||
this.#config.disconnectWhenError && ( await this.disconnect() ); | ||
return Ofn.setResponseKO( | ||
`SFTP Download failed: Folder (From) to download not exist.`, | ||
{ filepathFrom, filepathTo, code: 'ENOTFOUND' } | ||
); | ||
} | ||
@@ -87,8 +125,14 @@ | ||
.then( data => { | ||
return Ofn.setResponseOK( { filename: Ofn.getFilenameByPath( filepathTo ), filepath: Ofn.sanitizePath( filepathTo ) } ); | ||
return Ofn.setResponseOK( { | ||
filename: Ofn.getFilenameByPath( filepathTo ), | ||
filepath: Ofn.sanitizePath( filepathTo ) | ||
} ); | ||
} ) | ||
.catch( err => { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
let errArray = err.toString().split( '\r\n' ); | ||
return Ofn.setResponseKO( `SFTP Download failed: ${errArray[0]}.`, { ftp: errArray } ) | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
const { msg, code } = getMsgAndCodeByErr( err ); | ||
return Ofn.setResponseKO( | ||
`SFTP Download failed: ${msg.replace( 'get: ', '' )}.`, | ||
{ filepathFrom, filepathTo, code } | ||
) | ||
} ); | ||
@@ -102,9 +146,9 @@ } | ||
folder[ 0 ] === '/' && ( folder = `.${folder}` ); | ||
folder && folder.substr( folder.length - 1 ) !== '/' && ( folder += '/' ); | ||
folder && folder.slice( folder.length - 1 ) !== '/' && ( folder += '/' ); | ||
let folderPath = folder.indexOf( './' ) === 0 ? folder.substr( 2 ) : folder; | ||
const folderPath = folder.indexOf( './' ) === 0 ? folder.slice( 2 ) : folder; | ||
return await this.#ftpClient.list( folder, filters.pattern ) | ||
.then( data => { | ||
let files = []; | ||
const files = []; | ||
for( const elem of data ) { | ||
@@ -115,2 +159,10 @@ elem.date = new Date( elem.modifyTime ); | ||
elem.modifyDate = elem.modifyTime && new Date(elem.modifyTime); | ||
delete elem.modifyTime; | ||
elem.accessDate = elem.accessTime && new Date(elem.accessTime); | ||
delete elem.modifyTime; | ||
elem.owner = (elem.owner || '')+''; | ||
elem.group = (elem.group || '')+''; | ||
elem.path = `${folderPath}${elem.name}`; | ||
@@ -123,5 +175,5 @@ | ||
.catch( err => { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
let errArray = err.toString().split( '\r\n' ); | ||
return Ofn.setResponseKO( `SFTP List failed: ${errArray[0]}.`, { ftp: errArray } ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
const { msg, code } = getMsgAndCodeByErr( err ); | ||
return Ofn.setResponseKO( `SFTP List failed: ${msg}.`, { folder, filters, code } ); | ||
} ); | ||
@@ -133,10 +185,14 @@ } | ||
.then( data => { | ||
return Ofn.setResponseOK( | ||
{ filepathFrom, filepath: filepathTo, filename: Ofn.getFilenameByPath( filepathTo ) } ); | ||
return Ofn.setResponseOK( { | ||
filename: Ofn.getFilenameByPath( filepathTo ), | ||
filepath: filepathTo | ||
} ); | ||
} ) | ||
.catch( err => { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
let errArray = err.toString().split( '\r\n' ); | ||
return Ofn.setResponseKO( `SFTP Move failed: ${errArray[0]}.`, | ||
{ filepathFrom, filepath: filepathTo, ftp: errArray } ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
const { msg, code } = getMsgAndCodeByErr( err ); | ||
return Ofn.setResponseKO( | ||
`SFTP Move failed: ${msg.replace( '_rename: ', '' )}.`, | ||
{ filepathFrom, filepathTo, code } | ||
); | ||
} ); | ||
@@ -148,14 +204,44 @@ } | ||
.then( data => { | ||
return Ofn.setResponseOK( 'deleted successfully', | ||
{ filepath: filepathFrom, filename: Ofn.getFilenameByPath( filepathFrom ) } ); | ||
return Ofn.setResponseOK( 'deleted successfully', { | ||
filepath: filepathFrom, | ||
filename: Ofn.getFilenameByPath( filepathFrom ) | ||
} ); | ||
} ) | ||
.catch( err => { | ||
let errArray = err.toString().split( '\r\n' ); | ||
if( ! strict && errArray[ 0 ].match( /(Error: delete: No such file)/ ) ) { | ||
return Ofn.setResponseOK( `file not found`, | ||
{ filepath: filepathFrom, filename: Ofn.getFilenameByPath( filepathFrom ) } ); | ||
.catch( async (err) => { | ||
let { msg, code } = getMsgAndCodeByErr( err ); | ||
if( ! strict && msg.match( /(delete: No such file)|(delete: Failure)/ ) ) { | ||
const exists = await this.exists( filepathFrom ); | ||
if( ! exists.status || exists.type !== 'd') { | ||
return Ofn.setResponseOK( `file not found`, { | ||
filepath: filepathFrom, | ||
filename: Ofn.getFilenameByPath( filepathFrom ) | ||
} ); | ||
} | ||
const rmdir = await this.rmdir( filepathFrom, false, true ); | ||
return rmdir.status | ||
? Ofn.setResponseOK( rmdir.msg, | ||
{ | ||
filepath: rmdir.folderpath, | ||
filename: rmdir.foldername | ||
} ) | ||
: Ofn.setResponseKO( rmdir.error.msg.replace( 'Rmdir', 'Delete' ), | ||
{ | ||
filepathFrom: rmdir.error.folder, | ||
code: rmdir.error.code | ||
} ); | ||
} | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
return Ofn.setResponseKO( `SFTP Delete failed: ${errArray[0]}.`, | ||
{ filepath: filepathFrom, ftp: errArray } ) | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
if( msg === "TypeCannot read properties of undefined (reading 'unlink')" ) { | ||
msg = `FtpConnectionError: connection status is not yet connected`; | ||
code = 'UNCONNECTED'; | ||
} | ||
return Ofn.setResponseKO( | ||
`SFTP Delete failed: ${msg.replace('delete: ', '')}.`, | ||
{ filepathFrom, code } | ||
) | ||
} ); | ||
@@ -167,4 +253,6 @@ } | ||
.then( data => { | ||
let response = Ofn.setResponseOK( | ||
{ filepath: filepathFrom, filename: Ofn.getFilenameByPath( filepathFrom ) } ); | ||
const response = Ofn.setResponseOK( { | ||
filepath: filepathFrom, | ||
filename: Ofn.getFilenameByPath( filepathFrom ) | ||
} ); | ||
@@ -176,6 +264,12 @@ data && ( response.type = data ); | ||
.catch( err => { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
let errArray = err.toString().split( '\r\n' ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
const { msg, code } = getMsgAndCodeByErr( err ); | ||
return Ofn.setResponseKO( | ||
`SFTP Exists failed: ${errArray[0]}.`, { filepath: filepathFrom, ftp: errArray } ); | ||
`SFTP Exists failed: ${msg}.`, | ||
{ | ||
filepath: filepathFrom, | ||
filename: Ofn.getFilenameByPath( filepathFrom ), | ||
code | ||
} | ||
); | ||
} ); | ||
@@ -186,3 +280,3 @@ } | ||
if( ! folder ) { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
return Ofn.setResponseKO( `SFTP Mkdir failed: param folder is required.` ); | ||
@@ -192,10 +286,12 @@ } | ||
folder[ 0 ] === '/' && ( folder = `.${folder}` ); | ||
let folderpath = folder.indexOf( './' ) === 0 ? folder.substr( 2 ) : folder; | ||
const folderpath = folder.indexOf( './' ) === 0 ? folder.slice( 2 ) : folder; | ||
let exists = await this.exists( folder ); | ||
const exists = await this.exists( folder ); | ||
if( exists.status && exists.type === 'd' ) { | ||
if( strict ) { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
return Ofn.setResponseKO( `SFTP Mkdir failed: Folder already exists.`, | ||
{ folderpath, foldername: Ofn.getFilenameByPath( folder ), } ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
return Ofn.setResponseKO( | ||
`SFTP Mkdir failed: Folder already exists.`, | ||
{ folderpath, foldername: Ofn.getFilenameByPath( folder ) } | ||
); | ||
} | ||
@@ -210,5 +306,15 @@ | ||
.catch( err => { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
let errArray = err.toString().split( '\r\n' ); | ||
return Ofn.setResponseKO( `SFTP Mkdir failed: ${errArray[0]}.`, { folder, ftp: errArray } ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
let { msg, code } = getMsgAndCodeByErr( err ); | ||
if( code === 'ERR_BAD_PATH' ) { | ||
code = 'ENOTFOUND'; | ||
} | ||
msg = msg.replace('mkdir: ', '').replace('_doMkdir: ', '') | ||
return Ofn.setResponseKO( | ||
`SFTP Mkdir failed: ${msg}.`, | ||
{ folder, code } | ||
); | ||
} ); | ||
@@ -219,3 +325,3 @@ } | ||
if( ! folder ) { | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
return Ofn.setResponseKO( `SFTP Rmdir failed: param folder is required.` ); | ||
@@ -225,13 +331,27 @@ } | ||
folder[ 0 ] === '/' && ( folder = `.${folder}` ); | ||
let folderpath = folder.indexOf( './' ) === 0 ? folder.substr( 2 ) : folder; | ||
const folderpath = folder.indexOf( './' ) === 0 ? folder.slice( 2 ) : folder; | ||
return await this.#ftpClient.rmdir( folder, recursive ) | ||
.then( () => Ofn.setResponseOK( { folderpath, foldername: Ofn.getFilenameByPath( folder ) } ) ) | ||
.then( () => Ofn.setResponseOK( { | ||
folderpath, foldername: | ||
Ofn.getFilenameByPath( folder ) | ||
} ) ) | ||
.catch( err => { | ||
let errArray = err.toString().split( '\r\n' ); | ||
if( ! strict && errArray[ 0 ].match( /(Error: rmdir: Bad path:)/ ) ) { | ||
return Ofn.setResponseOK( `Folder not found.`, { folderpath, foldername: Ofn.getFilenameByPath( folder ) } ) | ||
let { msg, code } = getMsgAndCodeByErr( err ); | ||
if( ! strict && msg.match( /(Bad Path:)/ ) ) { | ||
return Ofn.setResponseOK( `Folder not found.`, { | ||
folderpath, | ||
foldername: Ofn.getFilenameByPath( folder ) | ||
} ) | ||
} | ||
this.#ftpConfig.disconnectWhenError && ( this.disconnect() ); | ||
return Ofn.setResponseKO( `SFTP Rmdir failed: ${errArray[0]}.`, { folder, ftp: errArray } ); | ||
this.#config.disconnectWhenError && ( this.disconnect() ); | ||
if( code === 'ERR_BAD_PATH' ) { | ||
code = 'ENOTFOUND'; | ||
} | ||
return Ofn.setResponseKO( | ||
`SFTP Rmdir failed: ${msg.replace('rmdir: ', '')}.`, | ||
{ folder, code } | ||
); | ||
} ); | ||
@@ -244,4 +364,8 @@ } | ||
.catch( err => { | ||
let errArray = err.toString().split( '\r\n' ); | ||
return Ofn.setResponseKO( `SFTP Disconnect failed: ${errArray[0]}.`, { ftp: errArray } ) | ||
const { msg } = getMsgAndCodeByErr( err ); | ||
return Ofn.setResponseKO( | ||
`SFTP Disconnect failed: ${msg}.`, | ||
undefined, | ||
true | ||
) | ||
} ); | ||
@@ -257,3 +381,3 @@ } | ||
this.disconnect(); | ||
await this.disconnect(); | ||
@@ -264,2 +388,2 @@ return sftpUpload; | ||
module.exports = OroSftp; | ||
module.exports = OSFtp; |
{ | ||
"name": "oro-sftp", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "Class OroSftp is a wrapper of ssh2-sftp-client to work as promises async/await.", | ||
@@ -26,8 +26,14 @@ "main": "index.js", | ||
"dependencies": { | ||
"oro-functions": "^1.1.7", | ||
"ssh2-sftp-client": "^8.1.0" | ||
"@types/ssh2-sftp-client": "^9.0.0", | ||
"oro-functions": "^1.3.2", | ||
"ssh2-sftp-client": "^9.1.0" | ||
}, | ||
"devDependencies": { | ||
"jest": "^28.1.1" | ||
"@babel/core": "^7.22.5", | ||
"@babel/preset-env": "^7.22.5", | ||
"@babel/preset-typescript": "^7.22.5", | ||
"@types/jest": "^29.5.2", | ||
"babel-jest": "^29.5.0", | ||
"jest": "^29.5.0" | ||
} | ||
} | ||
} |
645
README.md
@@ -7,2 +7,4 @@ # Oro Sftp | ||
If you need the same wrapper using FTP, then use [OroFtp](https://www.npmjs.com/package/oro-ftp) | ||
```shell | ||
@@ -17,2 +19,5 @@ npm install oro-sftp | ||
// ts | ||
import OSFtp from 'oro-sftp'; | ||
const sftpClient = new OSftp( { | ||
@@ -26,4 +31,5 @@ host: 'custom-server.com', | ||
const sftpUpload = await sftpClient.uploadOne( `./folder-from/filename`, 'folder-to/filename' ); | ||
console.log( sftpUpload ); | ||
// { status: true, ... } | ||
// -> { status: true, ... } | ||
``` | ||
@@ -33,23 +39,62 @@ | ||
* [new OSFtp()](#new-osftp-config---) | ||
* [Error Code List](#error-code-list) | ||
* [new OSFtp()](#new-osftp) | ||
* [.getClient()](#getclient) | ||
* [await .connect( config = {} )](#await-connect-config---) | ||
* [await .connect()](#await-connect) | ||
* [await .disconnect()](#await-disconnect) | ||
* [await .upload( filepathFrom, filepathTo = '' )](#await-upload-filepathfrom-filepathto---) | ||
* [await .uploadOne( filepathFrom, filepathTo = '' )](#await-uploadone-filepathfrom-filepathto---) | ||
* [await .download( filepathFrom, filepathTo = '' )](#await-download-filepathfrom-filepathto---) | ||
* [await .list( folder = '', filters = {} )](#await-list-folder---filters---) | ||
* [await .move( filepathFrom, filepathTo )](#await-move-filepathfrom-filepathto-) | ||
* [await .delete( filepathFrom, strict = false )](#await-delete-filepathfrom-strict--false-) | ||
* [await .exists( filepathFrom, disconnectWhenError = undefined )](#await-exists-filepathfrom-) | ||
* [await .mkdir( folder, recursive = true, strict = false )](#await-mkdir-folder-recursive--true-strict--false-) | ||
* [await .rmdir( folder, strict = false )](#await-rmdir-folder-recursive--false-strict--false-) | ||
* [await .upload()](#await-upload) | ||
* [await .uploadOne()](#await-uploadone) | ||
* [await .download()](#await-download) | ||
* [await .list()](#await-list) | ||
* [await .move()](#await-move) | ||
* [await .delete()](#await-delete) | ||
* [await .exists()](#await-exists) | ||
* [await .mkdir()](#await-mkdir) | ||
* [await .rmdir()](#await-rmdir) | ||
* [Testing](#testing) | ||
### Error Code List | ||
### new OSftp( config = {} ) | ||
When an error happens, instead to throw an error, it's returned a managed _responseKO_. | ||
On the construct, you can pass the server config data. You can also do it in `.connect()`. | ||
_responseKO_ is an object with 3 fields: | ||
In addition, `config` has a new param `disconnectWhenError` default `true`, so when an error happens the connection close automatically. | ||
````ts | ||
interface responseKO { | ||
status: false; | ||
error: { | ||
msg: string; // explaining the error | ||
code: OSFtpErrorCode; // string | ||
// ... // other data, it depends on method error | ||
}, | ||
tryAgain: boolean; | ||
} | ||
type OSFtpErrorCode = | ||
| 'UNCONNECTED' | ||
| 'ENOTFOUND' | ||
| 'ENTIMEOUT' | ||
| 'ENOENT' | ||
| 'EEXIST' | ||
| 'ENOTEMPTY'; | ||
```` | ||
### new OSftp() | ||
```ts | ||
new OSFtp( config?: OSFtpConfig ); | ||
type OSFtpConfig = SftpClient.ConnectOptions & { | ||
host?: string; | ||
port?: number; | ||
user?: string; | ||
password?: string; | ||
readyTimeout?: number; // def: 3000 | ||
disconnectWhenError?: boolean; // def: true | ||
} | ||
``` | ||
As parameters, you can pass the server config data (or you can also do it in `.connect()`). | ||
In addition, `config` has param `disconnectWhenError` (default `true`), so when an error happens, connection closes automatically. | ||
```js | ||
@@ -63,3 +108,2 @@ const OSftp = require( 'oro-sftp' ); | ||
readyTimeout: 3000, | ||
disconnectWhenError: true | ||
@@ -69,8 +113,10 @@ } | ||
const sftpClient = new OSftp( config ); | ||
``` | ||
### .getClient() | ||
```ts | ||
sftpClient.getClient(): SftpClient; | ||
``` | ||
If you want to use the library ssh2-sftp-client, you can get the object. | ||
If you want to use the library `ssh2-sftp-client`, you can get the object. | ||
@@ -83,7 +129,43 @@ ```js | ||
### await .connect( config = {} ) | ||
### await .connect() | ||
```ts | ||
await sftpClient.connect( config?: OSFtpConfig ) => Promise<OSFtpConnectResponse>; | ||
type OSFtpConfig = PromiseFtp.Options & { | ||
host?: string; | ||
port?: number; | ||
user?: string; | ||
password?: string; | ||
readyTimeout?: number; // def: 3000 | ||
disconnectWhenError?: boolean; // def: true | ||
} | ||
export type OSFtpConnectResponse = SResponse< | ||
SResponseOK, | ||
OSFtpConnectError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
code: OSFtpErrorCode; | ||
config: OSFtpConfig; | ||
} | ||
} | ||
interface OSFtpConnectError { | ||
msg: string; | ||
code: OSFtpErrorCode; | ||
config: OSFtpConfig; | ||
} | ||
``` | ||
When you create a connection, it's expected that you will disconnect it later. | ||
The action return a response, which is an object with `status: true` or `status: false`. | ||
This method return a _response_, which is an object with `status: true | false`. | ||
@@ -95,8 +177,30 @@ ```js | ||
console.log( connected ); | ||
// -> { status: true } | ||
``` | ||
### await .disconnect() | ||
```ts | ||
await sftpClient.disconnect() => Promise<OSFtpDisconnectResponse>; | ||
Note: It's not necessary to use `await` in `.disconnect()`. | ||
export type OSFtpDisconnectResponse = SResponse< | ||
SResponseOK, | ||
SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
} | ||
``` | ||
**Note**: If you don't `.disconnect()` when finished, the script still running. | ||
**Note2**: There is a param in _config_ `disconnectWhenError` by default `true`. | ||
This means that if a method (like `upload` or `move`) return `status: false`, the _ftpClient_ will be disconnected automatically. | ||
This method return a _response_, which is an object with `status: true | false`. | ||
```js | ||
@@ -111,9 +215,49 @@ const sftpClient = new OSftp( config ); | ||
console.log( disconnected ); | ||
// -> { status: true } | ||
``` | ||
### await .upload( filepathFrom, filepathTo = '' ) | ||
### await .upload() | ||
```ts | ||
await sftpClient.upload( filepathFrom: string, filepathTo?: string ) | ||
=> Promise<OSFtpFileResponse>; | ||
If `filepathTo` is not declared, it takes the filename of `filepathFrom` and save it on the main folder. | ||
export type OSFtpFileResponse = SResponse< | ||
OSFtpFileObject, // as SResponseOK | ||
OSFtpFileError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
filename: string; | ||
filepath: string; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
filepathFrom: string; | ||
filepathTo?: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
interface OSFtpFileObject { | ||
filename: string; | ||
filepath: string; | ||
} | ||
interface OSFtpFileError { | ||
msg: string; | ||
filepathFrom: string; | ||
filepathTo?: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
``` | ||
`upload` is the action to copy from _local_ to _ftp folder_. | ||
If `filepathTo` is not declared, it takes the filename of `filepathFrom` and save it on _ftp_ main folder. | ||
```js | ||
@@ -127,2 +271,3 @@ const sftpClient = new OSftp( config ); | ||
console.log( uploaded ); | ||
// -> { status: true, filename: 'custom-file.pdf', ... } | ||
@@ -132,9 +277,115 @@ sftpClient.disconnect(); | ||
### await .download( filepathFrom, filepathTo = '' ) | ||
### await .uploadOne() | ||
```ts | ||
await sftpClient.upload( filepathFrom: string, filepathTo?: string ) | ||
=> Promise<OSFtpUploadOneResponse>; | ||
If `filepathTo` is not declared, it takes the filename of `filepathFrom` and save it on the main folder. | ||
export type OSFtpUploadOneResponse = SResponse< | ||
OSFtpFileObject, // as SResponseOK | ||
OSFtpFileError | OSFtpConnectError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
filename: string; | ||
filepath: string; | ||
} | ||
type SResponseKO = | ||
| { | ||
status: false; | ||
error: { | ||
msg: string; | ||
filepathFrom: string; | ||
filepathTo?: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
| { | ||
status: false; | ||
error: { | ||
msg: string; | ||
code: OSFtpErrorCode; | ||
config: OSFtpConfig; | ||
} | ||
} | ||
interface OSFtpFileObject { | ||
filename: string; | ||
filepath: string; | ||
} | ||
interface OSFtpFileError { | ||
msg: string; | ||
filepathFrom: string; | ||
filepathTo?: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
interface OSFtpConnectError { | ||
msg: string; | ||
code: OSFtpErrorCode; | ||
config: OSFtpConfig; | ||
} | ||
``` | ||
If you want to upload just one file, you can use this method and inside: | ||
1. it's connected, | ||
2. file is uploaded, | ||
3. it's disconnected. | ||
```js | ||
const sftpClient = new OSftp( config ); | ||
const uploaded = await sftpClient.uploadOne( './files/custom-file.pdf' ); | ||
console.log( uploaded ); | ||
// -> { status: true, filename: 'custom-file.pdf', ... } | ||
``` | ||
### await .download() | ||
```ts | ||
await sftpClient.download( filepathFrom: string, filepathTo?: string ) | ||
=> Promise<OSFtpFileResponse>; | ||
export type OSFtpFileResponse = SResponse< | ||
OSFtpFileObject, // as SResponseOK | ||
OSFtpFileError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
filename: string; | ||
filepath: string; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
filepathFrom: string; | ||
filepathTo?: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
interface OSFtpFileObject { | ||
filename: string; | ||
filepath: string; | ||
} | ||
interface OSFtpFileError { | ||
msg: string; | ||
filepathFrom: string; | ||
filepathTo?: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
``` | ||
`download` is the action to copy from _ftp folder_ to _local_. | ||
If `filepathTo` is not declared, it takes the filename of `filepathFrom` and save it on _local_ main folder. | ||
```js | ||
const sftpClient = new OSftp( config ); | ||
const connected = await sftpClient.connect(); | ||
@@ -145,2 +396,3 @@ if( ! connected.status ) { return connected; } | ||
console.log( downloaded ); | ||
// -> { status: true, filename: 'custom-file.pdf', ... } | ||
@@ -150,44 +402,107 @@ sftpClient.disconnect(); | ||
### await .uploadOne( filepathFrom, filepathTo = '' ) | ||
### await .list() | ||
```ts | ||
await sftpClient.list( folder?: string, filters?: OSFtpListFilters ) | ||
=> Promise<OSFtpListResponse>; | ||
If you want to upload just one file, you can use sftpClient method and inside it creates the connection/disconnection flow. | ||
interface OSFtpListFilters { | ||
onlyFiles?: boolean | undefined; // def: false | ||
onlyFolders?: boolean | undefined; // def: false | ||
pattern?: string | RegExp | undefined; | ||
} | ||
export type OSFtpListResponse = SResponse< | ||
OSFtpListObject, // as SResponseOK | ||
OSFtpListError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
count: number; // list.length | ||
list: OSFtpListFile[]; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
folder: string; | ||
filters: OSFtpListFilters; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
export interface OSFtpListFile { | ||
path: string; | ||
name: string; | ||
type: OSFtpListFileType; | ||
date: Date; | ||
size: number; | ||
owner: string; | ||
group: string; | ||
target: string | undefined; | ||
rights: { | ||
user: string; | ||
group: string; | ||
other: string; | ||
} | ||
} | ||
type OSFtpListFileType = '-' | 'd' | 'l'; | ||
// 'file' | 'folder' | 'symlink' | ||
export interface OSFtpListObject { | ||
count: number; // list.length | ||
list: OSFtpListFile[]; | ||
} | ||
export interface OSFtpListError { | ||
msg: string; | ||
folder: string; | ||
filters: OSFtpListFilters; | ||
code?: OSFtpErrorCode; | ||
} | ||
``` | ||
`list` is the action to take a look at what is in _ftp folder_. | ||
```js | ||
const sftpClient = new OSftp( config ); | ||
const uploaded = await sftpClient.uploadOne( './files/custom-file.pdf' ); | ||
console.log( uploaded ); | ||
const connected = await sftpClient.connect(); | ||
if( ! connected.status ) { return connected; } | ||
const files = await sftpClient.list(); | ||
console.log( files ); | ||
// -> { status: true, count: 7, list: [ ... ] } | ||
sftpClient.disconnect(); | ||
``` | ||
### await .list( folder = '', filters = {} ) | ||
* Filter: `pattern` | ||
`pattern` filter can be a regular expression (most powerful option) or | ||
a simple glob-like string where `*` will match any number of characters, e.g. | ||
```js | ||
// Default filters: | ||
{ | ||
pattern: undefined, | ||
onlyFiles: false, | ||
onlyFolders: false, | ||
} | ||
// The filter options can be a regular expression (most powerful option) or | ||
// a simple glob-like string where * will match any number of characters, e.g. | ||
foo* => foo, foobar, foobaz | ||
*bar => bar, foobar, tabbar | ||
*oo* => foo, foobar, look, book | ||
``` | ||
// Response | ||
response example | ||
```js | ||
{ | ||
status: true, | ||
count: // list.length | ||
list:: [ | ||
list: [ | ||
{ | ||
type: // file type(-, d, l) | ||
name: // file name | ||
longname: // file name as linux promp | ||
path: // file path | ||
date: // file date of modified time | ||
modifyDate: // file date of modified time | ||
accessDate: // file date of access time | ||
size: // file size | ||
rights: { user: group: other: } // rwx | ||
modifyTime: // file timestamp of modified time | ||
accessTime: // file timestamp of access time | ||
rights: { user: 'rwx', group: 'rwx', other: 'rwx' } | ||
owner: // user number ID | ||
@@ -201,15 +516,42 @@ group: // group number ID | ||
```js | ||
const sftpClient = new OSftp( config ); | ||
### await .move() | ||
```ts | ||
await sftpClient.move( filepathFrom: string, filepathTo?: string ) | ||
=> Promise<OSFtpFileResponse>; | ||
const connected = await sftpClient.connect(); | ||
if( ! connected.status ) { return connected; } | ||
export type OSFtpFileResponse = SResponse< | ||
OSFtpFileObject, // as SResponseOK | ||
OSFtpFileError // as SResponseKO | ||
>; | ||
const files = await sftpClient.list(); | ||
console.log( files ); | ||
interface SResponseOK { | ||
status: true; | ||
filename: string; | ||
filepath: string; | ||
} | ||
sftpClient.disconnect(); | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
filepathFrom: string; | ||
filepathTo?: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
interface OSFtpFileObject { | ||
filename: string; | ||
filepath: string; | ||
} | ||
interface OSFtpFileError { | ||
msg: string; | ||
filepathFrom: string; | ||
filepathTo?: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
``` | ||
### await .move( filepathFrom, filepathTo ) | ||
`move` is the action to move from _ftp folder_ to _ftp folder_ (or event _rename_). | ||
@@ -224,2 +566,3 @@ ```js | ||
console.log( moved ); | ||
// -> { status: true, filename: 'custom-file.pdf', ... } | ||
@@ -229,4 +572,41 @@ sftpClient.disconnect(); | ||
### await .delete( filepathFrom, strict = false ) | ||
### await .delete() | ||
```ts | ||
await sftpClient.delete( filepathFrom: string, strict?: boolean ) | ||
=> Promise<OSFtpFileResponse>; | ||
export type OSFtpFileResponse = SResponse< | ||
OSFtpFileObject, // as SResponseOK | ||
OSFtpFileError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
filename: string; | ||
filepath: string; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
filepathFrom: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
interface OSFtpFileObject { | ||
filename: string; | ||
filepath: string; | ||
} | ||
interface OSFtpFileError { | ||
msg: string; | ||
filepathFrom: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
``` | ||
`delete` is the action to remove a file from _ftp folder_. | ||
When `strict = false` and not found the file, it returns `{ status: true }`. | ||
@@ -242,2 +622,3 @@ | ||
console.log( deleted ); | ||
// -> { status: true, filename: 'custom-file.pdf', ... } | ||
@@ -247,6 +628,45 @@ sftpClient.disconnect(); | ||
### await .exists( filepathFrom ) | ||
### await .exists() | ||
```ts | ||
await sftpClient.exists( filepathFrom: string, disconnectWhenError?: boolean ) | ||
=> Promise<OSFtpExistResponse>; | ||
It returns `{ status: Boolean, filepath: filepathFrom, [ type: <- only when file exists ] }`. | ||
export type OSFtpExistResponse = SResponse< | ||
OSFtpExistObject, // as SResponseOK | ||
OSFtpExistError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
filename: string; | ||
filepath: string; | ||
type: string; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
filename: string; | ||
filepath: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
interface OSFtpExistObject { | ||
filename: string; | ||
filepath: string; | ||
type: string; | ||
} | ||
interface OSFtpExistError { | ||
msg: string; | ||
filename: string; | ||
filepath: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
``` | ||
`exists` is the action to check if a file or folder exists in _ftp folder_. | ||
```js | ||
@@ -260,2 +680,3 @@ const sftpClient = new OSftp( config ); | ||
console.log( exists ); | ||
// -> { status: true, filename: 'custom-file.pdf', type: 'd' ... } | ||
@@ -265,6 +686,45 @@ sftpClient.disconnect(); | ||
### await .mkdir( folder, recursive = true, strict = false ) | ||
### await .mkdir() | ||
```ts | ||
await sftpClient.mkdir( folder, recursive?: boolean, strict?: boolean ) | ||
=> Promise<OSFtpFolderResponse>; | ||
It allows to create folders recursively. | ||
export type OSFtpFolderResponse = SResponse< | ||
OSFtpFolderObject, // as SResponseOK | ||
OSFtpFolderError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
foldername: string; | ||
folderpath: string; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
filepathFrom: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
interface OSFtpFolderObject { | ||
foldername: string; | ||
folderpath: string; | ||
} | ||
interface OSFtpFolderError { | ||
msg: string; | ||
folder: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
``` | ||
`mkdir` is the action to create folders in _ftp folder_. | ||
When `recursive = true` it allows to create the subfolders too. | ||
When `strict = false` and folder already exist, it returns `{ status: true }`. | ||
```js | ||
@@ -278,2 +738,3 @@ const sftpClient = new OSftp( config ); | ||
console.log( created ); | ||
// -> { status: true, foldername: 'custom-subfolder', ... } | ||
@@ -283,5 +744,43 @@ sftpClient.disconnect(); | ||
### await .rmdir( folder, recursive = false, strict = false ) | ||
### await .rmdir() | ||
```ts | ||
await sftpClient.rmdir( folder, recursive?: boolean, strict?: boolean ) | ||
=> Promise<OSFtpFolderResponse>; | ||
export type OSFtpFolderResponse = SResponse< | ||
OSFtpFolderObject, // as SResponseOK | ||
OSFtpFolderError // as SResponseKO | ||
>; | ||
interface SResponseOK { | ||
status: true; | ||
foldername: string; | ||
folderpath: string; | ||
} | ||
interface SResponseKO { | ||
status: false; | ||
error: { | ||
msg: string; | ||
filepathFrom: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
} | ||
interface OSFtpFolderObject { | ||
foldername: string; | ||
folderpath: string; | ||
} | ||
interface OSFtpFolderError { | ||
msg: string; | ||
folder: string; | ||
code?: OSFtpErrorCode; | ||
} | ||
``` | ||
`rmdir` is the action to remove folders in _ftp folder_. | ||
When `recursive = true` it allows to remove the folder-content too. | ||
When `strict = false` and not found the folder, it returns `{ status: true }`. | ||
@@ -297,2 +796,3 @@ | ||
console.log( removed ); | ||
// -> { status: true, foldername: 'custom-folder', ... } | ||
@@ -305,3 +805,3 @@ sftpClient.disconnect(); | ||
If you want to run `npm run test`, it's required to declare your own `./test/config.json` | ||
(you can copypaste it from `./test/config-default.json`) | ||
(you can _copypaste_ it from `./test/config-default.json`) | ||
@@ -317,6 +817,23 @@ ```json | ||
__ADVISE:__ When run the testing, in the server it's created and removed the next folders: | ||
`test-exists`, `test-mkdir`, `test-rmdir`, `test-list`, `test-delete`, `test-move`, `test-upload`, `test-download`; | ||
__NOTICE:__ When tests are running, in the _server ftp_ it's created (and removed when it has finished) the next folders: | ||
* `test-exists`, | ||
* `test-mkdir`, | ||
* `test-rmdir`, | ||
* `test-list`, | ||
* `test-delete`, | ||
* `test-move`, | ||
* `test-upload`, | ||
* `test-download`; | ||
* `test-exists-ts`, | ||
* `test-mkdir-ts`, | ||
* `test-rmdir-ts`, | ||
* `test-list-ts`, | ||
* `test-delete-ts`, | ||
* `test-move-ts`, | ||
* `test-upload-ts`, | ||
* `test-download-ts`; | ||
and the files `./zpython.pdf`, `./zpython2.pdf`. | ||
So, if in your _sftp server_ already exist them and there are required for you, avoid to `run test`. | ||
So, | ||
* `rw` permissions should be allowed, | ||
* and if in your _sftp server_ already exist them and there are required for you, avoid to `run test`. |
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
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
41074
7
419
814
3
6
1
+ Added@types/node@18.19.64(transitive)
+ Added@types/ssh2@1.15.1(transitive)
+ Added@types/ssh2-sftp-client@9.0.4(transitive)
+ Addedssh2-sftp-client@9.1.0(transitive)
+ Addedundici-types@5.26.5(transitive)
- Removed@types/node@22.9.0(transitive)
- Removedssh2-sftp-client@8.1.0(transitive)
- Removedundici-types@6.19.8(transitive)
Updatedoro-functions@^1.3.2
Updatedssh2-sftp-client@^9.1.0