Compare commits

..

2 Commits

Author SHA1 Message Date
Jacob Fletcher
9c3bce1258 revert test 2025-06-30 23:52:45 -04:00
Jacob Fletcher
e6380159d9 revert: should select document after creation from relationship field 2025-06-30 23:43:35 -04:00
130 changed files with 920 additions and 2128 deletions

View File

@@ -187,8 +187,7 @@ jobs:
services:
postgres:
# Custom postgres 17 docker image that supports both pg-vector and postgis: https://github.com/payloadcms/postgis-vector
image: ${{ (startsWith(matrix.database, 'postgres') ) && 'ghcr.io/payloadcms/postgis-vector:latest' || '' }}
image: ${{ (startsWith(matrix.database, 'postgres') ) && 'postgis/postgis:16-3.4' || '' }}
env:
# must specify password for PG Docker container image, see: https://registry.hub.docker.com/_/postgres?tab=description&page=1&name=10
POSTGRES_USER: ${{ env.POSTGRES_USER }}

View File

@@ -16,15 +16,14 @@ The labels you provide for your Collections and Globals are used to name the Gra
At the top of your Payload Config you can define all the options to manage GraphQL.
| Option | Description |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground in production environments, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
| `disableIntrospectionInProduction` | A boolean that if false will enable the GraphQL introspection in production environments, defaults to true. |
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
| `validationRules` | A function that takes the ExecutionArgs and returns an array of ValidationRules. |
| Option | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
| `validationRules` | A function that takes the ExecutionArgs and returns an array of ValidationRules. |
## Collections

View File

@@ -53,7 +53,7 @@ export default buildConfig({
admin: {
components: {
// The `BeforeLogin` component renders a message that you see while logging into your admin panel.
// Feel free to delete this at any time. Simply remove the line below.
// Feel free to delete this at any time. Simply remove the line below and the import `BeforeLogin` statement on line 15.
beforeLogin: ['@/components/BeforeLogin'],
afterDashboard: ['@/components/AfterDashboard'],
},

View File

@@ -14,12 +14,9 @@ export const superAdminOrTenantAdminAccess: Access = ({ req }) => {
return true
}
const adminTenantAccessIDs = getUserTenantIDs(req.user, 'tenant-admin')
const requestedTenant = req?.data?.tenant
if (requestedTenant && adminTenantAccessIDs.includes(requestedTenant)) {
return true
return {
tenant: {
in: getUserTenantIDs(req.user, 'tenant-admin'),
},
}
return false
}

View File

@@ -1,6 +1,6 @@
import type { Access } from 'payload'
import type { Tenant, User } from '../../../payload-types'
import type { User } from '../../../payload-types'
import { isSuperAdmin } from '../../../access/isSuperAdmin'
import { getUserTenantIDs } from '../../../utilities/getUserTenantIDs'
@@ -14,20 +14,9 @@ export const createAccess: Access<User> = ({ req }) => {
return true
}
if (!isSuperAdmin(req.user) && req.data?.roles?.includes('super-admin')) {
return false
}
const adminTenantAccessIDs = getUserTenantIDs(req.user, 'tenant-admin')
const requestedTenants: Tenant['id'][] =
req.data?.tenants?.map((t: { tenant: Tenant['id'] }) => t.tenant) ?? []
const hasAccessToAllRequestedTenants = requestedTenants.every((tenantID) =>
adminTenantAccessIDs.includes(tenantID),
)
if (hasAccessToAllRequestedTenants) {
if (adminTenantAccessIDs.length) {
return true
}

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.45.0",
"version": "3.44.0",
"private": true,
"type": "module",
"workspaces": [
@@ -151,8 +151,8 @@
"create-payload-app": "workspace:*",
"cross-env": "7.0.3",
"dotenv": "16.4.7",
"drizzle-kit": "0.31.4",
"drizzle-orm": "0.44.2",
"drizzle-kit": "0.31.0",
"drizzle-orm": "0.43.1",
"escape-html": "^1.0.3",
"execa": "5.1.1",
"form-data": "3.0.1",
@@ -166,7 +166,7 @@
"next": "15.3.2",
"open": "^10.1.0",
"p-limit": "^5.0.0",
"pg": "8.16.3",
"pg": "8.11.3",
"playwright": "1.50.0",
"playwright-core": "1.50.0",
"prettier": "3.5.3",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/admin-bar",
"version": "3.45.0",
"version": "3.44.0",
"description": "An admin bar for React apps using Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.45.0",
"version": "3.44.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.45.0",
"version": "3.44.0",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -77,9 +77,6 @@ const relationshipSort = ({
) {
const relationshipPath = segments.slice(0, i + 1).join('.')
let sortFieldPath = segments.slice(i + 1, segments.length).join('.')
if (sortFieldPath.endsWith('.id')) {
sortFieldPath = sortFieldPath.split('.').slice(0, -1).join('.')
}
if (Array.isArray(field.relationTo)) {
throw new APIError('Not supported')
}

View File

@@ -55,7 +55,6 @@ export const updateOne: UpdateOne = async function updateOne(
try {
if (returning === false) {
await Model.updateOne(query, data, options)
transform({ adapter: this, data, fields, operation: 'read' })
return null
} else {
result = await Model.findOneAndUpdate(query, data, options)

View File

@@ -417,7 +417,7 @@ export const transform = ({
if (operation === 'read') {
delete data['__v']
data.id = data._id || data.id
data.id = data._id
delete data['_id']
if (data.id instanceof Types.ObjectId) {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.45.0",
"version": "3.44.0",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -78,9 +78,9 @@
"@payloadcms/drizzle": "workspace:*",
"@types/pg": "8.10.2",
"console-table-printer": "2.12.1",
"drizzle-kit": "0.31.4",
"drizzle-kit": "0.31.1",
"drizzle-orm": "0.44.2",
"pg": "8.16.3",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "10.0.0"

View File

@@ -37,7 +37,6 @@ import {
updateMany,
updateOne,
updateVersion,
upsert,
} from '@payloadcms/drizzle'
import {
columnToCodeConverter,
@@ -208,7 +207,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
updateMany,
updateOne,
updateVersion,
upsert,
upsert: updateOne,
})
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.45.0",
"version": "3.44.0",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -76,7 +76,7 @@
"@libsql/client": "0.14.0",
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.12.1",
"drizzle-kit": "0.31.4",
"drizzle-kit": "0.31.1",
"drizzle-orm": "0.44.2",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",

View File

@@ -38,7 +38,6 @@ import {
updateMany,
updateOne,
updateVersion,
upsert,
} from '@payloadcms/drizzle'
import { like, notLike } from 'drizzle-orm'
import { createDatabaseAdapter, defaultBeginTransaction } from 'payload'
@@ -190,7 +189,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
updateGlobalVersion,
updateOne,
updateVersion,
upsert,
upsert: updateOne,
})
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-vercel-postgres",
"version": "3.45.0",
"version": "3.44.0",
"description": "Vercel Postgres adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -78,9 +78,9 @@
"@payloadcms/drizzle": "workspace:*",
"@vercel/postgres": "^0.9.0",
"console-table-printer": "2.12.1",
"drizzle-kit": "0.31.4",
"drizzle-kit": "0.31.1",
"drizzle-orm": "0.44.2",
"pg": "8.16.3",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "10.0.0"

View File

@@ -38,7 +38,6 @@ import {
updateMany,
updateOne,
updateVersion,
upsert,
} from '@payloadcms/drizzle'
import {
columnToCodeConverter,
@@ -203,7 +202,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
updateMany,
updateOne,
updateVersion,
upsert,
upsert: updateOne,
})
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.45.0",
"version": "3.44.0",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -78,7 +78,6 @@ export { updateJobs } from './updateJobs.js'
export { updateMany } from './updateMany.js'
export { updateOne } from './updateOne.js'
export { updateVersion } from './updateVersion.js'
export { upsert } from './upsert.js'
export { upsertRow } from './upsertRow/index.js'
export { buildCreateMigration } from './utilities/buildCreateMigration.js'
export { buildIndexName } from './utilities/buildIndexName.js'

View File

@@ -24,26 +24,20 @@ export const columnToCodeConverter: ColumnToCodeConverter = ({
const columnBuilderArgsArray: string[] = []
switch (column.type) {
case 'bit':
case 'halfvec':
case 'sparsevec':
case 'vector': {
if (column.dimensions) {
columnBuilderArgsArray.push(`dimensions: ${column.dimensions}`)
}
break
if (column.type === 'timestamp') {
columnBuilderArgsArray.push(`mode: '${column.mode}'`)
if (column.withTimezone) {
columnBuilderArgsArray.push('withTimezone: true')
}
case 'timestamp': {
columnBuilderArgsArray.push(`mode: '${column.mode}'`)
if (column.withTimezone) {
columnBuilderArgsArray.push('withTimezone: true')
}
if (typeof column.precision === 'number') {
columnBuilderArgsArray.push(`precision: ${column.precision}`)
}
break
if (typeof column.precision === 'number') {
columnBuilderArgsArray.push(`precision: ${column.precision}`)
}
}
if (column.type === 'vector') {
if (column.dimensions) {
columnBuilderArgsArray.push(`dimensions: ${column.dimensions}`)
}
}

View File

@@ -1,16 +1,13 @@
import type { ForeignKeyBuilder, IndexBuilder } from 'drizzle-orm/pg-core'
import {
bit,
boolean,
foreignKey,
halfvec,
index,
integer,
jsonb,
numeric,
serial,
sparsevec,
text,
timestamp,
uniqueIndex,
@@ -47,14 +44,6 @@ export const buildDrizzleTable = ({
for (const [key, column] of Object.entries(rawTable.columns)) {
switch (column.type) {
case 'bit': {
const builder = bit(column.name, { dimensions: column.dimensions })
columns[key] = builder
break
}
case 'enum':
if ('locale' in column) {
columns[key] = adapter.enums.enum__locales(column.name)
@@ -67,21 +56,6 @@ export const buildDrizzleTable = ({
}
break
case 'halfvec': {
const builder = halfvec(column.name, { dimensions: column.dimensions })
columns[key] = builder
break
}
case 'sparsevec': {
const builder = sparsevec(column.name, { dimensions: column.dimensions })
columns[key] = builder
break
}
case 'timestamp': {
let builder = timestamp(column.name, {
mode: column.mode,

View File

@@ -281,30 +281,12 @@ export type VectorRawColumn = {
type: 'vector'
} & BaseRawColumn
export type HalfVecRawColumn = {
dimensions?: number
type: 'halfvec'
} & BaseRawColumn
export type SparseVecRawColumn = {
dimensions?: number
type: 'sparsevec'
} & BaseRawColumn
export type BinaryVecRawColumn = {
dimensions?: number
type: 'bit'
} & BaseRawColumn
export type RawColumn =
| ({
type: 'boolean' | 'geometry' | 'jsonb' | 'numeric' | 'serial' | 'text' | 'varchar'
} & BaseRawColumn)
| BinaryVecRawColumn
| EnumRawColumn
| HalfVecRawColumn
| IntegerRawColumn
| SparseVecRawColumn
| TimestampRawColumn
| UUIDRawColumn
| VectorRawColumn

View File

@@ -18,7 +18,6 @@ export const updateOne: UpdateOne = async function updateOne(
data,
joins: joinQuery,
locale,
options = { upsert: false },
req,
returning,
select,
@@ -67,13 +66,6 @@ export const updateOne: UpdateOne = async function updateOne(
}
}
if (!idToUpdate && !options.upsert) {
// TODO: In 4.0, if returning === false, we should differentiate between:
// - No document found to update
// - Document found, but returning === false
return null
}
const result = await upsertRow({
id: idToUpdate,
adapter: this,

View File

@@ -1,20 +0,0 @@
import type { Upsert } from 'payload'
import type { DrizzleAdapter } from './types.js'
export const upsert: Upsert = async function upsert(
this: DrizzleAdapter,
{ collection, data, joins, locale, req, returning, select, where },
) {
return this.updateOne({
collection,
data,
joins,
locale,
options: { upsert: true },
req,
returning,
select,
where,
})
}

View File

@@ -13,13 +13,6 @@ import { deleteExistingArrayRows } from './deleteExistingArrayRows.js'
import { deleteExistingRowsByPath } from './deleteExistingRowsByPath.js'
import { insertArrays } from './insertArrays.js'
/**
* If `id` is provided, it will update the row with that ID.
* If `where` is provided, it will update the row that matches the `where`
* If neither `id` nor `where` is provided, it will create a new row.
*
* This function replaces the entire row and does not support partial updates.
*/
export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>({
id,
adapter,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.45.0",
"version": "3.44.0",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.45.0",
"version": "3.44.0",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.45.0",
"version": "3.44.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -113,7 +113,6 @@ export function configToSchema(config: SanitizedConfig): {
variables: args.variableValues,
// onComplete: (complexity) => { console.log('Query Complexity:', complexity); },
}),
...(config.graphQL.disableIntrospectionInProduction ? [NoProductionIntrospection] : []),
...(typeof config?.graphQL?.validationRules === 'function'
? config.graphQL.validationRules(args)
: []),
@@ -124,18 +123,3 @@ export function configToSchema(config: SanitizedConfig): {
validationRules,
}
}
const NoProductionIntrospection: GraphQL.ValidationRule = (context) => ({
Field(node) {
if (process.env.NODE_ENV === 'production') {
if (node.name.value === '__schema' || node.name.value === '__type') {
context.reportError(
new GraphQL.GraphQLError(
'GraphQL introspection is not allowed, but the query contained __schema or __type',
{ nodes: [node] },
),
)
}
}
},
})

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.45.0",
"version": "3.44.0",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.45.0",
"version": "3.44.0",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.45.0",
"version": "3.44.0",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.45.0",
"version": "3.44.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -146,7 +146,7 @@ export const DefaultTemplate: React.FC<DefaultTemplateProps> = ({
return (
<EntityVisibilityProvider visibleEntities={visibleEntities}>
<BulkUploadProvider drawerSlugPrefix={collectionSlug}>
<BulkUploadProvider>
<ActionsProvider Actions={Actions}>
{RenderServerComponent({
clientProps,

View File

@@ -6,7 +6,6 @@ import type {
DocumentViewServerProps,
DocumentViewServerPropsOnly,
EditViewComponent,
LivePreviewConfig,
PayloadComponent,
RenderDocumentVersionsProperties,
} from 'payload'
@@ -92,6 +91,7 @@ export const renderDocument = async ({
payload: {
config,
config: {
admin: { livePreview: livePreviewConfig },
routes: { admin: adminRoute, api: apiRoute },
serverURL,
},
@@ -329,12 +329,6 @@ export const renderDocument = async ({
viewType,
}
const livePreviewConfig: LivePreviewConfig = {
...(config.admin.livePreview || {}),
...(collectionConfig?.admin?.livePreview || {}),
...(globalConfig?.admin?.livePreview || {}),
}
const livePreviewURL =
typeof livePreviewConfig?.url === 'function'
? await livePreviewConfig.url({

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/payload-cloud",
"version": "3.45.0",
"version": "3.44.0",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -7,11 +7,11 @@ const dirname = path.dirname(filename)
async function build() {
const resultIndex = await esbuild.build({
entryPoints: ['src/index.ts'],
entryPoints: ['src/exports/index.ts'],
bundle: true,
platform: 'node',
format: 'esm',
outfile: 'dist/index.js',
outfile: 'dist/exports/index.js',
splitting: false,
external: [
'lodash',

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.45.0",
"version": "3.44.0",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",

View File

@@ -123,7 +123,6 @@ export const addDefaultsToConfig = (config: Config): Config => {
config.endpoints = config.endpoints ?? []
config.globals = config.globals ?? []
config.graphQL = {
disableIntrospectionInProduction: true,
disablePlaygroundInProduction: true,
maxComplexity: 1000,
schemaOutputFile: `${typeof process?.cwd === 'function' ? process.cwd() : ''}/schema.graphql`,

View File

@@ -1029,17 +1029,6 @@ export type Config = {
*/
graphQL?: {
disable?: boolean
/**
* Disable introspection queries in production.
*
* @default true
*/
disableIntrospectionInProduction?: boolean
/**
* Disable the GraphQL Playground in production.
*
* @default true
*/
disablePlaygroundInProduction?: boolean
maxComplexity?: number
/**

View File

@@ -1,6 +1,5 @@
import type { Dispatcher } from 'undici'
import { lookup } from 'dns/promises'
import ipaddr from 'ipaddr.js'
import { Agent, fetch as undiciFetch } from 'undici'
@@ -25,26 +24,12 @@ const isSafeIp = (ip: string) => {
return true
}
/**
* Checks if a hostname or IP address is safe to fetch from.
* @param hostname a hostname or IP address
* @returns
*/
const isSafe = async (hostname: string) => {
try {
if (ipaddr.isValid(hostname)) {
return isSafeIp(hostname)
}
const { address } = await lookup(hostname)
return isSafeIp(address)
} catch (_ignore) {
return false
}
}
const ssrfFilterInterceptor: Dispatcher.DispatcherComposeInterceptor = (dispatch) => {
return (opts, handler) => {
const url = new URL(opts.origin?.toString() + opts.path)
if (!isSafeIp(url.hostname)) {
throw new Error(`Blocked unsafe attempt to ${url}`)
}
return dispatch(opts, handler)
}
}
@@ -55,20 +40,11 @@ const safeDispatcher = new Agent().compose(ssrfFilterInterceptor)
* A "safe" version of undici's fetch that prevents SSRF attacks.
*
* - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.
* - Validates domain names by resolving them to IP addresses and checking if they're safe.
* - Undici was used because it supported interceptors as well as "credentials: include". Native fetch
*/
export const safeFetch = async (...args: Parameters<typeof undiciFetch>) => {
const [unverifiedUrl, options] = args
const [url, options] = args
try {
const url = new URL(unverifiedUrl)
const isHostnameSafe = await isSafe(url.hostname)
if (!isHostnameSafe) {
throw new Error(`Blocked unsafe attempt to ${url.toString()}`)
}
return await undiciFetch(url, {
...options,
dispatcher: safeDispatcher,
@@ -80,13 +56,11 @@ export const safeFetch = async (...args: Parameters<typeof undiciFetch>) => {
// The desired message we want to bubble up is in the cause
throw new Error(error.cause.message)
} else {
let stringifiedUrl: string | undefined = undefined
if (typeof unverifiedUrl === 'string') {
stringifiedUrl = unverifiedUrl
} else if (unverifiedUrl instanceof URL) {
stringifiedUrl = unverifiedUrl.toString()
} else if (unverifiedUrl instanceof Request) {
stringifiedUrl = unverifiedUrl.url
let stringifiedUrl: string | undefined | URL = undefined
if (typeof url === 'string' || url instanceof URL) {
stringifiedUrl = url
} else if (url instanceof Request) {
stringifiedUrl = url.url
}
throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"version": "3.45.0",
"version": "3.44.0",
"description": "The official cloud storage plugin for Payload CMS",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-form-builder",
"version": "3.45.0",
"version": "3.44.0",
"description": "Form builder plugin for Payload CMS",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-import-export",
"version": "3.45.0",
"version": "3.44.0",
"description": "Import-Export plugin for Payload",
"keywords": [
"payload",

View File

@@ -74,12 +74,11 @@ export const Preview = () => {
return
}
const { docs, totalDocs }: { docs: Record<string, unknown>[]; totalDocs: number } =
await res.json()
const { docs, totalDocs } = await res.json()
setResultCount(limit && limit < totalDocs ? limit : totalDocs)
const allKeys = Array.from(new Set(docs.flatMap((doc) => Object.keys(doc))))
const allKeys = Object.keys(docs[0] || {})
const defaultMetaFields = ['createdAt', 'updatedAt', '_status', 'id']
// Match CSV column ordering by building keys based on fields and regex
@@ -97,10 +96,13 @@ export const Preview = () => {
})
: allKeys.filter((key) => !defaultMetaFields.includes(key))
const fieldKeys =
Array.isArray(fields) && fields.length > 0
? selectedKeys // strictly only what was selected
: [...selectedKeys, ...defaultMetaFields.filter((key) => allKeys.includes(key))]
const includedMeta = new Set(selectedKeys)
const missingMetaFields = defaultMetaFields.flatMap((field) => {
const regex = fieldToRegex(field)
return allKeys.filter((key) => regex.test(key) && !includedMeta.has(key))
})
const fieldKeys = [...selectedKeys, ...missingMetaFields]
// Build columns based on flattened keys
const newColumns: Column[] = fieldKeys.map((key) => ({

View File

@@ -111,45 +111,23 @@ export const createExport = async (args: CreateExportArgs) => {
if (download) {
if (debug) {
req.payload.logger.info('Pre-scanning all columns before streaming')
}
const allColumnsSet = new Set<string>()
const allColumns: string[] = []
let scanPage = 1
let hasMore = true
while (hasMore) {
const result = await payload.find({ ...findArgs, page: scanPage })
result.docs.forEach((doc) => {
const flat = flattenObject({ doc, fields, toCSVFunctions })
Object.keys(flat).forEach((key) => {
if (!allColumnsSet.has(key)) {
allColumnsSet.add(key)
allColumns.push(key)
}
})
})
hasMore = result.hasNextPage
scanPage += 1
}
if (debug) {
req.payload.logger.info(`Discovered ${allColumns.length} columns`)
req.payload.logger.info('Starting download stream')
}
const encoder = new TextEncoder()
let isFirstBatch = true
let streamPage = 1
let columns: string[] | undefined
let page = 1
const stream = new Readable({
async read() {
const result = await payload.find({ ...findArgs, page: streamPage })
const result = await payload.find({
...findArgs,
page,
})
if (debug) {
req.payload.logger.info(`Streaming batch ${streamPage} with ${result.docs.length} docs`)
req.payload.logger.info(`Processing batch ${page} with ${result.docs.length} documents`)
}
if (result.docs.length === 0) {
@@ -157,24 +135,19 @@ export const createExport = async (args: CreateExportArgs) => {
return
}
const batchRows = result.docs.map((doc) => flattenObject({ doc, fields, toCSVFunctions }))
const csvInput = result.docs.map((doc) => flattenObject({ doc, fields, toCSVFunctions }))
const paddedRows = batchRows.map((row) => {
const fullRow: Record<string, unknown> = {}
for (const col of allColumns) {
fullRow[col] = row[col] ?? ''
}
return fullRow
})
if (isFirstBatch) {
columns = Object.keys(csvInput[0] ?? {})
}
const csvString = stringify(paddedRows, {
const csvString = stringify(csvInput, {
header: isFirstBatch,
columns: allColumns,
columns,
})
this.push(encoder.encode(csvString))
isFirstBatch = false
streamPage += 1
if (!result.hasNextPage) {
if (debug) {
@@ -182,6 +155,8 @@ export const createExport = async (args: CreateExportArgs) => {
}
this.push(null) // End the stream
}
page += 1
},
})
@@ -193,15 +168,11 @@ export const createExport = async (args: CreateExportArgs) => {
})
}
// Non-download path (buffered export)
if (debug) {
req.payload.logger.info('Starting file generation')
}
const outputData: string[] = []
const rows: Record<string, unknown>[] = []
const columnsSet = new Set<string>()
const columns: string[] = []
let isFirstBatch = true
let page = 1
let hasNextPage = true
@@ -218,19 +189,9 @@ export const createExport = async (args: CreateExportArgs) => {
}
if (isCSV) {
const batchRows = result.docs.map((doc) => flattenObject({ doc, fields, toCSVFunctions }))
// Track discovered column keys
batchRows.forEach((row) => {
Object.keys(row).forEach((key) => {
if (!columnsSet.has(key)) {
columnsSet.add(key)
columns.push(key)
}
})
})
rows.push(...batchRows)
const csvInput = result.docs.map((doc) => flattenObject({ doc, fields, toCSVFunctions }))
outputData.push(stringify(csvInput, { header: isFirstBatch }))
isFirstBatch = false
} else {
const jsonInput = result.docs.map((doc) => JSON.stringify(doc))
outputData.push(jsonInput.join(',\n'))
@@ -240,23 +201,6 @@ export const createExport = async (args: CreateExportArgs) => {
page += 1
}
if (isCSV) {
const paddedRows = rows.map((row) => {
const fullRow: Record<string, unknown> = {}
for (const col of columns) {
fullRow[col] = row[col] ?? ''
}
return fullRow
})
outputData.push(
stringify(paddedRows, {
header: true,
columns,
}),
)
}
const buffer = Buffer.from(format === 'json' ? `[${outputData.join(',')}]` : outputData.join(''))
if (debug) {
req.payload.logger.info(`${format} file generation complete`)

View File

@@ -11,7 +11,6 @@ import { getCustomFieldFunctions } from './export/getCustomFieldFunctions.js'
import { getSelect } from './export/getSelect.js'
import { getExportCollection } from './getExportCollection.js'
import { translations } from './translations/index.js'
import { getFlattenedFieldKeys } from './utilities/getFlattenedFieldKeys.js'
export const importExportPlugin =
(pluginConfig: ImportExportPluginConfig) =>
@@ -113,23 +112,13 @@ export const importExportPlugin =
select,
})
const possibleKeys = getFlattenedFieldKeys(collection.config.fields as FlattenedField[])
const transformed = docs.map((doc) => {
const row = flattenObject({
const transformed = docs.map((doc) =>
flattenObject({
doc,
fields,
toCSVFunctions,
})
for (const key of possibleKeys) {
if (!(key in row)) {
row[key] = null
}
}
return row
})
}),
)
return Response.json({
docs: transformed,

View File

@@ -1,75 +0,0 @@
import { type FlattenedField } from 'payload'
type FieldWithPresentational =
| {
fields?: FlattenedField[]
name?: string
tabs?: {
fields: FlattenedField[]
name?: string
}[]
type: 'collapsible' | 'row' | 'tabs'
}
| FlattenedField
export const getFlattenedFieldKeys = (fields: FieldWithPresentational[], prefix = ''): string[] => {
const keys: string[] = []
fields.forEach((field) => {
if (!('name' in field) || typeof field.name !== 'string') {
return
}
const name = prefix ? `${prefix}_${field.name}` : field.name
switch (field.type) {
case 'array': {
const subKeys = getFlattenedFieldKeys(field.fields as FlattenedField[], `${name}_0`)
keys.push(...subKeys)
break
}
case 'blocks':
field.blocks.forEach((block) => {
const blockKeys = getFlattenedFieldKeys(block.fields as FlattenedField[], `${name}_0`)
keys.push(...blockKeys)
})
break
case 'collapsible':
case 'group':
case 'row':
keys.push(...getFlattenedFieldKeys(field.fields as FlattenedField[], name))
break
case 'relationship':
if (field.hasMany) {
// e.g. hasManyPolymorphic_0_value_id
keys.push(`${name}_0_relationTo`, `${name}_0_value_id`)
} else {
// e.g. hasOnePolymorphic_id
keys.push(`${name}_id`, `${name}_relationTo`)
}
break
case 'tabs':
if (field.tabs) {
field.tabs.forEach((tab) => {
if (tab.name) {
const tabPrefix = prefix ? `${prefix}_${tab.name}` : tab.name
keys.push(...getFlattenedFieldKeys(tab.fields, tabPrefix))
} else {
keys.push(...getFlattenedFieldKeys(tab.fields, prefix))
}
})
}
break
default:
if ('hasMany' in field && field.hasMany) {
// Push placeholder for first index
keys.push(`${name}_0`)
} else {
keys.push(name)
}
break
}
})
return keys
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-multi-tenant",
"version": "3.45.0",
"version": "3.44.0",
"description": "Multi Tenant plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.45.0",
"version": "3.44.0",
"description": "The official Nested Docs plugin for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.45.0",
"version": "3.44.0",
"description": "Redirects plugin for Payload",
"keywords": [
"payload",

View File

@@ -28,7 +28,6 @@ export const redirectsPlugin =
index: true,
label: 'From URL',
required: true,
unique: true,
},
{
name: 'to',

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.45.0",
"version": "3.44.0",
"description": "Search plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-sentry",
"version": "3.45.0",
"version": "3.44.0",
"description": "Sentry plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.45.0",
"version": "3.44.0",
"description": "SEO plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-stripe",
"version": "3.45.0",
"version": "3.44.0",
"description": "Stripe plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.45.0",
"version": "3.44.0",
"description": "The officially supported Lexical richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -67,16 +67,6 @@ $lexical-contenteditable-bottom-padding: 8px;
}
}
}
@include small-break {
.rich-text-lexical {
&.error {
> .rich-text-lexical__wrap {
@include lightInputError;
}
}
}
}
}
html[data-theme='dark'] {
@@ -91,15 +81,5 @@ $lexical-contenteditable-bottom-padding: 8px;
}
}
}
@include small-break {
.rich-text-lexical {
&.error {
> .rich-text-lexical__wrap {
@include darkInputError;
}
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.45.0",
"version": "3.44.0",
"description": "The officially supported Slate richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-azure",
"version": "3.45.0",
"version": "3.44.0",
"description": "Payload storage adapter for Azure Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-gcs",
"version": "3.45.0",
"version": "3.44.0",
"description": "Payload storage adapter for Google Cloud Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-s3",
"version": "3.45.0",
"version": "3.44.0",
"description": "Payload storage adapter for Amazon S3",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-uploadthing",
"version": "3.45.0",
"version": "3.44.0",
"description": "Payload storage adapter for uploadthing",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-vercel-blob",
"version": "3.45.0",
"version": "3.44.0",
"description": "Payload storage adapter for Vercel Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.45.0",
"version": "3.44.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/ui",
"version": "3.45.0",
"version": "3.44.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -52,13 +52,7 @@ export const AddNewRelation: React.FC<Props> = ({
const onSave: DocumentDrawerContextType['onSave'] = useCallback(
({ doc, operation }) => {
// if autosave is enabled, the operation will be 'update'
const isAutosaveEnabled =
typeof collectionConfig?.versions?.drafts === 'object'
? collectionConfig.versions.drafts.autosave
: false
if (operation === 'create' || (operation === 'update' && isAutosaveEnabled)) {
if (operation === 'create') {
// ensure the value is not already in the array
let isNewValue = false
if (!value) {

View File

@@ -23,7 +23,6 @@ import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { formatTimeToNow } from '../../utilities/formatDocTitle/formatDateTitle.js'
import { reduceFieldsToValuesWithValidation } from '../../utilities/reduceFieldsToValuesWithValidation.js'
import { useDocumentDrawerContext } from '../DocumentDrawer/Provider.js'
import { LeaveWithoutSaving } from '../LeaveWithoutSaving/index.js'
import './index.scss'
@@ -57,12 +56,10 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
updateSavedDocumentData,
} = useDocumentInfo()
const { onSave: onSaveFromDocumentDrawer } = useDocumentDrawerContext()
const { reportUpdate } = useDocumentEvents()
const { dispatchFields, isValid, setBackgroundProcessing, setIsValid } = useForm()
const { dispatchFields, isValid, setBackgroundProcessing, setIsValid, setSubmitted } = useForm()
const [formState] = useAllFormFields()
const [fields] = useAllFormFields()
const modified = useFormModified()
const submitted = useFormSubmitted()
@@ -81,27 +78,32 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
)
const [_saving, setSaving] = useState(false)
const saving = useDeferredValue(_saving)
const debouncedFormState = useDebounce(formState, interval)
const formStateRef = useRef(formState)
const debouncedFields = useDebounce(fields, interval)
const fieldRef = useRef(fields)
const modifiedRef = useRef(modified)
const localeRef = useRef(locale)
/**
* Track the validation internally so Autosave can determine when to run queue processing again
* Helps us prevent infinite loops when the queue is processing and the form is invalid
*/
const isValidRef = useRef(isValid)
// Store fields in ref so the autosave func
// can always retrieve the most to date copies
// after the timeout has executed
formStateRef.current = formState
fieldRef.current = fields
// Store modified in ref so the autosave func
// can bail out if modified becomes false while
// timing out during autosave
modifiedRef.current = modified
// Store locale in ref so the autosave func
// can always retrieve the most to date locale
localeRef.current = locale
const { queueTask } = useQueues()
@@ -153,14 +155,14 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
if (url) {
if (modifiedRef.current) {
const { data, valid } = reduceFieldsToValuesWithValidation(formStateRef.current, true)
const { data, valid } = reduceFieldsToValuesWithValidation(fieldRef.current, true)
data._status = 'draft'
const skipSubmission =
submitted && !valid && versionsConfig?.drafts && versionsConfig?.drafts?.validate
if (!skipSubmission) {
if (!skipSubmission && isValidRef.current) {
let res
try {
@@ -181,8 +183,6 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
// We need to log the time in order to figure out if we need to trigger the state off later
endTimestamp = newDate.getTime()
const json = await res.json()
if (res.status === 200) {
setLastUpdateTime(newDate.getTime())
@@ -192,20 +192,13 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
updatedAt: newDate.toISOString(),
})
// if onSaveFromDocumentDrawer is defined, call it
if (typeof onSaveFromDocumentDrawer === 'function') {
void onSaveFromDocumentDrawer({
...json,
operation: 'update',
})
}
if (!mostRecentVersionIsAutosaved) {
incrementVersionCount()
setMostRecentVersionIsAutosaved(true)
setUnpublishedVersionCount((prev) => prev + 1)
}
}
const json = await res.json()
if (versionsConfig?.drafts && versionsConfig?.drafts?.validate && json?.errors) {
if (Array.isArray(json.errors)) {
@@ -245,7 +238,10 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
toast.error(err.message || i18n.t('error:unknown'))
})
// Set valid to false internally so the queue doesn't process
isValidRef.current = false
setIsValid(false)
setSubmitted(true)
hideIndicator()
return
}
@@ -256,6 +252,9 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
// Manually update the data since this function doesn't fire the `submit` function from useForm
if (document) {
setIsValid(true)
// Reset internal state allowing the queue to process
isValidRef.current = true
updateSavedDocumentData(document)
}
}
@@ -271,6 +270,11 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
setBackgroundProcessing(false)
},
beforeProcess: () => {
if (!isValidRef.current) {
isValidRef.current = true
return false
}
setBackgroundProcessing(true)
},
},
@@ -278,8 +282,7 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
})
const didMount = useRef(false)
const previousDebouncedData = useRef(reduceFieldsToValues(debouncedFormState))
const previousDebouncedFieldValues = useRef(reduceFieldsToValues(debouncedFields))
// When debounced fields change, autosave
useEffect(() => {
/**
@@ -292,19 +295,16 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
/**
* Ensure autosave only runs if the form data changes, not every time the entire form state changes
* Remove `updatedAt` from comparison as it changes on every autosave interval.
*/
const { updatedAt: _, ...formData } = reduceFieldsToValues(debouncedFormState)
const { updatedAt: __, ...prevFormData } = previousDebouncedData.current
if (dequal(formData, prevFormData)) {
const debouncedFieldValues = reduceFieldsToValues(debouncedFields)
if (dequal(debouncedFieldValues, previousDebouncedFieldValues)) {
return
}
previousDebouncedData.current = formData
previousDebouncedFieldValues.current = debouncedFieldValues
handleAutosave()
}, [debouncedFormState])
}, [debouncedFields])
/**
* If component unmounts, clear the autosave timeout

View File

@@ -1,5 +1,7 @@
'use client'
import { useRouter, useSearchParams } from 'next/navigation.js'
import { formatAdminURL } from 'payload/shared'
import React, { useCallback, useEffect } from 'react'
import type { EditFormProps } from './types.js'
@@ -10,7 +12,9 @@ import { WatchChildErrors } from '../../../forms/WatchChildErrors/index.js'
import { useConfig } from '../../../providers/Config/index.js'
import { useDocumentEvents } from '../../../providers/DocumentEvents/index.js'
import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js'
import { useEditDepth } from '../../../providers/EditDepth/index.js'
import { OperationProvider } from '../../../providers/Operation/index.js'
import { useRouteTransition } from '../../../providers/RouteTransition/index.js'
import { useServerFunctions } from '../../../providers/ServerFunctions/index.js'
import { abortAndIgnore, handleAbortRef } from '../../../utilities/abortAndIgnore.js'
import { useDocumentDrawerContext } from '../../DocumentDrawer/Provider.js'
@@ -19,6 +23,7 @@ import { MoveDocToFolder } from '../../FolderView/MoveDocToFolder/index.js'
import { Upload_v4 } from '../../Upload/index.js'
import { useFormsManager } from '../FormsManager/index.js'
import './index.scss'
import { BulkUploadProvider } from '../index.js'
const baseClass = 'collection-edit'
@@ -39,6 +44,7 @@ export function EditForm({
getDocPreferences,
hasSavePermission,
initialState,
isEditing,
isInitializing,
Upload: CustomUpload,
} = useDocumentInfo()
@@ -48,14 +54,23 @@ export function EditForm({
const { getFormState } = useServerFunctions()
const {
config: { folders },
config: {
folders,
routes: { admin: adminRoute },
},
getEntityConfig,
} = useConfig()
const abortOnChangeRef = React.useRef<AbortController>(null)
const collectionConfig = getEntityConfig({ collectionSlug: docSlug })
const router = useRouter()
const depth = useEditDepth()
const params = useSearchParams()
const { reportUpdate } = useDocumentEvents()
const { startRouteTransition } = useRouteTransition()
const locale = params.get('locale')
const collectionSlug = collectionConfig.slug
@@ -74,9 +89,31 @@ export function EditForm({
operation: 'create',
})
}
resetUploadEdits()
if (!isEditing && depth < 2) {
// Redirect to the same locale if it's been set
const redirectRoute = formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/${json?.doc?.id}${locale ? `?locale=${locale}` : ''}`,
})
startRouteTransition(() => router.push(redirectRoute))
} else {
resetUploadEdits()
}
},
[collectionSlug, onSaveFromContext, reportUpdate, resetUploadEdits],
[
adminRoute,
collectionSlug,
depth,
isEditing,
locale,
onSaveFromContext,
reportUpdate,
resetUploadEdits,
router,
startRouteTransition,
],
)
const onChange: NonNullable<FormProps['onChange']>[0] = useCallback(
@@ -113,52 +150,54 @@ export function EditForm({
return (
<OperationProvider operation="create">
<Form
action={action}
className={`${baseClass}__form`}
disabled={isInitializing || !hasSavePermission}
initialState={isInitializing ? undefined : initialState}
isInitializing={isInitializing}
method="POST"
onChange={[onChange]}
onSuccess={onSave}
submitted={submitted}
>
<DocumentFields
BeforeFields={
<React.Fragment>
{CustomUpload || (
<Upload_v4
collectionSlug={collectionConfig.slug}
customActions={[
folders && collectionConfig.folders && (
<MoveDocToFolder
buttonProps={{
buttonStyle: 'pill',
size: 'small',
}}
folderCollectionSlug={folders.slug}
folderFieldName={folders.fieldName}
key="move-doc-to-folder"
/>
),
].filter(Boolean)}
initialState={initialState}
resetUploadEdits={resetUploadEdits}
updateUploadEdits={updateUploadEdits}
uploadConfig={collectionConfig.upload}
uploadEdits={uploadEdits}
/>
)}
</React.Fragment>
}
docPermissions={docPermissions}
fields={collectionConfig.fields}
schemaPathSegments={[collectionConfig.slug]}
/>
<ReportAllErrors />
<GetFieldProxy />
</Form>
<BulkUploadProvider>
<Form
action={action}
className={`${baseClass}__form`}
disabled={isInitializing || !hasSavePermission}
initialState={isInitializing ? undefined : initialState}
isInitializing={isInitializing}
method="POST"
onChange={[onChange]}
onSuccess={onSave}
submitted={submitted}
>
<DocumentFields
BeforeFields={
<React.Fragment>
{CustomUpload || (
<Upload_v4
collectionSlug={collectionConfig.slug}
customActions={[
folders && collectionConfig.folders && (
<MoveDocToFolder
buttonProps={{
buttonStyle: 'pill',
size: 'small',
}}
folderCollectionSlug={folders.slug}
folderFieldName={folders.fieldName}
key="move-doc-to-folder"
/>
),
].filter(Boolean)}
initialState={initialState}
resetUploadEdits={resetUploadEdits}
updateUploadEdits={updateUploadEdits}
uploadConfig={collectionConfig.upload}
uploadEdits={uploadEdits}
/>
)}
</React.Fragment>
}
docPermissions={docPermissions}
fields={collectionConfig.fields}
schemaPathSegments={[collectionConfig.slug]}
/>
<ReportAllErrors />
<GetFieldProxy />
</Form>
</BulkUploadProvider>
</OperationProvider>
)
}

View File

@@ -11,11 +11,9 @@ import type { FormProps } from '../../../forms/Form/index.js'
import type { OnFieldSelect } from '../../FieldSelect/index.js'
import type { FieldOption } from '../../FieldSelect/reduceFieldOptions.js'
import type { State } from '../FormsManager/reducer.js'
import type { EditManyBulkUploadsProps } from './index.js'
import { Button } from '../../../elements/Button/index.js'
import { Form } from '../../../forms/Form/index.js'
import { FieldPathContext } from '../../../forms/RenderFields/context.js'
import { RenderField } from '../../../forms/RenderFields/RenderField.js'
import { XIcon } from '../../../icons/X/index.js'
import { useAuth } from '../../../providers/Auth/index.js'
@@ -24,7 +22,7 @@ import { useTranslation } from '../../../providers/Translation/index.js'
import { abortAndIgnore, handleAbortRef } from '../../../utilities/abortAndIgnore.js'
import { FieldSelect } from '../../FieldSelect/index.js'
import { useFormsManager } from '../FormsManager/index.js'
import { baseClass } from './index.js'
import { baseClass, type EditManyBulkUploadsProps } from './index.js'
import './index.scss'
import '../../../forms/RenderFields/index.scss'
@@ -171,25 +169,23 @@ export const EditManyBulkUploadsDrawerContent: React.FC<
/>
{selectedFields.length === 0 ? null : (
<div className="render-fields">
<FieldPathContext value={undefined}>
{selectedFields.map((option, i) => {
const {
value: { field, fieldPermissions, path },
} = option
{selectedFields.map((option, i) => {
const {
value: { field, fieldPermissions, path },
} = option
return (
<RenderField
clientFieldConfig={field}
indexPath=""
key={`${path}-${i}`}
parentPath=""
parentSchemaPath=""
path={path}
permissions={fieldPermissions}
/>
)
})}
</FieldPathContext>
return (
<RenderField
clientFieldConfig={field}
indexPath=""
key={`${path}-${i}`}
parentPath=""
parentSchemaPath=""
path={path}
permissions={fieldPermissions}
/>
)
})}
</div>
)}
<div className={`${baseClass}__sidebar-wrap`}>

View File

@@ -118,7 +118,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
const { toggleLoadingOverlay } = useLoadingOverlay()
const { closeModal } = useModal()
const { collectionSlug, drawerSlug, initialFiles, onSuccess, setInitialFiles } = useBulkUpload()
const { collectionSlug, drawerSlug, initialFiles, onSuccess } = useBulkUpload()
const [isUploading, setIsUploading] = React.useState(false)
const [loadingText, setLoadingText] = React.useState('')
@@ -366,10 +366,13 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
setIsUploading(false)
const remainingForms = []
const thumbnailIndexesToRemove = []
currentForms.forEach(({ errorCount }, i) => {
if (errorCount) {
remainingForms.push(currentForms[i])
} else {
thumbnailIndexesToRemove.push(i)
}
})
@@ -398,13 +401,8 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
totalErrorCount: remainingForms.reduce((acc, { errorCount }) => acc + errorCount, 0),
},
})
if (remainingForms.length === 0) {
setInitialFiles(undefined)
}
},
[
setInitialFiles,
actionURL,
collectionSlug,
getUploadHandler,

View File

@@ -8,7 +8,6 @@ import React from 'react'
import { toast } from 'sonner'
import { useConfig } from '../../providers/Config/index.js'
import { EditDepthProvider } from '../../providers/EditDepth/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { UploadControlsProvider } from '../../providers/UploadControls/index.js'
import { Drawer, useDrawerDepth } from '../Drawer/index.js'
@@ -78,9 +77,7 @@ export function BulkUploadDrawer() {
<Drawer gutter={false} Header={null} slug={drawerSlug}>
<FormsManagerProvider>
<UploadControlsProvider>
<EditDepthProvider>
<DrawerContent />
</EditDepthProvider>
<DrawerContent />
</UploadControlsProvider>
</FormsManagerProvider>
</Drawer>
@@ -89,57 +86,61 @@ export function BulkUploadDrawer() {
type BulkUploadContext = {
collectionSlug: string
currentActivePath: string
drawerSlug: string
initialFiles: FileList
maxFiles: number
onCancel: () => void
onSuccess: (newDocs: JsonObject[], errorCount: number) => void
setCollectionSlug: (slug: string) => void
setCurrentActivePath: (path: string) => void
setInitialFiles: (files: FileList) => void
setMaxFiles: (maxFiles: number) => void
setOnCancel: (onCancel: BulkUploadContext['onCancel']) => void
setOnSuccess: (onSuccess: BulkUploadContext['onSuccess']) => void
setOnSuccess: (path: string, onSuccess: BulkUploadContext['onSuccess']) => void
}
const Context = React.createContext<BulkUploadContext>({
collectionSlug: '',
currentActivePath: undefined,
drawerSlug: '',
initialFiles: undefined,
maxFiles: undefined,
onCancel: () => null,
onSuccess: () => null,
setCollectionSlug: () => null,
setCurrentActivePath: () => null,
setInitialFiles: () => null,
setMaxFiles: () => null,
setOnCancel: () => null,
setOnSuccess: () => null,
})
export function BulkUploadProvider({
children,
drawerSlugPrefix,
}: {
readonly children: React.ReactNode
readonly drawerSlugPrefix?: string
}) {
export function BulkUploadProvider({ children }: { readonly children: React.ReactNode }) {
const [collection, setCollection] = React.useState<string>()
const [onSuccessFunction, setOnSuccessFunction] = React.useState<BulkUploadContext['onSuccess']>()
const [onSuccessFunctionMap, setOnSuccessFunctionMap] =
React.useState<Record<string, BulkUploadContext['onSuccess']>>()
const [onCancelFunction, setOnCancelFunction] = React.useState<BulkUploadContext['onCancel']>()
const [initialFiles, setInitialFiles] = React.useState<FileList>(undefined)
const [maxFiles, setMaxFiles] = React.useState<number>(undefined)
const drawerSlug = `${drawerSlugPrefix ? `${drawerSlugPrefix}-` : ''}${useBulkUploadDrawerSlug()}`
const [currentActivePath, setCurrentActivePath] = React.useState<string>(undefined)
const drawerSlug = useBulkUploadDrawerSlug()
const setCollectionSlug: BulkUploadContext['setCollectionSlug'] = (slug) => {
setCollection(slug)
}
const setOnSuccess: BulkUploadContext['setOnSuccess'] = (onSuccess) => {
setOnSuccessFunction(() => onSuccess)
}
const setOnSuccess: BulkUploadContext['setOnSuccess'] = React.useCallback((path, onSuccess) => {
setOnSuccessFunctionMap((prev) => ({
...prev,
[path]: onSuccess,
}))
}, [])
return (
<Context
value={{
collectionSlug: collection,
currentActivePath,
drawerSlug,
initialFiles,
maxFiles,
@@ -149,11 +150,13 @@ export function BulkUploadProvider({
}
},
onSuccess: (docIDs, errorCount) => {
if (typeof onSuccessFunction === 'function') {
if (onSuccessFunctionMap && Object.hasOwn(onSuccessFunctionMap, currentActivePath)) {
const onSuccessFunction = onSuccessFunctionMap[currentActivePath]
onSuccessFunction(docIDs, errorCount)
}
},
setCollectionSlug,
setCurrentActivePath,
setInitialFiles,
setMaxFiles,
setOnCancel: setOnCancelFunction,

View File

@@ -42,16 +42,14 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
const [DocumentView, setDocumentView] = useState<React.ReactNode>(undefined)
const [isLoading, setIsLoading] = useState(true)
const hasInitialized = useRef(false)
const hasRenderedDocument = useRef(false)
const getDocumentView = useCallback(
(docID?: number | string, showLoadingIndicator: boolean = false) => {
(docID?: number | string) => {
const controller = handleAbortRef(abortGetDocumentViewRef)
const fetchDocumentView = async () => {
if (showLoadingIndicator) {
setIsLoading(true)
}
setIsLoading(true)
try {
const result = await renderDocument({
@@ -143,13 +141,13 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
)
const clearDoc = useCallback(() => {
getDocumentView(undefined, true)
getDocumentView()
}, [getDocumentView])
useEffect(() => {
if (!DocumentView && !hasInitialized.current) {
getDocumentView(existingDocID, true)
hasInitialized.current = true
if (!DocumentView && !hasRenderedDocument.current) {
getDocumentView(existingDocID)
hasRenderedDocument.current = true
}
}, [DocumentView, getDocumentView, existingDocID])

View File

@@ -63,7 +63,7 @@ export const StaticFileDetails: React.FC<StaticFileDetailsProps> = (props) => {
width={width as number}
/>
{(enableAdjustments || (hasImageSizes && doc.filename) || customUploadActions) && (
{(enableAdjustments || customUploadActions) && (
<UploadActions
customActions={customUploadActions}
enableAdjustments={Boolean(enableAdjustments)}

View File

@@ -27,7 +27,12 @@ export function ListBulkUploadButton({
*/
openBulkUpload?: () => void
}) {
const { drawerSlug: bulkUploadDrawerSlug, setCollectionSlug, setOnSuccess } = useBulkUpload()
const {
drawerSlug: bulkUploadDrawerSlug,
setCollectionSlug,
setCurrentActivePath,
setOnSuccess,
} = useBulkUpload()
const { t } = useTranslation()
const { openModal } = useModal()
const router = useRouter()
@@ -37,8 +42,9 @@ export function ListBulkUploadButton({
openBulkUploadFromProps()
} else {
setCollectionSlug(collectionSlug)
setCurrentActivePath(collectionSlug)
openModal(bulkUploadDrawerSlug)
setOnSuccess(() => {
setOnSuccess(collectionSlug, () => {
if (typeof onBulkUploadSuccess === 'function') {
onBulkUploadSuccess()
} else {
@@ -52,6 +58,7 @@ export function ListBulkUploadButton({
bulkUploadDrawerSlug,
openModal,
setCollectionSlug,
setCurrentActivePath,
setOnSuccess,
onBulkUploadSuccess,
openBulkUploadFromProps,

View File

@@ -304,16 +304,15 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {
setUploadStatus('failed')
}
}, [
api,
collectionSlug,
fileUrl,
uploadConfig,
setUploadStatus,
handleFileChange,
useServerSideFetch,
collectionSlug,
id,
serverURL,
setUploadStatus,
uploadConfig,
uploadControlFileName,
useServerSideFetch,
api,
])
useEffect(() => {

View File

@@ -185,7 +185,7 @@ export const WhereBuilder: React.FC<WhereBuilderProps> = (props) => {
onClick={async () => {
await addCondition({
andIndex: 0,
field: reducedFields.find((field) => !field.field.admin?.disableListFilter),
field: reducedFields[0],
orIndex: conditions.length,
relation: 'or',
})

View File

@@ -779,7 +779,7 @@ export const RelationshipInput: React.FC<RelationshipInputProps> = (props) => {
// and when the devtools are closed. Temporary solution, we can probably do better.
setTimeout(() => {
openListDrawer()
}, 100)
}, 50)
} else if (appearance === 'select') {
setMenuIsOpen(true)
if (!hasLoadedFirstPageRef.current) {

View File

@@ -118,8 +118,14 @@ export function UploadInput(props: UploadInputProps) {
)
const { openModal } = useModal()
const { drawerSlug, setCollectionSlug, setInitialFiles, setMaxFiles, setOnSuccess } =
useBulkUpload()
const {
drawerSlug,
setCollectionSlug,
setCurrentActivePath,
setInitialFiles,
setMaxFiles,
setOnSuccess,
} = useBulkUpload()
const { permissions } = useAuth()
const { code } = useLocale()
const { i18n, t } = useTranslation()
@@ -288,6 +294,7 @@ export function UploadInput(props: UploadInputProps) {
if (typeof maxRows === 'number') {
setMaxFiles(maxRows)
}
setCurrentActivePath(path)
openModal(drawerSlug)
},
[
@@ -299,6 +306,8 @@ export function UploadInput(props: UploadInputProps) {
setInitialFiles,
maxRows,
setMaxFiles,
path,
setCurrentActivePath,
],
)
@@ -452,7 +461,7 @@ export function UploadInput(props: UploadInputProps) {
}, [populateDocs, activeRelationTo, value])
useEffect(() => {
setOnSuccess(onUploadSuccess)
setOnSuccess(path, onUploadSuccess)
}, [value, path, onUploadSuccess, setOnSuccess])
const showDropzone =

View File

@@ -4,13 +4,12 @@ import type { UploadFieldClientProps } from 'payload'
import React, { useMemo } from 'react'
import { BulkUploadProvider } from '../../elements/BulkUpload/index.js'
import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js'
import { useConfig } from '../../providers/Config/index.js'
import './index.scss'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import { UploadInput } from './Input.js'
import './index.scss'
export { UploadInput } from './Input.js'
export type { UploadInputProps } from './Input.js'
@@ -63,35 +62,33 @@ export function UploadComponent(props: UploadFieldClientProps) {
const styles = useMemo(() => mergeFieldStyles(field), [field])
return (
<BulkUploadProvider drawerSlugPrefix={pathFromProps}>
<UploadInput
AfterInput={AfterInput}
allowCreate={allowCreate !== false}
api={config.routes.api}
BeforeInput={BeforeInput}
className={className}
Description={Description}
description={description}
displayPreview={displayPreview}
Error={Error}
filterOptions={filterOptions}
hasMany={hasMany}
isSortable={isSortable}
label={label}
Label={Label}
localized={localized}
maxRows={maxRows}
onChange={setValue}
path={path}
readOnly={readOnly || disabled}
relationTo={relationTo}
required={required}
serverURL={config.serverURL}
showError={showError}
style={styles}
value={value}
/>
</BulkUploadProvider>
<UploadInput
AfterInput={AfterInput}
allowCreate={allowCreate !== false}
api={config.routes.api}
BeforeInput={BeforeInput}
className={className}
Description={Description}
description={description}
displayPreview={displayPreview}
Error={Error}
filterOptions={filterOptions}
hasMany={hasMany}
isSortable={isSortable}
label={label}
Label={Label}
localized={localized}
maxRows={maxRows}
onChange={setValue}
path={path}
readOnly={readOnly || disabled}
relationTo={relationTo}
required={required}
serverURL={config.serverURL}
showError={showError}
style={styles}
value={value}
/>
)
}

View File

@@ -96,10 +96,31 @@ export const useField = <TValue,>(options?: Options): FieldType<TValue> => {
})
if (!disableModifyingForm) {
setModified(true)
if (typeof setModified === 'function') {
// Only update setModified to true if the form is not already set to modified. Otherwise the following could happen:
// 1. Text field: someone types in it in an unmodified form
// 2. After setTimeout triggers setModified(true): form is set to modified. Save Button becomes available. Good!
// 3. Type something in text field
// 4. Click on save button before setTimeout in useField has finished (so setModified(true) has not been run yet)
// 5. Form is saved, setModified(false) is set in the Form/index.tsx `submit` function, "saved successfully" toast appears
// 6. setModified(true) inside the timeout is run, form is set to modified again, even though it was already saved and thus set to unmodified. Bad! This should have happened before the form is saved. Now the form should be unmodified and stay that way
// until a NEW change happens. Due to this, the "Leave without saving" modal appears even though it should not when leaving the page fast immediately after saving the document.
// This is only an issue for forms which have already been set to modified true, as that causes the save button to be enabled. If we prevent this setTimeout to be run
// for already-modified forms first place (which is unnecessary), we can avoid this issue. As for unmodified forms, this race issue will not happen, because you cannot click the save button faster
// than the timeout in useField is run. That's because the save button won't even be enabled for clicking until the setTimeout in useField has run.
// This fixes e2e test flakes, as e2e tests were often so fast that they were saving the form before the timeout in useField has run.
// Specifically, this fixes the 'should not warn about unsaved changes when navigating to lexical editor with blocks node and then leaving the page after making a change and saving' lexical e2e test.
if (modified === false) {
// Update modified state after field value comes back
// to avoid cursor jump caused by state value / DOM mismatch
setTimeout(() => {
setModified(true)
}, 10)
}
}
}
},
[setModified, path, dispatchField, disableFormData, hasRows],
[setModified, path, dispatchField, disableFormData, hasRows, modified],
)
// Store result from hook as ref

View File

@@ -1,28 +0,0 @@
import { useCallback, useEffect, useRef, useState } from 'react'
/**
* A hook for managing state that can be controlled by props but also overridden locally.
* Props always take precedence if they change, but local state can override them temporarily.
*/
export function useControllableState<T>(
propValue: T,
defaultValue?: T,
): [T, (value: ((prev: T) => T) | T) => void] {
const [localValue, setLocalValue] = useState<T>(propValue ?? defaultValue)
const initialRenderRef = useRef(true)
useEffect(() => {
if (initialRenderRef.current) {
initialRenderRef.current = false
return
}
setLocalValue(propValue)
}, [propValue])
const setValue = useCallback((value: ((prev: T) => T) | T) => {
setLocalValue(value)
}, [])
return [localValue, setValue]
}

View File

@@ -14,7 +14,7 @@ type QueuedTaskOptions = {
* Can also be used to perform side effects before processing the queue
* @returns {boolean} If `false`, the queue will not process
*/
beforeProcess?: () => boolean | void
beforeProcess?: () => boolean
}
type QueueTask = (fn: QueuedFunction, options?: QueuedTaskOptions) => void

View File

@@ -1,10 +1,9 @@
'use client'
import type { ClientUser, DocumentPreferences } from 'payload'
import type { ClientUser, DocumentPreferences, SanitizedDocumentPermissions } from 'payload'
import * as qs from 'qs-esm'
import React, { createContext, use, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useControllableState } from '../../hooks/useControllableState.js'
import { useAuth } from '../../providers/Auth/index.js'
import { requests } from '../../utilities/api.js'
import { formatDocTitle } from '../../utilities/formatDocTitle/index.js'
@@ -46,11 +45,12 @@ const DocumentInfo: React.FC<
versionCount: versionCountFromProps,
} = props
const [docPermissions, setDocPermissions] = useControllableState(docPermissionsFromProps)
const [docPermissions, setDocPermissions] =
useState<SanitizedDocumentPermissions>(docPermissionsFromProps)
const [hasSavePermission, setHasSavePermission] = useControllableState(hasSavePermissionFromProps)
const [hasSavePermission, setHasSavePermission] = useState<boolean>(hasSavePermissionFromProps)
const [hasPublishPermission, setHasPublishPermission] = useControllableState(
const [hasPublishPermission, setHasPublishPermission] = useState<boolean>(
hasPublishPermissionFromProps,
)
@@ -101,24 +101,15 @@ const DocumentInfo: React.FC<
unpublishedVersionCountFromProps,
)
const [documentIsLocked, setDocumentIsLocked] = useControllableState<boolean | undefined>(
isLockedFromProps,
)
const [currentEditor, setCurrentEditor] = useControllableState<ClientUser | null>(
currentEditorFromProps,
)
const [lastUpdateTime, setLastUpdateTime] = useControllableState<number>(lastUpdateTimeFromProps)
const [savedDocumentData, setSavedDocumentData] = useControllableState(initialData)
const [uploadStatus, setUploadStatus] = useControllableState<'failed' | 'idle' | 'uploading'>(
'idle',
)
const [documentIsLocked, setDocumentIsLocked] = useState<boolean | undefined>(isLockedFromProps)
const [currentEditor, setCurrentEditor] = useState<ClientUser | null>(currentEditorFromProps)
const [lastUpdateTime, setLastUpdateTime] = useState<number>(lastUpdateTimeFromProps)
const [savedDocumentData, setSavedDocumentData] = useState(initialData)
const [uploadStatus, setUploadStatus] = useState<'failed' | 'idle' | 'uploading'>('idle')
const updateUploadStatus = useCallback(
(status: 'failed' | 'idle' | 'uploading') => {
setUploadStatus(status)
},
[setUploadStatus],
)
const updateUploadStatus = useCallback((status: 'failed' | 'idle' | 'uploading') => {
setUploadStatus(status)
}, [])
const { getPreference, setPreference } = usePreferences()
const { code: locale } = useLocale()
@@ -179,7 +170,7 @@ const DocumentInfo: React.FC<
console.error('Failed to unlock the document', error)
}
},
[serverURL, api, globalSlug, setDocumentIsLocked],
[serverURL, api, globalSlug],
)
const updateDocumentEditor = useCallback(
@@ -288,7 +279,7 @@ const DocumentInfo: React.FC<
(json) => {
setSavedDocumentData(json)
},
[setSavedDocumentData],
[],
)
/**

View File

@@ -226,13 +226,6 @@ export const LivePreviewProvider: React.FC<LivePreviewProviderProps> = ({
)
}, [isLivePreviewing, setPreference, collectionSlug, globalSlug])
const isLivePreviewEnabled = Boolean(
operation !== 'create' &&
((collectionSlug && config?.admin?.livePreview?.collections?.includes(collectionSlug)) ||
(globalSlug && config.admin?.livePreview?.globals?.includes(globalSlug)) ||
entityConfig?.admin?.livePreview),
)
return (
<LivePreviewContext
value={{
@@ -242,7 +235,13 @@ export const LivePreviewProvider: React.FC<LivePreviewProviderProps> = ({
fieldSchemaJSON,
iframeHasLoaded,
iframeRef,
isLivePreviewEnabled,
isLivePreviewEnabled: Boolean(
(operation !== 'create' &&
collectionSlug &&
config?.admin?.livePreview?.collections?.includes(collectionSlug)) ||
(globalSlug && config.admin?.livePreview?.globals?.includes(globalSlug)) ||
entityConfig?.admin?.livePreview,
),
isLivePreviewing,
isPopupOpen,
listeningForMessages,

View File

@@ -69,13 +69,12 @@ export const Auth: React.FC<Props> = (props) => {
})
if (operation === 'create') {
showPasswordFields =
passwordPermissions === true ||
(typeof passwordPermissions === 'object' && passwordPermissions.create)
showPasswordFields = typeof passwordPermissions === 'object' && passwordPermissions.create
} else {
showPasswordFields =
passwordPermissions === true ||
(typeof passwordPermissions === 'object' && passwordPermissions.update)
typeof passwordPermissions === 'object' &&
passwordPermissions.read &&
passwordPermissions.update
}
}

View File

@@ -86,7 +86,12 @@ export function DefaultListView(props: ListViewClientProps) {
} = useListQuery()
const { openModal } = useModal()
const { drawerSlug: bulkUploadDrawerSlug, setCollectionSlug, setOnSuccess } = useBulkUpload()
const {
drawerSlug: bulkUploadDrawerSlug,
setCollectionSlug,
setCurrentActivePath,
setOnSuccess,
} = useBulkUpload()
const collectionConfig = getEntityConfig({ collectionSlug })
@@ -119,9 +124,18 @@ export function DefaultListView(props: ListViewClientProps) {
const openBulkUpload = React.useCallback(() => {
setCollectionSlug(collectionSlug)
setCurrentActivePath(collectionSlug)
openModal(bulkUploadDrawerSlug)
setOnSuccess(() => router.refresh())
}, [router, collectionSlug, bulkUploadDrawerSlug, openModal, setCollectionSlug, setOnSuccess])
setOnSuccess(collectionSlug, () => router.refresh())
}, [
router,
collectionSlug,
bulkUploadDrawerSlug,
openModal,
setCollectionSlug,
setCurrentActivePath,
setOnSuccess,
])
useEffect(() => {
if (!isInDrawer) {

332
pnpm-lock.yaml generated
View File

@@ -44,7 +44,7 @@ importers:
version: 1.50.0
'@sentry/nextjs':
specifier: ^8.33.1
version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.2(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29))
version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.2(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))
'@sentry/node':
specifier: ^8.33.1
version: 8.37.1
@@ -97,11 +97,11 @@ importers:
specifier: 16.4.7
version: 16.4.7
drizzle-kit:
specifier: 0.31.4
version: 0.31.4
specifier: 0.31.0
version: 0.31.0
drizzle-orm:
specifier: 0.44.2
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8))(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(gel@2.0.1)(pg@8.16.3)
specifier: 0.43.1
version: 0.43.1(@libsql/client@0.14.0(bufferutil@4.0.8))(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(pg@8.11.3)
escape-html:
specifier: ^1.0.3
version: 1.0.3
@@ -142,8 +142,8 @@ importers:
specifier: ^5.0.0
version: 5.0.0
pg:
specifier: 8.16.3
version: 8.16.3
specifier: 8.11.3
version: 8.11.3
playwright:
specifier: 1.50.0
version: 1.50.0
@@ -319,14 +319,14 @@ importers:
specifier: 2.12.1
version: 2.12.1
drizzle-kit:
specifier: 0.31.4
version: 0.31.4
specifier: 0.31.1
version: 0.31.1
drizzle-orm:
specifier: 0.44.2
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(gel@2.0.1)(pg@8.16.3)
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)
pg:
specifier: 8.16.3
version: 8.16.3
specifier: 8.11.3
version: 8.11.3
prompts:
specifier: 2.4.2
version: 2.4.2
@@ -365,11 +365,11 @@ importers:
specifier: 2.12.1
version: 2.12.1
drizzle-kit:
specifier: 0.31.4
version: 0.31.4
specifier: 0.31.1
version: 0.31.1
drizzle-orm:
specifier: 0.44.2
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(gel@2.0.1)(pg@8.16.3)
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)
prompts:
specifier: 2.4.2
version: 2.4.2
@@ -408,14 +408,14 @@ importers:
specifier: 2.12.1
version: 2.12.1
drizzle-kit:
specifier: 0.31.4
version: 0.31.4
specifier: 0.31.1
version: 0.31.1
drizzle-orm:
specifier: 0.44.2
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(gel@2.0.1)(pg@8.16.3)
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)
pg:
specifier: 8.16.3
version: 8.16.3
specifier: 8.11.3
version: 8.11.3
prompts:
specifier: 2.4.2
version: 2.4.2
@@ -455,7 +455,7 @@ importers:
version: 2.0.3
drizzle-orm:
specifier: 0.44.2
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(gel@2.0.1)(pg@8.16.3)
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)
prompts:
specifier: 2.4.2
version: 2.4.2
@@ -1143,7 +1143,7 @@ importers:
dependencies:
'@sentry/nextjs':
specifier: ^8.33.1
version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29))
version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))
'@sentry/types':
specifier: ^8.33.1
version: 8.37.1
@@ -2045,7 +2045,7 @@ importers:
version: link:../packages/ui
'@sentry/nextjs':
specifier: ^8.33.1
version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.2(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29))
version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.2(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))
'@sentry/react':
specifier: ^7.77.0
version: 7.119.2(react@19.1.0)
@@ -2077,11 +2077,11 @@ importers:
specifier: 16.4.7
version: 16.4.7
drizzle-kit:
specifier: 0.31.4
version: 0.31.4
specifier: 0.31.1
version: 0.31.1
drizzle-orm:
specifier: 0.44.2
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8))(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(gel@2.0.1)(pg@8.16.3)
version: 0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(pg@8.11.3)
escape-html:
specifier: 1.0.3
version: 1.0.3
@@ -2116,8 +2116,8 @@ importers:
specifier: workspace:*
version: link:../packages/payload
pg:
specifier: 8.16.3
version: 8.16.3
specifier: 8.11.3
version: 8.11.3
qs-esm:
specifier: 7.0.2
version: 7.0.2
@@ -5040,9 +5040,6 @@ packages:
cpu: [x64]
os: [win32]
'@petamoriken/float16@3.9.2':
resolution: {integrity: sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -7060,6 +7057,10 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
buffer-writer@2.0.0:
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
engines: {node: '>=4'}
buffer@4.9.2:
resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==}
@@ -7650,10 +7651,103 @@ packages:
resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
engines: {node: '>=12'}
drizzle-kit@0.31.4:
resolution: {integrity: sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA==}
drizzle-kit@0.31.0:
resolution: {integrity: sha512-pcKVT+GbfPA+bUovPIilgVOoq+onNBo/YQBG86sf3/GFHkN6lRJPm1l7dKN0IMAk57RQoIm4GUllRrasLlcaSg==}
hasBin: true
drizzle-kit@0.31.1:
resolution: {integrity: sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q==}
hasBin: true
drizzle-orm@0.43.1:
resolution: {integrity: sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA==}
peerDependencies:
'@aws-sdk/client-rds-data': '>=3'
'@cloudflare/workers-types': '>=4'
'@electric-sql/pglite': '>=0.2.0'
'@libsql/client': '>=0.10.0'
'@libsql/client-wasm': '>=0.10.0'
'@neondatabase/serverless': '>=0.10.0'
'@op-engineering/op-sqlite': '>=2'
'@opentelemetry/api': ^1.4.1
'@planetscale/database': '>=1.13'
'@prisma/client': '*'
'@tidbcloud/serverless': '*'
'@types/better-sqlite3': '*'
'@types/pg': '*'
'@types/sql.js': '*'
'@vercel/postgres': '>=0.8.0'
'@xata.io/client': '*'
better-sqlite3: '>=7'
bun-types: '*'
expo-sqlite: '>=14.0.0'
gel: '>=2'
knex: '*'
kysely: '*'
mysql2: '>=2'
pg: '>=8'
postgres: '>=3'
prisma: '*'
sql.js: '>=1'
sqlite3: '>=5'
peerDependenciesMeta:
'@aws-sdk/client-rds-data':
optional: true
'@cloudflare/workers-types':
optional: true
'@electric-sql/pglite':
optional: true
'@libsql/client':
optional: true
'@libsql/client-wasm':
optional: true
'@neondatabase/serverless':
optional: true
'@op-engineering/op-sqlite':
optional: true
'@opentelemetry/api':
optional: true
'@planetscale/database':
optional: true
'@prisma/client':
optional: true
'@tidbcloud/serverless':
optional: true
'@types/better-sqlite3':
optional: true
'@types/pg':
optional: true
'@types/sql.js':
optional: true
'@vercel/postgres':
optional: true
'@xata.io/client':
optional: true
better-sqlite3:
optional: true
bun-types:
optional: true
expo-sqlite:
optional: true
gel:
optional: true
knex:
optional: true
kysely:
optional: true
mysql2:
optional: true
pg:
optional: true
postgres:
optional: true
prisma:
optional: true
sql.js:
optional: true
sqlite3:
optional: true
drizzle-orm@0.44.2:
resolution: {integrity: sha512-zGAqBzWWkVSFjZpwPOrmCrgO++1kZ5H/rZ4qTGeGOe18iXGVJWf3WPfHOVwFIbmi8kHjfJstC6rJomzGx8g/dQ==}
peerDependencies:
@@ -7799,10 +7893,6 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
env-paths@3.0.0:
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
environment@1.1.0:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
@@ -8484,11 +8574,6 @@ packages:
peerDependencies:
next: '>=13.2.0'
gel@2.0.1:
resolution: {integrity: sha512-gfem3IGvqKqXwEq7XseBogyaRwGsQGuE7Cw/yQsjLGdgiyqX92G1xENPCE0ltunPGcsJIa6XBOTx/PK169mOqw==}
engines: {node: '>= 18.0.0'}
hasBin: true
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@@ -9125,10 +9210,6 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
isexe@3.1.1:
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
engines: {node: '>=16'}
isomorphic-unfetch@3.1.0:
resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==}
@@ -10201,6 +10282,9 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
packet-reader@1.0.0:
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -10273,11 +10357,11 @@ packages:
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
pg-cloudflare@1.2.7:
resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==}
pg-cloudflare@1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
pg-connection-string@2.9.1:
resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==}
pg-connection-string@2.7.0:
resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==}
pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
@@ -10287,17 +10371,11 @@ packages:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
pg-pool@3.10.1:
resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==}
pg-pool@3.7.0:
resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==}
peerDependencies:
pg: '>=8.0'
pg-protocol@1.10.2:
resolution: {integrity: sha512-Ci7jy8PbaWxfsck2dwZdERcDG2A0MG8JoQILs+uZNjABFuBuItAZCWUNz8sXRDMoui24rJw7WlXqgpMdBSN/vQ==}
pg-protocol@1.10.3:
resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==}
pg-protocol@1.7.0:
resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==}
@@ -10309,9 +10387,9 @@ packages:
resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==}
engines: {node: '>=10'}
pg@8.16.3:
resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==}
engines: {node: '>= 16.0.0'}
pg@8.11.3:
resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
@@ -11138,10 +11216,6 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
shell-quote@1.8.3:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
engines: {node: '>= 0.4'}
shelljs@0.8.5:
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
engines: {node: '>=4'}
@@ -12277,11 +12351,6 @@ packages:
engines: {node: '>= 8'}
hasBin: true
which@4.0.0:
resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==}
engines: {node: ^16.13.0 || >=18.0.0}
hasBin: true
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
@@ -15903,9 +15972,6 @@ snapshots:
'@oxc-resolver/binding-win32-x64-msvc@5.3.0':
optional: true
'@petamoriken/float16@3.9.2':
optional: true
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -16400,7 +16466,7 @@ snapshots:
'@sentry/utils': 7.119.2
localforage: 1.10.0
'@sentry/nextjs@8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.2(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29))':
'@sentry/nextjs@8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.2(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0)
@@ -16414,7 +16480,7 @@ snapshots:
'@sentry/types': 8.37.1
'@sentry/utils': 8.37.1
'@sentry/vercel-edge': 8.37.1
'@sentry/webpack-plugin': 2.22.6(webpack@5.96.1(@swc/core@1.11.29))
'@sentry/webpack-plugin': 2.22.6(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))
chalk: 3.0.0
next: 15.3.2(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4)
resolve: 1.22.8
@@ -16429,7 +16495,7 @@ snapshots:
- supports-color
- webpack
'@sentry/nextjs@8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29))':
'@sentry/nextjs@8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(react@19.1.0)(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0)
@@ -16443,7 +16509,7 @@ snapshots:
'@sentry/types': 8.37.1
'@sentry/utils': 8.37.1
'@sentry/vercel-edge': 8.37.1
'@sentry/webpack-plugin': 2.22.6(webpack@5.96.1(@swc/core@1.11.29))
'@sentry/webpack-plugin': 2.22.6(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))
chalk: 3.0.0
next: 15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4)
resolve: 1.22.8
@@ -16553,12 +16619,12 @@ snapshots:
'@sentry/types': 8.37.1
'@sentry/utils': 8.37.1
'@sentry/webpack-plugin@2.22.6(webpack@5.96.1(@swc/core@1.11.29))':
'@sentry/webpack-plugin@2.22.6(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))':
dependencies:
'@sentry/bundler-plugin-core': 2.22.6
unplugin: 1.0.1
uuid: 9.0.0
webpack: 5.96.1(@swc/core@1.11.29)
webpack: 5.96.1(@swc/core@1.11.29)(esbuild@0.25.5)
transitivePeerDependencies:
- encoding
- supports-color
@@ -17415,13 +17481,13 @@ snapshots:
'@types/pg@8.11.6':
dependencies:
'@types/node': 22.15.30
pg-protocol: 1.10.2
pg-protocol: 1.7.0
pg-types: 4.0.2
'@types/pg@8.6.1':
dependencies:
'@types/node': 22.15.30
pg-protocol: 1.10.2
pg-protocol: 1.7.0
pg-types: 2.2.0
'@types/pluralize@0.0.33': {}
@@ -18411,6 +18477,8 @@ snapshots:
buffer-from@1.1.2: {}
buffer-writer@2.0.0: {}
buffer@4.9.2:
dependencies:
base64-js: 1.5.1
@@ -18974,7 +19042,7 @@ snapshots:
dotenv@16.4.7: {}
drizzle-kit@0.31.4:
drizzle-kit@0.31.0:
dependencies:
'@drizzle-team/brocli': 0.10.2
'@esbuild-kit/esm-loader': 2.6.5
@@ -18983,23 +19051,38 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(gel@2.0.1)(pg@8.16.3):
optionalDependencies:
'@libsql/client': 0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5)
'@opentelemetry/api': 1.9.0
'@types/pg': 8.10.2
'@vercel/postgres': 0.9.0
gel: 2.0.1
pg: 8.16.3
drizzle-kit@0.31.1:
dependencies:
'@drizzle-team/brocli': 0.10.2
'@esbuild-kit/esm-loader': 2.6.5
esbuild: 0.25.5
esbuild-register: 3.6.0(esbuild@0.25.5)
transitivePeerDependencies:
- supports-color
drizzle-orm@0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8))(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(gel@2.0.1)(pg@8.16.3):
drizzle-orm@0.43.1(@libsql/client@0.14.0(bufferutil@4.0.8))(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(pg@8.11.3):
optionalDependencies:
'@libsql/client': 0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5)
'@opentelemetry/api': 1.9.0
'@types/pg': 8.11.6
'@vercel/postgres': 0.9.0
gel: 2.0.1
pg: 8.16.3
pg: 8.11.3
drizzle-orm@0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3):
optionalDependencies:
'@libsql/client': 0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5)
'@opentelemetry/api': 1.9.0
'@types/pg': 8.10.2
'@vercel/postgres': 0.9.0
pg: 8.11.3
drizzle-orm@0.44.2(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5))(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(pg@8.11.3):
optionalDependencies:
'@libsql/client': 0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.5)
'@opentelemetry/api': 1.9.0
'@types/pg': 8.11.6
'@vercel/postgres': 0.9.0
pg: 8.11.3
dunder-proto@1.0.1:
dependencies:
@@ -19051,9 +19134,6 @@ snapshots:
entities@6.0.1: {}
env-paths@3.0.0:
optional: true
environment@1.1.0: {}
error-ex@1.3.2:
@@ -19238,7 +19318,7 @@ snapshots:
esbuild-register@3.6.0(esbuild@0.25.5):
dependencies:
debug: 4.4.1
debug: 4.3.7
esbuild: 0.25.5
transitivePeerDependencies:
- supports-color
@@ -20077,18 +20157,6 @@ snapshots:
dependencies:
next: 15.3.3(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4)
gel@2.0.1:
dependencies:
'@petamoriken/float16': 3.9.2
debug: 4.4.1
env-paths: 3.0.0
semver: 7.7.1
shell-quote: 1.8.3
which: 4.0.0
transitivePeerDependencies:
- supports-color
optional: true
gensync@1.0.0-beta.2: {}
get-caller-file@2.0.5: {}
@@ -20733,9 +20801,6 @@ snapshots:
isexe@2.0.0: {}
isexe@3.1.1:
optional: true
isomorphic-unfetch@3.1.0:
dependencies:
node-fetch: 2.7.0
@@ -22252,6 +22317,8 @@ snapshots:
package-json-from-dist@1.0.1: {}
packet-reader@1.0.0: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -22316,22 +22383,18 @@ snapshots:
perfect-debounce@1.0.0: {}
pg-cloudflare@1.2.7:
pg-cloudflare@1.1.1:
optional: true
pg-connection-string@2.9.1: {}
pg-connection-string@2.7.0: {}
pg-int8@1.0.1: {}
pg-numeric@1.0.2: {}
pg-pool@3.10.1(pg@8.16.3):
pg-pool@3.7.0(pg@8.11.3):
dependencies:
pg: 8.16.3
pg-protocol@1.10.2: {}
pg-protocol@1.10.3: {}
pg: 8.11.3
pg-protocol@1.7.0: {}
@@ -22353,15 +22416,17 @@ snapshots:
postgres-interval: 3.0.0
postgres-range: 1.1.4
pg@8.16.3:
pg@8.11.3:
dependencies:
pg-connection-string: 2.9.1
pg-pool: 3.10.1(pg@8.16.3)
pg-protocol: 1.10.3
buffer-writer: 2.0.0
packet-reader: 1.0.0
pg-connection-string: 2.7.0
pg-pool: 3.7.0(pg@8.11.3)
pg-protocol: 1.7.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.2.7
pg-cloudflare: 1.1.1
pgpass@1.0.5:
dependencies:
@@ -23261,9 +23326,6 @@ snapshots:
shebang-regex@3.0.0: {}
shell-quote@1.8.3:
optional: true
shelljs@0.8.5:
dependencies:
glob: 7.2.3
@@ -23839,16 +23901,17 @@ snapshots:
ansi-escapes: 4.3.2
supports-hyperlinks: 2.3.0
terser-webpack-plugin@5.3.10(@swc/core@1.11.29)(webpack@5.96.1(@swc/core@1.11.29)):
terser-webpack-plugin@5.3.10(@swc/core@1.11.29)(esbuild@0.25.5)(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5)):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.36.0
webpack: 5.96.1(@swc/core@1.11.29)
webpack: 5.96.1(@swc/core@1.11.29)(esbuild@0.25.5)
optionalDependencies:
'@swc/core': 1.11.29
esbuild: 0.25.5
terser@5.36.0:
dependencies:
@@ -24555,7 +24618,7 @@ snapshots:
webpack-virtual-modules@0.5.0: {}
webpack@5.96.1(@swc/core@1.11.29):
webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.7
@@ -24577,7 +24640,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
terser-webpack-plugin: 5.3.10(@swc/core@1.11.29)(webpack@5.96.1(@swc/core@1.11.29))
terser-webpack-plugin: 5.3.10(@swc/core@1.11.29)(esbuild@0.25.5)(webpack@5.96.1(@swc/core@1.11.29)(esbuild@0.25.5))
watchpack: 2.4.2
webpack-sources: 3.2.3
transitivePeerDependencies:
@@ -24671,11 +24734,6 @@ snapshots:
dependencies:
isexe: 2.0.0
which@4.0.0:
dependencies:
isexe: 3.1.1
optional: true
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0

View File

@@ -60,7 +60,7 @@ const BeforeDashboard: React.FC = () => {
</ul>
{'Pro Tip: This block is a '}
<a
href="https://payloadcms.com/docs/custom-components/overview"
href="https://payloadcms.com/docs/admin/custom-components/overview#base-component-overrides"
rel="noopener noreferrer"
target="_blank"
>

View File

@@ -24,10 +24,10 @@ export default buildConfig({
admin: {
components: {
// The `BeforeLogin` component renders a message that you see while logging into your admin panel.
// Feel free to delete this at any time. Simply remove the line below.
// Feel free to delete this at any time. Simply remove the line below and the import `BeforeLogin` statement on line 15.
beforeLogin: ['@/components/BeforeLogin'],
// The `BeforeDashboard` component renders the 'welcome' block that you see after logging into your admin panel.
// Feel free to delete this at any time. Simply remove the line below.
// Feel free to delete this at any time. Simply remove the line below and the import `BeforeDashboard` statement on line 15.
beforeDashboard: ['@/components/BeforeDashboard'],
},
importMap: {

View File

@@ -60,7 +60,7 @@ const BeforeDashboard: React.FC = () => {
</ul>
{'Pro Tip: This block is a '}
<a
href="https://payloadcms.com/docs/custom-components/overview"
href="https://payloadcms.com/docs/admin/custom-components/overview#base-component-overrides"
rel="noopener noreferrer"
target="_blank"
>

View File

@@ -24,10 +24,10 @@ export default buildConfig({
admin: {
components: {
// The `BeforeLogin` component renders a message that you see while logging into your admin panel.
// Feel free to delete this at any time. Simply remove the line below.
// Feel free to delete this at any time. Simply remove the line below and the import `BeforeLogin` statement on line 15.
beforeLogin: ['@/components/BeforeLogin'],
// The `BeforeDashboard` component renders the 'welcome' block that you see after logging into your admin panel.
// Feel free to delete this at any time. Simply remove the line below.
// Feel free to delete this at any time. Simply remove the line below and the import `BeforeDashboard` statement on line 15.
beforeDashboard: ['@/components/BeforeDashboard'],
},
importMap: {

View File

@@ -203,13 +203,6 @@ export interface User {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
@@ -348,13 +341,6 @@ export interface UsersSelect<T extends boolean = true> {
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema

View File

@@ -436,15 +436,10 @@ describe('Access Control', () => {
const documentDrawer = page.locator(`[id^=doc-drawer_${createNotUpdateCollectionSlug}_1_]`)
await expect(documentDrawer).toBeVisible()
await expect(documentDrawer.locator('#action-save')).toBeVisible()
await documentDrawer.locator('#field-name').fill('name')
await expect(documentDrawer.locator('#field-name')).toHaveValue('name')
await saveDocAndAssert(
page,
`[id^=doc-drawer_${createNotUpdateCollectionSlug}_1_] #action-save`,
)
await documentDrawer.locator('#action-save').click()
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
await expect(documentDrawer.locator('#action-save')).toBeHidden()
await expect(documentDrawer.locator('#field-name')).toBeDisabled()
})

View File

@@ -95,6 +95,7 @@ export interface Config {
'auth-collection': AuthCollection;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-sessions': PayloadSession;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
@@ -125,6 +126,7 @@ export interface Config {
'auth-collection': AuthCollectionSelect<false> | AuthCollectionSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-sessions': PayloadSessionsSelect<false> | PayloadSessionsSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
@@ -230,13 +232,6 @@ export interface User {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
@@ -254,13 +249,6 @@ export interface PublicUser {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
@@ -752,13 +740,6 @@ export interface AuthCollection {
_verificationToken?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -912,6 +893,26 @@ export interface PayloadPreference {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-sessions".
*/
export interface PayloadSession {
id: string;
session: string;
expiration: string;
user:
| {
relationTo: 'users';
value: string | User;
}
| {
relationTo: 'public-users';
value: string | PublicUser;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
@@ -938,13 +939,6 @@ export interface UsersSelect<T extends boolean = true> {
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -960,13 +954,6 @@ export interface PublicUsersSelect<T extends boolean = true> {
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -1307,13 +1294,6 @@ export interface AuthCollectionSelect<T extends boolean = true> {
_verificationToken?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -1337,6 +1317,17 @@ export interface PayloadPreferencesSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-sessions_select".
*/
export interface PayloadSessionsSelect<T extends boolean = true> {
session?: T;
expiration?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".

View File

@@ -18,7 +18,6 @@ export const UploadCollection: CollectionConfig = {
height: 100,
},
],
adminThumbnail: () =>
'https://raw.githubusercontent.com/payloadcms/website/refs/heads/main/public/images/universal-truth.jpg',
adminThumbnail: () => 'https://payloadcms.com/images/universal-truth.jpg',
},
}

View File

@@ -363,43 +363,6 @@ describe('Document View', () => {
})
describe('drawers', () => {
test('document drawers do not unmount across save events', async () => {
// Navigate to a post document
await navigateToDoc(page, postsUrl)
// Open the relationship drawer
await page
.locator('.field-type.relationship .relationship--single-value__drawer-toggler')
.click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
const drawerEditView = drawer.locator('.drawer__content .collection-edit')
await expect(drawerEditView).toBeVisible()
const drawerTitleField = drawerEditView.locator('#field-title')
const testTitle = 'Test Title for Persistence'
await drawerTitleField.fill(testTitle)
await expect(drawerTitleField).toHaveValue(testTitle)
await drawerEditView.evaluate((el) => {
el.setAttribute('data-test-instance', 'This is a test')
})
await expect(drawerEditView).toHaveAttribute('data-test-instance', 'This is a test')
await saveDocAndAssert(page, '[id^=doc-drawer_posts_1_] .drawer__content #action-save')
await expect(drawerEditView).toBeVisible()
await expect(drawerTitleField).toHaveValue(testTitle)
// Verify the element instance hasn't changed (i.e., it wasn't re-mounted and discarded the custom attribute)
await expect
.poll(async () => {
return await drawerEditView.getAttribute('data-test-instance')
})
.toBe('This is a test')
})
test('document drawers are visually stacking', async () => {
await navigateToDoc(page, postsUrl)
await page.locator('#field-title').fill(title)

View File

@@ -293,13 +293,6 @@ export interface User {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
@@ -827,13 +820,6 @@ export interface UsersSelect<T extends boolean = true> {
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema

View File

@@ -46,16 +46,6 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: 'categories-custom-id',
versions: { drafts: true },
fields: [
{
type: 'number',
name: 'id',
},
],
},
{
slug: postsSlug,
fields: [
@@ -70,11 +60,6 @@ export default buildConfigWithDefaults({
relationTo: 'categories',
name: 'category',
},
{
type: 'relationship',
relationTo: 'categories-custom-id',
name: 'categoryCustomID',
},
{
name: 'localized',
type: 'text',
@@ -531,11 +516,6 @@ export default buildConfigWithDefaults({
type: 'json',
virtual: 'post.category.id',
},
{
name: 'postCategoryCustomID',
type: 'number',
virtual: 'post.categoryCustomID.id',
},
{
name: 'postID',
type: 'json',

Some files were not shown because too many files have changed in this diff Show More