Compare commits
29 Commits
docs-hooks
...
perf/cache
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3545514736 | ||
|
|
c372c04bcd | ||
|
|
9da0158e1f | ||
|
|
b534fb4818 | ||
|
|
9d0ef67273 | ||
|
|
f0fecc4d3b | ||
|
|
61874272ab | ||
|
|
d4a6735d91 | ||
|
|
5f8f9f7a61 | ||
|
|
e264631114 | ||
|
|
8409de7196 | ||
|
|
616de3c1b6 | ||
|
|
a75e426433 | ||
|
|
815c5b55cb | ||
|
|
dac7f0bd89 | ||
|
|
8cba6c5d45 | ||
|
|
81763b66af | ||
|
|
7f881618e8 | ||
|
|
c588c7e38a | ||
|
|
4c6e9a190e | ||
|
|
952fd26849 | ||
|
|
44e1bd4ba7 | ||
|
|
7aa34535b5 | ||
|
|
8147299a2f | ||
|
|
9c79c51904 | ||
|
|
c40ff0173f | ||
|
|
c768fb054e | ||
|
|
c5789d6026 | ||
|
|
79e221d306 |
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@@ -296,6 +296,7 @@ jobs:
|
||||
- admin__e2e__3
|
||||
- admin-root
|
||||
- auth
|
||||
- auth-basic
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
- fields
|
||||
|
||||
@@ -17,7 +17,7 @@ const customReactVersionParser: CustomVersionParser = (version) => {
|
||||
|
||||
let checkedDependencies = false
|
||||
|
||||
export const checkDependencies = async () => {
|
||||
export const checkDependencies = () => {
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true' &&
|
||||
@@ -26,7 +26,7 @@ export const checkDependencies = async () => {
|
||||
checkedDependencies = true
|
||||
|
||||
// First check if there are mismatching dependency versions of next / react packages
|
||||
await payloadCheckDependencies({
|
||||
void payloadCheckDependencies({
|
||||
dependencyGroups: [
|
||||
{
|
||||
name: 'react',
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { AcceptedLanguages } from '@payloadcms/translations'
|
||||
import type { ImportMap, SanitizedConfig, ServerFunctionClient } from 'payload'
|
||||
import type { ConfigImport, ImportMap, SanitizedConfig, ServerFunctionClient } from 'payload'
|
||||
|
||||
import { rtlLanguages } from '@payloadcms/translations'
|
||||
import { RootProvider } from '@payloadcms/ui'
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
|
||||
import { getPayload, parseCookies } from 'payload'
|
||||
import { getConfig, getPayload, parseCookies } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import { getNavPrefs } from '../../elements/Nav/getNavPrefs.js'
|
||||
import { getClientConfig } from '../../utilities/getClientConfig.js'
|
||||
import { getRequestLanguage } from '../../utilities/getRequestLanguage.js'
|
||||
import { getRequestTheme } from '../../utilities/getRequestTheme.js'
|
||||
import { initReq } from '../../utilities/initReq.js'
|
||||
@@ -24,18 +24,18 @@ export const metadata = {
|
||||
|
||||
export const RootLayout = async ({
|
||||
children,
|
||||
config: configPromise,
|
||||
config: configImport,
|
||||
importMap,
|
||||
serverFunction,
|
||||
}: {
|
||||
readonly children: React.ReactNode
|
||||
readonly config: Promise<SanitizedConfig>
|
||||
readonly config: ConfigImport
|
||||
readonly importMap: ImportMap
|
||||
readonly serverFunction: ServerFunctionClient
|
||||
}) => {
|
||||
await checkDependencies()
|
||||
void checkDependencies()
|
||||
|
||||
const config = await configPromise
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
const headers = await getHeaders()
|
||||
const cookies = parseCookies(headers)
|
||||
@@ -54,7 +54,7 @@ export const RootLayout = async ({
|
||||
|
||||
const payload = await getPayload({ config, importMap })
|
||||
|
||||
const { i18n, permissions, req, user } = await initReq(config)
|
||||
const { i18n, permissions, user } = await initReq(config)
|
||||
|
||||
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
|
||||
? 'RTL'
|
||||
@@ -86,7 +86,7 @@ export const RootLayout = async ({
|
||||
|
||||
const navPrefs = await getNavPrefs({ payload, user })
|
||||
|
||||
const clientConfig = await getClientConfig({
|
||||
const clientConfig = getClientConfig({
|
||||
config,
|
||||
i18n,
|
||||
importMap,
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { ClientConfig, ImportMap, SanitizedConfig } from 'payload'
|
||||
|
||||
import { createClientConfig } from 'payload'
|
||||
import { cache } from 'react'
|
||||
|
||||
export const getClientConfig = cache(
|
||||
async (args: {
|
||||
config: SanitizedConfig
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): Promise<ClientConfig> => {
|
||||
const { config, i18n, importMap } = args
|
||||
|
||||
const clientConfig = createClientConfig({
|
||||
config,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
return Promise.resolve(clientConfig)
|
||||
},
|
||||
)
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { I18n, I18nClient } from '@payloadcms/translations'
|
||||
import type { PayloadRequest, SanitizedConfig, SanitizedPermissions, User } from 'payload'
|
||||
import type { ConfigImport, PayloadRequest, SanitizedPermissions, User } from 'payload'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { createLocalReq, getPayload, parseCookies } from 'payload'
|
||||
import { createLocalReq, getConfig, getPayload, parseCookies } from 'payload'
|
||||
import { cache } from 'react'
|
||||
|
||||
import { getRequestLanguage } from './getRequestLanguage.js'
|
||||
@@ -15,10 +15,9 @@ type Result = {
|
||||
user: User
|
||||
}
|
||||
|
||||
export const initReq = cache(async function (
|
||||
configPromise: Promise<SanitizedConfig> | SanitizedConfig,
|
||||
): Promise<Result> {
|
||||
const config = await configPromise
|
||||
export const initReq = cache(async function (configImport: ConfigImport): Promise<Result> {
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
const headers = await getHeaders()
|
||||
|
||||
@@ -102,6 +102,7 @@ export const Account: React.FC<AdminViewProps> = async ({
|
||||
await getVersions({
|
||||
id: user.id,
|
||||
collectionConfig,
|
||||
doc: data,
|
||||
docPermissions,
|
||||
locale: locale?.code,
|
||||
payload,
|
||||
|
||||
@@ -10,6 +10,13 @@ import { sanitizeID } from '@payloadcms/ui/shared'
|
||||
|
||||
type Args = {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
/**
|
||||
* Optional - performance optimization.
|
||||
* If a document has been fetched before fetching versions, pass it here.
|
||||
* If this document is set to published, we can skip the query to find out if a published document exists,
|
||||
* as the passed in document is proof of its existence.
|
||||
*/
|
||||
doc?: Record<string, any>
|
||||
docPermissions: SanitizedDocumentPermissions
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
id?: number | string
|
||||
@@ -27,9 +34,11 @@ type Result = Promise<{
|
||||
|
||||
// TODO: in the future, we can parallelize some of these queries
|
||||
// this will speed up the API by ~30-100ms or so
|
||||
// Note from the future: I have attempted parallelizing these queries, but it made this function almost 2x slower.
|
||||
export const getVersions = async ({
|
||||
id: idArg,
|
||||
collectionConfig,
|
||||
doc,
|
||||
docPermissions,
|
||||
globalConfig,
|
||||
locale,
|
||||
@@ -37,7 +46,7 @@ export const getVersions = async ({
|
||||
user,
|
||||
}: Args): Result => {
|
||||
const id = sanitizeID(idArg)
|
||||
let publishedQuery
|
||||
let publishedDoc
|
||||
let hasPublishedDoc = false
|
||||
let mostRecentVersionIsAutosaved = false
|
||||
let unpublishedVersionCount = 0
|
||||
@@ -70,37 +79,49 @@ export const getVersions = async ({
|
||||
}
|
||||
|
||||
if (versionsConfig?.drafts) {
|
||||
publishedQuery = await payload.find({
|
||||
collection: collectionConfig.slug,
|
||||
depth: 0,
|
||||
locale: locale || undefined,
|
||||
user,
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
or: [
|
||||
// Find out if a published document exists
|
||||
if (doc?._status === 'published') {
|
||||
publishedDoc = doc
|
||||
} else {
|
||||
publishedDoc = (
|
||||
await payload.find({
|
||||
collection: collectionConfig.slug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
locale: locale || undefined,
|
||||
pagination: false,
|
||||
select: {
|
||||
updatedAt: true,
|
||||
},
|
||||
user,
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
_status: {
|
||||
equals: 'published',
|
||||
},
|
||||
or: [
|
||||
{
|
||||
_status: {
|
||||
equals: 'published',
|
||||
},
|
||||
},
|
||||
{
|
||||
_status: {
|
||||
exists: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
_status: {
|
||||
exists: false,
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
)?.docs?.[0]
|
||||
}
|
||||
|
||||
if (publishedQuery.docs?.[0]) {
|
||||
if (publishedDoc) {
|
||||
hasPublishedDoc = true
|
||||
}
|
||||
|
||||
@@ -109,6 +130,9 @@ export const getVersions = async ({
|
||||
collection: collectionConfig.slug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
select: {
|
||||
autosave: true,
|
||||
},
|
||||
user,
|
||||
where: {
|
||||
and: [
|
||||
@@ -130,7 +154,7 @@ export const getVersions = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if (publishedQuery.docs?.[0]?.updatedAt) {
|
||||
if (publishedDoc?.updatedAt) {
|
||||
;({ totalDocs: unpublishedVersionCount } = await payload.countVersions({
|
||||
collection: collectionConfig.slug,
|
||||
user,
|
||||
@@ -148,7 +172,7 @@ export const getVersions = async ({
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
greater_than: publishedQuery.docs[0].updatedAt,
|
||||
greater_than: publishedDoc.updatedAt,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -159,6 +183,7 @@ export const getVersions = async ({
|
||||
|
||||
;({ totalDocs: versionCount } = await payload.countVersions({
|
||||
collection: collectionConfig.slug,
|
||||
depth: 0,
|
||||
user,
|
||||
where: {
|
||||
and: [
|
||||
@@ -173,15 +198,23 @@ export const getVersions = async ({
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
// Find out if a published document exists
|
||||
if (versionsConfig?.drafts) {
|
||||
publishedQuery = await payload.findGlobal({
|
||||
slug: globalConfig.slug,
|
||||
depth: 0,
|
||||
locale,
|
||||
user,
|
||||
})
|
||||
if (doc?._status === 'published') {
|
||||
publishedDoc = doc
|
||||
} else {
|
||||
publishedDoc = await payload.findGlobal({
|
||||
slug: globalConfig.slug,
|
||||
depth: 0,
|
||||
locale,
|
||||
select: {
|
||||
updatedAt: true,
|
||||
},
|
||||
user,
|
||||
})
|
||||
}
|
||||
|
||||
if (publishedQuery?._status === 'published') {
|
||||
if (publishedDoc?._status === 'published') {
|
||||
hasPublishedDoc = true
|
||||
}
|
||||
|
||||
@@ -204,7 +237,7 @@ export const getVersions = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if (publishedQuery?.updatedAt) {
|
||||
if (publishedDoc?.updatedAt) {
|
||||
;({ totalDocs: unpublishedVersionCount } = await payload.countGlobalVersions({
|
||||
depth: 0,
|
||||
global: globalConfig.slug,
|
||||
@@ -218,7 +251,7 @@ export const getVersions = async ({
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
greater_than: publishedQuery.updatedAt,
|
||||
greater_than: publishedDoc.updatedAt,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,46 +1,11 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type {
|
||||
ClientConfig,
|
||||
Data,
|
||||
DocumentPreferences,
|
||||
FormState,
|
||||
ImportMap,
|
||||
PayloadRequest,
|
||||
SanitizedConfig,
|
||||
VisibleEntities,
|
||||
} from 'payload'
|
||||
import type { Data, DocumentPreferences, FormState, PayloadRequest, VisibleEntities } from 'payload'
|
||||
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { createClientConfig, getAccessResults, isEntityHidden, parseCookies } from 'payload'
|
||||
import { getAccessResults, isEntityHidden, parseCookies } from 'payload'
|
||||
|
||||
import { renderDocument } from './index.js'
|
||||
|
||||
let cachedClientConfig = global._payload_clientConfig
|
||||
|
||||
if (!cachedClientConfig) {
|
||||
cachedClientConfig = global._payload_clientConfig = null
|
||||
}
|
||||
|
||||
export const getClientConfig = (args: {
|
||||
config: SanitizedConfig
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): ClientConfig => {
|
||||
const { config, i18n, importMap } = args
|
||||
|
||||
if (cachedClientConfig && process.env.NODE_ENV !== 'development') {
|
||||
return cachedClientConfig
|
||||
}
|
||||
|
||||
cachedClientConfig = createClientConfig({
|
||||
config,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
return cachedClientConfig
|
||||
}
|
||||
|
||||
type RenderDocumentResult = {
|
||||
data: any
|
||||
Document: React.ReactNode
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import type {
|
||||
AdminViewProps,
|
||||
Data,
|
||||
PayloadComponent,
|
||||
ServerProps,
|
||||
ServerSideEditViewProps,
|
||||
} from 'payload'
|
||||
import type { AdminViewProps, Data, PayloadComponent, ServerSideEditViewProps } from 'payload'
|
||||
|
||||
import { DocumentInfoProvider, EditDepthProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
@@ -137,6 +131,7 @@ export const renderDocument = async ({
|
||||
getVersions({
|
||||
id: idFromArgs,
|
||||
collectionConfig,
|
||||
doc,
|
||||
docPermissions,
|
||||
globalConfig,
|
||||
locale: locale?.code,
|
||||
|
||||
@@ -1,45 +1,12 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { ListPreferences } from '@payloadcms/ui'
|
||||
import type {
|
||||
ClientConfig,
|
||||
ImportMap,
|
||||
ListQuery,
|
||||
PayloadRequest,
|
||||
SanitizedConfig,
|
||||
VisibleEntities,
|
||||
} from 'payload'
|
||||
import type { ListQuery, PayloadRequest, VisibleEntities } from 'payload'
|
||||
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { createClientConfig, getAccessResults, isEntityHidden, parseCookies } from 'payload'
|
||||
import { getAccessResults, isEntityHidden, parseCookies } from 'payload'
|
||||
|
||||
import { renderListView } from './index.js'
|
||||
|
||||
let cachedClientConfig = global._payload_clientConfig
|
||||
|
||||
if (!cachedClientConfig) {
|
||||
cachedClientConfig = global._payload_clientConfig = null
|
||||
}
|
||||
|
||||
export const getClientConfig = (args: {
|
||||
config: SanitizedConfig
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): ClientConfig => {
|
||||
const { config, i18n, importMap } = args
|
||||
|
||||
if (cachedClientConfig && process.env.NODE_ENV !== 'development') {
|
||||
return cachedClientConfig
|
||||
}
|
||||
|
||||
cachedClientConfig = createClientConfig({
|
||||
config,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
return cachedClientConfig
|
||||
}
|
||||
|
||||
type RenderListResult = {
|
||||
List: React.ReactNode
|
||||
preferences: ListPreferences
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type { Metadata } from 'next'
|
||||
import type {
|
||||
AdminViewComponent,
|
||||
ImportMap,
|
||||
PayloadServerReactComponent,
|
||||
SanitizedConfig,
|
||||
} from 'payload'
|
||||
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import {
|
||||
type AdminViewComponent,
|
||||
type ConfigImport,
|
||||
getConfig,
|
||||
type ImportMap,
|
||||
type PayloadServerReactComponent,
|
||||
type SanitizedConfig,
|
||||
} from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import { DefaultTemplate } from '../../templates/Default/index.js'
|
||||
@@ -16,12 +18,12 @@ import { initPage } from '../../utilities/initPage/index.js'
|
||||
import { NotFoundClient } from './index.client.js'
|
||||
|
||||
export const generatePageMetadata = async ({
|
||||
config: configPromise,
|
||||
config: configImport,
|
||||
}: {
|
||||
config: Promise<SanitizedConfig> | SanitizedConfig
|
||||
config: ConfigImport
|
||||
params?: { [key: string]: string | string[] }
|
||||
}): Promise<Metadata> => {
|
||||
const config = await configPromise
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
const i18n = await getNextRequestI18n({
|
||||
config,
|
||||
@@ -39,12 +41,12 @@ export type GenerateViewMetadata = (args: {
|
||||
}) => Promise<Metadata>
|
||||
|
||||
export const NotFoundPage = async ({
|
||||
config: configPromise,
|
||||
config: configImport,
|
||||
importMap,
|
||||
params: paramsPromise,
|
||||
searchParams: searchParamsPromise,
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
config: ConfigImport
|
||||
importMap: ImportMap
|
||||
params: Promise<{
|
||||
segments: string[]
|
||||
@@ -53,7 +55,8 @@ export const NotFoundPage = async ({
|
||||
[key: string]: string | string[]
|
||||
}>
|
||||
}) => {
|
||||
const config = await configPromise
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
const { routes: { admin: adminRoute } = {} } = config
|
||||
|
||||
const searchParams = await searchParamsPromise
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { Metadata } from 'next'
|
||||
import type { ImportMap, SanitizedConfig } from 'payload'
|
||||
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import { type ConfigImport, getConfig, type ImportMap, type SanitizedConfig } from 'payload'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { DefaultTemplate } from '../../templates/Default/index.js'
|
||||
import { MinimalTemplate } from '../../templates/Minimal/index.js'
|
||||
import { getClientConfig } from '../../utilities/getClientConfig.js'
|
||||
import { initPage } from '../../utilities/initPage/index.js'
|
||||
import { getViewFromConfig } from './getViewFromConfig.js'
|
||||
|
||||
@@ -23,12 +23,12 @@ export type GenerateViewMetadata = (args: {
|
||||
}) => Promise<Metadata>
|
||||
|
||||
export const RootPage = async ({
|
||||
config: configPromise,
|
||||
config: configImport,
|
||||
importMap,
|
||||
params: paramsPromise,
|
||||
searchParams: searchParamsPromise,
|
||||
}: {
|
||||
readonly config: Promise<SanitizedConfig>
|
||||
readonly config: ConfigImport
|
||||
readonly importMap: ImportMap
|
||||
readonly params: Promise<{
|
||||
segments: string[]
|
||||
@@ -37,7 +37,7 @@ export const RootPage = async ({
|
||||
[key: string]: string | string[]
|
||||
}>
|
||||
}) => {
|
||||
const config = await configPromise
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
const {
|
||||
admin: {
|
||||
@@ -115,7 +115,7 @@ export const RootPage = async ({
|
||||
redirect(adminRoute)
|
||||
}
|
||||
|
||||
const clientConfig = await getClientConfig({
|
||||
const clientConfig = getClientConfig({
|
||||
config,
|
||||
i18n: initPageResult?.req.i18n,
|
||||
importMap,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Metadata } from 'next'
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import { type ConfigImport, getConfig } from 'payload'
|
||||
|
||||
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
|
||||
import { generateAccountMetadata } from '../Account/index.js'
|
||||
@@ -26,7 +27,7 @@ const oneSegmentMeta = {
|
||||
}
|
||||
|
||||
type Args = {
|
||||
config: Promise<SanitizedConfig>
|
||||
config: ConfigImport
|
||||
params: Promise<{
|
||||
[key: string]: string | string[]
|
||||
}>
|
||||
@@ -36,10 +37,10 @@ type Args = {
|
||||
}
|
||||
|
||||
export const generatePageMetadata = async ({
|
||||
config: configPromise,
|
||||
config: configImport,
|
||||
params: paramsPromise,
|
||||
}: Args) => {
|
||||
const config = await configPromise
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
const params = await paramsPromise
|
||||
const segments = Array.isArray(params.segments) ? params.segments : []
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ClientBlock, ClientField, Field } from '../../fields/config/types.
|
||||
import type { DocumentPreferences } from '../../preferences/types.js'
|
||||
import type { Operation, Payload, PayloadRequest } from '../../types/index.js'
|
||||
import type {
|
||||
ClientFieldSchemaMap,
|
||||
ClientTab,
|
||||
Data,
|
||||
FieldSchemaMap,
|
||||
@@ -67,6 +68,7 @@ export type FieldPaths = {
|
||||
|
||||
export type ServerComponentProps = {
|
||||
clientField: ClientFieldWithOptionalType
|
||||
clientFieldSchemaMap: ClientFieldSchemaMap
|
||||
collectionSlug: string
|
||||
data: Data
|
||||
field: Field
|
||||
|
||||
@@ -3,8 +3,16 @@ import type React from 'react'
|
||||
|
||||
import type { ImportMap } from '../bin/generateImportMap/index.js'
|
||||
import type { SanitizedConfig } from '../config/types.js'
|
||||
import type { Block, Field, FieldTypes, Tab } from '../fields/config/types.js'
|
||||
import type {
|
||||
Block,
|
||||
ClientBlock,
|
||||
ClientField,
|
||||
Field,
|
||||
FieldTypes,
|
||||
Tab,
|
||||
} from '../fields/config/types.js'
|
||||
import type { JsonObject } from '../types/index.js'
|
||||
import type { ClientTab } from './fields/Tabs.js'
|
||||
import type {
|
||||
BuildFormStateArgs,
|
||||
Data,
|
||||
@@ -489,3 +497,13 @@ export type FieldSchemaMap = Map<
|
||||
| Field
|
||||
| Tab
|
||||
>
|
||||
|
||||
export type ClientFieldSchemaMap = Map<
|
||||
SchemaPath,
|
||||
| {
|
||||
fields: ClientField[]
|
||||
}
|
||||
| ClientBlock
|
||||
| ClientField
|
||||
| ClientTab
|
||||
>
|
||||
|
||||
@@ -6,7 +6,7 @@ import path from 'path'
|
||||
import type { BinScript } from '../config/types.js'
|
||||
|
||||
import { findConfig } from '../config/find.js'
|
||||
import { getPayload } from '../index.js'
|
||||
import { getConfig, getPayload } from '../index.js'
|
||||
import { generateImportMap } from './generateImportMap/index.js'
|
||||
import { generateTypes } from './generateTypes.js'
|
||||
import { info } from './info.js'
|
||||
@@ -51,12 +51,13 @@ export const bin = async () => {
|
||||
}
|
||||
|
||||
const configPath = findConfig()
|
||||
const configPromise = await import(pathToFileURL(configPath).toString())
|
||||
let config = await configPromise
|
||||
if (config.default) {
|
||||
config = await config.default
|
||||
let configImport = await import(pathToFileURL(configPath).toString())
|
||||
if (configImport.default) {
|
||||
configImport = configImport.default
|
||||
}
|
||||
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
const userBinScript = Array.isArray(config.bin)
|
||||
? config.bin.find(({ key }) => key === script)
|
||||
: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { checkDependencies } from './utilities/dependencies/dependencyChecker.js'
|
||||
import { PAYLOAD_PACKAGE_LIST } from './versions/payloadPackageList.js'
|
||||
|
||||
export async function checkPayloadDependencies() {
|
||||
export function checkPayloadDependencies() {
|
||||
const dependencies = [...PAYLOAD_PACKAGE_LIST]
|
||||
|
||||
if (process.env.PAYLOAD_CI_DEPENDENCY_CHECKER !== 'true') {
|
||||
@@ -9,7 +9,7 @@ export async function checkPayloadDependencies() {
|
||||
}
|
||||
|
||||
// First load. First check if there are mismatching dependency versions of payload packages
|
||||
await checkDependencies({
|
||||
void checkDependencies({
|
||||
dependencyGroups: [
|
||||
{
|
||||
name: 'payload',
|
||||
|
||||
@@ -9,10 +9,10 @@ import type {
|
||||
} from '../../config/types.js'
|
||||
import type { ClientField } from '../../fields/config/client.js'
|
||||
import type { Payload } from '../../types/index.js'
|
||||
import type { SanitizedUploadConfig } from '../../uploads/types.js'
|
||||
import type { SanitizedCollectionConfig } from './types.js'
|
||||
|
||||
import { createClientFields } from '../../fields/config/client.js'
|
||||
import { deepCopyObjectSimple } from '../../utilities/deepCopyObject.js'
|
||||
|
||||
export type ServerOnlyCollectionProperties = keyof Pick<
|
||||
SanitizedCollectionConfig,
|
||||
@@ -47,12 +47,19 @@ export type ClientCollectionConfig = {
|
||||
| 'preview'
|
||||
| ServerOnlyCollectionAdminProperties
|
||||
>
|
||||
auth?: { verify?: true } & Omit<
|
||||
SanitizedCollectionConfig['auth'],
|
||||
'forgotPassword' | 'strategies' | 'verify'
|
||||
>
|
||||
fields: ClientField[]
|
||||
labels: {
|
||||
plural: StaticLabel
|
||||
singular: StaticLabel
|
||||
}
|
||||
} & Omit<SanitizedCollectionConfig, 'admin' | 'fields' | 'labels' | ServerOnlyCollectionProperties>
|
||||
} & Omit<
|
||||
SanitizedCollectionConfig,
|
||||
'admin' | 'auth' | 'fields' | 'labels' | ServerOnlyCollectionProperties
|
||||
>
|
||||
|
||||
const serverOnlyCollectionProperties: Partial<ServerOnlyCollectionProperties>[] = [
|
||||
'hooks',
|
||||
@@ -93,97 +100,147 @@ export const createClientCollectionConfig = ({
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): ClientCollectionConfig => {
|
||||
const clientCollection = deepCopyObjectSimple(
|
||||
collection,
|
||||
true,
|
||||
) as unknown as ClientCollectionConfig
|
||||
const clientCollection = {} as Partial<ClientCollectionConfig>
|
||||
|
||||
clientCollection.fields = createClientFields({
|
||||
clientFields: clientCollection?.fields || [],
|
||||
defaultIDType,
|
||||
fields: collection.fields,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
serverOnlyCollectionProperties.forEach((key) => {
|
||||
if (key in clientCollection) {
|
||||
delete clientCollection[key]
|
||||
for (const key in collection) {
|
||||
if (serverOnlyCollectionProperties.includes(key as any)) {
|
||||
continue
|
||||
}
|
||||
})
|
||||
|
||||
if ('upload' in clientCollection && typeof clientCollection.upload === 'object') {
|
||||
serverOnlyUploadProperties.forEach((key) => {
|
||||
if (key in clientCollection.upload) {
|
||||
delete clientCollection.upload[key]
|
||||
}
|
||||
})
|
||||
|
||||
if ('imageSizes' in clientCollection.upload && clientCollection.upload.imageSizes.length) {
|
||||
clientCollection.upload.imageSizes = clientCollection.upload.imageSizes.map((size) => {
|
||||
const sanitizedSize = { ...size }
|
||||
if ('generateImageName' in sanitizedSize) {
|
||||
delete sanitizedSize.generateImageName
|
||||
switch (key) {
|
||||
case 'admin':
|
||||
if (!collection.admin) {
|
||||
break
|
||||
}
|
||||
return sanitizedSize
|
||||
})
|
||||
clientCollection.admin = {} as ClientCollectionConfig['admin']
|
||||
for (const adminKey in collection.admin) {
|
||||
if (serverOnlyCollectionAdminProperties.includes(adminKey as any)) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch (adminKey) {
|
||||
case 'description':
|
||||
if (
|
||||
typeof collection.admin.description === 'string' ||
|
||||
typeof collection.admin.description === 'object'
|
||||
) {
|
||||
if (collection.admin.description) {
|
||||
clientCollection.admin.description = collection.admin.description
|
||||
}
|
||||
} else if (typeof collection.admin.description === 'function') {
|
||||
const description = collection.admin.description({ t: i18n.t })
|
||||
if (description) {
|
||||
clientCollection.admin.description = description
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'livePreview':
|
||||
clientCollection.admin.livePreview =
|
||||
{} as ClientCollectionConfig['admin']['livePreview']
|
||||
if (collection.admin.livePreview.breakpoints) {
|
||||
clientCollection.admin.livePreview.breakpoints =
|
||||
collection.admin.livePreview.breakpoints
|
||||
}
|
||||
break
|
||||
case 'preview':
|
||||
if (collection.admin.preview) {
|
||||
clientCollection.admin.preview = true
|
||||
}
|
||||
break
|
||||
default:
|
||||
clientCollection.admin[adminKey] = collection.admin[adminKey]
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'auth':
|
||||
if (!collection.auth) {
|
||||
break
|
||||
}
|
||||
clientCollection.auth = {} as { verify?: true } & SanitizedCollectionConfig['auth']
|
||||
if (collection.auth.cookies) {
|
||||
clientCollection.auth.cookies = collection.auth.cookies
|
||||
}
|
||||
if (collection.auth.depth !== undefined) {
|
||||
// Check for undefined as it can be a number (0)
|
||||
clientCollection.auth.depth = collection.auth.depth
|
||||
}
|
||||
if (collection.auth.disableLocalStrategy) {
|
||||
clientCollection.auth.disableLocalStrategy = collection.auth.disableLocalStrategy
|
||||
}
|
||||
if (collection.auth.lockTime !== undefined) {
|
||||
// Check for undefined as it can be a number (0)
|
||||
clientCollection.auth.lockTime = collection.auth.lockTime
|
||||
}
|
||||
if (collection.auth.loginWithUsername) {
|
||||
clientCollection.auth.loginWithUsername = collection.auth.loginWithUsername
|
||||
}
|
||||
if (collection.auth.maxLoginAttempts !== undefined) {
|
||||
// Check for undefined as it can be a number (0)
|
||||
clientCollection.auth.maxLoginAttempts = collection.auth.maxLoginAttempts
|
||||
}
|
||||
if (collection.auth.removeTokenFromResponses) {
|
||||
clientCollection.auth.removeTokenFromResponses = collection.auth.removeTokenFromResponses
|
||||
}
|
||||
|
||||
if (collection.auth.useAPIKey) {
|
||||
clientCollection.auth.useAPIKey = collection.auth.useAPIKey
|
||||
}
|
||||
if (collection.auth.tokenExpiration) {
|
||||
clientCollection.auth.tokenExpiration = collection.auth.tokenExpiration
|
||||
}
|
||||
if (collection.auth.verify) {
|
||||
clientCollection.auth.verify = true
|
||||
}
|
||||
break
|
||||
case 'fields':
|
||||
clientCollection.fields = createClientFields({
|
||||
defaultIDType,
|
||||
fields: collection.fields,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
break
|
||||
case 'labels':
|
||||
clientCollection.labels = {
|
||||
plural:
|
||||
typeof collection.labels.plural === 'function'
|
||||
? collection.labels.plural({ t: i18n.t })
|
||||
: collection.labels.plural,
|
||||
singular:
|
||||
typeof collection.labels.singular === 'function'
|
||||
? collection.labels.singular({ t: i18n.t })
|
||||
: collection.labels.singular,
|
||||
}
|
||||
break
|
||||
case 'upload':
|
||||
if (!collection.upload) {
|
||||
break
|
||||
}
|
||||
clientCollection.upload = {} as SanitizedUploadConfig
|
||||
for (const uploadKey in collection.upload) {
|
||||
if (serverOnlyUploadProperties.includes(uploadKey as any)) {
|
||||
continue
|
||||
}
|
||||
if (uploadKey === 'imageSizes') {
|
||||
clientCollection.upload.imageSizes = collection.upload.imageSizes.map((size) => {
|
||||
const sanitizedSize = { ...size }
|
||||
if ('generateImageName' in sanitizedSize) {
|
||||
delete sanitizedSize.generateImageName
|
||||
}
|
||||
return sanitizedSize
|
||||
})
|
||||
} else {
|
||||
clientCollection.upload[uploadKey] = collection.upload[uploadKey]
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
break
|
||||
default:
|
||||
clientCollection[key] = collection[key]
|
||||
}
|
||||
}
|
||||
|
||||
if ('auth' in clientCollection && typeof clientCollection.auth === 'object') {
|
||||
delete clientCollection.auth.strategies
|
||||
delete clientCollection.auth.forgotPassword
|
||||
delete clientCollection.auth.verify
|
||||
}
|
||||
|
||||
if (collection.labels) {
|
||||
Object.entries(collection.labels).forEach(([labelType, collectionLabel]) => {
|
||||
if (typeof collectionLabel === 'function') {
|
||||
clientCollection.labels[labelType] = collectionLabel({ t: i18n.t })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!clientCollection.admin) {
|
||||
clientCollection.admin = {} as ClientCollectionConfig['admin']
|
||||
}
|
||||
|
||||
serverOnlyCollectionAdminProperties.forEach((key) => {
|
||||
if (key in clientCollection.admin) {
|
||||
delete clientCollection.admin[key]
|
||||
}
|
||||
})
|
||||
|
||||
if (collection.admin.preview) {
|
||||
clientCollection.admin.preview = true
|
||||
}
|
||||
|
||||
let description = undefined
|
||||
|
||||
if (collection.admin?.description) {
|
||||
if (
|
||||
typeof collection.admin?.description === 'string' ||
|
||||
typeof collection.admin?.description === 'object'
|
||||
) {
|
||||
description = collection.admin.description
|
||||
} else if (typeof collection.admin?.description === 'function') {
|
||||
description = collection.admin?.description({ t: i18n.t })
|
||||
}
|
||||
}
|
||||
|
||||
if (description) {
|
||||
clientCollection.admin.description = description
|
||||
}
|
||||
|
||||
if (
|
||||
'livePreview' in clientCollection.admin &&
|
||||
clientCollection.admin.livePreview &&
|
||||
'url' in clientCollection.admin.livePreview
|
||||
) {
|
||||
delete clientCollection.admin.livePreview.url
|
||||
}
|
||||
|
||||
return clientCollection
|
||||
return clientCollection as ClientCollectionConfig
|
||||
}
|
||||
|
||||
export const createClientCollectionConfigs = ({
|
||||
|
||||
@@ -91,6 +91,11 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
|
||||
return null
|
||||
}
|
||||
|
||||
if (!result.version) {
|
||||
// Fallback if not selected
|
||||
;(result as any).version = {}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -93,6 +93,10 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
|
||||
docs: await Promise.all(
|
||||
paginatedDocs.docs.map(async (doc) => {
|
||||
const docRef = doc
|
||||
// Fallback if not selected
|
||||
if (!docRef.version) {
|
||||
;(docRef as any).version = {}
|
||||
}
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
|
||||
@@ -7,7 +7,31 @@ import { sanitizeConfig } from './sanitize.js'
|
||||
* @param config Payload Config
|
||||
* @returns Built and sanitized Payload Config
|
||||
*/
|
||||
export async function buildConfig(config: Config): Promise<SanitizedConfig> {
|
||||
export async function buildConfig(
|
||||
_config: (() => Config) | Config,
|
||||
): Promise<(() => Promise<SanitizedConfig>) | SanitizedConfig> {
|
||||
if (typeof _config !== 'function') {
|
||||
console.warn(
|
||||
'For optimal performance, buildConfig should be called with a function that returns a config object. Otherwise, you will notice increased memory usage and decreased performance during development.',
|
||||
)
|
||||
// We could still return a function that returns the sanitized config here,
|
||||
// so that it's cached properly and not loaded multiple times after every page transition.
|
||||
// However, in order for this to be backwards compatible, we return the sanitized config directly, the old way.
|
||||
// Otherwise, the imported config would suddenly be a function when imported, which may break standalone scripts
|
||||
return await loadAndSanitizeConfig(() => _config)
|
||||
} else {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return await loadAndSanitizeConfig(_config)
|
||||
} else {
|
||||
return async () => {
|
||||
return await loadAndSanitizeConfig(_config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loadAndSanitizeConfig = async (configFn: () => Config): Promise<SanitizedConfig> => {
|
||||
const config = configFn()
|
||||
if (Array.isArray(config.plugins)) {
|
||||
const configAfterPlugins = await config.plugins.reduce(async (acc, plugin) => {
|
||||
const configAfterPlugin = await acc
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { DeepPartial } from 'ts-essentials'
|
||||
|
||||
import type { ImportMap } from '../bin/generateImportMap/index.js'
|
||||
import type {
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
createClientCollectionConfigs,
|
||||
} from '../collections/config/client.js'
|
||||
import { type ClientGlobalConfig, createClientGlobalConfigs } from '../globals/config/client.js'
|
||||
import { deepCopyObjectSimple } from '../utilities/deepCopyObject.js'
|
||||
|
||||
export type ServerOnlyRootProperties = keyof Pick<
|
||||
SanitizedConfig,
|
||||
@@ -39,7 +39,6 @@ export type ServerOnlyRootAdminProperties = keyof Pick<SanitizedConfig['admin'],
|
||||
|
||||
export type ClientConfig = {
|
||||
admin: {
|
||||
components: null
|
||||
dependencies?: Record<string, React.ReactNode>
|
||||
livePreview?: Omit<LivePreviewConfig, ServerOnlyLivePreviewProperties>
|
||||
} & Omit<SanitizedConfig['admin'], 'components' | 'dependencies' | 'livePreview'>
|
||||
@@ -81,56 +80,95 @@ export const createClientConfig = ({
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): ClientConfig => {
|
||||
// We can use deepCopySimple here, as the clientConfig should be JSON serializable anyways, since it will be sent from server => client
|
||||
const clientConfig = deepCopyObjectSimple(config, true) as unknown as ClientConfig
|
||||
const clientConfig = {} as DeepPartial<ClientConfig>
|
||||
|
||||
for (const key of serverOnlyConfigProperties) {
|
||||
if (key in clientConfig) {
|
||||
delete clientConfig[key]
|
||||
for (const key in config) {
|
||||
if (serverOnlyConfigProperties.includes(key as any)) {
|
||||
continue
|
||||
}
|
||||
switch (key) {
|
||||
case 'admin':
|
||||
clientConfig.admin = {
|
||||
autoLogin: config.admin.autoLogin,
|
||||
avatar: config.admin.avatar,
|
||||
custom: config.admin.custom,
|
||||
dateFormat: config.admin.dateFormat,
|
||||
dependencies: config.admin.dependencies,
|
||||
disable: config.admin.disable,
|
||||
importMap: config.admin.importMap,
|
||||
meta: config.admin.meta,
|
||||
routes: config.admin.routes,
|
||||
theme: config.admin.theme,
|
||||
user: config.admin.user,
|
||||
}
|
||||
if (config.admin.livePreview) {
|
||||
clientConfig.admin.livePreview = {}
|
||||
|
||||
if (config.admin.livePreview.breakpoints) {
|
||||
clientConfig.admin.livePreview.breakpoints = config.admin.livePreview.breakpoints
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'collections':
|
||||
;(clientConfig.collections as ClientCollectionConfig[]) = createClientCollectionConfigs({
|
||||
collections: config.collections,
|
||||
defaultIDType: config.db.defaultIDType,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
break
|
||||
case 'globals':
|
||||
;(clientConfig.globals as ClientGlobalConfig[]) = createClientGlobalConfigs({
|
||||
defaultIDType: config.db.defaultIDType,
|
||||
globals: config.globals,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
break
|
||||
case 'i18n':
|
||||
clientConfig.i18n = {
|
||||
fallbackLanguage: config.i18n.fallbackLanguage,
|
||||
translations: config.i18n.translations,
|
||||
}
|
||||
break
|
||||
case 'localization':
|
||||
if (typeof config.localization === 'object' && config.localization) {
|
||||
clientConfig.localization = {}
|
||||
if (config.localization.defaultLocale) {
|
||||
clientConfig.localization.defaultLocale = config.localization.defaultLocale
|
||||
}
|
||||
if (config.localization.fallback) {
|
||||
clientConfig.localization.fallback = config.localization.fallback
|
||||
}
|
||||
if (config.localization.localeCodes) {
|
||||
clientConfig.localization.localeCodes = config.localization.localeCodes
|
||||
}
|
||||
if (config.localization.locales) {
|
||||
clientConfig.localization.locales = []
|
||||
for (const locale of config.localization.locales) {
|
||||
if (locale) {
|
||||
const clientLocale: Partial<(typeof config.localization.locales)[0]> = {}
|
||||
if (locale.code) {
|
||||
clientLocale.code = locale.code
|
||||
}
|
||||
if (locale.fallbackLocale) {
|
||||
clientLocale.fallbackLocale = locale.fallbackLocale
|
||||
}
|
||||
if (locale.label) {
|
||||
clientLocale.label = locale.label
|
||||
}
|
||||
if (locale.rtl) {
|
||||
clientLocale.rtl = locale.rtl
|
||||
}
|
||||
clientConfig.localization.locales.push(clientLocale)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
clientConfig[key] = config[key]
|
||||
}
|
||||
}
|
||||
|
||||
if ('localization' in clientConfig && clientConfig.localization) {
|
||||
for (const locale of clientConfig.localization.locales) {
|
||||
delete locale.toString
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
'i18n' in clientConfig &&
|
||||
'supportedLanguages' in clientConfig.i18n &&
|
||||
clientConfig.i18n.supportedLanguages
|
||||
) {
|
||||
delete clientConfig.i18n.supportedLanguages
|
||||
}
|
||||
|
||||
if (!clientConfig.admin) {
|
||||
clientConfig.admin = {} as ClientConfig['admin']
|
||||
}
|
||||
|
||||
clientConfig.admin.components = null
|
||||
|
||||
if (
|
||||
'livePreview' in clientConfig.admin &&
|
||||
clientConfig.admin.livePreview &&
|
||||
'url' in clientConfig.admin.livePreview
|
||||
) {
|
||||
delete clientConfig.admin.livePreview.url
|
||||
}
|
||||
|
||||
clientConfig.collections = createClientCollectionConfigs({
|
||||
collections: config.collections,
|
||||
defaultIDType: config.db.defaultIDType,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
clientConfig.globals = createClientGlobalConfigs({
|
||||
defaultIDType: config.db.defaultIDType,
|
||||
globals: config.globals,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
return clientConfig
|
||||
return clientConfig as ClientConfig
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig>
|
||||
}
|
||||
|
||||
export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedConfig> => {
|
||||
console.log('Sanitizing config')
|
||||
const configWithDefaults = {
|
||||
...defaults,
|
||||
...incomingConfig,
|
||||
|
||||
@@ -1090,6 +1090,10 @@ export type Config = {
|
||||
upload?: FetchAPIFileUploadOptions
|
||||
}
|
||||
|
||||
export type GetSanitizedConfig = () => Promise<SanitizedConfig>
|
||||
|
||||
export type ConfigImport = GetSanitizedConfig | Promise<SanitizedConfig> | SanitizedConfig
|
||||
|
||||
export type SanitizedConfig = {
|
||||
collections: SanitizedCollectionConfig[]
|
||||
/** Default richtext editor to use for richText fields */
|
||||
|
||||
@@ -39,61 +39,97 @@ export type ServerOnlyFieldProperties =
|
||||
|
||||
export type ServerOnlyFieldAdminProperties = keyof Pick<FieldBase['admin'], 'condition'>
|
||||
|
||||
const serverOnlyFieldProperties: Partial<ServerOnlyFieldProperties>[] = [
|
||||
'hooks',
|
||||
'access',
|
||||
'validate',
|
||||
'defaultValue',
|
||||
'filterOptions', // This is a `relationship` and `upload` only property
|
||||
'editor', // This is a `richText` only property
|
||||
'custom',
|
||||
'typescriptSchema',
|
||||
'dbName', // can be a function
|
||||
'enumName', // can be a function
|
||||
// the following props are handled separately (see below):
|
||||
// `label`
|
||||
// `fields`
|
||||
// `blocks`
|
||||
// `tabs`
|
||||
// `admin`
|
||||
]
|
||||
const serverOnlyFieldAdminProperties: Partial<ServerOnlyFieldAdminProperties>[] = ['condition']
|
||||
type FieldWithDescription = {
|
||||
admin: AdminClient
|
||||
} & ClientField
|
||||
|
||||
export const createClientField = ({
|
||||
clientField = {} as ClientField,
|
||||
defaultIDType,
|
||||
field: incomingField,
|
||||
i18n,
|
||||
importMap,
|
||||
}: {
|
||||
clientField?: ClientField
|
||||
defaultIDType: Payload['config']['db']['defaultIDType']
|
||||
field: Field
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): ClientField => {
|
||||
const serverOnlyFieldProperties: Partial<ServerOnlyFieldProperties>[] = [
|
||||
'hooks',
|
||||
'access',
|
||||
'validate',
|
||||
'defaultValue',
|
||||
'filterOptions', // This is a `relationship` and `upload` only property
|
||||
'editor', // This is a `richText` only property
|
||||
'custom',
|
||||
'typescriptSchema',
|
||||
'dbName', // can be a function
|
||||
'enumName', // can be a function
|
||||
// the following props are handled separately (see below):
|
||||
// `label`
|
||||
// `fields`
|
||||
// `blocks`
|
||||
// `tabs`
|
||||
// `admin`
|
||||
]
|
||||
|
||||
clientField.admin = clientField.admin || {}
|
||||
// clientField.admin.readOnly = true
|
||||
|
||||
serverOnlyFieldProperties.forEach((key) => {
|
||||
if (key in clientField) {
|
||||
delete clientField[key]
|
||||
}
|
||||
})
|
||||
const clientField: ClientField = {} as ClientField
|
||||
|
||||
const isHidden = 'hidden' in incomingField && incomingField?.hidden
|
||||
const disabledFromAdmin =
|
||||
incomingField?.admin && 'disabled' in incomingField.admin && incomingField.admin.disabled
|
||||
|
||||
if (fieldAffectsData(clientField) && (isHidden || disabledFromAdmin)) {
|
||||
if (fieldAffectsData(incomingField) && (isHidden || disabledFromAdmin)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (
|
||||
'label' in clientField &&
|
||||
'label' in incomingField &&
|
||||
typeof incomingField.label === 'function'
|
||||
) {
|
||||
clientField.label = incomingField.label({ t: i18n.t })
|
||||
for (const key in incomingField) {
|
||||
if (serverOnlyFieldProperties.includes(key as any)) {
|
||||
continue
|
||||
}
|
||||
switch (key) {
|
||||
case 'admin':
|
||||
if (!incomingField.admin) {
|
||||
break
|
||||
}
|
||||
clientField.admin = {} as AdminClient
|
||||
for (const adminKey in incomingField.admin) {
|
||||
if (serverOnlyFieldAdminProperties.includes(adminKey as any)) {
|
||||
continue
|
||||
}
|
||||
switch (adminKey) {
|
||||
case 'description':
|
||||
if ('description' in incomingField.admin) {
|
||||
if (typeof incomingField.admin?.description !== 'function') {
|
||||
;(clientField as FieldWithDescription).admin.description =
|
||||
incomingField.admin.description
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
default:
|
||||
clientField.admin[adminKey] = incomingField.admin[adminKey]
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'blocks':
|
||||
case 'fields':
|
||||
case 'tabs':
|
||||
// Skip - we handle sub-fields in the switch below
|
||||
break
|
||||
case 'label':
|
||||
//@ts-expect-error - would need to type narrow
|
||||
if (typeof incomingField.label === 'function') {
|
||||
//@ts-expect-error - would need to type narrow
|
||||
clientField.label = incomingField.label({ t: i18n.t })
|
||||
} else {
|
||||
//@ts-expect-error - would need to type narrow
|
||||
clientField.label = incomingField.label
|
||||
}
|
||||
break
|
||||
default:
|
||||
clientField[key] = incomingField[key]
|
||||
}
|
||||
}
|
||||
|
||||
switch (incomingField.type) {
|
||||
@@ -108,7 +144,6 @@ export const createClientField = ({
|
||||
}
|
||||
|
||||
field.fields = createClientFields({
|
||||
clientFields: field.fields,
|
||||
defaultIDType,
|
||||
disableAddingID: incomingField.type !== 'array',
|
||||
fields: incomingField.fields,
|
||||
@@ -167,7 +202,6 @@ export const createClientField = ({
|
||||
}
|
||||
|
||||
clientBlock.fields = createClientFields({
|
||||
clientFields: clientBlock.fields,
|
||||
defaultIDType,
|
||||
fields: block.fields,
|
||||
i18n,
|
||||
@@ -225,24 +259,29 @@ export const createClientField = ({
|
||||
const field = clientField as unknown as TabsFieldClient
|
||||
|
||||
if (incomingField.tabs?.length) {
|
||||
field.tabs = []
|
||||
|
||||
for (let i = 0; i < incomingField.tabs.length; i++) {
|
||||
const tab = incomingField.tabs[i]
|
||||
const clientTab = field.tabs[i]
|
||||
const clientTab = {} as unknown as TabsFieldClient['tabs'][0]
|
||||
|
||||
serverOnlyFieldProperties.forEach((key) => {
|
||||
if (key in clientTab) {
|
||||
delete clientTab[key]
|
||||
for (const key in tab) {
|
||||
if (serverOnlyFieldProperties.includes(key as any)) {
|
||||
continue
|
||||
}
|
||||
})
|
||||
|
||||
clientTab.fields = createClientFields({
|
||||
clientFields: clientTab.fields,
|
||||
defaultIDType,
|
||||
disableAddingID: true,
|
||||
fields: tab.fields,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
if (key === 'fields') {
|
||||
clientTab.fields = createClientFields({
|
||||
defaultIDType,
|
||||
disableAddingID: true,
|
||||
fields: tab.fields,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
} else {
|
||||
clientTab[key] = tab[key]
|
||||
}
|
||||
}
|
||||
field.tabs[i] = clientTab
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,70 +292,43 @@ export const createClientField = ({
|
||||
break
|
||||
}
|
||||
|
||||
const serverOnlyFieldAdminProperties: Partial<ServerOnlyFieldAdminProperties>[] = ['condition']
|
||||
|
||||
if (!clientField.admin) {
|
||||
clientField.admin = {} as AdminClient
|
||||
}
|
||||
|
||||
serverOnlyFieldAdminProperties.forEach((key) => {
|
||||
if (key in clientField.admin) {
|
||||
delete clientField.admin[key]
|
||||
}
|
||||
})
|
||||
|
||||
type FieldWithDescription = {
|
||||
admin: AdminClient
|
||||
} & ClientField
|
||||
|
||||
if (incomingField.admin && 'description' in incomingField.admin) {
|
||||
if (typeof incomingField.admin?.description === 'function') {
|
||||
delete (clientField as FieldWithDescription).admin.description
|
||||
} else {
|
||||
;(clientField as FieldWithDescription).admin.description = incomingField.admin.description
|
||||
}
|
||||
}
|
||||
|
||||
return clientField
|
||||
}
|
||||
|
||||
export const createClientFields = ({
|
||||
clientFields,
|
||||
defaultIDType,
|
||||
disableAddingID,
|
||||
fields,
|
||||
i18n,
|
||||
importMap,
|
||||
}: {
|
||||
clientFields: ClientField[]
|
||||
defaultIDType: Payload['config']['db']['defaultIDType']
|
||||
disableAddingID?: boolean
|
||||
fields: Field[]
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): ClientField[] => {
|
||||
const newClientFields: ClientField[] = []
|
||||
const clientFields: ClientField[] = []
|
||||
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const field = fields[i]
|
||||
|
||||
const newField = createClientField({
|
||||
clientField: clientFields[i],
|
||||
const clientField = createClientField({
|
||||
defaultIDType,
|
||||
field,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
if (newField) {
|
||||
newClientFields.push(newField)
|
||||
if (clientField) {
|
||||
clientFields.push(clientField)
|
||||
}
|
||||
}
|
||||
|
||||
const hasID = flattenTopLevelFields(fields).some((f) => fieldAffectsData(f) && f.name === 'id')
|
||||
|
||||
if (!disableAddingID && !hasID) {
|
||||
newClientFields.push({
|
||||
clientFields.push({
|
||||
name: 'id',
|
||||
type: defaultIDType,
|
||||
admin: {
|
||||
@@ -330,5 +342,5 @@ export const createClientFields = ({
|
||||
})
|
||||
}
|
||||
|
||||
return newClientFields
|
||||
return clientFields
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ClientField, Field, TabAsField } from './config/types.js'
|
||||
|
||||
import { fieldAffectsData } from './config/types.js'
|
||||
import type { ClientField, Field, TabAsField, TabAsFieldClient } from './config/types.js'
|
||||
|
||||
type Args = {
|
||||
field: ClientField | Field | TabAsField
|
||||
field: ClientField | Field | TabAsField | TabAsFieldClient
|
||||
index: number
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
|
||||
@@ -10,14 +10,16 @@ import type { Payload } from '../../types/index.js'
|
||||
import type { SanitizedGlobalConfig } from './types.js'
|
||||
|
||||
import { type ClientField, createClientFields } from '../../fields/config/client.js'
|
||||
import { deepCopyObjectSimple } from '../../utilities/deepCopyObject.js'
|
||||
|
||||
export type ServerOnlyGlobalProperties = keyof Pick<
|
||||
SanitizedGlobalConfig,
|
||||
'access' | 'admin' | 'custom' | 'endpoints' | 'fields' | 'flattenedFields' | 'hooks'
|
||||
>
|
||||
|
||||
export type ServerOnlyGlobalAdminProperties = keyof Pick<SanitizedGlobalConfig['admin'], 'hidden'>
|
||||
export type ServerOnlyGlobalAdminProperties = keyof Pick<
|
||||
SanitizedGlobalConfig['admin'],
|
||||
'components' | 'hidden'
|
||||
>
|
||||
|
||||
export type ClientGlobalConfig = {
|
||||
admin: {
|
||||
@@ -40,7 +42,10 @@ const serverOnlyProperties: Partial<ServerOnlyGlobalProperties>[] = [
|
||||
// `admin` is handled separately
|
||||
]
|
||||
|
||||
const serverOnlyGlobalAdminProperties: Partial<ServerOnlyGlobalAdminProperties>[] = ['hidden']
|
||||
const serverOnlyGlobalAdminProperties: Partial<ServerOnlyGlobalAdminProperties>[] = [
|
||||
'hidden',
|
||||
'components',
|
||||
]
|
||||
|
||||
export const createClientGlobalConfig = ({
|
||||
defaultIDType,
|
||||
@@ -53,44 +58,55 @@ export const createClientGlobalConfig = ({
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): ClientGlobalConfig => {
|
||||
const clientGlobal = deepCopyObjectSimple(global, true) as unknown as ClientGlobalConfig
|
||||
const clientGlobal = {} as ClientGlobalConfig
|
||||
|
||||
clientGlobal.fields = createClientFields({
|
||||
clientFields: clientGlobal?.fields || [],
|
||||
defaultIDType,
|
||||
fields: global.fields,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
serverOnlyProperties.forEach((key) => {
|
||||
if (key in clientGlobal) {
|
||||
delete clientGlobal[key]
|
||||
for (const key in global) {
|
||||
if (serverOnlyProperties.includes(key as any)) {
|
||||
continue
|
||||
}
|
||||
})
|
||||
|
||||
if (!clientGlobal.admin) {
|
||||
clientGlobal.admin = {} as ClientGlobalConfig['admin']
|
||||
}
|
||||
|
||||
serverOnlyGlobalAdminProperties.forEach((key) => {
|
||||
if (key in clientGlobal.admin) {
|
||||
delete clientGlobal.admin[key]
|
||||
switch (key) {
|
||||
case 'admin':
|
||||
if (!global.admin) {
|
||||
break
|
||||
}
|
||||
clientGlobal.admin = {} as ClientGlobalConfig['admin']
|
||||
for (const adminKey in global.admin) {
|
||||
if (serverOnlyGlobalAdminProperties.includes(adminKey as any)) {
|
||||
continue
|
||||
}
|
||||
switch (adminKey) {
|
||||
case 'livePreview':
|
||||
if (!global.admin.livePreview) {
|
||||
break
|
||||
}
|
||||
clientGlobal.admin.livePreview = {}
|
||||
if (global.admin.livePreview.breakpoints) {
|
||||
clientGlobal.admin.livePreview.breakpoints = global.admin.livePreview.breakpoints
|
||||
}
|
||||
break
|
||||
case 'preview':
|
||||
if (global.admin.preview) {
|
||||
clientGlobal.admin.preview = true
|
||||
}
|
||||
break
|
||||
default:
|
||||
clientGlobal.admin[adminKey] = global.admin[adminKey]
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'fields':
|
||||
clientGlobal.fields = createClientFields({
|
||||
defaultIDType,
|
||||
fields: global.fields,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
break
|
||||
default: {
|
||||
clientGlobal[key] = global[key]
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (global.admin.preview) {
|
||||
clientGlobal.admin.preview = true
|
||||
}
|
||||
|
||||
clientGlobal.admin.components = null
|
||||
|
||||
if (
|
||||
'livePreview' in clientGlobal.admin &&
|
||||
clientGlobal.admin.livePreview &&
|
||||
'url' in clientGlobal.admin.livePreview
|
||||
) {
|
||||
delete clientGlobal.admin.livePreview.url
|
||||
}
|
||||
|
||||
return clientGlobal
|
||||
|
||||
@@ -90,6 +90,10 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
|
||||
// Clone the result - it may have come back memoized
|
||||
let result: any = deepCopyObjectSimple(results[0])
|
||||
|
||||
if (!result.version) {
|
||||
result.version = {}
|
||||
}
|
||||
|
||||
// Patch globalType onto version doc
|
||||
result.version.globalType = globalConfig.slug
|
||||
|
||||
|
||||
@@ -90,6 +90,10 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
|
||||
...paginatedDocs,
|
||||
docs: await Promise.all(
|
||||
paginatedDocs.docs.map(async (data) => {
|
||||
if (!data.version) {
|
||||
// Fallback if not selected
|
||||
;(data as any).version = {}
|
||||
}
|
||||
return {
|
||||
...data,
|
||||
version: await afterRead<T>({
|
||||
|
||||
@@ -44,7 +44,7 @@ import type {
|
||||
ManyOptions as UpdateManyOptions,
|
||||
Options as UpdateOptions,
|
||||
} from './collections/operations/local/update.js'
|
||||
import type { InitOptions, SanitizedConfig } from './config/types.js'
|
||||
import type { ConfigImport, InitOptions, SanitizedConfig } from './config/types.js'
|
||||
import type { BaseDatabaseAdapter, PaginatedDocs } from './database/types.js'
|
||||
import type { InitializedEmailAdapter } from './email/types.js'
|
||||
import type { DataFromGlobalSlug, Globals, SelectFromGlobalSlug } from './globals/config/types.js'
|
||||
@@ -555,7 +555,7 @@ export class BasePayload {
|
||||
!checkedDependencies
|
||||
) {
|
||||
checkedDependencies = true
|
||||
await checkPayloadDependencies()
|
||||
void checkPayloadDependencies()
|
||||
}
|
||||
|
||||
this.importMap = options.importMap
|
||||
@@ -782,8 +782,37 @@ export const reload = async (
|
||||
if (payload.db.connect) {
|
||||
await payload.db.connect({ hotReload: true })
|
||||
}
|
||||
global._payload_clientConfig = null
|
||||
global._payload_schemaMap = null
|
||||
global._payload_clientSchemaMap = null
|
||||
global._payload_doNotCacheClientConfig = true // This will help refreshing the client config cache more reliably. If you remove this, please test HMR + client config refreshing (do new fields appear in the document?)
|
||||
global._payload_doNotCacheSchemaMap = true
|
||||
global._payload_doNotCacheClientSchemaMap = true
|
||||
}
|
||||
|
||||
export const getConfig = async (configImport: ConfigImport): Promise<SanitizedConfig> => {
|
||||
if (global._payload_config) {
|
||||
let config = await global._payload_config
|
||||
if (typeof config === 'function') {
|
||||
config = await config()
|
||||
}
|
||||
return await config
|
||||
}
|
||||
if (typeof configImport === 'function') {
|
||||
global._payload_config = configImport()
|
||||
await global._payload_config
|
||||
return global._payload_config
|
||||
} else {
|
||||
global._payload_config = configImport
|
||||
global._payload_config = await global._payload_config
|
||||
|
||||
if (typeof global._payload_config === 'function') {
|
||||
global._payload_config = global._payload_config()
|
||||
global._payload_config = await global._payload_config
|
||||
}
|
||||
return global._payload_config
|
||||
}
|
||||
}
|
||||
export const getPayload = async (
|
||||
options: Pick<InitOptions, 'config' | 'importMap'>,
|
||||
): Promise<Payload> => {
|
||||
|
||||
@@ -212,10 +212,8 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
editor.update(() => {
|
||||
const node = $getNodeByKey(nodeKey)
|
||||
if (node && $isBlockNode(node)) {
|
||||
const newData = {
|
||||
...newFormStateData,
|
||||
blockType: formData.blockType,
|
||||
}
|
||||
const newData = newFormStateData
|
||||
newData.blockType = formData.blockType
|
||||
node.setFields(newData)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -14,7 +14,6 @@ import type { JSX } from 'react'
|
||||
|
||||
import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode.js'
|
||||
import ObjectID from 'bson-objectid'
|
||||
import { deepCopyObjectSimple } from 'payload/shared'
|
||||
|
||||
type BaseBlockFields<TBlockFields extends JsonObject = JsonObject> = {
|
||||
/** Block form data */
|
||||
@@ -121,10 +120,8 @@ export class ServerBlockNode extends DecoratorBlockNode {
|
||||
}
|
||||
|
||||
setFields(fields: BlockFields): void {
|
||||
const fieldsCopy = deepCopyObjectSimple(fields)
|
||||
|
||||
const writable = this.getWritable()
|
||||
writable.__fields = fieldsCopy
|
||||
writable.__fields = fields
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import type { JSX } from 'react'
|
||||
|
||||
import ObjectID from 'bson-objectid'
|
||||
import { DecoratorNode } from 'lexical'
|
||||
import { deepCopyObjectSimple } from 'payload/shared'
|
||||
|
||||
export type InlineBlockFields = {
|
||||
/** Block form data */
|
||||
@@ -108,10 +107,8 @@ export class ServerInlineBlockNode extends DecoratorNode<null | React.ReactEleme
|
||||
}
|
||||
|
||||
setFields(fields: InlineBlockFields): void {
|
||||
const fieldsCopy = deepCopyObjectSimple(fields)
|
||||
|
||||
const writable = this.getWritable()
|
||||
writable.__fields = fieldsCopy
|
||||
writable.__fields = fields
|
||||
}
|
||||
|
||||
updateDOM(): boolean {
|
||||
|
||||
@@ -9,7 +9,6 @@ import type {
|
||||
|
||||
import escapeHTML from 'escape-html'
|
||||
import { sanitizeFields } from 'payload'
|
||||
import { deepCopyObject } from 'payload/shared'
|
||||
|
||||
import type { ClientProps } from '../client/index.js'
|
||||
|
||||
@@ -78,7 +77,7 @@ export const LinkFeature = createServerFeature<
|
||||
const validRelationships = _config.collections.map((c) => c.slug) || []
|
||||
|
||||
const _transformedFields = transformExtraFields(
|
||||
props.fields ? deepCopyObject(props.fields) : null,
|
||||
props.fields ? props.fields : null,
|
||||
_config,
|
||||
props.enabledCollections,
|
||||
props.disabledCollections,
|
||||
@@ -97,7 +96,7 @@ export const LinkFeature = createServerFeature<
|
||||
// the text field is not included in the node data.
|
||||
// Thus, for tasks like validation, we do not want to pass it a text field in the schema which will never have data.
|
||||
// Otherwise, it will cause a validation error (field is required).
|
||||
const sanitizedFieldsWithoutText = deepCopyObject(sanitizedFields).filter(
|
||||
const sanitizedFieldsWithoutText = sanitizedFields.filter(
|
||||
(field) => !('name' in field) || field.name !== 'text',
|
||||
)
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@ export const RscEntryLexicalField: React.FC<
|
||||
const field: RichTextFieldType = args.field as RichTextFieldType
|
||||
const path = args.path ?? (args.clientField as RichTextFieldClient).name
|
||||
const schemaPath = args.schemaPath ?? path
|
||||
|
||||
const { clientFeatures, featureClientSchemaMap } = initLexicalFeatures({
|
||||
clientFieldSchemaMap: args.clientFieldSchemaMap,
|
||||
fieldSchemaMap: args.fieldSchemaMap,
|
||||
i18n: args.i18n,
|
||||
path,
|
||||
@@ -43,6 +45,7 @@ export const RscEntryLexicalField: React.FC<
|
||||
initialLexicalFormState = await buildInitialState({
|
||||
context: {
|
||||
id: args.id,
|
||||
clientFieldSchemaMap: args.clientFieldSchemaMap,
|
||||
collectionSlug: args.collectionSlug,
|
||||
field,
|
||||
fieldSchemaMap: args.fieldSchemaMap,
|
||||
@@ -60,7 +63,7 @@ export const RscEntryLexicalField: React.FC<
|
||||
const props: LexicalRichTextFieldProps = {
|
||||
admin: args.admin,
|
||||
clientFeatures,
|
||||
featureClientSchemaMap,
|
||||
featureClientSchemaMap, // TODO: Does client need this? Why cant this just live in the server
|
||||
field: args.clientField as RichTextFieldClient,
|
||||
forceRender: args.forceRender,
|
||||
initialLexicalFormState,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import { cache } from 'react'
|
||||
|
||||
import { type SanitizedServerEditorConfig } from './index.js'
|
||||
import { defaultEditorConfig } from './lexical/config/server/default.js'
|
||||
import { sanitizeServerEditorConfig } from './lexical/config/server/sanitize.js'
|
||||
|
||||
let cachedDefaultSanitizedServerEditorConfig:
|
||||
| null
|
||||
| Promise<SanitizedServerEditorConfig>
|
||||
| SanitizedServerEditorConfig = (global as any)
|
||||
._payload_lexical_defaultSanitizedServerEditorConfig
|
||||
|
||||
if (!cachedDefaultSanitizedServerEditorConfig) {
|
||||
cachedDefaultSanitizedServerEditorConfig = (
|
||||
global as any
|
||||
)._payload_lexical_defaultSanitizedServerEditorConfig = null
|
||||
}
|
||||
|
||||
export const getDefaultSanitizedEditorConfig = cache(
|
||||
async (args: {
|
||||
config: SanitizedConfig
|
||||
parentIsLocalized: boolean
|
||||
}): Promise<SanitizedServerEditorConfig> => {
|
||||
const { config, parentIsLocalized } = args
|
||||
|
||||
if (cachedDefaultSanitizedServerEditorConfig) {
|
||||
return await cachedDefaultSanitizedServerEditorConfig
|
||||
}
|
||||
|
||||
cachedDefaultSanitizedServerEditorConfig = sanitizeServerEditorConfig(
|
||||
defaultEditorConfig,
|
||||
config,
|
||||
parentIsLocalized,
|
||||
)
|
||||
;(global as any).payload_lexical_defaultSanitizedServerEditorConfig =
|
||||
cachedDefaultSanitizedServerEditorConfig
|
||||
|
||||
cachedDefaultSanitizedServerEditorConfig = await cachedDefaultSanitizedServerEditorConfig
|
||||
;(global as any).payload_lexical_defaultSanitizedServerEditorConfig =
|
||||
cachedDefaultSanitizedServerEditorConfig
|
||||
|
||||
return cachedDefaultSanitizedServerEditorConfig
|
||||
},
|
||||
)
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
beforeChangeTraverseFields,
|
||||
beforeValidateTraverseFields,
|
||||
checkDependencies,
|
||||
deepCopyObject,
|
||||
deepCopyObjectSimple,
|
||||
withNullableJSONSchemaType,
|
||||
} from 'payload'
|
||||
|
||||
@@ -21,89 +19,80 @@ import type {
|
||||
LexicalRichTextAdapterProvider,
|
||||
} from './types.js'
|
||||
|
||||
import { getDefaultSanitizedEditorConfig } from './getDefaultSanitizedEditorConfig.js'
|
||||
import { i18n } from './i18n.js'
|
||||
import { defaultEditorConfig, defaultEditorFeatures } from './lexical/config/server/default.js'
|
||||
import { loadFeatures } from './lexical/config/server/loader.js'
|
||||
import {
|
||||
sanitizeServerEditorConfig,
|
||||
sanitizeServerFeatures,
|
||||
} from './lexical/config/server/sanitize.js'
|
||||
import { sanitizeServerFeatures } from './lexical/config/server/sanitize.js'
|
||||
import { populateLexicalPopulationPromises } from './populateGraphQL/populateLexicalPopulationPromises.js'
|
||||
import { getGenerateImportMap } from './utilities/generateImportMap.js'
|
||||
import { getGenerateSchemaMap } from './utilities/generateSchemaMap.js'
|
||||
import { recurseNodeTree } from './utilities/recurseNodeTree.js'
|
||||
import { richTextValidateHOC } from './validate/index.js'
|
||||
|
||||
let defaultSanitizedServerEditorConfig: null | SanitizedServerEditorConfig = null
|
||||
let checkedDependencies = false
|
||||
|
||||
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true' &&
|
||||
!checkedDependencies
|
||||
) {
|
||||
checkedDependencies = true
|
||||
void checkDependencies({
|
||||
dependencyGroups: [
|
||||
{
|
||||
name: 'lexical',
|
||||
dependencies: [
|
||||
'lexical',
|
||||
'@lexical/headless',
|
||||
'@lexical/link',
|
||||
'@lexical/list',
|
||||
'@lexical/mark',
|
||||
'@lexical/react',
|
||||
'@lexical/rich-text',
|
||||
'@lexical/selection',
|
||||
'@lexical/utils',
|
||||
],
|
||||
targetVersion: '0.20.0',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
return async ({ config, isRoot, parentIsLocalized }) => {
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true' &&
|
||||
!checkedDependencies
|
||||
) {
|
||||
checkedDependencies = true
|
||||
await checkDependencies({
|
||||
dependencyGroups: [
|
||||
{
|
||||
name: 'lexical',
|
||||
dependencies: [
|
||||
'lexical',
|
||||
'@lexical/headless',
|
||||
'@lexical/link',
|
||||
'@lexical/list',
|
||||
'@lexical/mark',
|
||||
'@lexical/react',
|
||||
'@lexical/rich-text',
|
||||
'@lexical/selection',
|
||||
'@lexical/utils',
|
||||
],
|
||||
targetVersion: '0.20.0',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
let features: FeatureProviderServer<unknown, unknown, unknown>[] = []
|
||||
let resolvedFeatureMap: ResolvedServerFeatureMap
|
||||
|
||||
let finalSanitizedEditorConfig: SanitizedServerEditorConfig // For server only
|
||||
if (!props || (!props.features && !props.lexical)) {
|
||||
if (!defaultSanitizedServerEditorConfig) {
|
||||
defaultSanitizedServerEditorConfig = await sanitizeServerEditorConfig(
|
||||
defaultEditorConfig,
|
||||
config,
|
||||
parentIsLocalized,
|
||||
)
|
||||
features = deepCopyObject(defaultEditorFeatures)
|
||||
}
|
||||
finalSanitizedEditorConfig = await getDefaultSanitizedEditorConfig({
|
||||
config,
|
||||
parentIsLocalized,
|
||||
})
|
||||
|
||||
finalSanitizedEditorConfig = deepCopyObject(defaultSanitizedServerEditorConfig)
|
||||
|
||||
delete finalSanitizedEditorConfig.lexical // We don't want to send the default lexical editor config to the client
|
||||
features = defaultEditorFeatures
|
||||
|
||||
resolvedFeatureMap = finalSanitizedEditorConfig.resolvedFeatureMap
|
||||
} else {
|
||||
const rootEditor = config.editor
|
||||
let rootEditorFeatures: FeatureProviderServer<unknown, unknown, unknown>[] = []
|
||||
if (typeof rootEditor === 'object' && 'features' in rootEditor) {
|
||||
rootEditorFeatures = (rootEditor as LexicalRichTextAdapter).features
|
||||
if (props.features && typeof props.features === 'function') {
|
||||
const rootEditor = config.editor
|
||||
let rootEditorFeatures: FeatureProviderServer<unknown, unknown, unknown>[] = []
|
||||
if (typeof rootEditor === 'object' && 'features' in rootEditor) {
|
||||
rootEditorFeatures = (rootEditor as LexicalRichTextAdapter).features
|
||||
}
|
||||
features = props.features({
|
||||
defaultFeatures: defaultEditorFeatures,
|
||||
rootFeatures: rootEditorFeatures,
|
||||
})
|
||||
} else {
|
||||
features = props.features as FeatureProviderServer<unknown, unknown, unknown>[]
|
||||
}
|
||||
|
||||
features =
|
||||
props.features && typeof props.features === 'function'
|
||||
? props.features({
|
||||
defaultFeatures: deepCopyObject(defaultEditorFeatures),
|
||||
rootFeatures: rootEditorFeatures,
|
||||
})
|
||||
: (props.features as FeatureProviderServer<unknown, unknown, unknown>[])
|
||||
if (!features) {
|
||||
features = deepCopyObject(defaultEditorFeatures)
|
||||
features = defaultEditorFeatures
|
||||
}
|
||||
|
||||
const lexical = props.lexical ?? deepCopyObjectSimple(defaultEditorConfig.lexical)!
|
||||
const lexical = props.lexical ?? defaultEditorConfig.lexical
|
||||
|
||||
resolvedFeatureMap = await loadFeatures({
|
||||
config,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import type {
|
||||
FeatureProviderServer,
|
||||
ResolvedServerFeature,
|
||||
ResolvedServerFeatureMap,
|
||||
ServerFeatureProviderMap,
|
||||
} from '../../../features/typesServer.js'
|
||||
@@ -186,14 +187,21 @@ export async function loadFeatures({
|
||||
unSanitizedEditorConfig,
|
||||
})
|
||||
: featureProvider.feature
|
||||
resolvedFeatures.set(featureProvider.key, {
|
||||
...feature,
|
||||
dependencies: featureProvider.dependencies!,
|
||||
dependenciesPriority: featureProvider.dependenciesPriority!,
|
||||
dependenciesSoft: featureProvider.dependenciesSoft!,
|
||||
key: featureProvider.key,
|
||||
order: loaded,
|
||||
})
|
||||
|
||||
const resolvedFeature: ResolvedServerFeature<any, any> = feature as ResolvedServerFeature<
|
||||
any,
|
||||
any
|
||||
>
|
||||
|
||||
// All these new properties would be added to the feature, as it's mutated. However, this does not cause any damage and allows
|
||||
// us to prevent an unnecessary spread operation.
|
||||
resolvedFeature.key = featureProvider.key
|
||||
resolvedFeature.order = loaded
|
||||
resolvedFeature.dependencies = featureProvider.dependencies!
|
||||
resolvedFeature.dependenciesPriority = featureProvider.dependenciesPriority!
|
||||
resolvedFeature.dependenciesSoft = featureProvider.dependenciesSoft!
|
||||
|
||||
resolvedFeatures.set(featureProvider.key, resolvedFeature)
|
||||
|
||||
loaded++
|
||||
}
|
||||
|
||||
@@ -80,10 +80,11 @@ export type LexicalRichTextAdapterProvider =
|
||||
parentIsLocalized: boolean
|
||||
}) => Promise<LexicalRichTextAdapter>
|
||||
|
||||
export type SingleFeatureClientSchemaMap = {
|
||||
[key: string]: ClientField[]
|
||||
}
|
||||
export type FeatureClientSchemaMap = {
|
||||
[featureKey: string]: {
|
||||
[key: string]: ClientField[]
|
||||
}
|
||||
[featureKey: string]: SingleFeatureClientSchemaMap
|
||||
}
|
||||
|
||||
export type LexicalRichTextFieldProps = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { SerializedLexicalNode } from 'lexical'
|
||||
import type {
|
||||
ClientFieldSchemaMap,
|
||||
DocumentPreferences,
|
||||
FieldSchemaMap,
|
||||
FormState,
|
||||
@@ -22,6 +23,7 @@ export type InitialLexicalFormState = {
|
||||
|
||||
type Props = {
|
||||
context: {
|
||||
clientFieldSchemaMap: ClientFieldSchemaMap
|
||||
collectionSlug: string
|
||||
field: RichTextField
|
||||
fieldSchemaMap: FieldSchemaMap
|
||||
@@ -68,6 +70,7 @@ export async function buildInitialState({
|
||||
|
||||
const formStateResult = await fieldSchemasToFormState({
|
||||
id: context.id,
|
||||
clientFieldSchemaMap: context.clientFieldSchemaMap,
|
||||
collectionSlug: context.collectionSlug,
|
||||
data: blockNode.fields,
|
||||
fields: (context.fieldSchemaMap.get(schemaFieldsPath) as any)?.fields,
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
|
||||
import {
|
||||
type ClientField,
|
||||
createClientFields,
|
||||
deepCopyObjectSimple,
|
||||
type FieldSchemaMap,
|
||||
type Payload,
|
||||
} from 'payload'
|
||||
import { type ClientFieldSchemaMap, type FieldSchemaMap, type Payload } from 'payload'
|
||||
import { getFromImportMap } from 'payload/shared'
|
||||
|
||||
import type { FeatureProviderProviderClient } from '../features/typesClient.js'
|
||||
import type { SanitizedServerEditorConfig } from '../lexical/config/types.js'
|
||||
import type { FeatureClientSchemaMap, LexicalRichTextFieldProps } from '../types.js'
|
||||
type Args = {
|
||||
clientFieldSchemaMap: ClientFieldSchemaMap
|
||||
fieldSchemaMap: FieldSchemaMap
|
||||
i18n: I18nClient
|
||||
path: string
|
||||
@@ -27,9 +22,6 @@ export function initLexicalFeatures(args: Args): {
|
||||
} {
|
||||
const clientFeatures: LexicalRichTextFieldProps['clientFeatures'] = {}
|
||||
|
||||
const fieldSchemaMap = Object.fromEntries(new Map(args.fieldSchemaMap))
|
||||
//&const value = deepCopyObjectSimple(args.fieldState.value)
|
||||
|
||||
// turn args.resolvedFeatureMap into an array of [key, value] pairs, ordered by value.order, lowest order first:
|
||||
const resolvedFeatureMapArray = Array.from(
|
||||
args.sanitizedEditorConfig.resolvedFeatureMap.entries(),
|
||||
@@ -84,64 +76,14 @@ export function initLexicalFeatures(args: Args): {
|
||||
featureKey,
|
||||
].join('.')
|
||||
|
||||
const featurePath = [...args.path.split('.'), 'lexical_internal_feature', featureKey].join(
|
||||
'.',
|
||||
)
|
||||
|
||||
// Like args.fieldSchemaMap, we only want to include the sub-fields of the current feature
|
||||
const featureSchemaMap: typeof fieldSchemaMap = {}
|
||||
for (const key in fieldSchemaMap) {
|
||||
const state = fieldSchemaMap[key]
|
||||
|
||||
if (key.startsWith(featureSchemaPath)) {
|
||||
featureSchemaMap[key] = state
|
||||
}
|
||||
}
|
||||
|
||||
featureClientSchemaMap[featureKey] = {}
|
||||
|
||||
for (const key in featureSchemaMap) {
|
||||
const state = featureSchemaMap[key]
|
||||
|
||||
const clientFields = createClientFields({
|
||||
clientFields: ('fields' in state
|
||||
? deepCopyObjectSimple(state.fields)
|
||||
: [deepCopyObjectSimple(state)]) as ClientField[],
|
||||
defaultIDType: args.payload.config.db.defaultIDType,
|
||||
disableAddingID: true,
|
||||
fields: 'fields' in state ? state.fields : [state],
|
||||
i18n: args.i18n,
|
||||
importMap: args.payload.importMap,
|
||||
})
|
||||
featureClientSchemaMap[featureKey][key] = clientFields
|
||||
}
|
||||
|
||||
/*
|
||||
This is for providing an initial form state. Right now we only want to provide the clientfields though
|
||||
const schemaMap: {
|
||||
[key: string]: FieldState
|
||||
} = {}
|
||||
|
||||
const lexicalDeepIterate = (editorState) => {
|
||||
console.log('STATE', editorState)
|
||||
|
||||
if (
|
||||
editorState &&
|
||||
typeof editorState === 'object' &&
|
||||
'children' in editorState &&
|
||||
Array.isArray(editorState.children)
|
||||
) {
|
||||
for (const childKey in editorState.children) {
|
||||
const childState = editorState.children[childKey]
|
||||
|
||||
if (childState && typeof childState === 'object') {
|
||||
lexicalDeepIterate(childState)
|
||||
}
|
||||
}
|
||||
// Like args.fieldSchemaMap, we only want to include the sub-fields of the current feature
|
||||
for (const [key, entry] of args.clientFieldSchemaMap.entries()) {
|
||||
if (key.startsWith(featureSchemaPath)) {
|
||||
featureClientSchemaMap[featureKey][key] = 'fields' in entry ? entry.fields : [entry]
|
||||
}
|
||||
}
|
||||
|
||||
lexicalDeepIterate(value.root)*/
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
} from 'payload'
|
||||
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { createClientFields, deepCopyObjectSimple } from 'payload'
|
||||
import { createClientFields } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import type { AdapterArguments, RichTextCustomElement, RichTextCustomLeaf } from '../types.js'
|
||||
@@ -134,11 +134,7 @@ export const RscEntrySlateField: React.FC<
|
||||
|
||||
switch (element.name) {
|
||||
case 'link': {
|
||||
let clientFields = deepCopyObjectSimple(
|
||||
args.admin?.link?.fields,
|
||||
) as unknown as ClientField[]
|
||||
clientFields = createClientFields({
|
||||
clientFields,
|
||||
const clientFields = createClientFields({
|
||||
defaultIDType: payload.config.db.defaultIDType,
|
||||
fields: args.admin?.link?.fields as Field[],
|
||||
i18n,
|
||||
@@ -166,11 +162,7 @@ export const RscEntrySlateField: React.FC<
|
||||
|
||||
uploadEnabledCollections.forEach((collection) => {
|
||||
if (args?.admin?.upload?.collections[collection.slug]?.fields) {
|
||||
let clientFields = deepCopyObjectSimple(
|
||||
args?.admin?.upload?.collections[collection.slug]?.fields,
|
||||
) as unknown as ClientField[]
|
||||
clientFields = createClientFields({
|
||||
clientFields,
|
||||
const clientFields = createClientFields({
|
||||
defaultIDType: payload.config.db.defaultIDType,
|
||||
fields: args?.admin?.upload?.collections[collection.slug]?.fields,
|
||||
i18n,
|
||||
|
||||
@@ -53,6 +53,11 @@
|
||||
"types": "./src/utilities/buildTableState.ts",
|
||||
"default": "./src/utilities/buildTableState.ts"
|
||||
},
|
||||
"./utilities/getClientConfig": {
|
||||
"import": "./src/utilities/getClientConfig.ts",
|
||||
"types": "./src/utilities/getClientConfig.ts",
|
||||
"default": "./src/utilities/getClientConfig.ts"
|
||||
},
|
||||
"./utilities/buildFieldSchemaMap/traverseFields": {
|
||||
"import": "./src/utilities/buildFieldSchemaMap/traverseFields.ts",
|
||||
"types": "./src/utilities/buildFieldSchemaMap/traverseFields.ts",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ClientFieldSchemaMap,
|
||||
Data,
|
||||
DocumentPreferences,
|
||||
Field,
|
||||
@@ -35,6 +36,7 @@ export type AddFieldStatePromiseArgs = {
|
||||
* if all parents are localized, then the field is localized
|
||||
*/
|
||||
anyParentLocalized?: boolean
|
||||
clientFieldSchemaMap?: ClientFieldSchemaMap
|
||||
collectionSlug?: string
|
||||
data: Data
|
||||
field: Field
|
||||
@@ -93,6 +95,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
id,
|
||||
addErrorPathToParent: addErrorPathToParentArg,
|
||||
anyParentLocalized = false,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data,
|
||||
field,
|
||||
@@ -119,6 +122,12 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
state,
|
||||
} = args
|
||||
|
||||
if (!args.clientFieldSchemaMap && args.renderFieldFn) {
|
||||
console.warn(
|
||||
'clientFieldSchemaMap is not passed to addFieldStatePromise - this will reduce performance',
|
||||
)
|
||||
}
|
||||
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
@@ -237,6 +246,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
id,
|
||||
addErrorPathToParent,
|
||||
anyParentLocalized: field.localized || anyParentLocalized,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data: row,
|
||||
fields: field.fields,
|
||||
@@ -370,6 +380,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
id,
|
||||
addErrorPathToParent,
|
||||
anyParentLocalized: field.localized || anyParentLocalized,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data: row,
|
||||
fields: block.fields,
|
||||
@@ -460,6 +471,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
id,
|
||||
addErrorPathToParent,
|
||||
anyParentLocalized: field.localized || anyParentLocalized,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data: data?.[field.name] || {},
|
||||
fields: field.fields,
|
||||
@@ -605,6 +617,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
// passthrough parent functionality
|
||||
addErrorPathToParent: addErrorPathToParentArg,
|
||||
anyParentLocalized: field.localized || anyParentLocalized,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data,
|
||||
fields: field.fields,
|
||||
@@ -668,6 +681,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
id,
|
||||
addErrorPathToParent: addErrorPathToParentArg,
|
||||
anyParentLocalized: tab.localized || anyParentLocalized,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data: isNamedTab ? data?.[tab.name] || {} : data,
|
||||
fields: tab.fields,
|
||||
@@ -733,6 +747,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
|
||||
renderFieldFn({
|
||||
id,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data: fullData,
|
||||
fieldConfig: fieldConfig as Field,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ClientFieldSchemaMap,
|
||||
Data,
|
||||
DocumentPreferences,
|
||||
Field,
|
||||
@@ -15,6 +16,12 @@ import { calculateDefaultValues } from './calculateDefaultValues/index.js'
|
||||
import { iterateFields } from './iterateFields.js'
|
||||
|
||||
type Args = {
|
||||
/**
|
||||
* The client field schema map is required for field rendering.
|
||||
* If fields should not be rendered (=> `renderFieldFn` is not provided),
|
||||
* then the client field schema map is not required.
|
||||
*/
|
||||
clientFieldSchemaMap?: ClientFieldSchemaMap
|
||||
collectionSlug?: string
|
||||
data?: Data
|
||||
fields: Field[] | undefined
|
||||
@@ -40,12 +47,19 @@ type Args = {
|
||||
renderAllFields: boolean
|
||||
renderFieldFn?: RenderFieldMethod
|
||||
req: PayloadRequest
|
||||
|
||||
schemaPath: string
|
||||
}
|
||||
|
||||
export const fieldSchemasToFormState = async (args: Args): Promise<FormState> => {
|
||||
if (!args.clientFieldSchemaMap && args.renderFieldFn) {
|
||||
console.warn(
|
||||
'clientFieldSchemaMap is not passed to fieldSchemasToFormState - this will reduce performance',
|
||||
)
|
||||
}
|
||||
const {
|
||||
id,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data = {},
|
||||
fields,
|
||||
@@ -77,6 +91,7 @@ export const fieldSchemasToFormState = async (args: Args): Promise<FormState> =>
|
||||
await iterateFields({
|
||||
id,
|
||||
addErrorPathToParent: null,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data: dataWithDefaultValues,
|
||||
fields,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ClientFieldSchemaMap,
|
||||
Data,
|
||||
DocumentPreferences,
|
||||
Field as FieldSchema,
|
||||
@@ -20,6 +21,7 @@ type Args = {
|
||||
* if any parents is localized, then the field is localized. @default false
|
||||
*/
|
||||
anyParentLocalized?: boolean
|
||||
clientFieldSchemaMap?: ClientFieldSchemaMap
|
||||
collectionSlug?: string
|
||||
data: Data
|
||||
fields: FieldSchema[]
|
||||
@@ -71,6 +73,7 @@ export const iterateFields = async ({
|
||||
id,
|
||||
addErrorPathToParent: addErrorPathToParentArg,
|
||||
anyParentLocalized = false,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data,
|
||||
fields,
|
||||
@@ -112,6 +115,7 @@ export const iterateFields = async ({
|
||||
id,
|
||||
addErrorPathToParent: addErrorPathToParentArg,
|
||||
anyParentLocalized,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data,
|
||||
field,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ClientComponentProps, ClientField, FieldPaths, ServerComponentProps } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { createClientField, deepCopyObjectSimple, MissingEditorProp } from 'payload'
|
||||
import { createClientField, MissingEditorProp } from 'payload'
|
||||
|
||||
import type { RenderFieldMethod } from './types.js'
|
||||
|
||||
@@ -18,6 +18,7 @@ const defaultUIFieldComponentKeys: Array<'Cell' | 'Description' | 'Field' | 'Fil
|
||||
|
||||
export const renderField: RenderFieldMethod = ({
|
||||
id,
|
||||
clientFieldSchemaMap,
|
||||
collectionSlug,
|
||||
data,
|
||||
fieldConfig,
|
||||
@@ -35,13 +36,14 @@ export const renderField: RenderFieldMethod = ({
|
||||
schemaPath,
|
||||
siblingData,
|
||||
}) => {
|
||||
const clientField = createClientField({
|
||||
clientField: deepCopyObjectSimple(fieldConfig) as ClientField,
|
||||
defaultIDType: req.payload.config.db.defaultIDType,
|
||||
field: fieldConfig,
|
||||
i18n: req.i18n,
|
||||
importMap: req.payload.importMap,
|
||||
})
|
||||
const clientField = clientFieldSchemaMap
|
||||
? (clientFieldSchemaMap.get(schemaPath) as ClientField)
|
||||
: createClientField({
|
||||
defaultIDType: req.payload.config.db.defaultIDType,
|
||||
field: fieldConfig,
|
||||
i18n: req.i18n,
|
||||
importMap: req.payload.importMap,
|
||||
})
|
||||
|
||||
const clientProps: ClientComponentProps & Partial<FieldPaths> = {
|
||||
customComponents: fieldState?.customComponents || {},
|
||||
@@ -61,6 +63,7 @@ export const renderField: RenderFieldMethod = ({
|
||||
const serverProps: ServerComponentProps = {
|
||||
id,
|
||||
clientField,
|
||||
clientFieldSchemaMap,
|
||||
data,
|
||||
field: fieldConfig,
|
||||
fieldSchemaMap,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ClientFieldSchemaMap,
|
||||
Data,
|
||||
DocumentPreferences,
|
||||
Field,
|
||||
@@ -11,6 +12,7 @@ import type {
|
||||
} from 'payload'
|
||||
|
||||
export type RenderFieldArgs = {
|
||||
clientFieldSchemaMap?: ClientFieldSchemaMap
|
||||
collectionSlug: string
|
||||
data: Data
|
||||
fieldConfig: Field
|
||||
|
||||
@@ -14,7 +14,7 @@ const LocaleContext = createContext({} as Locale)
|
||||
|
||||
export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const {
|
||||
config: { localization },
|
||||
config: { localization = false },
|
||||
} = useConfig()
|
||||
|
||||
const { user } = useAuth()
|
||||
|
||||
94
packages/ui/src/utilities/buildClientFieldSchemaMap/index.ts
Normal file
94
packages/ui/src/utilities/buildClientFieldSchemaMap/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type {
|
||||
ClientConfig,
|
||||
ClientField,
|
||||
ClientFieldSchemaMap,
|
||||
FieldSchemaMap,
|
||||
Payload,
|
||||
TextFieldClient,
|
||||
} from 'payload'
|
||||
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
const baseAuthFields: ClientField[] = [
|
||||
{
|
||||
name: 'password',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'confirm-password',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Flattens the config fields into a map of field schemas
|
||||
*/
|
||||
export const buildClientFieldSchemaMap = (args: {
|
||||
collectionSlug?: string
|
||||
config: ClientConfig
|
||||
globalSlug?: string
|
||||
i18n: I18n
|
||||
payload: Payload
|
||||
schemaMap: FieldSchemaMap
|
||||
}): { clientFieldSchemaMap: ClientFieldSchemaMap } => {
|
||||
const { collectionSlug, config, globalSlug, i18n, payload, schemaMap } = args
|
||||
|
||||
const clientSchemaMap: ClientFieldSchemaMap = new Map()
|
||||
|
||||
if (collectionSlug) {
|
||||
const matchedCollection = config.collections.find(
|
||||
(collection) => collection.slug === collectionSlug,
|
||||
)
|
||||
|
||||
if (matchedCollection) {
|
||||
if (matchedCollection.auth && !matchedCollection.auth.disableLocalStrategy) {
|
||||
// register schema with auth schemaPath
|
||||
;(baseAuthFields[0] as TextFieldClient).label = i18n.t('general:password')
|
||||
;(baseAuthFields[1] as TextFieldClient).label = i18n.t('authentication:confirmPassword')
|
||||
|
||||
clientSchemaMap.set(`_${matchedCollection.slug}.auth`, {
|
||||
fields: [...baseAuthFields, ...matchedCollection.fields],
|
||||
})
|
||||
}
|
||||
|
||||
clientSchemaMap.set(collectionSlug, {
|
||||
fields: matchedCollection.fields,
|
||||
})
|
||||
|
||||
traverseFields({
|
||||
clientSchemaMap,
|
||||
config,
|
||||
fields: matchedCollection.fields,
|
||||
i18n,
|
||||
parentIndexPath: '',
|
||||
parentSchemaPath: collectionSlug,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
}
|
||||
} else if (globalSlug) {
|
||||
const matchedGlobal = config.globals.find((global) => global.slug === globalSlug)
|
||||
|
||||
if (matchedGlobal) {
|
||||
clientSchemaMap.set(globalSlug, {
|
||||
fields: matchedGlobal.fields,
|
||||
})
|
||||
|
||||
traverseFields({
|
||||
clientSchemaMap,
|
||||
config,
|
||||
fields: matchedGlobal.fields,
|
||||
i18n,
|
||||
parentIndexPath: '',
|
||||
parentSchemaPath: globalSlug,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { clientFieldSchemaMap: clientSchemaMap }
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
|
||||
import {
|
||||
type ClientConfig,
|
||||
type ClientField,
|
||||
type ClientFieldSchemaMap,
|
||||
createClientFields,
|
||||
type FieldSchemaMap,
|
||||
type Payload,
|
||||
} from 'payload'
|
||||
import { getFieldPaths, tabHasName } from 'payload/shared'
|
||||
|
||||
type Args = {
|
||||
clientSchemaMap: ClientFieldSchemaMap
|
||||
config: ClientConfig
|
||||
fields: ClientField[]
|
||||
i18n: I18n<any, any>
|
||||
parentIndexPath: string
|
||||
parentSchemaPath: string
|
||||
payload: Payload
|
||||
schemaMap: FieldSchemaMap
|
||||
}
|
||||
|
||||
export const traverseFields = ({
|
||||
clientSchemaMap,
|
||||
config,
|
||||
fields,
|
||||
i18n,
|
||||
parentIndexPath,
|
||||
parentSchemaPath,
|
||||
payload,
|
||||
schemaMap,
|
||||
}: Args) => {
|
||||
for (const [index, field] of fields.entries()) {
|
||||
const { indexPath, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
index,
|
||||
parentIndexPath: 'name' in field ? '' : parentIndexPath,
|
||||
parentPath: '',
|
||||
parentSchemaPath,
|
||||
})
|
||||
|
||||
clientSchemaMap.set(schemaPath, field)
|
||||
|
||||
switch (field.type) {
|
||||
case 'array':
|
||||
case 'group':
|
||||
traverseFields({
|
||||
clientSchemaMap,
|
||||
config,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
parentIndexPath: '',
|
||||
parentSchemaPath: schemaPath,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'blocks':
|
||||
field.blocks.map((block) => {
|
||||
const blockSchemaPath = `${schemaPath}.${block.slug}`
|
||||
|
||||
clientSchemaMap.set(blockSchemaPath, block)
|
||||
traverseFields({
|
||||
clientSchemaMap,
|
||||
config,
|
||||
fields: block.fields,
|
||||
i18n,
|
||||
parentIndexPath: '',
|
||||
parentSchemaPath: blockSchemaPath,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
})
|
||||
|
||||
break
|
||||
case 'collapsible':
|
||||
case 'row':
|
||||
traverseFields({
|
||||
clientSchemaMap,
|
||||
config,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
parentIndexPath: indexPath,
|
||||
parentSchemaPath,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
break
|
||||
|
||||
case 'richText': {
|
||||
// richText sub-fields are not part of the ClientConfig or the Config.
|
||||
// They only exist in the field schema map.
|
||||
// Thus, we need to
|
||||
// 1. get them from the field schema map
|
||||
// 2. convert them to client fields
|
||||
// 3. add them to the client schema map
|
||||
|
||||
// So these would basically be all fields that are not part of the client config already
|
||||
const richTextFieldSchemaMap: FieldSchemaMap = new Map()
|
||||
for (const [path, subField] of schemaMap.entries()) {
|
||||
if (path.startsWith(`${schemaPath}.`)) {
|
||||
richTextFieldSchemaMap.set(path, subField)
|
||||
}
|
||||
}
|
||||
|
||||
// Now loop through them, convert each entry to a client field and add it to the client schema map
|
||||
for (const [path, subField] of richTextFieldSchemaMap.entries()) {
|
||||
const clientFields = createClientFields({
|
||||
defaultIDType: payload.config.db.defaultIDType,
|
||||
disableAddingID: true,
|
||||
fields: 'fields' in subField ? subField.fields : [subField],
|
||||
i18n,
|
||||
importMap: payload.importMap,
|
||||
})
|
||||
clientSchemaMap.set(path, {
|
||||
fields: clientFields,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'tabs':
|
||||
field.tabs.map((tab, tabIndex) => {
|
||||
const { indexPath: tabIndexPath, schemaPath: tabSchemaPath } = getFieldPaths({
|
||||
field: {
|
||||
...tab,
|
||||
type: 'tab',
|
||||
},
|
||||
index: tabIndex,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath: '',
|
||||
parentSchemaPath,
|
||||
})
|
||||
|
||||
clientSchemaMap.set(tabSchemaPath, tab)
|
||||
|
||||
traverseFields({
|
||||
clientSchemaMap,
|
||||
config,
|
||||
fields: tab.fields,
|
||||
i18n,
|
||||
parentIndexPath: tabHasName(tab) ? '' : tabIndexPath,
|
||||
parentSchemaPath: tabHasName(tab) ? tabSchemaPath : parentSchemaPath,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,25 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type { Field, FieldSchemaMap, SanitizedConfig } from 'payload'
|
||||
import type { Field, FieldSchemaMap, SanitizedConfig, TextField } from 'payload'
|
||||
|
||||
import { confirmPassword, password } from 'payload/shared'
|
||||
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
const baseAuthFields: Field[] = [
|
||||
{
|
||||
name: 'password',
|
||||
type: 'text',
|
||||
required: true,
|
||||
validate: password,
|
||||
},
|
||||
{
|
||||
name: 'confirm-password',
|
||||
type: 'text',
|
||||
required: true,
|
||||
validate: confirmPassword,
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Flattens the config fields into a map of field schemas
|
||||
*/
|
||||
@@ -25,22 +41,8 @@ export const buildFieldSchemaMap = (args: {
|
||||
if (matchedCollection) {
|
||||
if (matchedCollection.auth && !matchedCollection.auth.disableLocalStrategy) {
|
||||
// register schema with auth schemaPath
|
||||
const baseAuthFields: Field[] = [
|
||||
{
|
||||
name: 'password',
|
||||
type: 'text',
|
||||
label: i18n.t('general:password'),
|
||||
required: true,
|
||||
validate: password,
|
||||
},
|
||||
{
|
||||
name: 'confirm-password',
|
||||
type: 'text',
|
||||
label: i18n.t('authentication:confirmPassword'),
|
||||
required: true,
|
||||
validate: confirmPassword,
|
||||
},
|
||||
]
|
||||
;(baseAuthFields[0] as TextField).label = i18n.t('general:password')
|
||||
;(baseAuthFields[1] as TextField).label = i18n.t('authentication:confirmPassword')
|
||||
|
||||
schemaMap.set(`_${matchedCollection.slug}.auth`, {
|
||||
fields: [...baseAuthFields, ...matchedCollection.fields],
|
||||
|
||||
@@ -63,7 +63,6 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
case 'collapsible':
|
||||
|
||||
case 'row':
|
||||
traverseFields({
|
||||
config,
|
||||
|
||||
@@ -1,65 +1,15 @@
|
||||
import type { I18n, I18nClient } from '@payloadcms/translations'
|
||||
import type {
|
||||
BuildFormStateArgs,
|
||||
ClientConfig,
|
||||
ClientUser,
|
||||
ErrorResult,
|
||||
FieldSchemaMap,
|
||||
FormState,
|
||||
SanitizedConfig,
|
||||
} from 'payload'
|
||||
import type { BuildFormStateArgs, ClientConfig, ClientUser, ErrorResult, FormState } from 'payload'
|
||||
|
||||
import { formatErrors } from 'payload'
|
||||
import { reduceFieldsToValues } from 'payload/shared'
|
||||
|
||||
import { fieldSchemasToFormState } from '../forms/fieldSchemasToFormState/index.js'
|
||||
import { renderField } from '../forms/fieldSchemasToFormState/renderField.js'
|
||||
import { buildFieldSchemaMap } from './buildFieldSchemaMap/index.js'
|
||||
import { getClientConfig } from './getClientConfig.js'
|
||||
import { getClientSchemaMap } from './getClientSchemaMap.js'
|
||||
import { getSchemaMap } from './getSchemaMap.js'
|
||||
import { handleFormStateLocking } from './handleFormStateLocking.js'
|
||||
|
||||
let cachedFieldMap = global._payload_fieldMap
|
||||
let cachedClientConfig = global._payload_clientConfig
|
||||
|
||||
if (!cachedFieldMap) {
|
||||
cachedFieldMap = global._payload_fieldMap = null
|
||||
}
|
||||
|
||||
if (!cachedClientConfig) {
|
||||
cachedClientConfig = global._payload_clientConfig = null
|
||||
}
|
||||
|
||||
export const getFieldSchemaMap = (args: {
|
||||
collectionSlug?: string
|
||||
config: SanitizedConfig
|
||||
globalSlug?: string
|
||||
i18n: I18nClient
|
||||
}): FieldSchemaMap => {
|
||||
const { collectionSlug, config, globalSlug, i18n } = args
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
if (!cachedFieldMap) {
|
||||
cachedFieldMap = new Map()
|
||||
}
|
||||
const cachedEntityFieldMap = cachedFieldMap.get(collectionSlug || globalSlug)
|
||||
if (cachedEntityFieldMap) {
|
||||
return cachedEntityFieldMap
|
||||
}
|
||||
}
|
||||
|
||||
const { fieldSchemaMap: entityFieldMap } = buildFieldSchemaMap({
|
||||
collectionSlug,
|
||||
config,
|
||||
globalSlug,
|
||||
i18n: i18n as I18n,
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
cachedFieldMap.set(collectionSlug || globalSlug, entityFieldMap)
|
||||
}
|
||||
|
||||
return entityFieldMap
|
||||
}
|
||||
|
||||
type BuildFormStateSuccessResult = {
|
||||
clientConfig?: ClientConfig
|
||||
errors?: never
|
||||
@@ -167,15 +117,24 @@ export const buildFormState = async (
|
||||
throw new Error('Either collectionSlug or globalSlug must be provided')
|
||||
}
|
||||
|
||||
const fieldSchemaMap = getFieldSchemaMap({
|
||||
const schemaMap = getSchemaMap({
|
||||
collectionSlug,
|
||||
config,
|
||||
globalSlug,
|
||||
i18n,
|
||||
})
|
||||
|
||||
const clientSchemaMap = getClientSchemaMap({
|
||||
collectionSlug,
|
||||
config: getClientConfig({ config, i18n, importMap: req.payload.importMap }),
|
||||
globalSlug,
|
||||
i18n,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
|
||||
const id = collectionSlug ? idFromArgs : undefined
|
||||
const fieldOrEntityConfig = fieldSchemaMap.get(schemaPath)
|
||||
const fieldOrEntityConfig = schemaMap.get(schemaPath)
|
||||
|
||||
if (!fieldOrEntityConfig) {
|
||||
throw new Error(`Could not find "${schemaPath}" in the fieldSchemaMap`)
|
||||
@@ -216,10 +175,11 @@ export const buildFormState = async (
|
||||
|
||||
const formStateResult = await fieldSchemasToFormState({
|
||||
id,
|
||||
clientFieldSchemaMap: clientSchemaMap,
|
||||
collectionSlug,
|
||||
data,
|
||||
fields,
|
||||
fieldSchemaMap,
|
||||
fieldSchemaMap: schemaMap,
|
||||
operation,
|
||||
permissions: docPermissions?.fields || {},
|
||||
preferences: docPreferences || { fields: {} },
|
||||
|
||||
@@ -1,49 +1,21 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type {
|
||||
BuildTableStateArgs,
|
||||
ClientCollectionConfig,
|
||||
ClientConfig,
|
||||
ErrorResult,
|
||||
ImportMap,
|
||||
PaginatedDocs,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedConfig,
|
||||
} from 'payload'
|
||||
|
||||
import { dequal } from 'dequal' // TODO: Can we change this to dequal/lite ? If not, please add comment explaining why
|
||||
import { createClientConfig, formatErrors } from 'payload'
|
||||
import { formatErrors } from 'payload'
|
||||
|
||||
import type { Column } from '../elements/Table/index.js'
|
||||
import type { ListPreferences } from '../elements/TableColumns/index.js'
|
||||
|
||||
import { getClientConfig } from './getClientConfig.js'
|
||||
import { renderFilters, renderTable } from './renderTable.js'
|
||||
|
||||
let cachedClientConfig = global._payload_clientConfig
|
||||
|
||||
if (!cachedClientConfig) {
|
||||
cachedClientConfig = global._payload_clientConfig = null
|
||||
}
|
||||
|
||||
export const getClientConfig = (args: {
|
||||
config: SanitizedConfig
|
||||
i18n: I18nClient
|
||||
importMap: ImportMap
|
||||
}): ClientConfig => {
|
||||
const { config, i18n, importMap } = args
|
||||
|
||||
if (cachedClientConfig && process.env.NODE_ENV !== 'development') {
|
||||
return cachedClientConfig
|
||||
}
|
||||
|
||||
cachedClientConfig = createClientConfig({
|
||||
config,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
|
||||
return cachedClientConfig
|
||||
}
|
||||
|
||||
type BuildTableStateSuccessResult = {
|
||||
clientConfig?: ClientConfig
|
||||
data: PaginatedDocs
|
||||
|
||||
31
packages/ui/src/utilities/getClientConfig.ts
Normal file
31
packages/ui/src/utilities/getClientConfig.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { ClientConfig, ImportMap, SanitizedConfig } from 'payload'
|
||||
|
||||
import { createClientConfig } from 'payload'
|
||||
import { cache } from 'react'
|
||||
|
||||
let cachedClientConfig = global._payload_clientConfig
|
||||
|
||||
if (!cachedClientConfig) {
|
||||
cachedClientConfig = global._payload_clientConfig = null
|
||||
}
|
||||
|
||||
export const getClientConfig = cache(
|
||||
(args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
|
||||
if (cachedClientConfig && !global._payload_doNotCacheClientConfig) {
|
||||
return cachedClientConfig
|
||||
}
|
||||
const { config, i18n, importMap } = args
|
||||
|
||||
cachedClientConfig = createClientConfig({
|
||||
config,
|
||||
i18n,
|
||||
importMap,
|
||||
})
|
||||
global._payload_clientConfig = cachedClientConfig
|
||||
|
||||
global._payload_doNotCacheClientConfig = false
|
||||
|
||||
return cachedClientConfig
|
||||
},
|
||||
)
|
||||
54
packages/ui/src/utilities/getClientSchemaMap.ts
Normal file
54
packages/ui/src/utilities/getClientSchemaMap.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { I18n, I18nClient } from '@payloadcms/translations'
|
||||
import type { ClientConfig, ClientFieldSchemaMap, FieldSchemaMap, Payload } from 'payload'
|
||||
|
||||
import { cache } from 'react'
|
||||
|
||||
import { buildClientFieldSchemaMap } from './buildClientFieldSchemaMap/index.js'
|
||||
|
||||
let cachedClientSchemaMap = global._payload_clientSchemaMap
|
||||
|
||||
if (!cachedClientSchemaMap) {
|
||||
cachedClientSchemaMap = global._payload_clientSchemaMap = null
|
||||
}
|
||||
|
||||
export const getClientSchemaMap = cache(
|
||||
(args: {
|
||||
collectionSlug?: string
|
||||
config: ClientConfig
|
||||
globalSlug?: string
|
||||
i18n: I18nClient
|
||||
payload: Payload
|
||||
schemaMap: FieldSchemaMap
|
||||
}): ClientFieldSchemaMap => {
|
||||
const { collectionSlug, config, globalSlug, i18n, payload, schemaMap } = args
|
||||
|
||||
if (!cachedClientSchemaMap || global._payload_doNotCacheClientSchemaMap) {
|
||||
cachedClientSchemaMap = new Map()
|
||||
}
|
||||
|
||||
let cachedEntityClientFieldMap = cachedClientSchemaMap.get(collectionSlug || globalSlug)
|
||||
|
||||
if (cachedEntityClientFieldMap) {
|
||||
return cachedEntityClientFieldMap
|
||||
}
|
||||
|
||||
cachedEntityClientFieldMap = new Map()
|
||||
|
||||
const { clientFieldSchemaMap: entityClientFieldMap } = buildClientFieldSchemaMap({
|
||||
collectionSlug,
|
||||
config,
|
||||
globalSlug,
|
||||
i18n: i18n as I18n,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
|
||||
cachedClientSchemaMap.set(collectionSlug || globalSlug, entityClientFieldMap)
|
||||
|
||||
global._payload_clientSchemaMap = cachedClientSchemaMap
|
||||
|
||||
global._payload_doNotCacheClientSchemaMap = false
|
||||
|
||||
return entityClientFieldMap
|
||||
},
|
||||
)
|
||||
50
packages/ui/src/utilities/getSchemaMap.ts
Normal file
50
packages/ui/src/utilities/getSchemaMap.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { I18n, I18nClient } from '@payloadcms/translations'
|
||||
import type { FieldSchemaMap, SanitizedConfig } from 'payload'
|
||||
|
||||
import { cache } from 'react'
|
||||
|
||||
import { buildFieldSchemaMap } from './buildFieldSchemaMap/index.js'
|
||||
|
||||
let cachedSchemaMap = global._payload_schemaMap
|
||||
|
||||
if (!cachedSchemaMap) {
|
||||
cachedSchemaMap = global._payload_schemaMap = null
|
||||
}
|
||||
|
||||
export const getSchemaMap = cache(
|
||||
(args: {
|
||||
collectionSlug?: string
|
||||
config: SanitizedConfig
|
||||
globalSlug?: string
|
||||
i18n: I18nClient
|
||||
}): FieldSchemaMap => {
|
||||
const { collectionSlug, config, globalSlug, i18n } = args
|
||||
|
||||
if (!cachedSchemaMap || global._payload_doNotCacheSchemaMap) {
|
||||
cachedSchemaMap = new Map()
|
||||
}
|
||||
|
||||
let cachedEntityFieldMap = cachedSchemaMap.get(collectionSlug || globalSlug)
|
||||
|
||||
if (cachedEntityFieldMap) {
|
||||
return cachedEntityFieldMap
|
||||
}
|
||||
|
||||
cachedEntityFieldMap = new Map()
|
||||
|
||||
const { fieldSchemaMap: entityFieldMap } = buildFieldSchemaMap({
|
||||
collectionSlug,
|
||||
config,
|
||||
globalSlug,
|
||||
i18n: i18n as I18n,
|
||||
})
|
||||
|
||||
cachedSchemaMap.set(collectionSlug || globalSlug, entityFieldMap)
|
||||
|
||||
global._payload_schemaMap = cachedSchemaMap
|
||||
|
||||
global._payload_doNotCacheSchemaMap = false
|
||||
|
||||
return entityFieldMap
|
||||
},
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SanitizedCollectionConfig, VerifyConfig } from 'payload'
|
||||
import type { SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
export type Props = {
|
||||
className?: string
|
||||
@@ -13,5 +13,5 @@ export type Props = {
|
||||
setValidateBeforeSubmit: (validate: boolean) => void
|
||||
useAPIKey?: boolean
|
||||
username: string
|
||||
verify?: boolean | VerifyConfig
|
||||
verify?: boolean
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { MenuGlobal } from './globals/Menu/index.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
// ...extend config here
|
||||
collections: [PostsCollection, MediaCollection],
|
||||
admin: {
|
||||
@@ -43,4 +43,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -40,9 +40,9 @@ export interface Config {
|
||||
user: User & {
|
||||
collection: 'users';
|
||||
};
|
||||
jobs?: {
|
||||
jobs: {
|
||||
tasks: unknown;
|
||||
workflows?: unknown;
|
||||
workflows: unknown;
|
||||
};
|
||||
}
|
||||
export interface UserAuthOperations {
|
||||
|
||||
@@ -66,7 +66,7 @@ function isUser(user: Config['user']): user is {
|
||||
return user?.collection === 'users'
|
||||
}
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
autoLogin: false,
|
||||
user: 'users',
|
||||
@@ -706,4 +706,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -10,7 +10,7 @@ import { adminRoute } from './shared.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
collections: [PostsCollection],
|
||||
admin: {
|
||||
autoLogin: {
|
||||
@@ -47,4 +47,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
publicCustomViewPath,
|
||||
} from './shared.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -203,4 +203,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -6,7 +6,7 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
import { arraySlug } from './shared.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -64,4 +64,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
19
test/auth-basic/config.ts
Normal file
19
test/auth-basic/config.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
autoLogin: false,
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
},
|
||||
},
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
}))
|
||||
152
test/auth-basic/e2e.spec.ts
Normal file
152
test/auth-basic/e2e.spec.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { devUser } from 'credentials.js'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
||||
import type { Config } from './payload-types.js'
|
||||
|
||||
import { ensureCompilationIsDone, getRoutes, initPageConsoleErrorCatch } from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { reInitializeDB } from '../helpers/reInitializeDB.js'
|
||||
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
let payload: PayloadTestSDK<Config>
|
||||
|
||||
const { beforeAll, beforeEach, describe } = test
|
||||
|
||||
const createFirstUser = async ({
|
||||
page,
|
||||
serverURL,
|
||||
}: {
|
||||
customAdminRoutes?: SanitizedConfig['admin']['routes']
|
||||
customRoutes?: SanitizedConfig['routes']
|
||||
page: Page
|
||||
serverURL: string
|
||||
}) => {
|
||||
const {
|
||||
admin: {
|
||||
routes: { createFirstUser: createFirstUserRoute },
|
||||
},
|
||||
routes: { admin: adminRoute },
|
||||
} = getRoutes({})
|
||||
|
||||
// wait for create first user route
|
||||
await page.goto(serverURL + `${adminRoute}${createFirstUserRoute}`)
|
||||
|
||||
// forget to fill out confirm password
|
||||
await page.locator('#field-email').fill(devUser.email)
|
||||
await page.locator('#field-password').fill(devUser.password)
|
||||
await page.locator('.form-submit > button').click()
|
||||
await expect(page.locator('.field-type.confirm-password .field-error')).toHaveText(
|
||||
'This field is required.',
|
||||
)
|
||||
|
||||
// make them match, but does not pass password validation
|
||||
await page.locator('#field-email').fill(devUser.email)
|
||||
await page.locator('#field-password').fill('12')
|
||||
await page.locator('#field-confirm-password').fill('12')
|
||||
await page.locator('.form-submit > button').click()
|
||||
await expect(page.locator('.field-type.password .field-error')).toHaveText(
|
||||
'This value must be longer than the minimum length of 3 characters.',
|
||||
)
|
||||
|
||||
await page.locator('#field-email').fill(devUser.email)
|
||||
await page.locator('#field-password').fill(devUser.password)
|
||||
await page.locator('#field-confirm-password').fill(devUser.password)
|
||||
await page.locator('.form-submit > button').click()
|
||||
|
||||
await expect
|
||||
.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT })
|
||||
.not.toContain('create-first-user')
|
||||
}
|
||||
|
||||
describe('auth-basic', () => {
|
||||
let page: Page
|
||||
let url: AdminUrlUtil
|
||||
let serverURL: string
|
||||
let apiURL: string
|
||||
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
||||
apiURL = `${serverURL}/api`
|
||||
url = new AdminUrlUtil(serverURL, 'users')
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await ensureCompilationIsDone({
|
||||
page,
|
||||
serverURL,
|
||||
readyURL: `${serverURL}/admin/**`,
|
||||
noAutoLogin: true,
|
||||
})
|
||||
|
||||
// Undo onInit seeding, as we need to test this without having a user created, or testing create-first-user
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'auth-basic',
|
||||
deleteOnly: true,
|
||||
})
|
||||
|
||||
await payload.delete({
|
||||
collection: 'users',
|
||||
where: {
|
||||
id: {
|
||||
exists: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await ensureCompilationIsDone({
|
||||
page,
|
||||
serverURL,
|
||||
readyURL: `${serverURL}/admin/create-first-user`,
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await payload.delete({
|
||||
collection: 'users',
|
||||
where: {
|
||||
id: {
|
||||
exists: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('unauthenticated users', () => {
|
||||
test('ensure create first user page only has 3 fields', async () => {
|
||||
await page.goto(url.admin + '/create-first-user')
|
||||
|
||||
// Ensure there are only 2 elements with class field-type
|
||||
await expect(page.locator('.field-type')).toHaveCount(3) // Email, Password, Confirm Password
|
||||
})
|
||||
|
||||
test('ensure first user can be created', async () => {
|
||||
await createFirstUser({ page, serverURL })
|
||||
|
||||
// use the api key in a fetch to assert that it is disabled
|
||||
await expect(async () => {
|
||||
const users = await payload.find({
|
||||
collection: 'users',
|
||||
})
|
||||
|
||||
expect(users.totalDocs).toBe(1)
|
||||
expect(users.docs[0].email).toBe(devUser.email)
|
||||
}).toPass({
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
186
test/auth-basic/payload-types.ts
Normal file
186
test/auth-basic/payload-types.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
collections: {
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {};
|
||||
globalsSelect: {};
|
||||
locale: null;
|
||||
user: User & {
|
||||
collection: 'users';
|
||||
};
|
||||
jobs: {
|
||||
tasks: unknown;
|
||||
workflows: unknown;
|
||||
};
|
||||
}
|
||||
export interface UserAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
login: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
resetPasswordExpiration?: string | null;
|
||||
salt?: string | null;
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: string;
|
||||
document?: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null;
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
*/
|
||||
export interface UsersSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
email?: T;
|
||||
resetPasswordToken?: T;
|
||||
resetPasswordExpiration?: T;
|
||||
salt?: T;
|
||||
hash?: T;
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
*/
|
||||
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
||||
document?: T;
|
||||
globalSlug?: T;
|
||||
user?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences_select".
|
||||
*/
|
||||
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
||||
user?: T;
|
||||
key?: T;
|
||||
value?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations_select".
|
||||
*/
|
||||
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
batch?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "auth".
|
||||
*/
|
||||
export interface Auth {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
declare module 'payload' {
|
||||
// @ts-ignore
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
||||
13
test/auth-basic/tsconfig.eslint.json
Normal file
13
test/auth-basic/tsconfig.eslint.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
// extend your base config to share compilerOptions, etc
|
||||
//"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// ensure that nobody can accidentally use this config for a build
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
// whatever paths you intend to lint
|
||||
"./**/*.ts",
|
||||
"./**/*.tsx"
|
||||
]
|
||||
}
|
||||
3
test/auth-basic/tsconfig.json
Normal file
3
test/auth-basic/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../tsconfig.json"
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
import { apiKeysSlug, namedSaveToJWTValue, saveToJWTKey, slug } from './shared.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
autoLogin: {
|
||||
email: devUser.email,
|
||||
@@ -253,4 +253,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -41,136 +41,139 @@ import { testEmailAdapter } from './testEmailAdapter.js'
|
||||
// process.env.PAYLOAD_DATABASE = 'sqlite'
|
||||
|
||||
export async function buildConfigWithDefaults(
|
||||
testConfig?: Partial<Config>,
|
||||
testConfigFn?: () => Partial<Config>,
|
||||
options?: {
|
||||
disableAutoLogin?: boolean
|
||||
},
|
||||
): Promise<SanitizedConfig> {
|
||||
const config: Config = {
|
||||
db: databaseAdapter,
|
||||
editor: lexicalEditor({
|
||||
features: [
|
||||
ParagraphFeature(),
|
||||
RelationshipFeature(),
|
||||
LinkFeature({
|
||||
fields: ({ defaultFields }) => [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}),
|
||||
ChecklistFeature(),
|
||||
UnorderedListFeature(),
|
||||
OrderedListFeature(),
|
||||
AlignFeature(),
|
||||
BlockquoteFeature(),
|
||||
BoldFeature(),
|
||||
ItalicFeature(),
|
||||
UploadFeature({
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
UnderlineFeature(),
|
||||
StrikethroughFeature(),
|
||||
SubscriptFeature(),
|
||||
SuperscriptFeature(),
|
||||
InlineCodeFeature(),
|
||||
InlineToolbarFeature(),
|
||||
TreeViewFeature(),
|
||||
HeadingFeature(),
|
||||
IndentFeature(),
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'myBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'someText',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'someTextRequired',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'radios',
|
||||
type: 'radio',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option2',
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: 'option3',
|
||||
},
|
||||
],
|
||||
validate: (value) => {
|
||||
return value !== 'option2' ? true : 'Cannot be option2'
|
||||
): Promise<(() => Promise<SanitizedConfig>) | SanitizedConfig> {
|
||||
return await buildConfig(() => {
|
||||
const testConfig = testConfigFn ? testConfigFn() : undefined
|
||||
const config: Config = {
|
||||
db: databaseAdapter,
|
||||
editor: lexicalEditor({
|
||||
features: [
|
||||
ParagraphFeature(),
|
||||
RelationshipFeature(),
|
||||
LinkFeature({
|
||||
fields: ({ defaultFields }) => [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}),
|
||||
ChecklistFeature(),
|
||||
UnorderedListFeature(),
|
||||
OrderedListFeature(),
|
||||
AlignFeature(),
|
||||
BlockquoteFeature(),
|
||||
BoldFeature(),
|
||||
ItalicFeature(),
|
||||
UploadFeature({
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
email: testEmailAdapter,
|
||||
endpoints: [localAPIEndpoint, reInitEndpoint],
|
||||
secret: 'TEST_SECRET',
|
||||
sharp,
|
||||
telemetry: false,
|
||||
...testConfig,
|
||||
i18n: {
|
||||
supportedLanguages: {
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
}),
|
||||
UnderlineFeature(),
|
||||
StrikethroughFeature(),
|
||||
SubscriptFeature(),
|
||||
SuperscriptFeature(),
|
||||
InlineCodeFeature(),
|
||||
InlineToolbarFeature(),
|
||||
TreeViewFeature(),
|
||||
HeadingFeature(),
|
||||
IndentFeature(),
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'myBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'someText',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'someTextRequired',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'radios',
|
||||
type: 'radio',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option2',
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: 'option3',
|
||||
},
|
||||
],
|
||||
validate: (value) => {
|
||||
return value !== 'option2' ? true : 'Cannot be option2'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
email: testEmailAdapter,
|
||||
endpoints: [localAPIEndpoint, reInitEndpoint],
|
||||
secret: 'TEST_SECRET',
|
||||
sharp,
|
||||
telemetry: false,
|
||||
...testConfig,
|
||||
i18n: {
|
||||
supportedLanguages: {
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
},
|
||||
...(testConfig?.i18n || {}),
|
||||
},
|
||||
...(testConfig?.i18n || {}),
|
||||
},
|
||||
typescript: {
|
||||
declare: {
|
||||
ignoreTSError: true,
|
||||
typescript: {
|
||||
declare: {
|
||||
ignoreTSError: true,
|
||||
},
|
||||
...testConfig?.typescript,
|
||||
},
|
||||
...testConfig?.typescript,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.admin) {
|
||||
config.admin = {}
|
||||
}
|
||||
|
||||
if (config.admin.autoLogin === undefined) {
|
||||
config.admin.autoLogin =
|
||||
process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN === 'true' || options?.disableAutoLogin
|
||||
? false
|
||||
: {
|
||||
email: 'dev@payloadcms.com',
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.PAYLOAD_DISABLE_ADMIN === 'true') {
|
||||
if (typeof config.admin !== 'object') {
|
||||
if (!config.admin) {
|
||||
config.admin = {}
|
||||
}
|
||||
config.admin.disable = true
|
||||
}
|
||||
|
||||
return await buildConfig(config)
|
||||
if (config.admin.autoLogin === undefined) {
|
||||
config.admin.autoLogin =
|
||||
process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN === 'true' || options?.disableAutoLogin
|
||||
? false
|
||||
: {
|
||||
email: 'dev@payloadcms.com',
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.PAYLOAD_DISABLE_ADMIN === 'true') {
|
||||
if (typeof config.admin !== 'object') {
|
||||
config.admin = {}
|
||||
}
|
||||
config.admin.disable = true
|
||||
}
|
||||
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const pointSlug = 'point'
|
||||
|
||||
export const errorOnHookSlug = 'error-on-hooks'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -550,4 +550,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -43,7 +43,7 @@ export const errorOnHookSlug = 'error-on-hooks'
|
||||
|
||||
export const endpointsSlug = 'endpoints'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -409,4 +409,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -5,7 +5,7 @@ const dirname = path.dirname(filename)
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -122,4 +122,4 @@ export default buildConfigWithDefaults({
|
||||
origins: '*',
|
||||
headers: ['x-custom-header'],
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -22,7 +22,7 @@ const resolveTransactionId = async (_obj, _args, context) => {
|
||||
}
|
||||
}
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -73,4 +73,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -15,7 +15,7 @@ const defaultValueField: TextField = {
|
||||
defaultValue: 'default value from database',
|
||||
}
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -469,7 +469,7 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
export const postDoc = {
|
||||
title: 'test post',
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { Post } from './payload-types.js'
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -146,7 +146,7 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
export const postDoc: Pick<Post, 'title'> = {
|
||||
title: 'test post',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -34,4 +34,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -7,7 +7,7 @@ import { resendAdapter } from '@payloadcms/email-resend'
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -42,4 +42,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -12,7 +12,7 @@ import { MenuGlobal } from './globals/Menu/index.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -55,4 +55,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
noEndpointsGlobalSlug,
|
||||
} from './shared.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -80,4 +80,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ValidateDraftsOn } from './collections/ValidateDraftsOn/index.js'
|
||||
import { ValidateDraftsOnAndAutosave } from './collections/ValidateDraftsOnAutosave/index.js'
|
||||
import { GlobalValidateDraftsOn } from './globals/ValidateDraftsOn/index.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -41,4 +41,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -5,7 +5,7 @@ const dirname = path.dirname(filename)
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -105,4 +105,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -51,7 +51,7 @@ const baseRelationshipFields: CollectionConfig['fields'] = [
|
||||
},
|
||||
]
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -543,4 +543,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -99,7 +99,7 @@ export const collectionSlugs: CollectionConfig[] = [
|
||||
UIFields,
|
||||
]
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
collections: collectionSlugs,
|
||||
globals: [TabsWithRichText],
|
||||
custom: {
|
||||
@@ -136,4 +136,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -2,10 +2,8 @@ import path from 'path'
|
||||
|
||||
const [testConfigDir] = process.argv.slice(2)
|
||||
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import { generateImportMap } from 'payload'
|
||||
import { type ConfigImport, generateImportMap, getConfig } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
@@ -20,7 +18,8 @@ async function run() {
|
||||
const pathWithConfig = path.resolve(testDir, 'config.ts')
|
||||
console.log('Generating ad-hoc import map for config:', pathWithConfig)
|
||||
|
||||
const config: SanitizedConfig = await (await import(pathWithConfig)).default
|
||||
const configImport: ConfigImport = (await import(pathWithConfig)).default
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
let rootDir = ''
|
||||
if (testConfigDir === 'live-preview' || testConfigDir === 'admin-root') {
|
||||
|
||||
@@ -22,7 +22,7 @@ const access = {
|
||||
update: () => true,
|
||||
}
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -138,4 +138,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -5,7 +5,7 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -159,4 +159,4 @@ export default buildConfigWithDefaults({
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -64,11 +64,13 @@ export async function ensureCompilationIsDone({
|
||||
page,
|
||||
serverURL,
|
||||
noAutoLogin,
|
||||
readyURL,
|
||||
}: {
|
||||
customAdminRoutes?: Config['admin']['routes']
|
||||
customRoutes?: Config['routes']
|
||||
noAutoLogin?: boolean
|
||||
page: Page
|
||||
readyURL?: string
|
||||
serverURL: string
|
||||
}): Promise<void> {
|
||||
const {
|
||||
@@ -82,11 +84,16 @@ export async function ensureCompilationIsDone({
|
||||
|
||||
while (attempt <= maxAttempts) {
|
||||
try {
|
||||
console.log(`Checking if compilation is done (attempt ${attempt}/${maxAttempts})...`)
|
||||
console.log(
|
||||
`Checking if compilation is done (attempt ${attempt}/${maxAttempts})...`,
|
||||
readyURL ??
|
||||
(noAutoLogin ? `${adminURL + (adminURL.endsWith('/') ? '' : '/')}login` : adminURL),
|
||||
)
|
||||
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(
|
||||
noAutoLogin ? `${adminURL + (adminURL.endsWith('/') ? '' : '/')}login` : adminURL,
|
||||
readyURL ??
|
||||
(noAutoLogin ? `${adminURL + (adminURL.endsWith('/') ? '' : '/')}login` : adminURL),
|
||||
)
|
||||
|
||||
console.log('Successfully compiled')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Payload, SanitizedConfig } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
import { getPayload } from 'payload'
|
||||
import { getConfig, getPayload } from 'payload'
|
||||
|
||||
import { runInit } from '../runInit.js'
|
||||
import { NextRESTClient } from './NextRESTClient.js'
|
||||
@@ -17,15 +17,15 @@ export async function initPayloadInt(
|
||||
const testSuiteName = testSuiteNameOverride ?? path.basename(dirname)
|
||||
await runInit(testSuiteName, false, true)
|
||||
console.log('importing config', path.resolve(dirname, 'config.ts'))
|
||||
const { default: config } = await import(path.resolve(dirname, 'config.ts'))
|
||||
const { default: configImport } = await import(path.resolve(dirname, 'config.ts'))
|
||||
|
||||
if (!initializePayload) {
|
||||
return { config: await config }
|
||||
return { config: await getConfig(configImport) }
|
||||
}
|
||||
|
||||
console.log('starting payload')
|
||||
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayload({ config: await getConfig(configImport) })
|
||||
console.log('initializing rest client')
|
||||
const restClient = new NextRESTClient(payload.config)
|
||||
console.log('initPayloadInt done')
|
||||
|
||||
@@ -17,7 +17,7 @@ import Relations from './collections/Relations/index.js'
|
||||
import TransformHooks from './collections/Transform/index.js'
|
||||
import Users, { seedHooksUsers } from './collections/Users/index.js'
|
||||
import { DataHooksGlobal } from './globals/Data/index.js'
|
||||
export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
|
||||
export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -68,6 +68,6 @@ export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
export default HooksConfig
|
||||
|
||||
@@ -30,7 +30,7 @@ const customTranslationsObject = {
|
||||
export type CustomTranslationsObject = typeof customTranslationsObject.en
|
||||
export type CustomTranslationsKeys = NestedKeysStripped<CustomTranslationsObject>
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -94,4 +94,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import { generateImportMap, type SanitizedConfig } from 'payload'
|
||||
import { type ConfigImport, generateImportMap, getConfig } from 'payload'
|
||||
|
||||
import type { allDatabaseAdapters } from './generateDatabaseAdapter.js'
|
||||
|
||||
@@ -45,13 +45,13 @@ export async function initDevAndTest(
|
||||
console.log('Generating import map for config:', testDir)
|
||||
|
||||
const configUrl = pathToFileURL(path.resolve(testDir, 'config.ts')).href
|
||||
const config: SanitizedConfig = await (await import(configUrl)).default
|
||||
const configImport: ConfigImport = (await import(configUrl)).default
|
||||
|
||||
const config = await getConfig(configImport)
|
||||
|
||||
process.env.ROOT_DIR = getNextRootDir(testSuiteArg).rootDir
|
||||
|
||||
await generateImportMap(config, { log: true, force: true })
|
||||
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
if (runImmediately === 'true') {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
collections: [
|
||||
Posts,
|
||||
Categories,
|
||||
@@ -126,4 +126,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -11,7 +11,7 @@ import { docsBasePath } from './collections/Posts/shared.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
// ...extend config here
|
||||
collections: [
|
||||
PostsCollection,
|
||||
@@ -78,4 +78,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from './shared.js'
|
||||
import { formatLivePreviewURL } from './utilities/formatLivePreviewURL.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -46,4 +46,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Posts } from './collections/posts.js'
|
||||
import { Users } from './collections/users.js'
|
||||
import deepMerge from './deepMerge.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -50,4 +50,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -47,7 +47,7 @@ const openAccess = {
|
||||
update: () => true,
|
||||
}
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -561,4 +561,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -13,7 +13,7 @@ import { MenuGlobal } from './globals/Menu/index.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
export default buildConfigWithDefaults(() => ({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -61,4 +61,4 @@ export default buildConfigWithDefaults({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user