Compare commits
153 Commits
v3.0.0-alp
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea4203bb32 | ||
|
|
568b5073c8 | ||
|
|
471e1f4827 | ||
|
|
b9185a6fcd | ||
|
|
80496aa94c | ||
|
|
5fd6e3c1a8 | ||
|
|
54590c1700 | ||
|
|
b1259be8f2 | ||
|
|
cd161e4b16 | ||
|
|
cb4214fe6e | ||
|
|
9d42751a42 | ||
|
|
c2c637b359 | ||
|
|
2f446e11d6 | ||
|
|
4f566b088c | ||
|
|
0d40d87b31 | ||
|
|
94c0095b3b | ||
|
|
4328060637 | ||
|
|
d98d0fd5bd | ||
|
|
5db2863d08 | ||
|
|
ff5e438d6d | ||
|
|
bfd5f13ee9 | ||
|
|
8043188f36 | ||
|
|
e3e0998772 | ||
|
|
86adc6f282 | ||
|
|
b51b519d30 | ||
|
|
c70dcb6a59 | ||
|
|
2486c7dba0 | ||
|
|
1456fcdcad | ||
|
|
a216800c72 | ||
|
|
c3d8597c13 | ||
|
|
844663ce1a | ||
|
|
055e6af7b7 | ||
|
|
479e6ecddc | ||
|
|
b9456e8244 | ||
|
|
432dfef435 | ||
|
|
429c6f7a48 | ||
|
|
6d41f6c56d | ||
|
|
20ac2b86cf | ||
|
|
b88455166a | ||
|
|
9b86de1f9d | ||
|
|
1393c72281 | ||
|
|
bcb538aee2 | ||
|
|
01e8f8c649 | ||
|
|
1119cf3af9 | ||
|
|
216934145c | ||
|
|
40f952cac3 | ||
|
|
330e4a7724 | ||
|
|
e676503e02 | ||
|
|
06233fbb2f | ||
|
|
17298695b1 | ||
|
|
c1081ccfe2 | ||
|
|
30da5a8643 | ||
|
|
55bf5436e4 | ||
|
|
a4956dc649 | ||
|
|
512b7bd429 | ||
|
|
5119c51439 | ||
|
|
f3e25f3277 | ||
|
|
1275c70187 | ||
|
|
be69fc448d | ||
|
|
bcccefe98e | ||
|
|
2061f38d9e | ||
|
|
d0869d9087 | ||
|
|
1eabf316d6 | ||
|
|
a0dd750a52 | ||
|
|
2fc9885abc | ||
|
|
14d683fb9a | ||
|
|
e286519cb1 | ||
|
|
b1e78a3562 | ||
|
|
0bc103658a | ||
|
|
9037b9b4fa | ||
|
|
d194493e9a | ||
|
|
aa22344cdb | ||
|
|
736e7b822e | ||
|
|
2ebda95036 | ||
|
|
d8783eaad4 | ||
|
|
cddb08de1a | ||
|
|
d4e5d3df54 | ||
|
|
414b03ce74 | ||
|
|
96dbab8834 | ||
|
|
03a110a750 | ||
|
|
6accc705be | ||
|
|
312dca003b | ||
|
|
4f9fdb6c14 | ||
|
|
94af06466b | ||
|
|
7cf2686097 | ||
|
|
f14883aa11 | ||
|
|
9df8de2386 | ||
|
|
8b2cf4705e | ||
|
|
364e9832ac | ||
|
|
2deeb61f17 | ||
|
|
14498e8a9c | ||
|
|
eb78022387 | ||
|
|
3677a59a78 | ||
|
|
7c1c840a59 | ||
|
|
a73eaf5d37 | ||
|
|
68989a58a8 | ||
|
|
9841731ae7 | ||
|
|
ba7ac5d439 | ||
|
|
7c60772b26 | ||
|
|
1141a5d3af | ||
|
|
6f74fd1f98 | ||
|
|
75873bfcfa | ||
|
|
1faf621f17 | ||
|
|
1d1c73dfcc | ||
|
|
d057ce0a85 | ||
|
|
0ce26d2c08 | ||
|
|
abf285d713 | ||
|
|
c2ee8e3999 | ||
|
|
167ba0c68f | ||
|
|
9ad1cbe920 | ||
|
|
313ea52e3d | ||
|
|
3acfb7a83f | ||
|
|
783dae2bbb | ||
|
|
59681b211b | ||
|
|
98438175cf | ||
|
|
af40302e5f | ||
|
|
ec0e0ae449 | ||
|
|
607ff17033 | ||
|
|
e73e610669 | ||
|
|
30fddde066 | ||
|
|
a56d2842fb | ||
|
|
35f59a47cc | ||
|
|
817d57bd12 | ||
|
|
5826048e7b | ||
|
|
1a975b31cf | ||
|
|
73298a80f0 | ||
|
|
a5d14ef4c1 | ||
|
|
d0c79b65f8 | ||
|
|
2fc50b1a1f | ||
|
|
0ddeedb0b3 | ||
|
|
09c2fb10f3 | ||
|
|
5bfff5b7ba | ||
|
|
702088375c | ||
|
|
ea507fbcc4 | ||
|
|
0ff1e6632b | ||
|
|
996ee47f96 | ||
|
|
3e9bd5bb62 | ||
|
|
ee7221c986 | ||
|
|
318c126ae3 | ||
|
|
2154aea89f | ||
|
|
4d3ad1af35 | ||
|
|
75cab7688f | ||
|
|
5084d6dd97 | ||
|
|
518f80cbb6 | ||
|
|
c9399efa65 | ||
|
|
d3016b7eb5 | ||
|
|
69e884f5b7 | ||
|
|
3e7925e33f | ||
|
|
7a2ccba63c | ||
|
|
be2134eb69 | ||
|
|
95e422b0e1 | ||
|
|
53d9c4ca95 | ||
|
|
30948ab545 |
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
push:
|
||||
branches: ['main', 'alpha']
|
||||
branches: ['main', 'beta']
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -88,7 +88,6 @@ 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
|
||||
@@ -221,8 +220,11 @@ jobs:
|
||||
- email
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
# - fields
|
||||
- fields/lexical
|
||||
- fields
|
||||
- fields__collections__Blocks
|
||||
- fields__collections__Array
|
||||
- fields__collections__Relationship
|
||||
- fields__collections__Lexical
|
||||
- live-preview
|
||||
- localization
|
||||
- plugin-form-builder
|
||||
|
||||
@@ -1 +1 @@
|
||||
v18.19.1
|
||||
v18.20.2
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views/NotFound/index.js'
|
||||
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
@@ -14,8 +14,8 @@ type Args = {
|
||||
}
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params })
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams })
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { RootPage, generatePageMetadata } from '@payloadcms/next/views/Root/index.js'
|
||||
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes/index.js'
|
||||
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = REST_GET(config)
|
||||
export const POST = REST_POST(config)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes/index.js'
|
||||
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = GRAPHQL_PLAYGROUND_GET(config)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_POST } from '@payloadcms/next/routes/index.js'
|
||||
import { GRAPHQL_POST } from '@payloadcms/next/routes'
|
||||
|
||||
export const POST = GRAPHQL_POST(config)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
import configPromise from '@payload-config'
|
||||
import { RootLayout } from '@payloadcms/next/layouts/Root/index.js'
|
||||
import { RootLayout } from '@payloadcms/next/layouts'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import React from 'react'
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ import { CallToAction } from '../blocks/CallToAction'
|
||||
Here's an overview of all the included features:
|
||||
|
||||
| Feature Name | Included by default | Description |
|
||||
| ------------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|--------------------------------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`BoldTextFeature`** | Yes | Handles the bold text format |
|
||||
| **`ItalicTextFeature`** | Yes | Handles the italic text format |
|
||||
| **`UnderlineTextFeature`** | Yes | Handles the underline text format |
|
||||
@@ -157,7 +157,8 @@ Here's an overview of all the included features:
|
||||
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
|
||||
| **`BlockQuoteFeature`** | Yes | Allows you to create block-level quotes |
|
||||
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
|
||||
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
|
||||
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an <hr> element |
|
||||
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
|
||||
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
|
||||
|
||||
## Creating your own, custom Feature
|
||||
@@ -234,6 +235,19 @@ This method employs `convertLexicalToHTML` from `@payloadcms/richtext-lexical`,
|
||||
|
||||
Because every `Feature` is able to provide html converters, and because the `htmlFeature` can modify those or provide their own, we need to consolidate them with the default html Converters using the `consolidateHTMLConverters` function.
|
||||
|
||||
#### CSS
|
||||
|
||||
Payload's lexical HTML converter does not generate CSS for you, but it does add classes to the generated HTML. You can use these classes to style the HTML in your frontend.
|
||||
|
||||
Here is some "base" CSS you can use to ensure that nested lists render correctly:
|
||||
|
||||
```css
|
||||
/* Base CSS for Lexical HTML */
|
||||
.nestedListItem, .list-check {
|
||||
list-style-type: none;
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating your own HTML Converter
|
||||
|
||||
HTML Converters are typed as `HTMLConverter`, which contains the node type it should handle, and a function that accepts the serialized node from the lexical editor, and outputs the HTML string. Here's the HTML Converter of the Upload node as an example:
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export default {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-alpha.60",
|
||||
"version": "3.0.0-beta.10",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces:": [
|
||||
@@ -127,7 +127,7 @@
|
||||
"lint-staged": "^14.0.1",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"next": "14.2.0-canary.22",
|
||||
"next": "^14.2.0-canary.23",
|
||||
"node-mocks-http": "^1.14.1",
|
||||
"nodemon": "3.0.3",
|
||||
"open": "^10.1.0",
|
||||
@@ -163,7 +163,7 @@
|
||||
"react": "18.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0",
|
||||
"node": ">=18.20.2",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-alpha.60",
|
||||
"version": "3.0.0-beta.10",
|
||||
"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": "^4.0.1",
|
||||
"esprima-next": "^6.0.3",
|
||||
"execa": "^5.0.0",
|
||||
"figures": "^6.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
|
||||
@@ -4,6 +4,7 @@ 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'
|
||||
@@ -31,6 +32,8 @@ type InitNextArgs = Pick<CliArgs, '--debug'> & {
|
||||
useDistFiles?: boolean
|
||||
}
|
||||
|
||||
type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
type InitNextResult =
|
||||
| {
|
||||
isSrcDir: boolean
|
||||
@@ -45,11 +48,22 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
|
||||
const nextAppDetails = args.nextAppDetails || (await getNextAppDetails(projectDir))
|
||||
|
||||
const { hasTopLevelLayout, isSrcDir, nextAppDir } =
|
||||
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
|
||||
}
|
||||
|
||||
if (!nextAppDir) {
|
||||
return { isSrcDir, reason: `Could not find app directory in ${projectDir}`, success: false }
|
||||
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 (hasTopLevelLayout) {
|
||||
@@ -69,6 +83,7 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const configurationResult = installAndConfigurePayload({
|
||||
...args,
|
||||
nextAppDetails,
|
||||
nextConfigType,
|
||||
useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa
|
||||
})
|
||||
|
||||
@@ -96,6 +111,13 @@ 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',
|
||||
})
|
||||
@@ -119,13 +141,18 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
|
||||
}
|
||||
|
||||
function installAndConfigurePayload(
|
||||
args: InitNextArgs & { nextAppDetails: NextAppDetails; useDistFiles?: boolean },
|
||||
args: InitNextArgs & {
|
||||
nextAppDetails: NextAppDetails
|
||||
nextConfigType: NextConfigType
|
||||
useDistFiles?: boolean
|
||||
},
|
||||
):
|
||||
| { payloadConfigPath: string; success: true }
|
||||
| { payloadConfigPath?: string; reason: string; success: false } {
|
||||
const {
|
||||
'--debug': debug,
|
||||
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
|
||||
nextConfigType,
|
||||
projectDir,
|
||||
useDistFiles,
|
||||
} = args
|
||||
@@ -172,6 +199,7 @@ 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)}`,
|
||||
@@ -181,7 +209,7 @@ function installAndConfigurePayload(
|
||||
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
|
||||
|
||||
// Wrap next.config.js with withPayload
|
||||
wrapNextConfig({ nextConfigPath })
|
||||
wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
|
||||
return {
|
||||
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
|
||||
@@ -191,10 +219,10 @@ function installAndConfigurePayload(
|
||||
|
||||
async function installDeps(projectDir: string, packageManager: PackageManager, dbType: DbType) {
|
||||
const packagesToInstall = ['payload', '@payloadcms/next', '@payloadcms/richtext-lexical'].map(
|
||||
(pkg) => `${pkg}@alpha`,
|
||||
(pkg) => `${pkg}@beta`,
|
||||
)
|
||||
|
||||
packagesToInstall.push(`@payloadcms/db-${dbType}@alpha`)
|
||||
packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)
|
||||
|
||||
let exitCode = 0
|
||||
switch (packageManager) {
|
||||
@@ -226,6 +254,7 @@ type NextAppDetails = {
|
||||
isSrcDir: boolean
|
||||
nextAppDir?: string
|
||||
nextConfigPath?: string
|
||||
nextConfigType?: NextConfigType
|
||||
}
|
||||
|
||||
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
|
||||
@@ -246,6 +275,7 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
|
||||
await globby(['**/app'], {
|
||||
absolute: true,
|
||||
cwd: projectDir,
|
||||
ignore: ['**/node_modules/**'],
|
||||
onlyDirectories: true,
|
||||
})
|
||||
)?.[0]
|
||||
@@ -254,9 +284,31 @@ 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 }
|
||||
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'
|
||||
}
|
||||
|
||||
@@ -10,14 +10,18 @@ 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 || '',",
|
||||
' },',
|
||||
' }),',
|
||||
],
|
||||
|
||||
@@ -18,43 +18,46 @@ 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',
|
||||
},
|
||||
{
|
||||
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',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank-3.0#beta',
|
||||
},
|
||||
|
||||
// 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',
|
||||
// },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,61 +1,159 @@
|
||||
import { parseAndModifyConfigContent, withPayloadImportStatement } from './wrap-next-config.js'
|
||||
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'
|
||||
import * as p from '@clack/prompts'
|
||||
|
||||
const defaultNextConfig = `/** @type {import('next').NextConfig} */
|
||||
const esmConfigs = {
|
||||
defaultNextConfig: `/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
export default nextConfig;
|
||||
`
|
||||
|
||||
const nextConfigWithFunc = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default someFunc(nextConfig)
|
||||
`
|
||||
const nextConfigWithFuncMultiline = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
`,
|
||||
nextConfigWithFunc: `const nextConfig = {};
|
||||
export default someFunc(nextConfig);
|
||||
`,
|
||||
nextConfigWithFuncMultiline: `const nextConfig = {};;
|
||||
export default someFunc(
|
||||
nextConfig
|
||||
)
|
||||
`
|
||||
|
||||
const nextConfigExportNamedDefault = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
);
|
||||
`,
|
||||
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 wrapped = someFunc(asdf)
|
||||
export { wrapped as default }
|
||||
`
|
||||
|
||||
describe('parseAndInsertWithPayload', () => {
|
||||
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('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 config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFuncMultiline)
|
||||
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
|
||||
})
|
||||
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)')
|
||||
})
|
||||
|
||||
// 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'))
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
import type { Program } from 'esprima-next'
|
||||
|
||||
import chalk from 'chalk'
|
||||
import { parseModule } from 'esprima'
|
||||
import { Syntax, parseModule } from 'esprima-next'
|
||||
import fs from 'fs'
|
||||
|
||||
import { warning } from '../utils/log.js'
|
||||
import { log } from '../utils/log.js'
|
||||
|
||||
export const withPayloadImportStatement = `import { withPayload } from '@payloadcms/next'\n`
|
||||
export const withPayloadStatement = {
|
||||
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
|
||||
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n`,
|
||||
}
|
||||
|
||||
export const wrapNextConfig = (args: { nextConfigPath: string }) => {
|
||||
const { nextConfigPath } = args
|
||||
type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
export const wrapNextConfig = (args: {
|
||||
nextConfigPath: string
|
||||
nextConfigType: NextConfigType
|
||||
}) => {
|
||||
const { nextConfigPath, nextConfigType: configType } = args
|
||||
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
|
||||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(configContent)
|
||||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(
|
||||
configContent,
|
||||
configType,
|
||||
)
|
||||
|
||||
if (!success) {
|
||||
return
|
||||
@@ -22,72 +35,121 @@ export const wrapNextConfig = (args: { nextConfigPath: string }) => {
|
||||
/**
|
||||
* Parses config content with AST and wraps it with withPayload function
|
||||
*/
|
||||
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
|
||||
export function parseAndModifyConfigContent(
|
||||
content: string,
|
||||
configType: NextConfigType,
|
||||
): { modifiedConfigContent: string; success: boolean } {
|
||||
content = withPayloadStatement[configType] + content
|
||||
|
||||
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 (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()
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warnUserWrapNotSuccessful()
|
||||
if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
function warnUserWrapNotSuccessful() {
|
||||
function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
// 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:`)}
|
||||
|
||||
import withPayload from '@payloadcms/next/withPayload'
|
||||
${withPayloadStatement[configType]}
|
||||
|
||||
const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default withPayload(nextConfig)
|
||||
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'}
|
||||
|
||||
`
|
||||
|
||||
|
||||
@@ -20,32 +20,41 @@ export async function writeEnvFile(args: {
|
||||
return
|
||||
}
|
||||
|
||||
const envOutputPath = path.join(projectDir, '.env')
|
||||
|
||||
try {
|
||||
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
|
||||
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
|
||||
|
||||
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(path.join(projectDir, '.env'), envWithValues.join('\n'))
|
||||
// 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)
|
||||
}
|
||||
} else {
|
||||
const content = `DATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
|
||||
await fs.outputFile(`${projectDir}/.env`, content)
|
||||
|
||||
@@ -21,6 +21,12 @@ 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
|
||||
@@ -80,7 +86,7 @@ export function successfulNextInit(): string {
|
||||
}
|
||||
|
||||
export function moveMessage(args: { nextAppDir: string; projectDir: string }): string {
|
||||
const relativePath = path.relative(process.cwd(), args.nextAppDir)
|
||||
const relativeAppDir = path.relative(process.cwd(), args.nextAppDir)
|
||||
return `
|
||||
${header('Next Steps:')}
|
||||
|
||||
@@ -88,7 +94,10 @@ Payload does not support a top-level layout.tsx file in the app directory.
|
||||
|
||||
${chalk.bold('To continue:')}
|
||||
|
||||
Move all files from ./${relativePath} to a named directory such as ./${relativePath}/${chalk.bold('(app)')}
|
||||
- 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.
|
||||
|
||||
Once moved, rerun the create-payload-app command again.
|
||||
`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-alpha.60",
|
||||
"version": "3.0.0-beta.10",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -56,12 +56,6 @@ 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)
|
||||
|
||||
@@ -59,17 +59,12 @@ export async function buildSearchParam({
|
||||
let hasCustomID = false
|
||||
|
||||
if (sanitizedPath === '_id') {
|
||||
const customIDfield = payload.collections[collectionSlug]?.config.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
const customIDFieldType = payload.collections[collectionSlug]?.customIDType
|
||||
|
||||
let idFieldType: 'number' | 'text' = 'text'
|
||||
|
||||
if (customIDfield) {
|
||||
if (customIDfield?.type === 'text' || customIDfield?.type === 'number') {
|
||||
idFieldType = customIDfield.type
|
||||
}
|
||||
|
||||
if (customIDFieldType) {
|
||||
idFieldType = customIDFieldType
|
||||
hasCustomID = true
|
||||
}
|
||||
|
||||
@@ -213,18 +208,11 @@ export async function buildSearchParam({
|
||||
} else {
|
||||
;(Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]).forEach(
|
||||
(relationTo) => {
|
||||
const isRelatedToCustomNumberID = payload.collections[
|
||||
relationTo
|
||||
]?.config?.fields.find((relatedField) => {
|
||||
return (
|
||||
fieldAffectsData(relatedField) &&
|
||||
relatedField.name === 'id' &&
|
||||
relatedField.type === 'number'
|
||||
)
|
||||
})
|
||||
const isRelatedToCustomNumberID =
|
||||
payload.collections[relationTo]?.customIDType === 'number'
|
||||
|
||||
if (isRelatedToCustomNumberID) {
|
||||
if (isRelatedToCustomNumberID.type === 'number') hasNumberIDRelation = true
|
||||
hasNumberIDRelation = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { sanitizeConfig } from 'payload/config'
|
||||
import { SanitizedConfig, sanitizeConfig } from 'payload/config'
|
||||
import { Config } from 'payload/config'
|
||||
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
|
||||
|
||||
const config = {
|
||||
const config = sanitizeConfig({
|
||||
localization: {
|
||||
locales: ['en', 'es'],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
} as Config
|
||||
} as Config) as SanitizedConfig
|
||||
|
||||
describe('get localized sort property', () => {
|
||||
it('passes through a non-localized sort property', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -28,7 +30,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes an un-localized sort property', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -45,7 +47,7 @@ describe('get localized sort property', () => {
|
||||
it('keeps specifically asked-for localized sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title', 'es'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -62,7 +64,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes nested sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['group', 'title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'group',
|
||||
@@ -85,7 +87,7 @@ describe('get localized sort property', () => {
|
||||
it('keeps requested locale with nested sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['group', 'title', 'es'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'group',
|
||||
@@ -108,7 +110,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within row', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
@@ -130,7 +132,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within named tab', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['tab', 'title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
@@ -157,7 +159,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within unnamed tab', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-alpha.60",
|
||||
"version": "3.0.0-beta.10",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -120,7 +120,7 @@ export const findMany = async function find({
|
||||
|
||||
const findPromise = db.query[tableName].findMany(findManyArgs)
|
||||
|
||||
if (pagination !== false && (orderedIDs ? orderedIDs?.length >= limit : true)) {
|
||||
if (pagination !== false && (orderedIDs ? orderedIDs?.length <= limit : true)) {
|
||||
const selectCountMethods: ChainedMethods = []
|
||||
|
||||
joinAliases.forEach(({ condition, table }) => {
|
||||
|
||||
@@ -298,11 +298,12 @@ export const buildTable = ({
|
||||
throwValidationError: true,
|
||||
})
|
||||
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
|
||||
const relatedCollectionCustomID = relationshipConfig.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
|
||||
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
|
||||
|
||||
const relatedCollectionCustomIDType =
|
||||
adapter.payload.collections[relationshipConfig.slug]?.customIDType
|
||||
|
||||
if (relatedCollectionCustomIDType === 'number') colType = 'numeric'
|
||||
if (relatedCollectionCustomIDType === 'text') colType = 'varchar'
|
||||
|
||||
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
|
||||
`${formattedRelationTo}_id`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-alpha.60",
|
||||
"version": "3.0.0-beta.10",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.d.ts",
|
||||
"type": "module",
|
||||
|
||||
@@ -39,7 +39,13 @@
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": null,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
|
||||
@@ -32,7 +32,13 @@
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": null,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
|
||||
15
packages/next/.swcrc-cjs
Normal file
15
packages/next/.swcrc-cjs
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-alpha.60",
|
||||
"version": "3.0.0-beta.10",
|
||||
"main": "./src/index.js",
|
||||
"types": "./src/index.js",
|
||||
"type": "module",
|
||||
@@ -11,7 +11,8 @@
|
||||
"directory": "packages/next"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && pnpm build:webpack && rm dist/prod/index.js",
|
||||
"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:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"build:webpack": "webpack --config webpack.config.js",
|
||||
@@ -27,6 +28,10 @@
|
||||
"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",
|
||||
@@ -72,7 +77,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"http-status": "1.6.2",
|
||||
"next": "14.2.0-canary.23",
|
||||
"next": "^14.2.0-canary.23",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
@@ -84,9 +89,9 @@
|
||||
"require": "./dist/prod/styles.css",
|
||||
"default": "./dist/prod/styles.css"
|
||||
},
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
"./withPayload": {
|
||||
"import": "./dist/withPayload.js",
|
||||
"require": "./dist/cjs/withPayload.cjs"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./dist/exports/*.js",
|
||||
@@ -97,7 +102,7 @@
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
"node": ">=18.20.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { getNextI18n } from '../utilities/getNextI18n.js'
|
||||
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
|
||||
export { getPayloadHMR } from '../utilities/getPayloadHMR.js'
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import type { AcceptedLanguages } from '@payloadcms/translations'
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { translations } from '@payloadcms/translations/client'
|
||||
import { rtlLanguages } from '@payloadcms/translations'
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { RootProvider } from '@payloadcms/ui/providers/Root'
|
||||
import '@payloadcms/ui/scss/app.scss'
|
||||
import { buildComponentMap } from '@payloadcms/ui/utilities/buildComponentMap'
|
||||
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
|
||||
import { parseCookies } from 'payload/auth'
|
||||
import { createClientConfig } from 'payload/config'
|
||||
import { deepMerge } from 'payload/utilities'
|
||||
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'
|
||||
@@ -20,7 +22,15 @@ export const metadata = {
|
||||
title: 'Next.js',
|
||||
}
|
||||
|
||||
const rtlLanguages = ['ar', 'fa', 'ha', 'ku', 'ur', 'ps', 'dv', 'ks', 'khw', 'he', 'yi']
|
||||
import { Merriweather } from 'next/font/google'
|
||||
|
||||
const merriweather = Merriweather({
|
||||
display: 'swap',
|
||||
style: ['normal', 'italic'],
|
||||
subsets: ['latin'],
|
||||
variable: '--font-serif',
|
||||
weight: ['400', '900'],
|
||||
})
|
||||
|
||||
export const RootLayout = async ({
|
||||
children,
|
||||
@@ -30,26 +40,37 @@ export const RootLayout = async ({
|
||||
config: Promise<SanitizedConfig>
|
||||
}) => {
|
||||
const config = await configPromise
|
||||
const clientConfig = await createClientConfig(config)
|
||||
|
||||
const headers = getHeaders()
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const lang =
|
||||
getRequestLanguage({
|
||||
config,
|
||||
cookies,
|
||||
headers,
|
||||
}) ?? clientConfig.i18n.fallbackLanguage
|
||||
const languageCode = getRequestLanguage({
|
||||
config,
|
||||
cookies,
|
||||
headers,
|
||||
})
|
||||
|
||||
const dir = rtlLanguages.includes(lang) ? 'RTL' : 'LTR'
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const i18n = await initI18n({ config: config.i18n, context: 'client', language: languageCode })
|
||||
const clientConfig = await createClientConfig({ config, t: i18n.t })
|
||||
|
||||
const mergedTranslations = deepMerge(translations, clientConfig.i18n.translations)
|
||||
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
|
||||
? 'RTL'
|
||||
: 'LTR'
|
||||
|
||||
const languageOptions = Object.entries(translations || {}).map(([language, translations]) => ({
|
||||
label: translations.general.thisLanguage,
|
||||
value: language,
|
||||
}))
|
||||
const languageOptions = Object.entries(config.i18n.supportedLanguages || {}).reduce(
|
||||
(acc, [language, languageConfig]) => {
|
||||
if (Object.keys(config.i18n.supportedLanguages).includes(language)) {
|
||||
acc.push({
|
||||
label: languageConfig.translations.general.thisLanguage,
|
||||
value: language,
|
||||
})
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async function switchLanguageServerAction(lang: string): Promise<void> {
|
||||
@@ -66,20 +87,23 @@ export const RootLayout = async ({
|
||||
DefaultListView,
|
||||
children,
|
||||
config,
|
||||
i18n,
|
||||
payload,
|
||||
})
|
||||
|
||||
return (
|
||||
<html dir={dir} lang={lang}>
|
||||
<html className={merriweather.variable} dir={dir} lang={languageCode}>
|
||||
<body>
|
||||
<RootProvider
|
||||
componentMap={componentMap}
|
||||
config={clientConfig}
|
||||
dateFNSKey={i18n.dateFNSKey}
|
||||
fallbackLang={clientConfig.i18n.fallbackLanguage}
|
||||
lang={lang}
|
||||
languageCode={languageCode}
|
||||
languageOptions={languageOptions}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
switchLanguageServerAction={switchLanguageServerAction}
|
||||
translations={mergedTranslations[lang]}
|
||||
translations={i18n.translations}
|
||||
>
|
||||
{wrappedChildren}
|
||||
</RootProvider>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { logoutOperation } from 'payload/operations'
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
export const logout: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const result = logoutOperation({
|
||||
const result = await logoutOperation({
|
||||
collection,
|
||||
req,
|
||||
})
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import type { BuildFormStateArgs } from '@payloadcms/ui/forms/buildStateFromSchema'
|
||||
import type {
|
||||
DocumentPreferences,
|
||||
Field,
|
||||
PayloadRequest,
|
||||
SanitizedConfig,
|
||||
TypeWithID,
|
||||
} from 'payload/types'
|
||||
import type { DocumentPreferences, Field, PayloadRequest, TypeWithID } from 'payload/types'
|
||||
|
||||
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
|
||||
import { reduceFieldsToValues } from '@payloadcms/ui/utilities/reduceFieldsToValues'
|
||||
@@ -22,12 +16,12 @@ if (!cached) {
|
||||
cached = global._payload_fieldSchemaMap = null
|
||||
}
|
||||
|
||||
export const getFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
|
||||
export const getFieldSchemaMap = (req: PayloadRequest): FieldSchemaMap => {
|
||||
if (cached && process.env.NODE_ENV !== 'development') {
|
||||
return cached
|
||||
}
|
||||
|
||||
cached = buildFieldSchemaMap(config)
|
||||
cached = buildFieldSchemaMap(req)
|
||||
|
||||
return cached
|
||||
}
|
||||
@@ -65,7 +59,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const fieldSchemaMap = getFieldSchemaMap(req.payload.config)
|
||||
const fieldSchemaMap = getFieldSchemaMap(req)
|
||||
|
||||
const id = collectionSlug ? reqData.id : undefined
|
||||
const schemaPathSegments = schemaPath.split('.')
|
||||
@@ -162,7 +156,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (globalSlug) {
|
||||
if (globalSlug && schemaPath === globalSlug) {
|
||||
resolvedData = await req.payload.findGlobal({
|
||||
slug: globalSlug,
|
||||
depth: 0,
|
||||
|
||||
@@ -4,9 +4,22 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
export const deleteByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const deleteByID: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
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,
|
||||
|
||||
@@ -5,12 +5,24 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
export const duplicate: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const duplicate: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
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,
|
||||
|
||||
@@ -4,10 +4,22 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
export const findByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const findByID: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
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,
|
||||
|
||||
@@ -4,10 +4,22 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
export const findVersionByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const findVersionByID: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
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,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import httpStatus from 'http-status'
|
||||
import { extractJWT } from 'payload/auth'
|
||||
import { findByIDOperation } from 'payload/operations'
|
||||
import { isNumber } from 'payload/utilities'
|
||||
|
||||
@@ -24,11 +25,14 @@ 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,
|
||||
token: req.user?.token,
|
||||
req,
|
||||
token,
|
||||
})
|
||||
} catch (err) {
|
||||
routeError({
|
||||
|
||||
@@ -4,10 +4,22 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
export const restoreVersion: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const restoreVersion: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
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,
|
||||
|
||||
@@ -4,12 +4,24 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
export const updateByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const updateByID: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
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,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import httpStatus from 'http-status'
|
||||
import { extractJWT } from 'payload/auth'
|
||||
import { findOneOperation } from 'payload/operations'
|
||||
import { isNumber } from 'payload/utilities'
|
||||
|
||||
@@ -24,11 +25,14 @@ 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,
|
||||
token: req.user?.token,
|
||||
req,
|
||||
token,
|
||||
})
|
||||
} catch (err) {
|
||||
routeError({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Collection, PayloadRequest } from 'payload/types'
|
||||
|
||||
import httpStatus from 'http-status'
|
||||
import { APIError, ValidationError } from 'payload/errors'
|
||||
import { APIError } from 'payload/errors'
|
||||
|
||||
export type ErrorResponse = { data?: any; errors: unknown[]; stack?: string }
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
@import './fonts.scss';
|
||||
@import './styles.scss';
|
||||
@import './toastify.scss';
|
||||
@import './colors.scss';
|
||||
@@ -20,9 +19,9 @@
|
||||
--theme-overlay: rgba(5, 5, 5, 0.5);
|
||||
--theme-baseline: #{$baseline-px};
|
||||
--theme-baseline-body-size: #{$baseline-body-size};
|
||||
--font-body: 'Suisse Intl', system-ui;
|
||||
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
sans-serif;
|
||||
--font-mono: monospace;
|
||||
--font-serif: 'Merriweather', serif;
|
||||
|
||||
--style-radius-s: #{$style-radius-s};
|
||||
--style-radius-m: #{$style-radius-m};
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Suisse Intl';
|
||||
src:
|
||||
url('../assets/fonts/SuisseIntl.woff2') format('woff2'),
|
||||
url('../assets/fonts/SuisseIntl.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Suisse Intl';
|
||||
src:
|
||||
url('../assets/fonts/SuisseIntl-Medium.woff2') format('woff2'),
|
||||
url('../assets/fonts/SuisseIntl-Medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Suisse Intl';
|
||||
src:
|
||||
url('../assets/fonts/SuisseIntl-SemiBold.woff2') format('woff2'),
|
||||
url('../assets/fonts/SuisseIntl-SemiBold.woff') format('woff');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Suisse Intl';
|
||||
src:
|
||||
url('../assets/fonts/SuisseIntl-Bold.woff2') format('woff2'),
|
||||
url('../assets/fonts/SuisseIntl-Bold.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src:
|
||||
local(''),
|
||||
url('../assets/fonts/merriweather-v30-latin-regular.woff2') format('woff2'),
|
||||
url('../assets/fonts/merriweather-v30-latin-regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src:
|
||||
local(''),
|
||||
url('../assets/fonts/merriweather-v30-latin-italic.woff2') format('woff2'),
|
||||
url('../assets/fonts/merriweather-v30-latin-italic.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src:
|
||||
local(''),
|
||||
url('../assets/fonts/merriweather-v30-latin-900.woff2') format('woff2'),
|
||||
url('../assets/fonts/merriweather-v30-latin-900.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Merriweather';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
src:
|
||||
local(''),
|
||||
url('../assets/fonts/merriweather-v30-latin-900italic.woff2') format('woff2'),
|
||||
url('../assets/fonts/merriweather-v30-latin-900italic.woff') format('woff');
|
||||
}
|
||||
@@ -21,13 +21,6 @@ $baseline: math.div($baseline-px, $baseline-body-size) + rem;
|
||||
@return (math.div($baseline-px, $baseline-body-size) * $multiplier) + rem;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// FONTS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
|
||||
//////////////////////////////
|
||||
|
||||
$font-body: 'Suisse Intl' !default;
|
||||
$font-mono: monospace !default;
|
||||
|
||||
//////////////////////////////
|
||||
// COLORS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
|
||||
//////////////////////////////
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import type { FieldSchemaMap } from './types.js'
|
||||
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
|
||||
export const buildFieldSchemaMap = ({
|
||||
i18n,
|
||||
payload: { config },
|
||||
}: PayloadRequest): FieldSchemaMap => {
|
||||
const result: FieldSchemaMap = new Map()
|
||||
|
||||
const validRelationships = config.collections.map((c) => c.slug) || []
|
||||
@@ -13,6 +16,7 @@ export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap =>
|
||||
traverseFields({
|
||||
config,
|
||||
fields: collection.fields,
|
||||
i18n,
|
||||
schemaMap: result,
|
||||
schemaPath: collection.slug,
|
||||
validRelationships,
|
||||
@@ -23,6 +27,7 @@ export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap =>
|
||||
traverseFields({
|
||||
config,
|
||||
fields: global.fields,
|
||||
i18n,
|
||||
schemaMap: result,
|
||||
schemaPath: global.slug,
|
||||
validRelationships,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type { Field, SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { tabHasName } from 'payload/types'
|
||||
@@ -7,6 +8,7 @@ import type { FieldSchemaMap } from './types.js'
|
||||
type Args = {
|
||||
config: SanitizedConfig
|
||||
fields: Field[]
|
||||
i18n: I18n
|
||||
schemaMap: FieldSchemaMap
|
||||
schemaPath: string
|
||||
validRelationships: string[]
|
||||
@@ -15,6 +17,7 @@ type Args = {
|
||||
export const traverseFields = ({
|
||||
config,
|
||||
fields,
|
||||
i18n,
|
||||
schemaMap,
|
||||
schemaPath,
|
||||
validRelationships,
|
||||
@@ -28,6 +31,7 @@ export const traverseFields = ({
|
||||
traverseFields({
|
||||
config,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
schemaMap,
|
||||
schemaPath: `${schemaPath}.${field.name}`,
|
||||
validRelationships,
|
||||
@@ -39,6 +43,7 @@ export const traverseFields = ({
|
||||
traverseFields({
|
||||
config,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
schemaMap,
|
||||
schemaPath,
|
||||
validRelationships,
|
||||
@@ -54,6 +59,7 @@ export const traverseFields = ({
|
||||
traverseFields({
|
||||
config,
|
||||
fields: block.fields,
|
||||
i18n,
|
||||
schemaMap,
|
||||
schemaPath: blockSchemaPath,
|
||||
validRelationships,
|
||||
@@ -65,6 +71,7 @@ export const traverseFields = ({
|
||||
if (typeof field.editor.generateSchemaMap === 'function') {
|
||||
field.editor.generateSchemaMap({
|
||||
config,
|
||||
i18n,
|
||||
schemaMap,
|
||||
schemaPath: `${schemaPath}.${field.name}`,
|
||||
})
|
||||
@@ -83,6 +90,7 @@ export const traverseFields = ({
|
||||
traverseFields({
|
||||
config,
|
||||
fields: tab.fields,
|
||||
i18n,
|
||||
schemaMap,
|
||||
schemaPath: tabSchemaPath,
|
||||
validRelationships,
|
||||
|
||||
@@ -6,7 +6,6 @@ import type {
|
||||
} from 'payload/types'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import { executeAuthStrategies } from 'payload/auth'
|
||||
import { parseCookies } from 'payload/auth'
|
||||
import { getDataLoader } from 'payload/utilities'
|
||||
@@ -72,11 +71,10 @@ export const createPayloadRequest = async ({
|
||||
headers: request.headers,
|
||||
})
|
||||
|
||||
const i18n = initI18n({
|
||||
const i18n = await initI18n({
|
||||
config: config.i18n,
|
||||
context: 'api',
|
||||
language,
|
||||
translations,
|
||||
})
|
||||
|
||||
const customRequest: CustomPayloadRequest = {
|
||||
|
||||
@@ -18,7 +18,11 @@ export const getDataAndFile: GetDataAndFile = async ({ collection, config, reque
|
||||
const [contentType] = (request.headers.get('Content-Type') || '').split(';')
|
||||
|
||||
if (contentType === 'application/json') {
|
||||
data = await request.json()
|
||||
try {
|
||||
data = await request.json()
|
||||
} catch (error) {
|
||||
data = {}
|
||||
}
|
||||
} else if (contentType === 'multipart/form-data') {
|
||||
// possible upload request
|
||||
if (collection?.config?.upload) {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/client'
|
||||
import { cookies, headers } from 'next/headers.js'
|
||||
|
||||
import { getRequestLanguage } from './getRequestLanguage.js'
|
||||
|
||||
export const getNextI18n = ({
|
||||
config,
|
||||
language,
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
language?: string
|
||||
}): I18n =>
|
||||
initI18n({
|
||||
config: config.i18n,
|
||||
context: 'client',
|
||||
language: language || getRequestLanguage({ config, cookies: cookies(), headers: headers() }),
|
||||
translations,
|
||||
})
|
||||
19
packages/next/src/utilities/getNextRequestI18n.ts
Normal file
19
packages/next/src/utilities/getNextRequestI18n.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
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() }),
|
||||
})
|
||||
@@ -35,7 +35,10 @@ 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 }
|
||||
collections[collection.slug] = {
|
||||
config: collection,
|
||||
customIDType: cached.payload.collections[collection.slug]?.customIDType,
|
||||
}
|
||||
return collections
|
||||
}, {})
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
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 { matchLanguage } from '@payloadcms/translations'
|
||||
import { extractHeaderLanguage } from '@payloadcms/translations'
|
||||
|
||||
type GetRequestLanguageArgs = {
|
||||
config: SanitizedConfig
|
||||
cookies: Map<string, string> | ReadonlyRequestCookies
|
||||
defaultLanguage?: string
|
||||
defaultLanguage?: AcceptedLanguages
|
||||
headers: Request['headers']
|
||||
}
|
||||
|
||||
@@ -15,14 +16,23 @@ export const getRequestLanguage = ({
|
||||
cookies,
|
||||
defaultLanguage = 'en',
|
||||
headers,
|
||||
}: GetRequestLanguageArgs): string => {
|
||||
const acceptLanguage = headers.get('Accept-Language')
|
||||
const cookieLanguage = cookies.get(`${config.cookiePrefix || 'payload'}-lng`)
|
||||
}: 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 reqLanguage =
|
||||
(typeof cookieLanguage === 'string' ? cookieLanguage : cookieLanguage?.value) ||
|
||||
acceptLanguage ||
|
||||
defaultLanguage
|
||||
const supportedLanguageKeys = Object.keys(config?.i18n?.supportedLanguages || {})
|
||||
|
||||
return matchLanguage(reqLanguage)
|
||||
if (languageFromCookie && supportedLanguageKeys.includes(languageFromCookie)) {
|
||||
return languageFromCookie as AcceptedLanguages
|
||||
}
|
||||
|
||||
if (languageFromHeader && supportedLanguageKeys.includes(languageFromHeader)) {
|
||||
return languageFromHeader
|
||||
}
|
||||
|
||||
return supportedLanguageKeys.includes(fallbackLang) ? (fallbackLang as AcceptedLanguages) : 'en'
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
} from 'payload/types'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/client'
|
||||
import { findLocaleFromCode } from '@payloadcms/ui/utilities/findLocaleFromCode'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
@@ -45,14 +44,13 @@ export const initPage = async ({
|
||||
const cookies = parseCookies(headers)
|
||||
const language = getRequestLanguage({ config: payload.config, cookies, headers })
|
||||
|
||||
const i18n = initI18n({
|
||||
const i18n = await initI18n({
|
||||
config: payload.config.i18n,
|
||||
context: 'client',
|
||||
language,
|
||||
translations,
|
||||
})
|
||||
|
||||
const req = createLocalReq(
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
fallbackLocale: null,
|
||||
locale: locale.code,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Field } from 'payload/types'
|
||||
import type { Field, WithServerSideProps as WithServerSidePropsType } 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'
|
||||
|
||||
@@ -16,6 +17,8 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
||||
const {
|
||||
req,
|
||||
req: {
|
||||
i18n,
|
||||
payload,
|
||||
payload: {
|
||||
config,
|
||||
config: {
|
||||
@@ -48,9 +51,15 @@ 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,
|
||||
parentPath: userSlug,
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/t
|
||||
|
||||
import type { GenerateViewMetadata } from '../Root/index.js'
|
||||
|
||||
import { getNextI18n } from '../../utilities/getNextI18n.js'
|
||||
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.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 getNextI18n({
|
||||
const i18n = await getNextRequestI18n({
|
||||
config,
|
||||
})
|
||||
|
||||
|
||||
@@ -117,7 +117,10 @@ export const ListView: React.FC<AdminViewProps> = async ({ initPageResult, searc
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<ListInfoProvider
|
||||
collectionConfig={createClientCollectionConfig(collectionConfig)}
|
||||
collectionConfig={createClientCollectionConfig({
|
||||
collection: collectionConfig,
|
||||
t: initPageResult.req.i18n.t,
|
||||
})}
|
||||
collectionSlug={collectionSlug}
|
||||
hasCreatePermission={permissions?.collections?.[collectionSlug]?.create?.permission}
|
||||
newDocumentURL={`${admin}/collections/${collectionSlug}/create`}
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { I18n } from '@payloadcms/translations'
|
||||
import type { Metadata } from 'next'
|
||||
import type { AdminViewComponent, SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { getNextI18n } from '@payloadcms/next/utilities'
|
||||
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 { NotFoundClient } from './index.client.js'
|
||||
|
||||
@@ -19,7 +19,7 @@ export const generatePageMetadata = async ({
|
||||
}): Promise<Metadata> => {
|
||||
const config = await configPromise
|
||||
|
||||
const i18n = getNextI18n({
|
||||
const i18n = await getNextRequestI18n({
|
||||
config,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Metadata } from 'next'
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { getNextI18n } from '../../utilities/getNextI18n.js'
|
||||
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.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 = getNextI18n({
|
||||
const i18n = await getNextRequestI18n({
|
||||
config,
|
||||
})
|
||||
|
||||
|
||||
@@ -85,7 +85,9 @@ export const SetStepNav: React.FC<{
|
||||
url: `${adminRoute}/collections/${collectionSlug}/${id}/versions`,
|
||||
},
|
||||
{
|
||||
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
|
||||
label: doc?.createdAt
|
||||
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
|
||||
: '',
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -101,7 +103,9 @@ export const SetStepNav: React.FC<{
|
||||
url: `${adminRoute}/globals/${globalConfig.slug}/versions`,
|
||||
},
|
||||
{
|
||||
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
|
||||
label: doc?.createdAt
|
||||
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
|
||||
: '',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
} = config
|
||||
|
||||
const formattedCreatedAt = doc?.createdAt
|
||||
? formatDate(doc.createdAt, dateFormat, i18n.language)
|
||||
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
|
||||
: ''
|
||||
|
||||
const originalDocFetchURL = `${serverURL}${apiRoute}/${globalSlug ? 'globals/' : ''}${
|
||||
|
||||
@@ -88,7 +88,7 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
setOptions((existingOptions) => [
|
||||
...existingOptions,
|
||||
...data.docs.map((doc) => ({
|
||||
label: formatDate(doc.updatedAt, dateFormat, i18n.language),
|
||||
label: formatDate({ date: doc.updatedAt, i18n, pattern: dateFormat }),
|
||||
value: doc.id,
|
||||
})),
|
||||
])
|
||||
|
||||
@@ -22,7 +22,7 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
|
||||
const doc: any = {} // TODO: figure this out
|
||||
|
||||
const formattedCreatedAt = doc?.createdAt
|
||||
? formatDate(doc.createdAt, config?.admin?.dateFormat, i18n?.language)
|
||||
? formatDate({ date: doc.createdAt, i18n, pattern: config?.admin?.dateFormat })
|
||||
: ''
|
||||
|
||||
if (collectionConfig) {
|
||||
|
||||
@@ -38,7 +38,8 @@ export const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
|
||||
|
||||
return (
|
||||
<Link href={to}>
|
||||
{cellData && formatDate(cellData as Date | number | string, dateFormat, i18n.language)}
|
||||
{cellData &&
|
||||
formatDate({ date: cellData as Date | number | string, i18n, pattern: dateFormat })}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ export const VersionsView: EditViewComponent = async (props) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SetStepNav
|
||||
collectionSlug={collectionConfig?.slug || globalConfig?.slug}
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
globalSlug={globalConfig?.slug}
|
||||
id={id}
|
||||
pluralLabel={collectionConfig?.labels?.plural || globalConfig?.label}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const withPayload = (nextConfig = {}) => {
|
||||
/**
|
||||
* @param {import('next').NextConfig} nextConfig
|
||||
*
|
||||
* @returns {import('next').NextConfig}
|
||||
* */
|
||||
export const withPayload = (nextConfig = {}) => {
|
||||
return {
|
||||
...nextConfig,
|
||||
experimental: {
|
||||
|
||||
13
packages/next/tsconfig.cjs.json
Normal file
13
packages/next/tsconfig.cjs.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"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 */]
|
||||
}
|
||||
26
packages/payload/.gitignore
vendored
26
packages/payload/.gitignore
vendored
@@ -1,26 +0,0 @@
|
||||
/fields/
|
||||
/components/
|
||||
/auth.d.ts
|
||||
/auth.js
|
||||
/components.d.ts
|
||||
/components.js
|
||||
/config.d.ts
|
||||
/config.js
|
||||
/database.d.ts
|
||||
/database.js
|
||||
/errors.d.ts
|
||||
/errors.js
|
||||
/graphql.d.ts
|
||||
/graphql.js
|
||||
/types.d.ts
|
||||
/types.js
|
||||
/utilities.d.ts
|
||||
/utilities.js
|
||||
/versions.d.ts
|
||||
/versions.js
|
||||
/operations.js
|
||||
/operations.d.ts
|
||||
/node.js
|
||||
/node.d.ts
|
||||
/uploads.js
|
||||
/uploads.d.ts
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.0.0-alpha.60",
|
||||
"version": "3.0.0-beta.10",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./src/index.ts",
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && tsx ../../scripts/exportPointerFiles.ts ../packages/payload dist/exports",
|
||||
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"pnpm build:tsc\"",
|
||||
@@ -113,7 +113,7 @@
|
||||
"ts-essentials": "7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
"node": ">=18.20.2"
|
||||
},
|
||||
"files": [
|
||||
"bin.js",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
@import './dist/admin/scss/vars';
|
||||
@import './dist/admin/scss/z-index';
|
||||
@import './dist/admin/scss/type';
|
||||
@import './dist/admin/scss/queries';
|
||||
@import './dist/admin/scss/resets';
|
||||
@import './dist/admin/scss/svg';
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
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,
|
||||
@@ -27,11 +29,14 @@ type RichTextAdapterBase<
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void> | null
|
||||
generateComponentMap: (args: {
|
||||
WithServerSideProps: WithServerSideProps
|
||||
config: SanitizedConfig
|
||||
i18n: I18n
|
||||
schemaPath: string
|
||||
}) => Map<string, React.ReactNode>
|
||||
generateSchemaMap?: (args: {
|
||||
config: SanitizedConfig
|
||||
i18n: I18n
|
||||
schemaMap: Map<string, Field[]>
|
||||
schemaPath: string
|
||||
}) => Map<string, Field[]>
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export type CustomPreviewButton = React.ComponentType
|
||||
import type { CustomComponent } from '../../config/types.js'
|
||||
|
||||
export type CustomPreviewButton = CustomComponent
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export type CustomPublishButton = React.ComponentType
|
||||
import type { CustomComponent } from '../../config/types.js'
|
||||
|
||||
export type CustomPublishButton = CustomComponent
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export type CustomSaveButton = React.ComponentType
|
||||
import type { CustomComponent } from '../../config/types.js'
|
||||
|
||||
export type CustomSaveButton = CustomComponent
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export type CustomSaveDraftButton = React.ComponentType
|
||||
import type { CustomComponent } from '../../config/types.js'
|
||||
|
||||
export type CustomSaveDraftButton = CustomComponent
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export type WithServerSideProps = (args: {
|
||||
[key: string]: any
|
||||
Component: React.ComponentType<any>
|
||||
}) => React.ReactNode
|
||||
@@ -1,8 +1,11 @@
|
||||
import type React from 'react'
|
||||
|
||||
export type DescriptionFunction = () => string
|
||||
import type { CustomComponent, LabelFunction } from '../../config/types.js'
|
||||
import type { Payload } from '../../index.js'
|
||||
|
||||
export type DescriptionComponent = React.ComponentType<FieldDescriptionProps>
|
||||
export type DescriptionFunction = LabelFunction
|
||||
|
||||
export type DescriptionComponent = CustomComponent<FieldDescriptionProps>
|
||||
|
||||
export type Description =
|
||||
| DescriptionComponent
|
||||
@@ -15,4 +18,5 @@ export type FieldDescriptionProps = {
|
||||
className?: string
|
||||
description?: Record<string, string> | string
|
||||
marginPlacement?: 'bottom' | 'top'
|
||||
payload?: Payload
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { LabelFunction } from '../../config/types.js'
|
||||
|
||||
export type LabelProps = {
|
||||
CustomLabel?: React.ReactNode
|
||||
as?: 'label' | 'span'
|
||||
htmlFor?: string
|
||||
label?: Record<string, string> | false | string
|
||||
label?: LabelFunction | Record<string, string> | false | string
|
||||
required?: boolean
|
||||
unstyled?: boolean
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export type RowLabelComponent = React.ComponentType
|
||||
import type { CustomComponent } from '../../config/types.js'
|
||||
|
||||
export type RowLabelComponent = CustomComponent
|
||||
|
||||
export type RowLabel = Record<string, string> | RowLabelComponent | string
|
||||
|
||||
@@ -13,6 +13,7 @@ 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,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Translations } from '@payloadcms/translations'
|
||||
import type { SupportedLanguages } from '@payloadcms/translations'
|
||||
|
||||
import type { Permissions } from '../../auth/index.js'
|
||||
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
|
||||
@@ -43,7 +43,7 @@ export type InitPageResult = {
|
||||
locale: Locale
|
||||
permissions: Permissions
|
||||
req: PayloadRequest
|
||||
translations: Translations
|
||||
translations: SupportedLanguages
|
||||
visibleEntities: VisibleEntities
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,12 @@ import crypto from 'crypto'
|
||||
|
||||
import type { Field, FieldHook } from '../../fields/config/types.js'
|
||||
|
||||
import { extractTranslations } from '../../translations/extractTranslations.js'
|
||||
|
||||
const labels = extractTranslations(['authentication:enableAPIKey', 'authentication:apiKey'])
|
||||
|
||||
const encryptKey: FieldHook = ({ req, value }) =>
|
||||
value ? req.payload.encrypt(value as string) : null
|
||||
const decryptKey: FieldHook = ({ req, value }) =>
|
||||
value ? req.payload.decrypt(value as string) : undefined
|
||||
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export default [
|
||||
{
|
||||
name: 'enableAPIKey',
|
||||
@@ -21,7 +18,7 @@ export default [
|
||||
},
|
||||
},
|
||||
defaultValue: false,
|
||||
label: labels['authentication:enableAPIKey'],
|
||||
label: ({ t }) => t('authentication:enableAPIKey'),
|
||||
},
|
||||
{
|
||||
name: 'apiKey',
|
||||
@@ -35,7 +32,7 @@ export default [
|
||||
afterRead: [decryptKey],
|
||||
beforeChange: [encryptKey],
|
||||
},
|
||||
label: labels['authentication:apiKey'],
|
||||
label: ({ t }) => t('authentication:apiKey'),
|
||||
},
|
||||
{
|
||||
name: 'apiKeyIndex',
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { Field } from '../../fields/config/types.js'
|
||||
|
||||
import { email } from '../../fields/validations.js'
|
||||
import { extractTranslations } from '../../translations/extractTranslations.js'
|
||||
|
||||
const labels = extractTranslations(['general:email'])
|
||||
|
||||
const baseAuthFields: Field[] = [
|
||||
{
|
||||
@@ -14,7 +11,7 @@ const baseAuthFields: Field[] = [
|
||||
Field: () => null,
|
||||
},
|
||||
},
|
||||
label: labels['general:email'],
|
||||
label: ({ t }) => t('general:email'),
|
||||
required: true,
|
||||
unique: true,
|
||||
validate: email,
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { Field, FieldHook } from '../../fields/config/types.js'
|
||||
|
||||
import { extractTranslations } from '../../translations/extractTranslations.js'
|
||||
|
||||
const labels = extractTranslations(['authentication:verified'])
|
||||
|
||||
const autoRemoveVerificationToken: FieldHook = ({ data, operation, originalDoc, value }) => {
|
||||
// If a user manually sets `_verified` to true,
|
||||
// and it was `false`, set _verificationToken to `null`.
|
||||
@@ -33,7 +29,7 @@ export default [
|
||||
Field: () => null,
|
||||
},
|
||||
},
|
||||
label: labels['authentication:verified'],
|
||||
label: ({ t }) => t('authentication:verified'),
|
||||
},
|
||||
{
|
||||
name: '_verificationToken',
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { CollectionConfig } from '../collections/config/types.js'
|
||||
|
||||
import { extractTranslations } from '../translations/extractTranslations.js'
|
||||
|
||||
const labels = extractTranslations(['general:user', 'general:users'])
|
||||
|
||||
export const defaultUserCollection: CollectionConfig = {
|
||||
slug: 'users',
|
||||
admin: {
|
||||
@@ -14,7 +10,7 @@ export const defaultUserCollection: CollectionConfig = {
|
||||
},
|
||||
fields: [],
|
||||
labels: {
|
||||
plural: labels['general:users'],
|
||||
singular: labels['general:user'],
|
||||
plural: ({ t }) => t('general:users'),
|
||||
singular: ({ t }) => t('general:user'),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { GeneratedTypes } from '../index.js'
|
||||
import type { AuthStrategyFunctionArgs, User } from './index.js'
|
||||
|
||||
export const executeAuthStrategies = async (
|
||||
args: AuthStrategyFunctionArgs,
|
||||
): Promise<User | null> => {
|
||||
): Promise<GeneratedTypes['user'] | null> => {
|
||||
return args.payload.authStrategies.reduce(async (accumulatorPromise, strategy) => {
|
||||
const authUser = await accumulatorPromise
|
||||
if (!authUser) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* 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'
|
||||
@@ -105,7 +106,7 @@ const traverseFields = ({
|
||||
export const getFieldsToSign = (args: {
|
||||
collectionConfig: CollectionConfig
|
||||
email: string
|
||||
user: User
|
||||
user: PayloadRequest['user']
|
||||
}): Record<string, unknown> => {
|
||||
const { collectionConfig, email, user } = args
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { GeneratedTypes } from '../../index.js'
|
||||
import type { PayloadRequest } from '../../types/index.js'
|
||||
import type { Permissions, User } from '../types.js'
|
||||
|
||||
@@ -10,16 +11,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: User | null
|
||||
user: GeneratedTypes['user'] | null
|
||||
}
|
||||
|
||||
export const auth = async (args: AuthArgs): Promise<AuthResult> => {
|
||||
export const auth = async (args: Required<AuthArgs>): Promise<AuthResult> => {
|
||||
const { headers } = args
|
||||
const req = args.req as PayloadRequest
|
||||
const { payload } = req
|
||||
|
||||
@@ -10,6 +10,6 @@ export const auth = async (payload: Payload, options: AuthArgs): Promise<AuthRes
|
||||
|
||||
return await authOperation({
|
||||
headers,
|
||||
req: createLocalReq({ req: options.req as PayloadRequest }, payload),
|
||||
req: await createLocalReq({ req: options.req as PayloadRequest }, payload),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ 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
|
||||
@@ -78,7 +83,6 @@ export const resolve: ResolveFn = async (specifier, context, nextResolve) => {
|
||||
// and keep going
|
||||
let nextResult: ResolveResult
|
||||
|
||||
// First, try to
|
||||
if (!isTS) {
|
||||
try {
|
||||
nextResult = await nextResolve(specifier, context, nextResolve)
|
||||
|
||||
@@ -23,14 +23,22 @@ export type ClientCollectionConfig = Omit<
|
||||
fields: ClientFieldConfig[]
|
||||
}
|
||||
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import type { ClientFieldConfig } from '../../fields/config/client.js'
|
||||
import type { SanitizedCollectionConfig } from './types.js'
|
||||
|
||||
import { createClientFieldConfigs } from '../../fields/config/client.js'
|
||||
|
||||
export const createClientCollectionConfig = (collection: SanitizedCollectionConfig) => {
|
||||
export const createClientCollectionConfig = ({
|
||||
collection,
|
||||
t,
|
||||
}: {
|
||||
collection: SanitizedCollectionConfig
|
||||
t: TFunction
|
||||
}) => {
|
||||
const sanitized = { ...collection }
|
||||
sanitized.fields = createClientFieldConfigs(sanitized.fields)
|
||||
sanitized.fields = createClientFieldConfigs({ fields: sanitized.fields, t })
|
||||
|
||||
const serverOnlyCollectionProperties: Partial<ServerOnlyCollectionProperties>[] = [
|
||||
'hooks',
|
||||
@@ -60,6 +68,14 @@ export const createClientCollectionConfig = (collection: SanitizedCollectionConf
|
||||
delete sanitized.auth.verify
|
||||
}
|
||||
|
||||
if (sanitized.labels) {
|
||||
Object.entries(sanitized.labels).forEach(([labelType, collectionLabel]) => {
|
||||
if (typeof collectionLabel === 'function') {
|
||||
sanitized.labels[labelType] = collectionLabel({ t })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if ('admin' in sanitized) {
|
||||
sanitized.admin = { ...sanitized.admin }
|
||||
|
||||
@@ -85,7 +101,11 @@ export const createClientCollectionConfig = (collection: SanitizedCollectionConf
|
||||
return sanitized
|
||||
}
|
||||
|
||||
export const createClientCollectionConfigs = (
|
||||
collections: SanitizedCollectionConfig[],
|
||||
): ClientCollectionConfig[] =>
|
||||
collections.map((collection) => createClientCollectionConfig(collection))
|
||||
export const createClientCollectionConfigs = ({
|
||||
collections,
|
||||
t,
|
||||
}: {
|
||||
collections: SanitizedCollectionConfig[]
|
||||
t: TFunction
|
||||
}): ClientCollectionConfig[] =>
|
||||
collections.map((collection) => createClientCollectionConfig({ collection, t }))
|
||||
|
||||
@@ -11,15 +11,12 @@ import TimestampsRequired from '../../errors/TimestampsRequired.js'
|
||||
import { sanitizeFields } from '../../fields/config/sanitize.js'
|
||||
import { fieldAffectsData } from '../../fields/config/types.js'
|
||||
import mergeBaseFields from '../../fields/mergeBaseFields.js'
|
||||
import { extractTranslations } from '../../translations/extractTranslations.js'
|
||||
import { getBaseUploadFields } from '../../uploads/getBaseFields.js'
|
||||
import { formatLabels } from '../../utilities/formatLabels.js'
|
||||
import { isPlainObject } from '../../utilities/isPlainObject.js'
|
||||
import baseVersionFields from '../../versions/baseFields.js'
|
||||
import { authDefaults, defaults } from './defaults.js'
|
||||
|
||||
const translations = extractTranslations(['general:createdAt', 'general:updatedAt'])
|
||||
|
||||
const sanitizeCollection = (
|
||||
config: Config,
|
||||
collection: CollectionConfig,
|
||||
@@ -51,7 +48,7 @@ const sanitizeCollection = (
|
||||
disableBulkEdit: true,
|
||||
hidden: true,
|
||||
},
|
||||
label: translations['general:updatedAt'],
|
||||
label: ({ t }) => t('general:updatedAt'),
|
||||
})
|
||||
}
|
||||
if (!hasCreatedAt) {
|
||||
@@ -64,7 +61,7 @@ const sanitizeCollection = (
|
||||
// The default sort for list view is createdAt. Thus, enabling indexing by default, is a major performance improvement, especially for large or a large amount of collections.
|
||||
type: 'date',
|
||||
index: true,
|
||||
label: translations['general:createdAt'],
|
||||
label: ({ t }) => t('general:createdAt'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,10 +140,10 @@ const collectionSchema = joi.object().keys({
|
||||
labels: joi.object({
|
||||
plural: joi
|
||||
.alternatives()
|
||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
||||
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
||||
singular: joi
|
||||
.alternatives()
|
||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
||||
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
||||
}),
|
||||
timestamps: joi.boolean(),
|
||||
typescript: joi.object().keys({
|
||||
|
||||
@@ -10,10 +10,12 @@ import type {
|
||||
import type { Auth, ClientUser, IncomingAuthType } from '../../auth/types.js'
|
||||
import type {
|
||||
Access,
|
||||
CustomComponent,
|
||||
EditConfig,
|
||||
Endpoint,
|
||||
EntityDescription,
|
||||
GeneratePreviewURL,
|
||||
LabelFunction,
|
||||
LivePreviewConfig,
|
||||
} from '../../config/types.js'
|
||||
import type { Field } from '../../fields/config/types.js'
|
||||
@@ -200,10 +202,10 @@ export type CollectionAdminOptions = {
|
||||
* Custom admin components
|
||||
*/
|
||||
components?: {
|
||||
AfterList?: React.ComponentType<any>[]
|
||||
AfterListTable?: React.ComponentType<any>[]
|
||||
BeforeList?: React.ComponentType<any>[]
|
||||
BeforeListTable?: React.ComponentType<any>[]
|
||||
AfterList?: CustomComponent[]
|
||||
AfterListTable?: CustomComponent[]
|
||||
BeforeList?: CustomComponent[]
|
||||
BeforeListTable?: CustomComponent[]
|
||||
/**
|
||||
* Components within the edit view
|
||||
*/
|
||||
@@ -238,7 +240,7 @@ export type CollectionAdminOptions = {
|
||||
List?:
|
||||
| {
|
||||
Component?: React.ComponentType<any>
|
||||
actions?: React.ComponentType<any>[]
|
||||
actions?: CustomComponent[]
|
||||
}
|
||||
| React.ComponentType<any>
|
||||
}
|
||||
@@ -360,8 +362,8 @@ export type CollectionConfig = {
|
||||
* Label configuration
|
||||
*/
|
||||
labels?: {
|
||||
plural?: Record<string, string> | string
|
||||
singular?: Record<string, string> | string
|
||||
plural?: LabelFunction | Record<string, string> | string
|
||||
singular?: LabelFunction | Record<string, string> | string
|
||||
}
|
||||
slug: string
|
||||
/**
|
||||
@@ -407,6 +409,7 @@ export interface SanitizedCollectionConfig
|
||||
|
||||
export type Collection = {
|
||||
config: SanitizedCollectionConfig
|
||||
customIDType?: 'number' | 'text'
|
||||
graphQL?: {
|
||||
JWT: GraphQLObjectType
|
||||
mutationInputType: GraphQLNonNull<any>
|
||||
|
||||
@@ -5,8 +5,6 @@ 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.
|
||||
@@ -71,15 +69,13 @@ const batchAndLoadDocs =
|
||||
|
||||
const batchKey = JSON.stringify(batchKeyArray)
|
||||
|
||||
const idField = payload.collections?.[collection].config.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
const idType = payload.collections?.[collection].customIDType || payload.db.defaultIDType
|
||||
|
||||
let sanitizedID: number | string = id
|
||||
|
||||
if (idField?.type === 'number') sanitizedID = parseFloat(id)
|
||||
if (idType === 'number') sanitizedID = parseFloat(id)
|
||||
|
||||
if (isValidID(sanitizedID, getIDType(idField, payload?.db?.defaultIDType))) {
|
||||
if (isValidID(sanitizedID, idType)) {
|
||||
return {
|
||||
...batches,
|
||||
[batchKey]: [...(batches[batchKey] || []), sanitizedID],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user