Compare commits
9 Commits
v3.0.0-bet
...
feat/forma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2cd6bbaec | ||
|
|
b2657ed12b | ||
|
|
0851ef99da | ||
|
|
3c483ab57a | ||
|
|
9bdbbb674f | ||
|
|
eb191f335b | ||
|
|
02bb176890 | ||
|
|
082c6ef44a | ||
|
|
f93cdf9364 |
43
.github/workflows/main.yml
vendored
43
.github/workflows/main.yml
vendored
@@ -196,7 +196,6 @@ jobs:
|
||||
- postgres-custom-schema
|
||||
- postgres-uuid
|
||||
- supabase
|
||||
- sqlite
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
@@ -549,45 +548,3 @@ jobs:
|
||||
steps:
|
||||
- if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
|
||||
run: exit 1
|
||||
|
||||
publish-canary:
|
||||
name: Publish Canary
|
||||
if: github.ref == 'refs/heads/beta'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- all-green
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Load npm token
|
||||
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Canary release script
|
||||
# dry run hard-coded to true for testing and no npm token provided
|
||||
run: pnpm tsx ./scripts/publish-canary.ts
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
|
||||
1
.github/workflows/pr-title.yml
vendored
1
.github/workflows/pr-title.yml
vendored
@@ -38,7 +38,6 @@ jobs:
|
||||
db-\*
|
||||
db-mongodb
|
||||
db-postgres
|
||||
db-sqlite
|
||||
email-nodemailer
|
||||
eslint
|
||||
graphql
|
||||
|
||||
@@ -266,10 +266,12 @@ export const myField: Field = {
|
||||
|
||||
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
Custom Label Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
All Label Components receive the following props:
|
||||
|
||||
| Property | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
| **`label`** | Label value provided in field, it can be used with i18n. |
|
||||
| **`required`** | The `admin.required` property defined in the [Field Config](../fields/overview). |
|
||||
| **`schemaPath`** | The path to the field in the schema. Similar to `path`, but without dynamic indices. |
|
||||
|
||||
<Banner type="success">
|
||||
@@ -277,36 +279,6 @@ Custom Label Components receive all [Field Component](#the-field-component) prop
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview). The convention is to append `ErrorComponent` to the type of field, i.e. `TextFieldErrorComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldLabelComponent,
|
||||
BlocksFieldLabelComponent,
|
||||
CheckboxFieldLabelComponent,
|
||||
CodeFieldLabelComponent,
|
||||
CollapsibleFieldLabelComponent,
|
||||
DateFieldLabelComponent,
|
||||
EmailFieldLabelComponent,
|
||||
GroupFieldLabelComponent,
|
||||
HiddenFieldLabelComponent,
|
||||
JSONFieldLabelComponent,
|
||||
NumberFieldLabelComponent,
|
||||
PointFieldLabelComponent,
|
||||
RadioFieldLabelComponent,
|
||||
RelationshipFieldLabelComponent,
|
||||
RichTextFieldLabelComponent,
|
||||
RowFieldLabelComponent,
|
||||
SelectFieldLabelComponent,
|
||||
TabsFieldLabelComponent,
|
||||
TextFieldLabelComponent,
|
||||
TextareaFieldLabelComponent,
|
||||
UploadFieldLabelComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### The Error Component
|
||||
|
||||
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
|
||||
@@ -329,7 +301,7 @@ export const myField: Field = {
|
||||
|
||||
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
Custom Error Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
All Error Components receive the following props:
|
||||
|
||||
| Property | Description |
|
||||
| --------------- | ------------------------------------------------------------- |
|
||||
@@ -340,36 +312,6 @@ Custom Error Components receive all [Field Component](#the-field-component) prop
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview). The convention is to append `ErrorComponent` to the type of field, i.e. `TextFieldErrorComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldErrorComponent,
|
||||
BlocksFieldErrorComponent,
|
||||
CheckboxFieldErrorComponent,
|
||||
CodeFieldErrorComponent,
|
||||
CollapsibleFieldErrorComponent,
|
||||
DateFieldErrorComponent,
|
||||
EmailFieldErrorComponent,
|
||||
GroupFieldErrorComponent,
|
||||
HiddenFieldErrorComponent,
|
||||
JSONFieldErrorComponent,
|
||||
NumberFieldErrorComponent,
|
||||
PointFieldErrorComponent,
|
||||
RadioFieldErrorComponent,
|
||||
RelationshipFieldErrorComponent,
|
||||
RichTextFieldErrorComponent,
|
||||
RowFieldErrorComponent,
|
||||
SelectFieldErrorComponent,
|
||||
TabsFieldErrorComponent,
|
||||
TextFieldErrorComponent,
|
||||
TextareaFieldErrorComponent,
|
||||
UploadFieldErrorComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### The Description Property
|
||||
|
||||
Field Descriptions are used to provide additional information to the editor about a field, such as special instructions. Their placement varies from field to field, but typically are displayed with subtle style differences beneath the field inputs.
|
||||
@@ -464,7 +406,7 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
|
||||
_For details on how to build a Custom Description, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
Custom Description Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
All Description Components receive the following props:
|
||||
|
||||
| Property | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
@@ -475,36 +417,6 @@ Custom Description Components receive all [Field Component](#the-field-component
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview). The convention is to append `DescriptionComponent` to the type of field, i.e. `TextFieldDescriptionComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldDescriptionComponent,
|
||||
BlocksFieldDescriptionComponent,
|
||||
CheckboxFieldDescriptionComponent,
|
||||
CodeFieldDescriptionComponent,
|
||||
CollapsibleFieldDescriptionComponent,
|
||||
DateFieldDescriptionComponent,
|
||||
EmailFieldDescriptionComponent,
|
||||
GroupFieldDescriptionComponent,
|
||||
HiddenFieldDescriptionComponent,
|
||||
JSONFieldDescriptionComponent,
|
||||
NumberFieldDescriptionComponent,
|
||||
PointFieldDescriptionComponent,
|
||||
RadioFieldDescriptionComponent,
|
||||
RelationshipFieldDescriptionComponent,
|
||||
RichTextFieldDescriptionComponent,
|
||||
RowFieldDescriptionComponent,
|
||||
SelectFieldDescriptionComponent,
|
||||
TabsFieldDescriptionComponent,
|
||||
TextFieldDescriptionComponent,
|
||||
TextareaFieldDescriptionComponent,
|
||||
UploadFieldDescriptionComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### afterInput and beforeInput
|
||||
|
||||
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
|
||||
|
||||
@@ -205,9 +205,7 @@ export const MyField: Field = {
|
||||
}
|
||||
```
|
||||
|
||||
Default values can be defined as a static value or a function that returns a value. When a `defaultValue` is defined statically, Payload's DB adapters will apply it to the database schema or models.
|
||||
|
||||
Functions can be written to make use of the following argument properties:
|
||||
Default values can be defined as a static string or a function that returns a string. Functions are called with the following arguments:
|
||||
|
||||
- `user` - the authenticated user object
|
||||
- `locale` - the currently selected locale string
|
||||
@@ -296,7 +294,7 @@ When using custom validation functions, Payload will use yours in place of the d
|
||||
To reuse default field validations, call them from within your custom validation function:
|
||||
|
||||
```ts
|
||||
import { text } from 'payload/shared'
|
||||
import { text } from 'payload/fields/validations'
|
||||
|
||||
const field: Field = {
|
||||
name: 'notBad',
|
||||
|
||||
@@ -193,7 +193,7 @@ You can learn more about writing queries [here](/docs/queries/overview).
|
||||
When a relationship field has both <strong>filterOptions</strong> and a custom{' '}
|
||||
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
|
||||
unless you call the default relationship field validation function imported from{' '}
|
||||
<strong>payload/shared</strong> in your validate function.
|
||||
<strong>payload/fields/validations</strong> in your validate function.
|
||||
</Banner>
|
||||
|
||||
## How the data is saved
|
||||
|
||||
@@ -57,7 +57,6 @@ export const MyUploadField: Field = {
|
||||
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
|
||||
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. [Admin Options](../admin/fields#admin-options). |
|
||||
@@ -124,5 +123,5 @@ You can learn more about writing queries [here](/docs/queries/overview).
|
||||
When an upload field has both <strong>filterOptions</strong> and a custom{' '}
|
||||
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
|
||||
unless you call the default upload field validation function imported from{' '}
|
||||
<strong>payload/shared</strong> in your validate function.
|
||||
<strong>payload/fields/validations</strong> in your validate function.
|
||||
</Banner>
|
||||
|
||||
@@ -335,6 +335,7 @@ import {
|
||||
FieldMap,
|
||||
File,
|
||||
Form,
|
||||
FormFieldBase,
|
||||
FormLoadingOverlayToggle,
|
||||
FormSubmit,
|
||||
GenerateConfirmation,
|
||||
|
||||
@@ -94,7 +94,6 @@ _An asterisk denotes that an option is required._
|
||||
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |
|
||||
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -119,7 +119,7 @@
|
||||
"create-payload-app": "workspace:*",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.5",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"drizzle-orm": "0.29.4",
|
||||
"escape-html": "^1.0.3",
|
||||
"execa": "5.1.1",
|
||||
"form-data": "3.0.1",
|
||||
@@ -150,7 +150,7 @@
|
||||
"tempy": "1.0.1",
|
||||
"tsx": "4.16.2",
|
||||
"turbo": "^1.13.3",
|
||||
"typescript": "5.5.4"
|
||||
"typescript": "5.5.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -50,7 +50,6 @@
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@sindresorhus/slugify": "^1.1.0",
|
||||
"@swc/core": "^1.6.13",
|
||||
"arg": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"comment-json": "^4.2.3",
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const installSpinner = p.spinner()
|
||||
installSpinner.start('Installing Payload and dependencies...')
|
||||
|
||||
const configurationResult = await installAndConfigurePayload({
|
||||
const configurationResult = installAndConfigurePayload({
|
||||
...args,
|
||||
nextAppDetails,
|
||||
nextConfigType,
|
||||
@@ -143,16 +143,15 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
|
||||
}
|
||||
}
|
||||
|
||||
async function installAndConfigurePayload(
|
||||
function installAndConfigurePayload(
|
||||
args: {
|
||||
nextAppDetails: NextAppDetails
|
||||
nextConfigType: NextConfigType
|
||||
useDistFiles?: boolean
|
||||
} & InitNextArgs,
|
||||
): Promise<
|
||||
):
|
||||
| { payloadConfigPath: string; success: true }
|
||||
| { payloadConfigPath?: string; reason: string; success: false }
|
||||
> {
|
||||
| { payloadConfigPath?: string; reason: string; success: false } {
|
||||
const {
|
||||
'--debug': debug,
|
||||
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
|
||||
@@ -213,7 +212,7 @@ async function installAndConfigurePayload(
|
||||
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
|
||||
|
||||
// Wrap next.config.js with withPayload
|
||||
await wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
|
||||
return {
|
||||
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
|
||||
@@ -240,63 +239,25 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
|
||||
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
|
||||
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))
|
||||
|
||||
// Match next.config.js, next.config.ts, next.config.mjs, next.config.cjs
|
||||
const nextConfigPath: string | undefined = (
|
||||
await globby('next.config.(\\w)?(t|j)s', { absolute: true, cwd: projectDir })
|
||||
await globby('next.config.*js', { absolute: true, cwd: projectDir })
|
||||
)?.[0]
|
||||
|
||||
if (!nextConfigPath || nextConfigPath.length === 0) {
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion: false,
|
||||
nextConfigPath: undefined,
|
||||
nextVersion: null,
|
||||
}
|
||||
}
|
||||
|
||||
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
|
||||
// Check if Next.js version is new enough
|
||||
let nextVersion = null
|
||||
if (packageObj.dependencies?.next) {
|
||||
nextVersion = packageObj.dependencies.next
|
||||
// Match versions using regex matching groups
|
||||
const versionMatch = /(?<major>\d+)/.exec(nextVersion)
|
||||
if (!versionMatch) {
|
||||
p.log.warn(`Could not determine Next.js version from ${nextVersion}`)
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion: false,
|
||||
nextConfigPath,
|
||||
nextVersion,
|
||||
}
|
||||
}
|
||||
|
||||
const { major } = versionMatch.groups as { major: string }
|
||||
const majorVersion = parseInt(major)
|
||||
if (majorVersion < 15) {
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion: false,
|
||||
nextConfigPath,
|
||||
nextVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isSupportedNextVersion = true
|
||||
|
||||
// Check if Payload already installed
|
||||
if (packageObj.dependencies?.payload) {
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isPayloadInstalled: true,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion,
|
||||
nextConfigPath,
|
||||
nextVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,27 +280,14 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
|
||||
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
|
||||
: false
|
||||
|
||||
return {
|
||||
hasTopLevelLayout,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion,
|
||||
nextAppDir,
|
||||
nextConfigPath,
|
||||
nextConfigType: configType,
|
||||
nextVersion,
|
||||
}
|
||||
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath, nextConfigType: configType }
|
||||
}
|
||||
|
||||
function getProjectType(args: {
|
||||
nextConfigPath: string
|
||||
packageObj: Record<string, unknown>
|
||||
}): NextConfigType {
|
||||
}): 'cjs' | 'esm' {
|
||||
const { nextConfigPath, packageObj } = args
|
||||
|
||||
if (nextConfigPath.endsWith('.ts')) {
|
||||
return 'ts'
|
||||
}
|
||||
|
||||
if (nextConfigPath.endsWith('.mjs')) {
|
||||
return 'esm'
|
||||
}
|
||||
|
||||
@@ -29,22 +29,9 @@ const postgresReplacement: DbAdapterReplacement = {
|
||||
packageName: '@payloadcms/db-postgres',
|
||||
}
|
||||
|
||||
const sqliteReplacement: DbAdapterReplacement = {
|
||||
configReplacement: (envName = 'DATABASE_URI') => [
|
||||
' db: sqliteAdapter({',
|
||||
' client: {',
|
||||
` url: process.env.${envName} || '',`,
|
||||
' },',
|
||||
' }),',
|
||||
],
|
||||
importReplacement: "import { sqliteAdapter } from '@payloadcms/db-sqlite'",
|
||||
packageName: '@payloadcms/db-sqlite',
|
||||
}
|
||||
|
||||
export const dbReplacements: Record<DbType, DbAdapterReplacement> = {
|
||||
mongodb: mongodbReplacement,
|
||||
postgres: postgresReplacement,
|
||||
sqlite: sqliteReplacement,
|
||||
}
|
||||
|
||||
type StorageAdapterReplacement = {
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { CliArgs, DbDetails, DbType } from '../types.js'
|
||||
|
||||
type DbChoice = {
|
||||
dbConnectionPrefix: `${string}/`
|
||||
dbConnectionSuffix?: string
|
||||
title: string
|
||||
value: DbType
|
||||
}
|
||||
@@ -21,12 +20,6 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
|
||||
title: 'PostgreSQL (beta)',
|
||||
value: 'postgres',
|
||||
},
|
||||
sqlite: {
|
||||
dbConnectionPrefix: 'file:./',
|
||||
dbConnectionSuffix: '.db',
|
||||
title: 'SQLite (beta)',
|
||||
value: 'sqlite',
|
||||
},
|
||||
}
|
||||
|
||||
export async function selectDb(args: CliArgs, projectName: string): Promise<DbDetails> {
|
||||
@@ -44,10 +37,10 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
|
||||
dbType = await p.select<{ label: string; value: DbType }[], DbType>({
|
||||
initialValue: 'mongodb',
|
||||
message: `Select a database`,
|
||||
options: Object.values(dbChoiceRecord).map((dbChoice) => ({
|
||||
label: dbChoice.title,
|
||||
value: dbChoice.value,
|
||||
})),
|
||||
options: [
|
||||
{ label: 'MongoDB', value: 'mongodb' },
|
||||
{ label: 'Postgres', value: 'postgres' },
|
||||
],
|
||||
})
|
||||
if (p.isCancel(dbType)) process.exit(0)
|
||||
}
|
||||
@@ -57,7 +50,7 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
|
||||
let dbUri: string | symbol | undefined = undefined
|
||||
const initialDbUri = `${dbChoice.dbConnectionPrefix}${
|
||||
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
|
||||
}${dbChoice.dbConnectionSuffix || ''}`
|
||||
}`
|
||||
|
||||
if (args['--db-accept-recommended']) {
|
||||
dbUri = initialDbUri
|
||||
|
||||
@@ -3,35 +3,6 @@ import { jest } from '@jest/globals'
|
||||
|
||||
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'
|
||||
|
||||
const tsConfigs = {
|
||||
defaultNextConfig: `import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {};
|
||||
export default nextConfig;`,
|
||||
|
||||
nextConfigExportNamedDefault: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
export { wrapped as default };
|
||||
`,
|
||||
nextConfigWithFunc: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
export default someFunc(nextConfig);
|
||||
`,
|
||||
nextConfigWithFuncMultiline: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
export default someFunc(
|
||||
nextConfig
|
||||
);
|
||||
`,
|
||||
nextConfigWithSpread: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {
|
||||
...someConfig,
|
||||
};
|
||||
export default nextConfig;
|
||||
`,
|
||||
}
|
||||
|
||||
const esmConfigs = {
|
||||
defaultNextConfig: `/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
@@ -81,66 +52,27 @@ module.exports = nextConfig;
|
||||
}
|
||||
|
||||
describe('parseAndInsertWithPayload', () => {
|
||||
describe('ts', () => {
|
||||
const configType = 'ts'
|
||||
const importStatement = withPayloadStatement[configType]
|
||||
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
|
||||
it('should parse the config with a function', async () => {
|
||||
const { modifiedConfigContent: modifiedConfigContent2 } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent2).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
|
||||
it('should parse the config with a multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
})
|
||||
describe('esm', () => {
|
||||
const configType = 'esm'
|
||||
const importStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
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', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
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 multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
@@ -148,8 +80,8 @@ describe('parseAndInsertWithPayload', () => {
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
@@ -158,10 +90,10 @@ describe('parseAndInsertWithPayload', () => {
|
||||
})
|
||||
|
||||
// Unsupported: export { wrapped as default }
|
||||
it('should give warning with a named export as default', async () => {
|
||||
it('should give warning with a named export as default', () => {
|
||||
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
|
||||
|
||||
const { modifiedConfigContent, success } = await parseAndModifyConfigContent(
|
||||
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
@@ -177,39 +109,39 @@ describe('parseAndInsertWithPayload', () => {
|
||||
describe('cjs', () => {
|
||||
const configType = 'cjs'
|
||||
const requireStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
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', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
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', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
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 multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
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 {2}nextConfig\n\)\)/)
|
||||
})
|
||||
it('should parse the config with a named export as default', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
it('should parse the config with a named export as default', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
@@ -217,8 +149,8 @@ describe('parseAndInsertWithPayload', () => {
|
||||
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import type { ExportDefaultExpression, ModuleItem } from '@swc/core'
|
||||
import type { Program } from 'esprima-next'
|
||||
|
||||
import swc from '@swc/core'
|
||||
import chalk from 'chalk'
|
||||
import { Syntax, parseModule } from 'esprima-next'
|
||||
import fs from 'fs'
|
||||
|
||||
import type { NextConfigType } from '../types.js'
|
||||
|
||||
import { log, warning } from '../utils/log.js'
|
||||
|
||||
export const withPayloadStatement = {
|
||||
cjs: `const { withPayload } = require("@payloadcms/next/withPayload");`,
|
||||
esm: `import { withPayload } from "@payloadcms/next/withPayload";`,
|
||||
ts: `import { withPayload } from "@payloadcms/next/withPayload";`,
|
||||
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
|
||||
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n`,
|
||||
}
|
||||
|
||||
export const wrapNextConfig = async (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 } = await parseAndModifyConfigContent(
|
||||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(
|
||||
configContent,
|
||||
configType,
|
||||
)
|
||||
@@ -36,142 +34,113 @@ export const wrapNextConfig = async (args: {
|
||||
/**
|
||||
* Parses config content with AST and wraps it with withPayload function
|
||||
*/
|
||||
export async function parseAndModifyConfigContent(
|
||||
export function parseAndModifyConfigContent(
|
||||
content: string,
|
||||
configType: NextConfigType,
|
||||
): Promise<{ modifiedConfigContent: string; success: boolean }> {
|
||||
content = withPayloadStatement[configType] + '\n' + content
|
||||
): { modifiedConfigContent: string; success: boolean } {
|
||||
content = withPayloadStatement[configType] + content
|
||||
|
||||
console.log({ configType, content })
|
||||
|
||||
if (configType === 'cjs' || configType === 'esm') {
|
||||
try {
|
||||
const ast = parseModule(content, { loc: true })
|
||||
|
||||
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 Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
} else 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 Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
warning(`Unable to parse Next config. Error: ${error.message} `)
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
}
|
||||
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)
|
||||
}
|
||||
} else if (configType === 'ts') {
|
||||
const { moduleItems, parseOffset } = await compileTypeScriptFileToAST(content)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const exportDefaultDeclaration = moduleItems.find(
|
||||
(m) =>
|
||||
m.type === 'ExportDefaultExpression' &&
|
||||
(m.expression.type === 'Identifier' || m.expression.type === 'CallExpression'),
|
||||
) as ExportDefaultExpression | undefined
|
||||
if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
|
||||
if (exportDefaultDeclaration) {
|
||||
if (!('span' in exportDefaultDeclaration.expression)) {
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
|
||||
const modifiedConfigContent = insertBeforeAndAfterSWC(
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.expression.span,
|
||||
parseOffset,
|
||||
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 Promise.resolve({
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
// Output directions for user to update next.config.js
|
||||
const withPayloadMessage = `
|
||||
|
||||
${chalk.bold(`Please manually wrap your existing Next config with the withPayload function. Here is an example:`)}
|
||||
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)}
|
||||
|
||||
${withPayloadStatement[configType]}
|
||||
|
||||
@@ -179,7 +148,7 @@ function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
${configType === 'cjs' ? 'module.exports = withPayload(nextConfig)' : 'export default withPayload(nextConfig)'}
|
||||
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'}
|
||||
|
||||
`
|
||||
|
||||
@@ -217,7 +186,7 @@ type Loc = {
|
||||
start: { column: number; line: number }
|
||||
}
|
||||
|
||||
function insertBeforeAndAfter(content: string, loc: Loc): string {
|
||||
function insertBeforeAndAfter(content: string, loc: Loc) {
|
||||
const { end, start } = loc
|
||||
const lines = content.split('\n')
|
||||
|
||||
@@ -236,57 +205,3 @@ function insertBeforeAndAfter(content: string, loc: Loc): string {
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function insertBeforeAndAfterSWC(
|
||||
content: string,
|
||||
span: ModuleItem['span'],
|
||||
/**
|
||||
* WARNING: This is ONLY for unit tests. Defaults to 0 otherwise.
|
||||
*
|
||||
* @see compileTypeScriptFileToAST
|
||||
*/
|
||||
parseOffset: number,
|
||||
): string {
|
||||
const { end: preOffsetEnd, start: preOffsetStart } = span
|
||||
|
||||
const start = preOffsetStart - parseOffset
|
||||
const end = preOffsetEnd - parseOffset
|
||||
|
||||
const insert = (pos: number, text: string): string => {
|
||||
return content.slice(0, pos) + text + content.slice(pos)
|
||||
}
|
||||
|
||||
// insert ) after end
|
||||
content = insert(end - 1, ')')
|
||||
// insert withPayload before start
|
||||
content = insert(start - 1, 'withPayload(')
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile typescript to AST using the swc compiler
|
||||
*/
|
||||
async function compileTypeScriptFileToAST(
|
||||
fileContent: string,
|
||||
): Promise<{ moduleItems: ModuleItem[]; parseOffset: number }> {
|
||||
let parseOffset = 0
|
||||
|
||||
/**
|
||||
* WARNING: This is ONLY for unit tests.
|
||||
*
|
||||
* Multiple instances of swc DO NOT reset the .span.end value.
|
||||
* During unit tests, the .spawn.end value is read and accounted for.
|
||||
*
|
||||
* https://github.com/swc-project/swc/issues/1366
|
||||
*/
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
parseOffset = (await swc.parse('')).span.end
|
||||
}
|
||||
|
||||
const module = await swc.parse(fileContent, {
|
||||
syntax: 'typescript',
|
||||
})
|
||||
|
||||
return { moduleItems: module.body, parseOffset }
|
||||
}
|
||||
|
||||
@@ -85,22 +85,7 @@ export class Main {
|
||||
|
||||
// Detect if inside Next.js project
|
||||
const nextAppDetails = await getNextAppDetails(process.cwd())
|
||||
const {
|
||||
hasTopLevelLayout,
|
||||
isPayloadInstalled,
|
||||
isSupportedNextVersion,
|
||||
nextAppDir,
|
||||
nextConfigPath,
|
||||
nextVersion,
|
||||
} = nextAppDetails
|
||||
|
||||
if (nextConfigPath && !isSupportedNextVersion) {
|
||||
p.log.warn(
|
||||
`Next.js v${nextVersion} is unsupported. Next.js >= 15 is required to use Payload.`,
|
||||
)
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(0)
|
||||
}
|
||||
const { hasTopLevelLayout, isPayloadInstalled, nextAppDir, nextConfigPath } = nextAppDetails
|
||||
|
||||
// Upgrade Payload in existing project
|
||||
if (isPayloadInstalled && nextConfigPath) {
|
||||
|
||||
@@ -57,7 +57,7 @@ interface Template {
|
||||
|
||||
export type PackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'
|
||||
|
||||
export type DbType = 'mongodb' | 'postgres' | 'sqlite'
|
||||
export type DbType = 'mongodb' | 'postgres'
|
||||
|
||||
export type DbDetails = {
|
||||
dbUri: string
|
||||
@@ -70,13 +70,11 @@ export type NextAppDetails = {
|
||||
hasTopLevelLayout: boolean
|
||||
isPayloadInstalled?: boolean
|
||||
isSrcDir: boolean
|
||||
isSupportedNextVersion: boolean
|
||||
nextAppDir?: string
|
||||
nextConfigPath?: string
|
||||
nextConfigType?: NextConfigType
|
||||
nextVersion: null | string
|
||||
}
|
||||
|
||||
export type NextConfigType = 'cjs' | 'esm' | 'ts'
|
||||
export type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
export type StorageAdapterType = 'localDisk' | 'payloadCloud' | 'vercelBlobStorage'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -64,7 +64,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
forceCountFn: hasNearConstraint,
|
||||
lean: true,
|
||||
leanWithId: true,
|
||||
limit,
|
||||
offset: skip,
|
||||
options,
|
||||
page,
|
||||
pagination,
|
||||
|
||||
@@ -52,19 +52,9 @@ type FieldSchemaGenerator = (
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
) => void
|
||||
|
||||
/**
|
||||
* get a field's defaultValue only if defined and not dynamic so that it can be set on the field schema
|
||||
* @param field
|
||||
*/
|
||||
const formatDefaultValue = (field: FieldAffectingData) =>
|
||||
typeof field.defaultValue !== 'undefined' && typeof field.defaultValue !== 'function'
|
||||
? field.defaultValue
|
||||
: undefined
|
||||
|
||||
const formatBaseSchema = (field: FieldAffectingData, buildSchemaOptions: BuildSchemaOptions) => {
|
||||
const { disableUnique, draftsEnabled, indexSortableFields } = buildSchemaOptions
|
||||
const schema: SchemaTypeOptions<unknown> = {
|
||||
default: formatDefaultValue(field),
|
||||
index: field.index || (!disableUnique && field.unique) || indexSortableFields || false,
|
||||
required: false,
|
||||
unique: (!disableUnique && field.unique) || false,
|
||||
@@ -169,6 +159,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
},
|
||||
}),
|
||||
],
|
||||
default: undefined,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
@@ -183,6 +174,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
): void => {
|
||||
const fieldSchema = {
|
||||
type: [new mongoose.Schema({}, { _id: false, discriminatorKey: 'blockType' })],
|
||||
default: undefined,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
@@ -347,7 +339,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
},
|
||||
coordinates: {
|
||||
type: [Number],
|
||||
default: formatDefaultValue(field),
|
||||
default: field.defaultValue || undefined,
|
||||
required: false,
|
||||
},
|
||||
}
|
||||
@@ -428,9 +420,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
|
||||
return {
|
||||
...locales,
|
||||
[locale]: field.hasMany
|
||||
? { type: [localeSchema], default: formatDefaultValue(field) }
|
||||
: localeSchema,
|
||||
[locale]: field.hasMany ? { type: [localeSchema], default: undefined } : localeSchema,
|
||||
}
|
||||
}, {}),
|
||||
localized: true,
|
||||
@@ -450,7 +440,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
if (field.hasMany) {
|
||||
schemaToReturn = {
|
||||
type: [schemaToReturn],
|
||||
default: formatDefaultValue(field),
|
||||
default: undefined,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -463,7 +453,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
if (field.hasMany) {
|
||||
schemaToReturn = {
|
||||
type: [schemaToReturn],
|
||||
default: formatDefaultValue(field),
|
||||
default: undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -47,8 +47,8 @@
|
||||
"dependencies": {
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.23.1-7816536",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"drizzle-kit": "0.20.14-1f2c838",
|
||||
"drizzle-orm": "0.29.4",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
|
||||
@@ -53,7 +53,6 @@ export const connect: Connect = async function connect(
|
||||
const { hotReload } = options
|
||||
|
||||
this.schema = {
|
||||
pgSchema: this.pgSchema,
|
||||
...this.tables,
|
||||
...this.relations,
|
||||
...this.enums,
|
||||
|
||||
@@ -49,12 +49,6 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
|
||||
let drizzleJsonBefore = defaultDrizzleSnapshot
|
||||
|
||||
if (this.schemaName) {
|
||||
drizzleJsonBefore.schemas = {
|
||||
[this.schemaName]: this.schemaName,
|
||||
}
|
||||
}
|
||||
|
||||
if (!upSQL) {
|
||||
// Get latest migration snapshot
|
||||
const latestSnapshot = fs
|
||||
@@ -71,7 +65,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
|
||||
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
|
||||
const sqlStatementsDown = await generateMigration(drizzleJsonAfter, drizzleJsonBefore)
|
||||
const sqlExecute = 'await payload.db.drizzle.execute(sql`'
|
||||
const sqlExecute = 'await db.execute(sql`'
|
||||
|
||||
if (sqlStatementsUp?.length) {
|
||||
upSQL = `${sqlExecute}\n ${sqlStatementsUp?.join('\n')}\`)`
|
||||
|
||||
@@ -7,11 +7,10 @@ export const defaultDrizzleSnapshot: DrizzleSnapshotJSON = {
|
||||
schemas: {},
|
||||
tables: {},
|
||||
},
|
||||
dialect: 'postgresql',
|
||||
dialect: 'pg',
|
||||
enums: {},
|
||||
prevId: '00000000-0000-0000-0000-00000000000',
|
||||
schemas: {},
|
||||
sequences: {},
|
||||
tables: {},
|
||||
version: '7',
|
||||
version: '5',
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ export const getMigrationTemplate = ({
|
||||
upSQL,
|
||||
}: MigrationTemplateArgs): string => `import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
${imports ? `${imports}\n` : ''}
|
||||
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
${upSQL}
|
||||
}
|
||||
|
||||
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
${downSQL}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
import { type PgSchema, pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { createDatabaseAdapter } from 'payload'
|
||||
|
||||
import type { Args, PostgresAdapter } from './types.js'
|
||||
@@ -63,19 +62,12 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
const migrationDir = findMigrationDir(args.migrationDir)
|
||||
let resolveInitializing
|
||||
let rejectInitializing
|
||||
let adapterSchema: PostgresAdapter['pgSchema']
|
||||
|
||||
const initializing = new Promise<void>((res, rej) => {
|
||||
resolveInitializing = res
|
||||
rejectInitializing = rej
|
||||
})
|
||||
|
||||
if (args.schemaName) {
|
||||
adapterSchema = pgSchema(args.schemaName)
|
||||
} else {
|
||||
adapterSchema = { enum: pgEnum, table: pgTable }
|
||||
}
|
||||
|
||||
return createDatabaseAdapter<PostgresAdapter>({
|
||||
name: 'postgres',
|
||||
defaultDrizzleSnapshot,
|
||||
@@ -91,7 +83,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
operators: operatorMap,
|
||||
pgSchema: adapterSchema,
|
||||
pgSchema: undefined,
|
||||
pool: undefined,
|
||||
poolOptions: args.pool,
|
||||
push: args.push,
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import type { Init, SanitizedCollectionConfig } from 'payload'
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { SanitizedCollectionConfig } from 'payload'
|
||||
import type { Init } from 'payload'
|
||||
|
||||
import { createTableName } from '@payloadcms/drizzle'
|
||||
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { createTableName } from '../../drizzle/src/createTableName.js'
|
||||
import { buildTable } from './schema/build.js'
|
||||
|
||||
export const init: Init = function init(this: PostgresAdapter) {
|
||||
if (this.schemaName) {
|
||||
this.pgSchema = pgSchema(this.schemaName)
|
||||
} else {
|
||||
this.pgSchema = { table: pgTable }
|
||||
}
|
||||
if (this.payload.config.localization) {
|
||||
this.enums.enum__locales = this.pgSchema.enum(
|
||||
this.enums.enum__locales = pgEnum(
|
||||
'_locales',
|
||||
this.payload.config.localization.locales.map(({ code }) => code) as [string, ...string[]],
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import type {
|
||||
ForeignKeyBuilder,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { index, uniqueIndex } from 'drizzle-orm/pg-core'
|
||||
|
||||
import type { GenericColumn } from '../types.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
|
||||
import type { Field, TabAsField } from 'payload'
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
pgEnum,
|
||||
text,
|
||||
timestamp,
|
||||
varchar,
|
||||
@@ -33,7 +35,6 @@ import { buildTable } from './build.js'
|
||||
import { createIndex } from './createIndex.js'
|
||||
import { idToUUID } from './idToUUID.js'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap.js'
|
||||
import { withDefault } from './withDefault.js'
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
@@ -169,14 +170,14 @@ export const traverseFields = ({
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(varchar(columnName), field)
|
||||
targetTable[fieldName] = varchar(columnName)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'email':
|
||||
case 'code':
|
||||
case 'textarea': {
|
||||
targetTable[fieldName] = withDefault(varchar(columnName), field)
|
||||
targetTable[fieldName] = varchar(columnName)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -198,26 +199,23 @@ export const traverseFields = ({
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(numeric(columnName), field)
|
||||
targetTable[fieldName] = numeric(columnName)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText':
|
||||
case 'json': {
|
||||
targetTable[fieldName] = withDefault(jsonb(columnName), field)
|
||||
targetTable[fieldName] = jsonb(columnName)
|
||||
break
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
targetTable[fieldName] = withDefault(
|
||||
timestamp(columnName, {
|
||||
mode: 'string',
|
||||
precision: 3,
|
||||
withTimezone: true,
|
||||
}),
|
||||
field,
|
||||
)
|
||||
targetTable[fieldName] = timestamp(columnName, {
|
||||
mode: 'string',
|
||||
precision: 3,
|
||||
withTimezone: true,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
@@ -236,7 +234,7 @@ export const traverseFields = ({
|
||||
throwValidationError,
|
||||
})
|
||||
|
||||
adapter.enums[enumName] = adapter.pgSchema.enum(
|
||||
adapter.enums[enumName] = pgEnum(
|
||||
enumName,
|
||||
field.options.map((option) => {
|
||||
if (optionIsObject(option)) {
|
||||
@@ -313,13 +311,13 @@ export const traverseFields = ({
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(adapter.enums[enumName](fieldName), field)
|
||||
targetTable[fieldName] = adapter.enums[enumName](fieldName)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
targetTable[fieldName] = withDefault(boolean(columnName), field)
|
||||
targetTable[fieldName] = boolean(columnName)
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { PgColumnBuilder } from 'drizzle-orm/pg-core'
|
||||
import type { FieldAffectingData } from 'payload'
|
||||
|
||||
export const withDefault = (
|
||||
column: PgColumnBuilder,
|
||||
field: FieldAffectingData,
|
||||
): PgColumnBuilder => {
|
||||
if (typeof field.defaultValue === 'undefined' || typeof field.defaultValue === 'function')
|
||||
return column
|
||||
|
||||
if (typeof field.defaultValue === 'string' && field.defaultValue.includes("'")) {
|
||||
const escapedString = field.defaultValue.replaceAll("'", "''")
|
||||
return column.default(escapedString)
|
||||
}
|
||||
|
||||
return column.default(field.defaultValue)
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import type {
|
||||
PgSchema,
|
||||
PgTableWithColumns,
|
||||
PgTransactionConfig,
|
||||
pgEnum,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
@@ -107,13 +106,6 @@ type PostgresDrizzleAdapter = Omit<
|
||||
| 'relations'
|
||||
>
|
||||
|
||||
type Schema =
|
||||
| {
|
||||
enum: typeof pgEnum
|
||||
table: PgTableFn
|
||||
}
|
||||
| PgSchema
|
||||
|
||||
export type PostgresAdapter = {
|
||||
countDistinct: CountDistinct
|
||||
defaultDrizzleSnapshot: DrizzleSnapshotJSON
|
||||
@@ -133,7 +125,7 @@ export type PostgresAdapter = {
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
operators: Operators
|
||||
pgSchema?: Schema
|
||||
pgSchema?: { table: PgTableFn } | PgSchema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
push: boolean
|
||||
@@ -141,6 +133,7 @@ export type PostgresAdapter = {
|
||||
relations: Record<string, GenericRelation>
|
||||
relationshipsSuffix?: string
|
||||
resolveInitializing: () => void
|
||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||
schemaName?: Args['schemaName']
|
||||
sessions: {
|
||||
[id: string]: {
|
||||
@@ -163,8 +156,6 @@ declare module 'payload' {
|
||||
export interface DatabaseAdapter
|
||||
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
|
||||
DrizzleAdapter {
|
||||
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
|
||||
drizzle: PostgresDB
|
||||
enums: Record<string, GenericEnum>
|
||||
/**
|
||||
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
||||
@@ -182,7 +173,7 @@ declare module 'payload' {
|
||||
rejectInitializing: () => void
|
||||
relationshipsSuffix?: string
|
||||
resolveInitializing: () => void
|
||||
schema: Record<string, unknown>
|
||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||
schemaName?: Args['schemaName']
|
||||
tableNameMap: Map<string, string>
|
||||
versionsSuffix?: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.36",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -46,8 +46,8 @@
|
||||
"@libsql/client": "^0.6.2",
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.23.1-7816536",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"drizzle-kit": "0.20.14-1f2c838",
|
||||
"drizzle-orm": "0.29.4",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
"uuid": "9.0.0"
|
||||
|
||||
@@ -66,7 +66,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
const sqlStatementsUp = await generateSQLiteMigration(drizzleJsonBefore, drizzleJsonAfter)
|
||||
const sqlStatementsDown = await generateSQLiteMigration(drizzleJsonAfter, drizzleJsonBefore)
|
||||
// need to create tables as separate statements
|
||||
const sqlExecute = 'await payload.db.drizzle.run(sql`'
|
||||
const sqlExecute = 'await db.run(sql`'
|
||||
|
||||
if (sqlStatementsUp?.length) {
|
||||
upSQL = sqlStatementsUp
|
||||
|
||||
@@ -10,5 +10,5 @@ export const defaultDrizzleSnapshot: DrizzleSQLiteSnapshotJSON = {
|
||||
enums: {},
|
||||
prevId: '00000000-0000-0000-0000-00000000000',
|
||||
tables: {},
|
||||
version: '6',
|
||||
version: '3',
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ export const getMigrationTemplate = ({
|
||||
upSQL,
|
||||
}: MigrationTemplateArgs): string => `import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-sqlite'
|
||||
${imports ? `${imports}\n` : ''}
|
||||
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
${upSQL}
|
||||
}
|
||||
|
||||
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
${downSQL}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -105,7 +105,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
versionsSuffix: args.versionsSuffix || '_v',
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction: args.transactionOptions ? beginTransaction : undefined,
|
||||
beginTransaction: args.transactionOptions === false ? undefined : beginTransaction,
|
||||
commitTransaction,
|
||||
connect,
|
||||
convertPathToJSONTraversal,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { ColumnDataType, Relation } from 'drizzle-orm'
|
||||
import type {
|
||||
AnySQLiteColumn,
|
||||
ForeignKeyBuilder,
|
||||
IndexBuilder,
|
||||
SQLiteColumn,
|
||||
SQLiteColumnBuilder,
|
||||
SQLiteTableWithColumns,
|
||||
UniqueConstraintBuilder,
|
||||
@@ -31,7 +32,18 @@ import { traverseFields } from './traverseFields.js'
|
||||
export type BaseExtraConfig = Record<
|
||||
string,
|
||||
(cols: {
|
||||
[x: string]: AnySQLiteColumn
|
||||
[x: string]: SQLiteColumn<{
|
||||
baseColumn: never
|
||||
columnType: string
|
||||
data: unknown
|
||||
dataType: ColumnDataType
|
||||
driverParam: unknown
|
||||
enumValues: string[]
|
||||
hasDefault: false
|
||||
name: string
|
||||
notNull: false
|
||||
tableName: string
|
||||
}>
|
||||
}) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
|
||||
>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { AnySQLiteColumn} from 'drizzle-orm/sqlite-core';
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
import type { GenericColumn } from '../types.js'
|
||||
|
||||
type CreateIndexArgs = {
|
||||
columnName: string
|
||||
name: string | string[]
|
||||
@@ -10,7 +11,7 @@ type CreateIndexArgs = {
|
||||
}
|
||||
|
||||
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
|
||||
return (table: { [x: string]: AnySQLiteColumn }) => {
|
||||
return (table: { [x: string]: GenericColumn }) => {
|
||||
let columns
|
||||
if (Array.isArray(name)) {
|
||||
columns = name
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import type { IndexBuilder, SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
|
||||
import type { Field, TabAsField } from 'payload'
|
||||
@@ -29,7 +30,6 @@ import { buildTable } from './build.js'
|
||||
import { createIndex } from './createIndex.js'
|
||||
import { getIDColumn } from './getIDColumn.js'
|
||||
import { idToUUID } from './idToUUID.js'
|
||||
import { withDefault } from './withDefault.js'
|
||||
|
||||
type Args = {
|
||||
adapter: SQLiteAdapter
|
||||
@@ -166,14 +166,14 @@ export const traverseFields = ({
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
targetTable[fieldName] = text(columnName)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'email':
|
||||
case 'code':
|
||||
case 'textarea': {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
targetTable[fieldName] = text(columnName)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -195,19 +195,19 @@ export const traverseFields = ({
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(numeric(columnName), field)
|
||||
targetTable[fieldName] = numeric(columnName)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText':
|
||||
case 'json': {
|
||||
targetTable[fieldName] = withDefault(text(columnName, { mode: 'json' }), field)
|
||||
targetTable[fieldName] = text(columnName, { mode: 'json' })
|
||||
break
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
targetTable[fieldName] = text(columnName)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -295,13 +295,13 @@ export const traverseFields = ({
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(text(fieldName, { enum: options }), field)
|
||||
targetTable[fieldName] = text(fieldName, { enum: options })
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
targetTable[fieldName] = withDefault(integer(columnName, { mode: 'boolean' }), field)
|
||||
targetTable[fieldName] = integer(columnName, { mode: 'boolean' })
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
|
||||
import type { FieldAffectingData } from 'payload'
|
||||
|
||||
export const withDefault = (
|
||||
column: SQLiteColumnBuilder,
|
||||
field: FieldAffectingData,
|
||||
): SQLiteColumnBuilder => {
|
||||
if (typeof field.defaultValue === 'undefined' || typeof field.defaultValue === 'function')
|
||||
return column
|
||||
|
||||
if (typeof field.defaultValue === 'string' && field.defaultValue.includes("'")) {
|
||||
const escapedString = field.defaultValue.replaceAll("'", "''")
|
||||
return column.default(escapedString)
|
||||
}
|
||||
|
||||
return column.default(field.defaultValue)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Client, Config, ResultSet } from '@libsql/client'
|
||||
import type { Operators } from '@payloadcms/drizzle'
|
||||
import type { BuildQueryJoinAliases, DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
|
||||
import type { ColumnDataType, DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type {
|
||||
AnySQLiteColumn,
|
||||
SQLiteColumn,
|
||||
SQLiteInsertOnConflictDoUpdateConfig,
|
||||
SQLiteTableWithColumns,
|
||||
SQLiteTransactionConfig,
|
||||
@@ -25,8 +25,24 @@ export type Args = {
|
||||
versionsSuffix?: string
|
||||
}
|
||||
|
||||
export type GenericColumn = SQLiteColumn<
|
||||
{
|
||||
baseColumn: never
|
||||
columnType: string
|
||||
data: unknown
|
||||
dataType: ColumnDataType
|
||||
driverParam: unknown
|
||||
enumValues: string[]
|
||||
hasDefault: false
|
||||
name: string
|
||||
notNull: false
|
||||
tableName: string
|
||||
},
|
||||
object
|
||||
>
|
||||
|
||||
export type GenericColumns = {
|
||||
[x: string]: AnySQLiteColumn
|
||||
[x: string]: GenericColumn
|
||||
}
|
||||
|
||||
export type GenericTable = SQLiteTableWithColumns<{
|
||||
@@ -116,10 +132,12 @@ export type SQLiteAdapter = {
|
||||
export type IDType = 'integer' | 'numeric' | 'text'
|
||||
|
||||
export type MigrateUpArgs = {
|
||||
db: LibSQLDatabase
|
||||
payload: Payload
|
||||
req?: Partial<PayloadRequest>
|
||||
}
|
||||
export type MigrateDownArgs = {
|
||||
db: LibSQLDatabase
|
||||
payload: Payload
|
||||
req?: Partial<PayloadRequest>
|
||||
}
|
||||
@@ -128,8 +146,6 @@ declare module 'payload' {
|
||||
export interface DatabaseAdapter
|
||||
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
|
||||
DrizzleAdapter {
|
||||
beginTransaction: (options?: SQLiteTransactionConfig) => Promise<null | number | string>
|
||||
drizzle: LibSQLDatabase
|
||||
/**
|
||||
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
||||
* Used for returning properly formed errors from unique fields
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.36",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"drizzle-orm": "0.29.4",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
"uuid": "9.0.0"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Count , SanitizedCollectionConfig } from 'payload'
|
||||
import type { Count } from 'payload'
|
||||
import type { SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
@@ -14,7 +15,7 @@ export const count: Count = async function count(
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
|
||||
const { joins, where } = await buildQuery({
|
||||
adapter: this,
|
||||
|
||||
@@ -10,7 +10,7 @@ export const create: Create = async function create(
|
||||
this: DrizzleAdapter,
|
||||
{ collection: collectionSlug, data, req },
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function createGlobal<T extends Record<string, unknown>>(
|
||||
this: DrizzleAdapter,
|
||||
{ slug, data, req = {} as PayloadRequest }: CreateGlobalArgs,
|
||||
): Promise<T> {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
||||
this: DrizzleAdapter,
|
||||
{ autosave, globalSlug, req = {} as PayloadRequest, versionData }: CreateGlobalVersionArgs,
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
|
||||
|
||||
const tableName = this.tableNameMap.get(`_${toSnakeCase(global.slug)}${this.versionsSuffix}`)
|
||||
|
||||
@@ -18,7 +18,7 @@ export async function createVersion<T extends TypeWithID>(
|
||||
versionData,
|
||||
}: CreateVersionArgs<T>,
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const defaultTableName = toSnakeCase(collection.slug)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const deleteMany: DeleteMany = async function deleteMany(
|
||||
this: DrizzleAdapter,
|
||||
{ collection, req = {} as PayloadRequest, where },
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||
|
||||
@@ -14,7 +14,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
||||
this: DrizzleAdapter,
|
||||
{ collection: collectionSlug, req = {} as PayloadRequest, where: whereArg },
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
|
||||
@@ -12,7 +12,7 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
|
||||
this: DrizzleAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where: where },
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = this.tableNameMap.get(
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { DrizzleAdapter, DrizzleTransaction } from '../types.js'
|
||||
|
||||
export const beginTransaction: BeginTransaction = async function beginTransaction(
|
||||
this: DrizzleAdapter,
|
||||
options: DrizzleAdapter['transactionOptions'],
|
||||
) {
|
||||
let id
|
||||
try {
|
||||
@@ -42,7 +41,7 @@ export const beginTransaction: BeginTransaction = async function beginTransactio
|
||||
}
|
||||
transactionReady()
|
||||
})
|
||||
}, options || this.transactionOptions)
|
||||
}, this.transactionOptions)
|
||||
.catch(() => {
|
||||
// swallow
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Field, SanitizedConfig, TabAsField } from 'payload'
|
||||
|
||||
import type { Field, SanitizedConfig , TabAsField } from 'payload'
|
||||
|
||||
import { fieldAffectsData } from 'payload/shared'
|
||||
|
||||
|
||||
@@ -120,7 +120,6 @@ export type RequireDrizzleKit = () => {
|
||||
pushSchema: (
|
||||
schema: Record<string, unknown>,
|
||||
drizzle: DrizzleAdapter['drizzle'],
|
||||
filterSchema?: string[],
|
||||
) => Promise<{ apply; hasDataLoss; warnings }>
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
this: DrizzleAdapter,
|
||||
{ id, collection: collectionSlug, data, draft, locale, req, where: whereArg },
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
const whereToUse = whereArg || { id: { equals: id } }
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function updateGlobal<T extends Record<string, unknown>>(
|
||||
this: DrizzleAdapter,
|
||||
{ slug, data, req = {} as PayloadRequest }: UpdateGlobalArgs,
|
||||
): Promise<T> {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
where: whereArg,
|
||||
}: UpdateGlobalVersionArgs<T>,
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
|
||||
({ slug }) => slug === global,
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function updateVersion<T extends TypeWithID>(
|
||||
where: whereArg,
|
||||
}: UpdateVersionArgs<T>,
|
||||
) {
|
||||
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
|
||||
const db = this.sessions[await req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const whereToUse = whereArg || { id: { equals: id } }
|
||||
const tableName = this.tableNameMap.get(
|
||||
|
||||
@@ -4,10 +4,8 @@ import { fieldAffectsData, fieldHasSubFields } from 'payload/shared'
|
||||
|
||||
export const hasLocalesTable = (fields: Field[]): boolean => {
|
||||
return fields.some((field) => {
|
||||
// arrays always get a separate table
|
||||
if (field.type === 'array') return false
|
||||
if (fieldAffectsData(field) && field.localized) return true
|
||||
if (fieldHasSubFields(field)) return hasLocalesTable(field.fields)
|
||||
if (fieldHasSubFields(field) && field.type !== 'array') return hasLocalesTable(field.fields)
|
||||
if (field.type === 'tabs') return field.tabs.some((tab) => hasLocalesTable(tab.fields))
|
||||
return false
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ export const migrationTableExists = async (adapter: DrizzleAdapter): Promise<boo
|
||||
|
||||
if (adapter.name === 'postgres') {
|
||||
const prependSchema = adapter.schemaName ? `"${adapter.schemaName}".` : ''
|
||||
statement = `SELECT to_regclass('${prependSchema}"payload_migrations"') AS exists;`
|
||||
statement = `SELECT to_regclass('${prependSchema}"payload_migrations"') exists;`
|
||||
}
|
||||
|
||||
if (adapter.name === 'sqlite') {
|
||||
|
||||
@@ -12,11 +12,7 @@ export const pushDevSchema = async (adapter: DrizzleAdapter) => {
|
||||
const { pushSchema } = adapter.requireDrizzleKit()
|
||||
|
||||
// This will prompt if clarifications are needed for Drizzle to push new schema
|
||||
const { apply, hasDataLoss, warnings } = await pushSchema(
|
||||
adapter.schema,
|
||||
adapter.drizzle,
|
||||
adapter.schemaName ? [adapter.schemaName] : undefined,
|
||||
)
|
||||
const { apply, hasDataLoss, warnings } = await pushSchema(adapter.schema, adapter.drizzle)
|
||||
|
||||
if (warnings.length) {
|
||||
let message = `Warnings detected during schema push: \n\n${warnings.join('\n')}\n\n`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -39,7 +39,7 @@ export const index = deepMerge(
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/rules-of-hooks': 'warn',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"eslint-plugin-react-hooks": "5.1.0-rc-85acf2d195-20240711",
|
||||
"eslint-plugin-regexp": "2.6.0",
|
||||
"globals": "15.8.0",
|
||||
"typescript": "5.5.4",
|
||||
"typescript": "5.5.3",
|
||||
"typescript-eslint": "7.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint-plugin-react-hooks": "5.1.0-rc-85acf2d195-20240711",
|
||||
"eslint-plugin-regexp": "2.6.0",
|
||||
"globals": "15.8.0",
|
||||
"typescript": "5.5.4",
|
||||
"typescript": "5.5.3",
|
||||
"typescript-eslint": "7.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { GraphQLJSON, GraphQLJSONObject } from '../packages/graphql-type-json/index.js'
|
||||
export { buildPaginatedListType } from '../schema/buildPaginatedListType.js'
|
||||
export * as GraphQL from 'graphql'
|
||||
export { default as GraphQL } from 'graphql'
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { Context } from '../types.js'
|
||||
export type Resolver = (
|
||||
_: unknown,
|
||||
args: {
|
||||
draft?: boolean
|
||||
id: number | string
|
||||
},
|
||||
context: {
|
||||
@@ -21,7 +20,6 @@ export default function restoreVersionResolver(collection: Collection): Resolver
|
||||
id: args.id,
|
||||
collection,
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
req: isolateObjectProperty(context.req, 'transactionID'),
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { Context } from '../types.js'
|
||||
type Resolver = (
|
||||
_: unknown,
|
||||
args: {
|
||||
draft?: boolean
|
||||
id: number | string
|
||||
},
|
||||
context: {
|
||||
@@ -19,7 +18,6 @@ export default function restoreVersionResolver(globalConfig: SanitizedGlobalConf
|
||||
const options = {
|
||||
id: args.id,
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
globalConfig,
|
||||
req: isolateObjectProperty(context.req, 'transactionID'),
|
||||
}
|
||||
|
||||
@@ -342,7 +342,6 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: versionIDType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: restoreVersionResolver(collection),
|
||||
}
|
||||
|
||||
@@ -133,7 +133,6 @@ function initGlobalsGraphQL({ config, graphqlResult }: InitGlobalsGraphQLArgs):
|
||||
type: graphqlResult.globals.graphQL[slug].versionType,
|
||||
args: {
|
||||
id: { type: idType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
@@ -172,7 +171,6 @@ function initGlobalsGraphQL({ config, graphqlResult }: InitGlobalsGraphQLArgs):
|
||||
type: graphqlResult.globals.graphQL[slug].type,
|
||||
args: {
|
||||
id: { type: idType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: restoreVersionResolver(global),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.68",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import { useDocumentInfo } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { baseClass } from '../../Tab/index.js'
|
||||
|
||||
@@ -12,14 +12,13 @@ export const VersionsPill: React.FC = () => {
|
||||
// documents that are version enabled _always_ have at least one version
|
||||
const hasVersions = versions?.totalDocs > 0
|
||||
|
||||
if (hasVersions)
|
||||
return (
|
||||
<span
|
||||
className={[`${baseClass}__count`, hasVersions ? `${baseClass}__count--has-count` : '']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{versions.totalDocs.toString()}
|
||||
</span>
|
||||
)
|
||||
return (
|
||||
<span
|
||||
className={[`${baseClass}__count`, hasVersions ? `${baseClass}__count--has-count` : '']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{hasVersions ? versions.totalDocs.toString() : <Fragment> </Fragment>}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,18 +85,9 @@ export const DefaultNavClient: React.FC = () => {
|
||||
|
||||
const LinkElement = Link || 'a'
|
||||
|
||||
const activeCollection = window?.location?.pathname
|
||||
?.split('/')
|
||||
.find(
|
||||
(_, index, arr) =>
|
||||
arr[index - 1] === 'collections' || arr[index - 1] === 'globals',
|
||||
)
|
||||
|
||||
return (
|
||||
<LinkElement
|
||||
className={[`${baseClass}__link`, activeCollection === entity?.slug && `active`]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
className={`${baseClass}__link`}
|
||||
href={href}
|
||||
id={id}
|
||||
key={i}
|
||||
|
||||
@@ -134,8 +134,12 @@ export const POST =
|
||||
resHeaders.append(key, headers[key])
|
||||
}
|
||||
|
||||
if (req.responseHeaders) {
|
||||
mergeHeaders(req.responseHeaders, resHeaders)
|
||||
}
|
||||
|
||||
return new Response(apiResponse.body, {
|
||||
headers: req.responseHeaders ? mergeHeaders(req.responseHeaders, resHeaders) : resHeaders,
|
||||
headers: resHeaders,
|
||||
status: apiResponse.status,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ export const restoreVersion: CollectionRouteHandlerWithID = async ({
|
||||
}) => {
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
const draft = searchParams.get('draft')
|
||||
|
||||
const id = sanitizeCollectionID({
|
||||
id: incomingID,
|
||||
@@ -26,7 +25,6 @@ export const restoreVersion: CollectionRouteHandlerWithID = async ({
|
||||
id,
|
||||
collection,
|
||||
depth: isNumber(depth) ? Number(depth) : undefined,
|
||||
draft: draft === 'true' ? true : undefined,
|
||||
req,
|
||||
})
|
||||
|
||||
|
||||
@@ -9,12 +9,10 @@ import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
export const restoreVersion: GlobalRouteHandlerWithID = async ({ id, globalConfig, req }) => {
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
const draft = searchParams.get('draft')
|
||||
|
||||
const doc = await restoreVersionOperationGlobal({
|
||||
id,
|
||||
depth: isNumber(depth) ? Number(depth) : undefined,
|
||||
draft: draft === 'true' ? true : undefined,
|
||||
globalConfig,
|
||||
req,
|
||||
})
|
||||
|
||||
@@ -167,13 +167,7 @@ const handleCustomEndpoints = async ({
|
||||
|
||||
if (res instanceof Response) {
|
||||
if (req.responseHeaders) {
|
||||
const mergedResponse = new Response(res.body, {
|
||||
headers: mergeHeaders(req.responseHeaders, res.headers),
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
})
|
||||
|
||||
return mergedResponse
|
||||
mergeHeaders(req.responseHeaders, res.headers)
|
||||
}
|
||||
|
||||
return res
|
||||
@@ -385,13 +379,7 @@ export const GET =
|
||||
|
||||
if (res instanceof Response) {
|
||||
if (req.responseHeaders) {
|
||||
const mergedResponse = new Response(res.body, {
|
||||
headers: mergeHeaders(req.responseHeaders, res.headers),
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
})
|
||||
|
||||
return mergedResponse
|
||||
mergeHeaders(req.responseHeaders, res.headers)
|
||||
}
|
||||
|
||||
return res
|
||||
@@ -567,13 +555,7 @@ export const POST =
|
||||
|
||||
if (res instanceof Response) {
|
||||
if (req.responseHeaders) {
|
||||
const mergedResponse = new Response(res.body, {
|
||||
headers: mergeHeaders(req.responseHeaders, res.headers),
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
})
|
||||
|
||||
return mergedResponse
|
||||
mergeHeaders(req.responseHeaders, res.headers)
|
||||
}
|
||||
|
||||
return res
|
||||
@@ -661,13 +643,7 @@ export const DELETE =
|
||||
|
||||
if (res instanceof Response) {
|
||||
if (req.responseHeaders) {
|
||||
const mergedResponse = new Response(res.body, {
|
||||
headers: mergeHeaders(req.responseHeaders, res.headers),
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
})
|
||||
|
||||
return mergedResponse
|
||||
mergeHeaders(req.responseHeaders, res.headers)
|
||||
}
|
||||
|
||||
return res
|
||||
@@ -756,13 +732,7 @@ export const PATCH =
|
||||
|
||||
if (res instanceof Response) {
|
||||
if (req.responseHeaders) {
|
||||
const mergedResponse = new Response(res.body, {
|
||||
headers: mergeHeaders(req.responseHeaders, res.headers),
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
})
|
||||
|
||||
return mergedResponse
|
||||
mergeHeaders(req.responseHeaders, res.headers)
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
|
||||
import { getRouteWithoutAdmin, isAdminAuthRoute, isAdminRoute } from './shared.js'
|
||||
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
|
||||
|
||||
export const handleAdminPage = ({
|
||||
adminRoute,
|
||||
@@ -20,9 +20,9 @@ export const handleAdminPage = ({
|
||||
permissions: Permissions
|
||||
route: string
|
||||
}) => {
|
||||
if (isAdminRoute({ adminRoute, config, route })) {
|
||||
const routeWithoutAdmin = getRouteWithoutAdmin({ adminRoute, route })
|
||||
const routeSegments = routeWithoutAdmin.split('/').filter(Boolean)
|
||||
if (isAdminRoute(route, adminRoute)) {
|
||||
const baseAdminRoute = adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
|
||||
const routeSegments = baseAdminRoute.split('/').filter(Boolean)
|
||||
const [entityType, entitySlug, createOrID] = routeSegments
|
||||
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
|
||||
const globalSlug = entityType === 'globals' ? entitySlug : undefined
|
||||
@@ -47,7 +47,7 @@ export const handleAdminPage = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (!permissions.canAccessAdmin && !isAdminAuthRoute({ adminRoute, config, route })) {
|
||||
if (!permissions.canAccessAdmin && !isAdminAuthRoute(config, route, adminRoute)) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export const handleAuthRedirect = ({
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
|
||||
if (!isAdminAuthRoute({ adminRoute, config, route })) {
|
||||
if (!isAdminAuthRoute(config, route, adminRoute)) {
|
||||
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
|
||||
|
||||
const redirectRoute = encodeURIComponent(
|
||||
@@ -36,7 +36,7 @@ export const handleAuthRedirect = ({
|
||||
const customLoginRoute =
|
||||
typeof redirectUnauthenticatedUser === 'string' ? redirectUnauthenticatedUser : undefined
|
||||
|
||||
const loginRoute = isAdminRoute({ adminRoute, config, route })
|
||||
const loginRoute = isAdminRoute(route, adminRoute)
|
||||
? adminLoginRoute
|
||||
: customLoginRoute || loginRouteFromConfig
|
||||
|
||||
|
||||
@@ -11,42 +11,16 @@ const authRouteKeys: (keyof SanitizedConfig['admin']['routes'])[] = [
|
||||
'reset',
|
||||
]
|
||||
|
||||
export const isAdminRoute = ({
|
||||
adminRoute,
|
||||
config,
|
||||
route,
|
||||
}: {
|
||||
adminRoute: string
|
||||
config: SanitizedConfig
|
||||
route: string
|
||||
}): boolean => {
|
||||
return route.startsWith(adminRoute) && !isAdminAuthRoute({ adminRoute, config, route })
|
||||
export const isAdminRoute = (route: string, adminRoute: string) => {
|
||||
return route.startsWith(adminRoute)
|
||||
}
|
||||
|
||||
export const isAdminAuthRoute = ({
|
||||
adminRoute,
|
||||
config,
|
||||
route,
|
||||
}: {
|
||||
adminRoute: string
|
||||
config: SanitizedConfig
|
||||
route: string
|
||||
}): boolean => {
|
||||
export const isAdminAuthRoute = (config: SanitizedConfig, route: string, adminRoute: string) => {
|
||||
const authRoutes = config.admin?.routes
|
||||
? Object.entries(config.admin.routes)
|
||||
.filter(([key]) => authRouteKeys.includes(key as keyof SanitizedConfig['admin']['routes']))
|
||||
.map(([_, value]) => value)
|
||||
: []
|
||||
|
||||
return authRoutes.some((r) => getRouteWithoutAdmin({ adminRoute, route }).startsWith(r))
|
||||
}
|
||||
|
||||
export const getRouteWithoutAdmin = ({
|
||||
adminRoute,
|
||||
route,
|
||||
}: {
|
||||
adminRoute: string
|
||||
route: string
|
||||
}): string => {
|
||||
return adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
|
||||
return authRoutes.some((r) => route.replace(adminRoute, '').startsWith(r))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
export const mergeHeaders = (sourceHeaders: Headers, destinationHeaders: Headers): Headers => {
|
||||
// Create a new Headers object
|
||||
const combinedHeaders = new Headers(destinationHeaders)
|
||||
const headersToJoin = ['set-cookie', 'warning', 'www-authenticate', 'proxy-authenticate', 'vary']
|
||||
|
||||
// Append sourceHeaders to combinedHeaders
|
||||
sourceHeaders.forEach((value, key) => {
|
||||
combinedHeaders.append(key, value)
|
||||
export function mergeHeaders(sourceHeaders: Headers, destinationHeaders: Headers): void {
|
||||
// Create a map to store combined headers
|
||||
const combinedHeaders = new Headers()
|
||||
|
||||
// Add existing destination headers to the combined map
|
||||
destinationHeaders.forEach((value, key) => {
|
||||
combinedHeaders.set(key, value)
|
||||
})
|
||||
|
||||
return combinedHeaders
|
||||
// Add source headers to the combined map, joining specific headers
|
||||
sourceHeaders.forEach((value, key) => {
|
||||
const lowerKey = key.toLowerCase()
|
||||
if (headersToJoin.includes(lowerKey)) {
|
||||
if (combinedHeaders.has(key)) {
|
||||
combinedHeaders.set(key, `${combinedHeaders.get(key)}, ${value}`)
|
||||
} else {
|
||||
combinedHeaders.set(key, value)
|
||||
}
|
||||
} else {
|
||||
combinedHeaders.set(key, value)
|
||||
}
|
||||
})
|
||||
|
||||
// Clear the destination headers and set the combined headers
|
||||
destinationHeaders.forEach((_, key) => {
|
||||
destinationHeaders.delete(key)
|
||||
})
|
||||
combinedHeaders.forEach((value, key) => {
|
||||
destinationHeaders.append(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,17 +35,18 @@ export const CreateFirstUserClient: React.FC<{
|
||||
const fieldMap = getFieldMap({ collectionSlug: userSlug })
|
||||
|
||||
const onChange: FormProps['onChange'][0] = React.useCallback(
|
||||
async ({ formState: prevFormState }) =>
|
||||
getFormState({
|
||||
async ({ formState: prevFormState }) => {
|
||||
return getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
collectionSlug: userSlug,
|
||||
formState: prevFormState,
|
||||
operation: 'create',
|
||||
schemaPath: `_${userSlug}.auth`,
|
||||
schemaPath: userSlug,
|
||||
},
|
||||
serverURL,
|
||||
}),
|
||||
})
|
||||
},
|
||||
[apiRoute, userSlug, serverURL],
|
||||
)
|
||||
|
||||
@@ -63,15 +64,14 @@ export const CreateFirstUserClient: React.FC<{
|
||||
<LoginField required={requireEmail} type="email" />
|
||||
)}
|
||||
<PasswordField
|
||||
autoComplete="off"
|
||||
label={t('authentication:newPassword')}
|
||||
name="password"
|
||||
path="password"
|
||||
required
|
||||
/>
|
||||
<ConfirmPasswordField />
|
||||
<RenderFields
|
||||
fieldMap={fieldMap}
|
||||
forceRender
|
||||
operation="create"
|
||||
path=""
|
||||
readOnly={false}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { AdminViewProps } from 'payload'
|
||||
import type { AdminViewProps, Field } from 'payload'
|
||||
|
||||
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
|
||||
import React from 'react'
|
||||
|
||||
import type { LoginFieldProps } from '../Login/LoginField/index.js'
|
||||
|
||||
import { getDocumentData } from '../Document/getDocumentData.js'
|
||||
import { CreateFirstUserClient } from './index.client.js'
|
||||
import './index.scss'
|
||||
|
||||
@@ -12,7 +12,6 @@ export { generateCreateFirstUserMetadata } from './meta.js'
|
||||
|
||||
export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageResult }) => {
|
||||
const {
|
||||
locale,
|
||||
req,
|
||||
req: {
|
||||
payload: {
|
||||
@@ -27,6 +26,7 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
||||
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug)
|
||||
const { auth: authOptions } = collectionConfig
|
||||
const loginWithUsername = authOptions.loginWithUsername
|
||||
const loginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
|
||||
const emailRequired = loginWithUsername && loginWithUsername.requireEmail
|
||||
|
||||
let loginType: LoginFieldProps['type'] = loginWithUsername ? 'username' : 'email'
|
||||
@@ -34,11 +34,42 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
||||
loginType = 'emailOrUsername'
|
||||
}
|
||||
|
||||
const { formState } = await getDocumentData({
|
||||
collectionConfig,
|
||||
locale,
|
||||
const emailField = {
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: req.t('general:emailAddress'),
|
||||
required: emailRequired ? true : false,
|
||||
}
|
||||
|
||||
const usernameField = {
|
||||
name: 'username',
|
||||
type: 'text',
|
||||
label: req.t('authentication:username'),
|
||||
required: true,
|
||||
}
|
||||
|
||||
const fields = [
|
||||
...(loginWithUsername ? [usernameField] : []),
|
||||
...(emailRequired || loginWithEmail ? [emailField] : []),
|
||||
{
|
||||
name: 'password',
|
||||
type: 'text',
|
||||
label: req.t('general:password'),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'confirm-password',
|
||||
type: 'text',
|
||||
label: req.t('authentication:confirmPassword'),
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
const formState = await buildStateFromSchema({
|
||||
fieldSchema: fields as Field[],
|
||||
operation: 'create',
|
||||
preferences: { fields: {} },
|
||||
req,
|
||||
schemaPath: `_${collectionConfig.slug}.auth`,
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,11 +15,8 @@ export const getDocumentData = async (args: {
|
||||
id?: number | string
|
||||
locale: Locale
|
||||
req: PayloadRequest
|
||||
schemaPath?: string
|
||||
}): Promise<Data> => {
|
||||
const { id, collectionConfig, globalConfig, locale, req, schemaPath: schemaPathFromProps } = args
|
||||
|
||||
const schemaPath = schemaPathFromProps || collectionConfig?.slug || globalConfig?.slug
|
||||
const { id, collectionConfig, globalConfig, locale, req } = args
|
||||
|
||||
try {
|
||||
const formState = await buildFormState({
|
||||
@@ -31,7 +28,7 @@ export const getDocumentData = async (args: {
|
||||
globalSlug: globalConfig?.slug,
|
||||
locale: locale?.code,
|
||||
operation: (collectionConfig && id) || globalConfig ? 'update' : 'create',
|
||||
schemaPath,
|
||||
schemaPath: collectionConfig?.slug || globalConfig?.slug,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -78,7 +78,7 @@ export const APIKey: React.FC<{ enabled: boolean; readOnly?: boolean }> = ({
|
||||
if (!apiKeyValue && enabled) {
|
||||
setValue(initialAPIKey)
|
||||
}
|
||||
if (!enabled && apiKeyValue) {
|
||||
if (!enabled) {
|
||||
setValue(null)
|
||||
}
|
||||
}, [apiKeyValue, enabled, setValue, initialAPIKey])
|
||||
@@ -100,7 +100,6 @@ export const APIKey: React.FC<{ enabled: boolean; readOnly?: boolean }> = ({
|
||||
<div className={[fieldBaseClass, 'api-key', 'read-only'].filter(Boolean).join(' ')}>
|
||||
<FieldLabel CustomLabel={APIKeyLabel} htmlFor={path} />
|
||||
<input
|
||||
aria-label="API Key"
|
||||
className={highlightedField ? 'highlight' : undefined}
|
||||
disabled
|
||||
id="apiKey"
|
||||
|
||||
@@ -7,15 +7,13 @@ import {
|
||||
EmailField,
|
||||
PasswordField,
|
||||
TextField,
|
||||
useAuth,
|
||||
useConfig,
|
||||
useDocumentInfo,
|
||||
useFormFields,
|
||||
useFormModified,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { email as emailValidation } from 'payload/shared'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { Props } from './types.js'
|
||||
@@ -35,17 +33,13 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
operation,
|
||||
readOnly,
|
||||
requirePassword,
|
||||
setSchemaPath,
|
||||
setValidateBeforeSubmit,
|
||||
useAPIKey,
|
||||
username,
|
||||
verify,
|
||||
} = props
|
||||
|
||||
const { permissions } = useAuth()
|
||||
const [changingPassword, setChangingPassword] = useState(requirePassword)
|
||||
const enableAPIKey = useFormFields(([fields]) => (fields && fields?.enableAPIKey) || null)
|
||||
const forceOpenChangePassword = useFormFields(([fields]) => (fields && fields?.password) || null)
|
||||
const dispatchFields = useFormFields((reducer) => reducer[1])
|
||||
const modified = useFormModified()
|
||||
const { i18n, t } = useTranslation()
|
||||
@@ -56,50 +50,16 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
serverURL,
|
||||
} = useConfig()
|
||||
|
||||
const hasPermissionToUnlock: boolean = useMemo(() => {
|
||||
const collection = permissions?.collections?.[collectionSlug]
|
||||
|
||||
if (collection) {
|
||||
const unlock = 'unlock' in collection ? collection.unlock : undefined
|
||||
|
||||
if (unlock) {
|
||||
// current types for permissions do not include auth permissions, this will be fixed in another branch soon, for now we need to ignore the types
|
||||
// @todo: fix types
|
||||
// @ts-expect-error
|
||||
return unlock.permission
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}, [permissions, collectionSlug])
|
||||
|
||||
const handleChangePassword = useCallback(
|
||||
(showPasswordFields: boolean) => {
|
||||
if (showPasswordFields) {
|
||||
setValidateBeforeSubmit(true)
|
||||
setSchemaPath(`_${collectionSlug}.auth`)
|
||||
dispatchFields({
|
||||
type: 'UPDATE',
|
||||
errorMessage: t('validation:required'),
|
||||
path: 'password',
|
||||
valid: false,
|
||||
})
|
||||
dispatchFields({
|
||||
type: 'UPDATE',
|
||||
errorMessage: t('validation:required'),
|
||||
path: 'confirm-password',
|
||||
valid: false,
|
||||
})
|
||||
} else {
|
||||
setValidateBeforeSubmit(false)
|
||||
setSchemaPath(collectionSlug)
|
||||
(state: boolean) => {
|
||||
if (!state) {
|
||||
dispatchFields({ type: 'REMOVE', path: 'password' })
|
||||
dispatchFields({ type: 'REMOVE', path: 'confirm-password' })
|
||||
}
|
||||
|
||||
setChangingPassword(showPasswordFields)
|
||||
setChangingPassword(state)
|
||||
},
|
||||
[dispatchFields, t, collectionSlug, setSchemaPath, setValidateBeforeSubmit],
|
||||
[dispatchFields],
|
||||
)
|
||||
|
||||
const unlock = useCallback(async () => {
|
||||
@@ -120,7 +80,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
} else {
|
||||
toast.error(t('authentication:failedToUnlock'))
|
||||
}
|
||||
}, [i18n, serverURL, api, collectionSlug, email, username, t, loginWithUsername])
|
||||
}, [i18n, serverURL, api, collectionSlug, email, username, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (!modified) {
|
||||
@@ -134,8 +94,6 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
|
||||
const disabled = readOnly || isInitializing
|
||||
|
||||
const showPasswordFields = changingPassword || forceOpenChangePassword
|
||||
|
||||
return (
|
||||
<div className={[baseClass, className].filter(Boolean).join(' ')}>
|
||||
{!disableLocalStrategy && (
|
||||
@@ -159,33 +117,22 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
name="email"
|
||||
readOnly={readOnly}
|
||||
required={!loginWithUsername || loginWithUsername?.requireEmail}
|
||||
validate={(value) =>
|
||||
emailValidation(value, {
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
data: {},
|
||||
preferences: { fields: {} },
|
||||
req: { t } as any,
|
||||
required: true,
|
||||
siblingData: {},
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{(showPasswordFields || requirePassword) && (
|
||||
{(changingPassword || requirePassword) && (
|
||||
<div className={`${baseClass}__changing-password`}>
|
||||
<PasswordField
|
||||
autoComplete="off"
|
||||
disabled={disabled}
|
||||
label={t('authentication:newPassword')}
|
||||
name="password"
|
||||
path="password"
|
||||
required
|
||||
/>
|
||||
<ConfirmPasswordField disabled={readOnly} />
|
||||
</div>
|
||||
)}
|
||||
<div className={`${baseClass}__controls`}>
|
||||
{showPasswordFields && !requirePassword && (
|
||||
{changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={disabled}
|
||||
@@ -195,7 +142,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
)}
|
||||
{!showPasswordFields && !requirePassword && (
|
||||
{!changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={disabled}
|
||||
@@ -206,11 +153,11 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
{t('authentication:changePassword')}
|
||||
</Button>
|
||||
)}
|
||||
{operation === 'update' && hasPermissionToUnlock && (
|
||||
{operation === 'update' && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={disabled}
|
||||
onClick={() => void unlock()}
|
||||
onClick={() => unlock()}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:forceUnlock')}
|
||||
|
||||
@@ -9,8 +9,6 @@ export type Props = {
|
||||
operation: 'create' | 'update'
|
||||
readOnly: boolean
|
||||
requirePassword?: boolean
|
||||
setSchemaPath: (path: string) => void
|
||||
setValidateBeforeSubmit: (validate: boolean) => void
|
||||
useAPIKey?: boolean
|
||||
username: string
|
||||
verify?: VerifyConfig | boolean
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import { formatAdminURL, getFormState } from '@payloadcms/ui/shared'
|
||||
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import React, { Fragment, useCallback, useState } from 'react'
|
||||
import React, { Fragment, useCallback } from 'react'
|
||||
|
||||
import { LeaveWithoutSaving } from '../../../elements/LeaveWithoutSaving/index.js'
|
||||
import { Auth } from './Auth/index.js'
|
||||
@@ -102,9 +102,6 @@ export const DefaultEditView: React.FC = () => {
|
||||
|
||||
const classes = [baseClass, id && `${baseClass}--is-editing`].filter(Boolean).join(' ')
|
||||
|
||||
const [schemaPath, setSchemaPath] = React.useState(entitySlug)
|
||||
const [validateBeforeSubmit, setValidateBeforeSubmit] = useState(false)
|
||||
|
||||
const onSave = useCallback(
|
||||
(json) => {
|
||||
reportUpdate({
|
||||
@@ -161,6 +158,7 @@ export const DefaultEditView: React.FC = () => {
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
return getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
@@ -170,12 +168,12 @@ export const DefaultEditView: React.FC = () => {
|
||||
formState: prevFormState,
|
||||
globalSlug,
|
||||
operation,
|
||||
schemaPath,
|
||||
schemaPath: entitySlug,
|
||||
},
|
||||
serverURL,
|
||||
})
|
||||
},
|
||||
[apiRoute, collectionSlug, schemaPath, getDocPreferences, globalSlug, id, operation, serverURL],
|
||||
[serverURL, apiRoute, id, operation, entitySlug, collectionSlug, globalSlug, getDocPreferences],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -184,7 +182,7 @@ export const DefaultEditView: React.FC = () => {
|
||||
<Form
|
||||
action={action}
|
||||
className={`${baseClass}__form`}
|
||||
disableValidationOnSubmit={!validateBeforeSubmit}
|
||||
disableValidationOnSubmit
|
||||
disabled={isInitializing || !hasSavePermission}
|
||||
initialState={!isInitializing && initialState}
|
||||
isInitializing={isInitializing}
|
||||
@@ -233,8 +231,6 @@ export const DefaultEditView: React.FC = () => {
|
||||
operation={operation}
|
||||
readOnly={!hasSavePermission}
|
||||
requirePassword={!id}
|
||||
setSchemaPath={setSchemaPath}
|
||||
setValidateBeforeSubmit={setValidateBeforeSubmit}
|
||||
useAPIKey={auth.useAPIKey}
|
||||
username={data?.username}
|
||||
verify={auth.verify}
|
||||
@@ -259,7 +255,7 @@ export const DefaultEditView: React.FC = () => {
|
||||
docPermissions={docPermissions}
|
||||
fieldMap={fieldMap}
|
||||
readOnly={!hasSavePermission}
|
||||
schemaPath={schemaPath}
|
||||
schemaPath={entitySlug}
|
||||
/>
|
||||
{AfterDocument}
|
||||
</Form>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import type { FormProps } from '@payloadcms/ui'
|
||||
import type { FieldMap } from '@payloadcms/ui/utilities/buildComponentMap'
|
||||
import type {
|
||||
ClientCollectionConfig,
|
||||
ClientConfig,
|
||||
ClientGlobalConfig,
|
||||
Data,
|
||||
FieldMap,
|
||||
LivePreviewConfig,
|
||||
} from 'payload'
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
|
||||
autoComplete="email"
|
||||
label={t('general:email')}
|
||||
name="email"
|
||||
path="email"
|
||||
required={required}
|
||||
validate={(value) =>
|
||||
email(value, {
|
||||
@@ -40,7 +39,6 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
|
||||
<TextField
|
||||
label={t('authentication:username')}
|
||||
name="username"
|
||||
path="username"
|
||||
required
|
||||
validate={(value) =>
|
||||
username(value, {
|
||||
@@ -67,7 +65,6 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
|
||||
<TextField
|
||||
label={t('authentication:emailOrUsername')}
|
||||
name="username"
|
||||
path="username"
|
||||
required
|
||||
validate={(value) => {
|
||||
const passesUsername = username(value, {
|
||||
|
||||
@@ -6,10 +6,11 @@ import React from 'react'
|
||||
const baseClass = 'login__form'
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
import type { FormState } from 'payload'
|
||||
import type { FormState, PayloadRequest } from 'payload'
|
||||
|
||||
import { Form, FormSubmit, PasswordField, useConfig, useTranslation } from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { password } from 'payload/shared'
|
||||
|
||||
import type { LoginFieldProps } from '../LoginField/index.js'
|
||||
|
||||
@@ -81,7 +82,28 @@ export const LoginForm: React.FC<{
|
||||
>
|
||||
<div className={`${baseClass}__inputWrap`}>
|
||||
<LoginField type={loginType} />
|
||||
<PasswordField label={t('general:password')} name="password" required />
|
||||
<PasswordField
|
||||
autoComplete="off"
|
||||
label={t('general:password')}
|
||||
name="password"
|
||||
required
|
||||
validate={(value) =>
|
||||
password(value, {
|
||||
name: 'password',
|
||||
type: 'text',
|
||||
data: {},
|
||||
preferences: { fields: {} },
|
||||
req: {
|
||||
payload: {
|
||||
config,
|
||||
},
|
||||
t,
|
||||
} as PayloadRequest,
|
||||
required: true,
|
||||
siblingData: {},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
href={formatAdminURL({
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PasswordField,
|
||||
useAuth,
|
||||
useConfig,
|
||||
useFormFields,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
@@ -63,7 +64,7 @@ export const ResetPasswordClient: React.FC<Args> = ({ token }) => {
|
||||
toast.success(i18n.t('general:updatedSuccessfully'))
|
||||
}
|
||||
},
|
||||
[adminRoute, fetchFullUser, history, i18n, loginRoute],
|
||||
[fetchFullUser, history, adminRoute, i18n],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -73,15 +74,42 @@ export const ResetPasswordClient: React.FC<Args> = ({ token }) => {
|
||||
method="POST"
|
||||
onSuccess={onSuccess}
|
||||
>
|
||||
<PasswordField
|
||||
label={i18n.t('authentication:newPassword')}
|
||||
name="password"
|
||||
path="password"
|
||||
required
|
||||
/>
|
||||
<PasswordToConfirm />
|
||||
<ConfirmPasswordField />
|
||||
<HiddenField forceUsePathFromProps name="token" value={token} />
|
||||
<FormSubmit>{i18n.t('authentication:resetPassword')}</FormSubmit>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
const PasswordToConfirm = () => {
|
||||
const { t } = useTranslation()
|
||||
const { value: confirmValue } = useFormFields(
|
||||
([fields]) => (fields && fields?.['confirm-password']) || null,
|
||||
)
|
||||
|
||||
const validate = React.useCallback(
|
||||
(value: string) => {
|
||||
if (!value) {
|
||||
return t('validation:required')
|
||||
}
|
||||
|
||||
if (value === confirmValue) {
|
||||
return true
|
||||
}
|
||||
|
||||
return t('fields:passwordsDoNotMatch')
|
||||
},
|
||||
[confirmValue, t],
|
||||
)
|
||||
|
||||
return (
|
||||
<PasswordField
|
||||
autoComplete="off"
|
||||
label={t('authentication:newPassword')}
|
||||
name="password"
|
||||
required
|
||||
validate={validate}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { StepNavItem } from '@payloadcms/ui'
|
||||
import type { ClientCollectionConfig, ClientGlobalConfig, FieldMap } from 'payload'
|
||||
import type { FieldMap } from '@payloadcms/ui/utilities/buildComponentMap'
|
||||
import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload'
|
||||
import type React from 'react'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
@@ -81,8 +81,6 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
|
||||
const canUpdate = docPermissions?.update?.permission
|
||||
|
||||
const localeValues = locales.map((locale) => locale.value)
|
||||
|
||||
return (
|
||||
<main className={baseClass}>
|
||||
<SetViewActions actions={componentMap?.actionsMap?.Edit?.Version} />
|
||||
@@ -111,7 +109,6 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
globalSlug={globalSlug}
|
||||
label={collectionConfig?.labels.singular || globalConfig?.label}
|
||||
originalDocID={id}
|
||||
status={doc?.version?._status}
|
||||
versionDate={versionCreatedAt}
|
||||
versionID={versionID}
|
||||
/>
|
||||
@@ -139,7 +136,11 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
fieldMap={fieldMap}
|
||||
fieldPermissions={docPermissions?.fields}
|
||||
i18n={i18n}
|
||||
locales={localeValues}
|
||||
locales={
|
||||
locales
|
||||
? locales.map(({ label }) => (typeof label === 'string' ? label : undefined))
|
||||
: []
|
||||
}
|
||||
version={
|
||||
globalConfig
|
||||
? {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MappedField } from 'payload'
|
||||
import type { MappedField } from '@payloadcms/ui/utilities/buildComponentMap'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { getUniqueListBy } from 'payload/shared'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user