Compare commits

...

178 Commits

Author SHA1 Message Date
Elliot DeNolf
a198fe0be5 chore(release): v3.0.0-beta.107 [skip ci] 2024-09-16 11:50:44 -04:00
James Mikrut
c460868e52 fix: duplication with localized arrays in unnamed tabs (#8236)
Fixes a case where in relational DBs, you can't duplicate documents if
you have localized arrays within unnamed tabs.

The `beforeDuplicate` hooks were not being run for fields within unnamed
tabs.
2024-09-16 11:43:36 -04:00
Sasha
a0a1e20193 fix(drizzle): polymorphic querying of different ID types (#8191)
This PR fixes querying by a relationship field that has custom IDs in
`relationTo` with different types.
Now, in this case, we do cast the ID value in the database.

Example of the config / int test that reproduced the issue:

```ts
{
  slug: 'posts-a',
  fields: [],
},
{
  slug: 'posts-b',
  fields: [],
},
{
  slug: 'posts-custom-id',
  fields: [{ name: 'id', type: 'text' }],
},
{
  slug: 'roots',
  fields: [
    {
      name: 'rel',
      relationTo: ['posts-a', 'posts-b', 'posts-custom-id'],
      type: 'relationship',
    },
  ],
},
```

```ts
const postA = await payload.create({ collection: 'posts-a', data: {} })
const postB = await payload.create({ collection: 'posts-b', data: {} })
const postC = await payload.create({
  collection: 'posts-custom-id',
  data: { id: crypto.randomUUID() },
})

const root_1 = await payload.create({
  collection: 'roots',
  data: {
    rel: {
      value: postC.id,
      relationTo: 'posts-custom-id',
    },
  },
})

const res_1 = await payload.find({
  collection: 'roots',
  where: {
    'rel.value': { equals: postC.id },
  },
})

// COALESCE types integer and character varying cannot be matched

expect(res_1.totalDocs).toBe(1)
```

<!--

For external contributors, please include:

- A summary of the pull request and any related issues it fixes.
- Reasoning for the changes made or any additional context that may be
useful.

Ensure you have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

 -->
2024-09-16 10:39:55 -04:00
Jessica Chowdhury
3b59416298 feat: add new option for admin.components.header (#7647)
## Description

Adds `admin.components.header` option to allow users to insert custom
components in the page header / top of page.

[Related
discussion](https://github.com/payloadcms/payload/discussions/7584)

- [X] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [ ] Chore (non-breaking change which does not add functionality)
- [X] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Change to the
[templates](https://github.com/payloadcms/payload/tree/main/templates)
directory (does not affect core functionality)
- [ ] Change to the
[examples](https://github.com/payloadcms/payload/tree/main/examples)
directory (does not affect core functionality)
- [x] This change requires a documentation update

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works - will add
- [X] Existing test suite passes locally with my changes
2024-09-16 10:05:15 -04:00
Elliot DeNolf
7c8272b467 feat(cpa): add node engines (#8234) 2024-09-16 09:36:16 -04:00
Jacob Fletcher
06e5db6529 chore(examples): updates custom components example to latest payload (#8186) 2024-09-16 09:03:10 -04:00
Elliot DeNolf
8448e65b73 ci: release commenter (#8226)
In order to have beta releases properly trigger GitHub Actions'
`published` event in our `post-release` workflow, this job must exist on
the branch the release is on.
2024-09-15 13:41:09 -04:00
Alessio Gravili
6d1a287dd1 perf: remove find-up dependency, upgrade file-type dependency (#8195)
Fixes https://github.com/payloadcms/payload/issues/8111 and
https://github.com/payloadcms/payload/issues/8113

Before: 132 dependencies
After: 123 dependencies

This PR also contains a small performance optimization during telemetry
startup: By using the async `fs.promises.readFile` instead of
`readFileSync` we're not blocking the entire thread anymore and are
allowing other stuff to happen while the file is being read.
Also, in our dependency checker, this moves some variables out of loops,
to the module scope, as they only need to be calculated once.

We have to pin file-type to 19.3.0 and cannot upgrade it further (latest
is 19.5.0). See reasoning in
https://github.com/payloadcms/payload/issues/8111#issuecomment-2348119533
2024-09-15 16:53:53 +00:00
Elliot DeNolf
bb2dd5f4d2 chore(release): v3.0.0-beta.106 [skip ci] 2024-09-14 23:15:44 -04:00
James Mikrut
5873a3db06 fix: duplicating localized nested arrays (#8220)
Fixes an issue where duplicating documents in Postgres / SQLite would
crash because of a foreign key constraint / unique ID issue when you
have nested arrays / blocks within localized arrays / blocks.

We now run `beforeDuplicate` against all locales prior to
`beforeValidate` and `beforeChange` hooks.

This PR also fixes a series of issues in Postgres / SQLite where you
have localized groups / named tabs, and then arrays / blocks within the
localized groups / named tabs.
2024-09-15 02:51:31 +00:00
Elliot DeNolf
8fc2c43190 chore(release): v3.0.0-beta.105 [skip ci] 2024-09-14 22:40:24 -04:00
Jacob Fletcher
64f2395c58 fix(plugin-seo): removes duplicative json translations (#8206)
The SEO Plugin defines duplicative translations in both TS and JSON,
even though JSON translations are no longer in use. Translations were
still being maintained in JSON, despite this fact. This PR removes all
JSON files, replacing them with TS, and improving file organization and
overall types.
2024-09-14 20:19:12 -04:00
Paul
ff1c1e0c59 fix: error when viewing versions if plural label is set as a function (#8213) 2024-09-13 23:55:31 +00:00
Paul
d0bb1c9e60 fix(richtext-lexical): default Cell not being a link when used as the primary column in lists (#8212) 2024-09-13 21:17:44 +00:00
Paul
1608150a25 fix(ui): field type being overridden when providing a Cell component (#8211) 2024-09-13 20:16:28 +00:00
Alessio Gravili
fbc28b0249 perf: upgrade ajv, and upgrade typescript to 5.6.2 in monorepo (#8204)
Ajv 8.14.0 => 8.17.1

- Bundle size: 119.6kB => 111kB
- Dependencies: 5 => 4
- Gets rid of dependency on `punycode`. Will help with the annoying
deprecated module console warning spam

This also upgrades TypeScript to 5.6.2 in our monorepo. The most
type-relevant packages are updated as well, e.g. ts-essentials and
@types/node
2024-09-13 17:48:53 +00:00
Alessio Gravili
ec624bd1f2 perf: upgrade jsonwebtoken from 9.0.1 to 9.0.2 (#8202)
The bundle size of the `jsonwebtoken` package has been reduced in this
release. See
https://github.com/auth0/node-jsonwebtoken/blob/master/CHANGELOG.md
2024-09-13 16:33:03 +00:00
Sasha
43a9109b53 fix(db-postgres): preserve parent createdAt when creating a new version (#8160)
## Description

Fixes https://github.com/payloadcms/payload/issues/7915
- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-09-13 18:41:39 +03:00
Germán Jabloñski
334f940e0c fix(richtext-lexical): unnecessary isEnabled computations on toolbar items (#8176)
Fixes #8170 

- wrapped onActiveChange in useCallback
- removed an unnecessary mouseUp event

Note: I put the console.log for debugging in the useCallback called
updateStates inside
packages/richtext-lexical/src/features/toolbars/shared/ToolbarDropdown/index.tsx.

## Before


https://github.com/user-attachments/assets/07d715d4-f6c7-4a4a-91ab-5de418c909d6

## After


https://github.com/user-attachments/assets/2d404d1c-d1a7-46fd-a5b6-7d01c5c16959
2024-09-13 12:29:10 -03:00
Jarrod Flesch
dd5a9acb60 chore: prevent leave without saving from appearing when not needed (#8200) 2024-09-13 10:48:47 -04:00
Elliot DeNolf
9548961ec9 ci: post-release workflow, comment on releases 2024-09-12 22:43:37 -04:00
Elliot DeNolf
66082d5127 ci: initialize post-release workflow 2024-09-12 22:39:38 -04:00
Victor Winberg
db29e1ec98 fix(graphql): id field non null (#8169)
Resolves #8172

## Summary

This PR addresses an issue where the`id` field in the GraphQL schema is
incorrectly marked as `nullable`. The change ensures that the `id` field
is set to non-nullable, which aligns with the expectation that every
resource should have a non-nullable ID, especially when using UUIDs as
primary keys.

### Changes
- Fix: Set the `id` field type to `GraphQLNonNull` for consistency in
the GraphQL schema.
2024-09-12 17:34:12 -06:00
Paul
d28d40ced3 fix(ui)!: bulk selection and useSelection hook losing types of the IDs its returning (#8194)
This PR changes the type of `selected` returned from the `useSelection`
hook from the `SelectionProvider` from an object to a Map.

This fixes a bug where in some situations we lose the type of the ID
which can break data entry when using postgres, due to keys being cast
to strings inside of objects which doesn't happen when using a Map.

This PR also fixes a CSS bug with the checkbox when it should be
partially selected.

```ts
// before
selected: Record<number | string, boolean>

// after
selected: Map<number | string, boolean>
```

This means you now need to read the data differently than before.

```ts
// before
Object.entries(selected).forEach(([key, value]) => {
  // do something
})

// after
for (const [key, value] of selected) {
  // do something
}
```
2024-09-12 16:58:54 -06:00
Jacob Fletcher
a6f13f7330 fix(ui): properly extracts label from field in FieldLabel component (#8190)
Although the `<FieldLabel />` component receives a `field` prop, it does
not use this prop to extract the `label` from the field. This is
currently only an issue when rendering this component directly, such as
within `admin.components.Label`. The label simply won't appear unless
explicitly provided, despite it being passed as `field.label`. This is
not an issue when rendering field components themselves, because they
properly thread this value through as a top-level prop.

Here's an example of the issue:

```tsx
import type { TextFieldLabelServerComponent } from 'payload'

import { FieldLabel } from '@payloadcms/ui'
import React from 'react'

export const MyCustomLabelComponent: TextFieldLabelServerComponent = ({ clientField }) => {
  return (
    <FieldLabel
      field={clientField}
      label={clientField.label} // this should not be needed!
    />
  )
}
```

Here is the end result:

```tsx
import type { TextFieldLabelServerComponent } from 'payload'

import { FieldLabel } from '@payloadcms/ui'
import React from 'react'

export const MyCustomLabelComponent: TextFieldLabelServerComponent = ({ clientField }) => {
  return <FieldLabel field={clientField} />
}
```
2024-09-12 14:45:17 -04:00
Florian Quiblier
532e4b52fe docs: removes type keyword from useFormFields import (#8091) 2024-09-12 16:38:14 +00:00
Christoffer Hasselberg
59b6107e2d docs: fixes getPayloadHMR import path (#8180) 2024-09-12 12:33:22 -04:00
Jacob Fletcher
c28618b19c fix: requires client field prop in server field components (#8188)
Fixes a type error when using server components for field labels,
descriptions, and errors. The `clientField` prop will always exist, so
the types just need to be reflective of this. Here's an example:

```tsx
import type { TextFieldServerLabelComponent } from 'payload'

import { FieldLabel } from '@payloadcms/ui'
import React from 'react'

export const MyServerFieldLabelComponent: TextFieldServerLabelComponent = ({ clientField }) => {
  return <FieldLabel field={clientField} /> // `TextFieldClientWithoutType | undefined` is not assignable to type `ClientFieldWithoutType`
}
```
2024-09-12 12:15:26 -04:00
Elliot DeNolf
945d9192a1 chore(deps): bump turbo 2024-09-12 09:11:50 -04:00
Elliot DeNolf
dbf2301a61 chore(release): v3.0.0-beta.104 [skip ci] 2024-09-12 09:05:20 -04:00
Dan Ribbens
c34401dc4b test: uploads return correct content type headers (#8182) 2024-09-12 11:21:10 +00:00
Patrik
6e94884d18 fix(ui): properly retrieves singular labels for array field rows (#8171)
## Description

`singular` labels were not being used for array rows - this PR updates
the array field to properly retrieve the correct label

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-09-11 15:58:05 -04:00
Jacob Fletcher
8b307012f3 feat: passes client field config to server components (#8166)
## Description

### TL;DR:

It's currently not possible to render our field components from a server
component because their `field` prop is the original field config, not
the _client_ config which our components require. Currently, the `field`
prop passed into custom fields changes type depending on whether it's a
server or client component, leaving server components without any access
to the client field config or mechanism to acquire it.

This PR passes the client config to all server field components through
a new `clientField` prop. This allows the following in a server
component, which is very similar to how client field components
currently work:

Server component:

```tsx
import { TextField } from '@payloadcms/ui'
import type { TextFieldServerComponent } from 'payload'

export const MyCustomServerField: TextFieldServerComponent = ({ clientField }) => {
  return <TextField field={clientField} />
}
```

Client component:

```tsx
'use client'
import { TextField } from '@payloadcms/ui'
import type { TextFieldClientComponent } from 'payload'

export const MyCustomClientField: TextFieldClientComponent = ({ field }) => {
  return <TextField field={field} />
}
```

### Full Background

If you have a custom field component, and it's a server component, there
is currently no way to pass the field prop into Payload's client-side
field components.

Here's an example of the problem:

```tsx
import { TextField } from '@payloadcms/ui'
import type { TextFieldServerComponent } from 'payload'

import React from 'react'

export const MyServerComponent: TextFieldServerComponent = (props) => {
  const { field } = props

  return (
    <TextField field={field} /> // This is not possible
  )
}
```

The config needs to be transformed into a client config, however,
because of the sheer number of hard-to-find arguments that the
`createClientField` requires, we cannot use it in its raw form.

Here is another example of the problem:

```tsx
import { TextField } from '@payloadcms/ui'
import { createClientField } from '@payloadcms/ui/utilities/createClientField'
import type { TextFieldServerComponent } from 'payload'

import React from 'react'

export const MyServerComponent: TextFieldServerComponent = ({ createClientField }) => {
  const clientField = createClientField({...}) // Not a good option bc it requires many hard-to-find args

  return (
    <TextField field={clientField} />
  )
}
```

Theoretically, we could preformat a `createFieldConfig` function so it
can simply be called without arguments:

```tsx
import { TextField } from '@payloadcms/ui'
import type { TextFieldServerComponent } from 'payload'

import React from 'react'

export const MyServerComponent: TextFieldServerComponent = ({ createClientField }) => {
  return <TextField field={createClientField()} />
}
```

But this means the field config would be evaluated twice unnecessarily,
including label functions, etc.

The right way to fix this is to simply pass the client config to server
components through a new `clientField` prop:

```tsx
import { TextField } from '@payloadcms/ui'
import type { TextFieldServerComponent } from 'payload'

import React from 'react'

export const MyServerComponent: TextFieldServerComponent = ({ clientField }) => {
  return <TextField field={clientField} />
}
```

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## Checklist:

- [x] Existing test suite passes locally with my changes
- [x] I have made corresponding changes to the documentation
2024-09-11 15:47:56 -04:00
Paul
9561aa3f79 fix(templates): website media staticDir to public folder (#8175) 2024-09-11 18:37:55 +00:00
Jacob Fletcher
51bc8b4416 feat: document drawer controls (#7679)
## Description

Currently, you cannot create, delete, or duplicate documents within the
document drawer directly. To create a document within a relationship
field, for example, you must first navigate to the parent field and open
the "create new" drawer. Similarly (but worse), to duplicate or delete a
document, you must _navigate to the parent document to perform these
actions_ which is incredibly disruptive to the content editing workflow.
This becomes especially apparent within the relationship field where you
can edit documents inline, but cannot duplicate or delete them. This PR
supports all document-level actions within the document drawer so that
these actions can be performed on-the-fly without navigating away.

Inline duplication flow on a polymorphic "hasOne" relationship:


https://github.com/user-attachments/assets/bb80404a-079d-44a1-b9bc-14eb2ab49a46

Inline deletion flow on a polymorphic "hasOne" relationship:


https://github.com/user-attachments/assets/10f3587f-f70a-4cca-83ee-5dbcad32f063

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-09-11 14:34:03 -04:00
Paul
ec3730722b feat(drizzle): add support for in and not_in operators on json field (#8148)
Closes https://github.com/payloadcms/payload/issues/7952

Adds support for `in` and `not_in` operator against JSON field filters.

The following queries are now valid in postgres as well, previously it
only worked in mongo

```ts
await payload.find({
  collection: 'posts',
  where: {
    'data.value': {
      in: ['12', '13', '14'],
    },
  },
  context: {
    disable: true,
  },
})


await payload.find({
  collection: 'posts',
  where: {
    'data.value': {
      not_in: ['12', '13', '14'],
    },
  },
  context: {
    disable: true,
  },
})
```
2024-09-11 11:11:13 -06:00
Jacob Fletcher
465e47a219 fix!: properly names BlocksField and related types (#8174)
The `BlockField` type is not representative of the underlying "blocks"
field type, which is plural, i.e. `BlocksField`. This is a semantic
change that will better align the type with the field.

## Breaking Changes

Types related to the `blocks` field have change names. If you were using
the `BlockField` or related types in your own applications, simply
change the import name to be plural and instead of singular.

Old (singular):

```ts
import type {
  BlockField,
  BlockFieldClient,
  BlockFieldValidation,
  BlockFieldDescriptionClientComponent,
  BlockFieldDescriptionServerComponent,
  BlockFieldErrorClientComponent,
  BlocksFieldErrorServerComponent,
  BlockFieldLabelClientComponent,
  BlockFieldLabelServerComponent,
} from 'payload'
```

New (plural):

```ts
import type {
  BlocksField,
  BlocksFieldClient,
  BlocksFieldValidation,
  BlocksFieldDescriptionClientComponent,
  BlocksFieldDescriptionServerComponent,
  BlocksFieldErrorClientComponent,
  BlocksFieldErrorServerComponent,
  BlocksFieldLabelClientComponent,
  BlocksFieldLabelServerComponent,
} from 'payload'
```
2024-09-11 16:05:03 +00:00
Hampus Wallentin Olsen
043bf95a70 fix(cpa): match vercel postgres db type with package name (#8141)
## Description

Fixes the bug I reported in
https://github.com/payloadcms/payload/issues/8139 where the casing of
the defined value (camelCase) of Vercel's Postgres database adapter does
not match the casing of the package (kebab-case).
2024-09-11 09:47:09 -06:00
Germán Jabloñski
cd734b0f98 fix(ui): fix row width bug (#7940)
Closes https://github.com/payloadcms/payload/issues/7867

Problem: currently, setting an 

```ts
admin: {
   width: '30%'
}
```

does not work for fields inside a row or similar (group, array etc.)

Solution: when we render the field, we set a CSS variable
`--field-width` with the value of `admin.width`. This allows us to
calculate the correct width for a field in CSS by doing `flex: 0 1
var(--field-width);`

It also allows us to properly handle `gap` with `flex-wrap: wrap;`

Notes: added playwright tests to ensure widths are correctly rendered


![image](https://github.com/user-attachments/assets/0c0f11fc-2387-4f01-9298-a2613fceee22)
2024-09-11 12:36:54 -03:00
Elliot DeNolf
6e61431ca1 chore(release): v3.0.0-beta.103 [skip ci] 2024-09-11 09:04:49 -04:00
Paul
663e5119b2 fix: useAsTitle validation now accounts for default and base fields (#8165) 2024-09-11 03:52:52 +00:00
Jacob Fletcher
8fe6ffd39b feat(examples): adds custom components example (#8162) 2024-09-10 22:48:52 -04:00
Sasha
0118bce582 fix(next): set the user data before redirect after login (#8135)
## Description

Fixes https://github.com/payloadcms/payload/issues/8134

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-09-10 19:04:42 +00:00
Jarrod Flesch
46707e4c5e chore: update lexical docs links 2024-09-10 14:11:11 -04:00
Sasha
a234092b34 fix: upload.defParamCharset: utf8 by default (#8157)
## Description

Fixes https://github.com/payloadcms/payload/issues/8107

This has been confusing for people from countries where characters
aren't latin, for example the Japanese file name:
フェニックス.png
Turns into:
 ãã§ããã¯ã¹.png  

Additionally, ensures type-safety for `DEFAULT_OPTIONS` and removes
unused `fileHandler` property from there, which isn't defined in the
`FetchAPIFileUploadOptions` type.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)
## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-09-10 17:40:44 +00:00
Germán Jabloñski
281c80d2c7 fix(richtext-lexical): hover style of the button to remove blocks (#8154)
## Description

Fix #8045

Before: hover with same color as background, as in the issue
description.

After (light):
![Screenshot 2024-09-10 at 9 36
21 AM](https://github.com/user-attachments/assets/260dbc69-a583-42f6-9b25-a81b8d8d4f70)

After (dark):
![Screenshot 2024-09-10 at 9 35
34 AM](https://github.com/user-attachments/assets/3606ee3c-24d6-43dd-8a0e-11d12e1fe779)


- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] Existing test suite passes locally with my changes
- [x] I have made corresponding changes to the documentation
2024-09-10 11:47:05 -03:00
Jacob Fletcher
12a30a0585 fix: extends server props onto field component types (#8155) 2024-09-10 10:42:22 -04:00
Sasha
0c563ebd73 fix(db-postgres): querying on array wtihin a relationship field (#8152)
## Description

Fixes https://github.com/payloadcms/payload/issues/6037

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-09-10 08:44:38 -04:00
Bruno Crosier
82a684138a Merge branch 'beta' of https://github.com/payloadcms/payload into fix-row-field-width 2024-09-09 23:32:25 +01:00
Elliot DeNolf
df023a52fd chore(release): v3.0.0-beta.102 [skip ci] 2024-09-09 17:07:31 -04:00
Dan Ribbens
d267cad482 fix: beforeDuplicate localized blocks and arrays (#8144)
fixes #7988
2024-09-09 21:02:56 +00:00
Germán Jabloñski
fa38dfc16c fix(richtext-lexical): indent regression (#8138)
## Description

Fixes #8038, which was broken in #7817

I'm not entirely sure if this change violates the original intent of the
"base" utility, which from what I understand was introduced for
scalability reasons. Either way, I think it's a good idea to keep the
indent at 40px all the time.

The reason for this is that browsers use 40px as the indentation setting
for lists, and using that setting the indented paragraphs and headings
match the lists. See https://github.com/facebook/lexical/pull/4025

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [x] I have made corresponding changes to the documentation
2024-09-09 20:48:05 +00:00
Jacob Fletcher
8e1a5c8dba feat!: explicitly types field components (#8136)
## Description

Currently, there is no way of typing custom server field components.
This is because internally, all field components are client components,
and so these were never fully typed. For example, the docs currently
indicate for all custom fields to be typed in this way:

Old:
    
```tsx
export const MyClientTextFieldComponent: React.FC<TextFieldProps>
```

But if your component is a server component, you will never receive the
fully typed `field` prop, `payload` prop, etc. unless you've typed that
yourself using some of the underlying utilities. So to fix this, every
field now explicitly exports a type for each environment:

New:

- Client component:
    ```tsx
    'use client'
    export const MyClientTextFieldComponent: TextFieldClientComponent
    ```

- Server component:
    ```tsx
    export const MyServerTextFieldComponent: TextFieldServerComponent
    ```

This pattern applies to every field type, where the field name is
prepended onto the component type.

```ts
import type {
  TextFieldClientComponent,
  TextFieldServerComponent,
  TextFieldClientProps,
  TextFieldServerProps,
  TextareaFieldClientComponent,
  TextareaFieldServerComponent,
  TextareaFieldClientProps,
  TextareaFieldServerProps,
  // ...and so on for each field type
} from 'payload'
```

## BREAKING CHANGES

We are no longer exporting `TextFieldProps` etc. for each field type.
Instead, we now export props for each client/server environment
explicitly. If you were previously importing one of these types into
your custom component, simply change the import name to reflect your
environment.

Old:

```tsx
import type { TextFieldProps } from 'payload'
``` 

New:

```tsx
import type { TextFieldClientProps, TextFieldServerProps } from 'payload'
``` 

Related: #7754. 

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] New feature (non-breaking change which adds functionality)
- [x] This change requires a documentation update

## Checklist:

- [x] Existing test suite passes locally with my changes
- [x] I have made corresponding changes to the documentation
2024-09-09 20:15:10 +00:00
Germán Jabloñski
67e1d6abc5 fix hover block remove button 2024-09-09 17:07:30 -03:00
Elliot DeNolf
a8c60c1c02 chore(release): v3.0.0-beta.101 [skip ci] 2024-09-09 16:04:45 -04:00
James Mikrut
d44fb2db37 fix: #6800, graphql parallel queries with different fallback locales (#8140)
## Description

Fixes #6800 where parallel GraphQL queries with different locales /
fallbackLocales do not return their data properly.
2024-09-09 16:01:58 -04:00
Bruno Crosier
3a61d8d656 fix 2024-09-09 20:01:49 +01:00
Bruno Crosier
d04f6ab2bf fix test 2024-09-09 19:42:23 +01:00
Dan Ribbens
852f9fc1fd fix!: multiple preferences for the same user and entry (#8100)
fixes #7762

This change mitigates having multiple preferences for one user but not
awaiting the change to a preference and reduces querying by skipping the
access control. In the event that a user has multiple preferences with
the same key, only the one with the latest updatedAt will be returned.

BREAKING CHANGES:
- payload/preferences/operations are no longer default exports

## Description

<!-- Please include a summary of the pull request and any related issues
it fixes. Please also include relevant motivation and context. -->

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [ ] Chore (non-breaking change which does not add functionality)
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Change to the
[templates](https://github.com/payloadcms/payload/tree/main/templates)
directory (does not affect core functionality)
- [ ] Change to the
[examples](https://github.com/payloadcms/payload/tree/main/examples)
directory (does not affect core functionality)
- [ ] This change requires a documentation update

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation

---------

Co-authored-by: Paul Popus <paul@nouance.io>
2024-09-09 14:00:51 -04:00
Bruno Crosier
552239b637 fix type errors 2024-09-09 18:53:14 +01:00
Bruno Crosier
37e181a38d pr comments 2024-09-09 18:42:19 +01:00
Dan Ribbens
e2d803800d fix: removes transactions wrapping auth strategies and login (#8137)
## Description

By default all api requests are creating transactions due to the
authentication stategy. This change removes transactions for auth and
login requests. This should only happen when the database needs to make
changes in which case the auth strategy or login lockout updates will
invoke their own transactions still.

This should improve performance without any sacrifice to database
consistency.

Fixes #8092 

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [ ] Chore (non-breaking change which does not add functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Change to the
[templates](https://github.com/payloadcms/payload/tree/main/templates)
directory (does not affect core functionality)
- [ ] Change to the
[examples](https://github.com/payloadcms/payload/tree/main/examples)
directory (does not affect core functionality)
- [ ] This change requires a documentation update

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-09-09 13:27:21 -04:00
Germán Jabloñski
7fa68d17f5 fix(ui): wrong block indication when an error occurred (#7963) 2024-09-09 10:20:03 -04:00
Paul
9ec431a5bd fix(ui): bulk select checkbox being selected by default when in drawer (#8126) 2024-09-09 06:47:35 +00:00
Paul
cadf815ef6 fix(ui): thumbnails when serverURL config is provided (#8124) 2024-09-09 06:16:43 +00:00
Paul
638382e7fd feat: add validation for useAsTitle to throw an error if it's an invalid or nested field (#8122) 2024-09-08 18:53:12 -06:00
Elliot DeNolf
08fdbcacc0 chore: proper error log format (#8105)
Fix some error log formatting to use `{ msg, err }` properly
2024-09-07 02:48:59 +00:00
Paul
b27e42c484 fix(ui): various issues around documents lists, listQuery provider and search params (#8081)
This PR fixes and improves:
- ListQuery provider is now the source of truth for searchParams instead
of having components use the `useSearchParams` hook
- Various issues with search params and filters sticking around when
navigating between collections
- Pagination and limits not working inside DocumentDrawer
- Searching and filtering causing a flash of overlay in DocumentDrawer,
this now only shows for the first load and on slow networks
- Preferences are now respected in DocumentDrawer
- Changing the limit now resets your page back to 1 in case the current
page no longer exists

Fixes https://github.com/payloadcms/payload/issues/7085
Fixes https://github.com/payloadcms/payload/pull/8081
Fixes https://github.com/payloadcms/payload/issues/8086
2024-09-06 15:51:09 -06:00
Tylan Davis
32cc1a5761 fix(ui): missing thumbnail for non-image files in bulk upload sidebar (#8102)
## Description

Uses the `Thumbnail` component used in other places for the bulk upload
file rows. Closes #8099

In the future, we should consider adding different thumbnail icons based
on the `mimeType` to better describe the files being uploaded.

Before:
![Screenshot 2024-09-06 at 4 51
56 PM](https://github.com/user-attachments/assets/35cd528c-5086-465e-8d3c-7bb66d7c35da)


After:
![Screenshot 2024-09-06 at 4 50
12 PM](https://github.com/user-attachments/assets/54d2b98d-ac11-481e-abe5-4be071c3c8f2)


- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-09-06 21:28:50 +00:00
Tylan Davis
38be69b7d3 fix(ui): better responsiveness for upload fields in sidebar (#8101)
## Description

Adjusts the styling for the Dropzone component for upload fields with
`admin.position: sidebar`.

Before:
![Screenshot 2024-09-06 at 4 10
28 PM](https://github.com/user-attachments/assets/221d43f9-f426-4a44-ba58-29123050c775)

After:
![Screenshot 2024-09-06 at 4 09
32 PM](https://github.com/user-attachments/assets/c4369a65-d842-4e65-9153-19244fcf5600)


- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-09-06 20:37:38 +00:00
Elliot DeNolf
6b82196f01 chore(release): v3.0.0-beta.100 [skip ci] 2024-09-06 15:25:41 -04:00
Tylan Davis
ead12c8a49 fix(ui, next): adjust modal alignment and padding (#7931)
## Description

Updates styling on modals and auth forms for more consistent spacing and
alignment.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-09-06 14:54:39 -04:00
Jacob Fletcher
6253ec5d1a fix(ui): optimizes the relationship field by sharing a single document drawer across all values (#8094)
## Description

Currently, the relationship field's _value(s)_ each render and controls
its own document drawer. This has led to `hasMany` relationships
processing a potentially large number of drawers unnecessarily. But the
real problem is when attempting to perform side-effects as a result of a
drawer action. Currently, when you change the value of a relationship
field, all drawers within are (rightfully) unmounted because the
component representing the value was itself unmounted. This meant that
you could not update the title of a document, for example, then update
the underlying field's value, without also closing the document drawer
outright. This is needed in order to support things like creating and
duplicating documents within document drawers (#7679).

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-09-06 14:00:53 -04:00
Jacob Fletcher
f9ae56ec88 fix(ui): handles falsey relationship options on reset (#8095) 2024-09-06 12:55:09 -04:00
Sasha
0688c2b79d fix(db-postgres): sanitize tab/group path for table name (#8009)
## Description

Fixes https://github.com/payloadcms/payload/issues/7109

Example of table structures that lead to the problem with camelCased
group / tab names.
`group_field_array_localized` - `groupField` -> `array` (has a localized
field inside)
`group_field_array_nested_array` - `groupField` -> `array` ->
`nestedArray`

<!-- Please include a summary of the pull request and any related issues
it fixes. Please also include relevant motivation and context. -->

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-09-06 11:43:47 -04:00
Sasha
c6246618ba fix(cpa): detect package manager from command execution environment (#8087)
Previously, on some machines this command:
`pnpx create-payload-app@beta app` created a project using `npm`,
instead of `pnpm`, the same with `yarn`.

Also, the way we detected the package manager was always prioritizing
`pnpm`, even if they executed the command with `yarn` / `npm`. Now we
are relying only on from which package manager user executed
`create-payload-app`.

The code for detection is grabbed from create-next-app
https://github.com/vercel/next.js/blob/canary/packages/create-next-app/helpers/get-pkg-manager.ts
2024-09-06 08:57:20 -04:00
Alexander
b69826a81e feat(cpa): add support for bun package manager in v3 installer (#7709)
Adds support for bun package manger in v3, enabled with `--use-bun`
flag.

Related: #6932 (for v2)
2024-09-05 23:50:03 -04:00
Paul
e80da7cb75 chore: add jsdocs for authentication types and add missing config to docs (#8082) 2024-09-06 00:04:13 +00:00
Francisco Lourenço
6f512b6ca8 docs: fix TextFieldProps in client field component example (#8080)
## Description

Without using `React.FC<>`, the type needs to be placed on the right
side of the props object.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Chore (non-breaking change which does not add functionality)

## Checklist:

- [ ] ~I have added tests that prove my fix is effective or that my
feature works~
- [ ] ~Existing test suite passes locally with my changes~
- [x] I have made corresponding changes to the documentation
2024-09-05 15:41:48 -06:00
Elliot DeNolf
22ee8bf383 chore(release): v3.0.0-beta.99 [skip ci] 2024-09-05 12:38:08 -04:00
Jacob Fletcher
308fad8a7a fix(ui): significantly optimizes relationship field (#8063)
## Description

Reduces the number of client-side requests made by the relationship
field component, and fixes the visual "blink" of the field's value on
initial load. Does so through a new `useIgnoredEffect` hook that allows
this component's effects to be precisely triggered based on whether a
only _subset_ of its dependencies have changed, which looks something
like this:

```tsx
// ...
useIgnoredEffect(() => {
  // Do something
}, [deps], [ignoredDeps])
```

"Ignored deps" are still treated as normal dependencies of the
underlying `useEffect` hook, but they do not cause the provided function
to execute. This is useful if you have a list of dependencies that
change often, but need to scope your effect's logic to explicit
dependencies within that list. This is a typical pattern in React using
refs, just standardized within a reusable hook.

This significantly reduces the overall number of re-renders and
duplicative API requests within the relationship field because the
`useEffect` hooks that control the fetching of these related documents
were running unnecessarily often. In the future, we really ought to
leverage the `RelationshipProvider` used in the List View so that we can
also reduce the number of duplicative requests across _unrelated fields_
within the same document.

Before:


https://github.com/user-attachments/assets/ece7c85e-20fb-49f6-b393-c5e9d5176192

After:


https://github.com/user-attachments/assets/9f0a871e-f10f-4fd6-a58b-8146ece288c4

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-09-04 21:37:00 -04:00
Jessica Chowdhury
6427b7eb29 fix: only show restore as draft option when drafts enabled (#8066)
## Description

In version comparison view, the `Restore as draft` button should only be
visible when `versions.drafts: true`.

Before:
<img width="1414" alt="Screenshot 2024-09-04 at 3 33 21 PM"
src="https://github.com/user-attachments/assets/1f96d804-46d7-443a-99ea-7b6481839b47">

After:
<img width="1307" alt="Screenshot 2024-09-04 at 3 38 42 PM"
src="https://github.com/user-attachments/assets/d2621ddd-2b14-4dab-936c-29a5521444de">


- [X] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [X] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [X] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-09-04 19:54:34 +00:00
Sasha
3a657847f2 fix(db-postgres): query hasMany text/number in array/blocks (#8003)
## Description

Fixes https://github.com/payloadcms/payload/issues/7671

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-09-04 11:53:43 -04:00
Elliot DeNolf
8212c0d65f chore(eslint): silence some warnings that always get auto-fixed 2024-09-04 11:26:36 -04:00
Elliot DeNolf
772f869cc6 chore(release): v3.0.0-beta.98 [skip ci] 2024-09-03 12:59:23 -04:00
Alessio Gravili
b6a8d1c461 perf(richtext-lexical)!: greatly simplify lexical loading and improve performance (#8041)
We noticed that we can bring functions down to the client directly
without having to wrap them in a component first. This greatly
simplifies the loading of all lexical client components

**BREAKING:**
- `createClientComponent` is no longer exported as it's not needed
anymore
- The exported `ClientComponentProps` type has been renamed to
`BaseClientFeatureProps`.
- The order of arguments in `sanitizeClientEditorConfig` has changed
2024-09-03 12:48:41 -04:00
Elliot DeNolf
11576eda13 ci: adjust label pr on open [skip ci] (#8043) 2024-09-03 11:38:21 -04:00
Elliot DeNolf
3c62e6c772 chore(eslint): lint entire codebase including db packages (#8042) 2024-09-03 11:22:41 -04:00
Alessio Gravili
5b74879c5e chore: add lint commit to .git-blame-ignore-revs 2024-09-03 11:20:26 -04:00
Alessio Gravili
7fd736ea5b chore: lint entire codebase including db packages 2024-09-03 11:19:19 -04:00
Germán Jabloñski
7a3507d597 fix(richtext-lexical): toolbar styles (#7936)
fix https://github.com/payloadcms/payload/issues/7925.

The `active` style was not effective due to a typo in the CSS (`them`
instead of `theme`).

I took the opportunity to improve a few things:
- Now the colors on hover, active, and hover+active are slightly
different.
- Added a missing gap to the fixed toolbar buttons.

Gap changes: Before:
![CleanShot 2024-09-03 at 00 02
52@2x](https://github.com/user-attachments/assets/2381468c-7bdd-43f6-93b6-5baa587dd0a6)

After:
![CleanShot 2024-09-03 at 00 01
22@2x](https://github.com/user-attachments/assets/53d0cac9-9718-4b97-a478-f249b10d416e)


Thanks @tylandavis for the help!
2024-09-03 00:04:12 -04:00
Bruno Crosier
ef6fe9ca9a review comments 2024-09-02 23:52:04 +01:00
Paul
9bcdf0dc81 fix(plugin-seo): meta image selection not working (#8024)
Fixes https://github.com/payloadcms/payload/issues/8016
2024-09-02 05:11:27 +00:00
Paul
8203fe86cd fix: padding on the right of the default padding when scrollbars are enabled in the browser (#8023) 2024-09-02 04:22:02 +00:00
Elliot DeNolf
39cd8283c8 chore(scripts): release notes emoji 2024-09-01 19:39:52 -04:00
Giuseppe Chiruzzi
1130a581c0 feat(plugin-seo): add Italian translations (#8020)
Added italian translation and updated index.ts
2024-09-01 23:03:12 +00:00
Riley Pearce
d9cccc7081 fix(richtext-lexical): incorrect error check in TableActionMenu (#7964)
Fixes #7961 and #8021
2024-09-01 18:29:02 -04:00
Bruno Crosier
053256d5ce more tests and better implementation 2024-08-31 23:58:20 +01:00
Elliot DeNolf
751803d4f4 chore(cpa): get templates using tar (#8006)
Remove `degit` in favor of tar files from codeload.

Degit is rather dated and has unfixed bugs such as #5402  and #7463 .
2024-08-30 22:51:32 -04:00
Paul
ee3d5856e3 fix: collection pagination limits being merged with defaults instead of overidden (#8004) 2024-08-30 23:29:20 +00:00
Paul
cf9e13aebb fix(ui): radio fields are now selectable as options in filtering in query builder (#8002) 2024-08-30 23:04:31 +00:00
Elliot DeNolf
9816787fbf chore: remove all unused imports (#7999)
Removes all unused imports.

Temporarily swapped in
https://github.com/sweepline/eslint-plugin-unused-imports to
differentiate between unused imports and unused vars. The default rule
does not differentiate.
2024-08-30 16:52:08 -04:00
Alessio Gravili
b5fb92530c chore(eslint): change no-empty-object-type to warn instead of error (#7998) 2024-08-30 20:29:06 +00:00
Elliot DeNolf
2c1c0dae70 chore(release): eslint/3.0.0-beta.97 2024-08-30 16:11:29 -04:00
Paul
9295a6130e chore(templates): bump versions to just beta instead of pinned (#7997) 2024-08-30 19:30:06 +00:00
Paul
91fc5fb31b chore(templates): update folder structure for seed script in website template (#7995) 2024-08-30 19:14:03 +00:00
Paul
e25730f95c fix(ui): list view crash when using a code field type (#7994) 2024-08-30 12:47:33 -06:00
Elliot DeNolf
7f6b0f087f chore(release): v3.0.0-beta.97 [skip ci] 2024-08-30 14:21:21 -04:00
Paul
c1533bfd3e fix(templates): add button to exit preview mode (#7991) 2024-08-30 17:44:11 +00:00
Elliot DeNolf
442d105841 fix: migrations exit code (#7989)
Migration command were not returning proper error codes on failure. This
caused issues with CI in particular.

Fixes #7031 for 3.0
2024-08-30 16:37:39 +00:00
Elliot DeNolf
c45ee0d26b ci: add drizzle as valid pr title scope [skip ci] 2024-08-30 12:25:24 -04:00
Jarrod Flesch
b97dcc33c7 chore: hoist selection provider (#7985) 2024-08-30 11:02:58 -04:00
Elliot DeNolf
8202c3dee8 feat: move logger configuration to Payload config (#7973)
Removes `loggerOptions` and `loggerDestination` from `initOptions`
(these were not able to be used anyway).

Creates new `logger` property on the Payload config.

```ts
// Logger options only
logger: {
   level: 'info',
}

// Logger options with destination stream
logger: {
   options: {
   level: 'info',
   },
   destination: process.stdout
},

// Logger instance
logger: pino({ name: 'my-logger' })
```
2024-08-30 09:37:51 -04:00
Paul
b6ae6890aa fix(ui): upload field not showing admin.description (#7978) 2024-08-30 04:22:43 +00:00
Elliot DeNolf
c14dbfba40 build: bump nodejs (#7935)
Bumped to 22.6.0
2024-08-29 23:46:06 -04:00
Elliot DeNolf
0a36529dc5 build(deps): bump node.js to 22.7.0 2024-08-29 23:29:40 -04:00
Paul
e033488db7 fix(nested-docs-plugin): throw an error to the UI if children are not passing validation (#7977) 2024-08-30 02:48:38 +00:00
Paul
90b3e83fc2 feat(templates): update website src directory structure (#7971) 2024-08-30 02:41:02 +00:00
Elliot DeNolf
76dda13ca1 chore: significantly improve test suite eslint performance, lint and prettier everything (#7975)
Linting test/admin beta: > 3 minutes (stopped counting after 3 min)
Linting test/admin after my PR: 8s

Linting ui beta: 18s
Linting ui after my PR:  18s
2024-08-29 22:13:05 -04:00
Paul
e071382a79 fix(ui): upload with has many bulk select not working with postgres (#7976)
Fixes https://github.com/payloadcms/payload/issues/7921
2024-08-30 02:01:38 +00:00
Alessio Gravili
131f2def3c chore: undo lint changes to db-* and drizzle 2024-08-29 22:01:37 -04:00
Alessio Gravili
d97cd2fd5d chore: add lint commit to .git-blame-ignore-revs 2024-08-29 21:36:33 -04:00
Alessio Gravili
86fdad0bb8 chore: significantly improve eslint performance, lint and prettier everything 2024-08-29 21:25:50 -04:00
Alessio Gravili
bc367ab73c chore(eslint): upgrade to typescript-eslint v8, upgrade all eslint packages (#7082) 2024-08-29 16:27:58 -04:00
Jacob Fletcher
c0728220ff fix(ui): propagates sort change events through list query provider (#7968) 2024-08-29 13:18:05 -04:00
Jacob Fletcher
6893f404ac chore: removes duplicative loop over column state to determine linked cells (#7958) 2024-08-29 12:35:51 -04:00
Elliot DeNolf
2a8bd4c775 chore(release): v3.0.0-beta.96 [skip ci] 2024-08-29 11:25:10 -04:00
James Mikrut
ac10bad723 fix(db-postgres): nested localized arrays (#7962)
## Description

Fixes a bug with nested arrays within either localized blocks or arrays.
2024-08-29 15:01:53 +00:00
Elliot DeNolf
142616e6ad chore(eslint): curly [skip-lint] (#7959)
Now enforcing curly brackets on all if statements. Includes auto-fixer.


```ts
//  Bad
if (foo) foo++;

//  Good
if (foo) {
  foo++;
}
```




Note: this did not lint the `drizzle` package or any `db-*` packages.
This will be done in the future.
2024-08-29 10:15:36 -04:00
Jarrod Flesch
dd3d985091 chore: upload field style fix (#7942) 2024-08-29 00:11:56 -04:00
Jarrod Flesch
de3d7c95e7 fix: prevents duplicate active nav indicators (#7943) 2024-08-29 00:11:42 -04:00
Elliot DeNolf
570422ff9a chore(db-mongodb): adjust default exports (#7945) 2024-08-29 04:09:44 +00:00
Elliot DeNolf
53c41bdfd8 chore(cpa): unused vars (#7944) 2024-08-29 03:59:44 +00:00
Elliot DeNolf
e5c34ead16 chore: plugin lint fixes (#7947) 2024-08-29 03:59:16 +00:00
Elliot DeNolf
6e561b11ca chore(graphql): adjust default exports (#7946) 2024-08-29 03:51:03 +00:00
Jarrod Flesch
f7146362df chore: brings back tests from has many PR (#7917) 2024-08-28 22:44:58 -04:00
Elliot DeNolf
ec9d1cda2d chore(eslint): lint:fix on all packages (#7941)
- Run `lint:fix` on all packages to fix anything that may have slipped
through without being autofixed
- A few manual fixes as well.
2024-08-29 02:35:17 +00:00
Elliot DeNolf
657326b528 chore(eslint): remove unused .eslintignore files [skip ci] 2024-08-28 22:12:19 -04:00
James Mikrut
538b7ee616 feat!: auto-removes localized property from localized fields within other localized fields (#7933)
## Description

Payload localization works on a field-by-field basis. As you can nest
fields within other fields, you could potentially nest a localized field
within a localized field—but this would be redundant and unnecessary.
There would be no reason to define a localized field within a localized
parent field, given that the entire data structure from the parent field
onward would be localized.

Up until this point, Payload would _allow_ you to nest a localized field
within another localized field, and this might have worked in MongoDB
but it will throw errors in Postgres.

Now, Payload will automatically remove the `localized: true` property
from sub-fields within `sanitizeFields` if a parent field is localized.

This could potentially be a breaking change if you have a configuration
with MongoDB that nests localized fields within localized fields.

## Migrating

You probably only need to migrate if you are using MongoDB, as there,
you may not have noticed any problems. But in Postgres or SQLite, this
would have caused issues so it's unlikely that you've made it too far
without experiencing issues due to a nested localized fields config.

In the event you would like to keep existing data in this fashion, we
have added a `compatibility.allowLocalizedWithinLocalized` flag to the
Payload config, which you can set to `true`, and Payload will then
disable this new sanitization step.

Set this compatibility flag to `true` only if you have an existing
Payload MongoDB database from pre-3.0, and you have nested localized
fields that you would like to maintain without migrating.
2024-08-29 01:56:17 +00:00
Elliot DeNolf
828f5d866d build(scripts): add lint scripts to all, turbo lint tasks [skip ci] 2024-08-28 21:55:51 -04:00
Bruno Crosier
efe17ff5e4 fix(row): set max-width for row 2024-08-29 02:23:54 +01:00
Alessio Gravili
e375f6e727 feat: significantly reduce payload install size by removing unnecessary monaco-editor dependency (#7939)
This reduces the total install size of `payload` from 115 MB to 34 MB. 

We never used monaco-editor within payload - we were only using
`@monaco-editor/react` which is a lot smaller.

Since we expose its types to the end user, we have to add it to our
`dependencies`, not `devDependencies`.

Before:
https://npmgraph.js.org/?q=payload@3.0.0-canary.1a675ae#select=exact%3Apayload%403.0.0-canary.1a675ae&zoom=w

After:
https://npmgraph.js.org/?q=payload%403.0.0-canary.cdb9474#zoom=h&select=exact%3Apayload%403.0.0-canary.cdb9474
2024-08-28 23:24:42 +00:00
Paul
cc9b877e88 fix: improve validation errors for unique fields (#7937) 2024-08-28 16:10:54 -06:00
Alessio Gravili
dc12047723 feat: reduce package size and amount of dependencies by upgrading json-schema-to-typescript (#7938)
Closes https://github.com/payloadcms/payload/issues/7934
2024-08-28 21:59:32 +00:00
Jarrod Flesch
12fb691e4f chore: upload dropzone style changes (#7932) 2024-08-28 15:49:14 -04:00
Alessio Gravili
0962850086 fix: incorrect config.upload types (#7874)
Fixes https://github.com/payloadcms/payload/issues/7698

Now exporting `FetchAPIFileUploadOptions` from payload, as that type is
now used in `config.upload`.
2024-08-28 15:39:51 -04:00
Elliot DeNolf
78c8bb81a1 chore(release): v3.0.0-beta.95 [skip ci] 2024-08-28 14:49:15 -04:00
Elliot DeNolf
419b274bb1 chore: move ui and translations into deps from peerDeps (#7929)
Move `ui` and `translations` from peerDeps into deps for a few packages.
Users should not have to install these directly unless they are making
customizations.
2024-08-28 14:47:02 -04:00
Alessio Gravili
ef818fd5c8 fix(ui): admin.dependencies components not added to client config (#7928) 2024-08-28 13:56:52 -04:00
Germán Jabloñski
0aaf3af1ea fix(richtext-lexical): enabledCollections and disabledCollections props in RelationshipFeature (#7926)
fixes https://github.com/payloadcms/payload/issues/7379

The enabledCollections and disabledCollections properties of the
RelationshipFeature were not being sent to the client and therefore did
not have the expected effect.

Now those 2 properties are sent to the client via the
`clientFeatureProps` property.
2024-08-28 13:45:10 -04:00
James Mikrut
18b0806b5b fix(db-postgres, db-sqlite): hasMany text, number, poly relationship, blocks, arrays within localized fields (#7900)
## Description

In Postgres, localized blocks or arrays that contain other array / block
/ relationship fields were not properly storing locales in the database.

Now they are! Need to check a few things yet:

- Ensure test coverage is sufficient
- Test localized array, with non-localized array inside of it
- Test localized block with relationship field within it
- Ensure `_rels` table gets the `locale` column added if a single
non-localized relationship exists within a localized array / block

Fixes step 6 as identified in #7805
2024-08-28 17:43:12 +00:00
Jacob Fletcher
3d9051ad34 test: extracts reorderColumns e2e util for reuse (#7923) 2024-08-28 12:20:47 -04:00
Jarrod Flesch
e4ef47b938 chore(examples): multi tenant single domain fixes (#7922) 2024-08-28 11:34:22 -04:00
Alessio Gravili
c7e7dc71d3 fix(richtext-lexical): ensure html converter text is escaped (#7919) 2024-08-28 14:31:06 +00:00
Jarrod Flesch
375671c162 fix: active nav item set incorrectly in child routes (#7918) 2024-08-28 10:00:13 -04:00
Elliot DeNolf
23b495b145 build: update turborepo npm scripts (#7899)
Updating all turborepo npm scripts for this rather inconvenient breaking
change: https://github.com/vercel/turborepo/pull/8137
2024-08-27 22:04:05 -04:00
Paul
27d743e2a8 fix: depth not being respected by upload has many (#7897) 2024-08-28 00:50:29 +00:00
Elliot DeNolf
8c9ff3d54b revert(scripts): publish script progress prefix 2024-08-27 19:53:15 -04:00
Elliot DeNolf
5c447252e7 chore(release): v3.0.0-beta.94 [skip ci] 2024-08-27 19:47:37 -04:00
Jarrod Flesch
a76be81368 fix: upload has many field updates (#7894)
## Description

Implements fixes and style changes for the upload field component.

Fixes https://github.com/payloadcms/payload/issues/7819

![CleanShot 2024-08-27 at 16 22
33](https://github.com/user-attachments/assets/fa27251c-20b8-45ad-9109-55dee2e19e2f)

![CleanShot 2024-08-27 at 16 22
49](https://github.com/user-attachments/assets/de2d24f9-b2f5-4b72-abbe-24a6c56a4c21)


- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [ ] Chore (non-breaking change which does not add functionality)
- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Change to the
[templates](https://github.com/payloadcms/payload/tree/main/templates)
directory (does not affect core functionality)
- [ ] Change to the
[examples](https://github.com/payloadcms/payload/tree/main/examples)
directory (does not affect core functionality)
- [ ] This change requires a documentation update

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation

---------

Co-authored-by: Paul Popus <paul@nouance.io>
2024-08-27 19:07:18 -04:00
Paul
5d97d57e70 feat(templates): add new slug component to the website (#7895)
https://github.com/user-attachments/assets/1ba125d3-9c65-4bab-98df-fb80c70eeb71
2024-08-27 22:26:56 +00:00
Alessio Gravili
de7ff1f8c6 fix(richtext-lexical): html converters can populate relationships infinitely (#7890)
Fixes https://github.com/payloadcms/payload/issues/7743
2024-08-27 15:24:50 -04:00
James Mikrut
3d714d3e72 fix: locale selector + autosave race condition (#7891)
## Description

Fixes a race condition where you could switch locales and have autosave
trigger with old locale data.

By adding the `key` to the `Document` component, we will ensure that the
entire `Document` will be un-mounted and re-mounted between locale
switches.
2024-08-27 14:56:28 -04:00
Elliot DeNolf
2bbb02b9c0 chore(scripts): add package count to publish script [skip ci] 2024-08-27 14:41:47 -04:00
Elliot DeNolf
0533e7f5db chore(release): v3.0.0-beta.93 [skip ci] 2024-08-27 14:16:30 -04:00
Paul
23c5ef428d fix: bugs in versions UI with perPage, pagination and diffs not showing for tabs. publish button no longer double saves when using keyboard (#7889)
Fixes:
- issue with publish button double saving on keyboard command
- versions diffs not showing if fields are tabs
https://github.com/payloadcms/payload/issues/7860
- navigation on versions not working for perPage and pagination
2024-08-27 17:45:30 +00:00
Alessio Gravili
f046a04510 fix(richtext-lexical): dependency checker suggesting incorrect version (#7888)
Fixes this:
https://discord.com/channels/967097582721572934/1278031296970625190/1278031652089757818
2024-08-27 17:17:19 +00:00
Elliot DeNolf
4cda7d2363 chore(release): v3.0.0-beta.92 [skip ci] 2024-08-27 09:44:02 -04:00
Elliot DeNolf
ea48cfbfe9 feat: implement info command (#7882)
Implements `info` command similar to Next.js.

`pnpm payload info` will output info in this format:

```
Binaries:
  Node: 18.20.2
  npm: 10.5.0
  Yarn: 1.22.19
  pnpm: 9.7.0
Relevant Packages:
  payload: 3.0.0-beta.91
  next: 15.0.0-canary.104
  @payloadcms/db-mongodb: 3.0.0-beta.91
  @payloadcms/db-postgres: 3.0.0-beta.91
  @payloadcms/email-nodemailer: 3.0.0-beta.91
  @payloadcms/graphql: 3.0.0-beta.91
  @payloadcms/next/utilities: 3.0.0-beta.91
  @payloadcms/plugin-cloud: 3.0.0-beta.91
  @payloadcms/richtext-lexical: 3.0.0-beta.91
  @payloadcms/richtext-slate: 3.0.0-beta.91
  @payloadcms/translations: 3.0.0-beta.91
  @payloadcms/ui/shared: 3.0.0-beta.91
  react: 19.0.0-rc-06d0b89e-20240801
  react-dom: 19.0.0-rc-06d0b89e-20240801
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0: Mon Jul 29 21:13:04 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T6020
  Available memory (MB): 32768
  Available CPU cores: 12
 ```
2024-08-27 01:41:39 +00:00
Paul
1aeb912762 fix(templates): website live preview and code block (#7881) 2024-08-26 23:42:45 +00:00
Paul
ce2cb35d71 fix(plugin-seo): remove dependency on import from payload/next package (#7879) 2024-08-26 22:40:38 +00:00
Paul
d3ec68ac2f fix: error when closing the live preview popup window (#7878) 2024-08-26 22:33:56 +00:00
Paul
05bf52aac3 fix(templates): website bug fixes for slug generation and form builder and adds support for strictNullChecks: true (#7877) 2024-08-26 22:10:09 +00:00
Jacob Fletcher
fed7f2fa5b fix: sanitizes modifyResponseHeaders from client config (#7876) 2024-08-26 21:50:29 +00:00
James Mikrut
686b0865b2 fix: exports richtext-slate useSlatePlugin (#7875)
## Description

exports `useSlatePlugin` for `richtext-slate`
2024-08-26 17:21:25 -04:00
Alessio Gravili
dfb4c8eb4c feat: add nextjs and react version checks to dependency checker (#7868) 2024-08-26 17:19:14 -04:00
Alessio Gravili
ad7a387e19 feat(richtext-lexical): more lenient url validation, URL-encode invalid urls on save (#7870)
Fixes https://github.com/payloadcms/payload/issues/7477

This simplifies validation to the point where it only errors on spaces.
Actual validation is then used in beforeChange, which then automatically
url encodes the input if it doesn't pass
2024-08-26 15:33:29 -04:00
Elliot DeNolf
d05be016ce ci(scripts): emoji release notes 2024-08-23 16:25:50 -04:00
1543 changed files with 115991 additions and 21848 deletions

View File

@@ -22,3 +22,9 @@ fb7d1be2f3325d076b7c967b1730afcef37922c2
# 3.0 prettier & lint everywhere again
83fd4c66222d7846eeb5cc332dfa99bf1e830831
# Upgrade to typescript-eslint v8, then prettier & lint everywhere
86fdad0bb8ab27810599c8a32f3d8cba1341e1df
# Prettier and lint remaining db packages
7fd736ea5b2e9fc4ef936e9dc9e5e3d722f6d8bf

View File

@@ -0,0 +1,13 @@
module.exports = {
env: {
es6: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
}

View File

@@ -0,0 +1,74 @@
# Release Commenter
This GitHub Action automatically comments on and/or labels Issues and PRs when a fix is released for them.
> [!IMPORTANT]
> 🔧 Heavily modified version of https://github.com/apexskier/github-release-commenter
## Fork Modifications
- Filters to closed PRs only
- Adds tag filter to support non-linear releases
- Better logging
- Moved to pnpm
- Uses @vercel/ncc for packaging
- Comments on locked issues by unlocking then re-locking
## How it works
Use this action in a workflow [triggered by a release](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#release). It will scan commits between that and the prior release, find associated Issues and PRs, and comment on them to let people know a release has been made. Associated Issues and PRs can be directly [linked](https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) to the commit or manually linked from a PR associated with the commit.
## Inputs
**GITHUB_TOKEN**
A GitHub personal access token with repo scope, such as [`secrets.GITHUB_TOKEN`](https://docs.github.com/en/free-pro-team@latest/actions/reference/authentication-in-a-workflow#about-the-github_token-secret).
**comment-template** (optional)
Override the comment posted on Issues and PRs. Set to the empty string to disable commenting. Several variables strings will be automatically replaced:
- `{release_link}` - a markdown link to the release
- `{release_name}` - the release's name
- `{release_tag}` - the release's tag
**label-template** (optional)
Add the given label. Multiple labels can be separated by commas. Several variable strings will be automatically replaced:
- `{release_name}` - the release's name
- `{release_tag}` - the release's tag
**skip-label** (optional)
Skip processing if any of the given labels are present. Same processing rules as **label-template**. Default is "dependencies".
## Example
```yml
on:
release:
types: [published]
jobs:
release:
steps:
- uses: apexskier/github-release-commenter@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
comment-template: |
Release {release_link} addresses this.
```
## Known limitations
These are some known limitations of this action. I'd like to try to address them in the future.
- Non-linear releases aren't supported. For example, releasing a patch to a prior major release after a new major release has been bumped.
- Non-sequential releases aren't supported. For example, if you release multiple prereleases between two official releases, this will only create a comment for the first prerelease in which a fix is released, not the final release.
- The first release for a project will be ignored. This is intentional, as the use case is unlikely. Most projects will either have several alphas that don't need release comments, or won't use issues/PRs for the first commit.
- If a large number of things are commented on, you may see the error `Error: You have triggered an abuse detection mechanism. Please wait a few minutes before you try again.`. Consider using the `skip-label` input to reduce your load on the GitHub API.
## Versions
Workflows will automatically update the tags `v1` and `latest`, allowing you to reference one of those instead of locking to a specific release.

View File

@@ -0,0 +1,32 @@
name: Release Commenter
description: Comment on PRs and Issues when a fix is released
branding:
icon: message-square
color: blue
inputs:
GITHUB_TOKEN:
description: |
A GitHub personal access token with repo scope, such as
secrets.GITHUB_TOKEN.
required: true
comment-template:
description: |
Text template for the comment string.
required: false
default: |
Included in release {release_link}
label-template:
description: Add the given label. Multiple labels can be separated by commas.
required: false
skip-label:
description: Skip commenting if any of the given label are present. Multiple labels can be separated by commas.
required: false
default: 'dependencies'
tag-filter:
description: |
Filter tags by a regular expression. Must be escaped. e.g. 'v\\d' to isolate tags between major versions.
required: false
default: null
runs:
using: node20
main: dist/index.js

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
module.exports = {
testEnvironment: 'node',
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/dist/'],
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest'],
},
}

View File

@@ -0,0 +1,34 @@
{
"name": "release-commenter",
"version": "0.0.0",
"private": true,
"description": "GitHub Action to automatically comment on PRs and Issues when a fix is released.",
"license": "MIT",
"main": "dist/index.js",
"scripts": {
"build": "pnpm build:typecheck && pnpm build:ncc",
"build:ncc": "ncc build src/index.ts -t -o dist",
"build:typecheck": "tsc",
"clean": "rimraf dist",
"test": "jest"
},
"dependencies": {
"@actions/core": "^1.3.0",
"@actions/github": "^5.0.0"
},
"devDependencies": {
"@octokit/webhooks-types": "^7.5.1",
"@swc/jest": "^0.2.36",
"@types/jest": "^27.5.2",
"@types/node": "^20.16.5",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vercel/ncc": "0.38.1",
"concurrently": "^8.2.2",
"eslint": "^7.32.0",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"ts-jest": "^26.5.6",
"typescript": "^4.9.5"
}
}

5419
.github/actions/release-commenter/pnpm-lock.yaml generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,266 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`tests feature tests can apply labels 1`] = `
[
[
{
"issue_number": 123,
"labels": [
":dart: landed",
"release-current_tag_name",
"Release Name",
],
},
],
[
{
"issue_number": 7,
"labels": [
":dart: landed",
"release-current_tag_name",
"Release Name",
],
},
],
]
`;
exports[`tests main test 1`] = `
{
"graphql": [MockFunction] {
"calls": [
[
"
{
resource(url: "http://repository/commit/SHA1") {
... on Commit {
messageHeadlineHTML
messageBodyHTML
associatedPullRequests(first: 10) {
pageInfo {
hasNextPage
}
edges {
node {
bodyHTML
number
state
labels(first: 10) {
pageInfo {
hasNextPage
}
nodes {
name
}
}
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
pageInfo {
hasNextPage
}
nodes {
... on ConnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
... on DisconnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
}
}
}
}
}
}
}
}
",
],
[
"
{
resource(url: "http://repository/commit/SHA2") {
... on Commit {
messageHeadlineHTML
messageBodyHTML
associatedPullRequests(first: 10) {
pageInfo {
hasNextPage
}
edges {
node {
bodyHTML
number
state
labels(first: 10) {
pageInfo {
hasNextPage
}
nodes {
name
}
}
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
pageInfo {
hasNextPage
}
nodes {
... on ConnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
... on DisconnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
}
}
}
}
}
}
}
}
",
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
],
},
"rest": {
"issues": {
"addLabels": [MockFunction],
"createComment": [MockFunction] {
"calls": [
[
{
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
"issue_number": 3,
},
],
[
{
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
"issue_number": 123,
},
],
[
{
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
"issue_number": 7,
},
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
],
},
"get": [MockFunction] {
"calls": [
[
{
"issue_number": 3,
},
],
[
{
"issue_number": 123,
},
],
[
{
"issue_number": 7,
},
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
],
},
},
"repos": {
"compareCommits": [MockFunction] {
"calls": [
[
{
"base": "prior_tag_name",
"head": "current_tag_name",
},
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
],
},
"listReleases": [MockFunction] {
"calls": [
[
{
"per_page": 100,
},
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
],
},
},
},
}
`;

View File

@@ -0,0 +1,399 @@
import type * as githubModule from '@actions/github'
import type * as coreModule from '@actions/core'
import { mock } from 'node:test'
jest.mock('@actions/core')
jest.mock('@actions/github')
type Mocked<T> = {
-readonly [P in keyof T]: T[P] extends Function ? jest.Mock<T[P]> : jest.Mocked<Partial<T[P]>>
}
const github = require('@actions/github') as jest.Mocked<Mocked<typeof githubModule>>
const core = require('@actions/core') as jest.Mocked<Mocked<typeof coreModule>>
describe('tests', () => {
let mockOctokit: any = {}
let currentTag: string = 'current_tag_name'
;(core.warning as any) = jest.fn(console.warn.bind(console))
;(core.error as any) = jest.fn(console.error.bind(console))
let commentTempate: string = ''
let labelTemplate: string | null = null
const skipLabelTemplate: string | null = 'skip,test'
let tagFilter: string | RegExp | null = null
let simpleMockOctokit: any = {}
beforeEach(() => {
tagFilter = null
currentTag = 'current_tag_name'
;(github.context as any) = {
payload: {
repo: {
owner: 'owner',
repo: 'repo',
},
release: {
tag_name: currentTag,
},
repository: { html_url: 'http://repository' },
},
}
github.getOctokit.mockReset().mockImplementationOnce(((token: string) => {
expect(token).toBe('GITHUB_TOKEN_VALUE')
return mockOctokit
}) as any)
;(core.getInput as any).mockImplementation((key: string) => {
if (key == 'GITHUB_TOKEN') {
return 'GITHUB_TOKEN_VALUE'
}
if (key == 'comment-template') {
return commentTempate
}
if (key == 'label-template') {
return labelTemplate
}
if (key == 'skip-label') {
return skipLabelTemplate
}
if (key == 'tag-filter') {
return tagFilter
}
fail(`Unexpected input key ${key}`)
})
commentTempate =
'Included in release {release_link}. Replacements: {release_name}, {release_tag}.'
labelTemplate = null
simpleMockOctokit = {
rest: {
issues: {
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
createComment: jest.fn(() => Promise.resolve()),
addLabels: jest.fn(() => Promise.resolve()),
},
repos: {
listReleases: jest.fn(() =>
Promise.resolve({
data: [
{
name: 'Release Name',
tag_name: 'current_tag_name',
html_url: 'http://current_release',
},
{
tag_name: 'prior_tag_name',
html_url: 'http://prior_release',
},
],
}),
),
compareCommits: jest.fn(() =>
Promise.resolve({
data: { commits: [{ sha: 'SHA1' }] },
}),
),
},
},
graphql: jest.fn(() =>
Promise.resolve({
resource: {
messageHeadlineHTML: '',
messageBodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
associatedPullRequests: {
pageInfo: { hasNextPage: false },
edges: [],
},
},
}),
),
}
})
afterEach(() => {
expect(core.error).not.toHaveBeenCalled()
expect(core.warning).not.toHaveBeenCalled()
expect(core.setFailed).not.toHaveBeenCalled()
})
test('main test', async () => {
mockOctokit = {
...simpleMockOctokit,
rest: {
issues: {
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
createComment: jest.fn(() => Promise.resolve()),
addLabels: jest.fn(() => Promise.resolve()),
},
repos: {
listReleases: jest.fn(() =>
Promise.resolve({
data: [
{
tag_name: 'current_tag_name',
html_url: 'http://current_release',
},
{
tag_name: 'prior_tag_name',
html_url: 'http://prior_release',
},
],
}),
),
compareCommits: jest.fn(() =>
Promise.resolve({
data: { commits: [{ sha: 'SHA1' }, { sha: 'SHA2' }] },
}),
),
},
},
graphql: jest.fn(() =>
Promise.resolve({
resource: {
messageHeadlineHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #3.">Closes</span> <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="718013420" data-permission-text="Title is private" data-url="https://github.com/apexskier/github-release-commenter/issues/1" data-hovercard-type="issue" data-hovercard-url="/apexskier/github-release-commenter/issues/1/hovercard" href="https://github.com/apexskier/github-release-commenter/issues/1">#1</a>',
messageBodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
associatedPullRequests: {
pageInfo: { hasNextPage: false },
edges: [
{
node: {
bodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #4.">Closes</span> <span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #5.">Closes</span>',
number: 9,
labels: {
pageInfo: { hasNextPage: false },
nodes: [{ name: 'label1' }, { name: 'label2' }],
},
timelineItems: {
pageInfo: { hasNextPage: false },
nodes: [
{
isCrossRepository: true,
__typename: 'ConnectedEvent',
subject: { number: 1 },
},
{
isCrossRepository: false,
__typename: 'ConnectedEvent',
subject: { number: 2 },
},
{
isCrossRepository: false,
__typename: 'DisconnectedEvent',
subject: { number: 2 },
},
{
isCrossRepository: false,
__typename: 'ConnectedEvent',
subject: { number: 2 },
},
],
},
},
},
{
node: {
bodyHTML: '',
number: 42,
labels: {
pageInfo: { hasNextPage: false },
nodes: [{ name: 'label1' }, { name: 'skip' }],
},
timelineItems: {
pageInfo: { hasNextPage: false },
nodes: [
{
isCrossRepository: true,
__typename: 'ConnectedEvent',
subject: { number: 82 },
},
],
},
},
},
],
},
},
}),
),
}
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>(setImmediate)
expect(mockOctokit).toMatchSnapshot()
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledTimes(3)
})
describe('can filter tags', () => {
const v3prev = 'v3.0.1'
const v3current = 'v3.0.2'
const v2prev = 'v2.0.1'
const v2current = 'v2.0.2'
const listReleasesData = [
{
name: 'Current Release Name',
tag_name: v3current,
html_url: 'http://v3.0.2',
},
{
name: 'Prev Release Name',
tag_name: v3prev,
html_url: 'http://v3.0.1',
},
{
name: 'v2 Current Release Name',
tag_name: v2current,
html_url: 'http://v2.0.2',
},
{
name: 'v2 Prev Release Name',
tag_name: v2prev,
html_url: 'http://v2.0.1',
},
]
it.each`
description | prevTag | currentTag | filter
${'no filter'} | ${v3prev} | ${v3current} | ${null}
${'v3'} | ${v3prev} | ${v3current} | ${'v\\d'}
${'v2'} | ${v2prev} | ${v2current} | ${'v\\d'}
`('should filter tags with $description', async ({ prevTag, currentTag, filter }) => {
// @ts-ignore
github.context.payload.release.tag_name = currentTag
tagFilter = filter
mockOctokit = {
...simpleMockOctokit,
rest: {
issues: {
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
createComment: jest.fn(() => Promise.resolve()),
addLabels: jest.fn(() => Promise.resolve()),
},
repos: {
listReleases: jest.fn(() =>
Promise.resolve({
data: listReleasesData,
}),
),
compareCommits: jest.fn(() =>
Promise.resolve({
data: { commits: [{ sha: 'SHA1' }] },
}),
),
},
},
graphql: jest.fn(() =>
Promise.resolve({
resource: {
messageHeadlineHTML: '',
messageBodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
associatedPullRequests: {
pageInfo: { hasNextPage: false },
edges: [],
},
},
}),
),
}
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>((resolve) => setImmediate(() => resolve()))
expect(github.getOctokit).toHaveBeenCalled()
expect(mockOctokit.rest.repos.compareCommits.mock.calls).toEqual([
[{ base: prevTag, head: currentTag }],
])
})
})
describe('feature tests', () => {
beforeEach(() => {
mockOctokit = simpleMockOctokit
})
it('can disable comments', async () => {
commentTempate = ''
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>((resolve) => setImmediate(() => resolve()))
expect(github.getOctokit).toHaveBeenCalled()
expect(mockOctokit.rest.issues.createComment).not.toHaveBeenCalled()
})
it('should unlock and comment', async () => {
mockOctokit = {
...simpleMockOctokit,
rest: {
...simpleMockOctokit.rest,
issues: {
// Return locked for both issues to be commented on
get: jest.fn(() => Promise.resolve({ data: { locked: true } })),
lock: jest.fn(() => Promise.resolve()),
unlock: jest.fn(() => Promise.resolve()),
createComment: jest.fn(() => Promise.resolve()),
},
},
graphql: jest.fn(() =>
Promise.resolve({
resource: {
messageHeadlineHTML: '',
messageBodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
associatedPullRequests: {
pageInfo: { hasNextPage: false },
edges: [],
},
},
}),
),
}
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>((resolve) => setImmediate(() => resolve()))
expect(github.getOctokit).toHaveBeenCalled()
// Should call once for both linked issues
expect(mockOctokit.rest.issues.unlock).toHaveBeenCalledTimes(2)
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledTimes(2)
expect(mockOctokit.rest.issues.lock).toHaveBeenCalledTimes(2)
})
it.skip('can apply labels', async () => {
labelTemplate = ':dart: landed,release-{release_tag},{release_name}'
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>((resolve) => setImmediate(() => resolve()))
expect(github.getOctokit).toHaveBeenCalled()
expect(mockOctokit.rest.issues.addLabels.mock.calls).toMatchSnapshot()
})
})
})

View File

@@ -0,0 +1,349 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import type * as Webhooks from '@octokit/webhooks-types'
const closesMatcher = /aria-label="This (?:commit|pull request) closes issue #(\d+)\."/g
const releaseLinkTemplateRegex = /{release_link}/g
const releaseNameTemplateRegex = /{release_name}/g
const releaseTagTemplateRegex = /{release_tag}/g
;(async function main() {
try {
const payload = github.context.payload as Webhooks.EventPayloadMap['release']
const githubToken = core.getInput('GITHUB_TOKEN')
const tagFilter = core.getInput('tag-filter') || undefined // Accept tag filter as an input
const octokit = github.getOctokit(githubToken)
const commentTemplate = core.getInput('comment-template')
const labelTemplate = core.getInput('label-template') || null
const skipLabelTemplate = core.getInput('skip-label') || null
// Fetch the releases with the optional tag filter applied
const { data: rawReleases } = await octokit.rest.repos.listReleases({
...github.context.repo,
per_page: 100,
})
// Get the current release tag or latest tag
const currentTag = payload?.release?.tag_name || rawReleases?.[0]?.tag_name
let releases = rawReleases
// Filter releases by the tag filter if provided
if (tagFilter) {
core.info(`Filtering releases by tag filter: ${tagFilter}`)
// Get the matching part of the current release tag
const regexMatch = currentTag.match(tagFilter)?.[0]
if (!regexMatch) {
core.error(`Current release tag ${currentTag} does not match the tag filter ${tagFilter}`)
return
}
core.info(`Matched string from filter: ${regexMatch}`)
releases = releases
.filter((release) => {
const match = release.tag_name.match(regexMatch)?.[0]
return match
})
.slice(0, 2)
}
core.info(`Releases: ${JSON.stringify(releases, null, 2)}`)
if (releases.length < 2) {
if (!releases.length) {
core.error(`No releases found with the provided tag filter: '${tagFilter}'`)
return
}
core.info('first release')
return
}
const [currentRelease, priorRelease] = releases
core.info(`${priorRelease.tag_name}...${currentRelease.tag_name}`)
const {
data: { commits },
} = await octokit.rest.repos.compareCommits({
...github.context.repo,
base: priorRelease.tag_name,
head: currentRelease.tag_name,
})
if (!currentRelease.name) {
core.info('Current release has no name, will fall back to the tag name.')
}
const releaseLabel = currentRelease.name || currentRelease.tag_name
const comment = commentTemplate
.trim()
.split(releaseLinkTemplateRegex)
.join(`[${releaseLabel}](${currentRelease.html_url})`)
.split(releaseNameTemplateRegex)
.join(releaseLabel)
.split(releaseTagTemplateRegex)
.join(currentRelease.tag_name)
const parseLabels = (rawInput: string | null) =>
rawInput
?.split(releaseNameTemplateRegex)
.join(releaseLabel)
?.split(releaseTagTemplateRegex)
.join(currentRelease.tag_name)
?.split(',')
?.map((l) => l.trim())
.filter((l) => l)
const labels = parseLabels(labelTemplate)
const skipLabels = parseLabels(skipLabelTemplate)
const linkedIssuesPrs = new Set<number>()
await Promise.all(
commits.map((commit) =>
(async () => {
const query = `
{
resource(url: "${payload.repository.html_url}/commit/${commit.sha}") {
... on Commit {
messageHeadlineHTML
messageBodyHTML
associatedPullRequests(first: 10) {
pageInfo {
hasNextPage
}
edges {
node {
bodyHTML
number
state
labels(first: 10) {
pageInfo {
hasNextPage
}
nodes {
name
}
}
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
pageInfo {
hasNextPage
}
nodes {
... on ConnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
... on DisconnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
}
}
}
}
}
}
}
}
`
const response: {
resource: null | {
messageHeadlineHTML: string
messageBodyHTML: string
associatedPullRequests: {
pageInfo: { hasNextPage: boolean }
edges: ReadonlyArray<{
node: {
bodyHTML: string
number: number
state: 'OPEN' | 'CLOSED' | 'MERGED'
labels: {
pageInfo: { hasNextPage: boolean }
nodes: ReadonlyArray<{
name: string
}>
}
timelineItems: {
pageInfo: { hasNextPage: boolean }
nodes: ReadonlyArray<{
__typename: 'ConnectedEvent' | 'DisconnectedEvent'
isCrossRepository: boolean
subject: {
number: number
}
}>
}
}
}>
}
}
} = await octokit.graphql(query)
if (!response.resource) {
return
}
// core.info(JSON.stringify(response.resource, null, 2))
core.info(`Checking commit: ${payload.repository.html_url}/commit/${commit.sha}`)
const associatedClosedPREdges = response.resource.associatedPullRequests.edges.filter(
(e) => e.node.state === 'MERGED',
)
if (associatedClosedPREdges.length) {
core.info(
` Associated Merged PRs:\n ${associatedClosedPREdges.map((pr) => `${payload.repository.html_url}/pull/${pr.node.number}`).join('\n ')}`,
)
} else {
core.info(' No associated merged PRs')
}
const html = [
response.resource.messageHeadlineHTML,
response.resource.messageBodyHTML,
...associatedClosedPREdges.map((pr) => pr.node.bodyHTML),
].join(' ')
for (const match of html.matchAll(closesMatcher)) {
const [, num] = match
linkedIssuesPrs.add(parseInt(num, 10))
core.info(
` Linked issue/PR from closesMatcher: ${payload.repository.html_url}/pull/${num}`,
)
}
if (response.resource.associatedPullRequests.pageInfo.hasNextPage) {
core.warning(`Too many PRs associated with ${commit.sha}`)
}
const seen = new Set<number>()
for (const associatedPR of associatedClosedPREdges) {
if (associatedPR.node.timelineItems.pageInfo.hasNextPage) {
core.warning(`Too many links for #${associatedPR.node.number}`)
}
if (associatedPR.node.labels.pageInfo.hasNextPage) {
core.warning(`Too many labels for #${associatedPR.node.number}`)
}
// a skip labels is present on this PR
if (
skipLabels?.some((l) => associatedPR.node.labels.nodes.some(({ name }) => name === l))
) {
continue
}
linkedIssuesPrs.add(associatedPR.node.number)
core.info(
` Linked issue/PR from associated PR: ${payload.repository.html_url}/pull/${associatedPR.node.number}`,
)
// These are sorted by creation date in ascending order. The latest event for a given issue/PR is all we need
// ignore links that aren't part of this repo
const links = associatedPR.node.timelineItems.nodes
.filter((node) => !node.isCrossRepository)
.reverse()
for (const link of links) {
if (seen.has(link.subject.number)) {
continue
}
if (link.__typename == 'ConnectedEvent') {
linkedIssuesPrs.add(link.subject.number)
core.info(
`Linked issue/PR from connected event: ${payload.repository.html_url}/pull/${link.subject.number}`,
)
}
seen.add(link.subject.number)
}
}
})(),
),
)
core.info(
`Final issues/PRs to be commented on: \n${Array.from(linkedIssuesPrs)
.map((num) => ` ${payload.repository.html_url}/pull/${num}`)
.join('\n')}`,
)
const requests: Array<Promise<unknown>> = []
for (const issueNumber of linkedIssuesPrs) {
const baseRequest = {
...github.context.repo,
issue_number: issueNumber,
}
if (comment) {
const commentRequest = {
...baseRequest,
body: comment,
}
// Check if issue is locked or not
const { data: issue } = await octokit.rest.issues.get(baseRequest)
let createCommentPromise: () => Promise<void>
if (!issue.locked) {
createCommentPromise = async () => {
try {
await octokit.rest.issues.createComment(commentRequest)
} catch (error) {
core.error(error as Error)
core.error(
`Failed to comment on issue/PR: ${issueNumber}. ${payload.repository.html_url}/pull/${issueNumber}`,
)
}
}
} else {
core.info(
`Issue/PR is locked: ${issueNumber}. Unlocking, commenting, and re-locking. ${payload.repository.html_url}/pull/${issueNumber}`,
)
createCommentPromise = async () => {
try {
core.debug(`Unlocking issue/PR: ${issueNumber}`)
await octokit.rest.issues.unlock(baseRequest)
core.debug(`Commenting on issue/PR: ${issueNumber}`)
await octokit.rest.issues.createComment(commentRequest)
core.debug(`Re-locking issue/PR: ${issueNumber}`)
await octokit.rest.issues.lock(baseRequest)
} catch (error) {
core.error(error as Error)
core.error(
`Failed to unlock, comment, and re-lock issue/PR: ${issueNumber}. ${payload.repository.html_url}/pull/${issueNumber}`,
)
}
}
}
requests.push(createCommentPromise())
}
if (labels) {
const request = {
...baseRequest,
labels,
}
// core.info(JSON.stringify(request, null, 2))
requests.push(octokit.rest.issues.addLabels(request))
}
}
await Promise.all(requests)
} catch (error) {
core.error(error as Error)
core.setFailed((error as Error).message)
}
})()

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es2020.string"],
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"downlevelIteration": true,
"skipLibCheck": true,
},
"exclude": ["src/**/*.test.ts"]
}

View File

@@ -1,48 +0,0 @@
name: Setup node and pnpm
description: Configure the Node.js and pnpm versions
inputs:
node-version:
description: 'The Node.js version to use'
required: true
default: 18.20.2
pnpm-version:
description: 'The pnpm version to use'
required: true
default: 9.7.0
runs:
using: composite
steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
shell: bash
run: sudo ethtool -K eth0 tx off rx off
- name: Setup Node@${{ inputs.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ inputs.pnpm-version }}
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
pnpm-store-
pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- shell: bash
run: pnpm install

3927
.github/pnpm-lock.yaml generated vendored Normal file

File diff suppressed because it is too large Load Diff

2
.github/pnpm-workspace.yaml vendored Normal file
View File

@@ -0,0 +1,2 @@
packages:
- 'actions/*'

View File

@@ -17,7 +17,7 @@ concurrency:
cancel-in-progress: true
env:
NODE_VERSION: 18.20.2
NODE_VERSION: 22.6.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry

31
.github/workflows/post-release.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: post-release
on:
release:
types:
- published
workflow_dispatch:
jobs:
post_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Only needed if debugging on a branch other than default
# ref: ${{ github.event.release.target_commitish || github.ref }}
- run: echo "npm_version=$(npm pkg get version | tr -d '"')" >> "$GITHUB_ENV"
- uses: ./.github/actions/release-commenter
continue-on-error: true
env:
ACTIONS_STEP_DEBUG: true
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag-filter: 'v\d'
# Change to blank to disable commenting
# comment-template: ''
comment-template: |
🚀 This is included in version {release_link}

View File

@@ -39,6 +39,7 @@ jobs:
db-mongodb
db-postgres
db-sqlite
drizzle
email-nodemailer
eslint
graphql
@@ -106,14 +107,15 @@ jobs:
label-pr-on-open:
name: label-pr-on-open
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- name: Tag with main branch with v2
if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'main'
if: github.event.pull_request.base.ref == 'main'
uses: actions-ecosystem/action-add-labels@v1
with:
labels: v2
- name: Tag with beta branch with v3
if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'beta'
if: github.event.pull_request.base.ref == 'beta'
uses: actions-ecosystem/action-add-labels@v1
with:
labels: v3

View File

@@ -6,7 +6,7 @@ on:
- beta
env:
NODE_VERSION: 18.20.2
NODE_VERSION: 22.6.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry

3
.gitignore vendored
View File

@@ -5,6 +5,9 @@ dist
!/.idea/runConfigurations
!/.idea/payload.iml
# Custom actions
!.github/actions/**/dist
test/packed
test-results
.devcontainer

View File

@@ -1 +1 @@
v18.20.2
v22.6.0

2
.nvmrc
View File

@@ -1 +1 @@
v18.20.2
v22.6.0

2
.tool-versions Normal file
View File

@@ -0,0 +1,2 @@
pnpm 9.7.1
nodejs 22.6.0

11
.vscode/settings.json vendored
View File

@@ -31,8 +31,15 @@
"editor.formatOnSave": true
},
"editor.formatOnSaveMode": "file",
// All ESLint rules to 'warn' to differentate from TypeScript's 'error' level
"eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }],
"eslint.rules.customizations": [
// Defaultt all ESLint errors to 'warn' to differentate from TypeScript's 'error' level
{ "rule": "*", "severity": "warn" },
// Silence some warnings that will get auto-fixed
{ "rule": "perfectionist/*", "severity": "off", "fixable": true },
{ "rule": "curly", "severity": "off", "fixable": true },
{ "rule": "object-shorthand", "severity": "off", "fixable": true }
],
"typescript.tsdk": "node_modules/typescript/lib",
// Load .git-blame-ignore-revs file
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"],

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
<a href="https://payloadcms.com"><img width="100%" src="https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/assets/images/github-banner-alt.jpg?raw=true" alt="Payload headless CMS Admin panel built with React" /></a>
<br />
<br />
<p align="left">
<a href="https://github.com/payloadcms/payload/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/payloadcms/payload/main.yml?style=flat-square"></a>
&nbsp;

View File

@@ -196,6 +196,48 @@ import { MyFieldComponent } from 'my-external-package/client'
which is a valid way to access MyFieldComponent that can be resolved by the consuming project.
### Custom Components from unknown locations
By default, any component paths from known locations are added to the import map. However, if you need to add any components from unknown locations to the import map, you can do so by adding them to the `admin.dependencies` array in your Payload Config. This is mostly only relevant for plugin authors and not for regular Payload users.
Example:
```ts
export default {
// ...
admin: {
// ...
dependencies: {
myTestComponent: { // myTestComponent is the key - can be anything
path: '/components/TestComponent.js#TestComponent',
type: 'component',
clientProps: {
test: 'hello',
},
},
},
}
}
```
This way, `TestComponent` is added to the import map, no matter if it's referenced in a known location or not. On the client, you can then use the component like this:
```tsx
'use client'
import { RenderComponent, useConfig } from '@payloadcms/ui'
import React from 'react'
export const CustomView = () => {
const { config } = useConfig()
return (
<div>
<RenderComponent mappedComponent={config.admin.dependencies?.myTestComponent} />
</div>
)
}
```
## Root Components
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
@@ -234,7 +276,8 @@ The following options are available:
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
| **`providers`** | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](#custom-providers). |
| **`actions`** | An array of Custom Components to be rendered in the header of the Admin Panel, providing additional interactivity and functionality. |
| **`actions`** | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. |
| **`header`** | An array of Custom Components to be injected above the Payload header. |
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
<Banner type="success">
@@ -389,14 +432,14 @@ export const MyClientComponent: React.FC = () => {
See [Using Hooks](#using-hooks) for more details.
</Banner>
All [Field Components](./fields) automatically receive their respective Client Field Config through a common [`field`](./fields#the-field-prop) prop:
All [Field Components](./fields) automatically receive their respective Field Config through a common [`field`](./fields#the-field-prop) prop:
```tsx
'use client'
import React from 'react'
import type { TextFieldProps } from 'payload'
import type { TextFieldClientComponent } from 'payload'
export const MyClientFieldComponent: TextFieldProps = ({ field: { name } }) => {
export const MyClientFieldComponent: TextFieldClientComponent = ({ field: { name } }) => {
return (
<p>
{`This field's name is ${name}`}

View File

@@ -136,7 +136,8 @@ All Field Components receive the following props:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document.
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
@@ -175,51 +176,50 @@ export const CustomTextField: React.FC = () => {
#### TypeScript
When building Custom Field Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview). The convention is to append `Props` to the type of field, i.e. `TextFieldProps`.
When building Custom Field Components, you can import the component type to ensure type safety. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and for every client/server environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
ArrayFieldProps,
BlocksFieldProps,
CheckboxFieldProps,
CodeFieldProps,
CollapsibleFieldProps,
DateFieldProps,
EmailFieldProps,
GroupFieldProps,
HiddenFieldProps,
JSONFieldProps,
NumberFieldProps,
PointFieldProps,
RadioFieldProps,
RelationshipFieldProps,
RichTextFieldProps,
RowFieldProps,
SelectFieldProps,
TabsFieldProps,
TextFieldProps,
TextareaFieldProps,
UploadFieldProps
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
### The `field` Prop
All Field Components are passed a client-friendly version of their Field Config through a common `field` prop. Since the raw Field Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types), Payload sanitized it into a [Client Config](./components#accessing-the-payload-config) that is safe to pass into Client Components.
All Field Components are passed their own Field Config through a common `field` prop. Within Server Components, this is the original Field Config as written within your Payload Config. Within Client Components, however, this is a "Client Config", which is a sanitized, client-friendly version of the Field Config. This is because the original Field Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types), meaning it cannot be passed into Client Components without first being transformed.
The exact shape of this prop is unique to the specific [Field Type](../fields/overview) being rendered, minus all non-serializable properties. Any [Custom Components](../components) are also resolved into a "mapped component" that is safe to pass.
The Client Field Config is an exact copy of the original Field Config, minus all non-serializable properties, plus all evaluated functions such as field labels, [Custom Components](../components), etc.
Server Component:
```tsx
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
import { TextField } from '@payloadcms/ui'
export const MyServerField: TextFieldServerComponent = ({ clientField }) => {
return <TextField field={clientField} />
}
```
<Banner type="info">
<strong>Tip:</strong>
Server Components can still access the original Field Config through the `field` prop.
</Banner>
Client Component:
```tsx
'use client'
import React from 'react'
import type { TextFieldProps } from 'payload'
import type { TextFieldClientComponent } from 'payload'
export const MyClientFieldComponent: React.FC<TextFieldProps> = ({ field: { name } }) => {
return (
<p>
{`This field's name is ${name}`}
</p>
)
export const MyTextField: TextFieldClientComponent = ({ field }) => {
return <TextField field={field} />
}
```
@@ -238,40 +238,18 @@ The following additional properties are also provided to the `field` prop:
#### TypeScript
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview). The convention is to append `Client` to the type of field, i.e. `TextFieldClient`.
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
ArrayFieldClient,
BlocksFieldClient,
CheckboxFieldClient,
CodeFieldClient,
CollapsibleFieldClient,
DateFieldClient,
EmailFieldClient,
GroupFieldClient,
HiddenFieldClient,
JSONFieldClient,
NumberFieldClient,
PointFieldClient,
RadioFieldClient,
RelationshipFieldClient,
RichTextFieldClient,
RowFieldClient,
SelectFieldClient,
TabsFieldClient,
TextFieldClient,
TextareaFieldClient,
UploadFieldClient
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
When working on the client, you will never have access to objects of type `Field`. This is reserved for the server-side. Instead, you can use `ClientField` which is a union type of all the client fields:
```tsx
import type { ClientField } from 'payload'
```
### The Cell Component
The Cell Component is rendered in the table of the List View. It represents the value of the field when displayed in a table cell.
@@ -298,7 +276,8 @@ All Cell Components receive the following props:
| Property | Description |
| ---------------- | ----------------------------------------------------------------- |
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
| **`onClick`** | A function that is called when the cell is clicked. |
@@ -338,7 +317,8 @@ Custom Label Components receive all [Field Component](#the-field-component) prop
| Property | Description |
| -------------- | ---------------------------------------------------------------- |
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>
@@ -353,7 +333,7 @@ When building Custom Label Components, you can import the component props to ens
import type {
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// And so on for each Field Type
// ...and so on for each Field Type
} from 'payload'
```
@@ -383,7 +363,8 @@ Custom Error Components receive all [Field Component](#the-field-component) prop
| Property | Description |
| --------------- | ------------------------------------------------------------- |
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>
@@ -499,7 +480,8 @@ Custom Description Components receive all [Field Component](#the-field-component
| Property | Description |
| -------------- | ---------------------------------------------------------------- |
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>

View File

@@ -113,7 +113,7 @@ You can pass a Redux-like selector into the hook, which will ensure that you ret
```tsx
'use client'
import type { useFormFields } from '@payloadcms/ui'
import { useFormFields } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// Get only the `amount` field state, and only cause a rerender when that field changes

View File

@@ -31,8 +31,9 @@ const config = buildConfig({
admin: {
components: {
views: {
dashboard: {
Component: '/path/to/MyCustomDashboardView#MyCustomDashboardViewComponent', // highlight-line
customView: {
Component: '/path/to/MyCustomView#MyCustomView', // highlight-line
path: '/my-custom-view',
}
},
},
@@ -40,7 +41,42 @@ const config = buildConfig({
})
```
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
Your Custom Root Views can optionally use one of the templates that Payload provides. The most common of these is the Default Template which provides the basic layout and navigation. Here is an example of what that might look like:
```tsx
import type { AdminViewProps } from 'payload'
import { DefaultTemplate } from '@payloadcms/next/templates'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export const MyCustomView: React.FC<AdminViewProps> = ({
initPageResult,
params,
searchParams,
}) => {
return (
<DefaultTemplate
i18n={initPageResult.req.i18n}
locale={initPageResult.locale}
params={params}
payload={initPageResult.req.payload}
permissions={initPageResult.permissions}
searchParams={searchParams}
user={initPageResult.req.user || undefined}
visibleEntities={initPageResult.visibleEntities}
>
<Gutter>
<h1>Custom Default Root View</h1>
<br />
<p>This view uses the Default Template.</p>
</Gutter>
</DefaultTemplate>
)
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
The following options are available:
@@ -133,7 +169,7 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
}
```
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
<Banner type="warning">
<strong>Note:</strong>
@@ -184,7 +220,7 @@ export const MyGlobalConfig: SanitizedGlobalConfig = {
}
```
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
<Banner type="warning">
<strong>Note:</strong>
@@ -227,7 +263,7 @@ export const MyCollectionOrGlobalConfig: SanitizedCollectionConfig = {
}
```
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
<Banner type="warning">
<strong>Note:</strong>
@@ -312,13 +348,11 @@ Your Custom Views will be provided with the following props:
| Prop | Description |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| **`user`** | The currently logged in user. |
| **`locale`** | The current [Locale](../configuration/localization) of the [Admin Panel](./overview). |
| **`navGroups`** | The grouped navigation items according to `admin.group` in your [Collection Config](../collections/overview) or [Global Config](../globals/overview). |
| **`params`** | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
| **`permissions`** | The permissions of the currently logged in user. |
| **`searchParams`** | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
| **`visibleEntities`** | The current user's visible entities according to your [Access Control](../access-control/overview). |
| **`initPageResult`** | An object containing `req`, `payload`, `permissions`, etc. |
| **`clientConfig`** | The Client Config object. [More details](../components#accessing-the-payload-config). |
| **`importMap`** | The import map object. |
| **`params`** | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
| **`searchParams`** | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
<Banner type="success">
<strong>Reminder:</strong>

View File

@@ -85,6 +85,7 @@ The following options are available:
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
| **`loginWithUsername`** | Ability to allow users to login with username/password. [More](/docs/authentication/overview#login-with-username) |
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
| **`removeTokenFromResponses`** | Set to true if you want to remove the token from the returned authentication API responses such as login or refresh. |
| **`strategies`** | Advanced - an array of custom authentification strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |

View File

@@ -71,9 +71,11 @@ The following options are available:
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/overview#csrf-protection). |
@@ -253,3 +255,13 @@ import type { Config, SanitizedConfig } from 'payload'
The Payload Config only lives on the server and is not allowed to contain any client-side code. That way, you can load up the Payload Config in any server environment or standalone script, without having to use Bundlers or Node.js loaders to handle importing client-only modules (e.g. scss files or React Components) without any errors.
Behind the curtains, the Next.js-based Admin Panel generates a ClientConfig, which strips away any server-only code and enriches the config with React Components.
## Compatibility flags
The Payload Config can accept compatibility flags for running the newest versions but with older databases. You should only use these flags if you need to, and should confirm that you need to prior to enabling these flags.
`allowLocalizedWithinLocalized`
Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized.
By default, Payload will remove the `localized: true` property from sub-fields if a parent field is localized. Set this compatibility flag to `true` only if you have an existing Payload MongoDB database from pre-3.0, and you have nested localized fields that you would like to maintain without migrating.

View File

@@ -57,7 +57,6 @@ export default buildConfig({
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres` or to `@vercel/postgres` |
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
| `migrationDir` | Customize the directory that migrations are stored. |
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
| `schemaName` (experimental) | A string for the postgres schema to use, defaults to 'public'. |
| `idType` | A string of 'serial', or 'uuid' that is used for the data type given to id columns. |
| `transactionOptions` | A PgTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |

View File

@@ -11,6 +11,7 @@ Payload provides a vast array of examples to help you get started with your proj
Examples are changing every day, so be sure to check back often to see what new examples have been added. If you have a specific example you would like to see, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions) or open a new [PR](https://github.com/payloadcms/payload/pulls) to add it yourself.
- [Auth](https://github.com/payloadcms/payload/tree/main/examples/auth)
- [Custom Components](https://github.com/payloadcms/payload/tree/main/examples/custom-components)
- [Custom Server](https://github.com/payloadcms/payload/tree/main/examples/custom-server)
- [Draft Preview](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
- [Email](https://github.com/payloadcms/payload/tree/main/examples/email)

View File

@@ -20,7 +20,7 @@ Payload's rich text field is built on an "adapter pattern" which lets you specif
Right now, Payload is officially supporting two rich text editors:
1. [SlateJS](/docs/rich-text/slate) - stable, backwards-compatible with 1.0
2. [Lexical](/docs/rich-text/lexical) - beta, where things will be moving
2. [Lexical](/docs/lexical/overview) - beta, where things will be moving
<Banner type="success">
<strong>
@@ -82,4 +82,4 @@ The Rich Text Field inherits all of the default options from the base [Field Adm
## Editor-specific Options
For a ton more editor-specific options, including how to build custom rich text elements directly into your editor, take a look at either the [Slate docs](/docs/rich-text/slate) or the [Lexical docs](/docs/rich-text/lexical) depending on which editor you're using.
For a ton more editor-specific options, including how to build custom rich text elements directly into your editor, take a look at either the [Slate docs](/docs/rich-text/slate) or the [Lexical docs](/docs/lexical/overview) depending on which editor you're using.

View File

@@ -68,7 +68,7 @@ Here's a quick example of a React Server Component fetching data using the Local
```tsx
import React from 'react'
import config from '@payload-config'
import { getPayloadHMR } from '@payloadcms/next'
import { getPayloadHMR } from '@payloadcms/next/utilities'
const MyServerComponent: React.FC = () => {
// If you're working in Next.js, and you want HMR,

View File

@@ -200,7 +200,7 @@ user-friendly.
The `beforeDuplicate` field hook is called on each locale (when using localization), when duplicating a document. It may be used when documents having the
exact same properties may cause issue. This gives you a way to avoid duplicate names on `unique`, `required` fields or when external systems expect non-repeating values on documents.
This hook gets called after `beforeChange` hooks are called and before the document is saved to the database.
This hook gets called before the `beforeValidate` and `beforeChange` hooks are called.
By Default, unique and required text fields Payload will append "- Copy" to the original document value. The default is not added if your field has its own, you must return non-unique values from your beforeDuplicate hook to avoid errors or enable the `disableDuplicate` option on the collection.
Here is an example of a number field with a hook that increments the number to avoid unique constraint errors when duplicating a document:

View File

@@ -34,7 +34,7 @@ Then, render the `RefreshRouteOnSave` component anywhere in your `page.tsx`. Her
```tsx
import { RefreshRouteOnSave } from './RefreshRouteOnSave.tsx'
import { getPayloadHMR } from '@payloadcms/next'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import config from '../payload.config'
export default async function Page() {

View File

@@ -25,13 +25,12 @@ export const defaultESLintIgnores = [
let FlatConfig
export const rootParserOptions = {
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
EXPERIMENTAL_useProjectService: {
allowDefaultProjectForFiles: ['./src/*.ts', './src/*.tsx'],
maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 100,
},
sourceType: 'module',
ecmaVersion: 'latest',
projectService: {
maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 40,
allowDefaultProject: ['scripts/*.ts', '*.js', '*.mjs', '*.spec.ts', '*.d.ts'],
},
}
/** @type {FlatConfig[]} */
@@ -40,20 +39,12 @@ export const rootEslintConfig = [
{
ignores: [
...defaultESLintIgnores,
'packages/eslint-*/**',
'test/live-preview/next-app',
'packages/**/*.spec.ts',
'templates/**',
],
},
{
languageOptions: {
parserOptions: {
project: './tsconfig.json',
tsconfigDirName: import.meta.dirname,
...rootParserOptions,
},
},
},
{
plugins: {
payload: payloadPlugin,
@@ -78,6 +69,15 @@ export const rootEslintConfig = [
export default [
...rootEslintConfig,
{
languageOptions: {
parserOptions: {
...rootParserOptions,
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
files: ['packages/eslint-config/**/*.ts'],
rules: {

View File

@@ -15,7 +15,7 @@
width: 150px;
}
:global([data-theme="light"]) {
:global([data-theme='light']) {
.logo {
filter: invert(1);
}

View File

@@ -10,12 +10,12 @@ export const RenderParams: React.FC<{
className?: string
}> = ({ params = ['error', 'message', 'success'], message, className }) => {
const searchParams = useSearchParams()
const paramValues = params.map(param => searchParams.get(param)).filter(Boolean)
const paramValues = params.map((param) => searchParams.get(param)).filter(Boolean)
if (paramValues.length) {
return (
<div className={className}>
{paramValues.map(paramValue => (
{paramValues.map((paramValue) => (
<Message
key={paramValue}
message={(message || 'PARAM')?.replace('PARAM', paramValue || '')}

View File

@@ -1,7 +1,7 @@
@use './queries.scss' as *;
@use './colors.scss' as *;
@use './type.scss' as *;
@import "./theme.scss";
@import './theme.scss';
:root {
--base: 24px;
@@ -88,7 +88,7 @@ p {
margin: var(--base) 0;
@include mid-break {
margin: calc(var(--base) * .75) 0;
margin: calc(var(--base) * 0.75) 0;
}
}
@@ -102,12 +102,12 @@ a {
color: currentColor;
&:focus {
opacity: .8;
opacity: 0.8;
outline: none;
}
&:active {
opacity: .7;
opacity: 0.7;
outline: none;
}
}

View File

@@ -16,7 +16,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
const [user, setUser] = useState<User | null>()
const create = useCallback<Create>(
async args => {
async (args) => {
if (api === 'rest') {
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users`, args)
setUser(user)
@@ -38,7 +38,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
)
const login = useCallback<Login>(
async args => {
async (args) => {
if (api === 'rest') {
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/login`, args)
setUser(user)
@@ -110,7 +110,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
}, [api])
const forgotPassword = useCallback<ForgotPassword>(
async args => {
async (args) => {
if (api === 'rest') {
const user = await rest(
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/forgot-password`,
@@ -132,7 +132,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
)
const resetPassword = useCallback<ResetPassword>(
async args => {
async (args) => {
if (api === 'rest') {
const user = await rest(
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/reset-password`,

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.form {
margin-bottom: var(--base);

View File

@@ -139,7 +139,7 @@ export const AccountForm: React.FC = () => {
label="Confirm Password"
required
register={register}
validate={value => value === password.current || 'The passwords do not match'}
validate={(value) => value === password.current || 'The passwords do not match'}
error={errors.passwordConfirm}
/>
</Fragment>

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.form {
margin-bottom: var(--base);

View File

@@ -103,7 +103,7 @@ export const CreateAccountForm: React.FC = () => {
label="Confirm Password"
required
register={register}
validate={value => value === password.current || 'The passwords do not match'}
validate={(value) => value === password.current || 'The passwords do not match'}
error={errors.passwordConfirm}
/>
<Button

View File

@@ -1,4 +1,4 @@
@import "../_css/common";
@import '../_css/common';
.createAccount {
margin-bottom: var(--block-padding);

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.form {
margin-bottom: var(--base);

View File

@@ -1,4 +1,4 @@
@import "../_css/common";
@import '../_css/common';
.login {
margin-bottom: var(--block-padding);

View File

@@ -5,7 +5,7 @@ import Link from 'next/link'
import { useAuth } from '../../_providers/Auth'
export const LogoutPage: React.FC = props => {
export const LogoutPage: React.FC = (props) => {
const { logout } = useAuth()
const [success, setSuccess] = useState('')
const [error, setError] = useState('')

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.error {
color: red;
@@ -20,4 +20,3 @@
.message {
margin-bottom: var(--base);
}

View File

@@ -1,4 +1,4 @@
@import "../_css/common";
@import '../_css/common';
.recoverPassword {
margin-bottom: var(--block-padding);

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.form {
width: 66.66%;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@
width: 150px;
}
:global([data-theme="light"]) {
:global([data-theme='light']) {
.logo {
filter: invert(1);
}

View File

@@ -9,12 +9,12 @@ export const RenderParams: React.FC<{
}> = ({ params = ['error', 'message', 'success'], message, className }) => {
const router = useRouter()
const searchParams = new URLSearchParams(router.query as any)
const paramValues = params.map(param => searchParams.get(param)).filter(Boolean)
const paramValues = params.map((param) => searchParams.get(param)).filter(Boolean)
if (paramValues.length) {
return (
<div className={className}>
{paramValues.map(paramValue => (
{paramValues.map((paramValue) => (
<Message key={paramValue} message={(message || 'PARAM')?.replace('PARAM', paramValue)} />
))}
</div>

View File

@@ -1,7 +1,7 @@
@use './queries.scss' as *;
@use './colors.scss' as *;
@use './type.scss' as *;
@import "./theme.scss";
@import './theme.scss';
:root {
--base: 24px;
@@ -88,7 +88,7 @@ p {
margin: var(--base) 0;
@include mid-break {
margin: calc(var(--base) * .75) 0;
margin: calc(var(--base) * 0.75) 0;
}
}
@@ -102,12 +102,12 @@ a {
color: currentColor;
&:focus {
opacity: .8;
opacity: 0.8;
outline: none;
}
&:active {
opacity: .7;
opacity: 0.7;
outline: none;
}
}

View File

@@ -1,4 +1,4 @@
@import "../../css/common";
@import '../../css/common';
.account {
margin-bottom: var(--block-padding);

View File

@@ -150,7 +150,7 @@ const Account: React.FC = () => {
label="Confirm Password"
required
register={register}
validate={value => value === password.current || 'The passwords do not match'}
validate={(value) => value === password.current || 'The passwords do not match'}
error={errors.passwordConfirm}
/>
</Fragment>

View File

@@ -1,4 +1,4 @@
@import "../../css/common";
@import '../../css/common';
.createAccount {
margin-bottom: var(--block-padding);

View File

@@ -106,7 +106,7 @@ const CreateAccount: React.FC = () => {
label="Confirm Password"
required
register={register}
validate={value => value === password.current || 'The passwords do not match'}
validate={(value) => value === password.current || 'The passwords do not match'}
error={errors.passwordConfirm}
/>
<Button

View File

@@ -1,4 +1,4 @@
@import "../../css/common";
@import '../../css/common';
.login {
margin-bottom: var(--block-padding);

View File

@@ -1,4 +1,4 @@
@import "../../css/common";
@import '../../css/common';
.recoverPassword {
margin-bottom: var(--block-padding);
@@ -24,4 +24,3 @@
.message {
margin-bottom: var(--base);
}

View File

@@ -1,4 +1,4 @@
@import "../../css/common";
@import '../../css/common';
.resetPassword {
margin-bottom: var(--block-padding);

View File

@@ -14,7 +14,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
const [user, setUser] = useState<User | null>()
const create = useCallback<Create>(
async args => {
async (args) => {
if (api === 'rest') {
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users`, args)
setUser(user)
@@ -36,7 +36,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
)
const login = useCallback<Login>(
async args => {
async (args) => {
if (api === 'rest') {
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/login`, args)
setUser(user)
@@ -108,7 +108,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
}, [api])
const forgotPassword = useCallback<ForgotPassword>(
async args => {
async (args) => {
if (api === 'rest') {
const user = await rest(
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/forgot-password`,
@@ -130,7 +130,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
)
const resetPassword = useCallback<ResetPassword>(
async args => {
async (args) => {
if (api === 'rest') {
const user = await rest(
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/reset-password`,

View File

@@ -48,7 +48,7 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
```ts
import { headers as getHeaders } from 'next/headers.js'
import { getPayloadHMR } from '@payloadcms/next'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import config from '../../payload.config'
export default async function AccountPage({ searchParams }) {

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
'use client'
import type { ElementType } from 'react';
import type { ElementType } from 'react'
import Link from 'next/link'
import React from 'react'

View File

@@ -1,4 +1,4 @@
import type { Ref } from 'react';
import type { Ref } from 'react'
import React, { forwardRef } from 'react'

View File

@@ -15,7 +15,7 @@
width: 150px;
}
:global([data-theme="light"]) {
:global([data-theme='light']) {
.logo {
filter: invert(1);
}

View File

@@ -10,12 +10,12 @@ export const RenderParams: React.FC<{
params?: string[]
}> = ({ className, message, params = ['error', 'message', 'success'] }) => {
const searchParams = useSearchParams()
const paramValues = params.map(param => searchParams.get(param)).filter(Boolean)
const paramValues = params.map((param) => searchParams.get(param)).filter(Boolean)
if (paramValues.length) {
return (
<div className={className}>
{paramValues.map(paramValue => (
{paramValues.map((paramValue) => (
<Message
key={paramValue}
message={(message || 'PARAM')?.replace('PARAM', paramValue || '')}

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.form {
margin-bottom: var(--base);

View File

@@ -136,7 +136,7 @@ export const AccountForm: React.FC = () => {
register={register}
required
type="password"
validate={value => value === password.current || 'The passwords do not match'}
validate={(value) => value === password.current || 'The passwords do not match'}
/>
</Fragment>
)}

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.form {
margin-bottom: var(--base);

View File

@@ -103,7 +103,7 @@ export const CreateAccountForm: React.FC = () => {
register={register}
required
type="password"
validate={value => value === password.current || 'The passwords do not match'}
validate={(value) => value === password.current || 'The passwords do not match'}
/>
<Button
appearance="primary"

View File

@@ -1,4 +1,4 @@
@import "../_css/common";
@import '../_css/common';
.createAccount {
margin-bottom: var(--block-padding);

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.form {
margin-bottom: var(--base);

View File

@@ -1,4 +1,4 @@
@import "../_css/common";
@import '../_css/common';
.login {
margin-bottom: var(--block-padding);

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.error {
color: red;
@@ -20,4 +20,3 @@
.message {
margin-bottom: var(--base);
}

View File

@@ -1,4 +1,4 @@
@import "../_css/common";
@import '../_css/common';
.recoverPassword {
margin-bottom: var(--block-padding);

View File

@@ -1,4 +1,4 @@
@import "../../_css/common";
@import '../../_css/common';
.form {
width: 66.66%;

View File

@@ -3,8 +3,8 @@ import type { User } from '../../payload-types'
export const checkRole = (allRoles: User['roles'] = [], user: User = undefined): boolean => {
if (user) {
if (
allRoles.some(role => {
return user?.roles?.some(individualRole => {
allRoles.some((role) => {
return user?.roles?.some((individualRole) => {
return individualRole === role
})
})

View File

@@ -0,0 +1,3 @@
DATABASE_URI=mongodb://127.0.0.1/payload-example-custom-fields
PAYLOAD_SECRET=PAYLOAD_CUSTOM_FIELDS_EXAMPLE_SECRET_KEY
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@payloadcms'],
}

5
examples/custom-components/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
build
dist
node_modules
package-lock.json
.env

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
},
"transform": {
"react": {
"runtime": "automatic",
"pragmaFrag": "React.Fragment",
"throwIfNamespace": true,
"development": false,
"useBuiltins": true
}
}
},
"module": {
"type": "es6"
}
}

View File

@@ -0,0 +1,46 @@
# Payload Custom Components Example
This example demonstrates how to use Custom Components in [Payload](https://github.com/payloadcms/payload) Admin Panel. This example includes custom components for every field type available in Payload, including both server and client components. It also includes custom views, custom nav links, and more.
## Quick Start
To spin up this example locally, follow these steps:
1. Clone this repo
1. `cd` into this directory and run `pnpm i --ignore-workspace`\*, `yarn`, or `npm install`
> \*If you are running using pnpm within the Payload Monorepo, the `--ignore-workspace` flag is needed so that pnpm generates a lockfile in this example's directory despite the fact that one exists in root.
1. `pnpm dev`, `yarn dev` or `npm run dev` to start the server
- Press `y` when prompted to seed the database
1. `open http://localhost:3000` to access the home page
1. `open http://localhost:3000/admin` to access the admin panel
- Login with email `demo@payloadcms.com` and password `demo`
## How it works
### Collections
See the [Collections](https://payloadcms.com/docs/configuration/collections) docs for details on how to extend any of this functionality.
- #### Users
The `users` collection is auth-enabled which provides access to the admin panel.
For additional help with authentication, see the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth/cms#readme) or the [Authentication](https://payloadcms.com/docs/authentication/overview#authentication-overview) docs.
- #### Fields
The `fields` collection contains every field type available in Payload, each with custom components filled in every available "slot", i.e. `admin.components.Field`, `admin.components.Label`, etc. There are two of every field, one for server components, and the other for client components. This pattern shows how to use custom components in both environments, no matter which field type you are using.
- #### Views
The `views` collection demonstrates how to add collection-level views, including the default view and custom tabs.
- #### Root Views
The `root-views` collection demonstrates how to add a root document-level view to the admin panel.
## Questions
If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions).

View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,8 @@
import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Your Next.js config here
}
export default withPayload(nextConfig)

View File

@@ -0,0 +1,43 @@
{
"name": "payload-example-custom-fields",
"version": "1.0.0",
"description": "An example of custom fields in Payload.",
"license": "MIT",
"type": "module",
"scripts": {
"_dev": "cross-env NODE_OPTIONS=--no-deprecation && pnpm generate:importmap && next dev",
"build": "cross-env NODE_OPTIONS=--no-deprecation next build",
"dev": "cross-env NODE_OPTIONS=--no-deprecation && pnpm generate:importmap && pnpm seed && next dev --turbo",
"generate:importmap": "payload generate:importmap",
"generate:schema": "payload-graphql generate:schema",
"generate:types": "payload generate:types",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"seed": "npm run payload migrate:fresh",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
},
"dependencies": {
"@payloadcms/db-mongodb": "3.0.0-beta.106",
"@payloadcms/next": "3.0.0-beta.106",
"@payloadcms/richtext-lexical": "3.0.0-beta.106",
"@payloadcms/ui": "3.0.0-beta.106",
"cross-env": "^7.0.3",
"dotenv": "^8.2.0",
"graphql": "^16.9.0",
"next": "15.0.0-canary.104",
"payload": "3.0.0-beta.106",
"react": "19.0.0-rc-06d0b89e-20240801",
"react-dom": "19.0.0-rc-06d0b89e-20240801"
},
"devDependencies": {
"@payloadcms/graphql": "3.0.0-beta.106",
"@types/react": "npm:types-react@19.0.0-beta.2",
"@types/react-dom": "npm:types-react-dom@19.0.0-beta.2",
"eslint": "^8.57.0",
"eslint-config-next": "15.0.0-canary.146",
"tsx": "^4.7.1",
"typescript": "5.5.2"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"
}
}

6897
examples/custom-components/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import type { Metadata } from 'next'
import config from '@payload-config'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { generatePageMetadata, NotFoundPage } from '@payloadcms/next/views'
import { importMap } from '../importMap.js'
type Args = {
params: {
segments: string[]
}
searchParams: {
[key: string]: string | string[]
}
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const NotFound = ({ params, searchParams }: Args) =>
NotFoundPage({ config, importMap, params, searchParams })
export default NotFound

View File

@@ -0,0 +1,25 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import type { Metadata } from 'next'
import config from '@payload-config'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { generatePageMetadata, RootPage } from '@payloadcms/next/views'
import { importMap } from '../importMap.js'
type Args = {
params: {
segments: string[]
}
searchParams: {
[key: string]: string | string[]
}
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const Page = ({ params, searchParams }: Args) =>
RootPage({ config, importMap, params, searchParams })
export default Page

View File

@@ -0,0 +1,113 @@
import { CustomArrayFieldLabelServer as CustomArrayFieldLabelServer_0 } from '@/collections/Fields/array/components/server/Label'
import { CustomArrayFieldServer as CustomArrayFieldServer_1 } from '@/collections/Fields/array/components/server/Field'
import { CustomArrayFieldLabelClient as CustomArrayFieldLabelClient_2 } from '@/collections/Fields/array/components/client/Label'
import { CustomArrayFieldClient as CustomArrayFieldClient_3 } from '@/collections/Fields/array/components/client/Field'
import { CustomBlocksFieldServer as CustomBlocksFieldServer_4 } from '@/collections/Fields/blocks/components/server/Field'
import { CustomBlocksFieldClient as CustomBlocksFieldClient_5 } from '@/collections/Fields/blocks/components/client/Field'
import { CustomCheckboxFieldLabelServer as CustomCheckboxFieldLabelServer_6 } from '@/collections/Fields/checkbox/components/server/Label'
import { CustomCheckboxFieldServer as CustomCheckboxFieldServer_7 } from '@/collections/Fields/checkbox/components/server/Field'
import { CustomCheckboxFieldLabelClient as CustomCheckboxFieldLabelClient_8 } from '@/collections/Fields/checkbox/components/client/Label'
import { CustomCheckboxFieldClient as CustomCheckboxFieldClient_9 } from '@/collections/Fields/checkbox/components/client/Field'
import { CustomDateFieldLabelServer as CustomDateFieldLabelServer_10 } from '@/collections/Fields/date/components/server/Label'
import { CustomDateFieldServer as CustomDateFieldServer_11 } from '@/collections/Fields/date/components/server/Field'
import { CustomDateFieldLabelClient as CustomDateFieldLabelClient_12 } from '@/collections/Fields/date/components/client/Label'
import { CustomDateFieldClient as CustomDateFieldClient_13 } from '@/collections/Fields/date/components/client/Field'
import { CustomEmailFieldLabelServer as CustomEmailFieldLabelServer_14 } from '@/collections/Fields/email/components/server/Label'
import { CustomEmailFieldServer as CustomEmailFieldServer_15 } from '@/collections/Fields/email/components/server/Field'
import { CustomEmailFieldLabelClient as CustomEmailFieldLabelClient_16 } from '@/collections/Fields/email/components/client/Label'
import { CustomEmailFieldClient as CustomEmailFieldClient_17 } from '@/collections/Fields/email/components/client/Field'
import { CustomNumberFieldLabelServer as CustomNumberFieldLabelServer_18 } from '@/collections/Fields/number/components/server/Label'
import { CustomNumberFieldServer as CustomNumberFieldServer_19 } from '@/collections/Fields/number/components/server/Field'
import { CustomNumberFieldLabelClient as CustomNumberFieldLabelClient_20 } from '@/collections/Fields/number/components/client/Label'
import { CustomNumberFieldClient as CustomNumberFieldClient_21 } from '@/collections/Fields/number/components/client/Field'
import { CustomPointFieldLabelServer as CustomPointFieldLabelServer_22 } from '@/collections/Fields/point/components/server/Label'
import { CustomPointFieldServer as CustomPointFieldServer_23 } from '@/collections/Fields/point/components/server/Field'
import { CustomPointFieldLabelClient as CustomPointFieldLabelClient_24 } from '@/collections/Fields/point/components/client/Label'
import { CustomPointFieldClient as CustomPointFieldClient_25 } from '@/collections/Fields/point/components/client/Field'
import { CustomRadioFieldLabelServer as CustomRadioFieldLabelServer_26 } from '@/collections/Fields/radio/components/server/Label'
import { CustomRadioFieldServer as CustomRadioFieldServer_27 } from '@/collections/Fields/radio/components/server/Field'
import { CustomRadioFieldLabelClient as CustomRadioFieldLabelClient_28 } from '@/collections/Fields/radio/components/client/Label'
import { CustomRadioFieldClient as CustomRadioFieldClient_29 } from '@/collections/Fields/radio/components/client/Field'
import { CustomRelationshipFieldLabelServer as CustomRelationshipFieldLabelServer_30 } from '@/collections/Fields/relationship/components/server/Label'
import { CustomRelationshipFieldServer as CustomRelationshipFieldServer_31 } from '@/collections/Fields/relationship/components/server/Field'
import { CustomRelationshipFieldLabelClient as CustomRelationshipFieldLabelClient_32 } from '@/collections/Fields/relationship/components/client/Label'
import { CustomRelationshipFieldClient as CustomRelationshipFieldClient_33 } from '@/collections/Fields/relationship/components/client/Field'
import { CustomSelectFieldLabelServer as CustomSelectFieldLabelServer_34 } from '@/collections/Fields/select/components/server/Label'
import { CustomSelectFieldServer as CustomSelectFieldServer_35 } from '@/collections/Fields/select/components/server/Field'
import { CustomSelectFieldLabelClient as CustomSelectFieldLabelClient_36 } from '@/collections/Fields/select/components/client/Label'
import { CustomSelectFieldClient as CustomSelectFieldClient_37 } from '@/collections/Fields/select/components/client/Field'
import { CustomTextFieldLabelServer as CustomTextFieldLabelServer_38 } from '@/collections/Fields/text/components/server/Label'
import { CustomTextFieldServer as CustomTextFieldServer_39 } from '@/collections/Fields/text/components/server/Field'
import { CustomTextFieldLabelClient as CustomTextFieldLabelClient_40 } from '@/collections/Fields/text/components/client/Label'
import { CustomTextFieldClient as CustomTextFieldClient_41 } from '@/collections/Fields/text/components/client/Field'
import { CustomTextareaFieldLabelServer as CustomTextareaFieldLabelServer_42 } from '@/collections/Fields/textarea/components/server/Label'
import { CustomTextareaFieldServer as CustomTextareaFieldServer_43 } from '@/collections/Fields/textarea/components/server/Field'
import { CustomTextareaFieldLabelClient as CustomTextareaFieldLabelClient_44 } from '@/collections/Fields/textarea/components/client/Label'
import { CustomTextareaFieldClient as CustomTextareaFieldClient_45 } from '@/collections/Fields/textarea/components/client/Field'
import { CustomTabEditView as CustomTabEditView_46 } from '@/collections/Views/components/CustomTabEditView'
import { CustomDefaultEditView as CustomDefaultEditView_47 } from '@/collections/Views/components/CustomDefaultEditView'
import { CustomRootEditView as CustomRootEditView_48 } from '@/collections/RootViews/components/CustomRootEditView'
import { LinkToCustomView as LinkToCustomView_49 } from '@/components/afterNavLinks/LinkToCustomView'
import { LinkToCustomMinimalView as LinkToCustomMinimalView_50 } from '@/components/afterNavLinks/LinkToCustomMinimalView'
import { LinkToCustomDefaultView as LinkToCustomDefaultView_51 } from '@/components/afterNavLinks/LinkToCustomDefaultView'
import { CustomRootView as CustomRootView_52 } from '@/components/views/CustomRootView'
import { CustomDefaultRootView as CustomDefaultRootView_53 } from '@/components/views/CustomDefaultRootView'
import { CustomMinimalRootView as CustomMinimalRootView_54 } from '@/components/views/CustomMinimalRootView'
export const importMap = {
"@/collections/Fields/array/components/server/Label#CustomArrayFieldLabelServer": CustomArrayFieldLabelServer_0,
"@/collections/Fields/array/components/server/Field#CustomArrayFieldServer": CustomArrayFieldServer_1,
"@/collections/Fields/array/components/client/Label#CustomArrayFieldLabelClient": CustomArrayFieldLabelClient_2,
"@/collections/Fields/array/components/client/Field#CustomArrayFieldClient": CustomArrayFieldClient_3,
"@/collections/Fields/blocks/components/server/Field#CustomBlocksFieldServer": CustomBlocksFieldServer_4,
"@/collections/Fields/blocks/components/client/Field#CustomBlocksFieldClient": CustomBlocksFieldClient_5,
"@/collections/Fields/checkbox/components/server/Label#CustomCheckboxFieldLabelServer": CustomCheckboxFieldLabelServer_6,
"@/collections/Fields/checkbox/components/server/Field#CustomCheckboxFieldServer": CustomCheckboxFieldServer_7,
"@/collections/Fields/checkbox/components/client/Label#CustomCheckboxFieldLabelClient": CustomCheckboxFieldLabelClient_8,
"@/collections/Fields/checkbox/components/client/Field#CustomCheckboxFieldClient": CustomCheckboxFieldClient_9,
"@/collections/Fields/date/components/server/Label#CustomDateFieldLabelServer": CustomDateFieldLabelServer_10,
"@/collections/Fields/date/components/server/Field#CustomDateFieldServer": CustomDateFieldServer_11,
"@/collections/Fields/date/components/client/Label#CustomDateFieldLabelClient": CustomDateFieldLabelClient_12,
"@/collections/Fields/date/components/client/Field#CustomDateFieldClient": CustomDateFieldClient_13,
"@/collections/Fields/email/components/server/Label#CustomEmailFieldLabelServer": CustomEmailFieldLabelServer_14,
"@/collections/Fields/email/components/server/Field#CustomEmailFieldServer": CustomEmailFieldServer_15,
"@/collections/Fields/email/components/client/Label#CustomEmailFieldLabelClient": CustomEmailFieldLabelClient_16,
"@/collections/Fields/email/components/client/Field#CustomEmailFieldClient": CustomEmailFieldClient_17,
"@/collections/Fields/number/components/server/Label#CustomNumberFieldLabelServer": CustomNumberFieldLabelServer_18,
"@/collections/Fields/number/components/server/Field#CustomNumberFieldServer": CustomNumberFieldServer_19,
"@/collections/Fields/number/components/client/Label#CustomNumberFieldLabelClient": CustomNumberFieldLabelClient_20,
"@/collections/Fields/number/components/client/Field#CustomNumberFieldClient": CustomNumberFieldClient_21,
"@/collections/Fields/point/components/server/Label#CustomPointFieldLabelServer": CustomPointFieldLabelServer_22,
"@/collections/Fields/point/components/server/Field#CustomPointFieldServer": CustomPointFieldServer_23,
"@/collections/Fields/point/components/client/Label#CustomPointFieldLabelClient": CustomPointFieldLabelClient_24,
"@/collections/Fields/point/components/client/Field#CustomPointFieldClient": CustomPointFieldClient_25,
"@/collections/Fields/radio/components/server/Label#CustomRadioFieldLabelServer": CustomRadioFieldLabelServer_26,
"@/collections/Fields/radio/components/server/Field#CustomRadioFieldServer": CustomRadioFieldServer_27,
"@/collections/Fields/radio/components/client/Label#CustomRadioFieldLabelClient": CustomRadioFieldLabelClient_28,
"@/collections/Fields/radio/components/client/Field#CustomRadioFieldClient": CustomRadioFieldClient_29,
"@/collections/Fields/relationship/components/server/Label#CustomRelationshipFieldLabelServer": CustomRelationshipFieldLabelServer_30,
"@/collections/Fields/relationship/components/server/Field#CustomRelationshipFieldServer": CustomRelationshipFieldServer_31,
"@/collections/Fields/relationship/components/client/Label#CustomRelationshipFieldLabelClient": CustomRelationshipFieldLabelClient_32,
"@/collections/Fields/relationship/components/client/Field#CustomRelationshipFieldClient": CustomRelationshipFieldClient_33,
"@/collections/Fields/select/components/server/Label#CustomSelectFieldLabelServer": CustomSelectFieldLabelServer_34,
"@/collections/Fields/select/components/server/Field#CustomSelectFieldServer": CustomSelectFieldServer_35,
"@/collections/Fields/select/components/client/Label#CustomSelectFieldLabelClient": CustomSelectFieldLabelClient_36,
"@/collections/Fields/select/components/client/Field#CustomSelectFieldClient": CustomSelectFieldClient_37,
"@/collections/Fields/text/components/server/Label#CustomTextFieldLabelServer": CustomTextFieldLabelServer_38,
"@/collections/Fields/text/components/server/Field#CustomTextFieldServer": CustomTextFieldServer_39,
"@/collections/Fields/text/components/client/Label#CustomTextFieldLabelClient": CustomTextFieldLabelClient_40,
"@/collections/Fields/text/components/client/Field#CustomTextFieldClient": CustomTextFieldClient_41,
"@/collections/Fields/textarea/components/server/Label#CustomTextareaFieldLabelServer": CustomTextareaFieldLabelServer_42,
"@/collections/Fields/textarea/components/server/Field#CustomTextareaFieldServer": CustomTextareaFieldServer_43,
"@/collections/Fields/textarea/components/client/Label#CustomTextareaFieldLabelClient": CustomTextareaFieldLabelClient_44,
"@/collections/Fields/textarea/components/client/Field#CustomTextareaFieldClient": CustomTextareaFieldClient_45,
"@/collections/Views/components/CustomTabEditView#CustomTabEditView": CustomTabEditView_46,
"@/collections/Views/components/CustomDefaultEditView#CustomDefaultEditView": CustomDefaultEditView_47,
"@/collections/RootViews/components/CustomRootEditView#CustomRootEditView": CustomRootEditView_48,
"@/components/afterNavLinks/LinkToCustomView#LinkToCustomView": LinkToCustomView_49,
"@/components/afterNavLinks/LinkToCustomMinimalView#LinkToCustomMinimalView": LinkToCustomMinimalView_50,
"@/components/afterNavLinks/LinkToCustomDefaultView#LinkToCustomDefaultView": LinkToCustomDefaultView_51,
"@/components/views/CustomRootView#CustomRootView": CustomRootView_52,
"@/components/views/CustomDefaultRootView#CustomDefaultRootView": CustomDefaultRootView_53,
"@/components/views/CustomMinimalRootView#CustomMinimalRootView": CustomMinimalRootView_54
}

View File

@@ -0,0 +1,10 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)
export const OPTIONS = REST_OPTIONS(config)

View File

@@ -0,0 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export const GET = GRAPHQL_PLAYGROUND_GET(config)

View File

@@ -0,0 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { GRAPHQL_POST } from '@payloadcms/next/routes'
export const POST = GRAPHQL_POST(config)

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