From 4d3134afb84755577a590b89bd00810d73f95227 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Sat, 16 Oct 2021 06:10:36 -0400 Subject: [PATCH] BREAKING: allow hashes provided as a URL to a text file or a string (closes #1) --- README.md | 42 ++++++++++---- index.js | 139 +++++++++++++++++++++++---------------------- lib/checksum.js | 97 +++++++++++++++++++++++++++++++ lib/debug.js | 4 ++ lib/download.js | 20 +++++++ package.json | 13 ++--- test/index.spec.js | 138 ++++++++++++++++++++++++++++---------------- yarn.lock | 66 ++++++--------------- 8 files changed, 336 insertions(+), 183 deletions(-) create mode 100644 lib/checksum.js create mode 100644 lib/debug.js create mode 100644 lib/download.js diff --git a/README.md b/README.md index 46777ea..fdff17d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![npm](https://img.shields.io/npm/v/careful-downloader?logo=npm)](https://www.npmjs.com/package/careful-downloader) [![MIT License](https://img.shields.io/github/license/jakejarvis/careful-downloader?color=red)](LICENSE) -Downloads a file and its checksums to a temporary directory, validates the hash, and optionally extracts it if safe. A headache-averting wrapper around [`got`](https://github.com/sindresorhus/got), [`sumchecker`](https://github.com/malept/sumchecker), and [`decompress`](https://github.com/kevva/decompress). +Downloads a file and its checksums to a temporary directory, validates the hash, and optionally extracts it if safe. ## Install @@ -21,20 +21,34 @@ import downloader from "careful-downloader"; await downloader( "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_extended_0.88.1_Windows-64bit.zip", - "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", { + checksumUrl: "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", destDir: "./vendor", algorithm: "sha256", - encoding: "binary", extract: true, }, ); //=> '/Users/jake/src/carefully-downloaded/vendor/hugo.exe' ``` +Instead of `options.checksumUrl`, you can also simply provide a hash as a string via `options.checksumHash`. + +```js +await downloader( + "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_extended_0.88.1_Windows-64bit.zip", + { + checksumHash: "aaa20e258cd668cff66400d365d73ddc375e44487692d49a5285b56330f6e6b2", + destDir: "./vendor", + algorithm: "sha256", + extract: false, // default + }, +); +//=> '/Users/jake/src/carefully-downloaded/vendor/hugo_extended_0.88.1_Windows-64bit.zip' +``` + ## API -### downloader(downloadUrl, checksumUrl, options?) +### downloader(downloadUrl, options) #### downloadUrl @@ -42,7 +56,11 @@ Type: `string` Absolute URL to the desired file to download. -#### checksumUrl +#### options + +Type: `object` + +##### checksumUrl Type: `string` @@ -54,9 +72,15 @@ ad81192d188cb584a73074d3dea9350d4609a13ed5fccaafd229b424247e5890 hugo_0.88.1_Wi aaa20e258cd668cff66400d365d73ddc375e44487692d49a5285b56330f6e6b2 hugo_extended_0.88.1_Windows-64bit.zip ``` -#### options +**Either this option or `checksumHash` is required.** -Type: `object` +##### checksumHash + +Type: `string` + +A single hash for the given downloaded file, e.g. `abcd1234abcd1234abcd1234...`. + +**Either this option or `checksumUrl` is required.** ##### filename @@ -98,9 +122,7 @@ On recent releases of OpenSSL, `openssl list -digest-algorithms` will display th ##### encoding Type: `string`\ -Default: `"binary"` - -Tell the file stream to read the download as a binary, UTF-8 text file, base64, etc. +Default: `"hex"` ## License diff --git a/index.js b/index.js index 0749d60..f5acd94 100644 --- a/index.js +++ b/index.js @@ -1,33 +1,44 @@ import path from "path"; -import stream from "stream"; -import { promisify } from "util"; -import createDebug from "debug"; import fs from "fs-extra"; import tempy from "tempy"; -import got from "got"; -import sumchecker from "sumchecker"; import decompress from "decompress"; -import urlParse from "url-parse"; -import isPathInCwd from "is-path-in-cwd"; +import isPathInside from "is-path-inside"; -// set DEBUG=careful-downloader in environment to enable detailed logging -const debug = new createDebug("careful-downloader"); +import debug from "./lib/debug.js"; +import download from "./lib/download.js"; +import { checksumViaFile, checksumViaString } from "./lib/checksum.js"; -export default async function downloader(downloadUrl, checksumUrl, options = {}) { - // normalize options and set defaults +export default async (downloadUrl, options = {}) => { debug(`User-provided config: ${JSON.stringify(options)}`); + + let checksumMethod; + let checksumKey; + if (options.checksumUrl) { + // download and use checksum text file to parse and check + checksumMethod = "file"; + checksumKey = options.checksumUrl; + } else if (options.checksumHash) { + // simply compare hash of file to provided string + checksumMethod = "string"; + checksumKey = options.checksumHash; + } else { + throw new Error("must either provide checksumUrl or checksumHash."); + } + debug(`Provided a ${checksumMethod} to validate against: ${checksumKey}`); + + // normalize options and set defaults options = { - filename: options.filename || urlParse(downloadUrl).pathname.split("/").pop(), + filename: options.filename || new URL(downloadUrl).pathname.split("/").pop(), extract: !!options.extract, destDir: options.destDir ? path.resolve(process.cwd(), options.destDir) : path.resolve(process.cwd(), "downloads"), cleanDestDir: !!options.cleanDestDir, algorithm: options.algorithm || "sha256", - encoding: options.encoding || "binary", + encoding: options.encoding || "hex", }; debug(`Normalized config with defaults: ${JSON.stringify(options)}`); // throw an error if destDir is outside of the module to prevent path traversal for security reasons - if (!isPathInCwd(options.destDir)) { + if (!isPathInside(options.destDir, process.cwd())) { throw new Error(`destDir must be located within '${process.cwd()}', it's currently set to '${options.destDir}'.`); } @@ -36,70 +47,60 @@ export default async function downloader(downloadUrl, checksumUrl, options = {}) debug(`Temp dir generated: '${tempDir}'`); try { - // simultaneously download the desired file and its checksums - await Promise.all([ - downloadFile(downloadUrl, path.join(tempDir, options.filename)), - downloadFile(checksumUrl, path.join(tempDir, "checksums.txt")), - ]); + // get the desired file + await download(downloadUrl, path.join(tempDir, options.filename)); // validate the checksum of the download - if (await checkChecksum(tempDir, options.filename, "checksums.txt", options.algorithm, options.encoding)) { - // optionally clear the target directory of existing files - if (options.cleanDestDir && fs.existsSync(options.destDir)) { - debug(`Deleting contents of '${options.destDir}'`); - await fs.remove(options.destDir); - } + let validated = false; - // ensure the target directory exists - debug(`Ensuring target '${options.destDir}' exists`); - await fs.mkdirp(options.destDir); + if (checksumMethod === "file") { + debug("Using a downloaded checksum file to validate..."); - if (options.extract) { - // decompress download and move resulting files to final destination - debug(`Extracting '${options.filename}' to '${options.destDir}'`); - await decompress(path.join(tempDir, options.filename), options.destDir); - return options.destDir; - } else { - // move verified download to final destination as-is - debug(`Not told to extract; copying '${options.filename}' as-is to '${path.join(options.destDir, options.filename)}'`); - await fs.copy(path.join(tempDir, options.filename), path.join(options.destDir, options.filename)); - return path.join(options.destDir, options.filename); + const checksumFilename = new URL(checksumKey).pathname.split("/").pop(); + await download(checksumKey, path.join(tempDir, checksumFilename)); + + // eslint-disable-next-line max-len + if (await checksumViaFile(path.join(tempDir, options.filename), path.join(tempDir, checksumFilename), options.algorithm, options.encoding)) { + validated = true; } - } else { + } else if (checksumMethod === "string") { + debug("Using a provided hash to validate..."); + + // eslint-disable-next-line max-len + if (await checksumViaString(path.join(tempDir, options.filename), checksumKey, options.algorithm, options.encoding)) { + validated = true; + } + } + + // stop here if the checksum wasn't validated by either method + if (!validated) { throw new Error(`Invalid checksum for '${options.filename}'.`); } + + // optionally clear the target directory of existing files + if (options.cleanDestDir && fs.existsSync(options.destDir)) { + debug(`Deleting contents of '${options.destDir}'`); + await fs.remove(options.destDir); + } + + // ensure the target directory exists + debug(`Ensuring target '${options.destDir}' exists`); + await fs.mkdirp(options.destDir); + + if (options.extract) { + // decompress download and move resulting files to final destination + debug(`Extracting '${options.filename}' to '${options.destDir}'`); + await decompress(path.join(tempDir, options.filename), options.destDir); + return options.destDir; + } else { + // move verified download to final destination as-is + debug(`Not told to extract; copying '${options.filename}' as-is to '${path.join(options.destDir, options.filename)}'`); + await fs.copy(path.join(tempDir, options.filename), path.join(options.destDir, options.filename)); + return path.join(options.destDir, options.filename); + } } finally { // delete temporary directory debug(`Deleting temp dir: '${tempDir}'`); await fs.remove(tempDir); } -} - -// Download any file to any destination. Returns a promise. -async function downloadFile(url, dest) { - debug(`Downloading '${url}' to '${dest}'`); - - // get remote file and write locally - const pipeline = promisify(stream.pipeline); - const download = await pipeline( - got.stream(url, { followRedirect: true }), // GitHub releases redirect to unpredictable URLs - fs.createWriteStream(dest), - ); - - return download; -} - -// Check da checksum. -async function checkChecksum(baseDir, downloadFile, checksumFile, algorithm, encoding) { - debug(`Validating checksum of '${downloadFile}' (hash: '${algorithm}', encoding: '${encoding}')`); - - // instantiate checksum validator - const checker = new sumchecker.ChecksumValidator(algorithm, path.join(baseDir, checksumFile), { - defaultTextEncoding: encoding, - }); - - // finally test the file - const valid = await checker.validate(baseDir, downloadFile); - - return valid; -} +}; diff --git a/lib/checksum.js b/lib/checksum.js new file mode 100644 index 0000000..4c45754 --- /dev/null +++ b/lib/checksum.js @@ -0,0 +1,97 @@ +import path from "path"; +import crypto from "crypto"; +import fs from "fs-extra"; + +import debug from "./debug.js"; + +// Check the checksum via a parsed text file containing one or more hashes. +export const checksumViaFile = async (desiredFile, checksumFile, algorithm, encoding) => { + debug(`Validating checksum of '${desiredFile}' against hashes listed in '${checksumFile}' (algo: '${algorithm}', encoding: '${encoding}')`); + + const parsedHashes = await parseChecksumFile(checksumFile); + debug(`All hashes pulled from the checksums file '${path.basename(checksumFile)}' : ${JSON.stringify(parsedHashes)}`); + + const generatedHash = await generateHashFromFile(desiredFile, algorithm, encoding); + debug(`Generated a ${algorithm} hash of '${path.basename(desiredFile)}' => ${generatedHash}`); + + const correctHash = parsedHashes[path.basename(desiredFile)]; + debug(`Found '${path.basename(desiredFile)}' in '${path.basename(checksumFile)}', provided hash is: ${correctHash}`); + + if (!correctHash) { + throw new Error(`'${path.basename(desiredFile)}' isn't listed in checksums file.`); + } + + debug(`Checking if generated === provided... ${generatedHash} : ${correctHash}`); + if (generatedHash !== correctHash) { + debug(`Nope... ${generatedHash} !== ${correctHash}`); + throw new Error(`Hash of '${path.basename(desiredFile)}' doesn't match the given checksum.`); + } + + debug("Checksum validated via file, it's a match!"); + return true; +}; + +// Check the checksum via a provided hash. +export const checksumViaString = async (desiredFile, correctHash, algorithm, encoding) => { + debug(`Validating checksum of '${desiredFile}' against provided hash '${correctHash}' (algo: '${algorithm}', encoding: '${encoding}')`); + + const generatedHash = await generateHashFromFile(desiredFile, algorithm, encoding); + debug(`Generated a ${algorithm} hash of '${path.basename(desiredFile)}' => ${generatedHash}`); + + debug(`Checking if generated === provided... ${generatedHash} : ${correctHash}`); + if (generatedHash !== correctHash) { + debug(`Nope... ${generatedHash} !== ${correctHash}`); + throw new Error(`Hash of '${path.basename(desiredFile)}' doesn't match the given checksum.`); + } + + debug("Checksum validated via string, it's a match!"); + return true; +}; + +// Takes a path to a file and returns its hash. +const generateHashFromFile = async (file, algorithm, encoding) => { + const fileBuffer = fs.readFileSync(file); + const hashSum = crypto.createHash(algorithm); + hashSum.update(fileBuffer); + + return hashSum.digest(encoding); +}; + +// Largely adapted from sumchecker: +// https://github.com/malept/sumchecker/blob/28aed640a02787490d033fda56eaee30e24e5a71/src/index.ts#L97 +const parseChecksumFile = async (checksumFile) => { + // read the text file holding one or more checksums + const data = fs.readFileSync(checksumFile, { encoding: "utf8" }); + + // https://regexr.com/67k7i + const lineRegex = /^([\da-fA-F]+) ([ *])(.+)$/; + + // extract each file and its hash into this object + const checksums = {}; + + // loop through each line (should be one file & hash each) + let lineNumber = 0; + for (const line of data.trim().split(/[\r\n]+/)) { + lineNumber += 1; + + // parse the current line using the regex pattern above + const parsedLine = lineRegex.exec(line); + + if (parsedLine === null) { + // didn't match regex + debug(`Could not parse line number ${lineNumber}`); + throw new Error(lineNumber, line); + } else { + parsedLine.shift(); + + // eslint-disable-next-line no-unused-vars + const [hash, binary, file] = parsedLine; + + // save the current file & hash in the checksums object + checksums[file] = hash; + } + } + + // send back the cleaned up object of filenames & hashes + return checksums; +}; diff --git a/lib/debug.js b/lib/debug.js new file mode 100644 index 0000000..900b49d --- /dev/null +++ b/lib/debug.js @@ -0,0 +1,4 @@ +import debug from "debug"; + +// set DEBUG=careful-downloader in environment to enable detailed logging +export default debug("careful-downloader"); diff --git a/lib/download.js b/lib/download.js new file mode 100644 index 0000000..c7cfe7f --- /dev/null +++ b/lib/download.js @@ -0,0 +1,20 @@ +import stream from "stream"; +import { promisify } from "util"; +import fs from "fs-extra"; +import got from "got"; + +import debug from "./debug.js"; + +// Download any file to any destination. Returns a promise. +export default async (url, dest) => { + debug(`Downloading '${url}' to '${dest}'`); + + // get remote file and write locally + const pipeline = promisify(stream.pipeline); + const download = pipeline( + got.stream(url, { followRedirect: true }), // GitHub releases redirect to unpredictable URLs + fs.createWriteStream(dest), + ); + + return download; +}; diff --git a/package.json b/package.json index 5aa709a..c3b12d5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "types": "./index.d.ts", "files": [ "index.js", - "index.d.ts" + "index.d.ts", + "lib" ], "scripts": { "test": "eslint . && mocha" @@ -28,10 +29,8 @@ "decompress": "^4.2.1", "fs-extra": "^10.0.0", "got": "^11.8.2", - "is-path-in-cwd": "^4.0.0", - "sumchecker": "^3.0.1", - "tempy": "^2.0.0", - "url-parse": "^1.5.3" + "is-path-inside": "^4.0.0", + "tempy": "^2.0.0" }, "devDependencies": { "@jakejarvis/eslint-config": "*", @@ -40,8 +39,8 @@ "@types/fs-extra": "^9.0.13", "@types/url-parse": "^1.4.4", "chai": "^4.3.4", - "eslint": "^8.0.0", - "mocha": "^9.1.2" + "eslint": "^8.0.1", + "mocha": "^9.1.3" }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" diff --git a/test/index.spec.js b/test/index.spec.js index ed0ec80..ed4795a 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -4,74 +4,116 @@ import path from "path"; import { fileURLToPath } from "url"; import { expect } from "chai"; -import downloader from "../index.js"; +import download from "../index.js"; -// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#what-do-i-use-instead-of-__dirname-and-__filename const __dirname = path.dirname(fileURLToPath(import.meta.url)); -it("verified checksum, hugo.exe was extracted", async function () { - this.timeout(30000); // increase timeout to an excessive 30 seconds for CI +describe("checksum via downloaded text file", function () { + it("verified checksum, hugo.exe was extracted", async function () { + this.timeout(30000); // increase timeout to an excessive 30 seconds for CI - await downloader( - "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_extended_0.88.1_Windows-64bit.zip", - "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", - { - destDir: path.join(__dirname, "temp"), - algorithm: "sha256", - encoding: "binary", - extract: true, - }, - ); - - expect(fs.existsSync(path.join(__dirname, "temp", "hugo.exe"))).to.be.true; - - // clean up - fs.removeSync(path.join(__dirname, "temp")); -}); - -it("incorrect checksum, not extracted", async function () { - this.timeout(30000); // increase timeout to an excessive 30 seconds for CI - - expect(async () => downloader( - // download mismatching versions to trigger error - "https://github.com/gohugoio/hugo/releases/download/v0.88.0/hugo_0.88.0_Windows-64bit.zip", - "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", + await download( + "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_extended_0.88.1_Windows-64bit.zip", { + checksumUrl: "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", + destDir: path.join(__dirname, "temp"), + algorithm: "sha256", + extract: true, + }, + ); + + expect(fs.existsSync(path.join(__dirname, "temp", "hugo.exe"))).to.be.true; + + // clean up + fs.removeSync(path.join(__dirname, "temp")); + }); + + it("incorrect checksum file, not extracted", async function () { + this.timeout(30000); // increase timeout to an excessive 30 seconds for CI + + expect(async () => download( + // download mismatching versions to trigger error + "https://github.com/gohugoio/hugo/releases/download/v0.88.0/hugo_0.88.0_Windows-64bit.zip", + { + checksumUrl: "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", destDir: path.join(__dirname, "temp"), algorithm: "sha256", - encoding: "binary", extract: false, }, - )).to.throw; + )).to.throw; - expect(fs.existsSync(path.join(__dirname, "temp", "hugo.exe"))).to.be.false; + expect(fs.existsSync(path.join(__dirname, "temp", "hugo.exe"))).to.be.false; - // clean up - fs.removeSync(path.join(__dirname, "temp")); -}); + // clean up + fs.removeSync(path.join(__dirname, "temp")); + }); -it("destDir located outside of module, throw error", async function () { - this.timeout(30000); // increase timeout to an excessive 30 seconds for CI + it("destDir located outside of module, throw error", async function () { + this.timeout(30000); // increase timeout to an excessive 30 seconds for CI - expect(async () => downloader( + expect(async () => download( "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_Windows-64bit.zip", - "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", { + checksumUrl: "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", destDir: "../vendor", // invalid path }, - )).to.throw; + )).to.throw; + }); + + it("zero options, download zip and leave it alone", async function () { + this.timeout(30000); // increase timeout to an excessive 30 seconds for CI + + await download( + "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_extended_0.88.1_Windows-64bit.zip", + { + checksumUrl: "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", + }, + ); + + expect(fs.existsSync(path.join(__dirname, "../downloads", "hugo_extended_0.88.1_Windows-64bit.zip"))).to.be.true; + + // clean up + fs.removeSync(path.join(__dirname, "../downloads")); + }); }); -it("zero options, download zip and leave it alone", async function () { - this.timeout(30000); // increase timeout to an excessive 30 seconds for CI +describe("checksum via string", function () { + it("verified checksum, hugo.exe was extracted", async function () { + this.timeout(30000); // increase timeout to an excessive 30 seconds for CI - await downloader( - "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_extended_0.88.1_Windows-64bit.zip", - "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_0.88.1_checksums.txt", - ); + await download( + "https://github.com/gohugoio/hugo/releases/download/v0.88.1/hugo_extended_0.88.1_Windows-64bit.zip", + { + checksumHash: "aaa20e258cd668cff66400d365d73ddc375e44487692d49a5285b56330f6e6b2", + destDir: path.join(__dirname, "temp"), + algorithm: "sha256", + extract: true, + }, + ); - expect(fs.existsSync(path.join(__dirname, "../downloads", "hugo_extended_0.88.1_Windows-64bit.zip"))).to.be.true; + expect(fs.existsSync(path.join(__dirname, "temp", "hugo.exe"))).to.be.true; - // clean up - fs.removeSync(path.join(__dirname, "../downloads")); + // clean up + fs.removeSync(path.join(__dirname, "temp")); + }); + + it("incorrect checksum string, not extracted", async function () { + this.timeout(30000); // increase timeout to an excessive 30 seconds for CI + + expect(async () => download( + // download mismatching versions to trigger error + "https://github.com/gohugoio/hugo/releases/download/v0.88.0/hugo_0.88.0_Windows-64bit.zip", + { + checksumHash: "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234", + destDir: path.join(__dirname, "temp"), + algorithm: "sha256", + extract: false, + }, + )).to.throw; + + expect(fs.existsSync(path.join(__dirname, "temp", "hugo.exe"))).to.be.false; + + // clean up + fs.removeSync(path.join(__dirname, "temp")); + }); }); diff --git a/yarn.lock b/yarn.lock index 979e322..1f7a695 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@eslint/eslintrc@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.2.tgz#6044884f7f93c4ecc2d1694c7486cce91ef8f746" - integrity sha512-x1ZXdEFsvTcnbTZgqcWUL9w2ybgZCw/qbKTPQnab+XnYA2bMQpJCh+/bBzCRfDJaJdlrrQlOk49jNtru9gL/6Q== +"@eslint/eslintrc@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.3.tgz#41f08c597025605f672251dcc4e8be66b5ed7366" + integrity sha512-DHI1wDPoKCBPoLZA3qDR91+3te/wDSc1YhKg3jR8NxKKRJq2hwHwcWv31cSwSYvIBrmbENoYMWcenW8uproQqg== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -118,9 +118,9 @@ integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== "@types/node@*": - version "16.10.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.4.tgz#592f12b0b5f357533ddc3310b0176d42ea3e45d1" - integrity sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA== + version "16.11.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.0.tgz#4b95f2327bacd1ef8f08d8ceda193039c5d7f52e" + integrity sha512-8MLkBIYQMuhRBQzGN9875bYsOhPnf/0rgXGo66S2FemHkhbn9qtsz9ywV1iCG+vbjigE4WUNVvw37Dx+L0qsPg== "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" @@ -420,7 +420,7 @@ crypto-random-string@^4.0.0: dependencies: type-fest "^1.0.1" -debug@4.3.2, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2: +debug@4.3.2, debug@^4.1.1, debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -596,12 +596,12 @@ eslint-visitor-keys@^3.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== -eslint@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.0.0.tgz#2c2d0ac6353755667ac90c9ff4a9c1315e43fcff" - integrity sha512-03spzPzMAO4pElm44m60Nj08nYonPGQXmw6Ceai/S4QK82IgwWO1EXx1s9namKzVlbVu3Jf81hb+N+8+v21/HQ== +eslint@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.0.1.tgz#3610e7fe4a05c2154669515ca60835a76a19f700" + integrity sha512-LsgcwZgQ72vZ+SMp4K6pAnk2yFDWL7Ti4pJaRvsZ0Hsw2h8ZjUIW38a9AFn2cZXdBMlScMFYYgsSp4ttFI/0bA== dependencies: - "@eslint/eslintrc" "^1.0.2" + "@eslint/eslintrc" "^1.0.3" "@humanwhocodes/config-array" "^0.6.0" ajv "^6.10.0" chalk "^4.0.0" @@ -1019,13 +1019,6 @@ is-path-cwd@^2.2.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-in-cwd@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-4.0.0.tgz#e5a97a09cf34655d7bab7204e6c3997d834ef0b6" - integrity sha512-FjDhtYysbIKBKRFCQN8NcMaHHWfwAzJLOrRAhzd4hnK6Y1979p6ZthIUqdPjCyAk5jvrmY2fn56Y+kFE8RdsrA== - dependencies: - is-path-inside "^4.0.0" - is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -1189,10 +1182,10 @@ minimatch@3.0.4, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -mocha@^9.1.2: - version "9.1.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.2.tgz#93f53175b0f0dc4014bd2d612218fccfcf3534d3" - integrity sha512-ta3LtJ+63RIBP03VBjMGtSqbe6cWXRejF9SyM9Zyli1CKZJZ+vfCTj3oW24V7wAphMJdpOFLoMI3hjJ1LWbs0w== +mocha@^9.1.3: + version "9.1.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.3.tgz#8a623be6b323810493d8c8f6f7667440fa469fdb" + integrity sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" @@ -1391,11 +1384,6 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -1443,11 +1431,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - resolve-alpn@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -1572,13 +1555,6 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1. resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -sumchecker@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" - integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== - dependencies: - debug "^4.1.0" - supports-color@8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -1698,14 +1674,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -url-parse@^1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"