Skip to content

Commit 4aeff64

Browse files
authored
Merge pull request #982 from NWACus/dependabot/npm_and_yarn/payloadcms-35f66fb9a5
Bump the payloadcms group to 3.81.0
2 parents 2f5b564 + 38d0b15 commit 4aeff64

7 files changed

Lines changed: 1285 additions & 2554 deletions

File tree

__tests__/e2e/admin/tenant-selector/sync-on-save.e2e.spec.ts

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
VALID_TENANT_SLUGS,
2+
isValidTenantSlug,
33
type ValidTenantSlug,
44
} from '../../../../src/utilities/tenancy/avalancheCenters'
55
import { openNav } from '../../fixtures/nav.fixture'
@@ -18,22 +18,7 @@ import { AdminUrlUtil, CollectionSlugs, saveDocAndAssert, waitForFormReady } fro
1818
const TEMP_TENANT_NAME = 'E2E Sync Test Center'
1919
const UPDATED_TENANT_NAME = 'E2E Sync Test Renamed'
2020

21-
test.describe.configure({ timeout: 90000, mode: 'serial' })
22-
23-
/** Find the first valid tenant slug that doesn't already exist in the database */
24-
async function findUnusedTenantSlug(
25-
page: import('@playwright/test').Page,
26-
): Promise<ValidTenantSlug> {
27-
const response = await page.request.get('http://localhost:3000/api/tenants?limit=100')
28-
const data = await response.json()
29-
const usedSlugs = new Set(data.docs?.map((doc: { slug: string }) => doc.slug))
30-
31-
const unused = VALID_TENANT_SLUGS.find((slug) => !usedSlugs.has(slug))
32-
if (!unused) {
33-
throw new Error('No unused tenant slugs available')
34-
}
35-
return unused
36-
}
21+
test.describe.configure({ timeout: 120000, mode: 'serial' })
3722

3823
/** Delete all tenants matching a slug */
3924
async function deleteTenantBySlug(page: import('@playwright/test').Page, slug: string) {
@@ -63,15 +48,40 @@ async function createTenant(
6348
name: string,
6449
): Promise<ValidTenantSlug> {
6550
const tenantsUrl = new AdminUrlUtil('http://localhost:3000', CollectionSlugs.tenants)
66-
const slug = await findUnusedTenantSlug(page)
6751

6852
await page.goto(tenantsUrl.create)
69-
await page.waitForLoadState('networkidle')
53+
await page.waitForLoadState('domcontentloaded')
7054
await waitForFormReady(page)
7155

7256
const slugField = page.locator('#field-slug')
73-
await slugField.locator('button.dropdown-indicator').click()
74-
await slugField.locator('.rs__option', { hasText: new RegExp(`\\(${slug}\\)`) }).click()
57+
const dropdownButton = slugField.locator('button.dropdown-indicator')
58+
await dropdownButton.waitFor({ state: 'visible', timeout: 30000 })
59+
60+
// The TenantSlugField is a server component — the dropdown may not be interactive
61+
// immediately after the button renders. Retry clicking until the menu opens.
62+
const menu = slugField.locator('.rs__menu')
63+
await expect(async () => {
64+
await dropdownButton.click()
65+
await expect(menu).toBeVisible({ timeout: 2000 })
66+
}).toPass({ timeout: 30000 })
67+
68+
// Pick the first available option from the dropdown (the server component
69+
// already filters out slugs that are in use, so any visible option is valid)
70+
const firstOption = slugField.locator('.rs__option').first()
71+
await firstOption.waitFor({ state: 'visible', timeout: 10000 })
72+
// Extract slug from option text, e.g. "Bridgeport Avalanche Center (bac)" -> "bac"
73+
const optionText = await firstOption.textContent()
74+
const slugMatch = optionText?.match(/\((\w+)\)$/)
75+
if (!slugMatch) {
76+
throw new Error(`Could not extract slug from option text: ${optionText}`)
77+
}
78+
const extractedSlug = slugMatch[1]
79+
if (!isValidTenantSlug(extractedSlug)) {
80+
throw new Error(`Extracted slug is not a valid tenant slug: ${extractedSlug}`)
81+
}
82+
const slug: ValidTenantSlug = extractedSlug
83+
await firstOption.click()
84+
7585
// Fill name after slug selection — AutoFillNameFromSlug overwrites name on slug change
7686
await page.locator('#field-name').fill(name)
7787
await saveDocAndAssert(page)
@@ -94,10 +104,10 @@ test.describe('Tenant selector syncs on save', () => {
94104

95105
// Navigate to a tenant-scoped collection via client-side nav link (NOT page.goto)
96106
await openNav(page)
97-
await Promise.all([
98-
page.waitForURL('**/admin/collections/pages'),
99-
page.locator('nav a[href="/admin/collections/pages"]').click(),
100-
])
107+
const navLink = page.locator('nav a[href="/admin/collections/pages"]')
108+
await navLink.waitFor({ state: 'visible', timeout: 10000 })
109+
await navLink.click()
110+
await page.waitForURL('**/admin/collections/pages', { timeout: 30000 })
101111
await page.waitForLoadState('domcontentloaded')
102112

103113
// The tenant selector should show the new tenant without a full page reload
@@ -128,10 +138,10 @@ test.describe('Tenant selector syncs on save', () => {
128138

129139
// Navigate to a tenant-scoped collection via client-side nav link (NOT page.goto)
130140
await openNav(page)
131-
await Promise.all([
132-
page.waitForURL('**/admin/collections/pages'),
133-
page.locator('nav a[href="/admin/collections/pages"]').click(),
134-
])
141+
const navLink = page.locator('nav a[href="/admin/collections/pages"]')
142+
await navLink.waitFor({ state: 'visible', timeout: 10000 })
143+
await navLink.click()
144+
await page.waitForURL('**/admin/collections/pages', { timeout: 30000 })
135145
await page.waitForLoadState('domcontentloaded')
136146

137147
const options = await getTenantOptions(page)

package.json

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,17 @@
5555
"@libsql/client": "^0.15.4",
5656
"@open-iframe-resizer/core": "^2.1.0",
5757
"@open-iframe-resizer/react": "^2.1.0",
58-
"@payloadcms/admin-bar": "3.78.0",
59-
"@payloadcms/db-sqlite": "3.78.0",
60-
"@payloadcms/email-nodemailer": "3.78.0",
61-
"@payloadcms/email-resend": "3.78.0",
62-
"@payloadcms/next": "3.78.0",
63-
"@payloadcms/plugin-form-builder": "3.78.0",
64-
"@payloadcms/plugin-sentry": "3.78.0",
65-
"@payloadcms/plugin-seo": "3.78.0",
66-
"@payloadcms/richtext-lexical": "3.78.0",
67-
"@payloadcms/storage-vercel-blob": "3.78.0",
68-
"@payloadcms/ui": "3.78.0",
58+
"@payloadcms/admin-bar": "3.81.0",
59+
"@payloadcms/db-sqlite": "3.81.0",
60+
"@payloadcms/email-nodemailer": "3.81.0",
61+
"@payloadcms/email-resend": "3.81.0",
62+
"@payloadcms/next": "3.81.0",
63+
"@payloadcms/plugin-form-builder": "3.81.0",
64+
"@payloadcms/plugin-sentry": "3.81.0",
65+
"@payloadcms/plugin-seo": "3.81.0",
66+
"@payloadcms/richtext-lexical": "3.81.0",
67+
"@payloadcms/storage-vercel-blob": "3.81.0",
68+
"@payloadcms/ui": "3.81.0",
6969
"@radix-ui/react-accordion": "^1.2.4",
7070
"@radix-ui/react-avatar": "^1.1.7",
7171
"@radix-ui/react-checkbox": "^1.1.3",
@@ -107,7 +107,7 @@
107107
"nextjs-toploader": "^3.9.17",
108108
"nuqs": "^2.7.3",
109109
"path-to-regexp": "^8.3.0",
110-
"payload": "3.78.0",
110+
"payload": "3.81.0",
111111
"pino": "9.14.0",
112112
"pino-pretty": "13.1.2",
113113
"pluralize": "^8.0.0",
@@ -174,6 +174,9 @@
174174
"esbuild",
175175
"sharp",
176176
"unrs-resolver"
177-
]
177+
],
178+
"patchedDependencies": {
179+
"@payloadcms/storage-vercel-blob": "patches/@payloadcms__storage-vercel-blob.patch"
180+
}
178181
}
179182
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
diff --git a/dist/client/VercelBlobClientUploadHandler.d.ts b/dist/client/VercelBlobClientUploadHandler.d.ts
2+
index 984feebe49e7bfaabd0294eebaa9415e09e0e5cd..18285f61580c050750d6312d12a8cdfb5d4ba6c8 100644
3+
--- a/dist/client/VercelBlobClientUploadHandler.d.ts
4+
+++ b/dist/client/VercelBlobClientUploadHandler.d.ts
5+
@@ -1,5 +1,6 @@
6+
export type VercelBlobClientUploadHandlerExtra = {
7+
addRandomSuffix: boolean;
8+
+ allowOverwrite: boolean;
9+
baseURL: string;
10+
prefix: string;
11+
};
12+
diff --git a/dist/client/VercelBlobClientUploadHandler.js b/dist/client/VercelBlobClientUploadHandler.js
13+
index 94a3512c0436805acba26910adb595cf970f4961..cda0b0aea5769c0522b7e9d609d9b919f89a43aa 100644
14+
--- a/dist/client/VercelBlobClientUploadHandler.js
15+
+++ b/dist/client/VercelBlobClientUploadHandler.js
16+
@@ -3,7 +3,7 @@ import { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/clie
17+
import { upload } from '@vercel/blob/client';
18+
import { formatAdminURL } from 'payload/shared';
19+
export const VercelBlobClientUploadHandler = createClientUploadHandler({
20+
- handler: async ({ apiRoute, collectionSlug, extra: { addRandomSuffix, baseURL, prefix = '' }, file, serverHandlerPath, serverURL, updateFilename })=>{
21+
+ handler: async ({ apiRoute, collectionSlug, extra: { addRandomSuffix, allowOverwrite, baseURL, prefix = '' }, file, serverHandlerPath, serverURL, updateFilename })=>{
22+
const endpointRoute = formatAdminURL({
23+
apiRoute,
24+
path: serverHandlerPath,
25+
diff --git a/dist/getClientUploadRoute.d.ts b/dist/getClientUploadRoute.d.ts
26+
index b0d9157554801e9a873ad09c1ace7c6c5db43a90..519f19e05145f5aaabd7afe8571149b529019b50 100644
27+
--- a/dist/getClientUploadRoute.d.ts
28+
+++ b/dist/getClientUploadRoute.d.ts
29+
@@ -5,9 +5,10 @@ type Args = {
30+
req: PayloadRequest;
31+
}) => boolean | Promise<boolean>;
32+
addRandomSuffix?: boolean;
33+
+ allowOverwrite?: boolean;
34+
cacheControlMaxAge?: number;
35+
token: string;
36+
};
37+
-export declare const getClientUploadRoute: ({ access, addRandomSuffix, cacheControlMaxAge, token }: Args) => PayloadHandler;
38+
+export declare const getClientUploadRoute: ({ access, addRandomSuffix, allowOverwrite, cacheControlMaxAge, token }: Args) => PayloadHandler;
39+
export {};
40+
//# sourceMappingURL=getClientUploadRoute.d.ts.map
41+
\ No newline at end of file
42+
diff --git a/dist/getClientUploadRoute.js b/dist/getClientUploadRoute.js
43+
index e11cf568867309efcb243fab490a33c6b01c46a8..bfeeee12b5180db867576f1a33738ebd076850a7 100644
44+
--- a/dist/getClientUploadRoute.js
45+
+++ b/dist/getClientUploadRoute.js
46+
@@ -1,7 +1,7 @@
47+
import { handleUpload } from '@vercel/blob/client';
48+
import { APIError, Forbidden } from 'payload';
49+
const defaultAccess = ({ req })=>!!req.user;
50+
-export const getClientUploadRoute = ({ access = defaultAccess, addRandomSuffix, cacheControlMaxAge, token })=>async (req)=>{
51+
+export const getClientUploadRoute = ({ access = defaultAccess, addRandomSuffix, allowOverwrite, cacheControlMaxAge, token })=>async (req)=>{
52+
const body = await req.json();
53+
try {
54+
const jsonResponse = await handleUpload({
55+
@@ -18,6 +18,7 @@ export const getClientUploadRoute = ({ access = defaultAccess, addRandomSuffix,
56+
}
57+
return Promise.resolve({
58+
addRandomSuffix,
59+
+ allowOverwrite,
60+
cacheControlMaxAge
61+
});
62+
},
63+
diff --git a/dist/handleUpload.d.ts b/dist/handleUpload.d.ts
64+
index 74b7b972fc9dbeffb7a2c79c8b472c831e61a670..404b4e4f2adc0d8f0180fae9428444232a2e4d6f 100644
65+
--- a/dist/handleUpload.d.ts
66+
+++ b/dist/handleUpload.d.ts
67+
@@ -4,6 +4,6 @@ type HandleUploadArgs = {
68+
baseUrl: string;
69+
prefix?: string;
70+
} & Omit<VercelBlobStorageOptions, 'collections'>;
71+
-export declare const getHandleUpload: ({ access, addRandomSuffix, baseUrl, cacheControlMaxAge, prefix, token, }: HandleUploadArgs) => HandleUpload;
72+
+export declare const getHandleUpload: ({ access, addRandomSuffix, allowOverwrite, baseUrl, cacheControlMaxAge, prefix, token, }: HandleUploadArgs) => HandleUpload;
73+
export {};
74+
//# sourceMappingURL=handleUpload.d.ts.map
75+
\ No newline at end of file
76+
diff --git a/dist/handleUpload.js b/dist/handleUpload.js
77+
index db797445156822d2a2a0024b303df940b446e4c2..eedefda709bf53e7f0b4bd2f1b0cdc81e5267c52 100644
78+
--- a/dist/handleUpload.js
79+
+++ b/dist/handleUpload.js
80+
@@ -1,11 +1,12 @@
81+
import { put } from '@vercel/blob';
82+
import path from 'path';
83+
-export const getHandleUpload = ({ access = 'public', addRandomSuffix, baseUrl, cacheControlMaxAge, prefix = '', token })=>{
84+
+export const getHandleUpload = ({ access = 'public', addRandomSuffix, allowOverwrite, baseUrl, cacheControlMaxAge, prefix = '', token })=>{
85+
return async ({ data, file: { buffer, filename, mimeType } })=>{
86+
const fileKey = path.posix.join(data.prefix || prefix, filename);
87+
const result = await put(fileKey, buffer, {
88+
access,
89+
addRandomSuffix,
90+
+ allowOverwrite,
91+
cacheControlMaxAge,
92+
contentType: mimeType,
93+
token
94+
diff --git a/dist/index.d.ts b/dist/index.d.ts
95+
index 8949c2db42d2657bdd7a72e1476420ce9766ea23..1edaf94546ad7876d4577a7150ed3b85f14f1585 100644
96+
--- a/dist/index.d.ts
97+
+++ b/dist/index.d.ts
98+
@@ -14,6 +14,13 @@ export type VercelBlobStorageOptions = {
99+
* @default false
100+
*/
101+
addRandomSuffix?: boolean;
102+
+ /**
103+
+ * Allow overwriting existing blobs with the same pathname.
104+
+ * When false (default), uploading a blob with an existing pathname throws an error.
105+
+ *
106+
+ * @default false
107+
+ */
108+
+ allowOverwrite?: boolean;
109+
/**
110+
* When enabled, fields (like the prefix field) will always be inserted into
111+
* the collection schema regardless of whether the plugin is enabled. This
112+
diff --git a/dist/index.js b/dist/index.js
113+
index 7c56ee05908afb9dfb3bfa20f2a5b9ed3e4cca69..7ca12e268e986d595c86840cce40dd1ae5949021 100644
114+
--- a/dist/index.js
115+
+++ b/dist/index.js
116+
@@ -8,6 +8,7 @@ import { getStaticHandler } from './staticHandler.js';
117+
const defaultUploadOptions = {
118+
access: 'public',
119+
addRandomSuffix: false,
120+
+ allowOverwrite: false,
121+
cacheControlMaxAge: 60 * 60 * 24 * 365,
122+
enabled: true
123+
};
124+
@@ -32,12 +33,14 @@ export const vercelBlobStorage = (options)=>(incomingConfig)=>{
125+
enabled: !isPluginDisabled && Boolean(options.clientUploads),
126+
extraClientHandlerProps: (collection)=>({
127+
addRandomSuffix: !!optionsWithDefaults.addRandomSuffix,
128+
+ allowOverwrite: !!optionsWithDefaults.allowOverwrite,
129+
baseURL: baseUrl,
130+
prefix: typeof collection === 'object' && collection.prefix && `${collection.prefix}/` || ''
131+
}),
132+
serverHandler: getClientUploadRoute({
133+
access: typeof options.clientUploads === 'object' ? options.clientUploads.access : undefined,
134+
addRandomSuffix: optionsWithDefaults.addRandomSuffix,
135+
+ allowOverwrite: optionsWithDefaults.allowOverwrite,
136+
cacheControlMaxAge: options.cacheControlMaxAge,
137+
token: options.token ?? ''
138+
}),
139+
@@ -96,7 +99,7 @@ export const vercelBlobStorage = (options)=>(incomingConfig)=>{
140+
};
141+
function vercelBlobStorageInternal(options) {
142+
return ({ collection, prefix })=>{
143+
- const { access, addRandomSuffix, baseUrl, cacheControlMaxAge, clientUploads, token } = options;
144+
+ const { access, addRandomSuffix, allowOverwrite, baseUrl, cacheControlMaxAge, clientUploads, token } = options;
145+
if (!token) {
146+
throw new Error('Vercel Blob storage token is required');
147+
}
148+
@@ -115,6 +118,7 @@ function vercelBlobStorageInternal(options) {
149+
handleUpload: getHandleUpload({
150+
access,
151+
addRandomSuffix,
152+
+ allowOverwrite,
153+
baseUrl,
154+
cacheControlMaxAge,
155+
prefix,

0 commit comments

Comments
 (0)