diff --git a/README.md b/README.md index 582288fa..961d2023 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# skynet-js - Javascript Sia Skynet Client +# skynet-js - Javascript Sia Skynet SDK [![Version](https://img.shields.io/github/package-json/v/NebulousLabs/skynet-js)](https://www.npmjs.com/package/skynet-js) [![Build Status](https://img.shields.io/github/workflow/status/NebulousLabs/skynet-js/Node.js%20CI)](https://github.com/NebulousLabs/skynet-js/actions) [![Contributors](https://img.shields.io/github/contributors/NebulousLabs/skynet-js)](https://github.com/NebulousLabs/skynet-js/graphs/contributors) [![License](https://img.shields.io/github/license/NebulousLabs/skynet-js)](https://github.com/NebulousLabs/skynet-js) -A Javascript module made to simplify communication with Sia Skynet portals from the browser. +A Javascript module made to simplify communication with Sia Skynet portals from the browser. Now with support for Node.js! ## Updating to v3 from v2 diff --git a/babel.config.json b/babel.config.json index f48249c0..1063c48d 100644 --- a/babel.config.json +++ b/babel.config.json @@ -1,6 +1,6 @@ { "presets": ["@babel/preset-typescript", "@babel/preset-env"], - "plugins": ["@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties"], + "plugins": ["@babel/plugin-proposal-class-properties"], "env": { "test": { "presets": [ diff --git a/package.json b/package.json index cbf80a6b..61d40a3b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "name": "skynet-js", "version": "3.0.2", "description": "Sia Skynet Javascript Client", - "main": "dist/index.js", + "browser": "dist/web/index.web.js", + "main": "dist/node/index.node.js", + "module": "dist/web/index.web.js", "files": [ "dist/*" ], @@ -12,8 +14,11 @@ "not OperaMini all" ], "scripts": { - "build": "rimraf dist && babel src --out-dir dist --extensions .ts --ignore src/**/*.test.ts && tsc --project tsconfig.build.json", - "lint:eslint": "eslint --ext .ts utils src --max-warnings 0", + "build": "rimraf dist && yarn build-bundle && yarn build-node && yarn build-web", + "build-bundle": "yarn run webpack", + "build-node": "babel src --out-dir dist/node --extensions .ts --ignore 'src/**/*.test.ts' && tsc --project tsconfig.build.json --outDir dist/node && rimraf dist/node/**/web* && rimraf dist/node/**/*web*", + "build-web": "babel src --out-dir dist/web --extensions .ts --ignore 'src/**/*.test.ts' && tsc --project tsconfig.build.json --outDir dist/web && rimraf dist/web/**/node* && rimraf dist/web/**/*node*", + "lint:eslint": "eslint --ext .ts src utils --max-warnings 0", "lint:tsc": "tsc", "prepublishOnly": "yarn build", "test": "jest --coverage --coverageDirectory ../coverage" @@ -26,10 +31,10 @@ ], "coverageThreshold": { "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": 100 + "branches": 97, + "functions": 97, + "lines": 97, + "statements": 97 } }, "rootDir": "src" @@ -42,7 +47,7 @@ "lint-staged": { "*.{js,jsx,ts,tsx}": [ "eslint --ext .ts --fix", - "tsc --esModuleInterop --noemit", + "tsc", "prettier --write" ], "*.{json,yml,md}": [ @@ -70,19 +75,19 @@ "base64-js": "^1.3.1", "blakejs": "^1.1.0", "buffer": "^6.0.1", + "form-data": "^4.0.0", "mime": "^2.5.2", "path-browserify": "^1.0.1", "randombytes": "^2.1.0", "sjcl": "^1.0.8", "tweetnacl": "^1.0.3", "url-join": "^4.0.1", - "url-parse": "^1.4.7" + "url-parse": "1.4.7" }, "devDependencies": { "@babel/cli": "^7.11.6", "@babel/core": "^7.11.6", "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-transform-runtime": "^7.11.5", "@babel/preset-env": "^7.11.5", "@babel/preset-typescript": "^7.10.4", "@types/base64-js": "^1.3.0", @@ -91,20 +96,27 @@ "@types/node": "^14.11.2", "@types/randombytes": "^2.0.0", "@types/sjcl": "^1.0.29", + "@types/tmp": "^0.2.0", "@types/url-join": "^4.0.0", "@types/url-parse": "^1.4.3", "@typescript-eslint/eslint-plugin": "^4.3.0", "@typescript-eslint/parser": "^4.3.0", "axios-mock-adapter": "^1.18.2", + "babel-loader": "^8.2.2", "babel-plugin-transform-class-properties": "^6.24.1", "eslint": "^7.11.0", "eslint-plugin-compat": "^3.8.0", "eslint-plugin-jsdoc": "^32.0.0", - "husky": "^5.0.9", + "husky": "^5.1.3", "jest": "^26.4.2", "lint-staged": "^10.3.0", "prettier": "^2.1.1", "rimraf": "^3.0.2", - "typescript": "^4.0.3" + "tmp": "^0.2.1", + "ts-loader": "^8.0.17", + "typescript": "^4.0.3", + "webpack": "^5.23.0", + "webpack-cli": "^4.5.0", + "webpack-merge": "^5.7.3" } } diff --git a/scripts/deploy.js b/scripts/deploy.js new file mode 100644 index 00000000..78cbb182 --- /dev/null +++ b/scripts/deploy.js @@ -0,0 +1 @@ +const { SkynetClient } = require(""); diff --git a/src/client.ts b/src/client/index.ts similarity index 82% rename from src/client.ts rename to src/client/index.ts index eea55b7a..4c7e0c22 100644 --- a/src/client.ts +++ b/src/client/index.ts @@ -1,9 +1,7 @@ -import axios, { AxiosResponse } from "axios"; +import axios, { AxiosResponse, ResponseType } from "axios"; import type { Method } from "axios"; -import { uploadFile, uploadDirectory, uploadDirectoryRequest, uploadFileRequest } from "./upload"; + import { - downloadFile, - downloadFileHns, getSkylinkUrl, getHnsUrl, getHnsresUrl, @@ -11,14 +9,13 @@ import { getFileContent, getFileContentHns, getFileContentRequest, - openFile, - openFileHns, resolveHns, -} from "./download"; -import { getJSON, setJSON } from "./skydb"; -import { getEntry, getEntryUrl, setEntry } from "./registry"; +} from "../download"; +import { getJSON, setJSON } from "../skydb"; +import { getEntry, getEntryUrl, setEntry } from "../registry"; -import { addUrlQuery, defaultPortalUrl, makeUrl } from "./utils/url"; +import { addUrlQuery, defaultPortalUrl, makeUrl } from "../utils/url"; +import { CustomUploadOptions, UploadRequestResponse } from "../upload"; /** * Custom client options. @@ -54,29 +51,22 @@ export type RequestConfig = CustomClientOptions & { skykeyName?: string; skykeyId?: string; headers?: Record; + responseType?: ResponseType; transformRequest?: (data: unknown) => string; transformResponse?: (data: string) => Record; }; /** - * The Skynet Client which can be used to access Skynet. + * The base Skynet Client which can be used to access Skynet. */ -export class SkynetClient { +export abstract class SkynetClient { // TODO: This is currently the url of the skapp and not the portal. It should be the value of 'skynet-portal-api' header. This will be a promise, which will be a breaking change. portalUrl: string; customOptions: CustomClientOptions; // Set methods (defined in other files). - // Upload - uploadFile = uploadFile; - protected uploadFileRequest = uploadFileRequest; - uploadDirectory = uploadDirectory; - protected uploadDirectoryRequest = uploadDirectoryRequest; - // Download - downloadFile = downloadFile; - downloadFileHns = downloadFileHns; getSkylinkUrl = getSkylinkUrl; getHnsUrl = getHnsUrl; getHnsresUrl = getHnsresUrl; @@ -84,10 +74,22 @@ export class SkynetClient { getFileContent = getFileContent; getFileContentHns = getFileContentHns; protected getFileContentRequest = getFileContentRequest; - openFile = openFile; - openFileHns = openFileHns; resolveHns = resolveHns; + // Upload + abstract uploadFileContent( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions + ): Promise; + protected abstract uploadFileContentRequest( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions + ): Promise; + // SkyDB db = { getJSON: getJSON.bind(this), @@ -109,6 +111,9 @@ export class SkynetClient { * @param [customOptions] Configuration for the client. */ constructor(portalUrl: string = defaultPortalUrl(), customOptions: CustomClientOptions = {}) { + if (!portalUrl) { + portalUrl = defaultPortalUrl(); + } this.portalUrl = portalUrl; this.customOptions = customOptions; } @@ -159,6 +164,7 @@ export class SkynetClient { headers, auth, onUploadProgress, + responseType: config.responseType, transformRequest: config.transformRequest, transformResponse: config.transformResponse, diff --git a/src/client/node.ts b/src/client/node.ts new file mode 100644 index 00000000..1fd43c0c --- /dev/null +++ b/src/client/node.ts @@ -0,0 +1,25 @@ +import { downloadFileHnsToPath, downloadFileToPath, downloadFileToPathRequest } from "../download/node"; +import { + uploadDirectoryFromPath, + uploadDirectoryFromPathRequest, + uploadFileFromPath, + uploadFileFromPathRequest, + uploadFileContent, + uploadFileContentRequest, +} from "../upload/node"; +import { SkynetClient as Client } from "./index"; + +export class SkynetClient extends Client { + // Download + downloadFileToPath = downloadFileToPath; + downloadFileHnsToPath = downloadFileHnsToPath; + protected downloadFileToPathRequest = downloadFileToPathRequest; + + // Upload + uploadDirectoryFromPath = uploadDirectoryFromPath; + protected uploadDirectoryFromPathRequest = uploadDirectoryFromPathRequest; + uploadFileFromPath = uploadFileFromPath; + protected uploadFileFromPathRequest = uploadFileFromPathRequest; + uploadFileContent = uploadFileContent; + protected uploadFileContentRequest = uploadFileContentRequest; +} diff --git a/src/client/web.ts b/src/client/web.ts new file mode 100644 index 00000000..f45b28bd --- /dev/null +++ b/src/client/web.ts @@ -0,0 +1,26 @@ +import { downloadFile, downloadFileHns, openFile, openFileHns } from "../download/web"; +import { + uploadFile, + uploadDirectory, + uploadDirectoryRequest, + uploadFileRequest, + uploadFileContent, + uploadFileContentRequest, +} from "../upload/web"; +import { SkynetClient as Client } from "./index"; + +export class SkynetClient extends Client { + // Download + downloadFile = downloadFile; + downloadFileHns = downloadFileHns; + openFile = openFile; + openFileHns = openFileHns; + + // Upload + uploadFile = uploadFile; + protected uploadFileRequest = uploadFileRequest; + uploadDirectory = uploadDirectory; + protected uploadDirectoryRequest = uploadDirectoryRequest; + uploadFileContent = uploadFileContent; + protected uploadFileContentRequest = uploadFileContentRequest; +} diff --git a/src/download.ts b/src/download/index.ts similarity index 71% rename from src/download.ts rename to src/download/index.ts index 8e7632a0..28edc475 100644 --- a/src/download.ts +++ b/src/download/index.ts @@ -1,4 +1,5 @@ -import { SkynetClient } from "./client"; +import { AxiosResponse } from "axios"; +import { SkynetClient } from "../client"; import { BaseCustomOptions, convertSkylinkToBase32, @@ -7,9 +8,9 @@ import { parseSkylink, uriHandshakePrefix, uriHandshakeResolverPrefix, -} from "./utils/skylink"; -import { trimUriPrefix } from "./utils/string"; -import { addSubdomain, addUrlQuery, makeUrl } from "./utils/url"; +} from "../utils/skylink"; +import { trimUriPrefix } from "../utils/string"; +import { addSubdomain, addUrlQuery, makeUrl } from "../utils/url"; /** * Custom download options. @@ -78,71 +79,17 @@ export type ResolveHnsResponse = { skylink: string; }; -const defaultDownloadOptions = { +export const defaultDownloadOptions = { ...defaultOptions("/"), }; -const defaultDownloadHnsOptions = { +export const defaultDownloadHnsOptions = { ...defaultOptions("/hns"), hnsSubdomain: "hns", }; -const defaultResolveHnsOptions = { +export const defaultResolveHnsOptions = { ...defaultOptions("/hnsres"), }; -/** - * Initiates a download of the content of the skylink within the browser. - * - * @param this - SkynetClient - * @param skylinkUrl - 46-character skylink, or a valid skylink URL. Can be followed by a path. Note that the skylink will not be encoded, so if your path might contain special characters, consider using `customOptions.path`. - * @param [customOptions] - Additional settings that can optionally be set. - * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. - * @returns - The full URL that was used. - * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. - */ -export function downloadFile(this: SkynetClient, skylinkUrl: string, customOptions?: CustomDownloadOptions): string { - /* istanbul ignore next */ - if (typeof skylinkUrl !== "string") { - throw new Error(`Expected parameter skylinkUrl to be type string, was type ${typeof skylinkUrl}`); - } - - const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions, download: true }; - const url = this.getSkylinkUrl(skylinkUrl, opts); - - // Download the url. - window.location.assign(url); - - return url; -} - -/** - * Initiates a download of the content of the skylink at the Handshake domain. - * - * @param this - SkynetClient - * @param domain - Handshake domain. - * @param [customOptions] - Additional settings that can optionally be set. - * @param [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact. - * @returns - The full URL that was used. - * @throws - Will throw if the input domain is not a string. - */ -export async function downloadFileHns( - this: SkynetClient, - domain: string, - customOptions?: CustomDownloadOptions -): Promise { - /* istanbul ignore next */ - if (typeof domain !== "string") { - throw new Error(`Expected parameter domain to be type string, was type ${typeof domain}`); - } - - const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions, download: true }; - const url = this.getHnsUrl(domain, opts); - - // Download the url. - window.location.assign(url); - - return url; -} - /** * Constructs the full URL for the given skylink. * @@ -274,7 +221,7 @@ export function getHnsresUrl(this: SkynetClient, domain: string, customOptions?: * @param skylinkUrl - Skylink string. See `downloadFile`. * @param [customOptions] - Additional settings that can optionally be set. See `downloadFile` for the full list. * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. - * @returns - The metadata in JSON format. Empty if no metadata was found. + * @returns - The metadata in JSON format. Each field will be empty if no metadata was found. * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. */ export async function getMetadata( @@ -296,18 +243,7 @@ export async function getMetadata( url, }); - if (typeof response.headers === "undefined") { - throw new Error( - "Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists." - ); - } - - const contentType = response.headers["content-type"] ?? ""; - const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; - const portalUrl = response.headers["skynet-portal-api"] ?? ""; - const skylink = response.headers["skynet-skylink"] ? formatSkylink(response.headers["skynet-skylink"]) : ""; - - return { contentType, metadata, portalUrl, skylink }; + return getDownloadHeaders(response); } /** @@ -385,71 +321,10 @@ export async function getFileContentRequest( "Did not get 'data' in response despite a successful request. Please try again and report this issue to the devs if it persists." ); } - if (typeof response.headers === "undefined") { - throw new Error( - "Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists." - ); - } - const contentType = response.headers["content-type"] ?? ""; - const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; - const portalUrl = response.headers["skynet-portal-api"] ?? ""; - const skylink = response.headers["skynet-skylink"] ? formatSkylink(response.headers["skynet-skylink"]) : ""; + const headers = getDownloadHeaders(response); - return { data: response.data, contentType, portalUrl, metadata, skylink }; -} - -/** - * Opens the content of the skylink within the browser. - * - * @param this - SkynetClient - * @param skylinkUrl - Skylink string. See `downloadFile`. - * @param [customOptions] - Additional settings that can optionally be set. See `downloadFile` for the full list. - * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. - * @returns - The full URL that was used. - * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. - */ -export function openFile(this: SkynetClient, skylinkUrl: string, customOptions?: CustomDownloadOptions): string { - /* istanbul ignore next */ - if (typeof skylinkUrl !== "string") { - throw new Error(`Expected parameter skylinkUrl to be type string, was type ${typeof skylinkUrl}`); - } - - const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions }; - const url = this.getSkylinkUrl(skylinkUrl, opts); - - window.open(url, "_blank"); - - return url; -} - -/** - * Opens the content of the skylink from the given Handshake domain within the browser. - * - * @param this - SkynetClient - * @param domain - Handshake domain. - * @param [customOptions] - Additional settings that can optionally be set. See `downloadFileHns` for the full list. - * @param [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact. - * @returns - The full URL that was used. - * @throws - Will throw if the input domain is not a string. - */ -export async function openFileHns( - this: SkynetClient, - domain: string, - customOptions?: CustomHnsDownloadOptions -): Promise { - /* istanbul ignore next */ - if (typeof domain !== "string") { - throw new Error(`Expected parameter domain to be type string, was type ${typeof domain}`); - } - - const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; - const url = this.getHnsUrl(domain, opts); - - // Open the url in a new tab. - window.open(url, "_blank"); - - return url; + return { ...headers, data: response.data }; } /** @@ -484,3 +359,26 @@ export async function resolveHns( return response.data; } + +/** + * Gets the common download metadata headers from the Axios response. + * + * @param response - The Axios response. + * @returns - The metadata headers for the download. + * @throws - Will throw if headers are not available on the response. + */ +export function getDownloadHeaders(response: AxiosResponse): GetMetadataResponse { + /* istanbul ignore next */ + if (typeof response.headers === "undefined") { + throw new Error( + "Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const contentType = response.headers["content-type"] ?? ""; + const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; + const portalUrl = response.headers["skynet-portal-api"] ?? ""; + const skylink = response.headers["skynet-skylink"] ? formatSkylink(response.headers["skynet-skylink"]) : ""; + + return { contentType, metadata, portalUrl, skylink }; +} diff --git a/src/download/node.test.ts b/src/download/node.test.ts new file mode 100644 index 00000000..2ec2cb43 --- /dev/null +++ b/src/download/node.test.ts @@ -0,0 +1,120 @@ +import { PassThrough } from "stream"; +import axios from "axios"; +import fs from "fs"; + +import { SkynetClient, defaultPortalUrl, uriSkynetPrefix } from "../index.node"; + +jest.mock("axios"); +jest.mock("fs"); + +const portalUrl = defaultPortalUrl(); +const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"; +const sialink = `${uriSkynetPrefix}${skylink}`; +const client = new SkynetClient(); + +const skynetfileContentType = "application/json"; +const skynetFileMetadata = { filename: "sia.pdf" }; +const fullHeaders = { + "skynet-skylink": skylink, + "content-type": skynetfileContentType, + "skynet-file-metadata": JSON.stringify(skynetFileMetadata), +}; +const body = "asdf"; +const filename = "foo"; + +describe("downloadFileToPath", () => { + beforeEach(() => { + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: fullHeaders }); + const mockWriteable = new PassThrough(); + // @ts-expect-error TS complaining. + fs.createWriteStream.mockReturnValueOnce(mockWriteable); + setTimeout(() => { + mockWriteable.emit("finish"); + }, 100); + }); + + it("should send get request to default portal", async () => { + await client.downloadFileToPath(skylink, filename); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/${skylink}`, + method: "get", + }) + ); + }); + + it("should use custom connection options if defined on the client", async () => { + const client = new SkynetClient("", { APIKey: "foobar", customUserAgent: "Sia-Agent" }); + + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileToPath(skylink, filename, { + APIKey: "barfoo", + customUserAgent: "Sia-Agent-2", + }); + + expect(contentType).toEqual(skynetfileContentType); + expect(metadata).toEqual(skynetFileMetadata); + expect(skylink2).toEqual(sialink); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/${skylink}`, + auth: { username: "", password: "barfoo" }, + headers: expect.objectContaining({ "User-Agent": "Sia-Agent-2" }), + }) + ); + }); + + it("should fetch info even when headers are missing", async () => { + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: {} }); + + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileToPath(skylink, filename); + + expect(contentType).toEqual(""); + expect(metadata).toEqual({}); + expect(skylink2).toEqual(""); + }); +}); + +describe("downloadFileHnsToPath", () => { + const domain = "foo"; + + beforeEach(() => { + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: fullHeaders }); + const mockWriteable = new PassThrough(); + // @ts-expect-error TS complaining. + fs.createWriteStream.mockReturnValueOnce(mockWriteable); + setTimeout(() => { + mockWriteable.emit("finish"); + }, 100); + }); + + it("should send get request for HNS to default portal", async () => { + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileHnsToPath(domain, filename); + + expect(contentType).toEqual(skynetfileContentType); + expect(metadata).toEqual(skynetFileMetadata); + expect(skylink2).toEqual(sialink); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/hns/${domain}`, + method: "get", + }) + ); + }); + + it("should get info when headers are missing", async () => { + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: {} }); + + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileHnsToPath(domain, filename); + + expect(contentType).toEqual(""); + expect(metadata).toEqual({}); + expect(skylink2).toEqual(""); + }); +}); diff --git a/src/download/node.ts b/src/download/node.ts new file mode 100644 index 00000000..51f3190f --- /dev/null +++ b/src/download/node.ts @@ -0,0 +1,124 @@ +import fs from "fs"; + +import { SkynetClient } from "../client/node"; +import { + CustomDownloadOptions, + defaultDownloadOptions, + defaultDownloadHnsOptions, + CustomHnsDownloadOptions, + GetMetadataResponse, + getDownloadHeaders, +} from "./index"; + +/** + * Initiates a download of the content of the skylink to the given file. + * + * @param this - SkynetClient + * @param skylinkUrl - 46-character skylink, or a valid skylink URL. Can be followed by a path. Note that the skylink will not be encoded, so if your path might contain special characters, consider using `customOptions.path`. + * @param path - Path to create the local file at. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. + * @returns - The metadata in JSON format. Each field will be empty if no metadata was found. + * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path custom option is not a string. + */ +export async function downloadFileToPath( + this: SkynetClient, + skylinkUrl: string, + path: string, + customOptions?: CustomDownloadOptions +): Promise { + const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions }; + + const url = this.getSkylinkUrl(skylinkUrl, opts); + + const writer = fs.createWriteStream(path); + + const response = await this.executeRequest({ + ...opts, + method: "get", + url, + responseType: "stream", + }); + + /* istanbul ignore next */ + if (typeof response.data === "undefined") { + throw new Error( + "Did not get 'data' in response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + await new Promise((resolve, reject) => { + response.data.pipe(writer); + writer.on("finish", resolve); + writer.on("error", reject); + }); + + return getDownloadHeaders(response); +} + +/** + * Initiates a download of the content of the skylink to the given file. + * + * @param this - SkynetClient + * @param domain - Handshake domain. + * @param path - Path to create the local file at. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. + * @returns - The metadata in JSON format. Each field will be empty if no metadata was found. + * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path custom option is not a string. + */ +export async function downloadFileHnsToPath( + this: SkynetClient, + domain: string, + path: string, + customOptions?: CustomHnsDownloadOptions +): Promise { + const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; + + const url = this.getHnsUrl(domain, opts); + + return this.downloadFileToPathRequest(url, path, opts); +} + +/** + * Makes a request to download a file to a path from Skynet. + * + * @param this - SkynetClient + * @param url - URL. + * @param path - Path to create the local file at. + * @param [customOptions] - Additional settings that can optionally be set. + * @returns - The metadata in JSON format. Each field will be empty if no metadata was found. + * @throws - Will throw if the request does not succeed or the response is missing data. + */ +export async function downloadFileToPathRequest( + this: SkynetClient, + url: string, + path: string, + customOptions?: CustomDownloadOptions +): Promise { + const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; + + const writer = fs.createWriteStream(path); + + const response = await this.executeRequest({ + ...opts, + method: "get", + url, + responseType: "stream", + }); + + /* istanbul ignore next */ + if (typeof response.data === "undefined") { + throw new Error( + "Did not get 'data' in response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + await new Promise((resolve, reject) => { + response.data.pipe(writer); + writer.on("finish", resolve); + writer.on("error", reject); + }); + + return getDownloadHeaders(response); +} diff --git a/src/download.test.ts b/src/download/web.test.ts similarity index 98% rename from src/download.test.ts rename to src/download/web.test.ts index 58e6344b..79d4cf3c 100644 --- a/src/download.test.ts +++ b/src/download/web.test.ts @@ -1,8 +1,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { combineStrings, extractNonSkylinkPath } from "../utils/testing"; +import { combineStrings, extractNonSkylinkPath } from "../../utils/testing"; -import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "./index"; +import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "../index.web"; const portalUrl = defaultSkynetPortalUrl; const hnsLink = "foo"; @@ -208,11 +208,13 @@ describe("getFileContent", () => { beforeEach(() => { mock = new MockAdapter(axios); }); - const skynetFileMetadata = { filename: "sia.pdf" }; + + const skynetfileContentType = "application/json"; const skynetFileContents = { arbitrary: "json string" }; + const skynetFileMetadata = { filename: "sia.pdf" }; const fullHeaders = { "skynet-skylink": skylink, - "content-type": "application/json", + "content-type": skynetfileContentType, "skynet-file-metadata": JSON.stringify(skynetFileMetadata), }; @@ -223,7 +225,7 @@ describe("getFileContent", () => { const { data, contentType, metadata, skylink: skylink2 } = await client.getFileContent(input); expect(data).toEqual(skynetFileContents); - expect(contentType).toEqual("application/json"); + expect(contentType).toEqual(skynetfileContentType); expect(metadata).toEqual(skynetFileMetadata); expect(skylink2).toEqual(sialink); }); diff --git a/src/download/web.ts b/src/download/web.ts new file mode 100644 index 00000000..64211991 --- /dev/null +++ b/src/download/web.ts @@ -0,0 +1,114 @@ +import { SkynetClient } from "../client/web"; +import { + CustomDownloadOptions, + defaultDownloadOptions, + defaultDownloadHnsOptions, + CustomHnsDownloadOptions, +} from "./index"; + +/** + * Initiates a download of the content of the skylink within the browser. + * + * @param this - SkynetClient + * @param skylinkUrl - 46-character skylink, or a valid skylink URL. Can be followed by a path. Note that the skylink will not be encoded, so if your path might contain special characters, consider using `customOptions.path`. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. + * @returns - The full URL that was used. + * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. + */ +export function downloadFile(this: SkynetClient, skylinkUrl: string, customOptions?: CustomDownloadOptions): string { + /* istanbul ignore next */ + if (typeof skylinkUrl !== "string") { + throw new Error(`Expected parameter skylinkUrl to be type string, was type ${typeof skylinkUrl}`); + } + + const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions, download: true }; + const url = this.getSkylinkUrl(skylinkUrl, opts); + + // Download the url. + window.location.assign(url); + + return url; +} + +/** + * Initiates a download of the content of the skylink at the Handshake domain. + * + * @param this - SkynetClient + * @param domain - Handshake domain. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact. + * @returns - The full URL that was used. + * @throws - Will throw if the input domain is not a string. + */ +export async function downloadFileHns( + this: SkynetClient, + domain: string, + customOptions?: CustomDownloadOptions +): Promise { + /* istanbul ignore next */ + if (typeof domain !== "string") { + throw new Error(`Expected parameter domain to be type string, was type ${typeof domain}`); + } + + const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions, download: true }; + const url = this.getHnsUrl(domain, opts); + + // Download the url. + window.location.assign(url); + + return url; +} + +/** + * Opens the content of the skylink within the browser. + * + * @param this - SkynetClient + * @param skylinkUrl - Skylink string. See `downloadFile`. + * @param [customOptions] - Additional settings that can optionally be set. See `downloadFile` for the full list. + * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. + * @returns - The full URL that was used. + * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. + */ +export function openFile(this: SkynetClient, skylinkUrl: string, customOptions?: CustomDownloadOptions): string { + /* istanbul ignore next */ + if (typeof skylinkUrl !== "string") { + throw new Error(`Expected parameter skylinkUrl to be type string, was type ${typeof skylinkUrl}`); + } + + const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions }; + const url = this.getSkylinkUrl(skylinkUrl, opts); + + window.open(url, "_blank"); + + return url; +} + +/** + * Opens the content of the skylink from the given Handshake domain within the browser. + * + * @param this - SkynetClient + * @param domain - Handshake domain. + * @param [customOptions] - Additional settings that can optionally be set. See `downloadFileHns` for the full list. + * @param [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact. + * @returns - The full URL that was used. + * @throws - Will throw if the input domain is not a string. + */ +export async function openFileHns( + this: SkynetClient, + domain: string, + customOptions?: CustomHnsDownloadOptions +): Promise { + /* istanbul ignore next */ + if (typeof domain !== "string") { + throw new Error(`Expected parameter domain to be type string, was type ${typeof domain}`); + } + + const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; + const url = this.getHnsUrl(domain, opts); + + // Open the url in a new tab. + window.open(url, "_blank"); + + return url; +} diff --git a/src/index.node.test.ts b/src/index.node.test.ts new file mode 100644 index 00000000..0c166793 --- /dev/null +++ b/src/index.node.test.ts @@ -0,0 +1,34 @@ +import { SkynetClient } from "./index.node"; + +describe("SkynetClient", () => { + it("should contain all api methods", () => { + const client = new SkynetClient(); + + // Download + expect(client).toHaveProperty("downloadFileToPath"); + expect(client).toHaveProperty("downloadFileHnsToPath"); + expect(client).toHaveProperty("getFileContent"); + expect(client).toHaveProperty("getFileContentHns"); + expect(client).toHaveProperty("getHnsUrl"); + expect(client).toHaveProperty("getHnsresUrl"); + expect(client).toHaveProperty("getSkylinkUrl"); + expect(client).toHaveProperty("getMetadata"); + expect(client).toHaveProperty("resolveHns"); + + // Upload + expect(client).toHaveProperty("uploadFileContent"); + expect(client).toHaveProperty("uploadDirectoryFromPath"); + expect(client).toHaveProperty("uploadFileFromPath"); + + // SkyDB + expect(client).toHaveProperty("db"); + expect(client.db).toHaveProperty("getJSON"); + expect(client.db).toHaveProperty("setJSON"); + + // SkyDB helpers + expect(client).toHaveProperty("registry"); + expect(client.registry).toHaveProperty("getEntry"); + expect(client.registry).toHaveProperty("getEntryUrl"); + expect(client.registry).toHaveProperty("setEntry"); + }); +}); diff --git a/src/index.node.ts b/src/index.node.ts new file mode 100644 index 00000000..e52820fc --- /dev/null +++ b/src/index.node.ts @@ -0,0 +1,16 @@ +export { SkynetClient } from "./client/node"; +export { deriveChildSeed, genKeyPairAndSeed, genKeyPairFromSeed } from "./crypto"; +export { parseSkylink, uriHandshakePrefix, uriHandshakeResolverPrefix, uriSkynetPrefix } from "./utils/skylink"; +export { MAX_REVISION } from "./utils/number"; +export { defaultPortalUrl, defaultSkynetPortalUrl } from "./utils/url"; +export { getRelativeFilePath, getRootDirectory } from "./utils/file"; + +// Export types. + +export type { CustomClientOptions, RequestConfig } from "./client/index"; +export type { Signature } from "./crypto"; +export type { CustomDownloadOptions, ResolveHnsResponse } from "./download"; +export type { CustomGetEntryOptions, CustomSetEntryOptions, SignedRegistryEntry, RegistryEntry } from "./registry"; +export type { CustomGetJSONOptions, CustomSetJSONOptions, VersionedEntryData } from "./skydb"; +export type { CustomUploadOptions, UploadRequestResponse } from "./upload/index"; +export type { ParseSkylinkOptions } from "./utils/skylink"; diff --git a/src/index.test.ts b/src/index.web.test.ts similarity index 92% rename from src/index.test.ts rename to src/index.web.test.ts index 0cd2f50a..55e207a8 100644 --- a/src/index.test.ts +++ b/src/index.web.test.ts @@ -1,4 +1,4 @@ -import { SkynetClient } from "./index"; +import { SkynetClient } from "./index.web"; describe("SkynetClient", () => { it("should contain all api methods", () => { @@ -18,8 +18,9 @@ describe("SkynetClient", () => { expect(client).toHaveProperty("resolveHns"); // Upload - expect(client).toHaveProperty("uploadFile"); expect(client).toHaveProperty("uploadDirectory"); + expect(client).toHaveProperty("uploadFileContent"); + expect(client).toHaveProperty("uploadFile"); // SkyDB expect(client).toHaveProperty("db"); diff --git a/src/index.ts b/src/index.web.ts similarity index 94% rename from src/index.ts rename to src/index.web.ts index 2677fcba..7c676c18 100644 --- a/src/index.ts +++ b/src/index.web.ts @@ -1,4 +1,4 @@ -export { SkynetClient } from "./client"; +export { SkynetClient } from "./client/web"; export { deriveChildSeed, genKeyPairAndSeed, genKeyPairFromSeed } from "./crypto"; export { getRelativeFilePath, getRootDirectory } from "./utils/file"; export { MAX_REVISION } from "./utils/number"; @@ -7,7 +7,7 @@ export { defaultPortalUrl, defaultSkynetPortalUrl } from "./utils/url"; // Export types. -export type { CustomClientOptions, RequestConfig } from "./client"; +export type { CustomClientOptions, RequestConfig } from "./client/index"; export type { Signature } from "./crypto"; export type { CustomDownloadOptions, ResolveHnsResponse } from "./download"; export type { CustomGetEntryOptions, CustomSetEntryOptions, SignedRegistryEntry, RegistryEntry } from "./registry"; diff --git a/src/integration.test.ts b/src/integration.web.test.ts similarity index 99% rename from src/integration.test.ts rename to src/integration.web.test.ts index 53c662c4..8e4a8ffa 100644 --- a/src/integration.test.ts +++ b/src/integration.web.test.ts @@ -1,4 +1,4 @@ -import { genKeyPairAndSeed, SkynetClient } from "./index"; +import { genKeyPairAndSeed, SkynetClient } from "./index.web"; import { MAX_GET_ENTRY_TIMEOUT } from "./registry"; import { MAX_REVISION } from "./utils/number"; diff --git a/src/registry.test.ts b/src/registry.test.ts index 6d3496ab..dc4dbead 100644 --- a/src/registry.test.ts +++ b/src/registry.test.ts @@ -1,7 +1,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; + import { genKeyPairAndSeed } from "./crypto"; -import { SkynetClient, defaultSkynetPortalUrl, genKeyPairFromSeed } from "./index"; +import { SkynetClient, defaultSkynetPortalUrl, genKeyPairFromSeed } from "./index.web"; import { MAX_GET_ENTRY_TIMEOUT } from "./registry"; const { publicKey, privateKey } = genKeyPairFromSeed("insecure test seed"); diff --git a/src/skydb.test.ts b/src/skydb.test.ts index 4fcdc0f4..6f0821ed 100644 --- a/src/skydb.test.ts +++ b/src/skydb.test.ts @@ -3,7 +3,7 @@ import MockAdapter from "axios-mock-adapter"; import { MAX_REVISION } from "./utils/number"; import { defaultSkynetPortalUrl } from "./utils/url"; -import { SkynetClient, genKeyPairFromSeed } from "./index"; +import { SkynetClient, genKeyPairFromSeed } from "./index.web"; import { regexRevisionNoQuotes } from "./registry"; const { publicKey, privateKey } = genKeyPairFromSeed("insecure test seed"); diff --git a/src/skydb.ts b/src/skydb.ts index 53bff7a6..de2f051f 100644 --- a/src/skydb.ts +++ b/src/skydb.ts @@ -101,10 +101,10 @@ export async function setJSON( }; // Create the data to upload to acquire its skylink. - const file = new File([JSON.stringify(json)], dataKey, { type: "application/json" }); + const contents = JSON.stringify(json); // Start file upload, do not block. - const skyfilePromise: Promise = this.uploadFile(file, opts); + const skyfilePromise: Promise = this.uploadFileContent(contents, dataKey, opts); let skyfile: UploadRequestResponse; if (revision === undefined) { diff --git a/src/upload/index.ts b/src/upload/index.ts new file mode 100644 index 00000000..e25c69a2 --- /dev/null +++ b/src/upload/index.ts @@ -0,0 +1,34 @@ +import { defaultOptions, BaseCustomOptions } from "../utils/skylink"; + +/** + * Custom upload options. + * + * @property [portalFileFieldname="file"] - The file fieldname for uploading files on this portal. + * @property [portalDirectoryfilefieldname="files[]"] - The file fieldname for uploading directories on this portal. + * @property [customFilename] - The custom filename to use when uploading files. + */ +export type CustomUploadOptions = BaseCustomOptions & { + portalFileFieldname?: string; + portalDirectoryFileFieldname?: string; + customFilename?: string; +}; + +/** + * The response to an upload request. + * + * @property skylink - 46-character skylink. + * @property merkleroot - The hash that is encoded into the skylink. + * @property bitfield - The bitfield that gets encoded into the skylink. The bitfield contains a version, an offset and a length in a heavily compressed and optimized format. + */ +export type UploadRequestResponse = { + skylink: string; + merkleroot: string; + bitfield: number; +}; + +export const defaultUploadOptions = { + ...defaultOptions("/skynet/skyfile"), + portalFileFieldname: "file", + portalDirectoryFileFieldname: "files[]", + customFilename: "", +}; diff --git a/src/upload/node.test.ts b/src/upload/node.test.ts new file mode 100644 index 00000000..83163b5f --- /dev/null +++ b/src/upload/node.test.ts @@ -0,0 +1,201 @@ +import axios from "axios"; +import fs from "fs"; +import tmp from "tmp"; + +import { SkynetClient, defaultPortalUrl, uriSkynetPrefix } from "../index.node"; + +jest.mock("axios"); + +const portalUrl = defaultPortalUrl(); +const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"; +const sialink = `${uriSkynetPrefix}${skylink}`; +const client = new SkynetClient(); +const merkleroot = "QAf9Q7dBSbMarLvyeE6HTQmwhr7RX9VMrP9xIMzpU3I"; +const bitfield = 2048; +const data = { skylink, merkleroot, bitfield }; + +describe("uploadFile", () => { + const filename = "testdata/file1.txt"; + + beforeEach(() => { + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data }); + }); + + it("should send post request to default portal", () => { + client.uploadFileFromPath(filename); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining('Content-Disposition: form-data; name="file"; filename="file1.txt"'), + ]), + }), + }) + ); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + headers: expect.objectContaining({ "content-type": expect.stringContaining("multipart/form-data") }), + }) + ); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([expect.stringContaining("Content-Type: text/plain")]), + }), + }) + ); + }); + + it("should use custom upload options if defined", () => { + client.uploadFileFromPath(filename, { + endpointPath: "/skynet/file", + portalFileFieldname: "filetest", + customFilename: "test.jpg", + }); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/file`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining('Content-Disposition: form-data; name="filetest"; filename="test.jpg"'), + ]), + }), + headers: expect.anything(), + }) + ); + }); + + it("should use custom connection options if defined on the client", () => { + const client = new SkynetClient("", { APIKey: "foobar", customUserAgent: "Sia-Agent" }); + + client.uploadFileFromPath(filename); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining(`Content-Disposition: form-data; name="file"; filename="file1.txt"`), + ]), + }), + }) + ); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + auth: { username: "", password: "foobar" }, + headers: expect.objectContaining({ "User-Agent": "Sia-Agent" }), + }) + ); + }); + + it("should use custom connection options if defined on the API call", () => { + const client = new SkynetClient("", { APIKey: "foobar", customUserAgent: "Sia-Agent" }); + + client.uploadFileFromPath(filename, { APIKey: "barfoo", customUserAgent: "Sia-Agent-2" }); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining(`Content-Disposition: form-data; name="file"; filename="file1.txt"`), + ]), + }), + auth: { username: "", password: "barfoo" }, + headers: expect.objectContaining({ "User-Agent": "Sia-Agent-2" }), + }) + ); + }); + + it("should upload tmp files", async () => { + const file = tmp.fileSync({ postfix: ".json" }); + fs.writeFileSync(file.fd, JSON.stringify("testing")); + + const { skylink } = await client.uploadFileFromPath(file.name); + + expect(skylink).toEqual(sialink); + + file.removeCallback(); + }); + + it("should return skylink on success", async () => { + const { skylink } = await client.uploadFileFromPath(filename); + + expect(skylink).toEqual(sialink); + }); +}); + +describe("uploadDirectoryFromPath", () => { + const dirname = "testdata"; + const directory = ["file1.txt", "file2.txt", "dir1/file3.txt"]; + const filename = `${dirname}/${directory[0]}`; + + beforeEach(() => { + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data }); + }); + + it("should send post request to default portal", () => { + client.uploadDirectoryFromPath(dirname); + + for (const file of directory) { + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile?filename=${dirname}`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining(`Content-Disposition: form-data; name="files[]"; filename="${file}"`), + ]), + }), + headers: expect.anything(), + }) + ); + } + }); + + it("should use custom options if defined", () => { + client.uploadDirectoryFromPath(dirname, { + endpointPath: "/skynet/file", + portalDirectoryFileFieldname: "filetest", + }); + + for (const file of directory) { + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/file?filename=${dirname}`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining(`Content-Disposition: form-data; name="filetest"; filename="${file}"`), + ]), + }), + headers: expect.anything(), + }) + ); + } + }); + + it("should not work on files", async () => { + await expect(client.uploadDirectoryFromPath(filename)).rejects.toThrowError( + `Given path is not a directory: ${filename}` + ); + }); + + it("should return single skylink on success", async () => { + const { skylink } = await client.uploadDirectoryFromPath(dirname); + + expect(skylink).toEqual(sialink); + }); + + it("should return single skylink on success with dryRun", async () => { + const { skylink } = await client.uploadDirectoryFromPath(dirname, { dryRun: true }); + + expect(skylink).toEqual(sialink); + }); +}); diff --git a/src/upload/node.ts b/src/upload/node.ts new file mode 100644 index 00000000..af5befcb --- /dev/null +++ b/src/upload/node.ts @@ -0,0 +1,262 @@ +/** + * Node-only upload functions. + */ + +import { AxiosResponse } from "axios"; +import FormData from "form-data"; +import fs from "fs"; +import p from "path"; + +import { SkynetClient } from "../client/node"; +import { CustomUploadOptions, defaultUploadOptions, UploadRequestResponse } from "./index"; +import { formatSkylink } from "../utils/skylink"; + +/** + * Uploads a file from the given local path to Skynet. + * + * @param this - SkynetClient + * @param path - The path to the local file to upload. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response. + */ +export async function uploadFileFromPath( + this: SkynetClient, + path: string, + customOptions?: CustomUploadOptions +): Promise { + const response = await this.uploadFileFromPathRequest(path, customOptions); + + /* istanbul ignore next */ + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +/** + * Makes a request upload a file from the given local path to Skynet. + * + * @param this - SkynetClient + * @param path - The path to the local file to upload. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + */ +export async function uploadFileFromPathRequest( + this: SkynetClient, + path: string, + customOptions?: CustomUploadOptions +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + const formData = new FormData(); + const filename = opts.customFilename ? opts.customFilename : ""; + formData.append(opts.portalFileFieldname, fs.createReadStream(path), filename); + + return this.executeRequest({ + ...opts, + method: "post", + // @ts-expect-error TS doesn't recognize this external FormData. + data: formData, + headers: formData.getHeaders(), + }); +} + +/** + * Uploads a directory from the given local path to Skynet. + * + * @param this - SkynetClient + * @param path - The path to the local directory to upload. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response, or if the directory is invalid. + */ +export async function uploadDirectoryFromPath( + this: SkynetClient, + path: string, + customOptions = {} +): Promise { + const response = await this.uploadDirectoryFromPathRequest(path, customOptions); + + /* istanbul ignore next */ + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +/** + * Makes a request upload a directory from the given local path to Skynet. + * + * @param this - SkynetClient + * @param path - The path to the local directory to upload. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + * @throws - Will throw if the directory is invalid. + */ +export async function uploadDirectoryFromPathRequest( + this: SkynetClient, + path: string, + customOptions = {} +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + // Check if there is a directory at given path. + const stat = fs.statSync(path); + if (!stat.isDirectory()) { + throw new Error(`Given path is not a directory: ${path}`); + } + + const formData = new FormData(); + path = p.normalize(path); + let basepath = path; + // Ensure the basepath ends in a slash. + if (!basepath.endsWith("/")) { + basepath += "/"; + // Normalize the slash on non-Unix filesystems. + basepath = p.normalize(basepath); + } + + for (const file of walkDirectory(path)) { + // Remove the dir path from the start of the filename if it exists. + let filename = file; + if (file.startsWith(basepath)) { + filename = file.replace(basepath, ""); + } + formData.append(opts.portalDirectoryFileFieldname, fs.createReadStream(file), { filepath: filename }); + } + + // TODO: Implement customDirname + // let filename = opts.customDirname || path; + let filename = path; + /* istanbul ignore next */ + if (filename.startsWith("/")) { + filename = filename.slice(1); + } + + return this.executeRequest({ + ...opts, + method: "post", + // @ts-expect-error TS doesn't recognize this external FormData. + data: formData, + headers: formData.getHeaders(), + query: { filename }, + }); +} + +/** + * Uploads a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response. + */ +export async function uploadFileContent( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const response = await this.uploadFileContentRequest(fileContents, fileName, customOptions); + + /* istanbul ignore next */ + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +/** + * Makes a request to upload a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + */ +export async function uploadFileContentRequest( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + const formData = new FormData(); + formData.append(opts.portalFileFieldname, fileContents, fileName); + + return this.executeRequest({ + ...opts, + method: "post", + // @ts-expect-error TS doesn't recognize this external FormData. + data: formData, + headers: formData.getHeaders(), + }); +} + +/** + * Returns the full recursive list of files inside a directory. + * + * @param filepath - The directory path. + * @returns - The full list of files. + */ +function walkDirectory(filepath: string): Array { + /* istanbul ignore next */ + if (!fs.existsSync(filepath)) { + return []; + } + + let files: Array = []; + for (const subpath of fs.readdirSync(filepath)) { + const fullpath = p.join(filepath, subpath); + if (fs.statSync(fullpath).isDirectory()) { + files = files.concat(walkDirectory(fullpath)); + continue; + } + files.push(fullpath); + } + return files; +} diff --git a/src/upload.test.ts b/src/upload/web.test.ts similarity index 98% rename from src/upload.test.ts rename to src/upload/web.test.ts index b4df2c64..092c56fe 100644 --- a/src/upload.test.ts +++ b/src/upload/web.test.ts @@ -1,8 +1,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "./index"; -import { compareFormData } from "../utils/testing"; +import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "../index.web"; +import { compareFormData } from "../../utils/testing"; const portalUrl = defaultSkynetPortalUrl; const client = new SkynetClient(portalUrl); diff --git a/src/upload.ts b/src/upload/web.ts similarity index 66% rename from src/upload.ts rename to src/upload/web.ts index a3432adf..78b44df1 100644 --- a/src/upload.ts +++ b/src/upload/web.ts @@ -1,42 +1,9 @@ -import { getFileMimeType } from "./utils/file"; -import { BaseCustomOptions, defaultOptions, formatSkylink } from "./utils/skylink"; -import { SkynetClient } from "./client"; import { AxiosResponse } from "axios"; -/** - * Custom upload options. - * - * @property [portalFileFieldname="file"] - The file fieldname for uploading files on this portal. - * @property [portalDirectoryfilefieldname="files[]"] - The file fieldname for uploading directories on this portal. - * @property [customFilename] - The custom filename to use when uploading files. - * @property [query] - Query parameters. - */ -export type CustomUploadOptions = BaseCustomOptions & { - portalFileFieldname?: string; - portalDirectoryFileFieldname?: string; - customFilename?: string; - query?: Record; -}; - -/** - * The response to an upload request. - * - * @property skylink - 46-character skylink. - * @property merkleroot - The hash that is encoded into the skylink. - * @property bitfield - The bitfield that gets encoded into the skylink. The bitfield contains a version, an offset and a length in a heavily compressed and optimized format. - */ -export type UploadRequestResponse = { - skylink: string; - merkleroot: string; - bitfield: number; -}; - -const defaultUploadOptions = { - ...defaultOptions("/skynet/skyfile"), - portalFileFieldname: "file", - portalDirectoryFileFieldname: "files[]", - customFilename: "", -}; +import { SkynetClient } from "../client/web"; +import { getFileMimeType } from "../utils/file"; +import { formatSkylink } from "../utils/skylink"; +import { CustomUploadOptions, defaultUploadOptions, UploadRequestResponse } from "./index"; /** * Uploads a file to Skynet. @@ -45,7 +12,7 @@ const defaultUploadOptions = { * @param file - The file to upload. * @param [customOptions] - Additional settings that can optionally be set. * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. - * @returns - The returned skylink. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. * @throws - Will throw if the request is successful but the upload response does not contain a complete response. */ export async function uploadFile( @@ -96,13 +63,11 @@ export async function uploadFileRequest( formData.append(opts.portalFileFieldname, file); } - const response = await this.executeRequest({ + return this.executeRequest({ ...opts, method: "post", data: formData, }); - - return response; } /** @@ -171,14 +136,77 @@ export async function uploadDirectoryRequest( formData.append(opts.portalDirectoryFileFieldname, file as File, path); }); - const response = await this.executeRequest({ + return this.executeRequest({ ...opts, method: "post", data: formData, query: { filename }, }); +} + +/** + * Uploads a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response. + */ +export async function uploadFileContent( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const response = await this.uploadFileContentRequest(fileContents, fileName, customOptions); - return response; + /* istanbul ignore next */ + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +/** + * Makes a request to upload a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + */ +export async function uploadFileContentRequest( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + const formData = new FormData(); + formData.append(opts.portalFileFieldname, new Blob([fileContents]), fileName); + + return this.executeRequest({ + ...opts, + method: "post", + data: formData, + }); } /** diff --git a/src/utils/skylink.test.ts b/src/utils/skylink.test.ts index 13c30fd0..b7f1c3ef 100644 --- a/src/utils/skylink.test.ts +++ b/src/utils/skylink.test.ts @@ -14,7 +14,7 @@ describe("convertSkylinkToBase32", () => { describe("formatSkylink", () => { it("should ensure the skylink starts with the prefix", () => { - const prefixedSkylink = `sia:${skylink}`; + const prefixedSkylink = `sia://${skylink}`; expect(formatSkylink(skylink)).toEqual(prefixedSkylink); expect(formatSkylink(prefixedSkylink)).toEqual(prefixedSkylink); diff --git a/src/utils/skylink.ts b/src/utils/skylink.ts index 02329cf2..8a9d34d1 100644 --- a/src/utils/skylink.ts +++ b/src/utils/skylink.ts @@ -8,9 +8,11 @@ import { trimForwardSlash, trimSuffix, trimUriPrefix } from "./string"; * Base custom options for methods hitting the API. * * @property [endpointPath] - The relative URL path of the portal endpoint to contact. + * @property [query] - Query parameters. */ export type BaseCustomOptions = CustomClientOptions & { endpointPath?: string; + query?: Record; }; /** @@ -32,7 +34,7 @@ type ParseSkylinkBase32Options = { export const uriHandshakePrefix = "hns:"; export const uriHandshakeResolverPrefix = "hnsres:"; -export const uriSkynetPrefix = "sia:"; +export const uriSkynetPrefix = "sia://"; /** * Converts the given base64 skylink to base32. @@ -60,7 +62,7 @@ export function defaultOptions(endpointPath: string): CustomClientOptions & { en } /** - * Formats the skylink by adding the sia: prefix. + * Formats the skylink by adding the sia prefix. * * @param skylink - The skylink. * @returns - The formatted skylink. diff --git a/testdata/dir1/file3.txt b/testdata/dir1/file3.txt new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/testdata/dir1/file3.txt @@ -0,0 +1 @@ +test diff --git a/testdata/file1.txt b/testdata/file1.txt new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/testdata/file1.txt @@ -0,0 +1 @@ +test diff --git a/testdata/file2.txt b/testdata/file2.txt new file mode 100644 index 00000000..038d718d --- /dev/null +++ b/testdata/file2.txt @@ -0,0 +1 @@ +testing diff --git a/tsconfig.build.json b/tsconfig.build.json index abec6437..5b03407f 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -6,10 +6,9 @@ "declaration": true, "declarationMap": true, "isolatedModules": true, - "outDir": "dist", "types": ["node", "jest"], "typeRoots": ["./types", "./node_modules/@types"] }, "include": ["src", "types/**/*.d.ts"], - "exclude": ["src/*.test.ts"] + "exclude": ["src/**/*.test.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 7d5a016c..ba0b2602 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,8 @@ "esModuleInterop": true, "strict": true, "noImplicitAny": true, - "declaration": true, "isolatedModules": true, "types": ["node", "jest"], - "typeRoots": ["./types", "./node_modules/@types"], - "strictNullChecks": true + "typeRoots": ["./types", "./node_modules/@types"] } } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..f015a73b --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,31 @@ +const path = require("path"); +const { merge } = require("webpack-merge"); + +module.exports = { + entry: "./src/index.web.ts", + target: "web", + mode: "production", + + module: { + rules: [ + { + test: /\.tsx?$/, + exclude: /(node_modules|bower_components)/, + loader: "babel-loader", + options: { + ignore: ["src/**/*.test.ts"], + }, + }, + ], + }, + resolve: { + extensions: [".tsx", ".ts", ".js"], + }, + output: { + path: path.resolve(__dirname, "./dist/bundle"), + // The filename needs to match the index.web.d.ts declarations file. + filename: "index.js", + library: "skynet", + libraryTarget: "umd", + }, +}; diff --git a/yarn.lock b/yarn.lock index 4aacb876..f984ffa6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -710,18 +710,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-transform-runtime@^7.11.5": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.10.tgz#a1e40d22e2bf570c591c9c7e5ab42d6bf1e419e1" - integrity sha512-Y5k8ipgfvz5d/76tx7JYbKQTcgFSU6VgJ3kKQv4zGTKr+a9T/KBvfRvGtSFgKDQGt/DBykQixV0vNWKIdzWErA== - dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - babel-plugin-polyfill-corejs2 "^0.1.4" - babel-plugin-polyfill-corejs3 "^0.1.3" - babel-plugin-polyfill-regenerator "^0.1.2" - semver "^6.3.0" - "@babel/plugin-transform-shorthand-properties@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad" @@ -929,6 +917,11 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@discoveryjs/json-ext@^0.5.0": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" + integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== + "@eslint/eslintrc@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547" @@ -1228,6 +1221,32 @@ resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.3.0.tgz#c939fdba49846861caf5a246b165dbf5698a317c" integrity sha512-ZmI0sZGAUNXUfMWboWwi4LcfpoVUYldyN6Oe0oJ5cCsHDU/LlRq8nQKPXhYLOx36QYSW9bNIb1vvRrD6K7Llgw== +"@types/eslint-scope@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" + integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "7.2.7" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.7.tgz#f7ef1cf0dceab0ae6f9a976a0a9af14ab1baca26" + integrity sha512-EHXbc1z2GoQRqHaAT7+grxlTJ3WE2YNeD6jlpPoRc83cCoThRY+NUWjCUZaYmk51OICkPXn2hhphcWcWXgNW0Q== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "0.0.47" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" + integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== + +"@types/estree@^0.0.46": + version "0.0.46" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" + integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -1262,7 +1281,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/json-schema@^7.0.3": +"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== @@ -1309,6 +1328,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/tmp@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c" + integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ== + "@types/url-join@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-4.0.0.tgz#72eff71648a429c7d4acf94e03780e06671369bd" @@ -1401,6 +1425,154 @@ "@typescript-eslint/types" "4.18.0" eslint-visitor-keys "^2.0.0" +"@webassemblyjs/ast@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" + integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + +"@webassemblyjs/floating-point-hex-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" + integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== + +"@webassemblyjs/helper-api-error@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" + integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== + +"@webassemblyjs/helper-buffer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" + integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== + +"@webassemblyjs/helper-numbers@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" + integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" + integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== + +"@webassemblyjs/helper-wasm-section@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" + integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + +"@webassemblyjs/ieee754@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" + integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" + integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" + integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== + +"@webassemblyjs/wasm-edit@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" + integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/helper-wasm-section" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-opt" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wast-printer" "1.11.0" + +"@webassemblyjs/wasm-gen@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" + integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + +"@webassemblyjs/wasm-opt@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" + integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + +"@webassemblyjs/wasm-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" + integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + +"@webassemblyjs/wast-printer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" + integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.1.tgz#241aecfbdc715eee96bed447ed402e12ec171935" + integrity sha512-B+4uBUYhpzDXmwuo3V9yBH6cISwxEI4J+NO5ggDaGEEHb0osY/R7MzeKc0bHURXQuZjMM4qD+bSJCKIuI3eNBQ== + +"@webpack-cli/info@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.2.2.tgz#ef3c0cd947a1fa083e174a59cb74e0b6195c236c" + integrity sha512-5U9kUJHnwU+FhKH4PWGZuBC1hTEPYyxGSL5jjoBI96Gx8qcYJGOikpiIpFoTq8mmgX3im2zAo2wanv/alD74KQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.3.0.tgz#2730c770f5f1f132767c63dcaaa4ec28f8c56a6c" + integrity sha512-k2p2VrONcYVX1wRRrf0f3X2VGltLWcv+JzXRBDmvCxGlCeESx4OXw91TsWeKOkp784uNoVQo313vxJFHXPPwfw== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + abab@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -1429,6 +1601,11 @@ acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.0.4: + version "8.1.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe" + integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA== + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1437,7 +1614,12 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1655,6 +1837,16 @@ babel-jest@^26.6.3: graceful-fs "^4.2.4" slash "^3.0.0" +babel-loader@^8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" + integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^1.4.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + babel-messages@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" @@ -1839,6 +2031,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -2024,6 +2221,13 @@ chokidar@^3.4.0: optionalDependencies: fsevents "~2.3.1" +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -2073,6 +2277,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -2120,13 +2333,18 @@ colorette@^1.2.1: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -2137,11 +2355,21 @@ commander@^6.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + comment-parser@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.1.2.tgz#e5317d7a2ec22b470dcb54a29b25426c30bf39d8" integrity sha512-AOdq0i8ghZudnYv8RUnHrhTgafUGs61Rdz9jemU5x2lnZwAWyOq7vySo626K59e1fVKH1xSRorJwPVRLSWOoAQ== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -2209,7 +2437,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2383,6 +2611,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -2390,6 +2623,23 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +enhanced-resolve@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enhanced-resolve@^5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" + integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -2397,6 +2647,18 @@ enquirer@^2.3.5, enquirer@^2.3.6: dependencies: ansi-colors "^4.1.1" +envinfo@^7.7.3: + version "7.7.4" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.4.tgz#c6311cdd38a0e86808c1c9343f667e4267c4a320" + integrity sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ== + +errno@^0.1.3: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2404,6 +2666,11 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +es-module-lexer@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.1.tgz#dda8c6a14d8f340a24e34331e0fab0cb50438e0e" + integrity sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -2569,6 +2836,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + exec-sh@^0.3.2: version "0.3.4" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" @@ -2602,6 +2874,21 @@ execa@^4.0.0, execa@^4.1.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +execa@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" + integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -2703,6 +2990,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + fastq@^1.6.0: version "1.10.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.1.tgz#8b8f2ac8bf3632d67afcd65dac248d5fdc45385e" @@ -2748,6 +3040,15 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -2784,6 +3085,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -2868,6 +3178,11 @@ get-stream@^5.0.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" + integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2895,6 +3210,11 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -2943,7 +3263,7 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -3057,10 +3377,15 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== -husky@^5.0.9: - version "5.0.9" - resolved "https://registry.yarnpkg.com/husky/-/husky-5.0.9.tgz#6d38706643d66ed395bcd4ee952d02e3f15eb3a3" - integrity sha512-0SjcaY21a+IRdx7p7r/X33Vc09UR2m8SbP8yfkhUX2/jAmwcz+GR7i9jXkp2pP3GfX23JhMkVP6SWwXB18uXtg== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +husky@^5.1.3: + version "5.2.0" + resolved "https://registry.yarnpkg.com/husky/-/husky-5.2.0.tgz#fc5e1c2300d34855d47de4753607d00943fc0802" + integrity sha512-AM8T/auHXRBxlrfPVLKP6jt49GCM2Zz47m8G3FOMsLmTv8Dj/fKVWE0Rh2d4Qrvmy131xEsdQnb3OXRib67PGg== iconv-lite@0.4.24: version "0.4.24" @@ -3123,6 +3448,11 @@ inherits@2, inherits@^2.0.3, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -3842,6 +4172,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -3872,6 +4207,13 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + json5@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" @@ -3980,6 +4322,29 @@ listr2@^3.2.2: through "^2.3.8" wrap-ansi "^7.0.0" +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + +loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -4046,7 +4411,7 @@ make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -4072,6 +4437,14 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -4101,7 +4474,7 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: +micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -4114,6 +4487,11 @@ mime-db@1.45.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== +mime-db@1.46.0: + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.28" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" @@ -4121,6 +4499,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.45.0" +mime-types@^2.1.27: + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + dependencies: + mime-db "1.46.0" + mime@^2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" @@ -4183,6 +4568,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -4244,7 +4634,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.0: +npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -4306,7 +4696,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -4354,6 +4744,13 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -4462,7 +4859,7 @@ pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" -pkg-dir@^4.2.0: +pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -4524,6 +4921,11 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -4588,7 +4990,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^2.0.2: +readable-stream@^2.0.1, readable-stream@^2.0.2: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -4617,6 +5019,13 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +rechoir@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" + integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== + dependencies: + resolve "^1.9.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -4787,7 +5196,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1: +resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.9.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -4883,6 +5292,24 @@ saxes@^5.0.0: dependencies: xmlchars "^2.2.0" +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -4915,6 +5342,13 @@ semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: dependencies: lru-cache "^6.0.0" +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -4930,6 +5364,13 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -4959,7 +5400,7 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -5032,6 +5473,11 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +source-list-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -5043,7 +5489,7 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.6: +source-map-support@^0.5.6, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -5066,7 +5512,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: +source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== @@ -5258,6 +5704,16 @@ table@^6.0.4: slice-ansi "^4.0.0" string-width "^4.2.0" +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -5266,6 +5722,27 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" +terser-webpack-plugin@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" + integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q== + dependencies: + jest-worker "^26.6.2" + p-limit "^3.1.0" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.5.1" + +terser@^5.5.1: + version "5.6.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.1.tgz#a48eeac5300c0a09b36854bf90d9c26fb201973c" + integrity sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -5290,6 +5767,13 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -5361,6 +5845,17 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +ts-loader@^8.0.17: + version "8.0.18" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.18.tgz#b2385cbe81c34ad9f997915129cdde3ad92a61ea" + integrity sha512-hRZzkydPX30XkLaQwJTDcWDoxZHK6IrEMDQpNd7tgcakFruFkeUp/aY+9hBb7BUGb+ZWKI0jiOGMo0MckwzdDQ== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^4.0.0" + loader-utils "^2.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -5504,7 +5999,7 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.4.7: +url-parse@1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== @@ -5537,6 +6032,11 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== +v8-compile-cache@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + v8-to-istanbul@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz#5b95cef45c0f83217ec79f8fc7ee1c8b486aee07" @@ -5584,6 +6084,14 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +watchpack@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7" + integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -5594,6 +6102,71 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webpack-cli@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.5.0.tgz#b5213b84adf6e1f5de6391334c9fa53a48850466" + integrity sha512-wXg/ef6Ibstl2f50mnkcHblRPN/P9J4Nlod5Hg9HGFgSeF8rsqDGHJeVe4aR26q9l62TUJi6vmvC2Qz96YJw1Q== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.0.1" + "@webpack-cli/info" "^1.2.2" + "@webpack-cli/serve" "^1.3.0" + colorette "^1.2.1" + commander "^7.0.0" + enquirer "^2.3.6" + execa "^5.0.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + v8-compile-cache "^2.2.0" + webpack-merge "^5.7.3" + +webpack-merge@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" + integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" + integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + +webpack@^5.23.0: + version "5.28.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.28.0.tgz#0de8bcd706186b26da09d4d1e8cbd3e4025a7c2f" + integrity sha512-1xllYVmA4dIvRjHzwELgW4KjIU1fW4PEuEnjsylz7k7H5HgPOctIq7W1jrt3sKH9yG5d72//XWzsHhfoWvsQVg== + dependencies: + "@types/eslint-scope" "^3.7.0" + "@types/estree" "^0.0.46" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/wasm-edit" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + acorn "^8.0.4" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.7.0" + es-module-lexer "^0.4.0" + eslint-scope "^5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.4" + json-parse-better-errors "^1.0.2" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.0.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.1" + watchpack "^2.0.0" + webpack-sources "^2.1.1" + whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -5634,6 +6207,11 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -5726,3 +6304,8 @@ yargs@^15.4.1: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^18.1.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==