diff --git a/dist/index.js b/dist/index.js index 0528622..19b0671 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2999,130 +2999,130 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; - -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.HashDiff = exports.fileHash = void 0; -const fs_1 = __importDefault(__nccwpck_require__(7147)); -const crypto_1 = __importDefault(__nccwpck_require__(6113)); -function fileHash(filename, algorithm) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise((resolve, reject) => { - // Algorithm depends on availability of OpenSSL on platform - // Another algorithms: "sha1", "md5", "sha256", "sha512" ... - let shasum = crypto_1.default.createHash(algorithm); - try { - let s = fs_1.default.createReadStream(filename); - s.on("data", function (data) { - shasum.update(data); - }); - s.on("error", function (error) { - reject(error); - }); - // making digest - s.on("end", function () { - const hash = shasum.digest("hex"); - return resolve(hash); - }); - } - catch (error) { - return reject("calc fail"); - } - }); - }); -} -exports.fileHash = fileHash; -class HashDiff { - getDiffs(localFiles, serverFiles) { - var _a, _b, _c; - const uploadList = []; - const deleteList = []; - const replaceList = []; - const sameList = []; - let sizeUpload = 0; - let sizeDelete = 0; - let sizeReplace = 0; - // alphabetize each list based off path - const localFilesSorted = localFiles.data.sort((first, second) => first.name.localeCompare(second.name)); - const serverFilesSorted = serverFiles.data.sort((first, second) => first.name.localeCompare(second.name)); - let localPosition = 0; - let serverPosition = 0; - while (localPosition + serverPosition < localFilesSorted.length + serverFilesSorted.length) { - let localFile = localFilesSorted[localPosition]; - let serverFile = serverFilesSorted[serverPosition]; - let fileNameCompare = 0; - if (localFile === undefined) { - fileNameCompare = 1; - } - if (serverFile === undefined) { - fileNameCompare = -1; - } - if (localFile !== undefined && serverFile !== undefined) { - fileNameCompare = localFile.name.localeCompare(serverFile.name); - } - if (fileNameCompare < 0) { - uploadList.push(localFile); - sizeUpload += (_a = localFile.size) !== null && _a !== void 0 ? _a : 0; - localPosition += 1; - } - else if (fileNameCompare > 0) { - deleteList.push(serverFile); - sizeDelete += (_b = serverFile.size) !== null && _b !== void 0 ? _b : 0; - serverPosition += 1; - } - else if (fileNameCompare === 0) { - // paths are a match - if (localFile.type === "file" && serverFile.type === "file") { - if (localFile.hash === serverFile.hash) { - sameList.push(localFile); - } - else { - sizeReplace += (_c = localFile.size) !== null && _c !== void 0 ? _c : 0; - replaceList.push(localFile); - } - } - localPosition += 1; - serverPosition += 1; - } - } - // optimize modifications - let foldersToDelete = deleteList.filter((item) => item.type === "folder"); - // remove files/folders that have a nested parent folder we plan on deleting - const optimizedDeleteList = deleteList.filter((itemToDelete) => { - const parentFolderIsBeingDeleted = foldersToDelete.find((folder) => { - const isSameFile = itemToDelete.name === folder.name; - const parentFolderExists = itemToDelete.name.startsWith(folder.name); - return parentFolderExists && !isSameFile; - }) !== undefined; - if (parentFolderIsBeingDeleted) { - // a parent folder is being deleted, no need to delete this one - return false; - } - return true; - }); - return { - upload: uploadList, - delete: optimizedDeleteList, - replace: replaceList, - same: sameList, - sizeDelete, - sizeReplace, - sizeUpload - }; - } -} -exports.HashDiff = HashDiff; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HashDiff = exports.fileHash = void 0; +const fs_1 = __importDefault(__nccwpck_require__(7147)); +const crypto_1 = __importDefault(__nccwpck_require__(6113)); +function fileHash(filename, algorithm) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + // Algorithm depends on availability of OpenSSL on platform + // Another algorithms: "sha1", "md5", "sha256", "sha512" ... + let shasum = crypto_1.default.createHash(algorithm); + try { + let s = fs_1.default.createReadStream(filename); + s.on("data", function (data) { + shasum.update(data); + }); + s.on("error", function (error) { + reject(error); + }); + // making digest + s.on("end", function () { + const hash = shasum.digest("hex"); + return resolve(hash); + }); + } + catch (error) { + return reject("calc fail"); + } + }); + }); +} +exports.fileHash = fileHash; +class HashDiff { + getDiffs(localFiles, serverFiles) { + var _a, _b, _c; + const uploadList = []; + const deleteList = []; + const replaceList = []; + const sameList = []; + let sizeUpload = 0; + let sizeDelete = 0; + let sizeReplace = 0; + // alphabetize each list based off path + const localFilesSorted = localFiles.data.sort((first, second) => first.name.localeCompare(second.name)); + const serverFilesSorted = serverFiles.data.sort((first, second) => first.name.localeCompare(second.name)); + let localPosition = 0; + let serverPosition = 0; + while (localPosition + serverPosition < localFilesSorted.length + serverFilesSorted.length) { + let localFile = localFilesSorted[localPosition]; + let serverFile = serverFilesSorted[serverPosition]; + let fileNameCompare = 0; + if (localFile === undefined) { + fileNameCompare = 1; + } + if (serverFile === undefined) { + fileNameCompare = -1; + } + if (localFile !== undefined && serverFile !== undefined) { + fileNameCompare = localFile.name.localeCompare(serverFile.name); + } + if (fileNameCompare < 0) { + uploadList.push(localFile); + sizeUpload += (_a = localFile.size) !== null && _a !== void 0 ? _a : 0; + localPosition += 1; + } + else if (fileNameCompare > 0) { + deleteList.push(serverFile); + sizeDelete += (_b = serverFile.size) !== null && _b !== void 0 ? _b : 0; + serverPosition += 1; + } + else if (fileNameCompare === 0) { + // paths are a match + if (localFile.type === "file" && serverFile.type === "file") { + if (localFile.hash === serverFile.hash) { + sameList.push(localFile); + } + else { + sizeReplace += (_c = localFile.size) !== null && _c !== void 0 ? _c : 0; + replaceList.push(localFile); + } + } + localPosition += 1; + serverPosition += 1; + } + } + // optimize modifications + let foldersToDelete = deleteList.filter((item) => item.type === "folder"); + // remove files/folders that have a nested parent folder we plan on deleting + const optimizedDeleteList = deleteList.filter((itemToDelete) => { + const parentFolderIsBeingDeleted = foldersToDelete.find((folder) => { + const isSameFile = itemToDelete.name === folder.name; + const parentFolderExists = itemToDelete.name.startsWith(folder.name); + return parentFolderExists && !isSameFile; + }) !== undefined; + if (parentFolderIsBeingDeleted) { + // a parent folder is being deleted, no need to delete this one + return false; + } + return true; + }); + return { + upload: uploadList, + delete: optimizedDeleteList, + replace: replaceList, + same: sameList, + sizeDelete, + sizeReplace, + sizeUpload + }; + } +} +exports.HashDiff = HashDiff; /***/ }), @@ -3131,232 +3131,232 @@ exports.HashDiff = HashDiff; /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; - -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = 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) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.deploy = exports.getServerFiles = void 0; -const ftp = __importStar(__nccwpck_require__(7957)); -const fs_1 = __importDefault(__nccwpck_require__(7147)); -const types_1 = __nccwpck_require__(6703); -const HashDiff_1 = __nccwpck_require__(9946); -const utilities_1 = __nccwpck_require__(4389); -const pretty_bytes_1 = __importDefault(__nccwpck_require__(5168)); -const errorHandling_1 = __nccwpck_require__(3678); -const syncProvider_1 = __nccwpck_require__(1904); -const localFiles_1 = __nccwpck_require__(8660); -function downloadFileList(client, logger, path) { - return __awaiter(this, void 0, void 0, function* () { - // note: originally this was using a writable stream instead of a buffer file - // basic-ftp doesn't seam to close the connection when using steams over some ftps connections. This appears to be dependent on the ftp server - const tempFileNameHack = ".ftp-deploy-sync-server-state-buffer-file---delete.json"; - yield (0, utilities_1.retryRequest)(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.downloadTo(tempFileNameHack, path); })); - const fileAsString = fs_1.default.readFileSync(tempFileNameHack, { encoding: "utf-8" }); - const fileAsObject = JSON.parse(fileAsString); - fs_1.default.unlinkSync(tempFileNameHack); - return fileAsObject; - }); -} -function createLocalState(localFiles, logger, args) { - logger.verbose(`Creating local state at ${args["local-dir"]}${args["state-name"]}`); - fs_1.default.writeFileSync(`${args["local-dir"]}${args["state-name"]}`, JSON.stringify(localFiles, undefined, 4), { encoding: "utf8" }); - logger.verbose("Local state created"); -} -function connect(client, args, logger) { - return __awaiter(this, void 0, void 0, function* () { - let secure = false; - if (args.protocol === "ftps") { - secure = true; - } - else if (args.protocol === "ftps-legacy") { - secure = "implicit"; - } - client.ftp.verbose = args["log-level"] === "verbose"; - const rejectUnauthorized = args.security === "strict"; - try { - yield client.access({ - host: args.server, - user: args.username, - password: args.password, - port: args.port, - secure: secure, - secureOptions: { - rejectUnauthorized: rejectUnauthorized - } - }); - } - catch (error) { - logger.all("Failed to connect, are you sure your server works via FTP or FTPS? Users sometimes get this error when the server only supports SFTP."); - throw error; - } - if (args["log-level"] === "verbose") { - client.trackProgress(info => { - logger.verbose(`${info.type} progress for "${info.name}". Progress: ${info.bytes} bytes of ${info.bytesOverall} bytes`); - }); - } - }); -} -function getServerFiles(client, logger, timings, args) { - return __awaiter(this, void 0, void 0, function* () { - try { - yield (0, syncProvider_1.ensureDir)(client, logger, timings, args["server-dir"]); - if (args["dangerous-clean-slate"]) { - logger.all(`----------------------------------------------------------------`); - logger.all("🗑️ Removing all files on the server because 'dangerous-clean-slate' was set, this will make the deployment very slow..."); - if (args["dry-run"] === false) { - yield client.clearWorkingDir(); - } - logger.all("Clear complete"); - throw new Error("dangerous-clean-slate was run"); - } - const serverFiles = yield downloadFileList(client, logger, args["state-name"]); - logger.all(`----------------------------------------------------------------`); - logger.all(`Last published on 📅 ${new Date(serverFiles.generatedTime).toLocaleDateString(undefined, { weekday: "long", year: "numeric", month: "long", day: "numeric", hour: "numeric", minute: "numeric" })}`); - // apply exclude options to server - if (args.exclude.length > 0) { - const filteredData = serverFiles.data.filter((item) => (0, utilities_1.applyExcludeFilter)({ path: item.name, isDirectory: () => item.type === "folder" }, args.exclude)); - serverFiles.data = filteredData; - } - return serverFiles; - } - catch (error) { - logger.all(`----------------------------------------------------------------`); - logger.all(`No file exists on the server "${args["server-dir"] + args["state-name"]}" - this must be your first publish! 🎉`); - logger.all(`The first publish will take a while... but once the initial sync is done only differences are published!`); - logger.all(`If you get this message and its NOT your first publish, something is wrong.`); - // set the server state to nothing, because we don't know what the server state is - return { - description: types_1.syncFileDescription, - version: types_1.currentSyncFileVersion, - generatedTime: new Date().getTime(), - data: [], - }; - } - }); -} -exports.getServerFiles = getServerFiles; -function deploy(args, logger, timings) { - return __awaiter(this, void 0, void 0, function* () { - timings.start("total"); - // header - logger.all(`----------------------------------------------------------------`); - logger.all(`🚀 Thanks for using ftp-deploy. Let's deploy some stuff! `); - logger.all(`----------------------------------------------------------------`); - logger.all(`If you found this project helpful, please support it`); - logger.all(`by giving it a ⭐ on Github --> https://github.com/SamKirkland/FTP-Deploy-Action`); - logger.all(`or add a badge 🏷️ to your projects readme --> https://github.com/SamKirkland/FTP-Deploy-Action#badge`); - logger.verbose(`Using the following excludes filters: ${JSON.stringify(args.exclude)}`); - timings.start("hash"); - const localFiles = yield (0, localFiles_1.getLocalFiles)(args); - timings.stop("hash"); - createLocalState(localFiles, logger, args); - const client = new ftp.Client(args.timeout); - global.reconnect = function () { - return __awaiter(this, void 0, void 0, function* () { - timings.start("connecting"); - yield connect(client, args, logger); - timings.stop("connecting"); - }); - }; - let totalBytesUploaded = 0; - try { - yield global.reconnect(); - const serverFiles = yield getServerFiles(client, logger, timings, args); - timings.start("logging"); - const diffTool = new HashDiff_1.HashDiff(); - logger.standard(`----------------------------------------------------------------`); - logger.standard(`Local Files:\t${(0, utilities_1.formatNumber)(localFiles.data.length)}`); - logger.standard(`Server Files:\t${(0, utilities_1.formatNumber)(serverFiles.data.length)}`); - logger.standard(`----------------------------------------------------------------`); - logger.standard(`Calculating differences between client & server`); - logger.standard(`----------------------------------------------------------------`); - const diffs = diffTool.getDiffs(localFiles, serverFiles); - diffs.upload.filter((itemUpload) => itemUpload.type === "folder").map((itemUpload) => { - logger.standard(`📁 Create: ${itemUpload.name}`); - }); - diffs.upload.filter((itemUpload) => itemUpload.type === "file").map((itemUpload) => { - logger.standard(`📄 Upload: ${itemUpload.name}`); - }); - diffs.replace.map((itemReplace) => { - logger.standard(`🔁 File replace: ${itemReplace.name}`); - }); - diffs.delete.filter((itemUpload) => itemUpload.type === "file").map((itemDelete) => { - logger.standard(`📄 Delete: ${itemDelete.name} `); - }); - diffs.delete.filter((itemUpload) => itemUpload.type === "folder").map((itemDelete) => { - logger.standard(`📁 Delete: ${itemDelete.name} `); - }); - diffs.same.map((itemSame) => { - if (itemSame.type === "file") { - logger.standard(`⚖️ File content is the same, doing nothing: ${itemSame.name}`); - } - }); - timings.stop("logging"); - totalBytesUploaded = diffs.sizeUpload + diffs.sizeReplace; - timings.start("upload"); - try { - const syncProvider = new syncProvider_1.FTPSyncProvider(client, logger, timings, args["local-dir"], args["server-dir"], args["state-name"], args["dry-run"]); - yield syncProvider.syncLocalToServer(diffs); - } - finally { - timings.stop("upload"); - } - } - catch (error) { - (0, errorHandling_1.prettyError)(logger, args, error); - throw error; - } - finally { - client.close(); - timings.stop("total"); - } - const uploadSpeed = (0, pretty_bytes_1.default)(totalBytesUploaded / (timings.getTime("upload") / 1000)); - // footer - logger.all(`----------------------------------------------------------------`); - logger.all(`Time spent hashing: ${timings.getTimeFormatted("hash")}`); - logger.all(`Time spent connecting to server: ${timings.getTimeFormatted("connecting")}`); - logger.all(`Time spent deploying: ${timings.getTimeFormatted("upload")} (${uploadSpeed}/second)`); - logger.all(` - changing dirs: ${timings.getTimeFormatted("changingDir")}`); - logger.all(` - logging: ${timings.getTimeFormatted("logging")}`); - logger.all(`----------------------------------------------------------------`); - logger.all(`Total time: ${timings.getTimeFormatted("total")}`); - logger.all(`----------------------------------------------------------------`); - }); -} -exports.deploy = deploy; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = 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) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.deploy = exports.getServerFiles = void 0; +const ftp = __importStar(__nccwpck_require__(7957)); +const fs_1 = __importDefault(__nccwpck_require__(7147)); +const types_1 = __nccwpck_require__(6703); +const HashDiff_1 = __nccwpck_require__(9946); +const utilities_1 = __nccwpck_require__(4389); +const pretty_bytes_1 = __importDefault(__nccwpck_require__(5168)); +const errorHandling_1 = __nccwpck_require__(3678); +const syncProvider_1 = __nccwpck_require__(1904); +const localFiles_1 = __nccwpck_require__(8660); +function downloadFileList(client, logger, path) { + return __awaiter(this, void 0, void 0, function* () { + // note: originally this was using a writable stream instead of a buffer file + // basic-ftp doesn't seam to close the connection when using steams over some ftps connections. This appears to be dependent on the ftp server + const tempFileNameHack = ".ftp-deploy-sync-server-state-buffer-file---delete.json"; + yield (0, utilities_1.retryRequest)(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.downloadTo(tempFileNameHack, path); })); + const fileAsString = fs_1.default.readFileSync(tempFileNameHack, { encoding: "utf-8" }); + const fileAsObject = JSON.parse(fileAsString); + fs_1.default.unlinkSync(tempFileNameHack); + return fileAsObject; + }); +} +function createLocalState(localFiles, logger, args) { + logger.verbose(`Creating local state at ${args["local-dir"]}${args["state-name"]}`); + fs_1.default.writeFileSync(`${args["local-dir"]}${args["state-name"]}`, JSON.stringify(localFiles, undefined, 4), { encoding: "utf8" }); + logger.verbose("Local state created"); +} +function connect(client, args, logger) { + return __awaiter(this, void 0, void 0, function* () { + let secure = false; + if (args.protocol === "ftps") { + secure = true; + } + else if (args.protocol === "ftps-legacy") { + secure = "implicit"; + } + client.ftp.verbose = args["log-level"] === "verbose"; + const rejectUnauthorized = args.security === "strict"; + try { + yield client.access({ + host: args.server, + user: args.username, + password: args.password, + port: args.port, + secure: secure, + secureOptions: { + rejectUnauthorized: rejectUnauthorized + } + }); + } + catch (error) { + logger.all("Failed to connect, are you sure your server works via FTP or FTPS? Users sometimes get this error when the server only supports SFTP."); + throw error; + } + if (args["log-level"] === "verbose") { + client.trackProgress(info => { + logger.verbose(`${info.type} progress for "${info.name}". Progress: ${info.bytes} bytes of ${info.bytesOverall} bytes`); + }); + } + }); +} +function getServerFiles(client, logger, timings, args) { + return __awaiter(this, void 0, void 0, function* () { + try { + yield (0, syncProvider_1.ensureDir)(client, logger, timings, args["server-dir"]); + if (args["dangerous-clean-slate"]) { + logger.all(`----------------------------------------------------------------`); + logger.all("🗑️ Removing all files on the server because 'dangerous-clean-slate' was set, this will make the deployment very slow..."); + if (args["dry-run"] === false) { + yield client.clearWorkingDir(); + } + logger.all("Clear complete"); + throw new Error("dangerous-clean-slate was run"); + } + const serverFiles = yield downloadFileList(client, logger, args["state-name"]); + logger.all(`----------------------------------------------------------------`); + logger.all(`Last published on 📅 ${new Date(serverFiles.generatedTime).toLocaleDateString(undefined, { weekday: "long", year: "numeric", month: "long", day: "numeric", hour: "numeric", minute: "numeric" })}`); + // apply exclude options to server + if (args.exclude.length > 0) { + const filteredData = serverFiles.data.filter((item) => (0, utilities_1.applyExcludeFilter)({ path: item.name, isDirectory: () => item.type === "folder" }, args.exclude)); + serverFiles.data = filteredData; + } + return serverFiles; + } + catch (error) { + logger.all(`----------------------------------------------------------------`); + logger.all(`No file exists on the server "${args["server-dir"] + args["state-name"]}" - this must be your first publish! 🎉`); + logger.all(`The first publish will take a while... but once the initial sync is done only differences are published!`); + logger.all(`If you get this message and its NOT your first publish, something is wrong.`); + // set the server state to nothing, because we don't know what the server state is + return { + description: types_1.syncFileDescription, + version: types_1.currentSyncFileVersion, + generatedTime: new Date().getTime(), + data: [], + }; + } + }); +} +exports.getServerFiles = getServerFiles; +function deploy(args, logger, timings) { + return __awaiter(this, void 0, void 0, function* () { + timings.start("total"); + // header + logger.all(`----------------------------------------------------------------`); + logger.all(`🚀 Thanks for using ftp-deploy. Let's deploy some stuff! `); + logger.all(`----------------------------------------------------------------`); + logger.all(`If you found this project helpful, please support it`); + logger.all(`by giving it a ⭐ on Github --> https://github.com/SamKirkland/FTP-Deploy-Action`); + logger.all(`or add a badge 🏷️ to your projects readme --> https://github.com/SamKirkland/FTP-Deploy-Action#badge`); + logger.verbose(`Using the following excludes filters: ${JSON.stringify(args.exclude)}`); + timings.start("hash"); + const localFiles = yield (0, localFiles_1.getLocalFiles)(args); + timings.stop("hash"); + createLocalState(localFiles, logger, args); + const client = new ftp.Client(args.timeout); + global.reconnect = function () { + return __awaiter(this, void 0, void 0, function* () { + timings.start("connecting"); + yield connect(client, args, logger); + timings.stop("connecting"); + }); + }; + let totalBytesUploaded = 0; + try { + yield global.reconnect(); + const serverFiles = yield getServerFiles(client, logger, timings, args); + timings.start("logging"); + const diffTool = new HashDiff_1.HashDiff(); + logger.standard(`----------------------------------------------------------------`); + logger.standard(`Local Files:\t${(0, utilities_1.formatNumber)(localFiles.data.length)}`); + logger.standard(`Server Files:\t${(0, utilities_1.formatNumber)(serverFiles.data.length)}`); + logger.standard(`----------------------------------------------------------------`); + logger.standard(`Calculating differences between client & server`); + logger.standard(`----------------------------------------------------------------`); + const diffs = diffTool.getDiffs(localFiles, serverFiles); + diffs.upload.filter((itemUpload) => itemUpload.type === "folder").map((itemUpload) => { + logger.standard(`📁 Create: ${itemUpload.name}`); + }); + diffs.upload.filter((itemUpload) => itemUpload.type === "file").map((itemUpload) => { + logger.standard(`📄 Upload: ${itemUpload.name}`); + }); + diffs.replace.map((itemReplace) => { + logger.standard(`🔁 File replace: ${itemReplace.name}`); + }); + diffs.delete.filter((itemUpload) => itemUpload.type === "file").map((itemDelete) => { + logger.standard(`📄 Delete: ${itemDelete.name} `); + }); + diffs.delete.filter((itemUpload) => itemUpload.type === "folder").map((itemDelete) => { + logger.standard(`📁 Delete: ${itemDelete.name} `); + }); + diffs.same.map((itemSame) => { + if (itemSame.type === "file") { + logger.standard(`⚖️ File content is the same, doing nothing: ${itemSame.name}`); + } + }); + timings.stop("logging"); + totalBytesUploaded = diffs.sizeUpload + diffs.sizeReplace; + timings.start("upload"); + try { + const syncProvider = new syncProvider_1.FTPSyncProvider(client, logger, timings, args["local-dir"], args["server-dir"], args["state-name"], args["dry-run"]); + yield syncProvider.syncLocalToServer(diffs); + } + finally { + timings.stop("upload"); + } + } + catch (error) { + (0, errorHandling_1.prettyError)(logger, args, error); + throw error; + } + finally { + client.close(); + timings.stop("total"); + } + const uploadSpeed = (0, pretty_bytes_1.default)(totalBytesUploaded / (timings.getTime("upload") / 1000)); + // footer + logger.all(`----------------------------------------------------------------`); + logger.all(`Time spent hashing: ${timings.getTimeFormatted("hash")}`); + logger.all(`Time spent connecting to server: ${timings.getTimeFormatted("connecting")}`); + logger.all(`Time spent deploying: ${timings.getTimeFormatted("upload")} (${uploadSpeed}/second)`); + logger.all(` - changing dirs: ${timings.getTimeFormatted("changingDir")}`); + logger.all(` - logging: ${timings.getTimeFormatted("logging")}`); + logger.all(`----------------------------------------------------------------`); + logger.all(`Total time: ${timings.getTimeFormatted("total")}`); + logger.all(`----------------------------------------------------------------`); + }); +} +exports.deploy = deploy; /***/ }), @@ -3365,59 +3365,59 @@ exports.deploy = deploy; /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.prettyError = void 0; -const types_1 = __nccwpck_require__(6703); -function logOriginalError(logger, error) { - logger.all(); - logger.all(`----------------------------------------------------------------`); - logger.all(`---------------------- full error below ----------------------`); - logger.all(`----------------------------------------------------------------`); - logger.all(); - logger.all(error); -} -/** - * Converts a exception to helpful debug info - * @param error exception - */ -function prettyError(logger, args, error) { - logger.all(); - logger.all(`----------------------------------------------------------------`); - logger.all(`-------------- 🔥🔥🔥 an error occurred 🔥🔥🔥 --------------`); - logger.all(`----------------------------------------------------------------`); - const ftpError = error; - if (typeof error.code === "string") { - const errorCode = error.code; - if (errorCode === "ENOTFOUND") { - logger.all(`The server "${args.server}" doesn't seem to exist. Do you have a typo?`); - } - } - else if (typeof error.name === "string") { - const errorName = error.name; - if (errorName.includes("ERR_TLS_CERT_ALTNAME_INVALID")) { - logger.all(`The certificate for "${args.server}" is likely shared. The host did not place your server on the list of valid domains for this cert.`); - logger.all(`This is a common issue with shared hosts. You have a few options:`); - logger.all(` - Ignore this error by setting security back to loose`); - logger.all(` - Contact your hosting provider and ask them for your servers hostname`); - } - } - else if (typeof ftpError.code === "number") { - if (ftpError.code === types_1.ErrorCode.NotLoggedIn) { - const serverRequiresFTPS = ftpError.message.toLowerCase().includes("must use encryption"); - if (serverRequiresFTPS) { - logger.all(`The server you are connecting to requires encryption (ftps)`); - logger.all(`Enable FTPS by using the protocol option.`); - } - else { - logger.all(`Could not login with the username "${args.username}" and password "${args.password}".`); - logger.all(`Make sure you can login with those credentials. If you have a space or a quote in your username or password be sure to escape them!`); - } - } - } - logOriginalError(logger, error); -} -exports.prettyError = prettyError; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.prettyError = void 0; +const types_1 = __nccwpck_require__(6703); +function logOriginalError(logger, error) { + logger.all(); + logger.all(`----------------------------------------------------------------`); + logger.all(`---------------------- full error below ----------------------`); + logger.all(`----------------------------------------------------------------`); + logger.all(); + logger.all(error); +} +/** + * Converts a exception to helpful debug info + * @param error exception + */ +function prettyError(logger, args, error) { + logger.all(); + logger.all(`----------------------------------------------------------------`); + logger.all(`-------------- 🔥🔥🔥 an error occurred 🔥🔥🔥 --------------`); + logger.all(`----------------------------------------------------------------`); + const ftpError = error; + if (typeof error.code === "string") { + const errorCode = error.code; + if (errorCode === "ENOTFOUND") { + logger.all(`The server "${args.server}" doesn't seem to exist. Do you have a typo?`); + } + } + else if (typeof error.name === "string") { + const errorName = error.name; + if (errorName.includes("ERR_TLS_CERT_ALTNAME_INVALID")) { + logger.all(`The certificate for "${args.server}" is likely shared. The host did not place your server on the list of valid domains for this cert.`); + logger.all(`This is a common issue with shared hosts. You have a few options:`); + logger.all(` - Ignore this error by setting security back to loose`); + logger.all(` - Contact your hosting provider and ask them for your servers hostname`); + } + } + else if (typeof ftpError.code === "number") { + if (ftpError.code === types_1.ErrorCode.NotLoggedIn) { + const serverRequiresFTPS = ftpError.message.toLowerCase().includes("must use encryption"); + if (serverRequiresFTPS) { + logger.all(`The server you are connecting to requires encryption (ftps)`); + logger.all(`Enable FTPS by using the protocol option.`); + } + else { + logger.all(`Could not login with the username "${args.username}" and password "${args.password}".`); + logger.all(`Make sure you can login with those credentials. If you have a space or a quote in your username or password be sure to escape them!`); + } + } + } + logOriginalError(logger, error); +} +exports.prettyError = prettyError; /***/ }), @@ -3426,60 +3426,60 @@ exports.prettyError = prettyError; /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; - -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getLocalFiles = void 0; -const readdir_enhanced_1 = __importDefault(__nccwpck_require__(8811)); -const types_1 = __nccwpck_require__(6703); -const HashDiff_1 = __nccwpck_require__(9946); -const utilities_1 = __nccwpck_require__(4389); -function getLocalFiles(args) { - return __awaiter(this, void 0, void 0, function* () { - const files = yield readdir_enhanced_1.default.async(args["local-dir"], { deep: true, stats: true, sep: "/", filter: (stat) => (0, utilities_1.applyExcludeFilter)(stat, args.exclude) }); - const records = []; - for (let stat of files) { - if (stat.isDirectory()) { - records.push({ - type: "folder", - name: stat.path, - size: undefined - }); - continue; - } - if (stat.isFile()) { - records.push({ - type: "file", - name: stat.path, - size: stat.size, - hash: yield (0, HashDiff_1.fileHash)(args["local-dir"] + stat.path, "sha256") - }); - continue; - } - if (stat.isSymbolicLink()) { - console.warn("This script is currently unable to handle symbolic links - please add a feature request if you need this"); - } - } - return { - description: types_1.syncFileDescription, - version: types_1.currentSyncFileVersion, - generatedTime: new Date().getTime(), - data: records - }; - }); -} -exports.getLocalFiles = getLocalFiles; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getLocalFiles = void 0; +const readdir_enhanced_1 = __importDefault(__nccwpck_require__(8811)); +const types_1 = __nccwpck_require__(6703); +const HashDiff_1 = __nccwpck_require__(9946); +const utilities_1 = __nccwpck_require__(4389); +function getLocalFiles(args) { + return __awaiter(this, void 0, void 0, function* () { + const files = yield readdir_enhanced_1.default.async(args["local-dir"], { deep: true, stats: true, sep: "/", filter: (stat) => (0, utilities_1.applyExcludeFilter)(stat, args.exclude) }); + const records = []; + for (let stat of files) { + if (stat.isDirectory()) { + records.push({ + type: "folder", + name: stat.path, + size: undefined + }); + continue; + } + if (stat.isFile()) { + records.push({ + type: "file", + name: stat.path, + size: stat.size, + hash: yield (0, HashDiff_1.fileHash)(args["local-dir"] + stat.path, "sha256") + }); + continue; + } + if (stat.isSymbolicLink()) { + console.warn("This script is currently unable to handle symbolic links - please add a feature request if you need this"); + } + } + return { + description: types_1.syncFileDescription, + version: types_1.currentSyncFileVersion, + generatedTime: new Date().getTime(), + data: records + }; + }); +} +exports.getLocalFiles = getLocalFiles; /***/ }), @@ -3488,40 +3488,40 @@ exports.getLocalFiles = getLocalFiles; /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; - -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.deploy = exports.excludeDefaults = void 0; -const deploy_1 = __nccwpck_require__(232); -const utilities_1 = __nccwpck_require__(4389); -/** - * Default excludes, ignores all git files and the node_modules folder - * **\/.git* ignores all FILES that start with .git(in any folder or sub-folder) - * **\/.git*\/** ignores all FOLDERS that start with .git (in any folder or sub-folder) - * **\/node_modules\/** ignores all FOLDERS named node_modules (in any folder or sub-folder) - */ -exports.excludeDefaults = ["**/.git*", "**/.git*/**", "**/node_modules/**"]; -/** - * Syncs a local folder with a remote folder over FTP. - * After the initial sync only differences are synced, making deployments super fast! - */ -function deploy(args) { - return __awaiter(this, void 0, void 0, function* () { - const argsWithDefaults = (0, utilities_1.getDefaultSettings)(args); - const logger = new utilities_1.Logger(argsWithDefaults["log-level"]); - const timings = new utilities_1.Timings(); - yield (0, deploy_1.deploy)(argsWithDefaults, logger, timings); - }); -} -exports.deploy = deploy; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.deploy = exports.excludeDefaults = void 0; +const deploy_1 = __nccwpck_require__(232); +const utilities_1 = __nccwpck_require__(4389); +/** + * Default excludes, ignores all git files and the node_modules folder + * **\/.git* ignores all FILES that start with .git(in any folder or sub-folder) + * **\/.git*\/** ignores all FOLDERS that start with .git (in any folder or sub-folder) + * **\/node_modules\/** ignores all FOLDERS named node_modules (in any folder or sub-folder) + */ +exports.excludeDefaults = ["**/.git*", "**/.git*/**", "**/node_modules/**"]; +/** + * Syncs a local folder with a remote folder over FTP. + * After the initial sync only differences are synced, making deployments super fast! + */ +function deploy(args) { + return __awaiter(this, void 0, void 0, function* () { + const argsWithDefaults = (0, utilities_1.getDefaultSettings)(args); + const logger = new utilities_1.Logger(argsWithDefaults["log-level"]); + const timings = new utilities_1.Timings(); + yield (0, deploy_1.deploy)(argsWithDefaults, logger, timings); + }); +} +exports.deploy = deploy; /***/ }), @@ -3530,172 +3530,172 @@ exports.deploy = deploy; /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; - -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.FTPSyncProvider = exports.ensureDir = void 0; -const pretty_bytes_1 = __importDefault(__nccwpck_require__(5168)); -const types_1 = __nccwpck_require__(6703); -const utilities_1 = __nccwpck_require__(4389); -function ensureDir(client, logger, timings, folder) { - return __awaiter(this, void 0, void 0, function* () { - timings.start("changingDir"); - logger.verbose(` changing dir to ${folder}`); - yield (0, utilities_1.retryRequest)(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.ensureDir(folder); })); - logger.verbose(` dir changed`); - timings.stop("changingDir"); - }); -} -exports.ensureDir = ensureDir; -class FTPSyncProvider { - constructor(client, logger, timings, localPath, serverPath, stateName, dryRun) { - this.client = client; - this.logger = logger; - this.timings = timings; - this.localPath = localPath; - this.serverPath = serverPath; - this.stateName = stateName; - this.dryRun = dryRun; - } - /** - * Converts a file path (ex: "folder/otherfolder/file.txt") to an array of folder and a file path - * @param fullPath - */ - getFileBreadcrumbs(fullPath) { - var _a; - // todo see if this regex will work for nonstandard folder names - // todo what happens if the path is relative to the root dir? (starts with /) - const pathSplit = fullPath.split("/"); - const file = (_a = pathSplit === null || pathSplit === void 0 ? void 0 : pathSplit.pop()) !== null && _a !== void 0 ? _a : ""; // get last item - const folders = pathSplit.filter(folderName => folderName != ""); - return { - folders: folders.length === 0 ? null : folders, - file: file === "" ? null : file - }; - } - /** - * Navigates up {dirCount} number of directories from the current working dir - */ - upDir(dirCount) { - return __awaiter(this, void 0, void 0, function* () { - if (typeof dirCount !== "number") { - return; - } - // navigate back to the starting folder - for (let i = 0; i < dirCount; i++) { - yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.cdup(); })); - } - }); - } - createFolder(folderPath) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - this.logger.all(`creating folder "${folderPath + "/"}"`); - if (this.dryRun === true) { - return; - } - const path = this.getFileBreadcrumbs(folderPath + "/"); - if (path.folders === null) { - this.logger.verbose(` no need to change dir`); - } - else { - yield ensureDir(this.client, this.logger, this.timings, path.folders.join("/")); - } - // navigate back to the root folder - yield this.upDir((_a = path.folders) === null || _a === void 0 ? void 0 : _a.length); - this.logger.verbose(` completed`); - }); - } - removeFile(filePath) { - return __awaiter(this, void 0, void 0, function* () { - this.logger.all(`removing "${filePath}"`); - if (this.dryRun === false) { - try { - yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.remove(filePath); })); - } - catch (e) { - // this error is common when a file was deleted on the server directly - if (e.code === types_1.ErrorCode.FileNotFoundOrNoAccess) { - this.logger.standard("File not found or you don't have access to the file - skipping..."); - } - else { - throw e; - } - } - } - this.logger.verbose(` file removed`); - this.logger.verbose(` completed`); - }); - } - removeFolder(folderPath) { - return __awaiter(this, void 0, void 0, function* () { - const absoluteFolderPath = "/" + (this.serverPath.startsWith("./") ? this.serverPath.replace("./", "") : this.serverPath) + folderPath; - this.logger.all(`removing folder "${absoluteFolderPath}"`); - if (this.dryRun === false) { - yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.removeDir(absoluteFolderPath); })); - } - this.logger.verbose(` completed`); - }); - } - uploadFile(filePath, type = "upload") { - return __awaiter(this, void 0, void 0, function* () { - const typePresent = type === "upload" ? "uploading" : "replacing"; - const typePast = type === "upload" ? "uploaded" : "replaced"; - this.logger.all(`${typePresent} "${filePath}"`); - if (this.dryRun === false) { - yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.uploadFrom(this.localPath + filePath, filePath); })); - } - this.logger.verbose(` file ${typePast}`); - }); - } - syncLocalToServer(diffs) { - return __awaiter(this, void 0, void 0, function* () { - const totalCount = diffs.delete.length + diffs.upload.length + diffs.replace.length; - this.logger.all(`----------------------------------------------------------------`); - this.logger.all(`Making changes to ${totalCount} ${(0, utilities_1.pluralize)(totalCount, "file/folder", "files/folders")} to sync server state`); - this.logger.all(`Uploading: ${(0, pretty_bytes_1.default)(diffs.sizeUpload)} -- Deleting: ${(0, pretty_bytes_1.default)(diffs.sizeDelete)} -- Replacing: ${(0, pretty_bytes_1.default)(diffs.sizeReplace)}`); - this.logger.all(`----------------------------------------------------------------`); - // create new folders - for (const file of diffs.upload.filter(item => item.type === "folder")) { - yield this.createFolder(file.name); - } - // upload new files - for (const file of diffs.upload.filter(item => item.type === "file").filter(item => item.name !== this.stateName)) { - yield this.uploadFile(file.name, "upload"); - } - // replace new files - for (const file of diffs.replace.filter(item => item.type === "file").filter(item => item.name !== this.stateName)) { - // note: FTP will replace old files with new files. We run replacements after uploads to limit downtime - yield this.uploadFile(file.name, "replace"); - } - // delete old files - for (const file of diffs.delete.filter(item => item.type === "file")) { - yield this.removeFile(file.name); - } - // delete old folders - for (const file of diffs.delete.filter(item => item.type === "folder")) { - yield this.removeFolder(file.name); - } - this.logger.all(`----------------------------------------------------------------`); - this.logger.all(`🎉 Sync complete. Saving current server state to "${this.serverPath + this.stateName}"`); - if (this.dryRun === false) { - yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.uploadFrom(this.localPath + this.stateName, this.stateName); })); - } - }); - } -} -exports.FTPSyncProvider = FTPSyncProvider; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.FTPSyncProvider = exports.ensureDir = void 0; +const pretty_bytes_1 = __importDefault(__nccwpck_require__(5168)); +const types_1 = __nccwpck_require__(6703); +const utilities_1 = __nccwpck_require__(4389); +function ensureDir(client, logger, timings, folder) { + return __awaiter(this, void 0, void 0, function* () { + timings.start("changingDir"); + logger.verbose(` changing dir to ${folder}`); + yield (0, utilities_1.retryRequest)(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.ensureDir(folder); })); + logger.verbose(` dir changed`); + timings.stop("changingDir"); + }); +} +exports.ensureDir = ensureDir; +class FTPSyncProvider { + constructor(client, logger, timings, localPath, serverPath, stateName, dryRun) { + this.client = client; + this.logger = logger; + this.timings = timings; + this.localPath = localPath; + this.serverPath = serverPath; + this.stateName = stateName; + this.dryRun = dryRun; + } + /** + * Converts a file path (ex: "folder/otherfolder/file.txt") to an array of folder and a file path + * @param fullPath + */ + getFileBreadcrumbs(fullPath) { + var _a; + // todo see if this regex will work for nonstandard folder names + // todo what happens if the path is relative to the root dir? (starts with /) + const pathSplit = fullPath.split("/"); + const file = (_a = pathSplit === null || pathSplit === void 0 ? void 0 : pathSplit.pop()) !== null && _a !== void 0 ? _a : ""; // get last item + const folders = pathSplit.filter(folderName => folderName != ""); + return { + folders: folders.length === 0 ? null : folders, + file: file === "" ? null : file + }; + } + /** + * Navigates up {dirCount} number of directories from the current working dir + */ + upDir(dirCount) { + return __awaiter(this, void 0, void 0, function* () { + if (typeof dirCount !== "number") { + return; + } + // navigate back to the starting folder + for (let i = 0; i < dirCount; i++) { + yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.cdup(); })); + } + }); + } + createFolder(folderPath) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + this.logger.all(`creating folder "${folderPath + "/"}"`); + if (this.dryRun === true) { + return; + } + const path = this.getFileBreadcrumbs(folderPath + "/"); + if (path.folders === null) { + this.logger.verbose(` no need to change dir`); + } + else { + yield ensureDir(this.client, this.logger, this.timings, path.folders.join("/")); + } + // navigate back to the root folder + yield this.upDir((_a = path.folders) === null || _a === void 0 ? void 0 : _a.length); + this.logger.verbose(` completed`); + }); + } + removeFile(filePath) { + return __awaiter(this, void 0, void 0, function* () { + this.logger.all(`removing "${filePath}"`); + if (this.dryRun === false) { + try { + yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.remove(filePath); })); + } + catch (e) { + // this error is common when a file was deleted on the server directly + if (e.code === types_1.ErrorCode.FileNotFoundOrNoAccess) { + this.logger.standard("File not found or you don't have access to the file - skipping..."); + } + else { + throw e; + } + } + } + this.logger.verbose(` file removed`); + this.logger.verbose(` completed`); + }); + } + removeFolder(folderPath) { + return __awaiter(this, void 0, void 0, function* () { + const absoluteFolderPath = "/" + (this.serverPath.startsWith("./") ? this.serverPath.replace("./", "") : this.serverPath) + folderPath; + this.logger.all(`removing folder "${absoluteFolderPath}"`); + if (this.dryRun === false) { + yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.removeDir(absoluteFolderPath); })); + } + this.logger.verbose(` completed`); + }); + } + uploadFile(filePath, type = "upload") { + return __awaiter(this, void 0, void 0, function* () { + const typePresent = type === "upload" ? "uploading" : "replacing"; + const typePast = type === "upload" ? "uploaded" : "replaced"; + this.logger.all(`${typePresent} "${filePath}"`); + if (this.dryRun === false) { + yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.uploadFrom(this.localPath + filePath, filePath); })); + } + this.logger.verbose(` file ${typePast}`); + }); + } + syncLocalToServer(diffs) { + return __awaiter(this, void 0, void 0, function* () { + const totalCount = diffs.delete.length + diffs.upload.length + diffs.replace.length; + this.logger.all(`----------------------------------------------------------------`); + this.logger.all(`Making changes to ${totalCount} ${(0, utilities_1.pluralize)(totalCount, "file/folder", "files/folders")} to sync server state`); + this.logger.all(`Uploading: ${(0, pretty_bytes_1.default)(diffs.sizeUpload)} -- Deleting: ${(0, pretty_bytes_1.default)(diffs.sizeDelete)} -- Replacing: ${(0, pretty_bytes_1.default)(diffs.sizeReplace)}`); + this.logger.all(`----------------------------------------------------------------`); + // create new folders + for (const file of diffs.upload.filter(item => item.type === "folder")) { + yield this.createFolder(file.name); + } + // upload new files + for (const file of diffs.upload.filter(item => item.type === "file").filter(item => item.name !== this.stateName)) { + yield this.uploadFile(file.name, "upload"); + } + // replace new files + for (const file of diffs.replace.filter(item => item.type === "file").filter(item => item.name !== this.stateName)) { + // note: FTP will replace old files with new files. We run replacements after uploads to limit downtime + yield this.uploadFile(file.name, "replace"); + } + // delete old files + for (const file of diffs.delete.filter(item => item.type === "file")) { + yield this.removeFile(file.name); + } + // delete old folders + for (const file of diffs.delete.filter(item => item.type === "folder")) { + yield this.removeFolder(file.name); + } + this.logger.all(`----------------------------------------------------------------`); + this.logger.all(`🎉 Sync complete. Saving current server state to "${this.serverPath + this.stateName}"`); + if (this.dryRun === false) { + yield (0, utilities_1.retryRequest)(this.logger, () => __awaiter(this, void 0, void 0, function* () { return yield this.client.uploadFrom(this.localPath + this.stateName, this.stateName); })); + } + }); + } +} +exports.FTPSyncProvider = FTPSyncProvider; /***/ }), @@ -3704,75 +3704,75 @@ exports.FTPSyncProvider = FTPSyncProvider; /***/ ((__unused_webpack_module, exports) => { "use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.ErrorCode = exports.syncFileDescription = exports.currentSyncFileVersion = void 0; -exports.currentSyncFileVersion = "1.0.0"; -exports.syncFileDescription = "DO NOT DELETE THIS FILE. This file is used to keep track of which files have been synced in the most recent deployment. If you delete this file a resync will need to be done (which can take a while) - read more: https://github.com/SamKirkland/FTP-Deploy-Action"; -var ErrorCode; -(function (ErrorCode) { - // The requested action is being initiated, expect another reply before proceeding with a new command. - ErrorCode[ErrorCode["RestartMarkerReplay"] = 110] = "RestartMarkerReplay"; - ErrorCode[ErrorCode["ServiceReadyInNNNMinutes"] = 120] = "ServiceReadyInNNNMinutes"; - ErrorCode[ErrorCode["DataConnectionAlreadyOpenStartingTransfer"] = 125] = "DataConnectionAlreadyOpenStartingTransfer"; - ErrorCode[ErrorCode["FileStatusOkayOpeningDataConnection"] = 150] = "FileStatusOkayOpeningDataConnection"; - // The requested action has been successfully completed. - ErrorCode[ErrorCode["CommandNotImplemented"] = 202] = "CommandNotImplemented"; - ErrorCode[ErrorCode["SystemStatus"] = 211] = "SystemStatus"; - ErrorCode[ErrorCode["DirectoryStatus"] = 212] = "DirectoryStatus"; - ErrorCode[ErrorCode["FileStatus"] = 213] = "FileStatus"; - ErrorCode[ErrorCode["HelpMessage"] = 214] = "HelpMessage"; - ErrorCode[ErrorCode["IANAOfficialName"] = 215] = "IANAOfficialName"; - ErrorCode[ErrorCode["ReadyForNewUser"] = 220] = "ReadyForNewUser"; - ErrorCode[ErrorCode["ClosingControlConnection"] = 221] = "ClosingControlConnection"; - ErrorCode[ErrorCode["DataConnectionOpen"] = 225] = "DataConnectionOpen"; - ErrorCode[ErrorCode["SuccessNowClosingDataConnection"] = 226] = "SuccessNowClosingDataConnection"; - ErrorCode[ErrorCode["EnteringPassiveMode"] = 227] = "EnteringPassiveMode"; - ErrorCode[ErrorCode["EnteringLongPassiveMode"] = 228] = "EnteringLongPassiveMode"; - ErrorCode[ErrorCode["EnteringExtendedPassiveMode"] = 229] = "EnteringExtendedPassiveMode"; - ErrorCode[ErrorCode["UserLoggedIn"] = 230] = "UserLoggedIn"; - ErrorCode[ErrorCode["UserLoggedOut"] = 231] = "UserLoggedOut"; - ErrorCode[ErrorCode["LogoutWillCompleteWhenTransferDone"] = 232] = "LogoutWillCompleteWhenTransferDone"; - ErrorCode[ErrorCode["ServerAcceptsAuthenticationMethod"] = 234] = "ServerAcceptsAuthenticationMethod"; - ErrorCode[ErrorCode["ActionComplete"] = 250] = "ActionComplete"; - ErrorCode[ErrorCode["PathNameCreated"] = 257] = "PathNameCreated"; - // The command has been accepted, but the requested action is on hold, pending receipt of further information. - ErrorCode[ErrorCode["UsernameOkayPasswordNeeded"] = 331] = "UsernameOkayPasswordNeeded"; - ErrorCode[ErrorCode["NeedAccountForLogin"] = 332] = "NeedAccountForLogin"; - ErrorCode[ErrorCode["RequestedFileActionPendingFurtherInformation"] = 350] = "RequestedFileActionPendingFurtherInformation"; - // The command was not accepted and the requested action did not take place, but the error condition is temporary and the action may be requested again. - ErrorCode[ErrorCode["ServiceNotAvailable"] = 421] = "ServiceNotAvailable"; - ErrorCode[ErrorCode["CantOpenDataConnection"] = 425] = "CantOpenDataConnection"; - ErrorCode[ErrorCode["ConnectionClosed"] = 426] = "ConnectionClosed"; - ErrorCode[ErrorCode["InvalidUsernameOrPassword"] = 430] = "InvalidUsernameOrPassword"; - ErrorCode[ErrorCode["HostUnavailable"] = 434] = "HostUnavailable"; - ErrorCode[ErrorCode["FileActionNotTaken"] = 450] = "FileActionNotTaken"; - ErrorCode[ErrorCode["LocalErrorProcessing"] = 451] = "LocalErrorProcessing"; - ErrorCode[ErrorCode["InsufficientStorageSpaceOrFileInUse"] = 452] = "InsufficientStorageSpaceOrFileInUse"; - // Syntax error, command unrecognized and the requested action did not take place. This may include errors such as command line too long. - ErrorCode[ErrorCode["SyntaxErrorInParameters"] = 501] = "SyntaxErrorInParameters"; - ErrorCode[ErrorCode["CommandNotImpemented"] = 502] = "CommandNotImpemented"; - ErrorCode[ErrorCode["BadSequenceOfCommands"] = 503] = "BadSequenceOfCommands"; - ErrorCode[ErrorCode["CommandNotImplementedForThatParameter"] = 504] = "CommandNotImplementedForThatParameter"; - ErrorCode[ErrorCode["NotLoggedIn"] = 530] = "NotLoggedIn"; - ErrorCode[ErrorCode["NeedAccountForStoringFiles"] = 532] = "NeedAccountForStoringFiles"; - ErrorCode[ErrorCode["CouldNotConnectToServerRequiresSSL"] = 534] = "CouldNotConnectToServerRequiresSSL"; - ErrorCode[ErrorCode["FileNotFoundOrNoAccess"] = 550] = "FileNotFoundOrNoAccess"; - ErrorCode[ErrorCode["UnknownPageType"] = 551] = "UnknownPageType"; - ErrorCode[ErrorCode["ExceededStorageAllocation"] = 552] = "ExceededStorageAllocation"; - ErrorCode[ErrorCode["FileNameNotAllowed"] = 553] = "FileNameNotAllowed"; - // Replies regarding confidentiality and integrity - ErrorCode[ErrorCode["IntegrityProtectedReply"] = 631] = "IntegrityProtectedReply"; - ErrorCode[ErrorCode["ConfidentialityAndIntegrityProtectedReply"] = 632] = "ConfidentialityAndIntegrityProtectedReply"; - ErrorCode[ErrorCode["ConfidentialityProtectedReply"] = 633] = "ConfidentialityProtectedReply"; - // Common Winsock Error Codes[2] (These are not FTP return codes) - ErrorCode[ErrorCode["ConnectionClosedByServer"] = 10054] = "ConnectionClosedByServer"; - ErrorCode[ErrorCode["CannotConnect"] = 10060] = "CannotConnect"; - ErrorCode[ErrorCode["CannotConnectRefusedByServer"] = 10061] = "CannotConnectRefusedByServer"; - ErrorCode[ErrorCode["DirectoryNotEmpty"] = 10066] = "DirectoryNotEmpty"; - ErrorCode[ErrorCode["TooManyUsers"] = 10068] = "TooManyUsers"; -})(ErrorCode = exports.ErrorCode || (exports.ErrorCode = {})); -; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ErrorCode = exports.syncFileDescription = exports.currentSyncFileVersion = void 0; +exports.currentSyncFileVersion = "1.0.0"; +exports.syncFileDescription = "DO NOT DELETE THIS FILE. This file is used to keep track of which files have been synced in the most recent deployment. If you delete this file a resync will need to be done (which can take a while) - read more: https://github.com/SamKirkland/FTP-Deploy-Action"; +var ErrorCode; +(function (ErrorCode) { + // The requested action is being initiated, expect another reply before proceeding with a new command. + ErrorCode[ErrorCode["RestartMarkerReplay"] = 110] = "RestartMarkerReplay"; + ErrorCode[ErrorCode["ServiceReadyInNNNMinutes"] = 120] = "ServiceReadyInNNNMinutes"; + ErrorCode[ErrorCode["DataConnectionAlreadyOpenStartingTransfer"] = 125] = "DataConnectionAlreadyOpenStartingTransfer"; + ErrorCode[ErrorCode["FileStatusOkayOpeningDataConnection"] = 150] = "FileStatusOkayOpeningDataConnection"; + // The requested action has been successfully completed. + ErrorCode[ErrorCode["CommandNotImplemented"] = 202] = "CommandNotImplemented"; + ErrorCode[ErrorCode["SystemStatus"] = 211] = "SystemStatus"; + ErrorCode[ErrorCode["DirectoryStatus"] = 212] = "DirectoryStatus"; + ErrorCode[ErrorCode["FileStatus"] = 213] = "FileStatus"; + ErrorCode[ErrorCode["HelpMessage"] = 214] = "HelpMessage"; + ErrorCode[ErrorCode["IANAOfficialName"] = 215] = "IANAOfficialName"; + ErrorCode[ErrorCode["ReadyForNewUser"] = 220] = "ReadyForNewUser"; + ErrorCode[ErrorCode["ClosingControlConnection"] = 221] = "ClosingControlConnection"; + ErrorCode[ErrorCode["DataConnectionOpen"] = 225] = "DataConnectionOpen"; + ErrorCode[ErrorCode["SuccessNowClosingDataConnection"] = 226] = "SuccessNowClosingDataConnection"; + ErrorCode[ErrorCode["EnteringPassiveMode"] = 227] = "EnteringPassiveMode"; + ErrorCode[ErrorCode["EnteringLongPassiveMode"] = 228] = "EnteringLongPassiveMode"; + ErrorCode[ErrorCode["EnteringExtendedPassiveMode"] = 229] = "EnteringExtendedPassiveMode"; + ErrorCode[ErrorCode["UserLoggedIn"] = 230] = "UserLoggedIn"; + ErrorCode[ErrorCode["UserLoggedOut"] = 231] = "UserLoggedOut"; + ErrorCode[ErrorCode["LogoutWillCompleteWhenTransferDone"] = 232] = "LogoutWillCompleteWhenTransferDone"; + ErrorCode[ErrorCode["ServerAcceptsAuthenticationMethod"] = 234] = "ServerAcceptsAuthenticationMethod"; + ErrorCode[ErrorCode["ActionComplete"] = 250] = "ActionComplete"; + ErrorCode[ErrorCode["PathNameCreated"] = 257] = "PathNameCreated"; + // The command has been accepted, but the requested action is on hold, pending receipt of further information. + ErrorCode[ErrorCode["UsernameOkayPasswordNeeded"] = 331] = "UsernameOkayPasswordNeeded"; + ErrorCode[ErrorCode["NeedAccountForLogin"] = 332] = "NeedAccountForLogin"; + ErrorCode[ErrorCode["RequestedFileActionPendingFurtherInformation"] = 350] = "RequestedFileActionPendingFurtherInformation"; + // The command was not accepted and the requested action did not take place, but the error condition is temporary and the action may be requested again. + ErrorCode[ErrorCode["ServiceNotAvailable"] = 421] = "ServiceNotAvailable"; + ErrorCode[ErrorCode["CantOpenDataConnection"] = 425] = "CantOpenDataConnection"; + ErrorCode[ErrorCode["ConnectionClosed"] = 426] = "ConnectionClosed"; + ErrorCode[ErrorCode["InvalidUsernameOrPassword"] = 430] = "InvalidUsernameOrPassword"; + ErrorCode[ErrorCode["HostUnavailable"] = 434] = "HostUnavailable"; + ErrorCode[ErrorCode["FileActionNotTaken"] = 450] = "FileActionNotTaken"; + ErrorCode[ErrorCode["LocalErrorProcessing"] = 451] = "LocalErrorProcessing"; + ErrorCode[ErrorCode["InsufficientStorageSpaceOrFileInUse"] = 452] = "InsufficientStorageSpaceOrFileInUse"; + // Syntax error, command unrecognized and the requested action did not take place. This may include errors such as command line too long. + ErrorCode[ErrorCode["SyntaxErrorInParameters"] = 501] = "SyntaxErrorInParameters"; + ErrorCode[ErrorCode["CommandNotImpemented"] = 502] = "CommandNotImpemented"; + ErrorCode[ErrorCode["BadSequenceOfCommands"] = 503] = "BadSequenceOfCommands"; + ErrorCode[ErrorCode["CommandNotImplementedForThatParameter"] = 504] = "CommandNotImplementedForThatParameter"; + ErrorCode[ErrorCode["NotLoggedIn"] = 530] = "NotLoggedIn"; + ErrorCode[ErrorCode["NeedAccountForStoringFiles"] = 532] = "NeedAccountForStoringFiles"; + ErrorCode[ErrorCode["CouldNotConnectToServerRequiresSSL"] = 534] = "CouldNotConnectToServerRequiresSSL"; + ErrorCode[ErrorCode["FileNotFoundOrNoAccess"] = 550] = "FileNotFoundOrNoAccess"; + ErrorCode[ErrorCode["UnknownPageType"] = 551] = "UnknownPageType"; + ErrorCode[ErrorCode["ExceededStorageAllocation"] = 552] = "ExceededStorageAllocation"; + ErrorCode[ErrorCode["FileNameNotAllowed"] = 553] = "FileNameNotAllowed"; + // Replies regarding confidentiality and integrity + ErrorCode[ErrorCode["IntegrityProtectedReply"] = 631] = "IntegrityProtectedReply"; + ErrorCode[ErrorCode["ConfidentialityAndIntegrityProtectedReply"] = 632] = "ConfidentialityAndIntegrityProtectedReply"; + ErrorCode[ErrorCode["ConfidentialityProtectedReply"] = 633] = "ConfidentialityProtectedReply"; + // Common Winsock Error Codes[2] (These are not FTP return codes) + ErrorCode[ErrorCode["ConnectionClosedByServer"] = 10054] = "ConnectionClosedByServer"; + ErrorCode[ErrorCode["CannotConnect"] = 10060] = "CannotConnect"; + ErrorCode[ErrorCode["CannotConnectRefusedByServer"] = 10061] = "CannotConnectRefusedByServer"; + ErrorCode[ErrorCode["DirectoryNotEmpty"] = 10066] = "DirectoryNotEmpty"; + ErrorCode[ErrorCode["TooManyUsers"] = 10068] = "TooManyUsers"; +})(ErrorCode = exports.ErrorCode || (exports.ErrorCode = {})); +; /***/ }), @@ -3781,187 +3781,187 @@ var ErrorCode; /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; - -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.applyExcludeFilter = exports.getDefaultSettings = exports.Timer = exports.Timings = exports.retryRequest = exports.formatNumber = exports.pluralize = exports.Logger = void 0; -const pretty_ms_1 = __importDefault(__nccwpck_require__(1127)); -const module_1 = __nccwpck_require__(8347); -const types_1 = __nccwpck_require__(6703); -const multimatch_1 = __importDefault(__nccwpck_require__(8222)); -class Logger { - constructor(level) { - this.level = level; - } - all(...data) { - console.log(...data); - } - standard(...data) { - if (this.level === "minimal") { - return; - } - console.log(...data); - } - verbose(...data) { - if (this.level !== "verbose") { - return; - } - console.log(...data); - } -} -exports.Logger = Logger; -function pluralize(count, singular, plural) { - if (count === 1) { - return singular; - } - return plural; -} -exports.pluralize = pluralize; -function formatNumber(number) { - return number.toLocaleString(); -} -exports.formatNumber = formatNumber; -/** - * retry a request - * - * @example retryRequest(logger, async () => await item()); - */ -function retryRequest(logger, callback) { - return __awaiter(this, void 0, void 0, function* () { - try { - return yield callback(); - } - catch (e) { - if (e.code >= 400 && e.code <= 499) { - logger.standard("400 level error from server when performing action - retrying..."); - logger.standard(e); - if (e.code === types_1.ErrorCode.ConnectionClosed) { - logger.all("Connection closed. This library does not currently handle reconnects"); - // await global.reconnect(); - // todo reset current working dir - throw e; - } - return yield callback(); - } - else { - throw e; - } - } - }); -} -exports.retryRequest = retryRequest; -class Timings { - constructor() { - this.timers = {}; - } - start(type) { - if (this.timers[type] === undefined) { - this.timers[type] = new Timer(); - } - this.timers[type].start(); - } - stop(type) { - this.timers[type].stop(); - } - getTime(type) { - const timer = this.timers[type]; - if (timer === undefined || timer.time === null) { - return 0; - } - return timer.time; - } - getTimeFormatted(type) { - const timer = this.timers[type]; - if (timer === undefined || timer.time === null) { - return "💣 Failed"; - } - return (0, pretty_ms_1.default)(timer.time, { verbose: true }); - } -} -exports.Timings = Timings; -class Timer { - constructor() { - this.totalTime = null; - this.startTime = null; - this.endTime = null; - } - start() { - this.startTime = process.hrtime(); - } - stop() { - if (this.startTime === null) { - throw new Error("Called .stop() before calling .start()"); - } - this.endTime = process.hrtime(this.startTime); - const currentSeconds = this.totalTime === null ? 0 : this.totalTime[0]; - const currentNS = this.totalTime === null ? 0 : this.totalTime[1]; - this.totalTime = [ - currentSeconds + this.endTime[0], - currentNS + this.endTime[1] - ]; - } - get time() { - if (this.totalTime === null) { - return null; - } - return (this.totalTime[0] * 1000) + (this.totalTime[1] / 1000000); - } -} -exports.Timer = Timer; -function getDefaultSettings(withoutDefaults) { - var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; - if (withoutDefaults["local-dir"] !== undefined) { - if (!withoutDefaults["local-dir"].endsWith("/")) { - throw new Error("local-dir should be a folder (must end with /)"); - } - } - if (withoutDefaults["server-dir"] !== undefined) { - if (!withoutDefaults["server-dir"].endsWith("/")) { - throw new Error("server-dir should be a folder (must end with /)"); - } - } - return { - "server": withoutDefaults.server, - "username": withoutDefaults.username, - "password": withoutDefaults.password, - "port": (_a = withoutDefaults.port) !== null && _a !== void 0 ? _a : 21, - "protocol": (_b = withoutDefaults.protocol) !== null && _b !== void 0 ? _b : "ftp", - "local-dir": (_c = withoutDefaults["local-dir"]) !== null && _c !== void 0 ? _c : "./", - "server-dir": (_d = withoutDefaults["server-dir"]) !== null && _d !== void 0 ? _d : "./", - "state-name": (_e = withoutDefaults["state-name"]) !== null && _e !== void 0 ? _e : ".ftp-deploy-sync-state.json", - "dry-run": (_f = withoutDefaults["dry-run"]) !== null && _f !== void 0 ? _f : false, - "dangerous-clean-slate": (_g = withoutDefaults["dangerous-clean-slate"]) !== null && _g !== void 0 ? _g : false, - "exclude": (_h = withoutDefaults.exclude) !== null && _h !== void 0 ? _h : module_1.excludeDefaults, - "log-level": (_j = withoutDefaults["log-level"]) !== null && _j !== void 0 ? _j : "standard", - "security": (_k = withoutDefaults.security) !== null && _k !== void 0 ? _k : "loose", - "timeout": (_l = withoutDefaults.timeout) !== null && _l !== void 0 ? _l : 30000, - }; -} -exports.getDefaultSettings = getDefaultSettings; -function applyExcludeFilter(stat, excludeFilters) { - // match exclude, return immediatley - if (excludeFilters.length > 0) { - // todo this could be a performance problem... - const pathWithFolderSlash = stat.path + (stat.isDirectory() ? "/" : ""); - const excludeMatch = (0, multimatch_1.default)(pathWithFolderSlash, excludeFilters, { matchBase: true, dot: true }); - if (excludeMatch.length > 0) { - return false; - } - } - return true; -} -exports.applyExcludeFilter = applyExcludeFilter; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.applyExcludeFilter = exports.getDefaultSettings = exports.Timer = exports.Timings = exports.retryRequest = exports.formatNumber = exports.pluralize = exports.Logger = void 0; +const pretty_ms_1 = __importDefault(__nccwpck_require__(1127)); +const module_1 = __nccwpck_require__(8347); +const types_1 = __nccwpck_require__(6703); +const multimatch_1 = __importDefault(__nccwpck_require__(8222)); +class Logger { + constructor(level) { + this.level = level; + } + all(...data) { + console.log(...data); + } + standard(...data) { + if (this.level === "minimal") { + return; + } + console.log(...data); + } + verbose(...data) { + if (this.level !== "verbose") { + return; + } + console.log(...data); + } +} +exports.Logger = Logger; +function pluralize(count, singular, plural) { + if (count === 1) { + return singular; + } + return plural; +} +exports.pluralize = pluralize; +function formatNumber(number) { + return number.toLocaleString(); +} +exports.formatNumber = formatNumber; +/** + * retry a request + * + * @example retryRequest(logger, async () => await item()); + */ +function retryRequest(logger, callback) { + return __awaiter(this, void 0, void 0, function* () { + try { + return yield callback(); + } + catch (e) { + if (e.code >= 400 && e.code <= 499) { + logger.standard("400 level error from server when performing action - retrying..."); + logger.standard(e); + if (e.code === types_1.ErrorCode.ConnectionClosed) { + logger.all("Connection closed. This library does not currently handle reconnects"); + // await global.reconnect(); + // todo reset current working dir + throw e; + } + return yield callback(); + } + else { + throw e; + } + } + }); +} +exports.retryRequest = retryRequest; +class Timings { + constructor() { + this.timers = {}; + } + start(type) { + if (this.timers[type] === undefined) { + this.timers[type] = new Timer(); + } + this.timers[type].start(); + } + stop(type) { + this.timers[type].stop(); + } + getTime(type) { + const timer = this.timers[type]; + if (timer === undefined || timer.time === null) { + return 0; + } + return timer.time; + } + getTimeFormatted(type) { + const timer = this.timers[type]; + if (timer === undefined || timer.time === null) { + return "💣 Failed"; + } + return (0, pretty_ms_1.default)(timer.time, { verbose: true }); + } +} +exports.Timings = Timings; +class Timer { + constructor() { + this.totalTime = null; + this.startTime = null; + this.endTime = null; + } + start() { + this.startTime = process.hrtime(); + } + stop() { + if (this.startTime === null) { + throw new Error("Called .stop() before calling .start()"); + } + this.endTime = process.hrtime(this.startTime); + const currentSeconds = this.totalTime === null ? 0 : this.totalTime[0]; + const currentNS = this.totalTime === null ? 0 : this.totalTime[1]; + this.totalTime = [ + currentSeconds + this.endTime[0], + currentNS + this.endTime[1] + ]; + } + get time() { + if (this.totalTime === null) { + return null; + } + return (this.totalTime[0] * 1000) + (this.totalTime[1] / 1000000); + } +} +exports.Timer = Timer; +function getDefaultSettings(withoutDefaults) { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; + if (withoutDefaults["local-dir"] !== undefined) { + if (!withoutDefaults["local-dir"].endsWith("/")) { + throw new Error("local-dir should be a folder (must end with /)"); + } + } + if (withoutDefaults["server-dir"] !== undefined) { + if (!withoutDefaults["server-dir"].endsWith("/")) { + throw new Error("server-dir should be a folder (must end with /)"); + } + } + return { + "server": withoutDefaults.server, + "username": withoutDefaults.username, + "password": withoutDefaults.password, + "port": (_a = withoutDefaults.port) !== null && _a !== void 0 ? _a : 21, + "protocol": (_b = withoutDefaults.protocol) !== null && _b !== void 0 ? _b : "ftp", + "local-dir": (_c = withoutDefaults["local-dir"]) !== null && _c !== void 0 ? _c : "./", + "server-dir": (_d = withoutDefaults["server-dir"]) !== null && _d !== void 0 ? _d : "./", + "state-name": (_e = withoutDefaults["state-name"]) !== null && _e !== void 0 ? _e : ".ftp-deploy-sync-state.json", + "dry-run": (_f = withoutDefaults["dry-run"]) !== null && _f !== void 0 ? _f : false, + "dangerous-clean-slate": (_g = withoutDefaults["dangerous-clean-slate"]) !== null && _g !== void 0 ? _g : false, + "exclude": (_h = withoutDefaults.exclude) !== null && _h !== void 0 ? _h : module_1.excludeDefaults, + "log-level": (_j = withoutDefaults["log-level"]) !== null && _j !== void 0 ? _j : "standard", + "security": (_k = withoutDefaults.security) !== null && _k !== void 0 ? _k : "loose", + "timeout": (_l = withoutDefaults.timeout) !== null && _l !== void 0 ? _l : 30000, + }; +} +exports.getDefaultSettings = getDefaultSettings; +function applyExcludeFilter(stat, excludeFilters) { + // match exclude, return immediatley + if (excludeFilters.length > 0) { + // todo this could be a performance problem... + const pathWithFolderSlash = stat.path + (stat.isDirectory() ? "/" : ""); + const excludeMatch = (0, multimatch_1.default)(pathWithFolderSlash, excludeFilters, { matchBase: true, dot: true }); + if (excludeMatch.length > 0) { + return false; + } + } + return true; +} +exports.applyExcludeFilter = applyExcludeFilter; /***/ }), @@ -4116,12 +4116,12 @@ const netUtils_1 = __nccwpck_require__(6288); const transfer_1 = __nccwpck_require__(5803); const parseControlResponse_1 = __nccwpck_require__(9948); // 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"]; const LIST_COMMANDS_MLSD = ["MLSD", "LIST -a", "LIST"]; /** @@ -4174,7 +4174,7 @@ class Client { host, port, 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(); } /** @@ -4183,7 +4183,7 @@ class Client { */ connectImplicitTLS(host = "localhost", port = 21, tlsOptions = {}) { 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; return this._handleConnectResponse(); } @@ -4196,14 +4196,13 @@ class Client { // The connection has been destroyed by the FTPContext at this point. task.reject(res); } - else if (parseControlResponse_1.positiveCompletion(res.code)) { + else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { task.resolve(res); } // Reject all other codes, including 120 "Service ready in nnn minutes". else { // Don't stay connected but don't replace the socket yet by using reset() // so the user can inspect properties of this instance. - this.ftp.socket.destroy(); task.reject(new FtpContext_1.FTPError(res)); } }); @@ -4244,9 +4243,9 @@ class Client { */ async useTLS(options = {}, command = "AUTH TLS") { 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; } /** @@ -4256,12 +4255,12 @@ class Client { * @param password Password to use for login. Optional, default is "guest". */ 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) => { if (res instanceof Error) { task.reject(res); } - 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); } else if (res.code === 331) { // User name okay, need password @@ -4351,7 +4350,7 @@ class Client { const res = await this.sendIgnoringError("FEAT"); const features = new Map(); // 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. res.message.split("\n").slice(1, -1).forEach(line => { // A typical lines looks like: " REST STREAM" or " MDTM". @@ -4383,7 +4382,7 @@ class Client { const validPath = await this.protectWhitespace(path); const res = await this.send(`MDTM ${validPath}`); const date = res.message.slice(4); - return parseListMLSD_1.parseMLSxDate(date); + return (0, parseListMLSD_1.parseMLSxDate)(date); } /** * Get the size of a file. @@ -4467,7 +4466,7 @@ class Client { */ async _uploadLocalFile(localPath, remotePath, command, options) { const fd = await fsOpen(localPath, "r"); - const source = fs_1.createReadStream("", { + const source = (0, fs_1.createReadStream)("", { fd, start: options.localStart, end: options.localEndInclusive, @@ -4491,7 +4490,7 @@ class Client { await this.prepareTransfer(this.ftp); // Keep the keyword `await` or the `finally` clause below runs too early // 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, tracker: this._progressTracker, command, @@ -4529,7 +4528,7 @@ class Client { const appendingToLocalFile = startAt > 0; const fileSystemFlags = appendingToLocalFile ? "r+" : "w"; const fd = await fsOpen(localPath, fileSystemFlags); - const destination = fs_1.createWriteStream("", { + const destination = (0, fs_1.createWriteStream)("", { fd, start: startAt, autoClose: false @@ -4561,7 +4560,7 @@ class Client { await this.prepareTransfer(this.ftp); // Keep the keyword `await` or the `finally` clause below runs too early // 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, tracker: this._progressTracker, command: startAt > 0 ? `REST ${startAt}` : `RETR ${validPath}`, @@ -4606,7 +4605,7 @@ class Client { */ async _requestListWithCommand(command) { const buffer = new StringWriter_1.StringWriter(); - await transfer_1.downloadTo(buffer, { + await (0, transfer_1.downloadTo)(buffer, { ftp: this.ftp, tracker: this._progressTracker, command, @@ -4679,7 +4678,7 @@ class Client { async _uploadToWorkingDir(localDirPath) { const files = await fsReadDir(localDirPath); 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); if (stats.isFile()) { await this.uploadFrom(fullPath, file); @@ -4711,7 +4710,7 @@ class Client { async _downloadFromWorkingDir(localDirPath) { await ensureLocalDirectory(localDirPath); 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) { await this.cd(file.name); await this._downloadFromWorkingDir(localPath); @@ -4781,26 +4780,26 @@ class Client { * Try all available transfer strategies and pick the first one that works. Update `client` to * use the working strategy for all successive transfer requests. * - * @param transferModes * @returns a function that will try the provided strategies. */ - _enterFirstCompatibleMode(transferModes) { + _enterFirstCompatibleMode(strategies) { return async (ftp) => { - ftp.log("Trying to find optimal transfer mode..."); - for (const transferMode of transferModes) { + ftp.log("Trying to find optimal transfer strategy..."); + let lastError = undefined; + for (const strategy of strategies) { try { - const res = await transferMode(ftp); - ftp.log("Optimal transfer mode found."); - this.prepareTransfer = transferMode; // eslint-disable-line require-atomic-updates + const res = await strategy(ftp); + ftp.log("Optimal transfer strategy found."); + this.prepareTransfer = strategy; // 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.`); + lastError = err; } } - throw new Error("None of the available transfer modes work."); + throw new Error(`None of the available transfer strategies work. Last error response was '${lastError}'.`); }; } /** @@ -5038,7 +5037,8 @@ class FTPContext { return; } this._closingError = err; - // Before giving the user's task a chance to react, make sure we won't be bothered with any inputs. + this.send("QUIT"); // Don't wait for an answer + // Close the sockets but don't fully reset this context to preserve `this._closingError`. this._closeSocket(this._socket); this._closeSocket(this._dataSocket); // Give the user's task a chance to react, maybe cleanup resources. @@ -5071,16 +5071,17 @@ class FTPContext { set socket(socket) { // No data socket should be open in any case where the control socket is set or upgraded. this.dataSocket = undefined; + // This being a reset, reset any other state apart from the socket. this.tlsOptions = {}; - // This being a soft reset, remove any remaining partial response. this._partialResponse = ""; if (this._socket) { - // Only close the current connection if the new is not an upgrade. - const isUpgrade = socket.localPort === this._socket.localPort; - if (!isUpgrade) { - this._socket.destroy(); + const newSocketUpgradesExisting = socket.localPort === this._socket.localPort; + if (newSocketUpgradesExisting) { + this._removeSocketListeners(this.socket); + } + else { + this._closeSocket(this.socket); } - this._removeSocketListeners(this._socket); } if (socket) { // Setting a completely new control socket is in essence something like a reset. That's @@ -5168,7 +5169,6 @@ class FTPContext { */ handle(command, responseHandler) { 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()'?"); err.stack += `\nRunning task launched at: ${this._task.stack}`; this.closeWithError(err); @@ -5176,27 +5176,25 @@ class FTPContext { // because the context closed already. That way, users will receive an exception where // they called this method by mistake. } - return new Promise((resolvePromise, rejectPromise) => { - const stack = new Error().stack || "Unknown call stack"; - const resolver = { - resolve: (arg) => { - this._stopTrackingTask(); - resolvePromise(arg); - }, - reject: err => { - this._stopTrackingTask(); - rejectPromise(err); - } - }; + return new Promise((resolveTask, rejectTask) => { this._task = { - stack, - resolver, - responseHandler + stack: new Error().stack || "Unknown call stack", + responseHandler, + resolver: { + resolve: arg => { + this._stopTrackingTask(); + resolveTask(arg); + }, + reject: err => { + this._stopTrackingTask(); + rejectTask(err); + } + } }; if (this._closingError) { // This client has been closed. Provide an error that describes this one as being caused // by `_closingError`, include stack traces for both. - const err = new Error("Client is closed"); // Type 'Error' is not correctly defined, doesn't have 'code'. + const err = new Error(`Client is closed because ${this._closingError.message}`); // Type 'Error' is not correctly defined, doesn't have 'code'. err.stack += `\nClosing reason: ${this._closingError.stack}`; err.code = this._closingError.code !== undefined ? this._closingError.code : "0"; this._passToHandler(err); @@ -5244,7 +5242,7 @@ class FTPContext { this.log(`< ${chunk}`); // This chunk might complete an earlier partial response. const completeResponse = this._partialResponse + chunk; - const parsed = parseControlResponse_1.parseControlResponse(completeResponse); + const parsed = (0, parseControlResponse_1.parseControlResponse)(completeResponse); // Remember any incomplete remainder. this._partialResponse = parsed.rest; // Each response group is passed along individually. @@ -5285,7 +5283,10 @@ class FTPContext { this.closeWithError(new Error(`Socket closed due to transmission error (${identifier})`)); } }); - socket.once("timeout", () => this.closeWithError(new Error(`Timeout (${identifier})`))); + socket.once("timeout", () => { + socket.destroy(); + this.closeWithError(new Error(`Timeout (${identifier})`)); + }); } /** * Close a socket. @@ -5293,8 +5294,11 @@ class FTPContext { */ _closeSocket(socket) { if (socket) { - socket.destroy(); this._removeSocketListeners(socket); + socket.on("error", () => { }); + socket.on("timeout", () => socket.destroy()); + socket.setTimeout(this.timeout); + socket.end(); } } /** @@ -5453,7 +5457,11 @@ exports.StringWriter = StringWriter; 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) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -5515,7 +5523,7 @@ function upgradeSocket(socket, options) { const tlsOptions = Object.assign({}, options, { socket }); - const tlsSocket = tls_1.connect(tlsOptions, () => { + const tlsSocket = (0, tls_1.connect)(tlsOptions, () => { const expectCertificate = tlsOptions.rejectUnauthorized !== false; if (expectCertificate && !tlsSocket.authorized) { reject(tlsSocket.authorizationError); @@ -5545,7 +5553,8 @@ function ipIsPrivateV4Address(ip = "") { const octets = ip.split(".").map(o => parseInt(o, 10)); 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] === 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; @@ -5634,7 +5643,11 @@ function isNotBlank(str) { 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) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -5668,9 +5681,12 @@ const availableParsers = [ function firstCompatibleParser(line, parsers) { return parsers.find(parser => parser.testLine(line) === true); } -function stringIsNotBlank(str) { +function isNotBlank(str) { return str.trim() !== ""; } +function isNotMeta(str) { + return !str.startsWith("total"); +} const REGEX_NEWLINE = /\r?\n/; /** * Parse raw directory listing. @@ -5678,7 +5694,8 @@ const REGEX_NEWLINE = /\r?\n/; function parseList(rawList) { const lines = rawList .split(REGEX_NEWLINE) - .filter(stringIsNotBlank); + .filter(isNotBlank) + .filter(isNotMeta); if (lines.length === 0) { return []; } @@ -5797,7 +5814,7 @@ const factHandlersByName = { if (value.startsWith("OS.unix=slink")) { info.type = FileInfo_1.FileType.SymbolicLink; info.link = value.substr(value.indexOf(":") + 1); - return 1 /* Continue */; + return 1 /* FactHandlerResult.Continue */; } switch (value) { case "file": @@ -5813,11 +5830,11 @@ const factHandlersByName = { break; case "cdir": // Current directory being listed 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 */; }, "unix.mode": (value, info) => { const digits = value.substr(-3); @@ -5898,7 +5915,7 @@ function parseLine(line) { continue; } const result = factHandler(factValue, info); - if (result === 2 /* IgnoreFile */) { + if (result === 2 /* FactHandlerResult.IgnoreFile */) { return undefined; } } @@ -5996,7 +6013,7 @@ const JA_YEAR = "\u5e74"; * {@code @} file has extended attributes */ 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?? + "(\\d+)" // link count + "\\s+" // separator @@ -6126,6 +6143,7 @@ function parseMode(r, w, x) { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.downloadTo = exports.uploadFrom = exports.connectForPassiveTransfer = exports.parsePasvResponse = exports.enterPassiveModeIPv4 = exports.parseEpsvResponse = exports.enterPassiveModeIPv6 = void 0; const netUtils_1 = __nccwpck_require__(6288); +const stream_1 = __nccwpck_require__(2781); const tls_1 = __nccwpck_require__(4404); const parseControlResponse_1 = __nccwpck_require__(9948); /** @@ -6176,7 +6194,7 @@ async function enterPassiveModeIPv4(ftp) { // 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. 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; } await connectForPassiveTransfer(target.host, target.port, ftp); @@ -6200,15 +6218,21 @@ function parsePasvResponse(message) { exports.parsePasvResponse = parsePasvResponse; function connectForPassiveTransfer(host, port, ftp) { return new Promise((resolve, reject) => { + let socket = ftp._newSocket(); const handleConnErr = function (err) { err.message = "Can't open data connection in passive mode: " + err.message; reject(err); }; - 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, // Reuse the TLS session negotiated earlier when the control connection // was upgraded. Servers expect this because it provides additional @@ -6227,6 +6251,7 @@ function connectForPassiveTransfer(host, port, ftp) { } // Let the FTPContext listen to errors from now on, remove local handler. socket.removeListener("error", handleConnErr); + socket.removeListener("timeout", handleTimeout); ftp.dataSocket = socket; resolve(); }); @@ -6336,18 +6361,22 @@ function uploadFrom(source, config) { // 'secureConnect'. If this hasn't happened yet, getCipher() returns undefined. const canUpload = "getCipher" in dataSocket ? dataSocket.getCipher() !== undefined : true; 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); } // Ignore all other positive preliminary response codes (< 200) @@ -6358,9 +6387,6 @@ function downloadTo(destination, config) { if (!config.ftp.dataSocket) { 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); return config.ftp.handle(config.command, (res, task) => { if (res instanceof Error) { @@ -6372,17 +6398,24 @@ function downloadTo(destination, config) { resolver.onError(task, new Error("Download should begin but no data connection is available.")); return; } - 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); + } + }); } else if (res.code === 350) { // Restarting at startAt. config.ftp.send("RETR " + config.remotePath); } - 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); } // Ignore all other positive preliminary response codes (< 200) @@ -6406,18 +6439,6 @@ function onConditionOrEvent(condition, emitter, 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); -} /***/ }), @@ -9037,7 +9058,8 @@ async function runDeployment() { "dangerous-clean-slate": (0, parse_1.optionalBoolean)("dangerous-clean-slate", core.getInput("dangerous-clean-slate")), "exclude": (0, parse_1.optionalStringArray)("exclude", core.getMultilineInput("exclude")), "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); } diff --git a/package-lock.json b/package-lock.json index 5168343..299d265 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1090,7 +1090,7 @@ "resolved": "https://registry.npmjs.org/@samkirkland/ftp-deploy/-/ftp-deploy-1.2.2.tgz", "integrity": "sha512-cg63yUurpZesLKwjSEiRFgvt72uDdGBCAe3DTtu1UBFobU5kLoMmDNpYmsYUutTY0bNiS/vhUClkU6eos72XLw==", "dependencies": { - "basic-ftp": "^4.6.6", + "basic-ftp": "^5.0.0", "lodash": "^4.17.21", "multimatch": "^5.0.0", "pretty-bytes": "^5.6.0",