feat: conversions

This commit is contained in:
sylv 2022-07-26 08:30:22 +08:00
parent 1aeaace977
commit 92cef85511
15 changed files with 305 additions and 88 deletions

View File

@ -26,6 +26,7 @@ An invite-only file sharing service with support for ShareX. You can see a previ
- [x] URL Shortening
- [x] Mobile support
- [x] EXIF metadata removal
- [x] Conversions (GIF>WebM, WebP>PNG, etc.)
- [x] Purging of old and/or large files (`config.purge`).
## screenshots
@ -76,8 +77,6 @@ You should take a full database backup before updating. Pending database migrati
- [ ] Password recovery via emails
- [ ] SQLite support
- [ ] Private email aliases like firefox relay (might be difficult/expensive)
- [ ] Export data options (all files, though that could get huge)
- [ ] Convert gifs to mp4s on upload to save space
## discord

View File

@ -54,9 +54,11 @@ hosts:
# # to allow and require users to sign up with an email, specify an SMTP server to send emails from.
# # if you do not specify an email, it will not be asked for during signup and users will sign in with their username.
# # enabling this on an existing instance will prompt users without an email to add one (https://micro.sylo.digital/i/J1Ilba)
# # To allow and require users to sign up with an email, specify an SMTP server to send emails from.
# # If you do not specify an email, it will not be asked for during signup and users will sign in with their username.
# # Enabling this on an existing instance will prompt users without an email to add one (https://micro.sylo.digital/i/J1Ilba)
# email:
# from: 'noreply@example.net'
# smtp:
@ -66,3 +68,23 @@ hosts:
# auth:
# user: 'username'
# pass: 'password'
# Conversions allow you to convert files to other formats on upload.
# This is intended for converting inefficient formats like gif to webm (40mb>4mb in testing).
# The original copy will be discarded.
# Video conversions rely on ffmpeg which is a slow and flakey process. If possible,
# it's recommended to disable conversions and just don't upload gifs.
# The recommended options are below, on low power devices like a raspberry pi you should disable conversions entirely.
# This is currently not retroactive, but in the future old files may also be converted.
conversions:
# You cannot convert video/* to image/* and vice versa. How would that even work?
# image/gif to video/webm is an exception.
- from: image/gif # This could also be a list of types, just a category like "image", or both.
to: video/webm
minSize: 1MB # Required size before conversion, files uploaded under this limit are ignored

View File

@ -43,6 +43,7 @@
"escape-string-regexp": "^4",
"fastify": "^3.29.0",
"file-type": "^16",
"fluent-ffmpeg": "^2.1.2",
"get-stream": "^6.0.1",
"graphql": "^16.5.0",
"handlebars": "^4.7.7",
@ -68,6 +69,7 @@
"@swc/core": "^1.2.208",
"@sylo-digital/scripts": "^1.0.2",
"@types/dedent": "^0.7.0",
"@types/fluent-ffmpeg": "^2.1.20",
"@types/jest": "^28.1.4",
"@types/luxon": "^2.3.2",
"@types/mime-types": "^2.1.1",

View File

@ -3,6 +3,7 @@ import {
IsBoolean,
IsDefined,
IsEmail,
IsMimeType,
IsNumber,
IsOptional,
IsString,
@ -11,12 +12,13 @@ import {
NotEquals,
ValidateNested,
} from 'class-validator';
import fileType from 'file-type';
import path from 'path';
import xbytes from 'xbytes';
import { MicroConfigPurge } from './MicroConfigPurge';
import { expandMime } from '../helpers/expand-mime';
import { MicroConversion } from './MicroConversion';
import { MicroEmail } from './MicroEmail';
import { MicroHost } from './MicroHost';
import { MicroPurge } from './MicroPurge';
export class MicroConfig {
@IsUrl({ require_tld: false, require_protocol: true, protocols: ['postgresql', 'postgres'] })
@ -38,27 +40,11 @@ export class MicroConfig {
@Max(500000)
maxPasteLength = 500000;
@IsString({ each: true })
@IsMimeType({ each: true })
@IsOptional()
@Transform(({ value }) => {
if (!value) return value;
const clean: string[] = [];
for (const type of value) {
const stripped = type.replace(/\/\*$/u, '');
if (stripped.includes('/')) {
if (!fileType.mimeTypes.has(type)) {
throw new Error(`Invalid mime type: ${type}`);
}
clean.push(type);
continue;
}
for (const knownType of fileType.mimeTypes.values()) {
if (knownType.startsWith(stripped)) clean.push(knownType);
}
}
const clean = expandMime(value);
return new Set(clean);
})
allowTypes?: Set<string>;
@ -72,14 +58,19 @@ export class MicroConfig {
@ValidateNested()
@IsOptional()
@Type(() => MicroConfigPurge)
purge?: MicroConfigPurge;
@Type(() => MicroPurge)
purge?: MicroPurge;
@ValidateNested()
@IsOptional()
@Type(() => MicroEmail)
email: MicroEmail;
@ValidateNested({ each: true })
@IsOptional()
@Type(() => MicroConversion)
conversions?: MicroConversion[];
@ValidateNested({ each: true })
@IsDefined()
@Type(() => MicroHost)

View File

@ -0,0 +1,21 @@
import { Transform } from 'class-transformer';
import { IsMimeType, IsNumber, IsOptional, IsString } from 'class-validator';
import xbytes from 'xbytes';
import { expandMime } from '../helpers/expand-mime';
export class MicroConversion {
@IsString({ each: true })
@Transform(({ value }) => {
const clean = expandMime(value);
return new Set(clean);
})
from: Set<string>;
@IsMimeType()
to: string;
@IsNumber()
@IsOptional()
@Transform(({ value }) => xbytes.parseSize(value))
minSize?: number;
}

View File

@ -3,7 +3,7 @@ import { IsNumber } from 'class-validator';
import ms from 'ms';
import xbytes from 'xbytes';
export class MicroConfigPurge {
export class MicroPurge {
@IsNumber()
@Transform(({ value }) => xbytes.parseSize(value))
overLimit: number;

View File

@ -0,0 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should expand mime types 1`] = `
Array [
"video/mp4",
"video/webm",
"video/ogg",
"video/quicktime",
"video/x-ms-wmv",
"video/x-msvideo",
"video/x-flv",
"video/x-matroska",
"video/3gpp",
"video/3gpp2",
]
`;
exports[`should expand mime types 2`] = `
Array [
"image/bmp",
"image/gif",
"image/jpeg",
"image/png",
"image/svg+xml",
"image/tiff",
"image/webp",
]
`;
exports[`should expand mime types 3`] = `
Array [
"image/bmp",
"image/gif",
"image/jpeg",
"image/png",
"image/svg+xml",
"image/tiff",
"image/webp",
]
`;
exports[`should expand mime types 4`] = `
Array [
"image/bmp",
"image/gif",
"image/jpeg",
"image/png",
"image/svg+xml",
"image/tiff",
"image/webp",
"video/mp4",
]
`;
exports[`should expand mime types 5`] = `
Array [
"image/bmp",
"image/gif",
"image/jpeg",
"image/png",
"image/svg+xml",
"image/tiff",
"image/webp",
"video/mp4",
]
`;

View File

@ -0,0 +1,9 @@
import { expandMime } from './expand-mime';
it('should expand mime types', () => {
expect(expandMime('video')).toMatchSnapshot();
expect(expandMime('image')).toMatchSnapshot();
expect(expandMime(['image'])).toMatchSnapshot();
expect(expandMime(['image', 'video/mp4'])).toMatchSnapshot();
expect(expandMime(['image/*', 'video/mp4'])).toMatchSnapshot();
});

View File

@ -0,0 +1,58 @@
const WILDCARD_REGEX = /\/\*$/gu;
const MIME_MAP = new Map<string, string[]>([
[
'video',
[
'video/mp4',
'video/webm',
'video/ogg',
'video/quicktime',
'video/x-ms-wmv',
'video/x-msvideo',
'video/x-flv',
'video/x-matroska',
'video/3gpp',
'video/3gpp2',
],
],
['image', ['image/bmp', 'image/gif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/tiff', 'image/webp']],
[
'audio',
[
'audio/aac',
'audio/aacp',
'audio/amr',
'audio/amr-wb',
'audio/basic',
'audio/flac',
'audio/midi',
'audio/mp3',
'audio/mpeg',
'audio/mpeg3',
'audio/ogg',
'audio/opus',
'audio/wav',
'audio/webm',
],
],
]);
export const expandMime = (input: string | string[]) => {
if (!Array.isArray(input)) input = [input];
const output: string[] = [];
for (const mimeType of input) {
const alias = MIME_MAP.get(mimeType.replace(WILDCARD_REGEX, ''));
if (alias) {
output.push(...alias);
continue;
}
if (!mimeType.includes('/')) {
throw new Error(`Unknown mime category "${mimeType}"`);
}
output.push(mimeType);
}
return output;
};

View File

@ -7,7 +7,10 @@ import { BadRequestException, Injectable, Logger, NotFoundException, PayloadTooL
import { Cron, CronExpression } from '@nestjs/schedule';
import contentRange from 'content-range';
import type { FastifyReply, FastifyRequest } from 'fastify';
import ffmpeg from 'fluent-ffmpeg';
import { DateTime } from 'luxon';
import mime from 'mime-types';
import sharp from 'sharp';
import { PassThrough } from 'stream';
import xbytes from 'xbytes';
import type { MicroHost } from '../../classes/MicroHost';
@ -46,27 +49,86 @@ export class FileService implements OnApplicationBootstrap {
): Promise<File> {
if (host) this.hostService.checkUserCanUploadTo(host, owner);
if (!request.headers['content-length']) throw new BadRequestException('Missing "Content-Length" header.');
if (Number(request.headers['content-length']) >= config.uploadLimit) {
const contentLength = Number(request.headers['content-length']);
if (Number.isNaN(contentLength) || contentLength >= config.uploadLimit) {
const size = xbytes(Number(request.headers['content-length']));
this.logger.warn(
`User ${owner.id} tried uploading a ${size} file, which is over the configured upload size limit.`
);
throw new PayloadTooLargeException();
}
const stream = multipart.file;
const typeStream = stream.pipe(new PassThrough());
const uploadStream = stream.pipe(new PassThrough());
const type = (await getStreamType(multipart.filename, typeStream)) ?? multipart.mimetype;
if (config.allowTypes && !config.allowTypes.has(type)) {
throw new BadRequestException(`"${type}" is not supported by this server.`);
let uploadStream = stream.pipe(new PassThrough());
const fileType = (await getStreamType(multipart.filename, typeStream)) ?? multipart.mimetype;
if (config.allowTypes && !config.allowTypes.has(fileType)) {
throw new BadRequestException(`"${fileType}" is not supported by this server.`);
}
const conversion = config.conversions?.find((conversion) => {
if (!conversion.from.has(fileType)) return false;
if (conversion.minSize && contentLength < conversion.minSize) return false;
if (conversion.to === fileType) return false; // dont convert to the same type
return true;
});
if (conversion) {
this.logger.debug(`Converting ${fileType} to ${conversion.to}`);
const fromGroup = fileType.split('/')[0];
const toGroup = conversion.to.split('/')[0];
if (fromGroup !== toGroup && fileType !== 'image/gif') {
throw new Error(`Cannot convert from ${fromGroup} to ${toGroup}`);
}
switch (toGroup) {
case 'video': {
let fromFormat = fileType.split('/')[1];
if (fromFormat === 'gif') {
// ffmpeg doesnt support piping gifs unless "gif_pipe" is the input format.
// you have no idea how long it took to discover this.
fromFormat = 'gif_pipe';
}
const toFormat = conversion.to.split('/')[1];
const transcodeStream = new PassThrough();
ffmpeg()
.input(uploadStream)
.fromFormat(fromFormat)
.toFormat(toFormat)
.writeToStream(transcodeStream, { end: true });
uploadStream = transcodeStream;
break;
}
case 'image': {
const toFormat = conversion.to.split('/')[1];
if (!(toFormat in sharp.format)) {
throw new Error(`Unknown or unsupported image format ${toFormat}`);
}
// pages: -1 enables support to convert gif to webp without it being a static image
const transformer = sharp({ pages: -1 }).toFormat(toFormat as any, {
effort: 3,
quality: 70,
progressive: true,
});
uploadStream = uploadStream.pipe(transformer).pipe(new PassThrough());
break;
}
default:
throw new Error(`Unknown or unsupported conversion ${fromGroup} to ${toGroup}`);
}
}
const fileId = generateContentId();
const { hash, size } = await this.storageService.create(uploadStream);
const file = this.fileRepo.create({
id: fileId,
type: type,
type: fileType,
name: multipart.filename,
owner: owner.id,
hostname: host?.normalised.replace('{{username}}', owner.username),
@ -74,6 +136,17 @@ export class FileService implements OnApplicationBootstrap {
size: size,
});
if (conversion) {
// swap the file type to the new mime type
const originalExtension = mime.extension(file.type);
file.type = conversion.to;
const conversionExtension = mime.extension(conversion.to);
if (file.name && originalExtension && conversionExtension) {
// "fix" extensions in the ile name, eg "Test.png" > "Test.webp"
file.name = file.name.replace(`.${originalExtension}`, `.${conversionExtension}`);
}
}
await this.fileRepo.persistAndFlush(file);
return file;
}

View File

@ -8,5 +8,6 @@ export interface Embeddable {
paths: {
direct: string;
view?: string;
thumbnail?: string | null;
};
}

View File

@ -5,7 +5,7 @@ import { BASE_EMBED_CLASSES, MAX_HEIGHT } from '../embed';
import type { Embeddable } from '../embeddable';
export const EmbedImage = ({ data }: { data: Embeddable }) => {
const containerClasses = classNames('flex items-center justify-center relative', BASE_EMBED_CLASSES);
const containerClasses = classNames('flex items-center justify-center relative overflow-hidden', BASE_EMBED_CLASSES);
const imageClasses = classNames(`object-contain`, MAX_HEIGHT);
return (

View File

@ -9,6 +9,7 @@ export const EmbedVideo = ({ file }: { file: Embeddable }) => {
controls
loop
playsInline
autoPlay
className={classes}
height={file.height || undefined}
width={file.width || undefined}

View File

@ -82,11 +82,11 @@ export default function Upload() {
}
location.href = body.urls.view;
setFile(null);
} catch (error: unknown) {
const message = getErrorMessage(error) ?? 'An unknown error occured.';
createToast({ error: true, text: message });
} finally {
setFile(null);
setUploading(false);
}
};

View File

@ -33,6 +33,7 @@ importers:
'@sylo-digital/scripts': ^1.0.2
'@types/bcryptjs': ^2.4.2
'@types/dedent': ^0.7.0
'@types/fluent-ffmpeg': ^2.1.20
'@types/jest': ^28.1.4
'@types/luxon': ^2.3.2
'@types/mime-types': ^2.1.1
@ -50,6 +51,7 @@ importers:
escape-string-regexp: ^4
fastify: ^3.29.0
file-type: ^16
fluent-ffmpeg: ^2.1.2
get-stream: ^6.0.1
graphql: ^16.5.0
handlebars: ^4.7.7
@ -103,6 +105,7 @@ importers:
escape-string-regexp: 4.0.0
fastify: 3.29.0
file-type: 16.5.3
fluent-ffmpeg: 2.1.2
get-stream: 6.0.1
graphql: 16.5.0
handlebars: 4.7.7
@ -127,6 +130,7 @@ importers:
'@swc/core': 1.2.208
'@sylo-digital/scripts': 1.0.2_jest@28.1.2
'@types/dedent': 0.7.0
'@types/fluent-ffmpeg': 2.1.20
'@types/jest': 28.1.4
'@types/luxon': 2.3.2
'@types/mime-types': 2.1.1
@ -1596,7 +1600,6 @@ packages:
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@endemolshinegroup/cosmiconfig-typescript-loader/3.0.2_zmjss6mecb4soo3dpdlecld3xa:
resolution: {integrity: sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==}
@ -2352,6 +2355,7 @@ packages:
transitivePeerDependencies:
- supports-color
- ts-node
dev: true
/@jest/core/28.1.2_ts-node@10.8.2:
resolution: {integrity: sha512-Xo4E+Sb/nZODMGOPt2G3cMmCBqL4/W2Ijwr7/mrXlq4jdJwcFQ/9KrrJZT2adQRk2otVBXXOz1GRQ4Z5iOgvRQ==}
@ -2394,7 +2398,6 @@ packages:
transitivePeerDependencies:
- supports-color
- ts-node
dev: true
/@jest/create-cache-key-function/27.5.1:
resolution: {integrity: sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==}
@ -2567,7 +2570,7 @@ packages:
dependencies:
'@types/istanbul-lib-coverage': 2.0.3
'@types/istanbul-reports': 3.0.1
'@types/node': 16.11.33
'@types/node': 16.11.43
'@types/yargs': 15.0.14
chalk: 4.1.2
dev: false
@ -2660,7 +2663,6 @@ packages:
dependencies:
'@jridgewell/resolve-uri': 3.0.8
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@mikro-orm/cli/5.2.2_4k2cb7nrrwahnwnvazylxfgb44:
resolution: {integrity: sha512-IRMcfVFF6edBX2lQsKswMAjw6ILOvwnLT0cMJziknVMuPop/ckaWxlA5r4Uu1BorNtoHf52qB9i3r5YwEe9qAQ==}
@ -3546,7 +3548,6 @@ packages:
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@swc/core-android-arm64/1.2.204:
@ -3563,7 +3564,6 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@swc/core-darwin-arm64/1.2.204:
@ -3580,7 +3580,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-darwin-x64/1.2.204:
@ -3597,7 +3596,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-freebsd-x64/1.2.204:
@ -3614,7 +3612,6 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm-gnueabihf/1.2.204:
@ -3631,7 +3628,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-gnu/1.2.204:
@ -3648,7 +3644,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-musl/1.2.204:
@ -3665,7 +3660,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-gnu/1.2.204:
@ -3682,7 +3676,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-musl/1.2.204:
@ -3699,7 +3692,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-arm64-msvc/1.2.204:
@ -3716,7 +3708,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-ia32-msvc/1.2.204:
@ -3733,7 +3724,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-x64-msvc/1.2.204:
@ -3750,7 +3740,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core/1.2.204:
@ -3790,7 +3779,6 @@ packages:
'@swc/core-win32-arm64-msvc': 1.2.208
'@swc/core-win32-ia32-msvc': 1.2.208
'@swc/core-win32-x64-msvc': 1.2.208
dev: true
/@swc/helpers/0.4.2:
resolution: {integrity: sha512-556Az0VX7WR6UdoTn4htt/l3zPQ7bsQWK+HqdG4swV7beUCxo/BqmvbOpUkTIm/9ih86LIf1qsUnywNL3obGHw==}
@ -3833,7 +3821,7 @@ packages:
eslint: 8.19.0
eslint-config-galex: 3.6.5_eslint@8.19.0+jest@28.1.2
eslint-plugin-es: 4.1.0_eslint@8.19.0
jest: 28.1.2_@types+node@16.11.43
jest: 28.1.2_qv5kk3vgcyi3feqo7l5zvvzluy
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@ -3897,24 +3885,20 @@ packages:
/@tsconfig/node10/1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12/1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14/1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16/1.0.3:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
/@types/accepts/1.3.5:
resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
dev: false
optional: true
@ -3957,12 +3941,12 @@ packages:
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
dependencies:
'@types/connect': 3.4.35
'@types/node': 16.11.33
'@types/node': 16.11.43
/@types/connect/3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
/@types/content-disposition/0.5.5:
resolution: {integrity: sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==}
@ -3975,7 +3959,7 @@ packages:
'@types/connect': 3.4.35
'@types/express': 4.17.13
'@types/keygrip': 1.0.2
'@types/node': 16.11.33
'@types/node': 16.11.43
dev: false
optional: true
@ -3992,7 +3976,7 @@ packages:
/@types/express-serve-static-core/4.17.29:
resolution: {integrity: sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
@ -4013,14 +3997,14 @@ packages:
/@types/fs-capacitor/2.0.0:
resolution: {integrity: sha512-FKVPOCFbhCvZxpVAMhdBdTfVfXUpsh15wFHgqOKxh9N9vzWZVuWCSijZ5T4U34XYNnuj2oduh6xcs1i+LPI+BQ==}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
dev: false
optional: true
/@types/graceful-fs/4.1.5:
resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
/@types/hast/2.3.4:
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
@ -4114,7 +4098,7 @@ packages:
'@types/http-errors': 1.8.2
'@types/keygrip': 1.0.2
'@types/koa-compose': 3.2.5
'@types/node': 16.11.33
'@types/node': 16.11.43
dev: false
optional: true
@ -4154,7 +4138,7 @@ packages:
/@types/node-fetch/2.5.10:
resolution: {integrity: sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
form-data: 3.0.1
dev: false
optional: true
@ -4252,7 +4236,7 @@ packages:
resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==}
dependencies:
'@types/mime': 1.3.2
'@types/node': 16.11.33
'@types/node': 16.11.43
/@types/sharp/0.30.4:
resolution: {integrity: sha512-6oJEzKt7wZeS7e+6x9QFEOWGs0T/6of00+0onZGN1zSmcSjcTDZKgIGZ6YWJnHowpaKUCFBPH52mYljWqU32Eg==}
@ -4270,7 +4254,7 @@ packages:
/@types/ws/7.4.7:
resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
dev: false
optional: true
@ -4555,7 +4539,6 @@ packages:
/acorn-walk/8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn/7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
@ -5019,7 +5002,6 @@ packages:
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/arg/5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
@ -6077,7 +6059,6 @@ packages:
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/cron/1.8.2:
resolution: {integrity: sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==}
@ -6379,7 +6360,6 @@ packages:
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diff/5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
@ -7020,7 +7000,7 @@ packages:
'@typescript-eslint/eslint-plugin': 5.12.0_byxz3mnn3ms3gbtbci7fo4cm6q
'@typescript-eslint/utils': 5.29.0_m32fwjepeyylyephxtubzxm4ui
eslint: 8.19.0
jest: 28.1.2_@types+node@16.11.43
jest: 28.1.2_qv5kk3vgcyi3feqo7l5zvvzluy
transitivePeerDependencies:
- supports-color
- typescript
@ -8990,6 +8970,7 @@ packages:
- '@types/node'
- supports-color
- ts-node
dev: true
/jest-cli/28.1.2_qv5kk3vgcyi3feqo7l5zvvzluy:
resolution: {integrity: sha512-l6eoi5Do/IJUXAFL9qRmDiFpBeEJAnjJb1dcd9i/VWfVWbp3mJhuH50dNtX67Ali4Ecvt4eBkWb4hXhPHkAZTw==}
@ -9017,7 +8998,6 @@ packages:
- '@types/node'
- supports-color
- ts-node
dev: true
/jest-config/28.1.2_4maxphccb5fztufhofwcslq6fm:
resolution: {integrity: sha512-g6EfeRqddVbjPVBVY4JWpUY4IvQoFRIZcv4V36QkqzE0IGhEC/VkugFeBMAeUE7PRgC8KJF0yvJNDeQRbamEVA==}
@ -9057,7 +9037,6 @@ packages:
ts-node: 10.8.2_pbcylixk5f7tclmtradmulh4qa
transitivePeerDependencies:
- supports-color
dev: true
/jest-config/28.1.2_@types+node@16.11.43:
resolution: {integrity: sha512-g6EfeRqddVbjPVBVY4JWpUY4IvQoFRIZcv4V36QkqzE0IGhEC/VkugFeBMAeUE7PRgC8KJF0yvJNDeQRbamEVA==}
@ -9096,6 +9075,7 @@ packages:
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
dev: true
/jest-config/28.1.2_qv5kk3vgcyi3feqo7l5zvvzluy:
resolution: {integrity: sha512-g6EfeRqddVbjPVBVY4JWpUY4IvQoFRIZcv4V36QkqzE0IGhEC/VkugFeBMAeUE7PRgC8KJF0yvJNDeQRbamEVA==}
@ -9135,7 +9115,6 @@ packages:
ts-node: 10.8.2_pbcylixk5f7tclmtradmulh4qa
transitivePeerDependencies:
- supports-color
dev: true
/jest-diff/28.1.1:
resolution: {integrity: sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg==}
@ -9183,7 +9162,7 @@ packages:
dependencies:
'@jest/types': 26.6.2
'@types/graceful-fs': 4.1.5
'@types/node': 16.11.33
'@types/node': 16.11.43
anymatch: 3.1.2
fb-watchman: 2.0.1
graceful-fs: 4.2.10
@ -9359,7 +9338,7 @@ packages:
resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==}
engines: {node: '>= 10.14.2'}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
graceful-fs: 4.2.10
dev: false
@ -9398,7 +9377,7 @@ packages:
engines: {node: '>= 10.14.2'}
dependencies:
'@jest/types': 26.6.2
'@types/node': 16.11.33
'@types/node': 16.11.43
chalk: 4.1.2
graceful-fs: 4.2.10
is-ci: 2.0.0
@ -9444,7 +9423,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 16.11.33
'@types/node': 16.11.43
merge-stream: 2.0.0
supports-color: 7.2.0
dev: false
@ -9475,6 +9454,7 @@ packages:
- '@types/node'
- supports-color
- ts-node
dev: true
/jest/28.1.2_qv5kk3vgcyi3feqo7l5zvvzluy:
resolution: {integrity: sha512-Tuf05DwLeCh2cfWCQbcz9UxldoDyiR1E9Igaei5khjonKncYdc6LDfynKCEWozK0oLE3GD+xKAo2u8x/0s6GOg==}
@ -9494,7 +9474,6 @@ packages:
- '@types/node'
- supports-color
- ts-node
dev: true
/joycon/3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
@ -10024,7 +10003,6 @@ packages:
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/make-fetch-happen/8.0.14:
resolution: {integrity: sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ==}
@ -13126,7 +13104,6 @@ packages:
typescript: 4.7.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ts-node/9.1.1_typescript@4.7.4:
resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==}
@ -13443,7 +13420,6 @@ packages:
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/ua-parser-js/0.7.31:
resolution: {integrity: sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==}
@ -13676,7 +13652,6 @@ packages:
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/v8-compile-cache/2.3.0:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
@ -14024,7 +13999,6 @@ packages:
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}