Compare commits

..

No commits in common. "master" and "v4.3.3" have entirely different histories.

8 changed files with 1815 additions and 3157 deletions

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚚 Get latest code - name: 🚚 Get latest code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: 📂 Sync files - name: 📂 Sync files
uses: ./ uses: ./

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚚 Get latest code - name: 🚚 Get latest code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: 📂 Sync files - name: 📂 Sync files
uses: ./ uses: ./

View File

@ -20,10 +20,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚚 Get latest code - name: 🚚 Get latest code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: 📂 Sync files - name: 📂 Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5 uses: SamKirkland/FTP-Deploy-Action@4.3.3
with: with:
server: ftp.samkirkland.com server: ftp.samkirkland.com
username: myFtpUserName username: myFtpUserName
@ -67,7 +67,7 @@ I strongly recommend you store your `password` as a secret.
| `state-name` | No | `folder/.sync-state.json` | `.ftp-deploy-sync-state.json` | Path and name of the state file - this file is used to track which files have been deployed | | `state-name` | No | `folder/.sync-state.json` | `.ftp-deploy-sync-state.json` | Path and name of the state file - this file is used to track which files have been deployed |
| `dry-run` | No | `true` | `false` | Prints which modifications will be made with current config options, but doesn't actually make any changes | | `dry-run` | No | `true` | `false` | Prints which modifications will be made with current config options, but doesn't actually make any changes |
| `dangerous-clean-slate` | No | `true` | `false` | Deletes ALL contents of server-dir, even items in excluded with 'exclude' argument | | `dangerous-clean-slate` | No | `true` | `false` | Deletes ALL contents of server-dir, even items in excluded with 'exclude' argument |
| `exclude` | No | [See Example](#exclude-files) | [See Example](#exclude-files) | An array of glob patterns, these files will not be included in the publish/delete process. [List MUST be in this format](#exclude-files). You can use [a glob tester](https://www.digitalocean.com/community/tools/glob?comments=true&glob=%2A%2A%2F.git%2A%2F%2A%2A&matches=false&tests=test%2Fsam&tests=.git%2F%0D&tests=.github%2F%0D&tests=.git%2Ftest%0D&tests=.gitattributes%0D&tests=.gitignore%0D&tests=.git%2Fconfig%0D&tests=.git%2Ftest%2Ftest&tests=.github%2Fworkflows%2Fmain.yml&tests=node_modules%2Ffolder%2F%0D&tests=node_modules%2Fotherfolder%2F%0D&tests=subfolder%2Fnode_modules%2F) to test your pattern(s). | | `exclude` | No | [See Example](#exclude-files) | [See Example](#exclude-files) | An array of glob patterns, these files will not be included in the publish/delete process. [List MUST be in this format](#exclude-files). You can use [a glob tester](https://www.digitalocean.com/community/tools/glob?comments=true&glob=%2A%2A%2F.git%2A%2F%2A%2A&matches=false&tests=test%2Fsam&tests=.git%2F&tests=.github%2F&tests=.git%2Ftest&tests=.gitattributes&tests=.gitignore&tests=.git%2Fconfig&tests=.git%2Ftest%2Ftest&tests=.github%2Fworkflows%2Fmain.yml&tests=test%2F.git%2Fworkflows%2Fmain.yml&tests=node_modules%2Ffolder%2F&tests=node_modules%2Fotherfolder%2F&tests=subfolder%2Fnode_modules%2F) to test your pattern(s). |
| `log-level` | No | `minimal` | `standard` | `minimal`: only important info, `standard`: important info and basic file changes, `verbose`: print everything the script is doing | | `log-level` | No | `minimal` | `standard` | `minimal`: only important info, `standard`: important info and basic file changes, `verbose`: print everything the script is doing |
| `security` | No | `strict` | `loose` | `strict`: Reject any connection which is not authorized with the list of supplied CAs. `loose`: Allow connection even when the domain is not certificate | | `security` | No | `strict` | `loose` | `strict`: Reject any connection which is not authorized with the list of supplied CAs. `loose`: Allow connection even when the domain is not certificate |
| `timeout` | No | `60000` | `30000` | Timeout in milliseconds for FTP operations | | `timeout` | No | `60000` | `30000` | Timeout in milliseconds for FTP operations |
@ -86,7 +86,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚚 Get latest code - name: 🚚 Get latest code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Use Node.js 16 - name: Use Node.js 16
uses: actions/setup-node@v2 uses: actions/setup-node@v2
@ -99,7 +99,7 @@ jobs:
npm run build npm run build
- name: 📂 Sync files - name: 📂 Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5 uses: SamKirkland/FTP-Deploy-Action@4.3.3
with: with:
server: ftp.samkirkland.com server: ftp.samkirkland.com
username: myFtpUserName username: myFtpUserName
@ -116,10 +116,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚚 Get latest code - name: 🚚 Get latest code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: 📂 Sync files - name: 📂 Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5 uses: SamKirkland/FTP-Deploy-Action@4.3.3
with: with:
server: ftp.samkirkland.com server: ftp.samkirkland.com
username: myFtpUserName username: myFtpUserName
@ -139,10 +139,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚚 Get latest code - name: 🚚 Get latest code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: 📂 Sync files - name: 📂 Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5 uses: SamKirkland/FTP-Deploy-Action@4.3.3
with: with:
server: ftp.samkirkland.com server: ftp.samkirkland.com
username: myFtpUserName username: myFtpUserName
@ -161,10 +161,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚚 Get latest code - name: 🚚 Get latest code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: 📂 Sync files - name: 📂 Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5 uses: SamKirkland/FTP-Deploy-Action@4.3.3
with: with:
server: ftp.samkirkland.com server: ftp.samkirkland.com
username: myFtpUserName username: myFtpUserName

View File

@ -45,7 +45,7 @@ inputs:
required: false required: false
description: "Timeout in milliseconds for FTP operations" description: "Timeout in milliseconds for FTP operations"
runs: runs:
using: "node20" using: "node16"
main: "dist/index.js" main: "dist/index.js"
branding: branding:
icon: "upload-cloud" icon: "upload-cloud"

264
dist/index.js vendored
View File

@ -4116,14 +4116,14 @@ const netUtils_1 = __nccwpck_require__(6288);
const transfer_1 = __nccwpck_require__(5803); const transfer_1 = __nccwpck_require__(5803);
const parseControlResponse_1 = __nccwpck_require__(9948); const parseControlResponse_1 = __nccwpck_require__(9948);
// Use promisify to keep the library compatible with Node 8. // Use promisify to keep the library compatible with Node 8.
const fsReadDir = (0, util_1.promisify)(fs_1.readdir); const fsReadDir = util_1.promisify(fs_1.readdir);
const fsMkDir = (0, util_1.promisify)(fs_1.mkdir); const fsMkDir = util_1.promisify(fs_1.mkdir);
const fsStat = (0, util_1.promisify)(fs_1.stat); const fsStat = util_1.promisify(fs_1.stat);
const fsOpen = (0, util_1.promisify)(fs_1.open); const fsOpen = util_1.promisify(fs_1.open);
const fsClose = (0, util_1.promisify)(fs_1.close); const fsClose = util_1.promisify(fs_1.close);
const fsUnlink = (0, util_1.promisify)(fs_1.unlink); const fsUnlink = util_1.promisify(fs_1.unlink);
const LIST_COMMANDS_DEFAULT = () => ["LIST -a", "LIST"]; const LIST_COMMANDS_DEFAULT = ["LIST -a", "LIST"];
const LIST_COMMANDS_MLSD = () => ["MLSD", "LIST -a", "LIST"]; const LIST_COMMANDS_MLSD = ["MLSD", "LIST -a", "LIST"];
/** /**
* High-level API to interact with an FTP server. * High-level API to interact with an FTP server.
*/ */
@ -4134,7 +4134,7 @@ class Client {
* @param timeout Timeout in milliseconds, use 0 for no timeout. Optional, default is 30 seconds. * @param timeout Timeout in milliseconds, use 0 for no timeout. Optional, default is 30 seconds.
*/ */
constructor(timeout = 30000) { constructor(timeout = 30000) {
this.availableListCommands = LIST_COMMANDS_DEFAULT(); this.availableListCommands = LIST_COMMANDS_DEFAULT;
this.ftp = new FtpContext_1.FTPContext(timeout); this.ftp = new FtpContext_1.FTPContext(timeout);
this.prepareTransfer = this._enterFirstCompatibleMode([transfer_1.enterPassiveModeIPv6, transfer_1.enterPassiveModeIPv4]); this.prepareTransfer = this._enterFirstCompatibleMode([transfer_1.enterPassiveModeIPv6, transfer_1.enterPassiveModeIPv4]);
this.parseList = parseList_1.parseList; this.parseList = parseList_1.parseList;
@ -4174,7 +4174,7 @@ class Client {
host, host,
port, port,
family: this.ftp.ipFamily family: this.ftp.ipFamily
}, () => this.ftp.log(`Connected to ${(0, netUtils_1.describeAddress)(this.ftp.socket)} (${(0, netUtils_1.describeTLS)(this.ftp.socket)})`)); }, () => this.ftp.log(`Connected to ${netUtils_1.describeAddress(this.ftp.socket)} (${netUtils_1.describeTLS(this.ftp.socket)})`));
return this._handleConnectResponse(); return this._handleConnectResponse();
} }
/** /**
@ -4183,7 +4183,7 @@ class Client {
*/ */
connectImplicitTLS(host = "localhost", port = 21, tlsOptions = {}) { connectImplicitTLS(host = "localhost", port = 21, tlsOptions = {}) {
this.ftp.reset(); this.ftp.reset();
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.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.tlsOptions = tlsOptions; this.ftp.tlsOptions = tlsOptions;
return this._handleConnectResponse(); return this._handleConnectResponse();
} }
@ -4196,13 +4196,14 @@ class Client {
// The connection has been destroyed by the FTPContext at this point. // The connection has been destroyed by the FTPContext at this point.
task.reject(res); task.reject(res);
} }
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { else if (parseControlResponse_1.positiveCompletion(res.code)) {
task.resolve(res); task.resolve(res);
} }
// Reject all other codes, including 120 "Service ready in nnn minutes". // Reject all other codes, including 120 "Service ready in nnn minutes".
else { else {
// Don't stay connected but don't replace the socket yet by using reset() // Don't stay connected but don't replace the socket yet by using reset()
// so the user can inspect properties of this instance. // so the user can inspect properties of this instance.
this.ftp.socket.destroy();
task.reject(new FtpContext_1.FTPError(res)); task.reject(new FtpContext_1.FTPError(res));
} }
}); });
@ -4243,9 +4244,9 @@ class Client {
*/ */
async useTLS(options = {}, command = "AUTH TLS") { async useTLS(options = {}, command = "AUTH TLS") {
const ret = await this.send(command); const ret = await this.send(command);
this.ftp.socket = await (0, netUtils_1.upgradeSocket)(this.ftp.socket, options); this.ftp.socket = await 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.tlsOptions = options; // Keep the TLS options for later data connections that should use the same options.
this.ftp.log(`Control socket is using: ${(0, netUtils_1.describeTLS)(this.ftp.socket)}`); this.ftp.log(`Control socket is using: ${netUtils_1.describeTLS(this.ftp.socket)}`);
return ret; return ret;
} }
/** /**
@ -4255,12 +4256,12 @@ class Client {
* @param password Password to use for login. Optional, default is "guest". * @param password Password to use for login. Optional, default is "guest".
*/ */
login(user = "anonymous", password = "guest") { login(user = "anonymous", password = "guest") {
this.ftp.log(`Login security: ${(0, netUtils_1.describeTLS)(this.ftp.socket)}`); this.ftp.log(`Login security: ${netUtils_1.describeTLS(this.ftp.socket)}`);
return this.ftp.handle("USER " + user, (res, task) => { return this.ftp.handle("USER " + user, (res, task) => {
if (res instanceof Error) { if (res instanceof Error) {
task.reject(res); task.reject(res);
} }
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // User logged in proceed OR Command superfluous else if (parseControlResponse_1.positiveCompletion(res.code)) { // User logged in proceed OR Command superfluous
task.resolve(res); task.resolve(res);
} }
else if (res.code === 331) { // User name okay, need password else if (res.code === 331) { // User name okay, need password
@ -4284,10 +4285,10 @@ class Client {
// Use MLSD directory listing if possible. See https://tools.ietf.org/html/rfc3659#section-7.8: // Use MLSD directory listing if possible. See https://tools.ietf.org/html/rfc3659#section-7.8:
// "The presence of the MLST feature indicates that both MLST and MLSD are supported." // "The presence of the MLST feature indicates that both MLST and MLSD are supported."
const supportsMLSD = features.has("MLST"); const supportsMLSD = features.has("MLST");
this.availableListCommands = supportsMLSD ? LIST_COMMANDS_MLSD() : LIST_COMMANDS_DEFAULT(); this.availableListCommands = supportsMLSD ? LIST_COMMANDS_MLSD : LIST_COMMANDS_DEFAULT;
await this.send("TYPE I"); // Binary mode await this.send("TYPE I"); // Binary mode
await this.sendIgnoringError("STRU F"); // Use file structure await this.sendIgnoringError("STRU F"); // Use file structure
await this.sendIgnoringError("OPTS UTF8 ON"); // Some servers expect UTF-8 to be enabled explicitly and setting before login might not have worked. await this.sendIgnoringError("OPTS UTF8 ON"); // Some servers expect UTF-8 to be enabled explicitly
if (supportsMLSD) { if (supportsMLSD) {
await this.sendIgnoringError("OPTS MLST type;size;modify;unique;unix.mode;unix.owner;unix.group;unix.ownername;unix.groupname;"); // Make sure MLSD listings include all we can parse await this.sendIgnoringError("OPTS MLST type;size;modify;unique;unix.mode;unix.owner;unix.group;unix.ownername;unix.groupname;"); // Make sure MLSD listings include all we can parse
} }
@ -4322,9 +4323,6 @@ class Client {
secureOptions.host = (_b = secureOptions.host) !== null && _b !== void 0 ? _b : options.host; secureOptions.host = (_b = secureOptions.host) !== null && _b !== void 0 ? _b : options.host;
await this.useTLS(secureOptions); await this.useTLS(secureOptions);
} }
// Set UTF-8 on before login in case there are non-ascii characters in user or password.
// Note that this might not work before login depending on server.
await this.sendIgnoringError("OPTS UTF8 ON");
await this.login(options.user, options.password); await this.login(options.user, options.password);
await this.useDefaultSettings(); await this.useDefaultSettings();
return welcome; return welcome;
@ -4353,7 +4351,7 @@ class Client {
const res = await this.sendIgnoringError("FEAT"); const res = await this.sendIgnoringError("FEAT");
const features = new Map(); const features = new Map();
// Not supporting any special features will be reported with a single line. // Not supporting any special features will be reported with a single line.
if (res.code < 400 && (0, parseControlResponse_1.isMultiline)(res.message)) { if (res.code < 400 && parseControlResponse_1.isMultiline(res.message)) {
// The first and last line wrap the multiline response, ignore them. // The first and last line wrap the multiline response, ignore them.
res.message.split("\n").slice(1, -1).forEach(line => { res.message.split("\n").slice(1, -1).forEach(line => {
// A typical lines looks like: " REST STREAM" or " MDTM". // A typical lines looks like: " REST STREAM" or " MDTM".
@ -4385,7 +4383,7 @@ class Client {
const validPath = await this.protectWhitespace(path); const validPath = await this.protectWhitespace(path);
const res = await this.send(`MDTM ${validPath}`); const res = await this.send(`MDTM ${validPath}`);
const date = res.message.slice(4); const date = res.message.slice(4);
return (0, parseListMLSD_1.parseMLSxDate)(date); return parseListMLSD_1.parseMLSxDate(date);
} }
/** /**
* Get the size of a file. * Get the size of a file.
@ -4422,10 +4420,7 @@ class Client {
*/ */
async remove(path, ignoreErrorCodes = false) { async remove(path, ignoreErrorCodes = false) {
const validPath = await this.protectWhitespace(path); const validPath = await this.protectWhitespace(path);
if (ignoreErrorCodes) { return this.send(`DELE ${validPath}`, ignoreErrorCodes);
return this.sendIgnoringError(`DELE ${validPath}`);
}
return this.send(`DELE ${validPath}`);
} }
/** /**
* Report transfer progress for any upload or download to a given handler. * Report transfer progress for any upload or download to a given handler.
@ -4472,7 +4467,7 @@ class Client {
*/ */
async _uploadLocalFile(localPath, remotePath, command, options) { async _uploadLocalFile(localPath, remotePath, command, options) {
const fd = await fsOpen(localPath, "r"); const fd = await fsOpen(localPath, "r");
const source = (0, fs_1.createReadStream)("", { const source = fs_1.createReadStream("", {
fd, fd,
start: options.localStart, start: options.localStart,
end: options.localEndInclusive, end: options.localEndInclusive,
@ -4496,7 +4491,7 @@ class Client {
await this.prepareTransfer(this.ftp); await this.prepareTransfer(this.ftp);
// Keep the keyword `await` or the `finally` clause below runs too early // Keep the keyword `await` or the `finally` clause below runs too early
// and removes the event listener for the source stream too early. // and removes the event listener for the source stream too early.
return await (0, transfer_1.uploadFrom)(source, { return await transfer_1.uploadFrom(source, {
ftp: this.ftp, ftp: this.ftp,
tracker: this._progressTracker, tracker: this._progressTracker,
command, command,
@ -4534,7 +4529,7 @@ class Client {
const appendingToLocalFile = startAt > 0; const appendingToLocalFile = startAt > 0;
const fileSystemFlags = appendingToLocalFile ? "r+" : "w"; const fileSystemFlags = appendingToLocalFile ? "r+" : "w";
const fd = await fsOpen(localPath, fileSystemFlags); const fd = await fsOpen(localPath, fileSystemFlags);
const destination = (0, fs_1.createWriteStream)("", { const destination = fs_1.createWriteStream("", {
fd, fd,
start: startAt, start: startAt,
autoClose: false autoClose: false
@ -4566,7 +4561,7 @@ class Client {
await this.prepareTransfer(this.ftp); await this.prepareTransfer(this.ftp);
// Keep the keyword `await` or the `finally` clause below runs too early // Keep the keyword `await` or the `finally` clause below runs too early
// and removes the event listener for the source stream too early. // and removes the event listener for the source stream too early.
return await (0, transfer_1.downloadTo)(destination, { return await transfer_1.downloadTo(destination, {
ftp: this.ftp, ftp: this.ftp,
tracker: this._progressTracker, tracker: this._progressTracker,
command: startAt > 0 ? `REST ${startAt}` : `RETR ${validPath}`, command: startAt > 0 ? `REST ${startAt}` : `RETR ${validPath}`,
@ -4611,7 +4606,7 @@ class Client {
*/ */
async _requestListWithCommand(command) { async _requestListWithCommand(command) {
const buffer = new StringWriter_1.StringWriter(); const buffer = new StringWriter_1.StringWriter();
await (0, transfer_1.downloadTo)(buffer, { await transfer_1.downloadTo(buffer, {
ftp: this.ftp, ftp: this.ftp,
tracker: this._progressTracker, tracker: this._progressTracker,
command, command,
@ -4634,13 +4629,10 @@ class Client {
async removeDir(remoteDirPath) { async removeDir(remoteDirPath) {
return this._exitAtCurrentDirectory(async () => { return this._exitAtCurrentDirectory(async () => {
await this.cd(remoteDirPath); await this.cd(remoteDirPath);
// Get the absolute path of the target because remoteDirPath might be a relative path, even `../` is possible.
const absoluteDirPath = await this.pwd();
await this.clearWorkingDir(); await this.clearWorkingDir();
const dirIsRoot = absoluteDirPath === "/"; if (remoteDirPath !== "/") {
if (!dirIsRoot) {
await this.cdup(); await this.cdup();
await this.removeEmptyDir(absoluteDirPath); await this.removeEmptyDir(remoteDirPath);
} }
}); });
} }
@ -4687,7 +4679,7 @@ class Client {
async _uploadToWorkingDir(localDirPath) { async _uploadToWorkingDir(localDirPath) {
const files = await fsReadDir(localDirPath); const files = await fsReadDir(localDirPath);
for (const file of files) { for (const file of files) {
const fullPath = (0, path_1.join)(localDirPath, file); const fullPath = path_1.join(localDirPath, file);
const stats = await fsStat(fullPath); const stats = await fsStat(fullPath);
if (stats.isFile()) { if (stats.isFile()) {
await this.uploadFrom(fullPath, file); await this.uploadFrom(fullPath, file);
@ -4719,7 +4711,7 @@ class Client {
async _downloadFromWorkingDir(localDirPath) { async _downloadFromWorkingDir(localDirPath) {
await ensureLocalDirectory(localDirPath); await ensureLocalDirectory(localDirPath);
for (const file of await this.list()) { for (const file of await this.list()) {
const localPath = (0, path_1.join)(localDirPath, file.name); const localPath = path_1.join(localDirPath, file.name);
if (file.isDirectory) { if (file.isDirectory) {
await this.cd(file.name); await this.cd(file.name);
await this._downloadFromWorkingDir(localPath); await this._downloadFromWorkingDir(localPath);
@ -4789,26 +4781,26 @@ class Client {
* Try all available transfer strategies and pick the first one that works. Update `client` to * Try all available transfer strategies and pick the first one that works. Update `client` to
* use the working strategy for all successive transfer requests. * use the working strategy for all successive transfer requests.
* *
* @param transferModes
* @returns a function that will try the provided strategies. * @returns a function that will try the provided strategies.
*/ */
_enterFirstCompatibleMode(strategies) { _enterFirstCompatibleMode(transferModes) {
return async (ftp) => { return async (ftp) => {
ftp.log("Trying to find optimal transfer strategy..."); ftp.log("Trying to find optimal transfer mode...");
let lastError = undefined; for (const transferMode of transferModes) {
for (const strategy of strategies) {
try { try {
const res = await strategy(ftp); const res = await transferMode(ftp);
ftp.log("Optimal transfer strategy found."); ftp.log("Optimal transfer mode found.");
this.prepareTransfer = strategy; // eslint-disable-line require-atomic-updates this.prepareTransfer = transferMode; // eslint-disable-line require-atomic-updates
return res; return res;
} }
catch (err) { catch (err) {
// Try the next candidate no matter the exact error. It's possible that a server // 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. // answered incorrectly to a strategy, for example a PASV answer to an EPSV.
lastError = err; ftp.log(`Transfer mode failed: "${err.message}", will try next.`);
} }
} }
throw new Error(`None of the available transfer strategies work. Last error response was '${lastError}'.`); throw new Error("None of the available transfer modes work.");
}; };
} }
/** /**
@ -4887,7 +4879,7 @@ var FileType;
FileType[FileType["File"] = 1] = "File"; FileType[FileType["File"] = 1] = "File";
FileType[FileType["Directory"] = 2] = "Directory"; FileType[FileType["Directory"] = 2] = "Directory";
FileType[FileType["SymbolicLink"] = 3] = "SymbolicLink"; FileType[FileType["SymbolicLink"] = 3] = "SymbolicLink";
})(FileType || (exports.FileType = FileType = {})); })(FileType = exports.FileType || (exports.FileType = {}));
/** /**
* Describes a file, directory or symbolic link. * Describes a file, directory or symbolic link.
*/ */
@ -4994,9 +4986,6 @@ class FTPError extends Error {
} }
} }
exports.FTPError = FTPError; exports.FTPError = FTPError;
function doNothing() {
/** Do nothing */
}
/** /**
* FTPContext holds the control and data sockets of an FTP connection and provides a * FTPContext holds the control and data sockets of an FTP connection and provides a
* simplified way to interact with an FTP server, handle responses, errors and timeouts. * simplified way to interact with an FTP server, handle responses, errors and timeouts.
@ -5049,8 +5038,8 @@ class FTPContext {
return; return;
} }
this._closingError = err; this._closingError = err;
// Close the sockets but don't fully reset this context to preserve `this._closingError`. // Before giving the user's task a chance to react, make sure we won't be bothered with any inputs.
this._closeControlSocket(); this._closeSocket(this._socket);
this._closeSocket(this._dataSocket); this._closeSocket(this._dataSocket);
// Give the user's task a chance to react, maybe cleanup resources. // Give the user's task a chance to react, maybe cleanup resources.
this._passToHandler(err); this._passToHandler(err);
@ -5082,17 +5071,16 @@ class FTPContext {
set socket(socket) { set socket(socket) {
// No data socket should be open in any case where the control socket is set or upgraded. // No data socket should be open in any case where the control socket is set or upgraded.
this.dataSocket = undefined; this.dataSocket = undefined;
// This being a reset, reset any other state apart from the socket.
this.tlsOptions = {}; this.tlsOptions = {};
// This being a soft reset, remove any remaining partial response.
this._partialResponse = ""; this._partialResponse = "";
if (this._socket) { if (this._socket) {
const newSocketUpgradesExisting = socket.localPort === this._socket.localPort; // Only close the current connection if the new is not an upgrade.
if (newSocketUpgradesExisting) { const isUpgrade = socket.localPort === this._socket.localPort;
this._removeSocketListeners(this.socket); if (!isUpgrade) {
} this._socket.destroy();
else {
this._closeControlSocket();
} }
this._removeSocketListeners(this._socket);
} }
if (socket) { if (socket) {
// Setting a completely new control socket is in essence something like a reset. That's // Setting a completely new control socket is in essence something like a reset. That's
@ -5180,6 +5168,7 @@ class FTPContext {
*/ */
handle(command, responseHandler) { handle(command, responseHandler) {
if (this._task) { if (this._task) {
// The user or client instance called `handle()` while a task is still running.
const err = new Error("User launched a task while another one is still running. Forgot to use 'await' or '.then()'?"); const err = new Error("User launched a task while another one is still running. Forgot to use 'await' or '.then()'?");
err.stack += `\nRunning task launched at: ${this._task.stack}`; err.stack += `\nRunning task launched at: ${this._task.stack}`;
this.closeWithError(err); this.closeWithError(err);
@ -5187,25 +5176,27 @@ class FTPContext {
// because the context closed already. That way, users will receive an exception where // because the context closed already. That way, users will receive an exception where
// they called this method by mistake. // they called this method by mistake.
} }
return new Promise((resolveTask, rejectTask) => { return new Promise((resolvePromise, rejectPromise) => {
this._task = { const stack = new Error().stack || "Unknown call stack";
stack: new Error().stack || "Unknown call stack", const resolver = {
responseHandler, resolve: (arg) => {
resolver: { this._stopTrackingTask();
resolve: arg => { resolvePromise(arg);
this._stopTrackingTask(); },
resolveTask(arg); reject: err => {
}, this._stopTrackingTask();
reject: err => { rejectPromise(err);
this._stopTrackingTask();
rejectTask(err);
}
} }
}; };
this._task = {
stack,
resolver,
responseHandler
};
if (this._closingError) { if (this._closingError) {
// This client has been closed. Provide an error that describes this one as being caused // This client has been closed. Provide an error that describes this one as being caused
// by `_closingError`, include stack traces for both. // by `_closingError`, include stack traces for both.
const err = new Error(`Client is closed because ${this._closingError.message}`); // Type 'Error' is not correctly defined, doesn't have 'code'. const err = new Error("Client is closed"); // Type 'Error' is not correctly defined, doesn't have 'code'.
err.stack += `\nClosing reason: ${this._closingError.stack}`; err.stack += `\nClosing reason: ${this._closingError.stack}`;
err.code = this._closingError.code !== undefined ? this._closingError.code : "0"; err.code = this._closingError.code !== undefined ? this._closingError.code : "0";
this._passToHandler(err); this._passToHandler(err);
@ -5253,7 +5244,7 @@ class FTPContext {
this.log(`< ${chunk}`); this.log(`< ${chunk}`);
// This chunk might complete an earlier partial response. // This chunk might complete an earlier partial response.
const completeResponse = this._partialResponse + chunk; const completeResponse = this._partialResponse + chunk;
const parsed = (0, parseControlResponse_1.parseControlResponse)(completeResponse); const parsed = parseControlResponse_1.parseControlResponse(completeResponse);
// Remember any incomplete remainder. // Remember any incomplete remainder.
this._partialResponse = parsed.rest; this._partialResponse = parsed.rest;
// Each response group is passed along individually. // Each response group is passed along individually.
@ -5294,29 +5285,16 @@ class FTPContext {
this.closeWithError(new Error(`Socket closed due to transmission error (${identifier})`)); this.closeWithError(new Error(`Socket closed due to transmission error (${identifier})`));
} }
}); });
socket.once("timeout", () => { socket.once("timeout", () => this.closeWithError(new Error(`Timeout (${identifier})`)));
socket.destroy();
this.closeWithError(new Error(`Timeout (${identifier})`));
});
} }
/** /**
* Close the control socket. Sends QUIT, then FIN, and ignores any response or error. * Close a socket.
*/
_closeControlSocket() {
this._removeSocketListeners(this._socket);
this._socket.on("error", doNothing);
this.send("QUIT");
this._closeSocket(this._socket);
}
/**
* Close a socket, ignores any error.
* @protected * @protected
*/ */
_closeSocket(socket) { _closeSocket(socket) {
if (socket) { if (socket) {
this._removeSocketListeners(socket);
socket.on("error", doNothing);
socket.destroy(); socket.destroy();
this._removeSocketListeners(socket);
} }
} }
/** /**
@ -5475,11 +5453,7 @@ exports.StringWriter = StringWriter;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k; if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k); Object.defineProperty(o, k2, { enumerable: true, get: function() { return 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) { }) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k; if (k2 === undefined) k2 = k;
o[k2] = m[k]; o[k2] = m[k];
@ -5541,7 +5515,7 @@ function upgradeSocket(socket, options) {
const tlsOptions = Object.assign({}, options, { const tlsOptions = Object.assign({}, options, {
socket socket
}); });
const tlsSocket = (0, tls_1.connect)(tlsOptions, () => { const tlsSocket = tls_1.connect(tlsOptions, () => {
const expectCertificate = tlsOptions.rejectUnauthorized !== false; const expectCertificate = tlsOptions.rejectUnauthorized !== false;
if (expectCertificate && !tlsSocket.authorized) { if (expectCertificate && !tlsSocket.authorized) {
reject(tlsSocket.authorizationError); reject(tlsSocket.authorizationError);
@ -5571,8 +5545,7 @@ function ipIsPrivateV4Address(ip = "") {
const octets = ip.split(".").map(o => parseInt(o, 10)); const octets = ip.split(".").map(o => parseInt(o, 10));
return octets[0] === 10 // 10.0.0.0 - 10.255.255.255 return octets[0] === 10 // 10.0.0.0 - 10.255.255.255
|| (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) // 172.16.0.0 - 172.31.255.255 || (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; exports.ipIsPrivateV4Address = ipIsPrivateV4Address;
@ -5661,11 +5634,7 @@ function isNotBlank(str) {
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k; if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k); Object.defineProperty(o, k2, { enumerable: true, get: function() { return 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) { }) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k; if (k2 === undefined) k2 = k;
o[k2] = m[k]; o[k2] = m[k];
@ -5699,12 +5668,9 @@ const availableParsers = [
function firstCompatibleParser(line, parsers) { function firstCompatibleParser(line, parsers) {
return parsers.find(parser => parser.testLine(line) === true); return parsers.find(parser => parser.testLine(line) === true);
} }
function isNotBlank(str) { function stringIsNotBlank(str) {
return str.trim() !== ""; return str.trim() !== "";
} }
function isNotMeta(str) {
return !str.startsWith("total");
}
const REGEX_NEWLINE = /\r?\n/; const REGEX_NEWLINE = /\r?\n/;
/** /**
* Parse raw directory listing. * Parse raw directory listing.
@ -5712,8 +5678,7 @@ const REGEX_NEWLINE = /\r?\n/;
function parseList(rawList) { function parseList(rawList) {
const lines = rawList const lines = rawList
.split(REGEX_NEWLINE) .split(REGEX_NEWLINE)
.filter(isNotBlank) .filter(stringIsNotBlank);
.filter(isNotMeta);
if (lines.length === 0) { if (lines.length === 0) {
return []; return [];
} }
@ -5808,8 +5773,8 @@ function parseSize(value, info) {
* Parsers for MLSD facts. * Parsers for MLSD facts.
*/ */
const factHandlersByName = { const factHandlersByName = {
"size": parseSize, // File size "size": parseSize,
"sizd": parseSize, // Directory size "sizd": parseSize,
"unique": (value, info) => { "unique": (value, info) => {
info.uniqueID = value; info.uniqueID = value;
}, },
@ -5832,7 +5797,7 @@ const factHandlersByName = {
if (value.startsWith("OS.unix=slink")) { if (value.startsWith("OS.unix=slink")) {
info.type = FileInfo_1.FileType.SymbolicLink; info.type = FileInfo_1.FileType.SymbolicLink;
info.link = value.substr(value.indexOf(":") + 1); info.link = value.substr(value.indexOf(":") + 1);
return 1 /* FactHandlerResult.Continue */; return 1 /* Continue */;
} }
switch (value) { switch (value) {
case "file": case "file":
@ -5848,11 +5813,11 @@ const factHandlersByName = {
break; break;
case "cdir": // Current directory being listed case "cdir": // Current directory being listed
case "pdir": // Parent directory case "pdir": // Parent directory
return 2 /* FactHandlerResult.IgnoreFile */; // Don't include these entries in the listing return 2 /* IgnoreFile */; // Don't include these entries in the listing
default: default:
info.type = FileInfo_1.FileType.Unknown; info.type = FileInfo_1.FileType.Unknown;
} }
return 1 /* FactHandlerResult.Continue */; return 1 /* Continue */;
}, },
"unix.mode": (value, info) => { "unix.mode": (value, info) => {
const digits = value.substr(-3); const digits = value.substr(-3);
@ -5933,7 +5898,7 @@ function parseLine(line) {
continue; continue;
} }
const result = factHandler(factValue, info); const result = factHandler(factValue, info);
if (result === 2 /* FactHandlerResult.IgnoreFile */) { if (result === 2 /* IgnoreFile */) {
return undefined; return undefined;
} }
} }
@ -6031,7 +5996,7 @@ const JA_YEAR = "\u5e74";
* {@code @} file has extended attributes * {@code @} file has extended attributes
*/ */
const RE_LINE = new RegExp("([bcdelfmpSs-])" // file type 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?? + "\\s*" // separator TODO why allow it to be omitted??
+ "(\\d+)" // link count + "(\\d+)" // link count
+ "\\s+" // separator + "\\s+" // separator
@ -6161,7 +6126,6 @@ function parseMode(r, w, x) {
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.downloadTo = exports.uploadFrom = exports.connectForPassiveTransfer = exports.parsePasvResponse = exports.enterPassiveModeIPv4 = exports.parseEpsvResponse = exports.enterPassiveModeIPv6 = void 0; exports.downloadTo = exports.uploadFrom = exports.connectForPassiveTransfer = exports.parsePasvResponse = exports.enterPassiveModeIPv4 = exports.parseEpsvResponse = exports.enterPassiveModeIPv6 = void 0;
const netUtils_1 = __nccwpck_require__(6288); const netUtils_1 = __nccwpck_require__(6288);
const stream_1 = __nccwpck_require__(2781);
const tls_1 = __nccwpck_require__(4404); const tls_1 = __nccwpck_require__(4404);
const parseControlResponse_1 = __nccwpck_require__(9948); const parseControlResponse_1 = __nccwpck_require__(9948);
/** /**
@ -6212,7 +6176,7 @@ async function enterPassiveModeIPv4(ftp) {
// We can't always perform this replacement because it's possible (although unlikely) that the FTP server // We can't always perform this replacement because it's possible (although unlikely) that the FTP server
// indeed uses a different host for data connections. // indeed uses a different host for data connections.
const controlHost = ftp.socket.remoteAddress; const controlHost = ftp.socket.remoteAddress;
if ((0, netUtils_1.ipIsPrivateV4Address)(target.host) && controlHost && !(0, netUtils_1.ipIsPrivateV4Address)(controlHost)) { if (netUtils_1.ipIsPrivateV4Address(target.host) && controlHost && !netUtils_1.ipIsPrivateV4Address(controlHost)) {
target.host = controlHost; target.host = controlHost;
} }
await connectForPassiveTransfer(target.host, target.port, ftp); await connectForPassiveTransfer(target.host, target.port, ftp);
@ -6236,21 +6200,15 @@ function parsePasvResponse(message) {
exports.parsePasvResponse = parsePasvResponse; exports.parsePasvResponse = parsePasvResponse;
function connectForPassiveTransfer(host, port, ftp) { function connectForPassiveTransfer(host, port, ftp) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let socket = ftp._newSocket();
const handleConnErr = function (err) { const handleConnErr = function (err) {
err.message = "Can't open data connection in passive mode: " + err.message; err.message = "Can't open data connection in passive mode: " + err.message;
reject(err); reject(err);
}; };
const handleTimeout = function () { let socket = ftp._newSocket();
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("error", handleConnErr);
socket.on("timeout", handleTimeout);
socket.connect({ port, host, family: ftp.ipFamily }, () => { socket.connect({ port, host, family: ftp.ipFamily }, () => {
if (ftp.socket instanceof tls_1.TLSSocket) { if (ftp.socket instanceof tls_1.TLSSocket) {
socket = (0, tls_1.connect)(Object.assign({}, ftp.tlsOptions, { socket = tls_1.connect(Object.assign({}, ftp.tlsOptions, {
socket, socket,
// Reuse the TLS session negotiated earlier when the control connection // Reuse the TLS session negotiated earlier when the control connection
// was upgraded. Servers expect this because it provides additional // was upgraded. Servers expect this because it provides additional
@ -6269,7 +6227,6 @@ function connectForPassiveTransfer(host, port, ftp) {
} }
// Let the FTPContext listen to errors from now on, remove local handler. // Let the FTPContext listen to errors from now on, remove local handler.
socket.removeListener("error", handleConnErr); socket.removeListener("error", handleConnErr);
socket.removeListener("timeout", handleTimeout);
ftp.dataSocket = socket; ftp.dataSocket = socket;
resolve(); resolve();
}); });
@ -6379,22 +6336,18 @@ function uploadFrom(source, config) {
// 'secureConnect'. If this hasn't happened yet, getCipher() returns undefined. // 'secureConnect'. If this hasn't happened yet, getCipher() returns undefined.
const canUpload = "getCipher" in dataSocket ? dataSocket.getCipher() !== undefined : true; const canUpload = "getCipher" in dataSocket ? dataSocket.getCipher() !== undefined : true;
onConditionOrEvent(canUpload, dataSocket, "secureConnect", () => { onConditionOrEvent(canUpload, dataSocket, "secureConnect", () => {
config.ftp.log(`Uploading to ${(0, netUtils_1.describeAddress)(dataSocket)} (${(0, netUtils_1.describeTLS)(dataSocket)})`); config.ftp.log(`Uploading to ${netUtils_1.describeAddress(dataSocket)} (${netUtils_1.describeTLS(dataSocket)})`);
resolver.onDataStart(config.remotePath, config.type); resolver.onDataStart(config.remotePath, config.type);
(0, stream_1.pipeline)(source, dataSocket, err => { source.pipe(dataSocket).once("finish", () => {
if (err) { dataSocket.destroy(); // Explicitly close/destroy the socket to signal the end.
resolver.onError(task, err); resolver.onDataDone(task);
}
else {
resolver.onDataDone(task);
}
}); });
}); });
} }
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // Transfer complete else if (parseControlResponse_1.positiveCompletion(res.code)) { // Transfer complete
resolver.onControlDone(task, res); resolver.onControlDone(task, res);
} }
else if ((0, parseControlResponse_1.positiveIntermediate)(res.code)) { else if (parseControlResponse_1.positiveIntermediate(res.code)) {
resolver.onUnexpectedRequest(res); resolver.onUnexpectedRequest(res);
} }
// Ignore all other positive preliminary response codes (< 200) // Ignore all other positive preliminary response codes (< 200)
@ -6405,6 +6358,9 @@ function downloadTo(destination, config) {
if (!config.ftp.dataSocket) { if (!config.ftp.dataSocket) {
throw new Error("Download will be initiated but no data connection is available."); throw new Error("Download will be initiated but no data connection is available.");
} }
// 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); const resolver = new TransferResolver(config.ftp, config.tracker);
return config.ftp.handle(config.command, (res, task) => { return config.ftp.handle(config.command, (res, task) => {
if (res instanceof Error) { if (res instanceof Error) {
@ -6416,24 +6372,17 @@ function downloadTo(destination, config) {
resolver.onError(task, new Error("Download should begin but no data connection is available.")); resolver.onError(task, new Error("Download should begin but no data connection is available."));
return; return;
} }
config.ftp.log(`Downloading from ${(0, netUtils_1.describeAddress)(dataSocket)} (${(0, netUtils_1.describeTLS)(dataSocket)})`); config.ftp.log(`Downloading from ${netUtils_1.describeAddress(dataSocket)} (${netUtils_1.describeTLS(dataSocket)})`);
resolver.onDataStart(config.remotePath, config.type); resolver.onDataStart(config.remotePath, config.type);
(0, stream_1.pipeline)(dataSocket, destination, err => { onConditionOrEvent(isWritableFinished(destination), destination, "finish", () => resolver.onDataDone(task));
if (err) {
resolver.onError(task, err);
}
else {
resolver.onDataDone(task);
}
});
} }
else if (res.code === 350) { // Restarting at startAt. else if (res.code === 350) { // Restarting at startAt.
config.ftp.send("RETR " + config.remotePath); config.ftp.send("RETR " + config.remotePath);
} }
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // Transfer complete else if (parseControlResponse_1.positiveCompletion(res.code)) { // Transfer complete
resolver.onControlDone(task, res); resolver.onControlDone(task, res);
} }
else if ((0, parseControlResponse_1.positiveIntermediate)(res.code)) { else if (parseControlResponse_1.positiveIntermediate(res.code)) {
resolver.onUnexpectedRequest(res); resolver.onUnexpectedRequest(res);
} }
// Ignore all other positive preliminary response codes (< 200) // Ignore all other positive preliminary response codes (< 200)
@ -6457,6 +6406,18 @@ function onConditionOrEvent(condition, emitter, eventName, action) {
emitter.once(eventName, () => action()); emitter.once(eventName, () => action());
} }
} }
/**
* 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);
}
/***/ }), /***/ }),
@ -9076,8 +9037,7 @@ async function runDeployment() {
"dangerous-clean-slate": (0, parse_1.optionalBoolean)("dangerous-clean-slate", core.getInput("dangerous-clean-slate")), "dangerous-clean-slate": (0, parse_1.optionalBoolean)("dangerous-clean-slate", core.getInput("dangerous-clean-slate")),
"exclude": (0, parse_1.optionalStringArray)("exclude", core.getMultilineInput("exclude")), "exclude": (0, parse_1.optionalStringArray)("exclude", core.getMultilineInput("exclude")),
"log-level": (0, parse_1.optionalLogLevel)("log-level", core.getInput("log-level")), "log-level": (0, parse_1.optionalLogLevel)("log-level", core.getInput("log-level")),
"security": (0, parse_1.optionalSecurity)("security", core.getInput("security")), "security": (0, parse_1.optionalSecurity)("security", core.getInput("security"))
"timeout": (0, parse_1.optionalInt)("timeout", core.getInput("timeout"))
}; };
await (0, ftp_deploy_1.deploy)(args); await (0, ftp_deploy_1.deploy)(args);
} }

View File

@ -1,9 +1,8 @@
# How to migrate between versions # Migrating from v4.1.0 to v4.2.0
## Migrating from v4.1.0 to v4.2.0 `v4.2.0` parses the `exclude` option in a more standard way.
`v4.2.0` parses the `exclude` option in a more standard way. Going forward the `exclude` option **must** be in the following format:
Going forward the `exclude` option **must** be in the following format
```yml ```yml
exclude: | exclude: |
**/.git* **/.git*
@ -12,29 +11,28 @@ exclude: |
fileToExclude.txt fileToExclude.txt
``` ```
## Migrating from v3 to v4 ---
# Migrating from v3 to v4
Migrating from v3 to v4 should be fairly straightforward. Version 4 was designed with speed and ease of initial setup in mind. Going forward version 4 will be the only supported version. Migrating from v3 to v4 should be fairly straightforward. Version 4 was designed with speed and ease of initial setup in mind. Going forward version 4 will be the only supported version.
### Those who can't upgrade #### Those who can't upgrade
Most features have been carried forward and improved upon. However, some features did not make the cut.
Most features have been carried forward and improved upon. However, some features did not make the cut:
- **`sftp` is no longer supported**. If you have `sftp` access you are using `ssh`, that means you have access to a much more modern and capable protocol. I plan on releasing a separate github action that will deploy over `sftp`/`ssh` using `rsync`. Until then you can continue using version 3. - **`sftp` is no longer supported**. If you have `sftp` access you are using `ssh`, that means you have access to a much more modern and capable protocol. I plan on releasing a separate github action that will deploy over `sftp`/`ssh` using `rsync`. Until then you can continue using version 3.
- The `include` argument has been removed. I didn't see much need for it in the initial release. If you need this feature please create a support ticket. - The `include` argument has been removed. I didn't see much need for it in the initial release. If you need this feature please create a support ticket.
---
### How to upgrade ### How to upgrade
1. Remove `with: fetch-depth: 2`. It is no longer needed and removing it will _slightly_ speed up deployments. 1) Remove `with: fetch-depth: 2`. It is no longer needed and removing it will _slightly_ speed up deployments.
2. Change the version to `v4.X.X`, for example `SamKirkland/FTP-Deploy-Action@v4.3.5` (please check the [README](https://github.com/SamKirkland/FTP-Deploy-Action/blob/master/README.md) or the [releases page](https://github.com/SamKirkland/FTP-Deploy-Action/releases/latest) for the latest version). 2) Change the version to `4.X.X`, for example `SamKirkland/FTP-Deploy-Action@4.3.1` (please check readme for latest version)
3. If you have a `.git-ftp-include` file you should delete it. Version 4 tracks files differently and no longer needs this config file. 3) If you have a `.git-ftp-include` file you should delete it. Version 4 tracks files differently and no longer needs this config file.
4. If you have a `.git-ftp-ignore` file, you should transfer the options to the new `exclude` argument. **Note:** version 4 excludes any `.git*` and `node_modules/` files / folders by default. 4) If you have a `.git-ftp-ignore` file, you should transfer the options to the new `exclude` argument. _Note: Version 4 excludes any `.git*` and `node_modules/` files/folders by default_
5. Update your arguments to reflect the following changes: 5) Update your arguments to reflect the following changes
- `ftp-server` was split into 4 arguments: * `ftp-server` was split into 4 arguments. `server`, `port`, `protocol`, and `server-dir`. Transfer your config to these options as needed.
- `server` * `ftp-username` was renamed to `username`
- `port` * `ftp-password` was renamed to `password`
- `protocol` * `local-dir` and `server-dir` now **must** end with `/`
- `server-dir` * `git-ftp-args` and `known-hosts` arguments were removed
- `ftp-username` was renamed to `username`.
- `ftp-password` was renamed to `password`.
- `local-dir` and `server-dir` now **must** end with `/`.
- `git-ftp-args` and `known-hosts` arguments were removed.

4626
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "ftp-deploy-action", "name": "ftp-deploy-action",
"version": "4.3.5", "version": "4.3.3",
"private": true, "private": true,
"description": "Automate deploying websites and more with this GitHub action", "description": "Automate deploying websites and more with this GitHub action",
"main": "dist/index.js", "main": "dist/index.js",
@ -23,14 +23,14 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.9.1", "@actions/core": "^1.9.1",
"@samkirkland/ftp-deploy": "^1.2.4", "@samkirkland/ftp-deploy": "^1.2.2",
"@types/jest": "^29.4.1", "@types/jest": "^28.1.7",
"jest": "^29.5.0", "jest": "^28.1.3",
"ts-jest": "^29.0.5", "ts-jest": "^28.0.8",
"ts-node-dev": "^2.0.0" "ts-node-dev": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.11.24", "@types/node": "^14.0.27",
"@typescript-eslint/eslint-plugin": "^5.33.1", "@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1", "@typescript-eslint/parser": "^5.33.1",
"@vercel/ncc": "^0.34.0", "@vercel/ncc": "^0.34.0",