1
mirror of https://github.com/jakejarvis/careful-downloader.git synced 2025-04-26 06:35:22 -04:00

BREAKING: allow hashes provided as a URL to a text file or a string

(closes #1)
This commit is contained in:
Jake Jarvis 2021-10-16 06:10:36 -04:00
parent bcd2e89f7d
commit 4d3134afb8
Signed by: jake
GPG Key ID: 2B0C9CF251E69A39
8 changed files with 336 additions and 183 deletions

View File

@ -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

107
index.js
View File

@ -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,14 +47,36 @@ 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)) {
let validated = false;
if (checksumMethod === "file") {
debug("Using a downloaded checksum file to validate...");
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 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}'`);
@ -65,41 +98,9 @@ export default async function downloader(downloadUrl, checksumUrl, options = {})
await fs.copy(path.join(tempDir, options.filename), path.join(options.destDir, options.filename));
return path.join(options.destDir, options.filename);
}
} else {
throw new Error(`Invalid checksum for '${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;
}
};

97
lib/checksum.js Normal file
View File

@ -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;
};

4
lib/debug.js Normal file
View File

@ -0,0 +1,4 @@
import debug from "debug";
// set DEBUG=careful-downloader in environment to enable detailed logging
export default debug("careful-downloader");

20
lib/download.js Normal file
View File

@ -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;
};

View File

@ -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"

View File

@ -4,21 +4,20 @@ 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 () {
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(
await download(
"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: path.join(__dirname, "temp"),
algorithm: "sha256",
encoding: "binary",
extract: true,
},
);
@ -27,19 +26,18 @@ it("verified checksum, hugo.exe was extracted", async function () {
// clean up
fs.removeSync(path.join(__dirname, "temp"));
});
});
it("incorrect checksum, not extracted", async function () {
it("incorrect checksum file, not extracted", async function () {
this.timeout(30000); // increase timeout to an excessive 30 seconds for CI
expect(async () => downloader(
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",
"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: path.join(__dirname, "temp"),
algorithm: "sha256",
encoding: "binary",
extract: false,
},
)).to.throw;
@ -48,30 +46,74 @@ it("incorrect checksum, not extracted", async function () {
// clean up
fs.removeSync(path.join(__dirname, "temp"));
});
});
it("destDir located outside of module, throw error", async function () {
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;
});
});
it("zero options, download zip and leave it alone", async function () {
it("zero options, download zip and leave it alone", async function () {
this.timeout(30000); // increase timeout to an excessive 30 seconds for CI
await downloader(
await download(
"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",
},
);
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"));
});
});
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 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, "temp", "hugo.exe"))).to.be.true;
// 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"));
});
});

View File

@ -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"