Compare commits

..

1 Commits

Author SHA1 Message Date
Elliot DeNolf
e9c7f9da92 chore(release): v3.0.0-alpha.61 [skip ci] 2024-04-09 14:22:12 -04:00
184 changed files with 1328 additions and 1690 deletions

View File

@@ -4,7 +4,7 @@ on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches: ['main', 'beta']
branches: ['main', 'alpha']
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -88,6 +88,7 @@ jobs:
tests-unit:
runs-on: ubuntu-latest
needs: build
if: false # Disable until tests are updated for 3.0
steps:
- name: Use Node.js 18

1
emptyModule.js Normal file
View File

@@ -0,0 +1 @@
export default {}

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.6",
"version": "3.0.0-alpha.61",
"private": true,
"type": "module",
"workspaces:": [

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.6",
"version": "3.0.0-alpha.61",
"license": "MIT",
"type": "module",
"homepage": "https://payloadcms.com",
@@ -35,7 +35,7 @@
"comment-json": "^4.2.3",
"degit": "^2.8.4",
"detect-package-manager": "^3.0.1",
"esprima-next": "^6.0.3",
"esprima": "^4.0.1",
"execa": "^5.0.0",
"figures": "^6.1.0",
"fs-extra": "^9.0.1",

View File

@@ -4,7 +4,6 @@ import * as p from '@clack/prompts'
import { parse, stringify } from 'comment-json'
import execa from 'execa'
import fs from 'fs'
import fse from 'fs-extra'
import globby from 'globby'
import path from 'path'
import { promisify } from 'util'
@@ -32,8 +31,6 @@ type InitNextArgs = Pick<CliArgs, '--debug'> & {
useDistFiles?: boolean
}
type NextConfigType = 'cjs' | 'esm'
type InitNextResult =
| {
isSrcDir: boolean
@@ -48,22 +45,11 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
const nextAppDetails = args.nextAppDetails || (await getNextAppDetails(projectDir))
if (!nextAppDetails.nextAppDir) {
warning(`Could not find app directory in ${projectDir}, creating...`)
const createdAppDir = path.resolve(projectDir, nextAppDetails.isSrcDir ? 'src/app' : 'app')
fse.mkdirSync(createdAppDir, { recursive: true })
nextAppDetails.nextAppDir = createdAppDir
}
const { hasTopLevelLayout, isSrcDir, nextAppDir } =
nextAppDetails || (await getNextAppDetails(projectDir))
const { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigType } = nextAppDetails
if (!nextConfigType) {
return {
isSrcDir,
nextAppDir,
reason: `Could not determine Next Config type in ${projectDir}. Possibly try renaming next.config.js to next.config.cjs or next.config.mjs.`,
success: false,
}
if (!nextAppDir) {
return { isSrcDir, reason: `Could not find app directory in ${projectDir}`, success: false }
}
if (hasTopLevelLayout) {
@@ -83,7 +69,6 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
const configurationResult = installAndConfigurePayload({
...args,
nextAppDetails,
nextConfigType,
useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa
})
@@ -111,13 +96,6 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean) {
const tsConfigPath = path.resolve(projectDir, 'tsconfig.json')
// Check if tsconfig.json exists
if (!fs.existsSync(tsConfigPath)) {
warning(`Could not find tsconfig.json to add @payload-config path.`)
return
}
const userTsConfigContent = await readFile(tsConfigPath, {
encoding: 'utf8',
})
@@ -141,18 +119,13 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
}
function installAndConfigurePayload(
args: InitNextArgs & {
nextAppDetails: NextAppDetails
nextConfigType: NextConfigType
useDistFiles?: boolean
},
args: InitNextArgs & { nextAppDetails: NextAppDetails; useDistFiles?: boolean },
):
| { payloadConfigPath: string; success: true }
| { payloadConfigPath?: string; reason: string; success: false } {
const {
'--debug': debug,
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
nextConfigType,
projectDir,
useDistFiles,
} = args
@@ -199,7 +172,6 @@ function installAndConfigurePayload(
logDebug(`nextAppDir: ${nextAppDir}`)
logDebug(`projectDir: ${projectDir}`)
logDebug(`nextConfigPath: ${nextConfigPath}`)
logDebug(`payloadConfigPath: ${path.resolve(projectDir, 'payload.config.ts')}`)
logDebug(
`isSrcDir: ${isSrcDir}. source: ${templateSrcDir}. dest: ${path.dirname(nextConfigPath)}`,
@@ -209,7 +181,7 @@ function installAndConfigurePayload(
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
// Wrap next.config.js with withPayload
wrapNextConfig({ nextConfigPath, nextConfigType })
wrapNextConfig({ nextConfigPath })
return {
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
@@ -219,10 +191,10 @@ function installAndConfigurePayload(
async function installDeps(projectDir: string, packageManager: PackageManager, dbType: DbType) {
const packagesToInstall = ['payload', '@payloadcms/next', '@payloadcms/richtext-lexical'].map(
(pkg) => `${pkg}@beta`,
(pkg) => `${pkg}@alpha`,
)
packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)
packagesToInstall.push(`@payloadcms/db-${dbType}@alpha`)
let exitCode = 0
switch (packageManager) {
@@ -254,7 +226,6 @@ type NextAppDetails = {
isSrcDir: boolean
nextAppDir?: string
nextConfigPath?: string
nextConfigType?: NextConfigType
}
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
@@ -275,7 +246,6 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
await globby(['**/app'], {
absolute: true,
cwd: projectDir,
ignore: ['**/node_modules/**'],
onlyDirectories: true,
})
)?.[0]
@@ -284,31 +254,9 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
nextAppDir = undefined
}
const configType = await getProjectType(projectDir, nextConfigPath)
const hasTopLevelLayout = nextAppDir
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
: false
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath, nextConfigType: configType }
}
async function getProjectType(projectDir: string, nextConfigPath: string): Promise<'cjs' | 'esm'> {
if (nextConfigPath.endsWith('.mjs')) {
return 'esm'
}
if (nextConfigPath.endsWith('.cjs')) {
return 'cjs'
}
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
const packageJsonType = packageObj.type
if (packageJsonType === 'module') {
return 'esm'
}
if (packageJsonType === 'commonjs') {
return 'cjs'
}
return 'cjs'
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath }
}

View File

@@ -10,18 +10,14 @@ const mongodbReplacement: DbAdapterReplacement = {
importReplacement: "import { mongooseAdapter } from '@payloadcms/db-mongodb'",
packageName: '@payloadcms/db-mongodb',
// Replacement between `// database-adapter-config-start` and `// database-adapter-config-end`
configReplacement: [
' db: mongooseAdapter({',
" url: process.env.DATABASE_URI || '',",
' }),',
],
configReplacement: [' db: mongooseAdapter({', ' url: process.env.DATABASE_URI,', ' }),'],
}
const postgresReplacement: DbAdapterReplacement = {
configReplacement: [
' db: postgresAdapter({',
' pool: {',
" connectionString: process.env.DATABASE_URI || '',",
' connectionString: process.env.DATABASE_URI,',
' },',
' }),',
],

View File

@@ -18,46 +18,43 @@ export function getValidTemplates(): ProjectTemplate[] {
name: 'blank-3.0',
type: 'starter',
description: 'Blank 3.0 Template',
url: 'https://github.com/payloadcms/payload/templates/blank-3.0#beta',
url: 'https://github.com/payloadcms/payload/templates/blank-3.0',
},
{
name: 'blank',
type: 'starter',
description: 'Blank Template',
url: 'https://github.com/payloadcms/payload/templates/blank',
},
{
name: 'website',
type: 'starter',
description: 'Website Template',
url: 'https://github.com/payloadcms/payload/templates/website',
},
{
name: 'ecommerce',
type: 'starter',
description: 'E-commerce Template',
url: 'https://github.com/payloadcms/payload/templates/ecommerce',
},
{
name: 'plugin',
type: 'plugin',
description: 'Template for creating a Payload plugin',
url: 'https://github.com/payloadcms/payload-plugin-template',
},
{
name: 'payload-demo',
type: 'starter',
description: 'Payload demo site at https://demo.payloadcms.com',
url: 'https://github.com/payloadcms/public-demo',
},
{
name: 'payload-website',
type: 'starter',
description: 'Payload website CMS at https://payloadcms.com',
url: 'https://github.com/payloadcms/website-cms',
},
// Remove these until they have been updated for 3.0
// {
// name: 'blank',
// type: 'starter',
// description: 'Blank Template',
// url: 'https://github.com/payloadcms/payload/templates/blank',
// },
// {
// name: 'website',
// type: 'starter',
// description: 'Website Template',
// url: 'https://github.com/payloadcms/payload/templates/website',
// },
// {
// name: 'ecommerce',
// type: 'starter',
// description: 'E-commerce Template',
// url: 'https://github.com/payloadcms/payload/templates/ecommerce',
// },
// {
// name: 'plugin',
// type: 'plugin',
// description: 'Template for creating a Payload plugin',
// url: 'https://github.com/payloadcms/payload-plugin-template',
// },
// {
// name: 'payload-demo',
// type: 'starter',
// description: 'Payload demo site at https://demo.payloadcms.com',
// url: 'https://github.com/payloadcms/public-demo',
// },
// {
// name: 'payload-website',
// type: 'starter',
// description: 'Payload website CMS at https://payloadcms.com',
// url: 'https://github.com/payloadcms/website-cms',
// },
]
}

View File

@@ -1,159 +1,61 @@
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'
import { parseAndModifyConfigContent, withPayloadImportStatement } from './wrap-next-config.js'
import * as p from '@clack/prompts'
const esmConfigs = {
defaultNextConfig: `/** @type {import('next').NextConfig} */
const defaultNextConfig = `/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
`,
nextConfigWithFunc: `const nextConfig = {};
export default someFunc(nextConfig);
`,
nextConfigWithFuncMultiline: `const nextConfig = {};;
`
const nextConfigWithFunc = `const nextConfig = {
// Your Next.js config here
}
export default someFunc(nextConfig)
`
const nextConfigWithFuncMultiline = `const nextConfig = {
// Your Next.js config here
}
export default someFunc(
nextConfig
);
`,
nextConfigExportNamedDefault: `const nextConfig = {};
const wrapped = someFunc(asdf);
export { wrapped as default };
`,
nextConfigWithSpread: `const nextConfig = {
...someConfig,
};
export default nextConfig;
`,
}
)
`
const cjsConfigs = {
defaultNextConfig: `
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = nextConfig;
`,
anonConfig: `module.exports = {};`,
nextConfigWithFunc: `const nextConfig = {};
module.exports = someFunc(nextConfig);
`,
nextConfigWithFuncMultiline: `const nextConfig = {};
module.exports = someFunc(
nextConfig
);
`,
nextConfigExportNamedDefault: `const nextConfig = {};
const wrapped = someFunc(asdf);
module.exports = wrapped;
`,
nextConfigWithSpread: `const nextConfig = { ...someConfig };
module.exports = nextConfig;
`,
const nextConfigExportNamedDefault = `const nextConfig = {
// Your Next.js config here
}
const wrapped = someFunc(asdf)
export { wrapped as default }
`
describe('parseAndInsertWithPayload', () => {
describe('esm', () => {
const configType = 'esm'
const importStatement = withPayloadStatement[configType]
it('should parse the default next config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.defaultNextConfig,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
it('should parse the config with a function', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.nextConfigWithFunc,
configType,
)
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
})
it('should parse the config with a function on a new line', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.nextConfigWithFuncMultiline,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
it('should parse the config with a spread', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.nextConfigWithSpread,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
// Unsupported: export { wrapped as default }
it('should give warning with a named export as default', () => {
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
esmConfigs.nextConfigExportNamedDefault,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(success).toBe(false)
expect(warnLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Could not automatically wrap'),
)
})
it('should parse the default next config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(defaultNextConfig)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
it('should parse the config with a function', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFunc)
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
})
describe('cjs', () => {
const configType = 'cjs'
const requireStatement = withPayloadStatement[configType]
it('should parse the default next config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.defaultNextConfig,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
it('should parse anonymous default config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.anonConfig,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload({})')
})
it('should parse the config with a function', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigWithFunc,
configType,
)
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
})
it('should parse the config with a function on a new line', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigWithFuncMultiline,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
it('should parse the config with a named export as default', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigExportNamedDefault,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
})
it('should parse the config with a function on a new line', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFuncMultiline)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
it('should parse the config with a spread', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigWithSpread,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
// Unsupported: export { wrapped as default }
it('should give warning with a named export as default', () => {
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
nextConfigExportNamedDefault,
)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(success).toBe(false)
expect(warnLogSpy).toHaveBeenCalledWith(expect.stringContaining('Could not automatically wrap'))
})
})

View File

@@ -1,29 +1,16 @@
import type { Program } from 'esprima-next'
import chalk from 'chalk'
import { Syntax, parseModule } from 'esprima-next'
import { parseModule } from 'esprima'
import fs from 'fs'
import { warning } from '../utils/log.js'
import { log } from '../utils/log.js'
export const withPayloadStatement = {
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n`,
}
export const withPayloadImportStatement = `import { withPayload } from '@payloadcms/next'\n`
type NextConfigType = 'cjs' | 'esm'
export const wrapNextConfig = (args: {
nextConfigPath: string
nextConfigType: NextConfigType
}) => {
const { nextConfigPath, nextConfigType: configType } = args
export const wrapNextConfig = (args: { nextConfigPath: string }) => {
const { nextConfigPath } = args
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(
configContent,
configType,
)
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(configContent)
if (!success) {
return
@@ -35,121 +22,72 @@ export const wrapNextConfig = (args: {
/**
* Parses config content with AST and wraps it with withPayload function
*/
export function parseAndModifyConfigContent(
content: string,
configType: NextConfigType,
): { modifiedConfigContent: string; success: boolean } {
content = withPayloadStatement[configType] + content
export function parseAndModifyConfigContent(content: string): {
modifiedConfigContent: string
success: boolean
} {
content = withPayloadImportStatement + content
const ast = parseModule(content, { loc: true })
const exportDefaultDeclaration = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
| Directive
| undefined
let ast: Program | undefined
try {
ast = parseModule(content, { loc: true })
} catch (error: unknown) {
if (error instanceof Error) {
warning(`Unable to parse Next config. Error: ${error.message} `)
warnUserWrapNotSuccessful(configType)
}
return {
modifiedConfigContent: content,
success: false,
}
const exportNamedDeclaration = ast.body.find((p) => p.type === 'ExportNamedDeclaration') as
| ExportNamedDeclaration
| undefined
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
}
if (configType === 'esm') {
const exportDefaultDeclaration = ast.body.find(
(p) => p.type === Syntax.ExportDefaultDeclaration,
) as Directive | undefined
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
const modifiedConfigContent = insertBeforeAndAfter(
content,
exportDefaultDeclaration.declaration.loc,
)
return { modifiedConfigContent, success: true }
} else if (exportNamedDeclaration) {
const exportSpecifier = exportNamedDeclaration.specifiers.find(
(s) =>
s.type === 'ExportSpecifier' &&
s.exported?.name === 'default' &&
s.local?.type === 'Identifier' &&
s.local?.name,
)
const exportNamedDeclaration = ast.body.find(
(p) => p.type === Syntax.ExportNamedDeclaration,
) as ExportNamedDeclaration | undefined
if (exportSpecifier) {
warning('Could not automatically wrap next.config.js with withPayload.')
warning('Automatic wrapping of named exports as default not supported yet.')
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
}
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
const modifiedConfigContent = insertBeforeAndAfter(
content,
exportDefaultDeclaration.declaration.loc,
)
return { modifiedConfigContent, success: true }
} else if (exportNamedDeclaration) {
const exportSpecifier = exportNamedDeclaration.specifiers.find(
(s) =>
s.type === 'ExportSpecifier' &&
s.exported?.name === 'default' &&
s.local?.type === 'Identifier' &&
s.local?.name,
)
if (exportSpecifier) {
warning('Could not automatically wrap next.config.js with withPayload.')
warning('Automatic wrapping of named exports as default not supported yet.')
warnUserWrapNotSuccessful(configType)
return {
modifiedConfigContent: content,
success: false,
}
warnUserWrapNotSuccessful()
return {
modifiedConfigContent: content,
success: false,
}
}
warning('Could not automatically wrap Next config with withPayload.')
warnUserWrapNotSuccessful(configType)
return {
modifiedConfigContent: content,
success: false,
}
} else if (configType === 'cjs') {
// Find `module.exports = X`
const moduleExports = ast.body.find(
(p) =>
p.type === Syntax.ExpressionStatement &&
p.expression?.type === Syntax.AssignmentExpression &&
p.expression.left?.type === Syntax.MemberExpression &&
p.expression.left.object?.type === Syntax.Identifier &&
p.expression.left.object.name === 'module' &&
p.expression.left.property?.type === Syntax.Identifier &&
p.expression.left.property.name === 'exports',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
if (moduleExports && moduleExports.expression.right?.loc) {
const modifiedConfigContent = insertBeforeAndAfter(
content,
moduleExports.expression.right.loc,
)
return { modifiedConfigContent, success: true }
}
return {
modifiedConfigContent: content,
success: false,
}
}
warning('Could not automatically wrap Next config with withPayload.')
warnUserWrapNotSuccessful(configType)
warning('Could not automatically wrap next.config.js with withPayload.')
warnUserWrapNotSuccessful()
return {
modifiedConfigContent: content,
success: false,
}
}
function warnUserWrapNotSuccessful(configType: NextConfigType) {
function warnUserWrapNotSuccessful() {
// Output directions for user to update next.config.js
const withPayloadMessage = `
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)}
${withPayloadStatement[configType]}
import withPayload from '@payloadcms/next/withPayload'
const nextConfig = {
// Your Next.js config here
}
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'}
export default withPayload(nextConfig)
`

View File

@@ -20,41 +20,32 @@ export async function writeEnvFile(args: {
return
}
const envOutputPath = path.join(projectDir, '.env')
try {
if (fs.existsSync(envOutputPath)) {
if (template?.type === 'starter') {
// Parse .env file into key/value pairs
const envFile = await fs.readFile(path.join(projectDir, '.env.example'), 'utf8')
const envWithValues: string[] = envFile
.split('\n')
.filter((e) => e)
.map((line) => {
if (line.startsWith('#') || !line.includes('=')) return line
if (template?.type === 'starter' && fs.existsSync(path.join(projectDir, '.env.example'))) {
// Parse .env file into key/value pairs
const envFile = await fs.readFile(path.join(projectDir, '.env.example'), 'utf8')
const envWithValues: string[] = envFile
.split('\n')
.filter((e) => e)
.map((line) => {
if (line.startsWith('#') || !line.includes('=')) return line
const split = line.split('=')
const key = split[0]
let value = split[1]
const split = line.split('=')
const key = split[0]
let value = split[1]
if (key === 'MONGODB_URI' || key === 'MONGO_URL' || key === 'DATABASE_URI') {
value = databaseUri
}
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
value = payloadSecret
}
if (key === 'MONGODB_URI' || key === 'MONGO_URL' || key === 'DATABASE_URI') {
value = databaseUri
}
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
value = payloadSecret
}
return `${key}=${value}`
})
return `${key}=${value}`
})
// Write new .env file
await fs.writeFile(envOutputPath, envWithValues.join('\n'))
} else {
const existingEnv = await fs.readFile(envOutputPath, 'utf8')
const newEnv =
existingEnv + `\nDATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}\n`
await fs.writeFile(envOutputPath, newEnv)
}
// Write new .env file
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
} else {
const content = `DATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
await fs.outputFile(`${projectDir}/.env`, content)

View File

@@ -21,12 +21,6 @@ export function helpMessage(): void {
console.log(chalk`
{bold USAGE}
{dim Inside of an existing Next.js project}
{dim $} {bold npx create-payload-app}
{dim Create a new project from scratch}
{dim $} {bold npx create-payload-app}
{dim $} {bold npx create-payload-app} my-project
{dim $} {bold npx create-payload-app} -n my-project -t template-name
@@ -86,7 +80,7 @@ export function successfulNextInit(): string {
}
export function moveMessage(args: { nextAppDir: string; projectDir: string }): string {
const relativeAppDir = path.relative(process.cwd(), args.nextAppDir)
const relativePath = path.relative(process.cwd(), args.nextAppDir)
return `
${header('Next Steps:')}
@@ -94,10 +88,7 @@ Payload does not support a top-level layout.tsx file in the app directory.
${chalk.bold('To continue:')}
- Create a new directory in ./${relativeAppDir} such as ./${relativeAppDir}/${chalk.bold('(app)')}
- Move all files from ./${relativeAppDir} into that directory
It is recommended to do this from your IDE if your app has existing file references.
Move all files from ./${relativePath} to a named directory such as ./${relativePath}/${chalk.bold('(app)')}
Once moved, rerun the create-payload-app command again.
`

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.6",
"version": "3.0.0-alpha.61",
"description": "The officially supported MongoDB database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -56,6 +56,12 @@ export const init: Init = function init(this: MongooseAdapter) {
this.autoPluralization === true ? undefined : collection.slug,
) as CollectionModel
this.collections[collection.slug] = model
// TS expect error only needed until we launch 2.0.0
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
this.payload.collections[collection.slug] = {
config: collection,
}
})
const model = buildGlobalModel(this.payload.config)

View File

@@ -59,12 +59,17 @@ export async function buildSearchParam({
let hasCustomID = false
if (sanitizedPath === '_id') {
const customIDFieldType = payload.collections[collectionSlug]?.customIDType
const customIDfield = payload.collections[collectionSlug]?.config.fields.find(
(field) => fieldAffectsData(field) && field.name === 'id',
)
let idFieldType: 'number' | 'text' = 'text'
if (customIDFieldType) {
idFieldType = customIDFieldType
if (customIDfield) {
if (customIDfield?.type === 'text' || customIDfield?.type === 'number') {
idFieldType = customIDfield.type
}
hasCustomID = true
}
@@ -208,11 +213,18 @@ export async function buildSearchParam({
} else {
;(Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]).forEach(
(relationTo) => {
const isRelatedToCustomNumberID =
payload.collections[relationTo]?.customIDType === 'number'
const isRelatedToCustomNumberID = payload.collections[
relationTo
]?.config?.fields.find((relatedField) => {
return (
fieldAffectsData(relatedField) &&
relatedField.name === 'id' &&
relatedField.type === 'number'
)
})
if (isRelatedToCustomNumberID) {
hasNumberIDRelation = true
if (isRelatedToCustomNumberID.type === 'number') hasNumberIDRelation = true
}
},
)

View File

@@ -1,20 +1,18 @@
import { SanitizedConfig, sanitizeConfig } from 'payload/config'
import { sanitizeConfig } from 'payload/config'
import { Config } from 'payload/config'
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
const config = sanitizeConfig({
const config = {
localization: {
locales: ['en', 'es'],
defaultLocale: 'en',
fallback: true,
},
} as Config) as SanitizedConfig
} as Config
describe('get localized sort property', () => {
it('passes through a non-localized sort property', () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config,
config: sanitizeConfig(config),
fields: [
{
name: 'title',
@@ -30,7 +28,7 @@ describe('get localized sort property', () => {
it('properly localizes an un-localized sort property', () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config,
config: sanitizeConfig(config),
fields: [
{
name: 'title',
@@ -47,7 +45,7 @@ describe('get localized sort property', () => {
it('keeps specifically asked-for localized sort properties', () => {
const result = getLocalizedSortProperty({
segments: ['title', 'es'],
config,
config: sanitizeConfig(config),
fields: [
{
name: 'title',
@@ -64,7 +62,7 @@ describe('get localized sort property', () => {
it('properly localizes nested sort properties', () => {
const result = getLocalizedSortProperty({
segments: ['group', 'title'],
config,
config: sanitizeConfig(config),
fields: [
{
name: 'group',
@@ -87,7 +85,7 @@ describe('get localized sort property', () => {
it('keeps requested locale with nested sort properties', () => {
const result = getLocalizedSortProperty({
segments: ['group', 'title', 'es'],
config,
config: sanitizeConfig(config),
fields: [
{
name: 'group',
@@ -110,7 +108,7 @@ describe('get localized sort property', () => {
it('properly localizes field within row', () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config,
config: sanitizeConfig(config),
fields: [
{
type: 'row',
@@ -132,7 +130,7 @@ describe('get localized sort property', () => {
it('properly localizes field within named tab', () => {
const result = getLocalizedSortProperty({
segments: ['tab', 'title'],
config,
config: sanitizeConfig(config),
fields: [
{
type: 'tabs',
@@ -159,7 +157,7 @@ describe('get localized sort property', () => {
it('properly localizes field within unnamed tab', () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config,
config: sanitizeConfig(config),
fields: [
{
type: 'tabs',

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.6",
"version": "3.0.0-alpha.61",
"description": "The officially supported Postgres database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -298,12 +298,11 @@ export const buildTable = ({
throwValidationError: true,
})
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
const relatedCollectionCustomIDType =
adapter.payload.collections[relationshipConfig.slug]?.customIDType
if (relatedCollectionCustomIDType === 'number') colType = 'numeric'
if (relatedCollectionCustomIDType === 'text') colType = 'varchar'
const relatedCollectionCustomID = relationshipConfig.fields.find(
(field) => fieldAffectsData(field) && field.name === 'id',
)
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
`${formattedRelationTo}_id`,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.6",
"version": "3.0.0-alpha.61",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"type": "module",

View File

@@ -1,15 +0,0 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
}
},
"module": {
"type": "commonjs"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.6",
"version": "3.0.0-alpha.61",
"main": "./src/index.js",
"types": "./src/index.js",
"type": "module",
@@ -11,8 +11,7 @@
"directory": "packages/next"
},
"scripts": {
"build:cjs": "swc ./src/withPayload.js -o ./dist/cjs/withPayload.cjs --config-file .swcrc-cjs",
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:cjs && pnpm build:types && pnpm build:webpack && rm dist/prod/index.js",
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && pnpm build:webpack && rm dist/prod/index.js",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"build:webpack": "webpack --config webpack.config.js",
@@ -28,10 +27,6 @@
"require": "./src/index.js",
"types": "./src/index.js"
},
"./withPayload": {
"import": "./src/withPayload.js",
"require": "./src/withPayload.js"
},
"./*": {
"import": "./src/exports/*.ts",
"require": "./src/exports/*.ts",
@@ -89,9 +84,9 @@
"require": "./dist/prod/styles.css",
"default": "./dist/prod/styles.css"
},
"./withPayload": {
"import": "./dist/withPayload.js",
"require": "./dist/cjs/withPayload.cjs"
".": {
"import": "./dist/index.js",
"require": "./dist/index.js"
},
"./*": {
"import": "./dist/exports/*.js",

View File

@@ -1,2 +1,2 @@
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
export { getNextI18n } from '../utilities/getNextI18n.js'
export { getPayloadHMR } from '../utilities/getPayloadHMR.js'

View File

@@ -12,7 +12,6 @@ import { createClientConfig } from 'payload/config'
import React from 'react'
import 'react-toastify/dist/ReactToastify.css'
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
import { getRequestLanguage } from '../../utilities/getRequestLanguage.js'
import { DefaultEditView } from '../../views/Edit/Default/index.js'
import { DefaultListView } from '../../views/List/Default/index.js'
@@ -40,7 +39,6 @@ export const RootLayout = async ({
headers,
})
const payload = await getPayloadHMR({ config })
const i18n = await initI18n({ config: config.i18n, context: 'client', language: languageCode })
const clientConfig = await createClientConfig({ config, t: i18n.t })
@@ -78,7 +76,6 @@ export const RootLayout = async ({
children,
config,
i18n,
payload,
})
return (

View File

@@ -156,7 +156,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
})
}
if (globalSlug && schemaPath === globalSlug) {
if (globalSlug) {
resolvedData = await req.payload.findGlobal({
slug: globalSlug,
depth: 0,

View File

@@ -4,22 +4,9 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const deleteByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
export const deleteByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const doc = await deleteByIDOperation({
id,
collection,

View File

@@ -5,24 +5,12 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const duplicate: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
export const duplicate: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
// draft defaults to true, unless explicitly set requested as false to prevent the newly duplicated document from being published
const draft = searchParams.get('draft') !== 'false'
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const doc = await duplicateOperation({
id,
collection,

View File

@@ -4,22 +4,10 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const findByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
export const findByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const result = await findByIDOperation({
id,
collection,

View File

@@ -4,22 +4,10 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const findVersionByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
export const findVersionByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const result = await findVersionByIDOperation({
id,
collection,

View File

@@ -1,5 +1,4 @@
import httpStatus from 'http-status'
import { extractJWT } from 'payload/auth'
import { findByIDOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
@@ -25,14 +24,11 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re
(config) => config.slug === collection.config.slug,
)?.admin?.preview
const token = extractJWT(req)
if (typeof generatePreviewURL === 'function') {
try {
previewURL = await generatePreviewURL(result, {
locale: req.locale,
req,
token,
token: req.user?.token,
})
} catch (err) {
routeError({

View File

@@ -4,22 +4,10 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const restoreVersion: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
export const restoreVersion: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const result = await restoreVersionOperation({
id,
collection,

View File

@@ -4,24 +4,12 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const updateByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
export const updateByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const autosave = searchParams.get('autosave') === 'true'
const draft = searchParams.get('draft') === 'true'
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const doc = await updateByIDOperation({
id,
autosave,

View File

@@ -1,5 +1,4 @@
import httpStatus from 'http-status'
import { extractJWT } from 'payload/auth'
import { findOneOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
@@ -25,14 +24,11 @@ export const preview: GlobalRouteHandler = async ({ globalConfig, req }) => {
(config) => config.slug === globalConfig.slug,
)?.admin?.preview
const token = extractJWT(req)
if (typeof generatePreviewURL === 'function') {
try {
previewURL = await generatePreviewURL(result, {
locale: req.locale,
req,
token,
token: req.user?.token,
})
} catch (err) {
routeError({

View File

@@ -1,23 +0,0 @@
import type { Payload } from 'payload/types'
type Args = {
collectionSlug: string
id: string
payload: Payload
}
export const sanitizeCollectionID = ({ id, collectionSlug, payload }: Args): number | string => {
let sanitizedID: number | string = id
const collection = payload.collections[collectionSlug]
// If default db ID type is a number, we should sanitize
let shouldSanitize = Boolean(payload.db.defaultIDType === 'number')
// UNLESS the customIDType for this collection is text.... then we leave it
if (shouldSanitize && collection.customIDType === 'text') shouldSanitize = false
// If we still should sanitize, parse float
if (shouldSanitize) sanitizedID = parseFloat(sanitizedID)
return sanitizedID
}

View File

@@ -0,0 +1,20 @@
import type { AcceptedLanguages, I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { cookies, headers } from 'next/headers.js'
import { getRequestLanguage } from './getRequestLanguage.js'
export const getNextI18n = async ({
config,
language,
}: {
config: SanitizedConfig
language?: AcceptedLanguages
}): Promise<I18n> =>
initI18n({
config: config.i18n,
context: 'client',
language: language || getRequestLanguage({ config, cookies: cookies(), headers: headers() }),
})

View File

@@ -1,19 +0,0 @@
import type { I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { cookies, headers } from 'next/headers.js'
import { getRequestLanguage } from './getRequestLanguage.js'
/**
* In the context of NextJS, this function initializes the i18n object for the current request.
*
* It must be called on the server side, and within the lifecycle of a request since it relies on the request headers and cookies.
*/
export const getNextRequestI18n = async ({ config }: { config: SanitizedConfig }): Promise<I18n> =>
initI18n({
config: config.i18n,
context: 'client',
language: getRequestLanguage({ config, cookies: cookies(), headers: headers() }),
})

View File

@@ -35,10 +35,7 @@ export const getPayloadHMR = async (options: InitOptions): Promise<Payload> => {
cached.payload.config = config
cached.payload.collections = config.collections.reduce((collections, collection) => {
collections[collection.slug] = {
config: collection,
customIDType: cached.payload.collections[collection.slug]?.customIDType,
}
collections[collection.slug] = { config: collection }
return collections
}, {})

View File

@@ -2,7 +2,7 @@ import type { AcceptedLanguages } from '@payloadcms/translations'
import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies.js'
import type { SanitizedConfig } from 'payload/config'
import { extractHeaderLanguage } from '@payloadcms/translations'
import { matchLanguage } from '@payloadcms/translations'
type GetRequestLanguageArgs = {
config: SanitizedConfig
@@ -17,22 +17,14 @@ export const getRequestLanguage = ({
defaultLanguage = 'en',
headers,
}: GetRequestLanguageArgs): AcceptedLanguages => {
const langCookie = cookies.get(`${config.cookiePrefix || 'payload'}-lng`)
const languageFromCookie = typeof langCookie === 'string' ? langCookie : langCookie?.value
const languageFromHeader = headers.get('Accept-Language')
? extractHeaderLanguage(headers.get('Accept-Language'))
: undefined
const fallbackLang = config?.i18n?.fallbackLanguage || defaultLanguage
const acceptLanguage = headers.get('Accept-Language')
const cookieLanguage = cookies.get(`${config.cookiePrefix || 'payload'}-lng`)
const supportedLanguageKeys = Object.keys(config?.i18n?.supportedLanguages || {})
const reqLanguage =
(typeof cookieLanguage === 'string' ? cookieLanguage : cookieLanguage?.value) ||
acceptLanguage ||
config.i18n.fallbackLanguage ||
defaultLanguage
if (languageFromCookie && supportedLanguageKeys.includes(languageFromCookie)) {
return languageFromCookie as AcceptedLanguages
}
if (languageFromHeader && supportedLanguageKeys.includes(languageFromHeader)) {
return languageFromHeader
}
return supportedLanguageKeys.includes(fallbackLang) ? (fallbackLang as AcceptedLanguages) : 'en'
return matchLanguage(reqLanguage) || defaultLanguage
}

View File

@@ -1,10 +1,9 @@
import type { Field, WithServerSideProps as WithServerSidePropsType } from 'payload/types'
import type { Field } from 'payload/types'
import type { AdminViewProps } from 'payload/types'
import { Form } from '@payloadcms/ui/forms/Form'
import { FormSubmit } from '@payloadcms/ui/forms/Submit'
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import { WithServerSideProps as WithServerSidePropsGeneric } from '@payloadcms/ui/providers/ComponentMap'
import { mapFields } from '@payloadcms/ui/utilities/buildComponentMap'
import React from 'react'
@@ -18,7 +17,6 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
req,
req: {
i18n,
payload,
payload: {
config,
config: {
@@ -51,12 +49,7 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
},
]
const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => {
return <WithServerSidePropsGeneric Component={Component} payload={payload} {...rest} />
}
const createFirstUserFieldMap = mapFields({
WithServerSideProps,
config,
fieldSchema: fields,
i18n,

View File

@@ -3,7 +3,7 @@ import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/t
import type { GenerateViewMetadata } from '../Root/index.js'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { getNextI18n } from '../../utilities/getNextI18n.js'
import { generateMetadata as apiMeta } from '../API/meta.js'
import { generateMetadata as editMeta } from '../Edit/meta.js'
import { generateMetadata as livePreviewMeta } from '../LivePreview/meta.js'
@@ -89,7 +89,7 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
}
}
const i18n = await getNextRequestI18n({
const i18n = await getNextI18n({
config,
})

View File

@@ -6,8 +6,8 @@ import { HydrateClientUser } from '@payloadcms/ui/elements/HydrateClientUser'
import { DefaultTemplate } from '@payloadcms/ui/templates/Default'
import React, { Fragment } from 'react'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { initPage } from '../../utilities/initPage.js'
import { getNextI18n } from '.././../utilities/getNextI18n.js'
import { NotFoundClient } from './index.client.js'
export const generatePageMetadata = async ({
@@ -19,7 +19,7 @@ export const generatePageMetadata = async ({
}): Promise<Metadata> => {
const config = await configPromise
const i18n = await getNextRequestI18n({
const i18n = await getNextI18n({
config,
})

View File

@@ -1,7 +1,7 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { getNextI18n } from '../../utilities/getNextI18n.js'
import { generateAccountMetadata } from '../Account/index.js'
import { generateCreateFirstUserMetadata } from '../CreateFirstUser/index.js'
import { generateDashboardMetadata } from '../Dashboard/index.js'
@@ -49,7 +49,7 @@ export const generatePageMetadata = async ({ config: configPromise, params }: Ar
const isGlobal = segmentOne === 'globals'
const isCollection = segmentOne === 'collections'
const i18n = await getNextRequestI18n({
const i18n = await getNextI18n({
config,
})

View File

@@ -101,7 +101,7 @@ export const VersionsView: EditViewComponent = async (props) => {
return (
<React.Fragment>
<SetStepNav
collectionSlug={collectionConfig?.slug}
collectionSlug={collectionConfig?.slug || globalConfig?.slug}
globalSlug={globalConfig?.slug}
id={id}
pluralLabel={collectionConfig?.labels?.plural || globalConfig?.label}

View File

@@ -1,9 +1,5 @@
/**
* @param {import('next').NextConfig} nextConfig
*
* @returns {import('next').NextConfig}
* */
export const withPayload = (nextConfig = {}) => {
/** @type {import('next').NextConfig} */
const withPayload = (nextConfig = {}) => {
return {
...nextConfig,
experimental: {

View File

@@ -1,13 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"composite": true, // Required for references to work
"noEmit": false /* Do not emit outputs. */,
"emitDeclarationOnly": true,
"outDir": "./dist/cjs" /* Specify an output folder for all emitted files. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
"sourceMap": true
},
"include": ["src/withPayload.js" /* Include the withPayload.js file in the build */]
}

View File

@@ -24,4 +24,3 @@
/node.d.ts
/uploads.js
/uploads.d.ts
/i18n

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.6",
"version": "3.0.0-alpha.61",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./src/index.ts",

View File

@@ -4,7 +4,6 @@ import type { JSONSchema4 } from 'json-schema'
import type { SanitizedConfig } from '../config/types.js'
import type { Field, RichTextField, Validate } from '../fields/config/types.js'
import type { PayloadRequest, RequestContext } from '../types/index.js'
import type { WithServerSideProps } from './elements/WithServerSideProps.js'
export type RichTextFieldProps<
Value extends object,
@@ -29,7 +28,6 @@ type RichTextAdapterBase<
siblingDoc: Record<string, unknown>
}) => Promise<void> | null
generateComponentMap: (args: {
WithServerSideProps: WithServerSideProps
config: SanitizedConfig
i18n: I18n
schemaPath: string

View File

@@ -1,3 +1 @@
import type { CustomComponent } from '../../config/types.js'
export type CustomPreviewButton = CustomComponent
export type CustomPreviewButton = React.ComponentType

View File

@@ -1,3 +1 @@
import type { CustomComponent } from '../../config/types.js'
export type CustomPublishButton = CustomComponent
export type CustomPublishButton = React.ComponentType

View File

@@ -1,3 +1 @@
import type { CustomComponent } from '../../config/types.js'
export type CustomSaveButton = CustomComponent
export type CustomSaveButton = React.ComponentType

View File

@@ -1,3 +1 @@
import type { CustomComponent } from '../../config/types.js'
export type CustomSaveDraftButton = CustomComponent
export type CustomSaveDraftButton = React.ComponentType

View File

@@ -1,4 +0,0 @@
export type WithServerSideProps = (args: {
[key: string]: any
Component: React.ComponentType<any>
}) => React.ReactNode

View File

@@ -1,11 +1,10 @@
import type React from 'react'
import type { CustomComponent, LabelFunction } from '../../config/types.js'
import type { Payload } from '../../index.js'
import type { LabelFunction } from '../../config/types.js'
export type DescriptionFunction = LabelFunction
export type DescriptionComponent = CustomComponent<FieldDescriptionProps>
export type DescriptionComponent = React.ComponentType<FieldDescriptionProps>
export type Description =
| DescriptionComponent
@@ -18,5 +17,4 @@ export type FieldDescriptionProps = {
className?: string
description?: Record<string, string> | string
marginPlacement?: 'bottom' | 'top'
payload?: Payload
}

View File

@@ -1,5 +1,3 @@
import type { CustomComponent } from '../../config/types.js'
export type RowLabelComponent = CustomComponent
export type RowLabelComponent = React.ComponentType
export type RowLabel = Record<string, string> | RowLabelComponent | string

View File

@@ -13,7 +13,6 @@ export type {
DocumentTabConfig,
DocumentTabProps,
} from './elements/Tab.js'
export type { WithServerSideProps } from './elements/WithServerSideProps.js'
export type { ErrorProps } from './forms/Error.js'
export type {
Description,

View File

@@ -1,9 +1,8 @@
import type { GeneratedTypes } from '../index.js'
import type { AuthStrategyFunctionArgs, User } from './index.js'
export const executeAuthStrategies = async (
args: AuthStrategyFunctionArgs,
): Promise<GeneratedTypes['user'] | null> => {
): Promise<User | null> => {
return args.payload.authStrategies.reduce(async (accumulatorPromise, strategy) => {
const authUser = await accumulatorPromise
if (!authUser) {

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-param-reassign */
import type { CollectionConfig } from '../collections/config/types.js'
import type { Field, TabAsField } from '../fields/config/types.js'
import type { PayloadRequest } from '../types/index.js'
import type { User } from './index.js'
import { fieldAffectsData, tabHasName } from '../fields/config/types.js'
@@ -106,7 +105,7 @@ const traverseFields = ({
export const getFieldsToSign = (args: {
collectionConfig: CollectionConfig
email: string
user: PayloadRequest['user']
user: User
}): Record<string, unknown> => {
const { collectionConfig, email, user } = args

View File

@@ -1,4 +1,3 @@
import type { GeneratedTypes } from '../../index.js'
import type { PayloadRequest } from '../../types/index.js'
import type { Permissions, User } from '../types.js'
@@ -11,16 +10,16 @@ import { getAccessResults } from '../getAccessResults.js'
export type AuthArgs = {
headers: Request['headers']
req?: Omit<PayloadRequest, 'user'>
req: Omit<PayloadRequest, 'user'>
}
export type AuthResult = {
cookies: Map<string, string>
permissions: Permissions
user: GeneratedTypes['user'] | null
user: User | null
}
export const auth = async (args: Required<AuthArgs>): Promise<AuthResult> => {
export const auth = async (args: AuthArgs): Promise<AuthResult> => {
const { headers } = args
const req = args.req as PayloadRequest
const { payload } = req

View File

@@ -26,11 +26,6 @@ type ResolveFn = (...args: Required<ResolveArgs>) => Promise<ResolveResult>
const locatedConfig = getTsconfig()
const tsconfig = locatedConfig.config.compilerOptions as unknown as ts.CompilerOptions
// Ensure baseUrl is set in order to support paths
if (!tsconfig.baseUrl) {
tsconfig.baseUrl = '.'
}
// Don't resolve d.ts files, because we aren't type-checking
tsconfig.noDtsResolution = true
tsconfig.module = ts.ModuleKind.ESNext

View File

@@ -10,7 +10,6 @@ import type {
import type { Auth, ClientUser, IncomingAuthType } from '../../auth/types.js'
import type {
Access,
CustomComponent,
EditConfig,
Endpoint,
EntityDescription,
@@ -202,10 +201,10 @@ export type CollectionAdminOptions = {
* Custom admin components
*/
components?: {
AfterList?: CustomComponent[]
AfterListTable?: CustomComponent[]
BeforeList?: CustomComponent[]
BeforeListTable?: CustomComponent[]
AfterList?: React.ComponentType<any>[]
AfterListTable?: React.ComponentType<any>[]
BeforeList?: React.ComponentType<any>[]
BeforeListTable?: React.ComponentType<any>[]
/**
* Components within the edit view
*/
@@ -240,7 +239,7 @@ export type CollectionAdminOptions = {
List?:
| {
Component?: React.ComponentType<any>
actions?: CustomComponent[]
actions?: React.ComponentType<any>[]
}
| React.ComponentType<any>
}
@@ -409,7 +408,6 @@ export interface SanitizedCollectionConfig
export type Collection = {
config: SanitizedCollectionConfig
customIDType?: 'number' | 'text'
graphQL?: {
JWT: GraphQLObjectType
mutationInputType: GraphQLNonNull<any>

View File

@@ -5,6 +5,8 @@ import DataLoader from 'dataloader'
import type { PayloadRequest } from '../types/index.js'
import type { TypeWithID } from './config/types.js'
import { fieldAffectsData } from '../fields/config/types.js'
import { getIDType } from '../utilities/getIDType.js'
import { isValidID } from '../utilities/isValidID.js'
// Payload uses `dataloader` to solve the classic GraphQL N+1 problem.
@@ -69,13 +71,15 @@ const batchAndLoadDocs =
const batchKey = JSON.stringify(batchKeyArray)
const idType = payload.collections?.[collection].customIDType || payload.db.defaultIDType
const idField = payload.collections?.[collection].config.fields.find(
(field) => fieldAffectsData(field) && field.name === 'id',
)
let sanitizedID: number | string = id
if (idType === 'number') sanitizedID = parseFloat(id)
if (idField?.type === 'number') sanitizedID = parseFloat(id)
if (isValidID(sanitizedID, idType)) {
if (isValidID(sanitizedID, getIDType(idField, payload?.db?.defaultIDType))) {
return {
...batches,
[batchKey]: [...(batches[batchKey] || []), sanitizedID],

View File

@@ -22,6 +22,7 @@ import { generateFileData } from '../../uploads/generateFileData.js'
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles.js'
import { uploadFiles } from '../../uploads/uploadFiles.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import flattenFields from '../../utilities/flattenTopLevelFields.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
@@ -105,8 +106,11 @@ export const createOperation = async <TSlug extends keyof GeneratedTypes['collec
// /////////////////////////////////////
// Custom id
// /////////////////////////////////////
// @todo: Refactor code to store 'customId' on the collection configuration itself so we don't need to repeat flattenFields
const hasIdField =
flattenFields(collectionConfig.fields).findIndex((field) => field.name === 'id') > -1
if (payload.collections[collectionConfig.slug].customIDType) {
if (hasIdField) {
data = {
_id: data.id,
...data,

View File

@@ -7,18 +7,11 @@ import path from 'path'
* If no tsconfig.json file is found, returns the current working directory.
* @returns An object containing the source and output paths.
*/
const getTSConfigPaths = (): {
configPath?: string
outPath?: string
rootPath?: string
srcPath?: string
} => {
const getTSConfigPaths = (): { outPath: string; srcPath: string } => {
const tsConfigPath = findUp.sync('tsconfig.json')
if (!tsConfigPath) {
return {
rootPath: process.cwd(),
}
return { outPath: process.cwd(), srcPath: process.cwd() }
}
try {
@@ -30,25 +23,13 @@ const getTSConfigPaths = (): {
const tsConfig = JSON.parse(rawTsConfig)
const rootPath = process.cwd()
const srcPath = tsConfig.compilerOptions?.rootDir || path.resolve(process.cwd(), 'src')
const outPath = tsConfig.compilerOptions?.outDir || path.resolve(process.cwd(), 'dist')
const tsConfigDir = path.dirname(tsConfigPath)
let configPath = tsConfig.compilerOptions?.paths?.['@payload-config']?.[0]
if (configPath) {
configPath = path.resolve(tsConfigDir, configPath)
}
return {
configPath,
outPath,
rootPath,
srcPath,
}
const srcPath = tsConfig.compilerOptions?.rootDir || process.cwd()
const outPath = tsConfig.compilerOptions?.outDir || process.cwd()
return { outPath, srcPath }
} catch (error) {
console.error(`Error parsing tsconfig.json: ${error}`) // Do not throw the error, as we can still continue with the other config path finding methods
return {
rootPath: process.cwd(),
}
return { outPath: process.cwd(), srcPath: process.cwd() }
}
}
@@ -68,17 +49,12 @@ export const findConfig = (): string => {
return path.resolve(process.cwd(), process.env.PAYLOAD_CONFIG_PATH)
}
const { configPath, outPath, rootPath, srcPath } = getTSConfigPaths()
const { outPath, srcPath } = getTSConfigPaths()
const searchPaths =
process.env.NODE_ENV === 'production'
? [configPath, outPath, srcPath, rootPath]
: [configPath, srcPath, rootPath]
const searchPaths = process.env.NODE_ENV === 'production' ? [outPath, srcPath] : [srcPath]
// eslint-disable-next-line no-restricted-syntax
for (const searchPath of searchPaths) {
if (!searchPath) continue
const configPath = findUp.sync(
(dir) => {
const tsPath = path.join(dir, 'payload.config.ts')

View File

@@ -76,8 +76,7 @@ export type ServerOnlyLivePreviewProperties = keyof Pick<LivePreviewConfig, 'url
type GeneratePreviewURLOptions = {
locale: string
req: PayloadRequest
token: null | string
token: string
}
export type GeneratePreviewURL = (
@@ -280,7 +279,7 @@ export type EditViewConfig =
path: string
}
| {
actions?: CustomComponent[]
actions?: React.ComponentType<any>[]
}
/**
@@ -292,14 +291,6 @@ export type EditViewConfig =
*/
export type EditView = EditViewComponent | EditViewConfig
export type ServerProps = {
payload: Payload
}
export const serverProps: (keyof ServerProps)[] = ['payload']
export type CustomComponent<T extends any = any> = React.ComponentType<T & ServerProps>
export type Locale = {
/**
* value of supported locale
@@ -429,46 +420,46 @@ export type Config = {
/**
* Replace the navigation with a custom component
*/
Nav?: CustomComponent
Nav?: React.ComponentType<any>
/**
* Add custom components to the top right of the Admin Panel
*/
actions?: CustomComponent[]
actions?: React.ComponentType<any>[]
/**
* Add custom components after the collection overview
*/
afterDashboard?: CustomComponent[]
afterDashboard?: React.ComponentType<any>[]
/**
* Add custom components after the email/password field
*/
afterLogin?: CustomComponent[]
afterLogin?: React.ComponentType<any>[]
/**
* Add custom components after the navigation links
*/
afterNavLinks?: CustomComponent[]
afterNavLinks?: React.ComponentType<any>[]
/**
* Add custom components before the collection overview
*/
beforeDashboard?: CustomComponent[]
beforeDashboard?: React.ComponentType<any>[]
/**
* Add custom components before the email/password field
*/
beforeLogin?: CustomComponent[]
beforeLogin?: React.ComponentType<any>[]
/**
* Add custom components before the navigation links
*/
beforeNavLinks?: CustomComponent[]
beforeNavLinks?: React.ComponentType<any>[]
/** Replace graphical components */
graphics?: {
/** Replace the icon in the navigation */
Icon?: CustomComponent
Icon?: React.ComponentType<any>
/** Replace the logo on the login page */
Logo?: CustomComponent
Logo?: React.ComponentType<any>
}
/** Replace logout related components */
logout?: {
/** Replace the logout button */
Button?: CustomComponent
Button?: React.ComponentType<any>
}
/**
* Wrap the admin dashboard in custom context providers
@@ -725,7 +716,7 @@ export type EditConfig =
)
| EditViewComponent
export type EntityDescriptionComponent = CustomComponent
export type EntityDescriptionComponent = React.ComponentType<any>
export type EntityDescriptionFunction = () => string

View File

@@ -1 +0,0 @@
export { ar } from '@payloadcms/translations/languages/ar'

View File

@@ -1 +0,0 @@
export { az } from '@payloadcms/translations/languages/az'

View File

@@ -1 +0,0 @@
export { bg } from '@payloadcms/translations/languages/bg'

View File

@@ -1 +0,0 @@
export { cs } from '@payloadcms/translations/languages/cs'

View File

@@ -1 +0,0 @@
export { de } from '@payloadcms/translations/languages/de'

View File

@@ -1 +0,0 @@
export { en } from '@payloadcms/translations/languages/en'

View File

@@ -1 +0,0 @@
export { es } from '@payloadcms/translations/languages/es'

View File

@@ -1 +0,0 @@
export { fa } from '@payloadcms/translations/languages/fa'

View File

@@ -1 +0,0 @@
export { fr } from '@payloadcms/translations/languages/fr'

View File

@@ -1 +0,0 @@
export { hr } from '@payloadcms/translations/languages/hr'

View File

@@ -1 +0,0 @@
export { hu } from '@payloadcms/translations/languages/hu'

View File

@@ -1 +0,0 @@
export { it } from '@payloadcms/translations/languages/it'

View File

@@ -1 +0,0 @@
export { ja } from '@payloadcms/translations/languages/ja'

View File

@@ -1 +0,0 @@
export { ko } from '@payloadcms/translations/languages/ko'

View File

@@ -1 +0,0 @@
export { my } from '@payloadcms/translations/languages/my'

View File

@@ -1 +0,0 @@
export { nb } from '@payloadcms/translations/languages/nb'

View File

@@ -1 +0,0 @@
export { nl } from '@payloadcms/translations/languages/nl'

View File

@@ -1 +0,0 @@
export { pl } from '@payloadcms/translations/languages/pl'

View File

@@ -1 +0,0 @@
export { pt } from '@payloadcms/translations/languages/pt'

View File

@@ -1 +0,0 @@
export { ro } from '@payloadcms/translations/languages/ro'

View File

@@ -1 +0,0 @@
export { rs } from '@payloadcms/translations/languages/rs'

View File

@@ -1 +0,0 @@
export { rsLatin } from '@payloadcms/translations/languages/rsLatin'

View File

@@ -1 +0,0 @@
export { ru } from '@payloadcms/translations/languages/ru'

View File

@@ -1 +0,0 @@
export { sv } from '@payloadcms/translations/languages/sv'

View File

@@ -1 +0,0 @@
export { th } from '@payloadcms/translations/languages/th'

View File

@@ -1 +0,0 @@
export { tr } from '@payloadcms/translations/languages/tr'

View File

@@ -1 +0,0 @@
export { uk } from '@payloadcms/translations/languages/uk'

View File

@@ -1 +0,0 @@
export { vi } from '@payloadcms/translations/languages/vi'

View File

@@ -1 +0,0 @@
export { zh } from '@payloadcms/translations/languages/zh'

View File

@@ -1 +0,0 @@
export { zhTw } from '@payloadcms/translations/languages/zhTw'

View File

@@ -25,6 +25,7 @@ export { default as flattenTopLevelFields } from '../utilities/flattenTopLevelFi
export { formatLabels, formatNames, toWords } from '../utilities/formatLabels.js'
export { getCollectionIDFieldTypes } from '../utilities/getCollectionIDFieldTypes.js'
export { getIDType } from '../utilities/getIDType.js'
export { getObjectDotNotation } from '../utilities/getObjectDotNotation.js'
export { default as getUniqueListBy } from '../utilities/getUniqueListBy.js'
@@ -33,12 +34,7 @@ export { isEntityHidden } from '../utilities/isEntityHidden.js'
export { isNumber } from '../utilities/isNumber.js'
export { isPlainObject } from '../utilities/isPlainObject.js'
export {
isPlainFunction,
isReactClientComponent,
isReactComponent,
isReactServerComponent,
} from '../utilities/isReactComponent.js'
export { isPlainFunction, isReactComponent } from '../utilities/isReactComponent.js'
export { isValidID } from '../utilities/isValidID.js'
export { default as isolateObjectProperty } from '../utilities/isolateObjectProperty.js'

View File

@@ -1,8 +1,8 @@
import type { Field } from '../config/types.js'
import type { FieldWithRichTextRequiredEditor } from '../config/types.js'
import { baseIDField } from './baseIDField.js'
export const baseBlockFields: Field[] = [
export const baseBlockFields: FieldWithRichTextRequiredEditor[] = [
baseIDField,
{
name: 'blockName',

View File

@@ -14,10 +14,7 @@ import type { BaseDatabaseAdapter } from '../../database/types.js'
const dummyConfig: Config = {
collections: [],
db: {
defaultIDType: 'text',
init: () => ({}) as BaseDatabaseAdapter['init'],
} as BaseDatabaseAdapter,
db: () => ({}) as BaseDatabaseAdapter,
}
describe('sanitizeFields', () => {

View File

@@ -17,7 +17,7 @@ import type {
} from '../../admin/types.js'
import type { User } from '../../auth/index.js'
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.js'
import type { CustomComponent, LabelFunction } from '../../config/types.js'
import type { LabelFunction } from '../../config/types.js'
import type { DBIdentifierName } from '../../database/types.js'
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js'
@@ -81,7 +81,7 @@ export type FieldAccess<T extends TypeWithID = any, P = any, U = any> = (args: {
export type Condition<T extends TypeWithID = any, P = any> = (
data: Partial<T>,
siblingData: Partial<P>,
{ user }: { user: PayloadRequest['user'] },
{ user }: { user: User },
) => boolean
export type FilterOptionsProps<T = any> = {
@@ -104,7 +104,7 @@ export type FilterOptionsProps<T = any> = {
/**
* An object containing the currently authenticated user.
*/
user: Partial<PayloadRequest['user']>
user: Partial<User>
}
export type FilterOptions<T = any> =
@@ -115,8 +115,8 @@ export type FilterOptions<T = any> =
type Admin = {
className?: string
components?: {
Cell?: CustomComponent
Field?: CustomComponent
Cell?: React.ComponentType<any>
Field?: React.ComponentType<any>
Filter?: React.ComponentType<any>
}
/**
@@ -208,10 +208,10 @@ export type NumberField = FieldBase & {
/** Set this property to a string that will be used for browser autocomplete. */
autoComplete?: string
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
}
/** Set this property to define a placeholder string for the field. */
placeholder?: Record<string, string> | string
@@ -246,10 +246,10 @@ export type TextField = FieldBase & {
admin?: Admin & {
autoComplete?: string
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
}
placeholder?: Record<string, string> | string
rtl?: boolean
@@ -280,10 +280,10 @@ export type EmailField = FieldBase & {
admin?: Admin & {
autoComplete?: string
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
}
placeholder?: Record<string, string> | string
}
@@ -293,10 +293,10 @@ export type EmailField = FieldBase & {
export type TextareaField = FieldBase & {
admin?: Admin & {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
}
placeholder?: Record<string, string> | string
rows?: number
@@ -310,10 +310,10 @@ export type TextareaField = FieldBase & {
export type CheckboxField = FieldBase & {
admin?: Admin & {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
}
}
type: 'checkbox'
@@ -322,10 +322,10 @@ export type CheckboxField = FieldBase & {
export type DateField = FieldBase & {
admin?: Admin & {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
}
date?: ConditionalDateProps
placeholder?: Record<string, string> | string
@@ -416,8 +416,8 @@ export type TabAsField = Tab & {
export type UIField = {
admin: {
components?: {
Cell?: CustomComponent
Field: CustomComponent
Cell?: React.ComponentType<any>
Field: React.ComponentType<any>
Filter?: React.ComponentType<any>
}
condition?: Condition
@@ -435,8 +435,8 @@ export type UIField = {
export type UploadField = FieldBase & {
admin?: {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
}
}
filterOptions?: FilterOptions
@@ -447,8 +447,8 @@ export type UploadField = FieldBase & {
type CodeAdmin = Admin & {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
}
editorOptions?: EditorProps['options']
language?: string
@@ -463,8 +463,8 @@ export type CodeField = Omit<FieldBase, 'admin'> & {
type JSONAdmin = Admin & {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
}
editorOptions?: EditorProps['options']
}
@@ -477,8 +477,8 @@ export type JSONField = Omit<FieldBase, 'admin'> & {
export type SelectField = FieldBase & {
admin?: Admin & {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
}
isClearable?: boolean
isSortable?: boolean
@@ -533,8 +533,8 @@ type SharedRelationshipProperties = FieldBase & {
type RelationshipAdmin = Admin & {
allowCreate?: boolean
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
}
isSortable?: boolean
}
@@ -574,8 +574,8 @@ export type RichTextField<
> = FieldBase & {
admin?: Admin & {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
}
}
editor?: RichTextAdapter<Value, AdapterProps, AdapterProps>
@@ -618,8 +618,8 @@ export type ArrayField = FieldBase & {
export type RadioField = FieldBase & {
admin?: Admin & {
components?: {
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
}
layout?: 'horizontal' | 'vertical'
}

View File

@@ -1,12 +1,11 @@
import type { User } from '../auth/index.js'
import type { PayloadRequest } from '../types/index.js'
import { deepCopyObject } from '../utilities/deepCopyObject.js'
type Args = {
defaultValue: unknown
locale: string | undefined
user: PayloadRequest['user']
user: User
value?: unknown
}

View File

@@ -12,12 +12,7 @@ let options: ValidateOptions<any, any> = {
t,
context: {},
payload: {
config: {
db: {
defaultIDType: 'text',
init: () => null,
},
},
config: {},
},
},
}

View File

@@ -21,8 +21,10 @@ import type {
Validate,
} from './config/types.js'
import { getIDType } from '../utilities/getIDType.js'
import { isNumber } from '../utilities/isNumber.js'
import { isValidID } from '../utilities/isValidID.js'
import { fieldAffectsData } from './config/types.js'
export const text: Validate<string | string[], unknown, unknown, TextField> = (
value,
@@ -423,11 +425,13 @@ export const upload: Validate<unknown, unknown, unknown, UploadField> = (
}
if (typeof value !== 'undefined' && value !== null) {
const idType =
options?.req?.payload?.collections[options.relationTo]?.customIDType ||
options?.req?.payload?.db?.defaultIDType
const idField = options?.req?.payload?.config?.collections
?.find((collection) => collection.slug === options.relationTo)
?.fields?.find((field) => fieldAffectsData(field) && field.name === 'id')
if (!isValidID(value, idType)) {
const type = getIDType(idField, options?.req?.payload?.db?.defaultIDType)
if (!isValidID(value, type)) {
return options.req?.t('validation:validUploadID')
}
}
@@ -445,7 +449,11 @@ export const relationship: Validate<
maxRows,
minRows,
relationTo,
req: { payload, t },
req: {
payload,
payload: { config },
t,
},
required,
} = options
@@ -494,10 +502,13 @@ export const relationship: Validate<
if (requestedID === null) return false
const idType =
payload.collections[collectionSlug]?.customIDType || payload?.db?.defaultIDType || 'text'
const idField = config?.collections
?.find((collection) => collection.slug === collectionSlug)
?.fields?.find((field) => fieldAffectsData(field) && field.name === 'id')
return !isValidID(requestedID, idType)
const type = getIDType(idField, payload?.db?.defaultIDType)
return !isValidID(requestedID, type)
})
if (invalidRelationships.length > 0) {

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