From 31c0e95131f41f306d3fb2a0a79aeab0c7f60b57 Mon Sep 17 00:00:00 2001
From: Roland Bernard <rolbernard@unibz.it>
Date: Fri, 30 Apr 2021 23:18:41 +0200
Subject: [PATCH] Multiple changes to the database and api

- Added an icon to the tasks
- Added a color to the porjects
- Added an image to the users
- Removed unnecessary fields
- Added a API route to update tasks
- Fixed the API route to get tasks
---
 server/migrations/0000_initial.ts |   5 +-
 server/package.json               |   4 +
 server/src/index.ts               |   2 +
 server/src/v1/project.ts          |  99 ++++++------
 server/src/v1/task.ts             | 196 ++++++++++++++++++++++-
 server/src/v1/user.ts             |  94 ++++++++++-
 server/yarn.lock                  | 251 ++++++++++++++++++++++++++++--
 7 files changed, 580 insertions(+), 71 deletions(-)

diff --git a/server/migrations/0000_initial.ts b/server/migrations/0000_initial.ts
index 1a867e1..a76b150 100644
--- a/server/migrations/0000_initial.ts
+++ b/server/migrations/0000_initial.ts
@@ -9,6 +9,7 @@ export async function up(database: Knex): Promise<void> {
             table.string('passwd_hash', 60).notNullable();
             table.text('email');
             table.text('real_name');
+            table.binary('image');
         })
         .createTable('teams', table => {
             table.uuid('id').notNullable().primary();
@@ -28,6 +29,7 @@ export async function up(database: Knex): Promise<void> {
         .createTable('projects', table => {
             table.uuid('id').notNullable().primary();
             table.text('name').notNullable();
+            table.string('color').notNullable();
         })
         .createTable('team_projects', table => {
             table.uuid('project_id').notNullable().references('projects.id');
@@ -39,6 +41,7 @@ export async function up(database: Knex): Promise<void> {
             table.uuid('project_id').notNullable().references('projects.id');
             table.text('name').notNullable();
             table.text('text').notNullable();
+            table.string('icon').notNullable();
             table.enum('status', [ 'open', 'closed', 'suspended' ]).notNullable();
             table.enum('priority', [ 'low', 'medium', 'high', 'urgent' ]).notNullable();
             table.timestamp('created').notNullable();
@@ -59,8 +62,6 @@ export async function up(database: Knex): Promise<void> {
             table.uuid('user_id').notNullable().references('users.id');
             table.uuid('task_id').notNullable().references('tasks.id');
             table.primary(['user_id', 'task_id']);
-            table.boolean('assigned').notNullable();
-            table.boolean('working').notNullable();
         })
         .createTable('comments', table => {
             table.uuid('id').notNullable().primary();
diff --git a/server/package.json b/server/package.json
index 89c56c1..a026e72 100644
--- a/server/package.json
+++ b/server/package.json
@@ -11,8 +11,10 @@
         "bcrypt": "^5.0.1",
         "body-parser": "^1.19.0",
         "express": "^4.17.1",
+        "express-fileupload": "^1.2.1",
         "jsonwebtoken": "^8.5.1",
         "knex": "^0.95.4",
+        "sharp": "^0.28.1",
         "sqlite3": "^5.0.2",
         "uuid": "^8.3.2"
     },
@@ -25,9 +27,11 @@
         "@types/bcrypt": "^3.0.1",
         "@types/body-parser": "^1.19.0",
         "@types/express": "^4.17.11",
+        "@types/express-fileupload": "^1.1.6",
         "@types/jest": "^26.0.22",
         "@types/jsonwebtoken": "^8.5.1",
         "@types/node": "^14.14.37",
+        "@types/sharp": "^0.28.0",
         "@types/uuid": "^8.3.0",
         "jest": "^26.6.3",
         "nodemon": "^2.0.7",
diff --git a/server/src/index.ts b/server/src/index.ts
index 11ab5bb..3c6809c 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -1,6 +1,7 @@
 
 import express, { Request, Response, NextFunction } from 'express';
 import { json as bodyJson } from 'body-parser';
+import fileupload from 'express-fileupload';
 
 import { port } from './config';
 import { addDefaultHeaders } from './headers';
@@ -10,6 +11,7 @@ const app = express();
 
 app.use(addDefaultHeaders);
 app.use(bodyJson());
+app.use(fileupload());
 
 app.use('/v1', v1);
 
diff --git a/server/src/v1/project.ts b/server/src/v1/project.ts
index 3f76f3d..dabed66 100644
--- a/server/src/v1/project.ts
+++ b/server/src/v1/project.ts
@@ -85,11 +85,12 @@ project.get('/:uuid', async (req, res) => {
 interface AddProjectBody {
     teams: Array<string>;
     name: string;
+    color: string;
     token: Token;
 }
 
 project.post('/', async (req, res) => {
-    if (isOfType<AddProjectBody>(req.body, [['teams', 'object'], ['name', 'string']])) {
+    if (isOfType<AddProjectBody>(req.body, [['teams', 'object'], ['name', 'string'], ['color', 'string']])) {
         try {
             const team_ids = req.body.teams;
             for (const team_id of team_ids) {
@@ -101,36 +102,44 @@ project.post('/', async (req, res) => {
                     return;
                 }
             }
-            const project_id = uuid();
-            const team = await database('users')
-                .innerJoin('team_members', 'users.id', 'team_members.user_id')
-                .select({ id: 'team_members.team_id' })
-                .where({
-                    'users.id': req.body.token.id,
-                })
-                .whereIn('team_members.team_id', team_ids);
-            if (team.length === team_ids.length) {
-                await database.transaction(async transaction => {
-                    await transaction('projects').insert({
-                        id: project_id,
-                        name: req.body.name,
-                    });
-                    await transaction('team_projects').insert(
-                        team_ids.map(team_id => ({
-                            project_id: project_id,
-                            team_id: team_id,
-                        }))
-                    );
-                });
-                res.status(200).json({
-                    status: 'success',
-                    id: project_id,
-                });
-            } else {
-                res.status(404).json({
+            const color = req.body.color;
+            if (!color.match(/^#[0-9a-fA-F]{6}$/)) {
+                res.status(400).json({
                     status: 'error',
-                    message: 'team not found',
+                    message: 'malformed color',
                 });
+            } else {
+                const project_id = uuid();
+                const team = await database('users')
+                    .innerJoin('team_members', 'users.id', 'team_members.user_id')
+                    .select({ id: 'team_members.team_id' })
+                    .where({
+                        'users.id': req.body.token.id,
+                    })
+                    .whereIn('team_members.team_id', team_ids);
+                if (team.length === team_ids.length) {
+                    await database.transaction(async transaction => {
+                        await transaction('projects').insert({
+                            id: project_id,
+                            name: req.body.name,
+                        });
+                        await transaction('team_projects').insert(
+                            team_ids.map(team_id => ({
+                                project_id: project_id,
+                                team_id: team_id,
+                            }))
+                        );
+                    });
+                    res.status(200).json({
+                        status: 'success',
+                        id: project_id,
+                    });
+                } else {
+                    res.status(404).json({
+                        status: 'error',
+                        message: 'team not found',
+                    });
+                }
             }
         } catch (e) {
             res.status(400).json({
@@ -147,9 +156,10 @@ project.post('/', async (req, res) => {
 });
 
 interface UpdateProjectBody {
-    add_teams?: Array<string>;
     remove_teams?: Array<string>;
+    add_teams?: Array<string>;
     name?: string;
+    color?: string;
     token: Token;
 }
 
@@ -184,13 +194,20 @@ project.put('/:uuid', async (req, res) => {
                     });
                 if (projects.length >= 1) {
                     await database.transaction(async transaction => {
-                        if (typeof req.body.name === 'string') {
-                            await transaction('projects')
-                                .update({
-                                    name: req.body.name,
-                                }).where({
-                                    id: id,
-                                });
+                        await transaction('projects')
+                            .update({
+                                name: req.body.name,
+                                color: req.body.color,
+                            }).where({
+                                id: id,
+                            });
+                        if (remove_team_ids.length !== 0) {
+                            await transaction('team_projects')
+                                .delete()
+                                .where({
+                                    'project_id': id,
+                                })
+                                .whereIn('team_id', remove_team_ids);
                         }
                         if (add_team_ids.length !== 0) {
                             await transaction('team_projects').insert(
@@ -200,14 +217,6 @@ project.put('/:uuid', async (req, res) => {
                                 }))
                             );
                         }
-                        if (remove_team_ids.length !== 0) {
-                            await transaction('team_projects')
-                                .delete()
-                                .where({
-                                    'project_id': id,
-                                })
-                                .whereIn('team_id', remove_team_ids);
-                        }
                     });
                     res.status(200).json({
                         status: 'success',
diff --git a/server/src/v1/task.ts b/server/src/v1/task.ts
index 5d60576..486c755 100644
--- a/server/src/v1/task.ts
+++ b/server/src/v1/task.ts
@@ -17,7 +17,7 @@ task.get('/:uuid', async (req, res) => {
             const task = await database('users')
                 .innerJoin('team_members', 'users.id', 'team_members.user_id')
                 .innerJoin('team_projects', 'team_members.team_id', 'team_projects.team_id')
-                .innerJoin('tasks', 'team_projects.project_id', 'task.team_id')
+                .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id')
                 .select({
                     id: 'tasks.id',
                     project: 'tasks.project_id',
@@ -32,10 +32,40 @@ task.get('/:uuid', async (req, res) => {
                     'users.id': req.body.token.id,
                     'tasks.id': id,
                 });
-            if (task.length === 1) {
+            if (task.length >= 1) {
+                const assigned = await database('task_assignees')
+                    .select({
+                        id: 'task_assignees.user_id',
+                    })
+                    .where({
+                        'task_assignees.task_id': id,
+                    });
+                const dependentcies = await database('task_dependencies')
+                    .select({
+                        id: 'task_dependencies.requires_id',
+                    })
+                    .where({
+                        'task_dependencies.task_id': id,
+                    });
+                const requirements = await database('task_requirements')
+                    .select({
+                        role: 'task_requirements.role_id',
+                        time: 'task_requirements.time'
+                    })
+                    .where({
+                        'task_requirements.task_id': id,
+                    });
                 res.status(200).json({
                     status: 'success',
-                    task: task[0],
+                    task: {
+                        ...task[0],
+                        assigned: assigned.map(row => row.id),
+                        dependentcies: dependentcies.map(row => row.id),
+                        requirements: requirements.map(row => ({
+                            role: row.role,
+                            time: row.time,
+                        })),
+                    },
                 });
             } else {
                 res.status(404).json({
@@ -66,23 +96,26 @@ interface AddTaskBody {
     project: string;
     name: string;
     text: string;
+    icon: string;
     priority: string;
     dependentcies: Array<string>;
     requirements: Array<TaskRequirement>;
+    assigned: Array<string>;
     token: Token;
 }
 
 task.post('/', async (req, res) => {
     if (isOfType<AddTaskBody>(req.body, [
-        ['project', 'string'], ['name', 'string'], ['text', 'string'],
+        ['project', 'string'], ['name', 'string'], ['text', 'string'], ['icon', 'string'],
         ['priority', 'string'], ['dependentcies', 'object'], ['requirements', 'object'],
     ])) {
         try {
             const project_id = req.body.project;
             const dependentcy_ids = req.body.dependentcies;
+            const assigned_ids = req.body.assigned;
             const requirements = req.body.requirements;
             const requirement_ids = requirements.map(req => req.role);
-            for (const team_id of dependentcy_ids.concat(requirement_ids).concat([ project_id ])) {
+            for (const team_id of [ ...dependentcy_ids, ...requirement_ids, ...assigned_ids, project_id ]) {
                 if (!validate(team_id)) {
                     res.status(400).json({
                         status: 'error',
@@ -100,8 +133,7 @@ task.post('/', async (req, res) => {
                 .where({
                     'users.id': req.body.token.id,
                     'projects.id': project_id,
-                })
-                .groupBy('projects.id');
+                });
             if (project.length >= 1) {
                 await database.transaction(async transaction => {
                     await transaction('tasks').insert({
@@ -109,6 +141,7 @@ task.post('/', async (req, res) => {
                         project_id: project_id,
                         name: req.body.name,
                         text: req.body.text,
+                        icon: req.body.icon,
                         priority: req.body.priority,
                         status: 'open',
                         created: new Date(),
@@ -131,6 +164,14 @@ task.post('/', async (req, res) => {
                             }))
                         );
                     }
+                    if (assigned_ids.length !== 0) {
+                        await transaction('task_assignees').insert(
+                            assigned_ids.map(user_id => ({
+                                task_id: task_id,
+                                user_id: user_id,
+                            }))
+                        );
+                    }
                 });
                 res.status(200).json({
                     status: 'success',
@@ -143,7 +184,6 @@ task.post('/', async (req, res) => {
                 });
             }
         } catch (e) {
-            console.error(e);
             res.status(400).json({
                 status: 'error',
                 message: 'failed to create task',
@@ -157,5 +197,145 @@ task.post('/', async (req, res) => {
     }
 });
 
+interface UpdateTaskBody {
+    name?: string;
+    text?: string;
+    icon?: string;
+    priority?: string;
+    status?: string;
+    remove_dependentcies?: Array<string>;
+    remove_requirements?: Array<string>;
+    remove_assigned?: Array<string>;
+    add_dependentcies?: Array<string>;
+    add_requirements?: Array<TaskRequirement>;
+    add_assigned?: Array<string>;
+    token: Token;
+}
+
+task.put('/:uuid', async (req, res) => {
+    if (isOfType<UpdateTaskBody>(req.body, [])) {
+        try {
+            const task_id = req.params.uuid;
+            const remove_dependentcy_ids = req.body.remove_dependentcies ?? [];
+            const remove_assigned_ids = req.body.remove_assigned ?? [];
+            const remove_requirement_ids = req.body.remove_requirements ?? [];
+            const add_dependentcy_ids = req.body.add_dependentcies ?? [];
+            const add_assigned_ids = req.body.add_assigned ?? [];
+            const add_requirements = req.body.add_requirements ?? [];
+            const add_requirement_ids = add_requirements.map(req => req.role);
+            const all_ids = [
+                ...remove_requirement_ids,
+                ...remove_assigned_ids,
+                ...remove_dependentcy_ids,
+                ...add_requirement_ids,
+                ...add_assigned_ids,
+                ...add_dependentcy_ids,
+                task_id,
+            ];
+            for (const team_id of all_ids) {
+                if (!validate(team_id)) {
+                    res.status(400).json({
+                        status: 'error',
+                        message: 'malformed uuid',
+                    });
+                    return;
+                }
+            }
+            const task = await database('users')
+                .innerJoin('team_members', 'users.id', 'team_members.user_id')
+                .innerJoin('team_projects', 'team_members.team_id', 'team_projects.team_id')
+                .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id')
+                .select({ id: 'tasks.id' })
+                .where({
+                    'users.id': req.body.token.id,
+                    'tasks.id': task_id,
+                });
+            if (task.length >= 1) {
+                await database.transaction(async transaction => {
+                    await transaction('tasks')
+                        .update({
+                            name: req.body.name,
+                            text: req.body.text,
+                            icon: req.body.icon,
+                            priority: req.body.priority,
+                            status: req.body.status,
+                            edited: new Date(),
+                        })
+                        .where({
+                            'tasks.id': task_id,
+                        });
+                    if (remove_requirement_ids.length !== 0) {
+                        await transaction('task_requirements')
+                            .delete()
+                            .where({
+                                'task_id': task_id,
+                            })
+                            .whereIn('role_id', remove_requirement_ids);
+                    }
+                    if (remove_dependentcy_ids.length !== 0) {
+                        await transaction('task_dependencies')
+                            .delete()
+                            .where({
+                                'task_id': task_id,
+                            })
+                            .whereIn('requires_id', remove_dependentcy_ids);
+                    }
+                    if (remove_assigned_ids.length !== 0) {
+                        await transaction('task_assignees')
+                            .delete()
+                            .where({
+                                'task_id': task_id,
+                            })
+                            .whereIn('user_id', remove_assigned_ids);
+                    }
+                    if (add_requirements.length !== 0) {
+                        await transaction('task_requirements').insert(
+                            add_requirements.map(requirement => ({
+                                task_id: task_id,
+                                role_id: requirement.role,
+                                time: requirement.time,
+                            }))
+                        );
+                    }
+                    if (add_dependentcy_ids.length !== 0) {
+                        await transaction('task_dependencies').insert(
+                            add_dependentcy_ids.map(dependentcy_id => ({
+                                task_id: task_id,
+                                requires_id: dependentcy_id,
+                            }))
+                        );
+                    }
+                    if (add_assigned_ids.length !== 0) {
+                        await transaction('task_assignees').insert(
+                            add_assigned_ids.map(user_id => ({
+                                task_id: task_id,
+                                user_id: user_id,
+                            }))
+                        );
+                    }
+                });
+                res.status(200).json({
+                    status: 'success',
+                });
+            } else {
+                res.status(404).json({
+                    status: 'error',
+                    message: 'task not found',
+                });
+            }
+        } catch (e) {
+            res.status(400).json({
+                status: 'error',
+                message: 'failed to update task',
+            });
+        }
+    } else {
+        res.status(400).json({
+            status: 'error',
+            message: 'missing request fields',
+        });
+    }
+});
+
 export default task;
 
diff --git a/server/src/v1/user.ts b/server/src/v1/user.ts
index 9d0a522..822183b 100644
--- a/server/src/v1/user.ts
+++ b/server/src/v1/user.ts
@@ -1,6 +1,8 @@
 
-import express from 'express';
+import express, { Request, Response } from 'express';
 import { validate } from 'uuid';
+import sharp from 'sharp';
+import { UploadedFile } from 'express-fileupload';
 
 import database from '../database';
 import { isOfType } from '../util';
@@ -17,7 +19,7 @@ user.get('/name/:username', async (req, res) => {
                 username: 'user_name',
             })
             .where({ username: name });
-        if (user.length === 1) {
+        if (user.length >= 1) {
             res.status(200).json({
                 status: 'success',
                 user: user[0],
@@ -48,7 +50,7 @@ user.get('/', async (req, res) => {
                 realname: 'real_name',
             })
             .where({ id: req.body.token.id });
-        if (user.length === 1) {
+        if (user.length >= 1) {
             res.status(200).json({
                 status: 'success',
                 user: user[0],
@@ -85,7 +87,7 @@ user.put('/', async (req, res) => {
                     real_name: req.body.realname,
                 })
                 .where({ id: req.body.token.id });
-            if (updated === 1) {
+            if (updated >= 1) {
                 res.status(200).json({
                     status: 'success',
                 });
@@ -109,6 +111,43 @@ user.put('/', async (req, res) => {
     }
 });
 
+user.put('/image', async (req, res) => {
+    if (req.files?.image && isOfType<UploadedFile>(req.files.image, [['data', 'object']])) {
+        try {
+            const image = await sharp(req.files.image.data).resize(128, 128, {
+                fit: "contain",
+            }).png({
+                compressionLevel: 9,
+            }).toBuffer();
+            const updated = await database('users')
+                .update({
+                    image: image,
+                })
+                .where({ id: req.body.token.id });
+            if (updated >= 1) {
+                res.status(200).json({
+                    status: 'success',
+                });
+            } else {
+                res.status(404).json({
+                    status: 'error',
+                    message: 'user not found',
+                });
+            }
+        } catch (e) {
+            res.status(400).json({
+                status: 'error',
+                message: 'failed to update user',
+            });
+        }
+    } else {
+        res.status(400).json({
+            status: 'error',
+            message: 'missing image file',
+        });
+    }
+});
+
 user.get('/:uuid', async (req, res) => {
     try {
         const id = req.params.uuid;
@@ -121,7 +160,7 @@ user.get('/:uuid', async (req, res) => {
                     realname: 'real_name',
                 })
                 .where({ id: id });
-            if (user.length === 1) {
+            if (user.length >= 1) {
                 res.status(200).json({
                     status: 'success',
                     user: user[0],
@@ -146,4 +185,49 @@ user.get('/:uuid', async (req, res) => {
     }
 });
 
+function sendRangedData(req: Request, res: Response, data: Buffer) {
+    res.setHeader('Accept-Ranges', 'bytes');
+    const range = req.range(data.length);
+    if (typeof range === 'object' && range.type === 'bytes' && range.length === 1) {
+        res.status(206);
+        res.setHeader('Content-Range', `${range[0].start}\-${range[0].end}/${data.length}`);
+        res.send(data.slice(range[0].start, range[0].end+1));
+    } else {
+        res.status(200);
+        res.send(data);
+    }
+}
+
+user.get('/:uuid/image', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const user = await database('users')
+                .select({
+                    image: 'users.image'
+                })
+                .where({ id: id });
+            if (user.length >= 1) {
+                res.setHeader('Content-Type', 'image/png');
+                sendRangedData(req, res, user[0].image);
+            } else {
+                res.status(404).json({
+                    status: 'error',
+                    message: 'user not found',
+                });
+            }
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get user',
+        });
+    }
+});
+
 export default user;
diff --git a/server/yarn.lock b/server/yarn.lock
index a283166..ed1f5c9 100644
--- a/server/yarn.lock
+++ b/server/yarn.lock
@@ -577,6 +577,13 @@
   dependencies:
     "@types/node" "*"
 
+"@types/express-fileupload@^1.1.6":
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/@types/express-fileupload/-/express-fileupload-1.1.6.tgz#fdc3b1d6f54fe4522867f8550f0acf24bcbf5c45"
+  integrity sha512-8z92PCVgvWvG1TpxucRU9oRz3hZc5cUz+CkeDe4XwVmg2DJDdd/7QASMsJzIo+9Pbfp7LfTEWSeEFUJZBohv9g==
+  dependencies:
+    "@types/express" "*"
+
 "@types/express-serve-static-core@^4.17.18":
   version "4.17.19"
   resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d"
@@ -586,7 +593,7 @@
     "@types/qs" "*"
     "@types/range-parser" "*"
 
-"@types/express@^4.17.11":
+"@types/express@*", "@types/express@^4.17.11":
   version "4.17.11"
   resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545"
   integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==
@@ -675,6 +682,13 @@
     "@types/mime" "^1"
     "@types/node" "*"
 
+"@types/sharp@^0.28.0":
+  version "0.28.0"
+  resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.28.0.tgz#d61865182e386f1ec8d8b6a052da846695638a84"
+  integrity sha512-YvRFnQM44wAihAKzBDzu3BxnEohpqWd/5KXkwsSUl3qFTb51NyKHCKHX1D62YAy0jZij5aXgm/33v/Cv6VVsdA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/stack-utils@^2.0.0":
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
@@ -972,6 +986,11 @@ balanced-match@^1.0.0:
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
   integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
+base64-js@^1.3.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
 base@^0.11.1:
   version "0.11.2"
   resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@@ -1005,6 +1024,15 @@ binary-extensions@^2.0.0:
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
   integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 
+bl@^4.0.3:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+  integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
+  dependencies:
+    buffer "^5.5.0"
+    inherits "^2.0.4"
+    readable-stream "^3.4.0"
+
 block-stream@*:
   version "0.0.9"
   resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
@@ -1113,6 +1141,21 @@ buffer-from@1.x, buffer-from@^1.0.0:
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
   integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
 
+buffer@^5.5.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
+  dependencies:
+    base64-js "^1.3.1"
+    ieee754 "^1.1.13"
+
+busboy@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
+  integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==
+  dependencies:
+    dicer "0.3.0"
+
 bytes@3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
@@ -1297,7 +1340,7 @@ collection-visit@^1.0.0:
     map-visit "^1.0.0"
     object-visit "^1.0.0"
 
-color-convert@^1.9.0:
+color-convert@^1.9.0, color-convert@^1.9.1:
   version "1.9.3"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
   integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
@@ -1316,11 +1359,27 @@ color-name@1.1.3:
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
   integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
 
-color-name@~1.1.4:
+color-name@^1.0.0, color-name@~1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
+color-string@^1.5.4:
+  version "1.5.5"
+  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014"
+  integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==
+  dependencies:
+    color-name "^1.0.0"
+    simple-swizzle "^0.2.2"
+
+color@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
+  integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
+  dependencies:
+    color-convert "^1.9.1"
+    color-string "^1.5.4"
+
 colorette@1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
@@ -1515,6 +1574,13 @@ decompress-response@^3.3.0:
   dependencies:
     mimic-response "^1.0.0"
 
+decompress-response@^4.2.0:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
+  integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
+  dependencies:
+    mimic-response "^2.0.0"
+
 deep-extend@^0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
@@ -1587,6 +1653,13 @@ detect-newline@^3.0.0:
   resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
   integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
 
+dicer@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
+  integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==
+  dependencies:
+    streamsearch "0.1.2"
+
 diff-sequences@^26.6.2:
   version "26.6.2"
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
@@ -1661,7 +1734,7 @@ encodeurl@~1.0.2:
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
 
-end-of-stream@^1.1.0:
+end-of-stream@^1.1.0, end-of-stream@^1.4.1:
   version "1.4.4"
   resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
   integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@@ -1788,6 +1861,11 @@ expand-brackets@^2.1.4:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+expand-template@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
+  integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
+
 expect@^26.6.2:
   version "26.6.2"
   resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417"
@@ -1800,6 +1878,13 @@ expect@^26.6.2:
     jest-message-util "^26.6.2"
     jest-regex-util "^26.0.0"
 
+express-fileupload@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.2.1.tgz#73ac798bd14247d510adb1e439af2420c8367ded"
+  integrity sha512-fWPNAkBj+Azt9Itmcz/Reqdg3LeBfaXptDEev2JM8bCC0yDptglCnlizhf0YZauyU5X/g6v7v4Xxqhg8tmEfEA==
+  dependencies:
+    busboy "^0.3.1"
+
 express@^4.17.1:
   version "4.17.1"
   resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@@ -1976,6 +2061,11 @@ fresh@0.5.2:
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
   integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
 
+fs-constants@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
+  integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+
 fs-minipass@^1.2.5:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
@@ -2075,6 +2165,11 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
+github-from-package@0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
+  integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
+
 glob-parent@~5.1.0:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -2277,6 +2372,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
+ieee754@^1.1.13:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
+  integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+
 ignore-by-default@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
@@ -2315,7 +2415,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.4, inherits@~2.0.0, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -2364,6 +2464,11 @@ is-arrayish@^0.2.1:
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
   integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
 
+is-arrayish@^0.3.1:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
+  integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
+
 is-binary-path@~2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -3372,6 +3477,11 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
   integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
 
+mimic-response@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
+  integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
+
 minimatch@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -3379,7 +3489,7 @@ minimatch@^3.0.4:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
+minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@@ -3422,6 +3532,11 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
+mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
+  integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+
 mkdirp@1.x, mkdirp@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
@@ -3471,6 +3586,11 @@ nanomatch@^1.2.9:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+napi-build-utils@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
+  integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
+
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -3495,6 +3615,13 @@ nice-try@^1.0.4:
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
   integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 
+node-abi@^2.21.0:
+  version "2.26.0"
+  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.26.0.tgz#355d5d4bc603e856f74197adbf3f5117a396ba40"
+  integrity sha512-ag/Vos/mXXpWLLAYWsAoQdgS+gW7IwvgMLOgqopm/DbzAjazLltzgzpVMsFlgmo9TzG5hGXeaBZx2AI731RIsQ==
+  dependencies:
+    semver "^5.4.1"
+
 node-addon-api@^3.0.0, node-addon-api@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239"
@@ -3582,6 +3709,11 @@ nodemon@^2.0.7:
     undefsafe "^2.0.3"
     update-notifier "^4.1.0"
 
+noop-logger@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
+  integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=
+
 "nopt@2 || 3":
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@@ -3673,7 +3805,7 @@ npm-run-path@^4.0.0:
   dependencies:
     path-key "^3.0.0"
 
-"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.2, npmlog@^4.1.2:
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
   integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -3910,6 +4042,26 @@ posix-character-classes@^0.1.0:
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
   integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
 
+prebuild-install@^6.1.1:
+  version "6.1.2"
+  resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.2.tgz#6ce5fc5978feba5d3cbffedca0682b136a0b5bff"
+  integrity sha512-PzYWIKZeP+967WuKYXlTOhYBgGOvTRSfaKI89XnfJ0ansRAH7hDU45X+K+FZeI1Wb/7p/NnuctPH3g0IqKUuSQ==
+  dependencies:
+    detect-libc "^1.0.3"
+    expand-template "^2.0.3"
+    github-from-package "0.0.0"
+    minimist "^1.2.3"
+    mkdirp-classic "^0.5.3"
+    napi-build-utils "^1.0.1"
+    node-abi "^2.21.0"
+    noop-logger "^0.1.1"
+    npmlog "^4.0.1"
+    pump "^3.0.0"
+    rc "^1.2.7"
+    simple-get "^3.0.3"
+    tar-fs "^2.0.0"
+    tunnel-agent "^0.6.0"
+
 prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -4053,6 +4205,15 @@ readable-stream@^2.0.6:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
+readable-stream@^3.1.1, readable-stream@^3.4.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+  integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
+  dependencies:
+    inherits "^2.0.3"
+    string_decoder "^1.1.1"
+    util-deprecate "^1.0.1"
+
 readdirp@~3.5.0:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
@@ -4217,7 +4378,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@^5.0.1, safe-buffer@^5.1.2:
+safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -4268,12 +4429,12 @@ semver-diff@^3.1.1:
   dependencies:
     semver "^6.3.0"
 
-"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
+"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
-semver@7.x, semver@^7.3.2, semver@^7.3.4:
+semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
   version "7.3.5"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@@ -4339,6 +4500,20 @@ setprototypeof@1.1.1:
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
   integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
 
+sharp@^0.28.1:
+  version "0.28.1"
+  resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.28.1.tgz#9d7bbce1ca95b2c27482243cd4839c62ef40b0b7"
+  integrity sha512-4mCGMEN4ntaVuFGwHx7FvkJQkIgbI+S+F9a3bI7ugdvKjPr4sF7/ibvlRKhJyzhoQi+ODM+XYY1de8xs7MHbfA==
+  dependencies:
+    color "^3.1.3"
+    detect-libc "^1.0.3"
+    node-addon-api "^3.1.0"
+    prebuild-install "^6.1.1"
+    semver "^7.3.5"
+    simple-get "^3.1.0"
+    tar-fs "^2.1.1"
+    tunnel-agent "^0.6.0"
+
 shebang-command@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -4373,6 +4548,27 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
   integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
 
+simple-concat@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
+  integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
+
+simple-get@^3.0.3, simple-get@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
+  integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
+  dependencies:
+    decompress-response "^4.2.0"
+    once "^1.3.1"
+    simple-concat "^1.0.0"
+
+simple-swizzle@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
+  integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=
+  dependencies:
+    is-arrayish "^0.3.1"
+
 sisteransi@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@@ -4540,6 +4736,11 @@ stealthy-require@^1.1.1:
   resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
   integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
 
+streamsearch@0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
+  integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
+
 string-length@^4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
@@ -4583,6 +4784,13 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.0"
 
+string_decoder@^1.1.1:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+  integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+  dependencies:
+    safe-buffer "~5.2.0"
+
 string_decoder@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -4665,6 +4873,27 @@ symbol-tree@^3.2.4:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
+tar-fs@^2.0.0, tar-fs@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
+  integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
+  dependencies:
+    chownr "^1.1.1"
+    mkdirp-classic "^0.5.2"
+    pump "^3.0.0"
+    tar-stream "^2.1.4"
+
+tar-stream@^2.1.4:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
+  integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
+  dependencies:
+    bl "^4.0.3"
+    end-of-stream "^1.4.1"
+    fs-constants "^1.0.0"
+    inherits "^2.0.3"
+    readable-stream "^3.1.1"
+
 tar@^2.0.0:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
@@ -4991,7 +5220,7 @@ use@^3.1.0:
   resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
   integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
 
-util-deprecate@~1.0.1:
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-- 
GitLab