basic-ftp
Advanced tools
Comparing version 4.6.6 to 5.0.0
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { Readable, Writable } from "stream"; | ||
@@ -306,3 +307,3 @@ import { ConnectionOptions as TLSConnectionOptions } from "tls"; | ||
*/ | ||
protected _enterFirstCompatibleMode(transferModes: TransferStrategy[]): TransferStrategy; | ||
protected _enterFirstCompatibleMode(): TransferStrategy; | ||
/** | ||
@@ -309,0 +310,0 @@ * DEPRECATED, use `uploadFrom`. |
@@ -17,8 +17,8 @@ "use strict"; | ||
// Use promisify to keep the library compatible with Node 8. | ||
const fsReadDir = util_1.promisify(fs_1.readdir); | ||
const fsMkDir = util_1.promisify(fs_1.mkdir); | ||
const fsStat = util_1.promisify(fs_1.stat); | ||
const fsOpen = util_1.promisify(fs_1.open); | ||
const fsClose = util_1.promisify(fs_1.close); | ||
const fsUnlink = util_1.promisify(fs_1.unlink); | ||
const fsReadDir = (0, util_1.promisify)(fs_1.readdir); | ||
const fsMkDir = (0, util_1.promisify)(fs_1.mkdir); | ||
const fsStat = (0, util_1.promisify)(fs_1.stat); | ||
const fsOpen = (0, util_1.promisify)(fs_1.open); | ||
const fsClose = (0, util_1.promisify)(fs_1.close); | ||
const fsUnlink = (0, util_1.promisify)(fs_1.unlink); | ||
const LIST_COMMANDS_DEFAULT = ["LIST -a", "LIST"]; | ||
@@ -38,3 +38,3 @@ const LIST_COMMANDS_MLSD = ["MLSD", "LIST -a", "LIST"]; | ||
this.ftp = new FtpContext_1.FTPContext(timeout); | ||
this.prepareTransfer = this._enterFirstCompatibleMode([transfer_1.enterPassiveModeIPv6, transfer_1.enterPassiveModeIPv4]); | ||
this.prepareTransfer = this._enterFirstCompatibleMode(); | ||
this.parseList = parseList_1.parseList; | ||
@@ -77,3 +77,3 @@ this._progressTracker = new ProgressTracker_1.ProgressTracker(); | ||
family: this.ftp.ipFamily | ||
}, () => this.ftp.log(`Connected to ${netUtils_1.describeAddress(this.ftp.socket)} (${netUtils_1.describeTLS(this.ftp.socket)})`)); | ||
}, () => this.ftp.log(`Connected to ${(0, netUtils_1.describeAddress)(this.ftp.socket)} (${(0, netUtils_1.describeTLS)(this.ftp.socket)})`)); | ||
return this._handleConnectResponse(); | ||
@@ -87,3 +87,3 @@ } | ||
this.ftp.reset(); | ||
this.ftp.socket = tls_1.connect(port, host, tlsOptions, () => this.ftp.log(`Connected to ${netUtils_1.describeAddress(this.ftp.socket)} (${netUtils_1.describeTLS(this.ftp.socket)})`)); | ||
this.ftp.socket = (0, tls_1.connect)(port, host, tlsOptions, () => this.ftp.log(`Connected to ${(0, netUtils_1.describeAddress)(this.ftp.socket)} (${(0, netUtils_1.describeTLS)(this.ftp.socket)})`)); | ||
this.ftp.tlsOptions = tlsOptions; | ||
@@ -101,3 +101,3 @@ return this._handleConnectResponse(); | ||
} | ||
else if (parseControlResponse_1.positiveCompletion(res.code)) { | ||
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { | ||
task.resolve(res); | ||
@@ -109,3 +109,2 @@ } | ||
// so the user can inspect properties of this instance. | ||
this.ftp.socket.destroy(); | ||
task.reject(new FtpContext_1.FTPError(res)); | ||
@@ -151,5 +150,5 @@ } | ||
const ret = await this.send(command); | ||
this.ftp.socket = await netUtils_1.upgradeSocket(this.ftp.socket, options); | ||
this.ftp.socket = await (0, netUtils_1.upgradeSocket)(this.ftp.socket, options); | ||
this.ftp.tlsOptions = options; // Keep the TLS options for later data connections that should use the same options. | ||
this.ftp.log(`Control socket is using: ${netUtils_1.describeTLS(this.ftp.socket)}`); | ||
this.ftp.log(`Control socket is using: ${(0, netUtils_1.describeTLS)(this.ftp.socket)}`); | ||
return ret; | ||
@@ -164,3 +163,3 @@ } | ||
login(user = "anonymous", password = "guest") { | ||
this.ftp.log(`Login security: ${netUtils_1.describeTLS(this.ftp.socket)}`); | ||
this.ftp.log(`Login security: ${(0, netUtils_1.describeTLS)(this.ftp.socket)}`); | ||
return this.ftp.handle("USER " + user, (res, task) => { | ||
@@ -170,3 +169,3 @@ if (res instanceof Error) { | ||
} | ||
else if (parseControlResponse_1.positiveCompletion(res.code)) { // User logged in proceed OR Command superfluous | ||
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // User logged in proceed OR Command superfluous | ||
task.resolve(res); | ||
@@ -261,3 +260,3 @@ } | ||
// Not supporting any special features will be reported with a single line. | ||
if (res.code < 400 && parseControlResponse_1.isMultiline(res.message)) { | ||
if (res.code < 400 && (0, parseControlResponse_1.isMultiline)(res.message)) { | ||
// The first and last line wrap the multiline response, ignore them. | ||
@@ -294,3 +293,3 @@ res.message.split("\n").slice(1, -1).forEach(line => { | ||
const date = res.message.slice(4); | ||
return parseListMLSD_1.parseMLSxDate(date); | ||
return (0, parseListMLSD_1.parseMLSxDate)(date); | ||
} | ||
@@ -379,3 +378,3 @@ /** | ||
const fd = await fsOpen(localPath, "r"); | ||
const source = fs_1.createReadStream("", { | ||
const source = (0, fs_1.createReadStream)("", { | ||
fd, | ||
@@ -404,3 +403,3 @@ start: options.localStart, | ||
// and removes the event listener for the source stream too early. | ||
return await transfer_1.uploadFrom(source, { | ||
return await (0, transfer_1.uploadFrom)(source, { | ||
ftp: this.ftp, | ||
@@ -443,3 +442,3 @@ tracker: this._progressTracker, | ||
const fd = await fsOpen(localPath, fileSystemFlags); | ||
const destination = fs_1.createWriteStream("", { | ||
const destination = (0, fs_1.createWriteStream)("", { | ||
fd, | ||
@@ -476,3 +475,3 @@ start: startAt, | ||
// and removes the event listener for the source stream too early. | ||
return await transfer_1.downloadTo(destination, { | ||
return await (0, transfer_1.downloadTo)(destination, { | ||
ftp: this.ftp, | ||
@@ -522,3 +521,3 @@ tracker: this._progressTracker, | ||
const buffer = new StringWriter_1.StringWriter(); | ||
await transfer_1.downloadTo(buffer, { | ||
await (0, transfer_1.downloadTo)(buffer, { | ||
ftp: this.ftp, | ||
@@ -596,3 +595,3 @@ tracker: this._progressTracker, | ||
for (const file of files) { | ||
const fullPath = path_1.join(localDirPath, file); | ||
const fullPath = (0, path_1.join)(localDirPath, file); | ||
const stats = await fsStat(fullPath); | ||
@@ -629,3 +628,3 @@ if (stats.isFile()) { | ||
for (const file of await this.list()) { | ||
const localPath = path_1.join(localDirPath, file.name); | ||
const localPath = (0, path_1.join)(localDirPath, file.name); | ||
if (file.isDirectory) { | ||
@@ -703,19 +702,7 @@ await this.cd(file.name); | ||
*/ | ||
_enterFirstCompatibleMode(transferModes) { | ||
_enterFirstCompatibleMode() { | ||
return async (ftp) => { | ||
ftp.log("Trying to find optimal transfer mode..."); | ||
for (const transferMode of transferModes) { | ||
try { | ||
const res = await transferMode(ftp); | ||
ftp.log("Optimal transfer mode found."); | ||
this.prepareTransfer = transferMode; // eslint-disable-line require-atomic-updates | ||
return res; | ||
} | ||
catch (err) { | ||
// Try the next candidate no matter the exact error. It's possible that a server | ||
// answered incorrectly to a strategy, for example a PASV answer to an EPSV. | ||
ftp.log(`Transfer mode failed: "${err.message}", will try next.`); | ||
} | ||
} | ||
throw new Error("None of the available transfer modes work."); | ||
const features = await this.features(); | ||
this.prepareTransfer = features.has("EPSV") ? transfer_1.enterPassiveModeIPv6 : transfer_1.enterPassiveModeIPv4; | ||
return this.prepareTransfer(ftp); | ||
}; | ||
@@ -722,0 +709,0 @@ } |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { Socket } from "net"; | ||
@@ -3,0 +5,0 @@ import { ConnectionOptions as TLSConnectionOptions, TLSSocket } from "tls"; |
@@ -69,2 +69,3 @@ "use strict"; | ||
this._closingError = err; | ||
this.send("QUIT"); // Don't wait for an answer | ||
// Before giving the user's task a chance to react, make sure we won't be bothered with any inputs. | ||
@@ -110,3 +111,3 @@ this._closeSocket(this._socket); | ||
if (!isUpgrade) { | ||
this._socket.destroy(); | ||
this._socket.end(); | ||
} | ||
@@ -277,3 +278,3 @@ this._removeSocketListeners(this._socket); | ||
const completeResponse = this._partialResponse + chunk; | ||
const parsed = parseControlResponse_1.parseControlResponse(completeResponse); | ||
const parsed = (0, parseControlResponse_1.parseControlResponse)(completeResponse); | ||
// Remember any incomplete remainder. | ||
@@ -327,4 +328,6 @@ this._partialResponse = parsed.rest; | ||
if (socket) { | ||
socket.destroy(); | ||
this._removeSocketListeners(socket); | ||
socket.on("error", () => { }); | ||
socket.setTimeout(0); | ||
socket.end(); | ||
} | ||
@@ -331,0 +334,0 @@ } |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k; |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { Socket } from "net"; | ||
@@ -3,0 +4,0 @@ import { ConnectionOptions, TLSSocket } from "tls"; |
@@ -34,3 +34,3 @@ "use strict"; | ||
}); | ||
const tlsSocket = tls_1.connect(tlsOptions, () => { | ||
const tlsSocket = (0, tls_1.connect)(tlsOptions, () => { | ||
const expectCertificate = tlsOptions.rejectUnauthorized !== false; | ||
@@ -65,4 +65,5 @@ if (expectCertificate && !tlsSocket.authorized) { | ||
|| (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) // 172.16.0.0 - 172.31.255.255 | ||
|| (octets[0] === 192 && octets[1] === 168); // 192.168.0.0 - 192.168.255.255 | ||
|| (octets[0] === 192 && octets[1] === 168) // 192.168.0.0 - 192.168.255.255 | ||
|| ip === "127.0.0.1"; | ||
} | ||
exports.ipIsPrivateV4Address = ipIsPrivateV4Address; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
@@ -38,5 +42,8 @@ if (k2 === undefined) k2 = k; | ||
} | ||
function stringIsNotBlank(str) { | ||
function isNotBlank(str) { | ||
return str.trim() !== ""; | ||
} | ||
function isNotMeta(str) { | ||
return !str.startsWith("total"); | ||
} | ||
const REGEX_NEWLINE = /\r?\n/; | ||
@@ -49,3 +56,4 @@ /** | ||
.split(REGEX_NEWLINE) | ||
.filter(stringIsNotBlank); | ||
.filter(isNotBlank) | ||
.filter(isNotMeta); | ||
if (lines.length === 0) { | ||
@@ -52,0 +60,0 @@ return []; |
@@ -36,3 +36,3 @@ "use strict"; | ||
info.link = value.substr(value.indexOf(":") + 1); | ||
return 1 /* Continue */; | ||
return 1 /* FactHandlerResult.Continue */; | ||
} | ||
@@ -53,7 +53,7 @@ switch (value) { | ||
case "pdir": // Parent directory | ||
return 2 /* IgnoreFile */; // Don't include these entries in the listing | ||
return 2 /* FactHandlerResult.IgnoreFile */; // Don't include these entries in the listing | ||
default: | ||
info.type = FileInfo_1.FileType.Unknown; | ||
} | ||
return 1 /* Continue */; | ||
return 1 /* FactHandlerResult.Continue */; | ||
}, | ||
@@ -139,3 +139,3 @@ "unix.mode": (value, info) => { | ||
const result = factHandler(factValue, info); | ||
if (result === 2 /* IgnoreFile */) { | ||
if (result === 2 /* FactHandlerResult.IgnoreFile */) { | ||
return undefined; | ||
@@ -142,0 +142,0 @@ } |
@@ -39,3 +39,3 @@ "use strict"; | ||
const RE_LINE = new RegExp("([bcdelfmpSs-])" // file type | ||
+ "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?" // permissions | ||
+ "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]?)))\\+?" // permissions | ||
+ "\\s*" // separator TODO why allow it to be omitted?? | ||
@@ -42,0 +42,0 @@ + "(\\d+)" // link count |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { Writable } from "stream"; | ||
@@ -3,0 +4,0 @@ import { StringEncoding } from "./StringEncoding"; |
@@ -5,2 +5,3 @@ "use strict"; | ||
const netUtils_1 = require("./netUtils"); | ||
const stream_1 = require("stream"); | ||
const tls_1 = require("tls"); | ||
@@ -56,3 +57,3 @@ const parseControlResponse_1 = require("./parseControlResponse"); | ||
const controlHost = ftp.socket.remoteAddress; | ||
if (netUtils_1.ipIsPrivateV4Address(target.host) && controlHost && !netUtils_1.ipIsPrivateV4Address(controlHost)) { | ||
if ((0, netUtils_1.ipIsPrivateV4Address)(target.host) && controlHost && !(0, netUtils_1.ipIsPrivateV4Address)(controlHost)) { | ||
target.host = controlHost; | ||
@@ -81,2 +82,3 @@ } | ||
return new Promise((resolve, reject) => { | ||
let socket = ftp._newSocket(); | ||
const handleConnErr = function (err) { | ||
@@ -86,7 +88,12 @@ err.message = "Can't open data connection in passive mode: " + err.message; | ||
}; | ||
let socket = ftp._newSocket(); | ||
const handleTimeout = function () { | ||
socket.destroy(); | ||
reject(new Error(`Timeout when trying to open data connection to ${host}:${port}`)); | ||
}; | ||
socket.setTimeout(ftp.timeout); | ||
socket.on("error", handleConnErr); | ||
socket.on("timeout", handleTimeout); | ||
socket.connect({ port, host, family: ftp.ipFamily }, () => { | ||
if (ftp.socket instanceof tls_1.TLSSocket) { | ||
socket = tls_1.connect(Object.assign({}, ftp.tlsOptions, { | ||
socket = (0, tls_1.connect)(Object.assign({}, ftp.tlsOptions, { | ||
socket, | ||
@@ -110,2 +117,3 @@ // Reuse the TLS session negotiated earlier when the control connection | ||
socket.removeListener("error", handleConnErr); | ||
socket.removeListener("timeout", handleTimeout); | ||
ftp.dataSocket = socket; | ||
@@ -220,14 +228,18 @@ resolve(); | ||
onConditionOrEvent(canUpload, dataSocket, "secureConnect", () => { | ||
config.ftp.log(`Uploading to ${netUtils_1.describeAddress(dataSocket)} (${netUtils_1.describeTLS(dataSocket)})`); | ||
config.ftp.log(`Uploading to ${(0, netUtils_1.describeAddress)(dataSocket)} (${(0, netUtils_1.describeTLS)(dataSocket)})`); | ||
resolver.onDataStart(config.remotePath, config.type); | ||
source.pipe(dataSocket).once("finish", () => { | ||
dataSocket.destroy(); // Explicitly close/destroy the socket to signal the end. | ||
resolver.onDataDone(task); | ||
(0, stream_1.pipeline)(source, dataSocket, err => { | ||
if (err) { | ||
resolver.onError(task, err); | ||
} | ||
else { | ||
resolver.onDataDone(task); | ||
} | ||
}); | ||
}); | ||
} | ||
else if (parseControlResponse_1.positiveCompletion(res.code)) { // Transfer complete | ||
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // Transfer complete | ||
resolver.onControlDone(task, res); | ||
} | ||
else if (parseControlResponse_1.positiveIntermediate(res.code)) { | ||
else if ((0, parseControlResponse_1.positiveIntermediate)(res.code)) { | ||
resolver.onUnexpectedRequest(res); | ||
@@ -243,5 +255,2 @@ } | ||
} | ||
// It's possible that data transmission begins before the control socket | ||
// receives the announcement. Start listening for data immediately. | ||
config.ftp.dataSocket.pipe(destination); | ||
const resolver = new TransferResolver(config.ftp, config.tracker); | ||
@@ -258,5 +267,12 @@ return config.ftp.handle(config.command, (res, task) => { | ||
} | ||
config.ftp.log(`Downloading from ${netUtils_1.describeAddress(dataSocket)} (${netUtils_1.describeTLS(dataSocket)})`); | ||
config.ftp.log(`Downloading from ${(0, netUtils_1.describeAddress)(dataSocket)} (${(0, netUtils_1.describeTLS)(dataSocket)})`); | ||
resolver.onDataStart(config.remotePath, config.type); | ||
onConditionOrEvent(isWritableFinished(destination), destination, "finish", () => resolver.onDataDone(task)); | ||
(0, stream_1.pipeline)(dataSocket, destination, err => { | ||
if (err) { | ||
resolver.onError(task, err); | ||
} | ||
else { | ||
resolver.onDataDone(task); | ||
} | ||
}); | ||
} | ||
@@ -266,6 +282,6 @@ else if (res.code === 350) { // Restarting at startAt. | ||
} | ||
else if (parseControlResponse_1.positiveCompletion(res.code)) { // Transfer complete | ||
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // Transfer complete | ||
resolver.onControlDone(task, res); | ||
} | ||
else if (parseControlResponse_1.positiveIntermediate(res.code)) { | ||
else if ((0, parseControlResponse_1.positiveIntermediate)(res.code)) { | ||
resolver.onUnexpectedRequest(res); | ||
@@ -294,13 +310,1 @@ } | ||
} | ||
/** | ||
* Detect whether a writable stream is finished, supporting Node 8. | ||
* From https://github.com/nodejs/node/blob/3e2a3007107b7a100794f4e4adbde19263fc7464/lib/internal/streams/end-of-stream.js#L28-L33 | ||
*/ | ||
function isWritableFinished(stream) { | ||
if (stream.writableFinished) | ||
return true; | ||
const wState = stream._writableState; | ||
if (!wState || wState.errored) | ||
return false; | ||
return wState.finished || (wState.ended && wState.length === 0); | ||
} |
{ | ||
"name": "basic-ftp", | ||
"version": "4.6.6", | ||
"version": "5.0.0", | ||
"description": "FTP client for Node.js, supports FTPS over TLS, IPv6, Async/Await, and Typescript.", | ||
@@ -37,15 +37,15 @@ "main": "dist/index", | ||
"engines": { | ||
"node": ">=8.0.0" | ||
"node": ">=10.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/mocha": "8.2.2", | ||
"@types/node": "14.14.37", | ||
"@typescript-eslint/eslint-plugin": "4.19.0", | ||
"@typescript-eslint/parser": "4.19.0", | ||
"eslint": "7.23.0", | ||
"mocha": "8.3.2", | ||
"mock-fs": "4.13.0", | ||
"@types/mocha": "9.1.1", | ||
"@types/node": "18.0.5", | ||
"@typescript-eslint/eslint-plugin": "5.30.6", | ||
"@typescript-eslint/parser": "5.30.6", | ||
"eslint": "8.19.0", | ||
"mocha": "10.0.0", | ||
"mock-fs": "5.1.2", | ||
"rimraf": "3.0.2", | ||
"typescript": "4.2.3" | ||
"typescript": "4.7.4" | ||
} | ||
} |
# Basic FTP | ||
[![Build Status](https://travis-ci.org/patrickjuchli/basic-ftp.svg?branch=master)](https://travis-ci.org/patrickjuchli/basic-ftp) [![dependencies](https://img.shields.io/david/patrickjuchli/basic-ftp)](https://david-dm.org/patrickjuchli/basic-ftp) [![npm version](https://img.shields.io/npm/v/basic-ftp.svg)](https://www.npmjs.com/package/basic-ftp) | ||
[![npm version](https://img.shields.io/npm/v/basic-ftp.svg)](https://www.npmjs.com/package/basic-ftp) | ||
[![npm downloads](https://img.shields.io/npm/dm/basic-ftp)](https://www.npmjs.com/package/basic-ftp) | ||
@@ -13,10 +14,15 @@ This is an FTP client for Node.js. It supports FTPS over TLS, Passive Mode over IPv6, has a Promise-based API, and offers methods to operate on whole directories. | ||
Node 8.0 or later is the only dependency. | ||
Node 10.0 or later is the only dependency. | ||
## Introduction | ||
## Installation | ||
`npm install basic-ftp` | ||
## Usage | ||
The first example will connect to an FTP server using TLS, get a directory listing, upload a file and download it as a copy. Note that the FTP protocol doesn't allow multiple requests running in parallel. | ||
```js | ||
const ftp = require("basic-ftp") | ||
const ftp = require("basic-ftp") | ||
// ESM: import * as ftp from "basic-ftp" | ||
@@ -127,9 +133,9 @@ example() | ||
`uploadFrom(readableStream | localPath, remotePath): Promise<FTPResponse>` | ||
`uploadFrom(readableStream | localPath, remotePath, [options]): Promise<FTPResponse>` | ||
Upload data from a readable stream or a local file to a remote file. If such a file already exists it will be overwritten. | ||
Upload data from a readable stream or a local file to a remote file. If such a file already exists it will be overwritten. If a file is being uploaded, additional options offer `localStart` and `localEndInclusive` to only upload parts of it. | ||
`appendFrom(readableStream | localPath, remotePath): Promise<FTPResponse>` | ||
`appendFrom(readableStream | localPath, remotePath, [options]): Promise<FTPResponse>` | ||
Upload data from a readable stream or a local file by appending it to an existing file. If the file doesn't exist the FTP server should create it. | ||
Upload data from a readable stream or a local file by appending it to an existing file. If the file doesn't exist the FTP server should create it. If a file is being uploaded, additional options offer `localStart` and `localEndInclusive` to only upload parts of it. | ||
@@ -136,0 +142,0 @@ `downloadTo(writableStream | localPath, remotePath, startAt = 0): Promise<FTPResponse>` |
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
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
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
0
100
3002
246
134508
31