diff --git a/Dockerfile b/Dockerfile index ff885fd..63867b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16-alpine AS deps +FROM node:17-alpine AS deps WORKDIR /build COPY package.json yarn.lock ./ @@ -6,7 +6,7 @@ COPY package.json yarn.lock ./ RUN apk add --no-cache libc6-compat RUN yarn install --frozen-lockfile -FROM node:16-alpine AS builder +FROM node:17-alpine AS builder WORKDIR /build COPY --from=deps /build/node_modules ./node_modules @@ -20,7 +20,7 @@ ENV NEXT_TELEMETRY_DISABLED 1 RUN yarn build -FROM node:16-alpine AS runner +FROM node:17-alpine AS runner WORKDIR /zipline ENV NODE_ENV production diff --git a/Dockerfile-arm b/Dockerfile-arm new file mode 100644 index 0000000..e8207b9 --- /dev/null +++ b/Dockerfile-arm @@ -0,0 +1,44 @@ +FROM node:17 AS deps +WORKDIR /build + +COPY package.json yarn.lock ./ + +RUN yarn install --frozen-lockfile + +FROM node:17 AS builder +WORKDIR /build + +COPY --from=deps /build/node_modules ./node_modules +COPY src ./src +COPY scripts ./scripts +COPY prisma ./prisma +COPY package.json yarn.lock esbuild.config.js next.config.js next-env.d.ts zip-env.d.ts tsconfig.json ./ + +ENV ZIPLINE_DOCKER_BUILD 1 +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN yarn build + +FROM node:17 AS runner +WORKDIR /zipline + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 zipline +RUN adduser --system --uid 1001 zipline + +COPY --from=builder --chown=zipline:zipline /build/.next ./.next +COPY --from=builder --chown=zipline:zipline /build/dist ./dist +COPY --from=builder --chown=zipline:zipline /build/node_modules ./node_modules + +COPY --from=builder /build/next.config.js ./next.config.js +COPY --from=builder /build/src ./src +COPY --from=builder /build/scripts ./scripts +COPY --from=builder /build/prisma ./prisma +COPY --from=builder /build/tsconfig.json ./tsconfig.json +COPY --from=builder /build/package.json ./package.json + +USER zipline + +CMD ["node", "dist/server"] \ No newline at end of file diff --git a/esbuild.config.js b/esbuild.config.js index 209831f..8fb668f 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -1,8 +1,11 @@ const esbuild = require('esbuild'); +const { rm } = require('fs/promises'); (async () => { const watch = process.argv[2] === '--watch'; + await rm('./dist', { recursive: true }); + await esbuild.build({ tsconfig: 'tsconfig.json', outdir: 'dist', @@ -11,6 +14,7 @@ const esbuild = require('esbuild'); treeShaking: true, entryPoints: [ 'src/server/index.ts', + 'src/server/server.ts', 'src/server/util.ts', 'src/server/validateConfig.ts', 'src/lib/logger.ts', diff --git a/package.json b/package.json index 26020bf..87b2550 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zip3", - "version": "3.4.2", + "version": "3.4.3", "license": "MIT", "scripts": { "dev": "node esbuild.config.js && REACT_EDITOR=code-insiders NODE_ENV=development node dist/server", @@ -35,7 +35,8 @@ "colorette": "^1.2.2", "cookie": "^0.4.1", "fecha": "^4.2.1", - "multer": "^1.4.2", + "find-my-way": "^5.2.0", + "multer": "^1.4.4", "next": "^12.1.0", "prisma": "^3.9.2", "react": "^17.0.2", diff --git a/src/lib/datasource/Local.ts b/src/lib/datasource/Local.ts index cba4967..f9adaf8 100644 --- a/src/lib/datasource/Local.ts +++ b/src/lib/datasource/Local.ts @@ -1,4 +1,5 @@ -import { readdir, readFile, stat, writeFile } from 'fs/promises'; +import { createReadStream, ReadStream } from 'fs'; +import { readdir, stat, writeFile } from 'fs/promises'; import { join } from 'path'; import { Datasource } from './datasource'; @@ -13,10 +14,9 @@ export class Local extends Datasource { await writeFile(join(process.cwd(), this.path, file), data); } - public async get(file: string): Promise { + public get(file: string): ReadStream { try { - const data = await readFile(join(process.cwd(), this.path, file)); - return data; + return createReadStream(join(process.cwd(), this.path, file)); } catch (e) { return null; } diff --git a/src/lib/datasource/S3.ts b/src/lib/datasource/S3.ts index 7149978..f66aefb 100644 --- a/src/lib/datasource/S3.ts +++ b/src/lib/datasource/S3.ts @@ -1,5 +1,6 @@ import { Datasource } from './datasource'; import AWS from 'aws-sdk'; +import { Readable } from 'stream'; export class S3 extends Datasource { public name: string = 'S3'; @@ -33,20 +34,12 @@ export class S3 extends Datasource { }); } - public async get(file: string): Promise { - return new Promise((resolve, reject) => { - this.s3.getObject({ - Bucket: this.bucket, - Key: file, - }, (err, data) => { - if (err) { - reject(err); - } else { - // @ts-ignore - resolve(Buffer.from(data.Body)); - } - }); - }); + public get(file: string): Readable { + // Unfortunately, aws-sdk is bad and the stream still loads everything into memory. + return this.s3.getObject({ + Bucket: this.bucket, + Key: file, + }).createReadStream(); } public async size(): Promise { diff --git a/src/lib/datasource/datasource.ts b/src/lib/datasource/datasource.ts index f598fe5..b06ec52 100644 --- a/src/lib/datasource/datasource.ts +++ b/src/lib/datasource/datasource.ts @@ -1,7 +1,9 @@ +import { Readable } from 'stream'; + export abstract class Datasource { public name: string; public abstract save(file: string, data: Buffer): Promise; - public abstract get(file: string): Promise; + public abstract get(file: string): Readable; public abstract size(): Promise; } \ No newline at end of file diff --git a/src/lib/ds.ts b/src/lib/ds.ts index 9988f50..0fa9f73 100644 --- a/src/lib/ds.ts +++ b/src/lib/ds.ts @@ -5,16 +5,16 @@ import Logger from './logger'; if (!global.datasource) { switch (config.datasource.type) { case 's3': + Logger.get('datasource').info(`Using S3(${config.datasource.s3.bucket}) datasource`); global.datasource = new S3(config.datasource.s3.access_key_id, config.datasource.s3.secret_access_key, config.datasource.s3.bucket); - Logger.get('datasource').info(`Using S3:${config.datasource.s3.bucket} datasource`); break; case 'local': + Logger.get('datasource').info(`Using local(${config.datasource.local.directory}) datasource`); global.datasource = new Local(config.datasource.local.directory); - Logger.get('datasource').info(`Using local:${config.datasource.local.directory} datasource`); break; default: throw new Error('Invalid datasource type'); } -}; +} export default global.datasource; \ No newline at end of file diff --git a/src/lib/logger.ts b/src/lib/logger.ts index a848415..934133a 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -21,12 +21,12 @@ export default class Logger { this.name = name; } - info(message: string) { - console.log(this.formatMessage(LoggerLevel.INFO, this.name, message)); + info(...args) { + console.log(this.formatMessage(LoggerLevel.INFO, this.name, args.join(' '))); } - error(error: any) { - console.log(this.formatMessage(LoggerLevel.ERROR, this.name, error.stack ?? error)); + error(...args: any[]) { + console.log(this.formatMessage(LoggerLevel.ERROR, this.name, args.map(error => error.stack ?? error).join(' '))); } formatMessage(level: LoggerLevel, name, message) { diff --git a/src/pages/[...id].tsx b/src/pages/[...id].tsx index d9e8240..09be29d 100644 --- a/src/pages/[...id].tsx +++ b/src/pages/[...id].tsx @@ -127,10 +127,10 @@ export const getServerSideProps: GetServerSideProps = async (context) => { if (!image.mimetype.startsWith('image')) { const { default: datasource } = await import('lib/ds'); - const data = await datasource.get(image.file); + const data = datasource.get(image.file); if (!data) return { notFound: true }; - context.res.end(data); + data.pipe(context.res); return { props: {} }; } diff --git a/src/pages/api/upload.ts b/src/pages/api/upload.ts index ed77315..c8ed0d0 100644 --- a/src/pages/api/upload.ts +++ b/src/pages/api/upload.ts @@ -9,9 +9,7 @@ import { format as formatDate } from 'fecha'; import { v4 } from 'uuid'; import datasource from 'lib/ds'; -const uploader = multer({ - storage: multer.memoryStorage(), -}); +const uploader = multer(); async function handler(req: NextApiReq, res: NextApiRes) { if (req.method !== 'POST') return res.forbid('invalid method'); @@ -25,13 +23,16 @@ async function handler(req: NextApiReq, res: NextApiRes) { if (!user) return res.forbid('authorization incorect'); if (user.ratelimited) return res.ratelimited(); + + await run(uploader.array('file'))(req, res); + if (!req.files) return res.error('no files'); + if (req.files && req.files.length === 0) return res.error('no files'); const rawFormat = ((req.headers.format || '') as string).toUpperCase() || 'RANDOM'; const format: ImageFormat = Object.keys(ImageFormat).includes(rawFormat) && ImageFormat[rawFormat]; const files = []; - for (let i = 0; i !== req.files.length; ++i) { const file = req.files[i]; if (file.size > zconfig.uploader[user.administrator ? 'admin_limit' : 'user_limit']) return res.error(`file[${i}] size too big`); @@ -116,9 +117,7 @@ function run(middleware: any) { }); } -export default async function handlers(req, res) { - await run(uploader.array('file'))(req, res); - +export default async function handlers(req, res) { return withZipline(handler)(req, res); }; diff --git a/src/pages/auth/login.tsx b/src/pages/auth/login.tsx index 33e943a..9aab4ca 100644 --- a/src/pages/auth/login.tsx +++ b/src/pages/auth/login.tsx @@ -36,14 +36,14 @@ export default function Login() { icon: , }); } else { - router.push(router.query.url as string || '/dashboard'); + await router.push(router.query.url as string || '/dashboard'); } }; useEffect(() => { (async () => { const a = await fetch('/api/user'); - if (a.ok) router.push('/dashboard'); + if (a.ok) await router.push('/dashboard'); else { const v = await useFetch('/api/version'); setVersions(v); diff --git a/src/pages/auth/logout.tsx b/src/pages/auth/logout.tsx index c116b89..4a46f67 100644 --- a/src/pages/auth/logout.tsx +++ b/src/pages/auth/logout.tsx @@ -4,8 +4,6 @@ import { LoadingOverlay } from '@mantine/core'; export default function Logout() { const router = useRouter(); - const [visible, setVisible] = useState(true); - useEffect(() => { (async () => { @@ -20,7 +18,7 @@ export default function Logout() { }, []); return ( - + ); } diff --git a/src/pages/code/[id].tsx b/src/pages/code/[id].tsx index 1e41bc1..70f2689 100644 --- a/src/pages/code/[id].tsx +++ b/src/pages/code/[id].tsx @@ -11,7 +11,7 @@ export default function Code() { useEffect(() => { (async () => { const res = await fetch('/r/' + id); - if (id && !res.ok) router.push('/404'); + if (id && !res.ok) await router.push('/404'); const data = await res.text(); if (id) setPrismRenderCode(data); })(); diff --git a/src/server/index.ts b/src/server/index.ts index 6fde30b..c403607 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,164 +1,3 @@ -import next from 'next'; -import { createServer } from 'http'; -import { extname } from 'path'; -import Logger from '../lib/logger'; -import mimes from '../../scripts/mimes'; -import { log, getStats, migrations } from './util'; -import { PrismaClient } from '@prisma/client'; -import { version } from '../../package.json'; -import exts from '../../scripts/exts'; -import datasource from '../lib/ds'; -import config from '../lib/config'; -import { mkdir } from 'fs/promises'; -const serverLog = Logger.get('server'); +import Server from './server'; -serverLog.info(`starting zipline@${version} server`); - -const dev = process.env.NODE_ENV === 'development'; - -(async () => { - try { - await run(); - } catch (e) { - serverLog.error(e); - process.exit(1); - } -})(); - -async function run() { - process.env.DATABASE_URL = config.core.database_url; - await migrations(); - - if (config.datasource.type === 'local') { - await mkdir(config.datasource.local.directory, { recursive: true }); - } - - const app = next({ - dir: '.', - dev, - quiet: !dev, - hostname: config.core.host, - port: config.core.port, - }); - - await app.prepare(); - - const handle = app.getRequestHandler(); - const prisma = new PrismaClient(); - - const srv = createServer(async (req, res) => { - if (req.url.startsWith('/r')) { - const parts = req.url.split('/'); - if (!parts[2] || parts[2] === '') return; - - let image = await prisma.image.findFirst({ - where: { - OR: [ - { file: parts[2] }, - { invisible:{ invis: decodeURI(parts[2]) } }, - ], - }, - select: { - mimetype: true, - id: true, - file: true, - invisible: true, - }, - }); - - if (!image) { - const data = await datasource.get(parts[2]); - if (!data) return app.render404(req, res); - - const mimetype = mimes[extname(parts[2])] ?? 'application/octet-stream'; - res.setHeader('Content-Type', mimetype); - res.end(data); - } else { - const data = await datasource.get(parts[2]); - if (!data) return app.render404(req, res); - - await prisma.image.update({ - where: { id: image.id }, - data: { views: { increment: 1 } }, - }); - res.setHeader('Content-Type', image.mimetype); - res.end(data); - } - } else if (req.url.startsWith(config.uploader.route)) { - const parts = req.url.split('/'); - if (!parts[2] || parts[2] === '') return; - - let image = await prisma.image.findFirst({ - where: { - OR: [ - { file: parts[2] }, - { invisible:{ invis: decodeURI(parts[2]) } }, - ], - }, - select: { - mimetype: true, - id: true, - file: true, - invisible: true, - embed: true, - }, - }); - - if (!image) { - const data = await datasource.get(parts[2]); - if (!data) return app.render404(req, res); - - const mimetype = mimes[extname(parts[2])] ?? 'application/octet-stream'; - res.setHeader('Content-Type', mimetype); - res.end(data); - } else if (image.embed) { - handle(req, res); - } else { - const ext = image.file.split('.').pop(); - if (Object.keys(exts).includes(ext)) return handle(req, res); - - const data = await datasource.get(parts[2]); - if (!data) return app.render404(req, res); - - await prisma.image.update({ - where: { id: image.id }, - data: { views: { increment: 1 } }, - }); - res.setHeader('Content-Type', image.mimetype); - res.end(data); - } - } else { - handle(req, res); - } - - if (config.core.logger) log(req.url); - }); - - srv.on('error', (e) => { - serverLog.error(e); - - process.exit(1); - }); - - srv.on('listening', () => { - serverLog.info(`listening on ${config.core.host}:${config.core.port}`); - }); - - srv.listen(config.core.port, config.core.host ?? '0.0.0.0'); - - const stats = await getStats(prisma, datasource); - await prisma.stats.create({ - data: { - data: stats, - }, - }); - setInterval(async () => { - const stats = await getStats(prisma, datasource); - await prisma.stats.create({ - data: { - data: stats, - }, - }); - if (config.core.logger) serverLog.info('stats updated'); - }, config.core.stats_interval * 1000); -} \ No newline at end of file +new Server(); \ No newline at end of file diff --git a/src/server/server.ts b/src/server/server.ts new file mode 100644 index 0000000..0266f52 --- /dev/null +++ b/src/server/server.ts @@ -0,0 +1,186 @@ +import Router from 'find-my-way'; +import { NextServer, RequestHandler } from 'next/dist/server/next'; +import { Image, PrismaClient } from '@prisma/client'; +import { createServer, IncomingMessage, OutgoingMessage, Server as HttpServer, ServerResponse } from 'http'; +import next from 'next'; +import config from '../lib/config'; +import datasource from '../lib/ds'; +import { getStats, log, migrations } from './util'; +import { mkdir } from 'fs/promises'; +import Logger from '../lib/logger'; +import mimes from '../../scripts/mimes'; +import { extname } from 'path'; +import exts from '../../scripts/exts'; +import { version } from '../../package.json'; + +const serverLog = Logger.get('server'); + +export default class Server { + public router: Router.Instance; + public nextServer: NextServer; + public handle: RequestHandler; + public prisma: PrismaClient; + + private http: HttpServer; + + public constructor() { + serverLog.info(`starting zipline@${version} server`); + + this.start(); + } + + private async start() { + const dev = process.env.NODE_ENV === 'development'; + + process.env.DATABASE_URL = config.core.database_url; + await migrations(); + + this.prisma = new PrismaClient(); + + if (config.datasource.type === 'local') { + await mkdir(config.datasource.local.directory, { recursive: true }); + } + + this.nextServer = next({ + dir: '.', + dev, + quiet: !dev, + hostname: config.core.host, + port: config.core.port, + }); + + this.handle = this.nextServer.getRequestHandler(); + this.router = Router({ + defaultRoute: (req, res) => { + this.handle(req, res); + }, + }); + + this.router.on('GET', `${config.uploader.route}/:id`, async (req, res, params) => { + const image = await this.prisma.image.findFirst({ + where: { + OR: [ + { file: params.id }, + { invisible: { invis: decodeURI(params.id) } }, + ], + }, + select: { + mimetype: true, + id: true, + file: true, + invisible: true, + embed: true, + }, + }); + + if (!image) await this.rawFile(req, res, params.id); + else if (image.embed) await this.handle(req, res); + else await this.fileDb(req, res, image); + }); + + this.router.on('GET', '/r/:id', async (req, res, params) => { + const image = await this.prisma.image.findFirst({ + where: { + OR: [ + { file: params.id }, + { invisible: { invis: decodeURI(params.id) } }, + ], + }, + select: { + mimetype: true, + id: true, + file: true, + invisible: true, + embed: true, + }, + }); + + if (!image) await this.rawFile(req, res, params.id); + else await this.rawFileDb(req, res, image); + }); + + await this.nextServer.prepare(); + + this.http = createServer((req, res) => { + this.router.lookup(req, res); + if (config.core.logger) log(req.url); + }); + + this.http.on('error', (e) => { + serverLog.error(e); + process.exit(1); + }); + + this.http.on('listening', () => { + serverLog.info(`listening on ${config.core.host}:${config.core.port}`); + }); + + this.http.listen(config.core.port, config.core.host ?? '0.0.0.0'); + + this.stats(); + } + + private async rawFile(req: IncomingMessage, res: OutgoingMessage, id: string) { + const data = datasource.get(id); + if (!data) return this.nextServer.render404(req, res as ServerResponse); + + const mimetype = mimes[extname(id)] ?? 'application/octet-stream'; + res.setHeader('Content-Type', mimetype); + + data.pipe(res); + data.on('error', () => this.nextServer.render404(req, res as ServerResponse)); + data.on('end', () => res.end()); + } + + private async rawFileDb(req: IncomingMessage, res: OutgoingMessage, image: any) { + const data = datasource.get(image.file); + if (!data) return this.nextServer.render404(req, res as ServerResponse); + + res.setHeader('Content-Type', image.mimetype); + data.pipe(res); + data.on('error', () => this.nextServer.render404(req, res as ServerResponse)); + data.on('end', () => res.end()); + + await this.prisma.image.update({ + where: { id: image.id }, + data: { views: { increment: 1 } }, + }); + } + + private async fileDb(req: IncomingMessage, res: OutgoingMessage, image: any) { + const ext = image.file.split('.').pop(); + if (Object.keys(exts).includes(ext)) return this.handle(req, res as ServerResponse); + + const data = datasource.get(image.file); + if (!data) return this.nextServer.render404(req, res as ServerResponse); + + res.setHeader('Content-Type', image.mimetype); + data.pipe(res); + data.on('error', () => this.nextServer.render404(req, res as ServerResponse)); + data.on('end', () => res.end()); + + await this.prisma.image.update({ + where: { id: image.id }, + data: { views: { increment: 1 } }, + }); + } + + private async stats() { + const stats = await getStats(this.prisma, datasource); + await this.prisma.stats.create({ + data: { + data: stats, + }, + }); + + setInterval(async () => { + const stats = await getStats(this.prisma, datasource); + await this.prisma.stats.create({ + data: { + data: stats, + }, + }); + if (config.core.logger) serverLog.info('stats updated'); + }, config.core.stats_interval * 1000); + } +} \ No newline at end of file diff --git a/src/server/util.ts b/src/server/util.ts index f5f15b8..669bdac 100644 --- a/src/server/util.ts +++ b/src/server/util.ts @@ -1,9 +1,6 @@ -import { readFile, readdir, stat } from 'fs/promises'; -import { join } from 'path'; import { Migrate } from '@prisma/migrate/dist/Migrate'; import Logger from '../lib/logger'; -import { execSync } from 'child_process'; -import { Datasource } from '../lib/datasource/index'; +import { Datasource } from 'lib/datasource'; export async function migrations() { const migrate = new Migrate('./prisma/schema.prisma'); @@ -25,27 +22,6 @@ export function log(url) { return Logger.get('url').info(url); } -export function shouldUseYarn() { - try { - execSync('yarnpkg --version', { stdio: 'ignore' }); - return true; - } catch (e) { - return false; - } -} - -export async function sizeOfDir(directory) { - const files = await readdir(directory); - - let size = 0; - for (let i = 0, L = files.length; i !== L; ++i) { - const sta = await stat(join(directory, files[i])); - size += sta.size; - } - - return size; -} - export function bytesToRead(bytes) { const units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB']; let num = 0; diff --git a/yarn.lock b/yarn.lock index 1e8bbf7..1089c00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2450,6 +2450,11 @@ exit-on-epipe@~1.0.1: resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== +fast-decode-uri-component@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" + integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -2535,6 +2540,16 @@ find-cache-dir@3.3.2: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-my-way@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-5.2.0.tgz#3e8b6d55471c9eac52164d268db108ff64849ae0" + integrity sha512-YLocRSSJJ1PCnrupBmaw2pUcFkU125QTWfFfpdLq11h7bQ+r+Ke4D1/4v7UkERlw0037VkYMd+1RMGTbhFCbPw== + dependencies: + fast-decode-uri-component "^1.0.1" + fast-deep-equal "^3.1.3" + safe-regex2 "^2.0.0" + semver-store "^0.3.0" + find-root@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz" @@ -3546,9 +3561,9 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -mkdirp@^0.5.1: +mkdirp@^0.5.4: version "0.5.5" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" @@ -3596,15 +3611,15 @@ mssql@8.0.1: tarn "^3.0.2" tedious "^14.0.0" -multer@^1.4.2: - version "1.4.2" - resolved "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz" - integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg== +multer@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4.tgz#e2bc6cac0df57a8832b858d7418ccaa8ebaf7d8c" + integrity sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw== dependencies: append-field "^1.0.0" busboy "^0.2.11" concat-stream "^1.5.2" - mkdirp "^0.5.1" + mkdirp "^0.5.4" object-assign "^4.1.1" on-finished "^2.3.0" type-is "^1.6.4" @@ -4511,6 +4526,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +ret@~0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c" + integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== + retry@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" @@ -4557,6 +4577,13 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-regex2@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-2.0.0.tgz#b287524c397c7a2994470367e0185e1916b1f5b9" + integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ== + dependencies: + ret "~0.2.0" + "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" @@ -4585,6 +4612,11 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= +semver-store@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/semver-store/-/semver-store-0.3.0.tgz#ce602ff07df37080ec9f4fb40b29576547befbe9" + integrity sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg== + "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz"