Преглед на файлове

@uppy/url: migrate to TS (#4980)

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
Merlijn Vos преди 1 година
родител
ревизия
21e44763bf

+ 5 - 32
packages/@uppy/provider-views/src/View.ts

@@ -2,38 +2,12 @@ import type {
   UnknownProviderPlugin,
   UnknownSearchProviderPlugin,
 } from '@uppy/core/lib/Uppy'
-import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
+import type { Body, Meta, TagFile } from '@uppy/utils/lib/UppyFile'
 import type { CompanionFile } from '@uppy/utils/lib/CompanionFile'
 import getFileType from '@uppy/utils/lib/getFileType'
 import isPreviewSupported from '@uppy/utils/lib/isPreviewSupported'
 import remoteFileObjToLocal from '@uppy/utils/lib/remoteFileObjToLocal'
 
-type TagFile<M extends Meta> = {
-  id: string
-  source: string
-  data: Blob
-  name: string
-  type: string
-  isRemote: boolean
-  preview?: string
-  meta: {
-    authorName?: string
-    authorUrl?: string
-    relativePath?: string | null
-    absolutePath?: string
-  } & M
-  remote: {
-    companionUrl: string
-    url: string
-    body: {
-      fileId: string
-    }
-    providerName: string
-    provider: string
-    requestClientId: string
-  }
-}
-
 type PluginType = 'Provider' | 'SearchProvider'
 
 // Conditional type for selecting the plugin
@@ -155,7 +129,6 @@ export default class View<
       name: file.name || file.id,
       type: file.mimeType,
       isRemote: true,
-      // @ts-expect-error meta is filled conditionally below
       data: file,
       // @ts-expect-error meta is filled conditionally below
       meta: {},
@@ -184,17 +157,17 @@ export default class View<
 
     if (file.author) {
       if (file.author.name != null)
-        tagFile.meta.authorName = String(file.author.name)
-      if (file.author.url) tagFile.meta.authorUrl = file.author.url
+        tagFile.meta!.authorName = String(file.author.name)
+      if (file.author.url) tagFile.meta!.authorUrl = file.author.url
     }
 
     // add relativePath similar to non-remote files: https://github.com/transloadit/uppy/pull/4486#issuecomment-1579203717
     if (file.relDirPath != null)
-      tagFile.meta.relativePath =
+      tagFile.meta!.relativePath =
         file.relDirPath ? `${file.relDirPath}/${tagFile.name}` : null
     // and absolutePath (with leading slash) https://github.com/transloadit/uppy/pull/4537#issuecomment-1614236655
     if (file.absDirPath != null)
-      tagFile.meta.absolutePath =
+      tagFile.meta!.absolutePath =
         file.absDirPath ?
           `/${file.absDirPath}/${tagFile.name}`
         : `/${tagFile.name}`

+ 1 - 0
packages/@uppy/provider-views/src/index.ts

@@ -2,4 +2,5 @@ export {
   default as ProviderViews,
   defaultPickerIcon,
 } from './ProviderView/index.ts'
+
 export { default as SearchProviderViews } from './SearchProviderView/index.ts'

+ 1 - 0
packages/@uppy/url/.npmignore

@@ -0,0 +1 @@
+tsconfig.*

+ 0 - 195
packages/@uppy/url/src/Url.jsx

@@ -1,195 +0,0 @@
-import { h } from 'preact'
-import { UIPlugin } from '@uppy/core'
-import { RequestClient } from '@uppy/companion-client'
-import toArray from '@uppy/utils/lib/toArray'
-import UrlUI from './UrlUI.jsx'
-import forEachDroppedOrPastedUrl from './utils/forEachDroppedOrPastedUrl.js'
-
-import packageJson from '../package.json'
-import locale from './locale.js'
-
-function UrlIcon () {
-  return (
-    <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
-      <path d="M23.637 15.312l-2.474 2.464a3.582 3.582 0 01-.577.491c-.907.657-1.897.986-2.968.986a4.925 4.925 0 01-3.959-1.971c-.248-.329-.164-.902.165-1.149.33-.247.907-.164 1.155.164 1.072 1.478 3.133 1.724 4.618.656a.642.642 0 00.33-.328l2.473-2.463c1.238-1.313 1.238-3.366-.082-4.597a3.348 3.348 0 00-4.618 0l-1.402 1.395a.799.799 0 01-1.154 0 .79.79 0 010-1.15l1.402-1.394a4.843 4.843 0 016.843 0c2.062 1.805 2.144 5.007.248 6.896zm-8.081 5.664l-1.402 1.395a3.348 3.348 0 01-4.618 0c-1.319-1.23-1.319-3.365-.082-4.596l2.475-2.464.328-.328c.743-.492 1.567-.739 2.475-.657.906.165 1.648.574 2.143 1.314.248.329.825.411 1.155.165.33-.248.412-.822.165-1.15-.825-1.068-1.98-1.724-3.216-1.888-1.238-.247-2.556.082-3.628.902l-.495.493-2.474 2.464c-1.897 1.969-1.814 5.09.083 6.977.99.904 2.226 1.396 3.463 1.396s2.473-.492 3.463-1.395l1.402-1.396a.79.79 0 000-1.15c-.33-.328-.908-.41-1.237-.082z" fill="#FF753E" fill-rule="nonzero" />
-    </svg>
-  )
-}
-
-function addProtocolToURL (url) {
-  const protocolRegex = /^[a-z0-9]+:\/\//
-  const defaultProtocol = 'http://'
-  if (protocolRegex.test(url)) {
-    return url
-  }
-
-  return defaultProtocol + url
-}
-
-function canHandleRootDrop (e) {
-  const items = toArray(e.dataTransfer.items)
-  const urls = items.filter((item) => item.kind === 'string'
-    && item.type === 'text/uri-list')
-  return urls.length > 0
-}
-
-function checkIfCorrectURL (url) {
-  if (!url) return false
-
-  const protocol = url.match(/^([a-z0-9]+):\/\//)[1]
-  if (protocol !== 'http' && protocol !== 'https') {
-    return false
-  }
-
-  return true
-}
-
-function getFileNameFromUrl (url) {
-  const { pathname } = new URL(url)
-  return pathname.substring(pathname.lastIndexOf('/') + 1)
-}
-
-/**
- * Url
- *
- */
-export default class Url extends UIPlugin {
-  static VERSION = packageJson.version
-
-  static requestClientId = Url.name
-
-  constructor (uppy, opts) {
-    super(uppy, opts)
-    this.id = this.opts.id || 'Url'
-    this.title = this.opts.title || 'Link'
-    this.type = 'acquirer'
-    this.icon = () => <UrlIcon />
-
-    // Set default options and locale
-    this.defaultLocale = locale
-
-    const defaultOptions = {}
-
-    this.opts = { ...defaultOptions, ...opts }
-
-    this.i18nInit()
-
-    this.hostname = this.opts.companionUrl
-
-    if (!this.hostname) {
-      throw new Error('Companion hostname is required, please consult https://uppy.io/docs/companion')
-    }
-
-    // Bind all event handlers for referencability
-    this.getMeta = this.getMeta.bind(this)
-    this.addFile = this.addFile.bind(this)
-    this.handleRootDrop = this.handleRootDrop.bind(this)
-    this.handleRootPaste = this.handleRootPaste.bind(this)
-
-    this.client = new RequestClient(uppy, {
-      companionUrl: this.opts.companionUrl,
-      companionHeaders: this.opts.companionHeaders,
-      companionCookiesRule: this.opts.companionCookiesRule,
-    })
-
-    this.uppy.registerRequestClient(Url.requestClientId, this.client)
-  }
-
-  getMeta (url) {
-    return this.client.post('url/meta', { url })
-      .then((res) => {
-        if (res.error) {
-          this.uppy.log('[URL] Error:')
-          this.uppy.log(res.error)
-          throw new Error('Failed to fetch the file')
-        }
-        return res
-      })
-  }
-
-  async addFile (protocollessUrl, optionalMeta = undefined) {
-    const url = addProtocolToURL(protocollessUrl)
-    if (!checkIfCorrectURL(url)) {
-      this.uppy.log(`[URL] Incorrect URL entered: ${url}`)
-      this.uppy.info(this.i18n('enterCorrectUrl'), 'error', 4000)
-      return undefined
-    }
-
-    try {
-      const meta = await this.getMeta(url)
-
-      const tagFile = {
-        meta: optionalMeta,
-        source: this.id,
-        name: meta.name || getFileNameFromUrl(url),
-        type: meta.type,
-        data: {
-          size: meta.size,
-        },
-        isRemote: true,
-        body: {
-          url,
-        },
-        remote: {
-          companionUrl: this.opts.companionUrl,
-          url: `${this.hostname}/url/get`,
-          body: {
-            fileId: url,
-            url,
-          },
-          requestClientId: Url.requestClientId,
-        },
-      }
-
-      this.uppy.log('[Url] Adding remote file')
-      try {
-        return this.uppy.addFile(tagFile)
-      } catch (err) {
-        if (!err.isRestriction) {
-          this.uppy.log(err)
-        }
-        return err
-      }
-    } catch (err) {
-      this.uppy.log(err)
-      this.uppy.info({
-        message: this.i18n('failedToFetch'),
-        details: err,
-      }, 'error', 4000)
-      return err
-    }
-  }
-
-  handleRootDrop (e) {
-    forEachDroppedOrPastedUrl(e.dataTransfer, 'drop', (url) => {
-      this.uppy.log(`[URL] Adding file from dropped url: ${url}`)
-      this.addFile(url)
-    })
-  }
-
-  handleRootPaste (e) {
-    forEachDroppedOrPastedUrl(e.clipboardData, 'paste', (url) => {
-      this.uppy.log(`[URL] Adding file from pasted url: ${url}`)
-      this.addFile(url)
-    })
-  }
-
-  render () {
-    return <UrlUI i18n={this.i18n} addFile={this.addFile} />
-  }
-
-  install () {
-    const { target } = this.opts
-    if (target) {
-      this.mount(target, this)
-    }
-  }
-
-  uninstall () {
-    this.unmount()
-  }
-}
-
-// This is defined outside of the class body because it's not using `this`, but
-// we still want it available on the prototype so the Dashboard can access it.
-Url.prototype.canHandleRootDrop = canHandleRootDrop

+ 227 - 0
packages/@uppy/url/src/Url.tsx

@@ -0,0 +1,227 @@
+import { h, type ComponentChild } from 'preact'
+import { UIPlugin, Uppy } from '@uppy/core'
+import {
+  RequestClient,
+  type CompanionPluginOptions,
+} from '@uppy/companion-client'
+import toArray from '@uppy/utils/lib/toArray'
+import type { TagFile, Meta, Body } from '@uppy/utils/lib/UppyFile'
+import UrlUI from './UrlUI.tsx'
+import forEachDroppedOrPastedUrl from './utils/forEachDroppedOrPastedUrl.ts'
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore We don't want TS to generate types for the package.json
+import packageJson from '../package.json'
+import locale from './locale.ts'
+
+function UrlIcon() {
+  return (
+    <svg
+      aria-hidden="true"
+      focusable="false"
+      width="32"
+      height="32"
+      viewBox="0 0 32 32"
+    >
+      <path
+        d="M23.637 15.312l-2.474 2.464a3.582 3.582 0 01-.577.491c-.907.657-1.897.986-2.968.986a4.925 4.925 0 01-3.959-1.971c-.248-.329-.164-.902.165-1.149.33-.247.907-.164 1.155.164 1.072 1.478 3.133 1.724 4.618.656a.642.642 0 00.33-.328l2.473-2.463c1.238-1.313 1.238-3.366-.082-4.597a3.348 3.348 0 00-4.618 0l-1.402 1.395a.799.799 0 01-1.154 0 .79.79 0 010-1.15l1.402-1.394a4.843 4.843 0 016.843 0c2.062 1.805 2.144 5.007.248 6.896zm-8.081 5.664l-1.402 1.395a3.348 3.348 0 01-4.618 0c-1.319-1.23-1.319-3.365-.082-4.596l2.475-2.464.328-.328c.743-.492 1.567-.739 2.475-.657.906.165 1.648.574 2.143 1.314.248.329.825.411 1.155.165.33-.248.412-.822.165-1.15-.825-1.068-1.98-1.724-3.216-1.888-1.238-.247-2.556.082-3.628.902l-.495.493-2.474 2.464c-1.897 1.969-1.814 5.09.083 6.977.99.904 2.226 1.396 3.463 1.396s2.473-.492 3.463-1.395l1.402-1.396a.79.79 0 000-1.15c-.33-.328-.908-.41-1.237-.082z"
+        fill="#FF753E"
+        fill-rule="nonzero"
+      />
+    </svg>
+  )
+}
+
+function addProtocolToURL(url: string) {
+  const protocolRegex = /^[a-z0-9]+:\/\//
+  const defaultProtocol = 'http://'
+  if (protocolRegex.test(url)) {
+    return url
+  }
+
+  return defaultProtocol + url
+}
+
+function canHandleRootDrop(e: DragEvent) {
+  const items = toArray(e.dataTransfer!.items)
+  const urls = items.filter(
+    (item) => item.kind === 'string' && item.type === 'text/uri-list',
+  )
+  return urls.length > 0
+}
+
+function checkIfCorrectURL(url?: string) {
+  return url?.startsWith('http://') || url?.startsWith('https://')
+}
+
+function getFileNameFromUrl(url: string) {
+  const { pathname } = new URL(url)
+  return pathname.substring(pathname.lastIndexOf('/') + 1)
+}
+
+/*
+ * Response from the /url/meta Companion endpoint.
+ * Has to be kept in sync with `getURLMeta` in `companion/src/server/helpers/request.js`.
+ */
+type MetaResponse = {
+  name: string
+  type: string
+  size: number | null
+  statusCode: number
+}
+
+export type UrlOptions = CompanionPluginOptions
+
+export default class Url<M extends Meta, B extends Body> extends UIPlugin<
+  UrlOptions,
+  M,
+  B
+> {
+  static VERSION = packageJson.version
+
+  static requestClientId = Url.name
+
+  icon: () => JSX.Element
+
+  hostname: string
+
+  client: RequestClient<M, B>
+
+  canHandleRootDrop: typeof canHandleRootDrop
+
+  constructor(uppy: Uppy<M, B>, opts: UrlOptions) {
+    super(uppy, opts)
+    this.id = this.opts.id || 'Url'
+    this.title = this.opts.title || 'Link'
+    this.type = 'acquirer'
+    this.icon = () => <UrlIcon />
+
+    // Set default options and locale
+    this.defaultLocale = locale
+
+    this.i18nInit()
+
+    this.hostname = this.opts.companionUrl
+
+    if (!this.hostname) {
+      throw new Error(
+        'Companion hostname is required, please consult https://uppy.io/docs/companion',
+      )
+    }
+
+    this.client = new RequestClient(uppy, {
+      pluginId: this.id,
+      provider: 'url',
+      companionUrl: this.opts.companionUrl,
+      companionHeaders: this.opts.companionHeaders,
+      companionCookiesRule: this.opts.companionCookiesRule,
+    })
+
+    this.uppy.registerRequestClient(Url.requestClientId, this.client)
+  }
+
+  private getMeta = (url: string): Promise<MetaResponse> => {
+    return this.client.post<MetaResponse>('url/meta', { url }).then((res) => {
+      // TODO: remove this handler in the next major
+      if ((res as any).error) {
+        this.uppy.log('[URL] Error:')
+        this.uppy.log((res as any).error)
+        throw new Error('Failed to fetch the file')
+      }
+      return res
+    })
+  }
+
+  private addFile = async (
+    protocollessUrl: string,
+    optionalMeta?: M,
+  ): Promise<string | undefined> => {
+    const url = addProtocolToURL(protocollessUrl)
+    if (!checkIfCorrectURL(url)) {
+      this.uppy.log(`[URL] Incorrect URL entered: ${url}`)
+      this.uppy.info(this.i18n('enterCorrectUrl'), 'error', 4000)
+      return undefined
+    }
+
+    try {
+      const meta = await this.getMeta(url)
+
+      const tagFile: TagFile<M> = {
+        meta: optionalMeta,
+        source: this.id,
+        name: meta.name || getFileNameFromUrl(url),
+        type: meta.type,
+        data: {
+          size: meta.size,
+        },
+        isRemote: true,
+        body: {
+          url,
+        },
+        remote: {
+          companionUrl: this.opts.companionUrl,
+          url: `${this.hostname}/url/get`,
+          body: {
+            fileId: url,
+            url,
+          },
+          requestClientId: Url.requestClientId,
+        },
+      }
+
+      this.uppy.log('[Url] Adding remote file')
+      try {
+        return this.uppy.addFile(tagFile)
+      } catch (err) {
+        if (!err.isRestriction) {
+          this.uppy.log(err)
+        }
+        return err
+      }
+    } catch (err) {
+      this.uppy.log(err)
+      this.uppy.info(
+        {
+          message: this.i18n('failedToFetch'),
+          details: err,
+        },
+        'error',
+        4000,
+      )
+      return err
+    }
+  }
+
+  private handleRootDrop = (e: DragEvent) => {
+    forEachDroppedOrPastedUrl(e.dataTransfer!, 'drop', (url) => {
+      this.uppy.log(`[URL] Adding file from dropped url: ${url}`)
+      this.addFile(url)
+    })
+  }
+
+  private handleRootPaste = (e: ClipboardEvent) => {
+    forEachDroppedOrPastedUrl(e.clipboardData!, 'paste', (url) => {
+      this.uppy.log(`[URL] Adding file from pasted url: ${url}`)
+      this.addFile(url)
+    })
+  }
+
+  render(): ComponentChild {
+    return <UrlUI i18n={this.i18n} addFile={this.addFile} />
+  }
+
+  install(): void {
+    const { target } = this.opts
+    if (target) {
+      this.mount(target, this)
+    }
+  }
+
+  uninstall(): void {
+    this.unmount()
+  }
+}
+
+// This is defined outside of the class body because it's not using `this`, but
+// we still want it available on the prototype so the Dashboard can access it.
+Url.prototype.canHandleRootDrop = canHandleRootDrop

+ 18 - 8
packages/@uppy/url/src/UrlUI.jsx → packages/@uppy/url/src/UrlUI.tsx

@@ -1,33 +1,41 @@
-import { h, Component } from 'preact'
+import { h, Component, type ComponentChild } from 'preact'
 import { nanoid } from 'nanoid/non-secure'
+import type { I18n } from '@uppy/utils/lib/Translator'
 
-class UrlUI extends Component {
+type UrlUIProps = {
+  i18n: I18n
+  addFile: (url: string) => void
+}
+
+class UrlUI extends Component<UrlUIProps> {
   form = document.createElement('form')
 
-  constructor (props) {
+  input: HTMLInputElement
+
+  constructor(props: UrlUIProps) {
     super(props)
     this.form.id = nanoid()
   }
 
-  componentDidMount () {
+  componentDidMount(): void {
     this.input.value = ''
     this.form.addEventListener('submit', this.#handleSubmit)
     document.body.appendChild(this.form)
   }
 
-  componentWillUnmount () {
+  componentWillUnmount(): void {
     this.form.removeEventListener('submit', this.#handleSubmit)
     document.body.removeChild(this.form)
   }
 
-  #handleSubmit = (ev) => {
+  #handleSubmit = (ev: SubmitEvent) => {
     ev.preventDefault()
     const { addFile } = this.props
     const preparedValue = this.input.value.trim()
     addFile(preparedValue)
   }
 
-  render () {
+  render(): ComponentChild {
     const { i18n } = this.props
     return (
       <div className="uppy-Url">
@@ -36,7 +44,9 @@ class UrlUI extends Component {
           type="text"
           aria-label={i18n('enterUrlToImport')}
           placeholder={i18n('enterUrlToImport')}
-          ref={(input) => { this.input = input }}
+          ref={(input) => {
+            this.input = input!
+          }}
           data-uppy-super-focusable
           form={this.form.id}
         />

+ 0 - 1
packages/@uppy/url/src/index.js

@@ -1 +0,0 @@
-export { default } from './Url.jsx'

+ 1 - 0
packages/@uppy/url/src/index.ts

@@ -0,0 +1 @@
+export { default } from './Url.tsx'

+ 4 - 2
packages/@uppy/url/src/locale.js → packages/@uppy/url/src/locale.ts

@@ -5,8 +5,10 @@ export default {
     // Placeholder text for the URL input.
     enterUrlToImport: 'Enter URL to import a file',
     // Error message shown if Companion could not load a URL.
-    failedToFetch: 'Companion failed to fetch this URL, please make sure it’s correct',
+    failedToFetch:
+      'Companion failed to fetch this URL, please make sure it’s correct',
     // Error message shown if the input does not look like a URL.
-    enterCorrectUrl: 'Incorrect URL: Please make sure you are entering a direct link to a file',
+    enterCorrectUrl:
+      'Incorrect URL: Please make sure you are entering a direct link to a file',
   },
 }

+ 14 - 10
packages/@uppy/url/src/utils/forEachDroppedOrPastedUrl.js → packages/@uppy/url/src/utils/forEachDroppedOrPastedUrl.ts

@@ -53,12 +53,12 @@ import toArray from '@uppy/utils/lib/toArray'
 
 /**
  * Finds all links dropped/pasted from one browser window to another.
- *
- * @param {object} dataTransfer - DataTransfer instance, e.g. e.clipboardData, or e.dataTransfer
- * @param {string} isDropOrPaste - either 'drop' or 'paste'
- * @param {Function} callback - (urlString) => {}
  */
-export default function forEachDroppedOrPastedUrl (dataTransfer, isDropOrPaste, callback) {
+export default function forEachDroppedOrPastedUrl(
+  dataTransfer: DataTransfer,
+  isDropOrPaste: 'drop' | 'paste',
+  callback: (url: string) => void,
+): void {
   const items = toArray(dataTransfer.items)
 
   let urlItems
@@ -69,18 +69,22 @@ export default function forEachDroppedOrPastedUrl (dataTransfer, isDropOrPaste,
       if (atLeastOneFileIsDragged) {
         return
       }
-      urlItems = items.filter((item) => item.kind === 'string'
-          && item.type === 'text/plain')
+      urlItems = items.filter(
+        (item) => item.kind === 'string' && item.type === 'text/plain',
+      )
 
       break
     }
     case 'drop': {
-      urlItems = items.filter((item) => item.kind === 'string'
-        && item.type === 'text/uri-list')
+      urlItems = items.filter(
+        (item) => item.kind === 'string' && item.type === 'text/uri-list',
+      )
       break
     }
     default: {
-      throw new Error(`isDropOrPaste must be either 'drop' or 'paste', but it's ${isDropOrPaste}`)
+      throw new Error(
+        `isDropOrPaste must be either 'drop' or 'paste', but it's ${isDropOrPaste}`,
+      )
     }
   }
 

+ 30 - 0
packages/@uppy/url/tsconfig.build.json

@@ -0,0 +1,30 @@
+{
+  "extends": "../../../tsconfig.shared",
+  "compilerOptions": {
+    "noImplicitAny": false,
+    "outDir": "./lib",
+    "paths": {
+      "@uppy/companion-client": ["../companion-client/src/index.js"],
+      "@uppy/companion-client/lib/*": ["../companion-client/src/*"],
+      "@uppy/utils/lib/*": ["../utils/src/*"],
+      "@uppy/core": ["../core/src/index.js"],
+      "@uppy/core/lib/*": ["../core/src/*"]
+    },
+    "resolveJsonModule": false,
+    "rootDir": "./src",
+    "skipLibCheck": true
+  },
+  "include": ["./src/**/*.*"],
+  "exclude": ["./src/**/*.test.ts"],
+  "references": [
+    {
+      "path": "../companion-client/tsconfig.build.json"
+    },
+    {
+      "path": "../utils/tsconfig.build.json"
+    },
+    {
+      "path": "../core/tsconfig.build.json"
+    }
+  ]
+}

+ 26 - 0
packages/@uppy/url/tsconfig.json

@@ -0,0 +1,26 @@
+{
+  "extends": "../../../tsconfig.shared",
+  "compilerOptions": {
+    "emitDeclarationOnly": false,
+    "noEmit": true,
+    "paths": {
+      "@uppy/companion-client": ["../companion-client/src/index.js"],
+      "@uppy/companion-client/lib/*": ["../companion-client/src/*"],
+      "@uppy/utils/lib/*": ["../utils/src/*"],
+      "@uppy/core": ["../core/src/index.js"],
+      "@uppy/core/lib/*": ["../core/src/*"],
+    },
+  },
+  "include": ["./package.json", "./src/**/*.*"],
+  "references": [
+    {
+      "path": "../companion-client/tsconfig.build.json",
+    },
+    {
+      "path": "../utils/tsconfig.build.json",
+    },
+    {
+      "path": "../core/tsconfig.build.json",
+    },
+  ],
+}

+ 44 - 3
packages/@uppy/utils/src/UppyFile.ts

@@ -42,13 +42,54 @@ export interface UppyFile<M extends Meta, B extends Body> {
   }
 }
 
-// The user facing type for UppyFile used in uppy.addFile() and uppy.setOptions()
+/*
+ * The user facing type for UppyFile used in uppy.addFile() and uppy.setOptions()
+ */
 export type MinimalRequiredUppyFile<M extends Meta, B extends Body> = Required<
-  Pick<UppyFile<M, B>, 'name' | 'data'>
+  Pick<UppyFile<M, B>, 'name'>
 > &
   Partial<
     Omit<UppyFile<M, B>, 'name' | 'data' | 'meta'>
     // We want to omit the 'meta' from UppyFile because of internal metadata
     // (see InternalMetadata in `UppyFile.ts`), as when adding a new file
     // that is not required.
-  > & { meta?: M }
+  > & { meta?: M; data: { size: number | null } }
+
+/*
+ * We are not entirely sure what a "tag file" is.
+ * It is used as an intermidiate type between `CompanionFile` and `UppyFile`
+ * in `@uppy/provider-views` and `@uppy/url`.
+ * TODO: remove this in favor of UppyFile
+ */
+export type TagFile<M extends Meta> = {
+  id?: string
+  source: string
+  name: string
+  type: string
+  isRemote: boolean
+  preview?: string
+  data: {
+    size: number | null
+  }
+  body?: {
+    url?: string
+    fileId?: string
+  }
+  meta?: {
+    authorName?: string
+    authorUrl?: string
+    relativePath?: string | null
+    absolutePath?: string
+  } & M
+  remote: {
+    companionUrl: string
+    url: string
+    body: {
+      fileId: string
+      url?: string
+    }
+    providerName?: string
+    provider?: string
+    requestClientId: string
+  }
+}