Feat/expose sharp options (#1029)
Co-authored-by: khakimvinh <kha.kim.vinh@gmail.com> Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
This commit is contained in:
@@ -45,6 +45,8 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
|
||||
| **`staticURL`** \* | The base URL path to use to access your uploads. Example: `/media` |
|
||||
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
@@ -68,13 +70,13 @@ const Media: CollectionConfig = {
|
||||
name: 'thumbnail',
|
||||
width: 400,
|
||||
height: 300,
|
||||
crop: 'centre',
|
||||
position: 'centre',
|
||||
},
|
||||
{
|
||||
name: 'card',
|
||||
width: 768,
|
||||
height: 1024,
|
||||
crop: 'centre',
|
||||
position: 'centre',
|
||||
},
|
||||
{
|
||||
name: 'tablet',
|
||||
@@ -84,7 +86,7 @@ const Media: CollectionConfig = {
|
||||
// but it will retain its original aspect ratio
|
||||
// and calculate a height automatically.
|
||||
height: null,
|
||||
crop: 'centre',
|
||||
position: 'centre',
|
||||
},
|
||||
],
|
||||
adminThumbnail: 'thumbnail',
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"express-graphql": "0.12.0",
|
||||
"express-rate-limit": "^5.1.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"file-type": "16.5.4",
|
||||
"find-up": "4.1.0",
|
||||
"flatley": "^5.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
@@ -128,7 +129,6 @@
|
||||
"jwt-decode": "^3.1.2",
|
||||
"method-override": "^3.0.0",
|
||||
"micro-memoize": "^4.0.9",
|
||||
"mime": "^2.5.0",
|
||||
"mini-css-extract-plugin": "1.3.3",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
|
||||
@@ -135,11 +135,28 @@ const collectionSchema = joi.object().keys({
|
||||
width: joi.number().allow(null),
|
||||
height: joi.number().allow(null),
|
||||
crop: joi.string(), // TODO: add further specificity with joi.xor
|
||||
}),
|
||||
}).unknown(),
|
||||
),
|
||||
mimeTypes: joi.array().items(joi.string()),
|
||||
staticOptions: joi.object(),
|
||||
handlers: joi.array().items(joi.func()),
|
||||
resizeOptions: joi.object().keys({
|
||||
width: joi.number().allow(null),
|
||||
height: joi.number().allow(null),
|
||||
fit: joi.string(),
|
||||
position: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.number(),
|
||||
),
|
||||
background: joi.string(),
|
||||
kernel: joi.string(),
|
||||
withoutEnlargement: joi.bool(),
|
||||
fastShrinkOnLoad: joi.bool(),
|
||||
}).allow(null),
|
||||
formatOptions: joi.object().keys({
|
||||
format: joi.string(),
|
||||
options: joi.object(),
|
||||
}),
|
||||
}),
|
||||
joi.boolean(),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Document } from '../../../types';
|
||||
import getFileByPath from '../../../uploads/getFileByPath';
|
||||
import create from '../create';
|
||||
import { getDataLoader } from '../../dataloader';
|
||||
import { File } from '../../../uploads/types';
|
||||
|
||||
|
||||
export type Options<T> = {
|
||||
@@ -18,7 +19,7 @@ export type Options<T> = {
|
||||
disableVerificationEmail?: boolean
|
||||
showHiddenFields?: boolean
|
||||
filePath?: string
|
||||
file?: UploadedFile
|
||||
file?: File
|
||||
overwriteExistingFiles?: boolean
|
||||
req?: PayloadRequest
|
||||
draft?: boolean
|
||||
@@ -49,7 +50,7 @@ export default async function createLocal<T = any>(payload: Payload, options: Op
|
||||
req.fallbackLocale = fallbackLocale || req?.fallbackLocale || null;
|
||||
req.payload = payload;
|
||||
req.files = {
|
||||
file: (file as UploadedFile) ?? (getFileByPath(filePath) as UploadedFile),
|
||||
file: (file ?? (await getFileByPath(filePath))) as UploadedFile,
|
||||
};
|
||||
|
||||
if (typeof user !== 'undefined') req.user = user;
|
||||
|
||||
@@ -49,7 +49,7 @@ export default async function updateLocal<T = any>(payload: Payload, options: Op
|
||||
fallbackLocale,
|
||||
payload,
|
||||
files: {
|
||||
file: file ?? getFileByPath(filePath),
|
||||
file: file ?? await getFileByPath(filePath),
|
||||
},
|
||||
} as PayloadRequest;
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import fs from 'fs';
|
||||
import mime from 'mime';
|
||||
import { fromFile } from 'file-type';
|
||||
import path from 'path';
|
||||
import { File } from './types';
|
||||
|
||||
const getFileByPath = (filePath: string): File => {
|
||||
const getFileByPath = async (filePath: string): Promise<File> => {
|
||||
if (typeof filePath === 'string') {
|
||||
const data = fs.readFileSync(filePath);
|
||||
const mimetype = mime.getType(filePath);
|
||||
const mimetype = fromFile(filePath);
|
||||
const { size } = fs.statSync(filePath);
|
||||
|
||||
const name = path.basename(filePath);
|
||||
|
||||
return {
|
||||
data,
|
||||
mimetype,
|
||||
mimetype: (await mimetype).mime,
|
||||
name,
|
||||
size,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import fileExists from './fileExists';
|
||||
|
||||
const incrementName = (name: string) => {
|
||||
const extension = name.split('.').pop();
|
||||
const baseFilename = sanitize(name.substr(0, name.lastIndexOf('.')) || name);
|
||||
const baseFilename = sanitize(name.substring(0, name.lastIndexOf('.')) || name);
|
||||
let incrementedName = baseFilename;
|
||||
const regex = /(.*)-(\d)$/;
|
||||
const found = baseFilename.match(regex);
|
||||
@@ -15,8 +15,7 @@ const incrementName = (name: string) => {
|
||||
const matchedName = found[1];
|
||||
const matchedNumber = found[2];
|
||||
const incremented = Number(matchedNumber) + 1;
|
||||
const newName = `${matchedName}-${incremented}`;
|
||||
incrementedName = newName;
|
||||
incrementedName = `${matchedName}-${incremented}`;
|
||||
}
|
||||
return `${incrementedName}.${extension}`;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import sharp from 'sharp';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { fromBuffer } from 'file-type';
|
||||
import { ProbedImageSize } from './getImageSize';
|
||||
import fileExists from './fileExists';
|
||||
import { SanitizedCollectionConfig } from '../collections/config/types';
|
||||
@@ -8,18 +9,25 @@ import { FileSizes, ImageSize } from './types';
|
||||
import { PayloadRequest } from '../express/types';
|
||||
|
||||
type Args = {
|
||||
req: PayloadRequest,
|
||||
file: Buffer,
|
||||
dimensions: ProbedImageSize,
|
||||
staticPath: string,
|
||||
config: SanitizedCollectionConfig,
|
||||
savedFilename: string,
|
||||
mimeType: string,
|
||||
req: PayloadRequest
|
||||
file: Buffer
|
||||
dimensions: ProbedImageSize
|
||||
staticPath: string
|
||||
config: SanitizedCollectionConfig
|
||||
savedFilename: string
|
||||
mimeType: string
|
||||
}
|
||||
|
||||
function getOutputImage(sourceImage: string, size: ImageSize) {
|
||||
type OutputImage = {
|
||||
name: string,
|
||||
extension: string,
|
||||
width: number,
|
||||
height: number
|
||||
}
|
||||
|
||||
function getOutputImage(sourceImage: string, size: ImageSize): OutputImage {
|
||||
const extension = sourceImage.split('.').pop();
|
||||
const name = sanitize(sourceImage.substr(0, sourceImage.lastIndexOf('.')) || sourceImage);
|
||||
const name = sanitize(sourceImage.substring(0, sourceImage.lastIndexOf('.')) || sourceImage);
|
||||
|
||||
return {
|
||||
name,
|
||||
@@ -29,14 +37,6 @@ function getOutputImage(sourceImage: string, size: ImageSize) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
* @param staticPath Path to save images
|
||||
* @param config Payload config
|
||||
* @param savedFilename
|
||||
* @param mimeType
|
||||
* @returns image sizes keyed to strings
|
||||
*/
|
||||
export default async function resizeAndSave({
|
||||
req,
|
||||
file,
|
||||
@@ -44,17 +44,17 @@ export default async function resizeAndSave({
|
||||
staticPath,
|
||||
config,
|
||||
savedFilename,
|
||||
mimeType,
|
||||
}: Args): Promise<FileSizes> {
|
||||
const { imageSizes, disableLocalStorage } = config.upload;
|
||||
|
||||
const sizes = imageSizes
|
||||
.filter((desiredSize) => desiredSize.width <= dimensions.width || desiredSize.height <= dimensions.height)
|
||||
.filter((desiredSize) => needsResize(desiredSize, dimensions))
|
||||
.map(async (desiredSize) => {
|
||||
const resized = await sharp(file)
|
||||
.resize(desiredSize.width, desiredSize.height, {
|
||||
position: desiredSize.crop || 'centre',
|
||||
});
|
||||
let resized = await sharp(file).resize(desiredSize);
|
||||
|
||||
if (desiredSize.formatOptions) {
|
||||
resized = resized.toFormat(desiredSize.formatOptions.format, desiredSize.formatOptions.options);
|
||||
}
|
||||
|
||||
const bufferObject = await resized.toBuffer({
|
||||
resolveWithObject: true,
|
||||
@@ -63,7 +63,7 @@ export default async function resizeAndSave({
|
||||
req.payloadUploadSizes[desiredSize.name] = bufferObject.data;
|
||||
|
||||
const outputImage = getOutputImage(savedFilename, desiredSize);
|
||||
const imageNameWithDimensions = `${outputImage.name}-${bufferObject.info.width}x${bufferObject.info.height}.${outputImage.extension}`;
|
||||
const imageNameWithDimensions = createImageName(outputImage, bufferObject);
|
||||
const imagePath = `${staticPath}/${imageNameWithDimensions}`;
|
||||
const fileAlreadyExists = await fileExists(imagePath);
|
||||
|
||||
@@ -81,20 +81,34 @@ export default async function resizeAndSave({
|
||||
height: bufferObject.info.height,
|
||||
filename: imageNameWithDimensions,
|
||||
filesize: bufferObject.info.size,
|
||||
mimeType,
|
||||
mimeType: (await fromBuffer(bufferObject.data)).mime,
|
||||
};
|
||||
});
|
||||
|
||||
const savedSizes = await Promise.all(sizes);
|
||||
|
||||
return savedSizes.reduce((results, size) => ({
|
||||
...results,
|
||||
[size.name]: {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
filename: size.filename,
|
||||
mimeType: size.mimeType,
|
||||
filesize: size.filesize,
|
||||
},
|
||||
}), {});
|
||||
return savedSizes.reduce(
|
||||
(results, size) => ({
|
||||
...results,
|
||||
[size.name]: {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
filename: size.filename,
|
||||
mimeType: size.mimeType,
|
||||
filesize: size.filesize,
|
||||
},
|
||||
}),
|
||||
{},
|
||||
);
|
||||
}
|
||||
function createImageName(
|
||||
outputImage: OutputImage,
|
||||
bufferObject: { data: Buffer; info: sharp.OutputInfo },
|
||||
): string {
|
||||
return `${outputImage.name}-${bufferObject.info.width}x${bufferObject.info.height}.${outputImage.extension}`;
|
||||
}
|
||||
|
||||
function needsResize(desiredSize: ImageSize, dimensions: ProbedImageSize): boolean {
|
||||
return (typeof desiredSize.width === 'number' && desiredSize.width <= dimensions.width)
|
||||
|| (typeof desiredSize.height === 'number' && desiredSize.height <= dimensions.height);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import express from 'express';
|
||||
import serveStatic from 'serve-static';
|
||||
import { Sharp, ResizeOptions } from 'sharp';
|
||||
|
||||
export type FileSize = {
|
||||
filename: string;
|
||||
@@ -25,36 +26,50 @@ export type FileData = {
|
||||
sizes: FileSizes;
|
||||
};
|
||||
|
||||
export type ImageSize = {
|
||||
name: string,
|
||||
width: number | null,
|
||||
height: number | null,
|
||||
crop?: string, // comes from sharp package
|
||||
/**
|
||||
* Params sent to the sharp toFormat() function
|
||||
* @link https://sharp.pixelplumbing.com/api-output#toformat
|
||||
*/
|
||||
export type ImageUploadFormatOptions = {
|
||||
format: Parameters<Sharp['toFormat']>[0]
|
||||
options?: Parameters<Sharp['toFormat']>[1]
|
||||
}
|
||||
|
||||
export type ImageSize = ResizeOptions & {
|
||||
name: string
|
||||
formatOptions?: ImageUploadFormatOptions
|
||||
/**
|
||||
* @deprecated prefer position
|
||||
*/
|
||||
crop?: string // comes from sharp package
|
||||
};
|
||||
|
||||
export type GetAdminThumbnail = (args: { doc: Record<string, unknown> }) => string
|
||||
|
||||
export type IncomingUploadType = {
|
||||
imageSizes?: ImageSize[];
|
||||
staticURL?: string;
|
||||
staticDir?: string;
|
||||
imageSizes?: ImageSize[]
|
||||
staticURL?: string
|
||||
staticDir?: string
|
||||
disableLocalStorage?: boolean
|
||||
adminThumbnail?: string | GetAdminThumbnail;
|
||||
mimeTypes?: string[];
|
||||
adminThumbnail?: string | GetAdminThumbnail
|
||||
mimeTypes?: string[]
|
||||
staticOptions?: serveStatic.ServeStaticOptions<express.Response<any, Record<string, any>>>
|
||||
handlers?: any[]
|
||||
resizeOptions?: ResizeOptions
|
||||
formatOptions?: ImageUploadFormatOptions
|
||||
}
|
||||
|
||||
|
||||
export type Upload = {
|
||||
imageSizes?: ImageSize[]
|
||||
staticURL: string
|
||||
staticDir: string
|
||||
disableLocalStorage: boolean
|
||||
adminThumbnail?: string | GetAdminThumbnail
|
||||
mimeTypes?: string[];
|
||||
mimeTypes?: string[]
|
||||
staticOptions?: serveStatic.ServeStaticOptions<express.Response<any, Record<string, any>>>
|
||||
handlers?: any[]
|
||||
resizeOptions?: ResizeOptions;
|
||||
formatOptions?: ImageUploadFormatOptions
|
||||
}
|
||||
|
||||
export type File = {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import mkdirp from 'mkdirp';
|
||||
import path from 'path';
|
||||
import mime from 'mime';
|
||||
import sharp, { Sharp } from 'sharp';
|
||||
import { fromBuffer } from 'file-type';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { SanitizedConfig } from '../config/types';
|
||||
import { Collection } from '../collections/config/types';
|
||||
import { FileUploadError, MissingFile } from '../errors';
|
||||
@@ -37,7 +39,7 @@ const uploadFile = async ({
|
||||
if (collectionConfig.upload) {
|
||||
const fileData: Partial<FileData> = {};
|
||||
|
||||
const { staticDir, imageSizes, disableLocalStorage } = collectionConfig.upload;
|
||||
const { staticDir, imageSizes, disableLocalStorage, resizeOptions, formatOptions } = collectionConfig.upload;
|
||||
|
||||
const { file } = req.files || {};
|
||||
|
||||
@@ -56,16 +58,37 @@ const uploadFile = async ({
|
||||
}
|
||||
|
||||
if (file) {
|
||||
const fsSafeName = !overwriteExistingFiles ? await getSafeFileName(Model, staticPath, file.name) : file.name;
|
||||
|
||||
try {
|
||||
let fsSafeName: string;
|
||||
let fileBuffer: Buffer;
|
||||
let mimeType: string;
|
||||
let fileSize: number;
|
||||
|
||||
if (!disableLocalStorage) {
|
||||
await saveBufferToFile(file.data, `${staticPath}/${fsSafeName}`);
|
||||
let resized: Sharp | undefined;
|
||||
if (resizeOptions) {
|
||||
resized = sharp(file.data).resize(resizeOptions);
|
||||
}
|
||||
if (formatOptions) {
|
||||
resized = (resized ?? sharp(file.data)).toFormat(formatOptions.format, formatOptions.options);
|
||||
}
|
||||
fileBuffer = resized ? (await resized.toBuffer()) : file.data;
|
||||
const { mime, ext } = await fromBuffer(fileBuffer);
|
||||
mimeType = mime;
|
||||
fileSize = fileBuffer.length;
|
||||
const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name);
|
||||
fsSafeName = `${baseFilename}.${ext}`;
|
||||
|
||||
if (!overwriteExistingFiles) {
|
||||
fsSafeName = await getSafeFileName(Model, staticPath, fsSafeName);
|
||||
}
|
||||
|
||||
await saveBufferToFile(fileBuffer, `${staticPath}/${fsSafeName}`);
|
||||
}
|
||||
|
||||
fileData.filename = fsSafeName;
|
||||
fileData.filesize = file.size;
|
||||
fileData.mimeType = file.mimetype || mime.getType(fsSafeName);
|
||||
fileData.filename = fsSafeName || (!overwriteExistingFiles ? await getSafeFileName(Model, staticPath, file.name) : file.name);
|
||||
fileData.filesize = fileSize || file.size;
|
||||
fileData.mimeType = mimeType || (await fromBuffer(file.data)).mime;
|
||||
|
||||
if (isImage(file.mimetype)) {
|
||||
const dimensions = await getImageSize(file);
|
||||
|
||||
@@ -80,7 +80,7 @@ export default buildConfig({
|
||||
if (fs.existsSync(uploadsDir)) fs.readdirSync(uploadsDir).forEach((f) => fs.rmSync(`${uploadsDir}/${f}`));
|
||||
|
||||
const filePath = path.resolve(__dirname, './collections/Upload/payload.jpg');
|
||||
const file = getFileByPath(filePath);
|
||||
const file = await getFileByPath(filePath);
|
||||
|
||||
const createdUploadDoc = await payload.create({ collection: 'uploads', data: uploadsDoc, file });
|
||||
|
||||
|
||||
@@ -39,12 +39,23 @@ export default buildConfig({
|
||||
upload: {
|
||||
staticURL: '/media',
|
||||
staticDir: './media',
|
||||
resizeOptions: {
|
||||
width: 1280,
|
||||
height: 720,
|
||||
position: 'center',
|
||||
},
|
||||
formatOptions: {
|
||||
format: 'png',
|
||||
options: { quality: 90 },
|
||||
},
|
||||
imageSizes: [
|
||||
{
|
||||
name: 'maintainedAspectRatio',
|
||||
width: 1024,
|
||||
height: null,
|
||||
crop: 'center',
|
||||
position: 'center',
|
||||
formatOptions: { format: 'png', options: { quality: 90 } },
|
||||
},
|
||||
{
|
||||
name: 'tablet',
|
||||
@@ -89,7 +100,7 @@ export default buildConfig({
|
||||
});
|
||||
// Create image
|
||||
const filePath = path.resolve(__dirname, './image.png');
|
||||
const file = getFileByPath(filePath);
|
||||
const file = await getFileByPath(filePath);
|
||||
|
||||
const { id: uploadedImage } = await payload.create({
|
||||
collection: mediaSlug,
|
||||
|
||||
BIN
test/uploads/image.jpg
Normal file
BIN
test/uploads/image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@@ -80,6 +80,30 @@ describe('Collections - Uploads', () => {
|
||||
expect(doc.sizes.icon.filename).toBeDefined();
|
||||
});
|
||||
|
||||
it('creates images from a different format', async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', fs.createReadStream(path.join(__dirname, './image.jpg')));
|
||||
|
||||
const { status, doc } = await client.create({
|
||||
file: true,
|
||||
data: formData,
|
||||
auth: true,
|
||||
headers: {},
|
||||
});
|
||||
|
||||
expect(status).toBe(201);
|
||||
|
||||
// Check for files
|
||||
expect(await fileExists(path.join(__dirname, './media', doc.filename))).toBe(true);
|
||||
expect(await fileExists(path.join(__dirname, './media', doc.sizes.tablet.filename))).toBe(true);
|
||||
|
||||
// Check api response
|
||||
expect(doc.filename).toContain('.png');
|
||||
expect(doc.mimeType).toEqual('image/png');
|
||||
expect(doc.sizes.maintainedAspectRatio.filename).toContain('.png');
|
||||
expect(doc.sizes.maintainedAspectRatio.mimeType).toContain('image/png');
|
||||
});
|
||||
|
||||
it('creates media without storing a file', async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', fs.createReadStream(path.join(__dirname, './small.png')));
|
||||
@@ -106,7 +130,7 @@ describe('Collections - Uploads', () => {
|
||||
it('update', async () => {
|
||||
// Create image
|
||||
const filePath = path.resolve(__dirname, './image.png');
|
||||
const file = getFileByPath(filePath);
|
||||
const file = await getFileByPath(filePath);
|
||||
file.name = 'renamed.png';
|
||||
|
||||
const mediaDoc = await payload.create({
|
||||
@@ -118,7 +142,7 @@ describe('Collections - Uploads', () => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', fs.createReadStream(path.join(__dirname, './small.png')));
|
||||
|
||||
const { status, doc } = await client.update({
|
||||
const { status } = await client.update({
|
||||
id: mediaDoc.id,
|
||||
file: true,
|
||||
data: formData,
|
||||
@@ -135,7 +159,7 @@ describe('Collections - Uploads', () => {
|
||||
|
||||
it('should allow update removing a relationship', async () => {
|
||||
const filePath = path.resolve(__dirname, './image.png');
|
||||
const file = getFileByPath(filePath);
|
||||
const file = await getFileByPath(filePath);
|
||||
file.name = 'renamed.png';
|
||||
|
||||
const { id } = await payload.create({
|
||||
|
||||
49
yarn.lock
49
yarn.lock
@@ -1817,6 +1817,11 @@
|
||||
"@testing-library/dom" "^8.5.0"
|
||||
"@types/react-dom" "^18.0.0"
|
||||
|
||||
"@tokenizer/token@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
|
||||
integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
@@ -5898,6 +5903,15 @@ file-loader@^6.2.0:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
file-type@16.5.4:
|
||||
version "16.5.4"
|
||||
resolved "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd"
|
||||
integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==
|
||||
dependencies:
|
||||
readable-web-to-node-stream "^3.0.0"
|
||||
strtok3 "^6.2.4"
|
||||
token-types "^4.1.1"
|
||||
|
||||
file-uri-to-path@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba"
|
||||
@@ -6736,7 +6750,7 @@ icss-utils@^5.0.0, icss-utils@^5.1.0:
|
||||
resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
||||
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
||||
|
||||
ieee754@^1.1.13:
|
||||
ieee754@^1.1.13, ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
@@ -8511,11 +8525,6 @@ mime@1.6.0:
|
||||
resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.5.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
||||
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
@@ -9493,6 +9502,11 @@ pause@0.0.1:
|
||||
resolved "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
|
||||
integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
|
||||
|
||||
peek-readable@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72"
|
||||
integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
@@ -10697,6 +10711,13 @@ readable-stream@~1.0.31:
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-web-to-node-stream@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb"
|
||||
integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==
|
||||
dependencies:
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
@@ -11825,6 +11846,14 @@ strip-json-comments@~2.0.1:
|
||||
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
|
||||
|
||||
strtok3@^6.2.4:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0"
|
||||
integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==
|
||||
dependencies:
|
||||
"@tokenizer/token" "^0.3.0"
|
||||
peek-readable "^4.1.0"
|
||||
|
||||
style-loader@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c"
|
||||
@@ -12131,6 +12160,14 @@ toidentifier@1.0.1:
|
||||
resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
token-types@^4.1.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753"
|
||||
integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==
|
||||
dependencies:
|
||||
"@tokenizer/token" "^0.3.0"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
totalist@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
||||
|
||||
Reference in New Issue
Block a user