From 63ab457b453e28b71d63917db305a8865b40eb2c Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 31 Aug 2022 14:01:43 +0100 Subject: [PATCH] Refactor feed errors and error presentation (#459) * Add support for setting a global feed timeout * Track failures, and don't show the first non-serious error * Tidy up error reporting * Make a prettier error pane * Use the prettier event pane * Show failed feed attempts in the widget * Ensure we catch connection resets * Add styling file * Ensure we only track one result per fetch * changelog * Refactor to use better status codes * Make feed results cheaper * splice --- changelog.d/459.feature | 1 + config.sample.yml | 1 + package.json | 6 +- src/Bridge.ts | 5 + src/Config/Config.ts | 5 + src/Config/Defaults.ts | 1 + src/Connections/FeedConnection.ts | 77 +++++-- src/Connections/SetupConnection.ts | 7 +- src/api/error.ts | 29 +-- src/feeds/FeedReader.ts | 53 ++++- vite.config.js | 3 +- web/IBridgeEvents.ts | 0 web/components/elements/ErrorPane.css | 3 - web/components/elements/ErrorPane.module.scss | 4 + web/components/elements/ErrorPane.tsx | 8 +- .../roomConfig/FeedConnection.module.scss | 4 + web/components/roomConfig/FeedsConfig.tsx | 32 ++- web/components/roomConfig/RoomConfig.tsx | 38 +++- web/icons/warning-badge.svg | 5 + yarn.lock | 204 +++++++++++++++++- 20 files changed, 413 insertions(+), 73 deletions(-) create mode 100644 changelog.d/459.feature delete mode 100644 web/IBridgeEvents.ts delete mode 100644 web/components/elements/ErrorPane.css create mode 100644 web/components/elements/ErrorPane.module.scss create mode 100644 web/components/roomConfig/FeedConnection.module.scss create mode 100644 web/icons/warning-badge.svg diff --git a/changelog.d/459.feature b/changelog.d/459.feature new file mode 100644 index 00000000..e1268b6a --- /dev/null +++ b/changelog.d/459.feature @@ -0,0 +1 @@ +Added new config option `feeds.pollTimeoutSeconds` to explictly set how long to wait for a feed response. diff --git a/config.sample.yml b/config.sample.yml index 9d965209..a7d88b90 100644 --- a/config.sample.yml +++ b/config.sample.yml @@ -87,6 +87,7 @@ feeds: # enabled: false pollIntervalSeconds: 600 + pollTimeoutSeconds: 10 provisioning: # (Optional) Provisioning API for integration managers # diff --git a/package.json b/package.json index 960ffb60..f5f75b5a 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "cors": "^2.8.5", "express": "^4.17.1", "figma-js": "^1.14.0", + "http-status-codes": "^2.2.0", "ioredis": "^4.28.0", "jira-client": "^8.0.0", "markdown-it": "^12.3.2", @@ -85,7 +86,7 @@ "@types/micromatch": "^4.0.1", "@types/mime": "^2.0.3", "@types/mocha": "^9.0.0", - "@types/node": "^14", + "@types/node": "^16", "@types/node-emoji": "^1.8.1", "@types/uuid": "^8.3.3", "@types/xml2js": "^0.4.11", @@ -103,6 +104,7 @@ "sass": "^1.51.0", "ts-node": "^10.4.0", "typescript": "^4.5.2", - "vite": "^2.9.13" + "vite": "^2.9.13", + "vite-svg-loader": "^3.4.0" } } diff --git a/src/Bridge.ts b/src/Bridge.ts index 387c26a4..c026a5f3 100644 --- a/src/Bridge.ts +++ b/src/Bridge.ts @@ -613,6 +613,11 @@ export class Bridge { (data) => connManager.getConnectionsForFeedUrl(data.feed.url), (c, data) => c.handleFeedEntry(data), ); + this.bindHandlerToQueue( + "feed.success", + (data) => connManager.getConnectionsForFeedUrl(data.feed.url), + c => c.handleFeedSuccess(), + ); this.bindHandlerToQueue( "feed.error", (data) => connManager.getConnectionsForFeedUrl(data.url), diff --git a/src/Config/Config.ts b/src/Config/Config.ts index 39266478..34556dfb 100644 --- a/src/Config/Config.ts +++ b/src/Config/Config.ts @@ -232,15 +232,20 @@ export class BridgeConfigGitLab { export interface BridgeConfigFeedsYAML { enabled: boolean; pollIntervalSeconds: number; + pollTimeoutSeconds?: number; } export class BridgeConfigFeeds { public enabled: boolean; public pollIntervalSeconds: number; + public pollTimeoutSeconds: number; constructor(yaml: BridgeConfigFeedsYAML) { this.enabled = yaml.enabled; this.pollIntervalSeconds = yaml.pollIntervalSeconds; + assert.strictEqual(typeof this.pollIntervalSeconds, "number"); + this.pollTimeoutSeconds = yaml.pollTimeoutSeconds ?? 10; + assert.strictEqual(typeof this.pollTimeoutSeconds, "number"); } @hideKey() diff --git a/src/Config/Defaults.ts b/src/Config/Defaults.ts index 5b8d8d77..e5f2fa78 100644 --- a/src/Config/Defaults.ts +++ b/src/Config/Defaults.ts @@ -111,6 +111,7 @@ export const DefaultConfig = new BridgeConfig({ feeds: { enabled: false, pollIntervalSeconds: 600, + pollTimeoutSeconds: 10, }, provisioning: { secret: "!secretToken" diff --git a/src/Connections/FeedConnection.ts b/src/Connections/FeedConnection.ts index 776bf48a..2a94a900 100644 --- a/src/Connections/FeedConnection.ts +++ b/src/Connections/FeedConnection.ts @@ -10,16 +10,33 @@ import axios from "axios"; import markdown from "markdown-it"; import { Connection, ProvisionConnectionOpts } from "./IConnection"; import { GetConnectionsResponseItem } from "../provisioning/api"; - +import { StatusCodes } from "http-status-codes"; const log = new LogWrapper("FeedConnection"); const md = new markdown(); +export interface LastResultOk { + timestamp: number; + ok: true; +} +export interface LastResultFail { + timestamp: number; + ok: false; + error?: string; +} + + export interface FeedConnectionState extends IConnectionState { url: string; label?: string; } -export type FeedResponseItem = GetConnectionsResponseItem; +export interface FeedConnectionSecrets { + lastResults: Array; +} + +export type FeedResponseItem = GetConnectionsResponseItem; + +const MAX_LAST_RESULT_ITEMS = 5; @Connection export class FeedConnection extends BaseConnection implements IConnection { @@ -37,14 +54,23 @@ export class FeedConnection extends BaseConnection implements IConnection { static async validateUrl(url: string): Promise { try { new URL(url); - const res = await axios.head(url).catch(_ => axios.get(url)); - const contentType = res.headers['content-type']; - // we're deliberately liberal here, since different things pop up in the wild - if (!contentType.match(/xml/)) { - throw new Error(`${contentType} doesn't look like an RSS/Atom feed`); - } - } catch (err) { - throw new Error(`${url} doesn't look like a valid feed URL: ${err}`); + } catch (ex) { + throw new ApiError("Feed URL doesn't appear valid", ErrCode.BadValue); + } + let res; + try { + res = await axios.head(url).catch(() => axios.get(url)); + } catch (ex) { + throw new ApiError(`Could not read from URL: ${ex.message}`, ErrCode.BadValue); + } + const contentType = res.headers['content-type']; + // we're deliberately liberal here, since different things pop up in the wild + if (!contentType.match(/xml/)) { + throw new ApiError( + `Feed responded with a content type of "${contentType}", which doesn't look like an RSS/Atom feed`, + ErrCode.BadValue, + StatusCodes.UNSUPPORTED_MEDIA_TYPE + ); } } @@ -57,11 +83,7 @@ export class FeedConnection extends BaseConnection implements IConnection { if (typeof url !== 'string') { throw new ApiError('No URL specified', ErrCode.BadValue); } - try { - await FeedConnection.validateUrl(url); - } catch (err: any) { - throw new ApiError(err.toString(), ErrCode.BadValue); - } + await FeedConnection.validateUrl(url); if (typeof data.label !== 'undefined' && typeof data.label !== 'string') { throw new ApiError('Label must be a string', ErrCode.BadValue); } @@ -95,10 +117,14 @@ export class FeedConnection extends BaseConnection implements IConnection { url: this.feedUrl, label: this.state.label, }, + secrets: { + lastResults: this.lastResults, + } } } private hasError = false; + private readonly lastResults = new Array(); public get feedUrl(): string { return this.state.url; @@ -121,7 +147,6 @@ export class FeedConnection extends BaseConnection implements IConnection { } public async handleFeedEntry(entry: FeedEntry): Promise { - this.hasError = false; let entryDetails; if (entry.title && entry.link) { @@ -143,7 +168,27 @@ export class FeedConnection extends BaseConnection implements IConnection { }); } + handleFeedSuccess() { + this.hasError = false; + this.lastResults.unshift({ + ok: true, + timestamp: Date.now(), + }); + this.lastResults.splice(MAX_LAST_RESULT_ITEMS-1, 1); + } + public async handleFeedError(error: FeedError): Promise { + this.lastResults.unshift({ + ok: false, + timestamp: Date.now(), + error: error.message, + }); + this.lastResults.splice(MAX_LAST_RESULT_ITEMS-1, 1); + const wasLastResultSuccessful = this.lastResults[0]?.ok !== false; + if (wasLastResultSuccessful && error.shouldErrorBeSilent) { + // To avoid short term failures bubbling up, if the error is serious, we still bubble. + return; + } if (!this.hasError) { await this.as.botIntent.sendEvent(this.roomId, { msgtype: 'm.notice', diff --git a/src/Connections/SetupConnection.ts b/src/Connections/SetupConnection.ts index 3386e066..61bf25a2 100644 --- a/src/Connections/SetupConnection.ts +++ b/src/Connections/SetupConnection.ts @@ -14,6 +14,7 @@ import { AdminRoom } from "../AdminRoom"; import { GitLabRepoConnection } from "./GitlabRepo"; import { IConnectionState, ProvisionConnectionOpts } from "./IConnection"; import LogWrapper from "../LogWrapper"; +import { ApiError } from "matrix-appservice-bridge"; const md = new markdown(); const log = new LogWrapper("SetupConnection"); @@ -182,7 +183,11 @@ export class SetupConnection extends CommandConnection { await FeedConnection.validateUrl(url); } catch (err: unknown) { log.debug(`Feed URL '${url}' failed validation: ${err}`); - throw new CommandError("Invalid URL", `${url} doesn't look like a valid feed URL`); + if (err instanceof ApiError) { + throw new CommandError("Invalid URL", err.error); + } else { + throw new CommandError("Invalid URL", `${url} doesn't look like a valid feed URL`); + } } await FeedConnection.provisionConnection(this.roomId, userId, { url, label }, this.provisionOpts); diff --git a/src/api/error.ts b/src/api/error.ts index bd566cf2..93105362 100644 --- a/src/api/error.ts +++ b/src/api/error.ts @@ -1,5 +1,6 @@ import { ErrorObject } from "ajv"; import { NextFunction, Response, Request } from "express"; +import { StatusCodes } from "http-status-codes"; import { IApiError } from "matrix-appservice-bridge"; import LogWrapper from "../LogWrapper"; @@ -53,26 +54,26 @@ export enum ErrCode { MethodNotAllowed = "HS_METHOD_NOT_ALLOWED" } -const ErrCodeToStatusCode: Record = { - HS_UNKNOWN: 500, - HS_NOTFOUND: 404, - HS_UNSUPPORTED_OPERATION: 400, - HS_FORBIDDEN_USER: 403, - HS_FORBIDDEN_BOT: 403, - HS_NOT_IN_ROOM: 403, - HS_BAD_VALUE: 400, - HS_BAD_TOKEN: 401, - HS_DISABLED_FEATURE: 500, - HS_ADDITIONAL_ACTION_REQUIRED: 400, - HS_CONFLICTING_CONNECTION: 409, - HS_METHOD_NOT_ALLOWED: 405, +const ErrCodeToStatusCode: Record = { + HS_UNKNOWN: StatusCodes.INTERNAL_SERVER_ERROR, + HS_NOTFOUND: StatusCodes.NOT_FOUND, + HS_UNSUPPORTED_OPERATION: StatusCodes.BAD_REQUEST, + HS_FORBIDDEN_USER: StatusCodes.FORBIDDEN, + HS_FORBIDDEN_BOT: StatusCodes.FORBIDDEN, + HS_NOT_IN_ROOM: StatusCodes.FORBIDDEN, + HS_BAD_VALUE: StatusCodes.BAD_REQUEST, + HS_BAD_TOKEN: StatusCodes.UNAUTHORIZED, + HS_DISABLED_FEATURE: StatusCodes.INTERNAL_SERVER_ERROR, + HS_ADDITIONAL_ACTION_REQUIRED: StatusCodes.BAD_REQUEST, + HS_CONFLICTING_CONNECTION: StatusCodes.CONFLICT, + HS_METHOD_NOT_ALLOWED: StatusCodes.METHOD_NOT_ALLOWED, } export class ApiError extends Error implements IApiError { constructor( public readonly error: string, public readonly errcode = ErrCode.Unknown, - public readonly statusCode = -1, + public readonly statusCode: number|StatusCodes = -1, public readonly additionalContent: Record = {}, ) { super(`API error ${errcode}: ${error}`); diff --git a/src/feeds/FeedReader.ts b/src/feeds/FeedReader.ts index b0eaaef4..fd4bdab2 100644 --- a/src/feeds/FeedReader.ts +++ b/src/feeds/FeedReader.ts @@ -9,6 +9,8 @@ import Ajv from "ajv"; import axios from "axios"; import Parser from "rss-parser"; import Metrics from "../Metrics"; +import UserAgent from "../UserAgent"; +import { randomUUID } from "crypto"; const log = new LogWrapper("FeedReader"); @@ -16,9 +18,28 @@ export class FeedError extends Error { constructor( public url: string, public cause: Error, + public readonly fetchKey: string, ) { super(`Error fetching feed ${url}: ${cause.message}`); } + + get shouldErrorBeSilent() { + if (axios.isAxiosError(this.cause) && this.cause.response?.status) { + if (this.cause.response.status % 500 < 100) { + // 5XX error, retry these as it might be a server screwup. + return true; + } else if (this.cause.response.status % 400 < 100) { + // 4XX error, actually report these because the server is explicity stating we can't read the resource. + return false; + } + } + if (axios.isAxiosError(this.cause) && this.cause.code === 'ECONNRESET') { + // Fuzzy match this, because it's usually a tempoary error. + return true; + } + // Err on the side of safety and report the rest + return false; + } } export interface FeedEntry { @@ -28,6 +49,10 @@ export interface FeedEntry { }, title: string|null, link: string|null, + /** + * Unique key to identify the specific fetch across entries. + */ + fetchKey: string, } interface AccountData { @@ -65,10 +90,10 @@ export class FeedReader { static readonly seenEntriesEventType = "uk.half-shot.matrix-hookshot.feed.reader.seenEntries"; constructor( - private config: BridgeConfigFeeds, - private connectionManager: ConnectionManager, - private queue: MessageQueue, - private matrixClient: MatrixClient, + private readonly config: BridgeConfigFeeds, + private readonly connectionManager: ConnectionManager, + private readonly queue: MessageQueue, + private readonly matrixClient: MatrixClient, ) { this.connections = this.connectionManager.getAllConnectionsOfType(FeedConnection); this.calculateFeedUrls(); @@ -145,8 +170,15 @@ export class FeedReader { const fetchingStarted = Date.now(); for (const url of this.observedFeedUrls.values()) { + const fetchKey = randomUUID(); try { - const res = await axios.get(url.toString()); + const res = await axios.get(url.toString(), { + headers: { + 'User-Agent': UserAgent, + }, + // We don't want to wait forever for the feed. + timeout: this.config.pollTimeoutSeconds * 1000, + }); const feed = await (new Parser()).parseString(res.data); let initialSync = false; let seenGuids = this.seenEntries.get(url); @@ -182,6 +214,7 @@ export class FeedReader { }, title: item.title ? stripHtml(item.title) : null, link: item.link || null, + fetchKey }; log.debug('New entry:', entry); @@ -200,10 +233,12 @@ export class FeedReader { const newSeenItems = Array.from(new Set([ ...newGuids, ...seenGuids ]).values()).slice(0, maxGuids); this.seenEntries.set(url, newSeenItems); } - } catch (err: any) { - const error = new FeedError(url.toString(), err); - log.error(error.message); - this.queue.push({ eventName: 'feed.error', sender: 'FeedReader', data: error }); + this.queue.push({ eventName: 'feed.success', sender: 'FeedReader', data: undefined}); + } catch (err: unknown) { + const error = err instanceof Error ? err : new Error(`Unknown error ${err}`); + const feedError = new FeedError(url.toString(), error, fetchKey); + log.error("Unable to read feed:", feedError.message); + this.queue.push({ eventName: 'feed.error', sender: 'FeedReader', data: feedError}); } } if (seenEntriesChanged) await this.saveSeenEntries(); diff --git a/vite.config.js b/vite.config.js index e3f3ef4d..8d2c892f 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,9 +1,10 @@ import { defineConfig } from 'vite' import preact from '@preact/preset-vite' +import svgLoader from 'vite-svg-loader' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [preact()], + plugins: [preact(), svgLoader({ defaultImport: 'url'})], root: 'web', base: '', build: { diff --git a/web/IBridgeEvents.ts b/web/IBridgeEvents.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/web/components/elements/ErrorPane.css b/web/components/elements/ErrorPane.css deleted file mode 100644 index 246d9f44..00000000 --- a/web/components/elements/ErrorPane.css +++ /dev/null @@ -1,3 +0,0 @@ -.error-pane { - max-width: 480px; -} \ No newline at end of file diff --git a/web/components/elements/ErrorPane.module.scss b/web/components/elements/ErrorPane.module.scss new file mode 100644 index 00000000..811603b5 --- /dev/null +++ b/web/components/elements/ErrorPane.module.scss @@ -0,0 +1,4 @@ +.errorPane { + max-width: 480px; + color: #FF4B55; +} \ No newline at end of file diff --git a/web/components/elements/ErrorPane.tsx b/web/components/elements/ErrorPane.tsx index 4b4af6bd..c481a603 100644 --- a/web/components/elements/ErrorPane.tsx +++ b/web/components/elements/ErrorPane.tsx @@ -1,9 +1,9 @@ import { h, FunctionComponent } from "preact"; -import "./ErrorPane.css"; +import ErrorBadge from "../../icons/warning-badge.svg"; +import style from "./ErrorPane.module.scss"; export const ErrorPane: FunctionComponent<{header?: string}> = ({ children, header }) => { - return
-

{ header || "Error occured during widget load" }

-

{children}

+ return
+

{ header || "Error occured during widget load" }: {children}

; }; \ No newline at end of file diff --git a/web/components/roomConfig/FeedConnection.module.scss b/web/components/roomConfig/FeedConnection.module.scss new file mode 100644 index 00000000..f42cb608 --- /dev/null +++ b/web/components/roomConfig/FeedConnection.module.scss @@ -0,0 +1,4 @@ +.resultListItem { + list-style: none; + padding-bottom: 1rem; +} \ No newline at end of file diff --git a/web/components/roomConfig/FeedsConfig.tsx b/web/components/roomConfig/FeedsConfig.tsx index bb507a56..0ff7aea1 100644 --- a/web/components/roomConfig/FeedsConfig.tsx +++ b/web/components/roomConfig/FeedsConfig.tsx @@ -4,9 +4,28 @@ import { BridgeConfig } from "../../BridgeAPI"; import { FeedConnectionState, FeedResponseItem } from "../../../src/Connections/FeedConnection"; import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig"; import { Button, ButtonSet, InputField } from "../elements"; +import styles from "./FeedConnection.module.scss"; import FeedsIcon from "../../icons/feeds.png"; +const FeedRecentResults: FunctionComponent<{item: FeedResponseItem}> = ({ item }) => { + if (!item.secrets) { + return null; + } + return <> +

Recent feed results

+ {!item.secrets.lastResults.length && There have been no recent updates for this feed.} +
    + {item.secrets.lastResults.map(item =>
  • + {new Date(item.timestamp).toLocaleString()}: + {item.ok && `✅ Successful fetch`} + {!item.ok && `⚠️ ${item.error}`} +
  • + )} +
+ ; +} + const ConnectionConfiguration: FunctionComponent> = ({existingConnection, onSave, onRemove}) => { const urlRef = createRef(); const labelRef = createRef(); @@ -27,15 +46,20 @@ const ConnectionConfiguration: FunctionComponent - - - - + { existingConnection && } + + + + + + + { canEdit && } { canEdit && existingConnection && } + ; }; diff --git a/web/components/roomConfig/RoomConfig.tsx b/web/components/roomConfig/RoomConfig.tsx index b7eb8355..b67384e1 100644 --- a/web/components/roomConfig/RoomConfig.tsx +++ b/web/components/roomConfig/RoomConfig.tsx @@ -1,6 +1,6 @@ import { h, FunctionComponent } from "preact"; import { useCallback, useEffect, useReducer, useState } from "preact/hooks" -import { BridgeAPI } from "../../BridgeAPI"; +import { BridgeAPI, BridgeAPIError } from "../../BridgeAPI"; import { ErrorPane, ListItem } from "../elements"; import style from "./RoomConfig.module.scss"; import { GetConnectionsResponseItem } from "../../../src/provisioning/api"; @@ -33,7 +33,7 @@ interface IRoomConfigProps(props: IRoomConfigProps) { const { api, roomId, type, headerImg, text, listItemName, connectionEventType } = props; const ConnectionConfigComponent = props.connectionConfigComponent; - const [ error, setError ] = useState(null); + const [ error, setError ] = useState(null); const [ connections, setConnections ] = useState(null); const [ serviceConfig, setServiceConfig ] = useState(null); const [ canEditRoom, setCanEditRoom ] = useState(false); @@ -44,18 +44,28 @@ export const RoomConfig = function(roomId, type).then(res => { setCanEditRoom(res.canEdit); setConnections(res.connections); + setError(null); }).catch(ex => { console.warn("Failed to fetch existing connections", ex); - setError("Failed to fetch existing connections"); + setError({ + header: "Failed to fetch existing connections", + message: ex instanceof BridgeAPIError ? ex.message : "Unknown error" + }); }); }, [api, roomId, type, newConnectionKey]); useEffect(() => { api.getServiceConfig(type) .then(setServiceConfig) + .then(() => { + setError(null); + }) .catch(ex => { console.warn("Failed to fetch service config", ex); - setError("Failed to fetch service config"); + setError({ + header: "Failed to fetch service config", + message: ex instanceof BridgeAPIError ? ex.message : "Unknown error" + }); }) }, [api, type]); @@ -63,15 +73,19 @@ export const RoomConfig = function { // Force reload incrementConnectionKey(undefined); + setError(null); }).catch(ex => { console.warn("Failed to create connection", ex); - setError("Failed to create connection"); + setError({ + header: "Failed to create connection", + message: ex instanceof BridgeAPIError ? ex.message : "Unknown error" + }); }); }, [api, roomId, connectionEventType]); return
{ - error && {error} + error && {error.message} }
@@ -97,17 +111,25 @@ export const RoomConfig = function { // Force reload incrementConnectionKey(undefined); + setError(null); }).catch(ex => { console.warn("Failed to create connection", ex); - setError("Failed to create connection"); + setError({ + header: "Failed to create connection", + message: ex instanceof BridgeAPIError ? ex.message : "Unknown error" + }); }); }} onRemove={() => { api.removeConnection(roomId, c.id).then(() => { setConnections(conn => conn.filter(conn => c.id !== conn.id)); + setError(null); }).catch(ex => { console.warn("Failed to remove connection", ex); - setError("Failed to remove connection"); + setError({ + header: "Failed to remove connection", + message: ex instanceof BridgeAPIError ? ex.message : "Unknown error" + }); }); }} /> diff --git a/web/icons/warning-badge.svg b/web/icons/warning-badge.svg new file mode 100644 index 00000000..b35dabb7 --- /dev/null +++ b/web/icons/warning-badge.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/yarn.lock b/yarn.lock index 933fc638..9bee1541 100644 --- a/yarn.lock +++ b/yarn.lock @@ -330,6 +330,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ== +"@babel/parser@^7.16.4": + version "7.18.13" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4" + integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg== + "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" @@ -1077,6 +1082,11 @@ domhandler "^4.2.0" selderee "^0.6.0" +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -1250,10 +1260,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.31.tgz#a5bb84ecfa27eec5e1c802c6bbf8139bdb163a5d" integrity sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q== -"@types/node@^14": - version "14.18.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.12.tgz#0d4557fd3b94497d793efd4e7d92df2f83b4ef24" - integrity sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A== +"@types/node@^16": + version "16.11.56" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.56.tgz#dcbb617669481e158e0f1c6204d1c768cd675901" + integrity sha512-aFcUkv7EddxxOa/9f74DINReQ/celqH8DiB3fRYgVDM2Xm5QJL8sl80QKuAnGvwAsMn+H3IFA6WCrQh1CY7m1A== "@types/qs@*": version "6.9.7" @@ -1439,6 +1449,64 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@vue/compiler-core@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz#b3c42e04c0e0f2c496ff1784e543fbefe91e215a" + integrity sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/shared" "3.2.37" + estree-walker "^2.0.2" + source-map "^0.6.1" + +"@vue/compiler-dom@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5" + integrity sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ== + dependencies: + "@vue/compiler-core" "3.2.37" + "@vue/shared" "3.2.37" + +"@vue/compiler-sfc@^3.2.20": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4" + integrity sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/compiler-core" "3.2.37" + "@vue/compiler-dom" "3.2.37" + "@vue/compiler-ssr" "3.2.37" + "@vue/reactivity-transform" "3.2.37" + "@vue/shared" "3.2.37" + estree-walker "^2.0.2" + magic-string "^0.25.7" + postcss "^8.1.10" + source-map "^0.6.1" + +"@vue/compiler-ssr@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff" + integrity sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw== + dependencies: + "@vue/compiler-dom" "3.2.37" + "@vue/shared" "3.2.37" + +"@vue/reactivity-transform@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca" + integrity sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/compiler-core" "3.2.37" + "@vue/shared" "3.2.37" + estree-walker "^2.0.2" + magic-string "^0.25.7" + +"@vue/shared@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702" + integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1777,6 +1845,11 @@ body-parser@^1.19.0: raw-body "2.4.3" type-is "~1.6.18" +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2032,6 +2105,11 @@ commander@^2.19.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^7.2.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== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2108,6 +2186,37 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2257,14 +2366,14 @@ domelementtype@^2.0.1, domelementtype@^2.2.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domhandler@^4.0.0, domhandler@^4.2.0: +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== dependencies: domelementtype "^2.2.0" -domutils@^2.5.2: +domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -2695,7 +2804,7 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estree-walker@^2.0.1: +estree-walker@^2.0.1, estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== @@ -3265,6 +3374,11 @@ http-signature@~1.3.1: jsprim "^2.0.2" sshpk "^1.14.1" +http-status-codes@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be" + integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -3853,6 +3967,13 @@ lru-cache@^7.10.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4" integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== +magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -3935,6 +4056,11 @@ matrix-widget-api@^1.0.0: "@types/events" "^3.0.0" events "^3.2.0" +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -4115,7 +4241,7 @@ nanoid@3.1.20: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== -nanoid@^3.3.3: +nanoid@^3.1.30, nanoid@^3.3.3, nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== @@ -4197,6 +4323,13 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -4436,7 +4569,25 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -postcss@^8.3.11, postcss@^8.4.13: +postcss@^8.1.10: + version "8.4.16" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" + integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.3.11: + version "8.4.5" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" + integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== + dependencies: + nanoid "^3.1.30" + picocolors "^1.0.0" + source-map-js "^1.0.1" + +postcss@^8.4.13: version "8.4.13" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== @@ -4971,7 +5122,7 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -4989,11 +5140,16 @@ source-map@^0.5.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.0: +source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + sprintf-js@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" @@ -5029,6 +5185,11 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + stack-trace@0.0.x: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -5177,6 +5338,19 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +svgo@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + tdigest@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021" @@ -5390,6 +5564,14 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite-svg-loader@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/vite-svg-loader/-/vite-svg-loader-3.4.0.tgz#4638827fe86b85ecfcea1ad61dd972c351d5befd" + integrity sha512-xD3yb1FX+f4l9/TmsYIqyki8ncpcVsZ2gEJFh/wLuNNqt55C8OJ+JlcMWOA/Z9gRA+ylV/TA1wmJLxzZkCRqlA== + dependencies: + "@vue/compiler-sfc" "^3.2.20" + svgo "^2.7.0" + vite@^2.9.13: version "2.9.13" resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.13.tgz#859cb5d4c316c0d8c6ec9866045c0f7858ca6abc"