From 99ae72b0982ce1f921ea3783d0e70e659d803a42 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 31 Jan 2024 15:33:58 +0100 Subject: [PATCH 1/3] meta: validate `defaultOptions` for stricter option types --- .../@uppy/companion-client/src/Provider.ts | 3 +- packages/@uppy/core/src/BasePlugin.ts | 13 +++++++-- .../@uppy/image-editor/src/ImageEditor.tsx | 29 ++++++++++++------- packages/@uppy/xhr-upload/src/index.test.ts | 2 ++ packages/@uppy/xhr-upload/src/index.ts | 7 +++-- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/@uppy/companion-client/src/Provider.ts b/packages/@uppy/companion-client/src/Provider.ts index ee16399a8c..e1e504b0fe 100644 --- a/packages/@uppy/companion-client/src/Provider.ts +++ b/packages/@uppy/companion-client/src/Provider.ts @@ -1,5 +1,6 @@ import type { Uppy, BasePlugin } from '@uppy/core' import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile' +import type { PluginOpts } from '@uppy/core/lib/BasePlugin.ts' import RequestClient, { authErrorStatusCode, type RequestOptions, @@ -7,7 +8,7 @@ import RequestClient, { import * as tokenStorage from './tokenStorage.ts' // TODO: remove deprecated options in next major release -export type Opts = { +export interface Opts extends PluginOpts { /** @deprecated */ serverUrl?: string /** @deprecated */ diff --git a/packages/@uppy/core/src/BasePlugin.ts b/packages/@uppy/core/src/BasePlugin.ts index e763a39a92..b77de1e459 100644 --- a/packages/@uppy/core/src/BasePlugin.ts +++ b/packages/@uppy/core/src/BasePlugin.ts @@ -21,15 +21,22 @@ import type { State, Uppy } from './Uppy' export type PluginOpts = { locale?: Locale id?: string - [key: string]: unknown } +export type OnlyOptionals = Pick< + T, + { + // eslint-disable-next-line @typescript-eslint/ban-types + [K in keyof T]-?: {} extends Pick ? K : never + }[keyof T] +> + /** * DefinePluginOpts marks all of the passed AlwaysDefinedKeys as “required” or “always defined”. */ export type DefinePluginOpts< - Opts extends PluginOpts, - AlwaysDefinedKeys extends string, + Opts, + AlwaysDefinedKeys extends keyof OnlyOptionals, > = Opts & Required> export default class BasePlugin< diff --git a/packages/@uppy/image-editor/src/ImageEditor.tsx b/packages/@uppy/image-editor/src/ImageEditor.tsx index 905375b371..55e1f3d005 100644 --- a/packages/@uppy/image-editor/src/ImageEditor.tsx +++ b/packages/@uppy/image-editor/src/ImageEditor.tsx @@ -37,7 +37,7 @@ declare module '@uppy/core' { } export interface Opts extends UIPluginOptions { - target: string | HTMLElement + target?: string | HTMLElement quality?: number cropperOptions?: Cropper.Options & { croppedCanvasOptions?: Cropper.GetCroppedCanvasOptions @@ -60,7 +60,7 @@ type PluginState = { } const defaultCropperOptions = { - viewMode: 0, + viewMode: 0 as const, background: false, autoCropArea: 1, responsive: true, @@ -68,7 +68,7 @@ const defaultCropperOptions = { minCropBoxHeight: 70, croppedCanvasOptions: {}, initialAspectRatio: 0, -} satisfies Opts['cropperOptions'] +} satisfies Partial const defaultActions = { revert: true, @@ -80,7 +80,7 @@ const defaultActions = { cropSquare: true, cropWidescreen: true, cropWidescreenVertical: true, -} satisfies Opts['actions'] +} satisfies Partial const defaultOptions = { target: 'body', @@ -89,12 +89,21 @@ const defaultOptions = { quality: 0.8, actions: defaultActions, cropperOptions: defaultCropperOptions, -} satisfies Opts - -export type ImageEditorOpts = DefinePluginOpts< - Opts, - keyof typeof defaultOptions -> +} satisfies Partial + +export type ImageEditorOpts = Omit< + DefinePluginOpts, + 'actions' | 'cropperOptions' +> & { + actions: DefinePluginOpts< + NonNullable, + keyof typeof defaultActions + > + cropperOptions: DefinePluginOpts< + NonNullable, + keyof typeof defaultCropperOptions + > +} export default class ImageEditor< M extends Meta, diff --git a/packages/@uppy/xhr-upload/src/index.test.ts b/packages/@uppy/xhr-upload/src/index.test.ts index 68b1ea2b03..a9c0339126 100644 --- a/packages/@uppy/xhr-upload/src/index.test.ts +++ b/packages/@uppy/xhr-upload/src/index.test.ts @@ -26,6 +26,7 @@ describe('XHRUpload', () => { core.use(XHRUpload, { id: 'XHRUpload', endpoint: 'https://fake-endpoint.uppy.io', + // @ts-expect-error that option does not exist some: 'option', getResponseData, }) @@ -65,6 +66,7 @@ describe('XHRUpload', () => { core.use(XHRUpload, { id: 'XHRUpload', endpoint: 'https://fake-endpoint.uppy.io', + // @ts-expect-error that option doesn't exist some: 'option', validateStatus, getResponseError(responseText) { diff --git a/packages/@uppy/xhr-upload/src/index.ts b/packages/@uppy/xhr-upload/src/index.ts index d465a2f47c..35eb664173 100644 --- a/packages/@uppy/xhr-upload/src/index.ts +++ b/packages/@uppy/xhr-upload/src/index.ts @@ -105,7 +105,6 @@ function setTypeInBlob(file: UppyFile) { } const defaultOptions = { - endpoint: '', formData: true, fieldName: 'file', method: 'post', @@ -132,7 +131,7 @@ const defaultOptions = { validateStatus(status) { return status >= 200 && status < 300 }, -} satisfies XhrUploadOpts +} satisfies Partial> type Opts = DefinePluginOpts< XhrUploadOpts, @@ -169,6 +168,8 @@ export default class XHRUpload< // Simultaneous upload limiting is shared across all uploads with this plugin. if (internalRateLimitedQueue in this.opts) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore untyped internal this.requests = this.opts[internalRateLimitedQueue] } else { this.requests = new RateLimitedQueue(this.opts.limit) @@ -608,6 +609,8 @@ export default class XHRUpload< // No limit configured by the user, and no RateLimitedQueue passed in by a "parent" plugin // (basically just AwsS3) using the internal symbol + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore untyped internal if (this.opts.limit === 0 && !this.opts[internalRateLimitedQueue]) { this.uppy.log( '[XHRUpload] When uploading multiple files at once, consider setting the `limit` option (to `10` for example), to limit the number of concurrent uploads, which helps prevent memory and network issues: https://uppy.io/docs/xhr-upload/#limit-0', From 996a79a864de9fad004a31c80e50c9844b28ec87 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 31 Jan 2024 16:50:14 +0100 Subject: [PATCH 2/3] do not export the internal type --- packages/@uppy/image-editor/src/ImageEditor.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@uppy/image-editor/src/ImageEditor.tsx b/packages/@uppy/image-editor/src/ImageEditor.tsx index 55e1f3d005..50c8d453c7 100644 --- a/packages/@uppy/image-editor/src/ImageEditor.tsx +++ b/packages/@uppy/image-editor/src/ImageEditor.tsx @@ -91,7 +91,7 @@ const defaultOptions = { cropperOptions: defaultCropperOptions, } satisfies Partial -export type ImageEditorOpts = Omit< +type InternalImageEditorOpts = Omit< DefinePluginOpts, 'actions' | 'cropperOptions' > & { @@ -108,7 +108,7 @@ export type ImageEditorOpts = Omit< export default class ImageEditor< M extends Meta, B extends Body, -> extends UIPlugin> { +> extends UIPlugin> { static VERSION = packageJson.version cropper: Cropper From 97a772c770641d0b3daf8f7f550c5bef27c7ce61 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 31 Jan 2024 17:15:27 +0100 Subject: [PATCH 3/3] fix --- packages/@uppy/image-editor/src/Editor.tsx | 4 ++-- packages/@uppy/image-editor/src/ImageEditor.tsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/@uppy/image-editor/src/Editor.tsx b/packages/@uppy/image-editor/src/Editor.tsx index 9270469def..f8e5bea967 100644 --- a/packages/@uppy/image-editor/src/Editor.tsx +++ b/packages/@uppy/image-editor/src/Editor.tsx @@ -8,12 +8,12 @@ import getCanvasDataThatFitsPerfectlyIntoContainer from './utils/getCanvasDataTh import getScaleFactorThatRemovesDarkCorners from './utils/getScaleFactorThatRemovesDarkCorners.ts' import limitCropboxMovementOnMove from './utils/limitCropboxMovementOnMove.ts' import limitCropboxMovementOnResize from './utils/limitCropboxMovementOnResize.ts' -import type { ImageEditorOpts } from './ImageEditor.tsx' +import type ImageEditor from './ImageEditor.tsx' type Props = { currentImage: UppyFile storeCropperInstance: (cropper: Cropper) => void - opts: ImageEditorOpts + opts: ImageEditor['opts'] i18n: I18n // eslint-disable-next-line react/no-unused-prop-types save: () => void // eslint confused diff --git a/packages/@uppy/image-editor/src/ImageEditor.tsx b/packages/@uppy/image-editor/src/ImageEditor.tsx index 50c8d453c7..da9d1a8493 100644 --- a/packages/@uppy/image-editor/src/ImageEditor.tsx +++ b/packages/@uppy/image-editor/src/ImageEditor.tsx @@ -36,7 +36,7 @@ declare module '@uppy/core' { } } -export interface Opts extends UIPluginOptions { +interface Opts extends UIPluginOptions { target?: string | HTMLElement quality?: number cropperOptions?: Cropper.Options & { @@ -54,6 +54,7 @@ export interface Opts extends UIPluginOptions { cropWidescreenVertical?: boolean } } +export type { Opts as ImageEditorOptions } type PluginState = { currentImage: UppyFile | null