Compare commits

...

73 Commits

Author SHA1 Message Date
Elliot DeNolf
ee62ed6ebb chore(release): v3.0.0-beta.77 [skip ci] 2024-08-08 17:13:02 -04:00
Elliot DeNolf
0283039257 chore(db-*): remove unneeded drizzle import (#7590)
Removes unused drizzle snapshot
2024-08-08 17:11:34 -04:00
Jessica Chowdhury
b546c7b655 fix: empty path in error message (#7555)
## Description

Closes #7524

The query path is overwritten as an empty string in the
`getLocalizedPaths()` function - then when it should throw an invalid
path error it no longer has this info.

- [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)
- [ ] 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
2024-08-08 10:29:03 +00:00
Elliot DeNolf
a933eb7311 chore(release): v3.0.0-beta.76 [skip ci] 2024-08-07 15:18:17 -04:00
Elliot DeNolf
b5d65dd1ac fix(richtext-lexical): next.js multiple refs of same fix (#7572)
Fixes some issue w/ Next.js and passing the same ref multiple times.
2024-08-07 15:17:33 -04:00
Elliot DeNolf
c4ee623907 chore(release): v3.0.0-beta.75 [skip ci] 2024-08-07 14:01:09 -04:00
James Mikrut
1cb1e5e8b3 feat(db-*): allows for running migrations in production automatically (#7563)
## Description

Introduces a pattern for running migrations upon Payload init in
production.
2024-08-07 13:57:12 -04:00
Paul
e0699838e1 chore(ui): update useField jsdoc comment to point to the right internal hook (#7568) 2024-08-07 17:49:32 +00:00
Dan Ribbens
46f70d9df4 fix(db-postgres): #7492 migrate snapshots (#7540)
## Description

Fixes #7492 

In order to run createMigration, we need to read in the previous
snapshot file if one exists. When that snapshot was generated from an
older version of drizzle-kit, we have to first migrate it up match the
latest version for drizzle to generate the new migration. This change
adds in the call to check the version and migrate the snapshot if
needed.
2024-08-07 13:49:08 -04:00
Paul
b7e2c59622 fix(plugin-seo): issue with generating image from a function (#7566) 2024-08-07 17:35:43 +00:00
Jarrod Flesch
0cc7184023 fix: hydrate permissions on dashboard, fix active menu item logic 2024-08-07 12:14:58 -04:00
Jarrod Flesch
e905675a05 chore!: adjusts auth hydration from server (#7545)
Fixes https://github.com/payloadcms/payload/issues/6823

Allows the server to initialize the AuthProvider via props. Renames
`HydrateClientUser` to `HydrateAuthProvider`. It now only hydrates the
permissions as the user can be set from props. Permissions can be
initialized from props, but still need to be hydrated for some pages as
access control can be specific to docs/lists etc.

**BREAKING CHANGE**
- Renames exported `HydrateClientUser` to `HydrateAuthProvider`
2024-08-07 11:10:53 -04:00
Paul
4a20a63563 fix(ui): fixes issue when filtering by checkbox value in a different language (#7547)
Fixes https://github.com/payloadcms/payload/issues/7447
2024-08-07 00:48:50 +00:00
Paul
8d1fc6e8fb feat!: bump next canary to 104 and update withPayload for new config (#7541)
We are now bumping up the Next canary version to `15.0.0-canary.104` and
`react` and `react-dom` to `^19.0.0-rc-06d0b89e-20240801`.

Your new dependencies should look like this:
```
"next": "15.0.0-canary.104",
"react": "^19.0.0-rc-06d0b89e-20240801",
"react-dom": "^19.0.0-rc-06d0b89e-20240801",
```

---------

Co-authored-by: Alessio Gravili <alessio@gravili.de>
2024-08-06 23:54:34 +00:00
Patrik
62744e79ac fix(next, payload): enable relationship & upload version tracking when localization enabled (#7508) 2024-08-06 12:28:06 -04:00
Jarrod Flesch
e8bed7b315 chore: call refresh after the subscription is ready, fixes CI (#7542)
LivePreview data was stale if the user entered data while the socket
connection was being established. This change ensures fresh data is
fetched after the connection is established.

This is easy to see when turning on 4G connection and in CI, where it is
especially slow.
2024-08-06 12:17:50 -04:00
Alessio Gravili
f2b8ddb299 Merge PR: fix lexical upload html converter, export missing nodes #7539
Fixes https://github.com/payloadcms/payload/issues/7495

When the Upload HTML Converter was called from the local API, the upload
document did not populate properly due to overrideAccess not being
passed through to the dataloader. This PR also adds new properties to
the afterRead field hook, so that these can be used in the lexical html
field.

Reproduction here:
https://github.com/payloadcms/payload/tree/chore/reproduce-html-converter-issue

**BREAKING:** If you define your own, custom lexical HTML Converters
that have sub-nodes, or if you directly call the
`convertLexicalNodesToHTML` function anywhere, you now need to pass
through the `showHiddenFields`, draft and `overrideAccess` props to the
`convertLexicalNodesToHTML` function. These are available in the
arguments of your HTML Converter function
2024-08-06 12:04:56 -04:00
Alessio Gravili
ffd8ea516d feat(richtext-lexical): export serialized inline blocks and table node types 2024-08-06 11:42:09 -04:00
Paul
3bf09703e9 chore: turn off autocomplete for create first user form (#7538)
Turns off autocomplete on the first user form so it doesn't conflict
with wrong credentials being autofilled
2024-08-06 15:34:26 +00:00
Alessio Gravili
c15d679b65 fix(richtext-lexical)!: html converters not respecting overrideAccess property when populating values, in local API 2024-08-06 11:15:47 -04:00
Jarrod Flesch
a422a0d568 fix: scopes preferences queries and mutations by user (#7534)
Fixes https://github.com/payloadcms/payload/issues/7530

Properly scopes preferences queries/mutations by user.
2024-08-06 10:35:46 -04:00
Jarrod Flesch
edaeb1e29f fix: ensure pw confirmation when creating users in admin panel (#7535) 2024-08-06 10:31:08 -04:00
Jessica Chowdhury
6f35c356fe fix: custom meta icons getting overwritten by default icon (#7466)
## Description

Issue reported by Trading Point.

Payload favicon is still shown even when a custom icon is provided.

To replicate add to Payload config:
```ts
  admin: {
    meta: {
      icons: [
        {
          url: '/images/test.jpg',
          fetchPriority: 'high',
          sizes: '16x16',
        },
      ],
    },
  },
```

- [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-08-06 14:01:46 +00:00
Elliot DeNolf
0b9397399a chore(release): v3.0.0-beta.74 [skip ci] 2024-08-06 09:38:20 -04:00
Jarrod Flesch
cdcc35ccdb chore: fixes build error stemming from LoginField (#7532) 2024-08-06 09:15:21 -04:00
Jarrod Flesch
442189ec48 fix: email and username fields rendering in drawers (#7520)
Fixes https://github.com/payloadcms/payload/issues/7428

Now email and username fields are rendered with the RenderFields
component, making them behave similarly to other fields. They now appear
and can respect doc permissions, readOnly settings, etc.
2024-08-05 20:18:32 -04:00
Alessio Gravili
5d1cc760c9 fix(richtext-lexical): various table and icon style issues (#7522) 2024-08-05 22:10:18 +00:00
Alessio Gravili
2f90683c7d Merge PR: upgrade lexical, add table feature converter (#7521)
This PR
- upgrades lexical and ports all bug fixes from the playground over
- adds table action buttons. When hovering the edges of the table,
buttons pop up to easily add a new table column or row
- adds an html converter for the table feature
- makes the placeholder shown in the editor when no text is present
accessible

**BREAKING:** This upgrades lexical from 0.16.1 to 0.17.0. If you have
any lexical packages installed in your project, please update them
accordingly. Additionally, if you depend on the lexical APIs, please
consult their changelog, as lexical may introduce breaking changes:
https://github.com/facebook/lexical/releases/tag/v0.17.0
2024-08-05 17:18:57 -04:00
Patrik
3f5403a52a fix(ui): prevents hasMany text going outside of input boundaries (#7455)
## Description

V2 PR [here](https://github.com/payloadcms/payload/pull/7454)

`Before`:
![Screenshot 2024-07-31 at 12 40
50 PM](https://github.com/user-attachments/assets/ce61f4fc-e676-4273-aa4c-72610cb459b3)

`After`:
![Screenshot 2024-07-31 at 12 40
23 PM](https://github.com/user-attachments/assets/d92631eb-28fb-46ca-bc23-46c7916bba34)

- [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-08-05 17:10:35 -04:00
Alessio Gravili
9bccdfd60a feat(richtext-lexical): add HTML converter to table feature 2024-08-05 17:01:21 -04:00
Patrik
62666a9897 fix(ui): properly handles ID field component type based on payload.db.defaultIDType (#7416)
## Description

Fixes #7354 

Since the `defaultIDType` for IDs in `postgres` are of type `number` -
the `contains` operator should be available in the filter options.

This PR checks the `defaultIDType` of ID and properly outputs the
correct component type for IDs

I.e if ID is of type `number` - the filter operators for ID should
correspond to the the operators of type number as well

The `contains` operator only belongs on fields of type string, aka of
component type `text`

- [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-08-05 16:39:27 -04:00
Alessio Gravili
eb27b84854 chore(richtext-lexical): backport various minor bugfixes from lexical playground 2024-08-05 16:35:13 -04:00
Alessio Gravili
c3480811d3 feat(richtext-lexical): accessible editor placeholders 2024-08-05 16:19:02 -04:00
Alessio Gravili
12ba820de4 feat(richtext-lexical): add table hover actions to quickly add rows or columns 2024-08-05 16:08:31 -04:00
Elliot DeNolf
95fcd13929 fix(db-*): drizzle enums, bump drizzle-kit (#7514)
- bumps drizzle-kit
- Fixes https://github.com/payloadcms/payload/issues/7492 Enum issue.
2024-08-05 14:53:21 -04:00
Jarrod Flesch
6141c5950b chore: improves plugin creation docs (#7515) 2024-08-05 14:50:53 -04:00
Elliot DeNolf
0040e1756c fix(cpa): adjust template file location detection (#7507)
Adjust template file location detection. This was causing issues when
run with `pnpm create` because it is not run from a `dist` directory.

```
┌   create-payload-app
│
◇   ────────────────────────────────────────────╮
│                                               │
│  Welcome to Payload. Let's create a project!  │
│                                               │
├───────────────────────────────────────────────╯
│
▲  Payload installation detected in current project.
│
◇  Upgrade Payload in this project?
│  Yes
│
◇  Using pnpm.
│
│
◇  Updating 7 Payload packages to v3.0.0-beta.73...
│
│    - payload
│    - @payloadcms/db-mongodb
│    - @payloadcms/db-postgres
│    - @payloadcms/next
│    - @payloadcms/richtext-lexical
│    - @payloadcms/richtext-slate
│    - @payloadcms/ui
│
◇  Payload packages updated successfully.
│
◇  Updating Payload Next.js files...
│
■  ENOENT: no such file or directory, copyfile '/Users/elliot/Library/pnpm/store/v3/tmp/dlx-99797/node_modules/.pnpm/create-payload-app@3.0.0-beta.73/templates/blank-3.0/src/app/(payload)' -> '/Users/elliot/dev/payload-3.0-demo/src/app/(payload)'
```
2024-08-05 16:28:13 +00:00
Jarrod Flesch
1ebd54b315 feat: allows loginWithUsername to not require username (#7480)
Allows username to be optional when using the new loginWithUsername
feature. This can be done by the following:

```ts
auth: {
  loginWithUsername: {
    requireUsername: false, // <-- new property, default true
    requireEmail: false, // default: false
    allowEmailLogin: true, // default false
  },
},
```
2024-08-05 11:35:01 -04:00
Jessica Chowdhury
cdb2072a6d fix: error thrown in version view when localization is false (#7502)
## Description

`const localeValues = locales.map((locale) => locale.value)`

This line was previously throwing an error in the version view when
localization is false. Changed to ensure locales exist before mapping
over them.

- [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)
- [ ] 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
2024-08-05 15:10:02 +00:00
Tylan Davis
68553ff974 feat!: updated admin UI (#7424)
## Description

- Updates admin UI with more condensed spacing throughout.
- Improves hover states and read-only states for various components.
- Removes the `Merriweather` font from `next/font` and replaces with
stack of system serif fonts and fallbacks (Georgia, etc). Closes #7257

## BREAKING CHANGES
- Custom components and styling that don't utilize Payload's CSS/SCSS
variables may need adjustments to match the updated styling.
- If you are using the `Merriweather` font, you will need to manually
configure `next/font` in your own project.

---------

Co-authored-by: Paul Popus <paul@nouance.io>
2024-08-05 15:08:00 +00:00
Willy Brauner
9a3bce1118 feat: expose useTableColumns hook (#7448)
fix #4990 (v3)

## Description

Expose
[useTableColumns](b160686fff/packages/ui/src/elements/TableColumns/index.tsx (L25))
hook from client exported members of the ui packages.

The use of this hook, covered the case of custom ListView creation which
was not possible due to the lack of possibility to select a file if we
were in the "list-draw" view.

With `useTableColumns` we can execute the `onClick` defined in
`TableColumnsProvider` witch allows the selection on the clicked file.


b160686fff/packages/ui/src/elements/ListDrawer/DrawerContent.tsx (L290-L296)

## Use case

CustomListView.tsx:
```ts
const CustomListView = () => {
  // ...

  const tableColumns = useTableColumns()
  
  const handleItemClicked = (doc) => {
    const onClick = tableColumns.columns[0].cellProps?.onClick
    if (typeof onClick === 'function') {
      // we are in "list-drawer" view, execute the onClick function
      onClick({
        cellData: undefined,
        collectionSlug: doc,
        rowData: doc,
      })
    } else {
      // we are in "collection-admin" view, push the new route with next/navigation
      void router.push(`${collectionSlug}/${doc.id}`)
    }
  }
 
  return  <div className={"list"}>
            {data.docs?.length > 0 && (
              <RelationshipProvider>
                {docs.map((e, i) => (
                  <div className={"item"} key={i} onClick={() => handleItemClicked(e)}>
                     // ...
                  </div>
                ))}
              </RelationshipProvider>
            )}
          </div>
} 
```

This video shows the click of a file inside a CustomListView, in the
case of an "admin-collection" view then a "list-drawer" view.


https://github.com/user-attachments/assets/8aa17af5-a7aa-49de-b988-fc0db7ac8e47

- [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)
- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-08-05 14:52:47 +00:00
James Mikrut
005befcbe2 fix: #7488, cant deploy SQLite to Vercel (#7490)
## Description

Closes #7488 

Note - you'll also need to manually have `@libsql/client` installed in
your Next.js repository. This is not ideal, but it might be outside the
scope of what we can handle internally.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.
2024-08-05 10:41:12 -04:00
Alessio Gravili
e65b6478c9 feat(richtext-lexical)!: upgrade lexical from 0.16.1 to 0.17.0 2024-08-05 09:58:27 -04:00
Elliot DeNolf
a79e92a145 chore(release): v3.0.0-beta.73 [skip ci] 2024-08-02 09:53:56 -04:00
Alessio Gravili
995f51d941 fix: too many RSC props were being passed, inflating initial HTML size (#7474)
The following config caused the html size to grow to 500mb:

```ts
import type { ArrayField, Block, CollectionConfig } from 'payload'

import { BlocksFeature, lexicalEditor } from '@payloadcms/richtext-lexical'

const richTextLayoutBlockGridBoxes2: ArrayField = {
  name: 'gridBx',
  labels: { singular: 'Grid Box', plural: 'Grid Boxes' },
  type: 'array',
  fields: [
    {
      name: 'gridBx',
      label: 'Grid Box Content',
      type: 'blocks',
      maxRows: 1,
      blocks: [],
    },
  ],
}

const richTextLayoutBlock2: Block = {
  slug: 'layout',
  interfaceName: 'RichTextLayoutBlock',
  labels: { singular: 'Layout', plural: 'Layout' },
  fields: [richTextLayoutBlockGridBoxes2],
}

const richTextBlock2: Block = {
  slug: 'rich-text',
  interfaceName: 'RichTextBlock',
  labels: { singular: 'Rich Text', plural: 'Rich Text' },
  fields: [
    {
      name: 'richTextContent',
      label: 'Rich Text',
      type: 'richText',
      required: true,
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [
          ...defaultFeatures,
          BlocksFeature({ blocks: [richTextLayoutBlock2] }),
        ],
      }),
    },
  ],
}

const richTextLayoutBlockGridBoxes1: ArrayField = {
  name: 'gridBx',
  labels: { singular: 'Grid Box', plural: 'Grid Boxes' },
  type: 'array',
  fields: [
    {
      name: 'gridBx',
      label: 'Grid Box Content',
      type: 'blocks',
      maxRows: 1,
      blocks: [richTextBlock2],
    },
  ],
}

const richTextLayoutBlock1: Block = {
  slug: 'layout',
  interfaceName: 'RichTextLayoutBlock',
  labels: { singular: 'Layout', plural: 'Layout' },
  fields: [richTextLayoutBlockGridBoxes1],
}

const richTextBlock1: Block = {
  slug: 'rich-text',
  interfaceName: 'RichTextBlock',
  labels: { singular: 'Rich Text', plural: 'Rich Text' },
  fields: [
    {
      name: 'richTextContent',
      label: 'Rich Text',
      type: 'richText',
      required: true,
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [
          ...defaultFeatures,
          BlocksFeature({ blocks: [richTextLayoutBlock1] }),
        ],
      }),
    },
  ],
}

const richTextLayoutBlockGridBoxes: ArrayField = {
  name: 'gridBx',
  labels: { singular: 'Grid Box', plural: 'Grid Boxes' },
  type: 'array',
  fields: [
    {
      name: 'gridBx',
      label: 'Grid Box Content',
      type: 'blocks',
      maxRows: 1,
      blocks: [richTextBlock1],
    },
  ],
}

const richTextLayoutBlock: Block = {
  slug: 'layout',
  interfaceName: 'RichTextLayoutBlock',
  labels: { singular: 'Layout', plural: 'Layout' },
  fields: [richTextLayoutBlockGridBoxes],
}

const richTextBlock: Block = {
  slug: 'rich-text',
  interfaceName: 'RichTextBlock',
  labels: { singular: 'Rich Text', plural: 'Rich Text' },
  fields: [
    {
      name: 'richTextContent',
      label: 'Rich Text',
      type: 'richText',
      required: true,
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [
          ...defaultFeatures,
          BlocksFeature({ blocks: [richTextLayoutBlock] }),
        ],
      }),
    },
  ],
}

const layoutBlockGridBoxes2: ArrayField = {
  name: 'gridBx',
  label: 'Grid Boxes',
  type: 'array',
  fields: [
    {
      name: 'gridBx',
      label: 'Grid Box Content',
      type: 'blocks',
      maxRows: 1,
      blocks: [richTextBlock],
    },
  ],
}

const layoutBlock2: Block = {
  slug: 'layout',
  interfaceName: 'LayoutBlock',
  labels: { singular: 'Layout', plural: 'Layout' },
  fields: [layoutBlockGridBoxes2],
}

const layoutBlockGridBoxes1: ArrayField = {
  name: 'gridBx',
  label: 'Grid Boxes',
  type: 'array',
  fields: [
    {
      name: 'gridBx',
      label: 'Grid Box Content',
      type: 'blocks',
      maxRows: 1,
      blocks: [layoutBlock2, richTextBlock],
    },
  ],
}

const layoutBlock1: Block = {
  slug: 'layout',
  interfaceName: 'LayoutBlock',
  labels: { singular: 'Layout', plural: 'Layout' },
  fields: [layoutBlockGridBoxes1],
}

const layoutBlockGridBoxes: ArrayField = {
  name: 'gridBx',
  labels: { singular: 'Grid Box', plural: 'Grid Boxes' },
  type: 'array',
  fields: [
    {
      name: 'gridBx',
      label: 'Grid Box Content',
      type: 'blocks',
      maxRows: 1,
      blocks: [layoutBlock1, richTextBlock],
    },
  ],
}

const layoutBlock: Block = {
  slug: 'layout',
  interfaceName: 'LayoutBlock',
  labels: { singular: 'Layout', plural: 'Layout' },
  fields: [layoutBlockGridBoxes],
}

export const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'content',
      type: 'blocks',
      blocks: [layoutBlock],
    },
  ],
}
```

---------

Co-authored-by: James <james@trbl.design>
2024-08-02 13:17:56 +00:00
Radosław Kłos
4d19e64961 feat: adds upload's relationship thumbnail (#7473)
## Description
https://github.com/payloadcms/payload/pull/5015 's version for beta
branch. @JessChowdhury

- [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] New feature (non-breaking change which adds 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
- [X] Existing test suite passes locally with my changes
- [X] I have made corresponding changes to the documentation
2024-08-02 14:03:12 +01:00
Elliot DeNolf
31143599f6 feat(cpa): add sqlite (#7470)
Add `sqlite` as an option for create-payload-app.
2024-08-01 12:50:14 -04:00
Jessica Chowdhury
f752804410 fix: set active nav item (#7467)
## Description

Nav items not displaying different style when active.

We were previously using `NavLink` which determines if the item is
active and applies the classname. Now we are using the standard `Link`
and need to add the `active` classname manually.

- [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)
- [ ] 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
2024-08-01 16:01:57 +00:00
Elliot DeNolf
a18d4061ea chore(release): v3.0.0-beta.72 [skip ci] 2024-08-01 10:48:31 -04:00
Dan Ribbens
449c16d28f fix(db-postgres): incorrect schema type on adapter (#7459)
fixes a db-postgres type issue that was introduced in
https://github.com/payloadcms/payload/pull/7453
2024-08-01 10:39:54 -04:00
Jessica Chowdhury
d307d627ab feat: adds restore as draft option to versions (#7100)
## Description

Adds option to restore a version as a draft.

1. Run `versions` test suite
2. Go to `drafts` and choose any doc with `status: published`
3. Open the version
4. See new `restore as draft` option

<img width="1693" alt="Screenshot 2024-07-12 at 1 01 17 PM"
src="https://github.com/user-attachments/assets/14d4f806-c56c-46be-aa93-1a2bd04ffd5c">

- [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)
- [ ] 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
- [X] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-08-01 15:33:40 +01:00
Dan Ribbens
075819964d fix(db-postgres, db-sqlite): enum schema (#7453)
- updates drizzle-kit and drizzle-orm
- fix enum creation to fully support custom schemas
- sqlite by default will not use transactions
2024-07-31 16:42:00 -04:00
Dan Ribbens
1ec78a16f0 fix(db-postgres): localized array inside blocks field (#7457)
fixes #7371, #5240
2024-07-31 16:33:52 -04:00
Jarrod Flesch
290ffd3287 fix: validates password and confirm password on the server (#7410)
Fixes https://github.com/payloadcms/payload/issues/7380

Adjusts how the password/confirm-password fields are validated. Moves
validation to the server, adds them to a custom schema under the schema
path `${collectionSlug}.auth` for auth enabled collections.
2024-07-31 14:55:08 -04:00
Patrik
3d89508ce3 fix(ui): reincorporate basePath into logout component link (#7451)
## Description

Fixes issue where the `basePath` from the `next-config` was not
respected for the `logout` button link

- [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-07-31 11:00:02 -04:00
Elliot DeNolf
b160686fff ci: adjust canary release conditions 2024-07-31 00:39:22 -04:00
Elliot DeNolf
ba6ef6777f ci: auto release canary on success (#7444)
Automatically release canary on successful workflow.
2024-07-31 00:11:28 -04:00
Paul
febd7f7073 feat(ui): expose custom errors in deleteMany (#7438)
Closes https://github.com/payloadcms/payload/issues/7214
Exposes custom errors in the DeleteMany component so they can be more
descriptive:


![image](https://github.com/user-attachments/assets/f0c2f2e3-71a9-455f-9137-23eccfd21dbb)
2024-07-30 18:00:11 +00:00
Dan Ribbens
695ef32d1e feat(db-*): add defaultValues to database schemas (#7368)
## Description

Prior to this change, the `defaultValue` for fields have only been used
in the application layer of Payload. With this change, you get the added
benefit of having the database columns get the default also. This is
especially helpful when adding new columns to postgres with existing
data to avoid needing to write complex migrations. In MongoDB this
change applies the default to the Mongoose model which is useful when
calling payload.db.create() directly.

This only works for statically defined values.

🙏 A big thanks to @r1tsuu for the feature and implementation idea as I
lifted some code from PR https://github.com/payloadcms/payload/pull/6983

- [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] 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-07-30 13:41:18 -04:00
Elliot DeNolf
b5b2bb1907 fix(db-postgres): proper migrations table detection query (#7436)
Fixes postgres sql query to detect migrations table.

`error: syntax error at or near "exists"`
2024-07-30 11:38:28 -04:00
Elliot DeNolf
6f5cf5d916 feat(cpa): warn on unsupported Next.js version (#7434)
Improves messaging if running an unsupported version of Next.js.

Closes #7430
2024-07-30 11:14:57 -04:00
Alessio Gravili
aaf3a39f7e chore: upgrade typescript from 5.5.3 to 5.5.4 (#7435)
TypeScript 5.5.4 apparently speeds up linting by a lot:
https://github.com/microsoft/TypeScript/issues/59101
2024-07-30 15:09:28 +00:00
Jessica Chowdhury
5ef2951829 fix: formats locales for version comparison view (#7433)
Closes #7381
2024-07-30 10:24:55 -04:00
Paul
a943487fca fix: hide force unlock button if the user has no permissions to interact with it (#7418) 2024-07-29 20:49:57 +00:00
Jarrod Flesch
3a941c7c8a chore: duplicates prev value PRs from v2 (#7414)
Updates V3 with V2 PR's
- previousVersion type https://github.com/payloadcms/payload/pull/6805
- tests from https://github.com/payloadcms/payload/pull/6805
2024-07-29 16:28:28 -04:00
Dan Ribbens
354588898f feat(db-*, payload): better transactions (#7395)
## Description

### payload
- Removes calls to beginTransaction and commitTransaction from read
operations

### db-sqlite, db-postgres
- beginTransaction() options are passed through and used to create a
transaction
- declare module type adds beginTransaction with proper transaction
config args for postgres and sqlite
2024-07-29 15:35:19 -04:00
Jessica Chowdhury
ada9978a8c fix: page param not getting reset when applying filters (#7243)
Closes #7188

In the collection list view, after adding a filter, the page number
should be reset since the doc count will have changed.

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2024-07-29 13:25:43 -04:00
Jacob Fletcher
874279c530 fix(next): infinite loop when logging into root admin (#7412) 2024-07-29 12:57:57 -04:00
Paul
7ed6634bc5 fix: types for the 'validate' property across fields so internal validation functions can be reused (#7394)
Fixes the types for validate functions so that internal validation
functions can be re-used

Currently this has a type error
```ts
validate: (value, args) => {
  return text(value, args)
},
```
2024-07-29 12:36:28 -04:00
Michel v. Varendorff
09a0ee3ab9 fix: export default was not found in graphql (#6975) 2024-07-29 11:37:58 -04:00
Lynn Dylan Hurley
67acab2cd5 fix(searchPlugin): ensure search updates are unique to collection (#6363) 2024-07-29 11:33:59 -04:00
Jarrod Flesch
b3dc6cc811 chore: extends buildConfigWithDefaults to accept options arg (#7411) 2024-07-29 11:10:46 -04:00
Jarrod Flesch
5cd0c7ec7d fix: layout preferences for array/blocks were being saved twice in dev mode (#7396)
Fixes an issue where preferences for array/block collapsible's were not
being set correctly. React strict mode surfaced this issue.
2024-07-29 09:59:05 -04:00
525 changed files with 6649 additions and 3397 deletions

View File

@@ -196,6 +196,7 @@ jobs:
- postgres-custom-schema
- postgres-uuid
- supabase
- sqlite
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -548,3 +549,45 @@ jobs:
steps:
- if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
run: exit 1
publish-canary:
name: Publish Canary
if: github.ref == 'refs/heads/beta'
runs-on: ubuntu-latest
needs:
- all-green
steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Setup Node@${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Restore build
uses: actions/cache@v4
timeout-minutes: 10
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- name: Load npm token
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Canary release script
# dry run hard-coded to true for testing and no npm token provided
run: pnpm tsx ./scripts/publish-canary.ts
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true

14
.vscode/launch.json vendored
View File

@@ -56,6 +56,20 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "node --no-deprecation test/dev.js fields-relationship",
"cwd": "${workspaceFolder}",
"name": "Run Dev Fields-Relationship",
"request": "launch",
"type": "node-terminal"
},
{
"command": "node --no-deprecation test/dev.js login-with-username",
"cwd": "${workspaceFolder}",
"name": "Run Dev Login-With-Username",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm run dev plugin-cloud-storage",
"cwd": "${workspaceFolder}",

View File

@@ -205,7 +205,9 @@ export const MyField: Field = {
}
```
Default values can be defined as a static string or a function that returns a string. Functions are called with the following arguments:
Default values can be defined as a static value or a function that returns a value. When a `defaultValue` is defined statically, Payload's DB adapters will apply it to the database schema or models.
Functions can be written to make use of the following argument properties:
- `user` - the authenticated user object
- `locale` - the currently selected locale string

View File

@@ -57,6 +57,7 @@ export const MyUploadField: Field = {
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [Admin Options](../admin/fields#admin-options). |

View File

@@ -1,7 +1,7 @@
---
title: Building Your Own Plugin
label: Build Your Own
order: 50
order: 20
desc: Starting to build your own plugin? Find everything you need and learn best practices with the Payload plugin template.
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -159,36 +159,60 @@ export const seed = async (payload: Payload): Promise<void> => {
```
## Overview of the src folder
## Building a Plugin
Now that we have our environment setup and dev project ready to go - it&apos;s time to build the plugin!
**index.ts**
First up, the `src/index.ts` file - this is where the plugin should be imported from. It is best practice not to build the plugin directly in this file, instead we use this to export the plugin and types from their respective files.
**Plugin.ts**
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
```
import type { Config } from 'payload'
export const samplePlugin =
(pluginOptions: PluginTypes) =>
(incomingConfig: Config): Config => {
// create copy of incoming config
let config = { ...incomingConfig }
// do something cool with the config here
/**
* This is where you could modify the
* config based on the plugin options
*/
// If you wanted to add a new collection:
config.collections = [
...(config.collections || []),
newCollection,
]
// If you wanted to add a new global:
config.globals = [
...(config.globals || []),
newGlobal,
]
/**
* If you wanted to add a new field to a collection:
*
* 1. Loop over collections
* 2. Find the collection you want to add the field to
* 3. Add the field to the collection
*/
// If you wanted to add to the onInit:
config.onInit = async payload => {
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
// Add additional onInit code here
}
// Finally, return the modified config
return config
}
```
1. First, you need to receive the existing Payload Config along with any plugin options.
2. Then set the variable `config` to be equal to a copy of the existing config.
3. From here, you can extend the config however you like!
4. Finally, return the config and you&apos;re all set.
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
## Spread Syntax
### Spread syntax
[Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
@@ -206,7 +230,7 @@ config.collections = [
First, you need to spread the `config.collections` to ensure that we don&apos;t lose the existing collections. Then you can add any additional collections, just as you would in a regular Payload Config.
This same logic is applied to other properties like admin, globals, hooks:
This same logic is applied to other array and object like properties such as admin, globals and hooks:
```
config.globals = [
@@ -220,7 +244,10 @@ config.hooks = {
}
```
Some properties will be slightly different to extend, for instance the `onInit` property:
### Extending functions
Function properties cannot use spread syntax. The way to extend them is to execute the existing function if it exists and then run your additional functionality.
Here is an example extending the `onInit` property:
```
config.onInit = async payload => {
@@ -231,10 +258,6 @@ config.onInit = async payload => {
}
```
If you wish to add to the `onInit`, you must include the async/await. We don&apos;t use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
In the template, we have stubbed out a basic `onInitExtension` file that you can use, if not needed feel free to delete it.
## Types
If your plugin has options, you should define and provide types for these options in a separate file which gets exported from the main `index.ts`.

View File

@@ -1,7 +1,7 @@
---
title: Form Builder Plugin
label: Form Builder
order: 20
order: 40
desc: Easily build and manage forms from the Admin Panel. Send dynamic, personalized emails and even accept and process payments.
keywords: plugins, plugin, form, forms, form builder
---

View File

@@ -1,7 +1,7 @@
---
title: Nested Docs Plugin
label: Nested Docs
order: 20
order: 40
desc: Nested documents in a parent, child, and sibling relationship.
keywords: plugins, nested, documents, parent, child, sibling, relationship
---

View File

@@ -1,7 +1,7 @@
---
title: Redirects Plugin
label: Redirects
order: 20
order: 40
desc: Automatically create redirects for your Payload application
keywords: plugins, redirects, redirect, plugin, payload, cms, seo, indexing, search, search engine
---

View File

@@ -1,7 +1,7 @@
---
title: Search Plugin
label: Search
order: 20
order: 40
desc: Generates records of your documents that are extremely fast to search on.
keywords: plugins, search, search plugin, search engine, search index, search results, search bar, search box, search field, search form, search input
---

View File

@@ -1,7 +1,7 @@
---
title: Sentry Plugin
label: Sentry
order: 20
order: 40
desc: Integrate Sentry error tracking into your Payload application
keywords: plugins, sentry, error, tracking, monitoring, logging, bug, reporting, performance
---

View File

@@ -1,7 +1,7 @@
---
title: SEO Plugin
label: SEO
order: 20
order: 30
desc: Manage SEO metadata from your Payload admin
keywords: plugins, seo, meta, search, engine, ranking, google
---

View File

@@ -1,7 +1,7 @@
---
title: Stripe Plugin
label: Stripe
order: 20
order: 40
desc: Easily accept payments with Stripe
keywords: plugins, stripe, payments, ecommerce
---

View File

@@ -94,6 +94,7 @@ _An asterisk denotes that an option is required._
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). Crop is enabled by default. [More](#crop-and-focal-point-selector) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. |
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"private": true,
"type": "module",
"scripts": {
@@ -96,7 +96,7 @@
"devDependencies": {
"@jest/globals": "29.7.0",
"@libsql/client": "0.6.2",
"@next/bundle-analyzer": "15.0.0-canary.53",
"@next/bundle-analyzer": "15.0.0-canary.104",
"@payloadcms/eslint-config": "workspace:*",
"@payloadcms/eslint-plugin": "workspace:*",
"@payloadcms/live-preview-react": "workspace:*",
@@ -119,7 +119,7 @@
"create-payload-app": "workspace:*",
"cross-env": "7.0.3",
"dotenv": "16.4.5",
"drizzle-orm": "0.29.4",
"drizzle-orm": "0.32.1",
"escape-html": "^1.0.3",
"execa": "5.1.1",
"form-data": "3.0.1",
@@ -131,15 +131,15 @@
"lint-staged": "15.2.7",
"minimist": "1.2.8",
"mongodb-memory-server": "^9.0",
"next": "15.0.0-canary.53",
"next": "15.0.0-canary.104",
"open": "^10.1.0",
"p-limit": "^5.0.0",
"playwright": "1.43.0",
"playwright-core": "1.43.0",
"prettier": "3.3.2",
"prompts": "2.4.2",
"react": "^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0-rc-6230622a1a-20240610",
"react": "^19.0.0-rc-06d0b89e-20240801",
"react-dom": "^19.0.0-rc-06d0b89e-20240801",
"rimraf": "3.0.2",
"semver": "^7.5.4",
"sharp": "0.32.6",
@@ -150,11 +150,11 @@
"tempy": "1.0.1",
"tsx": "4.16.2",
"turbo": "^1.13.3",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"peerDependencies": {
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
},
"engines": {
"node": "^18.20.2 || >=20.9.0",

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -240,25 +240,63 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))
// Match next.config.js, next.config.ts, next.config.mjs, next.config.cjs
const nextConfigPath: string | undefined = (
await globby('next.config.*(t|j)s', { absolute: true, cwd: projectDir })
await globby('next.config.(\\w)?(t|j)s', { absolute: true, cwd: projectDir })
)?.[0]
if (!nextConfigPath || nextConfigPath.length === 0) {
return {
hasTopLevelLayout: false,
isSrcDir,
isSupportedNextVersion: false,
nextConfigPath: undefined,
nextVersion: null,
}
}
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
// Check if Next.js version is new enough
let nextVersion = null
if (packageObj.dependencies?.next) {
nextVersion = packageObj.dependencies.next
// Match versions using regex matching groups
const versionMatch = /(?<major>\d+)/.exec(nextVersion)
if (!versionMatch) {
p.log.warn(`Could not determine Next.js version from ${nextVersion}`)
return {
hasTopLevelLayout: false,
isSrcDir,
isSupportedNextVersion: false,
nextConfigPath,
nextVersion,
}
}
const { major } = versionMatch.groups as { major: string }
const majorVersion = parseInt(major)
if (majorVersion < 15) {
return {
hasTopLevelLayout: false,
isSrcDir,
isSupportedNextVersion: false,
nextConfigPath,
nextVersion,
}
}
}
const isSupportedNextVersion = true
// Check if Payload already installed
if (packageObj.dependencies?.payload) {
return {
hasTopLevelLayout: false,
isPayloadInstalled: true,
isSrcDir,
isSupportedNextVersion,
nextConfigPath,
nextVersion,
}
}
@@ -281,7 +319,15 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
: false
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath, nextConfigType: configType }
return {
hasTopLevelLayout,
isSrcDir,
isSupportedNextVersion,
nextAppDir,
nextConfigPath,
nextConfigType: configType,
nextVersion,
}
}
function getProjectType(args: {

View File

@@ -29,9 +29,22 @@ const postgresReplacement: DbAdapterReplacement = {
packageName: '@payloadcms/db-postgres',
}
const sqliteReplacement: DbAdapterReplacement = {
configReplacement: (envName = 'DATABASE_URI') => [
' db: sqliteAdapter({',
' client: {',
` url: process.env.${envName} || '',`,
' },',
' }),',
],
importReplacement: "import { sqliteAdapter } from '@payloadcms/db-sqlite'",
packageName: '@payloadcms/db-sqlite',
}
export const dbReplacements: Record<DbType, DbAdapterReplacement> = {
mongodb: mongodbReplacement,
postgres: postgresReplacement,
sqlite: sqliteReplacement,
}
type StorageAdapterReplacement = {

View File

@@ -5,6 +5,7 @@ import type { CliArgs, DbDetails, DbType } from '../types.js'
type DbChoice = {
dbConnectionPrefix: `${string}/`
dbConnectionSuffix?: string
title: string
value: DbType
}
@@ -20,6 +21,12 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
title: 'PostgreSQL (beta)',
value: 'postgres',
},
sqlite: {
dbConnectionPrefix: 'file:./',
dbConnectionSuffix: '.db',
title: 'SQLite (beta)',
value: 'sqlite',
},
}
export async function selectDb(args: CliArgs, projectName: string): Promise<DbDetails> {
@@ -37,10 +44,10 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
dbType = await p.select<{ label: string; value: DbType }[], DbType>({
initialValue: 'mongodb',
message: `Select a database`,
options: [
{ label: 'MongoDB', value: 'mongodb' },
{ label: 'Postgres', value: 'postgres' },
],
options: Object.values(dbChoiceRecord).map((dbChoice) => ({
label: dbChoice.title,
value: dbChoice.value,
})),
})
if (p.isCancel(dbType)) process.exit(0)
}
@@ -50,7 +57,7 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
let dbUri: string | symbol | undefined = undefined
const initialDbUri = `${dbChoice.dbConnectionPrefix}${
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
}`
}${dbChoice.dbConnectionSuffix || ''}`
if (args['--db-accept-recommended']) {
dbUri = initialDbUri

View File

@@ -74,9 +74,11 @@ export async function updatePayloadInProject(
info('Payload packages updated successfully.')
info(`Updating Payload Next.js files...`)
const templateFilesPath = dirname.endsWith('dist')
? path.resolve(dirname, '../..', 'dist/template')
: path.resolve(dirname, '../../../../templates/blank-3.0')
const templateFilesPath =
process.env.JEST_WORKER_ID !== undefined
? path.resolve(dirname, '../../../../templates/blank-3.0')
: path.resolve(dirname, '../..', 'dist/template')
const templateSrcDir = path.resolve(templateFilesPath, 'src/app/(payload)')

View File

@@ -85,7 +85,22 @@ export class Main {
// Detect if inside Next.js project
const nextAppDetails = await getNextAppDetails(process.cwd())
const { hasTopLevelLayout, isPayloadInstalled, nextAppDir, nextConfigPath } = nextAppDetails
const {
hasTopLevelLayout,
isPayloadInstalled,
isSupportedNextVersion,
nextAppDir,
nextConfigPath,
nextVersion,
} = nextAppDetails
if (nextConfigPath && !isSupportedNextVersion) {
p.log.warn(
`Next.js v${nextVersion} is unsupported. Next.js >= 15 is required to use Payload.`,
)
p.outro(feedbackOutro())
process.exit(0)
}
// Upgrade Payload in existing project
if (isPayloadInstalled && nextConfigPath) {

View File

@@ -57,7 +57,7 @@ interface Template {
export type PackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'
export type DbType = 'mongodb' | 'postgres'
export type DbType = 'mongodb' | 'postgres' | 'sqlite'
export type DbDetails = {
dbUri: string
@@ -70,9 +70,11 @@ export type NextAppDetails = {
hasTopLevelLayout: boolean
isPayloadInstalled?: boolean
isSrcDir: boolean
isSupportedNextVersion: boolean
nextAppDir?: string
nextConfigPath?: string
nextConfigType?: NextConfigType
nextVersion: null | string
}
export type NextConfigType = 'cjs' | 'esm' | 'ts'

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -54,6 +54,10 @@ export const connect: Connect = async function connect(
this.payload.logger.info('---- DROPPED DATABASE ----')
}
}
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
await this.migrate({ migrations: this.prodMigrations })
}
} catch (err) {
console.log(err)
this.payload.logger.error(`Error: cannot connect to MongoDB. Details: ${err.message}`, err)

View File

@@ -64,7 +64,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
forceCountFn: hasNearConstraint,
lean: true,
leanWithId: true,
offset: skip,
limit,
options,
page,
pagination,

View File

@@ -8,7 +8,7 @@ import mongoose from 'mongoose'
import path from 'path'
import { createDatabaseAdapter } from 'payload'
import type { CollectionModel, GlobalModel } from './types.js'
import type { CollectionModel, GlobalModel, MigrateDownArgs, MigrateUpArgs } from './types.js'
import { connect } from './connect.js'
import { count } from './count.js'
@@ -78,6 +78,11 @@ export interface Args {
* typed as any to avoid dependency
*/
mongoMemoryServer?: MongoMemoryReplSet
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
transactionOptions?: TransactionOptions | false
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
url: false | string
@@ -90,6 +95,11 @@ export type MongooseAdapter = {
connection: Connection
globals: GlobalModel
mongoMemoryServer: MongoMemoryReplSet
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
sessions: Record<number | string, ClientSession>
versions: {
[slug: string]: CollectionModel
@@ -107,6 +117,11 @@ declare module 'payload' {
connection: Connection
globals: GlobalModel
mongoMemoryServer: MongoMemoryReplSet
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
sessions: Record<number | string, ClientSession>
transactionOptions: TransactionOptions
versions: {
@@ -121,6 +136,7 @@ export function mongooseAdapter({
disableIndexHints = false,
migrationDir: migrationDirArg,
mongoMemoryServer,
prodMigrations,
transactionOptions = {},
url,
}: Args): DatabaseAdapterObj {
@@ -167,6 +183,7 @@ export function mongooseAdapter({
migrateFresh,
migrationDir,
payload,
prodMigrations,
queryDrafts,
rollbackTransaction,
updateGlobal,

View File

@@ -52,9 +52,19 @@ type FieldSchemaGenerator = (
buildSchemaOptions: BuildSchemaOptions,
) => void
/**
* get a field's defaultValue only if defined and not dynamic so that it can be set on the field schema
* @param field
*/
const formatDefaultValue = (field: FieldAffectingData) =>
typeof field.defaultValue !== 'undefined' && typeof field.defaultValue !== 'function'
? field.defaultValue
: undefined
const formatBaseSchema = (field: FieldAffectingData, buildSchemaOptions: BuildSchemaOptions) => {
const { disableUnique, draftsEnabled, indexSortableFields } = buildSchemaOptions
const schema: SchemaTypeOptions<unknown> = {
default: formatDefaultValue(field),
index: field.index || (!disableUnique && field.unique) || indexSortableFields || false,
required: false,
unique: (!disableUnique && field.unique) || false,
@@ -159,7 +169,6 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
},
}),
],
default: undefined,
}
schema.add({
@@ -174,7 +183,6 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
): void => {
const fieldSchema = {
type: [new mongoose.Schema({}, { _id: false, discriminatorKey: 'blockType' })],
default: undefined,
}
schema.add({
@@ -339,7 +347,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
},
coordinates: {
type: [Number],
default: field.defaultValue || undefined,
default: formatDefaultValue(field),
required: false,
},
}
@@ -420,7 +428,9 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
return {
...locales,
[locale]: field.hasMany ? { type: [localeSchema], default: undefined } : localeSchema,
[locale]: field.hasMany
? { type: [localeSchema], default: formatDefaultValue(field) }
: localeSchema,
}
}, {}),
localized: true,
@@ -440,7 +450,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
if (field.hasMany) {
schemaToReturn = {
type: [schemaToReturn],
default: undefined,
default: formatDefaultValue(field),
}
}
} else {
@@ -453,7 +463,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
if (field.hasMany) {
schemaToReturn = {
type: [schemaToReturn],
default: undefined,
default: formatDefaultValue(field),
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -47,8 +47,8 @@
"dependencies": {
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-orm": "0.29.4",
"drizzle-kit": "0.23.2-df9e596",
"drizzle-orm": "0.32.1",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",

View File

@@ -53,6 +53,7 @@ export const connect: Connect = async function connect(
const { hotReload } = options
this.schema = {
pgSchema: this.pgSchema,
...this.tables,
...this.relations,
...this.enums,
@@ -90,4 +91,8 @@ export const connect: Connect = async function connect(
}
if (typeof this.resolveInitializing === 'function') this.resolveInitializing()
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
await this.migrate({ migrations: this.prodMigrations })
}
}

View File

@@ -1,10 +1,10 @@
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { CreateMigration } from 'payload'
import fs from 'fs'
import { createRequire } from 'module'
import path from 'path'
import { getPredefinedMigration } from 'payload'
import { getPredefinedMigration, writeMigrationIndex } from 'payload'
import prompts from 'prompts'
import { fileURLToPath } from 'url'
@@ -25,7 +25,7 @@ export const createMigration: CreateMigration = async function createMigration(
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
const { generateDrizzleJson, generateMigration, upPgSnapshot } = require('drizzle-kit/api')
const drizzleJsonAfter = generateDrizzleJson(this.schema)
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
@@ -49,6 +49,12 @@ export const createMigration: CreateMigration = async function createMigration(
let drizzleJsonBefore = defaultDrizzleSnapshot
if (this.schemaName) {
drizzleJsonBefore.schemas = {
[this.schemaName]: this.schemaName,
}
}
if (!upSQL) {
// Get latest migration snapshot
const latestSnapshot = fs
@@ -58,9 +64,11 @@ export const createMigration: CreateMigration = async function createMigration(
.reverse()?.[0]
if (latestSnapshot) {
drizzleJsonBefore = JSON.parse(
fs.readFileSync(`${dir}/${latestSnapshot}`, 'utf8'),
) as DrizzleSnapshotJSON
drizzleJsonBefore = JSON.parse(fs.readFileSync(`${dir}/${latestSnapshot}`, 'utf8'))
if (drizzleJsonBefore.version < drizzleJsonAfter.version) {
drizzleJsonBefore = upPgSnapshot(drizzleJsonBefore)
}
}
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
@@ -107,5 +115,8 @@ export const createMigration: CreateMigration = async function createMigration(
upSQL: upSQL || ` // Migration code`,
}),
)
writeMigrationIndex({ migrationsDir: payload.db.migrationDir })
payload.logger.info({ msg: `Migration created at ${filePath}.ts` })
}

View File

@@ -1,4 +1,4 @@
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
export const defaultDrizzleSnapshot: DrizzleSnapshotJSON = {
id: '00000000-0000-0000-0000-000000000000',
@@ -7,10 +7,11 @@ export const defaultDrizzleSnapshot: DrizzleSnapshotJSON = {
schemas: {},
tables: {},
},
dialect: 'pg',
dialect: 'postgresql',
enums: {},
prevId: '00000000-0000-0000-0000-00000000000',
schemas: {},
sequences: {},
tables: {},
version: '5',
version: '7',
}

View File

@@ -32,6 +32,7 @@ import {
updateOne,
updateVersion,
} from '@payloadcms/drizzle'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { createDatabaseAdapter } from 'payload'
import type { Args, PostgresAdapter } from './types.js'
@@ -62,12 +63,19 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
const migrationDir = findMigrationDir(args.migrationDir)
let resolveInitializing
let rejectInitializing
let adapterSchema: PostgresAdapter['pgSchema']
const initializing = new Promise<void>((res, rej) => {
resolveInitializing = res
rejectInitializing = rej
})
if (args.schemaName) {
adapterSchema = pgSchema(args.schemaName)
} else {
adapterSchema = { enum: pgEnum, table: pgTable }
}
return createDatabaseAdapter<PostgresAdapter>({
name: 'postgres',
defaultDrizzleSnapshot,
@@ -83,9 +91,10 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger,
operators: operatorMap,
pgSchema: undefined,
pgSchema: adapterSchema,
pool: undefined,
poolOptions: args.pool,
prodMigrations: args.prodMigrations,
push: args.push,
relations: {},
relationshipsSuffix: args.relationshipsSuffix || '_rels',

View File

@@ -1,7 +1,6 @@
import type { Init, SanitizedCollectionConfig } from 'payload'
import { createTableName } from '@payloadcms/drizzle'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
import toSnakeCase from 'to-snake-case'
@@ -10,13 +9,8 @@ import type { PostgresAdapter } from './types.js'
import { buildTable } from './schema/build.js'
export const init: Init = function init(this: PostgresAdapter) {
if (this.schemaName) {
this.pgSchema = pgSchema(this.schemaName)
} else {
this.pgSchema = { table: pgTable }
}
if (this.payload.config.localization) {
this.enums.enum__locales = pgEnum(
this.enums.enum__locales = this.pgSchema.enum(
'_locales',
this.payload.config.localization.locales.map(({ code }) => code) as [string, ...string[]],
)

View File

@@ -1,5 +1,5 @@
import type { TransactionPg } from '@payloadcms/drizzle/types'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { Payload, PayloadRequest } from 'payload'
import { sql } from 'drizzle-orm'
@@ -43,7 +43,7 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => {
const dir = payload.db.migrationDir
// get the drizzle migrateUpSQL from drizzle using the last schema
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/api')
const drizzleJsonAfter = generateDrizzleJson(adapter.schema)
// Get the previous migration snapshot

View File

@@ -2,4 +2,4 @@ import type { RequireDrizzleKit } from '@payloadcms/drizzle/types'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/payload')
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/api')

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type {
ForeignKeyBuilder,

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import { index, uniqueIndex } from 'drizzle-orm/pg-core'
import type { GenericColumn } from '../types.js'

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
import type { Field, TabAsField } from 'payload'
@@ -19,7 +18,6 @@ import {
integer,
jsonb,
numeric,
pgEnum,
text,
timestamp,
varchar,
@@ -35,6 +33,7 @@ import { buildTable } from './build.js'
import { createIndex } from './createIndex.js'
import { idToUUID } from './idToUUID.js'
import { parentIDColumnMap } from './parentIDColumnMap.js'
import { withDefault } from './withDefault.js'
type Args = {
adapter: PostgresAdapter
@@ -170,14 +169,14 @@ export const traverseFields = ({
)
}
} else {
targetTable[fieldName] = varchar(columnName)
targetTable[fieldName] = withDefault(varchar(columnName), field)
}
break
}
case 'email':
case 'code':
case 'textarea': {
targetTable[fieldName] = varchar(columnName)
targetTable[fieldName] = withDefault(varchar(columnName), field)
break
}
@@ -199,23 +198,26 @@ export const traverseFields = ({
)
}
} else {
targetTable[fieldName] = numeric(columnName)
targetTable[fieldName] = withDefault(numeric(columnName), field)
}
break
}
case 'richText':
case 'json': {
targetTable[fieldName] = jsonb(columnName)
targetTable[fieldName] = withDefault(jsonb(columnName), field)
break
}
case 'date': {
targetTable[fieldName] = timestamp(columnName, {
mode: 'string',
precision: 3,
withTimezone: true,
})
targetTable[fieldName] = withDefault(
timestamp(columnName, {
mode: 'string',
precision: 3,
withTimezone: true,
}),
field,
)
break
}
@@ -234,7 +236,7 @@ export const traverseFields = ({
throwValidationError,
})
adapter.enums[enumName] = pgEnum(
adapter.enums[enumName] = adapter.pgSchema.enum(
enumName,
field.options.map((option) => {
if (optionIsObject(option)) {
@@ -311,13 +313,13 @@ export const traverseFields = ({
}),
)
} else {
targetTable[fieldName] = adapter.enums[enumName](fieldName)
targetTable[fieldName] = withDefault(adapter.enums[enumName](fieldName), field)
}
break
}
case 'checkbox': {
targetTable[fieldName] = boolean(columnName)
targetTable[fieldName] = withDefault(boolean(columnName), field)
break
}

View File

@@ -0,0 +1,17 @@
import type { PgColumnBuilder } from 'drizzle-orm/pg-core'
import type { FieldAffectingData } from 'payload'
export const withDefault = (
column: PgColumnBuilder,
field: FieldAffectingData,
): PgColumnBuilder => {
if (typeof field.defaultValue === 'undefined' || typeof field.defaultValue === 'function')
return column
if (typeof field.defaultValue === 'string' && field.defaultValue.includes("'")) {
const escapedString = field.defaultValue.replaceAll("'", "''")
return column.default(escapedString)
}
return column.default(field.defaultValue)
}

View File

@@ -4,7 +4,7 @@ import type {
DrizzleAdapter,
TransactionPg,
} from '@payloadcms/drizzle/types'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type {
ColumnBaseConfig,
ColumnDataType,
@@ -21,6 +21,7 @@ import type {
PgSchema,
PgTableWithColumns,
PgTransactionConfig,
pgEnum,
} from 'drizzle-orm/pg-core'
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
import type { Payload, PayloadRequest } from 'payload'
@@ -32,6 +33,11 @@ export type Args = {
logger?: DrizzleConfig['logger']
migrationDir?: string
pool: PoolConfig
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push?: boolean
relationshipsSuffix?: string
/**
@@ -106,6 +112,13 @@ type PostgresDrizzleAdapter = Omit<
| 'relations'
>
type Schema =
| {
enum: typeof pgEnum
table: PgTableFn
}
| PgSchema
export type PostgresAdapter = {
countDistinct: CountDistinct
defaultDrizzleSnapshot: DrizzleSnapshotJSON
@@ -125,15 +138,19 @@ export type PostgresAdapter = {
localesSuffix?: string
logger: DrizzleConfig['logger']
operators: Operators
pgSchema?: { table: PgTableFn } | PgSchema
pgSchema?: Schema
pool: Pool
poolOptions: Args['pool']
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relations: Record<string, GenericRelation>
relationshipsSuffix?: string
resolveInitializing: () => void
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
schemaName?: Args['schemaName']
sessions: {
[id: string]: {
@@ -156,6 +173,7 @@ declare module 'payload' {
export interface DatabaseAdapter
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
DrizzleAdapter {
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
drizzle: PostgresDB
enums: Record<string, GenericEnum>
/**
@@ -170,11 +188,16 @@ declare module 'payload' {
pgSchema?: { table: PgTableFn } | PgSchema
pool: Pool
poolOptions: Args['pool']
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relationshipsSuffix?: string
resolveInitializing: () => void
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
schema: Record<string, unknown>
schemaName?: Args['schemaName']
tableNameMap: Map<string, string>
versionsSuffix?: string

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -46,8 +46,8 @@
"@libsql/client": "^0.6.2",
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-orm": "0.29.4",
"drizzle-kit": "0.23.2-df9e596",
"drizzle-orm": "0.32.1",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"

View File

@@ -52,4 +52,8 @@ export const connect: Connect = async function connect(
}
if (typeof this.resolveInitializing === 'function') this.resolveInitializing()
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
await this.migrate({ migrations: this.prodMigrations })
}
}

View File

@@ -1,10 +1,10 @@
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { CreateMigration } from 'payload'
import fs from 'fs'
import { createRequire } from 'module'
import path from 'path'
import { getPredefinedMigration } from 'payload'
import { getPredefinedMigration, writeMigrationIndex } from 'payload'
import prompts from 'prompts'
import { fileURLToPath } from 'url'
@@ -25,7 +25,7 @@ export const createMigration: CreateMigration = async function createMigration(
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = require('drizzle-kit/payload')
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = require('drizzle-kit/api')
const drizzleJsonAfter = await generateSQLiteDrizzleJson(this.schema)
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
@@ -112,5 +112,8 @@ export const createMigration: CreateMigration = async function createMigration(
upSQL: upSQL || ` // Migration code`,
}),
)
writeMigrationIndex({ migrationsDir: payload.db.migrationDir })
payload.logger.info({ msg: `Migration created at ${filePath}.ts` })
}

View File

@@ -1,4 +1,4 @@
import type { DrizzleSQLiteSnapshotJSON } from 'drizzle-kit/payload'
import type { DrizzleSQLiteSnapshotJSON } from 'drizzle-kit/api'
export const defaultDrizzleSnapshot: DrizzleSQLiteSnapshotJSON = {
id: '00000000-0000-0000-0000-000000000000',
@@ -10,5 +10,5 @@ export const defaultDrizzleSnapshot: DrizzleSQLiteSnapshotJSON = {
enums: {},
prevId: '00000000-0000-0000-0000-00000000000',
tables: {},
version: '5',
version: '6',
}

View File

@@ -93,6 +93,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger,
operators,
prodMigrations: args.prodMigrations,
push: args.push,
relations: {},
relationshipsSuffix: args.relationshipsSuffix || '_rels',
@@ -105,7 +106,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
versionsSuffix: args.versionsSuffix || '_v',
// DatabaseAdapter
beginTransaction: args.transactionOptions === false ? undefined : beginTransaction,
beginTransaction: args.transactionOptions ? beginTransaction : undefined,
commitTransaction,
connect,
convertPathToJSONTraversal,

View File

@@ -10,6 +10,6 @@ export const requireDrizzleKit: RequireDrizzleKit = () => {
const {
generateSQLiteDrizzleJson: generateDrizzleJson,
pushSQLiteSchema: pushSchema,
} = require('drizzle-kit/payload')
} = require('drizzle-kit/api')
return { generateDrizzleJson, pushSchema }
}

View File

@@ -1,9 +1,8 @@
/* eslint-disable no-param-reassign */
import type { ColumnDataType, Relation } from 'drizzle-orm'
import type { Relation } from 'drizzle-orm'
import type {
AnySQLiteColumn,
ForeignKeyBuilder,
IndexBuilder,
SQLiteColumn,
SQLiteColumnBuilder,
SQLiteTableWithColumns,
UniqueConstraintBuilder,
@@ -32,18 +31,7 @@ import { traverseFields } from './traverseFields.js'
export type BaseExtraConfig = Record<
string,
(cols: {
[x: string]: SQLiteColumn<{
baseColumn: never
columnType: string
data: unknown
dataType: ColumnDataType
driverParam: unknown
enumValues: string[]
hasDefault: false
name: string
notNull: false
tableName: string
}>
[x: string]: AnySQLiteColumn
}) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
>

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-param-reassign */
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
import type { GenericColumn } from '../types.js'
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
type CreateIndexArgs = {
columnName: string
@@ -11,7 +10,7 @@ type CreateIndexArgs = {
}
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
return (table: { [x: string]: GenericColumn }) => {
return (table: { [x: string]: AnySQLiteColumn }) => {
let columns
if (Array.isArray(name)) {
columns = name

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
import type { Field, TabAsField } from 'payload'
@@ -30,6 +29,7 @@ import { buildTable } from './build.js'
import { createIndex } from './createIndex.js'
import { getIDColumn } from './getIDColumn.js'
import { idToUUID } from './idToUUID.js'
import { withDefault } from './withDefault.js'
type Args = {
adapter: SQLiteAdapter
@@ -166,14 +166,14 @@ export const traverseFields = ({
)
}
} else {
targetTable[fieldName] = text(columnName)
targetTable[fieldName] = withDefault(text(columnName), field)
}
break
}
case 'email':
case 'code':
case 'textarea': {
targetTable[fieldName] = text(columnName)
targetTable[fieldName] = withDefault(text(columnName), field)
break
}
@@ -195,19 +195,19 @@ export const traverseFields = ({
)
}
} else {
targetTable[fieldName] = numeric(columnName)
targetTable[fieldName] = withDefault(numeric(columnName), field)
}
break
}
case 'richText':
case 'json': {
targetTable[fieldName] = text(columnName, { mode: 'json' })
targetTable[fieldName] = withDefault(text(columnName, { mode: 'json' }), field)
break
}
case 'date': {
targetTable[fieldName] = text(columnName)
targetTable[fieldName] = withDefault(text(columnName), field)
break
}
@@ -295,13 +295,13 @@ export const traverseFields = ({
}),
)
} else {
targetTable[fieldName] = text(fieldName, { enum: options })
targetTable[fieldName] = withDefault(text(fieldName, { enum: options }), field)
}
break
}
case 'checkbox': {
targetTable[fieldName] = integer(columnName, { mode: 'boolean' })
targetTable[fieldName] = withDefault(integer(columnName, { mode: 'boolean' }), field)
break
}

View File

@@ -0,0 +1,17 @@
import type { SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
import type { FieldAffectingData } from 'payload'
export const withDefault = (
column: SQLiteColumnBuilder,
field: FieldAffectingData,
): SQLiteColumnBuilder => {
if (typeof field.defaultValue === 'undefined' || typeof field.defaultValue === 'function')
return column
if (typeof field.defaultValue === 'string' && field.defaultValue.includes("'")) {
const escapedString = field.defaultValue.replaceAll("'", "''")
return column.default(escapedString)
}
return column.default(field.defaultValue)
}

View File

@@ -1,10 +1,10 @@
import type { Client, Config, ResultSet } from '@libsql/client'
import type { Operators } from '@payloadcms/drizzle'
import type { BuildQueryJoinAliases, DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { ColumnDataType, DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
import type {
SQLiteColumn,
AnySQLiteColumn,
SQLiteInsertOnConflictDoUpdateConfig,
SQLiteTableWithColumns,
SQLiteTransactionConfig,
@@ -18,6 +18,11 @@ export type Args = {
localesSuffix?: string
logger?: DrizzleConfig['logger']
migrationDir?: string
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push?: boolean
relationshipsSuffix?: string
schemaName?: string
@@ -25,24 +30,8 @@ export type Args = {
versionsSuffix?: string
}
export type GenericColumn = SQLiteColumn<
{
baseColumn: never
columnType: string
data: unknown
dataType: ColumnDataType
driverParam: unknown
enumValues: string[]
hasDefault: false
name: string
notNull: false
tableName: string
},
object
>
export type GenericColumns = {
[x: string]: GenericColumn
[x: string]: AnySQLiteColumn
}
export type GenericTable = SQLiteTableWithColumns<{
@@ -116,6 +105,11 @@ export type SQLiteAdapter = {
localesSuffix?: string
logger: DrizzleConfig['logger']
operators: Operators
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relations: Record<string, GenericRelation>
@@ -144,6 +138,7 @@ declare module 'payload' {
export interface DatabaseAdapter
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
DrizzleAdapter {
beginTransaction: (options?: SQLiteTransactionConfig) => Promise<null | number | string>
drizzle: LibSQLDatabase
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
@@ -154,6 +149,11 @@ declare module 'payload' {
initializing: Promise<void>
localesSuffix?: string
logger: DrizzleConfig['logger']
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relationshipsSuffix?: string

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {
@@ -39,7 +39,7 @@
},
"dependencies": {
"console-table-printer": "2.11.2",
"drizzle-orm": "0.29.4",
"drizzle-orm": "0.32.1",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"

View File

@@ -1,5 +1,4 @@
import type { Count } from 'payload'
import type { SanitizedCollectionConfig } from 'payload'
import type { Count , SanitizedCollectionConfig } from 'payload'
import toSnakeCase from 'to-snake-case'
@@ -15,7 +14,7 @@ export const count: Count = async function count(
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const { joins, where } = await buildQuery({
adapter: this,

View File

@@ -10,7 +10,7 @@ export const create: Create = async function create(
this: DrizzleAdapter,
{ collection: collectionSlug, data, req },
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))

View File

@@ -10,7 +10,7 @@ export async function createGlobal<T extends Record<string, unknown>>(
this: DrizzleAdapter,
{ slug, data, req = {} as PayloadRequest }: CreateGlobalArgs,
): Promise<T> {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))

View File

@@ -12,7 +12,7 @@ export async function createGlobalVersion<T extends TypeWithID>(
this: DrizzleAdapter,
{ autosave, globalSlug, req = {} as PayloadRequest, versionData }: CreateGlobalVersionArgs,
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
const tableName = this.tableNameMap.get(`_${toSnakeCase(global.slug)}${this.versionsSuffix}`)

View File

@@ -18,7 +18,7 @@ export async function createVersion<T extends TypeWithID>(
versionData,
}: CreateVersionArgs<T>,
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const defaultTableName = toSnakeCase(collection.slug)

View File

@@ -11,7 +11,7 @@ export const deleteMany: DeleteMany = async function deleteMany(
this: DrizzleAdapter,
{ collection, req = {} as PayloadRequest, where },
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collectionConfig = this.payload.collections[collection].config
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))

View File

@@ -14,7 +14,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
this: DrizzleAdapter,
{ collection: collectionSlug, req = {} as PayloadRequest, where: whereArg },
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))

View File

@@ -12,7 +12,7 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
this: DrizzleAdapter,
{ collection, locale, req = {} as PayloadRequest, where: where },
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = this.tableNameMap.get(

View File

@@ -1,4 +1,3 @@
import type { Payload, PayloadRequest } from 'payload'
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
@@ -9,9 +8,12 @@ import type { DrizzleAdapter, Migration } from './types.js'
import { migrationTableExists } from './utilities/migrationTableExists.js'
import { parseError } from './utilities/parseError.js'
export async function migrate(this: DrizzleAdapter): Promise<void> {
export const migrate: DrizzleAdapter['migrate'] = async function migrate(
this: DrizzleAdapter,
args,
): Promise<void> {
const { payload } = this
const migrationFiles = await readMigrationFiles({ payload })
const migrationFiles = args?.migrations || (await readMigrationFiles({ payload }))
if (!migrationFiles.length) {
payload.logger.info({ msg: 'No migrations to run.' })
@@ -64,7 +66,7 @@ export async function migrate(this: DrizzleAdapter): Promise<void> {
// If already ran, skip
if (alreadyRan) {
continue
continue
}
await runMigrationFile(payload, migration, newBatch)
@@ -72,16 +74,12 @@ export async function migrate(this: DrizzleAdapter): Promise<void> {
}
async function runMigrationFile(payload: Payload, migration: Migration, batch: number) {
const db = payload.db as DrizzleAdapter
const { generateDrizzleJson } = db.requireDrizzleKit()
const start = Date.now()
const req = { payload } as PayloadRequest
const adapter = payload.db as DrizzleAdapter
payload.logger.info({ msg: `Migrating: ${migration.name}` })
const drizzleJSON = await generateDrizzleJson({ schema: adapter.schema })
try {
await initTransaction(req)
const db = adapter?.sessions[await req.transactionID]?.db || adapter.drizzle
@@ -92,7 +90,6 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n
data: {
name: migration.name,
batch,
schema: drizzleJSON,
},
req,
})

View File

@@ -42,7 +42,7 @@ export async function migrateFresh(
await this.dropDatabase({ adapter: this })
const migrationFiles = (await readMigrationFiles({ payload })) as Migration[]
const migrationFiles = await readMigrationFiles({ payload })
payload.logger.debug({
msg: `Found ${migrationFiles.length} migration files.`,
})

View File

@@ -6,6 +6,7 @@ import type { DrizzleAdapter, DrizzleTransaction } from '../types.js'
export const beginTransaction: BeginTransaction = async function beginTransaction(
this: DrizzleAdapter,
options: DrizzleAdapter['transactionOptions'],
) {
let id
try {
@@ -41,7 +42,7 @@ export const beginTransaction: BeginTransaction = async function beginTransactio
}
transactionReady()
})
}, this.transactionOptions)
}, options || this.transactionOptions)
.catch(() => {
// swallow
})

View File

@@ -120,6 +120,7 @@ export type RequireDrizzleKit = () => {
pushSchema: (
schema: Record<string, unknown>,
drizzle: DrizzleAdapter['drizzle'],
filterSchema?: string[],
) => Promise<{ apply; hasDataLoss; warnings }>
}
@@ -132,7 +133,7 @@ export type Migration = {
db?: DrizzleTransaction | LibSQLDatabase<Record<string, never>> | PostgresDB
payload: Payload
req: PayloadRequest
}) => Promise<boolean>
}) => Promise<void>
up: ({
db,
payload,
@@ -141,7 +142,7 @@ export type Migration = {
db?: DrizzleTransaction | LibSQLDatabase | PostgresDB
payload: Payload
req: PayloadRequest
}) => Promise<boolean>
}) => Promise<void>
} & MigrationData
export type CreateJSONQueryArgs = {

View File

@@ -12,7 +12,7 @@ export const updateOne: UpdateOne = async function updateOne(
this: DrizzleAdapter,
{ id, collection: collectionSlug, data, draft, locale, req, where: whereArg },
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
const whereToUse = whereArg || { id: { equals: id } }

View File

@@ -10,7 +10,7 @@ export async function updateGlobal<T extends Record<string, unknown>>(
this: DrizzleAdapter,
{ slug, data, req = {} as PayloadRequest }: UpdateGlobalArgs,
): Promise<T> {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))

View File

@@ -25,7 +25,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
where: whereArg,
}: UpdateGlobalVersionArgs<T>,
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
({ slug }) => slug === global,
)

View File

@@ -25,7 +25,7 @@ export async function updateVersion<T extends TypeWithID>(
where: whereArg,
}: UpdateVersionArgs<T>,
) {
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const whereToUse = whereArg || { id: { equals: id } }
const tableName = this.tableNameMap.get(

View File

@@ -4,8 +4,10 @@ import { fieldAffectsData, fieldHasSubFields } from 'payload/shared'
export const hasLocalesTable = (fields: Field[]): boolean => {
return fields.some((field) => {
// arrays always get a separate table
if (field.type === 'array') return false
if (fieldAffectsData(field) && field.localized) return true
if (fieldHasSubFields(field) && field.type !== 'array') return hasLocalesTable(field.fields)
if (fieldHasSubFields(field)) return hasLocalesTable(field.fields)
if (field.type === 'tabs') return field.tabs.some((tab) => hasLocalesTable(tab.fields))
return false
})

View File

@@ -5,7 +5,7 @@ export const migrationTableExists = async (adapter: DrizzleAdapter): Promise<boo
if (adapter.name === 'postgres') {
const prependSchema = adapter.schemaName ? `"${adapter.schemaName}".` : ''
statement = `SELECT to_regclass('${prependSchema}"payload_migrations"') exists;`
statement = `SELECT to_regclass('${prependSchema}"payload_migrations"') AS exists;`
}
if (adapter.name === 'sqlite') {

View File

@@ -12,7 +12,11 @@ export const pushDevSchema = async (adapter: DrizzleAdapter) => {
const { pushSchema } = adapter.requireDrizzleKit()
// This will prompt if clarifications are needed for Drizzle to push new schema
const { apply, hasDataLoss, warnings } = await pushSchema(adapter.schema, adapter.drizzle)
const { apply, hasDataLoss, warnings } = await pushSchema(
adapter.schema,
adapter.drizzle,
adapter.schemaName ? [adapter.schemaName] : undefined,
)
if (warnings.length) {
let message = `Warnings detected during schema push: \n\n${warnings.join('\n')}\n\n`

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -33,7 +33,7 @@
"eslint-plugin-react-hooks": "5.1.0-rc-85acf2d195-20240711",
"eslint-plugin-regexp": "2.6.0",
"globals": "15.8.0",
"typescript": "5.5.3",
"typescript": "5.5.4",
"typescript-eslint": "7.16.0"
}
}

View File

@@ -32,7 +32,7 @@
"eslint-plugin-react-hooks": "5.1.0-rc-85acf2d195-20240711",
"eslint-plugin-regexp": "2.6.0",
"globals": "15.8.0",
"typescript": "5.5.3",
"typescript": "5.5.4",
"typescript-eslint": "7.16.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,3 +1,3 @@
export { GraphQLJSON, GraphQLJSONObject } from '../packages/graphql-type-json/index.js'
export { buildPaginatedListType } from '../schema/buildPaginatedListType.js'
export { default as GraphQL } from 'graphql'
export * as GraphQL from 'graphql'

View File

@@ -7,6 +7,7 @@ import type { Context } from '../types.js'
export type Resolver = (
_: unknown,
args: {
draft?: boolean
id: number | string
},
context: {
@@ -20,6 +21,7 @@ export default function restoreVersionResolver(collection: Collection): Resolver
id: args.id,
collection,
depth: 0,
draft: args.draft,
req: isolateObjectProperty(context.req, 'transactionID'),
}

View File

@@ -7,6 +7,7 @@ import type { Context } from '../types.js'
type Resolver = (
_: unknown,
args: {
draft?: boolean
id: number | string
},
context: {
@@ -18,6 +19,7 @@ export default function restoreVersionResolver(globalConfig: SanitizedGlobalConf
const options = {
id: args.id,
depth: 0,
draft: args.draft,
globalConfig,
req: isolateObjectProperty(context.req, 'transactionID'),
}

View File

@@ -342,6 +342,7 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
type: collection.graphQL.type,
args: {
id: { type: versionIDType },
draft: { type: GraphQLBoolean },
},
resolve: restoreVersionResolver(collection),
}

View File

@@ -133,6 +133,7 @@ function initGlobalsGraphQL({ config, graphqlResult }: InitGlobalsGraphQLArgs):
type: graphqlResult.globals.graphQL[slug].versionType,
args: {
id: { type: idType },
draft: { type: GraphQLBoolean },
...(config.localization
? {
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
@@ -171,6 +172,7 @@ function initGlobalsGraphQL({ config, graphqlResult }: InitGlobalsGraphQLArgs):
type: graphqlResult.globals.graphQL[slug].type,
args: {
id: { type: idType },
draft: { type: GraphQLBoolean },
},
resolve: restoreVersionResolver(global),
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {
@@ -41,8 +41,8 @@
"payload": "workspace:*"
},
"peerDependencies": {
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
},
"publishConfig": {
"exports": {

View File

@@ -39,6 +39,9 @@ export const RefreshRouteOnSave: React.FC<{
ready({
serverURL,
})
// refresh after the ready message is sent to get the latest data
refresh()
}
return () => {
@@ -46,7 +49,7 @@ export const RefreshRouteOnSave: React.FC<{
window.removeEventListener('message', onMessage)
}
}
}, [serverURL, onMessage, depth, apiRoute])
}, [serverURL, onMessage, depth, apiRoute, refresh])
return null
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.71",
"version": "3.0.0-beta.77",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",
@@ -94,7 +94,7 @@
},
"peerDependencies": {
"graphql": "^16.8.1",
"next": "^15.0.0-canary.53",
"next": "^15.0.0-canary.104",
"payload": "workspace:*"
},
"engines": {

View File

@@ -35,7 +35,7 @@
position: absolute;
width: 100%;
height: 100%;
border-radius: 2px;
border-radius: var(--style-radius-s);
background-color: var(--theme-elevation-50);
opacity: 0;
}
@@ -51,6 +51,7 @@
}
&--active {
font-weight: 600;
&::before {
opacity: 1;
background-color: var(--theme-elevation-100);
@@ -78,14 +79,15 @@
gap: 4px;
width: 100%;
height: 100%;
padding: calc(var(--base) / 2) calc(var(--base));
line-height: base(1.2);
padding: base(0.2) base(0.6);
}
&__count {
min-width: 22px;
line-height: base(0.8);
min-width: base(0.8);
text-align: center;
padding: 2px 7px;
background-color: var(--theme-elevation-100);
border-radius: 1px;
border-radius: var(--style-radius-s);
}
}

View File

@@ -1,6 +1,6 @@
'use client'
import { useDocumentInfo } from '@payloadcms/ui'
import React, { Fragment } from 'react'
import React from 'react'
import { baseClass } from '../../Tab/index.js'
@@ -12,13 +12,14 @@ export const VersionsPill: React.FC = () => {
// documents that are version enabled _always_ have at least one version
const hasVersions = versions?.totalDocs > 0
return (
<span
className={[`${baseClass}__count`, hasVersions ? `${baseClass}__count--has-count` : '']
.filter(Boolean)
.join(' ')}
>
{hasVersions ? versions.totalDocs.toString() : <Fragment>&nbsp;</Fragment>}
</span>
)
if (hasVersions)
return (
<span
className={[`${baseClass}__count`, hasVersions ? `${baseClass}__count--has-count` : '']
.filter(Boolean)
.join(' ')}
>
{versions.totalDocs.toString()}
</span>
)
}

View File

@@ -0,0 +1,103 @@
'use client'
import type { FieldPermissions, LoginWithUsernameOptions } from 'payload'
import { EmailField, RenderFields, TextField, useTranslation } from '@payloadcms/ui'
import { email, username } from 'payload/shared'
import React from 'react'
type Props = {
loginWithUsername?: LoginWithUsernameOptions | false
}
function EmailFieldComponent(props: Props) {
const { loginWithUsername } = props
const { t } = useTranslation()
const requireEmail = !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail)
const showEmailField =
!loginWithUsername || loginWithUsername?.requireEmail || loginWithUsername?.allowEmailLogin
if (showEmailField) {
return (
<EmailField
autoComplete="off"
label={t('general:email')}
name="email"
path="email"
required={requireEmail}
validate={email}
/>
)
}
return null
}
function UsernameFieldComponent(props: Props) {
const { loginWithUsername } = props
const { t } = useTranslation()
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
const showUsernameField = Boolean(loginWithUsername)
if (showUsernameField) {
return (
<TextField
label={t('authentication:username')}
name="username"
path="username"
required={requireUsername}
validate={username}
/>
)
}
return null
}
type RenderEmailAndUsernameFieldsProps = {
className?: string
loginWithUsername?: LoginWithUsernameOptions | false
operation?: 'create' | 'update'
permissions?: {
[fieldName: string]: FieldPermissions
}
readOnly: boolean
}
export function RenderEmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps) {
const { className, loginWithUsername, operation, permissions, readOnly } = props
return (
<RenderFields
className={className}
fieldMap={[
{
name: 'email',
type: 'text',
CustomField: <EmailFieldComponent loginWithUsername={loginWithUsername} />,
cellComponentProps: null,
fieldComponentProps: { type: 'email', autoComplete: 'off', readOnly },
fieldIsPresentational: false,
isFieldAffectingData: true,
localized: false,
},
{
name: 'username',
type: 'text',
CustomField: <UsernameFieldComponent loginWithUsername={loginWithUsername} />,
cellComponentProps: null,
fieldComponentProps: { type: 'text', readOnly },
fieldIsPresentational: false,
isFieldAffectingData: true,
localized: false,
},
]}
forceRender
operation={operation}
path=""
permissions={permissions}
readOnly={readOnly}
schemaPath=""
/>
)
}

View File

@@ -37,10 +37,12 @@ const Component: React.FC<{
<p>{t('general:changesNotSaved')}</p>
</div>
<div className={`${baseClass}__controls`}>
<Button buttonStyle="secondary" onClick={onCancel}>
<Button buttonStyle="secondary" onClick={onCancel} size="large">
{t('general:stayOnThisPage')}
</Button>
<Button onClick={onConfirm}>{t('general:leaveAnyway')}</Button>
<Button onClick={onConfirm} size="large">
{t('general:leaveAnyway')}
</Button>
</div>
</div>
</Modal>

View File

@@ -14,6 +14,7 @@ import {
} from '@payloadcms/ui'
import { EntityType, formatAdminURL, groupNavItems } from '@payloadcms/ui/shared'
import LinkWithDefault from 'next/link.js'
import { usePathname } from 'next/navigation.js'
import React, { Fragment } from 'react'
const baseClass = 'nav'
@@ -21,6 +22,7 @@ const baseClass = 'nav'
export const DefaultNavClient: React.FC = () => {
const { permissions } = useAuth()
const { isEntityVisible } = useEntityVisibility()
const pathname = usePathname()
const {
collections,
@@ -84,18 +86,23 @@ export const DefaultNavClient: React.FC = () => {
LinkWithDefault) as typeof LinkWithDefault.default
const LinkElement = Link || 'a'
const activeCollection = pathname.endsWith(href)
return (
<LinkElement
className={`${baseClass}__link`}
className={[`${baseClass}__link`, activeCollection && `active`]
.filter(Boolean)
.join(' ')}
href={href}
id={id}
key={i}
tabIndex={!navOpen ? -1 : undefined}
>
<span className={`${baseClass}__link-icon`}>
<ChevronIcon direction="right" />
</span>
{activeCollection && (
<span className={`${baseClass}__link-icon`}>
<ChevronIcon direction="right" />
</span>
)}
<span className={`${baseClass}__link-label`}>{entityLabel}</span>
</LinkElement>
)

View File

@@ -110,16 +110,9 @@
&__link {
display: flex;
align-items: center;
&.active {
.nav__link-icon {
display: block;
}
}
}
&__link-icon {
display: none;
margin-right: calc(var(--base) * 0.25);
top: -1px;
position: relative;

View File

@@ -1,13 +1,13 @@
import type { AcceptedLanguages, I18nClient } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload'
import type { PayloadRequest, SanitizedConfig } from 'payload'
import { initI18n, rtlLanguages } from '@payloadcms/translations'
import { RootProvider } from '@payloadcms/ui'
import '@payloadcms/ui/scss/app.scss'
import { buildComponentMap } from '@payloadcms/ui/utilities/buildComponentMap'
import { Merriweather } from 'next/font/google'
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
import { createClientConfig, parseCookies } from 'payload'
import { createClientConfig, createLocalReq, parseCookies } from 'payload'
import * as qs from 'qs-esm'
import React from 'react'
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
@@ -16,14 +16,6 @@ import { getRequestTheme } from '../../utilities/getRequestTheme.js'
import { DefaultEditView } from '../../views/Edit/Default/index.js'
import { DefaultListView } from '../../views/List/Default/index.js'
const merriweather = Merriweather({
display: 'swap',
style: ['normal', 'italic'],
subsets: ['latin'],
variable: '--font-serif',
weight: ['400', '900'],
})
export const metadata = {
description: 'Generated by Next.js',
title: 'Next.js',
@@ -61,6 +53,20 @@ export const RootLayout = async ({
language: languageCode,
})
const req = await createLocalReq(
{
fallbackLocale: null,
req: {
headers,
host: headers.get('host'),
i18n,
url: `${payload.config.serverURL}`,
} as PayloadRequest,
},
payload,
)
const { permissions, user } = await payload.auth({ headers, req })
const clientConfig = await createClientConfig({ config, t: i18n.t })
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
@@ -100,7 +106,7 @@ export const RootLayout = async ({
})
return (
<html className={merriweather.variable} data-theme={theme} dir={dir} lang={languageCode}>
<html data-theme={theme} dir={dir} lang={languageCode}>
<body>
<RootProvider
componentMap={componentMap}
@@ -109,9 +115,11 @@ export const RootLayout = async ({
fallbackLang={clientConfig.i18n.fallbackLanguage}
languageCode={languageCode}
languageOptions={languageOptions}
permissions={permissions}
switchLanguageServerAction={switchLanguageServerAction}
theme={theme}
translations={i18n.translations}
user={user}
>
{wrappedChildren}
</RootProvider>

View File

@@ -14,6 +14,7 @@ export const restoreVersion: CollectionRouteHandlerWithID = async ({
}) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const draft = searchParams.get('draft')
const id = sanitizeCollectionID({
id: incomingID,
@@ -25,6 +26,7 @@ export const restoreVersion: CollectionRouteHandlerWithID = async ({
id,
collection,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: draft === 'true' ? true : undefined,
req,
})

View File

@@ -9,10 +9,12 @@ import { headersWithCors } from '../../../utilities/headersWithCors.js'
export const restoreVersion: GlobalRouteHandlerWithID = async ({ id, globalConfig, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const draft = searchParams.get('draft')
const doc = await restoreVersionOperationGlobal({
id,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: draft === 'true' ? true : undefined,
globalConfig,
req,
})

View File

@@ -1,9 +1,9 @@
@import './styles.scss';
@import 'styles';
@import './toasts.scss';
@import './colors.scss';
:root {
--base-px: 25;
--base-px: 20;
--base-body-size: 13;
--base: calc((var(--base-px) / var(--base-body-size)) * 1rem);
@@ -21,6 +21,7 @@
--theme-baseline-body-size: #{$baseline-body-size};
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
--font-serif: Georgia, 'Bitstream Charter', 'Charis SIL', Utopia, 'URW Bookman L', serif;
--font-mono: monospace;
--style-radius-s: #{$style-radius-s};
@@ -67,12 +68,6 @@ html {
@extend %body;
background: var(--theme-bg);
-webkit-font-smoothing: antialiased;
opacity: 0;
&[data-theme='dark'],
&[data-theme='light'] {
opacity: initial;
}
&[data-theme='dark'] {
--theme-bg: var(--theme-elevation-0);
@@ -111,12 +106,12 @@ body {
}
::selection {
background: var(--theme-success-500);
background: var(--color-success-250);
color: var(--theme-base-800);
}
::-moz-selection {
background: var(--theme-success-500);
background: var(--color-success-250);
color: var(--theme-base-800);
}

View File

@@ -0,0 +1,59 @@
@import 'vars';
@import 'queries';
.Toastify {
.Toastify__toast-container {
left: base(5);
transform: none;
right: base(5);
width: auto;
}
.Toastify__toast {
padding: base(0.5);
border-radius: $style-radius-m;
font-weight: 600;
}
.Toastify__close-button {
align-self: center;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
.Toastify__toast--success {
color: var(--color-success-900);
background: var(--color-success-500);
.Toastify__progress-bar {
background-color: var(--color-success-900);
}
}
.Toastify__close-button--success {
color: var(--color-success-900);
}
.Toastify__toast--error {
background: var(--theme-error-500);
color: #fff;
.Toastify__progress-bar {
background-color: #fff;
}
}
.Toastify__close-button--light {
color: inherit;
}
@include mid-break {
.Toastify__toast-container {
left: $baseline;
right: $baseline;
}
}
}

View File

@@ -1,24 +1,28 @@
@import './styles.scss';
.payload-toast-container {
padding: 0;
margin: 0;
.payload-toast-close-button {
position: absolute;
order: 3;
left: unset;
right: 0.5rem;
top: 1.55rem;
color: var(--theme-elevation-400);
inset-inline-end: base(0.5);
top: 50%;
transform: translateY(-50%);
color: var(--theme-elevation-600);
background: unset;
border: none;
display: flex;
width: 1.25rem;
height: 1.25rem;
justify-content: center;
align-items: center;
&:hover {
background: none;
}
svg {
width: 2rem;
height: 2rem;
width: base(0.75);
height: base(0.75);
}
&:hover {
color: var(--theme-elevation-250);
background: none;
}
[dir='RTL'] & {
@@ -27,16 +31,20 @@
}
}
.toast-title {
line-height: base(1);
}
.payload-toast-item {
padding: 1rem 2.5rem 1rem 1rem;
color: var(--theme-text);
padding: base(0.5);
color: var(--theme-elevation-800);
font-style: normal;
font-weight: 600;
display: flex;
gap: 1rem;
align-items: center;
width: 100%;
border-radius: 0.15rem;
border-radius: 4px;
border: 1px solid var(--theme-border-color);
background: var(--theme-input-bg);
box-shadow:
@@ -45,6 +53,7 @@
.toast-content {
transition: opacity 100ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
width: 100%;
}
&[data-front='false'] {
@@ -60,51 +69,72 @@
}
.toast-icon {
svg {
width: 2.4rem;
height: 2.4rem;
width: base(1);
height: base(1);
margin: 0;
display: flex;
align-items: center;
justify-content: center;
& > * {
width: base(1.2);
height: base(1.2);
}
}
&.toast-warning {
border-color: var(--theme-warning-200);
background-color: var(--theme-warning-100);
color: var(--theme-warning-800);
border-color: var(--theme-warning-150);
background-color: var(--theme-warning-50);
.payload-toast-close-button {
color: var(--theme-warning-600);
&:hover {
color: var(--theme-warning-250);
}
}
}
&.toast-error {
border-color: var(--theme-error-300);
background-color: var(--theme-error-150);
color: var(--theme-error-800);
border-color: var(--theme-error-150);
background-color: var(--theme-error-50);
.payload-toast-close-button {
color: var(--theme-error-600);
&:hover {
color: var(--theme-error-250);
}
}
}
&.toast-success {
border-color: var(--theme-success-200);
background-color: var(--theme-success-100);
color: var(--theme-success-800);
border-color: var(--theme-success-150);
background-color: var(--theme-success-50);
.payload-toast-close-button {
color: var(--theme-success-600);
&:hover {
color: var(--theme-success-250);
}
}
}
&.toast-info {
border-color: var(--theme-elevation-250);
background-color: var(--theme-elevation-100);
}
color: var(--theme-elevation-800);
border-color: var(--theme-elevation-150);
background-color: var(--theme-elevation-50);
[data-theme='light'] & {
&.toast-warning {
border-color: var(--theme-warning-550);
background-color: var(--theme-warning-100);
}
.payload-toast-close-button {
color: var(--theme-elevation-600);
&.toast-error {
border-color: var(--theme-error-200);
background-color: var(--theme-error-50);
}
&.toast-success {
border-color: var(--theme-success-550);
background-color: var(--theme-success-50);
}
&.toast-info {
border-color: var(--theme-border-color);
background-color: var(--theme-elevation-50);
&:hover {
color: var(--theme-elevation-250);
}
}
}
}

View File

@@ -15,17 +15,10 @@
font-weight: 500;
}
%jumbo {
font-size: base(2.5);
line-height: 1;
margin: 0 0 base(2);
}
%h1 {
margin: 0 0 base(1);
font-size: base(2);
line-height: 1.15;
letter-spacing: -1px;
font-size: base(1.6);
line-height: base(1.8);
@include small-break {
letter-spacing: -0.5px;
@@ -35,9 +28,8 @@
%h2 {
margin: 0 0 base(1);
font-size: base(1.25);
line-height: 1.15;
letter-spacing: -0.5px;
font-size: base(1.3);
line-height: base(1.6);
@include small-break {
font-size: base(0.85);
@@ -46,9 +38,8 @@
%h3 {
margin: 0 0 base(1);
font-size: base(0.925);
line-height: 1.25;
letter-spacing: -0.5px;
font-size: base(1);
line-height: base(1.2);
@include small-break {
font-size: base(0.65);
@@ -58,27 +49,27 @@
%h4 {
margin: 0 0 $baseline;
font-size: base(0.75);
line-height: 1.5;
font-size: base(0.8);
line-height: base(1);
letter-spacing: -0.375px;
}
%h5 {
margin: 0;
font-size: base(0.5625);
line-height: 1.5;
font-size: base(0.65);
line-height: base(0.8);
}
%h6 {
margin: 0;
font-size: base(0.5);
line-height: 1.5;
font-size: base(0.6);
line-height: base(0.8);
}
%small {
margin: 0;
font-size: 11px;
line-height: 1.5;
font-size: 12px;
line-height: 20px;
}
/////////////////////////////

View File

@@ -13,7 +13,7 @@ $breakpoint-l-width: 1440px !default;
// BASELINE GRID
//////////////////////////////
$baseline-px: 25px !default;
$baseline-px: 20px !default;
$baseline-body-size: 13px !default;
$baseline: math.div($baseline-px, $baseline-body-size) + rem;
@@ -40,7 +40,7 @@ $color-purple: #f3ddf3 !default;
$style-radius-s: 3px !default;
$style-radius-m: 4px !default;
$style-radius-l: 9px !default;
$style-radius-l: 8px !default;
$style-stroke-width: 1px !default;
$style-stroke-width-s: 1px !default;
@@ -50,8 +50,8 @@ $style-stroke-width-m: 2px !default;
// MISC
//////////////////////////////
$top-header-offset: calc(var(--base) - 1px);
$top-header-offset-m: calc(var(--base) * 3);
$top-header-offset: calc(base(1) - 1px);
$top-header-offset-m: base(3);
$focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
//////////////////////////////
@@ -59,41 +59,19 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
//////////////////////////////
@mixin shadow-sm {
box-shadow:
0 2px 3px 0 rgba(0, 2, 4, 0.05),
0 10px 4px -8px rgba(0, 2, 4, 0.02);
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
}
@mixin shadow-m {
box-shadow:
0 0 30px 0 rgb(0 2 4 / 12%),
0 30px 25px -8px rgb(0 2 4 / 10%);
box-shadow: 0 4px 8px -3px rgba(0, 0, 0, 0.1);
}
@mixin shadow-lg {
box-shadow:
0 20px 35px -10px rgba(0, 2, 4, 0.2),
0 6px 4px -4px rgba(0, 2, 4, 0.02);
box-shadow: 0 -2px 16px -2px rgba(0, 0, 0, 0.2);
}
@mixin shadow-lg-top {
box-shadow:
0 -20px 35px -10px rgba(0, 2, 4, 0.2),
0 -6px 4px -4px rgba(0, 2, 4, 0.02);
}
@mixin shadow {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.07);
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
@mixin inputShadowActive {
box-shadow:
0 2px 3px 0 rgba(0, 2, 4, 0.16),
0 6px 4px -4px rgba(0, 2, 4, 0.13);
box-shadow: 0 2px 16px -2px rgba(0, 0, 0, 0.2);
}
@mixin inputShadow {
@@ -101,15 +79,7 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
&:not(:disabled) {
&:hover {
box-shadow:
0 2px 3px 0 rgba(0, 2, 4, 0.13),
0 6px 4px -4px rgba(0, 2, 4, 0.1);
}
&:active,
&:focus-within,
&:focus {
@include inputShadowActive;
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.2);
}
}
}
@@ -147,19 +117,33 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
@include blur-bg(var(--theme-bg), 0.3);
}
@mixin readOnly {
background: var(--theme-elevation-100);
color: var(--theme-elevation-400);
box-shadow: none;
&:hover {
border-color: var(--theme-elevation-150);
box-shadow: none;
}
}
@mixin formInput() {
@include inputShadow;
font-family: var(--font-body);
width: 100%;
border: 1px solid var(--theme-elevation-150);
border-radius: var(--style-radius-s);
background: var(--theme-input-bg);
color: var(--theme-elevation-800);
border-radius: 0;
font-size: 1rem;
height: base(2);
line-height: base(1);
padding: base(0.5) base(0.75);
padding: base(0.4) base(0.75);
-webkit-appearance: none;
transition-property: border, box-shadow;
transition-duration: 100ms;
transition-timing-function: cubic-bezier(0, 0.2, 0.2, 1);
&[data-rtl='true'] {
direction: rtl;
@@ -189,12 +173,7 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
}
&:disabled {
background: var(--theme-elevation-200);
color: var(--theme-elevation-450);
&:hover {
border-color: var(--theme-elevation-150);
}
@include readOnly;
}
}

View File

@@ -7,7 +7,7 @@ import type {
import { notFound } from 'next/navigation.js'
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
import { getRouteWithoutAdmin, isAdminAuthRoute, isAdminRoute } from './shared.js'
export const handleAdminPage = ({
adminRoute,
@@ -20,9 +20,9 @@ export const handleAdminPage = ({
permissions: Permissions
route: string
}) => {
if (isAdminRoute(route, adminRoute)) {
const baseAdminRoute = adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
const routeSegments = baseAdminRoute.split('/').filter(Boolean)
if (isAdminRoute({ adminRoute, config, route })) {
const routeWithoutAdmin = getRouteWithoutAdmin({ adminRoute, route })
const routeSegments = routeWithoutAdmin.split('/').filter(Boolean)
const [entityType, entitySlug, createOrID] = routeSegments
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
const globalSlug = entityType === 'globals' ? entitySlug : undefined
@@ -47,7 +47,7 @@ export const handleAdminPage = ({
}
}
if (!permissions.canAccessAdmin && !isAdminAuthRoute(config, route, adminRoute)) {
if (!permissions.canAccessAdmin && !isAdminAuthRoute({ adminRoute, config, route })) {
notFound()
}

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