From 928e32c59b403e784a28b5e516d2c705a9b4d0dc Mon Sep 17 00:00:00 2001 From: SamKirkland Date: Sat, 24 Oct 2020 00:56:44 -0500 Subject: [PATCH] back to building with ncc --- action.yml | 2 +- dist/index.js | 6772 +++++++++++++++++++++++++++++++++++++++++++++ dist/main.js | 128 - package-lock.json | 6 + package.json | 5 +- tsconfig.json | 1 + 6 files changed, 6783 insertions(+), 131 deletions(-) create mode 100644 dist/index.js delete mode 100644 dist/main.js diff --git a/action.yml b/action.yml index 2eaca6b..4bc1992 100644 --- a/action.yml +++ b/action.yml @@ -43,7 +43,7 @@ inputs: description: 'strict or loose' runs: using: 'node12' - main: 'dist/main.js' + main: 'dist/index.js' branding: icon: 'upload-cloud' color: 'orange' \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..80f6a37 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,6772 @@ +module.exports = +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 7351: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +"use strict"; + +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +const os = __importStar(__webpack_require__(2087)); +const utils_1 = __webpack_require__(5278); +/** + * Commands + * + * Command Format: + * ::name key=value,key=value::message + * + * Examples: + * ::warning::This is the message + * ::set-env name=MY_VAR::some value + */ +function issueCommand(command, properties, message) { + const cmd = new Command(command, properties, message); + process.stdout.write(cmd.toString() + os.EOL); +} +exports.issueCommand = issueCommand; +function issue(name, message = '') { + issueCommand(name, {}, message); +} +exports.issue = issue; +const CMD_STRING = '::'; +class Command { + constructor(command, properties, message) { + if (!command) { + command = 'missing.command'; + } + this.command = command; + this.properties = properties; + this.message = message; + } + toString() { + let cmdStr = CMD_STRING + this.command; + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' '; + let first = true; + for (const key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + const val = this.properties[key]; + if (val) { + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; + } + } + } + } + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; + return cmdStr; + } +} +function escapeData(s) { + return utils_1.toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} +function escapeProperty(s) { + return utils_1.toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); +} +//# sourceMappingURL=command.js.map + +/***/ }), + +/***/ 2186: +/***/ (function(__unused_webpack_module, exports, __webpack_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 __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +const command_1 = __webpack_require__(7351); +const file_command_1 = __webpack_require__(717); +const utils_1 = __webpack_require__(5278); +const os = __importStar(__webpack_require__(2087)); +const path = __importStar(__webpack_require__(5622)); +/** + * The code to exit an action + */ +var ExitCode; +(function (ExitCode) { + /** + * A code indicating that the action was successful + */ + ExitCode[ExitCode["Success"] = 0] = "Success"; + /** + * A code indicating that the action was a failure + */ + ExitCode[ExitCode["Failure"] = 1] = "Failure"; +})(ExitCode = exports.ExitCode || (exports.ExitCode = {})); +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- +/** + * Sets env variable for this action and future actions in the job + * @param name the name of the variable to set + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function exportVariable(name, val) { + const convertedVal = utils_1.toCommandValue(val); + process.env[name] = convertedVal; + const filePath = process.env['GITHUB_ENV'] || ''; + if (filePath) { + const delimiter = '_GitHubActionsFileCommandDelimeter_'; + const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; + file_command_1.issueCommand('ENV', commandValue); + } + else { + command_1.issueCommand('set-env', { name }, convertedVal); + } +} +exports.exportVariable = exportVariable; +/** + * Registers a secret which will get masked from logs + * @param secret value of the secret + */ +function setSecret(secret) { + command_1.issueCommand('add-mask', {}, secret); +} +exports.setSecret = setSecret; +/** + * Prepends inputPath to the PATH (for this action and future actions) + * @param inputPath + */ +function addPath(inputPath) { + const filePath = process.env['GITHUB_PATH'] || ''; + if (filePath) { + file_command_1.issueCommand('PATH', inputPath); + } + else { + command_1.issueCommand('add-path', {}, inputPath); + } + process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; +} +exports.addPath = addPath; +/** + * Gets the value of an input. The value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +function getInput(name, options) { + const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`); + } + return val.trim(); +} +exports.getInput = getInput; +/** + * Sets the value of an output. + * + * @param name name of the output to set + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function setOutput(name, value) { + command_1.issueCommand('set-output', { name }, value); +} +exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + command_1.issue('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +function setFailed(message) { + process.exitCode = ExitCode.Failure; + error(message); +} +exports.setFailed = setFailed; +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; +/** + * Writes debug message to user log + * @param message debug message + */ +function debug(message) { + command_1.issueCommand('debug', {}, message); +} +exports.debug = debug; +/** + * Adds an error issue + * @param message error issue message. Errors will be converted to string via toString() + */ +function error(message) { + command_1.issue('error', message instanceof Error ? message.toString() : message); +} +exports.error = error; +/** + * Adds an warning issue + * @param message warning issue message. Errors will be converted to string via toString() + */ +function warning(message) { + command_1.issue('warning', message instanceof Error ? message.toString() : message); +} +exports.warning = warning; +/** + * Writes info to log with console.log. + * @param message info message + */ +function info(message) { + process.stdout.write(message + os.EOL); +} +exports.info = info; +/** + * Begin an output group. + * + * Output until the next `groupEnd` will be foldable in this group + * + * @param name The name of the output group + */ +function startGroup(name) { + command_1.issue('group', name); +} +exports.startGroup = startGroup; +/** + * End an output group. + */ +function endGroup() { + command_1.issue('endgroup'); +} +exports.endGroup = endGroup; +/** + * Wrap an asynchronous function call in a group. + * + * Returns the same type as the function itself. + * + * @param name The name of the group + * @param fn The function to wrap in the group + */ +function group(name, fn) { + return __awaiter(this, void 0, void 0, function* () { + startGroup(name); + let result; + try { + result = yield fn(); + } + finally { + endGroup(); + } + return result; + }); +} +exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + command_1.issueCommand('save-state', { name }, value); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; +//# sourceMappingURL=core.js.map + +/***/ }), + +/***/ 717: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +"use strict"; + +// For internal use, subject to change. +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +const fs = __importStar(__webpack_require__(5747)); +const os = __importStar(__webpack_require__(2087)); +const utils_1 = __webpack_require__(5278); +function issueCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) { + throw new Error(`Unable to find environment variable for file command ${command}`); + } + if (!fs.existsSync(filePath)) { + throw new Error(`Missing file at path: ${filePath}`); + } + fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { + encoding: 'utf8' + }); +} +exports.issueCommand = issueCommand; +//# sourceMappingURL=file-command.js.map + +/***/ }), + +/***/ 5278: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +Object.defineProperty(exports, "__esModule", ({ value: true })); +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +//# sourceMappingURL=utils.js.map + +/***/ }), + +/***/ 5176: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.createFilter = void 0; +const normalize_1 = __webpack_require__(7561); +const util_1 = __webpack_require__(9735); +function createFilter(options, ...args) { + let criteria = args.length <= 1 ? args[0] : args; + let filters = normalize_1.normalize(criteria, options); + pathFilter[util_1._filters] = filters; + return pathFilter; + function pathFilter(...args) { + // Does the file path match any of the exclude filters? + let exclude = filters.exclude.some((filter) => filter(...args)); + if (exclude) { + return false; + } + if (filters.include.length === 0) { + // Include everything that's not excluded + return true; + } + // Does the file path match any of the include filters? + let include = filters.include.some((filter) => filter(...args)); + return include; + } +} +exports.createFilter = createFilter; +//# sourceMappingURL=create-filter.js.map + +/***/ }), + +/***/ 2405: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.filePathFilter = void 0; +const create_filter_1 = __webpack_require__(5176); +function filePathFilter(...args) { + return create_filter_1.createFilter({}, ...args); +} +exports.filePathFilter = filePathFilter; +//# sourceMappingURL=file-path-filter.js.map + +/***/ }), + +/***/ 3410: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.filePathFilter = void 0; +const file_path_filter_1 = __webpack_require__(2405); +Object.defineProperty(exports, "filePathFilter", ({ enumerable: true, get: function () { return file_path_filter_1.filePathFilter; } })); +__exportStar(__webpack_require__(3225), exports); +var create_filter_1 = __webpack_require__(5176); +Object.defineProperty(exports, "createFilter", ({ enumerable: true, get: function () { return create_filter_1.createFilter; } })); +// Export `filePathFilter` as a named export and the default export +exports.default = file_path_filter_1.filePathFilter; +// CommonJS default export hack +/* eslint-env commonjs */ +if ( true && typeof module.exports === "object") { + module.exports = Object.assign(module.exports.default, module.exports); +} +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 7561: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.normalize = void 0; +const globToRegExp = __webpack_require__(7117); +const path = __webpack_require__(5622); +const util_1 = __webpack_require__(9735); +/** + * Normalizes the user-provided filter criteria. The normalized form is a `Filters` object + * whose `include` and `exclude` properties are both `FilterFunction` arrays. + */ +function normalize(criteria, opts) { + let filters = { + include: [], + exclude: [], + }; + let options = normalizeOptions(opts); + // Convert each criterion to a FilterFunction + let tuples = normalizeCriteria(criteria, options); + // Populate the `include` and `exclude` arrays + for (let [filter, filterFunction] of tuples) { + filters[filter].push(filterFunction); + } + return filters; +} +exports.normalize = normalize; +/** + * Fills-in defaults for any options that weren't specified by the caller. + */ +function normalizeOptions(options) { + return { + // TODO: Remove the "getPath" fallback in the next minor release + map: options.map || options.getPath || String, + sep: options.sep || path.sep, + }; +} +/** + * Creates a `FilterFunction` for each given criterion. + */ +function normalizeCriteria(criteria, options, filter) { + let tuples = []; + if (Array.isArray(criteria)) { + for (let criterion of criteria) { + tuples.push(...normalizeCriteria(criterion, options, filter)); + } + } + else if (util_1.isPathFilter(criteria)) { + for (let filterFunction of criteria[util_1._filters].include) { + tuples.push(["include", filterFunction]); + } + for (let filterFunction of criteria[util_1._filters].exclude) { + tuples.push(["exclude", filterFunction]); + } + } + else if (util_1.isFilterCriterion(criteria)) { + tuples.push(normalizeCriterion(criteria, options, filter)); + } + else if (criteria && typeof criteria === "object" && !filter) { + if (criteria.include !== undefined) { + tuples.push(...normalizeCriteria(criteria.include, options, "include")); + } + if (criteria.exclude !== undefined) { + tuples.push(...normalizeCriteria(criteria.exclude, options, "exclude")); + } + } + else { + throw new Error(`Invalid filter criteria: ${criteria}`); + } + return tuples; +} +/** + * Creates a `FilterFunction` for the given criterion. + * + * @param criteria - One or more filter critiera + * @param options - Options for how the `FilterFunction` should behave + * @param filter - The type of filter. Defaults to `include`, except for glob patterns that start with "!" + */ +function normalizeCriterion(criterion, options, filter) { + const globOptions = { extended: true, globstar: true }; + let type = typeof criterion; + let filterFunction; + if (type === "function") { + filterFunction = criterion; + } + else if (type === "boolean") { + let bool = criterion; + filterFunction = function booleanFilter() { + return bool; + }; + } + else if (type === "string") { + let glob = criterion; + let invert = false; + if (glob.startsWith("!")) { + glob = glob.substr(1); + invert = Boolean(filter); + filter = filter || "exclude"; + } + let pattern = globToRegExp(glob, globOptions); + filterFunction = createGlobFilter(pattern, options, invert); + } + else if (criterion instanceof RegExp) { + let pattern = criterion; + let { map } = options; + filterFunction = function regExpFilter(...args) { + let filePath = map(...args); + return pattern.test(filePath); + }; + } + else { + throw new Error(`Invalid filter criteria: ${criterion}`); + } + return [filter || "include", filterFunction]; +} +/** + * Creates a `FilterFunction` for filtering based on glob patterns + */ +function createGlobFilter(pattern, options, invert) { + let { map, sep } = options; + return function globFilter(...args) { + let filePath = map(...args); + if (sep !== "/") { + // Glob patterns always expect forward slashes, even on Windows + filePath = filePath.replace(new RegExp("\\" + sep, "g"), "/"); + } + let match = pattern.test(filePath); + return invert ? !match : match; + }; +} +//# sourceMappingURL=normalize.js.map + +/***/ }), + +/***/ 3225: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +//# sourceMappingURL=types.js.map + +/***/ }), + +/***/ 9735: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.isPathFilter = exports.isFilterCriterion = exports._filters = void 0; +/** + * Symbol used to store the underlying filters of a `pathFilter()` function. + */ +exports._filters = Symbol("_filters"); +/** + * Determines whether the given value is a `FilterCriterion`. + */ +function isFilterCriterion(value) { + let type = typeof value; + return type === "string" || + type === "boolean" || + type === "function" || + value instanceof RegExp; +} +exports.isFilterCriterion = isFilterCriterion; +/** + * Determines whether the given value is one of our internal `pathFilter()` functions. + */ +function isPathFilter(value) { + let fn = value; + return fn && + typeof fn === "function" && + typeof fn[exports._filters] === "object"; +} +exports.isPathFilter = isPathFilter; +//# sourceMappingURL=util.js.map + +/***/ }), + +/***/ 504: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.asyncForEach = void 0; +/** + * Simultaneously processes all items in the given array. + * + * @param array - The array to iterate over + * @param iterator - The function to call for each item in the array + * @param done - The function to call when all iterators have completed + * + * @internal + */ +function asyncForEach(array, iterator, done) { + if (!Array.isArray(array)) { + throw new TypeError(`${array} is not an array`); + } + if (array.length === 0) { + // NOTE: Normally a bad idea to mix sync and async, but it's safe here because + // of the way that this method is currently used by DirectoryReader. + done(); + return; + } + // Simultaneously process all items in the array. + let pending = array.length; + for (let item of array) { + iterator(item, callback); + } + function callback() { + if (--pending === 0) { + done(); + } + } +} +exports.asyncForEach = asyncForEach; +//# sourceMappingURL=for-each.js.map + +/***/ }), + +/***/ 5833: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readdirAsync = void 0; +const fs = __webpack_require__(5747); +const directory_reader_1 = __webpack_require__(4918); +const for_each_1 = __webpack_require__(504); +const asyncFacade = { fs, forEach: for_each_1.asyncForEach }; +function readdirAsync(dir, options, callback) { + if (typeof options === "function") { + callback = options; + options = undefined; + } + let promise = new Promise((resolve, reject) => { + let results = []; + let reader = new directory_reader_1.DirectoryReader(dir, options, asyncFacade); + let stream = reader.stream; + stream.on("error", (err) => { + reject(err); + stream.pause(); + }); + stream.on("data", (result) => { + results.push(result); + }); + stream.on("end", () => { + resolve(results); + }); + }); + if (callback) { + promise.then((results) => callback(null, results), (err) => callback(err, undefined)); + } + else { + return promise; + } +} +exports.readdirAsync = readdirAsync; +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 8188: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.callOnce = exports.safeCall = void 0; +/** + * Calls a function with the given arguments, and ensures that the error-first callback is _always_ + * invoked exactly once, even if the function throws an error. + * + * @param fn - The function to invoke + * @param args - The arguments to pass to the function. The final argument must be a callback function. + * + * @internal + */ +function safeCall(fn, input, callback) { + // Replace the callback function with a wrapper that ensures it will only be called once + callback = callOnce(callback); + try { + fn(input, callback); + } + catch (err) { + callback(err, undefined); + } +} +exports.safeCall = safeCall; +/** + * Returns a wrapper function that ensures the given callback function is only called once. + * Subsequent calls are ignored, unless the first argument is an Error, in which case the + * error is thrown. + * + * @param callback - The function that should only be called once + * + * @internal + */ +function callOnce(callback) { + let fulfilled = false; + return function onceWrapper(err, result) { + if (!fulfilled) { + fulfilled = true; + callback.call(this, err, result); + } + else if (err) { + // The callback has already been called, but now an error has occurred + // (most likely inside the callback function). So re-throw the error, + // so it gets handled further up the call stack + throw err; + } + }; +} +exports.callOnce = callOnce; +//# sourceMappingURL=call.js.map + +/***/ }), + +/***/ 4918: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.DirectoryReader = void 0; +const path = __webpack_require__(5622); +const stream_1 = __webpack_require__(2413); +const call_1 = __webpack_require__(8188); +const normalize_options_1 = __webpack_require__(2977); +const stat_1 = __webpack_require__(9445); +/** + * Asynchronously reads the contents of a directory and streams the results + * via a `ReadableStream`. + * + * @internal + */ +class DirectoryReader { + /** + * @param dir - The absolute or relative directory path to read + * @param [options] - User-specified options, if any (see `normalizeOptions()`) + * @param facade - sync or async function implementations + * @param emit - Indicates whether the reader should emit "file", "directory", and "symlink" events. + */ + constructor(dir, options, facade, emit = false) { + this.options = normalize_options_1.normalizeOptions(options, facade, emit); + // Indicates whether we should keep reading + // This is set false if stream.Readable.push() returns false. + this.shouldRead = true; + // The directories to read + // (initialized with the top-level directory) + this.queue = [{ + path: dir, + basePath: this.options.basePath, + depth: 0 + }]; + // The number of directories that are currently being processed + this.pending = 0; + // The data that has been read, but not yet emitted + this.buffer = []; + this.stream = new stream_1.Readable({ objectMode: true }); + this.stream._read = () => { + // Start (or resume) reading + this.shouldRead = true; + // If we have data in the buffer, then send the next chunk + if (this.buffer.length > 0) { + this.pushFromBuffer(); + } + // If we have directories queued, then start processing the next one + if (this.queue.length > 0) { + this.readNextDirectory(); + } + this.checkForEOF(); + }; + } + /** + * Reads the next directory in the queue + */ + readNextDirectory() { + let { facade } = this.options; + let dir = this.queue.shift(); + this.pending++; + // Read the directory listing + call_1.safeCall(facade.fs.readdir, dir.path, (err, items) => { + if (err) { + // fs.readdir threw an error + this.emit("error", err); + return this.finishedReadingDirectory(); + } + try { + // Process each item in the directory (simultaneously, if async) + facade.forEach(items, this.processItem.bind(this, dir), this.finishedReadingDirectory.bind(this, dir)); + } + catch (err2) { + // facade.forEach threw an error + // (probably because fs.readdir returned an invalid result) + this.emit("error", err2); + this.finishedReadingDirectory(); + } + }); + } + /** + * This method is called after all items in a directory have been processed. + * + * NOTE: This does not necessarily mean that the reader is finished, since there may still + * be other directories queued or pending. + */ + finishedReadingDirectory() { + this.pending--; + if (this.shouldRead) { + // If we have directories queued, then start processing the next one + if (this.queue.length > 0) { + this.readNextDirectory(); + } + this.checkForEOF(); + } + } + /** + * Determines whether the reader has finished processing all items in all directories. + * If so, then the "end" event is fired (via {@Readable#push}) + */ + checkForEOF() { + if (this.buffer.length === 0 && // The stuff we've already read + this.pending === 0 && // The stuff we're currently reading + this.queue.length === 0) { // The stuff we haven't read yet + // There's no more stuff! + this.stream.push(null); + } + } + /** + * Processes a single item in a directory. + * + * If the item is a directory, and `option.deep` is enabled, then the item will be added + * to the directory queue. + * + * If the item meets the filter criteria, then it will be emitted to the reader's stream. + * + * @param dir - A directory object from the queue + * @param item - The name of the item (name only, no path) + * @param done - A callback function that is called after the item has been processed + */ + processItem(dir, item, done) { + let stream = this.stream; + let options = this.options; + let itemPath = dir.basePath + item; + let fullPath = path.join(dir.path, item); + // If `options.deep` is a number, and we've already recursed to the max depth, + // then there's no need to check fs.Stats to know if it's a directory. + // If `options.deep` is a function, then we'll need fs.Stats + let maxDepthReached = dir.depth >= options.recurseDepth; + // Do we need to call `fs.stat`? + let needStats = !maxDepthReached || // we need the fs.Stats to know if it's a directory + options.stats || // the user wants fs.Stats objects returned + options.recurseFnNeedsStats || // we need fs.Stats for the recurse function + options.filterFnNeedsStats || // we need fs.Stats for the filter function + stream.listenerCount("file") || // we need the fs.Stats to know if it's a file + stream.listenerCount("directory") || // we need the fs.Stats to know if it's a directory + stream.listenerCount("symlink"); // we need the fs.Stats to know if it's a symlink + // If we don't need stats, then exit early + if (!needStats) { + if (this.filter({ path: itemPath })) { + this.pushOrBuffer({ data: itemPath }); + } + return done(); + } + // Get the fs.Stats object for this path + stat_1.stat(options.facade.fs, fullPath, (err, stats) => { + if (err) { + // fs.stat threw an error + this.emit("error", err); + return done(); + } + try { + // Add the item's path to the fs.Stats object + // The base of this path, and its separators are determined by the options + // (i.e. options.basePath and options.sep) + stats.path = itemPath; + // Add depth of the path to the fs.Stats object for use this in the filter function + stats.depth = dir.depth; + if (this.shouldRecurse(stats, maxDepthReached)) { + // Add this subdirectory to the queue + this.queue.push({ + path: fullPath, + basePath: itemPath + options.sep, + depth: dir.depth + 1, + }); + } + // Determine whether this item matches the filter criteria + if (this.filter(stats)) { + this.pushOrBuffer({ + data: options.stats ? stats : itemPath, + file: stats.isFile(), + directory: stats.isDirectory(), + symlink: stats.isSymbolicLink(), + }); + } + done(); + } + catch (err2) { + // An error occurred while processing the item + // (probably during a user-specified function, such as options.deep, options.filter, etc.) + this.emit("error", err2); + done(); + } + }); + } + /** + * Pushes the given chunk of data to the stream, or adds it to the buffer, + * depending on the state of the stream. + */ + pushOrBuffer(chunk) { + // Add the chunk to the buffer + this.buffer.push(chunk); + // If we're still reading, then immediately emit the next chunk in the buffer + // (which may or may not be the chunk that we just added) + if (this.shouldRead) { + this.pushFromBuffer(); + } + } + /** + * Immediately pushes the next chunk in the buffer to the reader's stream. + * The "data" event will always be fired (via `Readable.push()`). + * In addition, the "file", "directory", and/or "symlink" events may be fired, + * depending on the type of properties of the chunk. + */ + pushFromBuffer() { + let stream = this.stream; + let chunk = this.buffer.shift(); + // Stream the data + try { + this.shouldRead = stream.push(chunk.data); + } + catch (err) { + this.emit("error", err); + } + if (this.options.emit) { + // Also emit specific events, based on the type of chunk + chunk.file && this.emit("file", chunk.data); + chunk.symlink && this.emit("symlink", chunk.data); + chunk.directory && this.emit("directory", chunk.data); + } + } + /** + * Determines whether the given directory meets the user-specified recursion criteria. + * If the user didn't specify recursion criteria, then this function will default to true. + * + * @param stats - The directory's `Stats` object + * @param maxDepthReached - Whether we've already crawled the user-specified depth + */ + shouldRecurse(stats, maxDepthReached) { + let { recurseFn } = this.options; + if (maxDepthReached) { + // We've already crawled to the maximum depth. So no more recursion. + return false; + } + else if (!stats.isDirectory()) { + // It's not a directory. So don't try to crawl it. + return false; + } + else if (recurseFn) { + try { + // Run the user-specified recursion criteria + return !!recurseFn(stats); + } + catch (err) { + // An error occurred in the user's code. + // In Sync and Async modes, this will return an error. + // In Streaming mode, we emit an "error" event, but continue processing + this.emit("error", err); + } + } + else { + // No recursion function was specified, and we're within the maximum depth. + // So crawl this directory. + return true; + } + } + /** + * Determines whether the given item meets the user-specified filter criteria. + * If the user didn't specify a filter, then this function will always return true. + * + * @param stats - The item's `Stats` object, or an object with just a `path` property + */ + filter(stats) { + let { filterFn } = this.options; + if (filterFn) { + try { + // Run the user-specified filter function + return !!filterFn(stats); + } + catch (err) { + // An error occurred in the user's code. + // In Sync and Async modes, this will return an error. + // In Streaming mode, we emit an "error" event, but continue processing + this.emit("error", err); + } + } + else { + // No filter was specified, so match everything + return true; + } + } + /** + * Emits an event. If one of the event listeners throws an error, + * then an "error" event is emitted. + */ + emit(eventName, data) { + let stream = this.stream; + try { + stream.emit(eventName, data); + } + catch (err) { + if (eventName === "error") { + // Don't recursively emit "error" events. + // If the first one fails, then just throw + throw err; + } + else { + stream.emit("error", err); + } + } + } +} +exports.DirectoryReader = DirectoryReader; +//# sourceMappingURL=directory-reader.js.map + +/***/ }), + +/***/ 8811: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readdir = void 0; +const async_1 = __webpack_require__(5833); +const iterator_1 = __webpack_require__(5944); +const stream_1 = __webpack_require__(5521); +const sync_1 = __webpack_require__(704); +const readdir = async_1.readdirAsync; +exports.readdir = readdir; +readdir.sync = sync_1.readdirSync; +readdir.async = async_1.readdirAsync; +readdir.stream = stream_1.readdirStream; +readdir.iterator = iterator_1.readdirIterator; +var async_2 = __webpack_require__(5833); +Object.defineProperty(exports, "readdirAsync", ({ enumerable: true, get: function () { return async_2.readdirAsync; } })); +var iterator_2 = __webpack_require__(5944); +Object.defineProperty(exports, "readdirIterator", ({ enumerable: true, get: function () { return iterator_2.readdirIterator; } })); +var stream_2 = __webpack_require__(5521); +Object.defineProperty(exports, "readdirStream", ({ enumerable: true, get: function () { return stream_2.readdirStream; } })); +var sync_2 = __webpack_require__(704); +Object.defineProperty(exports, "readdirSync", ({ enumerable: true, get: function () { return sync_2.readdirSync; } })); +__exportStar(__webpack_require__(6299), exports); +exports.default = readdir; +// CommonJS default export hack +/* eslint-env commonjs */ +if ( true && typeof module.exports === "object") { + module.exports = Object.assign(module.exports.default, module.exports); +} +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 5944: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readdirIterator = void 0; +const fs = __webpack_require__(5747); +const for_each_1 = __webpack_require__(504); +const directory_reader_1 = __webpack_require__(4918); +const pending_1 = __webpack_require__(8553); +const iteratorFacade = { fs, forEach: for_each_1.asyncForEach }; +function readdirIterator(dir, options) { + let reader = new directory_reader_1.DirectoryReader(dir, options, iteratorFacade); + let stream = reader.stream; + let pendingValues = []; + let pendingReads = []; + let error; + let readable = false; + let done = false; + stream.on("error", function streamError(err) { + error = err; + stream.pause(); + fulfillPendingReads(); + }); + stream.on("end", function streamEnd() { + done = true; + fulfillPendingReads(); + }); + stream.on("readable", function streamReadable() { + readable = true; + fulfillPendingReads(); + }); + return { + [Symbol.asyncIterator]() { + return this; + }, + next() { + let pendingRead = pending_1.pending(); + pendingReads.push(pendingRead); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + Promise.resolve().then(fulfillPendingReads); + return pendingRead.promise; + } + }; + function fulfillPendingReads() { + if (error) { + while (pendingReads.length > 0) { + let pendingRead = pendingReads.shift(); + pendingRead.reject(error); + } + } + else if (pendingReads.length > 0) { + while (pendingReads.length > 0) { + let pendingRead = pendingReads.shift(); + let value = getNextValue(); + if (value) { + pendingRead.resolve({ value }); + } + else if (done) { + pendingRead.resolve({ done, value }); + } + else { + pendingReads.unshift(pendingRead); + break; + } + } + } + } + function getNextValue() { + let value = pendingValues.shift(); + if (value) { + return value; + } + else if (readable) { + readable = false; + while (true) { + value = stream.read(); + if (value) { + pendingValues.push(value); + } + else { + break; + } + } + return pendingValues.shift(); + } + } +} +exports.readdirIterator = readdirIterator; +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 8553: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.pending = void 0; +/** + * Returns a `Promise` and the functions to resolve or reject it. + * @internal + */ +function pending() { + let resolve, reject; + let promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { + promise, + resolve(result) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + Promise.resolve(result).then(resolve); + }, + reject(reason) { + Promise.reject(reason).catch(reject); + } + }; +} +exports.pending = pending; +//# sourceMappingURL=pending.js.map + +/***/ }), + +/***/ 2977: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.normalizeOptions = void 0; +const file_path_filter_1 = __webpack_require__(3410); +const path = __webpack_require__(5622); +/** + * Validates and normalizes the options argument + * + * @param [options] - User-specified options, if any + * @param facade - sync or async function implementations + * @param emit - Indicates whether the reader should emit "file", "directory", and "symlink" events. + * + * @internal + */ +function normalizeOptions(options, facade, emit) { + if (options === null || options === undefined) { + options = {}; + } + else if (typeof options !== "object") { + throw new TypeError("options must be an object"); + } + let sep = options.sep; + if (sep === null || sep === undefined) { + sep = path.sep; + } + else if (typeof sep !== "string") { + throw new TypeError("options.sep must be a string"); + } + let stats = Boolean(options.stats || options.withFileTypes); + let recurseDepth, recurseFn, recurseFnNeedsStats = false, deep = options.deep; + if (deep === null || deep === undefined) { + recurseDepth = 0; + } + else if (typeof deep === "boolean") { + recurseDepth = deep ? Infinity : 0; + } + else if (typeof deep === "number") { + if (deep < 0 || isNaN(deep)) { + throw new Error("options.deep must be a positive number"); + } + else if (Math.floor(deep) !== deep) { + throw new Error("options.deep must be an integer"); + } + else { + recurseDepth = deep; + } + } + else if (typeof deep === "function") { + // Recursion functions require a Stats object + recurseFnNeedsStats = true; + recurseDepth = Infinity; + recurseFn = deep; + } + else if (deep instanceof RegExp || (typeof deep === "string" && deep.length > 0)) { + recurseDepth = Infinity; + recurseFn = file_path_filter_1.createFilter({ map, sep }, deep); + } + else { + throw new TypeError("options.deep must be a boolean, number, function, regular expression, or glob pattern"); + } + let filterFn, filterFnNeedsStats = false, filter = options.filter; + if (filter !== null && filter !== undefined) { + if (typeof filter === "function") { + // Filter functions requres a Stats object + filterFnNeedsStats = true; + filterFn = filter; + } + else if (filter instanceof RegExp || + typeof filter === "boolean" || + (typeof filter === "string" && filter.length > 0)) { + filterFn = file_path_filter_1.createFilter({ map, sep }, filter); + } + else { + throw new TypeError("options.filter must be a boolean, function, regular expression, or glob pattern"); + } + } + let basePath = options.basePath; + if (basePath === null || basePath === undefined) { + basePath = ""; + } + else if (typeof basePath === "string") { + // Append a path separator to the basePath, if necessary + if (basePath && basePath.substr(-1) !== sep) { + basePath += sep; + } + } + else { + throw new TypeError("options.basePath must be a string"); + } + // Determine which facade methods to use + if (options.fs === null || options.fs === undefined) { + // The user didn't provide their own facades, so use our internal ones + } + else if (typeof options.fs === "object") { + // Merge the internal facade methods with the user-provided `fs` facades + facade = Object.assign({}, facade); + facade.fs = Object.assign({}, facade.fs, options.fs); + } + else { + throw new TypeError("options.fs must be an object"); + } + return { + recurseDepth, + recurseFn, + recurseFnNeedsStats, + filterFn, + filterFnNeedsStats, + stats, + sep, + basePath, + facade, + emit, + }; +} +exports.normalizeOptions = normalizeOptions; +/** + * Maps our modified fs.Stats objects to file paths + */ +function map(stats) { + return stats.path; +} +//# sourceMappingURL=normalize-options.js.map + +/***/ }), + +/***/ 9445: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.stat = void 0; +const call_1 = __webpack_require__(8188); +/** + * Retrieves the `Stats` for the given path. If the path is a symbolic link, + * then the Stats of the symlink's target are returned instead. If the symlink is broken, + * then the Stats of the symlink itself are returned. + * + * @param fs - Synchronous or Asynchronouse facade for the "fs" module + * @param path - The path to return stats for + * + * @internal + */ +function stat(fs, path, callback) { + let isSymLink = false; + call_1.safeCall(fs.lstat, path, (err, lstats) => { + if (err) { + // fs.lstat threw an eror + return callback(err, undefined); + } + try { + isSymLink = lstats.isSymbolicLink(); + } + catch (err2) { + // lstats.isSymbolicLink() threw an error + // (probably because fs.lstat returned an invalid result) + return callback(err2, undefined); + } + if (isSymLink) { + // Try to resolve the symlink + symlinkStat(fs, path, lstats, callback); + } + else { + // It's not a symlink, so return the stats as-is + callback(null, lstats); + } + }); +} +exports.stat = stat; +/** + * Retrieves the `Stats` for the target of the given symlink. + * If the symlink is broken, then the Stats of the symlink itself are returned. + * + * @param fs - Synchronous or Asynchronouse facade for the "fs" module + * @param path - The path of the symlink to return stats for + * @param lstats - The stats of the symlink + */ +function symlinkStat(fs, path, lstats, callback) { + call_1.safeCall(fs.stat, path, (err, stats) => { + if (err) { + // The symlink is broken, so return the stats for the link itself + return callback(null, lstats); + } + try { + // Return the stats for the resolved symlink target, + // and override the `isSymbolicLink` method to indicate that it's a symlink + stats.isSymbolicLink = () => true; + } + catch (err2) { + // Setting stats.isSymbolicLink threw an error + // (probably because fs.stat returned an invalid result) + return callback(err2, undefined); + } + callback(null, stats); + }); +} +//# sourceMappingURL=stat.js.map + +/***/ }), + +/***/ 5521: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readdirStream = void 0; +const fs = __webpack_require__(5747); +const for_each_1 = __webpack_require__(504); +const directory_reader_1 = __webpack_require__(4918); +const streamFacade = { fs, forEach: for_each_1.asyncForEach }; +function readdirStream(dir, options) { + let reader = new directory_reader_1.DirectoryReader(dir, options, streamFacade, true); + return reader.stream; +} +exports.readdirStream = readdirStream; +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 7448: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.syncForEach = void 0; +/** + * A facade that allows `Array.forEach()` to be called as though it were asynchronous. + * + * @param array - The array to iterate over + * @param iterator - The function to call for each item in the array + * @param done - The function to call when all iterators have completed + * + * @internal + */ +function syncForEach(array, iterator, done) { + if (!Array.isArray(array)) { + throw new TypeError(`${array} is not an array`); + } + for (let item of array) { + iterator(item, () => { + // Note: No error-handling here because this is currently only ever called + // by DirectoryReader, which never passes an `error` parameter to the callback. + // Instead, DirectoryReader emits an "error" event if an error occurs. + }); + } + done(); +} +exports.syncForEach = syncForEach; +//# sourceMappingURL=for-each.js.map + +/***/ }), + +/***/ 3073: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.syncFS = void 0; +const fs = __webpack_require__(5747); +const call_1 = __webpack_require__(8188); +/** + * Synchronous versions of `fs` methods. + * + * @internal + */ +exports.syncFS = { + /** + * A facade around `fs.readdirSync()` that allows it to be called + * the same way as `fs.readdir()`. + */ + readdir(dir, callback) { + // Make sure the callback is only called once + callback = call_1.callOnce(callback); + try { + let items = fs.readdirSync(dir); + callback(null, items); + } + catch (err) { + callback(err, undefined); + } + }, + /** + * A facade around `fs.statSync()` that allows it to be called + * the same way as `fs.stat()`. + */ + stat(path, callback) { + // Make sure the callback is only called once + callback = call_1.callOnce(callback); + try { + let stats = fs.statSync(path); + callback(null, stats); + } + catch (err) { + callback(err, undefined); + } + }, + /** + * A facade around `fs.lstatSync()` that allows it to be called + * the same way as `fs.lstat()`. + */ + lstat(path, callback) { + // Make sure the callback is only called once + callback = call_1.callOnce(callback); + try { + let stats = fs.lstatSync(path); + callback(null, stats); + } + catch (err) { + callback(err, undefined); + } + }, +}; +//# sourceMappingURL=fs.js.map + +/***/ }), + +/***/ 704: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readdirSync = void 0; +const directory_reader_1 = __webpack_require__(4918); +const for_each_1 = __webpack_require__(7448); +const fs_1 = __webpack_require__(3073); +const syncFacade = { fs: fs_1.syncFS, forEach: for_each_1.syncForEach }; +function readdirSync(dir, options) { + let reader = new directory_reader_1.DirectoryReader(dir, options, syncFacade); + let stream = reader.stream; + let results = []; + let data = stream.read(); + while (data !== null) { + results.push(data); + data = stream.read(); + } + return results; +} +exports.readdirSync = readdirSync; +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 6299: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +//# sourceMappingURL=types-public.js.map + +/***/ }), + +/***/ 9946: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HashDiff = void 0; +function formatNumber(number) { + return number.toLocaleString(); +} +class HashDiff { + getDiffs(localFiles, serverFiles, logger) { + var _a, _b, _c; + const uploadList = []; + const deleteList = []; + const replaceList = []; + 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)); + logger.standard(`----------------------------------------------------------------`); + logger.standard(`Local Files:\t${formatNumber(localFilesSorted.length)}`); + logger.standard(`Server Files:\t${formatNumber(localFilesSorted.length)}`); + logger.standard(`----------------------------------------------------------------`); + logger.standard(`Calculating differences between client & server`); + logger.standard(`----------------------------------------------------------------`); + 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) { + let icon = localFile.type === "folder" ? `๐Ÿ“ Create` : `โž• Upload`; + logger.standard(`${icon}: ${localFile.name}`); + uploadList.push(localFile); + sizeUpload += (_a = localFile.size) !== null && _a !== void 0 ? _a : 0; + localPosition += 1; + } + else if (fileNameCompare > 0) { + let icon = serverFile.type === "folder" ? `๐Ÿ“` : `๐Ÿ—‘๏ธ`; + logger.standard(`${icon} Delete: ${serverFile.name} `); + 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) { + logger.standard(`โš–๏ธ File content is the same, doing nothing: ${localFile.name}`); + } + else { + logger.standard(`๐Ÿ” File replace: ${localFile.name}`); + sizeReplace += (_c = localFile.size) !== null && _c !== void 0 ? _c : 0; + replaceList.push(localFile); + } + } + localPosition += 1; + serverPosition += 1; + } + } + return { + upload: uploadList, + delete: deleteList, + replace: replaceList, + sizeDelete, + sizeReplace, + sizeUpload + }; + } +} +exports.HashDiff = HashDiff; + + +/***/ }), + +/***/ 3678: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.prettyError = void 0; +const types_1 = __webpack_require__(6703); +function outputOriginalErrorAndExit(logger, error) { + logger.all(); + logger.all(`----------------------------------------------------------------`); + logger.all(`---------------------- Full Error below ----------------------`); + logger.all(error); + process.exit(); +} +/** + * Converts a exception to helpful debug info + * @param error exception + */ +function prettyError(logger, args, error) { + logger.all(); + logger.all(`----------------------------------------------------------------`); + logger.all(`--------------- ๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅ A error occurred ๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅ --------------`); + logger.all(`----------------------------------------------------------------`); + 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?`); + outputOriginalErrorAndExit(logger, error); + } + } + 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`); + outputOriginalErrorAndExit(logger, error); + } + } + const ftpError = error; + 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.`); + outputOriginalErrorAndExit(logger, error); + } + 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!`); + outputOriginalErrorAndExit(logger, error); + } + } + } + // unknown error :( + outputOriginalErrorAndExit(logger, error); +} +exports.prettyError = prettyError; + + +/***/ }), + +/***/ 8347: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (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.excludeDefaults = void 0; +const ftp = __importStar(__webpack_require__(7957)); +const readdir_enhanced_1 = __importDefault(__webpack_require__(8811)); +const crypto_1 = __importDefault(__webpack_require__(6417)); +const fs_1 = __importDefault(__webpack_require__(5747)); +const multiMatch_1 = __importDefault(__webpack_require__(4865)); +const types_1 = __webpack_require__(6703); +const HashDiff_1 = __webpack_require__(9946); +const utilities_1 = __webpack_require__(4389); +const pretty_bytes_1 = __importDefault(__webpack_require__(5168)); +const errorHandling_1 = __webpack_require__(3678); +/** + * Default excludes, ignores all git files and the node_modules folder + */ +exports.excludeDefaults = [".git*", ".git*/**", "node_modules/**", "node_modules/**/*"]; +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"); + } + }); + }); +} +function applyExcludeFilter(stat, args) { + // match exclude, return immediatley + if (args.exclude.length > 0) { + const excludeMatch = multiMatch_1.default(stat.path, args.exclude, { matchBase: true, dot: true }); + if (excludeMatch.length > 0) { + return false; + } + } + return true; +} +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) => applyExcludeFilter(stat, args) }); + 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 fileHash(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.currentVersion, + generatedTime: new Date().getTime(), + data: records + }; + }); +} +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 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; + }); +} +/** + * Converts a file path (ex: "folder/otherfolder/file.txt") to an array of folder and a file path + * @param fullPath + */ +function 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 + */ +function upDir(client, logger, 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 utilities_1.retryRequest(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.cdup(); })); + } + }); +} +function ensureDir(client, logger, timings, folder) { + return __awaiter(this, void 0, void 0, function* () { + timings.start("changingDir"); + logger.verbose(` changing dir to ${folder}`); + yield utilities_1.retryRequest(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.ensureDir(folder); })); + logger.verbose(` dir changed`); + timings.stop("changingDir"); + }); +} +/** + * + * @param client ftp client + * @param file file can include folder(s) + * Note working dir is modified and NOT reset after upload + * For now we are going to reset it - but this will be removed for performance + */ +function uploadFile(client, basePath, filePath, logger, type = "upload", dryRun) { + return __awaiter(this, void 0, void 0, function* () { + const typePresent = type === "upload" ? "uploading" : "replacing"; + const typePast = type === "upload" ? "uploaded" : "replaced"; + logger.all(`${typePresent} "${filePath}"`); + if (dryRun === false) { + yield utilities_1.retryRequest(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.uploadFrom(basePath + filePath, filePath); })); + } + logger.verbose(` file ${typePast}`); + }); +} +function createFolder(client, folderPath, logger, timings, dryRun) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + logger.all(`creating folder "${folderPath + "/"}"`); + if (dryRun === false) { + return; + } + const path = getFileBreadcrumbs(folderPath + "/"); + if (path.folders === null) { + logger.verbose(` no need to change dir`); + } + else { + yield ensureDir(client, logger, timings, path.folders.join("/")); + } + // navigate back to the root folder + yield upDir(client, logger, (_a = path.folders) === null || _a === void 0 ? void 0 : _a.length); + logger.verbose(` completed`); + }); +} +function removeFolder(client, folderPath, logger, dryRun) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + logger.all(`removing folder "${folderPath + "/"}"`); + const path = getFileBreadcrumbs(folderPath + "/"); + if (path.folders === null) { + logger.verbose(` no need to change dir`); + } + else { + try { + logger.verbose(` removing folder "${path.folders.join("/") + "/"}"`); + if (dryRun === false) { + yield utilities_1.retryRequest(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.removeDir(path.folders.join("/") + "/"); })); + } + } + catch (e) { + let error = e; + if (error.code === types_1.ErrorCode.FileNotFoundOrNoAccess) { + logger.verbose(` could not remove folder. It doesn't exist!`); + } + else { + // unknown error + throw error; + } + } + } + // navigate back to the root folder + yield upDir(client, logger, (_a = path.folders) === null || _a === void 0 ? void 0 : _a.length); + logger.verbose(` completed`); + }); +} +function removeFile(client, basePath, filePath, logger, dryRun) { + return __awaiter(this, void 0, void 0, function* () { + logger.all(`removing ${filePath}...`); + try { + if (dryRun === false) { + yield utilities_1.retryRequest(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.remove(basePath + filePath); })); + } + logger.verbose(` file removed`); + } + catch (e) { + let error = e; + if (error.code === types_1.ErrorCode.FileNotFoundOrNoAccess) { + logger.verbose(` could not remove file. It doesn't exist!`); + } + else { + // unknown error + throw error; + } + } + logger.verbose(` completed`); + }); +} +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 === "loose"; + yield client.access({ + host: args.server, + user: args.username, + password: args.password, + port: args.port, + secure: secure, + secureOptions: { + rejectUnauthorized: rejectUnauthorized + } + }); + 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 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..."); + yield client.clearWorkingDir(); + logger.all("Clear complete"); + throw new Error("nope"); + } + 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" })}`); + return serverFiles; + } + catch (e) { + logger.all(`----------------------------------------------------------------`); + logger.all(`No file exists on the server "${args["server-dir"] + args["state-name"]}" - this much 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.currentVersion, + generatedTime: new Date().getTime(), + data: [], + }; + } + }); +} +function getDefaultSettings(withoutDefaults) { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; + 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 : exports.excludeDefaults, + "log-level": (_j = withoutDefaults["log-level"]) !== null && _j !== void 0 ? _j : "standard", + "security": (_k = withoutDefaults.security) !== null && _k !== void 0 ? _k : "loose", + }; +} +function syncLocalToServer(client, diffs, logger, timings, args) { + return __awaiter(this, void 0, void 0, function* () { + const totalCount = diffs.delete.length + diffs.upload.length + diffs.replace.length; + logger.all(`----------------------------------------------------------------`); + logger.all(`Making changes to ${totalCount} ${utilities_1.pluralize(totalCount, "file", "files")} to sync server state`); + logger.all(`Uploading: ${pretty_bytes_1.default(diffs.sizeUpload)} -- Deleting: ${pretty_bytes_1.default(diffs.sizeDelete)} -- Replacing: ${pretty_bytes_1.default(diffs.sizeReplace)}`); + logger.all(`----------------------------------------------------------------`); + const basePath = args["local-dir"]; + // create new folders + for (const file of diffs.upload.filter(item => item.type === "folder")) { + yield createFolder(client, file.name, logger, timings, args["dry-run"]); + } + // upload new files + for (const file of diffs.upload.filter(item => item.type === "file").filter(item => item.name !== args["state-name"])) { + yield uploadFile(client, basePath, file.name, logger, "upload", args["dry-run"]); + } + // replace new files + for (const file of diffs.replace.filter(item => item.type === "file").filter(item => item.name !== args["state-name"])) { + // note: FTP will replace old files with new files. We run replacements after uploads to limit downtime + yield uploadFile(client, basePath, file.name, logger, "replace", args["dry-run"]); + } + // delete old files + for (const file of diffs.delete.filter(item => item.type === "file")) { + yield removeFile(client, basePath, file.name, logger, args["dry-run"]); + } + // delete old folders + for (const file of diffs.delete.filter(item => item.type === "folder")) { + yield removeFolder(client, file.name, logger, args["dry-run"]); + } + logger.all(`----------------------------------------------------------------`); + logger.all(`๐ŸŽ‰ Sync complete. Saving current server state to "${args["server-dir"] + args["state-name"]}"`); + yield utilities_1.retryRequest(logger, () => __awaiter(this, void 0, void 0, function* () { return yield client.uploadFrom(args["local-dir"] + args["state-name"], args["state-name"]); })); + }); +} +function deploy(deployArgs) { + return __awaiter(this, void 0, void 0, function* () { + const args = getDefaultSettings(deployArgs); + const logger = new utilities_1.Logger(args["log-level"]); + const timings = new utilities_1.Timings(); + timings.start("total"); + // header + // todo allow swapping out library/version text based on if we are using action + logger.all(`----------------------------------------------------------------`); + logger.all(`๐Ÿš€ Thanks for using ftp-deploy version ${types_1.currentVersion}. 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`); + timings.start("hash"); + const localFiles = yield getLocalFiles(args); + timings.stop("hash"); + createLocalState(localFiles, logger, args); + const client = new ftp.Client(); + 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(); + try { + const serverFiles = yield getServerFiles(client, logger, timings, args); + const diffTool = new HashDiff_1.HashDiff(); + const diffs = diffTool.getDiffs(localFiles, serverFiles, logger); + totalBytesUploaded = diffs.sizeUpload + diffs.sizeReplace; + timings.start("upload"); + try { + yield syncLocalToServer(client, diffs, logger, timings, args); + } + catch (e) { + if (e.code === types_1.ErrorCode.FileNameNotAllowed) { + logger.all("Error 553 FileNameNotAllowed, you don't have access to upload that file"); + logger.all(e); + process.exit(); + } + logger.all(e); + process.exit(); + } + finally { + timings.stop("upload"); + } + } + catch (error) { + const ftpError = error; + if (ftpError.code === types_1.ErrorCode.FileNotFoundOrNoAccess) { + logger.all("Couldn't find file"); + } + logger.all(ftpError); + } + } + catch (error) { + errorHandling_1.prettyError(logger, args, error); + } + finally { + client.close(); + timings.stop("total"); + } + const uploadSpeed = 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(`----------------------------------------------------------------`); + logger.all(`Total time: ${timings.getTimeFormatted("total")}`); + logger.all(`----------------------------------------------------------------`); + }); +} +exports.deploy = deploy; + + +/***/ }), + +/***/ 6703: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ErrorCode = exports.syncFileDescription = exports.currentVersion = void 0; +exports.currentVersion = "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 = {})); +; + + +/***/ }), + +/***/ 4389: +/***/ (function(__unused_webpack_module, exports, __webpack_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.Timer = exports.Timings = exports.retryRequest = exports.pluralize = exports.Logger = void 0; +const pretty_ms_1 = __importDefault(__webpack_require__(1127)); +const types_1 = __webpack_require__(6703); +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; +/** + * 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 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; + + +/***/ }), + +/***/ 6554: +/***/ ((module) => { + +"use strict"; + + +const arrayDiffer = (array, ...values) => { + const rest = new Set([].concat(...values)); + return array.filter(element => !rest.has(element)); +}; + +module.exports = arrayDiffer; + + +/***/ }), + +/***/ 9600: +/***/ ((module) => { + +"use strict"; + + +module.exports = (...arguments_) => { + return [...new Set([].concat(...arguments_))]; +}; + + +/***/ }), + +/***/ 1546: +/***/ ((module) => { + +"use strict"; + + +const arrify = value => { + if (value === null || value === undefined) { + return []; + } + + if (Array.isArray(value)) { + return value; + } + + if (typeof value === 'string') { + return [value]; + } + + if (typeof value[Symbol.iterator] === 'function') { + return [...value]; + } + + return [value]; +}; + +module.exports = arrify; + + +/***/ }), + +/***/ 9417: +/***/ ((module) => { + +"use strict"; + +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); + } + + i = ai < bi && ai >= 0 ? ai : bi; + } + + if (begs.length) { + result = [ left, right ]; + } + } + + return result; +} + + +/***/ }), + +/***/ 8337: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.Client = void 0; +const fs_1 = __webpack_require__(5747); +const path_1 = __webpack_require__(5622); +const tls_1 = __webpack_require__(4016); +const util_1 = __webpack_require__(1669); +const FtpContext_1 = __webpack_require__(9052); +const parseList_1 = __webpack_require__(2993); +const ProgressTracker_1 = __webpack_require__(7170); +const StringWriter_1 = __webpack_require__(8184); +const parseListMLSD_1 = __webpack_require__(8157); +const netUtils_1 = __webpack_require__(6288); +const transfer_1 = __webpack_require__(5803); +const parseControlResponse_1 = __webpack_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); +/** + * High-level API to interact with an FTP server. + */ +class Client { + /** + * Instantiate an FTP client. + * + * @param timeout Timeout in milliseconds, use 0 for no timeout. Optional, default is 30 seconds. + */ + constructor(timeout = 30000) { + /** + * Multiple commands to retrieve a directory listing are possible. This instance + * will try all of them in the order presented the first time a directory listing + * is requested. After that, `availableListCommands` will hold only the first + * entry that worked. + */ + this.availableListCommands = ["MLSD", "LIST -a", "LIST"]; + this.ftp = new FtpContext_1.FTPContext(timeout); + this.prepareTransfer = this._enterFirstCompatibleMode([transfer_1.enterPassiveModeIPv6, transfer_1.enterPassiveModeIPv4]); + this.parseList = parseList_1.parseList; + this._progressTracker = new ProgressTracker_1.ProgressTracker(); + } + /** + * Close the client and all open socket connections. + * + * Close the client and all open socket connections. The client canโ€™t be used anymore after calling this method, + * you have to either reconnect with `access` or `connect` or instantiate a new instance to continue any work. + * A client is also closed automatically if any timeout or connection error occurs. + */ + close() { + this.ftp.close(); + this._progressTracker.stop(); + } + /** + * Returns true if the client is closed and can't be used anymore. + */ + get closed() { + return this.ftp.closed; + } + /** + * Connect (or reconnect) to an FTP server. + * + * This is an instance method and thus can be called multiple times during the lifecycle of a `Client` + * instance. Whenever you do, the client is reset with a new control connection. This also implies that + * you can reopen a `Client` instance that has been closed due to an error when reconnecting with this + * method. In fact, reconnecting is the only way to continue using a closed `Client`. + * + * @param host Host the client should connect to. Optional, default is "localhost". + * @param port Port the client should connect to. Optional, default is 21. + */ + connect(host = "localhost", port = 21) { + this.ftp.reset(); + this.ftp.socket.connect({ + host, + port, + family: this.ftp.ipFamily + }, () => this.ftp.log(`Connected to ${netUtils_1.describeAddress(this.ftp.socket)} (${netUtils_1.describeTLS(this.ftp.socket)})`)); + return this._handleConnectResponse(); + } + /** + * As `connect` but using implicit TLS. Implicit TLS is not an FTP standard and has been replaced by + * explicit TLS. There are still FTP servers that support only implicit TLS, though. + */ + 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.tlsOptions = tlsOptions; + return this._handleConnectResponse(); + } + /** + * Handles the first reponse by an FTP server after the socket connection has been established. + */ + _handleConnectResponse() { + return this.ftp.handle(undefined, (res, task) => { + if (res instanceof Error) { + // The connection has been destroyed by the FTPContext at this point. + task.reject(res); + } + else if (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)); + } + }); + } + /** + * Send an FTP command and handle the first response. + */ + send(command, ignoreErrorCodesDEPRECATED = false) { + if (ignoreErrorCodesDEPRECATED) { // Deprecated starting from 3.9.0 + this.ftp.log("Deprecated call using send(command, flag) with boolean flag to ignore errors. Use sendIgnoringError(command)."); + return this.sendIgnoringError(command); + } + return this.ftp.request(command); + } + /** + * Send an FTP command and ignore an FTP error response. Any other kind of error or timeout will still reject the Promise. + * + * @param command + */ + sendIgnoringError(command) { + return this.ftp.handle(command, (res, task) => { + if (res instanceof FtpContext_1.FTPError) { + task.resolve({ code: res.code, message: res.message }); + } + else if (res instanceof Error) { + task.reject(res); + } + else { + task.resolve(res); + } + }); + } + /** + * Upgrade the current socket connection to TLS. + * + * @param options TLS options as in `tls.connect(options)`, optional. + * @param command Set the authentication command. Optional, default is "AUTH TLS". + */ + 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.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)}`); + return ret; + } + /** + * Login a user with a password. + * + * @param user Username to use for login. Optional, default is "anonymous". + * @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)}`); + 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 + task.resolve(res); + } + else if (res.code === 331) { // User name okay, need password + this.ftp.send("PASS " + password); + } + else { // Also report error on 332 (Need account) + task.reject(new FtpContext_1.FTPError(res)); + } + }); + } + /** + * Set the usual default settings. + * + * Settings used: + * * Binary mode (TYPE I) + * * File structure (STRU F) + * * Additional settings for FTPS (PBSZ 0, PROT P) + */ + async useDefaultSettings() { + await this.send("TYPE I"); // Binary mode + await this.sendIgnoringError("STRU F"); // Use file structure + await this.sendIgnoringError("OPTS UTF8 ON"); // Some servers expect UTF-8 to be enabled explicitly + await this.sendIgnoringError("OPTS MLST type;size;modify;unique;unix.mode;unix.owner;unix.group;unix.ownername;unix.groupname;"); // Make sure MLSD listings include all we can parse + if (this.ftp.hasTLS) { + await this.sendIgnoringError("PBSZ 0"); // Set to 0 for TLS + await this.sendIgnoringError("PROT P"); // Protect channel (also for data connections) + } + } + /** + * Convenience method that calls `connect`, `useTLS`, `login` and `useDefaultSettings`. + * + * This is an instance method and thus can be called multiple times during the lifecycle of a `Client` + * instance. Whenever you do, the client is reset with a new control connection. This also implies that + * you can reopen a `Client` instance that has been closed due to an error when reconnecting with this + * method. In fact, reconnecting is the only way to continue using a closed `Client`. + */ + async access(options = {}) { + const useExplicitTLS = options.secure === true; + const useImplicitTLS = options.secure === "implicit"; + let welcome; + if (useImplicitTLS) { + welcome = await this.connectImplicitTLS(options.host, options.port, options.secureOptions); + } + else { + welcome = await this.connect(options.host, options.port); + } + if (useExplicitTLS) { + await this.useTLS(options.secureOptions); + } + await this.login(options.user, options.password); + await this.useDefaultSettings(); + return welcome; + } + /** + * Get the current working directory. + */ + async pwd() { + const res = await this.send("PWD"); + // The directory is part of the return message, for example: + // 257 "/this/that" is current directory. + const parsed = res.message.match(/"(.+)"/); + if (parsed === null || parsed[1] === undefined) { + throw new Error(`Can't parse response to command 'PWD': ${res.message}`); + } + return parsed[1]; + } + /** + * Get a description of supported features. + * + * This sends the FEAT command and parses the result into a Map where keys correspond to available commands + * and values hold further information. Be aware that your FTP servers might not support this + * command in which case this method will not throw an exception but just return an empty Map. + */ + async features() { + 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)) { + // 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". + // Servers might not use an indentation though. + const entry = line.trim().split(" "); + features.set(entry[0], entry[1] || ""); + }); + } + return features; + } + /** + * Set the working directory. + */ + async cd(path) { + const validPath = await this.protectWhitespace(path); + return this.send("CWD " + validPath); + } + /** + * Switch to the parent directory of the working directory. + */ + async cdup() { + return this.send("CDUP"); + } + /** + * Get the last modified time of a file. This is not supported by every FTP server, in which case + * calling this method will throw an exception. + */ + async lastMod(path) { + const validPath = await this.protectWhitespace(path); + const res = await this.send(`MDTM ${validPath}`); + const date = res.message.slice(4); + return parseListMLSD_1.parseMLSxDate(date); + } + /** + * Get the size of a file. + */ + async size(path) { + const validPath = await this.protectWhitespace(path); + const command = `SIZE ${validPath}`; + const res = await this.send(command); + // The size is part of the response message, for example: "213 555555". It's + // possible that there is a commmentary appended like "213 5555, some commentary". + const size = parseInt(res.message.slice(4), 10); + if (Number.isNaN(size)) { + throw new Error(`Can't parse response to command '${command}' as a numerical value: ${res.message}`); + } + return size; + } + /** + * Rename a file. + * + * Depending on the FTP server this might also be used to move a file from one + * directory to another by providing full paths. + */ + async rename(srcPath, destPath) { + const validSrc = await this.protectWhitespace(srcPath); + const validDest = await this.protectWhitespace(destPath); + await this.send("RNFR " + validSrc); + return this.send("RNTO " + validDest); + } + /** + * Remove a file from the current working directory. + * + * You can ignore FTP error return codes which won't throw an exception if e.g. + * the file doesn't exist. + */ + async remove(path, ignoreErrorCodes = false) { + const validPath = await this.protectWhitespace(path); + return this.send(`DELE ${validPath}`, ignoreErrorCodes); + } + /** + * Report transfer progress for any upload or download to a given handler. + * + * This will also reset the overall transfer counter that can be used for multiple transfers. You can + * also call the function without a handler to stop reporting to an earlier one. + * + * @param handler Handler function to call on transfer progress. + */ + trackProgress(handler) { + this._progressTracker.bytesOverall = 0; + this._progressTracker.reportTo(handler); + } + /** + * Upload data from a readable stream or a local file to a remote file. + * + * @param source Readable stream or path to a local file. + * @param toRemotePath Path to a remote file to write to. + */ + async uploadFrom(source, toRemotePath, options = {}) { + return this._uploadWithCommand(source, toRemotePath, "STOR", options); + } + /** + * Upload data from a readable stream or a local file by appending it to an existing file. If the file doesn't + * exist the FTP server should create it. + * + * @param source Readable stream or path to a local file. + * @param toRemotePath Path to a remote file to write to. + */ + async appendFrom(source, toRemotePath, options = {}) { + return this._uploadWithCommand(source, toRemotePath, "APPE", options); + } + /** + * @protected + */ + async _uploadWithCommand(source, remotePath, command, options) { + if (typeof source === "string") { + return this._uploadLocalFile(source, remotePath, command, options); + } + return this._uploadFromStream(source, remotePath, command); + } + /** + * @protected + */ + async _uploadLocalFile(localPath, remotePath, command, options) { + const fd = await fsOpen(localPath, "r"); + const source = fs_1.createReadStream("", { + fd, + start: options.localStart, + end: options.localEndInclusive, + autoClose: false + }); + try { + return await this._uploadFromStream(source, remotePath, command); + } + finally { + await ignoreError(() => fsClose(fd)); + } + } + /** + * @protected + */ + async _uploadFromStream(source, remotePath, command) { + const onError = (err) => this.ftp.closeWithError(err); + source.once("error", onError); + try { + const validPath = await this.protectWhitespace(remotePath); + 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, { + ftp: this.ftp, + tracker: this._progressTracker, + command, + remotePath: validPath, + type: "upload" + }); + } + finally { + source.removeListener("error", onError); + } + } + /** + * Download a remote file and pipe its data to a writable stream or to a local file. + * + * You can optionally define at which position of the remote file you'd like to start + * downloading. If the destination you provide is a file, the offset will be applied + * to it as well. For example: To resume a failed download, you'd request the size of + * the local, partially downloaded file and use that as the offset. Assuming the size + * is 23, you'd download the rest using `downloadTo("local.txt", "remote.txt", 23)`. + * + * @param destination Stream or path for a local file to write to. + * @param fromRemotePath Path of the remote file to read from. + * @param startAt Position within the remote file to start downloading at. If the destination is a file, this offset is also applied to it. + */ + async downloadTo(destination, fromRemotePath, startAt = 0) { + if (typeof destination === "string") { + return this._downloadToFile(destination, fromRemotePath, startAt); + } + return this._downloadToStream(destination, fromRemotePath, startAt); + } + /** + * @protected + */ + async _downloadToFile(localPath, remotePath, startAt) { + const appendingToLocalFile = startAt > 0; + const fileSystemFlags = appendingToLocalFile ? "r+" : "w"; + const fd = await fsOpen(localPath, fileSystemFlags); + const destination = fs_1.createWriteStream("", { + fd, + start: startAt, + autoClose: false + }); + try { + return await this._downloadToStream(destination, remotePath, startAt); + } + catch (err) { + const localFileStats = await ignoreError(() => fsStat(localPath)); + const hasDownloadedData = localFileStats && localFileStats.size > 0; + const shouldRemoveLocalFile = !appendingToLocalFile && !hasDownloadedData; + if (shouldRemoveLocalFile) { + await ignoreError(() => fsUnlink(localPath)); + } + throw err; + } + finally { + await ignoreError(() => fsClose(fd)); + } + } + /** + * @protected + */ + async _downloadToStream(destination, remotePath, startAt) { + const onError = (err) => this.ftp.closeWithError(err); + destination.once("error", onError); + try { + const validPath = await this.protectWhitespace(remotePath); + 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, { + ftp: this.ftp, + tracker: this._progressTracker, + command: startAt > 0 ? `REST ${startAt}` : `RETR ${validPath}`, + remotePath: validPath, + type: "download" + }); + } + finally { + destination.removeListener("error", onError); + destination.end(); + } + } + /** + * List files and directories in the current working directory, or from `path` if specified. + * + * @param [path] Path to remote file or directory. + */ + async list(path = "") { + const validPath = await this.protectWhitespace(path); + let lastError; + for (const candidate of this.availableListCommands) { + const command = validPath === "" ? candidate : `${candidate} ${validPath}`; + await this.prepareTransfer(this.ftp); + try { + const parsedList = await this._requestListWithCommand(command); + // Use successful candidate for all subsequent requests. + this.availableListCommands = [candidate]; + return parsedList; + } + catch (err) { + const shouldTryNext = err instanceof FtpContext_1.FTPError; + if (!shouldTryNext) { + throw err; + } + lastError = err; + } + } + throw lastError; + } + /** + * @protected + */ + async _requestListWithCommand(command) { + const buffer = new StringWriter_1.StringWriter(); + await transfer_1.downloadTo(buffer, { + ftp: this.ftp, + tracker: this._progressTracker, + command, + remotePath: "", + type: "list" + }); + const text = buffer.getText(this.ftp.encoding); + this.ftp.log(text); + return this.parseList(text); + } + /** + * Remove a directory and all of its content. + * + * @param remoteDirPath The path of the remote directory to delete. + * @example client.removeDir("foo") // Remove directory 'foo' using a relative path. + * @example client.removeDir("foo/bar") // Remove directory 'bar' using a relative path. + * @example client.removeDir("/foo/bar") // Remove directory 'bar' using an absolute path. + * @example client.removeDir("/") // Remove everything. + */ + async removeDir(remoteDirPath) { + return this._exitAtCurrentDirectory(async () => { + await this.cd(remoteDirPath); + await this.clearWorkingDir(); + if (remoteDirPath !== "/") { + await this.cdup(); + await this.removeEmptyDir(remoteDirPath); + } + }); + } + /** + * Remove all files and directories in the working directory without removing + * the working directory itself. + */ + async clearWorkingDir() { + for (const file of await this.list()) { + if (file.isDirectory) { + await this.cd(file.name); + await this.clearWorkingDir(); + await this.cdup(); + await this.removeEmptyDir(file.name); + } + else { + await this.remove(file.name); + } + } + } + /** + * Upload the contents of a local directory to the remote working directory. + * + * This will overwrite existing files with the same names and reuse existing directories. + * Unrelated files and directories will remain untouched. You can optionally provide a `remoteDirPath` + * to put the contents inside a directory which will be created if necessary including all + * intermediate directories. If you did provide a remoteDirPath the working directory will stay + * the same as before calling this method. + * + * @param localDirPath Local path, e.g. "foo/bar" or "../test" + * @param [remoteDirPath] Remote path of a directory to upload to. Working directory if undefined. + */ + async uploadFromDir(localDirPath, remoteDirPath) { + return this._exitAtCurrentDirectory(async () => { + if (remoteDirPath) { + await this.ensureDir(remoteDirPath); + } + return await this._uploadToWorkingDir(localDirPath); + }); + } + /** + * @protected + */ + async _uploadToWorkingDir(localDirPath) { + const files = await fsReadDir(localDirPath); + for (const file of files) { + const fullPath = path_1.join(localDirPath, file); + const stats = await fsStat(fullPath); + if (stats.isFile()) { + await this.uploadFrom(fullPath, file); + } + else if (stats.isDirectory()) { + await this._openDir(file); + await this._uploadToWorkingDir(fullPath); + await this.cdup(); + } + } + } + /** + * Download all files and directories of the working directory to a local directory. + * + * @param localDirPath The local directory to download to. + * @param remoteDirPath Remote directory to download. Current working directory if not specified. + */ + async downloadToDir(localDirPath, remoteDirPath) { + return this._exitAtCurrentDirectory(async () => { + if (remoteDirPath) { + await this.cd(remoteDirPath); + } + return await this._downloadFromWorkingDir(localDirPath); + }); + } + /** + * @protected + */ + async _downloadFromWorkingDir(localDirPath) { + await ensureLocalDirectory(localDirPath); + for (const file of await this.list()) { + const localPath = path_1.join(localDirPath, file.name); + if (file.isDirectory) { + await this.cd(file.name); + await this._downloadFromWorkingDir(localPath); + await this.cdup(); + } + else if (file.isFile) { + await this.downloadTo(localPath, file.name); + } + } + } + /** + * Make sure a given remote path exists, creating all directories as necessary. + * This function also changes the current working directory to the given path. + */ + async ensureDir(remoteDirPath) { + // If the remoteDirPath was absolute go to root directory. + if (remoteDirPath.startsWith("/")) { + await this.cd("/"); + } + const names = remoteDirPath.split("/").filter(name => name !== ""); + for (const name of names) { + await this._openDir(name); + } + } + /** + * Try to create a directory and enter it. This will not raise an exception if the directory + * couldn't be created if for example it already exists. + * @protected + */ + async _openDir(dirName) { + await this.sendIgnoringError("MKD " + dirName); + await this.cd(dirName); + } + /** + * Remove an empty directory, will fail if not empty. + */ + async removeEmptyDir(path) { + const validPath = await this.protectWhitespace(path); + return this.send(`RMD ${validPath}`); + } + /** + * FTP servers can't handle filenames that have leading whitespace. This method transforms + * a given path to fix that issue for most cases. + */ + async protectWhitespace(path) { + if (!path.startsWith(" ")) { + return path; + } + // Handle leading whitespace by prepending the absolute path: + // " test.txt" while being in the root directory becomes "/ test.txt". + const pwd = await this.pwd(); + const absolutePathPrefix = pwd.endsWith("/") ? pwd : pwd + "/"; + return absolutePathPrefix + path; + } + async _exitAtCurrentDirectory(func) { + const userDir = await this.pwd(); + try { + return await func(); + } + finally { + if (!this.closed) { + await ignoreError(() => this.cd(userDir)); + } + } + } + /** + * 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 strategies + * @returns a function that will try the provided strategies. + */ + _enterFirstCompatibleMode(strategies) { + return async (ftp) => { + ftp.log("Trying to find optimal transfer strategy..."); + for (const strategy of strategies) { + try { + const res = await strategy(ftp); + ftp.log("Optimal transfer strategy found."); + this.prepareTransfer = strategy; // eslint-disable-line require-atomic-updates + return res; + } + catch (err) { + // Receiving an FTPError means that the last transfer strategy failed and we should + // try the next one. Any other exception should stop the evaluation of strategies because + // something else went wrong. + if (!(err instanceof FtpContext_1.FTPError)) { + throw err; + } + } + } + throw new Error("None of the available transfer strategies work."); + }; + } + /** + * DEPRECATED, use `uploadFrom`. + * @deprecated + */ + async upload(source, toRemotePath, options = {}) { + this.ftp.log("Warning: upload() has been deprecated, use uploadFrom()."); + return this.uploadFrom(source, toRemotePath, options); + } + /** + * DEPRECATED, use `appendFrom`. + * @deprecated + */ + async append(source, toRemotePath, options = {}) { + this.ftp.log("Warning: append() has been deprecated, use appendFrom()."); + return this.appendFrom(source, toRemotePath, options); + } + /** + * DEPRECATED, use `downloadTo`. + * @deprecated + */ + async download(destination, fromRemotePath, startAt = 0) { + this.ftp.log("Warning: download() has been deprecated, use downloadTo()."); + return this.downloadTo(destination, fromRemotePath, startAt); + } + /** + * DEPRECATED, use `uploadFromDir`. + * @deprecated + */ + async uploadDir(localDirPath, remoteDirPath) { + this.ftp.log("Warning: uploadDir() has been deprecated, use uploadFromDir()."); + return this.uploadFromDir(localDirPath, remoteDirPath); + } + /** + * DEPRECATED, use `downloadToDir`. + * @deprecated + */ + async downloadDir(localDirPath) { + this.ftp.log("Warning: downloadDir() has been deprecated, use downloadToDir()."); + return this.downloadToDir(localDirPath); + } +} +exports.Client = Client; +async function ensureLocalDirectory(path) { + try { + await fsStat(path); + } + catch (err) { + await fsMkDir(path, { recursive: true }); + } +} +async function ignoreError(func) { + try { + return await func(); + } + catch (err) { + // Ignore + return undefined; + } +} + + +/***/ }), + +/***/ 202: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.FileInfo = exports.FileType = void 0; +var FileType; +(function (FileType) { + FileType[FileType["Unknown"] = 0] = "Unknown"; + FileType[FileType["File"] = 1] = "File"; + FileType[FileType["Directory"] = 2] = "Directory"; + FileType[FileType["SymbolicLink"] = 3] = "SymbolicLink"; +})(FileType = exports.FileType || (exports.FileType = {})); +/** + * Describes a file, directory or symbolic link. + */ +class FileInfo { + constructor(name) { + this.name = name; + this.type = FileType.Unknown; + this.size = 0; + /** + * Unparsed, raw modification date as a string. + * + * If `modifiedAt` is undefined, the FTP server you're connected to doesn't support the more modern + * MLSD command for machine-readable directory listings. The older command LIST is then used returning + * results that vary a lot between servers as the format hasn't been standardized. Here, directory listings + * and especially modification dates were meant to be human-readable first. + * + * Be careful when still trying to parse this by yourself. Parsing dates from listings using LIST is + * unreliable. This library decides to offer parsed dates only when they're absolutely reliable and safe to + * use e.g. for comparisons. + */ + this.rawModifiedAt = ""; + /** + * Parsed modification date. + * + * Available if the FTP server supports the MLSD command. Only MLSD guarantees dates than can be reliably + * parsed with the correct timezone and a resolution down to seconds. See `rawModifiedAt` property for the unparsed + * date that is always available. + */ + this.modifiedAt = undefined; + /** + * Unix permissions if present. If the underlying FTP server is not running on Unix this will be undefined. + * If set, you might be able to edit permissions with the FTP command `SITE CHMOD`. + */ + this.permissions = undefined; + /** + * Hard link count if available. + */ + this.hardLinkCount = undefined; + /** + * Link name for symbolic links if available. + */ + this.link = undefined; + /** + * Unix group if available. + */ + this.group = undefined; + /** + * Unix user if available. + */ + this.user = undefined; + /** + * Unique ID if available. + */ + this.uniqueID = undefined; + this.name = name; + } + get isDirectory() { + return this.type === FileType.Directory; + } + get isSymbolicLink() { + return this.type === FileType.SymbolicLink; + } + get isFile() { + return this.type === FileType.File; + } + /** + * Deprecated, legacy API. Use `rawModifiedAt` instead. + * @deprecated + */ + get date() { + return this.rawModifiedAt; + } + set date(rawModifiedAt) { + this.rawModifiedAt = rawModifiedAt; + } +} +exports.FileInfo = FileInfo; +FileInfo.UnixPermission = { + Read: 4, + Write: 2, + Execute: 1 +}; + + +/***/ }), + +/***/ 9052: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.FTPContext = exports.FTPError = void 0; +const net_1 = __webpack_require__(1631); +const parseControlResponse_1 = __webpack_require__(9948); +/** + * Describes an FTP server error response including the FTP response code. + */ +class FTPError extends Error { + constructor(res) { + super(res.message); + this.name = this.constructor.name; + this.code = res.code; + } +} +exports.FTPError = FTPError; +/** + * FTPContext holds the control and data sockets of an FTP connection and provides a + * simplified way to interact with an FTP server, handle responses, errors and timeouts. + * + * It doesn't implement or use any FTP commands. It's only a foundation to make writing an FTP + * client as easy as possible. You won't usually instantiate this, but use `Client`. + */ +class FTPContext { + /** + * Instantiate an FTP context. + * + * @param timeout - Timeout in milliseconds to apply to control and data connections. Use 0 for no timeout. + * @param encoding - Encoding to use for control connection. UTF-8 by default. Use "latin1" for older servers. + */ + constructor(timeout = 0, encoding = "utf8") { + this.timeout = timeout; + /** Debug-level logging of all socket communication. */ + this.verbose = false; + /** IP version to prefer (4: IPv4, 6: IPv6, undefined: automatic). */ + this.ipFamily = undefined; + /** Options for TLS connections. */ + this.tlsOptions = {}; + /** A multiline response might be received as multiple chunks. */ + this._partialResponse = ""; + this._encoding = encoding; + // Help Typescript understand that we do indeed set _socket in the constructor but use the setter method to do so. + this._socket = this.socket = this._newSocket(); + this._dataSocket = undefined; + } + /** + * Close the context. + */ + close() { + // Internally, closing a context is always described with an error. If there is still a task running, it will + // abort with an exception that the user closed the client during a task. If no task is running, no exception is + // thrown but all newly submitted tasks after that will abort the exception that the client has been closed. + // In addition the user will get a stack trace pointing to where exactly the client has been closed. So in any + // case use _closingError to determine whether a context is closed. This also allows us to have a single code-path + // for closing a context making the implementation easier. + const message = this._task ? "User closed client during task" : "User closed client"; + const err = new Error(message); + this.closeWithError(err); + } + /** + * Close the context with an error. + */ + closeWithError(err) { + // If this context already has been closed, don't overwrite the reason. + if (this._closingError) { + 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._closeSocket(this._socket); + this._closeSocket(this._dataSocket); + // Give the user's task a chance to react, maybe cleanup resources. + this._passToHandler(err); + // The task might not have been rejected by the user after receiving the error. + this._stopTrackingTask(); + } + /** + * Returns true if this context has been closed or hasn't been connected yet. You can reopen it with `access`. + */ + get closed() { + return this.socket.remoteAddress === undefined || this._closingError !== undefined; + } + /** + * Reset this contex and all of its state. + */ + reset() { + this.socket = this._newSocket(); + } + /** + * Get the FTP control socket. + */ + get socket() { + return this._socket; + } + /** + * Set the socket for the control connection. This will only close the current control socket + * if the new one is not an upgrade to the current one. + */ + set socket(socket) { + // No data socket should be open in any case where the control socket is set or upgraded. + this.dataSocket = undefined; + 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(); + } + this._removeSocketListeners(this._socket); + } + if (socket) { + // Setting a completely new control socket is in essence something like a reset. That's + // why we also close any open data connection above. We can go one step further and reset + // a possible closing error. That means that a closed FTPContext can be "reopened" by + // setting a new control socket. + this._closingError = undefined; + // Don't set a timeout yet. Timeout for control sockets is only active during a task, see handle() below. + socket.setTimeout(0); + socket.setEncoding(this._encoding); + socket.setKeepAlive(true); + socket.on("data", data => this._onControlSocketData(data)); + // Server sending a FIN packet is treated as an error. + socket.on("end", () => this.closeWithError(new Error("Server sent FIN packet unexpectedly, closing connection."))); + // Control being closed without error by server is treated as an error. + socket.on("close", hadError => { if (!hadError) + this.closeWithError(new Error("Server closed connection unexpectedly.")); }); + this._setupDefaultErrorHandlers(socket, "control socket"); + } + this._socket = socket; + } + /** + * Get the current FTP data connection if present. + */ + get dataSocket() { + return this._dataSocket; + } + /** + * Set the socket for the data connection. This will automatically close the former data socket. + */ + set dataSocket(socket) { + this._closeSocket(this._dataSocket); + if (socket) { + // Don't set a timeout yet. Timeout data socket should be activated when data transmission starts + // and timeout on control socket is deactivated. + socket.setTimeout(0); + this._setupDefaultErrorHandlers(socket, "data socket"); + } + this._dataSocket = socket; + } + /** + * Get the currently used encoding. + */ + get encoding() { + return this._encoding; + } + /** + * Set the encoding used for the control socket. + * + * See https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings for what encodings + * are supported by Node. + */ + set encoding(encoding) { + this._encoding = encoding; + if (this.socket) { + this.socket.setEncoding(encoding); + } + } + /** + * Send an FTP command without waiting for or handling the result. + */ + send(command) { + const containsPassword = command.startsWith("PASS"); + const message = containsPassword ? "> PASS ###" : `> ${command}`; + this.log(message); + this._socket.write(command + "\r\n", this.encoding); + } + /** + * Send an FTP command and handle the first response. Use this if you have a simple + * request-response situation. + */ + request(command) { + return this.handle(command, (res, task) => { + if (res instanceof Error) { + task.reject(res); + } + else { + task.resolve(res); + } + }); + } + /** + * Send an FTP command and handle any response until you resolve/reject. Use this if you expect multiple responses + * to a request. This returns a Promise that will hold whatever the response handler passed on when resolving/rejecting its task. + */ + 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); + // Don't return here, continue with returning the Promise that will then be rejected + // 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: (...args) => { + this._stopTrackingTask(); + resolvePromise(...args); + }, + reject: err => { + this._stopTrackingTask(); + rejectPromise(err); + } + }; + this._task = { + stack, + resolver, + responseHandler + }; + 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'. + err.stack += `\nClosing reason: ${this._closingError.stack}`; + err.code = this._closingError.code !== undefined ? this._closingError.code : "0"; + this._passToHandler(err); + return; + } + // Only track control socket timeout during the lifecycle of a task. This avoids timeouts on idle sockets, + // the default socket behaviour which is not expected by most users. + this.socket.setTimeout(this.timeout); + if (command) { + this.send(command); + } + }); + } + /** + * Log message if set to be verbose. + */ + log(message) { + if (this.verbose) { + // tslint:disable-next-line no-console + console.log(message); + } + } + /** + * Return true if the control socket is using TLS. This does not mean that a session + * has already been negotiated. + */ + get hasTLS() { + return "encrypted" in this._socket; + } + /** + * Removes reference to current task and handler. This won't resolve or reject the task. + * @protected + */ + _stopTrackingTask() { + // Disable timeout on control socket if there is no task active. + this.socket.setTimeout(0); + this._task = undefined; + } + /** + * Handle incoming data on the control socket. The chunk is going to be of type `string` + * because we let `socket` handle encoding with `setEncoding`. + * @protected + */ + _onControlSocketData(chunk) { + this.log(`< ${chunk}`); + // This chunk might complete an earlier partial response. + const completeResponse = this._partialResponse + chunk; + const parsed = parseControlResponse_1.parseControlResponse(completeResponse); + // Remember any incomplete remainder. + this._partialResponse = parsed.rest; + // Each response group is passed along individually. + for (const message of parsed.messages) { + const code = parseInt(message.substr(0, 3), 10); + const response = { code, message }; + const err = code >= 400 ? new FTPError(response) : undefined; + this._passToHandler(err ? err : response); + } + } + /** + * Send the current handler a response. This is usually a control socket response + * or a socket event, like an error or timeout. + * @protected + */ + _passToHandler(response) { + if (this._task) { + this._task.responseHandler(response, this._task.resolver); + } + // Errors other than FTPError always close the client. If there isn't an active task to handle the error, + // the next one submitted will receive it using `_closingError`. + // There is only one edge-case: If there is an FTPError while no task is active, the error will be dropped. + // But that means that the user sent an FTP command with no intention of handling the result. So why should the + // error be handled? Maybe log it at least? Debug logging will already do that and the client stays useable after + // FTPError. So maybe no need to do anything here. + } + /** + * Setup all error handlers for a socket. + * @protected + */ + _setupDefaultErrorHandlers(socket, identifier) { + socket.once("error", error => { + error.message += ` (${identifier})`; + this.closeWithError(error); + }); + socket.once("close", hadError => { + if (hadError) { + this.closeWithError(new Error(`Socket closed due to transmission error (${identifier})`)); + } + }); + socket.once("timeout", () => this.closeWithError(new Error(`Timeout (${identifier})`))); + } + /** + * Close a socket. + * @protected + */ + _closeSocket(socket) { + if (socket) { + socket.destroy(); + this._removeSocketListeners(socket); + } + } + /** + * Remove all default listeners for socket. + * @protected + */ + _removeSocketListeners(socket) { + socket.removeAllListeners(); + // Before Node.js 10.3.0, using `socket.removeAllListeners()` without any name did not work: https://github.com/nodejs/node/issues/20923. + socket.removeAllListeners("timeout"); + socket.removeAllListeners("data"); + socket.removeAllListeners("end"); + socket.removeAllListeners("error"); + socket.removeAllListeners("close"); + socket.removeAllListeners("connect"); + } + /** + * Provide a new socket instance. + * + * Internal use only, replaced for unit tests. + */ + _newSocket() { + return new net_1.Socket(); + } +} +exports.FTPContext = FTPContext; + + +/***/ }), + +/***/ 7170: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ProgressTracker = void 0; +/** + * Tracks progress of one socket data transfer at a time. + */ +class ProgressTracker { + constructor() { + this.bytesOverall = 0; + this.intervalMs = 500; + this.onStop = noop; + this.onHandle = noop; + } + /** + * Register a new handler for progress info. Use `undefined` to disable reporting. + */ + reportTo(onHandle = noop) { + this.onHandle = onHandle; + } + /** + * Start tracking transfer progress of a socket. + * + * @param socket The socket to observe. + * @param name A name associated with this progress tracking, e.g. a filename. + * @param type The type of the transfer, typically "upload" or "download". + */ + start(socket, name, type) { + let lastBytes = 0; + this.onStop = poll(this.intervalMs, () => { + const bytes = socket.bytesRead + socket.bytesWritten; + this.bytesOverall += bytes - lastBytes; + lastBytes = bytes; + this.onHandle({ + name, + type, + bytes, + bytesOverall: this.bytesOverall + }); + }); + } + /** + * Stop tracking transfer progress. + */ + stop() { + this.onStop(false); + } + /** + * Call the progress handler one more time, then stop tracking. + */ + updateAndStop() { + this.onStop(true); + } +} +exports.ProgressTracker = ProgressTracker; +/** + * Starts calling a callback function at a regular interval. The first call will go out + * immediately. The function returns a function to stop the polling. + */ +function poll(intervalMs, updateFunc) { + const id = setInterval(updateFunc, intervalMs); + const stopFunc = (stopWithUpdate) => { + clearInterval(id); + if (stopWithUpdate) { + updateFunc(); + } + // Prevent repeated calls to stop calling handler. + updateFunc = noop; + }; + updateFunc(); + return stopFunc; +} +function noop() { } + + +/***/ }), + +/***/ 4677: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); + + +/***/ }), + +/***/ 8184: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.StringWriter = void 0; +const stream_1 = __webpack_require__(2413); +class StringWriter extends stream_1.Writable { + constructor() { + super(...arguments); + this.buf = Buffer.alloc(0); + } + _write(chunk, _, callback) { + if (chunk instanceof Buffer) { + this.buf = Buffer.concat([this.buf, chunk]); + callback(null); + } + else { + callback(new Error("StringWriter expects chunks of type 'Buffer'.")); + } + } + getText(encoding) { + return this.buf.toString(encoding); + } +} +exports.StringWriter = StringWriter; + + +/***/ }), + +/***/ 7957: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +/** + * Public API + */ +__exportStar(__webpack_require__(8337), exports); +__exportStar(__webpack_require__(9052), exports); +__exportStar(__webpack_require__(202), exports); +__exportStar(__webpack_require__(2993), exports); +__exportStar(__webpack_require__(4677), exports); +var transfer_1 = __webpack_require__(5803); +Object.defineProperty(exports, "enterPassiveModeIPv4", ({ enumerable: true, get: function () { return transfer_1.enterPassiveModeIPv4; } })); +Object.defineProperty(exports, "enterPassiveModeIPv6", ({ enumerable: true, get: function () { return transfer_1.enterPassiveModeIPv6; } })); + + +/***/ }), + +/***/ 6288: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ipIsPrivateV4Address = exports.upgradeSocket = exports.describeAddress = exports.describeTLS = void 0; +const tls_1 = __webpack_require__(4016); +/** + * Returns a string describing the encryption on a given socket instance. + */ +function describeTLS(socket) { + if (socket instanceof tls_1.TLSSocket) { + const protocol = socket.getProtocol(); + return protocol ? protocol : "Server socket or disconnected client socket"; + } + return "No encryption"; +} +exports.describeTLS = describeTLS; +/** + * Returns a string describing the remote address of a socket. + */ +function describeAddress(socket) { + if (socket.remoteFamily === "IPv6") { + return `[${socket.remoteAddress}]:${socket.remotePort}`; + } + return `${socket.remoteAddress}:${socket.remotePort}`; +} +exports.describeAddress = describeAddress; +/** + * Upgrade a socket connection with TLS. + */ +function upgradeSocket(socket, options) { + return new Promise((resolve, reject) => { + const tlsOptions = Object.assign({}, options, { + socket + }); + const tlsSocket = tls_1.connect(tlsOptions, () => { + const expectCertificate = tlsOptions.rejectUnauthorized !== false; + if (expectCertificate && !tlsSocket.authorized) { + reject(tlsSocket.authorizationError); + } + else { + // Remove error listener added below. + tlsSocket.removeAllListeners("error"); + resolve(tlsSocket); + } + }).once("error", error => { + reject(error); + }); + }); +} +exports.upgradeSocket = upgradeSocket; +/** + * Returns true if an IP is a private address according to https://tools.ietf.org/html/rfc1918#section-3. + * This will handle IPv4-mapped IPv6 addresses correctly but return false for all other IPv6 addresses. + * + * @param ip The IP as a string, e.g. "192.168.0.1" + */ +function ipIsPrivateV4Address(ip = "") { + // Handle IPv4-mapped IPv6 addresses like ::ffff:192.168.0.1 + if (ip.startsWith("::ffff:")) { + ip = ip.substr(7); // Strip ::ffff: prefix + } + 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 +} +exports.ipIsPrivateV4Address = ipIsPrivateV4Address; + + +/***/ }), + +/***/ 9948: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.positiveIntermediate = exports.positiveCompletion = exports.isMultiline = exports.isSingleLine = exports.parseControlResponse = void 0; +const LF = "\n"; +/** + * Parse an FTP control response as a collection of messages. A message is a complete + * single- or multiline response. A response can also contain multiple multiline responses + * that will each be represented by a message. A response can also be incomplete + * and be completed on the next incoming data chunk for which case this function also + * describes a `rest`. This function converts all CRLF to LF. + */ +function parseControlResponse(text) { + const lines = text.split(/\r?\n/).filter(isNotBlank); + const messages = []; + let startAt = 0; + let tokenRegex; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // No group has been opened. + if (!tokenRegex) { + if (isMultiline(line)) { + // Open a group by setting an expected token. + const token = line.substr(0, 3); + tokenRegex = new RegExp(`^${token}(?:$| )`); + startAt = i; + } + else if (isSingleLine(line)) { + // Single lines can be grouped immediately. + messages.push(line); + } + } + // Group has been opened, expect closing token. + else if (tokenRegex.test(line)) { + tokenRegex = undefined; + messages.push(lines.slice(startAt, i + 1).join(LF)); + } + } + // The last group might not have been closed, report it as a rest. + const rest = tokenRegex ? lines.slice(startAt).join(LF) + LF : ""; + return { messages, rest }; +} +exports.parseControlResponse = parseControlResponse; +function isSingleLine(line) { + return /^\d\d\d(?:$| )/.test(line); +} +exports.isSingleLine = isSingleLine; +function isMultiline(line) { + return /^\d\d\d-/.test(line); +} +exports.isMultiline = isMultiline; +/** + * Return true if an FTP return code describes a positive completion. + */ +function positiveCompletion(code) { + return code >= 200 && code < 300; +} +exports.positiveCompletion = positiveCompletion; +/** + * Return true if an FTP return code describes a positive intermediate response. + */ +function positiveIntermediate(code) { + return code >= 300 && code < 400; +} +exports.positiveIntermediate = positiveIntermediate; +function isNotBlank(str) { + return str !== ""; +} + + +/***/ }), + +/***/ 2993: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (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.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.parseList = void 0; +const dosParser = __importStar(__webpack_require__(6199)); +const unixParser = __importStar(__webpack_require__(2622)); +const mlsdParser = __importStar(__webpack_require__(8157)); +/** + * Available directory listing parsers. These are candidates that will be tested + * in the order presented. The first candidate will be used to parse the whole list. + */ +const availableParsers = [ + dosParser, + unixParser, + mlsdParser // Keep MLSD last, may accept filename only +]; +function firstCompatibleParser(line, parsers) { + return parsers.find(parser => parser.testLine(line) === true); +} +function stringIsNotBlank(str) { + return str.trim() !== ""; +} +const REGEX_NEWLINE = /\r?\n/; +/** + * Parse raw directory listing. + */ +function parseList(rawList) { + const lines = rawList + .split(REGEX_NEWLINE) + .filter(stringIsNotBlank); + if (lines.length === 0) { + return []; + } + const testLine = lines[lines.length - 1]; + const parser = firstCompatibleParser(testLine, availableParsers); + if (!parser) { + throw new Error("This library only supports MLSD, Unix- or DOS-style directory listing. Your FTP server seems to be using another format. You can see the transmitted listing when setting `client.ftp.verbose = true`. You can then provide a custom parser to `client.parseList`, see the documentation for details."); + } + const files = lines + .map(parser.parseLine) + .filter((info) => info !== undefined); + return parser.transformList(files); +} +exports.parseList = parseList; + + +/***/ }), + +/***/ 6199: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.transformList = exports.parseLine = exports.testLine = void 0; +const FileInfo_1 = __webpack_require__(202); +/** + * This parser is based on the FTP client library source code in Apache Commons Net provided + * under the Apache 2.0 license. It has been simplified and rewritten to better fit the Javascript language. + * + * https://github.com/apache/commons-net/blob/master/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java + */ +const RE_LINE = new RegExp("(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm swallow trailing spaces + + "(?:()|([0-9]+))\\s+" // or ddddd swallow trailing spaces + + "(\\S.*)" // First non-space followed by rest of line (name) +); +/** + * Returns true if a given line might be a DOS-style listing. + * + * - Example: `12-05-96 05:03PM myDir` + */ +function testLine(line) { + return /^\d{2}/.test(line) && RE_LINE.test(line); +} +exports.testLine = testLine; +/** + * Parse a single line of a DOS-style directory listing. + */ +function parseLine(line) { + const groups = line.match(RE_LINE); + if (groups === null) { + return undefined; + } + const name = groups[5]; + if (name === "." || name === "..") { // Ignore parent directory links + return undefined; + } + const file = new FileInfo_1.FileInfo(name); + const fileType = groups[3]; + if (fileType === "") { + file.type = FileInfo_1.FileType.Directory; + file.size = 0; + } + else { + file.type = FileInfo_1.FileType.File; + file.size = parseInt(groups[4], 10); + } + file.rawModifiedAt = groups[1] + " " + groups[2]; + return file; +} +exports.parseLine = parseLine; +function transformList(files) { + return files; +} +exports.transformList = transformList; + + +/***/ }), + +/***/ 8157: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.parseMLSxDate = exports.transformList = exports.parseLine = exports.testLine = void 0; +const FileInfo_1 = __webpack_require__(202); +function parseSize(value, info) { + info.size = parseInt(value, 10); +} +/** + * Parsers for MLSD facts. + */ +const factHandlersByName = { + "size": parseSize, + "sizd": parseSize, + "unique": (value, info) => { + info.uniqueID = value; + }, + "modify": (value, info) => { + info.modifiedAt = parseMLSxDate(value); + info.rawModifiedAt = info.modifiedAt.toISOString(); + }, + "type": (value, info) => { + // There seems to be confusion on how to handle symbolic links for Unix. RFC 3659 doesn't describe + // this but mentions some examples using the syntax `type=OS.unix=slink:`. But according to + // an entry in the Errata (https://www.rfc-editor.org/errata/eid1500) this syntax can't be valid. + // Instead it proposes to use `type=OS.unix=symlink` and to then list the actual target of the + // symbolic link as another entry in the directory listing. The unique identifiers can then be used + // to derive the connection between link(s) and target. We'll have to handle both cases as there + // are differing opinions on how to deal with this. Here are some links on this topic: + // - ProFTPD source: https://github.com/proftpd/proftpd/blob/56e6dfa598cbd4ef5c6cba439bcbcd53a63e3b21/modules/mod_facts.c#L531 + // - ProFTPD bug: http://bugs.proftpd.org/show_bug.cgi?id=3318 + // - ProFTPD statement: http://www.proftpd.org/docs/modules/mod_facts.html + // โ€“ FileZilla bug: https://trac.filezilla-project.org/ticket/9310 + if (value.startsWith("OS.unix=slink")) { + info.type = FileInfo_1.FileType.SymbolicLink; + info.link = value.substr(value.indexOf(":") + 1); + return 1 /* Continue */; + } + switch (value) { + case "file": + info.type = FileInfo_1.FileType.File; + break; + case "dir": + info.type = FileInfo_1.FileType.Directory; + break; + case "OS.unix=symlink": + info.type = FileInfo_1.FileType.SymbolicLink; + // The target of the symbolic link might be defined in another line in the directory listing. + // We'll handle this in `transformList()` below. + break; + case "cdir": // Current directory being listed + case "pdir": // Parent directory + return 2 /* IgnoreFile */; // Don't include these entries in the listing + default: + info.type = FileInfo_1.FileType.Unknown; + } + return 1 /* Continue */; + }, + "unix.mode": (value, info) => { + const digits = value.substr(-3); + info.permissions = { + user: parseInt(digits[0], 10), + group: parseInt(digits[1], 10), + world: parseInt(digits[2], 10) + }; + }, + "unix.ownername": (value, info) => { + info.user = value; + }, + "unix.owner": (value, info) => { + if (info.user === undefined) + info.user = value; + }, + get "unix.uid"() { + return this["unix.owner"]; + }, + "unix.groupname": (value, info) => { + info.group = value; + }, + "unix.group": (value, info) => { + if (info.group === undefined) + info.group = value; + }, + get "unix.gid"() { + return this["unix.group"]; + } + // Regarding the fact "perm": + // We don't handle permission information stored in "perm" because its information is conceptually + // different from what users of FTP clients usually associate with "permissions". Those that have + // some expectations (and probably want to edit them with a SITE command) often unknowingly expect + // the Unix permission system. The information passed by "perm" describes what FTP commands can be + // executed with a file/directory. But even this can be either incomplete or just meant as a "guide" + // as the spec mentions. From https://tools.ietf.org/html/rfc3659#section-7.5.5: "The permissions are + // described here as they apply to FTP commands. They may not map easily into particular permissions + // available on the server's operating system." The parser by Apache Commons tries to translate these + // to Unix permissions โ€“ this is misleading users and might not even be correct. +}; +/** + * Split a string once at the first position of a delimiter. For example + * `splitStringOnce("a b c d", " ")` returns `["a", "b c d"]`. + */ +function splitStringOnce(str, delimiter) { + const pos = str.indexOf(delimiter); + const a = str.substr(0, pos); + const b = str.substr(pos + delimiter.length); + return [a, b]; +} +/** + * Returns true if a given line might be part of an MLSD listing. + * + * - Example 1: `size=15227;type=dir;perm=el;modify=20190419065730; test one` + * - Example 2: ` file name` (leading space) + */ +function testLine(line) { + return /^\S+=\S+;/.test(line) || line.startsWith(" "); +} +exports.testLine = testLine; +/** + * Parse single line as MLSD listing, see specification at https://tools.ietf.org/html/rfc3659#section-7. + */ +function parseLine(line) { + const [packedFacts, name] = splitStringOnce(line, " "); + if (name === "" || name === "." || name === "..") { + return undefined; + } + const info = new FileInfo_1.FileInfo(name); + const facts = packedFacts.split(";"); + for (const fact of facts) { + const [factName, factValue] = splitStringOnce(fact, "="); + if (!factValue) { + continue; + } + const factHandler = factHandlersByName[factName.toLowerCase()]; + if (!factHandler) { + continue; + } + const result = factHandler(factValue, info); + if (result === 2 /* IgnoreFile */) { + return undefined; + } + } + return info; +} +exports.parseLine = parseLine; +function transformList(files) { + // Create a map of all files that are not symbolic links by their unique ID + const nonLinksByID = new Map(); + for (const file of files) { + if (!file.isSymbolicLink && file.uniqueID !== undefined) { + nonLinksByID.set(file.uniqueID, file); + } + } + const resolvedFiles = []; + for (const file of files) { + // Try to associate unresolved symbolic links with a target file/directory. + if (file.isSymbolicLink && file.uniqueID !== undefined && file.link === undefined) { + const target = nonLinksByID.get(file.uniqueID); + if (target !== undefined) { + file.link = target.name; + } + } + // The target of a symbolic link is listed as an entry in the directory listing but might + // have a path pointing outside of this directory. In that case we don't want this entry + // to be part of the listing. We generally don't want these kind of entries at all. + const isPartOfDirectory = !file.name.includes("/"); + if (isPartOfDirectory) { + resolvedFiles.push(file); + } + } + return resolvedFiles; +} +exports.transformList = transformList; +/** + * Parse date as specified in https://tools.ietf.org/html/rfc3659#section-2.3. + * + * Message contains response code and modified time in the format: YYYYMMDDHHMMSS[.sss] + * For example `19991005213102` or `19980615100045.014`. + */ +function parseMLSxDate(fact) { + return new Date(Date.UTC(+fact.slice(0, 4), // Year + +fact.slice(4, 6) - 1, // Month + +fact.slice(6, 8), // Date + +fact.slice(8, 10), // Hours + +fact.slice(10, 12), // Minutes + +fact.slice(12, 14), // Seconds + +fact.slice(15, 18) // Milliseconds + )); +} +exports.parseMLSxDate = parseMLSxDate; + + +/***/ }), + +/***/ 2622: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.transformList = exports.parseLine = exports.testLine = void 0; +const FileInfo_1 = __webpack_require__(202); +const JA_MONTH = "\u6708"; +const JA_DAY = "\u65e5"; +const JA_YEAR = "\u5e74"; +/** + * This parser is based on the FTP client library source code in Apache Commons Net provided + * under the Apache 2.0 license. It has been simplified and rewritten to better fit the Javascript language. + * + * https://github.com/apache/commons-net/blob/master/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java + * + * Below is the regular expression used by this parser. + * + * Permissions: + * r the file is readable + * w the file is writable + * x the file is executable + * - the indicated permission is not granted + * L mandatory locking occurs during access (the set-group-ID bit is + * on and the group execution bit is off) + * s the set-user-ID or set-group-ID bit is on, and the corresponding + * user or group execution bit is also on + * S undefined bit-state (the set-user-ID bit is on and the user + * execution bit is off) + * t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and + * execution is on + * T the 1000 bit is turned on, and execution is off (undefined bit- + * state) + * e z/OS external link bit + * Final letter may be appended: + * + file has extended security attributes (e.g. ACL) + * Note: local listings on MacOSX also use '@' + * this is not allowed for here as does not appear to be shown by FTP servers + * {@code @} file has extended attributes + */ +const RE_LINE = new RegExp("([bcdelfmpSs-])" // file type + + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?" // permissions + + "\\s*" // separator TODO why allow it to be omitted?? + + "(\\d+)" // link count + + "\\s+" // separator + + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces) + + "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces) + + "(\\d+(?:,\\s*\\d+)?)" // size or n,m + + "\\s+" // separator + /** + * numeric or standard format date: + * yyyy-mm-dd (expecting hh:mm to follow) + * MMM [d]d + * [d]d MMM + * N.B. use non-space for MMM to allow for languages such as German which use + * diacritics (e.g. umlaut) in some abbreviations. + * Japanese uses numeric day and month with suffixes to distinguish them + * [d]dXX [d]dZZ + */ + + "(" + + "(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd + "|(?:\\S{3}\\s+\\d{1,2})" + // MMM [d]d + "|(?:\\d{1,2}\\s+\\S{3})" + // [d]d MMM + "|(?:\\d{1,2}" + JA_MONTH + "\\s+\\d{1,2}" + JA_DAY + ")" + + ")" + + "\\s+" // separator + /** + * year (for non-recent standard format) - yyyy + * or time (for numeric or recent standard format) [h]h:mm + * or Japanese year - yyyyXX + */ + + "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20) + + "\\s" // separator + + "(.*)"); // the rest (21) +/** + * Returns true if a given line might be a Unix-style listing. + * + * - Example: `-rw-r--r--+ 1 patrick staff 1057 Dec 11 14:35 test.txt` + */ +function testLine(line) { + return RE_LINE.test(line); +} +exports.testLine = testLine; +/** + * Parse a single line of a Unix-style directory listing. + */ +function parseLine(line) { + const groups = line.match(RE_LINE); + if (groups === null) { + return undefined; + } + const name = groups[21]; + if (name === "." || name === "..") { // Ignore parent directory links + return undefined; + } + const file = new FileInfo_1.FileInfo(name); + file.size = parseInt(groups[18], 10); + file.user = groups[16]; + file.group = groups[17]; + file.hardLinkCount = parseInt(groups[15], 10); + file.rawModifiedAt = groups[19] + " " + groups[20]; + file.permissions = { + user: parseMode(groups[4], groups[5], groups[6]), + group: parseMode(groups[8], groups[9], groups[10]), + world: parseMode(groups[12], groups[13], groups[14]), + }; + // Set file type + switch (groups[1].charAt(0)) { + case "d": + file.type = FileInfo_1.FileType.Directory; + break; + case "e": // NET-39 => z/OS external link + file.type = FileInfo_1.FileType.SymbolicLink; + break; + case "l": + file.type = FileInfo_1.FileType.SymbolicLink; + break; + case "b": + case "c": + file.type = FileInfo_1.FileType.File; // TODO change this if DEVICE_TYPE implemented + break; + case "f": + case "-": + file.type = FileInfo_1.FileType.File; + break; + default: + // A 'whiteout' file is an ARTIFICIAL entry in any of several types of + // 'translucent' filesystems, of which a 'union' filesystem is one. + file.type = FileInfo_1.FileType.Unknown; + } + // Separate out the link name for symbolic links + if (file.isSymbolicLink) { + const end = name.indexOf(" -> "); + if (end !== -1) { + file.name = name.substring(0, end); + file.link = name.substring(end + 4); + } + } + return file; +} +exports.parseLine = parseLine; +function transformList(files) { + return files; +} +exports.transformList = transformList; +function parseMode(r, w, x) { + let value = 0; + if (r !== "-") { + value += FileInfo_1.FileInfo.UnixPermission.Read; + } + if (w !== "-") { + value += FileInfo_1.FileInfo.UnixPermission.Write; + } + const execToken = x.charAt(0); + if (execToken !== "-" && execToken.toUpperCase() !== execToken) { + value += FileInfo_1.FileInfo.UnixPermission.Execute; + } + return value; +} + + +/***/ }), + +/***/ 5803: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + +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 = __webpack_require__(6288); +const tls_1 = __webpack_require__(4016); +const parseControlResponse_1 = __webpack_require__(9948); +/** + * Prepare a data socket using passive mode over IPv6. + */ +async function enterPassiveModeIPv6(ftp) { + const res = await ftp.request("EPSV"); + const port = parseEpsvResponse(res.message); + if (!port) { + throw new Error("Can't parse EPSV response: " + res.message); + } + const controlHost = ftp.socket.remoteAddress; + if (controlHost === undefined) { + throw new Error("Control socket is disconnected, can't get remote address."); + } + await connectForPassiveTransfer(controlHost, port, ftp); + return res; +} +exports.enterPassiveModeIPv6 = enterPassiveModeIPv6; +/** + * Parse an EPSV response. Returns only the port as in EPSV the host of the control connection is used. + */ +function parseEpsvResponse(message) { + // Get port from EPSV response, e.g. "229 Entering Extended Passive Mode (|||6446|)" + // Some FTP Servers such as the one on IBM i (OS/400) use ! instead of | in their EPSV response. + const groups = message.match(/[|!]{3}(.+)[|!]/); + if (groups === null || groups[1] === undefined) { + throw new Error(`Can't parse response to 'EPSV': ${message}`); + } + const port = parseInt(groups[1], 10); + if (Number.isNaN(port)) { + throw new Error(`Can't parse response to 'EPSV', port is not a number: ${message}`); + } + return port; +} +exports.parseEpsvResponse = parseEpsvResponse; +/** + * Prepare a data socket using passive mode over IPv4. + */ +async function enterPassiveModeIPv4(ftp) { + const res = await ftp.request("PASV"); + const target = parsePasvResponse(res.message); + if (!target) { + throw new Error("Can't parse PASV response: " + res.message); + } + // If the host in the PASV response has a local address while the control connection hasn't, + // we assume a NAT issue and use the IP of the control connection as the target for the data connection. + // 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)) { + target.host = controlHost; + } + await connectForPassiveTransfer(target.host, target.port, ftp); + return res; +} +exports.enterPassiveModeIPv4 = enterPassiveModeIPv4; +/** + * Parse a PASV response. + */ +function parsePasvResponse(message) { + // Get host and port from PASV response, e.g. "227 Entering Passive Mode (192,168,1,100,10,229)" + const groups = message.match(/([-\d]+,[-\d]+,[-\d]+,[-\d]+),([-\d]+),([-\d]+)/); + if (groups === null || groups.length !== 4) { + throw new Error(`Can't parse response to 'PASV': ${message}`); + } + return { + host: groups[1].replace(/,/g, "."), + port: (parseInt(groups[2], 10) & 255) * 256 + (parseInt(groups[3], 10) & 255) + }; +} +exports.parsePasvResponse = parsePasvResponse; +function connectForPassiveTransfer(host, port, ftp) { + return new Promise((resolve, reject) => { + const handleConnErr = function (err) { + err.message = "Can't open data connection in passive mode: " + err.message; + reject(err); + }; + let socket = ftp._newSocket(); + socket.on("error", handleConnErr); + socket.connect({ port, host, family: ftp.ipFamily }, () => { + if (ftp.socket instanceof tls_1.TLSSocket) { + socket = 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 + // security: If a completely new session would be negotiated, a hacker + // could guess the port and connect to the new data connection before we do + // by just starting his/her own TLS session. + session: ftp.socket.getSession() + })); + // It's the responsibility of the transfer task to wait until the + // TLS socket issued the event 'secureConnect'. We can't do this + // here because some servers will start upgrading after the + // specific transfer request has been made. List and download don't + // have to wait for this event because the server sends whenever it + // is ready. But for upload this has to be taken into account, + // see the details in the upload() function below. + } + // Let the FTPContext listen to errors from now on, remove local handler. + socket.removeListener("error", handleConnErr); + ftp.dataSocket = socket; + resolve(); + }); + }); +} +exports.connectForPassiveTransfer = connectForPassiveTransfer; +/** + * Helps resolving/rejecting transfers. + * + * This is used internally for all FTP transfers. For example when downloading, the server might confirm + * with "226 Transfer complete" when in fact the download on the data connection has not finished + * yet. With all transfers we make sure that a) the result arrived and b) has been confirmed by + * e.g. the control connection. We just don't know in which order this will happen. + */ +class TransferResolver { + /** + * Instantiate a TransferResolver + */ + constructor(ftp, progress) { + this.ftp = ftp; + this.progress = progress; + this.response = undefined; + this.dataTransferDone = false; + } + /** + * Mark the beginning of a transfer. + * + * @param name - Name of the transfer, usually the filename. + * @param type - Type of transfer, usually "upload" or "download". + */ + onDataStart(name, type) { + // Let the data socket be in charge of tracking timeouts during transfer. + // The control socket sits idle during this time anyway and might provoke + // a timeout unnecessarily. The control connection will take care + // of timeouts again once data transfer is complete or failed. + if (this.ftp.dataSocket === undefined) { + throw new Error("Data transfer should start but there is no data connection."); + } + this.ftp.socket.setTimeout(0); + this.ftp.dataSocket.setTimeout(this.ftp.timeout); + this.progress.start(this.ftp.dataSocket, name, type); + } + /** + * The data connection has finished the transfer. + */ + onDataDone(task) { + this.progress.updateAndStop(); + // Hand-over timeout tracking back to the control connection. It's possible that + // we don't receive the response over the control connection that the transfer is + // done. In this case, we want to correctly associate the resulting timeout with + // the control connection. + this.ftp.socket.setTimeout(this.ftp.timeout); + if (this.ftp.dataSocket) { + this.ftp.dataSocket.setTimeout(0); + } + this.dataTransferDone = true; + this.tryResolve(task); + } + /** + * The control connection reports the transfer as finished. + */ + onControlDone(task, response) { + this.response = response; + this.tryResolve(task); + } + /** + * An error has been reported and the task should be rejected. + */ + onError(task, err) { + this.progress.updateAndStop(); + this.ftp.socket.setTimeout(this.ftp.timeout); + this.ftp.dataSocket = undefined; + task.reject(err); + } + /** + * Control connection sent an unexpected request requiring a response from our part. We + * can't provide that (because unknown) and have to close the contrext with an error because + * the FTP server is now caught up in a state we can't resolve. + */ + onUnexpectedRequest(response) { + const err = new Error(`Unexpected FTP response is requesting an answer: ${response.message}`); + this.ftp.closeWithError(err); + } + tryResolve(task) { + // To resolve, we need both control and data connection to report that the transfer is done. + const canResolve = this.dataTransferDone && this.response !== undefined; + if (canResolve) { + this.ftp.dataSocket = undefined; + task.resolve(this.response); + } + } +} +function uploadFrom(source, config) { + const resolver = new TransferResolver(config.ftp, config.tracker); + const fullCommand = `${config.command} ${config.remotePath}`; + return config.ftp.handle(fullCommand, (res, task) => { + if (res instanceof Error) { + resolver.onError(task, res); + } + else if (res.code === 150 || res.code === 125) { // Ready to upload + const dataSocket = config.ftp.dataSocket; + if (!dataSocket) { + resolver.onError(task, new Error("Upload should begin but no data connection is available.")); + return; + } + // If we are using TLS, we have to wait until the dataSocket issued + // '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)})`); + 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); + }); + }); + } + else if (parseControlResponse_1.positiveCompletion(res.code)) { // Transfer complete + resolver.onControlDone(task, res); + } + else if (parseControlResponse_1.positiveIntermediate(res.code)) { + resolver.onUnexpectedRequest(res); + } + // Ignore all other positive preliminary response codes (< 200) + }); +} +exports.uploadFrom = uploadFrom; +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) { + resolver.onError(task, res); + } + else if (res.code === 150 || res.code === 125) { // Ready to download + const dataSocket = config.ftp.dataSocket; + if (!dataSocket) { + 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)})`); + resolver.onDataStart(config.remotePath, config.type); + onConditionOrEvent(isWritableFinished(destination), destination, "finish", () => 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 + resolver.onControlDone(task, res); + } + else if (parseControlResponse_1.positiveIntermediate(res.code)) { + resolver.onUnexpectedRequest(res); + } + // Ignore all other positive preliminary response codes (< 200) + }); +} +exports.downloadTo = downloadTo; +/** + * Calls a function immediately if a condition is met or subscribes to an event and calls + * it once the event is emitted. + * + * @param condition The condition to test. + * @param emitter The emitter to use if the condition is not met. + * @param eventName The event to subscribe to if the condition is not met. + * @param action The function to call. + */ +function onConditionOrEvent(condition, emitter, eventName, action) { + if (condition === true) { + action(); + } + else { + 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); +} + + +/***/ }), + +/***/ 3717: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var concatMap = __webpack_require__(6891); +var balanced = __webpack_require__(9417); + +module.exports = expandTop; + +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +var escClose = '\0CLOSE'+Math.random()+'\0'; +var escComma = '\0COMMA'+Math.random()+'\0'; +var escPeriod = '\0PERIOD'+Math.random()+'\0'; + +function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) + : str.charCodeAt(0); +} + +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} + +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} + + +// Basically just str.split(","), but handling cases +// where we have nested braced sections, which should be +// treated as individual members, like {a,{b,c},d} +function parseCommaParts(str) { + if (!str) + return ['']; + + var parts = []; + var m = balanced('{', '}', str); + + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length-1] += postParts.shift(); + p.push.apply(p, postParts); + } + + parts.push.apply(parts, p); + + return parts; +} + +function expandTop(str) { + if (!str) + return []; + + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, + // but a{},b}c will be expanded to [a}c,abc]. + // One could argue that this is a bug in Bash, but since the goal of + // this module is to match Bash's rules, we escape a leading {} + if (str.substr(0, 2) === '{}') { + str = '\\{\\}' + str.substr(2); + } + + return expand(escapeBraces(str), true).map(unescapeBraces); +} + +function identity(e) { + return e; +} + +function embrace(str) { + return '{' + str + '}'; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} + +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} + +function expand(str, isTop) { + var expansions = []; + + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(',') >= 0; + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,.*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str); + } + return [str]; + } + + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } + } + + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. + + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.abs(numeric(n[2])) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { return expand(el, false) }); + } + + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } + } + + return expansions; +} + + + +/***/ }), + +/***/ 6891: +/***/ ((module) => { + +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); + } + return res; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + + +/***/ }), + +/***/ 7117: +/***/ ((module) => { + +module.exports = function (glob, opts) { + if (typeof glob !== 'string') { + throw new TypeError('Expected a string'); + } + + var str = String(glob); + + // The regexp we are building, as a string. + var reStr = ""; + + // Whether we are matching so called "extended" globs (like bash) and should + // support single character matching, matching ranges of characters, group + // matching, etc. + var extended = opts ? !!opts.extended : false; + + // When globstar is _false_ (default), '/foo/*' is translated a regexp like + // '^\/foo\/.*$' which will match any string beginning with '/foo/' + // When globstar is _true_, '/foo/*' is translated to regexp like + // '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT + // which does not have a '/' to the right of it. + // E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but + // these will not '/foo/bar/baz', '/foo/bar/baz.txt' + // Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when + // globstar is _false_ + var globstar = opts ? !!opts.globstar : false; + + // If we are doing extended matching, this boolean is true when we are inside + // a group (eg {*.html,*.js}), and false otherwise. + var inGroup = false; + + // RegExp flags (eg "i" ) to pass in to RegExp constructor. + var flags = opts && typeof( opts.flags ) === "string" ? opts.flags : ""; + + var c; + for (var i = 0, len = str.length; i < len; i++) { + c = str[i]; + + switch (c) { + case "/": + case "$": + case "^": + case "+": + case ".": + case "(": + case ")": + case "=": + case "!": + case "|": + reStr += "\\" + c; + break; + + case "?": + if (extended) { + reStr += "."; + break; + } + + case "[": + case "]": + if (extended) { + reStr += c; + break; + } + + case "{": + if (extended) { + inGroup = true; + reStr += "("; + break; + } + + case "}": + if (extended) { + inGroup = false; + reStr += ")"; + break; + } + + case ",": + if (inGroup) { + reStr += "|"; + break; + } + reStr += "\\" + c; + break; + + case "*": + // Move over all consecutive "*"'s. + // Also store the previous and next characters + var prevChar = str[i - 1]; + var starCount = 1; + while(str[i + 1] === "*") { + starCount++; + i++; + } + var nextChar = str[i + 1]; + + if (!globstar) { + // globstar is disabled, so treat any number of "*" as one + reStr += ".*"; + } else { + // globstar is enabled, so determine if this is a globstar segment + var isGlobstar = starCount > 1 // multiple "*"'s + && (prevChar === "/" || prevChar === undefined) // from the start of the segment + && (nextChar === "/" || nextChar === undefined) // to the end of the segment + + if (isGlobstar) { + // it's a globstar, so match zero or more path segments + reStr += "((?:[^/]*(?:\/|$))*)"; + i++; // move over the "/" + } else { + // it's not a globstar, so only match one path segment + reStr += "([^/]*)"; + } + } + break; + + default: + reStr += c; + } + } + + // When regexp 'g' flag is specified don't + // constrain the regular expression with ^ & $ + if (!flags || !~flags.indexOf('g')) { + reStr = "^" + reStr + "$"; + } + + return new RegExp(reStr, flags); +}; + + +/***/ }), + +/***/ 3973: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = minimatch +minimatch.Minimatch = Minimatch + +var path = { sep: '/' } +try { + path = __webpack_require__(5622) +} catch (er) {} + +var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} +var expand = __webpack_require__(3717) + +var plTypes = { + '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, + '?': { open: '(?:', close: ')?' }, + '+': { open: '(?:', close: ')+' }, + '*': { open: '(?:', close: ')*' }, + '@': { open: '(?:', close: ')' } +} + +// any single thing other than / +// don't need to escape / when using new RegExp() +var qmark = '[^/]' + +// * => any number of characters +var star = qmark + '*?' + +// ** when dots are allowed. Anything goes, except .. and . +// not (^ or / followed by one or two dots followed by $ or /), +// followed by anything, any number of times. +var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' + +// not a ^ or / followed by a dot, +// followed by anything, any number of times. +var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' + +// characters that need to be escaped in RegExp. +var reSpecials = charSet('().*{}+?[]^$\\!') + +// "abc" -> { a:true, b:true, c:true } +function charSet (s) { + return s.split('').reduce(function (set, c) { + set[c] = true + return set + }, {}) +} + +// normalizes slashes. +var slashSplit = /\/+/ + +minimatch.filter = filter +function filter (pattern, options) { + options = options || {} + return function (p, i, list) { + return minimatch(p, pattern, options) + } +} + +function ext (a, b) { + a = a || {} + b = b || {} + var t = {} + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) + Object.keys(a).forEach(function (k) { + t[k] = a[k] + }) + return t +} + +minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return minimatch + + var orig = minimatch + + var m = function minimatch (p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)) + } + + m.Minimatch = function Minimatch (pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)) + } + + return m +} + +Minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return Minimatch + return minimatch.defaults(def).Minimatch +} + +function minimatch (p, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + + // shortcut: comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + return false + } + + // "" only matches "" + if (pattern.trim() === '') return p === '' + + return new Minimatch(pattern, options).match(p) +} + +function Minimatch (pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options) + } + + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + pattern = pattern.trim() + + // windows support: need to use /, not \ + if (path.sep !== '/') { + pattern = pattern.split(path.sep).join('/') + } + + this.options = options + this.set = [] + this.pattern = pattern + this.regexp = null + this.negate = false + this.comment = false + this.empty = false + + // make the set of regexps etc. + this.make() +} + +Minimatch.prototype.debug = function () {} + +Minimatch.prototype.make = make +function make () { + // don't do it more than once. + if (this._made) return + + var pattern = this.pattern + var options = this.options + + // empty patterns and comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + this.comment = true + return + } + if (!pattern) { + this.empty = true + return + } + + // step 1: figure out negation, etc. + this.parseNegate() + + // step 2: expand braces + var set = this.globSet = this.braceExpand() + + if (options.debug) this.debug = console.error + + this.debug(this.pattern, set) + + // step 3: now we have a set, so turn each one into a series of path-portion + // matching patterns. + // These will be regexps, except in the case of "**", which is + // set to the GLOBSTAR object for globstar behavior, + // and will not contain any / characters + set = this.globParts = set.map(function (s) { + return s.split(slashSplit) + }) + + this.debug(this.pattern, set) + + // glob --> regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + + this.debug(this.pattern, set) + + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) + + this.debug(this.pattern, set) + + this.set = set +} + +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + + if (options.nonegate) return + + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } + + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} + +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} + +Minimatch.prototype.braceExpand = braceExpand + +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } + + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new TypeError('undefined pattern') + } + + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] + } + + return expand(pattern) +} + +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError('pattern is too long') + } + + var options = this.options + + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' + + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this + + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break + } + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false + } + } + + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue + } + + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false + + case '\\': + clearStateChar() + escaping = true + continue + + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } + + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue + + case '(': + if (inClass) { + re += '(' + continue + } + + if (!stateChar) { + re += '\\(' + continue + } + + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + var pl = patternListStack.pop() + // negation is (?:(?!js)[^/]*) + // The others are (?:) + re += pl.close + if (pl.type === '!') { + negativeLists.push(pl) + } + pl.reEnd = re.length + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue + + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() + + if (inClass) { + re += '\\' + c + continue + } + + inClass = true + classStart = i + reClassStart = re.length + re += c + continue + + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } + + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } + + // finish up the class. + hasMagic = true + inClass = false + re += c + continue + + default: + // swallow any state char that wasn't consumed + clearStateChar() + + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } + + re += c + + } // switch + } // for + + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] + } + + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length) + this.debug('setting tail', re, pl) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } + + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail, pl, re) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail + } + + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' + } + + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true + } + + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe + } + + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re + } + + if (addPatternStart) { + re = patternStart + re + } + + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } + + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } + + var flags = options.nocase ? 'i' : '' + try { + var regExp = new RegExp('^' + re + '$', flags) + } catch (er) { + // If it was an invalid regular expression, then it can't match + // anything. This trick looks for a character after the end of + // the string, which is of course impossible, except in multi-line + // mode, but it's not a /m regex. + return new RegExp('$.') + } + + regExp._glob = pattern + regExp._src = re + + return regExp +} + +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} + +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set + + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options + + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' + + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') + + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' + + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' + + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} + +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} + +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' + + if (f === '/' && partial) return true + + var options = this.options + + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } + + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) + + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. + + var set = this.set + this.debug(this.pattern, 'set', set) + + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } + + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate + } + } + + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate +} + +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options + + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) + + this.debug('matchOne', file.length, pattern.length) + + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] + + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false + } + return true + } + + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] + + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } + + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ + } + } + + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true + } + return false + } + + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) + } + + if (!hit) return false + } + + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* + + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd + } + + // should be unreachable. + throw new Error('wtf?') +} + +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') +} + +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +} + + +/***/ }), + +/***/ 4865: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; + +const minimatch = __webpack_require__(3973); +const arrayUnion = __webpack_require__(9600); +const arrayDiffer = __webpack_require__(6554); +const arrify = __webpack_require__(1546); + +module.exports = (list, patterns, options = {}) => { + list = arrify(list); + patterns = arrify(patterns); + + if (list.length === 0 || patterns.length === 0) { + return []; + } + + return patterns.reduce((result, pattern) => { + let process = arrayUnion; + + if (pattern[0] === '!') { + pattern = pattern.slice(1); + process = arrayDiffer; + } + + return process(result, minimatch.match(list, pattern, options)); + }, []); +}; + + +/***/ }), + +/***/ 7816: +/***/ ((module) => { + +"use strict"; + +module.exports = milliseconds => { + if (typeof milliseconds !== 'number') { + throw new TypeError('Expected a number'); + } + + const roundTowardsZero = milliseconds > 0 ? Math.floor : Math.ceil; + + return { + days: roundTowardsZero(milliseconds / 86400000), + hours: roundTowardsZero(milliseconds / 3600000) % 24, + minutes: roundTowardsZero(milliseconds / 60000) % 60, + seconds: roundTowardsZero(milliseconds / 1000) % 60, + milliseconds: roundTowardsZero(milliseconds) % 1000, + microseconds: roundTowardsZero(milliseconds * 1000) % 1000, + nanoseconds: roundTowardsZero(milliseconds * 1e6) % 1000 + }; +}; + + +/***/ }), + +/***/ 5168: +/***/ ((module) => { + +"use strict"; + + +const BYTE_UNITS = [ + 'B', + 'kB', + 'MB', + 'GB', + 'TB', + 'PB', + 'EB', + 'ZB', + 'YB' +]; + +const BIBYTE_UNITS = [ + 'B', + 'kiB', + 'MiB', + 'GiB', + 'TiB', + 'PiB', + 'EiB', + 'ZiB', + 'YiB' +]; + +const BIT_UNITS = [ + 'b', + 'kbit', + 'Mbit', + 'Gbit', + 'Tbit', + 'Pbit', + 'Ebit', + 'Zbit', + 'Ybit' +]; + +const BIBIT_UNITS = [ + 'b', + 'kibit', + 'Mibit', + 'Gibit', + 'Tibit', + 'Pibit', + 'Eibit', + 'Zibit', + 'Yibit' +]; + +/* +Formats the given number using `Number#toLocaleString`. +- If locale is a string, the value is expected to be a locale-key (for example: `de`). +- If locale is true, the system default locale is used for translation. +- If no value for locale is specified, the number is returned unmodified. +*/ +const toLocaleString = (number, locale) => { + let result = number; + if (typeof locale === 'string') { + result = number.toLocaleString(locale); + } else if (locale === true) { + result = number.toLocaleString(); + } + + return result; +}; + +module.exports = (number, options) => { + if (!Number.isFinite(number)) { + throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`); + } + + options = Object.assign({bits: false, binary: false}, options); + const UNITS = options.bits ? + (options.binary ? BIBIT_UNITS : BIT_UNITS) : + (options.binary ? BIBYTE_UNITS : BYTE_UNITS); + + if (options.signed && number === 0) { + return ' 0 ' + UNITS[0]; + } + + const isNegative = number < 0; + const prefix = isNegative ? '-' : (options.signed ? '+' : ''); + + if (isNegative) { + number = -number; + } + + if (number < 1) { + const numberString = toLocaleString(number, options.locale); + return prefix + numberString + ' ' + UNITS[0]; + } + + const exponent = Math.min(Math.floor(options.binary ? Math.log(number) / Math.log(1024) : Math.log10(number) / 3), UNITS.length - 1); + // eslint-disable-next-line unicorn/prefer-exponentiation-operator + number = Number((number / Math.pow(options.binary ? 1024 : 1000, exponent)).toPrecision(3)); + const numberString = toLocaleString(number, options.locale); + + const unit = UNITS[exponent]; + + return prefix + numberString + ' ' + unit; +}; + + +/***/ }), + +/***/ 1127: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; + +const parseMilliseconds = __webpack_require__(7816); + +const pluralize = (word, count) => count === 1 ? word : `${word}s`; + +const SECOND_ROUNDING_EPSILON = 0.0000001; + +module.exports = (milliseconds, options = {}) => { + if (!Number.isFinite(milliseconds)) { + throw new TypeError('Expected a finite number'); + } + + if (options.colonNotation) { + options.compact = false; + options.formatSubMilliseconds = false; + options.separateMilliseconds = false; + options.verbose = false; + } + + if (options.compact) { + options.secondsDecimalDigits = 0; + options.millisecondsDecimalDigits = 0; + } + + const result = []; + + const floorDecimals = (value, decimalDigits) => { + const flooredInterimValue = Math.floor((value * (10 ** decimalDigits)) + SECOND_ROUNDING_EPSILON); + const flooredValue = Math.round(flooredInterimValue) / (10 ** decimalDigits); + return flooredValue.toFixed(decimalDigits); + }; + + const add = (value, long, short, valueString) => { + if ((result.length === 0 || !options.colonNotation) && value === 0 && !(options.colonNotation && short === 'm')) { + return; + } + + valueString = (valueString || value || '0').toString(); + let prefix; + let suffix; + if (options.colonNotation) { + prefix = result.length > 0 ? ':' : ''; + suffix = ''; + const wholeDigits = valueString.includes('.') ? valueString.split('.')[0].length : valueString.length; + const minLength = result.length > 0 ? 2 : 1; + valueString = '0'.repeat(Math.max(0, minLength - wholeDigits)) + valueString; + } else { + prefix = ''; + suffix = options.verbose ? ' ' + pluralize(long, value) : short; + } + + result.push(prefix + valueString + suffix); + }; + + const parsed = parseMilliseconds(milliseconds); + + add(Math.trunc(parsed.days / 365), 'year', 'y'); + add(parsed.days % 365, 'day', 'd'); + add(parsed.hours, 'hour', 'h'); + add(parsed.minutes, 'minute', 'm'); + + if ( + options.separateMilliseconds || + options.formatSubMilliseconds || + (!options.colonNotation && milliseconds < 1000) + ) { + add(parsed.seconds, 'second', 's'); + if (options.formatSubMilliseconds) { + add(parsed.milliseconds, 'millisecond', 'ms'); + add(parsed.microseconds, 'microsecond', 'ยตs'); + add(parsed.nanoseconds, 'nanosecond', 'ns'); + } else { + const millisecondsAndBelow = + parsed.milliseconds + + (parsed.microseconds / 1000) + + (parsed.nanoseconds / 1e6); + + const millisecondsDecimalDigits = + typeof options.millisecondsDecimalDigits === 'number' ? + options.millisecondsDecimalDigits : + 0; + + const roundedMiliseconds = millisecondsAndBelow >= 1 ? + Math.round(millisecondsAndBelow) : + Math.ceil(millisecondsAndBelow); + + const millisecondsString = millisecondsDecimalDigits ? + millisecondsAndBelow.toFixed(millisecondsDecimalDigits) : + roundedMiliseconds; + + add( + Number.parseFloat(millisecondsString, 10), + 'millisecond', + 'ms', + millisecondsString + ); + } + } else { + const seconds = (milliseconds / 1000) % 60; + const secondsDecimalDigits = + typeof options.secondsDecimalDigits === 'number' ? + options.secondsDecimalDigits : + 1; + const secondsFixed = floorDecimals(seconds, secondsDecimalDigits); + const secondsString = options.keepDecimalsOnWholeSeconds ? + secondsFixed : + secondsFixed.replace(/\.0+$/, ''); + add(Number.parseFloat(secondsString, 10), 'second', 's', secondsString); + } + + if (result.length === 0) { + return '0' + (options.verbose ? ' milliseconds' : 'ms'); + } + + if (options.compact) { + return result[0]; + } + + if (typeof options.unitCount === 'number') { + const separator = options.colonNotation ? '' : ' '; + return result.slice(0, Math.max(options.unitCount, 1)).join(separator); + } + + return options.colonNotation ? result.join('') : result.join(' '); +}; + + +/***/ }), + +/***/ 399: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (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; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +const core = __importStar(__webpack_require__(2186)); +const ftp_deploy_1 = __webpack_require__(8347); +async function runDeployment() { + const args = { + server: core.getInput("server", { required: true }), + username: core.getInput("username", { required: true }), + password: core.getInput("password", { required: true }), + port: optionalInt("port", core.getInput("port")), + protocol: optionalProtocol("protocol", core.getInput("protocol")), + "local-dir": optionalString(core.getInput("local-dir")), + "server-dir": optionalString(core.getInput("server-dir")), + "state-name": optionalString(core.getInput("state-name")), + "dry-run": optionalBoolean("dry-run", core.getInput("dry-run")), + "dangerous-clean-slate": optionalBoolean("dangerous-clean-slate", core.getInput("dangerous-clean-slate")), + "exclude": optionalStringArray("exclude", core.getInput("exclude")), + "log-level": optionalLogLevel("log-level", core.getInput("log-level")), + "security": optionalSecurity("security", core.getInput("security")) + }; + try { + await ftp_deploy_1.deploy(args); + } + catch (error) { + core.setFailed(error); + } +} +runDeployment(); +function optionalString(rawValue) { + if (rawValue.length === 0) { + return undefined; + } + return rawValue; +} +function optionalBoolean(argumentName, rawValue) { + if (rawValue.length === 0) { + return undefined; + } + const cleanValue = rawValue.toLowerCase(); + if (cleanValue === "true") { + return true; + } + if (cleanValue === "false") { + return false; + } + core.setFailed(`${argumentName}: invalid parameter - please use a boolean, you provided "${rawValue}". Try true or false instead.`); +} +function optionalProtocol(argumentName, rawValue) { + if (rawValue.length === 0) { + return undefined; + } + const cleanValue = rawValue.toLowerCase(); + if (cleanValue === "ftp") { + return "ftp"; + } + if (cleanValue === "ftps") { + return "ftps"; + } + if (cleanValue === "ftps-legacy") { + return "ftps-legacy"; + } + core.setFailed(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "ftp", "ftps", or "ftps-legacy" instead.`); +} +function optionalLogLevel(argumentName, rawValue) { + if (rawValue.length === 0) { + return undefined; + } + const cleanValue = rawValue.toLowerCase(); + if (cleanValue === "minimal") { + return "minimal"; + } + if (cleanValue === "standard") { + return "standard"; + } + if (cleanValue === "verbose") { + return "verbose"; + } + core.setFailed(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "minimal", "standard", or "verbose" instead.`); +} +function optionalSecurity(argumentName, rawValue) { + if (rawValue.length === 0) { + return undefined; + } + const cleanValue = rawValue.toLowerCase(); + if (cleanValue === "loose") { + return "loose"; + } + if (cleanValue === "strict") { + return "strict"; + } + core.setFailed(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "loose" or "strict" instead.`); +} +function optionalInt(argumentName, rawValue) { + if (rawValue.length === 0) { + return undefined; + } + const valueAsNumber = parseFloat(rawValue); + if (Number.isInteger(valueAsNumber)) { + return valueAsNumber; + } + core.setFailed(`${argumentName}: invalid parameter - you provided "${rawValue}". Try a whole number (no decimals) instead like 1234`); +} +function optionalStringArray(argumentName, rawValue) { + if (rawValue.length === 0) { + return undefined; + } + // split value by space and comma + return rawValue.split(", "); +} + + +/***/ }), + +/***/ 6417: +/***/ ((module) => { + +"use strict"; +module.exports = require("crypto"); + +/***/ }), + +/***/ 5747: +/***/ ((module) => { + +"use strict"; +module.exports = require("fs"); + +/***/ }), + +/***/ 1631: +/***/ ((module) => { + +"use strict"; +module.exports = require("net"); + +/***/ }), + +/***/ 2087: +/***/ ((module) => { + +"use strict"; +module.exports = require("os"); + +/***/ }), + +/***/ 5622: +/***/ ((module) => { + +"use strict"; +module.exports = require("path"); + +/***/ }), + +/***/ 2413: +/***/ ((module) => { + +"use strict"; +module.exports = require("stream"); + +/***/ }), + +/***/ 4016: +/***/ ((module) => { + +"use strict"; +module.exports = require("tls"); + +/***/ }), + +/***/ 1669: +/***/ ((module) => { + +"use strict"; +module.exports = require("util"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ var threw = true; +/******/ try { +/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ threw = false; +/******/ } finally { +/******/ if(threw) delete __webpack_module_cache__[moduleId]; +/******/ } +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat */ +/******/ +/******/ __webpack_require__.ab = __dirname + "/";/************************************************************************/ +/******/ // module exports must be returned from runtime so entry inlining is disabled +/******/ // startup +/******/ // Load entry module and return exports +/******/ return __webpack_require__(399); +/******/ })() +; \ No newline at end of file diff --git a/dist/main.js b/dist/main.js deleted file mode 100644 index dcf35d6..0000000 --- a/dist/main.js +++ /dev/null @@ -1,128 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (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; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const core = __importStar(require("@actions/core")); -const ftp_deploy_1 = require("@samkirkland/ftp-deploy"); -async function runDeployment() { - const args = { - server: core.getInput("server", { required: true }), - username: core.getInput("username", { required: true }), - password: core.getInput("password", { required: true }), - port: optionalInt("port", core.getInput("port")), - protocol: optionalProtocol("protocol", core.getInput("protocol")), - "local-dir": optionalString(core.getInput("local-dir")), - "server-dir": optionalString(core.getInput("server-dir")), - "state-name": optionalString(core.getInput("state-name")), - "dry-run": optionalBoolean("dry-run", core.getInput("dry-run")), - "dangerous-clean-slate": optionalBoolean("dangerous-clean-slate", core.getInput("dangerous-clean-slate")), - "exclude": optionalStringArray("exclude", core.getInput("exclude")), - "log-level": optionalLogLevel("log-level", core.getInput("log-level")), - "security": optionalSecurity("security", core.getInput("security")) - }; - try { - await ftp_deploy_1.deploy(args); - } - catch (error) { - core.setFailed(error); - } -} -runDeployment(); -function optionalString(rawValue) { - if (rawValue.length === 0) { - return undefined; - } - return rawValue; -} -function optionalBoolean(argumentName, rawValue) { - if (rawValue.length === 0) { - return undefined; - } - const cleanValue = rawValue.toLowerCase(); - if (cleanValue === "true") { - return true; - } - if (cleanValue === "false") { - return false; - } - core.setFailed(`${argumentName}: invalid parameter - please use a boolean, you provided "${rawValue}". Try true or false instead.`); -} -function optionalProtocol(argumentName, rawValue) { - if (rawValue.length === 0) { - return undefined; - } - const cleanValue = rawValue.toLowerCase(); - if (cleanValue === "ftp") { - return "ftp"; - } - if (cleanValue === "ftps") { - return "ftps"; - } - if (cleanValue === "ftps-legacy") { - return "ftps-legacy"; - } - core.setFailed(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "ftp", "ftps", or "ftps-legacy" instead.`); -} -function optionalLogLevel(argumentName, rawValue) { - if (rawValue.length === 0) { - return undefined; - } - const cleanValue = rawValue.toLowerCase(); - if (cleanValue === "minimal") { - return "minimal"; - } - if (cleanValue === "standard") { - return "standard"; - } - if (cleanValue === "verbose") { - return "verbose"; - } - core.setFailed(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "minimal", "standard", or "verbose" instead.`); -} -function optionalSecurity(argumentName, rawValue) { - if (rawValue.length === 0) { - return undefined; - } - const cleanValue = rawValue.toLowerCase(); - if (cleanValue === "loose") { - return "loose"; - } - if (cleanValue === "strict") { - return "strict"; - } - core.setFailed(`${argumentName}: invalid parameter - you provided "${rawValue}". Try "loose" or "strict" instead.`); -} -function optionalInt(argumentName, rawValue) { - if (rawValue.length === 0) { - return undefined; - } - const valueAsNumber = parseFloat(rawValue); - if (Number.isInteger(valueAsNumber)) { - return valueAsNumber; - } - core.setFailed(`${argumentName}: invalid parameter - you provided "${rawValue}". Try a whole number (no decimals) instead like 1234`); -} -function optionalStringArray(argumentName, rawValue) { - if (rawValue.length === 0) { - return undefined; - } - // split value by space and comma - return rawValue.split(", "); -} diff --git a/package-lock.json b/package-lock.json index 1f50213..11838da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -240,6 +240,12 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@vercel/ncc": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.24.1.tgz", + "integrity": "sha512-r9m7brz2hNmq5TF3sxrK4qR/FhXn44XIMglQUir4sT7Sh5GOaYXlMYikHFwJStf8rmQGTlvOoBXt4yHVonRG8A==", + "dev": true + }, "acorn": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", diff --git a/package.json b/package.json index a510176..7f2c2b6 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,9 @@ "version": "1.0.1", "private": true, "description": "Automate deploying websites and more with this GitHub action", - "main": "dist/main.js", + "main": "dist/index.js", "scripts": { - "build": "tsc", + "build": "ncc build src/main.ts --no-cache", "lint": "eslint src/**/*.ts" }, "repository": { @@ -28,6 +28,7 @@ "devDependencies": { "@types/node": "^14.0.27", "@typescript-eslint/parser": "^4.0.1", + "@vercel/ncc": "^0.24.0", "eslint": "^7.8.1", "js-yaml": "^3.14.0", "typescript": "^4.0.2" diff --git a/tsconfig.json b/tsconfig.json index 673198f..f7ae47f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "strict": true, /* Enable all strict type-checking options. */ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "noEmit": true }, "exclude": [ "node_modules"