Add a graphql endpoint

This commit is contained in:
Jordan Eldredge 2022-02-20 00:21:55 -08:00
parent 25a88a4ec5
commit 00fef7c7b3
12 changed files with 596 additions and 11 deletions

View file

@ -1,4 +1,5 @@
import router from "./router";
import graphql from "./graphql";
import fileUpload from "express-fileupload";
import cors, { CorsOptions } from "cors";
import bodyParser from "body-parser";
@ -23,8 +24,14 @@ export type ApiAction =
| { type: "GOT_FEEDBACK"; message: string; email?: string; url?: string }
| { type: "SYNCED_TO_ARCHIVE"; successes: number; errors: number }
| { type: "STARTED_SYNC_TO_ARCHIVE"; count: number }
| { type: "POPULAR_TWEET"; bracket: number; url: string, likes: number, date: Date }
| { type: "TWEET_BOT_MILESTONE"; bracket: number; count: number};
| {
type: "POPULAR_TWEET";
bracket: number;
url: string;
likes: number;
date: Date;
}
| { type: "TWEET_BOT_MILESTONE"; bracket: number; count: number };
export type EventHandler = (event: ApiAction) => void;
export type Logger = {
@ -135,6 +142,7 @@ export function createApp({ eventHandler, extraMiddleware, logger }: Options) {
// Add routes
app.use("/", router);
app.use("/graphql", graphql);
// The error handler must be before any other error middleware and after all controllers
if (Sentry) {
@ -143,6 +151,7 @@ export function createApp({ eventHandler, extraMiddleware, logger }: Options) {
// Optional fallthrough error handler
app.use(function onError(err, _req, res, _next) {
console.error(err);
res.statusCode = 500;
res.json({ errorId: res.sentry, message: err.message });
});
@ -160,6 +169,7 @@ const allowList = [
/https:\/\/skins\.webamp\.org/,
/https:\/\/winamp-skin-museum\.pages\.dev/,
/http:\/\/localhost:3000/,
/http:\/\/localhost:3001/,
/netlify.app/,
];

View file

@ -0,0 +1,266 @@
import { Router } from "express";
import { graphqlHTTP } from "express-graphql";
import { buildSchema } from "graphql";
import SkinModel from "../../data/SkinModel";
import { knex } from "../../db";
import SkinResolver from "./resolvers/SkinResolver";
import * as Skins from "../../data/skins";
import UserResolver from "./resolvers/UserResolver";
const router = Router();
const schema = buildSchema(`
"""A classic Winamp skin"""
type Skin {
"""Database ID of the skin"""
id: Int,
"""MD5 hash of the skin's file"""
md5: String,
"""URL of the skin on the Winamp Skin Museum"""
museum_url: String,
"""URL of webamp.org with the skin loaded"""
webamp_url: String,
"""URL of a screenshot of the skin"""
screenshot_url: String,
"""URL to download the skin"""
download_url: String,
"""
Filename of skin when uploaded to the Museum. Note: In some cases a skin
has been uploaded under multiple names. Here we just pick one.
"""
filename: String,
"""Text of the readme file extracted from the skin"""
readme_text: String,
"""Has the skin been flagged as "not safe for wrok"?"""
nsfw: Boolean,
"""String representation (rgb usually) of the skin's average color"""
average_color: String,
"""Has the skin been tweeted?"""
tweeted: Boolean
"""List of @winampskins tweets that mentioned the skin."""
tweets: [Tweet]
"""List of files contained within the skin's .wsz archive"""
archive_files: [ArchiveFile]
"""The skin's "item" at archive.org"""
internet_archive_item: InternetArchiveItem
"""
Times that the skin has been reviewed either on the Museum's Tinder-style
reivew page, or via the Discord bot.
"""
reviews: [Review]
}
"""The judgement made about a skin by a moderator"""
enum Rating {
APPROVED
REJECTED
NSFW
}
"""
A review of a skin. Done either on the Museum's Tinder-style
reivew page, or via the Discord bot.
"""
type Review {
"""The skin that was reviewed"""
skin: Skin
"""
The user who made the review (if known). **Note:** In the early days we didn't
track this, so many will be null.
"""
reviewer: String
"""The rating that the user gave the skin"""
rating: Rating
}
"""A file found within a Winamp Skin's .wsz archive"""
type ArchiveFile {
"""Filename of the file within the archive"""
filename: String,
}
"""A tweet made by @winampskins mentioning a Winamp skin"""
type Tweet {
"""URL of the tweet"""
url: String
"""Number of likes the tweet has received. Updated nightly. (Note: Recent likes on older tweets may not be reflected here)"""
likes: Int
"""Number of retweets the tweet has received. Updated nightly. (Note: Recent retweets on older tweets may not be reflected here)"""
retweets: Int
skin: Skin
}
type InternetArchiveItem {
"""The Internet Archive's unique identifier for this item"""
identifier: String
"""The URL where this item can be viewed on the Internet Archive"""
url: String
"""The skin that this item contains"""
skin: Skin
}
"""A collection of classic Winamp skins"""
type SkinsConnection {
"""The total number of skins"""
count: Int
"""The list of skins"""
nodes: [Skin]
}
type User {
username: String
}
enum SkinsSortOption {
"""
the Museum's (https://skins.webamp.org) special sorting rules.
Roughly speaking, it's:
1. The four classic default skins
2. Tweeted skins first (sorted by the number of likes/retweets)
3. Approved, but not tweeted yet, skins
4. Unreviwed skins
5. Rejected skins
6. NSFW skins
"""
MUSEUM
}
enum SkinsFilterOption {
"""All the skins that have been approved for tweeting"""
APPROVED
}
type Query {
"""The currently authenticated user, if any."""
me: User
"""Get a skin by its MD5 hash"""
fetch_skin_by_md5(md5: String!): Skin
"""
All skins in the database
**Note:** We don't currently support combining sorting and filtering.
"""
skins(
first: Int,
offset: Int,
sort: SkinsSortOption,
filter: SkinsFilterOption
): SkinsConnection
}`);
class SkinsConnection {
_first: number;
_offset: number;
_sort: string;
_filter: string;
constructor(first: number, offset: number, sort: string, filter: string) {
this._first = first;
this._offset = offset;
this._filter = filter;
this._sort = sort;
}
async count() {
const count = await knex("skins")
.where({ skin_type: 1 })
.count("*", { as: "count" });
return count[0].count;
}
async nodes(args, ctx) {
if (this._sort === "MUSEUM") {
if (this._filter) {
throw new Error(
"We don't support combining sorting and filtering at the same time."
);
}
const items = await Skins.getMuseumPage({
first: this._first,
offset: this._offset,
});
return Promise.all(
items.map(async (item) => {
const model = await SkinModel.fromMd5Assert(ctx, item.md5);
return new SkinResolver(model);
})
);
}
let query = knex("skins");
if (this._filter === "APPROVED") {
query = query
.leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5")
.where("review", "APPROVED");
}
const skins = await query
.where({ skin_type: 1 })
.select()
.limit(this._first)
.offset(this._offset);
return skins.map((skin) => {
return new SkinResolver(new SkinModel(ctx, skin));
});
}
}
const root = {
async fetch_skin_by_md5({ md5 }, { ctx }) {
const skin = await SkinModel.fromMd5(ctx, md5);
if (skin == null) {
return null;
}
return new SkinResolver(skin);
},
async skins({ first, offset, sort, filter }) {
if (first > 1000) {
throw new Error("Maximum limit is 1000");
}
return new SkinsConnection(first, offset, sort, filter);
},
me() {
return new UserResolver();
},
};
router.use(
"/",
graphqlHTTP({
typeResolver(_type) {
throw new Error("We probably need to implement typeResolver");
},
schema: schema,
rootValue: root,
graphiql: true,
})
);
export default router;

View file

@ -0,0 +1,11 @@
import ArchiveFileModel from "../../../data/ArchiveFileModel";
export default class ArchiveFileResolver {
_model: ArchiveFileModel;
constructor(model: ArchiveFileModel) {
this._model = model;
}
filename() {
return this._model.getFileName();
}
}

View file

@ -0,0 +1,22 @@
import IaItemModel from "../../../data/IaItemModel";
import SkinResolver from "./SkinResolver";
export default class InternetArchiveItemResolver {
_model: IaItemModel;
constructor(model: IaItemModel) {
this._model = model;
}
identifier() {
return this._model.getIdentifier();
}
url() {
return this._model.getUrl();
}
async skin() {
const skin = await this._model.getSkin();
if (skin == null) {
return null;
}
return new SkinResolver(skin);
}
}

View file

@ -0,0 +1,14 @@
import { ReviewRow } from "../../../types";
export default class ReviewResolver {
_model: ReviewRow;
constructor(model: ReviewRow) {
this._model = model;
}
reviewer() {
return this._model.reviewer;
}
rating() {
return this._model.review;
}
}

View file

@ -0,0 +1,65 @@
import SkinModel from "../../../data/SkinModel";
import ArchiveFileResolver from "./ArchiveFileResolver";
import InternetArchiveItemResolver from "./InternetArchiveItemResolver";
import ReviewResolver from "./ReviewResolver";
import TweetResolver from "./TweetResolver";
export default class SkinResolver {
_model: SkinModel;
constructor(model: SkinModel) {
this._model = model;
}
id() {
return this._model.getId();
}
md5() {
return this._model.getMd5();
}
filename() {
return this._model.getFileName();
}
museum_url() {
return this._model.getMuseumUrl();
}
webamp_url() {
return this._model.getWebampUrl();
}
screenshot_url() {
return this._model.getScreenshotUrl();
}
download_url() {
return this._model.getSkinUrl();
}
readme_text() {
return this._model.getReadme();
}
nsfw() {
return this._model.getIsNsfw();
}
average_color() {
return this._model.getAverageColor();
}
tweeted() {
return this._model.tweeted();
}
async tweets() {
const tweets = await this._model.getTweets();
return tweets.map((tweetModel) => new TweetResolver(tweetModel));
}
async archive_files() {
const files = await this._model.getArchiveFiles();
return files.map((file) => new ArchiveFileResolver(file));
}
async internet_archive_item() {
const item = await this._model.getIaItem();
if (item == null) {
return null;
}
return new InternetArchiveItemResolver(item);
}
async reviews() {
const reviews = await this._model.getReviews();
return reviews.map((row) => new ReviewResolver(row));
}
}

View file

@ -0,0 +1,26 @@
import TweetModel from "../../../data/TweetModel";
import SkinResolver from "./SkinResolver";
export default class TweetResolver {
_model: TweetModel;
constructor(model: TweetModel) {
this._model = model;
}
url() {
return this._model.getUrl();
}
likes() {
return this._model.getLikes();
}
retweets() {
return this._model.getRetweets();
}
async skin() {
const skin = await this._model.getSkin();
if (skin == null) {
return null;
}
return new SkinResolver(skin);
}
}

View file

@ -0,0 +1,6 @@
export default class UserResolver {
username(_args, ctx) {
// For now every user is the current user.
return ctx.user.username;
}
}

View file

@ -35,8 +35,8 @@ export default class SkinModel {
md5: string
): Promise<SkinModel> {
const skin = await SkinModel.fromMd5(ctx, md5);
if(skin == null) {
throw new Error(`Expected to find skin with md5 "${md5}".`);
if (skin == null) {
throw new Error(`Expected to find skin with md5 "${md5}".`);
}
return skin;
}
@ -79,6 +79,10 @@ export default class SkinModel {
throw new Error(`Unknown skin_type ${this.row.skin_type}`);
}
getId(): number {
return this.row.id;
}
async tweeted(): Promise<boolean> {
return (await this.getTweet()) != null;
}

View file

@ -24,7 +24,9 @@
"express": "^4.17.1",
"express-async-handler": "^1.1.4",
"express-fileupload": "^1.1.7-alpha.3",
"express-graphql": "^0.12.0",
"express-sitemap-xml": "^2.0.0",
"graphql": "14.7.0",
"imagemin": "^7.0.0",
"imagemin-optipng": "^7.0.0",
"knex": "^0.21.1",
@ -35,7 +37,7 @@
"sharp": "^0.28.0",
"sqlite3": "^5.0.2",
"temp": "^0.9.0",
"ts-node": "^8.10.2",
"ts-node": "^10.5.0",
"twit": "^2.2.11",
"winston": "^3.2.1",
"yargs": "^13.2.4"
@ -63,5 +65,8 @@
"prettier": "^2.3.2",
"supertest": "^6.0.1",
"typescript": "^3.8.3"
},
"resolutions": {
"graphql": "15.3.0"
}
}

View file

@ -8,6 +8,7 @@ export type TweetStatus =
export type SkinType = "MODERN" | "CLASSIC";
export type SkinRow = {
id: number;
md5: string;
skin_type: number;
emails: string;

167
yarn.lock
View file

@ -2132,6 +2132,18 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==
"@cspotcode/source-map-support@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5"
integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==
dependencies:
"@cspotcode/source-map-consumer" "0.8.0"
"@discordjs/collection@^0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.1.6.tgz#9e9a7637f4e4e0688fd8b2b5c63133c91607682c"
@ -2948,6 +2960,26 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==
"@tsconfig/node12@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c"
integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==
"@tsconfig/node14@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==
"@tsconfig/node16@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==
"@types/anymatch@*":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
@ -3729,6 +3761,14 @@ abort-controller@^3.0.0:
dependencies:
event-target-shim "^5.0.0"
accepts@^1.3.7:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@ -3772,6 +3812,11 @@ acorn-walk@^8.0.0:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.0.tgz#56ae4c0f434a45fff4a125e7ea95fa9c98f67a16"
integrity sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^5.5.3:
version "5.7.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
@ -3793,6 +3838,11 @@ acorn@^8.0.4:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.4.tgz#7a3ae4191466a6984eee0fe3407a4f3aa9db8354"
integrity sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==
acorn@^8.4.1:
version "8.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
add-dom-event-listener@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310"
@ -4925,6 +4975,11 @@ bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
bytes@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
cac@^6.7.2:
version "6.7.3"
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.3.tgz#10410b8611677990cc2e3c8b576d471c1d71b768"
@ -5579,7 +5634,7 @@ content-disposition@0.5.3, content-disposition@^0.5.2:
dependencies:
safe-buffer "5.1.2"
content-type@~1.0.4:
content-type@^1.0.4, content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
@ -5727,6 +5782,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -7297,6 +7357,16 @@ express-fileupload@^1.1.7-alpha.3:
dependencies:
busboy "^0.3.1"
express-graphql@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/express-graphql/-/express-graphql-0.12.0.tgz#58deabc309909ca2c9fe2f83f5fbe94429aa23df"
integrity sha512-DwYaJQy0amdy3pgNtiTDuGGM2BLdj+YO2SgbKoLliCfuHv3VVTt7vNG/ZqK2hRYjtYHE2t2KB705EU94mE64zg==
dependencies:
accepts "^1.3.7"
content-type "^1.0.4"
http-errors "1.8.0"
raw-body "^2.4.1"
express-sitemap-xml@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/express-sitemap-xml/-/express-sitemap-xml-2.0.0.tgz#190b4993458007a61b4747a66e3a723121a207f6"
@ -8279,6 +8349,13 @@ graceful-fs@^4.2.3:
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
graphql@14.7.0:
version "14.7.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72"
integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==
dependencies:
iterall "^1.2.2"
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -8588,6 +8665,28 @@ http-errors@1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-errors@1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507"
integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==
dependencies:
depd "~1.1.2"
inherits "2.0.4"
setprototypeof "1.2.0"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-errors@1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==
dependencies:
depd "~1.1.2"
inherits "2.0.4"
setprototypeof "1.2.0"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.1"
http-errors@~1.6.2:
version "1.6.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
@ -9472,6 +9571,11 @@ isurl@^1.0.0-alpha5:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
iterall@^1.2.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
jest-changed-files@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039"
@ -11375,12 +11479,24 @@ mime-db@1.44.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0:
version "1.44.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
mime-db@1.51.0:
version "1.51.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
version "2.1.27"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
dependencies:
mime-db "1.44.0"
mime-types@~2.1.34:
version "2.1.34"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
dependencies:
mime-db "1.51.0"
mime@1.6.0, mime@^1.3.4, mime@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
@ -11689,6 +11805,11 @@ negotiator@0.6.2, negotiator@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
neo-async@^2.5.0, neo-async@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
@ -13449,6 +13570,16 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
raw-body@^2.4.1:
version "2.4.3"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c"
integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==
dependencies:
bytes "3.1.2"
http-errors "1.8.1"
iconv-lite "0.4.24"
unpipe "1.0.0"
rc-align@^2.4.0:
version "2.4.5"
resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-2.4.5.tgz#c941a586f59d1017f23a428f0b468663fb7102ab"
@ -14439,6 +14570,11 @@ setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
setprototypeof@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
sha.js@^2.4.0, sha.js@^2.4.8:
version "2.4.11"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
@ -14687,7 +14823,7 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.12:
source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
dependencies:
@ -15513,6 +15649,11 @@ toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
toidentifier@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
token-types@^1.0.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/token-types/-/token-types-1.3.2.tgz#2cd58386be7e1ff316cd6b8d5aa25ea070deb2f0"
@ -15594,14 +15735,23 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
ts-node@^8.10.2:
version "8.10.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
ts-node@^10.5.0:
version "10.5.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.5.0.tgz#618bef5854c1fbbedf5e31465cbb224a1d524ef9"
integrity sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw==
dependencies:
"@cspotcode/source-map-support" "0.7.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
v8-compile-cache-lib "^3.0.0"
yn "3.1.1"
tslib@^1.10.0:
@ -15992,6 +16142,11 @@ uuid@^8.3.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache-lib@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8"
integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==
v8-compile-cache@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"