From 9cbff02bb9ebb57018f1ed63da78e218052aa108 Mon Sep 17 00:00:00 2001 From: inadeqtfuturs Date: Thu, 7 Dec 2023 17:10:08 -0600 Subject: [PATCH 01/14] feat(vercel): init vercel blob work --- dev/package.json | 1 + dev/src/payload.config.ts | 13 + package.json | 5 + src/adapters/vercel/generateURL.ts | 19 ++ src/adapters/vercel/handleDelete.ts | 24 ++ src/adapters/vercel/handleUpload.ts | 44 +++ src/adapters/vercel/index.ts | 29 ++ src/adapters/vercel/mock.js | 1 + src/adapters/vercel/staticHandler.ts | 51 +++ src/adapters/vercel/webpack.ts | 21 ++ yarn.lock | 452 ++++++++++++++++++++++++++- 11 files changed, 656 insertions(+), 4 deletions(-) create mode 100644 src/adapters/vercel/generateURL.ts create mode 100644 src/adapters/vercel/handleDelete.ts create mode 100644 src/adapters/vercel/handleUpload.ts create mode 100644 src/adapters/vercel/index.ts create mode 100644 src/adapters/vercel/mock.js create mode 100644 src/adapters/vercel/staticHandler.ts create mode 100644 src/adapters/vercel/webpack.ts diff --git a/dev/package.json b/dev/package.json index a9f5cdf..e7ffbd9 100644 --- a/dev/package.json +++ b/dev/package.json @@ -8,6 +8,7 @@ "dev:azure": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER=azure nodemon", "dev:s3": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER=s3 nodemon", "dev:gcs": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER=gcs nodemon", + "dev:vercel": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER=vercel nodemon", "build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build", "build:server": "tsc", "build": "yarn build:payload && yarn build:server", diff --git a/dev/src/payload.config.ts b/dev/src/payload.config.ts index 3381130..bc3e032 100644 --- a/dev/src/payload.config.ts +++ b/dev/src/payload.config.ts @@ -5,6 +5,7 @@ import { cloudStorage } from '../../src' import { s3Adapter } from '../../src/adapters/s3' import { gcsAdapter } from '../../src/adapters/gcs' import { azureBlobStorageAdapter } from '../../src/adapters/azure' +import { vercelBlobAdapter } from '../../src/adapters/vercel' import type { Adapter } from '../../src/types' import { Media } from './collections/Media' @@ -53,6 +54,14 @@ if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'gcs') { }) } +if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'vercel') { + adapter = vercelBlobAdapter({ + token: process.env.BLOB_READ_WRITE_TOKEN, + storeId: process.env.VERCEL_STORE_ID, + bucketName: process.env.VERCEL_BUCKET_NAME, + }) +} + export default buildConfig({ serverURL: 'http://localhost:3000', collections: [Media, Users], @@ -85,6 +94,10 @@ export default buildConfig({ __dirname, '../../src/adapters/azure/mock.js', ), + [path.resolve(__dirname, '../../src/adapters/vercel/index')]: path.resolve( + __dirname, + '../../src/adapters/vercel/mock.js', + ), }, }, } diff --git a/package.json b/package.json index 7fe9a08..999d0c8 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@aws-sdk/lib-storage": "^3.267.0", "@azure/storage-blob": "^12.11.0", "@google-cloud/storage": "^6.4.1", + "@vercel/blob": "^0.15.1", "payload": "^1.7.2 || ^2.0.0" }, "peerDependenciesMeta": { @@ -31,6 +32,9 @@ }, "@google-cloud/storage": { "optional": true + }, + "@vercel/blob": { + "optional": true } }, "files": [ @@ -46,6 +50,7 @@ "@types/express": "^4.17.9", "@typescript-eslint/eslint-plugin": "5.12.1", "@typescript-eslint/parser": "5.12.1", + "@vercel/blob": "^0.15.1", "cross-env": "^7.0.3", "dotenv": "^8.2.0", "eslint": "^8.19.0", diff --git a/src/adapters/vercel/generateURL.ts b/src/adapters/vercel/generateURL.ts new file mode 100644 index 0000000..13d63c0 --- /dev/null +++ b/src/adapters/vercel/generateURL.ts @@ -0,0 +1,19 @@ +import path from 'path' + +import type { GenerateURL } from '../../types' + +interface Args { + storeId: string + bucketName: string +} + +const getGenerateURL = ({ storeId, bucketName }: Args): GenerateURL => { + return ({ filename, prefix = '' }) => { + return `https://${storeId}.public.blob.vercel-storage.com/${bucketName}/${path.posix.join( + prefix, + filename, + )}` + } +} + +export default getGenerateURL diff --git a/src/adapters/vercel/handleDelete.ts b/src/adapters/vercel/handleDelete.ts new file mode 100644 index 0000000..be77c3b --- /dev/null +++ b/src/adapters/vercel/handleDelete.ts @@ -0,0 +1,24 @@ +import { del } from '@vercel/blob' +import path from 'path' + +import type { HandleDelete } from '../../types' + +interface Args { + token: string + storeId: string + bucketName: string +} + +const getHandleDelete = ({ token, bucketName, storeId }: Args): HandleDelete => { + return async ({ filename, doc: { prefix = '' } }) => { + const fileUrl = `https://${storeId}.public.blob.vercel-storage.com/${bucketName}/${path.posix.join( + prefix, + filename, + )}` + + const deletedBlob = await del(fileUrl, { token }) + return deletedBlob + } +} + +export default getHandleDelete diff --git a/src/adapters/vercel/handleUpload.ts b/src/adapters/vercel/handleUpload.ts new file mode 100644 index 0000000..e0f67c0 --- /dev/null +++ b/src/adapters/vercel/handleUpload.ts @@ -0,0 +1,44 @@ +import { put } from '@vercel/blob' +import path from 'path' + +import type { HandleUpload } from '../../types' + +interface Args { + token: string + bucketName: string + access?: 'public' + prefix?: string + addRandomSuffix?: boolean + cacheControlMaxAge?: number +} + +const getHandleUpload = ({ + token, + bucketName, + access = 'public', + prefix = '', + addRandomSuffix = false, + cacheControlMaxAge = 31556926, +}: Args): HandleUpload => { + return async ({ data, file }) => { + const fileKey = path.posix.join(data.prefix || prefix, file.filename) + const filePath = `${bucketName}/${fileKey}` + const resp = await put(filePath, file.buffer, { + token, + contentType: file.mimeType, + access, + addRandomSuffix, + cacheControlMaxAge, + }) + + console.log('@--> data', data) + + if (addRandomSuffix) { + // handle updating data + } + + return data + } +} + +export default getHandleUpload diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts new file mode 100644 index 0000000..f5719ff --- /dev/null +++ b/src/adapters/vercel/index.ts @@ -0,0 +1,29 @@ +import type { Adapter, GeneratedAdapter } from '../../types' + +import getGenerateURL from './generateURL' +import getHandleDelete from './handleDelete' +import getHandleUpload from './handleUpload' +import getStaticHandler from './staticHandler' + +export interface Args { + token: string + storeId: string + bucketName: string +} + +export const vercelBlobAdapter = ({ token, storeId, bucketName }: Args): Adapter => { + return ({ collection }): GeneratedAdapter => { + console.log('@--> collection', collection) + return { + handleUpload: getHandleUpload({ token, bucketName }), + handleDelete: getHandleDelete({ token, bucketName, storeId }), + generateURL: getGenerateURL({ storeId, bucketName }), + staticHandler: getStaticHandler({ + token, + bucketName, + storeId, + collection, + }), + } + } +} diff --git a/src/adapters/vercel/mock.js b/src/adapters/vercel/mock.js new file mode 100644 index 0000000..4a1b002 --- /dev/null +++ b/src/adapters/vercel/mock.js @@ -0,0 +1 @@ +export const vercelBlobAdapter = () => ({}) diff --git a/src/adapters/vercel/staticHandler.ts b/src/adapters/vercel/staticHandler.ts new file mode 100644 index 0000000..42fb6ee --- /dev/null +++ b/src/adapters/vercel/staticHandler.ts @@ -0,0 +1,51 @@ +import path from 'path' +import { head } from '@vercel/blob' +import type { CollectionConfig } from 'payload/types' + +import type { StaticHandler } from '../../types' +import { getFilePrefix } from '../../utilities/getFilePrefix' + +interface Args { + token: string + storeId: string + bucketName: string + collection: CollectionConfig +} + +const getStaticHandler = ({ bucketName, storeId, collection }: Args): StaticHandler => { + return async (req, res, next) => { + try { + const prefix = await getFilePrefix({ req, collection }) + const fileUrl = `https://${storeId}.public.blob.vercel-storage.com/${bucketName}/${path.posix.join( + prefix, + req.params.filename, + )}` + const blobMetadata = await head(fileUrl) + + if (blobMetadata) { + const { contentType, size } = blobMetadata + const response = await fetch(fileUrl) + const blob = await response.blob() + + if (blob) { + res.set({ + 'Content-Type': contentType, + 'Content-Length': size, + 'Content-Disposition': 'inline', + }) + + blob.arrayBuffer().then(b => { + res.send(Buffer.from(b)) + res.end() + }) + } + } + + next() + } catch (err: unknown) { + next(err) + } + } +} + +export default getStaticHandler diff --git a/src/adapters/vercel/webpack.ts b/src/adapters/vercel/webpack.ts new file mode 100644 index 0000000..d86034c --- /dev/null +++ b/src/adapters/vercel/webpack.ts @@ -0,0 +1,21 @@ +import type { Configuration as WebpackConfig } from 'webpack' +import path from 'path' + +export const extendWebpackConfig = (existingWebpackConfig: WebpackConfig): WebpackConfig => { + const newConfig: WebpackConfig = { + ...existingWebpackConfig, + resolve: { + ...(existingWebpackConfig.resolve || {}), + fallback: { + ...(existingWebpackConfig.resolve?.fallback ? existingWebpackConfig.resolve.fallback : {}), + stream: false, + }, + alias: { + ...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}), + '@payloadcms/plugin-cloud-storage/vercel': path.resolve(__dirname, './mock.js'), + }, + }, + } + + return newConfig +} diff --git a/yarn.lock b/yarn.lock index af29254..d846bc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1067,6 +1067,14 @@ dependencies: "@babel/highlight" "^7.18.6" +"@babel/code-frame@^7.12.13": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + "@babel/helper-module-imports@^7.0.0": version "7.18.6" resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" @@ -1084,6 +1092,11 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" @@ -1093,6 +1106,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.5", "@babel/runtime@^7.19.0", "@babel/runtime@^7.20.6", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7": version "7.20.13" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" @@ -1385,6 +1407,11 @@ resolved "https://registry.npmjs.org/@faceless-ui/window-info/-/window-info-2.1.1.tgz#ed1474a60ab794295bca4c29e295b1e11a584d22" integrity sha512-gMAgda7beR4CNpBIXjgRVn97ek0LG3PAj9lxmoYdg574IEzLFZAh3eAYtTaS2XLKgb4+IHhsuBzlGmHbeOo2Aw== +"@fastify/busboy@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff" + integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA== + "@google-cloud/paginator@^3.0.7": version "3.0.7" resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz#fb6f8e24ec841f99defaebf62c75c2e744dd419b" @@ -1457,6 +1484,47 @@ resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.3.0": version "0.3.2" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" @@ -1583,6 +1651,25 @@ resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@swc/core-darwin-arm64@1.3.34": version "1.3.34" resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.34.tgz#1885fec4bd734c840897a68937a52ecab06cffbb" @@ -1751,6 +1838,34 @@ resolved "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz#30ec6d4234895230b576728ef77e70a52962f3b3" integrity sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ== +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jsdom@^20.0.0": + version "20.0.1" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" + integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" + "@types/json-schema@*", "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -1824,6 +1939,16 @@ dependencies: "@types/node" "*" +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/tough-cookie@*": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== + "@types/tunnel@^0.0.3": version "0.0.3" resolved "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9" @@ -1844,6 +1969,18 @@ "@types/node" "*" "@types/webidl-conversions" "*" +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.32" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" + integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@5.12.1": version "5.12.1" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.1.tgz#b2cd3e288f250ce8332d5035a2ff65aba3374ac4" @@ -1924,6 +2061,14 @@ "@typescript-eslint/types" "5.12.1" eslint-visitor-keys "^3.0.0" +"@vercel/blob@^0.15.1": + version "0.15.1" + resolved "https://registry.yarnpkg.com/@vercel/blob/-/blob-0.15.1.tgz#2b44ec3e59fe0c6f16d617988dc75ca5b28c323f" + integrity sha512-Wo7VU9/tM8kkv7krvxGoDeu/Li0h7PYR/TB1TEiyJZzACruA0jWqj44OGokJQCYN3ZvV5tXHpTAN6pImIBCrUw== + dependencies: + jest-environment-jsdom "29.7.0" + undici "5.27.0" + "@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": version "1.11.5" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c" @@ -2072,6 +2217,11 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + abbrev@1: version "1.1.1" resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -2092,6 +2242,14 @@ accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== + dependencies: + acorn "^8.1.0" + acorn-walk "^8.0.2" + acorn-import-assertions@^1.7.6: version "1.8.0" resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" @@ -2107,11 +2265,21 @@ acorn-walk@^8.0.0: resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn-walk@^8.0.2: + version "8.3.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" + integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== + acorn@^8.0.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: version "8.8.2" resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +acorn@^8.1.0, acorn@^8.8.1: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -2182,6 +2350,11 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -2513,7 +2686,7 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz#2e197c698fc1373d63e1406d6607ea4617c613f1" integrity sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w== -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2560,6 +2733,11 @@ chrome-trace-event@^1.0.2: resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + classnames@^2.2.5, classnames@^2.2.6: version "2.3.2" resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" @@ -2962,6 +3140,23 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + csstype@^2.5.7: version "2.6.21" resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" @@ -2980,6 +3175,15 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + dataloader@^2.1.0: version "2.2.1" resolved "https://registry.npmjs.org/dataloader/-/dataloader-2.2.1.tgz#f07ab712514313a34b1507a308dbb7dc14bac715" @@ -3030,6 +3234,11 @@ debug@^3.2.6, debug@^3.2.7: dependencies: ms "^2.1.1" +decimal.js@^10.4.2: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -3168,6 +3377,13 @@ domelementtype@^2.0.1, domelementtype@^2.2.0: resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -3279,6 +3495,11 @@ entities@^2.0.0: resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -3431,11 +3652,27 @@ escape-string-regexp@^1.0.5: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + eslint-config-airbnb-base@^14.2.1: version "14.2.1" resolved "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" @@ -3579,6 +3816,11 @@ espree@^9.4.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + esquery@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -4271,6 +4513,13 @@ hoist-non-react-statics@^3.1.0: dependencies: react-is "^16.7.0" +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + html-entities@^2.1.0: version "2.3.3" resolved "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" @@ -4342,7 +4591,7 @@ http-status@^1.6.2: resolved "https://registry.npmjs.org/http-status/-/http-status-1.6.2.tgz#6dc05188a9856d67d96e48e8b4fd645c719ce82a" integrity sha512-oUExvfNckrpTpDazph7kNG8sQi5au3BeTo0idaZFXEhTaJKu7GNJCLHI0rYY2wljm548MSTM+Ljj/c6anqu2zQ== -https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -4376,6 +4625,13 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -4608,6 +4864,11 @@ is-plain-object@^5.0.0: resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-promise@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -4711,6 +4972,56 @@ isomorphic-fetch@^3.0.0: node-fetch "^2.6.1" whatwg-fetch "^3.4.1" +jest-environment-jsdom@29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/jsdom" "^20.0.0" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jsdom "^20.0.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-worker@^27.0.2, jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -4753,6 +5064,38 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== + dependencies: + abab "^2.0.6" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.2" + decimal.js "^10.4.2" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.2" + parse5 "^7.1.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" + json-bigint@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" @@ -5385,6 +5728,11 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" +nwsapi@^2.2.2: + version "2.2.7" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" + integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== + object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -5554,6 +5902,13 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse5@^7.0.0, parse5@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -5797,7 +6152,7 @@ picocolors@^1.0.0: resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -6409,6 +6764,15 @@ pretty-error@^4.0.0: lodash "^4.17.20" renderkid "^3.0.0" +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + probe-image-size@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/probe-image-size/-/probe-image-size-6.0.0.tgz#4a85b19d5af4e29a8de7d53a9aa036f6fd02f5f4" @@ -6609,6 +6973,11 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-onclickoutside@^6.12.2: version "6.12.2" resolved "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz#8e6cf80c7d17a79f2c908399918158a7b02dda01" @@ -6870,7 +7239,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -6911,6 +7280,13 @@ sax@>=0.6.0, sax@^1.2.4: resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" @@ -7221,6 +7597,13 @@ stable@^0.1.8: resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + state-local@^1.0.6: version "1.0.7" resolved "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" @@ -7400,6 +7783,11 @@ swc-minify-webpack-plugin@^1.0.1: resolved "https://registry.npmjs.org/swc-minify-webpack-plugin/-/swc-minify-webpack-plugin-1.0.1.tgz#8a19b88717e3bcaf13c0c559a5ac3537291f5deb" integrity sha512-Zeg50WoMQc7gMbPoXryyXU4suxsQ4QBJb3Lsg3l2uFSLZ3ATDkXE4CUv1sGdyNsOGt9YIuc3aSnz9z+BoUHPSg== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + tabbable@^5.3.3: version "5.3.3" resolved "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz#aac0ff88c73b22d6c3c5a50b1586310006b47fbf" @@ -7573,6 +7961,16 @@ tough-cookie@^4.0.0: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^4.1.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tr46@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" @@ -7660,6 +8058,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -7712,6 +8115,13 @@ undefsafe@^2.0.5: resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +undici@5.27.0: + version "5.27.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.27.0.tgz#789f2e40ce982b5507899abc2c2ddeb2712b4554" + integrity sha512-l3ydWhlhOJzMVOYkymLykcRRXqbUaQriERtR70B9LzNkZ4bX52Fc8wbTDneMiwo8T+AemZXvXaTx+9o5ROxrXg== + dependencies: + "@fastify/busboy" "^2.0.0" + universalify@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" @@ -7809,6 +8219,13 @@ void-elements@3.1.0: resolved "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== + dependencies: + xml-name-validator "^4.0.0" + warning@^4.0.2: version "4.0.3" resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" @@ -7939,11 +8356,23 @@ webpack@^5.78.0: watchpack "^2.4.0" webpack-sources "^3.2.3" +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + whatwg-fetch@^3.4.1: version "3.6.2" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + whatwg-url@^11.0.0: version "11.0.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" @@ -8020,6 +8449,16 @@ ws@^7.3.1: resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@^8.11.0: + version "8.14.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" + integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + xml2js@^0.4.19: version "0.4.23" resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" @@ -8033,6 +8472,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xss@^1.0.6: version "1.0.14" resolved "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz#4f3efbde75ad0d82e9921cc3c95e6590dd336694" From b6746ce81c98c4ce1ea0679289daf67fa29b0662 Mon Sep 17 00:00:00 2001 From: inadeqtfuturs Date: Thu, 7 Dec 2023 20:07:17 -0600 Subject: [PATCH 02/14] fix(vercel): misc clean up. add webpack --- README.md | 2 +- dev/src/collections/Media.ts | 2 +- src/adapters/vercel/generateURL.ts | 4 +--- src/adapters/vercel/handleDelete.ts | 6 ++---- src/adapters/vercel/handleUpload.ts | 4 +--- src/adapters/vercel/index.ts | 13 ++++++++----- src/adapters/vercel/staticHandler.ts | 4 +--- 7 files changed, 15 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 6242416..e839342 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ This plugin supports the following adapters: However, you can create your own adapter for any third-party service you would like to use. -All adapters are implemented `dev` directory's [Payload Config](https://github.com/payloadcms/plugin-cloud-storage/blob/master/dev/src/payload.config.ts). See this file for examples. +All adapters are implemented in `dev` directory's [Payload Config](https://github.com/payloadcms/plugin-cloud-storage/blob/master/dev/src/payload.config.ts). See this file for examples. ## Plugin options diff --git a/dev/src/collections/Media.ts b/dev/src/collections/Media.ts index 988ecaf..c236915 100644 --- a/dev/src/collections/Media.ts +++ b/dev/src/collections/Media.ts @@ -7,7 +7,7 @@ const urlField: Field = { hooks: { afterRead: [ ({ value }) => { - console.log('hello from hook') + console.log('hello from hook', value) return value }, ], diff --git a/src/adapters/vercel/generateURL.ts b/src/adapters/vercel/generateURL.ts index 13d63c0..3102c26 100644 --- a/src/adapters/vercel/generateURL.ts +++ b/src/adapters/vercel/generateURL.ts @@ -7,7 +7,7 @@ interface Args { bucketName: string } -const getGenerateURL = ({ storeId, bucketName }: Args): GenerateURL => { +export const getGenerateURL = ({ storeId, bucketName }: Args): GenerateURL => { return ({ filename, prefix = '' }) => { return `https://${storeId}.public.blob.vercel-storage.com/${bucketName}/${path.posix.join( prefix, @@ -15,5 +15,3 @@ const getGenerateURL = ({ storeId, bucketName }: Args): GenerateURL => { )}` } } - -export default getGenerateURL diff --git a/src/adapters/vercel/handleDelete.ts b/src/adapters/vercel/handleDelete.ts index be77c3b..08cb6ee 100644 --- a/src/adapters/vercel/handleDelete.ts +++ b/src/adapters/vercel/handleDelete.ts @@ -1,6 +1,5 @@ import { del } from '@vercel/blob' import path from 'path' - import type { HandleDelete } from '../../types' interface Args { @@ -9,16 +8,15 @@ interface Args { bucketName: string } -const getHandleDelete = ({ token, bucketName, storeId }: Args): HandleDelete => { +export const getHandleDelete = ({ token, bucketName, storeId }: Args): HandleDelete => { return async ({ filename, doc: { prefix = '' } }) => { const fileUrl = `https://${storeId}.public.blob.vercel-storage.com/${bucketName}/${path.posix.join( prefix, filename, )}` + // TODO: do we need to return this? const deletedBlob = await del(fileUrl, { token }) return deletedBlob } } - -export default getHandleDelete diff --git a/src/adapters/vercel/handleUpload.ts b/src/adapters/vercel/handleUpload.ts index e0f67c0..830d8a8 100644 --- a/src/adapters/vercel/handleUpload.ts +++ b/src/adapters/vercel/handleUpload.ts @@ -12,7 +12,7 @@ interface Args { cacheControlMaxAge?: number } -const getHandleUpload = ({ +export const getHandleUpload = ({ token, bucketName, access = 'public', @@ -40,5 +40,3 @@ const getHandleUpload = ({ return data } } - -export default getHandleUpload diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index f5719ff..b222b4b 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -1,9 +1,10 @@ import type { Adapter, GeneratedAdapter } from '../../types' -import getGenerateURL from './generateURL' -import getHandleDelete from './handleDelete' -import getHandleUpload from './handleUpload' -import getStaticHandler from './staticHandler' +import { getGenerateURL } from './generateURL' +import { getHandleDelete } from './handleDelete' +import { getHandleUpload } from './handleUpload' +import { getStaticHandler } from './staticHandler' +import { extendWebpackConfig } from './webpack' export interface Args { token: string @@ -11,9 +12,10 @@ export interface Args { bucketName: string } +// TODO: handle config object here +// TODO: swap `storeId` for url export const vercelBlobAdapter = ({ token, storeId, bucketName }: Args): Adapter => { return ({ collection }): GeneratedAdapter => { - console.log('@--> collection', collection) return { handleUpload: getHandleUpload({ token, bucketName }), handleDelete: getHandleDelete({ token, bucketName, storeId }), @@ -24,6 +26,7 @@ export const vercelBlobAdapter = ({ token, storeId, bucketName }: Args): Adapter storeId, collection, }), + webpack: extendWebpackConfig, } } } diff --git a/src/adapters/vercel/staticHandler.ts b/src/adapters/vercel/staticHandler.ts index 42fb6ee..bbf1c3b 100644 --- a/src/adapters/vercel/staticHandler.ts +++ b/src/adapters/vercel/staticHandler.ts @@ -12,7 +12,7 @@ interface Args { collection: CollectionConfig } -const getStaticHandler = ({ bucketName, storeId, collection }: Args): StaticHandler => { +export const getStaticHandler = ({ bucketName, storeId, collection }: Args): StaticHandler => { return async (req, res, next) => { try { const prefix = await getFilePrefix({ req, collection }) @@ -47,5 +47,3 @@ const getStaticHandler = ({ bucketName, storeId, collection }: Args): StaticHand } } } - -export default getStaticHandler From c2b582c7ec4b283fe30baeadcac1cb71bc20985a Mon Sep 17 00:00:00 2001 From: inadeqtfuturs Date: Fri, 8 Dec 2023 10:06:16 -0600 Subject: [PATCH 03/14] fix(vercel): clear up TODO items --- src/adapters/azure/emulator/docker-compose.yml | 2 +- src/adapters/vercel/handleUpload.ts | 8 ++++++++ src/adapters/vercel/index.ts | 15 ++++++++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/adapters/azure/emulator/docker-compose.yml b/src/adapters/azure/emulator/docker-compose.yml index 3024339..eb330f7 100644 --- a/src/adapters/azure/emulator/docker-compose.yml +++ b/src/adapters/azure/emulator/docker-compose.yml @@ -2,7 +2,7 @@ version: "3" services: azure-storage: - image: mcr.microsoft.com/azure-storage/azurite:3.18.0 + image: mcr.microsoft.com/azure-storage/azurite restart: always command: "azurite --loose --blobHost 0.0.0.0 --tableHost 0.0.0.0 --queueHost 0.0.0.0" ports: diff --git a/src/adapters/vercel/handleUpload.ts b/src/adapters/vercel/handleUpload.ts index 830d8a8..0f15621 100644 --- a/src/adapters/vercel/handleUpload.ts +++ b/src/adapters/vercel/handleUpload.ts @@ -21,8 +21,16 @@ export const getHandleUpload = ({ cacheControlMaxAge = 31556926, }: Args): HandleUpload => { return async ({ data, file }) => { + // TODO: handle uploads > 4.5MB + // https://vercel.com/docs/storage/vercel-blob/quickstart#server-and-client-uploads const fileKey = path.posix.join(data.prefix || prefix, file.filename) const filePath = `${bucketName}/${fileKey}` + + // 1. How big is the file? + // 2. If < 4.5MB, use `put` + // 3. If < 500MB use `handleUpload` + // 4. Throw error if > 500MB + const resp = await put(filePath, file.buffer, { token, contentType: file.mimeType, diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index b222b4b..ac0365e 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -7,14 +7,23 @@ import { getStaticHandler } from './staticHandler' import { extendWebpackConfig } from './webpack' export interface Args { + // URL for blob uploads + baseUrl: string token: string - storeId: string + storeId: string // DEPRECATED bucketName: string + // https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#put + config?: { + bucketName?: string + addRandomSuffix?: boolean + cacheControlMaxAge?: number + } } +// TODO: swap `storeId` for url as `baseUrl` // TODO: handle config object here -// TODO: swap `storeId` for url -export const vercelBlobAdapter = ({ token, storeId, bucketName }: Args): Adapter => { +export const vercelBlobAdapter = ({ token, storeId, bucketName, config = {} }: Args): Adapter => { + // read config options and pass to `handleUpload` return ({ collection }): GeneratedAdapter => { return { handleUpload: getHandleUpload({ token, bucketName }), From f29e1ca6646b21b6caf46f68ce49bf4465af0971 Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Mon, 11 Dec 2023 01:13:52 -0500 Subject: [PATCH 04/14] feat(adapter): adjusted config, added custom type for vercel, tested locally --- dev/src/payload.config.ts | 8 +++- src/adapters/vercel/generateURL.ts | 24 ++++++------ src/adapters/vercel/handleDelete.ts | 26 +++++++------ src/adapters/vercel/handleUpload.ts | 57 ++++++++++++++++------------ src/adapters/vercel/index.ts | 33 +++------------- src/adapters/vercel/staticHandler.ts | 25 ++++++------ src/types.ts | 10 +++++ 7 files changed, 93 insertions(+), 90 deletions(-) diff --git a/dev/src/payload.config.ts b/dev/src/payload.config.ts index bc3e032..8ae60ba 100644 --- a/dev/src/payload.config.ts +++ b/dev/src/payload.config.ts @@ -57,8 +57,12 @@ if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'gcs') { if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'vercel') { adapter = vercelBlobAdapter({ token: process.env.BLOB_READ_WRITE_TOKEN, - storeId: process.env.VERCEL_STORE_ID, - bucketName: process.env.VERCEL_BUCKET_NAME, + baseUrl: process.env.VERCEL_BLOB_BASE_URL, + storeId: process.env.VERCEL_BLOB_STORE_ID, + access: 'public', // 'public' access control is currently the only option. + optionalUrlPrefix: process.env.VERCEL_OPTIONAL_URL_PREFIX, + addRandomSuffix: false, + cacheControlMaxAge: 31556926, // should this be set in the .env? or something that is configurable easily here? }) } diff --git a/src/adapters/vercel/generateURL.ts b/src/adapters/vercel/generateURL.ts index 3102c26..9f16014 100644 --- a/src/adapters/vercel/generateURL.ts +++ b/src/adapters/vercel/generateURL.ts @@ -1,17 +1,19 @@ import path from 'path' -import type { GenerateURL } from '../../types' +import type { GenerateURL, VercelBlobConfig } from '../../types' -interface Args { - storeId: string - bucketName: string -} - -export const getGenerateURL = ({ storeId, bucketName }: Args): GenerateURL => { +export const getGenerateURL = ({ + storeId, + baseUrl, + access, + optionalUrlPrefix, +}: VercelBlobConfig): GenerateURL => { return ({ filename, prefix = '' }) => { - return `https://${storeId}.public.blob.vercel-storage.com/${bucketName}/${path.posix.join( - prefix, - filename, - )}` + let url = `https://${storeId}.${access}.${baseUrl}` + + if (optionalUrlPrefix) { + url = `${url}/${optionalUrlPrefix}` + } + return `${url}/${path.posix.join(prefix, filename)}` } } diff --git a/src/adapters/vercel/handleDelete.ts b/src/adapters/vercel/handleDelete.ts index 08cb6ee..750664d 100644 --- a/src/adapters/vercel/handleDelete.ts +++ b/src/adapters/vercel/handleDelete.ts @@ -1,21 +1,23 @@ import { del } from '@vercel/blob' import path from 'path' -import type { HandleDelete } from '../../types' -interface Args { - token: string - storeId: string - bucketName: string -} +import type { HandleDelete, VercelBlobConfig } from '../../types' -export const getHandleDelete = ({ token, bucketName, storeId }: Args): HandleDelete => { +export const getHandleDelete = ({ + token, + storeId, + baseUrl, + access, + optionalUrlPrefix, +}: VercelBlobConfig): HandleDelete => { return async ({ filename, doc: { prefix = '' } }) => { - const fileUrl = `https://${storeId}.public.blob.vercel-storage.com/${bucketName}/${path.posix.join( - prefix, - filename, - )}` + let url = `https://${storeId}.${access}.${baseUrl}` + + if (optionalUrlPrefix) { + url = `${url}/${optionalUrlPrefix}` + } + const fileUrl = `${url}/${path.posix.join(prefix, filename)}` - // TODO: do we need to return this? const deletedBlob = await del(fileUrl, { token }) return deletedBlob } diff --git a/src/adapters/vercel/handleUpload.ts b/src/adapters/vercel/handleUpload.ts index 0f15621..e3c27e8 100644 --- a/src/adapters/vercel/handleUpload.ts +++ b/src/adapters/vercel/handleUpload.ts @@ -1,37 +1,21 @@ import { put } from '@vercel/blob' +import { upload } from '@vercel/blob/client' import path from 'path' -import type { HandleUpload } from '../../types' - -interface Args { - token: string - bucketName: string - access?: 'public' - prefix?: string - addRandomSuffix?: boolean - cacheControlMaxAge?: number -} +import type { HandleUpload, VercelBlobConfig } from '../../types' export const getHandleUpload = ({ token, - bucketName, access = 'public', - prefix = '', - addRandomSuffix = false, - cacheControlMaxAge = 31556926, -}: Args): HandleUpload => { + addRandomSuffix, + cacheControlMaxAge, + optionalUrlPrefix, +}: VercelBlobConfig): HandleUpload => { return async ({ data, file }) => { - // TODO: handle uploads > 4.5MB - // https://vercel.com/docs/storage/vercel-blob/quickstart#server-and-client-uploads + const prefix = optionalUrlPrefix || '' const fileKey = path.posix.join(data.prefix || prefix, file.filename) - const filePath = `${bucketName}/${fileKey}` - - // 1. How big is the file? - // 2. If < 4.5MB, use `put` - // 3. If < 500MB use `handleUpload` - // 4. Throw error if > 500MB - const resp = await put(filePath, file.buffer, { + await put(fileKey, file.buffer, { token, contentType: file.mimeType, access, @@ -39,7 +23,30 @@ export const getHandleUpload = ({ cacheControlMaxAge, }) - console.log('@--> data', data) + // TODO: handle uploads over 4.5mb -- notes on attempt: + // error when running: upload: 'Error: Vercel Blob: client/`upload` must be called from a client environment' -- need to figure out how to access payload's client environment + + // const fileSizeInMb = typeof file?.filesize === 'number' ? file.filesize / 1000000 : 0 // need better default here + // if (fileSizeInMb < 4.5) { + // blobResult = await put(fileKey, file.buffer, { + // token, + // contentType: file.mimeType, + // access, + // addRandomSuffix, + // cacheControlMaxAge, + // }) + // } else if (fileSizeInMb > 4.5 && fileSizeInMb < 500) { + // blobResult = await upload(fileKey, file.buffer, { + // access: 'public', + // handleUploadUrl: '/api/avatar/upload', + // }) + // } + + // const blobResult = await upload(file.filename, file.buffer, { + // access: 'public', + // handleUploadUrl: fileKey, + // contentType: file.mimeType, + // }) if (addRandomSuffix) { // handle updating data diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index ac0365e..77d7746 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -1,4 +1,4 @@ -import type { Adapter, GeneratedAdapter } from '../../types' +import type { Adapter, GeneratedAdapter, VercelBlobConfig } from '../../types' import { getGenerateURL } from './generateURL' import { getHandleDelete } from './handleDelete' @@ -6,35 +6,14 @@ import { getHandleUpload } from './handleUpload' import { getStaticHandler } from './staticHandler' import { extendWebpackConfig } from './webpack' -export interface Args { - // URL for blob uploads - baseUrl: string - token: string - storeId: string // DEPRECATED - bucketName: string - // https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#put - config?: { - bucketName?: string - addRandomSuffix?: boolean - cacheControlMaxAge?: number - } -} - -// TODO: swap `storeId` for url as `baseUrl` -// TODO: handle config object here -export const vercelBlobAdapter = ({ token, storeId, bucketName, config = {} }: Args): Adapter => { +export const vercelBlobAdapter = (config: VercelBlobConfig): Adapter => { // read config options and pass to `handleUpload` return ({ collection }): GeneratedAdapter => { return { - handleUpload: getHandleUpload({ token, bucketName }), - handleDelete: getHandleDelete({ token, bucketName, storeId }), - generateURL: getGenerateURL({ storeId, bucketName }), - staticHandler: getStaticHandler({ - token, - bucketName, - storeId, - collection, - }), + handleUpload: getHandleUpload(config), + handleDelete: getHandleDelete(config), + generateURL: getGenerateURL(config), + staticHandler: getStaticHandler(config, collection), webpack: extendWebpackConfig, } } diff --git a/src/adapters/vercel/staticHandler.ts b/src/adapters/vercel/staticHandler.ts index bbf1c3b..1cc5ac9 100644 --- a/src/adapters/vercel/staticHandler.ts +++ b/src/adapters/vercel/staticHandler.ts @@ -2,24 +2,23 @@ import path from 'path' import { head } from '@vercel/blob' import type { CollectionConfig } from 'payload/types' -import type { StaticHandler } from '../../types' +import type { StaticHandler, VercelBlobConfig } from '../../types' import { getFilePrefix } from '../../utilities/getFilePrefix' -interface Args { - token: string - storeId: string - bucketName: string - collection: CollectionConfig -} - -export const getStaticHandler = ({ bucketName, storeId, collection }: Args): StaticHandler => { +export const getStaticHandler = ( + { optionalUrlPrefix, storeId, baseUrl, access }: VercelBlobConfig, + collection: CollectionConfig, +): StaticHandler => { return async (req, res, next) => { try { const prefix = await getFilePrefix({ req, collection }) - const fileUrl = `https://${storeId}.public.blob.vercel-storage.com/${bucketName}/${path.posix.join( - prefix, - req.params.filename, - )}` + + let url = `https://${storeId}.${access}.${baseUrl}` + + if (optionalUrlPrefix) { + url = `${url}/${optionalUrlPrefix}` + } + const fileUrl = `${url}/${path.posix.join(prefix, req.params.filename)}` const blobMetadata = await head(fileUrl) if (blobMetadata) { diff --git a/src/types.ts b/src/types.ts index 09731e5..c8c506b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -77,3 +77,13 @@ export interface PluginOptions { enabled?: boolean collections: Record } + +export interface VercelBlobConfig { + token: string + baseUrl: string + storeId: string + access: 'public' // access type is currently set to 'public', until 'private' is available + optionalUrlPrefix?: string + addRandomSuffix?: boolean + cacheControlMaxAge?: number +} From 5de9aff18caad84ccab93ae26b7c143847251543 Mon Sep 17 00:00:00 2001 From: inadeqtfuturs Date: Mon, 11 Dec 2023 11:57:02 -0600 Subject: [PATCH 05/14] feat(docs): add todo items --- src/adapters/vercel/index.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index 77d7746..1e0aa2c 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -6,9 +6,31 @@ import { getHandleUpload } from './handleUpload' import { getStaticHandler } from './staticHandler' import { extendWebpackConfig } from './webpack' +// TODO: move VercelBlobConfig to this file as `Args` +export interface Args {} + +// TODO: use this as a reference +/* { + * token, + * baseUrl, // RENAME + * storeId, + * config { + * access, + * optionalUrlPrefix, + * addRandomSuffix, + * cacheControlMaxAge + * } + * } */ + +console.log('@--> here') export const vercelBlobAdapter = (config: VercelBlobConfig): Adapter => { + console.log('@--> here info here') // read config options and pass to `handleUpload` - return ({ collection }): GeneratedAdapter => { + // TODO: generate baseURL here and pass to functions + // ex. const baseUrl = `https://${storeId}.${access}.${baseUrl}` + // TODO: conditionally tack on `optionalUrlPrefix` + // TODO: utilize `prefix` in the same way other adapters are + return ({ collection, prefix }): GeneratedAdapter => { return { handleUpload: getHandleUpload(config), handleDelete: getHandleDelete(config), From 320298a41b0783e5b5609d42cfd7181351a009a5 Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Mon, 11 Dec 2023 13:43:13 -0500 Subject: [PATCH 06/14] fix(adapter): removed exported type, refactored arguments, renamed variable --- dev/src/payload.config.ts | 12 +++++++----- src/adapters/vercel/index.ts | 35 ++++++++++++++++------------------- src/types.ts | 10 ---------- 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/dev/src/payload.config.ts b/dev/src/payload.config.ts index 8ae60ba..b924395 100644 --- a/dev/src/payload.config.ts +++ b/dev/src/payload.config.ts @@ -57,12 +57,14 @@ if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'gcs') { if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'vercel') { adapter = vercelBlobAdapter({ token: process.env.BLOB_READ_WRITE_TOKEN, - baseUrl: process.env.VERCEL_BLOB_BASE_URL, + endpointUrl: process.env.VERCEL_BLOB_ENDPOINT_URL, storeId: process.env.VERCEL_BLOB_STORE_ID, - access: 'public', // 'public' access control is currently the only option. - optionalUrlPrefix: process.env.VERCEL_OPTIONAL_URL_PREFIX, - addRandomSuffix: false, - cacheControlMaxAge: 31556926, // should this be set in the .env? or something that is configurable easily here? + options: { + access: 'public', // 'public' access control is currently the only option. + optionalUrlPrefix: process.env.VERCEL_OPTIONAL_URL_PREFIX, + addRandomSuffix: false, + cacheControlMaxAge: 31556926, // should this be set in the .env? or something that is configurable easily here? + }, }) } diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index 1e0aa2c..f21d5a9 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -7,23 +7,20 @@ import { getStaticHandler } from './staticHandler' import { extendWebpackConfig } from './webpack' // TODO: move VercelBlobConfig to this file as `Args` -export interface Args {} - -// TODO: use this as a reference -/* { - * token, - * baseUrl, // RENAME - * storeId, - * config { - * access, - * optionalUrlPrefix, - * addRandomSuffix, - * cacheControlMaxAge - * } - * } */ +export interface Args { + token: string + endpointUrl: string // RENAME + storeId: string + options: { + access: 'public' + optionalUrlPrefix: string + addRandomSuffix: boolean + cacheControlMaxAge: number + } +} console.log('@--> here') -export const vercelBlobAdapter = (config: VercelBlobConfig): Adapter => { +export const vercelBlobAdapter = ({ token, endpointUrl, storeId, options }: Args): Adapter => { console.log('@--> here info here') // read config options and pass to `handleUpload` // TODO: generate baseURL here and pass to functions @@ -32,10 +29,10 @@ export const vercelBlobAdapter = (config: VercelBlobConfig): Adapter => { // TODO: utilize `prefix` in the same way other adapters are return ({ collection, prefix }): GeneratedAdapter => { return { - handleUpload: getHandleUpload(config), - handleDelete: getHandleDelete(config), - generateURL: getGenerateURL(config), - staticHandler: getStaticHandler(config, collection), + handleUpload: getHandleUpload(options), + handleDelete: getHandleDelete(options), + generateURL: getGenerateURL(options), + staticHandler: getStaticHandler(options, collection), webpack: extendWebpackConfig, } } diff --git a/src/types.ts b/src/types.ts index c8c506b..09731e5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -77,13 +77,3 @@ export interface PluginOptions { enabled?: boolean collections: Record } - -export interface VercelBlobConfig { - token: string - baseUrl: string - storeId: string - access: 'public' // access type is currently set to 'public', until 'private' is available - optionalUrlPrefix?: string - addRandomSuffix?: boolean - cacheControlMaxAge?: number -} From 3e26df07057e9e645bd791a870a5553d184ae7da Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Mon, 11 Dec 2023 15:35:32 -0500 Subject: [PATCH 07/14] feat(adapter): stabilized handleUpload, payload.config, and args. Other handlers are WIP --- dev/src/payload.config.ts | 2 +- src/adapters/vercel/handleUpload.ts | 85 +++++++++++++++++++++++------ src/adapters/vercel/index.ts | 28 ++++++++-- 3 files changed, 91 insertions(+), 24 deletions(-) diff --git a/dev/src/payload.config.ts b/dev/src/payload.config.ts index b924395..4e988a8 100644 --- a/dev/src/payload.config.ts +++ b/dev/src/payload.config.ts @@ -61,7 +61,7 @@ if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'vercel') { storeId: process.env.VERCEL_BLOB_STORE_ID, options: { access: 'public', // 'public' access control is currently the only option. - optionalUrlPrefix: process.env.VERCEL_OPTIONAL_URL_PREFIX, + optionalUrlPrefix: process.env.VERCEL_OPTIONAL_URL_PREFIX || '', addRandomSuffix: false, cacheControlMaxAge: 31556926, // should this be set in the .env? or something that is configurable easily here? }, diff --git a/src/adapters/vercel/handleUpload.ts b/src/adapters/vercel/handleUpload.ts index e3c27e8..dc454c5 100644 --- a/src/adapters/vercel/handleUpload.ts +++ b/src/adapters/vercel/handleUpload.ts @@ -1,18 +1,25 @@ import { put } from '@vercel/blob' -import { upload } from '@vercel/blob/client' +import { handleUpload, upload, type HandleUploadBody } from '@vercel/blob/client' import path from 'path' -import type { HandleUpload, VercelBlobConfig } from '../../types' +import type { HandleUpload } from '../../types' + +interface Args { + token: string + access: 'public' + prefix?: string + addRandomSuffix: boolean + cacheControlMaxAge: number +} export const getHandleUpload = ({ token, - access = 'public', + access, addRandomSuffix, cacheControlMaxAge, - optionalUrlPrefix, -}: VercelBlobConfig): HandleUpload => { + prefix = '', +}: Args): HandleUpload => { return async ({ data, file }) => { - const prefix = optionalUrlPrefix || '' const fileKey = path.posix.join(data.prefix || prefix, file.filename) await put(fileKey, file.buffer, { @@ -23,12 +30,13 @@ export const getHandleUpload = ({ cacheControlMaxAge, }) - // TODO: handle uploads over 4.5mb -- notes on attempt: - // error when running: upload: 'Error: Vercel Blob: client/`upload` must be called from a client environment' -- need to figure out how to access payload's client environment + // TODO: handle uploads over 4.5mb + /// //// Client-Side Upload Work /// //// + // Conditional to handle filesize: // const fileSizeInMb = typeof file?.filesize === 'number' ? file.filesize / 1000000 : 0 // need better default here // if (fileSizeInMb < 4.5) { - // blobResult = await put(fileKey, file.buffer, { + // await put(fileKey, file.buffer, { // token, // contentType: file.mimeType, // access, @@ -36,18 +44,61 @@ export const getHandleUpload = ({ // cacheControlMaxAge, // }) // } else if (fileSizeInMb > 4.5 && fileSizeInMb < 500) { - // blobResult = await upload(fileKey, file.buffer, { - // access: 'public', - // handleUploadUrl: '/api/avatar/upload', - // }) + // await upload(fileKey, file.buffer, { + // access: 'public', + // handleUploadUrl: '/server-route/to/call/for/token', // --> need to figure this out + // contentType: file.mimeType, + // }) // } - // const blobResult = await upload(file.filename, file.buffer, { - // access: 'public', - // handleUploadUrl: fileKey, - // contentType: file.mimeType, + // /// /// /// /// NOTES /// /// /// /// //// + + // Reached out to Payload Folks via discord -- Cliff Notes on discussion: + // // - Client hooks aren't accessible from the adapter/plugin + // // - We'll have to write our own Component and Endpoint + // // - The contributor speculated that the 4.5 limit is due to the server less function limitations + // Refs: + // // - https://payloadcms.com/docs/admin/components + // // - https://payloadcms.com/docs/rest-api/overview#custom-endpoints + + // When attempting to run 'upload' via server here: + // // - "Error: Vercel Blob: client/`upload` must be called from a client environment" + + /// //// EXAMPLE Server-Side `handleUpload` function from Vercel-Blob storage //// /// + // const body = (await req.payload.express?.request.body.json()) as HandleUploadBody + // const request = req.payload.express?.request.body + // const blobResponse = await handleUpload({ + // token, + // request, + // body, + // onBeforeGenerateToken: async (pathname: string, clientPayload?: string) => { + // return { + // allowedContentTypes: ['image/jpeg', 'image/png', 'image/gif'], + // tokenPayload: JSON.stringify({ + // // optional, sent to your server on upload completion + // // you could pass a user id from auth, or a value from clientPayload + // }), + // } + // }, + // onUploadCompleted: async ({ blob, tokenPayload }) => { + // // Get notified of client upload completion + // // ⚠️ This will not work on `localhost` websites, + // // Use ngrok or similar to get the full upload flow + + // console.log('blob upload completed', blob, tokenPayload) + + // try { + // // Run any logic after the file upload completed + // // const { userId } = JSON.parse(tokenPayload); + // // await db.update({ avatar: blob.url, userId }); + // } catch (error) { + // throw new Error('Could not update user') + // } + // }, // }) + /// //// END Client-Side Upload Work /// //// + if (addRandomSuffix) { // handle updating data } diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index f21d5a9..6102787 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -21,18 +21,34 @@ export interface Args { console.log('@--> here') export const vercelBlobAdapter = ({ token, endpointUrl, storeId, options }: Args): Adapter => { - console.log('@--> here info here') // read config options and pass to `handleUpload` // TODO: generate baseURL here and pass to functions - // ex. const baseUrl = `https://${storeId}.${access}.${baseUrl}` + const { access, optionalUrlPrefix, addRandomSuffix, cacheControlMaxAge } = options + const baseUrl = `https://${storeId}.${access}.${endpointUrl}${ + optionalUrlPrefix ? `/${optionalUrlPrefix}` : '' + }` + // TODO: conditionally tack on `optionalUrlPrefix` // TODO: utilize `prefix` in the same way other adapters are return ({ collection, prefix }): GeneratedAdapter => { return { - handleUpload: getHandleUpload(options), - handleDelete: getHandleDelete(options), - generateURL: getGenerateURL(options), - staticHandler: getStaticHandler(options, collection), + handleUpload: getHandleUpload({ + token, + prefix, + access, + addRandomSuffix, + cacheControlMaxAge, + }), + handleDelete: getHandleDelete( + token, + baseUrl, + access, + addRandomSuffix, + cacheControlMaxAge, + prefix, + ), + generateURL: getGenerateURL(options, baseUrl, prefix), + staticHandler: getStaticHandler(token, options, collection, prefix), webpack: extendWebpackConfig, } } From 53c9b0397549381d731afcd88c1c57160e2b7b3e Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Mon, 11 Dec 2023 15:51:03 -0500 Subject: [PATCH 08/14] feat(adapter): stabilized handleDelete, staticHandler & generateURL WIP --- src/adapters/vercel/handleDelete.ts | 25 ++++++++++--------------- src/adapters/vercel/index.ts | 14 +++----------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/adapters/vercel/handleDelete.ts b/src/adapters/vercel/handleDelete.ts index 750664d..2aaa649 100644 --- a/src/adapters/vercel/handleDelete.ts +++ b/src/adapters/vercel/handleDelete.ts @@ -1,24 +1,19 @@ import { del } from '@vercel/blob' import path from 'path' -import type { HandleDelete, VercelBlobConfig } from '../../types' +import type { HandleDelete } from '../../types' -export const getHandleDelete = ({ - token, - storeId, - baseUrl, - access, - optionalUrlPrefix, -}: VercelBlobConfig): HandleDelete => { - return async ({ filename, doc: { prefix = '' } }) => { - let url = `https://${storeId}.${access}.${baseUrl}` - - if (optionalUrlPrefix) { - url = `${url}/${optionalUrlPrefix}` - } - const fileUrl = `${url}/${path.posix.join(prefix, filename)}` +interface Args { + token: string + baseUrl: string + prefix?: string +} +export const getHandleDelete = ({ token, baseUrl }: Args): HandleDelete => { + return async ({ filename, doc: { prefix = '' } }) => { + const fileUrl = `${baseUrl}/${path.posix.join(prefix, filename)}` const deletedBlob = await del(fileUrl, { token }) + return deletedBlob } } diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index 6102787..2a8889c 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -19,7 +19,6 @@ export interface Args { } } -console.log('@--> here') export const vercelBlobAdapter = ({ token, endpointUrl, storeId, options }: Args): Adapter => { // read config options and pass to `handleUpload` // TODO: generate baseURL here and pass to functions @@ -39,16 +38,9 @@ export const vercelBlobAdapter = ({ token, endpointUrl, storeId, options }: Args addRandomSuffix, cacheControlMaxAge, }), - handleDelete: getHandleDelete( - token, - baseUrl, - access, - addRandomSuffix, - cacheControlMaxAge, - prefix, - ), - generateURL: getGenerateURL(options, baseUrl, prefix), - staticHandler: getStaticHandler(token, options, collection, prefix), + handleDelete: getHandleDelete({ token, baseUrl, prefix }), + generateURL: getGenerateURL({ options, baseUrl, prefix }), + staticHandler: getStaticHandler({ token, options, prefix }, collection), webpack: extendWebpackConfig, } } From 9d08c71b70ea44c2d082e383264a33192476ebe9 Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Mon, 11 Dec 2023 15:55:19 -0500 Subject: [PATCH 09/14] feat(adapter): stabilized/cleaned up generateURL --- src/adapters/vercel/generateURL.ts | 21 ++++++++------------- src/adapters/vercel/index.ts | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/adapters/vercel/generateURL.ts b/src/adapters/vercel/generateURL.ts index 9f16014..fc22d52 100644 --- a/src/adapters/vercel/generateURL.ts +++ b/src/adapters/vercel/generateURL.ts @@ -1,19 +1,14 @@ import path from 'path' -import type { GenerateURL, VercelBlobConfig } from '../../types' +import type { GenerateURL } from '../../types' -export const getGenerateURL = ({ - storeId, - baseUrl, - access, - optionalUrlPrefix, -}: VercelBlobConfig): GenerateURL => { - return ({ filename, prefix = '' }) => { - let url = `https://${storeId}.${access}.${baseUrl}` +interface Args { + baseUrl: string + prefix?: string +} - if (optionalUrlPrefix) { - url = `${url}/${optionalUrlPrefix}` - } - return `${url}/${path.posix.join(prefix, filename)}` +export const getGenerateURL = ({ baseUrl }: Args): GenerateURL => { + return ({ filename, prefix = '' }) => { + return `${baseUrl}/${path.posix.join(prefix, filename)}` } } diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index 2a8889c..279bd55 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -39,7 +39,7 @@ export const vercelBlobAdapter = ({ token, endpointUrl, storeId, options }: Args cacheControlMaxAge, }), handleDelete: getHandleDelete({ token, baseUrl, prefix }), - generateURL: getGenerateURL({ options, baseUrl, prefix }), + generateURL: getGenerateURL({ baseUrl, prefix }), staticHandler: getStaticHandler({ token, options, prefix }, collection), webpack: extendWebpackConfig, } From ae3a558ed65efa85d34c3b17fcfd6eff7c163632 Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Mon, 11 Dec 2023 16:12:22 -0500 Subject: [PATCH 10/14] feat(adapter): stabilized static handler -- cleaned up other functions --- src/adapters/vercel/handleUpload.ts | 5 ++++- src/adapters/vercel/index.ts | 7 +++---- src/adapters/vercel/staticHandler.ts | 15 +++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/adapters/vercel/handleUpload.ts b/src/adapters/vercel/handleUpload.ts index dc454c5..53816a8 100644 --- a/src/adapters/vercel/handleUpload.ts +++ b/src/adapters/vercel/handleUpload.ts @@ -10,6 +10,7 @@ interface Args { prefix?: string addRandomSuffix: boolean cacheControlMaxAge: number + optionalUrlPrefix: string } export const getHandleUpload = ({ @@ -18,9 +19,11 @@ export const getHandleUpload = ({ addRandomSuffix, cacheControlMaxAge, prefix = '', + optionalUrlPrefix, }: Args): HandleUpload => { return async ({ data, file }) => { - const fileKey = path.posix.join(data.prefix || prefix, file.filename) + const filename = optionalUrlPrefix ? `${optionalUrlPrefix}/${file.filename}` : file.filename + const fileKey = path.posix.join(data.prefix || prefix, filename) await put(fileKey, file.buffer, { token, diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index 279bd55..bad46d1 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -1,4 +1,4 @@ -import type { Adapter, GeneratedAdapter, VercelBlobConfig } from '../../types' +import type { Adapter, GeneratedAdapter } from '../../types' import { getGenerateURL } from './generateURL' import { getHandleDelete } from './handleDelete' @@ -27,8 +27,6 @@ export const vercelBlobAdapter = ({ token, endpointUrl, storeId, options }: Args optionalUrlPrefix ? `/${optionalUrlPrefix}` : '' }` - // TODO: conditionally tack on `optionalUrlPrefix` - // TODO: utilize `prefix` in the same way other adapters are return ({ collection, prefix }): GeneratedAdapter => { return { handleUpload: getHandleUpload({ @@ -37,10 +35,11 @@ export const vercelBlobAdapter = ({ token, endpointUrl, storeId, options }: Args access, addRandomSuffix, cacheControlMaxAge, + optionalUrlPrefix, }), handleDelete: getHandleDelete({ token, baseUrl, prefix }), generateURL: getGenerateURL({ baseUrl, prefix }), - staticHandler: getStaticHandler({ token, options, prefix }, collection), + staticHandler: getStaticHandler({ baseUrl }, collection), webpack: extendWebpackConfig, } } diff --git a/src/adapters/vercel/staticHandler.ts b/src/adapters/vercel/staticHandler.ts index 1cc5ac9..70bc5dd 100644 --- a/src/adapters/vercel/staticHandler.ts +++ b/src/adapters/vercel/staticHandler.ts @@ -2,25 +2,24 @@ import path from 'path' import { head } from '@vercel/blob' import type { CollectionConfig } from 'payload/types' -import type { StaticHandler, VercelBlobConfig } from '../../types' +import type { StaticHandler } from '../../types' import { getFilePrefix } from '../../utilities/getFilePrefix' +interface Args { + baseUrl: string +} + export const getStaticHandler = ( - { optionalUrlPrefix, storeId, baseUrl, access }: VercelBlobConfig, + { baseUrl }: Args, collection: CollectionConfig, ): StaticHandler => { return async (req, res, next) => { try { const prefix = await getFilePrefix({ req, collection }) - let url = `https://${storeId}.${access}.${baseUrl}` + const fileUrl = `${baseUrl}/${path.posix.join(prefix, req.params.filename)}` - if (optionalUrlPrefix) { - url = `${url}/${optionalUrlPrefix}` - } - const fileUrl = `${url}/${path.posix.join(prefix, req.params.filename)}` const blobMetadata = await head(fileUrl) - if (blobMetadata) { const { contentType, size } = blobMetadata const response = await fetch(fileUrl) From 7001200f914c9015919b6c45c50462993dd70820 Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Tue, 19 Dec 2023 11:10:05 -0500 Subject: [PATCH 11/14] feat(client-plugin): moved custom component and handle upload, refactored args in payload config, re-worked other details --- .../CustomComponents/handleServerUpload.ts | 49 ++++++++ dev/src/collections/Media.ts | 29 ++++- dev/src/collections/Media.tsx | 114 ++++++++++++++++++ dev/src/payload.config.ts | 17 ++- src/adapters/vercel/index.ts | 14 ++- src/types.ts | 3 +- 6 files changed, 211 insertions(+), 15 deletions(-) create mode 100644 dev/src/collections/CustomComponents/handleServerUpload.ts create mode 100644 dev/src/collections/Media.tsx diff --git a/dev/src/collections/CustomComponents/handleServerUpload.ts b/dev/src/collections/CustomComponents/handleServerUpload.ts new file mode 100644 index 0000000..7a67e00 --- /dev/null +++ b/dev/src/collections/CustomComponents/handleServerUpload.ts @@ -0,0 +1,49 @@ +import type { PutBlobResult } from '@vercel/blob' +import { handleUpload, type HandleUploadBody } from '@vercel/blob/client' +import type { PayloadHandler } from 'payload/config' + +export const handleVercelUpload: PayloadHandler = async (req, res) => { + console.log('this endpoint is firing!!!!') + const token = process.env.BLOB_READ_WRITE_TOKEN + + if (!token) { + console.log('missing token') + } + + try { + const request = req + const body = (await req.body) as HandleUploadBody + + const handleUploadResult = await handleUpload({ + request, + body, + onBeforeGenerateToken: async (pathname: string) => { + // not sure what to make use of pathname here. Type requires it, docs example doesn't use it + console.log('pathname', pathname) + + return { + tokenPayload: JSON.stringify({ token }), + addRandomSuffix: false, + cacheControlMaxAge: 31556926, + // allowedContentTypes: contentTypes, + // maximumSizeInBytes: maxBytes, + } + }, + onUploadCompleted: async ({ + url, + pathname, + contentDisposition, + contentType, + }: PutBlobResult) => { + console.log('Upload completed for url:', url) + console.log('Upload completed for pathname:', pathname) + console.log('Upload completed for contentDisposition:', contentDisposition) + console.log('Upload completed for contentType:', contentType) + }, + }) + + res.status(200).json(handleUploadResult) + } catch (error) { + console.error('Error in handleUpload:', error) + } +} diff --git a/dev/src/collections/Media.ts b/dev/src/collections/Media.ts index c236915..d8ec8ed 100644 --- a/dev/src/collections/Media.ts +++ b/dev/src/collections/Media.ts @@ -1,5 +1,11 @@ /* eslint-disable no-console */ -import type { CollectionConfig, Field } from 'payload/types' +import type { a as PutBlobResult } from '@vercel/blob/dist/put-96a1f07e' +import { handleUpload, type HandleUploadBody } from '@vercel/blob/client' + +import type { Field, SanitizedCollectionConfig } from 'payload/types' + +import { VercelUploadComponent } from './CustomComponents/VercelUploadComponent' +import { handleVercelUpload } from './CustomComponents/handleServerUpload' const urlField: Field = { name: 'url', @@ -14,8 +20,17 @@ const urlField: Field = { }, } -export const Media: CollectionConfig = { +export const Media: SanitizedCollectionConfig = { slug: 'media', + admin: { + components: { + // @ts-expect-error + // 'edit' errors because its expecting other cusomtized components, eventhough they're set as optional + edit: { + SaveButton: VercelUploadComponent, + }, + }, + }, upload: { imageSizes: [ { @@ -31,6 +46,9 @@ export const Media: CollectionConfig = { name: 'sixteenByNineMedium', }, ], + staticDir: '/media', + staticURL: '/media', + disableLocalStorage: false, }, fields: [ { @@ -53,4 +71,11 @@ export const Media: CollectionConfig = { ], }, ], + endpoints: [ + { + path: '/vercel-upload', + method: 'post', + handler: handleVercelUpload, + }, + ], } diff --git a/dev/src/collections/Media.tsx b/dev/src/collections/Media.tsx new file mode 100644 index 0000000..d0a91e8 --- /dev/null +++ b/dev/src/collections/Media.tsx @@ -0,0 +1,114 @@ +/* eslint-disable no-console */ +// had to convert to a react file to return react components + +import * as React from 'react' +import { handleUpload, upload, type HandleUploadBody } from '@vercel/blob/client' + +import type { + CollectionConfig, + CustomSaveButtonProps, + Field, + SanitizedCollectionConfig, +} from 'payload/types' + +const urlField: Field = { + name: 'url', + type: 'text', + hooks: { + afterRead: [ + ({ value }) => { + // console.log('hello from hook', value) + return value + }, + ], + }, +} +// custom react component, client-side. Need to figure out how to get the request data in here? +export const CustomSaveButton: CustomSaveButtonProps = ({ event, DefaultButton, label, save }) => { + console.log('CUSTOM BUTTON FIRING HERE!!!!!') + console.log('label', label) + console.log('window.event', window.event) + console.log('event', event) + console.log('document', document) + return ( +
+
+ + +
+ ) +} + +export const Media: SanitizedCollectionConfig = { + slug: 'media', + admin: { + components: { + edit: { + SaveButton: CustomSaveButton, + }, + }, + }, + upload: { + imageSizes: [ + { + height: 400, + width: 400, + crop: 'center', + name: 'square', + }, + { + width: 900, + height: 450, + crop: 'center', + name: 'sixteenByNineMedium', + }, + ], + staticDir: '', + staticURL: '', + disableLocalStorage: false, + }, + fields: [ + { + name: 'alt', + label: 'Alt Text', + type: 'text', + }, + + // The following fields should be able to be merged in to default upload fields + urlField, + { + name: 'sizes', + type: 'group', + fields: [ + { + name: 'square', + type: 'group', + fields: [urlField], + }, + ], + }, + ], + // custom endpoint below here, this runs on the server-side. + endpoints: [ + { + path: '/', + method: 'post', + handler: async (req, res, next) => { + console.log('req.params.filename', req.files.file.name) + console.log('req.files.file', req.files.file.data) + const blobResult = await upload(req.files.file.name, req.files.file.data, { + access: 'public', + handleUploadUrl: '/', + contentType: req.headers['content-type'], + }) + + console.log('blobResult', blobResult) + if (blobResult) { + res.status(200).send({ blobResult }) + } else { + res.status(404).send({ error: 'blob did not create' }) + } + }, + }, + ], +} diff --git a/dev/src/payload.config.ts b/dev/src/payload.config.ts index 4e988a8..ac7a9a9 100644 --- a/dev/src/payload.config.ts +++ b/dev/src/payload.config.ts @@ -5,6 +5,7 @@ import { cloudStorage } from '../../src' import { s3Adapter } from '../../src/adapters/s3' import { gcsAdapter } from '../../src/adapters/gcs' import { azureBlobStorageAdapter } from '../../src/adapters/azure' +import type { VercelConfigArgs } from '../../src/adapters/vercel' import { vercelBlobAdapter } from '../../src/adapters/vercel' import type { Adapter } from '../../src/types' import { Media } from './collections/Media' @@ -54,17 +55,20 @@ if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'gcs') { }) } +// edit this config as seen neccessary +const vercelBlobConfig: VercelConfigArgs = { + access: 'public', + optionalUrlPrefix: 'desired-url-prefix', + addRandomSuffix: false, + cacheControlMaxAge: 31556926, +} + if (process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER === 'vercel') { adapter = vercelBlobAdapter({ token: process.env.BLOB_READ_WRITE_TOKEN, endpointUrl: process.env.VERCEL_BLOB_ENDPOINT_URL, storeId: process.env.VERCEL_BLOB_STORE_ID, - options: { - access: 'public', // 'public' access control is currently the only option. - optionalUrlPrefix: process.env.VERCEL_OPTIONAL_URL_PREFIX || '', - addRandomSuffix: false, - cacheControlMaxAge: 31556926, // should this be set in the .env? or something that is configurable easily here? - }, + options: vercelBlobConfig, }) } @@ -72,6 +76,7 @@ export default buildConfig({ serverURL: 'http://localhost:3000', collections: [Media, Users], upload: uploadOptions, + custom: { vercelConfig: vercelBlobConfig }, admin: { // NOTE - these webpack extensions are only required // for development of this plugin. diff --git a/src/adapters/vercel/index.ts b/src/adapters/vercel/index.ts index bad46d1..feccdfa 100644 --- a/src/adapters/vercel/index.ts +++ b/src/adapters/vercel/index.ts @@ -7,16 +7,18 @@ import { getStaticHandler } from './staticHandler' import { extendWebpackConfig } from './webpack' // TODO: move VercelBlobConfig to this file as `Args` + +export interface VercelConfigArgs { + access: 'public' + optionalUrlPrefix: string + addRandomSuffix: boolean + cacheControlMaxAge: number +} export interface Args { token: string endpointUrl: string // RENAME storeId: string - options: { - access: 'public' - optionalUrlPrefix: string - addRandomSuffix: boolean - cacheControlMaxAge: number - } + options: VercelConfigArgs } export const vercelBlobAdapter = ({ token, endpointUrl, storeId, options }: Args): Adapter => { diff --git a/src/types.ts b/src/types.ts index 09731e5..f869c15 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,7 +14,8 @@ export interface File { export type HandleUpload = (args: { collection: CollectionConfig - req: PayloadRequest + request: Request + payloadRequest: PayloadRequest data: any file: File }) => Promise | void From 675195cb4c1a5184b3d601d00101558d3ea3f323 Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Tue, 19 Dec 2023 11:12:40 -0500 Subject: [PATCH 12/14] feat(client-plugin): removed old file, added custom component --- .../VercelUploadComponent.tsx | 53 ++++++++ dev/src/collections/Media.tsx | 114 ------------------ 2 files changed, 53 insertions(+), 114 deletions(-) create mode 100644 dev/src/collections/CustomComponents/VercelUploadComponent.tsx delete mode 100644 dev/src/collections/Media.tsx diff --git a/dev/src/collections/CustomComponents/VercelUploadComponent.tsx b/dev/src/collections/CustomComponents/VercelUploadComponent.tsx new file mode 100644 index 0000000..67fe4fc --- /dev/null +++ b/dev/src/collections/CustomComponents/VercelUploadComponent.tsx @@ -0,0 +1,53 @@ +/* eslint-disable no-console */ +import * as React from 'react' +import { upload } from '@vercel/blob/client' +import { useAllFormFields, reduceFieldsToValues } from 'payload/components/forms' +import { useConfig } from 'payload/components/utilities' + + +import type { CustomSaveButtonProps } from 'payload/types' + +// custom react component, client-side. +export const VercelUploadComponent: CustomSaveButtonProps = ({ + props, + label, + save, + DefaultButton, +}) => { + const [fields] = useAllFormFields() + const formData = reduceFieldsToValues(fields, true) + + const payloadConfig = useConfig() + const optionalUrlPrefix = payloadConfig.custom.vercelConfig.optionalUrlPrefix + const payloadServerUrl = payloadConfig.serverURL + + const handleFormSubmit = async () => { + save() + console.log('formData', formData.file) + const originalFileName = await formData.file.name + const fileName = `${ + optionalUrlPrefix ? `${optionalUrlPrefix}/${originalFileName}` : originalFileName + }` + + const reader = new FileReader() + reader.readAsArrayBuffer(formData.file) + reader.onload = async event => { + const fileData = event.target.result + + try { + const blobResult = await upload(fileName, fileData, { + access: 'public', + handleUploadUrl: `${payloadServerUrl}/api/media/vercel-upload`, + contentType: formData.file.type, + clientPayload: 'hello-from-upload', + }) + + console.log('blobResult', blobResult) + } catch (error) { + console.log('error uploading blob', error) + } + } + } + + return +} diff --git a/dev/src/collections/Media.tsx b/dev/src/collections/Media.tsx deleted file mode 100644 index d0a91e8..0000000 --- a/dev/src/collections/Media.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* eslint-disable no-console */ -// had to convert to a react file to return react components - -import * as React from 'react' -import { handleUpload, upload, type HandleUploadBody } from '@vercel/blob/client' - -import type { - CollectionConfig, - CustomSaveButtonProps, - Field, - SanitizedCollectionConfig, -} from 'payload/types' - -const urlField: Field = { - name: 'url', - type: 'text', - hooks: { - afterRead: [ - ({ value }) => { - // console.log('hello from hook', value) - return value - }, - ], - }, -} -// custom react component, client-side. Need to figure out how to get the request data in here? -export const CustomSaveButton: CustomSaveButtonProps = ({ event, DefaultButton, label, save }) => { - console.log('CUSTOM BUTTON FIRING HERE!!!!!') - console.log('label', label) - console.log('window.event', window.event) - console.log('event', event) - console.log('document', document) - return ( -
-
- - -
- ) -} - -export const Media: SanitizedCollectionConfig = { - slug: 'media', - admin: { - components: { - edit: { - SaveButton: CustomSaveButton, - }, - }, - }, - upload: { - imageSizes: [ - { - height: 400, - width: 400, - crop: 'center', - name: 'square', - }, - { - width: 900, - height: 450, - crop: 'center', - name: 'sixteenByNineMedium', - }, - ], - staticDir: '', - staticURL: '', - disableLocalStorage: false, - }, - fields: [ - { - name: 'alt', - label: 'Alt Text', - type: 'text', - }, - - // The following fields should be able to be merged in to default upload fields - urlField, - { - name: 'sizes', - type: 'group', - fields: [ - { - name: 'square', - type: 'group', - fields: [urlField], - }, - ], - }, - ], - // custom endpoint below here, this runs on the server-side. - endpoints: [ - { - path: '/', - method: 'post', - handler: async (req, res, next) => { - console.log('req.params.filename', req.files.file.name) - console.log('req.files.file', req.files.file.data) - const blobResult = await upload(req.files.file.name, req.files.file.data, { - access: 'public', - handleUploadUrl: '/', - contentType: req.headers['content-type'], - }) - - console.log('blobResult', blobResult) - if (blobResult) { - res.status(200).send({ blobResult }) - } else { - res.status(404).send({ error: 'blob did not create' }) - } - }, - }, - ], -} From c8cb31c7ff7160c40b8087c4f5b1e549e2091e3e Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Tue, 19 Dec 2023 11:13:42 -0500 Subject: [PATCH 13/14] feat(client-upload): revereted changes to server-side plugin files --- src/adapters/vercel/handleUpload.ts | 70 ----------------------------- src/types.ts | 4 +- 2 files changed, 2 insertions(+), 72 deletions(-) diff --git a/src/adapters/vercel/handleUpload.ts b/src/adapters/vercel/handleUpload.ts index 53816a8..3e93d3b 100644 --- a/src/adapters/vercel/handleUpload.ts +++ b/src/adapters/vercel/handleUpload.ts @@ -1,5 +1,4 @@ import { put } from '@vercel/blob' -import { handleUpload, upload, type HandleUploadBody } from '@vercel/blob/client' import path from 'path' import type { HandleUpload } from '../../types' @@ -33,75 +32,6 @@ export const getHandleUpload = ({ cacheControlMaxAge, }) - // TODO: handle uploads over 4.5mb - /// //// Client-Side Upload Work /// //// - - // Conditional to handle filesize: - // const fileSizeInMb = typeof file?.filesize === 'number' ? file.filesize / 1000000 : 0 // need better default here - // if (fileSizeInMb < 4.5) { - // await put(fileKey, file.buffer, { - // token, - // contentType: file.mimeType, - // access, - // addRandomSuffix, - // cacheControlMaxAge, - // }) - // } else if (fileSizeInMb > 4.5 && fileSizeInMb < 500) { - // await upload(fileKey, file.buffer, { - // access: 'public', - // handleUploadUrl: '/server-route/to/call/for/token', // --> need to figure this out - // contentType: file.mimeType, - // }) - // } - - // /// /// /// /// NOTES /// /// /// /// //// - - // Reached out to Payload Folks via discord -- Cliff Notes on discussion: - // // - Client hooks aren't accessible from the adapter/plugin - // // - We'll have to write our own Component and Endpoint - // // - The contributor speculated that the 4.5 limit is due to the server less function limitations - // Refs: - // // - https://payloadcms.com/docs/admin/components - // // - https://payloadcms.com/docs/rest-api/overview#custom-endpoints - - // When attempting to run 'upload' via server here: - // // - "Error: Vercel Blob: client/`upload` must be called from a client environment" - - /// //// EXAMPLE Server-Side `handleUpload` function from Vercel-Blob storage //// /// - // const body = (await req.payload.express?.request.body.json()) as HandleUploadBody - // const request = req.payload.express?.request.body - // const blobResponse = await handleUpload({ - // token, - // request, - // body, - // onBeforeGenerateToken: async (pathname: string, clientPayload?: string) => { - // return { - // allowedContentTypes: ['image/jpeg', 'image/png', 'image/gif'], - // tokenPayload: JSON.stringify({ - // // optional, sent to your server on upload completion - // // you could pass a user id from auth, or a value from clientPayload - // }), - // } - // }, - // onUploadCompleted: async ({ blob, tokenPayload }) => { - // // Get notified of client upload completion - // // ⚠️ This will not work on `localhost` websites, - // // Use ngrok or similar to get the full upload flow - - // console.log('blob upload completed', blob, tokenPayload) - - // try { - // // Run any logic after the file upload completed - // // const { userId } = JSON.parse(tokenPayload); - // // await db.update({ avatar: blob.url, userId }); - // } catch (error) { - // throw new Error('Could not update user') - // } - // }, - // }) - - /// //// END Client-Side Upload Work /// //// - if (addRandomSuffix) { // handle updating data } diff --git a/src/types.ts b/src/types.ts index f869c15..21afba2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import type { NextFunction, Response } from 'express' +import { Payload } from 'payload' import type { TypeWithID } from 'payload/dist/collections/config/types' import type { FileData, ImageSize } from 'payload/dist/uploads/types' import type { CollectionConfig, PayloadRequest } from 'payload/types' @@ -14,8 +15,7 @@ export interface File { export type HandleUpload = (args: { collection: CollectionConfig - request: Request - payloadRequest: PayloadRequest + req: PayloadRequest data: any file: File }) => Promise | void From 5d20ef13e46b10e1f8c96f16304a2a16d726b2a1 Mon Sep 17 00:00:00 2001 From: Dave Lashinsky Date: Tue, 19 Dec 2023 15:48:08 -0500 Subject: [PATCH 14/14] fix(client-upload): fixed saving to payload DB only on successful upload to vercel, cleaned up types, removed unneccessary logging --- .../VercelUploadComponent.tsx | 10 +++++++--- .../CustomComponents/handleServerUpload.ts | 20 +++++++------------ dev/src/collections/Media.ts | 4 ---- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/dev/src/collections/CustomComponents/VercelUploadComponent.tsx b/dev/src/collections/CustomComponents/VercelUploadComponent.tsx index 67fe4fc..1fcfa78 100644 --- a/dev/src/collections/CustomComponents/VercelUploadComponent.tsx +++ b/dev/src/collections/CustomComponents/VercelUploadComponent.tsx @@ -4,7 +4,6 @@ import { upload } from '@vercel/blob/client' import { useAllFormFields, reduceFieldsToValues } from 'payload/components/forms' import { useConfig } from 'payload/components/utilities' - import type { CustomSaveButtonProps } from 'payload/types' // custom react component, client-side. @@ -22,7 +21,6 @@ export const VercelUploadComponent: CustomSaveButtonProps = ({ const payloadServerUrl = payloadConfig.serverURL const handleFormSubmit = async () => { - save() console.log('formData', formData.file) const originalFileName = await formData.file.name const fileName = `${ @@ -42,9 +40,15 @@ export const VercelUploadComponent: CustomSaveButtonProps = ({ clientPayload: 'hello-from-upload', }) + if (!blobResult) { + throw new Error() + } + console.log('blobResult', blobResult) + // save to local DB after blob result has generated + save() } catch (error) { - console.log('error uploading blob', error) + console.log('error uploading blob to vercel', error) } } } diff --git a/dev/src/collections/CustomComponents/handleServerUpload.ts b/dev/src/collections/CustomComponents/handleServerUpload.ts index 7a67e00..87a8189 100644 --- a/dev/src/collections/CustomComponents/handleServerUpload.ts +++ b/dev/src/collections/CustomComponents/handleServerUpload.ts @@ -1,4 +1,4 @@ -import type { PutBlobResult } from '@vercel/blob' +/* eslint-disable no-console */ import { handleUpload, type HandleUploadBody } from '@vercel/blob/client' import type { PayloadHandler } from 'payload/config' @@ -25,20 +25,14 @@ export const handleVercelUpload: PayloadHandler = async (req, res) => { tokenPayload: JSON.stringify({ token }), addRandomSuffix: false, cacheControlMaxAge: 31556926, - // allowedContentTypes: contentTypes, - // maximumSizeInBytes: maxBytes, } }, - onUploadCompleted: async ({ - url, - pathname, - contentDisposition, - contentType, - }: PutBlobResult) => { - console.log('Upload completed for url:', url) - console.log('Upload completed for pathname:', pathname) - console.log('Upload completed for contentDisposition:', contentDisposition) - console.log('Upload completed for contentType:', contentType) + onUploadCompleted: async ({ blob, tokenPayload }) => { + console.log('blob upload completed', blob) + console.log('tokenPayload', tokenPayload) + if (!blob) { + throw new Error() + } }, }) diff --git a/dev/src/collections/Media.ts b/dev/src/collections/Media.ts index d8ec8ed..e4fd5cd 100644 --- a/dev/src/collections/Media.ts +++ b/dev/src/collections/Media.ts @@ -1,7 +1,4 @@ /* eslint-disable no-console */ -import type { a as PutBlobResult } from '@vercel/blob/dist/put-96a1f07e' -import { handleUpload, type HandleUploadBody } from '@vercel/blob/client' - import type { Field, SanitizedCollectionConfig } from 'payload/types' import { VercelUploadComponent } from './CustomComponents/VercelUploadComponent' @@ -13,7 +10,6 @@ const urlField: Field = { hooks: { afterRead: [ ({ value }) => { - console.log('hello from hook', value) return value }, ],