fix: incorrect config.upload types (#7874)

Fixes https://github.com/payloadcms/payload/issues/7698

Now exporting `FetchAPIFileUploadOptions` from payload, as that type is
now used in `config.upload`.
This commit is contained in:
Alessio Gravili
2024-08-28 15:39:51 -04:00
committed by GitHub
parent 78c8bb81a1
commit 0962850086
10 changed files with 120 additions and 125 deletions

View File

@@ -90,7 +90,7 @@
"@babel/preset-typescript": "^7.24.1", "@babel/preset-typescript": "^7.24.1",
"@next/eslint-plugin-next": "15.0.0-canary.104", "@next/eslint-plugin-next": "15.0.0-canary.104",
"@payloadcms/eslint-config": "workspace:*", "@payloadcms/eslint-config": "workspace:*",
"@types/busboy": "^1.5.3", "@types/busboy": "1.5.4",
"@types/react": "npm:types-react@19.0.0-rc.0", "@types/react": "npm:types-react@19.0.0-rc.0",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"@types/uuid": "10.0.0", "@types/uuid": "10.0.0",

View File

@@ -1,4 +1,6 @@
import type { FetchAPIFileUploadOptions, FileShape } from './index.js' import type { FetchAPIFileUploadOptions } from 'payload'
import type { FileShape } from './index.js'
import { import {
checkAndMakeDir, checkAndMakeDir,

View File

@@ -1,9 +1,9 @@
import type { FetchAPIFileUploadOptions } from 'payload'
import crypto from 'crypto' import crypto from 'crypto'
import fs, { WriteStream } from 'fs' import fs, { WriteStream } from 'fs'
import path from 'path' import path from 'path'
import type { FetchAPIFileUploadOptions } from './index.js'
import { checkAndMakeDir, debugLog, deleteFile, getTempFilename } from './utilities.js' import { checkAndMakeDir, debugLog, deleteFile, getTempFilename } from './utilities.js'
type Handler = ( type Handler = (

View File

@@ -1,4 +1,4 @@
import type { BusboyConfig } from 'busboy' import type { FetchAPIFileUploadOptions } from 'payload'
import path from 'path' import path from 'path'
import { APIError } from 'payload' import { APIError } from 'payload'
@@ -35,104 +35,6 @@ export type FileShape = {
truncated: boolean truncated: boolean
} }
export type FetchAPIFileUploadOptions = {
/**
* Returns a HTTP 413 when the file is bigger than the size limit if `true`.
* Otherwise, it will add a `truncated = true` to the resulting file structure.
* @default false
*/
abortOnLimit?: boolean | undefined
/**
* Automatically creates the directory path specified in `.mv(filePathName)`
* @default false
*/
createParentPath?: boolean | undefined
/**
* Turn on/off upload process logging. Can be useful for troubleshooting.
* @default false
*/
debug?: boolean | undefined
/**
* User defined limit handler which will be invoked if the file is bigger than configured limits.
* @default false
*/
limitHandler?: ((args: { request: Request; size: number }) => void) | boolean | undefined
/**
* By default, `req.body` and `req.files` are flattened like this:
* `{'name': 'John', 'hobbies[0]': 'Cinema', 'hobbies[1]': 'Bike'}
*
* When this option is enabled they are parsed in order to be nested like this:
* `{'name': 'John', 'hobbies': ['Cinema', 'Bike']}`
* @default false
*/
parseNested?: boolean | undefined
/**
* Preserves filename extension when using `safeFileNames` option.
* If set to `true`, will default to an extension length of `3`.
* If set to `number`, this will be the max allowable extension length.
* If an extension is smaller than the extension length, it remains untouched. If the extension is longer,
* it is shifted.
* @default false
*
* @example
* // true
* app.use(fileUpload({ safeFileNames: true, preserveExtension: true }));
* // myFileName.ext --> myFileName.ext
*
* @example
* // max extension length 2, extension shifted
* app.use(fileUpload({ safeFileNames: true, preserveExtension: 2 }));
* // myFileName.ext --> myFileNamee.xt
*/
preserveExtension?: boolean | number | undefined
/**
* Response which will be send to client if file size limit exceeded when `abortOnLimit` set to `true`.
* @default 'File size limit has been reached'
*/
responseOnLimit?: string | undefined
/**
* Strips characters from the upload's filename.
* You can use custom regex to determine what to strip.
* If set to `true`, non-alphanumeric characters _except_ dashes and underscores will be stripped.
* This option is off by default.
* @default false
*
* @example
* // strip slashes from file names
* app.use(fileUpload({ safeFileNames: /\\/g }))
*
* @example
* app.use(fileUpload({ safeFileNames: true }))
*/
safeFileNames?: RegExp | boolean | undefined
/**
* Path to store temporary files.
* Used along with the `useTempFiles` option. By default this module uses `'tmp'` folder
* in the current working directory.
* You can use trailing slash, but it is not necessary.
* @default './tmp'
*/
tempFileDir?: string | undefined
/**
* This defines how long to wait for data before aborting. Set to `0` if you want to turn off timeout checks.
* @default 60_000
*/
uploadTimeout?: number | undefined
/**
* Applies uri decoding to file names if set `true`.
* @default false
*/
uriDecodeFileNames?: boolean | undefined
/**
* By default this module uploads files into RAM.
* Setting this option to `true` turns on using temporary files instead of utilising RAM.
* This avoids memory overflow issues when uploading large files or in case of uploading
* lots of files at same time.
* @default false
*/
useTempFiles?: boolean | undefined
} & Partial<BusboyConfig>
type FetchAPIFileUploadResponseFile = { type FetchAPIFileUploadResponseFile = {
data: Buffer data: Buffer
mimetype: string mimetype: string

View File

@@ -1,10 +1,11 @@
import type { FetchAPIFileUploadOptions } from 'payload'
import type { Readable } from 'stream' import type { Readable } from 'stream'
import Busboy from 'busboy' import Busboy from 'busboy'
import httpStatus from 'http-status' import httpStatus from 'http-status'
import { APIError } from 'payload' import { APIError } from 'payload'
import type { FetchAPIFileUploadOptions, FetchAPIFileUploadResponse } from './index.js' import type { FetchAPIFileUploadResponse } from './index.js'
import { fileFactory } from './fileFactory.js' import { fileFactory } from './fileFactory.js'
import { memHandler, tempFileHandler } from './handlers.js' import { memHandler, tempFileHandler } from './handlers.js'
@@ -163,6 +164,8 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
uploadTimer.set() uploadTimer.set()
}) })
// TODO: Valid eslint error - this will likely be a floating promise. Evaluate if we need to handle this differently.
// eslint-disable-next-line @typescript-eslint/no-misused-promises
busboy.on('finish', async () => { busboy.on('finish', async () => {
debugLog(options, `Busboy finished parsing request.`) debugLog(options, `Busboy finished parsing request.`)
if (options.parseNested) { if (options.parseNested) {

View File

@@ -1,9 +1,9 @@
import type { FetchAPIFileUploadOptions } from 'payload'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { Readable } from 'stream' import { Readable } from 'stream'
import type { FetchAPIFileUploadOptions } from './index.js'
// Parameters for safe file name parsing. // Parameters for safe file name parsing.
const SAFE_FILE_NAME_REGEX = /[^\w-]/g const SAFE_FILE_NAME_REGEX = /[^\w-]/g
const MAX_EXTENSION_LENGTH = 3 const MAX_EXTENSION_LENGTH = 3

View File

@@ -2,8 +2,6 @@ import type { PayloadRequest } from 'payload'
import { APIError } from 'payload' import { APIError } from 'payload'
import type { FetchAPIFileUploadOptions } from '../fetchAPI-multipart/index.js'
import { fetchAPIFileUpload } from '../fetchAPI-multipart/index.js' import { fetchAPIFileUpload } from '../fetchAPI-multipart/index.js'
type AddDataAndFileToRequest = (req: PayloadRequest) => Promise<void> type AddDataAndFileToRequest = (req: PayloadRequest) => Promise<void>
@@ -30,7 +28,7 @@ export const addDataAndFileToRequest: AddDataAndFileToRequest = async (req) => {
} }
} else if (bodyByteSize && contentType.includes('multipart/')) { } else if (bodyByteSize && contentType.includes('multipart/')) {
const { error, fields, files } = await fetchAPIFileUpload({ const { error, fields, files } = await fetchAPIFileUpload({
options: payload.config.upload as FetchAPIFileUploadOptions, options: payload.config.upload,
request: req as Request, request: req as Request,
}) })

View File

@@ -86,6 +86,7 @@
"dependencies": { "dependencies": {
"@next/env": "^15.0.0-canary.104", "@next/env": "^15.0.0-canary.104",
"@payloadcms/translations": "workspace:*", "@payloadcms/translations": "workspace:*",
"@types/busboy": "1.5.4",
"ajv": "8.14.0", "ajv": "8.14.0",
"bson-objectid": "2.0.4", "bson-objectid": "2.0.4",
"ci-info": "^4.0.0", "ci-info": "^4.0.0",
@@ -114,7 +115,6 @@
"@hyrious/esbuild-plugin-commonjs": "^0.2.4", "@hyrious/esbuild-plugin-commonjs": "^0.2.4",
"@monaco-editor/react": "4.5.1", "@monaco-editor/react": "4.5.1",
"@payloadcms/eslint-config": "workspace:*", "@payloadcms/eslint-config": "workspace:*",
"@types/express-fileupload": "1.4.1",
"@types/json-schema": "7.0.15", "@types/json-schema": "7.0.15",
"@types/jsonwebtoken": "8.5.9", "@types/jsonwebtoken": "8.5.9",
"@types/minimist": "1.2.2", "@types/minimist": "1.2.2",

View File

@@ -4,7 +4,7 @@ import type {
I18nOptions, I18nOptions,
TFunction, TFunction,
} from '@payloadcms/translations' } from '@payloadcms/translations'
import type { Options as ExpressFileUploadOptions } from 'express-fileupload' import type { BusboyConfig } from 'busboy'
import type GraphQL from 'graphql' import type GraphQL from 'graphql'
import type { JSONSchema4 } from 'json-schema' import type { JSONSchema4 } from 'json-schema'
import type { DestinationStream, LoggerOptions } from 'pino' import type { DestinationStream, LoggerOptions } from 'pino'
@@ -536,6 +536,104 @@ export interface AdminDependencies {
[key: string]: AdminComponent | AdminFunction [key: string]: AdminComponent | AdminFunction
} }
export type FetchAPIFileUploadOptions = {
/**
* Returns a HTTP 413 when the file is bigger than the size limit if `true`.
* Otherwise, it will add a `truncated = true` to the resulting file structure.
* @default false
*/
abortOnLimit?: boolean | undefined
/**
* Automatically creates the directory path specified in `.mv(filePathName)`
* @default false
*/
createParentPath?: boolean | undefined
/**
* Turn on/off upload process logging. Can be useful for troubleshooting.
* @default false
*/
debug?: boolean | undefined
/**
* User defined limit handler which will be invoked if the file is bigger than configured limits.
* @default false
*/
limitHandler?: ((args: { request: Request; size: number }) => void) | boolean | undefined
/**
* By default, `req.body` and `req.files` are flattened like this:
* `{'name': 'John', 'hobbies[0]': 'Cinema', 'hobbies[1]': 'Bike'}
*
* When this option is enabled they are parsed in order to be nested like this:
* `{'name': 'John', 'hobbies': ['Cinema', 'Bike']}`
* @default false
*/
parseNested?: boolean | undefined
/**
* Preserves filename extension when using `safeFileNames` option.
* If set to `true`, will default to an extension length of `3`.
* If set to `number`, this will be the max allowable extension length.
* If an extension is smaller than the extension length, it remains untouched. If the extension is longer,
* it is shifted.
* @default false
*
* @example
* // true
* app.use(fileUpload({ safeFileNames: true, preserveExtension: true }));
* // myFileName.ext --> myFileName.ext
*
* @example
* // max extension length 2, extension shifted
* app.use(fileUpload({ safeFileNames: true, preserveExtension: 2 }));
* // myFileName.ext --> myFileNamee.xt
*/
preserveExtension?: boolean | number | undefined
/**
* Response which will be send to client if file size limit exceeded when `abortOnLimit` set to `true`.
* @default 'File size limit has been reached'
*/
responseOnLimit?: string | undefined
/**
* Strips characters from the upload's filename.
* You can use custom regex to determine what to strip.
* If set to `true`, non-alphanumeric characters _except_ dashes and underscores will be stripped.
* This option is off by default.
* @default false
*
* @example
* // strip slashes from file names
* app.use(fileUpload({ safeFileNames: /\\/g }))
*
* @example
* app.use(fileUpload({ safeFileNames: true }))
*/
safeFileNames?: RegExp | boolean | undefined
/**
* Path to store temporary files.
* Used along with the `useTempFiles` option. By default this module uses `'tmp'` folder
* in the current working directory.
* You can use trailing slash, but it is not necessary.
* @default './tmp'
*/
tempFileDir?: string | undefined
/**
* This defines how long to wait for data before aborting. Set to `0` if you want to turn off timeout checks.
* @default 60_000
*/
uploadTimeout?: number | undefined
/**
* Applies uri decoding to file names if set `true`.
* @default false
*/
uriDecodeFileNames?: boolean | undefined
/**
* By default this module uploads files into RAM.
* Setting this option to `true` turns on using temporary files instead of utilising RAM.
* This avoids memory overflow issues when uploading large files or in case of uploading
* lots of files at same time.
* @default false
*/
useTempFiles?: boolean | undefined
} & Partial<BusboyConfig>
/** /**
* This is the central configuration * This is the central configuration
* *
@@ -885,7 +983,7 @@ export type Config = {
/** /**
* Customize the handling of incoming file uploads for collections that have uploads enabled. * Customize the handling of incoming file uploads for collections that have uploads enabled.
*/ */
upload?: ExpressFileUploadOptions upload?: FetchAPIFileUploadOptions
} }
export type SanitizedConfig = { export type SanitizedConfig = {
@@ -906,7 +1004,7 @@ export type SanitizedConfig = {
* Deduped list of adapters used in the project * Deduped list of adapters used in the project
*/ */
adapters: string[] adapters: string[]
} & ExpressFileUploadOptions } & FetchAPIFileUploadOptions
} & Omit< } & Omit<
// TODO: DeepRequired breaks certain, advanced TypeScript types / certain type information is lost. We should remove it when possible. // TODO: DeepRequired breaks certain, advanced TypeScript types / certain type information is lost. We should remove it when possible.
// E.g. in packages/ui/src/graphics/Account/index.tsx in getComponent, if avatar.Component is casted to what it's supposed to be, // E.g. in packages/ui/src/graphics/Account/index.tsx in getComponent, if avatar.Component is casted to what it's supposed to be,

16
pnpm-lock.yaml generated
View File

@@ -759,7 +759,7 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../eslint-config version: link:../eslint-config
'@types/busboy': '@types/busboy':
specifier: ^1.5.3 specifier: 1.5.4
version: 1.5.4 version: 1.5.4
'@types/react': '@types/react':
specifier: npm:types-react@19.0.0-rc.0 specifier: npm:types-react@19.0.0-rc.0
@@ -800,6 +800,9 @@ importers:
'@payloadcms/translations': '@payloadcms/translations':
specifier: workspace:* specifier: workspace:*
version: link:../translations version: link:../translations
'@types/busboy':
specifier: 1.5.4
version: 1.5.4
ajv: ajv:
specifier: 8.14.0 specifier: 8.14.0
version: 8.14.0 version: 8.14.0
@@ -882,9 +885,6 @@ importers:
'@payloadcms/eslint-config': '@payloadcms/eslint-config':
specifier: workspace:* specifier: workspace:*
version: link:../eslint-config version: link:../eslint-config
'@types/express-fileupload':
specifier: 1.4.1
version: 1.4.1
'@types/json-schema': '@types/json-schema':
specifier: 7.0.15 specifier: 7.0.15
version: 7.0.15 version: 7.0.15
@@ -4375,9 +4375,6 @@ packages:
'@types/estree@1.0.5': '@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
'@types/express-fileupload@1.4.1':
resolution: {integrity: sha512-sbl865h1Sser6SF+efpw2F/+roGISj+PRIbMcGXbtzgJQCBAeeBmoSo7sPge/mBa22ymCHfFPtHFsag/wUxwfg==}
'@types/express-serve-static-core@4.19.5': '@types/express-serve-static-core@4.19.5':
resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==} resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==}
@@ -13030,11 +13027,6 @@ snapshots:
'@types/estree@1.0.5': {} '@types/estree@1.0.5': {}
'@types/express-fileupload@1.4.1':
dependencies:
'@types/busboy': 1.5.4
'@types/express': 4.17.21
'@types/express-serve-static-core@4.19.5': '@types/express-serve-static-core@4.19.5':
dependencies: dependencies:
'@types/node': 20.12.5 '@types/node': 20.12.5