Compare commits
178 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a198fe0be5 | ||
|
|
c460868e52 | ||
|
|
a0a1e20193 | ||
|
|
3b59416298 | ||
|
|
7c8272b467 | ||
|
|
06e5db6529 | ||
|
|
8448e65b73 | ||
|
|
6d1a287dd1 | ||
|
|
bb2dd5f4d2 | ||
|
|
5873a3db06 | ||
|
|
8fc2c43190 | ||
|
|
64f2395c58 | ||
|
|
ff1c1e0c59 | ||
|
|
d0bb1c9e60 | ||
|
|
1608150a25 | ||
|
|
fbc28b0249 | ||
|
|
ec624bd1f2 | ||
|
|
43a9109b53 | ||
|
|
334f940e0c | ||
|
|
dd5a9acb60 | ||
|
|
9548961ec9 | ||
|
|
66082d5127 | ||
|
|
db29e1ec98 | ||
|
|
d28d40ced3 | ||
|
|
a6f13f7330 | ||
|
|
532e4b52fe | ||
|
|
59b6107e2d | ||
|
|
c28618b19c | ||
|
|
945d9192a1 | ||
|
|
dbf2301a61 | ||
|
|
c34401dc4b | ||
|
|
6e94884d18 | ||
|
|
8b307012f3 | ||
|
|
9561aa3f79 | ||
|
|
51bc8b4416 | ||
|
|
ec3730722b | ||
|
|
465e47a219 | ||
|
|
043bf95a70 | ||
|
|
cd734b0f98 | ||
|
|
6e61431ca1 | ||
|
|
663e5119b2 | ||
|
|
8fe6ffd39b | ||
|
|
0118bce582 | ||
|
|
46707e4c5e | ||
|
|
a234092b34 | ||
|
|
281c80d2c7 | ||
|
|
12a30a0585 | ||
|
|
0c563ebd73 | ||
|
|
82a684138a | ||
|
|
df023a52fd | ||
|
|
d267cad482 | ||
|
|
fa38dfc16c | ||
|
|
8e1a5c8dba | ||
|
|
67e1d6abc5 | ||
|
|
a8c60c1c02 | ||
|
|
d44fb2db37 | ||
|
|
3a61d8d656 | ||
|
|
d04f6ab2bf | ||
|
|
852f9fc1fd | ||
|
|
552239b637 | ||
|
|
37e181a38d | ||
|
|
e2d803800d | ||
|
|
7fa68d17f5 | ||
|
|
9ec431a5bd | ||
|
|
cadf815ef6 | ||
|
|
638382e7fd | ||
|
|
08fdbcacc0 | ||
|
|
b27e42c484 | ||
|
|
32cc1a5761 | ||
|
|
38be69b7d3 | ||
|
|
6b82196f01 | ||
|
|
ead12c8a49 | ||
|
|
6253ec5d1a | ||
|
|
f9ae56ec88 | ||
|
|
0688c2b79d | ||
|
|
c6246618ba | ||
|
|
b69826a81e | ||
|
|
e80da7cb75 | ||
|
|
6f512b6ca8 | ||
|
|
22ee8bf383 | ||
|
|
308fad8a7a | ||
|
|
6427b7eb29 | ||
|
|
3a657847f2 | ||
|
|
8212c0d65f | ||
|
|
772f869cc6 | ||
|
|
b6a8d1c461 | ||
|
|
11576eda13 | ||
|
|
3c62e6c772 | ||
|
|
5b74879c5e | ||
|
|
7fd736ea5b | ||
|
|
7a3507d597 | ||
|
|
ef6fe9ca9a | ||
|
|
9bcdf0dc81 | ||
|
|
8203fe86cd | ||
|
|
39cd8283c8 | ||
|
|
1130a581c0 | ||
|
|
d9cccc7081 | ||
|
|
053256d5ce | ||
|
|
751803d4f4 | ||
|
|
ee3d5856e3 | ||
|
|
cf9e13aebb | ||
|
|
9816787fbf | ||
|
|
b5fb92530c | ||
|
|
2c1c0dae70 | ||
|
|
9295a6130e | ||
|
|
91fc5fb31b | ||
|
|
e25730f95c | ||
|
|
7f6b0f087f | ||
|
|
c1533bfd3e | ||
|
|
442d105841 | ||
|
|
c45ee0d26b | ||
|
|
b97dcc33c7 | ||
|
|
8202c3dee8 | ||
|
|
b6ae6890aa | ||
|
|
c14dbfba40 | ||
|
|
0a36529dc5 | ||
|
|
e033488db7 | ||
|
|
90b3e83fc2 | ||
|
|
76dda13ca1 | ||
|
|
e071382a79 | ||
|
|
131f2def3c | ||
|
|
d97cd2fd5d | ||
|
|
86fdad0bb8 | ||
|
|
bc367ab73c | ||
|
|
c0728220ff | ||
|
|
6893f404ac | ||
|
|
2a8bd4c775 | ||
|
|
ac10bad723 | ||
|
|
142616e6ad | ||
|
|
dd3d985091 | ||
|
|
de3d7c95e7 | ||
|
|
570422ff9a | ||
|
|
53c41bdfd8 | ||
|
|
e5c34ead16 | ||
|
|
6e561b11ca | ||
|
|
f7146362df | ||
|
|
ec9d1cda2d | ||
|
|
657326b528 | ||
|
|
538b7ee616 | ||
|
|
828f5d866d | ||
|
|
efe17ff5e4 | ||
|
|
e375f6e727 | ||
|
|
cc9b877e88 | ||
|
|
dc12047723 | ||
|
|
12fb691e4f | ||
|
|
0962850086 | ||
|
|
78c8bb81a1 | ||
|
|
419b274bb1 | ||
|
|
ef818fd5c8 | ||
|
|
0aaf3af1ea | ||
|
|
18b0806b5b | ||
|
|
3d9051ad34 | ||
|
|
e4ef47b938 | ||
|
|
c7e7dc71d3 | ||
|
|
375671c162 | ||
|
|
23b495b145 | ||
|
|
27d743e2a8 | ||
|
|
8c9ff3d54b | ||
|
|
5c447252e7 | ||
|
|
a76be81368 | ||
|
|
5d97d57e70 | ||
|
|
de7ff1f8c6 | ||
|
|
3d714d3e72 | ||
|
|
2bbb02b9c0 | ||
|
|
0533e7f5db | ||
|
|
23c5ef428d | ||
|
|
f046a04510 | ||
|
|
4cda7d2363 | ||
|
|
ea48cfbfe9 | ||
|
|
1aeb912762 | ||
|
|
ce2cb35d71 | ||
|
|
d3ec68ac2f | ||
|
|
05bf52aac3 | ||
|
|
fed7f2fa5b | ||
|
|
686b0865b2 | ||
|
|
dfb4c8eb4c | ||
|
|
ad7a387e19 | ||
|
|
d05be016ce |
@@ -22,3 +22,9 @@ fb7d1be2f3325d076b7c967b1730afcef37922c2
|
||||
|
||||
# 3.0 prettier & lint everywhere again
|
||||
83fd4c66222d7846eeb5cc332dfa99bf1e830831
|
||||
|
||||
# Upgrade to typescript-eslint v8, then prettier & lint everywhere
|
||||
86fdad0bb8ab27810599c8a32f3d8cba1341e1df
|
||||
|
||||
# Prettier and lint remaining db packages
|
||||
7fd736ea5b2e9fc4ef936e9dc9e5e3d722f6d8bf
|
||||
|
||||
13
.github/actions/release-commenter/.eslintrc.js
vendored
Normal file
13
.github/actions/release-commenter/.eslintrc.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
}
|
||||
74
.github/actions/release-commenter/README.md
vendored
Normal file
74
.github/actions/release-commenter/README.md
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# Release Commenter
|
||||
|
||||
This GitHub Action automatically comments on and/or labels Issues and PRs when a fix is released for them.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 🔧 Heavily modified version of https://github.com/apexskier/github-release-commenter
|
||||
|
||||
## Fork Modifications
|
||||
|
||||
- Filters to closed PRs only
|
||||
- Adds tag filter to support non-linear releases
|
||||
- Better logging
|
||||
- Moved to pnpm
|
||||
- Uses @vercel/ncc for packaging
|
||||
- Comments on locked issues by unlocking then re-locking
|
||||
|
||||
## How it works
|
||||
|
||||
Use this action in a workflow [triggered by a release](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#release). It will scan commits between that and the prior release, find associated Issues and PRs, and comment on them to let people know a release has been made. Associated Issues and PRs can be directly [linked](https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) to the commit or manually linked from a PR associated with the commit.
|
||||
|
||||
## Inputs
|
||||
|
||||
**GITHUB_TOKEN**
|
||||
|
||||
A GitHub personal access token with repo scope, such as [`secrets.GITHUB_TOKEN`](https://docs.github.com/en/free-pro-team@latest/actions/reference/authentication-in-a-workflow#about-the-github_token-secret).
|
||||
|
||||
**comment-template** (optional)
|
||||
|
||||
Override the comment posted on Issues and PRs. Set to the empty string to disable commenting. Several variables strings will be automatically replaced:
|
||||
|
||||
- `{release_link}` - a markdown link to the release
|
||||
- `{release_name}` - the release's name
|
||||
- `{release_tag}` - the release's tag
|
||||
|
||||
**label-template** (optional)
|
||||
|
||||
Add the given label. Multiple labels can be separated by commas. Several variable strings will be automatically replaced:
|
||||
|
||||
- `{release_name}` - the release's name
|
||||
- `{release_tag}` - the release's tag
|
||||
|
||||
**skip-label** (optional)
|
||||
|
||||
Skip processing if any of the given labels are present. Same processing rules as **label-template**. Default is "dependencies".
|
||||
|
||||
## Example
|
||||
|
||||
```yml
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
steps:
|
||||
- uses: apexskier/github-release-commenter@v1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment-template: |
|
||||
Release {release_link} addresses this.
|
||||
```
|
||||
|
||||
## Known limitations
|
||||
|
||||
These are some known limitations of this action. I'd like to try to address them in the future.
|
||||
|
||||
- Non-linear releases aren't supported. For example, releasing a patch to a prior major release after a new major release has been bumped.
|
||||
- Non-sequential releases aren't supported. For example, if you release multiple prereleases between two official releases, this will only create a comment for the first prerelease in which a fix is released, not the final release.
|
||||
- The first release for a project will be ignored. This is intentional, as the use case is unlikely. Most projects will either have several alphas that don't need release comments, or won't use issues/PRs for the first commit.
|
||||
- If a large number of things are commented on, you may see the error `Error: You have triggered an abuse detection mechanism. Please wait a few minutes before you try again.`. Consider using the `skip-label` input to reduce your load on the GitHub API.
|
||||
|
||||
## Versions
|
||||
|
||||
Workflows will automatically update the tags `v1` and `latest`, allowing you to reference one of those instead of locking to a specific release.
|
||||
32
.github/actions/release-commenter/action.yml
vendored
Normal file
32
.github/actions/release-commenter/action.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Release Commenter
|
||||
description: Comment on PRs and Issues when a fix is released
|
||||
branding:
|
||||
icon: message-square
|
||||
color: blue
|
||||
inputs:
|
||||
GITHUB_TOKEN:
|
||||
description: |
|
||||
A GitHub personal access token with repo scope, such as
|
||||
secrets.GITHUB_TOKEN.
|
||||
required: true
|
||||
comment-template:
|
||||
description: |
|
||||
Text template for the comment string.
|
||||
required: false
|
||||
default: |
|
||||
Included in release {release_link}
|
||||
label-template:
|
||||
description: Add the given label. Multiple labels can be separated by commas.
|
||||
required: false
|
||||
skip-label:
|
||||
description: Skip commenting if any of the given label are present. Multiple labels can be separated by commas.
|
||||
required: false
|
||||
default: 'dependencies'
|
||||
tag-filter:
|
||||
description: |
|
||||
Filter tags by a regular expression. Must be escaped. e.g. 'v\\d' to isolate tags between major versions.
|
||||
required: false
|
||||
default: null
|
||||
runs:
|
||||
using: node20
|
||||
main: dist/index.js
|
||||
34199
.github/actions/release-commenter/dist/index.js
vendored
Normal file
34199
.github/actions/release-commenter/dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
.github/actions/release-commenter/jest.config.js
vendored
Normal file
7
.github/actions/release-commenter/jest.config.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/dist/'],
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': ['@swc/jest'],
|
||||
},
|
||||
}
|
||||
34
.github/actions/release-commenter/package.json
vendored
Normal file
34
.github/actions/release-commenter/package.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "release-commenter",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "GitHub Action to automatically comment on PRs and Issues when a fix is released.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "pnpm build:typecheck && pnpm build:ncc",
|
||||
"build:ncc": "ncc build src/index.ts -t -o dist",
|
||||
"build:typecheck": "tsc",
|
||||
"clean": "rimraf dist",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.3.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/webhooks-types": "^7.5.1",
|
||||
"@swc/jest": "^0.2.36",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^20.16.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^26.5.6",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
5419
.github/actions/release-commenter/pnpm-lock.yaml
generated
vendored
Normal file
5419
.github/actions/release-commenter/pnpm-lock.yaml
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
266
.github/actions/release-commenter/src/__snapshots__/index.test.ts.snap
vendored
Normal file
266
.github/actions/release-commenter/src/__snapshots__/index.test.ts.snap
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`tests feature tests can apply labels 1`] = `
|
||||
[
|
||||
[
|
||||
{
|
||||
"issue_number": 123,
|
||||
"labels": [
|
||||
":dart: landed",
|
||||
"release-current_tag_name",
|
||||
"Release Name",
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"issue_number": 7,
|
||||
"labels": [
|
||||
":dart: landed",
|
||||
"release-current_tag_name",
|
||||
"Release Name",
|
||||
],
|
||||
},
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`tests main test 1`] = `
|
||||
{
|
||||
"graphql": [MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
"
|
||||
{
|
||||
resource(url: "http://repository/commit/SHA1") {
|
||||
... on Commit {
|
||||
messageHeadlineHTML
|
||||
messageBodyHTML
|
||||
associatedPullRequests(first: 10) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
bodyHTML
|
||||
number
|
||||
state
|
||||
labels(first: 10) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
... on ConnectedEvent {
|
||||
__typename
|
||||
isCrossRepository
|
||||
subject {
|
||||
... on Issue {
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DisconnectedEvent {
|
||||
__typename
|
||||
isCrossRepository
|
||||
subject {
|
||||
... on Issue {
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
",
|
||||
],
|
||||
[
|
||||
"
|
||||
{
|
||||
resource(url: "http://repository/commit/SHA2") {
|
||||
... on Commit {
|
||||
messageHeadlineHTML
|
||||
messageBodyHTML
|
||||
associatedPullRequests(first: 10) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
bodyHTML
|
||||
number
|
||||
state
|
||||
labels(first: 10) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
... on ConnectedEvent {
|
||||
__typename
|
||||
isCrossRepository
|
||||
subject {
|
||||
... on Issue {
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DisconnectedEvent {
|
||||
__typename
|
||||
isCrossRepository
|
||||
subject {
|
||||
... on Issue {
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
",
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
],
|
||||
},
|
||||
"rest": {
|
||||
"issues": {
|
||||
"addLabels": [MockFunction],
|
||||
"createComment": [MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
{
|
||||
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
|
||||
"issue_number": 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
|
||||
"issue_number": 123,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
|
||||
"issue_number": 7,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
],
|
||||
},
|
||||
"get": [MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
{
|
||||
"issue_number": 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"issue_number": 123,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"issue_number": 7,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"repos": {
|
||||
"compareCommits": [MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
{
|
||||
"base": "prior_tag_name",
|
||||
"head": "current_tag_name",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
],
|
||||
},
|
||||
"listReleases": [MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
{
|
||||
"per_page": 100,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": Promise {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
399
.github/actions/release-commenter/src/index.test.ts
vendored
Normal file
399
.github/actions/release-commenter/src/index.test.ts
vendored
Normal file
@@ -0,0 +1,399 @@
|
||||
import type * as githubModule from '@actions/github'
|
||||
import type * as coreModule from '@actions/core'
|
||||
import { mock } from 'node:test'
|
||||
|
||||
jest.mock('@actions/core')
|
||||
jest.mock('@actions/github')
|
||||
|
||||
type Mocked<T> = {
|
||||
-readonly [P in keyof T]: T[P] extends Function ? jest.Mock<T[P]> : jest.Mocked<Partial<T[P]>>
|
||||
}
|
||||
|
||||
const github = require('@actions/github') as jest.Mocked<Mocked<typeof githubModule>>
|
||||
const core = require('@actions/core') as jest.Mocked<Mocked<typeof coreModule>>
|
||||
|
||||
describe('tests', () => {
|
||||
let mockOctokit: any = {}
|
||||
let currentTag: string = 'current_tag_name'
|
||||
|
||||
;(core.warning as any) = jest.fn(console.warn.bind(console))
|
||||
;(core.error as any) = jest.fn(console.error.bind(console))
|
||||
|
||||
let commentTempate: string = ''
|
||||
let labelTemplate: string | null = null
|
||||
const skipLabelTemplate: string | null = 'skip,test'
|
||||
let tagFilter: string | RegExp | null = null
|
||||
|
||||
let simpleMockOctokit: any = {}
|
||||
|
||||
beforeEach(() => {
|
||||
tagFilter = null
|
||||
currentTag = 'current_tag_name'
|
||||
;(github.context as any) = {
|
||||
payload: {
|
||||
repo: {
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
},
|
||||
release: {
|
||||
tag_name: currentTag,
|
||||
},
|
||||
repository: { html_url: 'http://repository' },
|
||||
},
|
||||
}
|
||||
|
||||
github.getOctokit.mockReset().mockImplementationOnce(((token: string) => {
|
||||
expect(token).toBe('GITHUB_TOKEN_VALUE')
|
||||
return mockOctokit
|
||||
}) as any)
|
||||
;(core.getInput as any).mockImplementation((key: string) => {
|
||||
if (key == 'GITHUB_TOKEN') {
|
||||
return 'GITHUB_TOKEN_VALUE'
|
||||
}
|
||||
if (key == 'comment-template') {
|
||||
return commentTempate
|
||||
}
|
||||
if (key == 'label-template') {
|
||||
return labelTemplate
|
||||
}
|
||||
if (key == 'skip-label') {
|
||||
return skipLabelTemplate
|
||||
}
|
||||
if (key == 'tag-filter') {
|
||||
return tagFilter
|
||||
}
|
||||
fail(`Unexpected input key ${key}`)
|
||||
})
|
||||
|
||||
commentTempate =
|
||||
'Included in release {release_link}. Replacements: {release_name}, {release_tag}.'
|
||||
labelTemplate = null
|
||||
simpleMockOctokit = {
|
||||
rest: {
|
||||
issues: {
|
||||
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
|
||||
createComment: jest.fn(() => Promise.resolve()),
|
||||
addLabels: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
repos: {
|
||||
listReleases: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
name: 'Release Name',
|
||||
tag_name: 'current_tag_name',
|
||||
html_url: 'http://current_release',
|
||||
},
|
||||
{
|
||||
tag_name: 'prior_tag_name',
|
||||
html_url: 'http://prior_release',
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
compareCommits: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: { commits: [{ sha: 'SHA1' }] },
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
graphql: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
resource: {
|
||||
messageHeadlineHTML: '',
|
||||
messageBodyHTML:
|
||||
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
|
||||
associatedPullRequests: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
edges: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
expect(core.error).not.toHaveBeenCalled()
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
expect(core.setFailed).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('main test', async () => {
|
||||
mockOctokit = {
|
||||
...simpleMockOctokit,
|
||||
rest: {
|
||||
issues: {
|
||||
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
|
||||
createComment: jest.fn(() => Promise.resolve()),
|
||||
addLabels: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
repos: {
|
||||
listReleases: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
tag_name: 'current_tag_name',
|
||||
html_url: 'http://current_release',
|
||||
},
|
||||
{
|
||||
tag_name: 'prior_tag_name',
|
||||
html_url: 'http://prior_release',
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
compareCommits: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: { commits: [{ sha: 'SHA1' }, { sha: 'SHA2' }] },
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
graphql: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
resource: {
|
||||
messageHeadlineHTML:
|
||||
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #3.">Closes</span> <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="718013420" data-permission-text="Title is private" data-url="https://github.com/apexskier/github-release-commenter/issues/1" data-hovercard-type="issue" data-hovercard-url="/apexskier/github-release-commenter/issues/1/hovercard" href="https://github.com/apexskier/github-release-commenter/issues/1">#1</a>',
|
||||
messageBodyHTML:
|
||||
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
|
||||
associatedPullRequests: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
bodyHTML:
|
||||
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #4.">Closes</span> <span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #5.">Closes</span>',
|
||||
number: 9,
|
||||
labels: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [{ name: 'label1' }, { name: 'label2' }],
|
||||
},
|
||||
timelineItems: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
isCrossRepository: true,
|
||||
__typename: 'ConnectedEvent',
|
||||
subject: { number: 1 },
|
||||
},
|
||||
{
|
||||
isCrossRepository: false,
|
||||
__typename: 'ConnectedEvent',
|
||||
subject: { number: 2 },
|
||||
},
|
||||
{
|
||||
isCrossRepository: false,
|
||||
__typename: 'DisconnectedEvent',
|
||||
subject: { number: 2 },
|
||||
},
|
||||
{
|
||||
isCrossRepository: false,
|
||||
__typename: 'ConnectedEvent',
|
||||
subject: { number: 2 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
bodyHTML: '',
|
||||
number: 42,
|
||||
labels: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [{ name: 'label1' }, { name: 'skip' }],
|
||||
},
|
||||
timelineItems: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
isCrossRepository: true,
|
||||
__typename: 'ConnectedEvent',
|
||||
subject: { number: 82 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
jest.isolateModules(() => {
|
||||
require('./index')
|
||||
})
|
||||
|
||||
await new Promise<void>(setImmediate)
|
||||
|
||||
expect(mockOctokit).toMatchSnapshot()
|
||||
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
describe('can filter tags', () => {
|
||||
const v3prev = 'v3.0.1'
|
||||
const v3current = 'v3.0.2'
|
||||
const v2prev = 'v2.0.1'
|
||||
const v2current = 'v2.0.2'
|
||||
|
||||
const listReleasesData = [
|
||||
{
|
||||
name: 'Current Release Name',
|
||||
tag_name: v3current,
|
||||
html_url: 'http://v3.0.2',
|
||||
},
|
||||
{
|
||||
name: 'Prev Release Name',
|
||||
tag_name: v3prev,
|
||||
html_url: 'http://v3.0.1',
|
||||
},
|
||||
{
|
||||
name: 'v2 Current Release Name',
|
||||
tag_name: v2current,
|
||||
html_url: 'http://v2.0.2',
|
||||
},
|
||||
{
|
||||
name: 'v2 Prev Release Name',
|
||||
tag_name: v2prev,
|
||||
html_url: 'http://v2.0.1',
|
||||
},
|
||||
]
|
||||
|
||||
it.each`
|
||||
description | prevTag | currentTag | filter
|
||||
${'no filter'} | ${v3prev} | ${v3current} | ${null}
|
||||
${'v3'} | ${v3prev} | ${v3current} | ${'v\\d'}
|
||||
${'v2'} | ${v2prev} | ${v2current} | ${'v\\d'}
|
||||
`('should filter tags with $description', async ({ prevTag, currentTag, filter }) => {
|
||||
// @ts-ignore
|
||||
github.context.payload.release.tag_name = currentTag
|
||||
|
||||
tagFilter = filter
|
||||
|
||||
mockOctokit = {
|
||||
...simpleMockOctokit,
|
||||
rest: {
|
||||
issues: {
|
||||
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
|
||||
createComment: jest.fn(() => Promise.resolve()),
|
||||
addLabels: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
repos: {
|
||||
listReleases: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: listReleasesData,
|
||||
}),
|
||||
),
|
||||
compareCommits: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: { commits: [{ sha: 'SHA1' }] },
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
graphql: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
resource: {
|
||||
messageHeadlineHTML: '',
|
||||
messageBodyHTML:
|
||||
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
|
||||
associatedPullRequests: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
edges: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
jest.isolateModules(() => {
|
||||
require('./index')
|
||||
})
|
||||
|
||||
await new Promise<void>((resolve) => setImmediate(() => resolve()))
|
||||
|
||||
expect(github.getOctokit).toHaveBeenCalled()
|
||||
expect(mockOctokit.rest.repos.compareCommits.mock.calls).toEqual([
|
||||
[{ base: prevTag, head: currentTag }],
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('feature tests', () => {
|
||||
beforeEach(() => {
|
||||
mockOctokit = simpleMockOctokit
|
||||
})
|
||||
|
||||
it('can disable comments', async () => {
|
||||
commentTempate = ''
|
||||
|
||||
jest.isolateModules(() => {
|
||||
require('./index')
|
||||
})
|
||||
|
||||
await new Promise<void>((resolve) => setImmediate(() => resolve()))
|
||||
|
||||
expect(github.getOctokit).toHaveBeenCalled()
|
||||
expect(mockOctokit.rest.issues.createComment).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should unlock and comment', async () => {
|
||||
mockOctokit = {
|
||||
...simpleMockOctokit,
|
||||
rest: {
|
||||
...simpleMockOctokit.rest,
|
||||
issues: {
|
||||
// Return locked for both issues to be commented on
|
||||
get: jest.fn(() => Promise.resolve({ data: { locked: true } })),
|
||||
lock: jest.fn(() => Promise.resolve()),
|
||||
unlock: jest.fn(() => Promise.resolve()),
|
||||
createComment: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
},
|
||||
graphql: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
resource: {
|
||||
messageHeadlineHTML: '',
|
||||
messageBodyHTML:
|
||||
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
|
||||
associatedPullRequests: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
edges: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
jest.isolateModules(() => {
|
||||
require('./index')
|
||||
})
|
||||
|
||||
await new Promise<void>((resolve) => setImmediate(() => resolve()))
|
||||
|
||||
expect(github.getOctokit).toHaveBeenCalled()
|
||||
|
||||
// Should call once for both linked issues
|
||||
expect(mockOctokit.rest.issues.unlock).toHaveBeenCalledTimes(2)
|
||||
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledTimes(2)
|
||||
expect(mockOctokit.rest.issues.lock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it.skip('can apply labels', async () => {
|
||||
labelTemplate = ':dart: landed,release-{release_tag},{release_name}'
|
||||
|
||||
jest.isolateModules(() => {
|
||||
require('./index')
|
||||
})
|
||||
|
||||
await new Promise<void>((resolve) => setImmediate(() => resolve()))
|
||||
|
||||
expect(github.getOctokit).toHaveBeenCalled()
|
||||
expect(mockOctokit.rest.issues.addLabels.mock.calls).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
349
.github/actions/release-commenter/src/index.ts
vendored
Normal file
349
.github/actions/release-commenter/src/index.ts
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import type * as Webhooks from '@octokit/webhooks-types'
|
||||
|
||||
const closesMatcher = /aria-label="This (?:commit|pull request) closes issue #(\d+)\."/g
|
||||
|
||||
const releaseLinkTemplateRegex = /{release_link}/g
|
||||
const releaseNameTemplateRegex = /{release_name}/g
|
||||
const releaseTagTemplateRegex = /{release_tag}/g
|
||||
|
||||
;(async function main() {
|
||||
try {
|
||||
const payload = github.context.payload as Webhooks.EventPayloadMap['release']
|
||||
|
||||
const githubToken = core.getInput('GITHUB_TOKEN')
|
||||
const tagFilter = core.getInput('tag-filter') || undefined // Accept tag filter as an input
|
||||
const octokit = github.getOctokit(githubToken)
|
||||
|
||||
const commentTemplate = core.getInput('comment-template')
|
||||
const labelTemplate = core.getInput('label-template') || null
|
||||
const skipLabelTemplate = core.getInput('skip-label') || null
|
||||
|
||||
// Fetch the releases with the optional tag filter applied
|
||||
const { data: rawReleases } = await octokit.rest.repos.listReleases({
|
||||
...github.context.repo,
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
// Get the current release tag or latest tag
|
||||
const currentTag = payload?.release?.tag_name || rawReleases?.[0]?.tag_name
|
||||
|
||||
let releases = rawReleases
|
||||
|
||||
// Filter releases by the tag filter if provided
|
||||
if (tagFilter) {
|
||||
core.info(`Filtering releases by tag filter: ${tagFilter}`)
|
||||
// Get the matching part of the current release tag
|
||||
const regexMatch = currentTag.match(tagFilter)?.[0]
|
||||
if (!regexMatch) {
|
||||
core.error(`Current release tag ${currentTag} does not match the tag filter ${tagFilter}`)
|
||||
return
|
||||
}
|
||||
|
||||
core.info(`Matched string from filter: ${regexMatch}`)
|
||||
|
||||
releases = releases
|
||||
.filter((release) => {
|
||||
const match = release.tag_name.match(regexMatch)?.[0]
|
||||
return match
|
||||
})
|
||||
.slice(0, 2)
|
||||
}
|
||||
|
||||
core.info(`Releases: ${JSON.stringify(releases, null, 2)}`)
|
||||
|
||||
if (releases.length < 2) {
|
||||
if (!releases.length) {
|
||||
core.error(`No releases found with the provided tag filter: '${tagFilter}'`)
|
||||
return
|
||||
}
|
||||
|
||||
core.info('first release')
|
||||
return
|
||||
}
|
||||
|
||||
const [currentRelease, priorRelease] = releases
|
||||
|
||||
core.info(`${priorRelease.tag_name}...${currentRelease.tag_name}`)
|
||||
|
||||
const {
|
||||
data: { commits },
|
||||
} = await octokit.rest.repos.compareCommits({
|
||||
...github.context.repo,
|
||||
base: priorRelease.tag_name,
|
||||
head: currentRelease.tag_name,
|
||||
})
|
||||
|
||||
if (!currentRelease.name) {
|
||||
core.info('Current release has no name, will fall back to the tag name.')
|
||||
}
|
||||
const releaseLabel = currentRelease.name || currentRelease.tag_name
|
||||
|
||||
const comment = commentTemplate
|
||||
.trim()
|
||||
.split(releaseLinkTemplateRegex)
|
||||
.join(`[${releaseLabel}](${currentRelease.html_url})`)
|
||||
.split(releaseNameTemplateRegex)
|
||||
.join(releaseLabel)
|
||||
.split(releaseTagTemplateRegex)
|
||||
.join(currentRelease.tag_name)
|
||||
|
||||
const parseLabels = (rawInput: string | null) =>
|
||||
rawInput
|
||||
?.split(releaseNameTemplateRegex)
|
||||
.join(releaseLabel)
|
||||
?.split(releaseTagTemplateRegex)
|
||||
.join(currentRelease.tag_name)
|
||||
?.split(',')
|
||||
?.map((l) => l.trim())
|
||||
.filter((l) => l)
|
||||
|
||||
const labels = parseLabels(labelTemplate)
|
||||
const skipLabels = parseLabels(skipLabelTemplate)
|
||||
|
||||
const linkedIssuesPrs = new Set<number>()
|
||||
|
||||
await Promise.all(
|
||||
commits.map((commit) =>
|
||||
(async () => {
|
||||
const query = `
|
||||
{
|
||||
resource(url: "${payload.repository.html_url}/commit/${commit.sha}") {
|
||||
... on Commit {
|
||||
messageHeadlineHTML
|
||||
messageBodyHTML
|
||||
associatedPullRequests(first: 10) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
bodyHTML
|
||||
number
|
||||
state
|
||||
labels(first: 10) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
nodes {
|
||||
... on ConnectedEvent {
|
||||
__typename
|
||||
isCrossRepository
|
||||
subject {
|
||||
... on Issue {
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DisconnectedEvent {
|
||||
__typename
|
||||
isCrossRepository
|
||||
subject {
|
||||
... on Issue {
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const response: {
|
||||
resource: null | {
|
||||
messageHeadlineHTML: string
|
||||
messageBodyHTML: string
|
||||
associatedPullRequests: {
|
||||
pageInfo: { hasNextPage: boolean }
|
||||
edges: ReadonlyArray<{
|
||||
node: {
|
||||
bodyHTML: string
|
||||
number: number
|
||||
state: 'OPEN' | 'CLOSED' | 'MERGED'
|
||||
labels: {
|
||||
pageInfo: { hasNextPage: boolean }
|
||||
nodes: ReadonlyArray<{
|
||||
name: string
|
||||
}>
|
||||
}
|
||||
timelineItems: {
|
||||
pageInfo: { hasNextPage: boolean }
|
||||
nodes: ReadonlyArray<{
|
||||
__typename: 'ConnectedEvent' | 'DisconnectedEvent'
|
||||
isCrossRepository: boolean
|
||||
subject: {
|
||||
number: number
|
||||
}
|
||||
}>
|
||||
}
|
||||
}
|
||||
}>
|
||||
}
|
||||
}
|
||||
} = await octokit.graphql(query)
|
||||
|
||||
if (!response.resource) {
|
||||
return
|
||||
}
|
||||
|
||||
// core.info(JSON.stringify(response.resource, null, 2))
|
||||
|
||||
core.info(`Checking commit: ${payload.repository.html_url}/commit/${commit.sha}`)
|
||||
|
||||
const associatedClosedPREdges = response.resource.associatedPullRequests.edges.filter(
|
||||
(e) => e.node.state === 'MERGED',
|
||||
)
|
||||
|
||||
if (associatedClosedPREdges.length) {
|
||||
core.info(
|
||||
` Associated Merged PRs:\n ${associatedClosedPREdges.map((pr) => `${payload.repository.html_url}/pull/${pr.node.number}`).join('\n ')}`,
|
||||
)
|
||||
} else {
|
||||
core.info(' No associated merged PRs')
|
||||
}
|
||||
|
||||
const html = [
|
||||
response.resource.messageHeadlineHTML,
|
||||
response.resource.messageBodyHTML,
|
||||
...associatedClosedPREdges.map((pr) => pr.node.bodyHTML),
|
||||
].join(' ')
|
||||
|
||||
for (const match of html.matchAll(closesMatcher)) {
|
||||
const [, num] = match
|
||||
linkedIssuesPrs.add(parseInt(num, 10))
|
||||
core.info(
|
||||
` Linked issue/PR from closesMatcher: ${payload.repository.html_url}/pull/${num}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (response.resource.associatedPullRequests.pageInfo.hasNextPage) {
|
||||
core.warning(`Too many PRs associated with ${commit.sha}`)
|
||||
}
|
||||
|
||||
const seen = new Set<number>()
|
||||
for (const associatedPR of associatedClosedPREdges) {
|
||||
if (associatedPR.node.timelineItems.pageInfo.hasNextPage) {
|
||||
core.warning(`Too many links for #${associatedPR.node.number}`)
|
||||
}
|
||||
if (associatedPR.node.labels.pageInfo.hasNextPage) {
|
||||
core.warning(`Too many labels for #${associatedPR.node.number}`)
|
||||
}
|
||||
// a skip labels is present on this PR
|
||||
if (
|
||||
skipLabels?.some((l) => associatedPR.node.labels.nodes.some(({ name }) => name === l))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
linkedIssuesPrs.add(associatedPR.node.number)
|
||||
core.info(
|
||||
` Linked issue/PR from associated PR: ${payload.repository.html_url}/pull/${associatedPR.node.number}`,
|
||||
)
|
||||
|
||||
// These are sorted by creation date in ascending order. The latest event for a given issue/PR is all we need
|
||||
// ignore links that aren't part of this repo
|
||||
const links = associatedPR.node.timelineItems.nodes
|
||||
.filter((node) => !node.isCrossRepository)
|
||||
.reverse()
|
||||
for (const link of links) {
|
||||
if (seen.has(link.subject.number)) {
|
||||
continue
|
||||
}
|
||||
if (link.__typename == 'ConnectedEvent') {
|
||||
linkedIssuesPrs.add(link.subject.number)
|
||||
core.info(
|
||||
`Linked issue/PR from connected event: ${payload.repository.html_url}/pull/${link.subject.number}`,
|
||||
)
|
||||
}
|
||||
seen.add(link.subject.number)
|
||||
}
|
||||
}
|
||||
})(),
|
||||
),
|
||||
)
|
||||
|
||||
core.info(
|
||||
`Final issues/PRs to be commented on: \n${Array.from(linkedIssuesPrs)
|
||||
.map((num) => ` ${payload.repository.html_url}/pull/${num}`)
|
||||
.join('\n')}`,
|
||||
)
|
||||
|
||||
const requests: Array<Promise<unknown>> = []
|
||||
for (const issueNumber of linkedIssuesPrs) {
|
||||
const baseRequest = {
|
||||
...github.context.repo,
|
||||
issue_number: issueNumber,
|
||||
}
|
||||
if (comment) {
|
||||
const commentRequest = {
|
||||
...baseRequest,
|
||||
body: comment,
|
||||
}
|
||||
|
||||
// Check if issue is locked or not
|
||||
const { data: issue } = await octokit.rest.issues.get(baseRequest)
|
||||
|
||||
let createCommentPromise: () => Promise<void>
|
||||
if (!issue.locked) {
|
||||
createCommentPromise = async () => {
|
||||
try {
|
||||
await octokit.rest.issues.createComment(commentRequest)
|
||||
} catch (error) {
|
||||
core.error(error as Error)
|
||||
core.error(
|
||||
`Failed to comment on issue/PR: ${issueNumber}. ${payload.repository.html_url}/pull/${issueNumber}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
core.info(
|
||||
`Issue/PR is locked: ${issueNumber}. Unlocking, commenting, and re-locking. ${payload.repository.html_url}/pull/${issueNumber}`,
|
||||
)
|
||||
createCommentPromise = async () => {
|
||||
try {
|
||||
core.debug(`Unlocking issue/PR: ${issueNumber}`)
|
||||
await octokit.rest.issues.unlock(baseRequest)
|
||||
core.debug(`Commenting on issue/PR: ${issueNumber}`)
|
||||
await octokit.rest.issues.createComment(commentRequest)
|
||||
core.debug(`Re-locking issue/PR: ${issueNumber}`)
|
||||
await octokit.rest.issues.lock(baseRequest)
|
||||
} catch (error) {
|
||||
core.error(error as Error)
|
||||
core.error(
|
||||
`Failed to unlock, comment, and re-lock issue/PR: ${issueNumber}. ${payload.repository.html_url}/pull/${issueNumber}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requests.push(createCommentPromise())
|
||||
}
|
||||
if (labels) {
|
||||
const request = {
|
||||
...baseRequest,
|
||||
labels,
|
||||
}
|
||||
// core.info(JSON.stringify(request, null, 2))
|
||||
requests.push(octokit.rest.issues.addLabels(request))
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(requests)
|
||||
} catch (error) {
|
||||
core.error(error as Error)
|
||||
core.setFailed((error as Error).message)
|
||||
}
|
||||
})()
|
||||
15
.github/actions/release-commenter/tsconfig.json
vendored
Normal file
15
.github/actions/release-commenter/tsconfig.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es2020.string"],
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
||||
48
.github/actions/setup/action.yml
vendored
48
.github/actions/setup/action.yml
vendored
@@ -1,48 +0,0 @@
|
||||
name: Setup node and pnpm
|
||||
description: Configure the Node.js and pnpm versions
|
||||
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'The Node.js version to use'
|
||||
required: true
|
||||
default: 18.20.2
|
||||
pnpm-version:
|
||||
description: 'The pnpm version to use'
|
||||
required: true
|
||||
default: 9.7.0
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
shell: bash
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ inputs.pnpm-version }}
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
pnpm-store-
|
||||
pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- shell: bash
|
||||
run: pnpm install
|
||||
3927
.github/pnpm-lock.yaml
generated
vendored
Normal file
3927
.github/pnpm-lock.yaml
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
.github/pnpm-workspace.yaml
vendored
Normal file
2
.github/pnpm-workspace.yaml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- 'actions/*'
|
||||
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -17,7 +17,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.20.2
|
||||
NODE_VERSION: 22.6.0
|
||||
PNPM_VERSION: 9.7.1
|
||||
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
|
||||
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
|
||||
|
||||
31
.github/workflows/post-release.yml
vendored
Normal file
31
.github/workflows/post-release.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: post-release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
post_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Only needed if debugging on a branch other than default
|
||||
# ref: ${{ github.event.release.target_commitish || github.ref }}
|
||||
- run: echo "npm_version=$(npm pkg get version | tr -d '"')" >> "$GITHUB_ENV"
|
||||
- uses: ./.github/actions/release-commenter
|
||||
continue-on-error: true
|
||||
env:
|
||||
ACTIONS_STEP_DEBUG: true
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag-filter: 'v\d'
|
||||
|
||||
# Change to blank to disable commenting
|
||||
# comment-template: ''
|
||||
|
||||
comment-template: |
|
||||
🚀 This is included in version {release_link}
|
||||
6
.github/workflows/pr-title.yml
vendored
6
.github/workflows/pr-title.yml
vendored
@@ -39,6 +39,7 @@ jobs:
|
||||
db-mongodb
|
||||
db-postgres
|
||||
db-sqlite
|
||||
drizzle
|
||||
email-nodemailer
|
||||
eslint
|
||||
graphql
|
||||
@@ -106,14 +107,15 @@ jobs:
|
||||
label-pr-on-open:
|
||||
name: label-pr-on-open
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Tag with main branch with v2
|
||||
if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'main'
|
||||
if: github.event.pull_request.base.ref == 'main'
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: v2
|
||||
- name: Tag with beta branch with v3
|
||||
if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'beta'
|
||||
if: github.event.pull_request.base.ref == 'beta'
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: v3
|
||||
|
||||
2
.github/workflows/release-canary.yml
vendored
2
.github/workflows/release-canary.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
- beta
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.20.2
|
||||
NODE_VERSION: 22.6.0
|
||||
PNPM_VERSION: 9.7.1
|
||||
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
|
||||
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,6 +5,9 @@ dist
|
||||
!/.idea/runConfigurations
|
||||
!/.idea/payload.iml
|
||||
|
||||
# Custom actions
|
||||
!.github/actions/**/dist
|
||||
|
||||
test/packed
|
||||
test-results
|
||||
.devcontainer
|
||||
|
||||
@@ -1 +1 @@
|
||||
v18.20.2
|
||||
v22.6.0
|
||||
|
||||
2
.tool-versions
Normal file
2
.tool-versions
Normal file
@@ -0,0 +1,2 @@
|
||||
pnpm 9.7.1
|
||||
nodejs 22.6.0
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -31,8 +31,15 @@
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"editor.formatOnSaveMode": "file",
|
||||
// All ESLint rules to 'warn' to differentate from TypeScript's 'error' level
|
||||
"eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }],
|
||||
"eslint.rules.customizations": [
|
||||
// Defaultt all ESLint errors to 'warn' to differentate from TypeScript's 'error' level
|
||||
{ "rule": "*", "severity": "warn" },
|
||||
|
||||
// Silence some warnings that will get auto-fixed
|
||||
{ "rule": "perfectionist/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "curly", "severity": "off", "fixable": true },
|
||||
{ "rule": "object-shorthand", "severity": "off", "fixable": true }
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
// Load .git-blame-ignore-revs file
|
||||
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"],
|
||||
|
||||
818
CHANGELOG.md
818
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
<a href="https://payloadcms.com"><img width="100%" src="https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/assets/images/github-banner-alt.jpg?raw=true" alt="Payload headless CMS Admin panel built with React" /></a>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<p align="left">
|
||||
<a href="https://github.com/payloadcms/payload/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/payloadcms/payload/main.yml?style=flat-square"></a>
|
||||
|
||||
|
||||
@@ -196,6 +196,48 @@ import { MyFieldComponent } from 'my-external-package/client'
|
||||
|
||||
which is a valid way to access MyFieldComponent that can be resolved by the consuming project.
|
||||
|
||||
### Custom Components from unknown locations
|
||||
|
||||
By default, any component paths from known locations are added to the import map. However, if you need to add any components from unknown locations to the import map, you can do so by adding them to the `admin.dependencies` array in your Payload Config. This is mostly only relevant for plugin authors and not for regular Payload users.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
export default {
|
||||
// ...
|
||||
admin: {
|
||||
// ...
|
||||
dependencies: {
|
||||
myTestComponent: { // myTestComponent is the key - can be anything
|
||||
path: '/components/TestComponent.js#TestComponent',
|
||||
type: 'component',
|
||||
clientProps: {
|
||||
test: 'hello',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way, `TestComponent` is added to the import map, no matter if it's referenced in a known location or not. On the client, you can then use the component like this:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { RenderComponent, useConfig } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const CustomView = () => {
|
||||
const { config } = useConfig()
|
||||
return (
|
||||
<div>
|
||||
<RenderComponent mappedComponent={config.admin.dependencies?.myTestComponent} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Root Components
|
||||
|
||||
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
|
||||
@@ -234,7 +276,8 @@ The following options are available:
|
||||
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
|
||||
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
|
||||
| **`providers`** | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](#custom-providers). |
|
||||
| **`actions`** | An array of Custom Components to be rendered in the header of the Admin Panel, providing additional interactivity and functionality. |
|
||||
| **`actions`** | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. |
|
||||
| **`header`** | An array of Custom Components to be injected above the Payload header. |
|
||||
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
|
||||
|
||||
<Banner type="success">
|
||||
@@ -389,14 +432,14 @@ export const MyClientComponent: React.FC = () => {
|
||||
See [Using Hooks](#using-hooks) for more details.
|
||||
</Banner>
|
||||
|
||||
All [Field Components](./fields) automatically receive their respective Client Field Config through a common [`field`](./fields#the-field-prop) prop:
|
||||
All [Field Components](./fields) automatically receive their respective Field Config through a common [`field`](./fields#the-field-prop) prop:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import type { TextFieldProps } from 'payload'
|
||||
import type { TextFieldClientComponent } from 'payload'
|
||||
|
||||
export const MyClientFieldComponent: TextFieldProps = ({ field: { name } }) => {
|
||||
export const MyClientFieldComponent: TextFieldClientComponent = ({ field: { name } }) => {
|
||||
return (
|
||||
<p>
|
||||
{`This field's name is ${name}`}
|
||||
|
||||
@@ -136,7 +136,8 @@ All Field Components receive the following props:
|
||||
| Property | Description |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document.
|
||||
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
|
||||
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
|
||||
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
|
||||
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
|
||||
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
@@ -175,51 +176,50 @@ export const CustomTextField: React.FC = () => {
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Field Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview). The convention is to append `Props` to the type of field, i.e. `TextFieldProps`.
|
||||
When building Custom Field Components, you can import the component type to ensure type safety. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and for every client/server environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldProps,
|
||||
BlocksFieldProps,
|
||||
CheckboxFieldProps,
|
||||
CodeFieldProps,
|
||||
CollapsibleFieldProps,
|
||||
DateFieldProps,
|
||||
EmailFieldProps,
|
||||
GroupFieldProps,
|
||||
HiddenFieldProps,
|
||||
JSONFieldProps,
|
||||
NumberFieldProps,
|
||||
PointFieldProps,
|
||||
RadioFieldProps,
|
||||
RelationshipFieldProps,
|
||||
RichTextFieldProps,
|
||||
RowFieldProps,
|
||||
SelectFieldProps,
|
||||
TabsFieldProps,
|
||||
TextFieldProps,
|
||||
TextareaFieldProps,
|
||||
UploadFieldProps
|
||||
TextFieldClientComponent,
|
||||
TextFieldServerComponent,
|
||||
TextFieldClientProps,
|
||||
TextFieldServerProps,
|
||||
// ...and so on for each Field Type
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### The `field` Prop
|
||||
|
||||
All Field Components are passed a client-friendly version of their Field Config through a common `field` prop. Since the raw Field Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types), Payload sanitized it into a [Client Config](./components#accessing-the-payload-config) that is safe to pass into Client Components.
|
||||
All Field Components are passed their own Field Config through a common `field` prop. Within Server Components, this is the original Field Config as written within your Payload Config. Within Client Components, however, this is a "Client Config", which is a sanitized, client-friendly version of the Field Config. This is because the original Field Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types), meaning it cannot be passed into Client Components without first being transformed.
|
||||
|
||||
The exact shape of this prop is unique to the specific [Field Type](../fields/overview) being rendered, minus all non-serializable properties. Any [Custom Components](../components) are also resolved into a "mapped component" that is safe to pass.
|
||||
The Client Field Config is an exact copy of the original Field Config, minus all non-serializable properties, plus all evaluated functions such as field labels, [Custom Components](../components), etc.
|
||||
|
||||
Server Component:
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import type { TextFieldServerComponent } from 'payload'
|
||||
import { TextField } from '@payloadcms/ui'
|
||||
|
||||
export const MyServerField: TextFieldServerComponent = ({ clientField }) => {
|
||||
return <TextField field={clientField} />
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
<strong>Tip:</strong>
|
||||
Server Components can still access the original Field Config through the `field` prop.
|
||||
</Banner>
|
||||
|
||||
Client Component:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import type { TextFieldProps } from 'payload'
|
||||
import type { TextFieldClientComponent } from 'payload'
|
||||
|
||||
export const MyClientFieldComponent: React.FC<TextFieldProps> = ({ field: { name } }) => {
|
||||
return (
|
||||
<p>
|
||||
{`This field's name is ${name}`}
|
||||
</p>
|
||||
)
|
||||
export const MyTextField: TextFieldClientComponent = ({ field }) => {
|
||||
return <TextField field={field} />
|
||||
}
|
||||
```
|
||||
|
||||
@@ -238,40 +238,18 @@ The following additional properties are also provided to the `field` prop:
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview). The convention is to append `Client` to the type of field, i.e. `TextFieldClient`.
|
||||
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldClient,
|
||||
BlocksFieldClient,
|
||||
CheckboxFieldClient,
|
||||
CodeFieldClient,
|
||||
CollapsibleFieldClient,
|
||||
DateFieldClient,
|
||||
EmailFieldClient,
|
||||
GroupFieldClient,
|
||||
HiddenFieldClient,
|
||||
JSONFieldClient,
|
||||
NumberFieldClient,
|
||||
PointFieldClient,
|
||||
RadioFieldClient,
|
||||
RelationshipFieldClient,
|
||||
RichTextFieldClient,
|
||||
RowFieldClient,
|
||||
SelectFieldClient,
|
||||
TabsFieldClient,
|
||||
TextFieldClient,
|
||||
TextareaFieldClient,
|
||||
UploadFieldClient
|
||||
TextFieldClientComponent,
|
||||
TextFieldServerComponent,
|
||||
TextFieldClientProps,
|
||||
TextFieldServerProps,
|
||||
// ...and so on for each Field Type
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
When working on the client, you will never have access to objects of type `Field`. This is reserved for the server-side. Instead, you can use `ClientField` which is a union type of all the client fields:
|
||||
|
||||
```tsx
|
||||
import type { ClientField } from 'payload'
|
||||
```
|
||||
|
||||
### The Cell Component
|
||||
|
||||
The Cell Component is rendered in the table of the List View. It represents the value of the field when displayed in a table cell.
|
||||
@@ -298,7 +276,8 @@ All Cell Components receive the following props:
|
||||
|
||||
| Property | Description |
|
||||
| ---------------- | ----------------------------------------------------------------- |
|
||||
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
|
||||
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
|
||||
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
|
||||
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
|
||||
| **`onClick`** | A function that is called when the cell is clicked. |
|
||||
|
||||
@@ -338,7 +317,8 @@ Custom Label Components receive all [Field Component](#the-field-component) prop
|
||||
|
||||
| Property | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
|
||||
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
|
||||
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Reminder:</strong>
|
||||
@@ -353,7 +333,7 @@ When building Custom Label Components, you can import the component props to ens
|
||||
import type {
|
||||
TextFieldLabelServerComponent,
|
||||
TextFieldLabelClientComponent,
|
||||
// And so on for each Field Type
|
||||
// ...and so on for each Field Type
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
@@ -383,7 +363,8 @@ Custom Error Components receive all [Field Component](#the-field-component) prop
|
||||
|
||||
| Property | Description |
|
||||
| --------------- | ------------------------------------------------------------- |
|
||||
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
|
||||
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
|
||||
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Reminder:</strong>
|
||||
@@ -499,7 +480,8 @@ Custom Description Components receive all [Field Component](#the-field-component
|
||||
|
||||
| Property | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
|
||||
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
|
||||
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Reminder:</strong>
|
||||
|
||||
@@ -113,7 +113,7 @@ You can pass a Redux-like selector into the hook, which will ensure that you ret
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import type { useFormFields } from '@payloadcms/ui'
|
||||
import { useFormFields } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// Get only the `amount` field state, and only cause a rerender when that field changes
|
||||
|
||||
@@ -31,8 +31,9 @@ const config = buildConfig({
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
dashboard: {
|
||||
Component: '/path/to/MyCustomDashboardView#MyCustomDashboardViewComponent', // highlight-line
|
||||
customView: {
|
||||
Component: '/path/to/MyCustomView#MyCustomView', // highlight-line
|
||||
path: '/my-custom-view',
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -40,7 +41,42 @@ const config = buildConfig({
|
||||
})
|
||||
```
|
||||
|
||||
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
|
||||
Your Custom Root Views can optionally use one of the templates that Payload provides. The most common of these is the Default Template which provides the basic layout and navigation. Here is an example of what that might look like:
|
||||
|
||||
```tsx
|
||||
import type { AdminViewProps } from 'payload'
|
||||
|
||||
import { DefaultTemplate } from '@payloadcms/next/templates'
|
||||
import { Gutter } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const MyCustomView: React.FC<AdminViewProps> = ({
|
||||
initPageResult,
|
||||
params,
|
||||
searchParams,
|
||||
}) => {
|
||||
return (
|
||||
<DefaultTemplate
|
||||
i18n={initPageResult.req.i18n}
|
||||
locale={initPageResult.locale}
|
||||
params={params}
|
||||
payload={initPageResult.req.payload}
|
||||
permissions={initPageResult.permissions}
|
||||
searchParams={searchParams}
|
||||
user={initPageResult.req.user || undefined}
|
||||
visibleEntities={initPageResult.visibleEntities}
|
||||
>
|
||||
<Gutter>
|
||||
<h1>Custom Default Root View</h1>
|
||||
<br />
|
||||
<p>This view uses the Default Template.</p>
|
||||
</Gutter>
|
||||
</DefaultTemplate>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
|
||||
|
||||
The following options are available:
|
||||
|
||||
@@ -133,7 +169,7 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
|
||||
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
@@ -184,7 +220,7 @@ export const MyGlobalConfig: SanitizedGlobalConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
|
||||
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
@@ -227,7 +263,7 @@ export const MyCollectionOrGlobalConfig: SanitizedCollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
|
||||
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
@@ -312,13 +348,11 @@ Your Custom Views will be provided with the following props:
|
||||
|
||||
| Prop | Description |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`user`** | The currently logged in user. |
|
||||
| **`locale`** | The current [Locale](../configuration/localization) of the [Admin Panel](./overview). |
|
||||
| **`navGroups`** | The grouped navigation items according to `admin.group` in your [Collection Config](../collections/overview) or [Global Config](../globals/overview). |
|
||||
| **`params`** | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
|
||||
| **`permissions`** | The permissions of the currently logged in user. |
|
||||
| **`searchParams`** | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
|
||||
| **`visibleEntities`** | The current user's visible entities according to your [Access Control](../access-control/overview). |
|
||||
| **`initPageResult`** | An object containing `req`, `payload`, `permissions`, etc. |
|
||||
| **`clientConfig`** | The Client Config object. [More details](../components#accessing-the-payload-config). |
|
||||
| **`importMap`** | The import map object. |
|
||||
| **`params`** | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
|
||||
| **`searchParams`** | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Reminder:</strong>
|
||||
|
||||
@@ -85,6 +85,7 @@ The following options are available:
|
||||
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
|
||||
| **`loginWithUsername`** | Ability to allow users to login with username/password. [More](/docs/authentication/overview#login-with-username) |
|
||||
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
|
||||
| **`removeTokenFromResponses`** | Set to true if you want to remove the token from the returned authentication API responses such as login or refresh. |
|
||||
| **`strategies`** | Advanced - an array of custom authentification strategies to extend this collection's authentication with. [More details](./custom-strategies). |
|
||||
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
|
||||
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
|
||||
|
||||
@@ -71,9 +71,11 @@ The following options are available:
|
||||
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
||||
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
|
||||
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
|
||||
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
|
||||
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
|
||||
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
||||
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
|
||||
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
|
||||
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/overview#csrf-protection). |
|
||||
@@ -253,3 +255,13 @@ import type { Config, SanitizedConfig } from 'payload'
|
||||
The Payload Config only lives on the server and is not allowed to contain any client-side code. That way, you can load up the Payload Config in any server environment or standalone script, without having to use Bundlers or Node.js loaders to handle importing client-only modules (e.g. scss files or React Components) without any errors.
|
||||
|
||||
Behind the curtains, the Next.js-based Admin Panel generates a ClientConfig, which strips away any server-only code and enriches the config with React Components.
|
||||
|
||||
## Compatibility flags
|
||||
|
||||
The Payload Config can accept compatibility flags for running the newest versions but with older databases. You should only use these flags if you need to, and should confirm that you need to prior to enabling these flags.
|
||||
|
||||
`allowLocalizedWithinLocalized`
|
||||
|
||||
Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized.
|
||||
|
||||
By default, Payload will remove the `localized: true` property from sub-fields if a parent field is localized. Set this compatibility flag to `true` only if you have an existing Payload MongoDB database from pre-3.0, and you have nested localized fields that you would like to maintain without migrating.
|
||||
|
||||
@@ -57,7 +57,6 @@ export default buildConfig({
|
||||
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres` or to `@vercel/postgres` |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
| `schemaName` (experimental) | A string for the postgres schema to use, defaults to 'public'. |
|
||||
| `idType` | A string of 'serial', or 'uuid' that is used for the data type given to id columns. |
|
||||
| `transactionOptions` | A PgTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
|
||||
|
||||
@@ -11,6 +11,7 @@ Payload provides a vast array of examples to help you get started with your proj
|
||||
Examples are changing every day, so be sure to check back often to see what new examples have been added. If you have a specific example you would like to see, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions) or open a new [PR](https://github.com/payloadcms/payload/pulls) to add it yourself.
|
||||
|
||||
- [Auth](https://github.com/payloadcms/payload/tree/main/examples/auth)
|
||||
- [Custom Components](https://github.com/payloadcms/payload/tree/main/examples/custom-components)
|
||||
- [Custom Server](https://github.com/payloadcms/payload/tree/main/examples/custom-server)
|
||||
- [Draft Preview](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
|
||||
- [Email](https://github.com/payloadcms/payload/tree/main/examples/email)
|
||||
|
||||
@@ -20,7 +20,7 @@ Payload's rich text field is built on an "adapter pattern" which lets you specif
|
||||
Right now, Payload is officially supporting two rich text editors:
|
||||
|
||||
1. [SlateJS](/docs/rich-text/slate) - stable, backwards-compatible with 1.0
|
||||
2. [Lexical](/docs/rich-text/lexical) - beta, where things will be moving
|
||||
2. [Lexical](/docs/lexical/overview) - beta, where things will be moving
|
||||
|
||||
<Banner type="success">
|
||||
<strong>
|
||||
@@ -82,4 +82,4 @@ The Rich Text Field inherits all of the default options from the base [Field Adm
|
||||
|
||||
## Editor-specific Options
|
||||
|
||||
For a ton more editor-specific options, including how to build custom rich text elements directly into your editor, take a look at either the [Slate docs](/docs/rich-text/slate) or the [Lexical docs](/docs/rich-text/lexical) depending on which editor you're using.
|
||||
For a ton more editor-specific options, including how to build custom rich text elements directly into your editor, take a look at either the [Slate docs](/docs/rich-text/slate) or the [Lexical docs](/docs/lexical/overview) depending on which editor you're using.
|
||||
|
||||
@@ -68,7 +68,7 @@ Here's a quick example of a React Server Component fetching data using the Local
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import config from '@payload-config'
|
||||
import { getPayloadHMR } from '@payloadcms/next'
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
|
||||
const MyServerComponent: React.FC = () => {
|
||||
// If you're working in Next.js, and you want HMR,
|
||||
|
||||
@@ -200,7 +200,7 @@ user-friendly.
|
||||
The `beforeDuplicate` field hook is called on each locale (when using localization), when duplicating a document. It may be used when documents having the
|
||||
exact same properties may cause issue. This gives you a way to avoid duplicate names on `unique`, `required` fields or when external systems expect non-repeating values on documents.
|
||||
|
||||
This hook gets called after `beforeChange` hooks are called and before the document is saved to the database.
|
||||
This hook gets called before the `beforeValidate` and `beforeChange` hooks are called.
|
||||
|
||||
By Default, unique and required text fields Payload will append "- Copy" to the original document value. The default is not added if your field has its own, you must return non-unique values from your beforeDuplicate hook to avoid errors or enable the `disableDuplicate` option on the collection.
|
||||
Here is an example of a number field with a hook that increments the number to avoid unique constraint errors when duplicating a document:
|
||||
|
||||
@@ -34,7 +34,7 @@ Then, render the `RefreshRouteOnSave` component anywhere in your `page.tsx`. Her
|
||||
|
||||
```tsx
|
||||
import { RefreshRouteOnSave } from './RefreshRouteOnSave.tsx'
|
||||
import { getPayloadHMR } from '@payloadcms/next'
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import config from '../payload.config'
|
||||
|
||||
export default async function Page() {
|
||||
|
||||
@@ -25,13 +25,12 @@ export const defaultESLintIgnores = [
|
||||
let FlatConfig
|
||||
|
||||
export const rootParserOptions = {
|
||||
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
||||
EXPERIMENTAL_useProjectService: {
|
||||
allowDefaultProjectForFiles: ['./src/*.ts', './src/*.tsx'],
|
||||
maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 100,
|
||||
},
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest',
|
||||
projectService: {
|
||||
maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 40,
|
||||
allowDefaultProject: ['scripts/*.ts', '*.js', '*.mjs', '*.spec.ts', '*.d.ts'],
|
||||
},
|
||||
}
|
||||
|
||||
/** @type {FlatConfig[]} */
|
||||
@@ -40,20 +39,12 @@ export const rootEslintConfig = [
|
||||
{
|
||||
ignores: [
|
||||
...defaultESLintIgnores,
|
||||
'packages/eslint-*/**',
|
||||
'test/live-preview/next-app',
|
||||
'packages/**/*.spec.ts',
|
||||
'templates/**',
|
||||
],
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
tsconfigDirName: import.meta.dirname,
|
||||
...rootParserOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
payload: payloadPlugin,
|
||||
@@ -78,6 +69,15 @@ export const rootEslintConfig = [
|
||||
|
||||
export default [
|
||||
...rootEslintConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
...rootParserOptions,
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/eslint-config/**/*.ts'],
|
||||
rules: {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
:global([data-theme="light"]) {
|
||||
:global([data-theme='light']) {
|
||||
.logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ export const RenderParams: React.FC<{
|
||||
className?: string
|
||||
}> = ({ params = ['error', 'message', 'success'], message, className }) => {
|
||||
const searchParams = useSearchParams()
|
||||
const paramValues = params.map(param => searchParams.get(param)).filter(Boolean)
|
||||
const paramValues = params.map((param) => searchParams.get(param)).filter(Boolean)
|
||||
|
||||
if (paramValues.length) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{paramValues.map(paramValue => (
|
||||
{paramValues.map((paramValue) => (
|
||||
<Message
|
||||
key={paramValue}
|
||||
message={(message || 'PARAM')?.replace('PARAM', paramValue || '')}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@use './queries.scss' as *;
|
||||
@use './colors.scss' as *;
|
||||
@use './type.scss' as *;
|
||||
@import "./theme.scss";
|
||||
@import './theme.scss';
|
||||
|
||||
:root {
|
||||
--base: 24px;
|
||||
@@ -88,7 +88,7 @@ p {
|
||||
margin: var(--base) 0;
|
||||
|
||||
@include mid-break {
|
||||
margin: calc(var(--base) * .75) 0;
|
||||
margin: calc(var(--base) * 0.75) 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,12 +102,12 @@ a {
|
||||
color: currentColor;
|
||||
|
||||
&:focus {
|
||||
opacity: .8;
|
||||
opacity: 0.8;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: .7;
|
||||
opacity: 0.7;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const [user, setUser] = useState<User | null>()
|
||||
|
||||
const create = useCallback<Create>(
|
||||
async args => {
|
||||
async (args) => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users`, args)
|
||||
setUser(user)
|
||||
@@ -38,7 +38,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
)
|
||||
|
||||
const login = useCallback<Login>(
|
||||
async args => {
|
||||
async (args) => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/login`, args)
|
||||
setUser(user)
|
||||
@@ -110,7 +110,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
}, [api])
|
||||
|
||||
const forgotPassword = useCallback<ForgotPassword>(
|
||||
async args => {
|
||||
async (args) => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/forgot-password`,
|
||||
@@ -132,7 +132,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
)
|
||||
|
||||
const resetPassword = useCallback<ResetPassword>(
|
||||
async args => {
|
||||
async (args) => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/reset-password`,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.form {
|
||||
margin-bottom: var(--base);
|
||||
|
||||
@@ -139,7 +139,7 @@ export const AccountForm: React.FC = () => {
|
||||
label="Confirm Password"
|
||||
required
|
||||
register={register}
|
||||
validate={value => value === password.current || 'The passwords do not match'}
|
||||
validate={(value) => value === password.current || 'The passwords do not match'}
|
||||
error={errors.passwordConfirm}
|
||||
/>
|
||||
</Fragment>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.form {
|
||||
margin-bottom: var(--base);
|
||||
|
||||
@@ -103,7 +103,7 @@ export const CreateAccountForm: React.FC = () => {
|
||||
label="Confirm Password"
|
||||
required
|
||||
register={register}
|
||||
validate={value => value === password.current || 'The passwords do not match'}
|
||||
validate={(value) => value === password.current || 'The passwords do not match'}
|
||||
error={errors.passwordConfirm}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../_css/common";
|
||||
@import '../_css/common';
|
||||
|
||||
.createAccount {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.form {
|
||||
margin-bottom: var(--base);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../_css/common";
|
||||
@import '../_css/common';
|
||||
|
||||
.login {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -5,7 +5,7 @@ import Link from 'next/link'
|
||||
|
||||
import { useAuth } from '../../_providers/Auth'
|
||||
|
||||
export const LogoutPage: React.FC = props => {
|
||||
export const LogoutPage: React.FC = (props) => {
|
||||
const { logout } = useAuth()
|
||||
const [success, setSuccess] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
@@ -20,4 +20,3 @@
|
||||
.message {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../_css/common";
|
||||
@import '../_css/common';
|
||||
|
||||
.recoverPassword {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.form {
|
||||
width: 66.66%;
|
||||
|
||||
2097
examples/auth/next-app/pnpm-lock.yaml
generated
2097
examples/auth/next-app/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1799
examples/auth/next-pages/pnpm-lock.yaml
generated
1799
examples/auth/next-pages/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
:global([data-theme="light"]) {
|
||||
:global([data-theme='light']) {
|
||||
.logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ export const RenderParams: React.FC<{
|
||||
}> = ({ params = ['error', 'message', 'success'], message, className }) => {
|
||||
const router = useRouter()
|
||||
const searchParams = new URLSearchParams(router.query as any)
|
||||
const paramValues = params.map(param => searchParams.get(param)).filter(Boolean)
|
||||
const paramValues = params.map((param) => searchParams.get(param)).filter(Boolean)
|
||||
|
||||
if (paramValues.length) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{paramValues.map(paramValue => (
|
||||
{paramValues.map((paramValue) => (
|
||||
<Message key={paramValue} message={(message || 'PARAM')?.replace('PARAM', paramValue)} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@use './queries.scss' as *;
|
||||
@use './colors.scss' as *;
|
||||
@use './type.scss' as *;
|
||||
@import "./theme.scss";
|
||||
@import './theme.scss';
|
||||
|
||||
:root {
|
||||
--base: 24px;
|
||||
@@ -88,7 +88,7 @@ p {
|
||||
margin: var(--base) 0;
|
||||
|
||||
@include mid-break {
|
||||
margin: calc(var(--base) * .75) 0;
|
||||
margin: calc(var(--base) * 0.75) 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,12 +102,12 @@ a {
|
||||
color: currentColor;
|
||||
|
||||
&:focus {
|
||||
opacity: .8;
|
||||
opacity: 0.8;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: .7;
|
||||
opacity: 0.7;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../css/common";
|
||||
@import '../../css/common';
|
||||
|
||||
.account {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -150,7 +150,7 @@ const Account: React.FC = () => {
|
||||
label="Confirm Password"
|
||||
required
|
||||
register={register}
|
||||
validate={value => value === password.current || 'The passwords do not match'}
|
||||
validate={(value) => value === password.current || 'The passwords do not match'}
|
||||
error={errors.passwordConfirm}
|
||||
/>
|
||||
</Fragment>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../css/common";
|
||||
@import '../../css/common';
|
||||
|
||||
.createAccount {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -106,7 +106,7 @@ const CreateAccount: React.FC = () => {
|
||||
label="Confirm Password"
|
||||
required
|
||||
register={register}
|
||||
validate={value => value === password.current || 'The passwords do not match'}
|
||||
validate={(value) => value === password.current || 'The passwords do not match'}
|
||||
error={errors.passwordConfirm}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../css/common";
|
||||
@import '../../css/common';
|
||||
|
||||
.login {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../css/common";
|
||||
@import '../../css/common';
|
||||
|
||||
.recoverPassword {
|
||||
margin-bottom: var(--block-padding);
|
||||
@@ -24,4 +24,3 @@
|
||||
.message {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../css/common";
|
||||
@import '../../css/common';
|
||||
|
||||
.resetPassword {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -14,7 +14,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const [user, setUser] = useState<User | null>()
|
||||
|
||||
const create = useCallback<Create>(
|
||||
async args => {
|
||||
async (args) => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users`, args)
|
||||
setUser(user)
|
||||
@@ -36,7 +36,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
)
|
||||
|
||||
const login = useCallback<Login>(
|
||||
async args => {
|
||||
async (args) => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/login`, args)
|
||||
setUser(user)
|
||||
@@ -108,7 +108,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
}, [api])
|
||||
|
||||
const forgotPassword = useCallback<ForgotPassword>(
|
||||
async args => {
|
||||
async (args) => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/forgot-password`,
|
||||
@@ -130,7 +130,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
)
|
||||
|
||||
const resetPassword = useCallback<ResetPassword>(
|
||||
async args => {
|
||||
async (args) => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/reset-password`,
|
||||
|
||||
@@ -48,7 +48,7 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
|
||||
|
||||
```ts
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { getPayloadHMR } from '@payloadcms/next'
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import config from '../../payload.config'
|
||||
|
||||
export default async function AccountPage({ searchParams }) {
|
||||
|
||||
4636
examples/auth/payload/pnpm-lock.yaml
generated
4636
examples/auth/payload/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { ElementType } from 'react';
|
||||
import type { ElementType } from 'react'
|
||||
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Ref } from 'react';
|
||||
import type { Ref } from 'react'
|
||||
|
||||
import React, { forwardRef } from 'react'
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
:global([data-theme="light"]) {
|
||||
:global([data-theme='light']) {
|
||||
.logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ export const RenderParams: React.FC<{
|
||||
params?: string[]
|
||||
}> = ({ className, message, params = ['error', 'message', 'success'] }) => {
|
||||
const searchParams = useSearchParams()
|
||||
const paramValues = params.map(param => searchParams.get(param)).filter(Boolean)
|
||||
const paramValues = params.map((param) => searchParams.get(param)).filter(Boolean)
|
||||
|
||||
if (paramValues.length) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{paramValues.map(paramValue => (
|
||||
{paramValues.map((paramValue) => (
|
||||
<Message
|
||||
key={paramValue}
|
||||
message={(message || 'PARAM')?.replace('PARAM', paramValue || '')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.form {
|
||||
margin-bottom: var(--base);
|
||||
|
||||
@@ -136,7 +136,7 @@ export const AccountForm: React.FC = () => {
|
||||
register={register}
|
||||
required
|
||||
type="password"
|
||||
validate={value => value === password.current || 'The passwords do not match'}
|
||||
validate={(value) => value === password.current || 'The passwords do not match'}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.form {
|
||||
margin-bottom: var(--base);
|
||||
|
||||
@@ -103,7 +103,7 @@ export const CreateAccountForm: React.FC = () => {
|
||||
register={register}
|
||||
required
|
||||
type="password"
|
||||
validate={value => value === password.current || 'The passwords do not match'}
|
||||
validate={(value) => value === password.current || 'The passwords do not match'}
|
||||
/>
|
||||
<Button
|
||||
appearance="primary"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../_css/common";
|
||||
@import '../_css/common';
|
||||
|
||||
.createAccount {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.form {
|
||||
margin-bottom: var(--base);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../_css/common";
|
||||
@import '../_css/common';
|
||||
|
||||
.login {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
@@ -20,4 +20,3 @@
|
||||
.message {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../_css/common";
|
||||
@import '../_css/common';
|
||||
|
||||
.recoverPassword {
|
||||
margin-bottom: var(--block-padding);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../_css/common";
|
||||
@import '../../_css/common';
|
||||
|
||||
.form {
|
||||
width: 66.66%;
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { User } from '../../payload-types'
|
||||
export const checkRole = (allRoles: User['roles'] = [], user: User = undefined): boolean => {
|
||||
if (user) {
|
||||
if (
|
||||
allRoles.some(role => {
|
||||
return user?.roles?.some(individualRole => {
|
||||
allRoles.some((role) => {
|
||||
return user?.roles?.some((individualRole) => {
|
||||
return individualRole === role
|
||||
})
|
||||
})
|
||||
|
||||
3
examples/custom-components/.env.example
Normal file
3
examples/custom-components/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
DATABASE_URI=mongodb://127.0.0.1/payload-example-custom-fields
|
||||
PAYLOAD_SECRET=PAYLOAD_CUSTOM_FIELDS_EXAMPLE_SECRET_KEY
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
4
examples/custom-components/.eslintrc.cjs
Normal file
4
examples/custom-components/.eslintrc.cjs
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@payloadcms'],
|
||||
}
|
||||
5
examples/custom-components/.gitignore
vendored
Normal file
5
examples/custom-components/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
build
|
||||
dist
|
||||
node_modules
|
||||
package-lock.json
|
||||
.env
|
||||
24
examples/custom-components/.swcrc
Normal file
24
examples/custom-components/.swcrc
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
},
|
||||
"transform": {
|
||||
"react": {
|
||||
"runtime": "automatic",
|
||||
"pragmaFrag": "React.Fragment",
|
||||
"throwIfNamespace": true,
|
||||
"development": false,
|
||||
"useBuiltins": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
46
examples/custom-components/README.md
Normal file
46
examples/custom-components/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Payload Custom Components Example
|
||||
|
||||
This example demonstrates how to use Custom Components in [Payload](https://github.com/payloadcms/payload) Admin Panel. This example includes custom components for every field type available in Payload, including both server and client components. It also includes custom views, custom nav links, and more.
|
||||
|
||||
## Quick Start
|
||||
|
||||
To spin up this example locally, follow these steps:
|
||||
|
||||
1. Clone this repo
|
||||
1. `cd` into this directory and run `pnpm i --ignore-workspace`\*, `yarn`, or `npm install`
|
||||
|
||||
> \*If you are running using pnpm within the Payload Monorepo, the `--ignore-workspace` flag is needed so that pnpm generates a lockfile in this example's directory despite the fact that one exists in root.
|
||||
|
||||
1. `pnpm dev`, `yarn dev` or `npm run dev` to start the server
|
||||
- Press `y` when prompted to seed the database
|
||||
1. `open http://localhost:3000` to access the home page
|
||||
1. `open http://localhost:3000/admin` to access the admin panel
|
||||
- Login with email `demo@payloadcms.com` and password `demo`
|
||||
|
||||
## How it works
|
||||
|
||||
### Collections
|
||||
|
||||
See the [Collections](https://payloadcms.com/docs/configuration/collections) docs for details on how to extend any of this functionality.
|
||||
|
||||
- #### Users
|
||||
|
||||
The `users` collection is auth-enabled which provides access to the admin panel.
|
||||
|
||||
For additional help with authentication, see the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth/cms#readme) or the [Authentication](https://payloadcms.com/docs/authentication/overview#authentication-overview) docs.
|
||||
|
||||
- #### Fields
|
||||
|
||||
The `fields` collection contains every field type available in Payload, each with custom components filled in every available "slot", i.e. `admin.components.Field`, `admin.components.Label`, etc. There are two of every field, one for server components, and the other for client components. This pattern shows how to use custom components in both environments, no matter which field type you are using.
|
||||
|
||||
- #### Views
|
||||
|
||||
The `views` collection demonstrates how to add collection-level views, including the default view and custom tabs.
|
||||
|
||||
- #### Root Views
|
||||
|
||||
The `root-views` collection demonstrates how to add a root document-level view to the admin panel.
|
||||
|
||||
## Questions
|
||||
|
||||
If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions).
|
||||
5
examples/custom-components/next-env.d.ts
vendored
Normal file
5
examples/custom-components/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
8
examples/custom-components/next.config.mjs
Normal file
8
examples/custom-components/next.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default withPayload(nextConfig)
|
||||
43
examples/custom-components/package.json
Normal file
43
examples/custom-components/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "payload-example-custom-fields",
|
||||
"version": "1.0.0",
|
||||
"description": "An example of custom fields in Payload.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"_dev": "cross-env NODE_OPTIONS=--no-deprecation && pnpm generate:importmap && next dev",
|
||||
"build": "cross-env NODE_OPTIONS=--no-deprecation next build",
|
||||
"dev": "cross-env NODE_OPTIONS=--no-deprecation && pnpm generate:importmap && pnpm seed && next dev --turbo",
|
||||
"generate:importmap": "payload generate:importmap",
|
||||
"generate:schema": "payload-graphql generate:schema",
|
||||
"generate:types": "payload generate:types",
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
"seed": "npm run payload migrate:fresh",
|
||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/db-mongodb": "3.0.0-beta.106",
|
||||
"@payloadcms/next": "3.0.0-beta.106",
|
||||
"@payloadcms/richtext-lexical": "3.0.0-beta.106",
|
||||
"@payloadcms/ui": "3.0.0-beta.106",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^8.2.0",
|
||||
"graphql": "^16.9.0",
|
||||
"next": "15.0.0-canary.104",
|
||||
"payload": "3.0.0-beta.106",
|
||||
"react": "19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/graphql": "3.0.0-beta.106",
|
||||
"@types/react": "npm:types-react@19.0.0-beta.2",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-beta.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "15.0.0-canary.146",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "5.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0"
|
||||
}
|
||||
}
|
||||
6897
examples/custom-components/pnpm-lock.yaml
generated
Normal file
6897
examples/custom-components/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { generatePageMetadata, NotFoundPage } from '@payloadcms/next/views'
|
||||
|
||||
import { importMap } from '../importMap.js'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
segments: string[]
|
||||
}
|
||||
searchParams: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) =>
|
||||
NotFoundPage({ config, importMap, params, searchParams })
|
||||
|
||||
export default NotFound
|
||||
@@ -0,0 +1,25 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { generatePageMetadata, RootPage } from '@payloadcms/next/views'
|
||||
|
||||
import { importMap } from '../importMap.js'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
segments: string[]
|
||||
}
|
||||
searchParams: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const Page = ({ params, searchParams }: Args) =>
|
||||
RootPage({ config, importMap, params, searchParams })
|
||||
|
||||
export default Page
|
||||
113
examples/custom-components/src/app/(payload)/admin/importMap.js
Normal file
113
examples/custom-components/src/app/(payload)/admin/importMap.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { CustomArrayFieldLabelServer as CustomArrayFieldLabelServer_0 } from '@/collections/Fields/array/components/server/Label'
|
||||
import { CustomArrayFieldServer as CustomArrayFieldServer_1 } from '@/collections/Fields/array/components/server/Field'
|
||||
import { CustomArrayFieldLabelClient as CustomArrayFieldLabelClient_2 } from '@/collections/Fields/array/components/client/Label'
|
||||
import { CustomArrayFieldClient as CustomArrayFieldClient_3 } from '@/collections/Fields/array/components/client/Field'
|
||||
import { CustomBlocksFieldServer as CustomBlocksFieldServer_4 } from '@/collections/Fields/blocks/components/server/Field'
|
||||
import { CustomBlocksFieldClient as CustomBlocksFieldClient_5 } from '@/collections/Fields/blocks/components/client/Field'
|
||||
import { CustomCheckboxFieldLabelServer as CustomCheckboxFieldLabelServer_6 } from '@/collections/Fields/checkbox/components/server/Label'
|
||||
import { CustomCheckboxFieldServer as CustomCheckboxFieldServer_7 } from '@/collections/Fields/checkbox/components/server/Field'
|
||||
import { CustomCheckboxFieldLabelClient as CustomCheckboxFieldLabelClient_8 } from '@/collections/Fields/checkbox/components/client/Label'
|
||||
import { CustomCheckboxFieldClient as CustomCheckboxFieldClient_9 } from '@/collections/Fields/checkbox/components/client/Field'
|
||||
import { CustomDateFieldLabelServer as CustomDateFieldLabelServer_10 } from '@/collections/Fields/date/components/server/Label'
|
||||
import { CustomDateFieldServer as CustomDateFieldServer_11 } from '@/collections/Fields/date/components/server/Field'
|
||||
import { CustomDateFieldLabelClient as CustomDateFieldLabelClient_12 } from '@/collections/Fields/date/components/client/Label'
|
||||
import { CustomDateFieldClient as CustomDateFieldClient_13 } from '@/collections/Fields/date/components/client/Field'
|
||||
import { CustomEmailFieldLabelServer as CustomEmailFieldLabelServer_14 } from '@/collections/Fields/email/components/server/Label'
|
||||
import { CustomEmailFieldServer as CustomEmailFieldServer_15 } from '@/collections/Fields/email/components/server/Field'
|
||||
import { CustomEmailFieldLabelClient as CustomEmailFieldLabelClient_16 } from '@/collections/Fields/email/components/client/Label'
|
||||
import { CustomEmailFieldClient as CustomEmailFieldClient_17 } from '@/collections/Fields/email/components/client/Field'
|
||||
import { CustomNumberFieldLabelServer as CustomNumberFieldLabelServer_18 } from '@/collections/Fields/number/components/server/Label'
|
||||
import { CustomNumberFieldServer as CustomNumberFieldServer_19 } from '@/collections/Fields/number/components/server/Field'
|
||||
import { CustomNumberFieldLabelClient as CustomNumberFieldLabelClient_20 } from '@/collections/Fields/number/components/client/Label'
|
||||
import { CustomNumberFieldClient as CustomNumberFieldClient_21 } from '@/collections/Fields/number/components/client/Field'
|
||||
import { CustomPointFieldLabelServer as CustomPointFieldLabelServer_22 } from '@/collections/Fields/point/components/server/Label'
|
||||
import { CustomPointFieldServer as CustomPointFieldServer_23 } from '@/collections/Fields/point/components/server/Field'
|
||||
import { CustomPointFieldLabelClient as CustomPointFieldLabelClient_24 } from '@/collections/Fields/point/components/client/Label'
|
||||
import { CustomPointFieldClient as CustomPointFieldClient_25 } from '@/collections/Fields/point/components/client/Field'
|
||||
import { CustomRadioFieldLabelServer as CustomRadioFieldLabelServer_26 } from '@/collections/Fields/radio/components/server/Label'
|
||||
import { CustomRadioFieldServer as CustomRadioFieldServer_27 } from '@/collections/Fields/radio/components/server/Field'
|
||||
import { CustomRadioFieldLabelClient as CustomRadioFieldLabelClient_28 } from '@/collections/Fields/radio/components/client/Label'
|
||||
import { CustomRadioFieldClient as CustomRadioFieldClient_29 } from '@/collections/Fields/radio/components/client/Field'
|
||||
import { CustomRelationshipFieldLabelServer as CustomRelationshipFieldLabelServer_30 } from '@/collections/Fields/relationship/components/server/Label'
|
||||
import { CustomRelationshipFieldServer as CustomRelationshipFieldServer_31 } from '@/collections/Fields/relationship/components/server/Field'
|
||||
import { CustomRelationshipFieldLabelClient as CustomRelationshipFieldLabelClient_32 } from '@/collections/Fields/relationship/components/client/Label'
|
||||
import { CustomRelationshipFieldClient as CustomRelationshipFieldClient_33 } from '@/collections/Fields/relationship/components/client/Field'
|
||||
import { CustomSelectFieldLabelServer as CustomSelectFieldLabelServer_34 } from '@/collections/Fields/select/components/server/Label'
|
||||
import { CustomSelectFieldServer as CustomSelectFieldServer_35 } from '@/collections/Fields/select/components/server/Field'
|
||||
import { CustomSelectFieldLabelClient as CustomSelectFieldLabelClient_36 } from '@/collections/Fields/select/components/client/Label'
|
||||
import { CustomSelectFieldClient as CustomSelectFieldClient_37 } from '@/collections/Fields/select/components/client/Field'
|
||||
import { CustomTextFieldLabelServer as CustomTextFieldLabelServer_38 } from '@/collections/Fields/text/components/server/Label'
|
||||
import { CustomTextFieldServer as CustomTextFieldServer_39 } from '@/collections/Fields/text/components/server/Field'
|
||||
import { CustomTextFieldLabelClient as CustomTextFieldLabelClient_40 } from '@/collections/Fields/text/components/client/Label'
|
||||
import { CustomTextFieldClient as CustomTextFieldClient_41 } from '@/collections/Fields/text/components/client/Field'
|
||||
import { CustomTextareaFieldLabelServer as CustomTextareaFieldLabelServer_42 } from '@/collections/Fields/textarea/components/server/Label'
|
||||
import { CustomTextareaFieldServer as CustomTextareaFieldServer_43 } from '@/collections/Fields/textarea/components/server/Field'
|
||||
import { CustomTextareaFieldLabelClient as CustomTextareaFieldLabelClient_44 } from '@/collections/Fields/textarea/components/client/Label'
|
||||
import { CustomTextareaFieldClient as CustomTextareaFieldClient_45 } from '@/collections/Fields/textarea/components/client/Field'
|
||||
import { CustomTabEditView as CustomTabEditView_46 } from '@/collections/Views/components/CustomTabEditView'
|
||||
import { CustomDefaultEditView as CustomDefaultEditView_47 } from '@/collections/Views/components/CustomDefaultEditView'
|
||||
import { CustomRootEditView as CustomRootEditView_48 } from '@/collections/RootViews/components/CustomRootEditView'
|
||||
import { LinkToCustomView as LinkToCustomView_49 } from '@/components/afterNavLinks/LinkToCustomView'
|
||||
import { LinkToCustomMinimalView as LinkToCustomMinimalView_50 } from '@/components/afterNavLinks/LinkToCustomMinimalView'
|
||||
import { LinkToCustomDefaultView as LinkToCustomDefaultView_51 } from '@/components/afterNavLinks/LinkToCustomDefaultView'
|
||||
import { CustomRootView as CustomRootView_52 } from '@/components/views/CustomRootView'
|
||||
import { CustomDefaultRootView as CustomDefaultRootView_53 } from '@/components/views/CustomDefaultRootView'
|
||||
import { CustomMinimalRootView as CustomMinimalRootView_54 } from '@/components/views/CustomMinimalRootView'
|
||||
|
||||
export const importMap = {
|
||||
"@/collections/Fields/array/components/server/Label#CustomArrayFieldLabelServer": CustomArrayFieldLabelServer_0,
|
||||
"@/collections/Fields/array/components/server/Field#CustomArrayFieldServer": CustomArrayFieldServer_1,
|
||||
"@/collections/Fields/array/components/client/Label#CustomArrayFieldLabelClient": CustomArrayFieldLabelClient_2,
|
||||
"@/collections/Fields/array/components/client/Field#CustomArrayFieldClient": CustomArrayFieldClient_3,
|
||||
"@/collections/Fields/blocks/components/server/Field#CustomBlocksFieldServer": CustomBlocksFieldServer_4,
|
||||
"@/collections/Fields/blocks/components/client/Field#CustomBlocksFieldClient": CustomBlocksFieldClient_5,
|
||||
"@/collections/Fields/checkbox/components/server/Label#CustomCheckboxFieldLabelServer": CustomCheckboxFieldLabelServer_6,
|
||||
"@/collections/Fields/checkbox/components/server/Field#CustomCheckboxFieldServer": CustomCheckboxFieldServer_7,
|
||||
"@/collections/Fields/checkbox/components/client/Label#CustomCheckboxFieldLabelClient": CustomCheckboxFieldLabelClient_8,
|
||||
"@/collections/Fields/checkbox/components/client/Field#CustomCheckboxFieldClient": CustomCheckboxFieldClient_9,
|
||||
"@/collections/Fields/date/components/server/Label#CustomDateFieldLabelServer": CustomDateFieldLabelServer_10,
|
||||
"@/collections/Fields/date/components/server/Field#CustomDateFieldServer": CustomDateFieldServer_11,
|
||||
"@/collections/Fields/date/components/client/Label#CustomDateFieldLabelClient": CustomDateFieldLabelClient_12,
|
||||
"@/collections/Fields/date/components/client/Field#CustomDateFieldClient": CustomDateFieldClient_13,
|
||||
"@/collections/Fields/email/components/server/Label#CustomEmailFieldLabelServer": CustomEmailFieldLabelServer_14,
|
||||
"@/collections/Fields/email/components/server/Field#CustomEmailFieldServer": CustomEmailFieldServer_15,
|
||||
"@/collections/Fields/email/components/client/Label#CustomEmailFieldLabelClient": CustomEmailFieldLabelClient_16,
|
||||
"@/collections/Fields/email/components/client/Field#CustomEmailFieldClient": CustomEmailFieldClient_17,
|
||||
"@/collections/Fields/number/components/server/Label#CustomNumberFieldLabelServer": CustomNumberFieldLabelServer_18,
|
||||
"@/collections/Fields/number/components/server/Field#CustomNumberFieldServer": CustomNumberFieldServer_19,
|
||||
"@/collections/Fields/number/components/client/Label#CustomNumberFieldLabelClient": CustomNumberFieldLabelClient_20,
|
||||
"@/collections/Fields/number/components/client/Field#CustomNumberFieldClient": CustomNumberFieldClient_21,
|
||||
"@/collections/Fields/point/components/server/Label#CustomPointFieldLabelServer": CustomPointFieldLabelServer_22,
|
||||
"@/collections/Fields/point/components/server/Field#CustomPointFieldServer": CustomPointFieldServer_23,
|
||||
"@/collections/Fields/point/components/client/Label#CustomPointFieldLabelClient": CustomPointFieldLabelClient_24,
|
||||
"@/collections/Fields/point/components/client/Field#CustomPointFieldClient": CustomPointFieldClient_25,
|
||||
"@/collections/Fields/radio/components/server/Label#CustomRadioFieldLabelServer": CustomRadioFieldLabelServer_26,
|
||||
"@/collections/Fields/radio/components/server/Field#CustomRadioFieldServer": CustomRadioFieldServer_27,
|
||||
"@/collections/Fields/radio/components/client/Label#CustomRadioFieldLabelClient": CustomRadioFieldLabelClient_28,
|
||||
"@/collections/Fields/radio/components/client/Field#CustomRadioFieldClient": CustomRadioFieldClient_29,
|
||||
"@/collections/Fields/relationship/components/server/Label#CustomRelationshipFieldLabelServer": CustomRelationshipFieldLabelServer_30,
|
||||
"@/collections/Fields/relationship/components/server/Field#CustomRelationshipFieldServer": CustomRelationshipFieldServer_31,
|
||||
"@/collections/Fields/relationship/components/client/Label#CustomRelationshipFieldLabelClient": CustomRelationshipFieldLabelClient_32,
|
||||
"@/collections/Fields/relationship/components/client/Field#CustomRelationshipFieldClient": CustomRelationshipFieldClient_33,
|
||||
"@/collections/Fields/select/components/server/Label#CustomSelectFieldLabelServer": CustomSelectFieldLabelServer_34,
|
||||
"@/collections/Fields/select/components/server/Field#CustomSelectFieldServer": CustomSelectFieldServer_35,
|
||||
"@/collections/Fields/select/components/client/Label#CustomSelectFieldLabelClient": CustomSelectFieldLabelClient_36,
|
||||
"@/collections/Fields/select/components/client/Field#CustomSelectFieldClient": CustomSelectFieldClient_37,
|
||||
"@/collections/Fields/text/components/server/Label#CustomTextFieldLabelServer": CustomTextFieldLabelServer_38,
|
||||
"@/collections/Fields/text/components/server/Field#CustomTextFieldServer": CustomTextFieldServer_39,
|
||||
"@/collections/Fields/text/components/client/Label#CustomTextFieldLabelClient": CustomTextFieldLabelClient_40,
|
||||
"@/collections/Fields/text/components/client/Field#CustomTextFieldClient": CustomTextFieldClient_41,
|
||||
"@/collections/Fields/textarea/components/server/Label#CustomTextareaFieldLabelServer": CustomTextareaFieldLabelServer_42,
|
||||
"@/collections/Fields/textarea/components/server/Field#CustomTextareaFieldServer": CustomTextareaFieldServer_43,
|
||||
"@/collections/Fields/textarea/components/client/Label#CustomTextareaFieldLabelClient": CustomTextareaFieldLabelClient_44,
|
||||
"@/collections/Fields/textarea/components/client/Field#CustomTextareaFieldClient": CustomTextareaFieldClient_45,
|
||||
"@/collections/Views/components/CustomTabEditView#CustomTabEditView": CustomTabEditView_46,
|
||||
"@/collections/Views/components/CustomDefaultEditView#CustomDefaultEditView": CustomDefaultEditView_47,
|
||||
"@/collections/RootViews/components/CustomRootEditView#CustomRootEditView": CustomRootEditView_48,
|
||||
"@/components/afterNavLinks/LinkToCustomView#LinkToCustomView": LinkToCustomView_49,
|
||||
"@/components/afterNavLinks/LinkToCustomMinimalView#LinkToCustomMinimalView": LinkToCustomMinimalView_50,
|
||||
"@/components/afterNavLinks/LinkToCustomDefaultView#LinkToCustomDefaultView": LinkToCustomDefaultView_51,
|
||||
"@/components/views/CustomRootView#CustomRootView": CustomRootView_52,
|
||||
"@/components/views/CustomDefaultRootView#CustomDefaultRootView": CustomDefaultRootView_53,
|
||||
"@/components/views/CustomMinimalRootView#CustomMinimalRootView": CustomMinimalRootView_54
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = REST_GET(config)
|
||||
export const POST = REST_POST(config)
|
||||
export const DELETE = REST_DELETE(config)
|
||||
export const PATCH = REST_PATCH(config)
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
||||
@@ -0,0 +1,6 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = GRAPHQL_PLAYGROUND_GET(config)
|
||||
@@ -0,0 +1,6 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_POST } from '@payloadcms/next/routes'
|
||||
|
||||
export const POST = GRAPHQL_POST(config)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user